Last active
April 7, 2024 15:21
Revisions
-
Kyuuhachi revised this gist
Dec 26, 2023 . 1 changed file with 4 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -21,15 +21,10 @@ def decode(data: bytes) -> bytes: return bytes(o) def fnhash(name: bytes) -> int: return sum( (b - 32) * (1<<(i%5*5)) for i, b in enumerate(name) ) % 0xFFF1 def extract(na: bytes, ni: bytes, *, verify: bool = False) -> T.Iterator[tuple[str, bytes]]: def take(a: bytes, n: int) -> tuple[bytes, bytes]: return a[:n], a[n:] -
Kyuuhachi created this gist
Dec 26, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,87 @@ import typing as T from pathlib import Path import zlib import struct try: import numpy as np def decode(data: bytes) -> bytes: data = np.frombuffer(data, dtype=np.ubyte).copy() obf = 0x7C53F961 * 0x3D09 ** (1+np.arange(len(data), dtype=np.uint32)) data -= obf >> 16 return data.tobytes() except ImportError: print("Could not import numpy — falling back to slow method") def decode(data: bytes) -> bytes: k = 0x7C53F961 o = bytearray() for b in data: k *= 0x3D09 o.append((b - (k >> 16)) & 0xFF) return bytes(o) def fnhash(name: bytes) -> int: v = 0 for i, b in enumerate(name): v += (b - 32) * (1<<(i%5*5)) # There's gotta be a better way to do this... v = (v & 0xFFFF) + (v >> 16) * 15 v = (v & 0xFFFF) + (v >> 16) * 15 v = (v & 0xFFFF) + (v >> 16) * 15 if v > 0xFFF0: v = (v + 0xF) & 0xFFFF return v def extract(na: bytes, ni: bytes, *, verify: bool = False) -> T.Iterator[tuple[str, bytes]]: def take(a: bytes, n: int) -> tuple[bytes, bytes]: return a[:n], a[n:] head, ni = take(ni, 16) head, p2, p3, p4 = struct.unpack("<4sIII", head) assert head == b"NNI\0" toc, ni = take(ni, p2*16) names, ni = take(ni, p3) assert p4 & 0x01 == 0, ".\\core\\nnk_file.cpp: vfs::Startup: No incremental linking support." assert not ni toc = decode(toc) names = decode(names) for hash, size, pos, namepos in struct.iter_unpack("<IIII", toc): name = names[namepos:names.find(b"\0", namepos)] if verify: assert hash == fnhash(name), name name = name.decode("cp932").lower().replace("\\", "/") if (pos, size) == (0, 0): print(f"skipping blank file {name}") continue data = na[pos:pos+size] if name.endswith(".z"): name = name[:-2] try: checksum, usize = struct.unpack("<II", data[:8]) data = zlib.decompress(data[8:]) assert len(data) == usize if verify: assert checksum == zlib.crc32(data) except Exception: print(f"failed to decompress {name}") import traceback traceback.print_exc() yield name, data import argparse argp = argparse.ArgumentParser() argp.add_argument("-o", "--outdir", type=Path, required=True, dest="outdir") argp.add_argument("-v", "--verbose", action="store_true") argp.add_argument("-V", "--verify", action="store_true") argp.add_argument("files", nargs="+", type=Path) def __main__(outdir: Path, verbose: bool, verify: bool, files: list[Path]): for path in files: assert path.suffix in [".na", ".ni"], path na = path.with_suffix(".na").read_bytes() ni = path.with_suffix(".ni").read_bytes() for name, data in extract(na, ni, verify=verify): out = outdir / name if verbose: print(out) out.parent.mkdir(parents=True, exist_ok=True) out.write_bytes(data) if __name__ == "__main__": __main__(**argp.parse_args().__dict__)