Files
6soz/src/system/nes/mapper/vrc4.zig
T

180 lines
4.8 KiB
Zig

const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Vrc4 = @This();
prg_banks: [2]u8 = [_]u8{ 0, 1 },
chr_banks_lo: [8]u4 = [_]u4{0} ** 8,
chr_banks_hi: [8]u5 = [_]u5{0} ** 8,
prg_mode: u1 = 0,
mirroring_mode: Mirroring = .vertical,
irq_reload: u8 = 0,
irq_counter: u8 = 0,
irq_enabled: bool = false,
irq_enable_on_ack: bool = false,
irq_mode: u1 = 0, // 0 = cycle, 1 = scanline
irq_prescaler: i16 = 341,
irq_assert: bool = false,
shift_a: u3,
shift_b: u3,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
mapper_id: u8,
) Vrc4 {
const shift_a: u3 = if (mapper_id == 21) 1 else 0;
const shift_b: u3 = if (mapper_id == 21) 2 else 2;
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1,
.shift_a = shift_a,
.shift_b = shift_b,
};
}
pub fn cpuMapRead(
self: *const Vrc4,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address & 0x1fff);
const last_bank = self.prg_bank_count - 1;
const second_last_bank = self.prg_bank_count - 2;
const r0 = @as(usize, self.prg_banks[0] & 0x1f) % self.prg_bank_count;
const r1 = @as(usize, self.prg_banks[1] & 0x1f) % self.prg_bank_count;
const bank_idx = (address - 0x8000) / 0x2000;
var bank: usize = 0;
if (self.prg_mode == 0) {
switch (bank_idx) {
0 => bank = r0,
1 => bank = r1,
2 => bank = second_last_bank,
3 => bank = last_bank,
else => unreachable,
}
} else {
switch (bank_idx) {
0 => bank = second_last_bank,
1 => bank = r1,
2 => bank = r0,
3 => bank = last_bank,
else => unreachable,
}
}
return bank * 0x2000 + offset;
}
pub fn cpuWrite(
self: *Vrc4,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
const reg_addr = address & 0xf000;
const a = @as(u1, @truncate((address >> self.shift_a) & 1));
const b = @as(u1, @truncate((address >> self.shift_b) & 1));
const line = (@as(u2, b) << 1) | a;
switch (reg_addr) {
0x8000 => {
self.prg_banks[0] = value & 0x1f;
},
0x9000 => {
if (line == 0) {
switch (@as(u2, @truncate(value & 3))) {
0 => self.mirroring_mode = .vertical,
1 => self.mirroring_mode = .horizontal,
2 => self.mirroring_mode = .single_screen_lower,
3 => self.mirroring_mode = .single_screen_upper,
}
} else if (line == 1 or line == 2) {
self.prg_mode = @truncate((value >> 1) & 1);
}
},
0xa000 => {
self.prg_banks[1] = value & 0x1f;
},
0xb000, 0xc000, 0xd000, 0xe000 => {
const slot_base = ((reg_addr - 0xb000) / 0x1000) * 2;
const is_hi = (line & 2) != 0;
const is_odd = (line & 1) != 0;
const slot = slot_base + (if (is_odd) @as(usize, 1) else 0);
if (!is_hi) {
self.chr_banks_lo[slot] = @truncate(value & 0x0f);
} else {
self.chr_banks_hi[slot] = @truncate(value & 0x1f);
}
},
0xf000 => {
switch (line) {
0 => self.irq_reload = (self.irq_reload & 0xf0) | (value & 0x0f),
1 => self.irq_reload = (self.irq_reload & 0x0f) | ((value & 0x0f) << 4),
2 => {
self.irq_mode = @truncate((value >> 2) & 1);
self.irq_enabled = (value & 0x02) != 0;
self.irq_enable_on_ack = (value & 0x01) != 0;
if (self.irq_enabled) {
self.irq_counter = self.irq_reload;
self.irq_prescaler = 341;
}
self.irq_assert = false;
},
3 => {
self.irq_enabled = self.irq_enable_on_ack;
self.irq_assert = false;
},
}
},
else => {},
}
}
pub fn ppuMapRead(
self: *const Vrc4,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const slot = address / 0x0400;
const offset = @as(usize, address & 0x03ff);
const bank_val = (@as(usize, self.chr_banks_hi[slot]) << 4) | @as(usize, self.chr_banks_lo[slot]);
const bank = bank_val % self.chr_bank_count;
return bank * 0x0400 + offset;
}
pub fn ppuMapWrite(
self: *const Vrc4,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Vrc4,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(
self: *const Vrc4,
) bool {
return self.irq_assert;
}