/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando * * * * $Archive:: /Commando/Code/Commando/ConsoleMode.cpp $* * * * $Author:: Bhayes $* * * * $Modtime:: 1/21/03 11:09a $* * * * $Revision:: 12 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "consolemode.h" #include "consolefunction.h" #include "wwdebug.h" #include "conio.h" #include "slavemaster.h" #include #include "systimer.h" #include "widestring.h" #include "vector3.h" #include "cnetwork.h" #include "textdisplay.h" #include "console.h" #include "crc.h" #include "buildnum.h" #include "init.h" #include "gamesideservercontrol.h" #include "specialbuilds.h" #include "serversettings.h" /* ** Single instance of console. */ ConsoleModeClass ConsoleBox; /* ** Console title bar text */ #define MASTER_TITLE_BASE "Renegade Master Server" #define SLAVE_TITLE_BASE "Renegade Slave Server" #define MASTER_COLORS (FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_BLUE) #define SLAVE_COLORS (BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE) /*********************************************************************************************** * ConsoleModeClass::ConsoleModeClass -- Class constructor * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/17/2001 5:00PM ST : Created * *=============================================================================================*/ ConsoleModeClass::ConsoleModeClass(void) { ConsoleOutputHandle = INVALID_HANDLE_VALUE; ConsoleInputHandle = INVALID_HANDLE_VALUE; LastKeypressTime = 0; Pos = 1; IsExclusive = false; LastProfileCRC = 0; LastProfilePrint = 0; ProfileMode = false; } /*********************************************************************************************** * ConsoleModeClass::~ConsoleModeClass -- Class destructor * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/17/2001 5:01PM ST : Created * *=============================================================================================*/ ConsoleModeClass::~ConsoleModeClass(void) { if (ConsoleOutputHandle != INVALID_HANDLE_VALUE) { FreeConsole(); ConsoleOutputHandle = INVALID_HANDLE_VALUE; } } /*********************************************************************************************** * ConsoleModeClass::Init -- Enable console mode - start up a console * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/17/2001 5:01PM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Init(void) { if (ConsoleOutputHandle == INVALID_HANDLE_VALUE) { /* ** Create a console. */ if (AllocConsole()) { /* ** Get the input and output handles. */ ConsoleOutputHandle = GetStdHandle(STD_OUTPUT_HANDLE); WWASSERT(ConsoleOutputHandle != INVALID_HANDLE_VALUE); ConsoleInputHandle = GetStdHandle(STD_INPUT_HANDLE); WWASSERT(ConsoleInputHandle != INVALID_HANDLE_VALUE); /* ** Set the size of the console buffer. */ COORD coord; coord.X=80; coord.Y=4192; SetConsoleScreenBufferSize(ConsoleOutputHandle, coord); unsigned long written = 0; coord.X=0; coord.Y=0; /* ** Use different colors for master and slaves. */ if (!SlaveMaster.Am_I_Slave()) { FillConsoleOutputAttribute(ConsoleOutputHandle, MASTER_COLORS, 4192*50, coord, &written); SetConsoleTextAttribute(ConsoleOutputHandle, MASTER_COLORS); } else { FillConsoleOutputAttribute(ConsoleOutputHandle, SLAVE_COLORS, 4192*50, coord, &written); SetConsoleTextAttribute(ConsoleOutputHandle, SLAVE_COLORS); } /* ** Set the text in the console title bar. */ Set_Title(NULL, NULL); /* ** Get an HWND for the console window. */ ConsoleWindow = FindWindow("ConsoleWindowClass", Title); /* ** Bring up the console window to the foreground. */ SetForegroundWindow(ConsoleWindow); /* ** Print up version info. */ DWORD version_major = 1; DWORD version_minor = 0; Get_Version_Number(&version_major, &version_minor); #ifdef FREEDEDICATEDSERVER Print("Renegade Free Dedicated Server "); #else //FREEDEDICATEDSERVER Print("Renegade "); #endif //FREEDEDICATEDSERVER Print("v%d.%.3d %s-%s %s\n", (version_major >> 16), (version_major & 0xFFFF), BuildInfoClass::Get_Builder_Initials(), BuildInfoClass::Get_Build_Number_String(), BuildInfoClass::Get_Build_Date_String()); Print("Console mode active\n"); LastKeypressTime = 0; } } } /*********************************************************************************************** * ConsoleModeClass::Get_Slave_Window_By_Title -- Look for a slave window * * * * * * * * INPUT: Login name of slave * * Settings file name of slave * * * * OUTPUT: HWND of slave window * * * * WARNINGS: None * * * * HISTORY: * * 2/4/2002 1:21PM ST : Created * *=============================================================================================*/ HWND ConsoleModeClass::Get_Slave_Window_By_Title(char *name, char *settings) { StringClass title = Compose_Window_Title(name, settings, true); HWND window = FindWindow("ConsoleWindowClass", title.Peek_Buffer()); return(window); } /*********************************************************************************************** * ConsoleModeClass::Compose_Window_Title -- Build a window title string from name and settings* * * * * * * * INPUT: Login name * * Settings file name * * * * OUTPUT: StringClass containing full title bar string * * * * WARNINGS: None * * * * HISTORY: * * 2/4/2002 1:19PM ST : Created * *=============================================================================================*/ StringClass ConsoleModeClass::Compose_Window_Title(char *name, char *settings, bool slave) { char title[256]; if (!slave) { strcpy(title, MASTER_TITLE_BASE); } else { strcpy(title, SLAVE_TITLE_BASE); } if (name) { strcat(title, " - "); strcat(title, name); } if (settings) { strcat(title, " - "); strcat(title, settings); } return(StringClass(title)); } /*********************************************************************************************** * ConsoleModeClass::Set_Title -- Sets the text in the console title bar * * * * * * * * INPUT: Base text * * Name of settings file * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/17/2001 5:03PM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Set_Title(char *name, char *settings) { if (ConsoleOutputHandle) { StringClass title = Compose_Window_Title(name, settings, SlaveMaster.Am_I_Slave()); strcpy(Title, title.Peek_Buffer()); SetConsoleTitle(Title); } } /*********************************************************************************************** * ConsoleModeClass::Print -- Formatted print to console box * * * * * * * * INPUT: string * * format specifiers * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/17/2001 5:04PM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Print(char const * string, ...) { if (ConsoleOutputHandle != INVALID_HANDLE_VALUE) { char buffer[8192]; va_list va; va_start(va, string); vsprintf(&buffer[0], string, va); va_end(va); /* ** Have to use '%s' here or we end up doing the formatting twice. */ cprintf("%s", buffer); Log_To_Disk(buffer); //WWDEBUG_SAY((buffer)); Apply_Attributes(); } } /*********************************************************************************************** * ConsoleModeClass::Print_Maybe -- Formatted print to console box if not busy * * * * * * * * INPUT: string * * format specifiers * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/17/2001 5:04PM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Print_Maybe(char const * string, ...) { if (string && !ProfileMode) { char buffer[8192]; va_list va; va_start(va, string); vsprintf(&buffer[0], string, va); va_end(va); Log_To_Disk(buffer); if (Pos == 1 && (TIMEGETTIME() - LastKeypressTime > 5 * 1000) && ConsoleOutputHandle != INVALID_HANDLE_VALUE) { /* ** Have to use '%s' here or we end up doing the formatting twice. */ cprintf("%s", buffer); //WWDEBUG_SAY((buffer)); Apply_Attributes(); } } } /*********************************************************************************************** * ConsoleModeClass::Static_Print_Maybe -- Static version of Print_Maybe * * * * * * * * INPUT: String * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 8/14/2002 11:59AM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Static_Print_Maybe(char const * string, ...) { ConsoleBox.Print_Maybe(string); } /*********************************************************************************************** * ConsoleModeClass::cprintf -- let's get all the prints going through one place again * * * * * * * * INPUT: string * * format specifiers * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/17/2001 5:04PM ST : Created * *=============================================================================================*/ void ConsoleModeClass::cprintf(char const * string, ...) { if (string) { char buffer[8192]; va_list va; buffer[sizeof(buffer)-1] = 0; va_start(va, string); _vsnprintf(&buffer[0], sizeof(buffer)-1, string, va); va_end(va); /* ** Have to use '%s' here or we end up doing the formatting twice. */ ::cprintf("%s", buffer); GameSideServerControlClass::Print("%s", buffer); } } /*********************************************************************************************** * ConsoleModeClass::Get_Log_File_Nmae -- Get name of log file * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 8/8/2002 1:07PM ST : Created * *=============================================================================================*/ const char *ConsoleModeClass::Get_Log_File_Name(void) { static char _log_file_name[256]; static int _last_day = -1; SYSTEMTIME time; GetLocalTime(&time); sprintf(_log_file_name, "renlog_%d-%d-%02d.txt", time.wMonth, time.wDay, time.wYear); if (_last_day != time.wDay && ServerSettingsClass::Get_Disk_Log_Size() != -1) { _last_day = time.wDay; FILETIME file_time; if (SystemTimeToFileTime(&time, &file_time)) { _int64 int_file_time; memcpy(&int_file_time, &file_time, sizeof(int_file_time)); _int64 time_diff = ((_int64)10000000) * ((_int64)60*60*24*ServerSettingsClass::Get_Disk_Log_Size()); int_file_time -= time_diff; memcpy(&file_time, &int_file_time, sizeof(file_time)); /* ** Find all log files. */ WIN32_FIND_DATA find_data; HANDLE find_handle = FindFirstFile("renlog_*.txt", &find_data); while (find_handle != INVALID_HANDLE_VALUE) { if (CompareFileTime(&find_data.ftLastWriteTime, &file_time) == -1) { DeleteFile(find_data.cFileName); } if (!FindNextFile(find_handle, &find_data)) { break; } } FindClose(find_handle); } } return(_log_file_name); } /*********************************************************************************************** * ConsoleModeClass::Log_To_Disk -- Log console output to disk * * * * * * * * INPUT: String to log * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 8/8/2002 12:47PM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Log_To_Disk(const char *string) { if (ConsoleOutputHandle != INVALID_HANDLE_VALUE) { if (ServerSettingsClass::Get_Disk_Log_Size() > 0) { FILE *log_file = fopen(Get_Log_File_Name(), "at"); if (log_file != NULL) { char timestr[256] = "?"; GetTimeFormat(LOCALE_SYSTEM_DEFAULT, TIME_FORCE24HOURFORMAT, NULL, "'['HH':'mm':'ss'] '", timestr, 255); fwrite(timestr, 1, strlen(timestr), log_file); fwrite(string, 1, strlen(string), log_file); fclose(log_file); } } } } /*********************************************************************************************** * ConsoleModeClass::Think -- Handles input to the console box * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/17/2001 5:04PM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Think(void) { static char string[256] = ">"; char key = 0; static char suggestion[256] = ""; static char last_suggestion[256] = ""; static char help[256] = ""; static char suggestion_stub[256]; static unsigned long last_info_time = 0; static int num_players = -1; //eh? static int delay = 100; if (ConsoleInputHandle != INVALID_HANDLE_VALUE) { /* ** See if there is a key waiting in the queue. */ if (_kbhit()) { /* ** Get the key from the queue. */ key = _getche(); if (key == 0 || key == 0xE0) { key = _getche(); } /* ** Note the time of the last keypress. */ LastKeypressTime = TIMEGETTIME(); switch (key) { /* ** TAB key used to get command line suggestions. */ case 9: { string[Pos] = 0; if (!strlen(last_suggestion)) { strcpy(suggestion_stub, string+1); } ConsoleFunctionManager::Get_Command_Suggestion(suggestion_stub, last_suggestion, suggestion, help, 256); strcpy(last_suggestion, suggestion); /* ** Save the cursor position. */ CONSOLE_SCREEN_BUFFER_INFO info; int ok = GetConsoleScreenBufferInfo(ConsoleOutputHandle, &info); /* ** Clear out the two lines below. */ cprintf("\r\n "); cprintf("\r\n "); /* ** Move the cursor back up one line to the current command prompt line. */ if (ok) { COORD new_pos = info.dwCursorPosition; new_pos.X = 0; SetConsoleCursorPosition(ConsoleOutputHandle, new_pos); } /* ** Print the suggestion on the line below. */ cprintf("\r\n%s\r", help); /* ** Move the cursor back up one line to the current command prompt line. */ if (ok) { COORD new_pos = info.dwCursorPosition; new_pos.X = 0; SetConsoleCursorPosition(ConsoleOutputHandle, new_pos); } /* ** Clear out the command line and print the suggestion. */ cprintf("\r \r>%s", suggestion); strcpy(string+1, suggestion); Pos = strlen(string); break; } /* ** Handle backspace. */ case 8: if (Pos > 1) { Pos--; last_suggestion[0] = 0; cprintf(" \b"); } else { /* ** Compensate for backspace going too far. */ if (Pos == 1) { cprintf(">"); } } break; /* ** Handle escape. */ case 27: if (ProfileMode) { StatisticsDisplayManager::Set_Display("off"); ProfileMode = false; Print("\n\n\n\n"); Print("\n\n\n\n"); Print("\n\n\n\n"); Print("\n\n\n\n"); } cprintf("\r \r>"); Pos = 1; string[0] = '>'; string[1] = 0; break; /* ** Anything else gets put into the command buffer. */ default: if (ProfileMode) { Handle_Profile_Key(key); } else { if (key == 32 || isgraph(key)) { string[Pos++] = key; last_suggestion[0] = 0; } } break; } /* ** Handle user hitting enter (13). */ if (Pos > 1 && key == 13 || Pos > 200) { string[Pos] = 0; cprintf("\r\n \r>"); /* ** Pass the command string to the console parser. */ ConsoleFunctionManager::Parse_Input(string+1); cprintf("\r\n>"); Pos = 1; string[0] = '>'; Apply_Attributes(); } } /* ** Print up game info if console is idle. */ delay--; if (delay <= 0) { delay = 100; unsigned long time = TIMEGETTIME(); /* ** Handle timer reset. */ if (time < last_info_time) { last_info_time = time; } /* ** Print if enough time has gone by. */ if (Pos == 1 && time - LastKeypressTime > 30 * 1000) { if (time - last_info_time > 60 * 1000) { if (cNetwork::PServerConnection && cNetwork::PServerConnection->Get_Num_RHosts() != num_players) { num_players = cNetwork::PServerConnection->Get_Num_RHosts(); ConsoleFunctionManager::Parse_Input("game_info"); cprintf(">"); last_info_time = time; Apply_Attributes(); } } } } } } /*********************************************************************************************** * ConsoleModeClass::Add_Message -- Print colored text to the console * * * * * * * * INPUT: Formatted wide string * * Color to print in * * Forced - set to true to print even if the user is currently typing at the console * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/17/2001 5:06PM ST : Created * *=============================================================================================*/ ConsoleModeClass::Add_Message(WideStringClass *formatted_text, Vector3 *text_color, bool forced) { if (!ProfileMode && formatted_text && text_color && (forced || (Pos == 1 && TIMEGETTIME() - LastKeypressTime > 3 * 1000))) { unsigned short color = 0; /* ** Convert the Vector3 RGB to text attribute colors. */ if (text_color->X != 0.0f) { color |= FOREGROUND_RED; if (text_color->X > 0.4f) { color |= FOREGROUND_INTENSITY; } } if (text_color->Y != 0.0f) { color |= FOREGROUND_GREEN; if (text_color->Y > 0.4f) { color |= FOREGROUND_INTENSITY; } } if (text_color->Z != 0.0f) { color |= FOREGROUND_BLUE; if (text_color->Z > 0.4f) { color |= FOREGROUND_INTENSITY; } } if (!SlaveMaster.Am_I_Slave()) { SetConsoleTextAttribute(ConsoleOutputHandle, color | BACKGROUND_BLUE); } else { SetConsoleTextAttribute(ConsoleOutputHandle, color | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE); } StringClass string(128, true); formatted_text->Convert_To(string); cprintf("%s", string.Peek_Buffer()); Log_To_Disk(string.Peek_Buffer()); if (!SlaveMaster.Am_I_Slave()) { SetConsoleTextAttribute(ConsoleOutputHandle, MASTER_COLORS); } else { SetConsoleTextAttribute(ConsoleOutputHandle, SLAVE_COLORS); } /* ** Reprint the prompt. */ //cprintf(">"); Apply_Attributes(); } } /*********************************************************************************************** * ConsoleModeClass::Apply_Attributes -- Reapply the console colors. * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 12/24/2001 1:26PM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Apply_Attributes(void) { CONSOLE_SCREEN_BUFFER_INFO info; int ok = GetConsoleScreenBufferInfo(ConsoleOutputHandle, &info); if (ok) { COORD pos = info.dwCursorPosition; unsigned long written = 0; if (!SlaveMaster.Am_I_Slave()) { FillConsoleOutputAttribute(ConsoleOutputHandle, MASTER_COLORS, 5*80, pos, &written); } else { FillConsoleOutputAttribute(ConsoleOutputHandle, SLAVE_COLORS, 5*80, pos, &written); } } } // unrecognized character escape sequence #pragma warning(disable : 4129) /*********************************************************************************************** * ConsoleModeClass::Update_Profile -- Print the profile text * * * * * * * * INPUT: Profile text * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 1/13/2002 12:56PM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Update_Profile(StringClass profile_string) { if (profile_string.Get_Length() && ProfileMode && TIMEGETTIME() - LastProfilePrint > 1500) { LastProfilePrint = TIMEGETTIME(); /* ** Get a checksum of the profile string. */ unsigned long crc = CRC::Memory((unsigned char*)profile_string.Peek_Buffer(), profile_string.Get_Length()); if (crc != LastProfileCRC) { /* ** Create a copy of the string and scan it for '%'. */ int len = profile_string.Get_Length(); char *str = (char*) alloca(len * 2); char *src = profile_string.Peek_Buffer(); char *dst = str; char c; for (int i=0 ; i= '0' && key <= '9') { char profile_text[4]; profile_text[0] = (char)key; profile_text[1] = 0; Get_Console()->Profile_Command(profile_text); cprintf("\r"); LastProfilePrint = 0; } else { if (key >= 'a' && key <= 'z') { char profile_text[4]; profile_text[0] = '1'; profile_text[1] = (char)(key - 'a') + '0'; profile_text[2] = 0; Get_Console()->Profile_Command(profile_text); cprintf("\r"); LastProfilePrint = 0; } else { if (key == '.') { Get_Console()->Profile_Command("up"); cprintf("\r"); LastProfilePrint = 0; } } } } } /*********************************************************************************************** * ConsoleModeClass::Wait_For_Keypress -- Wait for user to press a key * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 2/4/2002 11:10AM ST : Created * *=============================================================================================*/ void ConsoleModeClass::Wait_For_Keypress(void) { if (Get_Console()) { if (ConsoleInputHandle != INVALID_HANDLE_VALUE) { Print("** Press any key to continue **\n"); } _getch(); } }