using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Text; // Used this with some changes // https://github.com/maxime-paquatte/csharp-minecraft-query namespace MinecraftStatus { /// /// Query a minecraft server to obtains the status (according this documentation : http://wiki.vg/Query). /// public class McStatus { const Byte Statistic = 0x00; const Byte Handshake = 0x09; private readonly Dictionary _keyValues; private List _players; public string MessageOfTheDay { get { return _keyValues["hostname"]; } } public string Gametype { get { return _keyValues["gametype"]; } } public string GameId { get { return _keyValues["game_id"]; } } public string Version { get { return _keyValues["version"]; } } public string Plugins { get { return _keyValues["plugins"]; } } public string Map { get { return _keyValues["map"]; } } public string NumPlayers { get { return _keyValues["numplayers"]; } } public string MaxPlayers { get { return _keyValues["maxplayers"]; } } public string HostPort { get { return _keyValues["hostport"]; } } public string HostIp { get { return _keyValues["hostip"]; } } public IEnumerable Players { get { return _players; } } internal McStatus(byte[] message) { _keyValues = new Dictionary(); _players = new List(); var buffer = new byte[256]; Stream stream = new MemoryStream(message); stream.Read(buffer, 0, 5);// Read Type + SessionID stream.Read(buffer, 0, 11); // Padding: 11 bytes constant var constant1 = new byte[] { 0x73, 0x70, 0x6C, 0x69, 0x74, 0x6E, 0x75, 0x6D, 0x00, 0x80, 0x00 }; for (int i = 0; i < constant1.Length; i++) Debug.Assert(constant1[i] == buffer[i], "Byte mismatch at " + i + " Val :" + String.Join(" ", buffer)); var sb = new StringBuilder(); string lastKey = string.Empty; int currentByte; while ((currentByte = stream.ReadByte()) != -1) { if (currentByte == 0x00) { if (!string.IsNullOrEmpty(lastKey)) { _keyValues.Add(lastKey, sb.ToString()); lastKey = string.Empty; } else { lastKey = sb.ToString(); if (string.IsNullOrEmpty(lastKey)) break; } sb.Clear(); } else sb.Append((char)currentByte); } stream.Read(buffer, 0, 10); // Padding: 10 bytes constant var constant2 = new byte[] { 0x01, 0x70, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x5F, 0x00, 0x00 }; //for (int i = 0; i < constant2.Length; i++) Debug.Assert(constant2[i] == buffer[i], "Byte mismatch at " + i + " Val :" + buffer[i]); while ((currentByte = stream.ReadByte()) != -1) { if (currentByte == 0x00) { var player = sb.ToString(); if (string.IsNullOrEmpty(player)) break; _players.Add(player); sb.Clear(); } else sb.Append((char)currentByte); } } /// /// Get the status of the given host and optional port /// /// The host name or address (monserver.com or 123.123.123.123) /// The query port, by default is 25565 public static McStatus? GetStatus(string host, int port = 25565) { var e = new IPEndPoint(IPAddress.Any, port); using (var u = new UdpClient()) { u.Client.SendTimeout = 1000; u.Client.ReceiveTimeout = 1000; u.ExclusiveAddressUse = false; u.Client.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.ReuseAddress, true); u.Client.Bind(e); try { var s = new UdpState { EndPoint = e, Client = u }; u.Connect(host, port); var status = GetStatus(s); if (status == null) throw new SocketException(); //check answer is correct var buffer = new byte[256]; Stream stream = new MemoryStream(status); stream.Read(buffer, 0, 5);// Read Type + SessionID stream.Read(buffer, 0, 11); // Padding: 11 bytes constant var constant1 = new byte[] { 0x73, 0x70, 0x6C, 0x69, 0x74, 0x6E, 0x75, 0x6D, 0x00, 0x80, 0x00 }; for (int i = 0; i < constant1.Length; i++) if (constant1[i] != buffer[i]) throw new Exception(String.Join(" ", status)); u.Close(); return new McStatus(status); } catch (SocketException) { u.Close(); return null; } catch (Exception ex) { Console.Out.WriteLine(ex.StackTrace + "\n" + ex.Message); u.Close(); return null; } finally { u.Close(); u.Dispose(); //u.Close(); } } } static byte[]? GetStatus(UdpState s) { var challengeToken = GetChallengeToken(s); if (challengeToken == null) return null; //append 4 bytes to obtains the Full status WriteData(s, Statistic, challengeToken, new byte[] { 0x00, 0x00, 0x00, 0x00 }); return ReceiveMessages(s); } static byte[]? GetChallengeToken(UdpState s) { WriteData(s, Handshake); var message = ReceiveMessages(s); var challangeBytes = new byte[16]; Array.Copy(message, 5, challangeBytes, 0, message.Length - 5); //string challengeStr = new string(Encoding.ASCII.GetString(challangeBytes).Where(char.IsDigit).ToArray()); //Console.Out.WriteLineAsync("|"+challengeStr + "|" + s.EndPoint.Port); //var challengeInt = Int64.Parse(challengeStr); try { var challengeInt = int.Parse(Encoding.ASCII.GetString(challangeBytes)); return BitConverter.GetBytes(challengeInt).Reverse().ToArray(); } catch (FormatException) { return null; } } static void WriteData(UdpState s, byte cmd, byte[] append = null, byte[] append2 = null) { var cmdData = new byte[] { 0xFE, 0xFD, cmd, 0x01, 0x02, 0x03, 0x04 }; var dataLength = cmdData.Length + (append != null ? append.Length : 0) + (append2 != null ? append2.Length : 0); var data = new byte[dataLength]; cmdData.CopyTo(data, 0); if (append != null) append.CopyTo(data, cmdData.Length); if (append2 != null) append2.CopyTo(data, cmdData.Length + (append != null ? append.Length : 0)); s.Client.Send(data, data.Length); } static byte[] ReceiveMessages(UdpState s) { return s.Client.Receive(ref s.EndPoint); } class UdpState { public UdpClient Client; public IPEndPoint EndPoint; } } }