coopdx-rs is a single-binary SDL2 application written in Rust. The entire UI, audio playback, game launching, and mod management run in one process. Understanding the module layout, state machine, and concurrency model makes it straightforward to navigate the codebase and extend it safely.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/retired64/sm64coopdx_launcher/llms.txt
Use this file to discover all available pages before exploring further.
Module structure
The crate is organized into two layers: a set of logic modules at the top level, and two subdirectory module groups for managers and UI components.Top-level modules
| Module | Responsibility |
|---|---|
main | Initializes all SDL2 subsystems (video, audio, TTF, image, joystick), owns the event loop, drives the AppState machine, and holds all render state |
config | Declares compile-time const values (window size, font sizes, colour constants, ROM MD5) and LazyLock<PathBuf> statics that resolve XDG asset paths at first use |
game | Resolves the game binary path through a five-level priority chain, validates the SM64 US ROM by MD5, and spawns the game process with the correct arguments |
assets | Provides FontCache, which loads and caches SDL2_ttf Font objects keyed by point size, avoiding repeated disk reads when the same font size is needed across frames |
error | Defines LauncherResult<T> = Result<T, String>, a lightweight error type used throughout to propagate SDL2 and I/O failures to main |
Manager modules
The five managers inmanagers/ each own one domain of persistent state. They read from and write to files under ~/.local/share/sm64coopdx/ and return plain Rust types — they never touch SDL2 or the renderer.
| Module | Domain |
|---|---|
mod_manager | Scans mods/ for .lua files; reads and writes the enabled-mod list in sm64config.txt |
dynos_manager | Scans dynos/packs/ for installed packs; reads and writes the enabled-pack list |
profile_manager | Creates, renames, deletes, and activates player profiles under profiles/; reads/writes profile.json |
network_manager | Reads and writes the network section of sm64config.txt (mode, server, CoopNet settings) |
download_manager | Downloads mod ZIP files over HTTP and extracts them into data_dir/; communicates progress via Arc<Mutex<DownloadProgress>> |
UI modules
The nine modules inui/ are pure rendering and layout helpers. Each one takes mutable Canvas plus read-only state and produces SDL2 draw calls. None of them own persistent state or perform I/O.
| Module | Renders |
|---|---|
common | Shared item-list layout, hit-testing helpers, scroll helpers |
menu | Arc menu overlay (five items in a curved layout) |
panel | Sliding side panel — header, body, footer; PanelState tracks the slide animation |
splash | Loading screen with animated progress bar and fade transitions |
vinyl | Spinning vinyl disc and track info display |
logo | SM64 Coop DX logo render with drop shadow |
keyboard | On-screen virtual keyboard for text input (profiles, network config) |
network_form | Network configuration form fields and mode selector |
download_browser | Mod browser with tag chips, author filter, search, and paginated list |
Application state machine
The entire application is driven by a singleAppState enum. Each frame, the event loop reads app_state to determine which events are active, which UI layers to render, and which transitions to allow.
Splash
The launcher starts here. Assets are loaded sequentially on the main thread across seven phases — nav sound, splash sound, vinyl texture, logo texture, icon surface, music track scan, and background texture. A progress bar driven by
PHASE_DELTAS advances after each phase. When all phases complete and the minimum display time (SPLASH_MIN_MS = 3000 ms) has elapsed, the state transitions to Game.Game
The main screen. The background, spinning vinyl disc, logo, track info, and “By Retired64” creator button are rendered each frame. Pressing Enter or gamepad A transitions to Launching. Pressing Tab or gamepad Start transitions to Menu. Holding gamepad B (or keyboard K) for 5 seconds transitions to ShuttingDown.
Menu
An arc menu overlay rendered on top of the Game screen. The menu lists five entries: Mod Manager, DynOS Packs, Profiles, Network, and Download Browser. Selecting an entry transitions to SubScreen and slides the corresponding panel in. Pressing Tab, Escape, or gamepad B returns to Game.
SubScreen
A side panel covers part of the screen. The active
SubScreenType (set in PanelState) determines what the panel renders: a toggleable item list (mods, DynOS packs), a profile list, a profile detail editor, a network form, or the download browser. Pressing Escape or gamepad B closes the panel and returns to Game.Launching
A fade-to-black overlay plays over
FADE_DURATION_MS (500 ms). After the fade, game::validate_launch checks that lang/English.ini exists and the data directory is writable, then searches for a valid US SM64 ROM by MD5 hash and copies it to ~/.local/share/sm64coopdx/baserom.us.z64. If validation passes, the game process is spawned. If it fails, an error message is rendered in red and the state returns to Game.Assets are loaded sequentially on the main thread during
Splash, not in a background thread. Since there is no user interaction during the splash screen, the simpler in-line loading approach gives identical visual feedback (a progress bar) without requiring Arc<Mutex<...>> wrappers around SDL2 textures, which cannot be sent across threads.Concurrency model
The launcher intentionally keeps nearly all state on the main thread. Only three patterns involve non-main threads, and in every case the non-main thread is forbidden from mutating UI state directly.Audio hook (SDL2_mixer callback)
sdl2::mixer::Music::hook_finished fires on the SDL2 audio thread when a music track ends. The callback must not touch any SDL2 render state. The solution is a single AtomicBool:
TRACK_FINISHED. When it is true, the main loop performs the actual track advance — loading the next .ogg file and calling music.play(0) — then resets the flag. The audio thread never touches music or texture state.
Game process monitor
After the game is spawned, a background thread callschild.wait() and sets a static GAME_EXITED AtomicBool when the process exits. The main loop polls this flag each frame and, when set, restores the music volume and updates the UI.
Download thread
Mod downloads are handled by astd::thread::spawn closure that runs the blocking HTTP fetch and ZIP extraction. Progress and cancellation are the only shared state:
progress under a Mutex lock after each chunk. The main loop reads it each frame to update the progress bar. The cancel flag is set via cancel.store(true, Ordering::Relaxed) when the user presses Escape, causing the download thread to abort cleanly.
Gamepad support
Gamepad input uses the SDL2 joystick subsystem (sdl2::joystick, not the higher-level sdl2::controller). Raw button indices and hat states are translated into logical actions by two pure functions with no SDL2 dependency:
GpAction | Meaning |
|---|---|
Confirm | Accept / select (A button, Enter key) |
Cancel | Back / close (B button, Escape key) |
Activate | Secondary action — activate profile (X button, Space key) |
NavUp | Navigate cursor up |
NavDown | Navigate cursor down |
NavLeft | Navigate cursor left / previous mode |
NavRight | Navigate cursor right / next mode |
PagePrev | Previous page in Download Browser (L1, PageUp) |
PageNext | Next page in Download Browser (R1, PageDown) |
Menu | Toggle arc menu (Start button, Tab key) |
None | No-op — returned for unmapped buttons and hat states (e.g. HatState::Centered) |