Skip to content

Load saves whose mods serialise their own payloads - #28

Merged
valentinps merged 1 commit into
mainfrom
fix/modded-object-trailing-bytes
Sep 6, 2026
Merged

Load saves whose mods serialise their own payloads#28
valentinps merged 1 commit into
mainfrom
fix/modded-object-trailing-bytes

Conversation

@valentinps

Copy link
Copy Markdown
Owner

A user reported the loader failing on a save recorded with FicsItCam:

Failed to load save: Found 267135 extra trailing bytes for ComponentHeader /Script/FicsItCam.FICCamera.

What's actually wrong

FicsItCam parks a camera recording on its FICCamera component — ~300 KB each, three of them in that save. The parser required every object to end exactly where its properties did, so the first one failed the whole load.

This is not a regression, and not the Satisfactory update. That code path hasn't changed since the original Rust port, FicsItCam has never been handled, and the save is on build 502094 — the same build as saves that load fine. What changed for the reporter is having camera recordings in the save at all; before, the component had nothing trailing to trip over.

The fix

An object declares its own size, so a payload we don't understand is skippable without guessing — we know exactly where it ends. The remainder is kept verbatim as opaque bytes.

Only for modded classes, though. Trailing bytes on vanilla content mean a hole in our own reading of the format, and staying loud about those is how the conveyor item-state bug (#22) got found. MODDED_RAW stays an explicit list because a mod may ship under a vanilla-looking path, as the golden golfcart does.

In the reported save this covers 3 objects out of 1.3 million. The other 13 modded classes present (DoggoHardHat, SkyUI, UtilityMod, EfficiencyChecker, the other FicsItCam classes) already parsed cleanly and are untouched.

Verification

  • All 23 corpus saves produce byte-identical payload and index JSON against main — no vanilla save changes in any way.
  • The reported save parses, round-trips through export to its exact 1.54 GB body, and re-parses — so the mod's camera data survives editing and saving.
  • It renders its 1,299,672 objects in the browser with no console errors.
  • Full suite green, including a debug-build run so the span-reparse debug_assert is live.
  • New unit tests pin the vanilla/modded rule, including near-misses like /Script/FactoryGameExtra. and the modded classes already special-cased by name.

Not covered

There's no CI fixture for the mod path — the reporter's save is 54 MB and isn't mine to publish. The corpus byte-identity check covers "nothing else changed"; the mod path itself was verified by hand as above.

Adds examples/debug_modded.rs, which lists a save's modded classes and which of them carry opaque payloads — the tool that produced the breakdown above, for the next report like this.

FicsItCam, the camera mod streamers record with, parks a camera recording
on its FICCamera component -- ~300 KB each, three of them in the reported
save. The parser required every object to end exactly where its properties
did, so the first such component failed the whole load:

  Found 267135 extra trailing bytes for ComponentHeader /Script/FicsItCam.FICCamera.

Nothing about that is new: this path has not changed since the original
Rust port, FicsItCam has never been handled, and the save is on the same
game build (502094) as saves that load fine. What changed for the reporter
is having camera recordings in the save at all.

An object declares its own size, so a payload we do not understand is
skippable without guessing: we know exactly where the object ends. The
remainder is now kept verbatim as opaque bytes -- but only for classes
that came from a mod, because trailing bytes on VANILLA content mean a
hole in our own reading of the format, and staying loud about those is how
the conveyor item-state bug got found. MODDED_RAW stays an explicit list:
a mod may ship under a vanilla-looking path, as the golden golfcart does.

In the reported save this covers 3 objects of 1.3 million; the other 13
modded classes present (DoggoHardHat, SkyUI, UtilityMod, EfficiencyChecker
and the rest) already parsed cleanly and are untouched.

Verified: all 23 corpus saves produce byte-identical payload and index
JSON, so no vanilla save changes at all; the reported save now parses,
round-trips through export to its exact 1.54 GB body, and renders its
1,299,672 objects in the browser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@valentinps
valentinps merged commit 2d9dea2 into main Sep 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant