pub struct Action53M28 { /* private fields */ }Expand description
Mapper 28 (Action 53 homebrew multicart).
Implemented from the NESdev wiki “Action 53 mapper” page (vendored at
nesdev_wiki/output/Action_53_mapper.md) and pinned to Damian Yerrick’s
test28 ROM (tests/roms/nes-test-roms/other/test28.nes).
Four registers are selected through $5000-$5FFF (bit 7 = supervisor, bit
0 = register) and written through $8000-$FFFF, with no bus conflicts:
$00CHR bank: bits 0-1 pick one of four 8 KiB banks of the 32 KiB CHR RAM.$01inner PRG bank: bits 0-3.$80mode: bits 0-1 mirroring (0/1 = 1-screen lower/upper, 2 = vertical, 3 = horizontal), bits 2-3 PRG mode, bits 4-5 outer bank size (32/64/128/256 KiB).$81outer PRG bank: all 8 bits.
While mirroring is 1-screen, D4 of a write to $00 or $01 replaces
mirroring bit 0 (AxROM’s single-screen select); in V/H it is ignored.
PRG resolution follows the wiki’s 12-row table: the “o” bits of the 16 KiB
bank number come from the top of the outer register and the “i” bits from
the bottom of the inner register, the number of inner bits growing with the
outer bank size. The fixed half of the UNROM-style modes ($8000 in mode
2, $C000 in mode 3) is resolved as if the size were 32 KiB, so all outer
bits pass straight through. Power-on maps the last 16 KiB at $C000; reset
leaves the mapper untouched.
Until v2.9.3 this board shifted the outer bank left instead of masking its
low bits, masked the inner bank to one bit, fixed the wrong half in modes 2
and 3, ignored the CHR bank register and the D4 mirroring write, and
powered on at bank 1 in $C000 – so test28 failed its first check. The
review thread on #97 reported the banking half.
Implementations§
Source§impl Action53M28
impl Action53M28
Sourcepub fn new(
prg_rom: Box<[u8]>,
_chr_rom: &[u8],
_mirroring: Mirroring,
) -> Result<Self, MapperError>
pub fn new( prg_rom: Box<[u8]>, _chr_rom: &[u8], _mirroring: Mirroring, ) -> Result<Self, MapperError>
Construct a new mapper 28 board.
§Errors
Returns MapperError::Invalid when PRG is not a non-zero multiple of
16 KiB.
Trait Implementations§
Source§impl Mapper for Action53M28
impl Mapper for Action53M28
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 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 current_mirroring(&self) -> Mirroring
fn current_mirroring(&self) -> Mirroring
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 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 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_cpu_cycle(&mut self)
fn notify_cpu_cycle(&mut self)
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 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 has_hardwired_mirroring(&self) -> bool
fn has_hardwired_mirroring(&self) -> bool
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 sram(&self) -> &[u8] ⓘ
fn sram(&self) -> &[u8] ⓘ
Source§fn sram_mut(&mut self) -> &mut [u8] ⓘ
fn sram_mut(&mut self) -> &mut [u8] ⓘ
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 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 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.