aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meson.build7
-rw-r--r--src/cmd-version.c13
-rw-r--r--src/jniwrap.c92
-rw-r--r--src/jniwrap.h23
-rw-r--r--src/launch.c336
-rw-r--r--src/launch.h8
-rw-r--r--src/meson.build2
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('.')