🧰 Zion Tools
Unity Editor Asset Tools
Overview
Animation Diagnostics
Zion Scene Backup
Zion Save Inspector
Zion Dependency Graph
Zion Asset Auditor
Overview
Installation
Quick Start
Concepts
Window Reference
Editing
Tree Search
Save Diff
Schema & Reports
Built-in Providers
PlayerPrefs
Custom Source
Custom Format
Tutorial
API Surface
Save Safety
Troubleshooting
Adding a Custom Source
Implement ISaveSource to add any storage backend - ScriptableObject snapshots, cloud saves, in-memory test fixtures. Drop the class in an editor assembly with a public parameterless constructor and it auto-discovers on the next domain reload.
Interface shape
public sealed class PlayerPrefsSource : ISaveSource
{
public string DisplayName => "PlayerPrefs";
public int Order => 5;
public IEnumerable<SaveEntry> List() { /* enumerate your keys */ }
public byte[] Load(SaveEntry e) { /* return raw bytes for the key */ }
public void Save(SaveEntry e, byte[] payload) { /* write back */ }
public void Delete(SaveEntry e) { /* delete key */ }
public void Reveal(SaveEntry e) { /* no-op or focus settings */ }
}
Worked example - PlayerPrefs via a manifest key
A stripped-down teaching version of the shipping source. The same pattern applies to any custom backend.
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using Zion.SaveInspector.Editor;
public sealed class PlayerPrefsSource : ISaveSource
{
public string DisplayName => "PlayerPrefs";
public int Order => 5;
private const string ManifestKey = "ZionSaveInspector.Manifest";
public IEnumerable<SaveEntry> List()
{
var manifest = PlayerPrefs.GetString(ManifestKey, string.Empty);
if (string.IsNullOrEmpty(manifest)) yield break;
foreach (var key in manifest.Split(';'))
{
if (string.IsNullOrEmpty(key) || !PlayerPrefs.HasKey(key)) continue;
var value = PlayerPrefs.GetString(key, string.Empty);
yield return new SaveEntry
{
DisplayName = key,
Identifier = key,
SizeBytes = Encoding.UTF8.GetByteCount(value),
LastModified = System.DateTime.Now, // PlayerPrefs has no timestamps
SourceTypeName = nameof(PlayerPrefsSource),
FormatHint = value.TrimStart().StartsWith("{") ? "json" : null
};
}
}
public byte[] Load(SaveEntry entry)
=> Encoding.UTF8.GetBytes(PlayerPrefs.GetString(entry.Identifier, string.Empty));
public void Save(SaveEntry entry, byte[] payload)
{
PlayerPrefs.SetString(entry.Identifier, Encoding.UTF8.GetString(payload));
PlayerPrefs.Save();
}
public void Delete(SaveEntry entry)
{
PlayerPrefs.DeleteKey(entry.Identifier);
// Also strip the key from the manifest so it doesn't reappear.
var manifest = PlayerPrefs.GetString(ManifestKey, string.Empty);
var keys = manifest.Split(';').Where(k => k != entry.Identifier);
PlayerPrefs.SetString(ManifestKey, string.Join(";", keys));
PlayerPrefs.Save();
}
public void Reveal(SaveEntry entry)
{
Debug.Log($"PlayerPrefs key: {entry.Identifier}");
}
}
After Unity recompiles, the source appears in the Source dropdown. Any registered key shows up as a save entry, and if the value is JSON the existing JsonSaveFormat parses it just like a file.
Opting in to backup diffs - ISaveBackupProvider 1.1
Additionally implement ISaveBackupProvider and the inspector's Compare > With Backup action lights up for your source, exactly like the built-ins:
public sealed class MyCloudSource : ISaveSource, ISaveBackupProvider
{
// ... ISaveSource members ...
public bool TryLoadBackup(SaveEntry entry, out byte[] payload,
out string backupLabel)
{
payload = LoadPreviousRevision(entry.Identifier);
backupLabel = "previous cloud revision";
return payload != null;
}
}
- Return
falsewhen no backup exists - the menu item shows as disabled. backupLabelappears in the diff window title so the user knows what they're comparing against.- The interface is opt-in; sources without it simply don't offer the menu item. Nothing else changes.
Continue to Custom Format.