diff --git a/.dockerignore b/.dockerignore index 42716b7..e56aea5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ .git/ +.idea/ caches/ .idea/ diff --git a/.gitignore b/.gitignore index 63fb57b..a50a8b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.idea/ + public/*/ caches/ diff --git a/generateQuilt.py b/generateQuilt.py new file mode 100755 index 0000000..2794611 --- /dev/null +++ b/generateQuilt.py @@ -0,0 +1,110 @@ +import json +import os + +from meta.common import ensure_component_dir, polymc_path, upstream_path, transform_maven_key +from meta.common.quilt import JARS_DIR, INSTALLER_INFO_DIR, META_DIR, INTERMEDIARY_COMPONENT, LOADER_COMPONENT, \ + USE_QUILT_MAPPINGS +from meta.model import MetaVersion, Dependency, Library, MetaPackage, GradleSpecifier +from meta.model.fabric import FabricJarInfo, FabricInstallerDataV1, FabricMainClasses + +PMC_DIR = polymc_path() +UPSTREAM_DIR = upstream_path() + +ensure_component_dir(LOADER_COMPONENT) +ensure_component_dir(INTERMEDIARY_COMPONENT) + + +def load_jar_info(artifact_key) -> FabricJarInfo: + return FabricJarInfo.parse_file(os.path.join(UPSTREAM_DIR, JARS_DIR, f"{artifact_key}.json")) + + +def load_installer_info(version) -> FabricInstallerDataV1: + return FabricInstallerDataV1.parse_file(os.path.join(UPSTREAM_DIR, INSTALLER_INFO_DIR, f"{version}.json")) + + +def process_loader_version(entry) -> MetaVersion: + jar_info = load_jar_info(transform_maven_key(entry["maven"])) + installer_info = load_installer_info(entry["version"]) + + v = MetaVersion(name="Quilt Loader", uid=LOADER_COMPONENT, version=entry["version"]) + v.release_time = jar_info.release_time + v.requires = [Dependency(uid=INTERMEDIARY_COMPONENT)] + v.order = 10 + v.type = "release" + if isinstance(installer_info.main_class, FabricMainClasses): + v.main_class = installer_info.main_class.client + else: + v.main_class = installer_info.main_class + v.libraries = [] + v.libraries.extend(installer_info.libraries.common) + v.libraries.extend(installer_info.libraries.client) + loader_lib = Library(name=GradleSpecifier.from_string(entry["maven"]), + url="https://maven.quiltmc.org/repository/release") + v.libraries.append(loader_lib) + return v + + +def process_intermediary_version(entry) -> MetaVersion: + jar_info = load_jar_info(transform_maven_key(entry["maven"])) + + v = MetaVersion(name="Quilt Intermediary Mappings", uid=INTERMEDIARY_COMPONENT, version=entry["version"]) + v.release_time = jar_info.release_time + v.requires = [Dependency(uid='net.minecraft', equals=entry["version"])] + v.order = 11 + v.type = "release" + v.libraries = [] + v.volatile = True + intermediary_lib = Library(name=GradleSpecifier.from_string(entry["maven"]), + url="https://maven.quiltmc.org/repository/release") + v.libraries.append(intermediary_lib) + return v + + +def main(): + recommended_loader_versions = [] + recommended_intermediary_versions = [] + + with open(os.path.join(UPSTREAM_DIR, META_DIR, "loader.json"), 'r', encoding='utf-8') as f: + loader_version_index = json.load(f) + for entry in loader_version_index: + version = entry["version"] + print(f"Processing loader {version}") + + v = process_loader_version(entry) + + if not recommended_loader_versions: # first (newest) loader is recommended + recommended_loader_versions.append(version) + + v.write(os.path.join(PMC_DIR, LOADER_COMPONENT, f"{v.version}.json")) + + if USE_QUILT_MAPPINGS: + with open(os.path.join(UPSTREAM_DIR, META_DIR, "hashed.json"), 'r', encoding='utf-8') as f: + intermediary_version_index = json.load(f) + for entry in intermediary_version_index: + version = entry["version"] + print(f"Processing intermediary {version}") + + v = process_intermediary_version(entry) + + recommended_intermediary_versions.append(version) # all intermediaries are recommended + + v.write(os.path.join(PMC_DIR, INTERMEDIARY_COMPONENT, f"{v.version}.json")) + + package = MetaPackage(uid=LOADER_COMPONENT, name='Quilt Loader') + package.recommended = recommended_loader_versions + package.description = "The Quilt project is an open, community-driven modding toolchain designed primarily for Minecraft." + package.project_url = "https://quiltmc.org/" + package.authors = ["Quilt Project"] + package.write(os.path.join(PMC_DIR, LOADER_COMPONENT, "package.json")) + + if USE_QUILT_MAPPINGS: + package = MetaPackage(uid=INTERMEDIARY_COMPONENT, name='Quilt Intermediary Mappings') + package.recommended = recommended_intermediary_versions + package.description = "Intermediary mappings allow using Quilt Loader with mods for Minecraft in a more compatible manner." + package.project_url = "https://quiltmc.org/" + package.authors = ["Quilt Project"] + package.write(os.path.join(PMC_DIR, INTERMEDIARY_COMPONENT, "package.json")) + + +if __name__ == '__main__': + main() diff --git a/meta/common/quilt.py b/meta/common/quilt.py new file mode 100644 index 0000000..6d5f479 --- /dev/null +++ b/meta/common/quilt.py @@ -0,0 +1,17 @@ +from os.path import join +from .fabric import INTERMEDIARY_COMPONENT as FABRIC_INTERMEDIARY_COMPONENT + +# Right now Quilt recommends using Fabric's intermediary +USE_QUILT_MAPPINGS = False + +BASE_DIR = "quilt" + +JARS_DIR = join(BASE_DIR, "jars") +INSTALLER_INFO_DIR = join(BASE_DIR, "loader-installer-json") +META_DIR = join(BASE_DIR, "meta-v3") + +LOADER_COMPONENT = "org.quiltmc.quilt-loader" +INTERMEDIARY_COMPONENT = "org.quiltmc.hashed" + +if not USE_QUILT_MAPPINGS: + INTERMEDIARY_COMPONENT = FABRIC_INTERMEDIARY_COMPONENT diff --git a/update.sh b/update.sh index 4547684..5f79d88 100755 --- a/update.sh +++ b/update.sh @@ -44,6 +44,7 @@ upstream_git checkout "${BRANCH}" || exit 1 python updateMojang.py || fail_in python updateForge.py || fail_in python updateFabric.py || fail_in +python updateQuilt.py || fail_in python updateLiteloader.py || fail_in if [ "${DEPLOY_TO_GIT}" = true ] ; then @@ -63,6 +64,7 @@ polymc_git checkout "${BRANCH}" || exit 1 python generateMojang.py || fail_out python generateForge.py || fail_out python generateFabric.py || fail_out +python generateQuilt.py || fail_out python generateLiteloader.py || fail_out python index.py || fail_out diff --git a/updateQuilt.py b/updateQuilt.py new file mode 100755 index 0000000..55bb63f --- /dev/null +++ b/updateQuilt.py @@ -0,0 +1,122 @@ +import hashlib +import json +import os +import zipfile +from datetime import datetime + +import requests +from cachecontrol import CacheControl +from cachecontrol.caches import FileCache + +from meta.common import upstream_path, ensure_upstream_dir, transform_maven_key +from meta.common.quilt import JARS_DIR, INSTALLER_INFO_DIR, META_DIR +from meta.common.fabric import DATETIME_FORMAT_HTTP +from meta.model.fabric import FabricJarInfo + +UPSTREAM_DIR = upstream_path() + +ensure_upstream_dir(JARS_DIR) +ensure_upstream_dir(INSTALLER_INFO_DIR) +ensure_upstream_dir(META_DIR) + +forever_cache = FileCache('caches/http_cache', forever=True) +sess = CacheControl(requests.Session(), forever_cache) + + +def filehash(filename, hashtype, blocksize=65536): + h = hashtype() + with open(filename, "rb") as f: + for block in iter(lambda: f.read(blocksize), b""): + h.update(block) + return h.hexdigest() + + +def get_maven_url(maven_key, server, ext): + parts = maven_key.split(":", 3) + maven_ver_url = server + parts[0].replace(".", "/") + "/" + parts[1] + "/" + parts[2] + "/" + maven_url = maven_ver_url + parts[1] + "-" + parts[2] + ext + return maven_url + + +def get_json_file(path, url): + with open(path, 'w', encoding='utf-8') as f: + r = sess.get(url) + r.raise_for_status() + version_json = r.json() + json.dump(version_json, f, sort_keys=True, indent=4) + return version_json + + +def get_plaintext(url): + r = sess.get(url) + r.raise_for_status() + return r.text + + +def head_file(url): + r = sess.head(url) + r.raise_for_status() + return r.headers + + +def get_binary_file(path, url): + with open(path, 'wb') as f: + r = sess.get(url) + r.raise_for_status() + for chunk in r.iter_content(chunk_size=128): + f.write(chunk) + + +def compute_jar_file(path, url): + # These two approaches should result in the same metadata, except for the timestamp which might be a few minutes + # off for the fallback method + try: + # Let's not download a Jar file if we don't need to. + headers = head_file(url) + tstamp = datetime.strptime(headers["Last-Modified"], DATETIME_FORMAT_HTTP) + sha1 = get_plaintext(url + ".sha1") + sha256 = get_plaintext(url + ".sha256") + size = int(headers["Content-Length"]) + except requests.HTTPError: + # Some older versions don't have a .sha256 file :( + print(f"Falling back to downloading jar for {url}") + + jar_path = path + ".jar" + get_binary_file(jar_path, url) + tstamp = datetime.fromtimestamp(0) + with zipfile.ZipFile(jar_path) as jar: + allinfo = jar.infolist() + for info in allinfo: + tstamp_new = datetime(*info.date_time) + if tstamp_new > tstamp: + tstamp = tstamp_new + + sha1 = filehash(jar_path, hashlib.sha1) + sha256 = filehash(jar_path, hashlib.sha256) + size = os.path.getsize(jar_path) + + data = FabricJarInfo(release_time=tstamp, sha1=sha1, sha256=sha256, size=size) + data.write(path + ".json") + + +def main(): + # get the version list for each component we are interested in + for component in ["hashed", "loader"]: + index = get_json_file(os.path.join(UPSTREAM_DIR, META_DIR, f"{component}.json"), + "https://meta.quiltmc.org/v3/versions/" + component) + for it in index: + print(f"Processing {component} {it['version']} ") + jar_maven_url = get_maven_url(it["maven"], "https://maven.quiltmc.org/repository/release/", ".jar") + compute_jar_file(os.path.join(UPSTREAM_DIR, JARS_DIR, transform_maven_key(it["maven"])), jar_maven_url) + + # for each loader, download installer JSON file from maven + with open(os.path.join(UPSTREAM_DIR, META_DIR, "loader.json"), 'r', encoding='utf-8') as loaderVersionIndexFile: + loader_version_index = json.load(loaderVersionIndexFile) + for it in loader_version_index: + print(f"Downloading installer info for loader {it['version']} ") + maven_url = get_maven_url(it["maven"], "https://maven.quiltmc.org/repository/release/", ".json") + get_json_file(os.path.join(UPSTREAM_DIR, INSTALLER_INFO_DIR, f"{it['version']}.json"), maven_url) + + +if __name__ == '__main__': + main()