更便捷地提取梅露露的炼金工房资源

extract_all_g1t.py

import struct
import os
import sys
import glob

def extract_g1t_from_pssg(pssg_path):
    """从单个 PSSG 文件中提取 G1T 资源"""
    # 生成输出文件夹名:原文件名 + "_g1t"
    base_name = os.path.splitext(os.path.basename(pssg_path))[0]
    output_dir = os.path.join(os.path.dirname(pssg_path) or '.', f"{base_name}_g1t")
    os.makedirs(output_dir, exist_ok=True)

    print(f"Processing: {pssg_path} -> {output_dir}")

    with open(pssg_path, mode='rb') as file:
        filecontent = file.read()
        # 搜索特征字节序列:\x00\x00\x00\x0a\x00\x00\x00\x04
        index = filecontent.find(b'\x00\x00\x00\x0a\x00\x00\x00\x04')
        extracted_count = 0

        while index > -1:
            # 获取数据块大小 (大端 uint32)
            file.seek(index + 8)
            size = struct.unpack('>I', file.read(4))[0]

            # 跳过 11 字节(根据原始格式)
            file.seek(11, 1)
            # 读取文件名长度 (大端 int8)
            namelength = struct.unpack('>b', file.read(1))[0]
            # 读取文件名
            name = file.read(namelength).decode('ascii')

            # 再跳过 12 字节到达数据区
            file.seek(12, 1)
            data = file.read(size)

            # 写入文件
            output_path = os.path.join(output_dir, name)
            with open(output_path, "wb") as g1t_file:
                g1t_file.write(data)

            extracted_count += 1
            # 继续搜索下一个匹配
            index = filecontent.find(b'\x00\x00\x00\x0a\x00\x00\x00\x04', index + 8)

        print(f"  Extracted {extracted_count} file(s) from {pssg_path}")

def main():
    # 获取当前目录下所有 .pssg 文件(不区分大小写)
    pssg_files = glob.glob("*.pssg") + glob.glob("*.PSSG")
    pssg_files = list(set(pssg_files))  # 去重

    if not pssg_files:
        print("No .pssg files found in current directory.")
        sys.exit(1)

    print(f"Found {len(pssg_files)} PSSG file(s).")
    for pssg_file in pssg_files:
        extract_g1t_from_pssg(pssg_file)

    print("Extraction complete.")

if __name__ == "__main__":
    main()

g1t_to_png.py

#!/usr/bin/env python3
"""
批量将 .g1t 文件转换为 PNG(保持原始方向)

用法 1(传统模式):
    python g1t_to_png.py <输入目录> [输出目录] [--recursive] [--flat]

用法 2(自动扫描 _g1t 文件夹):
    python g1t_to_png.py --scan-g1t-folders [根目录] [--flat]

默认:gust_g1t.exe 与脚本在同一目录下
"""

import sys
import argparse
import subprocess
import shutil
from pathlib import Path

import imageio.v3 as iio
import numpy as np


def convert_one_g1t(g1t_path: Path, png_path: Path, gust_exe: Path) -> None:
    """将单个 .g1t 转换为 PNG(修正上下颠倒)"""
    print(f"    [解包] {g1t_path.name}")
    abs_g1t = g1t_path.absolute()
    result = subprocess.run(
        [str(gust_exe), str(abs_g1t)],
        capture_output=False,
        check=False
    )
    if result.returncode != 0:
        raise RuntimeError(f"gust_g1t.exe 返回码 {result.returncode}")

    # 定位生成的 DDS 文件
    dds_dir = g1t_path.parent / g1t_path.stem
    dds_file = dds_dir / "000.dds"
    if not dds_file.is_file():
        dds_list = list(dds_dir.glob("*.dds"))
        if not dds_list:
            raise RuntimeError(f"未找到 DDS 文件,预期 {dds_file}")
        dds_file = dds_list[0]
        print(f"    [提示] 使用 {dds_file.name}")

    # 读取 DDS → 上下翻转 → 保存 PNG
    print(f"    [转换] 读取 DDS -> PNG(修正上下颠倒)")
    img = iio.imread(dds_file)
    img = np.flipud(img)          # 修正垂直方向
    png_path.parent.mkdir(parents=True, exist_ok=True)
    iio.imwrite(png_path, img)

    # 删除临时子目录
    shutil.rmtree(dds_dir)
    print(f"    [清理] 删除临时目录 {dds_dir}")


def process_g1t_folder(g1t_folder: Path, output_base: Path, gust_exe: Path, flat: bool) -> None:
    """
    处理单个 _g1t 文件夹中的所有 .g1t 文件
    g1t_folder:  输入目录(如 ./some_name_g1t)
    output_base: 输出根目录(如 ./some_name_png)
    flat:        是否平铺输出(不保留子目录结构)
    """
    print(f"\n>>> 处理文件夹: {g1t_folder}")
    print(f"    输出目录: {output_base}")

    # 收集所有 .g1t 文件(递归)
    g1t_files = list(g1t_folder.rglob("*.g1t"))
    if not g1t_files:
        print(f"    警告:{g1t_folder} 中没有 .g1t 文件,跳过")
        return

    success = 0
    for g1t_path in g1t_files:
        if flat:
            # 平铺模式:直接放在输出根目录下,用原文件名
            png_path = output_base / (g1t_path.stem + ".png")
        else:
            # 保留相对路径:相对于 g1t_folder 的路径 + .png
            rel = g1t_path.relative_to(g1t_folder)
            png_path = output_base / rel.with_suffix(".png")

        try:
            convert_one_g1t(g1t_path, png_path, gust_exe)
            success += 1
            print(f"        ✓ 完成 -> {png_path}")
        except Exception as e:
            print(f"        ✗ 失败: {e}")

    print(f"    文件夹处理完成:成功 {success} / {len(g1t_files)}")


def main():
    script_dir = Path(__file__).parent
    default_exe = script_dir / "gust_g1t.exe"

    parser = argparse.ArgumentParser(description="批量转换 .g1t 为 PNG(原始方向)")
    # 传统模式参数
    parser.add_argument("input_dir", nargs="?", type=Path, default=None,
                        help="包含 .g1t 的输入目录(传统模式)")
    parser.add_argument("output_dir", nargs="?", type=Path, default=None,
                        help="输出 PNG 目录(传统模式,默认:输入目录/g1t_png)")
    parser.add_argument("--exe", type=Path, default=default_exe,
                        help=f"gust_g1t.exe 路径(默认:{default_exe})")
    parser.add_argument("--recursive", action="store_true",
                        help="传统模式:递归搜索子目录中的 .g1t")
    parser.add_argument("--flat", action="store_true",
                        help="平铺输出,不保留子目录结构(传统模式和扫描模式均可用)")

    # 扫描模式参数
    parser.add_argument("--scan-g1t-folders", action="store_true",
                        help="自动扫描当前目录(或指定根目录)下所有以 _g1t 结尾的文件夹,"
                             "并转换为对应的 _png 文件夹")

    args = parser.parse_args()

    # 检查 gust_g1t.exe
    gust_exe = args.exe
    if not gust_exe.is_file():
        print(f"错误:未找到 gust_g1t.exe -> {gust_exe}")
        sys.exit(1)

    # ---------- 扫描模式 ----------
    if args.scan_g1t_folders:
        # 根目录:如果提供了 input_dir 则用它,否则为当前目录
        root_dir = args.input_dir if args.input_dir else Path.cwd()
        if not root_dir.is_dir():
            print(f"错误:根目录不存在 {root_dir}")
            sys.exit(1)

        print(f"扫描模式:在 {root_dir} 中查找以 _g1t 结尾的文件夹")
        # 查找所有以 _g1t 结尾的目录
        g1t_folders = [p for p in root_dir.iterdir() if p.is_dir() and p.name.endswith("_g1t")]
        if not g1t_folders:
            print(f"未找到任何以 _g1t 结尾的文件夹")
            return

        print(f"找到 {len(g1t_folders)} 个待处理文件夹")
        print(f"转换工具: {gust_exe}")
        print("=" * 60)

        for g1t_folder in g1t_folders:
            # 生成对应的输出文件夹名:将 _g1t 替换为 _png
            output_folder = g1t_folder.parent / (g1t_folder.name.replace("_g1t", "_png"))
            process_g1t_folder(g1t_folder, output_folder, gust_exe, flat=args.flat)

        print("\n全部处理完成!")
        return

    # ---------- 传统模式 ----------
    if args.input_dir is None:
        parser.print_help()
        sys.exit(1)

    input_dir = args.input_dir
    if not input_dir.is_dir():
        print(f"错误:输入目录不存在 {input_dir}")
        sys.exit(1)

    output_dir = args.output_dir or (input_dir / "g1t_png")
    # 收集 .g1t 文件
    if args.recursive:
        g1t_files = list(input_dir.rglob("*.g1t"))
    else:
        g1t_files = list(input_dir.glob("*.g1t"))

    if not g1t_files:
        print(f"在 {input_dir} 中未找到 .g1t 文件")
        return

    print(f"传统模式:找到 {len(g1t_files)} 个 .g1t 文件")
    print(f"转换工具: {gust_exe}")
    print(f"输出目录: {output_dir}")
    print("=" * 60)

    success = 0
    for idx, g1t_path in enumerate(g1t_files, 1):
        print(f"\n[{idx}/{len(g1t_files)}] 处理: {g1t_path}")
        if args.flat:
            png_path = output_dir / (g1t_path.stem + ".png")
        else:
            rel = g1t_path.relative_to(input_dir)
            png_path = output_dir / rel.with_suffix(".png")

        try:
            convert_one_g1t(g1t_path, png_path, gust_exe)
            success += 1
            print(f"    ✓ 完成 -> {png_path}")
        except Exception as e:
            print(f"    ✗ 失败: {e}")

    print(f"\n转换完成:成功 {success} / {len(g1t_files)}")
    if success > 0:
        print(f"输出目录:{output_dir}")


if __name__ == "__main__":
    main()

del_g1t_folder.py

import os
import shutil

def delete_folders_ending_with_g1t(root_dir='.'):
    """
    递归删除 root_dir 下所有以 '_g1t' 结尾的文件夹。
    
    Args:
        root_dir (str): 要搜索的根目录,默认为当前目录。
    """
    # 遍历目录树
    for dirpath, dirnames, filenames in os.walk(root_dir, topdown=False):
        for dirname in dirnames:
            if dirname.endswith('_g1t'):
                full_path = os.path.join(dirpath, dirname)
                try:
                    shutil.rmtree(full_path)
                    print(f"已删除: {full_path}")
                except Exception as e:
                    print(f"删除失败 {full_path}: {e}")

if __name__ == "__main__":
    # 示例:删除当前目录下所有以 _g1t 结尾的文件夹
    delete_folders_ending_with_g1t()

首先建一个文件夹,将这三个文件放进去,然后把 PSSG 文件也放进去,然后下载 https://github.com/VitaSmith/gust_tools/releases/download/v1.58/gust_tools.zip (打不开从 https://gitee.com/nbda1121440/gust_tools 下载),取出其中的gust_g1t.exe放文件夹里,然后依次执行

python .\extact_all_g1t.py
python g1t_to_png.py --scan-g1t-folders
python .\del_g1t_folder.py
posted @ 2026-06-09 18:48  hellozjf  阅读(8)  评论(0)    收藏  举报