From 9672f5b68b1f3f438bb86c65f8a3c198eb4b9eb1 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 30 Apr 2026 19:59:15 -0400 Subject: [PATCH] android: Change how apps access their APK's "assets" directory. Now they can explicitly access it with "assets://" filenames, and SDL_GetBasePath() returns "assets://" Fixes #15347. Fixes #5044. --- docs/README-android.md | 18 +++++++++++++ src/core/android/SDL_android.c | 25 ++++++++++++++---- src/filesystem/SDL_filesystem.c | 30 ++++++++++++++-------- src/filesystem/android/SDL_sysfilesystem.c | 2 +- src/filesystem/posix/SDL_sysfsops.c | 16 +++++++++--- src/io/SDL_iostream.c | 5 ++-- 6 files changed, 72 insertions(+), 24 deletions(-) diff --git a/docs/README-android.md b/docs/README-android.md index 61e289fee8..d3526c0247 100644 --- a/docs/README-android.md +++ b/docs/README-android.md @@ -212,6 +212,24 @@ Any files you put in the "app/src/main/assets" directory of your project directory will get bundled into the application package and you can load them using the standard functions in SDL_iostream.h. +As of SDL 3.6.0, SDL APIs, such as SDL_EnumerateDirectory() and +SDL_IOFromFile(), understand paths that are prefixed with "assets://" and will +look for paths exclusively inside the APK's "assets" directory. Since this is +where app-specific data files are meant to be located, SDL_GetBasePath() on +Android now returns "assets://" to make this work as expected across platforms. +Note that SDL 3.2.28 to 3.6.0 returned "./" on Android, and before that, +SDL_GetBasePath() always returned NULL on this platform. + +Obviously, paths prefixed with "assets://" are only useful to SDL; other APIs, +like fopen(), will not understand them at all. + +As an alternate approach: SDL APIs on Android treat relative paths in a +special way. It will look for files under the path returned by +SDL_GetAndroidInternalStoragePath() first, and failing that, will attempt to +look for them as if they were prefixed by "assets://", with the relative path +starting in the base of the assets tree. Absolute paths never check against +internal storage or assets. + There are also a few Android specific functions that allow you to get other useful paths for saving and loading data: * SDL_GetAndroidInternalStoragePath() diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 3e24056bd5..27289bbba0 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -1901,9 +1901,16 @@ static APKNode *FindAPKChildNode(APKNode *parent, const char *child) static const APKNode *FindAPKNode(const char *constpath) { + //SDL_Log("FindAPKNode('%s') ...", constpath); + if (SDL_strncmp(constpath, "assets://", 9) == 0) { + constpath += 9; + } + APKNode *parent = APKRootNode; if (!parent) { return NULL; + } else if (*constpath == '\0') { + return parent; } const size_t pathlen = SDL_strlen(constpath); @@ -2362,12 +2369,20 @@ static void Internal_Android_Destroy_AssetManager(void) static const char *GetAssetPath(const char *path) { - if (path && path[0] == '.' && path[1] == '/') { - path += 2; - while (*path == '/') { - ++path; - } + if (!path) { + return NULL; } + + if (path[0] == '.' && ((path[1] == '/') || (path[1] == '\0'))) { + path++; + } else if (SDL_strncmp(path, "assets://", 9) == 0) { + path += 9; + } + + while (*path == '/') { + ++path; + } + return path; } diff --git a/src/filesystem/SDL_filesystem.c b/src/filesystem/SDL_filesystem.c index dca0da5c99..bfce6a3292 100644 --- a/src/filesystem/SDL_filesystem.c +++ b/src/filesystem/SDL_filesystem.c @@ -371,20 +371,28 @@ char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_Glob return NULL; } - // if path ends with any slash, chop them off, so we don't confuse the pattern matcher later. char *pathcpy = NULL; size_t pathlen = SDL_strlen(path); - if ((pathlen > 1) && ((path[pathlen-1] == '/') || (path[pathlen-1] == '\\'))) { - pathcpy = SDL_strdup(path); - if (!pathcpy) { - return NULL; + + // if path ends with any slash, chop them off, so we don't confuse the pattern matcher later. + #ifdef SDL_PLATFORM_ANDROID + if (SDL_strcmp(path, "assets://") == 0) { // don't chop '//' off this if we're looking for the root of the asset tree. + pathlen--; // we'll add a 1 again later. + } else + #endif + { + if ((pathlen > 1) && ((path[pathlen-1] == '/') || (path[pathlen-1] == '\\'))) { + pathcpy = SDL_strdup(path); + if (!pathcpy) { + return NULL; + } + char *ptr = &pathcpy[pathlen-1]; + while ((ptr > pathcpy) && ((*ptr == '/') || (*ptr == '\\'))) { + *(ptr--) = '\0'; + --pathlen; + } + path = pathcpy; } - char *ptr = &pathcpy[pathlen-1]; - while ((ptr > pathcpy) && ((*ptr == '/') || (*ptr == '\\'))) { - *(ptr--) = '\0'; - --pathlen; - } - path = pathcpy; } if (!pattern) { diff --git a/src/filesystem/android/SDL_sysfilesystem.c b/src/filesystem/android/SDL_sysfilesystem.c index 7eddd28774..1f50abf165 100644 --- a/src/filesystem/android/SDL_sysfilesystem.c +++ b/src/filesystem/android/SDL_sysfilesystem.c @@ -31,7 +31,7 @@ char *SDL_SYS_GetBasePath(void) { - return SDL_strdup("./"); + return SDL_strdup("assets://"); } char *SDL_SYS_GetPrefPath(const char *org, const char *app) diff --git a/src/filesystem/posix/SDL_sysfsops.c b/src/filesystem/posix/SDL_sysfsops.c index fd42876215..6183485b22 100644 --- a/src/filesystem/posix/SDL_sysfsops.c +++ b/src/filesystem/posix/SDL_sysfsops.c @@ -47,6 +47,13 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback #if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) if (*path != '/') { #ifdef SDL_PLATFORM_ANDROID + if (SDL_strncmp(path, "assets://", 9) == 0) { + char *pathwithsep = NULL; + SDL_asprintf(&pathwithsep, "%s%s", path, (path[SDL_strlen(path) - 1] != '/') ? "/" : ""); + const bool retval = pathwithsep ? Android_JNI_EnumerateAssetDirectory(pathwithsep, cb, userdata) : false; + SDL_free(pathwithsep); + return retval; + } SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); #elif defined(SDL_PLATFORM_IOS) char *base = SDL_GetPrefPath("", ""); @@ -89,14 +96,13 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback DIR *dir = opendir(pathwithsep); if (!dir) { -#ifdef SDL_PLATFORM_ANDROID // Maybe it's an asset...? +#ifdef SDL_PLATFORM_ANDROID // Maybe it's an asset... that didn't use an "assets://" URL? const bool retval = Android_JNI_EnumerateAssetDirectory(pathwithsep + extralen, cb, userdata); SDL_free(pathwithsep); return retval; -#else +#endif SDL_free(pathwithsep); return SDL_SetError("Can't open directory: %s", strerror(errno)); -#endif } SDL_EnumerationResult result = SDL_ENUM_CONTINUE; @@ -342,6 +348,8 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) #ifdef SDL_PLATFORM_ANDROID if (*path == '/') { rc = stat(path, &statbuf); + } else if (SDL_strncmp(path, "assets://", 9) == 0) { + return Android_JNI_GetAssetPathInfo(path, info); } else { char *apath = NULL; SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path); @@ -351,7 +359,7 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info) rc = stat(apath, &statbuf); SDL_free(apath); } - if (rc < 0) { + if (rc < 0) { // Maybe it's an asset... that didn't use an "assets://" URL? return Android_JNI_GetAssetPathInfo(path, info); } #elif defined(SDL_PLATFORM_IOS) diff --git a/src/io/SDL_iostream.c b/src/io/SDL_iostream.c index 4f3b0606ad..6b19364678 100644 --- a/src/io/SDL_iostream.c +++ b/src/io/SDL_iostream.c @@ -1021,7 +1021,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) } return SDL_IOFromFP(fp, true); - } else { + } else if (SDL_strncmp(file, "assets://", 9) != 0) { // Try opening it from internal storage if it's a relative path char *path = NULL; SDL_asprintf(&path, "%s/%s", SDL_GetAndroidInternalStoragePath(), file); @@ -1040,8 +1040,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) } #endif // HAVE_STDIO_H - // Try to open the file from the asset system - + // Try to open the file from the asset system? void *iodata = NULL; if (!Android_JNI_FileOpen(&iodata, file, mode)) { return NULL;