#include #include "Network.h" #include "Client.h" #include "ClientEvents.h" #include "World.h" #include "Database.h" #include "Database2.h" #include "DatabaseIO.h" #include "crc.h" #include "BinaryWriter.h" #include "BinaryReader.h" #include "PacketController.h" #include "Server.h" #include "Config.h" // NOTE: A client can easily perform denial of service attacks by issuing a large number of connection requests if there ever comes a time this matters to fix CNetwork::CNetwork(class CPhatServer *server, in_addr address, WORD port) { m_server = server; m_port = port; m_addr = address; m_ServerID = 11; // arbitrary m_ClientArray = new CClient *[m_MaxClients]; memset(m_ClientArray, 0, sizeof(CClient *) * m_MaxClients); m_ClientSlots = new CClient *[m_MaxClients + 1]; memset(m_ClientSlots, 0, sizeof(CClient *) * (m_MaxClients + 1)); for (uint32_t i = 1; i <= m_MaxClients; i++) { m_OpenSlots.push_back(i); } LoadBans(); Init(); m_running = true; m_incomingThread = std::thread([&]() { IncomingThreadProc(); }); m_outgoingThread = std::thread([&]() { OutgoingThreadProc(); }); } CNetwork::~CNetwork() { m_running = false; if (m_outgoingThread.joinable()) m_outgoingThread.join(); if (m_incomingThread.joinable()) m_incomingThread.join(); Shutdown(); for (int i = 0; i < m_NumClients; i++) { SafeDelete(m_ClientArray[i]); } _queuedIncoming.clear(); _queuedOutgoing.clear(); SafeDeleteArray(m_ClientSlots); SafeDeleteArray(m_ClientArray); } void CNetwork::Init() { srand((unsigned int)time(NULL)); // #if defined(_WINDOWS) // WSADATA wsaData; // USHORT wVersionRequested = 0x0202; // WSAStartup(wVersionRequested, &wsaData); // #endif xsocket::socket_init(); // SOCKADDR_IN local_read; // local_read.sin_family = AF_INET; // local_read.sin_addr = m_addr; // m_read_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // m_write_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); WINLOG(Temp, Normal, "Binding to addr %s\n", inet_ntoa(m_addr)); if (m_read_sock.bind(m_addr, m_port)) { WINLOG(Temp, Normal, "Failed bind on recv port %u!\n", m_port); SERVER_ERROR << "Failed bind on recv port:" << m_port; } if (m_write_sock.bind(m_addr, m_port + 1)) { WINLOG(Temp, Normal, "Failed bind on send port %u!\n", m_port + 1); SERVER_ERROR << "Failed bind on send port:" << m_port + 1; } // local_read.sin_port = htons(m_port); // if (bind(m_read_sock, reinterpret_cast(&local_read), sizeof(SOCKADDR_IN))) // { // WINLOG(Temp, Normal, "Failed bind on recv port %u!\n", m_port); // SERVER_ERROR << "Failed bind on recv port:" << m_port; // } // SOCKADDR_IN local_write; // local_write.sin_family = AF_INET; // local_write.sin_addr = m_addr; // WORD sport = m_port + 1; // local_write.sin_port = htons(sport); // if (bind(m_write_sock, reinterpret_cast(&local_write), sizeof(SOCKADDR_IN))) // { // WINLOG(Temp, Normal, "Failed bind on send port %u!\n", sport); // SERVER_ERROR << "Failed bind on send port:" << sport; // } // configure for non-blocking int buffsize = g_pConfig->NetBufferSize(); m_read_sock.blocking(false); m_read_sock.buffer_size(&buffsize, &buffsize); m_write_sock.blocking(false); m_write_sock.buffer_size(&buffsize, &buffsize); // unsigned long arg = 1; // ioctlsocket(m_read_sock, FIONBIO, &arg); // setsockopt(m_read_sock, SOL_SOCKET, SO_SNDBUF, (char *)&buffsize, sizeof(buffsize)); // setsockopt(m_read_sock, SOL_SOCKET, SO_RCVBUF, (char *)&buffsize, sizeof(buffsize)); // ioctlsocket(m_write_sock, FIONBIO, &arg); // setsockopt(m_write_sock, SOL_SOCKET, SO_SNDBUF, (char *)&buffsize, sizeof(buffsize)); // setsockopt(m_write_sock, SOL_SOCKET, SO_RCVBUF, (char *)&buffsize, sizeof(buffsize)); } void CNetwork::Shutdown() { //closesocket(m_read_sock); //closesocket(m_write_sock); m_read_sock.close(); m_write_sock.close(); xsocket::socket_shutdown(); // #if defined(_WINDOWS) // WSACleanup(); // #endif } void CNetwork::IncomingThreadProc() { xsocket::socket_wait wait; while (m_running) { wait.reset(); wait.set(m_read_sock); wait.set(m_write_sock); int result = wait.wait(); if (result > 0) { if (wait.isset(m_read_sock)) QueueIncomingOnSocket(m_read_sock); if (wait.isset(m_write_sock)) QueueIncomingOnSocket(m_write_sock); } std::this_thread::yield(); } } void CNetwork::OutgoingThreadProc() { // one second delay //timeval waittime = { 1, 0 }; //fd_set fd = { 0 }; while (m_running) { { std::unique_lock sigLock(m_sigOutgoingLock); m_sigOutgoing.wait_for(sigLock, std::chrono::seconds(1)); } while (!_queuedOutgoing.empty()) { //FD_ZERO(&fd); //FD_SET(m_read_sock, &fd); //FD_SET(m_write_sock, &fd); //select(0, nullptr, &fd, nullptr, &waittime); std::scoped_lock lock(m_outgoingLock); auto entry = _queuedOutgoing.begin(); if (entry != _queuedOutgoing.end()) { CQueuedPacket& queued = *entry; //if (SendPacket(queued.useReadStream ? m_read_sock : m_write_sock, &queued.addr, queued.data.get(), queued.len)) if (SendPacket(queued.useReadStream ? m_read_sock : m_read_sock, &queued.addr, queued.data.get(), queued.len)) { _queuedOutgoing.erase(entry); } std::this_thread::yield(); } } std::this_thread::yield(); } } WORD CNetwork::GetServerID(void) { return m_ServerID; } void CNetwork::SendConnectlessBlob(SOCKADDR_IN *peer, BlobPacket_s *blob, uint32_t dwFlags, uint32_t dwSequence, WORD wTime, bool useReadStream) { BlobHeader_s *header = &blob->header; header->dwSequence = dwSequence; header->dwFlags = dwFlags; header->dwCRC = 0; header->wRecID = GetServerID(); header->wTime = wTime; header->wTable = 0x01; GenericCRC(blob); QueuePacket(peer, blob, BLOBLEN(blob), useReadStream); } bool CNetwork::SendPacket(xsocket::socket_udp socket, sockaddr_in *peer, void *data, uint32_t len) { if (!socket.valid()) { return false; } //WINLOG(Temp, Normal, "Sending to %s:%d\n", inet_ntoa(peer->sin_addr), ntohs(peer->sin_port)); //int bytesSent = sendto(socket, (char *)data, len, 0, (sockaddr *)peer, sizeof(SOCKADDR_IN)); int bytesSent = socket.sendto((uint8_t*)data, len, peer); bool success = (bytesSent > 0); if (success) { /* #ifdef _DEBUG if (false) // g_bDebugToggle) { LOG(Network, Normal, "Sent:\n"); LOG_BYTES(Network, Normal, data, len); } else { LOG(Network, Verbose, "Sent to %s:\n", inet_ntoa(peer->sin_addr)); LOG_BYTES(Network, Verbose, data, len); } #endif g_pGlobals->PacketSent(len); */ } return success; } void CNetwork::QueuePacket(SOCKADDR_IN *peer, void *data, uint32_t len, bool useReadStream) { CQueuedPacket qp; qp.addr = *peer; qp.data = std::make_unique(len); memcpy(qp.data.get(), data, len); qp.len = len; qp.useReadStream = useReadStream; { std::scoped_lock lock(m_outgoingLock); _queuedOutgoing.push_back(std::move(qp)); } m_sigOutgoing.notify_all(); } void CNetwork::QueueIncomingOnSocket(xsocket::socket_udp socket) { if (!socket.valid()) { return; } static BYTE buffer[0x1E4]; while (TRUE) { int clientaddrlen = sizeof(sockaddr_in); sockaddr_in clientaddr; // Doing it similar to AC.. //int bloblen = recvfrom(socket, (char *)buffer, 0x1E4, NULL, (sockaddr *)&clientaddr, &clientaddrlen); int bloblen = socket.recvfrom(buffer, sizeof(buffer), &clientaddr); if (bloblen <= 0) { // uint32_t dwCode = WSAGetLastError(); // if (dwCode != 10035) // { // // LOG(Temp, Normal, "Winsock Error %lu\n", dwCode); // } break; } else if (!bloblen) { break; } else if (bloblen < sizeof(BlobHeader_s)) { continue; } g_pGlobals->PacketRecv(bloblen); BlobPacket_s *blob = reinterpret_cast(buffer); WORD wSize = blob->header.wSize; WORD wRecID = blob->header.wRecID; if ((bloblen - sizeof(BlobHeader_s)) != wSize) continue; CQueuedPacket qp; memcpy(&qp.addr, &clientaddr, sizeof(sockaddr_in)); qp.data = std::make_unique(bloblen); memcpy(qp.data.get(), buffer, bloblen); qp.len = bloblen; qp.recvTime = g_pGlobals->UpdateTime(); { std::scoped_lock lock(m_incomingLock); _queuedIncoming.push_back(std::move(qp)); } // blob->header.dwCRC -= CalcTransportCRC((uint32_t *)blob); /* #ifdef _DEBUG SOCKADDR_IN addr; memset(&addr, 0, sizeof(addr)); int namelen = sizeof(addr); getsockname(socket, (sockaddr *)&addr, &namelen); if (false) // g_bDebugToggle) { LOG(Network, Normal, "Received on port %d:\n", ntohs(addr.sin_port)); LOG_BYTES(Network, Normal, &blob->header, blob->header.wSize + sizeof(blob->header)); } else { LOG(Network, Verbose, "Received on port %d:\n", ntohs(addr.sin_port)); LOG_BYTES(Network, Verbose, &blob->header, blob->header.wSize + sizeof(blob->header)); } #endif */ /* if (!wRecID) { ProcessConnectionless(&clientaddr, blob); } else { CClient *client = ValidateClient(wRecID, &clientaddr); if (client) client->IncomingBlob(blob); } */ } } void CNetwork::ProcessQueuedIncoming() { while (!_queuedIncoming.empty()) { CQueuedPacket qp; { std::scoped_lock lock(m_incomingLock); qp = std::move(_queuedIncoming.front()); _queuedIncoming.pop_front(); } BlobPacket_s *blob = (BlobPacket_s *)qp.data.get(); blob->header.dwCRC -= CalcTransportCRC((uint32_t *)blob); if (!blob->header.wRecID) { ProcessConnectionless(&qp.addr, blob); } else if (CClient *client = ValidateClient(blob->header.wRecID, &qp.addr)) { client->IncomingBlob(blob, qp.recvTime); } } } void CNetwork::LogoutAll() { for (uint32_t i = 0; i < m_NumClients; i++) { if (CClient *client = m_ClientArray[i]) { if (client->IsAlive() && client->GetEvents()) { client->GetEvents()->ForceLogout(); } } } } void CNetwork::CompleteLogoutAll() { for (uint32_t i = 0; i < m_NumClients; i++) { if (CClient *client = m_ClientArray[i]) { if (client->IsAlive() && client->GetEvents()) { //todo: send the user to the appropriate server connection lost screen. BinaryWriter EnterPortal; EnterPortal.Write(0xF751); EnterPortal.Write(0); client->SendNetMessage(EnterPortal.GetData(), EnterPortal.GetSize(), OBJECT_MSG); BinaryWriter popupString; popupString.Write(4); popupString.WriteString("The server has shutdown."); client->SendNetMessage(&popupString, PRIVATE_MSG, FALSE, FALSE); } } } } void CNetwork::Think() { ProcessQueuedIncoming(); for (uint32_t i = 0; i < m_NumClients; i++) { if (CClient *client = m_ClientArray[i]) { client->Think(); if (!client->IsAlive()) { KillClient(client->GetSlot()); } } } CClient **end = m_ClientArray + m_NumClients; #if defined(__cpp_lib_parallel_algorithm) std::for_each(std::execution::par, m_ClientArray, end, [](CClient* &client) #else std::for_each(m_ClientArray, end, [](CClient* &client) #endif { client->ThinkOutbound(); }); } CClient* CNetwork::GetClient(WORD slot) { if (!slot || slot > m_MaxClients) return NULL; return m_ClientSlots[slot]; } void CNetwork::KickClient(CClient *pClient) { if (!pClient) return; SERVER_INFO << "Client" << pClient->GetSlot() << "(" << pClient->GetAccount() << ") is being kicked."; BinaryWriter KC; KC.Write(0xF7DC); KC.Write(0); pClient->SendNetMessage(KC.GetData(), KC.GetSize(), PRIVATE_MSG); pClient->ThinkOutbound(); pClient->Kill(NULL, NULL); } void CNetwork::KickBannedClient(CClient *pClient, int32_t duration) { if (!pClient) return; // newlines to push turbine support url out of the window. std::string msg; msg += "\n\n"; msg += g_pConfig->GetBanString(); msg += "\n\n\n\n"; BinaryWriter KBC; KBC.Write(0xF7C1); KBC.Write(duration); // ban duration in seconds KBC.WriteString(msg); pClient->SendNetMessage(KBC.GetData(), KBC.GetSize(), PRIVATE_MSG); pClient->ThinkOutbound(); pClient->Kill(NULL, NULL); } void CNetwork::KickClient(WORD slot) { KickClient(GetClient(slot)); } CClient* CNetwork::ValidateClient(WORD index, sockaddr_in *peer) { CClient* pClient = GetClient(index); if (!pClient) return NULL; if (!pClient->CheckAddress(peer)) return NULL; return pClient; } void CNetwork::KillClient(WORD slot) { if (CClient *pClient = GetClient(slot)) { SERVER_INFO << "Client" << pClient->GetAccount() << "(" << inet_ntoa(pClient->GetHostAddress()->sin_addr) << ") disconnected"; uint32_t arrayPos = pClient->GetArrayPos(); delete pClient; m_NumClients--; m_ClientArray[arrayPos] = m_ClientArray[m_NumClients]; m_ClientArray[m_NumClients] = NULL; m_ClientSlots[slot] = NULL; m_OpenSlots.push_front(slot); if (m_ClientArray[arrayPos]) { m_ClientArray[arrayPos]->SetArrayPos(arrayPos); } // m_server->Stats().UpdateClientList(NULL, 0); } } CClient* CNetwork::FindClientByAccount(const char* account) { for (uint32_t i = 0; i < m_NumClients; i++) { if (CClient *client = m_ClientArray[i]) { if (client->CheckAccount(account)) return client; } } return NULL; } void CNetwork::SendConnectLoginFailure(sockaddr_in *addr, int error, const char *accountname, const char *password) { //Bad login. CREATEBLOB(BadLogin, sizeof(uint32_t) * 2); ((uint32_t *)BadLogin->data)[0] = (uint32_t)error; ((uint32_t *)BadLogin->data)[1] = ST_SERVER_ERRORS; SendConnectlessBlob(addr, BadLogin, BT_NETERROR, 0, 0, true); //SendConnectlessBlob(addr, BadLogin, BT_ERROR, 0, 0, true); DELETEBLOB(BadLogin); if (accountname) { SERVER_INFO << "Invalid login from " << inet_ntoa(addr->sin_addr) << ", used account name '" << accountname << "'"; } } void CNetwork::ConnectionRequest(sockaddr_in *addr, BlobPacket_s *p) { BinaryReader loginRequest(p->data, p->header.wSize); char *version_string = loginRequest.ReadString(); if (strncmp(version_string, "1802", 4)) { SendConnectLoginFailure(addr, STR_LOGIN_CLIENT_VERSION, nullptr, nullptr); return; } loginRequest.ReadUInt32(); // 0x20 ? uint32_t auth_method = loginRequest.ReadUInt32(); loginRequest.ReadUInt32(); // 0x0 ? uint32_t client_unix_timestamp = loginRequest.ReadUInt32(); // client unix timestamp char *login_credentials; if (auth_method != 1) // case 3 was turbine ticket method, took that out return; login_credentials = loginRequest.ReadString(); uint32_t portal_stamp = loginRequest.ReadUInt32(); uint32_t cell_stamp = loginRequest.ReadUInt32(); if (loginRequest.GetLastError()) return; char *szPassword = strstr(login_credentials, ":"); if (!szPassword) return; *(szPassword) = '\0'; szPassword++; if (!strcmp(login_credentials, "acservertracker")) { SendConnectLoginFailure(addr, STR_LOGIN_FAILED, nullptr, nullptr); return; } // Try to login to the login_credentials AccountInformation_t accountInfo; int error = 0; if (!g_pDBIO->VerifyAccount(login_credentials, szPassword, &accountInfo, &error)) { if (error == VERIFYACCOUNT_ERROR_DOESNT_EXIST && g_pConfig->AutoCreateAccounts()) { std::string ipaddress = inet_ntoa(addr->sin_addr); // try to create the login_credentials if (g_pDBIO->CreateAccount(login_credentials, szPassword, &error, ipaddress.c_str())) { // now try to verify the newly created login_credentials if (!g_pDBIO->VerifyAccount(login_credentials, szPassword, &accountInfo, &error)) { // fail SendConnectLoginFailure(addr, STR_LOGIN_FAILED, login_credentials, szPassword); return; } } else { // fail SendConnectLoginFailure(addr, STR_LOGIN_FAILED, login_credentials, szPassword); return; } } else { // fail SendConnectLoginFailure(addr, STR_LOGIN_FAILED, login_credentials, szPassword); return; } } CClient *client = FindClientByAccount(accountInfo.username.c_str()); if (client) { // Check existing client m_vars.ClientLoginUnixTime vs new connection request and ignore if 5 secs have not elapsed. if (stricmp(client->GetAccount(), "admin") && (client->GetLoginTime() + 5 <= client_unix_timestamp)) { KickClient(client); } else { SERVER_INFO << "Client" << client->GetSlot() << "(" << client->GetAccount() << ") is being ignored due to frequency of login requests."; return; } } WORD slot = AllocOpenClientSlot(); if (!slot) { // Server unavailable. SendConnectLoginFailure(addr, STR_LOGIN_SERVER_FULL, nullptr, nullptr); //CREATEBLOB(ServerFull, sizeof(uint32_t)); //*((uint32_t *)ServerFull->data) = 0x00000005; //SendConnectlessBlob(addr, ServerFull, BT_ERROR, 0, 0, true); //DELETEBLOB(ServerFull); return; } SERVER_INFO << "Client" << login_credentials << "(" << inet_ntoa(addr->sin_addr) << ":" << ntohs(addr->sin_port) << ") connected on slot" << slot; client = m_ClientSlots[slot] = new CClient(addr, slot, accountInfo); client->SetLoginData(client_unix_timestamp, portal_stamp, cell_stamp); m_ClientArray[m_NumClients] = client; client->SetArrayPos(m_NumClients); m_NumClients++; // Add the client to the HUD // m_server->Stats().UpdateClientList(m_clients, m_slotrange); BinaryWriter AcceptConnect; // Some server variables AcceptConnect.Write(g_pGlobals->Time()); BYTE cookie[] = { 0xbe, 0xc8, 0x8a, 0x58, 0x0b, 0x1e, 0x99, 0x43 }; AcceptConnect.Write(cookie, sizeof(cookie)); AcceptConnect.Write(slot); AcceptConnect.Write(client->GetPacketController()->GetServerCryptoSeed()); AcceptConnect.Write(client->GetPacketController()->GetClientCryptoSeed()); AcceptConnect.Write(2); uint32_t dwLength = AcceptConnect.GetSize(); if (dwLength <= 0x1D0) { CREATEBLOB(Woot, (WORD)dwLength); memcpy(Woot->data, AcceptConnect.GetData(), dwLength); SendConnectlessBlob(addr, Woot, BT_LOGINREPLY, 0x00000000, 0, true); DELETEBLOB(Woot); // Check the account in the database to prevent users from changing their ip if (IsBannedIP(addr->sin_addr) || accountInfo.banned == true) { int32_t duration = GetBanDuration(addr->sin_addr); if (duration >= 0) KickBannedClient(client, duration); else { RemoveBan(addr->sin_addr); g_pDBIO->UpdateBan(accountInfo.id, 0); } return; } } else { SERVER_INFO << "AcceptConnect.GetSize() > 0x1D0"; } } WORD CNetwork::AllocOpenClientSlot() { // Allocate an available slot for a connecting client if (m_OpenSlots.empty()) return 0; uint32_t slot = *m_OpenSlots.begin(); m_OpenSlots.pop_front(); return slot; } void CNetwork::ProcessConnectionless(sockaddr_in *peer, BlobPacket_s *blob) { uint32_t dwFlags = blob->header.dwFlags; if (dwFlags == BT_LOGIN) { ConnectionRequest(peer, blob); return; } // LOG(Network, Verbose, "Unhandled connectionless packet received: 0x%08X Look into this\n", dwFlags); } DEFINE_PACK(CBanDescription) { pWriter->Write(2); // version pWriter->WriteString(m_AdminName); pWriter->WriteString(m_Reason); pWriter->Write(m_Timestamp); pWriter->Write(m_BanDuration); pWriter->Write(m_AccountID); } DEFINE_UNPACK(CBanDescription) { uint32_t version = pReader->Read(); m_AdminName = pReader->ReadString(); m_Reason = pReader->ReadString(); m_Timestamp = pReader->Read(); if (version > 1) { m_BanDuration = pReader->Read(); m_AccountID = pReader->Read(); } return true; } DEFINE_PACK(CNetworkBanList) { pWriter->Write(1); // version m_BanTable.Pack(pWriter); } DEFINE_UNPACK(CNetworkBanList) { m_BanTable.clear(); uint32_t version = pReader->Read(); m_BanTable.UnPack(pReader); return true; } bool CNetwork::IsBannedIP(in_addr ipaddr) { return m_Bans.m_BanTable.lookup(ipaddr.s_addr) ? true : false; } int32_t CNetwork::GetBanDuration(in_addr ipaddr) { CBanDescription* ban = m_Bans.m_BanTable.lookup(ipaddr.s_addr); int32_t timestamp, duration = 0; if (ban) { timestamp = ban->m_Timestamp; duration = ban->m_BanDuration; } if (!duration) return 0; return (timestamp + duration) - time(0); } uint32_t CNetwork::GetBanID(in_addr ipaddr) { uint32_t id = m_Bans.m_BanTable.lookup(ipaddr.s_addr)->m_AccountID; if (!id) return 0; return id; } void CNetwork::LoadBans() { void *data = NULL; unsigned long length = 0; if (g_pDBIO->GetGlobalData(DBIO_GLOBAL_BAN_DATA, &data, &length)) { BinaryReader reader(data, length); m_Bans.UnPack(&reader); } } void CNetwork::SaveBans() { BinaryWriter banData; m_Bans.Pack(&banData); g_pDBIO->CreateOrUpdateGlobalData(DBIO_GLOBAL_BAN_DATA, banData.GetData(), banData.GetSize()); } void CNetwork::AddBan(in_addr ipaddr, const char *admin, const char *reason, uint32_t account_id) { CBanDescription *pBan = &m_Bans.m_BanTable[ipaddr.s_addr]; pBan->m_AdminName = admin; pBan->m_Reason = reason; pBan->m_Timestamp = time(0); pBan->m_BanDuration = 0; pBan->m_AccountID = account_id; SaveBans(); } void CNetwork::AddBan(in_addr ipaddr, const char *admin, const char *reason, int32_t duration, uint32_t account_id) { CBanDescription *pBan = &m_Bans.m_BanTable[ipaddr.s_addr]; pBan->m_AdminName = admin; pBan->m_Reason = reason; pBan->m_Timestamp = time(0); pBan->m_BanDuration = duration; pBan->m_AccountID = account_id; SaveBans(); } bool CNetwork::RemoveBan(in_addr ipaddr) { if (m_Bans.m_BanTable.lookup(ipaddr.s_addr)) { m_Bans.m_BanTable.erase(ipaddr.s_addr); SaveBans(); return true; } return false; } std::string CNetwork::GetBanList() { std::string banList = csprintf("Ban List (%d entries):", m_Bans.m_BanTable.size()); for (auto &entry : m_Bans.m_BanTable) { CBanDescription *pBan = &entry.second; banList += csprintf("\n%s - Admin: %s @ %s Reason: %s Duration: %d\n", inet_ntoa(*(in_addr *)&entry.first), pBan->m_AdminName.c_str(), timestampDateString(pBan->m_Timestamp), pBan->m_Reason.c_str(), GetBanDuration(*(in_addr *)&entry.first)); } return banList; } uint32_t CNetwork::GetUniques() { if (!m_NumClients) return 0; std::list _addresses; std::string addr = ""; for (uint32_t i = 0; i < m_NumClients; i++) { if (CClient *client = m_ClientArray[i]) { addr = inet_ntoa(client->GetHostAddress()->sin_addr); if (addr != "") _addresses.push_back(addr); } } _addresses.unique(); uint32_t count = _addresses.size(); return count; }