From 7935a63ed44b28c932c1e246401bf001ab978d19 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 17 Dec 2024 11:30:13 +0100 Subject: [PATCH 01/29] Add an SSCSM controller and environment skeleton --- src/client/client.cpp | 5 ++ src/client/client.h | 7 ++- src/script/CMakeLists.txt | 2 + src/script/sscsm/CMakeLists.txt | 5 ++ src/script/sscsm/sscsm_controller.cpp | 69 ++++++++++++++++++++ src/script/sscsm/sscsm_controller.h | 37 +++++++++++ src/script/sscsm/sscsm_environment.cpp | 48 ++++++++++++++ src/script/sscsm/sscsm_environment.h | 32 ++++++++++ src/script/sscsm/sscsm_events.h | 32 ++++++++++ src/script/sscsm/sscsm_ievent.h | 39 ++++++++++++ src/script/sscsm/sscsm_irequest.h | 67 ++++++++++++++++++++ src/script/sscsm/sscsm_requests.h | 43 +++++++++++++ src/script/sscsm/sscsm_stupid_channel.h | 84 +++++++++++++++++++++++++ 13 files changed, 468 insertions(+), 2 deletions(-) create mode 100644 src/script/sscsm/CMakeLists.txt create mode 100644 src/script/sscsm/sscsm_controller.cpp create mode 100644 src/script/sscsm/sscsm_controller.h create mode 100644 src/script/sscsm/sscsm_environment.cpp create mode 100644 src/script/sscsm/sscsm_environment.h create mode 100644 src/script/sscsm/sscsm_events.h create mode 100644 src/script/sscsm/sscsm_ievent.h create mode 100644 src/script/sscsm/sscsm_irequest.h create mode 100644 src/script/sscsm/sscsm_requests.h create mode 100644 src/script/sscsm/sscsm_stupid_channel.h diff --git a/src/client/client.cpp b/src/client/client.cpp index b1dfa59931..d5a1b6c5e3 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -54,6 +54,7 @@ #include "content/mod_configuration.h" #include "mapnode.h" #include "item_visuals_manager.h" +#include "script/sscsm/sscsm_controller.h" extern gui::IGUIEnvironment* guienv; @@ -136,6 +137,8 @@ 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(); } void Client::migrateModStorage() @@ -523,6 +526,8 @@ void Client::step(float dtime) */ LocalPlayer *player = m_env.getLocalPlayer(); + m_sscsm_controller->eventOnStep(this, dtime); + // Step environment (also handles player controls) m_env.step(dtime); m_sound->step(dtime); diff --git a/src/client/client.h b/src/client/client.h index 746db1027d..79ffeddc43 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -53,6 +53,8 @@ struct MapNode; struct PlayerControl; struct PointedThing; struct ItemVisualsManager; +class ClientScripting; +class SSCSMController; namespace scene { class IAnimatedMesh; @@ -100,8 +102,6 @@ private: std::map m_packets; }; -class ClientScripting; - class Client : public con::PeerHandler, public InventoryManager, public IGameDef { public: @@ -588,6 +588,9 @@ private: std::vector m_mods; StringMap m_mod_vfs; + // SSCSM + std::unique_ptr m_sscsm_controller; + bool m_shutdown = false; // CSM restrictions byteflag diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index b331049421..c4c8a02553 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -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") @@ -24,5 +25,6 @@ set(client_SCRIPT_SRCS ${client_SCRIPT_COMMON_SRCS} ${client_SCRIPT_CPP_API_SRCS} ${client_SCRIPT_LUA_API_SRCS} + ${client_SCRIPT_SSCSM_SRCS} PARENT_SCOPE) diff --git a/src/script/sscsm/CMakeLists.txt b/src/script/sscsm/CMakeLists.txt new file mode 100644 index 0000000000..3485a716ba --- /dev/null +++ b/src/script/sscsm/CMakeLists.txt @@ -0,0 +1,5 @@ + +set(client_SCRIPT_SSCSM_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/sscsm_controller.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sscsm_environment.cpp + PARENT_SCOPE) diff --git a/src/script/sscsm/sscsm_controller.cpp b/src/script/sscsm/sscsm_controller.cpp new file mode 100644 index 0000000000..095726c11a --- /dev/null +++ b/src/script/sscsm/sscsm_controller.cpp @@ -0,0 +1,69 @@ +// 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::create() +{ + auto channel = std::make_shared(); + auto thread = std::make_unique(channel); + thread->start(); + + // Wait for thread to finish initializing. + auto req0 = deserializeSSCSMRequest(channel->recvB()); + FATAL_ERROR_IF(!dynamic_cast(req0.get()), + "First request must be pollEvent."); + + return std::make_unique(std::move(thread), channel); +} + +SSCSMController::SSCSMController(std::unique_ptr thread, + std::shared_ptr 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(); + 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 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(request.get()) != nullptr) { + break; + } + + answer = handleRequest(client, request.get()); + } +} + +void SSCSMController::eventOnStep(Client *client, f32 dtime) +{ + auto event = std::make_unique(); + event->dtime = dtime; + runEvent(client, std::move(event)); +} diff --git a/src/script/sscsm/sscsm_controller.h b/src/script/sscsm/sscsm_controller.h new file mode 100644 index 0000000000..77a16aa75d --- /dev/null +++ b/src/script/sscsm/sscsm_controller.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2024 Luanti authors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include "irrlichttypes.h" +#include "sscsm_irequest.h" +#include "sscsm_ievent.h" +#include "util/basic_macros.h" + +class SSCSMEnvironment; +class StupidChannel; + +class SSCSMController +{ + std::unique_ptr m_thread; + std::shared_ptr m_channel; + + SerializedSSCSMAnswer handleRequest(Client *client, ISSCSMRequest *req); + +public: + static std::unique_ptr create(); + + SSCSMController(std::unique_ptr thread, + std::shared_ptr channel); + + ~SSCSMController(); + + DISABLE_CLASS_COPY(SSCSMController); + + // Handles requests until the next event is polled + void runEvent(Client *client, std::unique_ptr event); + + void eventOnStep(Client *client, f32 dtime); +}; diff --git a/src/script/sscsm/sscsm_environment.cpp b/src/script/sscsm/sscsm_environment.cpp new file mode 100644 index 0000000000..36364ca386 --- /dev/null +++ b/src/script/sscsm/sscsm_environment.cpp @@ -0,0 +1,48 @@ +// 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" + + +void *SSCSMEnvironment::run() +{ + while (true) { + auto next_event = requestPollNextEvent(); + + if (dynamic_cast(next_event.get())) { + break; + } + + next_event->exec(this); + } + + return nullptr; +} + +SerializedSSCSMAnswer SSCSMEnvironment::exchange(SerializedSSCSMRequest req) +{ + return m_channel->exchangeA(std::move(req)); +} + +std::unique_ptr SSCSMEnvironment::requestPollNextEvent() +{ + auto request = SSCSMRequestPollNextEvent{}; + auto answer = deserializeSSCSMAnswer( + exchange(serializeSSCSMRequest(request)) + ); + return std::move(answer.next_event); +} + +MapNode SSCSMEnvironment::requestGetNode(v3s16 pos) +{ + auto request = SSCSMRequestGetNode{}; + request.pos = pos; + auto answer = deserializeSSCSMAnswer( + exchange(serializeSSCSMRequest(request)) + ); + return answer.node; +} diff --git a/src/script/sscsm/sscsm_environment.h b/src/script/sscsm/sscsm_environment.h new file mode 100644 index 0000000000..48cf652bf0 --- /dev/null +++ b/src/script/sscsm/sscsm_environment.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 Luanti authors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include "client/client.h" +#include "threading/thread.h" +#include "sscsm_controller.h" +#include "sscsm_irequest.h" + +// The thread that runs SSCSM code. +// Meant to be replaced by a sandboxed process. +class SSCSMEnvironment : public Thread +{ + std::shared_ptr m_channel; + + void *run() override; + + SerializedSSCSMAnswer exchange(SerializedSSCSMRequest req); + +public: + SSCSMEnvironment(std::shared_ptr channel) : + Thread("SSCSMEnvironment-thread"), + m_channel(std::move(channel)) + { + } + + std::unique_ptr requestPollNextEvent(); + MapNode requestGetNode(v3s16 pos); +}; diff --git a/src/script/sscsm/sscsm_events.h b/src/script/sscsm/sscsm_events.h new file mode 100644 index 0000000000..1dba6e4a33 --- /dev/null +++ b/src/script/sscsm/sscsm_events.h @@ -0,0 +1,32 @@ +// 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 "irr_v3d.h" +#include "sscsm_environment.h" +#include "mapnode.h" + +struct SSCSMEventTearDown : public ISSCSMEvent +{ + void exec(SSCSMEnvironment *env) override + { + FATAL_ERROR("SSCSMEventTearDown needs to be handled by SSCSMEnvironment::run()"); + } +}; + +struct SSCSMEventOnStep final : public ISSCSMEvent +{ + f32 dtime; + + void exec(SSCSMEnvironment *env) override + { + // example + env->requestGetNode(v3s16(0, 0, 0)); + } +}; + diff --git a/src/script/sscsm/sscsm_ievent.h b/src/script/sscsm/sscsm_ievent.h new file mode 100644 index 0000000000..8742a3302d --- /dev/null +++ b/src/script/sscsm/sscsm_ievent.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2024 Luanti authors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include + +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; + +template +inline SerializedSSCSMEvent serializeSSCSMEvent(const T &event) +{ + static_assert(std::is_base_of_v); + + return std::make_unique(event); +} + +inline std::unique_ptr deserializeSSCSMEvent(SerializedSSCSMEvent event_serialized) +{ + // The actual deserialization will have to use a type tag, and then choose + // the appropriate deserializer. + return event_serialized; +} diff --git a/src/script/sscsm/sscsm_irequest.h b/src/script/sscsm/sscsm_irequest.h new file mode 100644 index 0000000000..b6104e4f91 --- /dev/null +++ b/src/script/sscsm/sscsm_irequest.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2024 Luanti authors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "exceptions.h" +#include +#include + +class SSCSMController; +class Client; + +struct ISSCSMAnswer +{ + virtual ~ISSCSMAnswer() = default; +}; + +// FIXME: actually serialize, and replace this with a string +using SerializedSSCSMAnswer = std::unique_ptr; + +// 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; + +template +inline SerializedSSCSMRequest serializeSSCSMRequest(const T &request) +{ + static_assert(std::is_base_of_v); + + return std::make_unique(request); +} + +template +inline T deserializeSSCSMAnswer(SerializedSSCSMAnswer answer_serialized) +{ + static_assert(std::is_base_of_v); + + // dynamic cast in place of actual deserialization + auto ptr = dynamic_cast(answer_serialized.get()); + if (!ptr) { + throw SerializationError("deserializeSSCSMAnswer failed"); + } + return std::move(*ptr); +} + +template +inline SerializedSSCSMAnswer serializeSSCSMAnswer(T &&answer) +{ + static_assert(std::is_base_of_v); + + return std::make_unique(std::move(answer)); +} + +inline std::unique_ptr deserializeSSCSMRequest(SerializedSSCSMRequest request_serialized) +{ + // The actual deserialization will have to use a type tag, and then choose + // the appropriate deserializer. + return request_serialized; +} diff --git a/src/script/sscsm/sscsm_requests.h b/src/script/sscsm/sscsm_requests.h new file mode 100644 index 0000000000..bc13082a45 --- /dev/null +++ b/src/script/sscsm/sscsm_requests.h @@ -0,0 +1,43 @@ +// 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" + +struct SSCSMRequestPollNextEvent : public ISSCSMRequest +{ + struct Answer : public ISSCSMAnswer + { + std::unique_ptr next_event; + }; + + SerializedSSCSMAnswer exec(Client *client) override + { + FATAL_ERROR("SSCSMRequestPollNextEvent needs to be handled by SSCSMControler::runEvent()"); + } +}; + +struct SSCSMRequestGetNode : public ISSCSMRequest +{ + struct Answer : public ISSCSMAnswer + { + MapNode node; + }; + + v3s16 pos; + + SerializedSSCSMAnswer exec(Client *client) override + { + MapNode node = client->getEnv().getMap().getNode(pos); + + Answer answer{}; + answer.node = node; + return serializeSSCSMAnswer(std::move(answer)); + } +}; diff --git a/src/script/sscsm/sscsm_stupid_channel.h b/src/script/sscsm/sscsm_stupid_channel.h new file mode 100644 index 0000000000..cb82d3ca12 --- /dev/null +++ b/src/script/sscsm/sscsm_stupid_channel.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2024 Luanti authors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include +#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(); + } +}; From 0fb8e1b398bf90f9a4ceece11369b0c19b2b2410 Mon Sep 17 00:00:00 2001 From: Desour Date: Mon, 27 Jan 2025 14:12:40 +0100 Subject: [PATCH 02/29] tmp --- builtin/settingtypes.txt | 4 +++ src/client/client.cpp | 37 +++++++++++++++++++++ src/defaultsettings.cpp | 1 + src/script/CMakeLists.txt | 1 + src/script/cpp_api/CMakeLists.txt | 1 + src/script/cpp_api/s_base.h | 24 +++++++++----- src/script/cpp_api/s_sscsm.cpp | 29 ++++++++++++++++ src/script/cpp_api/s_sscsm.h | 15 +++++++++ src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_base.cpp | 5 +++ src/script/lua_api/l_base.h | 2 ++ src/script/lua_api/l_sscsm.cpp | 46 ++++++++++++++++++++++++++ src/script/lua_api/l_sscsm.h | 20 +++++++++++ src/script/scripting_sscsm.cpp | 17 ++++++++++ src/script/scripting_sscsm.h | 25 ++++++++++++++ src/script/sscsm/CMakeLists.txt | 2 ++ src/script/sscsm/sscsm_environment.cpp | 22 ++++++++++++ src/script/sscsm/sscsm_environment.h | 20 +++++++++-- src/script/sscsm/sscsm_events.h | 29 +++++++++++++--- 19 files changed, 284 insertions(+), 17 deletions(-) create mode 100644 src/script/cpp_api/s_sscsm.cpp create mode 100644 src/script/cpp_api/s_sscsm.h create mode 100644 src/script/lua_api/l_sscsm.cpp create mode 100644 src/script/lua_api/l_sscsm.h create mode 100644 src/script/scripting_sscsm.cpp create mode 100644 src/script/scripting_sscsm.h 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); } }; From 3725fb125150de83d02912e3c3e28fb05ff07493 Mon Sep 17 00:00:00 2001 From: Desour Date: Fri, 14 Feb 2025 14:57:22 +0100 Subject: [PATCH 03/29] tmp2 --- src/script/cpp_api/s_sscsm.cpp | 12 +++++ src/script/lua_api/l_sscsm.cpp | 18 +++++--- src/script/scripting_sscsm.cpp | 3 +- src/script/sscsm/sscsm_environment.cpp | 24 ++++++---- src/script/sscsm/sscsm_environment.h | 12 +++++ src/script/sscsm/sscsm_requests.h | 64 +++++++++++++++++++++++++- 6 files changed, 114 insertions(+), 19 deletions(-) diff --git a/src/script/cpp_api/s_sscsm.cpp b/src/script/cpp_api/s_sscsm.cpp index ccbc082090..70960ef06e 100644 --- a/src/script/cpp_api/s_sscsm.cpp +++ b/src/script/cpp_api/s_sscsm.cpp @@ -10,6 +10,18 @@ void ScriptApiSSCSM::load_mods(const std::vector &init_paths) { //TODO + + SSCSMEnvironment *env = getSSCSMEnv(); + actionstream << "load_mods:\n"; + for (const auto &p : init_paths) { + actionstream << " " << p << ":\n"; + auto f = env->readVFSFile(p); + if (!f.has_value()) { + env->setFatalError("load_mods(): File doesn't exist: " + p); + return; + } + actionstream << *f << "\n"; + } } void ScriptApiSSCSM::environment_step(float dtime) diff --git a/src/script/lua_api/l_sscsm.cpp b/src/script/lua_api/l_sscsm.cpp index 711d6d4931..c676e1588f 100644 --- a/src/script/lua_api/l_sscsm.cpp +++ b/src/script/lua_api/l_sscsm.cpp @@ -9,14 +9,16 @@ #include "l_internal.h" #include "log.h" #include "script/sscsm/sscsm_environment.h" +#include "script/sscsm/sscsm_requests.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; + auto request = SSCSMRequestPrint{}; + request.text = luaL_checkstring(L, 1); + getSSCSMEnv(L)->doRequest(std::move(request)); + return 0; } @@ -28,11 +30,13 @@ int ModApiSSCSM::l_get_node_or_nil(lua_State *L) 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) { + auto request = SSCSMRequestGetNode{}; + request.pos = pos; + auto answer = getSSCSMEnv(L)->doRequest(std::move(request)); + + if (answer.is_pos_ok) { // Return node - pushnode(L, n); + pushnode(L, answer.node); } else { lua_pushnil(L); } diff --git a/src/script/scripting_sscsm.cpp b/src/script/scripting_sscsm.cpp index ef68897054..185047d68b 100644 --- a/src/script/scripting_sscsm.cpp +++ b/src/script/scripting_sscsm.cpp @@ -4,7 +4,8 @@ #include "scripting_sscsm.h" -SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) +SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) : + ScriptApiBase(ScriptingType::SSCSM) { setSSCSMEnv(env); diff --git a/src/script/sscsm/sscsm_environment.cpp b/src/script/sscsm/sscsm_environment.cpp index 77feae001f..7e04f694fb 100644 --- a/src/script/sscsm/sscsm_environment.cpp +++ b/src/script/sscsm/sscsm_environment.cpp @@ -42,20 +42,26 @@ void SSCSMEnvironment::updateVFSFiles(std::vector SSCSMEnvironment::readVFSFile(const std::string &path) +{ + auto it = m_vfs.find(path); + if (it == m_vfs.end()) + return std::nullopt; + else + return it->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; + auto request = SSCSMRequestSetFatalError{}; + request.reason = reason; + doRequest(std::move(request)); } std::unique_ptr SSCSMEnvironment::requestPollNextEvent() { auto request = SSCSMRequestPollNextEvent{}; - auto answer = deserializeSSCSMAnswer( - exchange(serializeSSCSMRequest(request)) - ); + auto answer = doRequest(std::move(request)); return std::move(answer.next_event); } @@ -63,8 +69,6 @@ MapNode SSCSMEnvironment::requestGetNode(v3s16 pos) { auto request = SSCSMRequestGetNode{}; request.pos = pos; - auto answer = deserializeSSCSMAnswer( - exchange(serializeSSCSMRequest(request)) - ); + auto answer = doRequest(std::move(request)); return answer.node; } diff --git a/src/script/sscsm/sscsm_environment.h b/src/script/sscsm/sscsm_environment.h index 1a37c4e81c..a300fed8af 100644 --- a/src/script/sscsm/sscsm_environment.h +++ b/src/script/sscsm/sscsm_environment.h @@ -5,6 +5,9 @@ #pragma once #include +#include +#include +#include #include "client/client.h" #include "threading/thread.h" #include "sscsm_controller.h" @@ -34,6 +37,7 @@ public: SSCSMScripting *getScript() { return m_script.get(); } void updateVFSFiles(std::vector> &&files); + std::optional readVFSFile(const std::string &path); void setFatalError(const std::string &reason); void setFatalError(const LuaError &e) @@ -41,6 +45,14 @@ public: setFatalError(std::string("Lua: ") + e.what()); } + template + typename RQ::Answer doRequest(RQ &&rq) + { + return deserializeSSCSMAnswer( + exchange(serializeSSCSMRequest(std::forward(rq))) + ); + } + std::unique_ptr requestPollNextEvent(); MapNode requestGetNode(v3s16 pos); }; diff --git a/src/script/sscsm/sscsm_requests.h b/src/script/sscsm/sscsm_requests.h index bc13082a45..7dee593b11 100644 --- a/src/script/sscsm/sscsm_requests.h +++ b/src/script/sscsm/sscsm_requests.h @@ -9,7 +9,9 @@ #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 @@ -23,21 +25,81 @@ struct SSCSMRequestPollNextEvent : public ISSCSMRequest } }; +// 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) +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) +struct SSCSMRequestLog : public ISSCSMRequest +{ + struct Answer : public ISSCSMAnswer + { + }; + + std::string text; + LogLevel level; + + SerializedSSCSMAnswer exec(Client *client) override + { + if (level >= LL_MAX) { + errorstream << "Tried to log at non-existent level." << std::endl; // TODO: should probably throw + } 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 { - MapNode node = client->getEnv().getMap().getNode(pos); + 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)); } }; From 31513cac6e95bbf5971867322fc9082bff11a841 Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 15 Feb 2025 18:16:10 +0100 Subject: [PATCH 04/29] tmp3 --- src/script/cpp_api/s_security.cpp | 106 +++++++++++++++++++++++++++++- src/script/cpp_api/s_security.h | 4 +- src/script/scripting_sscsm.cpp | 24 ++++++- 3 files changed, 128 insertions(+), 6 deletions(-) diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 834650fdc6..fab1f09e3a 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -378,6 +378,105 @@ void ScriptApiSecurity::initializeSecurityClient() setLuaEnv(L, thread); } +void ScriptApiSecurity::initializeSecuritySSCSM() +{ + static const char *whitelist[] = { + "assert", + "core", + "collectgarbage", + "DIR_DELIM", //TODO: useless? + "error", + "getfenv", + "ipairs", + "next", + "pairs", + "pcall", + "print", //TODO + "rawequal", + "rawget", + "rawset", + "select", + "setfenv", + "getmetatable", + "setmetatable", + "tonumber", + "tostring", + "type", + "unpack", //TODO: replace, because of UB in some lua versions + "_VERSION", + "xpcall", + // Completely safe libraries + "coroutine", + "string", + "table", + "math", + "bit", + }; + static const char *os_whitelist[] = { + "clock", //TODO: limit resolution, to mitigate side channel attacks + "date", + "difftime", + "time" + }; + +#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)); + lua_setfield(L, -3, "os"); + lua_pop(L, 1); // Pop old OS + + +#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 +874,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(L, 1); - const std::string *contents = script->getClient()->getModFile(path); + const std::string *contents = script->getClient()->getModFile(path); //TODO if (!contents) { std::string error_msg = "Couldn't find script called: " + path; lua_pushnil(L); diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h index 1241b0b06d..9d760ba530 100644 --- a/src/script/cpp_api/s_security.h +++ b/src/script/cpp_api/s_security.h @@ -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 diff --git a/src/script/scripting_sscsm.cpp b/src/script/scripting_sscsm.cpp index 185047d68b..287f79d6d3 100644 --- a/src/script/scripting_sscsm.cpp +++ b/src/script/scripting_sscsm.cpp @@ -3,16 +3,36 @@ // 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" SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) : ScriptApiBase(ScriptingType::SSCSM) { setSSCSMEnv(env); - //TODO + 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"); + + // infostream << "SCRIPTAPI: Initialized SSCSM modules" << std::endl; } void SSCSMScripting::initializeModApi(lua_State *L, int top) { - //TODO + // Initialize mod API modules + // ModApiUtil::Initialize(L, top); + ModApiSSCSM::Initialize(L, top); } From d8435dcf8d9b186cf8828d626002640142d38732 Mon Sep 17 00:00:00 2001 From: Desour Date: Fri, 21 Feb 2025 11:11:51 +0100 Subject: [PATCH 05/29] some error handling stuff --- src/script/cpp_api/s_sscsm.cpp | 9 ++----- src/script/lua_api/l_sscsm.cpp | 34 +++++++++++++++++++++++++- src/script/lua_api/l_sscsm.h | 3 +++ src/script/scripting_sscsm.cpp | 2 +- src/script/sscsm/sscsm_environment.cpp | 8 +++++- src/script/sscsm/sscsm_environment.h | 4 --- src/script/sscsm/sscsm_requests.h | 1 + 7 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/script/cpp_api/s_sscsm.cpp b/src/script/cpp_api/s_sscsm.cpp index 70960ef06e..154c2d3d87 100644 --- a/src/script/cpp_api/s_sscsm.cpp +++ b/src/script/cpp_api/s_sscsm.cpp @@ -17,8 +17,7 @@ void ScriptApiSSCSM::load_mods(const std::vector &init_paths) actionstream << " " << p << ":\n"; auto f = env->readVFSFile(p); if (!f.has_value()) { - env->setFatalError("load_mods(): File doesn't exist: " + p); - return; + throw ModError("load_mods(): File doesn't exist: " + p); } actionstream << *f << "\n"; } @@ -33,9 +32,5 @@ void ScriptApiSSCSM::environment_step(float dtime) 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); - } + runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } diff --git a/src/script/lua_api/l_sscsm.cpp b/src/script/lua_api/l_sscsm.cpp index c676e1588f..70e1753c00 100644 --- a/src/script/lua_api/l_sscsm.cpp +++ b/src/script/lua_api/l_sscsm.cpp @@ -13,7 +13,7 @@ #include "mapnode.h" // print(text) -int ModApiSSCSM::l_print(lua_State *L) +int ModApiSSCSM::l_print(lua_State *L) //TODO: not core.print { auto request = SSCSMRequestPrint{}; request.text = luaL_checkstring(L, 1); @@ -22,6 +22,37 @@ int ModApiSSCSM::l_print(lua_State *L) return 0; } +// log([level], text) +int ModApiSSCSM::l_log(lua_State *L) +{ + /* + auto request = SSCSMRequestLog{}; + request.text = luaL_checkstring(L, 1); + getSSCSMEnv(L)->doRequest(std::move(request)); + + std::string_view text; + LogLevel level = LL_NONE; + if (lua_isnoneornil(L, 2)) { + text = readParam(L, 1); + } else { + auto name = readParam(L, 1); + text = readParam(L, 2); + // if (name == "deprecated") { //TODO + // log_deprecated(L, text, 2); + // return 0; + // } + level = Logger::stringToLevel(name); + if (level == LL_MAX) { + warningstream << "Tried to log at unknown level '" << name + << "'. Defaulting to \"none\"." << std::endl; + level = LL_WARNING; + } + } + g_logger.log(level, text); + */ + return 0; +} + // get_node_or_nil(pos) // pos = {x=num, y=num, z=num} int ModApiSSCSM::l_get_node_or_nil(lua_State *L) @@ -46,5 +77,6 @@ int ModApiSSCSM::l_get_node_or_nil(lua_State *L) void ModApiSSCSM::Initialize(lua_State *L, int top) { API_FCT(print); + API_FCT(log); API_FCT(get_node_or_nil); } diff --git a/src/script/lua_api/l_sscsm.h b/src/script/lua_api/l_sscsm.h index 56dd8b67eb..d21785ecd1 100644 --- a/src/script/lua_api/l_sscsm.h +++ b/src/script/lua_api/l_sscsm.h @@ -12,6 +12,9 @@ private: // print(text) static int l_print(lua_State *L); + // log([level], text) + static int l_log(lua_State *L); + // get_node_or_nil(pos) static int l_get_node_or_nil(lua_State *L); diff --git a/src/script/scripting_sscsm.cpp b/src/script/scripting_sscsm.cpp index 287f79d6d3..e925396550 100644 --- a/src/script/scripting_sscsm.cpp +++ b/src/script/scripting_sscsm.cpp @@ -8,7 +8,7 @@ // #include "lua_api/l_util.h" SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) : - ScriptApiBase(ScriptingType::SSCSM) + ScriptApiBase(ScriptingType::SSCSM) //TODO: use different CUSTOM_RIDX_ERROR_HANDLER, or set debug.traceback { setSSCSMEnv(env); diff --git a/src/script/sscsm/sscsm_environment.cpp b/src/script/sscsm/sscsm_environment.cpp index 7e04f694fb..6f6d8d2b7b 100644 --- a/src/script/sscsm/sscsm_environment.cpp +++ b/src/script/sscsm/sscsm_environment.cpp @@ -24,7 +24,13 @@ void *SSCSMEnvironment::run() break; } - next_event->exec(this); + 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; diff --git a/src/script/sscsm/sscsm_environment.h b/src/script/sscsm/sscsm_environment.h index a300fed8af..f055a88b7b 100644 --- a/src/script/sscsm/sscsm_environment.h +++ b/src/script/sscsm/sscsm_environment.h @@ -40,10 +40,6 @@ public: std::optional readVFSFile(const std::string &path); void setFatalError(const std::string &reason); - void setFatalError(const LuaError &e) - { - setFatalError(std::string("Lua: ") + e.what()); - } template typename RQ::Answer doRequest(RQ &&rq) diff --git a/src/script/sscsm/sscsm_requests.h b/src/script/sscsm/sscsm_requests.h index 7dee593b11..d82194ffba 100644 --- a/src/script/sscsm/sscsm_requests.h +++ b/src/script/sscsm/sscsm_requests.h @@ -60,6 +60,7 @@ struct SSCSMRequestPrint : public ISSCSMRequest }; // core.log(level, text) +// FIXME: override global loggers to use this in sscsm process struct SSCSMRequestLog : public ISSCSMRequest { struct Answer : public ISSCSMAnswer From e4a3b631cf893992b8d0f87a30df362011bf3bdf Mon Sep 17 00:00:00 2001 From: Desour Date: Fri, 21 Feb 2025 12:19:04 +0100 Subject: [PATCH 06/29] mod_vfs stuff from TurkeyMcMac's PR Co-authored-by: Jude Melton-Houghton --- src/client/CMakeLists.txt | 1 + src/client/client.cpp | 53 +++--------------------- src/client/client.h | 13 ++---- src/client/mod_vfs.cpp | 56 ++++++++++++++++++++++++++ src/client/mod_vfs.h | 23 +++++++++++ src/script/cpp_api/s_base.cpp | 19 +++++++-- src/script/cpp_api/s_base.h | 2 + src/script/cpp_api/s_security.cpp | 3 +- src/script/sscsm/sscsm_environment.cpp | 12 ++++-- src/script/sscsm/sscsm_environment.h | 4 +- 10 files changed, 119 insertions(+), 67 deletions(-) create mode 100644 src/client/mod_vfs.cpp create mode 100644 src/client/mod_vfs.h diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index ac98d4bd88..60f756b428 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -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 diff --git a/src/client/client.cpp b/src/client/client.cpp index d56a8dbb12..7c15cc3b05 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -10,6 +10,7 @@ #include #include "client.h" #include "client/fontengine.h" +#include "client/mod_vfs.h" #include "network/clientopcodes.h" #include "network/connection.h" #include "network/networkpacket.h" @@ -223,12 +224,14 @@ void Client::loadMods() return; } + m_mod_vfs = std::make_unique(); + 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(); @@ -267,7 +270,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 @@ -289,35 +292,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 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"; @@ -2076,23 +2050,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 */ diff --git a/src/client/client.h b/src/client/client.h index 79ffeddc43..aabb07a554 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -55,6 +55,7 @@ struct PointedThing; struct ItemVisualsManager; class ClientScripting; class SSCSMController; +struct ModVFS; namespace scene { class IAnimatedMesh; @@ -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,7 @@ private: ModStorageDatabase *m_mod_storage_database = nullptr; float m_mod_storage_save_timer = 10.0f; std::vector m_mods; - StringMap m_mod_vfs; + std::unique_ptr m_mod_vfs; // SSCSM std::unique_ptr m_sscsm_controller; diff --git a/src/client/mod_vfs.cpp b/src/client/mod_vfs.cpp new file mode 100644 index 0000000000..391beb4ea6 --- /dev/null +++ b/src/client/mod_vfs.cpp @@ -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 + +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 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; +} diff --git a/src/client/mod_vfs.h b/src/client/mod_vfs.h new file mode 100644 index 0000000000..c192b22890 --- /dev/null +++ b/src/client/mod_vfs.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 Luanti authors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include + +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 m_vfs; +}; diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 6ed43a2583..324a15ed8b 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -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 @@ -269,12 +271,13 @@ void ScriptApiBase::loadModFromMemory(const std::string &mod_name) { 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; - const std::string *contents = getClient()->getModFile(init_filename); + const std::string *contents = getModVFS()->getModFile(init_filename); if (!contents) throw ModError("Mod \"" + mod_name + "\" lacks init.lua"); @@ -540,8 +543,18 @@ Server* ScriptApiBase::getServer() } #if CHECK_CLIENT_BUILD() -Client* ScriptApiBase::getClient() +Client *ScriptApiBase::getClient() { return dynamic_cast(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 diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 4435e140b9..0e6e9886b1 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -62,6 +62,7 @@ class GUIEngine; class SSCSMEnvironment; class ServerActiveObject; struct PlayerHPChangeReason; +struct ModVFS; class ScriptApiBase : protected LuaHelper { public: @@ -95,6 +96,7 @@ public: Server *getServer(); #if CHECK_CLIENT_BUILD() Client *getClient(); + ModVFS *getModVFS(); #endif // IMPORTANT: These cannot be used for any security-related uses, they exist diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index fab1f09e3a..962c3d5616 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -9,6 +9,7 @@ #include "server.h" #if CHECK_CLIENT_BUILD() #include "client/client.h" +#include "client/mod_vfs.h" #endif #include "settings.h" @@ -878,7 +879,7 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L) if (script->getType() == ScriptingType::Client || script->getType() == ScriptingType::SSCSM) { std::string path = readParam(L, 1); - const std::string *contents = script->getClient()->getModFile(path); //TODO + const std::string *contents = script->getModVFS()->getModFile(path); if (!contents) { std::string error_msg = "Couldn't find script called: " + path; lua_pushnil(L); diff --git a/src/script/sscsm/sscsm_environment.cpp b/src/script/sscsm/sscsm_environment.cpp index 6f6d8d2b7b..90bebeccb1 100644 --- a/src/script/sscsm/sscsm_environment.cpp +++ b/src/script/sscsm/sscsm_environment.cpp @@ -6,15 +6,19 @@ #include "sscsm_requests.h" #include "sscsm_events.h" #include "sscsm_stupid_channel.h" +#include "client/mod_vfs.h" SSCSMEnvironment::SSCSMEnvironment(std::shared_ptr channel) : Thread("SSCSMEnvironment-thread"), m_channel(std::move(channel)), - m_script(std::make_unique(this)) + m_script(std::make_unique(this)), + m_vfs(std::make_unique()) { } +SSCSMEnvironment::~SSCSMEnvironment() = default; + void *SSCSMEnvironment::run() { while (true) { @@ -44,14 +48,14 @@ SerializedSSCSMAnswer SSCSMEnvironment::exchange(SerializedSSCSMRequest req) void SSCSMEnvironment::updateVFSFiles(std::vector> &&files) { for (auto &&p : files) { - m_vfs.emplace(std::move(p.first), std::move(p.second)); + m_vfs->m_vfs.emplace(std::move(p.first), std::move(p.second)); } } std::optional SSCSMEnvironment::readVFSFile(const std::string &path) { - auto it = m_vfs.find(path); - if (it == m_vfs.end()) + auto it = m_vfs->m_vfs.find(path); + if (it == m_vfs->m_vfs.end()) return std::nullopt; else return it->second; diff --git a/src/script/sscsm/sscsm_environment.h b/src/script/sscsm/sscsm_environment.h index f055a88b7b..643e49fcd3 100644 --- a/src/script/sscsm/sscsm_environment.h +++ b/src/script/sscsm/sscsm_environment.h @@ -25,7 +25,7 @@ class SSCSMEnvironment : public Thread // /client_builtin/subdir/foo.lua // /server_builtin/subdir/foo.lua // /mods/modname/subdir/foo.lua - std::unordered_map m_vfs; + std::unique_ptr m_vfs; void *run() override; @@ -33,9 +33,11 @@ class SSCSMEnvironment : public Thread public: SSCSMEnvironment(std::shared_ptr channel); + ~SSCSMEnvironment(); SSCSMScripting *getScript() { return m_script.get(); } + ModVFS *getModVFS() { return m_vfs.get(); } void updateVFSFiles(std::vector> &&files); std::optional readVFSFile(const std::string &path); From 2a75ffa38ad37995a65b21b88d927522f8f3bb49 Mon Sep 17 00:00:00 2001 From: Desour Date: Fri, 21 Feb 2025 13:31:49 +0100 Subject: [PATCH 07/29] actually load the code, and make it not crash --- builtin/init.lua | 3 +++ builtin/sscsm_client/init.lua | 13 +++++++++++++ builtin/sscsm_client/register.lua | 5 +++++ builtin/sscsm_server/init.lua | 0 src/client/client.cpp | 20 +++++++++++--------- src/script/cpp_api/s_base.cpp | 12 +++++++----- src/script/cpp_api/s_base.h | 2 +- src/script/cpp_api/s_security.cpp | 14 +++++++++++++- src/script/cpp_api/s_sscsm.cpp | 17 +++++------------ src/script/cpp_api/s_sscsm.h | 2 +- src/script/lua_api/l_client.cpp | 17 +++++++++++++++-- src/script/lua_api/l_client.h | 1 + src/script/scripting_sscsm.cpp | 7 ++++--- src/script/sscsm/sscsm_events.h | 6 +++--- 14 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 builtin/sscsm_client/init.lua create mode 100644 builtin/sscsm_client/register.lua create mode 100644 builtin/sscsm_server/init.lua diff --git a/builtin/init.lua b/builtin/init.lua index 59d1558fca..519a7483b1 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -76,6 +76,9 @@ elseif INIT == "async_game" then dofile(asyncpath .. "game.lua") elseif INIT == "client" then dofile(scriptdir .. "client" .. DIR_DELIM .. "init.lua") +elseif INIT == "sscsm" then + -- FIXME: different branch for sscsm_server + dofile(scriptdir .. "sscsm_client" .. DIR_DELIM .. "init.lua") elseif INIT == "emerge" then dofile(scriptdir .. "emerge" .. DIR_DELIM .. "init.lua") elseif INIT == "pause_menu" then diff --git a/builtin/sscsm_client/init.lua b/builtin/sscsm_client/init.lua new file mode 100644 index 0000000000..177891c0a0 --- /dev/null +++ b/builtin/sscsm_client/init.lua @@ -0,0 +1,13 @@ + +local scriptpath = core.get_builtin_path() +local commonpath = scriptpath .. "common" .. DIR_DELIM +local mypath = scriptpath .. "sscsm_client".. DIR_DELIM + +-- Shared between builtin files, but +-- not exposed to outer context +local builtin_shared = {} + +assert(loadfile(commonpath .. "register.lua"))(builtin_shared) +assert(loadfile(mypath .. "register.lua"))(builtin_shared) + +dofile(commonpath .. "after.lua") diff --git a/builtin/sscsm_client/register.lua b/builtin/sscsm_client/register.lua new file mode 100644 index 0000000000..b224053bbb --- /dev/null +++ b/builtin/sscsm_client/register.lua @@ -0,0 +1,5 @@ +local builtin_shared = ... + +local make_registration = builtin_shared.make_registration + +core.registered_globalsteps, core.register_globalstep = make_registration() diff --git a/builtin/sscsm_server/init.lua b/builtin/sscsm_server/init.lua new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/client/client.cpp b/src/client/client.cpp index 7c15cc3b05..7eb0c5d284 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -144,36 +144,38 @@ Client::Client( { 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 + 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(); - event2->init_paths.emplace_back("/client_builtin/sscsm_client/init.lua"); + event2->mods.emplace_back("*client_builtin*", "*client_builtin*:init.lua"); m_sscsm_controller->runEvent(this, std::move(event2)); } { //TODO: network packets + //TODO: check that *client_builtin* is not overridden 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", + event1->files.emplace_back("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"); + event2->mods.emplace_back("sscsm_test0", "sscsm_test0:init.lua"); m_sscsm_controller->runEvent(this, std::move(event2)); } } diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 324a15ed8b..6e06cff74a 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -145,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); @@ -267,17 +268,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 || 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 = getModVFS()->getModFile(init_filename); + const std::string *contents = getModVFS()->getModFile(init_path); if (!contents) throw ModError("Mod \"" + mod_name + "\" lacks init.lua"); diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 0e6e9886b1..105eb04b16 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -80,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, diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 962c3d5616..363639a457 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -385,7 +385,7 @@ void ScriptApiSecurity::initializeSecuritySSCSM() "assert", "core", "collectgarbage", - "DIR_DELIM", //TODO: useless? + "DIR_DELIM", "error", "getfenv", "ipairs", @@ -419,6 +419,10 @@ void ScriptApiSecurity::initializeSecuritySSCSM() "difftime", "time" }; + static const char *debug_whitelist[] = { + "getinfo", // used by builtin and unset before mods load //TODO + "traceback" //TODO + }; #if USE_LUAJIT static const char *jit_whitelist[] = { @@ -465,6 +469,14 @@ void ScriptApiSecurity::initializeSecuritySSCSM() lua_pop(L, 1); // Pop old OS + // Copy safe debug functions //TODO + 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 + + #if USE_LUAJIT // Copy safe jit functions, if they exist lua_getglobal(L, "jit"); diff --git a/src/script/cpp_api/s_sscsm.cpp b/src/script/cpp_api/s_sscsm.cpp index 154c2d3d87..8abd0443b4 100644 --- a/src/script/cpp_api/s_sscsm.cpp +++ b/src/script/cpp_api/s_sscsm.cpp @@ -7,19 +7,12 @@ #include "s_internal.h" #include "script/sscsm/sscsm_environment.h" -void ScriptApiSSCSM::load_mods(const std::vector &init_paths) +void ScriptApiSSCSM::load_mods(const std::vector> &mods) { - //TODO - - SSCSMEnvironment *env = getSSCSMEnv(); - actionstream << "load_mods:\n"; - for (const auto &p : init_paths) { - actionstream << " " << p << ":\n"; - auto f = env->readVFSFile(p); - if (!f.has_value()) { - throw ModError("load_mods(): File doesn't exist: " + p); - } - actionstream << *f << "\n"; + infostream << "Loading SSCSMs:" << std::endl; + for (const auto &m : mods) { + infostream << "Loading SSCSM " << m.first << std::endl; + loadModFromMemory(m.first, m.second); } } diff --git a/src/script/cpp_api/s_sscsm.h b/src/script/cpp_api/s_sscsm.h index 693c8db5a8..2e16dd8386 100644 --- a/src/script/cpp_api/s_sscsm.h +++ b/src/script/cpp_api/s_sscsm.h @@ -9,7 +9,7 @@ class ScriptApiSSCSM : virtual public ScriptApiBase { public: - void load_mods(const std::vector &init_paths); + void load_mods(const std::vector> &mods); void environment_step(float dtime); }; diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 2aef6d470f..6623a5bfaa 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -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(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,12 @@ 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 ":"); + if (getScriptApiBase(L)->getType() == ScriptingType::Client) + lua_pushstring(L, BUILTIN_MOD_NAME ":"); + else if (getScriptApiBase(L)->getType() == ScriptingType::SSCSM) + lua_pushstring(L, "*client_builtin*:"); //TODO + else + return 0; return 1; } @@ -322,3 +327,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); +} diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index 777b128cc2..ec0ceca32b 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -71,4 +71,5 @@ private: public: static void Initialize(lua_State *L, int top); + static void InitializeSSCSM(lua_State *L, int top); }; diff --git a/src/script/scripting_sscsm.cpp b/src/script/scripting_sscsm.cpp index e925396550..b79aa55168 100644 --- a/src/script/scripting_sscsm.cpp +++ b/src/script/scripting_sscsm.cpp @@ -5,7 +5,8 @@ #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_util.h" +#include "lua_api/l_client.h" SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) : ScriptApiBase(ScriptingType::SSCSM) //TODO: use different CUSTOM_RIDX_ERROR_HANDLER, or set debug.traceback @@ -32,7 +33,7 @@ SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) : void SSCSMScripting::initializeModApi(lua_State *L, int top) { - // Initialize mod API modules - // ModApiUtil::Initialize(L, top); + ModApiUtil::InitializeClient(L, top); //TODO: probably needs an InitializeSSCSM + ModApiClient::InitializeSSCSM(L, top); ModApiSSCSM::Initialize(L, top); } diff --git a/src/script/sscsm/sscsm_events.h b/src/script/sscsm/sscsm_events.h index 5ab8d8032f..c1ad578c6a 100644 --- a/src/script/sscsm/sscsm_events.h +++ b/src/script/sscsm/sscsm_events.h @@ -30,12 +30,12 @@ struct SSCSMEventUpdateVFSFiles : public ISSCSMEvent struct SSCSMEventLoadMods : public ISSCSMEvent { - // paths to init.lua files, in load order - std::vector init_paths; + // modnames and paths to init.lua file, in load order + std::vector> mods; void exec(SSCSMEnvironment *env) override { - env->getScript()->load_mods(init_paths); + env->getScript()->load_mods(mods); } }; From 962742559f505ea89e241380bb07bf50b0b76708 Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 22 Feb 2025 12:21:36 +0100 Subject: [PATCH 08/29] stuff --- builtin/common/item_s.lua | 8 ++++++ builtin/sscsm_client/init.lua | 21 +++++++++++++++ src/client/client.cpp | 2 ++ src/script/cpp_api/s_security.cpp | 4 ++- src/script/lua_api/l_client.cpp | 10 ++++--- src/script/lua_api/l_sscsm.cpp | 45 ------------------------------- src/script/scripting_sscsm.cpp | 4 +-- src/script/sscsm/sscsm_requests.h | 3 ++- 8 files changed, 44 insertions(+), 53 deletions(-) diff --git a/builtin/common/item_s.lua b/builtin/common/item_s.lua index 2761e41dd5..8263067d6e 100644 --- a/builtin/common/item_s.lua +++ b/builtin/common/item_s.lua @@ -232,6 +232,14 @@ if core.set_read_node and core.set_push_node then core.set_read_node = nil local function push_node(content, param1, param2) + if false then -- TODO: tmp + print(dump(debug.traceback())) + --~ error() + for i = 0, 10 do + print("i="..i) + print(dump(debug.getinfo(i))) + end + end return {name = content2name[content], param1 = param1, param2 = param2} end core.set_push_node(push_node) diff --git a/builtin/sscsm_client/init.lua b/builtin/sscsm_client/init.lua index 177891c0a0..5ef5531a77 100644 --- a/builtin/sscsm_client/init.lua +++ b/builtin/sscsm_client/init.lua @@ -7,7 +7,28 @@ local mypath = scriptpath .. "sscsm_client".. DIR_DELIM -- not exposed to outer context local builtin_shared = {} +-- placeholders +-- FIXME: send actual content defs to sscsm env +function core.get_content_id(name) + return tonumber(name) +end +function core.get_name_from_content_id(id) + return tostring(id) +end + +assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared) assert(loadfile(commonpath .. "register.lua"))(builtin_shared) assert(loadfile(mypath .. "register.lua"))(builtin_shared) dofile(commonpath .. "after.lua") + + +-- TODO: tmp + +local function dings() + print(dump(core.get_node_or_nil(vector.zero()))) + core.after(1, dings) +end +--~ core.after(0, dings) + +print(core.get_current_modname()) diff --git a/src/client/client.cpp b/src/client/client.cpp index 7eb0c5d284..647c3aa703 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -171,6 +171,8 @@ Client::Client( event1->files.emplace_back("sscsm_test0:init.lua", R"=+=( print("sscsm_test0: loading") +--print(dump(_G)) +--print(debug.traceback()) )=+="); m_sscsm_controller->runEvent(this, std::move(event1)); diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 363639a457..31af49186f 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -421,7 +421,7 @@ void ScriptApiSecurity::initializeSecuritySSCSM() }; static const char *debug_whitelist[] = { "getinfo", // used by builtin and unset before mods load //TODO - "traceback" //TODO + "traceback" //TODO: is this fine, or does it print paths of C functions? }; #if USE_LUAJIT @@ -488,6 +488,8 @@ void ScriptApiSecurity::initializeSecuritySSCSM() // Set the environment to the one we created earlier setLuaEnv(L, thread); + + // TODO: tostring({}) } #endif diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 6623a5bfaa..4df3439748 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -284,12 +284,16 @@ int ModApiClient::l_get_privilege_list(lua_State *L) // get_builtin_path() int ModApiClient::l_get_builtin_path(lua_State *L) { + std::string modname; if (getScriptApiBase(L)->getType() == ScriptingType::Client) - lua_pushstring(L, BUILTIN_MOD_NAME ":"); + modname = BUILTIN_MOD_NAME; else if (getScriptApiBase(L)->getType() == ScriptingType::SSCSM) - lua_pushstring(L, "*client_builtin*:"); //TODO - else + modname = ScriptApiBase::getCurrentModNameInsecure(L); + + if (modname.empty()) return 0; + + lua_pushstring(L, (modname + ":").c_str()); return 1; } diff --git a/src/script/lua_api/l_sscsm.cpp b/src/script/lua_api/l_sscsm.cpp index 70e1753c00..a7447283b3 100644 --- a/src/script/lua_api/l_sscsm.cpp +++ b/src/script/lua_api/l_sscsm.cpp @@ -7,51 +7,8 @@ #include "common/c_content.h" #include "common/c_converter.h" #include "l_internal.h" -#include "log.h" #include "script/sscsm/sscsm_environment.h" #include "script/sscsm/sscsm_requests.h" -#include "mapnode.h" - -// print(text) -int ModApiSSCSM::l_print(lua_State *L) //TODO: not core.print -{ - auto request = SSCSMRequestPrint{}; - request.text = luaL_checkstring(L, 1); - getSSCSMEnv(L)->doRequest(std::move(request)); - - return 0; -} - -// log([level], text) -int ModApiSSCSM::l_log(lua_State *L) -{ - /* - auto request = SSCSMRequestLog{}; - request.text = luaL_checkstring(L, 1); - getSSCSMEnv(L)->doRequest(std::move(request)); - - std::string_view text; - LogLevel level = LL_NONE; - if (lua_isnoneornil(L, 2)) { - text = readParam(L, 1); - } else { - auto name = readParam(L, 1); - text = readParam(L, 2); - // if (name == "deprecated") { //TODO - // log_deprecated(L, text, 2); - // return 0; - // } - level = Logger::stringToLevel(name); - if (level == LL_MAX) { - warningstream << "Tried to log at unknown level '" << name - << "'. Defaulting to \"none\"." << std::endl; - level = LL_WARNING; - } - } - g_logger.log(level, text); - */ - return 0; -} // get_node_or_nil(pos) // pos = {x=num, y=num, z=num} @@ -76,7 +33,5 @@ int ModApiSSCSM::l_get_node_or_nil(lua_State *L) void ModApiSSCSM::Initialize(lua_State *L, int top) { - API_FCT(print); - API_FCT(log); API_FCT(get_node_or_nil); } diff --git a/src/script/scripting_sscsm.cpp b/src/script/scripting_sscsm.cpp index b79aa55168..908f36af00 100644 --- a/src/script/scripting_sscsm.cpp +++ b/src/script/scripting_sscsm.cpp @@ -9,7 +9,7 @@ #include "lua_api/l_client.h" SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) : - ScriptApiBase(ScriptingType::SSCSM) //TODO: use different CUSTOM_RIDX_ERROR_HANDLER, or set debug.traceback + ScriptApiBase(ScriptingType::SSCSM) { setSSCSMEnv(env); @@ -27,8 +27,6 @@ SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) : // Push builtin initialization type lua_pushstring(L, "sscsm"); lua_setglobal(L, "INIT"); - - // infostream << "SCRIPTAPI: Initialized SSCSM modules" << std::endl; } void SSCSMScripting::initializeModApi(lua_State *L, int top) diff --git a/src/script/sscsm/sscsm_requests.h b/src/script/sscsm/sscsm_requests.h index d82194ffba..39715237ee 100644 --- a/src/script/sscsm/sscsm_requests.h +++ b/src/script/sscsm/sscsm_requests.h @@ -43,6 +43,7 @@ struct SSCSMRequestSetFatalError : public ISSCSMRequest }; // print(text) +// FIXME: override global loggers to use this in sscsm process struct SSCSMRequestPrint : public ISSCSMRequest { struct Answer : public ISSCSMAnswer @@ -73,7 +74,7 @@ struct SSCSMRequestLog : public ISSCSMRequest SerializedSSCSMAnswer exec(Client *client) override { if (level >= LL_MAX) { - errorstream << "Tried to log at non-existent level." << std::endl; // TODO: should probably throw + throw BaseException("Tried to log at non-existent level."); // TODO: choose better exception type } else { g_logger.log(level, text); } From 21155488ebe1e709db1cbf085a419c6267784f38 Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 22 Feb 2025 12:37:39 +0100 Subject: [PATCH 09/29] (edit: don't) Fix unpack, and some other things --- src/client/client.cpp | 6 +++--- src/script/cpp_api/s_base.cpp | 5 +++-- src/script/cpp_api/s_security.cpp | 6 ++---- src/script/lua_api/l_sscsm.h | 6 ------ 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index 647c3aa703..0503721cd7 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -162,11 +162,11 @@ Client::Client( } { - //TODO: network packets - //TODO: check that *client_builtin* is not overridden + //FIXME: network packets + //FIXME: check that *client_builtin* is not overridden std::string enable_sscsm = g_settings->get("enable_sscsm"); - if (enable_sscsm == "singleplayer") { + if (enable_sscsm == "singleplayer") { //FIXME: enum auto event1 = std::make_unique(); event1->files.emplace_back("sscsm_test0:init.lua", R"=+=( diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 6e06cff74a..a29f03a42f 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -76,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); @@ -213,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"); } diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 31af49186f..6f3f35faeb 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -392,7 +392,6 @@ void ScriptApiSecurity::initializeSecuritySSCSM() "next", "pairs", "pcall", - "print", //TODO "rawequal", "rawget", "rawset", @@ -403,7 +402,7 @@ void ScriptApiSecurity::initializeSecuritySSCSM() "tonumber", "tostring", "type", - "unpack", //TODO: replace, because of UB in some lua versions + "unpack", "_VERSION", "xpcall", // Completely safe libraries @@ -423,7 +422,6 @@ void ScriptApiSecurity::initializeSecuritySSCSM() "getinfo", // used by builtin and unset before mods load //TODO "traceback" //TODO: is this fine, or does it print paths of C functions? }; - #if USE_LUAJIT static const char *jit_whitelist[] = { "arch", @@ -469,7 +467,7 @@ void ScriptApiSecurity::initializeSecuritySSCSM() lua_pop(L, 1); // Pop old OS - // Copy safe debug functions //TODO + // Copy safe debug functions lua_getglobal(L, "debug"); lua_newtable(L); copy_safe(L, debug_whitelist, sizeof(debug_whitelist)); diff --git a/src/script/lua_api/l_sscsm.h b/src/script/lua_api/l_sscsm.h index d21785ecd1..09a70163e2 100644 --- a/src/script/lua_api/l_sscsm.h +++ b/src/script/lua_api/l_sscsm.h @@ -9,12 +9,6 @@ class ModApiSSCSM : public ModApiBase { private: - // print(text) - static int l_print(lua_State *L); - - // log([level], text) - static int l_log(lua_State *L); - // get_node_or_nil(pos) static int l_get_node_or_nil(lua_State *L); From a79e337d7a50633797e41353e4f046b602a58452 Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 22 Feb 2025 14:13:35 +0100 Subject: [PATCH 10/29] decide not to overwrite tostring tostring({}) and string.format("%s", {}) give you pointers. (see lj_strfmt_obj) this is not very critical, but attacks could be made harder if we change this. the effort of overwriting is not worth it I think right now --- src/script/cpp_api/s_security.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 6f3f35faeb..a1d4cc9bb7 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -486,8 +486,6 @@ void ScriptApiSecurity::initializeSecuritySSCSM() // Set the environment to the one we created earlier setLuaEnv(L, thread); - - // TODO: tostring({}) } #endif From 1e0d96af26abdb363923be43c78e4119163f8deb Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 22 Feb 2025 14:36:11 +0100 Subject: [PATCH 11/29] add a ModApiUtil::InitializeSSCSM() --- src/script/lua_api/l_util.cpp | 31 +++++++++++++++++++++++++++++++ src/script/lua_api/l_util.h | 1 + src/script/scripting_sscsm.cpp | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index f91f4ca6fa..cb16be4876 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -798,6 +798,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); + + API_FCT(get_us_time); //TODO: is us to precise? + + 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); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 7185f369a0..3e1f6e0e32 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -134,4 +134,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); }; diff --git a/src/script/scripting_sscsm.cpp b/src/script/scripting_sscsm.cpp index 908f36af00..27e5354742 100644 --- a/src/script/scripting_sscsm.cpp +++ b/src/script/scripting_sscsm.cpp @@ -31,7 +31,7 @@ SSCSMScripting::SSCSMScripting(SSCSMEnvironment *env) : void SSCSMScripting::initializeModApi(lua_State *L, int top) { - ModApiUtil::InitializeClient(L, top); //TODO: probably needs an InitializeSSCSM + ModApiUtil::InitializeSSCSM(L, top); ModApiClient::InitializeSSCSM(L, top); ModApiSSCSM::Initialize(L, top); } From 6a360e507af492a32577d253d3e82053d06f97ce Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 22 Feb 2025 15:23:37 +0100 Subject: [PATCH 12/29] limit clock precision to 20 us 20 us was the value, firefox used as first response to the spectre attacks. now it's 100 us or 5 us, depending on whether it's "cross-origin isolated". we only have one origin, so choosing 20 us is probably fine, I guess see also: https://www.mozilla.org/en-US/security/advisories/mfsa2018-01/ https://developer.mozilla.org/en-US/docs/Web/API/Performance/now#security_requirements other clocks: * os.time() and os.date() only have seconds precision, AFAIK. * dtime is only given once per step, so it's not useful * there might be other ways to build clocks (if we get async envs for sscsm, with a busy loop, for example) --- src/constants.h | 4 ++++ src/script/cpp_api/s_security.cpp | 15 ++++++++++++++- src/script/cpp_api/s_security.h | 1 + src/script/lua_api/l_util.cpp | 13 ++++++++++++- src/script/lua_api/l_util.h | 3 +++ 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/constants.h b/src/constants.h index e97bd0507b..9f66b69f83 100644 --- a/src/constants.h +++ b/src/constants.h @@ -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 diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index a1d4cc9bb7..f78c93de79 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -12,6 +12,7 @@ #include "client/mod_vfs.h" #endif #include "settings.h" +#include "constants.h" #include #include @@ -413,7 +414,6 @@ void ScriptApiSecurity::initializeSecuritySSCSM() "bit", }; static const char *os_whitelist[] = { - "clock", //TODO: limit resolution, to mitigate side channel attacks "date", "difftime", "time" @@ -463,6 +463,10 @@ void ScriptApiSecurity::initializeSecuritySSCSM() 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 @@ -1074,3 +1078,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(t) / static_cast(CLOCKS_PER_SEC)); + return 1; +} diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h index 9d760ba530..d72612ffe1 100644 --- a/src/script/cpp_api/s_security.h +++ b/src/script/cpp_api/s_security.h @@ -117,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); }; diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index cb16be4876..06f1a65ff8 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -30,6 +30,7 @@ #include "util/png.h" #include "player.h" #include "daynightratio.h" +#include "constants.h" #include // 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; @@ -802,7 +813,7 @@ void ModApiUtil::InitializeSSCSM(lua_State *L, int top) { API_FCT(log); - API_FCT(get_us_time); //TODO: is us to precise? + registerFunction(L, "get_us_time", l_get_us_time_sscsm, top); API_FCT(parse_json); API_FCT(write_json); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 3e1f6e0e32..90944b0de3 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -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); From 85db322f58760c04e5bb00f68e1758cfe3779712 Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 22 Feb 2025 15:40:37 +0100 Subject: [PATCH 13/29] fix this path todos --- builtin/init.lua | 5 +++-- src/script/sscsm/sscsm_environment.h | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/builtin/init.lua b/builtin/init.lua index 519a7483b1..c0ba5f4001 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -76,9 +76,10 @@ elseif INIT == "async_game" then dofile(asyncpath .. "game.lua") elseif INIT == "client" then dofile(scriptdir .. "client" .. DIR_DELIM .. "init.lua") -elseif INIT == "sscsm" then - -- FIXME: different branch for sscsm_server +elseif INIT == "sscsm" and core.get_current_modname() == "*client_builtin*" then dofile(scriptdir .. "sscsm_client" .. DIR_DELIM .. "init.lua") +elseif INIT == "sscsm" and core.get_current_modname() == "*server_builtin*" then + dofile(scriptdir .. "sscsm_server" .. DIR_DELIM .. "init.lua") elseif INIT == "emerge" then dofile(scriptdir .. "emerge" .. DIR_DELIM .. "init.lua") elseif INIT == "pause_menu" then diff --git a/src/script/sscsm/sscsm_environment.h b/src/script/sscsm/sscsm_environment.h index 643e49fcd3..e929fab75e 100644 --- a/src/script/sscsm/sscsm_environment.h +++ b/src/script/sscsm/sscsm_environment.h @@ -20,11 +20,11 @@ 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 + // 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 m_vfs; void *run() override; From c56b8d25cfac546637208ba12a8830c23a8771b6 Mon Sep 17 00:00:00 2001 From: Desour Date: Mon, 24 Mar 2025 11:59:51 +0100 Subject: [PATCH 14/29] Apply wmikita's suggestion --- builtin/settingtypes.txt | 2 +- builtin/sscsm_client/init.lua | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 6636c95d73..3e7ec92d8a 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1885,7 +1885,7 @@ 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 +enable_sscsm (Enable SSCSM) enum nowhere nowhere,singleplayer,localhost,lan,everywhere # Replaces the default main menu with a custom one. main_menu_script (Main menu script) [client] string diff --git a/builtin/sscsm_client/init.lua b/builtin/sscsm_client/init.lua index 5ef5531a77..88725b91de 100644 --- a/builtin/sscsm_client/init.lua +++ b/builtin/sscsm_client/init.lua @@ -1,4 +1,3 @@ - local scriptpath = core.get_builtin_path() local commonpath = scriptpath .. "common" .. DIR_DELIM local mypath = scriptpath .. "sscsm_client".. DIR_DELIM From 322b65435b22401fcd90edc3cdbcc829246cf9fa Mon Sep 17 00:00:00 2001 From: Desour Date: Mon, 24 Mar 2025 12:35:53 +0100 Subject: [PATCH 15/29] remove request* and event* functions rationale: * it's just boilerplate, as these just fill out the structs. can also be done at call site * they are usually only called at one place * it would lead to many includes (or at least forward defs) in sscsm_controller.h and sscsm_environment.h --- src/client/client.cpp | 6 +++++- src/script/sscsm/sscsm_controller.cpp | 7 ------- src/script/sscsm/sscsm_controller.h | 2 -- src/script/sscsm/sscsm_environment.cpp | 21 +++++---------------- src/script/sscsm/sscsm_environment.h | 3 --- 5 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index 0503721cd7..90f59756a9 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -541,7 +541,11 @@ void Client::step(float dtime) */ LocalPlayer *player = m_env.getLocalPlayer(); - m_sscsm_controller->eventOnStep(this, dtime); + { + auto event = std::make_unique(); + event->dtime = dtime; + m_sscsm_controller->runEvent(this, std::move(event)); + } // Step environment (also handles player controls) m_env.step(dtime); diff --git a/src/script/sscsm/sscsm_controller.cpp b/src/script/sscsm/sscsm_controller.cpp index 095726c11a..6f41162880 100644 --- a/src/script/sscsm/sscsm_controller.cpp +++ b/src/script/sscsm/sscsm_controller.cpp @@ -60,10 +60,3 @@ void SSCSMController::runEvent(Client *client, std::unique_ptr even answer = handleRequest(client, request.get()); } } - -void SSCSMController::eventOnStep(Client *client, f32 dtime) -{ - auto event = std::make_unique(); - event->dtime = dtime; - runEvent(client, std::move(event)); -} diff --git a/src/script/sscsm/sscsm_controller.h b/src/script/sscsm/sscsm_controller.h index 77a16aa75d..e2a7585ae0 100644 --- a/src/script/sscsm/sscsm_controller.h +++ b/src/script/sscsm/sscsm_controller.h @@ -32,6 +32,4 @@ public: // Handles requests until the next event is polled void runEvent(Client *client, std::unique_ptr event); - - void eventOnStep(Client *client, f32 dtime); }; diff --git a/src/script/sscsm/sscsm_environment.cpp b/src/script/sscsm/sscsm_environment.cpp index 90bebeccb1..8224ffdbb0 100644 --- a/src/script/sscsm/sscsm_environment.cpp +++ b/src/script/sscsm/sscsm_environment.cpp @@ -22,7 +22,11 @@ SSCSMEnvironment::~SSCSMEnvironment() = default; void *SSCSMEnvironment::run() { while (true) { - auto next_event = requestPollNextEvent(); + auto next_event = [&]{ + auto request = SSCSMRequestPollNextEvent{}; + auto answer = doRequest(std::move(request)); + return std::move(answer.next_event); + }(); if (dynamic_cast(next_event.get())) { break; @@ -67,18 +71,3 @@ void SSCSMEnvironment::setFatalError(const std::string &reason) request.reason = reason; doRequest(std::move(request)); } - -std::unique_ptr SSCSMEnvironment::requestPollNextEvent() -{ - auto request = SSCSMRequestPollNextEvent{}; - auto answer = doRequest(std::move(request)); - return std::move(answer.next_event); -} - -MapNode SSCSMEnvironment::requestGetNode(v3s16 pos) -{ - auto request = SSCSMRequestGetNode{}; - request.pos = pos; - auto answer = doRequest(std::move(request)); - return answer.node; -} diff --git a/src/script/sscsm/sscsm_environment.h b/src/script/sscsm/sscsm_environment.h index e929fab75e..fc072cbac7 100644 --- a/src/script/sscsm/sscsm_environment.h +++ b/src/script/sscsm/sscsm_environment.h @@ -50,7 +50,4 @@ public: exchange(serializeSSCSMRequest(std::forward(rq))) ); } - - std::unique_ptr requestPollNextEvent(); - MapNode requestGetNode(v3s16 pos); }; From 76b7a2c415a3ef77495d8ed01699a9534ad1d688 Mon Sep 17 00:00:00 2001 From: Desour Date: Mon, 24 Mar 2025 12:47:50 +0100 Subject: [PATCH 16/29] move dings test code out of builtin --- builtin/sscsm_client/init.lua | 11 ----------- src/client/client.cpp | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/builtin/sscsm_client/init.lua b/builtin/sscsm_client/init.lua index 88725b91de..9f73c9f7f4 100644 --- a/builtin/sscsm_client/init.lua +++ b/builtin/sscsm_client/init.lua @@ -20,14 +20,3 @@ assert(loadfile(commonpath .. "register.lua"))(builtin_shared) assert(loadfile(mypath .. "register.lua"))(builtin_shared) dofile(commonpath .. "after.lua") - - --- TODO: tmp - -local function dings() - print(dump(core.get_node_or_nil(vector.zero()))) - core.after(1, dings) -end ---~ core.after(0, dings) - -print(core.get_current_modname()) diff --git a/src/client/client.cpp b/src/client/client.cpp index 90f59756a9..e610fe3741 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -168,12 +168,26 @@ Client::Client( std::string enable_sscsm = g_settings->get("enable_sscsm"); if (enable_sscsm == "singleplayer") { //FIXME: enum auto event1 = std::make_unique(); + + // 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(); From cdfb08ed0f91c54ebd12c944c46c82fafbfc48c6 Mon Sep 17 00:00:00 2001 From: Desour Date: Mon, 24 Mar 2025 13:07:33 +0100 Subject: [PATCH 17/29] add MisbehavedSSCSMException type --- src/exceptions.h | 5 +++++ src/script/sscsm/sscsm_requests.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/exceptions.h b/src/exceptions.h index 902d1b571e..b4d0c021ca 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -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: diff --git a/src/script/sscsm/sscsm_requests.h b/src/script/sscsm/sscsm_requests.h index 39715237ee..539ddd6e1f 100644 --- a/src/script/sscsm/sscsm_requests.h +++ b/src/script/sscsm/sscsm_requests.h @@ -74,7 +74,7 @@ struct SSCSMRequestLog : public ISSCSMRequest SerializedSSCSMAnswer exec(Client *client) override { if (level >= LL_MAX) { - throw BaseException("Tried to log at non-existent level."); // TODO: choose better exception type + throw MisbehavedSSCSMException("Tried to log at non-existent level."); } else { g_logger.log(level, text); } From 05456059f17573261aced9730e03f2a5afa8e5f5 Mon Sep 17 00:00:00 2001 From: Desour Date: Mon, 24 Mar 2025 13:21:13 +0100 Subject: [PATCH 18/29] os.date and string.dump potentially unsafe --- src/script/cpp_api/s_security.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index f78c93de79..277330ae27 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -408,13 +408,13 @@ void ScriptApiSecurity::initializeSecuritySSCSM() "xpcall", // Completely safe libraries "coroutine", - "string", + "string", //TODO: string.dump? "table", "math", "bit", }; static const char *os_whitelist[] = { - "date", + "date", // TODO: can crash? () "difftime", "time" }; From 51de8318b09e6bf47c3358703335e3b5398fbb84 Mon Sep 17 00:00:00 2001 From: Desour Date: Mon, 24 Mar 2025 21:32:15 +0100 Subject: [PATCH 19/29] doc comment --- src/script/sscsm/sscsm_controller.h | 8 ++++++++ src/script/sscsm/sscsm_environment.h | 10 ++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/script/sscsm/sscsm_controller.h b/src/script/sscsm/sscsm_controller.h index e2a7585ae0..e96e4fea90 100644 --- a/src/script/sscsm/sscsm_controller.h +++ b/src/script/sscsm/sscsm_controller.h @@ -13,6 +13,14 @@ 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 m_thread; diff --git a/src/script/sscsm/sscsm_environment.h b/src/script/sscsm/sscsm_environment.h index fc072cbac7..1839ae8bda 100644 --- a/src/script/sscsm/sscsm_environment.h +++ b/src/script/sscsm/sscsm_environment.h @@ -14,8 +14,14 @@ #include "sscsm_irequest.h" #include "../scripting_sscsm.h" -// The thread that runs SSCSM code. -// Meant to be replaced by a sandboxed process. +/** 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 m_channel; From a2f98b187939e92b089d65317d15809fd4d0d19b Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 25 Mar 2025 13:32:16 +0100 Subject: [PATCH 20/29] remove os.date and string.dump --- builtin/common/item_s.lua | 8 -------- src/script/cpp_api/s_security.cpp | 28 +++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/builtin/common/item_s.lua b/builtin/common/item_s.lua index 8263067d6e..2761e41dd5 100644 --- a/builtin/common/item_s.lua +++ b/builtin/common/item_s.lua @@ -232,14 +232,6 @@ if core.set_read_node and core.set_push_node then core.set_read_node = nil local function push_node(content, param1, param2) - if false then -- TODO: tmp - print(dump(debug.traceback())) - --~ error() - for i = 0, 10 do - print("i="..i) - print(dump(debug.getinfo(i))) - end - end return {name = content2name[content], param1 = param1, param2 = param2} end core.set_push_node(push_node) diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 277330ae27..c67e040d9c 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -408,19 +408,33 @@ void ScriptApiSecurity::initializeSecuritySSCSM() "xpcall", // Completely safe libraries "coroutine", - "string", //TODO: string.dump? "table", "math", "bit", }; static const char *os_whitelist[] = { - "date", // TODO: can crash? () "difftime", "time" }; static const char *debug_whitelist[] = { "getinfo", // used by builtin and unset before mods load //TODO - "traceback" //TODO: is this fine, or does it print paths of C functions? + "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[] = { @@ -479,6 +493,14 @@ void ScriptApiSecurity::initializeSecuritySSCSM() 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"); From 5a422ca0c6526a89a54c61193f29e94887c558b0 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 25 Mar 2025 13:45:22 +0100 Subject: [PATCH 21/29] unset debug.getinfo --- .luacheckrc | 6 ++++++ builtin/common/register.lua | 5 +++-- builtin/common/strict.lua | 6 +++--- builtin/game/register.lua | 5 +++-- builtin/profiler/instrumentation.lua | 3 ++- builtin/sscsm_client/init.lua | 3 +++ src/script/cpp_api/s_security.cpp | 4 ++-- 7 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index ae6aa728ee..c98397085a 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -47,6 +47,12 @@ files["builtin/client/register.lua"] = { } } +files["builtin/sscsm_client/init.lua"] = { + globals = { + debug = {fields={"getinfo"}}, + } +} + files["builtin/common/math.lua"] = { globals = { "math", diff --git a/builtin/common/register.lua b/builtin/common/register.lua index cbeac7c64f..9ad8a16fb6 100644 --- a/builtin/common/register.lua +++ b/builtin/common/register.lua @@ -1,4 +1,5 @@ local builtin_shared = ... +local debug_getinfo = debug.getinfo do local default = {mod = "??", name = "??"} @@ -56,7 +57,7 @@ function builtin_shared.make_registration() core.callback_origins[func] = { -- may be nil or return nil mod = core.get_current_modname and core.get_current_modname() or "??", - name = debug.getinfo(1, "n").name or "??" + name = debug_getinfo(1, "n").name or "??" } end return t, registerfunc @@ -69,7 +70,7 @@ function builtin_shared.make_registration_reverse() core.callback_origins[func] = { -- may be nil or return nil mod = core.get_current_modname and core.get_current_modname() or "??", - name = debug.getinfo(1, "n").name or "??" + name = debug_getinfo(1, "n").name or "??" } end return t, registerfunc diff --git a/builtin/common/strict.lua b/builtin/common/strict.lua index b3c4ccce46..c2c673aa6f 100644 --- a/builtin/common/strict.lua +++ b/builtin/common/strict.lua @@ -1,4 +1,4 @@ -local getinfo, rawget, rawset = debug.getinfo, rawget, rawset +local debug_getinfo, rawget, rawset = debug.getinfo, rawget, rawset function core.global_exists(name) if type(name) ~= "string" then @@ -18,7 +18,7 @@ function meta:__newindex(name, value) if declared[name] then return end - local info = getinfo(2, "Sl") + local info = debug_getinfo(2, "Sl") if info ~= nil then local desc = ("%s:%d"):format(info.short_src, info.currentline) local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name) @@ -36,7 +36,7 @@ function meta:__index(name) if declared[name] then return end - local info = getinfo(2, "Sl") + local info = debug_getinfo(2, "Sl") if info == nil then return end diff --git a/builtin/game/register.lua b/builtin/game/register.lua index b832ccc6db..cc2f5fe980 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -1,5 +1,6 @@ local builtin_shared = ... local S = core.get_translator("__builtin") +local debug_getinfo = debug.getinfo -- -- Make raw registration functions inaccessible to anyone except this file @@ -548,7 +549,7 @@ function core.registered_on_player_hpchange(player, hp_change, reason) local func = core.registered_on_player_hpchanges.modifiers[i] hp_change, last = func(player, hp_change, reason) if type(hp_change) ~= "number" then - local debuginfo = debug.getinfo(func) + local debuginfo = debug_getinfo(func) error("The register_on_hp_changes function has to return a number at " .. debuginfo.short_src .. " line " .. debuginfo.linedefined) end @@ -570,7 +571,7 @@ function core.register_on_player_hpchange(func, modifier) end core.callback_origins[func] = { mod = core.get_current_modname() or "??", - name = debug.getinfo(1, "n").name or "??" + name = debug_getinfo(1, "n").name or "??" } end diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua index 2b34295daf..39947cc7ef 100644 --- a/builtin/profiler/instrumentation.lua +++ b/builtin/profiler/instrumentation.lua @@ -5,6 +5,7 @@ local format, ipairs, type = string.format, ipairs, type local core, get_current_modname = core, core.get_current_modname local profiler, sampler = ... +local debug_getinfo = debug.getinfo local instrument_builtin = core.settings:get_bool("instrument.builtin", false) @@ -67,7 +68,7 @@ local worldmods_path = regex_escape(core.get_worldpath()) local user_path = regex_escape(core.get_user_path()) local builtin_path = regex_escape(core.get_builtin_path()) local function generate_source_location(def) - local info = debug.getinfo(def.func) + local info = debug_getinfo(def.func) local modpath = regex_escape(core.get_modpath(def.mod) or "") local source = info.source if modpath ~= "" then diff --git a/builtin/sscsm_client/init.lua b/builtin/sscsm_client/init.lua index 9f73c9f7f4..2bfc58c759 100644 --- a/builtin/sscsm_client/init.lua +++ b/builtin/sscsm_client/init.lua @@ -20,3 +20,6 @@ assert(loadfile(commonpath .. "register.lua"))(builtin_shared) assert(loadfile(mypath .. "register.lua"))(builtin_shared) dofile(commonpath .. "after.lua") + +-- unset, as promised in initializeSecuritySSCSM() +debug.getinfo = nil diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index c67e040d9c..99d68b204a 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -311,7 +311,7 @@ void ScriptApiSecurity::initializeSecurityClient() "time" }; static const char *debug_whitelist[] = { - "getinfo", // used by builtin and unset before mods load + "getinfo", // used by builtin and unset before mods load <- FIXME: doesn't actually happen "traceback" }; @@ -417,7 +417,7 @@ void ScriptApiSecurity::initializeSecuritySSCSM() "time" }; static const char *debug_whitelist[] = { - "getinfo", // used by builtin and unset before mods load //TODO + "getinfo", // used by client builtin and unset before mods load "traceback" }; static const char *string_whitelist[] = { // all but string.dump From 84e8f705f9a64e8f4dde9a4fe127dc3c79a14366 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 25 Mar 2025 13:47:14 +0100 Subject: [PATCH 22/29] fix debug.getinfo not being unset in CPCSM (regression) was introduced in eeb6cab --- .luacheckrc | 2 +- builtin/client/init.lua | 3 +++ src/script/cpp_api/s_security.cpp | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index c98397085a..54cf9e3a2a 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -41,7 +41,7 @@ stds.menu_common = { }, } -files["builtin/client/register.lua"] = { +files["builtin/client/init.lua"] = { globals = { debug = {fields={"getinfo"}}, } diff --git a/builtin/client/init.lua b/builtin/client/init.lua index ee0f267db7..769fbe56cb 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -13,3 +13,6 @@ dofile(commonpath .. "information_formspecs.lua") dofile(clientpath .. "chatcommands.lua") dofile(clientpath .. "misc.lua") assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions + +-- unset, as promised in initializeSecurityClient() +debug.getinfo = nil diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 99d68b204a..36f6b045b8 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -311,7 +311,7 @@ void ScriptApiSecurity::initializeSecurityClient() "time" }; static const char *debug_whitelist[] = { - "getinfo", // used by builtin and unset before mods load <- FIXME: doesn't actually happen + "getinfo", // used by builtin and unset before mods load "traceback" }; From 1c610705e4c1315e2351a305d198fd9fc1d7d743 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 25 Mar 2025 13:51:57 +0100 Subject: [PATCH 23/29] fixup! Apply wmikita's suggestion --- src/defaultsettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 1cdd82c63d..c5bcc9fb09 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -121,7 +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("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"); From f92a296c0db4e2e1f51d75dbffb8f33ee96ff095 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 25 Mar 2025 14:06:22 +0100 Subject: [PATCH 24/29] add a little explanation to l_get_builtin_path --- src/script/lua_api/l_client.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 4df3439748..a8b6f14807 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -285,10 +285,14 @@ int ModApiClient::l_get_privilege_list(lua_State *L) int ModApiClient::l_get_builtin_path(lua_State *L) { std::string modname; - if (getScriptApiBase(L)->getType() == ScriptingType::Client) + if (getScriptApiBase(L)->getType() == ScriptingType::Client) { modname = BUILTIN_MOD_NAME; - else if (getScriptApiBase(L)->getType() == ScriptingType::SSCSM) + } 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; From 1d972cb1c48d8fe2cb514cab220e70ed21a1cf00 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 25 Mar 2025 16:20:09 +0100 Subject: [PATCH 25/29] add API and security doc --- doc/sscsm_api.md | 212 ++++++++++++++++++++++++++++++++++++++++++ doc/sscsm_security.md | 76 +++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 doc/sscsm_api.md create mode 100644 doc/sscsm_security.md diff --git a/doc/sscsm_api.md b/doc/sscsm_api.md new file mode 100644 index 0000000000..600ce75ea8 --- /dev/null +++ b/doc/sscsm_api.md @@ -0,0 +1,212 @@ +# Server-sent client-side modding (SSCSM) API reference + +**Warning:** SSCSM is very experimental. The API will break. Always start your +mod with a version check (using `core.get_version()`). + +In SSCSM, the server sends scripts to the client, which it executes +client-side (in a sandbox, see also `sscsm_security.md`). +As modder, you can add these scripts to your server-side mod, and tell the engine +to send them. + +Please refer to `lua_api.md` for server-side modding. +(And refer to `client_lua_api.md` for client-provided client-side modding (CPCSM).) + + + +## Loading mods + +### Paths + +SSCSM uses a virtual file system (just a dictionary of virtual paths (strings)) +to file contents (strings). + +Each mod's files have paths of the form `modname:foo/bla.lua`. +Please don't rely on this, use `core.get_modpath()` instead. + +The virtual file paths within a mod are meant to mimic the filepaths on the +server, for example `/common/foo.lua` gets sent as `modname:common/foo.lua`. + +The engine loads `modname:init.lua` for all mods, in server mod dependency order. + +There is client and server builtin (modnames are `*client_builtin*` and +`*server_builtin*`). The server builtin is sent from the server, like any other +SSCSM, and the client builtin is located on the client. + + +### Mod sending API + +Currently, you can not add any mods. There's only a small hardcoded preview script +in C++ which is loaded when you set `enable_sscsm` to `singleplayer`. + + + +## API + +Unless noted otherwise, these work the same as in the server modding API. + +### Global callbacks + +* `core.register_globalstep(function(dtime))` + + +### SSCSM-specific API + +* `core.get_node_or_nil(pos)` +* `core.get_content_id(name)` +* `core.get_name_from_content_id(id)` + + +### Util API + +* `core.log([level,] text)` +* `core.get_us_time()` + * Limited in precision. +* `core.parse_json(str[, nullvalue])` +* `core.write_json(data[, styled])` +* `core.is_yes(arg)` +* `core.compress(data, method, ...)` +* `core.decompress(data, method, ...)` +* `core.encode_base64(string)` +* `core.decode_base64(string)` +* `core.get_version()` +* `core.sha1(string, raw)` +* `core.sha256(string, raw)` +* `core.colorspec_to_colorstring(colorspec)` +* `core.colorspec_to_bytes(colorspec)` +* `core.colorspec_to_table(colorspec)` +* `core.time_to_day_night_ratio(time_of_day)` +* `core.get_last_run_mod()` +* `core.set_last_run_mod(modname)` +* `core.urlencode(value)` + + +### Other + +* `core.get_current_modname()` +* `core.get_modpath(modname)` + + +### Builtin helpers + +* `math.*` additions + +* `vector.*` + +* `core.global_exists(name)` + +* `core.serialize(value)` +* `core.deserialize(str, safe)` + +* `dump2(obj, name, dumped)` +* `dump(obj, dumped)` +* `string.*` additions +* `table.*` additions +* `core.formspec_escape(text)` +* `core.hypertext_escape(text)` +* `core.wrap_text(str, limit, as_table)` +* `core.explode_table_event(evt)` +* `core.explode_textlist_event(evt)` +* `core.explode_scrollbar_event(evt)` +* `core.rgba(r, g, b, a)` +* `core.pos_to_string(pos, decimal_places)` +* `core.string_to_pos(value)` +* `core.string_to_area(value, relative_to)` +* `core.get_color_escape_sequence(color)` +* `core.get_background_escape_sequence(color)` +* `core.colorize(color, message)` +* `core.strip_foreground_colors(str)` +* `core.strip_background_colors(str)` +* `core.strip_colors(str)` +* `core.translate(textdomain, str, ...)` +* `core.translate_n(textdomain, str, str_plural, n, ...)` +* `core.get_translator(textdomain)` +* `core.pointed_thing_to_face_pos(placer, pointed_thing)` +* `core.string_to_privs(str, delim)` +* `core.privs_to_string(privs, delim)` +* `core.is_nan(number)` +* `core.parse_relative_number(arg, relative_to)` +* `core.parse_coordinates(x, y, z, relative_to)` + +* `core.inventorycube(img1, img2, img3)` +* `core.dir_to_facedir(dir, is6d)` +* `core.facedir_to_dir(facedir)` +* `core.dir_to_fourdir(dir)` +* `core.fourdir_to_dir(fourdir)` +* `core.dir_to_wallmounted(dir)` +* `core.wallmounted_to_dir(wallmounted)` +* `core.dir_to_yaw(dir)` +* `core.yaw_to_dir(yaw)` +* `core.is_colored_paramtype(ptype)` +* `core.strip_param2_color(param2, paramtype2)` + +* `core.after(time, func, ...)` + + +### Lua standard library + +* `assert` +* `collectgarbage` +* `error` +* `getfenv` +* `ipairs` +* `next` +* `pairs` +* `pcall` +* `rawequal` +* `rawget` +* `rawset` +* `select` +* `setfenv` +* `getmetatable` +* `setmetatable` +* `tonumber` +* `tostring` +* `type` +* `unpack` +* `_VERSION` +* `xpcall` +* `dofile` + * Overwritten. +* `load` + * Overwritten. +* `loadfile` + * Overwritten. +* `loadstring` + * Overwritten. +* `coroutine.*` +* `table.*` +* `math.*` +* `string.*` + * except `string.dump` +* `os.difftime` +* `os.time` +* `os.clock` + * Reduced precision. +* `debug.traceback` + + +### LuaJIT `jit` library + +* `jit.arch` +* `jit.flush` +* `jit.off` +* `jit.on` +* `jit.opt` +* `jit.os` +* `jit.status` +* `jit.version` +* `jit.version_num` + + +### Bit library + +* `bit.*` + + +### API only for client builtin + +* `core.get_builtin_path()` + * Returns path, depending on which builtin currently loads, or `nil`. +* `debug.getinfo(...)` +* `INIT` + * Is `"sscsm"`. diff --git a/doc/sscsm_security.md b/doc/sscsm_security.md new file mode 100644 index 0000000000..acebad00d2 --- /dev/null +++ b/doc/sscsm_security.md @@ -0,0 +1,76 @@ +# SSCSM security + + +## Threat model + +* SSCSM scripts come from the server (potential malicious actor). We are the client. +* Authenticity of server is not given (our networking is not secure). So we have + to expect anyone who can send us UDP packets to the appropriate IP address to be + able to act on behalf of the server. +* The server may not tamper with, or get access to information of, anything besides + the stuff explicitly made accessible via the modding API (i.e. gameplay relevant + stuff, like map, node definitions, ...). + In particular, this excludes for (non-exhaustive) example files, file paths, + and settings. +* DOS is not an issue (as it is already easily possible to DOS a client). +* We already have an API via network packets (see `networkprotocol.h`). + This acts as upper bound: Every SSCSM API function could instead be a network + packet endpoint. There are no efforts to make SSCSM more secure than this. + + +## Non-binary `enable_sscsm` setting + +The `enable_sscsm` setting does not just allow en-/disabling SSCSM, it also allows +limiting on what sort of servers to enable SSCSM. Options are `nowhere`, `singleplayer`, +`localhost` (or singleplayer), `lan` (or lower), and everywhere. +On options `localhost` and lower, we know that (anyone who acts on the behalf of) +the server runs on the same machine, and the risk of it being malicious is pretty +much zero. + +Until sufficient security measures are in place, users are disallowed to set this +setting to anything higher than `localhost`. + + +## Lua sandbox + +* We execute only Lua scripts, in a Lua sandbox. +* See also `initializeSecuritySSCSM()`. +* We do not trust the Lua implementation to not have bugs. => Additional process + isolation layer as fallback. + + +## Process isolation + +* Not yet implemented. +* Separate SSCSM process. +* Sandboxing: + * Linux: Uses SECCOMP. + * ... (FIXME: write down stuff when you implement) + + +## Limit where we call into SSCSM + +* Even if the Lua sandbox and/or the process isolation are bug-free, the main + process client code can still be vulnerable. Consider this example: + * Client has an inventorylist A. + * User moves an item. + * SSCSM gets called (callback when item is moved). + * SSCSM can do anything now. It decides to delete A, then returns. + * Client still has reference to A on stack, tries to access it. + * => Use-after-free. +* To avoid these sort of issues, we only give control-flow to SSCSM in few special + places. + In particular, this includes packet handlers, and the client's `step()` function. +* In these places, the client already does not assume anything about the current + state (e.g. that an inventory exists). +* This makes sure that SSCSM API calls can also just happen in these places. + In packet handlers, the server can already cause arbitrary network API "calls" + to happen. Hence, new SSCSM API calls here do not lead to new vulnerabilities + that a network API would not cause as well. + + +## No precise clocks + +To mitigate time-based side-channel attacks, all available clock API functions +(`os.clock()` and `core.get_us_time()`) only have a precision of +`SSCSM_CLOCK_RESOLUTION_US` (20) us. From 52512fc8d11679fdaf1a2306019603e8f34c56fe Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 25 Mar 2025 16:33:17 +0100 Subject: [PATCH 26/29] print we also have --- doc/sscsm_api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/sscsm_api.md b/doc/sscsm_api.md index 600ce75ea8..a79de7a1f9 100644 --- a/doc/sscsm_api.md +++ b/doc/sscsm_api.md @@ -152,6 +152,7 @@ Unless noted otherwise, these work the same as in the server modding API. * `next` * `pairs` * `pcall` +* `print` * `rawequal` * `rawget` * `rawset` From d620575a8accdf6608fce1d581e6c59b59fff98e Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 25 Mar 2025 16:39:38 +0100 Subject: [PATCH 27/29] add missing oveerride --- src/script/sscsm/sscsm_environment.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/sscsm/sscsm_environment.h b/src/script/sscsm/sscsm_environment.h index 1839ae8bda..79313dc1dc 100644 --- a/src/script/sscsm/sscsm_environment.h +++ b/src/script/sscsm/sscsm_environment.h @@ -39,7 +39,7 @@ class SSCSMEnvironment : public Thread public: SSCSMEnvironment(std::shared_ptr channel); - ~SSCSMEnvironment(); + ~SSCSMEnvironment() override; SSCSMScripting *getScript() { return m_script.get(); } From 09aa32234129119fa188dbad4e65a6d6cebe87cc Mon Sep 17 00:00:00 2001 From: Desour Date: Thu, 10 Apr 2025 19:40:37 +0200 Subject: [PATCH 28/29] reorder these two lines --- src/client/client.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index aabb07a554..6ed7ef8721 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -45,6 +45,8 @@ class NodeDefManager; class ParticleManager; class RenderingEngine; class SingleMediaDownloader; +class ClientScripting; +class SSCSMController; struct ChatMessage; struct ClientDynamicInfo; struct ClientEvent; @@ -53,8 +55,6 @@ struct MapNode; struct PlayerControl; struct PointedThing; struct ItemVisualsManager; -class ClientScripting; -class SSCSMController; struct ModVFS; namespace scene { From 3ac8e4e974dd23e9034f4cb1df90830d3e02e2e0 Mon Sep 17 00:00:00 2001 From: Desour Date: Thu, 29 May 2025 22:10:09 +0200 Subject: [PATCH 29/29] add context annotation to enable_sscsm setting --- builtin/settingtypes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 3e7ec92d8a..f1af101a2c 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1885,7 +1885,7 @@ enable_client_modding (Client modding) [client] bool false # Where to enable server-sent client-side modding (SSCSM). # Warning: Experimental. -enable_sscsm (Enable SSCSM) enum nowhere nowhere,singleplayer,localhost,lan,everywhere +enable_sscsm (Enable SSCSM) [client] enum nowhere nowhere,singleplayer,localhost,lan,everywhere # Replaces the default main menu with a custom one. main_menu_script (Main menu script) [client] string