216 lines
8.3 KiB
C#
216 lines
8.3 KiB
C#
using System;
|
|
using System.Collections.Frozen;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Spine
|
|
{
|
|
/// <summary>
|
|
/// Spine 运行时版本标识类
|
|
/// </summary>
|
|
public sealed class SpineVersion : IEquatable<SpineVersion>, IComparable<SpineVersion>
|
|
{
|
|
public static readonly SpineVersion V21 = new(typeof(SpineRuntime21.Skeleton));
|
|
public static readonly SpineVersion V34 = new(typeof(SpineRuntime34.Skeleton));
|
|
public static readonly SpineVersion V35 = new(typeof(SpineRuntime35.Skeleton));
|
|
public static readonly SpineVersion V36 = new(typeof(SpineRuntime36.Skeleton));
|
|
public static readonly SpineVersion V37 = new(typeof(SpineRuntime37.Skeleton));
|
|
public static readonly SpineVersion V38 = new(typeof(SpineRuntime38.Skeleton));
|
|
public static readonly SpineVersion V40 = new(typeof(SpineRuntime40.Skeleton));
|
|
public static readonly SpineVersion V41 = new(typeof(SpineRuntime41.Skeleton));
|
|
public static readonly SpineVersion V42 = new(typeof(SpineRuntime42.Skeleton));
|
|
|
|
/// <summary>
|
|
/// 所有可用的版本
|
|
/// </summary>
|
|
public static readonly ImmutableArray<SpineVersion> RegisteredVersions;
|
|
|
|
/// <summary>
|
|
/// tag 到版本的映射
|
|
/// </summary>
|
|
private static readonly FrozenDictionary<string, SpineVersion> _tagToRegisteredVersion;
|
|
|
|
static SpineVersion()
|
|
{
|
|
List<SpineVersion> registeredVersions = [];
|
|
Dictionary<string, SpineVersion> tagToRegisteredVersion = [];
|
|
|
|
// 通过反射获取所有 public static 字段,类型为 SpineVersion
|
|
var props = typeof(SpineVersion).GetFields(BindingFlags.Public | BindingFlags.Static).Where(p => p.FieldType == typeof(SpineVersion));
|
|
foreach (var prop in props)
|
|
{
|
|
if (prop.GetValue(null) is SpineVersion spVer)
|
|
{
|
|
registeredVersions.Add(spVer);
|
|
tagToRegisteredVersion[spVer.Tag] = spVer;
|
|
}
|
|
}
|
|
|
|
RegisteredVersions = registeredVersions.ToImmutableArray();
|
|
_tagToRegisteredVersion = tagToRegisteredVersion.ToFrozenDictionary();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从骨骼文件获取版本
|
|
/// </summary>
|
|
/// <param name="skelPath">文件路径</param>
|
|
/// <exception cref="InvalidDataException">没有检测到有效的版本字符串</exception>
|
|
/// <exception cref="ArgumentException">无效的字符串格式</exception>
|
|
/// <exception cref="KeyNotFoundException">未注册的版本号</exception>
|
|
public static SpineVersion GetVersion(string skelPath)
|
|
{
|
|
string? versionString = null;
|
|
byte[] data = File.ReadAllBytes(skelPath);
|
|
|
|
using var input = new MemoryStream(data);
|
|
var binaryReader = new Utils.BinaryReader(input);
|
|
var jsonReader = new Utf8JsonReader(data, true, default);
|
|
|
|
// try json format
|
|
if (Utils.Utf8Validator.IsUtf8(data))
|
|
{
|
|
try
|
|
{
|
|
if (JsonDocument.TryParseValue(ref jsonReader, out var doc))
|
|
{
|
|
var root = doc.RootElement;
|
|
if (root.ValueKind == JsonValueKind.Object &&
|
|
root.TryGetProperty("skeleton", out var skeleton) &&
|
|
skeleton.ValueKind == JsonValueKind.Object &&
|
|
skeleton.TryGetProperty("spine", out var _version) &&
|
|
_version.ValueKind == JsonValueKind.String)
|
|
{
|
|
versionString = _version.GetString();
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// try v4 binary format
|
|
if (versionString is null)
|
|
{
|
|
try
|
|
{
|
|
input.Position = 0;
|
|
var hash = binaryReader.ReadLong();
|
|
var versionPosition = input.Position;
|
|
var versionByteCount = binaryReader.ReadVarInt();
|
|
input.Position = versionPosition;
|
|
if (versionByteCount <= 13)
|
|
versionString = binaryReader.ReadString();
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// try v3 binary format
|
|
if (versionString is null)
|
|
{
|
|
try
|
|
{
|
|
input.Position = 0;
|
|
var hash = binaryReader.ReadString();
|
|
versionString = binaryReader.ReadString();
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
if (versionString is null)
|
|
throw new InvalidDataException($"No verison detected: '{skelPath}'");
|
|
|
|
var parts = versionString.Trim().Split(".", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
if (parts.Length < 2) throw new ArgumentException($"Unknown verison: {versionString}");
|
|
|
|
var tag = $"{parts[0]}.{parts[1]}";
|
|
if (_tagToRegisteredVersion.TryGetValue(tag, out var version))
|
|
return version;
|
|
throw new KeyNotFoundException($"Unregistered verison: {versionString}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 私有构造函数
|
|
/// </summary>
|
|
/// <param name="skeletonType">运行时库的 <c>Skeleton</c> 类型对象, 用于查找程序集信息</param>
|
|
private SpineVersion(Type skeletonType)
|
|
{
|
|
var version = skeletonType.Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
|
ArgumentNullException.ThrowIfNull(version, skeletonType.AssemblyQualifiedName);
|
|
|
|
// 此处假设自己填的版本号都是规范的 x.y.x
|
|
var parts = version.Trim().Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
|
|
Major = int.Parse(parts[0]);
|
|
Minor = int.Parse(parts[1]);
|
|
Tag = $"{Major}.{Minor}";
|
|
RuntimeVersion = version;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 主版本号
|
|
/// </summary>
|
|
public int Major { get; }
|
|
|
|
/// <summary>
|
|
/// 次版本号
|
|
/// </summary>
|
|
public int Minor { get; }
|
|
|
|
/// <summary>
|
|
/// 仅包含主次版本号的字符串, 例如 <c>3.8</c>
|
|
/// </summary>
|
|
public string Tag { get; }
|
|
|
|
/// <summary>
|
|
/// 带有补丁号的完整运行时版本号, 例如 <c>3.8.99</c>
|
|
/// </summary>
|
|
public string RuntimeVersion { get; }
|
|
|
|
public override string ToString() => Tag;
|
|
|
|
#region IEquatable 接口实现
|
|
|
|
public bool Equals(SpineVersion? other)
|
|
{
|
|
if (ReferenceEquals(this, other)) return true;
|
|
if (other is null) return false;
|
|
return Major == other.Major && Minor == other.Minor;
|
|
}
|
|
|
|
public override bool Equals(object? obj) => obj is SpineVersion other && Equals(other);
|
|
|
|
public override int GetHashCode() => Tag.GetHashCode();
|
|
|
|
public static bool operator ==(SpineVersion a, SpineVersion b) => a is null ? b is null : a.Equals(b);
|
|
|
|
public static bool operator !=(SpineVersion a, SpineVersion b) => !(a == b);
|
|
|
|
#endregion
|
|
|
|
#region IComparable 接口实现
|
|
|
|
public int CompareTo(SpineVersion? other)
|
|
{
|
|
if (other is null) return 1;
|
|
int majorDiff = Major.CompareTo(other.Major);
|
|
return majorDiff != 0 ? majorDiff : Minor.CompareTo(other.Minor);
|
|
}
|
|
|
|
public static bool operator <(SpineVersion a, SpineVersion b) => a is null ? b is not null : a.CompareTo(b) < 0;
|
|
|
|
public static bool operator >(SpineVersion a, SpineVersion b) => a is not null && a.CompareTo(b) > 0;
|
|
|
|
public static bool operator <=(SpineVersion a, SpineVersion b) => a is null || a.CompareTo(b) <= 0;
|
|
|
|
public static bool operator >=(SpineVersion a, SpineVersion b) => a is null ? b is null : a.CompareTo(b) >= 0;
|
|
|
|
#endregion
|
|
}
|
|
} |