[Diablo II: Resurrected]: [fails to start]
- Dominant language
- C++
- Stars
- 8k
- Forks
- 351
- Avg merge
- 12h 31m
- Merged PRs (30d)
- 102
Description
### 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
1. ARM64 host, Wine ARM64EC + FEX via Proton
2. Install Diablo II: Resurrected (Steam)
3. Launch from Steam
4. 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](https://github.com/user-attachments/files/31921532/steam-16146081220290150400.log)
Contributor guide
Research direction
No source file or test is named. Start by reproducing the ARM64EC/FEX failure using the attached Steam log and inspect handling of the two aliased NtMapViewOfSection views, writes through the RW view, and execution through the RX view. Done means identifying the emulator fault and demonstrating that Diablo II: Resurrected starts successfully without regressing related self-modifying-code behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, linux
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100