SDL_Libretro
SDL3-power libretro frontend.
Loading...
Searching...
No Matches
SDL_libretro_vfs.h
Go to the documentation of this file.
1
7#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_VFS_IMPL_ONCE)
8#define SDL_LIBRETRO_VFS_IMPL_ONCE
9
10
11#ifdef __DOXYGEN
19#define SDL_LIBRETRO_DISABLE_VFS
20#endif
21
22#ifndef SDL_LIBRETRO_DISABLE_VFS
23
24/*
25 * SDL_libretro - libretro VFS interface backed by SDL_IOStream
26 *
27 * Provides GET_VFS_INTERFACE v1–v4.
28 */
29
33struct retro_vfs_file_handle {
34 SDL_IOStream* io;
35 char* path;
36 unsigned mode;
37};
38
42struct retro_vfs_dir_handle {
43 char* dir;
44 char** names;
45 bool* isDir;
46 size_t count;
47 size_t index;
48};
49
53static const char* SDL_Libretro_VFS_GetPath(struct retro_vfs_file_handle* stream) {
54 return stream ? stream->path : NULL;
55}
56
60static struct retro_vfs_file_handle* SDL_Libretro_VFS_Open(const char* path, unsigned mode, unsigned hints) {
61 (void)hints;
62 if (!path) return NULL;
63
64 // The VFS contract requires open() to fail on a directory; SDL_IOFromFile may otherwise succeed in opening one for read on some platforms.
65 SDL_PathInfo pathInfo;
66 if (SDL_GetPathInfo(path, &pathInfo) && pathInfo.type == SDL_PATHTYPE_DIRECTORY) return NULL;
67
68 const char* sdlMode;
69 bool update = (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) != 0;
70 bool read = (mode & RETRO_VFS_FILE_ACCESS_READ) != 0;
71 bool write = (mode & RETRO_VFS_FILE_ACCESS_WRITE) != 0;
72
73 if (read && write) {
74 sdlMode = update ? "r+b" : "w+b";
75 } else if (write) {
76 sdlMode = "wb";
77 } else {
78 sdlMode = "rb";
79 }
80
81 SDL_IOStream* io = SDL_IOFromFile(path, sdlMode);
82 if (!io) return NULL;
83
84 struct retro_vfs_file_handle* h = (struct retro_vfs_file_handle*)SDL_malloc(sizeof(*h));
85 if (!h) { SDL_CloseIO(io); return NULL; }
86 h->io = io;
87 h->path = SDL_strdup(path);
88 h->mode = mode;
89 return h;
90}
91
95static int SDL_Libretro_VFS_Close(struct retro_vfs_file_handle* stream) {
96 if (!stream) return -1;
97 int ret = SDL_CloseIO(stream->io) ? 0 : -1;
98 SDL_free(stream->path);
99 SDL_free(stream);
100 return ret;
101}
102
106static int64_t SDL_Libretro_VFS_Size(struct retro_vfs_file_handle* stream) {
107 if (!stream) return -1;
108 int64_t sz = SDL_GetIOSize(stream->io);
109 return sz < 0 ? -1 : sz;
110}
111
115static int64_t SDL_Libretro_VFS_Tell(struct retro_vfs_file_handle* stream) {
116 if (!stream) return -1;
117 return SDL_TellIO(stream->io);
118}
119
123static int64_t SDL_Libretro_VFS_Seek(struct retro_vfs_file_handle* stream, int64_t offset, int seek_position) {
124 if (!stream) return -1;
125 SDL_IOWhence whence;
126 switch (seek_position) {
127 case RETRO_VFS_SEEK_POSITION_START: whence = SDL_IO_SEEK_SET; break;
128 case RETRO_VFS_SEEK_POSITION_CURRENT: whence = SDL_IO_SEEK_CUR; break;
129 case RETRO_VFS_SEEK_POSITION_END: whence = SDL_IO_SEEK_END; break;
130 default: return -1;
131 }
132 return SDL_SeekIO(stream->io, offset, whence);
133}
134
138static int64_t SDL_Libretro_VFS_Read(struct retro_vfs_file_handle* stream, void* s, uint64_t len) {
139 if (!stream || !s) return -1;
140 size_t n = SDL_ReadIO(stream->io, s, (size_t)len);
141 if (n == 0 && SDL_GetIOStatus(stream->io) == SDL_IO_STATUS_ERROR) return -1;
142 return (int64_t)n;
143}
144
148static int64_t SDL_Libretro_VFS_Write(struct retro_vfs_file_handle* stream, const void* s, uint64_t len) {
149 if (!stream || !s) return -1;
150 size_t n = SDL_WriteIO(stream->io, s, (size_t)len);
151 if (n == 0 && SDL_GetIOStatus(stream->io) == SDL_IO_STATUS_ERROR) return -1;
152 return (int64_t)n;
153}
154
158static int SDL_Libretro_VFS_Flush(struct retro_vfs_file_handle* stream) {
159 if (!stream) return -1;
160 return SDL_FlushIO(stream->io) ? 0 : -1;
161}
162
166static int SDL_Libretro_VFS_Remove(const char* path) {
167 return (path && SDL_RemovePath(path)) ? 0 : -1;
168}
169
173static int SDL_Libretro_VFS_Rename(const char* old_path, const char* new_path) {
174 return (old_path && new_path && SDL_RenamePath(old_path, new_path)) ? 0 : -1;
175}
176
186static int64_t SDL_Libretro_VFS_Truncate(struct retro_vfs_file_handle* stream, int64_t length) {
187 if (!stream || length < 0) return -1;
188
189 int64_t cur_size = SDL_GetIOSize(stream->io);
190 if (cur_size < 0) return -1;
191
192 // Preserve the caller's read/write position across the operation.
193 int64_t orig_pos = SDL_TellIO(stream->io);
194
195 if (length >= cur_size) {
196 // Grow: seek to end and pad with zeros in chunks.
197 if (SDL_SeekIO(stream->io, 0, SDL_IO_SEEK_END) < 0) return -1;
198 int64_t to_pad = length - cur_size;
199 if (to_pad > 0) {
200 Uint8 zeros[4096];
201 SDL_memset(zeros, 0, sizeof(zeros));
202 while (to_pad > 0) {
203 size_t chunk = (to_pad < (int64_t)sizeof(zeros)) ? (size_t)to_pad : sizeof(zeros);
204 if (SDL_WriteIO(stream->io, zeros, chunk) != chunk) return -1;
205 to_pad -= (int64_t)chunk;
206 }
207 }
208 if (orig_pos >= 0) SDL_SeekIO(stream->io, orig_pos, SDL_IO_SEEK_SET);
209 return 0;
210 }
211
212 // Read the first `length` bytes, then rewrite the file. length == 0 (truncate to empty) needs no buffer.
213 Uint8* buf = NULL;
214 if (length > 0) {
215 buf = (Uint8*)SDL_malloc((size_t)length);
216 if (!buf) return -1;
217
218 if (SDL_SeekIO(stream->io, 0, SDL_IO_SEEK_SET) < 0) { SDL_free(buf); return -1; }
219 size_t got = SDL_ReadIO(stream->io, buf, (size_t)length);
220 if ((int64_t)got != length) { SDL_free(buf); return -1; }
221 }
222
223 // Reopen with write access matching the original handle so subsequent reads/writes keep working.
224 bool canWrite = (stream->mode & RETRO_VFS_FILE_ACCESS_WRITE) != 0;
225
226 SDL_CloseIO(stream->io);
227 stream->io = SDL_IOFromFile(stream->path, "wb");
228 if (!stream->io) { SDL_free(buf); stream->io = SDL_IOFromFile(stream->path, canWrite ? "r+b" : "rb"); return -1; }
229 // A short write would leave the file the wrong size; report that as failure.
230 if (length > 0 && (int64_t)SDL_WriteIO(stream->io, buf, (size_t)length) != length) {
231 SDL_free(buf);
232 SDL_CloseIO(stream->io);
233 stream->io = SDL_IOFromFile(stream->path, canWrite ? "r+b" : "rb");
234 return -1;
235 }
236 SDL_free(buf);
237
238 // Reopen in original mode for continued use.
239 SDL_CloseIO(stream->io);
240 stream->io = SDL_IOFromFile(stream->path, canWrite ? "r+b" : "rb");
241 if (!stream->io) return -1;
242
243 // Restore the position, clamped to the new (smaller) length.
244 if (orig_pos >= 0) {
245 SDL_SeekIO(stream->io, orig_pos < length ? orig_pos : length, SDL_IO_SEEK_SET);
246 }
247 return 0;
248}
249
253static int SDL_Libretro_VFS_Stat64(const char* path, int64_t* size) {
254 if (!path) return 0;
255 SDL_PathInfo info;
256 if (!SDL_GetPathInfo(path, &info)) {
257 return 0;
258 }
259
260 int flags = RETRO_VFS_STAT_IS_VALID;
261 if (info.type == SDL_PATHTYPE_DIRECTORY) {
262 flags |= RETRO_VFS_STAT_IS_DIRECTORY;
263 }
264
265 if (size != NULL) {
266 *size = info.size;
267 }
268 return flags;
269}
270
274static int SDL_Libretro_VFS_Stat(const char* path, int32_t* size) {
275 if (!path) return 0;
276 int64_t outSize = 0;
277 int out = SDL_Libretro_VFS_Stat64(path, &outSize);
278 if (size != NULL) {
279 if (outSize > SDL_MAX_SINT32) {
280 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
281 "[SDL_Libretro] VFS stat size %" SDL_PRIs64 " for '%s' exceeds int32. Clamping to SDL_MAX_SINT32.",
282 outSize, path);
283 *size = SDL_MAX_SINT32;
284 }
285 else {
286 *size = (int32_t)outSize;
287 }
288 }
289 return out;
290}
291
295static int SDL_Libretro_VFS_Mkdir(const char* dir) {
296 if (!dir) return -1;
297 SDL_PathInfo info;
298 if (SDL_GetPathInfo(dir, &info)) return -2; /* already exists */
299 return SDL_CreateDirectory(dir) ? 0 : -1;
300}
301
305typedef struct {
306 char** names;
307 bool* isDir;
308 size_t count;
309 size_t capacity;
310 const char* dirPath;
311 bool includeHidden;
312} SDL_Libretro_DirCollect;
313
317static SDL_EnumerationResult SDL_Libretro_DirCallback(void* userdata, const char* dirname, const char* fname) {
318 (void)dirname;
319 SDL_Libretro_DirCollect* ctx = (SDL_Libretro_DirCollect*)userdata;
320 if (!ctx->includeHidden && fname[0] == '.') return SDL_ENUM_CONTINUE;
321
322 if (ctx->count >= ctx->capacity) {
323 size_t newCap = ctx->capacity ? ctx->capacity * 2 : 32;
324 // Store each successful realloc back immediately: realloc frees the old block, so on partial failure the cleanup path must not be left holding a dangling pointer (use-after-free / double-free).
325 char** newNames = (char**)SDL_realloc(ctx->names, newCap * sizeof(char*));
326 if (newNames) ctx->names = newNames;
327 bool* newIsDir = (bool*)SDL_realloc(ctx->isDir, newCap * sizeof(bool));
328 if (newIsDir) ctx->isDir = newIsDir;
329 if (!newNames || !newIsDir) return SDL_ENUM_FAILURE;
330 ctx->capacity = newCap;
331 }
332
333 ctx->names[ctx->count] = SDL_strdup(fname);
334 if (!ctx->names[ctx->count]) return SDL_ENUM_FAILURE;
335
336 /* Determine if entry is a directory. */
337 char fullpath[SDL_LIBRETRO_MAX_PATH];
338 SDL_snprintf(fullpath, sizeof(fullpath), "%s/%s", ctx->dirPath, fname);
339 SDL_PathInfo info;
340 ctx->isDir[ctx->count] = SDL_GetPathInfo(fullpath, &info) && info.type == SDL_PATHTYPE_DIRECTORY;
341 ctx->count++;
342 return SDL_ENUM_CONTINUE;
343}
344
348static struct retro_vfs_dir_handle* SDL_Libretro_VFS_Opendir(const char* dir, bool include_hidden) {
349 if (!dir) return NULL;
350
351 SDL_Libretro_DirCollect ctx;
352 SDL_memset(&ctx, 0, sizeof(ctx));
353 ctx.dirPath = dir;
354 ctx.includeHidden = include_hidden;
355
356 if (!SDL_EnumerateDirectory(dir, SDL_Libretro_DirCallback, &ctx)) {
357 // Free partial results on failure.
358 for (size_t i = 0; i < ctx.count; i++) SDL_free(ctx.names[i]);
359 SDL_free(ctx.names);
360 SDL_free(ctx.isDir);
361 return NULL;
362 }
363
364 struct retro_vfs_dir_handle* h = (struct retro_vfs_dir_handle*)SDL_malloc(sizeof(*h));
365 if (!h) {
366 for (size_t i = 0; i < ctx.count; i++) SDL_free(ctx.names[i]);
367 SDL_free(ctx.names);
368 SDL_free(ctx.isDir);
369 return NULL;
370 }
371 h->dir = SDL_strdup(dir);
372 h->names = ctx.names;
373 h->isDir = ctx.isDir;
374 h->count = ctx.count;
375 h->index = (size_t)-1; // readdir advances before returning
376 return h;
377}
378
382static bool SDL_Libretro_VFS_Readdir(struct retro_vfs_dir_handle* dirstream) {
383 if (!dirstream) return false;
384 size_t next = (dirstream->index == (size_t)-1) ? 0 : dirstream->index + 1;
385 if (next >= dirstream->count) return false;
386 dirstream->index = next;
387 return true;
388}
389
393static const char* SDL_Libretro_VFS_DirentGetName(struct retro_vfs_dir_handle* dirstream) {
394 if (!dirstream || dirstream->index >= dirstream->count) return NULL;
395 return dirstream->names[dirstream->index];
396}
397
401static bool SDL_Libretro_VFS_DirentIsDir(struct retro_vfs_dir_handle* dirstream) {
402 if (!dirstream || dirstream->index >= dirstream->count) return false;
403 return dirstream->isDir[dirstream->index];
404}
405
409static int SDL_Libretro_VFS_Closedir(struct retro_vfs_dir_handle* dirstream) {
410 if (!dirstream) return -1;
411 for (size_t i = 0; i < dirstream->count; i++) SDL_free(dirstream->names[i]);
412 SDL_free(dirstream->names);
413 SDL_free(dirstream->isDir);
414 SDL_free(dirstream->dir);
415 SDL_free(dirstream);
416 return 0;
417}
418
427void SDL_Libretro_SetVFS(SDL_Libretro* lr, void* vfs_interface) {
428 if (!lr) return;
429 struct retro_vfs_interface temp = {0};
430 struct retro_vfs_interface* vfs = (struct retro_vfs_interface*)vfs_interface;
431 if (vfs == NULL) {
432 vfs = &temp;
433 }
434
435 lr->vfs_interface.get_path = vfs->get_path != NULL ? vfs->get_path : &SDL_Libretro_VFS_GetPath;
436 lr->vfs_interface.open = vfs->open != NULL ? vfs->open : &SDL_Libretro_VFS_Open;
437 lr->vfs_interface.close = vfs->close != NULL ? vfs->close : &SDL_Libretro_VFS_Close;
438 lr->vfs_interface.size = vfs->size != NULL ? vfs->size : &SDL_Libretro_VFS_Size;
439 lr->vfs_interface.tell = vfs->tell != NULL ? vfs->tell : &SDL_Libretro_VFS_Tell;
440 lr->vfs_interface.seek = vfs->seek != NULL ? vfs->seek : &SDL_Libretro_VFS_Seek;
441 lr->vfs_interface.read = vfs->read != NULL ? vfs->read : &SDL_Libretro_VFS_Read;
442 lr->vfs_interface.write = vfs->write != NULL ? vfs->write : &SDL_Libretro_VFS_Write;
443 lr->vfs_interface.flush = vfs->flush != NULL ? vfs->flush : &SDL_Libretro_VFS_Flush;
444 lr->vfs_interface.remove = vfs->remove != NULL ? vfs->remove : &SDL_Libretro_VFS_Remove;
445 lr->vfs_interface.rename = vfs->rename != NULL ? vfs->rename : &SDL_Libretro_VFS_Rename;
446 lr->vfs_interface.truncate = vfs->truncate != NULL ? vfs->truncate : &SDL_Libretro_VFS_Truncate;
447 lr->vfs_interface.stat = vfs->stat != NULL ? vfs->stat : &SDL_Libretro_VFS_Stat;
448 lr->vfs_interface.mkdir = vfs->mkdir != NULL ? vfs->mkdir : &SDL_Libretro_VFS_Mkdir;
449 lr->vfs_interface.opendir = vfs->opendir != NULL ? vfs->opendir : &SDL_Libretro_VFS_Opendir;
450 lr->vfs_interface.readdir = vfs->readdir != NULL ? vfs->readdir : &SDL_Libretro_VFS_Readdir;
451 lr->vfs_interface.dirent_get_name = vfs->dirent_get_name != NULL ? vfs->dirent_get_name : &SDL_Libretro_VFS_DirentGetName;
452 lr->vfs_interface.dirent_is_dir = vfs->dirent_is_dir != NULL ? vfs->dirent_is_dir : &SDL_Libretro_VFS_DirentIsDir;
453 lr->vfs_interface.closedir = vfs->closedir != NULL ? vfs->closedir : &SDL_Libretro_VFS_Closedir;
454 lr->vfs_interface.stat_64 = vfs->stat_64 != NULL ? vfs->stat_64 : &SDL_Libretro_VFS_Stat64;
455}
456
457#endif /* !SDL_LIBRETRO_DISABLE_VFS */
458
459#endif /* SDL_LIBRETRO_VFS_IMPL_ONCE */