diff options
| author | 2024-01-06 06:07:57 -0600 | |
|---|---|---|
| committer | 2024-01-06 06:07:57 -0600 | |
| commit | 2d7d52542df9b925fb0d7786e828421cd731b82b (patch) | |
| tree | 15c90c29ea5c0f9d1c5111d7a6a25201895c8cb1 | |
| parent | launch info all in one place (diff) | |
launches the game
| -rw-r--r-- | meson.build | 7 | ||||
| -rw-r--r-- | src/cmd-version.c | 13 | ||||
| -rw-r--r-- | src/jniwrap.c | 92 | ||||
| -rw-r--r-- | src/jniwrap.h | 23 | ||||
| -rw-r--r-- | src/launch.c | 336 | ||||
| -rw-r--r-- | src/launch.h | 8 | ||||
| -rw-r--r-- | src/meson.build | 2 |
7 files changed, 448 insertions, 33 deletions
diff --git a/meson.build b/meson.build index d6093e0..b398cb3 100644 --- a/meson.build +++ b/meson.build @@ -1,10 +1,13 @@ -project('l2su', 'c', default_options : {'b_ndebug': 'if-release'}) +project('l2su', 'c', 'java', default_options : {'b_ndebug': 'if-release'}) add_global_arguments('-D_XOPEN_SOURCE=700', '-DPCRE2_CODE_UNIT_WIDTH=8', language : 'c') curl_dep = dependency('libcurl') jansson_dep = dependency('jansson') pcre2_dep = dependency('libpcre2-8') libzip_dep = dependency('libzip') +libjni_dep = dependency('jni', version : '>= 1.8.0', modules : [ 'jvm' ]).partial_dependency( + compile_args : true, + includes : true) config_data = configuration_data() @@ -117,4 +120,4 @@ if launcher_jre_arch == 'gamecore' endif subdir('src') -executable('l2su', launcher_srcs, dependencies : [curl_dep, jansson_dep, pcre2_dep, libzip_dep], include_directories : [config_include_dir], override_options : {'c_std': 'c99'}) +executable('l2su', launcher_srcs, dependencies : [curl_dep, jansson_dep, pcre2_dep, libzip_dep, libjni_dep], include_directories : [config_include_dir], override_options : {'c_std': 'c99'}) diff --git a/src/cmd-version.c b/src/cmd-version.c index adb501f..fbf87b1 100644 --- a/src/cmd-version.c +++ b/src/cmd-version.c @@ -46,7 +46,6 @@ bool feat_match_cb(const char *name, json_t *js, void *unused) { return false; } -void l2_launch__test(struct l2_launch *launch); unsigned cmd_version_install(struct l2_context_node *ctx, char **args) { unsigned res = l2_version_load_remote(); @@ -63,7 +62,17 @@ unsigned cmd_version_install(struct l2_context_node *ctx, char **args) CMD_FATAL0("Failed to launch the game"); } - l2_launch__test(&launch); + if (l2_launch_init_substitutor(&launch) < 0) { + CMD_FATAL0("Failed to initialize argument substitutor"); + } + + if (l2_launch_init_args(&launch) < 0) { + CMD_FATAL0("Failed to set JVM and game arguments"); + } + + if (l2_launch_jni(&launch) < 0) { + CMD_FATAL0("Failed to launch the game."); + } l2_launch_free_contents(&launch); diff --git a/src/jniwrap.c b/src/jniwrap.c new file mode 100644 index 0000000..ac796b1 --- /dev/null +++ b/src/jniwrap.c @@ -0,0 +1,92 @@ +#define L2_JNI__NO_DEFINE_PROXIES + +#include "config.h" +#include "jniwrap.h" +#include "macros.h" +#include "command.h" + +#include <stddef.h> +#include <dlfcn.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +/* this isn't even a little bit thread safe but it is what it is */ + +jint JNICALL l2_JNI__GetDefaultJavaVMInitArgs_load(void *args); +jint JNICALL l2_JNI__CreateJavaVM_load(JavaVM **pvm, void **penv, void *args); + +l2_JNI__GetDefaultJavaVMInitArgs_t *l2_JNI__GetDefaultJavaVMInitArgs_call = &l2_JNI__GetDefaultJavaVMInitArgs_load; +l2_JNI__CreateJavaVM_t *l2_JNI__CreateJavaVM_call = &l2_JNI__CreateJavaVM_load; + +void *volatile l2_JNI__jnimod = NULL; + +int l2_jni__try_load(const char *path) +{ + struct stat st; + if (stat(path, &st) < 0) { + CMD_ERROR("Could not stat %s: %s", path, strerror(errno)); + return -1; + } + + void *thelib = dlopen(path, RTLD_LAZY); + if (!thelib) { + CMD_ERROR("Failed to load JVM library %s: %s", path, dlerror()); + return -1; + } + + l2_JNI__jnimod = thelib; + return 0; +} + +int l2_jni_init(const char *modpath) +{ + if (l2_JNI__jnimod) return 0; + + if (l2_jni__try_load(modpath) < 0) { + CMD_ERROR0("Could not load JVM library."); + return -1; + } + + return 0; +} + +typedef void (l2_jni__fsym_unspec)(void); + +l2_jni__fsym_unspec *l2_jni__try_load_sym(const char *sym) +{ + void *themod = l2_JNI__jnimod; + if (!themod) { + CMD_MSG("bug", "JNI function %s called before l2_jni_init!", sym); + abort(); + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + l2_jni__fsym_unspec *thesym = dlsym(themod, sym); +#pragma GCC diagnostic pop + + if (!thesym) { + CMD_ERROR("Could not load JNI symbol %s: %s", sym, dlerror()); + abort(); + } + + return thesym; +} + +jint JNICALL l2_JNI__GetDefaultJavaVMInitArgs_load(void *args) +{ + l2_JNI__GetDefaultJavaVMInitArgs_t *thesym = (l2_JNI__GetDefaultJavaVMInitArgs_t *)l2_jni__try_load_sym("JNI_GetDefaultJavaVMInitArgs"); + l2_JNI__GetDefaultJavaVMInitArgs_call = thesym; + + return (*thesym)(args); +} + +jint JNICALL l2_JNI__CreateJavaVM_load(JavaVM **pvm, void **penv, void *args) +{ + l2_JNI__CreateJavaVM_t *thesym = (l2_JNI__CreateJavaVM_t *)l2_jni__try_load_sym("JNI_CreateJavaVM"); + l2_JNI__CreateJavaVM_call = thesym; + + return (*thesym)(pvm, penv, args); +} diff --git a/src/jniwrap.h b/src/jniwrap.h new file mode 100644 index 0000000..e3406aa --- /dev/null +++ b/src/jniwrap.h @@ -0,0 +1,23 @@ +#ifndef L2SU_JNIWRAP_H_INCLUDED +#define L2SU_JNIWRAP_H_INCLUDED + +#include <jni.h> + +/* internal stuff */ + +typedef jint (l2_JNI__GetDefaultJavaVMInitArgs_t)(void *args) JNICALL; +typedef jint (l2_JNI__CreateJavaVM_t)(JavaVM **pvm, void **penv, void *args) JNICALL; + +extern l2_JNI__GetDefaultJavaVMInitArgs_t *l2_JNI__GetDefaultJavaVMInitArgs_call; +extern l2_JNI__CreateJavaVM_t *l2_JNI__CreateJavaVM_call; + +/* actual interface */ + +#ifndef L2_JNI__NO_DEFINE_PROXIES +#define JNI_GetDefaultJavaVMInitArgs (*l2_JNI__GetDefaultJavaVMInitArgs_call) +#define JNI_CreateJavaVM (*l2_JNI__CreateJavaVM_call) +#endif + +int l2_jni_init(const char *java_home); + +#endif /* include guard */ diff --git a/src/launch.c b/src/launch.c index 6ac2ff7..7d1c2b5 100644 --- a/src/launch.c +++ b/src/launch.c @@ -1,3 +1,5 @@ +#include "config.h" +#include "jniwrap.h" #include "l2su.h" #include "launch.h" #include "macros.h" @@ -8,8 +10,11 @@ #include <stdlib.h> #include <stdarg.h> #include <stdio.h> +#include <unistd.h> bool l2_launch__check_feature(const char *name, json_t *val, void *user); +int l2_launch_init_substitutor(struct l2_launch *launch); +int l2_launch__build_classpath(struct l2_launch *launch, char **pcp); int l2_launch_init(struct l2_launch *launch, const char *vername, struct l2_instance *inst) { @@ -23,13 +28,6 @@ int l2_launch_init(struct l2_launch *launch, const char *vername, struct l2_inst char *nativesdir = NULL; char *main_class_dup = NULL; - l2_subst_t *subst = NULL; - - if (l2_subst_init(&subst) < 0) { - CMD_ERROR0("Failed to create argument substitutor"); - goto cleanup; - } - CMD_INFO("Attempting to load version %s...", vername); if ((res = l2_version_load_local(vername, &verjson)) != VERSION_SUCCESS) { CMD_ERROR("Failed to load version: %s", l2_version_strerror(res)); @@ -89,7 +87,7 @@ int l2_launch_init(struct l2_launch *launch, const char *vername, struct l2_inst CMD_ERROR("Failed to extract natives: %s", l2_version_strerror(res)); goto cleanup; } - + launch->instance = inst; launch->version = verjson; launch->libraries = libs; @@ -98,8 +96,9 @@ int l2_launch_init(struct l2_launch *launch, const char *vername, struct l2_inst launch->assets_root = assetrootpath; launch->virtual_assets = assetpath; launch->natives = nativesdir; - launch->arg_subst = subst; + launch->arg_subst = NULL; launch->main_class = main_class_dup; + launch->classpath = NULL; return 0; @@ -112,7 +111,6 @@ cleanup: free(assetpath); free(nativesdir); free(main_class_dup); - if (subst) l2_subst_free(subst); return -1; } @@ -128,6 +126,17 @@ void l2_launch_free_contents(struct l2_launch *launch) free(launch->natives); if (launch->arg_subst) l2_subst_free(launch->arg_subst); free(launch->main_class); + + for (size_t s = 0; s < launch->njvm_args; ++s) { + free(launch->jvm_args[s]); + } + free(launch->jvm_args); + + for (size_t s = 0; s < launch->ngame_args; ++s) { + free(launch->game_args[s]); + } + free(launch->game_args); + free(launch->classpath); } bool l2_launch__check_feature(const char *name, json_t *val, void *user) @@ -158,15 +167,63 @@ int L2_FORMAT(printf, 3, 4) l2_launch__sprintf_resize(char **buf, size_t *cap, c return 0; } -int l2_launch__fill_substitutor(struct l2_launch *launch) +int l2_launch__append_classpath(const char *path, char **pcp, size_t *plen) +{ + size_t pathlen = strlen(path); + size_t seplen = 0; + if (*plen > 0) { + seplen = L2_CSTRLEN(L2SU_CLASSPATH_SEP); + } + + char *temp = realloc(*pcp, *plen + pathlen + seplen + 1); + if (!temp) return -1; + *pcp = temp; + + if (*plen > 0) { + memcpy(*pcp + *plen, L2SU_CLASSPATH_SEP, seplen); + *plen += seplen; + } + + memcpy(*pcp + *plen, path, pathlen); + *plen += pathlen; + (*pcp)[*plen] = '\0'; + return 0; +} + +int l2_launch__build_classpath(struct l2_launch *launch, char **pcp) { + char *ocp = NULL; + size_t cplen = 0; + for (struct l2_version_library *cur = launch->libraries; cur; cur = cur->next) { + if (l2_launch__append_classpath(cur->fullpath, &ocp, &cplen) < 0) { + free(ocp); + return -1; + } + } + + if (l2_launch__append_classpath(launch->jarpath, &ocp, &cplen) < 0) { + free(ocp); + return -1; + } + + *pcp = ocp; + return 0; +} + +int l2_launch_init_substitutor(struct l2_launch *launch) +{ + if (launch->arg_subst) { + l2_subst_free(launch->arg_subst); + } + #define L2_LAUNCH_ADD_SUB(_st, _name, _val) \ if (l2_subst_add(_st, _name, _val) < 0) goto cleanup - l2_subst_t *st = launch->arg_subst; char *keyname = NULL, *apath = NULL; size_t keycap = 0, acap = 0; + char *classpath = launch->classpath; + const char *ver_name; const char *assets_idx_name; const char *version_type; @@ -174,16 +231,19 @@ int l2_launch__fill_substitutor(struct l2_launch *launch) return -1; } - /* auth_access_token */ - /* user_properties */ - /* user_property_map */ - /* auth_xuid */ - /* clientid (lowercase i is real) */ + if (l2_subst_init(&launch->arg_subst) < 0) goto cleanup; + l2_subst_t *st = launch->arg_subst; + + L2_LAUNCH_ADD_SUB(st, "auth_access_token", "-"); + L2_LAUNCH_ADD_SUB(st, "user_properties", "{}"); + L2_LAUNCH_ADD_SUB(st, "user_property_map", "{}"); + L2_LAUNCH_ADD_SUB(st, "auth_xuid", "null"); + L2_LAUNCH_ADD_SUB(st, "clientid", "null"); 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, "auth_uuid", "afc3f2d153844959bd05b2a5dc519c06"); + L2_LAUNCH_ADD_SUB(st, "user_type", "msa"); if (launch->instance) { L2_LAUNCH_ADD_SUB(st, "profile_name", launch->instance->name); @@ -211,7 +271,13 @@ int l2_launch__fill_substitutor(struct l2_launch *launch) L2_LAUNCH_ADD_SUB(st, "launcher_name", PROJECT_NAME); L2_LAUNCH_ADD_SUB(st, "launcher_version", "0.1.0"); L2_LAUNCH_ADD_SUB(st, "natives_directory", launch->natives); - L2_LAUNCH_ADD_SUB(st, "classpath", "TODO"); + + if (!classpath && l2_launch__build_classpath(launch, &classpath) < 0) { + CMD_ERROR0("Failed to build classpath"); + goto cleanup; + } + + L2_LAUNCH_ADD_SUB(st, "classpath", classpath); L2_LAUNCH_ADD_SUB(st, "classpath_separator", L2SU_CLASSPATH_SEP); L2_LAUNCH_ADD_SUB(st, "primary_jar", launch->jarpath); @@ -237,40 +303,254 @@ int l2_launch__fill_substitutor(struct l2_launch *launch) keyname = NULL; apath = NULL; + launch->classpath = classpath; + return 0; cleanup: free(keyname); free(apath); + if (classpath != launch->classpath) free(classpath); + return -1; #undef L2_LAUNCH_ADD_SUB } -void l2_launch__test(struct l2_launch *launch) +int l2_launch_init_args(struct l2_launch *launch) { - if (l2_launch__fill_substitutor(launch) < 0) CMD_FATAL0("fill"); + if (!launch->arg_subst) return -1; - char **jvm_args, **game_args; - size_t njvm_args, ngame_args; + char **jvm_args = NULL, **game_args = NULL; + size_t njvm_args = 0, ngame_args = 0; if (l2_args_get_jvm_args(launch->version, launch->arg_subst, &l2_launch__check_feature, launch, &jvm_args, &njvm_args) < 0) { - CMD_FATAL0("jvm"); + goto cleanup; } if (l2_args_get_game_args(launch->version, launch->arg_subst, &l2_launch__check_feature, launch, &game_args, &ngame_args) < 0) { - CMD_FATAL0("game"); + goto cleanup; } + launch->jvm_args = jvm_args; + launch->njvm_args = njvm_args; + launch->game_args = game_args; + launch->ngame_args = ngame_args; + return 0; + +cleanup: for (size_t s = 0; s < njvm_args; ++s) { - CMD_DEBUG("jvm: %s", jvm_args[s]); free(jvm_args[s]); } for(size_t s = 0; s < ngame_args; ++s) { - CMD_DEBUG("game: %s", game_args[s]); free(game_args[s]); } free(jvm_args); free(game_args); + return -1; +} + +L2_FORMAT(printf, 3, 4) int l2_launch__append_arg(JavaVMInitArgs *oargs, void *extra, const char *fmt, ...) +{ + va_list vp; + size_t argstrlen; + + JavaVMOption opt; + opt.optionString = NULL; + opt.extraInfo = extra; + + va_start(vp, fmt); + argstrlen = vsnprintf(NULL, 0, fmt, vp); + va_end(vp); + + opt.optionString = calloc(1, argstrlen + 1); + if (!opt.optionString) goto cleanup; + + va_start(vp, fmt); + argstrlen = vsnprintf(opt.optionString, argstrlen + 1, fmt, vp); + va_end(vp); + + JavaVMOption *temp = realloc(oargs->options, (oargs->nOptions + 1) * sizeof(JavaVMOption)); + if (!temp) { + goto cleanup; + } + + oargs->options = temp; + memcpy(oargs->options + oargs->nOptions, &opt, sizeof(JavaVMOption)); + ++oargs->nOptions; + + return 0; + +cleanup: + free(opt.optionString); + return -1; +} + +int l2_launch__append_literal_arg(JavaVMInitArgs *oargs, const char *arg, void *extra) +{ + JavaVMOption opt; + opt.optionString = strdup(arg); + opt.extraInfo = extra; + + if (!opt.optionString) return -1; + + JavaVMOption *temp = realloc(oargs->options, (oargs->nOptions + 1) * sizeof(JavaVMOption)); + if (!temp) goto cleanup; + + oargs->options = temp; + memcpy(oargs->options + oargs->nOptions, &opt, sizeof(JavaVMOption)); + ++oargs->nOptions; + + return 0; + +cleanup: + free(opt.optionString); + return -1; +} + +int l2_launch__convert_jvm_args(char **jvm_args, size_t njvm_args, JavaVMInitArgs *oargs) +{ + bool is_last = njvm_args == 1; + for (size_t argidx = 0; argidx < njvm_args; ++argidx, is_last = (argidx == njvm_args - 1)) { + if (!strcmp(jvm_args[argidx], "-cp")) { + if (l2_launch__append_arg(oargs, NULL, "-Djava.class.path=%s", is_last ? "" : jvm_args[argidx + 1]) < 0) { + return -1; + } + ++argidx; + } else if (!strncmp(jvm_args[argidx], "--", 2)) { + if (l2_launch__append_arg(oargs, NULL, "%s=%s", jvm_args[argidx], is_last ? "" : jvm_args[argidx+1]) < 0) { + return -1; + } + ++argidx; + } else { + if (l2_launch__append_literal_arg(oargs, jvm_args[argidx], NULL) < 0) { + return -1; + } + } + } + + return 0; +} + +jobjectArray l2_launch__create_game_args(JNIEnv *env, char **game_args, size_t nargs) +{ + jclass jstringcls = (*env)->FindClass(env, "java/lang/String"); + if ((*env)->ExceptionCheck(env) || !jstringcls) { + return NULL; + } + + jobjectArray jarr = (*env)->NewObjectArray(env, nargs, jstringcls, NULL); + if ((*env)->ExceptionCheck(env) || !jarr) { + return NULL; + } + + for (size_t idx = 0; idx < nargs; ++idx) { + jobject jarg = (*env)->NewStringUTF(env, game_args[idx]); + if ((*env)->ExceptionCheck(env) || !jarg) return NULL; + + (*env)->SetObjectArrayElement(env, jarr, idx, jarg); + if ((*env)->ExceptionCheck(env)) return NULL; + } + + return jarr; +} + +int l2_launch_jni(struct l2_launch *launch) +{ + int res = 0; + + if (chdir(launch->instance->path) < 0) { + CMD_ERROR("Failed to chdir to instance directory: %s", strerror(errno)); + return -1; + } + + if (l2_jni_init("/usr/lib/jvm/adoptopenjdk-8-hotspot-amd64/jre/lib/amd64/server/libjvm.so") < 0) { + CMD_ERROR0("Failed to initialize JNI"); + return -1; + } + + JavaVMInitArgs args; + jint jres; + memset(&args, 0, sizeof(JavaVMInitArgs)); + args.version = JNI_VERSION_1_8; + + char *main_class_fq; + size_t main_class_len; + L2_ASTRDUP(main_class_fq, main_class_len, launch->main_class); + for (char *cur = main_class_fq; *cur; ++cur) { + if (*cur == '.') *cur = '/'; + } + + if ((jres = JNI_GetDefaultJavaVMInitArgs(&args)) != JNI_OK) { + CMD_ERROR("Failed to get init args (%d)", res); + return -1; + } + + args.options = NULL; + args.nOptions = 0; + + if (l2_launch__convert_jvm_args(launch->jvm_args, launch->njvm_args, &args) < 0) { + CMD_ERROR0("Failed to convert JVM arguments"); + return -1; + } + + for (jint i = 0; i < args.nOptions; ++i) { + CMD_DEBUG("test: %s", args.options[i].optionString); + } + + args.ignoreUnrecognized = JNI_FALSE; + + JavaVM *vm = NULL; + JNIEnv *env; + if ((jres = JNI_CreateJavaVM(&vm, (void **)&env, &args)) != JNI_OK) { + CMD_ERROR("Failed to initialize the java VM: %d", res); + res = -1; + goto cleanup; + } + + jclass jmaincls = (*env)->FindClass(env, main_class_fq); + if ((*env)->ExceptionCheck(env) || !jmaincls) { + CMD_ERROR("Failed to load main class %s", main_class_fq); + (*env)->ExceptionDescribe(env); + res = -1; + goto cleanup; + } + + jobjectArray jargs = l2_launch__create_game_args(env, launch->game_args, launch->ngame_args); + if (!jargs || (*env)->ExceptionCheck(env)) { + CMD_ERROR0("Failed to build arguments array"); + (*env)->ExceptionDescribe(env); + res = -1; + goto cleanup; + } + + jmethodID main_method = (*env)->GetStaticMethodID(env, jmaincls, "main", "([Ljava/lang/String;)V"); + if ((*env)->ExceptionCheck(env) || !main_method) { + CMD_ERROR0("Failed to get main method"); + (*env)->ExceptionDescribe(env); + res = -1; + goto cleanup; + } + + (*env)->CallStaticVoidMethod(env, jmaincls, main_method, jargs); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + res = -1; + goto cleanup; + } + + res = 0; + +cleanup: + if (vm && (jres = (*vm)->DestroyJavaVM(vm)) != JNI_OK) { + CMD_ERROR("Failed to destroy the JVM: %d", res); + } + + for (jint i = 0; i < args.nOptions; ++i) { + free(args.options[i].optionString); + } + free(args.options); + + return res; } diff --git a/src/launch.h b/src/launch.h index d329358..e4b634c 100644 --- a/src/launch.h +++ b/src/launch.h @@ -28,9 +28,17 @@ struct l2_launch { char *classpath; l2_subst_t *arg_subst; + + char **game_args; + size_t ngame_args; + char **jvm_args; + size_t njvm_args; }; int l2_launch_init(struct l2_launch *launch, const char *vername, struct l2_instance *inst); +int l2_launch_init_substitutor(struct l2_launch *launch); +int l2_launch_init_args(struct l2_launch *launch); +int l2_launch_jni(struct l2_launch *launch); void l2_launch_free_contents(struct l2_launch *launch); diff --git a/src/meson.build b/src/meson.build index 61d1d2f..0dc22ca 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', 'args.c', 'launch.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', 'launch.c', 'jniwrap.c') configure_file(input : 'config.h.in', output : 'config.h', configuration : config_data) config_include_dir = include_directories('.') |
