The One Byte That Crashed Baldur's Gate

Using Process Monitor and AI to find a corruption that bisection couldn’t


In a previous article, I wrote about using AI to update a heavily-modded Baldur’s Gate: Enhanced Edition Trilogy install from game version 2.5 to 2.6.6, and how AI collapsed what used to take weeks into a couple of days. That article had a clean narrative: identify problems, work the tools, ship the fix.

This article is the messier one. It’s about what happens when AI assistance, decades of community knowledge, and every standard diagnostic technique fails to crack a problem — and what we did to break through anyway.

The short version: A specific encounter in my modded install crashed the game every time the script tried to summon a Fire Salamander. I lost most of a day to it. I tried five different fixes. Two of them made things worse. The AI ran out of ideas. I considered giving up and scripting the encounter out of the game entirely.

Then we installed a tool called Process Monitor, captured what the game was actually doing in the last 300 milliseconds before it died, and discovered that the entire crash came down to one wrong byte in one file.

This article is about that technique — how to set up Process Monitor, what to capture, and exactly what to tell an AI to look for. It generalizes far beyond Baldur’s Gate. Any time an application crashes for reasons that don’t show up in obvious places, this approach works.


Why I’m writing this

Most Baldur’s Gate modding articles assume the problem will eventually yield to forum-style debugging: try things, ask questions on Spellhold Studios or the Gibberlings3 forums, get a reply in a few days, try again. That works for a lot of problems.

It does not work for a specific class of crash where:

  • The crash is reproducible (happens every time)
  • The crash has no clear log entry
  • The crash happens after files are loaded, so it’s not a “missing file” problem
  • The crash is in a code path that mods modify, but no specific mod stands out as the obvious culprit

In those cases, you’re not looking for a “missing X” or “wrong setting Y” — you’re looking for a corruption that’s invisible to the game itself. The game loaded the file just fine. The bytes inside the file are what’s broken.

That’s where Process Monitor comes in. It tells you what file the engine was processing at the exact moment things went sideways. From there, you can compare that file against a known-good version, and the AI can spot the difference in seconds.

I’ll describe the technique in enough detail that someone with no prior Process Monitor experience could replicate it. The Baldur’s Gate context is just where I happened to use it.


The wall

Before I get to the technique, here’s the context that justifies it.

My install is heavy: 261 mod components on the BG2EE side, another 28 on BGEE, all merged together by EET. After getting the migration done, I’d been playing through Siege of Dragonspear content via the EET trilogy when I hit the Repository of Undeath — a side area in the Dwarves of Dumathoin questline that includes a script called BDPORTAL.bcs. That script runs a timer that randomly summons fire-themed creatures from a “broken portal to other planes.” One of those summons is a pair of Fire Salamanders.

The game crashed every time the salamanders spawned.

I started where you start: identify what file the engine was unhappy about. The salamander creature file is BDSALF01.CRE, and it contains a list of 30 “effects” (status effects applied at spawn — fire immunity, resistances, special protections). Some of those effects reference external files. If one of those referenced files is corrupted, the engine crashes when it tries to process the effect.

The standard approach for this kind of problem is binary search via effects count. The CRE file has a field at offset 0x2C8 that tells the engine how many effects to read. I temporarily set that count to small numbers (0, 1, 2, 4, 7, 15) and tested at each point. At zero effects, no crash but no salamander either. At one effect, crash on spawn. At four effects, crash on unpause instead of spawn. The boundary between “crash on spawn” and “crash on unpause” is itself a diagnostic signal — it tells you which range of effects is causing the problem.

Working from that bisection, I identified five files that needed restoration to their clean versions:

  • SPIN561.SPL (a fire-themed innate spell, restored from BGEE)
  • SPWI507.SPL (Mordenkainen’s Sword, restored from clean BIFF)
  • SPWI306.SPL (Slow, restored from clean BIFF)
  • SPPR208.SPL (Slow Poison, restored from clean BIFF)
  • SPNWCHRM.VVC (a charm visual effect, restored from clean BIFF)

Each of these was a real corruption. Each restoration was confirmed against multiple known-good sources. By all logic, fixing those files should have fixed the crash.

The crash persisted.

Then it got weirder: restoring SPNWCHRM.VVC actually made things worse. The crash moved from “on unpause” to “on spawn” — meaning my fix shifted the engine onto a different code path, but that code path also crashed. I reverted it.

At that point, I’d been at it for several hours. The AI had run out of theory-based ideas. I was looking at the script-level workaround — modifying BDPORTAL.bcs to summon a different creature in place of the salamanders — and considering taking the loss.

Then I asked a different question: instead of trying to guess which file was bad, could we just watch what the engine was doing at the moment it died?

That’s what Process Monitor does.


What Process Monitor is, and why it matters

Process Monitor (Procmon for short) is a free Microsoft Sysinternals tool. It captures every single file, registry, and process operation happening on your system in real time. When an application reads a file, Procmon logs it. When it writes a registry value, Procmon logs it. When it creates a thread, Procmon logs it.

The output is overwhelming if you don’t filter it — modern Windows generates thousands of events per second across all running processes. But with the right filter, you can isolate exactly what one specific program is doing, with microsecond-level timestamps.

For a crash like the one I was hunting, the question is simple: what was Baldur.exe doing in the last few hundred milliseconds before it died? Process Monitor answers that question precisely.

The key insight that took me too long to reach: a crashing game doesn’t always crash on the file it’s reading. It often crashes 200 to 500 milliseconds after the read, while it’s parsing or applying the data inside. So traditional “show me the last failed file open” diagnostics miss the problem. You need to see the LAST SUCCESSFUL READ before the process dies — that’s where the corrupted data came from.

Procmon gives you that. With a CSV export and an AI capable of parsing structured logs, finding the smoking gun takes minutes.


Setting up Process Monitor — exact steps

Here’s everything you need to do, in order. I’ll assume Windows 10 or Windows 11 and no prior Procmon experience.

Step 1: Download

Go to https://download.sysinternals.com/files/ProcessMonitor.zip in any browser. The download is about 3 MB. It’s distributed by Microsoft directly, so it’s safe and your antivirus will not flag it.

Extract the zip to a folder you’ll remember. I put mine in D:\Games\BG-EET-26\diagnostics\. The folder containing Procmon doesn’t matter — it just needs to be somewhere you can find later. Procmon doesn’t install; it just runs.

After extraction, you’ll have a few files:

  • Procmon.exe (32-bit version)
  • Procmon64.exe (64-bit version — use this one on modern Windows)
  • Procmon64a.exe (ARM64 version)
  • A handful of documentation files

Step 2: First run

Right-click Procmon64.exe and choose “Run as administrator.” Procmon needs admin privileges to install a kernel driver that does the actual event capture. Accept the UAC prompt. The first time you run it, you’ll get an EULA. Accept it.

Procmon opens. You’ll immediately see thousands of events scrolling by — every process on your system is being logged. That’s expected. We’ll filter to just what we care about.

Step 3: Configure the filter

A filter dialog will likely auto-open the first time. If it doesn’t, press Ctrl+L to open it manually.

In the filter dialog, you’ll see five dropdowns across the top. Set them as follows:

  1. First dropdown (column): Process Name
  2. Second dropdown (relation): is
  3. Third field (value): Type Baldur.exe exactly
  4. Fourth dropdown (action): Include

Click the Add button. You’ll see your filter appear in the list below.

If you’re using EEex (which means your game launcher is InfinityLoader.exe rather than Baldur.exe directly), add a second filter the same way:

  1. Process Name
  2. is
  3. InfinityLoader.exe
  4. Include

Click Add again.

Click OK to close the filter dialog.

The main Procmon window should now show very few events — only when Baldur.exe or InfinityLoader.exe does something. If your game isn’t running, you’ll see nothing at all. That’s correct.

Step 4: Verify capture is enabled

Look at the toolbar in the top-left of Procmon. You should see a magnifying glass icon. It should not have a red X through it. If it does, click it once to toggle capture on, or press Ctrl+E.

When capture is on, events will appear in real time as your filtered processes do anything. With the game not running, the list stays empty.

Step 5: Launch the game and trigger the crash

Leave Procmon running. Alt-Tab to your desktop and launch Baldur’s Gate normally — via your usual shortcut. The moment the game starts, you’ll see events scrolling rapidly in Procmon. That’s expected — even at the title screen, the game is loading hundreds of files per second.

Load your save. Navigate to wherever the crash happens. Trigger the crash.

The game will die. Procmon will keep capturing. The capture is happening in the kernel, not in the game’s process, so the game crashing has no effect on Procmon’s log.

Step 6: Stop capture and save the log

Alt-Tab back to Procmon. Press Ctrl+E to stop capturing. The toolbar magnifying glass should now have a red X.

Now save the log. This step has a gotcha that cost me one capture worth of data, so pay attention.

Go to File → Save… (or press Ctrl+S).

In the Save dialog:

  • Events to save: Choose “Events displayed using current filter”. Do NOT choose “All events” (you’ll get every process on the system, ten times more data than you need).
  • Also include profiling events: UNCHECK this box. Profiling events are noise — they’re periodic process snapshots from every program on your system, and they explode your log with garbage that has nothing to do with the crash. I learned this the hard way. Uncheck it.
  • Format: Native Process Monitor Format (PML). PML is Procmon’s binary format and is what we’ll convert to CSV in the next step.
  • Path: Save somewhere you can find again. I used D:\Games\BG-EET-26\diagnostics\baldur-trace.PML.

Click OK. The save takes a few seconds for a typical capture (the file will be 20-60 MB).

Step 7: Convert to CSV for AI analysis

The PML file is binary and not directly readable. To analyze it with AI, convert it to CSV. From a PowerShell prompt:

& "D:\Games\BG-EET-26\diagnostics\Procmon64.exe" /OpenLog "D:\Games\BG-EET-26\diagnostics\baldur-trace.PML" /SaveAs "D:\Games\BG-EET-26\diagnostics\baldur-trace.CSV"

Wait 10-30 seconds. The command exits silently when done. You’ll have a CSV file roughly half the size of the PML.

If your CSV is over 100 MB, your filter wasn’t tight enough or you accidentally saved with profiling events. Re-capture and re-save with the corrections above.


Asking the AI to analyze it

Now the AI takes over. The pattern I’m about to describe works with Claude, with ChatGPT, with any AI that can read large structured text files. The technique is more important than the model.

The framing prompt

Start with context. Don’t just dump the file. Tell the AI what you captured and what you’re looking for. Something like:

I captured a Process Monitor trace of [game/app] crashing. The crash signature is [0xc0000005 at offset 0x22afc in ntdll.dll heap function — or whatever yours is]. The trace contains all file operations from the [Baldur.exe and InfinityLoader.exe] processes. I want to find what the engine was doing in the last few hundred milliseconds before it died, so I can identify which file’s data triggered the crash.

This framing matters. Without it, the AI might focus on failed file operations (which are usually noise — Windows fails opens all the time during normal operation), or might get lost in registry queries. The framing focuses it on “what was the last meaningful thing before the process exited.”

Queries that worked for me

After uploading the CSV, the questions that produced useful results were:

1. The death sequence

Show me the last 30 file operations from Baldur.exe before the process exits. I want to see the sequence of file reads leading up to the crash, in chronological order.

What you’re looking for in the response: a sequence of CreateFile and ReadFile operations, in time order. The cleanup phase (CloseFile operations after the crash) doesn’t matter — those are Windows reclaiming handles after the process died. You want what came before that.

2. The crash boundary

Find the timestamp where WerFault.exe is created (that’s Windows Error Reporting, which spawns when a process crashes). Then show me every game resource file (.CRE, .SPL, .ITM, .BAM, .VVC, .EFF, .2DA) that was read in the 500 milliseconds before that timestamp.

The Process Create event for WerFault.exe is the crash moment. Everything before it is “what the engine was doing.” The 500-millisecond window is usually enough to see the trigger.

3. The last meaningful file

Looking only at the files in the game’s override folder, what was the very last one accessed before the crash? That’s the file whose data the engine was likely processing when it died.

This narrows the search to mod-affected files (the override folder is where mods write their changes, while BIFF archives are the original game data). When a crash is mod-related, the culprit is almost always in override.

What the AI did with mine

After running these prompts against my CSV, the AI produced a clear sequence:

8:37:30.043 BDSALF01.CRE loaded (the salamander creature)
8:37:30.044 BDSALF01.ITM loaded (the salamander's weapon)
8:37:30.077 BDSALF01.SPL loaded (the salamander's innate spell)
8:37:30.078 FIAURAC.VVC loaded (fire aura visual)
8:37:30.177 tra_06.wav loaded (sound effect)
8:37:30.405 WerFault.exe spawned (CRASH)

The gap between the last file read and the crash was 227 milliseconds. That’s an eternity in CPU time. It tells you the engine had finished loading and was actively processing the data when something went wrong.

Of the files loaded, BDSALF01.SPL was the one I’d never checked. I’d checked the CRE (matched BGEE). I’d checked the ITM (matched BGEE). I’d never checked the SPL.


Finding the corruption

With the AI pointing at BDSALF01.SPL, the next step was straightforward: compare the file against a known-good source.

For Siege of Dragonspear content (which my salamander is part of), the canonical known-good source is the BGEE install’s override folder — not BG2EE’s clean BIFF archive, because BG2EE doesn’t natively have SoD content. EET copies it over from BGEE during installation.

The comparison:

  • BG2EE-BASE-MODS-QoL\override\BDSALF01.SPL (the version that crashes): 250 bytes, hash 20E16F5F0B94…
  • Baldur's Gate - Enhanced Edition\override\BDSALF01.SPL (the BGEE version): 250 bytes, hash 6D39AFEADAF7…

Same size. Different hashes. Different bytes somewhere.

A byte-by-byte diff produced exactly one difference:

  • Offset 0x0A2 in BG2EE: 0x92 (decimal 146)
  • Offset 0x0A2 in BGEE: 0x03 (decimal 3)

One byte. That’s the entire bug.

To understand why one byte caused a heap corruption crash, you need a bit of Infinity Engine internals. The byte at offset 0x0A2 in this SPL file is the second parameter (p2) of an “effect” — specifically, opcode 318 (“Protection from Spell”). That effect makes the salamander immune to its own spell so two salamanders don’t damage each other.

Opcode 318’s p2 parameter is a mode selector. Valid values are:

  • 1: Match by spell name
  • 2: Match by spell type
  • 3: Match any spell with a given hash

Anything outside 1-3 is undefined behavior. The engine dereferences into a mode-handler table indexed by p2. If p2 is in range, the right handler runs. If p2 is 146, the engine reads garbage from memory that happens to live 146 entries past the start of the table, treats it as a function pointer, and either jumps somewhere it shouldn’t or corrupts the heap by writing to a location calculated from that garbage.

That’s the kind of bug no human is going to spot by reading source code, and no log is going to flag. The corruption is silent, the game loads the file just fine, and the explosion happens hundreds of milliseconds later in code that has nothing to do with the file itself.

But once you know it’s that one byte, the fix is trivial: copy the BGEE version over the BG2EE version, restoring p2 from 146 to 3.

I tested. The crash was gone. Salamanders spawned, attacked, died. The encounter played normally for the first time.


What this means for the next time you hit a wall

There are a few principles worth pulling out of this story, because they apply far beyond Baldur’s Gate.

1. Bisection finds the haystack, not the needle.

Earlier in the investigation, I’d narrowed the problem down to “something in BDSALF01.CRE’s effect table.” That was real progress — I went from “the entire mod stack” to “this 30-entry list.” But bisection within the list could only narrow it to “effects 0-3” because each effect references external files, and the actual bug was in those files. I was looking at the wrong layer.

When bisection stops yielding new information, the bisection is done. You need a different kind of evidence.

2. Reality is more reliable than theory.

Every wrong path I’d taken before Procmon was theory-based: “this opcode looks suspicious,” “this visual effect might be the culprit,” “this mod likely patches that file.” Some of those theories were even correct in part. But none of them led to the fix.

Procmon captures what’s actually happening, not what I think might be happening. The moment we had real data, the problem yielded in minutes.

3. AI is a force multiplier on top of structured data.

I want to be specific about what the AI did and didn’t do. It did not magically guess the answer. It did not have prior knowledge of which byte was wrong. What it did was:

  • Read a CSV with 72,000+ rows and surface the 30 most relevant ones
  • Recognize patterns (“here’s the crash boundary, here’s the last meaningful read”)
  • Suggest comparison targets (“BGEE override is the canonical source for SoD content”)
  • Produce a byte-level diff in seconds when asked

Without the AI, I could have done all of this manually. With Excel and patience, the CSV analysis is doable. Diff tools exist for byte comparison. But manually, each step would have taken 20-30 minutes. With the AI, the whole sequence — from “the file is saved” to “here’s the byte and the fix” — was under five minutes.

4. When you’re stuck, instrument.

This is the generalizable lesson. Process Monitor isn’t a Baldur’s Gate tool. It’s a Windows diagnostic instrument. Any time an application is doing something you can’t see — crashing in a way you can’t explain, behaving in a way you can’t predict, accessing files you can’t figure out — Procmon gives you an unfiltered view of reality.

The pattern works for application crashes, mysterious file lock errors, anti-cheat behavior in games, weird load times, suspect-malware traces, anything. Capture, filter, compare to expectation, find the gap.


A reusable diagnostic kit

If I were starting an investigation like this from scratch tomorrow, here’s the kit I’d want in place:

Tools:

Habits:

  • Backup before any modification (so reverting is one command)
  • Capture before changing anything (so you have “before” state)
  • Compare against multiple known-good sources (clean BIFF, BGEE override, a backup from before recent changes)
  • Trust the trace over your theory

Prompts:

  • “Show me the last 30 operations from [process] before exit”
  • “Find when WerFault.exe spawns and show me what happened in the 500ms before”
  • “What’s the last file in [folder] that was accessed before the crash?”
  • “Diff [suspect file] against [known good source] byte by byte and show me the differences”

That kit and that habit will get you out of more dead-ends than any amount of forum searching.


Where this leaves the install

The salamander crash is fixed. By extension, the Boareskyr Bridge encounter coming later in the game (which has 39 separate salamander summon references in its script) is also pre-fixed by this same one-byte change.

There are two more salamander-related creature files (BDSALLO.CRE and BDSALLO2.CRE) that also differ from their BGEE counterparts. They haven’t crashed me yet because I haven’t encountered the quests that use them. If they do crash, I now know exactly what to do: capture, find the file, compare to BGEE, fix the difference.

What used to be the scariest kind of bug — one that ate a day, defied logic, and resisted every standard technique — has become a predictable hour of work.

That’s what good tools do. They turn unknown unknowns into known knowns, and they turn theories into observations. Process Monitor was the missing tool in my modding toolkit. Now it isn’t.

If you’re modding any complex game and you’ve ever hit a crash you couldn’t explain — even one that has nothing to do with Baldur’s Gate — this is the technique I wish someone had pointed me at five years ago. Add it to your toolbox. The next time logic runs out, you’ll have something better than logic to fall back on.


Atomm bought Baldur’s Gate in 1998 and reviewed Baldur’s Gate II for GamersRadio when it released.

Earlier in this series: Modding Baldur’s Gate in 2026: A Player’s Guide to Doing It With AI covers the broader 2.5 → 2.6.6 migration story, and I Killed Sarevok and the Game Ended covers the broken EET hand-off into Siege of Dragonspear. This article covers the specific debugging technique that closed it out.