mirror of
https://github.com/sal063/AC6_recomp
synced 2026-06-24 17:53:20 -04:00
c2e2fbfbbc
Suspend the FPS unlock while a demo-manager Exec (DD sub_82184460 / EM sub_821856F8) ticks, so the frame-locked IngameCinematics Sequencer plays at native ~30fps instead of double speed. Adds ac6_cutscene_clamp CVar (default on).
91 lines
3.2 KiB
Python
91 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from ac6_fhm import ext_for_blob, magic_of, parse_fhm, safe_tag
|
|
|
|
|
|
def unpack(blob: bytes, out_dir: Path, root: Path, depth: int, max_depth: int) -> list[dict]:
|
|
children = parse_fhm(blob)
|
|
if children is None:
|
|
return []
|
|
recs = []
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
for child in children:
|
|
magic = child.magic
|
|
name = f"{child.index:04d}_{safe_tag(magic)}{ext_for_blob(child.data)}"
|
|
path = out_dir / name
|
|
path.write_bytes(child.data)
|
|
rec = {"index": child.index, "offset": child.offset,
|
|
"declared_size": child.declared_size, "size": child.size,
|
|
"magic": magic, "path": str(path.relative_to(root)).replace("\\", "/")}
|
|
if child.notes:
|
|
rec["parser_notes"] = child.notes
|
|
if depth < max_depth and child.data[:4] == b"FHM ":
|
|
nested = unpack(child.data, out_dir / f"{child.index:04d}_FHM", root, depth + 1, max_depth)
|
|
if nested:
|
|
rec["children"] = nested
|
|
recs.append(rec)
|
|
return recs
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--input", type=Path, default=Path("out") / "ac6_pac_extracted_raw" / "files")
|
|
ap.add_argument("--output", type=Path, default=Path("out") / "ac6_fhm_unpacked")
|
|
ap.add_argument("--max-depth", type=int, default=8)
|
|
args = ap.parse_args()
|
|
|
|
inp = args.input.resolve()
|
|
out_root = args.output.resolve()
|
|
out_root.mkdir(parents=True, exist_ok=True)
|
|
|
|
if inp.is_file():
|
|
sources = [inp]
|
|
else:
|
|
sources = sorted(inp.rglob("*.bin"))
|
|
|
|
manifest = []
|
|
fhm_count = leaf_count = 0
|
|
for src in sources:
|
|
blob = src.read_bytes()
|
|
stem = src.stem.split(".")[0]
|
|
if blob[:4] != b"FHM ":
|
|
# Top-level non-FHM (raw entry): copy through as a leaf.
|
|
dst = out_root / f"{stem}{ext_for_blob(blob)}"
|
|
dst.write_bytes(blob)
|
|
manifest.append({"source": src.name, "kind": "leaf", "magic": magic_of(blob),
|
|
"path": str(dst.relative_to(out_root)).replace("\\", "/")})
|
|
leaf_count += 1
|
|
continue
|
|
cdir = out_root / stem
|
|
recs = unpack(blob, cdir, out_root, 0, args.max_depth)
|
|
manifest.append({"source": src.name, "kind": "fhm", "child_count": len(recs),
|
|
"children": recs})
|
|
fhm_count += 1
|
|
|
|
def count_leaves(recs):
|
|
n = 0
|
|
for r in recs:
|
|
if "children" in r:
|
|
n += count_leaves(r["children"])
|
|
else:
|
|
n += 1
|
|
return n
|
|
|
|
total_leaves = leaf_count + sum(
|
|
count_leaves(m["children"]) for m in manifest if m["kind"] == "fhm")
|
|
(out_root / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
print(json.dumps({"sources": len(sources), "fhm_containers": fhm_count,
|
|
"total_leaves": total_leaves, "output": str(out_root)}, indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|