add nes cartridge subsystem and 41 ines mapper variants
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
pub const Mirroring = enum {
|
||||||
|
horizontal,
|
||||||
|
vertical,
|
||||||
|
four_screen,
|
||||||
|
|
||||||
|
// Needed by later mappers.
|
||||||
|
single_screen_lower,
|
||||||
|
single_screen_upper,
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user