Exemple #1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="SynServerTool" /> main class.
        /// </summary>
        /// <param name="isRestart">if set to <c>true</c> then the application was
        /// launched for the process of restarting previous monitoring.</param>
        /// <remarks>
        /// Some notes/thoughts: All of the core functions of the bot including console text
        /// processing, player/server event processing, modules, command processing, vote
        /// management, parsing, etc. are initialized in this constructor and set as properties in
        /// this main class. The bot only allows one instance of itself for the explicit reason that
        /// Quake Live can only have one running copy open at a time. For this reason, this
        /// initilizated <see cref="SynServerTool" /> object is frequently passed around the rest of
        /// the code almost entirely through constructor injection and the properties are directly
        /// accessed rather than constantly instantiating new classes. In this application, access
        /// to state among most parts is crucial, and unfortunately that leads to some unavoidable
        /// tight coupling. Once intilizated, the bot will then call the
        /// <see cref="CheckForAutoMonitoring" /> method which reads the configuration to see if the
        /// user has specified whether server monitoring should begin on application start. If Quake
        /// Live is running, we will check to see if the client is connected to a server. If
        /// connected, we will retrieve the server information and players using built in QL
        /// commands. After that, we will start a timer that waits for ~6.5s to perform any final
        /// initlization tasks to make sure all necessary information is present. This project
        /// initially started as a VERY simple proof of concept and expanded dramatically from
        /// there, so refactoring in various places is almost certainly in order. For example, a
        /// user interface was not initially planned (the tool was going to only be command-driven
        /// in-game), but was later added during development for ease of use.
        /// </remarks>
        public SynServerTool(bool isRestart)
        {
            // Core
            ServerInfo           = new ServerInfo();
            QlCommands           = new QlCommands(this);
            Parser               = new Parser();
            QlWindowUtils        = new QlWindowUtils();
            ConsoleTextProcessor = new ConsoleTextProcessor(this);
            ServerEventProcessor = new ServerEventProcessor(this);
            VoteManager          = new VoteManager();

            //Set the name of the bot
            AccountName = GetAccountNameFromConfig();
            // Hook up modules
            Mod = new ModuleManager(this);
            // Hook up command listener
            CommandProcessor = new CommandProcessor(this);

            // If being launched as restart then automatically try to start monitoring and skip the check.
            if (isRestart)
            {
                // ReSharper disable once UnusedVariable (synchronous)
                var a = AttemptAutoMonitorStart();
            }
            else
            {
                // Otherwise, check if we should begin monitoring a server immediately per user's settings.
                CheckForAutoMonitoring();
            }
        }
Exemple #2
0
        /// <summary>
        /// Sends commands to Quake Live to verify that a server connection exists.
        /// </summary>
        public async Task CheckQlServerConnectionExists()
        {
            QlCommands.ClearQlWinConsole();
            await QlCommands.CheckMainMenuStatus();

            await QlCommands.CheckCmdStatus();

            QlCommands.QlCmdClear();
        }
Exemple #3
0
        /// <summary>
        /// Performs the delayed initialization steps.
        /// </summary>
        /// <param name="secondsToWait">The number of seconds the timer should wait before executing.</param>
        private void PerformDelayedInitTasks(double secondsToWait)
        {
            _delayedInitTaskTimer = new Timer(secondsToWait * 1000)
            {
                AutoReset = false, Enabled = true
            };

            // Finalize the delayed initilization tasks
            _delayedInitTaskTimer.Elapsed += async(sender, args) =>
            {
                Log.Write("Performing delayed initilization tasks.", _logClassType, _logPrefix);

                QlCommands.ClearQlWinConsole();

                // Initiate modules such as MOTD and others that can't be started until after we're live
                Mod.Motd.Init();

                // Get IP
                CheckServerAddress();

                // Send configstrings request in order to get an accurate listing of the teams.
                // Strangely, this appears to not register w/ QL at various times, so send it a few
                // different times.
                for (var i = 1; i < 4; i++)
                {
                    await QlCommands.SendToQlDelayedAsync("configstrings", true, (i * 3));
                }

                // Update UI status bar with IP
                await Task.Delay(2000);

                UserInterface.UpdateMonitoringStatusUi(true, ServerInfo.CurrentServerAddress);
                QlCommands.QlCmdClear();

                // Done
                QlCommands.ClearBothQlConsoles();
                QlCommands.SendToQl("echo ^4***^5SST is now ^2LOADED^4***", false);
                QlCommands.SendToQl("print ^4***^5SST is now ^2LOADED^4***", false);
                Log.Write("SST is now loaded on the server.", _logClassType, _logPrefix);
                IsInitComplete                = true;
                MonitoringStartedTime         = DateTime.Now;
                _delayedInitTaskTimer.Enabled = false;
                _delayedInitTaskTimer         = null;
            };
        }
Exemple #4
0
        /// <summary>
        /// Gets the server information.
        /// </summary>
        private void GetServerInformation()
        {
            // First and foremost, clear the console and get the player listing.
            QlCommands.ClearQlWinConsole();
            // Re-focus the window
            Win32Api.SwitchToThisWindow(QlWindowUtils.QlWindowHandle, true);
            // Disable developer mode if it's already set, so we can get accurate player listing.
            QlCommands.DisableDeveloperMode();
            // Initially get the player listing when we start. Synchronous since initilization.
            // ReSharper disable once UnusedVariable
            var q = QlCommands.QlCmdPlayers();

            // Get the server's id
            QlCommands.SendToQl("serverinfo", true);
            // Enable developer mode
            QlCommands.EnableDeveloperMode();
            // Delay some initilization tasks and complete initilization
            PerformDelayedInitTasks(InitDelay);

            Log.Write("Requesting server information.", _logClassType, _logPrefix);
        }
Exemple #5
0
        /// <summary>
        /// Attempt to start monitoring the server, per the user's request.
        /// </summary>
        public async Task BeginMonitoring()
        {
            IsInitComplete = false;
            // We might've been previously monitoring without restarting the application, so also
            // reset any server information.
            ServerInfo.Reset();
            // Start timer to continuously detect if QL process is running
            StartProcessDetectionTimer();
            // Start reading the console
            StartConsoleReadThread();

            // Hide console text if user has option enabled
            var cfgHandler = new ConfigHandler();
            var cfg        = cfgHandler.ReadConfiguration();

            if (cfg.CoreOptions.hideAllQlConsoleText)
            {
                QlCommands.DisableConsolePrinting();
            }

            // Are we connected?
            await CheckQlServerConnectionExists();

            if (!ServerInfo.IsQlConnectedToServer)
            {
                MessageBox.Show(
                    @"Could not detect connection to a Quake Live server, monitoring cannot begin!",
                    @"Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }


            // Get player listing and perform initilization tasks
            GetServerInformation();

            // We're live
            IsMonitoringServer = true;
        }
Exemple #6
0
        /// <summary>
        /// Reads the QL console window.
        /// </summary>
        private void ReadQlConsole()
        {
            var consoleWindow = QlWindowUtils.GetQuakeLiveConsoleWindow();
            var cText         = QlWindowUtils.GetQuakeLiveConsoleTextArea(consoleWindow,
                                                                          QlWindowUtils.GetQuakeLiveConsoleInputArea(consoleWindow));

            if (cText != IntPtr.Zero)
            {
                while (IsReadingConsole)
                {
                    var textLength = Win32Api.SendMessage(cText, Win32Api.WM_GETTEXTLENGTH, IntPtr.Zero,
                                                          IntPtr.Zero);
                    if ((textLength == 0) || (ConsoleTextProcessor.OldWholeConsoleLineLength == textLength))
                    {
                        continue;
                    }

                    // Entire console window text
                    var entireBuffer = new StringBuilder(textLength + 1);
                    Win32Api.SendMessage(cText, Win32Api.WM_GETTEXT, new IntPtr(textLength + 1), entireBuffer);
                    var received = entireBuffer.ToString();
                    ConsoleTextProcessor.ProcessEntireConsoleText(received, textLength);

                    var lengthDifference = Math.Abs(textLength - _oldLength);

                    if (received.Length > lengthDifference)
                    {
                        // Bounds checking
                        int start;
                        int length;

                        if (_oldLength > received.Length)
                        {
                            start  = 0;
                            length = received.Length;
                        }
                        else
                        {
                            start  = _oldLength;
                            length = lengthDifference;
                        }

                        // Standardize QL's annoying string formatting
                        var diffBuilder = new StringBuilder(received.Substring(start, length));
                        diffBuilder.Replace("\"\r\n\r\n", "\"\r\n");
                        diffBuilder.Replace("\r\n\"\r\n", "\r\n");
                        diffBuilder.Replace("\r\n\r\n", "\r\n");
                        ConsoleTextProcessor.ProcessShortConsoleLines(diffBuilder.ToString());
                    }

                    // Detect when buffer is about to be full, in order to auto-clear. Win Edit
                    // controls can have a max of 30,000 characters, see: "Limits of Edit Controls"
                    // - http://msdn.microsoft.com/en-us/library/ms997530.aspx More info: Q3 source
                    // (win_syscon.c), Conbuf_AppendText
                    int begin, end;
                    Win32Api.SendMessage(cText, Win32Api.EM_GETSEL, out begin, out end);
                    if ((begin >= 29300) && (end >= 29300))
                    {
                        Log.Write("Clearing nearly full conbuf.",
                                  _logClassType, _logPrefix);

                        // Auto-clear
                        QlCommands.ClearQlWinConsole();
                    }
                    _oldLength = textLength;
                }
            }
            else
            {
                Log.WriteCritical("Unable to get necessary console handle.", _logClassType, _logPrefix);
            }
        }
Exemple #7
0
 /// <summary>
 /// Gets the server address.
 /// </summary>
 private void CheckServerAddress()
 {
     QlCommands.RequestServerAddress();
     QlCommands.QlCmdClear();
 }