/* * 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); } } }