External console
* Added Experimental External Console Co-authored-by: bodnjenie14 <134313862+bodnjenie23@users.noreply.github.com>
This commit is contained in:
parent
977d6c0660
commit
6e8d232e78
405
source/proxy-dll/component/console.cpp
Normal file
405
source/proxy-dll/component/console.cpp
Normal file
@ -0,0 +1,405 @@
|
||||
//TODO: Fix console branding
|
||||
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utilities/string.hpp>
|
||||
#include "definitions/game.hpp"
|
||||
#include "console.hpp"
|
||||
#include <utilities/thread.hpp>
|
||||
#include <utilities/hook.hpp>
|
||||
|
||||
#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<std::string> history{};
|
||||
} con{};
|
||||
|
||||
void set_cursor_pos(int x)
|
||||
{
|
||||
CONSOLE_SCREEN_BUFFER_INFO info{};
|
||||
GetConsoleScreenBufferInfo(OUTPUT_HANDLE, &info);
|
||||
info.dwCursorPosition.X = static_cast<short>(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 <typename... Args>
|
||||
int invoke_printf(const char* fmt, Args&&... args)
|
||||
{
|
||||
return printf_hook.invoke<int>(fmt, std::forward<Args>(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<size_t>(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<size_t>(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<int>(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<int>(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<int>(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)
|
44
source/proxy-dll/component/console.hpp
Normal file
44
source/proxy-dll/component/console.hpp
Normal file
@ -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 <typename... Args>
|
||||
void error(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_error, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void debug(const char* fmt, Args&&... args)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
print(con_type_debug, fmt, std::forward<Args>(args)...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void warn(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_warning, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void info(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_info, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
@ -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("project-bo4.log", std::ios_base::app);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user