From 11672146b9bf67cae05e8e3207c9bd1e47d52220 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Fri, 5 Jan 2024 04:53:20 -0600 Subject: extracts natives --- meson.build | 11 ++- src/args.c | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/args.h | 13 ++++ src/assets.c | 14 ++-- src/cmd-version.c | 32 +++++++++ src/config.h.in | 4 ++ src/l2su.h | 2 + src/launch.c | 31 ++++++++ src/launcherutil.c | 8 +++ src/macros.h | 5 +- src/meson.build | 2 +- src/version.c | 148 ++++++++++++++++++++++++++++++++++++-- src/version.h | 11 ++- 13 files changed, 467 insertions(+), 20 deletions(-) create mode 100644 src/args.c create mode 100644 src/args.h create mode 100644 src/launch.c diff --git a/meson.build b/meson.build index 0b18d02..c5588b7 100644 --- a/meson.build +++ b/meson.build @@ -4,6 +4,7 @@ add_global_arguments('-D_XOPEN_SOURCE=700', '-DPCRE2_CODE_UNIT_WIDTH=8', languag curl_dep = dependency('libcurl') jansson_dep = dependency('jansson') pcre2_dep = dependency('libpcre2-8') +libzip_dep = dependency('libzip') config_data = configuration_data() @@ -82,6 +83,14 @@ endif message('Using launcher OS', launcher_os) config_data.set_quoted('L2SU_LAUNCHER_OS', launcher_os) +if launcher_os == 'linux' + config_data.set('L2SU_LAUNCHER_IS_LINUX', true) +elif launcher_os == 'osx' + config_data.set('L2SU_LAUNCHER_IS_OSX', true) +elif launcher_os == 'windows' + config_data.set('L2SU_LAUNCHER_IS_WINDOWS', true) +endif + message('Using launcher arch bits', launcher_arch_bits) config_data.set_quoted('L2SU_LAUNCHER_ARCH_BITS', launcher_arch_bits) @@ -96,4 +105,4 @@ if launcher_jre_arch == 'gamecore' endif subdir('src') -executable('l2su', launcher_srcs, dependencies : [curl_dep, jansson_dep, pcre2_dep], include_directories : [config_include_dir], override_options : {'c_std': 'c99'}) +executable('l2su', launcher_srcs, dependencies : [curl_dep, jansson_dep, pcre2_dep, libzip_dep], include_directories : [config_include_dir], override_options : {'c_std': 'c99'}) diff --git a/src/args.c b/src/args.c new file mode 100644 index 0000000..7b307a8 --- /dev/null +++ b/src/args.c @@ -0,0 +1,206 @@ +#include "args.h" +#include "l2su.h" +#include "instance.h" +#include "version.h" +#include "macros.h" + +#include +#include +#include + +int l2_args__get_game_args(l2_subst_t *subst, const char *argsline, l2_version_feature_match_proc_t *feature_matcher, char ***out_args, size_t *out_nargs); +int l2_args__get_args_json(l2_subst_t *subst, json_t *jargs, l2_version_feature_match_proc_t *feature_matcher, char ***out_args, size_t *out_nargs); + +int l2_args_get_game_args(json_t *version, l2_subst_t *subst, l2_version_feature_match_proc_t *feature_matcher, char ***out_args, size_t *out_nargs) +{ + const char *mc_args = NULL; + json_t *jargs = NULL; + + if (json_unpack(version, "{s?:{s:o}, s?:s}", "arguments", "game", &jargs, "minecraftArguments", &mc_args) < 0) { + return -1; + } + + if (jargs) { + if (!json_is_array(jargs)) return -1; + + + return l2_args__get_args_json(subst, jargs, feature_matcher, out_args, out_nargs); + } else if (mc_args) { + return l2_args__get_game_args(subst, mc_args, feature_matcher, out_args, out_nargs); + } else { + return -1; + } +} + +int L2_SENTINEL l2_args__array_append(l2_subst_t *sp, char ***arr, size_t *nargs, ...) +{ + size_t nadd = 0; + const char *to_add; + + va_list parg; + va_start(parg, nargs); + do { + to_add = va_arg(parg, const char *); + ++nadd; + } while (to_add); + --nadd; + va_end(parg); + + char **temp = realloc(*arr, (*nargs + nadd) * sizeof(char *)); + if (!temp) return -1; + *arr = temp; + + memset(temp + *nargs, 0, sizeof(char *) * nadd); + + va_start(parg, nargs); + + for (; nadd; --nadd) { + if (l2_subst_apply(sp, va_arg(parg, const char *), temp + (*nargs)++) < 0) goto cleanup; + } + + va_end(parg); + return 0; + +cleanup: + va_end(parg); + return -1; +} + +int l2_args__get_game_args(l2_subst_t *subst, const char *argsline, l2_version_feature_match_proc_t *feature_matcher, char ***out_args, size_t *out_nargs) +{ + char **args = NULL; + size_t nargs = 0; + + char *arg_start; + size_t sz; + L2_ASTRDUP(arg_start, sz, argsline); + + for (char *argcur = arg_start; *argcur; ++argcur) { + if (*argcur == ' ') { + *argcur = '\0'; + if (l2_args__array_append(subst, &args, &nargs, arg_start, NULL) < 0) goto cleanup; + arg_start = argcur + 1; + } + } + + if (*arg_start && l2_args__array_append(subst, &args, &nargs, arg_start, NULL) < 0) goto cleanup; + + if ((*feature_matcher)("is_demo_user", json_true())) { + if (l2_args__array_append(subst, &args, &nargs, "--demo", NULL) < 0) goto cleanup; + } + + if ((*feature_matcher)("has_custom_resolution", json_true())) { + if (l2_args__array_append(subst, &args, &nargs, "--width", "${resolution_width}", "--height", "${resolution_height}", NULL) < 0) goto cleanup; + } + + *out_args = args; + *out_nargs = nargs; + return 0; + +cleanup: + for (char **cur = args; nargs; --nargs, ++cur) { + free(*cur); + } + + free(args); + + return -1; +} + +int l2_args_get_jvm_args(json_t *version, l2_subst_t *subst, l2_version_feature_match_proc_t *feature_matcher, char ***out_args, size_t *out_nargs) +{ + json_t *jarguments = json_object_get(version, "arguments"); + if (!jarguments) { + char **args = NULL; + size_t nargs = 0; + + if (l2_args__array_append(subst, &args, &nargs, +#ifdef L2SU_LAUNCHER_IS_WINDOWS + "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump", +#endif +#ifdef L2SU_LAUNCHER_IS_OSX + "-Xdock:icon=${asset=icons/minecraft.icns}", + "-Xdock:name=Minecraft", +#endif + "-Djava.library.path=${natives_directory}", + "-Dminecraft.launcher.brand=${launcher_name}", + "-Dminecraft.launcher.version=${launcher_version}", + "-Dminecraft.client.jar=${primary_jar}", + "-cp", + "${classpath}", + NULL) < 0) { + return -1; + } + + *out_args = args; + *out_nargs = nargs; + + return 0; + } else if (!json_is_object(jarguments)) { + return -1; + } + + json_t *jjvmargs = json_object_get(jarguments, "jvm"); + if (!json_is_array(jjvmargs)) { + return -1; + } + + return l2_args__get_args_json(subst, jjvmargs, feature_matcher, out_args, out_nargs); +} + +int l2_args__get_args_json(l2_subst_t *subst, json_t *jargs, l2_version_feature_match_proc_t *feature_matcher, char ***out_args, size_t *out_nargs) +{ + char **args = NULL; + size_t nargs = 0; + + size_t idx; + json_t *value; + + json_array_foreach(jargs, idx, value) { + if (json_is_string(value)) { + if (l2_args__array_append(subst, &args, &nargs, json_string_value(value), NULL) < 0) goto cleanup; + } else if (json_is_object(value)) { + json_t *rules; + json_t *values; + if (json_unpack(value, "{s:o, s:o}", "rules", &rules, "value", &values) < 0) { + goto cleanup; + } + + if (!json_is_array(rules)) goto cleanup; + + switch (l2_version_check_rules(rules, feature_matcher)) { + case RULE_CHECK_ALLOW: { + json_t *argval; + size_t argidx; + if (json_is_string(values)) { + if (l2_args__array_append(subst, &args, &nargs, json_string_value(values), NULL) < 0) goto cleanup; + } else { + /* this code strongly prefers brevity over performance (breaking news) */ + json_array_foreach(values, argidx, argval) { + if (!json_is_string(argval)) goto cleanup; + if (l2_args__array_append(subst, &args, &nargs, json_string_value(argval), NULL) < 0) goto cleanup; + } + } + break; + } + case RULE_CHECK_ERROR: + goto cleanup; + default: + continue; + } + } + } + + *out_args = args; + *out_nargs = nargs; + return 0; + +cleanup: + for (char **cur = args; nargs; --nargs, ++cur) { + free(*cur); + } + + free(args); + + return -1; +} diff --git a/src/args.h b/src/args.h new file mode 100644 index 0000000..3f15c61 --- /dev/null +++ b/src/args.h @@ -0,0 +1,13 @@ +#ifndef L2SU_ARGS_H_INCLUDED +#define L2SU_ARGS_H_INCLUDED + +#include +#include + +#include "l2su.h" +#include "version.h" + +int l2_args_get_game_args(json_t *version, l2_subst_t *subst, l2_version_feature_match_proc_t *feature_matcher, char ***out_args, size_t *out_nargs); +int l2_args_get_jvm_args(json_t *version, l2_subst_t *subst, l2_version_feature_match_proc_t *feature_matcher, char ***out_args, size_t *out_nargs); + +#endif /* include guard */ diff --git a/src/assets.c b/src/assets.c index 7699d33..695e0ab 100644 --- a/src/assets.c +++ b/src/assets.c @@ -28,7 +28,9 @@ struct l2_assets__download_info { l2_sha1_state_t digest_state; size_t recv_size; +#ifndef NDEBUG char errbuf[CURL_ERROR_SIZE]; +#endif struct l2_assets__download_info *next; }; @@ -64,12 +66,14 @@ int l2_assets__download_asset_init(CURL *curl, void *user) return -1; } - CMD_DEBUG("asset url %s", info->url); - curl_easy_setopt(curl, CURLOPT_URL, info->url); curl_easy_setopt(curl, CURLOPT_WRITEDATA, info); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &l2_assets__download_asset_dlcb); + +#ifndef NDEBUG + memset(&info->errbuf, 0, sizeof(info->errbuf)); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, info->errbuf); +#endif return 0; } @@ -84,7 +88,9 @@ int l2_assets__download_asset_complete(CURL *curl, CURLcode code, void *user) if (code != CURLE_OK) { CMD_WARN("Failed to download asset %s: %s", info->name, curl_easy_strerror(code)); - CMD_DEBUG("Info for %s: %s", info->name, info->errbuf); +#ifndef NDEBUG + CMD_DEBUG("Debug info for %s: %s", info->name, info->errbuf); +#endif return -1; } @@ -238,6 +244,7 @@ int l2_assets__download_assets(json_t *asset_index, char **path) res = -1; goto cleanup; } + CMD_INFO0("Successfully downloaded assets."); } else { CMD_INFO0("No assets to download."); res = 0; @@ -261,7 +268,6 @@ int l2_assets__download_assets(json_t *asset_index, char **path) *path = resbasepath; resbasepath = NULL; /* prevent out variable from being freed */ - CMD_INFO0("Successfully downloaded assets."); res = 0; cleanup: diff --git a/src/cmd-version.c b/src/cmd-version.c index bb02d90..4ee4309 100644 --- a/src/cmd-version.c +++ b/src/cmd-version.c @@ -4,6 +4,7 @@ #include "version.h" #include "l2su.h" #include "macros.h" +#include "args.h" #include #include @@ -87,6 +88,37 @@ unsigned cmd_version_install(struct l2_context_node *ctx, char **args) CMD_FATAL0("Failed to download assets."); } + char *nativesdir = NULL; + CMD_INFO0("Extracting natives..."); + if ((res = l2_version_extract_natives(libs, &nativesdir)) != VERSION_SUCCESS) { + CMD_FATAL("Failed to extract natives: %s", l2_version_strerror(res)); + } + + char **game_args; + size_t ngame_args; + l2_subst_t *st; + l2_subst_init(&st); + l2_subst_add(st, "version_name", "1.8.9"); + if (l2_args_get_game_args(js, st, &feat_match_cb, &game_args, &ngame_args) < 0) { + CMD_FATAL0("Failed to find game arguments"); + } + for (size_t n = 0; n < ngame_args; ++n) { + CMD_DEBUG("arg: %s", game_args[n]); + free(game_args[n]); + } + free(game_args); + + if (l2_args_get_jvm_args(js, st, &feat_match_cb, &game_args, &ngame_args) < 0) { + CMD_FATAL0("Failed to find JVM arguments"); + } + for (size_t n = 0; n < ngame_args; ++n) { + CMD_DEBUG("jvm arg: %s", game_args[n]); + free(game_args[n]); + } + free(game_args); + + l2_subst_free(st); + CMD_INFO("Assets base: %s", assetsbase); free(assetsbase); diff --git a/src/config.h.in b/src/config.h.in index ffb2c41..5584e92 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -12,4 +12,8 @@ #mesondefine L2SU_LAUNCHER_ARCH #mesondefine L2SU_JRE_ARCH +#mesondefine L2SU_LAUNCHER_IS_LINUX +#mesondefine L2SU_LAUNCHER_IS_OSX +#mesondefine L2SU_LAUNCHER_IS_WINDOWS + #endif /* include guard */ diff --git a/src/l2su.h b/src/l2su.h index 8c77e14..e46aa12 100644 --- a/src/l2su.h +++ b/src/l2su.h @@ -11,6 +11,7 @@ #include #include #include +#include struct l2_user_paths { const char *config; @@ -33,6 +34,7 @@ extern struct tag_l2_state_t l2_state; /* homecooked string methods */ char *l2_launcher_strapp(char *target, const char *src); +bool l2_launcher_strpre(const char *pre, const char *text); char *l2_launcher_sprintf_alloc(const char *fmt, ...) L2_FORMAT(printf, 1, 2); diff --git a/src/launch.c b/src/launch.c new file mode 100644 index 0000000..cc0721f --- /dev/null +++ b/src/launch.c @@ -0,0 +1,31 @@ +#include "l2su.h" + +/* TODO launch struct */ + +int l2_launch_fill_substitutor(l2_subst_t *st, void *reserved, json_t *version, struct l2_instance *inst) +{ +#define L2_LAUNCH_ADD_SUB(_st, _name, _val) \ + if (l2_subst_add(_st, _name, _val) < 0) return -1 + + const char *ver_name; + if (json_unpack(version, "{s:s}", "id", &ver_name) < 0) { + return -1; + } + + /* auth_access_token */ + /* user_properties */ + /* user_property_map */ + L2_LAUNCH_ADD_SUB(st, "auth_session", "-"); + + L2_LAUNCH_ADD_SUB(st, "auth_player_name", "figboot"); + L2_LAUNCH_ADD_SUB(st, "auth_uuid", "00000000000000000000000000000000"); + L2_LAUNCH_ADD_SUB(st, "user_type", "legacy"); + + L2_LAUNCH_ADD_SUB(st, "profile_name", inst->name); + L2_LAUNCH_ADD_SUB(st, "version_name", ver_name); + L2_LAUNCH_ADD_SUB(st, "game_directory", "."); + + + return 0; +#undef L2_LAUNCH_ADD_SUB +} diff --git a/src/launcherutil.c b/src/launcherutil.c index 2db6238..e1e8c40 100644 --- a/src/launcherutil.c +++ b/src/launcherutil.c @@ -36,6 +36,14 @@ char *l2_launcher_strapp(char *buf, const char *src) return ret; } +bool l2_launcher_strpre(const char *pre, const char *text) +{ + while (*pre) { + if (*(pre++) != *(text++)) return false; + } + return true; +} + char *l2_launcher_sprintf_alloc(const char *fmt, ...) { va_list pva; diff --git a/src/macros.h b/src/macros.h index 91924ca..668816c 100644 --- a/src/macros.h +++ b/src/macros.h @@ -46,7 +46,10 @@ #define L2_URL_RESOURCES_BASE "https://resources.download.minecraft.net" #ifdef __GNUC__ -#define L2_FORMAT(_flavor, _stridx, _argidx) __attribute__((format (_flavor, _stridx, _argidx))) +#define L2_GNU_ATTRIBUTE(_x) __attribute__(_x) + +#define L2_FORMAT(_flavor, _stridx, _argidx) L2_GNU_ATTRIBUTE((format (_flavor, _stridx, _argidx))) +#define L2_SENTINEL L2_GNU_ATTRIBUTE((sentinel)) #else #define L2_FORMAT(_unused1, _unused2, _unused3) #endif diff --git a/src/meson.build b/src/meson.build index 86728a6..9c9e545 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', 'subst.c', 'downloadpool.c', 'assets.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', 'downloadpool.c', 'assets.c', 'args.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 index 3568cf2..af7b56e 100644 --- a/src/version.c +++ b/src/version.c @@ -17,6 +17,7 @@ #include #include #include +#include const char *const l2_version__messages[] = { "Success", @@ -27,6 +28,7 @@ const char *const l2_version__messages[] = { "Error downloading version", "Version not found", "Max recursion depth exceeded", + "JAR format error", NULL }; @@ -971,13 +973,7 @@ unsigned l2_version__gen_one_library(json_t *lib, l2_subst_t *subst, struct l2_v goto cleanup; } - if (classifier) { - retlib->extract.extracttype = EXTRACT_OLD; - } else if (has_class) { - retlib->extract.extracttype = EXTRACT_NEW; - } else { - retlib->extract.extracttype = EXTRACT_NONE; - } + retlib->extract.extract = !!json_is_object(json_object_get(lib, "natives")); /* first, find the URL of the library */ json_t *jurl = json_object_get(lib, "url"); @@ -1367,3 +1363,141 @@ unsigned l2_version_download_jar(json_t *version, const char *specifier, char ** *path = pathstr; return VERSION_SUCCESS; } + +unsigned l2_version__extract_one_file(zip_t *archive, const char *dbg_libname, char **exclude, const char *template, zip_int64_t idx) +{ +#define EXTRACT_READBUF_SIZE (4096) + unsigned res = VERSION_SUCCESS; + char *fname; + size_t sz; + + FILE *ofile = NULL; + zip_file_t *file = NULL; + unsigned char buf[EXTRACT_READBUF_SIZE]; + + const char *entname = zip_get_name(archive, idx, ZIP_FL_ENC_GUESS); + if (!entname) { + CMD_WARN("Failed to get entry name in library %s: %s", dbg_libname, zip_strerror(archive)); + res = VERSION_EZIP; + goto cleanup; + } + + if (exclude) { + for (char **cur = exclude; *cur; ++cur) { + if (l2_launcher_strpre(*cur, entname)) { + res = VERSION_SUCCESS; + goto cleanup; + } + } + } + + file = zip_fopen_index(archive, idx, 0); + if (!file) { + CMD_WARN("Failed to open entry in library %s: %s", dbg_libname, zip_strerror(archive)); + res = VERSION_EZIP; + goto cleanup; + } + + L2_ASPRINTF(fname, sz, "%s/%s", template, entname); + if (l2_launcher_mkdir_parents_ex(fname, 1) < 0) { /* TODO: maybe don't repeatedly check if /tmp exists lol */ + CMD_WARN("Failed to extract entry %s from library %s: error making directory", entname, dbg_libname); + res = VERSION_ERRNO; + goto cleanup; + } + + ofile = fopen(fname, "wb"); + if (!ofile) { + CMD_WARN("Failed to open %s to extract %s from library %s: %s", fname, entname, dbg_libname, strerror(errno)); + res = VERSION_ERRNO; + goto cleanup; + } + + zip_int64_t nread; + while ((nread = zip_fread(file, buf, EXTRACT_READBUF_SIZE)) > 0) { + if (fwrite(buf, 1, nread, ofile) < (size_t)nread) { + CMD_WARN("Partial write while extracting %s from library %s", entname, dbg_libname); + res = VERSION_ERRNO; + goto cleanup; + } + } + + CMD_DEBUG("Extracted %s for library %s", entname, dbg_libname); + res = VERSION_SUCCESS; + +cleanup: + if (file) zip_fclose(file); + if (ofile) fclose(ofile); + return res; + +#undef EXTRACT_READBUF_SIZE +} + +unsigned l2_version__extract_lib_natives(struct l2_version_library *lib, const char *template) +{ + int err = 0; + unsigned res = VERSION_SUCCESS; + zip_t *archive = zip_open(lib->fullpath, ZIP_RDONLY, &err); + + if (!archive) { + zip_error_t ze; + zip_error_init_with_code(&ze, err); + CMD_WARN("Failed to open library jar %s: %s", lib->artifactpath, zip_error_strerror(&ze)); + zip_error_fini(&ze); + return VERSION_EZIP; + } + + zip_int64_t nentry = zip_get_num_entries(archive, 0); + for (zip_int64_t idx = 0; idx < nentry; ++idx) { + if ((res = l2_version__extract_one_file(archive, lib->artifactpath, lib->extract.exclude, template, idx)) != VERSION_SUCCESS) { + goto cleanup; + } + } + +cleanup: + if (archive) { + zip_discard(archive); + } + + return res; +} + +unsigned l2_version_extract_natives(struct l2_version_library *libs, char **out_nativesdir) +{ + char template[] = "/tmp/l2su-natives-XXXXXX"; /* TODO: make portable */ + char *nativesdir; + + for (struct l2_version_library *cur = libs; cur; cur = cur->next) { + if (cur->extract.extract) goto found; + } + + CMD_INFO0("Not extracting natives, there are no libraries that require it."); + return 0; + +found: + + if (!mkdtemp(template)) { + CMD_WARN("Failed to create directory for extracted natives: %s", strerror(errno)); + return VERSION_ERRNO; + } + + nativesdir = strdup(template); + if (!nativesdir) { + return VERSION_EALLOC; + } + + unsigned res; + for (struct l2_version_library *cur = libs; cur; cur = cur->next) { + if (!cur->extract.extract) continue; + if ((res = l2_version__extract_lib_natives(cur, template)) != VERSION_SUCCESS) { + goto cleanup; + } + } + + res = VERSION_SUCCESS; + *out_nativesdir = nativesdir; + nativesdir = NULL; + +cleanup: + free(nativesdir); + return res; +} diff --git a/src/version.h b/src/version.h index c008481..89341cb 100644 --- a/src/version.h +++ b/src/version.h @@ -14,7 +14,8 @@ enum { VERSION_ERRNO, VERSION_EDOWNLOAD, VERSION_ENOTFOUND, - VERSION_ERECURSE + VERSION_ERECURSE, + VERSION_EZIP }; struct l2_version_remote { @@ -64,11 +65,7 @@ struct l2_version_library { 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; + bool extract; char **exclude; } extract; @@ -101,4 +98,6 @@ void l2_version_free_libraries(struct l2_version_library *libs); unsigned l2_version_download_jar(json_t *version, const char *specifier, char **path); +unsigned l2_version_extract_natives(struct l2_version_library *libs, char **nativesdir); + #endif /* include guard */ -- cgit v1.2.3-70-g09d2