diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.cpp b/Minecraft.Client/Common/Network/GameNetworkManager.cpp index 2b415fe0..ec352d80 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Common/Network/GameNetworkManager.cpp @@ -773,31 +773,10 @@ void CGameNetworkManager::CancelJoinGame(LPVOID lpParam) s_pPlatformNetworkManager->CancelJoinGame(); #endif #ifdef _WINDOWS64 - static_cast(s_pPlatformNetworkManager)->CancelJoinGame(); + WinsockNetLayer::CancelJoinGame(); #endif } -#ifdef _WINDOWS64 -bool CGameNetworkManager::BeginJoinGameAsync(FriendSessionInfo *searchResult, int localUsersMask) -{ - app.SetTutorialMode(false); - g_NetworkManager.SetLocalGame(false); - - int primaryUserIndex = ProfileManager.GetLockedProfile(); - - Minecraft::GetInstance()->clearConnectionFailed(); - - localUsersMask |= GetLocalPlayerMask(ProfileManager.GetPrimaryPad()); - - return static_cast(s_pPlatformNetworkManager)->BeginJoinGameAsync(searchResult, localUsersMask, primaryUserIndex); -} - -int CGameNetworkManager::FinishJoinGame(FriendSessionInfo *searchResult) -{ - return static_cast(s_pPlatformNetworkManager)->FinishJoinGame(searchResult); -} -#endif - bool CGameNetworkManager::LeaveGame(bool bMigrateHost) { Minecraft::GetInstance()->gui->clearMessages(); diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.h b/Minecraft.Client/Common/Network/GameNetworkManager.h index 5f105165..22d58807 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.h +++ b/Minecraft.Client/Common/Network/GameNetworkManager.h @@ -47,7 +47,8 @@ public: { JOINGAME_SUCCESS, JOINGAME_FAIL_GENERAL, - JOINGAME_FAIL_SERVER_FULL + JOINGAME_FAIL_SERVER_FULL, + JOINGAME_PENDING } eJoinGameResult; void Initialise(); @@ -106,10 +107,6 @@ public: bool JoinGameFromInviteInfo( int userIndex, int userMask, const INVITE_INFO *pInviteInfo); eJoinGameResult JoinGame(FriendSessionInfo *searchResult, int localUsersMask); static void CancelJoinGame(LPVOID lpParam); // Not part of the shared interface -#ifdef _WINDOWS64 - bool BeginJoinGameAsync(FriendSessionInfo *searchResult, int localUsersMask); - int FinishJoinGame(FriendSessionInfo *searchResult); -#endif bool LeaveGame(bool bMigrateHost); static int JoinFromInvite_SignInReturned(void *pParam,bool bContinue, int iPad); void UpdateAndSetGameSessionData(INetworkPlayer *pNetworkPlayerLeaving = nullptr); diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index ff0d6fbb..da990f8e 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -173,6 +173,9 @@ bool CPlatformNetworkManagerStub::Initialise(CGameNetworkManager *pGameNetworkMa m_bSearchPending = false; m_bIsOfflineGame = false; +#ifdef _WINDOWS64 + m_bJoinPending = false; +#endif m_pSearchParam = nullptr; m_SessionsUpdatedCallback = nullptr; @@ -282,6 +285,39 @@ void CPlatformNetworkManagerStub::DoWork() m_bLeaveGameOnTick = false; } } + + // Async join finalization: when the background thread reports success, + // register players and transition the session to starting state. + if (m_bJoinPending) + { + WinsockNetLayer::eJoinState state = WinsockNetLayer::GetJoinState(); + if (state == WinsockNetLayer::eJoinState_Success) + { + WinsockNetLayer::FinalizeJoin(); + + BYTE localSmallId = WinsockNetLayer::GetLocalSmallId(); + IQNet::m_player[localSmallId].m_smallId = localSmallId; + IQNet::m_player[localSmallId].m_isRemote = false; + IQNet::m_player[localSmallId].m_isHostPlayer = false; + 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()); + IQNet::s_playerCount = localSmallId + 1; + + NotifyPlayerJoined(&IQNet::m_player[0]); + NotifyPlayerJoined(&IQNet::m_player[localSmallId]); + + m_pGameNetworkManager->StateChange_AnyToStarting(); + m_bJoinPending = false; + } + else if (state == WinsockNetLayer::eJoinState_Failed || + state == WinsockNetLayer::eJoinState_Rejected || + state == WinsockNetLayer::eJoinState_Cancelled) + { + m_bJoinPending = false; + } + } #endif } @@ -517,90 +553,19 @@ int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo* searchResult, int l WinsockNetLayer::StopDiscovery(); - if (!WinsockNetLayer::JoinGame(hostIP, hostPort)) + if (!WinsockNetLayer::BeginJoinGame(hostIP, hostPort)) { - app.DebugPrintf("Win64 LAN: Failed to connect to %s:%d\n", hostIP, hostPort); + app.DebugPrintf("Win64 LAN: Failed to start async join to %s:%d\n", hostIP, hostPort); return CGameNetworkManager::JOINGAME_FAIL_GENERAL; } - BYTE localSmallId = WinsockNetLayer::GetLocalSmallId(); - - 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()); - IQNet::s_playerCount = localSmallId + 1; - - NotifyPlayerJoined(&IQNet::m_player[0]); - NotifyPlayerJoined(&IQNet::m_player[localSmallId]); - - m_pGameNetworkManager->StateChange_AnyToStarting(); - - return CGameNetworkManager::JOINGAME_SUCCESS; + m_bJoinPending = true; + return CGameNetworkManager::JOINGAME_PENDING; #else return CGameNetworkManager::JOINGAME_SUCCESS; #endif } -#ifdef _WINDOWS64 -bool CPlatformNetworkManagerStub::BeginJoinGameAsync(FriendSessionInfo* searchResult, int localUsersMask, int primaryUserIndex) -{ - if (searchResult == nullptr) - return false; - - const char* hostIP = searchResult->data.hostIP; - int hostPort = searchResult->data.hostPort; - - if (hostPort <= 0 || hostIP[0] == 0) - return false; - - m_bLeavingGame = false; - m_bLeaveGameOnTick = false; - IQNet::s_isHosting = false; - m_pIQNet->ClientJoinGame(); - - IQNet::m_player[0].m_smallId = 0; - IQNet::m_player[0].m_isRemote = true; - IQNet::m_player[0].m_isHostPlayer = true; - IQNet::m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid(); - wcsncpy_s(IQNet::m_player[0].m_gamertag, 32, searchResult->data.hostName, _TRUNCATE); - - WinsockNetLayer::StopDiscovery(); - - return WinsockNetLayer::StartJoinGameAsync(hostIP, hostPort); -} - -int CPlatformNetworkManagerStub::FinishJoinGame(FriendSessionInfo* searchResult) -{ - BYTE localSmallId = WinsockNetLayer::GetLocalSmallId(); - - IQNet::m_player[localSmallId].m_smallId = localSmallId; - IQNet::m_player[localSmallId].m_isRemote = false; - IQNet::m_player[localSmallId].m_isHostPlayer = false; - 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()); - IQNet::s_playerCount = localSmallId + 1; - - NotifyPlayerJoined(&IQNet::m_player[0]); - NotifyPlayerJoined(&IQNet::m_player[localSmallId]); - - m_pGameNetworkManager->StateChange_AnyToStarting(); - - return CGameNetworkManager::JOINGAME_SUCCESS; -} - -void CPlatformNetworkManagerStub::CancelJoinGame() -{ - WinsockNetLayer::CancelJoinGame(); -} -#endif - bool CPlatformNetworkManagerStub::SetLocalGame(bool isLocal) { m_bIsOfflineGame = isLocal; diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h index 77c3a7eb..7b17ceb1 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h @@ -42,11 +42,6 @@ public: virtual void HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0); virtual int JoinGame(FriendSessionInfo *searchResult, int localUsersMask, int primaryUserIndex ); -#ifdef _WINDOWS64 - bool BeginJoinGameAsync(FriendSessionInfo *searchResult, int localUsersMask, int primaryUserIndex); - int FinishJoinGame(FriendSessionInfo *searchResult); - void CancelJoinGame(); -#endif virtual bool SetLocalGame(bool isLocal); virtual bool IsLocalGame() { return m_bIsOfflineGame; } virtual void SetPrivateGame(bool isPrivate); @@ -81,6 +76,9 @@ private: bool m_bIsOfflineGame; bool m_bIsPrivateGame; int m_flagIndexSize; +#ifdef _WINDOWS64 + bool m_bJoinPending; +#endif // This is only maintained by the host, and is not valid on client machines GameSessionData m_hostGameSessionData; diff --git a/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.cpp b/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.cpp index e10a5a62..e0e388db 100644 --- a/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.cpp +++ b/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.cpp @@ -2,6 +2,15 @@ #include "UI.h" #include "UIScene_ConnectingProgress.h" #include "..\..\Minecraft.h" +#ifdef _WINDOWS64 +#include "..\..\Windows64\Network\WinsockNetLayer.h" + +static int ConnectingProgress_OnRejectedDialogOK(LPVOID, int iPad, const C4JStorage::EMessageResult) +{ + ui.NavigateBack(iPad); + return 0; +} +#endif UIScene_ConnectingProgress::UIScene_ConnectingProgress(int iPad, void *_initData, UILayer *parentLayer) : UIScene(iPad, parentLayer) { @@ -43,6 +52,12 @@ UIScene_ConnectingProgress::UIScene_ConnectingProgress(int iPad, void *_initData m_cancelFuncParam = param->cancelFuncParam; m_removeLocalPlayer = false; m_showingButton = false; +#ifdef _WINDOWS64 + WinsockNetLayer::eJoinState initState = WinsockNetLayer::GetJoinState(); + m_asyncJoinActive = (initState == WinsockNetLayer::eJoinState_Connecting || + initState == WinsockNetLayer::eJoinState_Success); + m_asyncJoinFailed = false; +#endif } UIScene_ConnectingProgress::~UIScene_ConnectingProgress() @@ -53,6 +68,18 @@ UIScene_ConnectingProgress::~UIScene_ConnectingProgress() void UIScene_ConnectingProgress::updateTooltips() { +#ifdef _WINDOWS64 + if (m_asyncJoinActive) + { + ui.SetTooltips(m_iPad, -1, IDS_TOOLTIPS_BACK); + return; + } + if (m_asyncJoinFailed) + { + ui.SetTooltips(m_iPad, IDS_TOOLTIPS_SELECT, -1); + return; + } +#endif // 4J-PB - removing the option of cancel join, since it didn't work anyway //ui.SetTooltips( m_iPad, -1, m_showTooltips?IDS_TOOLTIPS_CANCEL_JOIN:-1); ui.SetTooltips( m_iPad, -1, -1); @@ -62,6 +89,61 @@ void UIScene_ConnectingProgress::tick() { UIScene::tick(); +#ifdef _WINDOWS64 + if (m_asyncJoinActive) + { + WinsockNetLayer::eJoinState state = WinsockNetLayer::GetJoinState(); + switch (state) + { + case WinsockNetLayer::eJoinState_Connecting: + { + int attempt = WinsockNetLayer::GetJoinAttempt(); + int maxAttempts = WinsockNetLayer::GetJoinMaxAttempts(); + wchar_t buf[128]; + if (attempt > 1) + swprintf_s(buf, L"Connecting... (attempt %d/%d)", attempt, maxAttempts); + else + swprintf_s(buf, L"Connecting..."); + m_labelTitle.setLabel(buf); + break; + } + case WinsockNetLayer::eJoinState_Success: + m_asyncJoinActive = false; + m_labelTitle.setLabel(L"Joining world..."); + break; + case WinsockNetLayer::eJoinState_Cancelled: + m_asyncJoinActive = false; + navigateBack(); + break; + case WinsockNetLayer::eJoinState_Rejected: + { + m_asyncJoinActive = false; + DisconnectPacket::eDisconnectReason reason = WinsockNetLayer::GetJoinRejectReason(); + int reasonStringId = IDS_CONNECTION_LOST_SERVER; + if (reason == DisconnectPacket::eDisconnect_ServerFull) + reasonStringId = IDS_DISCONNECTED_SERVER_FULL; + else if (reason == DisconnectPacket::eDisconnect_Kicked) + reasonStringId = IDS_DISCONNECTED_KICKED; + + UINT uiIDA[1]; + uiIDA[0] = IDS_CONFIRM_OK; + ui.RequestErrorMessage(IDS_CONNECTION_FAILED, reasonStringId, uiIDA, 1, m_iPad, ConnectingProgress_OnRejectedDialogOK, nullptr); + break; + } + case WinsockNetLayer::eJoinState_Failed: + m_asyncJoinActive = false; + m_asyncJoinFailed = true; + m_labelTitle.setLabel(app.GetString(IDS_CONNECTION_FAILED)); + m_buttonConfirm.setVisible(true); + m_showingButton = true; + updateTooltips(); + break; + default: + break; + } + } +#endif + if( m_removeLocalPlayer ) { m_removeLocalPlayer = false; @@ -94,6 +176,7 @@ void UIScene_ConnectingProgress::handleGainFocus(bool navBack) void UIScene_ConnectingProgress::handleLoseFocus() { + if (!m_runFailTimer) return; int millisecsLeft = getTimer(0)->targetTime - System::currentTimeMillis(); int millisecsTaken = getTimer(0)->duration - millisecsLeft; app.DebugPrintf("\n"); @@ -207,6 +290,18 @@ void UIScene_ConnectingProgress::handleInput(int iPad, int key, bool repeat, boo switch(key) { +#ifdef _WINDOWS64 + case ACTION_MENU_CANCEL: + if (pressed && m_asyncJoinActive) + { + m_asyncJoinActive = false; + WinsockNetLayer::CancelJoinGame(); + navigateBack(); + handled = true; + return; + } + break; +#endif // 4J-PB - Removed the option to cancel join - it didn't work anyway // case ACTION_MENU_CANCEL: // { @@ -250,6 +345,13 @@ void UIScene_ConnectingProgress::handlePress(F64 controlId, F64 childId) case eControl_Confirm: if(m_showingButton) { +#ifdef _WINDOWS64 + if (m_asyncJoinFailed) + { + navigateBack(); + break; + } +#endif if( m_iPad != ProfileManager.GetPrimaryPad() && g_NetworkManager.IsInSession() ) { // The connection failed if we see the button, so the temp player should be removed and the viewports updated again diff --git a/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.h b/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.h index 2c52284c..09eddc18 100644 --- a/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.h +++ b/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.h @@ -12,6 +12,10 @@ private: bool m_showingButton; void (*m_cancelFunc)(LPVOID param); LPVOID m_cancelFuncParam; +#ifdef _WINDOWS64 + bool m_asyncJoinActive; + bool m_asyncJoinFailed; +#endif enum EControls { diff --git a/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp b/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp index 176a4749..4390f92b 100644 --- a/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp @@ -29,8 +29,6 @@ UIScene_JoinMenu::UIScene_JoinMenu(int iPad, void *_initData, UILayer *parentLay m_editServerPhase = eEditServer_Idle; m_editServerButtonIndex = -1; m_deleteServerButtonIndex = -1; - m_asyncJoinInProgress = false; - m_joinLocalUsersMask = 0; #endif } @@ -64,35 +62,6 @@ void UIScene_JoinMenu::updateTooltips() void UIScene_JoinMenu::tick() { -#ifdef _WINDOWS64 - if (m_asyncJoinInProgress && WinsockNetLayer::IsJoinComplete()) - { - m_asyncJoinInProgress = false; - if (WinsockNetLayer::GetJoinResult()) - { - int result = g_NetworkManager.FinishJoinGame(m_selectedSession); - app.SetLiveLinkRequired(false); - if (result != CGameNetworkManager::JOINGAME_SUCCESS) - { - m_bIgnoreInput = false; - m_buttonJoinGame.setLabel(app.GetString(IDS_JOIN_GAME)); - updateTooltips(); - } - } - else - { - app.SetLiveLinkRequired(false); - m_bIgnoreInput = false; - m_buttonJoinGame.setLabel(app.GetString(IDS_JOIN_GAME)); - updateTooltips(); - - UINT uiIDA[1]; - uiIDA[0] = IDS_CONFIRM_OK; - ui.RequestErrorMessage(IDS_CONNECTION_FAILED, IDS_CONNECTION_LOST_SERVER, uiIDA, 1, ProfileManager.GetPrimaryPad()); - } - } -#endif - if( !m_friendInfoRequestIssued ) { ui.NavigateToScene(m_iPad, eUIScene_Timer); @@ -316,19 +285,6 @@ wstring UIScene_JoinMenu::getMoviePath() void UIScene_JoinMenu::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled) { -#ifdef _WINDOWS64 - if (m_asyncJoinInProgress && key == ACTION_MENU_CANCEL && pressed) - { - g_NetworkManager.CancelJoinGame(nullptr); - m_asyncJoinInProgress = false; - m_bIgnoreInput = false; - m_buttonJoinGame.setLabel(app.GetString(IDS_JOIN_GAME)); - updateTooltips(); - handled = true; - return; - } -#endif - if(m_bIgnoreInput) return; ui.AnimateKeyPress(m_iPad, key, repeat, pressed, released); @@ -625,23 +581,28 @@ void UIScene_JoinMenu::JoinGame(UIScene_JoinMenu* pClass) ProfileManager.DisplaySystemMessage( SCE_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_CHAT_RESTRICTION, ProfileManager.GetPrimaryPad() ); } #endif -#ifdef _WINDOWS64 - if (g_NetworkManager.BeginJoinGameAsync(pClass->m_selectedSession, dwLocalUsersMask)) - { - pClass->m_asyncJoinInProgress = true; - pClass->m_joinLocalUsersMask = dwLocalUsersMask; - pClass->m_buttonJoinGame.setLabel(app.GetString(IDS_PROGRESS_CONNECTING)); - ui.SetTooltips(DEFAULT_XUI_MENU_USER, -1, IDS_TOOLTIPS_CANCEL_JOIN); - return; - } - CGameNetworkManager::eJoinGameResult result = CGameNetworkManager::JOINGAME_FAIL_GENERAL; -#else CGameNetworkManager::eJoinGameResult result = g_NetworkManager.JoinGame( pClass->m_selectedSession, dwLocalUsersMask ); -#endif // Alert the app the we no longer want to be informed of ethernet connections app.SetLiveLinkRequired( false ); +#ifdef _WINDOWS64 + if (result == CGameNetworkManager::JOINGAME_PENDING) + { + pClass->m_bIgnoreInput = false; + ConnectionProgressParams *param = new ConnectionProgressParams(); + param->iPad = ProfileManager.GetPrimaryPad(); + param->stringId = IDS_PROGRESS_CONNECTING; + param->showTooltips = true; + param->setFailTimer = false; + param->timerTime = 0; + param->cancelFunc = nullptr; + param->cancelFuncParam = nullptr; + ui.NavigateToScene(ProfileManager.GetPrimaryPad(), eUIScene_ConnectingProgress, param); + return; + } +#endif + if( result != CGameNetworkManager::JOINGAME_SUCCESS ) { int exitReasonStringId = -1; diff --git a/Minecraft.Client/Common/UI/UIScene_JoinMenu.h b/Minecraft.Client/Common/UI/UIScene_JoinMenu.h index 7c555e84..566697cd 100644 --- a/Minecraft.Client/Common/UI/UIScene_JoinMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_JoinMenu.h @@ -70,8 +70,6 @@ private: wstring m_editServerPort; int m_editServerButtonIndex; int m_deleteServerButtonIndex; - bool m_asyncJoinInProgress; - DWORD m_joinLocalUsersMask; #endif public: diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index 130ab531..2694f789 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -67,12 +67,14 @@ SOCKET WinsockNetLayer::s_splitScreenSocket[XUSER_MAX_COUNT] = { INVALID_SOCKET, BYTE WinsockNetLayer::s_splitScreenSmallId[XUSER_MAX_COUNT] = { 0xFF, 0xFF, 0xFF, 0xFF }; HANDLE WinsockNetLayer::s_splitScreenRecvThread[XUSER_MAX_COUNT] = {nullptr, nullptr, nullptr, nullptr}; -volatile bool WinsockNetLayer::s_joinCancelled = false; -volatile bool WinsockNetLayer::s_joinComplete = false; -bool WinsockNetLayer::s_joinResult = false; -HANDLE WinsockNetLayer::s_joinGameThread = nullptr; +HANDLE WinsockNetLayer::s_joinThread = nullptr; +volatile WinsockNetLayer::eJoinState WinsockNetLayer::s_joinState = WinsockNetLayer::eJoinState_Idle; +volatile int WinsockNetLayer::s_joinAttempt = 0; +volatile bool WinsockNetLayer::s_joinCancel = false; char WinsockNetLayer::s_joinIP[256] = {}; int WinsockNetLayer::s_joinPort = 0; +BYTE WinsockNetLayer::s_joinAssignedSmallId = 0; +DisconnectPacket::eDisconnectReason WinsockNetLayer::s_joinRejectReason = DisconnectPacket::eDisconnect_Quitting; bool g_Win64MultiplayerHost = false; bool g_Win64MultiplayerJoin = false; @@ -121,6 +123,15 @@ void WinsockNetLayer::Shutdown() StopAdvertising(); StopDiscovery(); + s_joinCancel = true; + if (s_joinThread != nullptr) + { + WaitForSingleObject(s_joinThread, 5000); + CloseHandle(s_joinThread); + s_joinThread = nullptr; + } + s_joinState = eJoinState_Idle; + s_active = false; s_connected = false; @@ -349,7 +360,7 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) for (int attempt = 0; attempt < maxAttempts; ++attempt) { - if (s_joinCancelled) + if (s_joinCancel) { app.DebugPrintf("JoinGame cancelled by user\n"); break; @@ -478,56 +489,235 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) return true; } -DWORD WINAPI WinsockNetLayer::JoinGameThreadProc(LPVOID param) +bool WinsockNetLayer::BeginJoinGame(const char* ip, int port) { - s_joinResult = JoinGame(s_joinIP, s_joinPort); - s_joinComplete = true; - return 0; -} + if (!s_initialized && !Initialize()) return false; -bool WinsockNetLayer::StartJoinGameAsync(const char* ip, int port) -{ - // Wait for any previous join thread to finish - if (s_joinGameThread != nullptr) + // Clean up any prior join attempt + CancelJoinGame(); + if (s_joinThread != nullptr) { - WaitForSingleObject(s_joinGameThread, 5000); - CloseHandle(s_joinGameThread); - s_joinGameThread = nullptr; + WaitForSingleObject(s_joinThread, 5000); + CloseHandle(s_joinThread); + s_joinThread = nullptr; + } + + s_isHost = false; + s_hostSmallId = 0; + s_connected = false; + s_active = false; + + if (s_hostConnectionSocket != INVALID_SOCKET) + { + closesocket(s_hostConnectionSocket); + s_hostConnectionSocket = INVALID_SOCKET; + } + + if (s_clientRecvThread != nullptr) + { + WaitForSingleObject(s_clientRecvThread, 5000); + CloseHandle(s_clientRecvThread); + s_clientRecvThread = nullptr; } strncpy_s(s_joinIP, sizeof(s_joinIP), ip, _TRUNCATE); s_joinPort = port; - s_joinCancelled = false; - s_joinComplete = false; - s_joinResult = false; + s_joinAttempt = 0; + s_joinCancel = false; + s_joinAssignedSmallId = 0; + s_joinRejectReason = DisconnectPacket::eDisconnect_Quitting; + s_joinState = eJoinState_Connecting; - s_joinGameThread = CreateThread(nullptr, 0, JoinGameThreadProc, nullptr, 0, nullptr); - return s_joinGameThread != nullptr; + s_joinThread = CreateThread(nullptr, 0, JoinThreadProc, nullptr, 0, nullptr); + if (s_joinThread == nullptr) + { + s_joinState = eJoinState_Failed; + return false; + } + return true; } -bool WinsockNetLayer::IsJoinComplete() +DWORD WINAPI WinsockNetLayer::JoinThreadProc(LPVOID param) { - return s_joinComplete; -} + struct addrinfo hints = {}, *result = nullptr; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; -bool WinsockNetLayer::GetJoinResult() -{ - return s_joinResult; + char portStr[16]; + sprintf_s(portStr, "%d", s_joinPort); + + if (getaddrinfo(s_joinIP, portStr, &hints, &result) != 0) + { + app.DebugPrintf("getaddrinfo failed for %s:%d\n", s_joinIP, s_joinPort); + s_joinState = eJoinState_Failed; + return 0; + } + + bool connected = false; + BYTE assignedSmallId = 0; + SOCKET sock = INVALID_SOCKET; + const int connectTimeoutSec = 5; + + for (int attempt = 0; attempt < JOIN_MAX_ATTEMPTS; ++attempt) + { + if (s_joinCancel) { freeaddrinfo(result); s_joinState = eJoinState_Cancelled; return 0; } + + s_joinAttempt = attempt + 1; + + sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (sock == INVALID_SOCKET) break; + + int noDelay = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay)); + + // Non-blocking connect with select() timeout + u_long nonBlocking = 1; + ioctlsocket(sock, FIONBIO, &nonBlocking); + + int iResult = connect(sock, result->ai_addr, static_cast(result->ai_addrlen)); + if (iResult == SOCKET_ERROR) + { + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) + { + fd_set writeSet, errorSet; + FD_ZERO(&writeSet); FD_SET(sock, &writeSet); + FD_ZERO(&errorSet); FD_SET(sock, &errorSet); + struct timeval tv = { connectTimeoutSec, 0 }; + + int selectResult = select(0, nullptr, &writeSet, &errorSet, &tv); + if (selectResult <= 0 || FD_ISSET(sock, &errorSet)) + { + app.DebugPrintf("connect() to %s:%d timed out (attempt %d/%d)\n", s_joinIP, s_joinPort, attempt + 1, JOIN_MAX_ATTEMPTS); + closesocket(sock); sock = INVALID_SOCKET; + continue; + } + } + else + { + app.DebugPrintf("connect() to %s:%d failed (attempt %d/%d): %d\n", s_joinIP, s_joinPort, attempt + 1, JOIN_MAX_ATTEMPTS, err); + closesocket(sock); sock = INVALID_SOCKET; + for (int w = 0; w < 4 && !s_joinCancel; w++) Sleep(50); + continue; + } + } + + // Restore blocking mode + u_long blocking = 0; + ioctlsocket(sock, FIONBIO, &blocking); + + // Temporary recv timeout for the handshake only + DWORD recvTimeout = connectTimeoutSec * 1000; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&recvTimeout, sizeof(recvTimeout)); + + BYTE assignBuf[1]; + if (recv(sock, (char*)assignBuf, 1, 0) != 1) + { + app.DebugPrintf("Failed to receive small ID assignment from host (attempt %d/%d)\n", attempt + 1, JOIN_MAX_ATTEMPTS); + closesocket(sock); sock = INVALID_SOCKET; + continue; + } + + if (assignBuf[0] == WIN64_SMALLID_REJECT) + { + BYTE rejectBuf[5]; + if (!RecvExact(sock, rejectBuf, 5)) + { + app.DebugPrintf("Failed to receive reject reason from host\n"); + closesocket(sock); sock = INVALID_SOCKET; + continue; + } + int reason = ((rejectBuf[1] & 0xff) << 24) | ((rejectBuf[2] & 0xff) << 16) | + ((rejectBuf[3] & 0xff) << 8) | (rejectBuf[4] & 0xff); + s_joinRejectReason = (DisconnectPacket::eDisconnectReason)reason; + closesocket(sock); + freeaddrinfo(result); + s_joinState = eJoinState_Rejected; + return 0; + } + + assignedSmallId = assignBuf[0]; + connected = true; + break; + } + freeaddrinfo(result); + + if (s_joinCancel) + { + if (sock != INVALID_SOCKET) closesocket(sock); + s_joinState = eJoinState_Cancelled; + return 0; + } + + if (!connected) + { + s_joinState = eJoinState_Failed; + return 0; + } + + // Clear recv timeout before handing socket to recv thread + DWORD noTimeout = 0; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&noTimeout, sizeof(noTimeout)); + + s_hostConnectionSocket = sock; + s_joinAssignedSmallId = assignedSmallId; + s_joinState = eJoinState_Success; + return 0; } void WinsockNetLayer::CancelJoinGame() { - s_joinCancelled = true; + s_joinCancel = true; - // Close the socket to immediately unblock any in-progress connect/select/recv + // Close socket to immediately unblock any in-progress connect/select/recv SOCKET sock = s_hostConnectionSocket; if (sock != INVALID_SOCKET) { s_hostConnectionSocket = INVALID_SOCKET; closesocket(sock); } + + if (s_joinState == eJoinState_Success || s_joinState == eJoinState_Connecting) + { + s_joinState = eJoinState_Cancelled; + } } +bool WinsockNetLayer::FinalizeJoin() +{ + if (s_joinState != eJoinState_Success) + return false; + + s_localSmallId = s_joinAssignedSmallId; + + strncpy_s(g_Win64MultiplayerIP, sizeof(g_Win64MultiplayerIP), s_joinIP, _TRUNCATE); + g_Win64MultiplayerPort = s_joinPort; + + app.DebugPrintf("Win64 LAN: Connected to %s:%d, assigned smallId=%d\n", + s_joinIP, s_joinPort, s_localSmallId); + + s_active = true; + s_connected = true; + + s_clientRecvThread = CreateThread(nullptr, 0, ClientRecvThreadProc, nullptr, 0, nullptr); + + if (s_joinThread != nullptr) + { + WaitForSingleObject(s_joinThread, 2000); + CloseHandle(s_joinThread); + s_joinThread = nullptr; + } + + s_joinState = eJoinState_Idle; + return true; +} + +WinsockNetLayer::eJoinState WinsockNetLayer::GetJoinState() { return s_joinState; } +int WinsockNetLayer::GetJoinAttempt() { return s_joinAttempt; } +int WinsockNetLayer::GetJoinMaxAttempts() { return JOIN_MAX_ATTEMPTS; } +DisconnectPacket::eDisconnectReason WinsockNetLayer::GetJoinRejectReason() { return s_joinRejectReason; } + bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize) { if (sock == INVALID_SOCKET || dataSize <= 0 || dataSize > WIN64_NET_MAX_PACKET_SIZE) return false; diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h index 9bf67cfb..f3c7e299 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h @@ -8,6 +8,7 @@ #include #include #include "..\..\Common\Network\NetworkPlayerInterface.h" +#include "..\..\..\Minecraft.World\DisconnectPacket.h" #pragma comment(lib, "Ws2_32.lib") @@ -69,11 +70,23 @@ public: static bool HostGame(int port, const char* bindIp = nullptr); static bool JoinGame(const char* ip, int port); - // Async join: runs JoinGame on a background thread so the UI stays responsive - static bool StartJoinGameAsync(const char* ip, int port); - static bool IsJoinComplete(); - static bool GetJoinResult(); + // Async join: runs connection on a background thread so the UI stays responsive + enum eJoinState + { + eJoinState_Idle, + eJoinState_Connecting, + eJoinState_Success, + eJoinState_Failed, + eJoinState_Rejected, + eJoinState_Cancelled + }; + static bool BeginJoinGame(const char* ip, int port); static void CancelJoinGame(); + static bool FinalizeJoin(); + static eJoinState GetJoinState(); + static int GetJoinAttempt(); + static int GetJoinMaxAttempts(); + static DisconnectPacket::eDisconnectReason GetJoinRejectReason(); static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize); static bool SendOnSocket(SOCKET sock, const void* data, int dataSize); @@ -118,7 +131,7 @@ private: static DWORD WINAPI SplitScreenRecvThreadProc(LPVOID param); static DWORD WINAPI AdvertiseThreadProc(LPVOID param); static DWORD WINAPI DiscoveryThreadProc(LPVOID param); - static DWORD WINAPI JoinGameThreadProc(LPVOID param); + static DWORD WINAPI JoinThreadProc(LPVOID param); static SOCKET s_listenSocket; static SOCKET s_hostConnectionSocket; @@ -162,12 +175,15 @@ private: static CRITICAL_SECTION s_smallIdToSocketLock; // Async join state - static volatile bool s_joinCancelled; - static volatile bool s_joinComplete; - static bool s_joinResult; - static HANDLE s_joinGameThread; + static const int JOIN_MAX_ATTEMPTS = 3; + static HANDLE s_joinThread; + static volatile eJoinState s_joinState; + static volatile int s_joinAttempt; + static volatile bool s_joinCancel; static char s_joinIP[256]; static int s_joinPort; + static BYTE s_joinAssignedSmallId; + static DisconnectPacket::eDisconnectReason s_joinRejectReason; // Per-pad split-screen TCP connections (client-side, non-host only) static SOCKET s_splitScreenSocket[XUSER_MAX_COUNT];