#!/usr/bin/env python3 """ Initialize test data folders by archive signatures. The script scans all files in --input and copies matching archives into: --output/nres/ --output/rsli/ """ from __future__ import annotations import argparse import shutil import sys from pathlib import Path MAGIC_NRES = b"NRes" MAGIC_RSLI = b"NL\x00\x01" def is_relative_to(path: Path, base: Path) -> bool: try: path.relative_to(base) except ValueError: return False return True def detect_archive_type(path: Path) -> str | None: try: with path.open("rb") as handle: magic = handle.read(4) except OSError as exc: print(f"[warn] cannot read {path}: {exc}", file=sys.stderr) return None if magic == MAGIC_NRES: return "nres" if magic == MAGIC_RSLI: return "rsli" return None def scan_archives(input_root: Path, excluded_root: Path | None) -> list[tuple[Path, str]]: found: list[tuple[Path, str]] = [] for path in sorted(input_root.rglob("*")): if not path.is_file(): continue if excluded_root and is_relative_to(path.resolve(), excluded_root): continue archive_type = detect_archive_type(path) if archive_type: found.append((path, archive_type)) return found def confirm_overwrite(path: Path) -> str: prompt = ( f"File exists: {path}\n" "Overwrite? [y]es / [n]o / [a]ll / [q]uit (default: n): " ) while True: try: answer = input(prompt).strip().lower() except EOFError: return "quit" if answer in {"", "n", "no"}: return "no" if answer in {"y", "yes"}: return "yes" if answer in {"a", "all"}: return "all" if answer in {"q", "quit"}: return "quit" print("Please answer with y, n, a, or q.") def copy_archives( archives: list[tuple[Path, str]], input_root: Path, output_root: Path, force: bool, ) -> int: copied = 0 skipped = 0 overwritten = 0 overwrite_all = force type_counts = {"nres": 0, "rsli": 0} for _, archive_type in archives: type_counts[archive_type] += 1 print( f"Found archives: total={len(archives)}, " f"nres={type_counts['nres']}, rsli={type_counts['rsli']}" ) for source, archive_type in archives: rel_path = source.relative_to(input_root) destination = output_root / archive_type / rel_path destination.parent.mkdir(parents=True, exist_ok=True) if destination.exists(): if destination.is_dir(): print( f"[error] destination is a directory, expected file: {destination}", file=sys.stderr, ) return 2 if not overwrite_all: if not sys.stdin.isatty(): print( "[error] destination file exists but stdin is not interactive. " "Use --force to overwrite without prompts.", file=sys.stderr, ) return 2 decision = confirm_overwrite(destination) if decision == "quit": print("Aborted by user.") return 130 if decision == "no": skipped += 1 continue if decision == "all": overwrite_all = True overwritten += 1 try: shutil.copy2(source, destination) except OSError as exc: print(f"[error] failed to copy {source} -> {destination}: {exc}", file=sys.stderr) return 2 copied += 1 print( f"Done: copied={copied}, overwritten={overwritten}, skipped={skipped}, " f"output={output_root}" ) return 0 def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Initialize test data by scanning NRes/RsLi signatures." ) parser.add_argument( "--input", required=True, help="Input directory to scan recursively.", ) parser.add_argument( "--output", required=True, help="Output root directory (archives go to nres/ and rsli/ subdirs).", ) parser.add_argument( "--force", action="store_true", help="Overwrite destination files without confirmation prompts.", ) return parser def main() -> int: args = build_parser().parse_args() input_root = Path(args.input) if not input_root.exists(): print(f"[error] input directory does not exist: {input_root}", file=sys.stderr) return 2 if not input_root.is_dir(): print(f"[error] input path is not a directory: {input_root}", file=sys.stderr) return 2 output_root = Path(args.output) if output_root.exists() and not output_root.is_dir(): print(f"[error] output path exists and is not a directory: {output_root}", file=sys.stderr) return 2 input_resolved = input_root.resolve() output_resolved = output_root.resolve() if input_resolved == output_resolved: print("[error] input and output directories must be different.", file=sys.stderr) return 2 excluded_root: Path | None = None if is_relative_to(output_resolved, input_resolved): excluded_root = output_resolved print(f"Notice: output is inside input, skipping scan under: {excluded_root}") archives = scan_archives(input_root, excluded_root) output_root.mkdir(parents=True, exist_ok=True) return copy_archives(archives, input_root, output_root, force=args.force) if __name__ == "__main__": raise SystemExit(main())