build/...-private.mrpack lists every mod by download link, CurseForge-only ones included, with the credits and non-affiliation notice inside and no third-party jar. tools/check_private_pack.py downloads all of it and checks the hashes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
3.1 KiB
Python
Executable File
71 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Check the private pack as a launcher would install it: every link really downloads the pinned file, every mod of the
|
|
pack is listed exactly once with the right side, and no third-party jar sits inside.
|
|
|
|
Downloads each file fresh, so it needs the network and takes a minute or two.
|
|
|
|
Usage: tools/check_private_pack.py
|
|
"""
|
|
import hashlib
|
|
import json
|
|
import sys
|
|
import tomllib
|
|
import urllib.request
|
|
import zipfile
|
|
from pathlib import Path, PurePosixPath
|
|
|
|
import lock
|
|
|
|
ROOT = lock.ROOT
|
|
PACK = ROOT / "pack"
|
|
|
|
|
|
def main():
|
|
version = tomllib.loads((PACK / "pack.toml").read_text())["version"]
|
|
archive = ROOT / f"build/cobblemon-survival-pack-{version}-private.mrpack"
|
|
if not archive.exists():
|
|
sys.exit(f"{archive} is missing: run tools/build.py first")
|
|
problems = []
|
|
with zipfile.ZipFile(archive) as z:
|
|
names = z.namelist()
|
|
index = json.loads(z.read("modrinth.index.json"))
|
|
jars = [n for n in names if n.endswith(".jar")]
|
|
if [PurePosixPath(n).name for n in jars] != ["cobblesurvival-0.0.1.jar"]:
|
|
problems.append(f"jars inside the pack: {jars}")
|
|
if "overrides/CREDITS.md" not in names:
|
|
problems.append("no credits in the pack")
|
|
entries = {PurePosixPath(e["path"]).name: e for e in index["files"]}
|
|
if len(entries) != len(index["files"]):
|
|
problems.append("a file is listed twice")
|
|
metas = [tomllib.loads(p.read_text()) for p in sorted([*(PACK / "mods").glob("*.pw.toml"), *(PACK / "shaderpacks").glob("*.pw.toml")])]
|
|
wanted = {m["filename"]: m for m in metas}
|
|
if set(wanted) != set(entries):
|
|
problems.append(f"missing {sorted(set(wanted) - set(entries))}, unexpected {sorted(set(entries) - set(wanted))}")
|
|
total = 0
|
|
for name, entry in sorted(entries.items()):
|
|
request = urllib.request.Request(entry["downloads"][0], headers={"User-Agent": "cobblemon-survival-pack check"})
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=300) as response:
|
|
content = response.read()
|
|
except OSError as e:
|
|
problems.append(f"{name}: {e}")
|
|
continue
|
|
total += len(content)
|
|
if hashlib.sha512(content).hexdigest() != entry["hashes"]["sha512"] or hashlib.sha1(content).hexdigest() != entry["hashes"]["sha1"]:
|
|
problems.append(f"{name}: the download doesn't match its hash")
|
|
if len(content) != entry["fileSize"]:
|
|
problems.append(f"{name}: size {len(content)}, listed {entry['fileSize']}")
|
|
side = wanted.get(name, {}).get("side", "both")
|
|
expected = {"client": side != "server", "server": side != "client"}
|
|
if {k: v == "required" for k, v in entry["env"].items()} != expected:
|
|
problems.append(f"{name}: env {entry['env']} but side {side}")
|
|
print(f"{len(entries)} files, {total / 1e6:.0f} MB downloaded and checked; {archive.name} is {archive.stat().st_size / 1e6:.1f} MB")
|
|
if problems:
|
|
print("\n".join(f" - {p}" for p in problems))
|
|
sys.exit("PRIVATE PACK CHECK FAILED")
|
|
print("PRIVATE PACK CHECK PASSED")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|