Arkham City never finished compiling, and neither did LEGO Batman 2. The log said "LLVM crash recovery invoked" 240 times and then killed main_thread, which looks like a codegen bug and is not one. What actually happened: utils::memory_commit failed with ENOMEM inside the disposable LLVM worker. That thread dying is how run_recoverable_llvm reports any failure, so an out-of-memory device was indistinguishable from bad codegen. It cost a long detour through max_map_count, disk space and overcommit before the errno in the fatal gave it away, so the JIT's allocator now uses a checked commit and throws a plain "Out of memory" instead. The fatal part was the symbol resolvers. ppu_initialize ensure()d that every group's __resolve_symbols was present, but that function lives in the compiled output: when every module in a group fails, it is simply absent. The ensure turned a partial compile into a dead main_thread, which discarded the 170 modules that HAD compiled and surfaced as a boot that never ends. That contradicts the design either side of it -- a module that fails to load is deliberately not fatal, because a guest function with no compiled code keeps its dispatcher entry and is interpreted. A missing resolver is now the same: report it, skip it, let that group interpret. Losing one group's speed beats losing the boot. Also tell the user. Out of memory is the only compile failure they can act on, and the useful action is not obvious: compiled modules are already in the cache, so starting the game again resumes rather than restarting the work. One message after the workers join, not one per module -- once memory is short every remaining module fails identically, and two hundred popups would be worse than none. Lowering Max LLVM Compile Threads also avoids it, and is deliberately not suggested in the message: compile time is already the common complaint, and halving the workers to dodge a case that is now survivable is a bad trade.
ARMSX3
Uses the latest RPCS3 upstream code (the recent ARM64 improvements included).
Building
arm64-v8a and armv8.2 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