/// <summary> /// Internal helper method for parsing the `beta` SLP protocol payload. /// May be useful for unit tests and issue troubleshooting. /// </summary> /// <param name="rawPayload">The raw payload, without packet length and -id</param> /// <returns>ConnStatus - See <see cref="ConnStatus"/> for possible values</returns> private ConnStatus ParseBetaProtocol(byte[] rawPayload) { // Decode byte[] as UTF16BE var payloadString = Encoding.BigEndianUnicode.GetString(rawPayload, 0, rawPayload.Length); // The payload contains 3 parts, separated by '§' (section sign) // If the MOTD contains §, there may be more parts here (we take care of that later) var payloadArray = payloadString.Split('§'); if (payloadArray.Length < 3) { return(ConnStatus.Unknown); } // Max player count is the last element MaximumPlayersInt = Convert.ToInt32(payloadArray[payloadArray.Length - 1]); // Current player count is second-to-last element CurrentPlayersInt = Convert.ToInt32(payloadArray[payloadArray.Length - 2]); // Motd is first element, but may contain 'section sign' (the delimiter) Motd = String.Join("§", payloadArray.Take(payloadArray.Length - 2).ToArray()); // If we got here, everything is in order ServerUp = true; Protocol = SlpProtocol.Beta; // This protocol does not provide the server version. Version = "<= 1.3"; return(ConnStatus.Success); }
/// <summary> /// Internal helper method for parsing the 1.4-1.5 ('Legacy') and 1.6 ('ExtendedLegacy') SLP protocol payloads. /// The (response) payload for both protocols is identical, only the request is different. /// </summary> /// <param name="rawPayload">The raw payload, without packet length and -id</param> /// <param name="protocol">The protocol that was used (either Legacy or ExtendedLegacy)</param> /// <returns>ConnStatus - See <see cref="ConnStatus"/> for possible values</returns> /// <seealso cref="SlpProtocol.Legacy"/> /// <seealso cref="SlpProtocol.ExtendedLegacy"/> private ConnStatus ParseLegacyProtocol(byte[] rawPayload, SlpProtocol protocol = SlpProtocol.ExtendedLegacy) { // Decode byte[] as UTF16BE var payloadString = Encoding.BigEndianUnicode.GetString(rawPayload, 0, rawPayload.Length); // This "payload" contains six fields delimited by a NUL character, see below var payloadArray = payloadString.Split('\0'); // Check if we got the right amount of parts, expected is 6 for this protocol version if (payloadArray.Length != 6) { return(ConnStatus.Unknown); } // This "payload" contains six fields delimited by a NUL character: // - a fixed prefix '§1' (ignored) // - the protocol version (ignored) // - the server version Version = payloadArray[2]; // - the MOTD Motd = payloadArray[3]; // - the online player count CurrentPlayersInt = Convert.ToInt32(payloadArray[4]); // - the max player count MaximumPlayersInt = Convert.ToInt32(payloadArray[5]); // If we got here, everything is in order ServerUp = true; Protocol = protocol; return(ConnStatus.Success); }
/// <summary> /// Helper method for parsing the payload of the `json` SLP protocol /// </summary> /// <param name="rawPayload">The raw payload, without packet length and -id</param> /// <returns>ConnStatus - See <see cref="ConnStatus"/> for possible values</returns> /// <seealso cref="SlpProtocol.Json"/> private ConnStatus ParseJsonProtocolPayload(byte[] rawPayload) { try { var jsonReader = JsonReaderWriterFactory.CreateJsonReader(rawPayload, new System.Xml.XmlDictionaryReaderQuotas()); var root = XElement.Load(jsonReader); // This payload contains a json string like this: // {"description":{"text":"A Minecraft Server"},"players":{"max":20,"online":0},"version":{"name":"1.16.5","protocol":754}} // {"description":{"text":"This is MC \"1.16\" §9§oT§4E§r§lS§6§o§nT"},"players":{"max":20,"online":0},"version":{"name":"1.16.5","protocol":754"}} // Extract version Version = root.XPathSelectElement("//version/name")?.Value; // the MOTD var descriptionElement = root.XPathSelectElement("//description"); if (descriptionElement != null && descriptionElement.Attribute(XName.Get("type"))?.Value == "string") { Motd = descriptionElement.Value; } else if (root.XPathSelectElement("//description/text") != null) { Motd = root.XPathSelectElement("//description/text")?.Value; } // the online player count CurrentPlayersInt = Convert.ToInt32(root.XPathSelectElement("//players/online")?.Value); // the max player count MaximumPlayersInt = Convert.ToInt32(root.XPathSelectElement("//players/max")?.Value); // the online player list, if provided by the server // inspired by https://github.com/lunalunaaaa var playerSampleElement = root.XPathSelectElement("//players/sample"); if (playerSampleElement != null && playerSampleElement.Attribute(XName.Get("type"))?.Value == "array") { var playerSampleNameElements = root.XPathSelectElements("//players/sample/item/name"); PlayerList = playerSampleNameElements.Select(playerNameElement => playerNameElement.Value).ToArray(); } } catch (Exception) { return(ConnStatus.Unknown); } // Check if everything was filled if (Version == null || Motd == null) { return(ConnStatus.Unknown); } // If we got here, everything is in order ServerUp = true; Protocol = SlpProtocol.Json; return(ConnStatus.Success); }
/// <summary> /// Helper method for parsing the payload of the `json` SLP protocol /// </summary> /// <param name="rawPayload">The raw payload, without packet length and -id</param> /// <returns>ConnStatus - See <see cref="ConnStatus"/> for possible values</returns> /// <seealso cref="SlpProtocol.Json"/> private ConnStatus ParseJsonProtocolPayload(byte[] rawPayload) { try { var jsonReader = JsonReaderWriterFactory.CreateJsonReader(rawPayload, new System.Xml.XmlDictionaryReaderQuotas()); var root = XElement.Load(jsonReader); // This payload contains a json string like this: // {"description":{"text":"A Minecraft Server"},"players":{"max":20,"online":0},"version":{"name":"1.16.5","protocol":754}} // {"description":{"text":"This is MC \"1.16\" §9§oT§4E§r§lS§6§o§nT"},"players":{"max":20,"online":0},"version":{"name":"1.16.5","protocol":754"}} // Extract version Version = root.XPathSelectElement("//version/name")?.Value; // the MOTD Motd = root.XPathSelectElement("//description/text")?.Value; // the online player count CurrentPlayersInt = Convert.ToInt16(root.XPathSelectElement("//players/online")?.Value); // the max player count MaximumPlayersInt = Convert.ToInt16(root.XPathSelectElement("//players/max")?.Value); } catch (Exception) { return(ConnStatus.Unknown); } // Check if everything was filled if (Version == null || Motd == null) { return(ConnStatus.Unknown); } // If we got here, everything is in order ServerUp = true; Protocol = SlpProtocol.Json; return(ConnStatus.Success); }
/// <summary> /// MineStat is a Minecraft server status checker.<br/> /// After object creation, the appropriate SLP (server list ping) protocol will be automatically chosen based on the /// server version and all fields will be populated. /// </summary> /// <example> /// <code> /// MineStat ms = new MineStat("minecraft.frag.land", 25565); /// Console.WriteLine("The server is" + ms.ServerUp ? "online!" : "offline!"); /// </code> /// </example> /// <param name="address">Address (hostname or IP) of Minecraft server to connect to</param> /// <param name="port">Port to connect to on the address</param> /// <param name="timeout">(Optional) Timeout in seconds</param> /// <param name="protocol">(Optional) SLP protocol to use, defaults to automatic detection</param> public MineStat(string address, ushort port, int timeout = DefaultTimeout, SlpProtocol protocol = SlpProtocol.Automatic) { Address = address; Port = port; Timeout = timeout; // If the user manually selected a protocol, use that switch (protocol) { case SlpProtocol.Beta: RequestWrapper(RequestWithBetaProtocol); break; case SlpProtocol.Legacy: RequestWrapper(RequestWithLegacyProtocol); break; case SlpProtocol.ExtendedLegacy: RequestWrapper(RequestWithExtendedLegacyProtocol); break; case SlpProtocol.Json: RequestWrapper(RequestWithJsonProtocol); break; case SlpProtocol.Automatic: break; default: throw new ArgumentOutOfRangeException(nameof(protocol), "Invalid SLP protocol specified for parameter 'protocol'"); } // If a protocol was chosen manually, return if (protocol != SlpProtocol.Automatic) { return; } // The order of protocols here is (sadly) important. // Some server versions (1.3, 1.4) seem to have trouble with newer protocols and stop responding for a few seconds. // If, for example, the ext.-legacy protocol triggers this problem, the following connections are dropped/reset // even if they would have worked individually/normally. // For more information, see https://github.com/FragLand/minestat/issues/70 // // 1.: Legacy (1.4, 1.5) // 2.: Beta (b1.8-rel1.3) // 3.: Extended Legacy (1.6) // 4.: JSON (1.7+) var result = RequestWrapper(RequestWithLegacyProtocol); if (result != ConnStatus.Connfail && result != ConnStatus.Success) { result = RequestWrapper(RequestWithBetaProtocol); if (result != ConnStatus.Connfail) { result = RequestWrapper(RequestWithExtendedLegacyProtocol); } if (result != ConnStatus.Connfail && result != ConnStatus.Success) { RequestWrapper(RequestWithJsonProtocol); } } }