11#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_PHYSFS_IMPL_ONCE) && defined(SDL_LIBRETRO_ENABLE_PHYSFS) && !defined(SDL_LIBRETRO_DISABLE_PHYSFS)
12#define SDL_LIBRETRO_PHYSFS_IMPL_ONCE
14#ifdef SDL_LIBRETRO_DISABLE_VFS
15#error "SDL_libretro_physfs requires the SDL VFS; do not define SDL_LIBRETRO_DISABLE_VFS"
18#ifndef SDL_LIBRETRO_PHYSFS_NO_SDL_PHYSFS_IMPLEMENTATION
19#define SDL_PHYSFS_IMPLEMENTATION
21#include "SDL_PhysFS.h"
23#ifndef SDL_PHYSFS_PHYSFS_H
24#define SDL_PHYSFS_PHYSFS_H "physfs.h"
26#include SDL_PHYSFS_PHYSFS_H
28#ifndef SDL_LIBRETRO_PHYSFS_MOUNT_POINT
34#define SDL_LIBRETRO_PHYSFS_MOUNT_POINT "game"
45static struct retro_vfs_file_handle* SDL_Libretro_PhysFS_VFS_Open(
const char* path,
unsigned mode,
unsigned hints) {
47 if (path && (mode & RETRO_VFS_FILE_ACCESS_WRITE) == 0 &&
48 PHYSFS_stat(path, &st) && st.filetype == PHYSFS_FILETYPE_REGULAR) {
49 SDL_IOStream* io = SDL_PhysFS_IOFromFile(path);
51 return SDL_Libretro_VFS_WrapIO(io, path, mode);
54 return SDL_Libretro_VFS_Open(path, mode, hints);
63static int SDL_Libretro_PhysFS_VFS_Stat64(
const char* path, int64_t* size) {
65 if (path && PHYSFS_stat(path, &st)) {
66 int flags = RETRO_VFS_STAT_IS_VALID;
67 if (st.filetype == PHYSFS_FILETYPE_DIRECTORY) {
68 flags |= RETRO_VFS_STAT_IS_DIRECTORY;
71 *size = (int64_t)st.filesize;
75 return SDL_Libretro_VFS_Stat64(path, size);
83static int SDL_Libretro_PhysFS_VFS_Stat(
const char* path, int32_t* size) {
85 int out = SDL_Libretro_PhysFS_VFS_Stat64(path, &outSize);
87 *size = SDL_Libretro_VFS_ClampStatSize(path, outSize);
98static struct retro_vfs_dir_handle* SDL_Libretro_PhysFS_VFS_Opendir(
const char* dir,
bool include_hidden) {
100 if (!dir || !PHYSFS_stat(dir, &st) || st.filetype != PHYSFS_FILETYPE_DIRECTORY) {
101 return SDL_Libretro_VFS_Opendir(dir, include_hidden);
104 char** entries = PHYSFS_enumerateFiles(dir);
105 if (!entries)
return NULL;
108 while (entries[count]) count++;
110 struct retro_vfs_dir_handle* h = (
struct retro_vfs_dir_handle*)SDL_calloc(1,
sizeof(*h));
111 char** names = count > 0 ? (
char**)SDL_calloc(count,
sizeof(
char*)) : NULL;
112 bool* isDir = count > 0 ? (
bool*)SDL_calloc(count,
sizeof(
bool)) : NULL;
113 if (!h || (count > 0 && (!names || !isDir))) {
117 PHYSFS_freeList(entries);
122 for (
size_t i = 0; i < count; i++) {
123 if (!include_hidden && entries[i][0] ==
'.')
continue;
124 names[stored] = SDL_strdup(entries[i]);
125 if (!names[stored])
continue;
126 char full[SDL_LIBRETRO_MAX_PATH];
127 SDL_snprintf(full,
sizeof(full),
"%s/%s", dir, entries[i]);
128 PHYSFS_Stat entryStat;
129 isDir[stored] = PHYSFS_stat(full, &entryStat) && entryStat.filetype == PHYSFS_FILETYPE_DIRECTORY;
132 PHYSFS_freeList(entries);
134 h->dir = SDL_strdup(dir);
136 for (
size_t i = 0; i < stored; i++) SDL_free(names[i]);
145 h->index = (size_t)-1;
153static void SDL_Libretro_PhysFS_ClearMount(SDL_Libretro* lr) {
155 if (lr->physfsMountSource[0] !=
'\0') {
156 SDL_PhysFS_Unmount(lr->physfsMountSource);
157 lr->physfsMountSource[0] =
'\0';
179 if (!lr)
return false;
181 if (!lr->physfsReady) {
182 if (!SDL_PhysFS_Init(NULL)) {
185 lr->physfsReady =
true;
188 struct retro_vfs_interface vfs = {0};
189 vfs.open = SDL_Libretro_PhysFS_VFS_Open;
190 vfs.stat = SDL_Libretro_PhysFS_VFS_Stat;
191 vfs.stat_64 = SDL_Libretro_PhysFS_VFS_Stat64;
192 vfs.opendir = SDL_Libretro_PhysFS_VFS_Opendir;
210 SDL_Libretro_PhysFS_ClearMount(lr);
212 if (lr->physfsReady) {
214 lr->physfsReady =
false;
223typedef struct SDL_Libretro_PhysFS_FileList {
227} SDL_Libretro_PhysFS_FileList;
233static void SDL_Libretro_PhysFS_CollectFiles(
const char* dir, SDL_Libretro_PhysFS_FileList* list) {
234 char** entries = PHYSFS_enumerateFiles(dir);
235 if (!entries)
return;
237 for (
char** entry = entries; *entry; entry++) {
238 char full[SDL_LIBRETRO_MAX_PATH];
239 SDL_snprintf(full,
sizeof(full),
"%s/%s", dir, *entry);
241 if (!PHYSFS_stat(full, &st))
continue;
242 if (st.filetype == PHYSFS_FILETYPE_DIRECTORY) {
243 SDL_Libretro_PhysFS_CollectFiles(full, list);
245 else if (st.filetype == PHYSFS_FILETYPE_REGULAR) {
246 if (list->count >= list->capacity) {
247 int newCapacity = list->capacity > 0 ? list->capacity * 2 : 16;
248 char** grown = (
char**)SDL_realloc(list->paths, (
size_t)newCapacity *
sizeof(
char*));
249 if (!grown)
continue;
251 list->capacity = newCapacity;
253 list->paths[list->count] = SDL_strdup(full);
254 if (list->paths[list->count]) list->count++;
257 PHYSFS_freeList(entries);
266static bool SDL_Libretro_PhysFS_IsLoadableExtension(
const SDL_Libretro* lr,
const char* ext,
const char* validExts) {
267 if (validExts[0] !=
'\0') {
268 return SDL_Libretro_ExtensionInList(ext, validExts);
270 for (
unsigned c = 0; c < lr->coreLibraryCount; c++) {
271 if (SDL_Libretro_ExtensionInList(ext, lr->coreLibrary[c].supported_extensions)) {
275 return lr->coreLibraryCount == 0;
301static bool SDL_Libretro_PhysFS_PickContent(SDL_Libretro* lr,
const char* archivePath,
char* dst,
size_t dstSize) {
302 SDL_Libretro_PhysFS_FileList list = {0};
307 bool hasExts = validExts[0] !=
'\0';
310 char archiveName[SDL_LIBRETRO_MAX_PATH];
314 const char* found = NULL;
317 static const char* discExts[] = {
"m3u",
"cue"};
318 for (
size_t d = 0; !found && d < SDL_arraysize(discExts); d++) {
319 if (hasExts && !SDL_Libretro_ExtensionInList(discExts[d], validExts))
continue;
320 for (
int i = 0; !found && i < list.count; i++) {
321 if (SDL_strcasecmp(SDL_Libretro_GetExtension(list.paths[i]), discExts[d]) == 0) {
322 found = list.paths[i];
329 for (
int i = 0; !found && i < list.count; i++) {
330 char entryName[SDL_LIBRETRO_MAX_PATH];
332 if (SDL_strcasecmp(entryName, archiveName) == 0 &&
333 SDL_Libretro_PhysFS_IsLoadableExtension(lr, SDL_Libretro_GetExtension(list.paths[i]), validExts)) {
334 found = list.paths[i];
339 for (
int i = 0; !found && i < list.count; i++) {
340 if (SDL_Libretro_PhysFS_IsLoadableExtension(lr, SDL_Libretro_GetExtension(list.paths[i]), validExts)) {
341 found = list.paths[i];
346 if (!found && !hasExts && list.count > 0) {
347 found = list.paths[0];
351 SDL_strlcpy(dst, found, dstSize);
355 for (
int i = 0; i < list.count; i++) {
356 SDL_free(list.paths[i]);
358 SDL_free(list.paths);
360 return found != NULL;
379static bool SDL_Libretro_PhysFS_ExtractArchive(SDL_Libretro* lr,
const char* archivePath,
char* dir,
size_t dirSize) {
380 char tempDir[SDL_LIBRETRO_MAX_PATH];
381 if (!SDL_Libretro_GetTempDirectory(lr, tempDir,
sizeof(tempDir))) {
384 char name[SDL_LIBRETRO_MAX_PATH];
386 SDL_strlcpy(name,
"content",
sizeof(name));
390 SDL_snprintf(dir, dirSize,
"%s%s/", tempDir, name);
391 for (
int attempt = 1; SDL_GetPathInfo(dir, NULL); attempt++) {
393 return SDL_SetError(
"[SDL_Libretro] Could not find a free directory name for '%s' in '%s'", name, tempDir);
395 SDL_snprintf(dir, dirSize,
"%s%s-%d/", tempDir, name, attempt);
398 SDL_Libretro_PhysFS_FileList list = {0};
401 bool ok = SDL_CreateDirectory(dir);
402 for (
int i = 0; ok && i < list.count; i++) {
404 char path[SDL_LIBRETRO_MAX_PATH];
405 SDL_snprintf(path,
sizeof(path),
"%s%s", dir, list.paths[i] + prefixLen);
408 char* slash = SDL_strrchr(path,
'/');
409 if (slash && slash > path + SDL_strlen(dir) - 1) {
411 ok = SDL_CreateDirectory(path);
416 void* data = ok ? SDL_LoadFile_IO(SDL_PhysFS_IOFromFile(list.paths[i]), &size,
true) : NULL;
417 ok = data != NULL && SDL_SaveFile(path, data, size);
420 SDL_SetError(
"[SDL_Libretro] Failed to extract '%s' to '%s': %s", list.paths[i], path, SDL_GetError());
424 for (
int i = 0; i < list.count; i++) {
425 SDL_free(list.paths[i]);
427 SDL_free(list.paths);
430 SDL_Libretro_RemoveTree(dir);
471 if (!lr)
return false;
477 SDL_Libretro_PhysFS_ClearMount(lr);
481 return SDL_Libretro_LoadGameFile(lr, NULL);
485 if (SDL_strcasecmp(SDL_Libretro_GetExtension(gamePath),
"zip") != 0) {
486 return SDL_Libretro_LoadGameFile(lr, gamePath);
491 return SDL_Libretro_LoadGameFile(lr, gamePath);
496 if (lr->core.blockExtract || SDL_Libretro_ExtensionInList(
"zip", lr->core.validExtensions)) {
497 return SDL_Libretro_LoadGameFile(lr, gamePath);
505 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
506 const SDL_Libretro_CoreInfo* info = &lr->coreLibrary[i];
507 if (!SDL_Libretro_ExtensionInList(
"zip", info->supported_extensions))
continue;
509 return SDL_Libretro_LoadGameFile(lr, gamePath);
517 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Failed to mount '%s': %s", gamePath, SDL_GetError());
518 return SDL_Libretro_LoadGameFile(lr, gamePath);
522 SDL_strlcpy(lr->physfsMountSource, gamePath,
sizeof(lr->physfsMountSource));
525 char virtualPath[SDL_LIBRETRO_MAX_PATH];
526 if (!SDL_Libretro_PhysFS_PickContent(lr, gamePath, virtualPath,
sizeof(virtualPath))) {
527 SDL_Libretro_PhysFS_ClearMount(lr);
528 SDL_SetError(
"[SDL_Libretro] No suitable content found inside '%s'", gamePath);
534 SDL_Libretro_PhysFS_ClearMount(lr);
540 if (SDL_Libretro_ContentNeedsFullpath(lr, SDL_Libretro_GetExtension(virtualPath)) && lr->core.usedVFS) {
543 result = SDL_Libretro_LoadGameFile(lr, virtualPath);
545 else if (SDL_Libretro_ContentNeedsFullpath(lr, SDL_Libretro_GetExtension(virtualPath))) {
548 char dir[SDL_LIBRETRO_MAX_PATH];
549 result = SDL_Libretro_PhysFS_ExtractArchive(lr, gamePath, dir,
sizeof(dir));
551 char path[SDL_LIBRETRO_MAX_PATH];
553 result = SDL_Libretro_LoadGameFile(lr, path);
555 SDL_strlcpy(lr->core.contentTempDir, dir,
sizeof(lr->core.contentTempDir));
558 SDL_Libretro_RemoveTree(dir);
564 SDL_IOStream* io = SDL_PhysFS_IOFromFile(virtualPath);
569 SDL_Libretro_PhysFS_ClearMount(lr);
578 return SDL_SetError(
"SDL_Libretro_PhysFS not enabled");
588 return SDL_SetError(
"SDL_Libretro_PhysFS not enabled");
591static void SDL_Libretro_PhysFS_ClearMount(SDL_Libretro* lr) {
#define SDL_LIBRETRO_PHYSFS_MOUNT_POINT
PhysFS mount point for archives.
bool SDL_Libretro_IsCoreReady(const SDL_Libretro *lr)
Indicates whether a core is loaded.
size_t SDL_Libretro_GetFileName(char *dst, size_t dstSize, const char *path, bool withExtension)
Copy the file name portion of a path into a caller-provided buffer.
bool SDL_Libretro_PhysFS_LoadGame(SDL_Libretro *lr, const char *gamePath)
Archive-aware replacement for SDL_Libretro_LoadGame().
bool SDL_Libretro_PhysFS_Init(SDL_Libretro *lr)
Initialize PhysFS and route the libretro VFS through it.
bool SDL_Libretro_LoadGame_IO(SDL_Libretro *lr, SDL_IOStream *src, const char *path, bool closeio)
Loads a game from an SDL_IOStream.
void SDL_Libretro_SetVFS(SDL_Libretro *lr, void *vfs)
Set the Virtual File System the core uses for file access.
void SDL_Libretro_PhysFS_Quit(SDL_Libretro *lr)
Tear down PhysFS, unmounting any active archive and restoring the plain SDL VFS on the given context.
void SDL_Libretro_UnloadGame(SDL_Libretro *lr)
Unloads the actively loaded game.
bool SDL_Libretro_LoadCore(SDL_Libretro *lr, const char *core)
Loads a libretro core.