/* ** 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 . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : Command & Conquer * * * * $Archive:: /Commando/Code/Commando/bandwidthcheck.cpp $* * * * $Author:: Bhayes $* * * * $Modtime:: 3/06/02 11:48a $* * * * $Revision:: 25 $* * * * * *---------------------------------------------------------------------------------------------* * * * * *---------------------------------------------------------------------------------------------* * * * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* ** Disable warning about exception handling not being enabled. */ #pragma warning(disable : 4530) #include "always.h" #include #include "systimer.h" #include #include "bandwidthcheck.h" #include "autostart.h" #include "registry.h" #include "_globals.h" #include "useroptions.h" #include "translatedb.h" #include "string_ids.h" #include "consolemode.h" #include "specialbuilds.h" #include "gamespyadmin.h" #include /* ** Class statics. */ BandwidthCheckerClass::BandwidthCheckerThreadClass BandwidthCheckerClass::Thread; HANDLE BandwidthCheckerClass::EventNotify = NULL; unsigned long BandwidthCheckerClass::UpstreamBandwidth = 0; unsigned long BandwidthCheckerClass::ReportedUpstreamBandwidth = 0; unsigned short *BandwidthCheckerClass::UpstreamBandwidthString = NULL; unsigned long BandwidthCheckerClass::DownstreamBandwidth = 0; unsigned long BandwidthCheckerClass::ReportedDownstreamBandwidth = 0; unsigned short *BandwidthCheckerClass::DownstreamBandwidthString = NULL; int BandwidthCheckerClass::FailureCode = BANDTEST_OK; bool BandwidthCheckerClass::GotBandwidth = false; const char *BandwidthCheckerClass::DefaultServerName = "www.westwood.com"; /* ** Possible error codes from the bandwidth test. */ char *BandwidthCheckerClass::ErrorList[13] = { "BANDTEST_OK", "BANDTEST_NO_WINSOCK2", "BANDTEST_NO_RAW_SOCKET_PERMISSION", "BANDTEST_NO_RAW_SOCKET_CREATE", "BANDTEST_NO_UDP_SOCKET_BIND", "BANDTEST_NO_TTL_SET", "BANDTEST_NO_PING_RESPONSE", "BANDTEST_NO_FINAL_PING_TIME", "BANDTEST_NO_EXTERNAL_ROUTER", "BANDTEST_NO_IP_DETECT", "BANDTEST_UNKNOWN_ERROR", "BANDTEST_WRONG_API_VERSION", "BANDTEST_BAD_PARAM" }; #define NUM_BANDS 12 #ifdef FREEDEDICATEDSERVER #define DEFAULT_BAND 9 #else //FREEDEDICATEDSERVER #define DEFAULT_BAND 3 #endif //FREEDEDICATEDSERVER /* ** Lower and upper limits for each level of bandwidth. */ unsigned long BandwidthCheckerClass::Bandwidths[NUM_BANDS * 2] = { 12000, 14400, 28000, 28800, 29999, 33600, 53000, 57600, 62000, 67200, 105000, 115200, 125000, 128000, 250000, 256000, 500000, 512000, 999999, 1024000, 1999999, 2048000, 3999999, 4096000 }; /* ** Human readable names for each bandwidth level. */ unsigned short *BandwidthCheckerClass::BandwidthNames [NUM_BANDS+1] = { L"14400", L"28800", L"33600", L"57600", L"67200", L"115200", L"128k", L"256k", L"512k", L"1M", L"2M", L"4M", L"> 4M" }; /*********************************************************************************************** * BandwidthCheckerClass::Detect -- Create the bandwidth detect wait object * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:53PM ST : Created * *=============================================================================================*/ RefPtr BandwidthCheckerClass::Detect(void) { return(BandwidthDetectWait::Create()); } /*********************************************************************************************** * Start the bandwidth detection thread * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:53PM ST : Created * *=============================================================================================*/ void BandwidthCheckerClass::Check_Now(HANDLE event) { EventNotify = event; /* ** If the thread didn't finish for some reason then we need to take action. ** This will stall the dialogs but at least it won't crash. */ if (Thread.Is_Running()) { unsigned long timeout = 10 * 1000; unsigned long time = TIMEGETTIME(); while (Thread.Is_Running() && (TIMEGETTIME() - time) < timeout) { Sleep(1); } } if (Thread.Is_Running()) { Thread.Stop(2000); } WWASSERT(!Thread.Is_Running()); Thread.Execute(); } /*********************************************************************************************** * BandwidthCheckerClass::Get_Ping_Server_Name -- Get the name of a server to ping * * * * * * * * INPUT: Nothing * * * * OUTPUT: Ptr to server name * * * * WARNINGS: None * * * * HISTORY: * * 11/27/2001 1:17PM ST : Created * *=============================================================================================*/ const char *BandwidthCheckerClass::Get_Ping_Server_Name(void) { static char server_name_copy[128]; DynamicVectorClass list; const char *server_name = DefaultServerName; /* ** See if there are ping servers in the registry from a previous run. */ RegistryClass reg(APPLICATION_SUB_KEY_NAME_SERVER_LIST); WWASSERT(list.Count() == 0); reg.Get_Value_List(list); if (list.Count() > 0) { /* ** Get average and lowest ping server ping time. */ int num_times = 0; unsigned long total = 0; int lowest = 0xffff; int lowest_index = -1; for (int i=0 ; i 0 && time < 0xffff) { total += (unsigned long) time; num_times++; if (time < lowest) { lowest = time; lowest_index = i; } } } if (num_times) { int average_time = (int) (total / (unsigned) num_times); /* ** Pick one a little closer. */ average_time = average_time / 2; /* ** Find the server closest to the average time. */ int closest_index = -1; int closest_diff = 0x7fffffff; for (i=0 ; i 0 && time < 0xffff) { int diff = abs(time - average_time); if (diff < closest_diff) { /* ** Ignore the nearest server. */ if (i != lowest_index) { closest_diff = diff; closest_index = i; } } } } if (closest_index != -1) { WWASSERT(closest_index >= 0); WWASSERT(closest_index < list.Count()); strncpy(server_name_copy, list[closest_index].Peek_Buffer(), sizeof(server_name_copy)); server_name_copy[sizeof(server_name_copy) - 1] = 0; server_name = (const char*) server_name_copy; } } } return(server_name); } /*********************************************************************************************** * BandwidthCheckerClass::Check -- Check bandwidth. This is called from bandwidth thread. * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:54PM ST : Created * *=============================================================================================*/ void BandwidthCheckerClass::Check(void) { struct hostent *host; struct sockaddr_in address; int failure_code; /* ** If we are auto starting then just use the previous settings from the registry. */ if (AutoRestart.Is_Active()) { RegistryClass reg(APPLICATION_SUB_KEY_NAME_BANDTEST); int up = reg.Get_Int("Up", 0); int down = reg.Get_Int("Down", up); UpstreamBandwidth = up; DownstreamBandwidth = down; if (up) { GotBandwidth = true; SetEvent(EventNotify); return; } } ConsoleBox.Print("Detecting bandwidth...\n"); const char *host_name = NULL; if (cGameSpyAdmin::Is_Gamespy_Game()) { // US West Ping server host_name = "159.153.192.10"; } else { host_name = Get_Ping_Server_Name(); } WWDEBUG_SAY(("BandwidthCheckerClass::Check -- Trying server %s\n", host_name)); host = gethostbyname(host_name); if (host == NULL) { host_name = DefaultServerName; WWDEBUG_SAY(("BandwidthCheckerClass::Check -- Trying server %s\n", host_name)); host = gethostbyname(host_name); } if (host == NULL) { /* ** No DNS or no connection at all. Either way we are in trouble. */ WWDEBUG_SAY(("BandwidthCheckerClass - Unable to resolve host name\n")); ConsoleBox.Print("Unable to resolve host name for bandwidth check\n"); #ifdef FREEDEDICATEDSERVER UpstreamBandwidth = 1000000; DownstreamBandwidth = 1000000; ReportedUpstreamBandwidth = 1000000; ReportedDownstreamBandwidth = 1000000; #else //FREEDEDICATEDSERVER UpstreamBandwidth = 55000; DownstreamBandwidth = 55000; ReportedUpstreamBandwidth = 57600; ReportedDownstreamBandwidth = 57600; #endif //FREEDEDICATEDSERVER UpstreamBandwidthString = BandwidthNames[DEFAULT_BAND]; DownstreamBandwidthString = BandwidthNames[DEFAULT_BAND]; FailureCode = BANDTEST_NO_IP_DETECT; GotBandwidth = false; } else { memcpy(&(address.sin_addr), host->h_addr, host->h_length); /* ** Init the settings for the detection. */ BandtestSettingsStruct settings = { 0, //AlwaysICMP 0, //TTLScatter 50, //FastPingPackets 12, //SlowPingPackets 25, //Fast ping threshold 0 //PingProfile }; /* ** Call the .dll function to do the actual detection. */ UpstreamBandwidth = Detect_Bandwidth(ntohl(address.sin_addr.s_addr), 0, 2, failure_code, DownstreamBandwidth, BANDTEST_API_VERSION, &settings, APPLICATION_SUB_KEY_NAME_BANDTEST); /* ** If we failed due to a missing final ping then try again with fewer packets and no retries. */ if (UpstreamBandwidth == 0) { if (failure_code == BANDTEST_NO_FINAL_PING_TIME || (cGameSpyAdmin::Is_Gamespy_Game() && failure_code == BANDTEST_NO_EXTERNAL_ROUTER)) { BandtestSettingsStruct settings = { 0, //AlwaysICMP 0, //TTLScatter 25, //FastPingPackets 8, //SlowPingPackets 25, //Fast ping threshold 0 //PingProfile }; if (cGameSpyAdmin::Is_Gamespy_Game()) { // US East Ping server address.sin_addr.s_addr = inet_addr("159.153.224.10"); } UpstreamBandwidth = Detect_Bandwidth(ntohl(address.sin_addr.s_addr), 0, 0, failure_code, DownstreamBandwidth, BANDTEST_API_VERSION, &settings); } } /* ** If it's 0, we failed. */ if (UpstreamBandwidth == 0) { WWDEBUG_SAY(("Failed to get bandwidth - error code %s\n", ErrorList[failure_code])); /* ** Default to 57600. */ #ifdef FREEDEDICATEDSERVER ConsoleBox.Print("Failed to get bandwidth - error code %s, Setting 1Mbps bandwidth\n", ErrorList[failure_code]); UpstreamBandwidth = 1000000; DownstreamBandwidth = 1000000; #else //FREEDEDICATEDSERVER if (cGameSpyAdmin::Is_Gamespy_Game()) { UpstreamBandwidth = 128000; DownstreamBandwidth = 128000; } else { UpstreamBandwidth = 55000; DownstreamBandwidth = 55000; } #endif //FREEDEDICATEDSERVER FailureCode = failure_code; GotBandwidth = true; //false; } else { GotBandwidth = true; } /* ** Fix up the upstream bandwidth into one of our connection type bands. */ if (UpstreamBandwidth > 0x7fffffff) { WWDEBUG_SAY(("Upstream bandwidth is huge :-)\n")); WWDEBUG_SAY(("Reported upstream connection bandwidth is > 4M bits per second\n")); UpstreamBandwidth = 4096000; ReportedUpstreamBandwidth = 4096000; UpstreamBandwidthString = BandwidthNames[NUM_BANDS]; } else { if (UpstreamBandwidth > 100000) { #ifdef WWDEBUG float floater = (float)UpstreamBandwidth / 1024; #endif //WWDEBUG WWDEBUG_SAY(("Upstream bandwidth to external router is %.1f kilobits per second\n", floater)); } else { WWDEBUG_SAY(("Upstream bandwidth to external router is %d bits per second\n", UpstreamBandwidth)); } bool got_bw_str = false; for (int i=0 ; i 4M bits per second\n")); ReportedUpstreamBandwidth = 4096000; UpstreamBandwidthString = BandwidthNames[NUM_BANDS]; } StringClass little_string; WideStringClass(UpstreamBandwidthString, true).Convert_To(little_string); ConsoleBox.Print("Upstream bandwidth of %s bps detected\n", little_string.Peek_Buffer()); } /* ** Fix up the downstream bandwidth into one of our connection type bands. */ if (DownstreamBandwidth > 0x7fffffff) { WWDEBUG_SAY(("Downstream bandwidth is huge :-)\n")); WWDEBUG_SAY(("Reported downstream connection bandwidth is > 4M bits per second\n")); DownstreamBandwidth = 4096000; ReportedDownstreamBandwidth = 4096000; DownstreamBandwidthString = BandwidthNames[NUM_BANDS]; } else { if (DownstreamBandwidth > 100000) { #ifdef WWDEBUG float floater = (float)DownstreamBandwidth / 1024; #endif //WWDEBUG WWDEBUG_SAY(("Downstream bandwidth to external router is %.1f kilobits per second\n", floater)); } else { WWDEBUG_SAY(("Downstream bandwidth to external router is %d bits per second\n", DownstreamBandwidth)); } bool got_bw_str = false; for (int i=0 ; i 4M bits per second\n")); ReportedDownstreamBandwidth = 4096000; DownstreamBandwidthString = BandwidthNames[NUM_BANDS]; } } /* ** Testy. */ #ifdef _DEBUG PackedBandwidthType packed = Get_Packed_Bandwidth(); WWDEBUG_SAY(("Packed bandwidth as string = %s\n", Get_Bandwidth_As_String(packed))); #endif //_DEBUG } SetEvent(EventNotify); } /*********************************************************************************************** * BandwidthCheckerClass::Force_Upstream_Bandwidth -- Set actual upstream bandwidth * * * * * * * * INPUT: Upstream bandwidth in buts per second * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:54PM ST : Created * *=============================================================================================*/ void BandwidthCheckerClass::Force_Upstream_Bandwidth(unsigned int up) { WWASSERT(up); WWASSERT(cGameSpyAdmin::Is_Gamespy_Game()); UpstreamBandwidth = up; } /*********************************************************************************************** * BandwidthCheckerClass::Get_Upstream_Bandwidth -- Get actual upstream bandwidth * * * * * * * * INPUT: Nothing * * * * OUTPUT: Upstream bandwidth in buts per second * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:54PM ST : Created * *=============================================================================================*/ unsigned long BandwidthCheckerClass::Get_Upstream_Bandwidth(void) { return(UpstreamBandwidth); } /*********************************************************************************************** * BandwidthCheckerClass::Get_Reported_Upstream_Bandwidth -- Get reported up bandwidth * * * * * * * * INPUT: Nothing * * * * OUTPUT: Reported upstream bandwidth in bits per second * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:55PM ST : Created * *=============================================================================================*/ unsigned long BandwidthCheckerClass::Get_Reported_Upstream_Bandwidth(void) { return(ReportedUpstreamBandwidth); } /*********************************************************************************************** * BandwidthCheckerClass::Get_Upstream_Bandwidth_As_String -- Human readable upsream bw * * * * * * * * INPUT: Nothing * * * * OUTPUT: Ptr to bandwidth string * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:56PM ST : Created * *=============================================================================================*/ unsigned short *BandwidthCheckerClass::Get_Upstream_Bandwidth_As_String(void) { return(UpstreamBandwidthString); } /*********************************************************************************************** * BandwidthCheckerClass::Get_Downstream_Bandwidth -- Get actual downstream bandwidth * * * * * * * * INPUT: Nothing * * * * OUTPUT: Actual down bw in bits per second * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:56PM ST : Created * *=============================================================================================*/ unsigned long BandwidthCheckerClass::Get_Downstream_Bandwidth(void) { return(DownstreamBandwidth); } /*********************************************************************************************** * BandwidthCheckerClass::Get_Reported_Downstream_Bandwidth -- Get reported down bw * * * * * * * * INPUT: Nothing * * * * OUTPUT: Reported down bw in bits per second * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:57PM ST : Created * *=============================================================================================*/ unsigned long BandwidthCheckerClass::Get_Reported_Downstream_Bandwidth(void) { return(ReportedDownstreamBandwidth); } /*********************************************************************************************** * BandwidthCheckerClass::Get_Downstream_Bandwidth_As_String -- Get down bw as a string * * * * * * * * INPUT: Nothing * * * * OUTPUT: Ptr to down bw string * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:57PM ST : Created * *=============================================================================================*/ unsigned short *BandwidthCheckerClass::Get_Downstream_Bandwidth_As_String(void) { return(DownstreamBandwidthString); } /*********************************************************************************************** * BandwidthCheckerClass::Get_Bandwidth_As_String -- Get bandwidth description string * * * * * * * * INPUT: Nothing * * * * OUTPUT: Ptr to string * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:58PM ST : Created * *=============================================================================================*/ unsigned short *BandwidthCheckerClass::Get_Bandwidth_As_String(void) { if (cUserOptions::Get_Bandwidth_Type() == BANDWIDTH_AUTO) { static unsigned short _build_string[256]; swprintf(_build_string, L"%s,%s", DownstreamBandwidthString, UpstreamBandwidthString); return(_build_string); } else { return((unsigned short*)cBandwidth::Get_Bandwidth_String_From_Type( (BANDWIDTH_TYPE_ENUM)cUserOptions::Get_Bandwidth_Type())); } } /*********************************************************************************************** * BandwidthCheckerClass::Get_Bandwidth_As_String -- Get bandwidth description string * * * * * * * * INPUT: Packed up/down bandwidth * * * * OUTPUT: Ptr to bandwidth description string * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:58PM ST : Created * *=============================================================================================*/ unsigned short *BandwidthCheckerClass::Get_Bandwidth_As_String(PackedBandwidthType bandwidth) { static unsigned short _build_string[256]; assert(bandwidth.Bandwidth.Up < NUM_BANDS + 1); assert(bandwidth.Bandwidth.Down < NUM_BANDS + 1); swprintf(_build_string, L"%s,%s", BandwidthNames[bandwidth.Bandwidth.Down], BandwidthNames[bandwidth.Bandwidth.Up]); return(_build_string); } /*********************************************************************************************** * BandwidthCheckerClass::Get_Packed_Bandwidth -- Get bandwidth packed into a byte * * * * * * * * INPUT: Nothing * * * * OUTPUT: Bandwidth as a byte * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 2:59PM ST : Created * *=============================================================================================*/ BandwidthCheckerClass::PackedBandwidthType BandwidthCheckerClass::Get_Packed_Bandwidth(void) { PackedBandwidthType bandwidth = {0,0}; assert(sizeof(bandwidth) == 1); unsigned long bwu = ReportedUpstreamBandwidth; unsigned long bwd = ReportedDownstreamBandwidth; bool automode = true; if (cUserOptions::Get_Bandwidth_Type() != BANDWIDTH_AUTO) { bwu = cBandwidth::Get_Bandwidth_Bps_From_Type((BANDWIDTH_TYPE_ENUM)cUserOptions::Get_Bandwidth_Type()); bwd = bwu; automode = false; } /* ** Just return 0s if we haven't detected bandwidth yet. */ if (!automode || UpstreamBandwidth != 0) { for (int i=0 ; i BandwidthDetectWait::Create(void) { return (new BandwidthDetectWait()); } /*********************************************************************************************** * BandwidthDetectWait::BandwidthDetectWait -- BandwidthDetectWait constructor * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 3:22PM ST : Created * *=============================================================================================*/ BandwidthDetectWait::BandwidthDetectWait(void) : SingleWait(TRANSLATE (IDS_MENU_TESTING_BANDWIDTH), 60000), mEvent(NULL), mPingsRemaining(0xffffffff) { if (!cGameSpyAdmin::Is_Gamespy_Game()) { WOLSession = WWOnline::Session::GetInstance(false); assert(WOLSession.IsValid()); } } /*********************************************************************************************** * BandwidthDetectWait::~BandwidthDetectWait -- BandwidthDetectWait destructor * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 3:22PM ST : Created * *=============================================================================================*/ BandwidthDetectWait::~BandwidthDetectWait() { WWDEBUG_SAY(("BandwidthDetectWait: End - %S\n", mEndText)); if (WOLSession.IsValid()) WOLSession->EnablePinging(true); if (mEvent) { CloseHandle(mEvent); } } /*********************************************************************************************** * BandwidthDetectWait::WaitBeginning -- Called to init the wait * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 3:22PM ST : Created * *=============================================================================================*/ void BandwidthDetectWait::WaitBeginning(void) { WWDEBUG_SAY(("BandwidthDetectWait: Beginning\n")); mEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (mEvent == NULL) { WWDEBUG_SAY(("BandwidthDetectWait: Can't create event\n")); EndWait(Error, TRANSLATE (IDS_MENU_FAILED_TO_CREATE_BW_EVENT)); } else { if (WOLSession.IsValid()) WOLSession->EnablePinging(false); mTimeout = 15000; } } /*********************************************************************************************** * BandwidthDetectWait::GetResult -- See if there is a result for the wait condition * * * * * * * * INPUT: Nothing * * * * OUTPUT: Result code * * * * WARNINGS: None * * * * HISTORY: * * 11/21/2001 3:23PM ST : Created * *=============================================================================================*/ WaitCondition::WaitResult BandwidthDetectWait::GetResult(void) { if (mEndResult == Waiting) { /* ** Wait for pings to finish first. */ unsigned int pingsWaiting = 0; if (!cGameSpyAdmin::Is_Gamespy_Game()) { pingsWaiting = WOLSession->GetPendingPingCount(); } if (mPingsRemaining != pingsWaiting) { mPingsRemaining = pingsWaiting; if (mPingsRemaining == 0) { mTimeout = 60000; BandwidthCheckerClass::Check_Now(mEvent); } } if (mPingsRemaining == 0) { DWORD result = WaitForSingleObject(mEvent, 0); if (result == WAIT_OBJECT_0) { WWDEBUG_SAY(("BandwidthDetectWait: ConditionMet\n")); EndWait(ConditionMet, TRANSLATE (IDS_MENU_BW_DETECTION_COMPLETE)); } else { if (result == WAIT_FAILED) { WWDEBUG_SAY(("BandwidthDetectWait: WAIT_FAILED\n")); EndWait(Error, TRANSLATE (IDS_MENU_BW_DETECTION_FAILED)); } } } } if (mEndResult != Waiting && WOLSession.IsValid()) { WOLSession->EnablePinging(true); } return(mEndResult); }