diff --git a/docs/release-notes.md b/docs/release-notes.md
index 46c0951d..3bc4f46e 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -3,6 +3,8 @@
# Release notes
## Upcoming release
* For players:
+ * Added option to disable Harmony/MonoMod fix for players with certain crashes.
+ _This should only be changed if no installed mods use Harmony._
* Fixed split-screen crash for non-English players when mods added translated variants of content assets.
* SMAPI no longer rewrites mods which use Harmony 1.x, to help reduce Harmony crashes.
_This should affect very few mods that still work otherwise, and any Harmony mod updated after July 2021 should be unaffected._
diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs
index 4d17efe9..af59068d 100644
--- a/src/SMAPI/Framework/Logging/LogManager.cs
+++ b/src/SMAPI/Framework/Logging/LogManager.cs
@@ -261,7 +261,8 @@ namespace StardewModdingAPI.Framework.Logging
/// The mods which could not be loaded.
/// Whether to log issues for mods which directly use potentially sensitive .NET APIs like file or shell access.
/// Whether to include more technical details about broken mods in the TRACE logs. This is mainly useful for creating compatibility rewriters.
- public void LogModInfo(IModMetadata[] loaded, IModMetadata[] loadedContentPacks, IModMetadata[] loadedMods, IModMetadata[] skippedMods, bool logParanoidWarnings, bool logTechnicalDetailsForBrokenMods)
+ /// Whether Harmony was fixed to work with Stardew Valley.
+ public void LogModInfo(IModMetadata[] loaded, IModMetadata[] loadedContentPacks, IModMetadata[] loadedMods, IModMetadata[] skippedMods, bool logParanoidWarnings, bool logTechnicalDetailsForBrokenMods, bool hasHarmonyFix)
{
// log loaded mods
this.Monitor.Log($"Loaded {loadedMods.Length} mods" + (loadedMods.Length > 0 ? ":" : "."), LogLevel.Info);
@@ -300,7 +301,7 @@ namespace StardewModdingAPI.Framework.Logging
}
// log mod warnings
- this.LogModWarnings(loaded, skippedMods, logParanoidWarnings, logTechnicalDetailsForBrokenMods);
+ this.LogModWarnings(loaded, skippedMods, logParanoidWarnings, logTechnicalDetailsForBrokenMods, hasHarmonyFix);
}
///
@@ -318,8 +319,9 @@ namespace StardewModdingAPI.Framework.Logging
/// The mods which could not be loaded.
/// Whether to log issues for mods which directly use potentially sensitive .NET APIs like file or shell access.
/// Whether to include more technical details about broken mods in the TRACE logs. This is mainly useful for creating compatibility rewriters.
+ /// Whether Harmony was fixed to work with Stardew Valley.
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "Manifests aren't guaranteed non-null at this point in the loading process.")]
- private void LogModWarnings(IEnumerable mods, IModMetadata[] skippedMods, bool logParanoidWarnings, bool logTechnicalDetailsForBrokenMods)
+ private void LogModWarnings(IEnumerable mods, IModMetadata[] skippedMods, bool logParanoidWarnings, bool logTechnicalDetailsForBrokenMods, bool hasHarmonyFix)
{
// get mods with warnings
IModMetadata[] modsWithWarnings = mods
@@ -390,6 +392,15 @@ namespace StardewModdingAPI.Framework.Logging
"errors, or crashes in-game."
);
+ // missing Harmony fix
+ if (!hasHarmonyFix)
+ {
+ this.LogModWarningGroup(modsWithWarnings, ModWarning.PatchesGame, LogLevel.Warn, "Patched game code without Harmony fix",
+ $"These mods directly change the game code using Harmony, but you disabled the {nameof(SConfig.FixHarmony)} option.",
+ "The game will probably crash soon."
+ );
+ }
+
// 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",
@@ -397,10 +408,13 @@ namespace StardewModdingAPI.Framework.Logging
);
// 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."
- );
+ if (hasHarmonyFix)
+ {
+ 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."
+ );
+ }
// unvalidated update tick
this.LogModWarningGroup(modsWithWarnings, ModWarning.UsesUnvalidatedUpdateTick, LogLevel.Info, "Bypassed safety checks",
diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs
index 470e5501..40bdb130 100644
--- a/src/SMAPI/Framework/Models/SConfig.cs
+++ b/src/SMAPI/Framework/Models/SConfig.cs
@@ -24,6 +24,7 @@ namespace StardewModdingAPI.Framework.Models
[nameof(LogNetworkTraffic)] = false,
[nameof(LogTechnicalDetailsForBrokenMods)] = false,
[nameof(RewriteMods)] = true,
+ [nameof(FixHarmony)] = true,
[nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux,
[nameof(SuppressHarmonyDebugMode)] = true
};
@@ -71,6 +72,9 @@ namespace StardewModdingAPI.Framework.Models
/// Whether SMAPI should rewrite mods for compatibility.
public bool RewriteMods { get; set; }
+ /// Whether to apply fixes to Harmony so it works with Stardew Valley.
+ public bool FixHarmony { get; set; }
+
/// Whether to make SMAPI file APIs case-insensitive, even on Linux.
public bool UseCaseInsensitivePaths { get; set; }
@@ -109,6 +113,7 @@ namespace StardewModdingAPI.Framework.Models
///
///
///
+ ///
///
///
///
@@ -117,7 +122,7 @@ namespace StardewModdingAPI.Framework.Models
///
///
///
- public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, bool? logTechnicalDetailsForBrokenMods, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate)
+ public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? fixHarmony, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, bool? logTechnicalDetailsForBrokenMods, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate)
{
this.DeveloperMode = developerMode;
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
@@ -128,6 +133,7 @@ namespace StardewModdingAPI.Framework.Models
this.WebApiBaseUrl = webApiBaseUrl;
this.VerboseLogging = new HashSet(verboseLogging ?? Array.Empty(), StringComparer.OrdinalIgnoreCase);
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)];
+ this.FixHarmony = fixHarmony ?? (bool)SConfig.DefaultValues[nameof(this.FixHarmony)];
this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)];
this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)];
this.LogTechnicalDetailsForBrokenMods = logTechnicalDetailsForBrokenMods ?? (bool)SConfig.DefaultValues[nameof(this.LogTechnicalDetailsForBrokenMods)];
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 9401af1e..e82c8ceb 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -280,7 +280,8 @@ namespace StardewModdingAPI.Framework
GameRunner.instance = this.Game;
// fix Harmony for mods
- MiniMonoModHotfix.Apply();
+ if (this.Settings.FixHarmony)
+ MiniMonoModHotfix.Apply();
// set window titles
this.UpdateWindowTitles();
@@ -1743,7 +1744,7 @@ namespace StardewModdingAPI.Framework
this.ModRegistry.AreAllModsLoaded = true;
// log mod info
- this.LogManager.LogModInfo(loaded, loadedContentPacks, loadedMods, skippedMods.ToArray(), this.Settings.ParanoidWarnings, this.Settings.LogTechnicalDetailsForBrokenMods);
+ this.LogManager.LogModInfo(loaded, loadedContentPacks, loadedMods, skippedMods.ToArray(), this.Settings.ParanoidWarnings, this.Settings.LogTechnicalDetailsForBrokenMods, this.Settings.FixHarmony);
// initialize translations
this.ReloadTranslations(loaded);
diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json
index 26a3caaa..55f9869b 100644
--- a/src/SMAPI/SMAPI.config.json
+++ b/src/SMAPI/SMAPI.config.json
@@ -53,6 +53,13 @@ in future SMAPI versions.
*/
"RewriteMods": true,
+ /**
+ * Whether to fix the library mods use to patch game code, so it works with Stardew Valley.
+ *
+ * If you disable this, mods which use Harmony are likely to cause game crashes.
+ */
+ "FixHarmony": true,
+
/**
* Whether to make SMAPI file APIs case-insensitive (even if the filesystem is case-sensitive).
*