Initial commit: NES emulator with GTK4 desktop frontend
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:
2026-03-13 11:48:45 +03:00
commit bdf23de8db
143 changed files with 18501 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
use super::*;
pub(crate) struct InesMapper118 {
mmc3: Mmc3,
}
impl InesMapper118 {
pub(crate) fn new(rom: InesRom) -> Self {
Self {
mmc3: Mmc3::new(rom),
}
}
}
impl Mapper for InesMapper118 {
fn cpu_read(&self, addr: u16) -> u8 {
self.mmc3.cpu_read(addr)
}
fn cpu_write(&mut self, addr: u16, value: u8) {
self.mmc3.cpu_write(addr, value);
}
fn cpu_read_low(&self, addr: u16) -> Option<u8> {
self.mmc3.cpu_read_low(addr)
}
fn cpu_write_low(&mut self, addr: u16, value: u8) -> bool {
self.mmc3.cpu_write_low(addr, value)
}
fn ppu_read(&self, addr: u16) -> u8 {
self.mmc3.ppu_read(addr)
}
fn ppu_write(&mut self, addr: u16, value: u8) {
self.mmc3.ppu_write(addr, value);
}
fn mirroring(&self) -> Mirroring {
self.mmc3.mirroring()
}
fn map_nametable_addr(&self, addr: u16) -> Option<usize> {
if !(0x2000..=0x3EFF).contains(&addr) {
return None;
}
// TxSROM-class boards route CHR bank bit 7 (A17) to CIRAM A10 for NT fetches.
// The board responds to $2000-$2FFF the same as MMC3's $0000-$0FFF CHR decode.
let rel = (addr - 0x2000) & 0x0FFF;
let page = (rel / 0x0400) as usize; // NT0..NT3 -> 1KB pages 0..3
let offset = (rel & 0x03FF) as usize;
let bank = self.mmc3.chr_bank_for_1k_page(page) as u8;
let ciram_page = ((bank >> 7) & 1) as usize;
Some(ciram_page * 0x0400 + offset)
}
fn clock_scanline(&mut self) {
self.mmc3.clock_scanline();
}
fn needs_ppu_a12_clock(&self) -> bool {
self.mmc3.needs_ppu_a12_clock()
}
fn poll_irq(&mut self) -> bool {
self.mmc3.poll_irq()
}
fn save_state(&self, out: &mut Vec<u8>) {
self.mmc3.save_state(out);
}
fn load_state(&mut self, data: &[u8]) -> Result<(), String> {
self.mmc3.load_state(data)
}
}