diff options
| -rw-r--r-- | meson.build | 1 | ||||
| -rw-r--r-- | src/cmd-version.c | 16 | ||||
| -rw-r--r-- | src/l2su.h | 3 | ||||
| -rw-r--r-- | src/launcherutil.c | 24 | ||||
| -rw-r--r-- | src/macros.h | 8 | ||||
| -rw-r--r-- | src/version.c | 204 | ||||
| -rw-r--r-- | src/version.h | 103 |
7 files changed, 271 insertions, 88 deletions
diff --git a/meson.build b/meson.build index b4f6f89..5689d96 100644 --- a/meson.build +++ b/meson.build @@ -3,6 +3,7 @@ add_global_arguments('-D_XOPEN_SOURCE=700', language : 'c') curl_dep = dependency('libcurl') jansson_dep = dependency('jansson') +pcre_dep = dependency('libpcre2-8') config_data = configuration_data() diff --git a/src/cmd-version.c b/src/cmd-version.c index 260be7d..3896fcd 100644 --- a/src/cmd-version.c +++ b/src/cmd-version.c @@ -5,6 +5,7 @@ #include "l2su.h" #include "macros.h" +#include <jansson.h> #include <stdio.h> unsigned cmd_version_list_remote(struct l2_context_node *ctx, char **args) @@ -36,6 +37,19 @@ unsigned cmd_version_list_local(struct l2_context_node *ctx, char **args) unsigned cmd_version_install(struct l2_context_node *ctx, char **args) { - + unsigned res = l2_version_load_remote(); + if (res != VERSION_SUCCESS) { + CMD_FATAL("failed to load versions: %s", l2_version_strerror(res)); + } + + json_t *js; + res = l2_version_load_local("b1.7.3", &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)); + json_decref(js); + return CMD_RESULT_SUCCESS; } @@ -2,12 +2,14 @@ #define L2SU_MAIN_H_INCLUDED #include "command.h" +#include "digest/digest.h" #include "instance.h" #include "version.h" #include <fcntl.h> #include <time.h> #include <curl/curl.h> +#include <stdio.h> struct l2_user_paths { const char *config; @@ -54,5 +56,6 @@ void l2_launcher_download_cleanup(struct l2_dlbuf *buf); 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); #endif /* include guard */ diff --git a/src/launcherutil.c b/src/launcherutil.c index 38dddaa..e11c280 100644 --- a/src/launcherutil.c +++ b/src/launcherutil.c @@ -1,7 +1,10 @@ +#include "digest/digest.h" #include "macros.h" #include "l2su.h" #include <curl/easy.h> +#include <jansson.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> @@ -234,3 +237,24 @@ CURLcode l2_launcher_download(CURL *cd, const char *url, void **odata, size_t *o return CURLE_OK; } + +int l2_json_merge_objects(json_t *j1, json_t *j2) +{ + const char *key; + size_t keylen; + json_t *val; + json_t *myval; + + json_object_keylen_foreach(j2, key, keylen, val) { + myval = json_object_getn(j1, key, keylen); + if (json_is_object(myval) && json_is_object(val)) { + if (l2_json_merge_objects(myval, val) < 0) return -1; + } else if (json_is_array(myval) && json_is_array(val)) { + if (json_array_extend(myval, val) < 0) return -1; + } else if (!myval) { + if (json_object_setn_nocheck(j1, key, keylen, val) < 0) return -1; + } + } + + return 0; +} diff --git a/src/macros.h b/src/macros.h index 8be48dd..00e80a6 100644 --- a/src/macros.h +++ b/src/macros.h @@ -9,7 +9,9 @@ #include <alloca.h> #include <string.h> +#include <stdio.h> +/* I LOVE ALLOCA */ #define L2_ASTRCAT2(_var, _s1, _len1, _s2, _len2) do { \ _var = alloca(_len1 + _len2 + 1); \ memcpy(_var, (_s1), (_len1)); \ @@ -17,6 +19,12 @@ (_var)[((_len1) + (_len2))] = '\0'; \ } while (0) +#define L2_ASPRINTF(_var, _tmp, _fmt, ...) do { \ + _tmp = snprintf(NULL, 0, _fmt, __VA_ARGS__); \ + _var = alloca(_tmp + 1); \ + snprintf(_var, _tmp + 1, _fmt, __VA_ARGS__); \ +} while (0) + #define L2_USER_AGENT PROJECT_NAME "/0.1.0 <[email protected]>" #define L2_URL_META_BASE "https://piston-meta.mojang.com" diff --git a/src/version.c b/src/version.c index d8f264c..a5e63a0 100644 --- a/src/version.c +++ b/src/version.c @@ -21,6 +21,8 @@ const char *const l2_version__messages[] = { "Allocation failed", "OS error", "Error downloading version manifest", + "Version not found", + "Max recursion depth exceeded", NULL }; @@ -46,12 +48,183 @@ done: return r; } -unsigned l2_version_load_local(const char *name) +void l2_version__load_local(const char *id, size_t expectsz, l2_sha1_digest_t *digest, json_t **ojson); +bool l2_version__download_remote(struct l2_version_remote *remote, json_t **ojson); + +unsigned l2_version__load_or_download(const char *name, json_t **ojs) { + json_t *js = NULL; + struct l2_version_remote *remote = NULL; + for (struct l2_version_remote *cur = l2_state.ver_remote_head; cur; cur = cur->next) { + if (!strcmp(cur->id, name)) { + remote = cur; + break; + } + } + + if (remote) { + l2_version__load_local(remote->id, 0, &remote->sha1, &js); + if (!js) { + CMD_DEBUG0("local version not found, downloading"); + if (!l2_version__download_remote(remote, &js) || !js) + return VERSION_EDOWNLOAD; + } + } else { + l2_version__load_local(remote->id, 0, NULL, &js); + if (!js) return VERSION_ENOTFOUND; + } + + *ojs = js; return VERSION_SUCCESS; } +unsigned l2_version__load_recursive(const char *name, json_t *target, int depth) +{ + json_t *other = NULL; + json_t *inherit; + + if (depth <= 0) return VERSION_ERECURSE; + + unsigned res = l2_version__load_or_download(name, &other); + + if (res != VERSION_SUCCESS) return res; + l2_json_merge_objects(target, other); + + inherit = json_object_get(other, "inheritsFrom"); + if (!json_is_string(inherit)) { + json_decref(other); + return VERSION_SUCCESS; + } + + json_decref(other); + return l2_version__load_recursive(json_string_value(inherit), target, depth - 1); +} + +unsigned l2_version_load_local(const char *name, json_t **ojson) +{ + json_t *js = json_object(); + if (!js) return VERSION_EJSON; + + unsigned res = l2_version__load_recursive(name, js, 10); + *ojson = js; + return res; +} + +void l2_version__load_local(const char *id, size_t expectsz, l2_sha1_digest_t *digest, json_t **ojson) +{ + char *path; + size_t temp; + L2_ASPRINTF(path, temp, "%s/versions/%s/%s.json", l2_state.paths.data, id, id); + + FILE *ver = fopen(path, "r"); + if (!ver) return; + + int res = l2_version_check_integrity(ver, digest, expectsz); + if (res < 0) { + CMD_WARN("Error checking version file integrity for %s", id); + return; + } else if (res == 0) { + CMD_DEBUG("Version %s (%s) has bad integrity", id, path); + return; + } + + json_t *js; + json_error_t jerr; + + rewind(ver); + js = json_loadf(ver, JSON_REJECT_DUPLICATES, &jerr); + if (!js) { + CMD_WARN("JSON error loading %s: %s", id, jerr.text); + return; + } + + *ojson = js; +} + +/* downloads a remote version */ +bool l2_version__download_remote(struct l2_version_remote *remote, json_t **ojson) +{ + char *dirname; + char *fname; + size_t temp; + FILE *save = NULL; + json_t *js = NULL; + L2_ASPRINTF(dirname, temp, "%s/versions/%s", l2_state.paths.data, remote->id); + L2_ASPRINTF(fname, temp, "%s/%s.json", dirname, remote->id); + + if (l2_launcher_mkdir_parents(dirname) < 0) { + CMD_ERROR("Could not create directory for %s: %s", remote->id, strerror(errno)); + return false; + } + + CURL *cu = curl_easy_init(); + l2_sha1_state_t st; + l2_sha1_digest_t dig; + json_error_t err; + if (!cu) return false; + + void *data = NULL; + char errbuf[CURL_ERROR_SIZE]; + size_t sz; + + memset(errbuf, 0, CURL_ERROR_SIZE * sizeof(char)); + + curl_easy_setopt(cu, CURLOPT_ERRORBUFFER, errbuf); + CURLcode code = l2_launcher_download(cu, remote->url, &data, &sz); + + if (code != CURLE_OK) { + CMD_WARN("Error downloading version %s: %s: %s", remote->id, curl_easy_strerror(code), errbuf); + goto cleanup; + } + + l2_sha1_init(&st); + l2_sha1_update(&st, data, sz); + l2_sha1_finalize(&st, &dig); + + if (l2_sha1_digest_compare(&dig, &remote->sha1)) { + char d1[L2_SHA1_DIGESTLEN + 1]; + char d2[L2_SHA1_DIGESTLEN + 1]; + + l2_sha1_digest_to_hex(&dig, d1); + l2_sha1_digest_to_hex(&remote->sha1, d2); + + CMD_WARN("Downloaded file for %s has incorrect hash: expected %s, got %s!", remote->id, d2, d1); + goto cleanup; + } + + js = json_loadb(data, sz, JSON_REJECT_DUPLICATES, &err); + if (!js) { + CMD_WARN("JSON parse error reading version %s: %s", remote->id, err.text); + goto cleanup; + } + + save = fopen(fname, "w"); + if (!save) { + CMD_WARN("Error opening version file %s", fname); + goto cleanup; + } + + if (fwrite(data, sz, 1, save) < 1) { + CMD_WARN("Error saving version info to %s", fname); + goto cleanup; + } + + *ojson = js; + + curl_easy_cleanup(cu); + fclose(save); + free(data); + return true; + +cleanup: + curl_easy_cleanup(cu); + free(data); + if (save) fclose(save); + if (js) json_decref(js); + return false; +} + char *l2_version__read_etag(time_t *lastmod) { size_t dpathlen = strlen(l2_state.paths.data); @@ -386,3 +559,32 @@ cleanup: return res; } + +int l2_version_check_integrity(FILE *fp, l2_sha1_digest_t *digest, size_t sz) +{ + #define VER_READBUF_SZ (1024) + + size_t len = 0, nread; + uint8_t buf[VER_READBUF_SZ]; + l2_sha1_digest_t rdigest; + l2_sha1_state_t st; + + l2_sha1_init(&st); + + while ((nread = fread(buf, 1, VER_READBUF_SZ, fp)) > 0) { + len += nread; + l2_sha1_update(&st, buf, nread); + } + + if (ferror(fp)) return -1; + + l2_sha1_finalize(&st, &rdigest); + + if (sz > 0 && sz != len) return 0; + + if (digest) { + return !l2_sha1_digest_compare(&rdigest, digest) ? 1 : 0; + } else { + return 1; + } +} diff --git a/src/version.h b/src/version.h index 01dcbb8..7b02c21 100644 --- a/src/version.h +++ b/src/version.h @@ -11,7 +11,9 @@ enum { VERSION_EJSON, VERSION_EALLOC, VERSION_ERRNO, - VERSION_EDOWNLOAD + VERSION_EDOWNLOAD, + VERSION_ENOTFOUND, + VERSION_ERECURSE }; struct l2_version_remote { @@ -34,6 +36,15 @@ enum { VERSION_ARG_ACTION_DISALLOW }; +enum { + VERSION_DOWNLOAD_CLIENT, + VERSION_DOWNLOAD_SERVER, + VERSION_DOWNLOAD_WINDOWS_SERVER, + VERSION_DOWNLOAD_CLIENT_MAPPINGS, + VERSION_DOWNLOAD_SERVER_MAPPINGS, + VERSION_DOWNLOAD_MAX +}; + #define FEATURE_DEMO (1u << 0) #define FEATURE_CUSTOM_RES (1u << 1) #define FEATURE_QUICK_PLAY_SUPPORTED (1u << 2) @@ -47,91 +58,9 @@ struct l2_version_download { size_t size; }; -struct l2_version_rule { - unsigned action; - unsigned features; - - struct l2_version_argument_rule_os { - char *name; /* exact match */ - char *arch; /* regex */ - char *version; /* regex */ - } os; - - struct l2_version_argument_rule *next; -}; - -struct l2_version_argument { - char **value; /* ends with NULL */ - - struct l2_version_rule *rules; - - struct l2_version_argument *next; -}; - struct l2_version { - struct l2_version_argument *game; - struct l2_version_argument *jvm; - - struct l2_version_asset_index { - char *id; - struct l2_version_download download; - size_t total_size; - } asset_index; - - char *assets; /* should equal asset_index.id */ - json_int_t compliance_level; - - struct l2_version_jar_download { - char *id; - struct l2_version_jar { - char *name; - struct l2_version_download download; - struct l2_version_jar *next; - } *jars; - } downloads; - - struct l2_version_java_version { - char *component; - json_int_t major_version; - } java_version; - - struct l2_version_library { - char *name; - - struct l2_version_library_download { - struct l2_version_library_artifact { - char *path; - struct l2_version_download download; - } *artifact; - - struct l2_version_download_classifier { - char *name; - char *path; - struct l2_version_download download; - struct l2_version_download_classifier *next; - } *classifiers; - } downloads; - - struct l2_version_library_extract { - char **exclude; /* ends with NULL */ - } *extract; - - struct l2_version_rule *rules; - - struct l2_version_native { - char *os; - char *name; - struct l2_version_native *next; - } *natives; - } *libraries; - - char *main_class; - char *type; - time_t release_time; - time_t time; - - struct l2_version *inherits; - /* minecraftArguments, */ + json_t *data; + struct l2_version *inherit; }; extern const char *const l2_version__messages[]; @@ -140,6 +69,8 @@ extern const char *const l2_version__messages[]; unsigned l2_version_load_remote(void); -unsigned l2_version_load_local(const char *name); +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); #endif /* include guard */ |
