diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index dadad842..41645675 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -9,6 +9,7 @@ #include "../../Windows64/Windows64_LceLive.h" #include "../../Windows64/Windows64_LceLiveP2P.h" #include "../../Windows64/Windows64_LceLiveSignaling.h" +#include "../../Windows64/Windows64_LceLiveRelay.h" #include "../../Minecraft.h" #include "../../User.h" #include "../../MinecraftServer.h" @@ -448,6 +449,7 @@ bool CPlatformNetworkManagerStub::LeaveGame(bool bMigrateHost) #ifdef _WINDOWS64 Win64LceLiveSignaling::Close(); // close signaling WebSocket before P2P teardown + Win64LceLiveRelay::Close(); // close relay tunnel (prevents stale TCP forwarding/session ghosts) Win64LceLiveP2P::HostClose(); // tear down P2P socket + remove UPnP mapping WinsockNetLayer::Shutdown(); WinsockNetLayer::Initialize(); diff --git a/Minecraft.Client/Common/UI/UIScene.cpp b/Minecraft.Client/Common/UI/UIScene.cpp index b29a7e56..aeb6774a 100644 --- a/Minecraft.Client/Common/UI/UIScene.cpp +++ b/Minecraft.Client/Common/UI/UIScene.cpp @@ -312,14 +312,48 @@ void UIScene::loadMovie() { app.DebugPrintf("WARNING: Could not find iggy movie %ls, falling back on 720\n", moviePath.c_str()); - moviePath = getMoviePath(); - moviePath.append(L"720.swf"); - m_loadedResolution = eSceneResolution_720; + const wstring movieBasePath = getMoviePath(); + bool foundFallback = false; - if(!app.hasArchiveFile(moviePath)) + // Try the common fallback chain. Some scenes (for example Keyboard on Win64) + // only ship 1080 assets and do not have a 720 variant. + wstring tryPath = movieBasePath; + tryPath.append(L"720.swf"); + if(app.hasArchiveFile(tryPath)) { - app.DebugPrintf("ERROR: Could not find any iggy movie for %ls!\n", moviePath.c_str()); -#ifndef _CONTENT_PACKAGE + moviePath = tryPath; + m_loadedResolution = eSceneResolution_720; + foundFallback = true; + } + + if(!foundFallback) + { + tryPath = movieBasePath; + tryPath.append(L"1080.swf"); + if(app.hasArchiveFile(tryPath)) + { + moviePath = tryPath; + m_loadedResolution = eSceneResolution_1080; + foundFallback = true; + } + } + + if(!foundFallback) + { + tryPath = movieBasePath; + tryPath.append(L"480.swf"); + if(app.hasArchiveFile(tryPath)) + { + moviePath = tryPath; + m_loadedResolution = eSceneResolution_480; + foundFallback = true; + } + } + + if(!foundFallback) + { + app.DebugPrintf("ERROR: Could not find any iggy movie for %ls!\n", movieBasePath.c_str()); +#if !defined(_CONTENT_PACKAGE) && defined(_DEBUG) __debugbreak(); #endif app.FatalLoadError(); @@ -334,7 +368,7 @@ void UIScene::loadMovie() if(!swf) { app.DebugPrintf("ERROR: Failed to load iggy scene!\n"); -#ifndef _CONTENT_PACKAGE +#if !defined(_CONTENT_PACKAGE) && defined(_DEBUG) __debugbreak(); #endif app.FatalLoadError(); diff --git a/Minecraft.Client/Common/UI/UIScene_LceLiveInvites.cpp b/Minecraft.Client/Common/UI/UIScene_LceLiveInvites.cpp index e11ce81e..f3d1414d 100644 --- a/Minecraft.Client/Common/UI/UIScene_LceLiveInvites.cpp +++ b/Minecraft.Client/Common/UI/UIScene_LceLiveInvites.cpp @@ -486,7 +486,22 @@ void UIScene_LceLiveInvites::InvitePendingFriend() const int hostPort = WinsockNetLayer::GetHostPort(); // Include the P2P signaling session ID so the joiner can do hole punching. - const std::string signalingSessionId = Win64LceLiveSignaling::GetSnapshot().sessionId; + // Guard against stale IDs: invites sent while signaling is not active can + // fail immediately for joiners (for example WS close 4317). + const Win64LceLiveSignaling::SignalingSnapshot sigSnap = + Win64LceLiveSignaling::GetSnapshot(); + const bool signalingReady = + (sigSnap.state == Win64LceLiveSignaling::ESignalingState::Connecting || + sigSnap.state == Win64LceLiveSignaling::ESignalingState::Connected); + if (!signalingReady || sigSnap.sessionId.empty()) + { + m_statusMessage = L"Invite channel is refreshing. Try again in a second."; + m_pendingInviteAccountId.clear(); + m_pendingInviteLabel.clear(); + UpdateStatusLabel(); + return; + } + const std::string signalingSessionId = sigSnap.sessionId; const Win64LceLive::SocialActionResult result = Win64LceLive::SendGameInviteSync( m_pendingInviteAccountId, diff --git a/Minecraft.Client/Windows64/Windows64_LceLiveRelay.cpp b/Minecraft.Client/Windows64/Windows64_LceLiveRelay.cpp index e9b40888..5b0f094c 100644 --- a/Minecraft.Client/Windows64/Windows64_LceLiveRelay.cpp +++ b/Minecraft.Client/Windows64/Windows64_LceLiveRelay.cpp @@ -917,7 +917,7 @@ namespace Win64LceLiveRelay EnterCriticalSection(&g_relay.lock); CloseHandlesLocked(); - g_relay.state = ERelayState::Closed; + g_relay.state = ERelayState::Idle; g_relay.lastError.clear(); LeaveCriticalSection(&g_relay.lock); diff --git a/Minecraft.Client/Windows64/Windows64_LceLiveSignaling.cpp b/Minecraft.Client/Windows64/Windows64_LceLiveSignaling.cpp index f8ee7628..0e5ce00e 100644 --- a/Minecraft.Client/Windows64/Windows64_LceLiveSignaling.cpp +++ b/Minecraft.Client/Windows64/Windows64_LceLiveSignaling.cpp @@ -167,6 +167,7 @@ namespace // Handle the main thread can close to unblock a stuck WinHttpWebSocketReceive. volatile HINTERNET wsHandle; + volatile bool stopRequested; }; // ------------------------------------------------------------------------- @@ -249,11 +250,22 @@ namespace + L"?sessionId=" + Utf8ToWideLocal(ctx->sessionId) + L"&role=" + Utf8ToWideLocal(ctx->isHost ? "host" : "joiner"); - LCELOG("SIG", "connecting to %s%ls (role=%s sessionId=%s)", + int reconnectAttempts = 0; + static const int kMaxReconnectAttempts = 6; + + reconnect_websocket: + if (ctx->stopRequested) + { + ctx->workerDone = true; + return 0; + } + + LCELOG("SIG", "connecting to %s%ls (role=%s sessionId=%s attempt=%d)", secure ? "wss://" : "ws://", (hostW + wsPath).c_str(), ctx->isHost ? "host" : "joiner", - ctx->sessionId.c_str()); + ctx->sessionId.c_str(), + reconnectAttempts + 1); // ---- Open WinHTTP session ---- HINTERNET hSession = WinHttpOpen( @@ -270,7 +282,7 @@ namespace return 0; } - WinHttpSetTimeouts(hSession, 10000, 10000, 30000, 30000); + WinHttpSetTimeouts(hSession, 10000, 10000, 120000, 120000); HINTERNET hConnect = WinHttpConnect(hSession, hostW.c_str(), components.nPort, 0); if (hConnect == nullptr) @@ -387,7 +399,7 @@ namespace std::vector recvBuf(8192); bool done = false; - while (!done) + while (!done && !ctx->stopRequested) { DWORD bytesRead = 0; WINHTTP_WEB_SOCKET_BUFFER_TYPE bufType = WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE; @@ -401,7 +413,22 @@ namespace if (recvErr != ERROR_SUCCESS) { - // Closed from main thread (Close() called) or network error. + // Closed from main thread (Close() called) or network/transient backend error. + if (!ctx->stopRequested && !ctx->peerReceived && reconnectAttempts < kMaxReconnectAttempts) + { + reconnectAttempts++; + LCELOG("SIG", "receive ended (%lu) — reconnecting attempt %d/%d", + recvErr, reconnectAttempts, kMaxReconnectAttempts); + + WinHttpWebSocketClose(hWs, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, nullptr, 0); + WinHttpCloseHandle(hWs); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + ctx->wsHandle = nullptr; + Sleep(750); + goto reconnect_websocket; + } + LCELOG("SIG", "receive ended (%lu)", recvErr); break; } @@ -492,6 +519,7 @@ namespace ctx->peerPort = 0; ctx->peerNeedsHolePunch = false; ctx->wsHandle = nullptr; + ctx->stopRequested = false; g_sig.state = Win64LceLiveSignaling::ESignalingState::Connecting; g_sig.sessionId = sessionId; @@ -678,7 +706,10 @@ namespace Win64LceLiveSignaling HINTERNET wsToClose = nullptr; EnterCriticalSection(&g_sig.lock); if (g_sig.workerCtx != nullptr) + { + g_sig.workerCtx->stopRequested = true; wsToClose = g_sig.workerCtx->wsHandle; + } LeaveCriticalSection(&g_sig.lock); if (wsToClose != nullptr) @@ -707,7 +738,7 @@ namespace Win64LceLiveSignaling g_sig.workerCtx = nullptr; } g_sig.workerThread = nullptr; - g_sig.state = ESignalingState::Closed; + g_sig.state = ESignalingState::Idle; g_sig.sessionId.clear(); g_sig.peerIp.clear(); g_sig.peerPort = 0; diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index ea9acbf1..4b67d24e 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -1825,6 +1825,16 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, const Win64LceLiveSignaling::ESignalingState sigState = Win64LceLiveSignaling::GetSnapshot().state; + if (WinsockNetLayer::IsHosting() && + (sigState == Win64LceLiveSignaling::ESignalingState::PeerKnown || + sigState == Win64LceLiveSignaling::ESignalingState::Failed || + sigState == Win64LceLiveSignaling::ESignalingState::Closed)) + { + // Recycle stale/consumed signaling sessions so subsequent invites carry + // a fresh active session ID instead of a dead one. + Win64LceLiveSignaling::Close(); + } + // Host: edge-trigger on first Ready frame (requires active session). if (WinsockNetLayer::IsHosting() && WinsockNetLayer::IsActive() &&