diff --git a/services/flysim/crates/flybrain-gb/src/adapter.rs b/services/flysim/crates/flybrain-gb/src/adapter.rs index fb3f28a..15d8456 100644 --- a/services/flysim/crates/flybrain-gb/src/adapter.rs +++ b/services/flysim/crates/flybrain-gb/src/adapter.rs @@ -20,12 +20,37 @@ use crate::ratchet::RecoveryPolicy; /// equivalent of the prototype's `MemoryReader` interface. pub trait MemoryReader { fn read8(&mut self, address: u16) -> u8; + + /// One byte of a ROM bank, by bank number rather than off the CPU bus. + /// + /// [`MemoryReader::read8`] reads the bus, where banks 1 and up are whichever + /// bank the cartridge's last switch left mapped -- so a table in bank 3 is + /// unreadable through it, and the only way to make it readable would be to + /// *write* the mapper's bank register. The joypad is the only write this + /// workspace makes into a running game (`docs/design/macros.md` section 12), + /// so this reads the cartridge image the process already holds instead: the + /// same bytes, addressed the way the disassembly addresses them. + /// + /// `address` is a CPU address: below `$4000` it is bank 0 whatever `bank` + /// says, and `$4000..$8000` is the banked window. Anything else, and any + /// offset past the end of the image, is `None`. + /// + /// The default is `None`: a reader with no cartridge behind it cannot answer, + /// and every caller of this is written to narrow rather than guess when it + /// does not (`docs/design/macros-wram.md`, the whole-map grid). + fn read_rom(&mut self, _bank: u8, _address: u16) -> Option { + None + } } impl MemoryReader for &mut dyn MemoryReader { fn read8(&mut self, address: u16) -> u8 { (**self).read8(address) } + + fn read_rom(&mut self, bank: u8, address: u16) -> Option { + (**self).read_rom(bank, address) + } } /// One reward payout in one frame. diff --git a/services/flysim/crates/flybrain-gb/src/emulator.rs b/services/flysim/crates/flybrain-gb/src/emulator.rs index 1b6f7eb..dd9b3e3 100644 --- a/services/flysim/crates/flybrain-gb/src/emulator.rs +++ b/services/flysim/crates/flybrain-gb/src/emulator.rs @@ -26,6 +26,10 @@ pub const CPU_TICKS_PER_SECOND: u64 = 4_194_304; /// so there is no `NEW_FRAME` event to wait for yet. const MAX_FRAME_ATTEMPTS: u32 = 120; +/// Bytes in one ROM bank (`$4000`), which is how [`Emulator::read_rom_bank`] +/// turns a bank number and a CPU address into an offset in the cartridge image. +const ROM_BANK_BYTES: usize = 0x4000; + /// Audio frequency flysim runs the emulator at. binjgb resamples internally to /// whatever is requested, so this is only a default. pub const DEFAULT_AUDIO_FREQUENCY: u32 = 48_000; @@ -135,6 +139,13 @@ impl FrameCache { /// thread. It is deliberately not `Sync`. pub struct Emulator { gb: *mut ffi::FlyGb, + /// The cartridge image, for [`MemoryReader::read_rom`]. + /// + /// The shim owns its own padded copy behind the handle and does not hand it + /// back, so this is a second one. It is read-only from here: nothing in this + /// workspace writes a ROM byte, and the bank-addressed read is the only + /// reason it is kept. + rom: std::sync::Arc<[u8]>, rom_sha256: [u8; 32], audio_frequency: u32, /// Raw binjgb samples drained since the last [`Emulator::take_audio`]. @@ -167,6 +178,7 @@ impl Emulator { debug_assert_eq!(unsafe { ffi::fly_gb_frame_buffer_size() }, FRAMEBUFFER_LEN); Ok(Self { gb, + rom: rom.into(), rom_sha256: Sha256::digest(rom).into(), audio_frequency, pending_audio: Vec::new(), @@ -242,6 +254,25 @@ impl Emulator { unsafe { ffi::fly_gb_read_mem(self.gb, address) } } + /// One byte of ROM bank `bank`, from the cartridge image rather than the bus. + /// + /// `address` is a CPU address: `$0000..$4000` is bank 0 whatever `bank` says + /// (that is what "always mapped" means) and `$4000..$8000` is the banked + /// window. `None` for any other address and for an offset past the end of the + /// image, which is what a bank a smaller cartridge does not have reads as. + /// No bank register is written and the emulator's state does not move: this + /// is a read of bytes the process already owns. + pub fn read_rom_bank(&self, bank: u8, address: u16) -> Option { + let offset = match address { + 0x0000..=0x3fff => usize::from(address), + 0x4000..=0x7fff => { + usize::from(bank) * ROM_BANK_BYTES + usize::from(address) - ROM_BANK_BYTES + } + _ => return None, + }; + self.rom.get(offset).copied() + } + /// Sample rate of the raw buffer, as binjgb configured it. pub fn audio_frequency(&self) -> u32 { self.audio_frequency @@ -364,6 +395,10 @@ impl MemoryReader for Emulator { fn read8(&mut self, address: u16) -> u8 { self.read_wram(address) } + + fn read_rom(&mut self, bank: u8, address: u16) -> Option { + self.read_rom_bank(bank, address) + } } #[cfg(test)]