From ee85545e5a4dca75ddb2bb59974ac4862c0de91c Mon Sep 17 00:00:00 2001 From: BrentVL-1952840 <70229620+Brentdevent@users.noreply.github.com> Date: Wed, 12 Apr 2023 15:47:38 +0200 Subject: [PATCH 1/4] Mods support for dedicated servers --- src/client/component/getinfo.cpp | 3 + src/client/component/party.cpp | 21 ++- src/client/component/workshop.cpp | 162 ++++++++++++++---- src/client/component/workshop.hpp | 6 +- src/client/game/symbols.hpp | 2 +- .../steam/interfaces/matchmaking_servers.cpp | 6 +- 6 files changed, 151 insertions(+), 49 deletions(-) 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..824c74a5 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,107 @@ namespace workshop } } + const std::string get_mod_name(const std::string& mod_id) + { + if (mod_id == "usermaps") + { + return mod_id; + } + + const utils::nt::library host{}; + std::string path; + + if (game::is_server()) + { + const auto base_path = host.get_folder().generic_string(); + path = utils::string::va("%s/mods/%s/zone/workshop.json", base_path.data(), mod_id.data()); + } + else + { + return mod_id; + } + + std::string json_str; + utils::io::read_file(path, &json_str); + + if (json_str.empty()) + { + printf("[ Workshop ] Workshop.json has not been found in mod folder: %s", mod_id.data()); + return mod_id; + } + + rapidjson::Document doc; + doc.Parse(json_str); + + if (doc.HasMember("Title")) + { + std::string title = doc["Title"].GetString(); + + if (title.size() > 31) + { + title.resize(31); + } + + return title; + } + else + { + printf("[ Workshop ] Workshop.json has no \"Title\" member."); + return mod_id; + } + } + + 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_usermap_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; + } + + bool check_valid_mod_id(const std::string& mod) + { + if (mod.empty() || mod == "usermaps") + { + return true; + } + + if (!has_mod(mod)) + { + game::Com_Error(0, "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..71e5098b 100644 --- a/src/client/component/workshop.hpp +++ b/src/client/component/workshop.hpp @@ -3,6 +3,8 @@ 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); + const 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 38090235..3ede8880 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -98,7 +98,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}; 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); From f3ffd7659eb6ecd62bd8d1957d2f390c558d7b0e Mon Sep 17 00:00:00 2001 From: BrentVL-1952840 <70229620+Brentdevent@users.noreply.github.com> Date: Thu, 13 Apr 2023 11:35:06 +0200 Subject: [PATCH 2/4] Remove const return type --- src/client/component/workshop.cpp | 4 ++-- src/client/component/workshop.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/component/workshop.cpp b/src/client/component/workshop.cpp index 824c74a5..214ef824 100644 --- a/src/client/component/workshop.cpp +++ b/src/client/component/workshop.cpp @@ -63,7 +63,7 @@ namespace workshop } } - const std::string get_mod_name(const std::string& mod_id) + std::string get_mod_name(const std::string& mod_id) { if (mod_id == "usermaps") { @@ -113,7 +113,7 @@ namespace workshop } } - const std::string get_usermap_publisher_id(const std::string& mapname) + std::string get_usermap_publisher_id(const std::string& mapname) { const auto total_usermaps = *reinterpret_cast(0x1567B3580_g); diff --git a/src/client/component/workshop.hpp b/src/client/component/workshop.hpp index 71e5098b..5ef97818 100644 --- a/src/client/component/workshop.hpp +++ b/src/client/component/workshop.hpp @@ -2,8 +2,8 @@ namespace workshop { - const std::string get_usermap_publisher_id(const std::string& mapname); - const std::string get_mod_name(const std::string& mod_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); From 2fb7b42be8aa8e4c5d54797c7f9565d2145402f2 Mon Sep 17 00:00:00 2001 From: BrentVL-1952840 <70229620+Brentdevent@users.noreply.github.com> Date: Thu, 13 Apr 2023 13:40:19 +0200 Subject: [PATCH 3/4] Catch parse errors --- src/client/component/workshop.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/client/component/workshop.cpp b/src/client/component/workshop.cpp index 214ef824..f633c691 100644 --- a/src/client/component/workshop.cpp +++ b/src/client/component/workshop.cpp @@ -88,12 +88,18 @@ namespace workshop if (json_str.empty()) { - printf("[ Workshop ] Workshop.json has not been found in mod folder: %s", mod_id.data()); + printf("[ Workshop ] workshop.json has not been found in mod folder: %s\n", mod_id.data()); return mod_id; } rapidjson::Document doc; - doc.Parse(json_str); + 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")) { @@ -108,7 +114,7 @@ namespace workshop } else { - printf("[ Workshop ] Workshop.json has no \"Title\" member."); + printf("[ Workshop ] workshop.json has no \"Title\" member.\n"); return mod_id; } } From 81fe6bc7e170f9bef91a89a67eeddb0a1ed982e7 Mon Sep 17 00:00:00 2001 From: BrentVL-1952840 <70229620+Brentdevent@users.noreply.github.com> Date: Thu, 13 Apr 2023 20:17:56 +0200 Subject: [PATCH 4/4] Clean up + remove Com_Errors --- src/client/component/workshop.cpp | 33 +++++++++++-------------------- src/client/game/symbols.hpp | 1 + 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/client/component/workshop.cpp b/src/client/component/workshop.cpp index f633c691..f683d4b4 100644 --- a/src/client/component/workshop.cpp +++ b/src/client/component/workshop.cpp @@ -65,26 +65,15 @@ namespace workshop std::string get_mod_name(const std::string& mod_id) { - if (mod_id == "usermaps") + if (mod_id == "usermaps" || !game::is_server()) { return mod_id; } const utils::nt::library host{}; - std::string path; - - if (game::is_server()) - { - const auto base_path = host.get_folder().generic_string(); - path = utils::string::va("%s/mods/%s/zone/workshop.json", base_path.data(), mod_id.data()); - } - else - { - return mod_id; - } - - std::string json_str; - utils::io::read_file(path, &json_str); + 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()) { @@ -112,11 +101,9 @@ namespace workshop return title; } - else - { - printf("[ Workshop ] workshop.json has no \"Title\" member.\n"); - return mod_id; - } + + printf("[ Workshop ] workshop.json has no \"Title\" member.\n"); + return mod_id; } std::string get_usermap_publisher_id(const std::string& mapname) @@ -139,7 +126,8 @@ namespace workshop { 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()); + 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; } @@ -155,7 +143,8 @@ namespace workshop if (!has_mod(mod)) { - game::Com_Error(0, "Can't find mod with publisher id: %s!\nMake sure you're subscribed to the workshop item.", mod.data()); + 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; } diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 626d6df1..e011ceef 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -142,6 +142,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};