McStatus.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. using System.Diagnostics;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using System.Text;
  5. // Used this with some changes
  6. // https://github.com/maxime-paquatte/csharp-minecraft-query
  7. namespace MinecraftStatus
  8. {
  9. /// <summary>
  10. /// Query a minecraft server to obtains the status (according this documentation : http://wiki.vg/Query).
  11. /// </summary>
  12. public class McStatus
  13. {
  14. const Byte Statistic = 0x00;
  15. const Byte Handshake = 0x09;
  16. private readonly Dictionary<string, string> _keyValues;
  17. private List<string> _players;
  18. public string MessageOfTheDay
  19. {
  20. get { return _keyValues["hostname"]; }
  21. }
  22. public string Gametype
  23. {
  24. get { return _keyValues["gametype"]; }
  25. }
  26. public string GameId
  27. {
  28. get { return _keyValues["game_id"]; }
  29. }
  30. public string Version
  31. {
  32. get { return _keyValues["version"]; }
  33. }
  34. public string Plugins
  35. {
  36. get { return _keyValues["plugins"]; }
  37. }
  38. public string Map
  39. {
  40. get { return _keyValues["map"]; }
  41. }
  42. public string NumPlayers
  43. {
  44. get { return _keyValues["numplayers"]; }
  45. }
  46. public string MaxPlayers
  47. {
  48. get { return _keyValues["maxplayers"]; }
  49. }
  50. public string HostPort
  51. {
  52. get { return _keyValues["hostport"]; }
  53. }
  54. public string HostIp
  55. {
  56. get { return _keyValues["hostip"]; }
  57. }
  58. public IEnumerable<string> Players
  59. {
  60. get { return _players; }
  61. }
  62. internal McStatus(byte[] message)
  63. {
  64. _keyValues = new Dictionary<string, string>();
  65. _players = new List<string>();
  66. var buffer = new byte[256];
  67. Stream stream = new MemoryStream(message);
  68. stream.Read(buffer, 0, 5);// Read Type + SessionID
  69. stream.Read(buffer, 0, 11); // Padding: 11 bytes constant
  70. var constant1 = new byte[] { 0x73, 0x70, 0x6C, 0x69, 0x74, 0x6E, 0x75, 0x6D, 0x00, 0x80, 0x00 };
  71. for (int i = 0; i < constant1.Length; i++) Debug.Assert(constant1[i] == buffer[i], "Byte mismatch at " + i + " Val :" + String.Join(" ", buffer));
  72. var sb = new StringBuilder();
  73. string lastKey = string.Empty;
  74. int currentByte;
  75. while ((currentByte = stream.ReadByte()) != -1)
  76. {
  77. if (currentByte == 0x00)
  78. {
  79. if (!string.IsNullOrEmpty(lastKey))
  80. {
  81. _keyValues.Add(lastKey, sb.ToString());
  82. lastKey = string.Empty;
  83. }
  84. else
  85. {
  86. lastKey = sb.ToString();
  87. if (string.IsNullOrEmpty(lastKey)) break;
  88. }
  89. sb.Clear();
  90. }
  91. else sb.Append((char)currentByte);
  92. }
  93. stream.Read(buffer, 0, 10); // Padding: 10 bytes constant
  94. var constant2 = new byte[] { 0x01, 0x70, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x5F, 0x00, 0x00 };
  95. //for (int i = 0; i < constant2.Length; i++) Debug.Assert(constant2[i] == buffer[i], "Byte mismatch at " + i + " Val :" + buffer[i]);
  96. while ((currentByte = stream.ReadByte()) != -1)
  97. {
  98. if (currentByte == 0x00)
  99. {
  100. var player = sb.ToString();
  101. if (string.IsNullOrEmpty(player)) break;
  102. _players.Add(player);
  103. sb.Clear();
  104. }
  105. else sb.Append((char)currentByte);
  106. }
  107. }
  108. /// <summary>
  109. /// Get the status of the given host and optional port
  110. /// </summary>
  111. /// <param name="host">The host name or address (monserver.com or 123.123.123.123)</param>
  112. /// <param name="port">The query port, by default is 25565</param>
  113. public static McStatus? GetStatus(string host, int port = 25565)
  114. {
  115. var e = new IPEndPoint(IPAddress.Any, port);
  116. using (var u = new UdpClient())
  117. {
  118. u.Client.SendTimeout = 1000;
  119. u.Client.ReceiveTimeout = 1000;
  120. u.ExclusiveAddressUse = false;
  121. u.Client.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.ReuseAddress, true);
  122. u.Client.Bind(e);
  123. try
  124. {
  125. var s = new UdpState { EndPoint = e, Client = u };
  126. u.Connect(host, port);
  127. var status = GetStatus(s);
  128. if (status == null)
  129. throw new SocketException();
  130. //check answer is correct
  131. var buffer = new byte[256];
  132. Stream stream = new MemoryStream(status);
  133. stream.Read(buffer, 0, 5);// Read Type + SessionID
  134. stream.Read(buffer, 0, 11); // Padding: 11 bytes constant
  135. var constant1 = new byte[] { 0x73, 0x70, 0x6C, 0x69, 0x74, 0x6E, 0x75, 0x6D, 0x00, 0x80, 0x00 };
  136. for (int i = 0; i < constant1.Length; i++) if (constant1[i] != buffer[i]) throw new Exception(String.Join(" ", status));
  137. u.Close();
  138. return new McStatus(status);
  139. }
  140. catch (SocketException)
  141. {
  142. u.Close();
  143. return null;
  144. }
  145. catch (Exception ex)
  146. {
  147. Console.Out.WriteLine(ex.StackTrace + "\n" + ex.Message);
  148. u.Close();
  149. return null;
  150. }
  151. finally
  152. {
  153. u.Close();
  154. u.Dispose();
  155. //u.Close();
  156. }
  157. }
  158. }
  159. static byte[]? GetStatus(UdpState s)
  160. {
  161. var challengeToken = GetChallengeToken(s);
  162. if (challengeToken == null)
  163. return null;
  164. //append 4 bytes to obtains the Full status
  165. WriteData(s, Statistic, challengeToken, new byte[] { 0x00, 0x00, 0x00, 0x00 });
  166. return ReceiveMessages(s);
  167. }
  168. static byte[]? GetChallengeToken(UdpState s)
  169. {
  170. WriteData(s, Handshake);
  171. var message = ReceiveMessages(s);
  172. var challangeBytes = new byte[16];
  173. Array.Copy(message, 5, challangeBytes, 0, message.Length - 5);
  174. //string challengeStr = new string(Encoding.ASCII.GetString(challangeBytes).Where(char.IsDigit).ToArray());
  175. //Console.Out.WriteLineAsync("|"+challengeStr + "|" + s.EndPoint.Port);
  176. //var challengeInt = Int64.Parse(challengeStr);
  177. try
  178. {
  179. var challengeInt = int.Parse(Encoding.ASCII.GetString(challangeBytes));
  180. return BitConverter.GetBytes(challengeInt).Reverse().ToArray();
  181. }
  182. catch (FormatException)
  183. {
  184. return null;
  185. }
  186. }
  187. static void WriteData(UdpState s, byte cmd, byte[] append = null, byte[] append2 = null)
  188. {
  189. var cmdData = new byte[] { 0xFE, 0xFD, cmd, 0x01, 0x02, 0x03, 0x04 };
  190. var dataLength = cmdData.Length + (append != null ? append.Length : 0) + (append2 != null ? append2.Length : 0);
  191. var data = new byte[dataLength];
  192. cmdData.CopyTo(data, 0);
  193. if (append != null) append.CopyTo(data, cmdData.Length);
  194. if (append2 != null) append2.CopyTo(data, cmdData.Length + (append != null ? append.Length : 0));
  195. s.Client.Send(data, data.Length);
  196. }
  197. static byte[] ReceiveMessages(UdpState s)
  198. {
  199. return s.Client.Receive(ref s.EndPoint);
  200. }
  201. class UdpState
  202. {
  203. public UdpClient Client;
  204. public IPEndPoint EndPoint;
  205. }
  206. }
  207. }