101 lines
3.4 KiB
Python
101 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
||
import os
|
||
import re
|
||
import sys
|
||
import shutil
|
||
import tempfile
|
||
import subprocess
|
||
import unicodedata
|
||
|
||
def sanitize_name(name):
|
||
"""Normalize game names: remove symbols, accents, and replace ' - ' with space."""
|
||
n = unicodedata.normalize('NFKD', name).encode('ascii','ignore').decode('ascii')
|
||
n = n.replace("'", "").replace("’", "").replace("`", "").replace('"', '')
|
||
n = n.replace(" - ", " ") # Replace problematic ' - ' only in the game name
|
||
return ' '.join(n.split()).strip()
|
||
|
||
def title_case_preserve_numbers(name):
|
||
"""Capitalize words while preserving full-uppercase acronyms."""
|
||
return ' '.join(w.capitalize() if not w.isupper() else w for w in name.split())
|
||
|
||
def find_title_id(path):
|
||
"""Look for 16-character Title ID under any 'contents/' directory."""
|
||
for root, dirs, _ in os.walk(path):
|
||
if os.path.basename(root).lower() == 'contents':
|
||
for d in dirs:
|
||
if re.fullmatch(r'[0-9A-Fa-f]{16}', d):
|
||
return d
|
||
return None
|
||
|
||
def extract_with_7z(rar_path, tmpdir):
|
||
"""Extract only atmosphere/contents/* using 7z."""
|
||
cmd = [
|
||
"7z", "x", "-y",
|
||
rar_path,
|
||
f"-o{tmpdir}",
|
||
"atmosphere/contents/*"
|
||
]
|
||
proc = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||
return proc.returncode == 0 or find_title_id(tmpdir) is not None
|
||
|
||
def process_rar(root_folder, rar_relpath, output_root):
|
||
subdir, file = os.path.split(rar_relpath)
|
||
version_match = re.match(r"release_(.+)\.rar$", file, re.I)
|
||
if not version_match:
|
||
print(f"❌ Invalid release name: {file}")
|
||
return
|
||
version = version_match.group(1)
|
||
raw_game_name = os.path.basename(subdir)
|
||
|
||
# Clean and normalize game name (remove " - ", keep suffix clean)
|
||
cleaned_name = sanitize_name(raw_game_name)
|
||
game_name = title_case_preserve_numbers(cleaned_name)
|
||
pack_label = f"{game_name} - Graphics Pack" # Final folder
|
||
|
||
rar_path = os.path.join(root_folder, rar_relpath)
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
ok = extract_with_7z(rar_path, tmp)
|
||
if not ok:
|
||
print(f"❌ Extraction error (7z) on {rar_relpath}")
|
||
return
|
||
|
||
title_id = find_title_id(tmp)
|
||
if not title_id:
|
||
print(f"❌ No Title ID found in {rar_relpath}")
|
||
return # Skip making directory if no Title ID found
|
||
|
||
version_dir = os.path.join(output_root, pack_label, version)
|
||
os.makedirs(version_dir, exist_ok=True)
|
||
|
||
src = os.path.join(tmp, "atmosphere", "contents", title_id)
|
||
dst = os.path.join(version_dir, title_id)
|
||
try:
|
||
shutil.copytree(src, dst, dirs_exist_ok=True)
|
||
print(f"✅ {rar_relpath} → {os.path.join(pack_label, version, title_id)}")
|
||
except Exception as e:
|
||
print(f"❌ Copy failed for {rar_relpath}: {e}")
|
||
|
||
def main():
|
||
if len(sys.argv) != 2:
|
||
print("Usage: python collect_content_mods.py /path/to/root")
|
||
sys.exit(1)
|
||
|
||
root = sys.argv[1]
|
||
output = os.path.join(root, "format2")
|
||
os.makedirs(output, exist_ok=True)
|
||
|
||
tasks = []
|
||
for dirpath, _, files in os.walk(root):
|
||
for fn in files:
|
||
if re.match(r"release_.*\.rar$", fn, re.I):
|
||
rel = os.path.relpath(os.path.join(dirpath, fn), root)
|
||
tasks.append(rel)
|
||
|
||
for relpath in tasks:
|
||
process_rar(root, relpath, output)
|
||
|
||
print("✅ Done.")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|