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