Maelstrom: Enable XDG_DATA_HOME and XDG_DATA_DIRS for Linux distributions

From 1991199a9b9719a93bbf1d3bd4380ddb3fc5d167 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 26 Apr 2026 22:36:44 -0700
Subject: [PATCH] Enable XDG_DATA_HOME and XDG_DATA_DIRS for Linux
 distributions

---
 CMakeLists.txt |  6 ++++-
 utils/files.c  | 61 +++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 63 insertions(+), 4 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 251f398f..d7ce918b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -248,7 +248,11 @@ else()
         include(GNUInstallDirs)
         set(GAME_INSTALL_DATADIR "${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}")
 
-        target_compile_definitions(${TARGET_NAME} PRIVATE MAELSTROM_DATA=\"${CMAKE_INSTALL_PREFIX}/${GAME_INSTALL_DATADIR}/Data/\")
+        target_compile_definitions(${TARGET_NAME} PRIVATE
+            MAELSTROM_USE_XDG_DIRS
+            MAELSTROM_DATA=\"${CMAKE_INSTALL_PREFIX}/${GAME_INSTALL_DATADIR}/Data/\"
+            MAELSTROM_MODS=\"${CMAKE_INSTALL_PREFIX}/${GAME_INSTALL_DATADIR}/mods/\"
+        )
     endif()
 
     install(TARGETS ${TARGET_NAME} SDL3-shared LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" NAMELINK_SKIP RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
diff --git a/utils/files.c b/utils/files.c
index d53dbbe0..6ab77b50 100644
--- a/utils/files.c
+++ b/utils/files.c
@@ -22,7 +22,7 @@
 #include "files.h"
 
 #ifndef PATH_MAX
-#define PATH_MAX    256
+#define PATH_MAX	256
 #endif
 
 static const char *storage_org;
@@ -31,7 +31,50 @@ static char datapath[PATH_MAX];
 static char modpath[PATH_MAX];
 static char modfile[PATH_MAX];
 
-bool InitDataPath(void)
+#ifdef MAELSTROM_USE_XDG_DIRS
+
+static bool GetXDGDataPath(const char *directory, char *path, size_t pathlen)
+{
+	SDL_PathInfo info;
+	const char *env = SDL_getenv("XDG_DATA_HOME");
+	if (env && *env) {
+		SDL_snprintf(path, pathlen, "%s/maelstrom/%s", env, directory);
+		if (SDL_GetPathInfo(path, &info) && info.type == SDL_PATHTYPE_DIRECTORY) {
+			return true;
+		}
+	} else {
+		const char *home = SDL_getenv("HOME");
+		if (home && *home) {
+			SDL_snprintf(path, pathlen, "%s/.local/share/maelstrom/%s", home, directory);
+			if (SDL_GetPathInfo(path, &info) && info.type == SDL_PATHTYPE_DIRECTORY) {
+				return true;
+			}
+		}
+	}
+
+	bool result = false;
+	env = SDL_getenv("XDG_DATA_DIRS");
+	if (!env || !*env) {
+		env = "/usr/local/share/:/usr/share/";
+	}
+	char *paths = SDL_strdup(env);
+	if (paths) {
+		char *saveptr;
+		for (char *candidate = SDL_strtok_r(paths, ":", &saveptr); candidate; candidate = SDL_strtok_r(NULL, ":", &saveptr)) {
+			SDL_snprintf(path, pathlen, "%s/maelstrom/%s", candidate, directory);
+			if (SDL_GetPathInfo(path, &info) && info.type == SDL_PATHTYPE_DIRECTORY) {
+				result = true;
+				break;
+			}
+		}
+		SDL_free(paths);
+	}
+	return result;
+}
+
+#endif // MAELSTROM_USE_XDG_DIRS
+
+static bool InitDataPath(void)
 {
 	const char *env = SDL_getenv("MAELSTROM_DATA");
 
@@ -40,6 +83,12 @@ bool InitDataPath(void)
 		return true;
 	}
 
+#ifdef MAELSTROM_USE_XDG_DIRS
+	if (GetXDGDataPath("Data", datapath, sizeof(datapath))) {
+		return true;
+	}
+#endif
+
 #ifdef MAELSTROM_DATA
 	SDL_strlcpy(datapath, MAELSTROM_DATA, sizeof(datapath));
 	return true;
@@ -69,7 +118,7 @@ bool InitDataPath(void)
 #endif // MAELSTROM_DATA
 }
 
-bool InitModPath(void)
+static bool InitModPath(void)
 {
 	const char *env = SDL_getenv("MAELSTROM_MODS");
 
@@ -78,6 +127,12 @@ bool InitModPath(void)
 		return true;
 	}
 
+#ifdef MAELSTROM_USE_XDG_DIRS
+	if (GetXDGDataPath("mods", modpath, sizeof(modpath))) {
+		return true;
+	}
+#endif
+
 #ifdef MAELSTROM_MODS
 	SDL_strlcpy(modpath, MAELSTROM_MODS, sizeof(modpath));
 	return true;