From 55c3d9f9d81134c4289c1df3561e6394b87920cd Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 12 Jun 2020 03:37:58 -0400 Subject: [PATCH] android: PhysicsFS now has actual Android support. This compiled and worked on Android before, if you didn't care about PHYSFS_getBaseDir() and PHYSFS_getPrefDir() being useful. Now you can pass PHYSFS_init() some necessary Android objects to solve this. Passing NULL to PHYSFS_init is acceptable and will simply report "/" for the base dir and prefdir, under the assumption that the app queried the OS for these directly instead. --- CMakeLists.txt | 1 + src/physfs.c | 2 + src/physfs.h | 32 +++++++++- src/physfs_platform_android.c | 117 ++++++++++++++++++++++++++++++++++ src/physfs_platforms.h | 10 +-- 5 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 src/physfs_platform_android.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 292ae92..1b1caf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ set(PHYSFS_SRCS src/physfs_platform_windows.c src/physfs_platform_os2.c src/physfs_platform_qnx.c + src/physfs_platform_android.c src/physfs_archiver_dir.c src/physfs_archiver_unpacked.c src/physfs_archiver_grp.c diff --git a/src/physfs.c b/src/physfs.c index 1e64b2b..4c23d08 100644 --- a/src/physfs.c +++ b/src/physfs.c @@ -1233,7 +1233,9 @@ int PHYSFS_init(const char *argv0) if (!userDir) goto initFailed; /* Platform layer is required to append a dirsep. */ + #ifndef __ANDROID__ /* it's an APK file, not a directory, on Android. */ assert(baseDir[strlen(baseDir) - 1] == __PHYSFS_platformDirSeparator); + #endif assert(userDir[strlen(userDir) - 1] == __PHYSFS_platformDirSeparator); if (!initStaticArchivers()) goto initFailed; diff --git a/src/physfs.h b/src/physfs.h index 4b811ed..d182ff7 100644 --- a/src/physfs.h +++ b/src/physfs.h @@ -493,6 +493,14 @@ typedef struct PHYSFS_Version PHYSFS_DECL void PHYSFS_getLinkedVersion(PHYSFS_Version *ver); +#ifdef __ANDROID__ +typedef struct PHYSFS_AndroidInit +{ + void *jnienv; + void *context; +} PHYSFS_AndroidInit; +#endif + /** * \fn int PHYSFS_init(const char *argv0) * \brief Initialize the PhysicsFS library. @@ -502,11 +510,22 @@ PHYSFS_DECL void PHYSFS_getLinkedVersion(PHYSFS_Version *ver); * This should be called prior to any attempts to change your process's * current working directory. * + * \warning On Android, argv0 should be a non-NULL pointer to a + * PHYSFS_AndroidInit struct. This struct must hold a valid JNIEnv * + * and a JNI jobject of a Context (either the application context or + * the current Activity is fine). Both are cast to a void * so we + * don't need jni.h included wherever physfs.h is. PhysicsFS + * uses these objects to query some system details. PhysicsFS does + * not hold a reference to the JNIEnv or Context past the call to + * PHYSFS_init(). If you pass a NULL here, PHYSFS_init can still + * succeed, but PHYSFS_getBaseDir() and PHYSFS_getPrefDir() will be + * incorrect. + * * \param argv0 the argv[0] string passed to your program's mainline. * This may be NULL on most platforms (such as ones without a * standard main() function), but you should always try to pass - * something in here. Unix-like systems such as Linux _need_ to - * pass argv[0] from main() in here. + * something in here. Many Unix-like systems _need_ to pass argv[0] + * from main() in here. See warning about Android, too! * \return nonzero on success, zero on error. Specifics of the error can be * gleaned from PHYSFS_getLastError(). * @@ -762,6 +781,15 @@ PHYSFS_DECL char **PHYSFS_getCdRomDirs(void); * * You should probably use the base dir in your search path. * + * \warning On most platforms, this is a directory; on Android, this gives + * you the path to the app's package (APK) file. As APK files are + * just .zip files, you can mount them in PhysicsFS like regular + * directories. You'll probably want to call + * PHYSFS_setRoot(basedir, "/assets") after mounting to make your + * app's actual data available directly without all the Android + * metadata and directory offset. Note that if you passed a NULL to + * PHYSFS_init(), you will not get the APK file here. + * * \return READ ONLY string of base dir in platform-dependent notation. * * \sa PHYSFS_getPrefDir diff --git a/src/physfs_platform_android.c b/src/physfs_platform_android.c new file mode 100644 index 0000000..f892fed --- /dev/null +++ b/src/physfs_platform_android.c @@ -0,0 +1,117 @@ +/* + * Android support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_platforms.h" + +#ifdef PHYSFS_PLATFORM_ANDROID + +#include +#include +#include "physfs_internal.h" + +static char *prefpath = NULL; + + +int __PHYSFS_platformInit(void) +{ + return 1; /* always succeed. */ +} /* __PHYSFS_platformInit */ + + +void __PHYSFS_platformDeinit(void) +{ + if (prefpath) + { + allocator.Free(prefpath); + prefpath = NULL; + } /* if */ +} /* __PHYSFS_platformDeinit */ + + +void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data) +{ + /* no-op. */ +} /* __PHYSFS_platformDetectAvailableCDs */ + + +char *__PHYSFS_platformCalcBaseDir(const char *argv0) +{ + /* as a cheat, we expect argv0 to be a PHYSFS_AndroidInit* on Android. */ + PHYSFS_AndroidInit *ainit = (PHYSFS_AndroidInit *) argv0; + char *retval = NULL; + JNIEnv *jenv = NULL; + jobject jcontext; + + if (ainit == NULL) + return __PHYSFS_strdup("/"); /* oh well. */ + + jenv = (JNIEnv *) ainit->jnienv; + jcontext = (jobject) ainit->context; + + if ((*jenv)->PushLocalFrame(jenv, 16) >= 0) + { + jobject jfileobj = 0; + jmethodID jmeth = 0; + jthrowable jexception = 0; + jstring jstr = 0; + + jmeth = (*jenv)->GetMethodID(jenv, (*jenv)->GetObjectClass(jenv, jcontext), "getPackageResourcePath", "()Ljava/lang/String;"); + jstr = (jstring)(*jenv)->CallObjectMethod(jenv, jcontext, jmeth); + jexception = (*jenv)->ExceptionOccurred(jenv); /* this can't throw an exception, right? Just in case. */ + if (jexception != NULL) + (*jenv)->ExceptionClear(jenv); + else + { + const char *path = (*jenv)->GetStringUTFChars(jenv, jstr, NULL); + retval = __PHYSFS_strdup(path); + (*jenv)->ReleaseStringUTFChars(jenv, jstr, path); + } /* else */ + + /* We only can rely on the Activity being valid during this function call, + so go ahead and grab the prefpath too. */ + jmeth = (*jenv)->GetMethodID(jenv, (*jenv)->GetObjectClass(jenv, jcontext), "getFilesDir", "()Ljava/io/File;"); + jfileobj = (*jenv)->CallObjectMethod(jenv, jcontext, jmeth); + if (jfileobj) + { + jmeth = (*jenv)->GetMethodID(jenv, (*jenv)->GetObjectClass(jenv, jfileobj), "getCanonicalPath", "()Ljava/lang/String;"); + jstr = (jstring)(*jenv)->CallObjectMethod(jenv, jfileobj, jmeth); + jexception = (*jenv)->ExceptionOccurred(jenv); + if (jexception != NULL) + (*jenv)->ExceptionClear(jenv); + else + { + const char *path = (*jenv)->GetStringUTFChars(jenv, jstr, NULL); + const size_t len = strlen(path) + 2; + prefpath = allocator.Malloc(len); + if (prefpath) + snprintf(prefpath, len, "%s/", path); + (*jenv)->ReleaseStringUTFChars(jenv, jstr, path); + } /* else */ + } /* if */ + + (*jenv)->PopLocalFrame(jenv, NULL); + } /* if */ + + /* we can't return NULL because then PhysicsFS will treat argv0 as a string, but it's a non-NULL jobject! */ + if (retval == NULL) + retval = __PHYSFS_strdup("/"); /* we pray this works. */ + + return retval; +} /* __PHYSFS_platformCalcBaseDir */ + + +char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app) +{ + return __PHYSFS_strdup(prefpath ? prefpath : "/"); +} /* __PHYSFS_platformCalcPrefDir */ + +#endif /* PHYSFS_PLATFORM_ANDROID */ + +/* end of physfs_platform_android.c ... */ + diff --git a/src/physfs_platforms.h b/src/physfs_platforms.h index d4e4bfd..1ac17d9 100644 --- a/src/physfs_platforms.h +++ b/src/physfs_platforms.h @@ -40,11 +40,11 @@ # define PHYSFS_PLATFORM_POSIX 1 #elif defined(macintosh) # error Classic Mac OS support was dropped from PhysicsFS 2.0. Move to OS X. -#elif defined(ANDROID) -# define PHYSFS_PLATFORM_LINUX 1 -# define PHYSFS_PLATFORM_UNIX 1 -# define PHYSFS_PLATFORM_POSIX 1 -# define PHYSFS_NO_CDROM_SUPPORT 1 +#elif defined(__ANDROID__) + # define PHYSFS_PLATFORM_LINUX 1 + # define PHYSFS_PLATFORM_ANDROID 1 + # define PHYSFS_PLATFORM_POSIX 1 + # define PHYSFS_NO_CDROM_SUPPORT 1 #elif defined(__linux) # define PHYSFS_PLATFORM_LINUX 1 # define PHYSFS_PLATFORM_UNIX 1