mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/LCE-Revelations.git
synced 2026-09-23 19:40:50 +00:00
Fix server list refresh and add cancellable non-blocking connection
Server list: edits and deletions now update the UI immediately by calling SearchForGames() in ForceFriendsSessionRefresh() and UpdateGamesList() on nav-back to LoadOrJoinMenu. Connection: moved WinsockNetLayer::JoinGame() to a background thread with non-blocking sockets (5s timeout, 3 retries). Users can cancel with B/Escape during the attempt. Failed connections always show an error dialog.
This commit is contained in:
@@ -772,8 +772,32 @@ void CGameNetworkManager::CancelJoinGame(LPVOID lpParam)
|
|||||||
#ifdef _XBOX_ONE
|
#ifdef _XBOX_ONE
|
||||||
s_pPlatformNetworkManager->CancelJoinGame();
|
s_pPlatformNetworkManager->CancelJoinGame();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
static_cast<CPlatformNetworkManagerStub*>(s_pPlatformNetworkManager)->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<CPlatformNetworkManagerStub*>(s_pPlatformNetworkManager)->BeginJoinGameAsync(searchResult, localUsersMask, primaryUserIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CGameNetworkManager::FinishJoinGame(FriendSessionInfo *searchResult)
|
||||||
|
{
|
||||||
|
return static_cast<CPlatformNetworkManagerStub*>(s_pPlatformNetworkManager)->FinishJoinGame(searchResult);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool CGameNetworkManager::LeaveGame(bool bMigrateHost)
|
bool CGameNetworkManager::LeaveGame(bool bMigrateHost)
|
||||||
{
|
{
|
||||||
Minecraft::GetInstance()->gui->clearMessages();
|
Minecraft::GetInstance()->gui->clearMessages();
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ public:
|
|||||||
bool JoinGameFromInviteInfo( int userIndex, int userMask, const INVITE_INFO *pInviteInfo);
|
bool JoinGameFromInviteInfo( int userIndex, int userMask, const INVITE_INFO *pInviteInfo);
|
||||||
eJoinGameResult JoinGame(FriendSessionInfo *searchResult, int localUsersMask);
|
eJoinGameResult JoinGame(FriendSessionInfo *searchResult, int localUsersMask);
|
||||||
static void CancelJoinGame(LPVOID lpParam); // Not part of the shared interface
|
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);
|
bool LeaveGame(bool bMigrateHost);
|
||||||
static int JoinFromInvite_SignInReturned(void *pParam,bool bContinue, int iPad);
|
static int JoinFromInvite_SignInReturned(void *pParam,bool bContinue, int iPad);
|
||||||
void UpdateAndSetGameSessionData(INetworkPlayer *pNetworkPlayerLeaving = nullptr);
|
void UpdateAndSetGameSessionData(INetworkPlayer *pNetworkPlayerLeaving = nullptr);
|
||||||
|
|||||||
@@ -546,6 +546,61 @@ int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo* searchResult, int l
|
|||||||
#endif
|
#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)
|
bool CPlatformNetworkManagerStub::SetLocalGame(bool isLocal)
|
||||||
{
|
{
|
||||||
m_bIsOfflineGame = isLocal;
|
m_bIsOfflineGame = isLocal;
|
||||||
@@ -955,6 +1010,13 @@ void CPlatformNetworkManagerStub::ForceFriendsSessionRefresh()
|
|||||||
delete m_pSearchResults[i];
|
delete m_pSearchResults[i];
|
||||||
m_pSearchResults[i] = nullptr;
|
m_pSearchResults[i] = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
// Immediately rebuild the session list from servers.db so that
|
||||||
|
// edits/deletions are visible as soon as the UI regains focus,
|
||||||
|
// rather than waiting for the next TickSearch() cycle.
|
||||||
|
SearchForGames();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
INetworkPlayer *CPlatformNetworkManagerStub::addNetworkPlayer(IQNetPlayer *pQNetPlayer)
|
INetworkPlayer *CPlatformNetworkManagerStub::addNetworkPlayer(IQNetPlayer *pQNetPlayer)
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ public:
|
|||||||
|
|
||||||
virtual void HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0);
|
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 );
|
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 SetLocalGame(bool isLocal);
|
||||||
virtual bool IsLocalGame() { return m_bIsOfflineGame; }
|
virtual bool IsLocalGame() { return m_bIsOfflineGame; }
|
||||||
virtual void SetPrivateGame(bool isPrivate);
|
virtual void SetPrivateGame(bool isPrivate);
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
#include "..\..\MinecraftServer.h"
|
#include "..\..\MinecraftServer.h"
|
||||||
#include "..\..\..\Minecraft.World\net.minecraft.world.level.h"
|
#include "..\..\..\Minecraft.World\net.minecraft.world.level.h"
|
||||||
#include "..\..\..\Minecraft.World\net.minecraft.world.h"
|
#include "..\..\..\Minecraft.World\net.minecraft.world.h"
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
#include "..\..\Windows64\Network\WinsockNetLayer.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define UPDATE_PLAYERS_TIMER_ID 0
|
#define UPDATE_PLAYERS_TIMER_ID 0
|
||||||
#define UPDATE_PLAYERS_TIMER_TIME 30000
|
#define UPDATE_PLAYERS_TIMER_TIME 30000
|
||||||
@@ -26,6 +29,8 @@ UIScene_JoinMenu::UIScene_JoinMenu(int iPad, void *_initData, UILayer *parentLay
|
|||||||
m_editServerPhase = eEditServer_Idle;
|
m_editServerPhase = eEditServer_Idle;
|
||||||
m_editServerButtonIndex = -1;
|
m_editServerButtonIndex = -1;
|
||||||
m_deleteServerButtonIndex = -1;
|
m_deleteServerButtonIndex = -1;
|
||||||
|
m_asyncJoinInProgress = false;
|
||||||
|
m_joinLocalUsersMask = 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +64,35 @@ void UIScene_JoinMenu::updateTooltips()
|
|||||||
|
|
||||||
void UIScene_JoinMenu::tick()
|
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 )
|
if( !m_friendInfoRequestIssued )
|
||||||
{
|
{
|
||||||
ui.NavigateToScene(m_iPad, eUIScene_Timer);
|
ui.NavigateToScene(m_iPad, eUIScene_Timer);
|
||||||
@@ -282,6 +316,19 @@ wstring UIScene_JoinMenu::getMoviePath()
|
|||||||
|
|
||||||
void UIScene_JoinMenu::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled)
|
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;
|
if(m_bIgnoreInput) return;
|
||||||
|
|
||||||
ui.AnimateKeyPress(m_iPad, key, repeat, pressed, released);
|
ui.AnimateKeyPress(m_iPad, key, repeat, pressed, released);
|
||||||
@@ -578,7 +625,19 @@ void UIScene_JoinMenu::JoinGame(UIScene_JoinMenu* pClass)
|
|||||||
ProfileManager.DisplaySystemMessage( SCE_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_CHAT_RESTRICTION, ProfileManager.GetPrimaryPad() );
|
ProfileManager.DisplaySystemMessage( SCE_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_CHAT_RESTRICTION, ProfileManager.GetPrimaryPad() );
|
||||||
}
|
}
|
||||||
#endif
|
#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 );
|
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
|
// Alert the app the we no longer want to be informed of ethernet connections
|
||||||
app.SetLiveLinkRequired( false );
|
app.SetLiveLinkRequired( false );
|
||||||
@@ -637,16 +696,18 @@ void UIScene_JoinMenu::JoinGame(UIScene_JoinMenu* pClass)
|
|||||||
|
|
||||||
if( exitReasonStringId == -1 )
|
if( exitReasonStringId == -1 )
|
||||||
{
|
{
|
||||||
ui.NavigateBack(pClass->m_iPad);
|
// No specific disconnect reason was set — the server was likely
|
||||||
|
// unreachable. Show a "Connection Failed" dialog instead of
|
||||||
|
// silently navigating back so the user knows what happened.
|
||||||
|
exitReasonStringId = IDS_CONNECTION_LOST_SERVER;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
{
|
||||||
UINT uiIDA[1];
|
UINT uiIDA[1];
|
||||||
uiIDA[0]=IDS_CONFIRM_OK;
|
uiIDA[0]=IDS_CONFIRM_OK;
|
||||||
ui.RequestErrorMessage( IDS_CONNECTION_FAILED, exitReasonStringId, uiIDA,1,ProfileManager.GetPrimaryPad());
|
ui.RequestErrorMessage( IDS_CONNECTION_FAILED, exitReasonStringId, uiIDA,1,ProfileManager.GetPrimaryPad());
|
||||||
exitReasonStringId = -1;
|
|
||||||
|
|
||||||
ui.NavigateToHomeMenu();
|
pClass->m_bIgnoreInput = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ private:
|
|||||||
wstring m_editServerPort;
|
wstring m_editServerPort;
|
||||||
int m_editServerButtonIndex;
|
int m_editServerButtonIndex;
|
||||||
int m_deleteServerButtonIndex;
|
int m_deleteServerButtonIndex;
|
||||||
|
bool m_asyncJoinInProgress;
|
||||||
|
DWORD m_joinLocalUsersMask;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -637,6 +637,11 @@ void UIScene_LoadOrJoinMenu::handleGainFocus(bool navBack)
|
|||||||
|
|
||||||
if( m_bMultiplayerAllowed )
|
if( m_bMultiplayerAllowed )
|
||||||
{
|
{
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
// Refresh the games list immediately so that any server
|
||||||
|
// edits/deletions made in the JoinMenu are visible now.
|
||||||
|
UpdateGamesList();
|
||||||
|
#endif
|
||||||
#if TO_BE_IMPLEMENTED
|
#if TO_BE_IMPLEMENTED
|
||||||
HXUICLASS hClassFullscreenProgress = XuiFindClass( L"CScene_FullscreenProgress" );
|
HXUICLASS hClassFullscreenProgress = XuiFindClass( L"CScene_FullscreenProgress" );
|
||||||
HXUICLASS hClassConnectingProgress = XuiFindClass( L"CScene_ConnectingProgress" );
|
HXUICLASS hClassConnectingProgress = XuiFindClass( L"CScene_ConnectingProgress" );
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ SOCKET WinsockNetLayer::s_splitScreenSocket[XUSER_MAX_COUNT] = { INVALID_SOCKET,
|
|||||||
BYTE WinsockNetLayer::s_splitScreenSmallId[XUSER_MAX_COUNT] = { 0xFF, 0xFF, 0xFF, 0xFF };
|
BYTE WinsockNetLayer::s_splitScreenSmallId[XUSER_MAX_COUNT] = { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
HANDLE WinsockNetLayer::s_splitScreenRecvThread[XUSER_MAX_COUNT] = {nullptr, nullptr, nullptr, nullptr};
|
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;
|
||||||
|
char WinsockNetLayer::s_joinIP[256] = {};
|
||||||
|
int WinsockNetLayer::s_joinPort = 0;
|
||||||
|
|
||||||
bool g_Win64MultiplayerHost = false;
|
bool g_Win64MultiplayerHost = false;
|
||||||
bool g_Win64MultiplayerJoin = false;
|
bool g_Win64MultiplayerJoin = false;
|
||||||
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
|
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
|
||||||
@@ -337,10 +344,17 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
|
|||||||
|
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
BYTE assignedSmallId = 0;
|
BYTE assignedSmallId = 0;
|
||||||
const int maxAttempts = 12;
|
const int maxAttempts = 3;
|
||||||
|
const int connectTimeoutSec = 5;
|
||||||
|
|
||||||
for (int attempt = 0; attempt < maxAttempts; ++attempt)
|
for (int attempt = 0; attempt < maxAttempts; ++attempt)
|
||||||
{
|
{
|
||||||
|
if (s_joinCancelled)
|
||||||
|
{
|
||||||
|
app.DebugPrintf("JoinGame cancelled by user\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
s_hostConnectionSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
|
s_hostConnectionSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
|
||||||
if (s_hostConnectionSocket == INVALID_SOCKET)
|
if (s_hostConnectionSocket == INVALID_SOCKET)
|
||||||
{
|
{
|
||||||
@@ -351,16 +365,54 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
|
|||||||
int noDelay = 1;
|
int noDelay = 1;
|
||||||
setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay));
|
setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay));
|
||||||
|
|
||||||
|
// Use non-blocking connect with select() timeout so we don't freeze
|
||||||
|
// the game for the full OS TCP timeout when the server is unreachable.
|
||||||
|
u_long nonBlocking = 1;
|
||||||
|
ioctlsocket(s_hostConnectionSocket, FIONBIO, &nonBlocking);
|
||||||
|
|
||||||
iResult = connect(s_hostConnectionSocket, result->ai_addr, static_cast<int>(result->ai_addrlen));
|
iResult = connect(s_hostConnectionSocket, result->ai_addr, static_cast<int>(result->ai_addrlen));
|
||||||
if (iResult == SOCKET_ERROR)
|
if (iResult == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
int err = WSAGetLastError();
|
int err = WSAGetLastError();
|
||||||
|
if (err == WSAEWOULDBLOCK)
|
||||||
|
{
|
||||||
|
fd_set writeSet, errorSet;
|
||||||
|
FD_ZERO(&writeSet);
|
||||||
|
FD_SET(s_hostConnectionSocket, &writeSet);
|
||||||
|
FD_ZERO(&errorSet);
|
||||||
|
FD_SET(s_hostConnectionSocket, &errorSet);
|
||||||
|
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = connectTimeoutSec;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
|
int selectResult = select(0, nullptr, &writeSet, &errorSet, &tv);
|
||||||
|
if (selectResult <= 0 || FD_ISSET(s_hostConnectionSocket, &errorSet))
|
||||||
|
{
|
||||||
|
app.DebugPrintf("connect() to %s:%d timed out or failed (attempt %d/%d)\n", ip, port, attempt + 1, maxAttempts);
|
||||||
|
closesocket(s_hostConnectionSocket);
|
||||||
|
s_hostConnectionSocket = INVALID_SOCKET;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Connection succeeded via non-blocking path
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
app.DebugPrintf("connect() to %s:%d failed (attempt %d/%d): %d\n", ip, port, attempt + 1, maxAttempts, err);
|
app.DebugPrintf("connect() to %s:%d failed (attempt %d/%d): %d\n", ip, port, attempt + 1, maxAttempts, err);
|
||||||
closesocket(s_hostConnectionSocket);
|
closesocket(s_hostConnectionSocket);
|
||||||
s_hostConnectionSocket = INVALID_SOCKET;
|
s_hostConnectionSocket = INVALID_SOCKET;
|
||||||
Sleep(200);
|
Sleep(200);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore blocking mode for normal socket I/O
|
||||||
|
u_long blocking = 0;
|
||||||
|
ioctlsocket(s_hostConnectionSocket, FIONBIO, &blocking);
|
||||||
|
|
||||||
|
// Set a recv timeout so we don't block forever waiting for the small ID
|
||||||
|
DWORD recvTimeout = connectTimeoutSec * 1000;
|
||||||
|
setsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&recvTimeout, sizeof(recvTimeout));
|
||||||
|
|
||||||
BYTE assignBuf[1];
|
BYTE assignBuf[1];
|
||||||
int bytesRecv = recv(s_hostConnectionSocket, (char*)assignBuf, 1, 0);
|
int bytesRecv = recv(s_hostConnectionSocket, (char*)assignBuf, 1, 0);
|
||||||
@@ -369,7 +421,6 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
|
|||||||
app.DebugPrintf("Failed to receive small ID assignment from host (attempt %d/%d)\n", attempt + 1, maxAttempts);
|
app.DebugPrintf("Failed to receive small ID assignment from host (attempt %d/%d)\n", attempt + 1, maxAttempts);
|
||||||
closesocket(s_hostConnectionSocket);
|
closesocket(s_hostConnectionSocket);
|
||||||
s_hostConnectionSocket = INVALID_SOCKET;
|
s_hostConnectionSocket = INVALID_SOCKET;
|
||||||
Sleep(200);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +472,56 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI WinsockNetLayer::JoinGameThreadProc(LPVOID param)
|
||||||
|
{
|
||||||
|
s_joinResult = JoinGame(s_joinIP, s_joinPort);
|
||||||
|
s_joinComplete = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WinsockNetLayer::StartJoinGameAsync(const char* ip, int port)
|
||||||
|
{
|
||||||
|
// Wait for any previous join thread to finish
|
||||||
|
if (s_joinGameThread != nullptr)
|
||||||
|
{
|
||||||
|
WaitForSingleObject(s_joinGameThread, 5000);
|
||||||
|
CloseHandle(s_joinGameThread);
|
||||||
|
s_joinGameThread = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy_s(s_joinIP, sizeof(s_joinIP), ip, _TRUNCATE);
|
||||||
|
s_joinPort = port;
|
||||||
|
s_joinCancelled = false;
|
||||||
|
s_joinComplete = false;
|
||||||
|
s_joinResult = false;
|
||||||
|
|
||||||
|
s_joinGameThread = CreateThread(nullptr, 0, JoinGameThreadProc, nullptr, 0, nullptr);
|
||||||
|
return s_joinGameThread != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WinsockNetLayer::IsJoinComplete()
|
||||||
|
{
|
||||||
|
return s_joinComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WinsockNetLayer::GetJoinResult()
|
||||||
|
{
|
||||||
|
return s_joinResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinsockNetLayer::CancelJoinGame()
|
||||||
|
{
|
||||||
|
s_joinCancelled = true;
|
||||||
|
|
||||||
|
// Close the socket to immediately unblock any in-progress connect/select/recv
|
||||||
|
SOCKET sock = s_hostConnectionSocket;
|
||||||
|
if (sock != INVALID_SOCKET)
|
||||||
|
{
|
||||||
|
s_hostConnectionSocket = INVALID_SOCKET;
|
||||||
|
closesocket(sock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize)
|
bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize)
|
||||||
{
|
{
|
||||||
if (sock == INVALID_SOCKET || dataSize <= 0 || dataSize > WIN64_NET_MAX_PACKET_SIZE) return false;
|
if (sock == INVALID_SOCKET || dataSize <= 0 || dataSize > WIN64_NET_MAX_PACKET_SIZE) return false;
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ public:
|
|||||||
static bool HostGame(int port, const char* bindIp = nullptr);
|
static bool HostGame(int port, const char* bindIp = nullptr);
|
||||||
static bool JoinGame(const char* ip, int port);
|
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();
|
||||||
|
static void CancelJoinGame();
|
||||||
|
|
||||||
static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize);
|
static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize);
|
||||||
static bool SendOnSocket(SOCKET sock, const void* data, int dataSize);
|
static bool SendOnSocket(SOCKET sock, const void* data, int dataSize);
|
||||||
|
|
||||||
@@ -112,6 +118,7 @@ private:
|
|||||||
static DWORD WINAPI SplitScreenRecvThreadProc(LPVOID param);
|
static DWORD WINAPI SplitScreenRecvThreadProc(LPVOID param);
|
||||||
static DWORD WINAPI AdvertiseThreadProc(LPVOID param);
|
static DWORD WINAPI AdvertiseThreadProc(LPVOID param);
|
||||||
static DWORD WINAPI DiscoveryThreadProc(LPVOID param);
|
static DWORD WINAPI DiscoveryThreadProc(LPVOID param);
|
||||||
|
static DWORD WINAPI JoinGameThreadProc(LPVOID param);
|
||||||
|
|
||||||
static SOCKET s_listenSocket;
|
static SOCKET s_listenSocket;
|
||||||
static SOCKET s_hostConnectionSocket;
|
static SOCKET s_hostConnectionSocket;
|
||||||
@@ -154,6 +161,14 @@ private:
|
|||||||
static SOCKET s_smallIdToSocket[256];
|
static SOCKET s_smallIdToSocket[256];
|
||||||
static CRITICAL_SECTION s_smallIdToSocketLock;
|
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 char s_joinIP[256];
|
||||||
|
static int s_joinPort;
|
||||||
|
|
||||||
// Per-pad split-screen TCP connections (client-side, non-host only)
|
// Per-pad split-screen TCP connections (client-side, non-host only)
|
||||||
static SOCKET s_splitScreenSocket[XUSER_MAX_COUNT];
|
static SOCKET s_splitScreenSocket[XUSER_MAX_COUNT];
|
||||||
static BYTE s_splitScreenSmallId[XUSER_MAX_COUNT];
|
static BYTE s_splitScreenSmallId[XUSER_MAX_COUNT];
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ This project is based on source code of Minecraft Legacy Console Edition v1.6.05
|
|||||||
|
|
||||||
## Latest:
|
## Latest:
|
||||||
|
|
||||||
|
Server list and connection improvements:
|
||||||
|
- Server edits and deletions now apply immediately without needing to restart the game
|
||||||
|
- Connecting to an offline/unreachable server no longer freezes the game indefinitely
|
||||||
|
- Connection attempts use non-blocking sockets with a 5-second timeout (3 retries max) instead of the OS TCP timeout
|
||||||
|
- Connection runs on a background thread so the UI stays responsive, with a cancel option (press B or Escape) to back out at any time
|
||||||
|
- Failed connections now always show a "Connection Failed" dialog instead of silently navigating back
|
||||||
|
|
||||||
Upstream merge:
|
Upstream merge:
|
||||||
- Fixed font rendering for color and formatting codes, splash text like "Colormatic!" now renders with proper per-character colors
|
- Fixed font rendering for color and formatting codes, splash text like "Colormatic!" now renders with proper per-character colors
|
||||||
- Fixed Sign editing UI, SignEntryMenu720 restored to correct version
|
- Fixed Sign editing UI, SignEntryMenu720 restored to correct version
|
||||||
|
|||||||
Reference in New Issue
Block a user