summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStuart Pomeroy <rushmead@gmail.com>2023-08-06 12:15:18 +0100
committerSefa Eyeoglu <contact@scrumplex.net>2023-08-06 14:47:30 +0200
commitc06bc0cdaf2540bce9633e6d65fe68bb7fa62fe5 (patch)
treed92348ba5efb1003d7276b8033da0c3723ce3f2d
parent63194d47e829a8c072e5a40dac37863a21d1b11d (diff)
downloadProject-Tick-c06bc0cdaf2540bce9633e6d65fe68bb7fa62fe5.tar.gz
Project-Tick-c06bc0cdaf2540bce9633e6d65fe68bb7fa62fe5.zip
Add support for automatically generating NeoForge metadata
-rw-r--r--generateNeoForge.py424
-rwxr-xr-xindex.py3
-rw-r--r--meta/common/neoforge.py17
-rw-r--r--meta/model/__init__.py2
-rw-r--r--meta/model/neoforge.py264
-rw-r--r--testNeo.sh52
-rwxr-xr-xupdate.sh2
-rw-r--r--updateNeoForge.py338
8 files changed, 1100 insertions, 2 deletions
diff --git a/generateNeoForge.py b/generateNeoForge.py
new file mode 100644
index 0000000000..2826c037b6
--- /dev/null
+++ b/generateNeoForge.py
@@ -0,0 +1,424 @@
+import os
+import re
+import sys
+from distutils.version import LooseVersion
+from operator import attrgetter
+from typing import Collection
+
+from meta.common import ensure_component_dir, launcher_path, upstream_path, static_path
+from meta.common.neoforge import (
+ NEOFORGE_COMPONENT,
+ INSTALLER_MANIFEST_DIR,
+ VERSION_MANIFEST_DIR,
+ DERIVED_INDEX_FILE,
+ STATIC_LEGACYINFO_FILE,
+ INSTALLER_INFO_DIR,
+ BAD_VERSIONS,
+ FORGEWRAPPER_MAVEN,
+)
+from meta.common.forge import (FORGE_COMPONENT)
+from meta.common.mojang import MINECRAFT_COMPONENT
+from meta.model import (
+ MetaVersion,
+ Dependency,
+ Library,
+ GradleSpecifier,
+ MojangLibraryDownloads,
+ MojangArtifact,
+ MetaPackage,
+)
+from meta.model.neoforge import (
+ NeoForgeVersion,
+ NeoForgeInstallerProfile,
+ NeoForgeLegacyInfo,
+ fml_libs_for_version,
+ NeoForgeInstallerProfileV2,
+ InstallerInfo,
+ DerivedNeoForgeIndex,
+ NeoForgeLegacyInfoList,
+)
+from meta.model.mojang import MojangVersion
+
+LAUNCHER_DIR = launcher_path()
+UPSTREAM_DIR = upstream_path()
+STATIC_DIR = static_path()
+
+ensure_component_dir(NEOFORGE_COMPONENT)
+
+
+def eprint(*args, **kwargs):
+ print(*args, file=sys.stderr, **kwargs)
+
+
+# Construct a set of libraries out of a Minecraft version file, for filtering.
+mc_version_cache = {}
+
+
+def load_mc_version_filter(version: str):
+ if version in mc_version_cache:
+ return mc_version_cache[version]
+ v = MetaVersion.parse_file(
+ os.path.join(LAUNCHER_DIR, MINECRAFT_COMPONENT, f"{version}.json")
+ )
+ libs = set(map(attrgetter("name"), v.libraries))
+ mc_version_cache[version] = libs
+ return libs
+
+
+"""
+Match a library coordinate to a set of library coordinates.
+ * Block those that pass completely.
+ * For others, block those with lower versions than in the set.
+"""
+
+
+def should_ignore_artifact(libs: Collection[GradleSpecifier], match: GradleSpecifier):
+ for ver in libs:
+ if (
+ ver.group == match.group
+ and ver.artifact == match.artifact
+ and ver.classifier == match.classifier
+ ):
+ if ver.version == match.version:
+ # Everything is matched perfectly - this one will be ignored
+ return True
+ elif LooseVersion(ver.version) > LooseVersion(match.version):
+ return True
+ else:
+ # Otherwise it did not match - new version is higher and this is an upgrade
+ return False
+ # No match found in the set - we need to keep this
+ return False
+
+
+def version_from_profile(
+ profile: NeoForgeInstallerProfile, version: NeoForgeVersion
+) -> MetaVersion:
+ v = MetaVersion(name="NeoForge", version=version.rawVersion, uid=NEOFORGE_COMPONENT)
+ mc_version = profile.install.minecraft
+ v.requires = [Dependency(uid=MINECRAFT_COMPONENT, equals=mc_version)]
+ v.main_class = profile.version_info.main_class
+ v.release_time = profile.version_info.time
+
+ args = profile.version_info.minecraft_arguments
+ tweakers = []
+ expression = re.compile(r"--tweakClass ([a-zA-Z0-9.]+)")
+ match = expression.search(args)
+ while match is not None:
+ tweakers.append(match.group(1))
+ args = args[: match.start()] + args[match.end() :]
+ match = expression.search(args)
+ if len(tweakers) > 0:
+ args = args.strip()
+ v.additional_tweakers = tweakers
+ # v.minecraftArguments = args
+
+ v.libraries = []
+ mc_filter = load_mc_version_filter(mc_version)
+ for forge_lib in profile.version_info.libraries:
+ if (
+ forge_lib.name.is_lwjgl()
+ or forge_lib.name.is_log4j()
+ or should_ignore_artifact(mc_filter, forge_lib.name)
+ ):
+ continue
+
+ overridden_name = forge_lib.name
+ if overridden_name.group == "net.minecraftforge":
+ if overridden_name.artifact == "minecraftforge":
+ overridden_name.artifact = "forge"
+ overridden_name.version = "%s-%s" % (
+ mc_version,
+ overridden_name.version,
+ )
+
+ overridden_name.classifier = "universal"
+ elif overridden_name.artifact == "forge":
+ overridden_name.classifier = "universal"
+
+ overridden_lib = Library(name=overridden_name)
+ if forge_lib.url == "http://maven.minecraftforge.net/":
+ overridden_lib.url = "https://maven.minecraftforge.net/"
+ else:
+ overridden_lib.url = forge_lib.url
+ # if forge_lib.checksums and len(forge_lib.checksums) == 2:
+ # overridden_lib.mmcHint = "forge-pack-xz"
+ v.libraries.append(overridden_lib)
+
+ v.order = 5
+ return v
+
+
+def version_from_modernized_installer(
+ installer: MojangVersion, version: NeoForgeVersion
+) -> MetaVersion:
+ v = MetaVersion(name="NeoForge", version=version.rawVersion, uid=NEOFORGE_COMPONENT)
+ mc_version = version.mc_version
+ v.requires = [Dependency(uid=MINECRAFT_COMPONENT, equals=mc_version)]
+ v.main_class = installer.main_class
+ v.release_time = installer.release_time
+
+ args = installer.minecraft_arguments
+ tweakers = []
+ expression = re.compile("--tweakClass ([a-zA-Z0-9.]+)")
+ match = expression.search(args)
+ while match is not None:
+ tweakers.append(match.group(1))
+ args = args[: match.start()] + args[match.end() :]
+ match = expression.search(args)
+ if len(tweakers) > 0:
+ args = args.strip()
+ v.additional_tweakers = tweakers
+ # v.minecraftArguments = args
+
+ v.libraries = []
+
+ mc_filter = load_mc_version_filter(mc_version)
+ for upstream_lib in installer.libraries:
+ forge_lib = Library.parse_obj(
+ upstream_lib.dict()
+ ) # "cast" MojangLibrary to Library
+ if (
+ forge_lib.name.is_lwjgl()
+ or forge_lib.name.is_log4j()
+ or should_ignore_artifact(mc_filter, forge_lib.name)
+ ):
+ continue
+
+ if forge_lib.name.group == "net.minecraftforge":
+ if forge_lib.name.artifact == "forge":
+ overridden_name = forge_lib.name
+ overridden_name.classifier = "universal"
+ forge_lib.downloads.artifact.path = overridden_name.path()
+ forge_lib.downloads.artifact.url = (
+ "https://maven.minecraftforge.net/%s" % overridden_name.path()
+ )
+ forge_lib.name = overridden_name
+
+ elif forge_lib.name.artifact == "minecraftforge":
+ overridden_name = forge_lib.name
+ overridden_name.artifact = "forge"
+ overridden_name.classifier = "universal"
+ overridden_name.version = "%s-%s" % (
+ mc_version,
+ overridden_name.version,
+ )
+ forge_lib.downloads.artifact.path = overridden_name.path()
+ forge_lib.downloads.artifact.url = (
+ "https://maven.minecraftforge.net/%s" % overridden_name.path()
+ )
+ forge_lib.name = overridden_name
+
+ v.libraries.append(forge_lib)
+
+ v.order = 5
+ return v
+
+
+def version_from_legacy(info: NeoForgeLegacyInfo, version: NeoForgeVersion) -> MetaVersion:
+ v = MetaVersion(name="NeoForge", version=version.rawVersion, uid=NEOFORGE_COMPONENT)
+ mc_version = version.mc_version_sane
+ v.requires = [Dependency(uid=MINECRAFT_COMPONENT, equals=mc_version)]
+ v.release_time = info.release_time
+ v.order = 5
+ if fml_libs_for_version(
+ mc_version
+ ): # WHY, WHY DID I WASTE MY TIME REWRITING FMLLIBSMAPPING
+ v.additional_traits = ["legacyFML"]
+
+ classifier = "client"
+ if "universal" in version.url():
+ classifier = "universal"
+
+ main_mod = Library(
+ name=GradleSpecifier(
+ "net.minecraftforge", "forge", version.long_version, classifier
+ )
+ )
+ main_mod.downloads = MojangLibraryDownloads()
+ main_mod.downloads.artifact = MojangArtifact(
+ url=version.url(), sha1=info.sha1, size=info.size
+ )
+ main_mod.downloads.artifact.path = None
+ v.jar_mods = [main_mod]
+ return v
+
+
+def version_from_build_system_installer(
+ installer: MojangVersion, profile: NeoForgeInstallerProfileV2, version: NeoForgeVersion
+) -> MetaVersion:
+ v = MetaVersion(name="NeoForge", version=version.rawVersion, uid=NEOFORGE_COMPONENT)
+ v.requires = [Dependency(uid=MINECRAFT_COMPONENT, equals=version.mc_version_sane)]
+ v.main_class = "io.github.zekerzhayard.forgewrapper.installer.Main"
+
+ # FIXME: Add the size and hash here
+ v.maven_files = []
+
+ # load the locally cached installer file info and use it to add the installer entry in the json
+ info = InstallerInfo.parse_file(
+ os.path.join(UPSTREAM_DIR, INSTALLER_INFO_DIR, f"{version.long_version}.json")
+ )
+ installer_lib = Library(
+ name=GradleSpecifier(
+ "net.neoforged", "forge", version.long_version, "installer"
+ )
+ )
+ installer_lib.downloads = MojangLibraryDownloads()
+ installer_lib.downloads.artifact = MojangArtifact(
+ url="https://maven.neoforged.net/%s" % (installer_lib.name.path()),
+ sha1=info.sha1hash,
+ size=info.size,
+ )
+ v.maven_files.append(installer_lib)
+
+ for upstream_lib in profile.libraries:
+ forge_lib = Library.parse_obj(upstream_lib.dict())
+ if forge_lib.name.is_log4j():
+ continue
+
+ if (
+ forge_lib.name.group == "net.neoforged"
+ and forge_lib.name.artifact == "forge"
+ and forge_lib.name.classifier == "universal"
+ ):
+ forge_lib.downloads.artifact.url = (
+ "https://maven.neoforged.net/%s" % forge_lib.name.path()
+ )
+ v.maven_files.append(forge_lib)
+
+ v.libraries = []
+
+ wrapper_lib = Library(
+ name=GradleSpecifier("io.github.zekerzhayard", "ForgeWrapper", "1.5.6"))
+ wrapper_lib.downloads = MojangLibraryDownloads()
+ wrapper_lib.downloads.artifact = MojangArtifact(
+ url=FORGEWRAPPER_MAVEN,
+ sha1="b38d28e8b7fde13b1bc0db946a2da6760fecf98d",
+ size=34715,
+ )
+ v.libraries.append(wrapper_lib)
+
+ for upstream_lib in installer.libraries:
+ forge_lib = Library.parse_obj(upstream_lib.dict())
+ if forge_lib.name.is_log4j():
+ continue
+
+ if forge_lib.name.group == "net.neoforged":
+ if forge_lib.name.artifact == "forge":
+ forge_lib.name.classifier = "launcher"
+ forge_lib.downloads.artifact.path = forge_lib.name.path()
+ forge_lib.downloads.artifact.url = (
+ "https://maven.neoforged.net/%s" % forge_lib.name.path()
+ )
+ forge_lib.name = forge_lib.name
+ v.libraries.append(forge_lib)
+
+ v.release_time = installer.release_time
+ v.order = 5
+ mc_args = (
+ "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} "
+ "--assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} "
+ "--accessToken ${auth_access_token} --userType ${user_type} --versionType ${version_type}"
+ )
+ for arg in installer.arguments.game:
+ mc_args += f" {arg}"
+ v.minecraft_arguments = mc_args
+ return v
+
+
+def main():
+ # load the locally cached version list
+ remote_versions = DerivedNeoForgeIndex.parse_file(
+ os.path.join(UPSTREAM_DIR, DERIVED_INDEX_FILE)
+ )
+ recommended_versions = []
+
+
+ for key, entry in remote_versions.versions.items():
+ if entry.mc_version is None:
+ eprint("Skipping %s with invalid MC version" % key)
+ continue
+
+ version = NeoForgeVersion(entry)
+
+ if version.long_version in BAD_VERSIONS:
+ # Version 1.12.2-14.23.5.2851 is ultra cringe, I can't imagine why you would even spend one second on
+ # actually adding support for this version.
+ # It is cringe, because it's installer info is broken af
+ eprint(f"Skipping bad version {version.long_version}")
+ continue
+
+ if version.url() is None:
+ eprint("Skipping %s with no valid files" % key)
+ continue
+ eprint("Processing Forge %s" % version.rawVersion)
+ version_elements = version.rawVersion.split(".")
+ if len(version_elements) < 1:
+ eprint("Skipping version %s with not enough version elements" % key)
+ continue
+
+ major_version_str = version_elements[0]
+ if not major_version_str.isnumeric():
+ eprint(
+ "Skipping version %s with non-numeric major version %s"
+ % (key, major_version_str)
+ )
+ continue
+
+ if entry.recommended:
+ recommended_versions.append(version.rawVersion)
+
+ # If we do not have the corresponding Minecraft version, we ignore it
+ if not os.path.isfile(
+ os.path.join(
+ LAUNCHER_DIR, MINECRAFT_COMPONENT, f"{version.mc_version_sane}.json"
+ )
+ ):
+ eprint(
+ "Skipping %s with no corresponding Minecraft version %s"
+ % (key, version.mc_version_sane)
+ )
+ continue
+
+ # Path for new-style build system based installers
+ installer_version_filepath = os.path.join(
+ UPSTREAM_DIR, VERSION_MANIFEST_DIR, f"{version.long_version}.json"
+ )
+ profile_filepath = os.path.join(
+ UPSTREAM_DIR, INSTALLER_MANIFEST_DIR, f"{version.long_version}.json"
+ )
+
+ eprint(installer_version_filepath)
+ if os.path.isfile(installer_version_filepath):
+ installer = MojangVersion.parse_file(installer_version_filepath)
+ profile = NeoForgeInstallerProfileV2.parse_file(profile_filepath)
+ v = version_from_build_system_installer(installer, profile, version)
+ else:
+ if version.uses_installer():
+
+ # If we do not have the Forge json, we ignore this version
+ if not os.path.isfile(profile_filepath):
+ eprint("Skipping %s with missing profile json" % key)
+ continue
+ profile = NeoForgeInstallerProfile.parse_file(profile_filepath)
+ v = version_from_profile(profile, version)
+
+ v.write(os.path.join(LAUNCHER_DIR, NEOFORGE_COMPONENT, f"{v.version}.json"))
+ v.version = "NEO-"+v.version
+ v.write(os.path.join(LAUNCHER_DIR, FORGE_COMPONENT, f"{v.version}.json"))
+
+ recommended_versions.sort()
+
+ print("Recommended versions:", recommended_versions)
+
+ package = MetaPackage(
+ uid=NEOFORGE_COMPONENT,
+ name="NeoForge",
+ project_url="https://neoforged.net",
+ )
+ package.recommended = recommended_versions
+ package.write(os.path.join(LAUNCHER_DIR, NEOFORGE_COMPONENT, "package.json"))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/index.py b/index.py
index 38687037f4..2bf291616f 100755
--- a/index.py
+++ b/index.py
@@ -48,7 +48,6 @@ for package in sorted(os.listdir(LAUNCHER_DIR)):
for filename in os.listdir(LAUNCHER_DIR + "/%s" % package):
if filename in ignore:
continue
-
# parse and hash the version file
filepath = LAUNCHER_DIR + "/%s/%s" % (package, filename)
filehash = hash_file(hashlib.sha256, filepath)
@@ -58,7 +57,7 @@ for package in sorted(os.listdir(LAUNCHER_DIR)):
versionEntry = MetaVersionIndexEntry.from_meta_version(
versionFile, is_recommended, filehash
)
-
+
versionList.versions.append(versionEntry)
# sort the versions in descending order by time of release
diff --git a/meta/common/neoforge.py b/meta/common/neoforge.py
new file mode 100644
index 0000000000..d34ef19bd6
--- /dev/null
+++ b/meta/common/neoforge.py
@@ -0,0 +1,17 @@
+from os.path import join
+
+BASE_DIR = "neoforge"
+
+JARS_DIR = join(BASE_DIR, "jars")
+INSTALLER_INFO_DIR = join(BASE_DIR, "installer_info")
+INSTALLER_MANIFEST_DIR = join(BASE_DIR, "installer_manifests")
+VERSION_MANIFEST_DIR = join(BASE_DIR, "version_manifests")
+FILE_MANIFEST_DIR = join(BASE_DIR, "files_manifests")
+DERIVED_INDEX_FILE = join(BASE_DIR, "derived_index.json")
+
+STATIC_LEGACYINFO_FILE = join(BASE_DIR, "neoforge-legacyinfo.json")
+
+NEOFORGE_COMPONENT = "net.neoforged"
+
+FORGEWRAPPER_MAVEN = "https://github.com/ZekerZhayard/ForgeWrapper/releases/download/1.5.6/ForgeWrapper-1.5.6.jar"
+BAD_VERSIONS = [""]
diff --git a/meta/model/__init__.py b/meta/model/__init__.py
index 0246cdb3d1..14b054e5d2 100644
--- a/meta/model/__init__.py
+++ b/meta/model/__init__.py
@@ -1,5 +1,6 @@
import copy
from datetime import datetime
+from pathlib import Path
from typing import Optional, List, Dict, Any, Iterator
import pydantic
@@ -146,6 +147,7 @@ class MetaBase(pydantic.BaseModel):
)
def write(self, file_path):
+ Path(file_path).parent.mkdir(parents=True, exist_ok=True)
with open(file_path, "w") as f:
f.write(self.json())
diff --git a/meta/model/neoforge.py b/meta/model/neoforge.py
new file mode 100644
index 0000000000..b5721d04e8
--- /dev/null
+++ b/meta/model/neoforge.py
@@ -0,0 +1,264 @@
+from datetime import datetime
+from typing import Optional, List, Dict
+
+from pydantic import Field
+
+from . import MetaBase, GradleSpecifier, MojangLibrary
+from .mojang import MojangVersion
+
+
+class NeoForgeFile(MetaBase):
+ classifier: str
+ extension: str
+
+ def filename(self, long_version):
+ return "%s-%s-%s.%s" % ("forge", long_version, self.classifier, self.extension)
+
+ def url(self, long_version):
+ return "https://maven.neoforged.net/net/neoforged/forge/%s/%s" % (
+ long_version,
+ self.filename(long_version),
+ )
+
+
+class NeoForgeEntry(MetaBase):
+ long_version: str = Field(alias="longversion")
+ mc_version: str = Field(alias="mcversion")
+ version: str
+ build: int
+ branch: Optional[str]
+ latest: Optional[bool]
+ recommended: Optional[bool]
+ files: Optional[Dict[str, NeoForgeFile]]
+
+
+class NeoForgeMCVersionInfo(MetaBase):
+ latest: Optional[str]
+ recommended: Optional[str]
+ versions: List[str] = Field([])
+
+
+class DerivedNeoForgeIndex(MetaBase):
+ versions: Dict[str, NeoForgeEntry] = Field({})
+ by_mc_version: Dict[str, NeoForgeMCVersionInfo] = Field({}, alias="by_mcversion")
+
+
+class FMLLib(
+ MetaBase
+): # old ugly stuff. Maybe merge this with Library or MojangLibrary later
+ filename: str
+ checksum: str
+ ours: bool
+
+
+class NeoForgeInstallerProfileInstallSection(MetaBase):
+ """
+ "install": {
+ "profileName": "NeoForge",
+ "target":"NeoForge8.9.0.753",
+ "path":"net.minecraftNeoForge:minecraftNeoForge:8.9.0.753",
+ "version":"NeoForge 8.9.0.753",
+ "filePath":"minecraftNeoForge-universal-1.6.1-8.9.0.753.jar",
+ "welcome":"Welcome to the simple NeoForge installer.",
+ "minecraft":"1.6.1",
+ "logo":"/big_logo.png",
+ "mirrorList": "http://files.minecraftNeoForge.net/mirror-brand.list"
+ },
+ "install": {
+ "profileName": "NeoForge",
+ "target":"1.11-NeoForge1.11-13.19.0.2141",
+ "path":"net.minecraftNeoForge:NeoForge:1.11-13.19.0.2141",
+ "version":"NeoForge 1.11-13.19.0.2141",
+ "filePath":"NeoForge-1.11-13.19.0.2141-universal.jar",
+ "welcome":"Welcome to the simple NeoForge installer.",
+ "minecraft":"1.11",
+ "mirrorList" : "http://files.minecraftNeoForge.net/mirror-brand.list",
+ "logo":"/big_logo.png",
+ "modList":"none"
+ },
+ """
+
+ profile_name: str = Field(alias="profileName")
+ target: str
+ path: GradleSpecifier
+ version: str
+ file_path: str = Field(alias="filePath")
+ welcome: str
+ minecraft: str
+ logo: str
+ mirror_list: str = Field(alias="mirrorList")
+ mod_list: Optional[str] = Field(alias="modList")
+
+
+class NeoForgeLibrary(MojangLibrary):
+ url: Optional[str]
+ server_req: Optional[bool] = Field(alias="serverreq")
+ client_req: Optional[bool] = Field(alias="clientreq")
+ checksums: Optional[List[str]]
+ comment: Optional[str]
+
+
+class NeoForgeVersionFile(MojangVersion):
+ libraries: Optional[List[NeoForgeLibrary]] # overrides Mojang libraries
+ inherits_from: Optional[str] = Field("inheritsFrom")
+ jar: Optional[str]
+
+
+class NeoForgeOptional(MetaBase):
+ """
+ "optionals": [
+ {
+ "name": "Mercurius",
+ "client": true,
+ "server": true,
+ "default": true,
+ "inject": true,
+ "desc": "A mod that collects statistics about Minecraft and your system.<br>Useful for NeoForge to understand how Minecraft/NeoForge are used.",
+ "url": "http://www.minecraftNeoForge.net/forum/index.php?topic=43278.0",
+ "artifact": "net.minecraftNeoForge:MercuriusUpdater:1.11.2",
+ "maven": "http://maven.minecraftNeoForge.net/"
+ }
+ ]
+ """
+
+ name: Optional[str]
+ client: Optional[bool]
+ server: Optional[bool]
+ default: Optional[bool]
+ inject: Optional[bool]
+ desc: Optional[str]
+ url: Optional[str]
+ artifact: Optional[GradleSpecifier]
+ maven: Optional[str]
+
+
+class NeoForgeInstallerProfile(MetaBase):
+ install: NeoForgeInstallerProfileInstallSection
+ version_info: NeoForgeVersionFile = Field(alias="versionInfo")
+ optionals: Optional[List[NeoForgeOptional]]
+
+
+class NeoForgeLegacyInfo(MetaBase):
+ release_time: Optional[datetime] = Field(alias="releaseTime")
+ size: Optional[int]
+ sha256: Optional[str]
+ sha1: Optional[str]
+
+
+class NeoForgeLegacyInfoList(MetaBase):
+ number: Dict[str, NeoForgeLegacyInfo] = Field({})
+
+
+class DataSpec(MetaBase):
+ client: Optional[str]
+ server: Optional[str]
+
+
+class ProcessorSpec(MetaBase):
+ jar: Optional[str]
+ classpath: Optional[List[str]]
+ args: Optional[List[str]]
+ outputs: Optional[Dict[str, str]]
+ sides: Optional[List[str]]
+
+
+class NeoForgeInstallerProfileV2(MetaBase):
+ _comment: Optional[List[str]]
+ spec: Optional[int]
+ profile: Optional[str]
+ version: Optional[str]
+ icon: Optional[str]
+ json_data: Optional[str] = Field(alias="json")
+ path: Optional[GradleSpecifier]
+ logo: Optional[str]
+ minecraft: Optional[str]
+ welcome: Optional[str]
+ data: Optional[Dict[str, DataSpec]]
+ processors: Optional[List[ProcessorSpec]]
+ libraries: Optional[List[MojangLibrary]]
+ mirror_list: Optional[str] = Field(alias="mirrorList")
+ server_jar_path: Optional[str] = Field(alias="serverJarPath")
+
+
+class InstallerInfo(MetaBase):
+ sha1hash: Optional[str]
+ sha256hash: Optional[str]
+ size: Optional[int]
+
+
+# A post-processed entry constructed from the reconstructed NeoForge version index
+class NeoForgeVersion:
+ def __init__(self, entry: NeoForgeEntry):
+ self.build = entry.build
+ self.rawVersion = entry.version
+ self.mc_version = entry.mc_version
+ self.mc_version_sane = self.mc_version.replace("_pre", "-pre", 1)
+ self.branch = entry.branch
+ self.installer_filename = None
+ self.installer_url = None
+ self.universal_filename = None
+ self.universal_url = None
+ self.changelog_url = None
+ self.long_version = "%s-%s" % (self.mc_version, self.rawVersion)
+ if self.branch is not None:
+ self.long_version += "-%s" % self.branch
+
+ # this comment's whole purpose is to say this: cringe
+ for classifier, file in entry.files.items():
+ extension = file.extension
+ filename = file.filename(self.long_version)
+ url = file.url(self.long_version)
+ print(url)
+ print(self.long_version)
+ if (classifier == "installer") and (extension == "jar"):
+ self.installer_filename = filename
+ self.installer_url = url
+ if (classifier == "universal" or classifier == "client") and (
+ extension == "jar" or extension == "zip"
+ ):
+ self.universal_filename = filename
+ self.universal_url = url
+ if (classifier == "changelog") and (extension == "txt"):
+ self.changelog_url = url
+
+ def name(self):
+ return "forge %d" % self.build
+
+ def uses_installer(self):
+ if self.installer_url is None:
+ return False
+ if self.mc_version == "1.5.2":
+ return False
+ return True
+
+ def filename(self):
+ if self.uses_installer():
+ return self.installer_filename
+ return self.universal_filename
+
+ def url(self):
+ if self.uses_installer():
+ return self.installer_url
+ return self.universal_url
+
+ def is_supported(self):
+ if self.url() is None:
+ return False
+
+ foo = self.rawVersion.split(".")
+ if len(foo) < 1:
+ return False
+
+ major_version = foo[0]
+ if not major_version.isnumeric():
+ return False
+
+ # majorVersion = int(majorVersionStr)
+ # if majorVersion >= 37:
+ # return False
+
+ return True
+
+
+def fml_libs_for_version(mc_version: str) -> List[FMLLib]:
+ return []
diff --git a/testNeo.sh b/testNeo.sh
new file mode 100644
index 0000000000..92f7ac04fd
--- /dev/null
+++ b/testNeo.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+BASEDIR=$(dirname "$0")
+cd "${BASEDIR}" || exit 1
+BASEDIR=$(pwd)
+
+set -x
+
+source config.sh
+if [ -f config/config_local.sh ]; then
+ source config/config_local.sh
+fi
+
+MODE=${MODE:-develop}
+
+BRANCH_var="BRANCH_$MODE"
+BRANCH="${!BRANCH_var}"
+
+function fail_in {
+ upstream_git reset --hard HEAD
+ exit 1
+}
+
+function fail_out {
+ launcher_git reset --hard HEAD
+ exit 1
+}
+
+function upstream_git {
+ git -C "${BASEDIR}/${UPSTREAM_DIR}" "$@"
+}
+
+function launcher_git {
+ git -C "${BASEDIR}/${LAUNCHER_DIR}" "$@"
+}
+
+# make sure we *could* push to our repo
+
+currentDate=$(date -I)
+
+upstream_git reset --hard HEAD || exit 1
+upstream_git checkout "${BRANCH}" || exit 1
+
+
+python updateNeoForge.py || fail_in
+
+launcher_git reset --hard HEAD || exit 1
+launcher_git checkout "${BRANCH}" || exit 1
+
+
+python generateNeoForge.py || fail_out
+python index.py || fail_out \ No newline at end of file
diff --git a/update.sh b/update.sh
index 9286c80ca6..850e1cc883 100755
--- a/update.sh
+++ b/update.sh
@@ -43,6 +43,7 @@ upstream_git checkout "${BRANCH}" || exit 1
python updateMojang.py || fail_in
python updateForge.py || fail_in
+python updateNeoForge.py || fail_in
python updateFabric.py || fail_in
python updateQuilt.py || fail_in
python updateLiteloader.py || fail_in
@@ -64,6 +65,7 @@ launcher_git checkout "${BRANCH}" || exit 1
python generateMojang.py || fail_out
python generateForge.py || fail_out
+python generateNeoForge.py || fail_out
python generateFabric.py || fail_out
python generateQuilt.py || fail_out
python generateLiteloader.py || fail_out
diff --git a/updateNeoForge.py b/updateNeoForge.py
new file mode 100644
index 0000000000..d1ffc99ce3
--- /dev/null
+++ b/updateNeoForge.py
@@ -0,0 +1,338 @@
+"""
+ Get the source files necessary for generating Forge versions
+"""
+import copy
+import hashlib
+import json
+import os
+import re
+import sys
+import zipfile
+from contextlib import suppress
+from datetime import datetime
+from pathlib import Path
+from pprint import pprint
+import urllib.parse
+
+from pydantic import ValidationError
+
+from meta.common import upstream_path, ensure_upstream_dir, static_path, default_session
+from meta.common.forge import (
+ JARS_DIR,
+ INSTALLER_INFO_DIR,
+ INSTALLER_MANIFEST_DIR,
+ VERSION_MANIFEST_DIR,
+ FILE_MANIFEST_DIR,
+ BAD_VERSIONS,
+ STATIC_LEGACYINFO_FILE,
+)
+from meta.model.neoforge import (
+ NeoForgeFile,
+ NeoForgeEntry,
+ NeoForgeMCVersionInfo,
+ NeoForgeLegacyInfoList,
+ DerivedNeoForgeIndex,
+ NeoForgeVersion,
+ NeoForgeInstallerProfile,
+ NeoForgeInstallerProfileV2,
+ InstallerInfo,
+ NeoForgeLegacyInfo,
+)
+from meta.model.mojang import MojangVersion
+
+UPSTREAM_DIR = upstream_path()
+STATIC_DIR = static_path()
+
+ensure_upstream_dir(JARS_DIR)
+ensure_upstream_dir(INSTALLER_INFO_DIR)
+ensure_upstream_dir(INSTALLER_MANIFEST_DIR)
+ensure_upstream_dir(VERSION_MANIFEST_DIR)
+ensure_upstream_dir(FILE_MANIFEST_DIR)
+
+LEGACYINFO_PATH = os.path.join(STATIC_DIR, STATIC_LEGACYINFO_FILE)
+
+sess = default_session()
+
+
+def eprint(*args, **kwargs):
+ print(*args, file=sys.stderr, **kwargs)
+
+
+def filehash(filename, hashtype, blocksize=65536):
+ hashtype = hashtype()
+ with open(filename, "rb") as f:
+ for block in iter(lambda: f.read(blocksize), b""):
+ hashtype.update(block)
+ return hashtype.hexdigest()
+
+def find_nth(haystack, needle, n):
+ start = haystack.find(needle)
+ while start >= 0 and n > 1:
+ start = haystack.find(needle, start+len(needle))
+ n -= 1
+ return start
+
+def get_single_forge_files_manifest(longversion):
+ print(f"Getting NeoForge manifest for {longversion}")
+ path_thing = UPSTREAM_DIR + "/neoforge/files_manifests/%s.json" % longversion
+ files_manifest_file = Path(path_thing)
+ from_file = False
+ if files_manifest_file.is_file():
+ with open(path_thing, "r") as f:
+ files_json = json.load(f)
+ from_file = True
+ else:
+ file_url = (
+ "https://maven.neoforged.net/api/maven/details/releases/net%2Fneoforged%2Fforge%2F" + urllib.parse.quote(longversion)
+ )
+ r = sess.get(file_url)
+ r.raise_for_status()
+ files_json = r.json()
+
+ ret_dict = dict()
+
+ for file in files_json.get("files"):
+ assert type(file) == dict
+ name = file["name"]
+ file_name, file_ext = os.path.splitext(name)
+ if file_ext in [".md5", ".sha1", ".sha256", ".sha512"]:
+ continue
+
+ classifier = file["name"][find_nth(name, "-", 3)+1:len(file_name)]
+
+ # assert len(extensionObj.items()) == 1
+ index = 0
+ count = 0
+ file_obj = NeoForgeFile(
+ classifier=classifier, extension=file_ext[1:]
+ )
+ if count == 0:
+ ret_dict[classifier] = file_obj
+ index += 1
+ count += 1
+ else:
+ print(
+ "%s: Multiple objects detected for classifier %s:"
+ % (longversion, classifier)
+ )
+ assert False
+
+
+
+ if not from_file:
+ Path(path_thing).parent.mkdir(parents=True, exist_ok=True)
+ with open(path_thing, "w", encoding="utf-8") as f:
+ json.dump(files_json, f, sort_keys=True, indent=4)
+
+ return ret_dict
+
+
+def main():
+ # get the remote version list fragments
+ r = sess.get(
+ "https://maven.neoforged.net/api/maven/versions/releases/net%2Fneoforged%2Fforge"
+ )
+ r.raise_for_status()
+ main_json = r.json()["versions"]
+ assert type(main_json) == list
+
+ new_index = DerivedNeoForgeIndex()
+
+ version_expression = re.compile(
+ "^(?P<mc>[0-9a-zA-Z_\\.]+)-(?P<ver>[0-9\\.]+\\.(?P<build>[0-9]+))(-(?P<branch>[a-zA-Z0-9\\.]+))?$"
+ )
+
+ print("")
+ print("Processing versions:")
+ for long_version in main_json:
+ assert type(long_version) == str
+ mc_version = long_version.split("-")[0]
+ match = version_expression.match(long_version)
+ if not match:
+ pprint(long_version)
+ assert match
+ assert match.group("mc") == mc_version
+ try:
+ files = get_single_forge_files_manifest(long_version)
+ except:
+ continue
+ build = int(match.group("build"))
+ version = match.group("ver")
+ branch = match.group("branch")
+
+ is_recommended = False
+
+ entry = NeoForgeEntry(
+ long_version=long_version,
+ mc_version=mc_version,
+ version=version,
+ build=build,
+ branch=branch,
+ # NOTE: we add this later after the fact. The forge promotions file lies about these.
+ latest=False,
+ recommended=is_recommended,
+ files=files,
+ )
+ new_index.versions[long_version] = entry
+ if not new_index.by_mc_version:
+ new_index.by_mc_version = dict()
+ if mc_version not in new_index.by_mc_version:
+ new_index.by_mc_version.setdefault(mc_version, NeoForgeMCVersionInfo())
+ new_index.by_mc_version[mc_version].versions.append(long_version)
+ # NOTE: we add this later after the fact. The forge promotions file lies about these.
+ # if entry.latest:
+ # new_index.by_mc_version[mc_version].latest = long_version
+ if entry.recommended:
+ new_index.by_mc_version[mc_version].recommended = long_version
+
+ print("")
+ print("Dumping index files...")
+
+ with open(UPSTREAM_DIR + "/neoforge/maven-metadata.json", "w", encoding="utf-8") as f:
+ json.dump(main_json, f, sort_keys=True, indent=4)
+
+ new_index.write(UPSTREAM_DIR + "/neoforge/derived_index.json")
+
+ legacy_info_list = NeoForgeLegacyInfoList()
+
+ print("Grabbing installers and dumping installer profiles...")
+ # get the installer jars - if needed - and get the installer profiles out of them
+ for key, entry in new_index.versions.items():
+ eprint("Updating NeoForge %s" % key)
+ if entry.mc_version is None:
+ eprint("Skipping %d with invalid MC version" % entry.build)
+ continue
+
+ version = NeoForgeVersion(entry)
+ if version.url() is None:
+ eprint("Skipping %d with no valid files" % version.build)
+ continue
+ if version.long_version in BAD_VERSIONS:
+ eprint(f"Skipping bad version {version.long_version}")
+ continue
+
+ jar_path = os.path.join(UPSTREAM_DIR, JARS_DIR, version.filename())
+
+ if version.uses_installer():
+ installer_info_path = (
+ UPSTREAM_DIR + "/neoforge/installer_info/%s.json" % version.long_version
+ )
+ profile_path = (
+ UPSTREAM_DIR
+ + "/neoforge/installer_manifests/%s.json" % version.long_version
+ )
+ version_file_path = (
+ UPSTREAM_DIR + "/neoforge/version_manifests/%s.json" % version.long_version
+ )
+
+ installer_refresh_required = not os.path.isfile(
+ profile_path
+ ) or not os.path.isfile(installer_info_path)
+
+ if installer_refresh_required:
+ # grab the installer if it's not there
+ if not os.path.isfile(jar_path):
+ eprint("Downloading %s" % version.url())
+ try:
+ rfile = sess.get(version.url(), stream=True)
+ rfile.raise_for_status()
+ Path(jar_path).parent.mkdir(parents=True, exist_ok=True)
+ with open(jar_path, "wb") as f:
+ for chunk in rfile.iter_content(chunk_size=128):
+ f.write(chunk)
+ except Exception as e:
+ eprint("Failed to download %s" % version.url())
+ eprint("Error is %s" % e)
+ continue
+
+ eprint("Processing %s" % version.url())
+ # harvestables from the installer
+ if not os.path.isfile(profile_path):
+ print(jar_path)
+ with zipfile.ZipFile(jar_path) as jar:
+ with suppress(KeyError):
+ with jar.open("version.json") as profile_zip_entry:
+ version_data = profile_zip_entry.read()
+
+ # Process: does it parse?
+ MojangVersion.parse_raw(version_data)
+
+ Path(version_file_path).parent.mkdir(parents=True, exist_ok=True)
+ with open(version_file_path, "wb") as versionJsonFile:
+ versionJsonFile.write(version_data)
+ versionJsonFile.close()
+
+ with jar.open("install_profile.json") as profile_zip_entry:
+ install_profile_data = profile_zip_entry.read()
+
+ # Process: does it parse?
+ is_parsable = False
+ exception = None
+ try:
+ NeoForgeInstallerProfile.parse_raw(install_profile_data)
+ is_parsable = True
+ except ValidationError as err:
+ exception = err
+ try:
+ NeoForgeInstallerProfileV2.parse_raw(install_profile_data)
+ is_parsable = True
+ except ValidationError as err:
+ exception = err
+
+ if not is_parsable:
+ if version.is_supported():
+ raise exception
+ else:
+ eprint(
+ "Version %s is not supported and won't be generated later."
+ % version.long_version
+ )
+
+ Path(profile_path).parent.mkdir(parents=True, exist_ok=True)
+ with open(profile_path, "wb") as profileFile:
+ profileFile.write(install_profile_data)
+ profileFile.close()
+
+ # installer info v1
+ if not os.path.isfile(installer_info_path):
+ installer_info = InstallerInfo()
+ installer_info.sha1hash = filehash(jar_path, hashlib.sha1)
+ installer_info.sha256hash = filehash(jar_path, hashlib.sha256)
+ installer_info.size = os.path.getsize(jar_path)
+ installer_info.write(installer_info_path)
+ else:
+ # ignore the two versions without install manifests and jar mod class files
+ # TODO: fix those versions?
+ if version.mc_version_sane == "1.6.1":
+ continue
+
+ # only gather legacy info if it's missing
+ if not os.path.isfile(LEGACYINFO_PATH):
+ # grab the jar/zip if it's not there
+ if not os.path.isfile(jar_path):
+ rfile = sess.get(version.url(), stream=True)
+ rfile.raise_for_status()
+ with open(jar_path, "wb") as f:
+ for chunk in rfile.iter_content(chunk_size=128):
+ f.write(chunk)
+ # find the latest timestamp in the zip file
+ tstamp = datetime.fromtimestamp(0)
+ with zipfile.ZipFile(jar_path) as jar:
+ for info in jar.infolist():
+ tstamp_new = datetime(*info.date_time)
+ if tstamp_new > tstamp:
+ tstamp = tstamp_new
+ legacy_info = NeoForgeLegacyInfo()
+ legacy_info.release_time = tstamp
+ legacy_info.sha1 = filehash(jar_path, hashlib.sha1)
+ legacy_info.sha256 = filehash(jar_path, hashlib.sha256)
+ legacy_info.size = os.path.getsize(jar_path)
+ legacy_info_list.number[key] = legacy_info
+
+ # only write legacy info if it's missing
+ if not os.path.isfile(LEGACYINFO_PATH):
+ legacy_info_list.write(LEGACYINFO_PATH)
+
+
+if __name__ == "__main__":
+ main()