diff options
| author | berkay2578 <berkaytgy@gmail.com> | 2019-04-14 12:29:23 +0300 |
|---|---|---|
| committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2019-09-13 15:12:45 -0400 |
| commit | accaa6c0e03e99411d7b10f938dd17b142b8eedc (patch) | |
| tree | 5637f936a7bbf5dd79f7d9cf15b4343573f88347 /src | |
| parent | e22a54212182d0adc443ac95bc791e83c90f7e10 (diff) | |
| download | SMAPI-accaa6c0e03e99411d7b10f938dd17b142b8eedc.tar.gz SMAPI-accaa6c0e03e99411d7b10f938dd17b142b8eedc.tar.bz2 SMAPI-accaa6c0e03e99411d7b10f938dd17b142b8eedc.zip | |
checkEventPrecondition crash fix
Diffstat (limited to 'src')
| -rw-r--r-- | src/SMAPI/Framework/SCore.cs | 418 | ||||
| -rw-r--r-- | src/SMAPI/Patches/CheckEventPreconditionErrorPatch.cs | 77 | ||||
| -rw-r--r-- | src/SMAPI/StardewModdingAPI.csproj | 1 |
3 files changed, 215 insertions, 281 deletions
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 9ffa46a5..a09b0f73 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -13,8 +13,10 @@ using System.Text.RegularExpressions; using System.Threading; #if SMAPI_FOR_WINDOWS using System.Windows.Forms; + #endif using Newtonsoft.Json; + using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Events; using StardewModdingAPI.Framework.Exceptions; @@ -32,15 +34,15 @@ using StardewModdingAPI.Toolkit.Framework.Clients.WebApi; using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Utilities; + using StardewValley; + using Object = StardewValley.Object; using ThreadState = System.Threading.ThreadState; -namespace StardewModdingAPI.Framework -{ +namespace StardewModdingAPI.Framework { /// <summary>The core class which initialises and manages SMAPI.</summary> - internal class SCore : IDisposable - { + internal class SCore : IDisposable { /********* ** Fields *********/ @@ -130,8 +132,7 @@ namespace StardewModdingAPI.Framework /// <summary>Construct an instance.</summary> /// <param name="modsPath">The path to search for mods.</param> /// <param name="writeToConsole">Whether to output log messages to the console.</param> - public SCore(string modsPath, bool writeToConsole) - { + public SCore(string modsPath, bool writeToConsole) { // init paths this.VerifyPath(modsPath); this.VerifyPath(Constants.LogDir); @@ -144,8 +145,7 @@ namespace StardewModdingAPI.Framework // init basics this.Settings = JsonConvert.DeserializeObject<SConfig>(File.ReadAllText(Constants.ApiConfigPath)); this.LogFile = new LogFileManager(logPath); - this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.CancellationTokenSource, this.Settings.ColorScheme, this.Settings.VerboseLogging) - { + this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.CancellationTokenSource, this.Settings.ColorScheme, this.Settings.VerboseLogging) { WriteToConsole = writeToConsole, ShowTraceInConsole = this.Settings.DeveloperMode, ShowFullStampInConsole = this.Settings.DeveloperMode @@ -167,8 +167,7 @@ namespace StardewModdingAPI.Framework // validate platform #if SMAPI_FOR_WINDOWS - if (Constants.Platform != Platform.Windows) - { + if (Constants.Platform != Platform.Windows) { this.Monitor.Log("Oops! You're running Windows, but this version of SMAPI is for Linux or Mac. Please reinstall SMAPI to fix this.", LogLevel.Error); this.PressAnyKeyToExit(); return; @@ -185,11 +184,9 @@ namespace StardewModdingAPI.Framework /// <summary>Launch SMAPI.</summary> [HandleProcessCorruptedStateExceptions, SecurityCritical] // let try..catch handle corrupted state exceptions - public void RunInteractively() - { + public void RunInteractively() { // initialise SMAPI - try - { + try { #if !SMAPI_3_0_STRICT // hook up events ContentEvents.Init(this.EventManager); @@ -233,6 +230,7 @@ namespace StardewModdingAPI.Framework // apply game patches new GamePatcher(this.Monitor).Apply( + new CheckEventPreconditionErrorPatch(this.MonitorForGame, this.Reflection), new DialogueErrorPatch(this.MonitorForGame, this.Reflection), new ObjectErrorPatch(), new LoadForNewGamePatch(this.Reflection, this.GameInstance.OnLoadStageChanged) @@ -242,15 +240,11 @@ namespace StardewModdingAPI.Framework new Thread(() => { this.CancellationTokenSource.Token.WaitHandle.WaitOne(); - if (this.IsGameRunning) - { - try - { + if (this.IsGameRunning) { + try { File.WriteAllText(Constants.FatalCrashMarker, string.Empty); File.Copy(this.LogFile.Path, Constants.FatalCrashLog, overwrite: true); - } - catch (Exception ex) - { + } catch (Exception ex) { this.Monitor.Log($"SMAPI failed trying to track the crash details: {ex.GetLogSummary()}"); } @@ -265,22 +259,17 @@ namespace StardewModdingAPI.Framework this.GameInstance.Window.Title += " [SMAPI 3.0 strict mode]"; Console.Title += " [SMAPI 3.0 strict mode]"; #endif - } - catch (Exception ex) - { + } catch (Exception ex) { this.Monitor.Log($"SMAPI failed to initialise: {ex.GetLogSummary()}", LogLevel.Error); this.PressAnyKeyToExit(); return; } // check update marker - if (File.Exists(Constants.UpdateMarker)) - { + if (File.Exists(Constants.UpdateMarker)) { string rawUpdateFound = File.ReadAllText(Constants.UpdateMarker); - if (SemanticVersion.TryParse(rawUpdateFound, out ISemanticVersion updateFound)) - { - if (Constants.ApiVersion.IsPrerelease() && updateFound.IsNewerThan(Constants.ApiVersion)) - { + if (SemanticVersion.TryParse(rawUpdateFound, out ISemanticVersion updateFound)) { + if (Constants.ApiVersion.IsPrerelease() && updateFound.IsNewerThan(Constants.ApiVersion)) { this.Monitor.Log("A new version of SMAPI was detected last time you played.", LogLevel.Error); this.Monitor.Log($"You can update to {updateFound}: https://smapi.io.", LogLevel.Error); this.Monitor.Log("Press any key to continue playing anyway. (This only appears when using a SMAPI beta.)", LogLevel.Info); @@ -291,8 +280,7 @@ namespace StardewModdingAPI.Framework } // show details if game crashed during last session - if (File.Exists(Constants.FatalCrashMarker)) - { + if (File.Exists(Constants.FatalCrashMarker)) { this.Monitor.Log("The game crashed last time you played. That can be due to bugs in the game, but if it happens repeatedly you can ask for help here: https://community.playstarbound.com/threads/108375/.", LogLevel.Error); this.Monitor.Log("If you ask for help, make sure to share your SMAPI log: https://log.smapi.io.", LogLevel.Error); this.Monitor.Log("Press any key to delete the crash data and continue playing.", LogLevel.Info); @@ -303,38 +291,29 @@ namespace StardewModdingAPI.Framework // start game this.Monitor.Log("Starting game...", LogLevel.Debug); - try - { + try { this.IsGameRunning = true; StardewValley.Program.releaseBuild = true; // game's debug logic interferes with SMAPI opening the game window this.GameInstance.Run(); - } - catch (InvalidOperationException ex) when (ex.Source == "Microsoft.Xna.Framework.Xact" && ex.StackTrace.Contains("Microsoft.Xna.Framework.Audio.AudioEngine..ctor")) - { + } catch (InvalidOperationException ex) when (ex.Source == "Microsoft.Xna.Framework.Xact" && ex.StackTrace.Contains("Microsoft.Xna.Framework.Audio.AudioEngine..ctor")) { this.Monitor.Log("The game couldn't load audio. Do you have speakers or headphones plugged in?", LogLevel.Error); this.Monitor.Log($"Technical details: {ex.GetLogSummary()}", LogLevel.Trace); this.PressAnyKeyToExit(); - } - catch (FileNotFoundException ex) when (ex.Message == "Could not find file 'C:\\Program Files (x86)\\Steam\\SteamApps\\common\\Stardew Valley\\Content\\XACT\\FarmerSounds.xgs'.") // path in error is hardcoded regardless of install path - { + } catch (FileNotFoundException ex) when (ex.Message == "Could not find file 'C:\\Program Files (x86)\\Steam\\SteamApps\\common\\Stardew Valley\\Content\\XACT\\FarmerSounds.xgs'.") // path in error is hardcoded regardless of install path + { this.Monitor.Log("The game can't find its Content\\XACT\\FarmerSounds.xgs file. You can usually fix this by resetting your content files (see https://smapi.io/troubleshoot#reset-content ), or by uninstalling and reinstalling the game.", LogLevel.Error); this.Monitor.Log($"Technical details: {ex.GetLogSummary()}", LogLevel.Trace); this.PressAnyKeyToExit(); - } - catch (Exception ex) - { + } catch (Exception ex) { this.MonitorForGame.Log($"The game failed to launch: {ex.GetLogSummary()}", LogLevel.Error); this.PressAnyKeyToExit(); - } - finally - { + } finally { this.Dispose(); } } /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> - public void Dispose() - { + public void Dispose() { // skip if already disposed if (this.IsDisposed) return; @@ -342,14 +321,10 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Disposing...", LogLevel.Trace); // dispose mod data - foreach (IModMetadata mod in this.ModRegistry.GetAll()) - { - try - { + foreach (IModMetadata mod in this.ModRegistry.GetAll()) { + try { (mod.Mod as IDisposable)?.Dispose(); - } - catch (Exception ex) - { + } catch (Exception ex) { mod.LogAsMod($"Mod failed during disposal: {ex.GetLogSummary()}.", LogLevel.Warn); } } @@ -371,8 +346,7 @@ namespace StardewModdingAPI.Framework ** Private methods *********/ /// <summary>Initialise SMAPI and mods after the game starts.</summary> - private void InitialiseAfterGameStart() - { + private void InitialiseAfterGameStart() { // add headers #if SMAPI_3_0_STRICT this.Monitor.Log($"You're running SMAPI 3.0 strict mode, so most mods won't work correctly. If that wasn't intended, install the normal version of SMAPI from https://smapi.io instead.", LogLevel.Warn); @@ -412,10 +386,8 @@ namespace StardewModdingAPI.Framework this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase); // write metadata file - if (this.Settings.DumpMetadata) - { - ModFolderExport export = new ModFolderExport - { + if (this.Settings.DumpMetadata) { + ModFolderExport export = new ModFolderExport { Exported = DateTime.UtcNow.ToString("O"), ApiVersion = Constants.ApiVersion.ToString(), GameVersion = Constants.GameVersion.ToString(), @@ -428,8 +400,7 @@ namespace StardewModdingAPI.Framework // check for updates this.CheckForUpdatesAsync(mods); } - if (this.Monitor.IsExiting) - { + if (this.Monitor.IsExiting) { this.Monitor.Log("SMAPI shutting down: aborting initialisation.", LogLevel.Warn); return; } @@ -449,8 +420,7 @@ namespace StardewModdingAPI.Framework } /// <summary>Handle the game changing locale.</summary> - private void OnLocaleChanged() - { + private void OnLocaleChanged() { // get locale string locale = this.ContentCore.GetLocale(); LocalizedContentManager.LanguageCode languageCode = this.ContentCore.Language; @@ -462,8 +432,7 @@ namespace StardewModdingAPI.Framework /// <summary>Run a loop handling console input.</summary> [SuppressMessage("ReSharper", "FunctionNeverReturns", Justification = "The thread is aborted when the game exits.")] - private void RunConsoleLoop() - { + private void RunConsoleLoop() { // prepare console this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info); this.GameInstance.CommandManager.Add(null, "help", "Lists command documentation.\n\nUsage: help\nLists all available commands.\n\nUsage: help <cmd>\n- cmd: The name of a command whose documentation to display.", this.HandleCommand); @@ -472,8 +441,7 @@ namespace StardewModdingAPI.Framework // start handling command line input Thread inputThread = new Thread(() => { - while (true) - { + while (true) { // get input string input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) @@ -495,8 +463,7 @@ namespace StardewModdingAPI.Framework /// <summary>Look for common issues with the game's XNB content, and log warnings if anything looks broken or outdated.</summary> /// <returns>Returns whether all integrity checks passed.</returns> - private bool ValidateContentIntegrity() - { + private bool ValidateContentIntegrity() { this.Monitor.Log("Detecting common issues...", LogLevel.Trace); bool issuesFound = false; @@ -505,11 +472,9 @@ namespace StardewModdingAPI.Framework // detect issues bool hasObjectIssues = false; void LogIssue(int id, string issue) => this.Monitor.Log($@"Detected issue: item #{id} in Content\Data\ObjectInformation.xnb is invalid ({issue}).", LogLevel.Trace); - foreach (KeyValuePair<int, string> entry in Game1.objectInformation) - { + foreach (KeyValuePair<int, string> entry in Game1.objectInformation) { // must not be empty - if (string.IsNullOrWhiteSpace(entry.Value)) - { + if (string.IsNullOrWhiteSpace(entry.Value)) { LogIssue(entry.Key, "entry is empty"); hasObjectIssues = true; continue; @@ -517,19 +482,16 @@ namespace StardewModdingAPI.Framework // require core fields string[] fields = entry.Value.Split('/'); - if (fields.Length < Object.objectInfoDescriptionIndex + 1) - { + if (fields.Length < Object.objectInfoDescriptionIndex + 1) { LogIssue(entry.Key, "too few fields for an object"); hasObjectIssues = true; continue; } // check min length for specific types - switch (fields[Object.objectInfoTypeIndex].Split(new[] { ' ' }, 2)[0]) - { + switch (fields[Object.objectInfoTypeIndex].Split(new[] { ' ' }, 2)[0]) { case "Cooking": - if (fields.Length < Object.objectInfoBuffDurationIndex + 1) - { + if (fields.Length < Object.objectInfoBuffDurationIndex + 1) { LogIssue(entry.Key, "too few fields for a cooking item"); hasObjectIssues = true; } @@ -538,8 +500,7 @@ namespace StardewModdingAPI.Framework } // log error - if (hasObjectIssues) - { + if (hasObjectIssues) { issuesFound = true; this.Monitor.Log(@"Your Content\Data\ObjectInformation.xnb file seems to be broken or outdated.", LogLevel.Warn); } @@ -550,8 +511,7 @@ namespace StardewModdingAPI.Framework /// <summary>Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available.</summary> /// <param name="mods">The mods to include in the update check (if eligible).</param> - private void CheckForUpdatesAsync(IModMetadata[] mods) - { + private void CheckForUpdatesAsync(IModMetadata[] mods) { if (!this.Settings.CheckForUpdates) return; @@ -567,32 +527,23 @@ namespace StardewModdingAPI.Framework // check SMAPI version ISemanticVersion updateFound = null; - try - { + try { ModEntryModel response = client.GetModInfo(new[] { new ModSearchEntryModel("Pathoschild.SMAPI", new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) }).Single().Value; ISemanticVersion latestStable = response.Main?.Version; ISemanticVersion latestBeta = response.Optional?.Version; - if (latestStable == null && response.Errors.Any()) - { + if (latestStable == null && response.Errors.Any()) { this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn); this.Monitor.Log($"Error: {string.Join("\n", response.Errors)}"); - } - else if (this.IsValidUpdate(Constants.ApiVersion, latestBeta, this.Settings.UseBetaChannel)) - { + } else if (this.IsValidUpdate(Constants.ApiVersion, latestBeta, this.Settings.UseBetaChannel)) { updateFound = latestBeta; this.Monitor.Log($"You can update SMAPI to {latestBeta}: {Constants.HomePageUrl}", LogLevel.Alert); - } - else if (this.IsValidUpdate(Constants.ApiVersion, latestStable, this.Settings.UseBetaChannel)) - { + } else if (this.IsValidUpdate(Constants.ApiVersion, latestStable, this.Settings.UseBetaChannel)) { updateFound = latestStable; this.Monitor.Log($"You can update SMAPI to {latestStable}: {Constants.HomePageUrl}", LogLevel.Alert); - } - else + } else this.Monitor.Log(" SMAPI okay.", LogLevel.Trace); - } - catch (Exception ex) - { + } catch (Exception ex) { this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you won't be notified of new versions if this keeps happening.", LogLevel.Warn); this.Monitor.Log(ex is WebException && ex.InnerException == null ? $"Error: {ex.Message}" @@ -605,16 +556,13 @@ namespace StardewModdingAPI.Framework File.WriteAllText(Constants.UpdateMarker, updateFound.ToString()); // check mod versions - if (mods.Any()) - { - try - { + if (mods.Any()) { + try { HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.InvariantCultureIgnoreCase); // prepare search model List<ModSearchEntryModel> searchMods = new List<ModSearchEntryModel>(); - foreach (IModMetadata mod in mods) - { + foreach (IModMetadata mod in mods) { if (!mod.HasID() || suppressUpdateChecks.Contains(mod.Manifest.UniqueID)) continue; @@ -632,16 +580,14 @@ namespace StardewModdingAPI.Framework // extract update alerts & errors var updates = new List<Tuple<IModMetadata, ISemanticVersion, string>>(); var errors = new StringBuilder(); - foreach (IModMetadata mod in mods.OrderBy(p => p.DisplayName)) - { + foreach (IModMetadata mod in mods.OrderBy(p => p.DisplayName)) { // link to update-check data if (!mod.HasID() || !results.TryGetValue(mod.Manifest.UniqueID, out ModEntryModel result)) continue; mod.SetUpdateData(result); // handle errors - if (result.Errors != null && result.Errors.Any()) - { + if (result.Errors != null && result.Errors.Any()) { errors.AppendLine(result.Errors.Length == 1 ? $" {mod.DisplayName}: {result.Errors[0]}" : $" {mod.DisplayName}:\n - {string.Join("\n - ", result.Errors)}" @@ -669,23 +615,18 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Got update-check errors for some mods:\n" + errors.ToString().TrimEnd(), LogLevel.Trace); // show update alerts - if (updates.Any()) - { + if (updates.Any()) { this.Monitor.Newline(); this.Monitor.Log($"You can update {updates.Count} mod{(updates.Count != 1 ? "s" : "")}:", LogLevel.Alert); - foreach (var entry in updates) - { + foreach (var entry in updates) { IModMetadata mod = entry.Item1; ISemanticVersion newVersion = entry.Item2; string newUrl = entry.Item3; this.Monitor.Log($" {mod.DisplayName} {newVersion}: {newUrl}", LogLevel.Alert); } - } - else + } else this.Monitor.Log(" All mods up to date.", LogLevel.Trace); - } - catch (Exception ex) - { + } catch (Exception ex) { this.Monitor.Log("Couldn't check for new mod versions. This won't affect your game, but you won't be notified of mod updates if this keeps happening.", LogLevel.Warn); this.Monitor.Log(ex is WebException && ex.InnerException == null ? ex.Message @@ -700,8 +641,7 @@ namespace StardewModdingAPI.Framework /// <param name="currentVersion">The current semantic version.</param> /// <param name="newVersion">The target semantic version.</param> /// <param name="useBetaChannel">Whether the user enabled the beta channel and should be offered pre-release updates.</param> - private bool IsValidUpdate(ISemanticVersion currentVersion, ISemanticVersion newVersion, bool useBetaChannel) - { + private bool IsValidUpdate(ISemanticVersion currentVersion, ISemanticVersion newVersion, bool useBetaChannel) { return newVersion != null && newVersion.IsNewerThan(currentVersion) @@ -710,15 +650,11 @@ namespace StardewModdingAPI.Framework /// <summary>Create a directory path if it doesn't exist.</summary> /// <param name="path">The directory path.</param> - private void VerifyPath(string path) - { - try - { + private void VerifyPath(string path) { + try { if (!Directory.Exists(path)) Directory.CreateDirectory(path); - } - catch (Exception ex) - { + } catch (Exception ex) { // note: this happens before this.Monitor is initialised Console.WriteLine($"Couldn't create a path: {path}\n\n{ex.GetLogSummary()}"); } @@ -729,27 +665,23 @@ namespace StardewModdingAPI.Framework /// <param name="jsonHelper">The JSON helper with which to read mods' JSON files.</param> /// <param name="contentCore">The content manager to use for mod content.</param> /// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param> - private void LoadMods(IModMetadata[] mods, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase) - { + private void LoadMods(IModMetadata[] mods, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase) { this.Monitor.Log("Loading mods...", LogLevel.Trace); // load mods IDictionary<IModMetadata, Tuple<string, string>> skippedMods = new Dictionary<IModMetadata, Tuple<string, string>>(); - using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings)) - { + using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings)) { // init HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.InvariantCultureIgnoreCase); InterfaceProxyFactory proxyFactory = new InterfaceProxyFactory(); - void LogSkip(IModMetadata mod, string errorPhrase, string errorDetails) - { + void LogSkip(IModMetadata mod, string errorPhrase, string errorDetails) { skippedMods[mod] = Tuple.Create(errorPhrase, errorDetails); if (mod.Status != ModMetadataStatus.Failed) mod.SetStatus(ModMetadataStatus.Failed, errorPhrase); } // load mods - foreach (IModMetadata contentPack in mods) - { + foreach (IModMetadata contentPack in mods) { if (!this.TryLoadMod(contentPack, mods, modAssemblyLoader, proxyFactory, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out string errorPhrase, out string errorDetails)) LogSkip(contentPack, errorPhrase, errorDetails); } @@ -762,8 +694,7 @@ namespace StardewModdingAPI.Framework // log loaded mods this.Monitor.Log($"Loaded {loadedMods.Length} mods" + (loadedMods.Length > 0 ? ":" : "."), LogLevel.Info); - foreach (IModMetadata metadata in loadedMods.OrderBy(p => p.DisplayName)) - { + foreach (IModMetadata metadata in loadedMods.OrderBy(p => p.DisplayName)) { IManifest manifest = metadata.Manifest; this.Monitor.Log( $" {metadata.DisplayName} {manifest.Version}" @@ -775,13 +706,11 @@ namespace StardewModdingAPI.Framework this.Monitor.Newline(); // log loaded content packs - if (loadedContentPacks.Any()) - { + if (loadedContentPacks.Any()) { string GetModDisplayName(string id) => loadedMods.FirstOrDefault(p => p.HasID(id))?.DisplayName; this.Monitor.Log($"Loaded {loadedContentPacks.Length} content packs:", LogLevel.Info); - foreach (IModMetadata metadata in loadedContentPacks.OrderBy(p => p.DisplayName)) - { + foreach (IModMetadata metadata in loadedContentPacks.OrderBy(p => p.DisplayName)) { IManifest manifest = metadata.Manifest; this.Monitor.Log( $" {metadata.DisplayName} {manifest.Version}" @@ -801,11 +730,9 @@ namespace StardewModdingAPI.Framework this.ReloadTranslations(loadedMods); // initialise loaded non-content-pack mods - foreach (IModMetadata metadata in loadedMods) - { + foreach (IModMetadata metadata in loadedMods) { // add interceptors - if (metadata.Mod.Helper.Content is ContentHelper helper) - { + if (metadata.Mod.Helper.Content is ContentHelper helper) { // ReSharper disable SuspiciousTypeConversion.Global if (metadata.Mod is IAssetEditor editor) helper.ObservableAssetEditors.Add(editor); @@ -818,22 +745,17 @@ namespace StardewModdingAPI.Framework } // call entry method - try - { + try { IMod mod = metadata.Mod; mod.Entry(mod.Helper); - } - catch (Exception ex) - { + } catch (Exception ex) { metadata.LogAsMod($"Mod crashed on entry and might not work correctly. Technical details:\n{ex.GetLogSummary()}", LogLevel.Error); } // get mod API - try - { + try { object api = metadata.Mod.GetApi(); - if (api != null && !api.GetType().IsPublic) - { + if (api != null && !api.GetType().IsPublic) { api = null; this.Monitor.Log($"{metadata.DisplayName} provides an API instance with a non-public type. This isn't currently supported, so the API won't be available to other mods.", LogLevel.Warn); } @@ -841,31 +763,25 @@ namespace StardewModdingAPI.Framework if (api != null) this.Monitor.Log($" Found mod-provided API ({api.GetType().FullName}).", LogLevel.Trace); metadata.SetApi(api); - } - catch (Exception ex) - { + } catch (Exception ex) { this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error); } } // invalidate cache entries when needed // (These listeners are registered after Entry to avoid repeatedly reloading assets as mods initialise.) - foreach (IModMetadata metadata in loadedMods) - { - if (metadata.Mod.Helper.Content is ContentHelper helper) - { + foreach (IModMetadata metadata in loadedMods) { + if (metadata.Mod.Helper.Content is ContentHelper helper) { helper.ObservableAssetEditors.CollectionChanged += (sender, e) => { - if (e.NewItems?.Count > 0) - { + if (e.NewItems?.Count > 0) { this.Monitor.Log("Invalidating cache entries for new asset editors...", LogLevel.Trace); this.ContentCore.InvalidateCacheFor(e.NewItems.Cast<IAssetEditor>().ToArray(), new IAssetLoader[0]); } }; helper.ObservableAssetLoaders.CollectionChanged += (sender, e) => { - if (e.NewItems?.Count > 0) - { + if (e.NewItems?.Count > 0) { this.Monitor.Log("Invalidating cache entries for new asset loaders...", LogLevel.Trace); this.ContentCore.InvalidateCacheFor(new IAssetEditor[0], e.NewItems.Cast<IAssetLoader>().ToArray()); } @@ -876,8 +792,7 @@ namespace StardewModdingAPI.Framework // reset cache now if any editors or loaders were added during entry IAssetEditor[] editors = loadedMods.SelectMany(p => p.Mod.Helper.Content.AssetEditors).ToArray(); IAssetLoader[] loaders = loadedMods.SelectMany(p => p.Mod.Helper.Content.AssetLoaders).ToArray(); - if (editors.Any() || loaders.Any()) - { + if (editors.Any() || loaders.Any()) { this.Monitor.Log("Invalidating cached assets for new editors & loaders...", LogLevel.Trace); this.ContentCore.InvalidateCacheFor(editors, loaders); } @@ -898,8 +813,7 @@ namespace StardewModdingAPI.Framework /// <param name="errorReasonPhrase">The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable).</param> /// <param name="errorDetails">More detailed details about the error intended for developers (if any).</param> /// <returns>Returns whether the mod was successfully loaded.</returns> - private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet<string> suppressUpdateChecks, out string errorReasonPhrase, out string errorDetails) - { + private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet<string> suppressUpdateChecks, out string errorReasonPhrase, out string errorDetails) { errorDetails = null; // log entry @@ -918,8 +832,7 @@ namespace StardewModdingAPI.Framework mod.SetWarning(ModWarning.NoUpdateKeys); // validate status - if (mod.Status == ModMetadataStatus.Failed) - { + if (mod.Status == ModMetadataStatus.Failed) { this.Monitor.Log($" Failed: {mod.Error}", LogLevel.Trace); errorReasonPhrase = mod.Error; return false; @@ -935,12 +848,9 @@ namespace StardewModdingAPI.Framework // validate dependencies // Although dependences are validated before mods are loaded, a dependency may have failed to load. - if (mod.Manifest.Dependencies?.Any() == true) - { - foreach (IManifestDependency dependency in mod.Manifest.Dependencies.Where(p => p.IsRequired)) - { - if (this.ModRegistry.Get(dependency.UniqueID) == null) - { + if (mod.Manifest.Dependencies?.Any() == true) { + foreach (IManifestDependency dependency in mod.Manifest.Dependencies.Where(p => p.IsRequired)) { + if (this.ModRegistry.Get(dependency.UniqueID) == null) { string dependencyName = mods .FirstOrDefault(otherMod => otherMod.HasID(dependency.UniqueID)) ?.DisplayName ?? dependency.UniqueID; @@ -951,8 +861,7 @@ namespace StardewModdingAPI.Framework } // load as content pack - if (mod.IsContentPack) - { + if (mod.IsContentPack) { IManifest manifest = mod.Manifest; IMonitor monitor = this.GetSecondaryMonitor(mod.DisplayName); IContentHelper contentHelper = new ContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); @@ -965,8 +874,7 @@ namespace StardewModdingAPI.Framework } // load as mod - else - { + else { IManifest manifest = mod.Manifest; // load mod @@ -974,39 +882,31 @@ namespace StardewModdingAPI.Framework ? Path.Combine(mod.DirectoryPath, manifest.EntryDll) : null; Assembly modAssembly; - try - { + try { modAssembly = assemblyLoader.Load(mod, assemblyPath, assumeCompatible: mod.DataRecord?.Status == ModStatus.AssumeCompatible); this.ModRegistry.TrackAssemblies(mod, modAssembly); - } - catch (IncompatibleInstructionException) // details already in trace logs - { + } catch (IncompatibleInstructionException) // details already in trace logs + { string[] updateUrls = new[] { modDatabase.GetModPageUrlFor(manifest.UniqueID), "https://mods.smapi.io" }.Where(p => p != null).ToArray(); errorReasonPhrase = $"it's no longer compatible. Please check for a new version at {string.Join(" or ", updateUrls)}"; return false; - } - catch (SAssemblyLoadFailedException ex) - { + } catch (SAssemblyLoadFailedException ex) { errorReasonPhrase = $"it DLL couldn't be loaded: {ex.Message}"; return false; - } - catch (Exception ex) - { + } catch (Exception ex) { errorReasonPhrase = "its DLL couldn't be loaded."; errorDetails = $"Error: {ex.GetLogSummary()}"; return false; } // initialise mod - try - { + try { // get mod instance if (!this.TryLoadModEntry(modAssembly, out Mod modEntry, out errorReasonPhrase)) return false; // get content packs - IContentPack[] GetContentPacks() - { + IContentPack[] GetContentPacks() { if (!this.ModRegistry.AreAllModsLoaded) throw new InvalidOperationException("Can't access content packs before SMAPI finishes loading mods."); @@ -1031,8 +931,7 @@ namespace StardewModdingAPI.Framework IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(manifest.UniqueID, this.GameInstance.Multiplayer); ITranslationHelper translationHelper = new TranslationHelper(manifest.UniqueID, manifest.Name, contentCore.GetLocale(), contentCore.Language); - IContentPack CreateFakeContentPack(string packDirPath, IManifest packManifest) - { + IContentPack CreateFakeContentPack(string packDirPath, IManifest packManifest) { IMonitor packMonitor = this.GetSecondaryMonitor(packManifest.Name); IContentHelper packContentHelper = new ContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, packMonitor); return new ContentPack(packDirPath, packManifest, packContentHelper, this.Toolkit.JsonHelper); @@ -1050,9 +949,7 @@ namespace StardewModdingAPI.Framework mod.SetMod(modEntry); this.ModRegistry.Add(mod); return true; - } - catch (Exception ex) - { + } catch (Exception ex) { errorReasonPhrase = $"initialisation failed:\n{ex.GetLogSummary()}"; return false; } @@ -1062,8 +959,7 @@ namespace StardewModdingAPI.Framework /// <summary>Write a summary of mod warnings to the console and log.</summary> /// <param name="mods">The loaded mods.</param> /// <pa |
