1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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);
}
}
|