(Created page with "A '''save''' is a file that contains data about the current state of the game.")
 
No edit summary
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
A '''save''' is a file that contains data about the current state of the game.
<div class="infobox-wrap">
{| class="infobox"
|+ | '''Save file'''
|-
! scope="row" | Extension
| <code>.pc</code>
|-
! scope="row" | Folder
| <code>&lt;persistentDataPath&gt;/saves/</code>
|-
! scope="row" | Header
| Unity <code>JsonUtility</code> (<code>GameData</code>), one line
|-
! scope="row" | Body
| Newtonsoft JSON (<code>ContentData</code> aggregate)
|-
! scope="row" | File obfuscation
| XOR every character with <code>129</code>
|}
 
In '''[[PC Simulator]]''', a '''save''' (a '''save file''') is a persistent snapshot of a play session. Saves use the <code>.pc</code> extension, are stored under the application persistent data directory, and combine a small Unity JSON header with a larger Newtonsoft JSON body. The stored file is XOR-masked with a fixed key. [[Save Editor]] covers external editing; [[Main Menu]] covers listing and importing files.
 
Saves are written through <code>SaveManager</code> after <code>SaveManager.Loader</code> has been set (usually from the menu or an example load). Loading runs inside the gameplay scene and rebuilds money, time, difficulty, environment, the player transform, registered scene objects, and world items that implement <code>ISave</code>.
</div>
<div class="infobox-clear"></div>
 
== Purpose ==
 
A save records enough state to restore a room after exit: global session fields (money, timers, difficulty flags), the player pose, data from each configured <code>SceneObject</code>, and each world <code>Item</code> that participates in <code>ISave</code>. <code>SaveManager.LoadData()</code> consumes the static <code>SaveManager.Loader</code> instance prepared before the scene loads.
 
== Storage location and naming ==
 
<code>SaveUtility.GetFolderPath()</code> returns <code>Application.persistentDataPath + "/saves/"</code> and creates the directory if it is missing. The extension is <code>SaveUtility.extension</code>, defined as <code>".pc"</code>.
 
<code>SaveUtility.GetNewPath(string name)</code> places a file in that folder. If <code>name + ".pc"</code> exists, the routine appends <code> (n)</code> with increasing <code>n</code>. Names that already end with <code> (digits)</code> continue numbering from that index. Import uses this path to avoid overwriting existing files.
 
== On-disk format ==
 
=== Obfuscation ===
 
<code>SaveUtility.EncryptDecrypt</code> XORs each UTF-16 code unit with the integer <code>129</code>. The same function is used on read and write. This is reversible and is not strong encryption; it is adequate only as a light obfuscator.
 
=== Logical layout ===
 
After decryption, the text is split on the '''first''' newline:
 
# '''Header:''' one line of JSON for <code>GameData</code>, parsed with <code>JsonUtility.FromJson&lt;GameData&gt;</code>.
# '''Body:''' remaining text, deserialized with <code>JsonConvert.DeserializeObject&lt;ContentData&gt;</code>.
 
<code>DataLoader.WriteToFile</code> writes <code>JsonUtility.ToJson(GameData)</code>, one newline, then the body string, then applies XOR and writes with <code>File.WriteAllText</code>. A null header parse throws <code>Invalid file!</code>
 
== Header schema (<code>GameData</code>) ==
 
{| class="wikitable"
! Field !! Type !! Role
|-
| <code>version</code> || <code>string</code> || Set from <code>Application.version</code> on save.
|-
| <code>roomName</code> || <code>string</code> || Label in the [[Main Menu]] file list.
|-
| <code>coin</code> || <code>int</code> || Cash (<code>Main.Money</code>).
|-
| <code>room</code> || <code>int</code> || Added to menu <code>startRoomSceneIndex</code> when opening the scene.
|-
| <code>gravity</code> || <code>bool</code> || If false at load, <code>Physics.gravity</code> is set to zero.
|-
| <code>hardcore</code> || <code>bool</code> || Selects hardcore load behaviour.
|-
| <code>playtime</code> || <code>float</code> || <code>Main.playTime</code> (timed achievements).
|-
| <code>temperature</code> || <code>float</code> || AC-related value depending on mode.
|-
| <code>ac</code> || <code>bool</code> || AC power when not hardcore.
|-
| <code>light</code> || <code>bool</code> || Lamp switch; type default is true.
|-
| <code>sign</code> || <code>string</code> || Non-empty forces read-only example mode.
|}
 
== Body schema ==
 
<code>SaveManager.SaveData</code> serializes an object with:
 
* <code>playerData</code>: <code>PlayerData</code> from <code>Player.SavePlayer()</code>.
* <code>scene</code>: a <code>JObject</code> filled by each <code>SceneObject.ToData</code>.
* <code>itemData</code>: array of records with <code>spawnId</code>, <code>id</code>, position, rotation, and <code>data</code> for <code>ISave</code> components.
 
<code>spawnId</code> loads <code>Resources.Load("Components/" + spawnId)</code>. Items with <code>position.y &lt; -20</code> are omitted on save. Load failures (missing prefab or <code>FromData</code> exceptions) increment an error count.
 
=== <code>PlayerData</code> ===
 
Position <code>x</code>, <code>y</code>, <code>z</code>; body yaw <code>ry</code>; camera pitch <code>rx</code> (normalized from local euler angles). <code>LoadPlayer</code> applies position only if <code>y</code> is between <code>-100</code> and <code>100</code>.
 
== Save pipeline ==
 
On save, <code>SaveManager</code> copies version, money, play time, lamp state; if not hardcore, copies AC target and power from <code>AirConditioner.instance</code>; builds JSON content; assigns <code>Loader.Content</code>; calls <code>WriteToFile()</code>.
 
== Load pipeline ==
 
'''Read-only''' when <code>Loader.Path</code> is empty or <code>GameData.sign</code> is non-empty: sets <code>Main.example</code>, hides the configured save button.
 
Header restores money, play time, lamp, gravity, hardcore versus normal AC wiring. Empty <code>Loader.Content</code> activates a designer <code>preset</code> object and skips item deserialize.
 
<code>OnDestroy</code> resets default gravity and <code>AirConditioner.temperature</code> to normal constants.
 
== Integration ==
 
* [[Main Menu]]: file list, load, import.
* Examples: <code>StreamingAssets/Examples/*.pc</code> may load without a user path (read-only).
* [[Bitcoin]]: stored in <code>PlayerPrefs</code>, not in the inspected <code>GameData</code> fields.
 
== Compatibility ==
 
Import UI text references saves from version '''1.7.0''' and above. Invalid or truncated files throw or fail individual slots.
 
== See also ==
 
* [[PC Simulator]]
* [[Save Editor]]
* [[Main Menu]]
* [[Modding]]
* [[Decompile]]
* [[Secrets]]
 
== References ==
 
* <code>SaveUtility.cs</code>, <code>DataLoader.cs</code>, <code>GameData.cs</code>, <code>ContentData.cs</code>, <code>ItemData.cs</code>, <code>PlayerData.cs</code>, <code>SaveManager.cs</code>, <code>ISave.cs</code>, <code>SceneObject.cs</code>, <code>Player.cs</code>

Latest revision as of 18:29, 22 March 2026

Save file
Extension .pc
Folder <persistentDataPath>/saves/
Header Unity JsonUtility (GameData), one line
Body Newtonsoft JSON (ContentData aggregate)
File obfuscation XOR every character with 129

In PC Simulator, a save (a save file) is a persistent snapshot of a play session. Saves use the .pc extension, are stored under the application persistent data directory, and combine a small Unity JSON header with a larger Newtonsoft JSON body. The stored file is XOR-masked with a fixed key. Save Editor covers external editing; Main Menu covers listing and importing files.

Saves are written through SaveManager after SaveManager.Loader has been set (usually from the menu or an example load). Loading runs inside the gameplay scene and rebuilds money, time, difficulty, environment, the player transform, registered scene objects, and world items that implement ISave.

Purpose

A save records enough state to restore a room after exit: global session fields (money, timers, difficulty flags), the player pose, data from each configured SceneObject, and each world Item that participates in ISave. SaveManager.LoadData() consumes the static SaveManager.Loader instance prepared before the scene loads.

Storage location and naming

SaveUtility.GetFolderPath() returns Application.persistentDataPath + "/saves/" and creates the directory if it is missing. The extension is SaveUtility.extension, defined as ".pc".

SaveUtility.GetNewPath(string name) places a file in that folder. If name + ".pc" exists, the routine appends (n) with increasing n. Names that already end with (digits) continue numbering from that index. Import uses this path to avoid overwriting existing files.

On-disk format

Obfuscation

SaveUtility.EncryptDecrypt XORs each UTF-16 code unit with the integer 129. The same function is used on read and write. This is reversible and is not strong encryption; it is adequate only as a light obfuscator.

Logical layout

After decryption, the text is split on the first newline:

  1. Header: one line of JSON for GameData, parsed with JsonUtility.FromJson<GameData>.
  2. Body: remaining text, deserialized with JsonConvert.DeserializeObject<ContentData>.

DataLoader.WriteToFile writes JsonUtility.ToJson(GameData), one newline, then the body string, then applies XOR and writes with File.WriteAllText. A null header parse throws Invalid file!

Header schema (GameData)

Field Type Role
version string Set from Application.version on save.
roomName string Label in the Main Menu file list.
coin int Cash (Main.Money).
room int Added to menu startRoomSceneIndex when opening the scene.
gravity bool If false at load, Physics.gravity is set to zero.
hardcore bool Selects hardcore load behaviour.
playtime float Main.playTime (timed achievements).
temperature float AC-related value depending on mode.
ac bool AC power when not hardcore.
light bool Lamp switch; type default is true.
sign string Non-empty forces read-only example mode.

Body schema

SaveManager.SaveData serializes an object with:

  • playerData: PlayerData from Player.SavePlayer().
  • scene: a JObject filled by each SceneObject.ToData.
  • itemData: array of records with spawnId, id, position, rotation, and data for ISave components.

spawnId loads Resources.Load("Components/" + spawnId). Items with position.y < -20 are omitted on save. Load failures (missing prefab or FromData exceptions) increment an error count.

PlayerData

Position x, y, z; body yaw ry; camera pitch rx (normalized from local euler angles). LoadPlayer applies position only if y is between -100 and 100.

Save pipeline

On save, SaveManager copies version, money, play time, lamp state; if not hardcore, copies AC target and power from AirConditioner.instance; builds JSON content; assigns Loader.Content; calls WriteToFile().

Load pipeline

Read-only when Loader.Path is empty or GameData.sign is non-empty: sets Main.example, hides the configured save button.

Header restores money, play time, lamp, gravity, hardcore versus normal AC wiring. Empty Loader.Content activates a designer preset object and skips item deserialize.

OnDestroy resets default gravity and AirConditioner.temperature to normal constants.

Integration

  • Main Menu: file list, load, import.
  • Examples: StreamingAssets/Examples/*.pc may load without a user path (read-only).
  • Bitcoin: stored in PlayerPrefs, not in the inspected GameData fields.

Compatibility

Import UI text references saves from version 1.7.0 and above. Invalid or truncated files throw or fail individual slots.

See also

References

  • SaveUtility.cs, DataLoader.cs, GameData.cs, ContentData.cs, ItemData.cs, PlayerData.cs, SaveManager.cs, ISave.cs, SceneObject.cs, Player.cs