summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/SCore.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-06-20 12:43:08 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-06-20 12:43:08 -0400
commite64ecc89f94641d4054162eff4943f660f43030f (patch)
treec43ca79f9947b3e16f946e1dc5fd1d02f70ce571 /src/SMAPI/Framework/SCore.cs
parentdf6e745c6b842290338317ed1d3e969ee222998c (diff)
parentcb9ff7019995eff92104703f097856d2523e02ce (diff)
downloadSMAPI-e64ecc89f94641d4054162eff4943f660f43030f.tar.gz
SMAPI-e64ecc89f94641d4054162eff4943f660f43030f.tar.bz2
SMAPI-e64ecc89f94641d4054162eff4943f660f43030f.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Framework/SCore.cs')
-rw-r--r--src/SMAPI/Framework/SCore.cs193
1 files changed, 105 insertions, 88 deletions
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index de9c955d..2b04b1dc 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -16,6 +16,7 @@ using System.Windows.Forms;
#endif
using Newtonsoft.Json;
using StardewModdingAPI.Events;
+using StardewModdingAPI.Framework.Commands;
using StardewModdingAPI.Framework.Events;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Logging;
@@ -172,7 +173,7 @@ namespace StardewModdingAPI.Framework
this.MonitorForGame = this.GetSecondaryMonitor("game");
SCore.PerformanceMonitor = new PerformanceMonitor(this.Monitor);
- this.EventManager = new EventManager(this.Monitor, this.ModRegistry, SCore.PerformanceMonitor);
+ this.EventManager = new EventManager(this.ModRegistry, SCore.PerformanceMonitor);
SCore.PerformanceMonitor.InitializePerformanceCounterCollections(this.EventManager);
SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
@@ -336,6 +337,8 @@ namespace StardewModdingAPI.Framework
// add headers
if (this.Settings.DeveloperMode)
this.Monitor.Log($"You have SMAPI for developers, so the console will be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Info);
+ if (this.Settings.RewriteInParallel)
+ this.Monitor.Log($"You enabled experimental parallel rewriting. This may result in faster startup times, but intermittent startup errors. You can disable it by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Info);
if (!this.Settings.CheckForUpdates)
this.Monitor.Log($"You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Warn);
if (!this.Monitor.WriteToConsole)
@@ -508,8 +511,10 @@ namespace StardewModdingAPI.Framework
{
// 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);
- this.GameInstance.CommandManager.Add(null, "reload_i18n", "Reloads translation files for all mods.\n\nUsage: reload_i18n", this.HandleCommand);
+ this.GameInstance.CommandManager
+ .Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor)
+ .Add(new HarmonySummaryCommand(), this.Monitor)
+ .Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor);
// start handling command line input
Thread inputThread = new Thread(() =>
@@ -978,7 +983,7 @@ namespace StardewModdingAPI.Framework
Assembly modAssembly;
try
{
- modAssembly = assemblyLoader.Load(mod, assemblyPath, assumeCompatible: mod.DataRecord?.Status == ModStatus.AssumeCompatible);
+ modAssembly = assemblyLoader.Load(mod, assemblyPath, assumeCompatible: mod.DataRecord?.Status == ModStatus.AssumeCompatible, rewriteInParallel: this.Settings.RewriteInParallel);
this.ModRegistry.TrackAssemblies(mod, modAssembly);
}
catch (IncompatibleInstructionException) // details already in trace logs
@@ -989,7 +994,7 @@ namespace StardewModdingAPI.Framework
}
catch (SAssemblyLoadFailedException ex)
{
- errorReasonPhrase = $"it DLL couldn't be loaded: {ex.Message}";
+ errorReasonPhrase = $"its DLL couldn't be loaded: {ex.Message}";
return false;
}
catch (Exception ex)
@@ -1129,67 +1134,115 @@ namespace StardewModdingAPI.Framework
// log warnings
if (modsWithWarnings.Any())
{
- // issue block format logic
- void LogWarningGroup(ModWarning warning, LogLevel logLevel, string heading, params string[] blurb)
- {
- IModMetadata[] matches = modsWithWarnings
- .Where(mod => mod.HasUnsuppressWarning(warning))
- .ToArray();
- if (!matches.Any())
- return;
-
- this.Monitor.Log(" " + heading, logLevel);
- this.Monitor.Log(" " + "".PadRight(50, '-'), logLevel);
- foreach (string line in blurb)
- this.Monitor.Log(" " + line, logLevel);
- this.Monitor.Newline();
- foreach (IModMetadata match in matches)
- this.Monitor.Log($" - {match.DisplayName}", logLevel);
- this.Monitor.Newline();
- }
-
- // supported issues
- LogWarningGroup(ModWarning.BrokenCodeLoaded, LogLevel.Error, "Broken mods",
+ // broken code
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.BrokenCodeLoaded, LogLevel.Error, "Broken mods",
"These mods have broken code, but you configured SMAPI to load them anyway. This may cause bugs,",
"errors, or crashes in-game."
);
- LogWarningGroup(ModWarning.ChangesSaveSerializer, LogLevel.Warn, "Changed save serializer",
+
+ // changes serializer
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.ChangesSaveSerializer, LogLevel.Warn, "Changed save serializer",
"These mods change the save serializer. They may corrupt your save files, or make them unusable if",
"you uninstall these mods."
);
- if (this.Settings.ParanoidWarnings)
- {
- LogWarningGroup(ModWarning.AccessesConsole, LogLevel.Warn, "Accesses the console directly",
- "These mods directly access the SMAPI console, and you enabled paranoid warnings. (Note that this may be",
- "legitimate and innocent usage; this warning is meaningless without further investigation.)"
- );
- LogWarningGroup(ModWarning.AccessesFilesystem, LogLevel.Warn, "Accesses filesystem directly",
- "These mods directly access the filesystem, and you enabled paranoid warnings. (Note that this may be",
- "legitimate and innocent usage; this warning is meaningless without further investigation.)"
- );
- LogWarningGroup(ModWarning.AccessesShell, LogLevel.Warn, "Accesses shell/process directly",
- "These mods directly access the OS shell or processes, and you enabled paranoid warnings. (Note that",
- "this may be legitimate and innocent usage; this warning is meaningless without further investigation.)"
- );
- }
- LogWarningGroup(ModWarning.PatchesGame, LogLevel.Info, "Patched game code",
+
+ // patched game code
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.PatchesGame, LogLevel.Info, "Patched game code",
"These mods directly change the game code. They're more likely to cause errors or bugs in-game; if",
"your game has issues, try removing these first. Otherwise you can ignore this warning."
);
- LogWarningGroup(ModWarning.UsesUnvalidatedUpdateTick, LogLevel.Info, "Bypassed safety checks",
+
+ // unvalidated update tick
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.UsesUnvalidatedUpdateTick, LogLevel.Info, "Bypassed safety checks",
"These mods bypass SMAPI's normal safety checks, so they're more likely to cause errors or save",
"corruption. If your game has issues, try removing these first."
);
- LogWarningGroup(ModWarning.NoUpdateKeys, LogLevel.Debug, "No update keys",
+
+ // paranoid warnings
+ if (this.Settings.ParanoidWarnings)
+ {
+ this.LogModWarningGroup(
+ modsWithWarnings,
+ match: mod => mod.HasUnsuppressedWarnings(ModWarning.AccessesConsole, ModWarning.AccessesFilesystem, ModWarning.AccessesShell),
+ level: LogLevel.Debug,
+ heading: "Direct system access",
+ blurb: new[]
+ {
+ "You enabled paranoid warnings and these mods directly access the filesystem, shells/processes, or",
+ "SMAPI console. (This is usually legitimate and innocent usage; this warning is only useful for",
+ "further investigation.)"
+ },
+ modLabel: mod =>
+ {
+ List<string> labels = new List<string>();
+ if (mod.HasUnsuppressedWarnings(ModWarning.AccessesConsole))
+ labels.Add("console");
+ if (mod.HasUnsuppressedWarnings(ModWarning.AccessesFilesystem))
+ labels.Add("files");
+ if (mod.HasUnsuppressedWarnings(ModWarning.AccessesShell))
+ labels.Add("shells/processes");
+
+ return $"{mod.DisplayName} ({string.Join(", ", labels)})";
+ }
+ );
+ }
+
+ // no update keys
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.NoUpdateKeys, LogLevel.Debug, "No update keys",
"These mods have no update keys in their manifest. SMAPI may not notify you about updates for these",
"mods. Consider notifying the mod authors about this problem."
);
- LogWarningGroup(ModWarning.UsesDynamic, LogLevel.Debug, "Not crossplatform",
+
+ // not crossplatform
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.UsesDynamic, LogLevel.Debug, "Not crossplatform",
"These mods use the 'dynamic' keyword, and won't work on Linux/Mac."
);
}
}
+ /// <summary>Write a mod warning group to the console and log.</summary>
+ /// <param name="mods">The mods to search.</param>
+ /// <param name="match">Matches mods to include in the warning group.</param>
+ /// <param name="level">The log level for the logged messages.</param>
+ /// <param name="heading">A brief heading label for the group.</param>
+ /// <param name="blurb">A detailed explanation of the warning, split into lines.</param>
+ /// <param name="modLabel">Formats the mod label, or <c>null</c> to use the <see cref="IModMetadata.DisplayName"/>.</param>
+ private void LogModWarningGroup(IModMetadata[] mods, Func<IModMetadata, bool> match, LogLevel level, string heading, string[] blurb, Func<IModMetadata, string> modLabel = null)
+ {
+ // get matching mods
+ string[] modLabels = mods
+ .Where(match)
+ .Select(mod => modLabel?.Invoke(mod) ?? mod.DisplayName)
+ .OrderBy(p => p)
+ .ToArray();
+ if (!modLabels.Any())
+ return;
+
+ // log header/blurb
+ this.Monitor.Log(" " + heading, level);
+ this.Monitor.Log(" " + "".PadRight(50, '-'), level);
+ foreach (string line in blurb)
+ this.Monitor.Log(" " + line, level);
+ this.Monitor.Newline();
+
+ // log mod list
+ foreach (string label in modLabels)
+ this.Monitor.Log($" - {label}", level);
+
+ this.Monitor.Newline();
+ }
+
+ /// <summary>Write a mod warning group to the console and log.</summary>
+ /// <param name="mods">The mods to search.</param>
+ /// <param name="warning">The mod warning to match.</param>
+ /// <param name="level">The log level for the logged messages.</param>
+ /// <param name="heading">A brief heading label for the group.</param>
+ /// <param name="blurb">A detailed explanation of the warning, split into lines.</param>
+ void LogModWarningGroup(IModMetadata[] mods, ModWarning warning, LogLevel level, string heading, params string[] blurb)
+ {
+ this.LogModWarningGroup(mods, mod => mod.HasUnsuppressedWarnings(warning), level, heading, blurb);
+ }
+
/// <summary>Load a mod's entry class.</summary>
/// <param name="modAssembly">The mod assembly.</param>
/// <param name="mod">The loaded instance.</param>
@@ -1225,6 +1278,12 @@ namespace StardewModdingAPI.Framework
}
/// <summary>Reload translations for all mods.</summary>
+ private void ReloadTranslations()
+ {
+ this.ReloadTranslations(this.ModRegistry.GetAll());
+ }
+
+ /// <summary>Reload translations for the given mods.</summary>
/// <param name="mods">The mods for which to reload translations.</param>
private void ReloadTranslations(IEnumerable<IModMetadata> mods)
{
@@ -1309,48 +1368,6 @@ namespace StardewModdingAPI.Framework
return translations;
}
- /// <summary>The method called when the user submits a core SMAPI command in the console.</summary>
- /// <param name="name">The command name.</param>
- /// <param name="arguments">The command arguments.</param>
- private void HandleCommand(string name, string[] arguments)
- {
- switch (name)
- {
- case "help":
- if (arguments.Any())
- {
- Command result = this.GameInstance.CommandManager.Get(arguments[0]);
- if (result == null)
- this.Monitor.Log("There's no command with that name.", LogLevel.Error);
- else
- this.Monitor.Log($"{result.Name}: {result.Documentation}{(result.Mod != null ? $"\n(Added by {result.Mod.DisplayName}.)" : "")}", LogLevel.Info);
- }
- else
- {
- string message = "The following commands are registered:\n";
- IGrouping<string, string>[] groups = (from command in this.GameInstance.CommandManager.GetAll() orderby command.Mod?.DisplayName, command.Name group command.Name by command.Mod?.DisplayName).ToArray();
- foreach (var group in groups)
- {
- string modName = group.Key ?? "SMAPI";
- string[] commandNames = group.ToArray();
- message += $"{modName}:\n {string.Join("\n ", commandNames)}\n\n";
- }
- message += "For more information about a command, type 'help command_name'.";
-
- this.Monitor.Log(message, LogLevel.Info);
- }
- break;
-
- case "reload_i18n":
- this.ReloadTranslations(this.ModRegistry.GetAll(contentPacks: false));
- this.Monitor.Log("Reloaded translation files for all mods. This only affects new translations the mods fetch; if they cached some text, it may not be updated.", LogLevel.Info);
- break;
-
- default:
- throw new NotSupportedException($"Unrecognized core SMAPI command '{name}'.");
- }
- }
-
/// <summary>Redirect messages logged directly to the console to the given monitor.</summary>
/// <param name="gameMonitor">The monitor with which to log messages as the game.</param>
/// <param name="message">The message to log.</param>