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.
This commit is contained in:
Ryan C. Gordon
2026-04-30 19:59:15 -04:00
parent e8127a9a11
commit 9672f5b68b
6 changed files with 72 additions and 24 deletions

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;