diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp index 84899d56..841e72eb 100644 --- a/src/client/component/console.cpp +++ b/src/client/component/console.cpp @@ -1,38 +1,29 @@ #include #include "loader/component_loader.hpp" +#include "resource.hpp" + +#include "game/game.hpp" #include #include +#define CONSOLE_BUFFER_SIZE 16384 + namespace console { namespace { volatile bool g_started = false; - - void create_game_console() - { - reinterpret_cast(0x142333F80_g)(); - } + HANDLE logo; void print_message(const char* message) { if (g_started) { - reinterpret_cast(0x1421499C0_g)(0, 0, "%s", message); + game::Com_Printf(0, 0, "%s", message); } } - void execute_command(const char* command) - { - reinterpret_cast(0x1420EC8B0_g)(0, command); - } - - void terminate_game() - { - execute_command("quit\n"); - } - void print_stub(const char* fmt, ...) { va_list ap; @@ -45,6 +36,114 @@ namespace console va_end(ap); } + + LRESULT con_wnd_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + switch (uMsg) + { + case WM_CTLCOLORSTATIC: + SetBkColor((HDC)wParam, RGB(50, 50, 50)); + SetTextColor((HDC)wParam, RGB(232, 230, 227)); + return (INT_PTR)CreateSolidBrush(RGB(50, 50, 50)); + case WM_CTLCOLOREDIT: + SetBkColor((HDC)wParam, RGB(50, 50, 50)); + SetTextColor((HDC)wParam, RGB(232, 230, 227)); + return (INT_PTR)CreateSolidBrush(RGB(50, 50, 50)); + case WM_QUIT: + game::Cbuf_AddText(0, "quit\n"); + return DefWindowProcA(hWnd, uMsg, wParam, lParam); + default: + return utils::hook::invoke(0x142333520_g, hWnd, uMsg, wParam, lParam); + } + } + + LRESULT input_line_wnd_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + return utils::hook::invoke(0x142333820_g, hWnd, uMsg, wParam, lParam); + } + + void sys_create_console_stub(HINSTANCE hInstance) + { + // C6262 + char text[CONSOLE_BUFFER_SIZE]; + char cleanConsoleBuffer[CONSOLE_BUFFER_SIZE]; + + const auto* class_name = "BOIII WinConsole"; + const auto* window_name = "BOIII Console"; + + WNDCLASSA WndClass; + WndClass.style = 0; + WndClass.lpfnWndProc = con_wnd_proc; + WndClass.cbClsExtra = 0; + WndClass.cbWndExtra = 0; + WndClass.hInstance = hInstance; + WndClass.hIcon = LoadIconA(hInstance, (LPCSTR)1); + WndClass.hCursor = LoadCursorA(0, (LPCSTR)0x7F00); + WndClass.hbrBackground = CreateSolidBrush(RGB(50, 50, 50)); + WndClass.lpszMenuName = 0; + WndClass.lpszClassName = class_name; + + if (!RegisterClassA(&WndClass)) + { + return; + } + + tagRECT Rect; + Rect.left = 0; + Rect.right = 620; + Rect.top = 0; + Rect.bottom = 450; + AdjustWindowRect(&Rect, 0x80CA0000, 0); + + auto hDC = GetDC(GetDesktopWindow()); + auto swidth = GetDeviceCaps(hDC, 8); + auto sheight = GetDeviceCaps(hDC, 10); + ReleaseDC(GetDesktopWindow(), hDC); + + utils::hook::set(game::s_wcd::windowWidth, Rect.right - Rect.left + 1); + utils::hook::set(game::s_wcd::windowHeight, Rect.bottom - Rect.top + 1); + + utils::hook::set(game::s_wcd::hWnd, CreateWindowExA( + 0, class_name, window_name, 0x80CA0000, (swidth - 600) / 2, (sheight - 450) / 2, + Rect.right - Rect.left + 1, Rect.bottom - Rect.top + 1, 0, 0, hInstance, 0)); + + if (!*game::s_wcd::hWnd) + { + return; + } + + // create fonts + hDC = GetDC(*game::s_wcd::hWnd); + auto nHeight = MulDiv(8, GetDeviceCaps(hDC, 90), 72); + + utils::hook::set(game::s_wcd::hfBufferFont, CreateFontA( + -nHeight, 0, 0, 0, 300, 0, 0, 0, 1u, 0, 0, 0, 0x31u, "Courier New")); + + ReleaseDC(*game::s_wcd::hWnd, hDC); + + if (logo) + { + utils::hook::set(game::s_wcd::codLogo, CreateWindowExA( + 0, "Static", 0, 0x5000000Eu, 5, 5, 0, 0, *game::s_wcd::hWnd, (HMENU)1, hInstance, 0)); + SendMessageA(*game::s_wcd::codLogo, 0x172u, 0, (LPARAM)logo); + } + + // create the input line + utils::hook::set(game::s_wcd::hwndInputLine, CreateWindowExA( + 0, "edit", 0, 0x50800080u, 6, 400, 608, 20, *game::s_wcd::hWnd, (HMENU)0x65, hInstance, 0)); + utils::hook::set(game::s_wcd::hwndBuffer, CreateWindowExA( + 0, "edit", 0, 0x50A00844u, 6, 70, 606, 324, *game::s_wcd::hWnd, (HMENU)0x64, hInstance, 0)); + SendMessageA(*game::s_wcd::hwndBuffer, WM_SETFONT, (WPARAM)*game::s_wcd::hfBufferFont, 0); + + utils::hook::set(game::s_wcd::SysInputLineWndProc, (WNDPROC)SetWindowLongPtrA( + *game::s_wcd::hwndInputLine, -4, (LONG_PTR)input_line_wnd_proc)); + SendMessageA(*game::s_wcd::hwndInputLine, WM_SETFONT, (WPARAM)*game::s_wcd::hfBufferFont, 0); + + SetFocus(*game::s_wcd::hwndInputLine); + game::Con_GetTextCopy(text, 0x4000); + game::Conbuf_CleanText(text, cleanConsoleBuffer); + SetWindowTextA(*game::s_wcd::hwndBuffer, cleanConsoleBuffer); + } } class component final : public component_interface @@ -52,6 +151,9 @@ namespace console public: void post_unpack() override { + const utils::nt::library self; + logo = LoadImageA(self.get_handle(), MAKEINTRESOURCEA(IMAGE_LOGO), 0, 0, 0, LR_COPYFROMRESOURCE); + utils::hook::jump(printf, print_stub); this->terminate_runner_ = false; @@ -59,15 +161,10 @@ namespace console this->console_runner_ = utils::thread::create_named_thread("Console IO", [this] { { - utils::hook::detour d; - d.create(0x142333B40_g, utils::hook::assemble([](utils::hook::assembler& a) - { - a.mov(r8, "BOIII Console"); - a.mov(r9d, 0x80CA0000); - a.sub(eax, edx); - a.jmp(0x142333B4F_g); - })); - create_game_console(); + utils::hook::detour sys_create_console_hook; + sys_create_console_hook.create(0x1423339C0_g, sys_create_console_stub); + + game::Sys_ShowConsole(); g_started = true; } @@ -78,11 +175,6 @@ namespace console { TranslateMessage(&msg); DispatchMessageW(&msg); - - if(msg.message == WM_QUIT) - { - terminate_game(); - } } else { diff --git a/src/client/game/game.cpp b/src/client/game/game.cpp new file mode 100644 index 00000000..2660865c --- /dev/null +++ b/src/client/game/game.cpp @@ -0,0 +1,43 @@ +#include + +#include "loader/component_loader.hpp" +#include "game.hpp" + +namespace game +{ + // inlined in cod, functionality stolen from https://github.com/id-Software/Quake-III-Arena/blob/master/code/win32/win_syscon.c#L520 + int Conbuf_CleanText(const char* source, char* target) + { + char* b = target; + int i = 0; + while (source[i] && ((b - target) < sizeof(target) - 1)) + { + if (source[i] == '\n' && source[i + 1] == '\r') + { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + i++; + } + else if (source[i] == '\r' || source[i] == '\n') + { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + } + else if (source && source[0] == '^' && source[1] && source[1] != '^' && source[1] >= 48 && source[1] <= 64) // Q_IsColorString + { + i++; + } + else + { + *b = source[i]; // C6011 here, should we be worried? + b++; + } + i++; + } + + *b = 0; + return static_cast(b - target); + } +} diff --git a/src/client/game/game.hpp b/src/client/game/game.hpp new file mode 100644 index 00000000..00d8ac48 --- /dev/null +++ b/src/client/game/game.hpp @@ -0,0 +1,39 @@ +#pragma once + +//#include "structs.hpp" + +namespace game +{ + int Conbuf_CleanText(const char* source, char* target); + + template + class symbol + { + public: + symbol(const size_t address) + : address_(reinterpret_cast(address)) + { + } + + T* get() const + { + return reinterpret_cast((uint64_t)address_); + } + + operator T* () const + { + return this->get(); + } + + T* operator->() const + { + return this->get(); + } + + private: + T* address_; + }; + +} + +#include "symbols.hpp" diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp new file mode 100644 index 00000000..5fcb8d82 --- /dev/null +++ b/src/client/game/symbols.hpp @@ -0,0 +1,26 @@ +#pragma once + +#define WEAK __declspec(selectany) + +namespace game +{ + WEAK symbol Cbuf_AddText{0x1420EC8B0_g}; + + WEAK symbol Com_Printf{0x1421499C0_g}; + + WEAK symbol Con_GetTextCopy{0x14133A7D0_g}; + + WEAK symbol Sys_ShowConsole{0x142333F80_g}; + + namespace s_wcd + { + WEAK symbol codLogo{0x157E77A50_g}; + WEAK symbol hfBufferFont{0x157E77A58_g}; + WEAK symbol hWnd{0x157E77A40_g}; + WEAK symbol hwndBuffer{0x157E77A48_g}; + WEAK symbol hwndInputLine{0x157E77A60_g}; + WEAK symbol windowHeight{0x157E7806C_g}; + WEAK symbol windowWidth{0x157E78068_g}; + WEAK symbol SysInputLineWndProc{0x157E78070_g}; + } +} diff --git a/src/client/resource.hpp b/src/client/resource.hpp index d546dabf..f01f31e0 100644 --- a/src/client/resource.hpp +++ b/src/client/resource.hpp @@ -2,3 +2,5 @@ #define ID_ICON 102 #define IMAGE_SPLASH 300 + +#define IMAGE_LOGO 301 diff --git a/src/client/resource.rc b/src/client/resource.rc index 68145b75..e3563bab 100644 --- a/src/client/resource.rc +++ b/src/client/resource.rc @@ -93,6 +93,7 @@ END //ID_ICON ICON "resources/icon.ico" IMAGE_SPLASH BITMAP "resources/splash.bmp" +IMAGE_LOGO BITMAP "resources/logo.bmp" #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/src/client/resources/logo.bmp b/src/client/resources/logo.bmp new file mode 100644 index 00000000..d62dc9cb Binary files /dev/null and b/src/client/resources/logo.bmp differ diff --git a/src/client/steam/steam.cpp b/src/client/steam/steam.cpp index 54c3cf01..686a7adf 100644 --- a/src/client/steam/steam.cpp +++ b/src/client/steam/steam.cpp @@ -115,7 +115,7 @@ namespace steam return false; } - MessageBoxA(nullptr, "Steam must be installed for the game to run. Please install steam!", "Error", MB_ICONERROR); + MessageBoxA(nullptr, "Steam must be installed for the game to run. Please install Steam!", "Error", MB_ICONERROR); ShellExecuteA(nullptr, "open", "https://store.steampowered.com/about/", nullptr, nullptr, SW_SHOWNORMAL); TerminateProcess(GetCurrentProcess(), 1); return true;