Content Patcher Harmony Patches

WARNING! This feature is more advanced than the others and requires a little bit more technical know-how and self-guidance.

BETAS provides many new triggers to allow you to react to many more specific events than vanilla otherwise provides, but it cannot cover every possible scenario you might want. If you've hung around in the #making-mods-general channel in the Stardew Valley Discord, you may have noticed people mention something called "Harmony" from time to time. Harmony is a library available to C# programmers that allows mod authors to alter or react to any code in the game (by writing snippets of code called patches) rather than being limited to Trigger Actions. The Harmony Patching feature of BETAS aims to provide a small amount of that same freedom to you via JSON and Content Patcher, potentially removing the need to program a C# mod if all you want is to run an action after a certain event happens.

In order to utilize this feature of BETAS, you must be able to decompile your game and look at its code. The official wiki has steps you can follow here to get started.

However, this feature is weird and not for everyone (it might not really be for anyone) and a full explanation of how to use it would be impossible without also explaining the basics of C# and ILSpy, which is a bit much for this documentation. If you are someone who has that basic knowledge already but just not enough to program and build a C# mod yourself, this is kinda maybe for you? If you are not that kind of someone, but you know you just want to run an action after a very specific event that is not covered by BETAS, you might try asking in the Discord for help finding a suitable method to patch and what the Type names and whatnot should be. However, please also understand if they would rather respond with "this is Button's weird stuff, please don't bother me with it" and don't make them try and figure out my weird BETAS feature! If you'd like, you can feel free to ping me @spiderbuttons directly in the mod making channel for help with this patching feature if you still want to use it.

Patch Model

A BETAS Harmony Patch consists of an object with the following fields:

FIELD PURPOSE
Id The Id of your patch. This should be globally unique, so using the {{ModId}} Content Patcher token is highly recommended.
Target A TargetMethod object defining what method you want to patch. See below for more information.
PatchType The type of patch you want to create. This can be Prefix or Postfix. A prefix will run your action(s) before the target method runs, while a postfix will run your action(s) after the target method has already finished.
ChangeResult (Optional) A ResultOperation object defining how you want to change the result of the method you are patching. This is only applicable with Postfix patches. See below for more information.
Condition (Optional) A Game State Query determining whether or not your actions or result changes should apply. If not set, this will default to TRUE.
Actions (Optional) The actions to perform, as a list of strings.
Action (Optional) A single action to perform.

Target Method Model

A Target Method object consists of the following fields:

FIELD PURPOSE
Type The full Type name that the method you want to patch is defined in. It is always best to define the type as completely as possible to eliminate ambiguity i.e. "StardewValley.Menus.LetterViewerMenu" instead of just "LetterViewerMenu" though BETAS will attempt to resolve the Type as best as it can.
Method (Optional) The name of the method you want to patch within the aforementioned Type. If not set, your patch will target the Type constructor instead.
Parameters (Optional) A string list of Types that the method you want to patch takes as parameters. It is always best to define the type as completely as possible to eliminate ambiguity i.e. "Microsoft.Xna.Framework.Vector2" instead of just "Vector2" though BETAS will attempt to resolve the types as best as it can. If not set, BETAS will look for a function that takes no parameters.
IsGetter (Optional) A boolean value determining whether or not the method you want to patch is a property getter. Defaults to false.
IsSetter (Optional) A boolean value determining whether or not the method you want to patch is a property setter. Defaults to false.
Assembly (Optional) The name of the assembly that your type and method are found in. Defaults to Stardew Valley.

Result Operation Model

A Result Operation object consists of the following fields:

FIELD PURPOSE
Operation What kind of operation you want to perform on the result that the method you are patching would have originally returned. Possible values are ADD, SUBTRACT, MULTIPLY, DIVIDE, and ASSIGN. You may use any of these operations on numeric return types. For boolean return types, you may only use ASSIGN. For strings, you may use ADD and ASSIGN. Other types are not supported.
Value The value that will be used when performing the aforementioned operation on the result.

Examples

Here is an example of a Harmony Patch that will increment a stat after a quest is accepted from a letter during the Spring season:

 1
{
 2
   "Action": "EditData",
 3
   "Target": "Spiderbuttons.BETAS/HarmonyPatches",
 4
   "Entries": {
 5
      "ExampleTriggerAction": {
 6
         "Id": "ExampleTriggerAction",
 7
         "Target": {
 8
            "Type": "StardewValley.Menus.LetterViewerMenu",
 9
            "Method": "AcceptQuest",
10
            "Parameters": null,
11
            "IsGetter": false,
12
            "IsSetter": false,
13
            "Assembly": "Stardew Valley"
14
         },
15
         "PatchType": "Postfix",
16
         "ChangeResult": null,
17
         "Condition": "SEASON spring",
18
         "Action": "IncrementStat SpringLetterQuests 1"
19
      }
20
   }
21
}

Here is an example of a Harmony Patch that will change the DisplayName of every location in the game:

 1
{
 2
   "Action": "EditData",
 3
   "Target": "Spiderbuttons.BETAS/HarmonyPatches",
 4
   "Entries": {
 5
      "ExampleTriggerAction": {
 6
         "Id": "ExampleTriggerAction",
 7
         "Target": {
 8
            "Type": "StardewValley.GameLocation",
 9
            "Method": "DisplayName",
10
            "IsGetter": true
11
         },
12
         "PatchType": "Postfix",
13
         "ChangeResult": {
14
            "Operation": "ASSIGN",
15
            "Value": "New Name"
16
         }
17
      }
18
   }
19
}