Skip to content

Commit e4033f9

Browse files
committed
v0.1.2: ship corruption-safety release
- bump versions to 0.1.2 - README updated with honest framing of what v0.1.2 does and doesn't fix (corruption-safety yes; in-game collectible counters no — counters derived from enum entry presence, not state) - enums.ts: expanded KNOWN_ENUMS with member sets observed across all real saves including the 100% fixture (Consumed, Purchased, Viewed, StartedOrUnlocked, Visited, etc.) - offline README rewritten to match
1 parent 7653d9d commit e4033f9

5 files changed

Lines changed: 36 additions & 10 deletions

File tree

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A save editor for LEGO Batman: Legacy of the Dark Knight that runs in your browser. Drop in a `.sav`, edit what you want, download it. Decrypt, edit, and re-encrypt all happen on your machine.
44

5-
Live: https://tt-save-editor.vercel.app · Current release: **v0.1.1**
5+
Live: https://tt-save-editor.vercel.app · Current release: **v0.1.2**
66

77
## What it can do
88

@@ -14,11 +14,13 @@ Below that, dropdowns for the discrete settings — difficulty, mission state, c
1414

1515
The download gives you both `SaveSlot_X_TT.sav` and its matching `BackupCopy_SaveSlot_X_TT.sav`. The game checks the pair, so the tool writes both.
1616

17-
## Known limitation (will be lifted in v0.1.2)
17+
## What v0.1.2 actually fixes (and what it doesn't)
1818

19-
Right now, enum dropdowns only show options whose byte length matches the current value. So `Unlocked → Rewarded` (both 8 chars) works fine, but `Locked → Collected` (6 vs 9) is blocked. This is a temporary safety against a save-corruption bug: changing an enum to a different byte length shifts subsequent bytes, but the containing Array/Map's outer `Size` field stays stale, which causes the game to misread the rest of that section and reset neighbouring state.
19+
**Fixed:** the save-corruption bug from v0.1.1 (BlackcatXII's report). Changing an enum value to a member of different byte length used to silently shift the rest of that section, which made the game misread it and reset neighbouring state — that's gone. The full recursive GVAS walker now updates every parent container's `Size` field when the body changes length, so the dropdown's "same-length" restriction is lifted.
2020

21-
Fixing this properly means parsing UE 5.6's `StructProperty` tag format (with `TopLevelAssetPath`) well enough to walk the body recursively and find every container's `Size` field, then update them all when an edit changes byte length. That's the v0.1.2 work. See [`docs/v0.1.2-research-brief.md`](docs/v0.1.2-research-brief.md) for the open question, the bytes we've decoded so far, and what's still needed.
21+
**Not yet fixed:** in-game collectible counters (gold bricks, trophies, minikits, costumes, vehicles, etc.) **are derived from how many enum entries exist in your save, not what state they're in**. So flipping a `Locked` gold brick to `Collected` updates the file correctly but doesn't make the displayed `N/30` count go up — that needs adding a *new* entry, not editing an existing one. Insertion is a v0.1.3 thing.
22+
23+
State changes might still affect non-counter things (mission replay, in-world visuals, achievement triggers). We haven't fully mapped which.
2224

2325
## Credit
2426

@@ -58,7 +60,7 @@ pnpm -C apps/web dev # http://localhost:5173
5860

5961
The Studs box and the friendly enum titles live in data files: `packages/core/src/featured.ts` and `packages/core/src/enums.ts`. To map a new field: change it in-game, re-save, diff, add an entry. PRs welcome.
6062

61-
If you have UE GVAS expertise and want to take a swing at the container-size tracking for v0.1.2, see [`docs/v0.1.2-research-brief.md`](docs/v0.1.2-research-brief.md).
63+
The original v0.1.2 research brief that drove the structural-parser work is at [`docs/v0.1.2-research-brief.md`](docs/v0.1.2-research-brief.md) — kept for posterity.
6264

6365
## Legal
6466

apps/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@tt-save/web",
33
"private": true,
4-
"version": "0.1.1",
4+
"version": "0.1.2",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "tt-save-editor",
33
"private": true,
4-
"version": "0.1.1",
4+
"version": "0.1.2",
55
"description": "Open-source web save editor for LEGO Batman: Legacy of the Dark Knight",
66
"license": "MIT",
77
"scripts": {

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tt-save/core",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"type": "module",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",

packages/core/src/enums.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,36 @@
55
* a dropdown built from the members observed in the save.
66
*/
77
export const KNOWN_ENUMS: Record<string, string[]> = {
8-
// Member lists extracted from the game's reflection data.
8+
// Member lists union'd from the game's reflection data and observed across real
9+
// saves (including a 100%-completion save). Conservative — only members we've
10+
// actually seen used, plus the canonical Locked default for "state" enums where
11+
// it's the obvious starting point.
12+
913
EDifficultySetting: ["Normal", "Medium", "Hard"],
10-
ETtChallengeGameProgressState: ["Locked", "Unlocked", "Completed", "Rewarded"],
14+
15+
// Collectible / progress states
1116
ETtGameProgressUnlock: ["Locked", "Unlocked", "Collected"],
17+
ETtGameProgressState: ["Locked", "StartedOrUnlocked", "Complete"],
18+
ETtCollectableGameProgressState: ["Locked", "Unlocked", "Collected", "Consumed"],
19+
20+
// Missions / objectives
1221
ETtMissionGameProgress: ["Locked", "Unlocked", "InProgress", "Complete"],
1322
ETtObjectivesNodeGameProgress: ["Locked", "InProgress", "Complete"],
23+
24+
// Challenges / minigames
25+
ETtChallengeGameProgressState: ["Locked", "Unlocked", "Completed", "Rewarded"],
26+
ETtChallengeMinigameGameProgressState: ["Locked", "Unlocked", "CompletedGold"],
27+
28+
// Shops / upgrades / customization
29+
EShopGameProgressState: ["Locked", "Unlocked", "Visited"],
30+
EDinnerUpgradeGameProgressState: ["Locked", "Unlocked", "Purchased"],
31+
ETtAreaCustomizationItemGameProgressState: ["Locked", "Unlocked", "Purchased"],
32+
33+
// Characters / purple studs
34+
EDinnerCharacterGameProgressState: ["Locked", "Unlocked", "Viewed"],
35+
EPurpleStudGameProgressState: ["Locked", "Unlocked", "Collected"],
36+
37+
// System / metadata
1438
ETtPlayGoInstallState: ["Unknown", "InitialChunkOnly", "Complete"],
1539
ETtSaveGameVersion: ["NoVersion", "Initial", "LatestVersion", "UnknownVersion", "VersionCount"],
1640
ETtSaveSlotValidationState: ["Unknown", "Validated", "Invalidated"],

0 commit comments

Comments
 (0)