Cool console! (#477)

* Cool console!

* Debug-specific colors

* Fix release build

* Clear text "as we go"

* dpi awareness :>

* Address diamante'"s formatting review

* Addressed review, added font license

Co-authored-by: rackover <roxanne@thegamebakers.com>
Co-authored-by: Louvenarde <louve@louve.systems>
This commit is contained in:
Louve
2022-09-20 18:33:21 +02:00
committed by GitHub
parent ceaa63b01c
commit ea09208b2a
6 changed files with 39082 additions and 8 deletions

View File

@ -1,5 +1,7 @@
#include <STDInclude.hpp>
#define REMOVE_HEADERBAR 1
namespace Components
{
WINDOW* Console::OutputWindow;
@ -20,6 +22,24 @@ namespace Components
bool Console::HasConsole = false;
bool Console::SkipShutdown = false;
COLORREF Console::TextColor =
#if DEBUG
RGB(255, 200, 117);
#else
RGB(120, 237, 122);
#endif
COLORREF Console::BackgroundColor =
#if DEBUG
RGB(35, 21, 0);
#else
RGB(25, 32, 25);
#endif
HBRUSH Console::ForegroundBrush = CreateSolidBrush(TextColor);
HBRUSH Console::BackgroundBrush = CreateSolidBrush(BackgroundColor);
HANDLE Console::CustomConsoleFont;
std::thread Console::ConsoleThread;
Game::SafeArea Console::OriginalSafeArea;
@ -110,6 +130,44 @@ namespace Components
}
}
float Console::GetDpiScale(const HWND hWnd)
{
const auto user32 = Utils::Library("user32.dll");
const auto getDpiForWindow = user32.getProc<UINT(WINAPI*)(HWND)>("GetDpiForWindow");
const auto getDpiForMonitor = user32.getProc<HRESULT(WINAPI*)(HMONITOR, int, UINT*, UINT*)>("GetDpiForMonitor");
int dpi;
if (getDpiForWindow)
{
dpi = getDpiForWindow(hWnd);
}
else if (getDpiForMonitor)
{
HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
UINT xdpi, ydpi;
LRESULT success = getDpiForMonitor(hMonitor, 0, &xdpi, &ydpi);
if (success == S_OK)
{
dpi = static_cast<int>(ydpi);
}
dpi = 96;
}
else
{
HDC hDC = GetDC(hWnd);
INT ydpi = GetDeviceCaps(hDC, LOGPIXELSY);
ReleaseDC(NULL, hDC);
dpi = ydpi;
}
constexpr auto unawareDpi = 96.0f;
return dpi / unawareDpi;
}
const char* Console::Input()
{
if (!Console::HasConsole)
@ -374,6 +432,261 @@ namespace Components
Console::RefreshOutput();
}
HFONT __stdcall Console::ReplaceFont(
[[maybe_unused]] int cHeight,
int cWidth,
int cEscapement,
int cOrientation,
[[maybe_unused]] int cWeight,
DWORD bItalic,
DWORD bUnderline,
DWORD bStrikeOut,
DWORD iCharSet,
[[maybe_unused]] DWORD iOutPrecision,
DWORD iClipPrecision,
[[maybe_unused]] DWORD iQuality,
[[maybe_unused]] DWORD iPitchAndFamily,
[[maybe_unused]] LPCSTR pszFaceName)
{
auto font = CreateFontA(
12,
cWidth,
cEscapement,
cOrientation,
700,
bItalic,
bUnderline,
bStrikeOut,
iCharSet,
OUT_RASTER_PRECIS,
iClipPrecision,
NONANTIALIASED_QUALITY,
0x31,
"Terminus (TTF)"); // Terminus (TTF)
return font;
}
void Console::GetWindowPos(HWND hWnd, int* x, int* y)
{
HWND hWndParent = GetParent(hWnd);
POINT p = { 0 };
MapWindowPoints(hWnd, hWndParent, &p, 1);
(*x) = p.x;
(*y) = p.y;
}
BOOL CALLBACK Console::ResizeChildWindow(HWND hwndChild, LPARAM lParam)
{
auto id = GetWindowLong(hwndChild, GWL_ID);
bool isInputBox = id == INPUT_BOX;
bool isOutputBox = id == OUTPUT_BOX;
if (isInputBox || isOutputBox)
{
RECT newParentRect = *reinterpret_cast<LPRECT>(lParam);
RECT childRect;
if (GetWindowRect(hwndChild, &childRect))
{
int childX, childY;
GetWindowPos(hwndChild, &childX, &childY);
HWND parent = Utils::Hook::Get<HWND>(0x64A3288);
float scale = GetDpiScale(parent);
if (isInputBox)
{
int newX = childX; // No change!
int newY = static_cast<int>((newParentRect.bottom - newParentRect.top) - 65 * scale);
int newWidth = static_cast<int>((newParentRect.right - newParentRect.left) - 29 * scale);
int newHeight = static_cast<int>((childRect.bottom - childRect.top) * scale); // No change!
MoveWindow(hwndChild, newX, newY, newWidth, newHeight, TRUE);
}
if (isOutputBox)
{
int newX = childX; // No change!
int newY = childY; // No change!
int newWidth = static_cast<int>((newParentRect.right - newParentRect.left) - 29);
int margin = 70;
#ifdef REMOVE_HEADERBAR
margin = 10;
#endif
int newHeight = static_cast<int>((newParentRect.bottom - newParentRect.top) - 74 * scale - margin);
MoveWindow(hwndChild, newX, newY, newWidth, newHeight, TRUE);
}
}
}
return TRUE;
}
// Instead of clearing fully the console text whenever the 0x400's character is written, we
// clear it progressively when we run out of room by truncating the top line by line.
// A bit of trickery with SETREDRAW is required to avoid having the outputbox jump
// around whenever clearing occurs.
void Console::MakeRoomForText([[maybe_unused]] int addedCharacters)
{
constexpr unsigned int maxChars = 0x4000;
constexpr unsigned int maxAffectedChars = 0x100;
HWND outputBox = Utils::Hook::Get<HWND>(0x64A328C);
unsigned int totalChars;
unsigned int totalClearLength = 0;
char str[maxAffectedChars];
unsigned int fetchedCharacters = static_cast<unsigned int>(GetWindowText(outputBox, str, maxAffectedChars));
totalChars = GetWindowTextLengthA(outputBox);
while (totalChars - totalClearLength > maxChars)
{
unsigned int clearLength = maxAffectedChars; // Default to full clear
for (size_t i = 0; i < fetchedCharacters; i++)
{
if (str[i] == '\n')
{
// Shorter clear if I meet a linebreak
clearLength = i + 1;
break;
}
}
totalClearLength += clearLength;
}
if (totalClearLength > 0)
{
SendMessage(outputBox, WM_SETREDRAW, FALSE, 0);
SendMessage(outputBox, EM_SETSEL, 0, totalClearLength);
SendMessage(outputBox, EM_REPLACESEL, FALSE, 0);
SendMessage(outputBox, WM_SETREDRAW, TRUE, 0);
}
Utils::Hook::Set(0x64A38B8, totalChars - totalClearLength);
}
void __declspec(naked) Console::Sys_PrintStub()
{
__asm
{
pushad
push edi
call MakeRoomForText
pop edi
popad
// Go back to AppendText
push 0x4F57F8
ret
}
}
LRESULT CALLBACK Console::ConWndProc(HWND hWnd, UINT Msg, WPARAM wParam, unsigned int lParam)
{
switch (Msg)
{
case WM_CREATE:
{
BOOL darkMode = true;
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
if (SUCCEEDED(DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, reinterpret_cast<LPCVOID>(&darkMode), sizeof(darkMode))))
{
// cool !
}
break;
}
case WM_CTLCOLORSTATIC:
case WM_CTLCOLOREDIT:
{
SetBkColor(reinterpret_cast<HDC>(wParam), BackgroundColor);
SetTextColor(reinterpret_cast<HDC>(wParam), TextColor);
return reinterpret_cast<LRESULT>(BackgroundBrush);
}
case WM_SIZE:
RECT rect;
if (GetWindowRect(hWnd, &rect))
{
EnumChildWindows(hWnd, ResizeChildWindow, reinterpret_cast<LPARAM>(&rect));
}
return 0;
}
// Fall through to basegame
return Utils::Hook::Call<LRESULT CALLBACK(HWND, UINT, WPARAM, unsigned int)>(0x64DC50)(hWnd, Msg, wParam, lParam);
}
ATOM CALLBACK Console::RegisterClassHook(WNDCLASSA* lpWndClass)
{
DeleteObject(lpWndClass->hbrBackground);
HBRUSH brush = CreateSolidBrush(BackgroundColor);
lpWndClass->hbrBackground = brush;
return RegisterClassA(lpWndClass);
}
void Console::ApplyConsoleStyle()
{
Utils::Hook::Set<BYTE>(0x428A8E, 0); // Adjust logo Y pos
Utils::Hook::Set<BYTE>(0x428A90, 0); // Adjust logo X pos
Utils::Hook::Set<BYTE>(0x428AF2, 67); // Adjust output Y pos
Utils::Hook::Set<DWORD>(0x428AC5, 397); // Adjust input Y pos
Utils::Hook::Set<DWORD>(0x428951, 609); // Reduce window width
Utils::Hook::Set<DWORD>(0x42895D, 423); // Reduce window height
Utils::Hook::Set<DWORD>(0x428AC0, 597); // Reduce input width
Utils::Hook::Set<DWORD>(0x428AED, 596); // Reduce output width
DWORD fontsInstalled;
CustomConsoleFont = AddFontMemResourceEx(const_cast<void*>(reinterpret_cast<const void*>(Font::Terminus::DATA)), Font::Terminus::LENGTH, 0, &fontsInstalled);
if (fontsInstalled > 0)
{
Utils::Hook::Nop(0x428A44, 6);
Utils::Hook(0x428A44, ReplaceFont, HOOK_CALL).install()->quick();
}
Utils::Hook::Nop(0x42892D, 6);
Utils::Hook(0x42892D, RegisterClassHook, HOOK_CALL).install()->quick();
Utils::Hook::Set(0x4288E6 + 4, &ConWndProc);
auto style = WS_CAPTION | WS_SIZEBOX | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
Utils::Hook::Set(0x42893F + 1, style);
Utils::Hook::Set(0x4289E2 + 1, style);
#ifdef REMOVE_HEADERBAR
// Remove that hideous header window -rox
Utils::Hook::Set(0x428A7C, static_cast<char>(0xEB));
Utils::Hook::Set(0X428AF1 + 1, static_cast<char>(10));
#endif
// Never reset text
Utils::Hook::Nop(0x4F57DF, 0x4F57F6 - 0x4F57DF);
Utils::Hook(0x4F57DF, Console::Sys_PrintStub, HOOK_JUMP).install()->quick();
}
void Console::ConsoleRunner()
{
Console::SkipShutdown = false;
@ -588,14 +901,7 @@ namespace Components
Utils::Hook(0x482AC3, Console::RegisterConColor, HOOK_CALL).install()->quick();
// Modify console style
Utils::Hook::Set<BYTE>(0x428A8E, 0); // Adjust logo Y pos
Utils::Hook::Set<BYTE>(0x428A90, 0); // Adjust logo X pos
Utils::Hook::Set<BYTE>(0x428AF2, 67); // Adjust output Y pos
Utils::Hook::Set<DWORD>(0x428AC5, 397); // Adjust input Y pos
Utils::Hook::Set<DWORD>(0x428951, 609); // Reduce window width
Utils::Hook::Set<DWORD>(0x42895D, 423); // Reduce window height
Utils::Hook::Set<DWORD>(0x428AC0, 597); // Reduce input width
Utils::Hook::Set<DWORD>(0x428AED, 596); // Reduce output width
ApplyConsoleStyle();
// Don't resize the console
Utils::Hook(0x64DC6B, 0x64DCC2, HOOK_JUMP).install()->quick();

View File

@ -1,5 +1,7 @@
#pragma once
#include "Terminus_4.49.1.ttf.hpp"
#define OUTPUT_HEIGHT 250
#define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2))
@ -20,6 +22,10 @@ namespace Components
static void ShowAsyncConsole();
private:
static constexpr int OUTPUT_BOX = 0x64;
static constexpr int INPUT_BOX = 0x65;
// Text-based console stuff
static WINDOW* OutputWindow;
static WINDOW* InputWindow;
@ -32,6 +38,13 @@ namespace Components
static int OutBuffer;
static int LastRefresh;
static COLORREF TextColor;
static COLORREF BackgroundColor;
static HBRUSH ForegroundBrush;
static HBRUSH BackgroundBrush;
static HANDLE CustomConsoleFont;
static char LineBuffer[1024];
static char LineBuffer2[1024];
static int LineBufferIndex;
@ -69,5 +82,29 @@ namespace Components
static void AddConsoleCommand();
static Game::dvar_t* RegisterConColor(const char* dvarName, float r, float g, float b, float a, float min, float max, unsigned __int16 flags, const char* description);
static LRESULT CALLBACK ConWndProc(HWND hWnd, UINT Msg, WPARAM wParam, unsigned int lParam);
static ATOM CALLBACK RegisterClassHook(WNDCLASSA* lpWndClass);
static BOOL CALLBACK ResizeChildWindow(HWND hwndChild, LPARAM lParam);
static HFONT CALLBACK ReplaceFont(
int cHeight,
int cWidth,
int cEscapement,
int cOrientation,
int cWeight,
DWORD bItalic,
DWORD bUnderline,
DWORD bStrikeOut,
DWORD iCharSet,
DWORD iOutPrecision,
DWORD iClipPrecision,
DWORD iQuality,
DWORD iPitchAndFamily,
LPCSTR pszFaceName);
static void ApplyConsoleStyle();
static void GetWindowPos(HWND hWnd, int* x, int* y);
static void Sys_PrintStub();
static void MakeRoomForText(int addedCharacters);
static float GetDpiScale(const HWND hWnd);
};
}