emulators.ppsspp
PPSSPP (Sony PSP) launcher: ROM resolution, ini patching, and hotkey save states.
PPSSPP (Sony PSP) launcher: ROM resolution, ini patching, and hotkey save states.
PPSSPP has no control socket. Everything the broker needs pinned lives in two
inis, both patched before every launch. ppsspp.ini gets FirstRun and
CheckForNewVersion so a freshly seeded /config never opens the setup wizard or
an update toast behind a session nobody can click into, and StateSlot so every
save/load hotkey lands on the broker's one working slot without ever cycling
it. controls.ini gets the bracket keys added to Save State and Load State:
F1-F12 never reach PPSSPP through this container's streaming/input stack
(confirmed empirically, including with a real keyboard through the user's own
desktop session), so the brackets go in on every launch rather than being
trusted to survive from a one-off manual bind through PPSSPP's own remap UI.
They are merged into the action's existing comma-separated mapping list, not
written over it, so whatever else the player bound to those actions still
works. Both files are written with a leading UTF-8 BOM; the patcher has to strip
it on read and put it back on write; a naive line scan would loosen the BOM
onto the first [section] line and never match it.
Save states have no boot-time load flag, unlike Dolphin's -s, so a resume
always goes through the deferred hotkey load below rather than loading before
the window exists to fail into.
attributelog= logging.getLogger(__name__)attributeROM_ROOT= Path(os.environ.get('ROM_ROOT', '/romm'))Root of the RomM library mount (env ROM_ROOT, default /romm).
A resolved ROM must sit under it; candidates resolving outside are discarded.
attributeCONFIG_DIR= xdg_config_dir('ppsspp')PPSSPP's config root, which also holds the emulated memory stick.
Not configurable, and deliberately: nothing on PPSSPP's command line names it,
so an override would move only the copy the broker reads and writes. Since the
save data and states live under it too, that would point the dump and restore
at a tree PPSSPP does not write to. launch exports the root this resolved to
instead, which is an agreement the two cannot fall out of.
attributePSP_DIR= CONFIG_DIR / 'PSP'The emulated memory stick root, holding save data, states and the system inis.
attributeSYSTEM_DIR= PSP_DIR / 'SYSTEM'Directory holding ppsspp.ini and controls.ini.
attributeINI_PATH= SYSTEM_DIR / 'ppsspp.ini'The main ini the broker patches before every launch.
attributeCONTROLS_INI_PATH= SYSTEM_DIR / 'controls.ini'The control mapping ini the broker patches before every launch.
attributeSTATE_DIR= PSP_DIR / 'PPSSPP_STATE'Directory PPSSPP writes its .ppst save states and their .jpg screenshots into.
attributePPSSPP_LOG_PATH= Path(os.environ.get('PPSSPP_LOG_PATH', '/config/ppsspp.log'))Log file the broker tails for this emulator (env PPSSPP_LOG_PATH, default /config/ppsspp.log).
attributeSTATE_SLOT= int(os.environ.get('PPSSPP_STATE_SLOT', '1'))The one slot the broker works in (env PPSSPP_STATE_SLOT, default 1).
RomM holds the library of states, so a requested slot resolves to this one and the routes echo the effective slot.
attributeSAVE_KEY= 'bracketleft'xdotool key for saving the working slot.
F1-F12 never reach PPSSPP through this container's streaming/input stack
(confirmed empirically: bound and unbound alike, nothing was ever
delivered), so the working hotkeys are the bracket keys instead. The
device-1 code is NKCODE (device 1 is the keyboard; device 10 entries
elsewhere in this file are the gamepad's own, unrelated numbering) per
PPSSPP's Qt/NKCodeFromQt.h, which maps Qt::Key_BracketLeft/Right to
NKCODE_LEFT_BRACKET/RIGHT_BRACKET (71/72, Common/Input/KeyCodes.h), not
the 132/134 an earlier remap-UI capture recorded, which traced back to the
same broken injection path this binding works around.
attributeLOAD_KEY= 'bracketright'xdotool key for loading the working slot; see SAVE_KEY for why it is a bracket key.
attributeSTATE_WAIT= float(os.environ.get('PPSSPP_STATE_WAIT', '20.0'))Seconds a save state has to land on disk after the save hotkey (env PPSSPP_STATE_WAIT, default 20).
attributeSTATE_STABLE= float(os.environ.get('PPSSPP_STATE_STABLE', '1.5'))Seconds a state's size and mtime must both hold still before the write counts as finished.
From env PPSSPP_STATE_STABLE, default 1.5. Long enough that a stalled write
is not mistaken for a finished one, short enough to stay well inside
STATE_WAIT.
attributeLOAD_WAIT= float(os.environ.get('PPSSPP_LOAD_WAIT', '20.0'))Seconds PPSSPP has to read the state back after the load hotkey (env PPSSPP_LOAD_WAIT, default 20).
attributeLOAD_SETTLE= float(os.environ.get('PPSSPP_LOAD_SETTLE', '2.0'))Seconds a load gets to deserialize once the state file has been read.
From env PPSSPP_LOAD_SETTLE, default 2. The deserialize itself is
unobservable from outside the process, so this is the one bounded guess in
the load path.
attributeRESUME_LOAD_WAIT= float(os.environ.get('PPSSPP_RESUME_LOAD_WAIT', '90.0'))Seconds a deferred resume has to find both a state file and a game window (env PPSSPP_RESUME_LOAD_WAIT).
Defaults to 90. One budget covers both: a resume state is usually already on disk from the save archive, so nearly all of it goes on the boot.
attributeRESUME_LOAD_SETTLE= float(os.environ.get('PPSSPP_RESUME_LOAD_SETTLE', '12.0'))Seconds to wait after the game window is up before the load hotkey goes in.
From env PPSSPP_RESUME_LOAD_SETTLE, default 12. PPSSPP titles its window
with the game as soon as the disc is mounted, but keeps loading and
registering HLE module state well past that point, and a load that lands
before the event table is complete leaves the core reporting an unregistered
event and a black screen. This is not the wait for the window itself (that is
polled for): it is the margin between a window saying a game is running and a
core that can actually be loaded into.
attributeROM_EXTENSIONS= ('.chd', '.cso', '.pbp', '.iso', '.elf', '.prx')All formats PPSSPP boots directly, best first.
Best means most likely to be the verified, compressed copy; a folder holding several candidates picks by this order. PSP has no multi-disc titles, so there is no disc number to rank on.
attributeCACHE_DIR= Path(os.environ.get('PPSSPP_CACHE_DIR', str(xdg_data_dir('ppsspp') / 'extracted')))Where archived ROMs are extracted and booted from (env PPSSPP_CACHE_DIR).
Defaults to $XDG_DATA_HOME/ppsspp/extracted.
An extraction is kept and reused by every later launch of the same archive.
attributeCACHE_MAX_GB= float(os.environ.get('PPSSPP_CACHE_MAX_GB', '20'))Cap on the extraction cache in GB (env PPSSPP_CACHE_MAX_GB, default 20).
The least recently booted ROMs are evicted to make room for a new one.
attributeARCHIVE_LIST_TIMEOUT= 60.0Seconds 7z or unrar gets to list an archive's members before it is considered hung.