Browse Source

Add SDL_storage

Ethan Lee 1 year ago
parent
commit
744227e6ab

+ 30 - 0
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")

+ 3 - 0
VisualC-GDK/SDL/SDL.vcxproj

@@ -368,6 +368,7 @@
     <ClInclude Include="..\..\include\SDL3\SDL_scancode.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_sensor.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_stdinc.h" />
+    <ClInclude Include="..\..\include\SDL3\SDL_storage.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_surface.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_system.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_test.h" />
@@ -778,6 +779,8 @@
     <ClCompile Include="..\..\src\stdlib\SDL_stdlib.c" />
     <ClCompile Include="..\..\src\stdlib\SDL_string.c" />
     <ClCompile Include="..\..\src\stdlib\SDL_strtokr.c" />
+    <ClCompile Include="..\..\src\storage\generic\SDL_genericstorage.c" />
+    <ClCompile Include="..\..\src\storage\SDL_storage.c" />
     <ClCompile Include="..\..\src\thread\generic\SDL_syscond.c" />
     <ClCompile Include="..\..\src\thread\generic\SDL_sysrwlock.c" />
     <ClCompile Include="..\..\src\thread\SDL_thread.c" />

+ 3 - 0
VisualC-WinRT/SDL-UWP.vcxproj

@@ -82,6 +82,7 @@
     <ClInclude Include="..\include\SDL3\SDL_scancode.h" />
     <ClInclude Include="..\include\SDL3\SDL_sensor.h" />
     <ClInclude Include="..\include\SDL3\SDL_stdinc.h" />
+    <ClInclude Include="..\include\SDL3\SDL_storage.h" />
     <ClInclude Include="..\include\SDL3\SDL_surface.h" />
     <ClInclude Include="..\include\SDL3\SDL_system.h" />
     <ClInclude Include="..\include\SDL3\SDL_thread.h" />
@@ -439,6 +440,8 @@
     <ClCompile Include="..\src\stdlib\SDL_stdlib.c" />
     <ClCompile Include="..\src\stdlib\SDL_string.c" />
     <ClCompile Include="..\src\stdlib\SDL_strtokr.c" />
+    <ClCompile Include="..\src\storage\generic\SDL_genericstorage.c" />
+    <ClCompile Include="..\src\storage\SDL_storage.c" />
     <ClCompile Include="..\src\thread\generic\SDL_syssem.c" />
     <ClCompile Include="..\src\thread\SDL_thread.c" />
     <ClCompile Include="..\src\thread\stdcpp\SDL_syscond.cpp">

+ 4 - 0
VisualC/SDL/SDL.vcxproj

@@ -291,6 +291,7 @@
     <ClInclude Include="..\..\include\SDL3\SDL_scancode.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_sensor.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_stdinc.h" />
+    <ClInclude Include="..\..\include\SDL3\SDL_storage.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_surface.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_system.h" />
     <ClInclude Include="..\..\include\SDL3\SDL_test.h" />
@@ -635,6 +636,9 @@
     <ClCompile Include="..\..\src\stdlib\SDL_stdlib.c" />
     <ClCompile Include="..\..\src\stdlib\SDL_string.c" />
     <ClCompile Include="..\..\src\stdlib\SDL_strtokr.c" />
+    <ClCompile Include="..\..\src\storage\generic\SDL_genericstorage.c" />
+    <ClCompile Include="..\..\src\storage\steam\SDL_steamstorage.c" />
+    <ClCompile Include="..\..\src\storage\SDL_storage.c" />
     <ClCompile Include="..\..\src\thread\generic\SDL_syscond.c" />
     <ClCompile Include="..\..\src\thread\generic\SDL_sysrwlock.c" />
     <ClCompile Include="..\..\src\thread\SDL_thread.c" />

+ 1 - 0
include/SDL3/SDL.h

@@ -71,6 +71,7 @@
 #include <SDL3/SDL_iostream.h>
 #include <SDL3/SDL_scancode.h>
 #include <SDL3/SDL_sensor.h>
+#include <SDL3/SDL_storage.h>
 #include <SDL3/SDL_surface.h>
 #include <SDL3/SDL_system.h>
 #include <SDL3/SDL_thread.h>

+ 18 - 0
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.
  *

+ 276 - 0
include/SDL3/SDL_storage.h

@@ -0,0 +1,276 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  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 <SDL3/SDL_stdinc.h>
+#include <SDL3/SDL_mutex.h>
+#include <SDL3/SDL_properties.h>
+
+#include <SDL3/SDL_begin_code.h>
+
+/* 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 <SDL3/SDL_close_code.h>
+
+#endif /* SDL_storage_h_ */

+ 4 - 0
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@ */

+ 8 - 0
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: *;
 };

+ 8 - 0
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

+ 8 - 0
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)

+ 222 - 0
src/storage/SDL_storage.c

@@ -0,0 +1,222 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  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);
+}

+ 49 - 0
src/storage/SDL_sysstorage.h

@@ -0,0 +1,49 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  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_ */

+ 188 - 0
src/storage/generic/SDL_genericstorage.c

@@ -0,0 +1,188 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  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
+};

+ 199 - 0
src/storage/steam/SDL_steamstorage.c

@@ -0,0 +1,199 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  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 <stdbool.h> /* 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
+};

+ 14 - 0
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