MinecraftConsoles/Minecraft.Client/PendingConnection.cpp
MrTheShy 4f2352361a Add Mojang/Ely.by/offline authentication system
The old Windows64 port had no real player identity — it used hardcoded
fake XUIDs, so anyone could impersonate anyone. This replaces that with
proper auth supporting Mojang, Ely.by, and offline accounts.

MCAuth library (new, MCAuth/):
  Mojang auth via MSA device code flow (XBL, SISU, MC services),
  Ely.by via Yggdrasil with 2FA, offline UUID generation matching
  Java Edition (MD5 v3 from "OfflinePlayer:<name>"). Multi-account
  manager with background token refresh, per-slot sessions, and
  on-disk token persistence. Server-side session verification via
  Mojang/Ely.by hasJoined API. Skin fetching and PNG validation
  from texture servers.

Network protocol (version bumped to 80):
  Three new packets (AuthScheme, AuthResponse, AuthResult) implement
  a server-driven auth handshake before login completes. Player
  identity migrated from 64-bit XUID to 128-bit GameUUID backed by
  two uint64 fields (hi/lo). readPlayerUID/writePlayerUID now
  serialize 16 bytes on the wire. Old and new clients cannot connect
  to each other — version mismatch is rejected at PreLogin.

Save migration:
  Map data mappings auto-migrate from old format: the old 64-bit
  XUID is placed in hi, lo is set to 0 as a sentinel. On first
  access by the real player, the sentinel entry is upgraded in-place
  to the full 128-bit UUID. Format detection is by file size (2080,
  2112, or 4160 bytes). Player .dat filenames inside saveData.ms
  change from decimal XUID to dashed UUID — old saves need manual
  entry renaming in the archive.

UI:
  NativeUIRenderer: immediate-mode drawing system (quads, text,
  9-slice panels, scrollbars, focus lists) for rendering auth
  screens without Flash/Scaleform. UIScene_MSAuth handles device
  code display, Ely.by credential input with 2FA, per-account
  skin head preview, and multi-account add/remove/switch.

Server:
  online-mode and auth-provider (mojang/elyby) in server.properties.
  Whitelist and ban checks validate against the server-verified UUID.
  Incompatible auth scheme logs which provider the server expects
  vs what the client is using.

Also fixes a pre-existing exploit where any client could send a
DebugOptionsPacket to grant themselves CraftAnything and other debug
privileges on any server — now requires OP status server-side.
2026-03-23 01:14:23 +01:00

765 lines
25 KiB
C++
Raw Blame History

#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 <thread>
#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<BYTE>(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<std::mutex> 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<AuthResultPacket>(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<AuthResultPacket>(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<DisconnectPacket>(reason));
connection->sendAndQuit();
done = true;
// } catch (Exception e) {
// e.printStackTrace();
// }
}
void PendingConnection::handlePreLogin(shared_ptr<PreLoginPacket> 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<PreLoginPacket>(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<wstring> 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<AuthSchemePacket>(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<LoginPacket> 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<shared_ptr<ServerPlayer> >& 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<LoginPacket> 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<PreLoginPacket>(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<ServerPlayer> 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<TextureAndGeometryPacket>(wSkinKey, otherBuf, skinSize));
// Notify the other client that this player now uses this skin
otherPlayer->connection->send(
std::make_shared<TextureAndGeometryChangePacket>(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<TextureAndGeometryPacket>(otherPlayer->customTextureUrl, copyBuf, existingSize));
playerEntity->connection->send(
std::make_shared<TextureAndGeometryChangePacket>(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<AuthResponsePacket> 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<AuthVerifyResult>();
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<uint8_t> 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<std::mutex> 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<std::mutex> 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<AuthVerifyResult>();
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<uint8_t> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<AuthResultPacket>(
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<AuthResultPacket>(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<GetInfoPacket> packet)
{
//try {
//String message = server->motd + "<22>" + server->players->getPlayerCount() + "<22>" + server->players->getMaxPlayers();
//connection->send(new DisconnectPacket(message));
connection->send(std::make_shared<DisconnectPacket>(DisconnectPacket::eDisconnect_ServerFull));
connection->sendAndQuit();
server->connection->removeSpamProtection(connection->getSocket());
done = true;
//} catch (Exception e) {
// e.printStackTrace();
//}
}
void PendingConnection::handleKeepAlive(shared_ptr<KeepAlivePacket> packet)
{
// Ignore
}
void PendingConnection::onUnhandledPacket(shared_ptr<Packet> packet)
{
disconnect(DisconnectPacket::eDisconnect_UnexpectedPacket);
}
void PendingConnection::send(shared_ptr<Packet> 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;
}