Code/CharacterKit/SerializedMeshBones.cs

Reader for engine KV3 v2 LZ4-encoded mesh metadata that extracts the mesh-local bone name list from serialized VMDL metadata. It parses the binary KV3 structure, optionally LZ4-expands payload, decodes typed values and returns the m_boneName entries.

File Access
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);
			}
		}
	}
}