aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Client/ClientConnection.cpp
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/ClientConnection.cpp
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/ClientConnection.cpp')
-rw-r--r--Minecraft.Client/ClientConnection.cpp48
1 files changed, 45 insertions, 3 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
+}