2022-05-21 06:04:08 -04:00
|
|
|
#include <std_include.hpp>
|
2023-02-17 14:45:19 -05:00
|
|
|
#include "console.hpp"
|
2022-05-21 06:04:08 -04:00
|
|
|
#include "loader/component_loader.hpp"
|
2022-06-02 04:14:44 -04:00
|
|
|
#include "resource.hpp"
|
|
|
|
|
|
|
|
#include "game/game.hpp"
|
2023-02-16 17:09:37 -05:00
|
|
|
#include "scheduler.hpp"
|
2022-05-21 06:04:08 -04:00
|
|
|
|
|
|
|
#include <utils/thread.hpp>
|
|
|
|
#include <utils/hook.hpp>
|
2023-03-09 12:44:19 -05:00
|
|
|
#include <utils/flags.hpp>
|
2022-10-01 04:26:07 -04:00
|
|
|
#include <utils/concurrency.hpp>
|
2022-12-04 06:24:21 -05:00
|
|
|
#include <utils/image.hpp>
|
2022-05-21 06:04:08 -04:00
|
|
|
|
2022-06-02 15:50:30 -04:00
|
|
|
#define CONSOLE_BUFFER_SIZE 16384
|
|
|
|
#define WINDOW_WIDTH 608
|
2022-06-02 04:14:44 -04:00
|
|
|
|
2022-05-21 06:04:08 -04:00
|
|
|
namespace console
|
|
|
|
{
|
2023-02-17 14:45:19 -05:00
|
|
|
void set_title(const std::string& title)
|
|
|
|
{
|
|
|
|
SetWindowTextA(*game::s_wcd::hWnd, title.data());
|
|
|
|
}
|
|
|
|
|
2022-05-23 11:57:45 -04:00
|
|
|
namespace
|
|
|
|
{
|
2022-12-04 06:24:21 -05:00
|
|
|
utils::image::object logo;
|
2022-09-17 02:00:43 -04:00
|
|
|
std::atomic_bool started{false};
|
|
|
|
std::atomic_bool terminate_runner{false};
|
2023-03-14 14:12:50 -04:00
|
|
|
utils::concurrency::container<std::function<void(const std::string& message)>> interceptor{};
|
2022-10-01 04:26:07 -04:00
|
|
|
utils::concurrency::container<std::queue<std::string>> message_queue{};
|
2022-05-30 14:37:58 -04:00
|
|
|
|
|
|
|
void print_message(const char* message)
|
|
|
|
{
|
2022-10-17 14:10:18 -04:00
|
|
|
#ifndef NDEBUG
|
2023-01-06 04:29:24 -05:00
|
|
|
OutputDebugStringA(message);
|
2022-10-17 14:10:18 -04:00
|
|
|
#endif
|
2023-01-06 04:29:24 -05:00
|
|
|
|
|
|
|
if (started && !terminate_runner)
|
|
|
|
{
|
2022-06-02 04:14:44 -04:00
|
|
|
game::Com_Printf(0, 0, "%s", message);
|
2022-05-30 14:37:58 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-01 04:26:07 -04:00
|
|
|
void queue_message(const char* message)
|
|
|
|
{
|
2023-03-14 14:12:50 -04:00
|
|
|
interceptor.access([message](const std::function<void(const std::string&)>& callback)
|
|
|
|
{
|
|
|
|
if (callback)
|
|
|
|
{
|
|
|
|
callback(message);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-10-01 04:26:07 -04:00
|
|
|
message_queue.access([message](std::queue<std::string>& queue)
|
|
|
|
{
|
|
|
|
queue.push(message);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void print_message_to_console(const char* message)
|
|
|
|
{
|
|
|
|
static auto print_func = utils::hook::assemble([](utils::hook::assembler& a)
|
|
|
|
{
|
|
|
|
a.push(rbx);
|
|
|
|
a.mov(eax, 0x8030);
|
2023-03-04 06:36:06 -05:00
|
|
|
a.jmp(game::select(0x142332AA7, 0x140597527));
|
2022-10-01 04:26:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
static_cast<void(*)(const char*)>(print_func)(message);
|
|
|
|
}
|
|
|
|
|
2022-10-01 12:08:40 -04:00
|
|
|
std::queue<std::string> empty_message_queue()
|
2022-10-01 04:26:07 -04:00
|
|
|
{
|
2022-10-01 08:02:35 -04:00
|
|
|
std::queue<std::string> current_queue{};
|
|
|
|
|
|
|
|
message_queue.access([&](std::queue<std::string>& queue)
|
2022-10-01 04:26:07 -04:00
|
|
|
{
|
2022-10-01 08:02:35 -04:00
|
|
|
current_queue = std::move(queue);
|
|
|
|
queue = {};
|
2022-10-01 04:26:07 -04:00
|
|
|
});
|
2022-10-01 08:02:35 -04:00
|
|
|
|
2022-10-01 12:08:40 -04:00
|
|
|
return current_queue;
|
2022-10-01 04:26:07 -04:00
|
|
|
}
|
|
|
|
|
2022-05-30 14:37:58 -04:00
|
|
|
void print_stub(const char* fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
|
|
|
char buffer[1024]{0};
|
|
|
|
const int res = vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
|
2022-05-30 15:25:52 -04:00
|
|
|
(void)res;
|
2022-05-30 14:37:58 -04:00
|
|
|
print_message(buffer);
|
|
|
|
|
|
|
|
va_end(ap);
|
|
|
|
}
|
2022-06-02 04:14:44 -04:00
|
|
|
|
2022-10-01 13:54:47 -04:00
|
|
|
INT_PTR get_gray_brush()
|
|
|
|
{
|
2022-12-04 06:24:21 -05:00
|
|
|
static utils::image::object b(CreateSolidBrush(RGB(50, 50, 50)));
|
|
|
|
return reinterpret_cast<INT_PTR>(b.get());
|
2022-10-01 13:54:47 -04:00
|
|
|
}
|
|
|
|
|
2022-06-02 14:42:58 -04:00
|
|
|
LRESULT con_wnd_proc(const HWND hwnd, const UINT msg, const WPARAM wparam, const LPARAM lparam)
|
2022-06-02 04:14:44 -04:00
|
|
|
{
|
2022-06-02 14:42:58 -04:00
|
|
|
switch (msg)
|
2022-06-02 04:14:44 -04:00
|
|
|
{
|
|
|
|
case WM_CTLCOLOREDIT:
|
2022-06-02 14:42:58 -04:00
|
|
|
case WM_CTLCOLORSTATIC:
|
|
|
|
SetBkColor(reinterpret_cast<HDC>(wparam), RGB(50, 50, 50));
|
|
|
|
SetTextColor(reinterpret_cast<HDC>(wparam), RGB(232, 230, 227));
|
2022-10-01 13:54:47 -04:00
|
|
|
return get_gray_brush();
|
2022-06-02 14:49:03 -04:00
|
|
|
case WM_CLOSE:
|
2022-06-02 07:42:32 -04:00
|
|
|
game::Cbuf_AddText(0, "quit\n");
|
2022-06-02 14:49:03 -04:00
|
|
|
[[fallthrough]];
|
2022-06-02 04:14:44 -04:00
|
|
|
default:
|
2023-03-04 06:36:06 -05:00
|
|
|
return utils::hook::invoke<LRESULT>(game::select(0x142332960, 0x1405973E0), hwnd, msg, wparam, lparam);
|
2022-06-02 04:14:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-02 14:42:58 -04:00
|
|
|
LRESULT input_line_wnd_proc(const HWND hwnd, const UINT msg, const WPARAM wparam, const LPARAM lparam)
|
2022-06-02 04:14:44 -04:00
|
|
|
{
|
2023-03-04 06:36:06 -05:00
|
|
|
return utils::hook::invoke<LRESULT>(game::select(0x142332C60, 0x1405976E0), hwnd, msg, wparam, lparam);
|
2022-06-02 04:14:44 -04:00
|
|
|
}
|
|
|
|
|
2022-06-02 14:42:58 -04:00
|
|
|
void sys_create_console_stub(const HINSTANCE h_instance)
|
2022-06-02 04:14:44 -04:00
|
|
|
{
|
2022-11-11 11:19:26 -05:00
|
|
|
char text[CONSOLE_BUFFER_SIZE]{0};
|
2022-06-02 04:14:44 -04:00
|
|
|
|
|
|
|
const auto* class_name = "BOIII WinConsole";
|
2023-01-06 04:29:24 -05:00
|
|
|
const auto* window_name = game::is_server() ? "BOIII Server" : "BOIII Console";
|
2022-06-02 04:14:44 -04:00
|
|
|
|
2022-06-02 14:42:58 -04:00
|
|
|
WNDCLASSA wnd_class{};
|
|
|
|
wnd_class.style = 0;
|
|
|
|
wnd_class.lpfnWndProc = con_wnd_proc;
|
|
|
|
wnd_class.cbClsExtra = 0;
|
|
|
|
wnd_class.cbWndExtra = 0;
|
|
|
|
wnd_class.hInstance = h_instance;
|
|
|
|
wnd_class.hIcon = LoadIconA(h_instance, reinterpret_cast<LPCSTR>(1));
|
|
|
|
wnd_class.hCursor = LoadCursorA(nullptr, reinterpret_cast<LPCSTR>(0x7F00));
|
|
|
|
wnd_class.hbrBackground = CreateSolidBrush(RGB(50, 50, 50));
|
|
|
|
wnd_class.lpszMenuName = nullptr;
|
|
|
|
wnd_class.lpszClassName = class_name;
|
|
|
|
|
|
|
|
if (!RegisterClassA(&wnd_class))
|
2022-06-02 04:14:44 -04:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-02 15:45:45 -04:00
|
|
|
RECT rect{};
|
2022-06-02 14:42:58 -04:00
|
|
|
rect.left = 0;
|
|
|
|
rect.right = 620;
|
|
|
|
rect.top = 0;
|
|
|
|
rect.bottom = 450;
|
|
|
|
AdjustWindowRect(&rect, 0x80CA0000, 0);
|
2022-06-02 04:14:44 -04:00
|
|
|
|
2022-06-02 14:42:58 -04:00
|
|
|
auto dc = GetDC(GetDesktopWindow());
|
|
|
|
const auto swidth = GetDeviceCaps(dc, 8);
|
|
|
|
const auto sheight = GetDeviceCaps(dc, 10);
|
|
|
|
ReleaseDC(GetDesktopWindow(), dc);
|
2022-06-02 04:14:44 -04:00
|
|
|
|
2022-06-02 15:45:45 -04:00
|
|
|
utils::hook::set<int>(game::s_wcd::windowWidth, (rect.right - rect.left + 1));
|
|
|
|
utils::hook::set<int>(game::s_wcd::windowHeight, (rect.bottom - rect.top + 1));
|
2022-06-02 04:14:44 -04:00
|
|
|
|
|
|
|
utils::hook::set<HWND>(game::s_wcd::hWnd, CreateWindowExA(
|
2022-06-02 14:42:58 -04:00
|
|
|
0, class_name, window_name, 0x80CA0000, (swidth - 600) / 2, (sheight - 450) / 2,
|
|
|
|
rect.right - rect.left + 1, rect.bottom - rect.top + 1, nullptr, nullptr,
|
|
|
|
h_instance, nullptr));
|
2022-06-02 04:14:44 -04:00
|
|
|
|
|
|
|
if (!*game::s_wcd::hWnd)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create fonts
|
2022-06-02 14:42:58 -04:00
|
|
|
dc = GetDC(*game::s_wcd::hWnd);
|
|
|
|
const auto n_height = MulDiv(8, GetDeviceCaps(dc, 90), 72);
|
2022-06-02 04:14:44 -04:00
|
|
|
|
|
|
|
utils::hook::set<HFONT>(game::s_wcd::hfBufferFont, CreateFontA(
|
2022-06-02 14:42:58 -04:00
|
|
|
-n_height, 0, 0, 0, 300, 0, 0, 0, 1u, 0, 0, 0, 0x31u, "Courier New"));
|
2022-06-02 04:14:44 -04:00
|
|
|
|
2022-06-02 14:42:58 -04:00
|
|
|
ReleaseDC(*game::s_wcd::hWnd, dc);
|
2022-06-02 04:14:44 -04:00
|
|
|
|
|
|
|
if (logo)
|
|
|
|
{
|
|
|
|
utils::hook::set<HWND>(game::s_wcd::codLogo, CreateWindowExA(
|
2022-06-02 14:42:58 -04:00
|
|
|
0, "Static", nullptr, 0x5000000Eu, 5, 5, 0, 0, *game::s_wcd::hWnd,
|
|
|
|
reinterpret_cast<HMENU>(1), h_instance, nullptr));
|
2022-12-04 06:24:21 -05:00
|
|
|
SendMessageA(*game::s_wcd::codLogo, STM_SETIMAGE, IMAGE_BITMAP, logo);
|
2022-06-02 04:14:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// create the input line
|
|
|
|
utils::hook::set<HWND>(game::s_wcd::hwndInputLine, CreateWindowExA(
|
2022-06-02 15:50:30 -04:00
|
|
|
0, "edit", nullptr, 0x50800080u, 6, 400, WINDOW_WIDTH, 20, *game::s_wcd::hWnd,
|
2022-06-02 14:42:58 -04:00
|
|
|
reinterpret_cast<HMENU>(0x65), h_instance, nullptr));
|
2022-06-02 04:14:44 -04:00
|
|
|
utils::hook::set<HWND>(game::s_wcd::hwndBuffer, CreateWindowExA(
|
2022-06-02 15:50:30 -04:00
|
|
|
0, "edit", nullptr, 0x50A00844u, 6, 70, WINDOW_WIDTH, 324, *game::s_wcd::hWnd,
|
2022-06-02 14:42:58 -04:00
|
|
|
reinterpret_cast<HMENU>(0x64), h_instance, nullptr));
|
|
|
|
SendMessageA(*game::s_wcd::hwndBuffer, WM_SETFONT, reinterpret_cast<WPARAM>(*game::s_wcd::hfBufferFont), 0);
|
2022-06-02 04:14:44 -04:00
|
|
|
|
2022-06-02 14:42:58 -04:00
|
|
|
utils::hook::set<WNDPROC>(game::s_wcd::SysInputLineWndProc, reinterpret_cast<WNDPROC>(SetWindowLongPtrA(
|
|
|
|
*game::s_wcd::hwndInputLine, -4,
|
|
|
|
reinterpret_cast<LONG_PTR>(input_line_wnd_proc))));
|
2022-09-18 09:11:16 -04:00
|
|
|
SendMessageA(*game::s_wcd::hwndInputLine, WM_SETFONT, reinterpret_cast<WPARAM>(*game::s_wcd::hfBufferFont),
|
|
|
|
0);
|
2022-06-02 04:14:44 -04:00
|
|
|
|
|
|
|
SetFocus(*game::s_wcd::hwndInputLine);
|
2022-11-11 11:19:26 -05:00
|
|
|
game::Con_GetTextCopy(text, std::min(0x4000, static_cast<int>(sizeof(text))));
|
|
|
|
SetWindowTextA(*game::s_wcd::hwndBuffer, text);
|
2022-06-02 04:14:44 -04:00
|
|
|
}
|
2022-05-23 11:57:45 -04:00
|
|
|
}
|
2022-05-21 06:04:08 -04:00
|
|
|
|
2023-03-14 14:12:50 -04:00
|
|
|
void set_interceptor(std::function<void(const std::string& message)> callback)
|
|
|
|
{
|
|
|
|
interceptor.access([&callback](std::function<void(const std::string&)>& c)
|
|
|
|
{
|
|
|
|
c = std::move(callback);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void remove_interceptor()
|
|
|
|
{
|
|
|
|
set_interceptor({});
|
|
|
|
}
|
|
|
|
|
2023-01-06 04:29:24 -05:00
|
|
|
struct component final : generic_component
|
2022-05-21 06:04:08 -04:00
|
|
|
{
|
|
|
|
void post_unpack() override
|
|
|
|
{
|
2023-01-06 04:29:24 -05:00
|
|
|
if (!game::is_server())
|
|
|
|
{
|
|
|
|
utils::hook::set<uint8_t>(0x14133D2FE_g, 0xEB); // Always enable ingame console
|
2023-03-09 12:44:19 -05:00
|
|
|
|
|
|
|
if (utils::nt::is_wine() && !utils::flags::has_flag("console"))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2023-01-06 04:29:24 -05:00
|
|
|
}
|
|
|
|
|
2023-03-09 12:44:19 -05:00
|
|
|
utils::hook::jump(printf, print_stub);
|
|
|
|
|
2023-03-04 06:36:06 -05:00
|
|
|
utils::hook::jump(game::select(0x142332C30, 0x1405976B0), queue_message);
|
|
|
|
utils::hook::nop(game::select(0x142332C4A, 0x1405976CA), 2); // Print from every thread
|
2022-09-18 09:11:16 -04:00
|
|
|
|
2022-12-04 06:24:21 -05:00
|
|
|
//const auto self = utils::nt::library::get_by_address(sys_create_console_stub);
|
|
|
|
//logo = LoadImageA(self.get_handle(), MAKEINTRESOURCEA(IMAGE_LOGO), 0, 0, 0, LR_COPYFROMRESOURCE);
|
|
|
|
|
|
|
|
const auto res = utils::nt::load_resource(IMAGE_LOGO);
|
|
|
|
const auto img = utils::image::load_image(res);
|
|
|
|
logo = utils::image::create_bitmap(img);
|
2022-06-02 04:14:44 -04:00
|
|
|
|
2022-09-17 02:00:43 -04:00
|
|
|
terminate_runner = false;
|
2022-05-21 06:04:08 -04:00
|
|
|
|
2022-10-01 12:08:40 -04:00
|
|
|
this->message_runner_ = utils::thread::create_named_thread("Console IO", []
|
2022-10-01 08:02:35 -04:00
|
|
|
{
|
|
|
|
while (!terminate_runner)
|
|
|
|
{
|
2022-10-01 14:16:12 -04:00
|
|
|
std::string message_buffer{};
|
2022-10-01 12:08:40 -04:00
|
|
|
auto current_queue = empty_message_queue();
|
2022-10-01 14:16:12 -04:00
|
|
|
|
2022-10-01 12:23:29 -04:00
|
|
|
while (!current_queue.empty())
|
2022-10-01 12:08:40 -04:00
|
|
|
{
|
|
|
|
const auto& msg = current_queue.front();
|
2022-10-01 14:16:12 -04:00
|
|
|
message_buffer.append(msg);
|
2022-10-01 12:08:40 -04:00
|
|
|
current_queue.pop();
|
|
|
|
}
|
|
|
|
|
2022-10-01 14:16:12 -04:00
|
|
|
if (!message_buffer.empty())
|
|
|
|
{
|
|
|
|
print_message_to_console(message_buffer.data());
|
|
|
|
}
|
|
|
|
|
2022-10-01 13:54:47 -04:00
|
|
|
std::this_thread::sleep_for(5ms);
|
2022-10-01 08:02:35 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-10-01 12:08:40 -04:00
|
|
|
this->console_runner_ = utils::thread::create_named_thread("Console Window", [this]
|
2022-05-21 06:04:08 -04:00
|
|
|
{
|
2022-05-27 13:08:39 -04:00
|
|
|
{
|
2022-09-16 14:54:48 -04:00
|
|
|
static utils::hook::detour sys_create_console_hook;
|
2023-03-04 06:36:06 -05:00
|
|
|
sys_create_console_hook.create(game::select(0x142332E00, 0x140597880), sys_create_console_stub);
|
2022-06-02 04:14:44 -04:00
|
|
|
|
|
|
|
game::Sys_ShowConsole();
|
2022-09-17 02:00:43 -04:00
|
|
|
started = true;
|
2022-05-27 13:08:39 -04:00
|
|
|
}
|
2022-05-23 11:57:45 -04:00
|
|
|
|
|
|
|
MSG msg{};
|
2022-09-17 02:00:43 -04:00
|
|
|
while (!terminate_runner)
|
2022-05-23 11:57:45 -04:00
|
|
|
{
|
2022-05-29 12:51:24 -04:00
|
|
|
if (PeekMessageW(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
2022-05-23 11:57:45 -04:00
|
|
|
{
|
|
|
|
TranslateMessage(&msg);
|
2022-05-29 12:51:24 -04:00
|
|
|
DispatchMessageW(&msg);
|
2022-05-23 11:57:45 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-10-01 12:08:40 -04:00
|
|
|
std::this_thread::sleep_for(5ms);
|
2022-05-23 11:57:45 -04:00
|
|
|
}
|
|
|
|
}
|
2022-05-21 06:04:08 -04:00
|
|
|
});
|
2023-01-26 14:36:14 -05:00
|
|
|
|
|
|
|
while (!started)
|
|
|
|
{
|
|
|
|
std::this_thread::sleep_for(10ms);
|
|
|
|
}
|
2022-05-21 06:04:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void pre_destroy() override
|
|
|
|
{
|
2022-09-17 02:00:43 -04:00
|
|
|
terminate_runner = true;
|
2022-05-21 06:04:08 -04:00
|
|
|
|
2022-10-01 08:02:35 -04:00
|
|
|
if (this->message_runner_.joinable())
|
|
|
|
{
|
|
|
|
this->message_runner_.join();
|
|
|
|
}
|
|
|
|
|
2022-05-21 06:04:08 -04:00
|
|
|
if (this->console_runner_.joinable())
|
|
|
|
{
|
|
|
|
this->console_runner_.join();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2023-01-06 04:39:07 -05:00
|
|
|
std::thread console_runner_{};
|
|
|
|
std::thread message_runner_{};
|
2022-05-21 06:04:08 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-05 04:47:12 -05:00
|
|
|
REGISTER_COMPONENT(console::component)
|