diff options
| author | Sylvessa <225480449+sylvessa@users.noreply.github.com> | 2026-03-26 09:15:11 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-26 10:15:11 -0400 |
| commit | 1a50770647c582c5ce194e5741e3014bb1c1e8b2 (patch) | |
| tree | 8160994034f5f58b222032b73f8cda8c42e92479 /Minecraft.Client/Windows64/Network | |
| parent | dee559bd16e5fc4fb1d8cdd16e7e3924666b01c9 (diff) | |
Add asynchronous server joining (#1408)
Diffstat (limited to 'Minecraft.Client/Windows64/Network')
| -rw-r--r-- | Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp | 249 | ||||
| -rw-r--r-- | Minecraft.Client/Windows64/Network/WinsockNetLayer.h | 30 |
2 files changed, 279 insertions, 0 deletions
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; |
