Major refactor

- CPU is now it's own module
- Memory object is now shared to support mapper chips
- ROM is now stored as `Arc<[u8]>` to support mapper chips
This commit is contained in:
2026-01-24 03:38:42 -06:00
parent b5e1d1a4c3
commit f861f75b21
16 changed files with 3071 additions and 2450 deletions

View File

@@ -1,5 +1,11 @@
use crate::{hex_view::Memory, ppu::PPUMMRegisters};
use std::{fmt, sync::Arc};
use crate::{
CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState, hex_view::Memory,
ppu::PPUMMRegisters,
};
#[derive(Debug, Clone)]
pub enum Value<'a, R> {
Value(u8),
Register { reg: &'a R, offset: u16 },
@@ -14,23 +20,29 @@ impl<R> Value<'_, R> {
}
}
#[derive(Debug, Clone)]
pub enum Data<R> {
RAM(Vec<u8>),
ROM(Vec<u8>),
Mirror(usize),
ROM(Arc<[u8]>),
Mirror(u16),
Reg(R),
// Disabled(),
Disabled,
}
#[derive(Debug, Clone)]
pub struct Segment<R> {
name: &'static str,
name: SegmentId,
position: u16,
size: u16,
mem: Data<R>,
}
impl<R> Segment<R> {
pub fn ram(name: &'static str, position: u16, size: u16) -> Self {
fn is(&self, id: SegmentId) -> bool {
self.name == id
}
fn ram(name: SegmentId, position: u16, size: u16) -> Self {
Self {
name,
position,
@@ -38,15 +50,19 @@ impl<R> Segment<R> {
mem: Data::RAM(vec![0u8; size as usize]),
}
}
pub fn rom(name: &'static str, position: u16, raw: &[u8]) -> Self {
fn rom<Raw>(name: SegmentId, position: u16, raw: Raw) -> Self
where
Arc<[u8]>: From<Raw>,
{
let raw = Arc::from(raw);
Self {
name,
position,
size: raw.len() as u16,
mem: Data::ROM(Vec::from(raw)),
mem: Data::ROM(raw),
}
}
pub fn reg(name: &'static str, position: u16, size: u16, reg: R) -> Self {
fn reg(name: SegmentId, position: u16, size: u16, reg: R) -> Self {
Self {
name,
position,
@@ -54,7 +70,7 @@ impl<R> Segment<R> {
mem: Data::Reg(reg),
}
}
pub fn mirror(name: &'static str, position: u16, size: u16, of: usize) -> Self {
fn mirror(name: SegmentId, position: u16, size: u16, of: u16) -> Self {
Self {
name,
position,
@@ -62,20 +78,45 @@ impl<R> Segment<R> {
mem: Data::Mirror(of),
}
}
// fn take_rom(&mut self) -> Vec<u8> {
// match std::mem::replace(&mut self.mem, Data::Disabled) {
// Data::ROM(items) => items,
// _ => panic!("Cannot take rom since memory is not rom"),
// }
// }
// fn set_rom(&mut self, rom: Vec<u8>) {
// assert!(
// matches!(self.mem, Data::Disabled),
// "Cannot set non-disabled memory to rom"
// );
// self.mem = Data::ROM(rom);
// }
fn swap_rom(&mut self, rom: Arc<[u8]>) {
match &mut self.mem {
Data::ROM(items) => *items = rom,
_ => panic!("Cannot swap rom since memory is not rom"),
}
}
pub fn power_cycle(&mut self) {
match &mut self.mem {
Data::RAM(v) => v.fill(0),
_ => (),
}
}
}
#[derive(Debug, Clone)]
pub struct MemoryMap<R> {
edit_ver: usize,
segments: Vec<Segment<R>>,
// map: Remapper,
}
impl<R> MemoryMap<R> {
pub fn new(segments: Vec<Segment<R>>) -> Self {
Self {
edit_ver: 0,
segments,
}
}
impl<R: Copy> MemoryMap<R> {
pub fn read(&self, addr: u16) -> Value<'_, R> {
// self.edit_ver += 1;
for segment in &self.segments {
@@ -87,11 +128,13 @@ impl<R> MemoryMap<R> {
reg,
offset: addr - segment.position,
},
Data::Mirror(index) => {
Data::Mirror(pos) => {
let offset = addr - segment.position;
let s = &self.segments[*index];
self.read(s.position + offset % s.size)
} // Data::Disabled() => todo!(),
self.read(pos + offset)
// let s = &self.segments[*index];
// self.read(s.position + offset % s.size)
}
Data::Disabled => todo!(),
};
}
}
@@ -100,26 +143,32 @@ impl<R> MemoryMap<R> {
// todo!("Open bus")
}
pub fn write(&mut self, addr: u16, val: u8, reg_fn: impl FnOnce(&R, u16, u8)) {
pub fn write(&mut self, addr: u16, val: u8) -> Option<(R, u16, u8)> {
self.edit_ver += 1;
for segment in &mut self.segments {
if segment.position <= addr && addr - segment.position < segment.size {
return match &mut segment.mem {
Data::RAM(items) => {
items[(addr - segment.position) as usize] = val;
None
}
Data::ROM(_items) => (),
Data::Reg(reg) => reg_fn(reg, addr - segment.position, val),
Data::Mirror(index) => {
Data::ROM(_items) => None,
Data::Reg(reg) => Some((*reg, addr - segment.position, val)),
Data::Mirror(pos) => {
let pos = *pos;
let offset = addr - segment.position;
let index = *index;
let s = &self.segments[index];
self.write(s.position + offset % s.size, val, reg_fn)
} // Data::Disabled() => todo!(),
self.write(pos + offset, val)
// let index = *index;
// let s = &self.segments[index];
// self.write(s.position + offset % s.size, val)
}
Data::Disabled => todo!(),
};
}
}
todo!("Open bus")
// iirc, open bus just drops writes
None
// todo!("Open bus (${addr:04X})")
}
pub fn clear(&mut self) {
@@ -131,51 +180,26 @@ impl<R> MemoryMap<R> {
}
}
pub(crate) fn rom(&self, idx: usize) -> Option<&[u8]> {
if let Some(Segment {
mem: Data::ROM(val),
..
}) = self.segments.get(idx)
{
Some(val)
} else {
None
pub fn power_cycle(&mut self) -> Self {
for seg in &mut self.segments {
seg.power_cycle();
}
let segments = std::mem::take(&mut self.segments);
Self {
edit_ver: self.edit_ver + 1,
segments,
// map: self.map.power_cycle(),
}
}
pub(crate) fn rom_or_ram(&self, idx: usize) -> Option<&[u8]> {
if let Some(Segment {
mem: Data::ROM(val),
..
}) = self.segments.get(idx)
{
Some(val)
} else if let Some(Segment {
mem: Data::RAM(_), ..
}) = self.segments.get(idx)
{
Some(&[])
} else {
None
fn find(&mut self, id: SegmentId) -> Option<&mut Segment<R>> {
for s in &mut self.segments {
if s.is(id) {
return Some(s);
}
}
None
}
// pub fn with_peek_reg<'s>(&'s self, f: impl (Fn(&R, u16) -> Option<u8>) + 's) -> impl Memory + 's {
// struct MemImpl<'a, R>(&'a MemoryMap<R>, Box<dyn Fn(&R, u16) -> Option<u8> + 'a>);
// impl<R> Memory for MemImpl<'_, R> {
// fn peek(&self, val: u16) -> Option<u8> {
// match self.0.read(val) {
// Value::Value(v) => Some(v),
// Value::Register { reg, offset } => self.1(reg, offset),
// }
// }
// fn edit_ver(&self) -> usize {
// self.0.edit_ver()
// }
// }
// MemImpl(self, Box::new(f))
// }
}
impl<R> Memory for MemoryMap<R> {
@@ -186,11 +210,11 @@ impl<R> Memory for MemoryMap<R> {
Data::RAM(items) => Some(items[(addr - segment.position) as usize]),
Data::ROM(items) => Some(items[(addr - segment.position) as usize]),
Data::Reg(_) => None,
Data::Mirror(index) => {
Data::Mirror(pos) => {
let offset = addr - segment.position;
let s = &self.segments[*index];
self.peek(s.position + offset % s.size)
} // Data::Disabled() => todo!(),
self.peek(pos + offset)
}
Data::Disabled => None,
};
}
}
@@ -203,48 +227,470 @@ impl<R> Memory for MemoryMap<R> {
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Mapper {
struct Mapper {
horizontal_name_table: bool,
mapper: u16,
sub_mapper: u8,
prg_rom_size: u8,
chr_rom_size: u8,
}
impl Mapper {
pub fn from_flags(flags: u8) -> Self {
if flags & 0b11111110 != 0 {
todo!("Support other mapper flags");
}
Self {
horizontal_name_table: flags & (1 << 0) == 1,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum SegmentId {
#[allow(unused)]
TestRam,
pub(crate) fn ppu_map(&self, rom: &[u8]) -> MemoryMap<PPUMMRegisters> {
let chr = if rom.len() == 0 {
Segment::ram("CHR RAM", 0x0000, 0x2000)
InternalRam,
InternalRamMirror,
PpuRegisters,
ApuIoRegisters,
PrgRom,
Nametable0,
Nametable1,
Nametable2,
Nametable3,
VramMirror,
PaletteControl,
PaletteMirror,
PpuRom,
PpuRam,
// CpuBank(u32),
PrgBank0,
PrgBank1,
ChrBank0,
ChrBank1,
}
impl fmt::Display for SegmentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Clone)]
pub struct Mapped {
cpu: MemoryMap<CPUMMRegisters>,
ppu: MemoryMap<PPUMMRegisters>,
mapper: Remapper,
}
impl Mapped {
pub fn from_rom(rom: &[u8]) -> Self {
let (header, rom) = rom.split_at(0x10);
let nes_20 = header[7] & 0x0C == 0x08;
// assert!(nes_20, "Only supports nes 2.0 format");
// if header[6] & 0b11111110 != 0 {
// todo!("Support other mapper flags, {:08b}", header[6]);
// }
let mapper = if nes_20 {
assert_eq!(header[9], 0, "No support for larger PRG/CHR roms");
assert_eq!(header[10], 0, "No support for PRG-RAM/EEPROM");
assert_eq!(header[11], 0, "No support for CHR-RAM");
assert!(header[12] == 0 || header[12] == 2, "Only support NTSC NES");
Mapper {
horizontal_name_table: header[6] & (1 << 0) == 1,
mapper: ((header[6] as u16 & 0xF0) >> 4)
| ((header[7] as u16 & 0xF0) >> 0)
| ((header[8] as u16 & 0x0F) << 8),
sub_mapper: (header[8] & 0xF0) >> 4,
prg_rom_size: header[4],
chr_rom_size: header[5],
}
} else {
Segment::rom("CHR ROM", 0x0000, rom)
Mapper {
horizontal_name_table: header[6] & (1 << 0) == 1,
mapper: ((header[6] as u16 & 0xF0) >> 4) | ((header[7] as u16 & 0xF0) >> 0),
sub_mapper: 0,
prg_rom_size: header[4],
chr_rom_size: header[5],
}
};
if self.horizontal_name_table {
MemoryMap::new(vec![
chr,
Segment::ram("Internal VRAM", 0x2000, 0x400),
Segment::ram("Internal VRAM", 0x2400, 0x400),
Segment::mirror("Internal VRAM", 0x2800, 0x400, 1),
Segment::mirror("Internal VRAM", 0x2C00, 0x400, 2),
Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1),
Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette),
Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 6),
])
let (prg_rom, rom) = rom.split_at(mapper.prg_rom_size as usize * 0x4000);
let (chr_rom, rom) = rom.split_at(mapper.chr_rom_size as usize * 0x2000);
// assert_eq!(rom.len(), 0);
// let prg_rom = &file[self.cpu_offset()..self.ppu_offset()];
let mut cpu_segments = vec![
Segment::ram(SegmentId::InternalRam, 0x0000, 0x0800),
Segment::mirror(SegmentId::InternalRamMirror, 0x0800, 0x1800, 0x0000),
Segment::reg(SegmentId::PpuRegisters, 0x2000, 0x0008, CPUMMRegisters::PPU),
Segment::reg(
SegmentId::ApuIoRegisters,
0x4000,
0x0018,
CPUMMRegisters::APU,
),
];
let mut ppu_segments = vec![
Segment::mirror(SegmentId::VramMirror, 0x3000, 0x0F00, 0x0000),
Segment::reg(
SegmentId::PaletteControl,
0x3F00,
0x0020,
PPUMMRegisters::Palette,
),
Segment::mirror(SegmentId::PaletteMirror, 0x3F20, 0x00E0, 0x3F00),
];
if mapper.horizontal_name_table {
ppu_segments.push(Segment::ram(SegmentId::Nametable0, 0x2000, 0x400));
ppu_segments.push(Segment::ram(SegmentId::Nametable1, 0x2400, 0x400));
ppu_segments.push(Segment::mirror(
SegmentId::Nametable2,
0x2800,
0x400,
0x2000,
));
ppu_segments.push(Segment::mirror(
SegmentId::Nametable3,
0x2C00,
0x400,
0x2400,
));
} else {
MemoryMap::new(vec![
chr,
Segment::ram("Internal VRAM", 0x2000, 0x400),
Segment::mirror("Internal VRAM", 0x2400, 0x400, 1),
Segment::ram("Internal VRAM", 0x2800, 0x400),
Segment::mirror("Internal VRAM", 0x2C00, 0x400, 3),
Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1),
Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette),
Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 6),
])
ppu_segments.push(Segment::ram(SegmentId::Nametable0, 0x2000, 0x400));
ppu_segments.push(Segment::mirror(
SegmentId::Nametable1,
0x2400,
0x400,
0x2000,
));
ppu_segments.push(Segment::ram(SegmentId::Nametable2, 0x2800, 0x400));
ppu_segments.push(Segment::mirror(
SegmentId::Nametable3,
0x2C00,
0x400,
0x2800,
));
}
let remap = if mapper.mapper == 0 {
cpu_segments.push(Segment::rom(
SegmentId::PrgRom,
0x8000 + (0x8000 - prg_rom.len() as u16),
prg_rom,
));
if chr_rom.len() == 0 {
ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000));
} else {
ppu_segments.push(Segment::rom(SegmentId::PpuRom, 0, chr_rom));
}
Remapper::None
} else if mapper.mapper == 1 && mapper.sub_mapper == 0 {
let prg_banks: Vec<Arc<[u8]>> =
prg_rom.chunks(0x4000).map(|ch| Arc::from(ch)).collect();
for (i, b) in prg_banks.iter().enumerate() {
println!("{i}: {:X?}", &b[0x3FF0..]);
}
cpu_segments.push(Segment::rom(
SegmentId::PrgBank0,
0x8000,
prg_banks.first().unwrap().clone(),
));
cpu_segments.push(Segment::rom(
SegmentId::PrgBank1,
0xC000,
prg_banks.last().unwrap().clone(),
));
println!("CHR_ROM: {}", chr_rom.len());
let chr_banks: Vec<Arc<[u8]>> =
chr_rom.chunks(0x1000).map(|ch| Arc::from(ch)).collect();
if chr_rom.len() == 0 {
ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000));
} else {
ppu_segments.push(Segment::rom(
SegmentId::ChrBank0,
0x0000,
chr_banks[0].clone(),
));
ppu_segments.push(Segment::rom(
SegmentId::ChrBank1,
0x1000,
chr_banks[1].clone(),
));
}
Remapper::MMC1 {
shift_reg: 0,
count: 0,
prg_banks,
chr_banks,
mode: MMC1Mode::LastFixed,
cur_prg_bank: 0,
}
} else {
todo!()
};
Self {
cpu: MemoryMap {
edit_ver: 0,
segments: cpu_segments,
},
ppu: MemoryMap {
edit_ver: 0,
segments: ppu_segments,
},
mapper: remap,
}
}
pub fn power_cylce(&mut self) -> Self {
// TODO: mapper needs to reset cpu and ppu mem maps
let mut cpu = self.cpu.power_cycle();
let mut ppu = self.ppu.power_cycle();
Self {
mapper: self.mapper.power_cycle(&mut cpu, &mut ppu),
cpu,
ppu,
}
}
pub fn peek_ppu(&self, addr: u16) -> Option<u8> {
self.ppu.peek(addr)
}
pub fn ppu_edit_ver(&self) -> usize {
self.ppu.edit_ver
}
pub fn peek_cpu(&self, addr: u16) -> Option<u8> {
self.cpu.peek(addr)
}
pub fn cpu_edit_ver(&self) -> usize {
self.cpu.edit_ver
}
#[cfg(test)]
pub fn test_ram() -> Self {
Self {
cpu: MemoryMap {
edit_ver: 0,
segments: vec![Segment::ram(SegmentId::TestRam, 0, 0x8000)],
},
ppu: MemoryMap {
edit_ver: 0,
segments: vec![Segment::ram(SegmentId::TestRam, 0, 0x8000)],
},
mapper: Remapper::None,
}
}
}
pub struct CpuMem<'a> {
mem: &'a mut Mapped,
ppu: &'a mut PPU,
apu: &'a mut APU,
dma: &'a mut DmaState,
controllers: &'a mut Controllers,
}
impl<'a> CpuMem<'a> {
pub fn new(
mem: &'a mut Mapped,
ppu: &'a mut PPU,
apu: &'a mut APU,
dma: &'a mut DmaState,
controllers: &'a mut Controllers,
) -> Self {
Self {
mem,
ppu,
apu,
dma,
controllers,
}
}
pub fn read(&mut self, addr: u16) -> u8 {
match self.mem.mapper {
_ => (),
}
match self.mem.cpu.read(addr) {
Value::Value(v) => v,
Value::Register { reg, offset } => match reg {
CPUMMRegisters::PPU => self.ppu.read_reg(&mut PpuMem::new(self.mem), offset),
CPUMMRegisters::APU => {
if offset == 0x014 {
todo!("OAM DMA")
} else if offset == 0x16 {
self.controllers.read_joy1()
} else if offset == 0x17 {
self.controllers.read_joy2()
} else {
self.apu.read_reg(offset)
}
}
},
}
}
pub fn write(&mut self, addr: u16, val: u8) {
match &mut self.mem.mapper {
Remapper::MMC1 {
shift_reg,
count,
mode,
prg_banks,
chr_banks,
cur_prg_bank,
} if addr & 0x8000 != 0 => {
if val & 0x80 != 0 {
*shift_reg = 0;
*count = 0;
} else if *count == 4 {
let val = (*shift_reg << 1) | (val & 0x01);
if (addr & 0x6000) >> 13 == 0 {
// TODO: fix mem layout if it's changed
if val & 0b01100 == 0b01000 {
*mode = MMC1Mode::FirstFixed;
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
bank0.swap_rom(prg_banks[0].clone());
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
bank1.swap_rom(prg_banks[*cur_prg_bank].clone());
} else if val & 0b01100 == 0b01100 {
*mode = MMC1Mode::LastFixed;
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
bank0.swap_rom(prg_banks[*cur_prg_bank].clone());
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
bank1.swap_rom(prg_banks.last().unwrap().clone());
} else {
*mode = MMC1Mode::Full;
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
bank0.swap_rom(prg_banks[*cur_prg_bank & !1].clone());
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
bank1.swap_rom(prg_banks[(*cur_prg_bank & !1) + 1].clone());
}
if val & 0b10000 == 0b10000 {
// TODO: CHR-ROM mode
}
// TODO: Set name table mirroring
if val & 0b00011 == 0b00000 {
} else if val & 0b00011 == 0b00001 {
} else if val & 0b00011 == 0b00010 {
} else {
}
} else if (addr & 0x6000) >> 13 == 1 {
if chr_banks.len() != 0 {
todo!("Swap CHR bank 0")
}
} else if (addr & 0x6000) >> 13 == 2 {
if chr_banks.len() != 0 {
todo!("Swap CHR bank 1")
}
} else if (addr & 0x6000) >> 13 == 3 {
*cur_prg_bank = (val & 0x0F) as usize;
match mode {
MMC1Mode::Full => {
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
bank0.swap_rom(prg_banks[*cur_prg_bank & !1].clone());
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
bank1.swap_rom(prg_banks[(*cur_prg_bank & !1) + 1].clone());
}
MMC1Mode::FirstFixed => {
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
bank1.swap_rom(prg_banks[(val & 0x0F) as usize].clone());
}
MMC1Mode::LastFixed => {
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
bank0.swap_rom(prg_banks[(val & 0x0F) as usize].clone());
}
}
// TODO: handle MSB, changes some stuff...
} else {
todo!("Handle reg write {} => {}", val, (addr & 0x6000) >> 13,)
}
*shift_reg = 0;
*count = 0;
} else {
*shift_reg = (*shift_reg << 1) | (val & 0x01);
*count += 1;
}
}
_ => (),
}
match self.mem.cpu.write(addr, val) {
Some((CPUMMRegisters::PPU, offset, val)) => {
self.ppu.write_reg(&mut PpuMem::new(self.mem), offset, val)
}
Some((CPUMMRegisters::APU, offset, val)) => {
if offset == 0x014 {
*self.dma = DmaState::Running {
cpu_addr: (val as u16) << 8,
rem: 0xFF,
read: None,
};
} else if offset == 0x16 {
self.controllers.write_joy_strobe(val);
} else {
self.apu.write_reg(offset, val);
}
}
_ => (),
}
}
}
pub struct PpuMem<'a>(&'a mut Mapped);
impl<'a> PpuMem<'a> {
pub fn new(mem: &'a mut Mapped) -> Self {
Self(mem)
}
pub fn read(&mut self, addr: u16) -> Value<'_, PPUMMRegisters> {
self.0.ppu.read(addr)
}
pub fn write(&mut self, addr: u16, val: u8, reg_fn: impl FnOnce(&PPUMMRegisters, u16, u8)) {
match self.0.ppu.write(addr, val) {
Some((r, o, v)) => reg_fn(&r, o, v),
None => (),
}
}
}
#[derive(Debug, Clone)]
enum MMC1Mode {
Full,
FirstFixed,
LastFixed,
}
#[derive(Debug, Clone)]
enum Remapper {
None,
MMC1 {
shift_reg: u8,
count: u8,
prg_banks: Vec<Arc<[u8]>>,
chr_banks: Vec<Arc<[u8]>>,
mode: MMC1Mode,
cur_prg_bank: usize,
},
}
impl Remapper {
fn power_cycle(
&mut self,
cpu: &mut MemoryMap<CPUMMRegisters>,
_ppu: &mut MemoryMap<PPUMMRegisters>,
) -> Self {
match self {
Remapper::None => Remapper::None,
Remapper::MMC1 {
prg_banks,
chr_banks,
..
} => {
let prg_banks = std::mem::take(prg_banks);
let chr_banks = std::mem::take(chr_banks);
cpu.find(SegmentId::PrgBank0)
.unwrap()
.swap_rom(prg_banks[0].clone());
cpu.find(SegmentId::PrgBank1)
.unwrap()
.swap_rom(prg_banks.last().unwrap().clone());
Remapper::MMC1 {
shift_reg: 0,
count: 0,
prg_banks,
chr_banks,
mode: MMC1Mode::LastFixed,
cur_prg_bank: 0,
}
}
}
}
}