Fixing GTA V Legacy's Mysterious 2-Minute Silent Crash on Windows 11 24H2

Creative Geek•

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.

Fixing GTA V Legacy's Mysterious 2-Minute Silent Crash on Windows 11 24H2

TL;DR

  • Symptom: GTA V Legacy 1.0.3095.0 on 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.exe walks ntdll.dll's export table and dereferences a NULL name pointer. 24H2's ntdll.dll has an export with no name, which the check doesn't expect.
  • Fix: a small .asi plugin that registers a native x64 exception handler through GTA5.exe's .pdata table 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.0 only. 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:

ModPurpose
ScriptHookV.dll, ScriptHookVDotNet.asiNative script API and .NET script runtime
Menyoo.asi, NativeTrainer.asi, AddonSpawner.asiTrainers and spawners
QuantV.asi, openCameraV.asiVisual overhaul and free camera
OpenIV.asiOpenIV in-game integration
HeapAdjuster.asi, PackfileLimitAdjusterEnhanced.asiHeap 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:

  1. Memory and packfile limits. I installed updated HeapAdjuster.asi (heap raised to 2000 MB) and PackfileLimitAdjusterEnhanced.asi (packfile limit doubled from 2640 to 5280).
  2. Background hooks. I closed Mouse Imp Pro, which injects a global hook DLL (MImp64.dll).
  3. Trainer conflicts. I kept both Menyoo.asi and NativeTrainer.asi active, 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 dumpProcess startCrash timeUptime at crashWhere I was
GTA5.exe.14496.dmp01:57:0802:00:243 min 16 snot recorded
GTA5.exe.14104.dmp02:30:4802:32:241 min 36 sRockstar logo
GTA5.exe.26096.dmp02:33:4002:35:091 min 29 sin 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:

RegisterValueMeaning
RIPGTA5.exe + 0x03cdfb2eThe faulting instruction
RBX0x0NULL pointer (the bug)
R100x0Character index 0
R90x811C9DC5FNV-1a 32-bit offset basis
RDI0x1Loop counter
R13ntdll + 0x111DB0RtlAddVectoredExceptionHandler
RDXntdll + 0x00E908RtlpAddVectoredHandler (internal)
R14ntdll + 0x1B5C88AddressOfFunctions
R150x9D1 (2513)NumberOfNames
RCX&PebLdr.InLoadOrderModuleListModule 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:

  1. Finds ntdll.dll through PEB->Ldr->InLoadOrderModuleList.
  2. Resolves LdrLockLoaderLock, LdrUnlockLoaderLock, RtlAcquireSRWLockExclusive, RtlReleaseSRWLockExclusive and RtlAddVectoredExceptionHandler.
  3. Follows the jmp inside RtlAddVectoredExceptionHandler into the internal RtlpAddVectoredHandler, scans its call targets, and walks ntdll's export table.
  4. To identify a function, it fetches the export's name string (RBX) and hashes it byte by byte with FNV-1a, starting from 0x811C9DC5.

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.

A "Periodic check in GTA5.exe ~138 s " B "Locate ntdll.dll via PEB InLoadOrderModuleList". B C "Resolve RtlAddVectoredExceptionHandler - RtlpAddVectoredHandler". C D "Look up targets in ntdll's export table". D E "Export name found?". E -- "Yes pre-24H2 layout " F "FNV-1a hash the name, check passes". E -- "No 24H2: unnamed or unexported " G "RBX = NULL". G H "movzx eax, byte ptr rbx + r10 at +0x03cdfb2e". H I "0xC0000005 - silent exit to desktop"

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

A "Silent crash noticed" B "Event Viewer: GTA5.exe + 0x03cdfb2e". B C "Minidumps: RBX = NULL". C D "24H2 ntdll: unnamed export". D E "v1: patch export table br/ still crashes". E F "v2: export helpers + code hook br/ timing race, still crashes". F G "v3: .pdata SEH entry br/ fixed"

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:

  1. A scanner that adds RtlpAddVectoredHandler's unexported call targets (+0xdc5c, +0xe230) to ntdll's in-memory AddressOfFunctions.
  2. A background thread that polled for up to 40 seconds (2000 * 20 ms) for GTA5.exe + 0x03cdfb2e to unpack, so it could place a 5-byte jmp trampoline guarding against RBX == 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_FUNCTION and UNWIND_INFO. At startup the plugin allocates a small block near GTA5.exe, writes an UNWIND_INFO with UNW_FLAG_EHANDLER, and rewrites the last .pdata entry 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.

participant ASI as Arxan24H2Fix.asi. participant Game as GTA5.exe. participant Win as ntdll / Windows kernel. participant SEH as GtaCrashSehHandler. ASI- Game: DllMain DLL_PROCESS_ATTACH. ASI- Game: Allocate page near GTA5.exe, write thunk + UNWIND_INFO. ASI- Game: Overwrite last .pdata entry: 0x03cdfb2e..0x03cdfb38. Note over Game: ~138 s later, periodic check runs. Game- Win: movzx eax, rbx + r10 with RBX = NULL - 0xC0000005. Win- Win: RtlLookupFunctionEntry +0x03cdfb2e finds our entry. Win- SEH: Dispatch to handler. Note over SEH: RBX = &empty_string, RAX = 0, RIP = +0x03cdfb33. SEH Win: ExceptionContinueExecution. Win Game: Resume at the jmp, check completes

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.

  1. Make sure you're on GTA V Legacy build 1.0.3095.0 and Windows 11 24H2, with an ASI loader installed.
  2. Compile Arxan24H2Fix.cpp (command above) or use a build you trust, and drop Arxan24H2Fix.asi next to GTA5.exe.
  3. Launch the game and play past the first 2 to 3 minutes.
  4. Open Arxan24H2Fix.log in the same folder. You should see the SUCCESS line 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 (0x03cdfb2e and so on) are hard-coded for 1.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 of InstallGtaCrashFix would be a sensible addition.
  • It overwrites a real .pdata entry. The plugin replaces the last RUNTIME_FUNCTION in GTA5.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 ntdll implementation detail. The "last entry is checked first" behaviour of RtlLookupFunctionEntry is 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.exe survive 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.