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