mirror of
https://github.com/luanti-org/luanti.git
synced 2025-10-10 19:32:10 +00:00
Merge 3ac8e4e974
into 499f2284bd
This commit is contained in:
commit
2fbcaf5bb3
50 changed files with 1545 additions and 94 deletions
|
@ -75,6 +75,7 @@ set(client_SRCS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/texturesource.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/imagesource.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mod_vfs.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <json/json.h>
|
||||
#include "client.h"
|
||||
#include "client/fontengine.h"
|
||||
#include "client/mod_vfs.h"
|
||||
#include "network/clientopcodes.h"
|
||||
#include "network/connection.h"
|
||||
#include "network/networkpacket.h"
|
||||
|
@ -54,6 +55,8 @@
|
|||
#include "content/mod_configuration.h"
|
||||
#include "mapnode.h"
|
||||
#include "item_visuals_manager.h"
|
||||
#include "script/sscsm/sscsm_controller.h"
|
||||
#include "script/sscsm/sscsm_events.h"
|
||||
|
||||
extern gui::IGUIEnvironment* guienv;
|
||||
|
||||
|
@ -136,6 +139,62 @@ Client::Client(
|
|||
|
||||
m_cache_save_interval = g_settings->getU16("server_map_save_interval");
|
||||
m_mesh_grid = { g_settings->getU16("client_mesh_chunk") };
|
||||
|
||||
m_sscsm_controller = SSCSMController::create();
|
||||
|
||||
{
|
||||
auto event1 = std::make_unique<SSCSMEventUpdateVFSFiles>();
|
||||
|
||||
ModVFS tmp_mod_vfs;
|
||||
// FIXME: only read files that are relevant to sscsm, and compute sha2 digests
|
||||
tmp_mod_vfs.scanModIntoMemory("*client_builtin*", getBuiltinLuaPath());
|
||||
|
||||
for (auto &p : tmp_mod_vfs.m_vfs) {
|
||||
event1->files.emplace_back(p.first, std::move(p.second));
|
||||
}
|
||||
|
||||
m_sscsm_controller->runEvent(this, std::move(event1));
|
||||
|
||||
// load client builtin immediately
|
||||
auto event2 = std::make_unique<SSCSMEventLoadMods>();
|
||||
event2->mods.emplace_back("*client_builtin*", "*client_builtin*:init.lua");
|
||||
m_sscsm_controller->runEvent(this, std::move(event2));
|
||||
}
|
||||
|
||||
{
|
||||
//FIXME: network packets
|
||||
//FIXME: check that *client_builtin* is not overridden
|
||||
|
||||
std::string enable_sscsm = g_settings->get("enable_sscsm");
|
||||
if (enable_sscsm == "singleplayer") { //FIXME: enum
|
||||
auto event1 = std::make_unique<SSCSMEventUpdateVFSFiles>();
|
||||
|
||||
// some simple test code
|
||||
event1->files.emplace_back("sscsm_test0:init.lua",
|
||||
R"=+=(
|
||||
print("sscsm_test0: loading")
|
||||
|
||||
--print(dump(_G))
|
||||
--print(debug.traceback())
|
||||
|
||||
do
|
||||
local pos = vector.zero()
|
||||
local function print_nodes()
|
||||
print(string.format("node at %s: %s", pos, dump(core.get_node_or_nil(pos))))
|
||||
pos = pos:offset(1, 0, 0)
|
||||
core.after(1, print_nodes)
|
||||
end
|
||||
core.after(0, print_nodes)
|
||||
end
|
||||
)=+=");
|
||||
|
||||
m_sscsm_controller->runEvent(this, std::move(event1));
|
||||
|
||||
auto event2 = std::make_unique<SSCSMEventLoadMods>();
|
||||
event2->mods.emplace_back("sscsm_test0", "sscsm_test0:init.lua");
|
||||
m_sscsm_controller->runEvent(this, std::move(event2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::migrateModStorage()
|
||||
|
@ -183,12 +242,14 @@ void Client::loadMods()
|
|||
return;
|
||||
}
|
||||
|
||||
m_mod_vfs = std::make_unique<ModVFS>();
|
||||
|
||||
m_script = new ClientScripting(this);
|
||||
m_env.setScript(m_script);
|
||||
m_script->setEnv(&m_env);
|
||||
|
||||
// Load builtin
|
||||
scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
|
||||
m_mod_vfs->scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
|
||||
m_script->loadModFromMemory(BUILTIN_MOD_NAME);
|
||||
m_script->checkSetByBuiltin();
|
||||
|
||||
|
@ -227,7 +288,7 @@ void Client::loadMods()
|
|||
// Load "mod" scripts
|
||||
for (const ModSpec &mod : m_mods) {
|
||||
mod.checkAndLog();
|
||||
scanModIntoMemory(mod.name, mod.path);
|
||||
m_mod_vfs->scanModIntoMemory(mod.name, mod.path);
|
||||
}
|
||||
|
||||
// Run them
|
||||
|
@ -249,35 +310,6 @@ void Client::loadMods()
|
|||
m_script->on_minimap_ready(m_minimap.get());
|
||||
}
|
||||
|
||||
void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
|
||||
std::string mod_subpath)
|
||||
{
|
||||
std::string full_path = mod_path + DIR_DELIM + mod_subpath;
|
||||
std::vector<fs::DirListNode> mod = fs::GetDirListing(full_path);
|
||||
for (const fs::DirListNode &j : mod) {
|
||||
if (j.name[0] == '.')
|
||||
continue;
|
||||
|
||||
if (j.dir) {
|
||||
scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM);
|
||||
continue;
|
||||
}
|
||||
std::replace(mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/');
|
||||
|
||||
std::string real_path = full_path + j.name;
|
||||
std::string vfs_path = mod_name + ":" + mod_subpath + j.name;
|
||||
infostream << "Client::scanModSubfolder(): Loading \"" << real_path
|
||||
<< "\" as \"" << vfs_path << "\"." << std::endl;
|
||||
|
||||
std::string contents;
|
||||
if (!fs::ReadFile(real_path, contents, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_mod_vfs.emplace(vfs_path, contents);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &Client::getBuiltinLuaPath()
|
||||
{
|
||||
static const std::string builtin_dir = porting::path_share + DIR_DELIM + "builtin";
|
||||
|
@ -523,6 +555,12 @@ void Client::step(float dtime)
|
|||
*/
|
||||
LocalPlayer *player = m_env.getLocalPlayer();
|
||||
|
||||
{
|
||||
auto event = std::make_unique<SSCSMEventOnStep>();
|
||||
event->dtime = dtime;
|
||||
m_sscsm_controller->runEvent(this, std::move(event));
|
||||
}
|
||||
|
||||
// Step environment (also handles player controls)
|
||||
m_env.step(dtime);
|
||||
m_sound->step(dtime);
|
||||
|
@ -2034,23 +2072,6 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache)
|
|||
return mesh;
|
||||
}
|
||||
|
||||
const std::string* Client::getModFile(std::string filename)
|
||||
{
|
||||
// strip dir delimiter from beginning of path
|
||||
auto pos = filename.find_first_of(':');
|
||||
if (pos == std::string::npos)
|
||||
return nullptr;
|
||||
pos++;
|
||||
auto pos2 = filename.find_first_not_of('/', pos);
|
||||
if (pos2 > pos)
|
||||
filename.erase(pos, pos2 - pos);
|
||||
|
||||
StringMap::const_iterator it = m_mod_vfs.find(filename);
|
||||
if (it == m_mod_vfs.end())
|
||||
return nullptr;
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mod channels
|
||||
*/
|
||||
|
|
|
@ -45,6 +45,8 @@ class NodeDefManager;
|
|||
class ParticleManager;
|
||||
class RenderingEngine;
|
||||
class SingleMediaDownloader;
|
||||
class ClientScripting;
|
||||
class SSCSMController;
|
||||
struct ChatMessage;
|
||||
struct ClientDynamicInfo;
|
||||
struct ClientEvent;
|
||||
|
@ -53,6 +55,7 @@ struct MapNode;
|
|||
struct PlayerControl;
|
||||
struct PointedThing;
|
||||
struct ItemVisualsManager;
|
||||
struct ModVFS;
|
||||
|
||||
namespace scene {
|
||||
class IAnimatedMesh;
|
||||
|
@ -100,8 +103,6 @@ private:
|
|||
std::map<u16, u32> m_packets;
|
||||
};
|
||||
|
||||
class ClientScripting;
|
||||
|
||||
class Client : public con::PeerHandler, public InventoryManager, public IGameDef
|
||||
{
|
||||
public:
|
||||
|
@ -127,14 +128,6 @@ public:
|
|||
~Client();
|
||||
DISABLE_CLASS_COPY(Client);
|
||||
|
||||
// Load local mods into memory
|
||||
void scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
|
||||
std::string mod_subpath);
|
||||
inline void scanModIntoMemory(const std::string &mod_name, const std::string &mod_path)
|
||||
{
|
||||
scanModSubfolder(mod_name, mod_path, "");
|
||||
}
|
||||
|
||||
/*
|
||||
request all threads managed by client to be stopped
|
||||
*/
|
||||
|
@ -385,7 +378,7 @@ public:
|
|||
bool checkLocalPrivilege(const std::string &priv)
|
||||
{ return checkPrivilege(priv); }
|
||||
virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false);
|
||||
const std::string* getModFile(std::string filename);
|
||||
ModVFS *getModVFS() { return m_mod_vfs.get(); }
|
||||
ModStorageDatabase *getModStorageDatabase() override { return m_mod_storage_database; }
|
||||
|
||||
ItemVisualsManager *getItemVisualsManager() { return m_item_visuals_manager; }
|
||||
|
@ -586,7 +579,10 @@ private:
|
|||
ModStorageDatabase *m_mod_storage_database = nullptr;
|
||||
float m_mod_storage_save_timer = 10.0f;
|
||||
std::vector<ModSpec> m_mods;
|
||||
StringMap m_mod_vfs;
|
||||
std::unique_ptr<ModVFS> m_mod_vfs;
|
||||
|
||||
// SSCSM
|
||||
std::unique_ptr<SSCSMController> m_sscsm_controller;
|
||||
|
||||
bool m_shutdown = false;
|
||||
|
||||
|
|
56
src/client/mod_vfs.cpp
Normal file
56
src/client/mod_vfs.cpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
// SPDX-FileCopyrightText: 2025 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "mod_vfs.h"
|
||||
#include "filesys.h"
|
||||
#include "log.h"
|
||||
#include <algorithm>
|
||||
|
||||
void ModVFS::scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
|
||||
std::string mod_subpath)
|
||||
{
|
||||
std::string full_path = mod_path + DIR_DELIM + mod_subpath;
|
||||
std::vector<fs::DirListNode> mod = fs::GetDirListing(full_path);
|
||||
for (const fs::DirListNode &j : mod) {
|
||||
if (j.name[0] == '.')
|
||||
continue;
|
||||
|
||||
if (j.dir) {
|
||||
scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM);
|
||||
continue;
|
||||
}
|
||||
std::replace(mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/');
|
||||
|
||||
std::string real_path = full_path + j.name;
|
||||
std::string vfs_path = mod_name + ":" + mod_subpath + j.name;
|
||||
infostream << "ModVFS::scanModSubfolder(): Loading \"" << real_path
|
||||
<< "\" as \"" << vfs_path << "\"." << std::endl;
|
||||
|
||||
std::string contents;
|
||||
if (!fs::ReadFile(real_path, contents)) {
|
||||
errorstream << "ModVFS::scanModSubfolder(): Can't read file \""
|
||||
<< real_path << "\"." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_vfs.emplace(vfs_path, contents);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string *ModVFS::getModFile(std::string filename)
|
||||
{
|
||||
// strip dir delimiter from beginning of path
|
||||
auto pos = filename.find_first_of(':');
|
||||
if (pos == std::string::npos)
|
||||
return nullptr;
|
||||
++pos;
|
||||
auto pos2 = filename.find_first_not_of('/', pos);
|
||||
if (pos2 > pos)
|
||||
filename.erase(pos, pos2 - pos);
|
||||
|
||||
auto it = m_vfs.find(filename);
|
||||
if (it == m_vfs.end())
|
||||
return nullptr;
|
||||
return &it->second;
|
||||
}
|
23
src/client/mod_vfs.h
Normal file
23
src/client/mod_vfs.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
// SPDX-FileCopyrightText: 2025 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
struct ModVFS
|
||||
{
|
||||
void scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
|
||||
std::string mod_subpath);
|
||||
|
||||
inline void scanModIntoMemory(const std::string &mod_name, const std::string &mod_path)
|
||||
{
|
||||
scanModSubfolder(mod_name, mod_path, "");
|
||||
}
|
||||
|
||||
const std::string *getModFile(std::string filename);
|
||||
|
||||
std::unordered_map<std::string, std::string> m_vfs;
|
||||
};
|
|
@ -105,3 +105,7 @@
|
|||
// The intent is to ensure that the rendering doesn't turn terribly blurry
|
||||
// when filtering is enabled.
|
||||
#define TEXTURE_FILTER_MIN_SIZE 192U
|
||||
|
||||
// Resolution of clocks that SSCSM has access to, in us.
|
||||
// Used as countermeasure against side-channel attacks.
|
||||
#define SSCSM_CLOCK_RESOLUTION_US 20
|
||||
|
|
|
@ -121,6 +121,7 @@ void set_default_settings()
|
|||
settings->setDefault("curl_verify_cert", "true");
|
||||
settings->setDefault("enable_remote_media_server", "true");
|
||||
settings->setDefault("enable_client_modding", "false");
|
||||
settings->setDefault("enable_sscsm", "nowhere");
|
||||
settings->setDefault("max_out_chat_queue_size", "20");
|
||||
settings->setDefault("pause_on_lost_focus", "false");
|
||||
settings->setDefault("enable_split_login_register", "true");
|
||||
|
|
|
@ -87,6 +87,11 @@ public:
|
|||
ModError(const std::string &s): BaseException(s) {}
|
||||
};
|
||||
|
||||
class MisbehavedSSCSMException : public BaseException {
|
||||
public:
|
||||
MisbehavedSSCSMException(const std::string &s): BaseException(s) {}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Some "old-style" interrupts:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
add_subdirectory(common)
|
||||
add_subdirectory(cpp_api)
|
||||
add_subdirectory(lua_api)
|
||||
add_subdirectory(sscsm)
|
||||
|
||||
# Used by server and client
|
||||
file(GLOB common_SCRIPT_HDRS "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
|
||||
|
@ -20,9 +21,11 @@ set(client_SCRIPT_SRCS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/scripting_mainmenu.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/scripting_client.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/scripting_pause_menu.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/scripting_sscsm.cpp
|
||||
|
||||
${client_SCRIPT_COMMON_SRCS}
|
||||
${client_SCRIPT_CPP_API_SRCS}
|
||||
${client_SCRIPT_LUA_API_SRCS}
|
||||
${client_SCRIPT_SSCSM_SRCS}
|
||||
PARENT_SCOPE)
|
||||
|
||||
|
|
|
@ -22,5 +22,6 @@ set(client_SCRIPT_CPP_API_SRCS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/s_client_common.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/s_mainmenu.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/s_pause_menu.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/s_sscsm.cpp
|
||||
PARENT_SCOPE)
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include "server.h"
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
#include "client/client.h"
|
||||
#include "client/mod_vfs.h"
|
||||
#include "sscsm/sscsm_environment.h"
|
||||
#endif
|
||||
|
||||
#if BUILD_WITH_TRACY
|
||||
|
@ -74,7 +76,7 @@ ScriptApiBase::ScriptApiBase(ScriptingType type):
|
|||
|
||||
lua_atpanic(m_luastack, &luaPanic);
|
||||
|
||||
if (m_type == ScriptingType::Client)
|
||||
if (m_type == ScriptingType::Client || m_type == ScriptingType::SSCSM)
|
||||
clientOpenLibs(m_luastack);
|
||||
else
|
||||
luaL_openlibs(m_luastack);
|
||||
|
@ -143,7 +145,8 @@ ScriptApiBase::ScriptApiBase(ScriptingType type):
|
|||
// Finally, put the table into the global environment:
|
||||
lua_setglobal(m_luastack, "core");
|
||||
|
||||
if (m_type == ScriptingType::Client)
|
||||
if (m_type == ScriptingType::Client
|
||||
|| m_type == ScriptingType::SSCSM)
|
||||
lua_pushstring(m_luastack, "/");
|
||||
else
|
||||
lua_pushstring(m_luastack, DIR_DELIM);
|
||||
|
@ -210,7 +213,8 @@ void ScriptApiBase::checkSetByBuiltin()
|
|||
if (getType() == ScriptingType::Server ||
|
||||
(getType() == ScriptingType::Async && m_gamedef) ||
|
||||
getType() == ScriptingType::Emerge ||
|
||||
getType() == ScriptingType::Client) {
|
||||
getType() == ScriptingType::Client ||
|
||||
getType() == ScriptingType::SSCSM) {
|
||||
CHECK(CUSTOM_RIDX_READ_NODE, "read_node");
|
||||
CHECK(CUSTOM_RIDX_PUSH_NODE, "push_node");
|
||||
}
|
||||
|
@ -265,16 +269,18 @@ void ScriptApiBase::loadScript(const std::string &script_path)
|
|||
}
|
||||
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
void ScriptApiBase::loadModFromMemory(const std::string &mod_name)
|
||||
void ScriptApiBase::loadModFromMemory(const std::string &mod_name, std::string init_path)
|
||||
{
|
||||
ModNameStorer mod_name_storer(getStack(), mod_name);
|
||||
|
||||
sanity_check(m_type == ScriptingType::Client);
|
||||
sanity_check(m_type == ScriptingType::Client
|
||||
|| m_type == ScriptingType::SSCSM);
|
||||
|
||||
const std::string init_filename = mod_name + ":init.lua";
|
||||
const std::string chunk_name = "@" + init_filename;
|
||||
if (init_path.empty())
|
||||
init_path = mod_name + ":init.lua";
|
||||
const std::string chunk_name = "@" + init_path;
|
||||
|
||||
const std::string *contents = getClient()->getModFile(init_filename);
|
||||
const std::string *contents = getModVFS()->getModFile(init_path);
|
||||
if (!contents)
|
||||
throw ModError("Mod \"" + mod_name + "\" lacks init.lua");
|
||||
|
||||
|
@ -540,8 +546,18 @@ Server* ScriptApiBase::getServer()
|
|||
}
|
||||
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
Client* ScriptApiBase::getClient()
|
||||
Client *ScriptApiBase::getClient()
|
||||
{
|
||||
return dynamic_cast<Client *>(m_gamedef);
|
||||
}
|
||||
|
||||
ModVFS *ScriptApiBase::getModVFS()
|
||||
{
|
||||
if (m_type == ScriptingType::Client)
|
||||
return getClient()->getModVFS();
|
||||
else if (m_type == ScriptingType::SSCSM)
|
||||
return getSSCSMEnv()->getModVFS();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -43,11 +43,12 @@ extern "C" {
|
|||
|
||||
enum class ScriptingType: u8 {
|
||||
Async, // either mainmenu (client) or ingame (server)
|
||||
Client,
|
||||
Client, // CPCSM
|
||||
MainMenu,
|
||||
Server,
|
||||
Emerge,
|
||||
PauseMenu,
|
||||
SSCSM,
|
||||
};
|
||||
|
||||
class Server;
|
||||
|
@ -58,8 +59,10 @@ class EmergeThread;
|
|||
class IGameDef;
|
||||
class Environment;
|
||||
class GUIEngine;
|
||||
class SSCSMEnvironment;
|
||||
class ServerActiveObject;
|
||||
struct PlayerHPChangeReason;
|
||||
struct ModVFS;
|
||||
|
||||
class ScriptApiBase : protected LuaHelper {
|
||||
public:
|
||||
|
@ -77,7 +80,7 @@ public:
|
|||
void loadScript(const std::string &script_path);
|
||||
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
void loadModFromMemory(const std::string &mod_name);
|
||||
void loadModFromMemory(const std::string &mod_name, std::string init_path = "");
|
||||
#endif
|
||||
|
||||
void runCallbacksRaw(int nargs,
|
||||
|
@ -90,9 +93,10 @@ public:
|
|||
ScriptingType getType() { return m_type; }
|
||||
|
||||
IGameDef *getGameDef() { return m_gamedef; }
|
||||
Server* getServer();
|
||||
Server *getServer();
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
Client* getClient();
|
||||
Client *getClient();
|
||||
ModVFS *getModVFS();
|
||||
#endif
|
||||
|
||||
// IMPORTANT: These cannot be used for any security-related uses, they exist
|
||||
|
@ -158,6 +162,9 @@ protected:
|
|||
#if CHECK_CLIENT_BUILD()
|
||||
GUIEngine* getGuiEngine() { return m_guiengine; }
|
||||
void setGuiEngine(GUIEngine* guiengine) { m_guiengine = guiengine; }
|
||||
|
||||
SSCSMEnvironment *getSSCSMEnv() { return m_sscsm_environment; }
|
||||
void setSSCSMEnv(SSCSMEnvironment *env) { m_sscsm_environment = env; }
|
||||
#endif
|
||||
|
||||
EmergeThread* getEmergeThread() { return m_emerge; }
|
||||
|
@ -178,14 +185,15 @@ protected:
|
|||
private:
|
||||
static int luaPanic(lua_State *L);
|
||||
|
||||
lua_State *m_luastack = nullptr;
|
||||
lua_State *m_luastack = nullptr;
|
||||
|
||||
IGameDef *m_gamedef = nullptr;
|
||||
Environment *m_environment = nullptr;
|
||||
IGameDef *m_gamedef = nullptr;
|
||||
Environment *m_environment = nullptr;
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
GUIEngine *m_guiengine = nullptr;
|
||||
GUIEngine *m_guiengine = nullptr;
|
||||
SSCSMEnvironment *m_sscsm_environment = nullptr;
|
||||
#endif
|
||||
EmergeThread *m_emerge = nullptr;
|
||||
EmergeThread *m_emerge = nullptr;
|
||||
|
||||
ScriptingType m_type;
|
||||
ScriptingType m_type;
|
||||
};
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
#include "server.h"
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
#include "client/client.h"
|
||||
#include "client/mod_vfs.h"
|
||||
#endif
|
||||
#include "settings.h"
|
||||
#include "constants.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <string>
|
||||
|
@ -378,6 +380,140 @@ void ScriptApiSecurity::initializeSecurityClient()
|
|||
setLuaEnv(L, thread);
|
||||
}
|
||||
|
||||
void ScriptApiSecurity::initializeSecuritySSCSM()
|
||||
{
|
||||
static const char *whitelist[] = {
|
||||
"assert",
|
||||
"core",
|
||||
"collectgarbage",
|
||||
"DIR_DELIM",
|
||||
"error",
|
||||
"getfenv",
|
||||
"ipairs",
|
||||
"next",
|
||||
"pairs",
|
||||
"pcall",
|
||||
"rawequal",
|
||||
"rawget",
|
||||
"rawset",
|
||||
"select",
|
||||
"setfenv",
|
||||
"getmetatable",
|
||||
"setmetatable",
|
||||
"tonumber",
|
||||
"tostring",
|
||||
"type",
|
||||
"unpack",
|
||||
"_VERSION",
|
||||
"xpcall",
|
||||
// Completely safe libraries
|
||||
"coroutine",
|
||||
"table",
|
||||
"math",
|
||||
"bit",
|
||||
};
|
||||
static const char *os_whitelist[] = {
|
||||
"difftime",
|
||||
"time"
|
||||
};
|
||||
static const char *debug_whitelist[] = {
|
||||
"getinfo", // used by client builtin and unset before mods load
|
||||
"traceback"
|
||||
};
|
||||
static const char *string_whitelist[] = { // all but string.dump
|
||||
"byte",
|
||||
"char",
|
||||
"dump",
|
||||
"find",
|
||||
"format",
|
||||
"gmatch",
|
||||
"gsub",
|
||||
"len",
|
||||
"lower",
|
||||
"match",
|
||||
"rep",
|
||||
"reverse",
|
||||
"sub",
|
||||
"upper"
|
||||
};
|
||||
#if USE_LUAJIT
|
||||
static const char *jit_whitelist[] = {
|
||||
"arch",
|
||||
"flush",
|
||||
"off",
|
||||
"on",
|
||||
"opt",
|
||||
"os",
|
||||
"status",
|
||||
"version",
|
||||
"version_num",
|
||||
};
|
||||
#endif
|
||||
|
||||
m_secure = true;
|
||||
|
||||
lua_State *L = getStack();
|
||||
int thread = getThread(L);
|
||||
|
||||
// create an empty environment
|
||||
createEmptyEnv(L);
|
||||
|
||||
// Copy safe base functions
|
||||
lua_getglobal(L, "_G");
|
||||
lua_getfield(L, -2, "_G");
|
||||
copy_safe(L, whitelist, sizeof(whitelist));
|
||||
|
||||
// And replace unsafe ones
|
||||
SECURE_API(g, dofile);
|
||||
SECURE_API(g, load);
|
||||
SECURE_API(g, loadfile);
|
||||
SECURE_API(g, loadstring);
|
||||
SECURE_API(g, require);
|
||||
lua_pop(L, 2);
|
||||
|
||||
|
||||
|
||||
// Copy safe OS functions
|
||||
lua_getglobal(L, "os");
|
||||
lua_newtable(L);
|
||||
copy_safe(L, os_whitelist, sizeof(os_whitelist));
|
||||
|
||||
// And replace unsafe ones
|
||||
SECURE_API(os, clock);
|
||||
|
||||
lua_setfield(L, -3, "os");
|
||||
lua_pop(L, 1); // Pop old OS
|
||||
|
||||
|
||||
// Copy safe debug functions
|
||||
lua_getglobal(L, "debug");
|
||||
lua_newtable(L);
|
||||
copy_safe(L, debug_whitelist, sizeof(debug_whitelist));
|
||||
lua_setfield(L, -3, "debug");
|
||||
lua_pop(L, 1); // Pop old debug
|
||||
|
||||
|
||||
// Copy safe string functions
|
||||
lua_getglobal(L, "string");
|
||||
lua_newtable(L);
|
||||
copy_safe(L, string_whitelist, sizeof(string_whitelist));
|
||||
lua_setfield(L, -3, "string");
|
||||
lua_pop(L, 1); // Pop old string
|
||||
|
||||
|
||||
#if USE_LUAJIT
|
||||
// Copy safe jit functions, if they exist
|
||||
lua_getglobal(L, "jit");
|
||||
lua_newtable(L);
|
||||
copy_safe(L, jit_whitelist, sizeof(jit_whitelist));
|
||||
lua_setfield(L, -3, "jit");
|
||||
lua_pop(L, 1); // Pop old jit
|
||||
#endif
|
||||
|
||||
// Set the environment to the one we created earlier
|
||||
setLuaEnv(L, thread);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int ScriptApiSecurity::getThread(lua_State *L)
|
||||
|
@ -775,10 +911,11 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L)
|
|||
#if CHECK_CLIENT_BUILD()
|
||||
ScriptApiBase *script = ModApiBase::getScriptApiBase(L);
|
||||
|
||||
// Client implementation
|
||||
if (script->getType() == ScriptingType::Client) {
|
||||
// SSCSM & CPCSM implementation
|
||||
if (script->getType() == ScriptingType::Client
|
||||
|| script->getType() == ScriptingType::SSCSM) {
|
||||
std::string path = readParam<std::string>(L, 1);
|
||||
const std::string *contents = script->getClient()->getModFile(path);
|
||||
const std::string *contents = script->getModVFS()->getModFile(path);
|
||||
if (!contents) {
|
||||
std::string error_msg = "Couldn't find script called: " + path;
|
||||
lua_pushnil(L);
|
||||
|
@ -963,3 +1100,12 @@ int ScriptApiSecurity::sl_os_setlocale(lua_State *L)
|
|||
lua_call(L, cat ? 2 : 1, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int ScriptApiSecurity::sl_os_clock(lua_State *L)
|
||||
{
|
||||
auto t = clock();
|
||||
t = t - t % (SSCSM_CLOCK_RESOLUTION_US * CLOCKS_PER_SEC / 1'000'000);
|
||||
lua_pushnumber(L, static_cast<lua_Number>(t) / static_cast<lua_Number>(CLOCKS_PER_SEC));
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -31,8 +31,10 @@ public:
|
|||
void initializeSecurity();
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
void initializeSecurityClient();
|
||||
void initializeSecuritySSCSM();
|
||||
#else
|
||||
inline void initializeSecurityClient() { assert(0); }
|
||||
void initializeSecurityClient() { assert(0); }
|
||||
void initializeSecuritySSCSM() { assert(0); }
|
||||
#endif
|
||||
|
||||
// Checks if the Lua state has been secured
|
||||
|
@ -115,4 +117,5 @@ private:
|
|||
static int sl_os_rename(lua_State *L);
|
||||
static int sl_os_remove(lua_State *L);
|
||||
static int sl_os_setlocale(lua_State *L);
|
||||
static int sl_os_clock(lua_State *L);
|
||||
};
|
||||
|
|
29
src/script/cpp_api/s_sscsm.cpp
Normal file
29
src/script/cpp_api/s_sscsm.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
// SPDX-FileCopyrightText: 2025 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "s_sscsm.h"
|
||||
|
||||
#include "s_internal.h"
|
||||
#include "script/sscsm/sscsm_environment.h"
|
||||
|
||||
void ScriptApiSSCSM::load_mods(const std::vector<std::pair<std::string, std::string>> &mods)
|
||||
{
|
||||
infostream << "Loading SSCSMs:" << std::endl;
|
||||
for (const auto &m : mods) {
|
||||
infostream << "Loading SSCSM " << m.first << std::endl;
|
||||
loadModFromMemory(m.first, m.second);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptApiSSCSM::environment_step(float dtime)
|
||||
{
|
||||
SCRIPTAPI_PRECHECKHEADER
|
||||
|
||||
// Get core.registered_globalsteps
|
||||
lua_getglobal(L, "core");
|
||||
lua_getfield(L, -1, "registered_globalsteps");
|
||||
// Call callbacks
|
||||
lua_pushnumber(L, dtime);
|
||||
runCallbacks(1, RUN_CALLBACKS_MODE_FIRST);
|
||||
}
|
15
src/script/cpp_api/s_sscsm.h
Normal file
15
src/script/cpp_api/s_sscsm.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
// SPDX-FileCopyrightText: 2025 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp_api/s_base.h"
|
||||
|
||||
class ScriptApiSSCSM : virtual public ScriptApiBase
|
||||
{
|
||||
public:
|
||||
void load_mods(const std::vector<std::pair<std::string, std::string>> &mods);
|
||||
|
||||
void environment_step(float dtime);
|
||||
};
|
|
@ -43,4 +43,5 @@ set(client_SCRIPT_LUA_API_SRCS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/l_particles_local.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_pause_menu.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/l_sscsm.cpp
|
||||
PARENT_SCOPE)
|
||||
|
|
|
@ -58,6 +58,11 @@ GUIEngine *ModApiBase::getGuiEngine(lua_State *L)
|
|||
{
|
||||
return getScriptApiBase(L)->getGuiEngine();
|
||||
}
|
||||
|
||||
SSCSMEnvironment *ModApiBase::getSSCSMEnv(lua_State *L)
|
||||
{
|
||||
return getScriptApiBase(L)->getSSCSMEnv();
|
||||
}
|
||||
#endif
|
||||
|
||||
EmergeThread *ModApiBase::getEmergeThread(lua_State *L)
|
||||
|
|
|
@ -23,6 +23,7 @@ class EmergeThread;
|
|||
class ScriptApiBase;
|
||||
class Server;
|
||||
class Environment;
|
||||
class SSCSMEnvironment;
|
||||
class ServerInventoryManager;
|
||||
|
||||
class ModApiBase : protected LuaHelper {
|
||||
|
@ -33,6 +34,7 @@ public:
|
|||
#if CHECK_CLIENT_BUILD()
|
||||
static Client* getClient(lua_State *L);
|
||||
static GUIEngine* getGuiEngine(lua_State *L);
|
||||
static SSCSMEnvironment *getSSCSMEnv(lua_State *L);
|
||||
#endif // !SERVER
|
||||
static EmergeThread* getEmergeThread(lua_State *L);
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ int ModApiClient::l_get_current_modname(lua_State *L)
|
|||
int ModApiClient::l_get_modpath(lua_State *L)
|
||||
{
|
||||
std::string modname = readParam<std::string>(L, 1);
|
||||
// Client mods use a virtual filesystem, see Client::scanModSubfolder()
|
||||
// Client mods use a virtual filesystem, see ModVFS::scanModSubfolder()
|
||||
std::string path = modname + ":";
|
||||
lua_pushstring(L, path.c_str());
|
||||
return 1;
|
||||
|
@ -284,7 +284,20 @@ int ModApiClient::l_get_privilege_list(lua_State *L)
|
|||
// get_builtin_path()
|
||||
int ModApiClient::l_get_builtin_path(lua_State *L)
|
||||
{
|
||||
lua_pushstring(L, BUILTIN_MOD_NAME ":");
|
||||
std::string modname;
|
||||
if (getScriptApiBase(L)->getType() == ScriptingType::Client) {
|
||||
modname = BUILTIN_MOD_NAME;
|
||||
} else if (getScriptApiBase(L)->getType() == ScriptingType::SSCSM) {
|
||||
// get_builtin_path() is only called in builtin, so this is fine
|
||||
modname = ScriptApiBase::getCurrentModNameInsecure(L);
|
||||
if (modname != "*client_builtin*" && modname != "*server_builtin*")
|
||||
modname = "";
|
||||
}
|
||||
|
||||
if (modname.empty())
|
||||
return 0;
|
||||
|
||||
lua_pushstring(L, (modname + ":").c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -322,3 +335,11 @@ void ModApiClient::Initialize(lua_State *L, int top)
|
|||
API_FCT(get_language);
|
||||
API_FCT(get_csm_restrictions);
|
||||
}
|
||||
|
||||
void ModApiClient::InitializeSSCSM(lua_State *L, int top)
|
||||
{
|
||||
API_FCT(get_current_modname);
|
||||
API_FCT(get_modpath);
|
||||
API_FCT(print);
|
||||
API_FCT(get_builtin_path);
|
||||
}
|
||||
|
|
|
@ -71,4 +71,5 @@ private:
|
|||
|
||||
public:
|
||||
static void Initialize(lua_State *L, int top);
|
||||
static void InitializeSSCSM(lua_State *L, int top);
|
||||
};
|
||||
|
|
37
src/script/lua_api/l_sscsm.cpp
Normal file
37
src/script/lua_api/l_sscsm.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: 2025 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "l_sscsm.h"
|
||||
|
||||
#include "common/c_content.h"
|
||||
#include "common/c_converter.h"
|
||||
#include "l_internal.h"
|
||||
#include "script/sscsm/sscsm_environment.h"
|
||||
#include "script/sscsm/sscsm_requests.h"
|
||||
|
||||
// get_node_or_nil(pos)
|
||||
// pos = {x=num, y=num, z=num}
|
||||
int ModApiSSCSM::l_get_node_or_nil(lua_State *L)
|
||||
{
|
||||
// pos
|
||||
v3s16 pos = read_v3s16(L, 1);
|
||||
|
||||
// Do it
|
||||
auto request = SSCSMRequestGetNode{};
|
||||
request.pos = pos;
|
||||
auto answer = getSSCSMEnv(L)->doRequest(std::move(request));
|
||||
|
||||
if (answer.is_pos_ok) {
|
||||
// Return node
|
||||
pushnode(L, answer.node);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ModApiSSCSM::Initialize(lua_State *L, int top)
|
||||
{
|
||||
API_FCT(get_node_or_nil);
|
||||
}
|
17
src/script/lua_api/l_sscsm.h
Normal file
17
src/script/lua_api/l_sscsm.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// SPDX-FileCopyrightText: 2025 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lua_api/l_base.h"
|
||||
|
||||
class ModApiSSCSM : public ModApiBase
|
||||
{
|
||||
private:
|
||||
// get_node_or_nil(pos)
|
||||
static int l_get_node_or_nil(lua_State *L);
|
||||
|
||||
public:
|
||||
static void Initialize(lua_State *L, int top);
|
||||
};
|
|
@ -30,6 +30,7 @@
|
|||
#include "util/png.h"
|
||||
#include "player.h"
|
||||
#include "daynightratio.h"
|
||||
#include "constants.h"
|
||||
#include <cstdio>
|
||||
|
||||
// only available in zstd 1.3.5+
|
||||
|
@ -82,6 +83,16 @@ int ModApiUtil::l_get_us_time(lua_State *L)
|
|||
return 1;
|
||||
}
|
||||
|
||||
// get_us_time() for SSCSM
|
||||
int ModApiUtil::l_get_us_time_sscsm(lua_State *L)
|
||||
{
|
||||
NO_MAP_LOCK_REQUIRED;
|
||||
auto t = porting::getTimeUs();
|
||||
t = t - t % SSCSM_CLOCK_RESOLUTION_US;
|
||||
lua_pushnumber(L, t);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Maximum depth of a JSON object:
|
||||
// Reading and writing should not overflow the Lua, C, or jsoncpp stacks.
|
||||
constexpr static u16 MAX_JSON_DEPTH = 1024;
|
||||
|
@ -798,6 +809,37 @@ void ModApiUtil::InitializeClient(lua_State *L, int top)
|
|||
lua_setfield(L, top, "settings");
|
||||
}
|
||||
|
||||
void ModApiUtil::InitializeSSCSM(lua_State *L, int top)
|
||||
{
|
||||
API_FCT(log);
|
||||
|
||||
registerFunction(L, "get_us_time", l_get_us_time_sscsm, top);
|
||||
|
||||
API_FCT(parse_json);
|
||||
API_FCT(write_json);
|
||||
|
||||
API_FCT(is_yes);
|
||||
|
||||
API_FCT(compress);
|
||||
API_FCT(decompress);
|
||||
|
||||
API_FCT(encode_base64);
|
||||
API_FCT(decode_base64);
|
||||
|
||||
API_FCT(get_version);
|
||||
API_FCT(sha1);
|
||||
API_FCT(sha256);
|
||||
API_FCT(colorspec_to_colorstring);
|
||||
API_FCT(colorspec_to_bytes);
|
||||
API_FCT(colorspec_to_table);
|
||||
API_FCT(time_to_day_night_ratio);
|
||||
|
||||
API_FCT(get_last_run_mod);
|
||||
API_FCT(set_last_run_mod);
|
||||
|
||||
API_FCT(urlencode);
|
||||
}
|
||||
|
||||
void ModApiUtil::InitializeAsync(lua_State *L, int top)
|
||||
{
|
||||
API_FCT(log);
|
||||
|
|
|
@ -28,6 +28,9 @@ private:
|
|||
// get us precision time
|
||||
static int l_get_us_time(lua_State *L);
|
||||
|
||||
// get_us_time() for SSCSM. less precise
|
||||
static int l_get_us_time_sscsm(lua_State *L);
|
||||
|
||||
// parse_json(str[, nullvalue])
|
||||
static int l_parse_json(lua_State *L);
|
||||
|
||||
|
@ -134,4 +137,5 @@ public:
|
|||
static void Initialize(lua_State *L, int top);
|
||||
static void InitializeAsync(lua_State *L, int top);
|
||||
static void InitializeClient(lua_State *L, int top);
|
||||
static void InitializeSSCSM(lua_State *L, int top);
|
||||
};
|
||||
|
|
37
src/script/scripting_sscsm.cpp
Normal file
37
src/script/scripting_sscsm.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: 2025 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "scripting_sscsm.h"
|
||||
#include "cpp_api/s_internal.h"
|
||||
#include "lua_api/l_sscsm.h"
|
||||
#include "lua_api/l_util.h"
|
||||
#include "lua_api/l_client.h"
|
||||
|
||||
SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) :
|
||||
ScriptApiBase(ScriptingType::SSCSM)
|
||||
{
|
||||
setSSCSMEnv(env);
|
||||
|
||||
SCRIPTAPI_PRECHECKHEADER
|
||||
|
||||
initializeSecuritySSCSM();
|
||||
|
||||
lua_getglobal(L, "core");
|
||||
int top = lua_gettop(L);
|
||||
|
||||
// Initialize our lua_api modules
|
||||
initializeModApi(L, top);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Push builtin initialization type
|
||||
lua_pushstring(L, "sscsm");
|
||||
lua_setglobal(L, "INIT");
|
||||
}
|
||||
|
||||
void SSCSMScripting::initializeModApi(lua_State *L, int top)
|
||||
{
|
||||
ModApiUtil::InitializeSSCSM(L, top);
|
||||
ModApiClient::InitializeSSCSM(L, top);
|
||||
ModApiSSCSM::Initialize(L, top);
|
||||
}
|
25
src/script/scripting_sscsm.h
Normal file
25
src/script/scripting_sscsm.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2025 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cpp_api/s_base.h"
|
||||
#include "cpp_api/s_sscsm.h"
|
||||
#include "cpp_api/s_security.h"
|
||||
|
||||
class SSCSMScripting :
|
||||
virtual public ScriptApiBase,
|
||||
public ScriptApiSSCSM,
|
||||
public ScriptApiSecurity
|
||||
{
|
||||
public:
|
||||
SSCSMScripting(SSCSMEnvironment *env);
|
||||
|
||||
protected:
|
||||
bool checkPathInternal(const std::string &abs_path, bool write_required,
|
||||
bool *write_allowed) { return false; };
|
||||
|
||||
private:
|
||||
void initializeModApi(lua_State *L, int top);
|
||||
};
|
7
src/script/sscsm/CMakeLists.txt
Normal file
7
src/script/sscsm/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
file(GLOB client_SCRIPT_SSCSM_HDRS "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
|
||||
|
||||
set(client_SCRIPT_SSCSM_SRCS
|
||||
${client_SCRIPT_SSCSM_HDRS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/sscsm_controller.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/sscsm_environment.cpp
|
||||
PARENT_SCOPE)
|
62
src/script/sscsm/sscsm_controller.cpp
Normal file
62
src/script/sscsm/sscsm_controller.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
// SPDX-FileCopyrightText: 2024 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "sscsm_controller.h"
|
||||
#include "sscsm_environment.h"
|
||||
#include "sscsm_requests.h"
|
||||
#include "sscsm_events.h"
|
||||
#include "sscsm_stupid_channel.h"
|
||||
|
||||
std::unique_ptr<SSCSMController> SSCSMController::create()
|
||||
{
|
||||
auto channel = std::make_shared<StupidChannel>();
|
||||
auto thread = std::make_unique<SSCSMEnvironment>(channel);
|
||||
thread->start();
|
||||
|
||||
// Wait for thread to finish initializing.
|
||||
auto req0 = deserializeSSCSMRequest(channel->recvB());
|
||||
FATAL_ERROR_IF(!dynamic_cast<SSCSMRequestPollNextEvent *>(req0.get()),
|
||||
"First request must be pollEvent.");
|
||||
|
||||
return std::make_unique<SSCSMController>(std::move(thread), channel);
|
||||
}
|
||||
|
||||
SSCSMController::SSCSMController(std::unique_ptr<SSCSMEnvironment> thread,
|
||||
std::shared_ptr<StupidChannel> channel) :
|
||||
m_thread(std::move(thread)), m_channel(std::move(channel))
|
||||
{
|
||||
}
|
||||
|
||||
SSCSMController::~SSCSMController()
|
||||
{
|
||||
// send tear-down
|
||||
auto answer = SSCSMRequestPollNextEvent::Answer{};
|
||||
answer.next_event = std::make_unique<SSCSMEventTearDown>();
|
||||
m_channel->sendB(serializeSSCSMAnswer(std::move(answer)));
|
||||
// wait for death
|
||||
m_thread->stop();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
SerializedSSCSMAnswer SSCSMController::handleRequest(Client *client, ISSCSMRequest *req)
|
||||
{
|
||||
return req->exec(client);
|
||||
}
|
||||
|
||||
void SSCSMController::runEvent(Client *client, std::unique_ptr<ISSCSMEvent> event)
|
||||
{
|
||||
auto answer0 = SSCSMRequestPollNextEvent::Answer{};
|
||||
answer0.next_event = std::move(event);
|
||||
auto answer = serializeSSCSMAnswer(std::move(answer0));
|
||||
|
||||
while (true) {
|
||||
auto request = deserializeSSCSMRequest(m_channel->exchangeB(std::move(answer)));
|
||||
|
||||
if (dynamic_cast<SSCSMRequestPollNextEvent *>(request.get()) != nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
answer = handleRequest(client, request.get());
|
||||
}
|
||||
}
|
43
src/script/sscsm/sscsm_controller.h
Normal file
43
src/script/sscsm/sscsm_controller.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
// SPDX-FileCopyrightText: 2024 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "irrlichttypes.h"
|
||||
#include "sscsm_irequest.h"
|
||||
#include "sscsm_ievent.h"
|
||||
#include "util/basic_macros.h"
|
||||
|
||||
class SSCSMEnvironment;
|
||||
class StupidChannel;
|
||||
|
||||
/**
|
||||
* The purpose of this class is to:
|
||||
* * Be the RAII owner of the SSCSM process.
|
||||
* * Send events to SSCSM process, and process requests. (`runEvent`)
|
||||
* * Hide details (e.g. that it is a separate process, or that it has to do IPC calls).
|
||||
*
|
||||
* See also SSCSMEnvironment for other side.
|
||||
*/
|
||||
class SSCSMController
|
||||
{
|
||||
std::unique_ptr<SSCSMEnvironment> m_thread;
|
||||
std::shared_ptr<StupidChannel> m_channel;
|
||||
|
||||
SerializedSSCSMAnswer handleRequest(Client *client, ISSCSMRequest *req);
|
||||
|
||||
public:
|
||||
static std::unique_ptr<SSCSMController> create();
|
||||
|
||||
SSCSMController(std::unique_ptr<SSCSMEnvironment> thread,
|
||||
std::shared_ptr<StupidChannel> channel);
|
||||
|
||||
~SSCSMController();
|
||||
|
||||
DISABLE_CLASS_COPY(SSCSMController);
|
||||
|
||||
// Handles requests until the next event is polled
|
||||
void runEvent(Client *client, std::unique_ptr<ISSCSMEvent> event);
|
||||
};
|
73
src/script/sscsm/sscsm_environment.cpp
Normal file
73
src/script/sscsm/sscsm_environment.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
// SPDX-FileCopyrightText: 2024 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "sscsm_environment.h"
|
||||
#include "sscsm_requests.h"
|
||||
#include "sscsm_events.h"
|
||||
#include "sscsm_stupid_channel.h"
|
||||
#include "client/mod_vfs.h"
|
||||
|
||||
|
||||
SSCSMEnvironment::SSCSMEnvironment(std::shared_ptr<StupidChannel> channel) :
|
||||
Thread("SSCSMEnvironment-thread"),
|
||||
m_channel(std::move(channel)),
|
||||
m_script(std::make_unique<SSCSMScripting>(this)),
|
||||
m_vfs(std::make_unique<ModVFS>())
|
||||
{
|
||||
}
|
||||
|
||||
SSCSMEnvironment::~SSCSMEnvironment() = default;
|
||||
|
||||
void *SSCSMEnvironment::run()
|
||||
{
|
||||
while (true) {
|
||||
auto next_event = [&]{
|
||||
auto request = SSCSMRequestPollNextEvent{};
|
||||
auto answer = doRequest(std::move(request));
|
||||
return std::move(answer.next_event);
|
||||
}();
|
||||
|
||||
if (dynamic_cast<SSCSMEventTearDown *>(next_event.get())) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
next_event->exec(this);
|
||||
} catch (LuaError &e) {
|
||||
setFatalError(std::string("Lua error: ") + e.what());
|
||||
} catch (ModError &e) {
|
||||
setFatalError(std::string("Mod error: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SerializedSSCSMAnswer SSCSMEnvironment::exchange(SerializedSSCSMRequest req)
|
||||
{
|
||||
return m_channel->exchangeA(std::move(req));
|
||||
}
|
||||
|
||||
void SSCSMEnvironment::updateVFSFiles(std::vector<std::pair<std::string, std::string>> &&files)
|
||||
{
|
||||
for (auto &&p : files) {
|
||||
m_vfs->m_vfs.emplace(std::move(p.first), std::move(p.second));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string_view> SSCSMEnvironment::readVFSFile(const std::string &path)
|
||||
{
|
||||
auto it = m_vfs->m_vfs.find(path);
|
||||
if (it == m_vfs->m_vfs.end())
|
||||
return std::nullopt;
|
||||
else
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void SSCSMEnvironment::setFatalError(const std::string &reason)
|
||||
{
|
||||
auto request = SSCSMRequestSetFatalError{};
|
||||
request.reason = reason;
|
||||
doRequest(std::move(request));
|
||||
}
|
59
src/script/sscsm/sscsm_environment.h
Normal file
59
src/script/sscsm/sscsm_environment.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
// SPDX-FileCopyrightText: 2024 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "client/client.h"
|
||||
#include "threading/thread.h"
|
||||
#include "sscsm_controller.h"
|
||||
#include "sscsm_irequest.h"
|
||||
#include "../scripting_sscsm.h"
|
||||
|
||||
/** The thread that runs SSCSM code.
|
||||
*
|
||||
* Meant to be replaced by a sandboxed process.
|
||||
*
|
||||
* RAII-owns and abstracts away resources to communicate to the main process / thread.
|
||||
*
|
||||
* See also SSCSMController for other side.
|
||||
*/
|
||||
class SSCSMEnvironment : public Thread
|
||||
{
|
||||
std::shared_ptr<StupidChannel> m_channel;
|
||||
std::unique_ptr<SSCSMScripting> m_script;
|
||||
// the virtual file system.
|
||||
// paths look like this:
|
||||
// *client_builtin*:subdir/foo.lua
|
||||
// *server_builtin*:subdir/foo.lua
|
||||
// modname:subdir/foo.lua
|
||||
std::unique_ptr<ModVFS> m_vfs;
|
||||
|
||||
void *run() override;
|
||||
|
||||
SerializedSSCSMAnswer exchange(SerializedSSCSMRequest req);
|
||||
|
||||
public:
|
||||
SSCSMEnvironment(std::shared_ptr<StupidChannel> channel);
|
||||
~SSCSMEnvironment() override;
|
||||
|
||||
SSCSMScripting *getScript() { return m_script.get(); }
|
||||
|
||||
ModVFS *getModVFS() { return m_vfs.get(); }
|
||||
void updateVFSFiles(std::vector<std::pair<std::string, std::string>> &&files);
|
||||
std::optional<std::string_view> readVFSFile(const std::string &path);
|
||||
|
||||
void setFatalError(const std::string &reason);
|
||||
|
||||
template <typename RQ>
|
||||
typename RQ::Answer doRequest(RQ &&rq)
|
||||
{
|
||||
return deserializeSSCSMAnswer<typename RQ::Answer>(
|
||||
exchange(serializeSSCSMRequest(std::forward<RQ>(rq)))
|
||||
);
|
||||
}
|
||||
};
|
51
src/script/sscsm/sscsm_events.h
Normal file
51
src/script/sscsm/sscsm_events.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
// SPDX-FileCopyrightText: 2024 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sscsm_ievent.h"
|
||||
#include "debug.h"
|
||||
#include "irrlichttypes.h"
|
||||
#include "sscsm_environment.h"
|
||||
|
||||
struct SSCSMEventTearDown : public ISSCSMEvent
|
||||
{
|
||||
void exec(SSCSMEnvironment *env) override
|
||||
{
|
||||
FATAL_ERROR("SSCSMEventTearDown needs to be handled by SSCSMEnvironment::run()");
|
||||
}
|
||||
};
|
||||
|
||||
struct SSCSMEventUpdateVFSFiles : public ISSCSMEvent
|
||||
{
|
||||
// pairs are virtual path and file content
|
||||
std::vector<std::pair<std::string, std::string>> files;
|
||||
|
||||
void exec(SSCSMEnvironment *env) override
|
||||
{
|
||||
env->updateVFSFiles(std::move(files));
|
||||
}
|
||||
};
|
||||
|
||||
struct SSCSMEventLoadMods : public ISSCSMEvent
|
||||
{
|
||||
// modnames and paths to init.lua file, in load order
|
||||
std::vector<std::pair<std::string, std::string>> mods;
|
||||
|
||||
void exec(SSCSMEnvironment *env) override
|
||||
{
|
||||
env->getScript()->load_mods(mods);
|
||||
}
|
||||
};
|
||||
|
||||
struct SSCSMEventOnStep : public ISSCSMEvent
|
||||
{
|
||||
f32 dtime;
|
||||
|
||||
void exec(SSCSMEnvironment *env) override
|
||||
{
|
||||
env->getScript()->environment_step(dtime);
|
||||
}
|
||||
};
|
||||
|
39
src/script/sscsm/sscsm_ievent.h
Normal file
39
src/script/sscsm/sscsm_ievent.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
// SPDX-FileCopyrightText: 2024 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
class SSCSMEnvironment;
|
||||
|
||||
// Event triggered from the main env for the SSCSM env.
|
||||
struct ISSCSMEvent
|
||||
{
|
||||
virtual ~ISSCSMEvent() = default;
|
||||
|
||||
// Note: No return value (difference to ISSCSMRequest). These are not callbacks
|
||||
// that you can run at arbitrary locations, because the untrusted code could
|
||||
// then clobber your local variables.
|
||||
virtual void exec(SSCSMEnvironment *cntrl) = 0;
|
||||
};
|
||||
|
||||
// FIXME: actually serialize, and replace this with a string
|
||||
using SerializedSSCSMEvent = std::unique_ptr<ISSCSMEvent>;
|
||||
|
||||
template <typename T>
|
||||
inline SerializedSSCSMEvent serializeSSCSMEvent(const T &event)
|
||||
{
|
||||
static_assert(std::is_base_of_v<ISSCSMEvent, T>);
|
||||
|
||||
return std::make_unique<T>(event);
|
||||
}
|
||||
|
||||
inline std::unique_ptr<ISSCSMEvent> deserializeSSCSMEvent(SerializedSSCSMEvent event_serialized)
|
||||
{
|
||||
// The actual deserialization will have to use a type tag, and then choose
|
||||
// the appropriate deserializer.
|
||||
return event_serialized;
|
||||
}
|
67
src/script/sscsm/sscsm_irequest.h
Normal file
67
src/script/sscsm/sscsm_irequest.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
// SPDX-FileCopyrightText: 2024 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "exceptions.h"
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
class SSCSMController;
|
||||
class Client;
|
||||
|
||||
struct ISSCSMAnswer
|
||||
{
|
||||
virtual ~ISSCSMAnswer() = default;
|
||||
};
|
||||
|
||||
// FIXME: actually serialize, and replace this with a string
|
||||
using SerializedSSCSMAnswer = std::unique_ptr<ISSCSMAnswer>;
|
||||
|
||||
// Request made by the sscsm env to the main env.
|
||||
struct ISSCSMRequest
|
||||
{
|
||||
virtual ~ISSCSMRequest() = default;
|
||||
|
||||
virtual SerializedSSCSMAnswer exec(Client *client) = 0;
|
||||
};
|
||||
|
||||
// FIXME: actually serialize, and replace this with a string
|
||||
using SerializedSSCSMRequest = std::unique_ptr<ISSCSMRequest>;
|
||||
|
||||
template <typename T>
|
||||
inline SerializedSSCSMRequest serializeSSCSMRequest(const T &request)
|
||||
{
|
||||
static_assert(std::is_base_of_v<ISSCSMRequest, T>);
|
||||
|
||||
return std::make_unique<T>(request);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T deserializeSSCSMAnswer(SerializedSSCSMAnswer answer_serialized)
|
||||
{
|
||||
static_assert(std::is_base_of_v<ISSCSMAnswer, T>);
|
||||
|
||||
// dynamic cast in place of actual deserialization
|
||||
auto ptr = dynamic_cast<T *>(answer_serialized.get());
|
||||
if (!ptr) {
|
||||
throw SerializationError("deserializeSSCSMAnswer failed");
|
||||
}
|
||||
return std::move(*ptr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline SerializedSSCSMAnswer serializeSSCSMAnswer(T &&answer)
|
||||
{
|
||||
static_assert(std::is_base_of_v<ISSCSMAnswer, T>);
|
||||
|
||||
return std::make_unique<T>(std::move(answer));
|
||||
}
|
||||
|
||||
inline std::unique_ptr<ISSCSMRequest> deserializeSSCSMRequest(SerializedSSCSMRequest request_serialized)
|
||||
{
|
||||
// The actual deserialization will have to use a type tag, and then choose
|
||||
// the appropriate deserializer.
|
||||
return request_serialized;
|
||||
}
|
107
src/script/sscsm/sscsm_requests.h
Normal file
107
src/script/sscsm/sscsm_requests.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
// SPDX-FileCopyrightText: 2024 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sscsm_irequest.h"
|
||||
#include "sscsm_ievent.h"
|
||||
#include "mapnode.h"
|
||||
#include "map.h"
|
||||
#include "client/client.h"
|
||||
#include "log_internal.h"
|
||||
|
||||
// Poll the next event (e.g. on_globalstep)
|
||||
struct SSCSMRequestPollNextEvent : public ISSCSMRequest
|
||||
{
|
||||
struct Answer : public ISSCSMAnswer
|
||||
{
|
||||
std::unique_ptr<ISSCSMEvent> next_event;
|
||||
};
|
||||
|
||||
SerializedSSCSMAnswer exec(Client *client) override
|
||||
{
|
||||
FATAL_ERROR("SSCSMRequestPollNextEvent needs to be handled by SSCSMControler::runEvent()");
|
||||
}
|
||||
};
|
||||
|
||||
// Some error occured in the SSCSM env
|
||||
struct SSCSMRequestSetFatalError : public ISSCSMRequest
|
||||
{
|
||||
struct Answer : public ISSCSMAnswer
|
||||
{
|
||||
};
|
||||
|
||||
std::string reason;
|
||||
|
||||
SerializedSSCSMAnswer exec(Client *client) override
|
||||
{
|
||||
client->setFatalError("[SSCSM] " + reason);
|
||||
|
||||
return serializeSSCSMAnswer(Answer{});
|
||||
}
|
||||
};
|
||||
|
||||
// print(text)
|
||||
// FIXME: override global loggers to use this in sscsm process
|
||||
struct SSCSMRequestPrint : public ISSCSMRequest
|
||||
{
|
||||
struct Answer : public ISSCSMAnswer
|
||||
{
|
||||
};
|
||||
|
||||
std::string text;
|
||||
|
||||
SerializedSSCSMAnswer exec(Client *client) override
|
||||
{
|
||||
rawstream << text << std::endl;
|
||||
|
||||
return serializeSSCSMAnswer(Answer{});
|
||||
}
|
||||
};
|
||||
|
||||
// core.log(level, text)
|
||||
// FIXME: override global loggers to use this in sscsm process
|
||||
struct SSCSMRequestLog : public ISSCSMRequest
|
||||
{
|
||||
struct Answer : public ISSCSMAnswer
|
||||
{
|
||||
};
|
||||
|
||||
std::string text;
|
||||
LogLevel level;
|
||||
|
||||
SerializedSSCSMAnswer exec(Client *client) override
|
||||
{
|
||||
if (level >= LL_MAX) {
|
||||
throw MisbehavedSSCSMException("Tried to log at non-existent level.");
|
||||
} else {
|
||||
g_logger.log(level, text);
|
||||
}
|
||||
|
||||
return serializeSSCSMAnswer(Answer{});
|
||||
}
|
||||
};
|
||||
|
||||
// core.get_node(pos)
|
||||
struct SSCSMRequestGetNode : public ISSCSMRequest
|
||||
{
|
||||
struct Answer : public ISSCSMAnswer
|
||||
{
|
||||
MapNode node;
|
||||
bool is_pos_ok;
|
||||
};
|
||||
|
||||
v3s16 pos;
|
||||
|
||||
SerializedSSCSMAnswer exec(Client *client) override
|
||||
{
|
||||
bool is_pos_ok = false;
|
||||
MapNode node = client->getEnv().getMap().getNode(pos, &is_pos_ok);
|
||||
|
||||
Answer answer{};
|
||||
answer.node = node;
|
||||
answer.is_pos_ok = is_pos_ok;
|
||||
return serializeSSCSMAnswer(std::move(answer));
|
||||
}
|
||||
};
|
84
src/script/sscsm/sscsm_stupid_channel.h
Normal file
84
src/script/sscsm/sscsm_stupid_channel.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
// SPDX-FileCopyrightText: 2024 Luanti authors
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include "sscsm_irequest.h"
|
||||
|
||||
// FIXME: replace this with an ipc channel
|
||||
class StupidChannel
|
||||
{
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_condvar;
|
||||
SerializedSSCSMRequest m_request;
|
||||
SerializedSSCSMAnswer m_answer;
|
||||
|
||||
public:
|
||||
void sendA(SerializedSSCSMRequest request)
|
||||
{
|
||||
{
|
||||
auto lock = std::lock_guard(m_mutex);
|
||||
|
||||
m_request = std::move(request);
|
||||
}
|
||||
|
||||
m_condvar.notify_one();
|
||||
}
|
||||
|
||||
SerializedSSCSMAnswer recvA()
|
||||
{
|
||||
auto lock = std::unique_lock(m_mutex);
|
||||
|
||||
while (!m_answer) {
|
||||
m_condvar.wait(lock);
|
||||
}
|
||||
|
||||
auto answer = std::move(m_answer);
|
||||
m_answer = nullptr;
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
SerializedSSCSMAnswer exchangeA(SerializedSSCSMRequest request)
|
||||
{
|
||||
sendA(std::move(request));
|
||||
|
||||
return recvA();
|
||||
}
|
||||
|
||||
void sendB(SerializedSSCSMAnswer answer)
|
||||
{
|
||||
{
|
||||
auto lock = std::lock_guard(m_mutex);
|
||||
|
||||
m_answer = std::move(answer);
|
||||
}
|
||||
|
||||
m_condvar.notify_one();
|
||||
}
|
||||
|
||||
SerializedSSCSMRequest recvB()
|
||||
{
|
||||
auto lock = std::unique_lock(m_mutex);
|
||||
|
||||
while (!m_request) {
|
||||
m_condvar.wait(lock);
|
||||
}
|
||||
|
||||
auto request = std::move(m_request);
|
||||
m_request = nullptr;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
SerializedSSCSMRequest exchangeB(SerializedSSCSMAnswer answer)
|
||||
{
|
||||
sendB(std::move(answer));
|
||||
|
||||
return recvB();
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue