-
-
Notifications
You must be signed in to change notification settings - Fork 21
the_working_solution
Status: SOLVED (2026-07-14). Arknights: Endfield runs on Apple Silicon macOS under a custom-patched CrossOver Wine — past the VMProtect/TenProtect protector, past the ACE anti-cheat, rendering through Apple's D3DMetal, into the login screen and gameplay. This was universally considered impossible (CodeWeavers rated it "Installs, Will Not Run"; the community consensus was that CrossOver + Endfield could not work). It works.
- ✅ VMProtect/TenProtect "tpshell" protector (
EndfieldBase.dll) - ✅ ACE anti-cheat fully passes —
ACE-Base64.dll,ACE-Service64.exe, kernel driverACE-BASE.sys; no more driver-error-13 - ✅ Unity engine (
unityplayer.dll,GameAssembly.dll) loads and initializes - ✅ Graphics via Apple D3DMetal (
using d3dmetal as the graphics backend) - ✅ Login screen renders — the game is playable to the point of account login
⚠️ One non-fatal residual: an ACE thread aborts onntoskrnl.exe.PsGetProcessExitStatus(dw-proton's maintainer noted this abort "isn't really related" to the blocker; it's not in the em-backports). The game reaches login regardless. Trivial stub = clean follow-up.
Engine correction: the runtime modules prove Endfield is Unity IL2CPP, not Unreal Engine 5 as the initial research (06) said.
Both are Rosetta 2 bugs in how it delivers CPU faults for x86 instructions, fixed in dlls/ntdll/unix/signal_x86_64.c (segv_handler, the TRAP_x86_PRIVINFLT case), alongside CrossOver's existing handle_cet_nop/emulate_xgetbv hacks:
VMProtect emits multi-byte 0F 1F NOPs pervasively (100k+ per launch); Rosetta raises an illegal-instruction fault on them and CrossOver's handle_cet_nop had no 0F 1F case. → Decode the NOP length and skip it (it has no side effects). Cleared stage 1 — the protector runs and ACE loads.
ACE's driver reads mov rbx, cr3 (CR3, ring-0 only) as an anti-VM probe. On Linux this is a #GP → Wine reports EXCEPTION_PRIV_INSTRUCTION, which ACE's SEH expects; under Rosetta it arrives as an invalid-opcode fault, so Wine reported EXCEPTION_ILLEGAL_INSTRUCTION and ACE failed with driver-error-13. → On the Rosetta path, call Wine's existing is_privileged_instr() and deliver EXCEPTION_PRIV_INSTRUCTION (matching Linux). Cleared the ACE driver check.
Both are general CrossOver-on-Apple-Silicon bugs (they affect any VMProtect/TenProtect/kernel-anti-cheat title, cf. WineHQ Bug 45083) and are worth upstreaming to CodeWeavers.
Applied to CrossOver 26.3's Wine 11.0 (build/wine-src):
| File | Origin | Purpose |
|---|---|---|
dlls/ntdll/unix/signal_x86_64.c |
NEW — our fixes (patches/stage1-macos/) |
0F 1F NOP-skip + privileged-instruction (mov cr3) → PRIV_INSTRUCTION. The two Rosetta fixes. |
dlls/kernel32/module.c |
dw-proton | int3-stub KiUser*Dispatcher spoof (fired 2×). |
dlls/ntdll/unix/sync.c |
dw-proton |
NtDelayExecution via QPC (timing). |
dlls/ntoskrnl.exe/{ntoskrnl.c,.spec,_private.h,sync.c} |
dw-proton | 17 ntoskrnl.exe em-backports ACE's driver calls. |
dlls/win32u/vulkan.c |
build fix |
SONAME_LIBVULKAN fallback (minimal build compiles). |
Patches: patches/stage1-macos/ (our Rosetta fixes) + patches/stage2-dwproton/ (dw-proton set). The signal_x86_64.c patch also contains an optional CWC-ILLEGAL-INSTR debug ERR line — harmless (fires 0×), remove for production.
The custom Wine is built minimal (no graphics libs), so we surgically swap only the patched modules into a copy of CrossOver.app and let CrossOver provide D3DMetal/Metal + fonts/TLS. This is automated in scripts/swap-into-crossover.sh:
-
cp -a /Applications/CrossOver.app build/CrossOver_patched.app(must be 26.3, matching the build). - Copy 3 patched modules into
Contents/SharedSupport/CrossOver/lib/wine/:-
x86_64-unix/ntdll.so(both Rosetta fixes + NtDelayExecution) -
x86_64-windows/kernel32.dll(int3 hack) -
x86_64-windows/ntoskrnl.exe(em-backports)
-
-
codesign --force --sign -each swapped file; removeContents/_CodeSignature+Contents/CodeResources;xattr -drs com.apple.quarantine. - Run the game through
build/CrossOver_patched.app(its wrapper sets up D3DMetal) against the existing bottle → login screen.
scripts/build-wine.sh (deps→fetch→configure→build under arch -x86_64), apply all patches (git apply), rebuild, then scripts/swap-into-crossover.sh. Full build details: 04.
After the anti-cheat is solved, the remaining day-to-day gotcha is the graphics renderer, and game updates make it recur:
-
Endfield must run in DirectX 11 mode. It defaults to Vulkan/DX12, and under CrossOver 26.3 both fail → white/blank screen: DX12 →
vkd3derrorsCannot load DXIL conversion library(its DXIL/SM6 shaders never compile); native Vulkan → MoltenVK also fails. (Update 2026-09: Vulkan does render with a newer MoltenVK — see graphics-performance.md.) DX11 uses the mature D3DMetal/DXMT path and renders correctly. Set the renderer to DirectX 11 in the launcher's / in-game graphics settings (persists in the game's prefs /Software\\Gryphline\\Endfield). - A game update can reset the renderer back to Vulkan/DX12 → white screen returns. Re-select DirectX 11. (This is exactly what happened on 2026-07-15.)
-
Setting the CrossOver backend to D3DMetal (
CX_ACTIVE_GRAPHICS_BACKEND=d3dmetal) does NOT reroute the game's DX12 offvkd3d— verified. It helps DX11 go to D3DMetal, but it will not save you from the game choosing DX12. The game-side DX11 setting is the fix. -
Do NOT "fix" the white screen by overwriting
lib/wine/x86_64-windows/{d3d11,d3d12,dxgi}.dllwith theapple_gptk(D3DMetal) copies. Those are only meant to load through CrossOver's own D3DMetal backend path; dropping them in as the defaults makesunityplayer.dllfail to initialize (Windows error 1114, "missing or corrupt"). If you did this, restore the defaults from a stockCrossOver.app(lib/wine/x86_64-windows/). -
Launcher vs. direct launch: the Gryphline launcher sets up the game's working directory (and session); launching
Endfield.exedirectly can hit the same 1114 onunityplayer.dll. Prefer the launcher.
After a clean rebuild of the patch app, the game (set to DX11) failed with d3d11: failed to create device and context (80004005) and fell back to Vulkan, which doesn't render — easily misread as "the Vulkan translation broke." The Vulkan fallback was the symptom; the DX11 device-create failure was the disease. Diagnosis chain, for posterity:
-
WINEDEBUG=+processshowederr:winediag:wined3d_adapter_create Using the Vulkan renderer for d3d10/11 applicationsin the game's process — wined3d, not D3DMetal, was serving d3d11, and its experimental Vulkan renderer can't provide the feature levels Unity asks for →80004005. - CrossOver applies the bottle's
CX_GRAPHICS_BACKEND=d3dmetalper process vialib/wine/x86_64-unix/cxcompatdb.so, which stockntdll.sodlopens at process start (CW Hack 24067,start_main_threadindlls/ntdll/unix/loader.c). - The log's smoking gun:
warn:module:start_main_thread error loading cxcompatdb.so: … Library not loaded: @rpath/libgnutls.30.dylib. dyld resolves a dlopen'd library's@rpathdependencies through the calling image'sLC_RPATH— the caller is ntdll.so. CodeWeavers' ntdll.so carries@loader_path/../../../lib64(wherelibgnutls.30.dyliblives); our minimal Wine build's ntdll.so only had@loader_path/. So cxcompatdb silently failed to load in every process, no backend was ever applied, and d3d11 fell through to wined3d. - Fix (one command, now automated in
swap-into-crossover.sh):install_name_tool -add_rpath "@loader_path/../../../lib64" …/x86_64-unix/ntdll.sothen ad-hoc re-codesign. Verified: every process logsset_graphics_backend using d3dmetal as the graphics backend, DX11 device creation succeeds, the game renders.
Two adjacent findings from the same investigation:
-
A direct
Endfield.exelaunch does not inherit the launcher's DirectX-11 setting — the launcher passes it at spawn. Without-force-d3d11Unity defaults to Vulkan.launch-endfield.shnow appends-force-d3d11(override withGFXARGS). - The base
/Applications/CrossOver.apphad GPTK4 (D3DMetal 4.0b1) manually installed during the working period, and is now back to stock 3.0; the rebuilt patch app therefore had 3.0 until GPTK4 was reinstalled from~/Downloads/GPTK_4/redist/lib/external. The 80004005 was not a 3.0-vs-4.0b1 issue (it was the rpath), but the proven-good configuration is 4.0b1, which is what the patch app now carries. (D3DMetal reports an NVIDIA-spoofed adapter —vendorID=10de— so the DLSS/streamline path can bind to MetalFX; this is expected.) - CodeWeavers' compat database (
~/Library/Application Support/CrossOver/compatdb-26.dat, auto-downloaded) was investigated and is innocent — no need to touch it.
- Add
PsGetProcessExitStatusas an em-backport stub to silence the one residual ACE-thread abort. - Play-test past login (combat/rendering stability, the QPC-timing × D3DMetal interaction, DLSS fallback since it's NVIDIA-only).
- Upstream the two Rosetta signal fixes to CodeWeavers (with Bug 45083 as reference) — they fix a whole class of protected games on Apple Silicon.
- A distributable: bundle the patched modules as a CXPatcher-style overlay so others can apply it to their own CrossOver 26.3.
- installation.md — full build + deploy + run instructions
- graphics-performance.md — backend selection and the optional GPTK4 upgrade
- troubleshooting.md — common failures and fixes
- 10-milestone-1-results.md — the captured failure signature that motivated this
- 12-stage1-protector-fault.md — the stage-1 investigation record
- 11-linux-vs-macos-comparison.md — why the Linux fix wasn't enough on macOS
- 02-dwproton-ace-patches.md — the ported anti-cheat patches
- 04-building-crossover-wine.md — building the patched Wine
- 05-swapping-into-crossover.md — the module swap + code-signing