aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Client
diff options
context:
space:
mode:
authorkuwa <kuwa.com3@gmail.com>2026-03-06 15:01:36 +0900
committerGitHub <noreply@github.com>2026-03-06 00:01:36 -0600
commit182d76f3916e17727e3dcf9e38db2132360215dd (patch)
tree7c9993f0ffe90575ea1568f428ebe25d70f9b77d /Minecraft.Client
parentc45541080fc34ce57a9e49f917b8d847634987c4 (diff)
Introduce uid.dat (offline PlayerUIDs), fix multiplayer save data persistence (#536)
* fix: fix multiplayer player data mix between different players bug Fixes a Win64 multiplayer issue where player data (`players/*.dat`) could be mismatched because identity was effectively tied to connection-order `smallId` XUIDs. Introduces a deterministic username-derived persistent XUID and integrates it into the existing XUID-based save pipeline. - Added `Windows64_NameXuid` for deterministic `name -> persistent xuid` resolution - On Win64 login (`PlayerList`), set `ServerPlayer::xuid` from username-based resolver - Aligned local player `xuid` assignment (`Minecraft`) for create/init/respawn paths to use the same resolver - Added Win64 local-self guard in `ClientConnection::handleAddPlayer` using name match to avoid duplicate local remote-player creation - Kept `IQNet::GetPlayerByXuid` compatibility fallback behavior, while extending lookup to also resolve username-based XUIDs - Moved implementation to `Minecraft.Client/Windows64/Windows64_NameXuid.h`; kept legacy `Win64NameXuid.h` as compatibility include Rename migration is intentionally out of scope (same-name identity only). * fix: preserve legacy host xuid (base xuid + 0) for existing world compatibility - Add legacy embedded host XUID helper (base + 0). - When Minecraft.Client is hosting, force only the first host player to use legacy host XUID. - Keep name-based XUID for non-host players. - Prevent old singleplayer/hosted worlds from losing/mismatching host player data. * 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. * update: ensure uid.dat exists at startup in client mode for multiplayer
Diffstat (limited to 'Minecraft.Client')
-rw-r--r--Minecraft.Client/ClientConnection.cpp48
-rw-r--r--Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp8
-rw-r--r--Minecraft.Client/Extrax64Stubs.cpp36
-rw-r--r--Minecraft.Client/Minecraft.cpp39
-rw-r--r--Minecraft.Client/PendingConnection.cpp23
-rw-r--r--Minecraft.Client/PlayerList.cpp26
-rw-r--r--Minecraft.Client/Windows64/Windows64_Minecraft.cpp7
-rw-r--r--Minecraft.Client/Windows64/Windows64_Xuid.h214
8 files changed, 388 insertions, 13 deletions
diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp
index 2270433d..9b9bb86a 100644
--- a/Minecraft.Client/ClientConnection.cpp
+++ b/Minecraft.Client/ClientConnection.cpp
@@ -759,6 +759,18 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> packet)
return;
}
}
+#ifdef _WINDOWS64
+ // 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)
+ {
+ if (minecraft->localplayers[idx] != NULL && minecraft->localplayers[idx]->name == packet->name)
+ {
+ app.DebugPrintf("AddPlayerPacket received for local player name %ls\n", packet->name.c_str());
+ return;
+ }
+ }
+#endif
/*#ifdef _WINDOWS64
// On Windows64 all XUIDs are INVALID_XUID so the XUID check above never fires.
// packet->m_playerIndex is the server-assigned sequential index (set via LoginPacket),
@@ -800,8 +812,10 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> 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);
@@ -809,13 +823,39 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> packet)
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
@@ -985,6 +1025,8 @@ void ClientConnection::handleRemoveEntity(shared_ptr<RemoveEntitiesPacket> 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);
}
@@ -3956,4 +3998,4 @@ ClientConnection::DeferredEntityLinkPacket::DeferredEntityLinkPacket(shared_ptr<
{
m_recievedTick = GetTickCount();
m_packet = packet;
-} \ No newline at end of file
+}
diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
index 85531e47..80fbd98c 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"
#include <iostream>
@@ -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 29865e40..9e693d1a 100644
--- a/Minecraft.Client/Extrax64Stubs.cpp
+++ b/Minecraft.Client/Extrax64Stubs.cpp
@@ -21,6 +21,7 @@
#include "Windows64\Social\SocialManager.h"
#include "Windows64\Sentient\DynamicConfigurations.h"
#include "Windows64\Network\WinsockNetLayer.h"
+#include "Windows64\Windows64_Xuid.h"
#elif defined __PSVITA__
#include "PSVita\Sentient\SentientManager.h"
#include "StatsCounter.h"
@@ -200,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; }
@@ -226,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;
@@ -285,8 +295,13 @@ IQNetPlayer* IQNet::GetPlayerByXuid(PlayerUID xuid)
{
for (DWORD i = 0; i < s_playerCount; i++)
{
- if (Win64_IsActivePlayer(&m_player[i], i) && m_player[i].GetXuid() == xuid) return &m_player[i];
+ if (!Win64_IsActivePlayer(&m_player[i], i))
+ continue;
+
+ if (m_player[i].GetXuid() == xuid)
+ return &m_player[i];
}
+ // Keep existing stub behavior: return host slot instead of NULL on miss.
return &m_player[0];
}
DWORD IQNet::GetPlayerCount()
@@ -301,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;
@@ -312,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);
}
@@ -326,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);
}
@@ -575,10 +598,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 b87745f0..7ae206cf 100644
--- a/Minecraft.Client/Minecraft.cpp
+++ b/Minecraft.Client/Minecraft.cpp
@@ -52,6 +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_Xuid.h"
#include "ClientConnection.h"
#include "..\Minecraft.World\HellRandomLevelSource.h"
#include "..\Minecraft.World\net.minecraft.world.entity.animal.h"
@@ -1038,6 +1039,19 @@ shared_ptr<MultiplayerLocalPlayer> Minecraft::createExtraLocalPlayer(int idx, co
PlayerUID playerXUIDOnline = INVALID_XUID;
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 = Win64Xuid::GetLegacyEmbeddedHostXuid();
+ }
+ else
+ {
+ playerXUIDOffline = Win64Xuid::ResolvePersistentXuid();
+ }
+#endif
localplayers[idx]->setXuid(playerXUIDOffline);
localplayers[idx]->setOnlineXuid(playerXUIDOnline);
localplayers[idx]->setIsGuest(ProfileManager.IsGuest(idx));
@@ -4299,6 +4313,19 @@ void Minecraft::setLevel(MultiPlayerLevel *level, int message /*=-1*/, shared_pt
playerXUIDOnline.setForAdhoc();
}
#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 = Win64Xuid::GetLegacyEmbeddedHostXuid();
+ }
+ else
+ {
+ playerXUIDOffline = Win64Xuid::ResolvePersistentXuid();
+ }
+#endif
player->setXuid(playerXUIDOffline);
player->setOnlineXuid(playerXUIDOnline);
@@ -4481,6 +4508,18 @@ void Minecraft::respawnPlayer(int iPad, int dimension, int newEntityId)
PlayerUID playerXUIDOnline = INVALID_XUID;
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 = Win64Xuid::GetLegacyEmbeddedHostXuid();
+ }
+ else
+ {
+ playerXUIDOffline = Win64Xuid::ResolvePersistentXuid();
+ }
+#endif
player->setXuid(playerXUIDOffline);
player->setOnlineXuid(playerXUIDOnline);
player->setIsGuest( ProfileManager.IsGuest(iTempPad) );
diff --git a/Minecraft.Client/PendingConnection.cpp b/Minecraft.Client/PendingConnection.cpp
index b50669e5..72b8f382 100644
--- a/Minecraft.Client/PendingConnection.cpp
+++ b/Minecraft.Client/PendingConnection.cpp
@@ -161,6 +161,23 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> 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
@@ -169,6 +186,12 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> 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)
{
diff --git a/Minecraft.Client/PlayerList.cpp b/Minecraft.Client/PlayerList.cpp
index c1ccfe14..42724a4f 100644
--- a/Minecraft.Client/PlayerList.cpp
+++ b/Minecraft.Client/PlayerList.cpp
@@ -18,6 +18,7 @@
#include "..\Minecraft.World\ArrayWithLength.h"
#include "..\Minecraft.World\net.minecraft.network.packet.h"
#include "..\Minecraft.World\net.minecraft.network.h"
+#include "Windows64\Windows64_Xuid.h"
#include "..\Minecraft.World\Pos.h"
#include "..\Minecraft.World\ProgressListener.h"
#include "..\Minecraft.World\HellRandomLevelSource.h"
@@ -106,11 +107,18 @@ void PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
}
#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
@@ -520,12 +528,20 @@ shared_ptr<ServerPlayer> PlayerList::getPlayerForLogin(PendingConnection *pendin
player->setOnlineXuid( onlineXuid ); // 4J Added
#ifdef _WINDOWS64
{
+ // 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)
{
- PlayerUID realXuid = np->GetUID();
- player->setXuid(realXuid);
- player->setOnlineXuid(realXuid);
+ player->setOnlineXuid(np->GetUID());
+
+ // 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(Win64Xuid::GetLegacyEmbeddedHostXuid());
+ }
}
}
#endif
diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
index 1146b86d..fa4b8366 100644
--- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
+++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
@@ -42,6 +42,7 @@
#include "..\..\Minecraft.World\OldChunkStorage.h"
#include "Common/PostProcesser.h"
#include "Network\WinsockNetLayer.h"
+#include "Windows64_Xuid.h"
#include "Xbox/resource.h"
@@ -1221,6 +1222,12 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
Win64LaunchOptions launchOptions = ParseLaunchOptions();
ApplyScreenMode(launchOptions.screenMode);
+ // Ensure uid.dat exists from startup in client mode (before any multiplayer/login path).
+ if (!launchOptions.serverMode)
+ {
+ Win64Xuid::ResolvePersistentXuid();
+ }
+
// If no username, let's fall back
if (g_Win64Username[0] == 0)
{
diff --git a/Minecraft.Client/Windows64/Windows64_Xuid.h b/Minecraft.Client/Windows64/Windows64_Xuid.h
new file mode 100644
index 00000000..93577751
--- /dev/null
+++ b/Minecraft.Client/Windows64/Windows64_Xuid.h
@@ -0,0 +1,214 @@
+#pragma once
+
+#ifdef _WINDOWS64
+
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+#include <cerrno>
+#include <cstring>
+#include <Windows.h>
+
+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