summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2016-11-21 19:25:53 -0500
committerJesse Plamondon-Willard <github@jplamondonw.com>2016-11-25 23:41:19 -0500
commit517a9d82fc1234b6c6ae6f1e45ef7c0f160ee6c5 (patch)
tree5ef7e0890758783d9107650fefe36429debc7154 /src
parent00a3c14446b716fc32c63bccf12c79bdbee436d1 (diff)
downloadSMAPI-517a9d82fc1234b6c6ae6f1e45ef7c0f160ee6c5.tar.gz
SMAPI-517a9d82fc1234b6c6ae6f1e45ef7c0f160ee6c5.tar.bz2
SMAPI-517a9d82fc1234b6c6ae6f1e45ef7c0f160ee6c5.zip
preprocess mods through Mono.Cecil to allow rewriting later (#166)
Diffstat (limited to 'src')
-rw-r--r--src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj1
-rw-r--r--src/StardewModdingAPI/Framework/ModAssemblyLoader.cs65
-rw-r--r--src/StardewModdingAPI/Program.cs77
-rw-r--r--src/StardewModdingAPI/StardewModdingAPI.csproj18
-rw-r--r--src/StardewModdingAPI/packages.config1
5 files changed, 133 insertions, 29 deletions
diff --git a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj
index 6f9ed2eb..23e2d278 100644
--- a/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj
+++ b/src/StardewModdingAPI.Installer/StardewModdingAPI.Installer.csproj
@@ -67,6 +67,7 @@
<Copy SourceFiles="$(TargetDir)\readme.txt" DestinationFiles="$(CompiledInstallerPath)\readme.txt" />
<!-- copy SMAPI files for Mono -->
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(CompiledInstallerPath)\Mono" />
+ <Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(CompiledInstallerPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe" DestinationFolder="$(CompiledInstallerPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.exe.mdb" DestinationFolder="$(CompiledInstallerPath)\Mono" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI-settings.json" DestinationFolder="$(CompiledInstallerPath)\Mono" />
diff --git a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
new file mode 100644
index 00000000..2615e1f7
--- /dev/null
+++ b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
@@ -0,0 +1,65 @@
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Security.Cryptography;
+using Mono.Cecil;
+
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>Loads mod assemblies.</summary>
+ internal class ModAssemblyLoader
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The directory in which to cache data.</summary>
+ private readonly string CacheDirPath;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="cacheDirPath">The cache directory.</param>
+ public ModAssemblyLoader(string cacheDirPath)
+ {
+ this.CacheDirPath = cacheDirPath;
+ }
+
+ /// <summary>Read an assembly from the given path.</summary>
+ /// <param name="assemblyPath">The assembly file path.</param>
+ public Assembly ProcessAssembly(string assemblyPath)
+ {
+ // read assembly data
+ byte[] assemblyBytes = File.ReadAllBytes(assemblyPath);
+ byte[] hash = MD5.Create().ComputeHash(assemblyBytes);
+
+ // get cache data
+ string key = Path.GetFileNameWithoutExtension(assemblyPath);
+ string cachePath = Path.Combine(this.CacheDirPath, $"{key}.dll");
+ string cacheHashPath = Path.Combine(this.CacheDirPath, $"{key}-hash.txt");
+ bool canUseCache = File.Exists(cachePath) && File.Exists(cacheHashPath) && hash.SequenceEqual(File.ReadAllBytes(cacheHashPath));
+
+ // process assembly if not cached
+ if (!canUseCache)
+ {
+ // read assembly definition
+ AssemblyDefinition definition;
+ using (Stream readStream = new MemoryStream(assemblyBytes))
+ definition = AssemblyDefinition.ReadAssembly(readStream);
+
+ // write cache
+ using (MemoryStream outStream = new MemoryStream())
+ {
+ definition.Write(outStream);
+ byte[] outBytes = outStream.ToArray();
+ File.WriteAllBytes(cachePath, outBytes);
+ File.WriteAllBytes(cacheHashPath, hash);
+ }
+ }
+
+ // load assembly
+ return Assembly.UnsafeLoadFrom(cachePath);
+ }
+ }
+}
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index d31a1d39..12b6cad4 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -33,6 +33,9 @@ namespace StardewModdingAPI
/// <summary>The full path to the folder containing mods.</summary>
private static readonly string ModPath = Path.Combine(Constants.ExecutionPath, "Mods");
+ /// <summary>The full path to the folder containing cached SMAPI data.</summary>
+ private static readonly string CachePath = Path.Combine(Program.ModPath, ".cache");
+
/// <summary>The log file to which to write messages.</summary>
private static readonly LogFileManager LogFile = new LogFileManager(Constants.LogPath);
@@ -126,6 +129,7 @@ namespace StardewModdingAPI
Program.Monitor.Log("Loading SMAPI...");
Console.Title = Constants.ConsoleTitle;
Program.VerifyPath(Program.ModPath);
+ Program.VerifyPath(Program.CachePath);
Program.VerifyPath(Constants.LogDir);
if (!File.Exists(Program.GameExecutablePath))
{
@@ -293,6 +297,8 @@ namespace StardewModdingAPI
private static void LoadMods()
{
Program.Monitor.Log("Loading mods...");
+
+ ModAssemblyLoader modAssemblyLoader = new ModAssemblyLoader(Program.CachePath);
foreach (string directory in Directory.GetDirectories(Program.ModPath))
{
foreach (string manifestPath in Directory.GetFiles(directory, "manifest.json"))
@@ -382,9 +388,11 @@ namespace StardewModdingAPI
}
}
- // load DLL & hook up mod
+ // load assembly
+ Assembly modAssembly;
try
{
+ // get assembly path
string assemblyPath = Path.Combine(directory, manifest.EntryDll);
if (!File.Exists(assemblyPath))
{
@@ -392,36 +400,47 @@ namespace StardewModdingAPI
continue;
}
- Assembly modAssembly = Assembly.UnsafeLoadFrom(assemblyPath);
- if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) > 0)
+ // read assembly
+ modAssembly = modAssemblyLoader.ProcessAssembly(assemblyPath);
+ if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0)
{
- TypeInfo modEntryType = modAssembly.DefinedTypes.First(x => x.BaseType == typeof(Mod));
- Mod modEntry = (Mod)modAssembly.CreateInstance(modEntryType.ToString());
- if (modEntry != null)
- {
- // track mod
- Program.ModRegistry.Add(manifest, modAssembly);
-
- // hook up mod
- modEntry.Manifest = manifest;
- modEntry.Helper = helper;
- modEntry.Monitor = new Monitor(manifest.Name, Program.LogFile) { ShowTraceInConsole = Program.DeveloperMode };
- modEntry.PathOnDisk = directory;
- Program.Monitor.Log($"Loaded mod: {modEntry.Manifest.Name} by {modEntry.Manifest.Author}, v{modEntry.Manifest.Version} | {modEntry.Manifest.Description}", LogLevel.Info);
- Program.ModsLoaded += 1;
- modEntry.Entry(); // deprecated since 1.0
- modEntry.Entry((ModHelper)modEntry.Helper); // deprecated since 1.1
- modEntry.Entry(modEntry.Helper); // deprecated since 1.1
-
- // raise deprecation warning for old Entry() method
- if (Program.DeprecationManager.IsVirtualMethodImplemented(modEntryType, typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) }))
- Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.0", DeprecationLevel.Notice);
- if (Program.DeprecationManager.IsVirtualMethodImplemented(modEntryType, typeof(Mod), nameof(Mod.Entry), new[] { typeof(ModHelper) }))
- Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.1", DeprecationLevel.Notice);
- }
- }
- else
Program.Monitor.Log($"{errorPrefix}: the mod DLL does not contain an implementation of the 'Mod' class.", LogLevel.Error);
+ continue;
+ }
+ }
+ catch (Exception ex)
+ {
+ Program.Monitor.Log($"{errorPrefix}: an error occurred while optimising the target DLL.\n{ex}", LogLevel.Error);
+ continue;
+ }
+
+ // hook up mod
+ try
+ {
+ TypeInfo modEntryType = modAssembly.DefinedTypes.First(x => x.BaseType == typeof(Mod));
+ Mod modEntry = (Mod)modAssembly.CreateInstance(modEntryType.ToString());
+ if (modEntry != null)
+ {
+ // track mod
+ Program.ModRegistry.Add(manifest, modAssembly);
+
+ // hook up mod
+ modEntry.Manifest = manifest;
+ modEntry.Helper = helper;
+ modEntry.Monitor = new Monitor(manifest.Name, Program.LogFile) { ShowTraceInConsole = Program.DeveloperMode };
+ modEntry.PathOnDisk = directory;
+ Program.Monitor.Log($"Loaded mod: {modEntry.Manifest.Name} by {modEntry.Manifest.Author}, v{modEntry.Manifest.Version} | {modEntry.Manifest.Description}", LogLevel.Info);
+ Program.ModsLoaded += 1;
+ modEntry.Entry(); // deprecated since 1.0
+ modEntry.Entry((ModHelper)modEntry.Helper); // deprecated since 1.1
+ modEntry.Entry(modEntry.Helper); // deprecated since 1.1
+
+ // raise deprecation warning for old Entry() method
+ if (Program.DeprecationManager.IsVirtualMethodImplemented(modEntryType, typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) }))
+ Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.0", DeprecationLevel.Notice);
+ if (Program.DeprecationManager.IsVirtualMethodImplemented(modEntryType, typeof(Mod), nameof(Mod.Entry), new[] { typeof(ModHelper) }))
+ Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.1", DeprecationLevel.Notice);
+ }
}
catch (Exception ex)
{
diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj
index 0b6a185e..f3dbc45a 100644
--- a/src/StardewModdingAPI/StardewModdingAPI.csproj
+++ b/src/StardewModdingAPI/StardewModdingAPI.csproj
@@ -140,6 +140,22 @@
</Otherwise>
</Choose>
<ItemGroup>
+ <Reference Include="Mono.Cecil, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+ <HintPath>..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="Mono.Cecil.Mdb, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+ <HintPath>..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="Mono.Cecil.Pdb, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+ <HintPath>..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="Mono.Cecil.Rocks, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+ <HintPath>..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
@@ -200,6 +216,7 @@
<Compile Include="Framework\DeprecationLevel.cs" />
<Compile Include="Framework\DeprecationManager.cs" />
<Compile Include="Framework\InternalExtensions.cs" />
+ <Compile Include="Framework\ModAssemblyLoader.cs" />
<Compile Include="IModHelper.cs" />
<Compile Include="Framework\LogFileManager.cs" />
<Compile Include="LogLevel.cs" />
@@ -284,5 +301,6 @@
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)" />
+ <Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)" />
</Target>
</Project> \ No newline at end of file
diff --git a/src/StardewModdingAPI/packages.config b/src/StardewModdingAPI/packages.config
index 0aec11c6..dc07902b 100644
--- a/src/StardewModdingAPI/packages.config
+++ b/src/StardewModdingAPI/packages.config
@@ -2,5 +2,6 @@
<packages>
<package id="Costura.Fody" version="1.3.3.0" targetFramework="net461" developmentDependency="true" />
<package id="Fody" version="1.29.4" targetFramework="net461" developmentDependency="true" />
+ <package id="Mono.Cecil" version="0.9.6.4" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net461" />
</packages> \ No newline at end of file