Reader for engine KV3 v2 LZ4-backed serialized mesh metadata. It decompresses optional LZ4 payloads, parses typed KV3 binary sections into runtime objects, and extracts the mesh bone name list from m_bones entries.
using System;
using System.IO;
using System.Text;
namespace CharacterEditor;
/// <summary>Reads the mesh-local bone order in the engine's SaveToVmdl metadata.</summary>
internal static class SerializedMeshBones
{
// The engine's own serializer currently emits KV3 v2 with LZ4. This reader
// consumes that output, never arbitrary on-disk compressed clothing files.
public static string[] Read(byte[] block)
{
using var r = new BinaryReader(new MemoryStream(block));
if (r.ReadUInt32() != 0x4b563302)
throw new InvalidDataException("Unsupported serialized mesh metadata version.");
r.ReadBytes(16);
int compression = r.ReadInt32();
r.ReadInt32();
int bytes = r.ReadInt32(), ints = r.ReadInt32(), doubles = r.ReadInt32();
r.ReadInt32();
r.ReadInt32();
int size = r.ReadInt32(), packed = r.ReadInt32();
r.ReadInt32();
r.ReadInt32();
if (size < 0 || size > 32000000)
throw new InvalidDataException("Invalid mesh metadata size.");
var payload = r.ReadBytes(packed);
var data = compression == 0 ? payload : compression == 1 ? Expand(payload, size) : throw new InvalidDataException("Unsupported mesh metadata compression.");
var p4 = (bytes + 3) & ~3;
var p8 = (p4 + ints * 4 + 7) & ~7;
var stringsAt = p8 + doubles * 8;
int strings = BitConverter.ToInt32(data, p4);
var text = new string[strings];
int cursor = stringsAt;
for (int i = 0; i < strings; i++)
{
var end = Array.IndexOf(data, (byte)0, cursor);
if (end < 0)
throw new InvalidDataException("Missing metadata string terminator.");
text[i] = Encoding.UTF8.GetString(data, cursor, end - cursor);
cursor = end + 1;
}
var reader = new Values(data, text, 0, p4 + 4, p8, cursor);
var root = reader.Value(reader.Type(), 0) as Dictionary<string, object>;
if (root?.TryGetValue("m_skeleton", out var skeleton) == true)
root = skeleton as Dictionary<string, object>;
if (root is null || !root.TryGetValue("m_bones", out var boneValue) || boneValue is not List<object> bones)
throw new InvalidDataException("Serialized garment has no mesh bone table.");
return bones.Cast<Dictionary<string, object>>().Select(b => (string)b["m_boneName"]).ToArray();
}
static byte[] Expand(byte[] input, int length)
{
var output = new byte[length];
int i = 0, o = 0;
int Extended(int n)
{
if (n != 15)
return n;
int b;
do
{
b = input[i++];
n += b;
}
while (b == 255);
return n;
}
while (i < input.Length)
{
int token = input[i++];
int literal = Extended(token >> 4);
if (i + literal > input.Length || o + literal > length)
throw new InvalidDataException("Invalid metadata literal.");
Array.Copy(input, i, output, o, literal);
i += literal;
o += literal;
if (i == input.Length)
break;
int offset = input[i++] | input[i++] << 8;
int match = Extended(token & 15) + 4;
if (offset <= 0 || offset > o || o + match > length)
throw new InvalidDataException("Invalid metadata match.");
for (int j = 0; j < match; j++)
output[o + j] = output[o + j - offset];
o += match;
}
if (o != length)
throw new InvalidDataException("Truncated mesh metadata.");
return output;
}
sealed class Values
{
readonly byte[] data;
readonly string[] strings;
int p1, p4, p8, types;
public Values(byte[] data, string[] strings, int p1, int p4, int p8, int types)
{
this.data = data;
this.strings = strings;
this.p1 = p1;
this.p4 = p4;
this.p8 = p8;
this.types = types;
}
int I()
{
var value = BitConverter.ToInt32(data, p4);
p4 += 4;
return value;
}
long L()
{
var value = BitConverter.ToInt64(data, p8);
p8 += 8;
return value;
}
string S(int i) => i < 0 ? "" : strings[i];
public int Type()
{
int t = data[types++];
if ((t & 128) != 0)
{
t &= 127;
types++;
}
return t;
}
public object Value(int type, int depth)
{
if (depth > 64)
throw new InvalidDataException("Excessive mesh metadata nesting.");
switch (type)
{
case 1:
return null;
case 2:
return data[p1++] != 0;
case 3:
case 4:
return L();
case 5:
var number = BitConverter.Int64BitsToDouble(L());
return number;
case 6:
return S(I());
case 8:
case 10:
case 24:
int count = type == 24 ? data[p1++] : I();
if (count < 0 || count > 2000000)
throw new InvalidDataException("Invalid metadata array.");
int element = type == 8 ? 0 : Type();
var array = new List<object>(count);
for (int i = 0; i < count; i++)
array.Add(Value(element == 0 ? Type() : element, depth + 1));
return array;
case 9:
int members = I();
if (members < 0 || members > 100000)
throw new InvalidDataException("Invalid metadata object.");
var obj = new Dictionary<string, object>();
for (int i = 0; i < members; i++)
{
int t = Type();
string name = S(I());
obj[name] = Value(t, depth + 1);
}
return obj;
case 11:
case 12:
return I();
case 13:
return true;
case 14:
return false;
case 15:
return 0L;
case 16:
return 1L;
case 17:
return 0d;
case 18:
return 1d;
default:
throw new InvalidDataException("Unsupported mesh metadata node " + type);
}
}
}
}