emulators.flycast.Flycast
Exit-only Flycast (Sega Dreamcast) launcher.
Exit-only Flycast (Sega Dreamcast) launcher.
Save state is triggered by a graceful window-close request (Alt+F4) rather than a control socket, since Flycast has no other clean shutdown path; see the module docstring for the full hardware/protocol rationale.
Attributes
attributename= 'flycast'attributerom_cacheable= Trueattributedisplay_name= 'Flycast'attributesave_root= DATA_DIR.parentattributesave_subtrees= (DATA_DIR.name,)attributeclears_stale_saves= Trueattributerom_extensions= ROM_EXTENSIONSattributelog_path= FLYCAST_LOG_PATHattributeterm_timeout= float(os.environ.get('FLYCAST_STOP_WAIT', '20'))Functions
func__init__(self) -> NoneInitialize the emulator with no ROM loaded yet.
paramselfReturns
Nonefuncsave_file_kind(self, rel) -> strClassify an archive member for the manifest.
Flycast keeps the savestate loose beside the VMU images, so the
subtree-based default cannot split them; the .state suffix can.
paramselfparamrelstrThe member path, relative to save_root and posix-separated.
Returns
strstate for the savestate, save for everything else.
funcimport_spec(self) -> imports.ImportSpecDeclare what Flycast takes: VMU and flash saves, raw VMU cards, and one resume state.
The state rides the archive and resumes through save.resume_slot,
like the one the broker saves on exit. There is still no mid-session
save or load, so supports_states stays False.
paramselfReturns
webstation_broker.imports.ImportSpecThe spec. Its paths follow DATA_DIR, so it is built on every call.
funcplace_import(self, member, spec, ctx) -> Union[imports.Placement, imports.ImportRefusal]Place one declared member in the flat data dir.
paramselfparammemberimports.ImportMemberThe member, already past the kind gate.
paramspecimports.ImportSpecThis emulator's spec.
paramctximports.ImportCtxThe launch context.
Returns
typing.UnionThe placement, or a refusal.
funcresolve_rom_file(self, path) -> Optional[Path]Resolve a ROM path to a single playable disc image or file.
A pattern that cannot be walked (an unreadable subdirectory, a broken mount) costs only what that pattern would have contributed: the candidates already found still rank, since reporting a title unbootable over one bad directory is worse than booting the best of what could be read.
paramselfparampathPathA direct file path, or a folder searched up to one level deep for the best candidate, ranked by disc number, extension preference, and path depth.
Returns
typing.OptionalThe resolved file path, or None if no suitable candidate was
func_xdotool(self, *args) -> Optional[str]Run xdotool, returning its stdout, or None if it failed.
paramselfparamargsstr= ()Returns
typing.Optional[str]func_window(self) -> Optional[str]The window id belonging to this launch's own process, or None.
Matched by title then confirmed by pid rather than trusting the first title hit, since Alt+F4 acts on whatever id comes back and a stray match would close the wrong thing instead of just failing a hotkey.
paramselfReturns
typing.Optional[str]func_close_request(self, win_id) -> boolActivate the emulator's own window and send it Alt+F4.
The keystroke goes through XTEST (no key --window) on purpose:
Alt+F4 is a labwc keybind, and the compositor only acts on input that
reaches the seat, so a synthetic event delivered straight to the
client window would never fire its Close action and flycast would
never see SDL_QUIT. XTEST lands on whatever holds focus, so focus is
read back and the request is abandoned when a different window owns
it, rather than closing something the player did not ask to close.
paramselfparamwin_idstrWindow id already confirmed to belong to this launch.
Returns
boolTrue when the keystroke was sent, False when the window could not
funclaunch(self, rom_path, resume_slot) -> NoneStop any running instance and launch Flycast for the given ROM.
paramselfparamrom_pathPathThe resolved disc image or file to boot.
paramresume_slotOptional[int]If not None and a loadable resume state claimed by this rom exists, enable auto-load on boot; otherwise boot fresh.
Returns
Nonefuncstop(self) -> NoneRequest a graceful exit via Alt+F4, then escalate if it does not take.
Activates the emulator's own window and sends Alt+F4 to trigger Flycast's only clean-shutdown path (SDL_QUIT). Falls back to the base class's SIGTERM/SIGKILL escalation if no window can be found, activated or focused, or if the process has not exited within term_timeout.
paramselfReturns
Nonefuncclear_working_slot(self, excluded=()) -> NoneDrop every session-owned file left in DATA_DIR before a restore.
Resume states, their owner markers, VMU images, arcade nvmem and eeprom all sit loose in one flat directory, none of them namespaced by title or by player, and this runs before the rom is even known (activate resolves the rom, clears the slot, then launches). A leftover would either outrank its incoming archive member by mtime and get skipped by extract_save_archive's newer-file guard, or, with no member to replace it at all, be handed to the next player and swept into their exit archive as their own progress. Anything still here belongs to a session that already exited, so dropping it all is the same trade-off DuckStation's clear_working_slot makes.
CONTAINER_FILES and subdirectories (flycast's controller mappings)
are left: they are container setup, not one session's data. So are
states set aside under UNTRUSTED_SUFFIX, which can be the only copy
of that progress and which no resume can pick up anyway.
paramselfparamexcludedtuple[str, ...]= ()Subtrees carried by the whole-card routes. Flycast names no memory card subtree, so this is always empty.
Returns
Nonefunc_set_aside_untrusted_state(self, path) -> NoneRename a resume state that a force-killed exit may have torn, marker and all.
Size and mtime are all the broker has to judge a state by, and that
is enough to refuse to resume from one but not enough to destroy the
only copy of a player's progress, so the file is moved to a
UNTRUSTED_SUFFIX sidecar instead of unlinked. A previous sidecar
for the same rom is replaced, which bounds the leftovers at one per
title. The owner marker travels with it: left behind it would claim
the next state flycast writes under the same basename.
paramselfparampathPathThe resume state to move aside.
Returns
Nonefuncsave_and_exit(self, slot) -> dictStop the emulator and report whether this exit actually saved state.
A state the exit may have torn is not reported and not resumed from,
but it is kept: _set_aside_untrusted_state renames it rather than
deleting it. That quarantine does not depend on slot, since
AutoSaveState is on for every launch and the caller dumps DATA_DIR by
mtime whether or not a slot was asked for.
paramselfparamslotOptional[int]The resume slot to report on. If None, a torn state is still caught and set aside, but a good one is not waited for or reported.
Returns
dictA dict with "state_saved" (bool), "state_slot" (the given