-
Notifications
You must be signed in to change notification settings - Fork 0
Mobile and Cross Platform
rusty2600-mobile is a std, host-testable, platform-agnostic bridge over the core System, exposing a small UniFFI surface (load_rom/run_frame/save_state/load_state/is_rom_loaded) that the same generated bindings drive from both Kotlin (Android) and Swift (iOS) — one Rust implementation, two mobile hosts, unchanged across both.
The original plan called for a dedicated rusty2600-android glue crate doing JNI, native-window-to-wgpu rendering, and AAudio — "the only unsafe surface" on that platform. The actual design instead has run_frame return a plain owned Vec<u8> RGBA8 framebuffer and Vec<f32> normalized audio samples — data, not a handle to draw into. That reframing made a native-rendering glue crate unnecessary entirely: Android's own Bitmap (via copyPixelsFromBuffer) and AudioTrack (via ENCODING_PCM_FLOAT) already consume exactly those two shapes with zero hand-written JNI or unsafe on the Kotlin side — a strict improvement on this project's own unsafe-confinement convention, not a corner cut. The iOS app follows the identical pattern with Metal + AVAudioEngine.
-
MobileEmulator—new(),load_rom(bytes, rom_tag),is_rom_loaded(),run_frame(input),save_state(),load_state(bytes). -
MobileInput— both joysticks, all four paddles, and console switches, as named fields (UniFFI'sRecordderive doesn't support fixed-size array fields). -
FrameOutput—rgba: Vec<u8>(the emulated frame, same crop the desktop and WebAssembly builds apply) andaudio_samples: Vec<f32>(DC-blocked, normalized, at the TIA's native rate — the host resamples, not this crate).
cargo-ndk cross-compiles the Android cdylib; a bindgen-gated uniffi-bindgen binary target generates the Kotlin bindings from the compiled library's own embedded metadata. A key discovery from the iOS work: uniffi-bindgen generate only introspects that embedded metadata, which is identical regardless of which platform compiled the library — so a plain Linux x86_64 build of rusty2600-mobile is enough to generate real, correct Swift bindings, even on a machine with no Apple toolchain at all. The Swift bindings checked into the iOS app were genuinely generated this way.
A real Gradle project (not a placeholder): a custom View blitting the RGBA8 framebuffer via Bitmap.copyPixelsFromBuffer, a MainActivity driving run_frame at ~60 Hz on a background thread, audio through AudioTrack, and on-screen joystick/console-switch buttons. Verified running on a real Android emulator — booted, installed, launched crash-free, a test ROM loaded through the real system file picker, screenshots captured.
Mirrors the desktop convention exactly (see Frontend-Architecture): 8 numbered slots per ROM, one file per slot, kept under a directory keyed by the ROM's own content-hash tag so two different ROMs' slots can never collide even if written under the wrong key by mistake.
Reuses rusty2600-mobile unchanged for a SwiftUI host, following the identical MobileInput/FrameOutput data-not-handle pattern. Unlike Android, the iOS app has not been compiled or run anywhere in this project's development environment — there is no Mac toolchain available — so it ships source-complete-but-unverified, stated honestly rather than claimed as tested. This is a permanent boundary of the current development environment, not a scope decision that will be revisited by more releases; it needs a real Mac session whenever one becomes available.
Researched, not implemented: the reference implementations for cloud sync on each platform (CloudKit on iOS, Google Play Games Services v2 Snapshots on Android) both require live backend credentials this development environment has none of. Deferred with the concrete reference implementation documented for a future session with the right credentials, rather than guessed at.
Architecture-Overview · Frontend-Architecture · Deferred-Features
Rusty2600 is a cycle-accurate Atari 2600 (VCS) emulator written in pure Rust. Licensed under MIT OR Apache-2.0. | GitHub Repository | Web Demo