diff options
| author | kuwa <kuwa.com3@gmail.com> | 2026-03-06 15:01:36 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-06 00:01:36 -0600 |
| commit | 182d76f3916e17727e3dcf9e38db2132360215dd (patch) | |
| tree | 7c9993f0ffe90575ea1568f428ebe25d70f9b77d /Minecraft.Client/Extrax64Stubs.cpp | |
| parent | c45541080fc34ce57a9e49f917b8d847634987c4 (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/Extrax64Stubs.cpp')
| -rw-r--r-- | Minecraft.Client/Extrax64Stubs.cpp | 36 |
1 files changed, 31 insertions, 5 deletions
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 |
