aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meson.build1
-rw-r--r--src/cmd-version.c16
-rw-r--r--src/l2su.h3
-rw-r--r--src/launcherutil.c24
-rw-r--r--src/macros.h8
-rw-r--r--src/version.c204
-rw-r--r--src/version.h103
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;
}
diff --git a/src/l2su.h b/src/l2su.h
index 7487a9a..518f1ee 100644
--- a/src/l2su.h
+++ b/src/l2su.h
@@ -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 */