diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 333d1087..e9dcd629 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -81,6 +81,24 @@ namespace command assert(this->nesting_ < game::CMD_MAX_NESTING); } + params::params(const std::string& text) + : needs_end_(true) + { + auto* cmd_args = get_cmd_args(); + game::Cmd_TokenizeStringKernel(0, game::CONTROLLER_INDEX_FIRST, text.data(), + 512 - cmd_args->totalUsedArgvPool, false, cmd_args); + + this->nesting_ = cmd_args->nesting; + } + + params::~params() + { + if (this->needs_end_) + { + game::Cmd_EndTokenizedString(); + } + } + int params::size() const { return get_cmd_args()->argc[this->nesting_]; @@ -189,8 +207,10 @@ namespace command auto& allocator = *utils::memory::get_allocator(); const auto* cmd_string = allocator.duplicate_string(command); - game::Cmd_AddCommandInternal(cmd_string, game::Cbuf_AddServerText_f, allocator.allocate()); - game::Cmd_AddServerCommandInternal(cmd_string, execute_custom_sv_command, allocator.allocate()); + game::Cmd_AddCommandInternal(cmd_string, game::Cbuf_AddServerText_f, + allocator.allocate()); + game::Cmd_AddServerCommandInternal(cmd_string, execute_custom_sv_command, + allocator.allocate()); } } diff --git a/src/client/component/command.hpp b/src/client/component/command.hpp index c013b627..1a2de42a 100644 --- a/src/client/component/command.hpp +++ b/src/client/component/command.hpp @@ -6,6 +6,14 @@ namespace command { public: params(); + params(const std::string& text); + ~params(); + + params(params&&) = delete; + params(const params&) = delete; + + params& operator=(params&&) = delete; + params& operator=(const params&) = delete; [[nodiscard]] int size() const; [[nodiscard]] const char* get(int index) const; @@ -17,6 +25,7 @@ namespace command } private: + bool needs_end_{false}; int nesting_; }; diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp index 06639bd5..a586acb4 100644 --- a/src/client/component/console.cpp +++ b/src/client/component/console.cpp @@ -27,6 +27,7 @@ namespace console utils::image::object logo; std::atomic_bool started{false}; std::atomic_bool terminate_runner{false}; + utils::concurrency::container> interceptor{}; utils::concurrency::container> message_queue{}; void print_message(const char* message) @@ -43,6 +44,14 @@ namespace console void queue_message(const char* message) { + interceptor.access([message](const std::function& callback) + { + if (callback) + { + callback(message); + } + }); + message_queue.access([message](std::queue& queue) { queue.push(message); @@ -202,6 +211,19 @@ namespace console } } + void set_interceptor(std::function callback) + { + interceptor.access([&callback](std::function& c) + { + c = std::move(callback); + }); + } + + void remove_interceptor() + { + set_interceptor({}); + } + struct component final : generic_component { void post_unpack() override diff --git a/src/client/component/console.hpp b/src/client/component/console.hpp index a6526763..70395a88 100644 --- a/src/client/component/console.hpp +++ b/src/client/component/console.hpp @@ -3,4 +3,24 @@ namespace console { void set_title(const std::string& title); + void set_interceptor(std::function callback); + void remove_interceptor(); + + struct scoped_interceptor + { + scoped_interceptor(std::function callback) + { + set_interceptor(std::move(callback)); + } + + ~scoped_interceptor() + { + remove_interceptor(); + } + + scoped_interceptor(scoped_interceptor&&) = delete; + scoped_interceptor(const scoped_interceptor&) = delete; + scoped_interceptor& operator=(scoped_interceptor&&) = delete; + scoped_interceptor& operator=(const scoped_interceptor&) = delete; + }; } diff --git a/src/client/component/network.hpp b/src/client/component/network.hpp index aba46009..93f6ebaa 100644 --- a/src/client/component/network.hpp +++ b/src/client/component/network.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace network { using data_view = std::basic_string_view; diff --git a/src/client/component/rcon.cpp b/src/client/component/rcon.cpp new file mode 100644 index 00000000..82af9ecc --- /dev/null +++ b/src/client/component/rcon.cpp @@ -0,0 +1,74 @@ +#include +#include "loader/component_loader.hpp" + +#include "network.hpp" +#include "console.hpp" +#include "command.hpp" +#include "scheduler.hpp" + +#include +#include + +#include + +namespace rcon +{ + namespace + { + std::optional get_and_validate_rcon_command(const std::string& data) + { + const command::params params{reinterpret_cast(data.data())}; + + if (params.size() <= 1) + { + return {}; + } + + if (params[0] != game::get_dvar_string("rcon_password")) + { + return {}; + } + + return params.join(1); + } + + void rcon_executer(const game::netadr_t& target, const std::string& data) + { + const auto command = get_and_validate_rcon_command(data); + if (!command) + { + return; + } + + std::string console_buffer{}; + + console::scoped_interceptor _([&console_buffer](const std::string& text) + { + console_buffer += text; + }); + + game::Cmd_ExecuteSingleCommand(0, game::CONTROLLER_INDEX_FIRST, command->data(), true); + + network::send(target, "print", console_buffer); + } + + void rcon_handler(const game::netadr_t& target, const network::data_view& data) + { + auto str_data = std::string(reinterpret_cast(data.data()), data.size()); + scheduler::once([target, s = std::move(str_data)] + { + rcon_executer(target, s); + }, scheduler::main); + } + } + + struct component final : server_component + { + void post_unpack() override + { + network::on("rcon", rcon_handler); + } + }; +} + +REGISTER_COMPONENT(rcon::component) diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index cc06984b..741a07e5 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -50,8 +50,11 @@ namespace game }; WEAK symbol Cmd_ExecuteSingleCommand{ - 0x1420ED380 + 0x1420ED380, 0x1404F8890 }; + WEAK symbol Cmd_TokenizeStringKernel{0x1420EED60, 0x1404FA300}; + WEAK symbol Cmd_EndTokenizedString{0x1420ECED0, 0x1404F8420}; WEAK symbol Con_GetTextCopy{0x14133A7D0, 0x140182C40}; // DB