Raise the hard-coded 65 535 character limit on XCOM 2 save headers, so saves from heavily modded games load normally again.
Status: reverse engineered, patched, and confirmed in-game on War of the Chosen. The tool locates the patch site by byte signature, so it is not tied to one game build.
English | 简体中文
If you run enough mods, XCOM 2 will eventually show your saves as corrupt (red entry in the load list, "SAVE OUT OF DATE", and — clicking LOAD ANYWAY — a "load failed" dialog). The save is intact. The game just refuses to read it.
The usual explanation is that the combined length of all mod names passed 65 535. The cargo half of that is correct — the mod list really is what fills a save header, and it really is what pushes saves over the line. What the game actually measures is the length of the whole header, not the names:
| save file | header length field (a4) |
loads? |
|---|---|---|
save_传承行动 1:任务6,回合1 |
601 | yes |
save_榴莲_3 |
16 847 | yes |
save_sora |
49 663 | yes |
| — | 65 535 (the limit) | — |
save_快速保存:战役97,任务16,回合1 |
66 913 | no |
save_测试test |
72 968 | no |
The dword at file offset 0x04 is that header length — call it a4. The
game compares a4 against 65 536 and rejects anything larger, so the exact
rule is:
a4 > 65535⟺ the save cannot be loaded.
a4 tracks the mod list because the header is mostly the mod list: across a
dozen saves, the name records occupy 97.6 %–99.6 % of it. But the list does
not occupy it once. In an ordinary save every mod name appears exactly
twice, and heavier campaigns serialise the whole list four or six times:
| save | a4 |
distinct mod names | name records | each name appears |
|---|---|---|---|---|
save_榴莲_3 |
16 847 | 338 | 663 | 2× |
save_保存 63_1 |
36 829 | 797 | 1 583 | 2× |
save_sora |
49 663 | 336 | 1 976 | 6× |
save_测试test |
72 968 | 799 | 3 161 | 4× |
So the field that overflows is the header length, and the reason it grows so
fast is that a long mod list is written into it several times over. Both halves
matter: the mod list is the cargo, a4 is the number being measured.
Every length field inside a save file is 32-bit — there is not a single 16-bit field in the format. The limit lives in the game executable, as one hard-coded comparison in the function that reads a save header:
81 7d 38 00 00 01 00 cmp dword ptr [rbp+0x38], 0x10000 ; a4 vs 65536
0f 87 c7 00 00 00 ja <failure> ; over the limit -> bailThat is the only place in the whole executable that compares a save-header field against 65 535. Nothing on the write side checks it, which is why a patched install happily saves a 72 968-byte header — the bottleneck was only ever on the read path.
Full disassembly, function boundaries, hash verification and the evidence trail
are in docs/TECHNICAL.md.
One byte of the immediate changes:
before: 81 7d 38 00 00 01 00 cmp ..., 0x00010000 (64 KiB)
after: 81 7d 38 00 00 40 00 cmp ..., 0x00400000 (4 MiB)
^^
only this byte (01 -> 40)
The instruction encoding is untouched, so the executable remains structurally valid. A byte-for-byte diff of a patched executable against the original backup shows exactly one differing byte.
There are two implementations of the same patch. Both locate the site by byte signature, both back up before writing, both are equally verified.
PowerShell — no Python needed, works on any current Windows:
powershell -ExecutionPolicy Bypass -File tools\SaveFix.ps1 Status
powershell -ExecutionPolicy Bypass -File tools\SaveFix.ps1 Applyworkshop/item/SaveFix.bat is a double-click launcher around that script. With
no argument it runs the report and then offers to apply the patch itself, so
the tool is usable without ever opening a console window; with any argument it
passes straight through. It also retries the same operation elevated when
Windows denies access to the game folder.
Python 3.8+ — standard library only, no pip install:
# Get the repository
git clone https://github.com/A16Nights/xcom2-savefix.git
cd xcom2-savefix
# 1. See what the tool finds, and whether your game is already patched
python tools/xcom2_savefix.py status
# 2. Patch (default 4 MiB). Close the game first — it locks the executable.
python tools/xcom2_savefix.py apply
# 3. Check your saves
python tools/xcom2_savefix.py savesA backup is written next to the executable as XCom2.exe.65535bak before
anything is modified.
# Choose a different ceiling (any value from 1 to 4096 MiB)
python tools/xcom2_savefix.py apply 16
# Undo everything
python tools/xcom2_savefix.py restore
# Show what would happen without writing
python tools/xcom2_savefix.py apply 8 --dry-runThe tool auto-detects the game through the Steam registry and
libraryfolders.vdf, and handles both the base game and War of the Chosen.
Point it somewhere explicitly with --exe PATH if it guesses wrong.
| build | limit site | site state in a fresh install |
|---|---|---|
| War of the Chosen | RVA 0xa57d6c, file offset 0xa5736c |
cmp ..., 0x10000 |
| base game | RVA 0xa0617c, file offset 0xa0557c |
cmp ..., 0x10000 |
Addresses are shown for reference only — the tool finds the site by signature
(81 7d 38 ?? ?? ?? ?? 0f 87 ?? ?? ?? ?? 83 7b 4c 00 74 29), which resolves to
exactly one location in both executables. It refuses to act if a search returns
zero or multiple matches, rather than guessing.
- Close the game before patching. Steam holds a lock on the executable.
- Steam "Verify integrity of game files" and game updates restore the
original executable. Re-run
applyafterwards. - Saves are not made forward compatible. A save written with the raised
limit will not load on an unpatched install.
restorereturns you to vanilla behaviour. - Multiplayer / Challenge Mode ladders. This patch is for single-player play. Do not use it for ranked or shared-save modes; a modified executable is not something the other side agreed to.
- This is an unsupported modification. It is not affiliated with, endorsed
by, or supported by Firaxis Games, 2K, or Take-Two. See
DISCLAIMER.md.
- Static. The site is the only memory-operand
cmp ..., 0x10000in the entire.textsection; its containing function is confirmed by the PE exception directory (.pdata), and the pattern resolves uniquely in both the base game and WOTC builds. - Runtime. With the limit check instrumented, 52 of 52 save-header reads
returned success, 52 of 52 header hashes matched, and 24 reads would
have been rejected by the original rule. Every one of the oversized headers
(
66913,72690,72822,72834,72838,72870,72880,72902) was passed through. - File level. After patching on disk with no process injection running, reading the mapped executable back showed the patched immediate — proving the change comes from the file, not from a running debugger.
- In game. Previously-unloadable saves (red in the list) load normally.
tools/xcom2_savefix.py the patch tool, Python (status / apply / restore / saves)
tools/pe_util.py dependency-free PE reader and byte-pattern scanner
tools/SaveFix.ps1 the same patch tool in PowerShell, no Python needed
tools/check_vdf.py offline validator for the Workshop build file
workshop/item/ exactly what subscribers receive: manifest, ReadMe,
launcher, tool and preview image
workshop/workshop_build.vdf SteamCMD upload config for that folder
workshop/make_preview.py regenerates ModPreview.jpg (needs Pillow)
docs/TECHNICAL.md how the limit was located, with disassembly and evidence
docs/SAVE_FORMAT.md save-file header layout
docs/WORKSHOP.md why the Workshop item is a tool, and how it is published
README.zh-CN.md 简体中文说明
DISCLAIMER.md legal notice
workshop/item/ holds the files published to the Steam Workshop as
[WOTC] Savefix — Beyond 65535:
https://steamcommunity.com/sharedfiles/filedetails/?id=3818684374
The Workshop item ships the PowerShell tool; subscribers run it against their
own executable, so the item contains no game files at all. There is no second
copy of these files anywhere: the repository folder is the upload source, and
workshop_build.vdf points at it, so re-running SteamCMD updates the live item
from a fresh clone.
The Steam Workshop mod [WOTC] Too Many Mods takes a different approach: it truncates the mod list written into the save so the header stays under the limit. That works around the problem and can silently drop mods that matter. This project removes the limit instead.
MIT for the code in this repository. No game code, game binaries, or game assets are included or redistributed.