diff options
| -rw-r--r-- | meson.build | 83 | ||||
| -rw-r--r-- | meson.options | 15 | ||||
| -rw-r--r-- | src/cmd-version.c | 16 | ||||
| -rw-r--r-- | src/config.h.in | 5 | ||||
| -rw-r--r-- | src/l2su.h | 11 | ||||
| -rw-r--r-- | src/launcherutil.c | 21 | ||||
| -rw-r--r-- | src/macros.h | 6 | ||||
| -rw-r--r-- | src/meson.build | 2 | ||||
| -rw-r--r-- | src/subst.c | 148 | ||||
| -rw-r--r-- | src/version.c | 451 | ||||
| -rw-r--r-- | src/version.h | 35 |
11 files changed, 780 insertions, 13 deletions
diff --git a/meson.build b/meson.build index 5689d96..0b18d02 100644 --- a/meson.build +++ b/meson.build @@ -1,9 +1,9 @@ project('l2su', 'c', default_options : {'b_ndebug': 'if-release'}) -add_global_arguments('-D_XOPEN_SOURCE=700', language : 'c') +add_global_arguments('-D_XOPEN_SOURCE=700', '-DPCRE2_CODE_UNIT_WIDTH=8', language : 'c') curl_dep = dependency('libcurl') jansson_dep = dependency('jansson') -pcre_dep = dependency('libpcre2-8') +pcre2_dep = dependency('libpcre2-8') config_data = configuration_data() @@ -18,5 +18,82 @@ else error('Middle endian is unsupported') endif +launcher_os = get_option('launcher_os') +launcher_arch_bits = get_option('launcher_arch_bits') +launcher_arch = get_option('launcher_arch') +launcher_jre_arch = get_option('launcher_jre_arch') + +if launcher_os == 'auto' + if host_machine.system() == 'linux' + launcher_os = 'linux' + elif host_machine.system() == 'darwin' + launcher_os = 'osx' + elif host_machine.system() == 'windows' + launcher_os = 'windows' + else + error('Unable to determine OS. Please use the launcher_os build option.') + endif +endif + +if launcher_arch_bits == 'auto' + if host_machine.cpu_family() == 'x86' + launcher_arch_bits = '32' + elif host_machine.cpu_family() == 'x86_64' or host_machine.cpu_family() == 'arm64' + launcher_arch_bits = '64' + else + error('Unable to determine launcher arch bits. Please use the launcher_arch_bits option.') + endif +endif + +if launcher_arch == '' + launcher_arch = host_machine.cpu_family() +endif + +if launcher_jre_arch == 'auto' + if host_machine.system() == 'linux' + if host_machine.cpu_family() == 'x86' + launcher_jre_arch = 'linux-i386' + elif host_machine.cpu_family() == 'x86_64' + launcher_jre_arch = 'linux' + else + launcher_jre_arch = 'gamecore' + endif + elif host_machine.system() == 'darwin' + if host_machine.cpu_family() == 'aarch64' + launcher_jre_arch = 'mac-os-arm64' + else + launcher_jre_arch = 'mac-os' + endif + elif host_machine.system() == 'windows' + if host_machine.cpu_family() == 'x86' + launcher_jre_arch = 'windows-x86' + elif host_machine.cpu_family() == 'x86_64' + launcher_jre_arch = 'windows-x64' + elif host_machine.cpu_family() == 'aarch64' + launcher_jre_arch = 'windows-arm64' + else + launcher_jre_arch = 'gamecore' + endif + else + launcher_jre_arch = 'gamecore' + endif +endif + +message('Using launcher OS', launcher_os) +config_data.set_quoted('L2SU_LAUNCHER_OS', launcher_os) + +message('Using launcher arch bits', launcher_arch_bits) +config_data.set_quoted('L2SU_LAUNCHER_ARCH_BITS', launcher_arch_bits) + +message('Using launcher arch', launcher_arch) +config_data.set_quoted('L2SU_LAUNCHER_ARCH', launcher_arch) + +message('Using launcher JRE arch', launcher_jre_arch) +config_data.set_quoted('L2SU_JRE_ARCH', launcher_jre_arch) + +if launcher_jre_arch == 'gamecore' + message('(launcher_jre_arch == gamecore, so JREs will most likely not be downloaded.)') +endif + subdir('src') -executable('l2su', launcher_srcs, dependencies : [curl_dep, jansson_dep], include_directories : [config_include_dir], override_options : {'c_std': 'c99'}) +executable('l2su', launcher_srcs, dependencies : [curl_dep, jansson_dep, pcre2_dep], include_directories : [config_include_dir], override_options : {'c_std': 'c99'}) diff --git a/meson.options b/meson.options new file mode 100644 index 0000000..ea5e01e --- /dev/null +++ b/meson.options @@ -0,0 +1,15 @@ +option('launcher_os', type : 'combo', + choices : [ 'auto', 'windows', 'linux', 'osx' ], value : 'auto', + description : 'OS name to use for library fetching') + +option('launcher_arch_bits', type : 'combo', + choices : [ 'auto', '32', '64' ], value : 'auto', + description : 'system architecture bits') + +option('launcher_arch', type : 'string', + value : '', + description : 'launcher architecture') + +option('launcher_jre_arch', type : 'combo', + value : 'auto', choices : [ 'auto', 'gamecore', 'linux', 'linux-i386', 'mac-os', 'mac-os-arm64', 'windows-x86', 'windows-x64', 'windows-arm64' ], + description : 'launcher JRE architecture') diff --git a/src/cmd-version.c b/src/cmd-version.c index 3896fcd..b1e3fa8 100644 --- a/src/cmd-version.c +++ b/src/cmd-version.c @@ -31,10 +31,17 @@ unsigned cmd_version_list_remote(struct l2_context_node *ctx, char **args) unsigned cmd_version_list_local(struct l2_context_node *ctx, char **args) { - return CMD_RESULT_SUCCESS; } +unsigned l2_version_download_libraries(json_t *jlibraries, l2_version_feature_match_proc_t *feature_matcher); + +bool feat_match_cb(const char *name, json_t *js) { + L2_UNUSED(name); + L2_UNUSED(js); + return false; +} + unsigned cmd_version_install(struct l2_context_node *ctx, char **args) { unsigned res = l2_version_load_remote(); @@ -43,12 +50,17 @@ unsigned cmd_version_install(struct l2_context_node *ctx, char **args) } json_t *js; - res = l2_version_load_local("b1.7.3", &js); + res = l2_version_load_local(*args, &js); if (res != VERSION_SUCCESS) { CMD_FATAL("failed to load 1.8.9: %s", l2_version_strerror(res)); } json_dumpf(js, stdout, JSON_INDENT(4)); + + putchar('\n'); + + printf("%s\n", l2_version_strerror(l2_version_download_libraries(json_object_get(js, "libraries"), &feat_match_cb))); + json_decref(js); return CMD_RESULT_SUCCESS; diff --git a/src/config.h.in b/src/config.h.in index df91d35..ffb2c41 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -7,4 +7,9 @@ #mesondefine L2SU_ENDIAN_LITTLE #mesondefine L2SU_ENDIAN_BIG +#mesondefine L2SU_LAUNCHER_OS +#mesondefine L2SU_LAUNCHER_ARCH_BITS +#mesondefine L2SU_LAUNCHER_ARCH +#mesondefine L2SU_JRE_ARCH + #endif /* include guard */ @@ -5,6 +5,7 @@ #include "digest/digest.h" #include "instance.h" #include "version.h" +#include "macros.h" #include <fcntl.h> #include <time.h> @@ -33,6 +34,8 @@ extern struct tag_l2_state_t l2_state; /* homecooked string methods */ char *l2_launcher_strapp(char *target, const char *src); +char *l2_launcher_sprintf_alloc(const char *fmt, ...) L2_FORMAT(printf, 1, 2); + /* launcher utilities */ char *l2_launcher_find_config_path(void); char *l2_launcher_find_data_path(void); @@ -58,4 +61,12 @@ extern const curl_write_callback l2_dlcb; CURLcode l2_launcher_download(CURL *cd, const char *url, void **odata, size_t *osize); int l2_json_merge_objects(json_t *j1, json_t *j2); +/* string substitute utility */ +typedef struct l2_subst l2_subst_t; + +int l2_subst_init(l2_subst_t **sp); +int l2_subst_add(l2_subst_t *sp, const char *name, const char *value); +int l2_subst_apply(l2_subst_t *sp, const char *in, char **out); +void l2_subst_free(l2_subst_t *sp); + #endif /* include guard */ diff --git a/src/launcherutil.c b/src/launcherutil.c index e11c280..b31b2df 100644 --- a/src/launcherutil.c +++ b/src/launcherutil.c @@ -11,6 +11,7 @@ #include <unistd.h> #include <errno.h> #include <sys/stat.h> +#include <stdarg.h> /* handcoded string functions * @@ -34,6 +35,26 @@ char *l2_launcher_strapp(char *buf, const char *src) return ret; } +char *l2_launcher_sprintf_alloc(const char *fmt, ...) +{ + va_list pva; + + va_start(pva, fmt); + + size_t len = vsnprintf(NULL, 0, fmt, pva); + va_end(pva); + + char *ret = calloc(len, sizeof(char)); + if (!ret) { + return ret; + } + + va_start(pva, fmt); + vsnprintf(ret, len, fmt, pva); + va_end(pva); + return ret; +} + char *l2_launcher_find_config_path(void) { /* check for $L2SU_CONFIG */ diff --git a/src/macros.h b/src/macros.h index 00e80a6..71aa137 100644 --- a/src/macros.h +++ b/src/macros.h @@ -30,4 +30,10 @@ #define L2_URL_META_BASE "https://piston-meta.mojang.com" #define L2_URL_META_VERSION_MANIFEST L2_URL_META_BASE "/mc/game/version_manifest_v2.json" +#ifdef __GNUC__ +#define L2_FORMAT(_flavor, _stridx, _argidx) __attribute__((format (_flavor, _stridx, _argidx))) +#else +#define L2_FORMAT(_unused1, _unused2, _unused3) +#endif + #endif /* include guard */ diff --git a/src/meson.build b/src/meson.build index 0b90ba4..fea440e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,4 @@ -launcher_srcs = files('l2su.c', 'command.c', 'cmd-instance.c', 'uuid/uuid.c', 'launcherutil.c', 'instance.c', 'cmd-version.c', 'digest/sha1.c', 'version.c') +launcher_srcs = files('l2su.c', 'command.c', 'cmd-instance.c', 'uuid/uuid.c', 'launcherutil.c', 'instance.c', 'cmd-version.c', 'digest/sha1.c', 'version.c', 'subst.c') configure_file(input : 'config.h.in', output : 'config.h', configuration : config_data) config_include_dir = include_directories('.') diff --git a/src/subst.c b/src/subst.c new file mode 100644 index 0000000..6c8bec6 --- /dev/null +++ b/src/subst.c @@ -0,0 +1,148 @@ +#include "l2su.h" + +#include <stdlib.h> +#include <string.h> + +struct l2_subst__item { + char *name; + char *value; + + struct l2_subst__item *next; +}; + +struct l2_subst { + struct l2_subst__item *items; + int status; +}; + +int l2_subst_init(l2_subst_t **sp) +{ + l2_subst_t *ret = calloc(1, sizeof(struct l2_subst)); + if (!ret) return -1; + + *sp = ret; + + ret->items = NULL; + ret->status = 0; + + return 0; +} + +int l2_subst_add(l2_subst_t *sp, const char *name, const char *value) +{ + if (sp->status < 0) return sp->status; + + char *namedup = strdup(name); + char *valdup = strdup(value); + struct l2_subst__item *item = calloc(1, sizeof(struct l2_subst__item)); + + if (!namedup || !valdup || !item) { + free(namedup); + free(valdup); + free(item); + sp->status = -1; + return -1; + } + + item->name = namedup; + item->value = valdup; + item->next = sp->items; + sp->items = item; + + return 0; +} + +const char *l2_subst__find(l2_subst_t *sp, const char *name, size_t len) +{ + for (struct l2_subst__item *item = sp->items; item; item = item->next) { + if (!strncmp(item->name, name, len)) return item->value; + } + return NULL; +} + +int l2_subst__append(char **retbuf, size_t *sz, size_t *cap, const char *start, const char *end) +{ + ptrdiff_t len = end - start; + if (len <= 0) return 0; + + if (*sz + len > *cap) { + size_t newcap = *cap ? *cap : 16; + while (newcap && *sz + len > newcap) newcap <<= 1; + + if (!newcap) { + goto cleanup; + } + + char *newbuf = realloc(*retbuf, newcap); + if (!newbuf) { + goto cleanup; + } + + *retbuf = newbuf; + *cap = newcap; + } + + memcpy(*retbuf + *sz, start, len); + *sz += len; + return 0; + +cleanup: + free(*retbuf); + return -1; +} + +int l2_subst_apply(l2_subst_t *sp, const char *in, char **out) +{ + if (sp->status < 0) return sp->status; + + char *retbuf = NULL, *temp; + size_t size = 0; + size_t cap = 0; + + const char *lastcpy = in; + const char *open = NULL; + const char *cur; + + for (cur = in; *cur; ++cur) { + if (*cur == '$' && *(cur + 1) == '{') { + if (l2_subst__append(&retbuf, &size, &cap, lastcpy, cur) < 0) + return -1; + + lastcpy = cur; + open = cur + 2; + continue; + } else if (*cur == '}' && open) { + const char *rep = l2_subst__find(sp, open, cur - open); + if (rep) { + if (l2_subst__append(&retbuf, &size, &cap, rep, rep + strlen(rep)) < 0) + return -1; + lastcpy = cur + 1; + } + + open = NULL; + } + } + + if (l2_subst__append(&retbuf, &size, &cap, lastcpy, cur + 1) < 0) return -1; + + temp = realloc(retbuf, size); + if (temp) retbuf = temp; + *out = retbuf; + + return 0; +} + +void l2_subst_free(l2_subst_t *sp) +{ + if (!sp) return; + + for (struct l2_subst__item *cur = sp->items, *temp; cur; cur = temp) { + temp = cur->next; + free(cur->name); + free(cur->value); + free(cur); + } + + free(sp); + return; +} 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; +} diff --git a/src/version.h b/src/version.h index 7b02c21..c3448eb 100644 --- a/src/version.h +++ b/src/version.h @@ -4,6 +4,7 @@ #include "digest/digest.h" #include <time.h> #include <jansson.h> +#include <stdbool.h> enum { VERSION_SUCCESS, @@ -45,12 +46,7 @@ enum { VERSION_DOWNLOAD_MAX }; -#define FEATURE_DEMO (1u << 0) -#define FEATURE_CUSTOM_RES (1u << 1) -#define FEATURE_QUICK_PLAY_SUPPORTED (1u << 2) -#define FEATURE_QUICK_PLAY_SINGLEPLAYER (1u << 3) -#define FEATURE_QUICK_PLAY_MULTIPLAYER (1u << 4) -#define FEATURE_QUICK_PLAY_REALMS (1u << 5) +typedef bool (l2_version_feature_match_proc_t)(const char *name, json_t *val); struct l2_version_download { char *url; @@ -58,6 +54,26 @@ struct l2_version_download { size_t size; }; +struct l2_version_library { + char *url; + char *artifactpath; + + l2_sha1_digest_t digest; + bool has_digest; + size_t size; + + struct { + enum { + EXTRACT_NONE, /* do not extract anything */ + EXTRACT_OLD, /* extract whole jar, preserving entries that are not excluded */ + EXTRACT_NEW /* extract only .dll, .so, .dylib from jar */ + } extracttype; + char **exclude; + } extract; + + struct l2_version_library *next; +}; + struct l2_version { json_t *data; struct l2_version *inherit; @@ -73,4 +89,11 @@ unsigned l2_version_load_local(const char *name, json_t **ojs); int l2_version_check_integrity(FILE *fp, l2_sha1_digest_t *digest, size_t sz); +enum l2_version_check_result { + RULE_CHECK_ERROR = -1, + RULE_CHECK_DEFAULT, + RULE_CHECK_ALLOW, + RULE_CHECK_DISALLOW, +} l2_version_check_rules(json_t *rules, l2_version_feature_match_proc_t *matcher); + #endif /* include guard */ |
