Thursday, September 19, 2024 11:59:00 PM
> settings

Customize


Authenticate

> Function.cs
/*
 * Bryan
 * Exile Server Manager
 */
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace esm
{
    public partial class ESM
    {
        private static bool initialized = false;
        private static string Key { get; set; }
        private static string Website { get; set; }


        /// <summary>
        /// Whitelisted commands for the DLL
        /// </summary>
        private static List<string> COMMANDS = new List<string>
        {
            "version",
            "initialize",
            "request",
            "updateplayers"
        };


        /// <summary>
        /// A list of requests from the server
        /// </summary>
        private static List<List<string>> requests = new List<List<string>>();


        /// <summary>
        ///
        /// </summary>
        private static Regex RequestRegex = new Regex(@"(\w*),\[((?:[\w\d,]*))\]", RegexOptions.Compiled);


        /// <summary>
        /// Our DLL Version
        /// </summary>
        private const string DLL_VERSION = "1.0.0";


        /// <summary>
        /// The workhorse of the DLL
        /// </summary>
        /// <param name="functionObject"></param>
        /// <returns></returns>
        public static string ParseFunction(ArmACommand functionObject)
        {
            ArmAReturn output = new ArmAReturn();

            try
            {
                // Split the function
                string function = functionObject.Function.ToLower();

                // We failed!
                if (function == "failed")
                    throw new Exception(functionObject.Parameters[0]);

                // We don't contain the function? Throw it!
                if (!COMMANDS.Contains(function)) throw new InvalidFunctionException(function);

                /*
                 * Massive switch to handle the commands
                 */
                switch (function)
                {
                    /*
                     * Initializes the DLL and updates the server with important information.
                     */
                    case "initialize":
                        if (initialized) throw new Exception("ESM has already been initalized! Maybe server loop?");

                        /*
                         * callExtension calls are blocking, but are way faster than normal engine commands.
                         * However...
                         * The second the DLL is called, to the time it gets it's response back, the engine is "on hold" (freezes).
                         * Since initialize gets called during postInit (can't do preInit because missionConfigFile is non-existant to the server until preInit, #ArmaThings), I don't "have an issue" with doing this synchronous.
                         * Both pre and postInit events in Arma are scheduled events, but are blocking towards the rest of the EHs that have to fire, so if this ends up taking a long time, I'll just make it async.
                         */

                        // Save the website url for use later
                        Website = functionObject.Parameters[2];
                        Key = functionObject.Parameters[0];

                        // The package we are going to send to the server
                        NameValueCollection data = new NameValueCollection
                        {
                            ["key"] = functionObject.Parameters[0],
                            ["name"] = functionObject.Parameters[1],
                            ["poptab_amount"] = functionObject.Parameters[3],
                            ["ttl"] = functionObject.Parameters[4],
                            ["restart_hour"] = functionObject.Parameters[5],
                            ["restart_minute"] = functionObject.Parameters[6]
                        };

                        // Remove the bleh that we don't need anymore
                        functionObject.Parameters.RemoveRange(0, 7);

                        // Add the price, radius, and objects
                        int level = 1;
                        for (int i = 0; i < functionObject.Parameters.Count - 1; i += 3)
                        {
                            data.Add($"level_{level}_price", functionObject.Parameters[i]);
                            data.Add($"level_{level}_radius", functionObject.Parameters[i+1]);
                            data.Add($"level_{level}_objects", functionObject.Parameters[i+2]);
                            level++;
                        }

                        // Send the request to the server
                        using (WebClient wb = new WebClient())
                        {
                            byte[] response = wb.UploadValues($"{Website}/initializeServer.php", "POST", data);
                            output.Response = Encoding.ASCII.GetString(response) == "success";
                        }

                        // Lock initialize so we don't try to do shit twice.
                        initialized = true;

                        break;

                    /*
                     * Returns the DLL version, kinda pointless, but fun.
                     */
                    case "version":
                        output.Response = true;
                        output.Returned.Add(DLL_VERSION);
                        break;

                    /*
                     *  Requests the website to send any pending processes that need done. It will queue requests so when there are requests, it doesn't consistantly ping the server
                     */
                    case "request":
                        if (!initialized) throw new Exception("Cannot request until ESM has been initialized. Did postInit not fire?");

                        string strResponse = "";

                        // We are low on requests, request the server for more!
                        if (requests.Count == 0)
                        {
                            // Send the request to the server
                            using (WebClient wb = new WebClient())
                            {
                                byte[] response = wb.UploadValues($"{Website}/request.php", "POST", new NameValueCollection() { ["key"] = Key });
                                strResponse = Encoding.ASCII.GetString(response);
                            }

                            // Make sure we have a response
                            if (strResponse == "")
                            {
                                output.Response = false;
                                output.Returned.Add("Received nothing from server");
                                break;
                            }
                            else if (strResponse == "0")
                            {
                                output.Response = false;
                                output.Returned.Add("Failed to query");
                                break;
                            }
                            else if (strResponse == "1")
                            {
                                output.Response = false;
                                output.Returned.Add("Server key is not whitelisted in ESMs config.php");
                                break;
                            }

                            // Split something like "function1,[],function2,[param1,param2,param3]" into something a little more managable
                            foreach (Match ItemMatch in RequestRegex.Matches(strResponse))
                            {
                                requests.Add(new List<string>()
                                {
                                    ItemMatch.Groups[1].Value,
                                    ItemMatch.Groups[2].Value
                                });

                                //output.Returned.Add($"Matched 1: {ItemMatch.Groups[1].Value} | 2: {ItemMatch.Groups[2].Value}");
                            }
                        }

                        // We don't have any requests, just exit immediately
                        if (requests.Count == 0)
                        {
                            output.Response = false;
                            output.Returned.Add("No requests");
                            break;
                        }

                        // We have a request, send it to the server ASAP
                        output.Response = true;

                        // Function
                        output.Returned.Add(requests[0][0]);

                        // Parameters
                        output.Returned.Add("[");

                        // Make sure we have a string valid to add
                        if (requests[0][1] != "")
                        {
                            foreach(string par in requests[0][1].Split(','))
                                output.Returned.Add(par);
                        }

                        // Close the array
                        output.Returned.Add("]");

                        // Remove the request
                        requests.RemoveAt(0);

                        break;

                    /*
                     * Updates the online players from the server to the website
                     */
                    case "updateplayers":
                        if (!initialized) throw new Exception("Cannot request until ESM has been initialized. Did postInit not fire?");

                        // Thread this so we just immediately get a response back
                        Task.Factory.StartNew(() =>
                        {
                            // create the data
                            NameValueCollection playerData = new NameValueCollection
                            {
                                ["key"] = Key
                            };

                            if (functionObject.Parameters.Count != 0)
                            {
                                // Add all the players
                                int count = 0;
                                for (int i = 0; i < functionObject.Parameters.Count - 1; i += 2)
                                {
                                    playerData.Add($"player_{count}_name", functionObject.Parameters[i]);
                                    playerData.Add($"player_{count}_uid", functionObject.Parameters[i + 1]);
                                    count++;
                                }

                                // Send the request to the server
                                using (WebClient wb = new WebClient())
                                {
                                    byte[] response = wb.UploadValues($"{Website}/updatePlayers.php", "POST", playerData);
                                }
                            }
                        });

                        output.Response = true;

                        break;

                    /*
                     *  We don't want to do anything here
                     */
                    default:
                        throw new InvalidFunctionException();
                }
            }
            catch (InvalidFunctionException e)
            {
                output.Response = false;
                output.Returned.Add($"Invalid function: {e.Message}");
            }
            catch (InvalidFunctionCountException e)
            {
                output.Response = false;
                output.Returned.Add($"Invalid Count of Parameters. {e.Message}");
            }
            catch (Exception e)
            {
                output.Response = false;
                output.Returned.Add(e.Message);
            }

            return Converter.Serialize(output);
        }
    }
}
All opinions represented herein are my own
- © 2024 itsthedevman
- build 3c15a1b