Public API Built-Ins
PersistentAsset.BuiltIns, assemblies PersistentAsset.BuiltIns.*, one per optional module. Explained in Save on the device, Cloud & remote and What can be saved.Local managers
PrototypePersistenceManager
PM"Prototype". Zero-config local save, desktop/mobile/WebGL. No settings. Not consoles: those gate saving behind their own SDK, so they need a PersistenceManager subclass of your own.
FilePersistenceManager
PM"Local File". The manager to ship with: you name the file and the folder, and turn compression, encryption and the backup copy on.
| string FileName | The save file's name, subfolders included. |
|---|---|
| string HeaderText | An optional '#' comment line written at the top of the file. Players can edit or delete it; empty writes none. |
| string SaveFolder | Absolute folder this manager saves in, or a path starting with the folder token {Documents}, {UserProfile}, {AppData} or {LocalAppData}, which the machine running replaces with a folder of its own. Empty means the folder the platform hands the game. The SaveLocation subfolders are created under it as they are under the default. Desktop players and the editor only: elsewhere it refuses every operation, and a build for such a target fails while it is set. It applies in the editor too, so a manager using it shares its saves with an installed build of the game. |
| string FileExtension | Extension (no dot) this manager's files take. Empty means save. Part of the file name, so it decides which files a load finds. |
| Compression Compression / Encryption Encryption / SaveAnchor AnchorSaveTo | Storage protection; all three default to None. |
| bool DoBackup | Keep a second copy against crashes mid-save. On by default. |
| static string RuntimeSecret { get; set; } | Secret supplied at runtime for the RuntimeSecret anchor, forwarding to CheatProtectionUtility.RuntimeSecret: the two are one session-wide value. The setter throws ArgumentException when set to null/empty, and InvalidOperationException when set a second time with a different value. |
| static event Action<Object> OnTamperedLoaded | Raised after a load completed with tampered data now on the target (passed to the handler): the file failed its AppendHash integrity check, or carried the sticky mark of a previous detection. |
| static bool IsTampered(Object target) | Whether the data currently loaded on the target descends from a tampered save. The mark is sticky: it is rewritten into every save of the lineage (slot copies and Slots.SaveTo snapshots included), so it survives sessions; it resets on a clear, or when a load finds pristine data again. Throws ArgumentNullException for a null target. |
| static void MarkTampered(Object target) | Brand the current save lineage as tampered from your own cheat detection: the same sticky, authenticated mark an automatic detection writes, which a player cannot edit back out of a readable save. There is no way to unmark it, and OnTamperedLoaded is not raised. Throws ArgumentNullException for a null target, InvalidOperationException when no active file manager is in charge of it or its Encryption is not AppendHash. |
PlayerPrefsPersistenceManager
PM"Player Prefs". Saves through PlayerPrefs; synchronous; good for small data. The automatic load retry is off: a Player Prefs read never fails transiently. It reports no errors either, since Unity's own API has no channel for them: a save always comes back successful, and a write that did not survive reads later as a first run. A binary serializer is base64 encoded to fit, costing about a third more space: the one manager where a binary save is larger than a text one.
Remote managers
RemotePersistenceManager
PMabstractextendingBase for remote managers. ReadExecutionMode is always AsyncOnly and WriteExecutionMode is PreferAsync only while a cache is on, otherwise AsyncOnly: a remote load is never synchronous. Implement the async FetchRemote / StoreRemote / DeleteRemote primitives (each reports an Answer); inherit async, cache and push. Worked example: A custom remote manager.
| ConflictPolicy ConflictPolicy (virtual) | Only used while CacheMode enables a cache. What to do when a load finds this device and another one both saved the object: PreferNewest (default, keeps the most recent of the two and relies on the devices' clocks) or Ask, which raises Conflicts.OnAny and leaves the load undecided until Conflicts.Resolve answers. Not consulted for an IMergeable target. An inspector field on Server (HTTP) and Cloud Save (UGS); a custom remote manager opts in by overriding it with a serialized auto-property. See when two devices disagree. |
|---|---|
| static string RuntimeSecret { get; set; } | Secret supplied at runtime for the SaveAnchor.RuntimeSecret anchor, on the payload or on the cache. Forwards to CheatProtectionUtility.RuntimeSecret: that value, the file manager's and this one are one session-wide secret, reachable from whichever manager you are working in. The setter throws ArgumentException when set to null/empty, and InvalidOperationException when set a second time with a different value. |
| Compression Compression (virtual) | Defaults to None, and is breaking: saves written with the previous setting stop loading. Gzip is applied to the payload before Encryption and before the backend sees it, so a backend never compresses anything itself and the offline cache stores the compressed payload too. Opt in by overriding with a serialized auto-property, like the protection settings below. On the asynchronous write path the gzip and the encryption run on a background thread (the main thread on WebGL, which has none). |
| Encryption Encryption / SaveAnchor AnchorSaveTo (virtual) | Protection of the payload handed to the backend and read back from it, applied between the serializer and the primitives. Opt in by overriding with a serialized auto-property; a backend that does not override it shows no field and keeps the default. Encryption.AppendHash and SaveAnchor.TargetMachine are refused and the manager then refuses every operation. See Secure a remote save. |
| Encryption CacheEncryption / SaveAnchor CacheAnchorSaveTo (virtual) | Protection of the copy the offline cache keeps on the player's disk, independent of the pair above, so a payload readable to your backend can still be opaque locally. Both default to None, and they are used only while CacheMode enables a cache. SaveAnchor.TargetMachine is allowed here and is the strictest anchor available (the cache never leaves the device); Encryption.AppendHash is refused, as above. Changing either makes existing cache entries unreadable: one the server already holds is dropped and refetched, one holding unpushed work fails loudly instead of being discarded. |
| RemoteStatus Status { get; } / event Action<RemoteStatus> OnStatusChanged | Whether the offline cache is fully pushed (UpToDate), still holds changes to push (PendingChanges), or holds a change another device is blocking until a load settles it (Conflicted). Always UpToDate with no cache. |
| Task<RemoteStatus> PushPendingChangesAsync(CancellationToken = default) | Flush the cache to the server now. Never throws: a push that is cancelled or whose primitive fails reports through the status it returns. |
| enum Answer | How the remote responded, which the cache reasons about, so map yours exactly. Success: it landed, and a fetch returns the body byte for byte. NoSave: reachable, but nothing saved at this address. Corrupt: unreadable, a retry will not fix it. Error: the remote answered but the operation did not land, and a retry may help. Unreachable: the remote could not be contacted at all, the only answer an offline cache treats as offline. |
| WritePayloadSync / ClearPayloadSync / ReadPayloadAsync / WritePayloadAsync / ClearPayloadAsync (sealed) | Sealed by this base: a backend implements the asynchronous read, write and clear, and the cache drives the synchronous side. |
| bool SelfBoundsAsyncOperations (sealed) | Sealed on: with a cache on, the engine races the remote against the operation timeout and falls back to the cache, so it bounds itself. |
| static void ClearLocalCache(string managerName, string folder) | Delete one manager type's offline cache (backs a [ClearLocalData] editor cleaner). |
| Task<(Answer answer, byte[] body, string message)> FetchRemote(string slot, CancellationToken) (protected abstract) | Read the slot's payload from the server (the body is null unless the answer is Success). |
|---|---|
| Task<(Answer answer, string message)> StoreRemote(string slot, byte[] body, CancellationToken) (protected abstract) | Write the slot's payload to the server, encoded as your transport requires. |
| Task<(Answer answer, string message)> DeleteRemote(string slot, CancellationToken) (protected abstract) | Delete the slot's payload on the server. |
| RemoteCacheMode CacheMode { get; protected set; } / OfflineColdStart OfflineColdStart { get; protected set; } (virtual) | Offline-cache behavior; override to opt in (e.g. with [field: SerializeField]). |
| string SetupError (protected virtual) | A permanent fault message, such as a missing URL, which refuses every operation until it is fixed. null when there is none. |
| string LocalCacheFolder (protected virtual) | Name of the subfolder the offline cache is kept in. The type name by default. |
HttpPersistenceManager
PM"Server (HTTP)". Saves to your own server, or to a backend service, with a GET, a PUT and a DELETE at url/slot/id.
| string ReleaseUrl / DevelopmentUrl / Id | Endpoint, dev override, and save id. |
|---|---|
| string LoadUrlTemplate / SaveUrlTemplate / ClearUrlTemplate | Per-operation address, replacing url/slot/id. Supports {url}, {slot}, {id} and {token} (written back as its placeholder wherever an address is logged, so a failing request never carries a credential into a console); empty keeps the built-in scheme. |
| HttpVerb LoadVerb / SaveVerb / ClearVerb | Per-operation HTTP method (Get / Put / Delete by default). |
| string SaveBodyTemplate | JSON envelope a save is wrapped in, with {payload} (escaped for you; base64 when it has no text form, meaning compressed or from a binary serializer), plus {slot} and {id} for a service that wants the record named in the body too. Empty sends the payload verbatim. |
| string LoadBodyTemplate / ClearBodyTemplate | JSON body a load or a clear is sent with, for a service whose read or delete is an RPC rather than a plain GET or DELETE, with {slot} and {id} where it wants the record named. Empty sends no body, which is what a REST backend and a server of your own want. {payload} means nothing here: neither operation carries a save. |
| string ResponsePayloadPath | Dotted path the payload is read back from, for example data.Data.save.Value; a numeric step indexes an array, and a step takes {slot} and {id}, filled one step at a time so a slot holding a . names one member rather than splitting the path. |
| HttpNotFound NotFoundMeans | What a 404 to a load means, NoSave by default: NoSave (the address holds nothing, so the target starts on its defaults and a cached save for that slot is dropped) or Error (the load retries and a cached save is kept). Use Error only with a backend that signals an absent save some other way, since one that 404s for that too can then never finish a first load. |
| HttpEmptyAnswer EmptyAnswerMeans | What an answer that arrived cleanly but holds nothing at ResponsePayloadPath means, Corrupt by default: Corrupt reports it, so a path that does not fit the service is found rather than mistaken for a new player, and a cached save is kept and played from; NoSave reads it as a first launch, which is what the services answering 200 with an empty result need, at the cost of a mistyped path reading the same way and a cached entry for that slot being dropped. Only used while a payload path is set. |
| HttpHeader[] ExtraHeaders | Extra request headers; each value supports {token}, filled from Authorization. |
| string Authorization / Action<UnityWebRequest> ConfigureRequest | Runtime auth and per-request hook, this manager's own. Unset (null), each reads its Default below, so clearing Authorization on sign-out leaves the project-wide token going out: assign an empty string for "send none". |
| static string DefaultAuthorization / static Action<UnityWebRequest> DefaultConfigureRequest | What every HTTP manager that sets none of its own sends. Two managers pointed at different services each need their own, or the second signs its requests with the first one's credentials. Both are cleared when play mode ends, so a token never carries into the next session. |
| string GenerateAddress(string slot) (protected internal) / Task Send(UnityWebRequest, CancellationToken) (protected) / string ErrorText(UnityWebRequest) (protected) / string ErrorText(UnityWebRequest, string token) (protected) | Subclassing helpers to adapt the server contract. ErrorText writes the token back as {token} so a failing request never logs a credential, which is why it is an instance member rather than a static one. The one-argument form redacts against this manager's Authorization as it reads now; pass the token you built the address from where a refresh could land mid-request, or the older credential reaches the log in full. Send throws OperationCanceledException when the token is cancelled. |
|---|
CloudSavePersistenceManager
PM"Cloud Save (UGS)". Per signed-in player. Requires UGS init + sign-in. RegularSaveDelay defaults to 60 seconds, as on the HTTP manager.
| string Key | Cloud Save key (accepts the <slot> placeholder; only letters, digits, - and _ are valid, any other character is reported as invalid). |
|---|
Remote enums
enum| RemoteCacheMode | No Cache: every load and save reaches the server, and fails while it is unreachable. Server + Cache: loads go to the server when it answers, to the cache when it does not. Local Cache (Server Backup): the local save wins, and the server is a write-only backup read only when nothing is cached. |
|---|---|
| OfflineColdStart | Wait For Server (default) retries until it answers; Start Fresh starts on defaults. |
| RemoteStatus | UpToDate, PendingChanges, Conflicted. |
| ConflictPolicy | PreferNewest, Ask. |
RemoteSettings
staticThe project-wide settings of the remote managers, as set under Project Settings > Persistent Asset > Remote. Read here already clamped.
| float PushRetryDelay { get; } | Seconds between attempts to push pending offline-cache data to the server (clamped to ≥ 5). Defaults to 30. |
|---|---|
| int RequestTimeout { get; } | Seconds before a single remote request is forced to time out: a safety net against a server that accepts the connection and never answers, so it usually only bites when a manager's own Load / Save / Clear Timeout is higher or unbounded (whichever bound is reached first ends it). 0 disables it. Defaults to 120. |
| const float MinimumPushRetryDelay = 5 | Lower bound for PushRetryDelay. |
SteamCloudPersistenceManager
PM2.0"Steam Cloud". Saves through Steam Remote Storage, synchronously; the Steam client syncs the files. Needs Steamworks.NET, and your game to call SteamAPI.Init. Windows, macOS and Linux only. A plain PersistenceManager, not a RemotePersistenceManager: no offline cache, no payload encryption, and the Steam client does the syncing.
| string FileName | The cloud file a save is stored under, without extension (defaults to the unique id). The slot is part of it on a slotted manager. |
|---|---|
| const string FileExtension = ".sav" | The extension every cloud file it writes carries, leading dot included, unlike SaveFileUtility.FileExtension. |
| bool IsSteamReady | Whether the Steam API is initialized, so this manager can operate. While false every operation fails with one message. |
| bool IsCloudEnabledForAccount / IsCloudEnabledForApp | Whether Steam Cloud is on. With it off, saves succeed but stay on the machine. |
| bool TryGetQuota(out ulong totalBytes, out ulong availableBytes) | false while Steam is not initialized or the quota cannot be read. The player's cloud quota for this application. A write that would exceed it fails loudly. |
HttpVerb / HttpNotFound / HttpEmptyAnswer / HttpHeader
enumstruct2.0What the Server (HTTP) manager's Backend Service fields are made of.
| enum HttpVerb | Get, Post, Put, Delete, Patch. |
|---|---|
| enum HttpNotFound | NoSave, Error: what a 404 to a load is taken to mean. |
| enum HttpEmptyAnswer | Corrupt, NoSave: what an answer holding nothing at the payload path is taken to mean. |
| struct HttpHeader | Name and Value (a template supporting {token}), plus a constructor taking both. |
Other managers
PlatformPersistenceManager
PM2.0"Platform (Routing)". Saves through a different manager depending on where the game runs: it stores nothing itself and hands every operation to the route it resolved. Routes are authored in its inspector, each claiming platforms (and optionally a distribution) and owning the manager that stores there; the first route claiming the current platform wins, and one with no platform is the fallback. It keeps the serializer, the slot settings and the automatic load and save for the whole setup, and takes the execution modes, the timeouts and the storage from the route. With no matching route, persistence is disabled for the target and the reason is reported. See Save per platform.
| PersistenceManager ActiveManager { get; } | The manager this platform routes to, or null when nothing matches. |
|---|
SessionPersistenceManager
PM"Session (Memory)". Keeps the data in memory for the session and writes nothing to disk. Automatic load and save are forced on, whatever a target's policy asks for, and the load retry and the regular save are off.
TestPersistenceManager
PM"Test". A fake in-memory manager for seeing how the game behaves when saving misbehaves: force any operation to any outcome, and add a delay to it to try out loading screens, timeouts and cancellation. Assigning it always keeps an import copy of the setup it replaces, restored in one click from its inspector, and it never ships: a release build fails while one exists in the project, and a development build logs a warning. Worked example: Write a test manager.
| Inspector: Load Outcome / Save Outcome / Clear Outcome | What each operation does instead of reaching storage: Default runs it for real against the in-memory store, and the rest force the result. |
|---|---|
| Inspector: Load Delay / Save Delay / Clear Delay | Seconds each operation takes before it answers, for rehearsing a slow server against a real loading screen. |
| enum ForcedLoadOutcome | Default (run for real), Success, Invalid, Failure, Exception, Cancelled, Corrupt (an Invalid that also sets IsCorrupt). Declared in that order, and stored by number, so an existing asset keeps the outcome it was set to. |
| enum ForcedOutcome | Default, Success, Failure, Exception, Cancelled (save/clear; no Invalid). |
Serializers
UnityJsonSerializer
class"Unity JSON", the default serializer: serializes fields by Unity's own rules into readable JSON, and supports save versioning. Its inspector Format option is PrettyPrint, OneLine, or Obfuscated (compact base64 so players cannot casually read or edit it, obfuscation, not encryption).
| enum Format | PrettyPrint, OneLine, Obfuscated. |
|---|---|
| Format WriteFormat { get; } | The Format set on the asset. |
NewtonsoftJsonSerializer
class"Newtonsoft JSON". Serializes the union of what Unity serializes and what Newtonsoft serializes (dictionaries, properties, nullables, plain C# objects), and supports save versioning. Present only when the com.unity.nuget.newtonsoft-json package is installed. See What can be saved.
| enum Format | PrettyPrint, OneLine, Obfuscated. |
|---|---|
| Format WriteFormat { get; } | The Format set on the asset. |
| bool SharedReferences { get; } | Keeps an object referenced from several places as one object (and lets data refer back to itself), adding $id / $ref members. Turns SupportsNode off while it is on, so save upgrades and the conflict prompt's preview fields stop working: a repeat written as a placeholder would have an upgrade read bookkeeping where the values are. |
| bool PolymorphicTypeNames { get; } | Writes the concrete type of values in interface, abstract or base class fields. The type name becomes part of the save format, and a modified save can then name any type in your game. |
| IList<JsonConverter> Converters (static) | Converters your game adds for what this serializer cannot write by itself, an object created at runtime above all. One registered for a type wins over the package's own asset-reference handling of it. Register at start-up: which converter handles a type is decided the first time it is written. |
OdinDataSerializer
class"Odin". Serializes through Odin Serializer, with shared references and polymorphic types handled by Odin itself. Present only when Odin is in the project, either the serializer bundled with Odin Inspector or the free standalone Odin Serializer, which the package detects on its own. See What can be saved.
| enum Format | Json (readable, and the only one save upgrades can read old values from), Binary (smaller and faster, raw bytes). |
|---|---|
| Format WriteFormat { get; } | The Format set on the asset. |
MemoryPackDataSerializer
class2.0"MemoryPack". Raw bytes, with the readers and writers generated at compile time: no reflection at run time and no AOT step for an IL2CPP build, at the cost of marking every persistent type [MemoryPackable] partial and every asset member [MemoryPackAllowSerialize]. It also writes only public fields and settable public properties: a private [SerializeField] field the other serializers persist is left out with no error unless it carries [MemoryPackInclude]. A MemoryPack save is raw bytes, so save upgrades and field resets cannot read old values out of it. Present only when MemoryPack is in the project. See What can be saved.
It has no settings of its own.
MemoryPackScriptableObject
abstract2.0Derive from this instead of PersistentScriptableObject for a type serialized by MemoryPackDataSerializer. Deriving straight from the base class does not compile: MemoryPack needs every inherited member marked, and the base holds a PersistenceManager member inside the package that you cannot mark yourself. This class marks it for you and changes nothing else. Explained in What can be saved.
| PersistenceManager PersistenceManager { get; } | The base class member, re-declared only to carry [MemoryPackIgnore] so the generator leaves it out of the save. Same value. |
|---|
AssetFormatter<T>
sealed2.0Lets MemoryPack save a field pointing at a project asset: it writes the asset's registry id and looks the asset back up on load. The package ships one for Sprite, Texture2D, AudioClip, Material, Mesh, GameObject and ScriptableObject. Register your own for any other asset type, before the first load: MemoryPackFormatterProvider.Register(new AssetFormatter<AnimatorController>()). It has to match the field's declared type. An asset the registry no longer knows reads back as null.
Security
Encryption
enumExplained in Secure saves.
| None | No encryption. |
|---|---|
| AppendHash | Readable, with a secured hash: a modified save still loads but is permanently marked as tampered (see FilePersistenceManager.IsTampered). |
| Encrypt | Fully encrypted; a modified save cannot be loaded at all. |
Compression
enumExplained in Secure saves.
| None / Fast / Optimal | No compression, fast, or best (slower) gzip. |
|---|
SaveAnchor
enum [Flags]An anchor decides what fails the integrity check; Encryption decides the cost: Encrypt leaves the save unreadable, AppendHash loads it marked as tampered. Explained in Secure saves.
| None | Readable by any copy of the project. |
|---|---|
| TargetMachine | Fails its check on another machine. |
| FileLocation | Fails its check if moved, renamed or swapped. |
| RuntimeSecret | Fails its check without the runtime secret. |
| Everything | All locks. |
Utilities
SaveFileUtility
staticextendingLow-level file IO shared by the file-backed managers and the offline cache. You only need it to build your own file-based manager. Explained in Extend the package.
| const string FileExtension = "save" | Extension (no dot) every save file it reads and writes takes, unless one is passed for it. |
|---|---|
| bool SupportsCustomFolder | Whether this platform can read and write saves outside the folder it hands the game, which only the desktop players and the editor can. |
| string GetRootFolderPath(string root = null) / EnsureRootFolderExists(string root = null) | Root folder under which every save location lives (the second creates it). Pass a root to use that absolute folder instead of the platform one; null or empty is the platform one. |
| string GetSaveFolderPath(SaveLocation location, string root = null) / GetSaveFilePath(SaveLocation, string relativeFilePath, string root = null, string extension = null) | Resolve a location's folder, or a file path within it (GetSaveFilePath appends the extension, so pass relativeFilePath without one). Throws ArgumentOutOfRangeException for an undefined location. |
| byte[] Read(SaveLocation, string relativeFilePath, string root = null, string extension = null) / byte[] ReadHeader(SaveLocation, string, int maxBytes, string root = null, string extension = null) | Read a save file, or just its first bytes; null when it does not exist. Throw IOException when the file exists but cannot be read, and UnauthorizedAccessException without permission; ReadHeader throws ArgumentOutOfRangeException for a negative maxBytes. |
| void Write(SaveLocation, string relativeFilePath, string uniqueId, byte[] data, string root = null, string extension = null) / WriteAtomic(string path, string tempPath, byte[] data) | Throw IOException when the file cannot be written (disk full, destination held open) and UnauthorizedAccessException when it is read-only or unpermitted. Write a save file atomically (stage then swap, crash-safe). WebGL cannot swap: the data goes straight to the destination, so an interrupted write can leave it truncated. On a destination that refuses the swap (some network shares, some removable media) the previous save is staged aside by hand, under GetAsidePath. |
| byte[] ReadAside(SaveLocation, string relativeFilePath, string root = null, string extension = null, int maxBytes = 0) | Read the copy WriteAtomic parked aside, for the file at relativeFilePath. An interrupted by-hand swap leaves the save missing and this copy holding the previous one: a manager keeping a second copy of its own reads that instead, one keeping none reads this before reporting a first run. null when there is no such copy. Throws IOException and UnauthorizedAccessException as Read does. |
| string GetAsidePath(string path) | Where WriteAtomic parks the previous save while it stages the new one by hand. Nothing reads it, and a completed write removes it. A manager writing through WriteAtomic rather than Write deletes this alongside its own file when it clears storage, or a save it cleared stays readable beside it. |
| string GetStagedPath(string path, string uniqueId) | Where a write stages before it is swapped in. A manager staging its own writes deletes this alongside its file when it clears storage: an interrupted write leaves it holding the whole payload. |
| bool IsReservedFileName(string fileName) | Whether a name ends in one of the two above, which a save must not take: another manager writing the file it is named after would overwrite or delete it as a leftover of its own. |
| void Delete(SaveLocation, string relativeFilePath, string root = null, string extension = null, string uniqueId = null) | Throws IOException when the file is in use by another process, and UnauthorizedAccessException when it is read-only or unpermitted. Delete a save file (silent if absent), with the copies a write can leave beside it: pass the writing manager's uniqueId so the staged copy of an interrupted write goes too. |
CheatProtectionUtility
staticextendingEncrypt or hash save data with a key derived from the project secret (Project Settings > Persistent Asset > Security). Backs the FilePersistenceManager protection options, and the remote managers' payload and offline-cache protection. Explained in Extend the package.
| static string RuntimeSecret { get; set; } | Runtime secret mixed into the key for a SaveAnchor.RuntimeSecret anchor. The setter throws ArgumentException when set to null/empty, and InvalidOperationException when set a second time with a different value. |
|---|---|
| bool UsesRuntimeSecret(SaveAnchor anchorTo) | Whether the anchor effectively requires the runtime secret (the editor can opt out). |
| byte[] Encrypt(byte[] data, SaveAnchor anchorTo, string location) / byte[] Decrypt(byte[], SaveAnchor, string) | Encrypt/decrypt with an embedded integrity hash. Decrypt returns null when the integrity check fails (data modified, moved, or from another machine or project). Throws ArgumentNullException for null data, InvalidOperationException when the anchor needs an unset RuntimeSecret. |
| byte[] AppendSecuredHash(byte[], SaveAnchor, string, bool tampered = false) / bool CheckSecuredHash(byte[] dataWithHash, SaveAnchor, string, out byte[] originalData, out bool tampered) | Append/verify a tamper-evident hash (data stays readable). tampered writes/reads the tampered-lineage mark, covered by the hash itself so editing it in or out reads as freshly tampered; originalData is extracted even when the check fails, so a caller can carry a tampered payload onward. A CheckSecuredHash overload without the out tampered also exists. Same throws as Encrypt / Decrypt. |
GzipUtility
staticextendingGzip compress and decompress byte data. Empty data compresses to empty and decompresses back to empty; anything else must be one whole gzip stream, so a truncated one is refused instead of inflating in part. Explained in Extend the package.
| byte[] Compress(byte[] data, Compression compression) / Decompress(byte[] data, Compression compression) | Compress/decompress per a Compression level (None passes through). Throws ArgumentNullException for null data; Decompress throws IOException on a corrupt or truncated gzip stream (Unity's Mono runtime, not InvalidDataException) and InvalidDataException past the size ceiling. |
|---|---|
| byte[] Compress(byte[] data, CompressionLevel compressionLevel = Optimal) / Decompress(byte[] data) | Compress/decompress with a raw System.IO.Compression level. Throws ArgumentNullException for null data; Decompress throws IOException on a corrupt or truncated gzip stream (Unity's Mono runtime, not InvalidDataException) and InvalidDataException past the size ceiling. |
TaskUtility
staticextendingHelpers for asynchronous manager code: thread hopping and cancellation. Explained in Extend the package.
| ThreadType CurrentThreadType { get; } | Whether the caller is on the main or a background thread. |
|---|---|
| Task SwitchToMainThread() | Await to continue on the main thread. |
| Task StartNew(ThreadType, Action, CancellationToken = default, Action<AggregateException> = null) / Task<TResult> StartNew<TResult>(ThreadType, Func<TResult>, ...) | Run work on the chosen thread as a task. |
| Task<T> WithCancellation<T>(Task<T>, CancellationToken) / Task WithCancellation(Task, CancellationToken) | Make any task cancellable even when it ignores the token. Surfaces OperationCanceledException on cancel. |
| void Observe(Task task) | Observe an abandoned task's eventual fault (avoids UnobservedTaskException). |
SlotFormatUtility
staticextendingThe "<slot>" placeholder handling and the reversible file-path slot codec the file-backed managers share. Explained in Extend the package.
| const string Placeholder = "<slot>" | The token a manager replaces with the current slot in a user path, key or address. |
|---|---|
| string GenerateName(string customValue, string slot, string defaultName, params char[] separators) | Resolve a storage name: default under the slot, or the custom value with the slot placed/prepended. |
| bool Replace(ref string value, string replacement, params char[] separators) | Replace every placeholder, returning whether any was found. |
| string EncodeForFilePath(string slot) / DecodeForFilePath(string encoded) | Encode a slot into one safe, injective, reversible path segment (and back). |
| bool IsValidFilePath(string value, out string reason) | Whether a relative file path is valid on every platform. |
WebGLFileSync
staticextendingFlush the in-memory file system to IndexedDB on WebGL; call after writing or deleting save files. A no-op off WebGL. Explained in Extend the package.
| void RequestSync() | Flush pending file writes to persistent browser storage. |
|---|
SaveNodeJson
staticextendingReads JSON text into a SaveNode tree, to implement Serializer.ParseToNode in a JSON serializer (what lets save upgrades and field resets recover old values). Explained in Extend the package.
| SaveNode Parse(string json) | Parse JSON text into a tree, or null when it cannot be read (malformed, truncated, or nested past a safe depth). Never throws. |
|---|
FieldSnapshot
staticextendingCopies every data field of an object and puts it back. Capture before deserializing into a target and restore if that fails, so a failed load leaves the target exactly as it was, as Serializer's deserialize half requires. The copy is shallow. Explained in Extend the package.
| object[] Capture(object target) | Capture the current value of every data field of the target. A field the runtime refuses to read is logged and left out, and Restore then leaves it as it finds it rather than writing a null over it. Never throws. |
|---|---|
| void Restore(object target, object[] values) | Put back the values a Capture of the same object returned. Never throws. |
ThreadType
enum| Main / Background | The main thread (full Unity API) or a background thread (faster, most Unity API unavailable). |
|---|
SaveLocation
enum| Default / Backup / LocalCache | Subfolders of the save root (Application.persistentDataPath in a build, the project's UserSettings/Persistent Asset folder in the editor, or the folder a call names): the save folder, the backup copy, and the remote managers' offline cache. |
|---|