🧰 Zion Tools
Unity Editor Asset Tools
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.