diff --git a/.gitmodules b/.gitmodules index 3fa56e25..c93e05aa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -43,3 +43,6 @@ [submodule "deps/curl"] path = deps/curl url = https://github.com/curl/curl.git +[submodule "deps/gsc-tool-h2"] + path = deps/gsc-tool-h2 + url = https://github.com/fedddddd/gsc-tool-h2.git diff --git a/assets/github/banner.png b/assets/github/banner.png index ba02a20d..ffa68664 100644 Binary files a/assets/github/banner.png and b/assets/github/banner.png differ diff --git a/deps/extra/gsc-tool/interface.cpp b/deps/extra/gsc-tool/interface.cpp new file mode 100644 index 00000000..e9964b5c --- /dev/null +++ b/deps/extra/gsc-tool/interface.cpp @@ -0,0 +1,20 @@ +#include "stdafx.hpp" + +#include + +#include "interface.hpp" + +namespace gsc +{ + std::unique_ptr compiler() + { + auto compiler = std::make_unique(); + compiler->mode(xsk::gsc::build::prod); + return compiler; + } + + std::unique_ptr assembler() + { + return std::make_unique(); + } +} diff --git a/deps/extra/gsc-tool/interface.hpp b/deps/extra/gsc-tool/interface.hpp new file mode 100644 index 00000000..4d98ad35 --- /dev/null +++ b/deps/extra/gsc-tool/interface.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace gsc +{ + std::unique_ptr compiler(); + std::unique_ptr assembler(); +} diff --git a/deps/gsc-tool-h2 b/deps/gsc-tool-h2 new file mode 160000 index 00000000..b18d79c1 --- /dev/null +++ b/deps/gsc-tool-h2 @@ -0,0 +1 @@ +Subproject commit b18d79c1da68c8bd97bbba91b72321a1302e651f diff --git a/deps/libtomcrypt b/deps/libtomcrypt index 8fd5dad9..ddfe2e8a 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit 8fd5dad96b56beb53b5cf199cb63fb76dfba32bb +Subproject commit ddfe2e8aa7c4239463a8a1d26724aef123333549 diff --git a/deps/lua b/deps/lua index d61b0c60..997f11f5 160000 --- a/deps/lua +++ b/deps/lua @@ -1 +1 @@ -Subproject commit d61b0c60287c38008d312ddd11724a15b1737f7b +Subproject commit 997f11f54322883c3181225f29d101a597f31730 diff --git a/deps/premake/gsc-tool.lua b/deps/premake/gsc-tool.lua new file mode 100644 index 00000000..0c5367f4 --- /dev/null +++ b/deps/premake/gsc-tool.lua @@ -0,0 +1,68 @@ +gsc_tool = { + source = path.join(dependencies.basePath, "gsc-tool-h2/src") +} + +function gsc_tool.import() + links {"xsk-gsc-h2", "xsk-gsc-utils"} + gsc_tool.includes() +end + +function gsc_tool.includes() + includedirs { + path.join(gsc_tool.source, "utils"), + path.join(gsc_tool.source, "h2"), + path.join(dependencies.basePath, "extra/gsc-tool") -- https://github.com/GEEKiDoS/open-teknomw3/blob/master/deps/extra/gsc-tool + } +end + +-- https://github.com/xensik/gsc-tool/blob/dev/premake5.lua#L95 +function gsc_tool.project() + project "xsk-gsc-utils" + kind "StaticLib" + language "C++" + + pchheader "stdafx.hpp" + pchsource(path.join(gsc_tool.source, "utils/stdafx.cpp")) + + files { + path.join(gsc_tool.source, "utils/**.h"), + path.join(gsc_tool.source, "utils/**.hpp"), + path.join(gsc_tool.source, "utils/**.cpp") + } + + includedirs { + path.join(gsc_tool.source, "utils"), + gsc_tool.source + } + + zlib.includes() + + project "xsk-gsc-h2" + kind "StaticLib" + language "C++" + + pchheader "stdafx.hpp" + pchsource(path.join(gsc_tool.source, "h2/stdafx.cpp")) + + files { + path.join(gsc_tool.source, "h2/**.h"), + path.join(gsc_tool.source, "h2/**.hpp"), + path.join(gsc_tool.source, "h2/**.cpp"), + path.join(dependencies.basePath, "extra/gsc-tool/interface.cpp") + } + + includedirs { + path.join(gsc_tool.source, "h2"), + gsc_tool.source, + path.join(dependencies.basePath, "extra/gsc-tool") + } + + -- https://github.com/xensik/gsc-tool/blob/dev/premake5.lua#L25 + -- adding these build options fixes a bunch of parser stuff + filter "action:vs*" + buildoptions "/bigobj" + buildoptions "/Zc:__cplusplus" + filter {} +end + +table.insert(dependencies, gsc_tool) diff --git a/deps/rapidjson b/deps/rapidjson index 27c3a8dc..06d58b9e 160000 --- a/deps/rapidjson +++ b/deps/rapidjson @@ -1 +1 @@ -Subproject commit 27c3a8dc0e2c9218fe94986d249a12b5ed838f1d +Subproject commit 06d58b9e848c650114556a23294d0b6440078c61 diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index 4e955778..cb6efc97 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -168,14 +168,15 @@ namespace fastfiles { constexpr int asset_pool_sizes[] = { - 150, 1024, 16, 1, 128, 7000, 5248, 5120, - 10624, 256, 49152, 12288, 12288, 72864, - 512, 3072, 12000, 16000, 256, 64, 64, 64, - 64, 10000, 1, 1, 1, 1, 1, 2, 1, 1, 32, 0, - 128, 400, 0, 11500, 128, 360, 1, 2048, 4, - 6, 0, 0, 0, 0, 1024, 768, 400, 128, 128, - 24, 24, 24, 32, 128, 2, 0, 64, 384, 128, - 1, 128, 64, 32, 32, 16, 32, 16 + 150, 1024, 16, 1, 128, 7000, 5248, 2560, + 10624, 256, 49152, 12288, 12288, 72864, + 512, 3072, 12000, 16000, 256, 64, 64, + 64, 64, 10000, 1, 1, 1, 1, 1, 2, 1, + 1, 32, 0, 128, 400, 0, 11500, 128, + 360, 1, 2048, 4, 6, 0, 0, 0, 0, 1024, + 768, 400, 128, 128, 24, 24, 24, 32, + 128, 2, 0, 64, 384, 128, 1, 128, 64, + 32, 32, 16, 32, 16 }; return asset_pool_sizes[type]; @@ -204,15 +205,116 @@ namespace fastfiles return reallocate_asset_pool(); } - void reallocate_asset_pools() +#define RVA(ptr) static_cast(reinterpret_cast(ptr) - 0x140000000) + + void reallocate_xmodel_pool() { - const auto xmodel_pool = reallocate_asset_pool_multiplier(); + // array used for DB_GetAllXAssetOfType, not big enough if many assets are added + static game::XAssetHeader assets[0x100000]{}; + utils::hook::inject(0x1403E2AB7, &assets); + utils::hook::inject(0x1403E2AC3, &assets); + utils::hook::inject(0x1403E2ACF, &assets); + + constexpr auto xmodel_pool_size = get_pool_type_size(game::ASSET_TYPE_XMODEL) * 2; + const auto xmodel_pool = reallocate_asset_pool(); utils::hook::inject(0x140413D93, xmodel_pool + 8); - /*const auto image_pool = reallocate_asset_pool_multiplier(); - utils::hook::inject(0x140413B45, image_pool + 8); - utils::hook::inject(0x140413B63, image_pool + 8);*/ + // table 1 + static int xmodel_table_1[xmodel_pool_size]{}; + utils::hook::set(0x14041E0C2 + 4, RVA(&xmodel_table_1[0])); + utils::hook::inject(0x14041E7F6 + 3, &xmodel_table_1[0]); + utils::hook::set(0x140420797 + 4, RVA(&xmodel_table_1[0])); + utils::hook::inject(0x1404228F6 + 3, &xmodel_table_1[0]); + utils::hook::inject(0x14042290E + 3, &xmodel_table_1[0]); + utils::hook::set(0x140710280 + 4, RVA(&xmodel_table_1[0])); + // everything below doesnt seem to change anything + // table 2 + static int xmodel_table_2[xmodel_pool_size]{}; + utils::hook::set(0x14041E2FA + 4, RVA(&xmodel_table_2[0])); + utils::hook::set(0x1404207BC + 4, RVA(&xmodel_table_2[0])); + utils::hook::inject(0x140422AE1 + 3, &xmodel_table_2[0]); + utils::hook::set(0x140422B20 + 4, RVA(&xmodel_table_2[0])); + utils::hook::inject(0x140422B8D + 3, &xmodel_table_2[0]); + utils::hook::set(0x140422BC7 + 4, RVA(&xmodel_table_2[0])); + utils::hook::inject(0x140422C41 + 3, &xmodel_table_2[0]); + utils::hook::set(0x140422CE0 + 4, RVA(&xmodel_table_2[0])); + utils::hook::set(0x140422D16 + 4, RVA(&xmodel_table_2[0])); + utils::hook::set(0x140723BAE + 5, RVA(&xmodel_table_2[0])); + utils::hook::set(0x140723BCC + 5, RVA(&xmodel_table_2[0])); + utils::hook::inject(0x140728332 + 3, &xmodel_table_2[0]); + + // table 3 + static int xmodel_table_3[xmodel_pool_size]{}; + utils::hook::set(0x1404207D4 + 4, RVA(&xmodel_table_3[0])); + utils::hook::set(0x140724BA3 + 5, RVA(&xmodel_table_3[0])); + utils::hook::set(0x140724BC1 + 5, RVA(&xmodel_table_3[0])); + + // table 4 + static int xmodel_table_4[xmodel_pool_size]{}; + utils::hook::set(0x1404207C8 + 4, RVA(&xmodel_table_4[0])); + utils::hook::inject(0x140422888 + 3, &xmodel_table_4[0]); + utils::hook::inject(0x14041EAC0 + 3, reinterpret_cast(reinterpret_cast(&xmodel_table_4[0]) + 0x10)); + + // table 5 + static int xmodel_table_5[xmodel_pool_size]{}; + utils::hook::set(0x1404205BC + 4, RVA(&xmodel_table_5[0])); + utils::hook::set(0x14042062D + 4, RVA(&xmodel_table_5[0])); + utils::hook::inject(0x140420A35 + 3, &xmodel_table_5[0]); + + // hash table 1 + static int xmodel_hash_table_1[xmodel_pool_size]{}; + utils::hook::set(0x1404207AA + 4, RVA(&xmodel_hash_table_1[0])); + utils::hook::inject(0x1404208DE + 3, &xmodel_hash_table_1[0]); + utils::hook::inject(0x140422535 + 2, &xmodel_hash_table_1[0]); + + // hash table 2 + static int xmodel_hash_table_2[xmodel_pool_size]{}; + utils::hook::inject(0x1403E2A8E + 3, &xmodel_hash_table_2[0]); + utils::hook::inject(0x1403E2FD9 + 3, &xmodel_hash_table_2[0]); + utils::hook::inject(0x1403E37C5 + 3, &xmodel_hash_table_2[0]); + + // hash table 2 + static int xmodel_hash_table_3[xmodel_pool_size]{}; + utils::hook::inject(0x1403E2A8E + 3, &xmodel_hash_table_3[0]); + utils::hook::inject(0x1403E2FD9 + 3, &xmodel_hash_table_3[0]); + utils::hook::inject(0x1403E37C5 + 3, &xmodel_hash_table_3[0]); + + // hash table 3 + static int xmodel_hash_table_4[xmodel_pool_size]{}; + utils::hook::set(0x1404207B4 + 4, RVA(&xmodel_hash_table_4[0])); + utils::hook::inject(0x140422AD7 + 3, &xmodel_hash_table_4[0]); + utils::hook::set(0x140422B18 + 4, RVA(&xmodel_hash_table_4[0])); + utils::hook::inject(0x140422B83 + 3, &xmodel_hash_table_4[0]); + utils::hook::set(0x140422BBF + 4, RVA(&xmodel_hash_table_4[0])); + utils::hook::inject(0x140422C37 + 3, &xmodel_hash_table_4[0]); + utils::hook::inject(0x140422C97 + 3, &xmodel_hash_table_4[0]); + utils::hook::set(0x140422CD8 + 4, RVA(&xmodel_hash_table_4[0])); + utils::hook::set(0x140422D0E + 4, RVA(&xmodel_hash_table_4[0])); + + // seems to fix some issues but causes models to load slowly, probably not done correctly + static int xmodel_table_7[xmodel_pool_size * 6]{}; + utils::hook::set(0x1404205AF + 3, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 0))); + utils::hook::set(0x140420752 + 4, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 0))); + + utils::hook::set(0x1404205A7 + 4, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 8))); + utils::hook::set(0x14042065B + 4, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 8))); + utils::hook::set(0x14042068D + 4, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 8))); + utils::hook::set(0x1404206AF + 4, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 8))); + utils::hook::set(0x1404206F0 + 4, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 8))); + utils::hook::set(0x140420720 + 4, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 8))); + utils::hook::set(0x14042075F + 4, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 8))); + + utils::hook::set(0x14041EE9B + 4, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 0x10))); + utils::hook::set(0x1404205A0 + 3, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 0x10))); + utils::hook::set(0x14042060D + 5, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 0x10))); + utils::hook::set(0x140420618 + 3, RVA(reinterpret_cast(reinterpret_cast(&xmodel_table_7) + 0x10))); + } + + void reallocate_asset_pools() + { + //reallocate_xmodel_pool(); + //reallocate_asset_pool_multiplier(); reallocate_asset_pool_multiplier(); reallocate_asset_pool_multiplier(); reallocate_asset_pool_multiplier(); @@ -401,6 +503,20 @@ namespace fastfiles console::info("%i %s: %i / %i\n", type, game::g_assetNames[type], count, game::g_poolSize[type]); }); + + command::add("assetCount", [](const command::params& params) + { + auto count = 0; + for (auto i = 0; i < game::ASSET_TYPE_COUNT; i++) + { + enum_assets(static_cast(i), [&](game::XAssetHeader header) + { + count++; + }, true); + } + + console::info("assets: %i / %i\n", count, 155000); + }); } }; } diff --git a/src/client/component/gsc.cpp b/src/client/component/gsc.cpp new file mode 100644 index 00000000..8d046315 --- /dev/null +++ b/src/client/component/gsc.cpp @@ -0,0 +1,524 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "console.hpp" +#include "filesystem.hpp" +#include "scripting.hpp" +#include "gsc.hpp" +#include "scheduler.hpp" + +#include "game/scripting/execution.hpp" +#include "game/scripting/functions.hpp" +#include "game/scripting/lua/error.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace gsc +{ + namespace + { + game::dvar_t* developer_script = nullptr; + + std::unordered_map opcodes = + { + {0x17, "SET_NEW_LOCAL_VARIABLE_FIELD_CACHED0"}, + {0x18, "EVAL_SELF_FIELD_VARIABLE"}, + {0x19, "RETN"}, + {0x1A, "CALL_BUILTIN_FUNC_0"}, + {0x1B, "CALL_BUILTIN_FUNC_1"}, + {0x1C, "CALL_BUILTIN_FUNC_2"}, + {0x1D, "CALL_BUILTIN_FUNC_3"}, + {0x1E, "CALL_BUILTIN_FUNC_4"}, + {0x1F, "CALL_BUILTIN_FUNC_5"}, + {0x20, "CALL_BUILTIN_FUNC"}, + {0x21, "BOOL_NOT"}, + {0x22, "CALL_FAR_METHOD_THEAD"}, + {0x23, "JMP_EXPR_TRUE"}, + {0x24, "SET_LEVEL_FIELD_VARIABLE_FIELD"}, + {0x25, "CAST_BOOL"}, + {0x26, "EVAL_NEW_LOCAL_ARRAY_REF_CACHED0"}, + {0x27, "CALL_BUILTIN_FUNC_POINTER"}, + {0x28, "INEQUALITY"}, + {0x29, "GET_THISTHREAD"}, + {0x2A, "CLEAR_FIELD_VARIABLE"}, + {0x2B, "GET_FLOAT"}, + {0x2C, "SAFE_CREATE_VARIABLE_FIELD_CACHED"}, + {0x2D, "CALL_FAR_FUNC2"}, + {0x2E, "CALL_FAR_FUNC"}, + {0x2F, "CALL_FAR_FUNC_CHILD_THREAD"}, + {0x30, "CLEAR_LOCAL_VARIABLE_FIELD_CACHED0"}, + {0x31, "CLEAR_LOCAL_VARIABLE_FIELD_CACHED"}, + {0x32, "CHECK_CLEAR_PARAMS"}, + {0x33, "CAST_FIELD_OBJ"}, + {0x34, "END"}, + {0x35, "SIZE"}, + {0x36, "EMPTY_ARRAY"}, + {0x37, "BIT_AND"}, + {0x38, "LESSEQUAL"}, + {0x39, "VOIDCODEPOS"}, + {0x3A, "CALL_METHOD_THREAD_POINTER"}, + {0x3B, "ENDSWITCH"}, + {0x3C, "CLEAR_VARIABLE_FIELD"}, + {0x3D, "DIV"}, + {0x3E, "CALL_FAR_METHOD_CHILD_THEAD"}, + {0x3F, "GET_USHORT"}, + {0x40, "JMP_TRUE"}, + {0x41, "GET_SELF"}, + {0x42, "CALL_FAR_FUNC_THREAD"}, + {0x43, "CALL_LOCAL_FUNC_THREAD"}, + {0x44, "SET_LOCAL_VARIABLE_FIELD_CACHED0"}, + {0x45, "SET_LOCAL_VARIABLE_FIELD_CACHED"}, + {0x46, "PLUS"}, + {0x47, "BOOL_COMPLEMENT"}, + {0x48, "CALL_METHOD_POINTER"}, + {0x49, "INC"}, + {0x4A, "REMOVE_LOCAL_VARIABLES"}, + {0x4B, "JMP_EXPR_FALSE"}, + {0x4C, "SWITCH"}, + {0x4D, "CLEAR_PARAMS"}, + {0x4E, "EVAL_LOCAL_VARIABLE_REF_CACHED0"}, + {0x4F, "EVAL_LOCAL_VARIABLE_REF_CACHED"}, + {0x50, "CALL_LOCAL_METHOD"}, + {0x51, "EVAL_FIELD_VARIABLE"}, + {0x52, "EVAL_FIELD_VARIABLE_REF"}, + {0x53, "GET_STRING"}, + {0x54, "CALL_FUNC_POINTER"}, + {0x55, "EVAL_LEVEL_FIELD_VARIABLE"}, + {0x56, "GET_VECTOR"}, + {0x57, "ENDON"}, + {0x58, "GREATEREQUAL"}, + {0x59, "GET_SELF_OBJ"}, + {0x5A, "SET_ANIM_FIELD_VARIABLE_FIELD"}, + {0x5B, "SET_VARIABLE_FIELD"}, + {0x5C, "CALL_LOCAL_FUNC2"}, + {0x5D, "CALL_LOCAL_FUNC"}, + {0x5E, "EVAL_LOCAL_ARRAY_REF_CACHED0"}, + {0x5F, "EVAL_LOCAL_ARRAY_REF_CACHED"}, + {0x60, "GET_FAR_FUNC"}, + {0x61, "LESS"}, + {0x62, "GET_GAME_REF"}, + {0x63, "WAITFRAME"}, + {0x64, "WAITTILLFRAMEEND"}, + {0x65, "SAFE_SET_VARIABLE_FIELD_CACHED0"}, + {0x66, "SAFE_SET_VARIABLE_FIELD_CACHED"}, + {0x67, "CALL_METHOD_CHILD_THREAD_POINTER"}, + {0x68, "GET_LEVEL"}, + {0x69, "NOTIFY"}, + {0x6A, "DEC_TOP"}, + {0x6B, "SHIFT_LEFT"}, + {0x6C, "CALL_LOCAL_METHOD_THREAD"}, + {0x6D, "CALL_LOCAL_METHOD_CHILD_THREAD"}, + {0x6E, "GREATER"}, + {0x6F, "EVAL_LOCAL_VARIABLE_CACHED0"}, + {0x70, "EVAL_LOCAL_VARIABLE_CACHED1"}, + {0x71, "EVAL_LOCAL_VARIABLE_CACHED2"}, + {0x72, "EVAL_LOCAL_VARIABLE_CACHED3"}, + {0x73, "EVAL_LOCAL_VARIABLE_CACHED4"}, + {0x74, "EVAL_LOCAL_VARIABLE_CACHED5"}, + {0x75, "EVAL_LOCAL_VARIABLE_CACHED"}, + {0x76, "SAFE_SET_WAITTILL_VARIABLE_FIELD_CACHED"}, + {0x77, "JMP"}, + {0x78, "CALL_FUNC_THREAD_POINTER"}, + {0x79, "GET_ZERO"}, + {0x7A, "WAIT"}, + {0x7B, "MINUS"}, + {0x7C, "SET_SELF_FIELD_VARIABLE_FIELD"}, + {0x7D, "EVAL_NEW_LOCAL_VARIABLE_REF_CACHED0"}, + {0x7E, "MULT"}, + {0x7F, "CREATE_LOCAL_VARIABLE"}, + {0x80, "CALL_LOCAL_FUNC_CHILD_THREAD"}, + {0x81, "GET_INT"}, + {0x82, "MOD"}, + {0x83, "EVAL_ANIM_FIELD_VARIABLE_REF"}, + {0x84, "GET_BUILTIN_FUNC"}, + {0x85, "GET_GAME"}, + {0x86, "WAITTILL"}, + {0x87, "DEC"}, + {0x88, "EVAL_LOCAL_VARIABLE_OBJECT_CACHED"}, + {0x89, "PRE_CALL"}, + {0x8A, "GET_ANIM"}, + {0x8B, "GET_UNDEFINED"}, + {0x8C, "EVAL_LEVEL_FIELD_VARIABLE_REF"}, + {0x8D, "GET_ANIM_OBJ"}, + {0x8E, "GET_LEVEL_OBJ"}, + {0x8F, "BIT_EXOR"}, + {0x90, "EQUALITY"}, + {0x91, "CLEAR_ARRAY"}, + {0x92, "JMP_BACK"}, + {0x93, "GET_ANIMATION"}, + {0x94, "EVAL_ANIM_FIELD_VARIABLE"}, + {0x95, "GET_ANIMTREE"}, + {0x96, "GET_ISTRING"}, + {0x97, "EVAL_ARRAY_REF"}, + {0x98, "EVAL_SELF_FIELD_VARIABLE_REF"}, + {0x99, "GET_NBYTE"}, + {0x9A, "GET_BUILTIN_METHOD"}, + {0x9B, "CALL_BUILTIN_METHOD_POINTER"}, + {0x9C, "EVAL_ARRAY"}, + {0x9D, "VECTOR"}, + {0x9E, "CALL_FAR_METHOD"}, + {0x9F, "EVAL_LOCAL_ARRAY_CACHED"}, + {0xA0, "GET_BYTE"}, + {0xA1, "CALL_FUNC_CHILD_THREAD_POINTER"}, + {0xA2, "BIT_OR"}, + {0xA3, "ADD_ARRAY"}, + {0xA4, "WAITTILLMATCH2"}, + {0xA5, "WAITTILLMATCH"}, + {0xA6, "GET_LOCAL_FUNC"}, + {0xA7, "GET_NUSHORT"}, + {0xA8, "SHIFT_RIGHT"}, + {0xA9, "CALL_BUILTIN_METHOD_0"}, + {0xAA, "CALL_BUILTIN_METHOD_1"}, + {0xAB, "CALL_BUILTIN_METHOD_2"}, + {0xAC, "CALL_BUILTIN_METHOD_3"}, + {0xAD, "CALL_BUILTIN_METHOD_4"}, + {0xAE, "CALL_BUILTIN_METHOD_5"}, + {0xAF, "CALL_BUILTIN_METHOD"}, + {0xB0, "JMP_FALSE"}, + }; + + auto compiler = ::gsc::compiler(); + auto assembler = ::gsc::assembler(); + + std::unordered_map main_handles; + std::unordered_map init_handles; + std::unordered_map loaded_scripts; + + char* allocate_buffer(size_t size) + { + return utils::hook::invoke(0x14061E680, size, 4, 1, 5); + } + + game::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name) + { + if (loaded_scripts.find(real_name) != loaded_scripts.end()) + { + return loaded_scripts[real_name]; + } + + std::string source_buffer{}; + if (!filesystem::read_file(real_name + ".gsc", &source_buffer)) + { + return nullptr; + } + + auto data = std::vector{source_buffer.begin(), source_buffer.end()}; + + try + { + compiler->compile(real_name, data); + } + catch (const std::exception& e) + { + + console::error("*********** script compile error *************\n"); + console::error("failed to compile '%s':\n%s", real_name.data(), e.what()); + console::error("**********************************************\n"); + return nullptr; + } + + auto assembly = compiler->output(); + assembler->assemble(real_name, assembly); + + const auto script_file_ptr = reinterpret_cast(allocate_buffer(sizeof(game::ScriptFile))); + script_file_ptr->name = file_name; + + const auto stack = assembler->output_stack(); + script_file_ptr->len = static_cast(stack.size()); + + const auto script = assembler->output_script(); + script_file_ptr->bytecodeLen = static_cast(script.size()); + + const auto compressed = xsk::utils::zlib::compress(stack); + const auto buffer_size = script.size() + compressed.size() + 1; + const auto script_size = script.size(); + + const auto buffer = allocate_buffer(buffer_size); + std::memcpy(buffer, script.data(), script_size); + std::memcpy(&buffer[script_size], compressed.data(), compressed.size()); + + script_file_ptr->bytecode = &buffer[0]; + script_file_ptr->buffer = &buffer[script.size()]; + script_file_ptr->compressedLen = static_cast(compressed.size()); + + loaded_scripts[real_name] = script_file_ptr; + + return script_file_ptr; + } + + void load_scripts(const std::filesystem::path& root_dir) + { + std::filesystem::path script_dir = root_dir / "scripts"; + if (!utils::io::directory_exists(script_dir.generic_string())) + { + return; + } + + const auto scripts = utils::io::list_files(script_dir.generic_string()); + for (const auto& script : scripts) + { + if (!script.ends_with(".gsc")) + { + continue; + } + + std::filesystem::path path(script); + const auto relative = path.lexically_relative(root_dir).generic_string(); + const auto base_name = relative.substr(0, relative.size() - 4); + + if (!game::Scr_LoadScript(base_name.data())) + { + continue; + } + + const auto main_handle = game::Scr_GetFunctionHandle(base_name.data(), + xsk::gsc::h2::resolver::token_id("main")); + const auto init_handle = game::Scr_GetFunctionHandle(base_name.data(), + xsk::gsc::h2::resolver::token_id("init")); + + if (main_handle) + { + console::info("Loaded '%s::main'\n", base_name.data()); + main_handles[base_name] = main_handle; + } + else if (init_handle) + { + console::info("Loaded '%s::init'\n", base_name.data()); + init_handles[base_name] = init_handle; + } + } + } + + void clear() + { + main_handles.clear(); + init_handles.clear(); + loaded_scripts.clear(); + } + + void gscr_print_stub() + { + const auto num = game::Scr_GetNumParam(); + std::string buffer{}; + + for (auto i = 0; i < num; i++) + { + const auto str = game::Scr_GetString(i); + buffer.append(str); + buffer.append("\t"); + } + + printf("%s\n", buffer.data()); + } + + void load_gametype_script_stub(void* a1, void* a2) + { + utils::hook::invoke(0x1404E1400, a1, a2); + + clear(); + + for (const auto& path : filesystem::get_search_paths()) + { + load_scripts(path); + } + } + + void g_load_structs_stub() + { + for (auto& function_handle : main_handles) + { + console::info("Executing '%s::main'\n", function_handle.first.data()); + const auto thread = game::Scr_ExecThread(function_handle.second, 0); + game::RemoveRefToObject(thread); + } + + utils::hook::invoke(0x140510B40); + } + + void scr_load_level_stub() + { + utils::hook::invoke(0x1404FD130); + + for (auto& function_handle : init_handles) + { + console::info("Executing '%s::init'\n", function_handle.first.data()); + const auto thread = game::Scr_ExecThread(function_handle.second, 0); + game::RemoveRefToObject(thread); + } + } + + int db_is_xasset_default(int type, const char* name) + { + if (loaded_scripts.find(name) != loaded_scripts.end()) + { + return 0; + } + + return utils::hook::invoke(0x1404143C0, type, name); + } + + std::optional> find_function(const char* pos) + { + for (const auto& file : scripting::script_function_table_sort) + { + for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i) + { + const auto next = std::next(i); + if (pos >= i->second && pos < next->second) + { + return {std::make_pair(i->first, file.first)}; + } + } + } + + return {}; + } + + void print_callstack() + { + for (auto frame = game::scr_VmPub->function_frame; frame != game::scr_VmPub->function_frame_start; --frame) + { + const auto function = find_function(frame->fs.pos); + if (function.has_value()) + { + console::warn("\tat function \"%s\" in file \"%s.gsc\"", + function.value().first.data(), function.value().second.data(), frame->fs.pos); + } + else + { + console::warn("\tat unknown location", frame->fs.pos); + } + } + } + + std::optional get_opcode_name(const std::uint8_t opcode) + { + try + { + return {xsk::gsc::h2::resolver::opcode_name(opcode)}; + } + catch (...) + { + return {}; + } + } + + void* vm_error_stub(void* a1) + { + if (!developer_script->current.enabled) + { + return utils::hook::invoke(0x140614670, a1); + } + + console::warn("*********** script runtime error *************\n"); + + const auto opcode_id = *reinterpret_cast(0x14BAA93E8); + const auto opcode = get_opcode_name(opcode_id); + if (opcode.has_value()) + { + console::warn("while processing instruction %s\n", opcode.value().data()); + } + else + { + console::warn("while processing instruction 0x%X\n", opcode_id); + } + + print_callstack(); + console::warn("**********************************************\n"); + return utils::hook::invoke(0x140614670, a1); + } + + void unknown_function_stub() + { + game::Com_Error(game::ERR_DROP, "LinkFile: unknown function in script '%s.gsc'", + scripting::current_file.data()); + } + } + + game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/) + { + std::string real_name = name; + const auto id = static_cast(std::atoi(name)); + if (id) + { + real_name = xsk::gsc::h2::resolver::token_name(id); + } + + const auto script = load_custom_script(name, real_name); + if (script) + { + return script; + } + + return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name, 1).scriptfile; + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + developer_script = dvars::register_bool("developer_script", false, 0, "Print GSC errors"); + + // wait for other tokens to be added + scheduler::once([]() + { + for (const auto& function : scripting::function_map) + { + xsk::gsc::h2::resolver::add_function(function.first, static_cast(function.second)); + } + + for (const auto& method : scripting::method_map) + { + xsk::gsc::h2::resolver::add_method(method.first, static_cast(method.second)); + } + + for (const auto& token : scripting::token_map) + { + xsk::gsc::h2::resolver::add_token(token.first, static_cast(token.second)); + } + }, scheduler::pipeline::main); + + utils::hook::call(0x1405C6177, find_script); + utils::hook::call(0x1405C6187, db_is_xasset_default); + + // load handles + utils::hook::call(0x1404E17B2, load_gametype_script_stub); + + // execute handles + utils::hook::call(0x1404C8F71, g_load_structs_stub); + utils::hook::call(0x1404C8F80, scr_load_level_stub); + + // replace builtin print function + utils::hook::jump(0x1404EC640, gscr_print_stub); + + utils::hook::call(0x1405CB94F, vm_error_stub); + + utils::hook::call(0x1405BC583, unknown_function_stub); + utils::hook::call(0x1405BC5CF, unknown_function_stub); + + scripting::on_shutdown([](int free_scripts) + { + if (free_scripts) + { + clear(); + } + }); + } + }; +} + +REGISTER_COMPONENT(gsc::component) diff --git a/src/client/component/gsc.hpp b/src/client/component/gsc.hpp new file mode 100644 index 00000000..79444862 --- /dev/null +++ b/src/client/component/gsc.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace gsc +{ + game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/); +} + diff --git a/src/client/component/mapents.cpp b/src/client/component/mapents.cpp index 03f9847d..14dc9a85 100644 --- a/src/client/component/mapents.cpp +++ b/src/client/component/mapents.cpp @@ -404,7 +404,7 @@ namespace mapents utils::hook::call(0x14058BD6B, should_load_addon_mapents_stub); utils::hook::call(0x1406B3384, cm_trigger_model_bounds_stub); - add_field("script_specialops", game::SCRIPT_INTEGER, 0x20000); + add_field("script_specialops", game::SCRIPT_INTEGER, 0xEFFF); } }; } diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index 40e7ac62..ab336291 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -7,6 +7,8 @@ #include "command.hpp" #include "scheduler.hpp" #include "scripting.hpp" +#include "console.hpp" +#include "gsc.hpp" #include "game/scripting/event.hpp" #include "game/scripting/functions.hpp" @@ -15,15 +17,19 @@ #include #include +#include namespace scripting { std::unordered_map> fields_table; std::unordered_map> script_function_table; + std::unordered_map>> script_function_table_sort; utils::concurrency::container shared_table; std::unordered_map get_dvar_int_overrides; + std::string current_file; + namespace { utils::hook::detour vm_notify_hook; @@ -45,9 +51,11 @@ namespace scripting game::dvar_t* scr_auto_respawn = nullptr; - std::string current_file; + std::string current_scriptfile; unsigned int current_file_id{}; + std::vector> shutdown_callbacks; + std::unordered_map canonical_string_table; using notify_list = std::vector; @@ -100,9 +108,16 @@ namespace scripting { if (free_scripts) { + script_function_table_sort.clear(); + script_function_table.clear(); canonical_string_table.clear(); } + for (const auto& callback : shutdown_callbacks) + { + callback(free_scripts); + } + clear_scheduled_notifies(); lua::engine::stop(); g_shutdown_game_hook.invoke(free_scripts); @@ -122,6 +137,8 @@ namespace scripting void process_script_stub(const char* filename) { + current_scriptfile = filename; + const auto file_id = atoi(filename); if (file_id) { @@ -148,6 +165,39 @@ namespace scripting return result; } + std::string get_token_single(unsigned int id) + { + if (canonical_string_table.find(id) != canonical_string_table.end()) + { + return canonical_string_table[id]; + } + + return scripting::find_token_single(id); + } + + void add_function_sort(unsigned int id, const char* pos) + { + std::string filename = current_file; + if (current_file_id) + { + filename = get_token_single(current_file_id); + } + + if (script_function_table_sort.find(filename) == script_function_table_sort.end()) + { + const auto script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_scriptfile.data(), false); + if (script) + { + const auto end = &script->bytecode[script->bytecodeLen]; + script_function_table_sort[filename].emplace_back("__end__", end); + } + } + + const auto name = get_token_single(id); + auto& itr = script_function_table_sort[filename]; + itr.insert(itr.end() - 1, {name, pos}); + } + void add_function(const std::string& file, unsigned int id, const char* pos) { const auto function_names = scripting::get_token_names(id); @@ -159,6 +209,8 @@ namespace scripting void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos) { + add_function_sort(thread_name, code_pos); + if (current_file_id) { const auto names = scripting::get_token_names(current_file_id); @@ -237,6 +289,21 @@ namespace scripting } } + void on_shutdown(const std::function& callback) + { + shutdown_callbacks.push_back(callback); + } + + std::optional get_canonical_string(const unsigned int id) + { + if (canonical_string_table.find(id) == canonical_string_table.end()) + { + return {}; + } + + return {canonical_string_table[id]}; + } + class component final : public component_interface { public: diff --git a/src/client/component/scripting.hpp b/src/client/component/scripting.hpp index 91b4e8a7..b95cc62d 100644 --- a/src/client/component/scripting.hpp +++ b/src/client/component/scripting.hpp @@ -7,7 +7,13 @@ namespace scripting extern std::unordered_map> fields_table; extern std::unordered_map> script_function_table; + extern std::unordered_map>> script_function_table_sort; extern utils::concurrency::container shared_table; extern std::unordered_map get_dvar_int_overrides; + + extern std::string current_file; + + void on_shutdown(const std::function& callback); + std::optional get_canonical_string(const unsigned int id); } \ No newline at end of file diff --git a/src/client/game/scripting/functions.cpp b/src/client/game/scripting/functions.cpp index 533d34bb..6bf72ac0 100644 --- a/src/client/game/scripting/functions.cpp +++ b/src/client/game/scripting/functions.cpp @@ -105,6 +105,21 @@ namespace scripting return results; } + std::string find_token_single(unsigned int id) + { + std::vector results; + + for (const auto& token : token_map) + { + if (token.second == id) + { + return token.first; + } + } + + return utils::string::va("_id_%X", id); + } + unsigned int find_token_id(const std::string& name) { const auto result = token_map.find(name); diff --git a/src/client/game/scripting/functions.hpp b/src/client/game/scripting/functions.hpp index b6def198..1c2f5fc2 100644 --- a/src/client/game/scripting/functions.hpp +++ b/src/client/game/scripting/functions.hpp @@ -11,6 +11,7 @@ namespace scripting using script_function = void(*)(game::scr_entref_t); std::vector find_token(unsigned int id); + std::string find_token_single(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/symbols.hpp b/src/client/game/symbols.hpp index 203f3114..c1d74b79 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -124,7 +124,10 @@ namespace game WEAK symbol Scr_GetInt{0x1405C7890}; WEAK symbol Scr_AddInt{0x1405C69A0}; WEAK symbol Scr_AddString{0x1405C6A80}; - WEAK symbol Scr_LoadScript{0x1405BCEC0}; + WEAK symbol Scr_LoadScript{0x1405BCEC0}; + WEAK symbol Scr_GetNumParam{0x1405C7940}; + WEAK symbol Scr_GetFunctionHandle{0x1405BCD50}; + WEAK symbol Scr_ExecThread{0x1405C6F40}; WEAK symbol VM_Execute{0x1405C8DB0};