SDL: Add SDL_SaveFile and SDL_SaveFile_IO functions

From 3e2ef64c98897443707e0a26ed1975390ebdcf2a Mon Sep 17 00:00:00 2001
From: Semphris <[EMAIL REDACTED]>
Date: Thu, 7 Nov 2024 18:28:23 -0500
Subject: [PATCH] Add SDL_SaveFile and SDL_SaveFile_IO functions

---
 include/SDL3/SDL_iostream.h       | 38 ++++++++++++++++++++++
 src/dynapi/SDL_dynapi.sym         |  2 ++
 src/dynapi/SDL_dynapi_overrides.h |  2 ++
 src/dynapi/SDL_dynapi_procs.h     |  2 ++
 src/file/SDL_iostream.c           | 52 +++++++++++++++++++++++++++++++
 5 files changed, 96 insertions(+)

diff --git a/include/SDL3/SDL_iostream.h b/include/SDL3/SDL_iostream.h
index 76fdf0ad2d3fb..433fd1fac6ec6 100644
--- a/include/SDL3/SDL_iostream.h
+++ b/include/SDL3/SDL_iostream.h
@@ -664,6 +664,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FlushIO(SDL_IOStream *context);
  * \since This function is available since SDL 3.1.3.
  *
  * \sa SDL_LoadFile
+ * \sa SDL_SaveFile_IO
  */
 extern SDL_DECLSPEC void * SDLCALL SDL_LoadFile_IO(SDL_IOStream *src, size_t *datasize, bool closeio);
 
@@ -684,9 +685,46 @@ extern SDL_DECLSPEC void * SDLCALL SDL_LoadFile_IO(SDL_IOStream *src, size_t *da
  * \since This function is available since SDL 3.1.3.
  *
  * \sa SDL_LoadFile_IO
+ * \sa SDL_SaveFile
  */
 extern SDL_DECLSPEC void * SDLCALL SDL_LoadFile(const char *file, size_t *datasize);
 
+/**
+ * Save all the data into an SDL data stream.
+ *
+ * \param src the SDL_IOStream to write all data to.
+ * \param data the data to be written. If datasize is 0, may be NULL or a
+ *        invalid pointer.
+ * \param datasize the number of bytes to be written.
+ * \param closeio if true, calls SDL_CloseIO() on `src` before returning, even
+ *                in the case of an error.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ *          information.
+ *
+ * \since This function is available since SDL 3.1.3.
+ *
+ * \sa SDL_SaveFile
+ * \sa SDL_LoadFile_IO
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SaveFile_IO(SDL_IOStream *src, const void *data, size_t datasize, bool closeio);
+
+/**
+ * Save all the data into a file path.
+ *
+ * \param file the path to read all available data from.
+ * \param data the data to be written. If datasize is 0, may be NULL or a
+ *        invalid pointer.
+ * \param datasize the number of bytes to be written.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ *          information.
+ *
+ * \since This function is available since SDL 3.1.3.
+ *
+ * \sa SDL_SaveFile_IO
+ * \sa SDL_LoadFile
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SaveFile(const char *file, const void *data, size_t datasize);
+
 /**
  *  \name Read endian functions
  *
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index fcdb93b05fec7..e786d37002e76 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1184,6 +1184,8 @@ SDL3_0.0.0 {
     SDL_RenderDebugText;
     SDL_GetSandbox;
     SDL_CancelGPUCommandBuffer;
+    SDL_SaveFile_IO;
+    SDL_SaveFile;
     # 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 c33efb30b8c40..215f471510348 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1209,3 +1209,5 @@
 #define SDL_RenderDebugText SDL_RenderDebugText_REAL
 #define SDL_GetSandbox SDL_GetSandbox_REAL
 #define SDL_CancelGPUCommandBuffer SDL_CancelGPUCommandBuffer_REAL
+#define SDL_SaveFile_IO SDL_SaveFile_IO_REAL
+#define SDL_SaveFile SDL_SaveFile_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index cebdf0206ea87..05849f6ec3e33 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1215,3 +1215,5 @@ SDL_DYNAPI_PROC(SDL_LogOutputFunction,SDL_GetDefaultLogOutputFunction,(void),(),
 SDL_DYNAPI_PROC(bool,SDL_RenderDebugText,(SDL_Renderer *a,float b,float c,const char *d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(SDL_Sandbox,SDL_GetSandbox,(void),(),return)
 SDL_DYNAPI_PROC(bool,SDL_CancelGPUCommandBuffer,(SDL_GPUCommandBuffer *a),(a),return)
+SDL_DYNAPI_PROC(bool,SDL_SaveFile_IO,(SDL_IOStream *a,const void *b,size_t c,bool d),(a,b,c,d),return)
+SDL_DYNAPI_PROC(bool,SDL_SaveFile,(const char *a,const void *b,size_t c),(a,b,c),return)
diff --git a/src/file/SDL_iostream.c b/src/file/SDL_iostream.c
index 1fa652f89310d..7e70f5b931594 100644
--- a/src/file/SDL_iostream.c
+++ b/src/file/SDL_iostream.c
@@ -1218,6 +1218,58 @@ void *SDL_LoadFile(const char *file, size_t *datasize)
     return SDL_LoadFile_IO(stream, datasize, true);
 }
 
+bool SDL_SaveFile_IO(SDL_IOStream *src, const void *data, size_t datasize, bool closeio)
+{
+    size_t size_written = 0;
+    size_t size_total = 0;
+    bool success = true;
+
+    if (!src) {
+        SDL_InvalidParamError("src");
+        goto done;
+    }
+
+    if (!data && datasize > 0) {
+        SDL_InvalidParamError("data");
+        goto done;
+    }
+
+    if (datasize > 0) {
+        while (size_total < datasize) {
+            size_written = SDL_WriteIO(src, ((const char *) data) + size_written, datasize - size_written);
+
+            if (size_written <= 0) {
+                if (SDL_GetIOStatus(src) == SDL_IO_STATUS_NOT_READY) {
+                    // Wait for the stream to be ready
+                    SDL_Delay(1);
+                    continue;
+                } else {
+                    success = false;
+                    goto done;
+                }
+            }
+
+            size_total += size_written;
+        }
+    }
+
+done:
+    if (closeio && src) {
+        SDL_CloseIO(src);
+    }
+
+    return success;
+}
+
+bool SDL_SaveFile(const char *file, const void *data, size_t datasize)
+{
+    SDL_IOStream *stream = SDL_IOFromFile(file, "wb");
+    if (!stream) {
+        return false;
+    }
+    return SDL_SaveFile_IO(stream, data, datasize, true);
+}
+
 SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context)
 {
     if (!context) {