diff --git a/src/client/component/cheats.cpp b/src/client/component/cheats.cpp index 6e94754..84d402e 100644 --- a/src/client/component/cheats.cpp +++ b/src/client/component/cheats.cpp @@ -74,13 +74,13 @@ private: key_catcher::on_key_press( "Z", []([[maybe_unused]] const game::LocalClientNum_t& local_client) { game::Dvar_SetBool(cl_EnableCheats, true); - console::print("Enabled cl_EnableCheats"); + console::info("Enabled cl_EnableCheats"); }); key_catcher::on_key_press( "X", []([[maybe_unused]] const game::LocalClientNum_t& local_client) { game::Dvar_SetBool(cl_EnableCheats, false); - console::print("Disabled cl_EnableCheats"); + console::info("Disabled cl_EnableCheats"); }); key_catcher::on_key_press( @@ -88,7 +88,7 @@ private: const auto* cmd = utils::string::va("cmd mr %i 2 allies", *game::serverId); command::execute(cmd, true); - console::print("Executed: {}", cmd); + console::info("Executed: {}", cmd); }); key_catcher::on_key_press( @@ -96,7 +96,7 @@ private: const auto* cmd = utils::string::va("cmd mr %i -1 endround", *game::serverId); command::execute(cmd, true); - console::print("Executed: {}", cmd); + console::info("Executed: {}", cmd); }); } }; diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 8e2c823..eca09f7 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -89,7 +89,7 @@ private: add("quit", game::Com_Quit_f); add("vstr", [](const params& params) { if (params.size() < 2) { - console::print("vstr : execute a variable command"); + console::info("vstr : execute a variable command"); return; } @@ -97,12 +97,12 @@ private: const auto* dvar = game::Dvar_FindVar(dvar_name); if (dvar == nullptr) { - console::print("{} doesn't exist", dvar_name); + console::info("{} doesn't exist", dvar_name); return; } if (dvar->type != game::dvar_type::DVAR_TYPE_STRING && dvar->type != game::dvar_type::DVAR_TYPE_ENUM) { - console::print("{} is not a string-based dvar\n", dvar->name); + console::info("{} is not a string-based dvar\n", dvar->name); return; } diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp index 220fe2d..7a85544 100644 --- a/src/client/component/console.cpp +++ b/src/client/component/console.cpp @@ -1,39 +1,165 @@ #include #include "../loader/component_loader.hpp" +#include #include +#include +#include #include "console.hpp" +#include "command.hpp" namespace console { namespace { -std::thread thread; +using message_queue = std::queue; +utils::concurrency::container messages; -LRESULT __stdcall sys_start_console(HWND, UINT, WPARAM, LPARAM) { - game::Sys_ShowConsole(); - return 0; +std::string format(va_list* ap, const char* message) { + static thread_local char buffer[0x1000]; + + const auto count = _vsnprintf_s(buffer, _TRUNCATE, message, *ap); + + if (count < 0) + return {}; + return {buffer}; } -void console_unlock() { - const auto callback = SetWindowLongA( - *game::g_wv_hWnd, GWL_WNDPROC, reinterpret_cast(sys_start_console)); - - SendMessageA(*game::g_wv_hWnd, WM_QUIT, 0, 0); - SetWindowLongA(*game::g_wv_hWnd, GWL_WNDPROC, callback); +void dispatch_message(const std::string& message) { + messages.access([&message](message_queue& msgs) { msgs.emplace(message); }); } -void show_console() { - if (*game::s_wcd_hWnd) { - ShowWindow(*game::s_wcd_hWnd, SW_SHOW); - } -} +void append_text(const char* text) { dispatch_message(text); } } // namespace +class component final : public component_interface { +public: + component() { + ShowWindow(GetConsoleWindow(), SW_HIDE); + + (void)_pipe(this->handles_, 1024, _O_TEXT); + (void)_dup2(this->handles_[1], 1); + (void)_dup2(this->handles_[1], 2); + } + + void post_start() override { + this->terminate_runner_ = false; + + this->console_runner_ = utils::thread::create_named_thread( + "Console IO", [this] { this->runner(); }); + } + + void post_unpack() override { + utils::hook(0x446930, append_text, HOOK_JUMP).install()->quick(); + + this->initialize(); + } + + void pre_destroy() override { + this->terminate_runner_ = true; + + printf("\r\n"); + _flushall(); + + if (this->console_runner_.joinable()) { + this->console_runner_.join(); + } + + if (this->console_thread_.joinable()) { + this->console_thread_.join(); + } + + _close(this->handles_[0]); + _close(this->handles_[1]); + + messages.access([&](message_queue& msgs) { msgs = {}; }); + } + +private: + volatile bool console_initialized_ = false; + volatile bool terminate_runner_ = false; + + std::thread console_runner_; + std::thread console_thread_; + + int handles_[2]{}; + + void initialize() { + this->console_thread_ = + utils::thread::create_named_thread("Console", [this]() { + if (!utils::flags::has_flag("noconsole")) { + game::Sys_ShowConsole(); + } + + messages.access( + [&](message_queue&) { this->console_initialized_ = true; }); + + MSG msg; + while (!this->terminate_runner_) { + if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + command::execute("quit", false); + break; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } else { + this->log_messages(); + std::this_thread::sleep_for(1ms); + } + } + }); + } + + void log_messages() const { + if (this->console_initialized_ && !messages.get_raw().empty()) { + std::queue message_queue_copy; + + { + messages.access([&](message_queue& msgs) { + message_queue_copy = std::move(msgs); + msgs = {}; + }); + } + + while (!message_queue_copy.empty()) { + log_message(message_queue_copy.front()); + message_queue_copy.pop(); + } + } + + fflush(stdout); + fflush(stderr); + } + + static void log_message(const std::string& message) { + OutputDebugStringA(message.data()); + game::Conbuf_AppendText(message.data()); + } + + void runner() const { + char buffer[1024]; + + while (!this->terminate_runner_ && this->handles_[0]) { + const auto len = _read(this->handles_[0], buffer, sizeof(buffer)); + if (len > 0) { + dispatch_message(std::string(buffer, len)); + } else { + std::this_thread::sleep_for(1ms); + } + } + + std::this_thread::yield(); + } +}; + +HWND get_window() { return *reinterpret_cast(0x5A86330); } + #ifdef _DEBUG -void console_print(const std::source_location& location, std::string_view fmt, - std::format_args&& args) { +void print(const std::source_location& location, std::string_view fmt, + std::format_args&& args) { #else -void console_print(std::string_view fmt, std::format_args&& args) { +void print(std::string_view fmt, std::format_args&& args) { #endif #ifdef _DEBUG const auto msg = std::vformat(fmt, args); @@ -44,28 +170,8 @@ void console_print(std::string_view fmt, std::format_args&& args) { const auto line = std::vformat(fmt, args) + "\n"; #endif - if (IsDebuggerPresent()) { - OutputDebugStringA(line.data()); - } - - game::Conbuf_AppendText(line.data()); + dispatch_message(line); } - -class component final : public component_interface { -public: - void post_unpack() override { - thread = utils::thread::create_named_thread("Console Thread", []() { - console_unlock(); - show_console(); - }); - } - - void pre_destroy() override { - if (thread.joinable()) { - thread.join(); - } - } -}; } // namespace console REGISTER_COMPONENT(console::component) diff --git a/src/client/component/console.hpp b/src/client/component/console.hpp index 7f70a9b..50c21ce 100644 --- a/src/client/component/console.hpp +++ b/src/client/component/console.hpp @@ -1,28 +1,30 @@ #pragma once namespace console { +HWND get_window(); #ifdef _DEBUG -void console_print(const std::source_location& location, std::string_view fmt, - std::format_args&& args); +void print(const std::source_location& location, + + std::string_view fmt, std::format_args&& args); #else -void console_print(std::string_view fmt, std::format_args&& args); +void print(std::string_view fmt, std::format_args&& args); #endif -static inline void log(std::string_view fmt, std::format_args&& args) { +static inline void console_log(std::string_view fmt, std::format_args&& args) { #ifdef _DEBUG - console_print(std::source_location::current(), fmt, std::move(args)); + print(std::source_location::current(), fmt, std::move(args)); #else - console_print(fmt, std::move(args)); + print(fmt, std::move(args)); #endif } -static inline void print(std::string_view fmt) { - log(fmt, std::make_format_args(0)); -} - template -static inline void print(std::string_view fmt, Args&&... args) { - log(fmt, std::make_format_args(args...)); +static inline void info(std::string_view fmt, Args&&... args) { + console_log(fmt, std::make_format_args(args...)); +} + +static inline void info(std::string_view fmt) { + console_log(fmt, std::make_format_args(0)); } } // namespace console diff --git a/src/client/component/dvar_patches.cpp b/src/client/component/dvar_patches.cpp index 4c1306c..f42d2cf 100644 --- a/src/client/component/dvar_patches.cpp +++ b/src/client/component/dvar_patches.cpp @@ -3,20 +3,35 @@ #include +#include "scheduler.hpp" #include "console.hpp" namespace dvar_patches { +namespace { void dvar_set_from_string_by_name_stub(const char* dvar_name, const char* string) { - console::print("Server tried setting {} with value {}", dvar_name, string); + console::info("Server tried setting {} with value {}", dvar_name, string); } +void dvar_override_cheat_protection_stub(bool /*a1*/) { + *game::isCheatOverride = true; +} +} // namespace + class component final : public component_interface { public: void post_unpack() override { utils::hook(0x59C0EF, dvar_set_from_string_by_name_stub, HOOK_CALL) .install() ->quick(); + + *game::isCheatOverride = true; + utils::hook(0x482CC0, dvar_override_cheat_protection_stub, HOOK_JUMP) + .install() + ->quick(); + + // Remove read/write protection + utils::hook::nop(0x649227, 6); } }; } // namespace dvar_patches diff --git a/src/client/component/exploit.cpp b/src/client/component/exploit.cpp index 0a0bbd1..07e202f 100644 --- a/src/client/component/exploit.cpp +++ b/src/client/component/exploit.cpp @@ -82,17 +82,17 @@ private: static void add_exploit_commands() { command::add("exploit", []([[maybe_unused]] const command::params& params) { game::Dvar_SetBool(cl_exploit, true); - console::print("Enabled cl_exploit"); + console::info("Enabled cl_exploit"); }); - command::add("undo_exploit", + command::add("undoExploit", []([[maybe_unused]] const command::params& params) { game::Dvar_SetBool(cl_exploit, false); - console::print("Disabled cl_exploit"); + console::info("Disabled cl_exploit"); }); command::add( - "send_command", []([[maybe_unused]] const command::params& params) { + "sendCommand", []([[maybe_unused]] const command::params& params) { if (params.size() < 2) return; @@ -100,7 +100,7 @@ private: return; const auto cmd = std::format("queryserverinfo ;{}", params.join(1)); - console::print("Sending OOB packet {}", cmd); + console::info("Sending OOB packet {}", cmd); game::NET_OutOfBandPrint(game::NS_SERVER, game::localClientConnection->serverAddress, cmd.data()); diff --git a/src/client/game/game.cpp b/src/client/game/game.cpp index 2984988..3f1c008 100644 --- a/src/client/game/game.cpp +++ b/src/client/game/game.cpp @@ -4,4 +4,26 @@ namespace game { ScreenPlacement* ScrPlace_GetUnsafeFullPlacement() { return scrPlaceFullUnsafe; } + +void __declspec(naked) Dvar_SetVariant(dvar_t* /*dvar*/, DvarValue /*value*/, + DvarSetSource /*source*/) { + static DWORD func = 0x649170; + + __asm { + pushad + + mov eax, [esp + 0x20 + 0x4] // dvar + push [esp + 0x20 + 0x18] // source + push [esp + 0x20 + 0x18] // value + push [esp + 0x20 + 0x18] // value + push [esp + 0x20 + 0x18] // value + push [esp + 0x20 + 0x18] // value + call func + add esp, 0x14 + + popad + + ret + } +} } // namespace game diff --git a/src/client/game/game.hpp b/src/client/game/game.hpp index 6130553..50a3366 100644 --- a/src/client/game/game.hpp +++ b/src/client/game/game.hpp @@ -16,6 +16,7 @@ private: }; ScreenPlacement* ScrPlace_GetUnsafeFullPlacement(); +void Dvar_SetVariant(dvar_t* dvar, DvarValue value, DvarSetSource source); } // namespace game #include "symbols.hpp" diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 598015c..ac940b6 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -184,6 +184,13 @@ enum dvar_type : std::int8_t { DVAR_TYPE_FLOAT_3_COLOR = 0x9, }; +enum class DvarSetSource { + DVAR_SOURCE_INTERNAL, + DVAR_SOURCE_EXTERNAL, + DVAR_SOURCE_SCRIPT, + DVAR_SOURCE_DEVGUI, +}; + union DvarValue { bool enabled; int integer; @@ -194,6 +201,8 @@ union DvarValue { char color[4]; }; +static_assert(sizeof(DvarValue) == 0x10); + struct enum_limit { int stringCount; const char** strings; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index c5bf46b..4dff855 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -88,4 +88,5 @@ WEAK symbol s_wcd_hWnd{0x5A86330}; WEAK symbol serverId{0xFF5058}; WEAK symbol connectionState{0x1060214}; WEAK symbol scrPlaceFullUnsafe{0x1337FC0}; +WEAK symbol isCheatOverride{0x8B4444}; } // namespace game diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index 68306ac..bb97dcd 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include @@ -14,6 +16,7 @@ #include #include #include +#include #pragma comment(lib, "ntdll.lib") diff --git a/src/common/utils/flags.cpp b/src/common/utils/flags.cpp new file mode 100644 index 0000000..4e02c31 --- /dev/null +++ b/src/common/utils/flags.cpp @@ -0,0 +1,46 @@ +#include + +#include "../utils/nt.hpp" +#include "../utils/string.hpp" +#include "flags.hpp" + +#include +#include + +namespace utils::flags { +void parse_flags(std::vector& flags) { + int num_args; + auto* const argv = CommandLineToArgvW(GetCommandLineW(), &num_args); + + assert(flags.empty()); + + if (argv == nullptr) { + return; + } + + for (auto i = 0; i < num_args; ++i) { + std::wstring wide_flag(argv[i]); + if (wide_flag[0] == L'-') { + wide_flag.erase(wide_flag.begin()); + const auto converted_string = string::convert(wide_flag); + flags.emplace_back(string::to_lower(converted_string)); + } + } + + LocalFree(argv); +} + +bool has_flag(const std::string& flag) { + static auto parsed = false; + static std::vector enabled_flags; + + if (!parsed) { + parse_flags(enabled_flags); + parsed = true; + } + + return std::ranges::any_of( + enabled_flags.cbegin(), enabled_flags.cend(), + [flag](const auto& elem) { return elem == string::to_lower(flag); }); +} +} // namespace utils::flags diff --git a/src/common/utils/flags.hpp b/src/common/utils/flags.hpp new file mode 100644 index 0000000..899d3b8 --- /dev/null +++ b/src/common/utils/flags.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace utils::flags { +bool has_flag(const std::string& flag); +}