2022-07-23 00:06:32 +02:00
# include <gtk/gtk.h>
2022-10-02 23:06:56 +02:00
# include "../api-impl-jni/defines.h"
# include "../api-impl-jni/util.h"
2022-07-23 00:06:32 +02:00
# include <dlfcn.h>
2022-10-02 23:06:56 +02:00
# include <errno.h>
# include <stdio.h>
# include <sys/stat.h>
2022-07-23 00:06:32 +02:00
2022-10-02 23:06:56 +02:00
// generated by meson
# include "config.h"
2022-09-20 15:32:37 +02:00
2022-07-23 00:06:32 +02:00
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 ;
}
2022-10-02 23:06:56 +02:00
JNIEnv * create_vm ( char * api_impl_jar , char * apk_classpath , char * microg_apk ) {
2022-07-23 00:06:32 +02:00
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 " ,
2022-09-12 13:16:11 +02:00
" core-junit-hostdex.jar " ,
" core-libart-hostdex.jar " ,
" dex-host.jar " ,
" dx.jar " ,
" hamcrest-hostdex.jar " ,
" jarjar.jar " ,
2022-07-23 00:06:32 +02:00
} ;
options [ 0 ] . optionString = construct_boot_classpath ( " -Xbootclasspath: " , boot_cp_arr , ARRAY_SIZE ( boot_cp_arr ) ) ;
2022-10-02 23:06:56 +02:00
// 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
2022-07-23 00:06:32 +02:00
char * cp_array [ ] = {
2022-10-02 23:06:56 +02:00
api_impl_jar ,
apk_classpath ,
microg_apk ,
2022-07-23 00:06:32 +02:00
} ;
2022-10-02 23:06:56 +02:00
options [ 1 ] . optionString = construct_classpath ( " -Djava.class.path= " , " " , cp_array , ARRAY_SIZE ( cp_array ) ) ;
2022-07-23 00:06:32 +02:00
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 ;
}
2022-10-02 23:06:56 +02:00
// 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
2022-09-12 13:16:11 +02:00
bool HAXX__JavaVMExt__LoadNativeLibrary ( JNIEnv * env , char * path , jobject class_loader , char * * error_msg ) ;
2022-10-02 23:06:56 +02:00
# 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"
2022-07-23 00:06:32 +02:00
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 ] ) ) ;
}
*/
2022-10-02 23:06:56 +02:00
char * api_impl_jar ;
char * microg_apk ;
int errno_libdir ;
int errno_localdir ;
int ret ;
2022-07-23 00:06:32 +02:00
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 ) ;
}
2022-10-02 23:06:56 +02:00
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/<package_name>' - 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 ) ;
2022-07-23 00:06:32 +02:00
if ( ! d - > apk_main_activity_class ) {
printf ( " error: missing required option --launch-activity <activity>. \n you 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 ;
2022-09-12 13:16:11 +02:00
if ( ! HAXX__JavaVMExt__LoadNativeLibrary ( env , " libtranslation_layer_main.so " , _REF ( class_loader ) , & reason ) ) {
2022-07-23 00:06:32 +02:00
printf ( " [main] dvmLoadNativeCode failed for libtranslation_layer_main.so: %s " , reason ) ;
exit ( 1 ) ;
}
2022-09-12 13:16:11 +02:00
free ( reason ) ;
2022-07-23 00:06:32 +02:00
/* -- 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] \n you 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 ) ;
}
2022-10-02 23:06:56 +02:00
void init__r_debug ( ) ;
2022-07-23 00:06:32 +02:00
int main ( int argc , char * * argv /*, JNIEnv *env*/ )
{
GtkApplication * app ;
int status ;
2022-10-02 23:06:56 +02:00
/* this has to be done in the main executable, so might as well do it here*/
2022-09-20 15:32:37 +02:00
init__r_debug ( ) ;
2022-07-23 00:06:32 +02:00
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: \n LD_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} \n where ${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 " ;