aboutsummaryrefslogtreecommitdiffstats
path: root/src/version.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/version.c')
-rw-r--r--src/version.c451
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;
+}