From f9d11807c01ddde40f8f218b9cbb6021405d9018 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 4 Nov 2023 13:57:16 -0700
Subject: [PATCH] Added SDL_RWprintf() and SDL_RWvprintf() to do formatted
printing to an SDL_rwops stream
Fixes https://github.com/libsdl-org/SDL/issues/2390
---
include/SDL3/SDL_rwops.h | 45 +++++++++++++++++++++++++++++++
src/dynapi/SDL_dynapi.c | 20 ++++++++++++++
src/dynapi/SDL_dynapi.sym | 2 ++
src/dynapi/SDL_dynapi_overrides.h | 2 ++
src/dynapi/SDL_dynapi_procs.h | 2 ++
src/file/SDL_rwops.c | 29 ++++++++++++++++++++
test/testautomation_rwops.c | 35 ++++++++++++++++++++++--
7 files changed, 133 insertions(+), 2 deletions(-)
diff --git a/include/SDL3/SDL_rwops.h b/include/SDL3/SDL_rwops.h
index 26fd5299f471..8ecd75f1d684 100644
--- a/include/SDL3/SDL_rwops.h
+++ b/include/SDL3/SDL_rwops.h
@@ -490,11 +490,56 @@ extern DECLSPEC size_t SDLCALL SDL_RWread(SDL_RWops *context, void *ptr, size_t
* \sa SDL_RWFromConstMem
* \sa SDL_RWFromFile
* \sa SDL_RWFromMem
+ * \sa SDL_RWprint
* \sa SDL_RWread
* \sa SDL_RWseek
*/
extern DECLSPEC size_t SDLCALL SDL_RWwrite(SDL_RWops *context, const void *ptr, size_t size);
+/**
+ * Print to an SDL_RWops data stream.
+ *
+ * This function does formatted printing to the stream.
+ *
+ * \param context a pointer to an SDL_RWops structure
+ * \param fmt a printf() style format string
+ * \param ... additional parameters matching % tokens in the `fmt` string, if any
+ * \returns the number of bytes written, or 0 on error; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_RWclose
+ * \sa SDL_RWFromConstMem
+ * \sa SDL_RWFromFile
+ * \sa SDL_RWFromMem
+ * \sa SDL_RWread
+ * \sa SDL_RWseek
+ * \sa SDL_RWwrite
+ */
+extern DECLSPEC size_t SDLCALL SDL_RWprintf(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...);
+
+/**
+ * Print to an SDL_RWops data stream.
+ *
+ * This function does formatted printing to the stream.
+ *
+ * \param context a pointer to an SDL_RWops structure
+ * \param fmt a printf() style format string
+ * \param ap a variable argument list
+ * \returns the number of bytes written, or 0 on error; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_RWclose
+ * \sa SDL_RWFromConstMem
+ * \sa SDL_RWFromFile
+ * \sa SDL_RWFromMem
+ * \sa SDL_RWread
+ * \sa SDL_RWseek
+ * \sa SDL_RWwrite
+ */
+extern DECLSPEC size_t SDLCALL SDL_RWvprintf(SDL_RWops *context, const char *fmt, va_list ap);
+
/**
* Close and free an allocated SDL_RWops structure.
*
diff --git a/src/dynapi/SDL_dynapi.c b/src/dynapi/SDL_dynapi.c
index 603ea36a3df2..23cf60432e30 100644
--- a/src/dynapi/SDL_dynapi.c
+++ b/src/dynapi/SDL_dynapi.c
@@ -144,6 +144,16 @@ static void SDL_InitDynamicAPI(void);
va_end(ap); \
return retval; \
} \
+ _static size_t SDLCALL SDL_RWprintf##name(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \
+ { \
+ size_t retval; \
+ va_list ap; \
+ initcall; \
+ va_start(ap, fmt); \
+ retval = jump_table.SDL_RWvprintf(context, fmt, ap); \
+ va_end(ap); \
+ return retval; \
+ } \
_static void SDLCALL SDL_Log##name(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \
{ \
va_list ap; \
@@ -285,6 +295,16 @@ static int SDLCALL SDL_swprintf_LOGSDLCALLS(SDL_OUT_Z_CAP(maxlen) wchar_t *buf,
va_end(ap);
return retval;
}
+_static size_t SDLCALL SDL_RWprintf_LOGSDLCALLS(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
+{
+ size_t retval;
+ va_list ap;
+ SDL_Log_REAL("SDL3CALL SDL_RWprintf");
+ va_start(ap, fmt);
+ retval = SDL_RWvprintf_REAL(context, fmt, ap);
+ va_end(ap);
+ return retval;
+}
static void SDLCALL SDL_Log_LOGSDLCALLS(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 75e19c3c1bf1..fb0a2f38e6b9 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -922,6 +922,8 @@ SDL3_0.0.0 {
SDL_ClearProperty;
SDL_EnterAppMainCallbacks;
SDL_CleanupEvent;
+ SDL_RWprintf;
+ SDL_RWvprintf;
# 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 4cfa59166466..84d0d91f7663 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -947,3 +947,5 @@
#define SDL_ClearProperty SDL_ClearProperty_REAL
#define SDL_EnterAppMainCallbacks SDL_EnterAppMainCallbacks_REAL
#define SDL_CleanupEvent SDL_CleanupEvent_REAL
+#define SDL_RWprintf SDL_RWprintf_REAL
+#define SDL_RWvprintf SDL_RWvprintf_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index d73e3e312af6..5b9dace0ffdd 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -43,6 +43,7 @@ SDL_DYNAPI_PROC(int,SDL_asprintf,(char **a, SDL_PRINTF_FORMAT_STRING const char
SDL_DYNAPI_PROC(int,SDL_snprintf,(SDL_OUT_Z_CAP(b) char *a, size_t b, SDL_PRINTF_FORMAT_STRING const char *c, ...),(a,b,c),return)
SDL_DYNAPI_PROC(int,SDL_swprintf,(SDL_OUT_Z_CAP(b) wchar_t *a, size_t b, SDL_PRINTF_FORMAT_STRING const wchar_t *c, ...),(a,b,c),return)
SDL_DYNAPI_PROC(int,SDL_sscanf,(const char *a, SDL_SCANF_FORMAT_STRING const char *b, ...),(a,b),return)
+SDL_DYNAPI_PROC(size_t,SDL_RWprintf,(SDL_RWops *a, SDL_PRINTF_FORMAT_STRING const char *b, ...),(a,b),return)
#endif
#ifdef SDL_CreateThread
@@ -979,3 +980,4 @@ SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetWindowProperties,(SDL_Window *a),(a),ret
SDL_DYNAPI_PROC(int,SDL_ClearProperty,(SDL_PropertiesID a, const char *b),(a,b),return)
SDL_DYNAPI_PROC(int,SDL_EnterAppMainCallbacks,(int a, char *b[], SDL_AppInit_func c, SDL_AppIterate_func d, SDL_AppEvent_func e, SDL_AppQuit_func f),(a,b,c,d,e,f),return)
SDL_DYNAPI_PROC(void,SDL_CleanupEvent,(SDL_Event *a),(a),)
+SDL_DYNAPI_PROC(size_t,SDL_RWvprintf,(SDL_RWops *a, const char *b, va_list c),(a,b,c),return)
diff --git a/src/file/SDL_rwops.c b/src/file/SDL_rwops.c
index 049cf51206de..b6f943f1c68c 100644
--- a/src/file/SDL_rwops.c
+++ b/src/file/SDL_rwops.c
@@ -808,6 +808,35 @@ size_t SDL_RWwrite(SDL_RWops *context, const void *ptr, size_t size)
return bytes;
}
+size_t SDL_RWprintf(SDL_RWops *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
+{
+ va_list ap;
+ int size;
+ char *string = NULL;
+ size_t bytes;
+
+ va_start(ap, fmt);
+ size = SDL_vasprintf(&string, fmt, ap);
+ va_end(ap);
+
+ bytes = SDL_RWwrite(context, string, size);
+ SDL_free(string);
+ return bytes;
+}
+
+size_t SDL_RWvprintf(SDL_RWops *context, const char *fmt, va_list ap)
+{
+ int size;
+ char *string = NULL;
+ size_t bytes;
+
+ size = SDL_vasprintf(&string, fmt, ap);
+
+ bytes = SDL_RWwrite(context, string, size);
+ SDL_free(string);
+ return bytes;
+}
+
int SDL_RWclose(SDL_RWops *context)
{
if (!context) {
diff --git a/test/testautomation_rwops.c b/test/testautomation_rwops.c
index a8cdb903f20f..59487e36463b 100644
--- a/test/testautomation_rwops.c
+++ b/test/testautomation_rwops.c
@@ -110,11 +110,11 @@ static void testGenericRWopsValidations(SDL_RWops *rw, SDL_bool write)
SDLTest_AssertPass("Call to SDL_RWseek succeeded");
SDLTest_AssertCheck(i == (Sint64)0, "Verify seek to 0 with SDL_RWseek (SDL_RW_SEEK_SET), expected 0, got %" SDL_PRIs64, i);
- /* Test write. */
+ /* Test write */
s = SDL_RWwrite(rw, RWopsHelloWorldTestString, sizeof(RWopsHelloWorldTestString) - 1);
SDLTest_AssertPass("Call to SDL_RWwrite succeeded");
if (write) {
- SDLTest_AssertCheck(s == sizeof(RWopsHelloWorldTestString) - 1, "Verify result of writing one byte with SDL_RWwrite, expected 1, got %i", (int)s);
+ SDLTest_AssertCheck(s == sizeof(RWopsHelloWorldTestString) - 1, "Verify result of writing with SDL_RWwrite, expected %i, got %i", (int)sizeof(RWopsHelloWorldTestString) - 1, (int)s);
} else {
SDLTest_AssertCheck(s == 0, "Verify result of writing with SDL_RWwrite, expected: 0, got %i", (int)s);
}
@@ -129,6 +129,37 @@ static void testGenericRWopsValidations(SDL_RWops *rw, SDL_bool write)
SDLTest_AssertPass("Call to SDL_RWseek succeeded");
SDLTest_AssertCheck(i == (Sint64)0, "Verify seek to 0 with SDL_RWseek (SDL_RW_SEEK_SET), expected 0, got %" SDL_PRIs64, i);
+ /* Test read */
+ s = SDL_RWread(rw, buf, sizeof(RWopsHelloWorldTestString) - 1);
+ SDLTest_AssertPass("Call to SDL_RWread succeeded");
+ SDLTest_AssertCheck(
+ s == (sizeof(RWopsHelloWorldTestString) - 1),
+ "Verify result from SDL_RWread, expected %i, got %i",
+ (int)(sizeof(RWopsHelloWorldTestString) - 1),
+ (int)s);
+ SDLTest_AssertCheck(
+ SDL_memcmp(buf, RWopsHelloWorldTestString, sizeof(RWopsHelloWorldTestString) - 1) == 0,
+ "Verify read bytes match expected string, expected '%s', got '%s'", RWopsHelloWorldTestString, buf);
+
+ /* Test seek back to start */
+ i = SDL_RWseek(rw, 0, SDL_RW_SEEK_SET);
+ SDLTest_AssertPass("Call to SDL_RWseek succeeded");
+ SDLTest_AssertCheck(i == (Sint64)0, "Verify seek to 0 with SDL_RWseek (SDL_RW_SEEK_SET), expected 0, got %" SDL_PRIs64, i);
+
+ /* Test printf */
+ s = SDL_RWprintf(rw, "%s", RWopsHelloWorldTestString);
+ SDLTest_AssertPass("Call to SDL_RWprintf succeeded");
+ if (write) {
+ SDLTest_AssertCheck(s == sizeof(RWopsHelloWorldTestString) - 1, "Verify result of writing with SDL_RWprintf, expected %i, got %i", (int)sizeof(RWopsHelloWorldTestString) - 1, (int)s);
+ } else {
+ SDLTest_AssertCheck(s == 0, "Verify result of writing with SDL_RWwrite, expected: 0, got %i", (int)s);
+ }
+
+ /* Test seek back to start */
+ i = SDL_RWseek(rw, 0, SDL_RW_SEEK_SET);
+ SDLTest_AssertPass("Call to SDL_RWseek succeeded");
+ SDLTest_AssertCheck(i == (Sint64)0, "Verify seek to 0 with SDL_RWseek (SDL_RW_SEEK_SET), expected 0, got %" SDL_PRIs64, i);
+
/* Test read */
s = SDL_RWread(rw, buf, sizeof(RWopsHelloWorldTestString) - 1);
SDLTest_AssertPass("Call to SDL_RWread succeeded");