diff --git a/src/client/component/game_event.cpp b/src/client/component/game_event.cpp new file mode 100644 index 00000000..2925cfd5 --- /dev/null +++ b/src/client/component/game_event.cpp @@ -0,0 +1,72 @@ +#include +#include "loader/component_loader.hpp" +#include + +#include "game_event.hpp" + +#include +#include + +namespace game_event +{ + namespace + { + using event_task = std::vector>; + utils::concurrency::container g_init_game_tasks; + utils::concurrency::container g_shutdown_game_tasks; + + void rope_init_ropes_stub() + { + g_init_game_tasks.access([](event_task& tasks) + { + for (const auto& func : tasks) + { + func(); + } + }); + + game::Rope_InitRopes(); + } + + void mantle_shutdown_anims_stub() + { + g_shutdown_game_tasks.access([](event_task& tasks) + { + for (const auto& func : tasks) + { + func(); + } + }); + + game::Mantle_ShutdownAnims(); + } + } + + void on_g_init_game(const std::function& callback) + { + g_init_game_tasks.access([&callback](event_task& tasks) + { + tasks.emplace_back(callback); + }); + } + + void on_g_shutdown_game(const std::function& callback) + { + g_shutdown_game_tasks.access([&callback](event_task& tasks) + { + tasks.emplace_back(callback); + }); + } + + class component final : public generic_component + { + public: + void post_unpack() override + { + utils::hook::call(game::select(0x1419DD6EC, 0x1402ABC1B), rope_init_ropes_stub); + utils::hook::call(game::select(0x141A02AAD, 0x1402ADF1D), mantle_shutdown_anims_stub); + } + }; +} + +REGISTER_COMPONENT(game_event::component) diff --git a/src/client/component/game_event.hpp b/src/client/component/game_event.hpp new file mode 100644 index 00000000..875cc719 --- /dev/null +++ b/src/client/component/game_event.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace game_event +{ + void on_g_init_game(const std::function& callback); + void on_g_shutdown_game(const std::function& callback); +} diff --git a/src/client/component/script.cpp b/src/client/component/script.cpp index 0016d087..1e3cdb75 100644 --- a/src/client/component/script.cpp +++ b/src/client/component/script.cpp @@ -2,7 +2,7 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" -#include "scheduler.hpp" +#include "game_event.hpp" #include #include @@ -17,6 +17,7 @@ namespace script utils::hook::detour db_find_x_asset_header_hook; utils::hook::detour gscr_get_bgb_remaining_hook; + utils::memory::allocator allocator; std::unordered_map loaded_scripts; game::RawFile* get_loaded_script(const std::string& name) @@ -30,10 +31,8 @@ namespace script printf("Loading GSC script '%s'\n", name.data()); } - void load_script(std::string& name, const std::string& data) + void load_script(std::string& name, const std::string& data, const bool is_custom) { - auto& allocator = *utils::memory::get_allocator(); - const auto appdata_path = (game::get_appdata_path() / "data/").generic_string(); const auto host_path = (utils::nt::library{}.get_folder() / "boiii/").generic_string(); @@ -49,15 +48,39 @@ namespace script name.erase(i, host_path.length()); } + auto base_name = name; + if (!utils::string::ends_with(name, ".gsc")) + { + printf("GSC script '%s' failed to load due to invalid suffix.\n", name.data()); + return; + } + + if (is_custom) + { + // .gsc suffix will be added back by Scr_LoadScript + base_name = name.substr(0, name.size() - 4); + if (base_name.empty()) + { + printf("GSC script '%s' failed to load due to invalid name.\n", name.data()); + return; + } + } + auto* raw_file = allocator.allocate(); + // use script name with .gsc suffix for DB_FindXAssetHeader hook raw_file->name = allocator.duplicate_string(name); raw_file->buffer = allocator.duplicate_string(data); raw_file->len = static_cast(data.length()); loaded_scripts[name] = raw_file; + + if (is_custom) + { + game::Scr_LoadScript(game::SCRIPTINSTANCE_SERVER, base_name.data()); + } } - void load_scripts_folder(const std::string& script_dir) + void load_scripts_folder(const std::string& script_dir, const bool is_custom) { if (!utils::io::directory_exists(script_dir)) { @@ -66,31 +89,45 @@ namespace script const auto scripts = utils::io::list_files(script_dir); + std::error_code e; for (const auto& script : scripts) { std::string data; auto script_file = script.generic_string(); - if (!std::filesystem::is_directory(script) && utils::io::read_file(script_file, &data)) + if (!std::filesystem::is_directory(script, e) && utils::io::read_file(script_file, &data)) { if (data.size() >= sizeof(GSC_MAGIC) && !std::memcmp(data.data(), &GSC_MAGIC, sizeof(GSC_MAGIC))) { print_loading_script(script_file); - load_script(script_file, data); + load_script(script_file, data, is_custom); } + + continue; } - else if (std::filesystem::is_directory(script)) + + // Do not traverse directories for custom scripts. + // TODO: Add game type specific scripts. custom-scripts/cp, custom-scripts/mp and /custom-scripts/zm + if (std::filesystem::is_directory(script, e) && !is_custom) { - load_scripts_folder(script_file); + load_scripts_folder(script_file, is_custom); } } } void load_scripts() { - loaded_scripts = {}; const utils::nt::library host{}; - load_scripts_folder((game::get_appdata_path() / "data/scripts").string()); - load_scripts_folder((host.get_folder() / "boiii/scripts").string()); + + const auto data_folder = game::get_appdata_path() / "data"; + const auto boiii_folder = host.get_folder() / "boiii"; + + // scripts folder is for overriding stock scripts the game uses + load_scripts_folder((data_folder / "scripts").string(), false); + load_scripts_folder((boiii_folder / "scripts").string(), false); + + // custom_scripts is for loading completely custom scripts the game doesn't use + load_scripts_folder((data_folder / "custom_scripts").string(), true); + load_scripts_folder((boiii_folder / "custom_scripts").string(), true); } game::RawFile* db_find_x_asset_header_stub(const game::XAssetType type, const char* name, @@ -108,16 +145,37 @@ namespace script auto* script = get_loaded_script(name); if (script) { - // Copy over the checksum of the original script - utils::hook::copy(const_cast(script->buffer + 0x8), asset_header->buffer + 0x8, 4); - return script; } return asset_header; } - void gscr_get_bgb_remaining_stub(game::scriptInstance_t inst, void* entref) + void clear_script_memory() + { + loaded_scripts.clear(); + allocator.clear(); + } + + void load_gametype_script_stub() + { + if (!game::Com_IsInGame() || game::Com_IsRunningUILevel()) + { + game::GScr_LoadGametypeScript(); + return; + } + + game::GScr_LoadGametypeScript(); + load_scripts(); + } + + int server_script_checksum_stub() + { + return 1; + } + + void scr_loot_get_item_quantity_stub([[maybe_unused]] game::scriptInstance_t inst, + [[maybe_unused]] game::scr_entref_t entref) { game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 255); } @@ -127,17 +185,21 @@ namespace script { void post_unpack() override { - if (game::is_server()) - { - load_scripts(); - } - else - { - scheduler::once(load_scripts, scheduler::pipeline::main); - } - + // Return custom or overrided scripts if found db_find_x_asset_header_hook.create(game::select(0x141420ED0, 0x1401D5FB0), db_find_x_asset_header_stub); - gscr_get_bgb_remaining_hook.create(game::select(0x141A8CAB0, 0x1402D2310), gscr_get_bgb_remaining_stub); + + // Free our scripts when the game ends + game_event::on_g_shutdown_game(clear_script_memory); + + // Load our scripts when the gametype script is loaded + utils::hook::call(game::select(0x141AAF37C, 0x1402D8C7F), load_gametype_script_stub); + + // Force GSC checksums to be valid + utils::hook::call(game::select(0x1408F2E5D, 0x1400E2D22), server_script_checksum_stub); + + // Workaround for "Out of X" gobblegum + gscr_get_bgb_remaining_hook.create(game::select(0x141A8CAB0, 0x1402D2310), + scr_loot_get_item_quantity_stub); } }; }; diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 94d6bfbc..2f80831c 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -10,7 +10,7 @@ namespace game enum ControllerIndex_t { - INVALID_CONTROLLER_PORT = 0xFFFFFFFF, + INVALID_CONTROLLER_PORT = -1, CONTROLLER_INDEX_FIRST = 0x0, CONTROLLER_INDEX_0 = 0x0, CONTROLLER_INDEX_1 = 0x1, @@ -19,6 +19,18 @@ namespace game CONTROLLER_INDEX_COUNT = 0x4, }; + enum LocalClientNum_t + { + INVALID_LOCAL_CLIENT = -1, + LOCAL_CLIENT_0 = 0x0, + LOCAL_CLIENT_FIRST = 0x0, + LOCAL_CLIENT_KEYBOARD_AND_MOUSE = 0x0, + LOCAL_CLIENT_1 = 0x1, + LOCAL_CLIENT_2 = 0x2, + LOCAL_CLIENT_3 = 0x3, + LOCAL_CLIENT_COUNT = 0x4, + }; + enum eGameModes { MODE_GAME_MATCHMAKING_PLAYLIST = 0x0, @@ -910,7 +922,7 @@ namespace game enum LobbyNetworkMode { - LOBBY_NETWORKMODE_INVALID = 0xFFFFFFFF, + LOBBY_NETWORKMODE_INVALID = -1, LOBBY_NETWORKMODE_LOCAL = 0x0, LOBBY_NETWORKMODE_LAN = 0x1, LOBBY_NETWORKMODE_LIVE = 0x2, @@ -919,7 +931,7 @@ namespace game enum LobbyMainMode { - LOBBY_MAINMODE_INVALID = 0xFFFFFFFF, + LOBBY_MAINMODE_INVALID = -1, LOBBY_MAINMODE_CP = 0x0, LOBBY_MAINMODE_MP = 0x1, LOBBY_MAINMODE_ZM = 0x2, @@ -1141,8 +1153,8 @@ namespace game enum HksObjectType { - TANY = 0xFFFFFFFE, - TNONE = 0xFFFFFFFF, + TANY = -1, + TNONE = -1, TNIL = 0x0, TBOOLEAN = 0x1, TLIGHTUSERDATA = 0x2, @@ -1636,6 +1648,44 @@ namespace game static_assert(sizeof(client_s_cl) == 0xE5170); #endif + union Weapon + { + struct + { + uint64_t weaponIdx : 9; + uint64_t attachment1 : 6; + uint64_t attachment2 : 6; + uint64_t attachment3 : 6; + uint64_t attachment4 : 6; + uint64_t attachment5 : 6; + uint64_t attachment6 : 6; + uint64_t attachment7 : 6; + uint64_t attachment8 : 6; + uint64_t padding : 7; + } _anon_0; + uint64_t weaponData; + }; + + union EntRefUnion + { + int32_t entnum; + uint32_t hudElemIndex; + uint32_t pathNodeIndex; + short vehicleNodeIndex; + unsigned short absDynEntIndex; + Weapon weapon; + uint64_t val; + }; + + struct scr_entref_t + { + EntRefUnion u; + unsigned short classnum; + LocalClientNum_t client; + }; + + static_assert(sizeof(scr_entref_t) == 0x10); + enum scriptInstance_t { SCRIPTINSTANCE_SERVER = 0x0, diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 9648a30d..ee415ce6 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -40,7 +40,8 @@ namespace game WEAK symbol Com_GametypeSettings_GetUInt{ 0x1420F4E00, 0x1404FE5C0 }; - WEAK symbol Com_IsRunningUILevel{0x142148350}; + WEAK symbol Com_IsRunningUILevel{0x142148350, 0x140504BD0}; + WEAK symbol Com_IsInGame{0x1421482C0, 0x140504B90}; WEAK symbol Com_SwitchMode{ 0x14214A4D0 }; @@ -112,6 +113,15 @@ namespace game WEAK symbol Sys_GetTLS{0x1421837B0, 0x140525EB0}; WEAK symbol Sys_IsDatabaseReady{0x142183A60}; + // Rope + WEAK symbol Rope_InitRopes{0x1420D8D00, 0x1404E4300}; + + // Glass + WEAK symbol GlassSv_Shutdown{0x1425AA7A0, 0x14065BCC0}; + + // Mantle + WEAK symbol Mantle_ShutdownAnims{0x142678C80, 0x1406A1B50}; + // Unnamed WEAK symbol CopyString{0x1422AC220, 0x14056BD70}; @@ -184,6 +194,7 @@ namespace game 0x0, 0x1402F5FF0 }; WEAK symbol Scr_GetNumParam{0x0, 0x140171320}; + WEAK symbol Scr_LoadScript{0x1412C83F0, 0x140156610}; WEAK symbol Cinematic_StartPlayback{0x1412BE3A0}; @@ -232,6 +243,8 @@ namespace game 0x1422E9410, 0x1405811E0 }; + WEAK symbol GScr_LoadGametypeScript{0x141AAD850, 0x1402D7140}; + // Variables WEAK symbol cmd_functions{0x15689DF58, 0x14946F860}; WEAK symbol sv_cmd_args{0x15689AE30, 0x14944C740};