use std::{fmt, sync::Arc}; use bitfield::bitfield; use crate::{ CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState, ppu::PPUMMRegisters, }; pub trait Memory { fn peek(&self, val: usize) -> Option; fn len(&self) -> usize; fn edit_ver(&self) -> usize; } #[derive(Debug, Clone)] pub enum Value { Value(u8), Register { reg: R, offset: u16 }, } impl Value { pub fn reg_map(self, f: impl FnOnce(R, u16) -> u8) -> u8 { match self { Value::Value(v) => v, Value::Register { reg, offset } => f(reg, offset), } } } #[derive(Debug, Clone)] pub enum Data { RAM(Vec), ROM(Arc<[u8]>), Mirror(u16), Reg(R), // Disabled, } #[derive(Debug, Clone)] pub struct Segment { name: SegmentId, position: u16, size: u16, mem: Data, } impl Segment { fn is(&self, id: SegmentId) -> bool { self.name == id } fn ram(name: SegmentId, position: u16, size: u16) -> Self { Self { name, position, size, mem: Data::RAM(vec![0u8; size as usize]), } } fn rom(name: SegmentId, position: u16, raw: Raw) -> Self where Arc<[u8]>: From, { let raw = Arc::from(raw); Self { name, position, size: raw.len() as u16, mem: Data::ROM(raw), } } fn reg(name: SegmentId, position: u16, size: u16, reg: R) -> Self { Self { name, position, size, mem: Data::Reg(reg), } } fn mirror(name: SegmentId, position: u16, size: u16, of: u16) -> Self { Self { name, position, size, mem: Data::Mirror(of), } } // fn take_rom(&mut self) -> Vec { // 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) { // 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 { edit_ver: usize, segments: Vec>, // map: Remapper, } impl MemoryMap { pub fn read(&self, addr: u16) -> Value { // self.edit_ver += 1; for segment in &self.segments { if segment.position <= addr && addr - segment.position < segment.size { return match &segment.mem { Data::RAM(items) => Value::Value(items[(addr - segment.position) as usize]), Data::ROM(items) => Value::Value(items[(addr - segment.position) as usize]), Data::Reg(reg) => Value::Register { reg: *reg, offset: addr - segment.position, }, Data::Mirror(pos) => { let offset = addr - segment.position; self.read(pos + offset) // let s = &self.segments[*index]; // self.read(s.position + offset % s.size) } // Data::Disabled => todo!(), }; } } // TODO: Open bus Value::Value(0) // todo!("Open bus") } 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) => None, Data::Reg(reg) => Some((*reg, addr - segment.position, val)), Data::Mirror(pos) => { let pos = *pos; let offset = addr - segment.position; self.write(pos + offset, val) // let index = *index; // let s = &self.segments[index]; // self.write(s.position + offset % s.size, val) } // Data::Disabled => todo!(), }; } } // iirc, open bus just drops writes None // todo!("Open bus (${addr:04X})") } // pub fn clear(&mut self) { // for s in &mut self.segments { // match &mut s.mem { // Data::RAM(items) => items.fill(0), // _ => (), // } // } // } 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(), } } fn find(&mut self, id: SegmentId) -> Option<&mut Segment> { for s in &mut self.segments { if s.is(id) { return Some(s); } } None } fn peek_val(&self, addr: u16) -> Result> { for segment in &self.segments { if segment.position <= addr && addr - segment.position < segment.size { return match &segment.mem { Data::RAM(items) => Ok(items[(addr - segment.position) as usize]), Data::ROM(items) => Ok(items[(addr - segment.position) as usize]), Data::Reg(r) => Err(Some((*r, addr - segment.position))), Data::Mirror(pos) => { let offset = addr - segment.position; self.peek_val(pos + offset) } // Data::Disabled => Err(None), }; } } Err(None) } } impl Memory for MemoryMap { fn peek(&self, addr: usize) -> Option { self.peek_val(addr as u16).ok() } fn len(&self) -> usize { self.segments .iter() .map(|s| s.position as usize + s.size as usize) .max() .unwrap_or(0) } fn edit_ver(&self) -> usize { self.edit_ver } } bitfield! { #[derive(Clone, Copy, PartialEq, Eq)] struct ShiftPair(u8); impl Debug; high, set_high: 7, 4; low, set_low: 3, 0; } impl ShiftPair { fn low_count(&self) -> usize { if self.low() == 0 { 0 } else { 64 << self.low() } } fn high_count(&self) -> usize { if self.high() == 0 { 0 } else { 64 << self.high() } } } #[derive(Clone, Copy, PartialEq, Eq)] struct Mapper { is_nes_2: bool, horizontal_name_table: bool, mapper: u16, sub_mapper: u8, prg_rom_size: u8, chr_rom_size: u8, prg_nv_ram_size: ShiftPair, chr_nv_ram_size: ShiftPair, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum SegmentId { #[allow(unused)] TestRam, InternalRam, InternalRamMirror, PpuRegisters, ApuIoRegisters, PrgRom, Nametable0, Nametable1, Nametable2, Nametable3, VramMirror, PaletteControl, PaletteMirror, PpuRom, PpuRam, // CpuBank(u32), PrgBank0, PrgBank1, ChrBank0, ChrBank1, NVRam, } impl fmt::Display for SegmentId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } #[derive(Debug, Clone)] pub struct Mapped { cpu: MemoryMap, ppu: MemoryMap, 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 { is_nes_2: true, 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], prg_nv_ram_size: ShiftPair(header[10]), chr_nv_ram_size: ShiftPair(header[11]), } } else { Mapper { is_nes_2: false, 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], prg_nv_ram_size: ShiftPair(0), chr_nv_ram_size: ShiftPair(0), } }; 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); println!("Mapper: {}/{}", mapper.mapper, mapper.sub_mapper); assert_eq!( mapper.prg_nv_ram_size.low_count(), 0, "No support for PRG-RAM" ); // assert_eq!( // mapper.chr_nv_ram_size.low_count(), // 0, // "No support for CHR-RAM" // ); assert_eq!( mapper.chr_nv_ram_size.high_count(), 0, "No support for CHR-NVRAM" ); // 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, ), ]; // CHR-NVRAM if mapper.prg_nv_ram_size.high_count() > 0 { cpu_segments.push(Segment::ram( SegmentId::NVRam, 0x6000, mapper.prg_nv_ram_size.high_count() as u16, )); } 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 { 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 mapper.is_nes_2 { if mapper.chr_nv_ram_size.low_count() > 0 { ppu_segments.push(Segment::ram( SegmentId::PpuRam, 0, mapper.chr_nv_ram_size.low_count() as u16, )); } else { ppu_segments.push(Segment::rom(SegmentId::PpuRom, 0, chr_rom)); } } else { 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> = 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> = 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) -> Result> { self.ppu.peek_val(addr) } pub fn ppu_edit_ver(&self) -> usize { self.ppu.edit_ver } pub fn peek_cpu(&self, addr: u16) -> Option { self.cpu.peek_val(addr).ok() } 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) << 4); 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; // println!("Updating ROM: new {:X}", val); 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(); // Highest bit is ignored bank1.swap_rom(prg_banks[(val & 0x07) as usize].clone()); } MMC1Mode::LastFixed => { let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap(); // Highest bit is ignored bank0.swap_rom(prg_banks[(val & 0x07) 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) << 4); *count += 1; // println!("Mapper SHR {:05b}", *shift_reg); } } _ => (), } 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::Idle((val as u16) << 8); } 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 { 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>, chr_banks: Vec>, mode: MMC1Mode, cur_prg_bank: usize, }, } impl Remapper { fn power_cycle( &mut self, cpu: &mut MemoryMap, _ppu: &mut MemoryMap, ) -> 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, } } } } }