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= TrueOn; 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= TrueOn; activate empties the save subtrees this session owns.
attributesave_root= DATA_DIRPCSX2'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_MARKERFile whose presence makes PCSX2 treat a directory as a folder card.
attributerom_extensions= ROM_EXTENSIONSBootable disc formats, best first.
attributesupports_states= TrueTrue, states are saved and loaded over PINE.
attributestate_slot= STATE_SLOTThe 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_DIRWhere PCSX2 writes .p2s files.
attributelog_path= PCSX2_LOG_PATHThe pcsx2-qt log the broker exposes.
attribute_launch_seq= 0Functions
func__init__(self) -> NoneSet up the process state, the working slot, and the sequence counter that fences the watchdog.
paramselfReturns
Nonefuncresolve_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.
paramselfparampathPathThe ROM file or folder RomM handed over.
Returns
typing.OptionalThe 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.
paramselfparamplatformOptional[str]= NoneReturns
typing.Optional[pathlib.Path]func_ensure_folder_card(self) -> NoneHave 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.
paramselfReturns
Nonefunclaunch(self, rom_path, resume_slot) -> NoneFetch 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.
paramselfparamrom_pathPathThe disc image to boot.
paramresume_slotOptional[int]Slot to load once the VM is running, or None to boot clean.
Returns
Nonefunc_boot_watchdog(self, slot, seq) -> NoneVerify 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.
paramselfparamslotOptional[int]Slot to load after boot, or None for boot verification only.
paramseqintThe launch sequence number this watchdog belongs to.
Returns
Nonefuncsave_state(self, slot) -> boolSave 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.
paramselfparamslotintThe slot RomM requested; not used.
Returns
boolTrue once the state file has been written and settled within PINE_WAIT, False if
func_state_matches_disc(self, state) -> boolTell 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.
paramselfparamstatePathThe state file about to be loaded.
Returns
boolFalse only when both serials are known and name different discs.
funcload_state(self, slot) -> boolLoad 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.
paramselfparamslotintThe slot RomM requested; the broker's state_slot is what gets loaded.
Returns
boolTrue 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.
paramselfReturns
typing.Optional[pathlib.Path]funcclear_working_slot(self, excluded=()) -> NoneEmpty 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.
paramselfparamexcludedtuple[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
Nonefuncstate_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.
paramselfparamfilenamestrThe basename RomM is pushing.
Returns
typing.OptionalThe path to write to, or None when the name is not a plain, printable basename, is not
funcimport_spec(self) -> imports.ImportSpecDeclare 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.
paramselfReturns
webstation_broker.imports.ImportSpecThe 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.
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 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.
paramselfparamplanlist[imports.Placement]The placements that passed every per-member check.
paramctximports.ImportCtxThe launch context; its archive_paths are the archive's ordinary members.
Returns
listOne 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.
paramselfReturns
typing.OptionalA PlayStation serial source with no rom reader.
funcsave_and_exit(self, slot) -> dict[str, Any]Save a state if asked, then stop the emulator.
paramselfparamslotOptional[int]Slot RomM asked to save into (resolved to state_slot), or None to exit
without saving a state.
Returns
dictA dict with state_saved (bool), state_slot (the effective slot, or None when no
funcstop(self) -> NoneInvalidate any in-flight boot watchdog, and any state load it might still deliver, then kill.
paramselfReturns
None