#include #include "loader/component_loader.hpp" #include "game/game.hpp" #include "steam/steam.hpp" #include "party.hpp" #include "network.hpp" #include "scheduler.hpp" #include #include #include #include #include #include namespace party { namespace { game::netadr_t connect_host{{}, {}, game::NA_BAD, {}}; struct server_query { game::netadr_t host; std::string challenge; query_callback callback; std::chrono::high_resolution_clock::time_point query_time; }; utils::concurrency::container>& get_server_queries() { 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) { game::XSESSION_INFO info{}; 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); } } 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); if (!dvar) { return {}; } return game::Dvar_GetString(dvar); } } 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) { 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) { bool found_query = false; server_query query{}; const utils::info_string info{data}; get_server_queries().access([&](std::vector& server_queries) { 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); } }); 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); } }; } REGISTER_COMPONENT(party::component)