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.
|
/// equivalent of the prototype's `MemoryReader` interface.
|
||||||
pub trait MemoryReader {
|
pub trait MemoryReader {
|
||||||
fn read8(&mut self, address: u16) -> u8;
|
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 {
|
impl MemoryReader for &mut dyn MemoryReader {
|
||||||
fn read8(&mut self, address: u16) -> u8 {
|
fn read8(&mut self, address: u16) -> u8 {
|
||||||
(**self).read8(address)
|
(**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.
|
/// 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.
|
/// so there is no `NEW_FRAME` event to wait for yet.
|
||||||
const MAX_FRAME_ATTEMPTS: u32 = 120;
|
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
|
/// Audio frequency flysim runs the emulator at. binjgb resamples internally to
|
||||||
/// whatever is requested, so this is only a default.
|
/// whatever is requested, so this is only a default.
|
||||||
pub const DEFAULT_AUDIO_FREQUENCY: u32 = 48_000;
|
pub const DEFAULT_AUDIO_FREQUENCY: u32 = 48_000;
|
||||||
|
|
@ -135,6 +139,13 @@ impl FrameCache {
|
||||||
/// thread. It is deliberately not `Sync`.
|
/// thread. It is deliberately not `Sync`.
|
||||||
pub struct Emulator {
|
pub struct Emulator {
|
||||||
gb: *mut ffi::FlyGb,
|
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],
|
rom_sha256: [u8; 32],
|
||||||
audio_frequency: u32,
|
audio_frequency: u32,
|
||||||
/// Raw binjgb samples drained since the last [`Emulator::take_audio`].
|
/// 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);
|
debug_assert_eq!(unsafe { ffi::fly_gb_frame_buffer_size() }, FRAMEBUFFER_LEN);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
gb,
|
gb,
|
||||||
|
rom: rom.into(),
|
||||||
rom_sha256: Sha256::digest(rom).into(),
|
rom_sha256: Sha256::digest(rom).into(),
|
||||||
audio_frequency,
|
audio_frequency,
|
||||||
pending_audio: Vec::new(),
|
pending_audio: Vec::new(),
|
||||||
|
|
@ -242,6 +254,25 @@ impl Emulator {
|
||||||
unsafe { ffi::fly_gb_read_mem(self.gb, address) }
|
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.
|
/// Sample rate of the raw buffer, as binjgb configured it.
|
||||||
pub fn audio_frequency(&self) -> u32 {
|
pub fn audio_frequency(&self) -> u32 {
|
||||||
self.audio_frequency
|
self.audio_frequency
|
||||||
|
|
@ -364,6 +395,10 @@ impl MemoryReader for Emulator {
|
||||||
fn read8(&mut self, address: u16) -> u8 {
|
fn read8(&mut self, address: u16) -> u8 {
|
||||||
self.read_wram(address)
|
self.read_wram(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_rom(&mut self, bank: u8, address: u16) -> Option<u8> {
|
||||||
|
self.read_rom_bank(bank, address)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue