This post create with Claude support, I hope understandings, I'm not a professional.
[ARM64EC] Diablo II: Resurrected fails to start — code executed through RX alias of a section written via a separate RW mapping
Diablo II: Resurrected (D2R.exe) fails to start under Wine ARM64EC + FEX (libarm64ecfex.dll). The game's anti-tamper wrapper D2R_loader.dll crashes during DLL initialization with EXCEPTION_ACCESS_VIOLATION (execute fault) at an address that Wine has mapped as executable.
The protector maps a single section object twice into the same process — once with SECTION_MAP_EXECUTE and once with SECTION_MAP_WRITE — writes generated code through the RW view, and jumps into the RX view. Execution at the RX alias faults even though page permissions are correct from Wine's point of view.
The process never reaches a window; it aborts roughly one second after launch.
Environment
|
|
| Kernel |
Linux 7.2.0 #1 SMP PREEMPT Wed Aug 26 15:03:37 CEST 2026 aarch64 |
| Proton |
cachyos-11.0-20260703-slr (also reproduced on GE-Proton11-5) |
| Emulator |
libarm64ecfex.dll (bundled with Proton) |
| Game |
Diablo II: Resurrected, Steam build, launched from Steam |
| Proton options |
gamedrive, forcelgadd, pipewire |
Symptom
6657.665:0140:0144:warn:seh:dispatch_exception backtrace: --- Exception 0xc0000005.
6657.665:0140:0144:trace:seh:dispatch_exception code=c0000005 (EXCEPTION_ACCESS_VIOLATION) flags=0 addr=0000000001243B69
6657.665:0140:0144:trace:seh:dispatch_exception info[0]=0000000000000008
6657.665:0140:0144:trace:seh:dispatch_exception info[1]=0000000001243B69
6657.665:0140:0144:trace:seh:dispatch_exception rip=0000000001243b69 rsp=000000000041eb58
6657.665:0140:0144:warn:seh:virtual_unwind backtrace: 0000000001243B69: unknown module.
6657.665:0140:0144:warn:seh:virtual_unwind backtrace: 00006FFFFA8663CB: L"D2R_loader.dll" + 0000000001E163CB.
6657.665:0140:0144:warn:seh:virtual_unwind backtrace: 00006FFFFA866312: L"D2R_loader.dll" + 0000000001E16312.
6657.665:0140:0144:warn:seh:virtual_unwind backtrace: 00006FFFFA85BE8C: L"D2R_loader.dll" + 0000000001E0BE8C.
6657.665:0140:0144:warn:seh:virtual_unwind backtrace: 00006FFFFA838675: L"D2R_loader.dll" + 0000000001DE8675.
6657.665:0140:0144:warn:seh:virtual_unwind backtrace: 00006FFFFA833F35: L"D2R_loader.dll" + 0000000001DE3F35.
6657.665:0140:0144:warn:seh:virtual_unwind backtrace: 00006FFFFA836EF2: L"D2R_loader.dll" + 0000000001DE6EF2.
6657.665:0140:0144:warn:seh:virtual_unwind backtrace: 00006FFFFA837127: L"D2R_loader.dll" + 0000000001DE7127.
6657.665:0140:0144:err:module:loader_init "D2R_loader.dll" failed to initialize, aborting
6657.665:0140:0144:err:module:loader_init Initializing dlls for L"D:\Games\Diablo II Resurrected\D2R.exe" failed, status c0000005
info[0] = 8 indicates an execute fault rather than a read/write fault. The faulting address is reported as unknown module because it lies in anonymous memory, not in any loaded image.
The faulting address varies slightly between runs (0x1243A06, 0x1243A60, 0x1243A6B, 0x1243B69) — the protector lays out its generated code at a slightly different offset each time. The D2R_loader.dll return addresses are byte-identical across every run and across both Proton builds.
Root cause
A WINEDEBUG=+virtual trace shows what happens 145 ms before the crash. The protector creates one section (handle 0x80) and maps it twice into the same process with different access:
5509.489:0140:0144:trace:virtual:NtMapViewOfSection handle=0x80 process=0xffffffffffffffff addr=(nil) off=0 size=0x100000 alloc_type=0x0 access=0x20
5509.493:0140:0144:trace:virtual:virtual_map_section handle=0x80 size=100000 offset=0
5509.493:0140:0144:trace:virtual:dump_view View: 0x1240000 - 0x133ffff c-r-x (anonymous)
5509.493:0140:0144:trace:virtual:dump_view 0x1240000 - 0x133ffff c-r-x
5509.493:0140:0144:trace:virtual:virtual_map_section status 0.
5509.493:0140:0144:trace:virtual:NtMapViewOfSection handle=0x80 process=0xffffffffffffffff addr=(nil) off=0 size=0x100000 alloc_type=0x0 access=0x4
5509.494:0140:0144:trace:virtual:virtual_map_section handle=0x80 size=100000 offset=0
5509.494:0140:0144:trace:virtual:dump_view View: 0x1340000 - 0x143ffff c-rw- (anonymous)
5509.494:0140:0144:trace:virtual:dump_view 0x1340000 - 0x143ffff c-rw-
5509.494:0140:0144:trace:virtual:virtual_map_section status 0.
So:
0x1240000 - 0x133ffff — SECTION_MAP_EXECUTE (access=0x20), mapped r-x
0x1340000 - 0x143ffff — SECTION_MAP_WRITE (access=0x4), mapped rw-
Both views back the same section. The protector unpacks code through the RW view and executes it through the RX view — a standard W^X bypass that never requests an RWX page.
The faulting rip = 0x1243b69 lies inside the RX view, at offset ~0x3B69 from its base. The region is never unmapped, never freed, and no NtProtectVirtualMemory call is applied to it — I grepped the full trace for the range and these two NtMapViewOfSection calls are the only operations touching it.
Wine's page permissions are therefore correct. Execution at 0x1243b69 is legitimate as far as Wine is concerned, which suggests the fault originates in the emulator rather than from a real permission violation.
My working hypothesis: FEX tracks self-modifying code against the mapping it executes from. The writes land at 0x1340000, while the translated block is keyed to 0x1240000, so the modification is not observed and stale or zero-filled bytes are translated.
One more possibly relevant detail: NtAreMappedFilesTheSame is called 3777 times during startup. The protector actively verifies that the two views refer to the same underlying object, so this aliasing is deliberate and load-bearing, not incidental.
Reproduction
- ARM64 host, Wine ARM64EC + FEX via Proton
- Install Diablo II: Resurrected (Steam)
- Launch from Steam
- Process aborts before any window appears
Reproduced on every attempt across two Proton builds.
What I tried
GE-Proton11-5 and cachyos-11.0-20260703-slr — identical failure, identical D2R_loader.dll offsets
- Reinstalling the same Proton build to a different path — no change
FEX_SMCCHECKS=Full FEX_TSOENABLED=1 in the Steam launch options — no observable change; I could not confirm from the log whether these variables are read at all by the libarm64ecfex.dll shipped inside Proton. If there is a supported way to configure the in-Proton FEX build, or to confirm which settings are active, I am happy to re-test.
Log:
steam-16146081220290150400.log
This post create with Claude support, I hope understandings, I'm not a professional.
[ARM64EC] Diablo II: Resurrected fails to start — code executed through RX alias of a section written via a separate RW mappingDiablo II: Resurrected (
D2R.exe) fails to start under Wine ARM64EC + FEX (libarm64ecfex.dll). The game's anti-tamper wrapperD2R_loader.dllcrashes during DLL initialization withEXCEPTION_ACCESS_VIOLATION(execute fault) at an address that Wine has mapped as executable.The protector maps a single section object twice into the same process — once with
SECTION_MAP_EXECUTEand once withSECTION_MAP_WRITE— writes generated code through the RW view, and jumps into the RX view. Execution at the RX alias faults even though page permissions are correct from Wine's point of view.The process never reaches a window; it aborts roughly one second after launch.
Environment
Linux 7.2.0 #1 SMP PREEMPT Wed Aug 26 15:03:37 CEST 2026 aarch64cachyos-11.0-20260703-slr(also reproduced onGE-Proton11-5)libarm64ecfex.dll(bundled with Proton)gamedrive,forcelgadd,pipewireSymptom
info[0] = 8indicates an execute fault rather than a read/write fault. The faulting address is reported asunknown modulebecause it lies in anonymous memory, not in any loaded image.The faulting address varies slightly between runs (
0x1243A06,0x1243A60,0x1243A6B,0x1243B69) — the protector lays out its generated code at a slightly different offset each time. TheD2R_loader.dllreturn addresses are byte-identical across every run and across both Proton builds.Root cause
A
WINEDEBUG=+virtualtrace shows what happens 145 ms before the crash. The protector creates one section (handle0x80) and maps it twice into the same process with different access:So:
0x1240000 - 0x133ffff—SECTION_MAP_EXECUTE(access=0x20), mappedr-x0x1340000 - 0x143ffff—SECTION_MAP_WRITE(access=0x4), mappedrw-Both views back the same section. The protector unpacks code through the RW view and executes it through the RX view — a standard W^X bypass that never requests an RWX page.
The faulting
rip = 0x1243b69lies inside the RX view, at offset ~0x3B69 from its base. The region is never unmapped, never freed, and noNtProtectVirtualMemorycall is applied to it — I grepped the full trace for the range and these twoNtMapViewOfSectioncalls are the only operations touching it.Wine's page permissions are therefore correct. Execution at
0x1243b69is legitimate as far as Wine is concerned, which suggests the fault originates in the emulator rather than from a real permission violation.My working hypothesis: FEX tracks self-modifying code against the mapping it executes from. The writes land at
0x1340000, while the translated block is keyed to0x1240000, so the modification is not observed and stale or zero-filled bytes are translated.One more possibly relevant detail:
NtAreMappedFilesTheSameis called 3777 times during startup. The protector actively verifies that the two views refer to the same underlying object, so this aliasing is deliberate and load-bearing, not incidental.Reproduction
Reproduced on every attempt across two Proton builds.
What I tried
GE-Proton11-5andcachyos-11.0-20260703-slr— identical failure, identicalD2R_loader.dlloffsetsFEX_SMCCHECKS=Full FEX_TSOENABLED=1in the Steam launch options — no observable change; I could not confirm from the log whether these variables are read at all by thelibarm64ecfex.dllshipped inside Proton. If there is a supported way to configure the in-Proton FEX build, or to confirm which settings are active, I am happy to re-test.Log:
steam-16146081220290150400.log