Skip to content

feat(launcher): emulator games, PS1, PS2 and PS3 discs in an emulator you installed - #121

Merged
utkuhalis merged 6 commits into
mainfrom
feat/parity-emulator-games
Nov 13, 2026
Merged

utkuhalis merged 6 commits into
mainfrom
feat/parity-emulator-games

Conversation

@utkuhalis

@utkuhalis utkuhalis commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

What

This is the Mac counterpart of AnyTogether/AnyPS5-Desktop#85, "add standalone PlayStation emulator library support". B-Howell designed the feature and wrote the Desktop implementation. This PR ports it to the Mac launcher with the same behaviour, wording, preference keys and edge cases as the PR's current head (fc558d0). The two land together, so please do not merge this one on its own.

Emulator games is opt-in. With the switch off, which is the default, nothing changes for anyone.

  • Settings › Game library › Emulator games (retroLibrary). Off by default. When it is on, the library also lists the PS1, PS2 and PS3 discs in the games folders, and Settings › Emulators appears before About. Turning it on announces none of the discs as new games.

  • Settings › Emulators: DuckStation, PCSX2 and RPCS3 (retroEmulators, keyed ps1, ps2, ps3), and Show as folders (showRetroFolders). On the Mac you pick an application or a program. An .app is resolved to its own executable through its Info.plist, so a renamed bundle such as PCSX2-v2.8.2.app still works. A program you pick is used as it is.

  • Discovery, the same as on Desktop:

    • cue sheets whose tracks are all beside them
    • M3U playlists that gather a disc set (their discs are not games of their own then)
    • PBP files with the \0PBP header
    • ISO images whose SYSTEM.CNF has a BOOT2 line (PS2)
    • CHD images under a folder named for exactly one console
    • retro.json game folders
    • extracted PS3 discs named from PS3_GAME/PARAM.SFO

    A file named outside its own folder is refused. That covers an absolute path, a drive letter, .. out of the folder, and a retro.json file that is a link. What was read from a disc file is cached by path, size and modification time, and let go when the file leaves the library.

  • Identity and size: a disc's id is retro:<console>:<title ID or size of its files>:<path inside the games folder>, so it survives a changed mount point. A loose disc's Size card counts only its own files.

  • Home: a disc without its emulator reads Emulator required, and its button (Choose emulator) opens the picker. With Show as folders, a console's games become one tile after the other games once its emulator is chosen. The tile shows its games in a row under it. Down or Select goes in, Up or Back comes out, and △ does nothing on a folder. Home hides the Version and Community cards for a disc.

  • Start, cancel, exit: Play starts the emulator's program directly, with no shell. Cancelling a start frees the slot at once. An emulator that comes up after that is asked to end (SIGTERM) and killed after Stop's grace. A start begun in the meantime is left alone. Stop works the same way. A non-zero exit code is reported unless Stop ended the emulator. A disc whose file is gone says so and does not start.

  • Game Settings for a disc have its emulator and Open log, plus Stop while it plays, as every Mac game's settings do.

  • Kept out: the emulator plays outside the PS5 game's running slot, in a separate emulator slot. AnyTogether presence, game sessions, Discord, Play Together (start, join check, copies), share play, broadcasting and Remote Play all follow running, so they never see a disc. A selected disc is never the title of a post, a Find players filter or a party's listing. The family's play time still counts the emulator, recorded on this Mac only, as Desktop does (reported: false).

  • Texts: Desktop's 13 keys with the same wording, in all 28 language files. Turkish uses "kütüphane" ("kütüphaneyi yeniden tara").

Emulator command lines on macOS

I checked these against each emulator's current source. The Mac uses the same command lines as Windows and Linux.

Console Command Source
PS1 DuckStation.app/Contents/MacOS/DuckStation -nogui -- <disc> stenzek/duckstation src/duckstation-qt/qthost.cpp: -nogui "Disables main window from being shown, exits on shutdown", -- ends the options. The parser has no Apple branch. CMake OUTPUT_NAME DuckStation.
PS2 PCSX2*.app/Contents/MacOS/PCSX2 -nogui -- <disc> PCSX2/pcsx2 pcsx2-qt/QtHost.cpp ParseCommandLineOptions: -nogui implies batch mode and quits when the VM stops. The parser has no Apple branch. The release bundle is renamed PCSX2-<tag>.app but its executable stays PCSX2.
PS3 RPCS3.app/Contents/MacOS/rpcs3 --no-gui <PS3_GAME/USRDIR/EBOOT.BIN> RPCS3/rpcs3 rpcs3/rpcs3.cpp: no-gui option and a positional (S)ELF path. RPCS3 must not get --, because it passes everything after it to the game.

Differences that are the emulators' own and the same on every system:

  • RPCS3 installs no SIGTERM handler, so Stop ends it without a clean shutdown.
  • DuckStation may show its power-off confirmation on SIGTERM, and is killed after the grace if nobody answers it.
  • DuckStation and PCSX2 open their setup wizard once on a fresh install, even with -nogui.

Deliberate differences from Desktop

  • The emulator slot is separate from the PS5 game's slot. Mac code follows running, an NSRunningApplication, for presence, sessions, Discord, share play, broadcasting and Remote Play. Keeping the emulator out of that slot keeps a disc out of all of them, which is what our review asked Desktop for: no synthetic title ID to AnyTogether and no file name to Discord. Desktop's current head still sets running for a disc.
  • No migration of old ids. Desktop's _adoptRetroIds moves last-played entries from an unreleased revision of its PR. The Mac never had disc ids, so there is nothing to move, and our review asks Desktop to drop it.
  • Pause on menu is not sent to an emulator. This is the same as Desktop: the quick menu opens without pausing it.
  • The picker accepts an application bundle as well as a program, and the bundle is resolved to its executable. Desktop takes an .exe on Windows and any file on Linux.
  • Hidden files are skipped in disc folders, as the Mac scan already did for folders. This also skips the ._ files macOS writes on exFAT drives.
  • The console folder tile uses the Mac game tiles' own surface with rings in the theme's accent. It does not use a fixed colour.
  • Make report is hidden in a disc's log, because a report is about the engine.

Release note

Emulator games: turn on Settings › Game library › Emulator games to also list the PS1, PS2 and PS3 games in your games folders. Then pick DuckStation, PCSX2 or RPCS3 in Settings › Emulators, as an app you installed yourself, and play them from Home, optionally grouped in a folder per console. Nothing is bundled or downloaded. With the switch off, the library is exactly as before.

Tested

CI only. Nothing was built or run on a Mac: there was no local swift build or swift test, no emulator was started, and the screens were not looked at.

  • Tests/AnyPS5CoreTests/RetroLibraryTests.swift, mirroring Desktop's retro_library_test.dart with fixtures in temporary folders:
    • separate cue games and a playlist that gathers a disc set
    • PS5 dumps and retro.json games with their media
    • files outside their folder refused
    • ids that survive a moved games folder, and per-game file lists
    • two games folders with a disc at the same path
    • the cache rereading only on a size or time change, and letting go of gone files
    • PS2 ISO detection with synthetic images
    • CHD and PBP by header and console folder
    • a PS3 disc from a synthetic PARAM.SFO
    • bundle resolution and the three emulators' arguments
    • the process wrapper against /bin/sleep and /usr/bin/false
  • Off-by-default proofs:
    • a library with every kind of disc scans exactly as before without the switch (same listing, ids equal to folder paths, same incomplete dumps, same order as the old comparator)
    • the folder watch defaults to no discs
    • in the model, Home's tiles and navigation, the Settings categories and the library are unchanged until the switch is on
  • Tests/AnyPS5Tests/EmulatorGamesTests.swift, mirroring standalone_retro_test.dart with a fake process starter:
    • the picker and the launch arguments for all three consoles
    • a cancelled start whose emulator comes up late and ignores SIGTERM
    • Stop from Game Settings
    • a failed file panel
    • the switch and Settings › Emulators
    • a shared-folder disc's size
    • console folder navigation
    • disc exclusions: no running, no listing game, no Play Together copy, no Share play or Broadcast rows
  • The same file draws the screens into ANYPS5_TEST_SHOTS (uploaded as launcher-test-screens): home-emulator-required-en, home-retro-folder-en and home-retro-folder-open-en. Settings does not draw offscreen, so its rows are checked by the model tests instead.
  • tools/check_conventions.py passes and the 28 language files are valid JSON.

The scan can list the discs in the games folders, as AnyPS5 Desktop does: cue sheets whose tracks are all beside them, M3U playlists that gather a disc set, PBP files, PS2 ISO images with a BOOT2 line in SYSTEM.CNF, CHD images under a folder named for one console, retro.json game folders and extracted PS3 discs named from PARAM.SFO. A file named outside its own folder is refused. What was read from a disc file is kept by path, size and modification time.

A disc's identifier is its console, its title ID or the size of its files, and its path inside the games folder, so it stays the same when the folder is mounted elsewhere. A loose disc counts only its own files.

Discovery only runs when the caller asks for it (includeRetro). Without it the scan, the identifiers and the order of the dumps are the same as before, which the tests check.

StandaloneEmulator resolves a picked application bundle to its own program, gives each emulator the same arguments as on Windows and Linux, and runs it directly without a shell.
The 13 texts of AnyPS5 Desktop's emulator library with the same keys and wording, in all 28 language files. Turkish says kütüphane for the library.
This ports AnyPS5 Desktop's emulator library to the Mac. Settings › Game library gets an Emulator games switch, which is off by default. Turned on, the library lists the discs and Settings › Emulators appears. There you pick DuckStation, PCSX2 and RPCS3, either as an application or as a program. Show as folders groups a console's games in one tile on Home once its emulator is chosen. Down or Select opens the folder, Up or Back leaves it.

A disc without its emulator reads Emulator required, and its button asks for one. Play starts the emulator's own program on the disc. Cancelling the start frees the slot at once, and an emulator that comes up later is asked to end, then ended after Stop's grace. Stop asks the emulator to end the same way. The disc's Game Settings have its emulator and its log only. Home hides the version and community cards for a disc.

The emulator plays outside the PS5 game's slot, so AnyTogether presence, game sessions, Discord, Play Together, share play, broadcasting and Remote Play never see it. The family's play time still counts it, on this Mac only.
The temporary folder is listed through /private, and a bundle can give its program's path without it. The tests now compare both sides resolved.
The theme's animation does not draw offscreen, and the Settings panel draws nothing there either, so the pictures are the three Home screens over the theme's still.
…'s accent

Matches the Windows and Linux folder tile, so light and custom themes apply.
@utkuhalis
utkuhalis merged commit c108523 into main Nov 13, 2026
18 of 20 checks passed
@utkuhalis
utkuhalis deleted the feat/parity-emulator-games branch November 13, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant