const types = @import("types.zig"); const Mirroring = types.Mirroring; const Mmc5 = @This(); prg_mode: u2 = 3, chr_mode: u2 = 0, prg_banks: [4]u8 = [_]u8{ 0, 0, 1, 2 }, // $6000, $8000, $A000, $C000 chr_banks: [12]u16 = [_]u16{0} ** 12, mult_a: u8 = 0, mult_b: u8 = 0, irq_scanline: u8 = 0, irq_enabled: bool = false, irq_assert: bool = false, irq_in_frame: bool = false, current_scanline: u8 = 0, mirroring_mode: Mirroring = .vertical, ex_ram: [1024]u8 = [_]u8{0} ** 1024, prg_bank_count: usize, chr_bank_count: usize, pub fn init( prg_size: usize, chr_size: usize, ) Mmc5 { return .{ .prg_bank_count = prg_size / 0x2000, .chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1, }; } pub fn cpuMapRead( self: *const Mmc5, address: u16, ) ?usize { if (address < 0x5c00) return null; if (address >= 0x5c00 and address <= 0x5fff) { return 0x80000000 | @as(usize, address - 0x5c00); } if (address < 0x8000) { const bank = @as(usize, self.prg_banks[0] & 0x7f) % self.prg_bank_count; return bank * 0x2000 + (address & 0x1fff); } const last_bank = self.prg_bank_count - 1; const offset = @as(usize, address & 0x1fff); switch ((address - 0x8000) / 0x2000) { 0 => return (@as(usize, self.prg_banks[1] & 0x7f) % self.prg_bank_count) * 0x2000 + offset, 1 => return (@as(usize, self.prg_banks[2] & 0x7f) % self.prg_bank_count) * 0x2000 + offset, 2 => return (@as(usize, self.prg_banks[3] & 0x7f) % self.prg_bank_count) * 0x2000 + offset, 3 => return last_bank * 0x2000 + offset, else => unreachable, } } pub fn cpuWrite( self: *Mmc5, address: u16, value: u8, ) void { if (address >= 0x5c00 and address <= 0x5fff) { self.ex_ram[address - 0x5c00] = value; return; } switch (address) { 0x5100 => self.prg_mode = @truncate(value & 3), 0x5101 => self.chr_mode = @truncate(value & 3), 0x5105 => { 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, } }, 0x5114 => self.prg_banks[0] = value, 0x5115 => self.prg_banks[1] = value, 0x5116 => self.prg_banks[2] = value, 0x5117 => self.prg_banks[3] = value, 0x5120...0x512b => |addr| { const idx = addr - 0x5120; self.chr_banks[idx] = value; }, 0x5203 => self.irq_scanline = value, 0x5204 => { self.irq_enabled = (value & 0x80) != 0; self.irq_assert = false; }, 0x5205 => self.mult_a = value, 0x5206 => self.mult_b = value, else => {}, } } pub fn readRegister( self: *Mmc5, address: u16, ) ?u8 { switch (address) { 0x5204 => { const res: u8 = (if (self.irq_assert) @as(u8, 0x80) else 0) | (if (self.irq_in_frame) @as(u8, 0x40) else 0); self.irq_assert = false; return res; }, 0x5205 => { const prod = @as(u16, self.mult_a) * @as(u16, self.mult_b); return @truncate(prod); }, 0x5206 => { const prod = @as(u16, self.mult_a) * @as(u16, self.mult_b); return @truncate(prod >> 8); }, else => return null, } } pub fn ppuMapRead( self: *const Mmc5, address: u16, ) ?usize { if (address >= 0x2000) return null; const slot = address / 0x0400; const offset = @as(usize, address & 0x03ff); const bank = @as(usize, self.chr_banks[slot]) % self.chr_bank_count; return bank * 0x0400 + offset; } pub fn ppuMapWrite( self: *const Mmc5, address: u16, ) ?usize { return self.ppuMapRead(address); } pub fn handleScanline( self: *Mmc5, ) void { self.current_scanline += 1; if (self.current_scanline == self.irq_scanline) { if (self.irq_enabled) { self.irq_assert = true; } } if (self.current_scanline >= 240) { self.current_scanline = 0; self.irq_in_frame = false; } } pub fn mirroring( self: *const Mmc5, ) Mirroring { return self.mirroring_mode; } pub fn irqAsserted( self: *const Mmc5, ) bool { return self.irq_assert; }