Skip to content

FFI sound and saves; update to Rust 1.99 and current deps - #7

Merged
raphamorim merged 3 commits into
mainfrom
ffi-sound-and-saves
Oct 23, 2026
Merged

raphamorim merged 3 commits into
mainfrom
ffi-sound-and-saves

Conversation

@raphamorim

@raphamorim raphamorim commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

Makes the C FFI usable for a real native host (built for a SwiftUI macOS frontend): sound, persistent battery saves, and errors instead of aborts.

  • Correct mapper for FFI and wasm loads. get_mbc treated a missing file path as "use MBC3 without saves", so every FFI/wasm game ran on MBC3. Each mapper now takes Option<PathBuf> and saves are simply disabled without one.
  • On-demand saves. MemoryBankController::save() writes cartridge RAM (MBC3 keeps its RTC prefix). Drop still saves. Writes go to a .gbsave.tmp and are renamed, so a crash never leaves a truncated save. This matters for FFI because the global GAMEBOY static is never dropped.
  • No panics on bad input. Gameboy::try_new / Cpu::try_new return Result; new keeps its old behaviour. With panic = "abort" a bad ROM used to kill the host process.
  • New FFI functions (added to gameboy.h):
    • load_with_save(bytes, len, rom_path): like load, with .gbsave next to rom_path; returns a status code
    • save(): flush cartridge RAM now
    • enable_audio(): start cpal output, returns the sample rate
    • audio_queued(): queued stereo frames, so hosts can pace frame() to the audio clock (displays run at 60 Hz, the Game Boy at 59.73 Hz)
    • last_error(): message for the last failure
  • cpal 0.16 → 0.18. cpal 0.16's CoreAudio supported_configs passes &data_size of an immutable local as an out-parameter. In optimized builds the compiler assumes it stays 0, reserves a zero-sized buffer, and CoreAudio writes to 0x1 (EXC_BAD_ACCESS). Reproduced on macOS 26.5 in release builds; fixed upstream in 0.17.

Toolchain and dependency update

CI's Make desktop build step was failing (on main too): the examples have no lockfile, so they resolve dependencies fresh, and current crates need Rust 1.85+ and edition 2024.

  • rust-toolchain: 1.83.0 → 1.99.1
  • cpal 0.18, ratatui 0.30, ratatui-image 11, icy_sixel 0.5, base64 0.23, blip_buf 0.2, image 0.25.10, wasm-bindgen / js-sys / web-sys latest
  • glutin 0.28 → 0.32. Windowing moved out of glutin, so screen/desktop.rs now runs on winit 0.30 + glutin-winit with an ApplicationHandler. Same window, key mapping, pause-when-unfocused and ~60 FPS pacing; it also updates the GL viewport on resize now.
  • ratatui-image is built without its default chafa-dyn feature, which requires the system libchafa via pkg-config.
  • blip_buf 0.2 returns Err where 0.1 asserted; those calls now drop the sample instead of aborting.
  • Fixes for new clippy lints (repeat_n, is_multiple_of, format width).

Testing

CI passes (lint, tests, desktop build). Locally on macOS 26.5 with Rust 1.99.1:

  • make lint (clippy -D warnings), cargo test --release: pass
  • cargo build --release --target wasm32-unknown-unknown: builds
  • examples/desktop and examples/terminal: build
  • Desktop window (new winit/glutin code) runs Pokémon Silver, checked with a screenshot
  • cargo build --release --no-default-features --features ffi: builds; tested from a Swift host with Pokémon Silver (MBC3 + RTC + battery): audio callback runs, .gbsave is written (8-byte RTC + 32 KiB RAM)
  • Not tested: the terminal frontend interactively, Linux/Windows desktop windows, reloading a save after an in-game save

Expose sound and battery-backed saves to C hosts, and stop the FFI
build from forcing every cartridge onto MBC3.

- get_mbc no longer special-cases a missing path as MBC3; every mapper
  takes an optional save path, so FFI and wasm loads get the right MBC.
- MemoryBankController::save() writes cartridge RAM on demand (Drop still
  saves too). Writes go through a temp file and a rename.
- Gameboy::try_new / Cpu::try_new return errors instead of panicking,
  which matters with panic = "abort" in release.
- New FFI: load_with_save, save, enable_audio, audio_queued, last_error.
  audio_queued lets hosts pace frame() to the audio clock.
- Bump cpal to 0.17: 0.16's CoreAudio supported_configs passes a pointer
  to an immutable local as an out-parameter, so optimized builds reserve
  a zero-sized buffer and crash at 0x1 when querying the device.
CI's desktop example step resolves dependencies fresh (examples have no
lockfile), and current crates need Rust 1.85+ and edition 2024, so the
pinned 1.83 toolchain could no longer build it.

- rust-toolchain: 1.83.0 -> 1.99.1
- cpal 0.18, ratatui 0.30, ratatui-image 11, icy_sixel 0.5, base64 0.23,
  blip_buf 0.2, image 0.25.10, wasm-bindgen/js-sys/web-sys latest
- glutin 0.28 -> 0.32: windowing moved out of glutin, so the desktop
  screen now runs on winit 0.30 + glutin-winit (ApplicationHandler).
  Same window, keys, focus pause and ~60 FPS pacing; it now also updates
  the GL viewport on resize.
- blip_buf 0.2 returns Err where 0.1 asserted; those calls now drop the
  sample instead of aborting.
- Fixes for new clippy lints (repeat_n, is_multiple_of, format width).
ratatui-image 11 enables chafa-dyn by default, which needs the system
libchafa via pkg-config. Keep just the crossterm backend so the terminal
frontend has no new system dependency.
@raphamorim raphamorim changed the title FFI: sound, battery saves and fallible loading FFI sound and saves; update to Rust 1.99 and current deps Oct 23, 2026
@raphamorim
raphamorim merged commit 177ee1e into main Oct 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant