aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Client
diff options
context:
space:
mode:
authorSylvessa <225480449+sylvessa@users.noreply.github.com>2026-03-26 09:15:11 -0500
committerGitHub <noreply@github.com>2026-03-26 10:15:11 -0400
commit1a50770647c582c5ce194e5741e3014bb1c1e8b2 (patch)
tree8160994034f5f58b222032b73f8cda8c42e92479 /Minecraft.Client
parentdee559bd16e5fc4fb1d8cdd16e7e3924666b01c9 (diff)
Add asynchronous server joining (#1408)
Diffstat (limited to 'Minecraft.Client')
-rw-r--r--Minecraft.Client/Common/Network/GameNetworkManager.h3
-rw-r--r--Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp63
-rw-r--r--Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h6
-rw-r--r--Minecraft.Client/Common/UI/UIScene_ConnectingProgress.cpp127
-rw-r--r--Minecraft.Client/Common/UI/UIScene_ConnectingProgress.h5
-rw-r--r--Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp18
-rw-r--r--Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp249
-rw-r--r--Minecraft.Client/Windows64/Network/WinsockNetLayer.h30
8 files changed, 480 insertions, 21 deletions
diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.h b/Minecraft.Client/Common/Network/GameNetworkManager.h
index 3357b3cd..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();
diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
index 1e625098..430f2c11 100644
--- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
+++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
@@ -173,6 +173,11 @@ bool CPlatformNetworkManagerStub::Initialise(CGameNetworkManager *pGameNetworkMa
m_bSearchPending = false;
m_bIsOfflineGame = false;
+#ifdef _WINDOWS64
+ m_bJoinPending = false;
+ m_joinLocalUsersMask = 0;
+ m_joinHostName[0] = 0;
+#endif
m_pSearchParam = nullptr;
m_SessionsUpdatedCallback = nullptr;
@@ -282,6 +287,38 @@ void CPlatformNetworkManagerStub::DoWork()
m_bLeaveGameOnTick = false;
}
}
+
+ 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
}
@@ -511,36 +548,22 @@ int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo* searchResult, int l
IQNet::m_player[0].m_smallId = 0;
IQNet::m_player[0].m_isRemote = true;
IQNet::m_player[0].m_isHostPlayer = true;
- // Remote host still maps to legacy host XUID in mixed old/new sessions.
IQNet::m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid();
wcsncpy_s(IQNet::m_player[0].m_gamertag, 32, searchResult->data.hostName, _TRUNCATE);
WinsockNetLayer::StopDiscovery();
- if (!WinsockNetLayer::JoinGame(hostIP, hostPort))
+ wcsncpy_s(m_joinHostName, 32, searchResult->data.hostName, _TRUNCATE);
+ m_joinLocalUsersMask = localUsersMask;
+
+ if (!WinsockNetLayer::BeginJoinGame(hostIP, hostPort))
{
app.DebugPrintf("Win64 LAN: Failed to connect 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
diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h
index 4a3f4068..dffa3953 100644
--- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h
+++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h
@@ -77,6 +77,12 @@ private:
bool m_bIsPrivateGame;
int m_flagIndexSize;
+#ifdef _WINDOWS64
+ bool m_bJoinPending;
+ int m_joinLocalUsersMask;
+ wchar_t m_joinHostName[32];
+#endif
+
// This is only maintained by the host, and is not valid on client machines
GameSessionData m_hostGameSessionData;
CGameNetworkManager *m_pGameNetworkManager;
diff --git a/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.cpp b/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.cpp
index e10a5a62..40557cd5 100644
--- a/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.cpp
@@ -2,6 +2,16 @@
#include "UI.h"
#include "UIScene_ConnectingProgress.h"
#include "..\..\Minecraft.h"
+#ifdef _WINDOWS64
+#include "..\..\Windows64\Network\WinsockNetLayer.h"
+#include "..\..\..\Minecraft.World\DisconnectPacket.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 +53,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_Idle && initState != WinsockNetLayer::eJoinState_Cancelled);
+ m_asyncJoinFailed = false;
+#endif
}
UIScene_ConnectingProgress::~UIScene_ConnectingProgress()
@@ -53,6 +69,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 +90,85 @@ void UIScene_ConnectingProgress::tick()
{
UIScene::tick();
+#ifdef _WINDOWS64
+ if (m_asyncJoinActive)
+ {
+ WinsockNetLayer::eJoinState state = WinsockNetLayer::GetJoinState();
+ if (state == WinsockNetLayer::eJoinState_Connecting)
+ {
+ // connecting.............
+ int attempt = WinsockNetLayer::GetJoinAttempt();
+ int maxAttempts = WinsockNetLayer::GetJoinMaxAttempts();
+ char buf[128];
+ if (attempt <= 1)
+ sprintf_s(buf, "Connecting...");
+ else
+ sprintf_s(buf, "Connecting failed, trying again (%d/%d)", attempt, maxAttempts);
+ wchar_t wbuf[128];
+ mbstowcs(wbuf, buf, 128);
+ m_labelTitle.setLabel(wstring(wbuf));
+ }
+ else if (state == WinsockNetLayer::eJoinState_Success)
+ {
+ m_asyncJoinActive = false;
+ // go go go
+ }
+ else if (state == WinsockNetLayer::eJoinState_Cancelled)
+ {
+ // cancel
+ m_asyncJoinActive = false;
+ navigateBack();
+ }
+ else if (state == WinsockNetLayer::eJoinState_Rejected)
+ {
+ // server full and banned are passed differently compared to other disconnects it seems
+ m_asyncJoinActive = false;
+ DisconnectPacket::eDisconnectReason reason = WinsockNetLayer::GetJoinRejectReason();
+ int exitReasonStringId;
+ switch (reason)
+ {
+ case DisconnectPacket::eDisconnect_ServerFull:
+ exitReasonStringId = IDS_DISCONNECTED_SERVER_FULL;
+ break;
+ case DisconnectPacket::eDisconnect_Banned:
+ exitReasonStringId = IDS_DISCONNECTED_KICKED;
+ break;
+ default:
+ exitReasonStringId = IDS_CONNECTION_LOST_SERVER;
+ break;
+ }
+ UINT uiIDA[1];
+ uiIDA[0] = IDS_CONFIRM_OK;
+ ui.RequestErrorMessage(IDS_CONNECTION_FAILED, exitReasonStringId, uiIDA, 1, ProfileManager.GetPrimaryPad(), ConnectingProgress_OnRejectedDialogOK, nullptr, nullptr);
+ }
+ else if (state == WinsockNetLayer::eJoinState_Failed)
+ {
+ // FAIL
+ m_asyncJoinActive = false;
+ m_asyncJoinFailed = true;
+
+ int maxAttempts = WinsockNetLayer::GetJoinMaxAttempts();
+ char buf[256];
+ sprintf_s(buf, "Failed to connect after %d attempts. The server may be unavailable.", maxAttempts);
+ wchar_t wbuf[256];
+ mbstowcs(wbuf, buf, 256);
+
+ // TIL that these exist
+ // not going to use a actual popup due to it requiring messing with strings which can really mess things up
+ // i dont trust myself with that
+ // these need to be touched up later as teh button is a bit offset
+ m_labelTitle.setLabel(L"Unable to connect to server");
+ m_progressBar.setLabel(wstring(wbuf));
+ m_progressBar.showBar(false);
+ m_progressBar.setVisible(true);
+ m_buttonConfirm.setVisible(true);
+ m_showingButton = true;
+ m_controlTimer.setVisible(false);
+ }
+ return;
+ }
+#endif
+
if( m_removeLocalPlayer )
{
m_removeLocalPlayer = false;
@@ -94,6 +201,8 @@ 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");
@@ -208,6 +317,17 @@ void UIScene_ConnectingProgress::handleInput(int iPad, int key, bool repeat, boo
switch(key)
{
// 4J-PB - Removed the option to cancel join - it didn't work anyway
+#ifdef _WINDOWS64
+ case ACTION_MENU_CANCEL:
+ if (pressed && m_asyncJoinActive)
+ {
+ m_asyncJoinActive = false;
+ WinsockNetLayer::CancelJoinGame();
+ navigateBack();
+ handled = true;
+ }
+ break;
+#endif
// case ACTION_MENU_CANCEL:
// {
// if(m_cancelFunc != nullptr)
@@ -250,6 +370,13 @@ void UIScene_ConnectingProgress::handlePress(F64 controlId, F64 childId)
case eControl_Confirm:
if(m_showingButton)
{
+#ifdef _WINDOWS64
+ if (m_asyncJoinFailed)
+ {
+ navigateBack();
+ }
+ else
+#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..eaaea7f6 100644
--- a/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.h
+++ b/Minecraft.Client/Common/UI/UIScene_ConnectingProgress.h
@@ -13,6 +13,11 @@ private:
void (*m_cancelFunc)(LPVOID param);
LPVOID m_cancelFuncParam;
+#ifdef _WINDOWS64
+ bool m_asyncJoinActive;
+ bool m_asyncJoinFailed;
+#endif
+
enum EControls
{
eControl_Confirm
diff --git a/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp b/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp
index 417c1700..5b83ea7c 100644
--- a/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp
@@ -583,6 +583,24 @@ void UIScene_JoinMenu::JoinGame(UIScene_JoinMenu* pClass)
// 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 = -1;
+ 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/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
index 981ab3ab..acc043e5 100644
--- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
+++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
@@ -67,6 +67,16 @@ 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};
+// async stuff
+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;
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
@@ -114,6 +124,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;
@@ -421,6 +440,215 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
return true;
}
+bool WinsockNetLayer::BeginJoinGame(const char* ip, int port)
+{
+ if (!s_initialized && !Initialize()) return false;
+
+ // if there isnt any cleanup it sometime caused issues. Oops
+ CancelJoinGame();
+ if (s_joinThread != 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_joinAttempt = 0;
+ s_joinCancel = false;
+ s_joinAssignedSmallId = 0;
+ s_joinRejectReason = DisconnectPacket::eDisconnect_Quitting;
+ s_joinState = eJoinState_Connecting;
+
+ s_joinThread = CreateThread(nullptr, 0, JoinThreadProc, nullptr, 0, nullptr);
+ if (s_joinThread == nullptr)
+ {
+ s_joinState = eJoinState_Failed;
+ return false;
+ }
+ return true;
+}
+
+DWORD WINAPI WinsockNetLayer::JoinThreadProc(LPVOID param)
+{
+ struct addrinfo hints = {};
+ struct addrinfo* result = nullptr;
+
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ char portStr[16];
+ sprintf_s(portStr, "%d", s_joinPort);
+
+ int iResult = getaddrinfo(s_joinIP, portStr, &hints, &result);
+ if (iResult != 0)
+ {
+ app.DebugPrintf("getaddrinfo failed for %s:%d - %d\n", s_joinIP, s_joinPort, iResult);
+ s_joinState = eJoinState_Failed;
+ return 0;
+ }
+
+ bool connected = false;
+ BYTE assignedSmallId = 0;
+ SOCKET sock = INVALID_SOCKET;
+
+ 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)
+ {
+ app.DebugPrintf("socket() failed: %d\n", WSAGetLastError());
+ break;
+ }
+
+ int noDelay = 1;
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay));
+
+ iResult = connect(sock, result->ai_addr, static_cast<int>(result->ai_addrlen));
+ if (iResult == SOCKET_ERROR)
+ {
+ int err = WSAGetLastError();
+ 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;
+ }
+
+ BYTE assignBuf[1];
+ int bytesRecv = recv(sock, (char*)assignBuf, 1, 0);
+ if (bytesRecv != 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;
+ for (int w = 0; w < 4 && !s_joinCancel; w++)
+ Sleep(50);
+ 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;
+ for (int w = 0; w < 4 && !s_joinCancel; w++)
+ Sleep(50);
+ 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;
+ }
+
+ s_hostConnectionSocket = sock;
+ s_joinAssignedSmallId = assignedSmallId;
+ s_joinState = eJoinState_Success;
+ return 0;
+}
+
+void WinsockNetLayer::CancelJoinGame()
+{
+ if (s_joinState == eJoinState_Connecting)
+ {
+ s_joinCancel = true;
+ }
+ else if (s_joinState == eJoinState_Success)
+ {
+ // fix a race cond
+ if (s_hostConnectionSocket != INVALID_SOCKET)
+ {
+ closesocket(s_hostConnectionSocket);
+ s_hostConnectionSocket = INVALID_SOCKET;
+ }
+ 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("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;
+}
+
bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize)
{
if (sock == INVALID_SOCKET || dataSize <= 0 || dataSize > WIN64_NET_MAX_PACKET_SIZE) return false;
@@ -1334,4 +1562,25 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
return 0;
}
+// some lazy helper funcs
+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;
+}
+
#endif
diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
index afccbd66..8a11e391 100644
--- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
+++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
@@ -21,6 +21,8 @@
class Socket;
+#include "..\..\..\Minecraft.World\DisconnectPacket.h"
+
#pragma pack(push, 1)
struct Win64LANBroadcast
{
@@ -69,6 +71,23 @@ public:
static bool HostGame(int port, const char* bindIp = nullptr);
static bool JoinGame(const char* ip, int port);
+ 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 eJoinState GetJoinState();
+ static int GetJoinAttempt();
+ static int GetJoinMaxAttempts();
+ static DisconnectPacket::eDisconnectReason GetJoinRejectReason();
+ static bool FinalizeJoin();
+
static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize);
static bool SendOnSocket(SOCKET sock, const void* data, int dataSize);
@@ -112,6 +131,17 @@ private:
static DWORD WINAPI SplitScreenRecvThreadProc(LPVOID param);
static DWORD WINAPI AdvertiseThreadProc(LPVOID param);
static DWORD WINAPI DiscoveryThreadProc(LPVOID param);
+ static DWORD WINAPI JoinThreadProc(LPVOID param);
+
+ 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;
+ static const int JOIN_MAX_ATTEMPTS = 4;
static SOCKET s_listenSocket;
static SOCKET s_hostConnectionSocket;