android: PhysicsFS now has actual Android support. default tip
authorRyan C. Gordon <icculus@icculus.org>
Fri, 12 Jun 2020 03:37:58 -0400
changeset 1692 acdcf93d1f9b
parent 1691 c66c9326b05d
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
src/physfs.c
src/physfs.h
src/physfs_platform_android.c
src/physfs_platforms.h
--- a/CMakeLists.txt	Sun May 17 01:41:52 2020 -0400
+++ b/CMakeLists.txt	Fri Jun 12 03:37:58 2020 -0400
@@ -76,6 +76,7 @@
     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
--- a/src/physfs.c	Sun May 17 01:41:52 2020 -0400
+++ b/src/physfs.c	Fri Jun 12 03:37:58 2020 -0400
@@ -1233,7 +1233,9 @@
     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;
--- a/src/physfs.h	Sun May 17 01:41:52 2020 -0400
+++ b/src/physfs.h	Fri Jun 12 03:37:58 2020 -0400
@@ -493,6 +493,14 @@
 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 @@
  * 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 @@
  *
  * 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/physfs_platform_android.c	Fri Jun 12 03:37:58 2020 -0400
@@ -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 <jni.h>
+#include <android/log.h>
+#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 ... */
+
--- a/src/physfs_platforms.h	Sun May 17 01:41:52 2020 -0400
+++ b/src/physfs_platforms.h	Fri Jun 12 03:37:58 2020 -0400
@@ -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