update lcemp networking

This commit is contained in:
2026-08-02 03:33:11 +03:00
parent b87d044cf0
commit e254228cf5
15 changed files with 616 additions and 138 deletions
@@ -321,8 +321,18 @@ bool WinsockNetLayer::JoinGame(const char *ip, int port)
s_isHost = false;
s_hostSmallId = 0;
s_connected = false;
s_active = false;
const int maxAttempts = 3;
const int connectTimeoutMs = 3000;
#if defined _WINDOWS64
if (s_hostConnectionSocket != INVALID_SOCKET)
{
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
}
struct addrinfo hints = {};
struct addrinfo *result = NULL;
@@ -340,27 +350,97 @@ bool WinsockNetLayer::JoinGame(const char *ip, int port)
return false;
}
s_hostConnectionSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (s_hostConnectionSocket == INVALID_SOCKET)
bool connected = false;
BYTE assignedSmallId = 0;
for (int attempt = 0; attempt < maxAttempts; ++attempt)
{
app.DebugPrintf("socket() failed: %d\n", WSAGetLastError());
freeaddrinfo(result);
return false;
s_hostConnectionSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (s_hostConnectionSocket == INVALID_SOCKET)
{
app.DebugPrintf("socket() failed: %d\n", WSAGetLastError());
break;
}
int noDelay = 1;
setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay));
u_long nonBlocking = 1;
ioctlsocket(s_hostConnectionSocket, FIONBIO, &nonBlocking);
iResult = connect(s_hostConnectionSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK)
{
fd_set writeFds, exceptFds;
FD_ZERO(&writeFds);
FD_ZERO(&exceptFds);
FD_SET(s_hostConnectionSocket, &writeFds);
FD_SET(s_hostConnectionSocket, &exceptFds);
struct timeval tv;
tv.tv_sec = connectTimeoutMs / 1000;
tv.tv_usec = (connectTimeoutMs % 1000) * 1000;
int selectResult = select(0, NULL, &writeFds, &exceptFds, &tv);
if (selectResult <= 0 || FD_ISSET(s_hostConnectionSocket, &exceptFds))
{
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;
}
int sockErr = 0;
int sockErrLen = sizeof(sockErr);
getsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_ERROR, (char *)&sockErr, &sockErrLen);
if (sockErr != 0)
{
app.DebugPrintf("connect() to %s:%d failed with SO_ERROR %d (attempt %d/%d)\n", ip, port, sockErr, attempt + 1, maxAttempts);
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
continue;
}
}
else
{
app.DebugPrintf("connect() to %s:%d failed (attempt %d/%d): %d\n", ip, port, attempt + 1, maxAttempts, err);
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
continue;
}
}
u_long blocking = 0;
ioctlsocket(s_hostConnectionSocket, FIONBIO, &blocking);
DWORD recvTimeout = 3000;
setsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&recvTimeout, sizeof(recvTimeout));
BYTE assignBuf[1];
int bytesRecv = recv(s_hostConnectionSocket, (char *)assignBuf, 1, 0);
if (bytesRecv != 1)
{
app.DebugPrintf("Failed to receive small ID assignment from host (attempt %d/%d)\n", attempt + 1, maxAttempts);
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
continue;
}
assignedSmallId = assignBuf[0];
connected = true;
break;
}
int noDelay = 1;
setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay));
iResult = connect(s_hostConnectionSocket, result->ai_addr, (int)result->ai_addrlen);
freeaddrinfo(result);
if (iResult == SOCKET_ERROR)
#elif defined _XBOX
if (s_hostConnectionSocket != INVALID_SOCKET)
{
app.DebugPrintf("connect() to %s:%d failed: %d\n", ip, port, WSAGetLastError());
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
return false;
}
#elif defined _XBOX
XNDNS *pDns = NULL;
int iResult = XNetDnsLookup(ip, NULL, &pDns);
if (iResult != 0 || pDns == NULL)
@@ -387,25 +467,85 @@ bool WinsockNetLayer::JoinGame(const char *ip, int port)
addr.sin_addr = pDns->aina[0];
XNetDnsRelease(pDns);
s_hostConnectionSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s_hostConnectionSocket == INVALID_SOCKET)
{
app.DebugPrintf("socket() failed: %d\n", WSAGetLastError());
return false;
}
bool connected = false;
BYTE assignedSmallId = 0;
int noDelay = 1;
setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay));
iResult = connect(s_hostConnectionSocket, (struct sockaddr*)&addr, sizeof(addr));
if (iResult == SOCKET_ERROR)
for (int attempt = 0; attempt < maxAttempts; ++attempt)
{
app.DebugPrintf("connect() to %s:%d failed: %d\n", ip, port, WSAGetLastError());
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
return false;
s_hostConnectionSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s_hostConnectionSocket == INVALID_SOCKET)
{
app.DebugPrintf("socket() failed: %d\n", WSAGetLastError());
break;
}
int noDelay = 1;
setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay));
u_long nonBlocking = 1;
ioctlsocket(s_hostConnectionSocket, FIONBIO, &nonBlocking);
iResult = connect(s_hostConnectionSocket, (struct sockaddr*)&addr, sizeof(addr));
if (iResult == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK)
{
fd_set writeFds, exceptFds;
FD_ZERO(&writeFds);
FD_ZERO(&exceptFds);
FD_SET(s_hostConnectionSocket, &writeFds);
FD_SET(s_hostConnectionSocket, &exceptFds);
struct timeval tv;
tv.tv_sec = connectTimeoutMs / 1000;
tv.tv_usec = (connectTimeoutMs % 1000) * 1000;
int selectResult = select(0, NULL, &writeFds, &exceptFds, &tv);
if (selectResult <= 0 || FD_ISSET(s_hostConnectionSocket, &exceptFds))
{
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;
}
}
else
{
app.DebugPrintf("connect() to %s:%d failed (attempt %d/%d): %d\n", ip, port, attempt + 1, maxAttempts, err);
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
continue;
}
}
u_long blocking = 0;
ioctlsocket(s_hostConnectionSocket, FIONBIO, &blocking);
DWORD recvTimeout = 3000;
setsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&recvTimeout, sizeof(recvTimeout));
BYTE assignBuf[1];
int bytesRecv = recv(s_hostConnectionSocket, (char *)assignBuf, 1, 0);
if (bytesRecv != 1)
{
app.DebugPrintf("Failed to receive small ID assignment from host (attempt %d/%d)\n", attempt + 1, maxAttempts);
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
continue;
}
assignedSmallId = assignBuf[0];
connected = true;
break;
}
#elif defined __PS3__
if (s_hostConnectionSocket != -1)
{
socketclose(s_hostConnectionSocket);
s_hostConnectionSocket = -1;
}
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
@@ -418,38 +558,108 @@ bool WinsockNetLayer::JoinGame(const char *ip, int port)
return false;
}
int iResult = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
s_hostConnectionSocket = iResult;
if (s_hostConnectionSocket < 0)
{
app.DebugPrintf("socket() failed: %d\n", s_hostConnectionSocket);
return false;
}
bool connected = false;
BYTE assignedSmallId = 0;
int noDelay = 1;
setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay));
iResult = connect(s_hostConnectionSocket, (struct sockaddr*)&addr, sizeof(addr));
if (iResult < 0)
for (int attempt = 0; attempt < maxAttempts; ++attempt)
{
app.DebugPrintf("connect() to %s:%d failed: %d\n", ip, port, s_hostConnectionSocket);
socketclose(s_hostConnectionSocket);
s_hostConnectionSocket = -1;
return false;
int iResult = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
s_hostConnectionSocket = iResult;
if (s_hostConnectionSocket < 0)
{
app.DebugPrintf("socket() failed: %d\n", s_hostConnectionSocket);
break;
}
int noDelay = 1;
setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay));
int nonBlocking = 1;
setsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_NBIO, &nonBlocking, sizeof(nonBlocking));
iResult = connect(s_hostConnectionSocket, (struct sockaddr*)&addr, sizeof(addr));
if (iResult < 0)
{
int err = sys_net_errno;
if (err == SYS_NET_EINPROGRESS)
{
fd_set writeFds, exceptFds;
FD_ZERO(&writeFds);
FD_ZERO(&exceptFds);
FD_SET(s_hostConnectionSocket, &writeFds);
FD_SET(s_hostConnectionSocket, &exceptFds);
struct timeval tv;
tv.tv_sec = connectTimeoutMs / 1000;
tv.tv_usec = (connectTimeoutMs % 1000) * 1000;
int selectResult = socketselect(s_hostConnectionSocket + 1, NULL, &writeFds, &exceptFds, &tv);
if (selectResult <= 0 || FD_ISSET(s_hostConnectionSocket, &exceptFds))
{
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;
}
int sockErr = 0;
socklen_t sockErrLen = sizeof(sockErr);
getsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_ERROR, (char *)&sockErr, &sockErrLen);
if (sockErr != 0)
{
app.DebugPrintf("connect() to %s:%d failed with SO_ERROR %d (attempt %d/%d)\n", ip, port, sockErr, attempt + 1, maxAttempts);
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
continue;
}
}
else
{
app.DebugPrintf("connect() to %s:%d failed (attempt %d/%d): %d\n", ip, port, attempt + 1, maxAttempts, err);
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
continue;
}
}
int blocking = 0;
setsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_NBIO, &blocking, sizeof(blocking));
DWORD recvTimeout = 3000;
setsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&recvTimeout, sizeof(recvTimeout));
/*
iResult = connect(s_hostConnectionSocket, (struct sockaddr*)&addr, sizeof(addr));
if (iResult < 0)
{
app.DebugPrintf("connect() to %s:%d failed: %d\n", ip, port, s_hostConnectionSocket);
socketclose(s_hostConnectionSocket);
s_hostConnectionSocket = -1;
return false;
}
*/
BYTE assignBuf[1];
int bytesRecv = recv(s_hostConnectionSocket, (char *)assignBuf, 1, 0);
if (bytesRecv != 1)
{
app.DebugPrintf("Failed to receive small ID assignment from host (attempt %d/%d)\n", attempt + 1, maxAttempts);
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
continue;
}
assignedSmallId = assignBuf[0];
connected = true;
break;
}
#endif
BYTE assignBuf[1];
int bytesRecv = recv(s_hostConnectionSocket, (char *)assignBuf, 1, 0);
if (bytesRecv != 1)
if (!connected)
{
app.DebugPrintf("Failed to receive small ID assignment from host\n");
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
return false;
}
s_localSmallId = assignBuf[0];
s_localSmallId = assignedSmallId;
app.DebugPrintf("Win64 LAN: Connected to %s:%d, assigned smallId=%d\n", ip, port, s_localSmallId);
app.DebugPrintf("LAN: Connected to %s:%d, assigned smallId=%d\n", ip, port, s_localSmallId);
s_active = true;
s_connected = true;
@@ -698,7 +908,8 @@ int WinsockNetLayer::RecvThreadProc(LPVOID param)
BYTE clientSmallId = s_connections[connIdx].smallId;
LeaveCriticalSection(&s_connectionsLock);
BYTE *recvBuf = new BYTE[WIN64_NET_RECV_BUFFER_SIZE];
std::vector<BYTE> recvBuf;
recvBuf.resize(WIN64_NET_RECV_BUFFER_SIZE);
while (s_active)
{
@@ -709,33 +920,47 @@ int WinsockNetLayer::RecvThreadProc(LPVOID param)
break;
}
int packetSize = (header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3];
int packetSize =
((uint32_t)header[0] << 24) |
((uint32_t)header[1] << 16) |
((uint32_t)header[2] << 8) |
((uint32_t)header[3]);
if (packetSize <= 0 || packetSize > WIN64_NET_RECV_BUFFER_SIZE)
if (packetSize <= 0 || packetSize > WIN64_NET_MAX_PACKET_SIZE)
{
app.DebugPrintf("Win64 LAN: Invalid packet size %d from client smallId=%d\n", packetSize, clientSmallId);
app.DebugPrintf("LAN: Invalid packet size %d from client smallId=%d (max=%d)\n",
packetSize,
clientSmallId,
(int)WIN64_NET_MAX_PACKET_SIZE);
break;
}
if (!RecvExact(sock, recvBuf, packetSize))
if ((int)recvBuf.size() < packetSize)
{
app.DebugPrintf("Win64 LAN: Client smallId=%d disconnected (body)\n", clientSmallId);
recvBuf.resize(packetSize);
app.DebugPrintf("LAN: Resized host recv buffer to %d bytes for client smallId=%d\n", packetSize, clientSmallId);
}
if (!RecvExact(sock, &recvBuf[0], packetSize))
{
app.DebugPrintf("LAN: Client smallId=%d disconnected (body)\n", clientSmallId);
break;
}
HandleDataReceived(clientSmallId, s_hostSmallId, recvBuf, packetSize);
HandleDataReceived(clientSmallId, s_hostSmallId, &recvBuf[0], packetSize);
}
delete[] recvBuf;
EnterCriticalSection(&s_connectionsLock);
for (size_t i = 0; i < s_connections.size(); i++)
{
if (s_connections[i].smallId == clientSmallId)
{
s_connections[i].active = false;
closesocket(s_connections[i].tcpSocket);
s_connections[i].tcpSocket = INVALID_SOCKET;
if (s_connections[i].tcpSocket != INVALID_SOCKET)
{
closesocket(s_connections[i].tcpSocket);
s_connections[i].tcpSocket = INVALID_SOCKET;
}
break;
}
}
@@ -769,9 +994,26 @@ void WinsockNetLayer::PushFreeSmallId(BYTE smallId)
LeaveCriticalSection(&s_freeSmallIdLock);
}
void WinsockNetLayer::CloseConnectionBySmallId(BYTE smallId)
{
EnterCriticalSection(&s_connectionsLock);
for (size_t i = 0; i < s_connections.size(); i++)
{
if (s_connections[i].smallId == smallId && s_connections[i].active && s_connections[i].tcpSocket != INVALID_SOCKET)
{
closesocket(s_connections[i].tcpSocket);
s_connections[i].tcpSocket = INVALID_SOCKET;
app.DebugPrintf("Win64 LAN: Force-closed TCP connection for smallId=%d\n", smallId);
break;
}
}
LeaveCriticalSection(&s_connectionsLock);
}
int WinsockNetLayer::ClientRecvThreadProc(LPVOID param)
{
BYTE *recvBuf = new BYTE[WIN64_NET_RECV_BUFFER_SIZE];
std::vector<BYTE> recvBuf;
recvBuf.resize(WIN64_NET_RECV_BUFFER_SIZE);
#if defined _WINDOWS64 || defined _XBOX
while (s_active && s_hostConnectionSocket != INVALID_SOCKET)
@@ -782,29 +1024,33 @@ int WinsockNetLayer::ClientRecvThreadProc(LPVOID param)
BYTE header[4];
if (!RecvExact(s_hostConnectionSocket, header, 4))
{
app.DebugPrintf("Win64 LAN: Disconnected from host (header)\n");
app.DebugPrintf("LAN: Disconnected from host (header)\n");
break;
}
int packetSize = (header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3];
if (packetSize <= 0 || packetSize > WIN64_NET_RECV_BUFFER_SIZE)
if (packetSize <= 0 || packetSize > WIN64_NET_MAX_PACKET_SIZE)
{
app.DebugPrintf("Win64 LAN: Invalid packet size %d from host\n", packetSize);
app.DebugPrintf("LAN: Invalid packet size %d from host\n", packetSize);
break;
}
if (!RecvExact(s_hostConnectionSocket, recvBuf, packetSize))
if ((int)recvBuf.size() < packetSize)
{
app.DebugPrintf("Win64 LAN: Disconnected from host (body)\n");
recvBuf.resize(packetSize);
app.DebugPrintf("LAN: Resized client recv buffer to %d bytes\n", packetSize);
}
if (!RecvExact(s_hostConnectionSocket, &recvBuf[0], packetSize))
{
app.DebugPrintf("LAN: Disconnected from host (body)\n");
break;
}
HandleDataReceived(s_hostSmallId, s_localSmallId, recvBuf, packetSize);
HandleDataReceived(s_hostSmallId, s_localSmallId, &recvBuf[0], packetSize);
}
delete[] recvBuf;
s_connected = false;
return 0;
}
@@ -887,6 +1133,18 @@ void WinsockNetLayer::UpdateAdvertisePlayerCount(BYTE count)
LeaveCriticalSection(&s_advertiseLock);
}
void WinsockNetLayer::UpdateAdvertisePlayerNames(BYTE count, const char playerNames[][XUSER_NAME_SIZE])
{
EnterCriticalSection(&s_advertiseLock);
memset(s_advertiseData.playerNames, 0, sizeof(s_advertiseData.playerNames));
s_advertiseData.playerCount = count;
for (int i = 0; i < count && i < 8; i++)
{
memcpy(s_advertiseData.playerNames[i], playerNames[i], XUSER_NAME_SIZE);
}
LeaveCriticalSection(&s_advertiseLock);
}
void WinsockNetLayer::UpdateAdvertiseJoinable(bool joinable)
{
EnterCriticalSection(&s_advertiseLock);
@@ -1028,16 +1286,18 @@ void WinsockNetLayer::StopDiscovery()
std::vector<Win64LANSession> WinsockNetLayer::GetDiscoveredSessions()
{
std::vector<Win64LANSession> result;
EnterCriticalSection(&s_discoveryLock);
result = s_discoveredSessions;
LeaveCriticalSection(&s_discoveryLock);
if (TryEnterCriticalSection(&s_discoveryLock))
{
result = s_discoveredSessions;
LeaveCriticalSection(&s_discoveryLock);
}
return result;
}
int WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
{
app.DebugPrintf("Discovery thread started\n");
char recvBuf[512];
char recvBuf[1024];
while (s_discovering)
{
@@ -1111,6 +1371,7 @@ int WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
s_discoveredSessions[i].subTexturePackId = broadcast->subTexturePackId;
s_discoveredSessions[i].isJoinable = (broadcast->isJoinable != 0);
s_discoveredSessions[i].lastSeenTick = now;
memcpy(s_discoveredSessions[i].playerNames, broadcast->playerNames, sizeof(broadcast->playerNames));
found = true;
break;
}
@@ -1137,6 +1398,7 @@ int WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
session.subTexturePackId = broadcast->subTexturePackId;
session.isJoinable = (broadcast->isJoinable != 0);
session.lastSeenTick = now;
memcpy(session.playerNames, broadcast->playerNames, sizeof(broadcast->playerNames));
s_discoveredSessions.push_back(session);
app.DebugPrintf("LAN: Discovered game \"%ls\" at %s:%d\n",