1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#include "SDL_sysstorage.h"
25#include "../filesystem/SDL_sysfilesystem.h"
26
27// Available title storage drivers
28static TitleStorageBootStrap *titlebootstrap[] = {
29 &GENERIC_titlebootstrap,
30 NULL
31};
32
33// Available user storage drivers
34static UserStorageBootStrap *userbootstrap[] = {
35#ifdef SDL_STORAGE_STEAM
36 &STEAM_userbootstrap,
37#endif
38#ifdef SDL_STORAGE_PRIVATE
39 &PRIVATE_userbootstrap,
40#endif
41 &GENERIC_userbootstrap,
42 NULL
43};
44
45struct SDL_Storage
46{
47 SDL_StorageInterface iface;
48 void *userdata;
49};
50
51#define CHECK_STORAGE_MAGIC() \
52 if (!storage) { \
53 return SDL_SetError("Invalid storage container"); \
54 }
55
56#define CHECK_STORAGE_MAGIC_RET(result) \
57 if (!storage) { \
58 SDL_SetError("Invalid storage container"); \
59 return result; \
60 }
61
62// we don't make any effort to convert path separators here, because a)
63// everything including Windows will accept a '/' separator and b) that
64// conversion should probably happen in the storage backend anyhow.
65
66static bool ValidateStoragePath(const char *path)
67{
68 if (SDL_strchr(path, '\\')) {
69 return SDL_SetError("Windows-style path separators ('\\') not permitted, use '/' instead.");
70 }
71
72 const char *ptr;
73 const char *prev = path;
74 while ((ptr = SDL_strchr(prev, '/')) != NULL) {
75 if ((SDL_strncmp(prev, "./", 2) == 0) || (SDL_strncmp(prev, "../", 3) == 0)) {
76 return SDL_SetError("Relative paths not permitted");
77 }
78 prev = ptr + 1;
79 }
80
81 // check the last path element (or the only path element).
82 if ((SDL_strcmp(prev, ".") == 0) || (SDL_strcmp(prev, "..") == 0)) {
83 return SDL_SetError("Relative paths not permitted");
84 }
85
86 return true;
87}
88
89SDL_Storage *SDL_OpenTitleStorage(const char *override, SDL_PropertiesID props)
90{
91 SDL_Storage *storage = NULL;
92 int i = 0;
93
94 // Select the proper storage driver
95 const char *driver_name = SDL_GetHint(SDL_HINT_STORAGE_TITLE_DRIVER);
96 if (driver_name && *driver_name != 0) {
97 const char *driver_attempt = driver_name;
98 while (driver_attempt && *driver_attempt != 0 && !storage) {
99 const char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
100 size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt)
101 : SDL_strlen(driver_attempt);
102
103 for (i = 0; titlebootstrap[i]; ++i) {
104 if ((driver_attempt_len == SDL_strlen(titlebootstrap[i]->name)) &&
105 (SDL_strncasecmp(titlebootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) {
106 storage = titlebootstrap[i]->create(override, props);
107 break;
108 }
109 }
110
111 driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
112 }
113 } else {
114 for (i = 0; titlebootstrap[i]; ++i) {
115 storage = titlebootstrap[i]->create(override, props);
116 if (storage) {
117 break;
118 }
119 }
120 }
121 if (!storage) {
122 if (driver_name) {
123 SDL_SetError("%s not available", driver_name);
124 } else {
125 SDL_SetError("No available title storage driver");
126 }
127 }
128 return storage;
129}
130
131SDL_Storage *SDL_OpenUserStorage(const char *org, const char *app, SDL_PropertiesID props)
132{
133 SDL_Storage *storage = NULL;
134 int i = 0;
135
136 // Select the proper storage driver
137 const char *driver_name = SDL_GetHint(SDL_HINT_STORAGE_USER_DRIVER);
138 if (driver_name && *driver_name != 0) {
139 const char *driver_attempt = driver_name;
140 while (driver_attempt && *driver_attempt != 0 && !storage) {
141 const char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
142 size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt)
143 : SDL_strlen(driver_attempt);
144
145 for (i = 0; userbootstrap[i]; ++i) {
146 if ((driver_attempt_len == SDL_strlen(userbootstrap[i]->name)) &&
147 (SDL_strncasecmp(userbootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) {
148 storage = userbootstrap[i]->create(org, app, props);
149 break;
150 }
151 }
152
153 driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
154 }
155 } else {
156 for (i = 0; userbootstrap[i]; ++i) {
157 storage = userbootstrap[i]->create(org, app, props);
158 if (storage) {
159 break;
160 }
161 }
162 }
163 if (!storage) {
164 if (driver_name) {
165 SDL_SetError("%s not available", driver_name);
166 } else {
167 SDL_SetError("No available user storage driver");
168 }
169 }
170 return storage;
171}
172
173SDL_Storage *SDL_OpenFileStorage(const char *path)
174{
175 return GENERIC_OpenFileStorage(path);
176}
177
178SDL_Storage *SDL_OpenStorage(const SDL_StorageInterface *iface, void *userdata)
179{
180 SDL_Storage *storage;
181
182 if (!iface) {
183 SDL_InvalidParamError("iface");
184 return NULL;
185 }
186 if (iface->version < sizeof(*iface)) {
187 // Update this to handle older versions of this interface
188 SDL_SetError("Invalid interface, should be initialized with SDL_INIT_INTERFACE()");
189 return NULL;
190 }
191
192 storage = (SDL_Storage *)SDL_calloc(1, sizeof(*storage));
193 if (storage) {
194 SDL_copyp(&storage->iface, iface);
195 storage->userdata = userdata;
196 }
197 return storage;
198}
199
200bool SDL_CloseStorage(SDL_Storage *storage)
201{
202 bool result = true;
203
204 CHECK_STORAGE_MAGIC()
205
206 if (storage->iface.close) {
207 result = storage->iface.close(storage->userdata);
208 }
209 SDL_free(storage);
210 return result;
211}
212
213bool SDL_StorageReady(SDL_Storage *storage)
214{
215 CHECK_STORAGE_MAGIC_RET(false)
216
217 if (storage->iface.ready) {
218 return storage->iface.ready(storage->userdata);
219 }
220 return true;
221}
222
223bool SDL_GetStorageFileSize(SDL_Storage *storage, const char *path, Uint64 *length)
224{
225 SDL_PathInfo info;
226
227 if (SDL_GetStoragePathInfo(storage, path, &info)) {
228 if (length) {
229 *length = info.size;
230 }
231 return true;
232 } else {
233 if (length) {
234 *length = 0;
235 }
236 return false;
237 }
238}
239
240bool SDL_ReadStorageFile(SDL_Storage *storage, const char *path, void *destination, Uint64 length)
241{
242 CHECK_STORAGE_MAGIC()
243
244 if (!path) {
245 return SDL_InvalidParamError("path");
246 } else if (!ValidateStoragePath(path)) {
247 return false;
248 } else if (!storage->iface.read_file) {
249 return SDL_Unsupported();
250 }
251
252 return storage->iface.read_file(storage->userdata, path, destination, length);
253}
254
255bool SDL_WriteStorageFile(SDL_Storage *storage, const char *path, const void *source, Uint64 length)
256{
257 CHECK_STORAGE_MAGIC()
258
259 if (!path) {
260 return SDL_InvalidParamError("path");
261 } else if (!ValidateStoragePath(path)) {
262 return false;
263 } else if (!storage->iface.write_file) {
264 return SDL_Unsupported();
265 }
266
267 return storage->iface.write_file(storage->userdata, path, source, length);
268}
269
270bool SDL_CreateStorageDirectory(SDL_Storage *storage, const char *path)
271{
272 CHECK_STORAGE_MAGIC()
273
274 if (!path) {
275 return SDL_InvalidParamError("path");
276 } else if (!ValidateStoragePath(path)) {
277 return false;
278 } else if (!storage->iface.mkdir) {
279 return SDL_Unsupported();
280 }
281
282 return storage->iface.mkdir(storage->userdata, path);
283}
284
285bool SDL_EnumerateStorageDirectory(SDL_Storage *storage, const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata)
286{
287 CHECK_STORAGE_MAGIC()
288
289 if (!path) {
290 path = ""; // we allow NULL to mean "root of the storage tree".
291 }
292
293 if (!ValidateStoragePath(path)) {
294 return false;
295 } else if (!storage->iface.enumerate) {
296 return SDL_Unsupported();
297 }
298
299 return storage->iface.enumerate(storage->userdata, path, callback, userdata);
300}
301
302bool SDL_RemoveStoragePath(SDL_Storage *storage, const char *path)
303{
304 CHECK_STORAGE_MAGIC()
305
306 if (!path) {
307 return SDL_InvalidParamError("path");
308 } else if (!ValidateStoragePath(path)) {
309 return false;
310 } else if (!storage->iface.remove) {
311 return SDL_Unsupported();
312 }
313
314 return storage->iface.remove(storage->userdata, path);
315}
316
317bool SDL_RenameStoragePath(SDL_Storage *storage, const char *oldpath, const char *newpath)
318{
319 CHECK_STORAGE_MAGIC()
320
321 if (!oldpath) {
322 return SDL_InvalidParamError("oldpath");
323 } else if (!newpath) {
324 return SDL_InvalidParamError("newpath");
325 } else if (!ValidateStoragePath(oldpath)) {
326 return false;
327 } else if (!ValidateStoragePath(newpath)) {
328 return false;
329 } else if (!storage->iface.rename) {
330 return SDL_Unsupported();
331 }
332
333 return storage->iface.rename(storage->userdata, oldpath, newpath);
334}
335
336bool SDL_CopyStorageFile(SDL_Storage *storage, const char *oldpath, const char *newpath)
337{
338 CHECK_STORAGE_MAGIC()
339
340 if (!oldpath) {
341 return SDL_InvalidParamError("oldpath");
342 } else if (!newpath) {
343 return SDL_InvalidParamError("newpath");
344 } else if (!ValidateStoragePath(oldpath)) {
345 return false;
346 } else if (!ValidateStoragePath(newpath)) {
347 return false;
348 } else if (!storage->iface.copy) {
349 return SDL_Unsupported();
350 }
351
352 return storage->iface.copy(storage->userdata, oldpath, newpath);
353}
354
355bool SDL_GetStoragePathInfo(SDL_Storage *storage, const char *path, SDL_PathInfo *info)
356{
357 SDL_PathInfo dummy;
358
359 if (!info) {
360 info = &dummy;
361 }
362 SDL_zerop(info);
363
364 CHECK_STORAGE_MAGIC()
365
366 if (!path) {
367 return SDL_InvalidParamError("path");
368 } else if (!ValidateStoragePath(path)) {
369 return false;
370 } else if (!storage->iface.info) {
371 return SDL_Unsupported();
372 }
373
374 return storage->iface.info(storage->userdata, path, info);
375}
376
377Uint64 SDL_GetStorageSpaceRemaining(SDL_Storage *storage)
378{
379 CHECK_STORAGE_MAGIC_RET(0)
380
381 if (!storage->iface.space_remaining) {
382 SDL_Unsupported();
383 return 0;
384 }
385
386 return storage->iface.space_remaining(storage->userdata);
387}
388
389static bool GlobStorageDirectoryGetPathInfo(const char *path, SDL_PathInfo *info, void *userdata)
390{
391 return SDL_GetStoragePathInfo((SDL_Storage *) userdata, path, info);
392}
393
394static bool GlobStorageDirectoryEnumerator(const char *path, SDL_EnumerateDirectoryCallback cb, void *cbuserdata, void *userdata)
395{
396 return SDL_EnumerateStorageDirectory((SDL_Storage *) userdata, path, cb, cbuserdata);
397}
398
399char **SDL_GlobStorageDirectory(SDL_Storage *storage, const char *path, const char *pattern, SDL_GlobFlags flags, int *count)
400{
401 CHECK_STORAGE_MAGIC_RET(NULL)
402
403 if (!path) {
404 path = ""; // we allow NULL to mean "root of the storage tree".
405 }
406
407 if (!ValidateStoragePath(path)) {
408 return NULL;
409 }
410
411 return SDL_InternalGlobDirectory(path, pattern, flags, count, GlobStorageDirectoryEnumerator, GlobStorageDirectoryGetPathInfo, storage);
412}
413
414