Initial commit: NES emulator with GTK4 desktop frontend
Some checks failed
CI / rust (push) Has been cancelled
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.
This commit is contained in:
114
src/native_core/test_support.rs
Normal file
114
src/native_core/test_support.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user