summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-05 12:31:02 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-05 12:31:02 +0300
commit774b25378524ffbac5533e2d20622fb02ffbf60e (patch)
treedc257be7aeb818ff2a13adf2e4f220df816818a1
parent0254e84a6e069bf2ce418ff9f95d6f7bee092470 (diff)
downloadProject-Tick-774b25378524ffbac5533e2d20622fb02ffbf60e.tar.gz
Project-Tick-774b25378524ffbac5533e2d20622fb02ffbf60e.zip
NOISSUE fix optifine and some metadata loaders
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
-rw-r--r--meta/meta/common/modloadermp.py4
-rw-r--r--meta/meta/common/optifine.py9
-rw-r--r--meta/meta/common/risugami.py6
-rw-r--r--meta/meta/common/stationloader.py6
-rw-r--r--meta/meta/run/generate_modloadermp.py70
-rw-r--r--meta/meta/run/generate_optifine.py169
-rw-r--r--meta/meta/run/generate_risugami.py64
-rw-r--r--meta/meta/run/generate_stationloader.py79
-rw-r--r--meta/meta/run/update_modloadermp.py37
-rw-r--r--meta/meta/run/update_optifine.py477
-rw-r--r--meta/meta/run/update_risugami.py54
-rw-r--r--meta/meta/run/update_stationloader.py74
12 files changed, 594 insertions, 455 deletions
diff --git a/meta/meta/common/modloadermp.py b/meta/meta/common/modloadermp.py
index 1c71b94614..49c765dbec 100644
--- a/meta/meta/common/modloadermp.py
+++ b/meta/meta/common/modloadermp.py
@@ -1,5 +1,7 @@
+from os.path import join
+
BASE_DIR = "modloadermp"
MODLOADERMP_COMPONENT = "modloadermp"
-VERSIONS_FILE = "modloadermp/versions.json"
+VERSIONS_FILE = join(BASE_DIR, "versions.json")
diff --git a/meta/meta/common/optifine.py b/meta/meta/common/optifine.py
index 2fbb854c66..8000d95349 100644
--- a/meta/meta/common/optifine.py
+++ b/meta/meta/common/optifine.py
@@ -1,10 +1,7 @@
-# upstream directory name where OptiFine pages/versions are stored
+from os.path import join
+
BASE_DIR = "optifine"
-# launcher package uid (folder under `launcher/`) -- keep this as the canonical package id
-# Use 'net.optifine' so generated launcher metadata lives in `launcher/net.optifine/`
OPTIFINE_COMPONENT = "net.optifine"
-# upstream component path (under `upstream/`) and combined versions file path
-OPTIFINE_UPSTREAM_DIR = "optifine"
-VERSIONS_FILE = "optifine/versions.json"
+VERSIONS_FILE = join(BASE_DIR, "versions.json")
diff --git a/meta/meta/common/risugami.py b/meta/meta/common/risugami.py
index c04e8bc01b..3c5a6ea798 100644
--- a/meta/meta/common/risugami.py
+++ b/meta/meta/common/risugami.py
@@ -1,5 +1,7 @@
+from os.path import join
+
BASE_DIR = "risugami"
-RISUGAMI_COMPONENT = "risugami"
+RISUGAMI_COMPONENT = "risugami.modloader"
-VERSIONS_FILE = "risugami/versions.json"
+VERSIONS_FILE = join(BASE_DIR, "versions.json")
diff --git a/meta/meta/common/stationloader.py b/meta/meta/common/stationloader.py
index 3ce88888c6..0bed892fb5 100644
--- a/meta/meta/common/stationloader.py
+++ b/meta/meta/common/stationloader.py
@@ -1,5 +1,7 @@
+from os.path import join
+
BASE_DIR = "station-loader"
-STATIONLOADER_COMPONENT = "station-loader"
+STATIONLOADER_COMPONENT = "net.modificationstation.stationloader"
-VERSIONS_FILE = "station-loader/versions.json"
+VERSIONS_FILE = join(BASE_DIR, "versions.json")
diff --git a/meta/meta/run/generate_modloadermp.py b/meta/meta/run/generate_modloadermp.py
index a1c6c20cbb..f4ea0d4db5 100644
--- a/meta/meta/run/generate_modloadermp.py
+++ b/meta/meta/run/generate_modloadermp.py
@@ -1,8 +1,11 @@
import os
+import json
+
from meta.common import ensure_component_dir, launcher_path, upstream_path
from meta.common.modloadermp import MODLOADERMP_COMPONENT, VERSIONS_FILE
-from meta.model import MetaPackage
-
+from meta.common.mojang import MINECRAFT_COMPONENT
+from meta.common.risugami import RISUGAMI_COMPONENT
+from meta.model import MetaPackage, MetaVersion, Library, MojangLibraryDownloads, MojangArtifact, Dependency
LAUNCHER_DIR = launcher_path()
UPSTREAM_DIR = upstream_path()
@@ -11,14 +14,73 @@ ensure_component_dir(MODLOADERMP_COMPONENT)
def main():
+ src = os.path.join(UPSTREAM_DIR, VERSIONS_FILE)
+ if not os.path.exists(src):
+ print(f"Missing upstream file: {src}")
+ return
+
+ with open(src, "r", encoding="utf-8") as f:
+ entries = json.load(f)
+
+ if not entries:
+ print("No ModLoaderMP entries found, writing stub package")
+ package = MetaPackage(
+ uid=MODLOADERMP_COMPONENT,
+ name="ModLoaderMP",
+ description="ModLoaderMP - multiplayer companion to Risugami's ModLoader",
+ )
+ package.write(os.path.join(LAUNCHER_DIR, MODLOADERMP_COMPONENT, "package.json"))
+ return
+
+ all_versions = []
+
+ for key, data in entries.items():
+ mc_version = data.get("mc_version")
+ label = data.get("label", f"ModLoaderMP {key}")
+ requires_modloader = data.get("requires_modloader")
+
+ v = MetaVersion(
+ name=label,
+ uid=MODLOADERMP_COMPONENT,
+ version=key,
+ type="release",
+ order=11,
+ )
+
+ # Dependencies: Minecraft + Risugami ModLoader
+ deps = []
+ if mc_version:
+ deps.append(Dependency(uid=MINECRAFT_COMPONENT, equals=mc_version))
+ if requires_modloader:
+ deps.append(Dependency(uid=RISUGAMI_COMPONENT, equals=requires_modloader))
+ if deps:
+ v.requires = deps
+
+ # Attach download artifact if available
+ url = data.get("url") or data.get("download_url")
+ if url:
+ artifact_kwargs = {"url": url}
+ if data.get("size") is not None:
+ artifact_kwargs["size"] = data["size"]
+ if data.get("sha256") is not None:
+ artifact_kwargs["sha256"] = data["sha256"]
+ artifact = MojangArtifact(**artifact_kwargs)
+ lib = Library(downloads=MojangLibraryDownloads(artifact=artifact))
+ v.jar_mods = [lib]
+
+ v.write(os.path.join(LAUNCHER_DIR, MODLOADERMP_COMPONENT, f"{v.version}.json"))
+ all_versions.append(v.version)
+
package = MetaPackage(
uid=MODLOADERMP_COMPONENT,
name="ModLoaderMP",
- description="ModLoaderMP metadata (auto-generated stub)",
+ description="ModLoaderMP - multiplayer companion to Risugami's ModLoader",
+ recommended=all_versions[:3],
)
-
package.write(os.path.join(LAUNCHER_DIR, MODLOADERMP_COMPONENT, "package.json"))
+ print(f"Generated {len(all_versions)} ModLoaderMP versions")
+
if __name__ == "__main__":
main()
diff --git a/meta/meta/run/generate_optifine.py b/meta/meta/run/generate_optifine.py
index 802d3af530..3c77718930 100644
--- a/meta/meta/run/generate_optifine.py
+++ b/meta/meta/run/generate_optifine.py
@@ -1,12 +1,12 @@
import os
import json
+import re
from datetime import datetime
-from typing import List
from meta.common import ensure_component_dir, launcher_path, upstream_path
-from meta.common.optifine import OPTIFINE_COMPONENT, VERSIONS_FILE, OPTIFINE_UPSTREAM_DIR
-from meta.model import MetaPackage, MetaVersion, Library, MojangLibraryDownloads, MojangArtifact
-
+from meta.common.optifine import OPTIFINE_COMPONENT, VERSIONS_FILE, BASE_DIR
+from meta.common.mojang import MINECRAFT_COMPONENT
+from meta.model import MetaPackage, MetaVersion, Library, MojangLibraryDownloads, MojangArtifact, Dependency
LAUNCHER_DIR = launcher_path()
UPSTREAM_DIR = upstream_path()
@@ -15,96 +15,139 @@ ensure_component_dir(OPTIFINE_COMPONENT)
def _parse_date(d: str):
- # dates on the site are like DD.MM.YYYY
+ """Parse dates like DD.MM.YYYY from OptiFine site."""
try:
return datetime.strptime(d, "%d.%m.%Y")
except Exception:
return None
-def main():
- # Prefer per-version files in the upstream component directory, fallback to combined versions.json
- # upstream files live under `upstream/optifine`, launcher metadata should go under `launcher/net.optifine`
- comp_dir = os.path.join(UPSTREAM_DIR, OPTIFINE_UPSTREAM_DIR)
+def _extract_mc_version(key: str) -> str:
+ """Extract Minecraft version from an OptiFine version key.
+
+ Examples:
+ '1.21.4_HD_U_J3' -> '1.21.4'
+ 'preview_OptiFine_1.21.8_HD_U_J6_pre16' -> '1.21.8'
+ '1.8.9_HD_U_M5' -> '1.8.9'
+ """
+ # Strip preview prefix
+ clean = re.sub(r"^preview_(?:OptiFine_)?", "", key, flags=re.IGNORECASE)
+ # Match the Minecraft version at the start (e.g. 1.21.4, 1.8.9, 1.7.10)
+ m = re.match(r"(\d+\.\d+(?:\.\d+)?)", clean)
+ return m.group(1) if m else None
+
+
+def _is_preview(key: str, data: dict) -> bool:
+ """Check if a version is a preview/pre-release."""
+ filename = data.get("filename", "")
+ return "preview" in key.lower() or "preview" in filename.lower() or "pre" in key.lower()
+
+
+def _load_entries() -> dict:
+ """Load upstream OptiFine version entries from per-version files or combined index."""
+ comp_dir = os.path.join(UPSTREAM_DIR, BASE_DIR)
entries = {}
+
if os.path.isdir(comp_dir):
- files = [f for f in os.listdir(comp_dir) if f.endswith(".json")]
- # If there are many per-version files (excluding the combined file), read them
- per_files = [f for f in files if f != VERSIONS_FILE]
- if per_files:
- for fn in per_files:
- path = os.path.join(comp_dir, fn)
- try:
- with open(path, "r", encoding="utf-8") as f:
- data = json.load(f)
- key = os.path.splitext(fn)[0]
- entries[key] = data
- except Exception:
- print(f"Warning: failed to read upstream per-version file: {path}")
- # fallback to combined index
+ for fn in os.listdir(comp_dir):
+ if not fn.endswith(".json") or fn == "versions.json":
+ continue
+ # Skip the nested optifine/ subdir if it exists
+ path = os.path.join(comp_dir, fn)
+ if not os.path.isfile(path):
+ continue
+ try:
+ with open(path, "r", encoding="utf-8") as f:
+ data = json.load(f)
+ key = os.path.splitext(fn)[0]
+ entries[key] = data
+ except Exception:
+ print(f"Warning: failed to read {path}")
+
+ # Fallback to combined index
if not entries:
src = os.path.join(UPSTREAM_DIR, VERSIONS_FILE)
if not os.path.exists(src):
print(f"Missing upstream file: {src}")
- return
+ return {}
with open(src, "r", encoding="utf-8") as f:
entries = json.load(f)
- versions: List[str] = []
- parsed_versions = []
-
- for key, data in entries.items():
- # key already normalized by the updater
- v = MetaVersion(name="OptiFine", uid=OPTIFINE_COMPONENT, version=key)
- v.type = "release"
- v.order = 10
+ return entries
- filename = data.get("filename")
- download_page = data.get("download_page")
- resolved = data.get("resolved_url") or download_page
- label = data.get("label")
- changelog = data.get("changelog")
- date = data.get("date")
- size = data.get("size")
- sha256 = data.get("sha256")
- # attach jar mod as a simple artifact entry; prefer resolved_url and include sha256/size
- lib = Library()
- artifact_kwargs = {}
- if resolved:
- artifact_kwargs["url"] = resolved
- else:
- artifact_kwargs["url"] = download_page
- if size is not None:
- artifact_kwargs["size"] = size
- if sha256 is not None:
- artifact_kwargs["sha256"] = sha256
+def main():
+ entries = _load_entries()
+ if not entries:
+ print("No OptiFine entries found")
+ return
- artifact = MojangArtifact(**artifact_kwargs)
- lib.downloads = MojangLibraryDownloads(artifact=artifact)
+ parsed_versions = []
- v.jar_mods = [lib]
+ for key, data in entries.items():
+ mc_version = _extract_mc_version(key)
+ is_preview = _is_preview(key, data)
+
+ v = MetaVersion(
+ name="OptiFine",
+ uid=OPTIFINE_COMPONENT,
+ version=key,
+ type="snapshot" if is_preview else "release",
+ order=10,
+ )
+
+ # Add Minecraft version dependency if we could extract it
+ if mc_version:
+ v.requires = [Dependency(uid=MINECRAFT_COMPONENT, equals=mc_version)]
+
+ # Use label as display name if available
+ label = data.get("label")
if label:
v.name = label
+ # Parse release date
+ date = data.get("date")
if date:
dt = _parse_date(date)
if dt:
v.release_time = dt
- v.write(os.path.join(LAUNCHER_DIR, OPTIFINE_COMPONENT, f"{v.version}.json"))
- parsed_versions.append((v.version, v.release_time))
+ # Build jar mod artifact — use stable download?f= URL
+ filename = data.get("filename")
+ if filename:
+ resolved = f"https://optifine.net/download?f={filename}"
+ else:
+ resolved = data.get("resolved_url") or data.get("download_page")
+ if resolved:
+ artifact_kwargs = {"url": resolved}
+ if data.get("size") is not None:
+ artifact_kwargs["size"] = data["size"]
+ if data.get("sha256") is not None:
+ artifact_kwargs["sha256"] = data["sha256"]
- # choose recommended: latest non-preview by release_time if available
- parsed_versions.sort(key=lambda x: (x[1] or datetime.min), reverse=True)
- recommended = [p[0] for p in parsed_versions[:3]]
+ artifact = MojangArtifact(**artifact_kwargs)
+ lib = Library(downloads=MojangLibraryDownloads(artifact=artifact))
+ v.jar_mods = [lib]
- package = MetaPackage(uid=OPTIFINE_COMPONENT, name="OptiFine")
- package.recommended = recommended
- package.description = "OptiFine installer and downloads"
- package.project_url = "https://optifine.net"
+ v.write(os.path.join(LAUNCHER_DIR, OPTIFINE_COMPONENT, f"{v.version}.json"))
+ parsed_versions.append((v.version, v.release_time, is_preview))
+
+ # Recommended: latest non-preview releases by date
+ releases = [(ver, rt) for ver, rt, preview in parsed_versions if not preview]
+ releases.sort(key=lambda x: (x[1] or datetime.min), reverse=True)
+ recommended = [r[0] for r in releases[:3]]
+
+ package = MetaPackage(
+ uid=OPTIFINE_COMPONENT,
+ name="OptiFine",
+ description="OptiFine - Minecraft performance and graphics mod",
+ project_url="https://optifine.net",
+ recommended=recommended,
+ )
package.write(os.path.join(LAUNCHER_DIR, OPTIFINE_COMPONENT, "package.json"))
+ print(f"Generated {len(parsed_versions)} OptiFine versions ({len(recommended)} recommended)")
+
if __name__ == "__main__":
main()
diff --git a/meta/meta/run/generate_risugami.py b/meta/meta/run/generate_risugami.py
index 07962f25e4..f61370d90c 100644
--- a/meta/meta/run/generate_risugami.py
+++ b/meta/meta/run/generate_risugami.py
@@ -1,8 +1,10 @@
import os
+import json
+
from meta.common import ensure_component_dir, launcher_path, upstream_path
from meta.common.risugami import RISUGAMI_COMPONENT, VERSIONS_FILE
-from meta.model import MetaPackage
-
+from meta.common.mojang import MINECRAFT_COMPONENT
+from meta.model import MetaPackage, MetaVersion, Library, MojangLibraryDownloads, MojangArtifact, Dependency
LAUNCHER_DIR = launcher_path()
UPSTREAM_DIR = upstream_path()
@@ -11,16 +13,66 @@ ensure_component_dir(RISUGAMI_COMPONENT)
def main():
- # If an upstream versions file exists, we could parse it later.
- # For now create a minimal package.json so the meta tooling recognizes the component.
+ src = os.path.join(UPSTREAM_DIR, VERSIONS_FILE)
+ if not os.path.exists(src):
+ print(f"Missing upstream file: {src}")
+ return
+
+ with open(src, "r", encoding="utf-8") as f:
+ entries = json.load(f)
+
+ if not entries:
+ print("No Risugami ModLoader entries found, writing stub package")
+ package = MetaPackage(
+ uid=RISUGAMI_COMPONENT,
+ name="Risugami ModLoader",
+ description="Risugami's ModLoader for classic/legacy Minecraft",
+ )
+ package.write(os.path.join(LAUNCHER_DIR, RISUGAMI_COMPONENT, "package.json"))
+ return
+
+ all_versions = []
+
+ for key, data in entries.items():
+ mc_version = data.get("mc_version")
+ label = data.get("label", f"ModLoader {key}")
+
+ v = MetaVersion(
+ name=label,
+ uid=RISUGAMI_COMPONENT,
+ version=key,
+ type="release",
+ order=10,
+ )
+
+ if mc_version:
+ v.requires = [Dependency(uid=MINECRAFT_COMPONENT, equals=mc_version)]
+
+ # Attach download artifact if available
+ url = data.get("url") or data.get("download_url")
+ if url:
+ artifact_kwargs = {"url": url}
+ if data.get("size") is not None:
+ artifact_kwargs["size"] = data["size"]
+ if data.get("sha256") is not None:
+ artifact_kwargs["sha256"] = data["sha256"]
+ artifact = MojangArtifact(**artifact_kwargs)
+ lib = Library(downloads=MojangLibraryDownloads(artifact=artifact))
+ v.jar_mods = [lib]
+
+ v.write(os.path.join(LAUNCHER_DIR, RISUGAMI_COMPONENT, f"{v.version}.json"))
+ all_versions.append(v.version)
+
package = MetaPackage(
uid=RISUGAMI_COMPONENT,
name="Risugami ModLoader",
- description="Risugami ModLoader metadata (auto-generated stub)",
+ description="Risugami's ModLoader for classic/legacy Minecraft",
+ recommended=all_versions[:3],
)
-
package.write(os.path.join(LAUNCHER_DIR, RISUGAMI_COMPONENT, "package.json"))
+ print(f"Generated {len(all_versions)} Risugami ModLoader versions")
+
if __name__ == "__main__":
main()
diff --git a/meta/meta/run/generate_stationloader.py b/meta/meta/run/generate_stationloader.py
index 636772e600..26f79614c0 100644
--- a/meta/meta/run/generate_stationloader.py
+++ b/meta/meta/run/generate_stationloader.py
@@ -1,8 +1,11 @@
import os
+import json
+from datetime import datetime
+
from meta.common import ensure_component_dir, launcher_path, upstream_path
from meta.common.stationloader import STATIONLOADER_COMPONENT, VERSIONS_FILE
-from meta.model import MetaPackage
-
+from meta.common.mojang import MINECRAFT_COMPONENT
+from meta.model import MetaPackage, MetaVersion, Library, MojangLibraryDownloads, MojangArtifact, Dependency
LAUNCHER_DIR = launcher_path()
UPSTREAM_DIR = upstream_path()
@@ -11,14 +14,80 @@ ensure_component_dir(STATIONLOADER_COMPONENT)
def main():
+ src = os.path.join(UPSTREAM_DIR, VERSIONS_FILE)
+ if not os.path.exists(src):
+ print(f"Missing upstream file: {src}")
+ return
+
+ with open(src, "r", encoding="utf-8") as f:
+ entries = json.load(f)
+
+ if not entries:
+ print("No StationLoader entries found, writing stub package")
+ package = MetaPackage(
+ uid=STATIONLOADER_COMPONENT,
+ name="StationAPI",
+ description="StationAPI mod loader for Minecraft b1.7.3",
+ )
+ package.write(os.path.join(LAUNCHER_DIR, STATIONLOADER_COMPONENT, "package.json"))
+ return
+
+ all_versions = []
+
+ for key, data in entries.items():
+ mc_version = data.get("mc_version", "b1.7.3")
+ label = data.get("label", f"StationAPI {key}")
+ prerelease = data.get("prerelease", False)
+
+ v = MetaVersion(
+ name=label,
+ uid=STATIONLOADER_COMPONENT,
+ version=key,
+ type="snapshot" if prerelease else "release",
+ order=10,
+ )
+
+ v.requires = [Dependency(uid=MINECRAFT_COMPONENT, equals=mc_version)]
+
+ # Parse release date (ISO 8601 from GitHub)
+ date = data.get("date")
+ if date:
+ try:
+ v.release_time = datetime.fromisoformat(date.replace("Z", "+00:00"))
+ except Exception:
+ pass
+
+ # Attach download artifact if available
+ url = data.get("url")
+ if url:
+ artifact_kwargs = {"url": url}
+ if data.get("size") is not None:
+ artifact_kwargs["size"] = data["size"]
+ if data.get("sha256") is not None:
+ artifact_kwargs["sha256"] = data["sha256"]
+ artifact = MojangArtifact(**artifact_kwargs)
+ lib = Library(downloads=MojangLibraryDownloads(artifact=artifact))
+ v.jar_mods = [lib]
+
+ v.write(os.path.join(LAUNCHER_DIR, STATIONLOADER_COMPONENT, f"{v.version}.json"))
+ all_versions.append((v.version, v.release_time, prerelease))
+
+ # Recommended: latest non-prerelease versions
+ releases = [(ver, rt) for ver, rt, pre in all_versions if not pre]
+ releases.sort(key=lambda x: (x[1] or datetime.min), reverse=True)
+ recommended = [r[0] for r in releases[:3]]
+
package = MetaPackage(
uid=STATIONLOADER_COMPONENT,
- name="Station Loader",
- description="Station Loader metadata (auto-generated stub)",
+ name="StationAPI",
+ description="StationAPI mod loader for Minecraft b1.7.3",
+ project_url="https://github.com/modificationstation/StationAPI",
+ recommended=recommended,
)
-
package.write(os.path.join(LAUNCHER_DIR, STATIONLOADER_COMPONENT, "package.json"))
+ print(f"Generated {len(all_versions)} StationLoader versions ({len(recommended)} recommended)")
+
if __name__ == "__main__":
main()
diff --git a/meta/meta/run/update_modloadermp.py b/meta/meta/run/update_modloadermp.py
index 05d6bed6d8..7d36e2648a 100644
--- a/meta/meta/run/update_modloadermp.py
+++ b/meta/meta/run/update_modloadermp.py
@@ -1,23 +1,48 @@
import json
import os
-from meta.common import upstream_path, ensure_upstream_dir, default_session
+from meta.common import upstream_path, ensure_upstream_dir
from meta.common.modloadermp import VERSIONS_FILE, BASE_DIR
UPSTREAM_DIR = upstream_path()
-
ensure_upstream_dir(BASE_DIR)
-sess = default_session()
+# ModLoaderMP is a legacy/archived project (companion to Risugami's ModLoader for SMP).
+# No active upstream API exists; versions are curated from known historical releases.
+KNOWN_VERSIONS = {
+ "1.2.5": {"mc_version": "1.2.5", "label": "ModLoaderMP 1.2.5", "requires_modloader": "1.2.5"},
+ "1.2.4": {"mc_version": "1.2.4", "label": "ModLoaderMP 1.2.4", "requires_modloader": "1.2.4"},
+ "1.2.3": {"mc_version": "1.2.3", "label": "ModLoaderMP 1.2.3", "requires_modloader": "1.2.3"},
+ "1.1": {"mc_version": "1.1", "label": "ModLoaderMP 1.1", "requires_modloader": "1.1"},
+ "1.0": {"mc_version": "1.0", "label": "ModLoaderMP 1.0", "requires_modloader": "1.0"},
+ "b1.8.1": {"mc_version": "b1.8.1", "label": "ModLoaderMP b1.8.1", "requires_modloader": "b1.8.1"},
+ "b1.7.3": {"mc_version": "b1.7.3", "label": "ModLoaderMP b1.7.3", "requires_modloader": "b1.7.3"},
+ "b1.6.6": {"mc_version": "b1.6.6", "label": "ModLoaderMP b1.6.6", "requires_modloader": "b1.6.6"},
+}
def main():
- # Placeholder updater: upstream source not implemented yet.
out_path = os.path.join(UPSTREAM_DIR, VERSIONS_FILE)
+
+ existing = {}
+ if os.path.exists(out_path):
+ try:
+ with open(out_path, "r") as f:
+ existing = json.load(f)
+ except Exception:
+ pass
+
+ for key, data in KNOWN_VERSIONS.items():
+ if key not in existing:
+ existing[key] = data
+ else:
+ for field, value in data.items():
+ existing[key].setdefault(field, value)
+
with open(out_path, "w") as f:
- json.dump({}, f, indent=4)
+ json.dump(existing, f, indent=4)
- print(f"Wrote placeholder upstream file: {out_path}")
+ print(f"Wrote {len(existing)} ModLoaderMP entries to {out_path}")
if __name__ == "__main__":
diff --git a/meta/meta/run/update_optifine.py b/meta/meta/run/update_optifine.py
index 833e08e263..4eef132e0d 100644
--- a/meta/meta/run/update_optifine.py
+++ b/meta/meta/run/update_optifine.py
@@ -1,206 +1,115 @@
import json
import os
import re
-from urllib.parse import urljoin, urlparse, parse_qs
+import hashlib
import concurrent.futures
-import threading
-
-try:
- from meta.common import upstream_path, ensure_upstream_dir, default_session
- from meta.common.optifine import VERSIONS_FILE, BASE_DIR
- HAVE_META = True
-except Exception:
- # meta.common or its dependencies (requests) may not be available in this environment.
- HAVE_META = False
- def upstream_path():
- return "upstream"
-
- def ensure_upstream_dir(path):
- path = os.path.join(upstream_path(), path)
- if not os.path.exists(path):
- os.makedirs(path, exist_ok=True)
-
- def default_session():
- raise RuntimeError("HTTP session unavailable: install 'requests' and 'cachecontrol'")
+from urllib.parse import urljoin, urlparse, parse_qs
- VERSIONS_FILE = "versions.json"
- BASE_DIR = "optifine"
+from meta.common import upstream_path, ensure_upstream_dir, default_session
+from meta.common.optifine import VERSIONS_FILE, BASE_DIR
UPSTREAM_DIR = upstream_path()
-
ensure_upstream_dir(BASE_DIR)
-sess = None
-if HAVE_META:
- sess = default_session()
+sess = default_session()
+# Configurable via environment
+TIMEOUT = float(os.environ.get("OPTIFINE_TIMEOUT", "10"))
+HASH_TIMEOUT = float(os.environ.get("OPTIFINE_HASH_TIMEOUT", "120"))
+CONCURRENCY = max(1, int(os.environ.get("OPTIFINE_CONCURRENCY", "8")))
+COMPUTE_HASH = os.environ.get("OPTIFINE_COMPUTE_HASH", "1").lower() in ("1", "true", "yes")
-def _resolve_href(href: str):
- """Return (filename, resolved_href).
- Handles cases where href is a redirect wrapper (e.g., adfoc.us with an inner
- 'url=' parameter) or where the 'f' query parameter is present.
- """
+def _resolve_href(href: str):
+ """Return (filename, resolved_href) from an OptiFine download link."""
parsed = urlparse(href)
q = parse_qs(parsed.query)
- # Direct f parameter
f = q.get("f")
if f:
return f[0], href
- # Some wrappers embed an inner url parameter that contains the real target
inner = q.get("url")
if inner:
- # inner may be a list; pick first
- inner_url = inner[0]
- inner_parsed = urlparse(inner_url)
- inner_q = parse_qs(inner_parsed.query)
- inner_f = inner_q.get("f")
+ inner_parsed = urlparse(inner[0])
+ inner_f = parse_qs(inner_parsed.query).get("f")
if inner_f:
- return inner_f[0], inner_url
+ return inner_f[0], inner[0]
- # fallback: last path component
return os.path.basename(parsed.path), href
-def _clean_key(filename: str) -> str:
- # Remove OptiFine prefix, any trailing ad-wrapper segments, and the .jar suffix
- key = re.sub(r"^OptiFine[_-]", "", filename, flags=re.IGNORECASE)
- key = re.sub(r"\.jar$", "", key, flags=re.IGNORECASE)
- # Strip trailing ad/adload/adloadx wrapper fragments that appear in some links
- key = re.sub(r"[_-]ad[a-z0-9_-]*$", "", key, flags=re.IGNORECASE)
- return key
-
-
def _strip_ad_wrapper(filename: str) -> str:
- """Remove trailing ad/adload/adloadx wrapper fragments from a filename.
-
- Example: OptiFine_1.20.1_HD_U_H7_adloadx.jar -> OptiFine_1.20.1_HD_U_H7.jar
- """
+ """Remove trailing ad/adload/adloadx fragments from a filename."""
if not filename:
return filename
root, ext = os.path.splitext(filename)
- # remove trailing segments that start with _ad or -ad
root = re.sub(r"[_-]ad[a-z0-9_-]*$", "", root, flags=re.IGNORECASE)
return root + ext
-def _guess_platforms(filename: str, label: str = None, changelog: str = None):
- """Heuristically guess platform compatibility tags for an OptiFine build.
-
- Returns a list like ['mojang', 'neoforge', 'fabric'] based on keywords.
- """
- text = " ".join(filter(None, [filename or "", label or "", changelog or ""]))
- tl = text.lower()
- platforms = []
- # OptiFine always targets vanilla (Mojang) builds
- platforms.append("mojang")
- # Forge / NeoForge variants
- if "neoforge" in tl or "neo-forge" in tl or "forge" in tl:
- platforms.append("neoforge")
- # Fabric
- if "fabric" in tl:
- platforms.append("fabric")
- # Quilt
- if "quilt" in tl:
- platforms.append("quilt")
- # LiteLoader / older loaders
- if "liteloader" in tl:
- platforms.append("liteloader")
-
- # Deduplicate while preserving order
- seen = set()
- out = []
- for p in platforms:
- if p not in seen:
- seen.add(p)
- out.append(p)
- return out
+def _clean_key(filename: str) -> str:
+ """Normalize filename to version key: strip OptiFine_ prefix and .jar suffix."""
+ key = re.sub(r"^OptiFine[_-]", "", filename, flags=re.IGNORECASE)
+ key = re.sub(r"\.jar$", "", key, flags=re.IGNORECASE)
+ key = re.sub(r"[_-]ad[a-z0-9_-]*$", "", key, flags=re.IGNORECASE)
+ return key
def _score_entry(entry: dict) -> int:
+ """Score an entry to pick the best when duplicates exist."""
url = (entry.get("download_page") or "").lower()
s = 0
- if "optifine.net/adloadx" in url or "optifine.net/adload" in url or "optifine.net/download" in url:
+ if any(k in url for k in ("optifine.net/adloadx", "optifine.net/adload", "optifine.net/download")):
s += 10
- if url.endswith(".jar") or entry.get("filename", "").lower().endswith(".jar"):
+ if url.endswith(".jar") or (entry.get("filename") or "").lower().endswith(".jar"):
s += 5
if "preview" in (entry.get("filename") or "").lower():
s -= 2
return s
-def main():
- url = "https://optifine.net/downloads"
- print(f"Fetching OptiFine downloads page: {url}")
- # configurable timeouts (seconds)
- default_timeout = float(os.environ.get("OPTIFINE_TIMEOUT", "10"))
-
- try:
- r = sess.get(url, timeout=default_timeout)
- r.raise_for_status()
- html = r.text
- except Exception as e:
- print(f"Error fetching downloads page: {e}")
- html = ""
-
+def _scrape_downloads(html: str, base_url: str) -> dict:
+ """Parse OptiFine downloads page and return {key: entry_dict}."""
versions = {}
- # Try parsing with BeautifulSoup if available; be permissive about href forms
try:
from bs4 import BeautifulSoup
-
soup = BeautifulSoup(html, "html.parser")
- anchors = soup.find_all("a", href=True)
- inspected = 0
- matched = 0
- for a in anchors:
- inspected += 1
+
+ for a in soup.find_all("a", href=True):
href = a["href"]
href_l = href.lower()
-
- # Accept several formats: any URL containing '?f=' (adload/adloadx/download), or direct .jar links
if "?f=" not in href_l and not href_l.endswith(".jar"):
continue
- matched += 1
filename, resolved = _resolve_href(href)
- # strip ad/adload/adloadx wrapper parts from filename
filename = _strip_ad_wrapper(filename)
- # Try to get version text from the same table row or nearby text
ver_text = None
changelog = None
date = None
+
tr = a.find_parent("tr")
if tr:
tds = tr.find_all("td")
if tds:
ver_text = tds[0].get_text(strip=True)
- # find changelog link in the row
ch = tr.find("a", href=lambda h: h and "changelog" in h)
if ch:
changelog = ch.get("href")
- # find date cell
date_td = tr.find("td", class_=lambda c: c and "colDate" in c)
if date_td:
date = date_td.get_text(strip=True)
if not ver_text:
- # fallback: anchor text or nearby text nodes
- if a.string and a.string.strip():
- ver_text = a.string.strip()
- else:
- prev = a.find_previous(string=True)
- if prev:
- ver_text = prev.strip()
+ ver_text = (a.string or "").strip() or filename
key = _clean_key(filename)
data = {
"filename": filename,
- "download_page": urljoin(url, resolved),
- "label": ver_text or filename,
+ "download_page": urljoin(base_url, resolved),
+ "label": ver_text,
"changelog": changelog,
"date": date,
}
@@ -208,13 +117,9 @@ def main():
existing = versions.get(key)
if existing is None or _score_entry(data) > _score_entry(existing):
versions[key] = data
- platforms = _guess_platforms(data.get("filename"), data.get("label"), data.get("changelog"))
- print(f"Added {key}: platforms: {', '.join(platforms)}")
-
- print(f"Inspected {inspected} anchors, matched {matched} potential downloads")
- except Exception:
- # Fallback: regex parse (case-insensitive)
- print("BeautifulSoup not available or parsing failed, falling back to regex parse")
+ except ImportError:
+ # Fallback: regex parse
+ print("BeautifulSoup not available, falling back to regex parse")
for match in re.finditer(r'href="([^"]*\?f=[^"\s]+)"', html, flags=re.IGNORECASE):
href = match.group(1)
filename, resolved = _resolve_href(href)
@@ -222,234 +127,108 @@ def main():
key = _clean_key(filename)
data = {
"filename": filename,
- "download_page": urljoin(url, resolved),
+ "download_page": urljoin(base_url, resolved),
"label": filename,
}
existing = versions.get(key)
if existing is None or _score_entry(data) > _score_entry(existing):
versions[key] = data
- platforms = _guess_platforms(data.get("filename"), data.get("label"), data.get("changelog"))
- print(f"Added {key}: platforms: {', '.join(platforms)}")
- # Determine base output directory. Some upstream implementations return a
- # path that already includes BASE_DIR, avoid duplicating it.
- if UPSTREAM_DIR.endswith(BASE_DIR):
- base_out_dir = UPSTREAM_DIR
- else:
- base_out_dir = os.path.join(UPSTREAM_DIR, BASE_DIR)
-
- # Ensure output directory exists (defensive: collapse duplicate trailing BASE_DIR segments)
- parts = base_out_dir.split(os.sep)
- while len(parts) >= 2 and parts[-1] == BASE_DIR and parts[-2] == BASE_DIR:
- parts.pop(-1)
- base_out_dir = os.sep.join(parts)
- os.makedirs(base_out_dir, exist_ok=True)
-
- out_path = os.path.join(base_out_dir, VERSIONS_FILE)
- # Attempt to resolve final download URLs and optionally compute hashes
- # Default to computing SHA256 for each resolved file unless explicitly disabled
- compute_hash = os.environ.get("OPTIFINE_COMPUTE_HASH", "1").lower() in ("1", "true", "yes")
- resolved_count = 0
- hashed_count = 0
-
- if HAVE_META and sess is not None:
+ return versions
+
+
+def _make_download_url(filename: str) -> str:
+ """Build a stable OptiFine download URL from a filename.
+
+ Uses the permanent https://optifine.net/download?f=FILENAME format
+ instead of the adloadx/downloadx token URLs which expire.
+ """
+ return f"https://optifine.net/download?f={filename}"
+
+
+def _resolve_and_hash(key: str, data: dict) -> dict:
+ """Build stable download URL and optionally compute SHA256 for a single entry."""
+ filename = data.get("filename")
+ if not filename:
+ return data
+
+ # Use stable download URL instead of expiring token-based downloadx URLs
+ download_url = _make_download_url(filename)
+ data["resolved_url"] = download_url
+
+ # Compute hash if enabled
+ if COMPUTE_HASH:
try:
- # Use a ThreadPoolExecutor to parallelize network I/O for resolving URLs
- concurrency = int(os.environ.get("OPTIFINE_CONCURRENCY", "8"))
- if concurrency < 1:
- concurrency = 1
-
- total = len(versions)
- counter = {"idx": 0}
- counter_lock = threading.Lock()
-
- def _process_item(item):
- key, data = item
- with counter_lock:
- counter["idx"] += 1
- idx = counter["idx"]
-
- dp = data.get("download_page")
- if not dp:
- return key, data, False, False
-
- print(f"[{idx}/{total}] Resolving {key} ({data.get('filename')}) -> {dp}")
-
- # Each worker creates its own session to avoid any session thread-safety issues
- sess_local = None
- if HAVE_META:
- try:
- sess_local = default_session()
- except Exception:
- sess_local = None
-
- # Fallback to global sess if default_session unavailable
- if sess_local is None:
- sess_local = sess
-
- final_url = None
- try:
- # Try HEAD first
- try:
- resp = sess_local.head(dp, allow_redirects=True, timeout=default_timeout)
- except Exception as e_head:
- # Try GET as fallback for hosts that block HEAD
- try:
- resp = sess_local.get(dp, allow_redirects=True, timeout=default_timeout)
- except Exception:
- resp = None
-
- if resp is not None:
- final_url = getattr(resp, "url", None)
-
- # Try to extract downloadx link from page HTML (short GET if needed)
- page_text = None
- if resp is not None and hasattr(resp, "text") and resp.text:
- page_text = resp.text
- else:
- try:
- rtmp = sess_local.get(dp, allow_redirects=True, timeout=5)
- page_text = getattr(rtmp, "text", None)
- final_url = getattr(rtmp, "url", final_url)
- except Exception:
- page_text = None
-
- if page_text:
- m = re.search(r"(downloadx\?f=[^\"'\s>]+)", page_text, flags=re.IGNORECASE)
- if m:
- candidate = m.group(1)
- base_for_join = final_url or dp
- final_url = urljoin(base_for_join, candidate)
- print(f" Extracted downloadx link for {key}: {final_url}")
-
- # If still not a .jar/f param, do a full GET and inspect final URL
- if not final_url or (".jar" not in final_url and "?f=" not in final_url):
- try:
- resp2 = sess_local.get(dp, allow_redirects=True, timeout=30)
- final_url = getattr(resp2, "url", final_url)
- except Exception:
- pass
-
- hashed = False
- if final_url:
- data["resolved_url"] = final_url
- print(f" Resolved {key} -> {final_url}")
-
- if compute_hash:
- try:
- import hashlib
-
- print(f" Hashing {key} from {final_url} ...")
- h = hashlib.sha256()
- size = 0
- hash_timeout = float(os.environ.get("OPTIFINE_HASH_TIMEOUT", "120"))
- r2 = sess_local.get(final_url, stream=True, timeout=hash_timeout)
- r2.raise_for_status()
- for chunk in r2.iter_content(8192):
- if not chunk:
- continue
- h.update(chunk)
- size += len(chunk)
- data["sha256"] = h.hexdigest()
- data["size"] = size
- hashed = True
- print(f" Hashed {key}: sha256={data['sha256']} size={data['size']}")
- except Exception as e_hash:
- print(f" Warning: failed to hash {final_url}: {e_hash}")
-
- return key, data, bool(final_url), hashed
- except Exception as e:
- print(f" Error processing {key}: {e}")
- return key, data, False, False
-
- items = list(versions.items())
- if concurrency == 1:
- # run serially
- results = map(_process_item, items)
- else:
- with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as ex:
- results = ex.map(_process_item, items)
-
- # Collect results and write per-version files as each item completes
- for key, data, resolved_flag, hashed_flag in results:
- versions[key] = data
- # Ensure per-version dir exists
- try:
- os.makedirs(base_out_dir, exist_ok=True)
- per_path = os.path.join(base_out_dir, f"{key}.json")
- with open(per_path, "w") as pf:
- json.dump(data, pf, indent=4)
- print(f"Wrote per-version file: {per_path}")
- except Exception as e:
- print(f"Warning: failed to write per-version file for {key}: {e}")
-
- if resolved_flag:
- resolved_count += 1
- if hashed_flag:
- hashed_count += 1
- except KeyboardInterrupt:
- print("Interrupted by user (KeyboardInterrupt). Writing partial results...")
-
- # Write combined index (ensure parent exists)
- os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
- with open(out_path, "w") as f:
- json.dump(versions, f, indent=4)
+ h = hashlib.sha256()
+ size = 0
+ r = sess.get(download_url, stream=True, timeout=HASH_TIMEOUT)
+ r.raise_for_status()
+ for chunk in r.iter_content(8192):
+ if chunk:
+ h.update(chunk)
+ size += len(chunk)
+ data["sha256"] = h.hexdigest()
+ data["size"] = size
+ except Exception as e:
+ print(f" Warning: hash failed for {key}: {e}")
+
+ return data
+
+
+def main():
+ url = "https://optifine.net/downloads"
+ print(f"Fetching OptiFine downloads page: {url}")
- # Also write per-version JSON files under the upstream component directory
try:
- for key, data in versions.items():
- per_path = os.path.join(base_out_dir, f"{key}.json")
- with open(per_path, "w") as pf:
- json.dump(data, pf, indent=4)
- print(f"Wrote per-version file: {per_path}")
+ r = sess.get(url, timeout=TIMEOUT)
+ r.raise_for_status()
+ html = r.text
except Exception as e:
- print(f"Warning: failed to write per-version files: {e}")
-
- print(f"Wrote {len(versions)} OptiFine entries to {out_path}")
- if HAVE_META and sess is not None:
- print(f"Resolved {resolved_count} final URLs")
- if compute_hash:
- print(f"Computed {hashed_count} SHA256 hashes (OPTIFINE_COMPUTE_HASH=1)")
- # If some entries are missing sha256 (e.g., were written before hashing completed),
- # compute them now in parallel and update files.
- missing = [ (k,v) for k,v in versions.items() if v.get("resolved_url") and not v.get("sha256") ]
- if missing:
- print(f"Computing missing SHA256 for {len(missing)} entries...")
- def _compute_and_write(item):
- k, v = item
- url_final = v.get("resolved_url")
- try:
- import hashlib
- hash_timeout = float(os.environ.get("OPTIFINE_HASH_TIMEOUT", "120"))
- h = hashlib.sha256()
- size = 0
- r = sess.get(url_final, stream=True, timeout=hash_timeout)
- r.raise_for_status()
- for chunk in r.iter_content(8192):
- if not chunk:
- continue
- h.update(chunk)
- size += len(chunk)
- v["sha256"] = h.hexdigest()
- v["size"] = size
- per_path = os.path.join(base_out_dir, f"{k}.json")
- with open(per_path, "w") as pf:
- json.dump(v, pf, indent=4)
- print(f" Hashed {k}: {v['sha256']} size={v['size']}")
- return True
- except Exception as e:
- print(f" Warning: failed to compute hash for {k}: {e}")
- return False
-
- concurrency = int(os.environ.get("OPTIFINE_CONCURRENCY", "8"))
- if concurrency < 1:
- concurrency = 1
- completed = 0
- with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as ex:
- for ok in ex.map(_compute_and_write, missing):
- if ok:
- completed += 1
- print(f"Completed extra hashing: {completed}/{len(missing)}")
+ print(f"Error fetching downloads page: {e}")
+ return
+
+ versions = _scrape_downloads(html, url)
+ print(f"Scraped {len(versions)} OptiFine entries")
+
+ if not versions:
+ print("No versions found, aborting")
+ return
+
+ out_dir = os.path.join(UPSTREAM_DIR, BASE_DIR)
+ os.makedirs(out_dir, exist_ok=True)
+
+ # Resolve URLs and compute hashes in parallel
+ def _process(item):
+ key, data = item
+ print(f" Resolving {key}...")
+ data = _resolve_and_hash(key, data)
+ return key, data
+
+ items = list(versions.items())
+ if CONCURRENCY == 1:
+ results = [_process(item) for item in items]
+ else:
+ with concurrent.futures.ThreadPoolExecutor(max_workers=CONCURRENCY) as ex:
+ results = list(ex.map(_process, items))
+
+ # Write per-version files and build combined index
+ versions = {}
+ for key, data in results:
+ versions[key] = data
+ per_path = os.path.join(out_dir, f"{key}.json")
+ with open(per_path, "w") as f:
+ json.dump(data, f, indent=4)
+
+ # Write combined index
+ combined_path = os.path.join(UPSTREAM_DIR, VERSIONS_FILE)
+ os.makedirs(os.path.dirname(combined_path) or ".", exist_ok=True)
+ with open(combined_path, "w") as f:
+ json.dump(versions, f, indent=4)
+
+ resolved = sum(1 for v in versions.values() if v.get("resolved_url"))
+ hashed = sum(1 for v in versions.values() if v.get("sha256"))
+ print(f"Wrote {len(versions)} entries to {combined_path}")
+ print(f"Resolved: {resolved}, Hashed: {hashed}")
if __name__ == "__main__":
diff --git a/meta/meta/run/update_risugami.py b/meta/meta/run/update_risugami.py
index 66bd59eff8..26d162fac8 100644
--- a/meta/meta/run/update_risugami.py
+++ b/meta/meta/run/update_risugami.py
@@ -1,24 +1,64 @@
import json
import os
-from meta.common import upstream_path, ensure_upstream_dir, default_session
+from meta.common import upstream_path, ensure_upstream_dir
from meta.common.risugami import VERSIONS_FILE, BASE_DIR
UPSTREAM_DIR = upstream_path()
-
ensure_upstream_dir(BASE_DIR)
-sess = default_session()
+# Risugami's ModLoader is a legacy/archived project (last updated around MC 1.6.2).
+# No active upstream API exists; versions are curated from known historical releases.
+# To add a version, add an entry to this dict with the Minecraft version it targets.
+KNOWN_VERSIONS = {
+ "1.6.2": {"mc_version": "1.6.2", "label": "ModLoader 1.6.2"},
+ "1.6.1": {"mc_version": "1.6.1", "label": "ModLoader 1.6.1"},
+ "1.5.2": {"mc_version": "1.5.2", "label": "ModLoader 1.5.2"},
+ "1.5.1": {"mc_version": "1.5.1", "label": "ModLoader 1.5.1"},
+ "1.4.7": {"mc_version": "1.4.7", "label": "ModLoader 1.4.7"},
+ "1.4.6": {"mc_version": "1.4.6", "label": "ModLoader 1.4.6"},
+ "1.4.5": {"mc_version": "1.4.5", "label": "ModLoader 1.4.5"},
+ "1.4.4": {"mc_version": "1.4.4", "label": "ModLoader 1.4.4"},
+ "1.4.2": {"mc_version": "1.4.2", "label": "ModLoader 1.4.2"},
+ "1.3.2": {"mc_version": "1.3.2", "label": "ModLoader 1.3.2"},
+ "1.3.1": {"mc_version": "1.3.1", "label": "ModLoader 1.3.1"},
+ "1.2.5": {"mc_version": "1.2.5", "label": "ModLoader 1.2.5"},
+ "1.2.4": {"mc_version": "1.2.4", "label": "ModLoader 1.2.4"},
+ "1.2.3": {"mc_version": "1.2.3", "label": "ModLoader 1.2.3"},
+ "1.1": {"mc_version": "1.1", "label": "ModLoader 1.1"},
+ "1.0": {"mc_version": "1.0", "label": "ModLoader 1.0"},
+ "b1.8.1": {"mc_version": "b1.8.1", "label": "ModLoader b1.8.1"},
+ "b1.7.3": {"mc_version": "b1.7.3", "label": "ModLoader b1.7.3"},
+ "b1.6.6": {"mc_version": "b1.6.6", "label": "ModLoader b1.6.6"},
+ "b1.5_01": {"mc_version": "b1.5_01", "label": "ModLoader b1.5_01"},
+ "b1.4_01": {"mc_version": "b1.4_01", "label": "ModLoader b1.4_01"},
+ "b1.3_01": {"mc_version": "b1.3_01", "label": "ModLoader b1.3_01"},
+}
def main():
- # Placeholder updater: upstream source not implemented yet.
- # Create an empty versions file so the meta pipeline can proceed.
+ # Merge with any existing entries to preserve manually-added data (sha256, urls, etc.)
out_path = os.path.join(UPSTREAM_DIR, VERSIONS_FILE)
+ existing = {}
+ if os.path.exists(out_path):
+ try:
+ with open(out_path, "r") as f:
+ existing = json.load(f)
+ except Exception:
+ pass
+
+ for key, data in KNOWN_VERSIONS.items():
+ if key not in existing:
+ existing[key] = data
+ else:
+ # Preserve existing fields, add any missing ones from known data
+ for field, value in data.items():
+ existing[key].setdefault(field, value)
+
with open(out_path, "w") as f:
- json.dump({}, f, indent=4)
+ json.dump(existing, f, indent=4)
- print(f"Wrote placeholder upstream file: {out_path}")
+ print(f"Wrote {len(existing)} Risugami ModLoader entries to {out_path}")
if __name__ == "__main__":
diff --git a/meta/meta/run/update_stationloader.py b/meta/meta/run/update_stationloader.py
index 3e0a4b34bb..b421976eb8 100644
--- a/meta/meta/run/update_stationloader.py
+++ b/meta/meta/run/update_stationloader.py
@@ -5,19 +5,85 @@ from meta.common import upstream_path, ensure_upstream_dir, default_session
from meta.common.stationloader import VERSIONS_FILE, BASE_DIR
UPSTREAM_DIR = upstream_path()
-
ensure_upstream_dir(BASE_DIR)
sess = default_session()
+# StationAPI/StationLoader GitHub releases
+GITHUB_API_URL = "https://api.github.com/repos/modificationstation/StationAPI/releases"
+
def main():
- # Placeholder updater: upstream source not implemented yet.
out_path = os.path.join(UPSTREAM_DIR, VERSIONS_FILE)
+
+ # Load existing entries to preserve manually-added data
+ existing = {}
+ if os.path.exists(out_path):
+ try:
+ with open(out_path, "r") as f:
+ existing = json.load(f)
+ except Exception:
+ pass
+
+ # Fetch releases from GitHub
+ print(f"Fetching StationAPI releases from {GITHUB_API_URL}")
+ try:
+ r = sess.get(GITHUB_API_URL, timeout=15)
+ r.raise_for_status()
+ releases = r.json()
+ except Exception as e:
+ print(f"Error fetching GitHub releases: {e}")
+ # Write whatever we have
+ with open(out_path, "w") as f:
+ json.dump(existing, f, indent=4)
+ return
+
+ for release in releases:
+ tag = release.get("tag_name", "")
+ if not tag:
+ continue
+
+ key = tag.lstrip("v")
+ prerelease = release.get("prerelease", False)
+ published = release.get("published_at")
+
+ # Find the main jar asset
+ jar_url = None
+ jar_size = None
+ jar_name = None
+ for asset in release.get("assets", []):
+ name = asset.get("name", "")
+ if name.endswith(".jar"):
+ jar_url = asset.get("browser_download_url")
+ jar_size = asset.get("size")
+ jar_name = name
+ break
+
+ data = {
+ "mc_version": "b1.7.3",
+ "label": f"StationAPI {key}",
+ "tag": tag,
+ "prerelease": prerelease,
+ }
+ if published:
+ data["date"] = published
+ if jar_url:
+ data["url"] = jar_url
+ if jar_size is not None:
+ data["size"] = jar_size
+ if jar_name:
+ data["filename"] = jar_name
+
+ if key not in existing:
+ existing[key] = data
+ else:
+ for field, value in data.items():
+ existing[key].setdefault(field, value)
+
with open(out_path, "w") as f:
- json.dump({}, f, indent=4)
+ json.dump(existing, f, indent=4)
- print(f"Wrote placeholder upstream file: {out_path}")
+ print(f"Wrote {len(existing)} StationLoader entries to {out_path}")
if __name__ == "__main__":