emulators.retroarch.Retroarch
RetroArch driven over its stdin command interface.
RetroArch driven over its stdin command interface.
One launcher for every platform in PLATFORMS: platform is set from the
activate payload before launch, and picks the core, the ROM extensions and
the save scope. Saves, loads and quit go over stdin; replies come back on a
stdout pipe drained by a reader thread, and saves are confirmed on disk.
Declared imports take one .srm save per archive, renamed to the booted
content's stem and placed in saves/<library_name>/, where the core
loads SRAM. Only a platform whose core loads a .srm (save_ram in
PLATFORMS) takes one, and where the core reads a device's .sav the
same way (sav_is_srm), a .sav is placed as the .srm. The Wii and
3DS platforms take the files of their core's own tree instead, a NAND
title folder or a path in Azahar's SD card and NAND, and GameCube and
every other platform whose core keeps its saves elsewhere take nothing.
States are never imported; they are pushed after activate.
Attributes
attributename= 'retroarch'Registry key, retroarch.
attributedisplay_name= 'RetroArch'Shown as "RetroArch".
attributesave_root= RA_DATA_DIRRA_DATA_DIR, the broker-managed data root.
attributestate_subtrees= ('states',)states, where savestates land.
attributelog_path= RA_LOG_PATHRA_LOG_PATH, where RetroArch's stderr goes.
attributesupports_disc_swap= TrueOn; discs are swapped through the virtual tray.
attributesupports_menu= TrueOn; the Quick Menu is toggled over stdin.
attributestate_slot= STATE_SLOTSTATE_SLOT, the one slot the broker works in.
attributestate_dir= STATE_DIRSTATE_DIR, the broker-managed savestate directory.
attributeclears_stale_saves= TrueOn; activate empties the platform's save subtrees.
attributeterm_timeout= float(os.environ.get('RETROARCH_STOP_WAIT', '15'))Seconds QUIT gets before SIGTERM, from
RETROARCH_STOP_WAIT (default 15).
attributeplatformOptional[str]= NoneThe RomM platform slug, set before launch.
attribute_rom_basestr= ''The loaded content's basename, which RetroArch names its state and SRAM files after.
attribute_slot_homed= FalseWhether the current slot has been parked on STATE_SLOT since launch.
attribute_playing_monotonicOptional[float]= Nonetime.monotonic() when _track_first_playing first saw PLAYING; None until then.
Anchors _wait_for_first_save_settle. Reset to None on every launch.
attribute_launch_seq= 0Launch generation, bumped on every launch and stop so stale background waits bail out.
attribute_resume_settle= RESUME_LOAD_SETTLESeconds between PLAYING and a deferred resume load; set from the platform table at launch.
attribute_state_confirm_wait= STATE_CONFIRM_WAITSeconds a save gets to land on disk; set from the platform table at launch.
attribute_stdout_buf= bytearray()Replies read off RetroArch's stdout and not yet consumed.
attribute_stdout_lock= threading.Lock()Guards _stdout_buf between the reader thread and the callers waiting on replies.
attribute_reply_lock= threading.Lock()Serializes command/reply exchanges.
Replies carry no request id, so two threads waiting at once would each be free to consume the other's line.
attribute_readerOptional[threading.Thread]= NoneThe thread draining RetroArch's stdout into _stdout_buf.
attribute_playlistOptional[Path]= NoneThe playlist this session booted, or None when the content was not an .m3u.
attribute_disc_indexint= 0Where the tray currently sits.
RetroArch has no way to read the mounted disc back, so the broker remembers what it did and steps relative to that.
attribute_disc_lock= threading.Lock()Serializes swap_disc, save_state and one resume load attempt.
All three act on the live process after polling for PLAYING: a
LOAD_STATE landing inside a tray-settle window, or a SAVE_STATE
landing inside a resume's settle window, is the collision. The waiters
differ: a swap fails fast, a save queues. The resume's retry loop holds
this only for an attempt at a time, never across the gaps between them.
attribute_state_handout_atfloat= float('-inf')time.monotonic() of the last time the state-file GET route read the file.
Set by note_state_handout. load_state's confirmation watches the
same access time that read moves, so a handout landing mid-load reads
as a false confirmation unless the wait knows to discount it.
attribute_resolvedOptional[tuple[Optional[str], Optional[str], retroarch_cores.Profile]]= NoneThe (platform, core) the profile was resolved for, and the profile; see _profile.
attribute_observed_libOptional[str]= NoneAn untested core's library_name as confirmed on disk.
Unlike _seeded_lib, this is never overridden once set: it came from
watching RetroArch actually write the dir, not from a guess.
attribute_seeded_libOptional[str]= NoneAn untested core's library_name as recorded in a restored archive's manifest.
Only a seed, not a confirmation: adopt_archive_identity sets this,
but _observe_library_name keeps running and its result overrides it
(the manifest only seeds the name; observation may still correct it).
attribute_launch_wallfloat= 0.0time.time() set right before the launch spawn; anchors _observe_library_name.
attribute_prelaunch_mtimesdict[Path, float]= {}Every sorted-dir file's mtime just before the launch spawn.
_observe_library_name skips a file still at its recorded mtime: the
restore and the save carry stamp old mtimes, so an untouched file
proves nothing about where RetroArch writes.
attribute_broker_written_statesset[Path]= set()Paths this session's broker wrote itself: a mid-session state push or an import placement.
_observe_library_name excludes these: a file the broker itself put
there cannot be evidence of where RetroArch writes, and would
otherwise falsely confirm whatever dir the push happened to target.
attributesave_subtreestuple[str, ...]Per-platform dump scope.
Cores whose savefile dir is also their app-data dir (dolphin) narrow the archive to real save files.
attributesupports_statesboolWhether the loaded platform's core can save states.
attributerom_cacheableboolWhether, with the ROM cache enabled, launch boots a local copy of the ROM; off for the arcade cores that load romsets from beside it.
attributerom_extensionstuple[str, ...]The loaded platform's ROM extensions, or none when no platform is mapped.
Functions
func__init__(self) -> NoneSet up the reply buffer, reader thread slot and tray tracking for a session.
paramselfReturns
Nonefunc_experimental(self) -> boolWhether a blocked core may run: RomM's flag or RETROARCH_EXPERIMENTAL_CORES.
paramselfReturns
boolTrue when either is on.
func_profile(self) -> Optional[retroarch_cores.Profile]The profile for the current platform and core, resolved once per pair.
Callers that only set platform (import-spec, most tests) get the
default profile. select_core is where a bad core raises; here an
unmapped platform with no core is None.
paramselfReturns
typing.OptionalThe profile, or None when the platform is unset or unmapped.
funcselect_core(self) -> NoneResolve the platform's profile now, and warn about an untested or blocked core.
Raises
CoreRejectedError: For a core that is not offered, or blocked without the opt-in.
paramselfReturns
Nonefunclibrary_name(self) -> Optional[str]The sorted-dir name in effect: observed, then seeded, then the profile's.
An observed name (confirmed on disk) always wins. Absent that, a
manifest seed from adopt_archive_identity is used until observation
either confirms it or replaces it with something else.
paramselfReturns
typing.OptionalThe name, or None when unmapped.
funccore_identity(self) -> dict[str, Any]The tier and library name, for the manifest and the activate response.
paramselfReturns
dictcore_tier and library_name, or nothing when unmapped.
funcstate_library(self, path) -> Optional[str]The sorted dir path sits in, which can differ from library_name.
An unconfirmed core's state sits where RetroArch put it, not where the catalog guessed; a legacy state at the unsorted root is the default core's.
paramselfparampathPathThe state file the state GET is serving.
Returns
typing.OptionalThe file's first dir under STATE_DIR, else library_name().
func_known_libs(self) -> frozenset[str]Sorted-dir names that belong to cores other than the running one.
paramselfReturns
frozensetEvery library_name in the platform table (defaults and alternates)
func_state_scope(self) -> tuple[Optional[str], frozenset[str], bool]Where this core's states may be looked up.
paramselfReturns
typing.Optional(only, skip, include_root) for _newest_state. The default
func_observe_library_name(self) -> NoneAdopt the sorted dir RetroArch actually wrote for an untested core.
Takes the newest subdirectory of saves/ or states/ holding a file
genuinely written since launch. Called after PLAYING and after each
confirmed save; a no-op once a name is confirmed (_observed_lib
set), and for default and vetted cores. Keeps running even after a
manifest seed (_seeded_lib) is in place: the manifest only
seeds the name, and this observer's result, once it has one,
overrides that seed.
A candidate file's mtime has to fall in
[launch - OBSERVE_WINDOW_BEFORE_SLACK, now + OBSERVE_WINDOW_AFTER_SLACK]:
the lower bound is "since launch" from the spec, loosened by
OBSERVE_WINDOW_BEFORE_SLACK for a mount that only tracks mtime to a
coarse unit; the upper bound discards a clock-skewed or otherwise
bogus future timestamp, widened by OBSERVE_WINDOW_AFTER_SLACK to
tolerate the share and the broker's clocks disagreeing slightly.
Files this session's own broker wrote (_broker_written_states), and
files still at their pre-launch mtime (_prelaunch_mtimes), are
skipped outright: they prove nothing about where RetroArch writes.
paramselfReturns
Nonefuncadopt_archive_identity(self, identity) -> NoneSeed the library name from the restored archive's manifest.
Only a seed: _observe_library_name keeps running afterward, and its
result overrides this one once it has confirmed a real dir.
paramselfparamidentityOptional[Mapping[str, Any]]The restored archive's manifest session, or None.
Returns
Nonefunccarry_save_across_cores(self, identity, rom_file) -> NoneCarry the battery save from the archive's core's dir into this core's.
Runs after the archive is restored and before the baseline, so the
carry, which keeps the file's mtime, does not count as this session's
write. A default or vetted core's dir is known, so the save moves; an
untested or blocked core's dir is a guess, so the save is copied and
the original stays where the old core reads it. An existing target is
replaced only by a strictly newer source, and kept beside it as
.srm.bak. States are never carried. A carry that fails, or a target
dir name that is not a single safe path component, is logged and
skipped; it never fails activate.
paramselfparamidentityOptional[Mapping[str, Any]]The restored archive's manifest session, or None.
paramrom_fileOptional[Path]The file being booted; its stem names the .srm.
Returns
Nonefuncnote_broker_state_write(self, path) -> NoneTrack path so _observe_library_name does not mistake it for a core's own write.
Called for a mid-session state push and for every file a declared import places (and its sidecars).
paramselfparampathPathThe file's absolute path: a pushed state as returned by
state_target, or save_root joined with a placement's dest.
Returns
Nonefuncarchive_core(self) -> Optional[str]The libretro core booting the loaded platform, or None when unmapped.
paramselfReturns
typing.Optional[str]funcresolve_rom_file(self, path) -> Optional[Path]File the core should boot for path.
A file is taken as is. A folder is searched one level deep and ranked by
_pick_rom_file against the platform's extensions; when that finds
nothing, a lone archive holding content the core takes is booted.
paramselfparampathPathThe ROM as RomM delivered it, a file or a folder.
Returns
typing.OptionalThe file to boot, or None when the platform is unmapped, the path
func_spawn_ra(self, cmd, env) -> NoneSpawn with a real stdout pipe (stderr to the log).
stdout carries the command replies, so it must stay clean. A reader
thread drains it into _stdout_buf. The pid is recorded the way the
shared _spawn records it, so a broker that restarts mid-session can
still reap the RetroArch it no longer has a handle on.
paramselfparamcmdlist[str]The argv to run.
paramenvdict[str, str]The environment to run it in.
Returns
Nonefunc_read_stdout(self) -> NoneReader thread body: copy RetroArch's stdout into _stdout_buf until it closes.
paramselfReturns
Nonefunc_wait_for_reply(self, prefixes, timeout) -> Optional[str]Poll the reply buffer for a line beginning with one of prefixes.
Only _send calls this, and only while holding _reply_lock, so the
line consumed here belongs to the command that exchange just sent.
paramselfparamprefixestuple[str, ...]Reply prefixes the caller is waiting for.
paramtimeoutfloatSeconds to keep polling the buffer.
Returns
typing.OptionalThe matched line, with everything up to and including it dropped
func_write_cmd(self, cmd) -> boolWrite one command line to RetroArch's stdin.
paramselfparamcmdstrThe command line to send; the newline is added here.
Returns
boolTrue once the line is flushed. False when there is no running
func_send(self, cmd, wait_prefix, timeout=5.0) -> Optional[str]Send one command and return the reply it answers with.
A reply carries nothing naming the command that produced it, so one
exchange runs at a time and the buffer is emptied before the command
goes out. Without that, a background GET_STATUS poll consumes the
OK or the LOAD_STATE_SLOT echo another thread is waiting on, and a
reply left over from an earlier command answers this one.
paramselfparamcmdstrThe command line to send; the newline is added here.
paramwait_prefixUnion[str, tuple[str, ...]]One reply prefix, or several of which the earliest matching line wins.
paramtimeoutfloat= 5.0Seconds to wait for the reply.
Returns
typing.OptionalThe reply line, or None when the command could not be sent or no
funclaunch(self, rom_path, resume_slot) -> NoneStart RetroArch on rom_path with the platform's core.
Any running session is stopped first. The core is downloaded if missing, its assets linked, its fixed-path saves linked into the archive, its core options pinned or seeded, and the broker config written; then RetroArch starts fullscreen with that config appended. A resume is deferred to a background thread that waits for the game to be up.
Raises
RuntimeError: Whenplatformhas no core mapped, or the RetroArch binary (RETROARCH_BIN) is not onPATH.
paramselfparamrom_pathPathThe file to boot, as returned by resolve_rom_file.
paramresume_slotOptional[int]The slot to load once the game is running, or None.
Returns
Nonefunc_msu1_boot_path(self, rom_path) -> PathThe file to boot for rom_path: an MSU-1 extraction for a zipped MSU-1 game, else itself.
Snes9x finds MSU-1 audio and video beside the ROM on disk, so a zip RetroArch opens itself boots silent. Its extraction keeps the zip's stem, so saves and states keep their names. A game that cannot be extracted still boots from the zip, without the MSU-1 data.
paramselfparamrom_pathPathThe file launch was handed.
Returns
pathlib.PathThe extracted ROM, or rom_path unchanged.
func_deferred_load_state(self, slot, seq) -> NoneLoad slot once RetroArch reports the content PLAYING.
Cores with no game running yet (Dolphin boot screen) reject loads, so
this polls GET_STATUS first and does nothing else itself. Everything
the load needs (the wait for the slot's state file, _resume_settle,
the load and its retries) belongs to _load_until_confirmed, which
runs all of it under the tray lock for what is left of
RESUME_LOAD_WAIT.
The status poll is the one part that stays outside the tray: it sends no tray command and reads nothing off disk, so a save or disc swap running alongside it cannot collide with anything.
paramselfparamslotintThe slot to load.
paramseqintThe launch generation this load belongs to; a relaunch or stop
bumps _launch_seq and ends the wait.
Returns
Nonefunc_load_until_confirmed(self, slot, deadline, seq) -> boolWait for the slot's state file, settle, then load it, retrying until deadline.
A load RetroArch echoes but never reads leaves the player in a fresh
boot with the resume silently lost, and one attempt is too little to
call it: the core can still be finishing its own startup past the
settle. Attempts repeat every RESUME_LOAD_RETRY_GAP while the resume
budget holds. Nothing on the status route carries a resume result, so
an exhausted budget is escalated to an error log rather than setting
boot_failed, which means a game that never started at all.
Each attempt takes _disc_lock on its own and drops it again, rather
than the whole retry run holding it: an attempt is a few seconds long
while the run can span the entire resume budget, and a save queued
behind that would time out and be lost. The first attempt keeps the
wait for the state file and _resume_settle inside its own hold, since
a save landing in either window writes exactly the unrestored state the
resume is about to read back. Waiting outside the tray is not enough:
that save creates the very file the wait is looking for. Later attempts
already know the file is there and only re-send the load, but the tray
is free between attempts, so a save can still land in the retry gap;
the file is fingerprinted the moment it is found ready and re-checked
before every later attempt, and a mismatch ends the run rather than
loading whatever is there now back as if it were the resume.
The state-file wait gets no longer than RESUME_ATTEMPT_LOCK_WAIT of
any one hold; a slot still empty at that point drops the tray and comes
back on the next attempt, so a state RomM pushes late is still caught
without a save waiting out the whole resume budget behind it.
paramselfparamslotintThe slot to load.
paramdeadlinefloatA time.monotonic() value to stop retrying at.
paramseqintThe launch generation this load belongs to.
Returns
boolTrue once a load is confirmed; False when the budget runs out, the
func_wait_until_playing(self, deadline, seq) -> boolBlock until the core reports a running game, or deadline passes.
A tray command sent before the content is up is dropped, and there is
no error to catch when it is. seq is the launch generation this
wait belongs to, checked the same way _deferred_load_state checks
_launch_seq, so a relaunch during the wait ends it rather than
letting a stale wait later act on the new session.
paramselfparamdeadlinefloatA time.monotonic() value to give up at.
paramseqintThe launch generation this wait belongs to.
Returns
boolTrue once GET_STATUS reports PLAYING; False on timeout, relaunch
funcswap_disc(self, path) -> boolMount the playlist entry at path without restarting the core.
Stepping is relative and wraps, because the command protocol has
DISK_NEXT but no way to set an index or read the current one back.
The tray is opened, stepped the needed number of times, and closed;
the tracked index only moves once the session is confirmed to have
outlived the sequence. An open menu would drop those commands, so
_close_menu_before_tray closes one first.
paramselfparampathPathThe disc image to mount; it must be listed in the session's playlist.
Returns
boolTrue once the new disc is in the tray (or was already mounted);
func_track_first_playing(self, seq) -> NoneRecord the moment GET_STATUS first reports PLAYING, for _wait_for_first_save_settle.
Runs from launch in the background, polling tightly so the recorded
timestamp tracks the real transition closely rather than lagging it by
a coarse poll interval. _send is safe to call concurrently with the
rest of the session (see _reply_lock/_stdout_lock), so this can run
alongside a deferred resume load's own PLAYING poll without colliding.
paramselfparamseqintThe launch generation this belongs to; a relaunch or stop
bumps _launch_seq and ends the wait.
Returns
Nonefunc_wait_for_first_save_settle(self) -> NoneSleep off whatever is left of FIRST_SAVE_SETTLE since PLAYING.
Called once, right before a session's first _home_state_slot(). A
fast-booting core can already be running well past its own boot
sequence by the time the player's first save request arrives, in
which case this is a no-op; only a save requested within
FIRST_SAVE_SETTLE of PLAYING actually waits.
If _track_first_playing has not observed PLAYING yet (a very fast
first save, or a slow-booting core), this waits up to
FIRST_SAVE_SETTLE_WAIT for it before giving up and proceeding
unsettled.
paramselfReturns
Nonefunc_home_state_slot(self) -> boolPark RetroArch's current state slot on STATE_SLOT.
Absolute, not relative: MINUS runs the slot down onto its -1 floor
first, so this holds wherever the slot was, as long as it was no
higher than SLOT_HOME_STEPS presses above the floor. Nothing in the
protocol reads the slot back, so a player who cycled past that is only
caught by the save confirmation missing the slot afterwards.
paramselfReturns
boolTrue when every press was delivered. A dropped press leaves the
func_states(self) -> dict[Path, tuple[int, float]]Snapshot the loaded game's state files.
paramselfReturns
dict_state_snapshot of STATE_DIR for the loaded content.
func_toggle_menu_settled(self) -> boolSend MENU_TOGGLE and give the menu MENU_SETTLE to open or close.
paramselfReturns
boolFalse when the command could not be written.
func_try_save(self) -> boolSend SAVE_STATE once and confirm the file landed in STATE_SLOT.
paramselfReturns
boolTrue when the slot's state file changed on disk, is non-empty,
funcsave_state(self, slot) -> boolSave the running game into STATE_SLOT.
slot is what RomM asked for and is ignored: this saves into
STATE_SLOT and the caller reads the effective slot back off
state_slot.
Homing costs a couple of seconds, so it runs once per launch and then only to recover: a save landing on another slot means the player moved it with their own hotkeys, and re-homing puts the next one back.
The save takes _disc_lock, so it cannot land inside a deferred resume
load's settle window: a save written there captures the game before the
resume was restored, and the resume then loads that back over what the
player had. Unlike swap_disc this waits (up to SAVE_LOCK_WAIT)
instead of failing fast, because after the resume finishes the save is
exactly the one the caller asked for.
paramselfparamslotintThe slot RomM asked for; ignored.
Returns
boolTrue once the state file is confirmed on disk; False when the core
func_save_into_slot(self, lock_wait) -> boolTake the tray within lock_wait and write the running game into STATE_SLOT.
The wait is the caller's to pick because the cost of waiting is not the same everywhere: an ordinary save only delays itself, while the exit save holds the API's process-wide session lock for as long as it sits here.
A miss is retried once, and which retry depends on what the miss left
behind: a state written to another slot means the player moved the
slot, so it is re-homed; nothing written anywhere means a menu is
swallowing the command, so _save_past_menu closes it first.
paramselfparamlock_waitfloatSeconds to wait for _disc_lock before giving up.
Returns
boolTrue once the state file is confirmed on disk; False when the core
func_save_past_menu(self) -> boolClose the menu a save could not get past, retry, and reopen it if that missed too.
Called when a SAVE_STATE wrote nothing to any slot. That is what an
open menu does: it swallows the command, and nothing over stdin reports
whether the menu is up. A save that landed on another slot means the
player moved the slot instead, which _save_into_slot re-homes without
coming here. The slot is re-homed after the toggle because the menu
swallows slot presses too, and its own State Slot entry can move it.
paramselfReturns
boolTrue once the retried save is confirmed on disk. False when the
func_close_menu_before_tray(self) -> boolMake sure no menu is up to drop a swap's tray commands, closing one that is.
The menu drops DISK_* the way it drops SAVE_STATE, and GET_STATUS
still says PLAYING with it open, so a save is the only probe: one that
writes nothing means a menu took it. The probe's writes are undone, so
the exit dump never ships them. Called with _disc_lock held.
paramselfReturns
boolTrue when the menu is closed (or was closed here), and when the
func_probe_save(self) -> boolSend SAVE_STATE and report whether any state file changed.
paramselfReturns
boolTrue once a write to any slot is seen within _state_confirm_wait.
functoggle_menu(self) -> boolSend MENU_TOGGLE, which opens the Quick Menu or closes it.
Fails fast while a save, resume load or disc swap holds _disc_lock:
a toggle landing inside a save's retry would reopen the menu the retry
just closed, and the save would be swallowed again.
paramselfReturns
boolTrue once the command reached RetroArch's stdin; False when the
funcload_state(self, slot) -> boolTake _disc_lock and load STATE_SLOT, confirming the state was read.
The lock keeps this load's access-time backdate-and-poll from overlapping a deferred resume attempt's: both watch the same state file to confirm their own read, and without serializing them one can mistake the other's read for its own and report a load that never happened.
paramselfparamslotintThe slot RomM asked for; ignored in favour of STATE_SLOT.
Returns
boolTrue once the load is confirmed by _load_state_locked; False
func_load_state_locked(self, slot, *, resume=False) -> boolLoad STATE_SLOT into the running game and confirm the state was read.
Assumes _disc_lock is already held; called by load_state and by
the resume retry loop, which holds the lock across its own attempt.
LOAD_STATE_SLOT is absolute and does not move the current slot, so
this needs no homing. Its echo is a bare repeat of the command with no
success bit, and a load writes nothing to disk, so the echo alone
cannot tell a restored game from a refused load (a core that has no
content up yet rejects one). What does separate them is the read: a
load that happens opens the state file, so the file's access time is
stamped behind its own mtime beforehand and polled for afterwards.
On a noatime mount nothing records that read, and there the echo is
all there is; the load is then reported as delivered and the warning
says the result is unconfirmed. That charity is for the mount, where no
load could ever be confirmed. A mount that does track access times but
whose state file could not be stamped is a fault on that one file, so
it is reported as a failed load and left for the caller to retry. A
probe that could not run at all proved nothing about the mount, so it
is treated as tracking rather than earning the same charity: guessing
noatime there would pass every load through unchecked.
The state is fingerprinted alongside the stamp, so a state pushed in
over the slot while the load is in flight is not mistaken for the load
having read the one that was there. The state-file GET route reads the
same file and moves the same access time; an access-time move seen
within STATE_READ_HANDOUT_GRACE of one of those reads taints that
attempt's read for good (see _wait_for_state_read), so a tainted
attempt is retried with a fresh marker up to LOAD_TAINT_RETRY_LIMIT
times rather than being reported as a failed load RetroArch actually
performed.
A resume load an untested or blocked core never reads is logged as a warning naming the likely cause: a state pushed before the core's sorted dir was known may sit where that core does not look.
paramselfparamslotintThe slot RomM asked for; ignored in favour of STATE_SLOT.
paramresumebool= FalseWhether this is a resume attempt rather than a manual load.
Returns
boolTrue once RetroArch has read the slot's state file (or echoed the
funcstate_path(self) -> Optional[Path]The newest state file for the loaded content in STATE_SLOT, or None.
Scoped to the running core's own sorted dir once it is known (default, vetted, or an untested core whose dir has been observed), plus the legacy unsorted root for the default core only; otherwise everywhere except another known core's dir.
paramselfReturns
typing.OptionalThe file found by _newest_state, or None before a launch has set
funcnote_state_handout(self) -> NoneRecord now as the last time the state-file GET route read our state.
See _state_handout_at.
paramselfReturns
Nonefunclock_for_state_write(self) -> boolTake _disc_lock before a pushed state overwrites the working slot.
Without this, a push racing an in-flight resume attempt or manual load would replace the file those are backdating and polling for their own confirmation, which reads as a save landing in the retry gap even when it is the resume's own seed pushed twice.
paramselfReturns
boolTrue once _disc_lock is held; False after LOAD_LOCK_WAIT with
funcunlock_state_write(self) -> NoneRelease _disc_lock taken by lock_for_state_write.
paramselfReturns
Nonefuncclear_working_slot(self, excluded=()) -> NoneEmpty every save subtree the incoming archive restores into.
The states alone are not enough. RetroArch names both a state and an
SRAM file after the loaded content and nothing in either name says
whose session wrote it, so in a shared container the previous player's
.srm sits there with a fresh mtime when the next player activates
the same title. The restore then skips its own member as older than
what is on disk, and the exit dump ships the leftover back as the new
player's save. Clearing the whole tree first is what leaves the
restore the only thing that puts save data in place.
Scoped to save_subtrees, so the platforms that share a savefile dir
with their core's app data (dolphin, azahar) lose their saves and keep
the rest of that dir.
paramselfparamexcludedtuple[str, ...]= ()Subtrees carried by the whole-card routes. RetroArch names no memory card subtree, so this is always empty.
Returns
Nonefuncstate_target(self, filename) -> Optional[Path]Where a pushed state called filename belongs.
RetroArch looks a state up by the name it derives from the loaded content, so the target is that name in this broker's slot and a pushed one only has to say which content it belongs to.
paramselfparamfilenamestrThe name the pushed state was stored under.
Returns
typing.OptionalThe existing slot file when there is one, else the slot's path in
funcimport_spec(self) -> imports.ImportSpecTake one .srm (or .sav), NAND or SD files, or nothing; take states through the push routes.
A platform whose core reads a .sav as its .srm (sav_is_srm)
names both shapes, still one member at most.
A platform whose core loads no .srm and has no layout of its own
(see _srm_dir and _layout), and an unmapped platform, declare the
save kind with no shapes: discovery tells RomM the save is refused
there, and place_import says why. Wii and 3DS declare the shape of
their core's tree and the globs no import may write. A state placed in
the archive would race the resume's fingerprint check, so a mapped
platform whose core has states takes them through
PUT /api/session/state-file after activate.
paramselfReturns
webstation_broker.imports.ImportSpecThe spec for the loaded platform.
funcidentity_source(self) -> Optional[imports.IdentitySource]Name the game id a Wii or 3DS save has to match: RomM's, as neither is read off the rom.
The Dolphin module reads a disc's id in _rom_game_id, but RetroArch
may not import another emulator's module, so the session's id is
RomM's title_id alone, and a session without one places any title.
paramselfReturns
typing.OptionalA GameCube/Wii disc-id source on the Wii, a sixteen-digit title-id
funcplace_import(self, member, spec, ctx) -> Union[imports.Placement, imports.ImportRefusal]Place a Wii or 3DS file in its core's tree, or one .srm named for the booted content.
Only saves reach here: the kind gate refuses a state, which is pushed,
and a memory card, which is resent as the core's .srm save. On Wii
and 3DS a member that is not a .srm is placed by the layout; a
.srm there, and every member on any other platform, is answered by
_srm_dir. Where the core reads a .sav as its .srm
(sav_is_srm), a .sav is placed as that .srm.
paramselfparammemberimports.ImportMemberThe save member, already past the kind gate.
paramspecimports.ImportSpecThis emulator's spec.
paramctximports.ImportCtxThe launch context.
Returns
typing.UnionThe placement, or a refusal saying why this platform takes no such file.
func_place_import_unnoted(self, member, ctx) -> Union[imports.Placement, imports.ImportRefusal]Work out place_import's answer, before the placed paths are noted.
paramselfparammemberimports.ImportMemberThe save member, already past the kind gate.
paramctximports.ImportCtxThe launch context.
Returns
typing.UnionThe placement, or a refusal saying why this platform takes no such file.
func_flush_sram(self) -> boolAsk RetroArch to write the game's SRAM out before the archive is dumped.
SAVE_FILES answers OK or NO, and a refusal has to be reported:
the dump that follows ships whatever is on disk either way, so an
unflushed session ships the previous save as if it were current.
paramselfReturns
boolTrue when RetroArch answered OK; False when it refused or never
funcsave_and_exit(self, slot) -> dict[str, Any]Save state when asked, flush SRAM, and quit RetroArch.
The state save is skipped when slot is None or the platform entry
opts out of savestate. The SRAM flush always runs so the save dump
ships current save data, then the process is quit through _quit. A
flush RetroArch refuses is logged by _flush_sram; the exit still goes
ahead, since holding the session open would not make the refusal any
more likely to succeed.
The save here waits only EXIT_SAVE_LOCK_WAIT for the tray, not the
SAVE_LOCK_WAIT an ordinary save gets: this runs under the API's
process-wide session lock, so the wait is paid by every other session
route as well.
paramselfparamslotOptional[int]The slot to save into, or None to exit without writing a state.
Returns
dictA dict with `{"state_saved", "state_slot", "state_file",
func_quit(self) -> NoneQuit RetroArch gracefully over stdin, escalating to stop if it stays up.
QUIT is sent, then sent again after QUIT_CONFIRM_GAP if the process
is still running, and given QUIT_WAIT to exit. A graceful exit
forgets the handle; anything else falls through to self.stop
(not the base class's) and its SIGTERM, so an in-flight deferred
resume load is invalidated the same as any other exit path even when
the process outlives that SIGTERM/SIGKILL escalation.
paramselfReturns
Nonefuncstop(self) -> NoneStop RetroArch, invalidating any in-flight deferred state load before the kill.
paramselfReturns
None