diff options
Diffstat (limited to 'src/version.c')
| -rw-r--r-- | src/version.c | 451 |
1 files changed, 450 insertions, 1 deletions
diff --git a/src/version.c b/src/version.c index a5e63a0..9eb7d0e 100644 --- a/src/version.c +++ b/src/version.c @@ -1,4 +1,5 @@ #include "version.h" +#include "config.h" #include "digest/digest.h" #include "l2su.h" #include "macros.h" @@ -13,6 +14,7 @@ #include <stdint.h> #include <alloca.h> #include <unistd.h> +#include <pcre2.h> const char *const l2_version__messages[] = { "Success", @@ -71,7 +73,7 @@ unsigned l2_version__load_or_download(const char *name, json_t **ojs) return VERSION_EDOWNLOAD; } } else { - l2_version__load_local(remote->id, 0, NULL, &js); + l2_version__load_local(name, 0, NULL, &js); if (!js) return VERSION_ENOTFOUND; } @@ -588,3 +590,450 @@ int l2_version_check_integrity(FILE *fp, l2_sha1_digest_t *digest, size_t sz) return 1; } } + +unsigned l2_version__collect_libraries(json_t *jlibs, l2_subst_t *subst, struct l2_version_library **libs, l2_version_feature_match_proc_t *feature_matcher); +void l2_version__free_libraries(struct l2_version_library *libs); +int l2_version__get_library_artifact_path(json_t *lib, l2_subst_t *subst, const char *classifier, char **path); + +unsigned l2_version_download_libraries(json_t *jlibraries, l2_version_feature_match_proc_t *feature_matcher) +{ + struct l2_version_library *plib = NULL; + l2_subst_t *sp; + + l2_subst_init(&sp); + l2_subst_add(sp, "arch", L2SU_LAUNCHER_ARCH_BITS); + + unsigned u = l2_version__collect_libraries(jlibraries, sp, &plib, feature_matcher); + char hexdig[L2_SHA1_HEX_STRLEN + 1]; + for (struct l2_version_library *lib = plib; lib; lib = lib->next) { + if (lib->has_digest) { + l2_sha1_digest_to_hex(&lib->digest, hexdig); + } + + CMD_DEBUG("Library: %s url: %s dig: %s", lib->artifactpath, lib->url, lib->has_digest ? hexdig : "(none)"); + if (lib->extract.extracttype != EXTRACT_NONE) { + CMD_DEBUG("Extract: %s", lib->extract.extracttype == EXTRACT_NEW ? "new" : "old"); + } + } + + l2_version__free_libraries(plib); + l2_subst_free(sp); + return u; +} + +unsigned l2_version__library_get_classifier(json_t *lib, const char **class) +{ + json_t *natives = json_object_get(lib, "natives"); + if (json_is_object(natives)) { + json_t *mynative = json_object_get(natives, L2SU_LAUNCHER_OS); + + if (json_is_string(mynative)) { + *class = json_string_value(mynative); + } else if (mynative) { + return VERSION_EJSON; + } else { + *class = NULL; + } + } else if (natives) { + return VERSION_EJSON; + } else { + *class = NULL; + } + + return VERSION_SUCCESS; +} + +unsigned l2_version__gen_one_library(json_t *lib, l2_subst_t *subst, struct l2_version_library **olib) +{ + const char *classifier; + unsigned res; + int has_class; + + if ((res = l2_version__library_get_classifier(lib, &classifier)) != VERSION_SUCCESS) { + return res; + } + + struct l2_version_library *retlib = calloc(1, sizeof(struct l2_version_library)); + if (!retlib) return VERSION_EALLOC; + + if ((has_class = l2_version__get_library_artifact_path(lib, subst, classifier, &retlib->artifactpath)) < 0) { + res = VERSION_EJSON; + goto cleanup; + } + + if (classifier) { + retlib->extract.extracttype = EXTRACT_OLD; + } else if (has_class) { + retlib->extract.extracttype = EXTRACT_NEW; + } else { + retlib->extract.extracttype = EXTRACT_NONE; + } + + /* first, find the URL of the library */ + json_t *jurl = json_object_get(lib, "url"); + + if (json_is_string(jurl)) { + retlib->url = l2_launcher_sprintf_alloc("%s%s", json_string_value(jurl), retlib->artifactpath); + retlib->has_digest = false; + + if (!retlib->url) { + res = VERSION_EALLOC; + goto cleanup; + } + } else if (jurl) { + res = VERSION_EJSON; + goto cleanup; + } + + json_t *jdownloads = json_object_get(lib, "downloads"); + if (json_is_object(jdownloads)) { + json_t *dljson = NULL; + if (classifier) { + if (json_unpack(jdownloads, "{s:{s:o}}", "classifiers", classifier, &dljson) < 0 || !dljson) { + res = VERSION_EJSON; + goto cleanup; + } + } else { + dljson = json_object_get(jdownloads, "artifact"); + } + + if (!json_is_object(dljson)) { + res = VERSION_EJSON; + goto cleanup; + } + + const char *courl, *cosha1; + json_int_t asize; + if (json_unpack(dljson, "{s:s, s:s, s:I}", "url", &courl, "sha1", &cosha1, "size", &asize) < 0) { + res = VERSION_EJSON; + goto cleanup; + } + + retlib->url = strdup(courl); + if (!retlib->url) { + res = VERSION_EJSON; + goto cleanup; + } + + retlib->size = (size_t)asize; + + retlib->has_digest = true; + if (l2_sha1_digest_from_hex(&retlib->digest, cosha1) < 0) { + res = VERSION_EJSON; + goto cleanup; + } + } else if (jdownloads) { + res = VERSION_EJSON; + goto cleanup; + } else { /* guess from name */ + retlib->url = l2_launcher_sprintf_alloc("https://libraries.minecraft.net/%s", retlib->artifactpath); + retlib->has_digest = false; + + if (!retlib->url) { + res = VERSION_EALLOC; + goto cleanup; + } + res = VERSION_EJSON; + goto cleanup; + } + + json_t *jextract = json_object_get(lib, "extract"); + if (json_is_object(jextract)) { + json_t *jexclude = json_object_get(jextract, "exclude"); + if (json_is_array(jexclude)) { + size_t idx; + json_t *val; + retlib->extract.exclude = calloc(json_array_size(jexclude) + 1, sizeof(char *)); + if (!retlib->extract.exclude) { + res = VERSION_EALLOC; + goto cleanup; + } + + json_array_foreach(jexclude, idx, val) { + if (!json_is_string(val)) { + res = VERSION_EJSON; + goto cleanup; + } + + retlib->extract.exclude[idx] = strdup(json_string_value(val)); + if (!retlib->extract.exclude[idx]) { + res = VERSION_EALLOC; + goto cleanup; + } + } + } else if (jexclude) { + res = VERSION_EJSON; + goto cleanup; + } + } else if (jextract) { + res = VERSION_EJSON; + goto cleanup; + } + + *olib = retlib; + return VERSION_SUCCESS; + +cleanup: + l2_version__free_libraries(retlib); + return res; +} + +unsigned l2_version__collect_libraries(json_t *jlibs, l2_subst_t *subst, struct l2_version_library **libs, l2_version_feature_match_proc_t *feature_matcher) +{ + struct l2_version_library *libraries = NULL, *tail = NULL; + struct l2_version_library *curlib = NULL; + + unsigned ret = VERSION_EJSON; + enum l2_version_check_result applies; + size_t idx; + json_t *val; + + if (!json_is_array(jlibs)) return VERSION_EJSON; + + json_array_foreach(jlibs, idx, val) { + if (!json_is_object(val)) { + ret = VERSION_EJSON; + goto cleanup; + } + + json_t *rules = json_object_get(val, "rules"); + if (rules) { + applies = l2_version_check_rules(rules, feature_matcher); + if (applies == RULE_CHECK_ERROR) { + ret = VERSION_EJSON; + goto cleanup; + } else if (applies != RULE_CHECK_ALLOW) { + continue; + } + } + + /* this library applies, now generate the download for it */ + const char *name = json_string_value(json_object_get(val, "name")); + CMD_DEBUG("Library: %s", name); + + curlib = NULL; + ret = l2_version__gen_one_library(val, subst, &curlib); + + if (ret == VERSION_EJSON && !curlib) { + CMD_WARN("Library %p was not decoded properly. This is likely a launcher deficiency.", (void *)curlib); + continue; + } else if (ret != VERSION_SUCCESS) { + goto cleanup; + } + + if (tail) { + tail->next = curlib; + tail = curlib; + } else { + libraries = tail = curlib; + } + } + + *libs = libraries; + + return VERSION_SUCCESS; + +cleanup: + /* free libraries */ + l2_version__free_libraries(libraries); + return ret; +} + +void l2_version__free_libraries(struct l2_version_library *libs) +{ + if (!libs) return; + char **excludecur; + + for (struct l2_version_library *next = libs->next; libs; libs = next) { + next = libs->next; + + free(libs->artifactpath); + free(libs->url); + + excludecur = libs->extract.exclude; + if (excludecur) { + for (; *excludecur; ++excludecur) { + free(*excludecur); + } + free(libs->extract.exclude); + } + + free(libs); + } +} + +int l2_version__get_library_artifact_path(json_t *lib, l2_subst_t *subst, const char *classifier, char **path) +{ + json_t *jname = json_object_get(lib, "name"); + if (!json_is_string(jname)) return -1; + + const char *name = json_string_value(jname); + const char *group_id_end = NULL; + const char *artifact_id = NULL; + int artifact_id_len = 0; + const char *version = NULL; + int version_len = 0; + const char *tclass = NULL; + char *basepath; + size_t basepathlen; + + for (const char *cur = name; *cur; ++cur) { + if (*cur == ':') { + if (!group_id_end) { + group_id_end = cur; + artifact_id = cur + 1; + } else if (!version) { + version = cur + 1; + artifact_id_len = (int)(cur - artifact_id); + } else if (!tclass) { + tclass = cur + 1; + version_len = (int)(cur - version); + } else { + return -1; + } + } + } + + if (!group_id_end || !artifact_id || !version) return -1; + + basepathlen = group_id_end - name; + basepath = alloca(basepathlen + 1); + memcpy(basepath, name, basepathlen); + basepath[basepathlen] = '\0'; + + for (char *cur = basepath; *cur; ++cur) { + if (*cur == '.') *cur = '/'; + } + + size_t bufsz; + char *buf; + int has_class = 0; + + if (tclass || classifier) { + L2_ASPRINTF(buf, bufsz, "%s/%*.*s-%*.*s-%s.jar", basepath, + artifact_id_len, artifact_id_len, artifact_id, + version_len, version_len, version, tclass ? tclass : classifier); + has_class = 1; + } else { + L2_ASPRINTF(buf, bufsz, "%s/%*.*s-%s.jar", basepath, artifact_id_len, artifact_id_len, artifact_id, version); + } + + return l2_subst_apply(subst, buf, path) < 0 ? -1 : has_class; +} + +int l2_version__whole_string_matches(const char *pattern, const char *subject) +{ + PCRE2_SIZE erroffset; + int errcode; + + pcre2_code *cd = pcre2_compile((PCRE2_SPTR8)pattern, PCRE2_ZERO_TERMINATED, + PCRE2_ANCHORED | PCRE2_ENDANCHORED, &errcode, &erroffset, NULL); + pcre2_match_data *md; + if (!cd) { + return -1; + } + + md = pcre2_match_data_create(1, NULL); + errcode = pcre2_match(cd, (PCRE2_SPTR8)subject, PCRE2_ZERO_TERMINATED, 0, 0, md, NULL); + + /* we care about neither the code nor the match data */ + pcre2_code_free(cd); + pcre2_match_data_free(md); + + if (errcode == PCRE2_ERROR_NOMATCH) return 0; + return errcode >= 0 ? 1 : -1; +} + +enum l2_version_check_result l2_version__check_one_rule(json_t *rule, l2_version_feature_match_proc_t *feature_matcher) +{ + if (!json_is_object(rule)) + return RULE_CHECK_ERROR; + + enum l2_version_check_result myaction; + + json_t *action = json_object_get(rule, "action"); + if (!json_is_string(action)) return RULE_CHECK_ERROR; + const char *actionstr = json_string_value(action); + + if (!strcmp(actionstr, "allow")) { + myaction = RULE_CHECK_ALLOW; + } else if (!strcmp(actionstr, "disallow")) { + myaction = RULE_CHECK_DISALLOW; + } else { + return RULE_CHECK_ERROR; + } + + json_t *val = json_object_get(rule, "os"); + if (json_is_object(val)) { + json_t *checkval = json_object_get(val, "name"); + + if (json_is_string(checkval)) { + if (strcmp(json_string_value(checkval), L2SU_LAUNCHER_OS)) { /* wrong OS, rule doesn't apply */ + return RULE_CHECK_DEFAULT; + } + } else if (checkval) { + return RULE_CHECK_ERROR; + } + + checkval = json_object_get(val, "arch"); + if (json_is_string(checkval)) { + int res = l2_version__whole_string_matches(json_string_value(checkval), L2SU_LAUNCHER_ARCH); + if (res < 0) return RULE_CHECK_ERROR; + + if (!res) return RULE_CHECK_DEFAULT; + } else if (checkval) { + return RULE_CHECK_ERROR; + } + + /* FIXME: don't check OS version because idk how to get that portably yet */ + + return myaction; + } else if (val) { + return RULE_CHECK_ERROR; + } + + val = json_object_get(rule, "features"); + if (json_is_object(val)) { + json_t *feat; + const char *fname; + + json_object_foreach(val, fname, feat) { + if (!(*feature_matcher)(fname, feat)) { + return RULE_CHECK_DEFAULT; + } + } + } else if (val) { + return RULE_CHECK_ERROR; + } + + return myaction; +} + +enum l2_version_check_result l2_version_check_rules(json_t *rules, l2_version_feature_match_proc_t *feature_matcher) +{ + size_t idx; + json_t *val; + enum l2_version_check_result res = RULE_CHECK_DEFAULT; + + if (!json_is_array(rules)) { + return RULE_CHECK_ERROR; + } + + json_array_foreach(rules, idx, val) { + /* first, check if the rule actually matches us */ + switch (l2_version__check_one_rule(val, feature_matcher)) { + case RULE_CHECK_ERROR: + return RULE_CHECK_ERROR; + case RULE_CHECK_ALLOW: + res = RULE_CHECK_ALLOW; + break; + case RULE_CHECK_DISALLOW: + res = RULE_CHECK_DISALLOW; + break; + default: + break; + } + } + + return res; +} |
