diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp index 9d0b89ea..62b9d16e 100644 --- a/src/client/component/console.cpp +++ b/src/client/component/console.cpp @@ -3,7 +3,9 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" + #include "command.hpp" +#include "rcon.hpp" #include #include @@ -114,6 +116,11 @@ namespace console int dispatch_message(const int type, const std::string& message) { + if (rcon::message_redirect(message)) + { + return 0; + } + std::lock_guard _0(print_mutex); clear_output(); diff --git a/src/client/component/logger.cpp b/src/client/component/logger.cpp index 7acefc53..776313e1 100644 --- a/src/client/component/logger.cpp +++ b/src/client/component/logger.cpp @@ -117,10 +117,13 @@ namespace logger public: void post_unpack() override { - // lua stuff - utils::hook::jump(SELECT_VALUE(0x106010_b, 0x27CBB0_b), print_dev); // debug - utils::hook::jump(SELECT_VALUE(0x107680_b, 0x27E210_b), print_error); // error - utils::hook::jump(SELECT_VALUE(0x0E6E30_b, 0x1F6140_b), printf); // print + if (!game::environment::is_dedi()) + { + // lua stuff + utils::hook::jump(SELECT_VALUE(0x106010_b, 0x27CBB0_b), print_dev); // debug + utils::hook::jump(SELECT_VALUE(0x107680_b, 0x27E210_b), print_error); // error + utils::hook::jump(SELECT_VALUE(0x0E6E30_b, 0x1F6140_b), printf); // print + } com_error_hook.create(game::Com_Error, com_error_stub); } diff --git a/src/client/component/rcon.cpp b/src/client/component/rcon.cpp new file mode 100644 index 00000000..a7ea7705 --- /dev/null +++ b/src/client/component/rcon.cpp @@ -0,0 +1,232 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "command.hpp" +#include "console.hpp" +#include "network.hpp" +#include "scheduler.hpp" +#include "rcon.hpp" + +#include +#include + +namespace rcon +{ + namespace + { + bool is_redirecting_ = false; + bool has_redirected_ = false; + game::netadr_s redirect_target_ = {}; + std::recursive_mutex redirect_lock; + + void setup_redirect(const game::netadr_s& target) + { + std::lock_guard $(redirect_lock); + + has_redirected_ = false; + is_redirecting_ = true; + redirect_target_ = target; + } + + void clear_redirect() + { + std::lock_guard $(redirect_lock); + + has_redirected_ = false; + is_redirecting_ = false; + redirect_target_ = {}; + } + + void send_rcon_command(const std::string& password, const std::string& data) + { + // If you are the server, don't bother with rcon and just execute the command + if (game::Dvar_FindVar("sv_running")->current.enabled) + { + game::Cbuf_AddText(0, 0, data.data()); + return; + } + + if (password.empty()) + { + console::info("You must login first to use RCON\n"); + return; + } + + if (*game::mp::connect_state != nullptr && *game::connectionState >= game::CA_CONNECTED) + { + const auto target = (*game::mp::connect_state)->address; + const auto buffer = password + " " + data; + network::send(target, "rcon", buffer); + } + else + { + console::warn("You need to be connected to a server!\n"); + } + } + + std::string build_status_buffer() + { + const auto sv_maxclients = game::Dvar_FindVar("sv_maxclients"); + const auto mapname = game::Dvar_FindVar("mapname"); + + std::string buffer{}; + buffer.append(utils::string::va("map: %s\n", mapname->current.string)); + buffer.append( + "num score bot ping guid name address qport\n"); + buffer.append( + "--- ----- --- ---- -------------------------------- ---------------- --------------------- -----\n"); + + const auto svs_clients = *game::mp::svs_clients; + if (svs_clients == nullptr) + { + return buffer; + } + + for (int i = 0; i < sv_maxclients->current.integer; i++) + { + const auto client = &svs_clients[i]; + + char clean_name[32] = {0}; + strncpy_s(clean_name, client->name, sizeof(clean_name)); + game::I_CleanStr(clean_name); + + if (client->header.state >= 1) + { + buffer.append(utils::string::va("%3i %5i %3s %s %32s %16s %21s %5i\n", + i, + game::G_GetClientScore(i), + game::SV_BotIsBot(i) ? "Yes" : "No", + (client->header.state == 2) + ? "CNCT" + : (client->header.state == 1) + ? "ZMBI" + : utils::string::va("%4i", game::SV_GetClientPing(i)), + game::SV_GetGuid(i), + clean_name, + network::net_adr_to_string(client->header.remoteAddress), + client->header.remoteAddress.port) + ); + } + } + + return buffer; + } + } + + bool message_redirect(const std::string& message) + { + std::lock_guard $(redirect_lock); + + if (is_redirecting_) + { + has_redirected_ = true; + network::send(redirect_target_, "print", message, '\n'); + return true; + } + return false; + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + if (game::environment::is_sp()) + { + return; + } + + scheduler::once([]() + { + dvars::register_string("rcon_password", "", game::DvarFlags::DVAR_FLAG_NONE, + "The password for remote console"); + }, scheduler::pipeline::main); + + command::add("status", []() + { + const auto sv_running = game::Dvar_FindVar("sv_running"); + if (game::VirtualLobby_Loaded() || !sv_running || !sv_running->current.enabled) + { + console::error("Server is not running\n"); + return; + } + + auto status_buffer = build_status_buffer(); + console::info(status_buffer.data()); + }); + + if (!game::environment::is_dedi()) + { + command::add("rcon", [&](const command::params& params) + { + static std::string rcon_password{}; + + if (params.size() < 2) return; + + const auto operation = params.get(1); + if (operation == "login"s) + { + if (params.size() < 3) return; + + rcon_password = params.get(2); + } + else if (operation == "logout"s) + { + rcon_password.clear(); + } + else + { + send_rcon_command(rcon_password, params.join(1)); + } + }); + } + else + { + network::on("rcon", [](const game::netadr_s& addr, const std::string_view& data) + { + const auto message = std::string{data}; + const auto pos = message.find_first_of(" "); + if (pos == std::string::npos) + { + network::send(addr, "print", "Invalid RCon request", '\n'); + console::info("Invalid RCon request from %s\n", network::net_adr_to_string(addr)); + return; + } + + const auto password = message.substr(0, pos); + const auto command = message.substr(pos + 1); + const auto rcon_password = game::Dvar_FindVar("rcon_password"); + if (command.empty() || !rcon_password || !rcon_password->current.string || !strlen( + rcon_password->current.string)) + { + return; + } + + setup_redirect(addr); + + if (password != rcon_password->current.string) + { + network::send(redirect_target_, "print", "Invalid rcon password", '\n'); + console::error("Invalid rcon password\n"); + } + else + { + command::execute(command, true); + } + + if (!has_redirected_) + { + network::send(redirect_target_, "print", "", '\n'); + } + + clear_redirect(); + }); + } + } + }; +} + +REGISTER_COMPONENT(rcon::component) diff --git a/src/client/component/rcon.hpp b/src/client/component/rcon.hpp new file mode 100644 index 00000000..2a6c23c8 --- /dev/null +++ b/src/client/component/rcon.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace rcon +{ + bool message_redirect(const std::string& message); +} diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 8c4c5432..c3ef5ed1 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1466,6 +1466,13 @@ namespace game int num_players; }; + // made up + struct connect_state_t + { + char __pad0[0xC]; + netadr_s address; + }; + static_assert(offsetof(client_state_t, ping) == 0x4A50); static_assert(offsetof(client_state_t, num_players) == 0x4A5C); diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index b6b00474..89e40177 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -108,7 +108,7 @@ namespace game WEAK symbol generateHashValue{0x11FEA0, 0x183F80}; WEAK symbol G_Glass_Update{0x2992E0, 0x417940}; - WEAK symbol G_GetClientScore{0x0, 0x0}; + WEAK symbol G_GetClientScore{0x0, 0x420420}; WEAK symbol G_GetWeaponForName{0x2F20F0, 0x461180}; WEAK symbol G_GivePlayerWeapon{0x2F24F0, 0x461600}; @@ -241,6 +241,8 @@ namespace game WEAK symbol cmd_args{0xB48FEE0, 0x2ED1E00}; WEAK symbol cmd_argsPrivate{0, 0x3513F20}; + WEAK symbol connectionState{0x0, 0x2EC82C8}; + WEAK symbol g_poolSize{0x0, 0x0}; WEAK symbol scr_VarGlob{0xBD80E00, 0xB138180}; @@ -274,6 +276,7 @@ namespace game WEAK symbol virtualLobby_loaded{0x0, 0x2E6EC9D}; WEAK symbol client_state{0x0, 0x2EC84F0}; + WEAK symbol connect_state{0x0, 0x2EC8510}; } namespace sp