emulators.scummvm.Scummvm
ScummVM sessions, driven through the launcher's own config and menus.
ScummVM sessions, driven through the launcher's own config and menus.
A launch registers the game folder with scummvm --add, pins the settings
the macros and the stream depend on, and boots scummvm <target>. A resume
whose state is already on disk goes in on the command line with
--save-slot, which every engine reads in its startup path, so the game
never shows its title screen first; a state RomM pushes after activate
returns is delivered by a deferred thread over the GMM instead.
Saving and loading drive the Global Main Menu with xdotool, and the macro is silent, so a save is confirmed by watching the slot's file rather than by the keystrokes going out. Engines without runtime save support (gob's password-based games are the canonical case) put up a message dialog instead: the write never lands, the dialogs are dismissed, and the failure is reported rather than left as a game paused behind a menu.
Saves and states share saves/, so save_file_kind splits them by name
rather than by subtree, and the archive carries both.
A multilingual folder registers one target per language it detects, so the language the session was activated for decides which of them boots; without one the pick is alphabetical, which hands a French player a German game.
Declared imports take a save file per member, and one of them as the state.
The stem has to name a game of the folder, and the target the session boots
is worked out the way launch does, registering the folder when it is new.
Attributes
attributename= 'scummvm'Registry key, scummvm.
attributedisplay_name= 'ScummVM'Human-readable name shown in the UI.
attributesave_root= DATA_DIRScummVM's data directory, which the save subtree hangs off.
attributesave_subtrees= ('saves',)saves, holding the game's saves and the working slot alike.
attributestate_subtrees= ()Empty, because states are not in a subtree of their own.
attributeclears_stale_saves= TrueOn; activate empties the save directory.
attributesupports_states= TrueTrue, over the Global Main Menu.
attributestate_slot= STATE_SLOTThe one slot the broker works in, echoed back as the effective slot.
attributestate_dir= SAVE_DIRWhere ScummVM writes every save.
attributelog_path= SCUMMVM_LOG_PATHThe ScummVM output the broker exposes.
attributeterm_timeout= float(os.environ.get('SCUMMVM_STOP_WAIT', '10'))Seconds SIGTERM gets, which ScummVM spends flushing its config.
attribute_rom_dirOptional[Path]= NoneWhat resolve_rom_file picked: the folder launch registers, or an archive it extracts first.
attribute_targetOptional[str]= NoneThe target the running game booted under, and every save is named after.
Kept after the process stops: the state routes are read once the game is already gone, and they have no other way to name its files.
attribute_launch_seq= 0Bumped per launch, so a deferred resume can tell it has been superseded.
attributerom_extensionstuple[str, ...]The .scummvm marker file some libraries use; a game is a folder.
Functions
func__init__(self) -> NoneStart with no game folder, no target, and no launch behind it.
paramselfReturns
Nonefuncresolve_rom_file(self, path) -> Optional[Path]Resolve a RomM path to the game folder to register, or the archive to extract.
Kept cheap: the detection pass that can actually answer "is there a
game in here" takes seconds, and extracting an archive can take
longer, so both run in launch. A folder that holds files is accepted
here and only a folder ScummVM detects nothing in fails, later, as a
launch failure.
paramselfparampathPathThe ROM as RomM delivered it: the game folder, a file inside
it (a .scummvm marker, or the single file a library pointed
at), or an archived game.
Returns
typing.OptionalThe game folder, or the archive when the game is archived (a loose
func_resolve_archive(self, archive) -> Optional[Path]Accept an archived game for launch to extract.
paramselfparamarchivePathThe archive, a loose ROM file or the lone file in its folder.
Returns
typing.OptionalThe resolved archive, or None when the cache is off, it is not a
func_xdotool(self, *args, *, quiet=False) -> Optional[str]Run one xdotool command against the session display.
paramselfparamargsstr= ()paramquietbool= FalseSuppress the failure warning. For a caller polling for
something that is not there yet, where search exiting
non-zero is the expected answer rather than a fault.
Returns
typing.OptionalIts stdout, or None if it could not be run, timed out, or exited non-zero.
func_window(self, quiet=False) -> Optional[str]The X window this launch's ScummVM renders into.
Matched on this process's own pid, so a window left behind by an earlier session cannot swallow the macro. The last id wins: xdotool lists in creation order and ScummVM's game window is created after the transient ones.
paramselfparamquietbool= FalseSuppress the "no window" warning. For a caller polling for a window that is not mapped yet, where absence is the normal answer until it is not.
Returns
typing.OptionalThe window id as xdotool prints it, or None when this process has
func_activate(self, win_id) -> boolGive win_id the input focus before keys are sent at it.
The menu key and the button hotkey go through XTEST, which delivers to whatever holds focus, so a key sent at an unfocused game lands on the desktop instead.
paramselfparamwin_idstrThe window to focus.
Returns
boolTrue once the window is active.
func_open_gmm(self) -> boolOpen the Global Main Menu with the pinned menu key.
One unmodified key, bound to the global keymap in the ini before the
launch: see MENU_KEY for why neither ScummVM's own C+F5 nor the
engine-level F5 can be relied on here.
paramselfReturns
boolTrue when the keystroke went out.
func_type(self, text) -> boolType text into the focused window.
type rather than key: the GMM's button hotkeys follow the GUI
translation and can be Cyrillic, Greek or Hebrew, and only type
synthesizes the keysym whatever the X keyboard layout is.
paramselfparamtextstrThe hotkey letter to send.
Returns
boolTrue when the keystroke went out.
func_keys(self, win_id, keys) -> boolSend several keys to win_id in one xdotool call.
One call rather than one per key: each costs a process spawn, and the chooser walk sends one key per slot.
paramselfparamwin_idstrThe window to send to.
paramkeyslist[str]Key names in xdotool's syntax.
Returns
boolTrue when the keystrokes went out.
func_wait_for_write(self, before, deadline) -> boolPoll the working slot until this save's write settles, or deadline passes.
The first change is not the finished save. The caller stops ScummVM as
soon as this returns and the broker zips the save directory right after,
so returning on the first differing stat sends SIGTERM into a write
still in progress and ships whatever landed to RomM as the player's
progress. A write therefore only counts once the slot differs from
before, holds at least one non-empty file, and has held every size and
mtime still for STATE_STABLE.
paramselfparambeforedict[str, tuple[float, int]]The slot's (mtime, size) per filename, from before the macro.
paramdeadlinefloatA time.monotonic() value to give up at.
Returns
boolTrue once a slot file appeared or changed and its write settled,
funclaunch(self, rom_path, resume_slot) -> NoneRegister the game, pin the ini, and boot ScummVM on its target.
The folder is registered with --add and the target read back out of
the ini; a folder ScummVM detects nothing in is a launch failure, since
there is nothing to boot. With resume_slot set and the working slot
already holding this target's save, the state loads at boot through
--save-slot; otherwise a deferred thread waits for RomM's push and
loads it over the menu.
An archived game is extracted into the cache first (or its earlier extraction reused), and the game folder inside it is what gets registered.
Raises
RuntimeError: When no ROM folder was resolved, an archive could not be extracted, or ScummVM detects no game in it.RegisteredElsewhere: When another copy of the game holds its target.
paramselfparamrom_pathOptional[Path]The game folder or archive, as returned by resolve_rom_file.
paramresume_slotOptional[int]The slot to resume from, or None to boot clean.
Returns
Nonefunc_fill_screen(self, seq) -> NoneKeep the game window the size of the display, for as long as it runs.
Resizing once at launch is not enough: the display is sized by the streaming client, so a game that starts before a browser has connected is grown to whatever the last session left behind and then sits in the top left corner of a screen that changed under it. The same happens mid-session when a viewer resizes their browser. So this follows the display instead of photographing it, and exits when the launch is superseded or the game is gone.
Purely cosmetic, so every failure is logged and swallowed: a game running at its own size is worth more than a launch reported as broken.
paramselfparamseqintThe launch sequence number this belongs to.
Returns
Nonefunc_display_size(self) -> Optional[tuple[str, str]]The display's current size as a (width, height) pair of digits.
paramselfReturns
typing.OptionalThe pair, or None when xdotool could not be asked or answered
func_deferred_load_state(self, seq) -> NoneWait for a pushed state to arrive, then load it through the menu.
Gives the file RESUME_LOAD_WAIT to turn up and then the game
RESUME_LOAD_SETTLE to be far enough into its startup to answer the
menu. Abandons itself as soon as seq no longer matches the current
launch, so a superseded launch never gets a stray load.
paramselfparamseqintThe launch sequence number this load belongs to.
Returns
Nonefuncsave_state(self, slot) -> boolSave the running game into the broker's slot through the GMM.
slot is what RomM asked for and is ignored: the save lands in
STATE_SLOT and the caller reads the effective slot back off
state_slot. The chooser opens with its list focused and nothing
selected, so the first Down lands on slot 0 and slot N needs N+1 of
them; the list index is the slot number because empty slots still take
a row. In save mode the first Return starts editing the slot's
description and the second commits it, which is also the save.
paramselfparamslotintThe slot RomM requested; not used.
Returns
boolTrue once the slot's file has changed on disk within STATE_WAIT,
funcload_state(self, slot) -> boolLoad the broker's slot into the running game through the GMM.
In load mode the list is not editable, so a single Return activates
the selected slot and the GMM closes itself.
paramselfparamslotintThe slot RomM requested; STATE_SLOT is what gets loaded.
Returns
boolTrue when the slot holds a save and the keystrokes went out, False
funcstate_path(self) -> Optional[Path]The working slot's save file for the booted target, or None when empty.
paramselfReturns
typing.Optional[pathlib.Path]funcclear_working_slot(self, excluded=()) -> NoneEmpty the save directory before a new session boots.
The target only exists once a game has been registered and booted, so at activate time a leftover cannot be told apart from the save of the game about to start. Anything still here belongs to a session that has already exited and whose saves RomM holds; the incoming archive restores whatever should be here.
The whole directory goes, not just the broker's slot. ScummVM names a
save <target>.<NNN> for the slot the player chose in the game's own
menu, and nothing in that name says whose session wrote it, so
clearing only the broker's slot leaves every save the last player made
from inside the game readable by this one and swept into their dump.
paramselfparamexcludedtuple[str, ...]= ()Subtrees carried by the whole-card routes. ScummVM has no memory card, so this is always empty.
Returns
Nonefuncstate_target(self, filename) -> Optional[Path]Where a pushed state called filename belongs.
The name is rewritten onto the booted target and the broker's slot: ScummVM finds a save by name alone, so a state captured under another target (a multilingual folder registers one per language) has to be renamed or the engine will not see it. The suffix form the state arrived in is kept, since which of the two an engine writes is the engine's business.
paramselfparamfilenamestrThe name RomM stored the state under.
Returns
typing.OptionalThe path to write to, or None when the name is not a ScummVM save
funcsave_file_kind(self, rel) -> strClassify an archive member for the manifest.
ScummVM has no state format of its own, so the working slot's file is a save like every other and only its name says otherwise. Naming the target as well as the slot is what keeps another game's save in the same slot from being filed as this session's state.
paramselfparamrelstrThe member path, relative to save_root and posix-separated.
Returns
strstate for the booted target's working-slot save, save for
funcimport_spec(self) -> imports.ImportSpecDeclare what ScummVM takes: save files, and one of them as the state.
Saves and states share saves/, so the two kinds have the same shapes.
The state rides the archive and resumes through save.resume_slot,
which boots the game with --save-slot.
paramselfReturns
webstation_broker.imports.ImportSpecThe spec.
funcplace_import(self, member, spec, ctx) -> Union[imports.Placement, imports.ImportRefusal]Place one declared member: a save, or the state.
paramselfparammemberimports.ImportMemberThe member, already past the kind gate.
paramspecimports.ImportSpecThis emulator's spec.
paramctximports.ImportCtxThe launch context.
Returns
typing.UnionThe placement, or a refusal.
funcvalidate_import_plan(self, plan, ctx) -> list[imports.ImportRefusal]Refuse a state that shares the working slot with a save the archive carries.
check_plan counts the archive's own states by save_file_kind, which
answers save for everything until a game has booted. So a v1 save in
the working slot under the target's name goes uncounted there, and
would be restored on top of the state, or beside it under the other
spelling.
paramselfparamplanlist[imports.Placement]The placements that passed every per-member check.
paramctximports.ImportCtxThe launch context.
Returns
listOne destination_conflict for the state, or nothing when the state
funcidentity_source(self) -> Optional[imports.IdentitySource]Read the target the folder is registered under, for the session's identity.
paramselfReturns
typing.OptionalThe source.
func_read_target(self, rom) -> Optional[str]Look up the target registered for rom, without registering it.
Only a lookup: a launch that ran no preflight reads this in a worker thread, and registering can take up to two minutes. The identity is informational, so an unregistered folder reads as no identity.
paramselfparamromPathThe game folder, or an archived game.
Returns
typing.OptionalThe target, or None when the ini has no domain for the folder, or
funcsave_and_exit(self, slot) -> dict[str, Any]Save through the menu if asked, then stop ScummVM.
paramselfparamslotOptional[int]The slot RomM asked to save into, resolved to STATE_SLOT,
or None to exit without writing a state.
Returns
dictA dict with state_saved (bool), state_slot (the effective slot,