Skip to content
This repository has been archived by the owner on Mar 29, 2022. It is now read-only.

Commit

Permalink
implement FieldRef that works on ref structs
Browse files Browse the repository at this point in the history
implement PatchNotBeforeAttribute

add hash for SlipIntoShadowsPatch for v1.4.0 (needs verification)

add hashes for Builder, Construction Expert, Raiding for v1.2.1 (needs verification)
  • Loading branch information
Tyler-IN committed May 12, 2020
1 parent 3984daa commit f226e0e
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 78 deletions.
26 changes: 13 additions & 13 deletions src/CommunityPatch/CommunityPatch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<Configurations>Debug;Release</Configurations>
<Platform>x64</Platform>
<PlatformTarget>x64</PlatformTarget>
<RootNamespace/>
<RootNamespace />
<Title>Community Patch</Title>
<Authors>Tyler Young</Authors>
<Company>The Mount &amp; Blade II: Bannerlord Community</Company>
Expand Down Expand Up @@ -41,24 +41,24 @@
<None Include="..\..\README.md">
<Link>README.md</Link>
</None>
<None Remove="*.DotSettings"/>
<None Remove="*.DotSettings" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="HardwareProviders.CPU.Standard" Version="2.0.1"/>
<PackageReference Include="HardwareProviders.Standard" Version="2.0.1"/>
<PackageReference Include="HardwareProviders.CPU.Standard" Version="2.0.1" />
<PackageReference Include="HardwareProviders.Standard" Version="2.0.1" />
<PackageReference Include="Lib.Harmony" Version="2.0.0.9" PrivateAssets="All">
<GeneratePathProperty>True</GeneratePathProperty>
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.3.3"/>
<PackageReference Include="Microsoft.SourceLink.Common" Version="1.0.0"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0"/>
<PackageReference Include="Sigil" Version="5.0.0"/>
<PackageReference Include="System.Numerics.Vectors" Version="4.4.0"/>
<PackageReference Include="System.Reflection.Emit" Version="4.7.0"/>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1"/>
<PackageReference Include="TextCopy" Version="3.0.2"/>
<PackageReference Include="Tomlyn" Version="0.1.2"/>
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.3.3" />
<PackageReference Include="Microsoft.SourceLink.Common" Version="1.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" />
<PackageReference Include="Sigil" Version="5.0.0" />
<PackageReference Include="System.Numerics.Vectors" Version="4.4.0" />
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
<PackageReference Include="TextCopy" Version="3.0.2" />
<PackageReference Include="Tomlyn" Version="0.1.2" />
</ItemGroup>

<ItemGroup>
Expand Down
141 changes: 141 additions & 0 deletions src/CommunityPatch/Dynamic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Threading;
using Sigil;

namespace CommunityPatch {

Expand All @@ -17,6 +18,9 @@ public static class Dynamic {

private static readonly HashSet<string> InternalsAccessed = new HashSet<string>();

private static readonly CustomAttributeBuilder AggressiveInlining =
new CustomAttributeBuilder(typeof(MethodImplAttribute).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new[] {typeof(MethodImplOptions)}, null)!, new object[] {MethodImplOptions.AggressiveInlining});

private static long _unique = 0;

public static TypeBuilder CreateStaticClass(string name = null)
Expand Down Expand Up @@ -50,6 +54,143 @@ private static void AccessInternals(Assembly asm) {
}
}


public static FieldRef<T, TField> BuildRef<T, TField>(this FieldInfo fieldInfo, bool skipVerification = true) {
if (fieldInfo == null)
throw new ArgumentNullException(nameof(fieldInfo));

if (!typeof(TField).IsAssignableFrom(fieldInfo.FieldType))
throw new ArgumentException("Return type does not match.");

if (typeof(T) != typeof(object) && typeof(T) != typeof(IntPtr) && (fieldInfo.DeclaringType == null || !fieldInfo.DeclaringType.IsAssignableFrom(typeof(T))))
throw new MissingFieldException(typeof(T).Name, fieldInfo.Name);

var dt = Dynamic.CreateStaticClass();
var mn = $"{fieldInfo.Name}Getter";
var d = Emit<FieldRef<T, TField>>.BuildStaticMethod(dt, mn, MethodAttributes.Public,
allowUnverifiableCode: skipVerification, doVerify: !skipVerification);
d.LoadArgument(0);
d.LoadFieldAddress(fieldInfo);

if (fieldInfo.FieldType.IsValueType && !typeof(TField).IsValueType)
d.Box(typeof(TField));
d.Return();
var mb = d.CreateMethod();
mb.SetCustomAttribute(AggressiveInlining);
var dti = dt.CreateTypeInfo();
var dmi = dti!.GetMethod(mn, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public);
return (FieldRef<T, TField>) dmi!.CreateDelegate(typeof(FieldRef<T, TField>));
}


public static Func<T, TField> BuildGetter<T, TField>(this FieldInfo fieldInfo, bool skipVerification = true) {
if (fieldInfo == null)
throw new ArgumentNullException(nameof(fieldInfo));

if (!typeof(TField).IsAssignableFrom(fieldInfo.FieldType))
throw new ArgumentException("Return type does not match.");

if (typeof(T) != typeof(object) && typeof(T) != typeof(IntPtr) && (fieldInfo.DeclaringType == null || !fieldInfo.DeclaringType.IsAssignableFrom(typeof(T))))
throw new MissingFieldException(typeof(T).Name, fieldInfo.Name);

var dt = Dynamic.CreateStaticClass();
var mn = $"{fieldInfo.Name}Getter";
var d = Emit<Func<T, TField>>.BuildStaticMethod(dt, mn, MethodAttributes.Public,
allowUnverifiableCode: skipVerification, doVerify: !skipVerification);
d.LoadArgument(0);
d.LoadField(fieldInfo);

if (fieldInfo.FieldType.IsValueType && !typeof(TField).IsValueType)
d.Box(typeof(TField));
d.Return();
var mb = d.CreateMethod();
mb.SetCustomAttribute(AggressiveInlining);
var dti = dt.CreateTypeInfo();
var dmi = dti!.GetMethod(mn, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public);
return (Func<T, TField>) dmi!.CreateDelegate(typeof(Func<T, TField>));
}

public static Action<T, TField> BuildSetter<T, TField>(this FieldInfo fieldInfo, bool skipVerification = true) {
if (fieldInfo == null)
throw new ArgumentNullException(nameof(fieldInfo));

if (!typeof(TField).IsAssignableFrom(fieldInfo.FieldType))
throw new ArgumentException("Return type does not match.");

if (typeof(T) != typeof(object) && typeof(T) != typeof(IntPtr) && (fieldInfo.DeclaringType == null || !fieldInfo.DeclaringType.IsAssignableFrom(typeof(T))))
throw new MissingFieldException(typeof(T).Name, fieldInfo.Name);

var dt = Dynamic.CreateStaticClass();
var mn = $"{fieldInfo.Name}Setter";
var d = Emit<Action<T, TField>>.BuildStaticMethod(dt, mn, MethodAttributes.Public,
allowUnverifiableCode: skipVerification, doVerify: !skipVerification);
d.LoadArgument(0);
d.LoadArgument(1);
if (!fieldInfo.FieldType.IsValueType && typeof(TField).IsValueType)
d.Box(typeof(TField));
d.StoreField(fieldInfo);
d.Return();
var mb = d.CreateMethod();
mb.SetCustomAttribute(AggressiveInlining);
var dti = dt.CreateTypeInfo();
var dmi = dti!.GetMethod(mn, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public);
return (Action<T, TField>) dmi!.CreateDelegate(typeof(Action<T, TField>));
}

public static TDelegate BuildInvoker<TDelegate>(this MethodBase m) where TDelegate : Delegate {
var td = typeof(TDelegate);
Dynamic.AccessInternals(m);
var dtMi = td.GetMethod("Invoke", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
var dtPs = dtMi!.GetParameters();
var dt = Dynamic.CreateStaticClass();
var mn = $"{m.Name}Invoker";
var d = Emit<TDelegate>.BuildStaticMethod(dt, mn, MethodAttributes.Public);
var ps = m.GetParameters();
if (m.IsStatic) {
for (ushort i = 0; i < ps.Length; i++) {
var p = ps[i];
var dp = dtPs[i];
if (p.ParameterType != dp.ParameterType)
throw new NotImplementedException($"Unhandled parameter difference: {p.ParameterType.FullName} vs. {dp.ParameterType.FullName}");

d.LoadArgument(i);
}
}
else {
var thisParamType = m.GetThisParamType();
if (dtPs[0].ParameterType != thisParamType)
throw new NotImplementedException($"Unhandled this parameter difference: {dtPs[0].ParameterType.FullName} vs. {thisParamType}");

d.LoadArgument(0);
for (var i = 0; i < ps.Length; i++) {
var p = ps[i];
var dp = dtPs[i + 1];
if (p.ParameterType != dp.ParameterType)
throw new NotImplementedException($"Unhandled parameter difference: {p.ParameterType.FullName} vs. {dp.ParameterType.FullName}");

d.LoadArgument((ushort) (i + 1));
}
}

switch (m) {
case MethodInfo mi:
d.Call(mi);
break;
case ConstructorInfo ci:
d.Call(ci);
break;
default:
throw new NotSupportedException(m.MemberType.ToString());
}

d.Return();
var mb = d.CreateMethod();
mb.SetCustomAttribute(AggressiveInlining);
var dti = dt.CreateTypeInfo();
var dmi = dti!.GetMethod(mn, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public);
return (TDelegate) dmi!.CreateDelegate(td);
}

}

}
5 changes: 5 additions & 0 deletions src/CommunityPatch/FieldRef.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace CommunityPatch {

public delegate ref TField FieldRef<in T, TField>(T o);

}
57 changes: 0 additions & 57 deletions src/CommunityPatch/MethodHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ internal static class MethodHelpers {
private static readonly FieldInfo IlInstrOffsetField = typeof(Harmony).Assembly.GetType("HarmonyLib.ILInstruction")
.GetField("offset", Public | NonPublic | Instance | DeclaredOnly);

private static readonly CustomAttributeBuilder AggressiveInlining =
new CustomAttributeBuilder(typeof(MethodImplAttribute).GetConstructor(Public | Instance, null, new[] {typeof(MethodImplOptions)}, null)!, new object[] {MethodImplOptions.AggressiveInlining});

public static byte[] MakeCilSignatureSha256(this MethodBase mb) {
#if DEBUG_METHOD_SIGNATURE
using var hashSource = new MemoryStream(65536);
Expand Down Expand Up @@ -191,60 +188,6 @@ public static Type GetThisParamType(this MethodBase method) {
return type;
}

public static TDelegate BuildInvoker<TDelegate>(this MethodBase m) where TDelegate : Delegate {
var td = typeof(TDelegate);
Dynamic.AccessInternals(m);
var dtMi = td.GetMethod("Invoke", Public | NonPublic | Static | Instance);
var dtPs = dtMi!.GetParameters();
var dt = Dynamic.CreateStaticClass();
var mn = $"{m.Name}Invoker";
var d = Sigil.Emit<TDelegate>.BuildStaticMethod(dt, mn, MethodAttributes.Public);
var ps = m.GetParameters();
if (m.IsStatic) {
for (ushort i = 0; i < ps.Length; i++) {
var p = ps[i];
var dp = dtPs[i];
if (p.ParameterType != dp.ParameterType)
throw new NotImplementedException($"Unhandled parameter difference: {p.ParameterType.FullName} vs. {dp.ParameterType.FullName}");

d.LoadArgument(i);
}
}
else {
var thisParamType = m.GetThisParamType();
if (dtPs[0].ParameterType != thisParamType)
throw new NotImplementedException($"Unhandled this parameter difference: {dtPs[0].ParameterType.FullName} vs. {thisParamType}");

d.LoadArgument(0);
for (var i = 0; i < ps.Length; i++) {
var p = ps[i];
var dp = dtPs[i + 1];
if (p.ParameterType != dp.ParameterType)
throw new NotImplementedException($"Unhandled parameter difference: {p.ParameterType.FullName} vs. {dp.ParameterType.FullName}");

d.LoadArgument((ushort) (i + 1));
}
}

switch (m) {
case MethodInfo mi:
d.Call(mi);
break;
case ConstructorInfo ci:
d.Call(ci);
break;
default:
throw new NotSupportedException(m.MemberType.ToString());
}

d.Return();
var mb = d.CreateMethod();
mb.SetCustomAttribute(AggressiveInlining);
var dti = dt.CreateTypeInfo();
var dmi = dti!.GetMethod(mn, DeclaredOnly | Static | Public);
return (TDelegate) dmi!.CreateDelegate(td);
}

}

}
16 changes: 16 additions & 0 deletions src/CommunityPatch/PatchNotBeforeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using TaleWorlds.Library;

namespace CommunityPatch {

[AttributeUsage(AttributeTargets.Class)]
public class PatchNotBeforeAttribute : Attribute {

public ApplicationVersion Version { get; }

public PatchNotBeforeAttribute(ApplicationVersionType type, int major, int minor, int revision = 0, int changeSet = 0)
=> Version = new ApplicationVersion(type, major, minor, revision, changeSet);

}

}
2 changes: 2 additions & 0 deletions src/CommunityPatch/Patches/Feats/AseraiCheapCaravansPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.Actions;
using TaleWorlds.Core;
using TaleWorlds.Library;
using TaleWorlds.Localization;

namespace CommunityPatch.Patches.Feats {

[PatchObsolete(ApplicationVersionType.EarlyAccess,1,3)]
public sealed class AseraiCheapCaravansPatch : PatchBase<AseraiCheapCaravansPatch> {
// This patch only applies to player characters because it is triggered during a conversation where starting a
// caravan is possible. See TargetMethodInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ public override IEnumerable<MethodBase> GetMethodsChecked() {
0x12, 0xE9, 0x1C, 0xFE, 0x07, 0x49, 0xFB, 0x75,
0xB2, 0x55, 0xC1, 0xA9, 0xF0, 0xB7, 0xF9, 0xBB,
0x1F, 0x00, 0xF2, 0x7F, 0x04, 0x77, 0x84, 0x8E
},
new byte[] {
// e1.4.0.228869
0x1A, 0x65, 0x84, 0xA7, 0x10, 0x17, 0xDB, 0x7F,
0xE2, 0x09, 0x72, 0xE5, 0xDB, 0xB5, 0x6B, 0xC7,
0x93, 0x14, 0xA3, 0x40, 0xDD, 0x78, 0xE1, 0xB9,
0xCF, 0x9B, 0xAB, 0xE9, 0x53, 0xAD, 0x6B, 0xD3
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using TaleWorlds.CampaignSystem;
using TaleWorlds.Core;
using HarmonyLib;
using JetBrains.Annotations;
using TaleWorlds.Engine;
using TaleWorlds.Library;
using TaleWorlds.MountAndBlade;
using static System.Reflection.BindingFlags;
using static CommunityPatch.HarmonyHelpers;

namespace CommunityPatch.Patches.Perks.Endurance.Riding {

[PatchNotBefore(ApplicationVersionType.EarlyAccess, 1, 4)]
public sealed class TramplerPatch2 : PerkPatchBase<TramplerPatch2> {

public override bool Applied { get; protected set; }
Expand Down Expand Up @@ -94,12 +97,13 @@ public override void Apply(Game game) {
private static readonly FieldInfo AttackerAgentCharacterFieldInfo = AttackInformationType?.GetField("AttackerAgentCharacter", Public | Instance | DeclaredOnly);

[CanBeNull]
private static readonly AccessTools.FieldRef<object, BasicCharacterObject> AttackerAgentCharacter
= AttackerAgentCharacterFieldInfo == null ? null : AccessTools.FieldRefAccess<object, BasicCharacterObject>(AttackerAgentCharacterFieldInfo);
private static readonly FieldRef<IntPtr, BasicCharacterObject> AttackerAgentCharacter
= AttackerAgentCharacterFieldInfo == null ? null : AttackerAgentCharacterFieldInfo.BuildRef<IntPtr,BasicCharacterObject>();

private static void UpdateCorrectCharacterForHorseChargeDamagePostfix(ref object __instance, Agent attackerAgent, ref AttackCollisionData attackCollisionData) {
private static unsafe void UpdateCorrectCharacterForHorseChargeDamagePostfix(ref byte __instance, Agent attackerAgent, ref AttackCollisionData attackCollisionData) {
var p = (IntPtr)Unsafe.AsPointer(ref __instance);
if (attackCollisionData.IsHorseCharge && attackerAgent.RiderAgent?.Character != null)
AttackerAgentCharacter!(__instance) = attackerAgent.RiderAgent.Character;
AttackerAgentCharacter!(p) = attackerAgent.RiderAgent.Character;
}

private static bool HeroHasPerk(BasicCharacterObject character, PerkObject perk)
Expand All @@ -110,8 +114,9 @@ private int TramplerDamageModifier(int baseDamage) {
return (int) Math.Round(tramplerDamage, MidpointRounding.AwayFromZero);
}

private static void UpdateHorseDamagePostfix(ref object attackInformation, ref AttackCollisionData attackCollisionData, ref CombatLogData combatLog) {
if (!(attackCollisionData.IsHorseCharge && HeroHasPerk(AttackerAgentCharacter!(attackInformation), ActivePatch.Perk))) {
private static unsafe void UpdateHorseDamagePostfix(ref byte attackInformation, ref AttackCollisionData attackCollisionData, ref CombatLogData combatLog) {
var p = (IntPtr)Unsafe.AsPointer(ref attackInformation);
if (!(attackCollisionData.IsHorseCharge && HeroHasPerk(AttackerAgentCharacter!(p), ActivePatch.Perk))) {
return;
}

Expand Down
Loading

0 comments on commit f226e0e

Please sign in to comment.