aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_fomod_csharp/src/csharp_interface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/installer_fomod_csharp/src/csharp_interface.cpp')
-rw-r--r--libs/installer_fomod_csharp/src/csharp_interface.cpp157
1 files changed, 157 insertions, 0 deletions
diff --git a/libs/installer_fomod_csharp/src/csharp_interface.cpp b/libs/installer_fomod_csharp/src/csharp_interface.cpp
new file mode 100644
index 0000000..91a4a0d
--- /dev/null
+++ b/libs/installer_fomod_csharp/src/csharp_interface.cpp
@@ -0,0 +1,157 @@
+#include "csharp_interface.h"
+
+#include <fstream>
+#include <regex>
+#include <sstream>
+
+#include <uibase/log.h>
+
+#include "base_script.h"
+#include "csharp_utils.h"
+
+// clang-format off
+
+#using <System.dll>
+
+using namespace MOBase;
+
+/**
+ * This is a assembly resolve handler that does only one thing: returns the assembly
+ * containing BaseScript (usually the DLL) when requested.
+ *
+ * I don't know why this must be done manually... But I did not find any better solution.
+ */
+System::Reflection::Assembly^ currentDomain_AssemblyResolve(System::Object^, System::ResolveEventArgs^ args)
+{
+ using namespace System::Reflection;
+
+ try {
+ Assembly^ baseScriptAssembly = System::Reflection::Assembly::GetAssembly(BaseScript::typeid);
+ if (args->Name->Equals(baseScriptAssembly->FullName)) {
+ return baseScriptAssembly;
+ }
+ }
+ catch (...)
+ {
+ }
+
+ return nullptr;
+}
+
+IPluginInstaller::EInstallResult executeScript(System::String^ script) {
+
+ using namespace System;
+ using namespace System::CodeDom;
+ using namespace System::CodeDom::Compiler;
+ using namespace System::Collections::Generic;
+
+ AppDomain^ currentDomain = AppDomain::CurrentDomain;
+ currentDomain->AssemblyResolve += gcnew ResolveEventHandler(currentDomain_AssemblyResolve);
+
+ // From Nexus-Mods/fomod-installer:
+ Dictionary<String^, String^>^ dicOptions = gcnew Dictionary<String^, String^>(10);
+ dicOptions->Add("CompilerVersion", "v4.0");
+
+ // List of assemblies (including BaseScript) - From Nexus-Mods/fomod-installer:
+ array<String^>^ referenceAssemblies = {
+ "System.dll",
+ "System.Runtime.dll",
+ "System.Drawing.dll",
+ "System.Windows.Forms.dll",
+ "System.Xml.dll",
+ System::Reflection::Assembly::GetAssembly(BaseScript::typeid)->Location
+ };
+
+ CompilerParameters^ cp = gcnew CompilerParameters(referenceAssemblies);
+ cp->GenerateExecutable = false;
+ cp->IncludeDebugInformation = false;
+ cp->GenerateInMemory = true;
+ cp->TreatWarningsAsErrors = false;
+ CodeDomProvider^ provider = CodeDomProvider::CreateProvider("CSharp", dicOptions);
+
+ // Compile the script
+ auto result = provider->CompileAssemblyFromSource(cp, script);
+
+ int errorCount = 0;
+ for each (CompilerError ^ error in result->Errors) {
+ if (error->IsWarning) {
+ log::warn("C# [{}]: {}", error->Line, CSharp::to_string(error->ErrorText));
+ }
+ else {
+ log::error("C# [{}]: {}", error->Line, CSharp::to_string(error->ErrorText));
+ ++errorCount;
+ }
+ }
+
+ if (errorCount > 0) {
+ return IPluginInstaller::EInstallResult::RESULT_FAILED;
+ }
+
+ // Execute the script:
+ try {
+ auto scriptClass = result->CompiledAssembly->GetType("Script");
+ BaseScript^ scriptObject = (BaseScript^)System::Activator::CreateInstance(scriptClass);
+ auto onActivateMethod = scriptObject->GetType()->GetMethod("OnActivate");
+
+ auto success = (bool)(onActivateMethod->IsStatic ? onActivateMethod->Invoke(nullptr, nullptr) : onActivateMethod->Invoke(scriptObject, nullptr));
+ return success ? IPluginInstaller::EInstallResult::RESULT_SUCCESS : IPluginInstaller::EInstallResult::RESULT_CANCELED;
+ }
+ catch (System::Exception^ ex) {
+ log::error("C# ({}): {}\n{}", CSharp::to_string(ex->GetType()->FullName), CSharp::to_string(ex->Message), CSharp::to_string(ex->StackTrace));
+ System::Exception^ innerEx = ex->InnerException;
+ if (innerEx) {
+ log::error("C# ({}): {}\n{}", CSharp::to_string(innerEx->GetType()->FullName), CSharp::to_string(innerEx->Message), CSharp::to_string(innerEx->StackTrace));
+ }
+ return IPluginInstaller::EInstallResult::RESULT_FAILED;
+ }
+
+}
+
+namespace CSharp {
+
+ IPluginInstaller::EInstallResult executeCSharpScript(QString scriptPath, std::shared_ptr<IFileTree>& tree) {
+
+ using namespace System;
+ using namespace System::IO;
+ using namespace System::Text::RegularExpressions;
+
+ // Note: Using C# stuff here to mimicate NMM since there are some encoding issues, and
+ // some regex do not work in C++:
+ array<Byte>^ scriptBytes = File::ReadAllBytes(from_string(scriptPath.toStdWString()));
+
+ // Read the script (using C# to "auto-detect" encoding in a C# way):
+ String^ script;
+ {
+ auto memoryStream = gcnew MemoryStream(scriptBytes);
+ auto reader = gcnew StreamReader(memoryStream, true);
+
+ script = reader->ReadToEnd();
+
+ reader->Close();
+ memoryStream->Close();
+
+ delete reader;
+ delete memoryStream;
+ }
+
+
+ Regex^ regScriptClass = gcnew Regex(R"re((class\s+Script\s*:.*?)(\S*BaseScript))re");
+ Regex^ regFommUsing = gcnew Regex(R"re(\s*using\s*fomm.Scripting\s*;)re");
+
+ String^ strBaseScriptClassName = regScriptClass->Match(script)->Groups[2]->ToString();
+ Regex^ regOtherScriptClasses = gcnew Regex(String::Format(R"re((class\s+\S+\s*:.*?)(?<!\w){0})re", strBaseScriptClassName));
+ String^ strCode = script;
+ strCode = regScriptClass->Replace(strCode, "$1BaseScript");
+ strCode = regOtherScriptClasses->Replace(strCode, "$1BaseScript");
+ strCode = regFommUsing->Replace(strCode, "");
+
+ auto result = executeScript(strCode);
+
+ if (result != IPluginInstaller::EInstallResult::RESULT_SUCCESS) {
+ return result;
+ }
+
+ return postInstall(tree);
+ }
+
+}