From 076133cf927b812b1cd9fc2c695bb0c158bb910c Mon Sep 17 00:00:00 2001 From: bodnjenie14 <126781031+bodnjenie14@users.noreply.github.com> Date: Thu, 23 May 2024 07:48:21 +0100 Subject: [PATCH] External console * Added Experimental External Console Co-authored-by: bodnjenie14 <134313862+bodnjenie23@users.noreply.github.com> --- source/proxy-dll/component/console.cpp | 405 +++++++++++++++++++++++++ source/proxy-dll/component/console.hpp | 44 +++ source/proxy-dll/component/logger.cpp | 1 + 3 files changed, 450 insertions(+) create mode 100644 source/proxy-dll/component/console.cpp create mode 100644 source/proxy-dll/component/console.hpp diff --git a/source/proxy-dll/component/console.cpp b/source/proxy-dll/component/console.cpp new file mode 100644 index 0000000..be0b587 --- /dev/null +++ b/source/proxy-dll/component/console.cpp @@ -0,0 +1,405 @@ +//TODO: Fix console branding + +#include +#include "loader/component_loader.hpp" +#include +#include "definitions/game.hpp" +#include "console.hpp" +#include +#include + +#define OUTPUT_HANDLE GetStdHandle(STD_OUTPUT_HANDLE) + +namespace console +{ + namespace + { + const char* branding_str = "Project-Bo4 >"; + + size_t branding_lenght = std::strlen(branding_str); + + utilities::hook::detour printf_hook; + std::recursive_mutex print_mutex; + + struct + { + bool kill; + std::thread thread; + HANDLE kill_event; + char buffer[512]{}; + int cursor; + std::int32_t history_index = -1; + std::deque history{}; + } con{}; + + void set_cursor_pos(int x) + { + CONSOLE_SCREEN_BUFFER_INFO info{}; + GetConsoleScreenBufferInfo(OUTPUT_HANDLE, &info); + info.dwCursorPosition.X = static_cast(x); + SetConsoleCursorPosition(OUTPUT_HANDLE, info.dwCursorPosition); + } + + void show_cursor(const bool show) + { + CONSOLE_CURSOR_INFO info{}; + GetConsoleCursorInfo(OUTPUT_HANDLE, &info); + info.bVisible = show; + SetConsoleCursorInfo(OUTPUT_HANDLE, &info); + } + + template + int invoke_printf(const char* fmt, Args&&... args) + { + return printf_hook.invoke(fmt, std::forward(args)...); + } + + 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, static_cast(count)}; + } + + void update() + { + std::lock_guard _0(print_mutex); + + show_cursor(false); + set_cursor_pos(0); + invoke_printf("%s", con.buffer); + //invoke_printf("%s %s", branding_str, con.buffer); + + set_cursor_pos(con.cursor); + //set_cursor_pos(branding_lenght + con.cursor); + show_cursor(true); + } + + void clear_output() + { + std::lock_guard _0(print_mutex); + + show_cursor(false); + set_cursor_pos(0); + + for (auto i = 0; i < std::strlen(con.buffer); i++) + { + invoke_printf(" "); + } + + set_cursor_pos(con.cursor); + show_cursor(true); + } + + int dispatch_message(const int type, const std::string& message) + { + + std::lock_guard _0(print_mutex); + + clear_output(); + set_cursor_pos(0); + + const auto res = invoke_printf("%s", message.data()); + + if (message.size() <= 0 || message[message.size() - 1] != '\n') + { + invoke_printf("\n"); + } + + update(); + return res; + } + + void clear() + { + std::lock_guard _0(print_mutex); + + clear_output(); + strncpy_s(con.buffer, "", sizeof(con.buffer)); + + con.cursor = 0; + set_cursor_pos(0); + } + + size_t get_max_input_length() + { + CONSOLE_SCREEN_BUFFER_INFO info{}; + GetConsoleScreenBufferInfo(OUTPUT_HANDLE, &info); + const auto columns = static_cast(info.srWindow.Right - info.srWindow.Left - 1); + return std::max(size_t(0), std::min(columns, sizeof(con.buffer))); + } + + void handle_resize() + { + clear(); + update(); + } + + void handle_input(const INPUT_RECORD record) + { + if (record.EventType == WINDOW_BUFFER_SIZE_EVENT) + { + handle_resize(); + return; + } + + if (record.EventType != KEY_EVENT || !record.Event.KeyEvent.bKeyDown) + { + return; + } + + std::lock_guard _0(print_mutex); + + const auto key = record.Event.KeyEvent.wVirtualKeyCode; + switch (key) + { + case VK_UP: + { + if (++con.history_index >= con.history.size()) + { + con.history_index = static_cast(con.history.size()) - 1; + } + + clear(); + + if (con.history_index != -1) + { + strncpy_s(con.buffer, con.history.at(con.history_index).data(), sizeof(con.buffer)); + con.cursor = static_cast(strlen(con.buffer)); + } + + update(); + break; + } + case VK_DOWN: + { + if (--con.history_index < -1) + { + con.history_index = -1; + } + + clear(); + + if (con.history_index != -1) + { + strncpy_s(con.buffer, con.history.at(con.history_index).data(), sizeof(con.buffer)); + con.cursor = static_cast(strlen(con.buffer)); + } + + update(); + break; + } + case VK_LEFT: + { + if (con.cursor > 0) + { + con.cursor--; + set_cursor_pos(con.cursor); + //set_cursor_pos(branding_lenght + con.cursor); + + } + + break; + } + case VK_RIGHT: + { + if (con.cursor < std::strlen(con.buffer)) + { + con.cursor++; + set_cursor_pos(con.cursor); + //set_cursor_pos(branding_lenght + con.cursor); + } + + break; + } + case VK_RETURN: + { + if (con.history_index != -1) + { + const auto itr = con.history.begin() + con.history_index; + + if (*itr == con.buffer) + { + con.history.erase(con.history.begin() + con.history_index); + } + } + + if (con.buffer[0]) + { + con.history.push_front(con.buffer); + } + + if (con.history.size() > 10) + { + con.history.erase(con.history.begin() + 10); + } + + con.history_index = -1; + game::Cbuf_AddText(0, utilities::string::va("%s \n", con.buffer)); + + con.cursor = 0; + + clear_output(); + + invoke_printf("]%s\r\n", con.buffer); + + strncpy_s(con.buffer, "", sizeof(con.buffer)); + break; + } + case VK_BACK: + { + if (con.cursor <= 0) + { + break; + } + + clear_output(); + + std::memmove(con.buffer + con.cursor - 1, con.buffer + con.cursor, strlen(con.buffer) + 1 - con.cursor); + con.buffer - 1; + con.cursor--; + + update(); + break; + } + case VK_ESCAPE: + { + con.cursor = 0; + clear_output(); + strncpy_s(con.buffer, "", sizeof(con.buffer)); + break; + } + default: + { + const auto c = record.Event.KeyEvent.uChar.AsciiChar; + if (!c) + { + break; + } + + if (std::strlen(con.buffer) + 1 >= get_max_input_length()) + { + break; + } + + std::memmove(con.buffer + con.cursor + 1, + con.buffer + con.cursor, std::strlen(con.buffer) + 1 - con.cursor); + con.buffer[con.cursor] = c; + con.cursor++; + + update(); + break; + } + } + } + + int __cdecl printf_stub(const char* fmt, ...) + { + va_list ap; + va_start(ap, fmt); + const auto result = format(&ap, fmt); + va_end(ap); + + return dispatch_message(con_type_info, result); + } + } + + void print(const int type, const char* fmt, ...) + { + va_list ap; + va_start(ap, fmt); + const auto result = format(&ap, fmt); + va_end(ap); + + dispatch_message(type, result); + } + + class component final : public component_interface + { + public: + component() + { + ShowWindow(GetConsoleWindow(), SW_HIDE); + } + + void pre_start() override + { + FILE* empty; + AllocConsole(); + AttachConsole(GetCurrentProcessId()); + freopen_s(&empty, "CONOUT$", "r", stdin); + freopen_s(&empty, "CONOUT$", "w", stdout); + freopen_s(&empty, "CONOUT$", "w", stderr); + SetConsoleTitle("Project-Bo4:"); + + printf_hook.create(printf, printf_stub); + + ShowWindow(GetConsoleWindow(), SW_SHOW); + + con.kill_event = CreateEvent(NULL, TRUE, FALSE, NULL); + + con.thread = utilities::thread::create_named_thread("Console", []() + { + const auto handle = GetStdHandle(STD_INPUT_HANDLE); + HANDLE handles[2] = {handle, con.kill_event}; + MSG msg{}; + + INPUT_RECORD record{}; + DWORD num_events{}; + + while (!con.kill) + { + const auto result = MsgWaitForMultipleObjects(2, handles, FALSE, INFINITE, QS_ALLINPUT); + if (con.kill) + { + return; + } + + switch (result) + { + case WAIT_OBJECT_0: + { + if (!ReadConsoleInput(handle, &record, 1, &num_events) || num_events == 0) + { + break; + } + + handle_input(record); + break; + } + case WAIT_OBJECT_0 + 1: + { + if (!PeekMessageA(&msg, GetConsoleWindow(), NULL, NULL, PM_REMOVE)) + { + break; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + break; + } + } + } + }); + } + + void pre_destroy() override + { + con.kill = true; + SetEvent(con.kill_event); + + if (con.thread.joinable()) + { + con.thread.join(); + } + } + }; +} + +REGISTER_COMPONENT(console::component) diff --git a/source/proxy-dll/component/console.hpp b/source/proxy-dll/component/console.hpp new file mode 100644 index 0000000..4101a3a --- /dev/null +++ b/source/proxy-dll/component/console.hpp @@ -0,0 +1,44 @@ +#pragma once + +namespace console +{ + HWND get_window(); + void set_title(std::string title); + void set_size(int width, int height); + + enum console_type + { + con_type_error = 1, + con_type_debug = 2, + con_type_warning = 3, + con_type_info = 7 + }; + + void print(int type, const char* fmt, ...); + + template + void error(const char* fmt, Args&&... args) + { + print(con_type_error, fmt, std::forward(args)...); + } + + template + void debug(const char* fmt, Args&&... args) + { +#ifdef DEBUG + print(con_type_debug, fmt, std::forward(args)...); +#endif + } + + template + void warn(const char* fmt, Args&&... args) + { + print(con_type_warning, fmt, std::forward(args)...); + } + + template + void info(const char* fmt, Args&&... args) + { + print(con_type_info, fmt, std::forward(args)...); + } +} \ No newline at end of file diff --git a/source/proxy-dll/component/logger.cpp b/source/proxy-dll/component/logger.cpp index bb92c77..f474033 100644 --- a/source/proxy-dll/component/logger.cpp +++ b/source/proxy-dll/component/logger.cpp @@ -37,6 +37,7 @@ namespace logger OutputDebugStringA(text.c_str()); #endif // OUTPUT_DEBUG_API + printf(text.c_str()); //print debug messages to new console std::ofstream fs; fs.open("t8-mod.log", std::ios_base::app);