diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2016-11-21 19:25:53 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2016-11-25 23:41:19 -0500 |
commit | 517a9d82fc1234b6c6ae6f1e45ef7c0f160ee6c5 (patch) | |
tree | 5ef7e0890758783d9107650fefe36429debc7154 /src | |
parent | 00a3c14446b716fc32c63bccf12c79bdbee436d1 (diff) | |
download | SMAPI-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.csproj | 1 | ||||
-rw-r--r-- | src/StardewModdingAPI/Framework/ModAssemblyLoader.cs | 65 | ||||
-rw-r--r-- | src/StardewModdingAPI/Program.cs | 77 | ||||
-rw-r--r-- | src/StardewModdingAPI/StardewModdingAPI.csproj | 18 | ||||
-rw-r--r-- | src/StardewModdingAPI/packages.config | 1 |
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 |