/* ** Command & Conquer Generals(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) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: GameSpyGameInfo.cpp ////////////////////////////////////////////////////// // GameSpy game setup state info // Author: Matthew D. Campbell, December 2001 #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/GameEngine.h" #include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/RandomValue.h" #include "Common/Scorekeeper.h" #include "GameClient/Shell.h" #include "GameClient/GameText.h" #include "GameNetwork/GameSpy/PeerDefs.h" #include "GameNetwork/GameSpyGameInfo.h" #include "GameNetwork/NetworkInterface.h" #include "GameNetwork/NetworkUtil.h" #include "GameNetwork/NetworkDefs.h" #include "GameNetwork/NAT.h" #include "GameLogic/GameLogic.h" #include "GameLogic/VictoryConditions.h" // Singleton ------------------------------------------ GameSpyGameInfo *TheGameSpyGame = NULL; // Helper Functions ---------------------------------------- GameSpyGameSlot::GameSpyGameSlot() { GameSlot(); m_gameSpyLogin.clear(); m_gameSpyLocale.clear(); m_profileID = 0; } // Helper Functions ---------------------------------------- /* ** Function definitions for the MIB-II entry points. */ BOOL (__stdcall *SnmpExtensionInitPtr)(IN DWORD dwUpTimeReference, OUT HANDLE *phSubagentTrapEvent, OUT AsnObjectIdentifier *pFirstSupportedRegion); BOOL (__stdcall *SnmpExtensionQueryPtr)(IN BYTE bPduType, IN OUT RFC1157VarBindList *pVarBindList, OUT AsnInteger32 *pErrorStatus, OUT AsnInteger32 *pErrorIndex); LPVOID (__stdcall *SnmpUtilMemAllocPtr)(IN DWORD bytes); VOID (__stdcall *SnmpUtilMemFreePtr)(IN LPVOID pMem); typedef struct tConnInfoStruct { unsigned int State; unsigned long LocalIP; unsigned short LocalPort; unsigned long RemoteIP; unsigned short RemotePort; } ConnInfoStruct; #ifndef ARRAY_SIZE #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) #endif /*********************************************************************************************** * Get_Local_Chat_Connection_Address -- Which address are we using to talk to the chat server? * * * * * * * * INPUT: Ptr to address to return local address * * * * * OUTPUT: True if success * * * * WARNINGS: None * * * * HISTORY: * * 10/27/00 3:24PM ST : Created * *=============================================================================================*/ Bool GetLocalChatConnectionAddress(AsciiString serverName, UnsignedShort serverPort, UnsignedInt& localIP) { //return false; /* ** Local defines. */ enum { CLOSED = 1, LISTENING, SYN_SENT, SEN_RECEIVED, ESTABLISHED, FIN_WAIT, FIN_WAIT2, CLOSE_WAIT, LAST_ACK, CLOSING, TIME_WAIT, DELETE_TCB }; enum { tcpConnState = 1, tcpConnLocalAddress, tcpConnLocalPort, tcpConnRemAddress, tcpConnRemPort }; /* ** Locals. */ unsigned char serverAddress[4]; unsigned char remoteAddress[4]; HANDLE trap_handle; AsnObjectIdentifier first_supported_region; std::vector connectionVector; int last_field; int index; AsnInteger error_status; AsnInteger error_index; int conn_entry_type_index; int conn_entry_type; Bool found; /* ** Statics. */ static char _conn_state[][32] = { "?", "CLOSED", "LISTENING", "SYN_SENT", "SEN_RECEIVED", "ESTABLISHED", "FIN_WAIT", "FIN_WAIT2", "CLOSE_WAIT", "LAST_ACK", "CLOSING", "TIME_WAIT", "DELETE_TCB" }; DEBUG_LOG(("Finding local address used to talk to the chat server\n")); DEBUG_LOG(("Current chat server name is %s\n", serverName.str())); DEBUG_LOG(("Chat server port is %d\n", serverPort)); /* ** Get the address of the chat server. */ DEBUG_LOG( ("About to call gethostbyname\n")); struct hostent *host_info = gethostbyname(serverName.str()); if (!host_info) { DEBUG_LOG( ("gethostbyname failed! Error code %d\n", WSAGetLastError())); return(false); } memcpy(serverAddress, &host_info->h_addr_list[0][0], 4); unsigned long temp = *((unsigned long*)(&serverAddress[0])); temp = ntohl(temp); *((unsigned long*)(&serverAddress[0])) = temp; DEBUG_LOG(("Host address is %d.%d.%d.%d\n", serverAddress[3], serverAddress[2], serverAddress[1], serverAddress[0])); /* ** Load the MIB-II SNMP DLL. */ DEBUG_LOG(("About to load INETMIB1.DLL\n")); HINSTANCE mib_ii_dll = LoadLibrary("inetmib1.dll"); if (mib_ii_dll == NULL) { DEBUG_LOG(("Failed to load INETMIB1.DLL\n")); return(false); } DEBUG_LOG(("About to load SNMPAPI.DLL\n")); HINSTANCE snmpapi_dll = LoadLibrary("snmpapi.dll"); if (snmpapi_dll == NULL) { DEBUG_LOG(("Failed to load SNMPAPI.DLL\n")); FreeLibrary(mib_ii_dll); return(false); } /* ** Get the function pointers into the .dll */ SnmpExtensionInitPtr = (int (__stdcall *)(unsigned long,void ** ,AsnObjectIdentifier *)) GetProcAddress(mib_ii_dll, "SnmpExtensionInit"); SnmpExtensionQueryPtr = (int (__stdcall *)(unsigned char,SnmpVarBindList *,long *,long *)) GetProcAddress(mib_ii_dll, "SnmpExtensionQuery"); SnmpUtilMemAllocPtr = (void *(__stdcall *)(unsigned long)) GetProcAddress(snmpapi_dll, "SnmpUtilMemAlloc"); SnmpUtilMemFreePtr = (void (__stdcall *)(void *)) GetProcAddress(snmpapi_dll, "SnmpUtilMemFree"); if (SnmpExtensionInitPtr == NULL || SnmpExtensionQueryPtr == NULL || SnmpUtilMemAllocPtr == NULL || SnmpUtilMemFreePtr == NULL) { DEBUG_LOG(("Failed to get proc addresses for linked functions\n")); FreeLibrary(snmpapi_dll); FreeLibrary(mib_ii_dll); return(false); } RFC1157VarBindList *bind_list_ptr = (RFC1157VarBindList *) SnmpUtilMemAllocPtr(sizeof(RFC1157VarBindList)); RFC1157VarBind *bind_ptr = (RFC1157VarBind *) SnmpUtilMemAllocPtr(sizeof(RFC1157VarBind)); /* ** OK, here we go. Try to initialise the .dll */ DEBUG_LOG(("About to init INETMIB1.DLL\n")); int ok = SnmpExtensionInitPtr(GetCurrentTime(), &trap_handle, &first_supported_region); if (!ok) { /* ** Aw crap. */ DEBUG_LOG(("Failed to init the .dll\n")); SnmpUtilMemFreePtr(bind_list_ptr); SnmpUtilMemFreePtr(bind_ptr); FreeLibrary(snmpapi_dll); FreeLibrary(mib_ii_dll); return(false); } /* ** Name of mib_ii object we want to query. See RFC 1213. ** ** iso.org.dod.internet.mgmt.mib-2.tcp.tcpConnTable.TcpConnEntry.tcpConnState ** 1 3 6 1 2 1 6 13 1 1 */ unsigned int mib_ii_name[] = {1,3,6,1,2,1,6,13,1,1}; unsigned int *mib_ii_name_ptr = (unsigned int *) SnmpUtilMemAllocPtr(sizeof(mib_ii_name)); memcpy(mib_ii_name_ptr, mib_ii_name, sizeof(mib_ii_name)); /* ** Get the index of the conn entry data. */ conn_entry_type_index = ARRAY_SIZE(mib_ii_name) - 1; /* ** Set up the bind list. */ bind_ptr->name.idLength = ARRAY_SIZE(mib_ii_name); bind_ptr->name.ids = mib_ii_name; bind_list_ptr->list = bind_ptr; bind_list_ptr->len = 1; /* ** We start with the tcpConnLocalAddress field. */ last_field = 1; /* ** First connection. */ index = 0; /* ** Suck out that tcp connection info.... */ while (true) { if (!SnmpExtensionQueryPtr(SNMP_PDU_GETNEXT, bind_list_ptr, &error_status, &error_index)) { //if (!SnmpExtensionQueryPtr(ASN_RFC1157_GETNEXTREQUEST, bind_list_ptr, &error_status, &error_index)) { DEBUG_LOG(("SnmpExtensionQuery returned false\n")); SnmpUtilMemFreePtr(bind_list_ptr); SnmpUtilMemFreePtr(bind_ptr); FreeLibrary(snmpapi_dll); FreeLibrary(mib_ii_dll); return(false); } /* ** If this is something new we aren't looking for then we are done. */ if (bind_ptr->name.idLength < ARRAY_SIZE(mib_ii_name)) { break; } /* ** Get the type of info we are looking at. See RFC1213. ** ** 1 = tcpConnState ** 2 = tcpConnLocalAddress ** 3 = tcpConnLocalPort ** 4 = tcpConnRemAddress ** 5 = tcpConnRemPort ** ** tcpConnState is one of the following... ** ** 1 closed ** 2 listen ** 3 synSent ** 4 synReceived ** 5 established ** 6 finWait1 ** 7 finWait2 ** 8 closeWait ** 9 lastAck ** 10 closing ** 11 timeWait ** 12 deleteTCB */ conn_entry_type = bind_ptr->name.ids[conn_entry_type_index]; if (last_field != conn_entry_type) { index = 0; last_field = conn_entry_type; } switch (conn_entry_type) { /* ** 1. First field in the entry. Need to create a new connection info struct ** here to store this connection in. */ case tcpConnState: { ConnInfoStruct new_conn; new_conn.State = bind_ptr->value.asnValue.number; connectionVector.push_back(new_conn); break; } /* ** 2. Local address field. */ case tcpConnLocalAddress: DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index")); connectionVector[index].LocalIP = *((unsigned long*)bind_ptr->value.asnValue.address.stream); index++; break; /* ** 3. Local port field. */ case tcpConnLocalPort: DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index")); connectionVector[index].LocalPort = bind_ptr->value.asnValue.number; //connectionVector[index]->LocalPort = ntohs(connectionVector[index]->LocalPort); index++; break; /* ** 4. Remote address field. */ case tcpConnRemAddress: DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index")); connectionVector[index].RemoteIP = *((unsigned long*)bind_ptr->value.asnValue.address.stream); index++; break; /* ** 5. Remote port field. */ case tcpConnRemPort: DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index")); connectionVector[index].RemotePort = bind_ptr->value.asnValue.number; //connectionVector[index]->RemotePort = ntohs(connectionVector[index]->RemotePort); index++; break; } } SnmpUtilMemFreePtr(bind_list_ptr); SnmpUtilMemFreePtr(bind_ptr); SnmpUtilMemFreePtr(mib_ii_name_ptr); DEBUG_LOG(("Got %d connections in list, parsing...\n", connectionVector.size())); /* ** Right, we got the lot. Lets see if any of them have the same address as the chat ** server we think we are talking to. */ found = false; for (Int i=0; igetSlot(i); if (slot && slot->isOccupied()) numUsers++; } if (numUsers < 2) { if (TheGameSpyGame->amIHost()) { UnicodeString text; text.format(TheGameText->fetch("LAN:NeedMorePlayers"),numUsers); TheGameSpyInfo->addText(text, GSCOLOR_DEFAULT, NULL); } return; } TheGameSpyGame->startGame(0); } } void GameSpyLaunchGame( void ) { if (TheGameSpyGame) { // Set up the game network AsciiString user; AsciiString userList; DEBUG_ASSERTCRASH(TheNetwork == NULL, ("For some reason TheNetwork isn't NULL at the start of this game. Better look into that.")); if (TheNetwork != NULL) { delete TheNetwork; TheNetwork = NULL; } // Time to initialize TheNetwork for this game. TheNetwork = NetworkInterface::createNetwork(); TheNetwork->init(); /* if (!TheGameSpyGame->amIHost()) TheNetwork->setLocalAddress((207<<24) | (138<<16) | (47<<8) | 15, 8088); else */ TheNetwork->setLocalAddress(TheGameSpyGame->getLocalIP(), TheNAT->getSlotPort(TheGameSpyGame->getLocalSlotNum())); TheNetwork->attachTransport(TheNAT->getTransport()); user = TheGameSpyInfo->getLocalName(); for (Int i=0; igetSlot(i); if (!slot) { DEBUG_CRASH(("No GameSlot[%d]!", i)); delete TheNetwork; TheNetwork = NULL; return; } // UnsignedInt ip = htonl(slot->getIP()); UnsignedInt ip = slot->getIP(); AsciiString tmpUserName; tmpUserName.translate(slot->getName()); if (ip) { /* if (i == 1) { user.format(",%s@207.138.47.15:8088", tmpUserName.str()); } else */ { user.format(",%s@%d.%d.%d.%d:%d", tmpUserName.str(), ((ip & 0xff000000) >> 24), ((ip & 0xff0000) >> 16), ((ip & 0xff00) >> 8), ((ip & 0xff)), TheNAT->getSlotPort(i) ); } userList.concat(user); } } userList.trim(); TheNetwork->parseUserList(TheGameSpyGame); // shutdown the top, but do not pop it off the stack // TheShell->hideShell(); // setup the Global Data with the Map and Seed TheGlobalData->m_pendingFile = TheGameSpyGame->getMap(); if (TheGameLogic->isInGame()) { TheGameLogic->clearGameData(); } // send a message to the logic for a new game GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME ); msg->appendIntegerArgument(GAME_INTERNET); TheGlobalData->m_useFpsLimit = false; // Set the random seed InitGameLogicRandom( TheGameSpyGame->getSeed() ); DEBUG_LOG(("InitGameLogicRandom( %d )\n", TheGameSpyGame->getSeed())); if (TheNAT != NULL) { delete TheNAT; TheNAT = NULL; } } } void GameSpyGameInfo::init( void ) { GameInfo::init(); m_hasBeenQueried = false; } void GameSpyGameInfo::resetAccepted( void ) { GameInfo::resetAccepted(); if (m_hasBeenQueried && amIHost()) { // ANCIENTMUNKEE peerStateChanged(TheGameSpyChat->getPeer()); m_hasBeenQueried = false; DEBUG_LOG(("resetAccepted() called peerStateChange()\n")); } } Int GameSpyGameInfo::getLocalSlotNum( void ) const { DEBUG_ASSERTCRASH(m_inGame, ("Looking for local game slot while not in game")); if (!m_inGame) return -1; AsciiString localName = TheGameSpyInfo->getLocalName(); for (Int i=0; iisPlayer(localName)) return i; } return -1; } void GameSpyGameInfo::gotGOACall( void ) { DEBUG_LOG(("gotGOACall()\n")); m_hasBeenQueried = true; } void GameSpyGameInfo::startGame(Int gameID) { DEBUG_LOG(("GameSpyGameInfo::startGame - game id = %d\n", gameID)); DEBUG_ASSERTCRASH(m_transport == NULL, ("m_transport is not NULL when it should be")); DEBUG_ASSERTCRASH(TheNAT == NULL, ("TheNAT is not NULL when it should be")); // fill in GS-specific info for (Int i=0; igetPlayerInfoMap(); PlayerInfoMap::iterator it = pInfoMap->find(gsName); if (it != pInfoMap->end()) { m_GameSpySlot[i].setProfileID(it->second.m_profileID); m_GameSpySlot[i].setLocale(it->second.m_locale); } else { DEBUG_CRASH(("No player info for %s", gsName.str())); } } } if (TheNAT != NULL) { delete TheNAT; TheNAT = NULL; } TheNAT = NEW NAT(); TheNAT->attachSlotList(m_slot, getLocalSlotNum(), m_localIP); TheNAT->establishConnectionPaths(); } AsciiString GameSpyGameInfo::generateGameResultsPacket( void ) { Int i; Int endFrame = TheVictoryConditions->getEndFrame(); Int localSlotNum = getLocalSlotNum(); //GameSlot *localSlot = getSlot(localSlotNum); Bool sawGameEnd = (endFrame > 0);// && localSlot->lastFrameInGame() <= endFrame); Int winningTeam = -1; Int numPlayers = 0; Int numTeamsAtGameEnd = 0; Int lastTeamAtGameEnd = -1; for (i=0; ifindPlayerWithNameKey(NAMEKEY(playerName)); if (p) { ++numPlayers; if (TheVictoryConditions->hasAchievedVictory(p)) { winningTeam = getSlot(i)->getTeamNumber(); } // check if he lasted GameSlot *slot = getSlot(i); if (!slot->disconnected()) { if (slot->getTeamNumber() != lastTeamAtGameEnd || numTeamsAtGameEnd == 0) { lastTeamAtGameEnd = slot->getTeamNumber(); ++numTeamsAtGameEnd; } } } } AsciiString results; results.format("seed=%d,slotNum=%d,sawDesync=%d,sawGameEnd=%d,winningTeam=%d,disconEnd=%d,duration=%d,numPlayers=%d,isQM=%d", getSeed(), localSlotNum, TheNetwork->sawCRCMismatch(), sawGameEnd, winningTeam, (numTeamsAtGameEnd != 0), endFrame, numPlayers, m_isQM); Int playerID = 0; for (i=0; ifindPlayerWithNameKey(NAMEKEY(playerName)); if (p) { GameSpyGameSlot *slot = &(m_GameSpySlot[i]); ScoreKeeper *keeper = p->getScoreKeeper(); AsciiString playerName = slot->getLoginName(); Int gsPlayerID = slot->getProfileID(); AsciiString locale = slot->getLocale(); Int fps = TheNetwork->getAverageFPS(); Int unitsKilled = keeper->getTotalUnitsDestroyed(); Int unitsLost = keeper->getTotalUnitsLost(); Int unitsBuilt = keeper->getTotalUnitsBuilt(); Int buildingsKilled = keeper->getTotalBuildingsDestroyed(); Int buildingsLost = keeper->getTotalBuildingsLost(); Int buildingsBuilt = keeper->getTotalBuildingsBuilt(); Int earnings = keeper->getTotalMoneyEarned(); Int techCaptured = keeper->getTotalTechBuildingsCaptured(); Bool disconnected = slot->disconnected(); AsciiString playerStr; playerStr.format(",player%d=%s,playerID%d=%d,locale%d=%s", playerID, playerName.str(), playerID, gsPlayerID, playerID, locale.str()); results.concat(playerStr); playerStr.format(",unitsKilled%d=%d,unitsLost%d=%d,unitsBuilt%d=%d", playerID, unitsKilled, playerID, unitsLost, playerID, unitsBuilt); results.concat(playerStr); playerStr.format(",buildingsKilled%d=%d,buildingsLost%d=%d,buildingsBuilt%d=%d", playerID, buildingsKilled, playerID, buildingsLost, playerID, buildingsBuilt); results.concat(playerStr); playerStr.format(",fps%d=%d,cash%d=%d,capturedTech%d=%d,discon%d=%d", playerID, fps, playerID, earnings, playerID, techCaptured, playerID, disconnected); results.concat(playerStr); ++playerID; } } // Add a trailing size value (so the server can ensure it got the entire packet) int resultsLen = results.getLength()+10; AsciiString tail; tail.format("%10.10d", resultsLen); results.concat(tail); return results; }