Refactor console ending this saga

This commit is contained in:
6arelyFuture 2022-05-18 00:32:10 +02:00
parent 7f5643dcae
commit 59b5a16d6f
Signed by: Future
GPG Key ID: FA77F074E98D98A5
13 changed files with 274 additions and 64 deletions

View File

@ -74,13 +74,13 @@ private:
key_catcher::on_key_press( key_catcher::on_key_press(
"Z", []([[maybe_unused]] const game::LocalClientNum_t& local_client) { "Z", []([[maybe_unused]] const game::LocalClientNum_t& local_client) {
game::Dvar_SetBool(cl_EnableCheats, true); game::Dvar_SetBool(cl_EnableCheats, true);
console::print("Enabled cl_EnableCheats"); console::info("Enabled cl_EnableCheats");
}); });
key_catcher::on_key_press( key_catcher::on_key_press(
"X", []([[maybe_unused]] const game::LocalClientNum_t& local_client) { "X", []([[maybe_unused]] const game::LocalClientNum_t& local_client) {
game::Dvar_SetBool(cl_EnableCheats, false); game::Dvar_SetBool(cl_EnableCheats, false);
console::print("Disabled cl_EnableCheats"); console::info("Disabled cl_EnableCheats");
}); });
key_catcher::on_key_press( key_catcher::on_key_press(
@ -88,7 +88,7 @@ private:
const auto* cmd = const auto* cmd =
utils::string::va("cmd mr %i 2 allies", *game::serverId); utils::string::va("cmd mr %i 2 allies", *game::serverId);
command::execute(cmd, true); command::execute(cmd, true);
console::print("Executed: {}", cmd); console::info("Executed: {}", cmd);
}); });
key_catcher::on_key_press( key_catcher::on_key_press(
@ -96,7 +96,7 @@ private:
const auto* cmd = const auto* cmd =
utils::string::va("cmd mr %i -1 endround", *game::serverId); utils::string::va("cmd mr %i -1 endround", *game::serverId);
command::execute(cmd, true); command::execute(cmd, true);
console::print("Executed: {}", cmd); console::info("Executed: {}", cmd);
}); });
} }
}; };

View File

@ -89,7 +89,7 @@ private:
add("quit", game::Com_Quit_f); add("quit", game::Com_Quit_f);
add("vstr", [](const params& params) { add("vstr", [](const params& params) {
if (params.size() < 2) { if (params.size() < 2) {
console::print("vstr <variablename> : execute a variable command"); console::info("vstr <variablename> : execute a variable command");
return; return;
} }
@ -97,12 +97,12 @@ private:
const auto* dvar = game::Dvar_FindVar(dvar_name); const auto* dvar = game::Dvar_FindVar(dvar_name);
if (dvar == nullptr) { if (dvar == nullptr) {
console::print("{} doesn't exist", dvar_name); console::info("{} doesn't exist", dvar_name);
return; return;
} }
if (dvar->type != game::dvar_type::DVAR_TYPE_STRING && if (dvar->type != game::dvar_type::DVAR_TYPE_STRING &&
dvar->type != game::dvar_type::DVAR_TYPE_ENUM) { 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; return;
} }

View File

@ -1,39 +1,165 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "../loader/component_loader.hpp" #include "../loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/thread.hpp> #include <utils/thread.hpp>
#include <utils/flags.hpp>
#include <utils/concurrency.hpp>
#include "console.hpp" #include "console.hpp"
#include "command.hpp"
namespace console { namespace console {
namespace { namespace {
std::thread thread; using message_queue = std::queue<std::string>;
utils::concurrency::container<message_queue> messages;
LRESULT __stdcall sys_start_console(HWND, UINT, WPARAM, LPARAM) { std::string format(va_list* ap, const char* message) {
game::Sys_ShowConsole(); static thread_local char buffer[0x1000];
return 0;
const auto count = _vsnprintf_s(buffer, _TRUNCATE, message, *ap);
if (count < 0)
return {};
return {buffer};
} }
void console_unlock() { void dispatch_message(const std::string& message) {
const auto callback = SetWindowLongA( messages.access([&message](message_queue& msgs) { msgs.emplace(message); });
*game::g_wv_hWnd, GWL_WNDPROC, reinterpret_cast<LONG>(sys_start_console));
SendMessageA(*game::g_wv_hWnd, WM_QUIT, 0, 0);
SetWindowLongA(*game::g_wv_hWnd, GWL_WNDPROC, callback);
} }
void show_console() { void append_text(const char* text) { dispatch_message(text); }
if (*game::s_wcd_hWnd) {
ShowWindow(*game::s_wcd_hWnd, SW_SHOW);
}
}
} // namespace } // 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<std::string> 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<HWND*>(0x5A86330); }
#ifdef _DEBUG #ifdef _DEBUG
void console_print(const std::source_location& location, std::string_view fmt, void print(const std::source_location& location, std::string_view fmt,
std::format_args&& args) { std::format_args&& args) {
#else #else
void console_print(std::string_view fmt, std::format_args&& args) { void print(std::string_view fmt, std::format_args&& args) {
#endif #endif
#ifdef _DEBUG #ifdef _DEBUG
const auto msg = std::vformat(fmt, args); 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"; const auto line = std::vformat(fmt, args) + "\n";
#endif #endif
if (IsDebuggerPresent()) { dispatch_message(line);
OutputDebugStringA(line.data());
}
game::Conbuf_AppendText(line.data());
} }
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 } // namespace console
REGISTER_COMPONENT(console::component) REGISTER_COMPONENT(console::component)

View File

@ -1,28 +1,30 @@
#pragma once #pragma once
namespace console { namespace console {
HWND get_window();
#ifdef _DEBUG #ifdef _DEBUG
void console_print(const std::source_location& location, std::string_view fmt, void print(const std::source_location& location,
std::format_args&& args);
std::string_view fmt, std::format_args&& args);
#else #else
void console_print(std::string_view fmt, std::format_args&& args); void print(std::string_view fmt, std::format_args&& args);
#endif #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 #ifdef _DEBUG
console_print(std::source_location::current(), fmt, std::move(args)); print(std::source_location::current(), fmt, std::move(args));
#else #else
console_print(fmt, std::move(args)); print(fmt, std::move(args));
#endif #endif
} }
static inline void print(std::string_view fmt) {
log(fmt, std::make_format_args(0));
}
template <typename... Args> template <typename... Args>
static inline void print(std::string_view fmt, Args&&... args) { static inline void info(std::string_view fmt, Args&&... args) {
log(fmt, std::make_format_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 } // namespace console

View File

@ -3,20 +3,35 @@
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include "scheduler.hpp"
#include "console.hpp" #include "console.hpp"
namespace dvar_patches { namespace dvar_patches {
namespace {
void dvar_set_from_string_by_name_stub(const char* dvar_name, void dvar_set_from_string_by_name_stub(const char* dvar_name,
const char* string) { 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 { class component final : public component_interface {
public: public:
void post_unpack() override { void post_unpack() override {
utils::hook(0x59C0EF, dvar_set_from_string_by_name_stub, HOOK_CALL) utils::hook(0x59C0EF, dvar_set_from_string_by_name_stub, HOOK_CALL)
.install() .install()
->quick(); ->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 } // namespace dvar_patches

View File

@ -82,17 +82,17 @@ private:
static void add_exploit_commands() { static void add_exploit_commands() {
command::add("exploit", []([[maybe_unused]] const command::params& params) { command::add("exploit", []([[maybe_unused]] const command::params& params) {
game::Dvar_SetBool(cl_exploit, true); 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) { []([[maybe_unused]] const command::params& params) {
game::Dvar_SetBool(cl_exploit, false); game::Dvar_SetBool(cl_exploit, false);
console::print("Disabled cl_exploit"); console::info("Disabled cl_exploit");
}); });
command::add( command::add(
"send_command", []([[maybe_unused]] const command::params& params) { "sendCommand", []([[maybe_unused]] const command::params& params) {
if (params.size() < 2) if (params.size() < 2)
return; return;
@ -100,7 +100,7 @@ private:
return; return;
const auto cmd = std::format("queryserverinfo ;{}", params.join(1)); 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::NET_OutOfBandPrint(game::NS_SERVER,
game::localClientConnection->serverAddress, game::localClientConnection->serverAddress,
cmd.data()); cmd.data());

View File

@ -4,4 +4,26 @@ namespace game {
ScreenPlacement* ScrPlace_GetUnsafeFullPlacement() { ScreenPlacement* ScrPlace_GetUnsafeFullPlacement() {
return scrPlaceFullUnsafe; 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 } // namespace game

View File

@ -16,6 +16,7 @@ private:
}; };
ScreenPlacement* ScrPlace_GetUnsafeFullPlacement(); ScreenPlacement* ScrPlace_GetUnsafeFullPlacement();
void Dvar_SetVariant(dvar_t* dvar, DvarValue value, DvarSetSource source);
} // namespace game } // namespace game
#include "symbols.hpp" #include "symbols.hpp"

View File

@ -184,6 +184,13 @@ enum dvar_type : std::int8_t {
DVAR_TYPE_FLOAT_3_COLOR = 0x9, DVAR_TYPE_FLOAT_3_COLOR = 0x9,
}; };
enum class DvarSetSource {
DVAR_SOURCE_INTERNAL,
DVAR_SOURCE_EXTERNAL,
DVAR_SOURCE_SCRIPT,
DVAR_SOURCE_DEVGUI,
};
union DvarValue { union DvarValue {
bool enabled; bool enabled;
int integer; int integer;
@ -194,6 +201,8 @@ union DvarValue {
char color[4]; char color[4];
}; };
static_assert(sizeof(DvarValue) == 0x10);
struct enum_limit { struct enum_limit {
int stringCount; int stringCount;
const char** strings; const char** strings;

View File

@ -88,4 +88,5 @@ WEAK symbol<HWND> s_wcd_hWnd{0x5A86330};
WEAK symbol<int> serverId{0xFF5058}; WEAK symbol<int> serverId{0xFF5058};
WEAK symbol<connstate_t> connectionState{0x1060214}; WEAK symbol<connstate_t> connectionState{0x1060214};
WEAK symbol<ScreenPlacement> scrPlaceFullUnsafe{0x1337FC0}; WEAK symbol<ScreenPlacement> scrPlaceFullUnsafe{0x1337FC0};
WEAK symbol<bool> isCheatOverride{0x8B4444};
} // namespace game } // namespace game

View File

@ -6,6 +6,8 @@
#include <WinSock2.h> #include <WinSock2.h>
#include <Windows.h> #include <Windows.h>
#include <corecrt_io.h>
#include <fcntl.h>
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
@ -14,6 +16,7 @@
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <source_location> #include <source_location>
#include <queue>
#pragma comment(lib, "ntdll.lib") #pragma comment(lib, "ntdll.lib")

View File

@ -0,0 +1,46 @@
#include <string>
#include "../utils/nt.hpp"
#include "../utils/string.hpp"
#include "flags.hpp"
#include <cassert>
#include <shellapi.h>
namespace utils::flags {
void parse_flags(std::vector<std::string>& 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<std::string> 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

View File

@ -0,0 +1,5 @@
#pragma once
namespace utils::flags {
bool has_flag(const std::string& flag);
}