Maelstrom: Added physfs for initial mod support

From c399a2014ec625fcb6cbf68b5885aa216583ba69 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 6 Apr 2026 21:27:55 -0700
Subject: [PATCH] Added physfs for initial mod support

---
 .gitmodules                               |   3 +
 CMakeLists.txt                            |  12 ++
 Xcode/Maelstrom.xcodeproj/project.pbxproj | 129 ++++++++++++++++++++++
 external/physfs                           |   1 +
 utils/files.c                             |  77 +++++--------
 5 files changed, 173 insertions(+), 49 deletions(-)
 create mode 160000 external/physfs

diff --git a/.gitmodules b/.gitmodules
index 7d807f45..653f985c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -8,3 +8,6 @@
 	path = external/SteamworksSDK
 	url = https://github.com/slouken/SteamworksSDK
 	branch = v163+
+[submodule "external/physfs"]
+	path = external/physfs
+	url = https://github.com/icculus/physfs
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 81c1bbfe..a9e6d5ef 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,6 +8,9 @@ project(Maelstrom
     LANGUAGES C CXX
     VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}"
 )
+if(APPLE)
+	enable_language(OBJC)
+endif()
 
 # set the output directory for built objects.
 # This makes sure that the dynamic library goes into the build directory automatically.
@@ -23,7 +26,13 @@ if(EMSCRIPTEN)
 endif()
 
 add_subdirectory(maclib)
+
+set(PHYSFS_BUILD_SHARED OFF)
+set(PHYSFS_BUILD_TEST OFF)
+add_subdirectory(external/physfs EXCLUDE_FROM_ALL)
+
 add_subdirectory(external/SDL EXCLUDE_FROM_ALL)
+
 # SDL_net's ABI has not yet been finalized
 set(original_BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS}")
 set(BUILD_SHARED_LIBS OFF)
@@ -141,6 +150,8 @@ set(MAELSTROM_SOURCES
 	utils/rapidxml.h
 	utils/rapidxml.hpp
 
+	external/physfs/extras/physfssdl3.c
+
 	miniz/miniz.c
 
 	Maelstrom.rc
@@ -201,6 +212,7 @@ if(STEAM)
 endif()
 
 target_link_libraries(${TARGET_NAME} PRIVATE SDLmac)
+target_link_libraries(${TARGET_NAME} PRIVATE physfs-static)
 target_link_libraries(${TARGET_NAME} PRIVATE SDL3_net::SDL3_net)
 target_link_libraries(${TARGET_NAME} PRIVATE SDL3::SDL3)
 
diff --git a/Xcode/Maelstrom.xcodeproj/project.pbxproj b/Xcode/Maelstrom.xcodeproj/project.pbxproj
index be439f74..3cd89247 100644
--- a/Xcode/Maelstrom.xcodeproj/project.pbxproj
+++ b/Xcode/Maelstrom.xcodeproj/project.pbxproj
@@ -61,6 +61,28 @@
 		AA929E852EDBA1900005200A /* Docs in Resources */ = {isa = PBXBuildFile; fileRef = AA929E842EDBA1900005200A /* Docs */; };
 		AA929E872EDBA3B80005200A /* Data in Resources */ = {isa = PBXBuildFile; fileRef = AA929E862EDBA3B80005200A /* Data */; };
 		F3281F432F66693200107D76 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F3281F422F66693200107D76 /* Assets.xcassets */; };
+		F3A2C6E32F84C2DD0080C346 /* physfs_archiver_qpak.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D32F84C2DD0080C346 /* physfs_archiver_qpak.c */; };
+		F3A2C6E42F84C2DD0080C346 /* physfs_archiver_csm.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6CB2F84C2DD0080C346 /* physfs_archiver_csm.c */; };
+		F3A2C6E52F84C2DD0080C346 /* physfs_archiver_zip.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D92F84C2DD0080C346 /* physfs_archiver_zip.c */; };
+		F3A2C6E62F84C2DD0080C346 /* physfs_archiver_vdf.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D72F84C2DD0080C346 /* physfs_archiver_vdf.c */; };
+		F3A2C6E72F84C2DD0080C346 /* physfs_archiver_7z.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6CA2F84C2DD0080C346 /* physfs_archiver_7z.c */; };
+		F3A2C6E82F84C2DD0080C346 /* physfs.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6C92F84C2DD0080C346 /* physfs.c */; };
+		F3A2C6E92F84C2DD0080C346 /* physfs_archiver_iso9660.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6CF2F84C2DD0080C346 /* physfs_archiver_iso9660.c */; };
+		F3A2C6EA2F84C2DD0080C346 /* physfs_archiver_unpacked.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D62F84C2DD0080C346 /* physfs_archiver_unpacked.c */; };
+		F3A2C6EB2F84C2DD0080C346 /* physfs_archiver_lec3d.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D02F84C2DD0080C346 /* physfs_archiver_lec3d.c */; };
+		F3A2C6EC2F84C2DD0080C346 /* physfs_archiver_pod.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D22F84C2DD0080C346 /* physfs_archiver_pod.c */; };
+		F3A2C6ED2F84C2DD0080C346 /* physfs_platform_posix.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6E02F84C2DD0080C346 /* physfs_platform_posix.c */; };
+		F3A2C6EE2F84C2DD0080C346 /* physfs_unicode.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6E22F84C2DD0080C346 /* physfs_unicode.c */; };
+		F3A2C6EF2F84C2DD0080C346 /* physfs_archiver_dir.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6CC2F84C2DD0080C346 /* physfs_archiver_dir.c */; };
+		F3A2C6F02F84C2DD0080C346 /* physfs_archiver_mvl.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D12F84C2DD0080C346 /* physfs_archiver_mvl.c */; };
+		F3A2C6F12F84C2DD0080C346 /* physfs_archiver_hog.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6CE2F84C2DD0080C346 /* physfs_archiver_hog.c */; };
+		F3A2C6F22F84C2DD0080C346 /* physfs_archiver_rofs.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D42F84C2DD0080C346 /* physfs_archiver_rofs.c */; };
+		F3A2C6F32F84C2DD0080C346 /* physfs_archiver_wad.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D82F84C2DD0080C346 /* physfs_archiver_wad.c */; };
+		F3A2C6F42F84C2DD0080C346 /* physfs_platform_apple.m in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6DF2F84C2DD0080C346 /* physfs_platform_apple.m */; };
+		F3A2C6F52F84C2DD0080C346 /* physfs_archiver_slb.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6D52F84C2DD0080C346 /* physfs_archiver_slb.c */; };
+		F3A2C6F62F84C2DD0080C346 /* physfs_byteorder.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6DA2F84C2DD0080C346 /* physfs_byteorder.c */; };
+		F3A2C6F72F84C2DD0080C346 /* physfs_archiver_grp.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6CD2F84C2DD0080C346 /* physfs_archiver_grp.c */; };
+		F3A2C6FB2F84C2FE0080C346 /* physfssdl3.c in Sources */ = {isa = PBXBuildFile; fileRef = F3A2C6FA2F84C2FE0080C346 /* physfssdl3.c */; };
 		F3CC41422F60E6A10033BDFA /* steam.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F3CC41412F60E6A10033BDFA /* steam.cpp */; };
 /* End PBXBuildFile section */
 
@@ -198,6 +220,35 @@
 		AA929E842EDBA1900005200A /* Docs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Docs; path = ../Docs; sourceTree = "<group>"; };
 		AA929E862EDBA3B80005200A /* Data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Data; path = ../Data; sourceTree = "<group>"; };
 		F3281F422F66693200107D76 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		F3A2C6C82F84C2DD0080C346 /* physfs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = physfs.h; sourceTree = "<group>"; };
+		F3A2C6C92F84C2DD0080C346 /* physfs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs.c; sourceTree = "<group>"; };
+		F3A2C6CA2F84C2DD0080C346 /* physfs_archiver_7z.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_7z.c; sourceTree = "<group>"; };
+		F3A2C6CB2F84C2DD0080C346 /* physfs_archiver_csm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_csm.c; sourceTree = "<group>"; };
+		F3A2C6CC2F84C2DD0080C346 /* physfs_archiver_dir.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_dir.c; sourceTree = "<group>"; };
+		F3A2C6CD2F84C2DD0080C346 /* physfs_archiver_grp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_grp.c; sourceTree = "<group>"; };
+		F3A2C6CE2F84C2DD0080C346 /* physfs_archiver_hog.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_hog.c; sourceTree = "<group>"; };
+		F3A2C6CF2F84C2DD0080C346 /* physfs_archiver_iso9660.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_iso9660.c; sourceTree = "<group>"; };
+		F3A2C6D02F84C2DD0080C346 /* physfs_archiver_lec3d.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_lec3d.c; sourceTree = "<group>"; };
+		F3A2C6D12F84C2DD0080C346 /* physfs_archiver_mvl.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_mvl.c; sourceTree = "<group>"; };
+		F3A2C6D22F84C2DD0080C346 /* physfs_archiver_pod.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_pod.c; sourceTree = "<group>"; };
+		F3A2C6D32F84C2DD0080C346 /* physfs_archiver_qpak.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_qpak.c; sourceTree = "<group>"; };
+		F3A2C6D42F84C2DD0080C346 /* physfs_archiver_rofs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_rofs.c; sourceTree = "<group>"; };
+		F3A2C6D52F84C2DD0080C346 /* physfs_archiver_slb.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_slb.c; sourceTree = "<group>"; };
+		F3A2C6D62F84C2DD0080C346 /* physfs_archiver_unpacked.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_unpacked.c; sourceTree = "<group>"; };
+		F3A2C6D72F84C2DD0080C346 /* physfs_archiver_vdf.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_vdf.c; sourceTree = "<group>"; };
+		F3A2C6D82F84C2DD0080C346 /* physfs_archiver_wad.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_wad.c; sourceTree = "<group>"; };
+		F3A2C6D92F84C2DD0080C346 /* physfs_archiver_zip.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_archiver_zip.c; sourceTree = "<group>"; };
+		F3A2C6DA2F84C2DD0080C346 /* physfs_byteorder.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_byteorder.c; sourceTree = "<group>"; };
+		F3A2C6DB2F84C2DD0080C346 /* physfs_casefolding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = physfs_casefolding.h; sourceTree = "<group>"; };
+		F3A2C6DC2F84C2DD0080C346 /* physfs_internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = physfs_internal.h; sourceTree = "<group>"; };
+		F3A2C6DD2F84C2DD0080C346 /* physfs_lzmasdk.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = physfs_lzmasdk.h; sourceTree = "<group>"; };
+		F3A2C6DE2F84C2DD0080C346 /* physfs_miniz.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = physfs_miniz.h; sourceTree = "<group>"; };
+		F3A2C6DF2F84C2DD0080C346 /* physfs_platform_apple.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = physfs_platform_apple.m; sourceTree = "<group>"; };
+		F3A2C6E02F84C2DD0080C346 /* physfs_platform_posix.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_platform_posix.c; sourceTree = "<group>"; };
+		F3A2C6E12F84C2DD0080C346 /* physfs_platforms.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = physfs_platforms.h; sourceTree = "<group>"; };
+		F3A2C6E22F84C2DD0080C346 /* physfs_unicode.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfs_unicode.c; sourceTree = "<group>"; };
+		F3A2C6F92F84C2FE0080C346 /* physfssdl3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = physfssdl3.h; sourceTree = "<group>"; };
+		F3A2C6FA2F84C2FE0080C346 /* physfssdl3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = physfssdl3.c; sourceTree = "<group>"; };
 		F3B28F792F76DADC007DABD6 /* Maelstrom-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Maelstrom-Info.plist"; sourceTree = "<group>"; };
 		F3CC41402F60E6A10033BDFA /* steam.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = steam.h; sourceTree = "<group>"; };
 		F3CC41412F60E6A10033BDFA /* steam.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = steam.cpp; sourceTree = "<group>"; };
@@ -227,6 +278,7 @@
 				AA928DB12EDB9C5F0005200A /* game */,
 				AA928E4A2EDB9D540005200A /* maclib */,
 				AA928E462EDB9D260005200A /* miniz */,
+				F3A2C6C62F84C24D0080C346 /* physfs */,
 				AA928E0C2EDB9CF90005200A /* screenlib */,
 				AA928DFB2EDB9CB40005200A /* utils */,
 				F3281F422F66693200107D76 /* Assets.xcassets */,
@@ -418,6 +470,59 @@
 			name = Products;
 			sourceTree = "<group>";
 		};
+		F3A2C6C62F84C24D0080C346 /* physfs */ = {
+			isa = PBXGroup;
+			children = (
+				F3A2C6F82F84C2E20080C346 /* extras */,
+				F3A2C6C72F84C2700080C346 /* src */,
+			);
+			name = physfs;
+			path = ../external/physfs;
+			sourceTree = "<group>";
+		};
+		F3A2C6C72F84C2700080C346 /* src */ = {
+			isa = PBXGroup;
+			children = (
+				F3A2C6C82F84C2DD0080C346 /* physfs.h */,
+				F3A2C6C92F84C2DD0080C346 /* physfs.c */,
+				F3A2C6CA2F84C2DD0080C346 /* physfs_archiver_7z.c */,
+				F3A2C6CB2F84C2DD0080C346 /* physfs_archiver_csm.c */,
+				F3A2C6CC2F84C2DD0080C346 /* physfs_archiver_dir.c */,
+				F3A2C6CD2F84C2DD0080C346 /* physfs_archiver_grp.c */,
+				F3A2C6CE2F84C2DD0080C346 /* physfs_archiver_hog.c */,
+				F3A2C6CF2F84C2DD0080C346 /* physfs_archiver_iso9660.c */,
+				F3A2C6D02F84C2DD0080C346 /* physfs_archiver_lec3d.c */,
+				F3A2C6D12F84C2DD0080C346 /* physfs_archiver_mvl.c */,
+				F3A2C6D22F84C2DD0080C346 /* physfs_archiver_pod.c */,
+				F3A2C6D32F84C2DD0080C346 /* physfs_archiver_qpak.c */,
+				F3A2C6D42F84C2DD0080C346 /* physfs_archiver_rofs.c */,
+				F3A2C6D52F84C2DD0080C346 /* physfs_archiver_slb.c */,
+				F3A2C6D62F84C2DD0080C346 /* physfs_archiver_unpacked.c */,
+				F3A2C6D72F84C2DD0080C346 /* physfs_archiver_vdf.c */,
+				F3A2C6D82F84C2DD0080C346 /* physfs_archiver_wad.c */,
+				F3A2C6D92F84C2DD0080C346 /* physfs_archiver_zip.c */,
+				F3A2C6DA2F84C2DD0080C346 /* physfs_byteorder.c */,
+				F3A2C6DB2F84C2DD0080C346 /* physfs_casefolding.h */,
+				F3A2C6DC2F84C2DD0080C346 /* physfs_internal.h */,
+				F3A2C6DD2F84C2DD0080C346 /* physfs_lzmasdk.h */,
+				F3A2C6DE2F84C2DD0080C346 /* physfs_miniz.h */,
+				F3A2C6DF2F84C2DD0080C346 /* physfs_platform_apple.m */,
+				F3A2C6E02F84C2DD0080C346 /* physfs_platform_posix.c */,
+				F3A2C6E12F84C2DD0080C346 /* physfs_platforms.h */,
+				F3A2C6E22F84C2DD0080C346 /* physfs_unicode.c */,
+			);
+			path = src;
+			sourceTree = "<group>";
+		};
+		F3A2C6F82F84C2E20080C346 /* extras */ = {
+			isa = PBXGroup;
+			children = (
+				F3A2C6F92F84C2FE0080C346 /* physfssdl3.h */,
+				F3A2C6FA2F84C2FE0080C346 /* physfssdl3.c */,
+			);
+			path = extras;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -532,6 +637,7 @@
 				AA928DE82EDB9CA60005200A /* rect.cpp in Sources */,
 				F3CC41422F60E6A10033BDFA /* steam.cpp in Sources */,
 				AA928DE92EDB9CA60005200A /* netplay.cpp in Sources */,
+				F3A2C6FB2F84C2FE0080C346 /* physfssdl3.c in Sources */,
 				AA928DEA2EDB9CA60005200A /* objects.cpp in Sources */,
 				AA928DEB2EDB9CA60005200A /* game.cpp in Sources */,
 				AA928DEC2EDB9CA60005200A /* lobby.cpp in Sources */,
@@ -543,6 +649,27 @@
 				AA928DF22EDB9CA60005200A /* object.cpp in Sources */,
 				AA928DF32EDB9CA60005200A /* myerror.cpp in Sources */,
 				AA928E082EDB9CEA0005200A /* files.c in Sources */,
+				F3A2C6E32F84C2DD0080C346 /* physfs_archiver_qpak.c in Sources */,
+				F3A2C6E42F84C2DD0080C346 /* physfs_archiver_csm.c in Sources */,
+				F3A2C6E52F84C2DD0080C346 /* physfs_archiver_zip.c in Sources */,
+				F3A2C6E62F84C2DD0080C346 /* physfs_archiver_vdf.c in Sources */,
+				F3A2C6E72F84C2DD0080C346 /* physfs_archiver_7z.c in Sources */,
+				F3A2C6E82F84C2DD0080C346 /* physfs.c in Sources */,
+				F3A2C6E92F84C2DD0080C346 /* physfs_archiver_iso9660.c in Sources */,
+				F3A2C6EA2F84C2DD0080C346 /* physfs_archiver_unpacked.c in Sources */,
+				F3A2C6EB2F84C2DD0080C346 /* physfs_archiver_lec3d.c in Sources */,
+				F3A2C6EC2F84C2DD0080C346 /* physfs_archiver_pod.c in Sources */,
+				F3A2C6ED2F84C2DD0080C346 /* physfs_platform_posix.c in Sources */,
+				F3A2C6EE2F84C2DD0080C346 /* physfs_unicode.c in Sources */,
+				F3A2C6EF2F84C2DD0080C346 /* physfs_archiver_dir.c in Sources */,
+				F3A2C6F02F84C2DD0080C346 /* physfs_archiver_mvl.c in Sources */,
+				F3A2C6F12F84C2DD0080C346 /* physfs_archiver_hog.c in Sources */,
+				F3A2C6F22F84C2DD0080C346 /* physfs_archiver_rofs.c in Sources */,
+				F3A2C6F32F84C2DD0080C346 /* physfs_archiver_wad.c in Sources */,
+				F3A2C6F42F84C2DD0080C346 /* physfs_platform_apple.m in Sources */,
+				F3A2C6F52F84C2DD0080C346 /* physfs_archiver_slb.c in Sources */,
+				F3A2C6F62F84C2DD0080C346 /* physfs_byteorder.c in Sources */,
+				F3A2C6F72F84C2DD0080C346 /* physfs_archiver_grp.c in Sources */,
 				AA928E092EDB9CEA0005200A /* hashtable.c in Sources */,
 				AA928E0A2EDB9CEA0005200A /* loadxml.cpp in Sources */,
 				AA928E0B2EDB9CEA0005200A /* prefs.cpp in Sources */,
@@ -736,6 +863,7 @@
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				ENABLE_USER_SELECTED_FILES = readonly;
 				GENERATE_INFOPLIST_FILE = YES;
+				HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../external/physfs/src";
 				INFOPLIST_FILE = "Maelstrom-Info.plist";
 				INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
 				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.arcade-games";
@@ -792,6 +920,7 @@
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				ENABLE_USER_SELECTED_FILES = readonly;
 				GENERATE_INFOPLIST_FILE = YES;
+				HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../external/physfs/src";
 				INFOPLIST_FILE = "Maelstrom-Info.plist";
 				INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
 				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.arcade-games";
diff --git a/external/physfs b/external/physfs
new file mode 160000
index 00000000..49cfd5fd
--- /dev/null
+++ b/external/physfs
@@ -0,0 +1 @@
+Subproject commit 49cfd5fdd46f38b4c2611556b44ee9b0d308e162
diff --git a/utils/files.c b/utils/files.c
index 3d470165..2317aef5 100644
--- a/utils/files.c
+++ b/utils/files.c
@@ -16,7 +16,8 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 
-#include <limits.h>	// For PATH_MAX
+#include "physfs.h"
+#include "../external/physfs/extras/physfssdl3.h"
 
 #include "files.h"
 
@@ -27,7 +28,6 @@
 static const char *storage_org;
 static const char *storage_app;
 static char datapath[PATH_MAX];
-static char override[PATH_MAX];
 
 bool InitDataPath(void)
 {
@@ -67,28 +67,10 @@ bool InitDataPath(void)
 #endif // MAELSTROM_DATA
 }
 
-void InitOverridePath(void)
-{
-	const char *env = SDL_getenv("MAELSTROM_MOD");
-
-	if (env) {
-		SDL_strlcpy(override, env, sizeof(override));
-		return;
-	}
-
-#ifdef MAELSTROM_MOD
-	SDL_strlcpy(override, MAELSTROM_MOD, sizeof(override));
-#else
-	SDL_snprintf(override, sizeof(override), "%s../mod/", datapath);
-#endif
-
-	if (!SDL_GetPathInfo(override, NULL)) {
-		override[0] = '\0';
-	}
-}
-
 bool InitFilesystem(const char *org, const char *app)
 {
+	const char *env;
+
 	storage_org = org;
 	storage_app = app;
 
@@ -101,46 +83,43 @@ bool InitFilesystem(const char *org, const char *app)
 		SDL_strlcat(datapath, "/", sizeof(datapath));
 	}
 
-	InitOverridePath();
+	if (!PHYSFS_init(NULL)) {
+		SDL_SetError("Couldn't initialize PhysicsFS: %d", PHYSFS_getLastErrorCode());
+		return false;
+	}
+
+	env = SDL_getenv("MAELSTROM_MOD");
+	if (env) {
+		// Ignore failure to load mods, we'll fall back to the original data
+		PHYSFS_mount(env, "/", true);
+	}
 
-	// Make sure the override ends in '/'
-	if (override[SDL_strlen(override) - 1] != '/') {
-		SDL_strlcat(override, "/", sizeof(override));
+	if (!PHYSFS_mount(datapath, "/", true)) {
+		SDL_SetError("Couldn't add %s to the mount path: %d", datapath, PHYSFS_getLastErrorCode());
+		return false;
 	}
 
 	return true;
 }
 
-SDL_IOStream *OpenRead(const char *file)
+void QuitFilesystem(void)
 {
-	SDL_IOStream *stream = NULL;
-	char path[PATH_MAX];
+	PHYSFS_deinit();
+}
 
-	if (*override) {
-		SDL_snprintf(path, sizeof(path), "%s%s", override, file);
-		stream = SDL_IOFromFile(path, "rb");
-	}
-	if (!stream) {
-		SDL_snprintf(path, sizeof(path), "%s%s", datapath, file);
-		stream = SDL_IOFromFile(path, "rb");
-	}
-	return stream;
+SDL_IOStream *OpenRead(const char *file)
+{
+	return PHYSFSSDL3_openRead(file);
 }
 
 char *LoadFile(const char *file)
 {
-	char *data = NULL;
-	char path[PATH_MAX];
-
-	if (*override) {
-		SDL_snprintf(path, sizeof(path), "%s%s", override, file);
-		data = (char *)SDL_LoadFile(path, NULL);
-	}
-	if (!data) {
-		SDL_snprintf(path, sizeof(path), "%s%s", datapath, file);
-		data = (char *)SDL_LoadFile(path, NULL);
+	SDL_IOStream *stream = OpenRead(file);
+	if (!stream) {
+		return NULL;
 	}
-	return data;
+
+	return (char *)SDL_LoadFile_IO(stream, NULL, true);
 }
 
 SDL_Storage *OpenUserStorage(void)