From a18989c1e8932801b856f16f93d9d7a61faefe17 Mon Sep 17 00:00:00 2001 From: Giovanni Cascione Date: Tue, 4 Aug 2026 11:01:30 +0200 Subject: [PATCH] Add RETRO_ENVIRONMENT_GET_VFS_AUTHORIZED_LOCATIONS env --- frontend/drivers/platform_unix.c | 167 +++++++++++++++++++++++++++++ frontend/drivers/platform_unix.h | 4 + libretro-common/include/libretro.h | 45 ++++++++ runloop.c | 15 +++ 4 files changed, 231 insertions(+) diff --git a/frontend/drivers/platform_unix.c b/frontend/drivers/platform_unix.c index 8a827a25a76a..6d681caabfa0 100644 --- a/frontend/drivers/platform_unix.c +++ b/frontend/drivers/platform_unix.c @@ -68,6 +68,7 @@ #endif #include +#include #include #include #include @@ -127,6 +128,11 @@ static char app_dir[DIR_MAX_LENGTH]; unsigned storage_permissions = 0; struct android_app *g_android = NULL; static uint8_t g_platform_android_flags = 0; + +#ifdef HAVE_SAF +static struct retro_vfs_authorized_location *android_vfs_authorized_locations = NULL; +static size_t android_vfs_authorized_locations_count = 0; +#endif #else #define PROC_APM_PATH "/proc/apm" #define PROC_ACPI_BATTERY_PATH "/proc/acpi/battery" @@ -676,6 +682,160 @@ static void frontend_android_shutdown(bool unused) } #ifdef HAVE_SAF +static void android_vfs_authorized_locations_free(void) +{ + size_t i; + + if (!android_vfs_authorized_locations) + return; + + for (i = 0; i < android_vfs_authorized_locations_count; i++) + { + free((void*)android_vfs_authorized_locations[i].path); + free((void*)android_vfs_authorized_locations[i].label); + } + + free(android_vfs_authorized_locations); + android_vfs_authorized_locations = NULL; + android_vfs_authorized_locations_count = 0; +} + +static bool android_vfs_authorized_locations_refresh(void) +{ + JNIEnv *env; + jarray trees; + jsize trees_length; + jsize i; + + android_vfs_authorized_locations_free(); + + if (!g_android || !g_android->have_saf) + return false; + + env = jni_thread_getenv(); + if (!env) + return false; + + trees = (*env)->CallObjectMethod( + env, + g_android->activity->clazz, + g_android->getPersistedSafTrees); + + if ((*env)->ExceptionOccurred(env)) + { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return false; + } + + if (!trees) + return false; + + trees_length = (*env)->GetArrayLength(env, trees); + if ((*env)->ExceptionOccurred(env)) + { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + (*env)->DeleteLocalRef(env, trees); + return false; + } + + if (trees_length <= 0) + { + (*env)->DeleteLocalRef(env, trees); + return true; + } + + android_vfs_authorized_locations = + (struct retro_vfs_authorized_location*)calloc( + (size_t)trees_length, + sizeof(*android_vfs_authorized_locations)); + + if (!android_vfs_authorized_locations) + { + (*env)->DeleteLocalRef(env, trees); + return false; + } + + for (i = 0; i < trees_length; ++i) + { + jstring tree; + const char *tree_chars; + char *serialized_path; + + tree = (jstring)(*env)->GetObjectArrayElement(env, trees, i); + if ((*env)->ExceptionOccurred(env)) + { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + continue; + } + + tree_chars = (*env)->GetStringUTFChars(env, tree, NULL); + if ((*env)->ExceptionOccurred(env)) + { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + (*env)->DeleteLocalRef(env, tree); + continue; + } + + serialized_path = retro_vfs_path_join_saf(tree_chars, ""); + + if (serialized_path) + { + const char *label = msg_hash_to_str(MSG_REMOVABLE_STORAGE); + + android_vfs_authorized_locations[android_vfs_authorized_locations_count].path = serialized_path; + android_vfs_authorized_locations[android_vfs_authorized_locations_count].label = strdup(label ? label : "Storage"); + android_vfs_authorized_locations[android_vfs_authorized_locations_count].flags = 0; + + android_vfs_authorized_locations_count++; + } + + (*env)->ReleaseStringUTFChars(env, tree, tree_chars); + if ((*env)->ExceptionOccurred(env)) + { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + (*env)->DeleteLocalRef(env, tree); + if ((*env)->ExceptionOccurred(env)) + { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + } + + (*env)->DeleteLocalRef(env, trees); + if ((*env)->ExceptionOccurred(env)) + { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + return true; +} + +bool android_get_vfs_authorized_locations( + struct retro_vfs_authorized_locations *locations) +{ + if (!g_android || !g_android->have_saf) + return false; + + if (!android_vfs_authorized_locations && !android_vfs_authorized_locations_refresh()) + return false; + + if (!locations) + return true; + + locations->locations = android_vfs_authorized_locations; + locations->count = android_vfs_authorized_locations_count; + + return true; +} + void android_show_saf_tree_picker(void) { JNIEnv *env; @@ -730,6 +890,8 @@ JNIEXPORT void JNICALL Java_com_retroarch_browser_retroactivity_RetroActivityCom (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); } + + android_vfs_authorized_locations_refresh(); #endif } @@ -2175,6 +2337,8 @@ static void android_app_destroy(struct android_app *android_app) android_app->onRetroArchExit); #ifdef HAVE_SAF + android_vfs_authorized_locations_free(); + if (android_app->have_saf) retro_vfs_deinit_saf(); #endif @@ -2368,6 +2532,9 @@ static void frontend_unix_init(void *data) "getPersistedSafTrees", "()[Ljava/lang/String;"); android_app->have_saf = retro_vfs_init_saf(jni_thread_getenv, android_app->activity->clazz); + + if (android_app->have_saf) + android_vfs_authorized_locations_refresh(); #endif GET_OBJECT_CLASS(env, class, obj); diff --git a/frontend/drivers/platform_unix.h b/frontend/drivers/platform_unix.h index fd90fcdc2178..c73625333038 100644 --- a/frontend/drivers/platform_unix.h +++ b/frontend/drivers/platform_unix.h @@ -399,7 +399,11 @@ void frontend_android_get_version_sdk(int32_t *sdk); bool is_screen_reader_enabled(void); #ifdef HAVE_SAF +struct retro_vfs_authorized_locations; + void android_show_saf_tree_picker(void); +bool android_get_vfs_authorized_locations( + struct retro_vfs_authorized_locations *locations); #endif #endif diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index 06a6fe494d54..972a2d8257e1 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1714,6 +1714,24 @@ enum retro_mod */ #define RETRO_ENVIRONMENT_GET_VFS_INTERFACE (45 | RETRO_ENVIRONMENT_EXPERIMENTAL) +/** + * Returns a list of frontend-authorized filesystem locations. + * + * Paths returned by this call must be directly usable with the VFS interface, + * for example saf://... on Android. + * + * @param[out] data struct retro_vfs_authorized_locations *. + * The frontend owns the returned pointers. The core must copy strings + * if it needs to retain them. + * If \c data is \c NULL, the frontend should only return whether this + * environment callback is available. + * + * @return \c true if this environment call is available, + * \c false otherwise. + * @see RETRO_ENVIRONMENT_GET_VFS_INTERFACE + */ +#define RETRO_ENVIRONMENT_GET_VFS_AUTHORIZED_LOCATIONS (93 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /** * Returns an interface that the core can use * to set the state of any accessible device LEDs. @@ -3410,6 +3428,33 @@ struct retro_vfs_interface_info struct retro_vfs_interface *iface; }; +/** + * Represents a single frontend-authorized filesystem location. + * + * The \c path field must be directly usable through the frontend VFS + * interface, for example saf://... on Android. + * + * The frontend owns all returned pointers. Cores must copy strings if they + * need to retain them after the environment callback returns. + */ +struct retro_vfs_authorized_location +{ + const char *path; + const char *label; + unsigned flags; +}; + +/** + * Represents the list of frontend-authorized filesystem locations. + * + * This is returned by RETRO_ENVIRONMENT_GET_VFS_AUTHORIZED_LOCATIONS. + */ +struct retro_vfs_authorized_locations +{ + const struct retro_vfs_authorized_location *locations; + size_t count; +}; + /** @} */ /** @defgroup GET_HW_RENDER_INTERFACE Hardware Rendering Interface diff --git a/runloop.c b/runloop.c index f3f475725a03..c57caa9d72b0 100644 --- a/runloop.c +++ b/runloop.c @@ -100,6 +100,11 @@ #include "play_feature_delivery/play_feature_delivery.h" #endif +#if defined(ANDROID) && defined(HAVE_SAF) +bool android_get_vfs_authorized_locations( + struct retro_vfs_authorized_locations *locations); +#endif + #ifdef HAVE_PRESENCE #include "network/presence.h" #endif @@ -3194,6 +3199,16 @@ bool runloop_environment_cb(unsigned cmd, void *data) break; } + case RETRO_ENVIRONMENT_GET_VFS_AUTHORIZED_LOCATIONS: + { +#if defined(ANDROID) && defined(HAVE_SAF) + return android_get_vfs_authorized_locations( + (struct retro_vfs_authorized_locations*)data); +#else + return false; +#endif + } + case RETRO_ENVIRONMENT_GET_LED_INTERFACE: { struct retro_led_interface *ledintf = (struct retro_led_interface *)data;