diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp index 83d8209b..505c0dd6 100644 --- a/src/client/component/getinfo.cpp +++ b/src/client/component/getinfo.cpp @@ -5,6 +5,7 @@ #include "steam/steam.hpp" #include "network.hpp" +#include "workshop.hpp" #include #include @@ -114,6 +115,8 @@ namespace getinfo info.set("sv_running", std::to_string(game::is_server_running())); info.set("dedicated", game::is_server() ? "1" : "0"); info.set("hc", std::to_string(game::Com_GametypeSettings_GetUInt("hardcoremode", false))); + info.set("modname", workshop::get_mod_name(game::get_dvar_string("fs_game"))); + info.set("fs_game", game::get_dvar_string("fs_game")); info.set("shortversion", SHORTVERSION); network::send(target, "infoResponse", info.build(), '\n'); diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 7a80a301..837a97f9 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -37,12 +37,12 @@ namespace party } void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode, - const std::string& pub_id) + const std::string& usermap_id, const std::string& mod_id) { - workshop::load_usermap_mod_if_needed(pub_id); + workshop::load_mod_if_needed(usermap_id, mod_id); game::XSESSION_INFO info{}; - game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, mapname.data(), gamemode.data(), pub_id.data()); + game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, mapname.data(), gamemode.data(), usermap_id.data()); } void launch_mode(const game::eModes mode) @@ -56,12 +56,12 @@ 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 std::string& pub_id, + const std::string& gametype, const std::string& usermap_id, const std::string& mod_id, const bool was_retried = false) { if (game::Com_SessionMode_IsMode(mode)) { - connect_to_lobby(addr, mapname, gametype, pub_id); + connect_to_lobby(addr, mapname, gametype, usermap_id, mod_id); return; } @@ -69,7 +69,7 @@ namespace party { scheduler::once([=] { - connect_to_lobby_with_mode(addr, mode, mapname, gametype, pub_id, true); + connect_to_lobby_with_mode(addr, mode, mapname, gametype, usermap_id, mod_id, true); }, scheduler::main, 5s); launch_mode(mode); @@ -177,6 +177,8 @@ namespace party return; } + const auto mod_id = info.get("fs_game"); + //const auto hostname = info.get("sv_hostname"); const auto playmode = info.get("playmode"); const auto mode = static_cast(std::atoi(playmode.data())); @@ -184,9 +186,10 @@ namespace party scheduler::once([=] { - const auto publisher_id = workshop::get_usermap_publisher_id(mapname); + const auto usermap_id = workshop::get_usermap_publisher_id(mapname); - if (workshop::check_valid_publisher_id(mapname, publisher_id)) + if (workshop::check_valid_usermap_id(mapname, usermap_id) && + workshop::check_valid_mod_id(mod_id)) { if (is_connecting_to_dedi) { @@ -194,7 +197,7 @@ namespace party } //connect_to_session(target, hostname, xuid, mode); - connect_to_lobby_with_mode(target, mode, mapname, gametype, publisher_id); + connect_to_lobby_with_mode(target, mode, mapname, gametype, usermap_id, mod_id); } }, scheduler::main); } diff --git a/src/client/component/workshop.cpp b/src/client/component/workshop.cpp index cb732447..f683d4b4 100644 --- a/src/client/component/workshop.cpp +++ b/src/client/component/workshop.cpp @@ -5,48 +5,39 @@ #include "game/game.hpp" #include +#include +#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; + bool has_mod(const std::string& pub_id) + { + const auto total_mods = *reinterpret_cast(0x15678D170_g); + + for (unsigned int i = 0; i < total_mods; ++i) + { + const auto mod_data = reinterpret_cast(0x15678D178_g + (sizeof(game::workshop_data) * i)); + if (mod_data->publisherId == pub_id) + { + return true; + } + } + + return false; + } + + void load_usermap_mod_if_needed(const std::string& publisher_id) + { + if (!game::isModLoaded() && !publisher_id.empty()) + { + game::loadMod(0, "usermaps", true); + } + } + void setup_server_map_stub(int localClientNum, const char* mapname, const char* gametype) { const auto publisher_id = get_usermap_publisher_id(mapname); @@ -72,6 +63,102 @@ namespace workshop } } + std::string get_mod_name(const std::string& mod_id) + { + if (mod_id == "usermaps" || !game::is_server()) + { + return mod_id; + } + + const utils::nt::library host{}; + const auto base_path = host.get_folder().generic_string(); + const auto path = utils::string::va("%s/mods/%s/zone/workshop.json", base_path.data(), mod_id.data()); + const auto json_str = utils::io::read_file(path); + + if (json_str.empty()) + { + printf("[ Workshop ] workshop.json has not been found in mod folder: %s\n", mod_id.data()); + return mod_id; + } + + rapidjson::Document doc; + const rapidjson::ParseResult parse_result = doc.Parse(json_str); + + if (parse_result.IsError() || !doc.IsObject()) + { + printf("[ Workshop ] Unable to parse workshop.json\n"); + return mod_id; + } + + if (doc.HasMember("Title")) + { + std::string title = doc["Title"].GetString(); + + if (title.size() > 31) + { + title.resize(31); + } + + return title; + } + + printf("[ Workshop ] workshop.json has no \"Title\" member.\n"); + return mod_id; + } + + 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_usermap_id(const std::string& mapname, const std::string& pub_id) + { + if (!game::DB_FileExists(mapname.data(), 0) && pub_id.empty()) + { + game::UI_OpenErrorPopupWithMessage(0, 0x100, + utils::string::va("Can't find usermap: %s!\nMake sure you're subscribed to the workshop item.", mapname.data())); + return false; + } + + return true; + } + + bool check_valid_mod_id(const std::string& mod) + { + if (mod.empty() || mod == "usermaps") + { + return true; + } + + if (!has_mod(mod)) + { + game::UI_OpenErrorPopupWithMessage(0, 0x100, + utils::string::va("Can't find mod with publisher id: %s!\nMake sure you're subscribed to the workshop item.", mod.data())); + return false; + } + + return true; + } + + void load_mod_if_needed(const std::string& usermap, const std::string& mod) + { + if (!usermap.empty() || mod != "usermaps") + { + game::loadMod(0, mod.data(), true); + } + } + class component final : public client_component { public: diff --git a/src/client/component/workshop.hpp b/src/client/component/workshop.hpp index 1e6776c2..5ef97818 100644 --- a/src/client/component/workshop.hpp +++ b/src/client/component/workshop.hpp @@ -2,7 +2,9 @@ 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); + std::string get_usermap_publisher_id(const std::string& mapname); + std::string get_mod_name(const std::string& mod_id); + bool check_valid_usermap_id(const std::string& mapname, const std::string& pub_id); + bool check_valid_mod_id(const std::string& pub_id); + void load_mod_if_needed(const std::string& usermap, const std::string& mod); } diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 3165a4b5..36a82681 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -104,7 +104,7 @@ namespace game WEAK symbol CopyString{0x1422AC220, 0x14056BD70}; WEAK symbol isModLoaded{0x1420D5020}; - WEAK symbol loadMod{0x1420D6930}; + WEAK symbol loadMod{0x1420D6930}; // Dvar WEAK symbol Dvar_IsSessionModeBaseDvar{0x1422C23A0, 0x140576890}; @@ -145,6 +145,7 @@ namespace game }; // UI + WEAK symbol UI_OpenErrorPopupWithMessage{0x14228DEE0}; WEAK symbol UI_CoD_Init{0x141F29010, 0x1404A0A50}; WEAK symbol UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50}; WEAK symbol UI_CoD_Shutdown{0x141F32E10, 0x0}; diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index 79f0f784..b2996171 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -55,12 +55,14 @@ namespace steam const auto mode = game::eModes(std::atoi(playmode.data())); const auto* tags = ::utils::string::va( - R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\%s\zombies\%s\modName\\playerCount\%d\bots\%d\)", + R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\%s\zombies\%s\playerCount\%d\bots\%d\modName\%s\)", info.get("gametype").data(), info.get("dedicated") == "1" ? "true" : "false", info.get("hc") == "1" ? "true" : "false", mode == game::MODE_ZOMBIES ? "true" : "false", - server.m_nPlayers, atoi(info.get("bots").data())); + server.m_nPlayers, + atoi(info.get("bots").data()), + info.get("modname").data()); ::utils::string::copy(server.m_szGameTags, tags); server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16);