更便捷地提取梅露露的炼金工房资源
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

浙公网安备 33010602011771号