diff --git a/SpineViewer/Spine/SkeletonConverter.cs b/SpineViewer/Spine/SkeletonConverter.cs
new file mode 100644
index 0000000..eecb39b
--- /dev/null
+++ b/SpineViewer/Spine/SkeletonConverter.cs
@@ -0,0 +1,288 @@
+using Microsoft.VisualBasic;
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SpineViewer.Spine
+{
+ ///
+ /// SkeletonConverter 实现类标记
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+ public class SkeletonConverterImplementationAttribute : Attribute
+ {
+ public Version Version { get; }
+
+ public SkeletonConverterImplementationAttribute(Version version)
+ {
+ Version = version;
+ }
+ }
+
+ public abstract class SkeletonConverter
+ {
+ ///
+ /// 实现类缓存
+ ///
+ private static readonly Dictionary ImplementationTypes = [];
+
+ ///
+ /// 静态构造函数
+ ///
+ static SkeletonConverter()
+ {
+ // 遍历并缓存标记了 SkeletonConverterImplementationAttribute 的类型
+ var impTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(Spine).IsAssignableFrom(t) && !t.IsAbstract);
+ foreach (var type in impTypes)
+ {
+ var attr = type.GetCustomAttribute();
+ if (attr is not null)
+ {
+ if (ImplementationTypes.ContainsKey(attr.Version))
+ throw new InvalidOperationException($"Multiple implementations found: {attr.Version}");
+ ImplementationTypes[attr.Version] = type;
+ }
+ }
+ Program.Logger.Debug("Find SkeletonConverter implementations: [{}]", string.Join(", ", ImplementationTypes.Keys));
+ }
+
+ ///
+ /// 创建特定版本的 SkeletonConverter
+ ///
+ public static SkeletonConverter New(Version version)
+ {
+ if (!ImplementationTypes.TryGetValue(version, out var cvterType))
+ {
+ throw new NotImplementedException($"Not implemented version: {version}");
+ }
+ return (SkeletonConverter)Activator.CreateInstance(cvterType);
+ }
+
+ ///
+ /// 二进制转 Json 格式
+ ///
+ public abstract void BinaryToJson(string binPath, string jsonPath);
+
+ ///
+ /// Json 转二进制格式
+ ///
+ public abstract void JsonToBinary(string jsonPath, string binPath);
+
+ protected class BinaryReader
+ {
+ protected byte[] buffer = new byte[32];
+ protected byte[] bytesBigEndian = new byte[8];
+ public readonly List StringTable = new(32);
+ protected Stream input;
+
+ public BinaryReader(Stream input) { this.input = input; }
+ public int Read()
+ {
+ int val = input.ReadByte();
+ if (val == -1) throw new EndOfStreamException();
+ return val;
+ }
+ public byte ReadByte() => (byte)Read();
+ public byte ReadUByte() => (byte)Read();
+ public sbyte ReadSByte() => (sbyte)ReadByte();
+ public bool ReadBoolean() => Read() != 0;
+ public float ReadFloat()
+ {
+ if (input.Read(bytesBigEndian, 0, 4) < 4) throw new EndOfStreamException();
+ buffer[3] = bytesBigEndian[0];
+ buffer[2] = bytesBigEndian[1];
+ buffer[1] = bytesBigEndian[2];
+ buffer[0] = bytesBigEndian[3];
+ return BitConverter.ToSingle(buffer, 0);
+ }
+ public int ReadInt()
+ {
+ if (input.Read(bytesBigEndian, 0, 4) < 4) throw new EndOfStreamException();
+ return (bytesBigEndian[0] << 24)
+ | (bytesBigEndian[1] << 16)
+ | (bytesBigEndian[2] << 8)
+ | bytesBigEndian[3];
+ }
+ public long ReadLong()
+ {
+ if (input.Read(bytesBigEndian, 0, 8) < 8) throw new EndOfStreamException();
+ return ((long)(bytesBigEndian[0]) << 56)
+ | ((long)(bytesBigEndian[1]) << 48)
+ | ((long)(bytesBigEndian[2]) << 40)
+ | ((long)(bytesBigEndian[3]) << 32)
+ | ((long)(bytesBigEndian[4]) << 24)
+ | ((long)(bytesBigEndian[5]) << 16)
+ | ((long)(bytesBigEndian[6]) << 8)
+ | (long)(bytesBigEndian[7]);
+ }
+ public int ReadVarInt(bool optimizePositive = true)
+ {
+ byte b = ReadByte();
+ uint val = b & 0x7FU;
+ if ((b & 0x80) != 0)
+ {
+ b = ReadByte();
+ val |= (b & 0x7FU) << 7;
+ if ((b & 0x80) != 0)
+ {
+ b = ReadByte();
+ val |= (b & 0x7FU) << 14;
+ if ((b & 0x80) != 0)
+ {
+ b = ReadByte();
+ val |= (b & 0x7FU) << 21;
+ if ((b & 0x80) != 0)
+ val |= (ReadByte() & 0x7FU) << 28;
+ }
+ }
+ }
+ if (!optimizePositive) val = (val >> 1) ^ (uint)-(val & 1);
+ return (int)val;
+ }
+ public string ReadString()
+ {
+ int byteCount = ReadVarInt();
+ switch (byteCount)
+ {
+ case 0: return null;
+ case 1: return "";
+ }
+ byteCount--;
+ if (buffer.Length < byteCount) buffer = new byte[byteCount];
+ ReadFully(buffer, 0, byteCount);
+ return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
+ }
+ public string ReadStringRef()
+ {
+ int index = ReadVarInt();
+ return index == 0 ? null : StringTable[index - 1];
+ }
+ public void ReadFully(byte[] buffer, int offset, int length)
+ {
+ while (length > 0)
+ {
+ int count = input.Read(buffer, offset, length);
+ if (count <= 0) throw new EndOfStreamException();
+ offset += count;
+ length -= count;
+ }
+ }
+ }
+
+ protected class BinaryWriter
+ {
+ protected byte[] buffer = new byte[32];
+ protected byte[] bytesBigEndian = new byte[8];
+ public readonly List Strings = new(32);
+ protected Stream output;
+
+ public BinaryWriter(Stream output) { this.output = output; }
+ public void Write(int val) => output.WriteByte((byte)val);
+ public void WriteByte(byte val) => output.WriteByte(val);
+ public void WriteUByte(byte val) => output.WriteByte(val);
+ public void WriteSByte(sbyte val) => output.WriteByte((byte)val);
+ public void WriteBoolean(bool val) => output.WriteByte((byte)(val ? 1 : 0));
+ public void WriteFloat(float val)
+ {
+ uint v = BitConverter.SingleToUInt32Bits(val);
+ bytesBigEndian[0] = (byte)(v >> 24);
+ bytesBigEndian[1] = (byte)(v >> 16);
+ bytesBigEndian[2] = (byte)(v >> 8);
+ bytesBigEndian[3] = (byte)v;
+ output.Write(bytesBigEndian, 0, 4);
+ }
+ public void WriteInt(int val)
+ {
+ bytesBigEndian[0] = (byte)(val >> 24);
+ bytesBigEndian[1] = (byte)(val >> 16);
+ bytesBigEndian[2] = (byte)(val >> 8);
+ bytesBigEndian[3] = (byte)val;
+ output.Write(bytesBigEndian, 0, 4);
+ }
+ public void WriteLong(long val)
+ {
+ bytesBigEndian[0] = (byte)(val >> 56);
+ bytesBigEndian[1] = (byte)(val >> 48);
+ bytesBigEndian[2] = (byte)(val >> 40);
+ bytesBigEndian[3] = (byte)(val >> 32);
+ bytesBigEndian[4] = (byte)(val >> 24);
+ bytesBigEndian[5] = (byte)(val >> 16);
+ bytesBigEndian[6] = (byte)(val >> 8);
+ bytesBigEndian[7] = (byte)val;
+ output.Write(bytesBigEndian, 0, 8);
+ }
+ public void WriteVarInt(int val, bool optimizePositive = true)
+ {
+ if (!optimizePositive) val = (val << 1) ^ (val >> 31);
+ uint v = (uint)val;
+
+ byte b = (byte)(v & 0x7F);
+ v >>= 7;
+ if (v != 0)
+ {
+ output.WriteByte((byte)(b | 0x80));
+ b = (byte)(v & 0x7F);
+ v >>= 7;
+ if (v != 0)
+ {
+ output.WriteByte((byte)(b | 0x80));
+ b = (byte)(v & 0x7F);
+ v >>= 7;
+ if (v != 0)
+ {
+ output.WriteByte((byte)(b | 0x80));
+ b = (byte)(v & 0x7F);
+ v >>= 7;
+ if (v != 0)
+ {
+ output.WriteByte((byte)(b | 0x80));
+ b = (byte)(v & 0x7F);
+ }
+ }
+ }
+ }
+ output.WriteByte(b);
+ }
+ public void WriteString(string val)
+ {
+ if (val == null)
+ {
+ WriteVarInt(0);
+ return;
+ }
+ if (val.Length == 0)
+ {
+ WriteVarInt(1);
+ return;
+ }
+ int byteCount = System.Text.Encoding.UTF8.GetByteCount(val);
+ WriteVarInt(byteCount + 1);
+ if (buffer.Length < byteCount) buffer = new byte[byteCount];
+ System.Text.Encoding.UTF8.GetBytes(val, 0, val.Length, buffer, 0);
+ WriteFully(buffer, 0, byteCount);
+ }
+ public void WriteStringRef(List strings, string val)
+ {
+ if (val is null)
+ {
+ WriteVarInt(0);
+ return;
+ }
+ int index = strings.IndexOf(val);
+ if (index < 0)
+ {
+ strings.Add(val);
+ index = strings.Count - 1;
+ }
+ WriteVarInt(index + 1);
+ }
+ public void WriteFully(byte[] buffer, int offset, int length) => output.Write(buffer, offset, length);
+ }
+
+ }
+}