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
26
27static char *GENERIC_INTERNAL_CreateFullPath(const char *base, const char *relative)
28{
29 char *result = NULL;
30 SDL_asprintf(&result, "%s%s", base ? base : "", relative);
31 return result;
32}
33
34static bool GENERIC_CloseStorage(void *userdata)
35{
36 SDL_free(userdata);
37 return true;
38}
39
40typedef struct GenericEnumerateData
41{
42 size_t base_len;
43 SDL_EnumerateDirectoryCallback real_callback;
44 void *real_userdata;
45} GenericEnumerateData;
46
47static SDL_EnumerationResult SDLCALL GENERIC_EnumerateDirectory(void *userdata, const char *dirname, const char *fname)
48{
49 // SDL_EnumerateDirectory will return the full path, so for Storage we
50 // can take the base directory and add its length to the dirname string,
51 // effectively trimming the root without having to strdup anything.
52 const GenericEnumerateData *wrap_data = (GenericEnumerateData *)userdata;
53
54 dirname += wrap_data->base_len; // skip the base, just return the part inside of the Storage.
55
56 #ifdef SDL_PLATFORM_WINDOWS
57 char *dirnamecpy = NULL;
58 const size_t slen = SDL_strlen(dirname);
59 if (slen && (dirname[slen - 1] == '\\')) {
60 dirnamecpy = SDL_strdup(dirname);
61 if (!dirnamecpy) {
62 return SDL_ENUM_FAILURE;
63 }
64 dirnamecpy[slen - 1] = '/'; // storage layer always uses '/' path separators.
65 dirname = dirnamecpy;
66 }
67 const SDL_EnumerationResult retval = wrap_data->real_callback(wrap_data->real_userdata, dirname, fname);
68 SDL_free(dirnamecpy);
69 return retval;
70 #else
71 return wrap_data->real_callback(wrap_data->real_userdata, dirname, fname);
72 #endif
73}
74
75static bool GENERIC_EnumerateStorageDirectory(void *userdata, const char *path, SDL_EnumerateDirectoryCallback callback, void *callback_userdata)
76{
77 bool result = false;
78 GenericEnumerateData wrap_data;
79
80 char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path);
81 if (fullpath) {
82 wrap_data.base_len = SDL_strlen((char *)userdata);
83 wrap_data.real_callback = callback;
84 wrap_data.real_userdata = callback_userdata;
85
86 result = SDL_EnumerateDirectory(fullpath, GENERIC_EnumerateDirectory, &wrap_data);
87
88 SDL_free(fullpath);
89 }
90 return result;
91}
92
93static bool GENERIC_GetStoragePathInfo(void *userdata, const char *path, SDL_PathInfo *info)
94{
95 bool result = false;
96
97 char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path);
98 if (fullpath) {
99 result = SDL_GetPathInfo(fullpath, info);
100
101 SDL_free(fullpath);
102 }
103 return result;
104}
105
106static bool GENERIC_ReadStorageFile(void *userdata, const char *path, void *destination, Uint64 length)
107{
108 bool result = false;
109
110 if (length > SDL_SIZE_MAX) {
111 return SDL_SetError("Read size exceeds SDL_SIZE_MAX");
112 }
113
114 char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path);
115 if (fullpath) {
116 SDL_IOStream *stream = SDL_IOFromFile(fullpath, "rb");
117 if (stream) {
118 // FIXME: Should SDL_ReadIO use u64 now...?
119 if (SDL_ReadIO(stream, destination, (size_t)length) == length) {
120 result = true;
121 } else {
122 SDL_SetError("File length did not exactly match the destination length");
123 }
124 SDL_CloseIO(stream);
125 }
126 SDL_free(fullpath);
127 }
128 return result;
129}
130
131static bool GENERIC_WriteStorageFile(void *userdata, const char *path, const void *source, Uint64 length)
132{
133 // TODO: Recursively create subdirectories with SDL_CreateDirectory
134 bool result = false;
135
136 if (length > SDL_SIZE_MAX) {
137 return SDL_SetError("Write size exceeds SDL_SIZE_MAX");
138 }
139
140 char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path);
141 if (fullpath) {
142 SDL_IOStream *stream = SDL_IOFromFile(fullpath, "wb");
143
144 if (stream) {
145 // FIXME: Should SDL_WriteIO use u64 now...?
146 if (SDL_WriteIO(stream, source, (size_t)length) == length) {
147 result = true;
148 } else {
149 SDL_SetError("Resulting file length did not exactly match the source length");
150 }
151 SDL_CloseIO(stream);
152 }
153 SDL_free(fullpath);
154 }
155 return result;
156}
157
158static bool GENERIC_CreateStorageDirectory(void *userdata, const char *path)
159{
160 // TODO: Recursively create subdirectories with SDL_CreateDirectory
161 bool result = false;
162
163 char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path);
164 if (fullpath) {
165 result = SDL_CreateDirectory(fullpath);
166
167 SDL_free(fullpath);
168 }
169 return result;
170}
171
172static bool GENERIC_RemoveStoragePath(void *userdata, const char *path)
173{
174 bool result = false;
175
176 char *fullpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, path);
177 if (fullpath) {
178 result = SDL_RemovePath(fullpath);
179
180 SDL_free(fullpath);
181 }
182 return result;
183}
184
185static bool GENERIC_RenameStoragePath(void *userdata, const char *oldpath, const char *newpath)
186{
187 bool result = false;
188
189 char *fulloldpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, oldpath);
190 char *fullnewpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, newpath);
191 if (fulloldpath && fullnewpath) {
192 result = SDL_RenamePath(fulloldpath, fullnewpath);
193 }
194 SDL_free(fulloldpath);
195 SDL_free(fullnewpath);
196
197 return result;
198}
199
200static bool GENERIC_CopyStorageFile(void *userdata, const char *oldpath, const char *newpath)
201{
202 bool result = false;
203
204 char *fulloldpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, oldpath);
205 char *fullnewpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, newpath);
206 if (fulloldpath && fullnewpath) {
207 result = SDL_CopyFile(fulloldpath, fullnewpath);
208 }
209 SDL_free(fulloldpath);
210 SDL_free(fullnewpath);
211
212 return result;
213}
214
215static Uint64 GENERIC_GetStorageSpaceRemaining(void *userdata)
216{
217 // TODO: There's totally a way to query a folder root's quota...
218 return SDL_MAX_UINT64;
219}
220
221static const SDL_StorageInterface GENERIC_title_iface = {
222 sizeof(SDL_StorageInterface),
223 GENERIC_CloseStorage,
224 NULL, // ready
225 GENERIC_EnumerateStorageDirectory,
226 GENERIC_GetStoragePathInfo,
227 GENERIC_ReadStorageFile,
228 NULL, // write_file
229 NULL, // mkdir
230 NULL, // remove
231 NULL, // rename
232 NULL, // copy
233 NULL // space_remaining
234};
235
236static SDL_Storage *GENERIC_Title_Create(const char *override, SDL_PropertiesID props)
237{
238 SDL_Storage *result = NULL;
239 char *basepath = NULL;
240
241 if (override != NULL) {
242 // make sure override has a path separator at the end. If you're not on Windows and used '\\', that's on you.
243 const size_t slen = SDL_strlen(override);
244 const bool need_sep = (!slen || ((override[slen-1] != '/') && (override[slen-1] != '\\')));
245 if (SDL_asprintf(&basepath, "%s%s", override, need_sep ? "/" : "") == -1) {
246 return NULL;
247 }
248 } else {
249 const char *base = SDL_GetBasePath();
250 basepath = base ? SDL_strdup(base) : NULL;
251 }
252
253 if (basepath != NULL) {
254 result = SDL_OpenStorage(&GENERIC_title_iface, basepath);
255 if (result == NULL) {
256 SDL_free(basepath); // otherwise CloseStorage will free it.
257 }
258 }
259
260 return result;
261}
262
263TitleStorageBootStrap GENERIC_titlebootstrap = {
264 "generic",
265 "SDL generic title storage driver",
266 GENERIC_Title_Create
267};
268
269static const SDL_StorageInterface GENERIC_user_iface = {
270 sizeof(SDL_StorageInterface),
271 GENERIC_CloseStorage,
272 NULL, // ready
273 GENERIC_EnumerateStorageDirectory,
274 GENERIC_GetStoragePathInfo,
275 GENERIC_ReadStorageFile,
276 GENERIC_WriteStorageFile,
277 GENERIC_CreateStorageDirectory,
278 GENERIC_RemoveStoragePath,
279 GENERIC_RenameStoragePath,
280 GENERIC_CopyStorageFile,
281 GENERIC_GetStorageSpaceRemaining
282};
283
284static SDL_Storage *GENERIC_User_Create(const char *org, const char *app, SDL_PropertiesID props)
285{
286 SDL_Storage *result;
287 char *prefpath = SDL_GetPrefPath(org, app);
288 if (prefpath == NULL) {
289 return NULL;
290 }
291
292 result = SDL_OpenStorage(&GENERIC_user_iface, prefpath);
293 if (result == NULL) {
294 SDL_free(prefpath); // otherwise CloseStorage will free it.
295 }
296 return result;
297}
298
299UserStorageBootStrap GENERIC_userbootstrap = {
300 "generic",
301 "SDL generic user storage driver",
302 GENERIC_User_Create
303};
304
305static const SDL_StorageInterface GENERIC_file_iface = {
306 sizeof(SDL_StorageInterface),
307 GENERIC_CloseStorage,
308 NULL, // ready
309 GENERIC_EnumerateStorageDirectory,
310 GENERIC_GetStoragePathInfo,
311 GENERIC_ReadStorageFile,
312 GENERIC_WriteStorageFile,
313 GENERIC_CreateStorageDirectory,
314 GENERIC_RemoveStoragePath,
315 GENERIC_RenameStoragePath,
316 GENERIC_CopyStorageFile,
317 GENERIC_GetStorageSpaceRemaining
318};
319
320SDL_Storage *GENERIC_OpenFileStorage(const char *path)
321{
322 SDL_Storage *result;
323 size_t len = 0;
324 char *basepath = NULL;
325
326 if (path) {
327 len += SDL_strlen(path);
328 }
329 if (len > 0) {
330 #ifdef SDL_PLATFORM_WINDOWS
331 const bool appended_separator = (path[len-1] == '/') || (path[len-1] == '\\');
332 #else
333 const bool appended_separator = (path[len-1] == '/');
334 #endif
335 if (appended_separator) {
336 basepath = SDL_strdup(path);
337 if (!basepath) {
338 return NULL;
339 }
340 } else {
341 if (SDL_asprintf(&basepath, "%s/", path) < 0) {
342 return NULL;
343 }
344 }
345 }
346 result = SDL_OpenStorage(&GENERIC_file_iface, basepath);
347 if (result == NULL) {
348 SDL_free(basepath);
349 }
350 return result;
351}
352