SDL: Added portable file and directory operations (thanks @icculus!)

From db0c1d7aeb5a51c3d2bd69a87a8b012f65c48bee Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 16 Mar 2024 08:15:13 -0700
Subject: [PATCH] Added portable file and directory operations (thanks
 @icculus!)

---
 Android.mk                                    |   2 +
 CMakeLists.txt                                |  73 +++++++
 VisualC-GDK/SDL/SDL.vcxproj                   |   3 +
 VisualC-GDK/SDL/SDL.vcxproj.filters           |   9 +
 VisualC-WinRT/SDL-UWP.vcxproj                 |   3 +
 VisualC-WinRT/SDL-UWP.vcxproj.filters         |  15 ++
 VisualC/SDL/SDL.vcxproj                       |   3 +
 VisualC/SDL/SDL.vcxproj.filters               |   9 +
 Xcode/SDL/SDL.xcodeproj/project.pbxproj       |  19 ++
 include/SDL3/SDL_filesystem.h                 |  96 +++++++++
 include/build_config/SDL_build_config.h.cmake |   5 +
 .../build_config/SDL_build_config_android.h   |   1 +
 .../SDL_build_config_emscripten.h             |   1 +
 include/build_config/SDL_build_config_ios.h   |   1 +
 include/build_config/SDL_build_config_macos.h |   1 +
 .../build_config/SDL_build_config_minimal.h   |   1 +
 include/build_config/SDL_build_config_ngage.h |   1 +
 .../build_config/SDL_build_config_windows.h   |   1 +
 .../build_config/SDL_build_config_wingdk.h    |   1 +
 include/build_config/SDL_build_config_winrt.h |   4 +
 include/build_config/SDL_build_config_xbox.h  |   2 +
 src/dynapi/SDL_dynapi.sym                     |   6 +
 src/dynapi/SDL_dynapi_overrides.h             |   6 +
 src/dynapi/SDL_dynapi_procs.h                 |   6 +
 src/filesystem/SDL_filesystem.c               |  91 +++++++++
 src/filesystem/SDL_sysfilesystem.h            |  32 +++
 src/filesystem/dummy/SDL_sysfsops.c           |  54 +++++
 src/filesystem/posix/SDL_sysfsops.c           | 138 +++++++++++++
 src/filesystem/windows/SDL_sysfsops.c         | 188 ++++++++++++++++++
 test/testfilesystem.c                         |  71 ++++++-
 30 files changed, 841 insertions(+), 2 deletions(-)
 create mode 100644 src/filesystem/SDL_filesystem.c
 create mode 100644 src/filesystem/SDL_sysfilesystem.h
 create mode 100644 src/filesystem/dummy/SDL_sysfsops.c
 create mode 100644 src/filesystem/posix/SDL_sysfsops.c
 create mode 100644 src/filesystem/windows/SDL_sysfsops.c

diff --git a/Android.mk b/Android.mk
index 2758331744ddb..2b42555596f59 100644
--- a/Android.mk
+++ b/Android.mk
@@ -50,7 +50,9 @@ LOCAL_SRC_FILES := \
 	$(wildcard $(LOCAL_PATH)/src/misc/android/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/power/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/power/android/*.c) \
+	$(wildcard $(LOCAL_PATH)/src/filesystem/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/filesystem/android/*.c) \
+	$(wildcard $(LOCAL_PATH)/src/filesystem/posix/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/sensor/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/sensor/android/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/render/*.c) \
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fcbb93fc3d792..8017bc2b79881 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -482,6 +482,7 @@ sdl_glob_sources(
   "${SDL3_SOURCE_DIR}/src/dynapi/*.c"
   "${SDL3_SOURCE_DIR}/src/events/*.c"
   "${SDL3_SOURCE_DIR}/src/file/*.c"
+  "${SDL3_SOURCE_DIR}/src/filesystem/*.c"
   "${SDL3_SOURCE_DIR}/src/joystick/*.c"
   "${SDL3_SOURCE_DIR}/src/haptic/*.c"
   "${SDL3_SOURCE_DIR}/src/hidapi/*.c"
@@ -1288,6 +1289,14 @@ if(ANDROID)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/android/*.c")
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  set(SDL_FSOPS_POSIX 1)  # !!! FIXME: this might need something else for .apk data?
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
+  set(SDL_FSOPS_POSIX 1)  # !!! FIXME: this might need something else for .apk data?
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
   if(SDL_HAPTIC)
     set(SDL_HAPTIC_ANDROID 1)
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/android/*.c")
@@ -1446,6 +1455,14 @@ elseif(EMSCRIPTEN)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/emscripten/*.c")
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  set(SDL_FSOPS_POSIX 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
+  set(SDL_FSOPS_POSIX 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
   if(SDL_CAMERA)
     set(SDL_CAMERA_DRIVER_EMSCRIPTEN 1)
     set(HAVE_CAMERA TRUE)
@@ -1762,6 +1779,10 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU)
   endif()
   set(HAVE_SDL_STORAGE 1)
 
+  set(SDL_FSOPS_POSIX 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
   set(SDL_TIMER_UNIX 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -1976,11 +1997,15 @@ elseif(WINDOWS)
   set(SDL_FILESYSTEM_WINDOWS 1)
   if(WINDOWS_STORE)
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/winrt/*.cpp")
+    sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/windows/SDL_sysfsops.c")
   else()
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/windows/*.c")
   endif()
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  set(SDL_FSOPS_WINDOWS 1)
+  set(HAVE_SDL_FSOPS TRUE)
+
   set(SDL_STORAGE_GENERIC 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/storage/generic/*.c")
   if(NOT WINDOWS_STORE)
@@ -2230,6 +2255,10 @@ elseif(APPLE)
   endif()
   set(HAVE_SDL_STORAGE 1)
 
+  set(SDL_FSOPS_POSIX 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
   if(SDL_SENSOR)
     if(IOS OR VISIONOS)
       set(SDL_SENSOR_COREMOTION 1)
@@ -2422,6 +2451,14 @@ elseif(HAIKU)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/haiku/*.cc")
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  set(SDL_FSOPS_POSIX 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
+  set(SDL_FSOPS_POSIX 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
   set(SDL_TIMER_HAIKU 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/haiku/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2454,6 +2491,14 @@ elseif(RISCOS)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/riscos/*.c")
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  set(SDL_FSOPS_POSIX 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
+  set(SDL_FSOPS_POSIX 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
   set(SDL_TIMER_UNIX 1)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/unix/*.c")
   set(HAVE_SDL_TIMERS TRUE)
@@ -2491,6 +2536,10 @@ elseif(VITA)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/vita/*.c")
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  # !!! FIXME: do we need a FSops implementation for this?
+
+  # !!! FIXME: do we need a FSops implementation for this?
+
   if(SDL_JOYSTICK)
     set(SDL_JOYSTICK_VITA 1)
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/vita/*.c")
@@ -2625,6 +2674,10 @@ elseif(PSP)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/psp/*.c")
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  # !!! FIXME: do we need a FSops implementation for this?
+
+  # !!! FIXME: do we need a FSops implementation for this?
+
   if(SDL_JOYSTICK)
     set(SDL_JOYSTICK_PSP 1)
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/psp/*.c")
@@ -2688,6 +2741,10 @@ elseif(PS2)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ps2/*.c")
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  # !!! FIXME: do we need a FSops implementation for this?
+
+  # !!! FIXME: do we need a FSops implementation for this?
+
   if(SDL_JOYSTICK)
     set(SDL_JOYSTICK_PS2 1)
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/ps2/*.c")
@@ -2739,6 +2796,10 @@ elseif(N3DS)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/n3ds/*.c")
   set(HAVE_SDL_FILESYSTEM TRUE)
 
+  # !!! FIXME: do we need a FSops implementation for this?
+
+  # !!! FIXME: do we need a FSops implementation for this?
+
   if(SDL_JOYSTICK)
     set(SDL_JOYSTICK_N3DS 1)
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/n3ds/*.c")
@@ -2763,6 +2824,10 @@ elseif(N3DS)
   sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/n3ds/*.c")
   set(HAVE_SDL_TIMERS TRUE)
 
+  set(SDL_FSOPS_POSIX 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
+  set(HAVE_SDL_FSOPS TRUE)
+
   if(SDL_SENSOR)
     set(SDL_SENSOR_N3DS 1)
     sdl_glob_sources("${SDL3_SOURCE_DIR}/src/sensor/n3ds/*.c")
@@ -2850,6 +2915,14 @@ 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_FSOPS)
+  set(SDL_FSOPS_DUMMY 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/dummy/SDL_sysfsops.c")
+endif()
+if(NOT HAVE_SDL_FSOPS)
+  set(SDL_FSOPS_DUMMY 1)
+  sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/dummy/SDL_sysfsops.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 e50832cb1a827..948e75578ab4e 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -422,6 +422,7 @@
     <ClInclude Include="..\..\src\events\SDL_mouse_c.h" />
     <ClInclude Include="..\..\src\events\SDL_touch_c.h" />
     <ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
+    <ClInclude Include="..\..\src\filesystem\SDL_filesystem.h" />
     <ClInclude Include="..\..\src\haptic\SDL_haptic_c.h" />
     <ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
@@ -504,6 +505,8 @@
     </ClCompile>
     <ClCompile Include="..\..\src\camera\dummy\SDL_camera_dummy.c" />
     <ClCompile Include="..\..\src\camera\SDL_camera.c" />
+    <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c" />
+    <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c" />
     <ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c" />
     <ClCompile Include="..\..\src\main\SDL_main_callbacks.c" />
     <ClCompile Include="..\..\src\SDL_guid.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 49c00513c5b5d..bc62d7a0ba469 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -4,6 +4,12 @@
     <ClCompile Include="..\..\src\core\gdk\SDL_gdk.cpp" />
     <ClCompile Include="..\..\src\core\windows\pch.c" />
     <ClCompile Include="..\..\src\core\windows\pch_cpp.cpp" />
+    <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c">
+      <Filter>filesystem</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c">
+      <Filter>filesystem\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\render\direct3d12\SDL_render_d3d12_xbox.cpp" />
     <ClCompile Include="..\..\src\render\direct3d12\SDL_shaders_d3d12_xboxone.cpp" />
     <ClCompile Include="..\..\src\render\direct3d12\SDL_shaders_d3d12_xboxseries.cpp" />
@@ -313,6 +319,9 @@
     <ClInclude Include="..\..\src\events\SDL_mouse_c.h" />
     <ClInclude Include="..\..\src\events\SDL_touch_c.h" />
     <ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
+    <ClInclude Include="..\..\src\filesystem\SDL_filesystem.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\haptic\SDL_haptic_c.h" />
     <ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj
index 79d7a609786ea..4948abf9b5dc6 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj
+++ b/VisualC-WinRT/SDL-UWP.vcxproj
@@ -121,6 +121,7 @@
     <ClInclude Include="..\src\events\SDL_mouse_c.h" />
     <ClInclude Include="..\src\events\SDL_touch_c.h" />
     <ClInclude Include="..\src\events\SDL_windowevents_c.h" />
+    <ClInclude Include="..\src\filesystem\SDL_filesystem.h" />
     <ClInclude Include="..\src\haptic\SDL_haptic_c.h" />
     <ClInclude Include="..\src\haptic\SDL_syshaptic.h" />
     <ClInclude Include="..\src\haptic\windows\SDL_dinputhaptic_c.h" />
@@ -334,6 +335,8 @@
       <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
     </ClCompile>
     <ClCompile Include="..\src\file\SDL_iostream.c" />
+    <ClCompile Include="..\src\filesystem\SDL_filesystem.c" />
+    <ClCompile Include="..\src\filesystem\windows\SDL_sysfsops.c" />
     <ClCompile Include="..\src\haptic\dummy\SDL_syshaptic.c" />
     <ClCompile Include="..\src\haptic\SDL_haptic.c" />
     <ClCompile Include="..\src\haptic\windows\SDL_dinputhaptic.c" />
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters
index c143ae9e9fefb..ca56e1757ea81 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj.filters
+++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters
@@ -19,6 +19,12 @@
     <Filter Include="camera\dummy">
       <UniqueIdentifier>{000031d805439b865ff4550d2f620000}</UniqueIdentifier>
     </Filter>
+    <Filter Include="filesystem">
+      <UniqueIdentifier>{00004389761f0ae646deb5a3d65f0000}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="filesystem\windows">
+      <UniqueIdentifier>{0000bc587ef6c558d75ce2e620cb0000}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\include\SDL3\SDL_begin_code.h">
@@ -180,6 +186,9 @@
     <ClInclude Include="..\src\camera\SDL_syscamera.h">
       <Filter>camera</Filter>
     </ClInclude>
+    <ClInclude Include="..\src\filesystem\SDL_filesystem.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
     <ClInclude Include="..\src\joystick\SDL_gamepad_c.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -579,6 +588,12 @@
     <ClCompile Include="..\src\events\SDL_windowevents.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\filesystem\SDL_filesystem.c">
+      <Filter>filesystem</Filter>
+    </ClCompile>
+    <ClCompile Include="..\src\filesystem\windows\SDL_sysfsops.c">
+      <Filter>filesystem\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\src\filesystem\winrt\SDL_sysfilesystem.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 504e3f9c00f5e..24c397472c995 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -344,6 +344,7 @@
     <ClInclude Include="..\..\src\events\SDL_mouse_c.h" />
     <ClInclude Include="..\..\src\events\SDL_touch_c.h" />
     <ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
+    <ClInclude Include="..\..\src\filesystem\SDL_filesystem.h" />
     <ClInclude Include="..\..\src\haptic\SDL_haptic_c.h" />
     <ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
@@ -401,6 +402,8 @@
     <ClCompile Include="..\..\src\camera\dummy\SDL_camera_dummy.c" />
     <ClCompile Include="..\..\src\camera\mediafoundation\SDL_camera_mediafoundation.c" />
     <ClCompile Include="..\..\src\camera\SDL_camera.c" />
+    <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c" />
+    <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c" />
     <ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c" />
     <ClCompile Include="..\..\src\main\SDL_main_callbacks.c" />
     <ClCompile Include="..\..\src\render\vulkan\SDL_render_vulkan.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index c6568f85ff0d1..5d3e43001a086 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -417,6 +417,9 @@
     <ClInclude Include="..\..\src\camera\SDL_syscamera.h">
       <Filter>camera</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\filesystem\SDL_filesystem.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\main\SDL_main_callbacks.h">
       <Filter>main</Filter>
     </ClInclude>
@@ -871,6 +874,12 @@
     <ClCompile Include="..\..\src\camera\SDL_camera.c">
       <Filter>camera</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\filesystem\SDL_filesystem.c">
+      <Filter>filesystem</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c">
+      <Filter>filesystem\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c">
       <Filter>main\generic</Filter>
     </ClCompile>
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index b326435071848..df7d4548dd4a1 100644
--- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj
+++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
@@ -503,6 +503,9 @@
 		F3FA5A242B59ACE000FEAD97 /* yuv_rgb_lsx.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1B2B59ACE000FEAD97 /* yuv_rgb_lsx.h */; };
 		F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1C2B59ACE000FEAD97 /* yuv_rgb_common.h */; };
 		FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); };
+		000080903BC03006F24E0000 /* SDL_filesystem.c in Sources */ = {isa = PBXBuildFile; fileRef = 00002B010DB1A70931C20000 /* SDL_filesystem.c */; };
+		00000D60346481EEC8FB0000 /* SDL_filesystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 0000BE1BF5193C6D0F4F0000 /* SDL_filesystem.h */; };
+		0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */ = {isa = PBXBuildFile; fileRef = 0000F4E6AA3EF99DA3C80000 /* SDL_sysfsops.c */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -1034,6 +1037,9 @@
 		F59C710600D5CB5801000001 /* SDL.info */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = SDL.info; sourceTree = "<group>"; };
 		F5A2EF3900C6A39A01000001 /* BUGS.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; name = BUGS.txt; path = ../../BUGS.txt; sourceTree = SOURCE_ROOT; };
 		FA73671C19A540EF004122E4 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
+		00002B010DB1A70931C20000 /* SDL_filesystem.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_filesystem.c; path = SDL_filesystem.c; sourceTree = "<group>"; };
+		0000BE1BF5193C6D0F4F0000 /* SDL_filesystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_filesystem.h; path = SDL_filesystem.h; sourceTree = "<group>"; };
+		0000F4E6AA3EF99DA3C80000 /* SDL_sysfsops.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_sysfsops.c; path = SDL_sysfsops.c; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -1858,6 +1864,9 @@
 			children = (
 				A7D8A7FD23E2513F00DCD162 /* cocoa */,
 				A7D8A7F723E2513F00DCD162 /* dummy */,
+				00002B010DB1A70931C20000 /* SDL_filesystem.c */,
+				0000BE1BF5193C6D0F4F0000 /* SDL_filesystem.h */,
+				000050A2BB34616138570000 /* posix */,
 			);
 			path = filesystem;
 			sourceTree = "<group>";
@@ -2209,6 +2218,14 @@
 			path = resources;
 			sourceTree = "<group>";
 		};
+		000050A2BB34616138570000 /* posix */ = {
+			isa = PBXGroup;
+			children = (
+				0000F4E6AA3EF99DA3C80000 /* SDL_sysfsops.c */,
+			);
+			path = posix;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXHeadersBuildPhase section */
@@ -2792,6 +2809,8 @@
 				000098E9DAA43EF6FF7F0000 /* SDL_camera.c in Sources */,
 				00001B2471F503DD3C1B0000 /* SDL_camera_dummy.c in Sources */,
 				00002B20A48E055EB0350000 /* SDL_camera_coremedia.m in Sources */,
+				000080903BC03006F24E0000 /* SDL_filesystem.c in Sources */,
+				0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h
index 8030c04354a78..75e71bf9737ea 100644
--- a/include/SDL3/SDL_filesystem.h
+++ b/include/SDL3/SDL_filesystem.h
@@ -236,6 +236,102 @@ typedef enum
  */
 extern DECLSPEC char *SDLCALL SDL_GetUserFolder(SDL_Folder folder);
 
+
+/* Abstract filesystem interface */
+
+typedef enum SDL_PathType
+{
+    SDL_PATHTYPE_FILE, /**< a normal file */
+    SDL_PATHTYPE_DIRECTORY, /**< a directory */
+    SDL_PATHTYPE_OTHER /**< something completely different like a device node (not a symlink, those are always followed) */
+} SDL_PathType;
+
+/* SDL file timestamps are 64-bit integers representing seconds since the Unix epoch (Jan 1, 1970) */
+typedef Sint64 SDL_FileTimestamp;
+
+typedef struct SDL_PathInfo
+{
+    SDL_PathType type;              /* the path type */
+    Uint64 size;                    /* the file size in bytes */
+    SDL_FileTimestamp create_time;  /* the time when the path was created */
+    SDL_FileTimestamp modify_time;  /* the last time the path was modified */
+    SDL_FileTimestamp access_time;  /* the last time the path was read */
+} SDL_PathInfo;
+
+/**
+ * Create a directory.
+ *
+ * \param path the path of the directory to create
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_CreateDirectory(const char *path);
+
+/* Callback for filesystem enumeration. Return 1 to keep enumerating, 0 to stop enumerating (no error), -1 to stop enumerating and report an error. "origdir" is the directory being enumerated, "fname" is the enumerated entry. */
+typedef int (SDLCALL *SDL_EnumerateDirectoryCallback)(void *userdata, void *reserved, const char *origdir, const char *fname);
+
+/**
+ * Enumerate a directory.
+ *
+ * \param path the path of the directory to enumerate
+ * \param callback a function that is called for each entry in the directory
+ * \param userdata a pointer that is passed to `callback`
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata);
+
+/**
+ * Remove a file or an empty directory.
+ *
+ * \param path the path of the directory to enumerate
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_RemovePath(const char *path);
+
+/**
+ * Rename a file or directory.
+ *
+ * \param oldpath the old path
+ * \param newpath the new path
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_RenamePath(const char *oldpath, const char *newpath);
+
+/**
+ * Get information about a filesystem path.
+ *
+ * \param path the path to query
+ * \param info a pointer filled in with information about the path
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_GetPathInfo(const char *path, SDL_PathInfo *info);
+
+/* some helper functions ... */
+
+/* Converts an SDL file timestamp into a Windows FILETIME (100-nanosecond intervals since January 1, 1601). Fills in the two 32-bit values of the FILETIME structure.
+ *
+ * \param ftime the time to convert
+ * \param low a pointer filled in with the low portion of the Windows FILETIME value
+ * \param high a pointer filled in with the high portion of the Windows FILETIME value
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC void SDLCALL SDL_FileTimeToWindows(Sint64 ftime, Uint32 *low, Uint32 *high);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index 1e650c491c878..c71a0cafb0e28 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/include/build_config/SDL_build_config.h.cmake
@@ -469,6 +469,11 @@
 #cmakedefine SDL_STORAGE_GENERIC @SDL_STORAGE_GENERIC@
 #cmakedefine SDL_STORAGE_STEAM @SDL_STORAGE_STEAM@
 
+/* Enable system FSops support */
+#cmakedefine SDL_FSOPS_POSIX @SDL_FSOPS_POSIX@
+#cmakedefine SDL_FSOPS_WINDOWS @SDL_FSOPS_WINDOWS@
+#cmakedefine SDL_FSOPS_DUMMY @SDL_FSOPS_DUMMY@
+
 /* 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/include/build_config/SDL_build_config_android.h b/include/build_config/SDL_build_config_android.h
index 64f8076e00b01..b784afd000e86 100644
--- a/include/build_config/SDL_build_config_android.h
+++ b/include/build_config/SDL_build_config_android.h
@@ -187,6 +187,7 @@
 
 /* Enable the filesystem driver */
 #define SDL_FILESYSTEM_ANDROID   1
+#define SDL_FSOPS_POSIX 1
 
 /* Enable the camera driver */
 #define SDL_CAMERA_DRIVER_ANDROID 1
diff --git a/include/build_config/SDL_build_config_emscripten.h b/include/build_config/SDL_build_config_emscripten.h
index 89d5531f30ac6..bf6e919cf1667 100644
--- a/include/build_config/SDL_build_config_emscripten.h
+++ b/include/build_config/SDL_build_config_emscripten.h
@@ -206,6 +206,7 @@
 
 /* Enable system filesystem support */
 #define SDL_FILESYSTEM_EMSCRIPTEN 1
+#define SDL_FSOPS_POSIX 1
 
 /* Enable the camera driver */
 #define SDL_CAMERA_DRIVER_EMSCRIPTEN  1
diff --git a/include/build_config/SDL_build_config_ios.h b/include/build_config/SDL_build_config_ios.h
index e130cc4673053..875f1942aa74b 100644
--- a/include/build_config/SDL_build_config_ios.h
+++ b/include/build_config/SDL_build_config_ios.h
@@ -207,6 +207,7 @@
 
 /* enable filesystem support */
 #define SDL_FILESYSTEM_COCOA   1
+#define SDL_FSOPS_POSIX 1
 
 /* enable camera support */
 #ifndef SDL_PLATFORM_TVOS
diff --git a/include/build_config/SDL_build_config_macos.h b/include/build_config/SDL_build_config_macos.h
index 39ee50d4563a4..c853e58a9b3f9 100644
--- a/include/build_config/SDL_build_config_macos.h
+++ b/include/build_config/SDL_build_config_macos.h
@@ -264,6 +264,7 @@
 
 /* enable filesystem support */
 #define SDL_FILESYSTEM_COCOA   1
+#define SDL_FSOPS_POSIX 1
 
 /* enable camera support */
 #define SDL_CAMERA_DRIVER_COREMEDIA 1
diff --git a/include/build_config/SDL_build_config_minimal.h b/include/build_config/SDL_build_config_minimal.h
index 06d02557ea223..f949f94f634c1 100644
--- a/include/build_config/SDL_build_config_minimal.h
+++ b/include/build_config/SDL_build_config_minimal.h
@@ -88,6 +88,7 @@ typedef unsigned int uintptr_t;
 
 /* Enable the dummy filesystem driver (src/filesystem/dummy/\*.c) */
 #define SDL_FILESYSTEM_DUMMY  1
+#define SDL_FSOPS_DUMMY 1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */
 #define SDL_CAMERA_DRIVER_DUMMY  1
diff --git a/include/build_config/SDL_build_config_ngage.h b/include/build_config/SDL_build_config_ngage.h
index 3449627b02e8a..5969437032fe5 100644
--- a/include/build_config/SDL_build_config_ngage.h
+++ b/include/build_config/SDL_build_config_ngage.h
@@ -85,6 +85,7 @@ typedef unsigned long      uintptr_t;
 
 /* Enable the dummy filesystem driver (src/filesystem/dummy/\*.c) */
 #define SDL_FILESYSTEM_DUMMY 1
+#define SDL_FSOPS_DUMMY 1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */
 #define SDL_CAMERA_DRIVER_DUMMY  1
diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h
index 19873dbf17842..69852a766d270 100644
--- a/include/build_config/SDL_build_config_windows.h
+++ b/include/build_config/SDL_build_config_windows.h
@@ -312,6 +312,7 @@ typedef unsigned int uintptr_t;
 
 /* Enable filesystem support */
 #define SDL_FILESYSTEM_WINDOWS  1
+#define SDL_FSOPS_WINDOWS 1
 
 /* Enable the camera driver */
 #define SDL_CAMERA_DRIVER_MEDIAFOUNDATION 1
diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h
index 992052a819125..f760cf477e91f 100644
--- a/include/build_config/SDL_build_config_wingdk.h
+++ b/include/build_config/SDL_build_config_wingdk.h
@@ -244,6 +244,7 @@
 
 /* Enable filesystem support */
 #define SDL_FILESYSTEM_WINDOWS  1
+#define SDL_FSOPS_WINDOWS 1
 
 /* Enable the camera driver (src/camera/dummy/\*.c) */  /* !!! FIXME */
 #define SDL_CAMERA_DRIVER_DUMMY  1
diff --git a/include/build_config/SDL_build_config_winrt.h b/include/build_config/SDL_build_config_winrt.h
index b5e5725fba10f..9a7702d870d98 100644
--- a/include/build_config/SDL_build_config_winrt.h
+++ b/include/build_config/SDL_build_config_winrt.h
@@ -213,6 +213,10 @@
 /* Enable system power support */
 #define SDL_POWER_WINRT 1
 
+/* Enable filesystem support */
+#define SDL_FILESYSTEM_WINDOWS  1
+#define SDL_FSOPS_WINDOWS 1
+
 /* Enable the camera driver (src/camera/dummy/\*.c) */  /* !!! FIXME */
 #define SDL_CAMERA_DRIVER_DUMMY  1
 
diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h
index 8615036734984..4822fe6293b5c 100644
--- a/include/build_config/SDL_build_config_xbox.h
+++ b/include/build_config/SDL_build_config_xbox.h
@@ -228,6 +228,8 @@
 /* Enable filesystem support */
 /* #define SDL_FILESYSTEM_WINDOWS 1*/
 #define SDL_FILESYSTEM_XBOX 1
+#define SDL_FSOPS_WINDOWS 1
+
 
 /* Disable IME as not supported yet (TODO: Xbox IME?) */
 #define SDL_DISABLE_WINDOWS_IME 1
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 5ab6162200389..e233484a0a453 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -987,6 +987,12 @@ SDL3_0.0.0 {
     SDL_GetStorageFileSize;
     SDL_ReadStorageFile;
     SDL_GetStorageSpaceRemaining;
+    SDL_CreateDirectory;
+    SDL_EnumerateDirectory;
+    SDL_RemovePath;
+    SDL_RenamePath;
+    SDL_GetPathInfo;
+    SDL_FileTimeToWindows;
     # 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 9f55177bead48..f12c82ea581fc 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1012,3 +1012,9 @@
 #define SDL_GetStorageFileSize SDL_GetStorageFileSize_REAL
 #define SDL_ReadStorageFile SDL_ReadStorageFile_REAL
 #define SDL_GetStorageSpaceRemaining SDL_GetStorageSpaceRemaining_REAL
+#define SDL_CreateDirectory SDL_CreateDirectory_REAL
+#define SDL_EnumerateDirectory SDL_EnumerateDirectory_REAL
+#define SDL_RemovePath SDL_RemovePath_REAL
+#define SDL_RenamePath SDL_RenamePath_REAL
+#define SDL_GetPathInfo SDL_GetPathInfo_REAL
+#define SDL_FileTimeToWindows SDL_FileTimeToWindows_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 86263add1f9e8..fa2c50937a91b 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1037,3 +1037,9 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_StorageReady,(SDL_Stor

(Patch may be truncated, please check the link at the top of this post.)