diff --git a/CMakeLists.txt b/CMakeLists.txt index 0806f461c6bab..1259919894894 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,6 +297,13 @@ endif() add_subdirectory(lib/tiniergltf) +# Builtin +add_subdirectory(builtin) + +list(TRANSFORM BUILTIN_SRCS + PREPEND "${BUILTIN_BASE_PATH}/" + OUTPUT_VARIABLE BUILTIN_SRCS_ABS) + # Subdirectories # Be sure to add all relevant definitions above this add_subdirectory(src) diff --git a/builtin/CMakeLists.txt b/builtin/CMakeLists.txt new file mode 100644 index 0000000000000..a173d37e03910 --- /dev/null +++ b/builtin/CMakeLists.txt @@ -0,0 +1,24 @@ +add_subdirectory(async) +add_subdirectory(client) +add_subdirectory(common) +add_subdirectory(emerge) +add_subdirectory(fstk) +add_subdirectory(game) +add_subdirectory(mainmenu) +add_subdirectory(pause_menu) +add_subdirectory(profiler) + +set(BUILTIN_SRCS + init.lua + ${BUILTIN_ASYNC_SRCS} + ${BUILTIN_CLIENT_SRCS} + ${BUILTIN_COMMON_SRCS} + ${BUILTIN_EMERGE_SRCS} + ${BUILTIN_FSTK_SRCS} + ${BUILTIN_GAME_SRCS} + ${BUILTIN_MAINMENU_SRCS} + ${BUILTIN_PAUSE_MENU_SRCS} + ${BUILTIN_PROFILER_SRCS} + PARENT_SCOPE) + +set(BUILTIN_BASE_PATH ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) diff --git a/builtin/async/CMakeLists.txt b/builtin/async/CMakeLists.txt new file mode 100644 index 0000000000000..eb6c555dbfa9e --- /dev/null +++ b/builtin/async/CMakeLists.txt @@ -0,0 +1,4 @@ +set(BUILTIN_ASYNC_SRCS + async/game.lua + async/mainmenu.lua + PARENT_SCOPE) diff --git a/builtin/client/CMakeLists.txt b/builtin/client/CMakeLists.txt new file mode 100644 index 0000000000000..b7271543ac9ae --- /dev/null +++ b/builtin/client/CMakeLists.txt @@ -0,0 +1,6 @@ +set(BUILTIN_CLIENT_SRCS + client/chatcommands.lua + client/init.lua + client/misc.lua + client/register.lua + PARENT_SCOPE) diff --git a/builtin/common/CMakeLists.txt b/builtin/common/CMakeLists.txt new file mode 100644 index 0000000000000..50247e2cbb62b --- /dev/null +++ b/builtin/common/CMakeLists.txt @@ -0,0 +1,19 @@ +add_subdirectory(settings) + +set(BUILTIN_COMMON_SRCS + common/after.lua + common/chatcommands.lua + common/filterlist.lua + common/information_formspecs.lua + common/item_s.lua + common/math.lua + common/menu.lua + common/metatable.lua + common/misc_helpers.lua + common/mod_storage.lua + common/register.lua + common/serialize.lua + common/strict.lua + common/vector.lua + ${BUILTIN_COMMON_SETTINGS_SRCS} + PARENT_SCOPE) diff --git a/builtin/common/settings/CMakeLists.txt b/builtin/common/settings/CMakeLists.txt new file mode 100644 index 0000000000000..c7efe6ace6f88 --- /dev/null +++ b/builtin/common/settings/CMakeLists.txt @@ -0,0 +1,9 @@ +set(BUILTIN_COMMON_SETTINGS_SRCS + common/settings/components.lua + common/settings/dlg_change_mapgen_flags.lua + common/settings/dlg_settings.lua + common/settings/generate_from_settingtypes.lua + common/settings/init.lua + common/settings/settingtypes.lua + common/settings/shadows_component.lua + PARENT_SCOPE) diff --git a/builtin/emerge/CMakeLists.txt b/builtin/emerge/CMakeLists.txt new file mode 100644 index 0000000000000..2501584fb24a3 --- /dev/null +++ b/builtin/emerge/CMakeLists.txt @@ -0,0 +1,5 @@ +set(BUILTIN_EMERGE_SRCS + emerge/env.lua + emerge/init.lua + emerge/register.lua + PARENT_SCOPE) diff --git a/builtin/fstk/CMakeLists.txt b/builtin/fstk/CMakeLists.txt new file mode 100644 index 0000000000000..ee3698abeb323 --- /dev/null +++ b/builtin/fstk/CMakeLists.txt @@ -0,0 +1,6 @@ +set(BUILTIN_FSTK_SRCS + fstk/buttonbar.lua + fstk/dialog.lua + fstk/tabview.lua + fstk/ui.lua + PARENT_SCOPE) diff --git a/builtin/game/CMakeLists.txt b/builtin/game/CMakeLists.txt new file mode 100644 index 0000000000000..42b4b0951697e --- /dev/null +++ b/builtin/game/CMakeLists.txt @@ -0,0 +1,23 @@ +set(BUILTIN_GAME_SRCS + game/async.lua + game/auth.lua + game/chat.lua + game/constants.lua + game/death_screen.lua + game/deprecated.lua + game/detached_inventory.lua + game/falling.lua + game/features.lua + game/forceloading.lua + game/hud.lua + game/init.lua + game/item_entity.lua + game/item.lua + game/knockback.lua + game/misc.lua + game/misc_s.lua + game/privileges.lua + game/register.lua + game/static_spawn.lua + game/voxelarea.lua + PARENT_SCOPE) diff --git a/builtin/mainmenu/CMakeLists.txt b/builtin/mainmenu/CMakeLists.txt new file mode 100644 index 0000000000000..dff77820f9b10 --- /dev/null +++ b/builtin/mainmenu/CMakeLists.txt @@ -0,0 +1,26 @@ +add_subdirectory(content) + +set(BUILTIN_MAINMENU_SRCS + mainmenu/async_event.lua + mainmenu/common.lua + mainmenu/credits.json + mainmenu/dlg_clients_list.lua + mainmenu/dlg_config_world.lua + mainmenu/dlg_create_world.lua + mainmenu/dlg_delete_content.lua + mainmenu/dlg_delete_world.lua + mainmenu/dlg_rebind_keys.lua + mainmenu/dlg_register.lua + mainmenu/dlg_reinstall_mtg.lua + mainmenu/dlg_rename_modpack.lua + mainmenu/dlg_server_list_mods.lua + mainmenu/dlg_version_info.lua + mainmenu/game_theme.lua + mainmenu/init.lua + mainmenu/serverlistmgr.lua + mainmenu/tab_about.lua + mainmenu/tab_content.lua + mainmenu/tab_local.lua + mainmenu/tab_online.lua + ${BUILTIN_MAINMENU_CONTENT_SRCS} + PARENT_SCOPE) diff --git a/builtin/mainmenu/content/CMakeLists.txt b/builtin/mainmenu/content/CMakeLists.txt new file mode 100644 index 0000000000000..e05a13095acde --- /dev/null +++ b/builtin/mainmenu/content/CMakeLists.txt @@ -0,0 +1,11 @@ +set(BUILTIN_MAINMENU_CONTENT_SRCS + mainmenu/content/contentdb.lua + mainmenu/content/dlg_contentdb.lua + mainmenu/content/dlg_install.lua + mainmenu/content/dlg_overwrite.lua + mainmenu/content/dlg_package.lua + mainmenu/content/init.lua + mainmenu/content/pkgmgr.lua + mainmenu/content/screenshots.lua + mainmenu/content/update_detector.lua + PARENT_SCOPE) diff --git a/builtin/pause_menu/CMakeLists.txt b/builtin/pause_menu/CMakeLists.txt new file mode 100644 index 0000000000000..b49eda374b657 --- /dev/null +++ b/builtin/pause_menu/CMakeLists.txt @@ -0,0 +1,4 @@ +set(BUILTIN_PAUSE_MENU_SRCS + pause_menu/init.lua + pause_menu/register.lua + PARENT_SCOPE) diff --git a/builtin/profiler/CMakeLists.txt b/builtin/profiler/CMakeLists.txt new file mode 100644 index 0000000000000..0feb3c037a1a7 --- /dev/null +++ b/builtin/profiler/CMakeLists.txt @@ -0,0 +1,6 @@ +set(BUILTIN_PROFILER_SRCS + profiler/init.lua + profiler/instrumentation.lua + profiler/reporter.lua + profiler/sampling.lua + PARENT_SCOPE) diff --git a/cmake/Modules/GenerateBuiltinFilesList.cmake b/cmake/Modules/GenerateBuiltinFilesList.cmake new file mode 100644 index 0000000000000..81cd550e6be8f --- /dev/null +++ b/cmake/Modules/GenerateBuiltinFilesList.cmake @@ -0,0 +1,19 @@ +# Compute sha256 digests of builtin files + +set(BUILTIN_SHA256S "") +foreach(P_REL ${BUILTIN_SRCS}) + set(P_ABS "${BUILTIN_BASE_PATH}/${P_REL}") + + file(SHA256 ${P_ABS} H) + + list(APPEND BUILTIN_SHA256S "{\"${P_REL}\", \"${H}\"}") +endforeach() + +list(JOIN BUILTIN_SHA256S ",\n" BUILTIN_SHA256S_INITIALIZER_LIST) + +configure_file( + "${PROJECT_SOURCE_DIR}/builtin_files.cpp.in" + "${PROJECT_BINARY_DIR}/builtin_files.cpp" +) +# touch it, because configure_file doesn't if it doesn't change +file(TOUCH_NOCREATE "${PROJECT_BINARY_DIR}/builtin_files.cpp") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0813a11dc7c0e..c0e884bdd9113 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -395,6 +395,26 @@ add_custom_target(GenerateVersion WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") +# Command for builtin_files.cpp +add_custom_command( + OUTPUT "${PROJECT_BINARY_DIR}/builtin_files.cpp" + COMMAND ${CMAKE_COMMAND} + -D "PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + -D "PROJECT_BINARY_DIR=${PROJECT_BINARY_DIR}" + -D "BUILTIN_BASE_PATH=${BUILTIN_BASE_PATH}" + -D "BUILTIN_SRCS=\"${BUILTIN_SRCS}\"" # (relative paths, so improper escaping should work) + -P "${CMAKE_SOURCE_DIR}/cmake/Modules/GenerateBuiltinFilesList.cmake" + DEPENDS + ${BUILTIN_SRCS_ABS} + "${PROJECT_SOURCE_DIR}/builtin_files.cpp.in" + "${CMAKE_SOURCE_DIR}/cmake/Modules/GenerateBuiltinFilesList.cmake" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + +# Target to generate just builtin_files.cpp (used by util/ci/clang-tidy.sh) +add_custom_target(GenerateBuiltinFilesCpp + DEPENDS "${PROJECT_BINARY_DIR}/builtin_files.cpp") + + add_subdirectory(threading) add_subdirectory(content) add_subdirectory(database) @@ -438,6 +458,7 @@ set(independent_SRCS texture_override.cpp tileanimation.cpp tool.cpp + ${PROJECT_BINARY_DIR}/builtin_files.cpp ${common_network_SRCS} ${content_SRCS} ${database_SRCS} @@ -610,7 +631,7 @@ endif() add_library(EngineCommon OBJECT ${independent_SRCS} ) -add_dependencies(EngineCommon GenerateVersion) +add_dependencies(EngineCommon GenerateVersion GenerateBuiltinFilesCpp) target_link_libraries(EngineCommon sha256 ) diff --git a/src/builtin_files.cpp.in b/src/builtin_files.cpp.in new file mode 100644 index 0000000000000..ba689bff584c9 --- /dev/null +++ b/src/builtin_files.cpp.in @@ -0,0 +1,11 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 Luanti contributors + +// Filled in by the build system + +#include "builtin_files.h" + +extern const std::unordered_map g_builtin_file_sha256_map = { +@BUILTIN_SHA256S_INITIALIZER_LIST@ +}; diff --git a/src/builtin_files.h b/src/builtin_files.h new file mode 100644 index 0000000000000..ba5b30b6c29bc --- /dev/null +++ b/src/builtin_files.h @@ -0,0 +1,10 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 Luanti contributors + +#pragma once + +#include +#include + +extern const std::unordered_map g_builtin_file_sha256_map; diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 6ed43a25832f0..55e9df0b4adf1 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -284,7 +284,7 @@ void ScriptApiBase::loadModFromMemory(const std::string &mod_name) int error_handler = PUSH_ERROR_HANDLER(L); - bool ok = ScriptApiSecurity::safeLoadString(L, *contents, chunk_name.c_str()); + bool ok = ScriptApiSecurity::safeLoadFileContent(L, *contents, chunk_name.c_str()); if (ok) ok = !lua_pcall(L, 0, 0, error_handler); if (!ok) { diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 834650fdc6648..ed304a0d3e5e0 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -6,6 +6,9 @@ #include "lua_api/l_base.h" #include "filesys.h" #include "porting.h" +#include "util/hashing.h" +#include "util/hex.h" +#include "builtin_files.h" #include "server.h" #if CHECK_CLIENT_BUILD() #include "client/client.h" @@ -441,72 +444,66 @@ bool ScriptApiSecurity::safeLoadString(lua_State *L, std::string_view code, cons return true; } +bool ScriptApiSecurity::safeLoadFileContent(lua_State *L, std::string_view code, const char *chunk_name) +{ + // Skip the shebang line (but keep line-ending) + if (!code.empty() && code[0] == '#') { + size_t nl = code.find('\n', 1); + if (nl == code.npos) + nl = code.size(); + code = code.substr(nl); + } + + return safeLoadString(L, code, chunk_name); +} + bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char *display_name) { - FILE *fp; - char *chunk_name; - if (!display_name) - display_name = path; if (!path) { - fp = stdin; - chunk_name = const_cast("=stdin"); - } else { - fp = std::fopen(path, "rb"); - if (!fp) { - lua_pushfstring(L, "%s: %s", path, strerror(errno)); - return false; - } - size_t len = strlen(display_name) + 2; - chunk_name = new char[len]; - snprintf(chunk_name, len, "@%s", display_name); + lua_pushstring(L, "Loading code from stdin is not supported."); + return false; } - size_t start = 0; - int c = std::getc(fp); - if (c == '#') { - // Skip the shebang line (but keep line-ending) - while (c != EOF && c != '\n') - c = std::getc(fp); - start = std::ftell(fp) - 1; - } + if (!display_name) + display_name = path; + + std::string chunk_name = std::string("@") + display_name; // Read the file - int ret = std::fseek(fp, 0, SEEK_END); - if (ret) { - lua_pushfstring(L, "%s: %s", path, strerror(errno)); - if (path) { - std::fclose(fp); - delete [] chunk_name; - } + std::string code; + if (!fs::ReadFile(path, code)) { + lua_pushfstring(L, "%s: %s", path, "Failed reading file."); return false; } - size_t size = std::ftell(fp) - start; - std::string code(size, '\0'); - ret = std::fseek(fp, start, SEEK_SET); - if (ret) { - lua_pushfstring(L, "%s: %s", path, strerror(errno)); - if (path) { - std::fclose(fp); - delete [] chunk_name; - } - return false; - } + // Check sha256 if it's a builtin file + do { + assert(path != nullptr); + std::string path_abs = fs::AbsolutePathPartial(path); + std::string builtin_path_abs = + fs::AbsolutePathPartial(Server::getBuiltinLuaPath()) + DIR_DELIM; + if (path_abs.empty() || builtin_path_abs.empty()) + break; - size_t num_read = std::fread(&code[0], 1, size, fp); - if (path) - std::fclose(fp); - if (num_read != size) { - lua_pushliteral(L, "Error reading file to load."); - if (path) - delete [] chunk_name; - return false; - } + if (std::string_view(path_abs).substr(0, builtin_path_abs.size()) != builtin_path_abs) + break; // not in builtin + + auto path_local = std::string_view(path_abs).substr(builtin_path_abs.size()); + auto it = g_builtin_file_sha256_map.find(std::string(path_local)); + if (it == g_builtin_file_sha256_map.end()) { + warningstream << "No SHA256 known for builtin file \"" << path << "\"" + << std::endl; + break; + } + auto digest = hex_encode(hashing::sha256(code)); + if (it->second != digest) { + warningstream << "SHA256 of builtin file \"" << path + << "\" does not match. Expected: " << it->second.c_str() + << " Found: " << digest.c_str() << std::endl;; + } + } while (false); - bool result = safeLoadString(L, code, chunk_name); - if (path) - delete [] chunk_name; - return result; + return safeLoadFileContent(L, code, chunk_name.c_str()); } @@ -787,7 +784,7 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L) } std::string chunk_name = "@" + path; - if (!safeLoadString(L, *contents, chunk_name.c_str())) { + if (!safeLoadFileContent(L, *contents, chunk_name.c_str())) { lua_pushnil(L); lua_insert(L, -2); return 2; diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h index 1241b0b06d6bb..4b08498f207ba 100644 --- a/src/script/cpp_api/s_security.h +++ b/src/script/cpp_api/s_security.h @@ -42,8 +42,11 @@ class ScriptApiSecurity : virtual public ScriptApiBase /// Loads a string as Lua code safely (doesn't allow bytecode). static bool safeLoadString(lua_State *L, std::string_view code, const char *chunk_name); + /// Same as above, but removes shebangs. + static bool safeLoadFileContent(lua_State *L, std::string_view code, const char *chunk_name); /// Loads a file as Lua code safely (doesn't allow bytecode). /// @warning path is not validated in any way + /// Prints warnings for modified builtin files. static bool safeLoadFile(lua_State *L, const char *path, const char *display_name = nullptr); /** diff --git a/util/ci/clang-tidy.sh b/util/ci/clang-tidy.sh index 99f419395479c..42bdd5fbe7ca0 100755 --- a/util/ci/clang-tidy.sh +++ b/util/ci/clang-tidy.sh @@ -5,7 +5,7 @@ cmake -B build -DCMAKE_BUILD_TYPE=Debug \ -DRUN_IN_PLACE=TRUE \ -DENABLE_GETTEXT=FALSE \ -DBUILD_SERVER=TRUE -cmake --build build --target GenerateVersion +cmake --build build --target GenerateVersion GenerateBuiltinFilesCpp ./util/ci/run-clang-tidy.py \ -clang-tidy-binary=$CLANG_TIDY -p build \