From cbb1982bb3bafcae7935d8ccc641faaa85a2bb00 Mon Sep 17 00:00:00 2001 From: quaK <38787176+Joelrau@users.noreply.github.com> Date: Sat, 24 Sep 2022 01:55:46 +0300 Subject: [PATCH] connect experiments --- src/client/component/network.cpp | 246 +++++++++++++++++++++++++++++++ src/client/component/network.hpp | 48 ++++++ src/client/component/party.cpp | 242 ++++++++++++++++++++++++++++++ src/client/component/party.hpp | 22 +++ src/client/game/structs.hpp | 16 ++ src/client/game/symbols.hpp | 16 ++ 6 files changed, 590 insertions(+) create mode 100644 src/client/component/network.cpp create mode 100644 src/client/component/network.hpp create mode 100644 src/client/component/party.cpp create mode 100644 src/client/component/party.hpp diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp new file mode 100644 index 00000000..17f58f69 --- /dev/null +++ b/src/client/component/network.cpp @@ -0,0 +1,246 @@ +#include +#include "loader/component_loader.hpp" + +#include "network.hpp" + +#include "game/game.hpp" + +#include "console.hpp" + +#include +#include + +namespace network +{ + std::unordered_map& get_callbacks() + { + static std::unordered_map callbacks{}; + return callbacks; + } + + void on(const std::string& command, const callback& callback) + { + get_callbacks()[utils::string::to_lower(command)] = callback; + } + + bool handle_command(game::netadr_s* address, const char* command, game::msg_t* message) + { + const auto cmd_string = utils::string::to_lower(command); + auto& callbacks = get_callbacks(); + const auto handler = callbacks.find(cmd_string); + const auto offset = cmd_string.size() + 5; + if (message->cursize < offset || handler == callbacks.end()) + { + return false; + } + + const std::string_view data(message->data + offset, message->cursize - offset); + + handler->second(*address, data); +#ifdef DEBUG + console::info("[Network] Handling command %s\n", cmd_string.data()); +#endif + return true; + } + + namespace + { + utils::hook::detour cl_main_connectionless_packet_universal_hook; + bool cl_main_connectionless_packet_universal_stub(int client_num, game::netadr_s* from, game::msg_t* msg, int time, const char* c) + { + if (handle_command(from, c, msg)) + { + return true; + } + + return cl_main_connectionless_packet_universal_hook.invoke(client_num, from, msg, time, c); + } + + int sys_send_packet_stub(const int size, const char* src, game::netadr_s* to) + { + if (to->type == game::NA_BROADCAST || to->type == game::NA_IP) + { + if (*game::query_socket) + { + sockaddr s = {}; + game::NetadrToSockadr(to, &s); + return sendto(*game::query_socket, src, size, 0, &s, 16) >= 0; + } + } + else + { + game::Com_Error(game::ERR_DROP, "Sys_SendPacket: bad address type %i", to->type); + } + return 0; + } + + int net_compare_base_address(const game::netadr_s* a, const game::netadr_s* b) + { + if (a->type == b->type) + { + switch (a->type) + { + case game::netadrtype_t::NA_BOT: + case game::netadrtype_t::NA_LOOPBACK: + return a->port == b->port; + + case game::netadrtype_t::NA_IP: + return !memcmp(a->ip, b->ip, 4); + case game::netadrtype_t::NA_BROADCAST: + return true; + default: + break; + } + } + + console::warn("NET_CompareBaseAdr: bad address type\n"); + return false; + } + + int net_compare_address(const game::netadr_s* a, const game::netadr_s* b) + { + return net_compare_base_address(a, b) && a->port == b->port; + } + } + + void send(const game::netadr_s& address, const std::string& command, const std::string& data, const char separator) + { + std::string packet = "\xFF\xFF\xFF\xFF"; + packet.append(command); + packet.push_back(separator); + packet.append(data); + + send_data(address, packet); + } + + void send_data(const game::netadr_s& address, const std::string& data) + { + auto size = static_cast(data.size()); + if (address.type == game::NA_LOOPBACK) + { + // TODO: Fix this for loopback + if (size > 1280) + { + console::error("Packet was too long. Truncated!\n"); + size = 1280; + } + + game::NET_SendLoopPacket(game::NS_CLIENT1, size, data.data(), &address); + } + else + { + game::Sys_SendPacket(size, data.data(), &address); + } + } + + bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b) + { + return net_compare_address(&a, &b); + } + + const char* net_adr_to_string(const game::netadr_s& a) + { + if (a.type == game::netadrtype_t::NA_LOOPBACK) + { + return "loopback"; + } + + if (a.type == game::netadrtype_t::NA_BOT) + { + return "bot"; + } + + if (a.type == game::netadrtype_t::NA_IP || a.type == game::netadrtype_t::NA_BROADCAST) + { + if (a.port) + { + return utils::string::va("%u.%u.%u.%u:%u", a.ip[0], a.ip[1], a.ip[2], a.ip[3], htons(a.port)); + } + + return utils::string::va("%u.%u.%u.%u", a.ip[0], a.ip[1], a.ip[2], a.ip[3]); + } + + return "bad"; + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + // redirect packet sends to our stub + utils::hook::jump(game::Sys_SendPacket, sys_send_packet_stub); + + // intercept command handling + cl_main_connectionless_packet_universal_hook.create(0x9B06E0_b, cl_main_connectionless_packet_universal_stub); + + // handle xuid without secure connection + utils::hook::nop(0xC53315_b, 2); + + utils::hook::jump(game::NET_CompareAdr, net_compare_address); + utils::hook::jump(game::NET_CompareBaseAdr, net_compare_base_address); + + // don't establish secure conenction + utils::hook::set(0x9DBFDD_b, 0xEB); + utils::hook::set(0x9DC47D_b, 0xEB); + utils::hook::set(0x9DDC79_b, 0xEB); + + // ignore unregistered connection + utils::hook::jump(0xC4F200_b, 0xC4F1AB_b); + utils::hook::jump(0xC4F2F6_b, 0xC4F399_b); + + // disable xuid verification + //utils::hook::set(0x728BF_b, 0xEB); + + // ignore configstring mismatch + utils::hook::set(0x9B6F91_b, 0xEB); + + // ignore dw handle in SvClientMP::FindClientAtAddress + utils::hook::set(0xC58B2B_b, 0xEB); + + // ignore dw handle in SV_DirectConnect + utils::hook::set(0xC4EE1A_b, 0xEB); + utils::hook::set(0xC4F0FB_b, 0xEB); + + // increase cl_maxpackets + //dvars::override::register_int("cl_maxpackets", 1000, 1, 1000, game::DVAR_FLAG_SAVED); + + // increase snaps + //dvars::override::register_int("sv_remote_client_snapshot_msec", 33, 33, 100, game::DVAR_FLAG_NONE); + + // ignore impure client + utils::hook::jump(0xC500C8_b, 0xC500DE_b); // maybe add sv_pure dvar? + + // don't send checksum + //utils::hook::set(0x0, 0); + + // don't read checksum + utils::hook::set(0xCE6E60_b, 0xC301B0); + + // don't try to reconnect client + //utils::hook::call(0x0, reconnect_migratated_client); + //utils::hook::nop(0x0, 4); // this crashes when reconnecting for some reason + + // increase allowed packet size + //const auto max_packet_size = 0x20000; + //utils::hook::set(0x0, max_packet_size); + //utils::hook::set(0x0, max_packet_size); + //utils::hook::set(0x0, max_packet_size); + //utils::hook::set(0x0, max_packet_size); + + // ignore built in "print" oob command and add in our own + utils::hook::set(0x9B0326_b, 0xEB); + network::on("print", [](const game::netadr_s&, const std::string_view& data) + { + const std::string message{ data }; + console::info(message.data()); + }); + + // Use our own socket since the game's socket doesn't work with non localhost addresses + // why? no idea + //utils::hook::jump(0x0, create_socket); + } + }; +} + +REGISTER_COMPONENT(network::component) \ No newline at end of file diff --git a/src/client/component/network.hpp b/src/client/component/network.hpp new file mode 100644 index 00000000..80ad6456 --- /dev/null +++ b/src/client/component/network.hpp @@ -0,0 +1,48 @@ +#pragma once +#include "game/game.hpp" + +namespace network +{ + using callback = std::function; + + void on(const std::string& command, const callback& callback); + void send(const game::netadr_s& address, const std::string& command, const std::string& data = {}, char separator = ' '); + void send_data(const game::netadr_s& address, const std::string& data); + + bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b); + + const char* net_adr_to_string(const game::netadr_s& a); +} + +inline bool operator==(const game::netadr_s& a, const game::netadr_s& b) +{ + return network::are_addresses_equal(a, b); // +} + +inline bool operator!=(const game::netadr_s& a, const game::netadr_s& b) +{ + return !(a == b); // +} + +namespace std +{ + template <> + struct equal_to + { + using result_type = bool; + + bool operator()(const game::netadr_s& lhs, const game::netadr_s& rhs) const + { + return network::are_addresses_equal(lhs, rhs); + } + }; + + template <> + struct hash + { + size_t operator()(const game::netadr_s& x) const noexcept + { + return hash()(*reinterpret_cast(&x.ip[0])) ^ hash()(x.port); + } + }; +} \ No newline at end of file diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp new file mode 100644 index 00000000..32b388cb --- /dev/null +++ b/src/client/component/party.cpp @@ -0,0 +1,242 @@ +#include +#include "loader/component_loader.hpp" + +#include "party.hpp" + +#include "steam/steam.hpp" + +#include "game/game.hpp" + +#include "command.hpp" +#include "console.hpp" +#include "network.hpp" +#include "scheduler.hpp" + +#include +#include +#include +#include + +namespace party +{ + namespace + { + struct + { + game::netadr_s host{}; + std::string challenge{}; + bool hostDefined{ false }; + } connect_state; + + void connect_to_party(const game::netadr_s& target, const std::string& mapname, const std::string& gametype) + { + if (game::Com_GameMode_GetActiveGameMode() != game::GAME_MODE_MP && + game::Com_GameMode_GetActiveGameMode() != game::GAME_MODE_CP) + { + return; + } + + /*if (game::Live_SyncOnlineDataFlags(0) != 0) + { + // initialize the game after onlinedataflags is 32 (workaround) + if (game::Live_SyncOnlineDataFlags(0) == 32) + { + scheduler::once([=]() + { + command::execute("xstartprivateparty", true); + command::execute("disconnect", true); // 32 -> 0 + + connect_to_party(target, mapname, gametype); + }, scheduler::pipeline::main, 1s); + return; + } + else + { + scheduler::once([=]() + { + connect_to_party(target, mapname, gametype); + }, scheduler::pipeline::main, 1s); + return; + } + }*/ + + //perform_game_initialization(); + + // shutdown frontend + game::Com_FrontEndScene_ShutdownAndDisable(); + + // connect + char session_info[0x100] = {}; + game::CL_MainMP_ConnectAndPreloadMap(0, reinterpret_cast(session_info), &target, mapname.data(), gametype.data()); + } + + std::string get_dvar_string(const std::string& dvar) + { + auto* dvar_value = game::Dvar_FindVar(dvar.data()); + if (dvar_value && dvar_value->current.string) + { + return dvar_value->current.string; + } + + return {}; + } + + int get_dvar_int(const std::string& dvar) + { + auto* dvar_value = game::Dvar_FindVar(dvar.data()); + if (dvar_value && dvar_value->current.integer) + { + return dvar_value->current.integer; + } + + return -1; + } + + bool get_dvar_bool(const std::string& dvar) + { + auto* dvar_value = game::Dvar_FindVar(dvar.data()); + if (dvar_value && dvar_value->current.enabled) + { + return dvar_value->current.enabled; + } + + return false; + } + } + + int get_client_count() + { + return 0; + } + + int get_bot_count() + { + return 0; + } + + void connect(const game::netadr_s& target) + { + //command::execute("lui_open_popup popup_acceptinginvite", false); + + connect_state.host = target; + connect_state.challenge = utils::cryptography::random::get_challenge(); + connect_state.hostDefined = true; + + network::send(target, "getInfo", connect_state.challenge); + } + + void info_response_error(const std::string& error) + { + console::error("%s\n", error.data()); + //if (game::Menu_IsMenuOpenAndVisible(0, "popup_acceptinginvite")) + //{ + // command::execute("lui_close popup_acceptinginvite", false); + //} + + game::Com_SetLocalizedErrorMessage(error.data(), "MENU_NOTICE"); + //*reinterpret_cast(0x0) = 1; + } + + + class component final : public component_interface + { + public: + void post_unpack() override + { + command::add("connect", [](const command::params& argument) + { + if (argument.size() != 2) + { + return; + } + + game::netadr_s target{}; + if (game::NET_StringToAdr(argument[1], &target)) + { + connect(target); + } + }); + + network::on("getInfo", [](const game::netadr_s& target, const std::string_view& data) + { + utils::info_string info{}; + info.set("challenge", std::string{ data }); + info.set("gamename", "IW7"); + 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::svs_numclients)); + info.set("protocol", utils::string::va("%i", PROTOCOL)); + info.set("playmode", utils::string::va("%i", game::Com_GameMode_GetActiveGameMode())); + info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running") && !game::Com_FrontEndScene_IsActive())); + info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated"))); + + network::send(target, "infoResponse", info.build(), '\n'); + }); + + network::on("infoResponse", [](const game::netadr_s& target, const std::string_view& data) + { + const utils::info_string info{ data }; + //server_list::handle_info_response(target, info); + + if (connect_state.host != target) + { + return; + } + + if (info.get("challenge") != connect_state.challenge) + { + info_response_error("Invalid challenge."); + return; + } + + const auto gamename = info.get("gamename"); + if (gamename != "IW7"s) + { + info_response_error("Invalid gamename."); + return; + } + + const auto playmode = info.get("playmode"); + if (game::GameModeType(std::atoi(playmode.data())) != game::Com_GameMode_GetActiveGameMode()) + { + info_response_error("Invalid playmode."); + return; + } + + const auto sv_running = info.get("sv_running"); + if (!std::atoi(sv_running.data())) + { + info_response_error("Server not running."); + return; + } + + const auto mapname = info.get("mapname"); + if (mapname.empty()) + { + info_response_error("Invalid map."); + return; + } + + const auto gametype = info.get("gametype"); + if (gametype.empty()) + { + info_response_error("Connection failed: Invalid gametype."); + return; + } + + //party::sv_motd = info.get("sv_motd"); + //party::sv_maxclients = std::stoi(info.get("sv_maxclients")); + + connect_to_party(target, mapname, gametype); + }); + } + }; +} + +REGISTER_COMPONENT(party::component) \ No newline at end of file diff --git a/src/client/component/party.hpp b/src/client/component/party.hpp new file mode 100644 index 00000000..e770a426 --- /dev/null +++ b/src/client/component/party.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "game/game.hpp" + +namespace party +{ + void menu_error(const std::string& error); + + void reset_connect_state(); + + void connect(const game::netadr_s& target); + void start_map(const std::string& mapname, bool dev = false); + + void clear_sv_motd(); + game::netadr_s get_state_host(); + std::string get_state_challenge(); + int server_client_count(); + + int get_client_num_by_name(const std::string& name); + + int get_client_count(); + int get_bot_count(); +} \ No newline at end of file diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index a221c2a8..db5e86f5 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -391,6 +391,22 @@ namespace game unsigned int addrHandleIndex; }; + struct msg_t + { + int overflowed; + int readOnly; + char* data; + char* splitData; + int maxsize; + int cursize; + int splitSize; + int readcount; + int bit; + int lastEntityRef; + netsrc_t targetLocalNetID; + int useZlib; + }; + namespace entity { struct entityState_t diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 58b6410d..1d6ae89f 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -23,6 +23,8 @@ namespace game WEAK symbol Com_IsAnyLocalServerStarting{ 0xBAD9C0 }; WEAK symbol Com_IsAnyLocalServerRunning{ 0xBAD9A0 }; + WEAK symbol Com_SetLocalizedErrorMessage{ 0xBAF300 }; + WEAK symbol Com_Shutdown{ 0xBAFEA0 }; WEAK symbol Cbuf_AddText{ 0xB7C290 }; @@ -31,6 +33,9 @@ namespace game WEAK symbol CG_Utils_GameMessage{ 0x1D7FC0 }; WEAK symbol CG_Utils_BoldGameMessage{ 0x1D7F10 }; + WEAK symbol + CL_MainMP_ConnectAndPreloadMap{ 0x9AED80 }; + WEAK symbol CL_Keys_RemoveCatcher{ 0x9A9B00 }; WEAK symbol Cmd_ExecuteSingleCommand{ 0xB7D040 }; @@ -89,6 +94,13 @@ namespace game WEAK symbol Material_RegisterHandle{ 0xE11CE0 }; + WEAK symbol NetadrToSockadr{ 0xCE6B90 }; + WEAK symbol NET_OutOfBandPrint{ 0xBB4EE0 }; + WEAK symbol NET_SendLoopPacket{ 0xBB50A0 }; + WEAK symbol NET_StringToAdr{ 0xBB5180 }; + WEAK symbol NET_CompareAdr{ 0xBB49B0 }; + WEAK symbol NET_CompareBaseAdr{ 0xBB4A00 }; + WEAK symbol R_RegisterFont{ 0xDFC670 }; WEAK symbol R_TextWidth{ 0xDFC770 }; WEAK symbol R_GetFontHeight{ 0x12727B0 }; @@ -108,6 +120,8 @@ namespace game WEAK symbol Sys_CreateFile{ 0xCFDF50 }; + WEAK symbol Sys_SendPacket{ 0xD57DE0 }; + WEAK symbol ScrPlace_GetViewPlacement{ 0x9E4090 }; WEAK symbol SV_Cmd_TokenizeString{ 0xB7DD00 }; @@ -149,4 +163,6 @@ namespace game WEAK symbol sv_map_restart{ 0x6B2C9D4 }; WEAK symbol sv_loadScripts{ 0x6B2C9D8 }; WEAK symbol sv_migrate{ 0x6B2C9DC }; + + WEAK symbol query_socket{ 0x779FDC8 }; }