Skip to content

Commit

Permalink
Update Mod to v2.1.0/Restructured Project
Browse files Browse the repository at this point in the history
- Split up the UIAssemblerWindow Patch and the main Mod file
- Added a function to search for the correct patterns to hook the CodeMatcher into and use the found field indices instead of hardcoded ones
  • Loading branch information
Shad0wlife committed Dec 16, 2021
1 parent 889e38a commit 5459654
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 177 deletions.
148 changes: 6 additions & 142 deletions DSP_AssemblerUI/AssemblerSpeedUI/AssemblerSpeedUIMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
using BepInEx.Logging;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Reflection.Emit;

namespace DSP_AssemblerUI.AssemblerSpeedUI
{
Expand All @@ -25,8 +23,6 @@ public class AssemblerSpeedUIMod : BaseUnityPlugin
public static ConfigEntry<bool> configShowMinerSpeed;
public static ConfigEntry<bool> configShowMinerLiveSpeed;
public static ConfigEntry<bool> configMinerSpeedsPerSecond;

private static AdditionalSpeedLabels additionalSpeedLabels;

internal void Awake()
{
Expand All @@ -47,17 +43,17 @@ internal void Awake()
configShowMinerLiveSpeed = Config.Bind("Miner", "ShowMinerLiveSpeedInfo", false, "True: shows current speed of production building. False: shows regular recipe speed of production building.");
configMinerSpeedsPerSecond = Config.Bind("Miner", "EnableMinerOutputSpeedInfoPerSecond", false, "Sets the output speeds shown in Miners to items/s (default: items/min).");

additionalSpeedLabels = new AdditionalSpeedLabels(ModLogger, configEnableOutputSpeeds.Value, configEnableInputSpeeds.Value, Constants.AssemblerWindowSpeedTextPath);
UiMinerWindowPatch.additionalSpeedLabels = new AdditionalSpeedLabels(ModLogger, configShowMinerSpeed.Value, false, Constants.MinerWindowSpeedTextPath);
Patchers.UiAssemblerWindowPatch.additionalSpeedLabels = new Util.AdditionalSpeedLabels(ModLogger, configEnableOutputSpeeds.Value, configEnableInputSpeeds.Value, Constants.AssemblerWindowSpeedTextPath);
Patchers.UiMinerWindowPatch.additionalSpeedLabels = new Util.AdditionalSpeedLabels(ModLogger, configShowMinerSpeed.Value, false, Constants.MinerWindowSpeedTextPath);

harmony = new Harmony(ModInfo.ModID);
try
{
ModLogger.DebugLog("Patching AssemblerUI");
harmony.PatchAll(typeof(AssemblerSpeedUIMod));
harmony.PatchAll(typeof(Patchers.UiAssemblerWindowPatch));

ModLogger.DebugLog("Patching MinerUI");
harmony.PatchAll(typeof(UiMinerWindowPatch));
harmony.PatchAll(typeof(Patchers.UiMinerWindowPatch));
}
catch(Exception ex)
{
Expand All @@ -70,142 +66,10 @@ internal void OnDestroy()
{
harmony?.UnpatchSelf();

additionalSpeedLabels.Destroy();
}

public static void UpdateSpeedLabels(float baseSpeed, int[] productCounts, int[] requireCounts)
{
additionalSpeedLabels.UpdateSpeedLabels(baseSpeed, productCounts, requireCounts);
}

private static void SetupLabels(UIAssemblerWindow window)
{
int? productCount = window.factorySystem?.assemblerPool[window.assemblerId].products?.Length;
int? inputCount = window.factorySystem?.assemblerPool[window.assemblerId].requires?.Length;
additionalSpeedLabels.SetupLabels(productCount, inputCount);
Patchers.UiAssemblerWindowPatch.additionalSpeedLabels.Destroy();
Patchers.UiMinerWindowPatch.additionalSpeedLabels.Destroy();
}

#endregion

#region Patcher

[HarmonyPostfix, HarmonyPatch(typeof(UIAssemblerWindow), "OnAssemblerIdChange")]
public static void OnAssemblerIdChangePostfix(UIAssemblerWindow __instance)
{
SetupLabels(__instance);
}

[HarmonyPostfix, HarmonyPatch(typeof(UIAssemblerWindow), "OnRecipePickerReturn")]
public static void OnRecipePickerReturnPostfix(UIAssemblerWindow __instance)
{
SetupLabels(__instance);
}

[HarmonyTranspiler, HarmonyPatch(typeof(UIAssemblerWindow), "_OnUpdate")]
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator codeGen)
{
//declare local variable to keep our calculation base speed in
LocalBuilder baseSpeedValue = codeGen.DeclareLocal(typeof(float));
baseSpeedValue.SetLocalSymInfo("baseSpeedValue");

//define label we later use for a branching instruction. label location is set separately
Label noLiveData = codeGen.DefineLabel();

ModLogger.DebugLog("UiTextTranspiler started!");
CodeMatcher matcher = new CodeMatcher(instructions);
ModLogger.DebugLog($"UiTextTranspiler Matcher Codes Count: {matcher.Instructions().Count}, Matcher Pos: {matcher.Pos}!");

//find -->
//ldc.r4 60
//ldloc.s 6
//div
//stloc.s 7
//<-- endFind
matcher.MatchForward(
true,
new CodeMatch(i => i.opcode == OpCodes.Ldc_R4 && i.OperandIs(60f)),
new CodeMatch(i => i.opcode == OpCodes.Ldloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == 6),
new CodeMatch(OpCodes.Div),
new CodeMatch(i => i.opcode == OpCodes.Stloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == 7)
);
matcher.Advance(1); //move from last match to next element

//insert-->
//ldloc.s 18
//ldloca.s 0 //AssemblerComponent local var
//ldfld int32 AssemblerComponent::speed
//conv.r4
//ldc.r4 0.0001
//mul
//mul
//stloc baseSpeedValue
//<-- endInsert
//This is the speed calculation also done in 0.0001 * assemblerComponent.speed * num [num is power], but without num
//Whole calculation is done in float to avoid extra casting back from double to float at the end. Imprecision is too small to matter here
matcher.InsertAndAdvance(
new CodeInstruction(OpCodes.Ldloc_S, (byte)7), //Load base speed without power
new CodeInstruction(OpCodes.Ldloc_0),
new CodeInstruction(OpCodes.Ldfld, typeof(AssemblerComponent).GetField("speed")),
new CodeInstruction(OpCodes.Conv_R4),
new CodeInstruction(OpCodes.Ldc_R4, 0.0001f),
new CodeInstruction(OpCodes.Mul), //scale device speed to usable factor (0.0001 * speed)
new CodeInstruction(OpCodes.Mul), //multiply base (mk2) speed with factor
new CodeInstruction(OpCodes.Stloc_S, baseSpeedValue) //load value of config option
);

//find -->
//ldloc.s 7
//ldloc.s 8
//mul
//stloc.s 7
//<-- endFind
//This is the multiplication before the .ToString("0.0") + Translate when setting speedText.Text
matcher.MatchForward(
true,
new CodeMatch(i => i.opcode == OpCodes.Ldloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == 7),
new CodeMatch(i => i.opcode == OpCodes.Ldloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == 8),
new CodeMatch(OpCodes.Mul),
new CodeMatch(i => i.opcode == OpCodes.Stloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == 7)
);

ModLogger.DebugLog($"UiTextTranspiler Matcher Codes Count: {matcher.Instructions().Count}, Matcher Pos: {matcher.Pos}!");
matcher.Advance(1); //move from last match to next element

//Create code instruction with target label for Brfalse
CodeInstruction labelledInstruction = new CodeInstruction(OpCodes.Ldloc_S, baseSpeedValue);
labelledInstruction.labels.Add(noLiveData);

//insert-->
//ldsfld ConfigEntry<bool> AssemblerSpeedUIMod.configShowLiveSpeed
//callvirt int32 get_Value //or something like this. just run the getter of the Value Property
//brfalse noLiveData //if Value is false, branch to the labelledInstruction
//ldloc.s 18 //load calculated base speed incl. power
//stloc.s baseSpeedValue //use that as calculation base

//ldloc.s baseSpeedValue, label noLiveData
//ldloc.0 //AssemblerComponent local var
//ldfld int32[] AssemblerComponent::productCounts
//ldloc.0 //AssemblerComponent local var
//ldfld int32[] AssemblerComponent::requireCounts
//call update
//<-- endInsert
matcher.InsertAndAdvance(
new CodeInstruction(OpCodes.Ldsfld, typeof(AssemblerSpeedUIMod).GetField("configShowLiveSpeed")), //Load config option
new CodeInstruction(OpCodes.Callvirt, typeof(ConfigEntry<bool>).GetProperty("Value").GetGetMethod()), //load value of config option
new CodeInstruction(OpCodes.Brfalse, noLiveData), //branch the speed loading (labelledInstruction) if setting is false
new CodeInstruction(OpCodes.Ldloc_S, (byte)7), //Load the multiplied speed value the game uses (see last match)
new CodeInstruction(OpCodes.Stloc_S, baseSpeedValue),

labelledInstruction, //Load base speed on stack
new CodeInstruction(OpCodes.Ldloc_0),
new CodeInstruction(OpCodes.Ldfld, typeof(AssemblerComponent).GetField("productCounts")), //load product counts array on stack
new CodeInstruction(OpCodes.Ldloc_0),
new CodeInstruction(OpCodes.Ldfld, typeof(AssemblerComponent).GetField("requireCounts")), //load require counts array on stack
new CodeInstruction(OpCodes.Call, typeof(AssemblerSpeedUIMod).GetMethod("UpdateSpeedLabels")) //UpdateSpeedLabels(baseSpeed, productCounts[], requireCounts[])
);

return matcher.InstructionEnumeration();
}
#endregion
}
}
2 changes: 1 addition & 1 deletion DSP_AssemblerUI/AssemblerSpeedUI/ModInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public static class ModInfo
public const string ModID = "dsp.assemblerUI.speedMod";
public const string ModName = "Assembler UI Speed Info Mod";
public const string majorVersion = "2";
public const string minorVersion = "0";
public const string minorVersion = "1";
public const string hotfixVersion = "0";
public const string buildVersion = "0";
public const string VersionString = majorVersion + "." + minorVersion + "." + hotfixVersion + "." + buildVersion;
Expand Down
164 changes: 164 additions & 0 deletions DSP_AssemblerUI/AssemblerSpeedUI/Patchers/UiAssemblerWindowPatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System.Collections.Generic;
using System.Reflection.Emit;
using BepInEx.Configuration;
using DSP_AssemblerUI.AssemblerSpeedUI.Util;
using HarmonyLib;

namespace DSP_AssemblerUI.AssemblerSpeedUI.Patchers
{
public static class UiAssemblerWindowPatch
{
internal static AdditionalSpeedLabels additionalSpeedLabels;

public static void UpdateSpeedLabels(float baseSpeed, int[] productCounts, int[] requireCounts)
{
additionalSpeedLabels.UpdateSpeedLabels(baseSpeed, productCounts, requireCounts);
}

private static void SetupLabels(UIAssemblerWindow window)
{
int? productCount = window.factorySystem?.assemblerPool[window.assemblerId].products?.Length;
int? inputCount = window.factorySystem?.assemblerPool[window.assemblerId].requires?.Length;
additionalSpeedLabels.SetupLabels(productCount, inputCount);
}

#region Patcher

[HarmonyPostfix, HarmonyPatch(typeof(UIAssemblerWindow), "OnAssemblerIdChange")]
public static void OnAssemblerIdChangePostfix(UIAssemblerWindow __instance)
{
SetupLabels(__instance);
}

[HarmonyPostfix, HarmonyPatch(typeof(UIAssemblerWindow), "OnRecipePickerReturn")]
public static void OnRecipePickerReturnPostfix(UIAssemblerWindow __instance)
{
SetupLabels(__instance);
}

[HarmonyTranspiler, HarmonyPatch(typeof(UIAssemblerWindow), "_OnUpdate")]
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator codeGen)
{
//declare local variable to keep our calculation base speed in
LocalBuilder baseSpeedValue = codeGen.DeclareLocal(typeof(float));
baseSpeedValue.SetLocalSymInfo("baseSpeedValue");

//define label we later use for a branching instruction. label location is set separately
Label noLiveData = codeGen.DefineLabel();

int divisor60, speedvalue, outputstringFactor;

var searchResult = FieldIndexFinder.FindRelevantFieldIndices(instructions);
if (searchResult.HasValue)
{
(divisor60, speedvalue, outputstringFactor) = searchResult.Value;
AssemblerSpeedUIMod.ModLogger.DebugLog($"[UIAssemblerWindow] Found indices {divisor60}, {speedvalue}, {outputstringFactor}");
}
else
{
AssemblerSpeedUIMod.ModLogger.ErrorLog("[UIAssemblerWindow] Could not find the desired fields for patching the update logic.");
return instructions;
}

AssemblerSpeedUIMod.ModLogger.DebugLog("UiTextTranspiler started!");
CodeMatcher matcher = new CodeMatcher(instructions);
AssemblerSpeedUIMod.ModLogger.DebugLog($"UiTextTranspiler Matcher Codes Count: {matcher.Instructions().Count}, Matcher Pos: {matcher.Pos}!");



//find -->
//ldc.r4 60
//ldloc.s *divisor60*
//div
//stloc.s *speedvalue*
//<-- endFind
matcher.MatchForward(
true,
new CodeMatch(i => i.opcode == OpCodes.Ldc_R4 && i.OperandIs(60f)),
new CodeMatch(i => i.opcode == OpCodes.Ldloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == divisor60),
new CodeMatch(OpCodes.Div),
new CodeMatch(i => i.opcode == OpCodes.Stloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == speedvalue)
);
matcher.Advance(1); //move from last match to next element

//insert-->
//ldloc.s *speedvalue*
//ldloca.s 0 //AssemblerComponent local var
//ldfld int32 AssemblerComponent::speed
//conv.r4
//ldc.r4 0.0001
//mul
//mul
//stloc baseSpeedValue
//<-- endInsert
//This is the speed calculation also done in 0.0001 * assemblerComponent.speed * num [num is power], but without num
//Whole calculation is done in float to avoid extra casting back from double to float at the end. Imprecision is too small to matter here
matcher.InsertAndAdvance(
new CodeInstruction(OpCodes.Ldloc_S, (byte)speedvalue), //Load base speed without power
new CodeInstruction(OpCodes.Ldloc_0),
new CodeInstruction(OpCodes.Ldfld, typeof(AssemblerComponent).GetField("speed")),
new CodeInstruction(OpCodes.Conv_R4),
new CodeInstruction(OpCodes.Ldc_R4, 0.0001f),
new CodeInstruction(OpCodes.Mul), //scale device speed to usable factor (0.0001 * speed)
new CodeInstruction(OpCodes.Mul), //multiply base (mk2) speed with factor
new CodeInstruction(OpCodes.Stloc_S, baseSpeedValue) //load value of config option
);

//find -->
//ldloc.s *speedvalue*
//ldloc.s *outputstringFactor*
//mul
//stloc.s *speedvalue*
//<-- endFind
//This is the multiplication before the .ToString("0.0") + Translate when setting speedText.Text
matcher.MatchForward(
true,
new CodeMatch(i => i.opcode == OpCodes.Ldloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == speedvalue),
new CodeMatch(i => i.opcode == OpCodes.Ldloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == outputstringFactor),
new CodeMatch(OpCodes.Mul),
new CodeMatch(i => i.opcode == OpCodes.Stloc_S && i.operand is LocalBuilder lb && lb.LocalIndex == speedvalue)
);

AssemblerSpeedUIMod.ModLogger.DebugLog($"UiTextTranspiler Matcher Codes Count: {matcher.Instructions().Count}, Matcher Pos: {matcher.Pos}!");
matcher.Advance(1); //move from last match to next element

//Create code instruction with target label for Brfalse
CodeInstruction labelledInstruction = new CodeInstruction(OpCodes.Ldloc_S, baseSpeedValue);
labelledInstruction.labels.Add(noLiveData);

//insert-->
//ldsfld ConfigEntry<bool> AssemblerSpeedUIMod.configShowLiveSpeed
//callvirt int32 get_Value //or something like this. just run the getter of the Value Property
//brfalse noLiveData //if Value is false, branch to the labelledInstruction
//ldloc.s *speedvalue* //load calculated base speed incl. power
//stloc.s baseSpeedValue //use that as calculation base

//ldloc.s baseSpeedValue, label noLiveData
//ldloc.0 //AssemblerComponent local var
//ldfld int32[] AssemblerComponent::productCounts
//ldloc.0 //AssemblerComponent local var
//ldfld int32[] AssemblerComponent::requireCounts
//call update
//<-- endInsert
matcher.InsertAndAdvance(
new CodeInstruction(OpCodes.Ldsfld, typeof(AssemblerSpeedUIMod).GetField("configShowLiveSpeed")), //Load config option
new CodeInstruction(OpCodes.Callvirt, typeof(ConfigEntry<bool>).GetProperty("Value").GetGetMethod()), //load value of config option
new CodeInstruction(OpCodes.Brfalse, noLiveData), //branch the speed loading (labelledInstruction) if setting is false
new CodeInstruction(OpCodes.Ldloc_S, (byte)speedvalue), //Load the multiplied speed value the game uses (see last match)
new CodeInstruction(OpCodes.Stloc_S, baseSpeedValue),

labelledInstruction, //Load base speed on stack
new CodeInstruction(OpCodes.Ldloc_0),
new CodeInstruction(OpCodes.Ldfld, typeof(AssemblerComponent).GetField("productCounts")), //load product counts array on stack
new CodeInstruction(OpCodes.Ldloc_0),
new CodeInstruction(OpCodes.Ldfld, typeof(AssemblerComponent).GetField("requireCounts")), //load require counts array on stack
new CodeInstruction(OpCodes.Call, typeof(UiAssemblerWindowPatch).GetMethod("UpdateSpeedLabels")) //UpdateSpeedLabels(baseSpeed, productCounts[], requireCounts[])
);

AssemblerSpeedUIMod.ModLogger.DebugLog($"UiTextTranspiler Matcher Codes Count: {matcher.Instructions().Count}, Matcher Pos: {matcher.Pos}!");

return matcher.InstructionEnumeration();
}
#endregion
}
}
Loading

0 comments on commit 5459654

Please sign in to comment.