diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index aebf03fe..d2eba869 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -205,11 +205,19 @@ namespace network return a.port == b.port && a.addr == b.addr; } + uint64_t ret2() + { + return 2; + } + class component final : public component_interface { public: void post_unpack() override { + //utils::hook::jump(0x14143CAB0_g, ret2); // patch dwGetConnectionStatus + //utils::hook::jump(0x14233307E_g, 0x1423330C7_g); + utils::hook::nop(0x142332E76_g, 4); // don't increment data pointer to optionally skip socket byte utils::hook::call(0x142332E43_g, read_socket_byte_stub); // optionally read socket byte utils::hook::call(0x142332E81_g, verify_checksum_stub); // skip checksum verification diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 10308aef..c843f170 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -1,6 +1,7 @@ #include #include "loader/component_loader.hpp" +#include "party.hpp" #include "network.hpp" #include "scheduler.hpp" #include "game/game.hpp" @@ -10,6 +11,7 @@ #include #include #include +#include #include @@ -17,24 +19,20 @@ namespace party { namespace { - struct + game::netadr_t connect_host{{}, {}, game::NA_BAD, {}}; + + struct server_query { - game::netadr_t host{{}, {}, game::NA_BAD, {}}; - std::string challenge{}; - } connect_state{}; + game::netadr_t host; + std::string challenge; + query_callback callback; + std::chrono::high_resolution_clock::time_point query_time; + }; - void connect_stub(const char* address) + utils::concurrency::container>& get_server_queries() { - 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); + static utils::concurrency::container> server_queries; + return server_queries; } void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode) @@ -85,6 +83,122 @@ namespace party } } + game::LobbyMainMode convert_mode(const game::eModes mode) + { + switch (mode) + { + case game::MODE_CAMPAIGN: + return game::LOBBY_MAINMODE_CP; + case game::MODE_MULTIPLAYER: + return game::LOBBY_MAINMODE_MP; + case game::MODE_ZOMBIES: + return game::LOBBY_MAINMODE_ZM; + default: + return game::LOBBY_MAINMODE_INVALID; + } + } + + void connect_to_session(const game::netadr_t& addr, const std::string& hostname, const uint64_t xuid, + const game::eModes mode) + { + const auto LobbyJoin_Begin = reinterpret_cast(0x141ED9540_g); + + if (!LobbyJoin_Begin(0, game::CONTROLLER_INDEX_FIRST, game::LOBBY_TYPE_PRIVATE, game::LOBBY_TYPE_PRIVATE)) + { + return; + } + + auto& join = *game::s_join; + + auto& host = join.hostList[0]; + memset(&host, 0, sizeof(host)); + + host.info.netAdr = addr; + host.info.xuid = xuid; + strcpy_s(host.info.name, hostname.data()); + + host.lobbyType = game::LOBBY_TYPE_PRIVATE; + host.lobbyParams.networkMode = game::LOBBY_NETWORKMODE_LIVE; + host.lobbyParams.mainMode = convert_mode(mode); + + host.retryCount = 0; + host.retryTime = game::Sys_Milliseconds(); + + join.potentialHost = host; + join.hostCount = 1; + join.processedCount = 1; + join.state = game::JOIN_SOURCE_STATE_ASSOCIATING; + join.startTime = game::Sys_Milliseconds(); + + /*join.targetLobbyType = game::LOBBY_TYPE_PRIVATE; + join.sourceLobbyType = game::LOBBY_TYPE_PRIVATE; + join.controllerIndex = game::CONTROLLER_INDEX_FIRST; + join.joinType = game::JOIN_TYPE_NORMAL; + join.joinResult = game::JOIN_RESULT_INVALID; + join.isFinalized = false;*/ + + // LobbyJoinSource_Finalize + join.isFinalized = true; + } + + void handle_connect_query_response(const bool success, const game::netadr_t& target, + const utils::info_string& info) + { + if (!success) + { + 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 hostname = info.get("sv_hostname"); + const auto playmode = info.get("playmode"); + const auto mode = game::eModes(std::atoi(playmode.data())); + //const auto xuid = strtoull(info.get("xuid").data(), nullptr, 16); + + scheduler::once([=] + { + //connect_to_session(target, hostname, xuid, mode); + connect_to_lobby_with_mode(target, mode, mapname, gametype); + }, scheduler::main); + } + + void connect_stub(const char* address) + { + const auto target = network::address_from_string(address); + if (target.type == game::NA_BAD) + { + return; + } + + connect_host = target; + query_server(target, handle_connect_query_response); + } + std::string get_dvar_string(const char* dvar_name) { const auto dvar = game::Dvar_FindVar(dvar_name); @@ -97,11 +211,30 @@ namespace party } } + void query_server(const game::netadr_t& host, query_callback callback) + { + const auto challenge = utils::cryptography::random::get_challenge(); + + server_query query{}; + query.host = host; + query.query_time = std::chrono::high_resolution_clock::now(); + query.callback = std::move(callback); + query.challenge = challenge; + + get_server_queries().access([&](std::vector& server_queries) + { + server_queries.emplace_back(std::move(query)); + }); + + network::send(host, "getInfo", challenge); + } + class component final : public component_interface { public: void post_unpack() override { + utils::hook::jump(0x141E19B60_g, 0x141E19BB6_g); // patch steam id validity check utils::hook::jump(0x141EE6030_g, connect_stub); network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data) @@ -129,52 +262,57 @@ namespace party network::on("infoResponse", [](const game::netadr_t& target, const network::data_view& data) { + bool found_query = false; + server_query query{}; + const utils::info_string info{data}; - if (connect_state.host != target) + get_server_queries().access([&](std::vector& server_queries) { - return; + for (auto i = server_queries.begin(); i != server_queries.end(); ++i) + { + if (i->host == target && i->challenge == info.get("challenge")) + { + found_query = true; + query = std::move(*i); + i = server_queries.erase(i); + break; + } + } + }); + + if (found_query) + { + query.callback(true, query.host, info); } - - 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); }); + + scheduler::loop([] + { + std::vector removed_queries{}; + + get_server_queries().access([&](std::vector& server_queries) + { + const auto now = std::chrono::high_resolution_clock::now(); + for (auto i = server_queries.begin(); i != server_queries.end();) + { + if ((now - i->query_time) < 10s) + { + ++i; + continue; + } + + removed_queries.emplace_back(std::move(*i)); + i = server_queries.erase(i); + } + }); + + const utils::info_string empty{}; + for (const auto& query : removed_queries) + { + query.callback(false, query.host, empty); + } + }, scheduler::async, 1s); } }; } diff --git a/src/client/component/party.hpp b/src/client/component/party.hpp new file mode 100644 index 00000000..dfd278c7 --- /dev/null +++ b/src/client/component/party.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +namespace party +{ + using query_callback_func = void(bool success, const game::netadr_t& host, const ::utils::info_string& info); + using query_callback = std::function; + + void query_server(const game::netadr_t& host, query_callback callback); +} diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index ef3cd68e..58127534 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -1,5 +1,8 @@ #include #include "../steam.hpp" +#include "component/party.hpp" +#include "component/network.hpp" +#include "utils/string.hpp" namespace steam { @@ -8,9 +11,9 @@ namespace steam gameserveritem_t* get_server_item() { static gameserveritem_t server{}; - server.m_NetAdr.m_usConnectionPort = 27017; - server.m_NetAdr.m_usQueryPort = 27017; - server.m_NetAdr.m_unIP = 0x7F000001; + server.m_NetAdr.m_usConnectionPort = 28960; + server.m_NetAdr.m_usQueryPort = 28960; + server.m_NetAdr.m_unIP = ntohl(inet_addr("192.168.178.34")); server.m_nPing = 10; server.m_bHadSuccessfulResponse = true; server.m_bDoNotRefresh = false; @@ -27,11 +30,46 @@ namespace steam server.m_nServerVersion = 1000; strcpy_s(server.m_szServerName, "BO^3I^5I^6I ^7Server"); strcpy_s(server.m_szGameTags, - R"(\gametype\gun\dedicated\true\ranked\true\hardcore\false\zombies\false\modName\usermaps\playerCount\0)"); + R"(\gametype\gun\dedicated\true\ranked\true\hardcore\false\zombies\false\modName\\playerCount\0)"); server.m_steamID = steam_id(); return &server; } + + gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info) + { + gameserveritem_t server{}; + server.m_NetAdr.m_usConnectionPort = address.port; + server.m_NetAdr.m_usQueryPort = address.port; + server.m_NetAdr.m_unIP = address.addr; + server.m_nPing = 10; + server.m_bHadSuccessfulResponse = true; + server.m_bDoNotRefresh = false; + strcpy_s(server.m_szGameDir, ""); + strcpy_s(server.m_szMap, info.get("mapname").data()); + strcpy_s(server.m_szGameDescription, "Example BO^3I^5I^6I ^7Server"); + server.m_nAppID = 311210; + server.m_nPlayers = 0; + server.m_nMaxPlayers = 18; + server.m_nBotPlayers = 0; + server.m_bPassword = false; + server.m_bSecure = true; + server.m_ulTimeLastPlayed = 0; + server.m_nServerVersion = 1000; + strcpy_s(server.m_szServerName, info.get("sv_hostname").data()); + + const auto playmode = info.get("playmode"); + const auto mode = game::eModes(std::atoi(playmode.data())); + + const auto* tags = ::utils::string::va( + R"(\gametype\%s\dedicated\true\ranked\true\hardcore\false\zombies\%s\modName\\playerCount\0)", + info.get("gametype").data(), mode == game::MODE_ZOMBIES ? "true" : "false"); + + strcpy_s(server.m_szGameTags, tags); + server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16); + + return server; + } } void* matchmaking_servers::RequestInternetServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, @@ -104,10 +142,29 @@ namespace steam } void* matchmaking_servers::PingServer(unsigned int unIP, unsigned short usPort, - matchmaking_ping_response* pRequestServersResponse) + matchmaking_ping_response* pRequestServersResponse) { - pRequestServersResponse->ServerResponded(*get_server_item()); - return reinterpret_cast(7); + auto response = pRequestServersResponse; + const auto addr = network::address_from_ip(htonl(unIP), usPort); + + OutputDebugStringA(::utils::string::va("Sending: %u", (uint32_t)usPort)); + + party::query_server( + addr, [response](const bool success, const game::netadr_t& host, const ::utils::info_string& info) + { + OutputDebugStringA(::utils::string::va("Responded: %s", success ? "true" : "false")); + if (success) + { + auto server_item = create_server_item(host, info); + response->ServerResponded(server_item); + } + else + { + response->ServerFailedToRespond(); + } + }); + + return reinterpret_cast(static_cast(7 + rand())); } int matchmaking_servers::PlayerDetails(unsigned int unIP, unsigned short usPort, void* pRequestServersResponse)