Skip to content

Save File Format

Valkerran edited this page Oct 2, 2026 · 4 revisions

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.

The file as a whole

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 byte-order mark matters

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.

The ten sections

# 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.

Section 0 — Unlocks

Terra token balances (terraTokens, allTimeTerraTokens), the comma-joined unlockedGroups list, the opened-instance seed and timer, and — new in 2.102 — logisticsPaused.

Section 1 — Terraformation

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.

Section 2 — Players

Identity (id, name, host), location (planetId, playerPosition, rotation), the four gauges (oxygen, thirst, health, toxicity), and per-player statistics such as totalCraftedObjects and totalTerraTokenEarned.

Section 3 — World objects

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:

  1. 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.
  2. Not every object has a planet. Placed top-level objects (pods, teleporters, containers) do; items inside inventories and child objects usually do not.

Section 4 — Inventories

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.

Section 8 — Metadata

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, …).

Planet identity: two encodings

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.

Number formatting

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.

Unknown keys are preserved, not dropped

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.

Game versions

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.

Reference fixtures

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.

Clone this wiki locally