add different console types
This commit is contained in:
parent
9975e1ab64
commit
9eba4a442f
@ -6,7 +6,7 @@
|
|||||||
#include "steam/steam.hpp"
|
#include "steam/steam.hpp"
|
||||||
|
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
#include "game/dvars.hpp"
|
#include "game/dvars.hpp"
|
||||||
|
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
#include "game_console.hpp"
|
#include "game_console.hpp"
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
#include "dvars.hpp"
|
#include "dvars.hpp"
|
||||||
|
@ -1,695 +0,0 @@
|
|||||||
#include <std_include.hpp>
|
|
||||||
#include "console.hpp"
|
|
||||||
#include "loader/component_loader.hpp"
|
|
||||||
|
|
||||||
#include "game/game.hpp"
|
|
||||||
|
|
||||||
#include <utils/flags.hpp>
|
|
||||||
#include <utils/concurrency.hpp>
|
|
||||||
#include <utils/thread.hpp>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
bool is_dark_mode_windows()
|
|
||||||
{
|
|
||||||
HKEY reg_key;
|
|
||||||
if (RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_QUERY_VALUE,
|
|
||||||
®_key) ==
|
|
||||||
ERROR_SUCCESS)
|
|
||||||
{
|
|
||||||
DWORD light_theme_value = 0x1;
|
|
||||||
DWORD length = sizeof(light_theme_value);
|
|
||||||
RegQueryValueExA(reg_key, "AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&light_theme_value), &length);
|
|
||||||
RegCloseKey(reg_key);
|
|
||||||
return light_theme_value == 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool darkmode = is_dark_mode_windows();
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace game_console
|
|
||||||
{
|
|
||||||
void print(int type, const std::string& data);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace console
|
|
||||||
{
|
|
||||||
bool is_enabled()
|
|
||||||
{
|
|
||||||
static const auto noconsole = utils::flags::has_flag("noconsole");
|
|
||||||
return game::environment::is_dedi() || !noconsole;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace native
|
|
||||||
{
|
|
||||||
bool is_enabled()
|
|
||||||
{
|
|
||||||
static const auto nativeconsole = utils::flags::has_flag("nativeconsole");
|
|
||||||
return nativeconsole;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::atomic_bool ingame = false;
|
|
||||||
static std::atomic_bool exit = false;
|
|
||||||
|
|
||||||
DWORD WINAPI console(LPVOID)
|
|
||||||
{
|
|
||||||
ShowWindow(GetConsoleWindow(), SW_SHOW);
|
|
||||||
SetConsoleTitleA("IW7-Mod Console");
|
|
||||||
|
|
||||||
std::string cmd;
|
|
||||||
exit = false;
|
|
||||||
|
|
||||||
while (!exit)
|
|
||||||
{
|
|
||||||
std::getline(std::cin, cmd);
|
|
||||||
if (ingame)
|
|
||||||
{
|
|
||||||
game::Cbuf_AddText(0, cmd.data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace syscon
|
|
||||||
{
|
|
||||||
#define CONSOLE_BACKGROUND_COLOR darkmode ? RGB(50, 50, 50) : RGB(255, 255, 255)
|
|
||||||
#define CONSOLE_TEXT_COLOR darkmode ? RGB(232, 230, 227) : RGB(0, 0, 0)
|
|
||||||
|
|
||||||
// todo:
|
|
||||||
// - history
|
|
||||||
// - resize
|
|
||||||
|
|
||||||
struct WinConData
|
|
||||||
{
|
|
||||||
HWND hWnd;
|
|
||||||
HWND hwndBuffer;
|
|
||||||
HWND codLogo;
|
|
||||||
HFONT hfBufferFont;
|
|
||||||
HWND hwndInputLine;
|
|
||||||
char consoleText[512];
|
|
||||||
int windowWidth;
|
|
||||||
int windowHeight;
|
|
||||||
WNDPROC SysInputLineWndProc;
|
|
||||||
char cleanBuffer[65536];
|
|
||||||
_RTL_CRITICAL_SECTION critSect;
|
|
||||||
} s_wcd;
|
|
||||||
|
|
||||||
HBRUSH bg_brush;
|
|
||||||
|
|
||||||
HICON icon;
|
|
||||||
HANDLE logo;
|
|
||||||
|
|
||||||
LRESULT ConWndProc(const HWND hwnd, const UINT umsg, const WPARAM wparam, const LPARAM lparam)
|
|
||||||
{
|
|
||||||
switch (umsg)
|
|
||||||
{
|
|
||||||
case WM_ACTIVATE:
|
|
||||||
if (LOWORD(wparam) != WA_INACTIVE)
|
|
||||||
{
|
|
||||||
SetFocus(s_wcd.hwndInputLine);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WM_CLOSE:
|
|
||||||
game::Cbuf_AddCall(0, game::Com_Quit_f);
|
|
||||||
DestroyWindow(hwnd);
|
|
||||||
return 0;
|
|
||||||
case WM_CTLCOLOREDIT:
|
|
||||||
case WM_CTLCOLORSTATIC:
|
|
||||||
SetBkColor(reinterpret_cast<HDC>(wparam), CONSOLE_BACKGROUND_COLOR);
|
|
||||||
SetTextColor(reinterpret_cast<HDC>(wparam), CONSOLE_TEXT_COLOR);
|
|
||||||
return reinterpret_cast<LONG_PTR>(bg_brush);
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefWindowProcA(hwnd, umsg, wparam, lparam);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int Conbuf_CleanText(const char* source, char* target, size_t size)
|
|
||||||
{
|
|
||||||
char* b = target;
|
|
||||||
int i = 0;
|
|
||||||
while (source && source[i] && (reinterpret_cast<size_t>(b) - reinterpret_cast<size_t>(target)) < (size - 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[0] == '^' && source[1] && source[1] != '^' && source[1] >= 48 && source[1] <= 64) // Q_IsColorString
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*b = source[i];
|
|
||||||
b++;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
*b = '\0';
|
|
||||||
return static_cast<unsigned int>(b - target);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conbuf_AppendText(const char* pmsg)
|
|
||||||
{
|
|
||||||
const char* msg;
|
|
||||||
unsigned int buf_len;
|
|
||||||
static unsigned int s_total_chars = 0;
|
|
||||||
|
|
||||||
if (s_wcd.hwndBuffer)
|
|
||||||
{
|
|
||||||
// if the message is REALLY long, use just the last portion of it
|
|
||||||
if (strlen(pmsg) > ((sizeof(s_wcd.cleanBuffer) / 2) - 1))
|
|
||||||
{
|
|
||||||
msg = pmsg + strlen(pmsg) - ((sizeof(s_wcd.cleanBuffer) / 2) + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
msg = pmsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy into an intermediate buffer
|
|
||||||
buf_len = Conbuf_CleanText(msg, s_wcd.cleanBuffer, sizeof(s_wcd.cleanBuffer));
|
|
||||||
|
|
||||||
s_total_chars += buf_len;
|
|
||||||
|
|
||||||
if (s_total_chars <= sizeof(s_wcd.cleanBuffer))
|
|
||||||
{
|
|
||||||
SendMessageA(s_wcd.hwndBuffer, EM_SETSEL, 0xFFFF, 0xFFFF);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SendMessageA(s_wcd.hwndBuffer, EM_SETSEL, 0, -1);
|
|
||||||
s_total_chars = buf_len;
|
|
||||||
}
|
|
||||||
SendMessageA(s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xFFFF);
|
|
||||||
SendMessageA(s_wcd.hwndBuffer, EM_SCROLLCARET, 0, 0);
|
|
||||||
SendMessageA(s_wcd.hwndBuffer, EM_REPLACESEL, 0, reinterpret_cast<LPARAM>(s_wcd.cleanBuffer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sys_Print(const char* msg)
|
|
||||||
{
|
|
||||||
if (console::is_enabled())
|
|
||||||
{
|
|
||||||
Conbuf_AppendText(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LRESULT InputLineWndProc(const HWND hwnd, const UINT umsg, const WPARAM wparam, const LPARAM lparam)
|
|
||||||
{
|
|
||||||
char dest[sizeof(s_wcd.consoleText) + 8];
|
|
||||||
|
|
||||||
switch (umsg)
|
|
||||||
{
|
|
||||||
case WM_KILLFOCUS:
|
|
||||||
if (reinterpret_cast<HWND>(wparam) == s_wcd.hWnd)
|
|
||||||
{
|
|
||||||
SetFocus(hwnd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WM_CHAR:
|
|
||||||
const auto key = wparam;
|
|
||||||
|
|
||||||
// enter the line
|
|
||||||
if (key == VK_RETURN)
|
|
||||||
{
|
|
||||||
memset(dest, 0, sizeof(dest));
|
|
||||||
memset(s_wcd.consoleText, 0, sizeof(s_wcd.consoleText));
|
|
||||||
|
|
||||||
const auto length = GetWindowTextA(s_wcd.hwndInputLine, s_wcd.consoleText, sizeof(s_wcd.consoleText));
|
|
||||||
if (length)
|
|
||||||
{
|
|
||||||
sprintf_s(dest, sizeof(dest), "]%s\n", s_wcd.consoleText);
|
|
||||||
SetWindowTextA(s_wcd.hwndInputLine, "");
|
|
||||||
|
|
||||||
Sys_Print(dest);
|
|
||||||
game::Cbuf_AddText(0, s_wcd.consoleText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CallWindowProcA(s_wcd.SysInputLineWndProc, hwnd, umsg, wparam, lparam);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sys_CreateConsole(const HINSTANCE hinstance)
|
|
||||||
{
|
|
||||||
RECT rect;
|
|
||||||
WNDCLASSA wndclass;
|
|
||||||
HDC hdc;
|
|
||||||
int nheight;
|
|
||||||
int swidth, sheight;
|
|
||||||
DWORD DEDSTYLE = WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX;
|
|
||||||
|
|
||||||
const char* class_name = "IW7-Mod WinConsole";
|
|
||||||
const char* window_name = "IW7-Mod Console";
|
|
||||||
|
|
||||||
memset(&rect, 0, sizeof(rect));
|
|
||||||
memset(&wndclass, 0, sizeof(rect));
|
|
||||||
memset(&hdc, 0, sizeof(hdc));
|
|
||||||
|
|
||||||
memset(&s_wcd, 0, sizeof(s_wcd));
|
|
||||||
|
|
||||||
wndclass.style = 0;
|
|
||||||
wndclass.cbClsExtra = 0;
|
|
||||||
wndclass.cbWndExtra = 0;
|
|
||||||
wndclass.lpfnWndProc = ConWndProc;
|
|
||||||
wndclass.hInstance = hinstance;
|
|
||||||
wndclass.hIcon = icon;
|
|
||||||
wndclass.hbrBackground = bg_brush;
|
|
||||||
wndclass.hCursor = LoadCursorA(0, IDC_ARROW);
|
|
||||||
wndclass.lpszMenuName = nullptr;
|
|
||||||
wndclass.lpszClassName = class_name;
|
|
||||||
|
|
||||||
if (!RegisterClassA(&wndclass))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rect.top = 0;
|
|
||||||
rect.left = 0;
|
|
||||||
rect.right = 620;
|
|
||||||
rect.bottom = 450;
|
|
||||||
AdjustWindowRect(&rect, DEDSTYLE, 0);
|
|
||||||
|
|
||||||
hdc = GetDC(GetDesktopWindow());
|
|
||||||
swidth = GetDeviceCaps(hdc, HORZRES);
|
|
||||||
sheight = GetDeviceCaps(hdc, VERTRES);
|
|
||||||
ReleaseDC(GetDesktopWindow(), hdc);
|
|
||||||
|
|
||||||
s_wcd.windowHeight = rect.bottom - rect.top + 1;
|
|
||||||
s_wcd.windowWidth = rect.right - rect.left + 1;
|
|
||||||
|
|
||||||
// create main window
|
|
||||||
s_wcd.hWnd = CreateWindowExA(
|
|
||||||
0,
|
|
||||||
class_name,
|
|
||||||
window_name,
|
|
||||||
DEDSTYLE,
|
|
||||||
(swidth - 600) / 2,
|
|
||||||
(sheight - 450) / 2,
|
|
||||||
rect.right - rect.left + 1,
|
|
||||||
rect.bottom - rect.top + 1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
hinstance,
|
|
||||||
nullptr);
|
|
||||||
|
|
||||||
if (!s_wcd.hWnd)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create fonts
|
|
||||||
hdc = GetDC(s_wcd.hWnd);
|
|
||||||
nheight = -MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72);
|
|
||||||
s_wcd.hfBufferFont = CreateFontA(
|
|
||||||
nheight,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
FW_LIGHT,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
DEFAULT_CHARSET,
|
|
||||||
OUT_DEFAULT_PRECIS,
|
|
||||||
CLIP_DEFAULT_PRECIS,
|
|
||||||
DEFAULT_QUALITY,
|
|
||||||
FF_MODERN | FIXED_PITCH,
|
|
||||||
"Courier New");
|
|
||||||
ReleaseDC(s_wcd.hWnd, hdc);
|
|
||||||
|
|
||||||
// create logo
|
|
||||||
if (logo)
|
|
||||||
{
|
|
||||||
s_wcd.codLogo = CreateWindowExA(
|
|
||||||
0,
|
|
||||||
"Static",
|
|
||||||
0,
|
|
||||||
WS_CHILDWINDOW | WS_VISIBLE | 0xE,
|
|
||||||
5,
|
|
||||||
5,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
s_wcd.hWnd,
|
|
||||||
reinterpret_cast<HMENU>(1),
|
|
||||||
hinstance,
|
|
||||||
nullptr);
|
|
||||||
SendMessageA(s_wcd.codLogo, 0x172u, 0, reinterpret_cast<LPARAM>(logo));
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the input line
|
|
||||||
s_wcd.hwndInputLine = CreateWindowExA(
|
|
||||||
0,
|
|
||||||
"edit",
|
|
||||||
0,
|
|
||||||
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,
|
|
||||||
6,
|
|
||||||
426,
|
|
||||||
608,
|
|
||||||
20,
|
|
||||||
s_wcd.hWnd,
|
|
||||||
reinterpret_cast<HMENU>(0x65),
|
|
||||||
hinstance,
|
|
||||||
0);
|
|
||||||
|
|
||||||
// create the scrollbuffer
|
|
||||||
s_wcd.hwndBuffer = CreateWindowExA(
|
|
||||||
0,
|
|
||||||
"edit",
|
|
||||||
0,
|
|
||||||
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
|
|
||||||
6,
|
|
||||||
70,
|
|
||||||
608,
|
|
||||||
348,
|
|
||||||
s_wcd.hWnd,
|
|
||||||
reinterpret_cast<HMENU>(0x64),
|
|
||||||
hinstance,
|
|
||||||
0);
|
|
||||||
|
|
||||||
SendMessageA(s_wcd.hwndBuffer, WM_SETFONT, reinterpret_cast<WPARAM>(s_wcd.hfBufferFont), 0);
|
|
||||||
SendMessageA(s_wcd.hwndBuffer, EM_LIMITTEXT, sizeof(s_wcd.cleanBuffer), 0);
|
|
||||||
s_wcd.SysInputLineWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtrA(s_wcd.hwndInputLine, -4,
|
|
||||||
reinterpret_cast<LONG_PTR>(InputLineWndProc)));
|
|
||||||
SendMessageA(s_wcd.hwndInputLine, WM_SETFONT, reinterpret_cast<WPARAM>(s_wcd.hfBufferFont), 0);
|
|
||||||
SetFocus(s_wcd.hwndInputLine);
|
|
||||||
SetWindowTextA(s_wcd.hwndBuffer, s_wcd.cleanBuffer);
|
|
||||||
InitializeCriticalSection(&s_wcd.critSect);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sys_ShowConsole()
|
|
||||||
{
|
|
||||||
if (!console::is_enabled())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!s_wcd.hWnd)
|
|
||||||
{
|
|
||||||
Sys_CreateConsole(GetModuleHandleA(reinterpret_cast<LPCSTR>(s_wcd.hWnd)));
|
|
||||||
}
|
|
||||||
if (s_wcd.hWnd)
|
|
||||||
{
|
|
||||||
ShowWindow(s_wcd.hWnd, TRUE);
|
|
||||||
SendMessageA(s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xFFFF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sys_DestroyConsole()
|
|
||||||
{
|
|
||||||
if (s_wcd.hWnd)
|
|
||||||
{
|
|
||||||
ShowWindow(s_wcd.hWnd, SW_HIDE);
|
|
||||||
CloseWindow(s_wcd.hWnd);
|
|
||||||
DestroyWindow(s_wcd.hWnd);
|
|
||||||
s_wcd.hWnd = nullptr;
|
|
||||||
DeleteCriticalSection(&s_wcd.critSect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string format(va_list* ap, const char* message)
|
|
||||||
{
|
|
||||||
static thread_local char buffer[0x1000];
|
|
||||||
|
|
||||||
const auto count = _vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), message, *ap);
|
|
||||||
|
|
||||||
if (count < 0) return {};
|
|
||||||
return { buffer, static_cast<size_t>(count) };
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispatch_message(const int type, const std::string& message)
|
|
||||||
{
|
|
||||||
if (console::is_enabled())
|
|
||||||
{
|
|
||||||
if (native::is_enabled())
|
|
||||||
{
|
|
||||||
printf(message.data());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
syscon::Sys_Print(message.data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
game_console::print(type, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
if (!console::is_enabled() || native::is_enabled()) return;
|
|
||||||
|
|
||||||
syscon::bg_brush = CreateSolidBrush(CONSOLE_BACKGROUND_COLOR);
|
|
||||||
|
|
||||||
const utils::nt::library self;
|
|
||||||
syscon::icon = LoadIconA(self.get_handle(), MAKEINTRESOURCEA(ID_ICON));
|
|
||||||
syscon::logo = LoadImageA(self.get_handle(), MAKEINTRESOURCEA(IMAGE_LOGO), 0, 0, 0, LR_COPYFROMRESOURCE);
|
|
||||||
|
|
||||||
(void)_pipe(this->handles_, 1024, _O_TEXT);
|
|
||||||
(void)_dup2(this->handles_[1], 1);
|
|
||||||
(void)_dup2(this->handles_[1], 2);
|
|
||||||
|
|
||||||
//setvbuf(stdout, nullptr, _IONBF, 0);
|
|
||||||
//setvbuf(stderr, nullptr, _IONBF, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
~component()
|
|
||||||
{
|
|
||||||
if (!console::is_enabled() || native::is_enabled()) return;
|
|
||||||
|
|
||||||
if (syscon::bg_brush) DeleteObject(syscon::bg_brush);
|
|
||||||
|
|
||||||
if (syscon::icon) DestroyIcon(syscon::icon);
|
|
||||||
if (syscon::logo) DeleteObject(syscon::logo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void post_start() override
|
|
||||||
{
|
|
||||||
if (console::is_enabled())
|
|
||||||
{
|
|
||||||
if (native::is_enabled())
|
|
||||||
{
|
|
||||||
const auto handle = CreateThread(0, 0, native::console, 0, 0, 0);
|
|
||||||
utils::thread::set_name(handle, "Console");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->terminate_runner_ = false;
|
|
||||||
this->console_runner_ = utils::thread::create_named_thread("Console IO", [this]()
|
|
||||||
{
|
|
||||||
this->runner();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void post_unpack() override
|
|
||||||
{
|
|
||||||
if (console::is_enabled())
|
|
||||||
{
|
|
||||||
if (native::is_enabled())
|
|
||||||
{
|
|
||||||
native::ingame = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->initialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pre_destroy() override
|
|
||||||
{
|
|
||||||
if (console::is_enabled())
|
|
||||||
{
|
|
||||||
if (native::is_enabled())
|
|
||||||
{
|
|
||||||
native::ingame = false;
|
|
||||||
native::exit = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only needed for sys_console
|
|
||||||
private:
|
|
||||||
volatile bool console_initialized_ = false;
|
|
||||||
volatile bool terminate_runner_ = false;
|
|
||||||
|
|
||||||
std::thread console_runner_;
|
|
||||||
std::thread console_thread_;
|
|
||||||
|
|
||||||
int handles_[2]{};
|
|
||||||
|
|
||||||
using message_queue = std::queue<std::string>;
|
|
||||||
utils::concurrency::container<message_queue> messages;
|
|
||||||
|
|
||||||
void initialize()
|
|
||||||
{
|
|
||||||
this->console_thread_ = utils::thread::create_named_thread("Console", [this]()
|
|
||||||
{
|
|
||||||
syscon::Sys_ShowConsole();
|
|
||||||
|
|
||||||
if (!game::environment::is_dedi())
|
|
||||||
{
|
|
||||||
// Hide that shit
|
|
||||||
ShowWindow(syscon::s_wcd.hWnd, SW_MINIMIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
messages.access([&](message_queue&)
|
|
||||||
{
|
|
||||||
this->console_initialized_ = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
MSG msg;
|
|
||||||
while (!this->terminate_runner_)
|
|
||||||
{
|
|
||||||
if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
|
||||||
{
|
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessage(&msg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->log_messages();
|
|
||||||
std::this_thread::sleep_for(1ms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy()
|
|
||||||
{
|
|
||||||
syscon::Sys_DestroyConsole();
|
|
||||||
|
|
||||||
this->terminate_runner_ = true;
|
|
||||||
|
|
||||||
printf("\r\n");
|
|
||||||
_flushall();
|
|
||||||
|
|
||||||
if (this->console_runner_.joinable())
|
|
||||||
{
|
|
||||||
this->console_runner_.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->console_thread_.joinable())
|
|
||||||
{
|
|
||||||
this->console_thread_.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
_close(this->handles_[0]);
|
|
||||||
_close(this->handles_[1]);
|
|
||||||
|
|
||||||
messages.access([&](message_queue& msgs)
|
|
||||||
{
|
|
||||||
msgs = {};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void log_messages()
|
|
||||||
{
|
|
||||||
/*while*/
|
|
||||||
if (this->console_initialized_ && !messages.get_raw().empty())
|
|
||||||
{
|
|
||||||
std::queue<std::string> message_queue_copy;
|
|
||||||
|
|
||||||
{
|
|
||||||
messages.access([&](message_queue& msgs)
|
|
||||||
{
|
|
||||||
message_queue_copy = std::move(msgs);
|
|
||||||
msgs = {};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!message_queue_copy.empty())
|
|
||||||
{
|
|
||||||
log_message(message_queue_copy.front());
|
|
||||||
message_queue_copy.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fflush(stdout);
|
|
||||||
fflush(stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void log_message(const std::string& message)
|
|
||||||
{
|
|
||||||
OutputDebugStringA(message.data());
|
|
||||||
syscon::Conbuf_AppendText(message.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
void runner()
|
|
||||||
{
|
|
||||||
char buffer[1024];
|
|
||||||
|
|
||||||
while (!this->terminate_runner_ && this->handles_[0])
|
|
||||||
{
|
|
||||||
const auto len = _read(this->handles_[0], buffer, sizeof(buffer));
|
|
||||||
if (len > 0)
|
|
||||||
{
|
|
||||||
dispatch_message(con_type_info, std::string(buffer, len));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(1ms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::this_thread::yield();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void set_title(std::string title)
|
|
||||||
{
|
|
||||||
if (console::is_enabled())
|
|
||||||
{
|
|
||||||
if (native::is_enabled())
|
|
||||||
{
|
|
||||||
SetConsoleTitleA(title.data());
|
|
||||||
}
|
|
||||||
else if (syscon::s_wcd.hWnd)
|
|
||||||
{
|
|
||||||
SetWindowTextA(syscon::s_wcd.hWnd, title.data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_COMPONENT(console::component)
|
|
@ -1,48 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace console
|
|
||||||
{
|
|
||||||
bool is_enabled();
|
|
||||||
namespace native
|
|
||||||
{
|
|
||||||
bool is_enabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_title(std::string title);
|
|
||||||
|
|
||||||
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)...);
|
|
||||||
}
|
|
||||||
}
|
|
177
src/client/component/console/console.cpp
Normal file
177
src/client/component/console/console.cpp
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "console.hpp"
|
||||||
|
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
#include <utils/flags.hpp>
|
||||||
|
|
||||||
|
#include "wincon.hpp"
|
||||||
|
#include "terminal.hpp"
|
||||||
|
#include "syscon.hpp"
|
||||||
|
|
||||||
|
namespace game_console
|
||||||
|
{
|
||||||
|
void print(int type, const std::string& data);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace console
|
||||||
|
{
|
||||||
|
enum console_type
|
||||||
|
{
|
||||||
|
con_type_none,
|
||||||
|
con_type_wincon,
|
||||||
|
con_type_terminal,
|
||||||
|
con_type_syscon,
|
||||||
|
con_type_game = con_type_syscon,
|
||||||
|
con_type_default = con_type_terminal,
|
||||||
|
} con_type;
|
||||||
|
|
||||||
|
void init_console_type()
|
||||||
|
{
|
||||||
|
con_type = con_type_default;
|
||||||
|
|
||||||
|
const auto noconsole_flag = utils::flags::has_flag("noconsole");
|
||||||
|
if (!game::environment::is_dedi() && noconsole_flag)
|
||||||
|
{
|
||||||
|
con_type = con_type_none;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto wincon_flag = utils::flags::has_flag("wincon");
|
||||||
|
if (wincon_flag)
|
||||||
|
{
|
||||||
|
con_type = con_type_wincon;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto terminal_flag = utils::flags::has_flag("terminal");
|
||||||
|
if (terminal_flag)
|
||||||
|
{
|
||||||
|
con_type = con_type_terminal;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto syscon_flag = utils::flags::has_flag("syscon");
|
||||||
|
if (syscon_flag)
|
||||||
|
{
|
||||||
|
con_type = con_type_syscon;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto get_console_type()
|
||||||
|
{
|
||||||
|
return con_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_enabled()
|
||||||
|
{
|
||||||
|
return get_console_type() != console_type::con_type_none;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace wincon
|
||||||
|
{
|
||||||
|
bool is_enabled()
|
||||||
|
{
|
||||||
|
return get_console_type() == console_type::con_type_wincon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace terminal
|
||||||
|
{
|
||||||
|
bool is_enabled()
|
||||||
|
{
|
||||||
|
return get_console_type() == console_type::con_type_terminal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace syscon
|
||||||
|
{
|
||||||
|
bool is_enabled()
|
||||||
|
{
|
||||||
|
return get_console_type() == console_type::con_type_syscon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string format(va_list* ap, const char* message)
|
||||||
|
{
|
||||||
|
static thread_local char buffer[0x1000];
|
||||||
|
|
||||||
|
const auto count = _vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), message, *ap);
|
||||||
|
|
||||||
|
if (count < 0) return {};
|
||||||
|
return { buffer, static_cast<size_t>(count) };
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatch_message(const int type, const std::string& message)
|
||||||
|
{
|
||||||
|
if (console::is_enabled())
|
||||||
|
{
|
||||||
|
if (wincon::is_enabled())
|
||||||
|
{
|
||||||
|
printf(message.data());
|
||||||
|
}
|
||||||
|
else if (terminal::is_enabled())
|
||||||
|
{
|
||||||
|
::terminal::dispatch_message(type, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (syscon::is_enabled())
|
||||||
|
{
|
||||||
|
::syscon::Sys_Print(message.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game_console::print(type, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_title(const std::string& title)
|
||||||
|
{
|
||||||
|
if (console::is_enabled())
|
||||||
|
{
|
||||||
|
if (wincon::is_enabled() || terminal::is_enabled())
|
||||||
|
{
|
||||||
|
SetConsoleTitleA(title.data());
|
||||||
|
}
|
||||||
|
else if (syscon::is_enabled())
|
||||||
|
{
|
||||||
|
::syscon::set_title(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
static auto initialized = false;
|
||||||
|
if (initialized) return;
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
init_console_type();
|
||||||
|
if (get_console_type() == con_type_none)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (get_console_type() == con_type_wincon)
|
||||||
|
{
|
||||||
|
::wincon::init();
|
||||||
|
}
|
||||||
|
else if (get_console_type() == con_type_terminal)
|
||||||
|
{
|
||||||
|
::terminal::init();
|
||||||
|
}
|
||||||
|
else if (get_console_type() == con_type_syscon)
|
||||||
|
{
|
||||||
|
::syscon::init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/client/component/console/console.hpp
Normal file
46
src/client/component/console/console.hpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace console
|
||||||
|
{
|
||||||
|
void set_title(const std::string& title);
|
||||||
|
|
||||||
|
enum print_type
|
||||||
|
{
|
||||||
|
print_type_error = 1,
|
||||||
|
print_type_debug = 2,
|
||||||
|
print_type_warning = 3,
|
||||||
|
print_type_info = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
void dispatch_message(int type, const std::string& message);
|
||||||
|
|
||||||
|
void print(int type, const char* fmt, ...);
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void error(const char* fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
print(print_type_error, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void debug(const char* fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
print(print_type_debug, fmt, std::forward<Args>(args)...);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void warn(const char* fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
print(print_type_warning, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void info(const char* fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
print(print_type_info, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init();
|
||||||
|
}
|
568
src/client/component/console/syscon.cpp
Normal file
568
src/client/component/console/syscon.cpp
Normal file
@ -0,0 +1,568 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "syscon.hpp"
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "console.hpp"
|
||||||
|
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
#include <utils/thread.hpp>
|
||||||
|
#include <utils/concurrency.hpp>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool is_dark_mode_windows()
|
||||||
|
{
|
||||||
|
HKEY reg_key;
|
||||||
|
if (RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_QUERY_VALUE,
|
||||||
|
®_key) ==
|
||||||
|
ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
DWORD light_theme_value = 0x1;
|
||||||
|
DWORD length = sizeof(light_theme_value);
|
||||||
|
RegQueryValueExA(reg_key, "AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&light_theme_value), &length);
|
||||||
|
RegCloseKey(reg_key);
|
||||||
|
return light_theme_value == 0x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool darkmode = is_dark_mode_windows();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace syscon
|
||||||
|
{
|
||||||
|
#define CONSOLE_BACKGROUND_COLOR darkmode ? RGB(50, 50, 50) : RGB(255, 255, 255)
|
||||||
|
#define CONSOLE_TEXT_COLOR darkmode ? RGB(232, 230, 227) : RGB(0, 0, 0)
|
||||||
|
|
||||||
|
// todo:
|
||||||
|
// - history
|
||||||
|
// - resize
|
||||||
|
|
||||||
|
struct WinConData
|
||||||
|
{
|
||||||
|
HWND hWnd;
|
||||||
|
HWND hwndBuffer;
|
||||||
|
HWND codLogo;
|
||||||
|
HFONT hfBufferFont;
|
||||||
|
HWND hwndInputLine;
|
||||||
|
char consoleText[512];
|
||||||
|
int windowWidth;
|
||||||
|
int windowHeight;
|
||||||
|
WNDPROC SysInputLineWndProc;
|
||||||
|
char cleanBuffer[65536];
|
||||||
|
_RTL_CRITICAL_SECTION critSect;
|
||||||
|
} s_wcd;
|
||||||
|
|
||||||
|
HBRUSH bg_brush;
|
||||||
|
|
||||||
|
HICON icon;
|
||||||
|
HANDLE logo;
|
||||||
|
|
||||||
|
LRESULT ConWndProc(const HWND hwnd, const UINT umsg, const WPARAM wparam, const LPARAM lparam)
|
||||||
|
{
|
||||||
|
switch (umsg)
|
||||||
|
{
|
||||||
|
case WM_ACTIVATE:
|
||||||
|
if (LOWORD(wparam) != WA_INACTIVE)
|
||||||
|
{
|
||||||
|
SetFocus(s_wcd.hwndInputLine);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WM_CLOSE:
|
||||||
|
game::Cbuf_AddCall(0, game::Com_Quit_f);
|
||||||
|
DestroyWindow(hwnd);
|
||||||
|
return 0;
|
||||||
|
case WM_CTLCOLOREDIT:
|
||||||
|
case WM_CTLCOLORSTATIC:
|
||||||
|
SetBkColor(reinterpret_cast<HDC>(wparam), CONSOLE_BACKGROUND_COLOR);
|
||||||
|
SetTextColor(reinterpret_cast<HDC>(wparam), CONSOLE_TEXT_COLOR);
|
||||||
|
return reinterpret_cast<LONG_PTR>(bg_brush);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProcA(hwnd, umsg, wparam, lparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int Conbuf_CleanText(const char* source, char* target, size_t size)
|
||||||
|
{
|
||||||
|
char* b = target;
|
||||||
|
int i = 0;
|
||||||
|
while (source && source[i] && (reinterpret_cast<size_t>(b) - reinterpret_cast<size_t>(target)) < (size - 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[0] == '^' && source[1] && source[1] != '^' && source[1] >= 48 && source[1] <= 64) // Q_IsColorString
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*b = source[i];
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*b = '\0';
|
||||||
|
return static_cast<unsigned int>(b - target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conbuf_AppendText(const char* pmsg)
|
||||||
|
{
|
||||||
|
const char* msg;
|
||||||
|
unsigned int buf_len;
|
||||||
|
static unsigned int s_total_chars = 0;
|
||||||
|
|
||||||
|
if (s_wcd.hwndBuffer)
|
||||||
|
{
|
||||||
|
// if the message is REALLY long, use just the last portion of it
|
||||||
|
if (strlen(pmsg) > ((sizeof(s_wcd.cleanBuffer) / 2) - 1))
|
||||||
|
{
|
||||||
|
msg = pmsg + strlen(pmsg) - ((sizeof(s_wcd.cleanBuffer) / 2) + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msg = pmsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy into an intermediate buffer
|
||||||
|
buf_len = Conbuf_CleanText(msg, s_wcd.cleanBuffer, sizeof(s_wcd.cleanBuffer));
|
||||||
|
|
||||||
|
s_total_chars += buf_len;
|
||||||
|
|
||||||
|
if (s_total_chars <= sizeof(s_wcd.cleanBuffer))
|
||||||
|
{
|
||||||
|
SendMessageA(s_wcd.hwndBuffer, EM_SETSEL, 0xFFFF, 0xFFFF);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendMessageA(s_wcd.hwndBuffer, EM_SETSEL, 0, -1);
|
||||||
|
s_total_chars = buf_len;
|
||||||
|
}
|
||||||
|
SendMessageA(s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xFFFF);
|
||||||
|
SendMessageA(s_wcd.hwndBuffer, EM_SCROLLCARET, 0, 0);
|
||||||
|
SendMessageA(s_wcd.hwndBuffer, EM_REPLACESEL, 0, reinterpret_cast<LPARAM>(s_wcd.cleanBuffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sys_Print(const char* msg)
|
||||||
|
{
|
||||||
|
Conbuf_AppendText(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT InputLineWndProc(const HWND hwnd, const UINT umsg, const WPARAM wparam, const LPARAM lparam)
|
||||||
|
{
|
||||||
|
char dest[sizeof(s_wcd.consoleText) + 8];
|
||||||
|
|
||||||
|
switch (umsg)
|
||||||
|
{
|
||||||
|
case WM_KILLFOCUS:
|
||||||
|
if (reinterpret_cast<HWND>(wparam) == s_wcd.hWnd)
|
||||||
|
{
|
||||||
|
SetFocus(hwnd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WM_CHAR:
|
||||||
|
const auto key = wparam;
|
||||||
|
|
||||||
|
// enter the line
|
||||||
|
if (key == VK_RETURN)
|
||||||
|
{
|
||||||
|
memset(dest, 0, sizeof(dest));
|
||||||
|
memset(s_wcd.consoleText, 0, sizeof(s_wcd.consoleText));
|
||||||
|
|
||||||
|
const auto length = GetWindowTextA(s_wcd.hwndInputLine, s_wcd.consoleText, sizeof(s_wcd.consoleText));
|
||||||
|
if (length)
|
||||||
|
{
|
||||||
|
sprintf_s(dest, sizeof(dest), "]%s\n", s_wcd.consoleText);
|
||||||
|
SetWindowTextA(s_wcd.hwndInputLine, "");
|
||||||
|
|
||||||
|
Sys_Print(dest);
|
||||||
|
game::Cbuf_AddText(0, s_wcd.consoleText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CallWindowProcA(s_wcd.SysInputLineWndProc, hwnd, umsg, wparam, lparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sys_CreateConsole(const HINSTANCE hinstance)
|
||||||
|
{
|
||||||
|
RECT rect;
|
||||||
|
WNDCLASSA wndclass;
|
||||||
|
HDC hdc;
|
||||||
|
int nheight;
|
||||||
|
int swidth, sheight;
|
||||||
|
DWORD DEDSTYLE = WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX;
|
||||||
|
|
||||||
|
const char* class_name = "IW7-Mod WinConsole";
|
||||||
|
const char* window_name = "IW7-Mod Console";
|
||||||
|
|
||||||
|
memset(&rect, 0, sizeof(rect));
|
||||||
|
memset(&wndclass, 0, sizeof(rect));
|
||||||
|
memset(&hdc, 0, sizeof(hdc));
|
||||||
|
|
||||||
|
memset(&s_wcd, 0, sizeof(s_wcd));
|
||||||
|
|
||||||
|
wndclass.style = 0;
|
||||||
|
wndclass.cbClsExtra = 0;
|
||||||
|
wndclass.cbWndExtra = 0;
|
||||||
|
wndclass.lpfnWndProc = ConWndProc;
|
||||||
|
wndclass.hInstance = hinstance;
|
||||||
|
wndclass.hIcon = icon;
|
||||||
|
wndclass.hbrBackground = bg_brush;
|
||||||
|
wndclass.hCursor = LoadCursorA(0, IDC_ARROW);
|
||||||
|
wndclass.lpszMenuName = nullptr;
|
||||||
|
wndclass.lpszClassName = class_name;
|
||||||
|
|
||||||
|
if (!RegisterClassA(&wndclass))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect.top = 0;
|
||||||
|
rect.left = 0;
|
||||||
|
rect.right = 620;
|
||||||
|
rect.bottom = 450;
|
||||||
|
AdjustWindowRect(&rect, DEDSTYLE, 0);
|
||||||
|
|
||||||
|
hdc = GetDC(GetDesktopWindow());
|
||||||
|
swidth = GetDeviceCaps(hdc, HORZRES);
|
||||||
|
sheight = GetDeviceCaps(hdc, VERTRES);
|
||||||
|
ReleaseDC(GetDesktopWindow(), hdc);
|
||||||
|
|
||||||
|
s_wcd.windowHeight = rect.bottom - rect.top + 1;
|
||||||
|
s_wcd.windowWidth = rect.right - rect.left + 1;
|
||||||
|
|
||||||
|
// create main window
|
||||||
|
s_wcd.hWnd = CreateWindowExA(
|
||||||
|
0,
|
||||||
|
class_name,
|
||||||
|
window_name,
|
||||||
|
DEDSTYLE,
|
||||||
|
(swidth - 600) / 2,
|
||||||
|
(sheight - 450) / 2,
|
||||||
|
rect.right - rect.left + 1,
|
||||||
|
rect.bottom - rect.top + 1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
hinstance,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
if (!s_wcd.hWnd)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create fonts
|
||||||
|
hdc = GetDC(s_wcd.hWnd);
|
||||||
|
nheight = -MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72);
|
||||||
|
s_wcd.hfBufferFont = CreateFontA(
|
||||||
|
nheight,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
FW_LIGHT,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
DEFAULT_CHARSET,
|
||||||
|
OUT_DEFAULT_PRECIS,
|
||||||
|
CLIP_DEFAULT_PRECIS,
|
||||||
|
DEFAULT_QUALITY,
|
||||||
|
FF_MODERN | FIXED_PITCH,
|
||||||
|
"Courier New");
|
||||||
|
ReleaseDC(s_wcd.hWnd, hdc);
|
||||||
|
|
||||||
|
// create logo
|
||||||
|
if (logo)
|
||||||
|
{
|
||||||
|
s_wcd.codLogo = CreateWindowExA(
|
||||||
|
0,
|
||||||
|
"Static",
|
||||||
|
0,
|
||||||
|
WS_CHILDWINDOW | WS_VISIBLE | 0xE,
|
||||||
|
5,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
s_wcd.hWnd,
|
||||||
|
reinterpret_cast<HMENU>(1),
|
||||||
|
hinstance,
|
||||||
|
nullptr);
|
||||||
|
SendMessageA(s_wcd.codLogo, 0x172u, 0, reinterpret_cast<LPARAM>(logo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the input line
|
||||||
|
s_wcd.hwndInputLine = CreateWindowExA(
|
||||||
|
0,
|
||||||
|
"edit",
|
||||||
|
0,
|
||||||
|
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,
|
||||||
|
6,
|
||||||
|
426,
|
||||||
|
608,
|
||||||
|
20,
|
||||||
|
s_wcd.hWnd,
|
||||||
|
reinterpret_cast<HMENU>(0x65),
|
||||||
|
hinstance,
|
||||||
|
0);
|
||||||
|
|
||||||
|
// create the scrollbuffer
|
||||||
|
s_wcd.hwndBuffer = CreateWindowExA(
|
||||||
|
0,
|
||||||
|
"edit",
|
||||||
|
0,
|
||||||
|
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
|
||||||
|
6,
|
||||||
|
70,
|
||||||
|
608,
|
||||||
|
348,
|
||||||
|
s_wcd.hWnd,
|
||||||
|
reinterpret_cast<HMENU>(0x64),
|
||||||
|
hinstance,
|
||||||
|
0);
|
||||||
|
|
||||||
|
SendMessageA(s_wcd.hwndBuffer, WM_SETFONT, reinterpret_cast<WPARAM>(s_wcd.hfBufferFont), 0);
|
||||||
|
SendMessageA(s_wcd.hwndBuffer, EM_LIMITTEXT, sizeof(s_wcd.cleanBuffer), 0);
|
||||||
|
s_wcd.SysInputLineWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtrA(s_wcd.hwndInputLine, -4,
|
||||||
|
reinterpret_cast<LONG_PTR>(InputLineWndProc)));
|
||||||
|
SendMessageA(s_wcd.hwndInputLine, WM_SETFONT, reinterpret_cast<WPARAM>(s_wcd.hfBufferFont), 0);
|
||||||
|
SetFocus(s_wcd.hwndInputLine);
|
||||||
|
SetWindowTextA(s_wcd.hwndBuffer, s_wcd.cleanBuffer);
|
||||||
|
InitializeCriticalSection(&s_wcd.critSect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sys_ShowConsole()
|
||||||
|
{
|
||||||
|
if (!s_wcd.hWnd)
|
||||||
|
{
|
||||||
|
Sys_CreateConsole(GetModuleHandleA(reinterpret_cast<LPCSTR>(s_wcd.hWnd)));
|
||||||
|
}
|
||||||
|
if (s_wcd.hWnd)
|
||||||
|
{
|
||||||
|
ShowWindow(s_wcd.hWnd, TRUE);
|
||||||
|
SendMessageA(s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sys_DestroyConsole()
|
||||||
|
{
|
||||||
|
if (s_wcd.hWnd)
|
||||||
|
{
|
||||||
|
ShowWindow(s_wcd.hWnd, SW_HIDE);
|
||||||
|
CloseWindow(s_wcd.hWnd);
|
||||||
|
DestroyWindow(s_wcd.hWnd);
|
||||||
|
s_wcd.hWnd = nullptr;
|
||||||
|
DeleteCriticalSection(&s_wcd.critSect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
component()
|
||||||
|
{
|
||||||
|
syscon::bg_brush = CreateSolidBrush(CONSOLE_BACKGROUND_COLOR);
|
||||||
|
|
||||||
|
const utils::nt::library self;
|
||||||
|
syscon::icon = LoadIconA(self.get_handle(), MAKEINTRESOURCEA(ID_ICON));
|
||||||
|
syscon::logo = LoadImageA(self.get_handle(), MAKEINTRESOURCEA(IMAGE_LOGO), 0, 0, 0, LR_COPYFROMRESOURCE);
|
||||||
|
|
||||||
|
(void)_pipe(this->handles_, 1024, _O_TEXT);
|
||||||
|
(void)_dup2(this->handles_[1], 1);
|
||||||
|
(void)_dup2(this->handles_[1], 2);
|
||||||
|
|
||||||
|
//setvbuf(stdout, nullptr, _IONBF, 0);
|
||||||
|
//setvbuf(stderr, nullptr, _IONBF, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
~component()
|
||||||
|
{
|
||||||
|
if (syscon::bg_brush) DeleteObject(syscon::bg_brush);
|
||||||
|
|
||||||
|
if (syscon::icon) DestroyIcon(syscon::icon);
|
||||||
|
if (syscon::logo) DeleteObject(syscon::logo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_start() override
|
||||||
|
{
|
||||||
|
this->terminate_runner_ = false;
|
||||||
|
this->console_runner_ = utils::thread::create_named_thread("Console IO", [this]()
|
||||||
|
{
|
||||||
|
this->runner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
this->initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pre_destroy() override
|
||||||
|
{
|
||||||
|
this->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
volatile bool console_initialized_ = false;
|
||||||
|
volatile bool terminate_runner_ = false;
|
||||||
|
|
||||||
|
std::thread console_runner_;
|
||||||
|
std::thread console_thread_;
|
||||||
|
|
||||||
|
int handles_[2]{};
|
||||||
|
|
||||||
|
using message_queue = std::queue<std::string>;
|
||||||
|
utils::concurrency::container<message_queue> messages;
|
||||||
|
|
||||||
|
void initialize()
|
||||||
|
{
|
||||||
|
this->console_thread_ = utils::thread::create_named_thread("Console", [this]()
|
||||||
|
{
|
||||||
|
syscon::Sys_ShowConsole();
|
||||||
|
|
||||||
|
if (!game::environment::is_dedi())
|
||||||
|
{
|
||||||
|
// Hide that shit
|
||||||
|
ShowWindow(syscon::s_wcd.hWnd, SW_MINIMIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
messages.access([&](message_queue&)
|
||||||
|
{
|
||||||
|
this->console_initialized_ = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MSG msg;
|
||||||
|
while (!this->terminate_runner_)
|
||||||
|
{
|
||||||
|
if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
||||||
|
{
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->log_messages();
|
||||||
|
std::this_thread::sleep_for(1ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy()
|
||||||
|
{
|
||||||
|
syscon::Sys_DestroyConsole();
|
||||||
|
|
||||||
|
this->terminate_runner_ = true;
|
||||||
|
|
||||||
|
printf("\r\n");
|
||||||
|
_flushall();
|
||||||
|
|
||||||
|
if (this->console_runner_.joinable())
|
||||||
|
{
|
||||||
|
this->console_runner_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->console_thread_.joinable())
|
||||||
|
{
|
||||||
|
this->console_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
_close(this->handles_[0]);
|
||||||
|
_close(this->handles_[1]);
|
||||||
|
|
||||||
|
messages.access([&](message_queue& msgs)
|
||||||
|
{
|
||||||
|
msgs = {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_messages()
|
||||||
|
{
|
||||||
|
/*while*/
|
||||||
|
if (this->console_initialized_ && !messages.get_raw().empty())
|
||||||
|
{
|
||||||
|
std::queue<std::string> message_queue_copy;
|
||||||
|
|
||||||
|
{
|
||||||
|
messages.access([&](message_queue& msgs)
|
||||||
|
{
|
||||||
|
message_queue_copy = std::move(msgs);
|
||||||
|
msgs = {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!message_queue_copy.empty())
|
||||||
|
{
|
||||||
|
log_message(message_queue_copy.front());
|
||||||
|
message_queue_copy.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_message(const std::string& message)
|
||||||
|
{
|
||||||
|
//OutputDebugStringA(message.data());
|
||||||
|
syscon::Conbuf_AppendText(message.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void runner()
|
||||||
|
{
|
||||||
|
char buffer[1024];
|
||||||
|
|
||||||
|
while (!this->terminate_runner_ && this->handles_[0])
|
||||||
|
{
|
||||||
|
const auto len = _read(this->handles_[0], buffer, sizeof(buffer));
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
console::dispatch_message(console::print_type_info, std::string(buffer, len));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(1ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void set_title(const std::string& title)
|
||||||
|
{
|
||||||
|
if (syscon::s_wcd.hWnd)
|
||||||
|
{
|
||||||
|
SetWindowTextA(syscon::s_wcd.hWnd, title.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace syscon
|
||||||
|
{
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
// register component
|
||||||
|
static component_loader::installer<syscon::component> __component = component_loader::installer<syscon::component>("syscon::component");
|
||||||
|
}
|
||||||
|
}
|
10
src/client/component/console/syscon.hpp
Normal file
10
src/client/component/console/syscon.hpp
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace syscon
|
||||||
|
{
|
||||||
|
void init();
|
||||||
|
|
||||||
|
void set_title(const std::string& title);
|
||||||
|
|
||||||
|
void Sys_Print(const char* msg);
|
||||||
|
}
|
422
src/client/component/console/terminal.cpp
Normal file
422
src/client/component/console/terminal.cpp
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "terminal.hpp"
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "console.hpp"
|
||||||
|
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
#include "component/command.hpp"
|
||||||
|
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
#include <utils/thread.hpp>
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
|
||||||
|
#define OUTPUT_HANDLE GetStdHandle(STD_OUTPUT_HANDLE)
|
||||||
|
|
||||||
|
namespace game_console
|
||||||
|
{
|
||||||
|
void print(int type, const std::string& data);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace terminal
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
utils::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) };
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t get_attribute(const int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case console::print_type_info:
|
||||||
|
return 7; // white
|
||||||
|
case console::print_type_warning:
|
||||||
|
return 6; // yellow
|
||||||
|
case console::print_type_error:
|
||||||
|
return 4; // red
|
||||||
|
case console::print_type_debug:
|
||||||
|
return 3; // cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update()
|
||||||
|
{
|
||||||
|
std::lock_guard _0(print_mutex);
|
||||||
|
|
||||||
|
show_cursor(false);
|
||||||
|
set_cursor_pos(0);
|
||||||
|
invoke_printf("%s", con.buffer);
|
||||||
|
set_cursor_pos(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case VK_RIGHT:
|
||||||
|
{
|
||||||
|
if (con.cursor < std::strlen(con.buffer))
|
||||||
|
{
|
||||||
|
con.cursor++;
|
||||||
|
set_cursor_pos(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, con.buffer);
|
||||||
|
|
||||||
|
con.cursor = 0;
|
||||||
|
|
||||||
|
clear_output();
|
||||||
|
SetConsoleTextAttribute(OUTPUT_HANDLE, get_attribute(7)); // green
|
||||||
|
invoke_printf("]%s\r\n", con.buffer);
|
||||||
|
SetConsoleTextAttribute(OUTPUT_HANDLE, get_attribute(console::print_type_info));
|
||||||
|
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.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 terminal::dispatch_message(console::print_type_info, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int dispatch_message(const int type, const std::string& message)
|
||||||
|
{
|
||||||
|
std::lock_guard _0(print_mutex);
|
||||||
|
|
||||||
|
clear_output();
|
||||||
|
set_cursor_pos(0);
|
||||||
|
|
||||||
|
SetConsoleTextAttribute(OUTPUT_HANDLE, get_attribute(type));
|
||||||
|
const auto res = invoke_printf("%s", message.data());
|
||||||
|
SetConsoleTextAttribute(OUTPUT_HANDLE, get_attribute(console::print_type_info));
|
||||||
|
|
||||||
|
game_console::print(type, message);
|
||||||
|
|
||||||
|
if (message.size() <= 0 || message[message.size() - 1] != '\n')
|
||||||
|
{
|
||||||
|
invoke_printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
component()
|
||||||
|
{
|
||||||
|
ShowWindow(GetConsoleWindow(), SW_HIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
printf_hook.create(printf, printf_stub);
|
||||||
|
|
||||||
|
ShowWindow(GetConsoleWindow(), SW_SHOW);
|
||||||
|
SetConsoleTitle("IW7-Mod: " VERSION);
|
||||||
|
|
||||||
|
con.kill_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||||
|
|
||||||
|
con.thread = utils::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.message == WM_QUIT)
|
||||||
|
{
|
||||||
|
command::execute("quit", false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void pre_destroy() override
|
||||||
|
{
|
||||||
|
con.kill = true;
|
||||||
|
SetEvent(con.kill_event);
|
||||||
|
|
||||||
|
if (con.thread.joinable())
|
||||||
|
{
|
||||||
|
con.thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace terminal
|
||||||
|
{
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
// register component
|
||||||
|
static component_loader::installer<terminal::component> __component = component_loader::installer<terminal::component>("terminal::component");
|
||||||
|
}
|
||||||
|
}
|
8
src/client/component/console/terminal.hpp
Normal file
8
src/client/component/console/terminal.hpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace terminal
|
||||||
|
{
|
||||||
|
void init();
|
||||||
|
|
||||||
|
int dispatch_message(const int type, const std::string& message);
|
||||||
|
}
|
65
src/client/component/console/wincon.cpp
Normal file
65
src/client/component/console/wincon.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "wincon.hpp"
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "console.hpp"
|
||||||
|
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
#include <utils/thread.hpp>
|
||||||
|
|
||||||
|
namespace wincon
|
||||||
|
{
|
||||||
|
static std::atomic_bool ingame = false;
|
||||||
|
static std::atomic_bool exit = false;
|
||||||
|
|
||||||
|
DWORD WINAPI console(LPVOID)
|
||||||
|
{
|
||||||
|
ShowWindow(GetConsoleWindow(), SW_SHOW);
|
||||||
|
SetConsoleTitleA("IW7-Mod Console");
|
||||||
|
|
||||||
|
std::string cmd;
|
||||||
|
exit = false;
|
||||||
|
|
||||||
|
while (!exit)
|
||||||
|
{
|
||||||
|
std::getline(std::cin, cmd);
|
||||||
|
if (ingame)
|
||||||
|
{
|
||||||
|
game::Cbuf_AddText(0, cmd.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void post_start() override
|
||||||
|
{
|
||||||
|
const auto handle = CreateThread(0, 0, wincon::console, 0, 0, 0);
|
||||||
|
utils::thread::set_name(handle, "Console");
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
wincon::ingame = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pre_destroy() override
|
||||||
|
{
|
||||||
|
wincon::ingame = false;
|
||||||
|
wincon::exit = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace wincon
|
||||||
|
{
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
// register component
|
||||||
|
static component_loader::installer<wincon::component> __component = component_loader::installer<wincon::component>("wincon::component");
|
||||||
|
}
|
||||||
|
}
|
6
src/client/component/console/wincon.hpp
Normal file
6
src/client/component/console/wincon.hpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace wincon
|
||||||
|
{
|
||||||
|
void init();
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "dvars.hpp"
|
#include "dvars.hpp"
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
#include <utils/json.hpp>
|
#include <utils/json.hpp>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
#include "game_console.hpp"
|
#include "game_console.hpp"
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
@ -471,7 +471,7 @@ namespace game_console
|
|||||||
const auto lines = utils::string::split(data, '\n');
|
const auto lines = utils::string::split(data, '\n');
|
||||||
for (const auto& line : lines)
|
for (const auto& line : lines)
|
||||||
{
|
{
|
||||||
print_internal(type == console::con_type_info ? line : "^"s.append(std::to_string(type)).append(line));
|
print_internal(type == console::print_type_info ? line : "^"s.append(std::to_string(type)).append(line));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include "game/dvars.hpp"
|
#include "game/dvars.hpp"
|
||||||
|
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
#include "dvars.hpp"
|
#include "dvars.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
#include "network.hpp"
|
#include "network.hpp"
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
#include "game/dvars.hpp"
|
#include "game/dvars.hpp"
|
||||||
|
|
||||||
#include "console.hpp"
|
#include "console/console.hpp"
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
#include "component/console/console.hpp"
|
||||||
|
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
#include <utils/io.hpp>
|
#include <utils/io.hpp>
|
||||||
|
|
||||||
@ -106,6 +108,7 @@ void limit_parallel_dll_loading()
|
|||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
ShowWindow(GetConsoleWindow(), SW_HIDE);
|
ShowWindow(GetConsoleWindow(), SW_HIDE);
|
||||||
|
console::init();
|
||||||
|
|
||||||
FARPROC entry_point;
|
FARPROC entry_point;
|
||||||
enable_dpi_awareness();
|
enable_dpi_awareness();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user