add nes cartridge subsystem and 41 ines mapper variants

This commit is contained in:
2026-08-14 06:48:16 +02:00
parent 42f20478bd
commit 04fb7bcc1f
41 changed files with 5042 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+64
View File
@@ -0,0 +1,64 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Axrom = @This();
prg_bank_select: u8 = 0,
mirroring_select: u1 = 0,
prg_bank_count: usize,
pub fn init(
prg_size: usize,
) Axrom {
return .{
.prg_bank_count = prg_size / 0x8000,
};
}
pub fn cpuMapRead(
self: *const Axrom,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank_select & 0x07) % self.prg_bank_count;
return bank * 0x8000 + offset;
}
pub fn cpuWrite(
self: *Axrom,
address: u16,
value: u8,
) void {
if (address >= 0x8000) {
self.prg_bank_select = value & 0x07;
self.mirroring_select = @truncate((value >> 4) & 1);
}
}
pub fn ppuMapRead(
_: *const Axrom,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn ppuMapWrite(
_: *const Axrom,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Axrom,
) Mirroring {
return if (self.mirroring_select == 0) .single_screen_lower else .single_screen_upper;
}
pub fn irqAsserted(_: *const Axrom) bool {
return false;
}
+82
View File
@@ -0,0 +1,82 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Camerica = @This();
prg_bank: u8 = 0,
mirroring_select: u1 = 0,
mirroring_mode: Mirroring,
has_single_screen_mirroring: bool = false,
prg_bank_count: usize,
pub fn init(
prg_size: usize,
_mirroring: Mirroring,
) Camerica {
return .{
.prg_bank_count = prg_size / 0x4000,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Camerica,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xC000) {
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
const offset = @as(usize, address - 0xC000);
const fixed_bank = if (self.prg_bank_count > 0) self.prg_bank_count - 1 else 0;
return fixed_bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Camerica,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
if (address >= 0x8000 and address <= 0x9FFF) {
self.has_single_screen_mirroring = true;
self.mirroring_select = @truncate((value >> 4) & 1);
} else if (address >= 0xC000) {
self.prg_bank = value & 0x0F;
}
}
pub fn ppuMapRead(
_: *const Camerica,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn ppuMapWrite(
_: *const Camerica,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Camerica,
) Mirroring {
if (self.has_single_screen_mirroring) {
return if (self.mirroring_select == 0) .single_screen_lower else .single_screen_upper;
}
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Camerica) bool {
return false;
}
+69
View File
@@ -0,0 +1,69 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Cnrom = @This();
prg_16k: bool,
chr_bank_select: u8 = 0,
chr_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
_mirroring: Mirroring,
) Cnrom {
return .{
.prg_16k = prg_size == 0x4000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x2000 else 1,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Cnrom,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address - 0x8000);
return if (self.prg_16k) offset & 0x3fff else offset;
}
pub fn cpuWrite(
self: *Cnrom,
address: u16,
value: u8,
) void {
if (address >= 0x8000) {
self.chr_bank_select = value;
}
}
pub fn ppuMapRead(
self: *const Cnrom,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank = @as(usize, self.chr_bank_select) % self.chr_bank_count;
return bank * 0x2000 + @as(usize, address);
}
pub fn ppuMapWrite(
_: *const Cnrom,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Cnrom,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Cnrom) bool {
return false;
}
+73
View File
@@ -0,0 +1,73 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const ColorDreams = @This();
prg_bank: u8 = 0,
chr_bank: u8 = 0,
prg_bank_count: usize,
chr_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
_mirroring: Mirroring,
) ColorDreams {
return .{
.prg_bank_count = prg_size / 0x8000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x2000 else 1,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const ColorDreams,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x8000 + offset;
}
pub fn cpuWrite(
self: *ColorDreams,
address: u16,
value: u8,
) void {
if (address >= 0x8000) {
self.prg_bank = value & 0x03;
self.chr_bank = (value >> 4) & 0x0F;
}
}
pub fn ppuMapRead(
self: *const ColorDreams,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank = @as(usize, self.chr_bank) % self.chr_bank_count;
return bank * 0x2000 + @as(usize, address);
}
pub fn ppuMapWrite(
_: *const ColorDreams,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const ColorDreams,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const ColorDreams) bool {
return false;
}
+73
View File
@@ -0,0 +1,73 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Gxrom = @This();
prg_bank: u8 = 0,
chr_bank: u8 = 0,
prg_bank_count: usize,
chr_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
_mirroring: Mirroring,
) Gxrom {
return .{
.prg_bank_count = prg_size / 0x8000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x2000 else 1,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Gxrom,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x8000 + offset;
}
pub fn cpuWrite(
self: *Gxrom,
address: u16,
value: u8,
) void {
if (address >= 0x8000) {
self.chr_bank = value & 0x03;
self.prg_bank = (value >> 4) & 0x03;
}
}
pub fn ppuMapRead(
self: *const Gxrom,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank = @as(usize, self.chr_bank) % self.chr_bank_count;
return bank * 0x2000 + @as(usize, address);
}
pub fn ppuMapWrite(
_: *const Gxrom,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Gxrom,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Gxrom) bool {
return false;
}
+78
View File
@@ -0,0 +1,78 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Irem78 = @This();
prg_bank: u8 = 0,
chr_bank: u8 = 0,
mirroring_select: u1 = 0,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Irem78 {
return .{
.prg_bank_count = prg_size / 0x4000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x2000 else 1,
};
}
pub fn cpuMapRead(
self: *const Irem78,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xC000) {
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
const offset = @as(usize, address - 0xC000);
const fixed_bank = if (self.prg_bank_count > 0) self.prg_bank_count - 1 else 0;
return fixed_bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Irem78,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
self.prg_bank = value & 0x07;
self.mirroring_select = @truncate((value >> 3) & 1);
self.chr_bank = (value >> 4) & 0x0F;
}
pub fn ppuMapRead(
self: *const Irem78,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank = @as(usize, self.chr_bank) % self.chr_bank_count;
return bank * 0x2000 + @as(usize, address);
}
pub fn ppuMapWrite(
_: *const Irem78,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Irem78,
) Mirroring {
return if (self.mirroring_select == 0) .single_screen_lower else .single_screen_upper;
}
pub fn irqAsserted(_: *const Irem78) bool {
return false;
}
+69
View File
@@ -0,0 +1,69 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mmc3 = @import("mmc3.zig");
const Mapper118 = @This();
mmc3: Mmc3,
mirror_bit: u1 = 0,
pub fn init(
prg_size: usize,
chr_size: usize,
) Mapper118 {
return .{
.mmc3 = Mmc3.init(prg_size, chr_size),
};
}
pub fn cpuMapRead(
self: *const Mapper118,
address: u16,
) ?usize {
return self.mmc3.cpuMapRead(address);
}
pub fn cpuWrite(
self: *Mapper118,
address: u16,
value: u8,
) void {
if (address == 0x8001) {
if (self.mmc3.bank_select <= 5) {
self.mirror_bit = @truncate((value >> 7) & 1);
}
}
self.mmc3.cpuWrite(address, value);
}
pub fn ppuMapRead(
self: *Mapper118,
address: u16,
) ?usize {
return self.mmc3.ppuMapRead(address);
}
pub fn ppuMapWrite(
self: *const Mapper118,
address: u16,
) ?usize {
return self.mmc3.ppuMapWrite(address);
}
pub fn mirroring(
self: *const Mapper118,
) Mirroring {
return if (self.mirror_bit == 1) .single_screen_upper else .single_screen_lower;
}
pub fn irqAsserted(
self: *const Mapper118,
) bool {
return self.mmc3.irqAsserted();
}
pub fn handleScanline(
self: *Mapper118,
) void {
self.mmc3.handleScanline();
}
+113
View File
@@ -0,0 +1,113 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mmc3 = @import("mmc3.zig");
const Mapper119 = @This();
mmc3: Mmc3,
pub fn init(
prg_size: usize,
chr_size: usize,
) Mapper119 {
return .{
.mmc3 = Mmc3.init(prg_size, chr_size),
};
}
pub fn cpuMapRead(
self: *const Mapper119,
address: u16,
) ?usize {
return self.mmc3.cpuMapRead(address);
}
pub fn cpuWrite(
self: *Mapper119,
address: u16,
value: u8,
) void {
self.mmc3.cpuWrite(address, value);
}
pub fn ppuMapRead(
self: *Mapper119,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank_val = self.getChrBankValue(address);
const offset = (address % 0x0400);
if ((bank_val & 0x40) != 0) {
// Bit 6 is set -> Route to CHR RAM
const ram_bank = @as(usize, bank_val & 7);
return 0x80000000 | (ram_bank * 0x0400 + offset);
} else {
// Route to CHR ROM
return self.mmc3.ppuMapRead(address);
}
}
pub fn ppuMapWrite(
self: *const Mapper119,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank_val = self.getChrBankValue(address);
const offset = (address % 0x0400);
if ((bank_val & 0x40) != 0) {
// Bit 6 is set -> Route write to CHR RAM
const ram_bank = @as(usize, bank_val & 7);
return 0x80000000 | (ram_bank * 0x0400 + offset);
} else {
return self.mmc3.ppuMapWrite(address);
}
}
fn getChrBankValue(self: *const Mapper119, address: u16) u8 {
const chr_mode = (self.mmc3.bank_select & 0x80) != 0;
const addr_k = address / 0x0400;
if (!chr_mode) {
return switch (addr_k) {
0, 1 => (self.mmc3.bank_registers[0] & 0xfe) + @as(u8, @intCast(addr_k & 1)),
2, 3 => (self.mmc3.bank_registers[1] & 0xfe) + @as(u8, @intCast(addr_k & 1)),
4 => self.mmc3.bank_registers[2],
5 => self.mmc3.bank_registers[3],
6 => self.mmc3.bank_registers[4],
7 => self.mmc3.bank_registers[5],
else => unreachable,
};
} else {
return switch (addr_k) {
0 => self.mmc3.bank_registers[2],
1 => self.mmc3.bank_registers[3],
2 => self.mmc3.bank_registers[4],
3 => self.mmc3.bank_registers[5],
4, 5 => (self.mmc3.bank_registers[0] & 0xfe) + @as(u8, @intCast(addr_k & 1)),
6, 7 => (self.mmc3.bank_registers[1] & 0xfe) + @as(u8, @intCast(addr_k & 1)),
else => unreachable,
};
}
}
pub fn mirroring(
self: *const Mapper119,
) Mirroring {
return self.mmc3.mirroring();
}
pub fn irqAsserted(
self: *const Mapper119,
) bool {
return self.mmc3.irqAsserted();
}
pub fn handleScanline(
self: *Mapper119,
) void {
self.mmc3.handleScanline();
}
+69
View File
@@ -0,0 +1,69 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper13 = @This();
chr_bank: u8 = 0,
fixed_mirroring: Mirroring,
prg_mask: usize,
pub fn init(
prg_size: usize,
mirroring_mode: Mirroring,
) Mapper13 {
return .{
.fixed_mirroring = mirroring_mode,
.prg_mask = if (prg_size > 0) prg_size - 1 else 0x7fff,
};
}
pub fn cpuMapRead(
self: *const Mapper13,
address: u16,
) ?usize {
if (address < 0x8000) return null;
return (@as(usize, address) - 0x8000) & self.prg_mask;
}
pub fn cpuWrite(
self: *Mapper13,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
self.chr_bank = value & 0x03;
}
pub fn ppuMapRead(
self: *const Mapper13,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
if (address < 0x1000) {
return 0x80000000 | @as(usize, address);
} else {
const bank = @as(usize, self.chr_bank & 3);
const offset = @as(usize, address & 0x0fff);
return 0x80000000 | (bank * 0x1000 + offset);
}
}
pub fn ppuMapWrite(
self: *const Mapper13,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Mapper13,
) Mirroring {
return self.fixed_mirroring;
}
pub fn irqAsserted(
_: *const Mapper13,
) bool {
return false;
}
+73
View File
@@ -0,0 +1,73 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper140 = @This();
prg_bank: u8 = 0,
chr_bank: u8 = 0,
prg_bank_count: usize,
chr_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
_mirroring: Mirroring,
) Mapper140 {
return .{
.prg_bank_count = prg_size / 0x8000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x2000 else 1,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Mapper140,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x8000 + offset;
}
pub fn cpuWrite(
self: *Mapper140,
address: u16,
value: u8,
) void {
if (address >= 0x6000 and address <= 0x7FFF) {
self.prg_bank = (value >> 4) & 0x03;
self.chr_bank = value & 0x0F;
}
}
pub fn ppuMapRead(
self: *const Mapper140,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank = @as(usize, self.chr_bank) % self.chr_bank_count;
return bank * 0x2000 + @as(usize, address);
}
pub fn ppuMapWrite(
_: *const Mapper140,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mapper140,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mapper140) bool {
return false;
}
+70
View File
@@ -0,0 +1,70 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper180 = @This();
prg_bank: u8 = 0,
prg_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
_mirroring: Mirroring,
) Mapper180 {
return .{
.prg_bank_count = prg_size / 0x4000,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Mapper180,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xC000) {
const offset = @as(usize, address - 0x8000);
return offset; // Fixed bank 0
} else {
const offset = @as(usize, address - 0xC000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Mapper180,
address: u16,
value: u8,
) void {
if (address >= 0x8000) {
self.prg_bank = value & 0x07;
}
}
pub fn ppuMapRead(
_: *const Mapper180,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn ppuMapWrite(
_: *const Mapper180,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mapper180,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mapper180) bool {
return false;
}
+108
View File
@@ -0,0 +1,108 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper206 = @This();
bank_select: u3 = 0,
prg_bank0: u8 = 0,
prg_bank1: u8 = 1,
chr_banks: [6]u8 = [_]u8{ 0, 2, 4, 5, 6, 7 },
prg_bank_count: usize,
chr_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
_mirroring: Mirroring,
) Mapper206 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Mapper206,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xA000) {
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank0) % self.prg_bank_count;
return bank * 0x2000 + offset;
} else if (address < 0xC000) {
const offset = @as(usize, address - 0xA000);
const bank = @as(usize, self.prg_bank1) % self.prg_bank_count;
return bank * 0x2000 + offset;
} else if (address < 0xE000) {
const offset = @as(usize, address - 0xC000);
const bank = if (self.prg_bank_count >= 2) self.prg_bank_count - 2 else 0;
return bank * 0x2000 + offset;
} else {
const offset = @as(usize, address - 0xE000);
const bank = if (self.prg_bank_count >= 1) self.prg_bank_count - 1 else 0;
return bank * 0x2000 + offset;
}
}
pub fn cpuWrite(
self: *Mapper206,
address: u16,
value: u8,
) void {
if (address >= 0x8000 and address <= 0x9FFF) {
if ((address & 1) == 0) {
self.bank_select = @truncate(value & 0x07);
} else {
switch (self.bank_select) {
0...5 => |i| self.chr_banks[i] = value & 0x3F,
6 => self.prg_bank0 = value & 0x0F,
7 => self.prg_bank1 = value & 0x0F,
}
}
}
}
pub fn ppuMapRead(
self: *const Mapper206,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
if (address < 0x0800) {
const bank = (@as(usize, self.chr_banks[0] & 0xFE)) % self.chr_bank_count;
return bank * 0x0400 + @as(usize, address);
} else if (address < 0x1000) {
const bank = (@as(usize, self.chr_banks[1] & 0xFE)) % self.chr_bank_count;
return bank * 0x0400 + @as(usize, address - 0x0800);
} else {
const idx = 2 + (address - 0x1000) / 0x0400;
const offset = (address - 0x1000) % 0x0400;
const bank = @as(usize, self.chr_banks[idx]) % self.chr_bank_count;
return bank * 0x0400 + @as(usize, offset);
}
}
pub fn ppuMapWrite(
_: *const Mapper206,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mapper206,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mapper206) bool {
return false;
}
+105
View File
@@ -0,0 +1,105 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper210 = @This();
prg_bank0: u8 = 0,
prg_bank1: u8 = 0,
prg_bank2: u8 = 0,
chr_banks: [8]u8 = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7 },
mirroring_mode: Mirroring = .vertical,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Mapper210 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1,
};
}
pub fn cpuMapRead(
self: *const Mapper210,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xA000) {
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank0) % self.prg_bank_count;
return bank * 0x2000 + offset;
} else if (address < 0xC000) {
const offset = @as(usize, address - 0xA000);
const bank = @as(usize, self.prg_bank1) % self.prg_bank_count;
return bank * 0x2000 + offset;
} else if (address < 0xE000) {
const offset = @as(usize, address - 0xC000);
const bank = @as(usize, self.prg_bank2) % self.prg_bank_count;
return bank * 0x2000 + offset;
} else {
const offset = @as(usize, address - 0xE000);
const fixed_bank = if (self.prg_bank_count > 0) self.prg_bank_count - 1 else 0;
return fixed_bank * 0x2000 + offset;
}
}
pub fn cpuWrite(
self: *Mapper210,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
if (address >= 0x8000 and address <= 0xBFFF) {
const index = (address - 0x8000) / 0x0800;
self.chr_banks[index] = value;
} else if (address >= 0xC000 and address <= 0xC7FF) {
self.prg_bank0 = value & 0x3F;
} else if (address >= 0xC800 and address <= 0xCFFF) {
self.prg_bank1 = value & 0x3F;
} else if (address >= 0xD000 and address <= 0xD7FF) {
self.prg_bank2 = value & 0x3F;
} else if (address >= 0xE000 and address <= 0xE7FF) {
self.mirroring_mode = switch (@as(u2, @truncate(value >> 6))) {
0 => .vertical,
1 => .horizontal,
2 => .single_screen_lower,
3 => .single_screen_upper,
};
}
}
pub fn ppuMapRead(
self: *const Mapper210,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank_idx = address / 0x0400;
const offset = address % 0x0400;
const bank = @as(usize, self.chr_banks[bank_idx]) % self.chr_bank_count;
return bank * 0x0400 + offset;
}
pub fn ppuMapWrite(
_: *const Mapper210,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mapper210,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mapper210) bool {
return false;
}
+77
View File
@@ -0,0 +1,77 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper232 = @This();
outer_block: u8 = 0,
inner_bank: u8 = 0,
prg_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
_mirroring: Mirroring,
) Mapper232 {
return .{
.prg_bank_count = prg_size / 0x4000,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Mapper232,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const block_start = (@as(usize, self.outer_block) * 4) % self.prg_bank_count;
if (address < 0xC000) {
const offset = @as(usize, address - 0x8000);
const bank = (block_start + (@as(usize, self.inner_bank) & 3)) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
const offset = @as(usize, address - 0xC000);
const bank = (block_start + 3) % self.prg_bank_count;
return bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Mapper232,
address: u16,
value: u8,
) void {
if (address >= 0x8000 and address <= 0x9FFF) {
self.outer_block = (value >> 3) & 0x03;
} else if (address >= 0xA000) {
self.inner_bank = value & 0x03;
}
}
pub fn ppuMapRead(
_: *const Mapper232,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn ppuMapWrite(
_: *const Mapper232,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mapper232,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mapper232) bool {
return false;
}
+92
View File
@@ -0,0 +1,92 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper34 = @This();
prg_bank: u8 = 0,
chr_bank0: u8 = 0,
chr_bank1: u8 = 1,
prg_bank_count: usize,
chr_bank_count: usize,
fixed_mirroring: Mirroring,
is_nina01: bool,
pub fn init(
prg_size: usize,
chr_size: usize,
mirroring_mode: Mirroring,
) Mapper34 {
return .{
.prg_bank_count = if (prg_size >= 0x8000) prg_size / 0x8000 else 1,
.chr_bank_count = if (chr_size > 0) chr_size / 0x1000 else 1,
.fixed_mirroring = mirroring_mode,
.is_nina01 = (chr_size > 0),
};
}
pub fn cpuMapRead(
self: *const Mapper34,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address & 0x7fff);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x8000 + offset;
}
pub fn cpuWrite(
self: *Mapper34,
address: u16,
value: u8,
) void {
if (self.is_nina01) {
switch (address) {
0x7ffd => self.prg_bank = value,
0x7ffe => self.chr_bank0 = value,
0x7fff => self.chr_bank1 = value,
else => {},
}
} else {
if (address >= 0x8000) {
self.prg_bank = value;
}
}
}
pub fn ppuMapRead(
self: *const Mapper34,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
if (self.is_nina01) {
const offset = @as(usize, address & 0x0fff);
const bank_raw = if (address < 0x1000) self.chr_bank0 else self.chr_bank1;
const bank = @as(usize, bank_raw) % self.chr_bank_count;
return bank * 0x1000 + offset;
} else {
return @as(usize, address);
}
}
pub fn ppuMapWrite(
self: *const Mapper34,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Mapper34,
) Mirroring {
return self.fixed_mirroring;
}
pub fn irqAsserted(
_: *const Mapper34,
) bool {
return false;
}
+118
View File
@@ -0,0 +1,118 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper65 = @This();
prg_banks: [3]u8 = [_]u8{ 0, 1, 2 },
chr_banks: [8]u8 = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7 },
mirroring_mode: Mirroring = .vertical,
irq_enabled: bool = false,
irq_counter: u16 = 0,
irq_reload: u16 = 0,
irq_assert: bool = false,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
mirroring_mode: Mirroring,
) Mapper65 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1,
.mirroring_mode = mirroring_mode,
};
}
pub fn cpuMapRead(
self: *const Mapper65,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address & 0x1fff);
const last_bank = self.prg_bank_count - 1;
switch ((address - 0x8000) / 0x2000) {
0 => return (@as(usize, self.prg_banks[0]) % self.prg_bank_count) * 0x2000 + offset,
1 => return (@as(usize, self.prg_banks[1]) % self.prg_bank_count) * 0x2000 + offset,
2 => return (@as(usize, self.prg_banks[2]) % self.prg_bank_count) * 0x2000 + offset,
3 => return last_bank * 0x2000 + offset,
else => unreachable,
}
}
pub fn cpuWrite(
self: *Mapper65,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
switch (address) {
0x8000 => self.prg_banks[0] = value,
0xa000 => self.prg_banks[1] = value,
0xc000 => self.prg_banks[2] = value,
0x9001 => self.mirroring_mode = if ((value & 0x80) != 0) .horizontal else .vertical,
0x9003 => {
self.irq_enabled = (value & 0x80) != 0;
self.irq_assert = false;
},
0x9004 => {
self.irq_reload = (self.irq_reload & 0xff00) | value;
},
0x9005 => {
self.irq_reload = (self.irq_reload & 0x00ff) | (@as(u16, value) << 8);
self.irq_counter = self.irq_reload;
self.irq_assert = false;
},
0xb000 => self.chr_banks[0] = value,
0xb001 => self.chr_banks[1] = value,
0xb002 => self.chr_banks[2] = value,
0xb003 => self.chr_banks[3] = value,
0xb004 => self.chr_banks[4] = value,
0xb005 => self.chr_banks[5] = value,
0xb006 => self.chr_banks[6] = value,
0xb007 => self.chr_banks[7] = value,
else => {},
}
}
pub fn ppuMapRead(
self: *const Mapper65,
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 Mapper65,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Mapper65,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(
self: *const Mapper65,
) bool {
return self.irq_assert;
}
+103
View File
@@ -0,0 +1,103 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper68 = @This();
chr_banks: [4]u8 = [_]u8{ 0, 0, 0, 0 },
nt_banks: [2]u8 = [_]u8{ 0, 0 },
prg_bank: u8 = 0,
nametable_control: u8 = 0,
prg_bank_count: usize,
chr_bank_count: usize,
fixed_mirroring: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
mirroring_mode: Mirroring,
) Mapper68 {
return .{
.prg_bank_count = prg_size / 0x4000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0800 else 1,
.fixed_mirroring = mirroring_mode,
};
}
pub fn cpuMapRead(
self: *const Mapper68,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address & 0x3fff);
const last_bank = self.prg_bank_count - 1;
if (address < 0xc000) {
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
return last_bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Mapper68,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
switch (address & 0xf000) {
0x8000 => self.chr_banks[0] = value,
0x9000 => self.chr_banks[1] = value,
0xa000 => self.chr_banks[2] = value,
0xb000 => self.chr_banks[3] = value,
0xc000 => self.nt_banks[0] = value,
0xd000 => self.nt_banks[1] = value,
0xe000 => self.nametable_control = value,
0xf000 => self.prg_bank = value & 0x0f,
else => {},
}
}
pub fn ppuMapRead(
self: *const Mapper68,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const slot = address / 0x0800;
const offset = @as(usize, address & 0x07ff);
const bank = @as(usize, self.chr_banks[slot]) % self.chr_bank_count;
return bank * 0x0800 + offset;
}
pub fn ppuMapWrite(
self: *const Mapper68,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Mapper68,
) Mirroring {
if ((self.nametable_control & 0x10) == 0) {
switch (self.nametable_control & 0x03) {
0 => return .vertical,
1 => return .horizontal,
2 => return .single_screen_lower,
3 => return .single_screen_upper,
else => unreachable,
}
}
return self.fixed_mirroring;
}
pub fn irqAsserted(
_: *const Mapper68,
) bool {
return false;
}
+123
View File
@@ -0,0 +1,123 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper69 = @This();
command: u8 = 0,
chr_banks: [8]u8 = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7 },
prg_banks: [4]u8 = [_]u8{ 0, 1, 2, 3 }, // $6000, $8000, $A000, $C000
mirroring_mode: Mirroring = .vertical,
irq_enabled: bool = false,
irq_counter: u16 = 0,
irq_assert: bool = false,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
mirroring_mode: Mirroring,
) Mapper69 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1,
.mirroring_mode = mirroring_mode,
};
}
pub fn cpuMapRead(
self: *const Mapper69,
address: u16,
) ?usize {
if (address < 0x6000) return null;
const offset = @as(usize, address & 0x1fff);
const last_bank = self.prg_bank_count - 1;
if (address < 0x8000) {
const bank = @as(usize, self.prg_banks[0] & 0x3f) % self.prg_bank_count;
return bank * 0x2000 + offset;
}
switch ((address - 0x8000) / 0x2000) {
0 => return (@as(usize, self.prg_banks[1] & 0x3f) % self.prg_bank_count) * 0x2000 + offset,
1 => return (@as(usize, self.prg_banks[2] & 0x3f) % self.prg_bank_count) * 0x2000 + offset,
2 => return (@as(usize, self.prg_banks[3] & 0x3f) % self.prg_bank_count) * 0x2000 + offset,
3 => return last_bank * 0x2000 + offset,
else => unreachable,
}
}
pub fn cpuWrite(
self: *Mapper69,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
if (address < 0xa000) {
self.command = value & 0x0f;
} else if (address < 0xc000) {
switch (self.command) {
0...7 => |i| self.chr_banks[i] = value,
8 => self.prg_banks[0] = value,
9 => self.prg_banks[1] = value,
0xa => self.prg_banks[2] = value,
0xb => self.prg_banks[3] = value,
0xc => {
switch (value & 0x03) {
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 => unreachable,
}
},
0xd => {
self.irq_enabled = (value & 0x01) != 0;
self.irq_assert = false;
},
0xe => {
self.irq_counter = (self.irq_counter & 0xff00) | value;
},
0xf => {
self.irq_counter = (self.irq_counter & 0x00ff) | (@as(u16, value) << 8);
},
else => {},
}
}
}
pub fn ppuMapRead(
self: *const Mapper69,
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 Mapper69,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Mapper69,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(
self: *const Mapper69,
) bool {
return self.irq_assert;
}
+91
View File
@@ -0,0 +1,91 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper76 = @This();
bank_select: u8 = 0,
bank_registers: [8]u8 = [_]u8{0} ** 8,
prg_bank_count: usize,
chr_bank_count: usize,
fixed_mirroring: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
mirroring_mode: Mirroring,
) Mapper76 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0800 else 1,
.fixed_mirroring = mirroring_mode,
};
}
pub fn cpuMapRead(
self: *const Mapper76,
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 r6 = @as(usize, self.bank_registers[6]) % self.prg_bank_count;
const r7 = @as(usize, self.bank_registers[7]) % self.prg_bank_count;
switch ((address - 0x8000) / 0x2000) {
0 => return r6 * 0x2000 + offset,
1 => return r7 * 0x2000 + offset,
2 => return second_last_bank * 0x2000 + offset,
3 => return last_bank * 0x2000 + offset,
else => unreachable,
}
}
pub fn cpuWrite(
self: *Mapper76,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
if ((address & 1) == 0) {
self.bank_select = value & 0x07;
} else {
self.bank_registers[self.bank_select] = value;
}
}
pub fn ppuMapRead(
self: *const Mapper76,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const offset = @as(usize, address & 0x07ff);
const slot = address / 0x0800;
const reg_idx = @as(usize, 2 + slot);
const bank = (@as(usize, self.bank_registers[reg_idx])) % self.chr_bank_count;
return bank * 0x0800 + offset;
}
pub fn ppuMapWrite(
self: *const Mapper76,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Mapper76,
) Mirroring {
return self.fixed_mirroring;
}
pub fn irqAsserted(
_: *const Mapper76,
) bool {
return false;
}
+70
View File
@@ -0,0 +1,70 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper87 = @This();
chr_bank: u8 = 0,
prg_bank_count: usize,
chr_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
_mirroring: Mirroring,
) Mapper87 {
return .{
.prg_bank_count = prg_size / 0x8000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x2000 else 1,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Mapper87,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address - 0x8000);
const bank = if (self.prg_bank_count > 0) (self.prg_bank_count - 1) else 0;
return bank * 0x8000 + offset;
}
pub fn cpuWrite(
self: *Mapper87,
address: u16,
value: u8,
) void {
if (address >= 0x6000 and address <= 0x7FFF) {
self.chr_bank = ((value & 1) << 1) | ((value >> 1) & 1);
}
}
pub fn ppuMapRead(
self: *const Mapper87,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank = @as(usize, self.chr_bank) % self.chr_bank_count;
return bank * 0x2000 + @as(usize, address);
}
pub fn ppuMapWrite(
_: *const Mapper87,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mapper87,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mapper87) bool {
return false;
}
+103
View File
@@ -0,0 +1,103 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper88 = @This();
bank_select: u8 = 0,
bank_registers: [8]u8 = [_]u8{0} ** 8,
prg_bank_count: usize,
chr_bank_count: usize,
fixed_mirroring: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
mirroring_mode: Mirroring,
) Mapper88 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1,
.fixed_mirroring = mirroring_mode,
};
}
pub fn cpuMapRead(
self: *const Mapper88,
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 r6 = @as(usize, self.bank_registers[6] & 0x3f) % self.prg_bank_count;
const r7 = @as(usize, self.bank_registers[7] & 0x3f) % self.prg_bank_count;
switch ((address - 0x8000) / 0x2000) {
0 => return r6 * 0x2000 + offset,
1 => return r7 * 0x2000 + offset,
2 => return second_last_bank * 0x2000 + offset,
3 => return last_bank * 0x2000 + offset,
else => unreachable,
}
}
pub fn cpuWrite(
self: *Mapper88,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
if ((address & 1) == 0) {
self.bank_select = value & 0x07;
} else {
self.bank_registers[self.bank_select] = value;
}
}
pub fn ppuMapRead(
self: *const Mapper88,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const offset = @as(usize, address & 0x03ff);
const page = address / 0x0400;
var bank: usize = 0;
switch (page) {
0, 1 => bank = (@as(usize, self.bank_registers[0] & 0xfe) + (page & 1)),
2, 3 => bank = (@as(usize, self.bank_registers[1] & 0xfe) + (page & 1)),
4, 5, 6, 7 => {
const reg_idx = page - 2; // registers 2, 3, 4, 5
const val = self.bank_registers[reg_idx];
// Bit 6 maps to 64KB CHR block selection (bit 6 -> 0x40 added to bank number)
bank = (@as(usize, val & 0x3f) | (@as(usize, val & 0x40)));
},
else => unreachable,
}
bank = bank % self.chr_bank_count;
return bank * 0x0400 + offset;
}
pub fn ppuMapWrite(
self: *const Mapper88,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Mapper88,
) Mirroring {
return self.fixed_mirroring;
}
pub fn irqAsserted(
_: *const Mapper88,
) bool {
return false;
}
+78
View File
@@ -0,0 +1,78 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper89 = @This();
prg_bank: u8 = 0,
chr_bank: u8 = 0,
mirroring_mode: Mirroring = .single_screen_lower,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Mapper89 {
return .{
.prg_bank_count = prg_size / 0x4000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x2000 else 1,
};
}
pub fn cpuMapRead(
self: *const Mapper89,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xC000) {
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
const offset = @as(usize, address - 0xC000);
const fixed_bank = if (self.prg_bank_count > 0) self.prg_bank_count - 1 else 0;
return fixed_bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Mapper89,
address: u16,
value: u8,
) void {
if (address >= 0x8000) {
self.prg_bank = (value >> 4) & 0x07;
self.chr_bank = (value & 0x07) | ((value & 0x80) >> 4);
self.mirroring_mode = if ((value & 0x08) != 0) .single_screen_upper else .single_screen_lower;
}
}
pub fn ppuMapRead(
self: *const Mapper89,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank = @as(usize, self.chr_bank) % self.chr_bank_count;
return bank * 0x2000 + @as(usize, address);
}
pub fn ppuMapWrite(
_: *const Mapper89,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mapper89,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mapper89) bool {
return false;
}
+73
View File
@@ -0,0 +1,73 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper93 = @This();
prg_bank: u8 = 0,
mirroring_mode: Mirroring,
prg_bank_count: usize,
pub fn init(
prg_size: usize,
_mirroring: Mirroring,
) Mapper93 {
return .{
.prg_bank_count = prg_size / 0x4000,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Mapper93,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xC000) {
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
const offset = @as(usize, address - 0xC000);
const fixed_bank = if (self.prg_bank_count > 0) self.prg_bank_count - 1 else 0;
return fixed_bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Mapper93,
address: u16,
value: u8,
) void {
if (address >= 0x8000) {
self.prg_bank = (value >> 4) & 0x07;
self.mirroring_mode = if ((value & 1) != 0) .horizontal else .vertical;
}
}
pub fn ppuMapRead(
_: *const Mapper93,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn ppuMapWrite(
_: *const Mapper93,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mapper93,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mapper93) bool {
return false;
}
+71
View File
@@ -0,0 +1,71 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mapper94 = @This();
prg_bank: u8 = 0,
prg_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
_mirroring: Mirroring,
) Mapper94 {
return .{
.prg_bank_count = prg_size / 0x4000,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Mapper94,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xC000) {
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
const offset = @as(usize, address - 0xC000);
const fixed_bank = if (self.prg_bank_count > 0) self.prg_bank_count - 1 else 0;
return fixed_bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Mapper94,
address: u16,
value: u8,
) void {
if (address >= 0x8000) {
self.prg_bank = (value >> 2) & 0x07;
}
}
pub fn ppuMapRead(
_: *const Mapper94,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn ppuMapWrite(
_: *const Mapper94,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mapper94,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mapper94) bool {
return false;
}
+139
View File
@@ -0,0 +1,139 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mmc1 = @This();
shift_register: u8 = 0x10,
control: u8 = 0x0c, // Default: PRG mode 3 (fix $C000)
chr_bank_0: u8 = 0,
chr_bank_1: u8 = 0,
prg_bank: u8 = 0,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Mmc1 {
return .{
.prg_bank_count = prg_size / 0x4000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x1000 else 2,
};
}
pub fn cpuMapRead(
self: *const Mmc1,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const prg_mode = (self.control >> 2) & 0x03;
const offset = @as(usize, address & 0x3fff);
switch (prg_mode) {
0, 1 => {
// 32 KB mode
const bank = @as(usize, self.prg_bank & 0x0e) % self.prg_bank_count;
return bank * 0x4000 + @as(usize, address - 0x8000);
},
2 => {
// Fix first bank at $8000, switch 16 KB bank at $C000
if (address < 0xc000) {
return offset;
} else {
const bank = @as(usize, self.prg_bank & 0x0f) % self.prg_bank_count;
return bank * 0x4000 + offset;
}
},
3 => {
// Fix last bank at $C000, switch 16 KB bank at $8000
if (address < 0xc000) {
const bank = @as(usize, self.prg_bank & 0x0f) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
const last_bank = self.prg_bank_count - 1;
return last_bank * 0x4000 + offset;
}
},
else => unreachable,
}
}
pub fn cpuWrite(
self: *Mmc1,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
// Bit 7 reset flag
if ((value & 0x80) != 0) {
self.shift_register = 0x10;
self.control |= 0x0c;
return;
}
const complete = (self.shift_register & 1) != 0;
self.shift_register = (self.shift_register >> 1) | ((value & 1) << 4);
if (complete) {
const reg_data = self.shift_register;
self.shift_register = 0x10;
switch ((address >> 13) & 0x03) {
0 => self.control = reg_data,
1 => self.chr_bank_0 = reg_data,
2 => self.chr_bank_1 = reg_data,
3 => self.prg_bank = reg_data,
else => unreachable,
}
}
}
pub fn ppuMapRead(
self: *const Mmc1,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const chr_mode = (self.control & 0x10) != 0;
if (!chr_mode) {
// 8 KB CHR mode
const bank = @as(usize, self.chr_bank_0 & 0x1e) % self.chr_bank_count;
return bank * 0x1000 + @as(usize, address);
} else {
// 4 KB CHR mode
if (address < 0x1000) {
const bank = @as(usize, self.chr_bank_0) % self.chr_bank_count;
return bank * 0x1000 + @as(usize, address & 0x0fff);
} else {
const bank = @as(usize, self.chr_bank_1) % self.chr_bank_count;
return bank * 0x1000 + @as(usize, address & 0x0fff);
}
}
}
pub fn ppuMapWrite(
_: *const Mmc1,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mmc1,
) Mirroring {
return switch (self.control & 0x03) {
0 => .single_screen_lower,
1 => .single_screen_upper,
2 => .vertical,
3 => .horizontal,
else => unreachable,
};
}
pub fn irqAsserted(_: *const Mmc1) bool {
return false;
}
+104
View File
@@ -0,0 +1,104 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mmc2 = @This();
prg_bank: u8 = 0,
chr_0fd: u8 = 0,
chr_0fe: u8 = 0,
chr_1fd: u8 = 0,
chr_1fe: u8 = 0,
latch0: u1 = 0, // 0 = 0FD, 1 = 0FE
latch1: u1 = 0, // 0 = 1FD, 1 = 1FE
mirroring_mode: Mirroring = .vertical,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Mmc2 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x1000 else 1,
};
}
pub fn cpuMapRead(
self: *const Mmc2,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xA000) {
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x2000 + @as(usize, address - 0x8000);
} else {
const offset = @as(usize, address - 0xA000);
const fixed_start = if (self.prg_bank_count >= 3) (self.prg_bank_count - 3) * 0x2000 else 0;
return fixed_start + offset;
}
}
pub fn cpuWrite(
self: *Mmc2,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
switch (address & 0xF000) {
0xA000 => self.prg_bank = value & 0x0F,
0xB000 => self.chr_0fd = value & 0x1F,
0xC000 => self.chr_0fe = value & 0x1F,
0xD000 => self.chr_1fd = value & 0x1F,
0xE000 => self.chr_1fe = value & 0x1F,
0xF000 => self.mirroring_mode = if ((value & 1) == 0) .vertical else .horizontal,
else => {},
}
}
pub fn ppuMapRead(
self: *Mmc2,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
var bank: usize = 0;
if (address < 0x1000) {
const selected_bank = if (self.latch0 == 0) self.chr_0fd else self.chr_0fe;
bank = @as(usize, selected_bank) % self.chr_bank_count;
} else {
const selected_bank = if (self.latch1 == 0) self.chr_1fd else self.chr_1fe;
bank = @as(usize, selected_bank) % self.chr_bank_count;
}
// Check PPU tile latches
if (address == 0x0FD0) self.latch0 = 0;
if (address == 0x0FE0) self.latch0 = 1;
if (address == 0x1FD0) self.latch1 = 0;
if (address == 0x1FE0) self.latch1 = 1;
return bank * 0x1000 + @as(usize, address & 0x0FFF);
}
pub fn ppuMapWrite(
_: *const Mmc2,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Mmc2,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Mmc2) bool {
return false;
}
+161
View File
@@ -0,0 +1,161 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mmc3 = @This();
bank_select: u8 = 0,
bank_registers: [8]u8 = [_]u8{0} ** 8,
mirroring_mode: Mirroring = .horizontal,
irq_latch: u8 = 0,
irq_counter: u8 = 0,
irq_reload: bool = false,
irq_enabled: bool = false,
irq_pending: bool = false,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Mmc3 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 8,
};
}
pub fn cpuMapRead(
self: *const Mmc3,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address & 0x1fff);
const prg_mode = (self.bank_select & 0x40) != 0;
const last_bank = self.prg_bank_count - 1;
const second_last_bank = self.prg_bank_count - 2;
const r6 = @as(usize, self.bank_registers[6] & 0x3f) % self.prg_bank_count;
const r7 = @as(usize, self.bank_registers[7] & 0x3f) % self.prg_bank_count;
switch ((address - 0x8000) / 0x2000) {
0 => { // $8000-$9FFF
const bank = if (!prg_mode) r6 else second_last_bank;
return bank * 0x2000 + offset;
},
1 => { // $A000-$BFFF
return r7 * 0x2000 + offset;
},
2 => { // $C000-$DFFF
const bank = if (!prg_mode) second_last_bank else r6;
return bank * 0x2000 + offset;
},
3 => { // $E000-$FFFF
return last_bank * 0x2000 + offset;
},
else => unreachable,
}
}
pub fn cpuWrite(
self: *Mmc3,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
switch (address & 0xe001) {
0x8000 => self.bank_select = value,
0x8001 => {
const reg = self.bank_select & 0x07;
self.bank_registers[reg] = value;
},
0xa000 => {
self.mirroring_mode = if ((value & 1) != 0) .horizontal else .vertical;
},
0xa001 => {}, // PRG RAM protect
0xc000 => self.irq_latch = value,
0xc001 => self.irq_reload = true,
0xe000 => {
self.irq_enabled = false;
self.irq_pending = false;
},
0xe001 => self.irq_enabled = true,
else => {},
}
}
pub fn ppuMapRead(
self: *const Mmc3,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const chr_mode = (self.bank_select & 0x80) != 0;
const offset = @as(usize, address & 0x03ff);
var bank: usize = 0;
const addr_k = address / 0x0400;
if (!chr_mode) {
switch (addr_k) {
0, 1 => bank = (@as(usize, self.bank_registers[0] & 0xfe) + (addr_k & 1)),
2, 3 => bank = (@as(usize, self.bank_registers[1] & 0xfe) + (addr_k & 1)),
4 => bank = self.bank_registers[2],
5 => bank = self.bank_registers[3],
6 => bank = self.bank_registers[4],
7 => bank = self.bank_registers[5],
else => unreachable,
}
} else {
switch (addr_k) {
0 => bank = self.bank_registers[2],
1 => bank = self.bank_registers[3],
2 => bank = self.bank_registers[4],
3 => bank = self.bank_registers[5],
4, 5 => bank = (@as(usize, self.bank_registers[0] & 0xfe) + (addr_k & 1)),
6, 7 => bank = (@as(usize, self.bank_registers[1] & 0xfe) + (addr_k & 1)),
else => unreachable,
}
}
bank = bank % self.chr_bank_count;
return bank * 0x0400 + offset;
}
pub fn ppuMapWrite(
_: *const Mmc3,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn handleScanline(self: *Mmc3) void {
if (self.irq_counter == 0 or self.irq_reload) {
self.irq_counter = self.irq_latch;
self.irq_reload = false;
} else {
self.irq_counter -= 1;
}
if (self.irq_counter == 0 and self.irq_enabled) {
self.irq_pending = true;
}
}
pub fn mirroring(
self: *const Mmc3,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(
self: *const Mmc3,
) bool {
return self.irq_pending;
}
+117
View File
@@ -0,0 +1,117 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mmc4 = @This();
prg_bank: u8 = 0,
chr_banks_fd: [2]u8 = [_]u8{ 0, 0 },
chr_banks_fe: [2]u8 = [_]u8{ 0, 0 },
latch: [2]u8 = [_]u8{ 0xfe, 0xfe },
mirroring_mode: Mirroring = .vertical,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Mmc4 {
return .{
.prg_bank_count = prg_size / 0x4000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x1000 else 2,
};
}
pub fn cpuMapRead(
self: *const Mmc4,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address & 0x3fff);
const last_bank = self.prg_bank_count - 1;
if (address < 0xc000) {
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
return last_bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Mmc4,
address: u16,
value: u8,
) void {
if (address < 0xa000) return;
switch (address & 0xf000) {
0xa000 => self.prg_bank = value & 0x0f,
0xb000 => self.chr_banks_fd[0] = value & 0x1f,
0xc000 => self.chr_banks_fe[0] = value & 0x1f,
0xd000 => self.chr_banks_fd[1] = value & 0x1f,
0xe000 => self.chr_banks_fe[1] = value & 0x1f,
0xf000 => self.mirroring_mode = if ((value & 1) != 0) .horizontal else .vertical,
else => {},
}
}
pub fn ppuMapRead(
self: *Mmc4,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const slot = address / 0x1000;
const offset = @as(usize, address & 0x0fff);
const bank_raw = if (self.latch[slot] == 0xfd)
self.chr_banks_fd[slot]
else
self.chr_banks_fe[slot];
const bank = @as(usize, bank_raw) % self.chr_bank_count;
const result = bank * 0x1000 + offset;
// Check PPU tile latch triggers ($0FD0-$0FDF, $0FE0-$0FEF, $1FD0-$1FDF, $1FE0-$1FEF)
if (address == 0x0fd0 or address == 0x0fe0) {
self.latch[0] = @truncate(address >> 4);
} else if (address == 0x1fd0 or address == 0x1fe0) {
self.latch[1] = @truncate(address >> 4);
}
return result;
}
pub fn ppuMapWrite(
self: *const Mmc4,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const slot = address / 0x1000;
const offset = @as(usize, address & 0x0fff);
const bank_raw = if (self.latch[slot] == 0xfd)
self.chr_banks_fd[slot]
else
self.chr_banks_fe[slot];
const bank = @as(usize, bank_raw) % self.chr_bank_count;
return bank * 0x1000 + offset;
}
pub fn mirroring(
self: *const Mmc4,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(
_: *const Mmc4,
) bool {
return false;
}
+171
View File
@@ -0,0 +1,171 @@
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;
}
+73
View File
@@ -0,0 +1,73 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Nina03 = @This();
prg_bank: u8 = 0,
chr_bank: u8 = 0,
prg_bank_count: usize,
chr_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
chr_size: usize,
_mirroring: Mirroring,
) Nina03 {
return .{
.prg_bank_count = prg_size / 0x8000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x2000 else 1,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Nina03,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x8000 + offset;
}
pub fn cpuWrite(
self: *Nina03,
address: u16,
value: u8,
) void {
if (address >= 0x4100 and address <= 0x5FFF) {
self.prg_bank = @truncate((value >> 3) & 1);
self.chr_bank = value & 0x07;
}
}
pub fn ppuMapRead(
self: *const Nina03,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const bank = @as(usize, self.chr_bank) % self.chr_bank_count;
return bank * 0x2000 + @as(usize, address);
}
pub fn ppuMapWrite(
_: *const Nina03,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Nina03,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Nina03) bool {
return false;
}
+71
View File
@@ -0,0 +1,71 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Nrom = @This();
prg_16k: bool,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
_mirroring: Mirroring,
) Nrom {
return .{
.prg_16k = prg_size == 0x4000,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Nrom,
address: u16,
) ?usize {
if (address < 0x8000)
return null;
const offset =
@as(usize, address) - 0x8000;
return if (self.prg_16k)
offset & 0x3fff
else
offset;
}
pub fn cpuWrite(
_: *Nrom,
_: u16,
_: u8,
) void {
// NROM has no mapper registers.
}
pub fn ppuMapRead(
_: *const Nrom,
address: u16,
) ?usize {
if (address >= 0x2000)
return null;
return address;
}
pub fn ppuMapWrite(
_: *const Nrom,
address: u16,
) ?usize {
if (address >= 0x2000)
return null;
return address;
}
pub fn mirroring(
self: *const Nrom,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Nrom) bool {
return false;
}
+147
View File
@@ -0,0 +1,147 @@
pub const Nrom = @import("nrom.zig");
pub const Uxrom = @import("uxrom.zig");
pub const Mmc1 = @import("mmc1.zig");
pub const Cnrom = @import("cnrom.zig");
pub const Mmc3 = @import("mmc3.zig");
pub const Mmc4 = @import("mmc4.zig");
pub const Mmc5 = @import("mmc5.zig");
pub const Axrom = @import("axrom.zig");
pub const Gxrom = @import("gxrom.zig");
pub const Mmc2 = @import("mmc2.zig");
pub const ColorDreams = @import("colordreams.zig");
pub const Camerica = @import("camerica.zig");
pub const Mapper13 = @import("mapper13.zig");
pub const Mapper34 = @import("mapper34.zig");
pub const Mapper65 = @import("mapper65.zig");
pub const Mapper68 = @import("mapper68.zig");
pub const Mapper69 = @import("mapper69.zig");
pub const Nina03 = @import("nina03.zig");
pub const Mapper76 = @import("mapper76.zig");
pub const Irem78 = @import("irem78.zig");
pub const Mapper87 = @import("mapper87.zig");
pub const Mapper88 = @import("mapper88.zig");
pub const Mapper89 = @import("mapper89.zig");
pub const Mapper93 = @import("mapper93.zig");
pub const Mapper94 = @import("mapper94.zig");
pub const Mapper118 = @import("mapper118.zig");
pub const Mapper119 = @import("mapper119.zig");
pub const Mapper140 = @import("mapper140.zig");
pub const Mapper180 = @import("mapper180.zig");
pub const Vrc1 = @import("vrc1.zig");
pub const Vrc2 = @import("vrc2.zig");
pub const Mapper232 = @import("mapper232.zig");
pub const Vrc3 = @import("vrc3.zig");
pub const Vrc4 = @import("vrc4.zig");
pub const Vrc6 = @import("vrc6.zig");
pub const Vrc7 = @import("vrc7.zig");
pub const Mapper210 = @import("mapper210.zig");
pub const Mapper206 = @import("mapper206.zig");
const types = @import("types.zig");
pub const Mirroring = types.Mirroring;
pub const Mapper = union(enum) {
nrom: Nrom,
uxrom: Uxrom,
mmc1: Mmc1,
cnrom: Cnrom,
mmc3: Mmc3,
mmc4: Mmc4,
mmc5: Mmc5,
axrom: Axrom,
gxrom: Gxrom,
mmc2: Mmc2,
color_dreams: ColorDreams,
camerica: Camerica,
mapper13: Mapper13,
mapper34: Mapper34,
mapper65: Mapper65,
mapper68: Mapper68,
mapper69: Mapper69,
nina03: Nina03,
mapper76: Mapper76,
irem78: Irem78,
mapper87: Mapper87,
mapper88: Mapper88,
mapper89: Mapper89,
mapper93: Mapper93,
mapper94: Mapper94,
mapper118: Mapper118,
mapper119: Mapper119,
mapper140: Mapper140,
mapper180: Mapper180,
vrc1: Vrc1,
vrc2: Vrc2,
mapper232: Mapper232,
vrc3: Vrc3,
vrc4: Vrc4,
vrc6: Vrc6,
vrc7: Vrc7,
mapper210: Mapper210,
mapper206: Mapper206,
pub fn cpuMapRead(
self: *const Mapper,
address: u16,
) ?usize {
return switch (self.*) {
inline else => |*mapper| mapper.cpuMapRead(address),
};
}
pub fn cpuWrite(
self: *Mapper,
address: u16,
value: u8,
) void {
switch (self.*) {
inline else => |*mapper| mapper.cpuWrite(address, value),
}
}
pub fn ppuMapRead(
self: *Mapper,
address: u16,
) ?usize {
return switch (self.*) {
inline else => |*mapper| mapper.ppuMapRead(address),
};
}
pub fn ppuMapWrite(
self: *const Mapper,
address: u16,
) ?usize {
return switch (self.*) {
inline else => |*mapper| mapper.ppuMapWrite(address),
};
}
pub fn mirroring(
self: *const Mapper,
) Mirroring {
return switch (self.*) {
inline else => |*mapper| mapper.mirroring(),
};
}
pub fn irqAsserted(
self: *const Mapper,
) bool {
return switch (self.*) {
inline else => |*mapper| mapper.irqAsserted(),
};
}
pub fn handleScanline(
self: *Mapper,
) void {
switch (self.*) {
.mmc3 => |*mmc3| mmc3.handleScanline(),
.mmc5 => |*mmc5| mmc5.handleScanline(),
.mapper118 => |*m118| m118.handleScanline(),
.mapper119 => |*m119| m119.handleScanline(),
else => {},
}
}
};
+9
View File
@@ -0,0 +1,9 @@
pub const Mirroring = enum {
horizontal,
vertical,
four_screen,
// Needed by later mappers.
single_screen_lower,
single_screen_upper,
};
+73
View File
@@ -0,0 +1,73 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Uxrom = @This();
prg_bank_select: u8 = 0,
prg_bank_count: usize,
mirroring_mode: Mirroring,
pub fn init(
prg_size: usize,
_mirroring: Mirroring,
) Uxrom {
return .{
.prg_bank_count = prg_size / 0x4000,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Uxrom,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address & 0x3fff);
if (address < 0xc000) {
// Switchable 16 KB bank
const bank = @as(usize, self.prg_bank_select) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
// Fixed to last 16 KB bank
const last_bank = self.prg_bank_count - 1;
return last_bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Uxrom,
address: u16,
value: u8,
) void {
if (address >= 0x8000) {
self.prg_bank_select = value;
}
}
pub fn ppuMapRead(
_: *const Uxrom,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn ppuMapWrite(
_: *const Uxrom,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Uxrom,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Uxrom) bool {
return false;
}
+106
View File
@@ -0,0 +1,106 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Vrc1 = @This();
prg_bank0: u8 = 0,
prg_bank1: u8 = 0,
prg_bank2: u8 = 0,
chr_bank0: u8 = 0,
chr_bank1: u8 = 0,
mirroring_mode: Mirroring = .vertical,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Vrc1 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x1000 else 1,
};
}
pub fn cpuMapRead(
self: *const Vrc1,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xA000) {
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank0) % self.prg_bank_count;
return bank * 0x2000 + offset;
} else if (address < 0xC000) {
const offset = @as(usize, address - 0xA000);
const bank = @as(usize, self.prg_bank1) % self.prg_bank_count;
return bank * 0x2000 + offset;
} else if (address < 0xE000) {
const offset = @as(usize, address - 0xC000);
const bank = @as(usize, self.prg_bank2) % self.prg_bank_count;
return bank * 0x2000 + offset;
} else {
const offset = @as(usize, address - 0xE000);
const fixed_bank = if (self.prg_bank_count > 0) self.prg_bank_count - 1 else 0;
return fixed_bank * 0x2000 + offset;
}
}
pub fn cpuWrite(
self: *Vrc1,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
switch (address & 0xF000) {
0x8000 => self.prg_bank0 = value & 0x0F,
0x9000 => {
self.mirroring_mode = if ((value & 1) != 0) .horizontal else .vertical;
self.chr_bank0 = (self.chr_bank0 & 0x0F) | ((value & 0x02) << 3);
self.chr_bank1 = (self.chr_bank1 & 0x0F) | ((value & 0x04) << 2);
},
0xA000 => self.prg_bank1 = value & 0x0F,
0xC000 => self.prg_bank2 = value & 0x0F,
0xE000 => self.chr_bank0 = (self.chr_bank0 & 0x10) | (value & 0x0F),
0xF000 => self.chr_bank1 = (self.chr_bank1 & 0x10) | (value & 0x0F),
else => {},
}
}
pub fn ppuMapRead(
self: *const Vrc1,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
if (address < 0x1000) {
const bank = @as(usize, self.chr_bank0) % self.chr_bank_count;
return bank * 0x1000 + @as(usize, address);
} else {
const bank = @as(usize, self.chr_bank1) % self.chr_bank_count;
return bank * 0x1000 + @as(usize, address - 0x1000);
}
}
pub fn ppuMapWrite(
_: *const Vrc1,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Vrc1,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(_: *const Vrc1) bool {
return false;
}
+127
View File
@@ -0,0 +1,127 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Vrc2 = @This();
prg_banks: [2]u8 = [_]u8{ 0, 1 },
chr_banks_lo: [8]u4 = [_]u4{0} ** 8,
chr_banks_hi: [8]u5 = [_]u5{0} ** 8,
mirroring_mode: Mirroring = .vertical,
mapper_id: u8,
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,
) Vrc2 {
const shift_a: u3 = if (mapper_id == 22) 1 else 0;
const shift_b: u3 = if (mapper_id == 22) 0 else 1;
return .{
.mapper_id = mapper_id,
.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 Vrc2,
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;
switch ((address - 0x8000) / 0x2000) {
0 => return r0 * 0x2000 + offset,
1 => return r1 * 0x2000 + offset,
2 => return second_last_bank * 0x2000 + offset,
3 => return last_bank * 0x2000 + offset,
else => unreachable,
}
}
pub fn cpuWrite(
self: *Vrc2,
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 => {
self.mirroring_mode = if ((value & 0x01) != 0) .horizontal else .vertical;
},
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);
}
},
else => {},
}
}
pub fn ppuMapRead(
self: *const Vrc2,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const slot = address / 0x0400;
const offset = @as(usize, address & 0x03ff);
var bank_val = (@as(usize, self.chr_banks_hi[slot]) << 4) | @as(usize, self.chr_banks_lo[slot]);
if (self.mapper_id == 22) {
bank_val >>= 1;
}
const bank = bank_val % self.chr_bank_count;
return bank * 0x0400 + offset;
}
pub fn ppuMapWrite(
self: *const Vrc2,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Vrc2,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(
_: *const Vrc2,
) bool {
return false;
}
+118
View File
@@ -0,0 +1,118 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Vrc3 = @This();
prg_bank: u8 = 0,
prg_bank_count: usize,
mirroring_mode: Mirroring,
irq_latch: u16 = 0,
irq_counter: u16 = 0,
irq_enabled: bool = false,
irq_mode_8bit: bool = false,
irq_asserted_flag: bool = false,
pub fn init(
prg_size: usize,
_mirroring: Mirroring,
) Vrc3 {
return .{
.prg_bank_count = prg_size / 0x4000,
.mirroring_mode = _mirroring,
};
}
pub fn cpuMapRead(
self: *const Vrc3,
address: u16,
) ?usize {
if (address < 0x8000) return null;
if (address < 0xC000) {
const offset = @as(usize, address - 0x8000);
const bank = @as(usize, self.prg_bank) % self.prg_bank_count;
return bank * 0x4000 + offset;
} else {
const offset = @as(usize, address - 0xC000);
const fixed_bank = if (self.prg_bank_count > 0) self.prg_bank_count - 1 else 0;
return fixed_bank * 0x4000 + offset;
}
}
pub fn cpuWrite(
self: *Vrc3,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
switch (address & 0xF000) {
0x8000 => self.irq_latch = (self.irq_latch & 0xFFF0) | (value & 0x0F),
0x9000 => self.irq_latch = (self.irq_latch & 0xFF0F) | ((@as(u16, value) & 0x0F) << 4),
0xA000 => self.irq_latch = (self.irq_latch & 0xF0FF) | ((@as(u16, value) & 0x0F) << 8),
0xB000 => self.irq_latch = (self.irq_latch & 0x0FFF) | ((@as(u16, value) & 0x0F) << 12),
0xC000 => {
self.irq_enabled = (value & 0x02) != 0;
self.irq_mode_8bit = (value & 0x04) != 0;
self.irq_asserted_flag = false;
if (self.irq_enabled) {
self.irq_counter = self.irq_latch;
}
},
0xD000 => {
self.irq_asserted_flag = false;
},
0xF000 => {
self.prg_bank = value & 0x07;
},
else => {},
}
}
pub fn tickCycle(self: *Vrc3) void {
if (self.irq_enabled) {
if (self.irq_mode_8bit) {
const low = @as(u8, @truncate(self.irq_counter));
if (low == 0xFF) {
self.irq_counter = (self.irq_counter & 0xFF00) | @as(u16, @as(u8, @truncate(self.irq_latch)));
self.irq_asserted_flag = true;
} else {
self.irq_counter += 1;
}
} else {
if (self.irq_counter == 0xFFFF) {
self.irq_counter = self.irq_latch;
self.irq_asserted_flag = true;
} else {
self.irq_counter += 1;
}
}
}
}
pub fn ppuMapRead(
_: *const Vrc3,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn ppuMapWrite(
_: *const Vrc3,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
return address;
}
pub fn mirroring(
self: *const Vrc3,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(self: *const Vrc3) bool {
return self.irq_asserted_flag;
}
+179
View File
@@ -0,0 +1,179 @@
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;
}
+152
View File
@@ -0,0 +1,152 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Vrc6 = @This();
prg_banks: [2]u8 = [_]u8{ 0, 1 },
chr_banks: [8]u8 = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7 },
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,
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,
) Vrc6 {
const shift_a: u3 = if (mapper_id == 26) 1 else 0;
const shift_b: u3 = if (mapper_id == 26) 0 else 1;
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 Vrc6,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const last_bank = self.prg_bank_count - 1;
if (address < 0xc000) {
const offset = @as(usize, address & 0x3fff);
const bank16 = @as(usize, self.prg_banks[0] & 0x0f) % (self.prg_bank_count / 2);
return bank16 * 0x4000 + offset;
} else if (address < 0xe000) {
const offset = @as(usize, address & 0x1fff);
const bank8 = @as(usize, self.prg_banks[1] & 0x1f) % self.prg_bank_count;
return bank8 * 0x2000 + offset;
} else {
const offset = @as(usize, address & 0x1fff);
return last_bank * 0x2000 + offset;
}
}
pub fn cpuWrite(
self: *Vrc6,
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 => {
if (line == 0) self.prg_banks[0] = value & 0x0f;
},
0x9000...0xb000 => {
if (reg_addr == 0xb000 and line == 3) {
switch (@as(u2, @truncate((value >> 2) & 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,
}
}
},
0xc000 => {
if (line == 0) self.prg_banks[1] = value & 0x1f;
},
0xd000 => {
const slot = @as(usize, line);
self.chr_banks[slot] = value;
},
0xe000 => {
const slot = 4 + @as(usize, line);
self.chr_banks[slot] = value;
},
0xf000 => {
switch (line) {
0 => self.irq_reload = value,
1 => {
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_assert = false;
},
2 => {
self.irq_enabled = self.irq_enable_on_ack;
self.irq_assert = false;
},
else => {},
}
},
else => {},
}
}
pub fn ppuMapRead(
self: *const Vrc6,
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 Vrc6,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Vrc6,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(
self: *const Vrc6,
) bool {
return self.irq_assert;
}
+145
View File
@@ -0,0 +1,145 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Vrc7 = @This();
prg_banks: [3]u8 = [_]u8{ 0, 1, 2 },
chr_banks: [8]u8 = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7 },
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,
irq_assert: bool = false,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Vrc7 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1,
};
}
pub fn cpuMapRead(
self: *const Vrc7,
address: u16,
) ?usize {
if (address < 0x8000) return null;
const offset = @as(usize, address & 0x1fff);
const last_bank = self.prg_bank_count - 1;
switch ((address - 0x8000) / 0x2000) {
0 => return (@as(usize, self.prg_banks[0] & 0x3f) % self.prg_bank_count) * 0x2000 + offset,
1 => return (@as(usize, self.prg_banks[1] & 0x3f) % self.prg_bank_count) * 0x2000 + offset,
2 => return (@as(usize, self.prg_banks[2] & 0x3f) % self.prg_bank_count) * 0x2000 + offset,
3 => return last_bank * 0x2000 + offset,
else => unreachable,
}
}
pub fn cpuWrite(
self: *Vrc7,
address: u16,
value: u8,
) void {
if (address < 0x8000) return;
const reg_addr = address & 0xf000;
const a0 = if ((address & 0x10) != 0 or (address & 0x08) != 0) @as(u2, 1) else 0;
const a1 = if ((address & 0x20) != 0) @as(u2, 2) else 0;
const line = a0 | a1;
switch (reg_addr) {
0x8000 => {
if (line == 0) self.prg_banks[0] = value & 0x3f;
if (line == 1 or line == 2) self.prg_banks[1] = value & 0x3f;
},
0x9000 => {
if (line == 0) self.prg_banks[2] = value & 0x3f;
if (line == 2) {
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,
}
}
},
0xa000 => {
if (line == 0) self.chr_banks[0] = value;
if (line == 1 or line == 2) self.chr_banks[1] = value;
},
0xb000 => {
if (line == 0) self.chr_banks[2] = value;
if (line == 1 or line == 2) self.chr_banks[3] = value;
},
0xc000 => {
if (line == 0) self.chr_banks[4] = value;
if (line == 1 or line == 2) self.chr_banks[5] = value;
},
0xd000 => {
if (line == 0) self.chr_banks[6] = value;
if (line == 1 or line == 2) self.chr_banks[7] = value;
},
0xe000 => {
if (line == 0) self.irq_reload = value;
if (line == 1 or line == 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_assert = false;
}
},
0xf000 => {
if (line == 0) {
self.irq_enabled = self.irq_enable_on_ack;
self.irq_assert = false;
}
},
else => {},
}
}
pub fn ppuMapRead(
self: *const Vrc7,
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 Vrc7,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn mirroring(
self: *const Vrc7,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(
self: *const Vrc7,
) bool {
return self.irq_assert;
}