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(
"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);
});
}
};

View File

@ -89,7 +89,7 @@ private:
add("quit", game::Com_Quit_f);
add("vstr", [](const params& params) {
if (params.size() < 2) {
console::print("vstr <variablename> : execute a variable command");
console::info("vstr <variablename> : 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;
}

View File

@ -1,39 +1,165 @@
#include <std_include.hpp>
#include "../loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/thread.hpp>
#include <utils/flags.hpp>
#include <utils/concurrency.hpp>
#include "console.hpp"
#include "command.hpp"
namespace console {
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) {
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<LONG>(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<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
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) {
#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());
dispatch_message(line);
}
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
REGISTER_COMPONENT(console::component)

View File

@ -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 <typename... Args>
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

View File

@ -3,20 +3,35 @@
#include <utils/hook.hpp>
#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

View File

@ -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());

View File

@ -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

View File

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

View File

@ -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;

View File

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

View File

@ -6,6 +6,8 @@
#include <WinSock2.h>
#include <Windows.h>
#include <corecrt_io.h>
#include <fcntl.h>
#include <algorithm>
#include <cassert>
@ -14,6 +16,7 @@
#include <mutex>
#include <string>
#include <source_location>
#include <queue>
#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);
}