From fb84e0168c2f8be9f33af87547e961e4e8944828 Mon Sep 17 00:00:00 2001 From: Trumeet Date: Mon, 27 Dec 2021 15:24:28 -0800 Subject: Initial Fabric support --- gen.c | 337 +++++++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 240 insertions(+), 97 deletions(-) (limited to 'gen.c') diff --git a/gen.c b/gen.c index 48f4141..7ea6b74 100644 --- a/gen.c +++ b/gen.c @@ -18,6 +18,7 @@ #include #include #include +#include enum source_type { jar, @@ -25,7 +26,8 @@ enum source_type { extract_jar, log4j, asset, - asset_index + asset_index, + fabric }; struct source { @@ -42,12 +44,15 @@ static char *out_pkgbuild = "PKGBUILD.gen"; static FILE *pkgbuild = NULL; static char *out_launcher = "launcher.gen"; static FILE *launcher = NULL; +static char *out_fabric_launcher = "launcher.fabric.gen"; static char *version = NULL; static CURL *curl = NULL; static json_object *json = NULL; static json_tokener *tok = NULL; static char *all_version_manifest_url = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"; static char *version_manifest_url = NULL; +static char *all_fabric_manifest_url = "https://meta.fabricmc.net/v2/versions/loader"; +static char *fabric_manifest_url = NULL; static char *assets_url = NULL; static void cleanup(void) { @@ -71,9 +76,10 @@ static void cleanup(void) { free(s); s = s1; } + if (version) free(version); } -static void get(const char *url) { +static char get(const char *url, const char fail_not_found) { /* The url may belong to the json. Use it first. */ curl_easy_setopt(curl, CURLOPT_URL, url); if (json) { @@ -86,6 +92,9 @@ static void get(const char *url) { exit(1); } if (res) { + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (!fail_not_found && (http_code == 400 || http_code == 404)) return 1; cleanup(); errx(res, "Cannot GET %s: %s", url, @@ -97,6 +106,7 @@ static void get(const char *url) { errx(1, "Cannot parse response JSON: " "The stream ends without a full JSON."); } + return 0; } static FILE *try_fopen(const char *path) { @@ -133,7 +143,7 @@ static void src(const char *hash, const char *url, const enum source_type type) errx(1, "URL %s is not valid.", url); } /* Remove leading / */ - id ++; + id++; struct source *s = malloc(sizeof(struct source)); if (!s) { cleanup(); @@ -171,26 +181,21 @@ static void src(const char *hash, const char *url, const enum source_type type) source_fist = s; } -static char classpath_set = 0; -static char libraries_set = 0; - static void append(FILE *stream, const char *key, const char *value) { - if (!classpath_set && - !strcmp("JVM_ARGS", key) && - !strcmp("-cp", value)) { - classpath_set = 1; - } - if (!libraries_set && - !strcmp("JVM_ARGS", key) && - !strncmp("-Djava.library.path", value, 19)) { - libraries_set = 1; - } fprintf(stream, "%s=\"$%s %s\"\n", key, key, value); } +static void arg(const char jvm, const char *value) { + if (strchr(value, ' ')) { + fprintf(stderr, "Warn: Refusing argument with space: %s\n", value); + return; + } + append(launcher, jvm ? (jvm == 2 ? "JVM_ARGS_X86" : "JVM_ARGS") : "MC_ARGS", value); +} + static void set(FILE *stream, const char *key, const char *value) { fprintf(stream, "%s=\"%s\"\n", key, @@ -215,7 +220,7 @@ static void parse_artifact(json_object *artifact, const enum source_type type) { static char check_rules(json_object *rules) { char allow = 0; unsigned int rules_len = json_object_array_length(rules); - for (unsigned int j = 0; j < rules_len; j ++) { + for (unsigned int j = 0; j < rules_len; j++) { const json_object *rule = json_object_array_get_idx(rules, j); json_object *obj2; if (json_object_object_get_ex(rule, "os", &obj2)) { @@ -225,7 +230,7 @@ static char check_rules(json_object *rules) { continue; } if (json_object_object_get_ex(obj2, "arch", &obj3) && - !strcmp("x86", json_object_get_string(obj3))) { + !strcmp("x86", json_object_get_string(obj3))) { allow = 2; break; } @@ -246,7 +251,7 @@ static char check_rules(json_object *rules) { } static const char *fetch_version_manifest(void) { - get(all_version_manifest_url); + get(all_version_manifest_url, 1); json_object *obj; if (!version) { if (!json_object_object_get_ex(json, "latest", &obj)) { @@ -257,14 +262,21 @@ static const char *fetch_version_manifest(void) { cleanup(); errx(1, "Invalid version_manifest_v2.json: No release object."); } - version = (char *) json_object_get_string(obj); + const char *ver = json_object_get_string(obj); + unsigned int len = strlen(ver); + version = calloc(len + 1, sizeof(char)); + if (!version) { + cleanup(); + err(errno, "Cannot allocate memory"); + } + memcpy(version, ver, len + 1); } if (!json_object_object_get_ex(json, "versions", &obj)) { cleanup(); errx(1, "Invalid version_manifest_v2.json: No versions[] object."); } unsigned int len = json_object_array_length(obj); - for (unsigned int i = 0; i < len; i ++) { + for (unsigned int i = 0; i < len; i++) { const json_object *item = json_object_array_get_idx(obj, i); json_object *obj1; if (!json_object_object_get_ex(item, "id", &obj1)) { @@ -282,6 +294,27 @@ static const char *fetch_version_manifest(void) { errx(1, "Version %s is not found.", version); } +static const char *fetch_fabric_manifest(void) { + get(all_fabric_manifest_url, 1); + unsigned int len = json_object_array_length(json); + for (unsigned int i = 0; i < len; i++) { + const json_object *item = json_object_array_get_idx(json, i); + json_object *obj1; + if (!json_object_object_get_ex(item, "stable", &obj1)) { + cleanup(); + errx(1, "Invalid fabric version json: no stable."); + } + if (!json_object_get_boolean(obj1)) continue; + if (!json_object_object_get_ex(item, "version", &obj1)) { + cleanup(); + errx(1, "Fabric loader doesn't have the version object."); + } + return json_object_get_string(obj1); + } + cleanup(); + errx(1, "No stable Fabric versions found."); +} + static void parse_arguments(struct json_object *obj) { json_object *args; if (!json_object_object_get_ex(obj, "game", &args)) { @@ -289,10 +322,10 @@ static void parse_arguments(struct json_object *obj) { errx(1, "Invalid version.json: No game[] object."); } unsigned int args_len = json_object_array_length(args); - for (unsigned int i = 0; i < args_len; i ++) { + for (unsigned int i = 0; i < args_len; i++) { json_object *item = json_object_array_get_idx(args, i); if (json_object_is_type(item, json_type_string)) { - append(launcher, "MC_ARGS", json_object_get_string(item)); + arg(0, json_object_get_string(item)); continue; } if (!json_object_is_type(item, json_type_object)) { @@ -311,7 +344,7 @@ static void parse_arguments(struct json_object *obj) { } else if (json_object_is_type(val, json_type_array)) { fprintf(stderr, "Skipped unsupported game argument: "); unsigned int len = json_object_array_length(val); - for (unsigned int j = 0; j < len; j ++) { + for (unsigned int j = 0; j < len; j++) { fprintf(stderr, "%s ", json_object_get_string( json_object_array_get_idx(val, j) @@ -325,10 +358,10 @@ static void parse_arguments(struct json_object *obj) { errx(1, "Invalid version.json: No jvm[] object."); } args_len = json_object_array_length(args); - for (unsigned int i = 0; i < args_len; i ++) { + for (unsigned int i = 0; i < args_len; i++) { json_object *item = json_object_array_get_idx(args, i); if (json_object_is_type(item, json_type_string)) { - append(launcher, "JVM_ARGS", json_object_get_string(item)); + arg(1, json_object_get_string(item)); continue; } if (!json_object_is_type(item, json_type_object)) { @@ -350,10 +383,10 @@ static void parse_arguments(struct json_object *obj) { if (!rule) continue; if (rule == 2) { if (json_object_is_type(value, json_type_string)) { - append(launcher, "JVM_ARGS_X86", json_object_get_string(value)); + arg(2, json_object_get_string(value)); } else { - for (unsigned int j = 0; j < value_len; j ++) { - append(launcher, "JVM_ARGS_X86", + for (unsigned int j = 0; j < value_len; j++) { + arg(2, json_object_get_string( json_object_array_get_idx(value, j) )); @@ -361,10 +394,10 @@ static void parse_arguments(struct json_object *obj) { } } else { if (json_object_is_type(value, json_type_string)) { - append(launcher, "JVM_ARGS", json_object_get_string(value)); + arg(1, json_object_get_string(value)); } else { - for (unsigned int j = 0; j < value_len; j ++) { - append(launcher, "JVM_ARGS", + for (unsigned int j = 0; j < value_len; j++) { + arg(1, json_object_get_string( json_object_array_get_idx(value, j) )); @@ -375,21 +408,93 @@ static void parse_arguments(struct json_object *obj) { } } -static void fetch_version(const char *url) { +static char *parse_maven(const char *name, const char *maven_repo) { + /* Download from Maven instead */ + unsigned short seg = 0; + char *name_cpy = calloc(strlen(name) + 1, sizeof(char)); + if (!name_cpy) { + cleanup(); + err(errno, "Cannot allocate memory"); + } + memcpy(name_cpy, name, strlen(name) + 1); + const unsigned int repo_len = strlen(maven_repo); + unsigned int artifact_len = 0; + const unsigned int len = repo_len + 1 /* / */ + + 2 * strlen(name) + 1 /* / before jar name */ + 4 /* .jar */ + 1; + char *url_jar = calloc(len, sizeof(char)); + if (!url_jar) { + cleanup(); + err(errno, "Cannot allocate memory"); + } + unsigned int url_jar_len = 0; + unsigned int artifact_start = 0; + memcpy(url_jar, maven_repo, repo_len); + url_jar_len += repo_len; + url_jar[url_jar_len ++] = '/'; + /* Highly inefficient code. */ + for (char *p = strtok(name_cpy, ":"); p != NULL; p = strtok(NULL, ":")) { + switch (seg ++) { + case 0: { + unsigned int z = 0; + char c; + while ((c = p[z ++]) != '\0') { + if (c == '.') url_jar[url_jar_len ++] = '/'; + else url_jar[url_jar_len ++] = c; + } + url_jar[url_jar_len ++] = '/'; + break; + } + case 1: + artifact_len = strlen(p); + artifact_start = url_jar_len; + memcpy(&url_jar[url_jar_len], p, artifact_len); + url_jar_len += artifact_len; + url_jar[url_jar_len ++] = '/'; + break; + case 2: { + unsigned int version_len = strlen(p); + memcpy(&url_jar[url_jar_len], p, version_len); + url_jar_len += version_len; + url_jar[url_jar_len ++] = '/'; + memcpy(&url_jar[url_jar_len], &url_jar[artifact_start], artifact_len); + url_jar_len += artifact_len; + url_jar[url_jar_len ++] = '-'; + memcpy(&url_jar[url_jar_len], p, version_len); + url_jar_len += version_len; + memcpy(&url_jar[url_jar_len], ".jar\0", 5); + url_jar_len += 5; + break; + } + default: + free(url_jar); + free(name_cpy); + errx(1, "Illegal maven name: %s", name); + } + } + free(name_cpy); + return url_jar; +} + +static void fetch_version(const char *url, const char is_fabric) { /* URL is no longer valid. */ - get(url); + if (get(url, is_fabric ? 0 : 1)) { + fprintf(stderr, "Warn: No Fabric available for this version.\n"); + return; + } json_object *obj; if (!json_object_object_get_ex(json, "mainClass", &obj)) { cleanup(); errx(1, "Invalid version.json: No mainClass object."); } set(launcher, "MAIN_CLASS", json_object_get_string(obj)); - if (!json_object_object_get_ex(json, "id", &obj)) { - cleanup(); - errx(1, "Invalid version.json: No id object."); + if (!is_fabric) { + if (!json_object_object_get_ex(json, "id", &obj)) { + cleanup(); + errx(1, "Invalid version.json: No id object."); + } + set(launcher, "ID", json_object_get_string(obj)); + set(pkgbuild, "_MC_ID", json_object_get_string(obj)); } - set(launcher, "ID", json_object_get_string(obj)); - set(pkgbuild, "_MC_ID", json_object_get_string(obj)); if (json_object_object_get_ex(json, "javaVersion", &obj)) { if (!json_object_object_get_ex(obj, "majorVersion", &obj)) { cleanup(); @@ -403,15 +508,25 @@ static void fetch_version(const char *url) { errx(1, "Invalid version.json: No libraries[] object."); } unsigned int libs_len = json_object_array_length(obj); - for (unsigned int i = 0; i < libs_len; i ++) { + for (unsigned int i = 0; i < libs_len; i++) { const json_object *item = json_object_array_get_idx(obj, i); json_object *obj1; if (json_object_object_get_ex(item, "rules", &obj1)) { if (!check_rules(obj1)) continue; } if (!json_object_object_get_ex(item, "downloads", &obj1)) { - cleanup(); - errx(1, "Library doesn't have downloads object."); + json_object *maven; + if (!json_object_object_get_ex(item, "name", &obj1) || + !json_object_object_get_ex(item, "url", &maven)) { + cleanup(); + errx(1, "Library doesn't have downloads object."); + } + const char *name = json_object_get_string(obj1); + const char *maven_repo = json_object_get_string(maven); + char *maven_url = parse_maven(name, maven_repo); + src("SKIP", maven_url, is_fabric ? fabric : jar); + free(maven_url); + continue; } json_object *artifact; json_object *natives; @@ -420,7 +535,7 @@ static void fetch_version(const char *url) { * It seems that if they both exist, there must be a standalone artifact before it. */ if (json_object_object_get_ex(item, "natives", &natives) && - json_object_object_get_ex(obj1, "classifiers", &classifiers)) { + json_object_object_get_ex(obj1, "classifiers", &classifiers)) { if (!json_object_object_get_ex(natives, "linux", &obj1)) continue; if (!json_object_object_get_ex(classifiers, @@ -430,50 +545,53 @@ static void fetch_version(const char *url) { parse_artifact(obj1, extract_jar); } else if (json_object_object_get_ex(obj1, "artifact", &artifact)) { parse_artifact(artifact, - json_object_object_get_ex(item, "extract", &obj1) ? extract_jar - : jar); + json_object_object_get_ex(item, "extract", &obj1) ? extract_jar + : jar); } else { cleanup(); errx(1, "Library doesn't have an artifact or natives and classifiers object."); } } - if (!json_object_object_get_ex(json, "downloads", &obj)) { - cleanup(); - errx(1, "Invalid version.json: No downloads object."); - } - if (!json_object_object_get_ex(obj, "client", &obj)) { - cleanup(); - errx(1, "Invalid version.json: No client object."); - } - parse_artifact(obj, client); - if (!json_object_object_get_ex(json, "assetIndex", &obj)) { - cleanup(); - errx(1, "Invalid version.json: No assetIndex object."); - } - json_object *obj1; - if (!json_object_object_get_ex(obj, "url", &obj1)) { - cleanup(); - errx(1, "Invalid version.json: assetIndex does not have the url object."); - } - assets_url = (char *) json_object_get_string(obj1); - if (!json_object_object_get_ex(obj, "sha1", &obj1)) { - cleanup(); - errx(1, "Invalid version.json: assetIndex does not have the sha1 object."); - } - src(json_object_get_string(obj1), assets_url, asset_index); - if (!json_object_object_get_ex(obj, "id", &obj1)) { - cleanup(); - errx(1, "Invalid version.json: assetIndex does not have the id object."); + if (!is_fabric) { + if (!json_object_object_get_ex(json, "downloads", &obj)) { + cleanup(); + errx(1, "Invalid version.json: No downloads object."); + } + if (!json_object_object_get_ex(obj, "client", &obj)) { + cleanup(); + errx(1, "Invalid version.json: No client object."); + } + parse_artifact(obj, client); + + if (!json_object_object_get_ex(json, "assetIndex", &obj)) { + cleanup(); + errx(1, "Invalid version.json: No assetIndex object."); + } + json_object *obj1; + if (!json_object_object_get_ex(obj, "url", &obj1)) { + cleanup(); + errx(1, "Invalid version.json: assetIndex does not have the url object."); + } + assets_url = (char *) json_object_get_string(obj1); + if (!json_object_object_get_ex(obj, "sha1", &obj1)) { + cleanup(); + errx(1, "Invalid version.json: assetIndex does not have the sha1 object."); + } + src(json_object_get_string(obj1), assets_url, asset_index); + if (!json_object_object_get_ex(obj, "id", &obj1)) { + cleanup(); + errx(1, "Invalid version.json: assetIndex does not have the id object."); + } + set(pkgbuild, "_ASSET_ID", json_object_get_string(obj1)); + set(launcher, "ASSET_ID", json_object_get_string(obj1)); + set(launcher, "assets_index_name", json_object_get_string(obj1)); } - set(pkgbuild, "_ASSET_ID", json_object_get_string(obj1)); - set(launcher, "ASSET_ID", json_object_get_string(obj1)); - set(launcher, "assets_index_name", json_object_get_string(obj1)); if (json_object_object_get_ex(json, "arguments", &obj)) { parse_arguments(obj); } else if (json_object_object_get_ex(json, "minecraftArguments", &obj)) { - append(launcher, "MC_ARGS", json_object_get_string(obj)); + arg(0, json_object_get_string(obj)); } else { cleanup(); errx(1, "Invalid version.json: No arguments or minecraftArguments object."); @@ -481,12 +599,12 @@ static void fetch_version(const char *url) { if (json_object_object_get_ex(json, "logging", &obj)) { if (json_object_object_get_ex(obj, "client", &obj)) { json_object *file; - json_object *arg; + json_object *a; if (!json_object_object_get_ex(obj, "file", &file)) { cleanup(); errx(1, "Logging doesn't have the file object."); } - if (!json_object_object_get_ex(obj, "argument", &arg)) { + if (!json_object_object_get_ex(obj, "argument", &a)) { cleanup(); errx(1, "Logging doesn't have the argument object."); } @@ -500,19 +618,19 @@ static void fetch_version(const char *url) { parse_artifact(file, log4j); /* Hope Mojang won't change this. */ if (strcmp("-Dlog4j.configurationFile=${path}", - json_object_get_string(arg)) != 0) { + json_object_get_string(a)) != 0) { fprintf(stderr, "Unsupported Log4J Configuration: %s", - (char *) json_object_get_string(arg)); + (char *) json_object_get_string(a)); } else { - append(launcher, "JVM_ARGS", "-Dlog4j.configurationFile=LOG4J_XML_PATH"); + arg(1, "-Dlog4j.configurationFile=LOG4J_XML_PATH"); } } } } static void fetch_assets(void) { - get(assets_url); + get(assets_url, 1); json_object *obj; if (!json_object_object_get_ex(json, "objects", &obj)) { cleanup(); @@ -570,7 +688,7 @@ static void out(const char *tag, enum source_type type) { int main(int argc, char **argv) { int opt; - while ((opt = getopt(argc, argv, "o:O:v:m:M:")) != -1) { + while ((opt = getopt(argc, argv, "o:O:v:m:M:f:F:g:")) != -1) { switch (opt) { case 'o': out_pkgbuild = optarg; @@ -578,20 +696,37 @@ int main(int argc, char **argv) { case 'O': out_launcher = optarg; break; - case 'v': - version = optarg; + case 'v': { + unsigned int len = strlen(optarg); + version = calloc(len + 1, sizeof(char)); + if (!version) { + cleanup(); + err(errno, "Cannot allocate memory"); + } + memcpy(version, optarg, len + 1); break; + } case 'm': version_manifest_url = optarg; break; case 'M': all_version_manifest_url = optarg; break; + case 'f': + fabric_manifest_url = optarg; + break; + case 'F': + all_fabric_manifest_url = optarg; + break; + case 'g': + out_fabric_launcher = optarg; + break; default: - errx(EX_USAGE, "-o %s -O %s -v %s", + errx(EX_USAGE, "-o %s -O %s -v %s -g %s", out_pkgbuild, out_launcher, - version); + version, + out_fabric_launcher); } } pkgbuild = try_fopen(out_pkgbuild); @@ -607,29 +742,37 @@ int main(int argc, char **argv) { cleanup(); errx(1, "Cannot initialize libcurl."); } + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); tok = json_tokener_new(); if (!tok) { cleanup(); errx(1, "Cannot initialize JSON tok."); } - fetch_version(version_manifest_url == NULL ? fetch_version_manifest() : version_manifest_url); + fetch_version(version_manifest_url == NULL ? fetch_version_manifest() : version_manifest_url, 0); if (assets_url) fetch_assets(); - out("CLIENT", client); out("JAR", jar); out("EXTRACT_JAR", extract_jar); out("ASSET", asset); out("LOG4J", log4j); out("ASSET_INDEX", asset_index); - if (!classpath_set) { - /* Just for compatibility for old versions. */ - append(launcher, "JVM_ARGS", "-cp ${classpath}"); - } - if (!libraries_set) { - /* Just for compatibility for old versions. */ - append(launcher, "JVM_ARGS", "-Djava.library.path=${natives_directory}"); - } + fclose(launcher); + + launcher = try_fopen(out_fabric_launcher); + char fabric_built_url[256]; + if (!fabric_manifest_url) { + const char *ver = fetch_fabric_manifest(); + set(pkgbuild, "_FABRIC", ver); + set(launcher, "FABRIC", ver); + sprintf(fabric_built_url, + "https://meta.fabricmc.net/v2/versions/loader/%s/%s/profile/json", + version, + ver); + fabric_manifest_url = fabric_built_url; + } + fetch_version(fabric_manifest_url, 1); + out("FABRIC_JAR", fabric); cleanup(); return 0; -- cgit v1.2.3