summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/Logging/LogManager.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-05-03 18:11:31 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-05-03 18:11:31 -0400
commit5d3d919d490fd414fe9647e566e92c71d7f64509 (patch)
treee1eab3352287ef04b5de4cdc28b550e255d58c3f /src/SMAPI/Framework/Logging/LogManager.cs
parentc48f6d78cc412c5f2e40a8b460b7b3c1c993c51a (diff)
parent3447e2f575c2c83af729777e4d37e93f4c2a6467 (diff)
downloadSMAPI-5d3d919d490fd414fe9647e566e92c71d7f64509.tar.gz
SMAPI-5d3d919d490fd414fe9647e566e92c71d7f64509.tar.bz2
SMAPI-5d3d919d490fd414fe9647e566e92c71d7f64509.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Framework/Logging/LogManager.cs')
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs194
1 files changed, 134 insertions, 60 deletions
diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs
index 243ca3ae..a4df3c18 100644
--- a/src/SMAPI/Framework/Logging/LogManager.cs
+++ b/src/SMAPI/Framework/Logging/LogManager.cs
@@ -283,13 +283,16 @@ namespace StardewModdingAPI.Framework.Logging
/// <param name="customSettings">The custom SMAPI settings.</param>
public void LogIntro(string modsPath, IDictionary<string, object> customSettings)
{
- // get platform label
- string platformLabel = EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform);
- if ((Constants.GameFramework == GameFramework.Xna) != (Constants.Platform == Platform.Windows))
- platformLabel += $" with {Constants.GameFramework}";
+ // log platform & patches
+ {
+ this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info);
+
+ string[] patchLabels = this.GetPatchLabels().ToArray();
+ if (patchLabels.Any())
+ this.Monitor.Log($"Detected custom version: {string.Join(", ", patchLabels)}", LogLevel.Info);
+ }
- // init logging
- this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {platformLabel}", LogLevel.Info);
+ // log basic info
this.Monitor.Log($"Mods go here: {modsPath}", LogLevel.Info);
if (modsPath != Constants.DefaultModsPath)
this.Monitor.Log("(Using custom --mods-path argument.)");
@@ -406,6 +409,20 @@ namespace StardewModdingAPI.Framework.Logging
gameMonitor.Log(message, level);
}
+ /// <summary>Get human-readable labels to log for detected SMAPI and Stardew Valley customizations.</summary>
+ private IEnumerable<string> GetPatchLabels()
+ {
+ // custom game framework
+ if (EarlyConstants.IsWindows64BitHack)
+ yield return $"running 64-bit SMAPI with {Constants.GameFramework}";
+ else if ((Constants.GameFramework == GameFramework.Xna) != (Constants.Platform == Platform.Windows))
+ yield return $"running {Constants.GameFramework}";
+
+ // patched by Stardew64Installer
+ if (Constants.IsPatchedByStardew64Installer(out ISemanticVersion patchedByVersion))
+ yield return $"patched by Stardew64Installer {patchedByVersion}";
+ }
+
/// <summary>Write a summary of mod warnings to the console and log.</summary>
/// <param name="mods">The loaded mods.</param>
/// <param name="skippedMods">The mods which could not be loaded.</param>
@@ -426,67 +443,38 @@ namespace StardewModdingAPI.Framework.Logging
// log skipped mods
if (skippedMods.Any())
{
- // get logging logic
- HashSet<string> loggedDuplicateIds = new HashSet<string>();
- void LogSkippedMod(IModMetadata mod)
- {
- string message = $" - {mod.DisplayName}{(mod.Manifest?.Version != null ? " " + mod.Manifest.Version.ToString() : "")} because {mod.Error}";
+ var loggedDuplicateIds = new HashSet<string>();
- // handle duplicate mods
- // (log first duplicate only, don't show redundant version)
- if (mod.FailReason == ModFailReason.Duplicate && mod.HasManifest())
+ this.Monitor.Log(" Skipped mods", LogLevel.Error);
+ this.Monitor.Log(" " + "".PadRight(50, '-'), LogLevel.Error);
+ this.Monitor.Log(" These mods could not be added to your game.", LogLevel.Error);
+ this.Monitor.Newline();
+ foreach (var list in this.GroupFailedModsByPriority(skippedMods))
+ {
+ if (list.Any())
{
- if (!loggedDuplicateIds.Add(mod.Manifest.UniqueID))
- return; // already logged
+ foreach (IModMetadata mod in list.OrderBy(p => p.DisplayName))
+ {
+ string message = $" - {mod.DisplayName}{(" " + mod.Manifest?.Version?.ToString()).TrimEnd()} because {mod.Error}";
- message = $" - {mod.DisplayName} because {mod.Error}";
- }
+ // duplicate mod: log first one only, don't show redundant version
+ if (mod.FailReason == ModFailReason.Duplicate && mod.HasManifest())
+ {
+ if (loggedDuplicateIds.Add(mod.Manifest.UniqueID))
+ continue; // already logged
- // log message
- this.Monitor.Log(message, LogLevel.Error);
- if (mod.ErrorDetails != null)
- this.Monitor.Log($" ({mod.ErrorDetails})");
- }
+ message = $" - {mod.DisplayName} because {mod.Error}";
+ }
- // group mods
- List<IModMetadata> skippedDependencies = new List<IModMetadata>();
- List<IModMetadata> otherSkippedMods = new List<IModMetadata>();
- {
- // track broken dependencies
- HashSet<string> skippedDependencyIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- HashSet<string> skippedModIds = new HashSet<string>(from mod in skippedMods where mod.HasID() select mod.Manifest.UniqueID, StringComparer.OrdinalIgnoreCase);
- foreach (IModMetadata mod in skippedMods)
- {
- foreach (string requiredId in skippedModIds.Intersect(mod.GetRequiredModIds()))
- skippedDependencyIds.Add(requiredId);
- }
+ // log message
+ this.Monitor.Log(message, LogLevel.Error);
+ if (mod.ErrorDetails != null)
+ this.Monitor.Log($" ({mod.ErrorDetails})");
+ }
- // collect mod groups
- foreach (IModMetadata mod in skippedMods)
- {
- if (mod.HasID() && skippedDependencyIds.Contains(mod.Manifest.UniqueID))
- skippedDependencies.Add(mod);
- else
- otherSkippedMods.Add(mod);
+ this.Monitor.Newline();
}
}
-
- // log skipped mods
- this.Monitor.Log(" Skipped mods", LogLevel.Error);
- this.Monitor.Log(" " + "".PadRight(50, '-'), LogLevel.Error);
- this.Monitor.Log(" These mods could not be added to your game.", LogLevel.Error);
- this.Monitor.Newline();
-
- if (skippedDependencies.Any())
- {
- foreach (IModMetadata mod in skippedDependencies.OrderBy(p => p.DisplayName))
- LogSkippedMod(mod);
- this.Monitor.Newline();
- }
-
- foreach (IModMetadata mod in otherSkippedMods.OrderBy(p => p.DisplayName))
- LogSkippedMod(mod);
- this.Monitor.Newline();
}
// log warnings
@@ -553,9 +541,95 @@ namespace StardewModdingAPI.Framework.Logging
// not crossplatform
this.LogModWarningGroup(modsWithWarnings, ModWarning.UsesDynamic, LogLevel.Debug, "Not crossplatform",
- "These mods use the 'dynamic' keyword, and won't work on Linux/Mac."
+ "These mods use the 'dynamic' keyword, and won't work on Linux/macOS."
+ );
+ }
+ }
+
+ /// <summary>Group failed mods by the priority players should update them, where mods in earlier groups are more likely to fix multiple mods.</summary>
+ /// <param name="failedMods">The failed mods to group.</param>
+ private IEnumerable<IList<IModMetadata>> GroupFailedModsByPriority(IList<IModMetadata> failedMods)
+ {
+ var failedOthers = failedMods.ToList();
+ var skippedModIds = new HashSet<string>(from mod in failedMods where mod.HasID() select mod.Manifest.UniqueID, StringComparer.OrdinalIgnoreCase);
+
+ // group B: dependencies which failed
+ var failedOtherDependencies = new List<IModMetadata>();
+ {
+ // get failed dependency IDs
+ var skippedDependencyIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ foreach (IModMetadata mod in failedMods)
+ {
+ foreach (string requiredId in skippedModIds.Intersect(mod.GetRequiredModIds()))
+ skippedDependencyIds.Add(requiredId);
+ }
+
+ // group matching mods
+ this.FilterThrough(
+ fromList: failedOthers,
+ toList: failedOtherDependencies,
+ match: mod => mod.HasID() && skippedDependencyIds.Contains(mod.Manifest.UniqueID)
);
}
+
+ // group A: failed root dependencies which other dependencies need
+ var failedRootDependencies = new List<IModMetadata>();
+ {
+ var skippedDependencyIds = new HashSet<string>(failedOtherDependencies.Select(p => p.Manifest.UniqueID));
+ this.FilterThrough(
+ fromList: failedOtherDependencies,
+ toList: failedRootDependencies,
+ match: mod =>
+ {
+ // has no failed dependency
+ foreach (string requiredId in mod.GetRequiredModIds())
+ {
+ if (skippedDependencyIds.Contains(requiredId))
+ return false;
+ }
+
+ // another dependency depends on this mod
+ bool isDependedOn = false;
+ foreach (IModMetadata other in failedOtherDependencies)
+ {
+ if (other.HasRequiredModId(mod.Manifest.UniqueID, includeOptional: false))
+ {
+ isDependedOn = true;
+ break;
+ }
+ }
+
+ return isDependedOn;
+ }
+ );
+ }
+
+ // return groups
+ return new[]
+ {
+ failedRootDependencies,
+ failedOtherDependencies,
+ failedOthers
+ };
+ }
+
+ /// <summary>Filter matching items from one list and add them to the other.</summary>
+ /// <typeparam name="TItem">The list item type.</typeparam>
+ /// <param name="fromList">The list to filter.</param>
+ /// <param name="toList">The list to which to add filtered items.</param>
+ /// <param name="match">Matches items to filter through.</param>
+ private void FilterThrough<TItem>(IList<TItem> fromList, IList<TItem> toList, Func<TItem, bool> match)
+ {
+ for (int i = 0; i < fromList.Count; i++)
+ {
+ TItem item = fromList[i];
+ if (match(item))
+ {
+ toList.Add(item);
+ fromList.RemoveAt(i);
+ i--;
+ }
+ }
}
/// <summary>Write a mod warning group to the console and log.</summary>