mirror of
				https://github.com/KartKrewDev/RingRacers.git
				synced 2025-10-30 08:01:28 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			362 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.Text;
 | 
						|
using System.Net;
 | 
						|
using System.Net.Sockets;
 | 
						|
using System.IO;
 | 
						|
 | 
						|
namespace SRB2Updater
 | 
						|
{
 | 
						|
    class ServerQuerier
 | 
						|
    {
 | 
						|
        private UdpClient udpclient;
 | 
						|
        private IPEndPoint ipepMS;
 | 
						|
 | 
						|
        private const int MS_HOLEPUNCH_SIZE = 0;
 | 
						|
        private const int PT_ASKINFO_SIZE = 16;
 | 
						|
        private const byte PT_ASKINFO = 12;
 | 
						|
        private const byte PT_SERVERINFO = 13;
 | 
						|
        private const int MAXSERVERNAME = 32;
 | 
						|
        private const int MAX_WADPATH = 128;
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Constructs a ServerQuerier object.
 | 
						|
        /// </summary>
 | 
						|
        public ServerQuerier()
 | 
						|
        {
 | 
						|
            udpclient = new UdpClient(0, AddressFamily.InterNetwork);
 | 
						|
 | 
						|
            // Fix for WSAECONNRESET. Only affects Win2k and up. If I send a
 | 
						|
            // packet to a host which replies with an ICMP Port Unreachable,
 | 
						|
            // subsequent socket operations go doo-lally. So, we enable the
 | 
						|
            // older behaviour of ignoring these ICMP messages, since we don't
 | 
						|
            // care about them anyway.
 | 
						|
            if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
 | 
						|
                Environment.OSVersion.Version.Major >= 5)
 | 
						|
            {
 | 
						|
                const uint IOC_IN = 0x80000000;
 | 
						|
                const uint IOC_VENDOR = 0x18000000;
 | 
						|
                const uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
 | 
						|
 | 
						|
                udpclient.Client.IOControl(unchecked((int)SIO_UDP_CONNRESET), new byte[] { Convert.ToByte(false) }, null);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        public void StartListening(ServerInfoReceiveHandler sirh)
 | 
						|
        {
 | 
						|
            // Start listening.
 | 
						|
            udpclient.BeginReceive(new AsyncCallback(ServerInfoReceiveHandler.Receive), sirh);
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Sets the master server address. Necessary before querying via the MS.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="strAddress">IP address of hostname of MS.</param>
 | 
						|
        /// <param name="unPort">Port of MS.</param>
 | 
						|
        public void SetMasterServer(string strAddress, ushort unPort)
 | 
						|
        {
 | 
						|
            IPAddress address = Dns.GetHostEntry(strAddress).AddressList[0];
 | 
						|
            ipepMS = new IPEndPoint(address, unPort);
 | 
						|
        }
 | 
						|
 | 
						|
        public void Query(string strAddress, ushort unPort)
 | 
						|
        {
 | 
						|
            // Build the packet.
 | 
						|
            byte[] byPacket = new byte[PT_ASKINFO_SIZE];
 | 
						|
            BinaryWriter bw = new BinaryWriter(new MemoryStream(byPacket));
 | 
						|
 | 
						|
            bw.Seek(4, SeekOrigin.Begin);		// Skip the checksum.
 | 
						|
            bw.Write((byte)0);					// ack
 | 
						|
            bw.Write((byte)0);					// ackreturn
 | 
						|
            bw.Write((byte)PT_ASKINFO);			// Packet type.
 | 
						|
            bw.Write((byte)0);					// Reserved.
 | 
						|
            bw.Write((byte)0);					// Version. This is actually unnecessary -- the client will reply anyway. -MattW_CFI
 | 
						|
            bw.Write((byte)0);					// Reserved.
 | 
						|
            bw.Write((byte)0);					// Reserved.
 | 
						|
            bw.Write((byte)0);					// Reserved.
 | 
						|
            // Time for ping calculation.
 | 
						|
            bw.Write(unchecked((uint)(DateTime.Now.Ticks / 10000)));
 | 
						|
 | 
						|
            // Calculate the checksum.
 | 
						|
            bw.Seek(0, SeekOrigin.Begin);
 | 
						|
            bw.Write(SRB2Checksum(byPacket));
 | 
						|
 | 
						|
            // Send the packet.
 | 
						|
            udpclient.Send(byPacket, byPacket.Length, strAddress, unPort);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Calculates the checksum of an SRB2 packet.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="byPacket">Packet.</param>
 | 
						|
        /// <returns>Checksum.</returns>
 | 
						|
        private static uint SRB2Checksum(byte[] byPacket)
 | 
						|
        {
 | 
						|
            uint c = 0x1234567;
 | 
						|
            int i;
 | 
						|
 | 
						|
            for (i = 4; i < byPacket.Length; i++)
 | 
						|
                unchecked
 | 
						|
                {
 | 
						|
                    c += (uint)byPacket[i] * (uint)(i - 3);
 | 
						|
                }
 | 
						|
 | 
						|
            return c;
 | 
						|
        }
 | 
						|
 | 
						|
        private static string ReadFixedLengthStr(BinaryReader br, int iLen)
 | 
						|
        {
 | 
						|
            String str = Encoding.ASCII.GetString(br.ReadBytes(iLen));
 | 
						|
            int iPos = str.IndexOf("\0");
 | 
						|
            if (iPos >= 0)
 | 
						|
                str = str.Remove(iPos);
 | 
						|
 | 
						|
            return str;
 | 
						|
        }
 | 
						|
 | 
						|
        public abstract class ServerInfoReceiveHandler
 | 
						|
        {
 | 
						|
            UdpClient udpclient;
 | 
						|
            IPEndPoint ipepRemote;
 | 
						|
 | 
						|
            /// <summary>
 | 
						|
            /// Called after a server info packet is received.
 | 
						|
            /// </summary>
 | 
						|
            /// <param name="srb2si">Server info.</param>
 | 
						|
            public abstract void ProcessServerInfo(SRB2ServerInfo srb2si);
 | 
						|
            public abstract void HandleException(Exception e);
 | 
						|
 | 
						|
            public ServerInfoReceiveHandler(ServerQuerier sq)
 | 
						|
            {
 | 
						|
                ipepRemote = new IPEndPoint(IPAddress.Any, 0);
 | 
						|
 | 
						|
                udpclient = sq.udpclient;
 | 
						|
            }
 | 
						|
 | 
						|
            public static void Receive(IAsyncResult ar)
 | 
						|
            {
 | 
						|
                ServerInfoReceiveHandler sirh = (ServerInfoReceiveHandler)ar.AsyncState;
 | 
						|
 | 
						|
                byte[] byPacket = sirh.udpclient.EndReceive(ar, ref sirh.ipepRemote);
 | 
						|
 | 
						|
                // Analyse the packet.
 | 
						|
                BinaryReader br = new BinaryReader(new MemoryStream(byPacket));
 | 
						|
 | 
						|
                // Get the checksum.
 | 
						|
                uint uiChecksum = br.ReadUInt32();
 | 
						|
 | 
						|
                // Skip ack and ackreturn and get packet type.
 | 
						|
                br.ReadBytes(2);
 | 
						|
                byte byPacketType = br.ReadByte();
 | 
						|
 | 
						|
                // Only interested in valid PT_SERVERINFO packets.
 | 
						|
                if (byPacketType == PT_SERVERINFO && uiChecksum == SRB2Checksum(byPacket))
 | 
						|
                {
 | 
						|
                    bool bMalformed = true;
 | 
						|
 | 
						|
                    // Skip padding.
 | 
						|
                    br.ReadByte();
 | 
						|
 | 
						|
                    // Remember where we are.
 | 
						|
                    long iPacketStart = br.BaseStream.Position;
 | 
						|
 | 
						|
                    // Try to interpret the packet in each recognised format.
 | 
						|
                    foreach (ServerInfoVer siv in Enum.GetValues(typeof(ServerInfoVer)))
 | 
						|
                    {
 | 
						|
                        SRB2ServerInfo srb2si;
 | 
						|
                        byte byNumWads = 0;
 | 
						|
 | 
						|
                        srb2si.siv = siv;
 | 
						|
 | 
						|
                        br.BaseStream.Position = iPacketStart;
 | 
						|
 | 
						|
                        // Get address from socket.
 | 
						|
                        srb2si.strAddress = sirh.ipepRemote.Address.ToString();
 | 
						|
                        srb2si.unPort = unchecked((ushort)sirh.ipepRemote.Port);
 | 
						|
 | 
						|
                        // Get version.
 | 
						|
                        byte byVersion = br.ReadByte();
 | 
						|
 | 
						|
                        if (siv == ServerInfoVer.SIV_PREME)
 | 
						|
                        {
 | 
						|
                            br.ReadBytes(3);
 | 
						|
 | 
						|
                            uint uiSubVersion = br.ReadUInt32();
 | 
						|
 | 
						|
                            // Format version.
 | 
						|
                            // MattW_CFI: I hope you don't mind this exception, Oogaland, but 0.01.6 looks odd >_>
 | 
						|
                            if (byVersion == 1 && uiSubVersion == 6)
 | 
						|
                                srb2si.strVersion = "X.01.6";
 | 
						|
                            else
 | 
						|
                                srb2si.strVersion = byVersion.ToString();
 | 
						|
                                //srb2si.strVersion = String.Format("{0}.{1:00}.{2}", byVersion / 100, byVersion % 100, uiSubVersion);
 | 
						|
                        }
 | 
						|
                        else
 | 
						|
                        {
 | 
						|
                            byte bySubVersion = br.ReadByte();
 | 
						|
 | 
						|
                            // Format version.
 | 
						|
                            //srb2si.strVersion = String.Format("{0}.{1:00}.{2}", byVersion / 100, byVersion % 100, bySubVersion);
 | 
						|
                            srb2si.strVersion = byVersion.ToString();
 | 
						|
                        }
 | 
						|
 | 
						|
                        srb2si.byPlayers = br.ReadByte();
 | 
						|
                        srb2si.byMaxplayers = br.ReadByte();
 | 
						|
                        srb2si.byGametype = br.ReadByte();
 | 
						|
                        srb2si.bModified = (br.ReadByte() != 0);
 | 
						|
 | 
						|
                        if (siv == ServerInfoVer.SIV_ME)
 | 
						|
                            byNumWads = br.ReadByte();
 | 
						|
 | 
						|
                        srb2si.sbyAdminplayer = br.ReadSByte();
 | 
						|
 | 
						|
                        if (siv == ServerInfoVer.SIV_PREME)
 | 
						|
                            br.ReadBytes(3);
 | 
						|
 | 
						|
                        // Calculate ping.
 | 
						|
                        srb2si.uiTime = unchecked((uint)((long)(DateTime.Now.Ticks / 10000 - br.ReadUInt32()) % ((long)UInt32.MaxValue + 1)));
 | 
						|
 | 
						|
                        if (siv == ServerInfoVer.SIV_PREME)
 | 
						|
                            br.ReadUInt32();
 | 
						|
 | 
						|
                        // Get and tidy map name.
 | 
						|
                        if (siv == ServerInfoVer.SIV_PREME)
 | 
						|
                        {
 | 
						|
                            srb2si.strMapName = ReadFixedLengthStr(br, 8);
 | 
						|
                            srb2si.strName = ReadFixedLengthStr(br, MAXSERVERNAME);
 | 
						|
                        }
 | 
						|
                        else
 | 
						|
                        {
 | 
						|
                            srb2si.strName = ReadFixedLengthStr(br, MAXSERVERNAME);
 | 
						|
                            srb2si.strMapName = ReadFixedLengthStr(br, 8);
 | 
						|
                        }
 | 
						|
 | 
						|
                        if (siv == ServerInfoVer.SIV_PREME)
 | 
						|
                            byNumWads = br.ReadByte();
 | 
						|
 | 
						|
                        // Create new list of strings of initial size equal to number of wads.
 | 
						|
                        srb2si.listFiles = new List<AddedWad>(byNumWads);
 | 
						|
 | 
						|
                        // Get the files info.
 | 
						|
                        byte[] byFiles = br.ReadBytes(siv == ServerInfoVer.SIV_PREME ? 4096 : 936);
 | 
						|
                        BinaryReader brFiles = new BinaryReader(new MemoryStream(byFiles));
 | 
						|
 | 
						|
                        // Extract the filenames.
 | 
						|
                        try
 | 
						|
                        {
 | 
						|
                            for (int i = 0; i < byNumWads; i++)
 | 
						|
                            {
 | 
						|
                                bool bFullString = false;
 | 
						|
                                AddedWad aw = new AddedWad();
 | 
						|
 | 
						|
                                if (siv == ServerInfoVer.SIV_PREME)
 | 
						|
                                {
 | 
						|
                                    aw.bImportant = brFiles.ReadByte() != 0;
 | 
						|
                                    aw.downloadtype = (DownloadTypes)brFiles.ReadByte();
 | 
						|
                                }
 | 
						|
                                else
 | 
						|
                                {
 | 
						|
                                    byte byFileStatus = brFiles.ReadByte();
 | 
						|
                                    aw.bImportant = (byFileStatus & 0xF) != 0;
 | 
						|
                                    aw.downloadtype = (DownloadTypes)(byFileStatus >> 4);
 | 
						|
                                }
 | 
						|
 | 
						|
                                aw.uiSize = brFiles.ReadUInt32();
 | 
						|
 | 
						|
                                // Work out how long the string is.
 | 
						|
                                int iStringPos = (int)brFiles.BaseStream.Position;
 | 
						|
                                while (iStringPos < byFiles.Length && byFiles[iStringPos] != 0) iStringPos++;
 | 
						|
 | 
						|
                                // Make sure it's not longer than the max name length.
 | 
						|
                                if (iStringPos - (int)brFiles.BaseStream.Position > MAX_WADPATH)
 | 
						|
                                {
 | 
						|
                                    bFullString = true;
 | 
						|
                                    iStringPos = MAX_WADPATH + (int)brFiles.BaseStream.Position;
 | 
						|
                                }
 | 
						|
 | 
						|
                                // Get the info and add it, if possible.
 | 
						|
                                if (iStringPos > (int)brFiles.BaseStream.Position)
 | 
						|
                                {
 | 
						|
                                    aw.strFilename = Encoding.ASCII.GetString(brFiles.ReadBytes(iStringPos - (int)brFiles.BaseStream.Position));
 | 
						|
                                    srb2si.listFiles.Add(aw);
 | 
						|
                                }
 | 
						|
 | 
						|
                                // Skip nul.
 | 
						|
                                if (!bFullString) brFiles.ReadByte();
 | 
						|
 | 
						|
                                // Skip the md5sum.
 | 
						|
                                brFiles.ReadBytes(16);
 | 
						|
                            }
 | 
						|
 | 
						|
                            // Okay, done! Do something useful with the server info.
 | 
						|
                            sirh.ProcessServerInfo(srb2si);
 | 
						|
 | 
						|
                            // If we got this far without an exception, leave the foreach loop.
 | 
						|
                            bMalformed = false;
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
                        catch (EndOfStreamException)
 | 
						|
                        {
 | 
						|
                            // Packet doesn't match supposed type, so we swallow the exception
 | 
						|
                            // and try remaining types.
 | 
						|
                        }
 | 
						|
                        catch (Exception e)
 | 
						|
                        {
 | 
						|
                            sirh.HandleException(e);
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (bMalformed)
 | 
						|
                        sirh.HandleException(new Exception("Received invalid PT_SERVERINFO packet from " + sirh.ipepRemote.Address + ":" + sirh.ipepRemote.Port + "."));
 | 
						|
                }
 | 
						|
 | 
						|
                // Resume listening.
 | 
						|
                sirh.ipepRemote = new IPEndPoint(IPAddress.Any, 0);
 | 
						|
                sirh.udpclient.BeginReceive(new AsyncCallback(Receive), sirh);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        public enum DownloadTypes
 | 
						|
        {
 | 
						|
            DT_TOOBIG = 0,
 | 
						|
            DT_OK = 1,
 | 
						|
            DT_DISABLED = 2
 | 
						|
        }
 | 
						|
 | 
						|
        public struct AddedWad
 | 
						|
        {
 | 
						|
            public string strFilename;
 | 
						|
            public bool bImportant;
 | 
						|
            public uint uiSize;
 | 
						|
            public DownloadTypes downloadtype;
 | 
						|
        }
 | 
						|
 | 
						|
        public enum ServerInfoVer
 | 
						|
        {
 | 
						|
            SIV_PREME,
 | 
						|
            SIV_ME
 | 
						|
        };
 | 
						|
 | 
						|
        public struct SRB2ServerInfo
 | 
						|
        {
 | 
						|
            public string strAddress;
 | 
						|
            public ushort unPort;
 | 
						|
 | 
						|
            public ServerInfoVer siv;
 | 
						|
 | 
						|
            public string strVersion;
 | 
						|
            public byte byPlayers;
 | 
						|
            public byte byMaxplayers;
 | 
						|
            public byte byGametype;
 | 
						|
            public bool bModified;
 | 
						|
            public sbyte sbyAdminplayer;
 | 
						|
            public uint uiTime;
 | 
						|
            public string strMapName;
 | 
						|
            public string strName;
 | 
						|
 | 
						|
            public List<AddedWad> listFiles;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |