diff --git a/.editorconfig b/.editorconfig
index b0e4e46f18f..f53afb140e9 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -56,6 +56,9 @@ end_of_line = lf
[*.txt]
end_of_line = crlf
+[*.plist]
+end_of_line = lf
+
[AppRun]
end_of_line = lf
diff --git a/Packaging/apple/Info.plist b/Packaging/apple/Info.plist
index 91e32a9810c..52754cc115c 100644
--- a/Packaging/apple/Info.plist
+++ b/Packaging/apple/Info.plist
@@ -39,7 +39,7 @@
NSHumanReadableCopyright
${MACOSX_BUNDLE_COPYRIGHT}
SDL_FILESYSTEM_BASE_DIR_TYPE
- parent
+ resource
NSSupportsAutomaticGraphicsSwitching
UIApplicationSupportsIndirectInputEvents
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index d9bc5b8e666..78e02279cd2 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -184,6 +184,10 @@ endif()
if(USE_SDL1)
list(APPEND libdevilutionx_SRCS utils/sdl2_to_1_2_backports.cpp)
+ if(APPLE)
+ enable_language(OBJC)
+ list(APPEND libdevilutionx_SRCS platform/macos_sdl1/SDL_filesystem.m)
+ endif()
endif()
if(NOT DISABLE_DEMOMODE)
diff --git a/Source/init.cpp b/Source/init.cpp
index eb214c8f297..4e6fab1f5c1 100644
--- a/Source/init.cpp
+++ b/Source/init.cpp
@@ -128,7 +128,7 @@ std::vector GetMPQSearchPaths()
if (paths[0] == paths[1] || (paths.size() == 3 && (paths[0] == paths[2] || paths[1] == paths[2])))
paths.pop_back();
-#if defined(__unix__) && !defined(__ANDROID__)
+#if (defined(__unix__) || defined(__APPLE__)) && !defined(__ANDROID__)
// `XDG_DATA_HOME` is usually the root path of `paths::PrefPath()`, so we only
// add `XDG_DATA_DIRS`.
const char *xdgDataDirs = std::getenv("XDG_DATA_DIRS");
diff --git a/Source/platform/macos_sdl1/SDL_filesystem.m b/Source/platform/macos_sdl1/SDL_filesystem.m
new file mode 100644
index 00000000000..1bf4ed6e93d
--- /dev/null
+++ b/Source/platform/macos_sdl1/SDL_filesystem.m
@@ -0,0 +1,138 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ An altered version based on:
+ https://github.com/libsdl-org/SDL/blob/3c142abcb2b0b0ad7e08b096ea8d9a1a1e1af1ef/src/filesystem/cocoa/SDL_sysfilesystem.m
+
+ Modifications:
+ 1. Changes to compile with gcc (@autoreleasepool -> NSAutoreleasePool)
+ 2. Targets SDL-1.2 rather than SDL2 (SDL_InvalidParamError -> SDL_SetError)
+*/
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+/* System dependent filesystem routines */
+
+#include
+#include
+#include
+
+#include
+
+char *SDL_GetBasePath(void)
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSBundle *bundle = [NSBundle mainBundle];
+ const char *baseType = [[[bundle infoDictionary] objectForKey:@"SDL_FILESYSTEM_BASE_DIR_TYPE"] UTF8String];
+ const char *base = NULL;
+ char *retval = NULL;
+
+ if (baseType == NULL) {
+ baseType = "resource";
+ }
+ if (SDL_strcasecmp(baseType, "bundle") == 0) {
+ base = [[bundle bundlePath] fileSystemRepresentation];
+ } else if (SDL_strcasecmp(baseType, "parent") == 0) {
+ base = [[[bundle bundlePath] stringByDeletingLastPathComponent] fileSystemRepresentation];
+ } else {
+ /* this returns the exedir for non-bundled and the resourceDir for bundled apps */
+ base = [[bundle resourcePath] fileSystemRepresentation];
+ }
+
+ if (base) {
+ const size_t len = SDL_strlen(base) + 2;
+ retval = (char *)SDL_malloc(len);
+ if (retval == NULL) {
+ SDL_OutOfMemory();
+ } else {
+ SDL_snprintf(retval, len, "%s/", base);
+ }
+ }
+
+ [pool drain];
+ return retval;
+}
+
+char *SDL_GetPrefPath(const char *org, const char *app)
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ char *retval = NULL;
+ NSArray *array;
+
+ if (!app) {
+ SDL_SetError("SDL_GetPrefPath: app argument cannot be null");
+ return NULL;
+ }
+ if (!org) {
+ org = "";
+ }
+
+#if !TARGET_OS_TV
+ array = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
+#else
+ /* tvOS does not have persistent local storage!
+ * The only place on-device where we can store data is
+ * a cache directory that the OS can empty at any time.
+ *
+ * It's therefore very likely that save data will be erased
+ * between sessions. If you want your app's save data to
+ * actually stick around, you'll need to use iCloud storage.
+ */
+ {
+ static SDL_bool shown = SDL_FALSE;
+ if (!shown) {
+ shown = SDL_TRUE;
+ SDL_LogCritical(SDL_LOG_CATEGORY_SYSTEM, "tvOS does not have persistent local storage! Use iCloud storage if you want your data to persist between sessions.\n");
+ }
+ }
+
+ array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+#endif /* !TARGET_OS_TV */
+
+ if ([array count] > 0) { /* we only want the first item in the list. */
+ NSString *str = [array objectAtIndex:0];
+ const char *base = [str fileSystemRepresentation];
+ if (base) {
+ const size_t len = SDL_strlen(base) + SDL_strlen(org) + SDL_strlen(app) + 4;
+ retval = (char *)SDL_malloc(len);
+ if (retval == NULL) {
+ SDL_OutOfMemory();
+ } else {
+ char *ptr;
+ if (*org) {
+ SDL_snprintf(retval, len, "%s/%s/%s/", base, org, app);
+ } else {
+ SDL_snprintf(retval, len, "%s/%s/", base, app);
+ }
+ for (ptr = retval + 1; *ptr; ptr++) {
+ if (*ptr == '/') {
+ *ptr = '\0';
+ mkdir(retval, 0700);
+ *ptr = '/';
+ }
+ }
+ mkdir(retval, 0700);
+ }
+ }
+ }
+
+ [pool drain];
+ return retval;
+}
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/Source/utils/paths.cpp b/Source/utils/paths.cpp
index e80343635ab..9635ac3e382 100644
--- a/Source/utils/paths.cpp
+++ b/Source/utils/paths.cpp
@@ -1,5 +1,9 @@
#include "utils/paths.h"
+#include
+#include
+#include
+
#include
#include "appfat.h"
@@ -120,6 +124,23 @@ const std::string &AssetsPath()
assetsPath.emplace("D:\\assets\\");
#elif defined(__3DS__) || defined(__SWITCH__)
assetsPath.emplace("romfs:/");
+#elif defined(__APPLE__) && defined(USE_SDL1)
+ // In `Info.plist` we have
+ //
+ // SDL_FILESYSTEM_BASE_DIR_TYPE
+ // resource
+ //
+ // This means `SDL_GetBasePath()` returns exedir for non-bundled
+ // and the `app_dir.app/Resources/` for bundles.
+ //
+ // Our built-in resources are directly in the `devilutionx.app/Resources` directory
+ // and are normally looked up via a relative lookup in `FindAsset`.
+ // In SDL2, this is implemented by calling `SDL_OpenFPFromBundleOrFallback`
+ // from `SDL_RWFromFile` but SDL1 doesn't do it, so we set the directory explicitly.
+ //
+ // Note that SDL3 reverts to SDL1 behaviour!
+ // https://github.com/libsdl-org/SDL/blob/962268ca21ed10b9cee31198c22681099293f20a/docs/README-migration.md?plain=1#L1623
+ assetsPath.emplace(FromSDL(SDL_GetBasePath()));
#else
assetsPath.emplace(FromSDL(SDL_GetBasePath()) + ("assets" DIRECTORY_SEPARATOR_STR));
#endif
diff --git a/Source/utils/sdl2_to_1_2_backports.cpp b/Source/utils/sdl2_to_1_2_backports.cpp
index 7e4879de240..4d42a7e24d0 100644
--- a/Source/utils/sdl2_to_1_2_backports.cpp
+++ b/Source/utils/sdl2_to_1_2_backports.cpp
@@ -538,7 +538,7 @@ int WIN_SetError(const char *prefix)
} // namespace
-char *SDL_GetBasePath(void)
+extern "C" char *SDL_GetBasePath(void)
{
// From sdl2-2.0.9/src/filesystem/windows/SDL_sysfilesystem.c
@@ -605,7 +605,7 @@ char *SDL_GetBasePath(void)
return retval;
}
-char *SDL_GetPrefPath(const char *org, const char *app)
+extern "C" char *SDL_GetPrefPath(const char *org, const char *app)
{
// From sdl2-2.0.9/src/filesystem/windows/SDL_sysfilesystem.c
@@ -693,7 +693,8 @@ char *SDL_GetPrefPath(const char *org, const char *app)
return retval;
}
-#else
+// For Apple, definitions are in Source/platform/macos_sdl1/SDL_filesystem.m
+#elif !defined(__APPLE__)
namespace {
#if !defined(__QNXNTO__) && !defined(__amigaos__)
@@ -730,7 +731,7 @@ char *readSymLink(const char *path)
#endif
} // namespace
-char *SDL_GetBasePath()
+extern "C" char *SDL_GetBasePath()
{
// From sdl2-2.0.9/src/filesystem/unix/SDL_sysfilesystem.c
@@ -831,7 +832,7 @@ char *SDL_GetBasePath()
return retval;
}
-char *SDL_GetPrefPath(const char *org, const char *app)
+extern "C" char *SDL_GetPrefPath(const char *org, const char *app)
{
// From sdl2-2.0.9/src/filesystem/unix/SDL_sysfilesystem.c
/*
diff --git a/Source/utils/sdl2_to_1_2_backports.h b/Source/utils/sdl2_to_1_2_backports.h
index 93a5ac94960..9653b3ae8e1 100644
--- a/Source/utils/sdl2_to_1_2_backports.h
+++ b/Source/utils/sdl2_to_1_2_backports.h
@@ -319,5 +319,5 @@ int SDL_BlitScaled(SDL_Surface *src, SDL_Rect *srcrect,
Sint64 SDL_RWsize(SDL_RWops *context);
-char *SDL_GetBasePath();
-char *SDL_GetPrefPath(const char *org, const char *app);
+extern "C" char *SDL_GetBasePath();
+extern "C" char *SDL_GetPrefPath(const char *org, const char *app);