webstation-broker
DeveloperPython referenceemulators

emulators.rpcs3

RPCS3 (PlayStation 3) emulator launcher.

RPCS3 (PlayStation 3) emulator launcher.

Handles config.yml/ipc.yml patching, the PKG install hook, PINE-driven boot verification, and save states via the emulator's own exit-on-save hotkey.

Boot formats: decrypted .iso images and disc folder rips (PS3_GAME/USRDIR/EBOOT.BIN) boot directly. A .pkg is an installer, not an image: the first activation installs it via --headless --installpkg into dev_hdd0/game/<TITLEID>/ and later activations boot the installed EBOOT.BIN. .rap/.edat licenses are plain files copied into dev_hdd0/home/00000001/exdata/.

Save states: unlike PCSX2, RPCS3 has no live save/load. Pressing the save-state hotkey always terminates the process once the state is written, and loading one means booting RPCS3 pointed straight at the .SAVESTAT file (auto-detected by magic bytes, not extension) instead of a normal boot target, functionally the same as booting a ROM. So the broker follows DuckStation's model rather than PCSX2's: no live supports_states, a resume loads by choosing what to boot before the process starts, and a save happens by triggering the hotkey and waiting for the process to exit rather than by keeping it running. States are per-title, at DATA_DIR/savestates/<title_id>/, which is a sibling of dev_hdd0 rather than something under it; a symlink into dev_hdd0/savestates is what lets the existing save archive (keyed to dev_hdd0-relative paths) carry it without moving save_root out from under the subtrees already archived.

PINE: RPCS3's IPC is PINE-compatible but only implements the protocol's generic opcodes (memory access, version/title/status queries) - there is no save/load-state opcode the way PCSX2's variant adds one. The broker uses it for nothing but boot verification (MsgStatus) and, for boots (a bare .iso) whose title id the on-disk layout cannot supply, a title id lookup (MsgID) once the game is confirmed running.

RPCS3 installs no signal handler, so a plain kill (no state requested, or the hotkey/wait having failed) is a safe stop: whatever the game already wrote to its own save data is on disk the moment it's written. cellSaveData saves under dev_hdd0/home/00000001/savedata/, cellGameData saves under dev_hdd0/game/.

The emulator is expected to be brought to a working state in desktop mode (firmware, GPU settings, controllers) before automated launching.

attributelog
= logging.getLogger(__name__)
attributeROM_ROOT
= Path(os.environ.get('ROM_ROOT', '/romm'))
attributeDATA_DIR
= Path(os.environ.get('RPCS3_DATA_DIR', _default_data_dir()))
attributeCONFIG_PATH
= DATA_DIR / 'config.yml'
attributeIPC_PATH
= DATA_DIR / 'ipc.yml'
attributeDEV_HDD0
= DATA_DIR / 'dev_hdd0'
attributeUSER_HOME
= DEV_HDD0 / 'home' / '00000001'
attributeEXDATA_DIR
= USER_HOME / 'exdata'
attributeGAME_DIR
= DEV_HDD0 / 'game'
attributeSSTATE_ROOT
= DATA_DIR / 'savestates'
attributeRPCS3_LOG_PATH
= Path(os.environ.get('RPCS3_LOG_PATH', '/config/rpcs3.log'))
attributeINSTALL_TIMEOUT
= float(os.environ.get('RPCS3_INSTALL_TIMEOUT', '1800'))
attributeROM_EXTENSIONS
= ('.iso', '.pkg', '.bin', '.self', '.elf', '.7z', '.zip', '.rar')
attributeCACHE_DIR
= Path(os.environ.get('RPCS3_CACHE_DIR', str(DATA_DIR / 'extracted')))
attributeCACHE_ENABLED
= _truthy(os.environ.get('RPCS3_CACHE_ENABLED', 'false'))
attributeCACHE_MAX_GB
= float(os.environ.get('RPCS3_CACHE_MAX_GB', '30'))
attributeXDG_RUNTIME_DIR
= os.environ.get('XDG_RUNTIME_DIR', '/config/.XDG')
attributePINE_SOCKET
= Path(XDG_RUNTIME_DIR) / 'rpcs3.sock'
attributeBOOT_WAIT
= float(os.environ.get('RPCS3_BOOT_WAIT', '90.0'))
attributeSTATE_WAIT
= float(os.environ.get('RPCS3_STATE_WAIT', '60.0'))