cocos游戏json(skel)还原求助

大佬捞捞!如题,最近开始在学习解cocos游戏时遇到了一个奇怪的问题,对散乱文件名还原后,spine资源的json文件缺少典型值,spine版本信息以及动作资源?而atlas和png则完全正常,目前在两款游戏中遇到了上述问题(彼岸倩影录(qoo下载,典型3a游戏,这里单独把spine所在文件夹用于求助)和叫我大掌柜)。

未还原前import(json所在文件)有120mb,而还原后只有40mb,初步判断json同绘笔西游一样拼接,但是还原文件名后很多数据直接缺失,导致直接卡住,不知道应该怎么做,希望大佬指点一下,万分感谢!

资源和脚本

import json
import os
import sys
import shutil
import re
from pathlib import Path
from typing import Dict, Tuple

# ================== 配置区 (在这里修改路径) ==================
# 注意:路径中的 \ 需要写成 \\ 或者 / ,或者在引号前加 r

# 1. 输入目录 (包含乱七八糟文件的文件夹)
INPUT_DIR = Path(r"C:\Users\luota\Desktop\杂项\spine资源目录\native")

# 2. 输出目录 (还原后的文件要去哪里)
OUTPUT_ROOT = Path(r"C:\Users\luota\Desktop\杂项\spine资源目录\png")

# 3. 配置文件路径
CONFIG_PATH = Path(r"C:\Users\luota\Desktop\杂项\spine资源目录\cc.config.json")

# ================== 核心逻辑函数 ==================

UUID36_RE = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
BASE64_KEYS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="

def uuid_to_short_id(uuid_str: str) -> str:
    """将标准 UUID (36位) 压缩为短 ID (22位)"""
    if not UUID36_RE.match(uuid_str):
        return uuid_str
    clean_uuid = uuid_str.replace("-", "")
    res = [clean_uuid[:2]]
    for i in range(2, 32, 3):
        val = int(clean_uuid[i:i + 3], 16)
        res.append(BASE64_KEYS[(val >> 6) & 0x3F])
        res.append(BASE64_KEYS[val & 0x3F])
    return "".join(res)

def extract_id_from_filename(filename_stem: str) -> str:
    """从文件名提取 ID"""
    clean_name = filename_stem.split(".")[0]
    if UUID36_RE.match(clean_name):
        return uuid_to_short_id(clean_name)
    return clean_name

def build_mapping(config_path: Path) -> Dict[str, str]:
    """解析 config.json"""
    if not config_path.exists():
        print(f"[错误] 配置文件不存在: {config_path}")
        return {}
    try:
        with config_path.open("r", encoding="utf-8") as f:
            cfg = json.load(f)
        uuids = cfg.get("uuids", [])
        paths = cfg.get("paths", {})
        if not uuids or not paths:
            print("[警告] config.json 数据为空")
            return {}
        result_map = {}
        for key, path_list in paths.items():
            try:
                index = int(key)
                if 0 <= index < len(uuids):
                    short_id = uuid_to_short_id(uuids[index])
                    if path_list:
                        result_map[short_id] = path_list[0]
            except (ValueError, IndexError):
                continue
        print(f"✅ 成功加载映射表: {len(result_map)} 条")
        return result_map
    except Exception as e:
        print(f"[错误] 读取配置失败: {e}")
        return {}

def process_files(input_dir: Path, output_root: Path, error_dir: Path, id_map: Dict[str, str]):
    """核心处理循环"""
    # 确保目录存在
    output_root.mkdir(parents=True, exist_ok=True)
    error_dir.mkdir(parents=True, exist_ok=True)
    
    # 检查输入目录是否存在
    if not input_dir.exists():
        print(f"[严重错误] 输入目录不存在: {input_dir}")
        return

    files = list(input_dir.rglob("*"))
    print(f"🔍 发现 {len(files)} 个文件,开始处理...")
    
    success_count = 0
    fail_count = 0

    for src in files:
        if not src.is_file():
            continue

        file_id = extract_id_from_filename(src.stem)
        target_path = id_map.get(file_id)

        if not target_path:
            # 匹配失败 -> 移动至错误目录
            dest = error_dir / src.name
            try:
                shutil.move(str(src), str(dest))
                fail_count += 1
            except Exception as e:
                print(f"[!] 移动错误文件失败: {src.name}")
            continue

        # 匹配成功 -> 还原
        suffix = ".skel" if src.suffix.lower() == ".bin" else src.suffix
        dest = output_root / (target_path + suffix)

        try:
            dest.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(str(src), str(dest))
            success_count += 1
        except Exception as e:
            print(f"[!] 还原失败 {src.name}: {e}")
            fail_count += 1

    print(f"\n========== 处理完成 ==========")
    print(f"成功: {success_count}")
    print(f"失败/未匹配: {fail_count}")

# ================== 主程序入口 ==================

def main():
    # 使用代码顶部配置的变量
    # 错误目录自动设在输出目录的同级
    error_dir = OUTPUT_ROOT.parent / "ERRORRes"

    print(f"📂 输入目录: {INPUT_DIR}")
    print(f"📤 输出目录: {OUTPUT_ROOT}")
    print(f"⚙️ 配置文件: {CONFIG_PATH}")

    # 执行
    id_map = build_mapping(CONFIG_PATH)
    if id_map:
        process_files(INPUT_DIR, OUTPUT_ROOT, error_dir, id_map)
    
    input("\n按回车键退出...")

if __name__ == "__main__":
    main()

https://drive.google.com/file/d/1GueBjvd_HiBQbBgK9G_nrA8tXLq1Eanp/view?usp=drive_link

抱歉忘了开共享了emm

我一般找对应的文件是按照uuid的方式去找,你可以试试按照顺序去找对应的行数和对应的文件

  1. 分类时,会有重名文件相互覆盖(尤其是json),通常会有3个同名的json(其中一个是骨骼,其余两个没用),这里选择size最大的保存即可。需要注意的是这个游戏把骨骼存放在了import里面的资源配置json里面,而你选择 INPUT_DIR = Path(r"C:\Users\luota\Desktop\杂项\spine资源目录\native") 就会忽略掉import,改为上一级INPUT_DIR = Path(r"C:\Users\luota\Desktop\杂项\spine资源目录")即可
  2. 按照步骤1处理后,接下来只需要把骨骼json提取出来,这个游戏把骨骼和其余数据绑定在了一起,递归查找INPUT_PATH 路径下所有json文件,找到如下形式的json并删除多余部分,只保留骨骼部分
[
  1,
  ["b6Yj40aflHoZ8bNfzLUE6v@6c48a"],
  0,
  [
    [
      "sp.SkeletonData",
      ["_name", "_atlasText", "textureNames", "_skeletonJson", "textures"],
      -1,
      3
    ]
  ],
  [[0, 0, 1, 2, 3, 4, 5]],
  [
    [
      0,
      "interactBg_0015",
      "\r\ninteractBg_0015.png\r\nsize: 1927,1927\r\nformat: RGBA8888\r\nfilter: Linear,Linear\r\nrepeat: none\r\nbj_1\r\n  rotate: false\r\n  xy: 870, 1439\r\n  size: 284, 486\r\n  orig: 284, 626\r\n  offset: 0, 13\r\n  index: -1\r\nbj_2\r\n  rotate: true\r\n  xy: 870, 4\r\n  size: 515, 161\r\n  orig: 515, 161\r\n  offset: 0, 0\r\n  index: -1\r\nbj_3\r\n  rotate: false\r\n  xy: 2, 3\r\n  size: 866, 1922\r\n  orig: 866, 1922\r\n  offset: 0, 0\r\n  index: -1\r\n",
      ["interactBg_0015.png"],
      {
        // 骨骼... 
      },
      [0]
    ]
  ],
  0,
  0,
  [0],
  [-1],
  [0]
] 

听起来跟欲炼江湖差不多,有大佬可以帮忙看看吗 - #2,来自 cyb464b6
你试试行不行

感谢大佬,成功了,万分感谢

谢谢大佬解惑,感谢!