add rcon
This commit is contained in:
parent
86b9ccfa4d
commit
903c2f5790
@ -3,6 +3,8 @@
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "component/rcon.hpp"
|
||||
|
||||
#include <utils/flags.hpp>
|
||||
|
||||
#include "wincon.hpp"
|
||||
@ -105,6 +107,11 @@ namespace console
|
||||
|
||||
void dispatch_message(const int type, const std::string& message)
|
||||
{
|
||||
if (rcon::message_redirect(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (console::is_enabled())
|
||||
{
|
||||
if (wincon::is_enabled())
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "component/command.hpp"
|
||||
#include "component/rcon.hpp"
|
||||
|
||||
#include "version.h"
|
||||
|
||||
@ -311,6 +311,11 @@ namespace terminal
|
||||
|
||||
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();
|
||||
@ -386,7 +391,7 @@ namespace terminal
|
||||
|
||||
if (msg.message == WM_QUIT)
|
||||
{
|
||||
command::execute("quit", false);
|
||||
game::Cbuf_AddCall(0, game::Com_Quit_f);
|
||||
break;
|
||||
}
|
||||
|
||||
|
141
src/client/component/dvar_cheats.cpp
Normal file
141
src/client/component/dvar_cheats.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "console/console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace dvar_cheats
|
||||
{
|
||||
void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::DvarValue* value)
|
||||
{
|
||||
static const auto sv_cheats_checksum = game::Dvar_GenerateChecksum("sv_cheats");
|
||||
|
||||
if (dvar && dvar->checksum == sv_cheats_checksum)
|
||||
{
|
||||
// if dedi, do not allow internal to change value so servers can allow cheats if they want to
|
||||
if (game::environment::is_dedi() && source == game::DvarSetSource::DVAR_SOURCE_INTERNAL)
|
||||
{
|
||||
value->enabled = dvar->current.enabled;
|
||||
}
|
||||
|
||||
// if sv_cheats was enabled and it changes to disabled, we need to reset all cheat dvars
|
||||
else if (dvar->current.enabled && !value->enabled)
|
||||
{
|
||||
for (auto i = 0; i < *game::dvarCount; ++i)
|
||||
{
|
||||
const auto var = game::dvarPool[i];
|
||||
if (var && (var->flags & game::DvarFlags::DVAR_FLAG_CHEAT))
|
||||
{
|
||||
game::Dvar_Reset(var, game::DvarSetSource::DVAR_SOURCE_INTERNAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool dvar_flag_checks(const game::dvar_t* dvar, const game::DvarSetSource source)
|
||||
{
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_WRITE))
|
||||
{
|
||||
#ifdef DEBUG
|
||||
console::error("%s is write protected\n", dvars::dvar_get_name(dvar).data());
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_READ))
|
||||
{
|
||||
#ifdef DEBUG
|
||||
console::error("%s is read only\n", dvars::dvar_get_name(dvar).data());
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// only check cheat/replicated values when the source is external
|
||||
if (source == game::DvarSetSource::DVAR_SOURCE_EXTERNAL)
|
||||
{
|
||||
const auto cl_ingame = game::CL_IsGameClientActive(0);
|
||||
const auto sv_running = game::Dvar_FindVar("sv_running");
|
||||
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_REPLICATED) && (cl_ingame) && (sv_running && !sv_running->current.enabled))
|
||||
{
|
||||
console::error("%s can only be changed by the server\n", dvars::dvar_get_name(dvar).data());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto sv_cheats = game::Dvar_FindVar("sv_cheats");
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_CHEAT) && (sv_cheats && !sv_cheats->current.enabled))
|
||||
{
|
||||
#ifdef DEBUG
|
||||
console::error("%s is cheat protected\n", dvars::dvar_get_name(dvar).data());
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// pass all the flag checks, allow dvar to be changed
|
||||
return true;
|
||||
}
|
||||
|
||||
void* get_dvar_flag_checks_stub()
|
||||
{
|
||||
return utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
const auto can_set_value = a.newLabel();
|
||||
const auto zero_source = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
a.mov(r8, rdi);
|
||||
a.mov(edx, esi);
|
||||
a.mov(rcx, rbx);
|
||||
a.call_aligned(apply_sv_cheats); // check if we are setting sv_cheats
|
||||
a.popad64();
|
||||
a.cmp(esi, 0);
|
||||
a.jz(zero_source); // if the SetSource is 0 (INTERNAL) ignore flag checks
|
||||
|
||||
a.pushad64();
|
||||
a.mov(edx, esi); // source
|
||||
a.mov(rcx, rbx); // dvar
|
||||
a.call_aligned(dvar_flag_checks); // protect read/write/cheat/replicated dvars
|
||||
a.cmp(al, 1);
|
||||
a.jz(can_set_value);
|
||||
|
||||
// if we get here, we are non-zero source and CANNOT set values
|
||||
a.popad64(); // if I do this before the jz it won't work. for some reason the popad64 is affecting the ZR flag
|
||||
a.jmp(0xCEDBDF_b);
|
||||
|
||||
// if we get here, we are non-zero source and CAN set values
|
||||
a.bind(can_set_value);
|
||||
a.popad64(); // if I do this before the jz it won't work. for some reason the popad64 is affecting the ZR flag
|
||||
a.cmp(esi, 1);
|
||||
a.jmp(0xCED8EE_b);
|
||||
|
||||
// if we get here, we are zero source and ignore flags
|
||||
a.bind(zero_source);
|
||||
a.jmp(0xCED97A_b);
|
||||
});
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
utils::hook::nop(0xCED8D4_b, 8); // let our stub handle zero-source sets
|
||||
utils::hook::jump(0xCED8DF_b, get_dvar_flag_checks_stub(), true); // check extra dvar flags when setting values
|
||||
|
||||
scheduler::once([]
|
||||
{
|
||||
game::Dvar_RegisterBool("sv_cheats", false, game::DvarFlags::DVAR_FLAG_REPLICATED, "Allow cheat commands and dvars on this server");
|
||||
}, scheduler::pipeline::main);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(dvar_cheats::component)
|
@ -37,9 +37,7 @@ namespace network
|
||||
|
||||
const std::string_view data(message->data + offset, message->cursize - offset);
|
||||
|
||||
#ifdef DEBUG
|
||||
console::info("[Network] Handling command %s\n", cmd_string.data());
|
||||
#endif
|
||||
//console::debug("[Network] Handling command %s\n", cmd_string.data());
|
||||
|
||||
handler->second(*address, data);
|
||||
return true;
|
||||
|
@ -283,9 +283,10 @@ namespace party
|
||||
int get_client_count()
|
||||
{
|
||||
auto count = 0;
|
||||
const auto* svs_clients = *game::svs_clients;
|
||||
for (unsigned int i = 0; i < *game::svs_numclients; ++i)
|
||||
{
|
||||
if (game::svs_clients[i].header.state >= 1)
|
||||
if (svs_clients[i].header.state >= 1)
|
||||
{
|
||||
++count;
|
||||
}
|
||||
@ -297,9 +298,10 @@ namespace party
|
||||
int get_bot_count()
|
||||
{
|
||||
auto count = 0;
|
||||
const auto* svs_clients = *game::svs_clients;
|
||||
for (unsigned int i = 0; i < *game::svs_numclients; ++i)
|
||||
{
|
||||
if (game::svs_clients[i].header.state >= 1 &&
|
||||
if (svs_clients[i].header.state >= 1 &&
|
||||
game::SV_BotIsBot(i))
|
||||
{
|
||||
++count;
|
||||
|
225
src/client/component/rcon.cpp
Normal file
225
src/client/component/rcon.cpp
Normal file
@ -0,0 +1,225 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console/console.hpp"
|
||||
#include "network.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "rcon.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace rcon
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::atomic_bool is_redirecting_{ false };
|
||||
std::atomic_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<std::recursive_mutex> $(redirect_lock);
|
||||
|
||||
has_redirected_ = false;
|
||||
is_redirecting_ = true;
|
||||
redirect_target_ = target;
|
||||
}
|
||||
|
||||
void clear_redirect()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(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, data.data());
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.empty())
|
||||
{
|
||||
console::info("You must login first to use RCON\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (*game::cl_con_data && game::clientUIActives[0].connectionState >= game::CA_CONNECTED)
|
||||
{
|
||||
const auto target = (*game::cl_con_data)->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 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::svs_clients;
|
||||
if (svs_clients == nullptr)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
|
||||
for (auto i = 0u; i < *game::svs_numclients; i++)
|
||||
{
|
||||
const auto client = &svs_clients[i];
|
||||
|
||||
if (client->header.state >= 1 && client->gentity && client->gentity->client)
|
||||
{
|
||||
char clean_name[32] = { 0 };
|
||||
strncpy_s(clean_name, client->gentity->client->name, sizeof(clean_name));
|
||||
game::I_CleanStr(clean_name);
|
||||
|
||||
buffer.append(utils::string::va("%3i %5i %3s %s %32s %16s %21s %5i\n",
|
||||
i,
|
||||
game::G_MainMP_GetClientScore(i),
|
||||
game::SV_BotIsBot(i) ? "Yes" : "No",
|
||||
(client->header.state == game::CS_RECONNECTING)
|
||||
? "CNCT"
|
||||
: (client->header.state == game::CS_ZOMBIE)
|
||||
? "ZMBI"
|
||||
: utils::string::va("%4i", game::SV_ClientMP_GetClientPing(i)),
|
||||
game::SV_GameMP_GetGuid(i),
|
||||
clean_name,
|
||||
network::net_adr_to_string(client->remoteAddress),
|
||||
client->remoteAddress.port)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
bool message_redirect(const std::string& message)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(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
|
||||
{
|
||||
scheduler::once([]()
|
||||
{
|
||||
game::Dvar_RegisterString("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::Com_FrontEnd_IsInFrontEnd() || !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)
|
6
src/client/component/rcon.hpp
Normal file
6
src/client/component/rcon.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace rcon
|
||||
{
|
||||
bool message_redirect(const std::string& message);
|
||||
}
|
@ -508,6 +508,13 @@ namespace game
|
||||
}
|
||||
using namespace ddl;
|
||||
|
||||
// made up
|
||||
struct connection_data
|
||||
{
|
||||
char __pad0[131112];
|
||||
netadr_s address;
|
||||
};
|
||||
|
||||
namespace entity
|
||||
{
|
||||
enum connstate_t : std::uint32_t
|
||||
@ -572,24 +579,40 @@ namespace game
|
||||
static_assert(offsetof(gentity_s, client) == 368);
|
||||
static_assert(offsetof(gentity_s, flags) == 456);
|
||||
|
||||
enum SvClientConnectionState
|
||||
{
|
||||
CS_FREE = 0x0,
|
||||
CS_ZOMBIE = 0x1,
|
||||
CS_RECONNECTING = 0x2,
|
||||
CS_CONNECTED = 0x3,
|
||||
CS_CLIENTLOADING = 0x4,
|
||||
CS_ACTIVE = 0x5,
|
||||
};
|
||||
|
||||
struct clientHeader_t
|
||||
{
|
||||
char __pad0[8];
|
||||
void* unk; // 0
|
||||
int state; // 8
|
||||
}; // sizeof = ?
|
||||
|
||||
struct client_t
|
||||
{
|
||||
clientHeader_t header; // 0
|
||||
char __pad0[124];
|
||||
char __pad0[120];
|
||||
gentity_s* gentity; // 136
|
||||
char __pad1[1044];
|
||||
char __pad1[20];
|
||||
char userinfo[1024];
|
||||
char name[32]; // 1188
|
||||
char __pad2[714196];
|
||||
char __pad2[648396];
|
||||
netadr_s remoteAddress; // 649616
|
||||
char __pad3[65780];
|
||||
}; static_assert(sizeof(client_t) == 715416);
|
||||
|
||||
|
||||
static_assert(offsetof(client_t, header.state) == 8);
|
||||
static_assert(offsetof(client_t, gentity) == 136);
|
||||
static_assert(offsetof(client_t, userinfo) == 164);
|
||||
static_assert(offsetof(client_t, name) == 1188);
|
||||
static_assert(offsetof(client_t, remoteAddress) == 649616);
|
||||
}
|
||||
using namespace entity;
|
||||
|
||||
|
@ -95,6 +95,7 @@ namespace game
|
||||
WEAK symbol<const char* (const dvar_t* dvar)> Dvar_DisplayableLatchedValue{ 0xCEA1D0 };
|
||||
WEAK symbol<void(char* buffer, int index)> Dvar_GetCombinedString{ 0xBB1F30 };
|
||||
WEAK symbol<const char* (dvar_t* dvar, DvarValue value)> Dvar_ValueToString{ 0xCEED00 };
|
||||
WEAK symbol<void(dvar_t* dvar, DvarSetSource source)> Dvar_Reset{ 0xCEC490 };
|
||||
WEAK symbol<unsigned int(const char* name)> Dvar_GenerateChecksum{ 0xCEA520 };
|
||||
WEAK symbol<void(dvar_t* dvar, int value)> Dvar_SetInt{ 0xCED3D0 };
|
||||
|
||||
@ -102,6 +103,8 @@ namespace game
|
||||
WEAK symbol<void(char* buffer)> FS_FreeFile{ 0xCDE1F0 };
|
||||
WEAK symbol<void(int h, const char* fmt, ...)> FS_Printf{ 0xCDD1C0 };
|
||||
|
||||
WEAK symbol<int(int clientNum)> G_MainMP_GetClientScore{ 0xB20550 };
|
||||
|
||||
WEAK symbol<char* (char* string)> I_CleanStr{ 0xCFACC0 };
|
||||
|
||||
WEAK symbol<const char* (int, int, int)> Key_KeynumToString{ 0x9A95E0 };
|
||||
@ -165,7 +168,11 @@ namespace game
|
||||
WEAK symbol<void()> SV_CmdsMP_CheckLoadGame{ 0xC4C9E0 };
|
||||
WEAK symbol<void()> SV_CmdsSP_MapRestart_f{ 0xC12B30 };
|
||||
WEAK symbol<void()> SV_CmdsSP_FastRestart_f{ 0xC12AF0 };
|
||||
WEAK symbol<int (int clientNum)> SV_ClientMP_GetClientPing{ 0xC507D0 };
|
||||
WEAK symbol<char* (int entNum)> SV_GameMP_GetGuid{ 0XC12410 };
|
||||
WEAK symbol<void()> SV_MainMP_KillLocalServer{ 0xC58DF0 };
|
||||
WEAK symbol<void(int clientNum, svscmd_type type, const char* text)> SV_GameSendServerCommand{ 0xC54780 };
|
||||
WEAK symbol<void(client_t* drop, const char* reason, bool tellThem)> SV_DropClient{ 0xC4FBA0 };
|
||||
WEAK symbol<bool()> SV_Loaded{ 0xC114C0 };
|
||||
WEAK symbol<bool(const char* name)> SV_MapExists{ 0xCDB620 };
|
||||
WEAK symbol<bool(int clientNum)> SV_BotIsBot{ 0xC3BC90 };
|
||||
@ -193,10 +200,12 @@ namespace game
|
||||
WEAK symbol<gentity_s> g_entities{ 0x3D22610 };
|
||||
|
||||
WEAK symbol<unsigned int> svs_numclients{ 0x6B229E0 };
|
||||
WEAK symbol<client_t> svs_clients{ 0x6B22950 };
|
||||
WEAK symbol<client_t*> svs_clients{ 0x6B22950 };
|
||||
|
||||
WEAK symbol<clientUIActive_t> clientUIActives{ 0x2246C30 };
|
||||
|
||||
WEAK symbol<connection_data*> cl_con_data{ 0x1FE58B8 };
|
||||
|
||||
WEAK symbol<int> sv_map_restart{ 0x6B2C9D4 };
|
||||
WEAK symbol<int> sv_loadScripts{ 0x6B2C9D8 };
|
||||
WEAK symbol<int> sv_migrate{ 0x6B2C9DC };
|
||||
|
Loading…
Reference in New Issue
Block a user