internal AScriptHandler(OMOD omod, string script, OMODScriptSettings settings, string?extractionFolder) { ScriptSettings = settings; OMOD = omod; Script = script; var guid = Guid.NewGuid(); ExtractionFolder = extractionFolder ?? Path.Combine(Path.GetTempPath(), "OMODFramework", guid.ToString("D")); DataFolder = Path.Combine(ExtractionFolder, "data"); PluginsFolder = Path.Combine(ExtractionFolder, "plugins"); if (!settings.DryRun) { Directory.CreateDirectory(DataFolder); Directory.CreateDirectory(PluginsFolder); omod.ExtractFilesParallel(DataFolder, 4); if (omod.HasEntryFile(OMODEntryFileType.Plugins)) { omod.ExtractFiles(false, PluginsFolder); } } ScriptReturnData = new ScriptReturnData(DataFolder, PluginsFolder); ScriptFunctions = new ScriptFunctions(ScriptSettings, omod, ScriptReturnData); }
/// <summary> /// Executes the script /// </summary> public ScriptReturnData ExecuteScript() { srd = new ScriptReturnData(); var sf = new ScriptFunctions( srd, DataPath, PluginsPath, OMOD.GetFramework(), ScriptRunnerFunctions); switch (type) { case ScriptType.obmmScript: return(OBMMScriptHandler.Execute( OMOD.GetFramework(), script, DataPath, PluginsPath, ScriptRunnerFunctions)); case ScriptType.Python: throw new NotImplementedException(); case ScriptType.cSharp: DotNetScriptHandler.ExecuteCS(script, sf); break; case ScriptType.vb: DotNetScriptHandler.ExecuteVB(script, sf); break; } return(srd); }
internal DotNetScriptFunctions(ScriptReturnData srd, string dataFilesPath, string pluginsPath, ref SharedFunctionsHandler handler) { _srd = srd; _dataFiles = dataFilesPath; _plugins = pluginsPath; _handler = handler; }
internal ScriptFunctions(ScriptReturnData srd, string dataFilesPath, string pluginsPath, Framework _f, IScriptRunnerFunctions scriptRunnerFunctions) { f = _f; //Warn = scriptRunnerFunctions.Warn; IDialogYesNo = scriptRunnerFunctions.DialogYesNo; ExistsFile = scriptRunnerFunctions.ExistsFile; GetFileVersion = scriptRunnerFunctions.GetFileVersion; DialogSelect = scriptRunnerFunctions.DialogSelect; IMessage = scriptRunnerFunctions.Message; IDisplayImage = scriptRunnerFunctions.DisplayImage; IDisplayText = scriptRunnerFunctions.DisplayText; IInputString = scriptRunnerFunctions.InputString; IGetActiveESPNames = scriptRunnerFunctions.GetActiveESPNames; IGetFile = scriptRunnerFunctions.GetFileFromPath; IGetExistingESPNames = scriptRunnerFunctions.GetExistingESPNames; IUncheckESP = scriptRunnerFunctions.UncheckESP; IRegisterBSA = scriptRunnerFunctions.RegisterBSA; IUnregisterBSA = scriptRunnerFunctions.UnregisterBSA; IReadINI = scriptRunnerFunctions.ReadINI; IReadRendererInfo = scriptRunnerFunctions.ReadRendererInfo; this.srd = srd; DataFiles = dataFilesPath; Plugins = pluginsPath; }
internal static PluginFile GetPluginFile(this ScriptReturnData srd, string file, bool byInput = true) { var pluginFile = srd.PluginFiles.First(x => byInput ? x.Input.Name.Equals(file.MakePath(), StringComparison.OrdinalIgnoreCase) : x.Output.Equals(file.MakePath(), StringComparison.OrdinalIgnoreCase)); return(pluginFile); }
internal static ScriptReturnData ExecuteScript(string script, string dataPath, string pluginsPath, IScriptFunctions scriptFunctions) { Utils.Info("Starting script execution..."); if (string.IsNullOrWhiteSpace(script)) { Utils.Error("Script is empty! Returning empty ScriptReturnData"); return(new ScriptReturnData()); } ScriptType type; if ((byte)script[0] >= (byte)ScriptType.Count) { type = ScriptType.OBMMScript; } else { type = (ScriptType)script[0]; script = script.Substring(1); } Utils.Debug($"ScriptType is {type.ToString()}"); var handler = new SharedFunctionsHandler(type, ref scriptFunctions); var srd = new ScriptReturnData(); var dotNetScriptFunctions = new Lazy <DotNetScriptFunctions>(() => new DotNetScriptFunctions(srd, dataPath, pluginsPath, ref handler)).Value; switch (type) { case ScriptType.OBMMScript: return(OBMMScriptHandler.Execute(script, dataPath, pluginsPath, ref handler)); case ScriptType.Python: //TODO break; case ScriptType.CSharp: DotNetScriptHandler.ExecuteCS(script, ref dotNetScriptFunctions); break; case ScriptType.VB: DotNetScriptHandler.ExecuteVB(script, ref dotNetScriptFunctions); break; case ScriptType.Count: //Impossible Utils.Error("Impossible switch case triggered, how dafuq did this happen?"); break; default: //Impossible Utils.Error("Impossible switch case triggered, how dafuq did this happen?"); return(srd); } return(srd); }
internal static PluginFile GetOrAddPluginFile(this ScriptReturnData srd, string file, OMOD omod, bool byInput = true) { var filePath = file.MakePath(); var pluginFile = srd.PluginFiles.FirstOrDefault(x => byInput ? x.Input.Name.Equals(filePath, StringComparison.OrdinalIgnoreCase) : x.Output.Equals(filePath, StringComparison.OrdinalIgnoreCase)); if (pluginFile != null) { return(pluginFile); } var compressedFile = GetPluginFile(omod, filePath); pluginFile = new PluginFile(compressedFile); srd.PluginFiles.Add(pluginFile); return(pluginFile); }
static void Main(string[] args) { // BEFORE you checkout the example: // this example is not optimized, it is not intended to be used in production // this should be used for understanding how the Framework operates, what it // needs, what you can do with it and what it returns Framework f = new Framework(); // basic, do this or go home string temp = @"M:\Projects\omod\testDLL\temp"; string OutputDir = @"M:\Projects\omod\testDLL\output"; // check if the temp and output dir already exist and delete them if they do if (Directory.Exists(temp)) { DeleteDirectory(temp); } Directory.CreateDirectory(temp); if (Directory.Exists(OutputDir)) { DeleteDirectory(OutputDir); } Directory.CreateDirectory(OutputDir); f.SetOBMMVersion(1, 1, 12); // latest official obmm version use this unless you know what you're doing f.SetTempDirectory(temp); // setting the dll path is mostly used for debugging or if you execute the code from somewhere else // better safe than sorry just do this if f.SetDLLPath(@"M:\Projects\OMOD-Framework\OMOD-Framework\bin\Release\erri120.OMODFramework.dll"); // after everything is setup you can go ahead and grap the omod OMOD omod = new OMOD(@"M:\Projects\omod\testDLL\Robert Male Body Replacer v52 OMOD-40532-1.omod", ref f); // before you run the install script, extract the data files and plugins from the omod // ExtractDataFiles will always return something but ExtractPlugins can return null if there is no // plugins.crc in the OMOD string dataPath = omod.ExtractDataFiles(); //extracts all data files and returns the path to them string pluginsPath = omod.ExtractPlugins(); //extracts all plugins and returns the path to them // the interface IScriptRunnerFunctions should be implemented by something that you can pass on // as an argument, in this case I created an internal class called ScriptFunctions that implements // all functions from the interface ScriptFunctions a = new ScriptFunctions(); // the script runner can execute the script, return the script and/or script type ScriptRunner sr = new ScriptRunner(ref omod, a); //to get the type: //ScriptType scriptType = sr.GetScriptType(); //to get the entire script without the first byte: //String script = sr.GetScript(); // this will execute the script and return all information about what needs to be installed ScriptReturnData srd = sr.ExecuteScript(); // after the script executed go ahead and do whatever you want with the ScriptReturnData: // be sure to check if the installation is canceled or you will run into issues if (srd.CancelInstall) { Console.WriteLine("Installation canceled"); } // just for testing bool doPretty = true; if (doPretty) { // if you do not want the raw output but a more 'prettier' version, use this method // it will change the ScriptReturnData and will populate the InstallFiles list srd.Pretty(true, ref omod, ref pluginsPath, ref dataPath); // loop through the whole thing, do note the custom struct foreach (InstallFile file in srd.InstallFiles) { string s = Path.GetDirectoryName(file.InstallTo); if (!Directory.Exists(Path.Combine(OutputDir, s))) { Directory.CreateDirectory(Path.Combine(OutputDir, s)); } File.Move(file.InstallFrom, Path.Combine(OutputDir, file.InstallTo)); } } else { // in the following example I will create two lists, one for all data files and one for all // plugins that need to be installed // this may seem non-intuitive since the ScriptReturnData should return this list // the thing is that you have InstallAll, Install, Ignore and Copy operations // the install script in the omod decideds what is best for itself List <string> InstallPlugins = new List <string>(); List <string> InstallDataFiles = new List <string>(); // start by checking if you can install all plugins if (srd.InstallAllPlugins) { // simply get all plugin files from the omod and loop through them // the s.Contains is just a safety check foreach (string s in omod.GetPluginList()) { if (!s.Contains("\\")) { InstallPlugins.Add(s); } } } // if you can't install everything go and check the list called InstallPlugins // this list gets populated when InstallAllPlugins is false // the Framework comes with two utility functions that helps in creating the temp list: // strArrayContains and strArrayRemove foreach (string s in srd.InstallPlugins) { if (!Framework.strArrayContains(InstallPlugins, s)) { InstallPlugins.Add(s); } } // next up is removing all plugins that are set to be ignored: foreach (string s in srd.IgnorePlugins) { Framework.strArrayRemove(InstallPlugins, s); } // last is going through the CopyPlugins list // in case you ask why there is a CopyPlugins list and what is does: // (it makes more sense with data files but whatever) // if the omod has eg this folder structure: // // installfiles/ // Option1/ // Meshes/ // Textures/ // Option2/ // Meshes/ // Textures/ // this is nice for writing the installation script as you kan keep track of what option // has what files // Authors than call CopyPlugins/Data and move the files from the options folder to // the root folder: // // meshes/ // textures/ // installfiles/ // Option1/ // Meshes/ // Textures/ // Option2/ // Meshes/ // Textures/ foreach (ScriptCopyDataFile scd in srd.CopyPlugins) { // check if the file you want to copy actually exists if (!File.Exists(Path.Combine(pluginsPath, scd.CopyFrom))) { return; } else { // check if the mod author didnt make a mistake if (scd.CopyFrom != scd.CopyTo) { // unlikely but you never know if (File.Exists(Path.Combine(pluginsPath, scd.CopyTo))) { File.Delete(Path.Combine(pluginsPath, scd.CopyTo)); } File.Copy(Path.Combine(pluginsPath, scd.CopyFrom), Path.Combine(pluginsPath, scd.CopyTo)); } // important to add the file to the temp list or else it will not be installed if (!Framework.strArrayContains(InstallPlugins, scd.CopyTo)) { InstallPlugins.Add(scd.CopyTo); } } } // now do the same for the data files :) if (srd.InstallAllData) { foreach (string s in omod.GetDataFileList()) { InstallDataFiles.Add(s); } } foreach (string s in srd.InstallData) { if (!Framework.strArrayContains(InstallDataFiles, s)) { InstallDataFiles.Add(s); } } foreach (string s in srd.IgnoreData) { Framework.strArrayRemove(InstallDataFiles, s); } foreach (ScriptCopyDataFile scd in srd.CopyDataFiles) { if (!File.Exists(Path.Combine(dataPath, scd.CopyFrom))) { return; } else { if (scd.CopyFrom != scd.CopyTo) { // because data files can be in subdirectories we have to check if the folder actually exists string dirName = Path.GetDirectoryName(Path.Combine(dataPath, scd.CopyTo)); if (!Directory.Exists(dirName)) { Directory.CreateDirectory(dirName); } if (File.Exists(Path.Combine(dataPath, scd.CopyTo))) { File.Delete(Path.Combine(dataPath, scd.CopyTo)); } File.Copy(Path.Combine(dataPath, scd.CopyFrom), Path.Combine(dataPath, scd.CopyTo)); } if (!Framework.strArrayContains(InstallDataFiles, scd.CopyTo)) { InstallDataFiles.Add(scd.CopyTo); } } } // after everything is done some final checks for (int i = 0; i < InstallDataFiles.Count; i++) { // if the files have \\ at the start than Path.Combine wont work :( if (InstallDataFiles[i].StartsWith("\\")) { InstallDataFiles[i] = InstallDataFiles[i].Substring(1); } string currentFile = Path.Combine(dataPath, InstallDataFiles[i]); // also check if the file we want to install exists and is not in the 5th dimension eating lunch if (!File.Exists(currentFile)) { InstallDataFiles.RemoveAt(i--); } } for (int i = 0; i < InstallPlugins.Count; i++) { if (InstallPlugins[i].StartsWith("\\")) { InstallPlugins[i] = InstallPlugins[i].Substring(1); } string currentFile = Path.Combine(pluginsPath, InstallPlugins[i]); if (!File.Exists(currentFile)) { InstallPlugins.RemoveAt(i--); } } // now install for (int i = 0; i < InstallDataFiles.Count; i++) { // check if the folder exists before copying string s = Path.GetDirectoryName(InstallDataFiles[i]); if (!Directory.Exists(Path.Combine(OutputDir, s))) { Directory.CreateDirectory(Path.Combine(OutputDir, s)); } File.Move(Path.Combine(dataPath, InstallDataFiles[i]), Path.Combine(OutputDir, InstallDataFiles[i])); } for (int i = 0; i < InstallPlugins.Count; i++) { File.Move(Path.Combine(pluginsPath, InstallPlugins[i]), Path.Combine(OutputDir, InstallPlugins[i])); } } }
internal ScriptFunctions(OMODScriptSettings settings, OMOD omod, ScriptReturnData srd) { _settings = settings; _omod = omod; _srd = srd; }
internal static ScriptReturnData Execute(string inputScript, string dataPath, string pluginsPath, ref SharedFunctionsHandler handler) { Handler = handler; Srd = new ScriptReturnData(); if (string.IsNullOrWhiteSpace(inputScript)) { return(Srd); } DataFiles = dataPath; Plugins = pluginsPath; Variables = new Dictionary <string, string>(); var flowControl = new Stack <FlowControlStruct>(); Variables["NewLine"] = Environment.NewLine; Variables["Tab"] = "\t"; var script = inputScript.Replace("\r", "").Split('\n'); string skipTo = null; bool allowRunOnLines = false; bool Break = false; for (var i = 0; i < script.Length || ExtraLines.Count > 0; i++) { string s; if (ExtraLines.Count > 0) { i--; s = ExtraLines.Dequeue().Replace('\t', ' ').Trim(); } else { s = script[i].Replace('\t', ' ').Trim(); } CLine = i.ToString(); if (allowRunOnLines) { while (s.EndsWith("\\")) { s = s.Remove(s.Length - 1); if (ExtraLines.Count > 0) { s += ExtraLines.Dequeue().Replace('\t', ' ').Trim(); } else { if (++i == script.Length) { Warn("Run-on line passed end of script"); } else { s += script[i].Replace('\t', ' ').Trim(); } } } } if (skipTo != null) { if (s == skipTo) { skipTo = null; } else { continue; } } IReadOnlyCollection <string> line = SplitLine(s); if (line.Count == 0) { continue; } if (flowControl.Count != 0 && !flowControl.Peek().Active) { switch (line.ElementAt(0)) { case "": Warn("Empty function"); break; case "If": case "IfNot": flowControl.Push(new FlowControlStruct(0)); break; case "Else": if (flowControl.Count != 0 && flowControl.Peek().Type == 0) { flowControl.Peek().Active = flowControl.Peek().Line != -1; } else { Warn("Unexpected Else"); } break; case "EndIf": if (flowControl.Count != 0 && flowControl.Peek().Type == 0) { flowControl.Pop(); } else { Warn("Unexpected EndIf"); } break; case "Select": case "SelectMany": case "SelectWithPreview": case "SelectManyWithPreview": case "SelectWithDescriptions": case "SelectManyWithDescriptions": case "SelectWithDescriptionsAndPreviews": case "SelectManyWithDescriptionsAndPreviews": case "SelectVar": case "SelectString": flowControl.Push(new FlowControlStruct(1)); break; case "Case": if (flowControl.Count != 0 && flowControl.Peek().Type == 1) { if (flowControl.Peek().Line != -1 && Array.IndexOf(flowControl.Peek().Values, s) != -1) { flowControl.Peek().Active = true; flowControl.Peek().HitCase = true; } } else { Warn("Unexpected Break"); } break; case "Default": if (flowControl.Count != 0 && flowControl.Peek().Type == 1) { if (flowControl.Peek().Line != -1 && !flowControl.Peek().HitCase) { flowControl.Peek().Active = true; } } else { Warn("Unexpected Default"); } break; case "EndSelect": if (flowControl.Count != 0 && flowControl.Peek().Type == 1) { flowControl.Pop(); } else { Warn("Unexpected EndSelect"); } break; case "For": flowControl.Push(new FlowControlStruct(2)); break; case "EndFor": if (flowControl.Count != 0 && flowControl.Peek().Type == 2) { flowControl.Pop(); } else { Warn("Unexpected EndFor"); } break; case "Break": case "Continue": case "Exit": break; } } else { var lineMsg = ""; line.Do(cur => lineMsg += "'" + cur + "' "); var registry = new SharedFunctionsRegistry(Handler); var function = registry.GetFunctionByName(line.ElementAt(0)); if (function != null) { if (line.Count < function.MinArgs) { Warn($"Missing arguments for '{function.FuncName}'"); break; } if (function.MaxArgs != 0 && line.Count > function.MaxArgs) { Warn($"Unexpected arguments for '{function.FuncName}'"); } Utils.Script($"\"{function.FuncName}\" called with line: {lineMsg}"); function.Run(ref line); } else { Utils.Script($"\"{line.ElementAt(0)}\" called with line: {lineMsg}"); } switch (line.ElementAt(0)) { case "Goto": if (line.Count < 2) { Warn("Not enough arguments to function 'Goto'!"); } else { if (line.Count > 2) { Warn("Unexpected extra arguments to function 'Goto'"); } skipTo = $"Label {line.ElementAt(1)}"; flowControl.Clear(); } break; case "Label": break; case "If": flowControl.Push(new FlowControlStruct(i, FunctionIf(line))); break; case "IfNot": flowControl.Push(new FlowControlStruct(i, !FunctionIf(line))); break; case "Else": if (flowControl.Count != 0 && flowControl.Peek().Type == 0) { flowControl.Peek().Active = false; } else { Warn("Unexpected Else"); } break; case "EndIf": if (flowControl.Count != 0 && flowControl.Peek().Type == 0) { flowControl.Pop(); } else { Warn("Unexpected EndIf"); } break; case "Select": flowControl.Push(new FlowControlStruct(i, FunctionSelect(line, false, false, false))); break; case "SelectMany": flowControl.Push(new FlowControlStruct(i, FunctionSelect(line, true, false, false))); break; case "SelectWithPreview": flowControl.Push(new FlowControlStruct(i, FunctionSelect(line, false, true, false))); break; case "SelectManyWithPreview": flowControl.Push(new FlowControlStruct(i, FunctionSelect(line, true, true, false))); break; case "SelectWithDescriptions": flowControl.Push(new FlowControlStruct(i, FunctionSelect(line, false, false, true))); break; case "SelectManyWithDescriptions": flowControl.Push(new FlowControlStruct(i, FunctionSelect(line, true, false, true))); break; case "SelectWithDescriptionsAndPreviews": flowControl.Push(new FlowControlStruct(i, FunctionSelect(line, false, true, true))); break; case "SelectManyWithDescriptionsAndPreviews": flowControl.Push(new FlowControlStruct(i, FunctionSelect(line, true, true, true))); break; case "SelectVar": flowControl.Push(new FlowControlStruct(i, FunctionSelectVar(line, true))); break; case "SelectString": flowControl.Push(new FlowControlStruct(i, FunctionSelectVar(line, false))); break; case "Break": { bool found = false; var fcs = flowControl.ToArray(); for (int k = 0; k < fcs.Length; k++) { if (fcs[k].Type != 1) { continue; } for (int j = 0; j <= k; j++) { fcs[j].Active = false; } found = true; break; } if (!found) { Warn("Unexpected Break"); } break; } case "Case": if (flowControl.Count == 0 || flowControl.Peek().Type != 1) { Warn("Unexpected Case"); } break; case "Default": if (flowControl.Count == 0 || flowControl.Peek().Type != 1) { Warn("Unexpected Default"); } break; case "EndSelect": if (flowControl.Count != 0 && flowControl.Peek().Type == 1) { flowControl.Pop(); } else { Warn("Unexpected EndSelect"); } break; case "For": { var fc = FunctionFor(line, i); flowControl.Push(fc); if (fc.Line != -1 && fc.Values.Length > 0) { Variables[fc.Var] = fc.Values[0]; fc.Active = true; } break; } case "Continue": { var found = false; var fcs = flowControl.ToArray(); for (int k = 0; k < fcs.Length; k++) { if (fcs[k].Type != 2) { continue; } fcs[k].ForCount++; if (fcs[k].ForCount == fcs[k].Values.Length) { for (int j = 0; j <= k; j++) { fcs[j].Active = false; } } else { i = fcs[k].Line; Variables[fcs[k].Var] = fcs[k].Values[fcs[k].ForCount]; for (int j = 0; j < k; j++) { flowControl.Pop(); } } found = true; break; } if (!found) { Warn("Unexpected Continue"); } break; } case "Exit": { bool found = false; var fcs = flowControl.ToArray(); for (int k = 0; k < fcs.Length; k++) { if (fcs[k].Type != 2) { continue; } for (int j = 0; j <= k; j++) { flowControl.Peek().Active = false; } found = true; break; } if (!found) { Warn("Unexpected Exit"); } break; } case "EndFor": if (flowControl.Count != 0 && flowControl.Peek().Type == 2) { var fc = flowControl.Peek(); fc.ForCount++; if (fc.ForCount == fc.Values.Length) { flowControl.Pop(); } else { i = fc.Line; Variables[fc.Var] = fc.Values[fc.ForCount]; } } else { Warn("Unexpected EndFor"); } break; //Functions case "DontInstallAnyPlugins": Srd.InstallAllPlugins = false; break; case "DontInstallAnyDataFiles": Srd.InstallAllData = false; break; case "InstallAllPlugins": Srd.InstallAllPlugins = true; break; case "InstallAllDataFiles": Srd.InstallAllData = true; break; case "FatalError": Utils.Error("Script called FatalError!"); Srd.CancelInstall = true; break; case "Return": Break = true; break; case "AllowRunOnLines": allowRunOnLines = true; break; default: break; } } if (Break || Srd.CancelInstall) { break; } } if (skipTo != null) { Warn($"Expected: {skipTo}!"); } var temp = Srd; Srd = null; Variables = null; return(temp); }