From 433261e7d57096bec41ae6eea16f5f18b8045be8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 Aug 2018 16:18:23 -0400 Subject: fix ToSButton() methods not public --- src/SMAPI/SButton.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/SMAPI/SButton.cs b/src/SMAPI/SButton.cs index 3f95169a..bc76c91d 100644 --- a/src/SMAPI/SButton.cs +++ b/src/SMAPI/SButton.cs @@ -604,21 +604,21 @@ namespace StardewModdingAPI *********/ /// Get the equivalent for the given button. /// The keyboard button to convert. - internal static SButton ToSButton(this Keys key) + public static SButton ToSButton(this Keys key) { return (SButton)key; } /// Get the equivalent for the given button. /// The controller button to convert. - internal static SButton ToSButton(this Buttons key) + public static SButton ToSButton(this Buttons key) { return (SButton)(SButtonExtensions.ControllerOffset + key); } /// Get the equivalent for the given button. /// The Stardew Valley button to convert. - internal static SButton ToSButton(this InputButton input) + public static SButton ToSButton(this InputButton input) { // derived from InputButton constructors if (input.mouseLeft) -- cgit From a0f7a244740de91fc5104f9f33b844493cd4a3a8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 Aug 2018 16:32:46 -0400 Subject: fixed false compat error when constructing multidimensional arrays --- .../Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index cf5a3175..3a26660f 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -70,7 +70,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders // method reference MethodReference methodReference = RewriteHelper.AsMethodReference(instruction); - if (methodReference != null && this.ShouldValidate(methodReference.DeclaringType)) + if (methodReference != null && !this.IsUnsupported(methodReference) && this.ShouldValidate(methodReference.DeclaringType)) { // get potential targets MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); @@ -106,6 +106,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } + /// Get whether a method reference is a special case that's not currently supported (e.g. array methods). + /// The method reference. + private bool IsUnsupported(MethodReference method) + { + return + method.DeclaringType.Name.Contains("["); // array methods + } + /// Get a shorter type name for display. /// The type reference. private string GetFriendlyTypeName(TypeReference type) -- cgit From 7eca78426e46c90320be6d904cd97fa6319da1a7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 5 Aug 2018 22:11:11 -0400 Subject: mark Everytime Submarine incompatible (#574) --- src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json b/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json index e72efb39..e00ef63e 100644 --- a/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json @@ -566,6 +566,11 @@ "~2.0.6 | Status": "AssumeBroken" // broke in SMAPI 2.5 (error reflecting into SMAPI internals) }, + "Everytime Submarine": { + "ID": "MustafaDemirel.EverytimeSubmarine", + "~1.0.0 | Status": "AssumeBroken" // breaks player saves if their beach bridge is fixed + }, + "Expanded Fridge": { "ID": "Uwazouri.ExpandedFridge", "Default | UpdateKey": "Nexus:1191" -- cgit From a1a93ac4bf20458d49028ed40905e715aa1ae152 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 6 Aug 2018 22:21:10 -0400 Subject: fix spring tilesheets always used for custom festival maps (#577) --- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 671dc21e..a8b24a13 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -259,7 +259,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // get seasonal name (if applicable) string seasonalImageSource = null; - if (Game1.currentSeason != null) + if (Context.IsSaveLoaded && Game1.currentSeason != null) { string filename = Path.GetFileName(imageSource) ?? throw new InvalidOperationException($"The '{imageSource}' tilesheet couldn't be loaded: filename is unexpectedly null."); bool hasSeasonalPrefix = -- cgit From 8df5d79c9e01c2d665192ba5accfcd45a47e2dda Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 6 Aug 2018 22:23:18 -0400 Subject: fix ConsoleCommands item search code --- .../Framework/ItemData/ItemType.cs | 5 +--- .../Framework/ItemRepository.cs | 29 ++++++++++------------ 2 files changed, 14 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs index 797d4650..7ee662d0 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs @@ -1,4 +1,4 @@ -namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData { /// An item type that can be searched and added to the player through the console. internal enum ItemType @@ -9,9 +9,6 @@ /// A item. Boots, - /// A fish item. - Fish, - /// A flooring item. Flooring, diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index e678d057..7a3d8694 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -37,14 +37,15 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset, new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 1, new Shears()); yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 2, new Pan()); + yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 3, new Wand()); // wallpapers for (int id = 0; id < 112; id++) - yield return new SearchableItem(ItemType.Wallpaper, id, new Wallpaper(id)); + yield return new SearchableItem(ItemType.Wallpaper, id, new Wallpaper(id) { Category = SObject.furnitureCategory }); // flooring for (int id = 0; id < 40; id++) - yield return new SearchableItem(ItemType.Flooring, id, new Wallpaper(id, isFloor: true)); + yield return new SearchableItem(ItemType.Flooring, id, new Wallpaper(id, isFloor: true) { Category = SObject.furnitureCategory }); // equipment foreach (int id in Game1.content.Load>("Data\\Boots").Keys) @@ -75,10 +76,6 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework yield return new SearchableItem(ItemType.Furniture, id, new Furniture(id, Vector2.Zero)); } - // fish - foreach (int id in Game1.content.Load>("Data\\Fish").Keys) - yield return new SearchableItem(ItemType.Fish, id, new SObject(id, 999)); - // craftables foreach (int id in Game1.bigCraftablesInformation.Keys) yield return new SearchableItem(ItemType.BigCraftable, id, new SObject(Vector2.Zero, id)); @@ -103,16 +100,16 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework yield return new SearchableItem(ItemType.Object, id, item); // fruit products - if (item.category == SObject.FruitsCategory) + if (item.Category == SObject.FruitsCategory) { // wine SObject wine = new SObject(348, 1) { Name = $"{item.Name} Wine", - Price = item.price * 3 + Price = item.Price * 3 }; wine.preserve.Value = SObject.PreserveType.Wine; - wine.preservedParentSheetIndex.Value = item.parentSheetIndex; + wine.preservedParentSheetIndex.Value = item.ParentSheetIndex; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 2 + id, wine); // jelly @@ -122,21 +119,21 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework Price = 50 + item.Price * 2 }; jelly.preserve.Value = SObject.PreserveType.Jelly; - jelly.preservedParentSheetIndex.Value = item.parentSheetIndex; + jelly.preservedParentSheetIndex.Value = item.ParentSheetIndex; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 3 + id, jelly); } // vegetable products - else if (item.category == SObject.VegetableCategory) + else if (item.Category == SObject.VegetableCategory) { // juice SObject juice = new SObject(350, 1) { Name = $"{item.Name} Juice", - Price = (int)(item.price * 2.25d) + Price = (int)(item.Price * 2.25d) }; juice.preserve.Value = SObject.PreserveType.Juice; - juice.preservedParentSheetIndex.Value = item.parentSheetIndex; + juice.preservedParentSheetIndex.Value = item.ParentSheetIndex; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 4 + id, juice); // pickled @@ -146,16 +143,16 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework Price = 50 + item.Price * 2 }; pickled.preserve.Value = SObject.PreserveType.Pickle; - pickled.preservedParentSheetIndex.Value = item.parentSheetIndex; + pickled.preservedParentSheetIndex.Value = item.ParentSheetIndex; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 5 + id, pickled); } // flower honey - else if (item.category == SObject.flowersCategory) + else if (item.Category == SObject.flowersCategory) { // get honey type SObject.HoneyType? type = null; - switch (item.parentSheetIndex) + switch (item.ParentSheetIndex) { case 376: type = SObject.HoneyType.Poppy; -- cgit From 03e679d66cc7439b0218c95cbead925786e37e86 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Aug 2018 00:12:47 -0400 Subject: serialise all enums to string in JSON by default --- src/SMAPI/Program.cs | 3 --- .../Converters/StringEnumConverter.cs | 22 ---------------------- .../Serialisation/JsonHelper.cs | 7 ++++++- 3 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs (limited to 'src') diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 6012b15a..999aa23c 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -193,9 +193,6 @@ namespace StardewModdingAPI // init JSON parser JsonConverter[] converters = { - new StringEnumConverter(), - new StringEnumConverter(), - new StringEnumConverter(), new ColorConverter(), new PointConverter(), new RectangleConverter() diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs deleted file mode 100644 index 13e6e3a1..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Newtonsoft.Json.Converters; - -namespace StardewModdingAPI.Toolkit.Serialisation.Converters -{ - /// A variant of which only converts a specified enum. - /// The enum type. - internal class StringEnumConverter : StringEnumConverter - { - /********* - ** Public methods - *********/ - /// Get whether this instance can convert the specified object type. - /// The object type. - public override bool CanConvert(Type type) - { - return - base.CanConvert(type) - && (Nullable.GetUnderlyingType(type) ?? type) == typeof(T); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs index 00f334ad..3cabbab3 100644 --- a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs +++ b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using StardewModdingAPI.Toolkit.Serialisation.Converters; namespace StardewModdingAPI.Toolkit.Serialisation @@ -17,7 +18,11 @@ namespace StardewModdingAPI.Toolkit.Serialisation { Formatting = Formatting.Indented, ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection values are duplicated each time the config is loaded - Converters = new List { new SemanticVersionConverter() } + Converters = new List + { + new SemanticVersionConverter(), + new StringEnumConverter() + } }; -- cgit From 9488d6482b03aa2227318f0028d10a44849367f6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Aug 2018 16:32:00 -0400 Subject: fix some log files not deleted on startup --- src/SMAPI/Constants.cs | 9 ++++++--- src/SMAPI/Program.cs | 7 +++---- 2 files changed, 9 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index a6cddbe4..07bb3e17 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -70,11 +70,14 @@ namespace StardewModdingAPI /// The file path for the SMAPI metadata file. internal static string ApiMetadataPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.metadata.json"); - /// The filename prefix for SMAPI log files. - internal static string LogNamePrefix { get; } = "SMAPI-latest"; + /// The filename prefix used for all SMAPI logs. + internal static string LogNamePrefix { get; } = "SMAPI-"; + + /// The filename for SMAPI's main log, excluding the . + internal static string LogFilename { get; } = $"{Constants.LogNamePrefix}latest"; /// The filename extension for SMAPI log files. - internal static string LogNameExtension { get; } = "txt"; + internal static string LogExtension { get; } = "txt"; /// A copy of the log leading up to the previous fatal crash, if any. internal static string FatalCrashLog => Path.Combine(Constants.LogDir, "SMAPI-crash.txt"); diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 999aa23c..5d8b267f 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -435,7 +435,7 @@ namespace StardewModdingAPI ModFolderPath = Constants.ModPath, Mods = mods }; - this.Toolkit.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.metadata-dump.json"), export); + this.Toolkit.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}metadata-dump.json"), export); } // check for updates @@ -1060,7 +1060,6 @@ namespace StardewModdingAPI yield return "uses the 'dynamic' keyword. This won't work on Linux/Mac."; if (mask.HasFlag(ModWarning.NoUpdateKeys)) yield return "has no update keys in its manifest. SMAPI won't show update alerts for this mod."; - } /// Load a mod's entry class. @@ -1257,7 +1256,7 @@ namespace StardewModdingAPI { // default path { - FileInfo defaultFile = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.{Constants.LogNameExtension}")); + FileInfo defaultFile = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogFilename}.{Constants.LogExtension}")); if (!defaultFile.Exists) return defaultFile.FullName; } @@ -1265,7 +1264,7 @@ namespace StardewModdingAPI // get first disambiguated path for (int i = 2; i < int.MaxValue; i++) { - FileInfo file = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.player-{i}.{Constants.LogNameExtension}")); + FileInfo file = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogFilename}.player-{i}.{Constants.LogExtension}")); if (!file.Exists) return file.FullName; } -- cgit From 3d7ce99d798745ee746dafdb1f591b5c23ff16dd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Aug 2018 00:59:48 -0400 Subject: revamp how mod skips & issues are displayed (#571) --- src/SMAPI/Program.cs | 143 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 59 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 5d8b267f..4e4c913a 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -11,7 +11,6 @@ using System.Security; using System.Text; using System.Text.RegularExpressions; using System.Threading; -using Microsoft.Xna.Framework.Input; #if SMAPI_FOR_WINDOWS using System.Windows.Forms; #endif @@ -32,10 +31,8 @@ using StardewModdingAPI.Toolkit; using StardewModdingAPI.Toolkit.Framework.Clients.WebApi; using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Serialisation; -using StardewModdingAPI.Toolkit.Serialisation.Converters; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; -using Keys = Microsoft.Xna.Framework.Input.Keys; using Monitor = StardewModdingAPI.Framework.Monitor; using SObject = StardewValley.Object; using ThreadState = System.Threading.ThreadState; @@ -881,33 +878,15 @@ namespace StardewModdingAPI } IModMetadata[] loadedMods = this.ModRegistry.GetAll(contentPacks: false).ToArray(); - // log skipped mods - this.Monitor.Newline(); - if (skippedMods.Any()) - { - this.Monitor.Log($"Skipped {skippedMods.Count} mods:", LogLevel.Error); - foreach (var pair in skippedMods.OrderBy(p => p.Key.DisplayName)) - { - IModMetadata mod = pair.Key; - string[] reason = pair.Value; - - this.Monitor.Log($" {mod.DisplayName}{(mod.Manifest?.Version != null ? " " + mod.Manifest.Version.ToString() : "")} because {reason[0]}", LogLevel.Error); - if (reason[1] != null) - this.Monitor.Log($" {reason[1]}", LogLevel.Trace); - } - this.Monitor.Newline(); - } - // log loaded mods this.Monitor.Log($"Loaded {loadedMods.Length} mods" + (loadedMods.Length > 0 ? ":" : "."), LogLevel.Info); - foreach (IModMetadata metadata in loadedMods.OrderBy(p => p.DisplayName)) { IManifest manifest = metadata.Manifest; this.Monitor.Log( $" {metadata.DisplayName} {manifest.Version}" - + (!string.IsNullOrWhiteSpace(manifest.Author) ? $" by {manifest.Author}" : "") - + (!string.IsNullOrWhiteSpace(manifest.Description) ? $" | {manifest.Description}" : ""), + + (!string.IsNullOrWhiteSpace(manifest.Author) ? $" by {manifest.Author}" : "") + + (!string.IsNullOrWhiteSpace(manifest.Description) ? $" | {manifest.Description}" : ""), LogLevel.Info ); } @@ -933,27 +912,8 @@ namespace StardewModdingAPI this.Monitor.Newline(); } - // log warnings - { - IModMetadata[] modsWithWarnings = this.ModRegistry.GetAll().Where(p => p.Warnings != ModWarning.None).ToArray(); - if (modsWithWarnings.Any()) - { - this.Monitor.Log($"Found issues with {modsWithWarnings.Length} mods:", LogLevel.Warn); - foreach (IModMetadata metadata in modsWithWarnings) - { - string[] warnings = this.GetWarningText(metadata.Warnings).ToArray(); - if (warnings.Length == 1) - this.Monitor.Log($" {metadata.DisplayName} {warnings[0]}", LogLevel.Warn); - else - { - this.Monitor.Log($" {metadata.DisplayName}:", LogLevel.Warn); - foreach (string warning in warnings) - this.Monitor.Log(" - " + warning, LogLevel.Warn); - } - } - this.Monitor.Newline(); - } - } + // log mod warnings + this.LogModWarnings(this.ModRegistry.GetAll().ToArray(), skippedMods); // initialise translations this.ReloadTranslations(loadedMods); @@ -1044,22 +1004,87 @@ namespace StardewModdingAPI this.ModRegistry.AreAllModsInitialised = true; } - /// Get the warning text for a mod warning bit mask. - /// The mod warning bit mask. - private IEnumerable GetWarningText(ModWarning mask) + /// Write a summary of mod warnings to the console and log. + /// The loaded mods. + /// The mods which were skipped, along with the friendly and developer reasons. + private void LogModWarnings(IModMetadata[] mods, IDictionary skippedMods) { - if (mask.HasFlag(ModWarning.BrokenCodeLoaded)) - yield return "has broken code, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly."; - if (mask.HasFlag(ModWarning.ChangesSaveSerialiser)) - yield return "accesses the save serialiser and may break your saves."; - if (mask.HasFlag(ModWarning.PatchesGame)) - yield return "patches the game. This may cause errors or bugs in-game. If you have issues, try removing this mod first."; - if (mask.HasFlag(ModWarning.UsesUnvalidatedUpdateTick)) - yield return "bypasses normal SMAPI event protections. This may cause errors or save corruption. If you have issues, try removing this mod first."; - if (mask.HasFlag(ModWarning.UsesDynamic)) - yield return "uses the 'dynamic' keyword. This won't work on Linux/Mac."; - if (mask.HasFlag(ModWarning.NoUpdateKeys)) - yield return "has no update keys in its manifest. SMAPI won't show update alerts for this mod."; + // get mods with warnings + IModMetadata[] modsWithWarnings = mods.Where(p => p.Warnings != ModWarning.None).ToArray(); + if (!modsWithWarnings.Any() && !skippedMods.Any()) + return; + + // log intro + { + int count = modsWithWarnings.Union(skippedMods.Keys).Count(); + this.Monitor.Log($"Found {count} mod{(count == 1 ? "" : "s")} with warnings:", LogLevel.Info); + } + + // log skipped mods + if (skippedMods.Any()) + { + 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 pair in skippedMods.OrderBy(p => p.Key.DisplayName)) + { + IModMetadata mod = pair.Key; + string[] reason = pair.Value; + + this.Monitor.Log($" - {mod.DisplayName}{(mod.Manifest?.Version != null ? " " + mod.Manifest.Version.ToString() : "")} because {reason[0]}", LogLevel.Error); + if (reason[1] != null) + this.Monitor.Log($" ({reason[1]})", LogLevel.Trace); + } + this.Monitor.Newline(); + } + + // log warnings + if (modsWithWarnings.Any()) + { + // issue block format logic + void LogWarningGroup(ModWarning warning, LogLevel logLevel, string heading, params string[] blurb) + { + IModMetadata[] matches = modsWithWarnings.Where(p => p.Warnings.HasFlag(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", + "These mods have broken code, but you configured SMAPI to load them anyway. This may cause bugs,", + "errors, or crashes in-game." + ); + LogWarningGroup(ModWarning.ChangesSaveSerialiser, LogLevel.Warn, "Changed save serialiser", + "These mods change the save serialiser. They may corrupt your save files, or make them unusable if", + "you uninstall these mods." + ); + LogWarningGroup(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", + "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", + "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", + "These mods use the 'dynamic' keyword, and won't work on Linux/Mac." + ); + } } /// Load a mod's entry class. -- cgit From 086587c16be6a90a47b11795d3e07e153f1e500d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Aug 2018 23:56:56 -0400 Subject: add broken versions of Canon-Friendly Dialogue Expansion to compatibility list --- .../wwwroot/StardewModdingAPI.metadata.json | 26 ++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json b/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json index e00ef63e..c95abe75 100644 --- a/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json @@ -51,6 +51,23 @@ * mod is no longer compatible. */ "ModData": { + /********* + ** Content packs + *********/ + "Canon-Friendly Dialogue Expansion": { + "ID": "gizzymo.canonfriendlyexpansion", + "~1.1.1 | Status": "AssumeBroken" // 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 + }, + + + /********* + ** Mods + *********/ "AccessChestAnywhere": { "ID": "AccessChestAnywhere", "MapLocalVersions": { "1.1-1078": "1.1" }, @@ -566,11 +583,6 @@ "~2.0.6 | Status": "AssumeBroken" // broke in SMAPI 2.5 (error reflecting into SMAPI internals) }, - "Everytime Submarine": { - "ID": "MustafaDemirel.EverytimeSubmarine", - "~1.0.0 | Status": "AssumeBroken" // breaks player saves if their beach bridge is fixed - }, - "Expanded Fridge": { "ID": "Uwazouri.ExpandedFridge", "Default | UpdateKey": "Nexus:1191" @@ -827,7 +839,7 @@ }, "Level Extender": { - "ID": "DevinLematty.LevelExtender", + "ID": "DevinLematty.LevelExtender", "FormerIDs": "Devin Lematty.Level Extender", // changed in 1.3 "Default | UpdateKey": "Nexus:1471" }, @@ -1051,7 +1063,7 @@ }, "One Click Shed": { - "ID": "BitwiseJonMods.OneClickShedReloader", + "ID": "BitwiseJonMods.OneClickShedReloader", "Default | UpdateKey": "Nexus:2052" }, -- cgit From ef731de8318c7f01567baf2e23ae9a09789b4bdd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Aug 2018 14:18:52 -0400 Subject: add --mods-path CLI argument to allow switching between mod folders (#579) --- src/SMAPI/Constants.cs | 2 +- src/SMAPI/Program.cs | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 07bb3e17..c7cd6e41 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -89,7 +89,7 @@ namespace StardewModdingAPI internal static string UpdateMarker => Path.Combine(Constants.ExecutionPath, "StardewModdingAPI.update.marker"); /// The full path to the folder containing mods. - internal static string ModPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods"); + internal static string DefaultModsPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods"); /// The game's current semantic version. internal static ISemanticVersion GameVersion { get; } = new GameVersion(Constants.GetGameVersion()); diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 4e4c913a..a894e831 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -100,6 +100,9 @@ namespace StardewModdingAPI /// The mod toolkit used for generic mod interactions. private readonly ModToolkit Toolkit = new ModToolkit(); + /// The path to search for mods. + private readonly string ModsPath; + /********* ** Public methods @@ -113,18 +116,34 @@ namespace StardewModdingAPI // get flags from arguments bool writeToConsole = !args.Contains("--no-terminal"); + // get mods path from arguments + string modsPath = null; + { + int pathIndex = Array.LastIndexOf(args, "--mods-path") + 1; + if (pathIndex >= 1 && args.Length >= pathIndex) + { + modsPath = args[pathIndex]; + if (!string.IsNullOrWhiteSpace(modsPath) && !Path.IsPathRooted(modsPath)) + modsPath = Path.Combine(Constants.ExecutionPath, modsPath); + } + if (string.IsNullOrWhiteSpace(modsPath)) + modsPath = Constants.DefaultModsPath; + } + // load SMAPI - using (Program program = new Program(writeToConsole)) + using (Program program = new Program(modsPath, writeToConsole)) program.RunInteractively(); } /// Construct an instance. + /// The path to search for mods. /// Whether to output log messages to the console. - public Program(bool writeToConsole) + public Program(string modsPath, bool writeToConsole) { // init paths - this.VerifyPath(Constants.ModPath); + this.VerifyPath(modsPath); this.VerifyPath(Constants.LogDir); + this.ModsPath = modsPath; // init log file this.PurgeLogFiles(); @@ -143,7 +162,9 @@ namespace StardewModdingAPI // init logging this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info); - this.Monitor.Log($"Mods go here: {Constants.ModPath}"); + this.Monitor.Log($"Mods go here: {modsPath}"); + if (modsPath != Constants.DefaultModsPath) + this.Monitor.Log("(Using custom --mods-path argument.)", LogLevel.Trace); this.Monitor.Log($"Log started at {DateTime.UtcNow:s} UTC", LogLevel.Trace); // validate game version @@ -412,7 +433,7 @@ namespace StardewModdingAPI ModResolver resolver = new ModResolver(); // load manifests - IModMetadata[] mods = resolver.ReadManifests(toolkit, Constants.ModPath, modDatabase).ToArray(); + IModMetadata[] mods = resolver.ReadManifests(toolkit, this.ModsPath, modDatabase).ToArray(); resolver.ValidateManifests(mods, Constants.ApiVersion, toolkit.GetUpdateUrl); // process dependencies @@ -429,7 +450,7 @@ namespace StardewModdingAPI Exported = DateTime.UtcNow.ToString("O"), ApiVersion = Constants.ApiVersion.ToString(), GameVersion = Constants.GameVersion.ToString(), - ModFolderPath = Constants.ModPath, + ModFolderPath = this.ModsPath, Mods = mods }; this.Toolkit.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}metadata-dump.json"), export); @@ -740,7 +761,7 @@ namespace StardewModdingAPI // load content packs foreach (IModMetadata metadata in mods.Where(p => p.IsContentPack)) { - this.Monitor.Log($" {metadata.DisplayName} (content pack, {PathUtilities.GetRelativePath(Constants.ModPath, metadata.DirectoryPath)})...", LogLevel.Trace); + this.Monitor.Log($" {metadata.DisplayName} (content pack, {PathUtilities.GetRelativePath(this.ModsPath, metadata.DirectoryPath)})...", LogLevel.Trace); // show warning for missing update key if (metadata.HasManifest() && !metadata.HasUpdateKeys()) @@ -785,7 +806,7 @@ namespace StardewModdingAPI // get basic info IManifest manifest = metadata.Manifest; this.Monitor.Log(metadata.Manifest?.EntryDll != null - ? $" {metadata.DisplayName} ({PathUtilities.GetRelativePath(Constants.ModPath, metadata.DirectoryPath)}{Path.DirectorySeparatorChar}{metadata.Manifest.EntryDll})..." // don't use Path.Combine here, since EntryDLL might not be valid + ? $" {metadata.DisplayName} ({PathUtilities.GetRelativePath(this.ModsPath, metadata.DirectoryPath)}{Path.DirectorySeparatorChar}{metadata.Manifest.EntryDll})..." // don't use Path.Combine here, since EntryDLL might not be valid : $" {metadata.DisplayName}...", LogLevel.Trace); // show warnings -- cgit From b7907293349e95f84583e682f38e0eb491ac2e5d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Aug 2018 20:33:21 -0400 Subject: add support for loading unpacked .json files through content API (#576) --- src/SMAPI/Framework/ContentCoordinator.cs | 10 ++++-- .../Framework/ContentManagers/ModContentManager.cs | 31 +++++++++++++++--- src/SMAPI/Framework/ContentPack.cs | 4 ++- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 4 ++- src/SMAPI/Framework/SGame.cs | 26 +++++++-------- src/SMAPI/Framework/SGameConstructorHack.cs | 37 ++++++++++++++++++++++ src/SMAPI/Program.cs | 10 +++--- src/SMAPI/StardewModdingAPI.csproj | 1 + .../Framework/ModScanning/ModScanner.cs | 3 +- .../Serialisation/JsonHelper.cs | 15 +++++---- 10 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 src/SMAPI/Framework/SGameConstructorHack.cs (limited to 'src') diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index d9b2109a..9eb7b5f9 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -9,6 +9,7 @@ using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Metadata; +using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; @@ -32,6 +33,9 @@ namespace StardewModdingAPI.Framework /// Simplifies access to private code. private readonly Reflector Reflection; + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; + /// The loaded content managers (including the ). private readonly IList ContentManagers = new List(); @@ -67,10 +71,12 @@ namespace StardewModdingAPI.Framework /// The current culture for which to localise content. /// Encapsulates monitoring and logging. /// Simplifies access to private code. - public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection) + /// Encapsulates SMAPI's JSON file parsing. + public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper) { this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); this.Reflection = reflection; + this.JsonHelper = jsonHelper; this.FullRootDirectory = Path.Combine(Constants.ExecutionPath, rootDirectory); this.ContentManagers.Add( this.MainContentManager = new GameContentManager("Game1.content", serviceProvider, rootDirectory, currentCulture, this, monitor, reflection, this.OnDisposing) @@ -92,7 +98,7 @@ namespace StardewModdingAPI.Framework /// The root directory to search for content (or null for the default). public ModContentManager CreateModContentManager(string name, string rootDirectory) { - ModContentManager manager = new ModContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.OnDisposing); + ModContentManager manager = new ModContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.JsonHelper, this.OnDisposing); this.ContentManagers.Add(manager); return manager; } diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 80bf37e9..24ce69ea 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -5,6 +5,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Toolkit.Serialisation; using StardewValley; namespace StardewModdingAPI.Framework.ContentManagers @@ -12,6 +13,13 @@ namespace StardewModdingAPI.Framework.ContentManagers /// A content manager which handles reading files from a SMAPI mod folder with support for unpacked files. internal class ModContentManager : BaseContentManager { + /********* + ** Properties + *********/ + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; + + /********* ** Public methods *********/ @@ -23,9 +31,13 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The central coordinator which manages content managers. /// Encapsulates monitoring and logging. /// Simplifies access to private code. + /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke when the content manager is being disposed. - public ModContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing) - : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isModFolder: true) { } + public ModContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isModFolder: true) + { + this.JsonHelper = jsonHelper; + } /// Load an asset that has been processed by the content pipeline. /// The type of asset to load. @@ -95,9 +107,14 @@ namespace StardewModdingAPI.Framework.ContentManagers case ".xnb": return base.Load(relativePath, language); - // unpacked map - case ".tbin": - throw GetContentError($"can't read unpacked map file directly from the underlying content manager. It must be loaded through the mod's {typeof(IModHelper)}.{nameof(IModHelper.Content)} helper."); + // unpacked data + case ".json": + { + if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T data)) + throw GetContentError("the JSON file is invalid."); // should never happen since we check for file existence above + + return data; + } // unpacked image case ".png": @@ -114,6 +131,10 @@ namespace StardewModdingAPI.Framework.ContentManagers return (T)(object)texture; } + // unpacked map + case ".tbin": + throw GetContentError($"can't read unpacked map file directly from the underlying content manager. It must be loaded through the mod's {typeof(IModHelper)}.{nameof(IModHelper.Content)} helper."); + default: throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.png', '.tbin', or '.xnb'."); } diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 4a4adb90..62d8b80d 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -54,7 +54,9 @@ namespace StardewModdingAPI.Framework public TModel ReadJsonFile(string path) where TModel : class { path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); - return this.JsonHelper.ReadJsonFile(path); + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel model) + ? model + : null; } /// Load content from the content pack folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index d9498e83..0ba258b4 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -138,7 +138,9 @@ namespace StardewModdingAPI.Framework.ModHelpers where TModel : class { path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); - return this.JsonHelper.ReadJsonFile(path); + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + ? data + : null; } /// Save to a JSON file. diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 05fedc3d..83e8c9a7 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -17,6 +17,7 @@ using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.StateTracking; using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Toolkit.Serialisation; using StardewValley; using StardewValley.BellsAndWhistles; using StardewValley.Buildings; @@ -36,16 +37,6 @@ namespace StardewModdingAPI.Framework /********* ** Properties *********/ - /**** - ** Constructor hack - ****/ - /// A static instance of to use while is initialising, which happens before the constructor runs. - internal static IMonitor MonitorDuringInitialisation; - - /// A static instance of to use while is initialising, which happens before the constructor runs. - internal static Reflector ReflectorDuringInitialisation; - - /**** ** SMAPI state ****/ @@ -83,6 +74,9 @@ namespace StardewModdingAPI.Framework /// Simplifies access to private game code. private readonly Reflector Reflection; + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; + /**** ** Game state ****/ @@ -105,6 +99,9 @@ namespace StardewModdingAPI.Framework /********* ** Accessors *********/ + /// Static state to use while is initialising, which happens before the constructor runs. + internal static SGameConstructorHack ConstructorHack { get; set; } + /// SMAPI's content manager. public ContentCoordinator ContentCore { get; private set; } @@ -132,10 +129,13 @@ namespace StardewModdingAPI.Framework /// Encapsulates monitoring and logging. /// Simplifies access to private game code. /// Manages SMAPI events for mods. + /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke after the game finishes initialising. /// A callback to invoke when the game exits. - internal SGame(IMonitor monitor, Reflector reflection, EventManager eventManager, Action onGameInitialised, Action onGameExiting) + internal SGame(IMonitor monitor, Reflector reflection, EventManager eventManager, JsonHelper jsonHelper, Action onGameInitialised, Action onGameExiting) { + SGame.ConstructorHack = null; + // check expectations if (this.ContentCore == null) throw new InvalidOperationException($"The game didn't initialise its first content manager before SMAPI's {nameof(SGame)} constructor. This indicates an incompatible lifecycle change."); @@ -147,6 +147,7 @@ namespace StardewModdingAPI.Framework this.Monitor = monitor; this.Events = eventManager; this.Reflection = reflection; + this.JsonHelper = jsonHelper; this.OnGameInitialised = onGameInitialised; this.OnGameExiting = onGameExiting; Game1.input = new SInputState(); @@ -191,8 +192,7 @@ namespace StardewModdingAPI.Framework // NOTE: this method is called before the SGame constructor runs. Don't depend on anything being initialised at this point. if (this.ContentCore == null) { - this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, SGame.MonitorDuringInitialisation, SGame.ReflectorDuringInitialisation); - SGame.MonitorDuringInitialisation = null; + this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, SGame.ConstructorHack.Monitor, SGame.ConstructorHack.Reflection, SGame.ConstructorHack.JsonHelper); this.NextContentManagerIsMain = true; return this.ContentCore.CreateGameContentManager("Game1._temporaryContent"); } diff --git a/src/SMAPI/Framework/SGameConstructorHack.cs b/src/SMAPI/Framework/SGameConstructorHack.cs new file mode 100644 index 00000000..494bab99 --- /dev/null +++ b/src/SMAPI/Framework/SGameConstructorHack.cs @@ -0,0 +1,37 @@ +using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Toolkit.Serialisation; +using StardewValley; + +namespace StardewModdingAPI.Framework +{ + /// The static state to use while is initialising, which happens before the constructor runs. + internal class SGameConstructorHack + { + /********* + ** Accessors + *********/ + /// Encapsulates monitoring and logging. + public IMonitor Monitor { get; } + + /// Simplifies access to private game code. + public Reflector Reflection { get; } + + /// Encapsulates SMAPI's JSON file parsing. + public JsonHelper JsonHelper { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Encapsulates monitoring and logging. + /// Simplifies access to private game code. + /// Encapsulates SMAPI's JSON file parsing. + public SGameConstructorHack(IMonitor monitor, Reflector reflection, JsonHelper jsonHelper) + { + this.Monitor = monitor; + this.Reflection = reflection; + this.JsonHelper = jsonHelper; + } + } +} diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index a894e831..634c5066 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -229,9 +229,8 @@ namespace StardewModdingAPI AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => AssemblyLoader.ResolveAssembly(e.Name); // override game - SGame.MonitorDuringInitialisation = this.Monitor; - SGame.ReflectorDuringInitialisation = this.Reflection; - this.GameInstance = new SGame(this.Monitor, this.Reflection, this.EventManager, this.InitialiseAfterGameStart, this.Dispose); + SGame.ConstructorHack = new SGameConstructorHack(this.Monitor, this.Reflection, this.Toolkit.JsonHelper); + this.GameInstance = new SGame(this.Monitor, this.Reflection, this.EventManager, this.Toolkit.JsonHelper, this.InitialiseAfterGameStart, this.Dispose); StardewValley.Program.gamePtr = this.GameInstance; // add exit handler @@ -1160,7 +1159,10 @@ namespace StardewModdingAPI string locale = Path.GetFileNameWithoutExtension(file.Name.ToLower().Trim()); try { - translations[locale] = jsonHelper.ReadJsonFile>(file.FullName); + if (jsonHelper.ReadJsonFileIfExists(file.FullName, out IDictionary data)) + translations[locale] = data; + else + metadata.LogAsMod($"Mod's i18n/{locale}.json file couldn't be parsed."); } catch (Exception ex) { diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 0d0a5fe9..fc2d45ba 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -109,6 +109,7 @@ + diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs index de8d0f02..f1cce4a4 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -51,8 +51,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning { try { - manifest = this.JsonHelper.ReadJsonFile(manifestFile.FullName); - if (manifest == null) + if (!this.JsonHelper.ReadJsonFileIfExists(manifestFile.FullName, out manifest)) manifestError = "its manifest is invalid."; } catch (SParseException ex) diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs index 3cabbab3..cc8eeb73 100644 --- a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs +++ b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs @@ -32,10 +32,11 @@ namespace StardewModdingAPI.Toolkit.Serialisation /// Read a JSON file. /// The model type. /// The absolete file path. - /// Returns the deserialised model, or null if the file doesn't exist or is empty. - /// The given path is empty or invalid. - public TModel ReadJsonFile(string fullPath) - where TModel : class + /// The parsed content model. + /// Returns false if the file doesn't exist, else true. + /// The given is empty or invalid. + /// The file contains invalid JSON. + public bool ReadJsonFileIfExists(string fullPath, out TModel result) { // validate if (string.IsNullOrWhiteSpace(fullPath)) @@ -49,13 +50,15 @@ namespace StardewModdingAPI.Toolkit.Serialisation } catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) { - return null; + result = default(TModel); + return false; } // deserialise model try { - return this.Deserialise(json); + result = this.Deserialise(json); + return true; } catch (Exception ex) { -- cgit From 9029633f7f1076415fcb9e76fd3e0e58357ddcec Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Aug 2018 23:17:36 -0400 Subject: overhaul installer display (#554) The installer now validates preconditions earlier when possible, and after each step will reset the text and condense details from previous steps. This way players only see info for the current question to avoid confusion, and it's easier to add new steps. --- src/SMAPI.Installer/Framework/InstallerPaths.cs | 61 ++++ src/SMAPI.Installer/InteractiveInstaller.cs | 385 ++++++++++++--------- .../StardewModdingAPI.Installer.csproj | 1 + 3 files changed, 282 insertions(+), 165 deletions(-) create mode 100644 src/SMAPI.Installer/Framework/InstallerPaths.cs (limited to 'src') diff --git a/src/SMAPI.Installer/Framework/InstallerPaths.cs b/src/SMAPI.Installer/Framework/InstallerPaths.cs new file mode 100644 index 00000000..d212876a --- /dev/null +++ b/src/SMAPI.Installer/Framework/InstallerPaths.cs @@ -0,0 +1,61 @@ +using System.IO; + +namespace StardewModdingAPI.Installer.Framework +{ + /// Manages paths for the SMAPI installer. + internal class InstallerPaths + { + /********* + ** Accessors + *********/ + /// The directory containing the installer files for the current platform. + public DirectoryInfo PackageDir { get; } + + /// The directory containing the installed game. + public DirectoryInfo GameDir { get; } + + /// The directory into which to install mods. + public DirectoryInfo ModsDir { get; } + + /// The full path to the directory containing the installer files for the current platform. + public string PackagePath => this.PackageDir.FullName; + + /// The full path to the directory containing the installed game. + public string GamePath => this.GameDir.FullName; + + /// The full path to the directory into which to install mods. + public string ModsPath => this.ModsDir.FullName; + + /// The full path to the installed SMAPI executable file. + public string ExecutablePath { get; } + + /// The full path to the vanilla game launcher on Linux/Mac. + public string UnixLauncherPath { get; } + + /// The full path to the installed SMAPI launcher on Linux/Mac before it's renamed. + public string UnixSmapiLauncherPath { get; } + + /// The full path to the vanilla game launcher on Linux/Mac after SMAPI is installed. + public string UnixBackupLauncherPath { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The directory path containing the installer files for the current platform. + /// The directory path for the installed game. + /// The name of the game's executable file for the current platform. + public InstallerPaths(DirectoryInfo packageDir, DirectoryInfo gameDir, string gameExecutableName) + { + this.PackageDir = packageDir; + this.GameDir = gameDir; + this.ModsDir = new DirectoryInfo(Path.Combine(gameDir.FullName, "Mods")); + +