internal KartMap(serverinfo_pak srv)
        {
            unsafe
            {
                InternalName = KartUtils.DecodeString(srv.mapname, serverinfo_pak.MAX_MAP_NAME_LENGTH);

                Title = KartUtils.DecodeString(srv.maptitle, serverinfo_pak.MAX_MAP_TITLE_LENGTH);

                MD5 = new byte[16];
                for (var i = 0; i < 16; i++)
                {
                    MD5[i] = srv.mapmd5[i];
                }
            }

            ActNumber = srv.actnum;
            IsZone    = srv.iszone == 0 || srv.iszone == 1
                ? srv.iszone == 1
                : throw new ArgumentOutOfRangeException(nameof(srv.iszone));
            TimeElapsed = TimeSpan.FromSeconds(srv.leveltime / (double)KartUtils.SRB2_TICRATE);
        }
 internal KartServer(serverinfo_pak srv, IEnumerable <plrinfo> players, IEnumerable <(string Filename, byte[] Md5)> neededFiles)
        /// <summary>
        /// Attempts to query an SRB2Kart server for information.
        /// </summary>
        /// <param name="endpoint">The endpoint (IP address and port) of the server.</param>
        /// <returns>A <see cref="KartServer"/> object representing server query information.</returns>
        public static KartServer QueryServer(IPEndPoint endpoint)
        {
            using var client = new UdpClient(endpoint.Port)
                  {
                      Client = { ReceiveTimeout = (int)QueryTimeout }
                  };

            client.Connect(endpoint);

            // ask for server info
            SendPacket(client, (byte)PT_ASKINFO, new byte[] { 0, 0, 0, 0, 0 });
            var remoteEndpoint = new IPEndPoint(IPAddress.Any, 0);

            var start       = 0u;
            var morePackets = true;
            var srv         = new serverinfo_pak();
            var players     = new List <plrinfo>();
            var neededFiles = new List <(string Filename, byte[] Md5)>();

            while (morePackets)
            {
                var received = client.Receive(ref remoteEndpoint);
                var stream   = new MemoryStream(received);
                var header   = ReadPacketComponent <doomdata_t>(stream);

                var type = (packettype_t)header.packettype;

                var timeout = 0;

                switch (type)
                {
                case PT_SERVERINFO:     // 13
                    srv = ReadPacketComponent <serverinfo_pak>(stream);
                    break;

                case PT_PLAYERINFO:     // 14
                    for (var i = 0; i < 16; i++)
                    {
                        players.Add(ReadPacketComponent <plrinfo>(stream));
                    }

                    SendPacket(client, (byte)PT_TELLFILESNEEDED, BitConverter.GetBytes(start));

                    break;

                case PT_MOREFILESNEEDED:
                    var filesNeeded = ReadPacketComponent <filesneededconfig_pak>(stream);
                    start += filesNeeded.num;

                    neededFiles.AddRange(GetFilesNeeded(received));
                    if (filesNeeded.more == 1)
                    {
                        // ask for more files
                        SendPacket(client, (byte)PT_TELLFILESNEEDED, BitConverter.GetBytes(start));
                    }
                    else
                    {
                        morePackets = false;
                    }
                    break;

                default:
                    if (++timeout == 5)     // 5 invalid packets in a row
                    {
                        throw new Exception("Received 5 invalid response packets in a row.");
                    }

                    break;
                }
            }

            return(new KartServer(srv, players, neededFiles));
        }