re: saves can be written back, which settles what the develop blob's 4s mean and refutes the tail
The container's derived fields turned out to be reproducible -- length+10 at
+0x30, payload length at +0x8c, adler32(payload) at +0x8e, everything else
copied -- and savegame_edit.py re-wraps a real save BYTE-IDENTICALLY, which is
the check that those three are the only ones. A hand-written save then loaded.
That replaced a blocked experiment (the tail question needed a mission payout,
and none of the currently developable items even sit in the disputed range) with
a direct one: write the blob, read the Arsenal.
- controls: 4 at index 9 -> STILETTO BG1 Developed, 21 -> FALCON 9AM
Developed. A hand-written 4 reaches the screen.
- tail: 4 at 33 and 45 left their rows dashed (both on screen, not below the
fold), and 38 left TOMAHAWK ALPHA RAIL GUN at "0 P" -- not owned. So the
tail is not the weapon.tbl order continued.
- clearing the real save's {22,26,39,46,47} cost the Tomahawk its Developed
status, which puts its flag in that set (39 positionally) -- but a uniform
+1 fails for SPECIAL, so no shift is asserted. Indices >=32 stay marked.
Two behaviours fell out. The title RE-DERIVES developable state on load and
announces it ("You can now develop Broad Sword ..."), so only the 4s are stored
state and a written 2 is pointless. And a no-cost item is bought for 0 P rather
than granted -- TOMAHAWK at "0 P" is what unowned looks like -- which is the
actual reason items read Developed in a save where nothing was spent.
Slot 03 was restored from its archived original (md5 verified); slots 01/02 were
never touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -268,8 +268,32 @@ it would settle:
|
||||
is what maps blob indices to items — but it needs Points, and the balance is
|
||||
now 101 P, so it waits on a mission payout.
|
||||
|
||||
**Nothing here has been written back to a save.** Editing is a separate decision
|
||||
and needs the user's go-ahead; the round-trip in `savegame.py` exists so that an
|
||||
edit *could* be made safely (parse → change one field → re-serialize → re-deflate
|
||||
→ re-wrap), and so that a diff of two saves can be read structurally instead of
|
||||
as raw bytes.
|
||||
## Writing a save back — the container's derived fields
|
||||
|
||||
**A hand-written save loads.** Done 2026-08-11 in the sandbox, into our own
|
||||
throwaway slot 03 (the user's slots 01/02 were not touched and the original of 03
|
||||
is archived in `captures/`), to settle a question that otherwise needed story
|
||||
progress — see [the Arsenal develop economy](../arsenal-develop-economy.md).
|
||||
|
||||
`savegame.py` round-trips the *payload*; the container needs three more fields,
|
||||
all functions of the payload, found by diffing two saves whose payloads differed:
|
||||
|
||||
| header off | value |
|
||||
|---|---|
|
||||
| `+0x30` u32 | `len(deflate stream) + 10` |
|
||||
| `+0x8c` u16 | `len(payload)` — 545 in every save seen |
|
||||
| `+0x8e` u32 | `adler32(payload)` — the zlib trailer, duplicated into the header |
|
||||
|
||||
Everything else is copied from the donor. That is safe precisely because of the
|
||||
earlier same-state result: the words that move between two saves of one state are
|
||||
the FILETIME and uninitialised guest-pointer padding, so nothing else is
|
||||
validated. [`tools/re-capture/savegame_edit.py`](../../../tools/re-capture/savegame_edit.py)
|
||||
implements it and **re-wraps a real save byte-identically**, which is the check
|
||||
that the three fields above are the only derived ones.
|
||||
|
||||
What the title does with an edited save is itself a finding: on load it
|
||||
**re-derives** which items are *developable* and announces the difference ("You
|
||||
can now develop …"). So the blob's `4`s are authoritative state and its `2`s are
|
||||
not — see the economy note.
|
||||
|
||||
Editing beyond a throwaway slot is still the user's call.
|
||||
|
||||
Reference in New Issue
Block a user