From dd0070a6b8a28e2fe2ac591b3ea96813ba9ffcb0 Mon Sep 17 00:00:00 2001 From: ROllerozxa Date: Sun, 9 Feb 2025 18:09:07 +0100 Subject: [PATCH] Expose client version information in non-debug builds (#15708) Co-authored-by: SmallJoker Co-authored-by: Lars Mueller Co-authored-by: sfan5 --- builtin/game/misc_s.lua | 22 +++++++++++ doc/lua_api.md | 28 +++++++++++++- games/devtest/.luacheckrc | 2 +- games/devtest/mods/unittests/get_version.lua | 16 -------- games/devtest/mods/unittests/init.lua | 2 +- games/devtest/mods/unittests/version.lua | 40 ++++++++++++++++++++ src/network/networkprotocol.cpp | 1 + src/script/lua_api/l_server.cpp | 8 ++-- 8 files changed, 95 insertions(+), 24 deletions(-) delete mode 100644 games/devtest/mods/unittests/get_version.lua create mode 100644 games/devtest/mods/unittests/version.lua diff --git a/builtin/game/misc_s.lua b/builtin/game/misc_s.lua index 1e9b6d952..e64134e15 100644 --- a/builtin/game/misc_s.lua +++ b/builtin/game/misc_s.lua @@ -112,3 +112,25 @@ if core.set_push_moveresult1 then end) core.set_push_moveresult1 = nil end + +-- Protocol version table +-- see also src/network/networkprotocol.cpp +core.protocol_versions = { + ["5.0.0"] = 37, + ["5.1.0"] = 38, + ["5.2.0"] = 39, + ["5.3.0"] = 39, + ["5.4.0"] = 39, + ["5.5.0"] = 40, + ["5.6.0"] = 41, + ["5.7.0"] = 42, + ["5.8.0"] = 43, + ["5.9.0"] = 44, + ["5.9.1"] = 45, + ["5.10.0"] = 46, + ["5.11.0"] = 47, +} + +setmetatable(core.protocol_versions, {__newindex = function() + error("core.protocol_versions is read-only") +end}) diff --git a/doc/lua_api.md b/doc/lua_api.md index a98828080..ba72e97e9 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5578,7 +5578,7 @@ Utilities * It's possible that multiple Luanti instances are running at the same time, which may lead to corruption if you are not careful. * `core.is_singleplayer()` -* `core.features`: Table containing API feature flags +* `core.features`: Table containing *server-side* API feature flags ```lua { @@ -5693,6 +5693,7 @@ Utilities ``` * `core.has_feature(arg)`: returns `boolean, missing_features` + * checks for *server-side* feature availability * `arg`: string or table in format `{foo=true, bar=true}` * `missing_features`: `{foo=true, bar=true}` * `core.get_player_information(player_name)`: Table containing information @@ -5714,17 +5715,40 @@ Utilities min_jitter = 0.01, -- minimum packet time jitter max_jitter = 0.5, -- maximum packet time jitter avg_jitter = 0.03, -- average packet time jitter + + -- The version information is provided by the client and may be spoofed + -- or inconsistent in engine forks. You must not use this for checking + -- feature availability of clients. Instead, do use the fields + -- `protocol_version` and `formspec_version` where it matters. + -- Use `core.protocol_versions` to map Luanti versions to protocol versions. + -- This version string is only suitable for analysis purposes. + version_string = "0.4.9-git", -- full version string + -- the following information is available in a debug build only!!! -- DO NOT USE IN MODS --serialization_version = 26, -- serialization version used by client --major = 0, -- major version number --minor = 4, -- minor version number --patch = 10, -- patch version number - --version_string = "0.4.9-git", -- full version string --state = "Active" -- current client state } ``` +* `core.protocol_versions`: + * Table mapping Luanti versions to corresponding protocol versions for modder convenience. + * For example, to check whether a client has at least the feature set + of Luanti 5.8.0 or newer, you could do: + `core.get_player_information(player_name).protocol_version >= core.protocol_versions["5.8.0"]` + * (available since 5.11) + + ```lua + { + [version string] = protocol version at time of release + -- every major and minor version has an entry + -- patch versions only for the first release whose protocol version is not already present in the table + } + ``` + * `core.get_player_window_information(player_name)`: ```lua diff --git a/games/devtest/.luacheckrc b/games/devtest/.luacheckrc index c5a7119a4..2ef36d209 100644 --- a/games/devtest/.luacheckrc +++ b/games/devtest/.luacheckrc @@ -31,7 +31,7 @@ read_globals = { "PcgRandom", string = {fields = {"split", "trim"}}, - table = {fields = {"copy", "getn", "indexof", "insert_all"}}, + table = {fields = {"copy", "getn", "indexof", "insert_all", "key_value_swap"}}, math = {fields = {"hypot", "round"}}, } diff --git a/games/devtest/mods/unittests/get_version.lua b/games/devtest/mods/unittests/get_version.lua deleted file mode 100644 index 9903ac381..000000000 --- a/games/devtest/mods/unittests/get_version.lua +++ /dev/null @@ -1,16 +0,0 @@ - -unittests.register("test_get_version", function() - local version = core.get_version() - assert(type(version) == "table") - assert(type(version.project) == "string") - assert(type(version.string) == "string") - assert(type(version.proto_min) == "number") - assert(type(version.proto_max) == "number") - assert(version.proto_max >= version.proto_min) - assert(type(version.is_dev) == "boolean") - if version.is_dev then - assert(type(version.hash) == "string") - else - assert(version.hash == nil) - end -end) diff --git a/games/devtest/mods/unittests/init.lua b/games/devtest/mods/unittests/init.lua index a971632c9..22057f26a 100644 --- a/games/devtest/mods/unittests/init.lua +++ b/games/devtest/mods/unittests/init.lua @@ -192,7 +192,7 @@ dofile(modpath .. "/crafting.lua") dofile(modpath .. "/itemdescription.lua") dofile(modpath .. "/async_env.lua") dofile(modpath .. "/entity.lua") -dofile(modpath .. "/get_version.lua") +dofile(modpath .. "/version.lua") dofile(modpath .. "/itemstack_equals.lua") dofile(modpath .. "/content_ids.lua") dofile(modpath .. "/metadata.lua") diff --git a/games/devtest/mods/unittests/version.lua b/games/devtest/mods/unittests/version.lua new file mode 100644 index 000000000..baf4520a4 --- /dev/null +++ b/games/devtest/mods/unittests/version.lua @@ -0,0 +1,40 @@ +unittests.register("test_get_version", function() + local version = core.get_version() + assert(type(version) == "table") + assert(type(version.project) == "string") + assert(type(version.string) == "string") + assert(type(version.proto_min) == "number") + assert(type(version.proto_max) == "number") + assert(version.proto_max >= version.proto_min) + assert(type(version.is_dev) == "boolean") + if version.is_dev then + assert(type(version.hash) == "string") + else + assert(version.hash == nil) + end +end) + +unittests.register("test_protocol_version", function(player) + local info = core.get_player_information(player:get_player_name()) + + local maxver = 0 + for _, v in pairs(core.protocol_versions) do + maxver = math.max(maxver, v) + end + assert(maxver > 0) -- table must contain something valid + + -- If the client is older than a known version then it's pointless. + if info.protocol_version < maxver then + core.log("warning", "test_protocol_version: client is outdated, skipping test!") + return + end + local info_server = core.get_version() + if info.version_string ~= (info_server.hash or info_server.string) then + core.log("warning", "test_protocol_version: client is not the same version. False-positive possible.") + end + + -- The protocol version the client and server agreed on must exist in the table. + local match = table.key_value_swap(core.protocol_versions)[info.protocol_version] + assert(match ~= nil) + print(string.format("client proto matched: %s sent: %s", match, info.version_string)) +end, {player = true}) diff --git a/src/network/networkprotocol.cpp b/src/network/networkprotocol.cpp index f77e85d3c..85096930f 100644 --- a/src/network/networkprotocol.cpp +++ b/src/network/networkprotocol.cpp @@ -64,6 +64,7 @@ [scheduled bump for 5.11.0] */ +// Note: Also update core.protocol_versions in builtin when bumping const u16 LATEST_PROTOCOL_VERSION = 47; // See also formspec [Version History] in doc/lua_api.md diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 061dbb206..74ee5e5c9 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -239,6 +239,10 @@ int ModApiServer::l_get_player_information(lua_State *L) lua_pushstring(L, info.lang_code.c_str()); lua_settable(L, table); + lua_pushstring(L, "version_string"); + lua_pushstring(L, info.vers_string.c_str()); + lua_settable(L, table); + #ifndef NDEBUG lua_pushstring(L,"serialization_version"); lua_pushnumber(L, info.ser_vers); @@ -256,10 +260,6 @@ int ModApiServer::l_get_player_information(lua_State *L) lua_pushnumber(L, info.patch); lua_settable(L, table); - lua_pushstring(L,"version_string"); - lua_pushstring(L, info.vers_string.c_str()); - lua_settable(L, table); - lua_pushstring(L,"state"); lua_pushstring(L, ClientInterface::state2Name(info.state).c_str()); lua_settable(L, table);