webstation-broker
DeveloperPython referenceemulatorsemulators.retroarch

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.

Attributes

attributename
= 'retroarch'

Registry key, retroarch.

attributedisplay_name
= 'RetroArch'

Shown as "RetroArch".

attributesave_root
= RA_DATA_DIR

RA_DATA_DIR, the broker-managed data root.

attributelog_path
= RA_LOG_PATH

RA_LOG_PATH, where RetroArch's stderr goes.

attributesupports_states
= True

On; save states work for every core unless its platform entry opts out of savestate.

attributesupports_disc_swap
= True

On; discs are swapped through the virtual tray.

attributestate_slot
= STATE_SLOT

STATE_SLOT, the one slot the broker works in.

attributestate_dir
= STATE_DIR

STATE_DIR, the broker-managed savestate directory.

attributeterm_timeout
= float(os.environ.get('RETROARCH_STOP_WAIT', '15'))

Seconds QUIT gets before SIGTERM, from RETROARCH_STOP_WAIT (default 15).

attributeplatformOptional[str]
= None

The 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
= False

Whether the current slot has been parked on STATE_SLOT since launch.

attribute_launch_seq
= 0

Launch generation, bumped on every launch and stop so stale background waits bail out.

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_readerOptional[threading.Thread]
= None

The thread draining RetroArch's stdout into _stdout_buf.

attribute_playlistOptional[Path]
= None

The playlist this session booted, or None when the content was not an .m3u.

attribute_disc_indexint
= 0

Where 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 against itself and against the deferred resume load.

Both poll for PLAYING and then act on the live process, and a LOAD_STATE landing inside a tray-settle window is the collision.

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.

attributerom_extensionstuple[str, ...]

The loaded platform's ROM extensions, or none when no platform is mapped.

Functions

func__init__(self) -> None

Set up the reply buffer, reader thread slot and tray tracking for a session.

paramself

Returns

None
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.

paramself
parampathPath

The ROM as RomM delivered it, a file or a folder.

Returns

typing.Optional

The file to boot, or None when the platform is unmapped, the path

func_spawn_ra(self, cmd, env) -> None

Spawn 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. Unlike the shared _spawn, this does not record the pid.

paramself
paramcmdlist[str]

The argv to run.

paramenvdict[str, str]

The environment to run it in.

Returns

None
func_read_stdout(self) -> None

Reader thread body: copy RetroArch's stdout into _stdout_buf until it closes.

paramself

Returns

None
func_wait_for_reply(self, prefixes, timeout) -> Optional[str]

Wait for a stdout reply matching one of prefixes; consume it and return it.

Replies are newline-terminated except the bare echoes of the *_SLOT commands and GET_STATUS CONTENTLESS, so a match with no newline after it is taken as the whole rest of the buffer.

paramself
paramprefixesUnion[str, tuple[str, ...]]

One prefix, or several of which the earliest match wins.

paramtimeoutfloat

Seconds to keep polling the buffer.

Returns

typing.Optional

The matched line, with everything up to it dropped from the buffer,

func_send(self, cmd, wait_prefix=None, timeout=5.0) -> Optional[str]

Write one command to RetroArch's stdin, optionally waiting for its reply.

paramself
paramcmdstr

The command line to send; the newline is added here.

paramwait_prefixOptional[Union[str, tuple[str, ...]]]
= None

Reply prefix(es) to wait for, or None for a command with no reply.

paramtimeoutfloat
= 5.0

Seconds to wait for the reply.

Returns

typing.Optional

The reply line when one was waited for and arrived, otherwise None,

funclaunch(self, rom_path, resume_slot) -> None

Start RetroArch on rom_path with the platform's core.

Any running session is stopped first. The core is downloaded if missing, its assets linked, 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: When platform has no core mapped, or the RetroArch binary (RETROARCH_BIN) is not on PATH.
paramself
paramrom_pathPath

The file to boot, as returned by resolve_rom_file.

paramresume_slotOptional[int]

The slot to load once the game is running, or None.

Returns

None
func_deferred_load_state(self, slot, seq) -> None

Load slot once RetroArch reports the content PLAYING.

Cores with no game running yet (Dolphin boot screen) reject loads, so this polls GET_STATUS first, then waits out RESUME_LOAD_SETTLE and for the slot to hold a state file before loading.

paramself
paramslotint

The slot to load.

paramseqint

The launch generation this load belongs to; a relaunch or stop bumps _launch_seq and ends the wait.

Returns

None
func_wait_until_playing(self, deadline, seq) -> bool

Block 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.

paramself
paramdeadlinefloat

A time.monotonic() value to give up at.

paramseqint

The launch generation this wait belongs to.

Returns

bool

True once GET_STATUS reports PLAYING; False on timeout, relaunch

funcswap_disc(self, path) -> bool

Mount 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.

paramself
parampathPath

The disc image to mount; it must be listed in the session's playlist.

Returns

bool

True once the new disc is in the tray (or was already mounted);

func_home_state_slot(self) -> None

Park RetroArch's current state slot on STATE_SLOT.

Absolute, not relative: MINUS runs the slot down onto its -1 floor first, so this holds no matter where the slot was.

paramself

Returns

None
func_try_save(self) -> bool

Send SAVE_STATE once and confirm the file landed in STATE_SLOT.

paramself

Returns

bool

True when the slot's file changed on disk within

funcsave_state(self, slot) -> bool

Save 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.

paramself
paramslotint

The slot RomM asked for; ignored.

Returns

bool

True once the state file is confirmed on disk; False when the core

funcload_state(self, slot) -> bool

Load STATE_SLOT into the running game.

LOAD_STATE_SLOT is absolute and does not move the current slot, so this needs no homing. The echo carries no success bit and a load writes nothing to disk, so an empty slot is ruled out here instead.

paramself
paramslotint

The slot RomM asked for; ignored in favour of STATE_SLOT.

Returns

bool

True once RetroArch echoes the command; False when the core is not

funcstate_path(self) -> Optional[Path]

The newest state file for the loaded content in STATE_SLOT, or None.

paramself

Returns

typing.Optional

The file found by _newest_state, or None before a launch has set

funcstate_screenshot_path(self) -> Optional[Path]

The thumbnail RetroArch wrote beside the working slot's state, or None.

RetroArch writes the thumbnail as <state file>.png beside the state, so it is only meaningful next to the state it was taken with.

paramself

Returns

typing.Optional

The .png next to state_path, or None when either is missing.

funcstate_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.

paramself
paramfilenamestr

The name the pushed state was stored under.

Returns

typing.Optional

The existing slot file when there is one, else the slot's path at

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. SAVE_FILES is always sent so the save dump ships current save data, then the process is quit through _quit.

paramself
paramslotOptional[int]

The slot to save into, or None to exit without writing a state.

Returns

dict

A dict with {"state_saved", "state_slot", "state_file"}: whether

func_quit(self) -> None

Quit 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 the base stop and its SIGTERM.

paramself

Returns

None
funcstop(self) -> None

Stop RetroArch, invalidating any in-flight deferred state load before the kill.

paramself

Returns

None

On this page