From 9672f5b68b1f3f438bb86c65f8a3c198eb4b9eb1 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
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 61e289fee86e7..d3526c024797a 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 3e24056bd5f66..27289bbba042d 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 dca0da5c99aeb..bfce6a3292f93 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;
- }
- char *ptr = &pathcpy[pathlen-1];
- while ((ptr > pathcpy) && ((*ptr == '/') || (*ptr == '\\'))) {
- *(ptr--) = '\0';
- --pathlen;
+
+ // 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;
}
- path = pathcpy;
}
if (!pattern) {
diff --git a/src/filesystem/android/SDL_sysfilesystem.c b/src/filesystem/android/SDL_sysfilesystem.c
index 7eddd28774257..1f50abf165aea 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 fd42876215c4c..6183485b22daa 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 4f3b0606add5a..6b19364678c82 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;