Change a setting in the per-game settings screen and it does not take. The widget keeps the new value, the file on disk keeps it, and the emulator runs with something else — for every game the database has an entry for, which is most of the ones anyone plays. The layered read is not the problem; it already puts the per-game file above the global one. The inversion is that the database writes into EmuConfig afterwards, from ApplyGameFixes, with no idea where any value came from. So the real order is global < per-game < database, and the only ways out are two switches that are all or nothing: EnableGameFixes for the CPU side, Manual Hardware Fixes for graphics. Either one rescues the setting you wanted by throwing away every other fix the game had, which usually just trades one glitch for a different one. So tell the database which settings are not its to write. A key sitting in a per-game file is a deliberate act: every settings screen represents "use the global setting" by deleting the key, not by writing the global value. That makes presence the record we need, with nothing new to store and no frontend to change, and it works for files already on disk rather than only for edits made from here on. PerGameOverrides reads one per-game file — the game layer alone, since the layered stack cannot tell a per-game choice from a global one and only the per-game one wins — and reports what it claims. Both apply functions take it and leave those settings alone, down the road they already had for the global switches, so each one still gets named in the log and now also on screen. This generalises the pin that already existed for graphics hacks and had no writer outside iOS. That mask stays, because MaskUserHacks and any INI already carrying one speak it, and the derived bits merge into it. But the claim itself is keyed by hardware-fix id rather than by that mask: there are more fixes than its 32 bits can name, its width is a persisted format, and the settings players actually change — mipmapping, trilinear, deinterlacing, texture preloading, blend level, download mode — were never user hacks and so had no bit at all. That is also why the pin test moves out from under isUserHackHWFix: nested there, none of them could be claimed. The database keeps the last word where it should. The three renderer routine selectors have no setting and no UI, so nothing can claim them. The BIOS path is untouched — it forces instant DMA on for a hardware reason and strips hacks deliberately, and its own comment already says claims get no say there. The iOS bridge derived the same thing from its own copy of the key table; it now calls the shared one, so the two cannot drift. Android's live GS apply re-derives the claims after its reload, or masking would strip a hack the player set. Drive-by: the division rounding mode logged eeRoundMode's value under its own name, in both the applied and the skipped message.
ARMSX2 — Native ARM64 JIT Fork of PCSX2
ARMSX2 is a free and open-source PlayStation 2 (PS2) emulator based on PCSX2. Its purpose is to emulate the PS2's hardware, using a combination of MIPS CPU Interpreters, Recompilers and a Virtual Machine which manages hardware states and PS2 system memory. This allows you to play PS2 games on your phone, PC, or gaming handheld, with many additional features and benefits.
Thank You
The ARMSX2 team is eternally indebted to the PCSX2 project it is based on. We are so fortunate to build on their 20 years of hardcore development.
About This Fork
The upstream PCSX2 project ships an ARM64 interpreter build for ARM, but its high-performance JIT recompilers (EE, IOP, VU0, VU1, and vtlb fast memory) are x86-64 only.
This fork exists to close that gap. The goal is to preserve the correctness features of 20 years of PCSX2 development, while generating the fastest native ARM performance possible.
Current status:
- ✅ EE (Emotion Engine) recompiler — integer, float, MMI, COP0/COP1/COP2, branches, load/store
- ✅ IOP (I/O Processor / R3000A) recompiler — full integer, load/store, branches, coprocessors
- ✅ VU (Vector Unit) recompiler — microVU skeleton + Upper FMAC vector ISA complete; Lower ISA and runtime complete
- ✅ vtlb fast memory
- ✅ Native ARM64 binary builds and boots the PS2 BIOS
- ✅ 2D games are already playable
- ✅ 3D games run
Why LLMs / AI Were Used
A word on methodology:
The x86-64 JIT code in upstream ARMSX2 is already proven correct — it has run thousands of PS2 titles for years. The challenge in this port is not emulator design or JIT theory; it is mechanical translation of a large, well-understood x86-64 assembly codebase into equivalent ARM64 assembly (via VIXL) while preserving the exact same register-allocation contracts, block lifecycle, and recompiler semantics.
Large language models (LLMs) were used as an accelerant for this translation work — pattern-matching x86 JIT boilerplate to ARM64 equivalents, scaffolding emit routines, and keeping the porting velocity high. The JIT logic (block compiler, dispatcher, analysis passes, flag pipelines, clamping rules, Tri-Ace hacks, etc.) is taken directly from the upstream x86 implementation and validated against it. Nothing was hallucinated from scratch.
In other words: the hard engineering was done by the PCSX2 team over two decades. The hard typing — translating ~50k lines of x86 emitter code into ARM64 — is what AI helped compress.
System Requirements
ARMSX2 targets ARM64 across desktop (macOS, Windows, Linux) and mobile (Android, iOS/iPadOS), all from the single shared core. Our setup documentation page contains additional details on software and hardware requirements.
Please note that a BIOS dump from a legitimately-owned PS2 console is required to use the emulator. For more information, visit this page.
Building
Check out our github actions for the latest build recipe
