mirror of
https://github.com/Minecraft-Community-Edition/client.git
synced 2026-05-22 17:14:35 +00:00
355 lines
9.7 KiB
C++
355 lines
9.7 KiB
C++
#include "stdafx.h"
|
|
#ifdef _WINDOWS64
|
|
|
|
#include "LANSessionManager.h"
|
|
#include "..\..\..\Minecraft.World\System.h"
|
|
#include "..\..\..\Minecraft.World\StringHelpers.h"
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
|
|
CLANSessionManager::CLANSessionManager()
|
|
{
|
|
m_broadcastSocket = INVALID_SOCKET;
|
|
m_listenSocket = INVALID_SOCKET;
|
|
m_initialized = false;
|
|
m_broadcasting = false;
|
|
m_listening = false;
|
|
m_sessionsChanged = false;
|
|
m_lastBroadcastTime = 0;
|
|
m_localSessionId = 0;
|
|
memset(&m_broadcastPacket, 0, sizeof(m_broadcastPacket));
|
|
}
|
|
|
|
CLANSessionManager::~CLANSessionManager()
|
|
{
|
|
Shutdown();
|
|
}
|
|
|
|
bool CLANSessionManager::Initialize()
|
|
{
|
|
if (m_initialized)
|
|
return true;
|
|
|
|
WSADATA wsaData;
|
|
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
|
|
|
InitializeCriticalSection(&m_sessionsLock);
|
|
|
|
srand((unsigned int)time(NULL) ^ GetCurrentProcessId());
|
|
|
|
m_initialized = true;
|
|
app.DebugPrintf("LAN: Session manager initialized\n");
|
|
return true;
|
|
}
|
|
|
|
void CLANSessionManager::Shutdown()
|
|
{
|
|
if (!m_initialized)
|
|
return;
|
|
|
|
StopBroadcasting();
|
|
StopListening();
|
|
|
|
// Clean up discovered sessions
|
|
EnterCriticalSection(&m_sessionsLock);
|
|
for (size_t i = 0; i < m_discoveredSessions.size(); i++)
|
|
{
|
|
delete m_discoveredSessions[i].info;
|
|
}
|
|
m_discoveredSessions.clear();
|
|
LeaveCriticalSection(&m_sessionsLock);
|
|
|
|
DeleteCriticalSection(&m_sessionsLock);
|
|
m_initialized = false;
|
|
}
|
|
|
|
void CLANSessionManager::StartBroadcasting(const GameSessionData& sessionData, const char* hostName, const char* worldName)
|
|
{
|
|
if (!m_initialized)
|
|
return;
|
|
|
|
// Create broadcast socket
|
|
m_broadcastSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (m_broadcastSocket == INVALID_SOCKET)
|
|
{
|
|
app.DebugPrintf("LAN: Failed to create broadcast socket: %d\n", WSAGetLastError());
|
|
return;
|
|
}
|
|
|
|
BOOL broadcastEnable = TRUE;
|
|
setsockopt(m_broadcastSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcastEnable, sizeof(broadcastEnable));
|
|
|
|
BOOL reuseAddr = TRUE;
|
|
setsockopt(m_broadcastSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseAddr, sizeof(reuseAddr));
|
|
|
|
m_localSessionId = GenerateSessionId();
|
|
|
|
memset(&m_broadcastPacket, 0, sizeof(m_broadcastPacket));
|
|
m_broadcastPacket.magic = LAN_BROADCAST_MAGIC;
|
|
m_broadcastPacket.netVersion = sessionData.netVersion;
|
|
m_broadcastPacket.gameHostSettings = sessionData.m_uiGameHostSettings;
|
|
m_broadcastPacket.texturePackParentId = sessionData.texturePackParentId;
|
|
m_broadcastPacket.subTexturePackId = sessionData.subTexturePackId;
|
|
m_broadcastPacket.playerCount = 1; // Just the host for now
|
|
m_broadcastPacket.maxPlayers = MINECRAFT_NET_MAX_PLAYERS;
|
|
m_broadcastPacket.isJoinable = 1;
|
|
m_broadcastPacket.gamePort = 0; // Reserved for future TCP connection
|
|
m_broadcastPacket.hostIP = 0; // Receiver fills this from recvfrom
|
|
m_broadcastPacket.sessionId = m_localSessionId;
|
|
|
|
if (hostName != NULL)
|
|
strncpy(m_broadcastPacket.hostName, hostName, LAN_MAX_HOST_NAME - 1);
|
|
else
|
|
strncpy(m_broadcastPacket.hostName, "Player", LAN_MAX_HOST_NAME - 1);
|
|
|
|
if (worldName != NULL)
|
|
strncpy(m_broadcastPacket.worldName, worldName, LAN_MAX_WORLD_NAME - 1);
|
|
else
|
|
strncpy(m_broadcastPacket.worldName, "Minecraft World", LAN_MAX_WORLD_NAME - 1);
|
|
|
|
m_broadcasting = true;
|
|
m_lastBroadcastTime = 0; // Broadcast immediately on first tick
|
|
|
|
app.DebugPrintf("LAN: Started broadcasting session '%s' (host: %s, sessionId: %llu)\n",
|
|
m_broadcastPacket.worldName, m_broadcastPacket.hostName, m_localSessionId);
|
|
}
|
|
|
|
void CLANSessionManager::StopBroadcasting()
|
|
{
|
|
if (m_broadcastSocket != INVALID_SOCKET)
|
|
{
|
|
closesocket(m_broadcastSocket);
|
|
m_broadcastSocket = INVALID_SOCKET;
|
|
}
|
|
m_broadcasting = false;
|
|
}
|
|
|
|
void CLANSessionManager::SetPlayerCount(unsigned char count)
|
|
{
|
|
m_broadcastPacket.playerCount = count;
|
|
}
|
|
|
|
void CLANSessionManager::StartListening()
|
|
{
|
|
if (!m_initialized || m_listening)
|
|
return;
|
|
|
|
// Create listen socket
|
|
m_listenSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (m_listenSocket == INVALID_SOCKET)
|
|
{
|
|
app.DebugPrintf("LAN: Failed to create listen socket: %d\n", WSAGetLastError());
|
|
return;
|
|
}
|
|
|
|
BOOL reuseAddr = TRUE;
|
|
setsockopt(m_listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseAddr, sizeof(reuseAddr));
|
|
|
|
// Bind to the broadcast port
|
|
sockaddr_in listenAddr;
|
|
memset(&listenAddr, 0, sizeof(listenAddr));
|
|
listenAddr.sin_family = AF_INET;
|
|
listenAddr.sin_addr.s_addr = INADDR_ANY;
|
|
listenAddr.sin_port = htons(LAN_BROADCAST_PORT);
|
|
|
|
if (::bind(m_listenSocket, (sockaddr*)&listenAddr, sizeof(listenAddr)) == SOCKET_ERROR)
|
|
{
|
|
app.DebugPrintf("LAN: Failed to bind listen socket to port %d: %d\n", LAN_BROADCAST_PORT, WSAGetLastError());
|
|
closesocket(m_listenSocket);
|
|
m_listenSocket = INVALID_SOCKET;
|
|
return;
|
|
}
|
|
|
|
// Set non-blocking
|
|
u_long nonBlocking = 1;
|
|
ioctlsocket(m_listenSocket, FIONBIO, &nonBlocking);
|
|
|
|
m_listening = true;
|
|
app.DebugPrintf("LAN: Started listening for sessions on port %d\n", LAN_BROADCAST_PORT);
|
|
}
|
|
|
|
void CLANSessionManager::StopListening()
|
|
{
|
|
if (m_listenSocket != INVALID_SOCKET)
|
|
{
|
|
closesocket(m_listenSocket);
|
|
m_listenSocket = INVALID_SOCKET;
|
|
}
|
|
m_listening = false;
|
|
}
|
|
|
|
void CLANSessionManager::Tick()
|
|
{
|
|
if (!m_initialized)
|
|
return;
|
|
|
|
if (m_broadcasting)
|
|
TickBroadcast();
|
|
|
|
if (m_listening)
|
|
{
|
|
TickListen();
|
|
CleanExpiredSessions();
|
|
}
|
|
}
|
|
|
|
void CLANSessionManager::TickBroadcast()
|
|
{
|
|
__int64 now = System::currentTimeMillis();
|
|
|
|
if (now - m_lastBroadcastTime < LAN_BROADCAST_INTERVAL_MS)
|
|
return;
|
|
|
|
m_lastBroadcastTime = now;
|
|
|
|
sockaddr_in broadcastAddr;
|
|
memset(&broadcastAddr, 0, sizeof(broadcastAddr));
|
|
broadcastAddr.sin_family = AF_INET;
|
|
broadcastAddr.sin_port = htons(LAN_BROADCAST_PORT);
|
|
|
|
broadcastAddr.sin_addr.s_addr = INADDR_BROADCAST;
|
|
sendto(m_broadcastSocket, (const char*)&m_broadcastPacket, sizeof(m_broadcastPacket), 0,
|
|
(const sockaddr*)&broadcastAddr, sizeof(broadcastAddr));
|
|
|
|
broadcastAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
sendto(m_broadcastSocket, (const char*)&m_broadcastPacket, sizeof(m_broadcastPacket), 0,
|
|
(const sockaddr*)&broadcastAddr, sizeof(broadcastAddr));
|
|
}
|
|
|
|
void CLANSessionManager::TickListen()
|
|
{
|
|
LANBroadcastPacket packet;
|
|
sockaddr_in fromAddr;
|
|
int fromLen = sizeof(fromAddr);
|
|
|
|
while (true)
|
|
{
|
|
int recvLen = recvfrom(m_listenSocket, (char*)&packet, sizeof(packet), 0,
|
|
(sockaddr*)&fromAddr, &fromLen);
|
|
|
|
if (recvLen <= 0)
|
|
break;
|
|
|
|
if (recvLen < (int)sizeof(LANBroadcastPacket))
|
|
continue;
|
|
|
|
if (packet.magic != LAN_BROADCAST_MAGIC)
|
|
continue;
|
|
|
|
if (packet.sessionId == m_localSessionId && m_broadcasting)
|
|
continue;
|
|
|
|
unsigned int senderIP = fromAddr.sin_addr.s_addr;
|
|
|
|
__int64 now = System::currentTimeMillis();
|
|
|
|
EnterCriticalSection(&m_sessionsLock);
|
|
|
|
bool found = false;
|
|
for (size_t i = 0; i < m_discoveredSessions.size(); i++)
|
|
{
|
|
if (m_discoveredSessions[i].info->sessionId == packet.sessionId)
|
|
{
|
|
m_discoveredSessions[i].lastSeenTime = now;
|
|
m_discoveredSessions[i].hostIP = senderIP;
|
|
m_discoveredSessions[i].hostPort = packet.gamePort;
|
|
|
|
// Update session data
|
|
m_discoveredSessions[i].info->data.m_uiGameHostSettings = packet.gameHostSettings;
|
|
m_discoveredSessions[i].info->data.texturePackParentId = packet.texturePackParentId;
|
|
m_discoveredSessions[i].info->data.subTexturePackId = packet.subTexturePackId;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
LANDiscoveredSession newSession;
|
|
newSession.info = new FriendSessionInfo();
|
|
newSession.info->sessionId = packet.sessionId;
|
|
newSession.lastSeenTime = now;
|
|
newSession.hostIP = senderIP;
|
|
newSession.hostPort = packet.gamePort;
|
|
|
|
newSession.info->data.netVersion = packet.netVersion;
|
|
newSession.info->data.m_uiGameHostSettings = packet.gameHostSettings;
|
|
newSession.info->data.texturePackParentId = packet.texturePackParentId;
|
|
newSession.info->data.subTexturePackId = packet.subTexturePackId;
|
|
newSession.info->data.isReadyToJoin = (packet.isJoinable != 0);
|
|
|
|
wchar_t label[256];
|
|
wchar_t wHostName[LAN_MAX_HOST_NAME];
|
|
wchar_t wWorldName[LAN_MAX_WORLD_NAME];
|
|
mbstowcs(wHostName, packet.hostName, LAN_MAX_HOST_NAME);
|
|
mbstowcs(wWorldName, packet.worldName, LAN_MAX_WORLD_NAME);
|
|
|
|
swprintf_s(label, 256, L"%s - %s (%d/%d)",
|
|
wHostName, wWorldName, packet.playerCount, packet.maxPlayers);
|
|
|
|
int labelLen = (int)wcslen(label);
|
|
newSession.info->displayLabel = new wchar_t[labelLen + 1];
|
|
wcscpy_s(newSession.info->displayLabel, labelLen + 1, label);
|
|
newSession.info->displayLabelLength = (unsigned char)labelLen;
|
|
newSession.info->hasPartyMember = false;
|
|
|
|
m_discoveredSessions.push_back(newSession);
|
|
m_sessionsChanged = true;
|
|
|
|
in_addr addr;
|
|
addr.s_addr = senderIP;
|
|
app.DebugPrintf("LAN: Discovered session '%s' from %s (sessionId: %llu)\n",
|
|
packet.hostName, inet_ntoa(addr), packet.sessionId);
|
|
}
|
|
|
|
LeaveCriticalSection(&m_sessionsLock);
|
|
}
|
|
}
|
|
|
|
void CLANSessionManager::CleanExpiredSessions()
|
|
{
|
|
__int64 now = System::currentTimeMillis();
|
|
|
|
EnterCriticalSection(&m_sessionsLock);
|
|
|
|
for (int i = (int)m_discoveredSessions.size() - 1; i >= 0; i--)
|
|
{
|
|
if (now - m_discoveredSessions[i].lastSeenTime > LAN_SESSION_EXPIRY_MS)
|
|
{
|
|
app.DebugPrintf("LAN: Session expired (sessionId: %llu)\n",
|
|
m_discoveredSessions[i].info->sessionId);
|
|
delete m_discoveredSessions[i].info;
|
|
m_discoveredSessions.erase(m_discoveredSessions.begin() + i);
|
|
m_sessionsChanged = true;
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&m_sessionsLock);
|
|
}
|
|
|
|
std::vector<FriendSessionInfo*>* CLANSessionManager::GetSessionList()
|
|
{
|
|
std::vector<FriendSessionInfo*>* list = new std::vector<FriendSessionInfo*>();
|
|
|
|
EnterCriticalSection(&m_sessionsLock);
|
|
|
|
for (size_t i = 0; i < m_discoveredSessions.size(); i++)
|
|
{
|
|
list->push_back(m_discoveredSessions[i].info);
|
|
}
|
|
|
|
LeaveCriticalSection(&m_sessionsLock);
|
|
|
|
return list;
|
|
}
|
|
|
|
SessionID CLANSessionManager::GenerateSessionId()
|
|
{
|
|
SessionID id = 0;
|
|
id = (SessionID)GetCurrentProcessId();
|
|
id = (id << 32) | (SessionID)(time(NULL) ^ rand());
|
|
return id;
|
|
}
|
|
|
|
#endif // _WINDOWS64
|