#include "config.h" #include "jniwrap.h" #include "l2su.h" #include "launch.h" #include "macros.h" #include "version.h" #include "assets.h" #include "args.h" #include #include #include #include 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) { unsigned res; json_t *verjson = NULL; struct l2_version_library *libs = NULL; char *jarpath = NULL; json_t *jassetidx = NULL; char *assetrootpath = NULL; char *assetpath = NULL; char *nativesdir = NULL; char *main_class_dup = NULL; 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)); goto cleanup; } const char *main_class; if (json_unpack(verjson, "{s:s}", "mainClass", &main_class) < 0) { CMD_ERROR0("Failed to find main class."); goto cleanup; } main_class_dup = strdup(main_class); if (!main_class_dup) { CMD_ERROR0("Allocation failed."); goto cleanup; } CMD_INFO0("Collecting libraries"); if ((res = l2_version_collect_libraries(verjson, &libs, &l2_launch__check_feature, launch)) != VERSION_SUCCESS) { CMD_ERROR("Failed to collect libraries: %s", l2_version_strerror(res)); goto cleanup; } CMD_INFO0("Downloading libraries"); if ((res = l2_version_download_libraries(libs)) != VERSION_SUCCESS) { CMD_ERROR("Failed to download libraries: %s", l2_version_strerror(res)); goto cleanup; } CMD_INFO0("Downloading client jar"); if ((res = l2_version_download_jar(verjson, "client", &jarpath)) != VERSION_SUCCESS) { CMD_ERROR("Failed to download client jar: %s", l2_version_strerror(res)); goto cleanup; } CMD_INFO0("Downloading asset index"); if (l2_assets_load_index(verjson, &jassetidx) < 0) { CMD_ERROR0("Failed to load asset index."); goto cleanup; } CMD_INFO0("Downloading assets"); if (l2_assets_download_assets(jassetidx, &assetpath) < 0) { CMD_ERROR0("Failed to download assets."); goto cleanup; } assetrootpath = l2_launcher_sprintf_alloc("%s/assets", l2_state.paths.data); if (!assetrootpath) { CMD_ERROR0("Allocation failed"); goto cleanup; } CMD_INFO0("Extracting natives"); if ((res = l2_version_extract_natives(libs, &nativesdir)) != VERSION_SUCCESS) { CMD_ERROR("Failed to extract natives: %s", l2_version_strerror(res)); goto cleanup; } launch->instance = inst; launch->version = verjson; launch->libraries = libs; launch->jarpath = jarpath; launch->asset_index = jassetidx; launch->assets_root = assetrootpath; launch->virtual_assets = assetpath; launch->natives = nativesdir; launch->arg_subst = NULL; launch->main_class = main_class_dup; launch->classpath = NULL; return 0; cleanup: json_decref(verjson); l2_version_free_libraries(libs); free(jarpath); free(assetrootpath); json_decref(jassetidx); free(assetpath); free(nativesdir); free(main_class_dup); return -1; } void l2_launch_free_contents(struct l2_launch *launch) { json_decref(launch->version); l2_version_free_libraries(launch->libraries); free(launch->jarpath); free(launch->assets_root); json_decref(launch->asset_index); free(launch->virtual_assets); 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) { L2_UNUSED(name); L2_UNUSED(val); L2_UNUSED(user); return false; } int L2_FORMAT(printf, 3, 4) l2_launch__sprintf_resize(char **buf, size_t *cap, const char *fmt, ...) { va_list pv; va_start(pv, fmt); size_t reqlen = vsnprintf(NULL, 0, fmt, pv); va_end(pv); if (*cap <= reqlen) { char *temp = realloc(*buf, reqlen + 1); if (!temp) return -1; *buf = temp; *cap = reqlen + 1; } va_start(pv, fmt); vsnprintf(*buf, *cap, fmt, pv); va_end(pv); return 0; } 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 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; if (json_unpack(launch->version, "{s:s, s:{s:s}, s:s}", "id", &ver_name, "assetIndex", "id", &assets_idx_name, "type", &version_type) < 0) { return -1; } 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", "afc3f2d153844959bd05b2a5dc519c06"); L2_LAUNCH_ADD_SUB(st, "user_type", "msa"); if (launch->instance) { L2_LAUNCH_ADD_SUB(st, "profile_name", launch->instance->name); } L2_LAUNCH_ADD_SUB(st, "version_name", ver_name); L2_LAUNCH_ADD_SUB(st, "game_directory", "."); L2_LAUNCH_ADD_SUB(st, "game_assets", launch->virtual_assets); L2_LAUNCH_ADD_SUB(st, "assets_root", launch->assets_root); L2_LAUNCH_ADD_SUB(st, "assets_index_name", assets_idx_name); L2_LAUNCH_ADD_SUB(st, "version_type", version_type); if (l2_launch__check_feature("has_custom_resolution", json_true(), launch)) { char *widthstr, *heightstr; size_t tempsz; L2_ASPRINTF(widthstr, tempsz, "%u", launch->resolution.width); L2_ASPRINTF(heightstr, tempsz, "%u", launch->resolution.height); L2_LAUNCH_ADD_SUB(st, "resolution_width", widthstr); L2_LAUNCH_ADD_SUB(st, "resolution_height", heightstr); } L2_LAUNCH_ADD_SUB(st, "language", "en-us"); 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); 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); const char *assetpath, *assethash; json_t *objects = json_object_get(launch->asset_index, "objects"); if (json_is_object(objects)) { json_t *val; json_object_foreach(objects, assetpath, val) { json_t *hash_obj = json_object_get(val, "hash"); if (!json_is_string(hash_obj)) goto cleanup; assethash = json_string_value(hash_obj); if (l2_launch__sprintf_resize(&apath, &acap, "%s/assets/objects/%2.2s/%s", l2_state.paths.data, assethash, assethash) < 0) goto cleanup; if (l2_launch__sprintf_resize(&keyname, &keycap, "asset=%s", assetpath) < 0) goto cleanup; L2_LAUNCH_ADD_SUB(st, keyname, apath); } } free(keyname); free(apath); 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 } int l2_launch_init_args(struct l2_launch *launch) { if (!launch->arg_subst) return -1; 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) { goto cleanup; } if (l2_args_get_game_args(launch->version, launch->arg_subst, &l2_launch__check_feature, launch, &game_args, &ngame_args) < 0) { 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) { free(jvm_args[s]); } for(size_t s = 0; s < ngame_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; }