pub struct Bus {
pub ppu: Ppu,
pub apu: Apu,
pub cart: Option<Cart>,
pub dma: Dma,
pub clock: Clock,
/* private fields */
}Expand description
Everything mutable lives here.
Fields§
§ppu: PpuThe video subsystem (PPU1 + PPU2).
apu: ApuThe audio subsystem (SPC700 + S-DSP + ARAM).
cart: Option<Cart>The loaded cartridge (board mapping + any coprocessor), or None before a ROM loads.
dma: DmaThe 8-channel DMA/HDMA controller ($420B/$420C, $43xx).
clock: ClockThe master-clock phase + CPU timing registers.
Implementations§
Source§impl Bus
impl Bus
Sourcepub fn new(region: Region) -> Self
pub fn new(region: Region) -> Self
Construct a power-on Bus for the given console region.
§Panics
Panics only if the 128 KiB WRAM allocation cannot be sized to the fixed WRAM_SIZE array
(an out-of-memory condition at power-on), which cannot happen for the constant size.
Sourcepub fn sync_region_from_cart(&mut self)
pub fn sync_region_from_cart(&mut self)
Reconfigure the PPU’s region (line count / 50-vs-60 Hz status bit) from the installed
cart’s header, auto-detecting NTSC vs PAL rather than requiring the frontend to guess or
hardcode it. A no-op when no cart is installed. Region only ever affects the PPU’s
line-count/status-bit timeline here — the differing NTSC/PAL master-clock rate (Hz) is a
real-world audio/video pacing concern the frontend owns (docs/adr/0004); the core’s
master-clock counter is a pure tick count, not wall-clock time, so nothing else in the
core depends on which oscillator frequency a real console would use.
Sourcepub fn set_joypad(&mut self, player: usize, state: u16)
pub fn set_joypad(&mut self, player: usize, state: u16)
Set the latched controller state for a player (0 = P1, 1 = P2). 12-bit BYsSUDLR.....
Sourcepub fn joypad(&self, player: usize) -> u16
pub fn joypad(&self, player: usize) -> u16
The latched controller state for a player (0 = P1, 1 = P2) — the read side of
Self::set_joypad, for TAS movie recording (crate::movie::MovieRecorder) and the
debugger overlay.
Sourcepub fn set_port_device(&mut self, port: usize, device: PortDevice)
pub fn set_port_device(&mut self, port: usize, device: PortDevice)
Select which peripheral is connected to controller port port (0 = port 1, 1 = port
2). Defaults to PortDevice::Gamepad on both ports (this project’s original,
unchanged behavior) until a frontend explicitly calls this — a host/session configuration
choice, not emulated state (matching Self::set_cheats/Self::set_watchpoints’s own
“re-established by the frontend, not carried in a save-state” posture — a real SNES has no
memory of what was plugged in across a power cycle either).
Sourcepub fn port_device(&self, port: usize) -> PortDevice
pub fn port_device(&self, port: usize) -> PortDevice
The peripheral currently connected to controller port port — for the debugger overlay
and the frontend’s own input-routing (v0.9.0).
Sourcepub fn set_mouse(
&mut self,
port: usize,
dx: i32,
dy: i32,
left: bool,
right: bool,
)
pub fn set_mouse( &mut self, port: usize, dx: i32, dy: i32, left: bool, right: bool, )
Feed one frame’s worth of SNES Mouse input for port port (only meaningful when that
port’s device is PortDevice::Mouse). dx/dy are raw, unscaled host deltas since the
last call — the SNES Mouse’s own speed multiplier and 127-unit clamp are applied
internally at the hardware-accurate point (latch time), matching real hardware. Same
“always replace, re-synced once per frame” convention as Self::set_joypad.
Sourcepub fn set_superscope(&mut self, port: usize, x: i32, y: i32, buttons: u8)
pub fn set_superscope(&mut self, port: usize, x: i32, y: i32, buttons: u8)
Feed one frame’s worth of Super Scope input for port port (only meaningful when that
port’s device is PortDevice::SuperScope). x/y are absolute screen coordinates in
SNES pixel space (0..256, 0..240-ish; a small negative/over-max margin is allowed and
means “aimed off-screen”, matching real hardware). buttons is a bitmask over
crate::controller::scope’s TRIGGER/CURSOR/TURBO/PAUSE bits — the LIVE physical
switch/button state; this project reproduces real hardware’s own edge-detection internally
(crate::controller::SuperScopeState), so the frontend should pass the raw host state,
not a pre-toggled value. (A packed bitmask rather than one bool per button, matching
Self::set_joypad’s own convention.)
Sourcepub fn set_multitap_pad(&mut self, port: usize, sub_index: usize, buttons: u16)
pub fn set_multitap_pad(&mut self, port: usize, sub_index: usize, buttons: u16)
Feed one frame’s worth of input for Super Multitap sub-pad sub_index (0..=3) of port
port (only meaningful when that port’s device is PortDevice::Multitap) — same 12-bit
BYsSUDLR.... format and per-frame convention as Self::set_joypad.
Sourcepub fn multitap_pad(&self, port: usize, sub_index: usize) -> u16
pub fn multitap_pad(&self, port: usize, sub_index: usize) -> u16
The current input state of Super Multitap sub-pad sub_index of port port — the read
side of Self::set_multitap_pad, for the debugger overlay and TAS movie recording.
Sourcepub fn peek_wram(&self, addr24: u32) -> u8
pub fn peek_wram(&self, addr24: u32) -> u8
Non-intrusive read of WRAM for the test harness + debugger (does NOT advance the clock,
touch open bus, or trip register side effects). I/O registers and the cart region return
0 — this is for inspecting RAM-resident test-result variables, not for emulation.
Sourcepub fn poke_wram(&mut self, addr24: u32, val: u8)
pub fn poke_wram(&mut self, addr24: u32, val: u8)
Non-intrusive write of WRAM (the write counterpart to Self::peek_wram, same
addressing, same “no clock/open-bus/register side effects” contract) — for rustysnes- script’s Lua emu.write. A write to an address outside WRAM’s mirrors is silently
ignored (matching peek_wram’s _ => 0 read side) rather than erroring, since a script
address is arbitrary user input, not a bug to surface loudly. (Cheat codes, T-81-003, use
Self::set_cheats’s CPU-read intercept instead — real Game Genie/Pro Action Replay
codes overwhelmingly target cartridge ROM, which this WRAM-only accessor cannot reach.)
Sourcepub fn wram(&self) -> &[u8] ⓘ
pub fn wram(&self) -> &[u8] ⓘ
The full 128 KiB WRAM as a flat byte slice (linear address 0..0x1_FFFF, the same mapping
Self::peek_wram’s 0x7E..=0x7F bank arm uses) — for a host embedder that needs a raw
memory-map pointer (e.g. a libretro core’s RETRO_MEMORY_SYSTEM_RAM).
Sourcepub fn wram_mut(&mut self) -> &mut [u8] ⓘ
pub fn wram_mut(&mut self) -> &mut [u8] ⓘ
The mutable counterpart to Self::wram — same host-embedder use case (a libretro
frontend’s memory-map API hands this pointer to RetroAchievements/cheat tooling that
writes through it directly).
Sourcepub fn peek(&mut self, addr24: u32) -> u8
pub fn peek(&mut self, addr24: u32) -> u8
Non-intrusive read of an arbitrary 24-bit CPU address, for the debugger overlay’s
disassembly view (v0.9.0, T-81-001 PR B). Unlike CpuBus::read24, this does NOT touch
the open-bus latch, does NOT check watchpoints, and does NOT trigger any I/O register’s own
read side effect (VRAM/CGRAM auto-increment, NMI-flag-clear-on-read, the H/V-counter
latch, …) — genuinely just peeking. Real 65C816 code only ever executes from WRAM or cart
ROM/RAM space, so (mirroring Self::peek_wram’s own “not for register space” posture)
this only special-cases those two regions; any other address returns 0 rather than
reaching into a register’s live side effects, which is fine since real code never lives
there anyway. The cart-space branch still calls into the board (some coprocessors gate
their own ROM/RAM reads on internal state), but passes a neutral 0 open-bus fallback
rather than the Bus’s real, live latch — this peek must never read or write that shared
state.
Sourcepub fn set_cheats(&mut self, patches: &[CheatPatch])
pub fn set_cheats(&mut self, patches: &[CheatPatch])
Install the currently-active cheat-code patches (v0.8.0, T-81-003), replacing any
previously installed set. CpuBus::read24 checks this list on every CPU-visible read
and substitutes a matching patch’s value — the same point in the pipeline real Game
Genie/Pro Action Replay hardware intercepts at, which is why this is a read intercept and
not a poke_wram-style direct write: those codes overwhelmingly target cartridge ROM, not
WRAM, so a write-based model would silently do nothing for the vast majority of real
codes. The underlying ROM/RAM byte is never modified — only what the CPU observes reading
it.
Sourcepub const fn set_voice_mutes(&mut self, mutes: [bool; 8])
pub const fn set_voice_mutes(&mut self, mutes: [bool; 8])
Set the 8 per-voice audio mute toggles (v1.0.1). See
[rustysnes_apu::dsp::Dsp::set_voice_mutes]’s doc for why this is a frontend/debug
convenience re-synced once per real frame, not real S-DSP hardware state.
Sourcepub const fn frame_ready(&self) -> bool
pub const fn frame_ready(&self) -> bool
Whether the PPU has a finished frame ready to present.
Sourcepub fn framebuffer(&self) -> &[u16]
pub fn framebuffer(&self) -> &[u16]
The PPU framebuffer (256×239 15-bit BGR).
Sourcepub fn save_state(&self, w: &mut SaveWriter)
pub fn save_state(&self, w: &mut SaveWriter)
Write the PPU’s own section, the APU’s own section, the DMA controller’s own section,
then a "BUS0" section for WRAM + the Bus’s own timing/register state, then (if a cart is
loaded) its battery SRAM + coprocessor state as a final untagged tail (a presence flag,
the length-prefixed SRAM bytes, then the board’s own save_state bytes — the cart has no
single section of its own since its payload is really “however many bytes the board’s own
implementation writes”). The cart’s ROM/header are NOT written: the caller must reload the
same ROM (Cart::load) and install it before calling Bus::load_state, the same “never
embed a ROM byte” contract every coprocessor board in rustysnes-cart already follows.
Sourcepub fn load_state(
&mut self,
r: &mut SaveReader<'_>,
) -> Result<(), SaveStateError>
pub fn load_state( &mut self, r: &mut SaveReader<'_>, ) -> Result<(), SaveStateError>
The inverse of Self::save_state.
§Errors
SaveStateError on truncated/corrupt input, a section with unconsumed trailing bytes,
or SaveStateError::Invalid if the save-state’s cart presence doesn’t match this
Bus’s own (a save-state taken with a cart loaded can only be restored onto a Bus that
already has the SAME cart’s ROM loaded — via rustysnes_cart::Cart::load — installed
first; there is no ROM byte in the save-state to reconstruct it from) or if a restored
SRAM image’s length doesn’t match the installed cart’s own SRAM size (a mismatched ROM).
Trait Implementations§
Source§impl Bus for Bus
The 65C816’s view: route a 24-bit access + drive the master clock in lockstep.
impl Bus for Bus
The 65C816’s view: route a 24-bit access + drive the master clock in lockstep.
Source§fn read24(&mut self, addr24: u32) -> u8
fn read24(&mut self, addr24: u32) -> u8
Bus::advance around this call).Source§fn write24(&mut self, addr24: u32, val: u8)
fn write24(&mut self, addr24: u32, val: u8)
Bus::read24).Source§fn access_cycles(&self, addr24: u32) -> u32
fn access_cycles(&self, addr24: u32) -> u32
CPU::wait): the region-variable access speed
(FastROM vs SlowROM, WRAM, I/O). Defaults to the SlowROM/internal-cycle cost of 6.Source§fn advance(&mut self, clocks: u32)
fn advance(&mut self, clocks: u32)
clocks master ticks (ares CPU::step), ticking the
PPU/APU/coprocessor and HDMA in lockstep. Default no-op for buses whose timebase is
charged elsewhere (the SA-1 second CPU) or that don’t model timing (unit-test buses).Source§impl DmaBus for Bus
The DMA controller’s view: A-bus (24-bit) via the decode, B-bus via b_read/b_write.
impl DmaBus for Bus
The DMA controller’s view: A-bus (24-bit) via the decode, B-bus via b_read/b_write.
Source§fn read_a(&mut self, addr: u32) -> u8
fn read_a(&mut self, addr: u32) -> u8
Source§fn write_a(&mut self, addr: u32, val: u8)
fn write_a(&mut self, addr: u32, val: u8)
Source§fn step(&mut self, clocks: u32)
fn step(&mut self, clocks: u32)
clocks during a transfer, so time-dependent state that
gates the transfer — chiefly the PPU scanline that decides VRAM/CGRAM/OAM accessibility
($2118/$2119 land only in force-blank or V-blank) — tracks the transfer in lockstep,
exactly as the real bus keeps scanning while a DMA runs (ares Channel::step →
cpu.step). A big GP-DMA that starts late in active display and crosses into V-blank must
have its V-blank portion actually reach VRAM; freezing the scanline for the whole burst
would drop every write (the Star Fox framebuffer-transfer bug). Default no-op so the
controller stays unit-testable against an isolated bus with no clock.Source§fn scanline(&self) -> u16
fn scanline(&self) -> u16
$2100 write on the very lines the DMA is filling). crate::dma::Dma
drives that interleave itself from inside run_gp because the bus’s own per-tick HDMA path
is dormant while the bus has lent out its controller. Default u16::MAX so a clockless
unit-test bus never trips the interleave.Source§fn visible_height(&self) -> u16
fn visible_height(&self) -> u16
visible_height), so the in-GP-DMA
interleave knows the visible-line window. Default 0 (unit-test buses run no HDMA).Source§fn hdma_last_line(&self) -> u16
fn hdma_last_line(&self) -> u16
u16::MAX.Source§fn set_hdma_last_line(&mut self, line: u16)
fn set_hdma_last_line(&mut self, line: u16)
line, syncing the bus’s bookkeeping with the
interleave driven from inside run_gp. Default no-op.