71 lines
2.2 KiB
Python
71 lines
2.2 KiB
Python
import hashlib
|
|
import os
|
|
import struct
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
from mhwmm.core.paks import path_hash, pak_name
|
|
from mhwmm.core.paths import Paths
|
|
|
|
|
|
def snapshot(root: Path) -> dict:
|
|
"""Byte-level picture of a tree: files by hash, symlinks by target, and directories."""
|
|
snap = {}
|
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
for n in dirnames + filenames:
|
|
p = Path(dirpath) / n
|
|
rel = str(p.relative_to(root))
|
|
if p.is_symlink():
|
|
snap[rel] = ("l", os.readlink(p))
|
|
elif p.is_dir():
|
|
snap[rel] = ("d",)
|
|
else:
|
|
snap[rel] = ("f", hashlib.sha256(p.read_bytes()).hexdigest())
|
|
return snap
|
|
|
|
|
|
def make_game(root: Path, ceiling: int = 15) -> Path:
|
|
game = root / "game"
|
|
game.mkdir()
|
|
for chain in ("main", "sub"):
|
|
for n in range(1, ceiling + 1):
|
|
(game / pak_name(chain, n)).write_bytes(f"vanilla-{chain}-{n}".encode())
|
|
(game / "re_chunk_000.pak").write_bytes(b"base")
|
|
(game / "MonsterHunterWilds.exe").write_bytes(b"exe")
|
|
return game
|
|
|
|
|
|
def write_tree(root: Path, files: dict[str, bytes | str]) -> Path:
|
|
for rel, data in files.items():
|
|
p = root / rel
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
p.write_bytes(data if isinstance(data, bytes) else data.encode())
|
|
return root
|
|
|
|
|
|
def make_kpka(paths: list[str]) -> bytes:
|
|
"""Minimal KPKA with entries keyed by the path hashes of `paths`."""
|
|
out = b"KPKA" + struct.pack("<BBHII", 4, 0, 0, len(paths), 0)
|
|
for p in paths:
|
|
lo, up = path_hash(p)
|
|
out += struct.pack("<IIQQQQQ", lo, up, 0, 0, 0, 0, 0)
|
|
return out
|
|
|
|
|
|
def make_zip(path: Path, files: dict[str, bytes | str]) -> Path:
|
|
with zipfile.ZipFile(path, "w") as zf:
|
|
for name, data in files.items():
|
|
zf.writestr(name, data)
|
|
return path
|
|
|
|
|
|
class TmpCase(unittest.TestCase):
|
|
def setUp(self):
|
|
self._td = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self._td.cleanup)
|
|
self.tmp = Path(self._td.name)
|
|
self.paths = Paths(self.tmp / "data", self.tmp / "config")
|
|
self.paths.ensure()
|