webstation-broker
DeveloperPython referenceemulatorsemulators.base

emulators.base.Emulator

Contract every launcher implements, plus the process plumbing they share.

Contract every launcher implements, plus the process plumbing they share.

A subclass describes itself through the class attributes below and must override launch and resolve_rom_file; both raise NotImplementedError here. Everything else is an optional hook with a safe default:

  • save_state, load_state, state_path, state_screenshot_path, state_target, clear_working_slot and wait_for_state are the save-state hooks. The broker only calls the first two when supports_states is on; the defaults report an empty slot.
  • swap_disc is only called when supports_disc_swap is on.
  • memory_card_path pairs with memory_card_subtree for emulators whose whole memory card travels on its own routes.
  • prepare_restore runs before a save archive is extracted.
  • save_and_exit is the exit path; the default writes no state.

The lifecycle as the broker drives it:

  1. Activate: clear_working_slot drops the previous session's leftover state, prepare_restore runs, and the incoming save archive is extracted into save_root, scoped to save_subtrees.
  2. launch spawns the process through _spawn, which captures output to log_path, starts it in its own session and records its pid so a later broker process can reap it (see reap_orphan).
  3. Mid-session: save_state and load_state work the one slot in state_slot. The state-file routes serve state_path and write through state_target, with wait_for_state bridging a resume state that arrives after launch.
  4. save_and_exit saves when asked and stops. stop sends SIGTERM to the process group, escalates to SIGKILL after term_timeout, and _forget drops both the handle and the pid record.

Attributes

attributenamestr
= 'base'

Registry key and log label for the emulator.

attributedisplay_namestr
= 'Webstation'

Human-readable name the UI shows.

attributerequires_rombool
= True

Whether a launch needs a ROM; the desktop session does not.

attributesave_rootPath
= Path('/config')

Root of the emulator's writable data.

attributesave_subtreestuple[str, ...]
= ()

Subtrees under save_root that hold save data; save restore and dump are scoped to these.

attributerom_extensionstuple[str, ...]
= ()

File extensions the emulator will boot, in preference order.

attributesupports_statesbool
= False

Whether the emulator can save and load state mid-session.

attributesupports_disc_swapbool
= False

Whether the emulator can change the mounted disc without restarting.

attributestate_slotint
= 0

The one slot the broker saves into.

attributestate_dirPath
= Path('/config')

Where that slot's file lives.

attributelog_pathPath
= Path('/config/broker-app.log')

Where the emulator's stdout and stderr are appended.

attributeterm_timeoutfloat
= 5.0

Seconds SIGTERM gets before escalating to SIGKILL.

attributememory_card_subtreeOptional[str]
= None

The save subtree holding the whole memory card, or None for emulators without one.

attributememory_card_markerOptional[str]
= None

A file the emulator needs inside the card directory before it treats it as a card, or None.

attribute_procOptional[subprocess.Popen[bytes]]
= None
attributeboot_failedbool
= False

Set by an emulator that can tell its process is alive but never reached a running game.

Functions

func__init__(self) -> None

Start with no process handle and no boot failure flagged.

paramself

Returns

None
func_spawn(self, cmd, env, stdin_pipe=False) -> None

Start the app in its own process group with output captured.

A launch banner and then the child's stdout and stderr are appended to log_path; if the log cannot be opened the output is discarded. The pid is recorded through _record_pid once the process is up.

paramself
paramcmdlist[str]

The argv to run.

paramenvdict[str, str]

The environment to run it in, normally base_launch_env().

paramstdin_pipebool
= False

Keep the child's stdin as a pipe so emulators with a stdin control protocol (shadPS4 IPC) can be driven headlessly.

Returns

None
funcalive(self) -> bool

Whether a spawned process exists and has not exited.

paramself

Returns

bool
func_forget(self) -> None

Drop the handle on the emulator and the record of it on disk.

Every path that ends with the process gone has to go through here. A graceful exit that only clears _proc leaves a record pointing at a pid nobody owns, and the next broker start would hunt it.

paramself

Returns

None
funcstop(self) -> None

Terminate the running emulator, if any, and forget it.

The handle and pid record are dropped first, then the process group gets SIGTERM, escalating to SIGKILL once term_timeout passes. A process that is already gone is a no-op.

paramself

Returns

None
funcprepare_restore(self) -> None

Hook run before a save archive is extracted into save_root.

Default: nothing. Override to clear anything that would block the restore: a process holding a save file open, or an existing file the newer-file guard would wrongly keep over the archived one.

paramself

Returns

None
funclaunch(self, rom_path, resume_slot) -> None

Start the emulator on rom_path, optionally resuming a state.

Raises

  • NotImplementedError: Always; every subclass overrides this.
paramself
paramrom_pathOptional[Path]

The file to boot, as returned by resolve_rom_file, or None for an emulator that does not require a ROM.

paramresume_slotOptional[int]

The slot to load once the game is up, or None for a fresh start.

Returns

None
funcsave_state(self, slot) -> bool

Save the running game to slot.

Only called when supports_states.

Raises

  • NotImplementedError: When the emulator does not support states.
paramself
paramslotint

The slot RomM asked for; implementations may resolve it to state_slot.

Returns

bool

True once the state is confirmed written.

funcload_state(self, slot) -> bool

Load slot into the running game.

Only called when supports_states.

Raises

  • NotImplementedError: When the emulator does not support states.
paramself
paramslotint

The slot RomM asked for; implementations may resolve it to state_slot.

Returns

bool

True once the load was delivered to the emulator.

funcstate_path(self) -> Optional[Path]

The file the working slot holds right now, or None if it is empty.

This is what the state-file GET serves, so it has to be the file the emulator just wrote, not the newest state in the directory: another slot or another game's state would otherwise be filed in RomM as this save.

paramself

Returns

typing.Optional

The state file's path, or None. The default reports an empty slot.

funcstate_screenshot_path(self) -> Optional[Path]

The frame captured alongside the working slot's state, or None.

Only for emulators that write the thumbnail as a separate file. The ones that embed it in the state itself return None and let RomM pull it out of the state it already fetched.

paramself

Returns

typing.Optional

The screenshot's path, or None. The default reports none.

funcclear_working_slot(self) -> None

Drop whatever the working slot holds from an earlier session.

Called at activate, before the incoming save archive is restored, so only the container's own leftovers go. Emulators that name a state after the loaded content can tell a stale one apart on sight and leave this alone; the override exists for the ones that cannot.

paramself

Returns

None
funcmemory_card_path(self, platform=None) -> Optional[Path]

The directory holding the card the memory-card routes sync, or None.

The broker names the card rather than reading the name out of the emulator's own config, because RomM lays a card down before the first launch has written that config. platform is the ROM's platform slug, for an emulator whose card exists on only one of several platforms it serves (GameCube vs Wii on Dolphin); most emulators ignore it.

paramself
paramplatformOptional[str]
= None

Returns

typing.Optional

The card directory, or None for emulators without a memory card.

funcstate_target(self, filename) -> Optional[Path]

Where a pushed state called filename belongs.

Validating the name against the emulator's own convention is what keeps a caller from dropping arbitrary files into the save tree. The slot in it is not part of that test: RomM holds the library, so a stored state carries whatever slot it was captured in and lands in this broker's own working slot regardless.

paramself
paramfilenamestr

The name the pushed state was stored under.

Returns

typing.Optional

The path to write it to, or None if the name is not one this

funcwait_for_state(self, deadline, poll=0.5) -> bool

Block until the working slot holds a state file, or deadline passes.

A resume state can turn up after launch: the state-file routes only answer while a session is up, so RomM pushes its pick once activate has returned and the game is already booting. Waiting for it here is what keeps a deferred resume load from firing on a slot that is still empty and reporting a fresh start.

paramself
paramdeadlinefloat

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

parampollfloat
= 0.5

Seconds between checks of state_path.

Returns

bool

True if the slot holds a state file by the time this returns.

funcsave_and_exit(self, slot) -> dict[str, Any]

Save state (best effort) and stop.

Default: nothing to save. A slot of None is an exit that writes no state. The game's own save data is still flushed and shipped: not writing a state is the whole of "exit without saving", and discarding an in-game save the player made at a save point would be losing real progress.

paramself
paramslotOptional[int]

The slot to save into before stopping, or None to skip the state save.

Returns

dict

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

funcresolve_rom_file(self, path) -> Optional[Path]

File the emulator should boot for path (folder or file).

Raises

  • NotImplementedError: Always; every subclass overrides this.
paramself
parampathPath

The ROM as RomM delivered it, either a single file or a folder holding the game's files.

Returns

typing.Optional

The file to hand to launch, or None if nothing bootable is there.

funcswap_disc(self, path) -> bool

Mount path in place of the running disc.

Only called when supports_disc_swap.

Raises

  • NotImplementedError: When the emulator has no tray.
paramself
parampathPath

The disc image to mount.

Returns

bool

True once the new disc is in the tray.

On this page