diff options
Diffstat (limited to 'src/StardewModdingAPI/Program.cs')
-rw-r--r-- | src/StardewModdingAPI/Program.cs | 243 |
1 files changed, 142 insertions, 101 deletions
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index d31a1d39..f0686039 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -9,6 +9,7 @@ using System.Windows.Forms; #endif using Microsoft.Xna.Framework.Graphics; using Newtonsoft.Json; +using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; using StardewModdingAPI.Inheritance; @@ -23,16 +24,23 @@ namespace StardewModdingAPI /********* ** Properties *********/ - /// <summary>The full path to the Stardew Valley executable.</summary> + /// <summary>The target game platform.</summary> + private static readonly Platform TargetPlatform = #if SMAPI_FOR_WINDOWS - private static readonly string GameExecutablePath = Path.Combine(Constants.ExecutionPath, "Stardew Valley.exe"); + Platform.Windows; #else - private static readonly string GameExecutablePath = Path.Combine(Constants.ExecutionPath, "StardewValley.exe"); + Platform.Mono; #endif + /// <summary>The full path to the Stardew Valley executable.</summary> + private static readonly string GameExecutablePath = Path.Combine(Constants.ExecutionPath, Program.TargetPlatform == Platform.Windows ? "Stardew Valley.exe" : "StardewValley.exe"); + /// <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 +134,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,141 +302,173 @@ namespace StardewModdingAPI private static void LoadMods() { Program.Monitor.Log("Loading mods..."); + + ModAssemblyLoader modAssemblyLoader = new ModAssemblyLoader(Program.CachePath, Program.TargetPlatform, Program.Monitor); + AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); // supplement Mono's assembly resolution which doesn't handle assembly rewrites very well + foreach (string directory in Directory.GetDirectories(Program.ModPath)) { - foreach (string manifestPath in Directory.GetFiles(directory, "manifest.json")) + // ignore internal directory + if (new DirectoryInfo(directory).Name == ".cache") + continue; + + // check for cancellation + if (Program.CancellationTokenSource.IsCancellationRequested) + { + Program.Monitor.Log("Shutdown requested; interrupting mod loading.", LogLevel.Error); + return; + } + + // get helper + IModHelper helper = new ModHelper(directory); + + // get manifest path + string manifestPath = Path.Combine(directory, "manifest.json"); + string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'"; + Manifest manifest; + try { - // check for cancellation - if (Program.CancellationTokenSource.IsCancellationRequested) + // read manifest text + string json = File.ReadAllText(manifestPath); + if (string.IsNullOrEmpty(json)) + { + Program.Monitor.Log($"{errorPrefix}: manifest is empty.", LogLevel.Error); + continue; + } + + // deserialise manifest + manifest = helper.ReadJsonFile<Manifest>("manifest.json"); + if (manifest == null) { - Program.Monitor.Log("Shutdown requested; interrupting mod loading.", LogLevel.Error); - return; + Program.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error); + continue; + } + if (string.IsNullOrEmpty(manifest.EntryDll)) + { + Program.Monitor.Log($"{errorPrefix}: manifest doesn't specify an entry DLL.", LogLevel.Error); + continue; } - IModHelper helper = new ModHelper(directory); - string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'"; + // log deprecated fields + if (manifest.UsedAuthourField) + Program.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.Authour)}", "1.0", DeprecationLevel.Notice); + } + catch (Exception ex) + { + Program.Monitor.Log($"{errorPrefix}: manifest parsing failed.\n{ex.GetLogSummary()}", LogLevel.Error); + continue; + } - // read manifest - Manifest manifest; + // validate version + if (!string.IsNullOrWhiteSpace(manifest.MinimumApiVersion)) + { try { - // read manifest text - string json = File.ReadAllText(manifestPath); - if (string.IsNullOrEmpty(json)) + Version minVersion = new Version(manifest.MinimumApiVersion); + if (minVersion.IsNewerThan(Constants.Version)) { - Program.Monitor.Log($"{errorPrefix}: manifest is empty.", LogLevel.Error); + Program.Monitor.Log($"{errorPrefix}: this mod requires SMAPI {minVersion} or later. Please update SMAPI to the latest version to use this mod.", LogLevel.Error); continue; } - - // deserialise manifest - manifest = helper.ReadJsonFile<Manifest>("manifest.json"); - if (manifest == null) - { - Program.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error); - continue; - } - if (string.IsNullOrEmpty(manifest.EntryDll)) - { - Program.Monitor.Log($"{errorPrefix}: manifest doesn't specify an entry DLL.", LogLevel.Error); - continue; - } - - // log deprecated fields - if (manifest.UsedAuthourField) - Program.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.Authour)}", "1.0", DeprecationLevel.Notice); } - catch (Exception ex) + catch (FormatException ex) when (ex.Message.Contains("not a semantic version")) { - Program.Monitor.Log($"{errorPrefix}: manifest parsing failed.\n{ex.GetLogSummary()}", LogLevel.Error); + Program.Monitor.Log($"{errorPrefix}: the mod specified an invalid minimum SMAPI version '{manifest.MinimumApiVersion}'. This should be a semantic version number like {Constants.Version}.", LogLevel.Error); continue; } + } - // validate version - if (!string.IsNullOrWhiteSpace(manifest.MinimumApiVersion)) + // create per-save directory + if (manifest.PerSaveConfigs) + { + Program.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.PerSaveConfigs)}", "1.0", DeprecationLevel.Notice); + try { - try + string psDir = Path.Combine(directory, "psconfigs"); + Directory.CreateDirectory(psDir); + if (!Directory.Exists(psDir)) { - Version minVersion = new Version(manifest.MinimumApiVersion); - if (minVersion.IsNewerThan(Constants.Version)) - { - Program.Monitor.Log($"{errorPrefix}: this mod requires SMAPI {minVersion} or later. Please update SMAPI to the latest version to use this mod.", LogLevel.Error); - continue; - } - } - catch (FormatException ex) when (ex.Message.Contains("not a semantic version")) - { - Program.Monitor.Log($"{errorPrefix}: the mod specified an invalid minimum SMAPI version '{manifest.MinimumApiVersion}'. This should be a semantic version number like {Constants.Version}.", LogLevel.Error); + Program.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod. The failure reason is unknown.", LogLevel.Error); continue; } } + catch (Exception ex) + { + Program.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod.\n{ex.GetLogSummary()}", LogLevel.Error); + continue; + } + } - // create per-save directory - if (manifest.PerSaveConfigs) + // preprocess mod assemblies + { + bool succeeded = true; + foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll")) { - Program.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.PerSaveConfigs)}", "1.0", DeprecationLevel.Notice); try { - string psDir = Path.Combine(directory, "psconfigs"); - Directory.CreateDirectory(psDir); - if (!Directory.Exists(psDir)) - { - Program.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod. The failure reason is unknown.", LogLevel.Error); - continue; - } + modAssemblyLoader.ProcessAssembly(assemblyPath); } catch (Exception ex) { - Program.Monitor.Log($"{errorPrefix}: couldm't create the per-save configuration directory ('psconfigs') requested by this mod.\n{ex.GetLogSummary()}", LogLevel.Error); - continue; + Program.Monitor.Log($"{errorPrefix}: an error occurred while preprocessing '{Path.GetFileName(assemblyPath)}'.\n{ex.GetLogSummary()}", LogLevel.Error); + succeeded = false; + break; } } + if (!succeeded) + continue; + } - // load DLL & hook up mod - try + // load assembly + Assembly modAssembly; + try + { + string assemblyPath = Path.Combine(directory, manifest.EntryDll); + modAssembly = modAssemblyLoader.LoadCachedAssembly(assemblyPath); + if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0) { - string assemblyPath = Path.Combine(directory, manifest.EntryDll); - if (!File.Exists(assemblyPath)) - { - Program.Monitor.Log($"{errorPrefix}: target DLL '{assemblyPath}' does not exist.", LogLevel.Error); - continue; - } - - Assembly modAssembly = Assembly.UnsafeLoadFrom(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); + Program.Monitor.Log($"{errorPrefix}: the mod DLL does not contain an implementation of the 'Mod' class.", LogLevel.Error); + continue; } - catch (Exception ex) + } + catch (Exception ex) + { + Program.Monitor.Log($"{errorPrefix}: an error occurred while optimising the target DLL.\n{ex.GetLogSummary()}", 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) { - Program.Monitor.Log($"{errorPrefix}: an error occurred while loading the target DLL.\n{ex.GetLogSummary()}", LogLevel.Error); + // 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) + { + Program.Monitor.Log($"{errorPrefix}: an error occurred while loading the target DLL.\n{ex.GetLogSummary()}", LogLevel.Error); + } } // print result |