meta-unmojang/meta/run/generate_forge.py
Sefa Eyeoglu be4317cf9d
refactor!: package using Nix
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2024-04-24 16:56:23 +02:00

473 lines
16 KiB
Python
Executable File

import os
import re
import sys
from packaging import version as pversion
from operator import attrgetter
from typing import Collection
from meta.common import ensure_component_dir, launcher_path, upstream_path
from meta.common.forge import (
FORGE_COMPONENT,
INSTALLER_MANIFEST_DIR,
VERSION_MANIFEST_DIR,
DERIVED_INDEX_FILE,
LEGACYINFO_FILE,
INSTALLER_INFO_DIR,
BAD_VERSIONS,
FORGEWRAPPER_LIBRARY,
)
from meta.common.mojang import MINECRAFT_COMPONENT
from meta.model import (
MetaVersion,
Dependency,
Library,
GradleSpecifier,
MojangLibraryDownloads,
MojangArtifact,
MetaPackage,
)
from meta.model.forge import (
ForgeVersion,
ForgeInstallerProfile,
ForgeLegacyInfo,
fml_libs_for_version,
ForgeInstallerProfileV2,
InstallerInfo,
DerivedForgeIndex,
ForgeLegacyInfoList,
)
from meta.model.mojang import MojangVersion
LAUNCHER_DIR = launcher_path()
UPSTREAM_DIR = upstream_path()
ensure_component_dir(FORGE_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 pversion.parse(ver.version) > pversion.parse(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: ForgeInstallerProfile, version: ForgeVersion
) -> MetaVersion:
v = MetaVersion(name="Forge", version=version.rawVersion, uid=FORGE_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: ForgeVersion
) -> MetaVersion:
v = MetaVersion(name="Forge", version=version.rawVersion, uid=FORGE_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 forge_lib in installer.libraries:
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: ForgeLegacyInfo, version: ForgeVersion) -> MetaVersion:
v = MetaVersion(name="Forge", version=version.rawVersion, uid=FORGE_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: ForgeInstallerProfileV2, version: ForgeVersion
) -> MetaVersion:
v = MetaVersion(name="Forge", version=version.rawVersion, uid=FORGE_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.minecraftforge", "forge", version.long_version, "installer"
)
)
installer_lib.downloads = MojangLibraryDownloads()
installer_lib.downloads.artifact = MojangArtifact(
url="https://maven.minecraftforge.net/%s" % (installer_lib.name.path()),
sha1=info.sha1hash,
size=info.size,
)
v.maven_files.append(installer_lib)
for forge_lib in profile.libraries:
if forge_lib.name.is_log4j():
continue
if (
forge_lib.name.group == "net.minecraftforge"
and forge_lib.name.artifact == "forge"
and forge_lib.name.classifier == "universal"
):
forge_lib.downloads.artifact.url = (
"https://maven.minecraftforge.net/%s" % forge_lib.name.path()
)
v.maven_files.append(forge_lib)
v.libraries = []
v.libraries.append(FORGEWRAPPER_LIBRARY)
for forge_lib in installer.libraries:
if forge_lib.name.is_log4j():
continue
if forge_lib.name.group == "net.minecraftforge":
if forge_lib.name.artifact == "forge" and not forge_lib.name.classifier:
forge_lib.name.classifier = "launcher"
forge_lib.downloads.artifact.path = forge_lib.name.path()
forge_lib.downloads.artifact.url = (
"https://maven.minecraftforge.net/%s" % forge_lib.name.path()
)
forge_lib.name = forge_lib.name
# net.minecraftforge.forge:client doesn't exist??? (49.0.x)
if not len(forge_lib.downloads.artifact.url):
continue
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}"
if "--fml.forgeGroup" not in installer.arguments.game:
mc_args += f" --fml.forgeGroup net.minecraftforge"
if "--fml.forgeVersion" not in installer.arguments.game:
mc_args += f" --fml.forgeVersion {version.rawVersion}"
if "--fml.mcVersion" not in installer.arguments.game:
mc_args += f" --fml.mcVersion {version.mc_version}"
v.minecraft_arguments = mc_args
return v
def main():
# load the locally cached version list
remote_versions = DerivedForgeIndex.parse_file(
os.path.join(UPSTREAM_DIR, DERIVED_INDEX_FILE)
)
recommended_versions = []
legacy_info_list = ForgeLegacyInfoList.parse_file(
os.path.join(UPSTREAM_DIR, LEGACYINFO_FILE)
)
legacy_versions = [
"1.1",
"1.2.3",
"1.2.4",
"1.2.5",
"1.3.2",
"1.4.1",
"1.4.2",
"1.4.3",
"1.4.4",
"1.4.5",
"1.4.6",
"1.4.7",
"1.5",
"1.5.1",
"1.5.2",
"1.6.1",
"1.6.2",
"1.6.3",
"1.6.4",
"1.7.10",
"1.7.10-pre4",
"1.7.2",
"1.8",
"1.8.8",
"1.8.9",
"1.9",
"1.9.4",
"1.10",
"1.10.2",
"1.11",
"1.11.2",
"1.12",
"1.12.1",
"1.12.2",
]
for key, entry in remote_versions.versions.items():
if entry.mc_version is None:
eprint("Skipping %s with invalid MC version" % key)
continue
version = ForgeVersion(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)
if entry.mc_version in legacy_versions:
v = version_from_modernized_installer(installer, version)
else:
profile = ForgeInstallerProfileV2.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 = ForgeInstallerProfile.parse_file(profile_filepath)
v = version_from_profile(profile, version)
else:
# Generate json for legacy here
if version.mc_version_sane == "1.6.1":
continue
build = version.build
if (
str(build).encode("utf-8").decode("utf8")
not in legacy_info_list.number
):
eprint(
"Legacy build %d is missing in legacy info. Ignoring." % build
)
continue
v = version_from_legacy(legacy_info_list.number[str(build)], 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=FORGE_COMPONENT,
name="Forge",
project_url="https://www.minecraftforge.net/forum/",
)
package.recommended = recommended_versions
package.write(os.path.join(LAUNCHER_DIR, FORGE_COMPONENT, "package.json"))
if __name__ == "__main__":
main()