2022-02-27 07:53:44 -05:00
# include < STDInclude . hpp >
2022-12-26 07:07:24 -05:00
# include "Console.hpp"
2022-11-26 12:38:34 -05:00
2022-12-31 09:03:33 -05:00
# include "Terminus_4.49.1.ttf.hpp"
2022-11-26 12:38:34 -05:00
# include <version.hpp>
# ifdef MOUSE_MOVED
# undef MOUSE_MOVED
# endif
2022-10-26 19:48:33 -04:00
# include <curses.h>
2017-01-19 16:23:59 -05:00
2022-09-20 12:33:21 -04:00
# define REMOVE_HEADERBAR 1
2017-01-19 16:23:59 -05:00
namespace Components
{
2022-10-26 19:48:33 -04:00
static WINDOW * OutputWindow ;
static WINDOW * InputWindow ;
static WINDOW * InfoWindow ;
2017-01-19 16:23:59 -05:00
int Console : : OutputTop = 0 ;
int Console : : OutBuffer = 0 ;
int Console : : LastRefresh = 0 ;
int Console : : Height = 25 ;
int Console : : Width = 80 ;
2022-06-12 17:07:53 -04:00
char Console : : LineBuffer [ 1024 ] = { 0 } ;
char Console : : LineBuffer2 [ 1024 ] = { 0 } ;
2017-01-19 16:23:59 -05:00
int Console : : LineBufferIndex = 0 ;
bool Console : : HasConsole = false ;
bool Console : : SkipShutdown = false ;
2022-09-20 12:33:21 -04:00
COLORREF Console : : TextColor =
2022-12-31 09:03:33 -05:00
# if _DEBUG
2022-09-20 12:33:21 -04:00
RGB ( 255 , 200 , 117 ) ;
# else
RGB ( 120 , 237 , 122 ) ;
# endif
COLORREF Console : : BackgroundColor =
2022-12-31 09:03:33 -05:00
# if _DEBUG
2022-09-20 12:33:21 -04:00
RGB ( 35 , 21 , 0 ) ;
# else
RGB ( 25 , 32 , 25 ) ;
# endif
HBRUSH Console : : ForegroundBrush = CreateSolidBrush ( TextColor ) ;
HBRUSH Console : : BackgroundBrush = CreateSolidBrush ( BackgroundColor ) ;
HANDLE Console : : CustomConsoleFont ;
2017-01-19 16:23:59 -05:00
std : : thread Console : : ConsoleThread ;
Game : : SafeArea Console : : OriginalSafeArea ;
2022-08-23 03:12:20 -04:00
const char * * Console : : GetAutoCompleteFileList ( const char * path , const char * extension , Game : : FsListBehavior_e behavior , int * numfiles , int allocTrackType )
2017-01-19 16:23:59 -05:00
{
2017-02-21 14:15:16 -05:00
if ( path = = reinterpret_cast < char * > ( 0xBAADF00D ) | | path = = reinterpret_cast < char * > ( 0xCDCDCDCD ) | | : : Utils : : Memory : : IsBadReadPtr ( path ) ) return nullptr ;
2022-08-23 03:12:20 -04:00
return Game : : FS_ListFiles ( path , extension , behavior , numfiles , allocTrackType ) ;
2017-01-19 16:23:59 -05:00
}
void Console : : RefreshStatus ( )
{
2022-08-10 17:03:26 -04:00
const std : : string mapname = ( * Game : : sv_mapname ) - > current . string ;
const auto hostname = TextRenderer : : StripColors ( ( * Game : : sv_hostname ) - > current . string ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
if ( HasConsole )
2017-01-19 16:23:59 -05:00
{
SetConsoleTitleA ( hostname . data ( ) ) ;
2022-04-12 08:34:51 -04:00
auto clientCount = 0 ;
2022-12-31 09:03:33 -05:00
auto maxClientCount = * Game : : svs_clientCount ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
if ( maxClientCount )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
for ( auto i = 0 ; i < maxClientCount ; + + i )
2017-01-19 16:23:59 -05:00
{
2022-08-20 06:09:41 -04:00
if ( Game : : svs_clients [ i ] . header . state > = Game : : CS_CONNECTED )
2017-01-19 16:23:59 -05:00
{
+ + clientCount ;
}
}
}
else
{
2022-12-31 09:03:33 -05:00
maxClientCount = * Game : : party_maxplayers ? ( * Game : : party_maxplayers ) - > current . integer : 18 ;
clientCount = Game : : PartyHost_CountMembers ( Game : : g_lobbyData ) ;
2017-01-19 16:23:59 -05:00
}
2022-10-26 19:48:33 -04:00
wclear ( InfoWindow ) ;
2022-12-31 09:03:33 -05:00
wprintw ( InfoWindow , " %s : %d/%d players : map %s " , hostname . data ( ) , clientCount , maxClientCount , ( ! mapname . empty ( ) ) ? mapname . data ( ) : " none " ) ;
2022-10-26 19:48:33 -04:00
wnoutrefresh ( InfoWindow ) ;
2017-01-19 16:23:59 -05:00
}
2022-12-31 09:03:33 -05:00
else if ( IsWindow ( GetWindow ( ) ) ! = FALSE )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
SetWindowTextA ( GetWindow ( ) , Utils : : String : : VA ( " IW4x( " VERSION " ) : %s " , hostname . data ( ) ) ) ;
2017-01-19 16:23:59 -05:00
}
}
void Console : : ShowPrompt ( )
{
2022-10-26 19:48:33 -04:00
wattron ( InputWindow , COLOR_PAIR ( 10 ) | A_BOLD ) ;
wprintw ( InputWindow , " %s> " , VERSION ) ;
2017-01-19 16:23:59 -05:00
}
void Console : : RefreshOutput ( )
{
2022-12-31 09:03:33 -05:00
prefresh ( OutputWindow , ( ( OutputTop > 0 ) ? ( OutputTop - 1 ) : 0 ) , 0 , 1 , 0 , Height - 2 , Width - 1 ) ;
2017-01-19 16:23:59 -05:00
}
void Console : : ScrollOutput ( int amount )
{
2022-12-31 09:03:33 -05:00
OutputTop + = amount ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
if ( OutputTop > OUTPUT_MAX_TOP )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
OutputTop = OUTPUT_MAX_TOP ;
2017-01-19 16:23:59 -05:00
}
2022-12-31 09:03:33 -05:00
else if ( OutputTop < 0 )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
OutputTop = 0 ;
2017-01-19 16:23:59 -05:00
}
// make it only scroll the top if there's more than HEIGHT lines
2022-12-31 09:03:33 -05:00
if ( OutBuffer > = 0 )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
OutBuffer + = amount ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
if ( OutBuffer > = Height )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
OutBuffer = - 1 ;
2017-01-19 16:23:59 -05:00
}
2022-12-31 09:03:33 -05:00
if ( OutputTop < Height )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
OutputTop = 0 ;
2017-01-19 16:23:59 -05:00
}
}
}
2022-09-20 12:33:21 -04:00
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 )
{
2022-12-31 09:03:33 -05:00
dpi = static_cast < int > ( getDpiForWindow ( hWnd ) ) ;
2022-09-20 12:33:21 -04:00
}
else if ( getDpiForMonitor )
{
HMONITOR hMonitor = MonitorFromWindow ( hWnd , MONITOR_DEFAULTTONEAREST ) ;
UINT xdpi , ydpi ;
2022-12-31 09:03:33 -05:00
getDpiForMonitor ( hMonitor , 0 , & xdpi , & ydpi ) ;
2022-09-20 12:33:21 -04:00
dpi = 96 ;
}
else
{
HDC hDC = GetDC ( hWnd ) ;
INT ydpi = GetDeviceCaps ( hDC , LOGPIXELSY ) ;
2022-12-31 09:03:33 -05:00
ReleaseDC ( nullptr , hDC ) ;
2022-09-20 12:33:21 -04:00
dpi = ydpi ;
}
constexpr auto unawareDpi = 96.0f ;
2022-12-31 09:03:33 -05:00
return static_cast < float > ( dpi ) / unawareDpi ;
2022-09-20 12:33:21 -04:00
}
2017-01-19 16:23:59 -05:00
const char * Console : : Input ( )
{
2022-12-31 09:03:33 -05:00
if ( ! HasConsole )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
ShowPrompt ( ) ;
2022-10-26 19:48:33 -04:00
wrefresh ( InputWindow ) ;
2022-12-31 09:03:33 -05:00
HasConsole = true ;
2017-01-19 16:23:59 -05:00
}
2022-12-31 09:03:33 -05:00
auto currentTime = static_cast < int > ( GetTickCount64 ( ) ) ; // Make our compiler happy
if ( ( currentTime - LastRefresh ) > 250 )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
RefreshOutput ( ) ;
LastRefresh = currentTime ;
2017-01-19 16:23:59 -05:00
}
2022-12-31 09:03:33 -05:00
auto c = wgetch ( InputWindow ) ;
2017-01-19 16:23:59 -05:00
if ( c = = ERR )
{
2017-01-20 16:41:03 -05:00
return nullptr ;
2017-01-19 16:23:59 -05:00
}
switch ( c )
{
case ' \r ' :
case 459 : // keypad enter
{
2022-10-26 19:48:33 -04:00
wattron ( OutputWindow , COLOR_PAIR ( 10 ) | A_BOLD ) ;
wprintw ( OutputWindow , " %s " , " ] " ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
if ( LineBufferIndex )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
wprintw ( OutputWindow , " %s " , LineBuffer ) ;
2017-01-19 16:23:59 -05:00
}
2022-10-26 19:48:33 -04:00
wprintw ( OutputWindow , " %s " , " \n " ) ;
wattroff ( OutputWindow , A_BOLD ) ;
wclear ( InputWindow ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
ShowPrompt ( ) ;
2017-01-19 16:23:59 -05:00
2022-10-26 19:48:33 -04:00
wrefresh ( InputWindow ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
ScrollOutput ( 1 ) ;
RefreshOutput ( ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
if ( LineBufferIndex )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
strcpy_s ( LineBuffer2 , LineBuffer ) ;
strcat_s ( LineBuffer , " \n " ) ;
LineBufferIndex = 0 ;
return LineBuffer ;
2017-01-19 16:23:59 -05:00
}
break ;
}
case ' c ' - ' a ' + 1 : // ctrl-c
case 27 :
{
2022-12-31 09:03:33 -05:00
LineBuffer [ 0 ] = ' \0 ' ;
LineBufferIndex = 0 ;
2017-01-19 16:23:59 -05:00
2022-10-26 19:48:33 -04:00
wclear ( InputWindow ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
ShowPrompt ( ) ;
2017-01-19 16:23:59 -05:00
2022-10-26 19:48:33 -04:00
wrefresh ( InputWindow ) ;
2017-01-19 16:23:59 -05:00
break ;
}
case 8 : // backspace
{
2022-12-31 09:03:33 -05:00
if ( LineBufferIndex > 0 )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
LineBufferIndex - - ;
LineBuffer [ LineBufferIndex ] = ' \0 ' ;
2017-01-19 16:23:59 -05:00
2022-10-26 19:48:33 -04:00
wprintw ( InputWindow , " %c %c " , static_cast < char > ( c ) , static_cast < char > ( c ) ) ;
wrefresh ( InputWindow ) ;
2017-01-19 16:23:59 -05:00
}
break ;
}
case KEY_PPAGE :
{
2022-12-31 09:03:33 -05:00
ScrollOutput ( - 1 ) ;
RefreshOutput ( ) ;
2017-01-19 16:23:59 -05:00
break ;
}
case KEY_NPAGE :
{
2022-12-31 09:03:33 -05:00
ScrollOutput ( 1 ) ;
RefreshOutput ( ) ;
2017-01-19 16:23:59 -05:00
break ;
}
case KEY_UP :
{
2022-10-26 19:48:33 -04:00
wclear ( InputWindow ) ;
2022-12-31 09:03:33 -05:00
ShowPrompt ( ) ;
wprintw ( InputWindow , " %s " , LineBuffer2 ) ;
2022-10-26 19:48:33 -04:00
wrefresh ( InputWindow ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
strcpy_s ( LineBuffer , LineBuffer2 ) ;
LineBufferIndex = static_cast < int > ( std : : strlen ( LineBuffer ) ) ;
2017-01-19 16:23:59 -05:00
break ;
}
default :
2022-12-31 09:03:33 -05:00
if ( c < = 127 & & LineBufferIndex < 1022 )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
// temporary workaround, find out what overwrites our index later on
2017-01-19 16:23:59 -05:00
//consoleLineBufferIndex = strlen(consoleLineBuffer);
2022-12-31 09:03:33 -05:00
LineBuffer [ LineBufferIndex + + ] = static_cast < char > ( c ) ;
LineBuffer [ LineBufferIndex ] = ' \0 ' ;
2022-10-26 19:48:33 -04:00
wprintw ( InputWindow , " %c " , static_cast < char > ( c ) ) ;
wrefresh ( InputWindow ) ;
2017-01-19 16:23:59 -05:00
}
break ;
}
2017-01-20 16:41:03 -05:00
return nullptr ;
2017-01-19 16:23:59 -05:00
}
void Console : : Destroy ( )
{
2017-07-14 05:36:37 -04:00
__try
2017-06-08 05:42:01 -04:00
{
2022-10-26 19:48:33 -04:00
delwin ( OutputWindow ) ;
delwin ( InputWindow ) ;
delwin ( InfoWindow ) ;
2017-06-08 06:22:45 -04:00
endwin ( ) ;
2017-06-08 05:42:01 -04:00
delscreen ( SP ) ;
}
2017-07-14 05:36:37 -04:00
__finally { }
2017-01-19 16:23:59 -05:00
2022-10-26 19:48:33 -04:00
OutputWindow = nullptr ;
InputWindow = nullptr ;
InfoWindow = nullptr ;
2017-01-19 16:23:59 -05:00
}
void Console : : Create ( )
{
2022-12-31 09:03:33 -05:00
OutputTop = 0 ;
OutBuffer = 0 ;
LastRefresh = 0 ;
LineBufferIndex = 0 ;
HasConsole = false ;
2017-01-19 16:23:59 -05:00
CONSOLE_SCREEN_BUFFER_INFO info ;
if ( GetConsoleScreenBufferInfo ( GetStdHandle ( STD_OUTPUT_HANDLE ) , & info ) )
{
2022-12-31 09:03:33 -05:00
Width = info . dwSize . X ;
Height = info . srWindow . Bottom - info . srWindow . Top + 1 ;
2017-01-19 16:23:59 -05:00
}
else
{
2022-12-31 09:03:33 -05:00
Height = 25 ;
Width = 80 ;
2017-01-19 16:23:59 -05:00
}
initscr ( ) ;
raw ( ) ;
noecho ( ) ;
2022-12-31 09:03:33 -05:00
OutputWindow = newpad ( Height - 1 , Width ) ;
InputWindow = newwin ( 1 , Width , Height - 1 , 0 ) ;
InfoWindow = newwin ( 1 , Width , 0 , 0 ) ;
2017-01-19 16:23:59 -05:00
2022-10-26 19:48:33 -04:00
scrollok ( OutputWindow , true ) ;
idlok ( OutputWindow , true ) ;
scrollok ( InputWindow , true ) ;
nodelay ( InputWindow , true ) ;
keypad ( InputWindow , true ) ;
2017-01-19 16:23:59 -05:00
if ( has_colors ( ) )
{
start_color ( ) ;
init_pair ( 1 , COLOR_BLACK , COLOR_WHITE ) ;
init_pair ( 2 , COLOR_WHITE , COLOR_BLACK ) ;
init_pair ( 3 , COLOR_RED , COLOR_BLACK ) ;
init_pair ( 4 , COLOR_GREEN , COLOR_BLACK ) ;
init_pair ( 5 , COLOR_YELLOW , COLOR_BLACK ) ;
init_pair ( 6 , COLOR_BLUE , COLOR_BLACK ) ;
init_pair ( 7 , COLOR_CYAN , COLOR_BLACK ) ;
init_pair ( 8 , COLOR_RED , COLOR_BLACK ) ;
init_pair ( 9 , COLOR_WHITE , COLOR_BLACK ) ;
init_pair ( 10 , COLOR_WHITE , COLOR_BLACK ) ;
}
2022-10-26 19:48:33 -04:00
wbkgd ( InfoWindow , COLOR_PAIR ( 1 ) ) ;
2017-01-19 16:23:59 -05:00
2022-10-26 19:48:33 -04:00
wrefresh ( InfoWindow ) ;
wrefresh ( InputWindow ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
RefreshOutput ( ) ;
2017-01-19 16:23:59 -05:00
}
2022-06-12 17:07:53 -04:00
void Console : : Error ( const char * fmt , . . . )
2017-01-19 16:23:59 -05:00
{
2022-06-12 17:07:53 -04:00
char buf [ 4096 ] = { 0 } ;
2017-01-19 16:23:59 -05:00
va_list va ;
2022-06-12 17:07:53 -04:00
va_start ( va , fmt ) ;
2022-07-23 09:49:56 -04:00
vsnprintf_s ( buf , _TRUNCATE , fmt , va ) ;
2017-01-19 16:23:59 -05:00
va_end ( va ) ;
2022-07-23 09:49:56 -04:00
Logger : : PrintError ( Game : : CON_CHANNEL_ERROR , " {} \n " , buf ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
RefreshOutput ( ) ;
2017-01-19 16:23:59 -05:00
if ( IsDebuggerPresent ( ) )
{
while ( true )
{
std : : this_thread : : sleep_for ( 5 s ) ;
}
}
2022-12-31 09:03:33 -05:00
TerminateProcess ( GetCurrentProcess ( ) , EXIT_FAILURE ) ;
2017-01-19 16:23:59 -05:00
}
void Console : : Print ( const char * message )
{
2022-10-26 19:48:33 -04:00
if ( ! OutputWindow ) return ;
2017-01-19 16:23:59 -05:00
const char * p = message ;
while ( * p ! = ' \0 ' )
{
if ( * p = = ' ^ ' )
{
+ + p ;
2022-12-31 09:03:33 -05:00
const char color = ( * p - ' 0 ' ) ;
2017-01-19 16:23:59 -05:00
if ( color < 9 & & color > 0 )
{
2022-10-26 19:48:33 -04:00
wattron ( OutputWindow , COLOR_PAIR ( color + 2 ) ) ;
2017-01-19 16:23:59 -05:00
+ + p ;
continue ;
}
}
2022-10-26 19:48:33 -04:00
waddch ( OutputWindow , * p ) ;
2017-01-19 16:23:59 -05:00
+ + p ;
}
2022-10-26 19:48:33 -04:00
wattron ( OutputWindow , COLOR_PAIR ( 9 ) ) ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
RefreshOutput ( ) ;
}
HFONT CALLBACK 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 ,
2022-09-20 12:33:21 -04:00
[[maybe_unused]] LPCSTR pszFaceName )
{
2022-12-31 09:03:33 -05:00
HFONT font = CreateFontA (
2022-09-20 12:33:21 -04:00
12 ,
cWidth ,
cEscapement ,
cOrientation ,
700 ,
bItalic ,
bUnderline ,
bStrikeOut ,
iCharSet ,
OUT_RASTER_PRECIS ,
iClipPrecision ,
NONANTIALIASED_QUALITY ,
0x31 ,
2022-12-31 09:03:33 -05:00
" Terminus (TTF) "
) ; // Terminus (TTF)
2022-09-20 12:33:21 -04:00
return font ;
}
void Console : : GetWindowPos ( HWND hWnd , int * x , int * y )
{
HWND hWndParent = GetParent ( hWnd ) ;
2022-12-31 09:03:33 -05:00
POINT p { } ;
2022-09-20 12:33:21 -04:00
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 ) ;
2022-12-31 09:03:33 -05:00
auto isInputBox = id = = INPUT_BOX ;
auto isOutputBox = id = = OUTPUT_BOX ;
2022-09-20 12:33:21 -04:00
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 ) ;
2022-12-31 09:03:33 -05:00
auto scale = GetDpiScale ( parent ) ;
2022-09-20 12:33:21 -04:00
if ( isInputBox )
{
2022-12-31 09:03:33 -05:00
auto newX = childX ; // No change!
auto newY = static_cast < int > ( ( newParentRect . bottom - newParentRect . top ) - 65 * scale ) ;
auto newWidth = static_cast < int > ( ( newParentRect . right - newParentRect . left ) - 29 * scale ) ;
auto newHeight = static_cast < int > ( ( childRect . bottom - childRect . top ) * scale ) ; // No change!
2022-09-20 12:33:21 -04:00
MoveWindow ( hwndChild , newX , newY , newWidth , newHeight , TRUE ) ;
}
if ( isOutputBox )
{
2022-12-31 09:03:33 -05:00
auto newX = childX ; // No change!
auto newY = childY ; // No change!
auto newWidth = static_cast < int > ( ( newParentRect . right - newParentRect . left ) - 29 ) ;
2022-09-20 12:33:21 -04:00
# ifdef REMOVE_HEADERBAR
2022-12-31 09:03:33 -05:00
constexpr auto margin = 10 ;
# else
constexpr auto margin = 70 ;
2022-09-20 12:33:21 -04:00
# endif
2022-12-31 09:03:33 -05:00
auto newHeight = static_cast < int > ( ( newParentRect . bottom - newParentRect . top ) - 74 * scale - margin ) ;
2022-09-20 12:33:21 -04:00
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 )
{
2022-12-31 09:03:33 -05:00
constexpr auto maxChars = 0x4000 ;
constexpr auto maxAffectedChars = 0x100 ;
2022-09-20 12:33:21 -04:00
HWND outputBox = Utils : : Hook : : Get < HWND > ( 0x64A328C ) ;
2022-12-31 09:03:33 -05:00
auto totalClearLength = 0 ;
2022-09-20 12:33:21 -04:00
char str [ maxAffectedChars ] ;
2022-12-31 09:03:33 -05:00
const auto fetchedCharacters = GetWindowTextA ( outputBox , str , maxAffectedChars ) ;
2022-09-20 12:33:21 -04:00
2022-12-31 09:03:33 -05:00
auto totalChars = GetWindowTextLengthA ( outputBox ) ;
2022-09-20 12:33:21 -04:00
while ( totalChars - totalClearLength > maxChars )
{
2022-12-31 09:03:33 -05:00
auto clearLength = maxAffectedChars ; // Default to full clear
2022-09-20 12:33:21 -04:00
2022-12-31 09:03:33 -05:00
for ( auto i = 0 ; i < fetchedCharacters ; i + + )
2022-09-20 12:33:21 -04:00
{
if ( str [ i ] = = ' \n ' )
{
// Shorter clear if I meet a linebreak
clearLength = i + 1 ;
break ;
}
}
totalClearLength + = clearLength ;
}
if ( totalClearLength > 0 )
{
2022-12-31 09:03:33 -05:00
SendMessageA ( outputBox , WM_SETREDRAW , FALSE , 0 ) ;
SendMessageA ( outputBox , EM_SETSEL , 0 , totalClearLength ) ;
SendMessageA ( outputBox , EM_REPLACESEL , FALSE , 0 ) ;
SendMessageA ( outputBox , WM_SETREDRAW , TRUE , 0 ) ;
2022-09-20 12:33:21 -04:00
}
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 :
{
2022-12-31 09:03:33 -05:00
BOOL darkMode = TRUE ;
constexpr auto DWMWA_USE_IMMERSIVE_DARK_MODE = 20 ;
DwmSetWindowAttribute ( hWnd , DWMWA_USE_IMMERSIVE_DARK_MODE , & darkMode , sizeof ( darkMode ) ) ;
2022-09-20 12:33:21 -04:00
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 ;
}
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 ( )
{
2022-12-31 09:03:33 -05:00
Utils : : Hook : : Set < std : : uint8_t > ( 0x428A8E , 0 ) ; // Adjust logo Y pos
Utils : : Hook : : Set < std : : uint8_t > ( 0x428A90 , 0 ) ; // Adjust logo X pos
Utils : : Hook : : Set < std : : uint8_t > ( 0x428AF2 , 67 ) ; // Adjust output Y pos
Utils : : Hook : : Set < std : : uint32_t > ( 0x428AC5 , 397 ) ; // Adjust input Y pos
Utils : : Hook : : Set < std : : uint32_t > ( 0x428951 , 609 ) ; // Reduce window width
Utils : : Hook : : Set < std : : uint32_t > ( 0x42895D , 423 ) ; // Reduce window height
Utils : : Hook : : Set < std : : uint32_t > ( 0x428AC0 , 597 ) ; // Reduce input width
Utils : : Hook : : Set < std : : uint32_t > ( 0x428AED , 596 ) ; // Reduce output width
2022-09-20 12:33:21 -04:00
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 ) ;
2022-12-31 09:03:33 -05:00
Utils : : Hook ( 0x4F57DF , Sys_PrintStub , HOOK_JUMP ) . install ( ) - > quick ( ) ;
2022-09-20 12:33:21 -04:00
}
2017-01-19 16:23:59 -05:00
void Console : : ConsoleRunner ( )
{
2022-12-31 09:03:33 -05:00
SkipShutdown = false ;
2017-01-19 16:23:59 -05:00
Game : : Sys_ShowConsole ( ) ;
MSG message ;
2022-12-31 09:03:33 -05:00
while ( IsWindow ( GetWindow ( ) ) ! = FALSE & & GetMessageA ( & message , nullptr , 0 , 0 ) )
2017-01-19 16:23:59 -05:00
{
TranslateMessage ( & message ) ;
DispatchMessageA ( & message ) ;
}
2022-12-31 09:03:33 -05:00
if ( SkipShutdown ) return ;
2017-01-19 16:23:59 -05:00
2022-12-31 09:03:33 -05:00
if ( Game : : Sys_Milliseconds ( ) - LastRefresh > 100 & &
2017-01-20 16:41:03 -05:00
MessageBoxA ( nullptr , " The application is not responding anymore, do you want to force its termination? " , " Application is not responding " , MB_ICONEXCLAMATION | MB_YESNO ) = = IDYES )
2017-01-19 16:23:59 -05:00
{
// Force process termination
// if the main thread is not responding
2022-12-31 09:03:33 -05:00
# ifdef _DEBUG
OutputDebugStringA ( " Process termination was forced as the main thread is not responding! " ) ;
# endif
2017-01-19 16:23:59 -05:00
// We can not force the termination in this thread
// The destructor would be called in this thread
// and would try to join this thread, which is impossible
2022-12-31 09:03:33 -05:00
TerminateProcess ( GetCurrentProcess ( ) , EXIT_FAILURE ) ;
2017-01-19 16:23:59 -05:00
}
else
{
// Send quit command to safely terminate the application
Command : : Execute ( " wait 200;quit \n " , false ) ;
}
}
void Console : : StdOutPrint ( const char * message )
{
printf ( " %s " , message ) ;
fflush ( stdout ) ;
}
2022-06-12 17:07:53 -04:00
void Console : : StdOutError ( const char * fmt , . . . )
2017-01-19 16:23:59 -05:00
{
2022-06-12 17:07:53 -04:00
char buffer [ 4096 ] = { 0 } ;
2017-01-19 16:23:59 -05:00
va_list ap ;
2022-06-12 17:07:53 -04:00
va_start ( ap , fmt ) ;
2022-12-31 09:03:33 -05:00
vsnprintf_s ( buffer , _TRUNCATE , fmt , ap ) ;
2017-01-19 16:23:59 -05:00
va_end ( ap ) ;
perror ( buffer ) ;
fflush ( stderr ) ;
ExitProcess ( 1 ) ;
}
__declspec ( naked ) void Console : : DrawSolidConsoleStub ( )
{
__asm
{
pushad
call Console : : StoreSafeArea
popad
// We need esi preserved here, so we have to backup 'all' registers when storing the safearea
call Game : : Con_DrawSolidConsole
2017-02-01 07:44:25 -05:00
pushad
2017-01-19 16:23:59 -05:00
call Console : : RestoreSafeArea
2017-02-01 07:44:25 -05:00
popad
2017-01-19 16:23:59 -05:00
retn
}
}
void Console : : StoreSafeArea ( )
{
// Backup the original safe area
2022-12-31 09:03:33 -05:00
OriginalSafeArea = * Game : : safeArea ;
2017-01-19 16:23:59 -05:00
// Apply new safe area and border
float border = 6.0f ;
Game : : safeArea - > top = border ;
Game : : safeArea - > left = border ;
Game : : safeArea - > bottom = static_cast < float > ( Renderer : : Height ( ) ) - border ;
Game : : safeArea - > right = static_cast < float > ( Renderer : : Width ( ) ) - border ;
Game : : safeArea - > textHeight = static_cast < int > ( ( Game : : safeArea - > bottom - Game : : safeArea - > top - ( 2 * Game : : safeArea - > fontHeight ) - 24.0 ) / Game : : safeArea - > fontHeight ) ;
Game : : safeArea - > textWidth = static_cast < int > ( Game : : safeArea - > right - Game : : safeArea - > left - 10.0f - 18.0 ) ;
}
void Console : : RestoreSafeArea ( )
{
// Restore the initial safe area
2022-12-31 09:03:33 -05:00
* Game : : safeArea = OriginalSafeArea ;
2017-01-19 16:23:59 -05:00
}
void Console : : SetSkipShutdown ( )
{
2022-12-31 09:03:33 -05:00
SkipShutdown = true ;
2017-01-19 16:23:59 -05:00
}
void Console : : FreeNativeConsole ( )
{
2022-10-15 16:31:16 -04:00
if ( ! Flags : : HasFlag ( " stdout " ) & & ( ! Dedicated : : IsEnabled ( ) | | Flags : : HasFlag ( " console " ) ) & & ! Loader : : IsPerformingUnitTests ( ) )
2017-01-19 16:23:59 -05:00
{
FreeConsole ( ) ;
}
}
2017-02-10 03:50:08 -05:00
HWND Console : : GetWindow ( )
{
return * reinterpret_cast < HWND * > ( 0x64A3288 ) ;
}
2017-05-27 11:17:12 -04:00
void Console : : ShowAsyncConsole ( )
{
2022-12-31 09:03:33 -05:00
ConsoleThread = std : : thread ( ConsoleRunner ) ;
2017-05-27 11:17:12 -04:00
}
2022-12-31 09:03:33 -05:00
Game : : dvar_t * Console : : RegisterConColor ( const char * dvarName , float r , float g , float b , float a , float min , float max , unsigned __int16 flags , const char * description )
2017-01-19 16:23:59 -05:00
{
static struct
{
const char * name ;
float color [ 4 ] ;
} patchedColors [ ] =
{
{ " con_inputBoxColor " , { 0.20f , 0.20f , 0.20f , 1.00f } } ,
{ " con_inputHintBoxColor " , { 0.30f , 0.30f , 0.30f , 1.00f } } ,
{ " con_outputBarColor " , { 0.50f , 0.50f , 0.50f , 0.60f } } ,
{ " con_outputSliderColor " , { 0.70f , 1.00f , 0.00f , 1.00f } } ,
{ " con_outputWindowColor " , { 0.25f , 0.25f , 0.25f , 0.85f } } ,
} ;
2022-06-12 17:07:53 -04:00
for ( std : : size_t i = 0 ; i < ARRAYSIZE ( patchedColors ) ; + + i )
2017-01-19 16:23:59 -05:00
{
2022-03-08 07:20:28 -05:00
if ( std : : strcmp ( dvarName , patchedColors [ i ] . name ) = = 0 )
2017-01-19 16:23:59 -05:00
{
r = patchedColors [ i ] . color [ 0 ] ;
g = patchedColors [ i ] . color [ 1 ] ;
b = patchedColors [ i ] . color [ 2 ] ;
a = patchedColors [ i ] . color [ 3 ] ;
break ;
}
}
2022-03-08 07:20:28 -05:00
return reinterpret_cast < Game : : Dvar_RegisterVec4_t > ( 0x471500 ) ( dvarName , r , g , b , a , min , max , flags , description ) ;
2017-01-19 16:23:59 -05:00
}
2022-07-23 09:49:56 -04:00
void Console : : Con_ToggleConsole ( )
{
Game : : Field_Clear ( Game : : g_consoleField ) ;
if ( Game : : conDrawInputGlob - > matchIndex > = 0 & & Game : : conDrawInputGlob - > autoCompleteChoice [ 0 ] ! = ' \0 ' )
{
Game : : conDrawInputGlob - > matchIndex = - 1 ;
Game : : conDrawInputGlob - > autoCompleteChoice [ 0 ] = ' \0 ' ;
}
Game : : g_consoleField - > fixedSize = 1 ;
Game : : con - > outputVisible = false ;
Game : : g_consoleField - > widthInPixels = * Game : : g_console_field_width ;
Game : : g_consoleField - > charHeight = * Game : : g_console_char_height ;
for ( std : : size_t localClientNum = 0 ; localClientNum < Game : : MAX_LOCAL_CLIENTS ; + + localClientNum )
{
assert ( ( Game : : clientUIActives [ 0 ] . keyCatchers & Game : : KEYCATCH_CONSOLE ) = = ( Game : : clientUIActives [ localClientNum ] . keyCatchers & Game : : KEYCATCH_CONSOLE ) ) ;
Game : : clientUIActives [ localClientNum ] . keyCatchers ^ = 1 ;
}
}
void Console : : AddConsoleCommand ( )
{
Command : : Add ( " con_echo " , [ ]
{
2022-12-31 09:03:33 -05:00
Con_ToggleConsole ( ) ;
2022-07-23 09:49:56 -04:00
Game : : I_strncpyz ( Game : : g_consoleField - > buffer , " \\ echo " , sizeof ( Game : : field_t : : buffer ) ) ;
Game : : g_consoleField - > cursor = static_cast < int > ( std : : strlen ( Game : : g_consoleField - > buffer ) ) ;
Game : : Field_AdjustScroll ( Game : : ScrPlace_GetFullPlacement ( ) , Game : : g_consoleField ) ;
} ) ;
}
2017-01-19 16:23:59 -05:00
Console : : Console ( )
{
2022-08-13 11:19:45 -04:00
AssertOffset ( Game : : clientUIActive_t , connectionState , 0x9B8 ) ;
2022-08-15 11:25:20 -04:00
AssertOffset ( Game : : clientUIActive_t , keyCatchers , 0x9B0 ) ;
2022-08-13 11:19:45 -04:00
2017-01-19 16:23:59 -05:00
// Console '%s: %s> ' string
2022-07-23 09:49:56 -04:00
Utils : : Hook : : Set < const char * > ( 0x5A44B4 , " IW4x MP: " VERSION " > " ) ;
2017-01-19 16:23:59 -05:00
// Patch console color
static float consoleColor [ ] = { 0.70f , 1.00f , 0.00f , 1.00f } ;
Utils : : Hook : : Set < float * > ( 0x5A451A , consoleColor ) ;
Utils : : Hook : : Set < float * > ( 0x5A4400 , consoleColor ) ;
2022-05-13 13:44:33 -04:00
// Remove the need to type '\' or '/' to send a console command
2022-12-31 09:03:33 -05:00
Utils : : Hook : : Set < std : : uint8_t > ( 0x431565 , 0xEB ) ;
2022-05-13 13:44:33 -04:00
2017-01-19 16:23:59 -05:00
// Internal console
2022-12-31 09:03:33 -05:00
Utils : : Hook ( 0x4F690C , Con_ToggleConsole , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x4F65A5 , Con_ToggleConsole , HOOK_JUMP ) . install ( ) - > quick ( ) ;
2017-01-19 16:23:59 -05:00
// Patch safearea for ingame-console
2022-12-31 09:03:33 -05:00
Utils : : Hook ( 0x5A50EF , DrawSolidConsoleStub , HOOK_CALL ) . install ( ) - > quick ( ) ;
2017-01-19 16:23:59 -05:00
// Check for bad food ;)
2022-12-31 09:03:33 -05:00
Utils : : Hook ( 0x4CB9F4 , GetAutoCompleteFileList , HOOK_CALL ) . install ( ) - > quick ( ) ;
2017-01-19 16:23:59 -05:00
// Patch console dvars
2022-12-31 09:03:33 -05:00
Utils : : Hook ( 0x4829AB , RegisterConColor , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x4829EE , RegisterConColor , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x482A31 , RegisterConColor , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x482A7A , RegisterConColor , HOOK_CALL ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x482AC3 , RegisterConColor , HOOK_CALL ) . install ( ) - > quick ( ) ;
2017-01-19 16:23:59 -05:00
// Modify console style
2022-09-20 12:33:21 -04:00
ApplyConsoleStyle ( ) ;
2017-01-19 16:23:59 -05:00
// Don't resize the console
Utils : : Hook ( 0x64DC6B , 0x64DCC2 , HOOK_JUMP ) . install ( ) - > quick ( ) ;
2022-07-23 09:49:56 -04:00
# ifdef _DEBUG
Console : : AddConsoleCommand ( ) ;
# endif
2017-01-19 16:23:59 -05:00
if ( Dedicated : : IsEnabled ( ) & & ! ZoneBuilder : : IsEnabled ( ) )
{
2022-12-31 09:03:33 -05:00
Scheduler : : Loop ( RefreshStatus , Scheduler : : Pipeline : : MAIN ) ;
2017-01-19 16:23:59 -05:00
}
// Code below is not necessary when performing unit tests!
2017-07-03 09:40:32 -04:00
if ( Loader : : IsPerformingUnitTests ( ) ) return ;
2017-01-19 16:23:59 -05:00
// External console
2022-10-15 16:31:16 -04:00
if ( Flags : : HasFlag ( " stdout " ) )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
Utils : : Hook ( 0x4B2080 , StdOutPrint , HOOK_JUMP ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x43D570 , StdOutError , HOOK_JUMP ) . install ( ) - > quick ( ) ;
2017-01-19 16:23:59 -05:00
}
else if ( Flags : : HasFlag ( " console " ) | | ZoneBuilder : : IsEnabled ( ) ) // ZoneBuilder uses the game's console, until the native one is adapted.
{
Utils : : Hook : : Nop ( 0x60BB58 , 11 ) ;
// Redirect input (]command)
Utils : : Hook ( 0x47025A , 0x4F5770 , HOOK_CALL ) . install ( ) - > quick ( ) ;
2022-10-15 16:31:16 -04:00
Utils : : Hook ( 0x60BB68 , [ ]
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
ShowAsyncConsole ( ) ;
2017-01-19 16:23:59 -05:00
} , HOOK_CALL ) . install ( ) - > quick ( ) ;
2022-12-31 09:03:33 -05:00
Utils : : Hook ( 0x4D69A2 , [ ]
2017-01-25 09:00:05 -05:00
{
2022-12-31 09:03:33 -05:00
SetSkipShutdown ( ) ;
2017-01-25 09:00:05 -05:00
// Sys_DestroyConsole
Utils : : Hook : : Call < void ( ) > ( 0x4528A0 ) ( ) ;
2022-12-31 09:03:33 -05:00
if ( ConsoleThread . joinable ( ) )
2017-01-25 09:00:05 -05:00
{
2022-12-31 09:03:33 -05:00
ConsoleThread . join ( ) ;
2017-01-25 09:00:05 -05:00
}
} , HOOK_CALL ) . install ( ) - > quick ( ) ;
2022-05-06 19:15:58 -04:00
Scheduler : : Loop ( [ ]
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
LastRefresh = Game : : Sys_Milliseconds ( ) ;
2022-05-05 10:03:14 -04:00
} , Scheduler : : Pipeline : : MAIN ) ;
2017-01-19 16:23:59 -05:00
}
2022-12-31 09:03:33 -05:00
else if ( Dedicated : : IsEnabled ( ) )
2017-01-19 16:23:59 -05:00
{
2017-06-04 04:35:45 -04:00
DWORD type = GetFileType ( GetStdHandle ( STD_INPUT_HANDLE ) ) ;
if ( type ! = FILE_TYPE_CHAR )
{
MessageBoxA ( nullptr , " Console not supported, please use '-stdout' or '-console' flag! " , " ERRROR " , MB_ICONERROR ) ;
TerminateProcess ( GetCurrentProcess ( ) , 1 ) ;
}
2017-01-19 16:23:59 -05:00
Utils : : Hook : : Nop ( 0x60BB58 , 11 ) ;
2022-12-31 09:03:33 -05:00
Utils : : Hook ( 0x4305E0 , Create , HOOK_JUMP ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x4528A0 , Destroy , HOOK_JUMP ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x4B2080 , Print , HOOK_JUMP ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x43D570 , Error , HOOK_JUMP ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x4859A5 , Input , HOOK_CALL ) . install ( ) - > quick ( ) ;
2017-01-19 16:23:59 -05:00
}
2017-07-03 09:40:32 -04:00
else if ( ! Loader : : IsPerformingUnitTests ( ) )
2017-01-19 16:23:59 -05:00
{
FreeConsole ( ) ;
}
}
Console : : ~ Console ( )
{
2022-12-31 09:03:33 -05:00
SetSkipShutdown ( ) ;
if ( ConsoleThread . joinable ( ) )
2017-01-19 16:23:59 -05:00
{
2022-12-31 09:03:33 -05:00
ConsoleThread . join ( ) ;
2017-01-19 16:23:59 -05:00
}
}
}