summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-06-20 12:43:08 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-06-20 12:43:08 -0400
commite64ecc89f94641d4054162eff4943f660f43030f (patch)
treec43ca79f9947b3e16f946e1dc5fd1d02f70ce571 /src/SMAPI/Framework
parentdf6e745c6b842290338317ed1d3e969ee222998c (diff)
parentcb9ff7019995eff92104703f097856d2523e02ce (diff)
downloadSMAPI-e64ecc89f94641d4054162eff4943f660f43030f.tar.gz
SMAPI-e64ecc89f94641d4054162eff4943f660f43030f.tar.bz2
SMAPI-e64ecc89f94641d4054162eff4943f660f43030f.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/CommandManager.cs13
-rw-r--r--src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs202
-rw-r--r--src/SMAPI/Framework/Commands/HelpCommand.cs64
-rw-r--r--src/SMAPI/Framework/Commands/IInternalCommand.cs24
-rw-r--r--src/SMAPI/Framework/Commands/ReloadI18nCommand.cs44
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs5
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs11
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs151
-rw-r--r--src/SMAPI/Framework/Events/ManagedEventHandler.cs56
-rw-r--r--src/SMAPI/Framework/Events/ModDisplayEvents.cs20
-rw-r--r--src/SMAPI/Framework/Events/ModGameLoopEvents.cs28
-rw-r--r--src/SMAPI/Framework/Events/ModInputEvents.cs8
-rw-r--r--src/SMAPI/Framework/Events/ModMultiplayerEvents.cs15
-rw-r--r--src/SMAPI/Framework/Events/ModPlayerEvents.cs6
-rw-r--r--src/SMAPI/Framework/Events/ModSpecialisedEvents.cs6
-rw-r--r--src/SMAPI/Framework/Events/ModWorldEvents.cs8
-rw-r--r--src/SMAPI/Framework/IModMetadata.cs6
-rw-r--r--src/SMAPI/Framework/Input/GamePadStateBuilder.cs17
-rw-r--r--src/SMAPI/Framework/Input/MouseStateBuilder.cs23
-rw-r--r--src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs5
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs122
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs40
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs53
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs40
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs40
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs48
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs49
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs51
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs120
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs77
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs344
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs (renamed from src/SMAPI/Framework/ModLoading/RewriteHelper.cs)102
-rw-r--r--src/SMAPI/Framework/ModLoading/IInstructionHandler.cs32
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs11
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs44
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs84
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs47
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs (renamed from src/SMAPI/Framework/RewriteFacades/SpriteBatchMethods.cs)16
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs37
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs42
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs129
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs71
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs35
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs136
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs11
-rw-r--r--src/SMAPI/Framework/Networking/SGalaxyNetServer.cs29
-rw-r--r--src/SMAPI/Framework/Networking/SLidgrenServer.cs31
-rw-r--r--src/SMAPI/Framework/Patching/GamePatcher.cs10
-rw-r--r--src/SMAPI/Framework/Patching/IHarmonyPatch.cs8
-rw-r--r--src/SMAPI/Framework/Patching/PatchHelper.cs2
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/AlertContext.cs2
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/AlertEntry.cs2
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PeakEntry.cs2
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs3
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterEntry.cs2
-rw-r--r--src/SMAPI/Framework/Rendering/SDisplayDevice.cs13
-rw-r--r--src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs193
-rw-r--r--src/SMAPI/Framework/SGame.cs4
-rw-r--r--src/SMAPI/Framework/SMultiplayer.cs4
-rw-r--r--src/SMAPI/Framework/Serialization/ColorConverter.cs2
-rw-r--r--src/SMAPI/Framework/Serialization/PointConverter.cs2
-rw-r--r--src/SMAPI/Framework/Serialization/RectangleConverter.cs2
-rw-r--r--src/SMAPI/Framework/Serialization/Vector2Converter.cs2
64 files changed, 1912 insertions, 896 deletions
diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs
index ceeb6f93..eaa91c86 100644
--- a/src/SMAPI/Framework/CommandManager.cs
+++ b/src/SMAPI/Framework/CommandManager.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using StardewModdingAPI.Framework.Commands;
namespace StardewModdingAPI.Framework
{
@@ -27,7 +28,7 @@ namespace StardewModdingAPI.Framework
/// <exception cref="ArgumentNullException">The <paramref name="name"/> or <paramref name="callback"/> is null or empty.</exception>
/// <exception cref="FormatException">The <paramref name="name"/> is not a valid format.</exception>
/// <exception cref="ArgumentException">There's already a command with that name.</exception>
- public void Add(IModMetadata mod, string name, string documentation, Action<string, string[]> callback, bool allowNullCallback = false)
+ public CommandManager Add(IModMetadata mod, string name, string documentation, Action<string, string[]> callback, bool allowNullCallback = false)
{
name = this.GetNormalizedName(name);
@@ -45,6 +46,16 @@ namespace StardewModdingAPI.Framework
// add command
this.Commands.Add(name, new Command(mod, name, documentation, callback));
+ return this;
+ }
+
+ /// <summary>Add a console command.</summary>
+ /// <param name="command">the SMAPI console command to add.</param>
+ /// <param name="monitor">Writes messages to the console.</param>
+ /// <exception cref="ArgumentException">There's already a command with that name.</exception>
+ public CommandManager Add(IInternalCommand command, IMonitor monitor)
+ {
+ return this.Add(null, command.Name, command.Description, (name, args) => command.HandleCommand(args, monitor));
}
/// <summary>Get a command by its unique name.</summary>
diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
new file mode 100644
index 00000000..8fdd4282
--- /dev/null
+++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+#if HARMONY_2
+using HarmonyLib;
+#else
+using Harmony;
+#endif
+
+namespace StardewModdingAPI.Framework.Commands
+{
+ /// <summary>The 'harmony_summary' SMAPI console command.</summary>
+ internal class HarmonySummaryCommand : IInternalCommand
+ {
+#if !HARMONY_2
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The Harmony instance through which to fetch patch info.</summary>
+ private readonly HarmonyInstance HarmonyInstance = HarmonyInstance.Create($"SMAPI.{nameof(HarmonySummaryCommand)}");
+#endif
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The command name, which the user must type to trigger it.</summary>
+ public string Name { get; } = "harmony_summary";
+
+ /// <summary>The human-readable documentation shown when the player runs the built-in 'help' command.</summary>
+ public string Description { get; } = "Harmony is a library which rewrites game code, used by SMAPI and some mods. This command lists current Harmony patches.\n\nUsage: harmony_summary\nList all Harmony patches.\n\nUsage: harmony_summary <search>\n- search: one more more words to search. If any word matches a method name, the method and all its patchers will be listed; otherwise only matching patchers will be listed for the method.";
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Handle the console command when it's entered by the user.</summary>
+ /// <param name="args">The command arguments.</param>
+ /// <param name="monitor">Writes messages to the console.</param>
+ public void HandleCommand(string[] args, IMonitor monitor)
+ {
+ SearchResult[] matches = this.FilterPatches(args).OrderBy(p => p.MethodName).ToArray();
+
+ StringBuilder result = new StringBuilder();
+
+ if (!matches.Any())
+ result.AppendLine("No current patches match your search.");
+ else
+ {
+ result.AppendLine(args.Any() ? "Harmony patches which match your search terms:" : "Current Harmony patches:");
+ result.AppendLine();
+ foreach (var match in matches)
+ {
+ result.AppendLine($" {match.MethodName}");
+ foreach (var ownerGroup in match.PatchTypesByOwner.OrderBy(p => p.Key))
+ {
+ var sortedTypes = ownerGroup.Value
+ .OrderBy(p => p switch
+ {
+ PatchType.Prefix => 0,
+ PatchType.Postfix => 1,
+#if HARMONY_2
+ PatchType.Finalizer => 2,
+#endif
+ PatchType.Transpiler => 3,
+ _ => 4
+ });
+
+ result.AppendLine($" - {ownerGroup.Key} ({string.Join(", ", sortedTypes).ToLower()})");
+ }
+ result.AppendLine();
+ }
+ }
+
+ monitor.Log(result.ToString().TrimEnd(), LogLevel.Info);
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get all current Harmony patches matching any of the given search terms.</summary>
+ /// <param name="searchTerms">The search terms to match.</param>
+ private IEnumerable<SearchResult> FilterPatches(string[] searchTerms)
+ {
+ bool hasSearch = searchTerms.Any();
+ bool IsMatch(string target) => !hasSearch || searchTerms.Any(search => target != null && target.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1);
+
+ foreach (var patch in this.GetAllPatches())
+ {
+ // matches entire patch
+ if (IsMatch(patch.MethodDescription))
+ {
+ yield return patch;
+ continue;
+ }
+
+ // matches individual patchers
+ foreach (var pair in patch.PatchTypesByOwner.ToArray())
+ {
+ if (!IsMatch(pair.Key) && !pair.Value.Any(type => IsMatch(type.ToString())))
+ patch.PatchTypesByOwner.Remove(pair.Key);
+ }
+
+ if (patch.PatchTypesByOwner.Any())
+ yield return patch;
+ }
+ }
+
+ /// <summary>Get all current Harmony patches.</summary>
+ private IEnumerable<SearchResult> GetAllPatches()
+ {
+#if HARMONY_2
+ foreach (MethodBase method in Harmony.GetAllPatchedMethods())
+#else
+ foreach (MethodBase method in this.HarmonyInstance.GetPatchedMethods())
+#endif
+ {
+ // get metadata for method
+#if HARMONY_2
+ HarmonyLib.Patches patchInfo = Harmony.GetPatchInfo(method);
+#else
+ Harmony.Patches patchInfo = this.HarmonyInstance.GetPatchInfo(method);
+#endif
+
+ IDictionary<PatchType, IReadOnlyCollection<Patch>> patchGroups = new Dictionary<PatchType, IReadOnlyCollection<Patch>>
+ {
+ [PatchType.Prefix] = patchInfo.Prefixes,
+ [PatchType.Postfix] = patchInfo.Postfixes,
+#if HARMONY_2
+ [PatchType.Finalizer] = patchInfo.Finalizers,
+#endif
+ [PatchType.Transpiler] = patchInfo.Transpilers
+ };
+
+ // get patch types by owner
+ var typesByOwner = new Dictionary<string, ISet<PatchType>>();
+ foreach (var group in patchGroups)
+ {
+ foreach (var patch in group.Value)
+ {
+ if (!typesByOwner.TryGetValue(patch.owner, out ISet<PatchType> patchTypes))
+ typesByOwner[patch.owner] = patchTypes = new HashSet<PatchType>();
+ patchTypes.Add(group.Key);
+ }
+ }
+
+ // create search result
+ yield return new SearchResult(method, typesByOwner);
+ }
+ }
+
+ /// <summary>A Harmony patch type.</summary>
+ private enum PatchType
+ {
+ /// <summary>A prefix patch.</summary>
+ Prefix,
+
+ /// <summary>A postfix patch.</summary>
+ Postfix,
+
+#if HARMONY_2
+ /// <summary>A finalizer patch.</summary>
+ Finalizer,
+#endif
+
+ /// <summary>A transpiler patch.</summary>
+ Transpiler
+ }
+
+ /// <summary>A patch search result for a method.</summary>
+ private class SearchResult
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>A simple human-readable name for the patched method.</summary>
+ public string MethodName { get; }
+
+ /// <summary>A detailed description for the patched method.</summary>
+ public string MethodDescription { get; }
+
+ /// <summary>The patch types by the Harmony instance ID that added them.</summary>
+ public IDictionary<string, ISet<PatchType>> PatchTypesByOwner { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="method">The patched method.</param>
+ /// <param name="patchTypesByOwner">The patch types by the Harmony instance ID that added them.</param>
+ public SearchResult(MethodBase method, IDictionary<string, ISet<PatchType>> patchTypesByOwner)
+ {
+ this.MethodName = $"{method.DeclaringType?.FullName}.{method.Name}";
+ this.MethodDescription = method.FullDescription();
+ this.PatchTypesByOwner = patchTypesByOwner;
+ }
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Commands/HelpCommand.cs b/src/SMAPI/Framework/Commands/HelpCommand.cs
new file mode 100644
index 00000000..b8730a00
--- /dev/null
+++ b/src/SMAPI/Framework/Commands/HelpCommand.cs
@@ -0,0 +1,64 @@
+using System.Linq;
+
+namespace StardewModdingAPI.Framework.Commands
+{
+ /// <summary>The 'help' SMAPI console command.</summary>
+ internal class HelpCommand : IInternalCommand
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Manages console commands.</summary>
+ private readonly CommandManager CommandManager;
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The command name, which the user must type to trigger it.</summary>
+ public string Name { get; } = "help";
+
+ /// <summary>The human-readable documentation shown when the player runs the built-in 'help' command.</summary>
+ public string Description { get; } = "Lists command documentation.\n\nUsage: help\nLists all available commands.\n\nUsage: help <cmd>\n- cmd: The name of a command whose documentation to display.";
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="commandManager">Manages console commands.</param>
+ public HelpCommand(CommandManager commandManager)
+ {
+ this.CommandManager = commandManager;
+ }
+
+ /// <summary>Handle the console command when it's entered by the user.</summary>
+ /// <param name="args">The command arguments.</param>
+ /// <param name="monitor">Writes messages to the console.</param>
+ public void HandleCommand(string[] args, IMonitor monitor)
+ {
+ if (args.Any())
+ {
+ Command result = this.CommandManager.Get(args[0]);
+ if (result == null)
+ monitor.Log("There's no command with that name.", LogLevel.Error);
+ else
+ monitor.Log($"{result.Name}: {result.Documentation}{(result.Mod != null ? $"\n(Added by {result.Mod.DisplayName}.)" : "")}", LogLevel.Info);
+ }
+ else
+ {
+ string message = "The following commands are registered:\n";
+ IGrouping<string, string>[] groups = (from command in this.CommandManager.GetAll() orderby command.Mod?.DisplayName, command.Name group command.Name by command.Mod?.DisplayName).ToArray();
+ foreach (var group in groups)
+ {
+ string modName = group.Key ?? "SMAPI";
+ string[] commandNames = group.ToArray();
+ message += $"{modName}:\n {string.Join("\n ", commandNames)}\n\n";
+ }
+ message += "For more information about a command, type 'help command_name'.";
+
+ monitor.Log(message, LogLevel.Info);
+ }
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Commands/IInternalCommand.cs b/src/SMAPI/Framework/Commands/IInternalCommand.cs
new file mode 100644
index 00000000..abf105b6
--- /dev/null
+++ b/src/SMAPI/Framework/Commands/IInternalCommand.cs
@@ -0,0 +1,24 @@
+namespace StardewModdingAPI.Framework.Commands
+{
+ /// <summary>A core SMAPI console command.</summary>
+ interface IInternalCommand
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The command name, which the user must type to trigger it.</summary>
+ string Name { get; }
+
+ /// <summary>The human-readable documentation shown when the player runs the built-in 'help' command.</summary>
+ string Description { get; }
+
+
+ /*********
+ ** Methods
+ *********/
+ /// <summary>Handle the console command when it's entered by the user.</summary>
+ /// <param name="args">The command arguments.</param>
+ /// <param name="monitor">Writes messages to the console.</param>
+ void HandleCommand(string[] args, IMonitor monitor);
+ }
+}
diff --git a/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs b/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs
new file mode 100644
index 00000000..12328bb6
--- /dev/null
+++ b/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs
@@ -0,0 +1,44 @@
+using System;
+
+namespace StardewModdingAPI.Framework.Commands
+{
+ /// <summary>The 'reload_i18n' SMAPI console command.</summary>
+ internal class ReloadI18nCommand : IInternalCommand
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Reload translations for all mods.</summary>
+ private readonly Action ReloadTranslations;
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The command name, which the user must type to trigger it.</summary>
+ public string Name { get; } = "reload_i18n";
+
+ /// <summary>The human-readable documentation shown when the player runs the built-in 'help' command.</summary>
+ public string Description { get; } = "Reloads translation files for all mods.\n\nUsage: reload_i18n";
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="reloadTranslations">Reload translations for all mods..</param>
+ public ReloadI18nCommand(Action reloadTranslations)
+ {
+ this.ReloadTranslations = reloadTranslations;
+ }
+
+ /// <summary>Handle the console command when it's entered by the user.</summary>
+ /// <param name="args">The command arguments.</param>
+ /// <param name="monitor">Writes messages to the console.</param>
+ public void HandleCommand(string[] args, IMonitor monitor)
+ {
+ this.ReloadTranslations();
+ monitor.Log("Reloaded translation files for all mods. This only affects new translations the mods fetch; if they cached some text, it may not be updated.", LogLevel.Info);
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index fda80a83..cfda55b9 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -246,10 +246,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
texture.GetData(data);
for (int i = 0; i < data.Length; i++)
{
- if (data[i].A == byte.MinValue || data[i].A == byte.MaxValue)
+ var pixel = data[i];
+ if (pixel.A == byte.MinValue || pixel.A == byte.MaxValue)
continue; // no need to change fully transparent/opaque pixels
- data[i] = Color.FromNonPremultiplied(data[i].ToVector4());
+ data[i] = new Color(pixel.R * pixel.A / byte.MaxValue, pixel.G * pixel.A / byte.MaxValue, pixel.B * pixel.A / byte.MaxValue, pixel.A); // slower version: Color.FromNonPremultiplied(data[i].ToVector4())