diff --git a/CMakeLists.txt b/CMakeLists.txt index 408c6e88dc..fcbb93fc3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -494,6 +494,7 @@ sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/render/*/*.c" "${SDL3_SOURCE_DIR}/src/sensor/*.c" "${SDL3_SOURCE_DIR}/src/stdlib/*.c" + "${SDL3_SOURCE_DIR}/src/storage/*.c" "${SDL3_SOURCE_DIR}/src/thread/*.c" "${SDL3_SOURCE_DIR}/src/timer/*.c" "${SDL3_SOURCE_DIR}/src/video/*.c" @@ -1753,6 +1754,14 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/unix/*.c") set(HAVE_SDL_FILESYSTEM TRUE) + set(SDL_STORAGE_GENERIC 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") + if(LINUX) + set(SDL_STORAGE_STEAM 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/steam/*.c") + endif() + set(HAVE_SDL_STORAGE 1) + set(SDL_TIMER_UNIX 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c") set(HAVE_SDL_TIMERS TRUE) @@ -1972,6 +1981,14 @@ elseif(WINDOWS) endif() set(HAVE_SDL_FILESYSTEM TRUE) + set(SDL_STORAGE_GENERIC 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") + if(NOT WINDOWS_STORE) + set(SDL_STORAGE_STEAM 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/steam/*.c") + endif() + set(HAVE_SDL_STORAGE 1) + # Libraries for Win32 native and MinGW if(NOT WINDOWS_STORE) sdl_link_dependency(base LIBS kernel32 user32 gdi32 winmm imm32 ole32 oleaut32 version uuid advapi32 setupapi shell32) @@ -2204,6 +2221,15 @@ elseif(APPLE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/cocoa/*.m") set(HAVE_SDL_FILESYSTEM TRUE) + # TODO: SDL_STORAGE_ICLOUD + set(SDL_STORAGE_GENERIC 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") + if(DARWIN OR MACOSX) + set(SDL_STORAGE_STEAM 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/steam/*.c") + endif() + set(HAVE_SDL_STORAGE 1) + if(SDL_SENSOR) if(IOS OR VISIONOS) set(SDL_SENSOR_COREMOTION 1) @@ -2820,6 +2846,10 @@ if(NOT HAVE_SDL_FILESYSTEM) set(SDL_FILESYSTEM_DUMMY 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/dummy/*.c") endif() +if(NOT HAVE_SDL_STORAGE) + set(SDL_STORAGE_GENERIC 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c") +endif() if(NOT HAVE_SDL_LOCALE) set(SDL_LOCALE_DUMMY 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/dummy/*.c") diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 8b8fdad0b0..e50832cb1a 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -368,6 +368,7 @@ + @@ -778,6 +779,8 @@ + + diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj index 807697af54..79d7a60978 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj +++ b/VisualC-WinRT/SDL-UWP.vcxproj @@ -82,6 +82,7 @@ + @@ -439,6 +440,8 @@ + + diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index f923aba841..504e3f9c00 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -291,6 +291,7 @@ + @@ -635,6 +636,9 @@ + + + diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h index 0b5ad69287..4a25af8c19 100644 --- a/include/SDL3/SDL.h +++ b/include/SDL3/SDL.h @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 5b16a34516..0dbbe4e317 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -1904,6 +1904,24 @@ extern "C" { */ #define SDL_HINT_SHUTDOWN_DBUS_ON_QUIT "SDL_SHUTDOWN_DBUS_ON_QUIT" +/** + * A variable that specifies a backend to use for title storage. + * + * By default, SDL will try all available storage backends in a reasonable order until it finds one that can work, but this hint allows the app or user to force a specific target, such as "pc" if, say, you are on Steam but want to avoid SteamRemoteStorage for title data. + * + * This hint should be set before SDL is initialized. + */ +#define SDL_HINT_STORAGE_TITLE_DRIVER "SDL_STORAGE_TITLE_DRIVER" + +/** + * A variable that specifies a backend to use for user storage. + * + * By default, SDL will try all available storage backends in a reasonable order until it finds one that can work, but this hint allows the app or user to force a specific target, such as "pc" if, say, you are on Steam but want to avoid SteamRemoteStorage for user data. + * + * This hint should be set before SDL is initialized. + */ +#define SDL_HINT_STORAGE_USER_DRIVER "SDL_STORAGE_USER_DRIVER" + /** * Specifies whether SDL_THREAD_PRIORITY_TIME_CRITICAL should be treated as realtime. * diff --git a/include/SDL3/SDL_storage.h b/include/SDL3/SDL_storage.h new file mode 100644 index 0000000000..386964f264 --- /dev/null +++ b/include/SDL3/SDL_storage.h @@ -0,0 +1,276 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_storage.h + * + * Include file for storage container SDL API functions + */ + +#ifndef SDL_storage_h_ +#define SDL_storage_h_ + +#include +#include +#include + +#include + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* !!! FIXME: Don't let this ship without async R/W support!!! */ + +typedef struct SDL_StorageInterface +{ + int (SDLCALL *close)(void *userdata); + + SDL_bool (SDLCALL *ready)(void *userdata); + + int (SDLCALL *fileSize)(void *userdata, const char *path, Uint64 *length); + + int (SDLCALL *readFile)(void *userdata, const char *path, void *destination, Uint64 length); + + int (SDLCALL *writeFile)(void *userdata, const char *path, const void *source, Uint64 length); + + Uint64 (SDLCALL *spaceRemaining)(void *userdata); +} SDL_StorageInterface; + +typedef struct SDL_Storage SDL_Storage; + +/** + * Opens up a read-only container for the application's filesystem. + * + * \param override a path to override the backend's default title root + * \param props a property list that may contain backend-specific information + * \returns a title storage container on success or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenUserStorage + * \sa SDL_OpenStorage + * \sa SDL_TitleStorageReady + * \sa SDL_CloseStorage + * \sa SDL_StorageReady + * \sa SDL_StorageFileSize + * \sa SDL_StorageReadFile + * \sa SDL_StorageWriteFile + * \sa SDL_StorageSpaceRemaining + */ +extern DECLSPEC SDL_Storage *SDLCALL SDL_OpenTitleStorage(const char *override, SDL_PropertiesID props); + +/** + * Opens up a container for a user's unique read/write filesystem. + * + * While title storage can generally be kept open throughout runtime, user + * storage should only be opened when the client is ready to read/write files. + * This allows the backend to properly batch R/W operations and flush them when + * the container has been closed; ensuring safe and optimal save I/O. + * + * \param org the name of your organization + * \param app the name of your application + * \param props a property list that may contain backend-specific information + * \returns a user storage container on success or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenTitleStorage + * \sa SDL_OpenStorage + * \sa SDL_CloseStorage + * \sa SDL_StorageReady + * \sa SDL_StorageFileSize + * \sa SDL_StorageReadFile + * \sa SDL_StorageWriteFile + * \sa SDL_StorageSpaceRemaining + */ +extern DECLSPEC SDL_Storage *SDLCALL SDL_OpenUserStorage(const char *org, const char *app, SDL_PropertiesID props); + +/** + * Opens up a container using a client-provided storage interface. + * + * Applications do not need to use this function unless they are providing + * their own SDL_Storage implementation. If you just need an + * SDL_Storage, you should use the built-in implementations in SDL, + * like SDL_OpenTitleStorage() or SDL_OpenUserStorage(). + * + * \param iface the function table to be used by this container + * \param userdata the pointer that will be passed to the store interface + * \returns a storage container on success or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenTitleStorage + * \sa SDL_OpenUserStorage + * \sa SDL_CloseStorage + * \sa SDL_StorageReady + * \sa SDL_StorageFileSize + * \sa SDL_StorageReadFile + * \sa SDL_StorageWriteFile + * \sa SDL_StorageSpaceRemaining + */ +extern DECLSPEC SDL_Storage *SDLCALL SDL_OpenStorage(const SDL_StorageInterface *iface, void *userdata); + +/** + * Closes and frees a storage container. + * + * \param storage a storage container to close + * \returns 0 if the container was freed with no errors, a negative value + * otherwise; call SDL_GetError() for more information. Even if the + * function returns an error, the container data will be freed; the + * error is only for informational purposes. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenTitleStorage + * \sa SDL_OpenUserStorage + * \sa SDL_OpenStorage + * \sa SDL_StorageReady + * \sa SDL_StorageFileSize + * \sa SDL_StorageReadFile + * \sa SDL_StorageWriteFile + * \sa SDL_StorageSpaceRemaining + */ +extern DECLSPEC int SDLCALL SDL_CloseStorage(SDL_Storage *storage); + +/** + * Checks if the storage container is ready to use. + * + * This function should be called in regular intervals until it returns + * SDL_TRUE - however, it is not recommended to spinwait on this call, as + * the backend may depend on a synchronous message loop. + * + * \param storage a storage container to query + * \returns SDL_TRUE if the container is ready, SDL_FALSE otherwise + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenTitleStorage + * \sa SDL_OpenUserStorage + * \sa SDL_OpenStorage + * \sa SDL_CloseStorage + * \sa SDL_StorageFileSize + * \sa SDL_StorageReadFile + * \sa SDL_StorageWriteFile + * \sa SDL_StorageSpaceRemaining + */ +extern DECLSPEC SDL_bool SDLCALL SDL_StorageReady(SDL_Storage *storage); + +/** + * Query the size of a file within a storage container. + * + * \param storage a storage container to query + * \param path the relative path of the file to query + * \param length a pointer to be filled with the file's length + * \returns 0 if the file could be queried, a negative value otherwise; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenTitleStorage + * \sa SDL_OpenUserStorage + * \sa SDL_OpenStorage + * \sa SDL_CloseStorage + * \sa SDL_StorageReady + * \sa SDL_StorageReadFile + * \sa SDL_StorageWriteFile + * \sa SDL_StorageSpaceRemaining + */ +extern DECLSPEC int SDLCALL SDL_StorageFileSize(SDL_Storage *storage, const char *path, Uint64 *length); + +/** + * Synchronously read a file from a storage container into a client-provided buffer. + * + * \param storage a storage container to read from + * \param path the relative path of the file to read + * \param destination a client-provided buffer to read the file into + * \param length the length of the destination buffer + * \returns 0 if the file was read, a negative value otherwise; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenTitleStorage + * \sa SDL_OpenUserStorage + * \sa SDL_OpenStorage + * \sa SDL_CloseStorage + * \sa SDL_StorageReady + * \sa SDL_StorageFileSize + * \sa SDL_StorageWriteFile + * \sa SDL_StorageSpaceRemaining + */ +extern DECLSPEC int SDLCALL SDL_StorageReadFile(SDL_Storage *storage, const char *path, void *destination, Uint64 length); + +/** + * Synchronously write a file from client memory into a storage container. + * + * \param storage a storage container to write to + * \param path the relative path of the file to write + * \param source a client-provided buffer to write from + * \param length the length of the source buffer + * \returns 0 if the file was written, a negative value otherwise; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenTitleStorage + * \sa SDL_OpenUserStorage + * \sa SDL_OpenStorage + * \sa SDL_CloseStorage + * \sa SDL_StorageReady + * \sa SDL_StorageFileSize + * \sa SDL_StorageReadFile + * \sa SDL_StorageSpaceRemaining + */ +extern DECLSPEC int SDL_StorageWriteFile(SDL_Storage *storage, const char *path, const void *source, Uint64 length); + +/** + * Queries the remaining space in a storage container. + * + * \param storage a storage container to query + * \returns the amount of remaining space, in bytes + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_OpenTitleStorage + * \sa SDL_OpenUserStorage + * \sa SDL_OpenStorage + * \sa SDL_CloseStorage + * \sa SDL_StorageReady + * \sa SDL_StorageFileSize + * \sa SDL_StorageReadFile + * \sa SDL_StorageReadFileAsync + * \sa SDL_StorageWriteFile + * \sa SDL_StorageWriteFileAsync + */ +extern DECLSPEC Uint64 SDLCALL SDL_StorageSpaceRemaining(SDL_Storage *storage); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include + +#endif /* SDL_storage_h_ */ diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 606dbbe7d2..1e650c491c 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -465,6 +465,10 @@ #cmakedefine SDL_FILESYSTEM_PS2 @SDL_FILESYSTEM_PS2@ #cmakedefine SDL_FILESYSTEM_N3DS @SDL_FILESYSTEM_N3DS@ +/* Enable system storage support */ +#cmakedefine SDL_STORAGE_GENERIC @SDL_STORAGE_GENERIC@ +#cmakedefine SDL_STORAGE_STEAM @SDL_STORAGE_STEAM@ + /* Enable camera subsystem */ #cmakedefine SDL_CAMERA_DRIVER_DUMMY @SDL_CAMERA_DRIVER_DUMMY@ /* !!! FIXME: for later cmakedefine SDL_CAMERA_DRIVER_DISK @SDL_CAMERA_DRIVER_DISK@ */ diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index e2e209be1e..b623877d81 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -979,6 +979,14 @@ SDL3_0.0.0 { SDL_OpenIO; SDL_CloseIO; SDL_GetIOStatus; + SDL_OpenTitleStorage; + SDL_OpenUserStorage; + SDL_OpenStorage; + SDL_CloseStorage; + SDL_StorageReady; + SDL_StorageFileSize; + SDL_StorageReadFile; + SDL_StorageSpaceRemaining; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 790bf10743..16eec6028d 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1004,3 +1004,11 @@ #define SDL_OpenIO SDL_OpenIO_REAL #define SDL_CloseIO SDL_CloseIO_REAL #define SDL_GetIOStatus SDL_GetIOStatus_REAL +#define SDL_OpenTitleStorage SDL_OpenTitleStorage_REAL +#define SDL_OpenUserStorage SDL_OpenUserStorage_REAL +#define SDL_OpenStorage SDL_OpenStorage_REAL +#define SDL_CloseStorage SDL_CloseStorage_REAL +#define SDL_StorageReady SDL_StorageReady_REAL +#define SDL_StorageFileSize SDL_StorageFileSize_REAL +#define SDL_StorageReadFile SDL_StorageReadFile_REAL +#define SDL_StorageSpaceRemaining SDL_StorageSpaceRemaining_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 83cc68d907..00a2718ff8 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1029,3 +1029,11 @@ SDL_DYNAPI_PROC(void,SDL_ShowOpenFolderDialog,(SDL_DialogFileCallback a, void *b SDL_DYNAPI_PROC(SDL_IOStream*,SDL_OpenIO,(const SDL_IOStreamInterface *a, void *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_CloseIO,(SDL_IOStream *a),(a),return) SDL_DYNAPI_PROC(SDL_IOStatus,SDL_GetIOStatus,(SDL_IOStream *a),(a),return) +SDL_DYNAPI_PROC(SDL_Storage*,SDL_OpenTitleStorage,(const char *a, SDL_PropertiesID b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Storage*,SDL_OpenUserStorage,(const char *a, const char *b, SDL_PropertiesID c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_Storage*,SDL_OpenStorage,(const SDL_StorageInterface *a, void *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_CloseStorage,(SDL_Storage *a),(a),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_StorageReady,(SDL_Storage *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_StorageFileSize,(SDL_Storage *a, const char *b, Uint64 *c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_StorageReadFile,(SDL_Storage *a, const char *b, void *c, Uint64 d),(a,b,c,d),return) +SDL_DYNAPI_PROC(Uint64,SDL_StorageSpaceRemaining,(SDL_Storage *a),(a),return) diff --git a/src/storage/SDL_storage.c b/src/storage/SDL_storage.c new file mode 100644 index 0000000000..877db6c7ea --- /dev/null +++ b/src/storage/SDL_storage.c @@ -0,0 +1,222 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#include "SDL_sysstorage.h" + +/* Available title storage drivers */ +static TitleStorageBootStrap *titlebootstrap[] = { + &GENERIC_titlebootstrap, +}; + +/* Available user storage drivers */ +static UserStorageBootStrap *userbootstrap[] = { +#ifdef SDL_STORAGE_STEAM + &STEAM_userbootstrap, +#endif + &GENERIC_userbootstrap, +}; + +struct SDL_Storage +{ + SDL_StorageInterface iface; + void *userdata; +}; + +#define CHECK_STORAGE_MAGIC() \ + if (!storage) { \ + return SDL_SetError("Invalid storage container"); \ + } + +#define CHECK_STORAGE_MAGIC_RET(retval) \ + if (!storage) { \ + SDL_SetError("Invalid storage container"); \ + return retval; \ + } + +SDL_Storage *SDL_OpenTitleStorage(const char *override, SDL_PropertiesID props) +{ + SDL_Storage *storage = NULL; + int i = 0; + + /* Select the proper storage driver */ + const char *driver_name = SDL_GetHint(SDL_HINT_STORAGE_TITLE_DRIVER); + if (driver_name && *driver_name != 0) { + const char *driver_attempt = driver_name; + while (driver_attempt && *driver_attempt != 0 && !storage) { + const char *driver_attempt_end = SDL_strchr(driver_attempt, ','); + size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt) + : SDL_strlen(driver_attempt); + + for (i = 0; titlebootstrap[i]; ++i) { + if ((driver_attempt_len == SDL_strlen(titlebootstrap[i]->name)) && + (SDL_strncasecmp(titlebootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) { + storage = titlebootstrap[i]->create(override, props); + break; + } + } + + driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL; + } + } else { + for (i = 0; titlebootstrap[i]; ++i) { + storage = titlebootstrap[i]->create(override, props); + if (storage) { + break; + } + } + } + if (!storage) { + if (driver_name) { + SDL_SetError("%s not available", driver_name); + } else { + SDL_SetError("No available title storage driver"); + } + } + return storage; +} + +SDL_Storage *SDL_OpenUserStorage(const char *org, const char *app, SDL_PropertiesID props) +{ + SDL_Storage *storage = NULL; + int i = 0; + + /* Select the proper storage driver */ + const char *driver_name = SDL_GetHint(SDL_HINT_STORAGE_USER_DRIVER); + if (driver_name && *driver_name != 0) { + const char *driver_attempt = driver_name; + while (driver_attempt && *driver_attempt != 0 && !storage) { + const char *driver_attempt_end = SDL_strchr(driver_attempt, ','); + size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt) + : SDL_strlen(driver_attempt); + + for (i = 0; userbootstrap[i]; ++i) { + if ((driver_attempt_len == SDL_strlen(userbootstrap[i]->name)) && + (SDL_strncasecmp(userbootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) { + storage = userbootstrap[i]->create(org, app, props); + break; + } + } + + driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL; + } + } else { + for (i = 0; userbootstrap[i]; ++i) { + storage = userbootstrap[i]->create(org, app, props); + if (storage) { + break; + } + } + } + if (!storage) { + if (driver_name) { + SDL_SetError("%s not available", driver_name); + } else { + SDL_SetError("No available user storage driver"); + } + } + return storage; +} + +SDL_Storage *SDL_OpenStorage(const SDL_StorageInterface *iface, void *userdata) +{ + SDL_Storage *storage; + + if (iface->close == NULL || iface->ready == NULL || iface->fileSize == NULL) { + SDL_SetError("iface is missing required callbacks"); + return NULL; + } + + if ((iface->writeFile != NULL) != (iface->spaceRemaining != NULL)) { + SDL_SetError("Writeable containers must have both writeFile and spaceRemaining"); + return NULL; + } + + storage = (SDL_Storage*) SDL_malloc(sizeof(SDL_Storage)); + if (storage == NULL) { + SDL_OutOfMemory(); + return NULL; + } + + SDL_memcpy(&storage->iface, iface, sizeof(SDL_StorageInterface)); + storage->userdata = userdata; + return storage; +} + +int SDL_CloseStorage(SDL_Storage *storage) +{ + int retval; + + CHECK_STORAGE_MAGIC() + + retval = storage->iface.close(storage->userdata); + SDL_free(storage); + return retval; +} + +SDL_bool SDL_StorageReady(SDL_Storage *storage) +{ + CHECK_STORAGE_MAGIC_RET(SDL_FALSE) + + return storage->iface.ready(storage->userdata); +} + +int SDL_StorageFileSize(SDL_Storage *storage, const char *path, Uint64 *length) +{ + CHECK_STORAGE_MAGIC() + + return storage->iface.fileSize(storage->userdata, path, length); +} + +int SDL_StorageReadFile(SDL_Storage *storage, const char *path, void *destination, Uint64 length) +{ + CHECK_STORAGE_MAGIC() + + if (storage->iface.readFile == NULL) { + return SDL_SetError("Storage container does not have read capability"); + } + + return storage->iface.readFile(storage->userdata, path, destination, length); +} + +int SDL_StorageWriteFile(SDL_Storage *storage, const char *path, const void *source, Uint64 length) +{ + CHECK_STORAGE_MAGIC() + + if (storage->iface.writeFile == NULL) { + return SDL_SetError("Storage container does not have write capability"); + } + + return storage->iface.writeFile(storage->userdata, path, source, length); +} + +Uint64 SDL_StorageSpaceRemaining(SDL_Storage *storage) +{ + CHECK_STORAGE_MAGIC_RET(0) + + if (storage->iface.spaceRemaining == NULL) { + SDL_SetError("Storage container does not have write capability"); + return 0; + } + + return storage->iface.spaceRemaining(storage->userdata); +} diff --git a/src/storage/SDL_sysstorage.h b/src/storage/SDL_sysstorage.h new file mode 100644 index 0000000000..c16c5cc251 --- /dev/null +++ b/src/storage/SDL_sysstorage.h @@ -0,0 +1,49 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_sysstorage_h_ +#define SDL_sysstorage_h_ + +#include "SDL_internal.h" + +typedef struct TitleStorageBootStrap +{ + const char *name; + const char *desc; + SDL_Storage *(*create)(const char*, SDL_PropertiesID); +} TitleStorageBootStrap; + +typedef struct UserStorageBootStrap +{ + const char *name; + const char *desc; + SDL_Storage *(*create)(const char*, const char*, SDL_PropertiesID); +} UserStorageBootStrap; + +/* Not all of these are available in a given build. Use #ifdefs, etc. */ + +extern TitleStorageBootStrap GENERIC_titlebootstrap; +/* Steam does not have title storage APIs */ + +extern UserStorageBootStrap GENERIC_userbootstrap; +extern UserStorageBootStrap STEAM_userbootstrap; + +#endif /* SDL_sysstorage_h_ */ diff --git a/src/storage/generic/SDL_genericstorage.c b/src/storage/generic/SDL_genericstorage.c new file mode 100644 index 0000000000..26296be9e4 --- /dev/null +++ b/src/storage/generic/SDL_genericstorage.c @@ -0,0 +1,188 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#include "../SDL_sysstorage.h" + +static char *GENERIC_INTERNAL_CreateFullPath(const char *base, const char *relative) +{ + size_t fulllen = SDL_strlen(base) + SDL_strlen(relative) + 1; + char *result = (char*) SDL_malloc(fulllen); + if (result != NULL) { + SDL_snprintf(result, fulllen, "%s%s", base, relative); + } + return result; +} + +static int GENERIC_StorageClose(void *userdata) +{ + SDL_free(userdata); + return 0; +} + +static SDL_bool GENERIC_StorageReady(void *userdata) +{ + return SDL_TRUE; +} + +static int GENERIC_StorageFileSize(void *userdata, const char *path, Uint64 *length) +{ + SDL_IOStream *stream; + Sint64 result; + + char *fullpath = GENERIC_INTERNAL_CreateFullPath((char*) userdata, path); + if (fullpath == NULL) { + return SDL_OutOfMemory(); + } + stream = SDL_IOFromFile(fullpath, "rb"); + SDL_free(fullpath); + + result = SDL_SizeIO(stream); + SDL_CloseIO(stream); + if (result < 0) { + return result; + } + + /* FIXME: Should SDL_SizeIO use u64 now...? */ + *length = (Uint64) result; + return 0; +} + +static int GENERIC_StorageReadFile(void *userdata, const char *path, void *destination, Uint64 length) +{ + SDL_IOStream *stream; + char *fullpath; + int fullread; + + if (length > SDL_SIZE_MAX) { + return SDL_SetError("Read size exceeds SDL_SIZE_MAX"); + } + + fullpath = GENERIC_INTERNAL_CreateFullPath((char*) userdata, path); + if (fullpath == NULL) { + return SDL_OutOfMemory(); + } + stream = SDL_IOFromFile(fullpath, "rb"); + SDL_free(fullpath); + + /* FIXME: Should SDL_ReadIO use u64 now...? */ + fullread = (SDL_ReadIO(stream, destination, (size_t) length) == length); + SDL_CloseIO(stream); + return fullread - 1; +} + +static int GENERIC_StorageWriteFile(void *userdata, const char *path, const void *source, Uint64 length) +{ + /* TODO: Recursively create subdirectories with SDL_mkdir */ + SDL_IOStream *stream; + char *fullpath; + int fullwrite; + + if (length > SDL_SIZE_MAX) { + return SDL_SetError("Write size exceeds SDL_SIZE_MAX"); + } + + fullpath = GENERIC_INTERNAL_CreateFullPath((char*) userdata, path); + if (fullpath == NULL) { + return SDL_OutOfMemory(); + } + stream = SDL_IOFromFile(fullpath, "wb"); + SDL_free(fullpath); + + /* FIXME: Should SDL_WriteIO use u64 now...? */ + fullwrite = (SDL_WriteIO(stream, source, (size_t) length) == length); + SDL_CloseIO(stream); + return fullwrite - 1; +} + +static Uint64 GENERIC_StorageSpaceRemaining(void *userdata) +{ + /* TODO: There's totally a way to query a folder root's quota... */ + return SDL_MAX_UINT64; +} + +static const SDL_StorageInterface GENERIC_title_iface = { + GENERIC_StorageClose, + GENERIC_StorageReady, + GENERIC_StorageFileSize, + GENERIC_StorageReadFile, + NULL, + NULL +}; + +static SDL_Storage *GENERIC_Title_Create(const char *override, SDL_PropertiesID props) +{ + SDL_Storage *result; + + char *basepath; + if (override != NULL) { + basepath = SDL_strdup(override); + } else { + basepath = SDL_GetBasePath(); + } + if (basepath == NULL) { + return NULL; + } + + result = SDL_OpenStorage(&GENERIC_title_iface, basepath); + if (result == NULL) { + SDL_free(basepath); + } + return result; +} + +TitleStorageBootStrap GENERIC_titlebootstrap = { + "generic", + "SDL generic title storage driver", + GENERIC_Title_Create +}; + +static const SDL_StorageInterface GENERIC_user_iface = { + GENERIC_StorageClose, + GENERIC_StorageReady, + GENERIC_StorageFileSize, + GENERIC_StorageReadFile, + GENERIC_StorageWriteFile, + GENERIC_StorageSpaceRemaining +}; + +static SDL_Storage *GENERIC_User_Create(const char *org, const char *app, SDL_PropertiesID props) +{ + SDL_Storage *result; + + char *prefpath = SDL_GetPrefPath(org, app); + if (prefpath == NULL) { + return NULL; + } + + result = SDL_OpenStorage(&GENERIC_user_iface, prefpath); + if (result == NULL) { + SDL_free(prefpath); + } + return result; +} + +UserStorageBootStrap GENERIC_userbootstrap = { + "generic", + "SDL generic user storage driver", + GENERIC_User_Create +}; diff --git a/src/storage/steam/SDL_steamstorage.c b/src/storage/steam/SDL_steamstorage.c new file mode 100644 index 0000000000..1b41b149af --- /dev/null +++ b/src/storage/steam/SDL_steamstorage.c @@ -0,0 +1,199 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#include "../SDL_sysstorage.h" + +#include /* Needed by Steamworks */ + +// !!! FIXME: Async API can use SteamRemoteStorage_ReadFileAsync +// !!! FIXME: Async API can use SteamRemoteStorage_WriteFileAsync + +#define STEAM_PROC(ret, func, parms) \ + typedef ret (*steamfntype_##func) parms; +#include "SDL_steamstorage_proc.h" + +typedef struct STEAM_RemoteStorage +{ + void *libsteam_api; + #define STEAM_PROC(ret, func, parms) \ + steamfntype_##func func; + #include "SDL_steamstorage_proc.h" +} STEAM_RemoteStorage; + +static int STEAM_UserStorageClose(void *userdata) +{ + int result = 0; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + result = SDL_SetError("SteamRemoteStorage unavailable"); + } else if (!steam->SteamAPI_ISteamRemoteStorage_EndFileWriteBatch(steamremotestorage)) { + result = SDL_SetError("SteamRemoteStorage()->EndFileWriteBatch() failed"); + } + SDL_UnloadObject(steam->libsteam_api); + SDL_free(steam); + return result; +} + +static SDL_bool STEAM_UserStorageReady(void *userdata) +{ + return SDL_TRUE; +} + +static int STEAM_UserStorageFileSize(void *userdata, const char *path, Uint64 *length) +{ + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + return SDL_SetError("SteamRemoteStorage unavailable"); + } + *length = steam->SteamAPI_ISteamRemoteStorage_GetFileSize(steamremotestorage, path); + return 0; +} + +static int STEAM_UserStorageReadFile(void *userdata, const char *path, void *destination, Uint64 length) +{ + int retval; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + return SDL_SetError("SteamRemoteStorage unavailable"); + } + if (length > SDL_MAX_SINT32) { + return SDL_SetError("SteamRemoteStorage only supports INT32_MAX read size"); + } + retval = steam->SteamAPI_ISteamRemoteStorage_FileRead(steamremotestorage, path, destination, (Sint32) length) == length; + return retval - 1; +} + +static int STEAM_UserStorageWriteFile(void *userdata, const char *path, const void *source, Uint64 length) +{ + int retval; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + return SDL_SetError("SteamRemoteStorage unavailable"); + } + if (length > SDL_MAX_SINT32) { + return SDL_SetError("SteamRemoteStorage only supports INT32_MAX write size"); + } + retval = steam->SteamAPI_ISteamRemoteStorage_FileWrite(steamremotestorage, path, source, (Sint32) length) == length; + return retval - 1; +} + +static Uint64 STEAM_UserStorageSpaceRemaining(void *userdata) +{ + Uint64 total, remaining; + STEAM_RemoteStorage *steam = (STEAM_RemoteStorage*) userdata; + void *steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + SDL_SetError("SteamRemoteStorage unavailable"); + return 0; + } + if (!steam->SteamAPI_ISteamRemoteStorage_GetQuota(steamremotestorage, &total, &remaining)) { + SDL_SetError("SteamRemoteStorage()->GetQuota failed"); + return 0; + } + return remaining; +} + +static const SDL_StorageInterface STEAM_user_iface = { + STEAM_UserStorageClose, + STEAM_UserStorageReady, + STEAM_UserStorageFileSize, + STEAM_UserStorageReadFile, + STEAM_UserStorageWriteFile, + STEAM_UserStorageSpaceRemaining +}; + +static SDL_Storage *STEAM_User_Create(const char *org, const char *app, SDL_PropertiesID props) +{ + SDL_Storage *result; + STEAM_RemoteStorage *steam; + void *steamremotestorage; + + steam = (STEAM_RemoteStorage*) SDL_malloc(sizeof(STEAM_RemoteStorage)); + if (steam == NULL) { + SDL_OutOfMemory(); + return NULL; + } + + steam->libsteam_api = SDL_LoadObject( +#if defined(_WIN64) + "steam_api64.dll" +#elif defined(_WIN32) + "steam_api.dll" +#elif defined(__APPLE__) + "libsteam_api.dylib" +#else + "libsteam_api.so" +#endif + ); + if (steam->libsteam_api == NULL) { + SDL_free(steam); + return NULL; + } + + #define STEAM_PROC(ret, func, parms) \ + steam->func = (steamfntype_##func) SDL_LoadFunction(steam->libsteam_api, #func); \ + if (steam->func == NULL) { \ + SDL_SetError("Could not load function " #func); \ + goto steamfail; \ + } + #include "SDL_steamstorage_proc.h" + + steamremotestorage = steam->SteamAPI_SteamRemoteStorage_v016(); + if (steamremotestorage == NULL) { + SDL_SetError("SteamRemoteStorage unavailable"); + goto steamfail; + } + if (!steam->SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount(steamremotestorage)) { + SDL_SetError("Steam cloud is disabled for this user"); + goto steamfail; + } + if (!steam->SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp(steamremotestorage)) { + SDL_SetError("Steam cloud is disabled for this application"); + goto steamfail; + } + if (!steam->SteamAPI_ISteamRemoteStorage_BeginFileWriteBatch(steamremotestorage)) { + SDL_SetError("SteamRemoteStorage()->BeginFileWriteBatch failed"); + goto steamfail; + } + + result = SDL_OpenStorage(&STEAM_user_iface, steam); + if (result == NULL) { + goto steamfail; + } + return result; + +steamfail: + SDL_UnloadObject(steam->libsteam_api); + SDL_free(steam); + return NULL; +} + +UserStorageBootStrap STEAM_userbootstrap = { + "steam", + "SDL Steam user storage driver", + STEAM_User_Create +}; diff --git a/src/storage/steam/SDL_steamstorage_proc.h b/src/storage/steam/SDL_steamstorage_proc.h new file mode 100644 index 0000000000..9d259e70d1 --- /dev/null +++ b/src/storage/steam/SDL_steamstorage_proc.h @@ -0,0 +1,14 @@ +STEAM_PROC(void*, SteamAPI_SteamRemoteStorage_v016, (void)) + +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount, (void*)) +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp, (void*)) + +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_BeginFileWriteBatch, (void*)) +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_EndFileWriteBatch, (void*)) + +STEAM_PROC(Sint32, SteamAPI_ISteamRemoteStorage_GetFileSize, (void*, const char*)) +STEAM_PROC(Sint32, SteamAPI_ISteamRemoteStorage_FileRead, (void*, const char*, void*, Sint32)) +STEAM_PROC(Sint32, SteamAPI_ISteamRemoteStorage_FileWrite, (void*, const char*, const void*, Sint32)) +STEAM_PROC(bool, SteamAPI_ISteamRemoteStorage_GetQuota, (void*, Uint64*, Uint64*)) + +#undef STEAM_PROC