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 | |
27 | static 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 | |
34 | static bool GENERIC_CloseStorage(void *userdata) |
35 | { |
36 | SDL_free(userdata); |
37 | return true; |
38 | } |
39 | |
40 | typedef struct GenericEnumerateData |
41 | { |
42 | size_t base_len; |
43 | SDL_EnumerateDirectoryCallback real_callback; |
44 | void *real_userdata; |
45 | } GenericEnumerateData; |
46 | |
47 | static 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 | |
75 | static 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 | |
93 | static 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 | |
106 | static 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 | |
131 | static 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 | |
158 | static 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 | |
172 | static 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 | |
185 | static 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 | |
200 | static 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 | |
215 | static 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 | |
221 | static 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 | |
236 | static 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 | |
263 | TitleStorageBootStrap GENERIC_titlebootstrap = { |
264 | "generic" , |
265 | "SDL generic title storage driver" , |
266 | GENERIC_Title_Create |
267 | }; |
268 | |
269 | static 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 | |
284 | static 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 | |
299 | UserStorageBootStrap GENERIC_userbootstrap = { |
300 | "generic" , |
301 | "SDL generic user storage driver" , |
302 | GENERIC_User_Create |
303 | }; |
304 | |
305 | static 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 | |
320 | SDL_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 | |