webstation-broker
DeveloperPython referenceemulators

emulators.pcsx2

PCSX2 launcher: disc-image resolution, PCSX2.ini patching, and PINE-driven save states.

PCSX2 launcher: disc-image resolution, PCSX2.ini patching, and PINE-driven save states.

Resolves a RomM ROM folder to the disc image pcsx2-qt boots, forces the broker-required PCSX2.ini settings before every launch, keeps a folder memory card waiting in Slot 1, and drives save state load and save over PCSX2's PINE socket. A boot watchdog confirms the VM actually reaches a running state, because a disc that fails to boot leaves PCSX2 sitting on an error dialog rather than exiting.

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 disc image must sit under it; candidates resolving outside are discarded.

attributeDATA_DIR
= _data_root()

PCSX2's data root, holding inis/, memcards/ and sstates/.

Not configurable, and deliberately: nothing on pcsx2-qt's command line names it (-help offers only -portable, which forces the tree next to the binary), so an override would move only the ini the broker patches and the states it globs, while PCSX2 kept resolving its own. launch exports the root this resolved to instead.

attributeINI_PATH
= DATA_DIR / 'inis' / 'PCSX2.ini'

The PCSX2.ini the broker patches before every launch, inis/PCSX2.ini under DATA_DIR.

attributeSSTATE_DIR
= DATA_DIR / 'sstates'

Directory PCSX2 writes its .p2s save states into, sstates under DATA_DIR.

attributePATCHES_ZIP
= _PATCHES_ZIP_SYSTEM_PATH

The GameDB/widescreen/fix patch bundle PCSX2 opens at boot, from its install resources.

Not configurable, and not under DATA_DIR: pcsx2-qt reads this one path and no other (a copy under the user tree is ignored), and it never downloads the file itself. The Ubuntu +dfsg package ships without it, so a missing file means a permanent "Failed to open patches.zip" warning and no game fixes. _ensure_patches_zip fetches it before a launch.

attributePATCHES_URL
= 'https://github.com/PCSX2/pcsx2_patches/releases/download/latest/patches.zip'

Upstream's rolling release of the patch bundle, rebuilt by CI on every push to its main branch.

Hardcoded, like the destination: this download ends in a root-owned file, so neither end of it may come from a request, the environment or config. Upstream publishes no checksum, so trust rests on TLS to GitHub plus _patches_zip_problem.

attributePATCHES_FETCH
= _fetch_enabled(os.environ.get('PCSX2_PATCHES_FETCH', 'true'))

Whether launches fetch patches.zip (env PCSX2_PATCHES_FETCH, default true).

Set it to false (or 0, no, off) on a deploy with no route to GitHub, or one that should not use sudo at all: PCSX2 then runs without game patches, as it did before.

attributePATCHES_MAX_AGE
= 7 * 24 * 3600.0

Age in seconds past which an installed bundle is refreshed in the background.

Upstream rebuilds on every merge, but patches change slowly; a week keeps launches to one fetch between container recreations without letting fixes lag far behind.

attributePCSX2_LOG_PATH
= Path(os.environ.get('PCSX2_LOG_PATH', '/config/pcsx2-qt.log'))

Log file the broker tails for this emulator (env PCSX2_LOG_PATH, default /config/pcsx2-qt.log).

attributeSTATE_SLOT
= int(os.environ.get('PCSX2_STATE_SLOT', '10'))

Default slot the broker works in (env PCSX2_STATE_SLOT, default 10).

10 is PCSX2's own autosave slot, which the per-emulator broker has always used, so containers that ran that one keep resolving their existing states.

Only a default: each Pcsx2 copies it into state_slot at construction and every state operation reads that instance attribute, so a session can be pointed at another slot without moving the whole process.

attributeMEMCARD_DIR
= DATA_DIR / 'memcards'

Directory PCSX2 keeps its memory cards in, memcards under DATA_DIR.

attributeSLOT1_CARD_NAME
= _slot1_card_name(os.environ.get('PCSX2_SLOT1_CARD', _DEFAULT_SLOT1_CARD))

Name of the Slot-1 card the broker owns (env PCSX2_SLOT1_CARD, default romm-slot1).

PCSX2 tells a folder card from a file card by what it finds at the path, so this is a directory and the name carries no .ps2 extension, to keep it from reading as one of PCSX2's own file cards. Validated by _slot1_card_name.

attributeSLOT1_MARKER
= '_pcsx2_superblock'

File that marks a directory as a folder card.

PCSX2 only counts a directory as a folder card once this file is inside it, and skips the directory entirely otherwise, which leaves the slot reading as missing with nowhere for the game to save.

attributePINE_WAIT
= float(os.environ.get('PINE_WAIT', '20.0'))

Seconds a save state has to land on disk after the PINE save command (env PINE_WAIT, default 20).

attributeRESUME_LOAD_WAIT
= float(os.environ.get('RESUME_LOAD_WAIT', '90.0'))

Seconds the boot watchdog gives the VM to start running (env RESUME_LOAD_WAIT, default 90).

attributeRESUME_LOAD_SETTLE
= float(os.environ.get('RESUME_LOAD_SETTLE', '3.0'))

Seconds to wait after the VM reports running before a resume load is sent (env RESUME_LOAD_SETTLE).

attributeRESUME_STATE_WAIT
= float(os.environ.get('PCSX2_RESUME_STATE_WAIT', '60.0'))

Seconds a booted VM gives the resume state to arrive (env PCSX2_RESUME_STATE_WAIT, default 60).

Its own budget rather than whatever is left of the boot deadline: RomM pushes its resume pick after activate returns, so a slow boot would otherwise leave the wait no time at all and the player would start a fresh game.

attributePINE_SOCKET
= Path(XDG_RUNTIME_DIR) / 'pcsx2.sock'

Path of the PINE Unix socket pcsx2-qt listens on.

attributeROM_EXTENSIONS
= ('.chd', '.iso', '.cso', '.zso', '.gz', '.mdf', '.dump', '.bin', '.elf')

Disc formats pcsx2-qt can boot, best first.

A folder holding several candidates picks by this order so a .chd beats the raw .bin beside it.