diff options
| author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-01-03 14:31:27 -0500 |
|---|---|---|
| committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-01-03 14:31:27 -0500 |
| commit | 04c6733adae9ce568aefb5d9dee6101097e994c5 (patch) | |
| tree | c93f0650f6f79a95016c29526f8af437ad91a815 /src | |
| parent | 48bb1581a6adeabfefbdd774011796e09a07aae2 (diff) | |
| parent | 2b3f0506a16622b25a702aae250e10005287c4f4 (diff) | |
| download | SMAPI-04c6733adae9ce568aefb5d9dee6101097e994c5.tar.gz SMAPI-04c6733adae9ce568aefb5d9dee6101097e994c5.tar.bz2 SMAPI-04c6733adae9ce568aefb5d9dee6101097e994c5.zip | |
Merge branch 'develop' into stable
Diffstat (limited to 'src')
20 files changed, 337 insertions, 162 deletions
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 61c610d0..13e85c70 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.8.1", + "Version": "3.8.2", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.8.1" + "MinimumApiVersion": "3.8.2" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 7cf63e66..475ec165 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.8.1", + "Version": "3.8.2", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.8.1" + "MinimumApiVersion": "3.8.2" } diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs index b3954693..44422f01 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs @@ -71,6 +71,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData // non-manifest fields case ModDataFieldKey.AlternativeUrl: case ModDataFieldKey.StatusReasonPhrase: + case ModDataFieldKey.StatusReasonDetails: case ModDataFieldKey.Status: return false; diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs index 09dd0cc5..068291aa 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs @@ -13,6 +13,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData Status, /// <summary>A reason phrase for the <see cref="Status"/>, or <c>null</c> to use the default reason.</summary> - StatusReasonPhrase + StatusReasonPhrase, + + /// <summary>Technical details shown in TRACE logs for the <see cref="Status"/>, or <c>null</c> to omit it.</summary> + StatusReasonDetails } } diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs index 3201c421..f28f6afe 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs @@ -107,6 +107,11 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData case ModDataFieldKey.StatusReasonPhrase: parsed.StatusReasonPhrase = field.Value; break; + + // status technical reason + case ModDataFieldKey.StatusReasonDetails: + parsed.StatusReasonDetails = field.Value; + break; } } diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs index 598da66a..f0282eb4 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs @@ -24,6 +24,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// <summary>A reason phrase for the <see cref="Status"/>, or <c>null</c> to use the default reason.</summary> public string StatusReasonPhrase { get; set; } + /// <summary>Technical details shown in TRACE logs for the <see cref="Status"/>, or <c>null</c> to omit it.</summary> + public string StatusReasonDetails { get; set; } + /// <summary>The upper version for which the <see cref="Status"/> applies (if any).</summary> public ISemanticVersion StatusUpperVersion { get; set; } } diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs index 031afbb0..00db9903 100644 --- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs +++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs @@ -132,5 +132,11 @@ namespace StardewModdingAPI.Toolkit.Serialization { return JsonConvert.SerializeObject(model, formatting, this.JsonSettings); } + + /// <summary>Get a low-level JSON serializer matching the <see cref="JsonSettings"/>.</summary> + public JsonSerializer GetSerializer() + { + return JsonSerializer.CreateDefault(this.JsonSettings); + } } } diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json index 2a81e12a..e75a6bc0 100644 --- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json @@ -42,6 +42,9 @@ * - StatusReasonPhrase: a message to show to the player explaining why the mod can't be loaded * (if applicable). If blank, will default to a generic not-compatible message. * + * - StatusReasonDetails: a technical reason shown in TRACE logs, indicating why the status + * was overridden. If not provided, it defaults to the StatusReasonPhrase or 'no reason given'. + * * - AlternativeUrl: a URL where the player can find an unofficial update or alternative if the * mod is no longer compatible. */ @@ -76,8 +79,7 @@ "JSON Assets": { "ID": "spacechase0.JsonAssets", - "Default | UpdateKey": "Nexus:1720", - "1.3.1 | Status": "AssumeBroken" // causes runtime crashes + "Default | UpdateKey": "Nexus:1720" }, "Mail Framework": { @@ -87,8 +89,7 @@ "MTN": { "ID": "SgtPickles.MTN", - "Default | UpdateKey": "Nexus:2256", - "~1.2.6 | Status": "AssumeBroken" // replaces Game1.multiplayer, which breaks SMAPI's multiplayer API. + "Default | UpdateKey": "Nexus:2256" }, "PyTK": { @@ -157,42 +158,76 @@ }, /********* - ** Broke in SDV 1.5 + ** Broke in SDV 1.5 (Content Patcher packs) + *********/ + "mi.Mermaids": { + "ID": "mi.Mermaids", + "~1.0.0 | Status": "AssumeBroken", + "~1.0.0 | StatusReasonDetails": "causes errors due to removed Stardew Valley 1.5 content" + }, + + /********* + ** Broke in SDV 1.5 (SMAPI mods) *********/ "Audio Devices": { "ID": "maxvollmer.audiodevices", - "~2.0.0 | Status": "AssumeBroken" // causes crash to desktop when starting the game + "~2.0.0 | Status": "AssumeBroken", + "~2.0.0 | StatusReasonDetails": "causes crash to desktop when starting the game" + }, + + "ChestEx": { + "ID": "berkayylmao.ChestEx", + "~1.3.4 | Status": "AssumeBroken", + "~1.3.4 | StatusReasonDetails": "has no effect due to changes in Stardew Valley 1.5, causes crashes in other mods like Chests Anywhere" }, + "Custom Furniture": { + "ID": "Platonymous.CustomFurniture", + "~0.11.2 | Status": "AssumeBroken", + "~0.11.2 | StatusReasonDetails": "causes errors and custom furniture no longer work in Stardew Valley 1.5" + }, + "Custom Localization": { "ID": "ZaneYork.CustomLocalization", "FormerIDs": "SMAPI.CustomLocalization", // changed in 1.0.1 - "~1.1 | Status": "AssumeBroken" // reflection error for _localizedAssets field + "~1.1 | Status": "AssumeBroken", + "~1.1 | StatusReasonDetails": "reflection error due to renamed _localizedAssets field" + }, + + "Geode Info Menu": { + "ID": "cat.geodeinfomenu", + "~1.5.2 | Status": "AssumeBroken", + "~1.5.2 | StatusReasonDetails": "shows no info, freezes game if you try to search" }, "Mod Settings Tab": { "ID": "GilarF.ModSettingsTab", - "~0.2.1 | Status": "AssumeBroken" // fails extending title menu + "~0.2.1 | Status": "AssumeBroken", + "~0.2.1 | StatusReasonDetails": "fails extending title menu" }, "More Grass": { "ID": "EpicBellyFlop45.MoreGrass", - "~1.0.8 | Status": "AssumeBroken" // crashes save load + "~1.0.8 | Status": "AssumeBroken", + "~1.0.8 | StatusReasonDetails": "crashes on save load" }, "Movement Speed": { "ID": "bcmpinc.MovementSpeed", - "~3.0.0 | Status": "AssumeBroken" // transpiler errors + "~3.0.0 | Status": "AssumeBroken", + "~3.0.0 | StatusReasonDetails": "broken due to transpiler errors" }, "Tree Spread": { "ID": "bcmpinc.TreeSpread", - "~3.0.0 | Status": "AssumeBroken" // transpiler errors + "~3.0.0 | Status": "AssumeBroken", + "~3.0.0 | StatusReasonDetails": "broken due to transpiler errors" }, "TreeTransplant": { "ID": "TreeTransplant", - "~1.0.9 | Status": "AssumeBroken" // causes AccessViolationException which prevents game launch + "~1.0.9 | Status": "AssumeBroken", + "~3.0.0 | StatusReasonDetails": "breaks game launch due to AccessViolationException" }, /********* @@ -200,17 +235,14 @@ *********/ "Auto Quality Patch": { "ID": "SilentOak.AutoQualityPatch", - "~2.1.3-unofficial.7-mizzion | Status": "AssumeBroken" // runtime errors + "~2.1.3-unofficial.7-mizzion | Status": "AssumeBroken", + "~2.1.3-unofficial.7-mizzion | StatusReasonDetails": "broken due to runtime errors" }, "Fix Dice": { "ID": "ashley.fixdice", - "~1.1.2 | Status": "AssumeBroken" // crashes game on startup - }, - - "Grass Growth": { - "ID": "bcmpinc.GrassGrowth", - "~1.0 | Status": "AssumeBroken" + "~1.1.2 | Status": "AssumeBroken", + "~1.1.2 | StatusReasonDetails": "crashes game on startup" }, "Invite Code Mod": { @@ -238,11 +270,6 @@ "~2.15 | Status": "AssumeBroken" }, - "Yet Another Harvest With Scythe Mod": { - "ID": "bcmpinc.HarvestWithScythe", - "~1.1 | Status": "AssumeBroken" - }, - /********* ** Broke in SMAPI 3.0 (runtime errors due to lifecycle changes) *********/ @@ -251,16 +278,6 @@ "~1.0.0 | Status": "AssumeBroken" }, - "Arcade 2048": { - "ID": "Platonymous.2048", - "~1.0.6 | Status": "AssumeBroken" // possibly due to PyTK - }, - - "Arcade Snake": { - "ID": "Platonymous.Snake", - "~1.1.0 | Status": "AssumeBroken" // possibly due to PyTK - }, - "Better Sprinklers": { "ID": "Speeder.BetterSprinklers", "~2.3.1-unofficial.7-pathoschild | Status": "AssumeBroken" @@ -278,12 +295,8 @@ "Decrafting Mod": { "ID": "MSCFC.DecraftingMod", - "~1.0 | Status": "AssumeBroken" // NRE in ModEntry - }, - - "JoJaBan - Arcade Sokoban": { - "ID": "Platonymous.JoJaBan", - "~0.4.3 | Status": "AssumeBroken" // possibly due to PyTK + "~1.0 | Status": "AssumeBroken", + "~1.0 | StatusReasonDetails": "fails due to NullReferenceException in ModEntry" }, "Level Extender": { @@ -301,11 +314,6 @@ "~1.5 | Status": "AssumeBroken" }, - "Seed Bag": { - "ID": "Platonymous.SeedBag", - "~1.2.7 | Status": "AssumeBroken" // possibly due to PyTK - }, - "Stardew Valley ESP": { "ID": "reimu.sdv-helper", "~1.1 | Status": "AssumeBroken" @@ -313,12 +321,14 @@ "Underdark Krobus": { "ID": "melnoelle.underdarkkrobus", - "~1.0.0 | Status": "AssumeBroken" // NRE in ModEntry + "~1.0 | Status": "AssumeBroken", + "~1.0 | StatusReasonDetails": "fails due to NullReferenceException in ModEntry" }, "Underdark Sewer": { "ID": "melnoelle.underdarksewer", - "~1.1.0 | Status": "AssumeBroken" // NRE in ModEntry + "~1.1.0 | Status": "AssumeBroken", + "~1.1.0 | StatusReasonDetails": "fails due to NullReferenceException in ModEntry" }, /********* @@ -327,60 +337,70 @@ "2cute FarmCave": { "ID": "taintedwheat.2CuteFarmCave", "Default | UpdateKey": "Nexus:843", - "~2.0 | Status": "AssumeBroken" // references deleted Content/Mine.xnb + "~2.0 | Status": "AssumeBroken", + "~2.0 | StatusReasonDetails": "references the deleted Content/Mine asset" }, "Ace's Expanded Caves - Default Cave": { "ID": "Acerbicon.AECdefault", "Default | UpdateKey": "Nexus:2131", - "~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb + "~1.2.2 | Status": "AssumeBroken", + "~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset" }, "Ace's Expanded Caves - Desert Cave": { "ID": "Acerbicon.AECdesert", "Default | UpdateKey": "Nexus:2131", - "~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb + "~1.2.2 | Status": "AssumeBroken", + "~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset" }, "Ace's Expanded Caves - Ice Cave": { "ID": "Acerbicon.AECice", "Default | UpdateKey": "Nexus:2131", - "~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb + "~1.2.2 | Status": "AssumeBroken", + "~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset" }, "Ace's Expanded Caves - Lava Cave": { "ID": "Acerbicon.AEClava", "Default | UpdateKey": "Nexus:2131", - "~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb + "~1.2.2 | Status": "AssumeBroken", + "~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset" }, "Ace's Expanded Caves - Slime Cave": { "ID": "Acerbicon.AECslime", "Default | UpdateKey": "Nexus:2131", - "~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb + "~1.2.2 | Status": "AssumeBroken", + "~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset" }, "Green Pastures Farm": { "ID": "bugbuddy.GreenPasturesFarm", "Default | UpdateKey": "Nexus:2326", - "~1.0 | Status": "AssumeBroken" // references deleted Content/weapons.xnb + "~1.0 | Status": "AssumeBroken", + "~1.0 | StatusReasonDetails": "references the deleted Content/weapons asset" }, "Immersive Farm 2": { "ID": "zander.immersivefarm2", - "~2.0.1 | Status": "AssumeBroken" // references deleted Content/Mine.xnb + "~2.0.1 | Status": "AssumeBroken", + "~2.0.1 | StatusReasonDetails": "references the deleted Content/Mine asset" }, "Karmylla's Immersive Map Edits": { "ID": "Karmylla.ImmersiveMapEdits", "Default | UpdateKey": "Nexus:1149", - "~2.4 | Status": "AssumeBroken" // references deleted Content/weapons.xnb + "~2.4 | Status": "AssumeBroken", + "~2.4 | StatusReasonDetails": "references the deleted Content/weapons asset" }, "Secret Gardens Greenhouse": { "ID": "jessebot.secretgardens", "Default | UpdateKey": "Nexus:3067", - "~2.0.1 | Status": "AssumeBroken" // references deleted Content/Mine.xnb + "~2.0.1 | Status": "AssumeBroken", + "~2.0.1 | StatusReasonDetails": "references the deleted Content/Mine asset" }, /********* @@ -388,102 +408,55 @@ *********/ "Canon-Friendly Dialogue Expansion": { "ID": "gizzymo.canonfriendlyexpansion", - "~1.1.1 | Status": "AssumeBroken" // causes a save crash on certain dates + "~1.1.1 | Status": "AssumeBroken", + "~1.1.1 | StatusReasonDetails": "causes a save crash on certain dates" }, "Everytime Submarine": { "ID": "MustafaDemirel.EverytimeSubmarine", - "~1.0.0 | Status": "AssumeBroken" // breaks player saves if their beach bridge is fixed - }, - - "Always Scroll Map": { - "ID": "bcmpinc.AlwaysScrollMap", - "~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request) - }, - - "Arcade Pong": { - "ID": "Platonymous.ArcadePong", - "~1.0.2 | Status": "AssumeBroken" // broke in SMAPI 2.6-beta.16 due to reflection into SMAPI internals + "~1.0.0 | Status": "AssumeBroken", + "~1.1.1 | StatusReasonDetails": "breaks player saves if their beach bridge is fixed" }, "BJS Night Sounds": { "ID": "BunnyJumps.BJSNightSounds", - "~1.0.0 | Status": "AssumeBroken" // runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+ - }, - - "Craft Counter": { - "ID": "bcmpinc.CraftCounter", - "~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request) + "~1.0.0 | Status": "AssumeBroken", + "~1.0.0 | StatusReasonDetails": "runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+" }, "Fishing Adjust": { "ID": "shuaiz.FishingAdjustMod", - "~2.0.1 | Status": "AssumeBroken" // Method not found: 'Void Harmony.HarmonyInstance.Patch(System.Reflection.MethodBase, Harmony.HarmonyMethod, Harmony.HarmonyMethod, Harmony.HarmonyMethod)' + "~2.0.1 | Status": "AssumeBroken", + "~2.0.1 | StatusReasonDetails": "fails with 'method not found' error for 'Void Harmony.HarmonyInstance.Patch(System.Reflection.MethodBase, Harmony.HarmonyMethod, Harmony.HarmonyMethod, Harmony.HarmonyMethod)'" }, "Fishing Automaton": { "ID": "Drynwynn.FishingAutomaton", - "~1.1 | Status": "AssumeBroken" // runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+ - }, - - "Fix Animal Tools": { - "ID": "bcmpinc.FixAnimalTools", - "~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request) - }, - - "Fix Scythe Exp": { - "ID": "bcmpinc.FixScytheExp", - "~0.3 | Status": "AssumeBroken" // broke in 1.3: Exception from HarmonyInstance "bcmpinc.FixScytheExp" [...] Bad label content in ILGenerator. + "~1.1 | Status": "AssumeBroken", + "~1.1 | StatusReasonDetails": "runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+" }, "More Silo Storage": { "ID": "OrneryWalrus.MoreSiloStorage", - "~1.0.1 | Status": "AssumeBroken" // broke in SDV 1.3 + "~1.0.1 | Status": "AssumeBroken" }, "No Added Flying Mine Monsters": { "ID": "Drynwynn.NoAddedFlyingMineMonsters", - "~1.1 | Status": "AssumeBroken" // runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+ + "~1.1 | Status": "AssumeBroken", + "~1.1 | StatusReasonDetails": "runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+" }, "Server Bookmarker": { "ID": "Ilyaki.ServerBookmarker", - "~1.0.0 | Status": "AssumeBroken" // broke in Stardew Valley 1.3.29 (runtime errors) - }, - - "Skull Cave Saver": { - "ID": "cantorsdust.SkullCaveSaver", - "FormerIDs": "8ac06349-26f7-4394-806c-95d48fd35774 | community.SkullCaveSaver", // changed in 1.1 and 1.2.2 - "1.3-beta | Status": "AssumeBroken" // doesn't work in multiplayer, no longer maintained - }, - - "Stardew Hack": { - "ID": "bcmpinc.StardewHack", - "~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request) + "~1.0.0 | Status": "AssumeBroken", + "~1.0.0 | StatusReasonDetails": "runtime errors in Stardew Valley 1.3.29" }, "Stephan's Lots of Crops": { "ID": "stephansstardewcrops", - "~1.1 | Status": "AssumeBroken" // broke in SDV 1.3 (overwrites vanilla items) - }, - - "Summit Reborn": { - "ID": "KoihimeNakamura.summitreborn", - "FormerIDs": "emissaryofinfinity.summitreborn", // changed in 1.0.2 - "~1.0.2 | Status": "AssumeBroken" // broke in SDV 1.3 (runtime errors) - }, - - "Tilled Soil Decay": { - "ID": "bcmpinc.TilledSoilDecay", - "~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request) - }, - - /********* - ** Broke circa SDV 1.2 - *********/ - "Move Faster": { - "ID": "shuaiz.MoveFasterMod", - "~1.0.1 | Status": "AssumeBroken" // doesn't do anything as of SDV 1.2.33 (bad Harmony patch?) + "~1.1 | Status": "AssumeBroken", + "~1.1 | StatusReasonDetails": "causes errors due to overwritten Stardew Valley 1.3 items" } } } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 9d5501a3..ef996c0f 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -54,7 +54,7 @@ namespace StardewModdingAPI ** Public ****/ /// <summary>SMAPI's current semantic version.</summary> - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.8.1"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.8.2"); /// <summary>The minimum supported version of Stardew Valley.</summary> public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.1"); diff --git a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs index d4370028..d75a7540 100644 --- a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs +++ b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs @@ -1,5 +1,6 @@ using System; using StardewModdingAPI.Framework.Networking; +using StardewModdingAPI.Toolkit.Serialization; namespace StardewModdingAPI.Events { @@ -12,6 +13,9 @@ namespace StardewModdingAPI.Events /// <summary>The underlying message model.</summary> private readonly ModMessageModel Message; + /// <summary>The JSON helper used to deserialize models.</summary> + private readonly JsonHelper JsonHelper; + /********* ** Accessors @@ -31,16 +35,18 @@ namespace StardewModdingAPI.Events *********/ /// <summary>Construct an instance.</summary> /// <param name="message">The received message.</param> - internal ModMessageReceivedEventArgs(ModMessageModel message) + /// <param name="jsonHelper">The JSON helper used to deserialize models.</param> + internal ModMessageReceivedEventArgs(ModMessageModel message, JsonHelper jsonHelper) { this.Message = message; + this.JsonHelper = jsonHelper; } /// <summary>Read the message data into the given model type.</summary> /// <typeparam name="TModel">The message model type.</typeparam> public TModel ReadAs<TModel>() { - return this.Message.Data.ToObject<TModel>(); + return this.Message.Data.ToObject<TModel>(this.JsonHelper.GetSerializer()); } } } diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index f9027972..3d5bb29d 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -54,6 +54,9 @@ namespace StardewModdingAPI.Framework /// <remarks>The game may adds content managers in asynchronous threads (e.g. when populating the load screen).</remarks> private readonly ReaderWriterLockSlim ContentManagerLock = new ReaderWriterLockSlim(); + /// <summary>An unmodified content manager which doesn't intercept assets, used to compare asset data.</summary> + private readonly LocalizedContentManager VanillaContentManager; + /********* ** Accessors @@ -95,6 +98,7 @@ namespace StardewModdingAPI.Framework this.ContentManagers.Add( this.MainContentManager = new GameContentManager("Game1.content", serviceProvider, rootDirectory, currentCulture, this, monitor, reflection, this.OnDisposing, onLoadingFirstAsset) ); + this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory); this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormalizeAssetName, reflection); } @@ -150,6 +154,8 @@ namespace StardewModdingAPI.Framework { foreach (IContentManager contentManager in this.ContentManagers) contentManager.OnLocaleChanged(); + + this.VanillaContentManager.Unload(); }); } @@ -287,6 +293,23 @@ namespace StardewModdingAPI.Framework }); } + /// <summary>Get a vanilla asset without interception.</summary> + /// <typeparam name="T">The type of asset to load.</typeparam> + /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param> + public bool TryLoadVanillaAsset<T>(string assetName, out T asset) + { + try + { + asset = this.VanillaContentManager.Load<T>(assetName); + return true; + } + catch + { + asset = default; + return false; + } + } + /// <summary>Dispose held resources.</summary> public void Dispose() { diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index ad8f2ef1..424d6ff3 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -11,6 +11,7 @@ using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Utilities; using StardewValley; using xTile; +using xTile.Tiles; namespace StardewModdingAPI.Framework.ContentManagers { @@ -308,15 +309,10 @@ namespace StardewModdingAPI.Framework.ContentManagers return null; } - // validate asset - if (data == null) - { - mod.LogAsMod($"Mod incorrectly set asset '{info.AssetName}' to a null value; ignoring override.", LogLevel.Error); - return null; - } - // return matched asset - return new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName); + return this.TryValidateLoadedAsset(info, data, mod) + ? new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName) + : null; } /// <summary>Apply any <see cref="Editors"/> to a loaded asset.</summary> @@ -386,5 +382,77 @@ namespace StardewModdingAPI.Framework.ContentManagers // return result return asset; } + + /// <summary>Validate that an asset loaded by a mod is valid and won't cause issues.</summary> + /// <typeparam name="T">The asset type.</typeparam> + /// <param name="info">The basic asset metadata.</param> + /// <param name="data">The loaded asset data.</param> + /// <param name="mod">The mod which loaded the asset.</param> + private bool TryValidateLoadedAsset<T>(IAssetInfo info, T data, IModMetadata mod) + { + // can't load a null asset + if (data == null) + { + mod.LogAsMod($"SMAPI blocked asset replacement for '{info.AssetName}': mod incorrectly set asset to a null value.", LogLevel.Error); + return false; + } + + // when replacing a map, the vanilla tilesheets must have the same order and IDs + if (data is Map loadedMap && this.Coordinator.TryLoadVanillaAsset(info.AssetName, out Map vanillaMap)) + { + for (int i = 0; i < vanillaMap.TileSheets.Count; i++) + { + // check for match + TileSheet vanillaSheet = vanillaMap.TileSheets[i]; + bool found = this.TryFindTilesheet(loadedMap, vanillaSheet.Id, out int loadedIndex, out TileSheet loadedSheet); + if (found && loadedIndex == i) + continue; + + // handle mismatch + { + // only show warning if not farm map + // This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting. + bool isFarmMap = info.AssetNameEquals("Maps/Farm") || info.AssetNameEquals("Maps/Farm_Combat") || info.AssetNameEquals("Maps/Farm_Fishing") || info.AssetNameEquals("Maps/Farm_Foraging") || info.AssetNameEquals("Maps/Farm_FourCorners") || info.AssetNameEquals("Maps/Farm_Island") || info.AssetNameEquals("Maps/Farm_Mining"); + + + string reason = found + ? $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\n\nTechnical details for mod author:\nExpected order [{string.Join(", ", vanillaMap.TileSheets.Select(p => $"'{p.ImageSource}' (id: {p.Id})"))}], but found tilesheet '{vanillaSheet.Id}' at index {loadedIndex} instead of {i}. Make sure custom tilesheet IDs are prefixed with 'z_' to avoid reordering tilesheets." + : $"mod has no tilesheet with ID '{vanillaSheet.Id}'. Map replacements must keep the original tilesheets to avoid errors or crashes."; + + SCore.DeprecationManager.PlaceholderWarn("3.8.2", DeprecationLevel.PendingRemoval); + if (isFarmMap) + { + mod.LogAsMod($"SMAPI blocked asset replacement for |
