const std = @import("std"); const common = @import("../common.zig"); const Mirroring = common.Mirroring; // Mapper 28 (Action 53) pub const Action53 = @This(); selected_reg: u2 = 0, regs: [4]u8 = [_]u8{0} ** 4, mirroring_bit: u1 = 0, prg_page_0: usize = 0, prg_page_1: usize = 0xffff, // resolves to last 16KB bank at init mirroring_mode: Mirroring = .vertical, pub fn init(initial_mirroring: Mirroring) Action53 { return .{ .mirroring_mode = initial_mirroring, }; } pub fn cpuRead(self: *const Action53, address: u16, prg_rom: []const u8, prg_ram: []const u8) ?u8 { if (address >= 0x6000 and address <= 0x7fff) { const offset = address - 0x6000; if (offset < prg_ram.len) return prg_ram[offset]; return 0; } if (address >= 0x8000 and address <= 0xffff) { const total_16k = @max(1, prg_rom.len / 0x4000); const bank = if (address < 0xc000) self.prg_page_0 % total_16k else (if (self.prg_page_1 == 0xffff) total_16k - 1 else self.prg_page_1 % total_16k); const offset = (bank * 0x4000) + (address & 0x3fff); if (offset < prg_rom.len) return prg_rom[offset]; return 0; } return null; } pub fn cpuWrite(self: *Action53, address: u16, value: u8, prg_ram: []u8) bool { if (address >= 0x5000 and address <= 0x5fff) { self.selected_reg = @truncate(((value & 0x80) >> 6) | (value & 0x01)); return true; } if (address >= 0x6000 and address <= 0x7fff) { const offset = address - 0x6000; if (offset < prg_ram.len) { prg_ram[offset] = value; return true; } return false; } if (address >= 0x8000 and address <= 0xffff) { if (self.selected_reg <= 1) { self.mirroring_bit = @truncate((value >> 4) & 0x01); } else if (self.selected_reg == 2) { self.mirroring_bit = @truncate(value & 0x01); } self.regs[self.selected_reg] = value; self.updateState(); return true; } return false; } fn updateState(self: *Action53) void { var mirroring_val: u8 = self.regs[2] & 0x03; if ((mirroring_val & 0x02) == 0) { mirroring_val = self.mirroring_bit; } self.mirroring_mode = switch (mirroring_val) { 0 => .single_screen_lower, 1 => .single_screen_upper, 2 => .vertical, 3 => .horizontal, else => unreachable, }; const game_size: usize = (self.regs[2] & 0x30) >> 4; const prg_size: usize = (self.regs[2] & 0x08) >> 3; const slot_select: usize = (self.regs[2] & 0x04) >> 2; var prg_select: usize = self.regs[1] & 0x0f; const outer_prg_select: usize = @as(usize, self.regs[3]) << 1; if (prg_size != 0) { const outer_mask = [_]usize{ 0x1fe, 0x1fc, 0x1f8, 0x1f0 }; const inner_mask = [_]usize{ 0x01, 0x03, 0x07, 0x0f }; const switchable = (outer_prg_select & outer_mask[game_size]) | (prg_select & inner_mask[game_size]); const fixed = (outer_prg_select & 0x1fe) | slot_select; if (slot_select != 0) { self.prg_page_0 = switchable; self.prg_page_1 = fixed; } else { self.prg_page_0 = fixed; self.prg_page_1 = switchable; } } else { prg_select <<= 1; const outer_and = [_]usize{ 0x1fe, 0x1fc, 0x1f8, 0x1f0 }; const inner_and = [_]usize{ 0x01, 0x03, 0x07, 0x0f }; self.prg_page_0 = (outer_prg_select & outer_and[game_size]) | (prg_select & inner_and[game_size]); self.prg_page_1 = (outer_prg_select & outer_and[game_size]) | ((prg_select | 1) & inner_and[game_size]); } } pub fn ppuRead(self: *const Action53, address: u16, chr_rom: []const u8, chr_ram: []const u8, chr_is_ram: bool) ?u8 { if (address < 0x2000) { const chr_select: usize = self.regs[0] & 0x03; if (chr_is_ram) { const total_8k = @max(1, chr_ram.len / 0x2000); const offset = ((chr_select % total_8k) * 0x2000) + address; if (offset < chr_ram.len) return chr_ram[offset]; } else if (chr_rom.len > 0) { const total_8k = @max(1, chr_rom.len / 0x2000); const offset = ((chr_select % total_8k) * 0x2000) + address; if (offset < chr_rom.len) return chr_rom[offset]; } return 0; } return null; } pub fn ppuWrite(self: *Action53, address: u16, value: u8, chr_ram: []u8, chr_is_ram: bool) bool { if (address < 0x2000 and chr_is_ram) { const chr_select: usize = self.regs[0] & 0x03; const total_8k = @max(1, chr_ram.len / 0x2000); const offset = ((chr_select % total_8k) * 0x2000) + address; if (offset < chr_ram.len) { chr_ram[offset] = value; return true; } } return false; } pub fn mirroring(self: *const Action53) Mirroring { return self.mirroring_mode; } pub fn irqLine(_: *const Action53) bool { return false; } pub fn notifyPpuAddress(_: *Action53, _: u16) void {} test "action53 register write" { var a = Action53.init(.vertical); var ram: [0x2000]u8 = undefined; _ = a.cpuWrite(0x5000, 0x80, &ram); // select supervisor reg 2 (mode) _ = a.cpuWrite(0x8000, 0x01, &ram); // 1-screen upper try std.testing.expectEqual(Mirroring.single_screen_upper, a.mirroring()); }