求助大神

感谢整理工具,这玩意手动整理起来太折磨了 :innocent:

牛逼,感谢大佬

大佬给了纯爱航线的spine 整理工具,我就来将原帖游戏的spine整理一下吧。

由于技术问题,我提取的skel和atlas的效率不是很理想(虽然可以用)。所以就借用一下纯爱航线的spine 整理工具。(不知道为什么把大佬的工具在hex修改salt也不能提取png)。

先用纯爱航线的spine 整理工具解密解压astc,提取skel和atlas;

然后用下面这个提取json格式的atlas文件,记得输出到新的文件夹中(偷懒,提取png的脚本不会识别json是否有atlas结构);

json版atlas寻找.py
import os
import shutil
import json
import time

def remove_quotes(path):
    """去除路径字符串中的引号"""
    return path.strip('"\'')

def filter_json_files(input_folder, output_folder, target_strings):
    """
    遍历输入文件夹,筛选包含特定字符串的JSON文件并复制到输出文件夹
    
    参数:
        input_folder (str): 输入文件夹路径
        output_folder (str): 输出文件夹路径
        target_strings (list): 需要查找的目标字符串列表
    """
    # 去除引号
    input_folder = remove_quotes(input_folder)
    output_folder = remove_quotes(output_folder)

    # 确保输出文件夹存在
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
        print(f"创建输出文件夹: {output_folder}")
    
    # 计数器
    total_json_files = 0
    matched_files = 0
    processed_files = 0
    
    # 开始计时
    start_time = time.time()
    
    # 遍历输入文件夹统计总数
    print(f"\n开始扫描文件夹: {input_folder}")
    for root, _, files in os.walk(input_folder):
        for file in files:
            if file.lower().endswith('.json'):
                total_json_files += 1
    
    print(f"找到 {total_json_files} 个JSON文件,开始筛选包含以下字符串的文件: {target_strings}")
    
    # 再次遍历处理文件
    for root, _, files in os.walk(input_folder):
        for file in files:
            if file.lower().endswith('.json'):
                processed_files += 1
                file_path = os.path.join(root, file)
                
                # 显示进度
                progress = (processed_files / total_json_files) * 100
                print(f"\r处理进度: {processed_files}/{total_json_files} ({progress:.1f}%) - 检查 {file}", end="")
                
                # 读取并解析JSON文件
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        data = json.load(f)
                    
                    # 检查是否包含所有目标字符串
                    if contains_all_strings(data, target_strings):
                        # 复制文件到输出文件夹
                        relative_path = os.path.relpath(file_path, input_folder)
                        dest_path = os.path.join(output_folder, relative_path)
                        
                        # 确保目标文件夹存在
                        dest_dir = os.path.dirname(dest_path)
                        if not os.path.exists(dest_dir):
                            os.makedirs(dest_dir)
                            print(f"\n创建子文件夹: {dest_dir}")
                        
                        shutil.copy2(file_path, dest_path)
                        matched_files += 1
                        print(f"\n匹配成功: {relative_path}")
                
                except json.JSONDecodeError:
                    print(f"\n警告: {file_path} 不是有效的JSON文件")
                except Exception as e:
                    print(f"\n处理文件 {file_path} 时出错: {e}")
    
    # 结束计时
    elapsed_time = time.time() - start_time
    
    # 打印统计信息
    print(f"\n\n处理完成!")
    print(f"共扫描 {total_json_files} 个JSON文件")
    print(f"匹配并复制了 {matched_files} 个文件到 {output_folder}")
    print(f"总耗时: {elapsed_time:.2f} 秒")

def contains_all_strings(obj, strings):
    """递归检查对象的所有字符串值是否包含所有目标字符串"""
    if isinstance(obj, str):
        # 检查当前字符串是否包含所有目标字符串
        return all(s in obj for s in strings)
    
    elif isinstance(obj, dict):
        # 递归检查字典的所有值
        for value in obj.values():
            if contains_all_strings(value, strings):
                return True
    
    elif isinstance(obj, list):
        # 递归检查列表的所有元素
        for item in obj:
            if contains_all_strings(item, strings):
                return True
    
    return False

def main():
    """交互式主函数"""
    target_strings = ['size:', 'format:', 'filter:']  # 目标字符串
    
    while True:
        print("\n" + "="*40)
        print("=== JSON文件筛选复制工具 ===")
        print("="*40)
        print(f"输入文件夹路径,程序将筛选包含以下字符串的JSON文件: {target_strings}")
        
        # 获取输入文件夹路径
        input_folder = input("\n请输入源文件夹路径 (输入'q'退出): ").strip()
        
        # 检查退出条件
        if input_folder.lower() == 'q':
            print("程序已退出。")
            break
        
        input_folder = remove_quotes(input_folder)
        if not os.path.exists(input_folder):
            print(f"错误: 文件夹 {input_folder} 不存在")
            continue
        
        # 获取输出文件夹路径
        output_folder = input("请输入输出文件夹路径: ").strip()
        output_folder = remove_quotes(output_folder)
        
        # 确认输出文件夹
        if os.path.exists(output_folder):
            confirm = input(f"警告: 输出文件夹 {output_folder} 已存在,可能会覆盖文件。继续? (y/n): ").strip().lower()
            if confirm != 'y':
                print("操作已取消")
                continue
        
        # 执行筛选和复制
        filter_json_files(input_folder, output_folder, target_strings)
        
        # 询问是否继续
        continue_choice = input("\n是否继续执行? (y/n): ").strip().lower()
        if continue_choice != 'y':
            print("程序已退出。")
            break

if __name__ == "__main__":
    main()

再用下面这个提取png;

提取png.py
import os
import re
import hashlib
import shutil
from pathlib import Path
import concurrent.futures
import multiprocessing

# Base64字符集和值映射(采用你提供的定义方式)
BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
BASE64_VALUES = [0] * 128
for idx, char in enumerate(BASE64_CHARS):
    BASE64_VALUES[ord(char)] = idx

HEX_CHARS = list('0123456789abcdef')
_t = ['', '', '', '']
UUID_TEMPLATE = _t + _t + ['-'] + _t + ['-'] + _t + ['-'] + _t + ['-'] + _t + _t + _t
INDICES = [i for i, x in enumerate(UUID_TEMPLATE) if x != '-']

def decode_uuid(base64_str):
    """将Base64编码的字符串还原为UUID格式(采用你提供的实现逻辑)"""
    if len(base64_str) != 22:
        return base64_str
    result = UUID_TEMPLATE.copy()

    result[0] = base64_str[0]
    result[1] = base64_str[1]

    j = 2
    for i in range(2, 22, 2):
        lhs = BASE64_VALUES[ord(base64_str[i])]
        rhs = BASE64_VALUES[ord(base64_str[i + 1])]

        result[INDICES[j]] = HEX_CHARS[lhs >> 2]
        j += 1
        result[INDICES[j]] = HEX_CHARS[((lhs & 3) << 2) | (rhs >> 4)]
        j += 1
        result[INDICES[j]] = HEX_CHARS[rhs & 0xF]
        j += 1

    print(f"解码UUID过程: base64_str={base64_str} -> uuid={''.join(result)}")
    return ''.join(result)

def calculate_formatted_md5(uuid_str, suffix="120e8962b50b34d26e49730d884a32e4"):
    """计算MD5并格式化为指定形式"""
    combined_str = f"{uuid_str}.astc{suffix}"
    print(f"计算MD5输入: {combined_str}")
    md5_hash = hashlib.md5(combined_str.encode()).hexdigest()
    print(f"原始MD5哈希: {md5_hash} (长度: {len(md5_hash)})")

    # 格式化为8-5-5-5-9的形式
    if len(md5_hash) >= 32:
        formatted = f"{md5_hash[:8]}-{md5_hash[8:13]}-{md5_hash[13:18]}-{md5_hash[18:23]}-{md5_hash[23:]}.png"
        print(f"格式化后的文件名: {formatted}")
        return formatted
    else:
        print(f"警告: MD5哈希长度不足,使用原始哈希: {md5_hash}.png")
        return f"{md5_hash}.png"

def find_and_copy_file(filename, source_dir, dest_dir, json_filename):
    """在源目录中查找文件并复制到目标目录的子文件夹中"""
    if not os.path.exists(source_dir):
        print(f"错误: 源目录 {source_dir} 不存在")
        return False

    # 提取JSON文件名(不包含扩展名)
    json_base_name = os.path.splitext(json_filename)[0]

    # 寻找包含JSON文件名的子文件夹
    found_subfolder = None
    for subfolder in os.listdir(dest_dir):
        subfolder_path = os.path.join(dest_dir, subfolder)
        if os.path.isdir(subfolder_path) and json_base_name in subfolder:
            found_subfolder = subfolder_path
            print(f"提示: 找到包含JSON文件名的子文件夹 {found_subfolder},将使用此文件夹")
            break

    # 如果没有找到,使用output子文件夹
    if found_subfolder is None:
        output_subfolder = os.path.join(dest_dir, "output")
        if not os.path.exists(output_subfolder):
            os.makedirs(output_subfolder)
            print(f"创建子文件夹: {output_subfolder}")
        found_subfolder = output_subfolder

    print(f"搜索文件: {filename} 在目录: {source_dir}")
    found = False
    for root, _, files in os.walk(source_dir):
        if filename in files:
            source_path = os.path.join(root, filename)
            dest_path = os.path.join(found_subfolder, filename)
            print(f"找到文件: {source_path},准备复制到: {dest_path}")

            try:
                # 覆盖已存在的文件
                if os.path.exists(dest_path):
                    os.remove(dest_path)
                shutil.copy2(source_path, dest_path)
                print(f"成功复制: {source_path} -> {dest_path}")
                found = True
                break
            except Exception as e:
                print(f"复制文件时出错: {e}")
                return False

    if not found:
        print(f"警告: 未找到文件 {filename} 在目录 {source_dir} 中")
    return found

def rename_png_using_atlas(png_dir, atlas_filename, png_filename):
    """使用atlas文件中的信息重命名PNG文件(支持覆盖已存在文件)"""
    atlas_path = os.path.join(png_dir, atlas_filename)
    print(f"尝试读取atlas文件: {atlas_path}")

    if not os.path.exists(atlas_path):
        print(f"警告: 未找到atlas文件 {atlas_path}")
        return False

    try:
        with open(atlas_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
            print(f"atlas文件包含 {len(lines)} 行")
            for i, line in enumerate(lines):
                line = line.strip()
                print(f"处理第 {i+1} 行: '{line}'")
                if '.png' in line:
                    parts = line.split()
                    print(f"分割后的行内容: {parts}")
                    if parts:
                        new_name = parts[0]
                        print(f"找到新文件名: {new_name}")
                        old_png_path = os.path.join(png_dir, png_filename)
                        new_png_path = os.path.join(png_dir, new_name)

                        print(f"原PNG路径: {old_png_path}")
                        print(f"新PNG路径: {new_png_path}")
                        if os.path.exists(old_png_path):
                            # 覆盖已存在的新文件名
                            if os.path.exists(new_png_path):
                                os.remove(new_png_path)
                            os.rename(old_png_path, new_png_path)
                            print(f"成功重命名: {png_filename} -> {new_name}")
                            return True
                        else:
                            print(f"警告: 未找到PNG文件 {old_png_path}")
                            return False
            print(f"警告: 在atlas文件中未找到包含'.png'的行")
            return False
    except Exception as e:
        print(f"读取atlas文件时出错: {e}")
        return False

def process_json_file(json_path, png_dir, output_dir):
    """处理单个JSON文件"""
    try:
        json_filename = os.path.basename(json_path)
        print(f"\n===== 开始处理JSON文件: {json_filename} =====")

        # 读取前256字节
        with open(json_path, 'r', encoding='utf-8', errors='ignore') as f:
            first_256 = f.read(256)
        print(f"读取JSON文件前256字节: {first_256[:100]}... (完整长度: {len(first_256)})")

        # 提取符合模式的字符串 (22字符+@+5字符)
        pattern = r'([A-Za-z0-9+/]{22})@[A-Za-z0-9]{5}'
        matches = re.findall(pattern, first_256)
        print(f"正则表达式匹配结果: {matches} (共 {len(matches)} 个匹配)")

        if not matches:
            print(f"警告: 在{json_filename}中未找到符合模式的字符串")
            return

        for i, match in enumerate(matches):
            print(f"\n--- 处理第 {i+1} 个匹配项 ---")
            base64_str = match
            print(f"找到Base64字符串: {base64_str}")

            # 还原UUID(使用新的解码逻辑)
            uuid = decode_uuid(base64_str)
            print(f"还原的UUID: {uuid}")

            # 计算格式化的MD5
            formatted_md5 = calculate_formatted_md5(uuid)
            print(f"生成的文件名: {formatted_md5}")

            # 查找并复制文件
            found_subfolder = None  # 初始化变量
            if find_and_copy_file(formatted_md5, png_dir, output_dir, json_filename):
                # 获取实际使用的子文件夹路径(从find_and_copy_file函数中获取)
                json_base_name = os.path.splitext(json_filename)[0]
                for subfolder in os.listdir(output_dir):
                    subfolder_path = os.path.join(output_dir, subfolder)
                    if os.path.isdir(subfolder_path) and json_base_name in subfolder:
                        found_subfolder = subfolder_path
                        break

                # 如果没有找到,使用output子文件夹
                if found_subfolder is None:
                    found_subfolder = os.path.join(output_dir, "output")

                # 查找atlas文件并重命名PNG
                print(f"atlas文件搜索目录: {found_subfolder}")

                # 查找atlas文件并增加存在性检查
                if os.path.exists(found_subfolder):
                    atlas_files = [f for f in os.listdir(found_subfolder) if f.endswith('.atlas')]
                    print(f"找到 {len(atlas_files)} 个atlas文件: {atlas_files}")
                    if atlas_files:
                        atlas_file = atlas_files[0]
                        print(f"使用atlas文件: {atlas_file}")
                        try:
                            rename_png_using_atlas(found_subfolder, atlas_file, formatted_md5)
                        except Exception as e:
                            print(f"重命名PNG时出错: {e}")
                    else:
                        print(f"警告: 在{found_subfolder}中未找到atlas文件")
                else:
                    print(f"警告: 目录{found_subfolder}不存在")

    except IndexError as e:
        print(f"\n===== 处理文件{json_path}时发生索引越界错误 =====")
        print(f"错误详情: {e}")
        print(f"当前JSON文件名: {json_filename}")
        # 打印可能导致错误的上下文信息
        try:
            with open(json_path, 'r', encoding='utf-8', errors='ignore') as f:
                print(f"JSON文件前500字节内容: {f.read(500)}...")
        except:
            print("无法读取JSON文件内容进行调试")
    except Exception as e:
        print(f"处理文件{json_path}时出错: {e}")

def main():
    """交互式主函数"""
    print("=== UUID还原与资源处理工具 (调试模式) ===")
    print("本程序将详细显示字符串处理的每一步过程")
    print("输入'q'或'exit'退出程序")

    while True:
        # 获取用户输入
        json_input = input("\n请输入JSON文件或文件夹路径: ").strip()
        if json_input.lower() in ['q', 'exit']:
            print("程序已退出。")
            break

        png_dir = input("请输入PNG文件夹路径: ").strip()
        if png_dir.lower() in ['q', 'exit']:
            print("程序已退出。")
            break

        output_dir = input("请输入输出文件夹路径: ").strip()
        if output_dir.lower() in ['q', 'exit']:
            print("程序已退出。")
            break

        # 确保输出目录存在
        os.makedirs(output_dir, exist_ok=True)

        try:
            if os.path.isfile(json_input) and json_input.endswith('.json'):
                # 处理单个JSON文件
                process_json_file(json_input, png_dir, output_dir)
            elif os.path.isdir(json_input):
                # 处理文件夹中的所有JSON文件
                json_files = [os.path.join(json_input, f) for f in os.listdir(json_input)
                              if f.endswith('.json')]

                if not json_files:
                    print(f"警告: 在{json_input}中未找到JSON文件")
                else:
                    print(f"找到{len(json_files)}个JSON文件,开始处理...")
                    # 获取CPU核心数
                    num_threads = multiprocessing.cpu_count()
                    with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor:
                        futures = [executor.submit(process_json_file, json_file, png_dir, output_dir) for json_file in json_files]
                        for future in concurrent.futures.as_completed(futures):
                            try:
                                future.result()
                            except Exception as e:
                                print(f"处理过程中发生错误: {e}")
            else:
                print(f"错误: 路径{json_input}不是有效的JSON文件或文件夹")
        except Exception as e:
            print(f"处理过程中发生错误: {e}")

        # 询问是否继续
        continue_choice = input("\n是否继续处理其他文件? (y/n): ").strip().lower()
        if continue_choice != 'y':
            print("程序已退出。")
            break

if __name__ == "__main__":
    main()

前两天整理工具天更新了v2版本,整理输出目录有所调整,其实早就写好了,只是忘记到这里更新了

1 个赞