get rid of launcher script requirement; fix app data dir being hardcoded; update documentation; remove prebuilt dalvik (NOTE - purged from history at this point, so is not part of the diff)

This commit is contained in:
Mis012
2022-10-12 17:23:19 +02:00
parent 31cf1a521b
commit 49961186a2
17 changed files with 989 additions and 899 deletions

View File

@@ -9,15 +9,18 @@
#include "util.h"
#include "generated_headers/android_content_res_AssetManager.h"
#define ASSET_DIR "data/assets/"
#define ASSET_DIR "assets/"
char *get_app_data_dir();
JNIEXPORT jint JNICALL Java_android_content_res_AssetManager_openAsset(JNIEnv *env, jobject this, jstring _file_name, jint mode)
{
const char *file_name = _CSTRING(_file_name);
char *path = malloc(strlen(file_name) + strlen(ASSET_DIR) + 1);
char *app_data_dir = get_app_data_dir();
char *path = malloc(strlen(app_data_dir) + strlen(ASSET_DIR) + strlen(file_name) + 1);
int fd;
strcpy(path, ASSET_DIR);
strcpy(path, app_data_dir);
strcat(path, ASSET_DIR);
strcat(path, file_name);
printf("openning asset with filename: %s\n", _CSTRING(_file_name));

View File

@@ -0,0 +1,11 @@
#include "defines.h"
#include "util.h"
#include "generated_headers/android_os_Environment.h"
char *get_app_data_dir();
JNIEXPORT jstring JNICALL Java_android_os_Environment_native_1get_1app_1data_1dir(JNIEnv *env, jclass this)
{
return _JSTRING(get_app_data_dir());
}

View File

@@ -13,6 +13,7 @@
#define _CLASS(object) ((*env)->GetObjectClass(env, object))
#define _SUPER(object) ((*env)->GetSuperclass(env, object))
#define _METHOD(class, method, attrs) ((*env)->GetMethodID(env, class, method, attrs))
#define _STATIC_METHOD(class, method, attrs) ((*env)->GetStaticMethodID(env, class, method, attrs))
#define _JSTRING(cstring) ((*env)->NewStringUTF(env, cstring))
#define _CSTRING(jstring) ((*env)->GetStringUTFChars(env, jstring, NULL))
#define _FIELD_ID(class, field, type) ((*env)->GetFieldID(env, class , field, type))

View File

@@ -0,0 +1,21 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class android_os_Environment */
#ifndef _Included_android_os_Environment
#define _Included_android_os_Environment
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: android_os_Environment
* Method: native_get_app_data_dir
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_android_os_Environment_native_1get_1app_1data_1dir
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -112,7 +112,7 @@ public class Activity extends Context {
public void setContentView(int layoutResID) throws Exception {
System.out.println("- setContentView - yay!");
String layout_xml_file = "data/" + getString(layoutResID);
String layout_xml_file = android.os.Environment.getExternalStorageDirectory().getPath() + "/" + getString(layoutResID);
System.out.println("loading layout from: " + layout_xml_file);

View File

@@ -122,7 +122,7 @@ public class Context extends Object {
private File getDataDirFile() {
if(data_dir == null) {
data_dir = new File("data/");
data_dir = android.os.Environment.getExternalStorageDirectory();
}
return data_dir;
}
@@ -185,7 +185,7 @@ public class Context extends Object {
public FileOutputStream openFileOutput(String name, int mode) throws java.io.FileNotFoundException {
System.out.println("openFileOutput called for: '"+name+"'");
return new FileOutputStream("data/files/" + name);
return new FileOutputStream(android.os.Environment.getExternalStorageDirectory().getPath() + "/files/" + name);
}
public int checkCallingOrSelfPermission(String permission) {

View File

@@ -467,7 +467,7 @@ public final class AssetManager {
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
xpp.setInput( new FileReader("data/" + fileName) );
xpp.setInput( new FileReader(android.os.Environment.getExternalStorageDirectory().getPath() + "/" + fileName) );
return (XmlResourceParser)xpp;
}

View File

@@ -111,7 +111,7 @@ public final class Bitmap {
} // FIXME
Bitmap(String path) {
pixbuf = native_bitmap_from_path("data/"+path);
pixbuf = native_bitmap_from_path(android.os.Environment.getExternalStorageDirectory().getPath() + "/" + path);
mIsMutable = false;
mIsPremultiplied = false;

File diff suppressed because it is too large Load Diff

View File

@@ -91,7 +91,7 @@ public class LayoutInflater {
public View inflate(int layoutResID, ViewGroup root, boolean attachToRoot) throws Exception {
String layout_xml_file = "data/" + Context.this_application.getString(layoutResID);
String layout_xml_file = android.os.Environment.getExternalStorageDirectory().getPath() + "/" + Context.this_application.getString(layoutResID);
System.out.println("loading layout from: " + layout_xml_file);

View File

@@ -1,6 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
#define INSTALL_LIBDIR "@install_libdir@"
#endif

View File

@@ -18,14 +18,16 @@ typedef int64_t off64_t;
typedef void JNIEnv;
typedef void * jobject;
#define ASSET_DIR "data/assets/"
#define ASSET_DIR "assets/"
char *get_app_data_dir();
struct AAsset* AAssetManager_open(struct AAssetManager *amgr, const char *file_name, int mode)
{
char *path = malloc(strlen(file_name) + strlen(ASSET_DIR) + 1);
char *app_data_dir = get_app_data_dir();
char *path = malloc(strlen(app_data_dir) + strlen(ASSET_DIR) + strlen(file_name) + 1);
int fd;
strcpy(path, ASSET_DIR);
strcpy(path, app_data_dir);
strcat(path, ASSET_DIR);
strcat(path, file_name);
printf("openning asset with filename: %s\n", file_name);

View File

@@ -1,3 +1,6 @@
// for dladdr
#define _GNU_SOURCE
#include <gtk/gtk.h>
#include "../api-impl-jni/defines.h"
@@ -8,13 +11,6 @@
#include <stdio.h>
#include <sys/stat.h>
// generated by meson
#include "config.h"
// TODO: arguably we shouldn't rely on our install prefix being the same as art's install prefix
// maybe we can have art export a function returning it's install prefix for us
#define INSTALL_DEXDIR INSTALL_LIBDIR "/java/dex/"
GtkWidget *window;
// standard Gtk Application stuff, more or less
@@ -28,22 +24,30 @@ gboolean app_exit(GtkWindow* self, JNIEnv *env) // TODO: do more cleanup?
return false;
}
// this is the equivalent of /data/data/com.example.app/
char *app_data_dir = NULL;
char *get_app_data_dir()
{
return app_data_dir;
}
// 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)
char *construct_classpath(char *prefix, 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
if(cp_array[i])
result_len += 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(cp_array[i])
strcat(result, cp_array[i]);
if (i < (len - 1))
strcat(result, ":");
}
@@ -51,28 +55,7 @@ char *construct_classpath(char *prefix, char *path_to_prepend, char **cp_array,
return result;
}
char *construct_boot_classpath(char *prefix, char **cp_array, size_t len)
{
char *framework_dir_path;
const char *android_root_path = getenv("ANDROID_ROOT");
if(!android_root_path) { // look in PREFIX by default
framework_dir_path = INSTALL_DEXDIR "/art/";
}
else { // if ANDROID_ROOT is set, look there
const char *framework_dir = "/framework/";
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);
if(android_root_path) // otherwise it's not allocated dynamically
free(framework_dir_path);
return result;
}
JNIEnv* create_vm(char *api_impl_jar, char *apk_classpath, char *microg_apk) {
JNIEnv* create_vm(char *api_impl_jar, char *apk_classpath, char *microg_apk, char *api_impl_natives_dir, char *app_lib_dir) {
JavaVM* jvm;
JNIEnv* env;
JavaVMInitArgs args;
@@ -80,23 +63,16 @@ JNIEnv* create_vm(char *api_impl_jar, char *apk_classpath, char *microg_apk) {
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",
"hamcrest-hostdex.jar",
};
options[0].optionString = construct_boot_classpath("-Xbootclasspath:", boot_cp_arr, ARRAY_SIZE(boot_cp_arr));
if(getenv("RUN_FROM_BUILDDIR")) {
options[0].optionString = construct_classpath("-Djava.library.path=", (char *[]){"./", app_lib_dir}, 2);
} else {
printf(">>%s<<\n", api_impl_natives_dir);
options[0].optionString = construct_classpath("-Djava.library.path=", (char *[]){api_impl_natives_dir, app_lib_dir}, 2);
}
// 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));
// microg is purposefully after the apk, so that we get the correct resources.arsc
// TODO: request resources.arsc from concrete apk instead of taking the first one in classpath
options[1].optionString = construct_classpath("-Djava.class.path=", (char *[]){api_impl_jar, apk_classpath, microg_apk}, 3);
options[2].optionString = "-verbose:jni";
args.options = options;
@@ -111,14 +87,17 @@ JNIEnv* create_vm(char *api_impl_jar, char *apk_classpath, char *microg_apk) {
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);
// this is exported by the shim bionic linker
void dl_parse_library_path(const char *path, char *delim);
#define DOT_LOCAL_DATA_PATH "/.local/share/android_translation_layer"
#define REL_DEX_INSTALL_PATH "/../java/dex"
#define REL_API_IMPL_JAR_INSTALL_PATH "/android_translation_layer/api-impl.jar"
#define REL_API_IMPL_NATIVES_INSTALL_PATH "/android_translation_layer/natives"
#define REL_MICROG_APK_INSTALL_PATH "/microg/com.google.android.gms.apk"
#define API_IMPL_JAR_PATH_LIBDIR INSTALL_DEXDIR "/translation-layer/api-impl.jar"
#define API_IMPL_JAR_PATH_LOCAL "./api-impl.jar"
#define MICROG_APK_PATH_LIBDIR INSTALL_DEXDIR "/microg/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;};
@@ -131,6 +110,7 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h
printf(">- [%s]\n", g_file_get_path(files[i]));
}
*/
char *dex_install_dir;
char *api_impl_jar;
char *microg_apk;
int errno_libdir;
@@ -138,28 +118,91 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h
int ret;
char *apk_classpath = g_file_get_path(files[0]);
char *apk_name = g_file_get_basename(files[0]);
if(apk_classpath == NULL) {
printf("error: the specified file path doesn't seem to be valid\n");
exit(1);
}
Dl_info libart_so_dl_info;
// JNI_CreateJavaVM chosen arbitrarily, what matters is that it's a symbol exported by by libart.so
// TODO: we shouldn't necessarily count on art being installed in the same prefix as we are
dladdr(JNI_CreateJavaVM, &libart_so_dl_info);
// make sure we didn't get NULL
if(libart_so_dl_info.dli_fname) {
// it's simpler if we can modify the string, so strdup it
char *libart_so_full_path = strdup(libart_so_dl_info.dli_fname);
*strrchr(libart_so_full_path, '/') = '\0'; // now we should have something like /usr/lib64/art
dex_install_dir = malloc(strlen(libart_so_full_path) + strlen(REL_DEX_INSTALL_PATH) + 1); // +1 for NULL
strcpy(dex_install_dir, libart_so_full_path);
strcat(dex_install_dir, REL_DEX_INSTALL_PATH);
free(libart_so_full_path);
} else {
dex_install_dir = "DIDN'T_GET_SO_PATH_WITH_dladdr_SUS"; // in case we print this as part of some other error, it should be clear what the real cause is
}
char* app_data_dir_base = getenv("ANDROID_APP_DATA_DIR");
if(!app_data_dir_base) {
const char* home_dir = getenv("HOME");
if(home_dir) {
app_data_dir_base = malloc(strlen(home_dir) + strlen(DOT_LOCAL_DATA_PATH) + 1); // +1 for NULL
strcpy(app_data_dir_base, home_dir);
strcat(app_data_dir_base, DOT_LOCAL_DATA_PATH);
ret = mkdir(app_data_dir_base, DEFFILEMODE | S_IXUSR | S_IXGRP | S_IXOTH);
if (ret) {
if(errno != EEXIST) {
fprintf(stderr, "error: ANDROID_APP_DATA_DIR not set, and the default directory (%s) coudldn't be created (error: %s)\n", app_data_dir_base, strerror(errno));
exit(1);
}
}
} else {
fprintf(stderr, "error: ANDROID_APP_DATA_DIR not set, and HOME is not set either so we can't construct the default path\n");
exit(1);
}
}
app_data_dir = malloc(strlen(app_data_dir_base) + 1 + strlen(apk_name) + 1 + 1); // +1 for middle '/', +1 for end '/', and +1 for NULL
strcpy(app_data_dir, app_data_dir_base);
strcat(app_data_dir, "/");
// TODO: we should possibly use the app id instead, but we don't currently have a way to get that
// arguably both the app id and the apk name might have an issue with duplicates, but if two apks use the same app id, chances are it's less of an issue than when two apks have the same name
strcat(app_data_dir, apk_name);
strcat(app_data_dir, "/");
struct stat dont_care;
ret = stat(app_data_dir, &dont_care);
if(ret) {
printf("can't stat %s (%s); we don't currently support parsing certain resources from the apk file, "
"so you will need to create this directory and put in those resources\n"
"some of these resources need to be preprocessed, apktool is mostly fine except it replaces "
"the numeric values in the layout xml files with what it assumes was in the source xml; "
"android studio's apk anylyzer (for example) leaves the 'compiled' integer values that our code expects\n"
"NOTE: if the app you're trying to run doesn't use any such resources, simply create the directory "
"and leave it empty (this will be done automatically once the maunal preprocessing step is not required)\n",
app_data_dir, strerror(errno));
exit(1);
}
// check for api-impl.jar and com.google.android.gms.apk in './' first (for running from builddir), and in system install path second
ret = stat(API_IMPL_JAR_PATH_LOCAL, &dont_care);
errno_localdir = errno;
if(!ret) {
api_impl_jar = API_IMPL_JAR_PATH_LOCAL;
api_impl_jar = strdup(API_IMPL_JAR_PATH_LOCAL); // for running out of builddir; using strdup so we can always safely call free on this
} else {
ret = stat(API_IMPL_JAR_PATH_LIBDIR, &dont_care); // for running out of builddir
char *api_impl_install_dir = malloc(strlen(dex_install_dir) + strlen(REL_API_IMPL_JAR_INSTALL_PATH) + 1); // +1 for NULL
strcpy(api_impl_install_dir, dex_install_dir);
strcat(api_impl_install_dir, REL_API_IMPL_JAR_INSTALL_PATH);
ret = stat(api_impl_install_dir, &dont_care);
errno_libdir = errno;
if(!ret) {
api_impl_jar = API_IMPL_JAR_PATH_LIBDIR;
api_impl_jar = api_impl_install_dir;
} 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));
"\t\"%s\", got - %s\n",
strerror(errno_localdir),
api_impl_install_dir, strerror(errno_libdir));
exit(1);
}
}
@@ -167,31 +210,39 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h
ret = stat(MICROG_APK_PATH_LOCAL, &dont_care);
errno_localdir = errno;
if(!ret) {
microg_apk = MICROG_APK_PATH_LOCAL;
microg_apk = strdup(MICROG_APK_PATH_LOCAL); // for running out of builddir; using strdup so we can always safely call free on this
} else {
ret = stat(MICROG_APK_PATH_LIBDIR, &dont_care); // for running out of builddir
char *microg_install_dir = malloc(strlen(dex_install_dir) + strlen(REL_MICROG_APK_INSTALL_PATH) + 1); // +1 for NULL
strcpy(microg_install_dir, dex_install_dir);
strcat(microg_install_dir, REL_MICROG_APK_INSTALL_PATH);
ret = stat(microg_install_dir, &dont_care);
errno_libdir = errno;
if(!ret) {
microg_apk = MICROG_APK_PATH_LIBDIR;
microg_apk = microg_install_dir;
} 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));
"\t\"%s\", got - %s\n",
strerror(errno_localdir),
microg_install_dir, 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);
}
char *api_impl_natives_dir = malloc(strlen(dex_install_dir) + strlen(REL_API_IMPL_NATIVES_INSTALL_PATH) + 1); // +1 for NULL
strcpy(api_impl_natives_dir, dex_install_dir);
strcat(api_impl_natives_dir, REL_API_IMPL_NATIVES_INSTALL_PATH);
JNIEnv* env = create_vm(api_impl_jar, apk_classpath, microg_apk);
char *app_lib_dir = malloc(strlen(app_data_dir) + strlen("/lib") + 1); // +1 for NULL
strcpy(app_lib_dir, app_data_dir);
strcat(app_lib_dir, "/lib");
// calling directly into the shim bionic linker to whitelist the app's lib dir as containing bionic-linked libraries
dl_parse_library_path(app_lib_dir, ":");
JNIEnv* env = create_vm(api_impl_jar, apk_classpath, microg_apk, api_impl_natives_dir, app_lib_dir);
free(app_lib_dir);
if(!d->apk_main_activity_class) {
printf("error: missing required option --launch-activity <activity>.\nyou can specify --help to see the list of options\n");
@@ -227,13 +278,14 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h
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);
}
jclass java_runtime_class = (*env)->FindClass(env, "java/lang/Runtime");
free(reason);
jmethodID getRuntime = _STATIC_METHOD(java_runtime_class, "getRuntime", "()Ljava/lang/Runtime;");
jobject java_runtime = (*env)->CallStaticObjectMethod(env, java_runtime_class, getRuntime);
/* this method is private, but it seems we get away with calling it from C */
jmethodID loadLibrary_with_classloader = _METHOD(java_runtime_class, "loadLibrary", "(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
(*env)->CallVoidMethod(env, java_runtime, loadLibrary_with_classloader, _JSTRING("translation_layer_main"), class_loader);
/* -- run the main activity's onCreate -- */