diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index e2d708eb..12352e59 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -179,6 +179,22 @@ namespace network return addr; } + bool are_addresses_equal(const game::netadr_t& a, const game::netadr_t& b) + { + if (a.type != b.type) + { + return false; + } + + if (a.type != game::NA_RAWIP && a.type != game::NA_IP) + { + return true; + } + + return a.port == b.port && *reinterpret_cast(&a.ipv4.a) == *reinterpret_cast(& + b.ipv4.a); + } + class component final : public component_interface { public: diff --git a/src/client/component/network.hpp b/src/client/component/network.hpp index 6cd70d1e..f9002801 100644 --- a/src/client/component/network.hpp +++ b/src/client/component/network.hpp @@ -14,4 +14,47 @@ namespace network void send_data(const game::netadr_t& address, const std::string& data); game::netadr_t address_from_string(const std::string& address); + + bool are_addresses_equal(const game::netadr_t& a, const game::netadr_t& b); +} + +inline bool operator==(const game::netadr_t& a, const game::netadr_t& b) +{ + return network::are_addresses_equal(a, b); // +} + +inline bool operator!=(const game::netadr_t& a, const game::netadr_t& b) +{ + return !(a == b); // +} + +namespace std +{ + template <> + struct equal_to + { + using result_type = bool; + + bool operator()(const game::netadr_t& lhs, const game::netadr_t& rhs) const + { + return network::are_addresses_equal(lhs, rhs); + } + }; + + template <> + struct hash + { + size_t operator()(const game::netadr_t& x) const noexcept + { + const auto type_hash = hash()(x.type); + + if (x.type != game::NA_IP && x.type != game::NA_RAWIP) + { + return type_hash; + } + + return type_hash ^ hash()(*reinterpret_cast(&x.ipv4.a)) ^ hash< + uint16_t>()(x.port); + } + }; } diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index ad073f49..10308aef 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -2,20 +2,98 @@ #include "loader/component_loader.hpp" #include "network.hpp" +#include "scheduler.hpp" #include "game/game.hpp" +#include "steam/steam.hpp" #include +#include +#include +#include + +#include namespace party { namespace { + struct + { + game::netadr_t host{{}, {}, game::NA_BAD, {}}; + std::string challenge{}; + } connect_state{}; + void connect_stub(const char* address) { - auto addr = network::address_from_string(address); + const auto target = network::address_from_string(address); + if (target.type == game::NA_BAD) + { + return; + } + connect_state.host = target; + connect_state.challenge = utils::cryptography::random::get_challenge(); + + network::send(target, "getInfo", connect_state.challenge); + } + + void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode) + { game::XSESSION_INFO info{}; - game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, "mp_nuketown_x", "tdm"); + game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, mapname.data(), gamemode.data()); + } + + void launch_mode(const game::eModes mode) + { + const auto* command = ""; + + switch (mode) + { + case game::MODE_CAMPAIGN: + command = "startCampaign;"; + break; + case game::MODE_ZOMBIES: + command = "startZombies;"; + break; + case game::MODE_MULTIPLAYER: + command = "startMultiplayer;"; + break; + default: + return; + } + + game::Cbuf_AddText(0, command); + } + + 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) + { + if (game::Com_SessionMode_IsMode(mode)) + { + connect_to_lobby(addr, mapname, gametype); + return; + } + + if (!was_retried) + { + scheduler::once([=] + { + connect_to_lobby_with_mode(addr, mode, mapname, gametype, true); + }, scheduler::main, 5s); + + launch_mode(mode); + } + } + + std::string get_dvar_string(const char* dvar_name) + { + const auto dvar = game::Dvar_FindVar(dvar_name); + if (!dvar) + { + return {}; + } + + return game::Dvar_GetString(dvar); } } @@ -26,10 +104,77 @@ namespace party { utils::hook::jump(0x141EE6030_g, connect_stub); - /*network::on("_pong", [](const game::netadr_t& source, const network::data_view& data) + network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data) { - - });*/ + utils::info_string info{}; + info.set("challenge", std::string(data.begin(), data.end())); + info.set("gamename", "T7"); + info.set("hostname", get_dvar_string("sv_hostname")); + info.set("gametype", get_dvar_string("g_gametype")); + //info.set("sv_motd", get_dvar_string("sv_motd")); + info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits)); + info.set("mapname", get_dvar_string("mapname")); + //info.set("isPrivate", get_dvar_string("g_password").empty() ? "0" : "1"); + //info.set("clients", utils::string::va("%i", get_client_count())); + //info.set("bots", utils::string::va("%i", get_bot_count())); + //info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients)); + info.set("protocol", utils::string::va("%i", 1/*PROTOCOL*/)); + info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); + //info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running"))); + //info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated"))); + info.set("shortversion", SHORTVERSION); + + network::send(target, "infoResponse", info.build(), '\n'); + }); + + network::on("infoResponse", [](const game::netadr_t& target, const network::data_view& data) + { + const utils::info_string info{data}; + + if (connect_state.host != target) + { + return; + } + + if (info.get("challenge") != connect_state.challenge) + { + const auto str = "Invalid challenge."; + printf("%s\n", str); + return; + } + + const auto gamename = info.get("gamename"); + if (gamename != "T7"s) + { + const auto str = "Invalid gamename."; + printf("%s\n", str); + return; + } + + const auto mapname = info.get("mapname"); + if (mapname.empty()) + { + const auto str = "Invalid map."; + printf("%s\n", str); + return; + } + + const auto gametype = info.get("gametype"); + if (gametype.empty()) + { + const auto str = "Invalid gametype."; + printf("%s\n", str); + return; + } + + const auto playmode = info.get("playmode"); + const auto mode = game::eModes(std::atoi(playmode.data())); + + scheduler::once([=] + { + connect_to_lobby_with_mode(target, mode, mapname, gametype); + }, scheduler::main); + }); } }; } diff --git a/src/client/game/game.cpp b/src/client/game/game.cpp index 2660865c..0ea62173 100644 --- a/src/client/game/game.cpp +++ b/src/client/game/game.cpp @@ -25,7 +25,8 @@ namespace game b[1] = '\n'; b += 2; } - else if (source && source[0] == '^' && source[1] && source[1] != '^' && source[1] >= 48 && source[1] <= 64) // Q_IsColorString + else if (source && source[0] == '^' && source[1] && source[1] != '^' && source[1] >= 48 && source[1] <= 64) + // Q_IsColorString { i++; } @@ -40,4 +41,9 @@ namespace game *b = 0; return static_cast(b - target); } + + game::eModes Com_SessionMode_GetMode() + { + return game::eModes(*reinterpret_cast(0x1568EF7F4_g) << 28 >> 28); + } } diff --git a/src/client/game/game.hpp b/src/client/game/game.hpp index abdf58a3..8cdea0c1 100644 --- a/src/client/game/game.hpp +++ b/src/client/game/game.hpp @@ -8,6 +8,7 @@ namespace game Com_Error_(__FILE__, __LINE__, code, fmt, ##__VA_ARGS__) int Conbuf_CleanText(const char* source, char* target); + game::eModes Com_SessionMode_GetMode(); template class symbol diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 2d9baaba..2005aca7 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -4,6 +4,17 @@ namespace game { #endif + + enum eModes + { + MODE_ZOMBIES = 0x0, + MODE_MULTIPLAYER = 0x1, + MODE_CAMPAIGN = 0x2, + MODE_COUNT = 0x3, + MODE_INVALID = 0x3, + MODE_FIRST = 0x0, + }; + enum bdLobbyErrorCode { BD_NO_ERROR = 0x0, diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 132e2ccf..8303a6b1 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -8,13 +8,14 @@ namespace game { // CL - WEAK symbol CL_ConnectFromLobby {0x14134C570_g}; // Com WEAK symbol Com_Printf{0x1421499C0_g}; WEAK symbol Com_Error_{0x1420F8BD0_g}; + WEAK symbol Com_SessionMode_IsMode{0x1420F7DD0_g}; WEAK symbol Cbuf_AddText{0x1420EC8B0_g}; WEAK symbol Cmd_AddCommandInternal{ diff --git a/src/common/utils/info_string.cpp b/src/common/utils/info_string.cpp index 3b0287e3..7b857926 100644 --- a/src/common/utils/info_string.cpp +++ b/src/common/utils/info_string.cpp @@ -13,6 +13,12 @@ namespace utils { } + + info_string::info_string(const std::basic_string_view& buffer) + : info_string(std::string_view(reinterpret_cast(buffer.data()), buffer.size())) + { + } + void info_string::set(const std::string& key, const std::string& value) { this->key_value_pairs_[key] = value; diff --git a/src/common/utils/info_string.hpp b/src/common/utils/info_string.hpp index 73910411..776739ab 100644 --- a/src/common/utils/info_string.hpp +++ b/src/common/utils/info_string.hpp @@ -11,6 +11,7 @@ namespace utils info_string() = default; info_string(const std::string& buffer); info_string(const std::string_view& buffer); + info_string(const std::basic_string_view& buffer); void set(const std::string& key, const std::string& value); std::string get(const std::string& key) const;