Fixing GTA V Legacy's Mysterious 2-Minute Silent Crash on Windows 11 24H2
How a red herring with GTA V Enhanced led to dissecting Windows 11 24H2 minidumps, uncovering a 138-second periodic NULL-pointer bug in GTA5.exe (build 1.0.3095.0), and writing a 120-line C++ ASI plugin to fix it.

TL;DR
- Symptom: GTA V Legacy
1.0.3095.0on Windows 11 24H2 (Build 26100) exits to the desktop with no error, roughly 1.5 to 3 minutes after launch.- Cause: a periodic check inside
GTA5.exewalksntdll.dll's export table and dereferences aNULLname pointer. 24H2'sntdll.dllhas an export with no name, which the check doesn't expect.- Fix: a small
.asiplugin that registers a native x64 exception handler throughGTA5.exe's.pdatatable and repairs the register state when the fault happens. It touches no game files and patches no game code.- Scope: single-player, build
1.0.3095.0only. See Caveats.
What the crash looks like
The game launches normally, loads your mods, and enters Story Mode. Then, somewhere between 90 seconds and a few minutes later, it vanishes. No error dialog, no ScriptHookV popup. Just your desktop.
When I searched, I found other people reporting the same symptom on Windows 11 24H2 but no public fix. The standard advice was to roll Windows back to 23H2, or to update GTA5.exe to a newer build, which breaks mods compiled for 3095.
If your Event Viewer entry (Windows Logs → Application, Event ID 1000) shows something like this, you probably have the same problem:
Faulting application name: GTA5.exe, version: 1.0.3095.0
Exception code: 0xc0000005 (EXCEPTION_ACCESS_VIOLATION)
Fault offset: 0x03cdfb2e
The fault offset 0x03cdfb2e is the fingerprint. Everything below is about that one instruction.
The setup, and a red herring
I've run GTA V Legacy (1.0.3095.0) with a heavy mod stack for months on an RTX 3060 (12 GB) desktop:
| Mod | Purpose |
|---|---|
ScriptHookV.dll, ScriptHookVDotNet.asi | Native script API and .NET script runtime |
Menyoo.asi, NativeTrainer.asi, AddonSpawner.asi | Trainers and spawners |
QuantV.asi, openCameraV.asi | Visual overhaul and free camera |
OpenIV.asi | OpenIV in-game integration |
HeapAdjuster.asi, PackfileLimitAdjusterEnhanced.asi | Heap and packfile limits for modded installs |
The crashes started the day I installed GTA V Enhanced next to Legacy for a side-by-side comparison. So I blamed that. I suspected the two installs were sharing AppData or Social Club folders, or that my mods had hit a memory ceiling.
I ruled out the usual suspects first:
- Memory and packfile limits. I installed updated
HeapAdjuster.asi(heap raised to2000 MB) andPackfileLimitAdjusterEnhanced.asi(packfile limit doubled from2640to5280). - Background hooks. I closed Mouse Imp Pro, which injects a global hook DLL (
MImp64.dll). - Trainer conflicts. I kept both
Menyoo.asiandNativeTrainer.asiactive, since they had coexisted for months.
It still crashed: once on the Rockstar logo screen, and once a couple of seconds into gameplay.
Diagnosing the crash
Event Viewer: the crash isn't tied to what's on screen
Three crashes, three minidumps, one identical fault offset:
| Crash dump | Process start | Crash time | Uptime at crash | Where I was |
|---|---|---|---|---|
GTA5.exe.14496.dmp | 01:57:08 | 02:00:24 | 3 min 16 s | not recorded |
GTA5.exe.14104.dmp | 02:30:48 | 02:32:24 | 1 min 36 s | Rockstar logo |
GTA5.exe.26096.dmp | 02:33:40 | 02:35:09 | 1 min 29 s | in gameplay |
Whether I was sitting on the intro logo or driving through Los Santos, something inside GTA5.exe fired on a timer and died at the same instruction every time: GTA5.exe + 0x03cdfb2e.
One honest note: the time from launch to the first crash varied (89 s to 3 min 16 s across these dumps). Later, with the fix installed, the repeat interval was a very steady 138 seconds (see the logs). I don't know why the first firing varies. I only know that once it starts, it repeats every 138 s.
Minidump analysis
I parsed the dumps with Python and capstone. The register state was identical in all three:
| Register | Value | Meaning |
|---|---|---|
RIP | GTA5.exe + 0x03cdfb2e | The faulting instruction |
RBX | 0x0 | NULL pointer (the bug) |
R10 | 0x0 | Character index 0 |
R9 | 0x811C9DC5 | FNV-1a 32-bit offset basis |
RDI | 0x1 | Loop counter |
R13 | ntdll + 0x111DB0 | RtlAddVectoredExceptionHandler |
RDX | ntdll + 0x00E908 | RtlpAddVectoredHandler (internal) |
R14 | ntdll + 0x1B5C88 | AddressOfFunctions |
R15 | 0x9D1 (2513) | NumberOfNames |
RCX | &PebLdr.InLoadOrderModuleList | Module list used to find ntdll.dll |
And the faulting code, inside the dynamically unpacked section of GTA5.exe (.text RVA 0x031C4000 to 0x0401A200):
>>> 0x00007FF63720FB2E: 42 0f b6 04 13 movzx eax, byte ptr [rbx + r10]
0x00007FF63720FB33: e9 51 08 73 ff jmp 0x7ff636940389 ; GTA5.exe + 0x03410389
With RBX = 0 and R10 = 0, [rbx + r10] is a read from address 0. Instant EXCEPTION_ACCESS_VIOLATION.
What the game was doing, and why 24H2 broke it
Reading the registers, the routine does roughly this every ~138 seconds:
- Finds
ntdll.dllthroughPEB->Ldr->InLoadOrderModuleList. - Resolves
LdrLockLoaderLock,LdrUnlockLoaderLock,RtlAcquireSRWLockExclusive,RtlReleaseSRWLockExclusiveandRtlAddVectoredExceptionHandler. - Follows the
jmpinsideRtlAddVectoredExceptionHandlerinto the internalRtlpAddVectoredHandler, scans itscalltargets, and walksntdll's export table. - To identify a function, it fetches the export's name string (
RBX) and hashes it byte by byte with FNV-1a, starting from0x811C9DC5.
This looks like an anti-tamper or integrity check that verifies nothing has hooked the exception-handling APIs. As far as I know GTA V's PC build uses Arxan's protection, which is why I named the plugin Arxan24H2Fix, but I'm inferring that from the behaviour. I haven't confirmed it from the dumps alone.
What changed in 24H2
In ntdll.dll 10.0.26100.2894 (Windows 11 24H2), the export directory looks like this:
NumberOfFunctions = 2514
NumberOfNames = 2513
AddressOfFunctions[0] = RVA 0x1D280 <- Ordinal 8, exported by ordinal only (no name)
AddressOfNameOrdinals = [1, 2, 3, ..., 2513] <- index 0 is missing
Function index 0 has no name entry. Separately, the very first call inside RtlpAddVectoredHandler (+0x00e92b: call +0x00dc5c) now targets an unexported internal helper that isn't in AddressOfFunctions at all.
The check seems to assume every function it meets has a name. When the name lookup fails, RBX ends up NULL, and the next instruction reads through it.
Why it started the day I installed Enhanced
I'm not certain. The root cause is the ntdll.dll change above, not GTA V Enhanced. But Legacy had worked for months on this machine, so something changed that day, and I haven't verified when this ntdll.dll build actually landed on my PC (Windows Update history would show it). I only noticed because I launched Legacy right after installing Enhanced. If you can pin down the trigger on your own machine, I'd like to hear about it.
Three attempts at a fix
v1: patch ntdll.dll's export directory
My first theory was that RBX was NULL only because function index 0 had no name. At startup, Arxan24H2Fix.asi v1 allocated a new AddressOfNames / AddressOfNameOrdinals pair and mapped index 0 to an empty string (NumberOfNames: 2513 -> 2514). An empty string sorts before "A_SHAFinal", so GetProcAddress's binary search stayed intact. All 2,513 existing exports resolved identically before and after.
Result: still crashed. But the new dump showed R15 = 0x9D2 (2514), which proved GTA5.exe was reading my patched table. RBX was still NULL, so the check was tripping on something else.
v2: export the internal helpers, and hook the crash site
Disassembling RtlpAddVectoredHandler showed that its first call (0x7ff898cae92b -> 0x7ff898cadc5c) goes to an unexported helper. For v2 I added:
- A scanner that adds
RtlpAddVectoredHandler's unexportedcalltargets (+0xdc5c,+0xe230) tontdll's in-memoryAddressOfFunctions. - A background thread that polled for up to 40 seconds (
2000 * 20 ms) forGTA5.exe + 0x03cdfb2eto unpack, so it could place a 5-bytejmptrampoline guarding againstRBX == NULL.
Result: crashed again, and the plugin's log said why:
[03:26:35.061] Waiting for GTA5.exe+0x03cdfb2e (00007ff63720fb2e) to unpack...
[03:27:16.028] WARNING: Crash site bytes did not match expected pattern: FE EB C6 73 1D
Forty seconds in, the code at the crash site was still encrypted (FE EB C6 73 1D). GTA5.exe only decrypts that block just in time, right before running it at around the 138-second mark. Any polling loop is racing the decryptor, and patching those bytes also risks tripping the game's own self-checksumming.
v3: native x64 .pdata exception handling
This worked, and the reasoning is what makes it robust.
I looked at GTA5.exe's PE exception directory (.pdata, RVA 0x300c000, size 0x10842c). It only covers RVAs up to 0x19d8ba7. The entire unpacked second .text section (0x031C4000 to 0x0401A200, which contains 0x03cdfb2e) has no RUNTIME_FUNCTION entries at all, so when the access violation hits there, Windows finds no handler and the process dies.
On top of that, in my disassembly of ntdll!RtlLookupFunctionEntry, it checks the very last entry of a module's .pdata table before it starts its binary search. That gives a clean way in:
- No code patching. I don't change a byte of
GTA5.exe's.text, so just-in-time decryption and self-checksums stay untouched. - No vectored exception handler. I don't call
AddVectoredExceptionHandler, which would modify the very handler list the game is inspecting. - A real
RUNTIME_FUNCTIONandUNWIND_INFO. At startup the plugin allocates a small block nearGTA5.exe, writes anUNWIND_INFOwithUNW_FLAG_EHANDLER, and rewrites the last.pdataentry to cover[0x03cdfb2e, 0x03cdfb38).
Because .pdata is just a static table consulted at fault time, none of this is timing-sensitive. It doesn't matter whether the section is packed or unpacked yet.
The handler points RBX at a valid empty string, zeroes RAX (what the skipped movzx would have loaded for an empty name), and moves RIP past the faulting instruction to the jmp.
The complete source
Arxan24H2Fix.cpp is about 140 lines. It compiles to a small standalone .asi (around 40 KB) with MinGW-w64 or MSVC:
g++ -shared -O2 -static -s Arxan24H2Fix.cpp -o Arxan24H2Fix.asi
// Fixing GTA V Legacy's game crash on Windows 11 24H2
// (NULL pointer dereference at GTA5.exe + 0x03cdfb2e, build 1.0.3095.0)
#include <windows.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
static const char g_EmptyString[64] = {0};
static volatile LONG g_CrashFixCount = 0;
static void LogMessage(const char* fmt, ...) {
char path[MAX_PATH] = {0};
GetModuleFileNameA(NULL, path, MAX_PATH);
char* lastSlash = strrchr(path, '\\');
if (lastSlash) {
strcpy_s(lastSlash + 1, MAX_PATH - (lastSlash - path + 1), "Arxan24H2Fix.log");
} else {
strcpy_s(path, MAX_PATH, "Arxan24H2Fix.log");
}
FILE* f = NULL;
fopen_s(&f, path, "a");
if (!f) return;
SYSTEMTIME st;
GetLocalTime(&st);
fprintf(f, "[%04d-%02d-%02d %02d:%02d:%02d.%03d] ",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
va_list args;
va_start(args, fmt);
vfprintf(f, fmt, args);
va_end(args);
fprintf(f, "\n");
fclose(f);
}
static uint8_t* AllocateNearModule(uint8_t* modBase, size_t sizeOfImage, size_t allocSize, DWORD protect) {
SYSTEM_INFO si;
GetSystemInfo(&si);
uintptr_t gran = si.dwAllocationGranularity ? si.dwAllocationGranularity : 0x10000;
uintptr_t start = ((uintptr_t)modBase + sizeOfImage + gran - 1) & ~(gran - 1);
uintptr_t maxAddr = (uintptr_t)modBase + 0x7FFF0000ULL;
for (uintptr_t addr = start; addr < maxAddr; addr += gran) {
void* p = VirtualAlloc((void*)addr, allocSize, MEM_RESERVE | MEM_COMMIT, protect);
if (p) return (uint8_t*)p;
}
return NULL;
}
extern "C" EXCEPTION_DISPOSITION NTAPI GtaCrashSehHandler(
PEXCEPTION_RECORD ExceptionRecord,
ULONG64 EstablisherFrame,
PCONTEXT ContextRecord,
PVOID DispatcherContext
) {
uint8_t* gtaBase = (uint8_t*)GetModuleHandleW(NULL);
uintptr_t crashAddr = (uintptr_t)(gtaBase + 0x03cdfb2e);
if (ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
ContextRecord->Rip == crashAddr) {
// Point RBX at a valid empty string instead of NULL
ContextRecord->Rbx = (DWORD64)&g_EmptyString[0];
ContextRecord->Rax = 0;
// Skip past the faulting instruction to the jmp that follows
ContextRecord->Rip = crashAddr + 5; // 0x03cdfb33 (jmp 0x03410389)
LONG count = InterlockedIncrement(&g_CrashFixCount);
if (count <= 10 || (count % 100) == 0) {
LogMessage("Fixed GTA V game crash at %p (NULL RBX -> empty string, count=%ld)",
(void*)crashAddr, count);
}
return ExceptionContinueExecution;
}
return ExceptionContinueSearch;
}
static void InstallGtaCrashFix() {
uint8_t* gtaBase = (uint8_t*)GetModuleHandleW(NULL);
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)gtaBase;
IMAGE_NT_HEADERS64* nt = (IMAGE_NT_HEADERS64*)(gtaBase + dos->e_lfanew);
size_t gtaSize = nt->OptionalHeader.SizeOfImage;
IMAGE_DATA_DIRECTORY excDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
if (!excDir.VirtualAddress || excDir.Size < sizeof(RUNTIME_FUNCTION)) {
LogMessage("ERROR: Could not find .pdata exception directory in GTA5.exe");
return;
}
// Allocate a page near GTA5.exe so its RVA fits in 32-bit UNWIND_INFO fields
uint8_t* block = AllocateNearModule(gtaBase, gtaSize, 4096, PAGE_EXECUTE_READWRITE);
if (!block) {
LogMessage("ERROR: Failed to allocate unwind block near GTA5.exe");
return;
}
memset(block, 0, 4096);
// At block+0x00: 14-byte absolute jump thunk to GtaCrashSehHandler
uint8_t* handlerThunk = block;
handlerThunk[0] = 0xFF;
handlerThunk[1] = 0x25;
*(uint32_t*)(handlerThunk + 2) = 0;
*(uint64_t*)(handlerThunk + 6) = (uint64_t)&GtaCrashSehHandler;
// At block+0x20: UNWIND_INFO structure (4-byte header + 4-byte ExceptionHandler RVA)
uint8_t* unwindInfo = block + 0x20;
unwindInfo[0] = 0x01 | (0x01 << 3); // Version = 1, Flags = UNW_FLAG_EHANDLER (1)
unwindInfo[1] = 0; // SizeOfProlog = 0
unwindInfo[2] = 0; // CountOfCodes = 0
unwindInfo[3] = 0; // FrameRegister = 0
*(uint32_t*)(unwindInfo + 4) = (uint32_t)(handlerThunk - gtaBase);
FlushInstructionCache(GetCurrentProcess(), block, 64);
// Update the last RUNTIME_FUNCTION entry in GTA5.exe's .pdata table
// to cover [0x03cdfb2e, 0x03cdfb38)
RUNTIME_FUNCTION* pdata = (RUNTIME_FUNCTION*)(gtaBase + excDir.VirtualAddress);
size_t numEntries = excDir.Size / sizeof(RUNTIME_FUNCTION);
RUNTIME_FUNCTION* lastEntry = &pdata[numEntries - 1];
DWORD oldProt = 0;
if (VirtualProtect(lastEntry, sizeof(RUNTIME_FUNCTION), PAGE_READWRITE, &oldProt)) {
lastEntry->BeginAddress = 0x03cdfb2e;
lastEntry->EndAddress = 0x03cdfb38;
lastEntry->UnwindData = (uint32_t)(unwindInfo - gtaBase);
VirtualProtect(lastEntry, sizeof(RUNTIME_FUNCTION), oldProt, &oldProt);
LogMessage("SUCCESS: Installed .pdata SEH crash fix for GTA5.exe+0x03cdfb2e (UnwindData RVA=0x%X)",
lastEntry->UnwindData);
} else {
LogMessage("ERROR: VirtualProtect failed on .pdata lastEntry (err=%lu)", GetLastError());
}
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hModule);
LogMessage("=== Arxan24H2Fix.asi v3 (GTA V Crash Fix) loaded ===");
InstallGtaCrashFix();
}
return TRUE;
}
Proof in the logs
With Arxan24H2Fix.asi v3 dropped in next to Menyoo.asi, NativeTrainer.asi, QuantV.asi, HeapAdjuster.asi and PackfileLimitAdjusterEnhanced.asi, the game ran for 30+ minutes without a hiccup. The log shows the periodic check firing every 138 seconds, and the handler repairing it every time:
[2026-10-11 03:41:25.494] === Arxan24H2Fix.asi v3 (GTA V Crash Fix) loaded ===
[2026-10-11 03:41:25.494] SUCCESS: Installed .pdata SEH crash fix for GTA5.exe+0x03cdfb2e (UnwindData RVA=0x4020020)
[2026-10-11 03:43:44.385] Fixed GTA V game crash at 00007ff63720fb2e (NULL RBX -> empty string, count=1)
[2026-10-11 03:46:02.403] Fixed GTA V game crash at 00007ff63720fb2e (NULL RBX -> empty string, count=2)
[2026-10-11 03:48:20.412] Fixed GTA V game crash at 00007ff63720fb2e (NULL RBX -> empty string, count=3)
...
[2026-10-11 04:04:26.538] Fixed GTA V game crash at 00007ff63720fb2e (NULL RBX -> empty string, count=10)
Ten fatal crashes intercepted in 23 minutes, each 2 minutes 18 seconds apart, became zero crashes and uninterrupted gameplay.
Installing it
An .asi file is just a Windows DLL renamed so an ASI loader (dinput8.dll, by Alexander Blade) will load it into the game process at startup. Most modded GTA V installs already have one.
- Make sure you're on GTA V Legacy build
1.0.3095.0and Windows 11 24H2, with an ASI loader installed. - Compile
Arxan24H2Fix.cpp(command above) or use a build you trust, and dropArxan24H2Fix.asinext toGTA5.exe. - Launch the game and play past the first 2 to 3 minutes.
- Open
Arxan24H2Fix.login the same folder. You should see theSUCCESSline at load and, after a couple of minutes,Fixed GTA V game crash ... count=1.
Caveats and limitations
- Single-player only. The plugin makes no network calls, but ASI mods in general aren't safe for GTA Online. Don't load them there.
- Build-specific. The offsets (
0x03cdfb2eand so on) are hard-coded for1.0.3095.0, and the plugin doesn't check the game version. On any other build, don't use it as is. A version check at the top ofInstallGtaCrashFixwould be a sensible addition. - It overwrites a real
.pdataentry. The plugin replaces the lastRUNTIME_FUNCTIONinGTA5.exe's table. I haven't identified which function that entry described. In 30+ minutes of play nothing has gone wrong, but it is a trade-off, and a more careful version might preserve or relocate the original entry. - It relies on an
ntdllimplementation detail. The "last entry is checked first" behaviour ofRtlLookupFunctionEntryis something I observed in disassembly, not a documented guarantee. A future Windows update could change it. - It treats the symptom of a fragile check. It makes
GTA5.exesurvive the NULL read. It does not fix the underlying assumption in the game's code.
Diagnosed and fixed on Windows 11 24H2 (Build 26100, ntdll.dll 10.0.26100.2894), GTA V Legacy 1.0.3095.0, October 2026.