782 lines
25 KiB
Rust
782 lines
25 KiB
Rust
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<u8>;
|
|
fn len(&self) -> usize;
|
|
fn edit_ver(&self) -> usize;
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Value<R> {
|
|
Value(u8),
|
|
Register { reg: R, offset: u16 },
|
|
}
|
|
|
|
impl<R> Value<R> {
|
|
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<R> {
|
|
RAM(Vec<u8>),
|
|
ROM(Arc<[u8]>),
|
|
Mirror(u16),
|
|
Reg(R),
|
|
// Disabled,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Segment<R> {
|
|
name: SegmentId,
|
|
position: u16,
|
|
size: u16,
|
|
mem: Data<R>,
|
|
}
|
|
|
|
impl<R> Segment<R> {
|
|
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<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(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<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: Copy> MemoryMap<R> {
|
|
pub fn read(&self, addr: u16) -> Value<R> {
|
|
// 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<R>> {
|
|
for s in &mut self.segments {
|
|
if s.is(id) {
|
|
return Some(s);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn peek_val(&self, addr: u16) -> Result<u8, Option<(R, u16)>> {
|
|
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<R: Copy> Memory for MemoryMap<R> {
|
|
fn peek(&self, addr: usize) -> Option<u8> {
|
|
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<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 {
|
|
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<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) -> Result<u8, Option<(PPUMMRegisters, u16)>> {
|
|
self.ppu.peek_val(addr)
|
|
}
|
|
pub fn ppu_edit_ver(&self) -> usize {
|
|
self.ppu.edit_ver
|
|
}
|
|
pub fn peek_cpu(&self, addr: u16) -> Option<u8> {
|
|
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<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,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|