/// <summary>
        /// This method parses and handles a module load notify message from a
        /// game server.
        /// </summary>
        /// <param name="Parser">Supplies the message parse context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstModuleLoadNotify(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            Byte ExpansionsMask;
            string ModuleName;

            if (!Parser.ReadBYTE(out ExpansionsMask))
                return;
            if (!Parser.ReadSmallString(out ModuleName, 16))
                return;

            //
            // Query for whether the sender is blacklisted and drop the message
            // if so.
            //

            BlacklistLookup Lookup = new BlacklistLookup();

            Lookup.ServerAddress = Sender;
            Lookup.ModuleName = ModuleName;

            if (ServerTracker.IsBlacklisted(Lookup))
                return;

            NWGameServer Server = ServerTracker.LookupServerByAddress(Sender);

            //
            // Record the module load and request an updated player count.
            //

            Server.OnModuleLoad(ExpansionsMask, ModuleName);
            RefreshServerStatus(Sender);

            Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvMstModuleLoadNotify(): Server {0} ModuleName={1} ExpansionsMask={2}.", Sender, ModuleName, ExpansionsMask);
        }
        /// <summary>
        /// This method parses and handles a MOTD request from a game server or
        /// game client.
        /// </summary>
        /// <param name="Parser">Supplies the message parse context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstMOTDRequest(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            UInt16 DataPort;
            Byte Unknown0; // 0
            Byte Unknown1; // 0

            if (!Parser.ReadWORD(out DataPort))
                return;
            if (!Parser.ReadBYTE(out Unknown0))
                return;
            if (!Parser.ReadBYTE(out Unknown1))
                return;

            SendMstMOTD(Sender, MOTD);
        }
        /// <summary>
        /// This method parses and handles a player disconnect notify message
        /// from a game server.
        /// </summary>
        /// <param name="Parser">Supplies the message parse context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstDisconnectNotify(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            UInt16 DataPort;
            UInt16 EntryCount;
            UInt16 CDKeyCount;
            List<string> CDKeyList;
            NWGameServer Server;

            if (!Parser.ReadWORD(out DataPort))
                return;
            if (!Parser.ReadWORD(out EntryCount))
                return;

            if (EntryCount == 0)
            {
                //
                // Server shutdown.
                //

                Server = ServerTracker.LookupServerByAddress(Sender, false);

                if (Server == null || Server.Online == false)
                    return;

                //
                // Record the server shutdown.
                //

                Server.OnShutdownNotify();
                Logger.Log(LogLevel.Normal, "NWMasterServer.OnRecvMstShutdownNotify(): Server {0} shut down.", Sender);
                return;
            }
            else if (EntryCount != 1)
            {
                return;
            }

            if (!Parser.ReadWORD(out CDKeyCount))
                return;

            CDKeyList = new List<string>();

            while (CDKeyCount-- != 0)
            {
                string CDKey;

                if (!Parser.ReadSmallString(out CDKey, 16))
                    return;

                CDKeyList.Add(CDKey);
            }

            Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvMstDisconnectNotify()");
        }
        /// <summary>
        /// This method parses and handles a server startup notify message from
        /// a game server.
        /// </summary>
        /// <param name="Parser">Supplies the message parse context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstStartupNotify(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            Byte Platform;
            UInt16 BuildNumber;
            Byte Unknown0; // 0
            Byte Unknown1; // 0
            Byte Unknown2; // 1
            Byte Unknown3; // 0
            Byte Unknown4; // 3

            if (!Parser.ReadBYTE(out Platform))
                return;
            if (!Parser.ReadWORD(out BuildNumber))
                return;
            if (!Parser.ReadBYTE(out Unknown0))
                return;
            if (!Parser.ReadBYTE(out Unknown1))
                return;
            if (!Parser.ReadBYTE(out Unknown2))
                return;
            if (!Parser.ReadBYTE(out Unknown3))
                return;
            if (!Parser.ReadBYTE(out Unknown4))
                return;

            //
            // Query for whether the sender is blacklisted and drop the message
            // if so.
            //

            BlacklistLookup Lookup = new BlacklistLookup();

            Lookup.ServerAddress = Sender;

            if (ServerTracker.IsBlacklisted(Lookup))
                return;

            NWGameServer Server = ServerTracker.LookupServerByAddress(Sender);

            //
            // Record the server startup.
            //

            Server.OnStartupNotify(Platform, BuildNumber);
            Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvMstStartupNotify(): Server {0} Platform={1} BuildNumber={2}.", Sender, (char)Platform, BuildNumber);
        }
        /// <summary>
        /// This method parses and handles a CD-Key authorization request from
        /// from a game server.
        /// </summary>
        /// <param name="Parser">Supplies the message parse context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstCDKeyAuthorizationRequest(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            UInt16 DataPort;
            UInt16 EntryCount;
            UInt32 ClientIP;
            UInt16 ClientPort;
            byte[] ServerChallenge;
            UInt16 Length;
            List<CDKeyInfo> CDKeyHashes;
            CDKeyInfo CDKeyHash;
            string AccountName;
            int KeyIndex;
            int PortNumberHbo;

            if (!Parser.ReadWORD(out DataPort))
                return;
            if (!Parser.ReadWORD(out EntryCount))
                return;
            if (EntryCount != 1)
                return;
            if (!Parser.ReadDWORD(out ClientIP))
                return;
            if (!Parser.ReadWORD(out ClientPort))
                return;

            PortNumberHbo = IPAddress.NetworkToHostOrder(ClientPort);

            if ((PortNumberHbo == 0) || ((PortNumberHbo & 0xFFFF) != PortNumberHbo))
                PortNumberHbo = 5120;

            IPEndPoint ClientEndpoint = new IPEndPoint((long)ClientIP, PortNumberHbo);

            if (!Parser.ReadWORD(out Length))
                return;
            if ((ServerChallenge = Parser.ReadBytes(Length)) == null)
                return;
            if (!Parser.ReadWORD(out Length))
                return;

            CDKeyHashes = new List<CDKeyInfo>();
            CDKeyHash = new CDKeyInfo();

            KeyIndex = 0;
            while (Length-- != 0)
            {
                UInt16 HashLength;

                if (!Parser.ReadSmallString(out CDKeyHash.PublicCDKey, 16))
                    return;
                if (!Parser.ReadWORD(out HashLength))
                    return;
                if ((CDKeyHash.CDKeyHash = Parser.ReadBytes(HashLength)) == null)
                    return;

                //
                // N.B.  The following is somewhat of a hack in that we are not
                //       bothering to extract the (real) product type from the
                //       CD-Key.  As a result, it is possible that the wrong
                //       answer could be provided for clients without all of
                //       the expansions; this situation is considered unlikely
                //       in the current state of affairs.
                //
                //       A better fit could be had by examining the expansion
                //       mask required by the server in the data table, but
                //       this does not appear worthwhile at this stage.
                //

                if (KeyIndex == 0)
                    CDKeyHash.Product = 0;
                else
                    CDKeyHash.Product = (UInt16) (1 << (KeyIndex - 1));

                CDKeyHash.AuthStatus = (UInt16)ConnectStatus.CONNECT_ERR_SUCCESS;

                CDKeyHashes.Add(CDKeyHash);
            }

            if (!Parser.ReadSmallString(out AccountName, 16))
                return;

            SendMstCDKeyAuthorization(Sender, CDKeyHashes);
        }
        /// <summary>
        /// This method parses and handles a heartbeat message from a game
        /// server.
        /// </summary>
        /// <param name="Parser">Supplies the message parse context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstHeartbeat(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            UInt16 PlayerCount;
            List<List<string>> PlayerCDKeyList = new List<List<string>>();

            if (!Parser.ReadWORD(out PlayerCount))
                return;

            while (PlayerCount-- != 0)
            {
                UInt16 CDKeyCount;
                List<string> CDKeyList = new List<string>();

                if (!Parser.ReadWORD(out CDKeyCount))
                    return;

                while (CDKeyCount-- != 0)
                {
                    string CDKey;

                    if (!Parser.ReadSmallString(out CDKey, 16))
                        return;

                    CDKeyList.Add(CDKey);
                }

                PlayerCDKeyList.Add(CDKeyList);
            }

            Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvMstHeartbeat(): Server {0} ActivePlayerCount={1}.", Sender, PlayerCDKeyList.Count);
        }
        /// <summary>
        /// This method handles a received master server communication protocol
        /// message.  The message is examined and then dispatched to a handler.
        /// </summary>
        /// <param name="Cmd">Supplies the command code of the message.</param>
        /// <param name="ParseBuffer">Supplies the message body.</param>
        /// <param name="Sender">Supplies the reply address for the
        /// sender.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstMessage(uint Cmd, ExoParseBuffer ParseBuffer, IPEndPoint Sender, SocketInfo Socket)
        {
            switch (Cmd)
            {

                case (uint)MstCmd.CommunityAuthorizationRequest:
                    OnRecvMstCommunityAuthorizationRequest(ParseBuffer, Sender, Socket);
                    break;

                case (uint)MstCmd.CDKeyAuthorizationRequest:
                    OnRecvMstCDKeyAuthorizationRequest(ParseBuffer, Sender, Socket);
                    break;

                case (uint)MstCmd.Heartbeat:
                    OnRecvMstHeartbeat(ParseBuffer, Sender, Socket);
                    break;

                case (uint)MstCmd.DisconnectNotify:
                    OnRecvMstDisconnectNotify(ParseBuffer, Sender, Socket);
                    break;

                case (uint)MstCmd.StartupNotify:
                    OnRecvMstStartupNotify(ParseBuffer, Sender, Socket);
                    break;

                case (uint)MstCmd.ModuleLoadNotify:
                    OnRecvMstModuleLoadNotify(ParseBuffer, Sender, Socket);
                    break;

                case (uint)MstCmd.MOTDRequest:
                    OnRecvMstMOTDRequest(ParseBuffer, Sender, Socket);
                    break;

                case (uint)MstCmd.VersionRequest:
                    OnRecvMstVersionRequest(ParseBuffer, Sender, Socket);
                    break;

                case (uint)MstCmd.StatusRequest:
                    OnRecvMstStatusRequest(ParseBuffer, Sender, Socket);
                    break;

                case (uint)ConnAuthCmd.ServerInfoResponse:
                    OnRecvServerInfoResponse(ParseBuffer, Sender, Socket);
                    break;

                case (uint)ConnAuthCmd.ServerNameResponse:
                    OnRecvServerNameResponse(ParseBuffer, Sender, Socket);
                    break;

                case (uint)ConnAuthCmd.ServerDescriptionResponse:
                    OnRecvServerDescriptionResponse(ParseBuffer, Sender, Socket);
                    break;

            }
        }
        /// <summary>
        /// This method parses a server name response from a game server.
        /// </summary>
        /// <param name="Parser">Supplies the message parser context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvServerNameResponse(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            Byte UpdateType;
            UInt16 DataPort;
            Byte RequestCorrelationCookie;
            string ServerName;

            if (!Parser.ReadBYTE(out UpdateType))
                return;
            if (UpdateType != 'U')
                return;
            if (!Parser.ReadWORD(out DataPort))
                return;
            if (!Parser.ReadBYTE(out RequestCorrelationCookie))
                return;
            if (RequestCorrelationCookie != 0)
                return;
            if (!Parser.ReadSmallString(out ServerName))
                return;

            NWGameServer Server = ServerTracker.LookupServerByAddress(Sender, false);

            if (Server != null)
                Server.OnServerNameUpdate(ServerName);

            Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvServerNameResponse(): Server {0} name is {1}.", Sender, ServerName);
        }
        /// <summary>
        /// This method handles a received datagram on the master server
        /// socket.  It examines the request and dispatches it as appropriate.
        /// </summary>
        /// <param name="Buffer">Supplies the received datagram.</param>
        /// <param name="RecvLen">Supplies the length of the received
        /// datagram.</param>
        /// <param name="Sender">Supplies the datagram sender.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstDatagram(byte[] Buffer, int RecvLen, IPEndPoint Sender, SocketInfo Socket)
        {
            if (RecvLen < 4)
                return;

            unsafe
            {
                uint Cmd;

                Cmd = ((uint)Buffer[0] <<  0) |
                      ((uint)Buffer[1] <<  8) |
                      ((uint)Buffer[2] << 16) |
                      ((uint)Buffer[3] << 24);

                fixed (void *ByteData = &Buffer[4])
                {
                    ExoParseBuffer ParseBuffer;

                    using (ParseBuffer = new ExoParseBuffer(ByteData, (uint)RecvLen - 4, null, 0))
                    {
                        OnRecvMstMessage(Cmd, ParseBuffer, Sender, Socket);
                    }
                }
            }
        }
        /// <summary>
        /// This method handles a received datagram on the GameSpy server
        /// socket.  It examines the request and dispatches it as appropriate.
        /// </summary>
        /// <param name="Buffer">Supplies the received datagram.</param>
        /// <param name="RecvLen">Supplies the length of the received
        /// datagram.</param>
        /// <param name="Sender">Supplies the datagram sender.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvGameSpyDatagram(byte[] Buffer, int RecvLen, IPEndPoint Sender, SocketInfo Socket)
        {
            if (RecvLen < 1)
                return;

            unsafe
            {
                uint Cmd = (uint)Buffer[0];

                fixed (void* ByteData = &Buffer[1])
                {
                    ExoParseBuffer ParseBuffer;

                    using (ParseBuffer = new ExoParseBuffer(ByteData, (uint)RecvLen - 1, null, 0))
                    {
                        OnRecvGameSpyMessage(Cmd, ParseBuffer, Sender, Socket);
                    }
                }
            }
        }
        /// <summary>
        /// This method parses a GameSpy check server status request from a
        /// client.</summary>
        /// <param name="Parser">Supplies the message parser context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvGameSpyCheckServerStatus(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            //
            // The message contains:
            // (DWORD) 0
            // (STRING) ProductName "nwn2"
            //

            SendGameSpyCheckServerStatusResponse(Sender);
        }
        /// <summary>
        /// This method handles a received GameSpy server communication
        /// protocol message.  The message is examined and then dispatched to
        /// a handler.
        /// </summary>
        /// <param name="Cmd">Supplies the command code of the message.</param>
        /// <param name="ParseBuffer">Supplies the message body.</param>
        /// <param name="Sender">Supplies the reply address for the
        /// sender.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvGameSpyMessage(uint Cmd, ExoParseBuffer ParseBuffer, IPEndPoint Sender, SocketInfo Socket)
        {
            switch (Cmd)
            {

                case (uint)GameSpyCmd.CheckServerStatus:
                    OnRecvGameSpyCheckServerStatus(ParseBuffer, Sender, Socket);
                    break;

            }
        }
        /// <summary>
        /// This method parses a server name response from a game server.
        /// </summary>
        /// <param name="Parser">Supplies the message parser context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvServerDescriptionResponse(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            UInt16 DataPort;
            string GameDetails;
            string ModuleDescription;
            string BuildNumber;
            UInt16 GameType;
            string ModuleUrl;
            string PWCUrl;
            UInt16 Build;

            if (!Parser.ReadWORD(out DataPort))
                return;
            if (!Parser.ReadSmallString(out GameDetails, 32))
                return;
            if (!Parser.ReadSmallString(out ModuleDescription, 32))
                return;
            if (!Parser.ReadSmallString(out BuildNumber, 32))
                return;
            if (!Parser.ReadWORD(out GameType))
                return;

            if (Mode == GameMode.NWN2)
            {
                if (!Parser.ReadSmallString(out ModuleUrl, 32))
                    return;
                if (!Parser.ReadSmallString(out PWCUrl, 32))
                    return;
            }
            else
            {
                ModuleUrl = "";
                PWCUrl = "";
            }

            try
            {
                Build = Convert.ToUInt16(BuildNumber);
            }
            catch
            {
                Build = 0;
            }

            NWGameServer Server = ServerTracker.LookupServerByAddress(Sender, false);

            if (Server != null)
                Server.OnDescriptionInfoUpdate(ModuleDescription, ModuleUrl, GameType, PWCUrl, Build, GameDetails);

            Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvServerDescriptionResponse(): Server {0} description '{1}' URL '{2}' has game type {3}.",
                Sender,
                ModuleDescription,
                ModuleUrl,
                GameType);
        }
        /// <summary>
        /// This method parses and handles a version request from a game server
        /// or game client.
        /// </summary>
        /// <param name="Parser">Supplies the message parse context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstVersionRequest(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            UInt16 DataPort;
            Byte Unknown0; // 0
            Byte Unknown1; // 0
            Byte Unknown2; // 1

            if (!Parser.ReadWORD(out DataPort))
                return;
            if (!Parser.ReadBYTE(out Unknown0))
                return;
            if (!Parser.ReadBYTE(out Unknown1))
                return;
            if (!Parser.ReadBYTE(out Unknown2))
                return;

            SendMstVersion(Sender, BuildNumber);
        }
        /// <summary>
        /// This method parses and handles a community authorization request
        /// from a game server.
        /// </summary>
        /// <param name="Parser">Supplies the message parse context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstCommunityAuthorizationRequest(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            UInt16 DataPort;
            UInt16 Length;
            byte[] ServerChallenge;
            string AccountName;
            byte[] ClientVerifier;
            UInt16 Language;
            Byte Platform;
            Byte IsPlayer;

            if (!Parser.ReadWORD(out DataPort))
                return;
            if (!Parser.ReadWORD(out Length))
                return;
            if ((ServerChallenge = Parser.ReadBytes(Length)) == null)
                return;
            if (!Parser.ReadSmallString(out AccountName, 16))
                return;
            if (!Parser.ReadWORD(out Length))
                return;
            if ((ClientVerifier = Parser.ReadBytes(Length)) == null)
                return;
            if (!Parser.ReadWORD(out Language))
                return;
            if (!Parser.ReadBYTE(out Platform))
                return;
            if (!Parser.ReadBYTE(out IsPlayer))
                return;

            SendMstCommunityAccountAuthorization(Sender, AccountName, ConnectStatus.CONNECT_ERR_SUCCESS);
        }
        /// <summary>
        /// This method parses and handles a status request from a game server
        /// or game client.
        /// </summary>
        /// <param name="Parser">Supplies the message parser context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvMstStatusRequest(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            UInt16 DataPort;

            if (!Parser.ReadWORD(out DataPort))
                return;

            //
            // Do not enter the server into the server list until
            // bidirectional communication has been established.  However,
            // some broken NATs may respond briefly to the current source
            // port and then choose another (different!) source port later
            // for future pings.  To handle this case, send a BNXI probe to
            // both internal and external addresses.  If the responses are
            // the same, then assume that the server is actually located at
            // the internal address; otherwise, create server records for
            // both servers.
            //

            if (DataPort != (UInt16)Sender.Port)
            {
                IPEndPoint InternalAddress = new IPEndPoint(Sender.Address, (int)DataPort);

                SendServerInfoRequest(InternalAddress);
            }

            SendServerInfoRequest(Sender);

            SendMstStatusResponse(Sender, MstStatus.MST_STATUS_ONLINE);
        }
        /// <summary>
        /// This method parses a server info response from a game server.
        /// </summary>
        /// <param name="Parser">Supplies the message parser context.</param>
        /// <param name="Sender">Supplies the game server address.</param>
        /// <param name="Socket">Supplies the associated socket descriptor
        /// upon which the message was received.</param>
        private void OnRecvServerInfoResponse(ExoParseBuffer Parser, IPEndPoint Sender, SocketInfo Socket)
        {
            UInt16 DataPort;
            Byte Reserved; // 0xFC
            Byte HasPlayerPassword;
            Byte MinLevel;
            Byte MaxLevel;
            Byte ActivePlayers;
            Byte MaximumPlayers;
            Byte IsLocalVault;
            Byte PVPLevel;
            Byte IsPlayerPauseAllowed;
            Byte IsOnePartyOnly;
            Byte IsELC;
            Byte HasILR;
            Byte ExpansionsMask;
            string ModuleName;
            string BuildNumber;
            ServerInfo Info = new ServerInfo();

            if (!Parser.ReadWORD(out DataPort))
                return;
            if (!Parser.ReadBYTE(out Reserved))
                return;

            if (Mode == GameMode.NWN2)
            {
                if (Reserved != 0xFC)
                    return;
            }
            else
            {
                if (Reserved != 0xFD)
                    return;
            }

            if (!Parser.ReadBYTE(out HasPlayerPassword))
                return;
            if (!Parser.ReadBYTE(out MinLevel))
                return;
            if (!Parser.ReadBYTE(out MaxLevel))
                return;
            if (!Parser.ReadBYTE(out ActivePlayers))
                return;
            if (!Parser.ReadBYTE(out MaximumPlayers))
                return;
            if (!Parser.ReadBYTE(out IsLocalVault))
                return;
            if (!Parser.ReadBYTE(out PVPLevel))
                return;
            if (!Parser.ReadBYTE(out IsPlayerPauseAllowed))
                return;
            if (!Parser.ReadBYTE(out IsOnePartyOnly))
                return;
            if (!Parser.ReadBYTE(out IsELC))
                return;
            if (!Parser.ReadBYTE(out HasILR))
                return;
            if (!Parser.ReadBYTE(out ExpansionsMask))
                return;
            if (!Parser.ReadSmallString(out ModuleName))
                return;

            if (Mode == GameMode.NWN2)
            {
                if (!Parser.ReadSmallString(out BuildNumber))
                    return;
            }
            else
            {
                BuildNumber = "0";
            }

            try
            {
                Info.BuildNumber = Convert.ToUInt16(BuildNumber);
            }
            catch
            {
                Info.BuildNumber = 0;
            }

            //
            // Query for whether the sender is blacklisted and drop the message
            // if so.
            //

            BlacklistLookup Lookup = new BlacklistLookup();

            Lookup.ServerAddress = Sender;
            Lookup.ModuleName = ModuleName;

            if (ServerTracker.IsBlacklisted(Lookup))
                return;

            Info.HasPlayerPassword = (HasPlayerPassword != 0);
            Info.MinLevel = MinLevel;
            Info.MaxLevel = MaxLevel;
            Info.ActivePlayers = ActivePlayers;
            Info.MaximumPlayers = MaximumPlayers;
            Info.IsLocalVault = (IsLocalVault != 0);
            Info.PVPLevel = PVPLevel;
            Info.IsPlayerPauseAllowed = (IsPlayerPauseAllowed != 0);
            Info.IsOnePartyOnly = (IsOnePartyOnly != 0);
            Info.IsELC = (IsELC != 0);
            Info.HasILR = (HasILR != 0);
            Info.ExpansionsMask = ExpansionsMask;
            Info.ModuleName = ModuleName;

            //
            // Look up the server and update the current server information.
            // Since the BNXR reply is used to differentiate between broken
            // NATs and endpoints with multiple servers on the same IP address,
            // carefully check for whether a duplicate server record exists on
            // the server internal port before creating a new server record.
            //

            NWGameServer Server;

            Server = ServerTracker.LookupServerByAddress(Sender);

            if (Mode != GameMode.NWN2)
                Info.BuildNumber = Server.BuildNumber;

            Server.OnServerInfoUpdate(Info);

            if (DataPort == (UInt16)Sender.Port)
            {
                //
                // Both internal and external ports match; the sender is not
                // likely behind a NAT.  No action is necessary behind the
                // creation of the server record above.
                //
            }
            else
            {
                NWGameServer ServerInternal;
                IPEndPoint InternalAddress = new IPEndPoint(Sender.Address, (int)DataPort);
                
                ServerInternal = ServerTracker.LookupServerByAddress(InternalAddress, false);

                if (ServerInternal == null)
                {
                    //
                    // No record of a server existing at the internal address
                    // is yet known.  Proceed to create the server record at
                    // the external address (as was already performed above).
                    //
                }
                else
                {
                    //
                    // A record exists for both internal and external
                    // addresses for the server.  If the configuration values
                    // between both servers are the same, then mark the
                    // external address version as offline and prefer the
                    // internal server address as authoritative (since it must
                    // be globally reachable for a response to have been
                    // received).
                    //

                    if (ServerInternal.CheckForNATDuplicate(Server))
                    {
                        Logger.Log(LogLevel.Normal, "NWMasterServer.OnRecvServerInfoResponse(): Removing NAT duplicate server {0} in preference of server {1}.", Sender, InternalAddress);
                        return;
                    }
                }
            }

            Logger.Log(LogLevel.Verbose, "NWMasterServer.OnRecvServerInfoResponse(): Server {0} has {1}/{2} players ({3}).", Sender, Info.ActivePlayers, Info.MaximumPlayers,
                 Info.ModuleName);
        }