Files
cafeberry/Minecraft.Client/Common/Network/WinsockNetLayer.cpp
T
2026-07-31 22:35:05 +03:00

1079 lines
28 KiB
C++

#include "stdafx.h"
#include "WinsockNetLayer.h"
#include "..\..\Common\Network\PlatformNetworkManagerStub.h"
#include "..\..\..\Minecraft.World\Socket.h"
#if defined _WINDOWS64
SOCKET WinsockNetLayer::s_listenSocket = INVALID_SOCKET;
SOCKET WinsockNetLayer::s_hostConnectionSocket = INVALID_SOCKET;
SOCKET WinsockNetLayer::s_advertiseSock = INVALID_SOCKET;
SOCKET WinsockNetLayer::s_discoverySock = INVALID_SOCKET;
#elif defined __PS3__
SOCKET WinsockNetLayer::s_listenSocket = -1;
SOCKET WinsockNetLayer::s_hostConnectionSocket = -1;
SOCKET WinsockNetLayer::s_advertiseSock = -1;
SOCKET WinsockNetLayer::s_discoverySock = -1;
int closesocket(int s) { return socketclose(s); };
void WSACleanup() {};
#endif
C4JThread* WinsockNetLayer::s_acceptThread = NULL;
C4JThread* WinsockNetLayer::s_clientRecvThread = NULL;
bool WinsockNetLayer::s_isHost = false;
bool WinsockNetLayer::s_connected = false;
bool WinsockNetLayer::s_active = false;
bool WinsockNetLayer::s_initialized = false;
BYTE WinsockNetLayer::s_localSmallId = 0;
BYTE WinsockNetLayer::s_hostSmallId = 0;
BYTE WinsockNetLayer::s_nextSmallId = 1;
CRITICAL_SECTION WinsockNetLayer::s_sendLock;
CRITICAL_SECTION WinsockNetLayer::s_connectionsLock;
std::vector<Win64RemoteConnection> WinsockNetLayer::s_connections;
C4JThread* WinsockNetLayer::s_advertiseThread = NULL;
volatile bool WinsockNetLayer::s_advertising = false;
Win64LANBroadcast WinsockNetLayer::s_advertiseData = {};
CRITICAL_SECTION WinsockNetLayer::s_advertiseLock;
int WinsockNetLayer::s_hostGamePort = WIN64_NET_DEFAULT_PORT;
C4JThread* WinsockNetLayer::s_discoveryThread = NULL;
volatile bool WinsockNetLayer::s_discovering = false;
CRITICAL_SECTION WinsockNetLayer::s_discoveryLock;
std::vector<Win64LANSession> WinsockNetLayer::s_discoveredSessions;
CRITICAL_SECTION WinsockNetLayer::s_disconnectLock;
std::vector<BYTE> WinsockNetLayer::s_disconnectedSmallIds;
CRITICAL_SECTION WinsockNetLayer::s_freeSmallIdLock;
std::vector<BYTE> WinsockNetLayer::s_freeSmallIds;
bool g_Win64MultiplayerHost = false;
bool g_Win64MultiplayerJoin = true;
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
char g_Win64MultiplayerIP[256] = "127.0.0.1";
bool WinsockNetLayer::Initialize()
{
if (s_initialized) return true;
#if defined _WINDOWS64
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0)
{
app.DebugPrintf("WSAStartup failed: %d\n", result);
return false;
}
#elif defined __PS3__
int result = sys_net_initialize_network();
if (result != 0) {
app.DebugPrintf("sys_net_initialize_network failed: %d\n", result);
return false;
}
result = cellNetCtlInit();
if (result < 0) {
app.DebugPrintf("cellNetCtlInit failed: %d\n", result);
sys_net_finalize_network();
return false;
}
#endif
InitializeCriticalSection(&s_sendLock);
InitializeCriticalSection(&s_connectionsLock);
InitializeCriticalSection(&s_advertiseLock);
InitializeCriticalSection(&s_discoveryLock);
InitializeCriticalSection(&s_disconnectLock);
InitializeCriticalSection(&s_freeSmallIdLock);
s_initialized = true;
StartDiscovery();
return true;
}
void WinsockNetLayer::Shutdown()
{
StopAdvertising();
StopDiscovery();
s_active = false;
s_connected = false;
#if defined _WINDOWS64
if (s_listenSocket != INVALID_SOCKET)
{
closesocket(s_listenSocket);
s_listenSocket = INVALID_SOCKET;
}
if (s_hostConnectionSocket != INVALID_SOCKET)
{
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
}
#elif defined __PS3__
if (s_listenSocket >= 0)
{
socketclose(s_listenSocket);
s_listenSocket = -1;
}
if (s_hostConnectionSocket >= 0)
{
socketclose(s_hostConnectionSocket);
s_hostConnectionSocket = -1;
}
#endif
EnterCriticalSection(&s_connectionsLock);
for (size_t i = 0; i < s_connections.size(); i++)
{
s_connections[i].active = false;
#if defined _WINDOWS64
if (s_connections[i].tcpSocket != INVALID_SOCKET)
#elif defined __PS3__
if (s_connections[i].tcpSocket >= 0)
#endif
{
closesocket(s_connections[i].tcpSocket);
}
}
s_connections.clear();
LeaveCriticalSection(&s_connectionsLock);
if (s_acceptThread != NULL)
{
s_acceptThread->WaitForCompletion(2000);
delete s_acceptThread;
s_acceptThread = NULL;
}
if (s_clientRecvThread != NULL)
{
s_clientRecvThread->WaitForCompletion(2000);
delete s_clientRecvThread;
s_clientRecvThread = NULL;
}
if (s_initialized)
{
DeleteCriticalSection(&s_sendLock);
DeleteCriticalSection(&s_connectionsLock);
DeleteCriticalSection(&s_advertiseLock);
DeleteCriticalSection(&s_discoveryLock);
DeleteCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.clear();
DeleteCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.clear();
WSACleanup();
s_initialized = false;
}
}
bool WinsockNetLayer::HostGame(int port)
{
if (!s_initialized && !Initialize()) return false;
s_isHost = true;
s_localSmallId = 0;
s_hostSmallId = 0;
s_nextSmallId = 1;
s_hostGamePort = port;
EnterCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.clear();
LeaveCriticalSection(&s_freeSmallIdLock);
int iResult;
#if defined _WINDOWS64
struct addrinfo hints = {};
struct addrinfo *result = NULL;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
char portStr[16];
sprintf_s(portStr, "%d", port);
iResult = getaddrinfo(NULL, portStr, &hints, &result);
if (iResult != 0)
{
app.DebugPrintf("getaddrinfo failed: %d\n", iResult);
return false;
}
s_listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (s_listenSocket == INVALID_SOCKET)
{
app.DebugPrintf("socket() failed: %d\n", WSAGetLastError());
freeaddrinfo(result);
return false;
}
#elif defined __PS3__
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons((uint16_t)port);
s_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s_listenSocket < 0)
{
app.DebugPrintf("socket() failed: %d\n", s_listenSocket);
return false;
}
#endif
int opt = 1;
setsockopt(s_listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
#if defined _WINDOWS64
iResult = ::bind(s_listenSocket, result->ai_addr, (int)result->ai_addrlen);
freeaddrinfo(result);
if (iResult == SOCKET_ERROR)
{
app.DebugPrintf("bind() failed: %d\n", WSAGetLastError());
closesocket(s_listenSocket);
s_listenSocket = INVALID_SOCKET;
return false;
}
iResult = listen(s_listenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR)
{
app.DebugPrintf("listen() failed: %d\n", WSAGetLastError());
closesocket(s_listenSocket);
s_listenSocket = INVALID_SOCKET;
return false;
}
#elif defined __PS3__
iResult = ::bind(s_listenSocket, (struct sockaddr*)&addr, sizeof(addr));
if (iResult < 0)
{
app.DebugPrintf("bind() failed: %d\n", iResult);
closesocket(s_listenSocket);
s_listenSocket = -1;
return false;
}
iResult = listen(s_listenSocket, 5);
if (iResult < 0)
{
app.DebugPrintf("listen() failed: %d\n", iResult);
closesocket(s_listenSocket);
s_listenSocket = -1;
return false;
}
#endif
s_active = true;
s_connected = true;
s_acceptThread = new C4JThread(AcceptThreadProc, NULL, "AcceptThreadProc");
s_acceptThread->Run();
app.DebugPrintf("LAN: Hosting on port %d\n", port);
return true;
}
bool WinsockNetLayer::JoinGame(const char *ip, int port)
{
if (!s_initialized && !Initialize()) return false;
s_isHost = false;
s_hostSmallId = 0;
#if defined _WINDOWS64
struct addrinfo hints = {};
struct addrinfo *result = NULL;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
char portStr[16];
sprintf_s(portStr, "%d", port);
int iResult = getaddrinfo(ip, portStr, &hints, &result);
if (iResult != 0)
{
app.DebugPrintf("getaddrinfo failed for %s:%d - %d\n", ip, port, iResult);
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());
freeaddrinfo(result);
return false;
}
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)
{
app.DebugPrintf("connect() to %s:%d failed: %d\n", ip, port, WSAGetLastError());
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
return false;
}
#elif defined __PS3__
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons((uint16_t)port);
addr.sin_addr.s_addr = inet_addr(ip);
if (addr.sin_addr.s_addr == INADDR_NONE)
{
app.DebugPrintf("inet_addr failed for %s:%d\n", ip, 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;
}
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)
{
app.DebugPrintf("connect() to %s:%d failed: %d\n", ip, port, s_hostConnectionSocket);
socketclose(s_hostConnectionSocket);
s_hostConnectionSocket = -1;
return false;
}
#endif
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\n");
closesocket(s_hostConnectionSocket);
s_hostConnectionSocket = INVALID_SOCKET;
return false;
}
s_localSmallId = assignBuf[0];
app.DebugPrintf("Win64 LAN: Connected to %s:%d, assigned smallId=%d\n", ip, port, s_localSmallId);
s_active = true;
s_connected = true;
s_clientRecvThread = new C4JThread(ClientRecvThreadProc, NULL, "ClientRecvThreadProc");
s_clientRecvThread->Run();
return true;
}
bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void *data, int dataSize)
{
#if defined _WINDOWS64
if (sock == INVALID_SOCKET || dataSize <= 0) return false;
#elif defined __PS3__
if (sock < 0 || dataSize <= 0) return false;
#endif
EnterCriticalSection(&s_sendLock);
BYTE header[4];
header[0] = (BYTE)((dataSize >> 24) & 0xFF);
header[1] = (BYTE)((dataSize >> 16) & 0xFF);
header[2] = (BYTE)((dataSize >> 8) & 0xFF);
header[3] = (BYTE)(dataSize & 0xFF);
int totalSent = 0;
int toSend = 4;
while (totalSent < toSend)
{
int sent = send(sock, (const char *)header + totalSent, toSend - totalSent, 0);
#if defined _WINDOWS64
if (sent == SOCKET_ERROR || sent == 0)
#elif defined __PS3__
if (sent < 0 || sent == 0)
#endif
{
LeaveCriticalSection(&s_sendLock);
return false;
}
totalSent += sent;
}
totalSent = 0;
while (totalSent < dataSize)
{
int sent = send(sock, (const char *)data + totalSent, dataSize - totalSent, 0);
#if defined _WINDOWS64
if (sent == SOCKET_ERROR || sent == 0)
#elif defined __PS3__
if (sent < 0 || sent == 0)
#endif
{
LeaveCriticalSection(&s_sendLock);
return false;
}
totalSent += sent;
}
LeaveCriticalSection(&s_sendLock);
return true;
}
bool WinsockNetLayer::SendToSmallId(BYTE targetSmallId, const void *data, int dataSize)
{
if (!s_active) return false;
if (s_isHost)
{
SOCKET sock = GetSocketForSmallId(targetSmallId);
#if defined _WINDOWS64
if (sock == INVALID_SOCKET) return false;
#elif defined __PS3__
if (sock < 0) return false;
#endif
return SendOnSocket(sock, data, dataSize);
}
else
{
return SendOnSocket(s_hostConnectionSocket, data, dataSize);
}
}
SOCKET WinsockNetLayer::GetSocketForSmallId(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)
{
SOCKET sock = s_connections[i].tcpSocket;
LeaveCriticalSection(&s_connectionsLock);
return sock;
}
}
LeaveCriticalSection(&s_connectionsLock);
#if defined _WINDOWS64
return INVALID_SOCKET;
#elif defined __PS3__
return -1;
#endif
}
static bool RecvExact(SOCKET sock, BYTE *buf, int len)
{
int totalRecv = 0;
while (totalRecv < len)
{
int r = recv(sock, (char *)buf + totalRecv, len - totalRecv, 0);
if (r <= 0) return false;
totalRecv += r;
}
return true;
}
void WinsockNetLayer::HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsigned char *data, unsigned int dataSize)
{
INetworkPlayer *pPlayerFrom = g_NetworkManager.GetPlayerBySmallId(fromSmallId);
INetworkPlayer *pPlayerTo = g_NetworkManager.GetPlayerBySmallId(toSmallId);
if (pPlayerFrom == NULL || pPlayerTo == NULL) return;
if (s_isHost)
{
::Socket *pSocket = pPlayerFrom->GetSocket();
if (pSocket != NULL)
pSocket->pushDataToQueue(data, dataSize, false);
}
else
{
::Socket *pSocket = pPlayerTo->GetSocket();
if (pSocket != NULL)
pSocket->pushDataToQueue(data, dataSize, true);
}
}
int WinsockNetLayer::AcceptThreadProc(LPVOID param)
{
while (s_active)
{
SOCKET clientSocket = accept(s_listenSocket, NULL, NULL);
#if defined _WINDOWS64
if (clientSocket == INVALID_SOCKET)
{
if (s_active)
app.DebugPrintf("accept() failed: %d\n", WSAGetLastError());
break;
}
#elif __PS3__
if (clientSocket < 0)
{
if (s_active)
app.DebugPrintf("accept() failed: %d\n", clientSocket);
break;
}
#endif
int noDelay = 1;
setsockopt(clientSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay));
extern QNET_STATE _iQNetStubState;
if (_iQNetStubState != QNET_STATE_GAME_PLAY)
{
app.DebugPrintf("Win64 LAN: Rejecting connection, game not ready\n");
closesocket(clientSocket);
continue;
}
BYTE assignedSmallId;
EnterCriticalSection(&s_freeSmallIdLock);
if (!s_freeSmallIds.empty())
{
assignedSmallId = s_freeSmallIds.back();
s_freeSmallIds.pop_back();
}
else if (s_nextSmallId < MINECRAFT_NET_MAX_PLAYERS)
{
assignedSmallId = s_nextSmallId++;
}
else
{
LeaveCriticalSection(&s_freeSmallIdLock);
app.DebugPrintf("Win64 LAN: Server full, rejecting connection\n");
closesocket(clientSocket);
continue;
}
LeaveCriticalSection(&s_freeSmallIdLock);
BYTE assignBuf[1] = { assignedSmallId };
int sent = send(clientSocket, (const char *)assignBuf, 1, 0);
if (sent != 1)
{
app.DebugPrintf("Failed to send small ID to client\n");
closesocket(clientSocket);
continue;
}
Win64RemoteConnection conn;
conn.tcpSocket = clientSocket;
conn.smallId = assignedSmallId;
conn.active = true;
conn.recvThread = NULL;
EnterCriticalSection(&s_connectionsLock);
s_connections.push_back(conn);
int connIdx = (int)s_connections.size() - 1;
LeaveCriticalSection(&s_connectionsLock);
app.DebugPrintf("Win64 LAN: Client connected, assigned smallId=%d\n", assignedSmallId);
IQNetPlayer *qnetPlayer = &IQNet::m_player[assignedSmallId];
extern void Win64_SetupRemoteQNetPlayer(IQNetPlayer *player, BYTE smallId, bool isHost, bool isLocal);
Win64_SetupRemoteQNetPlayer(qnetPlayer, assignedSmallId, false, false);
extern CPlatformNetworkManagerStub *g_pPlatformNetworkManager;
g_pPlatformNetworkManager->NotifyPlayerJoined(qnetPlayer);
DWORD *threadParam = new DWORD;
*threadParam = connIdx;
C4JThread* hThread = new C4JThread(RecvThreadProc, threadParam, "RecvThreadProc");
hThread->Run();
EnterCriticalSection(&s_connectionsLock);
if (connIdx < (int)s_connections.size())
s_connections[connIdx].recvThread = hThread;
LeaveCriticalSection(&s_connectionsLock);
}
return 0;
}
int WinsockNetLayer::RecvThreadProc(LPVOID param)
{
DWORD connIdx = *(DWORD *)param;
delete (DWORD *)param;
EnterCriticalSection(&s_connectionsLock);
if (connIdx >= (DWORD)s_connections.size())
{
LeaveCriticalSection(&s_connectionsLock);
return 0;
}
SOCKET sock = s_connections[connIdx].tcpSocket;
BYTE clientSmallId = s_connections[connIdx].smallId;
LeaveCriticalSection(&s_connectionsLock);
BYTE *recvBuf = new BYTE[WIN64_NET_RECV_BUFFER_SIZE];
while (s_active)
{
BYTE header[4];
if (!RecvExact(sock, header, 4))
{
app.DebugPrintf("Win64 LAN: Client smallId=%d disconnected (header)\n", clientSmallId);
break;
}
int packetSize = (header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3];
if (packetSize <= 0 || packetSize > WIN64_NET_RECV_BUFFER_SIZE)
{
app.DebugPrintf("Win64 LAN: Invalid packet size %d from client smallId=%d\n", packetSize, clientSmallId);
break;
}
if (!RecvExact(sock, recvBuf, packetSize))
{
app.DebugPrintf("Win64 LAN: Client smallId=%d disconnected (body)\n", clientSmallId);
break;
}
HandleDataReceived(clientSmallId, s_hostSmallId, recvBuf, 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;
break;
}
}
LeaveCriticalSection(&s_connectionsLock);
EnterCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.push_back(clientSmallId);
LeaveCriticalSection(&s_disconnectLock);
return 0;
}
bool WinsockNetLayer::PopDisconnectedSmallId(BYTE *outSmallId)
{
bool found = false;
EnterCriticalSection(&s_disconnectLock);
if (!s_disconnectedSmallIds.empty())
{
*outSmallId = s_disconnectedSmallIds.back();
s_disconnectedSmallIds.pop_back();
found = true;
}
LeaveCriticalSection(&s_disconnectLock);
return found;
}
void WinsockNetLayer::PushFreeSmallId(BYTE smallId)
{
EnterCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.push_back(smallId);
LeaveCriticalSection(&s_freeSmallIdLock);
}
int WinsockNetLayer::ClientRecvThreadProc(LPVOID param)
{
BYTE *recvBuf = new BYTE[WIN64_NET_RECV_BUFFER_SIZE];
#if defined _WINDOWS64
while (s_active && s_hostConnectionSocket != INVALID_SOCKET)
#elif defined __PS3__
while (s_active && s_hostConnectionSocket >= 0)
#endif
{
BYTE header[4];
if (!RecvExact(s_hostConnectionSocket, header, 4))
{
app.DebugPrintf("Win64 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)
{
app.DebugPrintf("Win64 LAN: Invalid packet size %d from host\n", packetSize);
break;
}
if (!RecvExact(s_hostConnectionSocket, recvBuf, packetSize))
{
app.DebugPrintf("Win64 LAN: Disconnected from host (body)\n");
break;
}
HandleDataReceived(s_hostSmallId, s_localSmallId, recvBuf, packetSize);
}
delete[] recvBuf;
s_connected = false;
return 0;
}
bool WinsockNetLayer::StartAdvertising(int gamePort, const wchar_t *hostName, unsigned int gameSettings, unsigned int texPackId, unsigned char subTexId, unsigned short netVer)
{
if (s_advertising) return true;
if (!s_initialized) return false;
EnterCriticalSection(&s_advertiseLock);
memset(&s_advertiseData, 0, sizeof(s_advertiseData));
s_advertiseData.magic = WIN64_LAN_BROADCAST_MAGIC;
s_advertiseData.netVersion = netVer;
s_advertiseData.gamePort = (WORD)gamePort;
size_t converted = wcstombs(s_advertiseData.hostName, hostName, 32);
s_advertiseData.playerCount = 1;
s_advertiseData.maxPlayers = MINECRAFT_NET_MAX_PLAYERS;
s_advertiseData.gameHostSettings = gameSettings;
s_advertiseData.texturePackParentId = texPackId;
s_advertiseData.subTexturePackId = subTexId;
s_advertiseData.isJoinable = 0;
s_hostGamePort = gamePort;
LeaveCriticalSection(&s_advertiseLock);
s_advertiseSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#if defined _WINDOWS64
if (s_advertiseSock == INVALID_SOCKET)
{
app.DebugPrintf("Win64 LAN: Failed to create advertise socket: %d\n", WSAGetLastError());
return false;
}
#elif defined __PS3__
if (s_advertiseSock < 0)
{
app.DebugPrintf("PS3 LAN: Failed to create advertise socket: %d\n", s_advertiseSock);
return false;
}
#endif
BOOL broadcast = TRUE;
setsockopt(s_advertiseSock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast));
s_advertising = true;
s_advertiseThread = new C4JThread(AdvertiseThreadProc, NULL, "AdvertiseThreadProc");
s_advertiseThread->Run();
app.DebugPrintf("Win64 LAN: Started advertising on UDP port %d\n", WIN64_LAN_DISCOVERY_PORT);
return true;
}
void WinsockNetLayer::StopAdvertising()
{
s_advertising = false;
#if defined _WINDOWS64
if (s_advertiseSock != INVALID_SOCKET)
{
closesocket(s_advertiseSock);
s_advertiseSock = INVALID_SOCKET;
}
#elif defined __PS3__
if (s_advertiseSock >= 0)
{
closesocket(s_advertiseSock);
s_advertiseSock = -1;
}
#endif
if (s_advertiseThread != NULL)
{
s_advertiseThread->WaitForCompletion(2000);
delete s_advertiseThread;
s_advertiseThread = NULL;
}
}
void WinsockNetLayer::UpdateAdvertisePlayerCount(BYTE count)
{
EnterCriticalSection(&s_advertiseLock);
s_advertiseData.playerCount = count;
LeaveCriticalSection(&s_advertiseLock);
}
void WinsockNetLayer::UpdateAdvertiseJoinable(bool joinable)
{
EnterCriticalSection(&s_advertiseLock);
s_advertiseData.isJoinable = joinable ? 1 : 0;
LeaveCriticalSection(&s_advertiseLock);
}
int WinsockNetLayer::AdvertiseThreadProc(LPVOID param)
{
struct sockaddr_in broadcastAddr;
memset(&broadcastAddr, 0, sizeof(broadcastAddr));
broadcastAddr.sin_family = AF_INET;
broadcastAddr.sin_port = htons(WIN64_LAN_DISCOVERY_PORT);
broadcastAddr.sin_addr.s_addr = INADDR_BROADCAST;
while (s_advertising)
{
EnterCriticalSection(&s_advertiseLock);
Win64LANBroadcast data = s_advertiseData;
LeaveCriticalSection(&s_advertiseLock);
data.magic = htonl(data.magic);
data.netVersion = htons(data.netVersion);
data.gamePort = htons(data.gamePort);
data.gameHostSettings = htonl(data.gameHostSettings);
data.texturePackParentId = htonl(data.texturePackParentId);
int sent = sendto(s_advertiseSock, (const char *)&data, sizeof(data), 0,
(struct sockaddr *)&broadcastAddr, sizeof(broadcastAddr));
#if defined _WINDOWS64
if (sent == SOCKET_ERROR && s_advertising)
{
app.DebugPrintf("Win64 LAN: Broadcast sendto failed: %d\n", WSAGetLastError());
}
#elif defined __PS3__
if (sent < 0 && s_advertising)
{
app.DebugPrintf("PS3 LAN: Broadcast sendto failed: %d\n", sent);
}
#endif
C4JThread::Sleep(1000);
}
return 0;
}
bool WinsockNetLayer::StartDiscovery()
{
if (s_discovering) return true;
if (!s_initialized) return false;
s_discoverySock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#if defined _WINDOWS64
if (s_discoverySock == INVALID_SOCKET)
{
app.DebugPrintf("Win64 LAN: Failed to create discovery socket: %d\n", WSAGetLastError());
return false;
}
#elif defined __PS3__
app.DebugPrintf("Discovery socket: %d\n", s_discoverySock);
if (s_discoverySock < 0)
{
app.DebugPrintf("PS3 LAN: Failed to create discovery socket: %d\n", s_discoverySock);
cellNetCtlTerm();
sys_net_finalize_network();
return false;
}
#endif
BOOL reuseAddr = TRUE;
setsockopt(s_discoverySock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuseAddr, sizeof(reuseAddr));
struct sockaddr_in bindAddr;
memset(&bindAddr, 0, sizeof(bindAddr));
bindAddr.sin_family = AF_INET;
bindAddr.sin_port = htons(WIN64_LAN_DISCOVERY_PORT);
bindAddr.sin_addr.s_addr = INADDR_ANY;
#if defined _WINDOWS64
if (::bind(s_discoverySock, (struct sockaddr *)&bindAddr, sizeof(bindAddr)) == SOCKET_ERROR)
{
app.DebugPrintf("Win64 LAN: Discovery bind failed: %d\n", WSAGetLastError());
closesocket(s_discoverySock);
s_discoverySock = INVALID_SOCKET;
return false;
}
#elif defined __PS3__
if (::bind(s_discoverySock, (struct sockaddr *)&bindAddr, sizeof(bindAddr)) < 0)
{
app.DebugPrintf("PS3 LAN: Discovery bind failed: %d\n", sys_net_errno);
socketclose(s_discoverySock);
s_discoverySock = -1;
return false;
}
#endif
DWORD timeout = 500;
setsockopt(s_discoverySock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout));
s_discovering = true;
s_discoveryThread = new C4JThread(DiscoveryThreadProc, NULL, "DiscoveryThreadProc");
s_discoveryThread->Run();
app.DebugPrintf("LAN: Listening for LAN games on UDP port %d\n", WIN64_LAN_DISCOVERY_PORT);
return true;
}
void WinsockNetLayer::StopDiscovery()
{
s_discovering = false;
#if defined _WINDOWS64
if (s_discoverySock != INVALID_SOCKET)
{
closesocket(s_discoverySock);
s_discoverySock = INVALID_SOCKET;
}
#elif defined __PS3__
if (s_discoverySock >= 0)
{
socketclose(s_discoverySock);
s_discoverySock = -1;
}
#endif
if (s_discoveryThread != NULL)
{
s_discoveryThread->WaitForCompletion(2000);
delete s_discoveryThread;
s_discoveryThread = NULL;
}
EnterCriticalSection(&s_discoveryLock);
s_discoveredSessions.clear();
LeaveCriticalSection(&s_discoveryLock);
}
std::vector<Win64LANSession> WinsockNetLayer::GetDiscoveredSessions()
{
std::vector<Win64LANSession> result;
EnterCriticalSection(&s_discoveryLock);
result = s_discoveredSessions;
LeaveCriticalSection(&s_discoveryLock);
return result;
}
int WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
{
app.DebugPrintf("Discovery thread started\n");
char recvBuf[512];
while (s_discovering)
{
struct sockaddr_in senderAddr;
#if defined _WINDOWS64
int senderLen = sizeof(senderAddr);
#elif defined __PS3__
socklen_t senderLen = sizeof(senderAddr);
#endif
int recvLen = recvfrom(s_discoverySock, recvBuf, sizeof(recvBuf), 0,
(struct sockaddr *)&senderAddr, &senderLen);
#ifdef _WINDOWS64
if (recvLen == SOCKET_ERROR)
{
continue;
}
#elif defined __PS3__
if (recvLen < 0)
{
continue;
}
#endif
if (recvLen < (int)sizeof(Win64LANBroadcast))
continue;
Win64LANBroadcast *broadcast = (Win64LANBroadcast *)recvBuf;
broadcast->magic = ntohl(broadcast->magic);
broadcast->netVersion = ntohs(broadcast->netVersion);
broadcast->gamePort = ntohs(broadcast->gamePort);
broadcast->gameHostSettings = ntohl(broadcast->gameHostSettings);
broadcast->texturePackParentId = ntohl(broadcast->texturePackParentId);
if (broadcast->magic != WIN64_LAN_BROADCAST_MAGIC)
continue;
char senderIP[64];
inet_ntop(AF_INET, &senderAddr.sin_addr, senderIP, sizeof(senderIP));
DWORD now = GetTickCount();
EnterCriticalSection(&s_discoveryLock);
bool found = false;
for (size_t i = 0; i < s_discoveredSessions.size(); i++)
{
if (strcmp(s_discoveredSessions[i].hostIP, senderIP) == 0 &&
s_discoveredSessions[i].hostPort == (int)broadcast->gamePort)
{
s_discoveredSessions[i].netVersion = broadcast->netVersion;
size_t converted = mbstowcs(s_discoveredSessions[i].hostName, broadcast->hostName, 31);
if (converted != (size_t)-1)
s_discoveredSessions[i].hostName[converted] = L'\0';
else
s_discoveredSessions[i].hostName[0] = L'\0';
s_discoveredSessions[i].hostName[31] = L'\0';
s_discoveredSessions[i].playerCount = broadcast->playerCount;
s_discoveredSessions[i].maxPlayers = broadcast->maxPlayers;
s_discoveredSessions[i].gameHostSettings = broadcast->gameHostSettings;
s_discoveredSessions[i].texturePackParentId = broadcast->texturePackParentId;
s_discoveredSessions[i].subTexturePackId = broadcast->subTexturePackId;
s_discoveredSessions[i].isJoinable = (broadcast->isJoinable != 0);
s_discoveredSessions[i].lastSeenTick = now;
found = true;
break;
}
}
if (!found)
{
Win64LANSession session;
memset(&session, 0, sizeof(session));
strncpy(session.hostIP, senderIP, sizeof(session.hostIP) - 1);
session.hostIP[sizeof(session.hostIP) - 1] = '\0';
session.hostPort = (int)broadcast->gamePort;
session.netVersion = broadcast->netVersion;
size_t converted = mbstowcs(session.hostName, broadcast->hostName, 31);
if (converted != (size_t)-1)
session.hostName[converted] = L'\0';
else
session.hostName[0] = L'\0';
session.playerCount = broadcast->playerCount;
session.maxPlayers = broadcast->maxPlayers;
session.gameHostSettings = broadcast->gameHostSettings;
session.texturePackParentId = broadcast->texturePackParentId;
session.subTexturePackId = broadcast->subTexturePackId;
session.isJoinable = (broadcast->isJoinable != 0);
session.lastSeenTick = now;
s_discoveredSessions.push_back(session);
app.DebugPrintf("LAN: Discovered game \"%ls\" at %s:%d\n",
session.hostName, session.hostIP, session.hostPort);
}
for (size_t i = s_discoveredSessions.size(); i > 0; i--)
{
if (now - s_discoveredSessions[i - 1].lastSeenTick > 5000)
{
app.DebugPrintf("LAN: Session \"%ls\" at %s timed out\n",
s_discoveredSessions[i - 1].hostName, s_discoveredSessions[i - 1].hostIP);
s_discoveredSessions.erase(s_discoveredSessions.begin() + (i - 1));
}
}
LeaveCriticalSection(&s_discoveryLock);
}
return 0;
}