sdl2-compat: Fixed crashes if the application cleans up after SDL_Quit()

From 4ed42d7963866c8f0b90a7c84ea4fea08e41c163 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 14 Feb 2025 07:41:13 -0800
Subject: [PATCH] Fixed crashes if the application cleans up after SDL_Quit()

The application might free surfaces and audio streams after SDL_Quit(), which is allowed.

Wesnoth and 'python py-sdl2/examples/gui.py' both exit cleanly now.

Fixes https://github.com/libsdl-org/sdl2-compat/issues/313
---
 src/sdl2_compat.c | 56 +++++++++++++++++++++++++++++++++++++----------
 src/sdl3_syms.h   |  1 +
 2 files changed, 45 insertions(+), 12 deletions(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index fbb5d4b..db73b5c 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -213,6 +213,9 @@ do { \
 #define PROP_WINDOW_EXPECTED_SCALE "sdl2-compat.window.expected_scale"
 #define PROP_RENDERER_BATCHING "sdl2-compat.renderer.batching"
 #define PROP_RENDERER_RELATIVE_SCALING "sdl2-compat.renderer.relative-scaling"
+#define PROP_SURFACE2 "sdl2-compat.surface2"
+#define PROP_STREAM2 "sdl2-compat.stream2"
+
 
 
 static bool WantDebugLogging = false;
@@ -3038,18 +3041,23 @@ SDL_LoadWAV_RW(SDL2_RWops *rwops2, int freesrc, SDL2_AudioSpec *spec2, Uint8 **a
     return retval;
 }
 
-#define PROP_SURFACE2 "sdl2-compat.surface2"
-
 static void SDLCALL CleanupSurface2(void *userdata, void *value)
 {
     SDL2_Surface *surface = (SDL2_Surface *)value;
+    unsigned int i;
 
-    if (surface->format) {
-        SDL_SetPixelFormatPalette(surface->format, NULL);
-        SDL_FreeFormat(surface->format);
-        surface->format = NULL;
+    /* The application will clean up all surfaces except window surfaces */
+    for (i = 0; (i < SDL_arraysize(OldWindowSurfaces)) && OldWindowSurfaces[i]; i++) {
+        if (OldWindowSurfaces[i] == surface) {
+            OldWindowSurfaces[i] = NULL;
+            surface->flags &= ~SDL_DONTFREE;
+            surface->refcount = 1;
+            surface->map = NULL;
+            SDL_FreeSurface(surface);
+            OldWindowSurfaces[i] = surface;
+            break;
+        }
     }
-    SDL3_free(surface);
 }
 
 static SDL2_Surface *CreateSurface2from3(SDL_Surface *surface3)
@@ -3440,14 +3448,13 @@ SDL_CreateRGBSurfaceWithFormatFrom(void *pixels, int width, int height, int dept
 SDL_DECLSPEC void SDLCALL
 SDL_FreeSurface(SDL2_Surface *surface)
 {
-    const int total = (int) (SDL_arraysize(OldWindowSurfaces));
-    int i;
+    unsigned int i;
 
     if (!surface) {
         return;
     }
 
-    for (i = 0; (i < total) && OldWindowSurfaces[i]; i++) {
+    for (i = 0; (i < SDL_arraysize(OldWindowSurfaces)) && OldWindowSurfaces[i]; i++) {
         if (OldWindowSurfaces[i] == surface) {
             return;  /* this is a pointer from SDL_GetWindowSurface--a bug in the app--refuse to free it. */
         }
@@ -3461,7 +3468,19 @@ SDL_FreeSurface(SDL2_Surface *surface)
         return;
     }
 
-    SDL3_DestroySurface(Surface2to3(surface));
+    if (surface->map) {
+        SDL_Surface *surface3 = (SDL_Surface *)surface->map;
+        SDL3_DestroySurface(surface3);
+        surface->map = NULL;
+    }
+
+    if (surface->format) {
+        SDL_SetPixelFormatPalette(surface->format, NULL);
+        SDL_FreeFormat(surface->format);
+        surface->format = NULL;
+    }
+
+    SDL3_free(surface);
 }
 
 SDL_DECLSPEC int SDLCALL
@@ -6678,6 +6697,16 @@ static void AudioSi16SysToUi16MSB(Uint16 *dst, const Sint16 *src, const size_t n
     }
 }
 
+static void SDLCALL CleanupStream2(void *userdata, void *value)
+{
+    SDL2_AudioStream *stream = (SDL2_AudioStream *)value;
+
+    /* The SDL3 audio stream is being cleaned up, make sure we don't double-free
+     * if the application frees the SDL2 audio stream later.
+     */
+    stream->stream3 = NULL;
+}
+
 SDL_DECLSPEC SDL2_AudioStream * SDLCALL
 SDL_NewAudioStream(const SDL2_AudioFormat real_src_format, const Uint8 src_channels, const int src_rate, const SDL2_AudioFormat real_dst_format, const Uint8 dst_channels, const int dst_rate)
 {
@@ -6709,6 +6738,7 @@ SDL_NewAudioStream(const SDL2_AudioFormat real_src_format, const Uint8 src_chann
         SDL3_free(retval);
         return NULL;
     }
+    SDL3_SetPointerPropertyWithCleanup(SDL3_GetAudioStreamProperties(retval->stream3), PROP_STREAM2, retval, CleanupStream2, NULL);
 
     retval->src_format = real_src_format;
     retval->dst_format = real_dst_format;
@@ -6779,7 +6809,9 @@ SDL_DECLSPEC void SDLCALL
 SDL_FreeAudioStream(SDL2_AudioStream *stream2)
 {
     if (stream2) {
-        SDL3_DestroyAudioStream(stream2->stream3);
+        if (stream2->stream3) {
+            SDL3_DestroyAudioStream(stream2->stream3);
+        }
         SDL3_free(stream2->callback2_buffer);
         SDL3_free(stream2);
     }
diff --git a/src/sdl3_syms.h b/src/sdl3_syms.h
index 79a2441..d9cc83e 100644
--- a/src/sdl3_syms.h
+++ b/src/sdl3_syms.h
@@ -252,6 +252,7 @@ SDL3_SYM(SDL_AudioDeviceID*,GetAudioRecordingDevices,(int *a),(a),return)
 SDL3_SYM(int,GetAudioStreamAvailable,(SDL_AudioStream *a),(a),return)
 SDL3_SYM(int,GetAudioStreamData,(SDL_AudioStream *a, void *b, int c),(a,b,c),return)
 SDL3_SYM(SDL_AudioDeviceID,GetAudioStreamDevice,(SDL_AudioStream *a),(a),return)
+SDL3_SYM(SDL_PropertiesID,GetAudioStreamProperties,(SDL_AudioStream *a),(a),return)
 SDL3_SYM(const char*,GetBasePath,(void),(),return)
 SDL3_SYM(bool,GetBooleanProperty,(SDL_PropertiesID a, const char *b, bool c),(a,b,c),return)
 SDL3_SYM_PASSTHROUGH(int,GetCPUCacheLineSize,(void),(),return)