seam: a bank-aware ROM read beside the bus read
MemoryReader::read8 reads the CPU bus, where banks 1 and up are whichever bank the cartridge last switched to, so a table in another bank could only be read by writing the mapper register -- and the joypad is the only write this workspace makes into a running game. read_rom takes a bank and a CPU address and reads the cartridge image the process already holds instead. Defaulted to None, so every reader without a cartridge behind it narrows rather than guesses.
This commit is contained in:
parent
320b05ecee
commit
d75b7320ea
2 changed files with 60 additions and 0 deletions
|
|
@ -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<u8> {
|
||||
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<u8> {
|
||||
(**self).read_rom(bank, address)
|
||||
}
|
||||
}
|
||||
|
||||
/// One reward payout in one frame.
|
||||
|
|
|
|||
|
|
@ -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<u8> {
|
||||
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<u8> {
|
||||
self.read_rom_bank(bank, address)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue