From 515433aa8a8fdeb0be4d0e2e1ee733ad16fc7d6e Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 11 Jul 2025 14:16:18 -0400 Subject: [PATCH] android: If various POSIX fsops functions fail, try using AAssetManager. This specifically affects SDL_EnumerateDirectory and SDL_GetPathInfo. Android assets are read-only, so no need to do this for things like SDL_CreateDirectory, etc, and the POSIX SDL_CopyFile() uses SDL_IOStream behind the scenes, which already supports Android assets. Fixes #13050. --- src/core/android/SDL_android.c | 56 +++++++++++++++++++++++++++++ src/core/android/SDL_android.h | 2 ++ src/filesystem/posix/SDL_sysfsops.c | 16 ++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 6cdffef245..655da3179c 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -1853,6 +1853,62 @@ bool Android_JNI_FileClose(void *userdata) return true; } +bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) +{ + SDL_assert((*path == '\0') || (path[SDL_strlen(path) - 1] == '/')); // SDL_SYS_EnumerateDirectory() should have verified this. + + if (!asset_manager) { + Internal_Android_Create_AssetManager(); + if (!asset_manager) { + return SDL_SetError("Couldn't create asset manager"); + } + } + + AAssetDir *adir = AAssetManager_openDir(asset_manager, path); + if (!adir) { + return SDL_SetError("AAssetManager_openDir failed"); + } + + SDL_EnumerationResult result = SDL_ENUM_CONTINUE; + const char *ent; + while ((result == SDL_ENUM_CONTINUE) && ((ent = AAssetDir_getNextFileName(adir)) != NULL)) { + result = cb(userdata, path, ent); + } + + AAssetDir_close(adir); + + return (result != SDL_ENUM_FAILURE); +} + +bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info) +{ + if (!asset_manager) { + Internal_Android_Create_AssetManager(); + if (!asset_manager) { + return SDL_SetError("Couldn't create asset manager"); + } + } + + // this is sort of messy, but there isn't a stat()-like interface to the Assets. + AAssetDir *adir = AAssetManager_openDir(asset_manager, path); + if (adir) { // it's a directory! + AAssetDir_close(adir); + info->type = SDL_PATHTYPE_DIRECTORY; + info->size = 0; + return true; + } + + AAsset *aasset = AAssetManager_open(asset_manager, path, AASSET_MODE_UNKNOWN); + if (aasset) { // it's a file! + info->type = SDL_PATHTYPE_FILE; + info->size = (Uint64) AAsset_getLength64(aasset); + AAsset_close(aasset); + return true; + } + + return SDL_SetError("Couldn't open asset '%s'", path); +} + bool Android_JNI_SetClipboardText(const char *text) { JNIEnv *env = Android_JNI_GetEnv(); diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index 925cc19994..b89dbb9f32 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -88,6 +88,8 @@ Sint64 Android_JNI_FileSeek(void *userdata, Sint64 offset, SDL_IOWhence whence); size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status); size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status); bool Android_JNI_FileClose(void *userdata); +bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata); +bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info); // Environment support void Android_JNI_GetManifestEnvironmentVariables(void); diff --git a/src/filesystem/posix/SDL_sysfsops.c b/src/filesystem/posix/SDL_sysfsops.c index 015b8d4b5a..64e47886a1 100644 --- a/src/filesystem/posix/SDL_sysfsops.c +++ b/src/filesystem/posix/SDL_sysfsops.c @@ -35,6 +35,10 @@ #include #include +#ifdef SDL_PLATFORM_ANDROID +#include "../../core/android/SDL_android.h" +#endif + bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata) { char *pathwithsep = NULL; @@ -51,8 +55,14 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback DIR *dir = opendir(pathwithsep); if (!dir) { + #ifdef SDL_PLATFORM_ANDROID // Maybe it's an asset...? + const bool retval = Android_JNI_EnumerateAssetDirectory(pathwithsep, cb, userdata); + SDL_free(pathwithsep); + return retval; + #else SDL_free(pathwithsep); return SDL_SetError("Can't open directory: %s", strerror(errno)); + #endif } // make sure there's a path separator at the end now for the actual callback. @@ -173,7 +183,11 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) struct stat statbuf; const int rc = stat(path, &statbuf); if (rc < 0) { + #ifdef SDL_PLATFORM_ANDROID // Maybe it's an asset...? + return Android_JNI_GetAssetPathInfo(path, info); + #else return SDL_SetError("Can't stat: %s", strerror(errno)); + #endif } else if (S_ISREG(statbuf.st_mode)) { info->type = SDL_PATHTYPE_FILE; info->size = (Uint64) statbuf.st_size; @@ -203,7 +217,7 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) return true; } -// Note that this isn't actually part of filesystem, not fsops, but everything that uses posix fsops uses this implementation, even with separate filesystem code. +// Note that this is actually part of filesystem, not fsops, but everything that uses posix fsops uses this implementation, even with separate filesystem code. char *SDL_SYS_GetCurrentDirectory(void) { size_t buflen = 64;