Knowledge base

Save files

Research in progress: some of this may be wrong. These notes record what XF Studio’s research has worked out so far about how Cyberpunk 2077 and its modding frameworks behave. Most of it comes from reading source code and game resources offline; not all of it has been confirmed in the running game, and claims graded hypothesis are unproven. If you spot a mistake or know more, please open an issue or suggest an edit.

Evidence grades

source
Read in engine/framework/tool source or decompiled data.
resource
Observed in extracted game or mod resources.
wiki
Documented in the Modding Docs, text or image.
runtime
Observed in the running game.
hypothesis
Not yet established.

Maturity: Draft. Consolidated on 27 September 2026 (with the Save Explorer's measurements added the same day) from WolvenKit's save code, the scripting RTTI dump, Codeware, EquipmentEx, Cyber Engine Tweaks and redscript source, the Modding Docs, and read-only structural probes of three private 2.31 saves (save version 269), which printed layout, class and field names, counts and sizes only. Nothing on this page has runtime evidence except where marked. Evidence grades follow the knowledge rules: source engine, framework or tool source; resource bytes of real saves or game files; wiki Modding Docs; runtime running game; [offline] measured by our own probes; hypothesis not yet established. The evidence, the node-by-node catalogue and the editor design are in the save editor design.

This page answers four questions for XF Studio agents:

  1. How is a save laid out, and which parts hold what?
  2. Which parts can be decoded generically, from type information, and which need a hand-written layout?
  3. Where do mods keep their data, in the save or elsewhere?
  4. What must an editor preserve so the game still accepts the save?

Evidence snapshot

SourceVersionWhat it established
WolvenKitcommit 11720772 (GPL-3.0; studied only): WolvenKit.RED4/Save/ (reader, writer, node writer, 56 node parsers, hash helper), Archive/IO/RedPackageReader*.csContainer, node dispatch, the bespoke node layouts, the hash-keyed persistency stream, how unknown classes are handled
Scripting RTTI dumpred-dump-json at a8e52990 (exported by psiberx, before 2.31)Type names, enum names and members, PlayerDevelopmentData fields
Codeware613a1cb8 (MIT)Where script services and dynamic entities persist
EquipmentEx3208ff4c (MIT)A script mod's persistent fields, including a mod enum
Cyber Engine Tweaks9a8522f (MIT), src/scripting/LuaSandbox.cppCET mods keep their state in their own db.sqlite3
Modding Docsbe2f44ee: CyberCAT page (manavortex), save file page (darkcart)Editor version dependence, the risk of editing quest facts, the header and tables
Three private 2.31 saves169, 330 and 336 nodesEverything marked resource or [offline]
Two older private 2.31 saves5,410 and 6,658 nodesThe Save Explorer's measurements at scale (§3)
redscript scc3ca666c (MIT), crates/scc/core/src/settings.rsThe modded script bundle is written beside the game's final.redscripts

1. The container source resource

A save is a folder under %USERPROFILE%\Saved Games\CD Projekt Red\Cyberpunk 2077\ holding sav.dat, metadata.9.json and screenshot.png.

Diagram. Its boxes and arrows are listed here as text; GitHub draws it as a flowchart. View the drawn diagram on GitHub.

Arrows

  1. Header arrow to Chunk table
  2. Chunk table arrow to LZ4 chunks
  3. LZ4 chunks arrow to Node table
  4. Node table arrow to Footer

Boxes

Header
CSAV, save version,; game version, archive version
Chunk table
FZLC, used count,; offset / stored / expanded
LZ4 chunks
4ZLX blocks of 256 KiB,; one expanded stream
Node table
EDON: name, next ID,; child ID, offset, size
Footer
node-table offset, ENOD
Mermaid source
flowchart LR
  H["Header<br/>CSAV, save version,<br/>game version, archive version"] --> T["Chunk table<br/>FZLC, used count,<br/>offset / stored / expanded"]
  T --> C["LZ4 chunks<br/>4ZLX blocks of 256 KiB,<br/>one expanded stream"]
  C --> N["Node table<br/>EDON: name, next ID,<br/>child ID, offset, size"]
  N --> F["Footer<br/>node-table offset, ENOD"]
  • The expanded stream is cut into fixed-size chunks with no regard for node boundaries; node offsets count the header and table as if they were part of the stream. Details: save write-back design §2.
  • Nodes form a tree of named nodes, at most two levels deep in 2.31 saves resource. A node's ID is its position in the table, and its data starts with that ID as a u32. A parent's size includes its children (questSystem → FactsDB → ten FactsTable nodes; inventory → one itemData per item).
  • No integrity data: no checksum, hash or signature [source: WolvenKit reader and writer].
  • metadata.9.json is the load menu's summary (life path, genders, level, attributes and skills, location, player position, quests, a few facts, isModded, DLC ids, fileSize). It also holds the platform user name, so it is personal. fileSize must equal the length of sav.dat resource.
  • Layout by position [resource: one 2.31 save]: the type database and the 5.5 MB world persistency node are at the front of the stream; inventory, the journal, script systems, stats, facts, the creator node and the wardrobe are all in the last three of 32 chunks. Editing one of those re-compresses little; editing world persistency re-compresses everything.

2. Three encodings

Every node is one of three kinds source [offline]:

EncodingNodesSelf-describing?Decode
Object packageScriptableSystemsContainer (every script system, vanilla and modded) and nine native systems: StatsSystem, StatPoolsSystem, godModeSystem, tierSystem, scanningController, MovingPlatformSystem, RenderGameplayEffectsManagerSystem, GuardAreaManager, PreventionSpawnSystemYes: class names, field names, stored types and offsets are written in the packageGeneric; enum-typed fields need type information (§3)
Hash-keyed persistency streamPersistencySystem2: the persistent states (…PS classes) of devices, doors, vehicles, containers and other world objects, 55,000–95,000 entriesPartly: each property is a name hash, a type hash and a value; each entry has a sizeGeneric with the save's own type database (§3)
Bespoke binaryEverything else: the creator node, inventory/itemData, FactsDB/FactsTable, JournalManager, WardrobeSystem*, world, AI, loot, time and UI nodesNoA hand-written codec per node; WolvenKit's has many "unknown" fields; nodes without one are kept as raw bytes

Object packages share the layout of the compiledData packages in .ent and .app resources (archive format): ScriptableSystemsContainer carries a u32 CRUID count and CRUIDs after the section offsets (the save variant worn clothing §2.5 describes), the nine native-system packages carry none [offline]. A field at its default value is not written: a Corporate V's PlayerDevelopmentData has no lifePath field, a Nomad V's has one resource.

Bespoke nodes that matter most to players [source: WolvenKit]:

  • Inventory: per inventory (u64 ID), items whose ItemID is a TweakDBID, a u32 seed, a structure byte, a u16 unique counter and a flags byte, then a quantity and, for items with parts, a recursive list of attached parts. UniqueItemCounter keeps item IDs unique.
  • Facts: each FactsTable is a VLQ count, then all fact-name hashes (FNV-1a 32; the names are not stored), then all values (u32), sorted by hash.
  • The creator node: fully decoded, see save import and the CC file chain §7.

3. The save's own type database [offline]

TypeDatabase_v2 records the schema of the script and persistency data the save holds. No tool we know decodes it; measured layout:

PartLayout
Headeru32 node ID, u32 body size, u32 3, u64 unknown, three u32 counts: types T, an unknown section B, properties P (890, 616 and 8,893 in one save; 32 + 20T + 8B + 12P is the node size exactly)
Types, 20 bytes eachu64 FNV-1a 64 of the type name, sorted ascending; u64 hash of the element or pointee type (array:X, handle:X) or 0; u16 flags; u16 count. Flag bits 4–7 are the RTTI type kind (0 name, 1 fundamental, 2 class or struct, 3 array, 4 simple, 5 enum, 6 static array, 7 native array, 9 handle); bits 12–15 the byte size of fixed-size values
Unknown, 8 bytes each–
Properties, 12 bytes eachu64 FNV-1a 64 of the property name; u32 whose low 16 bits index the type table

It lists script-system classes, including mod classes (EquipmentEx.OutfitState) and their enums (gamedataEquipmentArea has kind 5), and the persistency classes; it does not list the nine native-system package classes. 728 of its 890 type hashes and 8,286 of its 8,893 property hashes match names in the vanilla RTTI dump; the rest are script, mod or newer types.

What that makes possible [offline, three saves]:

  • All objects of ScriptableSystemsContainer, about 60 mod classes among them, decode with enum information taken only from the save itself; without any enum information 26 of 451 fail.
  • The native-system packages decode once the vanilla RTTI's enum names are added: 472 of 472 objects in one save.
  • 99.4–99.7 % of non-empty PersistencySystem2 entries walk exactly to their end from the type database alone (99.1–99.3 % in two larger, older saves); static and native arrays are the known gaps, and every entry stays preservable byte for byte through its size. A top-level entry may end with a zero name hash as its last 8 bytes, as a nested class does [offline: about 1,450 entries a save].
  • Opaque values that stay bounded and preservable: NodeRef, static arrays and DataBuffer (stat modifiers, which WolvenKit reads with a bespoke layout).

A hash becomes a name by hashing candidate names (the vanilla RTTI, the installed script bundle) and looking them up. A script field is spelled m_name in the bundle and persisted as name: counting that, the installed bundle names about 92 % of a save's property hashes and 95 % of its world-object classes [offline]. With redscript installed, the bundle the game runs is r6/cache/final.redscripts.modded (under MO2, the copy in the overwrite folder or in the highest-priority mod that has one) [source: redscript scc] resource.

4. Where mods keep their data

MechanismStored inGrade
redscript persistent fields of a ScriptableSystem (EquipmentEx's OutfitSystem.m_state → OutfitState → outfits and parts)ScriptableSystemsContainer, as ordinary objects (the m_ prefix dropped), described by the save's type databasesource [offline]
persistent fields of game objects' persistent statesPersistencySystem2source
@addField persistent fields a mod adds to a vanilla persistent state (Eviction Notice's three fields on ApartmentScreenControllerPS)PersistencySystem2, with that device's statesource (quests and story §6.1)
Quest facts a mod sets (every quest mod's flags and counters)questSystem → FactsDB, keyed by the FNV-1a 32-bit hash of the namesource (quests and story §2.4)
Codeware dynamic entities with persistState or persistSpawnPersistencySystem2, as App.DynamicEntitySystemPS[source: Codeware DynamicEntitySystem.cpp]
Codeware ScriptableService persistent fieldsNot the save: red4ext/plugins/Codeware/Persistent/ScriptableServiceContainer.dat, shared by all playthroughs[source: ScriptableServiceContainer.cpp; wiki Home.md]
CET Lua modsNot the save: each mod's db.sqlite3 and files. psiberx's CET Kit GameSession joins a per-save Lua file to its save through a session-key fact (_psxgs_session_key), as Stock Market and News System does[source: CET LuaSandbox.cpp; Stock Market modules/external/GameSession.lua]
TweakXL and ArchiveXL additionsNot the save; the save references their records (TweakDBIDs) and resources (hashes)source

So a save's mod data can be read with no per-mod schema: the package names every class and field, and the save's type database settles which types are enums. Readable labels for hashes and enum members can come from the user's installed r6/cache/final.redscripts, which holds every compiled script class, field (with its persistent flag) and enum, vanilla and modded [source: redscript crates/io]. What happens to a removed mod's data on the next save is untested hypothesis.

5. Editing safely

  • The game validates nothing we know of when loading, beyond the structure being readable [hypothesis: no load-time checks found in source or tools]. Mistakes surface later: CyberCAT's page warns that an edited save can load and still be broken, and that a quest fact changed at the wrong moment can stall a quest for good, with no fix but an older save wiki.
  • Save editors are version-bound. CyberCAT lists which of its versions match which game versions wiki; a writer should accept only the save and game versions it was tested with.
  • Everything is cross-referenced by ID: node IDs are table positions written into each node, items by ItemID (with the unique counter), equipment by item, stats by object ID, package handles by chunk index, persistency entries by persistent ID, dynamic entities by IDs from DynamicEntityIDSystem source. Inserting a node renumbers every later node.
  • Defaults are omitted, so setting a value to its default removes a field and setting an absent one inserts it; both re-lay out the package resource.
  • The Studio's rules for writing (new save folder only, minimal diff, identity gates, only types the save already knows, value edits before structural ones, facts read-only by default) are in the save editor design §7 and the write-back design. Structural changes such as adding items are safer through the game's own systems over the runtime bridge [source; not built].

6. What XF Studio reads today source

  • The container codec (engines/save/): header, LZ4 chunks, node table and tree, read-only.
  • The generic object layer (engines/red-object/): the save's type database; the type oracle (the save's database, then the shipped engine type list, then names from the installed scripts and the save's own packages); one package reader, shared with the resource reader; and the persistency walker.
  • save-reader.ts: the creator node, byte for byte, over the container codec (save import). save-loadout.ts: the vanilla equipment loadout and the active wardrobe set (worn clothing §2.6), with its enums answered by the save's own type database, and, since 30 September, the arm cyberware area's (ArmsCW) active item, read the same way (SavedLoadout.arms), whose holster state the arms draw (body rendering §1.1).
  • The Save Explorer (a Studio module, read-only): the player's saves listed automatically, every node with its size and decode status, package objects and world objects inspected with names by source, and script mods' data grouped by namespace. Bespoke nodes (inventory, facts, journal, wardrobe) are shown as bytes, and nothing writes. Status and measurements: save editor design, phase 1.
  • What bounds a damaged or hostile save [offline]: declared chunk sizes against stored bytes (LZ4 expands at most 255 times) before anything is allocated; package names inside the name data, at most 1 MB decoded a package and 8 MB a save (a 2.31 save's ten packages spell out 19 KB); at most 50,000 values a world-object entry (the largest of a 2.31 save's 99,697 decodes to 458); at most 5,000 fields an inspection; script-bundle names once per offset, at most 32 MB (a 2.31 install's 96,452 CNames take 2.2 MB). Measured on private saves and an install's bundle, counts only.

Open questions

  1. Where is the authoritative player position (the metadata copy serves the load menu)?
  2. What are the type database's 8-byte section, the high bits of each property record and the header's u64?
  3. Does the game re-save a removed mod's classes, drop them, or fail?
  4. Does the game read TypeDatabase_v2 when loading, and what happens if a package uses a type it doesn't list?
  5. How many fact names can be recovered by hashing names found in the installed quest resources and scripts?
  6. Which nodes does the game rewrite on every save even when nothing changed (the baseline an editor's diff is judged against)?

In-game test asks

Batched in the save editor design §11: a read cross-check of offline values against the bridge's live reads, a no-change container rewrite, a scalar edit in a script system, a field insertion, and a mod-data edit (an EquipmentEx outfit name), each followed by the game's own re-save.

Save editor design · Save write-back design · Save import · Worn clothing · CC file chain · Archive and resource formats · Runtime access

Corrections and credit

This page is generated from knowledge/save-files.md in the repository. If something here is wrong or missing, please open an issue or suggest an edit; links to sources, captures or tests help most.

It builds on the Cyberpunk 2077 modding community’s work. The inline citations link to the wiki pages, mods, tools and repositories each finding came from, and the credits acknowledge everyone we learned from. Citing a source does not mean its authors endorse these notes.