diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index 2694f789..62cf1e8c 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -14,6 +14,8 @@ #endif #include "..\..\..\Minecraft.World\DisconnectPacket.h" #include "..\..\Minecraft.h" +#include +#pragma comment(lib, "Dnsapi.lib") #include "..\4JLibs\inc\4J_Profile.h" #include @@ -310,6 +312,44 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) return true; } +// Resolve a Minecraft SRV record (_minecraft._tcp.) to get the actual host and port. +// Returns true if an SRV record was found and outHost/outPort were updated. +// Returns false (no changes) for numeric IPs, missing records, or DNS errors. +static bool ResolveSRV(const char* hostname, char* outHost, size_t outHostSize, int* outPort) +{ + // Skip numeric IPs (starts with digit, contains no letters) + if (hostname[0] >= '0' && hostname[0] <= '9') + { + bool hasLetter = false; + for (const char* p = hostname; *p; ++p) + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')) { hasLetter = true; break; } + if (!hasLetter) return false; + } + + char srvName[300]; + sprintf_s(srvName, "_minecraft._tcp.%s", hostname); + + DNS_RECORD* records = nullptr; + DNS_STATUS status = DnsQuery_A(srvName, DNS_TYPE_SRV, DNS_QUERY_STANDARD, nullptr, &records, nullptr); + if (status != 0 || records == nullptr) + return false; + + // Find the first SRV record + for (DNS_RECORD* r = records; r != nullptr; r = r->pNext) + { + if (r->wType == DNS_TYPE_SRV) + { + strncpy_s(outHost, outHostSize, r->Data.SRV.pNameTarget, _TRUNCATE); + *outPort = r->Data.SRV.wPort; + DnsRecordListFree(records, DnsFreeRecordList); + return true; + } + } + + DnsRecordListFree(records, DnsFreeRecordList); + return false; +} + bool WinsockNetLayer::JoinGame(const char* ip, int port) { if (!s_initialized && !Initialize()) return false; @@ -336,6 +376,13 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) s_clientRecvThread = nullptr; } + // Try SRV record resolution for hostnames + char resolvedHost[256]; + int resolvedPort = port; + strncpy_s(resolvedHost, sizeof(resolvedHost), ip, _TRUNCATE); + if (ResolveSRV(ip, resolvedHost, sizeof(resolvedHost), &resolvedPort)) + app.DebugPrintf("SRV resolved %s -> %s:%d\n", ip, resolvedHost, resolvedPort); + struct addrinfo hints = {}; struct addrinfo* result = nullptr; @@ -344,9 +391,9 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) hints.ai_protocol = IPPROTO_TCP; char portStr[16]; - sprintf_s(portStr, "%d", port); + sprintf_s(portStr, "%d", resolvedPort); - int iResult = getaddrinfo(ip, portStr, &hints, &result); + int iResult = getaddrinfo(resolvedHost, portStr, &hints, &result); if (iResult != 0) { app.DebugPrintf("getaddrinfo failed for %s:%d - %d\n", ip, port, iResult); @@ -539,17 +586,24 @@ bool WinsockNetLayer::BeginJoinGame(const char* ip, int port) DWORD WINAPI WinsockNetLayer::JoinThreadProc(LPVOID param) { + // Try SRV record resolution for hostnames + char resolvedHost[256]; + int resolvedPort = s_joinPort; + strncpy_s(resolvedHost, sizeof(resolvedHost), s_joinIP, _TRUNCATE); + if (ResolveSRV(s_joinIP, resolvedHost, sizeof(resolvedHost), &resolvedPort)) + app.DebugPrintf("SRV resolved %s -> %s:%d\n", s_joinIP, resolvedHost, resolvedPort); + struct addrinfo hints = {}, *result = nullptr; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; char portStr[16]; - sprintf_s(portStr, "%d", s_joinPort); + sprintf_s(portStr, "%d", resolvedPort); - if (getaddrinfo(s_joinIP, portStr, &hints, &result) != 0) + if (getaddrinfo(resolvedHost, portStr, &hints, &result) != 0) { - app.DebugPrintf("getaddrinfo failed for %s:%d\n", s_joinIP, s_joinPort); + app.DebugPrintf("getaddrinfo failed for %s:%d\n", resolvedHost, resolvedPort); s_joinState = eJoinState_Failed; return 0; } diff --git a/README.md b/README.md index 82480868..417acbb6 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ This project is based on source code of Minecraft Legacy Console Edition v1.6.05 ## Latest: +SRV record support and async join refactor: +- Added DNS SRV record resolution (`_minecraft._tcp.`), matching Java Edition behavior. Players can connect using just a domain name (e.g. `play.example.com`) and the client will automatically look up the correct server address and port from DNS +- Refactored the async server joining system: replaced boolean flags with a clean `eJoinState` enum state machine, moved connection progress handling into a dedicated `UIScene_ConnectingProgress` class with attempt counter and cancel support, and added a `FinalizeJoin()` separation so the recv thread only starts after the UI confirms success + Piston fix for dedicated servers: - Fixed a bug where pistons would permanently break server-wide on dedicated servers when a redstone clock ran long enough. The piston update lock (`ignoreUpdate`) was set at the start of `triggerEvent` but never cleared on three early-return paths, permanently blocking all piston neighbor updates for the rest of the session. A fast clock would eventually hit one of these paths (e.g. signal state changing between event queuing and processing), locking out every piston in the world