7#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_VFS_IMPL_ONCE)
8#define SDL_LIBRETRO_VFS_IMPL_ONCE
19#define SDL_LIBRETRO_DISABLE_VFS
22#ifndef SDL_LIBRETRO_DISABLE_VFS
33struct retro_vfs_file_handle {
42struct retro_vfs_dir_handle {
53static const char* SDL_Libretro_VFS_GetPath(
struct retro_vfs_file_handle* stream) {
54 return stream ? stream->path : NULL;
63static struct retro_vfs_file_handle* SDL_Libretro_VFS_WrapIO(SDL_IOStream* io,
const char* path,
unsigned mode) {
64 struct retro_vfs_file_handle* h = (
struct retro_vfs_file_handle*)SDL_malloc(
sizeof(*h));
69 h->path = SDL_strdup(path);
83static struct retro_vfs_file_handle* SDL_Libretro_VFS_Open(
const char* path,
unsigned mode,
unsigned hints) {
85 if (!path)
return NULL;
88 SDL_PathInfo pathInfo;
89 if (SDL_GetPathInfo(path, &pathInfo) && pathInfo.type == SDL_PATHTYPE_DIRECTORY)
return NULL;
92 bool update = (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) != 0;
93 bool read = (mode & RETRO_VFS_FILE_ACCESS_READ) != 0;
94 bool write = (mode & RETRO_VFS_FILE_ACCESS_WRITE) != 0;
97 sdlMode = update ?
"r+b" :
"w+b";
101 sdlMode = update ?
"r+b" :
"wb";
107 SDL_IOStream* io = SDL_IOFromFile(path, sdlMode);
109 if (!io && write && !read && update) {
110 io = SDL_IOFromFile(path,
"wb");
112 if (!io)
return NULL;
114 return SDL_Libretro_VFS_WrapIO(io, path, mode);
120static int SDL_Libretro_VFS_Close(
struct retro_vfs_file_handle* stream) {
121 if (!stream)
return -1;
122 int ret = SDL_CloseIO(stream->io) ? 0 : -1;
123 SDL_free(stream->path);
131static int64_t SDL_Libretro_VFS_Size(
struct retro_vfs_file_handle* stream) {
132 if (!stream)
return -1;
133 int64_t sz = SDL_GetIOSize(stream->io);
134 return sz < 0 ? -1 : sz;
140static int64_t SDL_Libretro_VFS_Tell(
struct retro_vfs_file_handle* stream) {
141 if (!stream)
return -1;
142 return SDL_TellIO(stream->io);
151static int64_t SDL_Libretro_VFS_Seek(
struct retro_vfs_file_handle* stream, int64_t offset,
int seek_position) {
152 if (!stream)
return -1;
154 switch (seek_position) {
155 case RETRO_VFS_SEEK_POSITION_START: whence = SDL_IO_SEEK_SET;
break;
156 case RETRO_VFS_SEEK_POSITION_CURRENT: whence = SDL_IO_SEEK_CUR;
break;
157 case RETRO_VFS_SEEK_POSITION_END: whence = SDL_IO_SEEK_END;
break;
160 return SDL_SeekIO(stream->io, offset, whence) < 0 ? -1 : 0;
166static int64_t SDL_Libretro_VFS_Read(
struct retro_vfs_file_handle* stream,
void* s, uint64_t len) {
167 if (!stream || !s)
return -1;
168 size_t n = SDL_ReadIO(stream->io, s, (
size_t)len);
169 if (n == 0 && SDL_GetIOStatus(stream->io) == SDL_IO_STATUS_ERROR)
return -1;
176static int64_t SDL_Libretro_VFS_Write(
struct retro_vfs_file_handle* stream,
const void* s, uint64_t len) {
177 if (!stream || !s)
return -1;
178 size_t n = SDL_WriteIO(stream->io, s, (
size_t)len);
179 if (n == 0 && SDL_GetIOStatus(stream->io) == SDL_IO_STATUS_ERROR)
return -1;
186static int SDL_Libretro_VFS_Flush(
struct retro_vfs_file_handle* stream) {
187 if (!stream)
return -1;
188 return SDL_FlushIO(stream->io) ? 0 : -1;
194static int SDL_Libretro_VFS_Remove(
const char* path) {
195 return (path && SDL_RemovePath(path)) ? 0 : -1;
201static int SDL_Libretro_VFS_Rename(
const char* old_path,
const char* new_path) {
202 return (old_path && new_path && SDL_RenamePath(old_path, new_path)) ? 0 : -1;
214static int64_t SDL_Libretro_VFS_Truncate(
struct retro_vfs_file_handle* stream, int64_t length) {
215 if (!stream || length < 0)
return -1;
219 if ((stream->mode & RETRO_VFS_FILE_ACCESS_WRITE) == 0)
return -1;
221 int64_t cur_size = SDL_GetIOSize(stream->io);
222 if (cur_size < 0)
return -1;
225 int64_t orig_pos = SDL_TellIO(stream->io);
227 if (length >= cur_size) {
229 if (SDL_SeekIO(stream->io, 0, SDL_IO_SEEK_END) < 0)
return -1;
230 int64_t to_pad = length - cur_size;
233 SDL_memset(zeros, 0,
sizeof(zeros));
235 size_t chunk = (to_pad < (int64_t)
sizeof(zeros)) ? (
size_t)to_pad :
sizeof(zeros);
236 if (SDL_WriteIO(stream->io, zeros, chunk) != chunk)
return -1;
237 to_pad -= (int64_t)chunk;
240 if (orig_pos >= 0) SDL_SeekIO(stream->io, orig_pos, SDL_IO_SEEK_SET);
248 void* kept = SDL_malloc(length > 0 ? (
size_t)length : 1);
249 if (!kept)
return -1;
251 if (SDL_SeekIO(stream->io, 0, SDL_IO_SEEK_SET) < 0 ||
252 SDL_ReadIO(stream->io, kept, (
size_t)length) != (
size_t)length) {
254 if (orig_pos >= 0) SDL_SeekIO(stream->io, orig_pos, SDL_IO_SEEK_SET);
259 char* tmpPath = NULL;
260 if (SDL_asprintf(&tmpPath,
"%s.tmp", stream->path) < 0 || !tmpPath) {
262 if (orig_pos >= 0) SDL_SeekIO(stream->io, orig_pos, SDL_IO_SEEK_SET);
265 bool saved = SDL_SaveFile(tmpPath, kept, (
size_t)length);
269 SDL_RemovePath(tmpPath);
271 if (orig_pos >= 0) SDL_SeekIO(stream->io, orig_pos, SDL_IO_SEEK_SET);
278 bool renamed = SDL_RenamePath(tmpPath, stream->path);
280 SDL_CloseIO(stream->io);
282 renamed = SDL_RenamePath(tmpPath, stream->path);
283 if (!renamed) SDL_RemovePath(tmpPath);
289 SDL_IOStream* io = SDL_IOFromFile(stream->path,
"r+b");
290 if (!io) io = SDL_IOFromFile(stream->path,
"rb");
292 if (stream->io) SDL_CloseIO(stream->io);
296 SDL_SetError(
"[SDL_Libretro] VFS truncate could not reopen '%s'", stream->path);
302 if (orig_pos >= 0) SDL_SeekIO(stream->io, orig_pos, SDL_IO_SEEK_SET);
308 SDL_SeekIO(stream->io, orig_pos < length ? orig_pos : length, SDL_IO_SEEK_SET);
316static int SDL_Libretro_VFS_Stat64(
const char* path, int64_t* size) {
319 if (!SDL_GetPathInfo(path, &info)) {
323 int flags = RETRO_VFS_STAT_IS_VALID;
324 if (info.type == SDL_PATHTYPE_DIRECTORY) {
325 flags |= RETRO_VFS_STAT_IS_DIRECTORY;
339static int32_t SDL_Libretro_VFS_ClampStatSize(
const char* path, int64_t size) {
340 if (size > SDL_MAX_SINT32) {
341 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] VFS stat size %" SDL_PRIs64
" for '%s' exceeds int32. Clamping to SDL_MAX_SINT32.", size, path);
342 return SDL_MAX_SINT32;
344 return (int32_t)size;
350static int SDL_Libretro_VFS_Stat(
const char* path, int32_t* size) {
353 int out = SDL_Libretro_VFS_Stat64(path, &outSize);
355 *size = SDL_Libretro_VFS_ClampStatSize(path, outSize);
363static int SDL_Libretro_VFS_Mkdir(
const char* dir) {
366 if (SDL_GetPathInfo(dir, &info))
return -2;
367 return SDL_CreateDirectory(dir) ? 0 : -1;
380} SDL_Libretro_DirCollect;
385static SDL_EnumerationResult SDL_Libretro_DirCallback(
void* userdata,
const char* dirname,
const char* fname) {
387 SDL_Libretro_DirCollect* ctx = (SDL_Libretro_DirCollect*)userdata;
388 if (!ctx->includeHidden && fname[0] ==
'.')
return SDL_ENUM_CONTINUE;
390 if (ctx->count >= ctx->capacity) {
391 size_t newCap = ctx->capacity ? ctx->capacity * 2 : 32;
393 char** newNames = (
char**)SDL_realloc(ctx->names, newCap *
sizeof(
char*));
394 if (newNames) ctx->names = newNames;
395 bool* newIsDir = (
bool*)SDL_realloc(ctx->isDir, newCap *
sizeof(
bool));
396 if (newIsDir) ctx->isDir = newIsDir;
397 if (!newNames || !newIsDir)
return SDL_ENUM_FAILURE;
398 ctx->capacity = newCap;
401 ctx->names[ctx->count] = SDL_strdup(fname);
402 if (!ctx->names[ctx->count])
return SDL_ENUM_FAILURE;
405 char fullpath[SDL_LIBRETRO_MAX_PATH];
406 SDL_snprintf(fullpath,
sizeof(fullpath),
"%s/%s", ctx->dirPath, fname);
408 ctx->isDir[ctx->count] = SDL_GetPathInfo(fullpath, &info) && info.type == SDL_PATHTYPE_DIRECTORY;
410 return SDL_ENUM_CONTINUE;
416static struct retro_vfs_dir_handle* SDL_Libretro_VFS_Opendir(
const char* dir,
bool include_hidden) {
417 if (!dir)
return NULL;
419 SDL_Libretro_DirCollect ctx;
420 SDL_memset(&ctx, 0,
sizeof(ctx));
422 ctx.includeHidden = include_hidden;
424 if (!SDL_EnumerateDirectory(dir, SDL_Libretro_DirCallback, &ctx)) {
426 for (
size_t i = 0; i < ctx.count; i++) SDL_free(ctx.names[i]);
432 struct retro_vfs_dir_handle* h = (
struct retro_vfs_dir_handle*)SDL_malloc(
sizeof(*h));
434 for (
size_t i = 0; i < ctx.count; i++) SDL_free(ctx.names[i]);
439 h->dir = SDL_strdup(dir);
441 for (
size_t i = 0; i < ctx.count; i++) SDL_free(ctx.names[i]);
447 h->names = ctx.names;
448 h->isDir = ctx.isDir;
449 h->count = ctx.count;
450 h->index = (size_t)-1;
457static bool SDL_Libretro_VFS_Readdir(
struct retro_vfs_dir_handle* dirstream) {
458 if (!dirstream)
return false;
459 size_t next = (dirstream->index == (size_t)-1) ? 0 : dirstream->index + 1;
460 if (next >= dirstream->count)
return false;
461 dirstream->index = next;
468static const char* SDL_Libretro_VFS_DirentGetName(
struct retro_vfs_dir_handle* dirstream) {
469 if (!dirstream || dirstream->index >= dirstream->count)
return NULL;
470 return dirstream->names[dirstream->index];
476static bool SDL_Libretro_VFS_DirentIsDir(
struct retro_vfs_dir_handle* dirstream) {
477 if (!dirstream || dirstream->index >= dirstream->count)
return false;
478 return dirstream->isDir[dirstream->index];
484static int SDL_Libretro_VFS_Closedir(
struct retro_vfs_dir_handle* dirstream) {
485 if (!dirstream)
return -1;
486 for (
size_t i = 0; i < dirstream->count; i++) SDL_free(dirstream->names[i]);
487 SDL_free(dirstream->names);
488 SDL_free(dirstream->isDir);
489 SDL_free(dirstream->dir);
513 struct retro_vfs_interface temp = {0};
514 struct retro_vfs_interface* vfs = (
struct retro_vfs_interface*)vfs_interface;
519 lr->vfs_interface.get_path = vfs->get_path != NULL ? vfs->get_path : &SDL_Libretro_VFS_GetPath;
520 lr->vfs_interface.open = vfs->open != NULL ? vfs->open : &SDL_Libretro_VFS_Open;
521 lr->vfs_interface.close = vfs->close != NULL ? vfs->close : &SDL_Libretro_VFS_Close;
522 lr->vfs_interface.size = vfs->size != NULL ? vfs->size : &SDL_Libretro_VFS_Size;
523 lr->vfs_interface.tell = vfs->tell != NULL ? vfs->tell : &SDL_Libretro_VFS_Tell;
524 lr->vfs_interface.seek = vfs->seek != NULL ? vfs->seek : &SDL_Libretro_VFS_Seek;
525 lr->vfs_interface.read = vfs->read != NULL ? vfs->read : &SDL_Libretro_VFS_Read;
526 lr->vfs_interface.write = vfs->write != NULL ? vfs->write : &SDL_Libretro_VFS_Write;
527 lr->vfs_interface.flush = vfs->flush != NULL ? vfs->flush : &SDL_Libretro_VFS_Flush;
528 lr->vfs_interface.remove = vfs->remove != NULL ? vfs->remove : &SDL_Libretro_VFS_Remove;
529 lr->vfs_interface.rename = vfs->rename != NULL ? vfs->rename : &SDL_Libretro_VFS_Rename;
530 lr->vfs_interface.truncate = vfs->truncate != NULL ? vfs->truncate : &SDL_Libretro_VFS_Truncate;
531 lr->vfs_interface.stat = vfs->stat != NULL ? vfs->stat : &SDL_Libretro_VFS_Stat;
532 lr->vfs_interface.mkdir = vfs->mkdir != NULL ? vfs->mkdir : &SDL_Libretro_VFS_Mkdir;
533 lr->vfs_interface.opendir = vfs->opendir != NULL ? vfs->opendir : &SDL_Libretro_VFS_Opendir;
534 lr->vfs_interface.readdir = vfs->readdir != NULL ? vfs->readdir : &SDL_Libretro_VFS_Readdir;
535 lr->vfs_interface.dirent_get_name = vfs->dirent_get_name != NULL ? vfs->dirent_get_name : &SDL_Libretro_VFS_DirentGetName;
536 lr->vfs_interface.dirent_is_dir = vfs->dirent_is_dir != NULL ? vfs->dirent_is_dir : &SDL_Libretro_VFS_DirentIsDir;
537 lr->vfs_interface.closedir = vfs->closedir != NULL ? vfs->closedir : &SDL_Libretro_VFS_Closedir;
538 lr->vfs_interface.stat_64 = vfs->stat_64 != NULL ? vfs->stat_64 : &SDL_Libretro_VFS_Stat64;
565 lr->vfs_interface = *(
struct retro_vfs_interface*)vfs_interface;
568 SDL_zero(lr->vfs_interface);
void SDL_Libretro_SetVFS(SDL_Libretro *lr, void *vfs_interface)
Set the Virtual File System the core uses for file access.