emulators.flycast
Flycast (Sega Dreamcast) launcher.
Flycast (Sega Dreamcast) launcher.
Handles ROM resolution, save state via a graceful close request, and boot-time resume.
Flycast has no control socket and, unlike DuckStation, installs no
SIGTERM/SIGINT handler at all: a bare SIGTERM is a hard kill. Its only
graceful-shutdown path is SDL_QUIT (a window close request), which
dc_exit() turns into unloadGame(), and that is also the only place
Dreamcast.AutoSaveState gets written.
Getting a real SDL_QUIT out of it takes more than an xdotool window-close
command: windowclose is a raw XDestroyWindow, which never reaches SDL's own
event loop, and windowquit's _NET_CLOSE_WINDOW is sent to the root window
for a window manager to pick up, but this container's compositor (labwc, on
wlroots) has no handler for that message at all, only for its own internal
Close action. That action is what actually sends a real WM_DELETE_WINDOW to
the window (wlroots' wlr_xwayland_surface_close()), and the only way to
reach it externally is the same keybind a player would use: Alt+F4, which
labwc's default config binds to Close. stop() therefore activates the
window and sends that keypress, exactly the hotkey-injection shape
dolphin.py/ppsspp.py already use for their own hotkeys, before falling back
to the base SIGTERM->SIGKILL, the same graceful-trigger-then-escalate shape
shadPS4 uses for its IPC STOP command.
Everything the broker needs (auto-load, auto-save, slot, fullscreen) rides
-config section:key=value transient overrides on the command line rather
than a patched ini, so nothing here touches emu.cfg. The section for the
typed Dreamcast.* options is "config" (that's the whole key, dot included;
see core/cfg/option.cpp), not "Dreamcast" as the key's own name might
suggest.
Save state is a single deterministic file, <rom-basename>.state, in the
flat XDG data dir ($XDG_DATA_HOME/flycast or ~/.local/share/flycast, same
resolution flycast itself does). With slot pinned to 0 the broker can compute
that path itself instead of guessing at the newest or most-recently-changed
file in the directory. clear_working_slot() still has to sweep the directory
though: the name is only unambiguous once the rom is known, and that happens
after clear_working_slot() runs (activate resolves the rom, then clears the
slot, then launches), so a leftover from an earlier local session for the
same title would otherwise sit there with a newer mtime than the incoming
archive member and get skipped by extract_save_archive's newer-file guard,
same as DuckStation's problem.
VMU saves are loose files in the same flat data dir (vmu_save_A1.bin etc., shared across games by default). The whole directory ships as one save subtree rather than named files, since nothing here otherwise separates VMU saves from savestates from a first-run onboarding artifact.
attributelog= logging.getLogger(__name__)attributeROM_ROOT= Path(os.environ.get('ROM_ROOT', '/romm'))attributeFLYCAST_BIN= os.environ.get('FLYCAST_BIN', '/opt/flycast/AppRun')attributeDATA_DIR= Path(os.environ.get('FLYCAST_DATA_DIR', _default_data_dir()))attributeFLYCAST_LOG_PATH= Path(os.environ.get('FLYCAST_LOG_PATH', '/config/flycast.log'))attributeROM_EXTENSIONS= ('.chd', '.gdi', '.cdi', '.cue', '.elf')