From fc6e27d7b0a5799d5e4e28323aee6fd77b0a8921 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Mar 2022 20:11:04 +0000 Subject: [PATCH 01/32] Bump deps/asmjit from `6efd4d5` to `f1a399c` Bumps [deps/asmjit](https://github.com/asmjit/asmjit) from `6efd4d5` to `f1a399c`. - [Release notes](https://github.com/asmjit/asmjit/releases) - [Commits](https://github.com/asmjit/asmjit/compare/6efd4d563dee6832224295fa3bbf1647964246c4...f1a399c4fe74d1535a4190a2b8727c51045cc914) --- updated-dependencies: - dependency-name: deps/asmjit dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/asmjit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/asmjit b/deps/asmjit index 6efd4d56..f1a399c4 160000 --- a/deps/asmjit +++ b/deps/asmjit @@ -1 +1 @@ -Subproject commit 6efd4d563dee6832224295fa3bbf1647964246c4 +Subproject commit f1a399c4fe74d1535a4190a2b8727c51045cc914 From dea945b6e1d0f5b82b17e2b5f16706b48c2f9931 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Wed, 16 Mar 2022 00:27:49 +0100 Subject: [PATCH 02/32] Custom material stupport --- src/client/component/materials.cpp | 167 +++++++++++++++++++++++++++++ src/client/component/materials.hpp | 6 ++ src/client/game/structs.hpp | 81 +++++++++++++- src/client/game/symbols.hpp | 5 +- 4 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 src/client/component/materials.cpp create mode 100644 src/client/component/materials.hpp diff --git a/src/client/component/materials.cpp b/src/client/component/materials.cpp new file mode 100644 index 00000000..3ca18c5d --- /dev/null +++ b/src/client/component/materials.cpp @@ -0,0 +1,167 @@ +#include +#include "loader/component_loader.hpp" + +#include "materials.hpp" +#include "console.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include +#include +#include +#include +#include +#include + +namespace materials +{ + namespace + { + utils::hook::detour db_material_streaming_fail_hook; + utils::hook::detour material_register_handle_hook; + + struct material_data_t + { + std::unordered_map materials; + std::unordered_map images; + }; + + utils::concurrency::container material_data; + + game::GfxImage* setup_image(game::GfxImage* image, const utils::image& raw_image) + { + image->imageFormat = 0x1000003; + image->resourceSize = -1; + + D3D11_SUBRESOURCE_DATA data{}; + data.SysMemPitch = raw_image.get_width() * 4; + data.SysMemSlicePitch = data.SysMemPitch * raw_image.get_height(); + data.pSysMem = raw_image.get_buffer(); + + game::Image_Setup(image, raw_image.get_width(), raw_image.get_height(), image->depth, image->numElements, + image->imageFormat, DXGI_FORMAT_R8G8B8A8_UNORM, image->name, &data); + + return image; + } + + game::Material* create_material(const std::string& name, const std::string& data) + { + const auto white = *reinterpret_cast(SELECT_VALUE(0x141F3D860, 0x14282C330)); + + const auto material = utils::memory::get_allocator()->allocate(); + const auto texture_table = utils::memory::get_allocator()->allocate(); + const auto image = utils::memory::get_allocator()->allocate(); + + std::memcpy(material, white, sizeof(game::Material)); + std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef)); + std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage)); + + material->name = utils::memory::get_allocator()->duplicate_string(name); + image->name = material->name; + + material->textureTable = texture_table; + material->textureTable->u.image = setup_image(image, data); + + return material; + } + + game::Material* load_material(const std::string& name) + { + return material_data.access([&](material_data_t& data_) -> game::Material* + { + if (const auto i = data_.materials.find(name); i != data_.materials.end()) + { + return i->second; + } + + std::string data{}; + if (const auto i = data_.images.find(name); i != data_.images.end()) + { + data = i->second; + } + + if (data.empty() + && !utils::io::read_file(utils::string::va("h1-mod/materials/%s.png", name.data()), &data) + && !utils::io::read_file(utils::string::va("data/materials/%s.png", name.data()), &data)) + { + return nullptr; + } + + const auto material = create_material(name, data); + data_.materials[name] = material; + + return material; + }); + } + + game::Material* try_load_material(const std::string& name) + { + try + { + return load_material(name); + } + catch (const std::exception& e) + { + console::error("Failed to load material %s: %s\n", name.data(), e.what()); + } + + return nullptr; + } + + game::Material* material_register_handle_stub(const char* name) + { + auto result = try_load_material(name); + if (result == nullptr) + { + result = material_register_handle_hook.invoke(name); + } + return result; + } + + bool db_material_streaming_fail_stub(game::Material* material) + { + const auto found = material_data.access([material](material_data_t& data_) + { + if (data_.materials.find(material->name) != data_.materials.end()) + { + return true; + } + + return false; + }); + + if (found) + { + return false; + } + + return db_material_streaming_fail_hook.invoke(material); + } + } + + void add(const std::string& name, const std::string& data) + { + material_data.access([&](material_data_t& data_) + { + data_.images[name] = data; + }); + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + if (game::environment::is_dedi()) + { + return; + } + + material_register_handle_hook.create(game::Material_RegisterHandle, material_register_handle_stub); + db_material_streaming_fail_hook.create(SELECT_VALUE(0x1401D3180, 0x1402C6260), db_material_streaming_fail_stub); + } + }; +} + +REGISTER_COMPONENT(materials::component) diff --git a/src/client/component/materials.hpp b/src/client/component/materials.hpp new file mode 100644 index 00000000..ac58b511 --- /dev/null +++ b/src/client/component/materials.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace materials +{ + void add(const std::string& name, const std::string& data); +} diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 3437a026..dc3f33c7 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1101,11 +1101,91 @@ namespace game int forceTechType; }; + struct GfxImage; + + union MaterialTextureDefInfo + { + GfxImage* image; + void* water; + }; + + struct MaterialTextureDef + { + unsigned int nameHash; + char nameStart; + char nameEnd; + char samplerState; + char semantic; + MaterialTextureDefInfo u; + char pad[1000]; + }; + + struct MaterialPass + { + void* vertexShader; + void* vertexDecl; + void* hullShader; + void* domainShader; + void* pixelShader; + char pixelOutputMask; + char perPrimArgCount; + char perObjArgCount; + char stableArgCount; + unsigned __int16 perPrimArgSize; + unsigned __int16 perObjArgSize; + unsigned __int16 stableArgSize; + char zone; + char perPrimConstantBuffer; + char perObjConstantBuffer; + char stableConstantBuffer; + unsigned int customBufferFlags; + char customSamplerFlags; + char precompiledIndex; + char stageConfig; + void* args; + }; + + struct MaterialTechnique + { + const char* name; + unsigned __int16 flags; + unsigned __int16 passCount; + MaterialPass passArray[1]; + }; + + struct MaterialTechniqueSet + { + const char* name; + unsigned __int16 flags; + char worldVertFormat; + char preDisplacementOnlyCount; + MaterialTechnique* techniques[309]; + }; + + struct GfxStateBits + { + unsigned int loadBits[3]; + char zone; + char depthStencilState[11]; + char blendState; + char rasterizerState; + }; + struct Material { const char* name; + char __pad0[0x118]; + char textureCount; + char __pad1[7]; + MaterialTechniqueSet* techniqueSet; + MaterialTextureDef* textureTable; + void* constantTable; + GfxStateBits* stateBitsTable; + char __pad2[0x108]; }; + static_assert(sizeof(Material) == 0x250); + struct Glyph { unsigned short letter; @@ -1252,7 +1332,6 @@ namespace game GfxImageLoadDef* loadDef; }; - struct GfxTexture { $3FA29451CE6F1FA138A5ABAB84BE9676 ___u0; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 257bbf15..9381150a 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -121,6 +121,9 @@ namespace game #define R_AddCmdDrawTextWithCursor(TXT, MC, F, UNK, X, Y, XS, YS, R, C, S, CP, CC) \ H1_AddBaseDrawTextCmd(TXT, MC, F, game::R_GetFontHeight(F), X, Y, XS, YS, R, C, S, CP, CC, game::R_DrawSomething(S)) + WEAK symbol Image_Setup{0x1404D7D50, 0x1405DCF90}; + WEAK symbol VM_Execute{0x140376360, 0x140444350}; @@ -138,7 +141,7 @@ namespace game WEAK symbol DB_EnumXAssets_Internal{0x1401C9C10, 0x1402BA830}; - WEAK symbol DB_GetXAssetName{0x14019A390, 0x14028BE50}; + WEAK symbol DB_GetXAssetName{0x14019A390, 0x14028BE50}; WEAK symbol DB_GetXAssetTypeSize{0x14019A3B0, 0x14028BE70}; WEAK symbol Date: Wed, 16 Mar 2022 00:30:55 +0100 Subject: [PATCH 03/32] Remove this --- src/client/game/structs.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index dc3f33c7..bb7c3aec 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1117,7 +1117,6 @@ namespace game char samplerState; char semantic; MaterialTextureDefInfo u; - char pad[1000]; }; struct MaterialPass From 72bfe9c40b5e04be57c73eb7f3801a901dbc3085 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Wed, 16 Mar 2022 01:56:00 +0100 Subject: [PATCH 04/32] Dont try to load 'white' material --- src/client/component/materials.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/component/materials.cpp b/src/client/component/materials.cpp index 3ca18c5d..287238c0 100644 --- a/src/client/component/materials.cpp +++ b/src/client/component/materials.cpp @@ -97,6 +97,11 @@ namespace materials game::Material* try_load_material(const std::string& name) { + if (name == "white") + { + return nullptr; + } + try { return load_material(name); From 559de0931428a411abeefac0a5cdd08ff17c97c7 Mon Sep 17 00:00:00 2001 From: m Date: Wed, 16 Mar 2022 07:38:26 -0500 Subject: [PATCH 05/32] removed git protocol submodules [skip ci] --- .gitmodules | 15 --------------- deps/libtomcrypt | 1 - deps/libtommath | 1 - deps/protobuf | 1 - deps/zlib | 1 - 5 files changed, 19 deletions(-) delete mode 160000 deps/libtomcrypt delete mode 160000 deps/libtommath delete mode 160000 deps/protobuf delete mode 160000 deps/zlib diff --git a/.gitmodules b/.gitmodules index 7230acd8..959a72ac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,19 +28,4 @@ [submodule "deps/stb"] path = deps/stb url = https://github.com/nothings/stb.git -[submodule "deps/libtomcrypt"] - path = deps/libtomcrypt - url = git://github.com/libtom/libtomcrypt.git branch = develop -[submodule "deps/zlib"] - path = deps/zlib - url = git://github.com/madler/zlib.git - branch = develop -[submodule "deps/libtommath"] - path = deps/libtommath - url = git://github.com/libtom/libtommath.git - branch = develop -[submodule "deps/protobuf"] - path = deps/protobuf - url = git://github.com/protocolbuffers/protobuf.git - branch = 3.17.x diff --git a/deps/libtomcrypt b/deps/libtomcrypt deleted file mode 160000 index 673f5ce2..00000000 --- a/deps/libtomcrypt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 673f5ce29015a9bba3c96792920a10601b5b0718 diff --git a/deps/libtommath b/deps/libtommath deleted file mode 160000 index 66de8642..00000000 --- a/deps/libtommath +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 66de86426e9cdb88526974c765108f01554af2b0 diff --git a/deps/protobuf b/deps/protobuf deleted file mode 160000 index 5500c72c..00000000 --- a/deps/protobuf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5500c72c5b616da9f0125bcfab513987a1226e2b diff --git a/deps/zlib b/deps/zlib deleted file mode 160000 index 2014a993..00000000 --- a/deps/zlib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2014a993addbc8f1b9785d97f55fd189792c2f78 From b03cd1b0cda1c7046a834117a9d009c99344e0aa Mon Sep 17 00:00:00 2001 From: m Date: Wed, 16 Mar 2022 07:42:38 -0500 Subject: [PATCH 06/32] readd submodules via http protocol --- .gitmodules | 16 ++++++++++++++++ deps/libtomcrypt | 1 + deps/libtommath | 1 + deps/protobuf | 1 + deps/zlib | 1 + 5 files changed, 20 insertions(+) create mode 160000 deps/libtomcrypt create mode 160000 deps/libtommath create mode 160000 deps/protobuf create mode 160000 deps/zlib diff --git a/.gitmodules b/.gitmodules index 959a72ac..e92c76fd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,3 +29,19 @@ path = deps/stb url = https://github.com/nothings/stb.git branch = develop +[submodule "deps/libtomcrypt"] + path = deps/libtomcrypt + url = https://github.com/libtom/libtomcrypt.git + branch = develop +[submodule "deps/libtommath"] + path = deps/libtommath + url = https://github.com/libtom/libtommath.git + branch = develop +[submodule "deps/protobuf"] + path = deps/protobuf + url = https://github.com/protocolbuffers/protobuf.git + branch = 3.17.x +[submodule "deps/zlib"] + path = deps/zlib + url = https://github.com/madler/zlib.git + branch = develop diff --git a/deps/libtomcrypt b/deps/libtomcrypt new file mode 160000 index 00000000..673f5ce2 --- /dev/null +++ b/deps/libtomcrypt @@ -0,0 +1 @@ +Subproject commit 673f5ce29015a9bba3c96792920a10601b5b0718 diff --git a/deps/libtommath b/deps/libtommath new file mode 160000 index 00000000..66de8642 --- /dev/null +++ b/deps/libtommath @@ -0,0 +1 @@ +Subproject commit 66de86426e9cdb88526974c765108f01554af2b0 diff --git a/deps/protobuf b/deps/protobuf new file mode 160000 index 00000000..5500c72c --- /dev/null +++ b/deps/protobuf @@ -0,0 +1 @@ +Subproject commit 5500c72c5b616da9f0125bcfab513987a1226e2b diff --git a/deps/zlib b/deps/zlib new file mode 160000 index 00000000..2014a993 --- /dev/null +++ b/deps/zlib @@ -0,0 +1 @@ +Subproject commit 2014a993addbc8f1b9785d97f55fd189792c2f78 From fc6deb476b8e2173c2b4c2e8767f168178d019c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:55:11 -0500 Subject: [PATCH 07/32] Bump deps/libtomcrypt from `673f5ce` to `06a81ae` (#31) Bumps [deps/libtomcrypt](https://github.com/libtom/libtomcrypt) from `673f5ce` to `06a81ae`. - [Release notes](https://github.com/libtom/libtomcrypt/releases) - [Commits](https://github.com/libtom/libtomcrypt/compare/673f5ce29015a9bba3c96792920a10601b5b0718...06a81aeb227424182125363f7554fad5146d6d2a) --- updated-dependencies: - dependency-name: deps/libtomcrypt dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- deps/libtomcrypt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/libtomcrypt b/deps/libtomcrypt index 673f5ce2..06a81aeb 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit 673f5ce29015a9bba3c96792920a10601b5b0718 +Subproject commit 06a81aeb227424182125363f7554fad5146d6d2a From cea4d720be1e4668c512cff9758655e4e445a8bb Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Thu, 17 Mar 2022 20:58:26 +0100 Subject: [PATCH 08/32] Add LUI notify function --- src/client/game/symbols.hpp | 2 + src/client/game/ui_scripting/execution.cpp | 39 +++++++++++++++++++ src/client/game/ui_scripting/execution.hpp | 2 + src/client/game/ui_scripting/script_value.hpp | 1 + 4 files changed, 44 insertions(+) diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 9381150a..f42835dd 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -146,6 +146,8 @@ namespace game WEAK symbol LUI_OpenMenu{0x14039D5F0, 0x1404CD210}; + WEAK symbol LUI_BeginEvent{0x1400D27F0, 0x140161A00}; + WEAK symbol LUI_EndEvent{0x1400D3A80, 0x140162CD0}; WEAK symbol Menu_IsMenuOpenAndVisible{0x1404709C0, 0x1404C7320}; diff --git a/src/client/game/ui_scripting/execution.cpp b/src/client/game/ui_scripting/execution.cpp index fff6b88e..cc8df795 100644 --- a/src/client/game/ui_scripting/execution.cpp +++ b/src/client/game/ui_scripting/execution.cpp @@ -37,6 +37,45 @@ namespace ui_scripting return values; } + bool notify(const std::string& name, const event_arguments& arguments) + { + const auto state = *game::hks::lua_state; + if (!game::LUI_BeginEvent(0, name.data(), state)) + { + return false; + } + + const auto _1 = gsl::finally(&disable_error_hook); + enable_error_hook(); + + const auto top = state->m_apistack.top; + try + { + const auto event = get_return_value(0).as(); + + for (const auto& arg : arguments) + { + event.set(arg.first, arg.second); + } + } + catch (...) + { + } + + state->m_apistack.top = top; + + try + { + game::LUI_EndEvent(state); + } + catch (const std::exception& e) + { + throw std::runtime_error(std::string("Error while processing event: ") + e.what()); + } + + return true; + } + arguments call_script_function(const function& function, const arguments& arguments) { const auto state = *game::hks::lua_state; diff --git a/src/client/game/ui_scripting/execution.hpp b/src/client/game/ui_scripting/execution.hpp index 24f4dd72..4a3d3562 100644 --- a/src/client/game/ui_scripting/execution.hpp +++ b/src/client/game/ui_scripting/execution.hpp @@ -9,6 +9,8 @@ namespace ui_scripting script_value get_return_value(int offset); arguments get_return_values(int count); + bool notify(const std::string& name, const event_arguments& arguments); + arguments call_script_function(const function& function, const arguments& arguments); script_value get_field(const userdata& self, const script_value& key); diff --git a/src/client/game/ui_scripting/script_value.hpp b/src/client/game/ui_scripting/script_value.hpp index 3de52ddf..a16d2622 100644 --- a/src/client/game/ui_scripting/script_value.hpp +++ b/src/client/game/ui_scripting/script_value.hpp @@ -53,4 +53,5 @@ namespace ui_scripting }; using arguments = std::vector; + using event_arguments = std::unordered_map; } From f6fd11d662ef44e095ca0406a87be78fe8633ee1 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Thu, 17 Mar 2022 23:56:15 +0100 Subject: [PATCH 09/32] Fix custom materials --- src/client/component/materials.cpp | 22 ++++++++-------------- src/client/game/structs.hpp | 7 ++++++- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/client/component/materials.cpp b/src/client/component/materials.cpp index 287238c0..1b0b17c5 100644 --- a/src/client/component/materials.cpp +++ b/src/client/component/materials.cpp @@ -27,6 +27,8 @@ namespace materials std::unordered_map images; }; + char constant_table[0x20] = {}; + utils::concurrency::container material_data; game::GfxImage* setup_image(game::GfxImage* image, const utils::image& raw_image) @@ -57,6 +59,7 @@ namespace materials std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef)); std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage)); + material->constantTable = &constant_table; material->name = utils::memory::get_allocator()->duplicate_string(name); image->name = material->name; @@ -85,6 +88,7 @@ namespace materials && !utils::io::read_file(utils::string::va("h1-mod/materials/%s.png", name.data()), &data) && !utils::io::read_file(utils::string::va("data/materials/%s.png", name.data()), &data)) { + data_.materials[name] = nullptr; return nullptr; } @@ -124,24 +128,14 @@ namespace materials return result; } - bool db_material_streaming_fail_stub(game::Material* material) + int db_material_streaming_fail_stub(game::Material* material) { - const auto found = material_data.access([material](material_data_t& data_) + if (material->constantTable == &constant_table) { - if (data_.materials.find(material->name) != data_.materials.end()) - { - return true; - } - - return false; - }); - - if (found) - { - return false; + return 0; } - return db_material_streaming_fail_hook.invoke(material); + return db_material_streaming_fail_hook.invoke(material); } } diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index bb7c3aec..40515051 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1175,7 +1175,12 @@ namespace game const char* name; char __pad0[0x118]; char textureCount; - char __pad1[7]; + char constantCount; + char stateBitsCount; + char stateFlags; + char cameraRegion; + char materialType; + char assetFlags; MaterialTechniqueSet* techniqueSet; MaterialTextureDef* textureTable; void* constantTable; From 3471ae9dd851d064b27a8f71c9cf2d35109a0056 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Fri, 18 Mar 2022 00:27:13 +0100 Subject: [PATCH 10/32] Fix stats --- src/client/component/stats.cpp | 36 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/client/component/stats.cpp b/src/client/component/stats.cpp index 81e7c3da..99ffdd5f 100644 --- a/src/client/component/stats.cpp +++ b/src/client/component/stats.cpp @@ -21,7 +21,7 @@ namespace stats utils::hook::detour is_item_unlocked_hook2; utils::hook::detour is_item_unlocked_hook3; - int is_item_unlocked_stub(void* a1, void* a2, void* a3) + int is_item_unlocked_stub(int a1, void* a2, int a3) { if (cg_unlock_all_items->current.enabled) { @@ -31,7 +31,7 @@ namespace stats return is_item_unlocked_hook.invoke(a1, a2, a3); } - int is_item_unlocked_stub2(void* a1, void* a2, void* a3, void* a4, void* a5, void* a6) + int is_item_unlocked_stub2(int a1, void* a2, void* a3, void* a4, int a5, void* a6) { if (cg_unlock_all_items->current.enabled) { @@ -41,7 +41,7 @@ namespace stats return is_item_unlocked_hook2.invoke(a1, a2, a3, a4, a5, a6); } - int is_item_unlocked_stub3(void* a1) + int is_item_unlocked_stub3(int a1) { if (cg_unlock_all_items->current.enabled) { @@ -50,6 +50,11 @@ namespace stats return is_item_unlocked_hook3.invoke(a1); } + + int is_item_unlocked() + { + return 0; + } } class component final : public component_interface @@ -57,19 +62,28 @@ namespace stats public: void post_unpack() override { - if (!game::environment::is_mp()) + if (game::environment::is_sp()) { return; } - cg_unlock_all_items = dvars::register_bool("cg_unlockall_items", false, game::DVAR_FLAG_SAVED, - "Whether items should be locked based on the player's stats or always unlocked."); - dvars::register_bool("cg_unlockall_classes", false, game::DVAR_FLAG_SAVED, - "Whether classes should be locked based on the player's stats or always unlocked."); + if (game::environment::is_dedi()) + { + utils::hook::jump(0x140413E60, is_item_unlocked); + utils::hook::jump(0x140413860, is_item_unlocked); + utils::hook::jump(0x140412B70, is_item_unlocked); + } + else + { + cg_unlock_all_items = dvars::register_bool("cg_unlockall_items", false, game::DVAR_FLAG_SAVED, + "Whether items should be locked based on the player's stats or always unlocked."); + dvars::register_bool("cg_unlockall_classes", false, game::DVAR_FLAG_SAVED, + "Whether classes should be locked based on the player's stats or always unlocked."); - is_item_unlocked_hook.create(0x140413E60, is_item_unlocked_stub); - is_item_unlocked_hook2.create(0x140413860, is_item_unlocked_stub2); - is_item_unlocked_hook3.create(0x140412B70, is_item_unlocked_stub3); + is_item_unlocked_hook.create(0x140413E60, is_item_unlocked_stub); + is_item_unlocked_hook2.create(0x140413860, is_item_unlocked_stub2); + is_item_unlocked_hook3.create(0x140412B70, is_item_unlocked_stub3); + } } }; } From 60443263566ac71ec0d50bb116184056c49c0a0b Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Fri, 18 Mar 2022 00:56:03 +0100 Subject: [PATCH 11/32] Custom font support --- src/client/component/fonts.cpp | 116 +++++++++++++++++++++++++++++++++ src/client/component/fonts.hpp | 6 ++ src/client/game/structs.hpp | 9 +++ src/client/game/symbols.hpp | 2 + 4 files changed, 133 insertions(+) create mode 100644 src/client/component/fonts.cpp create mode 100644 src/client/component/fonts.hpp diff --git a/src/client/component/fonts.cpp b/src/client/component/fonts.cpp new file mode 100644 index 00000000..ae8f37fe --- /dev/null +++ b/src/client/component/fonts.cpp @@ -0,0 +1,116 @@ +#include +#include "loader/component_loader.hpp" + +#include "fonts.hpp" +#include "console.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include +#include +#include +#include +#include +#include + +namespace fonts +{ + namespace + { + struct font_data_t + { + std::unordered_map fonts; + std::unordered_map raw_fonts; + }; + + utils::concurrency::container font_data; + + game::TTF* create_font(const std::string& name, const std::string& data) + { + const auto font = utils::memory::get_allocator()->allocate(); + font->name = utils::memory::get_allocator()->duplicate_string(name); + font->buffer = utils::memory::get_allocator()->duplicate_string(data); + font->len = static_cast(data.size()); + font->fontFace = 0; + return font; + } + + game::TTF* load_font(const std::string& name) + { + return font_data.access([&](font_data_t& data_) -> game::TTF* + { + if (const auto i = data_.fonts.find(name); i != data_.fonts.end()) + { + return i->second; + } + + std::string data{}; + if (const auto i = data_.raw_fonts.find(name); i != data_.raw_fonts.end()) + { + data = i->second; + } + + if (data.empty() + && !utils::io::read_file(utils::string::va("h1-mod/%s", name.data()), &data) + && !utils::io::read_file(utils::string::va("data/%s", name.data()), &data)) + { + return nullptr; + } + + const auto material = create_font(name, data); + data_.fonts[name] = material; + + return material; + }); + } + + game::TTF* try_load_font(const std::string& name) + { + try + { + return load_font(name); + } + catch (const std::exception& e) + { + console::error("Failed to load font %s: %s\n", name.data(), e.what()); + } + + return nullptr; + } + + game::TTF* db_find_xasset_header_stub(game::XAssetType type, const char* name, int create_default) + { + auto result = try_load_font(name); + if (result == nullptr) + { + result = game::DB_FindXAssetHeader(type, name, create_default).ttf; + } + return result; + } + } + + void add(const std::string& name, const std::string& data) + { + font_data.access([&](font_data_t& data_) + { + data_.raw_fonts[name] = data; + }); + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + if (game::environment::is_dedi()) + { + return; + } + + utils::hook::call(SELECT_VALUE(0x1404D41B6, 0x1405D9296), db_find_xasset_header_stub); + } + }; +} + +REGISTER_COMPONENT(fonts::component) diff --git a/src/client/component/fonts.hpp b/src/client/component/fonts.hpp new file mode 100644 index 00000000..15749bcf --- /dev/null +++ b/src/client/component/fonts.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace fonts +{ + void add(const std::string& name, const std::string& data); +} diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 40515051..4d6b3405 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1316,6 +1316,14 @@ namespace game const char* buffer; }; + struct TTF + { + const char* name; + int len; + const char* buffer; + int fontFace; + }; + struct GfxImageLoadDef { char levelCount; @@ -1387,6 +1395,7 @@ namespace game StringTable* stringTable; LuaFile* luaFile; GfxImage* image; + TTF* ttf; }; struct XAsset diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index f42835dd..1ade88ce 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -143,6 +143,8 @@ namespace game DB_EnumXAssets_Internal{0x1401C9C10, 0x1402BA830}; WEAK symbol DB_GetXAssetName{0x14019A390, 0x14028BE50}; WEAK symbol DB_GetXAssetTypeSize{0x14019A3B0, 0x14028BE70}; + WEAK symbol DB_FindXAssetHeader{0x1401CA150, 0x1402BAC70}; WEAK symbol LUI_OpenMenu{0x14039D5F0, 0x1404CD210}; From ac7b359c96c53fa6a492b50c888f1d3ca357137c Mon Sep 17 00:00:00 2001 From: m Date: Fri, 18 Mar 2022 17:47:07 -0500 Subject: [PATCH 12/32] fix safeArea + naming of ui_drawCrosshair --- src/client/component/patches.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index 7c323ded..594879f1 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -241,10 +241,10 @@ namespace patches // unlock safeArea_* utils::hook::jump(0x1402624F5, 0x140262503); utils::hook::jump(0x14026251C, 0x140262547); - dvars::override::register_int("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED); - dvars::override::register_int("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED); - dvars::override::register_int("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED); - dvars::override::register_int("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED); + dvars::override::register_float("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED); + dvars::override::register_float("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED); + dvars::override::register_float("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED); + dvars::override::register_float("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED); // allow servers to check for new packages more often dvars::override::register_int("sv_network_fps", 1000, 20, 1000, game::DVAR_FLAG_SAVED); @@ -257,7 +257,7 @@ namespace patches dvars::register_int("scr_game_spectatetype", 1, 0, 99, game::DVAR_FLAG_REPLICATED, ""); - dvars::override::register_bool("ui_drawcrosshair", true, game::DVAR_FLAG_WRITE); + dvars::override::register_bool("ui_drawCrosshair", true, game::DVAR_FLAG_WRITE); dvars::override::register_int("com_maxfps", 0, 0, 1000, game::DVAR_FLAG_SAVED); From 47fb9b88eb26060189fd91f288c2e52cdc714d3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 13:01:59 -0500 Subject: [PATCH 13/32] Bump deps/asmjit from `f1a399c` to `21a31b8` (#32) --- deps/asmjit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/asmjit b/deps/asmjit index f1a399c4..21a31b8a 160000 --- a/deps/asmjit +++ b/deps/asmjit @@ -1 +1 @@ -Subproject commit f1a399c4fe74d1535a4190a2b8727c51045cc914 +Subproject commit 21a31b8a338da3341d2b423f85913597b8ec3d63 From 8f148b4f4d297028f32c45b9afb1a3c50994f8da Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Mon, 28 Mar 2022 02:21:24 +0200 Subject: [PATCH 14/32] Small fix --- src/client/component/materials.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/component/materials.cpp b/src/client/component/materials.cpp index 1b0b17c5..99b22a45 100644 --- a/src/client/component/materials.cpp +++ b/src/client/component/materials.cpp @@ -49,8 +49,7 @@ namespace materials game::Material* create_material(const std::string& name, const std::string& data) { - const auto white = *reinterpret_cast(SELECT_VALUE(0x141F3D860, 0x14282C330)); - + const auto white = material_register_handle_hook.invoke("white"); const auto material = utils::memory::get_allocator()->allocate(); const auto texture_table = utils::memory::get_allocator()->allocate(); const auto image = utils::memory::get_allocator()->allocate(); From 57de6ae3de7a27d45202510c5037cf6ba7e11bfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:41:34 -0500 Subject: [PATCH 15/32] Bump deps/GSL from `4377f6e` to `3837236` (#33) Bumps [deps/GSL](https://github.com/Microsoft/GSL) from `4377f6e` to `3837236`. - [Release notes](https://github.com/Microsoft/GSL/releases) - [Commits](https://github.com/Microsoft/GSL/compare/4377f6e603c64a86c934f1546aa9db482f2e1a4e...383723676cd548d615159701ac3d050f8dd1e128) --- updated-dependencies: - dependency-name: deps/GSL dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- deps/GSL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/GSL b/deps/GSL index 4377f6e6..38372367 160000 --- a/deps/GSL +++ b/deps/GSL @@ -1 +1 @@ -Subproject commit 4377f6e603c64a86c934f1546aa9db482f2e1a4e +Subproject commit 383723676cd548d615159701ac3d050f8dd1e128 From f8fc7da86eaa53e16e8c82a97fd71c68bf7a5acf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:41:48 -0500 Subject: [PATCH 16/32] Bump deps/libtommath from `66de864` to `5108f12` (#34) Bumps [deps/libtommath](https://github.com/libtom/libtommath) from `66de864` to `5108f12`. - [Release notes](https://github.com/libtom/libtommath/releases) - [Commits](https://github.com/libtom/libtommath/compare/66de86426e9cdb88526974c765108f01554af2b0...5108f12350b6daa4aa5dbc846517ad1db2f8388a) --- updated-dependencies: - dependency-name: deps/libtommath dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- deps/libtommath | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/libtommath b/deps/libtommath index 66de8642..5108f123 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit 66de86426e9cdb88526974c765108f01554af2b0 +Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a From 10a3225d603a541af356599abfd1bcf0aa95e949 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:41:57 -0500 Subject: [PATCH 17/32] Bump deps/zlib from `2014a99` to `21767c6` (#35) Bumps [deps/zlib](https://github.com/madler/zlib) from `2014a99` to `21767c6`. - [Release notes](https://github.com/madler/zlib/releases) - [Commits](https://github.com/madler/zlib/compare/2014a993addbc8f1b9785d97f55fd189792c2f78...21767c654d31d2dccdde4330529775c6c5fd5389) --- updated-dependencies: - dependency-name: deps/zlib dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- deps/zlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/zlib b/deps/zlib index 2014a993..21767c65 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit 2014a993addbc8f1b9785d97f55fd189792c2f78 +Subproject commit 21767c654d31d2dccdde4330529775c6c5fd5389 From 722d86f7f132096ed8441de182727ac1881b5c8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:42:05 -0500 Subject: [PATCH 18/32] Bump deps/asmjit from `21a31b8` to `7342f7d` (#36) Bumps [deps/asmjit](https://github.com/asmjit/asmjit) from `21a31b8` to `7342f7d`. - [Release notes](https://github.com/asmjit/asmjit/releases) - [Commits](https://github.com/asmjit/asmjit/compare/21a31b8a338da3341d2b423f85913597b8ec3d63...7342f7d78d8cd3088bb7b475c3f7ec52f7619980) --- updated-dependencies: - dependency-name: deps/asmjit dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- deps/asmjit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/asmjit b/deps/asmjit index 21a31b8a..7342f7d7 160000 --- a/deps/asmjit +++ b/deps/asmjit @@ -1 +1 @@ -Subproject commit 21a31b8a338da3341d2b423f85913597b8ec3d63 +Subproject commit 7342f7d78d8cd3088bb7b475c3f7ec52f7619980 From dc7b2284df9f9efc7970b42301d5eb6b87c8da9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Mar 2022 17:28:58 +0000 Subject: [PATCH 19/32] Bump deps/zlib from `21767c6` to `ce12773` Bumps [deps/zlib](https://github.com/madler/zlib) from `21767c6` to `ce12773`. - [Release notes](https://github.com/madler/zlib/releases) - [Commits](https://github.com/madler/zlib/compare/21767c654d31d2dccdde4330529775c6c5fd5389...ce12773790517034317274f5c65ba70cfeea29f7) --- updated-dependencies: - dependency-name: deps/zlib dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/zlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/zlib b/deps/zlib index 21767c65..ce127737 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit 21767c654d31d2dccdde4330529775c6c5fd5389 +Subproject commit ce12773790517034317274f5c65ba70cfeea29f7 From bcfcb882120bc643c5555ae3fc5a50998fe83646 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:36:08 +0000 Subject: [PATCH 20/32] Bump deps/zlib from `ce12773` to `ec3df00` Bumps [deps/zlib](https://github.com/madler/zlib) from `ce12773` to `ec3df00`. - [Release notes](https://github.com/madler/zlib/releases) - [Commits](https://github.com/madler/zlib/compare/ce12773790517034317274f5c65ba70cfeea29f7...ec3df00224d4b396e2ac6586ab5d25f673caa4c2) --- updated-dependencies: - dependency-name: deps/zlib dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/zlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/zlib b/deps/zlib index ce127737..ec3df002 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit ce12773790517034317274f5c65ba70cfeea29f7 +Subproject commit ec3df00224d4b396e2ac6586ab5d25f673caa4c2 From 9bb32940009d5aadd870cbaad7b89e3c04263cea Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Sun, 3 Apr 2022 14:33:45 +0200 Subject: [PATCH 21/32] Update function_tables.cpp --- src/client/game/scripting/function_tables.cpp | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/client/game/scripting/function_tables.cpp b/src/client/game/scripting/function_tables.cpp index b36aea0f..b3c66253 100644 --- a/src/client/game/scripting/function_tables.cpp +++ b/src/client/game/scripting/function_tables.cpp @@ -1457,36 +1457,36 @@ namespace scripting {"visionsyncwithplayer", 0x82A0}, // SP 0x000000000 MP 0x14032ED90 {"showhudsplash", 0x82A1}, // SP 0x140263850 MP 0x14032FB10 {"setperk", 0x82A2}, // SP 0x140265490 MP 0x1403297E0 - {"_meth_82a3", 0x82A3}, // SP 0x1402659A0 MP 0x140329D00 - {"_meth_82a4", 0x82A4}, // SP 0x1402661B0 MP 0x14032A460 - {"_meth_82a5", 0x82A5}, // SP 0x140265D40 MP 0x14032A0A0 + {"hasperk", 0x82A3}, // SP 0x1402659A0 MP 0x140329D00 + {"clearperks", 0x82A4}, // SP 0x1402661B0 MP 0x14032A460 + {"unsetperk", 0x82A5}, // SP 0x140265D40 MP 0x14032A0A0 {"registerparty", 0x82A6}, // SP 0x000000000 MP 0x1403323C0 - {"_meth_82a7", 0x82A7}, // SP 0x000000000 MP 0x1403324F0 - {"_meth_82a8", 0x82A8}, // SP 0x1405D92F0 MP 0x14032A8F0 - {"_meth_82a9", 0x82A9}, // SP 0x1405D92F0 MP 0x14032A900 + {"getfireteammembers", 0x82A7}, // SP 0x000000000 MP 0x1403324F0 + {"noclip", 0x82A8}, // SP 0x1405D92F0 MP 0x14032A8F0 + {"ufo", 0x82A9}, // SP 0x1405D92F0 MP 0x14032A900 {"moveto", 0x82AA}, // SP 0x1402B2A10 MP 0x14037E950 - {"rotatepitch", 0x82AB}, // SP 0x1402B2F60 MP 0x14037EEB0 - {"rotateyaw", 0x82AC}, // SP 0x1402B2F70 MP 0x14037EEC0 - {"rotateroll", 0x82AD}, // SP 0x1402B2F90 MP 0x14037EEE0 + {"movex", 0x82AB}, // SP 0x1402B2F60 MP 0x14037EEB0 + {"movey", 0x82AC}, // SP 0x1402B2F70 MP 0x14037EEC0 + {"movez", 0x82AD}, // SP 0x1402B2F90 MP 0x14037EEE0 {"movegravity", 0x82AE}, // SP 0x1402B2C10 MP 0x14037EB00 - {"_meth_82af", 0x82AF}, // SP 0x1402B2D70 MP 0x14037EC90 - {"_meth_82b0", 0x82B0}, // SP 0x1402B2EE0 MP 0x14037EE20 + {"moveslide", 0x82AF}, // SP 0x1402B2D70 MP 0x14037EC90 + {"stopmoveslide", 0x82B0}, // SP 0x1402B2EE0 MP 0x14037EE20 {"rotateto", 0x82B1}, // SP 0x1402B3030 MP 0x14037EF10 - {"_meth_82b2", 0x82B2}, // SP 0x1402B3460 MP 0x14037F060 + {"rotatepitch", 0x82B2}, // SP 0x1402B3460 MP 0x14037F060 {"rotateyaw", 0x82B3}, // SP 0x1402B3470 MP 0x14037F070 - {"_meth_82b4", 0x82B4}, // SP 0x1402B3490 MP 0x14037F090 // looks similar to moveto/rotateto, wtf + {"rotateroll", 0x82B4}, // SP 0x1402B3490 MP 0x14037F090 // looks similar to moveto/rotateto, wtf {"addpitch", 0x82B5}, // SP 0x1402B3410 MP 0x14037F010 {"addyaw", 0x82B6}, // SP 0x1402B3430 MP 0x14037F030 - {"addoll", 0x82B7}, // SP 0x1402B3450 MP 0x14037F050 - {"_meth_82b8", 0x82B8}, // SP 0x1402B34B0 MP 0x14037F0B0 + {"addroll", 0x82B7}, // SP 0x1402B3450 MP 0x14037F050 + {"vibrate", 0x82B8}, // SP 0x1402B34B0 MP 0x14037F0B0 {"rotatevelocity", 0x82B9}, // SP 0x1402B3700 MP 0x14037F3C0 {"solid", 0x82BA}, // SP 0x1402B45E0 MP 0x1403808A0 {"notsolid", 0x82BB}, // SP 0x1402B4690 MP 0x140380950 {"setcandamage", 0x82BC}, // SP 0x1402B3880 MP 0x14037F590 {"setcanradiusdamage", 0x82BD}, // SP 0x1402B38E0 MP 0x14037F5F0 {"physicslaunchclient", 0x82BE}, // SP 0x1402B3960 MP 0x14037F670 - {"_meth_82bf", 0x82BF}, // SP 0x000000000 MP 0x1403351A0 - {"_meth_82c0", 0x82C0}, // SP 0x000000000 MP 0x1403351B0 + {"setcardicon", 0x82BF}, // SP 0x000000000 MP 0x1403351A0 + {"setcardnameplate", 0x82C0}, // SP 0x000000000 MP 0x1403351B0 {"setcarddisplayslot", 0x82C1}, // SP 0x000000000 MP 0x1403351C0 {"regweaponforfxremoval", 0x82C2}, // SP 0x000000000 MP 0x1403352B0 {"laststandrevive", 0x82C3}, // SP 0x000000000 MP 0x140331E00 From bfed693737d08c6a657f2bc622a688bffa28cf2f Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 5 Apr 2022 01:30:24 +0200 Subject: [PATCH 22/32] Scripting changes + SP support --- premake5.lua | 1 + src/client/component/logfile.cpp | 2 +- src/client/component/scripting.cpp | 92 ++++++++++++++----- src/client/game/scripting/execution.cpp | 90 +++++++++--------- src/client/game/scripting/execution.hpp | 9 +- src/client/game/scripting/functions.cpp | 35 ++++++- src/client/game/scripting/functions.hpp | 2 +- src/client/game/scripting/lua/context.cpp | 41 ++++++++- src/client/game/scripting/lua/engine.cpp | 28 ++++-- src/client/game/scripting/lua/engine.hpp | 1 + .../game/scripting/lua/value_conversion.cpp | 55 ++++------- src/client/game/symbols.hpp | 7 +- src/client/game/ui_scripting/lua/engine.cpp | 11 +++ 13 files changed, 249 insertions(+), 125 deletions(-) diff --git a/premake5.lua b/premake5.lua index 16043516..9823d960 100644 --- a/premake5.lua +++ b/premake5.lua @@ -264,6 +264,7 @@ filter {} filter "configurations:Debug" optimize "Debug" + buildoptions {"/bigobj"} defines {"DEBUG", "_DEBUG"} filter {} diff --git a/src/client/component/logfile.cpp b/src/client/component/logfile.cpp index fc949d92..d1886ca0 100644 --- a/src/client/component/logfile.cpp +++ b/src/client/component/logfile.cpp @@ -212,7 +212,7 @@ namespace logfile return false; } - const auto hook = vm_execute_hooks[pos]; + const auto& hook = vm_execute_hooks[pos]; const auto state = hook.lua_state(); const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId); diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index e3116122..f8730c86 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -2,7 +2,6 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" -#include #include "game/scripting/entity.hpp" #include "game/scripting/functions.hpp" @@ -13,6 +12,8 @@ #include "scheduler.hpp" #include "scripting.hpp" +#include + namespace scripting { std::unordered_map> fields_table; @@ -21,6 +22,7 @@ namespace scripting namespace { utils::hook::detour vm_notify_hook; + utils::hook::detour vm_execute_hook; utils::hook::detour scr_load_level_hook; utils::hook::detour g_shutdown_game_hook; @@ -29,7 +31,10 @@ namespace scripting utils::hook::detour scr_set_thread_position_hook; utils::hook::detour process_script_hook; + utils::hook::detour sl_get_canonical_string_hook; + std::string current_file; + unsigned int current_file_id{}; void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value, game::VariableValue* top) @@ -48,11 +53,6 @@ namespace scripting e.arguments.emplace_back(*value); } - if (e.name == "entitydeleted") - { - scripting::clear_entity_fields(e.entity); - } - lua::engine::notify(e); } } @@ -60,6 +60,16 @@ namespace scripting vm_notify_hook.invoke(notify_list_owner_id, string_value, top); } + unsigned int vm_execute_stub() + { + if (!lua::engine::is_running()) + { + lua::engine::start(); + } + + return vm_execute_hook.invoke(); + } + void scr_load_level_stub() { scr_load_level_hook.invoke(); @@ -71,20 +81,25 @@ namespace scripting void g_shutdown_game_stub(const int free_scripts) { + if (free_scripts) + { + script_function_table.clear(); + } + lua::engine::stop(); return g_shutdown_game_hook.invoke(free_scripts); } - void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t _name, unsigned int canonicalString, unsigned int offset) + void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t name, unsigned int canonical_string, unsigned int offset) { - const auto name = game::SL_ConvertToString(_name); + const auto name_str = game::SL_ConvertToString(name); - if (fields_table[classnum].find(name) == fields_table[classnum].end()) + if (fields_table[classnum].find(name_str) == fields_table[classnum].end()) { - fields_table[classnum][name] = offset; + fields_table[classnum][name_str] = offset; } - scr_add_class_field_hook.invoke(classnum, _name, canonicalString, offset); + scr_add_class_field_hook.invoke(classnum, name, canonical_string, offset); } void process_script_stub(const char* filename) @@ -92,21 +107,49 @@ namespace scripting const auto file_id = atoi(filename); if (file_id) { - current_file = scripting::find_token(file_id); + current_file_id = file_id; } else { + current_file_id = 0; current_file = filename; } process_script_hook.invoke(filename); } - void scr_set_thread_position_stub(unsigned int threadName, const char* codePos) + void add_function(const std::string& file, unsigned int id, const char* pos) { - const auto function_name = scripting::find_token(threadName); - script_function_table[current_file][function_name] = codePos; - scr_set_thread_position_hook.invoke(threadName, codePos); + const auto function_names = scripting::find_token(id); + for (const auto& name : function_names) + { + script_function_table[file][name] = pos; + } + } + + void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos) + { + if (current_file_id) + { + const auto names = scripting::find_token(current_file_id); + for (const auto& name : names) + { + add_function(name, thread_name, code_pos); + } + } + else + { + add_function(current_file, thread_name, code_pos); + } + + scr_set_thread_position_hook.invoke(thread_name, code_pos); + } + + unsigned int sl_get_canonical_string_stub(const char* str) + { + const auto result = sl_get_canonical_string_hook.invoke(str); + scripting::token_map[str] = result; + return result; } } @@ -115,11 +158,6 @@ namespace scripting public: void post_unpack() override { - if (game::environment::is_sp()) - { - return; - } - vm_notify_hook.create(SELECT_VALUE(0x140379A00, 0x1404479F0), vm_notify_stub); scr_add_class_field_hook.create(SELECT_VALUE(0x140370370, 0x14043E2C0), scr_add_class_field_stub); @@ -127,9 +165,19 @@ namespace scripting scr_set_thread_position_hook.create(SELECT_VALUE(0x14036A180, 0x140437D10), scr_set_thread_position_stub); process_script_hook.create(SELECT_VALUE(0x1403737E0, 0x1404417E0), process_script_stub); - scr_load_level_hook.create(SELECT_VALUE(0x1402A5BE0, 0x1403727C0), scr_load_level_stub); + if (!game::environment::is_sp()) + { + scr_load_level_hook.create(SELECT_VALUE(0x1402A5BE0, 0x1403727C0), scr_load_level_stub); + } + else + { + vm_execute_hook.create(SELECT_VALUE(0x140376590, 0x140444580), vm_execute_stub); + } + g_shutdown_game_hook.create(SELECT_VALUE(0x140277D40, 0x140345A60), g_shutdown_game_stub); + sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, sl_get_canonical_string_stub); + scheduler::loop([]() { lua::engine::run_frame(); diff --git a/src/client/game/scripting/execution.cpp b/src/client/game/scripting/execution.cpp index 273b4f7a..1380ab1e 100644 --- a/src/client/game/scripting/execution.cpp +++ b/src/client/game/scripting/execution.cpp @@ -145,47 +145,6 @@ namespace scripting return exec_ent_thread(entity, pos, arguments); } - static std::unordered_map> custom_fields; - - script_value get_custom_field(const entity& entity, const std::string& field) - { - auto& fields = custom_fields[entity.get_entity_id()]; - const auto _field = fields.find(field); - if (_field != fields.end()) - { - return _field->second; - } - return {}; - } - - void set_custom_field(const entity& entity, const std::string& field, const script_value& value) - { - const auto id = entity.get_entity_id(); - - if (custom_fields[id].find(field) != custom_fields[id].end()) - { - custom_fields[id][field] = value; - return; - } - - custom_fields[id].insert(std::make_pair(field, value)); - } - - void clear_entity_fields(const entity& entity) - { - const auto id = entity.get_entity_id(); - - if (custom_fields.find(id) != custom_fields.end()) - { - custom_fields[id].clear(); - } - } - - void clear_custom_fields() - { - custom_fields.clear(); - } - void set_entity_field(const entity& entity, const std::string& field, const script_value& value) { const auto entref = entity.get_entity_reference(); @@ -206,8 +165,7 @@ namespace scripting } else { - // Read custom fields - set_custom_field(entity, field, value); + set_object_variable(entity.get_entity_id(), field, value); } } @@ -234,8 +192,7 @@ namespace scripting return value; } - // Add custom fields - return get_custom_field(entity, field); + return get_object_variable(entity.get_entity_id(), field); } unsigned int make_array() @@ -248,4 +205,47 @@ namespace scripting return index; } + + void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value) + { + const auto offset = 0xFA00 * (parent_id & 3); + const auto variable_id = game::GetVariable(parent_id, id); + const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset]; + const auto& raw_value = value.get_raw(); + + game::AddRefToValue(raw_value.type, raw_value.u); + game::RemoveRefToValue(variable->type, variable->u.u); + + variable->type = static_cast(raw_value.type); + variable->u.u = raw_value.u; + } + + void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value) + { + const auto id = scripting::find_token_id(name); + set_object_variable(parent_id, id, value); + } + + script_value get_object_variable(const unsigned int parent_id, const unsigned int id) + { + const auto offset = 0xFA00 * (parent_id & 3); + const auto variable_id = game::FindVariable(parent_id, id); + if (!variable_id) + { + return {}; + } + + const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset]; + game::VariableValue value{}; + value.type = static_cast(variable->type); + value.u = variable->u.u; + + return value; + } + + script_value get_object_variable(const unsigned int parent_id, const std::string& name) + { + const auto id = scripting::find_token_id(name); + return get_object_variable(parent_id, id); + } } diff --git a/src/client/game/scripting/execution.hpp b/src/client/game/scripting/execution.hpp index 94a2678d..9ec6f62f 100644 --- a/src/client/game/scripting/execution.hpp +++ b/src/client/game/scripting/execution.hpp @@ -27,13 +27,16 @@ namespace scripting script_value call_script_function(const entity& entity, const std::string& filename, const std::string& function, const std::vector& arguments); - void clear_entity_fields(const entity& entity); - void clear_custom_fields(); - void set_entity_field(const entity& entity, const std::string& field, const script_value& value); script_value get_entity_field(const entity& entity, const std::string& field); void notify(const entity& entity, const std::string& event, const std::vector& arguments); unsigned int make_array(); + + script_value get_object_variable(const unsigned int parent_id, const unsigned int id); + script_value get_object_variable(const unsigned int parent_id, const std::string& name); + + void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value); + void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value); } diff --git a/src/client/game/scripting/functions.cpp b/src/client/game/scripting/functions.cpp index 0f233547..ab2c0b9f 100644 --- a/src/client/game/scripting/functions.cpp +++ b/src/client/game/scripting/functions.cpp @@ -69,19 +69,40 @@ namespace scripting return reinterpret_cast(method_table)[index - 0x8000]; } + + unsigned int parse_token_id(const std::string& name) + { + if (name.starts_with("_ID")) + { + return static_cast(std::strtol(name.substr(3).data(), nullptr, 10)); + } + + if (name.starts_with("_id_")) + { + return static_cast(std::strtol(name.substr(4).data(), nullptr, 16)); + } + + return 0; + } } - std::string find_token(unsigned int id) + std::vector find_token(unsigned int id) { + std::vector results; + + results.push_back(utils::string::va("_id_%X", id)); + results.push_back(utils::string::va("_ID%i", id)); + for (const auto& token : token_map) { if (token.second == id) { - return token.first; + results.push_back(token.first); + break; } } - return utils::string::va("_ID%i", id); + return results; } unsigned int find_token_id(const std::string& name) @@ -93,7 +114,13 @@ namespace scripting return result->second; } - return 0; + const auto parsed_id = parse_token_id(name); + if (parsed_id) + { + return parsed_id; + } + + return game::SL_GetCanonicalString(name.data()); } script_function find_function(const std::string& name, const bool prefer_global) diff --git a/src/client/game/scripting/functions.hpp b/src/client/game/scripting/functions.hpp index 0422bcf7..95c51763 100644 --- a/src/client/game/scripting/functions.hpp +++ b/src/client/game/scripting/functions.hpp @@ -9,7 +9,7 @@ namespace scripting using script_function = void(*)(game::scr_entref_t); - std::string find_token(unsigned int id); + std::vector find_token(unsigned int id); unsigned int find_token_id(const std::string& name); script_function find_function(const std::string& name, const bool prefer_global); diff --git a/src/client/game/scripting/lua/context.cpp b/src/client/game/scripting/lua/context.cpp index 7aaa9f89..cb24ff3e 100644 --- a/src/client/game/scripting/lua/context.cpp +++ b/src/client/game/scripting/lua/context.cpp @@ -17,7 +17,6 @@ namespace scripting::lua { namespace { - vector normalize_vector(const vector& vec) { const auto length = sqrt( @@ -155,12 +154,52 @@ namespace scripting::lua { return normalize_vector(a); }; + + vector_type["normalize"] = [](const vector& a) + { + return normalize_vector(a); + }; + + vector_type["toangles"] = [](const vector& a) + { + return call("vectortoangles", {a}).as(); + }; + + vector_type["toyaw"] = [](const vector& a) + { + return call("vectortoyaw", {a}).as(); + }; + + vector_type["tolerp"] = [](const vector& a) + { + return call("vectortolerp", {a}).as(); + }; + + vector_type["toup"] = [](const vector& a) + { + return call("anglestoup", {a}).as(); + }; + + vector_type["toright"] = [](const vector& a) + { + return call("anglestoright", {a}).as(); + }; + + vector_type["toforward"] = [](const vector& a) + { + return call("anglestoforward", {a}).as(); + }; } void setup_entity_type(sol::state& state, event_handler& handler, scheduler& scheduler) { state["level"] = entity{*game::levelEntityId}; + if (game::environment::is_sp()) + { + state["player"] = call("getentbynum", {0}).as(); + } + auto entity_type = state.new_usertype("entity"); for (const auto& func : method_map) diff --git a/src/client/game/scripting/lua/engine.cpp b/src/client/game/scripting/lua/engine.cpp index d481dc76..390a5995 100644 --- a/src/client/game/scripting/lua/engine.cpp +++ b/src/client/game/scripting/lua/engine.cpp @@ -11,6 +11,8 @@ namespace scripting::lua::engine { namespace { + bool running = false; + auto& get_scripts() { static std::vector> scripts{}; @@ -38,21 +40,30 @@ namespace scripting::lua::engine void stop() { + running = false; logfile::clear_callbacks(); get_scripts().clear(); } void start() { - // No SP until there is a concept - if (game::environment::is_sp()) - { - return; - } - stop(); + load_scripts("h1-mod/scripts/"); load_scripts("data/scripts/"); + + if (game::environment::is_sp()) + { + load_scripts("h1-mod/scripts/sp/"); + load_scripts("data/scripts/sp/"); + } + else + { + load_scripts("h1-mod/scripts/mp/"); + load_scripts("data/scripts/mp/"); + } + + running = true; } void notify(const event& e) @@ -70,4 +81,9 @@ namespace scripting::lua::engine script->run_frame(); } } + + bool is_running() + { + return running; + } } diff --git a/src/client/game/scripting/lua/engine.hpp b/src/client/game/scripting/lua/engine.hpp index 471316cd..2df0b0ef 100644 --- a/src/client/game/scripting/lua/engine.hpp +++ b/src/client/game/scripting/lua/engine.hpp @@ -8,4 +8,5 @@ namespace scripting::lua::engine void stop(); void notify(const event& e); void run_frame(); + bool is_running(); } diff --git a/src/client/game/scripting/lua/value_conversion.cpp b/src/client/game/scripting/lua/value_conversion.cpp index a838c787..a12abc8e 100644 --- a/src/client/game/scripting/lua/value_conversion.cpp +++ b/src/client/game/scripting/lua/value_conversion.cpp @@ -165,56 +165,33 @@ namespace scripting::lua auto table = sol::table::create(state); auto metatable = sol::table::create(state); - const auto offset = 64000 * (parent_id & 3); - - metatable[sol::meta_function::new_index] = [offset, parent_id](const sol::table t, const sol::this_state s, + metatable[sol::meta_function::new_index] = [parent_id](const sol::table t, const sol::this_state s, const sol::lua_value& field, const sol::lua_value& value) { - const auto id = field.is() - ? scripting::find_token_id(field.as()) - : field.as(); - - if (!id) + const auto new_variable = convert({s, value}); + if (field.is()) { - return; + scripting::set_object_variable(parent_id, field.as(), new_variable); + } + else if (field.is()) + { + scripting::set_object_variable(parent_id, field.as(), new_variable); } - - const auto variable_id = game::GetVariable(parent_id, id); - const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset]; - const auto new_variable = convert({s, value}).get_raw(); - - game::AddRefToValue(new_variable.type, new_variable.u); - game::RemoveRefToValue(variable->type, variable->u.u); - - variable->type = (char)new_variable.type; - variable->u.u = new_variable.u; }; - metatable[sol::meta_function::index] = [offset, parent_id](const sol::table t, const sol::this_state s, + metatable[sol::meta_function::index] = [parent_id](const sol::table t, const sol::this_state s, const sol::lua_value& field) { - const auto id = field.is() - ? scripting::find_token_id(field.as()) - : field.as(); - - if (!id) + if (field.is()) { - return sol::lua_value{s, sol::lua_nil}; + return convert(s, scripting::get_object_variable(parent_id, field.as())); + } + else if (field.is()) + { + return convert(s, scripting::get_object_variable(parent_id, field.as())); } - const auto variable_id = game::FindVariable(parent_id, id); - if (!variable_id) - { - return sol::lua_value{s, sol::lua_nil}; - } - - const auto variable = game::scr_VarGlob->childVariableValue[variable_id + offset]; - - game::VariableValue result{}; - result.u = variable.u.u; - result.type = (game::scriptType_e)variable.type; - - return convert(s, result); + return sol::lua_value{s, sol::lua_nil}; }; table[sol::metatable_key] = metatable; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 1ade88ce..1f277cee 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -70,7 +70,7 @@ namespace game WEAK symbol FS_Startup{0x1403B85D0, 0x1404EDD30}; WEAK symbol FS_AddLocalizedGameDirectory{0x1403B6030, 0x1404EBE20}; - WEAK symbol GetVariable{0x14036FDD0, 0x1403F3730}; + WEAK symbol GetVariable{0x14036FDD0, 0x14043DD70}; WEAK symbol GetNewVariable{0x14036FA00, 0x14043D990}; WEAK symbol GetNewArrayVariable{0x14036F880, 0x14043D810}; WEAK symbol GScr_LoadConsts{0x1402D13E0, 0x140393810}; @@ -136,6 +136,7 @@ namespace game WEAK symbol Scr_ClearOutParams{0x140374460, 0x140442510}; WEAK symbol Scr_GetEntityIdRef{0x140372D50, 0x140440D80}; WEAK symbol Scr_GetEntityId{0x140372CA0, 0x140440CD0}; + WEAK symbol Scr_SetObjectField{0x1402B9F60, 0x140385330}; WEAK symbol ScrPlace_GetViewPlacement{0x1401981F0, 0x140288550}; @@ -155,8 +156,8 @@ namespace game WEAK symbol SL_FindString{0x14036D700, 0x14043B470}; WEAK symbol SL_GetString{0x14036D9A0, 0x14043B840}; - WEAK symbol SL_ConvertToString{0x14036D420, 0x14043B170}; - WEAK symbol Scr_SetObjectField{0x1402B9F60, 0x140385330}; + WEAK symbol SL_ConvertToString{0x14036D420, 0x14043B170}; + WEAK symbol SL_GetCanonicalString{0x14036A310, 0x140437EA0}; WEAK symbol SV_DirectConnect{0, 0x140480860}; WEAK symbol SV_Cmd_ArgvBuffer{0x1403446C0, 0x140404CA0}; diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index 8faf198c..cc3d3d33 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -54,6 +54,17 @@ namespace ui_scripting::lua::engine load_scripts("h1-mod/ui_scripts/"); load_scripts("data/ui_scripts/"); + + if (game::environment::is_sp()) + { + load_scripts("h1-mod/ui_scripts/sp/"); + load_scripts("data/ui_scripts/sp/"); + } + else + { + load_scripts("h1-mod/ui_scripts/mp/"); + load_scripts("data/ui_scripts/mp/"); + } } void stop() From 36d07057c5f5e0e8caba6296d5f7d28cfbe6b1cf Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 5 Apr 2022 01:54:13 +0200 Subject: [PATCH 23/32] Add some functions --- src/client/component/scripting.cpp | 1 + src/client/component/scripting.hpp | 3 + src/client/game/scripting/lua/context.cpp | 71 ++++++++++++++++++++ src/client/game/symbols.hpp | 2 + src/client/game/ui_scripting/lua/context.cpp | 69 +++++++++++++++++++ 5 files changed, 146 insertions(+) diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index f8730c86..9aa37307 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -18,6 +18,7 @@ namespace scripting { std::unordered_map> fields_table; std::unordered_map> script_function_table; + utils::concurrency::container shared_table; namespace { diff --git a/src/client/component/scripting.hpp b/src/client/component/scripting.hpp index 865ae858..5794bff2 100644 --- a/src/client/component/scripting.hpp +++ b/src/client/component/scripting.hpp @@ -3,6 +3,9 @@ namespace scripting { + using shared_table_t = std::unordered_map; + extern std::unordered_map> fields_table; extern std::unordered_map> script_function_table; + extern utils::concurrency::container shared_table; } \ No newline at end of file diff --git a/src/client/game/scripting/lua/context.cpp b/src/client/game/scripting/lua/context.cpp index cb24ff3e..396b3837 100644 --- a/src/client/game/scripting/lua/context.cpp +++ b/src/client/game/scripting/lua/context.cpp @@ -9,6 +9,7 @@ #include "../../../component/command.hpp" #include "../../../component/logfile.hpp" #include "../../../component/scripting.hpp" +#include "../../../component/fastfiles.hpp" #include #include @@ -501,6 +502,76 @@ namespace scripting::lua return detour; }; + + game_type["assetlist"] = [](const game&, const sol::this_state s, const std::string& type_string) + { + auto table = sol::table::create(s.lua_state()); + auto index = 1; + auto type_index = -1; + + for (auto i = 0; i < ::game::XAssetType::ASSET_TYPE_COUNT; i++) + { + if (type_string == ::game::g_assetNames[i]) + { + type_index = i; + } + } + + if (type_index == -1) + { + throw std::runtime_error("Asset type does not exist"); + } + + const auto type = static_cast<::game::XAssetType>(type_index); + fastfiles::enum_assets(type, [type, &table, &index](const ::game::XAssetHeader header) + { + const auto asset = ::game::XAsset{type, header}; + const std::string asset_name = ::game::DB_GetXAssetName(&asset); + table[index++] = asset_name; + }, true); + + return table; + }; + + game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value) + { + scripting::shared_table.access([key, value](scripting::shared_table_t& table) + { + table[key] = value; + }); + }; + + game_type["sharedget"] = [](const game&, const std::string& key) + { + std::string result; + scripting::shared_table.access([key, &result](scripting::shared_table_t& table) + { + result = table[key]; + }); + return result; + }; + + game_type["sharedclear"] = [](const game&) + { + scripting::shared_table.access([](scripting::shared_table_t& table) + { + table.clear(); + }); + }; + + game_type["getentbyref"] = [](const game&, const sol::this_state s, + const unsigned int entnum, const unsigned int classnum) + { + const auto id = ::game::Scr_GetEntityId(entnum, classnum); + if (id) + { + return convert(s, scripting::entity{id}); + } + else + { + return sol::lua_value{s, sol::lua_nil}; + } + }; } } diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 1f277cee..974cbab9 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -43,6 +43,8 @@ namespace game WEAK symbol CG_GameMessageBold{0x140138750, 0x140220620}; WEAK symbol CG_SetClientDvarFromServer{0, 0x140236120}; + WEAK symbol CG_GetWeaponDisplayName{0x14016EC30, 0x1400B5840}; WEAK symbol CL_IsCgameInitialized{0x14017EE30, 0x140245650}; diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 96286cdd..0d975d0d 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -10,6 +10,8 @@ #include "../../../component/updater.hpp" #include "../../../component/fps.hpp" #include "../../../component/localized_strings.hpp" +#include "../../../component/fastfiles.hpp" +#include "../../../component/scripting.hpp" #include "component/game_console.hpp" #include "component/scheduler.hpp" @@ -68,6 +70,73 @@ namespace ui_scripting::lua localized_strings::override(string, value); }; + game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value) + { + scripting::shared_table.access([key, value](scripting::shared_table_t& table) + { + table[key] = value; + }); + }; + + game_type["sharedget"] = [](const game&, const std::string& key) + { + std::string result; + scripting::shared_table.access([key, &result](scripting::shared_table_t& table) + { + result = table[key]; + }); + return result; + }; + + game_type["sharedclear"] = [](const game&) + { + scripting::shared_table.access([](scripting::shared_table_t& table) + { + table.clear(); + }); + }; + + game_type["assetlist"] = [](const game&, const sol::this_state s, const std::string& type_string) + { + auto table = sol::table::create(s.lua_state()); + auto index = 1; + auto type_index = -1; + + for (auto i = 0; i < ::game::XAssetType::ASSET_TYPE_COUNT; i++) + { + if (type_string == ::game::g_assetNames[i]) + { + type_index = i; + } + } + + if (type_index == -1) + { + throw std::runtime_error("Asset type does not exist"); + } + + const auto type = static_cast<::game::XAssetType>(type_index); + fastfiles::enum_assets(type, [type, &table, &index](const ::game::XAssetHeader header) + { + const auto asset = ::game::XAsset{type, header}; + const std::string asset_name = ::game::DB_GetXAssetName(&asset); + table[index++] = asset_name; + }, true); + + return table; + }; + + game_type["getweapondisplayname"] = [](const game&, const std::string& name) + { + const auto alternate = name.starts_with("alt_"); + const auto weapon = ::game::G_GetWeaponForName(name.data()); + + char buffer[0x400] = {0}; + ::game::CG_GetWeaponDisplayName(weapon, alternate, buffer, 0x400); + + return std::string(buffer); + }; + auto userdata_type = state.new_usertype("userdata_"); userdata_type["new"] = sol::property( From 7e3976558ab22c679b31fa1ba0d66bb2a97299fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 12:57:10 -0500 Subject: [PATCH 24/32] Bump deps/asmjit from `7342f7d` to `752eb38` (#41) Bumps [deps/asmjit](https://github.com/asmjit/asmjit) from `7342f7d` to `752eb38`. - [Release notes](https://github.com/asmjit/asmjit/releases) - [Commits](https://github.com/asmjit/asmjit/compare/7342f7d78d8cd3088bb7b475c3f7ec52f7619980...752eb38a4dbe590995cbadaff06baadd8378eeeb) --- updated-dependencies: - dependency-name: deps/asmjit dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- deps/asmjit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/asmjit b/deps/asmjit index 7342f7d7..752eb38a 160000 --- a/deps/asmjit +++ b/deps/asmjit @@ -1 +1 @@ -Subproject commit 7342f7d78d8cd3088bb7b475c3f7ec52f7619980 +Subproject commit 752eb38a4dbe590995cbadaff06baadd8378eeeb From 6452db033a7565f35c7de8d1fbc6cceff228dbfe Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 5 Apr 2022 21:41:02 +0200 Subject: [PATCH 25/32] Add lui key events + fixes --- src/client/component/input.cpp | 13 +++++ src/client/game/symbols.hpp | 3 + src/client/game/ui_scripting/execution.cpp | 65 +++++++++++++--------- src/client/game/ui_scripting/types.cpp | 34 +++++++++++ src/client/game/ui_scripting/types.hpp | 24 ++++++++ 5 files changed, 112 insertions(+), 27 deletions(-) diff --git a/src/client/component/input.cpp b/src/client/component/input.cpp index a10ac12a..c1b39b9d 100644 --- a/src/client/component/input.cpp +++ b/src/client/component/input.cpp @@ -4,6 +4,7 @@ #include "game/game.hpp" #include "game_console.hpp" +#include "game/ui_scripting/execution.hpp" #include @@ -16,6 +17,12 @@ namespace input void cl_char_event_stub(const int local_client_num, const int key) { + ui_scripting::notify("keypress", + { + {"keynum", key}, + {"key", game::Key_KeynumToString(key, 0, 1)}, + }); + if (!game_console::console_char_event(local_client_num, key)) { return; @@ -26,6 +33,12 @@ namespace input void cl_key_event_stub(const int local_client_num, const int key, const int down) { + ui_scripting::notify(down ? "keydown" : "keyup", + { + {"keynum", key}, + {"key", game::Key_KeynumToString(key, 0, 1)}, + }); + if (!game_console::console_key_event(local_client_num, key, down)) { return; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 974cbab9..6653b555 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -153,6 +153,8 @@ namespace game int a3, int a4, unsigned int a5)> LUI_OpenMenu{0x14039D5F0, 0x1404CD210}; WEAK symbol LUI_BeginEvent{0x1400D27F0, 0x140161A00}; WEAK symbol LUI_EndEvent{0x1400D3A80, 0x140162CD0}; + WEAK symbol LUI_EnterCriticalSection{0x1400D3B70, 0x140162DC0}; + WEAK symbol LUI_LeaveCriticalSection{0x1400D8DB0, 0x140168150}; WEAK symbol Menu_IsMenuOpenAndVisible{0x1404709C0, 0x1404C7320}; @@ -273,5 +275,6 @@ namespace game int internal_, int profilerTreatClosureAsFunc)> cclosure_Create{0x14008B5D0, 0x14011B540}; WEAK symbol hksi_luaL_ref{0x1400A64D0, 0x140136D30}; WEAK symbol hksi_luaL_unref{0x14009EF10, 0x14012F610}; + WEAK symbol closePendingUpvalues{0x14008EA00, 0x14011E970}; } } diff --git a/src/client/game/ui_scripting/execution.cpp b/src/client/game/ui_scripting/execution.cpp index cc8df795..716fe1ca 100644 --- a/src/client/game/ui_scripting/execution.cpp +++ b/src/client/game/ui_scripting/execution.cpp @@ -40,53 +40,53 @@ namespace ui_scripting bool notify(const std::string& name, const event_arguments& arguments) { const auto state = *game::hks::lua_state; - if (!game::LUI_BeginEvent(0, name.data(), state)) + if (!state) { return false; } - - const auto _1 = gsl::finally(&disable_error_hook); - enable_error_hook(); - const auto top = state->m_apistack.top; + const auto _1 = gsl::finally(game::LUI_LeaveCriticalSection); + game::LUI_EnterCriticalSection(); + try { - const auto event = get_return_value(0).as
(); + const auto globals = table((*::game::hks::lua_state)->globals.v.table); + const auto engine = globals.get("Engine").as
(); + const auto root = engine.get("GetLuiRoot").as().call({})[0].as(); + const auto process_event = root.get("processEvent").as(); + + table event{}; + event.set("name", name); for (const auto& arg : arguments) { event.set(arg.first, arg.second); } - } - catch (...) - { - } - state->m_apistack.top = top; - - try - { - game::LUI_EndEvent(state); + process_event.call({root, event}); + return true; } catch (const std::exception& e) { - throw std::runtime_error(std::string("Error while processing event: ") + e.what()); + printf("Error processing event '%s' %s\n", name.data(), e.what()); + return false; } - - return true; } arguments call_script_function(const function& function, const arguments& arguments) { const auto state = *game::hks::lua_state; - state->m_apistack.top = state->m_apistack.base; + stack stack; push_value(function); for (auto i = arguments.begin(); i != arguments.end(); ++i) { push_value(*i); } + const auto num_args = static_cast(arguments.size()); + stack.save(num_args + 1); + const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); @@ -98,6 +98,7 @@ namespace ui_scripting } catch (const std::exception& e) { + stack.fix(); throw std::runtime_error(std::string("Error executing script function: ") + e.what()); } } @@ -105,9 +106,10 @@ namespace ui_scripting script_value get_field(const userdata& self, const script_value& key) { const auto state = *game::hks::lua_state; - state->m_apistack.top = state->m_apistack.base; + stack stack; push_value(key); + stack.save(1); const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); @@ -124,16 +126,18 @@ namespace ui_scripting } catch (const std::exception& e) { - throw std::runtime_error(std::string("Error getting userdata field: ") + e.what()); + stack.fix(); + throw std::runtime_error("Error getting userdata field: "s + e.what()); } } script_value get_field(const table& self, const script_value& key) { const auto state = *game::hks::lua_state; - state->m_apistack.top = state->m_apistack.base; + stack stack; push_value(key); + stack.save(1); const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); @@ -150,14 +154,17 @@ namespace ui_scripting } catch (const std::exception& e) { - throw std::runtime_error(std::string("Error getting table field: ") + e.what()); + stack.fix(); + throw std::runtime_error("Error getting table field: "s + e.what()); } } void set_field(const userdata& self, const script_value& key, const script_value& value) { const auto state = *game::hks::lua_state; - state->m_apistack.top = state->m_apistack.base; + + stack stack; + stack.save(0); const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); @@ -172,14 +179,17 @@ namespace ui_scripting } catch (const std::exception& e) { - throw std::runtime_error(std::string("Error setting userdata field: ") + e.what()); + stack.fix(); + throw std::runtime_error("Error setting userdata field: "s + e.what()); } } void set_field(const table& self, const script_value& key, const script_value& value) { const auto state = *game::hks::lua_state; - state->m_apistack.top = state->m_apistack.base; + + stack stack; + stack.save(0); const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); @@ -194,7 +204,8 @@ namespace ui_scripting } catch (const std::exception& e) { - throw std::runtime_error(std::string("Error setting table field: ") + e.what()); + stack.fix(); + throw std::runtime_error("Error setting table field: "s + e.what()); } } } diff --git a/src/client/game/ui_scripting/types.cpp b/src/client/game/ui_scripting/types.cpp index 37032b1b..66e8d497 100644 --- a/src/client/game/ui_scripting/types.cpp +++ b/src/client/game/ui_scripting/types.cpp @@ -273,4 +273,38 @@ namespace ui_scripting { return call_script_function(*this, arguments); } + + /*************************************************************** + * Stack + **************************************************************/ + + stack::stack() + { + this->state = *game::hks::lua_state; + this->state->m_apistack.top = this->state->m_apistack.base; + } + + void stack::save(int num_args) + { + this->num_args_ = num_args; + this->num_calls_ = state->m_numberOfCCalls; + this->base_bottom_ = state->m_apistack.base - state->m_apistack.bottom; + this->top_bottom_ = state->m_apistack.top - state->m_apistack.bottom; + this->callstack_ = state->m_callStack.m_current - state->m_callStack.m_records; + } + + void stack::fix() + { + this->state->m_numberOfCCalls = this->num_calls_; + + game::hks::closePendingUpvalues(this->state, &this->state->m_apistack.bottom[this->top_bottom_ - this->num_args_]); + this->state->m_callStack.m_current = &this->state->m_callStack.m_records[this->callstack_]; + + this->state->m_apistack.base = &this->state->m_apistack.bottom[this->base_bottom_]; + this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ - static_cast(this->num_args_ + 1)]; + + this->state->m_apistack.bottom[this->top_bottom_].t = this->state->m_apistack.top[-1].t; + this->state->m_apistack.bottom[this->top_bottom_].v.ptr = this->state->m_apistack.top[-1].v.ptr; + this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ + 1]; + } } diff --git a/src/client/game/ui_scripting/types.hpp b/src/client/game/ui_scripting/types.hpp index 1924407f..bc2f7216 100644 --- a/src/client/game/ui_scripting/types.hpp +++ b/src/client/game/ui_scripting/types.hpp @@ -86,4 +86,28 @@ namespace ui_scripting int ref{}; }; + + class stack final + { + public: + stack(); + + void save(int num_args); + void fix(); + + stack(stack&&) = delete; + stack(const stack&) = delete; + stack& operator=(stack&&) = delete; + stack& operator=(const stack&) = delete; + + private: + game::hks::lua_State* state; + + int num_args_; + int num_calls_; + + uint64_t base_bottom_; + uint64_t top_bottom_; + uint64_t callstack_; + }; } From 5ed6a3a3d635320b664e91289b5d5eb8f70bbef7 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 5 Apr 2022 22:16:41 +0200 Subject: [PATCH 26/32] Add sp player notify function --- src/client/game/ui_scripting/lua/context.cpp | 46 ++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 0d975d0d..1db94910 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -2,6 +2,7 @@ #include "context.hpp" #include "error.hpp" #include "value_conversion.hpp" +#include "../../scripting/execution.hpp" #include "../script_value.hpp" #include "../execution.hpp" @@ -262,6 +263,51 @@ namespace ui_scripting::lua updater_table["getcurrentfile"] = updater::get_current_file; state["updater"] = updater_table; + + if (::game::environment::is_sp()) + { + struct player + { + }; + auto player_type = state.new_usertype("player_"); + state["player"] = player(); + + player_type["notify"] = [](const player&, const sol::this_state s, const std::string& name, sol::variadic_args va) + { + if (!::game::CL_IsCgameInitialized() || !::game::sp::g_entities[0].client) + { + throw std::runtime_error("Not in game"); + } + + const sol::state_view view{s}; + const auto to_string = view["tostring"].get(); + + std::vector args{}; + for (auto arg : va) + { + args.push_back(to_string.call(arg).get()); + } + + ::scheduler::once([s, name, args]() + { + try + { + std::vector arguments{}; + + for (const auto& arg : args) + { + arguments.push_back(arg); + } + + const auto player = scripting::call("getentbynum", {0}).as(); + scripting::notify(player, name, arguments); + } + catch (...) + { + } + }, ::scheduler::pipeline::server); + }; + } } } From b7a246a1ec0b3fec34fabde48fb43737a48ffa66 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 5 Apr 2022 22:16:47 +0200 Subject: [PATCH 27/32] Update function_tables.cpp --- src/client/game/scripting/function_tables.cpp | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/client/game/scripting/function_tables.cpp b/src/client/game/scripting/function_tables.cpp index b3c66253..e0cba30c 100644 --- a/src/client/game/scripting/function_tables.cpp +++ b/src/client/game/scripting/function_tables.cpp @@ -931,12 +931,12 @@ namespace scripting {"setstablemissile", 0x8092}, // SP 0x1402AD800 MP 0x000000000 {"playersetgroundreferenceent", 0x8093}, // SP 0x1402A9070 MP 0x1403752A0 {"dontinterpolate", 0x8094}, // SP 0x1402A0070 MP 0x140358360 - {"_meth_8095", 0x8095}, // SP 0x1402AAC80 MP 0x000000000 - {"_meth_8096", 0x8096}, // SP 0x1402AAD20 MP 0x000000000 + {"dospawn", 0x8095}, // SP 0x1402AAC80 MP 0x000000000 + {"stalingradspawn", 0x8096}, // SP 0x1402AAD20 MP 0x000000000 {"getorigin", 0x8097}, // SP 0x1402AADC0 MP 0x140377CA0 - {"_meth_8098", 0x8098}, // SP 0x1402AAE70 MP 0x000000000 - {"_meth_8099", 0x8099}, // SP 0x1402AAF90 MP 0x000000000 - {"_meth_809a", 0x809A}, // SP 0x1402AC1D0 MP 0x000000000 + {"getcentroid", 0x8098}, // SP 0x1402AAE70 MP 0x000000000 + {"getshootatpos", 0x8099}, // SP 0x1402AAF90 MP 0x000000000 + {"getdebugeye", 0x809A}, // SP 0x1402AC1D0 MP 0x000000000 {"useby", 0x809B}, // SP 0x1402AC470 MP 0x140377D00 {"playsound", 0x809C}, // SP 0x1402AC9B0 MP 0x140377F40 {"_meth_809d", 0x809D}, @@ -1336,14 +1336,14 @@ namespace scripting {"vehicleturretcontroloff", 0x8227}, // SP 0x140464260 MP 0x140562970 {"isturretready", 0x8228}, // SP 0x140464340 MP 0x1405629E0 {"_meth_8229", 0x8229}, // SP 0x140464570 MP 0x140562C70 - {"dospawn", 0x822A}, // SP 0x1404646D0 MP 0x140562D90 - {"isphysveh", 0x822B}, // SP 0x1404647A0 MP 0x140562E80 - {"crash", 0x822C}, // SP 0x140464840 MP 0x140562F80 - {"launch", 0x822D}, // SP 0x140464980 MP 0x1405630A0 - {"disablecrashing", 0x822E}, // SP 0x140464B20 MP 0x140563240 - {"enablecrashing", 0x822F}, // SP 0x140464C10 MP 0x140563310 - {"setspeed", 0x8230}, // SP 0x140464C90 MP 0x1405633E0 - {"setconveyorbelt", 0x8231}, // SP 0x140464E50 MP 0x140563610 + {"vehicle_dospawn", 0x822A}, // SP 0x1404646D0 MP 0x140562D90 + {"vehicle_isphysveh", 0x822B}, // SP 0x1404647A0 MP 0x140562E80 + {"vehphys_crash", 0x822C}, // SP 0x140464840 MP 0x140562F80 + {"vehphys_launch", 0x822D}, // SP 0x140464980 MP 0x1405630A0 + {"vehphys_disablecrashing", 0x822E}, // SP 0x140464B20 MP 0x140563240 + {"vehphys_enablecrashing", 0x822F}, // SP 0x140464C10 MP 0x140563310 + {"vehphys_setspeed", 0x8230}, // SP 0x140464C90 MP 0x1405633E0 + {"vehphys_setconveyorbelt", 0x8231}, // SP 0x140464E50 MP 0x140563610 {"freevehicle", 0x8232}, // SP 0x000000000 MP 0x1405609B0 {"_meth_8233", 0x8233}, // SP 0x140290E70 MP 0x000000000 {"_meth_8234", 0x8234}, // SP 0x1402910E0 MP 0x000000000 @@ -1393,8 +1393,8 @@ namespace scripting {"canturrettargetpoint", 0x8260}, // SP 0x1404635A0 MP 0x140561DF0 {"setlookatent", 0x8261}, // SP 0x1404638A0 MP 0x1405620F0 {"clearlookatent", 0x8262}, // SP 0x140463950 MP 0x1405621A0 - {"setweapon", 0x8263}, // SP 0x140463AB0 MP 0x140562280 - {"_meth_8264", 0x8264}, // SP 0x140463B20 MP 0x1405622F0 + {"setvehweapon", 0x8263}, // SP 0x140463AB0 MP 0x140562280 + {"fireweapon", 0x8264}, // SP 0x140463B20 MP 0x1405622F0 {"vehicleturretcontrolon", 0x8265}, // SP 0x1404641D0 MP 0x1405628F0 {"finishplayerdamage", 0x8266}, // SP 0x000000000 MP 0x1403337A0 {"suicide", 0x8267}, // SP 0x000000000 MP 0x140333E20 @@ -1421,16 +1421,16 @@ namespace scripting {"setswitchnode", 0x827C}, // SP 0x140465740 MP 0x14055F4D0 {"setwaitspeed", 0x827D}, // SP 0x140465840 MP 0x14055F560 {"finishdamage", 0x827E}, // SP 0x000000000 MP 0x14055F5E0 - {"setspeed", 0x827F}, // SP 0x1404658C0 MP 0x14055F840 - {"setspeedimmediate", 0x8280}, // SP 0x140465930 MP 0x14055F8B0 - {"_meth_8281", 0x8281}, // SP 0x140465AC0 MP 0x14055FA60 - {"getspeed", 0x8282}, // SP 0x140465BE0 MP 0x14055FB80 - {"getvelocity", 0x8283}, // SP 0x140465CD0 MP 0x14055FC70 - {"getbodyvelocity", 0x8284}, // SP 0x140465D40 MP 0x14055FCE0 - {"getsteering", 0x8285}, // SP 0x140465DB0 MP 0x14055FD50 - {"getthrottle", 0x8286}, // SP 0x140465E30 MP 0x14055FDE0 - {"turnengineoff", 0x8287}, // SP 0x140465EA0 MP 0x14055FE50 - {"turnengineon", 0x8288}, // SP 0x140465F00 MP 0x14055FEC0 + {"vehicle_setspeed", 0x827F}, // SP 0x1404658C0 MP 0x14055F840 + {"vehicle_setspeedimmediate", 0x8280}, // SP 0x140465930 MP 0x14055F8B0 + {"vehicle_rotateyaw", 0x8281}, // SP 0x140465AC0 MP 0x14055FA60 + {"vehicle_getspeed", 0x8282}, // SP 0x140465BE0 MP 0x14055FB80 + {"vehicle_getvelocity", 0x8283}, // SP 0x140465CD0 MP 0x14055FC70 + {"vehicle_getbodyvelocity", 0x8284}, // SP 0x140465D40 MP 0x14055FCE0 + {"vehicle_getsteering", 0x8285}, // SP 0x140465DB0 MP 0x14055FD50 + {"vehicle_getthrottle", 0x8286}, // SP 0x140465E30 MP 0x14055FDE0 + {"vehicle_turnengineoff", 0x8287}, // SP 0x140465EA0 MP 0x14055FE50 + {"vehicle_turnengineon", 0x8288}, // SP 0x140465F00 MP 0x14055FEC0 {"_meth_8289", 0x8289}, // SP 0x140465F60 MP 0x000000000 {"getgoalspeedmph", 0x828A}, // SP 0x140466020 MP 0x14055FF30 {"_meth_828b", 0x828B}, // SP 0x140466090 MP 0x14055FFA0 @@ -1509,15 +1509,15 @@ namespace scripting {"visionsetthermalforplayer", 0x82D4}, // SP 0x140263710 MP 0x14032FD20 {"visionsetpainforplayer", 0x82D5}, // SP 0x140263730 MP 0x14032FD40 {"setblurforplayer", 0x82D6}, // SP 0x140264890 MP 0x140330B80 - {"_meth_82d7", 0x82D7}, // SP 0x140264C80 MP 0x140331310 - {"_meth_82d8", 0x82D8}, // SP 0x140264C80 MP 0x140331330 - {"_meth_82d9", 0x82D9}, // SP 0x1402ABE90 MP 0x000000000 - {"getbuildnumber", 0x82DA}, // SP 0x1402663A0 MP 0x14032A910 - {"_meth_82db", 0x82DB}, // SP 0x140266AF0 MP 0x14032AE90 - {"_meth_82dc", 0x82DC}, // SP 0x140266CD0 MP 0x14032B120 + {"getplayerweaponmodel", 0x82D7}, // SP 0x140264C80 MP 0x140331310 + {"getplayerknifemodel", 0x82D8}, // SP 0x140264C80 MP 0x140331330 + {"updateplayermodelwithweapons", 0x82D9}, // SP 0x1402ABE90 MP 0x000000000 + {"notifyonplayercommand", 0x82DA}, // SP 0x1402663A0 MP 0x14032A910 + {"canmantle", 0x82DB}, // SP 0x140266AF0 MP 0x14032AE90 + {"forcemantle", 0x82DC}, // SP 0x140266CD0 MP 0x14032B120 {"ismantling", 0x82DD}, // SP 0x140266FE0 MP 0x14032B500 {"playfx", 0x82DE}, // SP 0x140267330 MP 0x14032B9F0 - {"playerrecoilscaleon", 0x82DF}, // SP 0x140267530 MP 0x14032BD00 + {"player_recoilscaleon", 0x82DF}, // SP 0x140267530 MP 0x14032BD00 {"player_recoilscaleoff", 0x82E0}, // SP 0x140267600 MP 0x14032BDD0 {"weaponlockstart", 0x82E1}, // SP 0x1402676E0 MP 0x14032C000 {"weaponlockfinalize", 0x82E2}, // SP 0x140260240 MP 0x14032C240 From b0827b80dcd605832c5d7d0d03d724004642b8cb Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Fri, 8 Apr 2022 16:18:00 +0200 Subject: [PATCH 28/32] SP mods --- data/ui_scripts/mods/__init__.lua | 3 + data/ui_scripts/mods/loading.lua | 116 ++++++ src/client/component/filesystem.cpp | 39 ++ src/client/component/filesystem.hpp | 4 + src/client/component/fonts.cpp | 26 +- src/client/component/fonts.hpp | 1 + src/client/component/game_console.cpp | 2 +- src/client/component/input.cpp | 27 +- src/client/component/materials.cpp | 45 ++- src/client/component/materials.hpp | 1 + src/client/component/mods.cpp | 119 ++++++ src/client/component/mods.hpp | 6 + src/client/game/scripting/lua/engine.cpp | 22 +- src/client/game/symbols.hpp | 5 +- src/client/game/ui_scripting/lua/context.cpp | 33 ++ src/client/game/ui_scripting/lua/engine.cpp | 22 +- src/client/resource.hpp | 2 + src/client/resource.rc | 2 + src/client/resources/json.lua | 388 +++++++++++++++++++ src/common/utils/io.cpp | 5 + src/common/utils/io.hpp | 1 + 21 files changed, 831 insertions(+), 38 deletions(-) create mode 100644 data/ui_scripts/mods/__init__.lua create mode 100644 data/ui_scripts/mods/loading.lua create mode 100644 src/client/component/mods.cpp create mode 100644 src/client/component/mods.hpp create mode 100644 src/client/resources/json.lua diff --git a/data/ui_scripts/mods/__init__.lua b/data/ui_scripts/mods/__init__.lua new file mode 100644 index 00000000..1ca1f8d2 --- /dev/null +++ b/data/ui_scripts/mods/__init__.lua @@ -0,0 +1,3 @@ +if (game:issingleplayer()) then + require("loading") +end diff --git a/data/ui_scripts/mods/loading.lua b/data/ui_scripts/mods/loading.lua new file mode 100644 index 00000000..16cc3084 --- /dev/null +++ b/data/ui_scripts/mods/loading.lua @@ -0,0 +1,116 @@ +game:addlocalizedstring("MENU_MODS", "MODS") +game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.") +game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.") +game:addlocalizedstring("LUA_MENU_MOD_DESC", "&&1\nAuthor: &&2\nVersion: &&3") +game:addlocalizedstring("LUA_MENU_OPEN_STORE", "Open store") +game:addlocalizedstring("LUA_MENU_OPEN_STORE_DESC", "Download and install mods.") +game:addlocalizedstring("LUA_MENU_LOADED_MOD", "Loaded mod: ^2&&1") +game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods") +game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload") +game:addlocalizedstring("LUA_MENU_UNLOAD_DESC", "Unload the currently loaded mod.") + +function createdivider(menu, text) + local element = LUI.UIElement.new( { + leftAnchor = true, + rightAnchor = true, + left = 0, + right = 0, + topAnchor = true, + bottomAnchor = false, + top = 0, + bottom = 33.33 + }) + + element.scrollingToNext = true + element:addElement(LUI.MenuBuilder.BuildRegisteredType("h1_option_menu_titlebar", { + title_bar_text = Engine.ToUpperCase(text) + })) + + menu.list:addElement(element) +end + +function string:truncate(length) + if (#self <= length) then + return self + end + + return self:sub(1, length - 3) .. "..." +end + +LUI.addmenubutton("main_campaign", { + index = 6, + text = "@MENU_MODS", + description = Engine.Localize("@MENU_MODS_DESC"), + callback = function() + LUI.FlowManager.RequestAddMenu(nil, "mods_menu") + end +}) + +function getmodname(path) + local name = path + local desc = Engine.Localize("@LUA_MENU_MOD_DESC_DEFAULT", name) + local infofile = path .. "/info.json" + + if (io.fileexists(infofile)) then + pcall(function() + local data = json.decode(io.readfile(infofile)) + desc = Engine.Localize("@LUA_MENU_MOD_DESC", + data.description, data.author, data.version) + name = data.name + end) + end + + return name, desc +end + +LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1) + local menu = LUI.MenuTemplate.new(a1, { + menu_title = "@MENU_MODS", + exclusiveController = 0, + menu_width = 400, + menu_top_indent = LUI.MenuTemplate.spMenuOffset, + showTopRightSmallBar = true + }) + + local modfolder = game:getloadedmod() + if (modfolder ~= "") then + createdivider(menu, Engine.Localize("@LUA_MENU_LOADED_MOD", getmodname(modfolder):truncate(24))) + + menu:AddButton("@LUA_MENU_UNLOAD", function() + Engine.Exec("unloadmod") + end, nil, true, nil, { + desc_text = Engine.Localize("@LUA_MENU_UNLOAD_DESC") + }) + end + + createdivider(menu, Engine.Localize("@LUA_MENU_AVAILABLE_MODS")) + + if (io.directoryexists("mods")) then + local mods = io.listfiles("mods/") + for i = 1, #mods do + if (io.directoryexists(mods[i]) and not io.directoryisempty(mods[i])) then + local name, desc = getmodname(mods[i]) + + if (mods[i] ~= modfolder) then + game:addlocalizedstring(name, name) + menu:AddButton(name, function() + Engine.Exec("loadmod " .. mods[i]) + end, nil, true, nil, { + desc_text = desc + }) + end + end + end + end + + menu:AddBackButton(function(a1) + Engine.PlaySound(CoD.SFX.MenuBack) + LUI.FlowManager.RequestLeaveMenu(a1) + end) + + LUI.Options.InitScrollingList(menu.list, nil) + menu:CreateBottomDivider() + menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu) + + return menu +end diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index da11d2e4..d1827936 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace filesystem { @@ -70,6 +71,40 @@ namespace filesystem return this->name_; } + std::unordered_set& get_search_paths() + { + static std::unordered_set search_paths{}; + return search_paths; + } + + std::string read_file(const std::string& path) + { + for (const auto& search_path : get_search_paths()) + { + const auto path_ = search_path + "/" + path; + if (utils::io::file_exists(path_)) + { + return utils::io::read_file(path_); + } + } + + return {}; + } + + bool read_file(const std::string& path, std::string* data) + { + for (const auto& search_path : get_search_paths()) + { + const auto path_ = search_path + "/" + path; + if (utils::io::read_file(path_, data)) + { + return true; + } + } + + return false; + } + class component final : public component_interface { public: @@ -87,6 +122,10 @@ namespace filesystem utils::hook::call(SELECT_VALUE(0x1403B8D31, 0x1404EE3D0), register_custom_path_stub); utils::hook::call(SELECT_VALUE(0x1403B8D51, 0x1404EE3F0), register_custom_path_stub); utils::hook::call(SELECT_VALUE(0x1403B8D90, 0x1404EE42F), register_custom_path_stub); + + get_search_paths().insert("."); + get_search_paths().insert("h1-mod"); + get_search_paths().insert("data"); } }; } diff --git a/src/client/component/filesystem.hpp b/src/client/component/filesystem.hpp index 6cec8c87..c3d36dc6 100644 --- a/src/client/component/filesystem.hpp +++ b/src/client/component/filesystem.hpp @@ -16,4 +16,8 @@ namespace filesystem std::string name_; std::string buffer_; }; + + std::unordered_set& get_search_paths(); + std::string read_file(const std::string& path); + bool read_file(const std::string& path, std::string* data); } \ No newline at end of file diff --git a/src/client/component/fonts.cpp b/src/client/component/fonts.cpp index ae8f37fe..32f55399 100644 --- a/src/client/component/fonts.cpp +++ b/src/client/component/fonts.cpp @@ -3,6 +3,7 @@ #include "fonts.hpp" #include "console.hpp" +#include "filesystem.hpp" #include "game/game.hpp" #include "game/dvars.hpp" @@ -36,6 +37,13 @@ namespace fonts return font; } + void free_font(game::TTF* font) + { + utils::memory::get_allocator()->free(font->buffer); + utils::memory::get_allocator()->free(font->name); + utils::memory::get_allocator()->free(font); + } + game::TTF* load_font(const std::string& name) { return font_data.access([&](font_data_t& data_) -> game::TTF* @@ -51,9 +59,7 @@ namespace fonts data = i->second; } - if (data.empty() - && !utils::io::read_file(utils::string::va("h1-mod/%s", name.data()), &data) - && !utils::io::read_file(utils::string::va("data/%s", name.data()), &data)) + if (data.empty() && !filesystem::read_file(name, &data)) { return nullptr; } @@ -98,6 +104,20 @@ namespace fonts }); } + void clear() + { + font_data.access([&](font_data_t& data_) + { + for (auto& font : data_.fonts) + { + free_font(font.second); + } + + data_.fonts.clear(); + utils::hook::set(SELECT_VALUE(0x14F09DBB8, 0x14FD61EE8), 0); // reset registered font count + }); + } + class component final : public component_interface { public: diff --git a/src/client/component/fonts.hpp b/src/client/component/fonts.hpp index 15749bcf..c2d22869 100644 --- a/src/client/component/fonts.hpp +++ b/src/client/component/fonts.hpp @@ -3,4 +3,5 @@ namespace fonts { void add(const std::string& name, const std::string& data); + void clear(); } diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp index 23b63212..26d492ce 100644 --- a/src/client/component/game_console.cpp +++ b/src/client/component/game_console.cpp @@ -571,7 +571,7 @@ namespace game_console { if (key == game::keyNum_t::K_F10) { - if (!game::Com_InFrontEnd()) + if (!game::Com_InFrontend()) { return false; } diff --git a/src/client/component/input.cpp b/src/client/component/input.cpp index c1b39b9d..7801bcbd 100644 --- a/src/client/component/input.cpp +++ b/src/client/component/input.cpp @@ -15,13 +15,21 @@ namespace input utils::hook::detour cl_char_event_hook; utils::hook::detour cl_key_event_hook; + bool lui_running() + { + return *game::hks::lua_state != nullptr; + } + void cl_char_event_stub(const int local_client_num, const int key) { - ui_scripting::notify("keypress", + if (lui_running()) { - {"keynum", key}, - {"key", game::Key_KeynumToString(key, 0, 1)}, - }); + ui_scripting::notify("keypress", + { + {"keynum", key}, + {"key", game::Key_KeynumToString(key, 0, 1)}, + }); + } if (!game_console::console_char_event(local_client_num, key)) { @@ -33,11 +41,14 @@ namespace input void cl_key_event_stub(const int local_client_num, const int key, const int down) { - ui_scripting::notify(down ? "keydown" : "keyup", + if (lui_running()) { - {"keynum", key}, - {"key", game::Key_KeynumToString(key, 0, 1)}, - }); + ui_scripting::notify(down ? "keydown" : "keyup", + { + {"keynum", key}, + {"key", game::Key_KeynumToString(key, 0, 1)}, + }); + } if (!game_console::console_key_event(local_client_num, key, down)) { diff --git a/src/client/component/materials.cpp b/src/client/component/materials.cpp index 99b22a45..4fbb3ed8 100644 --- a/src/client/component/materials.cpp +++ b/src/client/component/materials.cpp @@ -3,6 +3,7 @@ #include "materials.hpp" #include "console.hpp" +#include "filesystem.hpp" #include "game/game.hpp" #include "game/dvars.hpp" @@ -20,6 +21,7 @@ namespace materials { utils::hook::detour db_material_streaming_fail_hook; utils::hook::detour material_register_handle_hook; + utils::hook::detour db_get_material_index_hook; struct material_data_t { @@ -68,6 +70,16 @@ namespace materials return material; } + void free_material(game::Material* material) + { + material->textureTable->u.image->textures.___u0.map->Release(); + material->textureTable->u.image->textures.shaderView->Release(); + utils::memory::get_allocator()->free(material->textureTable->u.image); + utils::memory::get_allocator()->free(material->textureTable); + utils::memory::get_allocator()->free(material->name); + utils::memory::get_allocator()->free(material); + } + game::Material* load_material(const std::string& name) { return material_data.access([&](material_data_t& data_) -> game::Material* @@ -83,9 +95,7 @@ namespace materials data = i->second; } - if (data.empty() - && !utils::io::read_file(utils::string::va("h1-mod/materials/%s.png", name.data()), &data) - && !utils::io::read_file(utils::string::va("data/materials/%s.png", name.data()), &data)) + if (data.empty() && !filesystem::read_file(utils::string::va("materials/%s.png", name.data()), &data)) { data_.materials[name] = nullptr; return nullptr; @@ -136,6 +146,16 @@ namespace materials return db_material_streaming_fail_hook.invoke(material); } + + unsigned int db_get_material_index_stub(game::Material* material) + { + if (material->constantTable == &constant_table) + { + return 0; + } + + return db_get_material_index_hook.invoke(material); + } } void add(const std::string& name, const std::string& data) @@ -146,6 +166,24 @@ namespace materials }); } + void clear() + { + material_data.access([&](material_data_t& data_) + { + for (auto& material : data_.materials) + { + if (material.second == nullptr) + { + continue; + } + + free_material(material.second); + } + + data_.materials.clear(); + }); + } + class component final : public component_interface { public: @@ -158,6 +196,7 @@ namespace materials material_register_handle_hook.create(game::Material_RegisterHandle, material_register_handle_stub); db_material_streaming_fail_hook.create(SELECT_VALUE(0x1401D3180, 0x1402C6260), db_material_streaming_fail_stub); + db_get_material_index_hook.create(SELECT_VALUE(0x1401CAD00, 0x1402BBB20), db_get_material_index_stub); } }; } diff --git a/src/client/component/materials.hpp b/src/client/component/materials.hpp index ac58b511..3a548548 100644 --- a/src/client/component/materials.hpp +++ b/src/client/component/materials.hpp @@ -3,4 +3,5 @@ namespace materials { void add(const std::string& name, const std::string& data); + void clear(); } diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp new file mode 100644 index 00000000..ea6a9027 --- /dev/null +++ b/src/client/component/mods.cpp @@ -0,0 +1,119 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "command.hpp" +#include "console.hpp" +#include "scheduler.hpp" +#include "filesystem.hpp" +#include "materials.hpp" +#include "fonts.hpp" +#include "mods.hpp" + +#include +#include + +namespace mods +{ + std::string mod_path{}; + + namespace + { + utils::hook::detour db_release_xassets_hook; + bool release_assets = false; + + void db_release_xassets_stub() + { + if (release_assets) + { + materials::clear(); + fonts::clear(); + } + + db_release_xassets_hook.invoke(); + } + + void restart() + { + scheduler::once([]() + { + release_assets = true; + game::Com_Shutdown(""); + release_assets = false; + }, scheduler::pipeline::main); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + if (!game::environment::is_sp()) + { + return; + } + + if (!utils::io::directory_exists("mods")) + { + utils::io::create_directory("mods"); + } + + db_release_xassets_hook.create(SELECT_VALUE(0x1401CD560, 0x1402BF160), db_release_xassets_stub); + + command::add("loadmod", [](const command::params& params) + { + if (params.size() < 2) + { + console::info("Usage: loadmod mods/"); + return; + } + + if (!game::Com_InFrontend()) + { + console::info("Cannot load mod while in-game!\n"); + game::CG_GameMessage(0, "^1Cannot unload mod while in-game!"); + return; + } + + const auto path = params.get(1); + if (!utils::io::directory_exists(path)) + { + console::info("Mod %s not found!\n", path); + return; + } + + console::info("Loading mod %s\n", path); + filesystem::get_search_paths().erase(mod_path); + filesystem::get_search_paths().insert(path); + mod_path = path; + restart(); + }); + + command::add("unloadmod", [](const command::params& params) + { + if (mod_path.empty()) + { + console::info("No mod loaded\n"); + return; + } + + if (!game::Com_InFrontend()) + { + console::info("Cannot unload mod while in-game!\n"); + game::CG_GameMessage(0, "^1Cannot unload mod while in-game!"); + return; + } + + console::info("Unloading mod %s\n", mod_path.data()); + filesystem::get_search_paths().erase(mod_path); + mod_path.clear(); + restart(); + }); + } + }; +} + +REGISTER_COMPONENT(mods::component) diff --git a/src/client/component/mods.hpp b/src/client/component/mods.hpp new file mode 100644 index 00000000..364a2f11 --- /dev/null +++ b/src/client/component/mods.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace mods +{ + extern std::string mod_path; +} \ No newline at end of file diff --git a/src/client/game/scripting/lua/engine.cpp b/src/client/game/scripting/lua/engine.cpp index 390a5995..936c8e1f 100644 --- a/src/client/game/scripting/lua/engine.cpp +++ b/src/client/game/scripting/lua/engine.cpp @@ -4,6 +4,7 @@ #include "../execution.hpp" #include "../../../component/logfile.hpp" +#include "../../../component/filesystem.hpp" #include @@ -49,18 +50,17 @@ namespace scripting::lua::engine { stop(); - load_scripts("h1-mod/scripts/"); - load_scripts("data/scripts/"); - - if (game::environment::is_sp()) + for (const auto& path : filesystem::get_search_paths()) { - load_scripts("h1-mod/scripts/sp/"); - load_scripts("data/scripts/sp/"); - } - else - { - load_scripts("h1-mod/scripts/mp/"); - load_scripts("data/scripts/mp/"); + load_scripts(path + "/scripts/"); + if (game::environment::is_sp()) + { + load_scripts(path + "/scripts/sp/"); + } + else + { + load_scripts(path + "/scripts/mp/"); + } } running = true; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 6653b555..58058533 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -33,10 +33,12 @@ namespace game WEAK symbol Com_Frame_Try_Block_Function{0x1401CE8D0, 0x1400D8310}; WEAK symbol Com_GetCurrentCoDPlayMode{0, 0x1405039A0}; - WEAK symbol Com_InFrontEnd{0x1400E4B30, 0x140176A30}; + WEAK symbol Com_InFrontend{0x1400E4B30, 0x140176A30}; WEAK symbol Com_SetSlowMotion{0, 0x1400DB790}; WEAK symbol Com_Error{0x1403509C0, 0x1400D78A0}; WEAK symbol Com_Quit_f{0x140352BE0, 0x1400DA830}; + WEAK symbol Com_Shutdown{0x140353B70, 0x1400DB8A0}; + WEAK symbol Quit{0x140352D90, 0x1400DA830}; WEAK symbol CG_GameMessage{0x1401389A0, 0x140220CC0}; @@ -47,6 +49,7 @@ namespace game bool isAlternate, char* outputBuffer, int bufferLen)> CG_GetWeaponDisplayName{0x14016EC30, 0x1400B5840}; WEAK symbol CL_IsCgameInitialized{0x14017EE30, 0x140245650}; + WEAK symbol CL_VirtualLobbyShutdown{0, 0x140256D40}; WEAK symbol Dvar_SetCommand{0x1403C72B0, 0x1404FD0A0}; WEAK symbol Dvar_FindVar{0x1403C5D50, 0x1404FBB00}; diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 1db94910..d9a930f9 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -13,6 +13,7 @@ #include "../../../component/localized_strings.hpp" #include "../../../component/fastfiles.hpp" #include "../../../component/scripting.hpp" +#include "../../../component/mods.hpp" #include "component/game_console.hpp" #include "component/scheduler.hpp" @@ -25,6 +26,31 @@ namespace ui_scripting::lua { namespace { + const auto json_script = utils::nt::load_resource(LUA_JSON); + + void setup_json(sol::state& state) + { + const auto json = state.safe_script(json_script, &sol::script_pass_on_error); + handle_error(json); + state["json"] = json; + } + + void setup_io(sol::state& state) + { + state["io"]["fileexists"] = utils::io::file_exists; + state["io"]["writefile"] = utils::io::write_file; + state["io"]["movefile"] = utils::io::move_file; + state["io"]["filesize"] = utils::io::file_size; + state["io"]["createdirectory"] = utils::io::create_directory; + state["io"]["directoryexists"] = utils::io::directory_exists; + state["io"]["directoryisempty"] = utils::io::directory_is_empty; + state["io"]["listfiles"] = utils::io::list_files; + state["io"]["copyfolder"] = utils::io::copy_folder; + state["io"]["removefile"] = utils::io::remove_file; + state["io"]["removedirectory"] = utils::io::remove_directory; + state["io"]["readfile"] = static_cast(utils::io::read_file); + } + void setup_types(sol::state& state, scheduler& scheduler) { struct game @@ -138,6 +164,11 @@ namespace ui_scripting::lua return std::string(buffer); }; + game_type["getloadedmod"] = [](const game&) + { + return mods::mod_path; + }; + auto userdata_type = state.new_usertype("userdata_"); userdata_type["new"] = sol::property( @@ -322,6 +353,8 @@ namespace ui_scripting::lua sol::lib::math, sol::lib::table); + setup_json(this->state_); + setup_io(this->state_); setup_types(this->state_, this->scheduler_); if (type == script_type::file) diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index cc3d3d33..a52b1b9e 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -3,6 +3,7 @@ #include "context.hpp" #include "../../../component/ui_scripting.hpp" +#include "../../../component/filesystem.hpp" #include #include @@ -52,18 +53,17 @@ namespace ui_scripting::lua::engine load_code(lui_common); load_code(lui_updater); - load_scripts("h1-mod/ui_scripts/"); - load_scripts("data/ui_scripts/"); - - if (game::environment::is_sp()) + for (const auto& path : filesystem::get_search_paths()) { - load_scripts("h1-mod/ui_scripts/sp/"); - load_scripts("data/ui_scripts/sp/"); - } - else - { - load_scripts("h1-mod/ui_scripts/mp/"); - load_scripts("data/ui_scripts/mp/"); + load_scripts(path + "/ui_scripts/"); + if (game::environment::is_sp()) + { + load_scripts(path + "/ui_scripts/sp/"); + } + else + { + load_scripts(path + "/ui_scripts/mp/"); + } } } diff --git a/src/client/resource.hpp b/src/client/resource.hpp index adc92d9c..ceb43a33 100644 --- a/src/client/resource.hpp +++ b/src/client/resource.hpp @@ -18,3 +18,5 @@ #define LUI_COMMON 309 #define LUI_UPDATER 310 + +#define LUA_JSON 311 diff --git a/src/client/resource.rc b/src/client/resource.rc index c53c106a..2996abfa 100644 --- a/src/client/resource.rc +++ b/src/client/resource.rc @@ -121,6 +121,8 @@ ICON_IMAGE RCDATA "resources/icon.png" LUI_COMMON RCDATA "resources/ui_scripts/common.lua" LUI_UPDATER RCDATA "resources/ui_scripts/updater.lua" +LUA_JSON RCDATA "resources/json.lua" + #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/src/client/resources/json.lua b/src/client/resources/json.lua new file mode 100644 index 00000000..711ef786 --- /dev/null +++ b/src/client/resources/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json diff --git a/src/common/utils/io.cpp b/src/common/utils/io.cpp index 4968f449..9b161d39 100644 --- a/src/common/utils/io.cpp +++ b/src/common/utils/io.cpp @@ -104,6 +104,11 @@ namespace utils::io return std::filesystem::is_empty(directory); } + bool remove_directory(const std::string& directory) + { + return std::filesystem::remove_all(directory); + } + std::vector list_files(const std::string& directory) { std::vector files; diff --git a/src/common/utils/io.hpp b/src/common/utils/io.hpp index ab4ebaa4..38344987 100644 --- a/src/common/utils/io.hpp +++ b/src/common/utils/io.hpp @@ -16,6 +16,7 @@ namespace utils::io bool create_directory(const std::string& directory); bool directory_exists(const std::string& directory); bool directory_is_empty(const std::string& directory); + bool remove_directory(const std::string& directory); std::vector list_files(const std::string& directory); void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target); } From b88c8d3eb013428df3cc6c599c822ec95966291d Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Fri, 8 Apr 2022 18:24:35 +0200 Subject: [PATCH 29/32] Some fixes --- src/client/component/logfile.cpp | 4 ++-- src/client/game/scripting/lua/engine.cpp | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/client/component/logfile.cpp b/src/client/component/logfile.cpp index d1886ca0..08ce07dd 100644 --- a/src/client/component/logfile.cpp +++ b/src/client/component/logfile.cpp @@ -296,6 +296,8 @@ namespace logfile public: void post_unpack() override { + utils::hook::jump(SELECT_VALUE(0x140376655, 0x140444645), utils::hook::assemble(vm_execute_stub), true); + if (game::environment::is_sp()) { return; @@ -308,8 +310,6 @@ namespace logfile utils::hook::call(0x140484EC0, g_shutdown_game_stub); utils::hook::call(0x1404853C1, g_shutdown_game_stub); - - utils::hook::jump(SELECT_VALUE(0x140376655, 0x140444645), utils::hook::assemble(vm_execute_stub), true); } }; } diff --git a/src/client/game/scripting/lua/engine.cpp b/src/client/game/scripting/lua/engine.cpp index 936c8e1f..6d025409 100644 --- a/src/client/game/scripting/lua/engine.cpp +++ b/src/client/game/scripting/lua/engine.cpp @@ -49,7 +49,7 @@ namespace scripting::lua::engine void start() { stop(); - + running = true; for (const auto& path : filesystem::get_search_paths()) { load_scripts(path + "/scripts/"); @@ -62,8 +62,6 @@ namespace scripting::lua::engine load_scripts(path + "/scripts/mp/"); } } - - running = true; } void notify(const event& e) From 698b94a78673f4355fa2a99ccb0c7e1a26c1d811 Mon Sep 17 00:00:00 2001 From: Clayton <42688647+netadr@users.noreply.github.com> Date: Sat, 9 Apr 2022 01:15:51 -0400 Subject: [PATCH 30/32] Add debug-dir option for setting the VS working directory (#43) --- premake5.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/premake5.lua b/premake5.lua index 9823d960..fc9e6e5b 100644 --- a/premake5.lua +++ b/premake5.lua @@ -321,6 +321,10 @@ if _OPTIONS["copy-to"] then postbuildcommands {"copy /y \"$(TargetPath)\" \"" .. _OPTIONS["copy-to"] .. "\""} end +if _OPTIONS["debug-dir"] then + debugdir ( _OPTIONS["debug-dir"] ) +end + dependencies.imports() project "tlsdll" From 8d5e714a63b6052690b4fbc85a6662774a565c57 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Sun, 10 Apr 2022 21:40:20 +0200 Subject: [PATCH 31/32] Add g_dumpScripts dvar --- src/client/component/scripting.cpp | 33 ++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index 9aa37307..b31e2329 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -2,6 +2,7 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" +#include "game/dvars.hpp" #include "game/scripting/entity.hpp" #include "game/scripting/functions.hpp" @@ -13,6 +14,8 @@ #include "scripting.hpp" #include +#include +#include namespace scripting { @@ -34,9 +37,13 @@ namespace scripting utils::hook::detour sl_get_canonical_string_hook; + utils::hook::detour db_find_xasset_header_hook; + std::string current_file; unsigned int current_file_id{}; + game::dvar_t* g_dump_scripts; + void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value, game::VariableValue* top) { @@ -152,6 +159,26 @@ namespace scripting scripting::token_map[str] = result; return result; } + + game::XAssetHeader db_find_xasset_header_stub(game::XAssetType type, const char* name, int allow_create_default) + { + const auto result = db_find_xasset_header_hook.invoke(type, name, allow_create_default); + if (!g_dump_scripts->current.enabled || type != game::XAssetType::ASSET_TYPE_SCRIPTFILE) + { + return result; + } + + std::string buffer; + buffer.append(result.scriptfile->name, strlen(result.scriptfile->name) + 1); + buffer.append(reinterpret_cast(&result.scriptfile->compressedLen), 4); + buffer.append(reinterpret_cast(&result.scriptfile->len), 4); + buffer.append(reinterpret_cast(&result.scriptfile->bytecodeLen), 4); + buffer.append(result.scriptfile->buffer, result.scriptfile->compressedLen); + buffer.append(result.scriptfile->bytecode, result.scriptfile->bytecodeLen); + utils::io::write_file(utils::string::va("gsc_dump/%s.gscbin", name), buffer); + + return result; + } } class component final : public component_interface @@ -165,6 +192,7 @@ namespace scripting scr_set_thread_position_hook.create(SELECT_VALUE(0x14036A180, 0x140437D10), scr_set_thread_position_stub); process_script_hook.create(SELECT_VALUE(0x1403737E0, 0x1404417E0), process_script_stub); + sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, sl_get_canonical_string_stub); if (!game::environment::is_sp()) { @@ -177,7 +205,8 @@ namespace scripting g_shutdown_game_hook.create(SELECT_VALUE(0x140277D40, 0x140345A60), g_shutdown_game_stub); - sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, sl_get_canonical_string_stub); + db_find_xasset_header_hook.create(game::DB_FindXAssetHeader, db_find_xasset_header_stub); + g_dump_scripts = dvars::register_bool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts"); scheduler::loop([]() { @@ -187,4 +216,4 @@ namespace scripting }; } -REGISTER_COMPONENT(scripting::component) \ No newline at end of file +REGISTER_COMPONENT(scripting::component) From 7786faa6f418d5f0da4fa7cb6677d74944a8a1ac Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Sun, 10 Apr 2022 21:40:27 +0200 Subject: [PATCH 32/32] Update main.html --- src/client/resources/main.html | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/client/resources/main.html b/src/client/resources/main.html index 887b6863..52a2756f 100644 --- a/src/client/resources/main.html +++ b/src/client/resources/main.html @@ -18,8 +18,7 @@ body { -ms-overflow-style: none; margin: 0; - /* Also we can use image incoder https://www.base64-image.de/ */ - background: url(https://cdn.discordapp.com/attachments/895680402142941194/935150813901365268/360_F_176672598_cJ4yPCFhxvDXm9Cu7vDLIcXpvTMQJ9zm.jpg) no-repeat center center fixed; + background: url('data:image/jpeg;base64,/9j/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAA8cAVoAAxslRxwBAAACAAQA/+EMgWh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSfvu78nIGlkPSdXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQnPz4KPHg6eG1wbWV0YSB4bWxuczp4PSdhZG9iZTpuczptZXRhLycgeDp4bXB0az0nSW1hZ2U6OkV4aWZUb29sIDEwLjEwJz4KPHJkZjpSREYgeG1sbnM6cmRmPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjJz4KCiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0nJwogIHhtbG5zOnRpZmY9J2h0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvJz4KICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogIDx0aWZmOlhSZXNvbHV0aW9uPjMwMC8xPC90aWZmOlhSZXNvbHV0aW9uPgogIDx0aWZmOllSZXNvbHV0aW9uPjMwMC8xPC90aWZmOllSZXNvbHV0aW9uPgogPC9yZGY6RGVzY3JpcHRpb24+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9JycKICB4bWxuczp4bXBNTT0naHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyc+CiAgPHhtcE1NOkRvY3VtZW50SUQ+YWRvYmU6ZG9jaWQ6c3RvY2s6Y2YxZTI3ZTYtN2Q3ZS00MTg4LTlhOGItNzAyNzdiOTRmZmExPC94bXBNTTpEb2N1bWVudElEPgogIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6ZWQwYmQ4OTItYWMzOS00MTdmLWFkNmQtNTQyM2EyY2IzNDI5PC94bXBNTTpJbnN0YW5jZUlEPgogPC9yZGY6RGVzY3JpcHRpb24+CjwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9J3cnPz7/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/wAALCAFoAhwBAREA/8QAGQABAQEBAQEAAAAAAAAAAAAAAQIAAwQH/8QAGRABAQEBAQEAAAAAAAAAAAAAAAERAhJh/9oACAEBAAA/APhXNdOavmunNdOa6c1fNdOa6c105rpzV810lXzVyrlXKuVcqpVyrlVKqVUqpVSnTp062tra2totGi0Wi1NqbU2ptTam1FqbUWotTai1FqLUWotRai1HVRa52o6qLUWo6qLUWvPzXTmr5rpzXTmr5rpzXTmunNXzXTmunNXKvmrlXKuVcqpVyrlVKqVUqpVTpUplMp062tp0a3oaLRaLRam0WptTai1NqbUWptRanqotRai1FqLUWotR1UWotR1UWotR1UWotcOavmr5rpzXTmr5rpzXTmr5rpzV810lXKvmrlXKuVUq5VyqlVKuVUqpVSmVUradOtra2traNGtqbRam0WptTam1NqLU2ptRai1NqLUWotRai1Nrnai1FqLU2udqbUWotcOavmunNXzXTmr5rpzV8105q+avmunNXKuVcq5VSrlXKqVUq5VSqlVKqUyqlOtp1tOto1tGj0NFotFqbU2ptTam1NqbUWptRam1FqLUWp6qLUWotRai1FqLUWotRam1w5q+a6c1fNXzXSVfNdOavmr5rpzV81cq5VyqlXKuVUq5VSqlVKqVUqpTKqdHTra2nRra2jW0WjU2i1NotTam1NqbUWptTai1NqLUWptRai1FqLU2otRai1HVRai1OuHNXzXTmr5q5XSVfNXzV8105q+auVcq5VSrlXKqVcqpVSqlXKZVSqlMqtOnW062to1tGto0am0WptFqbU2ptTam1NqLU2otTai1NqLUWptRai1FqLUWptRai1OuErpzVyr5q+auVcrpKvmr5q5VyrlXKqVcqpVyqlXKqVUqpVSmVUplVKZTradb03oa2totGi0WptFqbRam1NqbU2otTam1FqbUWptRam1FqLUWptRai1FqbUWptcZVyrlXKuVfNXzVyr5q5V81cq5VyqlXKqVcqpVSqlVKqVUqpTKZVadOnW1tbW0a2jRo0WptFotTam1NqbU2ptTam1FqbUWptRam1FqbUWotTai1FqbUWptcZVyrlXKvmrlXKuVcq5VyrlVKuVUq5VSqlXKqVUqpTKqVUplVplMp1vR1tbW0a2i0aLRaLRam1NotTam1NqbU2otTam1FqbU2otTai1FqbUWptRai1NqdcZV81cq5VSrlXKuVcq5VyrlVKuVUq5VSqlVKqVUqpVSqlMqpTKdOnTra2tra2jRotGjRaLU2i1NqbRam1NqLU2ptTam1FqbUWptRam1FqbUWptRam1OuMq5VyrlVKuVcq5VSr5q5VyqlXKqVUqpVyqlVKqUyqlVKZVTo6Z0fR9N6PpvTa3oa2jW9C9C9J0XoXpNotTam1NqbU2ptTam1NqLU2ptRam1FqbUWptRam1NqdcZVyqlXKuVUq5VyqlXKuVUq5VSqlXKqVUqpVSqlM6VOjOlTozoyq9N6Po+m9H03pvQ9N6HoXoeheheheham9C1NqbU2i1NqbUWptTam1NqLU2ptRam1NqLU2otTanXKVUq5VSrlXKqVcqpVyrlVKqVcqpVSqlVKqVUqpTOlSmdKnRnRnR0zpvR9H03pvTem0em9D0PQvQtFqbRaLU2ptTaLU2ptTam1NqbUWptTam1FqbU2otTam1NqdcpVSqlXKqVcqpVyqlXKqVcqpVSqlVKqVUqpVSmVUqp0Z0Z0dV6b0fR9H03pvR9N6HpvQ9N6F6FotFqbRaLU2ptFqbU2ptTam1NqbU2ptTam1FqbU2ptTai1No1xlXKqVUq5VSrlVKqVcqpVSrlVKqVUplVKqVUpnSp0dVOjKfR9HTp9Np1vTem9N6Gto9DW1NranRam0WptFqbU2i1NqbU2ptTam1NqbU2otTam1NqbU2ptcpVSrlVKqVUq5VSqlXKqVUqpVSqlVKqVUplVKqUymVU6OnTp9N6Po63pvR9N6b0PTeheh6GjRaLRaLU2i1NqbRam1NqbRam1NqbU2ptTam1NqLU2ptTaNcpVSqlVKuVUqpVSrlVKqVUqpVSqlVKZVSmVUqpTKdVKZ0fR063o+m9H03pvTem9NrWjR6Gi0XoaLRam0WptFqbRam1NqbU2i1NqbU2ptTam1NqbU2ptTa5yqlVKqVUqpVSqlXKqVUqpVSmVUqpVSmVUplVKZTKdVplOtp0+m0+m1vTem1vTaNGi0aLRaLRaLU2i1NotTam0WptTam0WptTam1NqbU2ptFqbU65yqlVKqVUqpVSqlVKqVUqpVSmVUqpTKqUyqlMp1Up0ynT6bT6Otrej6bW1tb0NbR6GtaNTotFo0WptFqbRam0WptTam0WptTam1NotTam1NqbRrnKZVSqlVKqVUqpVSqlVKqUyqlVKZVSmVUplMqtOmU6ZTradbTradbW1tbRraNGtotGi1No0aLU2i1NotTaLU2ptFqbU2i1NqbU2i1NqbRrnKZVSqlVKqVUqpTKqVUqpTKqVUplMqpTKqUymUymU6dOtp062tp1vTem9N6Gto1tGi0Wi0Wi0WptFotTaLU2i1NqbRam0WptTaLU2ptFqbRrnKZVSqlVKZVSqlVKqUyqlMqpTKqUyqlMplMplOnTp06dbTra2nW1tbW0a2to0aNFo0Wi0Wi0WptFotTaLU2i1NotTam0WptFqbU2i0a5ymVUqpTKqVUqpTKqVUplVKZTKqUymVUp0ymU6dOtp062nW062tra2traNbRraNGjRaLRotFqbRaLU2i1NotFqbU2i1NotTaLU2i1OolMqpTKqVUplVKqUyqlMqpTKZVSmU6ZVSmU6dbTp062nW062tra2tra2to1tGjW0aLRaNFqbRaLRam0Wi1Oi1NotFqbRam0WptFo1zlMqpTKqUyqlVKZVSmVUplMqpTKZTp06dOnTp062nW062tradbW0a2traNbRo0WjWtGptFotFotFqbRaLU2i0WptFqbRam0Wi0a5ymVUplVKZVSmVUplVKZTKqU6ZTplOmU6dOnTradbTradbW1tbW1tbW0a2jW0aNGjRaLRotFotGptFotFqbRaLRam0am0Wi0a5yqlMplVKdVKZVSmUyqlOmUynTplOmU6dOnW06dbTra2nW1tbW1tbRra2jW0aNGjRo1tTaLRaLRaLRam0Wi0am0Wi1OjRaNRKZTKqUymVUplMqpTplMp06qVtVradOnTradOtp1tOtra2tp0a2tra2jW0a2jRaNbRotGjRaLU6LRaNFotTaNFotFqbRaESmUyqlMplVKZTplVKdOmU6ZTKdMp1tOnTradbTra2nW1tbW1tbW1tbRraNbRraLRotGjRaNFotGi0WjU2i0WjRaLRqbQiKhlMplMqpTplMp0ymU6qVtOnTp062nTradbTra2nW1tbW1tbW1tbRrb9Gto1tGjRo1rRotGjRaNTotGjRaNGi0WjQgwynTKqUymUynTplOmU6dOnW06dOtp062nfra2nW1tbW362/W362tra2jW1tGto0a1o0WjRaNFo0aLRo0WjRaLRotFotCITqjDKdMp06ZTplOnTp1tOnTradOtrb9Otradbfrb9bTra2to362/W362jW0b9bW0aNFo1tGi0aLRo0WjRaNGijRoo1kSmGU6TplMp0ynTp06dbTp0ytp062nW062nW1tOtra2tra2traNbW0a2jW0a2jRo1rRotGi0aNGjWo0UaNA0JlJlbTDph06ZTplOmU62nTK2nTraZTra2nfra2nW1tbfra2trb9bW1tGtraNbW0aNbRo0a2jRo0aNa0aKNA0aG0IlJOk6Tp0tp06dOtp062nW06dbW062tp362tp1tbW1tbW0a2tv0a2traNbRo1tFo1rRotGitoGgaG0DQNSdJ1jKTp0nW06dOtpOtp1tOtp1tOtv1tOtra2tv1t+tv1t+tv1tbW1tGtraNbW0aNbRo1tGjW0DQNYaBoFrDUmVjpMpLadJ1jpbTp1tOtp1tOtradZtOtra2tra2tra2to1tbRra2jW0a2jQ2ga2gaG0DQ2gaG1OsZSdLaTp1jpbTp1tLadbTra2nW062tra2nfrb9bfra2tv1t+tv0a2traNbRra2jW0aw0No0NoGhtA1gNZLGUtpOltJbSdbS2nWOtpbTraxbW1tbW062tra2jW1tbW1g2to1hrawGsNGsNDaA2gaw0JMpbSTrHS2nWOnWOtpbTrMdbW1tLa2tp1tbW1tbW0a2trDW1tbWDaNYa2htGhtA1tA1gGGplJbSdLHWOljrFtOs2nW0tra2nWbTra2tra2tra2tobWbW0a2sNbRrNo1ho1hraBrBtA1gGOsdLHSx1jraWOtrHW06zNp1tbWLM2tra2trMwZtbRrM2jW0NraGGhhrBtDaA2hJ0tpY6WOsW06x1tLNp1tYtra2nW1mZmZmZmZtGtrawbW0azDW0No1ho1m0aGGsAx1i2ljpZtOlm06zHW0szadbW1mZizMwZmbW1tGszDW0M2jWYaGbQG0azDQG0sdLNpY62ljraWbW06zHW1tbW0szMzMzMzDW1tbW0M2jW1mGtoYawbRrMNDNr/2Q==') no-repeat center center fixed; background-size: cover; background-position: center; background-repeat: no-repeat; @@ -416,8 +415,7 @@
- - +
Singleplayer @@ -435,7 +433,7 @@
- +
Multiplayer @@ -462,7 +460,7 @@
-
+

Special thanks to all contributors.

This project is based on S1x and H2-mod.