diff options
Diffstat (limited to 'src')
61 files changed, 1415 insertions, 1191 deletions
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 55e9c064..ab07c864 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -9,6 +9,7 @@ using StardewModdingApi.Installer.Enums; using StardewModdingAPI.Installer.Framework; using StardewModdingAPI.Internal.ConsoleWriting; using StardewModdingAPI.Toolkit; +using StardewModdingAPI.Toolkit.Framework; using StardewModdingAPI.Toolkit.Framework.ModScanning; using StardewModdingAPI.Toolkit.Utilities; @@ -571,7 +572,7 @@ namespace StardewModdingApi.Installer /// <param name="executablePath">The absolute path to the executable file.</param> private bool Is64Bit(string executablePath) { - return AssemblyName.GetAssemblyName(executablePath).ProcessorArchitecture != ProcessorArchitecture.X86; + return LowLevelEnvironmentUtility.Is64BitAssembly(executablePath); } /// <summary>Get the display text for a color scheme.</summary> diff --git a/src/SMAPI.Internal.Patching/BasePatcher.cs b/src/SMAPI.Internal.Patching/BasePatcher.cs new file mode 100644 index 00000000..87155d7f --- /dev/null +++ b/src/SMAPI.Internal.Patching/BasePatcher.cs @@ -0,0 +1,54 @@ +using System; +using System.Reflection; +using HarmonyLib; + +namespace StardewModdingAPI.Internal.Patching +{ + /// <summary>Provides base implementation logic for <see cref="IPatcher"/> instances.</summary> + internal abstract class BasePatcher : IPatcher + { + /********* + ** Public methods + *********/ + /// <inheritdoc /> + public abstract void Apply(Harmony harmony, IMonitor monitor); + + + /********* + ** Protected methods + *********/ + /// <summary>Get a method and assert that it was found.</summary> + /// <typeparam name="TTarget">The type containing the method.</typeparam> + /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param> + protected ConstructorInfo RequireConstructor<TTarget>(params Type[] parameters) + { + return PatchHelper.RequireConstructor<TTarget>(parameters); + } + + /// <summary>Get a method and assert that it was found.</summary> + /// <typeparam name="TTarget">The type containing the method.</typeparam> + /// <param name="name">The method name.</param> + /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param> + /// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param> + protected MethodInfo RequireMethod<TTarget>(string name, Type[] parameters = null, Type[] generics = null) + { + return PatchHelper.RequireMethod<TTarget>(name, parameters, generics); + } + + /// <summary>Get a Harmony patch method on the current patcher instance.</summary> + /// <param name="name">The method name.</param> + /// <param name="priority">The patch priority to apply, usually specified using Harmony's <see cref="Priority"/> enum, or <c>null</c> to keep the default value.</param> + protected HarmonyMethod GetHarmonyMethod(string name, int? priority = null) + { + var method = new HarmonyMethod( + AccessTools.Method(this.GetType(), name) + ?? throw new InvalidOperationException($"Can't find patcher method {PatchHelper.GetMethodString(this.GetType(), name)}.") + ); + + if (priority.HasValue) + method.priority = priority.Value; + + return method; + } + } +} diff --git a/src/SMAPI.Internal.Patching/HarmonyPatcher.cs b/src/SMAPI.Internal.Patching/HarmonyPatcher.cs new file mode 100644 index 00000000..c07e3b41 --- /dev/null +++ b/src/SMAPI.Internal.Patching/HarmonyPatcher.cs @@ -0,0 +1,36 @@ +using System; +using HarmonyLib; + +namespace StardewModdingAPI.Internal.Patching +{ + /// <summary>Simplifies applying <see cref="IPatcher"/> instances to the game.</summary> + internal static class HarmonyPatcher + { + /********* + ** Public methods + *********/ + /// <summary>Apply the given Harmony patchers.</summary> + /// <param name="id">The mod ID applying the patchers.</param> + /// <param name="monitor">The monitor with which to log any errors.</param> + /// <param name="patchers">The patchers to apply.</param> + public static Harmony Apply(string id, IMonitor monitor, params IPatcher[] patchers) + { + Harmony harmony = new Harmony(id); + + foreach (IPatcher patcher in patchers) + { + try + { + patcher.Apply(harmony, monitor); + } + catch (Exception ex) + { + monitor.Log($"Couldn't apply runtime patch '{patcher.GetType().Name}' to the game. Some SMAPI features may not work correctly. See log file for details.", LogLevel.Error); + monitor.Log($"Technical details:\n{ex.GetLogSummary()}"); + } + } + + return harmony; + } + } +} diff --git a/src/SMAPI.Internal.Patching/IPatcher.cs b/src/SMAPI.Internal.Patching/IPatcher.cs new file mode 100644 index 00000000..a732d64f --- /dev/null +++ b/src/SMAPI.Internal.Patching/IPatcher.cs @@ -0,0 +1,16 @@ +using HarmonyLib; + +namespace StardewModdingAPI.Internal.Patching +{ + /// <summary>A set of Harmony patches to apply.</summary> + internal interface IPatcher + { + /********* + ** Public methods + *********/ + /// <summary>Apply the Harmony patches for this instance.</summary> + /// <param name="harmony">The Harmony instance.</param> + /// <param name="monitor">The monitor with which to log any errors.</param> + public void Apply(Harmony harmony, IMonitor monitor); + } +} diff --git a/src/SMAPI.Internal.Patching/PatchHelper.cs b/src/SMAPI.Internal.Patching/PatchHelper.cs new file mode 100644 index 00000000..fc79ddf2 --- /dev/null +++ b/src/SMAPI.Internal.Patching/PatchHelper.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Text; +using HarmonyLib; + +namespace StardewModdingAPI.Internal.Patching +{ + /// <summary>Provides utility methods for patching game code with Harmony.</summary> + internal static class PatchHelper + { + /********* + ** Public methods + *********/ + /// <summary>Get a constructor and assert that it was found.</summary> + /// <typeparam name="TTarget">The type containing the method.</typeparam> + /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param> + /// <exception cref="InvalidOperationException">The type has no matching constructor.</exception> + public static ConstructorInfo RequireConstructor<TTarget>(Type[] parameters = null) + { + return + AccessTools.Constructor(typeof(TTarget), parameters) + ?? throw new InvalidOperationException($"Can't find constructor {PatchHelper.GetMethodString(typeof(TTarget), null, parameters)} to patch."); + } + + /// <summary>Get a method and assert that it was found.</summary> + /// <typeparam name="TTarget">The type containing the method.</typeparam> + /// <param name="name">The method name.</param> + /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param> + /// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param> + /// <exception cref="InvalidOperationException">The type has no matching method.</exception> + public static MethodInfo RequireMethod<TTarget>(string name, Type[] parameters = null, Type[] generics = null) + { + return + AccessTools.Method(typeof(TTarget), name, parameters, generics) + ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(typeof(TTarget), name, parameters, generics)} to patch."); + } + + /// <summary>Get a human-readable representation of a method target.</summary> + /// <param name="type">The type containing the method.</param> + /// <param name="name">The method name, or <c>null</c> for a constructor.</param> + /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param> + /// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param> + public static string GetMethodString(Type type, string name, Type[] parameters = null, Type[] generics = null) + { + StringBuilder str = new StringBuilder(); + + // type + str.Append(type.FullName); + + // method name (if not constructor) + if (name != null) + { + str.Append('.'); + str.Append(name); + } + + // generics + if (generics?.Any() == true) + { + str.Append('<'); + str.Append(string.Join(", ", generics.Select(p => p.FullName))); + str.Append('>'); + } + + // parameters + if (parameters?.Any() == true) + { + str.Append('('); + str.Append(string.Join(", ", parameters.Select(p => p.FullName))); + str.Append(')'); + } + + return str.ToString(); + } + } +} diff --git a/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.projitems b/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.projitems new file mode 100644 index 00000000..4fa2a062 --- /dev/null +++ b/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.projitems @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' < '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> + <HasSharedItems>true</HasSharedItems> + <SharedGUID>6c16e948-3e5c-47a7-bf4b-07a7469a87a5</SharedGUID> + </PropertyGroup> + <PropertyGroup Label="Configuration"> + <Import_RootNamespace>SMAPI.Internal.Patching</Import_RootNamespace> + </PropertyGroup> + <ItemGroup> + <Compile Include="$(MSBuildThisFileDirectory)BasePatcher.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)HarmonyPatcher.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)IPatcher.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)PatchHelper.cs" /> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.shproj b/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.shproj new file mode 100644 index 00000000..1a102c82 --- /dev/null +++ b/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.shproj @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Label="Globals"> + <ProjectGuid>6c16e948-3e5c-47a7-bf4b-07a7469a87a5</ProjectGuid> + <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion> + </PropertyGroup> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" /> + <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" /> + <PropertyGroup /> + <Import Project="SMAPI.Internal.Patching.projitems" Label="Shared" /> + <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" /> +</Project> diff --git a/src/SMAPI.Internal/ExceptionExtensions.cs b/src/SMAPI.Internal/ExceptionExtensions.cs new file mode 100644 index 00000000..d7a2252b --- /dev/null +++ b/src/SMAPI.Internal/ExceptionExtensions.cs @@ -0,0 +1,41 @@ +using System; +using System.Reflection; + +namespace StardewModdingAPI.Internal +{ + /// <summary>Provides extension methods for handling exceptions.</summary> + internal static class ExceptionExtensions + { + /********* + ** Public methods + *********/ + /// <summary>Get a string representation of an exception suitable for writing to the error log.</summary> + /// <param name="exception">The error to summarize.</param> + public static string GetLogSummary(this Exception exception) + { + switch (exception) + { + case TypeLoadException ex: + return $"Failed loading type '{ex.TypeName}': {exception}"; + + case ReflectionTypeLoadException ex: + string summary = exception.ToString(); + foreach (Exception childEx in ex.LoaderExceptions) + summary += $"\n\n{childEx.GetLogSummary()}"; + return summary; + + default: + return exception.ToString(); + } + } + + /// <summary>Get the lowest exception in an exception stack.</summary> + /// <param name="exception">The exception from which to search.</param> + public static Exception GetInnermostException(this Exception exception) + { + while (exception.InnerException != null) + exception = exception.InnerException; + return exception; + } + } +} diff --git a/src/SMAPI.Internal/SMAPI.Internal.projitems b/src/SMAPI.Internal/SMAPI.Internal.projitems index 0d583a6d..0ee94a5b 100644 --- a/src/SMAPI.Internal/SMAPI.Internal.projitems +++ b/src/SMAPI.Internal/SMAPI.Internal.projitems @@ -14,5 +14,6 @@ <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ConsoleLogLevel.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\IConsoleWriter.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)ExceptionExtensions.cs" /> </ItemGroup> </Project>
\ No newline at end of file diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs index 4cfaf242..ceaeb278 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs @@ -15,7 +15,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World ** Fields *********/ /// <summary>The valid types that can be cleared.</summary> - private readonly string[] ValidTypes = { "crops", "debris", "fruit-trees", "furniture", "grass", "trees", "everything" }; + private readonly string[] ValidTypes = { "crops", "debris", "fruit-trees", "furniture", "grass", "trees", "removable", "everything" }; /// <summary>The resource clump IDs to consider debris.</summary> private readonly int[] DebrisClumps = { ResourceClump.stumpIndex, ResourceClump.hollowLogIndex, ResourceClump.meteoriteIndex, ResourceClump.boulderIndex }; @@ -30,8 +30,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World name: "world_clear", description: "Clears in-game entities in a given location.\n\n" + "Usage: world_clear <location> <object type>\n" - + "- location: the location name for which to clear objects (like Farm), or 'current' for the current location.\n" - + " - object type: the type of object clear. You can specify 'crops', 'debris' (stones/twigs/weeds and dead crops), 'furniture', 'grass', and 'trees' / 'fruit-trees'. You can also specify 'everything', which includes things not removed by the other types (like resource clumps)." + + " - location: the location name for which to clear objects (like Farm), or 'current' for the current location.\n" + + " - object type: the type of object clear. You can specify 'crops', 'debris' (stones/twigs/weeds and dead crops), 'furniture', 'grass', and 'trees' / 'fruit-trees'. You can also specify 'removable' (remove everything that can be removed or destroyed during normal gameplay) or 'everything' (remove everything including permanent bushes)." ) { } @@ -133,13 +133,15 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World break; } + case "removable": case "everything": { + bool everything = type == "everything"; int removed = this.RemoveFurniture(location, p => true) + this.RemoveObjects(location, p => true) + this.RemoveTerrainFeatures(location, p => true) - + this.RemoveLargeTerrainFeatures(location, p => true) + + this.RemoveLargeTerrainFeatures(location, p => everything || !(p is Bush bush) || bush.isDestroyable(location, p.currentTileLocation)) + this.RemoveResourceClumps(location, p => true); monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info); break; diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 1781c40d..09684f32 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.11.0", + "Version": "3.12.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.11.0" + "MinimumApiVersion": "3.12.0" } diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index d9426d75..067f6a8d 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -1,8 +1,7 @@ +using System; using System.Reflection; using StardewModdingAPI.Events; -using StardewModdingAPI.Framework; -using StardewModdingAPI.Framework.Logging; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewModdingAPI.Mods.ErrorHandler.Patches; using StardewValley; @@ -29,15 +28,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler IMonitor monitorForGame = this.GetMonitorForGame(); // apply patches - new GamePatcher(this.Monitor).Apply( - new DialogueErrorPatch(monitorForGame, this.Helper.Reflection), - new EventPatches(monitorForGame), - new GameLocationPatches(monitorForGame), - new ObjectErrorPatch(), - new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved), - new ScheduleErrorPatch(monitorForGame), - new SpriteBatchValidationPatches(), - new UtilityErrorPatches() + HarmonyPatcher.Apply(this.ModManifest.UniqueID, this.Monitor, + new DialoguePatcher(monitorForGame, this.Helper.Reflection), + new DictionaryPatcher(this.Helper.Reflection), + new EventPatcher(monitorForGame), + new GameLocationPatcher(monitorForGame), + new IClickableMenuPatcher(), + new NpcPatcher(monitorForGame), + new ObjectPatcher(), + new SaveGamePatcher(this.Monitor, this.OnSaveContentRemoved), + new SpriteBatchPatcher(), + new UtilityPatcher() ); // hook events @@ -70,12 +71,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler /// <summary>Get the monitor with which to log game errors.</summary> pr |
