#include #include "../api-impl-jni/defines.h" #include "../api-impl-jni/util.h" #include #include #include #include // generated by meson #include "config.h" GtkWidget *window; // standard Gtk Application stuff, more or less gboolean app_exit(GtkWindow* self, JNIEnv *env) // TODO: do more cleanup? { /* -- run the main activity's onDestroy -- */ (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onDestroy, NULL); return false; } // FIXME: used by hacks in GLSurfaceView int FIXME__WIDTH; int FIXME__HEIGHT; char *construct_classpath(char *prefix, char *path_to_prepend, char **cp_array, size_t len) { size_t result_len = strlen(prefix); for(int i = 0; i < len; i++) { result_len += strlen(path_to_prepend) + strlen(cp_array[i]) + 1; // the 1 is for either : or the final \0 } char *result = malloc(result_len); strcpy(result, prefix); for(int i = 0; i < len; i++) { strcat(result, path_to_prepend); strcat(result, cp_array[i]); if (i < (len - 1)) strcat(result, ":"); } return result; } char *construct_boot_classpath(char *prefix, char **cp_array, size_t len) { char *android_root_path = getenv("ANDROID_ROOT"); char *framework_dir = "/framework/"; char *framework_dir_path = malloc(strlen(android_root_path) + strlen(framework_dir) + 1); strcpy(framework_dir_path, android_root_path); strcat(framework_dir_path, framework_dir); char *result = construct_classpath(prefix, framework_dir_path, cp_array, len); free(framework_dir_path); return result; } JNIEnv* create_vm(char *api_impl_jar, char *apk_classpath, char *microg_apk) { JavaVM* jvm; JNIEnv* env; JavaVMInitArgs args; JavaVMOption options[3]; args.version = JNI_VERSION_1_6; args.nOptions = 3; // TODO: should probably not hardcode this char *boot_cp_arr[] = { "apache-xml-hostdex.jar", "core-junit-hostdex.jar", "core-libart-hostdex.jar", "dex-host.jar", "dx.jar", "hamcrest-hostdex.jar", "jarjar.jar", }; options[0].optionString = construct_boot_classpath("-Xbootclasspath:", boot_cp_arr, ARRAY_SIZE(boot_cp_arr)); // micorg is purposefully after the apk, so that we get the correct resources.arsc // TODO: request resources.arsc from concrete classloader instead of taking the first one in classpath char *cp_array[] = { api_impl_jar, apk_classpath, microg_apk, }; options[1].optionString = construct_classpath("-Djava.class.path=", "", cp_array, ARRAY_SIZE(cp_array)); options[2].optionString = "-verbose:jni"; args.options = options; args.ignoreUnrecognized = JNI_FALSE; int ret = JNI_CreateJavaVM(&jvm, (void **)&env, &args); if (ret<0){ printf("Unable to Launch JVM\n"); } else { printf("JVM launched successfully\n"); } return env; } // this is an extern "C" function which we added to art in order to simplify calling the underlying c++ method, // which we use to inject our JNI library into the correct classloader without having to load it from java code bool HAXX__JavaVMExt__LoadNativeLibrary(JNIEnv* env, char *path, jobject class_loader, char** error_msg); #define API_IMPL_JAR_PATH_LIBDIR INSTALL_LIBDIR "/java/dex/api-impl.jar" #define API_IMPL_JAR_PATH_LOCAL "./api-impl.jar" #define MICROG_APK_PATH_LIBDIR INSTALL_LIBDIR "/java/dex/com.google.android.gms.apk" #define MICROG_APK_PATH_LOCAL "./com.google.android.gms.apk" struct jni_callback_data { char *apk_main_activity_class; uint32_t window_width; uint32_t window_height;}; static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* hint, struct jni_callback_data *d) { //TODO: pass all files to classpath /* printf("nfiles: %d\n", nfiles); for(int i = 0; i < nfiles; i++) { printf(">- [%s]\n", g_file_get_path(files[i])); } */ char *api_impl_jar; char *microg_apk; int errno_libdir; int errno_localdir; int ret; char *apk_classpath = g_file_get_path(files[0]); if(apk_classpath == NULL) { printf("error: the specified file path doesn't seem to be valid\n"); exit(1); } struct stat dont_care; ret = stat(API_IMPL_JAR_PATH_LOCAL, &dont_care); errno_localdir = errno; if(!ret) { api_impl_jar = API_IMPL_JAR_PATH_LOCAL; } else { ret = stat(API_IMPL_JAR_PATH_LIBDIR, &dont_care); // for running out of builddir errno_libdir = errno; if(!ret) { api_impl_jar = API_IMPL_JAR_PATH_LIBDIR; } else { printf("error: can't stat api-impl.jar; tried:\n" "\t\"" API_IMPL_JAR_PATH_LOCAL "\", got - %s\n" "\t\"" API_IMPL_JAR_PATH_LIBDIR "\", got - %s\n", strerror(errno_localdir), strerror(errno_libdir)); exit(1); } } ret = stat(MICROG_APK_PATH_LOCAL, &dont_care); errno_localdir = errno; if(!ret) { microg_apk = MICROG_APK_PATH_LOCAL; } else { ret = stat(MICROG_APK_PATH_LIBDIR, &dont_care); // for running out of builddir errno_libdir = errno; if(!ret) { microg_apk = MICROG_APK_PATH_LIBDIR; } else { printf("warning: can't stat com.google.android.gms.apk; tried:\n" "\t\"" MICROG_APK_PATH_LOCAL "\", got - %s\n" "\t\"" MICROG_APK_PATH_LIBDIR "\", got - %s\n", strerror(errno_localdir), strerror(errno_libdir)); } } //FIXME ret = stat("./data", &dont_care); if(ret) { printf("FIXME: the app's data dir is currently hardcoded to \"./data\" (this should arguably be fixed); can't stat \"./data\": %s\n", strerror(errno)); printf("also FIXME: \"./data/lib/\" must exist, or we crash\n"); printf("please note that the app's data dir currently needs special attention - it's where we read assets and apktool-preprocessed resources from\n"); printf("do also note that any app you run will use './data' directly as it's data dir, not './data/' - this means you can't run multiple apps at once, unless the contents of their data folders happen to not conflict\n"); exit(1); } JNIEnv* env = create_vm(api_impl_jar, apk_classpath, microg_apk); if(!d->apk_main_activity_class) { printf("error: missing required option --launch-activity .\nyou can specify --help to see the list of options\n"); exit(1); } set_up_handle_cache(env, d->apk_main_activity_class); jclass display_class = (*env)->FindClass(env, "android/view/Display"); _SET_STATIC_INT_FIELD(display_class, "window_width", d->window_width); _SET_STATIC_INT_FIELD(display_class, "window_height", d->window_height); FIXME__WIDTH = d->window_width; FIXME__HEIGHT = d->window_height; window = gtk_application_window_new (app); (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.set_window, _INTPTR(window)); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); gtk_window_set_title(GTK_WINDOW(window), "com.example.demo_application"); gtk_window_set_default_size(GTK_WINDOW(window), d->window_width, d->window_height); g_signal_connect(window, "close-request", G_CALLBACK (app_exit), env); // TODO: set icon according to how android gets it for the purposes of displaying it in the launcher // gtk_window_set_icon_name(window, "weather-clear"); gtk_widget_show(window); /* -- register our JNI library under the appropriate classloader -- */ /* 'android/view/View' is part of the "hax.dex" package, any other function from that package would serve just as well */ jmethodID getClassLoader = _METHOD((*env)->FindClass(env, "java/lang/Class"), "getClassLoader", "()Ljava/lang/ClassLoader;"); jobject class_loader = (*env)->CallObjectMethod(env, handle_cache.view.class, getClassLoader); char* reason = NULL; if (!HAXX__JavaVMExt__LoadNativeLibrary(env, "libtranslation_layer_main.so", _REF(class_loader), &reason)) { printf("[main] dvmLoadNativeCode failed for libtranslation_layer_main.so: %s", reason); exit(1); } free(reason); /* -- run the main activity's onCreate -- */ (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onCreate, NULL); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); // TODO: some apps wait for this to actually do stuff /* (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onWindowFocusChanged, true); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env);*/ } static void activate(GtkApplication *app, struct jni_callback_data *d) { printf("error: usage: ./main [app.apk] [path/to/activity]\nyou can specify --help to see the list of options\n"); exit(1); } void init_cmd_parameters(GApplication *app, struct jni_callback_data *d) { const GOptionEntry cmd_params[] = { { .long_name = "launch-activity", .short_name = 'l', .flags = G_OPTION_FLAG_NONE, .arg = G_OPTION_ARG_STRING, .arg_data = &d->apk_main_activity_class, .description = "the fully quilifed name of the activity you wish to launch (usually the apk's main activity)", .arg_description = NULL, }, { .long_name = "window-width", .short_name = 'w', .flags = G_OPTION_FLAG_NONE, .arg = G_OPTION_ARG_INT, .arg_data = &d->window_width, .description = "window width to launch with (some apps react poorly to runtime window size adjustments)", .arg_description = NULL, }, { .long_name = "window-height", .short_name = 'h', .flags = G_OPTION_FLAG_NONE, .arg = G_OPTION_ARG_INT, .arg_data = &d->window_height, .description = "window height to launch with (some apps react poorly to runtime window size adjustments)", .arg_description = NULL, }, {NULL} }; g_application_add_main_option_entries (G_APPLICATION (app), cmd_params); } void init__r_debug(); int main(int argc, char **argv/*, JNIEnv *env*/) { GtkApplication *app; int status; /* this has to be done in the main executable, so might as well do it here*/ init__r_debug(); struct jni_callback_data *callback_data = malloc(sizeof(struct jni_callback_data)); callback_data->apk_main_activity_class = NULL; callback_data->window_width = 960; callback_data->window_height = 540; app = gtk_application_new("com.example.demo_application", G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_OPEN); // cmdline related setup init_cmd_parameters(G_APPLICATION(app), callback_data); g_application_set_option_context_summary(G_APPLICATION(app), "actual usage:\nLD_PRELOAD=libpthread_bio.so ./dalvik/dalvik -verbose:jni -cp hax_arsc_parser.dex:hax_xmlpull.dex:hax.dex:main.dex:${1}:com.google.android.gms.apk org/launch/main ${2}\nwhere ${1} is the path to the apk and ${2} is the cmdline"); g_signal_connect(app, "activate", G_CALLBACK (activate), callback_data); g_signal_connect(app, "open", G_CALLBACK (open), callback_data); status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return status; } const char dl_loader[] __attribute__((section(".interp"))) = "/lib/ld-linux.so.2";