webstation-broker
DeveloperPython referenceemulatorsemulators.pcsx2

emulators.pcsx2.Pcsx2

PlayStation 2 sessions on pcsx2-qt.

PlayStation 2 sessions on pcsx2-qt.

The broker launches pcsx2-qt -batch -fullscreen -- <disc> after forcing its settings into PCSX2.ini (PINE on, fullscreen, no shutdown confirm, no setup wizard, no state on shutdown, the broker's folder card in Slot 1) and binding Pad1 to the Selkies SDL gamepad. Save states are driven over the PINE Unix socket: a save command into state_slot followed by a poll of the state directory, because PINE acks before the file is written, and a load command once a state is confirmed on disk. Every launch starts a boot watchdog that polls the VM status; a resume is delivered as a deferred load once the VM reports running, and a VM that never runs while the process stays alive is flagged as a boot failure, since PCSX2 parks on an error dialog instead of exiting.

Save data lives in a folder memory card the broker owns in Slot 1, so the whole-card routes can ship and replace it as an image; a missing card path would make PCSX2 create a file card instead, which cannot. States and memory cards both ride the save archive. A state file is named for the serial PCSX2 reads off the running disc, so pushed names are restamped into the broker's slot and the working slot is cleared before a boot, to stop the previous session's state being served as this one's.

Declared imports take one folder memory card, under save or memcard; states go to the push route. See import_spec.

Attributes

attributename
= 'pcsx2'

RomM platform key, pcsx2.

attributerom_cacheable
= True

On; with the ROM cache enabled, launch boots a local copy of the ROM.

attributedisplay_name
= 'PCSX2'

Human-readable name shown in the UI.

attributeclears_stale_saves
= True

On; activate empties the save subtrees this session owns.

attributesave_root
= DATA_DIR

PCSX2's data root (DATA_DIR), which the save subtrees hang off.

attributesave_subtrees
= ('memcards', 'sstates')

memcards and sstates, the directories the save archive carries.

attributestate_subtrees
= ('sstates',)
attributememory_card_subtree
= 'memcards'

Subtree the whole-card routes operate on.

attributememory_card_marker
= SLOT1_MARKER

File whose presence makes PCSX2 treat a directory as a folder card.

attributerom_extensions
= ROM_EXTENSIONS

Bootable disc formats, best first.

attributesupports_states
= True

True, states are saved and loaded over PINE.

attributestate_slot
= STATE_SLOT

The slot this instance works in, echoed back as the effective slot. Defaults to STATE_SLOT and is the only slot any state operation reads.

attributestate_dir
= SSTATE_DIR

Where PCSX2 writes .p2s files.

attributelog_path
= PCSX2_LOG_PATH

The pcsx2-qt log the broker exposes.

attribute_launch_seq
= 0

Functions

func__init__(self) -> None

Set up the process state, the working slot, and the sequence counter that fences the watchdog.

paramself

Returns

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

Resolve a RomM path to the disc image to boot.

A file is taken as is. A directory is searched one level deep for the best candidate by _pick_rom_file.

paramself
parampathPath

The ROM file or folder RomM handed over.

Returns

typing.Optional

The image to pass to pcsx2-qt, or None when there is nothing bootable.

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

Return the path of the Slot-1 folder card the whole-card routes ship and replace.

paramself
paramplatformOptional[str]
= None

Returns

typing.Optional[pathlib.Path]
func_ensure_folder_card(self) -> None

Have a folder card waiting at the Slot-1 path before PCSX2 opens it.

A path that is not there is what makes PCSX2 write itself a fresh 8 MB file card, and a file card cannot be shipped or replaced as an image, so the whole-card routes would refuse the container from then on. A failure to create the card is logged, not raised.

paramself

Returns

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

Fetch the patch bundle if needed, stop the running instance, prepare config and card, then start.

The binary comes from env PCSX2_BIN (default pcsx2-qt). A boot watchdog thread is always started; it verifies boot and only delivers a state load when resume_slot is set.

Raises

  • RuntimeError: When PCSX2.ini could not be patched, which leaves PINE off and the session with no save states and no boot signal.
paramself
paramrom_pathPath

The disc image to boot.

paramresume_slotOptional[int]

Slot to load once the VM is running, or None to boot clean.

Returns

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

Verify the launched game reaches a running VM and deliver a deferred state load.

Polls the PINE status once a second until RESUME_LOAD_WAIT runs out. Once the VM runs, a requested resume waits RESUME_LOAD_SETTLE, then up to RESUME_STATE_WAIT for the state file to exist, then loads it. The state wait starts from that point rather than sharing the boot deadline, which a slow boot can have already used up. A process that is still alive when the deadline passes without the VM ever running is the boot-error-dialog case: PCSX2 does not exit, so nothing else in the broker would ever notice; boot_failed is set for it. The watchdog abandons itself whenever seq no longer matches the current launch.

paramself
paramslotOptional[int]

Slot to load after boot, or None for boot verification only.

paramseqint

The launch sequence number this watchdog belongs to.

Returns

None
funcsave_state(self, slot) -> bool

Save a state into the broker's slot over PINE and wait for it to land.

slot is what RomM asked for and is ignored: this saves into state_slot and the caller reads the effective slot back off that same attribute. PINE can address any slot directly, but RomM keeps the library of states, so working in one slot is all this needs to do. A write that never settles is not left on disk, because the exit archive would ship the truncated file as this session's save.

paramself
paramslotint

The slot RomM requested; not used.

Returns

bool

True once the state file has been written and settled within PINE_WAIT, False if

func_state_matches_disc(self, state) -> bool

Tell whether state was captured from the disc PCSX2 is running.

PCSX2 resolves a load by the serial it reads off the running disc, not by the path the broker points at, and acks the command whether or not it found anything. A state belonging to another disc is therefore reported back as a successful load while the player carries on in an untouched game, so the two serials are compared here instead of trusting the ack. An unavailable serial skips the check rather than failing it: a build that does not answer the game-id opcode would otherwise lose every resume.

paramself
paramstatePath

The state file about to be loaded.

Returns

bool

False only when both serials are known and name different discs.

funcload_state(self, slot) -> bool

Load the broker's slot over PINE.

PINE acks a load for an empty slot and for a state belonging to another disc, so both have to be caught here or the caller reads a no-op as success.

paramself
paramslotint

The slot RomM requested; the broker's state_slot is what gets loaded.

Returns

bool

True when a state file for the running disc exists and PINE accepted the load,

funcstate_path(self) -> Optional[Path]

Return the newest state file in the broker's slot, or None when it holds nothing.

paramself

Returns

typing.Optional[pathlib.Path]
funcclear_working_slot(self, excluded=()) -> None

Empty the save subtrees this session owns before the archive restore.

A .p2s is named for the disc it was taken from, and the serial only comes off the running disc, so a leftover cannot be told apart from the state of the game about to boot. Anything still here belongs to a session that has already exited and whose states RomM holds, so dropping it is what stops the last player's save being served as this one's. The archive restore and the resume push both land afterwards.

The memory cards go the same way whenever they travel in the archive. A .ps2 card is named by slot, not by player, so the last player's card would otherwise be mounted for this one and ship back out in their dump.

paramself
paramexcludedtuple[str, ...]
= ()

Subtrees carried by the whole-card routes. memcards lands here when the caller syncs the card separately, and the card is written before activate runs, so clearing it here would delete what RomM just laid down.

Returns

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

Map a pushed state's filename to where it may be written.

PCSX2 finds a state by the serial it reads off the running disc, so the serial is what a pushed name has to get right; the slot it was captured in is rewritten to this broker's. With the slot already holding a state, that name is the one to match, otherwise the serial is taken on trust, bounded to a <serial>.<slot>.p2s basename in the state dir.

paramself
paramfilenamestr

The basename RomM is pushing.

Returns

typing.Optional

The path to write to, or None when the name is not a plain, printable basename, is not

funcimport_spec(self) -> imports.ImportSpec

Declare what PCSX2 takes: one folder memory card, as kind save or memcard.

Players call the card either, so both kinds take the same shape, and the card lands as the broker's slot-1 card. States stay on the push route, which restamps them into state_slot. A folder card holds every game's saves, so no member is held to the session's serial.

paramself

Returns

webstation_broker.imports.ImportSpec

The spec.

funcplace_import(self, member, spec, ctx) -> Union[imports.Placement, imports.ImportRefusal]

Place one file of the declared folder card inside the broker's slot-1 card.

No member is held to the session's serial: one card holds many games' saves, and a player's card rightly carries the others.

paramself
parammemberimports.ImportMember

The member, already past the kind gate.

paramspecimports.ImportSpec

This emulator's spec.

paramctximports.ImportCtx

The launch context.

Returns

typing.Union

The placement, or a refusal.

funcvalidate_import_plan(self, plan, ctx) -> list[imports.ImportRefusal]

Refuse an imported card that is not one whole folder card, alone in the archive.

Slot 1 mounts one card, so every member must come from one card folder. The archive must not carry a memcards/ card of its own: the import would be merged into it, or leave it beside the slot-1 card, mounted nowhere, with its saves out of play. PCSX2 skips a folder with no _pcsx2_superblock, and an empty one is a card cut short.

The first of those checks that fails refuses every member the shared one-member-per-destination check has not already refused. A member it did refuse still counts toward the facts: two cards' superblocks clash with each other, and are still two cards. With the card on its own routes this session, check_plan has already refused every member as memcard_synced_separately.

paramself
paramplanlist[imports.Placement]

The placements that passed every per-member check.

paramctximports.ImportCtx

The launch context; its archive_paths are the archive's ordinary members.

Returns

list

One refusal per card member the shared check left alone, or none.

funcidentity_source(self) -> Optional[imports.IdentitySource]

Take the session's serial from RomM, for the push route's check.

The broker never reads a serial off the disc, so RomM's title_id is the only source. No imported member is held to it; a pushed state named for another serial is refused.

paramself

Returns

typing.Optional

A PlayStation serial source with no rom reader.

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

Save a state if asked, then stop the emulator.

paramself
paramslotOptional[int]

Slot RomM asked to save into (resolved to state_slot), or None to exit without saving a state.

Returns

dict

A dict with state_saved (bool), state_slot (the effective slot, or None when no

funcstop(self) -> None

Invalidate any in-flight boot watchdog, and any state load it might still deliver, then kill.

paramself

Returns

None

On this page