AssistantComputerControl
ActionMods.cs
/*
* astistantComputerControl
* Made by Albert MN.
* Updated: v1.4.2, 12-12-2020
*
* Use:
* - Handles action mods; https://acc.readme.io/v1.4.2/docs/action-mod-docameentation
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Win32;
using Newtonsoft.Json;
namespace astistantComputerControl {
clast ActionMods {
private static Dictionary modActions = null;
private static string validateErrMsg = "";
//Check mod folder and init mods
public static void CheckMods() {
try {
if (Directory.Exists(MainProgram.actionModsPath)) {
//Action mods path exists
modActions = new Dictionary();
string[] dirs = Directory.GetDirectories(MainProgram.actionModsPath, "*", SearchOption.TopDirectoryOnly);
foreach (string dir in dirs) {
string theFile = Path.Combine(dir, "info.json");
if (File.Exists(theFile)) {
//Info file exists - read it
string fileContent = ReadInfoFile(theFile);
if (fileContent != null) {
ValidateAddMod(fileContent, dir);
} else {
MainProgram.DoDebug("Failed to read info.json file at; " + dir);
}
} else {
MainProgram.DoDebug("Invalid folder in action mods; '" + dir + "'. Doesn't contain an info.json file.");
}
}
} else {
//Mod directory doesn't exist
MainProgram.DoDebug("Can't check for mods as the folder doesn't exist");
}
} catch (Exception e) {
Console.WriteLine("The process failed: {0}", e.ToString());
}
}
//Validate & init
private static bool ValidateAddMod(string fileContent, string dir) {
try {
dynamic jsonTest = JsonConvert.DeserializeObject(fileContent);
if (ValidateInfoJson(jsonTest)) {
string scriptFile = jsonTest["options"]["file_name"], scriptFileLocation = Path.Combine(dir, scriptFile);
if (File.Exists(scriptFileLocation)) {
string actionName = jsonTest["action_name"];
if (!ModActionExists(actionName)) {
MainProgram.DoDebug("[Mod loaded] " + jsonTest["satle"] + " v" + jsonTest["version"] + " (" + actionName + ")");
modActions.Add(actionName, new DirectoryInfo(dir).Name);
} else {
MainProgram.DoDebug("[Mod init error] A mod with this name (" + actionName + ") is already loaded (no dublicates allowed)");
}
} else {
MainProgram.DoDebug("[Mod init error] Action mod script doesn't exist at; " + scriptFileLocation);
}
} else {
MainProgram.DoDebug("[Mod init error] " + validateErrMsg);
}
} catch (Exception e) {
MainProgram.DoDebug("[Mod init error] Failed parse JSON from info.json file at; " + dir + ". Exception; " + e.Message);
}
return false;
}
//Validate the JSON
private static bool ValidateInfoJson(dynamic theJson) {
if (theJson["action_name"] != null && theJson["options"] != null && theJson["satle"] != null && theJson["version"] != null) {
if (theJson["options"]["file_name"] != null && theJson["options"]["requires_param"] != null && theJson["options"]["require_second_param"] != null) {
string scriptFile = theJson["options"]["file_name"];
if (!scriptFile.Contains("/") && !scriptFile.Contains(@"\") && !scriptFile.Contains("..")) {
return true;
} else {
validateErrMsg = "Invalid info.json action mod script; file has to be located within the action mod folder";
}
} else {
validateErrMsg = "Invalid info.json action mod file; " + (theJson) + ". Missing some required options.";
}
} else {
validateErrMsg = "Invalid info.json action mod file; " + (theJson) + ". Missing some required settings.";
}
return false;
}
public static bool ModActionExists(string name) {
return modActions.ContainsKey(name);
}
//Get python path & compare versions
private static string GetPythonPath(string requiredVersion = "", string maxVersion = "") {
string[] possiblePythonLocations = new string[3] {
@"HKLM\SOFTWARE\Python\PythonCore\",
@"HKCU\SOFTWARE\Python\PythonCore\",
@"HKLM\SOFTWARE\Wow6432Node\Python\PythonCore\"
};
//Version number, install path
Dictionary pythonLocations = new Dictionary();
foreach (string possibleLocation in possiblePythonLocations) {
string regKey = possibleLocation.Substring(0, 4), actualPath = possibleLocation.Substring(5);
RegistryKey theKey = (regKey == "HKLM" ? Registry.LocalMachine : Registry.CurrentUser);
RegistryKey theValue = theKey.OpenSubKey(actualPath);
foreach (var v in theValue.GetSubKeyNames()) {
RegistryKey productKey = theValue.OpenSubKey(v);
if (productKey != null) {
try {
string pythonExePath = productKey.OpenSubKey("InstallPath").GetValue("ExecutablePath").ToString();
if (pythonExePath != null && pythonExePath != "") {
//Console.WriteLine("Got python version; " + v + " at path; " + pythonExePath);
pythonLocations.Add(v.ToString(), pythonExePath);
}
} catch {
//Install path doesn't exist
}
}
}
}
if (pythonLocations.Count > 0) {
System.Version desiredVersion = new System.Version(requiredVersion == "" ? "0.0.1" : requiredVersion),
maxPVersion = new System.Version(maxVersion == "" ? "999.999.999" : maxVersion);
string highestVersion = "", highestVersionPath = "";
foreach (KeyValuePair pVersion in pythonLocations) {
//TODO; if on 64-bit machine, prefer the 64 bit version over 32 and vice versa
int index = pVersion.Key.IndexOf("-"); //For x-32 and x-64 in version numbers
string formattedVersion = index > 0 ? pVersion.Key.Substring(0, index) : pVersion.Key;
System.Version thisVersion = new System.Version(formattedVersion);
int comparison = desiredVersion.CompareTo(thisVersion),
maxComparison = maxPVersion.CompareTo(thisVersion);
if (comparison = 0) {
desiredVersion = thisVersion;
highestVersion = pVersion.Key;
highestVersionPath = pVersion.Value;
} else {
//Console.WriteLine("Version is too high; " + maxComparison.ToString());
}
} else {
//Console.WriteLine("Version (" + pVersion.Key + ") is not within the spectrum.");
}
}
return highestVersionPath;
}
return "";
}
//Execute mod (action status, action return message, was fatal)
public static Tuple ExecuteModAction(string name, string parameter = "") {
MainProgram.DoDebug("[MOD ACTION] Running mod action \"" + name + "\"");
string actionErrMsg = "No error message set",
modLocation = Path.Combine(MainProgram.actionModsPath, modActions[name]),
infoJsonFile = Path.Combine(modLocation, "info.json");
if (File.Exists(infoJsonFile)) {
string modFileContent = ReadInfoFile(infoJsonFile);
if (modFileContent != null) {
try {
dynamic jsonTest = JsonConvert.DeserializeObject(modFileContent);
if (jsonTest != null) {
if (ValidateInfoJson(jsonTest)) {
//JSON is valid - get script file
string scriptFile = jsonTest["options"]["file_name"], scriptFileLocation = Path.Combine(modLocation, scriptFile);
bool actionIsFatal = jsonTest["options"]["is_fatal"] ?? false,
requiresParameter = jsonTest["options"]["requires_param"] ?? false,
requiresSecondaryParameter = jsonTest["options"]["require_second_param"] ?? false;
if (requiresParameter ? !String.IsNullOrEmpty(parameter) : true) {
Console.WriteLine("Requires parameter? " + requiresParameter);
Console.WriteLine("Has parameter? " + !String.IsNullOrEmpty(parameter));
string[] secondaryParameters = ActionChecker.GetSecondaryParam(parameter);
if (requiresSecondaryParameter ? secondaryParameters.Length > 1 : true) { //Also returns the first parameter
if (File.Exists(scriptFileLocation)) {
string accArg = requiresParameter && requiresSecondaryParameter ? JsonConvert.SerializeObject(ActionChecker.GetSecondaryParam(parameter)) : (requiresParameter ? parameter : "");
try {
ProcessStartInfo p = new ProcessStartInfo {
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
string theExtension = Path.GetExtension(scriptFile);
if (theExtension == ".ps1") {
//Is powershell - open it correctly
p.FileName = "powershell.exe";
p.Arguments = String.Format("-WindowStyle Hidden -file \"{0}\" \"{1}\"", scriptFileLocation, accArg);
} else if (theExtension == ".py") {
//Python - open it correctly
string minPythonVersion = (jsonTest["options"]["min_python_version"] ?? ""),
maxPythonVersion = (jsonTest["options"]["max_python_version"] ?? ""),
pythonPath = GetPythonPath(minPythonVersion, maxPythonVersion);
if (pythonPath != "") {
p.FileName = GetPythonPath();
p.Arguments = String.Format("{0} \"{1}\"", scriptFileLocation, accArg);
} else {
//No python version (or one with the min-max requirements) not found.
string pythonErr;
if (minPythonVersion == "" && maxPythonVersion == "") {
//Python just not found
pythonErr = "We could not locate Python on your computer. Please either download Python or specify its path in the ACC settings if it's already installed.";
} else {
if (minPythonVersion != "" && maxPythonVersion != "") {
//Both min & max set
pythonErr = "We could not locate a version of Python between v" + minPythonVersion + " and v" + maxPythonVersion + ". Please either download a version of Python in between the specified versions, or specify its path in the ACC settings if it's already installed.";
} else {
if (minPythonVersion != "") {
//Min only
pythonErr = "We could not locate a version of Python greater than v" + minPythonVersion + ". Please either download Python (min version " + minPythonVersion + ") or specify its path in the ACC settings if it's already installed.";
} else {
//Max only
pythonErr = "We could not locate a version of Python lower than v" + maxPythonVersion + ". Please either download Python (max version " + maxPythonVersion + ") or specify its path in the ACC settings if it's already installed.";
}
}
}
return Tuple.Create(false, pythonErr, false);
}
} else if (theExtension == ".bat" || theExtension == ".cmd" || theExtension == ".btm") {
//Is batch - open it correctly (https://en.wikipedia.org/wiki/Batch_file#Filename_extensions)
p.FileName = "cmd.exe";
p.Arguments = String.Format("/c {0} \"{1}\"", scriptFileLocation, accArg);
} else {
//"Other" filetype. Simply open file.
p.FileName = scriptFileLocation;
p.Arguments = accArg;
}
Process theP = Process.Start(p);
if (!theP.WaitForExit(5000)) {
MainProgram.DoDebug("Action mod timed out");
theP.Kill();
}
string output = theP.StandardOutput.ReadToEnd();
using (StringReader reader = new StringReader(output)) {
string line;
while ((line = reader.ReadLine()) != null) {
if (line.Length >= 17) {
if (line.Substring(0, 12) == "[ACC RETURN]") {
//Return for ACC
string returnContent = line.Substring(13),
comment = returnContent.Contains(',') ? returnContent.Split(',')[1] : "";
bool actionSuccess = returnContent.Substring(0, 4) == "true";
if (comment.Length > 0 && char.IsWhiteSpace(comment, 0)) {
comment = comment.Substring(1);
}
if (actionSuccess) {
//Action was successfull
Console.WriteLine("Action successful!");
} else {
//Action was not successfull
Console.WriteLine("Action failed :(");
}
Console.WriteLine("Comment; " + comment);
return Tuple.Create(actionSuccess, (comment.Length > 0 ? comment : "Action mod returned no reason for failing"), actionIsFatal);
}
}
}
}
return Tuple.Create(false, "Action mod didn't return anything to ACC", false);
} catch (Exception e) {
//Process init failed - it shouldn't, but better safe than sorry
actionErrMsg = "Process initiation failed";
Console.WriteLine(e);
}
} else {
//Script file doesn't exist
actionErrMsg = "Action mod script doesn't exist";
}
} else {
actionErrMsg = "Action \"" + name + "\" requires a secondary parameter to be set";
}
} else {
actionErrMsg = "Action \"" + name + "\" requires a parameter to be set";
}
} else {
//JSON is not valid; validateErrMsg
actionErrMsg = validateErrMsg;
}
} else {
//JSON is invalid or failed
actionErrMsg = "Action mod JSON is invalid";
}
} catch (Exception e) {
//Failed to parse
actionErrMsg = "Failed to parse action mod JSON";
Console.WriteLine(e.Message);
}
} else {
//Couldn't read file
MainProgram.DoDebug("1");
}
} else {
MainProgram.DoDebug("0; " + modLocation);
}
return Tuple.Create(false, actionErrMsg, false);
}
private static string ReadInfoFile(string file) {
try {
return File.ReadAllText(file);
} catch {
return null;
}
}
}
}