aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmd-version.c2
-rw-r--r--src/command.h8
-rw-r--r--src/digest/sha1.c2
-rw-r--r--src/l2su.h22
-rw-r--r--src/launcherutil.c77
-rw-r--r--src/macros.h14
-rw-r--r--src/meson.build2
-rw-r--r--src/version.c327
-rw-r--r--src/version.h30
9 files changed, 481 insertions, 3 deletions
diff --git a/src/cmd-version.c b/src/cmd-version.c
index 873108a..62baa3f 100644
--- a/src/cmd-version.c
+++ b/src/cmd-version.c
@@ -1,11 +1,13 @@
#include "command.h"
#include "commands.h"
#include "digest/digest.h"
+#include "version.h"
#include <stdio.h>
unsigned cmd_version_list_remote(struct l2_context_node *ctx, char **args)
{
+ printf("%s\n", l2_version_strerror(l2_version_load_remote()));
return CMD_RESULT_SUCCESS;
}
diff --git a/src/command.h b/src/command.h
index 7709292..76a9166 100644
--- a/src/command.h
+++ b/src/command.h
@@ -54,6 +54,14 @@ enum {
#define CMD_WARN(_s, ...) CMD_MSG("warn", _s, __VA_ARGS__)
#define CMD_WARN0(_s) CMD_MSG0("warn", _s)
+#ifdef NDEBUG
+#define CMD_DEBUG(_s, ...)
+#define CMD_DEBUG0(_s)
+#else
+#define CMD_DEBUG(_s, ...) CMD_MSG("debug", _s, __VA_ARGS__)
+#define CMD_DEBUG0(_s) CMD_MSG0("debug", _s)
+#endif
+
#define CMD_MSG_UNKNOWN_ARGUMENT "Unknown argument '%s'."
#define CMD_MSG_REQ_ARG_UNSPECIFIED "Required argument '%s' not specified."
#define CMD_MSG_INVALID_UUID "Invalid UUID '%s'."
diff --git a/src/digest/sha1.c b/src/digest/sha1.c
index 6601961..e563abb 100644
--- a/src/digest/sha1.c
+++ b/src/digest/sha1.c
@@ -215,7 +215,7 @@ int l2_sha1__hex_to_uint32(const char *in, uint32_t *out)
return 0;
}
-#if 0
+#if L2_SHA1_STANDALONE_TEST
int main(int argc, char **argv)
{
if (argc < 2) return 1;
diff --git a/src/l2su.h b/src/l2su.h
index 1746a53..520c756 100644
--- a/src/l2su.h
+++ b/src/l2su.h
@@ -3,8 +3,11 @@
#include "command.h"
#include "instance.h"
+#include "version.h"
#include <fcntl.h>
+#include <time.h>
+#include <curl/curl.h>
struct l2_user_paths {
const char *config;
@@ -18,6 +21,9 @@ struct tag_l2_state_t {
* but for now a doubly-linked list accomplishes our goals */
struct l2_instance *instance_head;
struct l2_instance *instance_tail;
+
+ struct l2_version_remote *ver_remote_head;
+ struct l2_version_remote *ver_remote_tail;
};
extern struct tag_l2_state_t l2_state;
@@ -33,4 +39,20 @@ int l2_launcher_open_config(const char *path, int flags, mode_t mode);
int l2_launcher_mkdir_parents(const char *path);
+char *l2_launcher_parse_iso_time(const char *str, struct tm *ts);
+
+struct l2_dlbuf {
+ void *data;
+ size_t size;
+ size_t capacity;
+};
+
+void l2_launcher_download_init(struct l2_dlbuf *buf);
+void *l2_launcher_download_finalize(struct l2_dlbuf *buf, size_t *psz);
+void l2_launcher_download_cleanup(struct l2_dlbuf *buf);
+//extern size_t (*const l2_dlcb)(char *, size_t, size_t, void *);
+extern const curl_write_callback l2_dlcb;
+
+int l2_launcher_download(CURL *cd, const char *url, void **odata, size_t *osize);
+
#endif /* include guard */
diff --git a/src/launcherutil.c b/src/launcherutil.c
index 8df4a71..f73428a 100644
--- a/src/launcherutil.c
+++ b/src/launcherutil.c
@@ -1,6 +1,7 @@
#include "macros.h"
#include "l2su.h"
+#include <curl/easy.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
@@ -155,3 +156,79 @@ mdcleanup:
return 0;
}
+
+char *l2_launcher_parse_iso_time(const char *str, struct tm *ts)
+{
+ return strptime(str, "%FT%T%z", ts); /* TODO: replace with something portable */
+}
+
+void l2_launcher_download_init(struct l2_dlbuf *buf)
+{
+ buf->data = NULL;
+ buf->size = 0;
+ buf->capacity = 0;
+}
+
+size_t l2_launcher__download_callback(char *data, size_t size, size_t nmemb, void *user)
+{
+ struct l2_dlbuf *buf = user;
+ size_t realsz = size * nmemb;
+
+ if (buf->size + realsz > buf->capacity) {
+ size_t newcap = 16;
+ while (newcap && buf->size + realsz < newcap)
+ newcap <<= 1;
+
+ if (!newcap) return CURLE_WRITE_ERROR;
+
+ void *newbuf = realloc(buf->data, newcap);
+ if (!newbuf) return CURLE_WRITE_ERROR;
+
+ buf->data = newbuf;
+ buf->capacity = newcap;
+ }
+
+ memcpy((char *)buf->data + buf->size, data, realsz);
+ buf->size += realsz;
+ return realsz;
+}
+
+void *l2_launcher_download_finalize(struct l2_dlbuf *buf, size_t *psz)
+{
+ void *smaller = realloc(buf->data, buf->size);
+ if (smaller) buf->data = smaller;
+
+ void *data = buf->data;
+ buf->data = NULL;
+
+ *psz = buf->size;
+ return data;
+}
+
+void l2_launcher_download_cleanup(struct l2_dlbuf *buf)
+{
+ free(buf->data);
+}
+
+const curl_write_callback l2_dlcb = &l2_launcher__download_callback;
+
+int l2_launcher_download(CURL *cd, const char *url, void **odata, size_t *osize)
+{
+ struct l2_dlbuf db;
+
+ l2_launcher_download_init(&db);
+
+ curl_easy_setopt(cd, CURLOPT_URL, url);
+ curl_easy_setopt(cd, CURLOPT_WRITEDATA, &db);
+ curl_easy_setopt(cd, CURLOPT_WRITEFUNCTION, l2_dlcb);
+
+ if (curl_easy_perform(cd) != CURLE_OK) {
+ l2_launcher_download_cleanup(&db);
+ return -1;
+ }
+
+ *odata = l2_launcher_download_finalize(&db, osize);
+ l2_launcher_download_cleanup(&db); /* not strictly necessary but ok */
+
+ return 0;
+}
diff --git a/src/macros.h b/src/macros.h
index 4cf6e7b..5487b12 100644
--- a/src/macros.h
+++ b/src/macros.h
@@ -4,6 +4,20 @@
#include "config.h"
#define L2_ARRLEN(_a) (sizeof(_a) / sizeof(*(_a)))
+#define L2_CSTRLEN(_s) (L2_ARRLEN(_s) - 1)
#define L2_UNUSED(_v) ((void)(_v))
+#include <alloca.h>
+#include <string.h>
+
+#define L2_ASTRCAT2(_var, _s1, _len1, _s2, _len2) do { \
+ _var = alloca(_len1 + _len2 + 1); \
+ memcpy(_var, (_s1), (_len1)); \
+ memcpy(_var + (_len1), (_s2), (_len2)); \
+ (_var)[((_len1) + (_len2))] = '\0'; \
+} while (0)
+
+#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"
+
#endif /* include guard */
diff --git a/src/meson.build b/src/meson.build
index 261148a..0b90ba4 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')
+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')
configure_file(input : 'config.h.in', output : 'config.h', configuration : config_data)
config_include_dir = include_directories('.')
diff --git a/src/version.c b/src/version.c
new file mode 100644
index 0000000..b442f85
--- /dev/null
+++ b/src/version.c
@@ -0,0 +1,327 @@
+#include "version.h"
+#include "digest/digest.h"
+#include "l2su.h"
+#include "macros.h"
+#include "endian.h"
+#include "command.h"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+#include <curl/header.h>
+#include <jansson.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <alloca.h>
+
+const char *const l2_version__messages[] = {
+ "Success",
+ "Malformed version manifest",
+ "Version manifest JSON error",
+ "Allocation failed",
+ "OS error",
+ "An unspecified error has occurred",
+ NULL
+};
+
+#define L2_VERSION_MANIFEST_ETAG_PATH "/.l2_vermanifest_etag"
+#define L2_VERSION_MANIFEST_PATH "version_manifest_v2.json"
+#define L2_VERSION_MANIFEST_EXP_TIME ((time_t)120)
+
+unsigned l2_version__download_manifest(json_t **out_json);
+unsigned l2_version__load_all_from_json(json_t *json);
+
+unsigned l2_version__add_remote(json_t *obj);
+
+unsigned l2_version_load_remote(void)
+{
+ json_t *vers;
+ return l2_version__download_manifest(&vers);
+}
+
+unsigned l2_version_load_local(void)
+{
+ return VERSION_SUCCESS;
+}
+
+char *l2_version__read_etag(time_t *lastmod)
+{
+ size_t dpathlen = strlen(l2_state.paths.data);
+ char *etagpath;
+
+ L2_ASTRCAT2(etagpath, l2_state.paths.data, dpathlen, L2_VERSION_MANIFEST_ETAG_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_ETAG_PATH));
+
+ FILE *etagfp = fopen(etagpath, "rb");
+ char *etag = NULL;
+ uint32_t buf;
+ uint64_t ts;
+
+ if (!etagfp) return NULL;
+ if (fread(&buf, sizeof(uint32_t), 1, etagfp) < sizeof(uint32_t)) goto cleanup;
+ if (fread(&ts, sizeof(uint64_t), 1, etagfp) < sizeof(uint64_t)) goto cleanup;
+
+ buf = l2_betoh32(buf);
+ ts = l2_betoh64(ts);
+
+ etag = calloc(buf + 1, 1);
+ if (!etag) goto cleanup;
+
+ if (fread(etag, 1, buf, etagfp) < buf) goto cleanup;
+ etag[buf] = '\0';
+
+ *lastmod = (time_t)ts;
+
+ fclose(etagfp);
+
+ return etag;
+
+cleanup:
+ free(etag);
+ fclose(etagfp);
+ return NULL;
+}
+
+/* this function does not report errors on failure because its operation is not essential */
+void l2_version__write_etag(time_t modtime, const char *etag)
+{
+ size_t dpathlen = strlen(l2_state.paths.data);
+ char *etagpath;
+
+ L2_ASTRCAT2(etagpath, l2_state.paths.data, dpathlen, L2_VERSION_MANIFEST_ETAG_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_ETAG_PATH));
+
+ uint32_t etaglen = l2_betoh32((uint32_t)strlen(etag));
+ uint64_t mt_write = l2_betoh64((uint64_t)modtime);
+
+ FILE *etagfp = fopen(etagpath, "wb");
+ if (!etagfp) return;
+
+ if (fwrite(&etaglen, sizeof(uint32_t), 1, etagfp) < sizeof(uint32_t)) goto cleanup;
+ if (fwrite(&mt_write, sizeof(uint64_t), 1, etagfp) < sizeof(uint64_t)) goto cleanup;
+ if (fwrite(etag, 1, etaglen, etagfp) < etaglen) goto cleanup;
+
+cleanup:
+ fclose(etagfp);
+}
+
+int l2_version__read_manifest_file(json_t **manifest)
+{
+ size_t dpathlen = strlen(l2_state.paths.data);
+ char *manpath;
+
+ L2_ASTRCAT2(manpath, l2_state.paths.data, dpathlen, L2_VERSION_MANIFEST_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_PATH));
+
+ json_error_t err;
+ json_t *man = json_load_file(manpath, JSON_REJECT_DUPLICATES, &err);
+ if (!man) {
+ CMD_WARN("Error loading manifest file: %s\n", err.text);
+ return -1;
+ }
+
+ *manifest = man;
+ return 0;
+}
+
+#if 0
+unsigned l2_version__download_manifest(json_t **out_versions)
+{
+ unsigned ret = VERSION_SUCCESS;
+ CURL *pc = NULL;
+ FILE *outfile = NULL;
+ void *verdata = NULL;
+ int en;
+
+ struct curl_slist *headers = NULL;
+
+ time_t lastmod;
+ char *etag = l2_version__read_etag(&lastmod);
+
+ if (etag) {
+ if (lastmod + L2_VERSION_MANIFEST_EXP_TIME > time(NULL)) {
+ /* no need to redownload */
+ *out_versions = NULL;
+ return VERSION_SUCCESS;
+ }
+ }
+
+ pc = curl_easy_init();
+ struct l2_dlbuf dlbuf;
+ if (!pc) return VERSION_EUNSPEC;
+
+ l2_launcher_download_init(&dlbuf);
+
+ if (etag) {
+ size_t etaglen = strlen(etag);
+ char *etagheader;
+
+#define H_IFNONEMATCH "If-None-Match: "
+ L2_ASTRCAT2(etagheader, H_IFNONEMATCH, L2_CSTRLEN(H_IFNONEMATCH), etag, etaglen);
+#undef H_IFNONEMATCH
+
+ headers = curl_slist_append(NULL, etagheader);
+ if (!headers) {
+ ret = VERSION_EUNSPEC;
+ goto cleanup;
+ }
+
+ curl_easy_setopt(pc, CURLOPT_HTTPHEADER, headers);
+ }
+
+ curl_easy_setopt(pc, CURLOPT_URL, L2_URL_META_VERSION_MANIFEST);
+ curl_easy_setopt(pc, CURLOPT_WRITEFUNCTION, l2_dlcb);
+ curl_easy_setopt(pc, CURLOPT_WRITEDATA, &dlbuf);
+
+ if (curl_easy_perform(pc) != CURLE_OK) {
+ /* TODO: set error string */
+ ret = VERSION_EUNSPEC;
+ goto cleanup;
+ }
+
+ long rescode = 0;
+ curl_easy_getinfo(pc, CURLINFO_RESPONSE_CODE, &rescode);
+ if (rescode == 200) {
+ char *mfpath;
+ size_t datalen;
+ verdata = l2_launcher_download_finalize(&dlbuf, &datalen);
+
+ L2_ASTRCAT2(mfpath, l2_state.paths.data, pathlen, L2_VERSION_MANIFEST_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_PATH));
+
+ outfile = fopen(mfpath, "w");
+ if (!outfile) {
+ ret = VERSION_ERRNO;
+ goto cleanup;
+ }
+
+ if (fwrite(verdata, 1, datalen, outfile) < datalen) {
+ ret = VERSION_EUNSPEC;
+ goto cleanup;
+ }
+
+ fclose(outfile);
+ outfile = NULL;
+ }
+
+ struct curl_header *ch;
+ if (curl_easy_header(pc, "ETag", 0, CURLH_HEADER | CURLH_TRAILER | CURLH_1XX, 0, &ch) == CURLHE_OK) {
+ l2_version__write_etag(etagpath, time(NULL), ch->value);
+ }
+
+ ret = VERSION_SUCCESS;
+
+cleanup:
+ en = errno;
+
+ if (pc) curl_easy_cleanup(pc);
+ if (headers) curl_slist_free_all(headers);
+ if (outfile) fclose(outfile);
+ free(verdata);
+ l2_launcher_download_cleanup(&dlbuf);
+
+ errno = en;
+ return ret;
+}
+#endif
+
+/* parses an instance of version_manifest_v2.json */
+unsigned l2_version__load_all_from_json(json_t *json)
+{
+ if (!json_is_object(json)) {
+ return VERSION_EFORMAT;
+ }
+
+ json_t *latest = json_object_get(json, "latest");
+ json_t *versions = json_object_get(json, "versions");
+
+ if (!json_is_object(latest) || !json_is_array(versions)) {
+ return VERSION_EFORMAT;
+ }
+
+ const char *latestrel = json_string_value(json_object_get(latest, "release"));
+ const char *latestsnap = json_string_value(json_object_get(latest, "snapshot"));
+ size_t index;
+ json_t *value;
+ unsigned res;
+
+ if (!latestrel || !latestsnap) {
+ return VERSION_EFORMAT;
+ }
+
+ json_array_foreach(versions, index, value) {
+ if ((res = l2_version__add_remote(value)) != VERSION_SUCCESS)
+ return res;
+ }
+
+ return VERSION_SUCCESS;
+}
+
+unsigned l2_version__add_remote(json_t *js)
+{
+ struct l2_version_remote *ver = NULL;
+ struct tm ts;
+ json_t *val;
+
+ unsigned res = VERSION_SUCCESS;
+
+ if (!json_is_object(js)) {
+ return VERSION_EFORMAT;
+ }
+
+ ver = calloc(1, sizeof(struct l2_version_remote));
+ if (!ver) return VERSION_EALLOC;
+
+ res = VERSION_EFORMAT;
+
+ val = json_object_get(js, "id");
+ if (!json_is_string(val)) goto cleanup;
+ ver->id = strdup(json_string_value(val));
+
+ val = json_object_get(js, "type");
+ if (!json_is_string(val)) goto cleanup;
+ ver->type = strdup(json_string_value(val));
+
+ val = json_object_get(js, "url");
+ if (!json_is_string(val)) goto cleanup;
+ ver->url = strdup(json_string_value(val));
+
+ val = json_object_get(js, "sha1");
+ if (!json_is_string(val)) goto cleanup;
+ if (l2_sha1_digest_from_hex(&ver->sha1, json_string_value(val)) < 0)
+ goto cleanup;
+
+ val = json_object_get(js, "complianceLevel");
+ if (!json_is_number(val)) goto cleanup;
+ ver->compliance_level = json_integer_value(val);
+
+ val = json_object_get(js, "time");
+ if (!json_is_string(val)) goto cleanup;
+ memset(&ts, 0, sizeof(struct tm));
+ if (!l2_launcher_parse_iso_time(json_string_value(val), &ts)) goto cleanup;
+ ver->update_time = mktime(&ts);
+
+ val = json_object_get(js, "releaseTime");
+ if (!json_is_string(val)) goto cleanup;
+ memset(&ts, 0, sizeof(struct tm));
+ if (!l2_launcher_parse_iso_time(json_string_value(val), &ts)) goto cleanup;
+ ver->release_time = mktime(&ts);
+
+ /* add the thing to the global list */
+ if (l2_state.ver_remote_tail) {
+ l2_state.ver_remote_tail->next = ver;
+ ver->prev = l2_state.ver_remote_tail;
+ } else {
+ l2_state.ver_remote_head = l2_state.ver_remote_tail = ver;
+ }
+
+ return VERSION_SUCCESS;
+
+cleanup:
+ if (ver) {
+ free(ver->id);
+ free(ver->type);
+ free(ver->url);
+ }
+
+ free(ver);
+
+ return res;
+}
diff --git a/src/version.h b/src/version.h
index b3f640f..246e346 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,10 +1,38 @@
#ifndef L2SU_VERSION_H_INCLUDED
#define L2SU_VERSION_H_INCLUDED
+#include "digest/digest.h"
+#include <time.h>
+#include <jansson.h>
+
+enum {
+ VERSION_SUCCESS,
+ VERSION_EFORMAT,
+ VERSION_EJSON,
+ VERSION_EALLOC,
+ VERSION_ERRNO,
+ VERSION_EUNSPEC
+};
+
struct l2_version_remote {
-
+ struct l2_version_remote *next;
+ struct l2_version_remote *prev;
+
+ char *id;
+ char *type;
+ char *url;
+
+ l2_sha1_digest_t sha1;
+ json_int_t compliance_level;
+
+ time_t update_time;
+ time_t release_time;
};
+extern const char *const l2_version__messages[];
+
+#define l2_version_strerror(_en) l2_version__messages[(_en)]
+
unsigned l2_version_load_remote(void);
unsigned l2_version_load_local(void);