Example #1
0
        /// <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);
        }
Example #2
0
        /// <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);
        }
Example #3
0
        /// <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);
        }
Example #4
0
        /// <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);
        }
Example #5
0
        /// <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);
                }
            }
        }