124 lines
5.0 KiB
Python
Executable File
124 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Rebuild the Create Aeronautics .mrpack from a live Prism instance.
|
|
|
|
Resolves every mod jar in the instance's mods/ folder to its Modrinth version
|
|
by SHA-1 hash (falling back to a manual CurseForge entry for aero-shadow-fix),
|
|
then packs modrinth.index.json + overrides into a Prism-importable .mrpack.
|
|
"""
|
|
import hashlib, json, os, subprocess, sys, tempfile, zipfile, urllib.request
|
|
|
|
USER_AGENT = "opencode-pack-builder/1.0"
|
|
INSTANCE = os.path.expanduser(
|
|
"~/.local/share/PrismLauncher/instances/Reaper's Sky - dev/.minecraft"
|
|
)
|
|
MODS_DIR = os.path.join(INSTANCE, "mods")
|
|
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"ReapersSky-1.2.2.mrpack")
|
|
|
|
# (name in instance, override source path) — shipped as-is into .minecraft
|
|
OVERRIDES = [
|
|
("servers.dat", os.path.join(INSTANCE, "servers.dat")),
|
|
("options.txt", os.path.join(INSTANCE, "options.txt")),
|
|
("config/flywheel-client.toml", os.path.join(INSTANCE, "config/flywheel-client.toml")),
|
|
("config/iris.properties", os.path.join(INSTANCE, "config/iris.properties")),
|
|
("config/irisflw-client.toml", os.path.join(INSTANCE, "config/irisflw-client.toml")),
|
|
("resourcepacks/FreshAnimations_v1.10.4.zip", os.path.join(INSTANCE, "resourcepacks/FreshAnimations_v1.10.4.zip")),
|
|
("shaderpacks/ComplementaryReimagined_r5.8.1 + EuphoriaPatches_1.9.3",
|
|
os.path.join(INSTANCE, "shaderpacks/ComplementaryReimagined_r5.8.1 + EuphoriaPatches_1.9.3")),
|
|
]
|
|
|
|
# CurseForge-only mods: project 1641969 (aero-shadow-fix), file 8589529, and
|
|
# project 1653477 (Sophisticated Backpacks: Ars Compat), file 8653384.
|
|
CF_FILES = {
|
|
"aero-shadow-fix.jar": {
|
|
"url": "https://www.curseforge.com/api/v1/mods/1641969/files/8589529/download",
|
|
},
|
|
"arssophisticatedcompat-0.3.0.jar": {
|
|
"url": "https://www.curseforge.com/api/v1/mods/1653477/files/8653384/download",
|
|
},
|
|
}
|
|
|
|
def sha1(path):
|
|
h = hashlib.sha1()
|
|
with open(path, "rb") as f:
|
|
for block in iter(lambda: f.read(65536), b""):
|
|
h.update(block)
|
|
return h.hexdigest()
|
|
|
|
def sha512(path):
|
|
h = hashlib.sha512()
|
|
with open(path, "rb") as f:
|
|
for block in iter(lambda: f.read(65536), b""):
|
|
h.update(block)
|
|
return h.hexdigest()
|
|
|
|
def resolve(sha):
|
|
url = f"https://api.modrinth.com/v2/version_file/{sha}?algorithm=sha1"
|
|
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
|
with urllib.request.urlopen(req, timeout=20) as r:
|
|
d = json.load(r)
|
|
return d.get("id", ""), d.get("files", [{}])[0].get("url", "")
|
|
|
|
def main():
|
|
files = []
|
|
for fname in sorted(os.listdir(MODS_DIR)):
|
|
if not fname.endswith(".jar"):
|
|
continue
|
|
path = os.path.join(MODS_DIR, fname)
|
|
fsize = os.path.getsize(path)
|
|
if fname in CF_FILES:
|
|
url = CF_FILES[fname]["url"]
|
|
else:
|
|
vid, url = resolve(sha1(path))
|
|
if not url:
|
|
print(f"!! could not resolve {fname} — add to CF_FILES and rerun")
|
|
sys.exit(1)
|
|
files.append({
|
|
"path": "mods/" + fname,
|
|
"hashes": {"sha1": sha1(path), "sha512": sha512(path)},
|
|
"env": {"client": "required", "server": "optional"},
|
|
"downloads": [url],
|
|
"fileSize": fsize,
|
|
})
|
|
|
|
index = {
|
|
"formatVersion": 1,
|
|
"game": "minecraft",
|
|
"versionId": "1.2.2",
|
|
"name": "Reaper's Sky",
|
|
"summary": "Airships, exploration & tech. 111 mods for Minecraft 1.21.1 / NeoForge.",
|
|
"files": files,
|
|
"dependencies": {"minecraft": "1.21.1", "neoforge": "21.1.248"},
|
|
}
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
with open(os.path.join(tmp, "modrinth.index.json"), "w") as f:
|
|
json.dump(index, f, indent=2)
|
|
ov = os.path.join(tmp, "overrides")
|
|
for name, src in OVERRIDES:
|
|
dst = os.path.join(ov, name)
|
|
if os.path.isdir(src):
|
|
os.makedirs(dst, exist_ok=True)
|
|
for root, _, fnames in os.walk(src):
|
|
for fn in fnames:
|
|
sp = os.path.join(root, fn)
|
|
dp = os.path.join(dst, os.path.relpath(sp, src))
|
|
os.makedirs(os.path.dirname(dp), exist_ok=True)
|
|
with open(sp, "rb") as si, open(dp, "wb") as di:
|
|
di.write(si.read())
|
|
else:
|
|
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
with open(src, "rb") as si, open(dst, "wb") as di:
|
|
di.write(si.read())
|
|
with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED) as z:
|
|
z.write(os.path.join(tmp, "modrinth.index.json"), "modrinth.index.json")
|
|
for root, _, fnames in os.walk(ov):
|
|
for fn in fnames:
|
|
sp = os.path.join(root, fn)
|
|
z.write(sp, os.path.relpath(sp, tmp))
|
|
|
|
print(f"Wrote {OUT} ({len(files)} mods, "
|
|
f"{os.path.getsize(OUT)//1024//1024} MB)")
|
|
|
|
if __name__ == "__main__":
|
|
main() |