From d92a7f79017132c3a6b3bc879a344d47015f2244 Mon Sep 17 00:00:00 2001
From: SuperKod <79586895+terenP@users.noreply.github.com>
Date: Wed, 18 Feb 2026 22:04:29 +0100
Subject: [PATCH 1/3] Add files via upload
---
CSharp/MineStat/MineStat.cs | 206 ++++++++++++++++++++++++++++++++++--
1 file changed, 200 insertions(+), 6 deletions(-)
diff --git a/CSharp/MineStat/MineStat.cs b/CSharp/MineStat/MineStat.cs
index 2c889b5..d2ce59c 100644
--- a/CSharp/MineStat/MineStat.cs
+++ b/CSharp/MineStat/MineStat.cs
@@ -30,6 +30,7 @@
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
+using System.Net;
namespace MineStatLib
{
@@ -182,6 +183,9 @@ public MineStat(string address, ushort port, int timeout = DefaultTimeout, SlpPr
case SlpProtocol.Bedrock_Raknet:
ConnectionStatus = RequestWithRaknetProtocol();
break;
+ case SlpProtocol.Ut3Gs4Query:
+ ConnectionStatus = RequestWithUt3Gs4QueryProtocol();
+ break;
case SlpProtocol.Automatic:
break;
default:
@@ -201,15 +205,18 @@ public MineStat(string address, ushort port, int timeout = DefaultTimeout, SlpPr
// For more information, see https://github.com/FragLand/minestat/issues/70
//
// 1.: Raknet (Bedrock)
- // 2.: Legacy (1.4, 1.5)
- // 3.: Beta (b1.8-rel1.3)
- // 4.: Extended Legacy (1.6)
- // 5.: JSON (1.7+)
+ // 2.: UT3/GS4 Query
+ // 3.: Legacy (1.4, 1.5)
+ // 4.: Beta (b1.8-rel1.3)
+ // 5.: Extended Legacy (1.6)
+ // 6.: JSON (1.7+)
ConnectionStatus = RequestWithRaknetProtocol();
if (ConnectionStatus == ConnStatus.Connfail || ConnectionStatus == ConnStatus.Success)
return;
+ ConnectionStatus = RequestWithUt3Gs4QueryProtocol();
+
ConnectionStatus = RequestWrapper(RequestWithLegacyProtocol);
ConnStatus result;
@@ -780,8 +787,183 @@ private ConnStatus ParseBetaProtocol(byte[] rawPayload)
Version = "<= 1.3";
return ConnStatus.Success;
+ }
+
+ ///
+ /// Requests the server data with the UT3/GS4 Query protocol.
+ ///
+ /// ConnStatus - See for possible values
+ ///
+ public ConnStatus RequestWithUt3Gs4QueryProtocol()
+ {
+ var sock = new UdpClient();
+ sock.Client.ReceiveTimeout = Timeout * 1000;
+ sock.Client.SendTimeout = Timeout * 1000;
+
+ var stopWatch = new Stopwatch();
+ stopWatch.Start();
+
+ try
+ {
+ sock.Connect(Address, Port);
+ }
+ catch (SocketException)
+ {
+ return ConnStatus.Connfail;
+ }
+
+ stopWatch.Stop();
+ Latency = stopWatch.ElapsedMilliseconds;
+
+
+ byte[] statResponse;
+ try
+ {
+ // Get Challenge Token
+ var challengeRequest = new byte[] { 0xFE, 0xFD, 0x09, 0x00, 0x00, 0x00, 0x00};
+ sock.Send(challengeRequest, challengeRequest.Length);
+
+ IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, Port);
+ byte[] challengeResponse = sock.Receive(ref remoteEP);
+
+ if (challengeResponse.Length < 11) return ConnStatus.Unknown;
+
+ //(Big-Endian)
+ byte[] tokenBytes = new byte[4];
+ Array.Copy(challengeResponse, 7, tokenBytes, 0, 4);
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(tokenBytes);
+ int token = BitConverter.ToInt32(tokenBytes, 0);
+
+ byte[] statRequest = new byte[11];
+ statRequest[0] = 0xFE; statRequest[1] = 0xFD; statRequest[2] = 0x00;
+
+ // Token jako Big-Endian
+ tokenBytes = BitConverter.GetBytes(token);
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(tokenBytes);
+ Array.Copy(tokenBytes, 0, statRequest, 7, 4);
+
+ sock.Send(statRequest, statRequest.Length);
+
+ // --- 3. ODBIERZ STAT RESPONSE ---
+ statResponse = sock.Receive(ref remoteEP);
+
+ }
+ catch
+ {
+ return ConnStatus.Unknown;
+ }
+ finally
+ {
+ sock.Close();
+ }
+
+ return ParseUt3Gs4Protocol(statResponse);
}
-
+
+ ///
+ /// Internal helper method for parsing the UT3/GS4 Query protocol payload.
+ ///
+ /// The raw payload, without packet length and -id
+ /// ConnStatus - See for possible values
+ private ConnStatus ParseUT3GS4Protocol(byte[] rawPayload)
+ {
+ try
+ {
+ int pos = 5;
+
+ var data = new Dictionary();
+
+ while (pos < rawPayload.Length && rawPayload[pos] != 0)
+ {
+ // Read Key
+ int keyStart = pos;
+ while (pos < rawPayload.Length && rawPayload[pos] != 0)
+ pos++;
+
+ if (pos >= rawPayload.Length) break;
+
+ string key = Encoding.UTF8.GetString(rawPayload, keyStart, pos - keyStart);
+ pos++; // We omit null
+
+ if (pos >= rawPayload.Length) break;
+
+ // Read value
+ int valStart = pos;
+ while (pos < rawPayload.Length && rawPayload[pos] != 0)
+ pos++;
+
+ if (pos >= rawPayload.Length) break;
+
+ string value = Encoding.UTF8.GetString(rawPayload, valStart, pos - valStart);
+ pos++; // We omit null
+
+ data[key] = value;
+ }
+
+ // Check if we have the minimum data
+ if (!data.ContainsKey("hostname") && !data.ContainsKey("numplayers"))
+ {
+ return ConnStatus.Unknown;
+ }
+
+
+ // MOTD
+ if (data.TryGetValue("hostname", out var hostname))
+ {
+ Motd = hostname;
+ Stripped_Motd = strip_motd_formatting(hostname);
+ }
+ else
+ {
+ Motd = "Unknown";
+ Stripped_Motd = "Unknown";
+ }
+
+ // Version
+ if (data.TryGetValue("version", out var version))
+ {
+ Version = version;
+ }
+ else
+ {
+ Version = "Unknown";
+ }
+
+ // Players count
+ if (data.TryGetValue("numplayers", out var num) && int.TryParse(num, out int current))
+ {
+ CurrentPlayersInt = current;
+ }
+ else
+ {
+ CurrentPlayersInt = 0;
+ }
+
+ if (data.TryGetValue("maxplayers", out var max) && int.TryParse(max, out int maximum))
+ {
+ MaximumPlayersInt = maximum;
+ }
+ else
+ {
+ MaximumPlayersInt = 0;
+ }
+
+ // --- SET STATUS ---
+ ServerUp = true;
+ Protocol = SlpProtocol.Ut3Gs4Query;
+
+ return ConnStatus.Success;
+ }
+ catch (Exception)
+ {
+ return ConnStatus.Unknown;
+ }
+
+
+ }
+
///
/// Internal helper method for connecting to a remote host and setting timeouts.
///
@@ -1159,7 +1341,19 @@ public enum SlpProtocol
/// It contains very few details, no server version info, only MOTD, max- and online player counts.
/// Available since Minecraft Beta 1.8
///
- Beta,
+ Beta,
+
+ ///
+ /// Requests server data using the UT3/GS4 Query protocol (GameSpy 4).
+ /// UDP-based protocol used by Minecraft Beta 1.8 through 1.6.x.
+ ///
+ /// /// Protocol flow:
+ /// 1. Send challenge request [FE FD 09 00 00 00 00]
+ /// 2. Receive token (4-byte Big-Endian integer)
+ /// 3. Send stat request with token [FE FD 00 session token]
+ /// 4. Parse null-terminated key-value pairs from response
+ ///
+ Ut3Gs4Query,
///
/// Not a protocol. Used for setting the default, automatic protocol detection.
From 9ef230db77251d23561439b4df1d07096af91628 Mon Sep 17 00:00:00 2001
From: SuperKod <79586895+terenP@users.noreply.github.com>
Date: Thu, 19 Feb 2026 12:31:35 +0100
Subject: [PATCH 2/3] Add files via upload
---
CSharp/MineStat/MineStat.cs | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/CSharp/MineStat/MineStat.cs b/CSharp/MineStat/MineStat.cs
index d2ce59c..2022477 100644
--- a/CSharp/MineStat/MineStat.cs
+++ b/CSharp/MineStat/MineStat.cs
@@ -815,7 +815,6 @@ public ConnStatus RequestWithUt3Gs4QueryProtocol()
stopWatch.Stop();
Latency = stopWatch.ElapsedMilliseconds;
-
byte[] statResponse;
try
{
@@ -848,7 +847,6 @@ public ConnStatus RequestWithUt3Gs4QueryProtocol()
// --- 3. ODBIERZ STAT RESPONSE ---
statResponse = sock.Receive(ref remoteEP);
-
}
catch
{
@@ -859,7 +857,7 @@ public ConnStatus RequestWithUt3Gs4QueryProtocol()
sock.Close();
}
- return ParseUt3Gs4Protocol(statResponse);
+ return ParseUT3GS4Protocol(statResponse);
}
///
@@ -908,7 +906,6 @@ private ConnStatus ParseUT3GS4Protocol(byte[] rawPayload)
return ConnStatus.Unknown;
}
-
// MOTD
if (data.TryGetValue("hostname", out var hostname))
{
@@ -960,8 +957,6 @@ private ConnStatus ParseUT3GS4Protocol(byte[] rawPayload)
{
return ConnStatus.Unknown;
}
-
-
}
///
From f205135ad62813d4e41ed83e35a6147621eb864f Mon Sep 17 00:00:00 2001
From: SuperKod <79586895+terenP@users.noreply.github.com>
Date: Thu, 19 Feb 2026 12:39:30 +0100
Subject: [PATCH 3/3] Add files via upload
---
CSharp/MineStat/MineStat.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/CSharp/MineStat/MineStat.cs b/CSharp/MineStat/MineStat.cs
index 2022477..bb226bb 100644
--- a/CSharp/MineStat/MineStat.cs
+++ b/CSharp/MineStat/MineStat.cs
@@ -837,7 +837,7 @@ public ConnStatus RequestWithUt3Gs4QueryProtocol()
byte[] statRequest = new byte[11];
statRequest[0] = 0xFE; statRequest[1] = 0xFD; statRequest[2] = 0x00;
- // Token jako Big-Endian
+ // Token Big-Endian
tokenBytes = BitConverter.GetBytes(token);
if (BitConverter.IsLittleEndian)
Array.Reverse(tokenBytes);
@@ -845,10 +845,10 @@ public ConnStatus RequestWithUt3Gs4QueryProtocol()
sock.Send(statRequest, statRequest.Length);
- // --- 3. ODBIERZ STAT RESPONSE ---
+ // --- 3. RECEIVE STAT RESPONSE ---
statResponse = sock.Receive(ref remoteEP);
}
- catch
+ catch (Exception)
{
return ConnStatus.Unknown;
}