repost of original issue found here
from @acrimon:
Ryubing crashes on startup when launching Pokemon SV with the very popular Compass mod.
Versions:
- Ryubing: 1.3.3
- Firmware: Tested 20.0.1 and 20.5.0
- Compass: 2.1.1.1
I've attempted to extensively test and debug Ryubing here, but I can't quite figure out what is going wrong, so if someone has other ideas that'd be good.
This issue does not occur on x86_64 Windows or with the hypervisor setting on, but using hypervisor causes random game freezes so it's unusable.
On launch, the a break syscall is encountered and the game terminates, if Ryubing is patched to ignore this, 4 zero bytes are located after it, which causes an UndefinedInstructionException. If Ryubing is patched to ignore these, valid ARM code with reasonable disassembly is found after them, so this is happening in the middle of a JIT compiled function.
from @babib3l3l:
Hey! So I’ve been taking a look at this on and off with a friend who’s much much better than me at this kinda stuff- and we might have found a solution.
I do not think the BRK itself is the real bug. My best guess is that this is a non-hypervisor Apple Silicon JIT invalidation issue, most likely in the LightningJit path, and Compass is just the mod that reliably triggers it because it includes exeFS/runtime code changes.
On macOS ARM64 with hypervisor disabled, Ryujinx uses a different CPU backend than the one used on x86_64 Windows, and hypervisor mode bypasses that JIT path entirely. That lines up very well with the repro matrix here, where
- macOS + no hypervisor: crashes
- macOS + hypervisor: does not crash
- x86_64 Windows: does not crash
Compass 2.1.1.1 also includes exeFS changes, so it is the kind of mod that can involve startup-time code patching, hook installation, trampolines, or other self-modifying/runtime-generated code behavoir.
The BRK, followed by 0x00000000, followed by valid-looking ARM code does not look like random corruption to me. It looks much more like the emulator is landing on placeholder or pre-patch bytes, or on a hook/trampoline region that should no longer be executed once the final patching/cache maintenance has happened.
I think the guest is patching code correctly, but the non-hypervisor ARM64 JIT is continuing to execute a stale translation of that region instead of re-translating it after the guest performs cache maintenance.
In other words:
- Compass or one of its runtime components installs or finalizes code very early.
- The game does the usual cache maintenance sequence for modified code.
- Hypervisor mode is fine because it executes guest ARM64 directly.
- Windows x86_64 is fine because it uses a different JIT path.
LightningJit on Apple Silicon likely keeps an old translation alive, so execution still sees the old BRK/padding bytes.
That would also explain why “ignoring the break” just advances into four zero bytes and then dies again: you are not fixing the underlying issue, you are just forcing execution further into stale or placeholder code.
To fix this, I would look at LightningJit’s handling of guest cache maintenance and executable-code patching, not at special-casing this particular BRK.
The most likely fixes we could think of :
- Invalidate translated blocks when the guest does instruction-cache invalidation for modified code.
- Make sure runtime code patching / code-memory remapping paths also invalidate any existing JIT translations for the affected guest range.
- Re-translate the affected block after invalidation rather than continuing with the stale cached one.
from @babib3l3l:
hey @acri
I might have a fix out, could you test it out?
https://legacy.git.ryujinx.app/babib3l/ryujinx/-/tree/lightningjit-ic-ivau-invalidation?ref_type=heads
from @acrimon:
from @babib3l3l:
from @babib3l3l: