Some checks failed
CI / rust (push) Has been cancelled
Full NES emulation: CPU, PPU, APU, 47 mappers, iNES/NES 2.0 parsing. GTK4 desktop client with HeaderBar, pixel-perfect Cairo rendering, drag-and-drop ROM loading, and keyboard shortcuts. 187 tests covering core emulation, mappers, and runtime.
115 lines
3.0 KiB
Rust
115 lines
3.0 KiB
Rust
use crate::native_core::cpu::{Cpu6502, CpuBus};
|
|
use crate::native_core::ines::{InesHeader, InesRom, Mirroring};
|
|
|
|
pub(crate) struct TestRamBus(pub [u8; 0x10000]);
|
|
|
|
impl TestRamBus {
|
|
pub(crate) fn new() -> Self {
|
|
Self([0; 0x10000])
|
|
}
|
|
|
|
pub(crate) fn load(&mut self, start: u16, bytes: &[u8]) {
|
|
for (i, b) in bytes.iter().copied().enumerate() {
|
|
self.0[start as usize + i] = b;
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CpuBus for TestRamBus {
|
|
fn read(&mut self, addr: u16) -> u8 {
|
|
self.0[addr as usize]
|
|
}
|
|
|
|
fn write(&mut self, addr: u16, value: u8) {
|
|
self.0[addr as usize] = value;
|
|
}
|
|
}
|
|
|
|
pub(crate) fn cpu_setup_with_reset(prog: &[u8]) -> (Cpu6502, TestRamBus) {
|
|
let mut bus = TestRamBus::new();
|
|
bus.load(0x8000, prog);
|
|
bus.0[0xFFFC] = 0x00;
|
|
bus.0[0xFFFD] = 0x80;
|
|
let mut cpu = Cpu6502::default();
|
|
cpu.reset(&mut bus);
|
|
(cpu, bus)
|
|
}
|
|
|
|
pub(crate) struct MapperRomBuilder {
|
|
mapper: u16,
|
|
submapper: u8,
|
|
prg_banks_16k: u16,
|
|
chr_banks_8k: u16,
|
|
mirroring: Mirroring,
|
|
}
|
|
|
|
impl MapperRomBuilder {
|
|
pub(crate) fn new(mapper: u16) -> Self {
|
|
Self {
|
|
mapper,
|
|
submapper: 0,
|
|
prg_banks_16k: 1,
|
|
chr_banks_8k: 1,
|
|
mirroring: Mirroring::Horizontal,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn submapper(mut self, submapper: u8) -> Self {
|
|
self.submapper = submapper;
|
|
self
|
|
}
|
|
|
|
pub(crate) fn prg_banks_16k(mut self, banks: u16) -> Self {
|
|
self.prg_banks_16k = banks;
|
|
self
|
|
}
|
|
|
|
pub(crate) fn chr_banks_8k(mut self, banks: u16) -> Self {
|
|
self.chr_banks_8k = banks;
|
|
self
|
|
}
|
|
|
|
pub(crate) fn mirroring(mut self, mirroring: Mirroring) -> Self {
|
|
self.mirroring = mirroring;
|
|
self
|
|
}
|
|
|
|
pub(crate) fn build(self) -> InesRom {
|
|
let prg_rom_len = self.prg_banks_16k as usize * 16 * 1024;
|
|
let chr_len = if self.chr_banks_8k == 0 {
|
|
8 * 1024
|
|
} else {
|
|
self.chr_banks_8k as usize * 8 * 1024
|
|
};
|
|
let chr_data = if self.chr_banks_8k == 0 {
|
|
vec![0; chr_len]
|
|
} else {
|
|
(0..chr_len).map(|v| (v & 0xFF) as u8).collect()
|
|
};
|
|
InesRom {
|
|
header: InesHeader {
|
|
mapper: self.mapper,
|
|
submapper: self.submapper,
|
|
is_nes2: false,
|
|
prg_rom_banks_16k: self.prg_banks_16k,
|
|
chr_rom_banks_8k: self.chr_banks_8k,
|
|
has_trainer: false,
|
|
has_battery: false,
|
|
mirroring: self.mirroring,
|
|
prg_ram_shift: 0,
|
|
prg_nvram_shift: 0,
|
|
chr_ram_shift: 0,
|
|
chr_nvram_shift: 0,
|
|
cpu_ppu_timing_mode: 0,
|
|
vs_hardware_type: 0,
|
|
vs_ppu_type: 0,
|
|
misc_rom_count: 0,
|
|
default_expansion_device: 0,
|
|
},
|
|
prg_rom: (0..prg_rom_len).map(|v| (v & 0xFF) as u8).collect(),
|
|
chr_data,
|
|
chr_is_ram: self.chr_banks_8k == 0,
|
|
}
|
|
}
|
|
}
|