extract_all_g1t.py
import struct
import os
import sys
import globdef 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\x04index = filecontent.find(b'\x00\x00\x00\x0a\x00\x00\x00\x04')extracted_count = 0while 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 Pathimport imageio.v3 as iio
import numpy as npdef 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.stemdds_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 → 上下翻转 → 保存 PNGprint(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 文件,跳过")returnsuccess = 0for g1t_path in g1t_files:if flat:# 平铺模式:直接放在输出根目录下,用原文件名png_path = output_base / (g1t_path.stem + ".png")else:# 保留相对路径:相对于 g1t_folder 的路径 + .pngrel = 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 += 1print(f" ✓ 完成 -> {png_path}")except Exception as e:print(f" ✗ 失败: {e}")print(f" 文件夹处理完成:成功 {success} / {len(g1t_files)}")def main():script_dir = Path(__file__).parentdefault_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.exegust_exe = args.exeif 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 结尾的文件夹")returnprint(f"找到 {len(g1t_folders)} 个待处理文件夹")print(f"转换工具: {gust_exe}")print("=" * 60)for g1t_folder in g1t_folders:# 生成对应的输出文件夹名:将 _g1t 替换为 _pngoutput_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_dirif 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 文件")returnprint(f"传统模式:找到 {len(g1t_files)} 个 .g1t 文件")print(f"转换工具: {gust_exe}")print(f"输出目录: {output_dir}")print("=" * 60)success = 0for 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 += 1print(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 shutildef 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 文件也放进去,然后依次执行
python .\extact_all_g1t.py
python g1t_to_png.py --scan-g1t-folders
python .\del_g1t_folder.py
