pub struct Fds { /* private fields */ }Expand description
FDS RAM-adapter device, modelled as a Mapper.
Owns the PRG-RAM, CHR-RAM, BIOS, the inserted disk image, all register
state, the timer-IRQ counter, and the disk-read head. Routed by the bus for
every CPU access in $4020-$FFFF; $4020-$409F registers are surfaced via
Mapper::cpu_read_unmapped returning false for that window.
Implementations§
Source§impl Fds
impl Fds
Sourcepub fn new(disk: FdsDisk, bios: &[u8]) -> Result<Self, RomError>
pub fn new(disk: FdsDisk, bios: &[u8]) -> Result<Self, RomError>
Construct an FDS device from a parsed disk image and an 8 KiB BIOS.
§Errors
Returns RomError::InvalidConfig if the BIOS is not exactly 8 KiB.
Sourcepub fn medium_write_verify(&self) -> Result<(), FdsMediumError>
pub fn medium_write_verify(&self) -> Result<(), FdsMediumError>
Walk the synthesized wire image of the currently inserted side and verify every block’s gap / start-mark framing and per-block CRC-16 round-trips — the synthetic FDS write-verify oracle (v2.2.0 “Capstone”).
This is deliberately BIOS-free: it validates the emulator’s own medium
synthesis (gap runs, $80 start marks, CRC-16/KERMIT block CRCs) so the
write path can be exercised and checked entirely in CI without any
copyright FDS BIOS. The real-BIOS write path (which recomputes the CRC in
its own RAM and streams it to $4024) is validated only from a local,
gitignored dump — see docs/accuracy-ledger.md for the CI-verifiable vs
local-only split.
§Errors
Returns the first FdsMediumError encountered (missing start mark,
CRC mismatch, truncated block, or a corrupted inter-block gap). Returns
Ok(()) when no side is inserted (nothing to verify).
Sourcepub const fn set_analog_head_seek(&mut self, enabled: bool)
pub const fn set_analog_head_seek(&mut self, enabled: bool)
Enable or disable the continuous analog head-seek model (default off).
Opt-in accuracy feature: when disabled (the default) motor-restart
rewinds use the flat HEAD_RESEEK_CYCLES window, so a non-writing
.fds run is byte-identical to prior releases. When enabled, the re-seek
window scales with head-travel distance (belt velocity). See
Self::reseek_window_cycles.
Sourcepub const fn analog_head_seek(&self) -> bool
pub const fn analog_head_seek(&self) -> bool
Whether the continuous analog head-seek model is currently enabled.
Sourcepub fn quirk(&self) -> FdsQuirk
pub fn quirk(&self) -> FdsQuirk
The per-game timing quirk resolved from the disk CRC-32 at construction
(quirk_for_crc). FdsQuirk::NONE for titles without a table entry.
Sourcepub fn enable_trace(&mut self)
pub fn enable_trace(&mut self)
Start recording the diagnostic FDS read-stream trace (see FdsTraceRec).
Off by default; recording is pure observation and never affects emulation.
Sourcepub fn take_trace(&mut self) -> Vec<FdsTraceRec>
pub fn take_trace(&mut self) -> Vec<FdsTraceRec>
Drain the accumulated FDS trace records.
Trait Implementations§
Source§impl Mapper for Fds
impl Mapper for Fds
Source§fn sram(&self) -> &[u8] ⓘ
fn sram(&self) -> &[u8] ⓘ
Source§fn sram_mut(&mut self) -> &mut [u8] ⓘ
fn sram_mut(&mut self) -> &mut [u8] ⓘ
Source§fn caps(&self) -> MapperCaps
fn caps(&self) -> MapperCaps
Source§fn cpu_write(&mut self, addr: u16, value: u8)
fn cpu_write(&mut self, addr: u16, value: u8)
$4020-$FFFF.Source§fn cpu_read_unmapped(&self, addr: u16) -> bool
fn cpu_read_unmapped(&self, addr: u16) -> bool
true when addr is not wired to mapper-resident
memory — i.e. when cpu_read(addr) returns junk and the bus
should fall through to the open-bus latch instead of overwriting
it. The CPU databus is left floating in this case, so the most
recently driven byte stays visible to the next read. Read moreSource§fn ppu_read(&mut self, addr: u16) -> u8
fn ppu_read(&mut self, addr: u16) -> u8
$0000-$3FFF (pattern table Read moreSource§fn ppu_write(&mut self, addr: u16, value: u8)
fn ppu_write(&mut self, addr: u16, value: u8)
$0000-$3FFF.Source§fn notify_cpu_cycle(&mut self)
fn notify_cpu_cycle(&mut self)
Source§fn irq_pending(&self) -> bool
fn irq_pending(&self) -> bool
true if the mapper is currently asserting an IRQ.Source§fn irq_acknowledge(&mut self)
fn irq_acknowledge(&mut self)
Source§fn mix_audio(&mut self) -> i32
fn mix_audio(&mut self) -> i32
Source§fn current_mirroring(&self) -> Mirroring
fn current_mirroring(&self) -> Mirroring
Source§fn disk_side_count(&self) -> usize
fn disk_side_count(&self) -> usize
Source§fn inserted_disk_side(&self) -> Option<usize>
fn inserted_disk_side(&self) -> Option<usize>
None when ejected (or for
non-FDS mappers).Source§fn set_disk_side(&mut self, side: Option<usize>)
fn set_disk_side(&mut self, side: Option<usize>)
i (Some) or eject the disk (None). No-op for
non-FDS mappers; an out-of-range index is ignored by the FDS device.Source§fn enable_fds_trace(&mut self)
fn enable_fds_trace(&mut self)
crate::FdsTraceRec.Source§fn take_fds_trace(&mut self) -> Vec<FdsTraceRec>
fn take_fds_trace(&mut self) -> Vec<FdsTraceRec>
Source§fn disk_image_bytes(&self) -> Vec<u8> ⓘ
fn disk_image_bytes(&self) -> Vec<u8> ⓘ
Source§fn disk_is_dirty(&self) -> bool
fn disk_is_dirty(&self) -> bool
false for non-FDS
mappers.Source§fn clear_disk_dirty(&mut self)
fn clear_disk_dirty(&mut self)
Source§fn set_disk_write_protected(&mut self, protected: bool)
fn set_disk_write_protected(&mut self, protected: bool)
true) or writable (false). No-op
for non-FDS mappers.Source§fn save_state(&self) -> Vec<u8> ⓘ
fn save_state(&self) -> Vec<u8> ⓘ
Source§fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError>
fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError>
Mapper::save_state blob back into the mapper. Read moreSource§fn debug_info(&self) -> MapperDebugInfo
fn debug_info(&self) -> MapperDebugInfo
Source§fn notify_floating_read(&mut self, _addr: u16, _value: u8)
fn notify_floating_read(&mut self, _addr: u16, _value: u8)
Self::cpu_read_unmapped) has just
completed, and value is what floated on the bus. Read moreSource§fn notify_ppu_register_write(&mut self, _addr: u16, _value: u8)
fn notify_ppu_register_write(&mut self, _addr: u16, _value: u8)
value to the PPU register window ($2000-$3FFF), at
the undecoded address addr. Read moreSource§fn nametable_unfolded(&self) -> bool
fn nametable_unfolded(&self) -> bool
$3000-$3EFF is independent RAM on this cartridge rather
than a mirror of $2000-$2EFF. Read moreSource§fn cpu_read_driven_mask(&self, _addr: u16) -> u8
fn cpu_read_driven_mask(&self, _addr: u16) -> u8
$4020-$5FFF)
actually drives; the rest float and keep the bus’s open-bus value. Read moreSource§fn ppu_read_sprite(&mut self, addr: u16) -> u8
fn ppu_read_sprite(&mut self, addr: u16) -> u8
$0000-$1FFF) on
behalf of a sprite tile fetch. MMC5 in 8x16 sprite mode uses a
separate set of CHR bank registers ($5120-$5127) for sprite
fetches; other mappers default to forwarding to Mapper::ppu_read.Source§fn chr_phys(&self, _addr: u16) -> Option<u32>
fn chr_phys(&self, _addr: u16) -> Option<u32>
$0000-$1FFF (Some(offset)), or None for CHR-RAM
(content-hashed instead). tile_index = offset / 16 is the key Mesen uses
for CHR-ROM <tile> replacements. Default None so an unported mapper
falls back to the content-hash path (no worse than before); the common
CHR-ROM mappers override it by exposing their internal CHR mapping.Source§fn chr_reads_are_pure(&self) -> bool
fn chr_reads_are_pure(&self) -> bool
T-SPRITE-LIMIT) — whether Self::ppu_read and
Self::ppu_read_sprite on $0000-$1FFF change nothing but return a
byte. true (the default) lets the PPU’s “disable sprite limit” option
make extra, display-only pattern reads on this board. Read moreSource§fn set_mmc3_revision_override(
&mut self,
_revision: Option<Mmc3Revision>,
) -> bool
fn set_mmc3_revision_override( &mut self, _revision: Option<Mmc3Revision>, ) -> bool
T-MMC3-NEC-OVERRIDE, ACC-13) — force an MMC3’s IRQ revision
(Some), or return to the one its header selected (None). Returns
whether this board is an MMC3 that applied it; every other board
ignores it (the default). Lets an iNES 1.0 dump, which cannot name its
MMC3 revision, run under the alternate (Nec) behaviour.Source§fn nametable_fetch(&mut self, _addr: u16) -> Option<u8>
fn nametable_fetch(&mut self, _addr: u16) -> Option<u8>
addr ($2000-$3EFF). Read moreSource§fn nametable_write(&mut self, _addr: u16, _value: u8) -> bool
fn nametable_write(&mut self, _addr: u16, _value: u8) -> bool
addr ($2000-$3EFF) directly
into mapper-resident storage. Read moreSource§fn peek_ex_attribute(&mut self, _v: u16) -> Option<ExAttribute>
fn peek_ex_attribute(&mut self, _v: u16) -> Option<ExAttribute>
Source§fn bg_split_state(
&mut self,
_scanline_y: u16,
_coarse_x: u16,
) -> Option<BgSplitState>
fn bg_split_state( &mut self, _scanline_y: u16, _coarse_x: u16, ) -> Option<BgSplitState>
Source§fn nametable_address(&self, addr: u16) -> u16
fn nametable_address(&self, addr: u16) -> u16
$2000-$3EFF to a CIRAM offset in
0..0x800. The PPU owns the 2 KiB CIRAM and uses this hook to apply
per-mapper mirroring without giving the mapper direct access to the
console-side VRAM. Read moreSource§fn notify_a12(&mut self, _level: bool)
fn notify_a12(&mut self, _level: bool)
Source§fn notify_a12_at_sub_dot(&mut self, level: bool, _sub_dot: u8)
fn notify_a12_at_sub_dot(&mut self, level: bool, _sub_dot: u8)
Self::notify_a12 so existing
mappers compile unchanged; MMC3 overrides this for the
M2-phase-aware IRQ-output propagation delay required by
mmc3_test_2/4-scanline_timing sub-test #3 (C1 step B4 successor).Source§fn notify_frame_event(&mut self, _events: MapperFrameEvents)
fn notify_frame_event(&mut self, _events: MapperFrameEvents)
Source§fn notify_scanline_start(&mut self)
fn notify_scanline_start(&mut self)
Source§fn notify_vblank(&mut self)
fn notify_vblank(&mut self)
Source§fn enable_vs_dual_wram(&mut self)
fn enable_vs_dual_wram(&mut self)
DualSystem): provision the board’s shared 2 KiB
work RAM at $6000-$7FFF (mirrored across the 8 KiB window — MAME
vsnes.cpp: map(0x6000, 0x67ff).mirror(0x1800).ram()). Only the
Vs. System board (mapper 99) implements this — the DualSystem
cabinets carry a 2 KiB RAM shared between the two consoles, absent
on UniSystem carts (whose $6000 window stays open bus,
byte-identically). Called by the VsDualSystem wrapper at
construction on both consoles: each console holds its own COPY, and
the wrapper converges the copies by draining the write log (below)
after every stepped instruction — MAME’s fully-shared
.share("nvram") model at soft-lockstep granularity. Default no-op.Source§fn set_vs_dual_sub(&mut self)
fn set_vs_dual_sub(&mut self)
DualSystem): mark this mapper instance as the
cabinet’s SUB console — it banks the second 32 KiB PRG half and the
upper CHR pages (the two CPUs run DIFFERENT programs; MAME
balonfgt loads distinct sub-region ROMs, Mesen2 uses
prgOuter = IsVsMainConsole() ? 0 : 4). Applied by the
VsDualSystem wrapper at construction, like the bus’s sub
identity. Default no-op.Source§fn drain_vs_dual_wram_writes(&mut self, dst: &mut Vec<(u16, u8)>)
fn drain_vs_dual_wram_writes(&mut self, dst: &mut Vec<(u16, u8)>)
DualSystem): drain this console’s shared-WRAM
write log — every (offset, value) the CPU wrote to the window
since the last drain, in order — by APPENDING into dst (never
clearing it first). The wrapper replays them into the partner’s
copy (Self::apply_vs_dual_wram_write), which is what makes the
RAM behave as ONE simultaneously-shared memory (MAME’s model;
nesdev also documents a $4016-bit-1 access mux, but MAME — where
the four DualSystem games verifiably run — shares the RAM
unconditionally, and Balloon Fight’s boot handshake requires the
partner to see writes made while the mux would deny it access). Read moreSource§fn take_vs_dual_wram_writes(&mut self) -> Vec<(u16, u8)>
fn take_vs_dual_wram_writes(&mut self) -> Vec<(u16, u8)>
Self::drain_vs_dual_wram_writes for
callers that don’t already hold a reusable buffer (diagnostics,
tests) — NOT used by the hot pump_comms path, which owns and
reuses its own scratch buffer instead.Source§fn apply_vs_dual_wram_write(&mut self, offset: u16, value: u8)
fn apply_vs_dual_wram_write(&mut self, offset: u16, value: u8)
DualSystem): replay one partner-console write
into this console’s copy of the shared WRAM (see
Self::take_vs_dual_wram_writes). Does NOT re-log the write (no
echo loop). Default no-op.Source§fn take_vs_dual_wram(&mut self) -> Option<Box<[u8]>>
fn take_vs_dual_wram(&mut self) -> Option<Box<[u8]>>
DualSystem): take the console’s shared-WRAM copy
(used by the wrapper’s snapshot-restore normalization — the two
copies are re-converged from one buffer after a restore). Returns
None on boards without the dual WRAM. Default None.Source§fn set_vs_dual_wram(&mut self, wram: Box<[u8]>)
fn set_vs_dual_wram(&mut self, wram: Box<[u8]>)
DualSystem): install a shared-WRAM copy (the
other half of the restore normalization). Default no-op.Source§fn has_hardwired_mirroring(&self) -> bool
fn has_hardwired_mirroring(&self) -> bool
Source§fn save_data(&self) -> &[u8] ⓘ
fn save_data(&self) -> &[u8] ⓘ
Self::sram, and the default says so. Read moreSource§fn save_data_mut(&mut self) -> &mut [u8] ⓘ
fn save_data_mut(&mut self) -> &mut [u8] ⓘ
Self::save_data, for loading a save.Source§fn clear_save_data(&mut self)
fn clear_save_data(&mut self)
power_on_for_movie).Source§fn nsf_song_count(&self) -> u8
fn nsf_song_count(&self) -> u8
Source§fn nsf_current_song(&self) -> u8
fn nsf_current_song(&self) -> u8
Source§fn nsf_set_song(&mut self, _song: u8) -> bool
fn nsf_set_song(&mut self, _song: u8) -> bool
true if this is an NSF mapper (so the
caller knows to re-run the reset that re-vectors into the driver’s
init). Default no-op returning false.