#include "stdafx.h" #include "PendingConnection.h" #include "PlayerConnection.h" #include "ServerConnection.h" #include "ServerPlayer.h" #include "ServerPlayerGameMode.h" #include "ServerLevel.h" #include "PlayerList.h" #include "MinecraftServer.h" #include "..\Minecraft.World\net.minecraft.network.h" #include "..\Minecraft.World\net.minecraft.network.packet.h" #include "..\Minecraft.World\pos.h" #include "..\Minecraft.World\net.minecraft.world.level.dimension.h" #include "..\Minecraft.World\net.minecraft.world.level.storage.h" #include "..\Minecraft.World\net.minecraft.world.item.h" #include "..\Minecraft.World\SharedConstants.h" #include "..\Minecraft.World\GameUUID.h" #include "Settings.h" #ifdef _WINDOWS64 #include "..\..\MCAuth\include\MCAuth.h" #include #endif #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) #include "..\Minecraft.Server\ServerLogManager.h" #include "..\Minecraft.Server\ServerLogger.h" #include "..\Minecraft.Server\Access\Access.h" #include "..\Minecraft.World\Socket.h" #endif // #ifdef __PS3__ // #include "PS3\Network\NetworkPlayerSony.h" // #endif // Auth logging: use INFO-level server logger on dedicated server, DebugPrintf on client #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) static void AuthLog(const char* fmt, ...) { char buf[2048]; va_list args; va_start(args, fmt); vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args); va_end(args); // Strip trailing newline for server logger (it adds its own) size_t len = strlen(buf); while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) buf[--len] = '\0'; ServerRuntime::LogInfof("auth", "%s", buf); } #define AUTH_LOG(fmt, ...) AuthLog(fmt, ##__VA_ARGS__) #else #define AUTH_LOG(fmt, ...) app.DebugPrintf(fmt, ##__VA_ARGS__) #endif Random *PendingConnection::random = new Random(); #ifdef _WINDOWS64 bool g_bRejectDuplicateNames = true; #endif #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) namespace { static unsigned char GetPendingConnectionSmallId(Connection *connection) { if (connection != nullptr) { Socket *socket = connection->getSocket(); if (socket != nullptr) { return socket->getSmallId(); } } return 0; } } #endif struct UgcGatherResult { PlayerUID *xuids; DWORD cappedCount; BYTE friendsOnlyBits; BYTE cappedHostIndex; char uniqueMapName[14]; }; static UgcGatherResult GatherUgcData() { UgcGatherResult r = {}; StorageManager.GetSaveUniqueFilename(r.uniqueMapName); r.xuids = new PlayerUID[MINECRAFT_NET_MAX_PLAYERS]; DWORD count = 0; DWORD hostIndex = 0; PlayerList *playerList = MinecraftServer::getInstance()->getPlayers(); for (auto& player : playerList->players) { if (player != nullptr && player->connection->m_xuid != INVALID_XUID) { if (player->connection->m_friendsOnlyUGC) r.friendsOnlyBits |= (1 << count); r.xuids[count] = player->connection->m_xuid; if (player->connection->getNetworkPlayer() != nullptr && player->connection->getNetworkPlayer()->IsHost()) hostIndex = count; ++count; } } r.cappedCount = (count > 255u) ? 255u : count; r.cappedHostIndex = (hostIndex >= 255u) ? 254 : static_cast(hostIndex); return r; } PendingConnection::PendingConnection(MinecraftServer *server, Socket *socket, const wstring& id) { // 4J - added initialisers done = false; _tick = 0; name = L""; acceptedLogin = nullptr; loginKey = L""; this->server = server; connection = new Connection(socket, id, this); connection->fakeLag = FAKE_LAG; } PendingConnection::~PendingConnection() { if (m_authVerifyResult) m_authVerifyResult->cancelled.store(true, std::memory_order_release); delete connection; } void PendingConnection::tick() { if (acceptedLogin != nullptr) { this->handleAcceptedLogin(acceptedLogin); acceptedLogin = nullptr; } #ifdef _WINDOWS64 // Poll async Mojang verification result if (m_authState == eAuth_Verifying && m_authVerifyResult && m_authVerifyResult->ready.load(std::memory_order_acquire)) { std::lock_guard lock(m_authVerifyResult->mutex); if (m_authVerifyResult->success) { m_authUsername = m_authVerifyResult->username; m_authUuid = MCAuth::DashUuid(m_authVerifyResult->uuid); AUTH_LOG("[Auth] %s verification SUCCESS for '%s' (uuid=%s)\n", m_authScheme.c_str(), m_authUsername.c_str(), m_authUuid.c_str()); // Store skin data for later use in placeNewPlayer m_authSkinData = std::move(m_authVerifyResult->skinData); if (!m_authSkinData.empty()) { if (m_authScheme == "elyby") m_authSkinUrl = MCAuth::MakeElybySkinKey(m_authVerifyResult->uuid); else m_authSkinUrl = MCAuth::MakeSkinKey(m_authVerifyResult->uuid); AUTH_LOG("[Auth] Downloaded %s skin for %s (%zu bytes)\n", m_authScheme.c_str(), m_authUsername.c_str(), m_authSkinData.size()); } wstring wUuid(m_authUuid.begin(), m_authUuid.end()); wstring wName(m_authUsername.begin(), m_authUsername.end()); // Send skin key + bytes inline so the client doesn't need a separate download wstring wSkinKey(m_authSkinUrl.begin(), m_authSkinUrl.end()); connection->send(make_shared(true, wUuid, wName, L"", wSkinKey, m_authSkinData)); m_authState = eAuth_WaitingAck; } else { AUTH_LOG("[Auth] %s verification FAILED for pending connection (error=%s), disconnecting\n", m_authScheme.c_str(), m_authVerifyResult->errorDetail.c_str()); connection->send(make_shared(false, L"", L"", L"Authentication failed")); connection->sendAndQuit(); m_authState = eAuth_Done; done = true; } } #endif if (_tick++ == MAX_TICKS_BEFORE_LOGIN) { disconnect(DisconnectPacket::eDisconnect_LoginTooLong); } else { connection->tick(); } } void PendingConnection::disconnect(DisconnectPacket::eDisconnectReason reason) { if (m_authVerifyResult) m_authVerifyResult->cancelled.store(true, std::memory_order_release); const char* reasonStr = "unknown"; switch (reason) { case DisconnectPacket::eDisconnect_Closed: reasonStr = "Closed"; break; case DisconnectPacket::eDisconnect_Kicked: reasonStr = "Kicked"; break; case DisconnectPacket::eDisconnect_LoginTooLong: reasonStr = "LoginTooLong"; break; case DisconnectPacket::eDisconnect_OutdatedServer: reasonStr = "OutdatedServer"; break; case DisconnectPacket::eDisconnect_OutdatedClient: reasonStr = "OutdatedClient"; break; case DisconnectPacket::eDisconnect_ServerFull: reasonStr = "ServerFull"; break; case DisconnectPacket::eDisconnect_AuthFailed: reasonStr = "AuthFailed"; break; case DisconnectPacket::eDisconnect_UnexpectedPacket: reasonStr = "UnexpectedPacket"; break; default: break; } AUTH_LOG("[Auth] Pending connection disconnect: reason=%d (%s)\n", reason, reasonStr); connection->send(std::make_shared(reason)); connection->sendAndQuit(); done = true; // } catch (Exception e) { // e.printStackTrace(); // } } void PendingConnection::handlePreLogin(shared_ptr packet) { if (packet->m_netcodeVersion != MINECRAFT_NET_VERSION) { app.DebugPrintf("Netcode version is %d not equal to %d\n", packet->m_netcodeVersion, MINECRAFT_NET_VERSION); if (packet->m_netcodeVersion > MINECRAFT_NET_VERSION) { disconnect(DisconnectPacket::eDisconnect_OutdatedServer); } else { disconnect(DisconnectPacket::eDisconnect_OutdatedClient); } return; } // printf("Server: handlePreLogin\n"); name = packet->loginKey; // 4J Stu - Change from the login packet as we know better on client end during the pre-login packet sendPreLoginResponse(); } void PendingConnection::sendPreLoginResponse() { // 4J Stu - Calculate the players with UGC privileges set UgcGatherResult ugc = GatherUgcData(); connection->send(std::make_shared(L"-", ugc.xuids, ugc.cappedCount, ugc.friendsOnlyBits, server->m_ugcPlayersVersion, ugc.uniqueMapName, app.GetGameHostOption(eGameHostOption_All), ugc.cappedHostIndex, server->m_texturePackId)); // --- Auth handshake --- // Generate random serverId (20 hex chars) { static const char hex[] = "0123456789abcdef"; m_serverId.resize(20); for (int i = 0; i < 20; i++) m_serverId[i] = hex[random->nextInt(16)]; } vector schemes; if (server->onlineMode) { if (server->authProvider == "elyby") { schemes.push_back(L"elyby"); AUTH_LOG("[Auth] Server onlineMode=true, authProvider=elyby, offering scheme: [elyby]\n"); } else { schemes.push_back(L"mojang"); AUTH_LOG("[Auth] Server onlineMode=true, authProvider=mojang, offering scheme: [mojang]\n"); } } else { schemes.push_back(L"mojang"); schemes.push_back(L"offline"); AUTH_LOG("[Auth] Server onlineMode=false, offering schemes: [mojang, offline]\n"); } wstring wServerId(m_serverId.begin(), m_serverId.end()); connection->send(make_shared(schemes, wServerId)); m_authState = eAuth_WaitingResponse; AUTH_LOG("[Auth] Sent AuthSchemePacket to client, serverId=%s\n", m_serverId.c_str()); } void PendingConnection::handleLogin(shared_ptr packet) { // printf("Server: handleLogin\n"); //name = packet->userName; // Reject login if auth handshake has not completed. // eAuth_None means no auth handshake occurred — reject to prevent auth bypass. if (m_authState == eAuth_WaitingAck) { // LoginPacket completes the auth handshake wstring wName(m_authUsername.begin(), m_authUsername.end()); name = wName; m_authState = eAuth_Done; AUTH_LOG("[Auth] Auth handshake complete for '%s' (implicit ack via LoginPacket)\n", m_authUsername.c_str()); } else if (m_authState == eAuth_Done) { // Already completed — allow through } else { // eAuth_None, eAuth_WaitingResponse, eAuth_Verifying — all invalid AUTH_LOG("[Auth] Received LoginPacket before auth completed (state=%d), disconnecting\n", (int)m_authState); disconnect(DisconnectPacket::eDisconnect_AuthFailed); return; } if (packet->clientVersion != SharedConstants::NETWORK_PROTOCOL_VERSION) { app.DebugPrintf("Client version is %d not equal to %d\n", packet->clientVersion, SharedConstants::NETWORK_PROTOCOL_VERSION); if (packet->clientVersion > SharedConstants::NETWORK_PROTOCOL_VERSION) { disconnect(DisconnectPacket::eDisconnect_OutdatedServer); } else { disconnect(DisconnectPacket::eDisconnect_OutdatedClient); } return; } //if (true)// 4J removed !server->onlineMode) PlayerUID loginXuid = packet->m_xuid; // INVALID_XUID may indicate a truncated packet (readPlayerUID returns {0,0} on EOF) if (loginXuid == INVALID_XUID) { AUTH_LOG("[Auth] Warning: received INVALID_XUID in LoginPacket — possible truncated packet or offline client\n"); } bool duplicateXuid = (loginXuid != INVALID_XUID && server->getPlayers()->getPlayer(loginXuid) != nullptr); bool bannedXuid = (loginXuid != INVALID_XUID && server->getPlayers()->isXuidBanned(loginXuid)); // Also check ban against server-verified auth UUID to prevent bypass via INVALID_XUID if (!bannedXuid && !m_authUuid.empty()) { GameUUID authUid = GameUUID::fromDashed(m_authUuid); if (authUid.isValid()) { bannedXuid = server->getPlayers()->isXuidBanned(authUid); } } bool whitelistSatisfied = true; #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) if (ServerRuntime::Access::IsWhitelistEnabled()) { whitelistSatisfied = (loginXuid != INVALID_XUID && ServerRuntime::Access::IsPlayerWhitelisted(loginXuid)); if (!whitelistSatisfied && !m_authUuid.empty()) { GameUUID authUid = GameUUID::fromDashed(m_authUuid); if (authUid.isValid()) { whitelistSatisfied = ServerRuntime::Access::IsPlayerWhitelisted(authUid); } } } #endif if (bannedXuid) { #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) ServerRuntime::ServerLogManager::OnRejectedPlayerLogin(GetPendingConnectionSmallId(connection), name, ServerRuntime::ServerLogManager::eLoginRejectReason_BannedXuid); #endif disconnect(DisconnectPacket::eDisconnect_Banned); } else if (!whitelistSatisfied) { #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) ServerRuntime::ServerLogManager::OnRejectedPlayerLogin(GetPendingConnectionSmallId(connection), name, ServerRuntime::ServerLogManager::eLoginRejectReason_NotWhitelisted); #endif disconnect(DisconnectPacket::eDisconnect_Banned); } else if (duplicateXuid) { #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) ServerRuntime::ServerLogManager::OnRejectedPlayerLogin(GetPendingConnectionSmallId(connection), name, ServerRuntime::ServerLogManager::eLoginRejectReason_DuplicateXuid); #endif // Reject the incoming connection — a player with this UID is already // on the server. Allowing duplicates causes invisible players and // other undefined behaviour. app.DebugPrintf("LOGIN: Rejecting duplicate xuid for name: %ls\n", name.c_str()); disconnect(DisconnectPacket::eDisconnect_Banned); } #ifdef _WINDOWS64 else if (g_bRejectDuplicateNames) { bool nameTaken = false; vector >& pl = server->getPlayers()->players; for (const auto& i : pl) { if (i != nullptr && i->name == name) { nameTaken = true; break; } } if (nameTaken) { #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) ServerRuntime::ServerLogManager::OnRejectedPlayerLogin(GetPendingConnectionSmallId(connection), name, ServerRuntime::ServerLogManager::eLoginRejectReason_DuplicateName); #endif app.DebugPrintf("Rejecting duplicate name: %ls\n", name.c_str()); disconnect(DisconnectPacket::eDisconnect_Banned); } else { handleAcceptedLogin(packet); } } #endif else { handleAcceptedLogin(packet); } } void PendingConnection::handleAcceptedLogin(shared_ptr packet) { if(packet->m_ugcPlayersVersion != server->m_ugcPlayersVersion) { // UGC version mismatch — resend pre-login info but do NOT restart auth handshake. // Only resend the PreLoginPacket with updated UGC player list; the auth state // (m_authState, m_authUsername, m_authUuid) remains valid from the completed handshake. UgcGatherResult ugc = GatherUgcData(); connection->send(std::make_shared(L"-", ugc.xuids, ugc.cappedCount, ugc.friendsOnlyBits, server->m_ugcPlayersVersion, ugc.uniqueMapName, app.GetGameHostOption(eGameHostOption_All), ugc.cappedHostIndex, server->m_texturePackId)); return; } PlayerUID playerXuid = packet->m_xuid; // Fallback: generate offline UUID from player name if still invalid if (playerXuid == INVALID_XUID) { if (name.empty()) { AUTH_LOG("[Auth] Rejecting login with empty name and no UUID\n"); disconnect(DisconnectPacket::eDisconnect_AuthFailed); return; } playerXuid = GameUUID::generateOffline(std::string(name.begin(), name.end())); } shared_ptr playerEntity = server->getPlayers()->getPlayerForLogin(this, name, playerXuid); if (playerEntity != nullptr) { // Use server-verified auth UUID instead of client-supplied packet->m_mojangUuid if (!m_authUuid.empty()) { GameUUID verifiedUuid = GameUUID::fromDashed(m_authUuid); if (verifiedUuid.isValid()) { playerEntity->setXuid(verifiedUuid); } } #if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) ServerRuntime::ServerLogManager::OnAcceptedPlayerLogin(GetPendingConnectionSmallId(connection), name); #endif server->getPlayers()->placeNewPlayer(connection, playerEntity, packet); // Override with Mojang skin after placeNewPlayer has set the DLC skin if (!m_authSkinData.empty() && !m_authSkinUrl.empty()) { wstring wSkinKey(m_authSkinUrl.begin(), m_authSkinUrl.end()); // AddMemoryTextureFile takes ownership of the pointer — must be heap-allocated DWORD skinSize = (DWORD)m_authSkinData.size(); PBYTE skinBuf = new BYTE[skinSize]; memcpy(skinBuf, m_authSkinData.data(), skinSize); app.AddMemoryTextureFile(wSkinKey, skinBuf, skinSize); playerEntity->customTextureUrl = wSkinKey; app.DebugPrintf("Set Mojang skin for player %s: %s (%zu bytes)\n", m_authUsername.c_str(), m_authSkinUrl.c_str(), m_authSkinData.size()); // Send Mojang skin to all other clients for (auto& otherPlayer : server->getPlayers()->players) { if (otherPlayer != nullptr && otherPlayer != playerEntity && otherPlayer->connection != nullptr) { // Send the skin PNG data so the other client has it in memory PBYTE otherBuf = new BYTE[skinSize]; memcpy(otherBuf, m_authSkinData.data(), skinSize); otherPlayer->connection->send( std::make_shared(wSkinKey, otherBuf, skinSize)); // Notify the other client that this player now uses this skin otherPlayer->connection->send( std::make_shared(playerEntity, wSkinKey)); } } // Send existing Mojang skins to the new joiner for (auto& otherPlayer : server->getPlayers()->players) { if (otherPlayer != nullptr && otherPlayer != playerEntity && !otherPlayer->customTextureUrl.empty() && (otherPlayer->customTextureUrl.substr(0, 6) == L"mojang" || otherPlayer->customTextureUrl.substr(0, 5) == L"elyby")) { PBYTE existingData = nullptr; DWORD existingSize = 0; app.GetMemFileDetails(otherPlayer->customTextureUrl, &existingData, &existingSize); if (existingData != nullptr && existingSize > 0) { PBYTE copyBuf = new BYTE[existingSize]; memcpy(copyBuf, existingData, existingSize); playerEntity->connection->send( std::make_shared(otherPlayer->customTextureUrl, copyBuf, existingSize)); playerEntity->connection->send( std::make_shared(otherPlayer, otherPlayer->customTextureUrl)); } } } } connection = nullptr; // We've moved responsibility for this over to the new PlayerConnection, nullptr so we don't delete our reference to it here in our dtor } else { disconnect(DisconnectPacket::eDisconnect_ServerFull); return; } done = true; } void PendingConnection::handleAuthResponse(shared_ptr packet) { if (m_authState != eAuth_WaitingResponse) { AUTH_LOG("[Auth] Received AuthResponse in unexpected state %d, disconnecting\n", (int)m_authState); disconnect(DisconnectPacket::eDisconnect_UnexpectedPacket); return; } // Convert wstring fields to std::string for MCAuth string scheme(packet->chosenScheme.begin(), packet->chosenScheme.end()); string username(packet->username.begin(), packet->username.end()); string uuid(packet->mojangUuid.begin(), packet->mojangUuid.end()); AUTH_LOG("[Auth] Received AuthResponse: scheme='%s', username='%s', uuid='%s'\n", scheme.c_str(), username.c_str(), uuid.c_str()); if (username.empty()) { AUTH_LOG("[Auth] REJECTED: empty username\n"); disconnect(DisconnectPacket::eDisconnect_AuthFailed); return; } m_authScheme = scheme; #ifdef _WINDOWS64 if (scheme == "mojang") { AUTH_LOG("[Auth] Starting Mojang session verification for '%s'...\n", username.c_str()); // Verify with Mojang sessionserver in a background thread // shared_ptr so the result outlives PendingConnection if destroyed m_authState = eAuth_Verifying; m_authVerifyResult = std::make_shared(); string serverId = m_serverId; auto sharedResult = m_authVerifyResult; // capture shared_ptr by value std::thread([username, serverId, sharedResult]() { try { if (sharedResult->cancelled.load(std::memory_order_acquire)) return; string error; auto result = MCAuth::HasJoined(username, serverId, error); // Client may have disconnected if (sharedResult->cancelled.load(std::memory_order_acquire)) return; std::vector skinBytes; if (result.success && !result.skinUrl.empty()) { string skinError; skinBytes = MCAuth::FetchSkinPng(result.skinUrl, skinError); } if (sharedResult->cancelled.load(std::memory_order_acquire)) return; // Prevents data race with tick() { std::lock_guard lock(sharedResult->mutex); sharedResult->success = result.success; if (result.success) { sharedResult->username = result.username; sharedResult->uuid = result.uuid; sharedResult->skinUrl = result.skinUrl; sharedResult->skinData = std::move(skinBytes); } else { sharedResult->errorDetail = error; } } // Signal main thread sharedResult->ready.store(true, std::memory_order_release); } catch (...) { std::lock_guard lock(sharedResult->mutex); sharedResult->success = false; sharedResult->errorDetail = "unknown exception"; sharedResult->ready.store(true, std::memory_order_release); } }).detach(); } else if (scheme == "elyby") { AUTH_LOG("[Auth] Starting ely.by session verification for '%s'...\n", username.c_str()); m_authState = eAuth_Verifying; m_authVerifyResult = std::make_shared(); string capturedServerId = m_serverId; auto sharedResult = m_authVerifyResult; std::thread([username, capturedServerId, sharedResult]() { try { if (sharedResult->cancelled.load(std::memory_order_acquire)) return; string error; auto result = MCAuth::ElybyHasJoined(username, capturedServerId, error); if (sharedResult->cancelled.load(std::memory_order_acquire)) return; std::vector skinBytes; if (result.success && !result.skinUrl.empty()) { string skinError; skinBytes = MCAuth::FetchSkinPng(result.skinUrl, skinError); } if (sharedResult->cancelled.load(std::memory_order_acquire)) return; { std::lock_guard lock(sharedResult->mutex); sharedResult->success = result.success; if (result.success) { sharedResult->username = result.username; sharedResult->uuid = result.uuid; sharedResult->skinUrl = result.skinUrl; sharedResult->skinData = std::move(skinBytes); } else { sharedResult->errorDetail = error; } } sharedResult->ready.store(true, std::memory_order_release); } catch (const std::exception& ex) { std::lock_guard lock(sharedResult->mutex); sharedResult->success = false; sharedResult->errorDetail = std::string("exception: ") + ex.what(); sharedResult->ready.store(true, std::memory_order_release); } catch (...) { std::lock_guard lock(sharedResult->mutex); sharedResult->success = false; sharedResult->errorDetail = "unknown exception"; sharedResult->ready.store(true, std::memory_order_release); } }).detach(); } else #endif if (scheme == "offline") { if (server->onlineMode) { AUTH_LOG("[Auth] REJECTED: Client chose 'offline' but server requires online-mode (onlineMode=true)\n"); connection->send(make_shared( false, L"", L"", L"Server requires Microsoft authentication")); connection->sendAndQuit(); m_authState = eAuth_Done; done = true; return; } // Server-generated UUID prevents UUID forgery GameUUID offlineGuid = GameUUID::generateOffline(username); std::string offlineUuid = offlineGuid.toDashed(); AUTH_LOG("[Auth] Accepted offline auth for '%s' (server-assigned uuid=%s, client-sent uuid=%s)\n", username.c_str(), offlineUuid.c_str(), uuid.c_str()); m_authUsername = username; m_authUuid = offlineUuid; wstring wUuid(offlineUuid.begin(), offlineUuid.end()); wstring wName(username.begin(), username.end()); connection->send(make_shared(true, wUuid, wName, L"")); m_authState = eAuth_WaitingAck; } else { AUTH_LOG("[Auth] REJECTED: Unknown auth scheme '%s', disconnecting\n", scheme.c_str()); disconnect(DisconnectPacket::eDisconnect_AuthFailed); } } void PendingConnection::onDisconnect(DisconnectPacket::eDisconnectReason reason, void *reasonObjects) { // logger.info(getName() + " lost connection"); done = true; } void PendingConnection::handleGetInfo(shared_ptr packet) { //try { //String message = server->motd + "�" + server->players->getPlayerCount() + "�" + server->players->getMaxPlayers(); //connection->send(new DisconnectPacket(message)); connection->send(std::make_shared(DisconnectPacket::eDisconnect_ServerFull)); connection->sendAndQuit(); server->connection->removeSpamProtection(connection->getSocket()); done = true; //} catch (Exception e) { // e.printStackTrace(); //} } void PendingConnection::handleKeepAlive(shared_ptr packet) { // Ignore } void PendingConnection::onUnhandledPacket(shared_ptr packet) { disconnect(DisconnectPacket::eDisconnect_UnexpectedPacket); } void PendingConnection::send(shared_ptr packet) { connection->send(packet); } wstring PendingConnection::getName() { return L"Unimplemented"; // if (name != null) return name + " [" + connection.getRemoteAddress().toString() + "]"; // return connection.getRemoteAddress().toString(); } bool PendingConnection::isServerPacketListener() { return true; } bool PendingConnection::isDisconnected() { return done; }