diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index aad629900a..6636c95d73 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1883,6 +1883,10 @@ mgvalleys_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), # This support is experimental and API can change. enable_client_modding (Client modding) [client] bool false +# Where to enable server-sent client-side modding (SSCSM). +# Warning: Experimental. +enable_sscsm (Client modding) enum off off,singleplayer,localhost,lan,worldwide + # Replaces the default main menu with a custom one. main_menu_script (Main menu script) [client] string diff --git a/src/client/client.cpp b/src/client/client.cpp index d5a1b6c5e3..d56a8dbb12 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -55,6 +55,7 @@ #include "mapnode.h" #include "item_visuals_manager.h" #include "script/sscsm/sscsm_controller.h" +#include "script/sscsm/sscsm_events.h" extern gui::IGUIEnvironment* guienv; @@ -139,6 +140,42 @@ Client::Client( m_mesh_grid = { g_settings->getU16("client_mesh_chunk") }; m_sscsm_controller = SSCSMController::create(); + + { + auto event1 = std::make_unique(); + //TODO: read files + event1->files.emplace_back("/client_builtin/sscsm_client/init.lua", + R"=+=( +print("client builtin: loading") + )=+="); + + //TODO: checksum + + m_sscsm_controller->runEvent(this, std::move(event1)); + + // load client builtin immediately + auto event2 = std::make_unique(); + event2->init_paths.emplace_back("/client_builtin/sscsm_client/init.lua"); + m_sscsm_controller->runEvent(this, std::move(event2)); + } + + { + //TODO: network packets + + std::string enable_sscsm = g_settings->get("enable_sscsm"); + if (enable_sscsm == "singleplayer") { + auto event1 = std::make_unique(); + event1->files.emplace_back("/mods/sscsm_test0/init.lua", + R"=+=( +print("sscsm_test0: loading") + )=+="); + m_sscsm_controller->runEvent(this, std::move(event1)); + + auto event2 = std::make_unique(); + event2->init_paths.emplace_back("/client_builtin/sscsm_client/init.lua"); + m_sscsm_controller->runEvent(this, std::move(event2)); + } + } } void Client::migrateModStorage() diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index c27da7988e..1cdd82c63d 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -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", "off"); settings->setDefault("max_out_chat_queue_size", "20"); settings->setDefault("pause_on_lost_focus", "false"); settings->setDefault("enable_split_login_register", "true"); diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index c4c8a02553..a45e9ffd4b 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -21,6 +21,7 @@ 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} diff --git a/src/script/cpp_api/CMakeLists.txt b/src/script/cpp_api/CMakeLists.txt index 713dbe28b1..c256ce7537 100644 --- a/src/script/cpp_api/CMakeLists.txt +++ b/src/script/cpp_api/CMakeLists.txt @@ -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) diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index b532e9cd99..4435e140b9 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -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,6 +59,7 @@ class EmergeThread; class IGameDef; class Environment; class GUIEngine; +class SSCSMEnvironment; class ServerActiveObject; struct PlayerHPChangeReason; @@ -90,9 +92,9 @@ public: ScriptingType getType() { return m_type; } IGameDef *getGameDef() { return m_gamedef; } - Server* getServer(); + Server *getServer(); #if CHECK_CLIENT_BUILD() - Client* getClient(); + Client *getClient(); #endif // IMPORTANT: These cannot be used for any security-related uses, they exist @@ -158,6 +160,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 +183,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; }; diff --git a/src/script/cpp_api/s_sscsm.cpp b/src/script/cpp_api/s_sscsm.cpp new file mode 100644 index 0000000000..ccbc082090 --- /dev/null +++ b/src/script/cpp_api/s_sscsm.cpp @@ -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 &init_paths) +{ + //TODO +} + +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); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); + } catch (LuaError &e) { + getSSCSMEnv()->setFatalError(e); + } +} diff --git a/src/script/cpp_api/s_sscsm.h b/src/script/cpp_api/s_sscsm.h new file mode 100644 index 0000000000..693c8db5a8 --- /dev/null +++ b/src/script/cpp_api/s_sscsm.h @@ -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 &init_paths); + + void environment_step(float dtime); +}; diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index 5f0cd08163..a1be8df728 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -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) diff --git a/src/script/lua_api/l_base.cpp b/src/script/lua_api/l_base.cpp index c95acc7335..31908e59e5 100644 --- a/src/script/lua_api/l_base.cpp +++ b/src/script/lua_api/l_base.cpp @@ -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) diff --git a/src/script/lua_api/l_base.h b/src/script/lua_api/l_base.h index 138b4cf24d..0666a93474 100644 --- a/src/script/lua_api/l_base.h +++ b/src/script/lua_api/l_base.h @@ -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); diff --git a/src/script/lua_api/l_sscsm.cpp b/src/script/lua_api/l_sscsm.cpp new file mode 100644 index 0000000000..711d6d4931 --- /dev/null +++ b/src/script/lua_api/l_sscsm.cpp @@ -0,0 +1,46 @@ +// 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 "log.h" +#include "script/sscsm/sscsm_environment.h" +#include "mapnode.h" + +// print(text) +int ModApiSSCSM::l_print(lua_State *L) +{ + // TODO: send request to main process + std::string text = luaL_checkstring(L, 1); + rawstream << text << std::endl; + return 0; +} + +// 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 + bool pos_ok = true; + MapNode n = getSSCSMEnv(L)->requestGetNode(pos); //TODO: add pos_ok to request + if (pos_ok) { + // Return node + pushnode(L, n); + } else { + lua_pushnil(L); + } + return 1; +} + +void ModApiSSCSM::Initialize(lua_State *L, int top) +{ + API_FCT(print); + API_FCT(get_node_or_nil); +} diff --git a/src/script/lua_api/l_sscsm.h b/src/script/lua_api/l_sscsm.h new file mode 100644 index 0000000000..56dd8b67eb --- /dev/null +++ b/src/script/lua_api/l_sscsm.h @@ -0,0 +1,20 @@ +// 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: + // print(text) + static int l_print(lua_State *L); + + // get_node_or_nil(pos) + static int l_get_node_or_nil(lua_State *L); + +public: + static void Initialize(lua_State *L, int top); +}; diff --git a/src/script/scripting_sscsm.cpp b/src/script/scripting_sscsm.cpp new file mode 100644 index 0000000000..ef68897054 --- /dev/null +++ b/src/script/scripting_sscsm.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Luanti authors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "scripting_sscsm.h" + +SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) +{ + setSSCSMEnv(env); + + //TODO +} + +void SSCSMScripting::initializeModApi(lua_State *L, int top) +{ + //TODO +} diff --git a/src/script/scripting_sscsm.h b/src/script/scripting_sscsm.h new file mode 100644 index 0000000000..25a5a05799 --- /dev/null +++ b/src/script/scripting_sscsm.h @@ -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); +}; diff --git a/src/script/sscsm/CMakeLists.txt b/src/script/sscsm/CMakeLists.txt index 3485a716ba..1508216de9 100644 --- a/src/script/sscsm/CMakeLists.txt +++ b/src/script/sscsm/CMakeLists.txt @@ -1,5 +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) diff --git a/src/script/sscsm/sscsm_environment.cpp b/src/script/sscsm/sscsm_environment.cpp index 36364ca386..77feae001f 100644 --- a/src/script/sscsm/sscsm_environment.cpp +++ b/src/script/sscsm/sscsm_environment.cpp @@ -8,6 +8,13 @@ #include "sscsm_stupid_channel.h" +SSCSMEnvironment::SSCSMEnvironment(std::shared_ptr channel) : + Thread("SSCSMEnvironment-thread"), + m_channel(std::move(channel)), + m_script(std::make_unique(this)) +{ +} + void *SSCSMEnvironment::run() { while (true) { @@ -28,6 +35,21 @@ SerializedSSCSMAnswer SSCSMEnvironment::exchange(SerializedSSCSMRequest req) return m_channel->exchangeA(std::move(req)); } +void SSCSMEnvironment::updateVFSFiles(std::vector> &&files) +{ + for (auto &&p : files) { + m_vfs.emplace(std::move(p.first), std::move(p.second)); + } +} + +void SSCSMEnvironment::setFatalError(const std::string &reason) +{ + //TODO + // what to do on error? + // probably send a request + errorstream << "SSCSMEnvironment::setFatalError() reason: " << reason << std::endl; +} + std::unique_ptr SSCSMEnvironment::requestPollNextEvent() { auto request = SSCSMRequestPollNextEvent{}; diff --git a/src/script/sscsm/sscsm_environment.h b/src/script/sscsm/sscsm_environment.h index 48cf652bf0..1a37c4e81c 100644 --- a/src/script/sscsm/sscsm_environment.h +++ b/src/script/sscsm/sscsm_environment.h @@ -9,22 +9,36 @@ #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. class SSCSMEnvironment : public Thread { std::shared_ptr m_channel; + std::unique_ptr m_script; + // virtual file system. + // TODO: decide and doc how paths look like, maybe: + // /client_builtin/subdir/foo.lua + // /server_builtin/subdir/foo.lua + // /mods/modname/subdir/foo.lua + std::unordered_map m_vfs; void *run() override; SerializedSSCSMAnswer exchange(SerializedSSCSMRequest req); public: - SSCSMEnvironment(std::shared_ptr channel) : - Thread("SSCSMEnvironment-thread"), - m_channel(std::move(channel)) + SSCSMEnvironment(std::shared_ptr channel); + + SSCSMScripting *getScript() { return m_script.get(); } + + void updateVFSFiles(std::vector> &&files); + + void setFatalError(const std::string &reason); + void setFatalError(const LuaError &e) { + setFatalError(std::string("Lua: ") + e.what()); } std::unique_ptr requestPollNextEvent(); diff --git a/src/script/sscsm/sscsm_events.h b/src/script/sscsm/sscsm_events.h index 1dba6e4a33..5ab8d8032f 100644 --- a/src/script/sscsm/sscsm_events.h +++ b/src/script/sscsm/sscsm_events.h @@ -7,9 +7,7 @@ #include "sscsm_ievent.h" #include "debug.h" #include "irrlichttypes.h" -#include "irr_v3d.h" #include "sscsm_environment.h" -#include "mapnode.h" struct SSCSMEventTearDown : public ISSCSMEvent { @@ -19,14 +17,35 @@ struct SSCSMEventTearDown : public ISSCSMEvent } }; -struct SSCSMEventOnStep final : public ISSCSMEvent +struct SSCSMEventUpdateVFSFiles : public ISSCSMEvent +{ + // pairs are virtual path and file content + std::vector> files; + + void exec(SSCSMEnvironment *env) override + { + env->updateVFSFiles(std::move(files)); + } +}; + +struct SSCSMEventLoadMods : public ISSCSMEvent +{ + // paths to init.lua files, in load order + std::vector init_paths; + + void exec(SSCSMEnvironment *env) override + { + env->getScript()->load_mods(init_paths); + } +}; + +struct SSCSMEventOnStep : public ISSCSMEvent { f32 dtime; void exec(SSCSMEnvironment *env) override { - // example - env->requestGetNode(v3s16(0, 0, 0)); + env->getScript()->environment_step(dtime); } };