"""Extract Naninovel DefaultUI TextAsset from Tomie into this workspace. Only reads the game. Writes a JSON translation input and a source manifest. Requires: python -m pip install -e ".[unity]" """ import argparse import hashlib import json from pathlib import Path from local_translator.naninovel import parse_dictionary def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("game", type=Path) parser.add_argument("--output", type=Path, default=Path("data/tomie/input.json")) parser.add_argument("--limit", type=int, default=15, help="0 extracts the full DefaultUI dictionary") args = parser.parse_args() if args.limit < 0: parser.error("limit must be nonnegative") try: import UnityPy except ImportError: parser.error('Install optional extractor: python -m pip install -e ".[unity]"') asset_path = args.game.resolve() / "TomieWGM_Data" / "data.unity3d" output = args.output.resolve() if output.is_relative_to(args.game.resolve()): parser.error("Choose an output outside the game directory") environment = UnityPy.load(str(asset_path)) assets = [obj for obj in environment.objects if obj.type.name == "TextAsset" and obj.read().m_Name == "DefaultUI"] if len(assets) != 1: parser.error(f"Expected exactly one DefaultUI TextAsset, found {len(assets)}") obj = assets[0] script = obj.read().m_Script if isinstance(script, bytes): script = script.decode("utf-8-sig") entries = parse_dictionary(script) selected = entries[:args.limit] if args.limit else entries output.parent.mkdir(parents=True, exist_ok=True) output.write_text(json.dumps([{"id": row["id"], "text": row["text"]} for row in selected], ensure_ascii=False, indent=2), encoding="utf-8") manifest = { "game": str(args.game.resolve()), "source_file": str(asset_path), "asset_name": "DefaultUI", "path_id": obj.path_id, "serialized_file": obj.assets_file.name, "script_sha256": hashlib.sha256(script.encode("utf-8")).hexdigest(), "total_entries": len(entries), "selected_entries": selected, "note": "Read-only extraction. No patching, font replacement or changes to game files.", } output.with_suffix(".manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8") print(f"Extracted {len(selected)}/{len(entries)} UI strings to {output}") if __name__ == "__main__": main()