From 9bec17b4040161938bb288feb9516d4f962bbaba Mon Sep 17 00:00:00 2001 From: kuwacom Date: Fri, 6 Mar 2026 14:04:53 +0900 Subject: [PATCH] update: migrate Win64 player uid to `uid.dat`-backed XUID and add XUID based duplicate login guards - Replace Win64 username-derived XUID resolution with persistent `uid.dat`-backed identity (`Windows64_Xuid` / `Win64Xuid`). - Persist a per-client XUID next to the executable, with first-run generation, read/write, and process-local caching. - Keep legacy host compatibility by pinning host self to legacy embedded `base + 0` XUID for existing world/playerdata continuity. - Propagate packet-authoritative XUIDs into QNet player slots via `m_resolvedXuid`, and use it for `GetXuid`/`GetPlayerByXuid` with legacy fallback. - Update Win64 profile/network paths to use persistent XUID for non-host clients and clear resolved identity on disconnect. - Add login-time duplicate checks: reject connections when the same XUID is already connected (in addition to existing duplicate-name checks on Win64). - Add inline compatibility comments around legacy/new identity coexistence paths for easier future maintenance. --- Minecraft.Client/ClientConnection.cpp | 36 ++- .../Network/PlatformNetworkManagerStub.cpp | 8 + Minecraft.Client/Extrax64Stubs.cpp | 39 ++-- Minecraft.Client/Minecraft.cpp | 19 +- Minecraft.Client/PendingConnection.cpp | 25 +- Minecraft.Client/PlayerList.cpp | 21 +- .../Windows64/Windows64_NameXuid.h | 57 ----- Minecraft.Client/Windows64/Windows64_Xuid.h | 214 ++++++++++++++++++ Minecraft.World/x64headers/extraX64.h | 1 + 9 files changed, 332 insertions(+), 88 deletions(-) delete mode 100644 Minecraft.Client/Windows64/Windows64_NameXuid.h create mode 100644 Minecraft.Client/Windows64/Windows64_Xuid.h diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index fd27eb043..0848d3738 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -763,7 +763,7 @@ void ClientConnection::handleAddPlayer(shared_ptr packet) } } #ifdef _WINDOWS64 - // Win64 player-data XUIDs are resolved from player name, so also guard against creating + // Win64 keeps local-player identity separate from network smallId; also guard against creating // a duplicate remote player for a local slot by checking the username directly. for (unsigned int idx = 0; idx < XUSER_MAX_COUNT; ++idx) { @@ -815,22 +815,50 @@ void ClientConnection::handleAddPlayer(shared_ptr packet) #ifdef _WINDOWS64 { + IQNetPlayer* matchedQNetPlayer = NULL; PlayerUID pktXuid = player->getXuid(); const PlayerUID WIN64_XUID_BASE = (PlayerUID)0xe000d45248242f2e; + // Legacy compatibility path for peers still using embedded smallId XUIDs. if (pktXuid >= WIN64_XUID_BASE && pktXuid < WIN64_XUID_BASE + MINECRAFT_NET_MAX_PLAYERS) { BYTE smallId = (BYTE)(pktXuid - WIN64_XUID_BASE); INetworkPlayer* np = g_NetworkManager.GetPlayerBySmallId(smallId); if (np != NULL) { + NetworkPlayerXbox* npx = (NetworkPlayerXbox*)np; + matchedQNetPlayer = npx->GetQNetPlayer(); + } + } + + // Current Win64 path: identify QNet player by name and attach packet XUID. + if (matchedQNetPlayer == NULL) + { + for (BYTE smallId = 0; smallId < MINECRAFT_NET_MAX_PLAYERS; ++smallId) + { + INetworkPlayer* np = g_NetworkManager.GetPlayerBySmallId(smallId); + if (np == NULL) + continue; + NetworkPlayerXbox* npx = (NetworkPlayerXbox*)np; IQNetPlayer* qp = npx->GetQNetPlayer(); - if (qp != NULL && qp->m_gamertag[0] == 0) + if (qp != NULL && _wcsicmp(qp->m_gamertag, packet->name.c_str()) == 0) { - wcsncpy_s(qp->m_gamertag, 32, packet->name.c_str(), _TRUNCATE); + matchedQNetPlayer = qp; + break; } } } + + if (matchedQNetPlayer != NULL) + { + // Store packet-authoritative XUID on this network slot so later lookups by XUID + // (e.g. remove player, display mapping) work for both legacy and uid.dat clients. + matchedQNetPlayer->m_resolvedXuid = pktXuid; + if (matchedQNetPlayer->m_gamertag[0] == 0) + { + wcsncpy_s(matchedQNetPlayer->m_gamertag, 32, packet->name.c_str(), _TRUNCATE); + } + } } #endif @@ -1000,6 +1028,8 @@ void ClientConnection::handleRemoveEntity(shared_ptr packe qp->m_smallId = 0; qp->m_isRemote = false; qp->m_isHostPlayer = false; + // Clear resolved id to avoid stale XUID -> player matches after disconnect. + qp->m_resolvedXuid = INVALID_XUID; qp->m_gamertag[0] = 0; qp->SetCustomDataValue(0); } diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index c2466fe39..83d646ffd 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -5,6 +5,7 @@ #include "..\..\Xbox\Network\NetworkPlayerXbox.h" #ifdef _WINDOWS64 #include "..\..\Windows64\Network\WinsockNetLayer.h" +#include "..\..\Windows64\Windows64_Xuid.h" #include "..\..\Minecraft.h" #include "..\..\User.h" #endif @@ -234,6 +235,7 @@ void CPlatformNetworkManagerStub::DoWork() qnetPlayer->m_smallId = 0; qnetPlayer->m_isRemote = false; qnetPlayer->m_isHostPlayer = false; + qnetPlayer->m_resolvedXuid = INVALID_XUID; qnetPlayer->m_gamertag[0] = 0; qnetPlayer->SetCustomDataValue(0); WinsockNetLayer::PushFreeSmallId(disconnectedSmallId); @@ -354,7 +356,9 @@ void CPlatformNetworkManagerStub::HostGame(int localUsersMask, bool bOnlineGame, #ifdef _WINDOWS64 IQNet::m_player[0].m_smallId = 0; IQNet::m_player[0].m_isRemote = false; + // world host is pinned to legacy host XUID to keep old player data compatibility. IQNet::m_player[0].m_isHostPlayer = true; + IQNet::m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid(); IQNet::s_playerCount = 1; #endif @@ -411,6 +415,8 @@ int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo* searchResult, int l IQNet::m_player[0].m_smallId = 0; IQNet::m_player[0].m_isRemote = true; IQNet::m_player[0].m_isHostPlayer = true; + // Remote host still maps to legacy host XUID in mixed old/new sessions. + IQNet::m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid(); wcsncpy_s(IQNet::m_player[0].m_gamertag, 32, searchResult->data.hostName, _TRUNCATE); WinsockNetLayer::StopDiscovery(); @@ -426,6 +432,8 @@ int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo* searchResult, int l IQNet::m_player[localSmallId].m_smallId = localSmallId; IQNet::m_player[localSmallId].m_isRemote = false; IQNet::m_player[localSmallId].m_isHostPlayer = false; + // Local non-host identity is the persistent uid.dat XUID. + IQNet::m_player[localSmallId].m_resolvedXuid = Win64Xuid::ResolvePersistentXuid(); Minecraft* pMinecraft = Minecraft::GetInstance(); wcscpy_s(IQNet::m_player[localSmallId].m_gamertag, 32, pMinecraft->user->name.c_str()); diff --git a/Minecraft.Client/Extrax64Stubs.cpp b/Minecraft.Client/Extrax64Stubs.cpp index 1e2b6c2ca..24d8102c3 100644 --- a/Minecraft.Client/Extrax64Stubs.cpp +++ b/Minecraft.Client/Extrax64Stubs.cpp @@ -21,7 +21,7 @@ #include "Windows64\Social\SocialManager.h" #include "Windows64\Sentient\DynamicConfigurations.h" #include "Windows64\Network\WinsockNetLayer.h" -#include "Windows64\Windows64_NameXuid.h" +#include "Windows64\Windows64_Xuid.h" #elif defined __PSVITA__ #include "PSVita\Sentient\SentientManager.h" #include "StatsCounter.h" @@ -201,7 +201,15 @@ DWORD IQNetPlayer::GetCurrentRtt() { return 0; } bool IQNetPlayer::IsHost() { return m_isHostPlayer; } bool IQNetPlayer::IsGuest() { return false; } bool IQNetPlayer::IsLocal() { return !m_isRemote; } -PlayerUID IQNetPlayer::GetXuid() { return (PlayerUID)(0xe000d45248242f2e + m_smallId); } // todo: restore to INVALID_XUID once saves support this +PlayerUID IQNetPlayer::GetXuid() +{ + // Compatibility model: + // - Preferred path: use per-player resolved XUID populated from login/add-player flow. + // - Fallback path: keep legacy base+smallId behavior for peers/saves still on old scheme. + if (m_resolvedXuid != INVALID_XUID) + return m_resolvedXuid; + return (PlayerUID)(0xe000d45248242f2e + m_smallId); +} LPCWSTR IQNetPlayer::GetGamertag() { return m_gamertag; } int IQNetPlayer::GetSessionIndex() { return m_smallId; } bool IQNetPlayer::IsTalking() { return false; } @@ -227,6 +235,7 @@ void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost player->m_smallId = smallId; player->m_isRemote = !isLocal; player->m_isHostPlayer = isHost; + player->m_resolvedXuid = INVALID_XUID; swprintf_s(player->m_gamertag, 32, L"Player%d", smallId); if (smallId >= IQNet::s_playerCount) IQNet::s_playerCount = smallId + 1; @@ -286,20 +295,13 @@ IQNetPlayer* IQNet::GetPlayerByXuid(PlayerUID xuid) { for (DWORD i = 0; i < s_playerCount; i++) { - // Conventional XUID implementation except for Windows 64. if (!Win64_IsActivePlayer(&m_player[i], i)) continue; if (m_player[i].GetXuid() == xuid) return &m_player[i]; - - // For Windows 64, NameBase XUID is used. -#ifdef _WINDOWS64 - PlayerUID nameXuid = Win64NameXuid::ResolvePersistentXuidFromName(m_player[i].GetGamertag()); - if (nameXuid == xuid) - return &m_player[i]; -#endif } + // Keep existing stub behavior: return host slot instead of NULL on miss. return &m_player[0]; } DWORD IQNet::GetPlayerCount() @@ -314,7 +316,13 @@ DWORD IQNet::GetPlayerCount() QNET_STATE IQNet::GetState() { return _iQNetStubState; } bool IQNet::IsHost() { return s_isHosting; } HRESULT IQNet::JoinGameFromInviteInfo(DWORD dwUserIndex, DWORD dwUserMask, const INVITE_INFO * pInviteInfo) { return S_OK; } -void IQNet::HostGame() { _iQNetStubState = QNET_STATE_SESSION_STARTING; s_isHosting = true; } +void IQNet::HostGame() +{ + _iQNetStubState = QNET_STATE_SESSION_STARTING; + s_isHosting = true; + // Host slot keeps legacy XUID so old host player data remains addressable. + m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid(); +} void IQNet::ClientJoinGame() { _iQNetStubState = QNET_STATE_SESSION_STARTING; @@ -325,6 +333,7 @@ void IQNet::ClientJoinGame() m_player[i].m_smallId = (BYTE)i; m_player[i].m_isRemote = true; m_player[i].m_isHostPlayer = false; + m_player[i].m_resolvedXuid = INVALID_XUID; m_player[i].m_gamertag[0] = 0; m_player[i].SetCustomDataValue(0); } @@ -339,6 +348,7 @@ void IQNet::EndGame() m_player[i].m_smallId = (BYTE)i; m_player[i].m_isRemote = false; m_player[i].m_isHostPlayer = false; + m_player[i].m_resolvedXuid = INVALID_XUID; m_player[i].m_gamertag[0] = 0; m_player[i].SetCustomDataValue(0); } @@ -584,10 +594,13 @@ void C_4JProfile::GetXUID(int iPad, PlayerUID * pXuid, bool bOnlineXuid) *pXuid = INVALID_XUID; return; } + // LoginPacket reads this value as client identity: + // - host keeps legacy host XUID for world compatibility + // - non-host uses persistent uid.dat-backed XUID if (IQNet::s_isHosting) - *pXuid = 0xe000d45248242f2e; + *pXuid = Win64Xuid::GetLegacyEmbeddedHostXuid(); else - *pXuid = 0xe000d45248242f2e + WinsockNetLayer::GetLocalSmallId(); + *pXuid = Win64Xuid::ResolvePersistentXuid(); #else * pXuid = 0xe000d45248242f2e + iPad; #endif diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp index 197d66836..662d5fafc 100644 --- a/Minecraft.Client/Minecraft.cpp +++ b/Minecraft.Client/Minecraft.cpp @@ -52,7 +52,7 @@ #include "..\Minecraft.World\net.minecraft.world.level.dimension.h" #include "..\Minecraft.World\net.minecraft.world.item.h" #include "..\Minecraft.World\Minecraft.World.h" -#include "Windows64\Windows64_NameXuid.h" +#include "Windows64\Windows64_Xuid.h" #include "ClientConnection.h" #include "..\Minecraft.World\HellRandomLevelSource.h" #include "..\Minecraft.World\net.minecraft.world.entity.animal.h" @@ -1040,14 +1040,16 @@ shared_ptr Minecraft::createExtraLocalPlayer(int idx, co ProfileManager.GetXUID(idx,&playerXUIDOffline,false); ProfileManager.GetXUID(idx,&playerXUIDOnline,true); #ifdef _WINDOWS64 + // Compatibility rule for Win64 id migration + // host keeps legacy host XUID, non-host uses persistent uid.dat XUID. INetworkPlayer *localNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(idx); if(localNetworkPlayer != NULL && localNetworkPlayer->IsHost()) { - playerXUIDOffline = Win64NameXuid::GetLegacyEmbeddedHostXuid(); + playerXUIDOffline = Win64Xuid::GetLegacyEmbeddedHostXuid(); } else { - playerXUIDOffline = Win64NameXuid::ResolvePersistentXuidFromName(localplayers[idx]->name); + playerXUIDOffline = Win64Xuid::ResolvePersistentXuid(); } #endif localplayers[idx]->setXuid(playerXUIDOffline); @@ -4309,14 +4311,16 @@ void Minecraft::setLevel(MultiPlayerLevel *level, int message /*=-1*/, shared_pt } #endif #ifdef _WINDOWS64 + // On Windows, the implementation has been changed to use a per-client pseudo XUID based on `uid.dat`. + // To maintain player data compatibility with existing worlds, the world host (the first player) will use the previous embedded pseudo XUID. INetworkPlayer *localNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(iPrimaryPlayer); if(localNetworkPlayer != NULL && localNetworkPlayer->IsHost()) { - playerXUIDOffline = Win64NameXuid::GetLegacyEmbeddedHostXuid(); + playerXUIDOffline = Win64Xuid::GetLegacyEmbeddedHostXuid(); } else { - playerXUIDOffline = Win64NameXuid::ResolvePersistentXuidFromName(player->name); + playerXUIDOffline = Win64Xuid::ResolvePersistentXuid(); } #endif player->setXuid(playerXUIDOffline); @@ -4502,14 +4506,15 @@ void Minecraft::respawnPlayer(int iPad, int dimension, int newEntityId) ProfileManager.GetXUID(iTempPad,&playerXUIDOffline,false); ProfileManager.GetXUID(iTempPad,&playerXUIDOnline,true); #ifdef _WINDOWS64 + // Same compatibility rule as create/init paths. INetworkPlayer *localNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(iTempPad); if(localNetworkPlayer != NULL && localNetworkPlayer->IsHost()) { - playerXUIDOffline = Win64NameXuid::GetLegacyEmbeddedHostXuid(); + playerXUIDOffline = Win64Xuid::GetLegacyEmbeddedHostXuid(); } else { - playerXUIDOffline = Win64NameXuid::ResolvePersistentXuidFromName(player->name); + playerXUIDOffline = Win64Xuid::ResolvePersistentXuid(); } #endif player->setXuid(playerXUIDOffline); diff --git a/Minecraft.Client/PendingConnection.cpp b/Minecraft.Client/PendingConnection.cpp index 8034dff2b..77c0a7cff 100644 --- a/Minecraft.Client/PendingConnection.cpp +++ b/Minecraft.Client/PendingConnection.cpp @@ -162,6 +162,23 @@ void PendingConnection::handleLogin(shared_ptr packet) //if (true)// 4J removed !server->onlineMode) bool sentDisconnect = false; + // Use the same Xuid choice as handleAcceptedLogin (offline first, online fallback). + // + PlayerUID loginXuid = packet->m_offlineXuid; + if (loginXuid == INVALID_XUID) loginXuid = packet->m_onlineXuid; + + bool duplicateXuid = false; + if (loginXuid != INVALID_XUID && server->getPlayers()->getPlayer(loginXuid) != nullptr) + { + duplicateXuid = true; + } + else if (packet->m_onlineXuid != INVALID_XUID && + packet->m_onlineXuid != loginXuid && + server->getPlayers()->getPlayer(packet->m_onlineXuid) != nullptr) + { + duplicateXuid = true; + } + if( sentDisconnect ) { // Do nothing @@ -170,6 +187,12 @@ void PendingConnection::handleLogin(shared_ptr packet) { disconnect(DisconnectPacket::eDisconnect_Banned); } + else if (duplicateXuid) + { + // if same XUID already in use by another player so disconnect this one. + app.DebugPrintf("Rejecting duplicate xuid for name: %ls\n", name.c_str()); + disconnect(DisconnectPacket::eDisconnect_Banned); + } #ifdef _WINDOWS64 else if (g_bRejectDuplicateNames) { @@ -258,7 +281,7 @@ void PendingConnection::onDisconnect(DisconnectPacket::eDisconnectReason reason, void PendingConnection::handleGetInfo(shared_ptr packet) { //try { - //String message = server->motd + "§" + server->players->getPlayerCount() + "§" + server->players->getMaxPlayers(); + //String message = server->motd + "�" + server->players->getPlayerCount() + "�" + server->players->getMaxPlayers(); //connection->send(new DisconnectPacket(message)); connection->send(shared_ptr(new DisconnectPacket(DisconnectPacket::eDisconnect_ServerFull) ) ); connection->sendAndQuit(); diff --git a/Minecraft.Client/PlayerList.cpp b/Minecraft.Client/PlayerList.cpp index 350cc7164..83a70bf5d 100644 --- a/Minecraft.Client/PlayerList.cpp +++ b/Minecraft.Client/PlayerList.cpp @@ -16,7 +16,7 @@ #include "..\Minecraft.World\ArrayWithLength.h" #include "..\Minecraft.World\net.minecraft.network.packet.h" #include "..\Minecraft.World\net.minecraft.network.h" -#include "Windows64\Windows64_NameXuid.h" +#include "Windows64\Windows64_Xuid.h" #include "..\Minecraft.World\Pos.h" #include "..\Minecraft.World\ProgressListener.h" #include "..\Minecraft.World\HellRandomLevelSource.h" @@ -105,11 +105,18 @@ void PlayerList::placeNewPlayer(Connection *connection, shared_ptr } #endif #ifdef _WINDOWS64 - if (networkPlayer != NULL && !networkPlayer->IsLocal()) + if (networkPlayer != NULL) { NetworkPlayerXbox* nxp = (NetworkPlayerXbox*)networkPlayer; IQNetPlayer* qnp = nxp->GetQNetPlayer(); - wcsncpy_s(qnp->m_gamertag, 32, player->name.c_str(), _TRUNCATE); + if (qnp != NULL) + { + if (!networkPlayer->IsLocal()) + { + wcsncpy_s(qnp->m_gamertag, 32, player->name.c_str(), _TRUNCATE); + } + qnp->m_resolvedXuid = player->getXuid(); + } } #endif // 4J Stu - TU-1 hotfix @@ -521,9 +528,8 @@ shared_ptr PlayerList::getPlayerForLogin(PendingConnection *pendin player->setOnlineXuid( onlineXuid ); // 4J Added #ifdef _WINDOWS64 { - PlayerUID persistentXuid = Win64NameXuid::ResolvePersistentXuidFromName(userName); - player->setXuid(persistentXuid); - + // Use packet-supplied identity from LoginPacket. + // Do not recompute from name here: mixed-version clients must stay compatible. INetworkPlayer* np = pendingConnection->connection->getSocket()->getPlayer(); if (np != NULL) { @@ -531,9 +537,10 @@ shared_ptr PlayerList::getPlayerForLogin(PendingConnection *pendin // Backward compatibility: when Minecraft.Client is hosting, keep the first // host player on the legacy embedded host XUID (base + 0). + // This preserves pre-migration host playerdata in existing worlds. if (np->IsHost()) { - player->setXuid(Win64NameXuid::GetLegacyEmbeddedHostXuid()); + player->setXuid(Win64Xuid::GetLegacyEmbeddedHostXuid()); } } } diff --git a/Minecraft.Client/Windows64/Windows64_NameXuid.h b/Minecraft.Client/Windows64/Windows64_NameXuid.h deleted file mode 100644 index 2d6821d47..000000000 --- a/Minecraft.Client/Windows64/Windows64_NameXuid.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#ifdef _WINDOWS64 - -#include - -namespace Win64NameXuid -{ - inline PlayerUID GetLegacyEmbeddedBaseXuid() - { - return (PlayerUID)0xe000d45248242f2eULL; - } - - inline PlayerUID GetLegacyEmbeddedHostXuid() - { - // Legacy behavior used "embedded base + smallId"; host was always smallId 0. - return GetLegacyEmbeddedBaseXuid(); - } - - /** - * ## Resolves a persistent 64-bit player ID from the player's username. - * - * We keep this deterministic so existing player save/map systems can key off XUID. - * - * @param playerName The player's username. - * @return The resolved PlayerUID. - */ - inline PlayerUID ResolvePersistentXuidFromName(const std::wstring &playerName) - { - const unsigned __int64 fnvOffset = 14695981039346656037ULL; - const unsigned __int64 fnvPrime = 1099511628211ULL; - unsigned __int64 hash = fnvOffset; - - for (size_t i = 0; i < playerName.length(); ++i) - { - unsigned short codeUnit = (unsigned short)playerName[i]; - hash ^= (unsigned __int64)(codeUnit & 0xFF); - hash *= fnvPrime; - hash ^= (unsigned __int64)((codeUnit >> 8) & 0xFF); - hash *= fnvPrime; - } - - // Namespace the hash away from legacy smallId-based values. - hash ^= 0x9E3779B97F4A7C15ULL; - hash |= 0x8000000000000000ULL; - - if (hash == (unsigned __int64)INVALID_XUID) - { - hash ^= 0x0100000000000001ULL; - } - - return (PlayerUID)hash; - } -} - -#endif - diff --git a/Minecraft.Client/Windows64/Windows64_Xuid.h b/Minecraft.Client/Windows64/Windows64_Xuid.h new file mode 100644 index 000000000..93577751a --- /dev/null +++ b/Minecraft.Client/Windows64/Windows64_Xuid.h @@ -0,0 +1,214 @@ +#pragma once + +#ifdef _WINDOWS64 + +#include +#include +#include +#include +#include +#include + +namespace Win64Xuid +{ + inline PlayerUID GetLegacyEmbeddedBaseXuid() + { + return (PlayerUID)0xe000d45248242f2eULL; + } + + inline PlayerUID GetLegacyEmbeddedHostXuid() + { + // Legacy behavior used "embedded base + smallId"; host was always smallId 0. + // We intentionally keep this value for host/self compatibility with pre-migration worlds. + return GetLegacyEmbeddedBaseXuid(); + } + + inline bool IsLegacyEmbeddedRange(PlayerUID xuid) + { + // Old Win64 XUIDs were not persistent and always lived in this narrow base+smallId range. + // Treat them as legacy/non-persistent so uid.dat values never collide with old slot IDs. + const PlayerUID base = GetLegacyEmbeddedBaseXuid(); + return xuid >= base && xuid < (base + MINECRAFT_NET_MAX_PLAYERS); + } + + inline bool IsPersistedUidValid(PlayerUID xuid) + { + return xuid != INVALID_XUID && !IsLegacyEmbeddedRange(xuid); + } + + + // ./uid.dat + inline bool BuildUidFilePath(char* outPath, size_t outPathSize) + { + if (outPath == NULL || outPathSize == 0) + return false; + + outPath[0] = 0; + + char exePath[MAX_PATH] = {}; + DWORD len = GetModuleFileNameA(NULL, exePath, MAX_PATH); + if (len == 0 || len >= MAX_PATH) + return false; + + char* lastSlash = strrchr(exePath, '\\'); + if (lastSlash != NULL) + { + *(lastSlash + 1) = 0; + } + + if (strcpy_s(outPath, outPathSize, exePath) != 0) + return false; + if (strcat_s(outPath, outPathSize, "uid.dat") != 0) + return false; + + return true; + } + + inline bool ReadUid(PlayerUID* outXuid) + { + if (outXuid == NULL) + return false; + + char path[MAX_PATH] = {}; + if (!BuildUidFilePath(path, MAX_PATH)) + return false; + + FILE* f = NULL; + if (fopen_s(&f, path, "rb") != 0 || f == NULL) + return false; + + char buffer[128] = {}; + size_t readBytes = fread(buffer, 1, sizeof(buffer) - 1, f); + fclose(f); + + if (readBytes == 0) + return false; + + // Compatibility: earlier experiments may have written raw 8-byte uid.dat. + if (readBytes == sizeof(unsigned __int64)) + { + unsigned __int64 raw = 0; + memcpy(&raw, buffer, sizeof(raw)); + PlayerUID parsed = (PlayerUID)raw; + if (IsPersistedUidValid(parsed)) + { + *outXuid = parsed; + return true; + } + } + + buffer[readBytes] = 0; + char* begin = buffer; + while (*begin == ' ' || *begin == '\t' || *begin == '\r' || *begin == '\n') + { + ++begin; + } + + errno = 0; + char* end = NULL; + unsigned __int64 raw = _strtoui64(begin, &end, 0); + if (begin == end || errno != 0) + return false; + + while (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n') + { + ++end; + } + if (*end != 0) + return false; + + PlayerUID parsed = (PlayerUID)raw; + if (!IsPersistedUidValid(parsed)) + return false; + + *outXuid = parsed; + return true; + } + + inline bool WriteUid(PlayerUID xuid) + { + char path[MAX_PATH] = {}; + if (!BuildUidFilePath(path, MAX_PATH)) + return false; + + FILE* f = NULL; + if (fopen_s(&f, path, "wb") != 0 || f == NULL) + return false; + + int written = fprintf_s(f, "0x%016llX\n", (unsigned long long)xuid); + fclose(f); + return written > 0; + } + + inline unsigned __int64 Mix64(unsigned __int64 x) + { + x += 0x9E3779B97F4A7C15ULL; + x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ULL; + x = (x ^ (x >> 27)) * 0x94D049BB133111EBULL; + return x ^ (x >> 31); + } + + inline PlayerUID GeneratePersistentUid() + { + // Avoid rand_s dependency: mix several Win64 runtime values into a 64-bit seed. + FILETIME ft = {}; + GetSystemTimeAsFileTime(&ft); + unsigned __int64 t = (((unsigned __int64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + + LARGE_INTEGER qpc = {}; + QueryPerformanceCounter(&qpc); + + unsigned __int64 seed = t; + seed ^= (unsigned __int64)qpc.QuadPart; + seed ^= ((unsigned __int64)GetCurrentProcessId() << 32); + seed ^= (unsigned __int64)GetCurrentThreadId(); + seed ^= (unsigned __int64)GetTickCount(); + seed ^= (unsigned __int64)(size_t)&qpc; + seed ^= (unsigned __int64)(size_t)GetModuleHandleA(NULL); + + unsigned __int64 raw = Mix64(seed) ^ Mix64(seed + 0xA0761D6478BD642FULL); + raw ^= 0x8F4B2D6C1A93E705ULL; + raw |= 0x8000000000000000ULL; + + PlayerUID xuid = (PlayerUID)raw; + if (!IsPersistedUidValid(xuid)) + { + raw ^= 0x0100000000000001ULL; + xuid = (PlayerUID)raw; + } + + if (!IsPersistedUidValid(xuid)) + { + // Last-resort deterministic fallback for pathological cases. + xuid = (PlayerUID)0xD15EA5E000000001ULL; + } + + return xuid; + } + + inline PlayerUID ResolvePersistentXuid() + { + // Process-local cache: uid.dat is immutable during runtime and this path is hot. + static bool s_cached = false; + static PlayerUID s_xuid = INVALID_XUID; + + if (s_cached) + return s_xuid; + + PlayerUID fileXuid = INVALID_XUID; + if (ReadUid(&fileXuid)) + { + s_xuid = fileXuid; + s_cached = true; + return s_xuid; + } + + // First launch on this client: generate once and persist to uid.dat. + s_xuid = GeneratePersistentUid(); + WriteUid(s_xuid); + s_cached = true; + return s_xuid; + } +} + +#endif diff --git a/Minecraft.World/x64headers/extraX64.h b/Minecraft.World/x64headers/extraX64.h index b89998ace..fa6ee0f99 100644 --- a/Minecraft.World/x64headers/extraX64.h +++ b/Minecraft.World/x64headers/extraX64.h @@ -219,6 +219,7 @@ public: BYTE m_smallId; bool m_isRemote; bool m_isHostPlayer; + PlayerUID m_resolvedXuid; wchar_t m_gamertag[32]; private: ULONG_PTR m_customData;