ZionGames Docs

Home / Zion Tools

🧰 Zion Tools

Unity Editor Asset Tools

Adding a Custom Format

Implement ISaveFormat to teach the inspector a format it doesn't know - binary protocols, MessagePack, Protobuf, or your own layout.

Interface shape

public sealed class MyBinaryFormat : ISaveFormat
{
    public string DisplayName => "My Binary";
    public int Order => 20;

    public bool CanParse(SaveEntry entry, byte[] payload) => /* sniff header */;
    public SaveNode Parse(byte[] payload) { /* build SaveNode tree */ }
    public byte[] Serialize(SaveNode root) { /* serialise tree */ }
}

Worked example - a magic-byte binary layout

Suppose your game writes a binary save: a 4-byte magic MGS1, an int32 version, a length-prefixed string name, then int32 level and gold.

using System.IO;
using Zion.SaveInspector.Editor;

public sealed class MyBinaryFormat : ISaveFormat
{
    public string DisplayName => "MyGame Binary v1";
    public int Order => 20; // tried after JSON but before raw text

    public bool CanParse(SaveEntry entry, byte[] payload)
    {
        return payload != null && payload.Length >= 4
            && payload[0] == 0x4D && payload[1] == 0x47   // 'M','G'
            && payload[2] == 0x53 && payload[3] == 0x31;  // 'S','1'
    }

    public SaveNode Parse(byte[] payload)
    {
        var root = SaveNode.NewObject("(root)");
        using var ms = new MemoryStream(payload);
        using var r  = new BinaryReader(ms);

        r.ReadBytes(4); // magic
        root.Children.Add(SaveNode.NewNumber("version",    r.ReadInt32()));
        root.Children.Add(SaveNode.NewString("playerName", r.ReadString()));
        root.Children.Add(SaveNode.NewNumber("level",      r.ReadInt32()));
        root.Children.Add(SaveNode.NewNumber("gold",       r.ReadInt32()));
        return root;
    }

    public byte[] Serialize(SaveNode root)
    {
        using var ms = new MemoryStream();
        using var w  = new BinaryWriter(ms);

        w.Write(new byte[] { 0x4D, 0x47, 0x53, 0x31 });

        // Look up each field by name so the order matches the original layout
        // regardless of how the user has folded the tree.
        int     version    = (int)FindNumber(root, "version");
        string  playerName =      FindString(root, "playerName");
        int     level      = (int)FindNumber(root, "level");
        int     gold       = (int)FindNumber(root, "gold");

        w.Write(version);
        w.Write(playerName ?? string.Empty);
        w.Write(level);
        w.Write(gold);

        return ms.ToArray();
    }

    private static double FindNumber(SaveNode root, string name)
    {
        var n = root.Children.Find(c => c.Name == name);
        return n != null ? n.NumberValue : 0;
    }

    private static string FindString(SaveNode root, string name)
    {
        var n = root.Children.Find(c => c.Name == name);
        return n?.StringValue;
    }
}

Things worth noticing

  • Order = 20 runs before RawTextFormat (which is int.MaxValue) but after the built-in JSON (which is 10). With the magic-byte check, JSON rejects the payload, your format claims it, and Raw text never runs.
  • Serialize reads children by name rather than position - the safer pattern, since users can rearrange or collapse the tree without breaking output order.
  • Number nodes are stored as double. Cast back to int when serializing to integer fields, otherwise you'll write a BinaryWriter.Write(double) by accident.
  • Once the format compiles, diff, schema validation, search and clipboard all work on your binary saves too 1.1 - they operate on the parsed tree, not the raw bytes.
Continue to Tutorial.