Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ jobs:
# `hd-pack` placeholder) — testing it directly instead of re-listing the flags keeps this
# step and `full`'s own Cargo.toml definition from silently drifting apart.
- run: cargo clippy -p rustysnes-frontend --all-targets --features full -- -D warnings
# `rustysnes-libretro` (`v1.2.0`): already covered by the `--workspace` clippy line above (a
# regular workspace member, no non-default features to combo over), but clippy doesn't
# necessarily exercise the FFI-crate-specific bit that matters most for a `cdylib`/
# `staticlib`: does it actually LINK. A build-only step (not `cargo test` -- this crate has
# no tests of its own; its logic lives in and is tested by `rustysnes-core`) is cheap and
# catches a broken libretro C-ABI export/link before it reaches a release artifact.
- run: cargo build -p rustysnes-libretro
# Cheap locally (~4s) so it belongs on every PR, not just tag pushes -- catches broken
# intra-doc links and rustdoc-specific warnings clippy's own lints don't cover.
- run: RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps
Expand Down
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(breakpoints, single-step, VRAM viewer scroll) on top of the relocated facade. Zero behavior
change: every pure-facade method is a one-line delegation, verified by the unchanged frontend
test suite, the full ROM-oracle battery, and the `no_std` CI job (the acid test that the new
`#[cfg(feature = "std")]` gate actually removes the facade from the `thumbv7em` build). See
`#[cfg(feature = "std")]` gate actually removes the facade from the `thumbv7em` build). Also
fixes a determinism-seed-discarding bug found in review: `load_rom`/`power_cycle`/`close_rom`
rebuilt `System::new(0)` on every call, silently ignoring the caller's seed. See
`docs/architecture.md` §3/§6 and `docs/frontend.md`.

### Added

- **`rustysnes-libretro`: a libretro core.** A thin C-ABI wrapper over
`rustysnes_core::facade::EmuCore`, loadable by RetroArch or any other libretro-compatible
frontend — region-aware NTSC/PAL geometry+timing, the S-DSP's real 32 kHz output rate,
coprocessor firmware auto-resolution from the frontend's system directory, Game Genie/Pro
Action Replay cheat support, and raw WRAM/VRAM/SRAM memory-map pointers for RetroArch's own
SRAM autosave and RetroAchievements/cheat tooling. Peripheral negotiation (Mouse/Super
Scope/Multitap via `RETRO_DEVICE_SUBCLASS`) is a documented follow-up, not yet wired. New
additive `Bus::wram`/`wram_mut`, `Ppu::vram`/`vram_mut`, `Cart::sram_mut` accessors support it.
See `docs/libretro.md`.

## [1.1.0] "Latchkey" - 2026-07-11

### Fixed
Expand Down
154 changes: 152 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"crates/rustysnes-cheevos",
"crates/rustysnes-script",
"crates/rustysnes-frontend",
"crates/rustysnes-libretro",
"crates/rustysnes-test-harness",
]

Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ that). The reproducible record (methodology, all benches, and save-state cost) i
| [Project status matrix](docs/STATUS.md) | Per-suite pass count, coprocessor coverage, feature flags, version policy — the single source of truth |
| [Architecture](docs/architecture.md) | System design and the load-bearing decisions |
| [Frontend](docs/frontend.md) | The desktop/wasm shell, save states, pacing, the debugger overlay, scripting, netplay, RetroAchievements |
| [Libretro core](docs/libretro.md) | `rustysnes-libretro`, a RetroArch-loadable core — build steps, manual verification, known scope cuts |
| [CHANGELOG.md](CHANGELOG.md) | Version history and release notes |
| [Roadmap](to-dos/ROADMAP.md) | The forward roadmap — the phase spine |
| [Version plan](to-dos/VERSION-PLAN.md) | The named, versioned release ladder to `v1.0.0` and beyond |
Expand Down Expand Up @@ -359,9 +360,9 @@ API docs (rustdoc) at

## Current Release

RustySNES's current release is **v1.0.1 "Aftertouch"**. See
RustySNES's current release is **v1.1.0 "Latchkey"**. See
[`docs/STATUS.md`](docs/STATUS.md) for the full release history
(`v0.1.0` through `v1.0.1`) and per-release detail.
(`v0.1.0` through `v1.1.0`) and per-release detail.

- **Download:** the [GitHub Releases](https://github.com/doublegate/RustySNES/releases) page —
desktop binaries for Linux, macOS (aarch64), and Windows.
Expand All @@ -374,16 +375,20 @@ RustySNES's current release is **v1.0.1 "Aftertouch"**. See
explicitly deferred out of that cut: per-channel (per-voice) audio mutes and global keyboard
hotkeys — both landed (see the Desktop UX + Audio sections above, `CHANGELOG.md`).

**`v1.1.0`** (in progress) is a research + accuracy pass: a real, independent bug fix
**`v1.1.0`** was a research + accuracy pass: a real, independent bug fix
(`SuperFxBoard::map`'s Game-Pak-RAM-ownership open-bus gap), `emu-thread`'s two biggest gaps
closed (real audio output + a proper pause/ROM-loaded/speed lifecycle — still not full parity
with the synchronous drive), and three accuracy investigations (open-bus-via-DMA-latch, DRAM
refresh timing, and a fractional-timebase-refactor go/no-go assessment) — see `CHANGELOG.md` and
`to-dos/VERSION-PLAN.md`'s `v1.1.0` section for the full breakdown, including what's still open.

**`v1.2.0`** (in progress) relocates the pure `EmuCore` embedding facade into
`rustysnes-core::facade` and lands a real **Libretro core** (`rustysnes-libretro`, loadable by
RetroArch — region-aware NTSC/PAL, cheats, coprocessor firmware auto-resolution, raw memory-map
pointers; see `docs/libretro.md`); a **CRT/HQ2x shader pipeline** is next.

**Still deferred:** HD texture packs (the `hd-pack` flag exists in the manifest as a forward
placeholder; the loader itself is a TODO stub), a Libretro core, and a CRT/HQ2x shader pipeline —
all planned for the `v1.2.0`/`v1.3.0` follow-up arc.
placeholder; the loader itself is a TODO stub) — planned for `v1.3.0`.

The full roadmap lives in [`to-dos/ROADMAP.md`](to-dos/ROADMAP.md) (the phase spine) and
[`to-dos/VERSION-PLAN.md`](to-dos/VERSION-PLAN.md) (the named release ladder).
Expand Down
10 changes: 10 additions & 0 deletions crates/rustysnes-cart/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ impl Cart {
dst[..n].copy_from_slice(&data[..n]);
}

/// The mutable counterpart to [`Self::save_sram`] — for a host embedder that needs a raw
/// memory-map pointer (e.g. a libretro core's `RETRO_MEMORY_SAVE_RAM`, which some frontends
/// write through directly rather than only calling [`Self::load_sram`]).
pub fn sram_mut(&mut self) -> &mut [u8] {
self.board.sram_mut()
}

/// Advance any on-cart coprocessor by one of its clock units. Default boards no-op.
pub fn coprocessor_tick(&mut self) {
self.board.coprocessor_tick();
Expand Down Expand Up @@ -186,5 +193,8 @@ mod tests {
snap[0x10] = 0xEE;
cart.load_sram(&snap);
assert_eq!(cart.read24(0x70_0010, 0x00), 0xEE);
// sram_mut() is the same backing storage save_sram()/load_sram() operate on.
cart.sram_mut()[0x20] = 0x11;
assert_eq!(cart.read24(0x70_0020, 0x00), 0x11);
}
}
25 changes: 25 additions & 0 deletions crates/rustysnes-core/src/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,21 @@ impl Bus {
}
}

/// 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`).
#[must_use]
pub fn wram(&self) -> &[u8] {
&*self.wram
}

/// 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).
pub fn wram_mut(&mut self) -> &mut [u8] {
&mut *self.wram
}

/// 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
Expand Down Expand Up @@ -1187,6 +1202,16 @@ mod tests {
assert_eq!(<Bus as CpuBus>::read24(&mut bus, 0x7E_0042), 0x99);
}

#[test]
fn wram_and_wram_mut_expose_the_same_flat_128kib() {
let mut bus = Bus::default();
assert_eq!(bus.wram().len(), 0x2_0000);
<Bus as CpuBus>::write24(&mut bus, 0x7E_1234, 0xAB);
assert_eq!(bus.wram()[0x1234], 0xAB);
bus.wram_mut()[0x5678] = 0xCD;
assert_eq!(<Bus as CpuBus>::read24(&mut bus, 0x7E_5678), 0xCD);
}

#[test]
fn peek_reads_wram_without_side_effects() {
let mut bus = Bus::default();
Expand Down
Loading