diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 4d7e1110..793cc8a9 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -1,10 +1,12 @@ #include #include "loader/component_loader.hpp" #include "game/game.hpp" +#include "game/utils.hpp" #include "party.hpp" #include "network.hpp" #include "scheduler.hpp" +#include "workshop.hpp" #include #include @@ -12,6 +14,7 @@ #include #include + namespace party { namespace @@ -34,10 +37,13 @@ namespace party return server_queries; } - void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode) + void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode, + const std::string& pub_id) { + workshop::load_usermap_mod_if_needed(pub_id); + game::XSESSION_INFO info{}; - game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, mapname.data(), gamemode.data(), nullptr); + game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, mapname.data(), gamemode.data(), pub_id.data()); } void launch_mode(const game::eModes mode) @@ -51,11 +57,11 @@ namespace party } void connect_to_lobby_with_mode(const game::netadr_t& addr, const game::eModes mode, const std::string& mapname, - const std::string& gametype, const bool was_retried = false) + const std::string& gametype, const std::string& pub_id, const bool was_retried = false) { if (game::Com_SessionMode_IsMode(mode)) { - connect_to_lobby(addr, mapname, gametype); + connect_to_lobby(addr, mapname, gametype, pub_id); return; } @@ -63,7 +69,7 @@ namespace party { scheduler::once([=] { - connect_to_lobby_with_mode(addr, mode, mapname, gametype, true); + connect_to_lobby_with_mode(addr, mode, mapname, gametype, pub_id, true); }, scheduler::main, 5s); launch_mode(mode); @@ -171,13 +177,18 @@ namespace party scheduler::once([=] { - if (is_connecting_to_dedi) - { - game::Com_SessionMode_SetGameMode(game::MODE_GAME_MATCHMAKING_PLAYLIST); - } + const auto publisher_id = workshop::get_usermap_publisher_id(mapname); - //connect_to_session(target, hostname, xuid, mode); - connect_to_lobby_with_mode(target, mode, mapname, gametype); + if (workshop::check_valid_publisher_id(mapname, publisher_id)) + { + if (is_connecting_to_dedi) + { + game::Com_SessionMode_SetGameMode(game::MODE_GAME_MATCHMAKING_PLAYLIST); + } + + //connect_to_session(target, hostname, xuid, mode); + connect_to_lobby_with_mode(target, mode, mapname, gametype, publisher_id); + } }, scheduler::main); } diff --git a/src/client/component/workshop.cpp b/src/client/component/workshop.cpp new file mode 100644 index 00000000..cb732447 --- /dev/null +++ b/src/client/component/workshop.cpp @@ -0,0 +1,89 @@ +#include +#include "loader/component_loader.hpp" +#include "workshop.hpp" + +#include "game/game.hpp" + +#include + +namespace workshop +{ + const std::string get_usermap_publisher_id(const std::string& mapname) + { + const auto total_usermaps = *reinterpret_cast(0x1567B3580_g); + + for (unsigned int i = 0; i < total_usermaps; ++i) + { + const auto usermap_data = reinterpret_cast(0x1567B3588_g + (sizeof(game::workshop_data) * i)); + if (usermap_data->folderName == mapname) + { + return usermap_data->publisherId; + } + } + + return ""; + } + + bool check_valid_publisher_id(const std::string& mapname, const std::string& pub_id) + { + if (!game::DB_FileExists(mapname.data(), 0) && pub_id.empty()) + { + game::Com_Error(0, "Can't find usermap: %s!\nMake sure you're subscribed to the workshop item.", mapname.data()); + return false; + } + + return true; + } + + void load_usermap_mod_if_needed(const std::string& pub_id) + { + if (!game::isModLoaded() && !pub_id.empty()) + { + game::loadMod(0, "usermaps", 0); + } + } + + namespace + { + utils::hook::detour setup_server_map_hook; + + void setup_server_map_stub(int localClientNum, const char* mapname, const char* gametype) + { + const auto publisher_id = get_usermap_publisher_id(mapname); + load_usermap_mod_if_needed(publisher_id); + + setup_server_map_hook.invoke(localClientNum, mapname, gametype); + } + + bool has_workshop_item_stub(int type, const char* mapname, int a3) + { + const auto publisher_id = get_usermap_publisher_id(mapname); + const auto name = publisher_id.empty() ? mapname : publisher_id.data(); + + return utils::hook::invoke(0x1420D6380_g, type, name, a3); + } + + game::workshop_data* load_usermap_stub(const char* mapname) + { + const auto publisher_id = get_usermap_publisher_id(mapname); + const auto name = publisher_id.empty() ? mapname : publisher_id.data(); + + return utils::hook::invoke(0x1420D5700_g, name); + } + } + + class component final : public client_component + { + public: + void post_unpack() override + { + setup_server_map_hook.create(0x14135CD20_g, setup_server_map_stub); + + // Allow client to switch maps if server sends zone name instead of publisher id + utils::hook::call(0x14135CD84_g, has_workshop_item_stub); + utils::hook::call(0x14135CE48_g, load_usermap_stub); + } + }; +} + +REGISTER_COMPONENT(workshop::component) diff --git a/src/client/component/workshop.hpp b/src/client/component/workshop.hpp new file mode 100644 index 00000000..1e6776c2 --- /dev/null +++ b/src/client/component/workshop.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace workshop +{ + const std::string get_usermap_publisher_id(const std::string& mapname); + bool check_valid_publisher_id(const std::string& mapname, const std::string& pub_id); + void load_usermap_mod_if_needed(const std::string& pub_id); +} diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 2601428e..a172ff72 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1584,6 +1584,31 @@ namespace game static_assert(sizeof(gentity_s) == 0x4F8); + enum workshop_type + { + WORKSHOP_MOD = 0x1, + WORKSHOP_USERMAP = 0x2 + }; + + struct workshop_data + { + char title[100]; + char folderName[32]; + char publisherId[32]; + char description[256]; + char contentPathToZoneFiles[260]; + char absolutePathContentFolder[260]; + char absolutePathZoneFiles[260]; + int unk; // 1 + int unk2; // 0 + unsigned int publisherIdInteger; + int unk3; + unsigned int unk4; + workshop_type type; + }; + + static_assert(sizeof(workshop_data) == 0x4C8); + union XAssetHeader { /*PhysPreset* physPreset; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index d9b7ac1b..e242117e 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -62,6 +62,8 @@ namespace game 0x141420ED0, 0x1401D5FB0 }; WEAK symbol DB_GetXAssetName{0x1413E9DA0, 0x14019F080}; + WEAK symbol DB_FileExists{0x141420B40}; + WEAK symbol DB_ReleaseXAssets{0x1414247C0}; // Live WEAK symbol Live_GetConnectivityInformation{0x141E0C380}; @@ -84,6 +86,9 @@ namespace game // Unnamed WEAK symbol CopyString{0x1422AC220, 0x14056BD70}; + WEAK symbol isModLoaded{0x1420D5020}; + WEAK symbol loadMod{0x1420D6930}; + // Dvar WEAK symbol Dvar_IsSessionModeBaseDvar{0x1422C23A0, 0x140576890}; WEAK symbol Dvar_FindVar{0x1422BCCD0, 0x140575540};