Repository navigation
Save File Format
Everything PCEdit knows about the on-disk shape of a Planet Crafter save. This page describes the data; Save File Library covers the code that reads and writes it.
Note
Reverse-engineered from real saves on game versions 2.008 and 2.102, Steam and Xbox / PC Game Pass, and 2.103 on Steam. It is not documentation from the developers, and nothing here is guaranteed to survive a future game build.
A save is a single UTF-8 text file. Despite the .json extension on Steam, the file is not
JSON overall — it is ten sections of JSON glued together with control characters.
BOM? \r <section 0> \r@\r <section 1> \r@\r … \r@\r <section 9> \r@
| Element | Bytes |
|---|---|
| Leading byte-order mark | present on Steam saves, absent on Xbox / PC Game Pass (WGS) |
| File prefix | \r |
| Section separator | \r@\r |
| File suffix |
\r@ (no trailing newline) |
| Record separator inside a list section | a pipe followed by \n
|
A section holds either one JSON object or a list of JSON objects joined by the record separator.
Important
An @ only separates sections where the framing line breaks bracket it. Players can type one into
a container or sign label, or into a player name, and those sit inside sections 3 and 2 as
ordinary JSON string content. A reader that splits on every @ in the file mis-frames such a save
— PCEdit did until v1.3.0, and for one placement it dropped part of the file with no error at
all.
The Steam build writes the BOM; the WGS (Game Pass) build does not, and the game rejects a Game Pass save that has gained one — it reports a "file error" on load. PCEdit therefore preserves whatever the file on disk already had, and only writes a BOM for a path that does not exist yet.
| # | Section | Shape | Model |
|---|---|---|---|
| 0 | Unlocks | single object | SaveFileUnlocks |
| 1 | Terraformation | list — one per planet | PlanetTerraformation |
| 2 | Players | list | PlayerData |
| 3 | World objects | list — the big one (~12,000 in a real multi-planet save) | WorldObject |
| 4 | Inventories | list (~1,000 in a real multi-planet save) | Inventory |
| 5 | Statistics | single object | SaveFileStatistics |
| 6 | Read messages | list | ReadMessage |
| 7 | Story events | list | StoryEvent |
| 8 | Metadata | single object | SaveFileMetadata |
| 9 | Procedural instances | list | ProceduralInstance |
The order is fixed and positional — there are no section names in the file.
Terra token balances (terraTokens, allTimeTerraTokens), the comma-joined unlockedGroups list,
the opened-instance seed and timer, and — new in 2.102 — logisticsPaused.
One record per planet:
planetId, unitOxygenLevel, unitHeatLevel, unitPressureLevel, unitPlantsLevel,
unitInsectsLevel, unitAnimalsLevel, unitPurificationLevel.
unitPurificationLevel is -1 on planets where purification does not apply — a sentinel, not a
value, and PCEdit preserves it as such.
Identity (id, name, host), location (planetId, playerPosition, rotation), the four gauges
(oxygen, thirst, health, toxicity), and per-player statistics such as totalCraftedObjects and
totalTerraTokenEarned.
Every object that exists in the world or in an inventory. Keys are heavily abbreviated:
| Key | Meaning |
|---|---|
id |
Object id — the identity referenced from inventories |
gId |
Group id: what the object is (Iron, Uranim, Teleporter1…) |
pos / rot
|
Position / rotation, as "x,y,z" strings |
planet |
Integer hash of the planet id (see below) |
liId |
Linked inventory id — how a container points at its inventory |
liGrps |
Linked inventory groups |
siIds |
Spawned instance ids |
grwth |
Growth stage |
count |
Mineable amount on a resource node, as "remaining,total"
|
pnls |
Panel settings |
linkedWo |
Id of another world object this one is bound to (e.g. a toxic-water collector → its vein) |
text |
Player-typed label on a container or sign |
color |
Colour |
Two properties of this section drive the whole design of the serializer:
- Key order is not stable. The same pair of keys appears in both orders across records in one real file. A fixed property order cannot reproduce the file, so the reader records each record's key order and the writer replays it.
-
Not every object has a
planet. Placed top-level objects (pods, teleporters, containers) do; items inside inventories and child objects usually do not.
| Key | Meaning |
|---|---|
id |
Inventory id |
woIds |
Comma-separated world-object ids — the contents |
size |
Slot count |
demandGrps / supplyGrps
|
Logistics demand / supply groups |
priority |
Logistics priority (-3 … 3) — written only on logistics containers
|
An item is "in" an inventory when its world-object id appears in that inventory's woIds string.
That is the entire relationship; there is no back-pointer on the object.
The save's display name, its planetId, the game version string, the world seed, the game mode
and start-location labels, preInterplanetarySave, modded, the unlock switches
(unlockedSpaceTrading, unlockedTeleporters, unlockedDrones, …) and the difficulty modifiers
(modifierTerraformationPace, modifierPowerConsumption, modifierGaugeDrain, …).
A planet is written as a name (planetId, e.g. Prime) on players, terraforming entries and the
metadata — but as an integer hash in WorldObject.planet:
WorldObject.planet == PlanetHash.Of(planetId) // Unity's string GetStableHashCode
PCEdit.SaveFileHandler/PlanetHash.cs implements that hash, and it is what lets PCEdit say which
world an object — and therefore an inventory — belongs to. See
Worlds & Planets.
The game writes every decimal with a fractional part: 1.0, never 1. Round-tripping through a
plain .NET serializer would emit 1 and change the file. GameDecimalConverter forces N.0.
Every model carries a [JsonExtensionData] catch-all, so any key the game writes that PCEdit does
not model survives a load → save untouched. On WorldObject the custom converter goes further and
re-emits unknown keys in their original position.
This is what lets PCEdit open a save from a game build it has never seen without corrupting it.
| From | To | What changed |
|---|---|---|
| 2.008 | 2.102 (Skeo) |
One key: logisticsPaused added to section 0. Framing, section count and order, BOM behaviour, and every key in sections 1–9 unchanged — on both Steam and Game Pass. |
| 2.102 | 2.103 | Nothing. The same world saved by 2.102 and by 2.103 has identical keys in every section (Steam; no 2.103 Game Pass save has been checked). |
A version-added field is modelled as nullable, so a save written before the field existed does not gain it on save and keeps round-tripping byte for byte. Apply that rule to every future addition.
tools/save-diff/diff_saves.py produces this comparison for a new build — see
New Game Version.
Real saves live in the repo as test fixtures. All are byte-exact and marked -text in
.gitattributes so nothing normalises their line endings.
| Fixture | Game | Platform | BOM | Shape |
|---|---|---|---|---|
Standard-2.json |
2.008 | Steam | yes | Single planet (Prime) — the backward-compat regression |
mini-save.json |
2.008 | hand-authored | yes | The rare object shapes + a deliberately unknown key |
Humble-2.102.json |
2.102 | Steam | yes | Single planet (Humble) |
Humble-2.103.json |
2.103 | Steam | yes | The same Humble world, re-saved by 2.103 |
Interplanetary-2.102.json |
2.102 | Xbox / Game Pass | no | Prime + Aqualis + Selenea |
Interplanetary-2.102.json is a raw WGS blob exactly as the game wrote it — the only fixture that
proves the BOM-less path on real bytes, and the only multi-planet one. It must never gain a BOM.
See Testing for what each fixture is asserted to do.
PCEdit · unofficial fan tool for The Planet Crafter · GPL-3.0-or-later · back up your saves (disclaimer)
Using PCEdit
- Installation
- Save File Locations
- Quick Start
- Worlds & Planets
- Overview & Terraforming
- Inventories
- Logistics Editor
- Terra Tokens
- Teleport
- Languages & Settings
- FAQ & Troubleshooting
Development
- Architecture
- Save File Format
- Save File Library
- App Core
- Desktop UI
- Localization
- Item Catalog
- Testing
Shipping