osx: fix the chained-fixups reader so Unity 6.3 games hook - #117
Merged
ManlyMarco merged 1 commit intoSep 24, 2026
Merged
Conversation
Two bugs kept doorstop from hooking any image built with LC_DYLD_CHAINED_FIXUPS. The fixups header was read at (mach_header + dataoff), but dataoff is a file offset and __LINKEDIT is mapped at a different delta. On Ages of Conflict the gap is 1753088 bytes, so the reader landed in the string table and refused the image with "unknown imports format 1836216166", the ASCII "form". With that corrected the reader ran but mapped import index i to __got slot i. The import list order is not the __got layout order: on the same image dlsym is import 546 and lives in slot 556, and 694 of 1024 entries were wrong. Patching a wrong slot corrupts the process, and the game died the moment the hook landed. Chained fixups bind every slot before the image runs, so look the slot up by what it holds instead.
cdobbyn
marked this pull request as ready for review
September 22, 2026 04:13
ManlyMarco
approved these changes
Sep 24, 2026
This was referenced Sep 25, 2026
TomXV
pushed a commit
to TomXV/dragnwash-localization
that referenced
this pull request
Sep 26, 2026
… mod on macOS The Doorstop in BepInEx 5.4.23.5 cannot hook Unity 6.3 games on macOS (NeighTools/UnityDoorstop#108), so the script only stopped with that known issue. It now installs the setup checked by hand in 223n/dragnwash-modframework#3: BepInEx 5.4.23.5 for macOS with the Doorstop from UnityDoorstop's ci pre-release, pinned by SHA-256 to its 4.5.0 build (which has NeighTools/UnityDoorstop#117); the game started as x86_64 under Rosetta, since BepInEx/BepInEx#1402 is not released; and run_bepinex.sh handing Steam's overlay libraries on to the game. That last change was merged upstream as NeighTools/UnityDoorstop#121, so a run.sh from 4.6.0 on is left as it is. The script also installs the ModFramework release mod-install.json names, the mod and the language, sets the Steam launch option, and has a Check mode. The ci pre-release is rebuilt under the same URL. Since it became 4.6.0 the pinned zip answers 404, so installs that have to download it stop with a message pointing to the issue, until the stable 4.6.0 is released, tested and pinned. A game folder that already has the pinned libdoorstop.dylib, or --doorstop-zip with a saved copy, still installs; --bepinex-zip and --framework-zip take local copies too. Uninstall removes only what the script added: a marker file lists the ModFramework parts it put there, and parts switched off in the Mods screen are left off. run_bepinex.sh and libdoorstop.dylib are backed up once, localconfig.vdf before every edit. Run once on the real game on an Apple A18 Pro with macOS 27.2, over a setup made by hand with the ci 4.6.0 Doorstop (whose libdoorstop.dylib is the pinned one, byte for byte), so it downloaded nothing: the translation, the Steam overlay and the F1 window worked. A fresh install has only been run against a sandboxed game folder and Steam tree, under /bin/bash 3.2, so the script stays experimental and out of the release zip. The README, PLAN and ROADMAP say the same in each language.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Doorstop cannot hook any macOS image built with LC_DYLD_CHAINED_FIXUPS. Unity switched to it somewhere between 6000.0 and 6000.3, so newer games fail while older ones work. Ages of Conflict (Unity 6000.3.8f1) never starts BepInEx and writes no log at all; Valheim (6000.0.75) and Patrick's Parabox (2020.3.30) still use LC_DYLD_INFO_ONLY and take the other path, which is fine.
Two bugs:
The fixups header was read at (mach_header + dataoff). dataoff is a file offset and __LINKEDIT is mapped at a different delta. On that image the gap is 1753088 bytes, so the reader landed in the string table and refused with "unknown imports format 1836216166", which is the ASCII "form".
With the pointer corrected the reader ran, but it mapped import index i to __got slot i. The import list order is not the __got layout order. On the same image dlsym is import 546 and lives in slot 556, and 694 of 1024 entries pointed at the wrong slot. Doorstop then wrote its hook over an unrelated function and the game died before printing anything. Chained fixups bind every slot before the image runs, so the slot can be found by what it holds.
Measured on macOS 26.6.2, M4 Pro, arm64, with a debug build of master:
End to end, Ages of Conflict now reaches "Chainloader started" and reports Unity v6000.3.8f1. Valheim still loads its plugins.
Draft for two reasons. Only the arm64 slice is tested, not x86_64. And the 140 entries dladdr cannot name still fall back to the old arithmetic, which is what plthook does today but is wrong; making that refuse instead of guess belongs in its own change.