Browse Source

Add world-independent storage directory for mods (#12315)

Fixes #4821
rubenwardy 1 month ago
parent
commit
6c4a110679

+ 1 - 0
.gitignore

@@ -64,6 +64,7 @@ AppDir
 /clientmods/*
 !/clientmods/preview/
 /client/mod_storage/
+/mod_data
 
 ## Configuration/log files
 minetest.conf

+ 6 - 0
doc/lua_api.md

@@ -5306,6 +5306,12 @@ Utilities
 
 * `minetest.get_worldpath()`: returns e.g. `"/home/user/.minetest/world"`
     * Useful for storing custom data
+* `minetest.get_mod_data_path()`: returns e.g. `"/home/user/.minetest/mod_data/mymod"`
+    * Useful for storing custom data *independently of worlds*.
+    * Must be called during mod load time.
+    * Can read or write to this directory at any time.
+    * It's possible that multiple Minetest instances are running at the same
+      time, which may lead to corruption if you are not careful.
 * `minetest.is_singleplayer()`
 * `minetest.features`: Table containing API feature flags
 

+ 1 - 0
src/gamedef.h

@@ -75,6 +75,7 @@ public:
 	virtual const ModSpec* getModSpec(const std::string &modname) const = 0;
 	virtual const SubgameSpec* getGameSpec() const { return nullptr; }
 	virtual std::string getWorldPath() const { return ""; }
+	virtual std::string getModDataPath() const { return ""; }
 	virtual ModStorageDatabase *getModStorageDatabase() = 0;
 
 	virtual bool joinModChannel(const std::string &channel) = 0;

+ 7 - 0
src/script/cpp_api/s_security.cpp

@@ -600,6 +600,13 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
 		}
 	}
 
+	// Allow read/write access to all mod common dirs
+	str = fs::AbsolutePath(gamedef->getModDataPath());
+	if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
+		if (write_allowed) *write_allowed = true;
+		return true;
+	}
+
 	str = fs::AbsolutePath(gamedef->getWorldPath());
 	if (!str.empty()) {
 		// Don't allow access to other paths in the world mod/game path.

+ 20 - 0
src/script/lua_api/l_server.cpp

@@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "environment.h"
 #include "remoteplayer.h"
 #include "log.h"
+#include "filesys.h"
 #include <algorithm>
 
 // request_shutdown()
@@ -507,6 +508,24 @@ int ModApiServer::l_get_worldpath(lua_State *L)
 	return 1;
 }
 
+// get_mod_data_path()
+int ModApiServer::l_get_mod_data_path(lua_State *L)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	std::string modname = ScriptApiBase::getCurrentModNameInsecure(L);
+	if (modname.empty())
+		return 0;
+
+	const Server *srv = getServer(L);
+	std::string path = srv->getModDataPath() + DIR_DELIM + modname;
+	if (!fs::CreateAllDirs(path))
+		throw LuaError("Failed to create dir");
+
+	lua_pushstring(L, path.c_str());
+	return 1;
+}
+
 // sound_play(spec, parameters, [ephemeral])
 int ModApiServer::l_sound_play(lua_State *L)
 {
@@ -716,6 +735,7 @@ void ModApiServer::Initialize(lua_State *L, int top)
 	API_FCT(get_server_status);
 	API_FCT(get_server_uptime);
 	API_FCT(get_server_max_lag);
+	API_FCT(get_mod_data_path);
 	API_FCT(get_worldpath);
 	API_FCT(is_singleplayer);
 

+ 3 - 0
src/script/lua_api/l_server.h

@@ -39,6 +39,9 @@ private:
 	// get_worldpath()
 	static int l_get_worldpath(lua_State *L);
 
+	// get_mod_data_path()
+	static int l_get_mod_data_path(lua_State *L);
+
 	// is_singleplayer()
 	static int l_is_singleplayer(lua_State *L);
 

+ 4 - 0
src/server.cpp

@@ -317,6 +317,10 @@ Server::Server(
 			"Number of map edit events");
 
 	m_lag_gauge->set(g_settings->getFloat("dedicated_server_step"));
+
+	m_path_mod_data = porting::path_user + DIR_DELIM "mod_data";
+	if (!fs::CreateDir(m_path_mod_data))
+		throw ServerError("Failed to create mod data dir");
 }
 
 Server::~Server()

+ 2 - 0
src/server.h

@@ -306,6 +306,7 @@ public:
 	virtual const SubgameSpec* getGameSpec() const { return &m_gamespec; }
 	static std::string getBuiltinLuaPath();
 	virtual std::string getWorldPath() const { return m_path_world; }
+	virtual std::string getModDataPath() const { return m_path_mod_data; }
 
 	inline bool isSingleplayer() const
 			{ return m_simple_singleplayer_mode; }
@@ -609,6 +610,7 @@ private:
 	*/
 	// World directory
 	std::string m_path_world;
+	std::string m_path_mod_data;
 	// Subgame specification
 	SubgameSpec m_gamespec;
 	// If true, do not allow multiple players and hide some multiplayer