/*------------------------------------------------------------------------- LobbyApp.cpp Implementation of the lobby Owner: Copyright 1986-2000 Microsoft Corporation, All Rights Reserved *-----------------------------------------------------------------------*/ #include "pch.h" #include #include ALLOC_MSG_LIST; CLobbyApp * g_pLobbyApp = NULL; #ifdef USEAUTH void CLobbyApp::OnSQLErrorRecord(SSERRORINFO * perror, OLECHAR * postrError) { // don't make the event an error event, because this may or may not be fatal. // But we certainly want to see them all in any case. m_plas->LogEvent(EVENTLOG_WARNING_TYPE, LE_DatabaseError, perror->pwszMessage, perror->pwszProcedure, perror->lNative, perror->wLineNumber, postrError); } #endif /*------------------------------------------------------------------------- * CLobbyApp.ProcessMsgPump *------------------------------------------------------------------------- Purpose: Process all thread messages, which so far are only sql query completion notifications Returns: Whether we received a WM_QUIT */ bool CLobbyApp::ProcessMsgPump() { static CTimer timerMsgPump("in message pump", 0.1f); timerMsgPump.Start(); bool fQuit = false; // Process the message queue, if any messages were received MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // dispatch Windows Messages to allow for the admin tool's COM to work TranslateMessage(&msg); switch (msg.message) { #ifdef USEAUTH case wm_sql_querydone: { CSQLQuery * pQuery = (CSQLQuery *) msg.lParam; pQuery->DataReady(); break; } #endif case WM_QUIT: fQuit = true; break; default: DispatchMessage(&msg); } } timerMsgPump.Stop(); return fQuit; } void CLobbyApp::SetConstantGameInfo() { ZGameServerInfoMsg* gameInfo = GetGameServerInfoMsg(); gameInfo->protocolSignature = zGameInfoSignature; gameInfo->protocolVersion = zGameInfoCurrentProtocolVersion; gameInfo->numEntries = 1; ZGameServerInfoMsgEndian(gameInfo); lstrcpy(gameInfo->info[0].gameInternalName, m_fFreeLobby ? "sOBLI_xx_x02" : "sOBLI_xx_x01"); lstrcpy(gameInfo->info[0].gameFriendlyName, "Allegiance"); lstrcpy(gameInfo->info[0].gameRoomDescription, "Am I supposed to put something here?"); lstrcpy(gameInfo->info[0].setupToken, ""); //reserverd for internal Zone use } void CLobbyApp::SetVariableGameInfo() { ZGameServerInfoMsg* gameInfo = GetGameServerInfoMsg(); gameInfo->info[0].order = 1; //used for Web page order of view gameInfo->info[0].blobsize = 0; //reserved for internal Zone use gameInfo->info[0].maxPopulation = 1000; gameInfo->info[0].gameAddr = 0; //inet_addr ("127.0.0/1"); gameInfo->info[0].gamePort = 0; //2803; //your game port gameInfo->info[0].serviceType = GAMEINFO_SERVICE_TYPE_GAME; gameInfo->info[0].gameState = zGameStateActive; gameInfo->info[0].gameVersion = 1; SYSTEMTIME systime; GetSystemTime (&systime); SystemTimeToFileTime(&systime, &(gameInfo->info[0].timeGameStart)); gameInfo->info[0].numPlayers = m_pCounters->cPlayersMissions + m_pCounters->cPlayersLobby; gameInfo->info[0].numSysops = 0; gameInfo->info[0].numNotPlaying = m_pCounters->cPlayersLobby; gameInfo->info[0].numGamesServed = m_pCounters->cMissions; // relies on perf counters being updated // can not use interlocked calls here b/c they're 16bit values gameInfo->info[0].numTables = 1; gameInfo->info[0].numTablesInUse = 1; } void CLobbyApp::SendGameInfo() { ZGameServerInfoMsg* gameInfo = GetGameServerInfoMsg(); SetVariableGameInfo(); //must do endian so both side of network get correct numbers //gameinfo used on Unix and Intel boxes //note that doing this once will invalidate all numbers //so set gameInfo->info[0] numbers again. ZGameInstanceInfoMsgEndian( gameInfo->info ); //send, usually to many Ip addresses which are Zone Web servers, so add this to your game configuration for (int i = 0; i < m_cReportServers; i++) ZGameInfoSendTo(m_rgulIP[i], 2000, GetGameServerInfoMsg(), sizeof(m_GameInfoBuf)); } CLobbyApp::CLobbyApp(ILobbyAppSite * plas) : m_plas(plas), m_fmServers(&m_psiteServer), m_fmClients(&m_psiteClient), m_cReportServers(0), m_sGameInfoInterval(0), // doesn't really matter, but... m_fProtocol(true) #ifdef USEAUTH , m_csqlSilentThreads(0), m_csqlNotifyThreads(0), m_sql(this) #endif { assert(m_plas); m_plas->LogEvent(EVENTLOG_INFORMATION_TYPE, LE_Creating); #ifdef USEAUTH m_strSQLConfig.Empty(); #endif // see if we're setup to report to any web servers HKEY hk; if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, HKLM_AllLobby, 0, "", REG_OPTION_NON_VOLATILE, KEY_READ, NULL, &hk, NULL) == ERROR_SUCCESS) { // read AutoUpdate portion of registry char szServers[c_cReportServersMax * 17]; bool bSuccess = _Module.ReadFromRegistry(hk, true, "GameInfoServers", szServers, 0, true); if(bSuccess) { char * token; token = strtok((char *) szServers, " "); while(token) { unsigned long ip = inet_addr(token); if (INADDR_NONE == ip) // then try to resolve by name { HOSTENT * phe = gethostbyname(token); if (phe) ip = * (unsigned long *) phe->h_addr_list[0]; } if (INADDR_NONE != ip) m_rgulIP[m_cReportServers++] = ip; else m_plas->LogEvent(EVENTLOG_INFORMATION_TYPE, LE_BadGameInfoSrv, token); token = strtok(NULL, " "); } bSuccess = _Module.ReadFromRegistry(hk, false, "GameInfoInterval", &m_sGameInfoInterval, 25); } m_szToken[0] = '\0'; bSuccess = _Module.ReadFromRegistry(hk, true, "Token", m_szToken, NULL); DWORD dwProtocol; bSuccess = _Module.ReadFromRegistry(hk, false, "fProtocol", &dwProtocol, (unsigned long) true); m_fProtocol = !!dwProtocol; DWORD dwFreeLobby; bSuccess = _Module.ReadFromRegistry(hk, false, "fFreeLobby", &dwFreeLobby, (unsigned long) #ifdef USEAUTH false #else true #endif ); m_fFreeLobby = !!dwFreeLobby; DWORD dwCheckKey; bSuccess = _Module.ReadFromRegistry(hk, false, "fCheckCDKey", &dwCheckKey, (unsigned long) #ifdef USEAUTH true #else false #endif ); m_fCheckCDKey = !!dwCheckKey; #ifdef USEAUTH bSuccess = _Module.ReadFromRegistry(hk, false, "SQLThreadsNotify", &m_csqlNotifyThreads, (unsigned long) 5); bSuccess = _Module.ReadFromRegistry(hk, false, "SQLThreadsSilent", &m_csqlSilentThreads, (unsigned long) 1); if (FAILED(LoadRegString(hk, "SQLConfig", m_strSQLConfig))) { m_strSQLConfig.Empty(); _Module.LogEvent(EVENTLOG_ERROR_TYPE, LE_RegStrMissingNoDef, "SQLConfig"); } #endif } g_pLobbyApp = this; // stuff for reporting population to zone WSAData data; WSAStartup(MAKEWORD(1,0),&data); //initialize UDP send API ZGameInfoInit(0); //initialize structure SetConstantGameInfo(); // if zone club lobby #ifdef USEAUTH m_pzas = CreateZoneAuthServer(); #endif } CLobbyApp::~CLobbyApp() { m_plas->LogEvent(EVENTLOG_INFORMATION_TYPE, LE_ShuttingDown); m_pzas = NULL; m_perfshare.FreeCounters(m_pCounters); ZGameInfoClose(); WSACleanup(); } HRESULT CLobbyApp::Init() { HRESULT hr = E_FAIL; m_plas->LogEvent(EVENTLOG_INFORMATION_TYPE, LE_Initializing); ZVerify(m_perfshare.Initialize()); m_pCounters = (LOBBY_COUNTERS *)m_perfshare.AllocateCounters( "AllLobby", "0", // if there are ever multiple lobbies running, change this sizeof(LOBBY_COUNTERS)); ZeroMemory(m_pCounters, sizeof(LOBBY_COUNTERS)); #ifdef USEAUTH hr = m_sql.Init(m_strSQLConfig.m_str, GetCurrentThreadId(), m_csqlSilentThreads, m_csqlNotifyThreads); if (FAILED(hr)) { m_plas->LogEvent(EVENTLOG_ERROR_TYPE, LE_SQLInitFailed); return hr; } #endif // TODO: Make keep-alives an option if (FAILED(hr = m_fmServers.HostSession(m_fFreeLobby ? FEDFREELOBBYSERVERS_GUID : FEDLOBBYSERVERS_GUID, false, 0, m_fProtocol)) || FAILED(hr = m_fmClients.HostSession(m_fFreeLobby ? FEDFREELOBBYCLIENTS_GUID : FEDLOBBYCLIENTS_GUID, true, 0, m_fProtocol))) { m_plas->LogEvent(EVENTLOG_ERROR_TYPE, LE_HostSessionFailure); return hr; } // // Read Registry // DWORD dw; // Gets result of whether it opened or created... HKEY hk; if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, HKLM_AllLobby, 0, "", REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &hk, &dw) == ERROR_SUCCESS) { // read AutoUpdate portion of registry DWORD dwWantAutoDownload; bool bSuccess = _Module.ReadFromRegistry(hk, false, "AutoUpdateActive", &dwWantAutoDownload, 0); if(bSuccess && dwWantAutoDownload) { char szFileName[MAX_PATH+16]; strcpy(szFileName, _Module.GetModulePath()); strcat(szFileName, "FileList.txt"); CreateAutoUpdate(hk, szFileName); } else g_pAutoUpdate = NULL; RegCloseKey(hk); } return hr; } void CLobbyApp::UpdatePerfCounters() { static CTempTimer timerPerfCounters("assembling perf info", .05f); timerPerfCounters.Start(); m_fmClients.GetSendQueue(&(m_pCounters->cOutboundQueueLength), &(m_pCounters->cOutboundQueueSize)); m_fmClients.GetReceiveQueue(&(m_pCounters->cInboundQueueLength), &(m_pCounters->cInboundQueueSize)); m_pCounters->cPlayersLobby = m_fmClients.GetCountConnections(); m_pCounters->cServers = m_fmServers.GetConnectionCount(); // Get all the per server stuff, and agregate the count of missions and players ListConnections::Iterator iterCnxn(*m_fmServers.GetConnections()); int cMissions = 0; DWORD cPlayers = 0; while (!iterCnxn.End()) { CFLServer * pServerT = CFLServer::FromConnection(*iterCnxn.Value()); cMissions += (pServerT->GetCounters()->cMissions = pServerT->GetMissions()->GetCount()); cPlayers += (pServerT->GetCounters()->cPlayers = pServerT->GetPlayerCount()); pServerT->GetCounters()->percentLoad = pServerT->GetPercentLoad(); iterCnxn.Next(); } m_pCounters->cMissions = cMissions; m_pCounters->cPlayersMissions = cPlayers; timerPerfCounters.Stop(); } void CLobbyApp::RollCall() { ListConnections::Iterator iterCnxn(*m_fmServers.GetConnections()); while (!iterCnxn.End()) { CFMConnection & cnxn = *iterCnxn.Value(); iterCnxn.Next(); // have to move iterator FIRST, because we might kill the current node CFLServer * pServerT = CFLServer::FromConnection(cnxn); if (pServerT->GetHere()) { // not combining ifs, since order would matter if (cnxn.IncAbsentCount() > 4) // dead--nuke 'em { m_plas->LogEvent(EVENTLOG_WARNING_TYPE, LE_ServerMissedRollCall, cnxn.GetName()); m_fmServers.DeleteConnection(cnxn); } } } } int CLobbyApp::Run() { const DWORD c_dwUpdateInterval = 200; // milliseconds DWORD dwSleep = c_dwUpdateInterval; DWORD dwWait = WAIT_TIMEOUT; m_plas->LogEvent(EVENTLOG_INFORMATION_TYPE, LE_Running); _putts("---------Press Q to exit---------"); printf("Ready for clients/servers.\n"); CTempTimer timerIterations("between iterations", .25f); timerIterations.Start(); CTempTimer timerReceiveClientsMessages("in clients ReceiveMessages()", .05f); CTempTimer timerReceiveServersMessages("in servers ReceiveMessages()", .05f); Time timeLastQueueCheck = Time::Now(); Time timeLastGameInfo = Time::Now(); while (true) { timerIterations.Stop(); timerIterations.Start(); if (ProcessMsgPump() || (_kbhit() && toupper(_getch()) == 'Q')) return 0; SetNow(); m_pCounters->timeInnerLoop = timerIterations.LastInterval(); // receive any messages in the queue timerReceiveClientsMessages.Start(); m_fmClients.ReceiveMessages(); timerReceiveClientsMessages.Stop(); timerReceiveServersMessages.Start(); m_fmServers.ReceiveMessages(); timerReceiveServersMessages.Stop(); if (GetNow() - timeLastQueueCheck >= 1.0f) { // count the fairly expensive stuff no more than once a second UpdatePerfCounters(); timeLastQueueCheck = GetNow(); if (GetNow() - timeLastGameInfo >= (float) m_sGameInfoInterval) { SendGameInfo(); timeLastGameInfo = GetNow(); } // Do a periodic roll call. If we haven't heard from anyone for two roll calls in a row, waste 'em static Time timeRollCall = Time::Now(); if (GetNow() - timeRollCall >= 5.0f) { RollCall(); timeRollCall = GetNow(); } } Sleep(1); } return 0; } int CLobbyApp::OnMessageBox(const char * strText, const char * strCaption, UINT nType) { char sz[256]; if (strCaption && *strCaption) { lstrcpy(sz, strCaption); lstrcat(sz, ": "); } lstrcat(sz, strText); return m_plas->LogEvent(EVENTLOG_ERROR_TYPE, LE_ODBC_Error, strText); } PER_SERVER_COUNTERS * CLobbyApp::AllocatePerServerCounters(const char * szServername) { PER_SERVER_COUNTERS * pPerServerCounters = (PER_SERVER_COUNTERS *) m_perfshare.AllocateCounters((CHAR *) "AllLobbyPerServer", (CHAR*) szServername, sizeof(PER_SERVER_COUNTERS)); ZeroMemory(pPerServerCounters, sizeof(*pPerServerCounters)); return pPerServerCounters; } bool CLobbyApp::OnAssert(const char* psz, const char* pszFile, int line, const char* pszModule) { m_plas->LogEvent(EVENTLOG_ERROR_TYPE, LE_Assert, ZString("'") + psz + "' (" + pszFile + ":" + ZString(line) + ")\n" ); return true; } void CLobbyApp::DebugOutput(const char *psz) { ::OutputDebugString("AllLobby: "); #ifdef _DEBUG Win32App::DebugOutput(psz); #endif } void CLobbyApp::BootPlayersByName(const ZString& strName) { PlayerByName::iterator iterPlayer = m_playerByName.find(strName); // if we think that player is already logged on... while (iterPlayer != m_playerByName.end() && (*iterPlayer).first == strName) { // boot all old copies CFLMission * pMissionOld = (*(*iterPlayer).second).second.GetMission(); BEGIN_PFM_CREATE(m_fmServers, pfmRemovePlayer, L, REMOVE_PLAYER) FM_VAR_PARM((PCC)strName, CB_ZTS) FM_VAR_PARM(NULL, 0) END_PFM_CREATE pfmRemovePlayer->dwMissionCookie = pMissionOld->GetCookie(); pfmRemovePlayer->reason = RPR_duplicateName; m_fmServers.SendMessages(pMissionOld->GetServer()->GetConnection(), FM_GUARANTEED, FM_FLUSH); ++iterPlayer; } } bool CLobbyApp::BootPlayersByCDKey(const ZString& strCDKey, const ZString& strNameExclude, ZString& strOldPlayer) { PlayerByCDKey::iterator iterPlayerByCDKey = m_playerByCDKey.find(strCDKey); bool bBootedSomeone = false; // if we think that player is already logged on... while (iterPlayerByCDKey != m_playerByCDKey.end() && (*iterPlayerByCDKey).first == strCDKey) { // boot all old copies if ((*iterPlayerByCDKey).second.GetName() != strNameExclude) { CFLMission * pMissionOld = (*iterPlayerByCDKey).second.GetMission(); BEGIN_PFM_CREATE(m_fmServers, pfmRemovePlayer, L, REMOVE_PLAYER) FM_VAR_PARM((PCC)(*iterPlayerByCDKey).second.GetName(), CB_ZTS) FM_VAR_PARM((PCC)(strNameExclude), CB_ZTS) END_PFM_CREATE pfmRemovePlayer->dwMissionCookie = pMissionOld->GetCookie(); pfmRemovePlayer->reason = RPR_duplicateCDKey; m_fmServers.SendMessages(pMissionOld->GetServer()->GetConnection(), FM_GUARANTEED, FM_FLUSH); // note: only returns the name of the last player we booted. strOldPlayer = (*iterPlayerByCDKey).second.GetName(); bBootedSomeone = true; } ++iterPlayerByCDKey; } return bBootedSomeone; } void CLobbyApp::SetPlayerMission(const char* szPlayerName, const char* szCDKey, CFLMission* pMission) { ZString strPlayerName = szPlayerName; ZString strCDKey = szCDKey; // boot any old copies of this player #ifdef USEAUTH BootPlayersByName(strPlayerName); #endif if (EnforceCDKey()) { // make sure the key requested is valid (since we can't guarantee that // they reported the correct value to the server). /* // we don't have any "instant" way to validate keys, and it's not worth it (now) to go back to db if (!CDKeyIsValid(szCDKey)) { BEGIN_PFM_CREATE(m_fmServers, pfmRemovePlayer, L, REMOVE_PLAYER) FM_VAR_PARM(szPlayerName, CB_ZTS) END_PFM_CREATE pfmRemovePlayer->dwMissionCookie = pMission->GetCookie(); pfmRemovePlayer->reason = RPR_duplicateCDKey; m_fmServers.SendMessages(pMission->GetServer()->GetConnection(), FM_GUARANTEED, FM_FLUSH); GetSite()->LogEvent(EVENTLOG_WARNING_TYPE, LE_BadCDKey, szCDKey, pMission->GetServer()->GetConnection()->GetName(), szPlayerName); } else */ ZString strOldPlayer; if (BootPlayersByCDKey(strCDKey, szPlayerName, strOldPlayer)) { BEGIN_PFM_CREATE(m_fmServers, pfmRemovePlayer, L, REMOVE_PLAYER) FM_VAR_PARM(szPlayerName, CB_ZTS) FM_VAR_PARM((PCC)strOldPlayer, CB_ZTS) END_PFM_CREATE pfmRemovePlayer->dwMissionCookie = pMission->GetCookie(); pfmRemovePlayer->reason = RPR_duplicateCDKey; m_fmServers.SendMessages(pMission->GetServer()->GetConnection(), FM_GUARANTEED, FM_FLUSH); } } // create a new player by creating entries in the maps PlayerByCDKey::iterator iterPlayerByCDKey = m_playerByCDKey.insert(PlayerByCDKey::value_type(strCDKey, PlayerLocInfo(strPlayerName, pMission))); m_playerByName.insert(PlayerByName::value_type(strPlayerName, iterPlayerByCDKey)); pMission->AddPlayer(); } void CLobbyApp::RemovePlayerFromMission(const char* szPlayerName, CFLMission* pMission) { ZString strPlayerName = szPlayerName; PlayerByName::iterator iterPlayer = m_playerByName.find(strPlayerName); // we better have found the player if(iterPlayer != m_playerByName.end()) { // find the entry coresponding to this mission while (iterPlayer != m_playerByName.end() && (*iterPlayer).first == strPlayerName) { if ((*(*iterPlayer).second).second.GetMission() == pMission) { pMission->RemovePlayer(); // delete the player from the maps m_playerByCDKey.erase((*iterPlayer).second); m_playerByName.erase(iterPlayer); return; } ++iterPlayer; } } m_plas->LogEvent(EVENTLOG_WARNING_TYPE, LE_CantRemovePlayer, szPlayerName, pMission->GetCookie()); } void CLobbyApp::RemoveAllPlayersFromMission(CFLMission* pMission) { // remove all players playing in this mission if (pMission->GetPlayerCount() != 0) { // REVIEW: O(AllPlayers). We could do this in O(ln(AllPlayers)) by // storing a set of players on each mission, but that requires extra // work for the common case (inserting or removing players) to speed // up the uncommon case (removing a server with players). // loop through all of the players PlayerByName::iterator iterPlayer = m_playerByName.begin(); while (iterPlayer != m_playerByName.end()) { // if this player was playing in the mission to be deleted... if ((*(*iterPlayer).second).second.GetMission() == pMission) { pMission->RemovePlayer(); // delete the player from the maps m_playerByCDKey.erase((*iterPlayer).second); iterPlayer = m_playerByName.erase(iterPlayer); } else ++iterPlayer; } assert(pMission->GetPlayerCount() == 0); } } void CLobbyApp::RemoveAllPlayersFromServer(CFLServer* pServer) { // remove all players playing in this server if (pServer->GetPlayerCount() != 0) { // REVIEW: O(AllPlayers). We could do this in O(ln(AllPlayers)) by // storing a set of players on each mission, but that requires extra // work for the common case (inserting or removing players) to speed // up the uncommon case (removing a server with players). // loop through all of the players PlayerByName::iterator iterPlayer = m_playerByName.begin(); while (iterPlayer != m_playerByName.end()) { // if this player was playing in the mission to be deleted... if ((*(*iterPlayer).second).second.GetMission()->GetServer() == pServer) { (*(*iterPlayer).second).second.GetMission()->RemovePlayer(); // delete the player from the map m_playerByCDKey.erase((*iterPlayer).second); iterPlayer = m_playerByName.erase(iterPlayer); } else ++iterPlayer; } assert(pServer->GetPlayerCount() == 0); } } CFLMission* CLobbyApp::FindPlayersMission(const char* szPlayerName) { PlayerByName::iterator iterPlayerName = m_playerByName.find(szPlayerName); if (iterPlayerName != m_playerByName.end()) { CFLMission* pmission = (*(*iterPlayerName).second).second.GetMission(); return pmission->GetServer()->GetPaused() ? NULL : pmission; } else return NULL; } bool CLobbyApp::StringCmpLess::operator () (const ZString& str1, const ZString& str2) const { // comparing lengths first is faster than a strcmp and still creates a strong ordering int nLength1 = str1.GetLength(); int nLength2 = str2.GetLength(); if (nLength1 == nLength2) return memcmp((PCC)str1, (PCC)str2, nLength1) < 0; else return nLength1 < nLength2; }; bool CLobbyApp::StringICmpLess::operator () (const ZString& str1, const ZString& str2) const { // comparing lengths first is faster than a strcmp and still creates a strong ordering int nLength1 = str1.GetLength(); int nLength2 = str2.GetLength(); if (nLength1 == nLength2) return _stricmp(str1, str2) < 0; else return nLength1 < nLength2; };