I mentioned game freezing non stop on the Discord, I found the fix.
Freezes were happening constantly even on character selection screen but only when the mouse cursor was visible and moving. Game was unplayable with those freezes and now it's buttery smooth.
What i changed to fix it:
Fix for the 50–400 ms freezes while moving the mouse cursor (Game.dll 2711.0907.1018.4770)
Symptom: the client freezes for 50–400 ms while the cursor moves freely over the game window. It doesn't happen while holding a mouse button to turn the camera. Measured: 25 freezes in 50 s, worst 409 ms. During a freeze the main thread is stuck in the message loop inside NtUserSetCursor and draws no frames.
Cause: the WM_SETCURSOR handler in Game.dll (function at Game+2AD6B0, branch msg == 0x20 && LOWORD(lParam) == HTCLIENT) does this on every mouse move:
Game+2AD700: push 0 / call SetCursor, i.e. SetCursor(NULL).
Game+2AD72D: renderer->ShowCursor(...) (vtable +0x410). This goes through XRenderD3D9 to IDirect3DDevice9::ShowCursor(TRUE), which calls SetCursor(hHwCursor).
d3d9's ShowCursor skips the call when GetCursor() already returns its cursor. Step 1 defeats that, so every WM_SETCURSOR makes two real cursor changes: NULL, then the cursor. On modern Windows each real cursor change costs about 1 ms (measured: median 1.08 ms per NtUserSetCursor). Mouse moves arrive faster than the ~2 ms it takes to handle each one, so the message loop never drains and no frame is drawn until the mouse stops.
Measured: 6 121 SetCursor calls in 30 s, strictly alternating NULL / 01C70A19.
Fix: NOP the 8 bytes at Game+2AD700 (the SetCursor(NULL) call).
Original code: 6A 00 FF 15 <SetCursor IAT>. In this install it was already redirected by version_nostalgia_original.dll: 6A 00 E8 xx xx xx xx 90.
Patched: 90 90 90 90 90 90 90 90.
Guard bytes. Before: 66 83 7D 10 01 0F 85 A6 02 00 00 (cmp word [ebp+10h],1 / jne). After: 8B 4D 14 C7 01 (mov ecx,[ebp+14h] / mov dword [ecx],...).
Hiding still works, because d3d9's ShowCursor(FALSE) calls SetCursor(NULL) itself. The SetCursor hook in version_nostalgia_original.dll (the windowed-mode ClipCursor logic) still sees those calls.
Result: 19 SetCursor calls in 30 s instead of 6 121, and 0 frames over 40 ms in 41 s of play (the worst frame went from 409 ms to about 6 ms).
I'm applying it with a small dinput8.dll proxy in bin32. It waits for Game.dll to unpack, checks the guard bytes, pauses the other threads and writes the NOPs. It could easily be folded into loadfix.dll instead.
Freezes were happening constantly even on character selection screen but only when the mouse cursor was visible and moving. Game was unplayable with those freezes and now it's buttery smooth.
What i changed to fix it:
Fix for the 50–400 ms freezes while moving the mouse cursor (Game.dll 2711.0907.1018.4770)
Symptom: the client freezes for 50–400 ms while the cursor moves freely over the game window. It doesn't happen while holding a mouse button to turn the camera. Measured: 25 freezes in 50 s, worst 409 ms. During a freeze the main thread is stuck in the message loop inside NtUserSetCursor and draws no frames.
Cause: the WM_SETCURSOR handler in Game.dll (function at Game+2AD6B0, branch msg == 0x20 && LOWORD(lParam) == HTCLIENT) does this on every mouse move:
Game+2AD700: push 0 / call SetCursor, i.e. SetCursor(NULL).
Game+2AD72D: renderer->ShowCursor(...) (vtable +0x410). This goes through XRenderD3D9 to IDirect3DDevice9::ShowCursor(TRUE), which calls SetCursor(hHwCursor).
d3d9's ShowCursor skips the call when GetCursor() already returns its cursor. Step 1 defeats that, so every WM_SETCURSOR makes two real cursor changes: NULL, then the cursor. On modern Windows each real cursor change costs about 1 ms (measured: median 1.08 ms per NtUserSetCursor). Mouse moves arrive faster than the ~2 ms it takes to handle each one, so the message loop never drains and no frame is drawn until the mouse stops.
Measured: 6 121 SetCursor calls in 30 s, strictly alternating NULL / 01C70A19.
Fix: NOP the 8 bytes at Game+2AD700 (the SetCursor(NULL) call).
Original code: 6A 00 FF 15 <SetCursor IAT>. In this install it was already redirected by version_nostalgia_original.dll: 6A 00 E8 xx xx xx xx 90.
Patched: 90 90 90 90 90 90 90 90.
Guard bytes. Before: 66 83 7D 10 01 0F 85 A6 02 00 00 (cmp word [ebp+10h],1 / jne). After: 8B 4D 14 C7 01 (mov ecx,[ebp+14h] / mov dword [ecx],...).
Hiding still works, because d3d9's ShowCursor(FALSE) calls SetCursor(NULL) itself. The SetCursor hook in version_nostalgia_original.dll (the windowed-mode ClipCursor logic) still sees those calls.
Result: 19 SetCursor calls in 30 s instead of 6 121, and 0 frames over 40 ms in 41 s of play (the worst frame went from 409 ms to about 6 ms).
I'm applying it with a small dinput8.dll proxy in bin32. It waits for Game.dll to unpack, checks the guard bytes, pauses the other threads and writes the NOPs. It could easily be folded into loadfix.dll instead.