Pause reached the core for the first time. Rpcs3Bridge.pause() set a bool and returned, on the belief that RPCS3 has no explicit pause entry point -- Emu.Pause() exists and _rpcsx_surfaceEvent has always called it on surface loss, which is why backgrounding the app was the only thing that paused. Exported as _rpcsx_pause through all four layers; resume already reached the core, so the pair was asymmetric. Restart no longer crashes: setCustomDriver dlclose'd the previous driver handle, and applyRendererPrefs re-applies the driver on every start, so restart unloaded the library VMA had resolved vkGetPhysicalDeviceMemoryProperties2 out of. ~VKGSRender then freed its heaps and UpdateVulkanBudget called into an unmapped mapping. An ICD cannot be unloaded while anything resolved from it is reachable, so it is no longer closed. Restart no longer returns to the library either: shutdown() set stopRequested, called kill() and returned with the VM still live, so the run loop's finally started the replacement and the in-flight teardown killed it -- two BootGame calls, then Unloading ISO, by which point the restart flag was spent. shutdown() now waits (bounded) for the core to report Stopped, and the restart is queued on vmStopControl behind it. FPS cap applies at every value. ConfigStore recorded a persistent core override of Video@@Frame limit=60 and Settings rewrote it on every push, both from a migration escaping a stored 120 -- but that node is the cap control, and overrides replay last, so presets were pinned at 60 while 20 and 45 worked through Second Frame Limit. The Vblank Rate force stays, since Frame limit Auto resolves to it. Stale overrides are cleared once. 90 and 120 dropped from the row: the min() in the pacer discards them. Cover art for PKG installs: the library grid's fallback chain stopped one leg short of the extracted ICON0.PNG while the in-game menu's did not. Both now share one chain, so they cannot diverge again. has()/discIconFile require bytes rather than existence, and the staging rename is checked instead of discarded. Licences are grouped per game and collapsed instead of a flat list of content ids. Trophies: a library-wide browser and an in-game tab for the running title, reading TROPCONF.SFM and TROPUSR.DAT directly -- no account, no network. The in-game set is identified from the core's own current_trophy_name (try_get, since get<> would construct it outside emulation and hand back an empty name), falling back to TROPDIR on disk because a game registers its context lazily. Note the entry stride there is 16 + entries_size, not entries_size. Also: renderer.upscale.label was defined twice, so Internal Resolution was dead.
ARMSX3
Uses the latest RPCS3 upstream code (the recent ARM64 improvements included).
Building
Only arm64-v8a is supported. You need the Android SDK with NDK r27 or newer, CMake 3.30 or newer, and a JDK 17. Android Studio ships all of these.
Clone with submodules, then fetch the two third party checkouts that are not submodules:
git clone --recursive https://github.com/ARMSX2/ARMSX3.git
cd ARMSX3
git clone https://github.com/SnowflakePowered/librashader 3rdparty/librashader
git clone https://github.com/bylaws/libadrenotools android/armsx3-ui/app/src/main/cpp/libadrenotools
Build the core. This is the long part and produces an unstripped library of around 1.3 GB:
export ANDROID_HOME=$HOME/Library/Android/sdk
cmake -B build-android -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_HOME/ndk/<version>/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-31 \
-DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build-android --target rpcsx-android -j8
Strip it and put it where the app expects it:
llvm-strip --strip-unneeded build-android/android/libarmsx3-core.so
cp build-android/android/libarmsx3-core.so \
android/armsx3-ui/app/src/main/jniLibs/arm64-v8a/
Then build the app:
cd android/armsx3-ui
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
./gradlew :app:assembleRelease
The apk lands in app/build/outputs/apk/release/.
Note that the core library has to be rebuilt and copied again whenever anything under rpcs3/ or android/src/ changes. Gradle does not build it for you.
The Discord Social SDK is proprietary and is not redistributed here. Get it from Discord's developer portal and drop it in app/libs/ and app/src/main/cpp/discord_sdk/ if you want that feature. The build skips it otherwise.
Running it needs PS3 firmware, which is not included. License
GPL-2.0-only, the same as RPCS3. See LICENSE. Some files may be licensed differently, check the file headers.
Based on RPCS3, https://github.com/RPCS3/rpcs3