/* ** 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/ServerSettings.cpp $* * * * $Author:: Steve_t $* * * * $Modtime:: 8/09/02 2:12p $* * * * $Revision:: 17 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "ServerSettings.h" #include "slavemaster.h" #include "wwdebug.h" #include "gamedata.h" #include "gdcnc.h" #include "ini.h" #include "registry.h" #include "rawfile.h" #include "consolemode.h" #include "specialbuilds.h" #include "_globals.h" #include "bandwidth.h" #include "mpsettingsmgr.h" #include "wwonline\wolserver.h" #include "useroptions.h" #include "gamespyadmin.h" #include "servercontrol.h" #include "gamesideservercontrol.h" #include "autostart.h" #include "GameSpy_QnR.h" #include "bandwidthcheck.h" const char *ConfigSettingsName = "Config"; const char *MasterServerSection = "Server"; const char *SlaveServerSection = "Slave"; #define ENCRYPTION_STRING_LENGTH 128 #define KEY_SLAVE_SERIAL "Serial" char ServerSettingsClass::SettingsFile[MAX_PATH]; bool ServerSettingsClass::IsActive = false; char ServerSettingsClass::MasterPassword[128]; ServerSettingsClass::GameModeTypeEnum ServerSettingsClass::GameMode = MODE_NONE; unsigned long ServerSettingsClass::MasterBandwidth = 0; char ServerSettingsClass::PreferredLoginServer[256]; int ServerSettingsClass::DiskLogSize = -1; const char *ServerListTag = "Available Westwood Servers:"; const char *ServerListEnd = "; End generated section."; /*********************************************************************************************** * ServerSettingsClass::Set_Settings_File_Name -- Set the name of the settings file * * * * * * * * INPUT: File name * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 2/1/2002 12:07PM ST : Created * *=============================================================================================*/ void ServerSettingsClass::Set_Settings_File_Name(char *filename) { if (strlen(filename) < sizeof(SettingsFile)) { strcpy(SettingsFile, filename); IsActive = true; } } /*********************************************************************************************** * ServerSettingsClass::Parse -- Pull the server info out of the settings file * * * * * * * * INPUT: True if we should apply the settings. False to parse for errors only. * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 2/1/2002 12:07PM ST : Created * *=============================================================================================*/ bool ServerSettingsClass::Parse(bool apply) { char master_settings[MAX_PATH]; char slave_settings[MAX_PATH]; char slave_section[256]; char slave_nick[128]; char slave_serial[128]; char slave_pass[128]; int slave_port; int slave_bw; char master_nick[128]; char master_serial[128]; int master_port; int master_bw; char master_pass[128]; char remote_admin_pass[128]; char game_type[32]; char heartbeat_list[512]; bool wol = true; char remote_admin_ip[128]; MasterPassword[0] = 0; /* ** IsActive is set when the settigns file name is set. */ if (IsActive) { WWASSERT(The_Game() || !apply); if (apply) { WWDEBUG_SAY(("Applying server settings\n")); ConsoleBox.Print("Applying server settings\n"); } /* ** Make sure the server config file is there. It should be since it's verified at command line parsing time. */ RawFileClass file(SettingsFile); if (!file.Is_Available()) { WWDEBUG_SAY(("Server startup file '%s' not found\n", SettingsFile)); ConsoleBox.Print("Error - server startup file '%s' not found - aborting\n", SettingsFile); ConsoleBox.Wait_For_Keypress(); return(false); } /* ** Get the name of the master server settings file. */ INIClass ini(file); ini.Get_String(MasterServerSection, ConfigSettingsName, "", master_settings, sizeof(master_settings)); if (strlen(master_settings) == 0) { if (apply) { WWDEBUG_SAY(("No master server settings specified - using defaults\n")); ConsoleBox.Print("No master server settings specified - using defaults\n"); } } /* ** Load the master server settings from the ini file. */ /* ** Game Type. */ ini.Get_String(MasterServerSection, "GameType", "WOL", game_type, sizeof(game_type)); if (cGameSpyAdmin::Get_Is_Server_Gamespy_Listed()) { strcpy(game_type, "GameSpy"); } if (stricmp(game_type, "WOL") == 0) { GameMode = MODE_WOL; } else if (stricmp(game_type, "LAN") == 0) { wol = false; GameMode = MODE_LAN; } else if (stricmp(game_type, "GameSpy") == 0) { wol = false; cGameSpyAdmin::Set_Is_Server_Gamespy_Listed(true); GameSpyQnR.Enable_Reporting(true); GameMode = MODE_GAMESPY; } else { WWDEBUG_SAY(("Bad game type specified in server.ini\n")); ConsoleBox.Print("Error - Unknown game type in server settings. Use 'LAN', 'WOL', or 'GameSpy' - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } /* ** Parse the list of GameSpy Style Master servers from the HeartBeat List */ heartbeat_list[sizeof(heartbeat_list)-1] = 0; ini.Get_String(MasterServerSection, "HeartBeatServers", GameSpyQnR.Get_Default_HeartBeat_List(), heartbeat_list, sizeof(heartbeat_list)-1); if (!GameSpyQnR.Parse_HeartBeat_List(heartbeat_list)) { GameSpyQnR.Parse_HeartBeat_List(GameSpyQnR.Get_Default_HeartBeat_List()); } /* ** Make sure the master server settings file is there. */ char filename[MAX_PATH]; sprintf(filename, "data\\%s", master_settings); file.Set_Name(filename); if (!file.Is_Available()) { WWDEBUG_SAY(("Server settings file '%s' not found\n", filename)); ConsoleBox.Print("Error - server settings file '%s' not found - aborting\n", filename); ConsoleBox.Wait_For_Keypress();; return(false); } else { if (!Check_Game_Settings_File(master_settings)) { WWDEBUG_SAY(("Server settings file '%s' not usable\n", master_settings)); ConsoleBox.Print("Error - server settings file '%s' contains errors - aborting\n", master_settings); ConsoleBox.Wait_For_Keypress();; return(false); } if (apply && The_Game()) { The_Game()->Set_Ini_Filename(master_settings); } } /* ** Restart Flag */ RegistryClass restart_reg(APPLICATION_SUB_KEY_NAME_WOLSETTINGS); if (restart_reg.Is_Valid ()) { restart_reg.Set_Int(AutoRestartClass::REG_VALUE_AUTO_RESTART_FLAG, 1); switch (GameMode) { case MODE_WOL: restart_reg.Set_Int(AutoRestartClass::REG_VALUE_AUTO_RESTART_TYPE, 1); break; case MODE_LAN: case MODE_GAMESPY: restart_reg.Set_Int(AutoRestartClass::REG_VALUE_AUTO_RESTART_TYPE, 0); break; } } /* ** Nickname. */ ini.Get_String(MasterServerSection, "Nickname", "", master_nick, sizeof(master_nick)); #ifdef FREEDEDICATEDSERVER /* ** We only need to validate this for the FDS. The regular game can allow the login name to be specified in the registry. */ if (wol && strlen(master_nick) == 0) { WWDEBUG_SAY(("Error - No login nickname specified for master server - aborting\n")); ConsoleBox.Print("Error - No login nickname specified for master server - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } #endif //FREEDEDICATEDSERVER /* ** Password. */ ini.Get_String(MasterServerSection, "Password", "", master_pass, sizeof(master_pass)); #ifdef FREEDEDICATEDSERVER /* ** We only need to validate this for the FDS. The regular game can allow the login name to be specified in the registry. */ if (wol && strlen(master_pass) == 0) { WWDEBUG_SAY(("Error - No login password specified for master server - aborting\n")); ConsoleBox.Print("Error - No login password specified for master server - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } #endif //FREEDEDICATEDSERVER /* ** Serial number. */ ini.Get_String(MasterServerSection, "Serial", "", master_serial, sizeof(master_serial)); #ifdef FREEDEDICATEDSERVER /* ** We only need to validate the serial number if we are the FDS. For the regular game, the master serial will be stored ** in the registry. */ if (wol && strlen(master_serial) == 0) { WWDEBUG_SAY(("Error - No serial number specified for master server - aborting\n")); ConsoleBox.Print("Error - No serial number specified for master server - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } #endif //FREEDEDICATEDSERVER /* ** Get the port number. */ master_port = ini.Get_Int(MasterServerSection, "Port", 0xffffffff); if (wol && master_port != 0xffffffff && (master_port < 0 || master_port > 0xffff)) { WWDEBUG_SAY(("Error - Invalid port number %d specified for master server - aborting\n", master_port)); ConsoleBox.Print("Error - Invalid port number %d specified for master server - aborting\n", master_port); ConsoleBox.Wait_For_Keypress(); return(false); } /* ** Get the GameSpy Query port number. */ int gsqport = cUserOptions::GameSpyQueryPort.Get(); gsqport = ini.Get_Int(MasterServerSection, "GameSpyQueryPort", gsqport); if (!gsqport) gsqport = cUserOptions::GameSpyQueryPort.Get(); if (gsqport < 0 || gsqport > 0xffff) { WWDEBUG_SAY(("Error - Invalid port number %d specified for GameSpy Query Port - aborting\n", gsqport)); ConsoleBox.Print("Error - Invalid port number %d specified for GameSpy Query Port - aborting\n", gsqport); ConsoleBox.Wait_For_Keypress();; return(false); } cUserOptions::GameSpyQueryPort.Set(gsqport); /* ** Get the GameSpy Game port number. */ int gsgport = cUserOptions::GameSpyGamePort.Get(); gsgport = ini.Get_Int(MasterServerSection, "GameSpyGamePort", gsgport); if (!gsgport) gsgport = cUserOptions::GameSpyGamePort.Get(); if (gsgport < 0 || gsgport > 0xffff || gsgport == gsqport) { WWDEBUG_SAY(("Error - Invalid port number %d specified for GameSpy Game Port - aborting\n", gsgport)); ConsoleBox.Print("Error - Invalid port number %d specified for GameSpy Game Port - aborting\n", gsgport); ConsoleBox.Wait_For_Keypress();; return(false); } cUserOptions::GameSpyGamePort.Set(gsgport); /* ** Get the bandwidth allowance. */ master_bw = ini.Get_Int(MasterServerSection, "BandwidthUp", 0xffffffff); if (master_bw != 0 && master_bw != 0xffffffff && master_bw < 33600) { WWDEBUG_SAY(("Error - Insufficient bandwidth specified for master server - aborting\n")); ConsoleBox.Print("Error - Insufficient bandwidth specified for master server - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } MasterBandwidth = master_bw; /* ** Get the max size of the disk log in days. */ DiskLogSize = ini.Get_Int(MasterServerSection, "DiskLogSize", 7); if (DiskLogSize > 365) { WWDEBUG_SAY(("Error - Disk log size too large - aborting\n")); ConsoleBox.Print("Error - Disk log size too large - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } /* ** Get the preferred login server. No preference means use default from ping profile. */ ini.Get_String(MasterServerSection, "LoginServer", "", PreferredLoginServer, sizeof(PreferredLoginServer)); /* ** Get the Network Update Rate override. */ int nur = ini.Get_Int(MasterServerSection, "NetUpdateRate", 8); if (nur < 5 || nur > 30) { WWDEBUG_SAY(("Error - Bad NetUpdateRate specified - aborting\n")); ConsoleBox.Print("Error - NetUpdateRate must be between 5 and 30 - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } cUserOptions::NetUpdateRate.Set(nur); /* ** Get the remote admin settings. */ bool allow_remote = ini.Get_Bool(MasterServerSection, "AllowRemoteAdmin", false); RegistryClass reg_remote(APPLICATION_SUB_KEY_NAME_NET_SERVER_CONTROL); if (allow_remote) { ini.Get_String(MasterServerSection, "RemoteAdminPassword", "", remote_admin_pass, sizeof(remote_admin_pass)); int len = strlen(remote_admin_pass); if (len == 0) { ConsoleBox.Print("Error - Remote admin password must be specified - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } else { if (len > 31) { ConsoleBox.Print("Error - Remote admin password too long - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } } int admin_port = ini.Get_Int(MasterServerSection, "RemoteAdminPort", 0); if (admin_port != 0 && (admin_port < 1024 || admin_port > 65535-8)) { ConsoleBox.Print("Error - Remote admin port number out of range - aborting\n"); ConsoleBox.Wait_For_Keypress();; return(false); } ServerControl.Allow_Remote_Admin(true); /* ** Set the port number into the registry. */ if (admin_port == 0) { admin_port = DEFAULT_SERVER_CONTROL_PORT; } reg_remote.Set_Int(SERVER_CONTROL_PORT_KEY, admin_port); /* ** Set the password into the registry. */ reg_remote.Set_String(SERVER_CONTROL_PASSWORD_KEY, remote_admin_pass); /* ** We need to bind to more than just the loopback address when listening for control messages. */ reg_remote.Set_Int(SERVER_CONTROL_LOOPBACK_KEY, 0); /* ** There may be an IP override specified. */ ini.Get_String(MasterServerSection, "RemoteAdminIP", "0.0.0.0", remote_admin_ip, sizeof(remote_admin_ip)); unsigned long admin_ip_long = ntohl(inet_addr(remote_admin_ip)); reg_remote.Set_Int(SERVER_CONTROL_IP_KEY, admin_ip_long); } else { /* ** Only listen to control messages on the loopback address. */ reg_remote.Set_Int(SERVER_CONTROL_LOOPBACK_KEY, 1); ServerControl.Allow_Remote_Admin(false); } /* ** Set the master settings into the registry. */ //if (apply) { /* ** Serial number. */ if (wol) { if (strlen(master_serial)) { StringClass serial(master_serial, true); StringClass encrypted_serial; Encrypt_Serial(serial, encrypted_serial); RegistryClass reg_base(APPLICATION_SUB_KEY_NAME); if (reg_base.Is_Valid()) { reg_base.Set_String(KEY_SLAVE_SERIAL, encrypted_serial.Peek_Buffer()); } } /* ** Nickname. */ if (strlen(master_nick)) { RegistryClass reg_wol(APPLICATION_SUB_KEY_NAME_WOLSETTINGS); if (reg_wol.Is_Valid()) { reg_wol.Set_String("AutoLogin", master_nick); reg_wol.Set_String("LastLogin", master_nick); reg_wol.Set_Int("AutoLoginPrompt", 0); MPSettingsMgrClass::Set_Auto_Login(master_nick); } } /* ** Password. */ MPSettingsMgrClass::Set_Auto_Password(master_pass); /* ** Port number. */ if (master_port != 0xffffffff) { RegistryClass reg_fw(APPLICATION_SUB_KEY_NAME_NET_FIREWALL); reg_fw.Set_Int("ForcePort", master_port); } } /* ** Bandwidth. */ if (master_bw != 0xffffffff) { RegistryClass reg_netopt(APPLICATION_SUB_KEY_NAME_NETOPTIONS); if (reg_netopt.Is_Valid()) { if (cGameSpyAdmin::Is_Gamespy_Game()) { if (master_bw == 0) master_bw = 1000000; cUserOptions::Set_Bandwidth_Bps(master_bw); } else { if (wol) { reg_netopt.Set_Int("BandwidthType", BANDWIDTH_AUTO); /* ** We want this to be set always on the first time through, but not neccessarily on the second, apply, pass. */ if (master_bw != 0 || !apply) { RegistryClass reg_bw(APPLICATION_SUB_KEY_NAME_BANDTEST); if (reg_bw.Is_Valid()) { reg_bw.Set_Int("Up", master_bw); reg_bw.Set_Int("Down", master_bw); } } } else { cUserOptions::Set_Bandwidth_Type(BANDWIDTH_LANT1); } } } } //} if (apply) { SlaveMaster.Reset(); } /* ** Read the slave server info. */ for (int i=0 ; i 0xffff) { WWDEBUG_SAY(("Error - Invalid port number %d specified for slave %d - aborting\n", slave_port, i+1)); ConsoleBox.Print("Error - Invalid port number %d specified for slave %d - aborting\n", slave_port, i+1); ConsoleBox.Wait_For_Keypress();; return(false); } /* ** Get the bandwidth allowance. */ slave_bw = ini.Get_Int(slave_section, "BandwidthUp", 0xffffffff); if (slave_bw != 0 && slave_bw != 0xffffffff && slave_bw < 33600) { WWDEBUG_SAY(("Error - Insufficient bandwidth specified for slave %d - aborting\n", i+1)); ConsoleBox.Print("Error - Insufficient bandwidth specified for slave %d - aborting\n", i+1); ConsoleBox.Wait_For_Keypress();; return(false); } } /* ** Apply the settings. */ if (apply) { SlaveMaster.Add_Slave(enabled, slave_nick, slave_serial, (unsigned short)slave_port, slave_settings, slave_bw, slave_pass); } } if (apply) { SlaveMaster.Save(); } } return(true); } /*********************************************************************************************** * ServerSettingsClass::Encrypt_Serial -- Serial number encryption/decryption * * * * * * * * INPUT: Input serial number * * Reference to output serial number * * Encrypt flag - true to encrypt, false to decrypt * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 2/1/2002 12:08PM ST : Created * *=============================================================================================*/ void ServerSettingsClass::Encrypt_Serial(StringClass serial_in, StringClass &serial_out, bool encrypt) { char *s; int numberlength = serial_in.Get_Length(); unsigned long bytesread; char stringbuffer[ENCRYPTION_STRING_LENGTH]; int p; WWASSERT(numberlength); s = new char [numberlength + 1]; memcpy(s, serial_in.Peek_Buffer(), numberlength + 1); /* ** See if the key file is available. If not, don't bother encrypting. */ HANDLE handle = CreateFile ("woldata.key", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (handle == INVALID_HANDLE_VALUE) { delete [] s; serial_out = serial_in; return; } /* ** Read the key. */ if (!ReadFile(handle, stringbuffer, sizeof (stringbuffer), &bytesread, NULL)) { WWDEBUG_SAY(("Unable to read serial encryption key file\n")); delete [] s; serial_out = serial_in; return; } int sign = encrypt ? 1 : -1; p = 0; for (unsigned i = 0; i < ENCRYPTION_STRING_LENGTH; i++) { int t; char c; t = s[p] - '0'; t %= 10; t += (sign * stringbuffer[i]); t += 1000; t %= 10; c = t + '0'; s[p] = c; p++; if (p == numberlength) { p = 0; } } serial_out = StringClass(s, true); delete [] s; } /*********************************************************************************************** * ServerSettingsClass::Decrypt_Serial -- Serial number decryption * * * * * * * * INPUT: Input serial number * * Reference to output serial number * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 2/1/2002 12:08PM ST : Created * *=============================================================================================*/ void ServerSettingsClass::Decrypt_Serial(StringClass serial_in, StringClass &serial_out) { Encrypt_Serial(serial_in, serial_out, false); } /*********************************************************************************************** * ServerSettingsClass::Get_Preferred_Server -- Get the users specified server * * * * * * * * INPUT: Server list from servserv * * * * OUTPUT: Name of preferred server * * * * WARNINGS: None * * * * HISTORY: * * 2/1/2002 1:18PM ST : Created * *=============================================================================================*/ char *ServerSettingsClass::Get_Preferred_Server(const WWOnline::IRCServerList &server_list) { if (IsActive && server_list.size()) { Write_Server_List(server_list); } return(PreferredLoginServer); } /*********************************************************************************************** * ServerSettingsClass::Write_Server_List -- Write the server list into the settings ini file * * * * * * * * INPUT: Server list * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 2/1/2002 1:19PM ST : Created * *=============================================================================================*/ void ServerSettingsClass::Write_Server_List(const WWOnline::IRCServerList &server_list) { WWASSERT(server_list.size()); WWASSERT(strlen(SettingsFile) != 0); char temp[256]; if (server_list.size()) { RawFileClass file(SettingsFile); /* ** This is a bit hacky. Basically we just want the .ini file commented with the latest server list. ** ** We will do this by reading in the file, looking for a tag that tells us where to write the server list, then writing ** the file back out with the server list in place. */ if (file.Is_Available()) { unsigned long size = file.Size(); if (size) { /* ** Read the file. */ char *file_buffer = new char[size + 8192]; #ifdef WWDEBUG unsigned long read_size = #endif //WWDEBUG file.Read(file_buffer, size); WWASSERT(read_size == size); file.Close(); /* ** Find the placeholder tag. */ char *tag = strstr(file_buffer, ServerListTag); char *end_tag = strstr(file_buffer, ServerListEnd); if (tag && end_tag) { /* ** Write out the first portion of the file unchanged. */ file.Open(FileClass::WRITE); file.Write(file_buffer, (tag - file_buffer) + strlen(ServerListTag)); /* ** Insert the server list. */ char *server_list_text = new char [8192]; strcpy(server_list_text, "\r\n;\r\n"); for (unsigned int i = 0; i < server_list.size(); i++) { const RefPtr &server = server_list[i]; if (server->HasLanguageCode()) { const char *server_name = server->GetName(); sprintf(temp, "; %s\r\n", server_name); strcat(server_list_text, temp); } } strcat(server_list_text, ";\r\n"); file.Write(server_list_text, strlen(server_list_text)); /* ** Write out the post server list sections of the original file. */ file.Write(end_tag, (file_buffer + size) - end_tag); file.Close(); } } } } } /*********************************************************************************************** * ServerSettingsClass::Check_Game_Settings_File -- Check game settings for validity * * * * * * * * INPUT: Game settings .ini file name * * * * OUTPUT: True if valid. * * * * WARNINGS: None * * * * HISTORY: * * 2/3/2002 9:37PM ST : Created * *=============================================================================================*/ bool ServerSettingsClass::Check_Game_Settings_File(char *config_file) { cGameDataCnc *game_settings = (cGameDataCnc*) cGameData::Create_Game_Of_Type(cGameData::GAME_TYPE_CNC); WWASSERT(game_settings != NULL); if (game_settings) { game_settings->Set_Ini_Filename(config_file); game_settings->Load_From_Server_Config(); WideStringClass outMsg; bool ok = game_settings->Is_Valid_Settings(outMsg, true); delete game_settings; return(ok); } return(false); }