From f8cbd4f8445a18287e5d2555d70fced598e87ef9 Mon Sep 17 00:00:00 2001 From: kuwacom Date: Thu, 5 Mar 2026 12:29:15 +0900 Subject: [PATCH] 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). --- Minecraft.Client/ClientConnection.cpp | 14 +++++- Minecraft.Client/Extrax64Stubs.cpp | 15 +++++- Minecraft.Client/Minecraft.cpp | 10 ++++ Minecraft.Client/PlayerList.cpp | 8 ++-- .../Windows64/Windows64_NameXuid.h | 46 +++++++++++++++++++ 5 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 Minecraft.Client/Windows64/Windows64_NameXuid.h diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index 8123a2f0..fd27eb04 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -762,6 +762,18 @@ void ClientConnection::handleAddPlayer(shared_ptr packet) return; } } +#ifdef _WINDOWS64 + // Win64 player-data XUIDs are resolved from player name, so 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), @@ -3957,4 +3969,4 @@ ClientConnection::DeferredEntityLinkPacket::DeferredEntityLinkPacket(shared_ptr< { m_recievedTick = GetTickCount(); m_packet = packet; -} \ No newline at end of file +} diff --git a/Minecraft.Client/Extrax64Stubs.cpp b/Minecraft.Client/Extrax64Stubs.cpp index 5a3c5279..1e2b6c2c 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_NameXuid.h" #elif defined __PSVITA__ #include "PSVita\Sentient\SentientManager.h" #include "StatsCounter.h" @@ -285,7 +286,19 @@ 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]; + // 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 } return &m_player[0]; } diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp index 19ee79bd..1a176fa1 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_NameXuid.h" #include "ClientConnection.h" #include "..\Minecraft.World\HellRandomLevelSource.h" #include "..\Minecraft.World\net.minecraft.world.entity.animal.h" @@ -1038,6 +1039,9 @@ shared_ptr Minecraft::createExtraLocalPlayer(int idx, co PlayerUID playerXUIDOnline = INVALID_XUID; ProfileManager.GetXUID(idx,&playerXUIDOffline,false); ProfileManager.GetXUID(idx,&playerXUIDOnline,true); +#ifdef _WINDOWS64 + playerXUIDOffline = Win64NameXuid::ResolvePersistentXuidFromName(localplayers[idx]->name); +#endif localplayers[idx]->setXuid(playerXUIDOffline); localplayers[idx]->setOnlineXuid(playerXUIDOnline); localplayers[idx]->setIsGuest(ProfileManager.IsGuest(idx)); @@ -4295,6 +4299,9 @@ void Minecraft::setLevel(MultiPlayerLevel *level, int message /*=-1*/, shared_pt // player doesn't have an online UID, set it from the player name playerXUIDOnline.setForAdhoc(); } +#endif +#ifdef _WINDOWS64 + playerXUIDOffline = Win64NameXuid::ResolvePersistentXuidFromName(player->name); #endif player->setXuid(playerXUIDOffline); player->setOnlineXuid(playerXUIDOnline); @@ -4478,6 +4485,9 @@ 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 + playerXUIDOffline = Win64NameXuid::ResolvePersistentXuidFromName(player->name); +#endif player->setXuid(playerXUIDOffline); player->setOnlineXuid(playerXUIDOnline); player->setIsGuest( ProfileManager.IsGuest(iTempPad) ); diff --git a/Minecraft.Client/PlayerList.cpp b/Minecraft.Client/PlayerList.cpp index 0de0c36b..1810c152 100644 --- a/Minecraft.Client/PlayerList.cpp +++ b/Minecraft.Client/PlayerList.cpp @@ -16,6 +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 "..\Minecraft.World\Pos.h" #include "..\Minecraft.World\ProgressListener.h" #include "..\Minecraft.World\HellRandomLevelSource.h" @@ -520,12 +521,13 @@ shared_ptr PlayerList::getPlayerForLogin(PendingConnection *pendin player->setOnlineXuid( onlineXuid ); // 4J Added #ifdef _WINDOWS64 { + PlayerUID persistentXuid = Win64NameXuid::ResolvePersistentXuidFromName(userName); + player->setXuid(persistentXuid); + INetworkPlayer* np = pendingConnection->connection->getSocket()->getPlayer(); if (np != NULL) { - PlayerUID realXuid = np->GetUID(); - player->setXuid(realXuid); - player->setOnlineXuid(realXuid); + player->setOnlineXuid(np->GetUID()); } } #endif diff --git a/Minecraft.Client/Windows64/Windows64_NameXuid.h b/Minecraft.Client/Windows64/Windows64_NameXuid.h new file mode 100644 index 00000000..5b3e8e1a --- /dev/null +++ b/Minecraft.Client/Windows64/Windows64_NameXuid.h @@ -0,0 +1,46 @@ +#pragma once + +#ifdef _WINDOWS64 + +#include + +namespace Win64NameXuid +{ + /** + * ## 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 +