├── WrathPatches
├── IgnoreHarmonyVersion.txt
├── IsExternalInit.cs
├── WrathPatchAttribute.cs
├── Patches
│ ├── SaveSlotGroupVM.HandleNewSave.cs
│ ├── MakeBlueprintBeingReadThreadStatic.cs
│ ├── ShaderWrapper.cs
│ ├── HambeardLeakFixes.cs
│ ├── AbilityExecutionContextSpellDescriptor.cs
│ ├── LogsHotkey.cs
│ ├── Element_AssetGuidShort_Fix.cs
│ ├── ProgressionRoot_HumanRace.cs
│ ├── Experimental
│ │ └── BlueprintsCacheInitAddBlueprint.cs
│ ├── ActingInSurpriseRoundFix.cs
│ ├── GameStatistic.Tick.PlayerNullFix.cs
│ ├── MissingSettingsUISoundsFix.cs
│ ├── CurrentSelectedCharacterNREFix.cs
│ ├── EventSubscriptionLeakFixes.cs
│ ├── BloodyFaceControllerUnitEntityView.cs
│ ├── LoadAssembliesFix.cs
│ ├── OwlcatButtonClickDelayFix.cs
│ ├── KeyboardAccess.Bind.cs
│ ├── SetMagusFeatureActiveFix.cs
│ ├── ModReloadClearBlueprintReferenceCache.cs
│ ├── ActuallyFixSharedStrings.cs
│ ├── UnitMarkManager.LateUpdate.cs
│ ├── AnimationClipWrapperStateMachineBehaviour.cs
│ ├── WeaponSetGripVMFix.cs
│ ├── KingmakerInputModule.CheckEventSystem.cs
│ ├── BetterModLogger.cs
│ ├── LogExceptions.cs
│ ├── OwlcatModification.LoadAssemblies_Patch.cs
│ ├── AddFactsIgnoreDeadReferences.cs
│ ├── SpellSlotComparisonFixes.cs
│ ├── OwnerBlueprintWarning.cs
│ ├── OwlModDirectReferenceBundleDependenciesFix.cs
│ ├── OMMExtensions.cs
│ ├── TheEnhance.cs
│ ├── EntityFactComponent.Activate.cs
│ └── OnDemandBlueprints.cs
├── CheckHarmonyVersion.cs
├── TranspilerUtil.cs
├── HarmonyPatchCategory.cs
├── Main.cs
├── LinqExtensions.cs
└── WrathPatches.csproj
├── ShaderWrapper
├── scriptableshaderwrapper
├── Main.cs
├── ShaderWrapper.csproj
└── ScriptableShaderWrapper.cs
├── common.props
├── Repository.json
├── LICENSE.txt
├── WrathPatches.sln
├── README.md
├── .gitattributes
└── .gitignore
/WrathPatches/IgnoreHarmonyVersion.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ShaderWrapper/scriptableshaderwrapper:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoftenator2022/WrathPatches/HEAD/ShaderWrapper/scriptableshaderwrapper
--------------------------------------------------------------------------------
/WrathPatches/IsExternalInit.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 |
4 | namespace System.Runtime.CompilerServices
5 | {
6 | [DebuggerNonUserCode]
7 | internal static class IsExternalInit { }
8 | }
--------------------------------------------------------------------------------
/common.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1.19.1
4 |
5 |
6 | debug
7 |
8 |
--------------------------------------------------------------------------------
/Repository.json:
--------------------------------------------------------------------------------
1 | {
2 | "Releases": [
3 | {
4 | "Id": "WrathPatches",
5 | "Version": "1.19.1",
6 | "DownloadUrl": "https://github.com/microsoftenator2022/WrathPatches/releases/download/v1.19.1/WrathPatches-1.19.1.zip"
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/WrathPatches/WrathPatchAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace WrathPatches
8 | {
9 | [AttributeUsage(AttributeTargets.Class)]
10 | internal sealed class WrathPatchAttribute(string name) : Attribute
11 | {
12 | public readonly string Name = name;
13 | public string Description = "";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/SaveSlotGroupVM.HandleNewSave.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | using HarmonyLib;
8 |
9 | using Kingmaker.UI.MVVM._VM.SaveLoad;
10 |
11 | namespace WrathPatches
12 | {
13 | [WrathPatch("Fix save slot VM double bind")]
14 | [HarmonyPatch(typeof(SaveSlotGroupVM), nameof(SaveSlotGroupVM.HandleNewSave))]
15 | internal static class SaveSlotGroupVM_HandleNewSave
16 | {
17 | static void Prefix(SaveSlotGroupVM __instance, SaveSlotVM slot)
18 | {
19 | if (slot.Owner == null) return;
20 |
21 | slot.Owner.RemoveDisposable(slot);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ShaderWrapper/Main.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Kingmaker.Modding;
7 | using HarmonyLib;
8 | using System.Reflection;
9 |
10 | namespace ShaderWrapper
11 | {
12 | internal class Main
13 | {
14 | static Harmony harmony;
15 |
16 | //[OwlcatModificationEnterPoint]
17 | //public static void EntryPoint(OwlcatModification owlcatModification)
18 | //{
19 | // if (harmony == null)
20 | // {
21 | // harmony = new Harmony(owlcatModification.Manifest.UniqueName);
22 | // harmony.PatchAll(Assembly.GetExecutingAssembly());
23 | // }
24 | //}
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/MakeBlueprintBeingReadThreadStatic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using HarmonyLib;
4 |
5 | using Kingmaker.Blueprints.JsonSystem;
6 |
7 | namespace WrathPatches.Patches;
8 |
9 | [WrathPatch("Make BlueprintBeingRead ThreadStatic")]
10 | [HarmonyPatch(typeof(Json), nameof(Json.BlueprintBeingRead))]
11 | internal static class MakeBlueprintBeingReadThreadStatic
12 | {
13 | [ThreadStatic]
14 | static BlueprintJsonWrapper? blueprintBeingRead;
15 |
16 | [HarmonyPatch(MethodType.Setter)]
17 | [HarmonyPostfix]
18 | static void SetterPatch(BlueprintJsonWrapper value) =>
19 | blueprintBeingRead = value;
20 |
21 | [HarmonyPatch(MethodType.Getter)]
22 | [HarmonyPostfix]
23 | static void GetterPatch(ref BlueprintJsonWrapper? __result) =>
24 | __result = blueprintBeingRead;
25 | }
26 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/ShaderWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | using HarmonyLib;
10 |
11 | using Kingmaker.Modding;
12 |
13 | namespace WrathPatches.Patches;
14 |
15 | [WrathPatch("@Kurufinve ShaderWrapper fix for owlmod material shaders")]
16 | [HarmonyPatch(typeof(OwlcatModificationsManager), nameof(OwlcatModificationsManager.Start))]
17 | static class ShaderWrapper
18 | {
19 | static bool Applied = false;
20 | static void Prefix()
21 | {
22 | if (Applied) return;
23 |
24 | var shaderWrapperAssembly =
25 | Assembly.LoadFrom(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "ShaderWrapper.dll"));
26 |
27 | Main.HarmonyInstance.PatchAll(shaderWrapperAssembly);
28 |
29 | Applied = true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/HambeardLeakFixes.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | using HarmonyLib;
4 |
5 | using Kingmaker.UI._ConsoleUI.TurnBasedMode;
6 | using Kingmaker.UI.MVVM._PCView.ActionBar;
7 | using Kingmaker.UI.MVVM._VM.ActionBar;
8 |
9 | namespace WrathPatches.Patches;
10 |
11 | [WrathPatch("Event subscription leak fixes")]
12 | [HarmonyPatch]
13 | static partial class EventSubscriptionLeakFixes
14 | {
15 | [HarmonyPatch(typeof(ActionBarSpellGroupPCView), nameof(ActionBarSpellGroupPCView.AddEmptySlots))]
16 | [HarmonyPostfix]
17 | static void PatchAddEmptySlots(ActionBarSpellGroupPCView __instance, List slotVms) =>
18 | slotVms.ForEach(__instance.AddDisposable);
19 |
20 | [HarmonyPatch(typeof(InitiativeTrackerUnitVM), nameof(InitiativeTrackerUnitVM.UpdateBuffs))]
21 | [HarmonyPrefix]
22 | static void PatchUpdateBuffs(InitiativeTrackerUnitVM __instance) =>
23 | __instance.UnitBuffs.ForEach(x => x.Dispose());
24 | }
--------------------------------------------------------------------------------
/WrathPatches/Patches/AbilityExecutionContextSpellDescriptor.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 |
3 | using Kingmaker.Blueprints.Classes.Spells;
4 | using Kingmaker.EntitySystem.Entities;
5 | using Kingmaker.RuleSystem;
6 | using Kingmaker.UnitLogic.Abilities;
7 | using Kingmaker.UnitLogic.Mechanics;
8 | using Kingmaker.Utility;
9 |
10 | namespace WrathPatches.Patches
11 | {
12 | [WrathPatch("Add AbilityData spell descriptor to AbilityExecutionContext")]
13 | [HarmonyPatch(typeof(AbilityExecutionContext))]
14 | static class AbilityExecutionContextSpellDescriptor
15 | {
16 | [HarmonyPatch(MethodType.Constructor,
17 | [
18 | typeof(AbilityData),
19 | typeof(AbilityParams),
20 | typeof(TargetWrapper),
21 | typeof(RulebookEventContext),
22 | typeof(UnitEntityData)
23 | ])]
24 | [HarmonyPostfix]
25 | static void Postfix(AbilityExecutionContext __instance) =>
26 | __instance.SpellDescriptor |= __instance.Ability.SpellDescriptor;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/LogsHotkey.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using HarmonyLib;
9 |
10 | using Kingmaker;
11 | using Kingmaker.Cheats;
12 |
13 | using UnityEngine;
14 |
15 | namespace WrathPatches.Patches;
16 |
17 | [WrathPatch("OpenGameLogFull opens Player.log too")]
18 | [HarmonyPatch(typeof(CheatsDebug), nameof(CheatsDebug.OpenGameLogFull), [typeof(string)])]
19 | internal static class LogsHotkey
20 | {
21 | public static void EnableHotKey()
22 | {
23 |
24 | Main.Logger.Log("Enabling GameLogFull keybind");
25 |
26 | Game.Instance.Keyboard.Bind("OpenGameLogFull", CheatsDebug.OpenGameLogFull);
27 | }
28 |
29 | [HarmonyPostfix]
30 | [HarmonyPatch]
31 | static void OpenGameLogFull_Postfix()
32 | {
33 | try
34 | {
35 | Application.OpenURL(Path.Combine(Application.persistentDataPath, "Player.log"));
36 | }
37 | catch
38 | {
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Aaron Maslen (microsoftenator)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/Element_AssetGuidShort_Fix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | using HarmonyLib;
8 |
9 | using Kingmaker;
10 | using Kingmaker.ElementsSystem;
11 |
12 | namespace WrathPatches
13 | {
14 | [WrathPatch("Element AssetGuid null fix")]
15 | [HarmonyPatch(typeof(Element))]
16 | static class Element_AssetGuidShort_Fix
17 | {
18 | static bool handleNullName(Element __instance, ref string __result)
19 | {
20 | __result = "anonymous";
21 |
22 | return !string.IsNullOrEmpty(__instance.name);
23 | }
24 |
25 | [HarmonyPatch(nameof(Element.AssetGuidShort), MethodType.Getter)]
26 | [HarmonyPrefix]
27 | static bool AssetGuidShort_Prefix(Element __instance, ref string __result) =>
28 | handleNullName(__instance, ref __result);
29 |
30 | [HarmonyPatch(nameof(Element.AssetGuid), MethodType.Getter)]
31 | [HarmonyPrefix]
32 | static bool AssetGuid_Prefix(Element __instance, ref string __result) =>
33 | handleNullName(__instance, ref __result);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/ProgressionRoot_HumanRace.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Emit;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using HarmonyLib;
9 |
10 | using Kingmaker.Blueprints;
11 | using Kingmaker.Blueprints.Classes;
12 | using Kingmaker.Blueprints.JsonSystem;
13 | using Kingmaker.Blueprints.Root;
14 |
15 | namespace WrathPatches.Patches;
16 |
17 | [WrathPatch("Make ProgressionRoot.HumanRace not break for new races")]
18 | [HarmonyPatch(typeof(ProgressionRoot), nameof(ProgressionRoot.HumanRace), MethodType.Getter)]
19 | internal static class ProgressionRoot_HumanRace
20 | {
21 | static IEnumerable Transpiler(IEnumerable _)
22 | {
23 | yield return new CodeInstruction(OpCodes.Ldnull);
24 | yield return new CodeInstruction(OpCodes.Ret);
25 | }
26 |
27 | static BlueprintRace Postfix(BlueprintRace _, ProgressionRoot __instance)
28 | {
29 | __instance.m_HumanRaceCached ??= ResourcesLibrary.TryGetBlueprint("0a5d473ead98b0646b94495af250fdc4");
30 |
31 | return __instance.m_HumanRaceCached;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/WrathPatches/CheckHarmonyVersion.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 |
7 | namespace WrathPatches
8 | {
9 | static partial class Main
10 | {
11 | static Assembly GetHarmonyAss() => AppDomain.CurrentDomain.GetAssemblies()
12 | .FirstOrDefault(ass => ass.GetName().Name == "0Harmony");
13 |
14 | static Version HarmonyVersion => GetHarmonyAss().GetName().Version;
15 |
16 | static Assembly GetUmmAss() => AppDomain.CurrentDomain.GetAssemblies()
17 | .FirstOrDefault(ass => ass.GetName().Name == "UnityModManager");
18 |
19 | static string UmmHarmonyPath => Path.Combine(Path.GetDirectoryName(GetUmmAss().Location), "0Harmony.dll");
20 |
21 | static Version UmmHarmonyVersion => Version.Parse(FileVersionInfo.GetVersionInfo(UmmHarmonyPath).FileVersion);
22 |
23 | static void ReplaceHarmony(string harmonyPath, string ummHarmonyPath)
24 | {
25 | Logger.Warning($"Copying {ummHarmonyPath} -> {harmonyPath}. Please restart the game.");
26 | File.Copy(harmonyPath, Path.Combine(Path.GetDirectoryName(harmonyPath), $"0Harmony.{HarmonyVersion}.old"));
27 | File.Copy(ummHarmonyPath, harmonyPath, true);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/Experimental/BlueprintsCacheInitAddBlueprint.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | using HarmonyLib;
8 |
9 | using Kingmaker.Blueprints;
10 | using Kingmaker.Blueprints.JsonSystem;
11 |
12 | namespace WrathPatches.Experimental
13 | {
14 | #if DEBUG
15 | //[HarmonyPatchCategory("Experimental")]
16 | //[WrathPatch("Allow add existing in BlueprintsCache.Init")]
17 | //[HarmonyPatch(typeof(BlueprintsCache))]
18 | static class BlueprintsCacheInitAddBlueprint
19 | {
20 | //[HarmonyPatch(nameof(BlueprintsCache.Init))]
21 | //[HarmonyTranspiler]
22 | static IEnumerable Init_Transpiler(IEnumerable instructions)
23 | {
24 | Main.Logger.Log($"{nameof(BlueprintsCacheInitAddBlueprint)}.{nameof(Init_Transpiler)}");
25 |
26 | var (_, instruction) = instructions.Indexed()
27 | .First(i => i.item.Calls(
28 | AccessTools.Method(typeof(Dictionary),
29 | nameof(Dictionary.Add))));
30 |
31 | instruction.operand = AccessTools.PropertySetter(typeof(Dictionary), "Item");
32 |
33 | return instructions;
34 | }
35 | }
36 | #endif
37 | }
38 |
--------------------------------------------------------------------------------
/ShaderWrapper/ShaderWrapper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net472
5 | latest
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | PreserveNewest
19 |
20 |
21 |
22 |
23 |
24 |
25 | all
26 | runtime; build; native; contentfiles; analyzers; buildtransitive
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/ActingInSurpriseRoundFix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Reflection.Emit;
6 |
7 | using HarmonyLib;
8 |
9 | using TurnBased.Controllers;
10 |
11 | namespace WrathPatches.Patches;
12 |
13 | [WrathPatch("Fix surprise round turns")]
14 | [HarmonyPatch]
15 | internal class ActingInSurpriseRoundFix
16 | {
17 | static IEnumerable TargetMethods()
18 | {
19 | yield return AccessTools.Method(typeof(CombatController), nameof(CombatController.HandleCombatStart));
20 | foreach (var m in typeof(CombatController).GetNestedTypes(AccessTools.all)
21 | .SelectMany(AccessTools.GetDeclaredMethods)
22 | .Where(m => m.Name.Contains(nameof(CombatController.HandleCombatStart))))
23 | yield return m;
24 | }
25 |
26 | static IEnumerable Transpiler(IEnumerable instructions, MethodBase __originalMethod)
27 | {
28 | var Timespan_get_Seconds = AccessTools.PropertyGetter(typeof(TimeSpan), nameof(TimeSpan.Seconds));
29 |
30 | foreach (var i in instructions)
31 | {
32 | if (i.opcode == OpCodes.Call && (MethodInfo)i.operand == Timespan_get_Seconds)
33 | {
34 | i.operand = AccessTools.PropertyGetter(typeof(TimeSpan), nameof(TimeSpan.TotalSeconds));
35 | #if DEBUG
36 | Main.Logger.Log($"[{nameof(ActingInSurpriseRoundFix)}] patched method {__originalMethod.FullDescription()}");
37 | #endif
38 | }
39 |
40 |
41 | yield return i;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/GameStatistic.Tick.PlayerNullFix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Emit;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using HarmonyLib;
9 |
10 | using Kingmaker;
11 |
12 | namespace WrathPatches
13 | {
14 | [WrathPatch("GameStatistic.Tick null Player fix")]
15 | [HarmonyPatch(typeof(GameStatistic), nameof(GameStatistic.Tick))]
16 | internal static class GameStatistic_Tick_PlayerNullFix
17 | {
18 | static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
19 | {
20 | var insertAfter = instructions.Indexed().FirstOrDefault(i => i.item.opcode == OpCodes.Nop);
21 |
22 | if (insertAfter == default)
23 | {
24 | return instructions;
25 | }
26 |
27 | var insertAtIndex = insertAfter.index + 1;
28 |
29 | var iList = instructions.ToList();
30 |
31 | var targetNop = new CodeInstruction(OpCodes.Nop);
32 | var jumpLabel = generator.DefineLabel();
33 | targetNop.labels.Add(jumpLabel);
34 |
35 | iList.InsertRange(insertAtIndex, new[]
36 | {
37 | new CodeInstruction(OpCodes.Ldarg_1),
38 | new CodeInstruction(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Game), nameof(Game.Player))),
39 | new CodeInstruction(OpCodes.Brtrue_S, jumpLabel),
40 | new CodeInstruction(OpCodes.Ret),
41 | targetNop
42 | });
43 |
44 | return iList;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/MissingSettingsUISoundsFix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using HarmonyLib;
9 |
10 | using Kingmaker.UI;
11 | using Kingmaker.UI.MVVM._PCView.Settings;
12 |
13 | namespace WrathPatches.Patches
14 | {
15 | [WrathPatch("Don't try to play missing settings UI sound types")]
16 | [HarmonyPatch]
17 | class MissingSettingsUISoundsFix
18 | {
19 | [HarmonyTargetMethods]
20 | static IEnumerable TargetMethods() =>
21 | [
22 | AccessTools.Method(typeof(SettingsPCView), nameof(SettingsPCView.Show)),
23 | AccessTools.Method(typeof(SettingsPCView), nameof(SettingsPCView.Hide))
24 | ];
25 |
26 | [HarmonyTranspiler]
27 | static IEnumerable Transpiler(IEnumerable instructions)
28 | {
29 | var iList = instructions.ToArray();
30 |
31 | var UISoundController_Play =
32 | AccessTools.Method(typeof(UISoundController), nameof(UISoundController.Play), [typeof(UISoundType)]);
33 |
34 | for (var i = 0; i < iList.Length; i++)
35 | {
36 | if (iList[i].Calls(UISoundController_Play))
37 | {
38 | #if DEBUG
39 | var value = (UISoundType)(sbyte)(iList[i - 1].operand);
40 | Main.Logger.Log($"{value} -> {UISoundType.None}");
41 | #endif
42 | iList[i - 1].operand = (int)UISoundType.None;
43 | }
44 | }
45 |
46 | return iList;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/CurrentSelectedCharacterNREFix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Emit;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using HarmonyLib;
9 |
10 | using Kingmaker.Armies.TacticalCombat.Data;
11 | using Kingmaker.Controllers;
12 | using Kingmaker.EntitySystem.Entities;
13 |
14 | namespace WrathPatches.Patches;
15 |
16 | [WrathPatch("Fix NRE in SelectionCharacterController.CurrentSelectedCharacter")]
17 | [HarmonyPatch(
18 | typeof(SelectionCharacterController),
19 | nameof(SelectionCharacterController.CurrentSelectedCharacter),
20 | MethodType.Getter)]
21 | static class CurrentSelectedCharacterNREFix
22 | {
23 | static IEnumerable Transpiler(IEnumerable instructions, ILGenerator ilGen)
24 | {
25 | var matcher = new CodeMatcher(instructions, ilGen)
26 | .MatchStartForward(
27 | CodeMatch.Calls(
28 | AccessTools.PropertyGetter(
29 | typeof(TurnState), nameof(TurnState.Unit))),
30 | CodeMatch.StoresLocal(),
31 | CodeMatch.LoadsLocal(),
32 | CodeMatch.Calls(
33 | AccessTools.PropertyGetter(
34 | typeof(UnitEntityData), nameof(UnitEntityData.IsDirectlyControllable))))
35 | .Advance(1)
36 | .InsertAndAdvance([new(OpCodes.Dup)]);
37 |
38 | return matcher
39 | .InsertBranchAndAdvance(OpCodes.Brtrue_S, matcher.Pos)
40 | .InsertAndAdvance([new(OpCodes.Pop), new(OpCodes.Ldnull), new(OpCodes.Ret)])
41 | .InstructionEnumeration();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/EventSubscriptionLeakFixes.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using Kingmaker.Items;
3 | using Kingmaker.PubSubSystem;
4 | using Kingmaker.UI._ConsoleUI.TurnBasedMode;
5 | using Kingmaker.UI.MVVM._PCView.ActionBar;
6 | using Kingmaker.UI.MVVM._VM.ActionBar;
7 | using Kingmaker.UnitLogic.Abilities;
8 | using System;
9 | using System.Collections.Generic;
10 | using System.Linq;
11 | using System.Reflection.Emit;
12 | using System.Text;
13 | using System.Threading.Tasks;
14 | using WrathPatches.TranspilerUtil;
15 |
16 | namespace WrathPatches.Patches
17 | {
18 | [HarmonyPatch]
19 | partial class EventSubscriptionLeakFixes
20 | {
21 | [HarmonyPatch(typeof(ActionBarVM), MethodType.Constructor)]
22 | [HarmonyTranspiler]
23 | static IEnumerable DoubleSubscribe_Transpiler(IEnumerable instructions)
24 | {
25 | var toMatch = new Func[]
26 | {
27 | ci => ci.opcode == OpCodes.Ldarg_0,
28 | ci => ci.Calls(AccessTools.Method(typeof(EventBus), nameof(EventBus.Subscribe), [typeof(object)])),
29 | ci => ci.opcode == OpCodes.Pop
30 | };
31 |
32 | var match = instructions.FindInstructionsIndexed(toMatch).ToArray();
33 |
34 | if (match.Length != toMatch.Length)
35 | throw new Exception("Could not find target instructions");
36 |
37 | var iList = instructions.ToList();
38 |
39 | foreach (var (index, _) in match)
40 | {
41 | iList[index].opcode = OpCodes.Nop;
42 | iList[index].operand = null;
43 | }
44 |
45 | return iList;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/BloodyFaceControllerUnitEntityView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Emit;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using HarmonyLib;
9 |
10 | using Kingmaker.View;
11 | using Kingmaker.Visual.MaterialEffects;
12 |
13 | using Owlcat.Runtime.Core;
14 |
15 | namespace WrathPatches.Patches
16 | {
17 | [WrathPatch("No BloodyFaceController for non-UnitEntityView")]
18 | [HarmonyPatch(typeof(StandardMaterialController), nameof(StandardMaterialController.Awake))]
19 | internal class BloodyFaceControllerUnitEntityView
20 | {
21 | static bool HasUnitEntityView(StandardMaterialController smc) => smc.gameObject.GetComponent() is not null;
22 |
23 | [HarmonyTranspiler]
24 | static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
25 | {
26 | var Application_isPlaying =
27 | AccessTools.PropertyGetter(typeof(UnityEngine.Application), nameof(UnityEngine.Application.isPlaying));
28 |
29 | var index = instructions.FindIndex(ci => ci.Calls(Application_isPlaying));
30 |
31 | if (index < 0)
32 | throw new Exception("Could not find target instruction");
33 |
34 | var iList = instructions.ToList();
35 |
36 | var ifFalse = iList[index + 1];
37 |
38 | iList.InsertRange(index - 2,
39 | [
40 | new CodeInstruction(OpCodes.Ldarg_0),
41 | CodeInstruction.Call((StandardMaterialController x) => HasUnitEntityView(x)),
42 | ifFalse
43 | ]);
44 |
45 | return iList;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/LoadAssembliesFix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection.Emit;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | using HarmonyLib;
10 |
11 | using Kingmaker.Modding;
12 |
13 | namespace WrathPatches.Patches
14 | {
15 | [WrathPatch("Only load .dll files from owlmod Assemblies directories")]
16 | [HarmonyPatch(typeof(OwlcatModification), nameof(OwlcatModification.LoadAssemblies))]
17 | static class LoadAssembliesFix
18 | {
19 | static IEnumerable GetAssemblies(IEnumerable files)
20 | {
21 | //return files.Where(f => Path.GetExtension(f) == ".dll");
22 |
23 | foreach (var f in files)
24 | {
25 | if (Path.GetExtension(f) == ".dll")
26 | {
27 | #if DEBUG
28 | Main.Logger.Log($"Loading assembly: {f}");
29 | #endif
30 |
31 | yield return f;
32 | }
33 | #if DEBUG
34 | else
35 | {
36 | Main.Logger.Log($"Skipping non-dll file: {f}");
37 | }
38 | #endif
39 | }
40 | }
41 | static IEnumerable Transpiler(IEnumerable instructions)
42 | {
43 | foreach (var i in instructions)
44 | {
45 | yield return i;
46 |
47 | if (i.Calls(AccessTools.Method(typeof(OwlcatModification), nameof(OwlcatModification.GetFilesFromDirectory))))
48 | {
49 | yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(LoadAssembliesFix), nameof(GetAssemblies)));
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/OwlcatButtonClickDelayFix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Reflection.Emit;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | using HarmonyLib;
10 |
11 | using Owlcat.Runtime.UI.Controls.Button;
12 |
13 | using WrathPatches.TranspilerUtil;
14 |
15 | namespace WrathPatches.Patches;
16 |
17 | [WrathPatch("Reduce button click delay from 300ms to 1 frame")]
18 | [HarmonyPatch]
19 | internal static class OwlcatButtonClickDelayFix
20 | {
21 | internal static IEnumerable TargetMethods() => new Type[]
22 | {
23 | typeof(OwlcatButton),
24 | typeof(OwlcatMultiButton)
25 | }.SelectMany(t => t
26 | .GetNestedTypes(AccessTools.all)
27 | .Where(t => t.Name.Contains("LeftClickTime")))
28 | .Select(t => AccessTools.Method(t, "MoveNext"));
29 |
30 | [HarmonyTranspiler]
31 | internal static IEnumerable Transpiler(IEnumerable instructions, MethodBase originalMethod)
32 | {
33 | var match = instructions.FindInstructionsIndexed(
34 | [
35 | ci => ci.opcode == OpCodes.Ldc_R4 && (float)ci.operand == 0.3f,
36 | ci => ci.opcode == OpCodes.Blt_S
37 | ]).ToArray();
38 |
39 | if (match.Length != 2)
40 | {
41 | Main.Logger.Warning($"Could not find patch target in {originalMethod}");
42 |
43 | #if DEBUG
44 | Main.Logger.Log(string.Join("\n", instructions));
45 | #endif
46 |
47 | return instructions;
48 | }
49 |
50 | var iList = instructions.ToList();
51 |
52 | iList[match[0].index].operand = 0.0f;
53 | iList[match[1].index].opcode = OpCodes.Bgt_S;
54 |
55 | return iList;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/KeyboardAccess.Bind.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Reflection.Emit;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | using HarmonyLib;
10 |
11 | using Kingmaker;
12 | using Kingmaker.UI;
13 |
14 | using WrathPatches.TranspilerUtil;
15 |
16 | namespace WrathPatches
17 | {
18 | [WrathPatch("Silence 'no binding' warnings")]
19 | [HarmonyPatch]
20 | internal static class SilenceNoBindingLog
21 | {
22 | [HarmonyTargetMethods]
23 | static IEnumerable Methods() =>
24 | [
25 | AccessTools.Method(typeof(KeyboardAccess), nameof(KeyboardAccess.Bind)),
26 | AccessTools.Method(typeof(KeyboardAccess), nameof(KeyboardAccess.DoUnbind))
27 | ];
28 |
29 | [HarmonyTranspiler]
30 | static IEnumerable Patch_Transpiler(IEnumerable instructions)
31 | {
32 | var PFLog_get_Default = AccessTools.PropertyGetter(typeof(PFLog), nameof(PFLog.Default));
33 |
34 | var match = instructions.FindInstructionsIndexed(
35 | [
36 | ci => ci.opcode == OpCodes.Brfalse_S,
37 | ci => ci.Calls(PFLog_get_Default),
38 | ci => ci.opcode == OpCodes.Ldstr && ci.operand as string == "Bind: no binding named {0}"
39 | ]);
40 |
41 | if (match.Count() != 3)
42 | throw new KeyNotFoundException("Unable to find patch location");
43 |
44 | var (index, i) = match.First();
45 |
46 | var iList = instructions.ToList();
47 |
48 | iList[index] = new CodeInstruction(OpCodes.Br, i.operand);
49 | iList.Insert(index, new CodeInstruction(OpCodes.Pop));
50 |
51 | return iList;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/SetMagusFeatureActiveFix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Emit;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using HarmonyLib;
9 |
10 | using Kingmaker.Items;
11 | using Kingmaker.UnitLogic.FactLogic;
12 | using WrathPatches.TranspilerUtil;
13 |
14 | namespace WrathPatches.Patches
15 | {
16 | [WrathPatch("Remove unnecessary code in SetMagusFeatureActive.OnTurnOff")]
17 | [HarmonyPatch(typeof(SetMagusFeatureActive), nameof(SetMagusFeatureActive.OnTurnOff))]
18 | internal static class SetMagusFeatureActiveFix
19 | {
20 | [HarmonyTranspiler]
21 | static IEnumerable Transpiler(IEnumerable instructions)
22 | {
23 | var HandsEquipmentSet_get_GripType = AccessTools.PropertyGetter(typeof(HandsEquipmentSet), nameof(HandsEquipmentSet.GripType));
24 |
25 | var toMatch = new Func[]
26 | {
27 | ci => ci.opcode == OpCodes.Ldarg_0,
28 | ci => ci.opcode == OpCodes.Call,
29 | ci => ci.opcode == OpCodes.Callvirt,
30 | ci => ci.opcode == OpCodes.Callvirt,
31 | ci => ci.Calls(HandsEquipmentSet_get_GripType),
32 | ci => ci.opcode == OpCodes.Pop
33 | };
34 |
35 | var match = instructions.FindInstructionsIndexed(toMatch).ToArray();
36 |
37 | if (match.Length != toMatch.Length)
38 | throw new Exception("Could not find target instructions");
39 |
40 | var iList = instructions.ToList();
41 |
42 | foreach (var (index, _) in match)
43 | {
44 | iList[index].opcode = OpCodes.Nop;
45 | iList[index].operand = null;
46 | }
47 |
48 | return iList;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/ModReloadClearBlueprintReferenceCache.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using HarmonyLib;
4 |
5 | using Kingmaker.Blueprints;
6 | using Kingmaker.Modding;
7 |
8 | namespace WrathPatches.Patches;
9 |
10 | [WrathPatch("Clear BlueprintReference caches when reloading OMM mods")]
11 | [HarmonyPatch]
12 | public static class ModReloadClearBlueprintReferenceCache
13 | {
14 | static readonly HashSet references = [];
15 |
16 | [HarmonyPatch(typeof(OwlcatModification), nameof(OwlcatModification.Reload))]
17 | [HarmonyPrefix]
18 | public static void ClearCaches(OwlcatModification __instance)
19 | {
20 | var rs = references
21 | .Where(r => __instance.Blueprints.Contains(r.guid))
22 | .ToArray();
23 |
24 | #if DEBUG
25 | Main.Logger.Log($"Clearing {rs.Length} blueprint reference caches for {__instance.Manifest.UniqueName}");
26 | #endif
27 |
28 | foreach (var r in rs)
29 | {
30 | r.Cached = null;
31 | _ = references.Remove(r);
32 | }
33 | }
34 |
35 | [HarmonyPatch(
36 | typeof(BlueprintReferenceBase),
37 | nameof(BlueprintReferenceBase.Cached),
38 | MethodType.Setter), HarmonyPrefix]
39 | static void SetCached_Prefix(BlueprintReferenceBase __instance, SimpleBlueprint value)
40 | {
41 | if (value is null)
42 | return;
43 |
44 | var assetId = __instance.Guid.ToString();
45 |
46 | foreach (var mod in OwlcatModificationsManager.Instance.AppliedModifications)
47 | if (mod.Blueprints.Contains(assetId))
48 | {
49 | #if DEBUG
50 | Main.Logger.Log($"Tracking reference {__instance} to mod {mod.Manifest.UniqueName} blueprint {value}");
51 | #endif
52 |
53 | _ = references.Add(__instance);
54 | break;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/WrathPatches.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.7.33711.374
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WrathPatches", "WrathPatches\WrathPatches.csproj", "{2D651098-A88E-4BC8-8008-2D4414497A77}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4DE6A293-0C63-4588-A521-A577923C51A3}"
9 | ProjectSection(SolutionItems) = preProject
10 | common.props = common.props
11 | Repository.json = Repository.json
12 | EndProjectSection
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShaderWrapper", "ShaderWrapper\ShaderWrapper.csproj", "{CCD98E38-C932-49F6-8DEE-CDFE87E7DA82}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {2D651098-A88E-4BC8-8008-2D4414497A77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {2D651098-A88E-4BC8-8008-2D4414497A77}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {2D651098-A88E-4BC8-8008-2D4414497A77}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {2D651098-A88E-4BC8-8008-2D4414497A77}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {CCD98E38-C932-49F6-8DEE-CDFE87E7DA82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {CCD98E38-C932-49F6-8DEE-CDFE87E7DA82}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {CCD98E38-C932-49F6-8DEE-CDFE87E7DA82}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {CCD98E38-C932-49F6-8DEE-CDFE87E7DA82}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {ED66E901-F169-4704-980D-5A24E4C767D6}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/ActuallyFixSharedStrings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Emit;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using UnityEngine;
9 |
10 | using Kingmaker.Blueprints.JsonSystem.Converters;
11 | using Kingmaker.Localization;
12 |
13 | using HarmonyLib;
14 |
15 | using System.Reflection;
16 |
17 | namespace WrathPatches
18 | {
19 | [WrathPatch("Fix Shared Strings")]
20 | [HarmonyPatch]
21 | public static class ActuallyFixSharedStrings
22 | {
23 | static MethodInfo? CreateSharedStringInstance => typeof(ScriptableObject)
24 | .GetMembers(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)
25 | .OfType()
26 | .Where(m => m.Name == nameof(ScriptableObject.CreateInstance) && m.IsGenericMethodDefinition)
27 | .FirstOrDefault()
28 | ?.MakeGenericMethod(typeof(SharedStringAsset));
29 |
30 | [HarmonyPatch(typeof(SharedStringConverter), nameof(SharedStringConverter.ReadJson))]
31 | [HarmonyTranspiler]
32 | static IEnumerable SharedStringConverter_ReadJson_Transpiler(IEnumerable instructions)
33 | {
34 | //Main.Logger.Log($"{nameof(SharedStringConverter_ReadJson_Transpiler)}");
35 |
36 | var ssCons = typeof(SharedStringAsset).GetConstructor([]);
37 |
38 | var createSs = CreateSharedStringInstance ?? throw new Exception($"Unable to find or create constructor");
39 |
40 | var count = 0;
41 |
42 | foreach (var i in instructions)
43 | {
44 | if (i.Is(OpCodes.Newobj, ssCons))
45 | yield return new CodeInstruction(OpCodes.Call, createSs);
46 |
47 | else yield return i;
48 |
49 | count++;
50 | }
51 |
52 | if (count == 0)
53 | throw new Exception("Could not find instructions to patch");
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Wrath Patches
2 |
3 | A collection of small patches for Pathfinder: Wrath of the Righteous. Mostly aimed at cleaning up the
4 | game's log files, but fixed some minor bugs along the way.
5 |
6 | Any patch can be disabled by removing the corresponding dll from the mod directory.
7 |
8 | Thanks to Kurufinve on the Owlcat discord for the assistance and inspiration. Also Kurufinve and Hambeard for their contributions.
9 |
10 | ## Current fixes
11 |
12 | This list is infrequently updated and likely out of date
13 |
14 | - Add AbilityData spell descriptor to AbilityExecutionContext
15 | - Fix Shared Strings
16 | - AddFacts.UpdateFacts NRE fix
17 | - Silence missing animator state errors
18 | - Add per-OwlMod log sinks
19 | - Use OwlMod logger in place of PFLog.Mods
20 | - Catch and log Blueprint patch exceptions
21 | - No BloodyFaceController for non-UnitEntityView
22 | - Fix NRE in SelectionCharacterController.CurrentSelectedCharacter
23 | - Element AssetGuid null fix
24 | - Better EntityFactComponent error messages
25 | - Make BlueprintBeingRead ThreadStatic
26 | - GameStatistic.Tick null Player fix
27 | - Event subscription leak fixes
28 | - Silence 'no binding' warnings
29 | - Silence 'Await event system' messages
30 | - Only load .dll files from owlmod Assemblies directories
31 | - Less verbose short log exceptions
32 | - OpenGameLogFull opens Player.log too
33 | - Don't try to play missing settings UI sound types
34 | - OwlcatModificationManager: UMM mods as dependencies for OMM mods
35 | - OwlcatModificationManager: Compare OMM mod versions like UMM
36 | - Reduce button click delay from 300ms to 1 frame
37 | - Add components from mod assemblies to binder cache
38 | - Load OwlMod BlueprintDirectReferences dependencies
39 | - Non-matching BlueprintComponent.OwnerBlueprint warning
40 | - Make ProgressionRoot.HumanRace not break for new races
41 | - Hambeard: Fix save slot VM double bind
42 | - Remove unnecessary code in SetMagusFeatureActive.OnTurnOff
43 | - Kurufinve: ShaderWrapper fix for owlmod material shaders
44 | - Fix Spell Slot comparison
45 | - Kurufinve: Owlmod Enhancer
46 | - Fix UnitMarkManager.LateUpdate iterator mutation
47 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/UnitMarkManager.LateUpdate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Emit;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using HarmonyLib;
9 |
10 | using Kingmaker.UI.Selection;
11 |
12 | using WrathPatches.TranspilerUtil;
13 |
14 | namespace WrathPatches
15 | {
16 | [WrathPatch("Fix UnitMarkManager.LateUpdate iterator mutation")]
17 | [HarmonyPatch(typeof(UnitMarkManager), nameof(UnitMarkManager.LateUpdate))]
18 | internal static class UnitMarkManager_LateUpdate
19 | {
20 | [HarmonyTranspiler]
21 | static IEnumerable Transpiler(IEnumerable instructions)
22 | {
23 | var UnitMarkManager_m_Marks = typeof(UnitMarkManager).GetField(nameof(UnitMarkManager.m_Marks), AccessTools.all);
24 | var Dictionary_string_UIDecalBase_Values =
25 | AccessTools.PropertyGetter(typeof(Dictionary), nameof(Dictionary.Values));
26 | var Dictionary_string_UIDecalBase_ValueCollection =
27 | typeof(Dictionary.ValueCollection)
28 | .GetMethod(nameof(Dictionary.ValueCollection.GetEnumerator));
29 |
30 | var match = new Func[]
31 | {
32 | ci => ci.opcode == OpCodes.Ldarg_0,
33 | ci => ci.LoadsField(UnitMarkManager_m_Marks),
34 | ci => ci.Calls(Dictionary_string_UIDecalBase_Values),
35 | ci => ci.Calls(Dictionary_string_UIDecalBase_ValueCollection)
36 | };
37 |
38 | var iList = instructions.ToList();
39 |
40 | var iMatch = instructions.FindInstructionsIndexed(match);
41 |
42 | if (!iMatch.Any()) return instructions;
43 |
44 | var index = iMatch.First().index + 2;
45 |
46 | iList.Insert(index, new CodeInstruction(OpCodes.Newobj,
47 | AccessTools.Constructor(typeof(Dictionary), new[] { typeof(Dictionary) } )));
48 |
49 | return iList;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/AnimationClipWrapperStateMachineBehaviour.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Reflection.Emit;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | using HarmonyLib;
10 |
11 | using Kingmaker.Visual.Animation.Events;
12 |
13 | namespace WrathPatches
14 | {
15 | [WrathPatch("Silence missing animator state errors")]
16 | [HarmonyPatch(typeof(AnimationClipWrapperStateMachineBehaviour))]
17 | internal static class AnimationClipWrapperStateMachineBehaviour_Patch
18 | {
19 | [HarmonyPatch(nameof(AnimationClipWrapperStateMachineBehaviour.OnStateEnter))]
20 | [HarmonyTranspiler]
21 | static IEnumerable OnStateEnter_Transpiler(IEnumerable instructions)
22 | {
23 | var AnimationClipWrapperStateMachineBehaviour_AnimationParameterIdleOffsetIsMissing =
24 | AccessTools.Field(typeof(AnimationClipWrapperStateMachineBehaviour),
25 | nameof(AnimationClipWrapperStateMachineBehaviour.AnimationParameterIdleOffsetIsMissing));
26 |
27 | var Nullable_bool_Value = AccessTools.PropertyGetter(typeof(bool?), nameof(Nullable.Value));
28 |
29 | var matched = instructions.FindSequence(
30 | [
31 | ci => ci.opcode == OpCodes.Ldflda &&
32 | ((FieldInfo)ci.operand) == AnimationClipWrapperStateMachineBehaviour_AnimationParameterIdleOffsetIsMissing,
33 | ci =>
34 | ci.opcode == OpCodes.Call && ((MethodInfo)ci.operand) == Nullable_bool_Value,
35 | ci => ci.opcode == OpCodes.Brfalse_S
36 | ]).ToArray();
37 |
38 | if (matched.Length == 3)
39 | {
40 | matched[1].opcode = OpCodes.Pop;
41 | matched[1].operand = null;
42 |
43 | matched[2].opcode = OpCodes.Br;
44 | }
45 | else
46 | {
47 | Main.Logger.Error($"{nameof(AnimationClipWrapperStateMachineBehaviour_Patch)}.{nameof(OnStateEnter_Transpiler)} failed to find instructions to patch");
48 | }
49 |
50 | return instructions;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/WeaponSetGripVMFix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Reflection.Emit;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | using HarmonyLib;
10 |
11 | using Kingmaker.PubSubSystem;
12 | using Kingmaker.UI.MVVM._VM.Slots;
13 | using Kingmaker.UnitLogic.FactLogic;
14 | using Kingmaker.Utility;
15 |
16 | namespace WrathPatches.Patches
17 | {
18 | #if false
19 | //[HarmonyPatchCategory("Experimental")]
20 | [WrathPatch("Add null check to WeaponSetGripVM.HandleUnitChangedGripAutoMode")]
21 | [HarmonyPatch]
22 | internal static class WeaponSetGripVMFix
23 | {
24 | [HarmonyTargetMethod]
25 | static MethodInfo TargetMethod()
26 | {
27 | var interfaceMap = typeof(WeaponSetGripVM).GetInterfaceMap(typeof(IUnityChangedGripAutoModeHandler));
28 |
29 | if (interfaceMap.InterfaceMethods
30 | .Zip(interfaceMap.TargetMethods)
31 | .TryFind((pair) =>
32 | pair.Item1.Name == nameof(IUnityChangedGripAutoModeHandler.HandleUnitChangedGripAutoMode),
33 | out var pair))
34 | {
35 | return pair.Item2;
36 | }
37 |
38 | throw new Exception("Missing interface method");
39 | }
40 |
41 | [HarmonyTranspiler]
42 | static IEnumerable Transpiler(IEnumerable instructions, ILGenerator ilGen)
43 | {
44 | var index = instructions.FindIndex(ci =>
45 | ci.LoadsField(AccessTools.Field(typeof(WeaponSetGripVM), nameof(WeaponSetGripVM.m_HandsEquipmentSet))));
46 |
47 | if (index < 0)
48 | {
49 | throw new Exception("Could not find target instruction");
50 | }
51 |
52 | var iList = instructions.ToList();
53 |
54 | var label = ilGen.DefineLabel();
55 |
56 | iList.InsertRange(index + 1,
57 | [
58 | new(OpCodes.Dup),
59 | new(OpCodes.Brtrue_S, label),
60 | new(OpCodes.Pop),
61 | new(OpCodes.Pop),
62 | new(OpCodes.Ret),
63 | new(OpCodes.Nop) { labels = [label] }
64 | ]);
65 |
66 | return iList;
67 | }
68 | }
69 | #endif
70 | }
71 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/KingmakerInputModule.CheckEventSystem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Reflection.Emit;
6 | using System.Runtime.CompilerServices;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | using HarmonyLib;
11 |
12 | using Kingmaker.UI.Selection;
13 |
14 | using Owlcat.Runtime.Core.Logging;
15 |
16 | using WrathPatches.TranspilerUtil;
17 |
18 | namespace WrathPatches
19 | {
20 | [WrathPatch("Silence 'Await event system' messages")]
21 | [HarmonyPatch]
22 | internal static class KingmakerInputModule_CheckEventSystem
23 | {
24 | static MethodBase? TargetMethod() =>
25 | typeof(KingmakerInputModule)
26 | .GetNestedTypes(AccessTools.all)
27 | .Where(t => t.GetCustomAttributes().Any())
28 | .Select(t => t.GetMethod("MoveNext", AccessTools.all))
29 | .FirstOrDefault();
30 |
31 | [HarmonyTranspiler]
32 | static IEnumerable Transpiler(IEnumerable instructions)
33 | {
34 | Main.Logger.Log($"{nameof(KingmakerInputModule_CheckEventSystem)}.{nameof(Transpiler)}");
35 |
36 | var LogChannel_System = typeof(LogChannel).GetField(nameof(LogChannel.System), AccessTools.all);
37 |
38 | var match = new Func[]
39 | {
40 | ci => ci.LoadsField(LogChannel_System),
41 | ci => ci.opcode == OpCodes.Ldstr && "Await event system".Equals(ci.operand),
42 | ci => ci.opcode == OpCodes.Call,
43 | ci => ci.opcode == OpCodes.Callvirt
44 | };
45 |
46 | var iMatch = instructions.FindInstructionsIndexed(match);
47 |
48 | if (!iMatch.Any())
49 | {
50 | Main.Logger.Log("No match found");
51 | return instructions;
52 | }
53 |
54 | var iList = instructions.ToList();
55 |
56 | foreach ((var index, var _) in iMatch)
57 | {
58 | var i = new CodeInstruction(OpCodes.Nop)
59 | {
60 | labels = iList[index].labels,
61 | blocks = iList[index].blocks
62 | };
63 | iList[index] = i;
64 | }
65 |
66 | return iList;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/WrathPatches/TranspilerUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection.Emit;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | using HarmonyLib;
9 |
10 | namespace WrathPatches.TranspilerUtil
11 | {
12 | public static class TranspilerUtil
13 | {
14 | public static IEnumerable GetNestedTypesAndSelf(this Type t)
15 | {
16 | yield return t;
17 |
18 | foreach (var n in t.GetNestedTypes().SelectMany(n => n.GetNestedTypesAndSelf()))
19 | {
20 | yield return n;
21 | }
22 | }
23 |
24 | public static IEnumerable<(int index, CodeInstruction instruction)> FindInstructionsIndexed(
25 | this IEnumerable instructions,
26 | IEnumerable> matchFuncs)
27 | {
28 | var matched = instructions
29 | .Indexed()
30 | .FindSequence(matchFuncs
31 | .Select, Func<(int index, CodeInstruction item), bool>>(f =>
32 | i => f(i.item)));
33 |
34 | if (!matched.Any()) return Enumerable.Empty<(int, CodeInstruction)>();
35 |
36 | return matched;
37 | }
38 |
39 | public static IEnumerable ReplaceInstructions(
40 | IEnumerable source,
41 | IEnumerable match,
42 | IEnumerable replaceWith)
43 | {
44 | var matchIndexed = match.Select>(m =>
45 | ((int, CodeInstruction instruction) ici) =>
46 | m.opcode == ici.instruction.opcode &&
47 | (m.operand is null || m.operand == ici.instruction.operand));
48 |
49 | (int index, CodeInstruction i)[] matchedInstructions = source.Indexed().FindSequence(matchIndexed).ToArray();
50 |
51 | if (!matchedInstructions.Any())
52 | {
53 | return Enumerable.Empty();
54 | }
55 |
56 | var index = matchedInstructions.First().index;
57 |
58 | var iList = source.ToList();
59 |
60 | iList.RemoveRange(index, matchedInstructions.Length);
61 | iList.InsertRange(index, replaceWith);
62 |
63 |
64 | return iList;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/BetterModLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Reflection.Emit;
7 | using System.Runtime.CompilerServices;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | using HarmonyLib;
12 |
13 | using Kingmaker;
14 | using Kingmaker.Blueprints;
15 | using Kingmaker.Blueprints.JsonSystem;
16 | using Kingmaker.Localization.Shared;
17 | using Kingmaker.Modding;
18 | using Kingmaker.UnitLogic.FactLogic;
19 |
20 | using Newtonsoft.Json;
21 |
22 | using Owlcat.Runtime.Core.Logging;
23 |
24 | namespace WrathPatches.Patches;
25 |
26 | [WrathPatch("Add per-OwlMod log sinks")]
27 | [HarmonyPatch]
28 | static class BetterModLogger
29 | {
30 | static readonly Dictionary ModLoggers = [];
31 |
32 | [HarmonyPatch(typeof(OwlcatModification), MethodType.Constructor, [typeof(string), typeof(string), typeof(OwlcatModificationManifest), typeof(Exception)])]
33 | [HarmonyPostfix]
34 | internal static void AddModLogSink(OwlcatModification __instance, string dataFolderPath)
35 | {
36 | if (__instance is null || __instance.Manifest is null)
37 | return;
38 |
39 | Main.Logger.Log($"{__instance.Manifest.UniqueName}.Logger.Name = {__instance.Logger?.Name}");
40 |
41 | if (__instance.Logger is null || __instance.Logger.Name != __instance.Manifest.UniqueName)
42 | return;
43 |
44 | var path = Path.GetDirectoryName(__instance.DataFilePath);
45 |
46 | var fileName = $"{OwlcatModification.InvalidPathCharsRegex.Replace(__instance.Manifest.UniqueName, "")}_Log.txt";
47 |
48 | Main.Logger.Log($"Log file path: {Path.Combine(path, fileName)}");
49 |
50 | if (ModLoggers.ContainsKey(fileName))
51 | {
52 | PFLog.Mods.Error($"Logger for {__instance.Manifest.UniqueName} has already been created");
53 | return;
54 | }
55 |
56 | var sink = new UberLoggerFilter(new UberLoggerFile(fileName, path), LogSeverity.Disabled, [__instance.Manifest.UniqueName]);
57 |
58 | ModLoggers.Add(fileName, sink);
59 |
60 | Logger.Instance.AddLogger(sink, false);
61 | }
62 | }
63 |
64 | [WrathPatch("Use OwlMod logger in place of PFLog.Mods")]
65 | [HarmonyPatch]
66 | static class PatchModLogInvocations
67 | {
68 | static IEnumerable TargetMethods() =>
69 | AccessTools.GetDeclaredMethods(typeof(OwlcatModification)).Where(m => !m.IsStatic && !m.IsGenericMethod);
70 |
71 | static IEnumerable Transpiler(IEnumerable instructions)
72 | {
73 | foreach (var i in instructions)
74 | {
75 | if (i.Calls(AccessTools.PropertyGetter(typeof(PFLog), nameof(PFLog.Mods))))
76 | {
77 | yield return new(OpCodes.Ldarg_0);
78 |
79 | i.opcode = OpCodes.Ldfld;
80 | i.operand = AccessTools.Field(typeof(OwlcatModification), nameof(OwlcatModification.Logger));
81 | }
82 |
83 | yield return i;
84 | }
85 | }
86 | }
87 |
88 | [WrathPatch("Catch and log Blueprint patch exceptions")]
89 | [HarmonyPatch(typeof(OwlcatModification), nameof(OwlcatModification.TryPatchBlueprint))]
90 | static class CatchBlueprintPatchExceptions
91 | {
92 | static Exception? Finalizer(Exception __exception, OwlcatModification __instance)
93 | {
94 | if (__exception is not null)
95 | __instance.Logger.Exception(__exception);
96 |
97 | return null;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/WrathPatches/HarmonyPatchCategory.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Reflection;
7 |
8 | using HarmonyLib;
9 |
10 | namespace WrathPatches
11 | {
12 | internal static class HarmonyAttributeExtensions
13 | {
14 | static readonly FieldInfo containerType =
15 | typeof(PatchClassProcessor).GetField(nameof(containerType), BindingFlags.Instance | BindingFlags.NonPublic);
16 |
17 | static readonly FieldInfo containerAttributes =
18 | typeof(PatchClassProcessor).GetField(nameof(containerAttributes), BindingFlags.Instance | BindingFlags.NonPublic);
19 |
20 | public static bool HasPatchAttribute(this PatchClassProcessor patchClass) => containerAttributes.GetValue(patchClass) is not null;
21 |
22 | public static string? GetCategory(this PatchClassProcessor patchClass) =>
23 | (containerType.GetValue(patchClass) as Type)?.GetCustomAttribute()?.Category;
24 |
25 | /// Searches an assembly for Harmony-annotated classes without category annotations and uses them to create patches
26 | /// The harmony instance
27 | ///
28 | public static void PatchAllUncategorized(this Harmony harmony)
29 | {
30 | var method = new StackTrace().GetFrame(1).GetMethod();
31 | var assembly = method.ReflectedType.Assembly;
32 | harmony.PatchAllUncategorized(assembly);
33 | }
34 |
35 | /// Searches an assembly for Harmony-annotated classes without category annotations and uses them to create patches
36 | /// The harmony instance
37 | /// The assembly
38 | ///
39 | public static void PatchAllUncategorized(this Harmony harmony, Assembly assembly)
40 | {
41 | var patchClasses = AccessTools.GetTypesFromAssembly(assembly).Select(harmony.CreateClassProcessor).ToArray();
42 | patchClasses.DoIf((patchClass => string.IsNullOrEmpty(patchClass.GetCategory())), (patchClass => patchClass.Patch()));
43 | }
44 |
45 | /// Searches an assembly for Harmony annotations with a specific category and uses them to create patches
46 | /// The harmony instance
47 | /// Name of patch category
48 | ///
49 | public static void PatchCategory(this Harmony harmony, string category)
50 | {
51 | var method = new StackTrace().GetFrame(1).GetMethod();
52 | var assembly = method.ReflectedType.Assembly;
53 | harmony.PatchCategory(assembly, category);
54 | }
55 |
56 | /// Searches an assembly for Harmony annotations with a specific category and uses them to create patches
57 | /// The harmony instance
58 | /// The assembly
59 | /// Name of patch category
60 | ///
61 | public static void PatchCategory(this Harmony harmony, Assembly assembly, string category)
62 | {
63 | var patchClasses = AccessTools.GetTypesFromAssembly(assembly).Select(harmony.CreateClassProcessor).ToArray();
64 | patchClasses.DoIf((patchClass => patchClass.GetCategory() == category), (patchClass => patchClass.Patch()));
65 | }
66 | }
67 |
68 | /// Annotation to define a category for use with PatchCategory
69 | ///
70 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
71 | internal class HarmonyPatchCategory : Attribute
72 | {
73 | public readonly string Category;
74 |
75 | /// Annotation specifying the category
76 | /// Name of patch category
77 | ///
78 | public HarmonyPatchCategory(string category)
79 | {
80 | this.Category = category;
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/LogExceptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection.Emit;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | using HarmonyLib;
10 |
11 | using Kingmaker;
12 | using Kingmaker.Logging.Configuration.Platforms;
13 |
14 | using Owlcat.Runtime.Core.Logging;
15 |
16 | namespace WrathPatches
17 | {
18 | [WrathPatch("Less verbose short log exceptions")]
19 | [HarmonyPatch]
20 | internal static class Log_Exceptions_Patch
21 | {
22 | class ShortLogWithoutCallstacks : UberLoggerFile, IDisposableLogSink, ILogSink
23 | {
24 | public ShortLogWithoutCallstacks(string filename, string path = null!, bool includeCallStacks = true, bool extendedLog = false)
25 | : base(filename, path, includeCallStacks, extendedLog)
26 | {
27 | #if DEBUG
28 | Main.Logger.Log(string.Join(" ", "new", typeof(ShortLogWithoutCallstacks)));
29 |
30 | if (this.LogFileWriter.BaseStream is not FileStream fs)
31 | Main.Logger.Error($"{nameof(LogFileWriter)} is not a FileStream");
32 | else
33 | Main.Logger.Log($"File: {fs.Name}");
34 | #endif
35 | }
36 |
37 | void ILogSink.Log(LogInfo logInfo)
38 | {
39 | #if DEBUG
40 | Main.Logger.Log(string.Join(".", nameof(ShortLogWithoutCallstacks), nameof(Log)));
41 | Main.Logger.Log($"IsException? {logInfo.IsException}");
42 | Main.Logger.Log(logInfo.Message);
43 | #endif
44 |
45 | if (!logInfo.IsException)
46 | {
47 | logInfo = new LogInfo(
48 | logInfo.Source,
49 | logInfo.Channel,
50 | logInfo.TimeStamp,
51 | logInfo.Severity,
52 | null,
53 | logInfo.IsException,
54 | logInfo.Message);
55 | }
56 |
57 | base.Log(logInfo);
58 | }
59 | }
60 |
61 | [HarmonyPatch(typeof(LogSinkFactory), nameof(LogSinkFactory.CreateShort))]
62 | [HarmonyTranspiler]
63 | static IEnumerable LogSinkFactory_CreateShort_Transpiler(IEnumerable instructions)
64 | {
65 | Main.Logger.Log(nameof(LogSinkFactory_CreateShort_Transpiler));
66 |
67 | foreach (var i in instructions)
68 | {
69 | if (i.opcode == OpCodes.Newobj &&
70 | typeof(UberLoggerFile).GetConstructors(AccessTools.all)
71 | .Any(i.OperandIs))
72 | i.operand = typeof(ShortLogWithoutCallstacks).GetConstructors().First();
73 |
74 | yield return i;
75 | }
76 | }
77 |
78 | [HarmonyPatch(typeof(Logger), nameof(Logger.Log))]
79 | [HarmonyTranspiler]
80 | static IEnumerable Logger_Log_Transpiler(IEnumerable instructions, ILGenerator ilGen)
81 | {
82 | var exParamIndex = 4;
83 |
84 | var lb = ilGen.DeclareLocal(typeof(Exception));
85 | lb.SetLocalSymInfo("ex2");
86 | var localIndex = lb.LocalIndex;
87 |
88 | var iList = instructions.ToList();
89 |
90 | iList.InsertRange(0, new[]
91 | {
92 | new CodeInstruction(OpCodes.Ldarg_S, exParamIndex), // Exception ex
93 | new CodeInstruction(OpCodes.Stloc_S, localIndex),
94 | });
95 |
96 | var logInfoCtorIndex = iList
97 | .Indexed()
98 | .Where(i => i.item.opcode == OpCodes.Newobj &&
99 | typeof(LogInfo).GetConstructors()
100 | .Any(i.item.OperandIs))
101 | .Select(i => i.index)
102 | .First();
103 |
104 | var getEx = iList[logInfoCtorIndex - 5];
105 |
106 | if (!getEx.IsLdarg(exParamIndex))
107 | throw new InvalidOperationException($"{getEx} should be ldarg");
108 |
109 | getEx.opcode = OpCodes.Ldloc_S;
110 | getEx.operand = localIndex;
111 |
112 | return iList;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/OwlcatModification.LoadAssemblies_Patch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 |
7 | using HarmonyLib;
8 |
9 | using Kingmaker;
10 | using Kingmaker.Blueprints.JsonSystem;
11 | using Kingmaker.Modding;
12 |
13 | using UnityModManagerNet;
14 |
15 | namespace WrathPatches;
16 |
17 | [WrathPatch("Add components from mod assemblies to binder cache")]
18 | [HarmonyPatch(typeof(OwlcatModification), "LoadAssemblies")]
19 | internal static class OwlcatModification_LoadAssemblies_Patch
20 | {
21 | [HarmonyPostfix]
22 | static void Postfix(OwlcatModification __instance)
23 | {
24 | var binder = (GuidClassBinder)Json.Serializer.Binder;
25 |
26 | foreach (var assembly in __instance.LoadedAssemblies)
27 | {
28 | foreach (var (type, guid) in assembly.GetTypes()
29 | .Select(type => (type, type.GetCustomAttribute()?.GuidString))
30 | .Where(t => t.GuidString is not null))
31 | {
32 |
33 | var logMessage = $"Adding {type} with TypeId {guid} to binder cache";
34 | WrathPatches.Main.Logger.Log(logMessage);
35 | __instance.Logger.Log(logMessage);
36 |
37 | binder.AddToCache(type, guid);
38 | }
39 | }
40 | }
41 | }
42 |
43 | [HarmonyPatch(typeof(OwlcatModificationsManager), nameof(OwlcatModificationsManager.Start))]
44 | static class UmmModsToGuidClassBinder
45 | {
46 |
47 | static readonly Dictionary> DuplicateTypeIds = [];
48 |
49 | static void Prefix()
50 | {
51 | var binder = (GuidClassBinder)Json.Serializer.Binder;
52 |
53 | foreach (var mod in UnityModManager.modEntries.Where(me => me.Started))
54 | {
55 | Main.Logger.Log($"Adding types to binder from {mod.Info.DisplayName}");
56 |
57 | foreach (var f in Directory
58 | .EnumerateFiles(mod.Path, "*.dll", SearchOption.AllDirectories)
59 | //.Where(path => AppDomain.CurrentDomain.GetAssemblies()
60 | // .Select(ass => Path.GetFullPath((ass.Location)))
61 | // .Contains(Path.GetFullPath(path)))
62 | )
63 | {
64 | Type[] types = [];
65 |
66 | try
67 | {
68 | types = Assembly.LoadFrom(f).GetTypes();
69 | }
70 | catch (ReflectionTypeLoadException rtle)
71 | {
72 | types = rtle.Types;
73 | }
74 | catch (Exception e)
75 | {
76 | Main.Logger.LogException(e);
77 | continue;
78 | }
79 | try
80 | {
81 | foreach (var (type, guid) in types
82 | .Select(type => (type, type.GetCustomAttribute()?.GuidString))
83 | .Where(t => t.GuidString is not null))
84 | {
85 | if (guid is null)
86 | continue;
87 |
88 | Main.Logger.Log($"Adding {type} with TypeId {guid} to binder cache");
89 |
90 | if (binder.m_GuidToTypeCache.ContainsKey(guid))
91 | {
92 | //PFLog.Mods.Error("I told kuru this would happen");
93 |
94 | if (!DuplicateTypeIds.TryGetValue(guid, out var ts))
95 | ts = DuplicateTypeIds[guid] = [];
96 |
97 | ts.Add(type);
98 |
99 | PFLog.Mods.Log($"Duplicate typeid {guid}\n" + string.Join("\n", ts.Select(t => $"{t.Assembly.Location}: {t.FullName}")));
100 |
101 | continue;
102 | }
103 |
104 | binder.m_GuidToTypeCache.Add(guid, type);
105 | binder.m_TypeToGuidCache.Add(type, guid);
106 | }
107 | }
108 | catch (Exception ex)
109 | {
110 | PFLog.Mods.Exception(ex);
111 | continue;
112 | }
113 | }
114 | }
115 | }
116 | }
--------------------------------------------------------------------------------
/WrathPatches/Main.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | using HarmonyLib;
7 |
8 | using UnityEngine;
9 |
10 | using UnityModManagerNet;
11 |
12 | using WrathPatches.Patches;
13 |
14 | namespace WrathPatches;
15 |
16 | static partial class Main
17 | {
18 | static void OnGUI(UnityModManager.ModEntry _)
19 | {
20 | GUILayout.BeginHorizontal();
21 | {
22 | GUILayout.BeginVertical();
23 | {
24 | GUILayout.Label("Patches Status");
25 |
26 | var font = GUI.skin.label.font;
27 |
28 | foreach (var (t, pc) in PatchClasses.Value)
29 | {
30 | var name = t.Name;
31 |
32 | AppliedPatches.TryGetValue(t, out var applied);
33 |
34 | if (t.GetCustomAttribute() is { } attr)
35 | name = attr.Name;
36 |
37 | if (IsExperimental(pc))
38 | name = $"(Experimental) {name}";
39 |
40 | GUILayout.Toggle(applied is true, name);
41 | }
42 | }
43 | GUILayout.EndVertical();
44 |
45 | GUILayout.BeginVertical();
46 | {
47 | static bool IsVersionMisMatch() => HarmonyVersion < UmmHarmonyVersion;
48 |
49 | GUILayout.BeginHorizontal();
50 | {
51 | GUILayout.Label($"Harmony version: {HarmonyVersion}");
52 |
53 | if (IsVersionMisMatch() && ModEntry!.Active)
54 | {
55 | if (GUILayout.Button("Update"))
56 | {
57 | ModEntry!.OnToggle = (_, value) => !value;
58 | ModEntry!.Active = false;
59 | ModEntry!.Info.DisplayName = $"{ModEntry!.Info.DisplayName} - RESTART REQUIRED";
60 |
61 | ReplaceHarmony(GetHarmonyAss().Location, UmmHarmonyPath);
62 | }
63 | }
64 | }
65 | GUILayout.EndHorizontal();
66 |
67 | GUILayout.Label($"UMM Harmony Version: {UmmHarmonyVersion}");
68 | }
69 | GUILayout.EndVertical();
70 | }
71 | GUILayout.EndHorizontal();
72 | }
73 |
74 | private static UnityModManager.ModEntry? ModEntry;
75 |
76 | public static UnityModManager.ModEntry.ModLogger Logger => ModEntry!.Logger;
77 |
78 | internal static Harmony HarmonyInstance = null!;
79 |
80 | static readonly Lazy<(Type t, PatchClassProcessor pc)[]> PatchClasses = new(() =>
81 | AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly())
82 | .Select(t => (t, pc: HarmonyInstance!.CreateClassProcessor(t)))
83 | .Where(tuple => tuple.pc.HasPatchAttribute())
84 | .ToArray());
85 |
86 | static readonly Dictionary AppliedPatches = new();
87 |
88 | static bool IsExperimental(PatchClassProcessor pc) => pc.GetCategory() == "Experimental";
89 |
90 | static void RunPatches(IEnumerable<(Type, PatchClassProcessor)> typesAndPatches)
91 | {
92 | foreach (var (t, pc) in typesAndPatches)
93 | {
94 | try
95 | {
96 | Logger.Log($"Running patch class {t.Name}");
97 | pc.Patch();
98 |
99 | AppliedPatches[t] = true;
100 | }
101 | catch (Exception ex)
102 | {
103 | Logger.Error($"Exception in patch class {t.Name}");
104 | Logger.LogException(ex);
105 |
106 | AppliedPatches[t] = false;
107 | }
108 | }
109 | }
110 |
111 | internal static bool Load(UnityModManager.ModEntry modEntry)
112 | {
113 | ModEntry = modEntry;
114 |
115 | ModEntry.OnGUI = OnGUI;
116 |
117 | ModEntry.OnToggle = (_, value) => value;
118 |
119 | HarmonyInstance = new Harmony(modEntry.Info.Id);
120 |
121 | RunPatches(PatchClasses.Value.Where(tuple => !IsExperimental(tuple.pc)));
122 |
123 | #if DEBUG
124 | var harmonyDebug = Harmony.DEBUG;
125 | Harmony.DEBUG = true;
126 |
127 | Logger.Log("Running experimental patches");
128 | try
129 | {
130 | RunPatches(PatchClasses.Value.Where(tuple => IsExperimental(tuple.pc)));
131 | }
132 | catch(Exception ex)
133 | {
134 | Logger.LogException(ex);
135 | }
136 |
137 | Harmony.DEBUG = harmonyDebug;
138 | #endif
139 |
140 | LogsHotkey.EnableHotKey();
141 |
142 | return true;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/WrathPatches/Patches/AddFactsIgnoreDeadReferences.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Reflection.Emit;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | using HarmonyLib;
10 |
11 | using Kingmaker.Blueprints;
12 | using Kingmaker.Blueprints.Facts;
13 | using Kingmaker.UnitLogic;
14 | using Kingmaker.UnitLogic.FactLogic;
15 |
16 | using WrathPatches.TranspilerUtil;
17 |
18 | namespace WrathPatches
19 | {
20 | internal static class AddFactsIgnoreDeadReferences
21 | {
22 | //[HarmonyPatch(typeof(AddFacts), nameof(AddFacts.Facts), MethodType.Getter)]
23 | //static class AddFacts_Facts_Patch
24 | //{
25 | // static ReferenceArrayProxy
26 | // Postfix(ReferenceArrayProxy arr)
27 | // {
28 | // return arr.m_Array.Where(bpRef => ResourcesLibrary.BlueprintsCache.m_LoadedBlueprints.ContainsKey(bpRef.deserializedGuid)).ToArray();
29 | // }
30 | //}
31 |
32 | static readonly MethodInfo HasItemMethod =
33 | typeof(Kingmaker.Utility.LinqExtensions).GetMethods()
34 | .First(mi =>
35 | {
36 | if (mi.Name != nameof(Kingmaker.Utility.LinqExtensions.HasItem))
37 | return false;
38 |
39 | var ps = mi.GetParameters();
40 |
41 | return ps.Length == 2 &&
42 | ps[1].ParameterType.IsGenericType &&
43 | ps[1].ParameterType.GetGenericTypeDefinition() ==
44 | typeof(Func