From 8b28c20d7adc3824f96fbcc34ad65d778a97a05b Mon Sep 17 00:00:00 2001 From: lspepinho <162769565+lspepinho@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:30:22 -0300 Subject: Fixes for PR #96 (#170) * Implement basic multiplayer functionality * Update README.md --------- Co-authored-by: Slenderman --- .../Windows64/Network/WinsockNetLayer.cpp | 844 +++++++++++++++++++++ 1 file changed, 844 insertions(+) create mode 100644 Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp (limited to 'Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp') diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp new file mode 100644 index 00000000..19fc2598 --- /dev/null +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -0,0 +1,844 @@ +#include "stdafx.h" + +#ifdef _WINDOWS64 + +#include "WinsockNetLayer.h" +#include "..\..\Common\Network\PlatformNetworkManagerStub.h" +#include "..\..\..\Minecraft.World\Socket.h" + +SOCKET WinsockNetLayer::s_listenSocket = INVALID_SOCKET; +SOCKET WinsockNetLayer::s_hostConnectionSocket = INVALID_SOCKET; +HANDLE WinsockNetLayer::s_acceptThread = NULL; +HANDLE 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 WinsockNetLayer::s_connections; + +SOCKET WinsockNetLayer::s_advertiseSock = INVALID_SOCKET; +HANDLE 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; + +SOCKET WinsockNetLayer::s_discoverySock = INVALID_SOCKET; +HANDLE WinsockNetLayer::s_discoveryThread = NULL; +volatile bool WinsockNetLayer::s_discovering = false; +CRITICAL_SECTION WinsockNetLayer::s_discoveryLock; +std::vector WinsockNetLayer::s_discoveredSessions; + +CRITICAL_SECTION WinsockNetLayer::s_disconnectLock; +std::vector WinsockNetLayer::s_disconnectedSmallIds; + +CRITICAL_SECTION WinsockNetLayer::s_freeSmallIdLock; +std::vector WinsockNetLayer::s_freeSmallIds; + +bool g_Win64MultiplayerHost = false; +bool g_Win64MultiplayerJoin = false; +int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT; +char g_Win64MultiplayerIP[256] = "127.0.0.1"; + +bool WinsockNetLayer::Initialize() +{ + if (s_initialized) return true; + + WSADATA wsaData; + int result = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (result != 0) + { + app.DebugPrintf("WSAStartup failed: %d\n", result); + return false; + } + + 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 (s_listenSocket != INVALID_SOCKET) + { + closesocket(s_listenSocket); + s_listenSocket = INVALID_SOCKET; + } + + if (s_hostConnectionSocket != INVALID_SOCKET) + { + closesocket(s_hostConnectionSocket); + s_hostConnectionSocket = INVALID_SOCKET; + } + + EnterCriticalSection(&s_connectionsLock); + for (size_t i = 0; i < s_connections.size(); i++) + { + s_connections[i].active = false; + if (s_connections[i].tcpSocket != INVALID_SOCKET) + { + closesocket(s_connections[i].tcpSocket); + } + } + s_connections.clear(); + LeaveCriticalSection(&s_connectionsLock); + + if (s_acceptThread != NULL) + { + WaitForSingleObject(s_acceptThread, 2000); + CloseHandle(s_acceptThread); + s_acceptThread = NULL; + } + + if (s_clientRecvThread != NULL) + { + WaitForSingleObject(s_clientRecvThread, 2000); + CloseHandle(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); + + 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); + + int 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; + } + + int opt = 1; + setsockopt(s_listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); + + 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; + } + + s_active = true; + s_connected = true; + + s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, NULL, 0, NULL); + + app.DebugPrintf("Win64 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; + + 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; + } + + 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 = CreateThread(NULL, 0, ClientRecvThreadProc, NULL, 0, NULL); + + return true; +} + +bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void *data, int dataSize) +{ + if (sock == INVALID_SOCKET || dataSize <= 0) return false; + + 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 (sent == SOCKET_ERROR || sent == 0) + { + LeaveCriticalSection(&s_sendLock); + return false; + } + totalSent += sent; + } + + totalSent = 0; + while (totalSent < dataSize) + { + int sent = send(sock, (const char *)data + totalSent, dataSize - totalSent, 0); + if (sent == SOCKET_ERROR || sent == 0) + { + 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 (sock == INVALID_SOCKET) return false; + 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); + return INVALID_SOCKET; +} + +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); + } +} + +DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) +{ + while (s_active) + { + SOCKET clientSocket = accept(s_listenSocket, NULL, NULL); + if (clientSocket == INVALID_SOCKET) + { + if (s_active) + app.DebugPrintf("accept() failed: %d\n", WSAGetLastError()); + break; + } + + 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; + HANDLE hThread = CreateThread(NULL, 0, RecvThreadProc, threadParam, 0, NULL); + + EnterCriticalSection(&s_connectionsLock); + if (connIdx < (int)s_connections.size()) + s_connections[connIdx].recvThread = hThread; + LeaveCriticalSection(&s_connectionsLock); + } + return 0; +} + +DWORD WINAPI 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); +} + +DWORD WINAPI WinsockNetLayer::ClientRecvThreadProc(LPVOID param) +{ + BYTE *recvBuf = new BYTE[WIN64_NET_RECV_BUFFER_SIZE]; + + while (s_active && s_hostConnectionSocket != INVALID_SOCKET) + { + 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; + wcsncpy_s(s_advertiseData.hostName, 32, hostName, _TRUNCATE); + 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 (s_advertiseSock == INVALID_SOCKET) + { + app.DebugPrintf("Win64 LAN: Failed to create advertise socket: %d\n", WSAGetLastError()); + return false; + } + + BOOL broadcast = TRUE; + setsockopt(s_advertiseSock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)); + + s_advertising = true; + s_advertiseThread = CreateThread(NULL, 0, AdvertiseThreadProc, NULL, 0, NULL); + + app.DebugPrintf("Win64 LAN: Started advertising on UDP port %d\n", WIN64_LAN_DISCOVERY_PORT); + return true; +} + +void WinsockNetLayer::StopAdvertising() +{ + s_advertising = false; + + if (s_advertiseSock != INVALID_SOCKET) + { + closesocket(s_advertiseSock); + s_advertiseSock = INVALID_SOCKET; + } + + if (s_advertiseThread != NULL) + { + WaitForSingleObject(s_advertiseThread, 2000); + CloseHandle(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); +} + +DWORD WINAPI 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); + + int sent = sendto(s_advertiseSock, (const char *)&data, sizeof(data), 0, + (struct sockaddr *)&broadcastAddr, sizeof(broadcastAddr)); + + if (sent == SOCKET_ERROR && s_advertising) + { + app.DebugPrintf("Win64 LAN: Broadcast sendto failed: %d\n", WSAGetLastError()); + } + + 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 (s_discoverySock == INVALID_SOCKET) + { + app.DebugPrintf("Win64 LAN: Failed to create discovery socket: %d\n", WSAGetLastError()); + return false; + } + + 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 (::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; + } + + DWORD timeout = 500; + setsockopt(s_discoverySock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout)); + + s_discovering = true; + s_discoveryThread = CreateThread(NULL, 0, DiscoveryThreadProc, NULL, 0, NULL); + + app.DebugPrintf("Win64 LAN: Listening for LAN games on UDP port %d\n", WIN64_LAN_DISCOVERY_PORT); + return true; +} + +void WinsockNetLayer::StopDiscovery() +{ + s_discovering = false; + + if (s_discoverySock != INVALID_SOCKET) + { + closesocket(s_discoverySock); + s_discoverySock = INVALID_SOCKET; + } + + if (s_discoveryThread != NULL) + { + WaitForSingleObject(s_discoveryThread, 2000); + CloseHandle(s_discoveryThread); + s_discoveryThread = NULL; + } + + EnterCriticalSection(&s_discoveryLock); + s_discoveredSessions.clear(); + LeaveCriticalSection(&s_discoveryLock); +} + +std::vector WinsockNetLayer::GetDiscoveredSessions() +{ + std::vector result; + EnterCriticalSection(&s_discoveryLock); + result = s_discoveredSessions; + LeaveCriticalSection(&s_discoveryLock); + return result; +} + +DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param) +{ + char recvBuf[512]; + + while (s_discovering) + { + struct sockaddr_in senderAddr; + int senderLen = sizeof(senderAddr); + + int recvLen = recvfrom(s_discoverySock, recvBuf, sizeof(recvBuf), 0, + (struct sockaddr *)&senderAddr, &senderLen); + + if (recvLen == SOCKET_ERROR) + { + continue; + } + + if (recvLen < (int)sizeof(Win64LANBroadcast)) + continue; + + Win64LANBroadcast *broadcast = (Win64LANBroadcast *)recvBuf; + 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; + wcsncpy_s(s_discoveredSessions[i].hostName, 32, broadcast->hostName, _TRUNCATE); + 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_s(session.hostIP, sizeof(session.hostIP), senderIP, _TRUNCATE); + session.hostPort = (int)broadcast->gamePort; + session.netVersion = broadcast->netVersion; + wcsncpy_s(session.hostName, 32, broadcast->hostName, _TRUNCATE); + 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("Win64 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("Win64 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; +} + +#endif -- cgit v1.2.3 From ac480f674500e15c3cdb1633bcc17278cc08337e Mon Sep 17 00:00:00 2001 From: Slenderman Date: Tue, 3 Mar 2026 16:50:28 -0500 Subject: Update username logic and implement latest LCEMP changes (#311) * Update username logic and implement latest LCEMP changes * Update old reference * Fix tutorial world crash * Restore deleted comment --- Minecraft.Client/ClientConnection.cpp | 65 ++++++- Minecraft.Client/Extrax64Stubs.cpp | 52 ++--- Minecraft.Client/Windows64/4JLibs/inc/4J_Profile.h | 2 +- .../Windows64/Network/WinsockNetLayer.cpp | 215 ++++++++++++++------- .../Windows64/Network/WinsockNetLayer.h | 16 +- Minecraft.Client/Windows64/Windows64_App.cpp | 39 +--- Minecraft.Client/Windows64/Windows64_App.h | 1 - Minecraft.Client/Windows64/Windows64_Minecraft.cpp | 44 +++++ 8 files changed, 287 insertions(+), 147 deletions(-) (limited to 'Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp') diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index f3510d33..8123a2f0 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -55,6 +55,12 @@ #endif #include "DLCTexturePack.h" +#ifdef _WINDOWS64 +#include "Xbox\Network\NetworkPlayerXbox.h" +#include "Common\Network\PlatformNetworkManagerStub.h" +#endif + + #ifdef _DURANGO #include "..\Minecraft.World\DurangoStats.h" #include "..\Minecraft.World\GenericStats.h" @@ -421,7 +427,6 @@ void ClientConnection::handleAddEntity(shared_ptr packet) { case AddEntityPacket::MINECART: e = Minecart::createMinecart(level, x, y, z, packet->data); - break; case AddEntityPacket::FISH_HOOK: { // 4J Stu - Brought forward from 1.4 to be able to drop XP from fishing @@ -444,7 +449,7 @@ void ClientConnection::handleAddEntity(shared_ptr packet) } } - if (owner->instanceof(eTYPE_PLAYER)) + if (owner != NULL && owner->instanceof(eTYPE_PLAYER)) { shared_ptr player = dynamic_pointer_cast(owner); shared_ptr hook = shared_ptr( new FishingHook(level, x, y, z, player) ); @@ -793,7 +798,28 @@ void ClientConnection::handleAddPlayer(shared_ptr packet) if (networkPlayer != NULL) player->m_displayName = networkPlayer->GetDisplayName(); #else // On all other platforms display name is just gamertag so don't check with the network manager - player->m_displayName = player->name; + player->m_displayName = player->getName(); +#endif + +#ifdef _WINDOWS64 + { + PlayerUID pktXuid = player->getXuid(); + const PlayerUID WIN64_XUID_BASE = (PlayerUID)0xe000d45248242f2e; + if (pktXuid >= WIN64_XUID_BASE && pktXuid < WIN64_XUID_BASE + MINECRAFT_NET_MAX_PLAYERS) + { + BYTE smallId = (BYTE)(pktXuid - WIN64_XUID_BASE); + INetworkPlayer* np = g_NetworkManager.GetPlayerBySmallId(smallId); + if (np != NULL) + { + NetworkPlayerXbox* npx = (NetworkPlayerXbox*)np; + IQNetPlayer* qp = npx->GetQNetPlayer(); + if (qp != NULL && qp->m_gamertag[0] == 0) + { + wcsncpy_s(qp->m_gamertag, 32, packet->name.c_str(), _TRUNCATE); + } + } + } + } #endif // printf("\t\t\t\t%d: Add player\n",packet->id,packet->yRot); @@ -938,6 +964,39 @@ void ClientConnection::handleMoveEntitySmall(shared_ptr p void ClientConnection::handleRemoveEntity(shared_ptr packet) { +#ifdef _WINDOWS64 + if (!g_NetworkManager.IsHost()) + { + for (int i = 0; i < packet->ids.length; i++) + { + shared_ptr entity = getEntity(packet->ids[i]); + if (entity != NULL && entity->GetType() == eTYPE_PLAYER) + { + shared_ptr player = dynamic_pointer_cast(entity); + if (player != NULL) + { + PlayerUID xuid = player->getXuid(); + INetworkPlayer* np = g_NetworkManager.GetPlayerByXuid(xuid); + if (np != NULL) + { + NetworkPlayerXbox* npx = (NetworkPlayerXbox*)np; + IQNetPlayer* qp = npx->GetQNetPlayer(); + if (qp != NULL) + { + extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager; + g_pPlatformNetworkManager->NotifyPlayerLeaving(qp); + qp->m_smallId = 0; + qp->m_isRemote = false; + qp->m_isHostPlayer = false; + qp->m_gamertag[0] = 0; + qp->SetCustomDataValue(0); + } + } + } + } + } + } +#endif for (int i = 0; i < packet->ids.length; i++) { level->removeEntity(packet->ids[i]); diff --git a/Minecraft.Client/Extrax64Stubs.cpp b/Minecraft.Client/Extrax64Stubs.cpp index 22ad578f..5a3c5279 100644 --- a/Minecraft.Client/Extrax64Stubs.cpp +++ b/Minecraft.Client/Extrax64Stubs.cpp @@ -199,11 +199,10 @@ DWORD IQNetPlayer::GetSendQueueSize(IQNetPlayer * player, DWORD dwFlags) { retur DWORD IQNetPlayer::GetCurrentRtt() { return 0; } bool IQNetPlayer::IsHost() { return m_isHostPlayer; } bool IQNetPlayer::IsGuest() { return false; } -bool IQNetPlayer::IsLocal() { return true; } +bool IQNetPlayer::IsLocal() { return !m_isRemote; } PlayerUID IQNetPlayer::GetXuid() { return (PlayerUID)(0xe000d45248242f2e + m_smallId); } // todo: restore to INVALID_XUID once saves support this -extern wstring g_playerName; -LPCWSTR IQNetPlayer::GetGamertag() { return g_playerName.empty() ? L"Windows" : g_playerName.c_str(); } -int IQNetPlayer::GetSessionIndex() { return 0; } +LPCWSTR IQNetPlayer::GetGamertag() { return m_gamertag; } +int IQNetPlayer::GetSessionIndex() { return m_smallId; } bool IQNetPlayer::IsTalking() { return false; } bool IQNetPlayer::IsMutedByLocalUser(DWORD dwUserIndex) { return false; } bool IQNetPlayer::HasVoice() { return false; } @@ -232,13 +231,17 @@ void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost IQNet::s_playerCount = smallId + 1; } +static bool Win64_IsActivePlayer(IQNetPlayer* p, DWORD index); + HRESULT IQNet::AddLocalPlayerByUserIndex(DWORD dwUserIndex) { return S_OK; } IQNetPlayer* IQNet::GetHostPlayer() { return &m_player[0]; } IQNetPlayer* IQNet::GetLocalPlayerByUserIndex(DWORD dwUserIndex) { if (s_isHosting) { - if (dwUserIndex < MINECRAFT_NET_MAX_PLAYERS && !m_player[dwUserIndex].m_isRemote) + if (dwUserIndex < MINECRAFT_NET_MAX_PLAYERS && + !m_player[dwUserIndex].m_isRemote && + Win64_IsActivePlayer(&m_player[dwUserIndex], dwUserIndex)) return &m_player[dwUserIndex]; return NULL; } @@ -246,7 +249,7 @@ IQNetPlayer* IQNet::GetLocalPlayerByUserIndex(DWORD dwUserIndex) return NULL; for (DWORD i = 0; i < s_playerCount; i++) { - if (!m_player[i].m_isRemote) + if (!m_player[i].m_isRemote && Win64_IsActivePlayer(&m_player[i], i)) return &m_player[i]; } return NULL; @@ -299,15 +302,28 @@ QNET_STATE IQNet::GetState() { return _iQNetStubState; } bool IQNet::IsHost() { return s_isHosting; } HRESULT IQNet::JoinGameFromInviteInfo(DWORD dwUserIndex, DWORD dwUserMask, const INVITE_INFO * pInviteInfo) { return S_OK; } void IQNet::HostGame() { _iQNetStubState = QNET_STATE_SESSION_STARTING; s_isHosting = true; } -void IQNet::ClientJoinGame() { _iQNetStubState = QNET_STATE_SESSION_STARTING; s_isHosting = false; } +void IQNet::ClientJoinGame() +{ + _iQNetStubState = QNET_STATE_SESSION_STARTING; + s_isHosting = false; + + for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++) + { + m_player[i].m_smallId = (BYTE)i; + m_player[i].m_isRemote = true; + m_player[i].m_isHostPlayer = false; + m_player[i].m_gamertag[0] = 0; + m_player[i].SetCustomDataValue(0); + } +} void IQNet::EndGame() { _iQNetStubState = QNET_STATE_IDLE; s_isHosting = false; s_playerCount = 1; - for (int i = 1; i < MINECRAFT_NET_MAX_PLAYERS; i++) + for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++) { - m_player[i].m_smallId = 0; + m_player[i].m_smallId = (BYTE)i; m_player[i].m_isRemote = false; m_player[i].m_isHostPlayer = false; m_player[i].m_gamertag[0] = 0; @@ -587,23 +603,9 @@ void C_4JProfile::SetPrimaryPad(int iPad) {} #ifdef _DURANGO char fakeGamerTag[32] = "PlayerName"; void SetFakeGamertag(char* name) { strcpy_s(fakeGamerTag, name); } -char* C_4JProfile::GetGamertag(int iPad) { return fakeGamerTag; } #else -#include - -const char* C_4JProfile::GetGamertag(int iPad) -{ - static std::string narrowName; - const wchar_t* wideName = g_playerName.c_str(); - - int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, wideName, -1, nullptr, 0, nullptr, nullptr); - - narrowName.resize(sizeNeeded); - WideCharToMultiByte(CP_UTF8, 0, wideName, -1, &narrowName[0], sizeNeeded, nullptr, nullptr); - - return narrowName.c_str(); -} -wstring C_4JProfile::GetDisplayName(int iPad) { return g_playerName; } +char* C_4JProfile::GetGamertag(int iPad) { extern char g_Win64Username[17]; return g_Win64Username; } +wstring C_4JProfile::GetDisplayName(int iPad) { extern wchar_t g_Win64UsernameW[17]; return g_Win64UsernameW; } #endif bool C_4JProfile::IsFullVersion() { return s_bProfileIsFullVersion; } void C_4JProfile::SetSignInChangeCallback(void (*Func)(LPVOID, bool, unsigned int), LPVOID lpParam) {} diff --git a/Minecraft.Client/Windows64/4JLibs/inc/4J_Profile.h b/Minecraft.Client/Windows64/4JLibs/inc/4J_Profile.h index f7718a83..f1bd85bb 100644 --- a/Minecraft.Client/Windows64/4JLibs/inc/4J_Profile.h +++ b/Minecraft.Client/Windows64/4JLibs/inc/4J_Profile.h @@ -75,7 +75,7 @@ public: // SYS int GetPrimaryPad(); void SetPrimaryPad(int iPad); - const char* GetGamertag(int iPad); + char* GetGamertag(int iPad); wstring GetDisplayName(int iPad); bool IsFullVersion(); void SetSignInChangeCallback(void ( *Func)(LPVOID, bool, unsigned int),LPVOID lpParam); diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index 19fc2598..d3ea1c3a 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -1,3 +1,6 @@ +// Code implemented by LCEMP, credit if used on other repos +// https://github.com/LCEMP/LCEMP + #include "stdafx.h" #ifdef _WINDOWS64 @@ -151,7 +154,7 @@ bool WinsockNetLayer::HostGame(int port) LeaveCriticalSection(&s_freeSmallIdLock); struct addrinfo hints = {}; - struct addrinfo *result = NULL; + struct addrinfo* result = NULL; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; @@ -177,7 +180,7 @@ bool WinsockNetLayer::HostGame(int port) } int opt = 1; - setsockopt(s_listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); + setsockopt(s_listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)); iResult = ::bind(s_listenSocket, result->ai_addr, (int)result->ai_addrlen); freeaddrinfo(result); @@ -207,15 +210,23 @@ bool WinsockNetLayer::HostGame(int port) return true; } -bool WinsockNetLayer::JoinGame(const char *ip, int port) +bool WinsockNetLayer::JoinGame(const char* ip, int port) { if (!s_initialized && !Initialize()) return false; s_isHost = false; s_hostSmallId = 0; + s_connected = false; + s_active = false; + + if (s_hostConnectionSocket != INVALID_SOCKET) + { + closesocket(s_hostConnectionSocket); + s_hostConnectionSocket = INVALID_SOCKET; + } struct addrinfo hints = {}; - struct addrinfo *result = NULL; + struct addrinfo* result = NULL; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; @@ -231,37 +242,55 @@ 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; + const int maxAttempts = 12; + + 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)); + 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; + iResult = connect(s_hostConnectionSocket, result->ai_addr, (int)result->ai_addrlen); + if (iResult == SOCKET_ERROR) + { + int err = WSAGetLastError(); + 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; + Sleep(200); + continue; + } + + 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; + Sleep(200); + continue; + } + + assignedSmallId = assignBuf[0]; + connected = true; + break; } + freeaddrinfo(result); - 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); @@ -273,7 +302,7 @@ bool WinsockNetLayer::JoinGame(const char *ip, int port) return true; } -bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void *data, int dataSize) +bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize) { if (sock == INVALID_SOCKET || dataSize <= 0) return false; @@ -289,7 +318,7 @@ bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void *data, int dataSize) int toSend = 4; while (totalSent < toSend) { - int sent = send(sock, (const char *)header + totalSent, toSend - totalSent, 0); + int sent = send(sock, (const char*)header + totalSent, toSend - totalSent, 0); if (sent == SOCKET_ERROR || sent == 0) { LeaveCriticalSection(&s_sendLock); @@ -301,7 +330,7 @@ bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void *data, int dataSize) totalSent = 0; while (totalSent < dataSize) { - int sent = send(sock, (const char *)data + totalSent, dataSize - totalSent, 0); + int sent = send(sock, (const char*)data + totalSent, dataSize - totalSent, 0); if (sent == SOCKET_ERROR || sent == 0) { LeaveCriticalSection(&s_sendLock); @@ -314,7 +343,7 @@ bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void *data, int dataSize) return true; } -bool WinsockNetLayer::SendToSmallId(BYTE targetSmallId, const void *data, int dataSize) +bool WinsockNetLayer::SendToSmallId(BYTE targetSmallId, const void* data, int dataSize) { if (!s_active) return false; @@ -346,34 +375,34 @@ SOCKET WinsockNetLayer::GetSocketForSmallId(BYTE smallId) return INVALID_SOCKET; } -static bool RecvExact(SOCKET sock, BYTE *buf, int len) +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); + 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) +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); + 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(); + ::Socket* pSocket = pPlayerFrom->GetSocket(); if (pSocket != NULL) pSocket->pushDataToQueue(data, dataSize, false); } else { - ::Socket *pSocket = pPlayerTo->GetSocket(); + ::Socket* pSocket = pPlayerTo->GetSocket(); if (pSocket != NULL) pSocket->pushDataToQueue(data, dataSize, true); } @@ -392,7 +421,7 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) } int noDelay = 1; - setsockopt(clientSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay)); + setsockopt(clientSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay)); extern QNET_STATE _iQNetStubState; if (_iQNetStubState != QNET_STATE_GAME_PLAY) @@ -423,7 +452,7 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) LeaveCriticalSection(&s_freeSmallIdLock); BYTE assignBuf[1] = { assignedSmallId }; - int sent = send(clientSocket, (const char *)assignBuf, 1, 0); + int sent = send(clientSocket, (const char*)assignBuf, 1, 0); if (sent != 1) { app.DebugPrintf("Failed to send small ID to client\n"); @@ -444,15 +473,15 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) app.DebugPrintf("Win64 LAN: Client connected, assigned smallId=%d\n", assignedSmallId); - IQNetPlayer *qnetPlayer = &IQNet::m_player[assignedSmallId]; + IQNetPlayer* qnetPlayer = &IQNet::m_player[assignedSmallId]; - extern void Win64_SetupRemoteQNetPlayer(IQNetPlayer *player, BYTE smallId, bool isHost, bool isLocal); + extern void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost, bool isLocal); Win64_SetupRemoteQNetPlayer(qnetPlayer, assignedSmallId, false, false); - extern CPlatformNetworkManagerStub *g_pPlatformNetworkManager; + extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager; g_pPlatformNetworkManager->NotifyPlayerJoined(qnetPlayer); - DWORD *threadParam = new DWORD; + DWORD* threadParam = new DWORD; *threadParam = connIdx; HANDLE hThread = CreateThread(NULL, 0, RecvThreadProc, threadParam, 0, NULL); @@ -466,8 +495,8 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) DWORD WINAPI WinsockNetLayer::RecvThreadProc(LPVOID param) { - DWORD connIdx = *(DWORD *)param; - delete (DWORD *)param; + DWORD connIdx = *(DWORD*)param; + delete (DWORD*)param; EnterCriticalSection(&s_connectionsLock); if (connIdx >= (DWORD)s_connections.size()) @@ -479,7 +508,8 @@ DWORD WINAPI WinsockNetLayer::RecvThreadProc(LPVOID param) BYTE clientSmallId = s_connections[connIdx].smallId; LeaveCriticalSection(&s_connectionsLock); - BYTE *recvBuf = new BYTE[WIN64_NET_RECV_BUFFER_SIZE]; + std::vector recvBuf; + recvBuf.resize(WIN64_NET_RECV_BUFFER_SIZE); while (s_active) { @@ -490,33 +520,47 @@ DWORD WINAPI 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("Win64 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) + { + recvBuf.resize(packetSize); + app.DebugPrintf("Win64 LAN: Resized host recv buffer to %d bytes for client smallId=%d\n", packetSize, clientSmallId); + } + + if (!RecvExact(sock, &recvBuf[0], packetSize)) { app.DebugPrintf("Win64 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; } } @@ -529,7 +573,7 @@ DWORD WINAPI WinsockNetLayer::RecvThreadProc(LPVOID param) return 0; } -bool WinsockNetLayer::PopDisconnectedSmallId(BYTE *outSmallId) +bool WinsockNetLayer::PopDisconnectedSmallId(BYTE* outSmallId) { bool found = false; EnterCriticalSection(&s_disconnectLock); @@ -550,9 +594,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); +} + DWORD WINAPI WinsockNetLayer::ClientRecvThreadProc(LPVOID param) { - BYTE *recvBuf = new BYTE[WIN64_NET_RECV_BUFFER_SIZE]; + std::vector recvBuf; + recvBuf.resize(WIN64_NET_RECV_BUFFER_SIZE); while (s_active && s_hostConnectionSocket != INVALID_SOCKET) { @@ -565,28 +626,34 @@ DWORD WINAPI WinsockNetLayer::ClientRecvThreadProc(LPVOID param) 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("Win64 LAN: Invalid packet size %d from host (max=%d)\n", + packetSize, + (int)WIN64_NET_MAX_PACKET_SIZE); break; } - if (!RecvExact(s_hostConnectionSocket, recvBuf, packetSize)) + if ((int)recvBuf.size() < packetSize) + { + recvBuf.resize(packetSize); + app.DebugPrintf("Win64 LAN: Resized client recv buffer to %d bytes\n", packetSize); + } + + if (!RecvExact(s_hostConnectionSocket, &recvBuf[0], packetSize)) { app.DebugPrintf("Win64 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; } -bool WinsockNetLayer::StartAdvertising(int gamePort, const wchar_t *hostName, unsigned int gameSettings, unsigned int texPackId, unsigned char subTexId, unsigned short netVer) +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; @@ -614,7 +681,7 @@ bool WinsockNetLayer::StartAdvertising(int gamePort, const wchar_t *hostName, un } BOOL broadcast = TRUE; - setsockopt(s_advertiseSock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)); + setsockopt(s_advertiseSock, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof(broadcast)); s_advertising = true; s_advertiseThread = CreateThread(NULL, 0, AdvertiseThreadProc, NULL, 0, NULL); @@ -669,8 +736,8 @@ DWORD WINAPI WinsockNetLayer::AdvertiseThreadProc(LPVOID param) Win64LANBroadcast data = s_advertiseData; LeaveCriticalSection(&s_advertiseLock); - int sent = sendto(s_advertiseSock, (const char *)&data, sizeof(data), 0, - (struct sockaddr *)&broadcastAddr, sizeof(broadcastAddr)); + int sent = sendto(s_advertiseSock, (const char*)&data, sizeof(data), 0, + (struct sockaddr*)&broadcastAddr, sizeof(broadcastAddr)); if (sent == SOCKET_ERROR && s_advertising) { @@ -696,7 +763,7 @@ bool WinsockNetLayer::StartDiscovery() } BOOL reuseAddr = TRUE; - setsockopt(s_discoverySock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuseAddr, sizeof(reuseAddr)); + setsockopt(s_discoverySock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseAddr, sizeof(reuseAddr)); struct sockaddr_in bindAddr; memset(&bindAddr, 0, sizeof(bindAddr)); @@ -704,7 +771,7 @@ bool WinsockNetLayer::StartDiscovery() bindAddr.sin_port = htons(WIN64_LAN_DISCOVERY_PORT); bindAddr.sin_addr.s_addr = INADDR_ANY; - if (::bind(s_discoverySock, (struct sockaddr *)&bindAddr, sizeof(bindAddr)) == SOCKET_ERROR) + if (::bind(s_discoverySock, (struct sockaddr*)&bindAddr, sizeof(bindAddr)) == SOCKET_ERROR) { app.DebugPrintf("Win64 LAN: Discovery bind failed: %d\n", WSAGetLastError()); closesocket(s_discoverySock); @@ -713,7 +780,7 @@ bool WinsockNetLayer::StartDiscovery() } DWORD timeout = 500; - setsockopt(s_discoverySock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout)); + setsockopt(s_discoverySock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); s_discovering = true; s_discoveryThread = CreateThread(NULL, 0, DiscoveryThreadProc, NULL, 0, NULL); @@ -763,7 +830,7 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param) int senderLen = sizeof(senderAddr); int recvLen = recvfrom(s_discoverySock, recvBuf, sizeof(recvBuf), 0, - (struct sockaddr *)&senderAddr, &senderLen); + (struct sockaddr*)&senderAddr, &senderLen); if (recvLen == SOCKET_ERROR) { @@ -773,7 +840,7 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param) if (recvLen < (int)sizeof(Win64LANBroadcast)) continue; - Win64LANBroadcast *broadcast = (Win64LANBroadcast *)recvBuf; + Win64LANBroadcast* broadcast = (Win64LANBroadcast*)recvBuf; if (broadcast->magic != WIN64_LAN_BROADCAST_MAGIC) continue; @@ -841,4 +908,4 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param) return 0; } -#endif +#endif \ No newline at end of file diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h index 96b03c9b..029dd0a7 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h @@ -1,3 +1,5 @@ +// Code implemented by LCEMP, credit if used on other repos +// https://github.com/LCEMP/LCEMP #pragma once #ifdef _WINDOWS64 @@ -12,6 +14,7 @@ #define WIN64_NET_DEFAULT_PORT 25565 #define WIN64_NET_MAX_CLIENTS 7 #define WIN64_NET_RECV_BUFFER_SIZE 65536 +#define WIN64_NET_MAX_PACKET_SIZE (4 * 1024 * 1024) #define WIN64_LAN_DISCOVERY_PORT 25566 #define WIN64_LAN_BROADCAST_MAGIC 0x4D434C4E @@ -63,10 +66,10 @@ public: static void Shutdown(); static bool HostGame(int port); - static bool JoinGame(const char *ip, int port); + static bool JoinGame(const char* ip, int port); - static bool SendToSmallId(BYTE targetSmallId, const void *data, int dataSize); - static bool SendOnSocket(SOCKET sock, const void *data, int dataSize); + static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize); + static bool SendOnSocket(SOCKET sock, const void* data, int dataSize); static bool IsHosting() { return s_isHost; } static bool IsConnected() { return s_connected; } @@ -77,12 +80,13 @@ public: static SOCKET GetSocketForSmallId(BYTE smallId); - static void HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsigned char *data, unsigned int dataSize); + static void HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsigned char* data, unsigned int dataSize); - static bool PopDisconnectedSmallId(BYTE *outSmallId); + static bool PopDisconnectedSmallId(BYTE* outSmallId); static void PushFreeSmallId(BYTE smallId); + static void CloseConnectionBySmallId(BYTE smallId); - static bool StartAdvertising(int gamePort, const wchar_t *hostName, unsigned int gameSettings, unsigned int texPackId, unsigned char subTexId, unsigned short netVer); + static bool StartAdvertising(int gamePort, const wchar_t* hostName, unsigned int gameSettings, unsigned int texPackId, unsigned char subTexId, unsigned short netVer); static void StopAdvertising(); static void UpdateAdvertisePlayerCount(BYTE count); static void UpdateAdvertiseJoinable(bool joinable); diff --git a/Minecraft.Client/Windows64/Windows64_App.cpp b/Minecraft.Client/Windows64/Windows64_App.cpp index 461e8c34..ad9e1f24 100644 --- a/Minecraft.Client/Windows64/Windows64_App.cpp +++ b/Minecraft.Client/Windows64/Windows64_App.cpp @@ -10,46 +10,11 @@ #include "..\..\Minecraft.World\BiomeSource.h" #include "..\..\Minecraft.World\LevelType.h" -wstring g_playerName; - CConsoleMinecraftApp app; -static void LoadPlayerName() -{ - if (!g_playerName.empty()) return; - g_playerName = L"Windows"; - - char exePath[MAX_PATH] = {}; - GetModuleFileNameA(NULL, exePath, MAX_PATH); - char *lastSlash = strrchr(exePath, '\\'); - if (lastSlash) *(lastSlash + 1) = '\0'; - char filePath[MAX_PATH] = {}; - _snprintf_s(filePath, sizeof(filePath), _TRUNCATE, "%susername.txt", exePath); - - FILE *f = NULL; - if (fopen_s(&f, filePath, "r") == 0 && f) - { - char buf[128] = {}; - if (fgets(buf, sizeof(buf), f)) - { - int len = (int)strlen(buf); - while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r' || buf[len-1] == ' ')) - buf[--len] = '\0'; - if (len > 0) - { - wchar_t wbuf[128] = {}; - mbstowcs(wbuf, buf, 127); - g_playerName = wbuf; - } - } - fclose(f); - } -} - CConsoleMinecraftApp::CConsoleMinecraftApp() : CMinecraftApp() { m_bShutdown = false; - LoadPlayerName(); } void CConsoleMinecraftApp::SetRichPresenceContext(int iPad, int contextId) @@ -110,8 +75,8 @@ void CConsoleMinecraftApp::TemporaryCreateGameStart() Minecraft *pMinecraft=Minecraft::GetInstance(); app.ReleaseSaveThumbnail(); ProfileManager.SetLockedProfile(0); - LoadPlayerName(); - pMinecraft->user->name = g_playerName; + extern wchar_t g_Win64UsernameW[17]; + pMinecraft->user->name = g_Win64UsernameW; app.ApplyGameSettingsChanged(0); ////////////////////////////////////////////////////////////////////////////////////////////// From CScene_MultiGameJoinLoad::OnInit diff --git a/Minecraft.Client/Windows64/Windows64_App.h b/Minecraft.Client/Windows64/Windows64_App.h index 32d204ad..bff916ec 100644 --- a/Minecraft.Client/Windows64/Windows64_App.h +++ b/Minecraft.Client/Windows64/Windows64_App.h @@ -33,7 +33,6 @@ public: virtual void TemporaryCreateGameStart(); bool m_bShutdown; - wstring g_playerName; }; extern CConsoleMinecraftApp app; diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index 78ab76fe..4a3d835c 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -88,6 +88,9 @@ int g_iScreenHeight = 1080; UINT g_ScreenWidth = 1920; UINT g_ScreenHeight = 1080; +char g_Win64Username[17] = { 0 }; +wchar_t g_Win64UsernameW[17] = { 0 }; + // Fullscreen toggle state static bool g_isFullscreen = false; static WINDOWPLACEMENT g_wpPrev = { sizeof(g_wpPrev) }; @@ -762,8 +765,47 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, //g_iScreenWidth = 960; //g_iScreenHeight = 544; } + + // Default username will be "Windows" + strncpy_s(g_Win64Username, sizeof(g_Win64Username), "Windows", _TRUNCATE); + + char exePath[MAX_PATH] = {}; + GetModuleFileNameA(NULL, exePath, MAX_PATH); + char* lastSlash = strrchr(exePath, '\\'); + if (lastSlash) *(lastSlash + 1) = '\0'; + + char filePath[MAX_PATH] = {}; + _snprintf_s(filePath, sizeof(filePath), _TRUNCATE, "%susername.txt", exePath); + + FILE* f = nullptr; + if (fopen_s(&f, filePath, "r") == 0 && f) + { + char buf[128] = {}; + if (fgets(buf, sizeof(buf), f)) + { + int len = (int)strlen(buf); + while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r' || buf[len - 1] == ' ')) + buf[--len] = '\0'; + + if (len > 0) + { + strncpy_s(g_Win64Username, sizeof(g_Win64Username), buf, _TRUNCATE); + } + } + fclose(f); + } } + if (g_Win64Username[0] == 0) + { + DWORD sz = 17; + if (!GetUserNameA(g_Win64Username, &sz)) + strncpy_s(g_Win64Username, 17, "Player", _TRUNCATE); + g_Win64Username[16] = 0; + } + + MultiByteToWideChar(CP_ACP, 0, g_Win64Username, -1, g_Win64UsernameW, 17); + // Initialize global strings MyRegisterClass(hInstance); @@ -935,6 +977,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, IQNet::m_player[i].m_isHostPlayer = (i == 0); swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i); } + extern wchar_t g_Win64UsernameW[17]; + wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW); WinsockNetLayer::Initialize(); -- cgit v1.2.3 From d112090fde200c545a70ec5dc33fe91cca0f26ec Mon Sep 17 00:00:00 2001 From: daoge_cmd <3523206925@qq.com> Date: Wed, 4 Mar 2026 16:18:47 +0800 Subject: feat: headless server --- CMakeLists.txt | 1 + .../Common/Network/PlatformNetworkManagerStub.cpp | 19 +- Minecraft.Client/Minecraft.Client.vcxproj | 5 +- Minecraft.Client/MinecraftServer.cpp | 470 ++++++++++++++- Minecraft.Client/MinecraftServer.h | 1 + Minecraft.Client/Settings.cpp | 80 ++- Minecraft.Client/Settings.h | 6 +- .../Windows64/Network/WinsockNetLayer.cpp | 21 +- .../Windows64/Network/WinsockNetLayer.h | 5 +- Minecraft.Client/Windows64/Windows64_Minecraft.cpp | 639 ++++++++++++--------- Minecraft.World/Minecraft.World.vcxproj | 5 +- README.md | 12 +- 12 files changed, 973 insertions(+), 291 deletions(-) (limited to 'Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 74851754..2d83c5c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ function(configure_msvc_target target) $<$>,$>:/W3> $<$,$>:/W0> $<$:/MP> + $<$:/FS> $<$:/EHsc> $<$,$>:/GL /O2 /Oi /GT /GF> ) diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index 10d1a6a5..c2466fe3 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -362,12 +362,23 @@ void CPlatformNetworkManagerStub::HostGame(int localUsersMask, bool bOnlineGame, #ifdef _WINDOWS64 int port = WIN64_NET_DEFAULT_PORT; + const char* bindIp = NULL; + if (g_Win64DedicatedServer) + { + if (g_Win64DedicatedServerPort > 0) + port = g_Win64DedicatedServerPort; + if (g_Win64DedicatedServerBindIP[0] != 0) + bindIp = g_Win64DedicatedServerBindIP; + } if (!WinsockNetLayer::IsActive()) - WinsockNetLayer::HostGame(port); + WinsockNetLayer::HostGame(port, bindIp); - const wchar_t* hostName = IQNet::m_player[0].m_gamertag; - unsigned int settings = app.GetGameHostOption(eGameHostOption_All); - WinsockNetLayer::StartAdvertising(port, hostName, settings, 0, 0, MINECRAFT_NET_VERSION); + if (WinsockNetLayer::IsActive()) + { + const wchar_t* hostName = IQNet::m_player[0].m_gamertag; + unsigned int settings = app.GetGameHostOption(eGameHostOption_All); + WinsockNetLayer::StartAdvertising(port, hostName, settings, 0, 0, MINECRAFT_NET_VERSION); + } #endif //#endif } diff --git a/Minecraft.Client/Minecraft.Client.vcxproj b/Minecraft.Client/Minecraft.Client.vcxproj index 54915636..d7c49acf 100644 --- a/Minecraft.Client/Minecraft.Client.vcxproj +++ b/Minecraft.Client/Minecraft.Client.vcxproj @@ -1545,6 +1545,7 @@ if not exist "$(TargetDir)\savedata" mkdir "$(TargetDir)\savedata" true Default false + /FS %(AdditionalOptions) true @@ -1773,7 +1774,7 @@ xcopy /q /y /i /s /e $(ProjectDir)DurangoMedia\CU $(LayoutDir)Image\Loose\CUtrue true true - /Ob3 + /FS /Ob3 %(AdditionalOptions) true @@ -48677,4 +48678,4 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU - \ No newline at end of file + diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp index 4206a399..974c7405 100644 --- a/Minecraft.Client/MinecraftServer.cpp +++ b/Minecraft.Client/MinecraftServer.cpp @@ -27,6 +27,11 @@ #include "..\Minecraft.World\Pos.h" #include "..\Minecraft.World\System.h" #include "..\Minecraft.World\StringHelpers.h" +#include "..\Minecraft.World\net.minecraft.world.entity.item.h" +#include "..\Minecraft.World\net.minecraft.world.item.h" +#include "..\Minecraft.World\net.minecraft.world.item.enchantment.h" +#include "..\Minecraft.World\net.minecraft.world.damagesource.h" +#include #ifdef SPLIT_SAVES #include "..\Minecraft.World\ConsoleSaveFileSplit.h" #endif @@ -78,6 +83,438 @@ bool MinecraftServer::s_slowQueuePacketSent = false; unordered_map MinecraftServer::ironTimers; +static void PrintConsoleLine(const wchar_t *prefix, const wstring &message) +{ + wprintf(L"%ls%ls\n", prefix, message.c_str()); + fflush(stdout); +} + +static bool TryParseIntValue(const wstring &text, int &value) +{ + std::wistringstream stream(text); + stream >> value; + return !stream.fail() && stream.eof(); +} + +static vector SplitConsoleCommand(const wstring &command) +{ + vector tokens; + std::wistringstream stream(command); + wstring token; + while (stream >> token) + { + tokens.push_back(token); + } + return tokens; +} + +static wstring JoinConsoleCommandTokens(const vector &tokens, size_t startIndex) +{ + wstring joined; + for (size_t i = startIndex; i < tokens.size(); ++i) + { + if (!joined.empty()) joined += L" "; + joined += tokens[i]; + } + return joined; +} + +static shared_ptr FindPlayerByName(PlayerList *playerList, const wstring &name) +{ + if (playerList == NULL) return nullptr; + + for (size_t i = 0; i < playerList->players.size(); ++i) + { + shared_ptr player = playerList->players[i]; + if (player != NULL && equalsIgnoreCase(player->getName(), name)) + { + return player; + } + } + + return nullptr; +} + +static void SetAllLevelTimes(MinecraftServer *server, int value) +{ + for (unsigned int i = 0; i < server->levels.length; ++i) + { + if (server->levels[i] != NULL) + { + server->levels[i]->setDayTime(value); + } + } +} + +static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCommand) +{ + if (server == NULL) + return false; + + wstring command = trimString(rawCommand); + if (command.empty()) + return true; + + if (command[0] == L'/') + { + command = trimString(command.substr(1)); + } + + vector tokens = SplitConsoleCommand(command); + if (tokens.empty()) + return true; + + const wstring action = toLower(tokens[0]); + PlayerList *playerList = server->getPlayers(); + + if (action == L"help" || action == L"?") + { + server->info(L"Commands: help, stop, list, say , save-all, time , weather [seconds], tp , give [amount] [aux], enchant [level], kill "); + return true; + } + + if (action == L"stop") + { + server->info(L"Stopping server..."); + MinecraftServer::HaltServer(); + return true; + } + + if (action == L"list") + { + wstring playerNames = (playerList != NULL) ? playerList->getPlayerNames() : L""; + if (playerNames.empty()) playerNames = L"(none)"; + server->info(L"Players (" + _toString((playerList != NULL) ? playerList->getPlayerCount() : 0) + L"): " + playerNames); + return true; + } + + if (action == L"say") + { + if (tokens.size() < 2) + { + server->warn(L"Usage: say "); + return false; + } + + wstring message = L"[Server] " + JoinConsoleCommandTokens(tokens, 1); + if (playerList != NULL) + { + playerList->broadcastAll(shared_ptr(new ChatPacket(message))); + } + server->info(message); + return true; + } + + if (action == L"save-all") + { + if (playerList != NULL) + { + playerList->saveAll(NULL, false); + } + server->info(L"World saved."); + return true; + } + + if (action == L"time") + { + if (tokens.size() < 2) + { + server->warn(L"Usage: time set | time add "); + return false; + } + + if (toLower(tokens[1]) == L"add") + { + if (tokens.size() < 3) + { + server->warn(L"Usage: time add "); + return false; + } + + int delta = 0; + if (!TryParseIntValue(tokens[2], delta)) + { + server->warn(L"Invalid tick value: " + tokens[2]); + return false; + } + + for (unsigned int i = 0; i < server->levels.length; ++i) + { + if (server->levels[i] != NULL) + { + server->levels[i]->setDayTime(server->levels[i]->getDayTime() + delta); + } + } + + server->info(L"Added " + _toString(delta) + L" ticks."); + return true; + } + + wstring timeValue = toLower(tokens[1]); + if (timeValue == L"set") + { + if (tokens.size() < 3) + { + server->warn(L"Usage: time set "); + return false; + } + timeValue = toLower(tokens[2]); + } + + int targetTime = 0; + if (timeValue == L"day") + { + targetTime = 0; + } + else if (timeValue == L"night") + { + targetTime = 12500; + } + else if (!TryParseIntValue(timeValue, targetTime)) + { + server->warn(L"Invalid time value: " + timeValue); + return false; + } + + SetAllLevelTimes(server, targetTime); + server->info(L"Time set to " + _toString(targetTime) + L"."); + return true; + } + + if (action == L"weather") + { + if (tokens.size() < 2) + { + server->warn(L"Usage: weather [seconds]"); + return false; + } + + int durationSeconds = 600; + if (tokens.size() >= 3 && !TryParseIntValue(tokens[2], durationSeconds)) + { + server->warn(L"Invalid duration: " + tokens[2]); + return false; + } + + if (server->levels[0] == NULL) + { + server->warn(L"The overworld is not loaded."); + return false; + } + + LevelData *levelData = server->levels[0]->getLevelData(); + int duration = durationSeconds * SharedConstants::TICKS_PER_SECOND; + levelData->setRainTime(duration); + levelData->setThunderTime(duration); + + wstring weather = toLower(tokens[1]); + if (weather == L"clear") + { + levelData->setRaining(false); + levelData->setThundering(false); + } + else if (weather == L"rain") + { + levelData->setRaining(true); + levelData->setThundering(false); + } + else if (weather == L"thunder") + { + levelData->setRaining(true); + levelData->setThundering(true); + } + else + { + server->warn(L"Usage: weather [seconds]"); + return false; + } + + server->info(L"Weather set to " + weather + L"."); + return true; + } + + if (action == L"tp" || action == L"teleport") + { + if (tokens.size() < 3) + { + server->warn(L"Usage: tp "); + return false; + } + + shared_ptr subject = FindPlayerByName(playerList, tokens[1]); + shared_ptr destination = FindPlayerByName(playerList, tokens[2]); + if (subject == NULL) + { + server->warn(L"Unknown player: " + tokens[1]); + return false; + } + if (destination == NULL) + { + server->warn(L"Unknown player: " + tokens[2]); + return false; + } + if (subject->level->dimension->id != destination->level->dimension->id || !subject->isAlive()) + { + server->warn(L"Teleport failed because the players are not in the same dimension or the source player is dead."); + return false; + } + + subject->ride(nullptr); + subject->connection->teleport(destination->x, destination->y, destination->z, destination->yRot, destination->xRot); + server->info(L"Teleported " + subject->getName() + L" to " + destination->getName() + L"."); + return true; + } + + if (action == L"give") + { + if (tokens.size() < 3) + { + server->warn(L"Usage: give [amount] [aux]"); + return false; + } + + shared_ptr player = FindPlayerByName(playerList, tokens[1]); + if (player == NULL) + { + server->warn(L"Unknown player: " + tokens[1]); + return false; + } + + int itemId = 0; + int amount = 1; + int aux = 0; + if (!TryParseIntValue(tokens[2], itemId)) + { + server->warn(L"Invalid item id: " + tokens[2]); + return false; + } + if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], amount)) + { + server->warn(L"Invalid amount: " + tokens[3]); + return false; + } + if (tokens.size() >= 5 && !TryParseIntValue(tokens[4], aux)) + { + server->warn(L"Invalid aux value: " + tokens[4]); + return false; + } + if (itemId <= 0 || Item::items[itemId] == NULL) + { + server->warn(L"Unknown item id: " + _toString(itemId)); + return false; + } + if (amount <= 0) + { + server->warn(L"Amount must be positive."); + return false; + } + + shared_ptr itemInstance(new ItemInstance(itemId, amount, aux)); + shared_ptr drop = player->drop(itemInstance); + if (drop != NULL) + { + drop->throwTime = 0; + } + server->info(L"Gave item " + _toString(itemId) + L" x" + _toString(amount) + L" to " + player->getName() + L"."); + return true; + } + + if (action == L"enchant") + { + if (tokens.size() < 3) + { + server->warn(L"Usage: enchant [level]"); + return false; + } + + shared_ptr player = FindPlayerByName(playerList, tokens[1]); + if (player == NULL) + { + server->warn(L"Unknown player: " + tokens[1]); + return false; + } + + int enchantmentId = 0; + int enchantmentLevel = 1; + if (!TryParseIntValue(tokens[2], enchantmentId)) + { + server->warn(L"Invalid enchantment id: " + tokens[2]); + return false; + } + if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], enchantmentLevel)) + { + server->warn(L"Invalid enchantment level: " + tokens[3]); + return false; + } + + shared_ptr selectedItem = player->getSelectedItem(); + if (selectedItem == NULL) + { + server->warn(L"The player is not holding an item."); + return false; + } + + Enchantment *enchantment = Enchantment::enchantments[enchantmentId]; + if (enchantment == NULL) + { + server->warn(L"Unknown enchantment id: " + _toString(enchantmentId)); + return false; + } + if (!enchantment->canEnchant(selectedItem)) + { + server->warn(L"That enchantment cannot be applied to the selected item."); + return false; + } + + if (enchantmentLevel < enchantment->getMinLevel()) enchantmentLevel = enchantment->getMinLevel(); + if (enchantmentLevel > enchantment->getMaxLevel()) enchantmentLevel = enchantment->getMaxLevel(); + + if (selectedItem->hasTag()) + { + ListTag *enchantmentTags = selectedItem->getEnchantmentTags(); + if (enchantmentTags != NULL) + { + for (int i = 0; i < enchantmentTags->size(); i++) + { + int type = enchantmentTags->get(i)->getShort((wchar_t *)ItemInstance::TAG_ENCH_ID); + if (Enchantment::enchantments[type] != NULL && !Enchantment::enchantments[type]->isCompatibleWith(enchantment)) + { + server->warn(L"That enchantment conflicts with an existing enchantment on the selected item."); + return false; + } + } + } + } + + selectedItem->enchant(enchantment, enchantmentLevel); + server->info(L"Enchanted " + player->getName() + L"'s held item with " + _toString(enchantmentId) + L" " + _toString(enchantmentLevel) + L"."); + return true; + } + + if (action == L"kill") + { + if (tokens.size() < 2) + { + server->warn(L"Usage: kill "); + return false; + } + + shared_ptr player = FindPlayerByName(playerList, tokens[1]); + if (player == NULL) + { + server->warn(L"Unknown player: " + tokens[1]); + return false; + } + + player->hurt(DamageSource::outOfWorld, 3.4e38f); + server->info(L"Killed " + player->getName() + L"."); + return true; + } + + server->warn(L"Unknown command: " + command); + return false; +} + MinecraftServer::MinecraftServer() { // 4J - added initialisers @@ -107,12 +544,14 @@ MinecraftServer::MinecraftServer() forceGameType = false; commandDispatcher = new ServerCommandDispatcher(); + InitializeCriticalSection(&m_consoleInputCS); DispenserBootstrap::bootStrap(); } MinecraftServer::~MinecraftServer() { + DeleteCriticalSection(&m_consoleInputCS); } bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DWORD initSettings, bool findSeed) @@ -150,6 +589,15 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW #endif settings = new Settings(new File(L"server.properties")); + app.SetGameHostOption(eGameHostOption_Difficulty, settings->getInt(L"difficulty", app.GetGameHostOption(eGameHostOption_Difficulty))); + app.SetGameHostOption(eGameHostOption_GameType, settings->getInt(L"gamemode", app.GetGameHostOption(eGameHostOption_GameType))); + app.SetGameHostOption(eGameHostOption_Structures, settings->getBoolean(L"generate-structures", app.GetGameHostOption(eGameHostOption_Structures) > 0) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_BonusChest, settings->getBoolean(L"bonus-chest", app.GetGameHostOption(eGameHostOption_BonusChest) > 0) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_PvP, settings->getBoolean(L"pvp", app.GetGameHostOption(eGameHostOption_PvP) > 0) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_TrustPlayers, settings->getBoolean(L"trust-players", app.GetGameHostOption(eGameHostOption_TrustPlayers) > 0) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_FireSpreads, settings->getBoolean(L"fire-spreads", app.GetGameHostOption(eGameHostOption_FireSpreads) > 0) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_TNT, settings->getBoolean(L"tnt", app.GetGameHostOption(eGameHostOption_TNT) > 0) ? 1 : 0); + app.DebugPrintf("\n*** SERVER SETTINGS ***\n"); app.DebugPrintf("ServerSettings: host-friends-only is %s\n",(app.GetGameHostOption(eGameHostOption_FriendsOfFriends)>0)?"on":"off"); app.DebugPrintf("ServerSettings: game-type is %s\n",(app.GetGameHostOption(eGameHostOption_GameType)==0)?"Survival Mode":"Creative Mode"); @@ -169,11 +617,11 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW setAnimals(settings->getBoolean(L"spawn-animals", true)); setNpcsEnabled(settings->getBoolean(L"spawn-npcs", true)); - setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false); // settings->getBoolean(L"pvp", true); + setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false); // 4J Stu - We should never have hacked clients flying when they shouldn't be like the PC version, so enable flying always // Fix for #46612 - TU5: Code: Multiplayer: A client can be banned for flying when accidentaly being blown by dynamite - setFlightAllowed(true); //settings->getBoolean(L"allow-flight", false); + setFlightAllowed(settings->getBoolean(L"allow-flight", true)); // 4J Stu - Enabling flight to stop it kicking us when we use it #ifdef _DEBUG_MENUS_ENABLED @@ -1707,17 +2155,23 @@ void MinecraftServer::tick() void MinecraftServer::handleConsoleInput(const wstring& msg, ConsoleInputSource *source) { + EnterCriticalSection(&m_consoleInputCS); consoleInput.push_back(new ConsoleInput(msg, source)); + LeaveCriticalSection(&m_consoleInputCS); } void MinecraftServer::handleConsoleInputs() { - while (consoleInput.size() > 0) + vector pendingInputs; + EnterCriticalSection(&m_consoleInputCS); + pendingInputs.swap(consoleInput); + LeaveCriticalSection(&m_consoleInputCS); + + for (size_t i = 0; i < pendingInputs.size(); ++i) { - AUTO_VAR(it, consoleInput.begin()); - ConsoleInput *input = *it; - consoleInput.erase(it); - // commands->handleCommand(input); // 4J - removed - TODO - do we want equivalent of console commands? + ConsoleInput *input = pendingInputs[i]; + ExecuteConsoleCommand(this, input->msg); + delete input; } } @@ -1750,10 +2204,12 @@ File *MinecraftServer::getFile(const wstring& name) void MinecraftServer::info(const wstring& string) { + PrintConsoleLine(L"[INFO] ", string); } void MinecraftServer::warn(const wstring& string) { + PrintConsoleLine(L"[WARN] ", string); } wstring MinecraftServer::getConsoleName() diff --git a/Minecraft.Client/MinecraftServer.h b/Minecraft.Client/MinecraftServer.h index 5f33fa85..ec0218e0 100644 --- a/Minecraft.Client/MinecraftServer.h +++ b/Minecraft.Client/MinecraftServer.h @@ -103,6 +103,7 @@ private: // vector tickables = new ArrayList(); // 4J - removed CommandDispatcher *commandDispatcher; vector consoleInput; // 4J - was synchronizedList - TODO - investigate + CRITICAL_SECTION m_consoleInputCS; public: bool onlineMode; bool animals; diff --git a/Minecraft.Client/Settings.cpp b/Minecraft.Client/Settings.cpp index 89946773..4223b001 100644 --- a/Minecraft.Client/Settings.cpp +++ b/Minecraft.Client/Settings.cpp @@ -1,18 +1,90 @@ #include "stdafx.h" #include "Settings.h" +#include "..\Minecraft.World\File.h" #include "..\Minecraft.World\StringHelpers.h" +#include + +static wstring ParsePropertyText(const string &text) +{ + return trimString(convStringToWstring(text)); +} + +static bool TryParseBoolean(const wstring &text, bool defaultValue) +{ + wstring lowered = toLower(trimString(text)); + if (lowered == L"true" || lowered == L"1" || lowered == L"yes" || lowered == L"on") + return true; + if (lowered == L"false" || lowered == L"0" || lowered == L"no" || lowered == L"off") + return false; + return defaultValue; +} -// 4J - TODO - serialise/deserialise from file Settings::Settings(File *file) { + if (file != NULL) + { + filePath = file->getPath(); + } + + if (filePath.empty()) + return; + + std::ifstream stream(wstringtofilename(filePath), std::ios::in | std::ios::binary); + if (!stream.is_open()) + return; + + string line; + while (std::getline(stream, line)) + { + if (!line.empty() && line[line.size() - 1] == '\r') + line.erase(line.size() - 1); + + if (line.size() >= 3 && + (unsigned char)line[0] == 0xEF && + (unsigned char)line[1] == 0xBB && + (unsigned char)line[2] == 0xBF) + { + line.erase(0, 3); + } + + size_t commentPos = line.find_first_of("#;"); + if (commentPos != string::npos && line.find_first_not_of(" \t") == commentPos) + continue; + + size_t separatorPos = line.find('='); + if (separatorPos == string::npos) + continue; + + wstring key = ParsePropertyText(line.substr(0, separatorPos)); + if (key.empty()) + continue; + + wstring value = ParsePropertyText(line.substr(separatorPos + 1)); + properties[key] = value; + } } void Settings::generateNewProperties() { + saveProperties(); } void Settings::saveProperties() { + if (filePath.empty()) + return; + + std::ofstream stream(wstringtofilename(filePath), std::ios::out | std::ios::binary | std::ios::trunc); + if (!stream.is_open()) + return; + + stream << "# MinecraftConsoles dedicated server properties\r\n"; + for (unordered_map::const_iterator it = properties.begin(); it != properties.end(); ++it) + { + string key = string(wstringtochararray(it->first)); + string value = string(wstringtochararray(it->second)); + stream << key << "=" << value << "\r\n"; + } } wstring Settings::getString(const wstring& key, const wstring& defaultValue) @@ -39,17 +111,17 @@ bool Settings::getBoolean(const wstring& key, bool defaultValue) { if(properties.find(key) == properties.end()) { - properties[key] = _toString(defaultValue); + properties[key] = defaultValue ? L"true" : L"false"; saveProperties(); } MemSect(35); - bool retval = _fromString(properties[key]); + bool retval = TryParseBoolean(properties[key], defaultValue); MemSect(0); return retval; } void Settings::setBooleanAndSave(const wstring& key, bool value) { - properties[key] = _toString(value); + properties[key] = value ? L"true" : L"false"; saveProperties(); } \ No newline at end of file diff --git a/Minecraft.Client/Settings.h b/Minecraft.Client/Settings.h index b6a2c018..4a3c130b 100644 --- a/Minecraft.Client/Settings.h +++ b/Minecraft.Client/Settings.h @@ -7,8 +7,8 @@ class Settings // public static Logger logger = Logger.getLogger("Minecraft"); // private Properties properties = new Properties(); private: - unordered_map properties; // 4J - TODO was Properties type, will need to implement something we can serialise/deserialise too - //File *file; + unordered_map properties; + wstring filePath; public: Settings(File *file); @@ -18,4 +18,4 @@ public: int getInt(const wstring& key, int defaultValue); bool getBoolean(const wstring& key, bool defaultValue); void setBooleanAndSave(const wstring& key, bool value); -}; +}; \ No newline at end of file diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index d3ea1c3a..ca1d62af 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -51,6 +51,9 @@ bool g_Win64MultiplayerHost = false; bool g_Win64MultiplayerJoin = false; int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT; char g_Win64MultiplayerIP[256] = "127.0.0.1"; +bool g_Win64DedicatedServer = false; +int g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT; +char g_Win64DedicatedServerBindIP[256] = ""; bool WinsockNetLayer::Initialize() { @@ -139,7 +142,7 @@ void WinsockNetLayer::Shutdown() } } -bool WinsockNetLayer::HostGame(int port) +bool WinsockNetLayer::HostGame(int port, const char* bindIp) { if (!s_initialized && !Initialize()) return false; @@ -159,15 +162,19 @@ bool WinsockNetLayer::HostGame(int port) hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = AI_PASSIVE; + hints.ai_flags = (bindIp == NULL || bindIp[0] == 0) ? AI_PASSIVE : 0; char portStr[16]; sprintf_s(portStr, "%d", port); - int iResult = getaddrinfo(NULL, portStr, &hints, &result); + const char* resolvedBindIp = (bindIp != NULL && bindIp[0] != 0) ? bindIp : NULL; + int iResult = getaddrinfo(resolvedBindIp, portStr, &hints, &result); if (iResult != 0) { - app.DebugPrintf("getaddrinfo failed: %d\n", iResult); + app.DebugPrintf("getaddrinfo failed for %s:%d - %d\n", + resolvedBindIp != NULL ? resolvedBindIp : "*", + port, + iResult); return false; } @@ -206,7 +213,9 @@ bool WinsockNetLayer::HostGame(int port) s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, NULL, 0, NULL); - app.DebugPrintf("Win64 LAN: Hosting on port %d\n", port); + app.DebugPrintf("Win64 LAN: Hosting on %s:%d\n", + resolvedBindIp != NULL ? resolvedBindIp : "*", + port); return true; } @@ -908,4 +917,4 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param) return 0; } -#endif \ No newline at end of file +#endif diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h index 029dd0a7..fd1280f7 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h @@ -65,7 +65,7 @@ public: static bool Initialize(); static void Shutdown(); - static bool HostGame(int port); + static bool HostGame(int port, const char* bindIp = NULL); static bool JoinGame(const char* ip, int port); static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize); @@ -147,5 +147,8 @@ extern bool g_Win64MultiplayerHost; extern bool g_Win64MultiplayerJoin; extern int g_Win64MultiplayerPort; extern char g_Win64MultiplayerIP[256]; +extern bool g_Win64DedicatedServer; +extern int g_Win64DedicatedServerPort; +extern char g_Win64DedicatedServerBindIP[256]; #endif diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index 3d5eec00..ed6781a3 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -4,7 +4,9 @@ #include "stdafx.h" #include +#include #include +#include #include "GameConfig\Minecraft.spa.h" #include "..\MinecraftServer.h" #include "..\LocalPlayer.h" @@ -34,6 +36,7 @@ #include "Sentient\SentientManager.h" #include "..\..\Minecraft.World\IntCache.h" #include "..\Textures.h" +#include "..\Settings.h" #include "Resource.h" #include "..\..\Minecraft.World\compression.h" #include "..\..\Minecraft.World\OldChunkStorage.h" @@ -95,6 +98,148 @@ wchar_t g_Win64UsernameW[17] = { 0 }; static bool g_isFullscreen = false; static WINDOWPLACEMENT g_wpPrev = { sizeof(g_wpPrev) }; +struct Win64LaunchOptions +{ + int screenMode; + bool serverMode; +}; + +static void CopyWideArgToAnsi(LPCWSTR source, char* dest, size_t destSize) +{ + if (destSize == 0) + return; + + dest[0] = 0; + if (source == NULL) + return; + + WideCharToMultiByte(CP_ACP, 0, source, -1, dest, (int)destSize, NULL, NULL); + dest[destSize - 1] = 0; +} + +static void ApplyScreenMode(int screenMode) +{ + switch (screenMode) + { + case 1: + g_iScreenWidth = 1280; + g_iScreenHeight = 720; + break; + case 2: + g_iScreenWidth = 640; + g_iScreenHeight = 480; + break; + case 3: + g_iScreenWidth = 720; + g_iScreenHeight = 408; + break; + default: + break; + } +} + +static Win64LaunchOptions ParseLaunchOptions() +{ + Win64LaunchOptions options = {}; + options.screenMode = 0; + options.serverMode = false; + + g_Win64MultiplayerJoin = false; + g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT; + g_Win64DedicatedServer = false; + g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT; + g_Win64DedicatedServerBindIP[0] = 0; + + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (argv == NULL) + return options; + + if (argc > 1 && lstrlenW(argv[1]) == 1) + { + if (argv[1][0] >= L'1' && argv[1][0] <= L'3') + options.screenMode = argv[1][0] - L'0'; + } + + for (int i = 1; i < argc; ++i) + { + if (_wcsicmp(argv[i], L"-server") == 0) + { + options.serverMode = true; + break; + } + } + + g_Win64DedicatedServer = options.serverMode; + + for (int i = 1; i < argc; ++i) + { + if (_wcsicmp(argv[i], L"-name") == 0 && (i + 1) < argc) + { + CopyWideArgToAnsi(argv[++i], g_Win64Username, sizeof(g_Win64Username)); + } + else if (_wcsicmp(argv[i], L"-ip") == 0 && (i + 1) < argc) + { + char ipBuf[256]; + CopyWideArgToAnsi(argv[++i], ipBuf, sizeof(ipBuf)); + if (options.serverMode) + { + strncpy_s(g_Win64DedicatedServerBindIP, sizeof(g_Win64DedicatedServerBindIP), ipBuf, _TRUNCATE); + } + else + { + strncpy_s(g_Win64MultiplayerIP, sizeof(g_Win64MultiplayerIP), ipBuf, _TRUNCATE); + g_Win64MultiplayerJoin = true; + } + } + else if (_wcsicmp(argv[i], L"-port") == 0 && (i + 1) < argc) + { + wchar_t* endPtr = NULL; + long port = wcstol(argv[++i], &endPtr, 10); + if (endPtr != argv[i] && *endPtr == 0 && port > 0 && port <= 65535) + { + if (options.serverMode) + g_Win64DedicatedServerPort = (int)port; + else + g_Win64MultiplayerPort = (int)port; + } + } + } + + LocalFree(argv); + return options; +} + +static BOOL WINAPI HeadlessServerCtrlHandler(DWORD ctrlType) +{ + switch (ctrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_SHUTDOWN_EVENT: + app.m_bShutdown = true; + MinecraftServer::HaltServer(); + return TRUE; + default: + return FALSE; + } +} + +static void SetupHeadlessServerConsole() +{ + if (AllocConsole()) + { + FILE* stream = NULL; + freopen_s(&stream, "CONIN$", "r", stdin); + freopen_s(&stream, "CONOUT$", "w", stdout); + freopen_s(&stream, "CONOUT$", "w", stderr); + SetConsoleTitleA("Minecraft Server"); + } + + SetConsoleCtrlHandler(HeadlessServerCtrlHandler, TRUE); +} + void DefineActions(void) { // The app needs to define the actions required, and the possible mappings for these @@ -722,6 +867,225 @@ void CleanupDevice() if( g_pd3dDevice ) g_pd3dDevice->Release(); } +static Minecraft* InitialiseMinecraftRuntime() +{ + app.loadMediaArchive(); + + RenderManager.Initialise(g_pd3dDevice, g_pSwapChain); + + app.loadStringTable(); + ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_iScreenWidth, g_iScreenHeight); + + InputManager.Initialise(1, 3, MINECRAFT_ACTION_MAX, ACTION_MAX_MENU); + KMInput.Init(g_hWnd); + DefineActions(); + InputManager.SetJoypadMapVal(0, 0); + InputManager.SetKeyRepeatRate(0.3f, 0.2f); + + ProfileManager.Initialise(TITLEID_MINECRAFT, + app.m_dwOfferID, + PROFILE_VERSION_10, + NUM_PROFILE_VALUES, + NUM_PROFILE_SETTINGS, + dwProfileSettingsA, + app.GAME_DEFINED_PROFILE_DATA_BYTES * XUSER_MAX_COUNT, + &app.uiGameDefinedDataChangedBitmask + ); + ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback, (LPVOID)&app); + + g_NetworkManager.Initialise(); + + for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++) + { + IQNet::m_player[i].m_smallId = (BYTE)i; + IQNet::m_player[i].m_isRemote = false; + IQNet::m_player[i].m_isHostPlayer = (i == 0); + swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i); + } + wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW); + + WinsockNetLayer::Initialize(); + + ProfileManager.SetDebugFullOverride(true); + + Tesselator::CreateNewThreadStorage(1024 * 1024); + AABB::CreateNewThreadStorage(); + Vec3::CreateNewThreadStorage(); + IntCache::CreateNewThreadStorage(); + Compression::CreateNewThreadStorage(); + OldChunkStorage::CreateNewThreadStorage(); + Level::enableLightingCache(); + Tile::CreateNewThreadStorage(); + + Minecraft::main(); + Minecraft* pMinecraft = Minecraft::GetInstance(); + if (pMinecraft == NULL) + return NULL; + + app.InitGameSettings(); + app.InitialiseTips(); + + pMinecraft->options->set(Options::Option::MUSIC, 1.0f); + pMinecraft->options->set(Options::Option::SOUND, 1.0f); + + return pMinecraft; +} + +static int HeadlessServerConsoleThreadProc(void* lpParameter) +{ + UNREFERENCED_PARAMETER(lpParameter); + + std::string line; + while (!app.m_bShutdown) + { + if (!std::getline(std::cin, line)) + { + if (std::cin.eof()) + { + break; + } + + std::cin.clear(); + Sleep(50); + continue; + } + + wstring command = trimString(convStringToWstring(line)); + if (command.empty()) + continue; + + MinecraftServer* server = MinecraftServer::getInstance(); + if (server != NULL) + { + server->handleConsoleInput(command, server); + } + } + + return 0; +} + +static int RunHeadlessServer() +{ + SetupHeadlessServerConsole(); + + Settings serverSettings(new File(L"server.properties")); + wstring configuredBindIp = serverSettings.getString(L"server-ip", L""); + + const char* bindIp = "*"; + if (g_Win64DedicatedServerBindIP[0] != 0) + { + bindIp = g_Win64DedicatedServerBindIP; + } + else if (!configuredBindIp.empty()) + { + bindIp = wstringtochararray(configuredBindIp); + } + + const int port = g_Win64DedicatedServerPort > 0 ? g_Win64DedicatedServerPort : serverSettings.getInt(L"server-port", WIN64_NET_DEFAULT_PORT); + + printf("Starting headless server on %s:%d\n", bindIp, port); + fflush(stdout); + + Minecraft* pMinecraft = InitialiseMinecraftRuntime(); + if (pMinecraft == NULL) + { + fprintf(stderr, "Failed to initialise the Minecraft runtime.\n"); + return 1; + } + + app.SetGameHostOption(eGameHostOption_Difficulty, serverSettings.getInt(L"difficulty", 1)); + app.SetGameHostOption(eGameHostOption_Gamertags, 1); + app.SetGameHostOption(eGameHostOption_GameType, serverSettings.getInt(L"gamemode", 0)); + app.SetGameHostOption(eGameHostOption_LevelType, 0); + app.SetGameHostOption(eGameHostOption_Structures, serverSettings.getBoolean(L"generate-structures", true) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_BonusChest, serverSettings.getBoolean(L"bonus-chest", false) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_PvP, serverSettings.getBoolean(L"pvp", true) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_TrustPlayers, serverSettings.getBoolean(L"trust-players", true) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_FireSpreads, serverSettings.getBoolean(L"fire-spreads", true) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_TNT, serverSettings.getBoolean(L"tnt", true) ? 1 : 0); + app.SetGameHostOption(eGameHostOption_HostCanFly, 1); + app.SetGameHostOption(eGameHostOption_HostCanChangeHunger, 1); + app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, 1); + app.SetGameHostOption(eGameHostOption_MobGriefing, 1); + app.SetGameHostOption(eGameHostOption_KeepInventory, 0); + app.SetGameHostOption(eGameHostOption_DoMobSpawning, 1); + app.SetGameHostOption(eGameHostOption_DoMobLoot, 1); + app.SetGameHostOption(eGameHostOption_DoTileDrops, 1); + app.SetGameHostOption(eGameHostOption_NaturalRegeneration, 1); + app.SetGameHostOption(eGameHostOption_DoDaylightCycle, 1); + + MinecraftServer::resetFlags(); + g_NetworkManager.HostGame(0, false, true, MINECRAFT_NET_MAX_PLAYERS, 0); + + if (!WinsockNetLayer::IsActive()) + { + fprintf(stderr, "Failed to bind the server socket on %s:%d.\n", bindIp, port); + return 1; + } + + g_NetworkManager.FakeLocalPlayerJoined(); + + NetworkGameInitData* param = new NetworkGameInitData(); + param->seed = 0; + param->settings = app.GetGameHostOption(eGameHostOption_All); + + g_NetworkManager.ServerStoppedCreate(true); + g_NetworkManager.ServerReadyCreate(true); + + C4JThread* thread = new C4JThread(&CGameNetworkManager::ServerThreadProc, param, "Server", 256 * 1024); + thread->SetProcessor(CPU_CORE_SERVER); + thread->Run(); + + g_NetworkManager.ServerReadyWait(); + g_NetworkManager.ServerReadyDestroy(); + + if (MinecraftServer::serverHalted()) + { + fprintf(stderr, "The server halted during startup.\n"); + g_NetworkManager.LeaveGame(false); + return 1; + } + + app.SetGameStarted(true); + g_NetworkManager.DoWork(); + + printf("Server ready on %s:%d\n", bindIp, port); + printf("Type 'help' for server commands.\n"); + fflush(stdout); + + C4JThread* consoleThread = new C4JThread(&HeadlessServerConsoleThreadProc, NULL, "Server console", 128 * 1024); + consoleThread->Run(); + + MSG msg = { 0 }; + while (WM_QUIT != msg.message && !app.m_bShutdown && !MinecraftServer::serverHalted()) + { + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } + + app.UpdateTime(); + ProfileManager.Tick(); + StorageManager.Tick(); + RenderManager.Tick(); + ui.tick(); + g_NetworkManager.DoWork(); + app.HandleXuiActions(); + + Sleep(10); + } + + printf("Stopping server...\n"); + fflush(stdout); + + app.m_bShutdown = true; + MinecraftServer::HaltServer(); + g_NetworkManager.LeaveGame(false); + return 0; +} + int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, @@ -742,70 +1106,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, SetProcessDPIAware(); g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN); g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN); - - if(lpCmdLine) - { - if(lpCmdLine[0] == '1') - { - g_iScreenWidth = 1280; - g_iScreenHeight = 720; - } - else if(lpCmdLine[0] == '2') - { - g_iScreenWidth = 640; - g_iScreenHeight = 480; - } - else if(lpCmdLine[0] == '3') - { - // Vita - g_iScreenWidth = 720; - g_iScreenHeight = 408; - - // Vita native - //g_iScreenWidth = 960; - //g_iScreenHeight = 544; - } - - char cmdLineA[1024]; - strncpy_s(cmdLineA, sizeof(cmdLineA), lpCmdLine, _TRUNCATE); - - char *nameArg = strstr(cmdLineA, "-name "); - if (nameArg) - { - nameArg += 6; - while (*nameArg == ' ') nameArg++; - char nameBuf[17]; - int n = 0; - while (nameArg[n] && nameArg[n] != ' ' && n < 16) { nameBuf[n] = nameArg[n]; n++; } - nameBuf[n] = 0; - strncpy_s(g_Win64Username, 17, nameBuf, _TRUNCATE); - } - - char *ipArg = strstr(cmdLineA, "-ip "); - if (ipArg) - { - ipArg += 4; - while (*ipArg == ' ') ipArg++; - char ipBuf[256]; - int n = 0; - while (ipArg[n] && ipArg[n] != ' ' && n < 255) { ipBuf[n] = ipArg[n]; n++; } - ipBuf[n] = 0; - strncpy_s(g_Win64MultiplayerIP, 256, ipBuf, _TRUNCATE); - g_Win64MultiplayerJoin = true; - } - - char *portArg = strstr(cmdLineA, "-port "); - if (portArg) - { - portArg += 6; - while (*portArg == ' ') portArg++; - char portBuf[16]; - int n = 0; - while (portArg[n] && portArg[n] != ' ' && n < 15) { portBuf[n] = portArg[n]; n++; } - portBuf[n] = 0; - g_Win64MultiplayerPort = atoi(portBuf); - } - } + Win64LaunchOptions launchOptions = ParseLaunchOptions(); + ApplyScreenMode(launchOptions.screenMode); if (g_Win64Username[0] == 0) { @@ -821,7 +1123,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, MyRegisterClass(hInstance); // Perform application initialization: - if (!InitInstance (hInstance, nCmdShow)) + if (!InitInstance (hInstance, launchOptions.serverMode ? SW_HIDE : nCmdShow)) { return FALSE; } @@ -834,6 +1136,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, return 0; } + if (launchOptions.serverMode) + { + int serverResult = RunHeadlessServer(); + CleanupDevice(); + return serverResult; + } + #if 0 // Main message loop MSG msg = {0}; @@ -886,203 +1195,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, } #endif - app.loadMediaArchive(); - - RenderManager.Initialise(g_pd3dDevice, g_pSwapChain); - - app.loadStringTable(); - ui.init(g_pd3dDevice,g_pImmediateContext,g_pRenderTargetView,g_pDepthStencilView,g_iScreenWidth,g_iScreenHeight); - - //////////////// - // Initialise // - //////////////// - - // Set the number of possible joypad layouts that the user can switch between, and the number of actions - InputManager.Initialise(1,3,MINECRAFT_ACTION_MAX, ACTION_MAX_MENU); - - // Initialize keyboard/mouse input - KMInput.Init(g_hWnd); - - // Set the default joypad action mappings for Minecraft - DefineActions(); - InputManager.SetJoypadMapVal(0,0); - InputManager.SetKeyRepeatRate(0.3f,0.2f); - - // Initialise the profile manager with the game Title ID, Offer ID, a profile version number, and the number of profile values and settings - ProfileManager.Initialise(TITLEID_MINECRAFT, - app.m_dwOfferID, - PROFILE_VERSION_10, - NUM_PROFILE_VALUES, - NUM_PROFILE_SETTINGS, - dwProfileSettingsA, - app.GAME_DEFINED_PROFILE_DATA_BYTES*XUSER_MAX_COUNT, - &app.uiGameDefinedDataChangedBitmask - ); -#if 0 - // register the awards - ProfileManager.RegisterAward(eAward_TakingInventory, ACHIEVEMENT_01, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_GettingWood, ACHIEVEMENT_02, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_Benchmarking, ACHIEVEMENT_03, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_TimeToMine, ACHIEVEMENT_04, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_HotTopic, ACHIEVEMENT_05, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_AquireHardware, ACHIEVEMENT_06, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_TimeToFarm, ACHIEVEMENT_07, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_BakeBread, ACHIEVEMENT_08, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_TheLie, ACHIEVEMENT_09, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_GettingAnUpgrade, ACHIEVEMENT_10, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_DeliciousFish, ACHIEVEMENT_11, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_OnARail, ACHIEVEMENT_12, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_TimeToStrike, ACHIEVEMENT_13, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_MonsterHunter, ACHIEVEMENT_14, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_CowTipper, ACHIEVEMENT_15, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_WhenPigsFly, ACHIEVEMENT_16, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_LeaderOfThePack, ACHIEVEMENT_17, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_MOARTools, ACHIEVEMENT_18, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_DispenseWithThis, ACHIEVEMENT_19, eAwardType_Achievement); - ProfileManager.RegisterAward(eAward_InToTheNether, ACHIEVEMENT_20, eAwardType_Achievement); - - ProfileManager.RegisterAward(eAward_mine100Blocks, GAMER_PICTURE_GAMERPIC1, eAwardType_GamerPic,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_GAMERPIC1,IDS_CONFIRM_OK); - ProfileManager.RegisterAward(eAward_kill10Creepers, GAMER_PICTURE_GAMERPIC2, eAwardType_GamerPic,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_GAMERPIC2,IDS_CONFIRM_OK); - - ProfileManager.RegisterAward(eAward_eatPorkChop, AVATARASSETAWARD_PORKCHOP_TSHIRT, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR1,IDS_CONFIRM_OK); - ProfileManager.RegisterAward(eAward_play100Days, AVATARASSETAWARD_WATCH, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR2,IDS_CONFIRM_OK); - ProfileManager.RegisterAward(eAward_arrowKillCreeper, AVATARASSETAWARD_CAP, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR3,IDS_CONFIRM_OK); - - ProfileManager.RegisterAward(eAward_socialPost, 0, eAwardType_Theme,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_THEME,IDS_CONFIRM_OK,THEME_NAME,THEME_FILESIZE); - - // Rich Presence init - number of presences, number of contexts - ProfileManager.RichPresenceInit(4,1); - ProfileManager.RegisterRichPresenceContext(CONTEXT_GAME_STATE); - - // initialise the storage manager with a default save display name, a Minimum save size, and a callback for displaying the saving message - StorageManager.Init(app.GetString(IDS_DEFAULT_SAVENAME),"savegame.dat",FIFTY_ONE_MB,&CConsoleMinecraftApp::DisplaySavingMessage,(LPVOID)&app); - // Set up the global title storage path - StorageManager.StoreTMSPathName(); - - // set a function to be called when there's a sign in change, so we can exit a level if the primary player signs out - ProfileManager.SetSignInChangeCallback(&CConsoleMinecraftApp::SignInChangeCallback,(LPVOID)&app); - - // set a function to be called when the ethernet is disconnected, so we can back out if required - ProfileManager.SetNotificationsCallback(&CConsoleMinecraftApp::NotificationsCallback,(LPVOID)&app); - -#endif - // Set a callback for the default player options to be set - when there is no profile data for the player - ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback,(LPVOID)&app); -#if 0 - // Set a callback to deal with old profile versions needing updated to new versions - ProfileManager.SetOldProfileVersionCallback(&CConsoleMinecraftApp::OldProfileVersionCallback,(LPVOID)&app); - - // Set a callback for when there is a read error on profile data - ProfileManager.SetProfileReadErrorCallback(&CConsoleMinecraftApp::ProfileReadErrorCallback,(LPVOID)&app); - -#endif - // QNet needs to be setup after profile manager, as we do not want its Notify listener to handle - // XN_SYS_SIGNINCHANGED notifications. This does mean that we need to have a callback in the - // ProfileManager for XN_LIVE_INVITE_ACCEPTED for QNet. - g_NetworkManager.Initialise(); - - for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++) + Minecraft *pMinecraft = InitialiseMinecraftRuntime(); + if (pMinecraft == NULL) { - IQNet::m_player[i].m_smallId = (BYTE)i; - IQNet::m_player[i].m_isRemote = false; - IQNet::m_player[i].m_isHostPlayer = (i == 0); - swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i); - } - extern wchar_t g_Win64UsernameW[17]; - wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW); - - WinsockNetLayer::Initialize(); - - // 4J-PB moved further down - //app.InitGameSettings(); - - // debug switch to trial version - ProfileManager.SetDebugFullOverride(true); - -#if 0 - //ProfileManager.AddDLC(2); - StorageManager.SetDLCPackageRoot("DLCDrive"); - StorageManager.RegisterMarketplaceCountsCallback(&CConsoleMinecraftApp::MarketplaceCountsCallback,(LPVOID)&app); - // Kinect ! - - if(XNuiGetHardwareStatus()!=0) - { - // If the Kinect Sensor is not physically connected, this function returns 0. - NuiInitialize(NUI_INITIALIZE_FLAG_USES_HIGH_QUALITY_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH | - NUI_INITIALIZE_FLAG_EXTRAPOLATE_FLOOR_PLANE | NUI_INITIALIZE_FLAG_USES_FITNESS | NUI_INITIALIZE_FLAG_NUI_GUIDE_DISABLED | NUI_INITIALIZE_FLAG_SUPPRESS_AUTOMATIC_UI,NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD ); + CleanupDevice(); + return 1; } - // Sentient ! - hr = TelemetryManager->Init(); - -#endif - // Initialise TLS for tesselator, for this main thread - Tesselator::CreateNewThreadStorage(1024*1024); - // Initialise TLS for AABB and Vec3 pools, for this main thread - AABB::CreateNewThreadStorage(); - Vec3::CreateNewThreadStorage(); - IntCache::CreateNewThreadStorage(); - Compression::CreateNewThreadStorage(); - OldChunkStorage::CreateNewThreadStorage(); - Level::enableLightingCache(); - Tile::CreateNewThreadStorage(); - - Minecraft::main(); - Minecraft *pMinecraft=Minecraft::GetInstance(); - - app.InitGameSettings(); - -#if 0 - //bool bDisplayPauseMenu=false; - - // set the default gamma level - float fVal=50.0f*327.68f; - RenderManager.UpdateGamma((unsigned short)fVal); - - // load any skins - //app.AddSkinsToMemoryTextureFiles(); - - // set the achievement text for a trial achievement, now we have the string table loaded - ProfileManager.SetTrialTextStringTable(app.GetStringTable(),IDS_CONFIRM_OK, IDS_CONFIRM_CANCEL); - ProfileManager.SetTrialAwardText(eAwardType_Achievement,IDS_UNLOCK_TITLE,IDS_UNLOCK_ACHIEVEMENT_TEXT); - ProfileManager.SetTrialAwardText(eAwardType_GamerPic,IDS_UNLOCK_TITLE,IDS_UNLOCK_GAMERPIC_TEXT); - ProfileManager.SetTrialAwardText(eAwardType_AvatarItem,IDS_UNLOCK_TITLE,IDS_UNLOCK_AVATAR_TEXT); - ProfileManager.SetTrialAwardText(eAwardType_Theme,IDS_UNLOCK_TITLE,IDS_UNLOCK_THEME_TEXT); - ProfileManager.SetUpsellCallback(&app.UpsellReturnedCallback,&app); - - // Set up a debug character press sequence -#ifndef _FINAL_BUILD - app.SetDebugSequence("LRLRYYY"); -#endif - - // Initialise the social networking manager. - CSocialManager::Instance()->Initialise(); - - // Update the base scene quick selects now that the minecraft class exists - //CXuiSceneBase::UpdateScreenSettings(0); -#endif - app.InitialiseTips(); -#if 0 - - DWORD initData=0; - -#ifndef _FINAL_BUILD -#ifndef _DEBUG -#pragma message(__LOC__"Need to define the _FINAL_BUILD before submission") -#endif -#endif - - // Set the default sound levels - pMinecraft->options->set(Options::Option::MUSIC,1.0f); - pMinecraft->options->set(Options::Option::SOUND,1.0f); - - app.NavigateToScene(XUSER_INDEX_ANY,eUIScene_Intro,&initData); -#endif - - // Set the default sound levels - pMinecraft->options->set(Options::Option::MUSIC,1.0f); - pMinecraft->options->set(Options::Option::SOUND,1.0f); - //app.TemporaryCreateGameStart(); //Sleep(10000); @@ -1637,4 +1756,4 @@ void MemPixStuff() PIXAddNamedCounter(((float)allSectsTotal)/(4096.0f),"MemSect total pages"); } -#endif \ No newline at end of file +#endif diff --git a/Minecraft.World/Minecraft.World.vcxproj b/Minecraft.World/Minecraft.World.vcxproj index 58880529..7ab7c4ce 100644 --- a/Minecraft.World/Minecraft.World.vcxproj +++ b/Minecraft.World/Minecraft.World.vcxproj @@ -1257,6 +1257,7 @@ false true Default + /FS %(AdditionalOptions) true @@ -1355,7 +1356,7 @@ true true true - /Ob3 + /FS /Ob3 %(AdditionalOptions) true @@ -4957,4 +4958,4 @@ - \ No newline at end of file + diff --git a/README.md b/README.md index e6583e76..a84ee508 100644 --- a/README.md +++ b/README.md @@ -41,14 +41,22 @@ This feature is based on [LCEMP](https://github.com/LCEMP/LCEMP/) | Argument | Description | |--------------------|----------------------------------------------------------------------------------------------------------------| | `-name ` | Sets your in-game username | -| `-ip
` | Manually connect to an IP if LAN advertising does not work or if the server cannot be discovered automatically | -| `-port ` | Override the default port if it was changed in the source | +| `-server` | Launches a headless server instead of the client | +| `-ip
` | Client mode: manually connect to an IP. Server mode: override the bind IP from `server.properties` | +| `-port ` | Client mode: override the join port. Server mode: override the listen port from `server.properties` | Example: ``` Minecraft.Client.exe -name Steve -ip 192.168.0.25 -port 25565 ``` +Headless server example: +``` +Minecraft.Client.exe -server -ip 0.0.0.0 -port 25565 +``` + +The headless server also reads and writes `server.properties` in the working directory. If `-ip` / `-port` are omitted in `-server` mode, it falls back to `server-ip` / `server-port` from that file. Dedicated-server host options such as `trust-players`, `pvp`, `fire-spreads`, `tnt`, `difficulty`, `gamemode`, `spawn-animals`, and `spawn-npcs` are persisted there as well. + ## Controls (Keyboard & Mouse) - **Movement**: `W` `A` `S` `D` -- cgit v1.2.3