Editor/Core/MetaFileRules.cs
using System;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace TeamCreate;
/// <summary>
/// What a project file contributes to the baseline and the manifest. Everything is its own bytes except the engine's <c>.meta</c> files, which carry
/// bookkeeping the engine rewrites by itself: it sets <c>sceneCompileDirty</c> the moment a scene is edited. That flag says nothing about the project, but
/// hashed as raw bytes it made a collaborator's first edit change the baseline, so Save refused with "Source/assets changed during collaboration" and two
/// machines could disagree about whether they had the same project. It is left out; every other field of a .meta still counts.
/// </summary>
public static class MetaFileRules
{
private static readonly string[] IgnoredFields = { "sceneCompileDirty" };
/// <summary>The bytes to hash for a file. Anything that is not a readable JSON-object .meta file is returned exactly as given.</summary>
public static byte[] Canonical(string path, byte[] content)
{
if (content == null || string.IsNullOrEmpty(path) || !path.EndsWith(".meta", StringComparison.OrdinalIgnoreCase))
{
return content;
}
try
{
ReadOnlySpan<byte> body = content;
if (body.Length >= 3 && body[0] == 0xEF && body[1] == 0xBB && body[2] == 0xBF)
{
body = body.Slice(3);
}
if (!(JsonNode.Parse(body) is JsonObject document))
{
return content;
}
foreach (string field in IgnoredFields)
{
document.Remove(field);
}
// Sorted by field name so the order the engine happened to write them in does not matter, and compact so whitespace does not either.
SortedObject(document, out JsonObject sorted);
return Encoding.UTF8.GetBytes(sorted.ToJsonString(new JsonSerializerOptions { WriteIndented = false }));
}
catch (Exception ex) when (ex is JsonException || ex is InvalidOperationException || ex is ArgumentException)
{
return content;
}
}
private static void SortedObject(JsonObject source, out JsonObject result)
{
result = new JsonObject();
foreach (var pair in System.Linq.Enumerable.OrderBy(source, (System.Collections.Generic.KeyValuePair<string, JsonNode> x) => x.Key, StringComparer.Ordinal))
{
result[pair.Key] = pair.Value == null ? null : JsonNode.Parse(pair.Value.ToJsonString());
}
}
}