求助大佬,Megaha:Re 异世界实干记,解包


找到了对应的结构我静态看看…

新版本似乎写死在了metadata

image


看样子还是得等完全解析完再分析…
image

这下加密文件名的逻辑差不多搞清楚了至于CRC怎么变成Hash128的由于unity引擎的底层不熟悉我不太清楚…

import hashlib
import struct

class SpookyHashV2:
“”"
SpookyHash V2 的 Python 实现
基于 Bob Jenkins 的公共领域算法
针对 32位输入(CRC)优化
“”"

# 常数
SC_CONST = 0xdeadbeefdeadbeef
MASK_64 = 0xffffffffffffffff  # 64位掩码

@staticmethod
def rot64(x, k):
    """64位循环左移,确保结果在64位范围内"""
    return ((x << k) | (x >> (64 - k))) & SpookyHashV2.MASK_64

@classmethod
def hash128_uint32(cls, val, seed1=0, seed2=0):
    """
    对32位整数计算128位SpookyHash
    """
    # 将32位整数转为8字节(小端序,高位补0)
    data = struct.pack('<I', val) + b'\x00' * 4
    return cls._short_hash(data, 4, seed1 & cls.MASK_64, seed2 & cls.MASK_64)

@classmethod
def _short_hash(cls, message, length, h0, h1):
    """
    短消息哈希(SpookyHash ShortHash)
    对应C++代码中的 Short 函数
    """
    h2 = cls.SC_CONST
    h3 = cls.SC_CONST
    
    # 确保消息长度为32字节(补0)
    padded = message + b'\x00' * (32 - length)
    p64 = struct.unpack('<4Q', padded[:32])
    
    # 处理完整的32字节块
    if length >= 16:
        h2 = (h2 + p64[0]) & cls.MASK_64
        h3 = (h3 + p64[1]) & cls.MASK_64
        h0, h1, h2, h3 = cls._short_mix(h0, h1, h2, h3)
        if length >= 32:
            h0 = (h0 + p64[2]) & cls.MASK_64
            h1 = (h1 + p64[3]) & cls.MASK_64
    
    # 处理剩余字节
    h3 = (h3 & 0x00ffffffffffffff) | (length << 56)
    
    # 根据剩余字节数处理
    remainder = length % 32
    if remainder >= 8:
        # 取部分字节
        mask = (1 << (remainder * 8)) - 1
        h2 = (h2 + (p64[0] & mask)) & cls.MASK_64
    else:
        h2 = (h2 + cls.SC_CONST) & cls.MASK_64
        h3 = (h3 + cls.SC_CONST) & cls.MASK_64
    
    # ShortEnd
    return cls._short_end(h0, h1, h2, h3)

@staticmethod
def _short_mix(h0, h1, h2, h3):
    """ShortMix 函数,确保所有操作在64位范围内"""
    # 第1轮
    h2 = (h2 << 50) | (h2 >> 14); h2 &= SpookyHashV2.MASK_64
    h2 = (h2 + h3) & SpookyHashV2.MASK_64
    h0 ^= h2
    
    h3 = (h3 << 52) | (h3 >> 12); h3 &= SpookyHashV2.MASK_64
    h3 = (h3 + h0) & SpookyHashV2.MASK_64
    h1 ^= h3
    
    h0 = (h0 << 30) | (h0 >> 34); h0 &= SpookyHashV2.MASK_64
    h0 = (h0 + h1) & SpookyHashV2.MASK_64
    h2 ^= h0
    
    h1 = (h1 << 41) | (h1 >> 23); h1 &= SpookyHashV2.MASK_64
    h1 = (h1 + h2) & SpookyHashV2.MASK_64
    h3 ^= h1
    
    # 第2轮
    h2 = (h2 << 54) | (h2 >> 10); h2 &= SpookyHashV2.MASK_64
    h2 = (h2 + h3) & SpookyHashV2.MASK_64
    h0 ^= h2
    
    h3 = (h3 << 48) | (h3 >> 16); h3 &= SpookyHashV2.MASK_64
    h3 = (h3 + h0) & SpookyHashV2.MASK_64
    h1 ^= h3
    
    h0 = (h0 << 38) | (h0 >> 26); h0 &= SpookyHashV2.MASK_64
    h0 = (h0 + h1) & SpookyHashV2.MASK_64
    h2 ^= h0
    
    h1 = (h1 << 37) | (h1 >> 27); h1 &= SpookyHashV2.MASK_64
    h1 = (h1 + h2) & SpookyHashV2.MASK_64
    h3 ^= h1
    
    # 第3轮
    h2 = (h2 << 62) | (h2 >> 2); h2 &= SpookyHashV2.MASK_64
    h2 = (h2 + h3) & SpookyHashV2.MASK_64
    h0 ^= h2
    
    h3 = (h3 << 34) | (h3 >> 30); h3 &= SpookyHashV2.MASK_64
    h3 = (h3 + h0) & SpookyHashV2.MASK_64
    h1 ^= h3
    
    return h0, h1, h2, h3

@staticmethod
def _short_end(h0, h1, h2, h3):
    """ShortEnd 函数,确保所有操作在64位范围内"""
    # 第1轮
    h3 ^= h2
    h2 = (h2 << 15) | (h2 >> 49); h2 &= SpookyHashV2.MASK_64
    h3 = (h3 + h2) & SpookyHashV2.MASK_64
    
    h0 ^= h3
    h3 = (h3 << 52) | (h3 >> 12); h3 &= SpookyHashV2.MASK_64
    h0 = (h0 + h3) & SpookyHashV2.MASK_64
    
    h1 ^= h0
    h0 = (h0 << 26) | (h0 >> 38); h0 &= SpookyHashV2.MASK_64
    h1 = (h1 + h0) & SpookyHashV2.MASK_64
    
    # 第2轮
    h2 ^= h1
    h1 = (h1 << 51) | (h1 >> 13); h1 &= SpookyHashV2.MASK_64
    h2 = (h2 + h1) & SpookyHashV2.MASK_64
    
    h3 ^= h2
    h2 = (h2 << 28) | (h2 >> 36); h2 &= SpookyHashV2.MASK_64
    h3 = (h3 + h2) & SpookyHashV2.MASK_64
    
    h0 ^= h3
    h3 = (h3 << 9) | (h3 >> 55); h3 &= SpookyHashV2.MASK_64
    h0 = (h0 + h3) & SpookyHashV2.MASK_64
    
    # 第3轮
    h1 ^= h0
    h0 = (h0 << 47) | (h0 >> 17); h0 &= SpookyHashV2.MASK_64
    h1 = (h1 + h0) & SpookyHashV2.MASK_64
    
    h2 ^= h1
    h1 = (h1 << 54) | (h1 >> 10); h1 &= SpookyHashV2.MASK_64
    h2 = (h2 + h1) & SpookyHashV2.MASK_64
    
    h3 ^= h2
    h2 = (h2 << 32) | (h2 >> 32); h2 &= SpookyHashV2.MASK_64
    h3 = (h3 + h2) & SpookyHashV2.MASK_64
    
    # 第4轮
    h0 ^= h3
    h3 = (h3 << 25) | (h3 >> 39); h3 &= SpookyHashV2.MASK_64
    h0 = (h0 + h3) & SpookyHashV2.MASK_64
    
    return h0 & SpookyHashV2.MASK_64, h1 & SpookyHashV2.MASK_64

def crc_to_hash128(crc_value, seed1=0, seed2=0):
“”"
使用标准SpookyHash V2将CRC转为Hash128

Args:
    crc_value: 32位CRC值
    seed1, seed2: SpookyHash种子(通常为0)

Returns:
    32字符的hash128十六进制字符串
"""
# 计算SpookyHash V2
h1, h2 = SpookyHashV2.hash128_uint32(crc_value, seed1, seed2)

# 确保在64位范围内
h1 &= SpookyHashV2.MASK_64
h2 &= SpookyHashV2.MASK_64

# 转为16字节(小端序)
hash_bytes = struct.pack('<QQ', h1, h2)

# 转为十六进制字符串
return hash_bytes.hex()

def compute_final_hash(baseFileName, crc_value, hashKey, separator=“+”):
“”"
完整解密函数:CRC → SpookyHash → 拼接 → SHA256 → x2
“”"

统一CRC格式

if isinstance(crc_value, str):
if crc_value.startswith(‘0x’):
crc_int = int(crc_value, 16)
else:
crc_int = int(crc_value, 16)
else:
crc_int = crc_value

print("=" * 90)
print("=" * 90)
print(f"基础文件名: {baseFileName}")
print(f"CRC: {crc_int} (0x{crc_int:08X})")
print(f"AbHashKey: \"{hashKey}\"")
print(f"分隔符: '{separator}'")
print("=" * 90)

# 步骤1: CRC → Hash128
print("\n【步骤1】CRC转Hash128 (SpookyHash V2)")
hash128 = crc_to_hash128(crc_int, seed1=0, seed2=0)
print(f"生成的Hash128: {hash128}")
print(f"Hash128长度: {len(hash128)} 字符")

# 步骤2: 拼接
print("\n【步骤2】拼接字符串")
if separator == "":
    combined = f"{baseFileName}{hash128}{hashKey}"
else:
    combined = f"{baseFileName}{separator}{hash128}{separator}{hashKey}"
print(f"拼接结果: {combined}")

# 步骤3: UTF-8编码
print("\n【步骤3】UTF-8编码")
utf8_bytes = combined.encode('utf-8')
print(f"UTF-8字节长度: {len(utf8_bytes)} 字节")
print(f"UTF-8原始字节 (前16个): {utf8_bytes[:16].hex()}...")

# 步骤4: SHA256计算 - 显示原始字节数组
print("\n【步骤4】SHA256原始字节数组")
raw_hash = hashlib.sha256(utf8_bytes).digest()
print(f"SHA256原始字节 (32字节): [", end="")
for i, b in enumerate(raw_hash):
    if i > 0:
        print(", ", end="")
    print(f"0x{b:02x}", end="")
print("]")
print(f"原始字节十六进制: {raw_hash.hex()}")

# 显示原始字节与x2的对比
print("\n原始字节 vs x2转换 (前8个):")
print("  索引 | 原始字节 | 原始值 | x2转换")
print("  -----|----------|--------|--------")
for i in range(8):
    print(f"  {i:4d} | 0x{raw_hash[i]:02x}     | {raw_hash[i]:6d} | \"{raw_hash[i]:02x}\"")

# 步骤5: x2转换 - 显式展示转换过程
print("\n【步骤5】x2转换 - 每个字节转两位十六进制")
x2_parts = []
for i, b in enumerate(raw_hash):
    hex_byte = f"{b:02x}"
    x2_parts.append(hex_byte)
    if i < 5:  # 只显示前5个示例
        print(f"  raw_hash[{i}] = {b:3d} (0x{b:02x}) → \"{hex_byte}\"")

final_hash = ''.join(x2_parts)
print(f"\n拼接所有x2片段: {final_hash}")
print(f"最终哈希长度: {len(final_hash)} 字符")

print("\n" + "=" * 90)
print("解密完成!")
print("=" * 90)

return {
    'hash128': hash128,
    'raw_bytes': raw_hash,
    'final_hash': final_hash,
    'separator': separator
}

def simple_version(baseFileName, crc_value, hashKey, separator=“_”):
“”“简洁版 - 只返回最终哈希”“”
hash128 = crc_to_hash128(crc_value)
if separator == “”:
combined = f"{baseFileName}{hash128}{hashKey}"
else:
combined = f"{baseFileName}{separator}{hash128}{separator}{hashKey}"
return hashlib.sha256(combined.encode()).hexdigest()

您的参数

baseFileName = “4694165b0206bf77910ebd6a1191b9e3”
crc_value = 3583767260
hashKey = “ria_gpl_ab”

print(“\n” + “★” * 90)
print(“开始执行解密…”)
print(“★” * 90)

测试加号分隔符

result_plus = compute_final_hash(baseFileName, crc_value, hashKey, “_”)

所有分隔符结果

print(“\n” + “★” * 90)
print(“所有分隔符结果”)
print(“★” * 90)
for sep in [“_”, “+”, “-”, “.”, “”]:
final = simple_version(baseFileName, crc_value, hashKey, sep)
sep_display = f"‘{sep}’" if sep else “无分隔符”
print(f"{sep_display:6}: {final}")

感觉大致的加密流程应该是crc→hash128—>bundleName+hash128+abhashkey—>sha256—>X2,不过crc的加密过程好像不是常规的SpookyHash,而且连接符也不太确定,没逆向,根据上面的代码片段研究了半天,提供一个思路看对不对

实际上我已经搞定了。。。只是不想公开。。。就我发的那些已经足够推出来了只是还少了一步(无后缀文件名实际上是已经经过一次加密的照葫芦画瓢交叉引用一下有点乱花点时间还是可以找到的)
另外他的hash128计算不是单纯拿crc的字符串去算而是uint32

蹲一下,怀疑官方再更新加密:joy:

另外少用AI
我一开始用AI去分析代码一直是错的他只能提供一个大概的方向大部分都是AI猜的(我也不知道为啥AI特别喜欢猜而不是告诉你这东西不确定)例如

这里一看就是AI猜的实际上不是这样

可能是我记错了这里实际上是.bundle文件名+hash128+abhashkey
至于catalog的版本号可以使用

大佬提供的代码将请求payload替换为

        payload = {
            "c": "Index",
            "dgs_adult": 1,
            "app_vc": {
                "os": 1,
                "catalog": "",
                "app_ver": "",
                "app_type": 1
            },
        }

获取返回的MaxAppVer然后将app_ver替换为获取到的MaxAppVer再请求就能获取到最新的RemoteCatalogName
这游戏有移动版和桌面版
移动版的下载URL前缀是

https://cdn.karen-megahare.com/prod/res/Android/

桌面版是

https://cdn.megahare.com/prod/res/StandaloneWindows64/

请求服务器(版本号 剧情)

https://prod.karen-megahare.com/i.php

加密文件名生成逻辑

v7 = System_String__Format_130277216(
           (System_String_o *)StringLiteral_17126,// {0}+{1}+{2}
           (Il2CppObject *)assetBundleName,     // xxxxx.bundle
           v6,                                  // Hash128
           (Il2CppObject *)hashKey,             // AbHashKey -> ria_gpl_ab
           0LL);

至于Hash128怎么算可以使用GitHub Copilot · GitHub
多拷打几下就行了
这里提供几个计算结果

3583767260 -> 2afeda0eee427e1b146a211d05f2f047
1295375149 -> ee7bb02c3b97dd74347850f9aa5a8de6
949661317 -> 7799395f5a27facdf6cb12796e4e9173

不想公开的原因有很多最主要的是有些曾经和我换过文件的人都将这个游戏文件名加密逻辑保密这是我发完分析才知道的
而且这个游戏社区已经养成了一个友好的文件互换环境如果直接贴出代码会直接影响这个社区

我已经说的很详细了只需要解决hash128的计算问题就搞定了如果有人搞定的话也最好不要将代码发出来一个是避免倒勾破坏社区还有避免官方再换加密

假如官方真的有在看这帖子的话,那还是太善良了,只是来来回回改东西而没有混淆代码,或者上高强度的反作弊 :thinking:

感谢,hash128的加密已经弄清楚了,不过gpt4太蠢了,绕了大半天,给个错的,还硬说是对的,最后用字节切片转成功了

GPT现在是真的蠢,编程推荐用claude

这二进制目录到底是怎么解析的,尝试用AddressablesToolsPy失败了,编写代码查找规律,发现偏移量也不固定,参照符也不固定,最后根据上面的3组hash和crc让AI推算了几个小时才反推出剩下crc的位置 :joy:

看来得花点时间看下使用说明

在整理完他的ab包后确实没发现姿势文件新版本应该已经移除了这个文件先丢一边
他的spine看上去像是用某种批量方式转换的确实会出现穿模现象因为没有任何绘制顺序调整
整个spine没有一个骨骼全是网格变形
这里丢一个剧情文件
storydata154.zip (5.5 KB)

对应最后一个spine unit_still_spine_00010100
又是没见过的格式又得花时间去折腾里面看样子是有一些动画配置的


7000多行代码…

BCUL_Peerless_ScriptEngineADV__OnCommand
BCUL.Peerless.ScriptEngineADV$$OnCommand

累死了,难怪之前的live2d没有完整的动作,用unity的timeline控制的,脚本提取的时间轴,不知道对不对,明天用spine试下

aHR0cHM6Ly9nb2ZpbGUuaW8vZC9uR3lQM2c= 錄了 00010100 給大佬們比較 看了這帖 才跑去玩遊戲 還不小心課金大概4-5萬台幣 :sweat_smile: gofile 死的可能比較快

实际上spine的播放没有那么复杂我只是想看看他的剧情具体怎么实现包括表情切换那堆
顺带一提如果没有使用spine runtime的基础的话就不需要继续折腾了
只需要在IDA交叉引用spine API的FindAnimation

就可以找到一个

Updraft_SpineController__ChangeAnimation

粗略分析

void Updraft_SpineController__ChangeAnimation(
        Updraft_SpineController_o *this,
        System_String_o *animName,              // 待播放动画名
        float crossFadeDuration,                // 混合时间
        float startFixedTime,                   // 指定从动画的某个时间点开始
        int32_t layerIndex,                     // -1 自动设置轨道否则自定义设置
        const MethodInfo *method)
{
  struct Spine_Skeleton_o *skeleton; // x8
  Spine_SkeletonData_o *data; // x0
  Spine_Animation_o *Animation; // x0
  __int64 v14; // x1
  Spine_Animation_o *v15; // x21
  Updraft_SpineController_c *v16; // x0
  System_Collections_Generic_HashSet_object__o *NoLoopAnimationNames; // x0
  bool v18; // w23
  Spine_AnimationState_o *animationState; // x0
  Spine_TrackEntry_o *v20; // x0
  Spine_TrackEntry_o *v21; // x21
  struct Spine_Animation_o *v22; // x8
  float duration; // s0
  struct System_Collections_Generic_Dictionary_string__SpineController_ParameterData__o *parameterValues; // x0
  System_Collections_Generic_Dictionary_ValueCollection_TKey__TValue__o *Values; // x0
  const MethodInfo *v26; // x2
  Spine_AnimationState_TrackEntryDelegate_o *v27; // x22
  System_Collections_Generic_Dictionary_ValueCollection_Enumerator_TKey__TValue__o v28; // [xsp+8h] [xbp-58h] BYREF

  if ( (byte_96D32A0 & 1) == 0 )
  {
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    byte_96D32A0 = 1;
  }
  memset(&v28, 0, sizeof(v28));
  skeleton = this->fields._skeleton;
  if ( !skeleton )
    goto LABEL_34;
  data = skeleton->fields.data;
  if ( !data )
    goto LABEL_34;
  Animation = Spine_SkeletonData__FindAnimation(data, animName, 0LL);// 判断目标动作是否存在
  if ( !Animation )
    return;
  v15 = Animation;
  v16 = Updraft_SpineController_TypeInfo;
  if ( !Updraft_SpineController_TypeInfo->_2.cctor_finished )
  {
    j_il2cpp_runtime_class_init_0(Updraft_SpineController_TypeInfo, v14);
    v16 = Updraft_SpineController_TypeInfo;
  }
  NoLoopAnimationNames = (System_Collections_Generic_HashSet_object__o *)v16->static_fields->NoLoopAnimationNames;// 是否循环?
  if ( !NoLoopAnimationNames )
    goto LABEL_34;
  v18 = System_Collections_Generic_HashSet_object___Contains(// 是否循环?  T->不循环 F->循环
          NoLoopAnimationNames,
          (Il2CppObject *)animName,
          (const MethodInfo_5C76C88 *)Method_System_Collections_Generic_HashSet_string__Contains__);
  if ( layerIndex == -1 )                       // -1自动设置轨道
  {
    if ( System_String__op_Equality(animName, (System_String_o *)StringLiteral_14745, 0LL) )
      layerIndex = 6;                           // nod 动作轨道固定为6 立绘点头动作? 其他为5
    else
      layerIndex = 5;
  }
  animationState = this->fields._animationState;
  if ( !animationState )
    goto LABEL_34;
  v20 = Spine_AnimationState__SetAnimation_133484248(animationState, layerIndex, v15, !v18, 0LL);// 设置动画
  v21 = v20;
  if ( startFixedTime <= 0.0 )                  // 指定动画从指定时间线播放?
  {
    if ( !v20 )
      goto LABEL_34;
  }
  else
  {
    if ( !v20 )
      goto LABEL_34;
    v20->fields.trackTime = startFixedTime;
  }
  if ( layerIndex != 6 )
  {
    v20->fields.mixDuration = crossFadeDuration;
    if ( layerIndex != 5 )
      goto LABEL_29;
    parameterValues = this->fields._parameterValues;
    if ( parameterValues )
    {
      Values = System_Collections_Generic_Dictionary_object__object___get_Values(
                 (System_Collections_Generic_Dictionary_object__object__o *)parameterValues,
                 (const MethodInfo_56AC7B8 *)Method_System_Collections_Generic_Dictionary_string__SpineController_ParameterData__get_Values__);
      if ( Values )
      {
        System_Collections_Generic_Dictionary_ValueCollection_object__object___GetEnumerator(
          &v28,
          Values,
          (const MethodInfo_691C728 *)Method_System_Collections_Generic_Dictionary_ValueCollection_string__SpineController_ParameterData__GetEnumerator__);
        while ( System_Collections_Generic_Dictionary_ValueCollection_Enumerator_object__object___MoveNext(
                  &v28,
                  (const MethodInfo_58CD474 *)Method_System_Collections_Generic_Dictionary_ValueCollection_Enumerator_string__SpineController_ParameterData__MoveNext__) )
        {
          if ( !v28.fields._currentValue )
            sub_3D658A8();
          LODWORD(v28.fields._currentValue[1].klass) = HIDWORD(v28.fields._currentValue[1].klass);
        }
        System_Collections_Generic_Dictionary_ValueCollection_Enumerator_object__object___Dispose(
          &v28,
          (const MethodInfo_58CD470 *)Method_System_Collections_Generic_Dictionary_ValueCollection_Enumerator_string__SpineController_ParameterData__Dispose__);
        this->fields._currentParameterCurve = (struct Updraft_SpineController_ParameterCurve_o *)System_Collections_Generic_CollectionExtensions__GetValueOrDefault_object__object__85459036(
                                                                                                   (System_Collections_Generic_IReadOnlyDictionary_TKey__TValue__o *)this->fields._parameterCurves,
                                                                                                   (Il2CppObject *)animName,
                                                                                                   0LL,
                                                                                                   (const MethodInfo_518005C *)Method_System_Collections_Generic_CollectionExtensions_GetValueOrDefault_string__SpineController_ParameterCurve___);
        sub_3D655C8((__int64)&this->fields._currentParameterCurve);
        goto LABEL_29;
      }
    }
LABEL_34:
    sub_3D658A8();
  }
  v22 = v20->fields.animation;
  v20->fields.mixBlend = 3;
  if ( !v22 )
    goto LABEL_34;
  duration = v22->fields.duration;
  v20->fields.loop = 0;                         // nod 6轨道不循环?
  v20->fields.trackEnd = duration;
LABEL_29:
  if ( System_String__op_Equality(animName, (System_String_o *)StringLiteral_15941, 0LL) )// startle 惊吓?点击动作播放一次不循环?
  {
    v27 = (Spine_AnimationState_TrackEntryDelegate_o *)sub_3D658A4(Spine_AnimationState_TrackEntryDelegate_TypeInfo);
    Spine_AnimationState_TrackEntryDelegate___ctor(
      v27,
      (Il2CppObject *)this,
      Method_Updraft_SpineController__ChangeAnimation_b__110_0__,
      0LL);
    Spine_TrackEntry__add_Complete(v21, v27, 0LL);
  }
  Updraft_SpineController__set_CurrentAnimationName(this, animName, v26);
}

中间那坨乱的没看懂但是交叉引用还有一个

Updraft_SpineController__PlayParameterTrack
void Updraft_SpineController__PlayParameterTrack(
        Updraft_SpineController_o *this,
        int32_t trackIndex,
        System_String_o *parameterName,
        float value,
        int32_t mixBlend,
        const MethodInfo *method)
{
  struct Spine_Skeleton_o *skeleton; // x8
  Spine_SkeletonData_o *data; // x0
  Spine_Animation_o *Animation; // x0
  const MethodInfo *v14; // x2
  Spine_Animation_o *v15; // x21
  struct System_Collections_Generic_Dictionary_string__SpineController_ParameterData__o *parameterValues; // x0
  Il2CppObject *v17; // x24
  float v18; // s0
  float v19; // s1
  Spine_AnimationState_o *animationState; // x0
  float v21; // s8
  Spine_TrackEntry_o *Current; // x0
  struct Spine_Animation_o *v23; // x8
  Spine_TrackEntry_o *v24; // x24
  float trackTime; // s9
  float v26; // s1
  float v27; // s2
  struct UnityEngine_Mathf_StaticFields *static_fields; // x8
  float v29; // s0
  Spine_AnimationState_o *v30; // x0
  Spine_TrackEntry_o *v31; // x0
  struct System_Collections_Generic_HashSet_string__o *facialParameterNames; // x0
  int32_t v33; // w22
  Spine_AnimationState_o *v34; // x0
  Spine_TrackEntry_o *v35; // x0
  struct Spine_Animation_o *v36; // x8
  Spine_TrackEntry_o *v37; // x23
  System_String_o *name; // x0
  Spine_AnimationState_o *v39; // x0
  Spine_TrackEntry_o *v40; // x0
  Il2CppObject *valuea; // [xsp+8h] [xbp-58h] BYREF

  if ( (byte_96D32A2 & 1) == 0 )
  {
    sub_3D6561C();
    sub_3D6561C();
    sub_3D6561C();
    byte_96D32A2 = 1;
  }
  valuea = 0LL;
  if ( trackIndex >= 8 )
  {
    skeleton = this->fields._skeleton;
    if ( !skeleton )
      goto LABEL_47;
    data = skeleton->fields.data;
    if ( !data )
      goto LABEL_47;
    Animation = Spine_SkeletonData__FindAnimation(data, parameterName, 0LL);
    if ( !Animation )
    {
      Updraft_SpineController__ChangeEmptyAnimation(this, 0.0, trackIndex, v14);
      return;
    }
    v15 = Animation;
    parameterValues = this->fields._parameterValues;
    if ( !parameterValues )
      goto LABEL_47;
    if ( System_Collections_Generic_Dictionary_object__object___TryGetValue(
           (System_Collections_Generic_Dictionary_object__object__o *)parameterValues,
           (Il2CppObject *)parameterName,
           &valuea,
           (const MethodInfo_56AE4BC *)Method_System_Collections_Generic_Dictionary_string__SpineController_ParameterData__TryGetValue__) )
    {
      v17 = valuea;
      if ( !valuea )
        goto LABEL_47;
    }
    else
    {
      v17 = (Il2CppObject *)sub_3D658A4(Updraft_SpineController_ParameterData_TypeInfo);
      System_Object___ctor(v17, 0LL);
      if ( !v17 )
        goto LABEL_47;
      HIDWORD(v17[1].klass) = 0;
      LODWORD(v17[1].monitor) = 0;
      HIDWORD(v17[1].monitor) = 1065353216;
      valuea = v17;
    }
    v18 = *(float *)&v17[1].monitor;
    v19 = *((float *)&v17[1].monitor + 1);
    animationState = this->fields._animationState;
    if ( v19 >= value )
      v19 = value;
    if ( v18 > value )
      v19 = *(float *)&v17[1].monitor;
    if ( !animationState )
      goto LABEL_47;
    v21 = v19 - v18;
    Current = Spine_AnimationState__GetCurrent(animationState, trackIndex, 0LL);
    if ( !Current )
      goto LABEL_29;
    v23 = Current->fields.animation;
    v24 = Current;
    if ( !v23 )
      goto LABEL_47;
    if ( !System_String__op_Equality(v23->fields.name, parameterName, 0LL) )
      goto LABEL_29;
    trackTime = v24->fields.trackTime;
    if ( !byte_96CD2A8 )
    {
      sub_3D6561C();
      byte_96CD2A8 = 1;
    }
    v26 = fabsf(trackTime);
    v27 = fabsf(v21);
    if ( v26 <= v27 )
      v26 = v27;
    static_fields = UnityEngine_Mathf_TypeInfo->static_fields;
    v29 = v26 * 0.000001;
    if ( (float)(v26 * 0.000001) <= (float)(static_fields->Epsilon * 8.0) )
      v29 = static_fields->Epsilon * 8.0;
    if ( vabds_f32(v21, trackTime) >= v29 )
    {
LABEL_29:
      v30 = this->fields._animationState;
      if ( v30 )
      {
        v31 = Spine_AnimationState__SetAnimation_133484248(v30, trackIndex, v15, 0, 0LL);
        if ( v31 )
        {
          v31->fields.timeScale = 0.0;
          v31->fields.mixBlend = mixBlend;
          v31->fields.mixDuration = 0.0;
          v31->fields.trackTime = v21;
          facialParameterNames = this->fields._facialParameterNames;
          if ( facialParameterNames )
          {
            if ( System_Collections_Generic_HashSet_object___Contains(
                   (System_Collections_Generic_HashSet_object__o *)facialParameterNames,
                   (Il2CppObject *)parameterName,
                   (const MethodInfo_5C76C88 *)Method_System_Collections_Generic_HashSet_string__Contains__) )
            {
              return;
            }
            v33 = 1;
            while ( 1 )
            {
              v34 = this->fields._animationState;
              if ( !v34 )
                goto LABEL_47;
              v35 = Spine_AnimationState__GetCurrent(v34, v33, 0LL);
              if ( !v35 )
                break;
              v36 = v35->fields.animation;
              v37 = v35;
              if ( v36 )
                name = v36->fields.name;
              else
                name = 0LL;
              if ( System_String__op_Equality(name, parameterName, 0LL) )
                return;
              if ( Spine_TrackEntry__get_IsEmptyAnimation(v37, 0LL) )
                break;
              if ( ++v33 == 5 )
                return;
            }
            v39 = this->fields._animationState;
            if ( v39 )
            {
              v40 = Spine_AnimationState__SetAnimation_133484248(v39, v33, v15, 0, 0LL);
              if ( v40 )
              {
                v40->fields.timeScale = 0.0;
                v40->fields.mixBlend = 0;
                v40->fields.mixDuration = 0.0;
                v40->fields.trackTime = 0.0;
                return;
              }
            }
          }
        }
      }
LABEL_47:
      sub_3D658A8();
    }
  }
}

这个函数已经大概实现了Param的播放和图层的重新排序
这东西实际是一个比较新的技术Spine主要负责播放主动作剩下的表情图层排序主要交给类似live2d的逻辑通过主动作中的事件实现


但是我没找到表情的参数


43是缺失的所以翻剧情看看有没有啥东西