From 01e2f4dc71fe51772e596bd786512618c9a6bfbc Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Thu, 15 Jan 2026 15:03:33 -0500
Subject: [PATCH 1/4] add CompilerGeneratedObjectWrapper.cs and
StateMachineWrapper.cs
---
.../CompilerGeneratedObjectWrapper.cs | 83 ++++++++++++++
Reactor/Utilities/StateMachineWrapper.cs | 104 ++++++++++++++++++
2 files changed, 187 insertions(+)
create mode 100644 Reactor/Utilities/CompilerGeneratedObjectWrapper.cs
create mode 100644 Reactor/Utilities/StateMachineWrapper.cs
diff --git a/Reactor/Utilities/CompilerGeneratedObjectWrapper.cs b/Reactor/Utilities/CompilerGeneratedObjectWrapper.cs
new file mode 100644
index 0000000..000166d
--- /dev/null
+++ b/Reactor/Utilities/CompilerGeneratedObjectWrapper.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using HarmonyLib;
+
+namespace Reactor.Utilities;
+
+///
+/// A safe wrapper around compiler generated classes like DisplayClass or IEnumerator state machines.
+///
+public class CompilerGeneratedObjectWrapper
+{
+ ///
+ /// Gets a reference to the compiler generated object.
+ ///
+ public object GeneratedObject { get; }
+
+ ///
+ /// Gets the type of the compiler generated object.
+ ///
+ protected Type GeneratedType { get; }
+
+ ///
+ /// Gets the property info cache for faster lookups.
+ ///
+ protected Dictionary PropertyCache { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// An instance of the compiler generated object.
+ public CompilerGeneratedObjectWrapper(object generatedObject)
+ {
+ GeneratedObject = generatedObject;
+ GeneratedType = generatedObject.GetType();
+
+ PropertyCache = [];
+ }
+
+ ///
+ /// Gets the value of a field in the compiler generated object.
+ ///
+ /// The name of the field to get.
+ /// The type of the field.
+ /// >The value of the field.
+ /// Thrown if the field does not exist.
+ public TField GetField(string fieldName)
+ {
+ if (!PropertyCache.TryGetValue(fieldName, out var propertyInfo))
+ {
+ propertyInfo = AccessTools.Property(GeneratedType, fieldName);
+ if (propertyInfo == null)
+ {
+ throw new MissingMemberException($"Could not find field '{fieldName}' in type '{GeneratedType}'.");
+ }
+ PropertyCache[fieldName] = propertyInfo;
+ }
+
+ return (TField) propertyInfo.GetValue(GeneratedObject)!;
+ }
+
+ ///
+ /// Sets the value of a field in the compiler generated object.
+ ///
+ /// The name of the field to set.
+ /// The value to set.
+ /// The type of the field.
+ /// Thrown if the field does not exist.
+ public void SetField(string fieldName, TField value)
+ {
+ if (!PropertyCache.TryGetValue(fieldName, out var propertyInfo))
+ {
+ propertyInfo = AccessTools.Property(GeneratedType, fieldName);
+ if (propertyInfo == null)
+ {
+ throw new MissingMemberException($"Could not find field '{fieldName}' in type '{GeneratedType}'.");
+ }
+ PropertyCache[fieldName] = propertyInfo;
+ }
+
+ propertyInfo.SetValue(GeneratedObject, value);
+ }
+}
diff --git a/Reactor/Utilities/StateMachineWrapper.cs b/Reactor/Utilities/StateMachineWrapper.cs
new file mode 100644
index 0000000..f67617b
--- /dev/null
+++ b/Reactor/Utilities/StateMachineWrapper.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using HarmonyLib;
+
+namespace Reactor.Utilities;
+
+///
+/// A wrapper for state machine objects to access their parent instance and state.
+///
+/// The type of the parent class that owns the state machine.
+public class StateMachineWrapper : CompilerGeneratedObjectWrapper
+{
+ // normally it is fields, but IL2CPP turns them into properties
+ private readonly PropertyInfo _thisProperty;
+ private readonly PropertyInfo _stateProperty;
+
+ private T? _parentInstance;
+
+ ///
+ /// Gets the instance of the parent class that owns the state machine.
+ ///
+ public T Instance => _parentInstance ??= (T) _thisProperty.GetValue(GeneratedObject)!;
+
+ ///
+ /// Gets or sets the current state of the state machine.
+ ///
+ /// The current state as an integer.
+ public int State
+ {
+ get => (int) _stateProperty.GetValue(GeneratedObject)!;
+ set => _stateProperty.SetValue(GeneratedObject, value);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The state machine instance to wrap.
+ public StateMachineWrapper(object stateMachine) : base(stateMachine)
+ {
+ _thisProperty = AccessTools.Property(GeneratedType, "__4__this");
+ _stateProperty = AccessTools.Property(GeneratedType, "__1__state");
+
+ if (_thisProperty == null || _stateProperty == null)
+ {
+ throw new MissingMemberException($"Could not find required properties in type '{GeneratedType}'.");
+ }
+ }
+
+ ///
+ /// Gets a parameter from the state machine by its name.
+ ///
+ /// The name of the parameter to retrieve.
+ /// The type of the parameter to retrieve.
+ /// >The value of the specified parameter.
+ /// Thrown if the specified parameter does not exist.
+ public TField GetParameter(string parameterName)
+ {
+ return GetField(parameterName);
+ }
+
+ ///
+ /// Sets a parameter in the state machine by its name.
+ ///
+ /// The name of the parameter to set.
+ /// The value to set for the parameter.
+ /// The type of the parameter to set.
+ /// Thrown if the specified parameter does not exist.
+ public void SetParameter(string parameterName, TField value)
+ {
+ SetField(parameterName, value);
+ }
+
+ ///
+ /// Attempts to retrieve the MoveNext method of a state machine for the specified method name in type T.
+ ///
+ /// The name of the method whose state machine MoveNext method is to be retrieved.
+ /// The type containing the state machine.
+ /// The MoveNext if found; otherwise, null.
+ public static MethodBase? GetStateMachineMoveNext(string methodName)
+ {
+ var typeName = typeof(T).FullName;
+ var showRoleStateMachine =
+ typeof(T)
+ .GetNestedTypes()
+ .FirstOrDefault(x => x.Name.Contains(methodName));
+
+ if (showRoleStateMachine == null)
+ {
+ Error($"Failed to find {methodName} state machine for {typeName}");
+ return null;
+ }
+
+ var moveNext = AccessTools.Method(showRoleStateMachine, "MoveNext");
+ if (moveNext == null)
+ {
+ Error($"Failed to find MoveNext method for {typeName}.{methodName}");
+ return null;
+ }
+
+ Info($"Found {methodName}.MoveNext");
+ return moveNext;
+ }
+}
From a5956eb59ce5e36c579475ffdc7042ccd97cd27e Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Thu, 15 Jan 2026 15:08:11 -0500
Subject: [PATCH 2/4] Use compiler generated wrappers for patching
---
Reactor/GlobalUsings.cs | 1 -
Reactor/Networking/Patches/ClientPatches.cs | 58 ++++++++++++-----
.../Networking/Patches/ReactorConnection.cs | 23 ++++---
Reactor/Patches/Fixes/CoFindGamePatch.cs | 9 ++-
.../Miscellaneous/CustomServersPatch.cs | 64 +++++++++++++------
5 files changed, 110 insertions(+), 45 deletions(-)
delete mode 100644 Reactor/GlobalUsings.cs
diff --git a/Reactor/GlobalUsings.cs b/Reactor/GlobalUsings.cs
deleted file mode 100644
index bb00b4a..0000000
--- a/Reactor/GlobalUsings.cs
+++ /dev/null
@@ -1 +0,0 @@
-global using AmongUsClient_CoFindGame = AmongUsClient._CoFindGame_d__54;
diff --git a/Reactor/Networking/Patches/ClientPatches.cs b/Reactor/Networking/Patches/ClientPatches.cs
index 333c440..1c8dbaf 100644
--- a/Reactor/Networking/Patches/ClientPatches.cs
+++ b/Reactor/Networking/Patches/ClientPatches.cs
@@ -1,15 +1,18 @@
using System;
using System.Linq;
+using System.Reflection;
using AmongUs.Data;
using AmongUs.InnerNet.GameDataMessages;
using BepInEx.Unity.IL2CPP.Utils;
using HarmonyLib;
using Hazel;
using Il2CppInterop.Runtime;
+using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using InnerNet;
using Reactor.Networking.Extensions;
using Reactor.Networking.Messages;
+using Reactor.Utilities;
using UnityEngine;
using IEnumerator = System.Collections.IEnumerator;
@@ -30,15 +33,22 @@ public static void Prefix(InnerNetClient __instance, ref DisconnectReasons reaso
}
}
- [HarmonyPatch(typeof(InnerNetClient._HandleGameDataInner_d__165), nameof(InnerNetClient._HandleGameDataInner_d__165.MoveNext))]
+ [HarmonyPatch]
public static class HandleGameDataInnerPatch
{
- public static bool Prefix(InnerNetClient._HandleGameDataInner_d__165 __instance, ref bool __result)
+ public static MethodBase TargetMethod()
{
- var innerNetClient = __instance.__4__this;
- var reader = __instance.reader;
+ return StateMachineWrapper.GetStateMachineMoveNext(nameof(InnerNetClient.HandleGameDataInner))!;
+ }
+
+ public static bool Prefix(Il2CppObjectBase __instance, ref bool __result)
+ {
+ var wrapper = new StateMachineWrapper(__instance);
+
+ var innerNetClient = wrapper.Instance;
+ var reader = wrapper.GetParameter("reader");
- if (__instance.__1__state != 0) return true;
+ if (wrapper.State != 0) return true;
if (reader.Tag == byte.MaxValue)
{
@@ -162,14 +172,22 @@ IEnumerator CoKick()
}
}
- [HarmonyPatch(typeof(InnerNetClient._CoSendSceneChange_d__156), nameof(InnerNetClient._CoSendSceneChange_d__156.MoveNext))]
+ [HarmonyPatch]
public static class CoSendSceneChangePatch
{
- public static bool Prefix(InnerNetClient._CoSendSceneChange_d__156 __instance, ref bool __result)
+ public static MethodBase TargetMethod()
+ {
+ return StateMachineWrapper.GetStateMachineMoveNext(nameof(InnerNetClient.CoSendSceneChange))!;
+ }
+
+ public static bool Prefix(Il2CppObjectBase __instance, ref bool __result)
{
+ var wrapper = new StateMachineWrapper(__instance);
+
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return true;
- var innerNetClient = __instance.__4__this;
+ var innerNetClient = wrapper.Instance;
+ var sceneName = wrapper.GetParameter("sceneName");
// Check for the conditions when the scene change message should be sent
if (!innerNetClient.AmHost &&
@@ -184,7 +202,7 @@ public static bool Prefix(InnerNetClient._CoSendSceneChange_d__156 __instance, r
writer.Write(innerNetClient.GameId);
writer.StartMessage((byte) GameDataTypes.SceneChangeFlag);
writer.WritePacked(innerNetClient.ClientId);
- writer.Write(__instance.sceneName);
+ writer.Write(sceneName);
// PATCH - Inject ReactorHandshakeC2S
Debug("Injecting ReactorHandshakeC2S to CoSendSceneChange");
@@ -197,10 +215,10 @@ public static bool Prefix(InnerNetClient._CoSendSceneChange_d__156 __instance, r
writer.Recycle();
// Create a new coroutine to let AmongUsClient handle scene changes too
- innerNetClient.StartCoroutine(innerNetClient.CoOnPlayerChangedScene(clientData, __instance.sceneName));
+ innerNetClient.StartCoroutine(innerNetClient.CoOnPlayerChangedScene(clientData, sceneName));
// Cancel this coroutine
- __instance.__1__state = -1;
+ wrapper.State = -1;
__result = false;
return false;
}
@@ -210,16 +228,24 @@ public static bool Prefix(InnerNetClient._CoSendSceneChange_d__156 __instance, r
}
}
- [HarmonyPatch(typeof(InnerNetClient._CoHandleSpawn_d__166), nameof(InnerNetClient._CoHandleSpawn_d__166.MoveNext))]
+ [HarmonyPatch]
public static class CoHandleSpawnPatch
{
- public static void Postfix(InnerNetClient._CoHandleSpawn_d__166 __instance, bool __result)
+ public static MethodBase TargetMethod()
+ {
+ return StateMachineWrapper.GetStateMachineMoveNext(nameof(InnerNetClient.CoHandleSpawn))!;
+ }
+
+ public static void Postfix(Il2CppObjectBase __instance, bool __result)
{
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return;
- if (!__result && !AmongUsClient.Instance.AmHost && __instance._ownerId_5__2 == AmongUsClient.Instance.ClientId)
+ var wrapper = new StateMachineWrapper(__instance);
+ var ownerId = wrapper.GetParameter("ownerId");
+
+ if (!__result && !AmongUsClient.Instance.AmHost && ownerId == AmongUsClient.Instance.ClientId)
{
- var reader = __instance.reader;
+ var reader = wrapper.GetParameter("reader");
if (reader.BytesRemaining >= ReactorHeader.Size && ReactorHeader.Read(reader))
{
ModdedHandshakeS2C.Deserialize(reader, out var serverName, out var serverVersion, out _);
@@ -228,7 +254,7 @@ public static void Postfix(InnerNetClient._CoHandleSpawn_d__166 __instance, bool
else
{
Debug("Host is not modded");
- if (!Mod.Validate(ModList.Current, Array.Empty(), out var reason))
+ if (!Mod.Validate(ModList.Current, [], out var reason))
{
AmongUsClient.Instance.DisconnectWithReason(reason);
}
diff --git a/Reactor/Networking/Patches/ReactorConnection.cs b/Reactor/Networking/Patches/ReactorConnection.cs
index ffbcd5e..5ec921e 100644
--- a/Reactor/Networking/Patches/ReactorConnection.cs
+++ b/Reactor/Networking/Patches/ReactorConnection.cs
@@ -1,5 +1,7 @@
+using System.Reflection;
using HarmonyLib;
using InnerNet;
+using Reactor.Utilities;
namespace Reactor.Networking.Patches;
@@ -20,13 +22,16 @@ public class ReactorConnection
///
public static ReactorConnection? Instance { get; private set; }
+ // CoConnect(string) was inlined, so we patch the MoveNext method instead.
[HarmonyPatch]
- private static class Patches
+ internal static class CoConnectPatch
{
- // CoConnect(string) was inlined, so we patch the MoveNext method instead.
- [HarmonyPatch(typeof(InnerNetClient._CoConnect_d__65), nameof(InnerNetClient._CoConnect_d__65.MoveNext))]
- [HarmonyPrefix]
- public static void CoConnect()
+ public static MethodBase TargetMethod()
+ {
+ return StateMachineWrapper.GetStateMachineMoveNext(nameof(InnerNetClient.CoConnect))!;
+ }
+
+ public static void Prefix()
{
if (Instance == null)
{
@@ -34,10 +39,12 @@ public static void CoConnect()
Instance = new ReactorConnection();
}
}
+ }
- [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))]
- [HarmonyPostfix]
- public static void DisconnectInternalPostfix()
+ [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))]
+ internal static class InnerNetClientDisconnectPatch
+ {
+ public static void Postfix()
{
Debug("ReactorConnection disconnected");
Instance = null;
diff --git a/Reactor/Patches/Fixes/CoFindGamePatch.cs b/Reactor/Patches/Fixes/CoFindGamePatch.cs
index 46b97e4..58ed2af 100644
--- a/Reactor/Patches/Fixes/CoFindGamePatch.cs
+++ b/Reactor/Patches/Fixes/CoFindGamePatch.cs
@@ -1,13 +1,20 @@
+using System.Reflection;
using HarmonyLib;
+using Reactor.Utilities;
namespace Reactor.Patches.Fixes;
///
/// Fixes Game Lists not working on servers using legacy matchmaking.
///
-[HarmonyPatch(typeof(AmongUsClient_CoFindGame), nameof(AmongUsClient_CoFindGame.MoveNext))]
+[HarmonyPatch]
internal static class CoFindGamePatch
{
+ public static MethodBase TargetMethod()
+ {
+ return StateMachineWrapper.GetStateMachineMoveNext(nameof(AmongUsClient.CoFindGame))!;
+ }
+
public static void Prefix()
{
if (AmongUsClient.Instance.LastDisconnectReason == DisconnectReasons.Unknown)
diff --git a/Reactor/Patches/Miscellaneous/CustomServersPatch.cs b/Reactor/Patches/Miscellaneous/CustomServersPatch.cs
index 3e10387..0f328dc 100644
--- a/Reactor/Patches/Miscellaneous/CustomServersPatch.cs
+++ b/Reactor/Patches/Miscellaneous/CustomServersPatch.cs
@@ -1,10 +1,13 @@
using System;
+using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using HarmonyLib;
+using Il2CppInterop.Runtime.InteropTypes;
+using Reactor.Utilities;
namespace Reactor.Patches.Miscellaneous;
-[HarmonyPatch]
internal static class CustomServersPatch
{
private static bool IsCurrentServerOfficial()
@@ -16,32 +19,55 @@ private static bool IsCurrentServerOfficial()
regionInfo.Servers.All(serverInfo => serverInfo.Ip.EndsWith(Domain, StringComparison.Ordinal));
}
- [HarmonyPatch(typeof(AuthManager._CoConnect_d__4), nameof(AuthManager._CoConnect_d__4.MoveNext))]
- [HarmonyPatch(typeof(AuthManager._CoWaitForNonce_d__6), nameof(AuthManager._CoWaitForNonce_d__6.MoveNext))]
- [HarmonyPrefix]
- public static bool DisableAuthServer(ref bool __result)
+ [HarmonyPatch]
+ public static class DisableAuthServerPatch
{
- if (IsCurrentServerOfficial())
+ public static IEnumerable TargetMethods() =>
+ [
+ StateMachineWrapper.GetStateMachineMoveNext(nameof(AuthManager.CoConnect))!,
+ StateMachineWrapper.GetStateMachineMoveNext(nameof(AuthManager.CoWaitForNonce))!
+ ];
+
+ public static bool Prefix(ref bool __result)
{
- return true;
- }
+ if (IsCurrentServerOfficial())
+ {
+ return true;
+ }
- __result = false;
- return false;
+ __result = false;
+ return false;
+ }
}
- [HarmonyPatch(typeof(AmongUsClient._CoJoinOnlinePublicGame_d__49), nameof(AmongUsClient._CoJoinOnlinePublicGame_d__49.MoveNext))]
- [HarmonyPrefix]
- public static void EnableUdpMatchmaking(AmongUsClient._CoJoinOnlinePublicGame_d__49 __instance)
+ [HarmonyPatch]
+ public static class EnableUdpPatch
{
- // Skip to state 1 which just calls CoJoinOnlineGameDirect
- if (__instance.__1__state == 0 && !ServerManager.Instance.IsHttp)
+ public static MethodBase TargetMethod()
+ {
+ return StateMachineWrapper.GetStateMachineMoveNext(nameof(AmongUsClient.CoJoinOnlinePublicGame))!;
+ }
+
+ public static void Prefix(Il2CppObjectBase __instance)
{
- __instance.__1__state = 1;
- __instance.__8__1 = new AmongUsClient.__c__DisplayClass49_0
+ var stateMachine = new StateMachineWrapper(__instance);
+
+ // Skip to state 1 which just calls CoJoinOnlineGameDirect
+ if (stateMachine.State == 0 && !ServerManager.Instance.IsHttp)
{
- matchmakerToken = string.Empty,
- };
+ stateMachine.State = 1;
+ var lambdaType = stateMachine.GetParameter("__8__1").GetType();
+ var newDisplayClass = Activator.CreateInstance(lambdaType);
+ if (newDisplayClass == null)
+ {
+ throw new InvalidOperationException($"Could not create display class of type '{lambdaType}'.");
+ }
+
+ var displayClass = new CompilerGeneratedObjectWrapper(newDisplayClass);
+ displayClass.SetField("matchmakerToken", string.Empty);
+
+ stateMachine.SetParameter("__8__1", newDisplayClass);
+ }
}
}
}
From 71983a9acc5b03dc08a083acbe3cc56114f41f9c Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Thu, 15 Jan 2026 15:31:32 -0500
Subject: [PATCH 3/4] Fix CoHandleSpawnPatch
---
Reactor/Networking/Patches/ClientPatches.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Reactor/Networking/Patches/ClientPatches.cs b/Reactor/Networking/Patches/ClientPatches.cs
index 1c8dbaf..c03e805 100644
--- a/Reactor/Networking/Patches/ClientPatches.cs
+++ b/Reactor/Networking/Patches/ClientPatches.cs
@@ -241,7 +241,7 @@ public static void Postfix(Il2CppObjectBase __instance, bool __result)
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return;
var wrapper = new StateMachineWrapper(__instance);
- var ownerId = wrapper.GetParameter("ownerId");
+ var ownerId = wrapper.GetParameter("_ownerId_5__2");
if (!__result && !AmongUsClient.Instance.AmHost && ownerId == AmongUsClient.Instance.ClientId)
{
From 44cb0661f144b7ad970067c3b3fdb96899f61151 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Wed, 21 Jan 2026 08:50:06 -0500
Subject: [PATCH 4/4] Use typed delegates for faster invoke
---
.../CompilerGeneratedObjectWrapper.cs | 61 +++++++++++++++----
1 file changed, 49 insertions(+), 12 deletions(-)
diff --git a/Reactor/Utilities/CompilerGeneratedObjectWrapper.cs b/Reactor/Utilities/CompilerGeneratedObjectWrapper.cs
index 000166d..99e3610 100644
--- a/Reactor/Utilities/CompilerGeneratedObjectWrapper.cs
+++ b/Reactor/Utilities/CompilerGeneratedObjectWrapper.cs
@@ -25,6 +25,16 @@ public class CompilerGeneratedObjectWrapper
///
protected Dictionary PropertyCache { get; }
+ ///
+ /// Gets the getter cache for faster property access.
+ ///
+ protected Dictionary GetterCache { get; }
+
+ ///
+ /// Gets the setter cache for faster property access.
+ ///
+ protected Dictionary SetterCache { get; }
+
///
/// Initializes a new instance of the class.
///
@@ -35,6 +45,31 @@ public CompilerGeneratedObjectWrapper(object generatedObject)
GeneratedType = generatedObject.GetType();
PropertyCache = [];
+ GetterCache = [];
+ SetterCache = [];
+ }
+
+ public PropertyInfo CacheProperty(string fieldName)
+ {
+ var propertyInfo = AccessTools.Property(GeneratedType, fieldName)
+ ?? throw new MissingMemberException(
+ $"Could not find field '{fieldName}' in type '{GeneratedType}'.");
+
+ if (propertyInfo.PropertyType != typeof(T))
+ {
+ throw new InvalidCastException(
+ $"Field '{fieldName}' is of type '{propertyInfo.PropertyType}', not '{typeof(T)}'.");
+ }
+
+ PropertyCache[fieldName] = propertyInfo;
+
+ var funcType = typeof(Func);
+ GetterCache[fieldName] = propertyInfo.GetMethod!.CreateDelegate(funcType, GeneratedObject);
+
+ var actionType = typeof(Action);
+ SetterCache[fieldName] = propertyInfo.SetMethod!.CreateDelegate(actionType, GeneratedObject);
+
+ return propertyInfo;
}
///
@@ -48,12 +83,12 @@ public TField GetField(string fieldName)
{
if (!PropertyCache.TryGetValue(fieldName, out var propertyInfo))
{
- propertyInfo = AccessTools.Property(GeneratedType, fieldName);
- if (propertyInfo == null)
- {
- throw new MissingMemberException($"Could not find field '{fieldName}' in type '{GeneratedType}'.");
- }
- PropertyCache[fieldName] = propertyInfo;
+ propertyInfo = CacheProperty(fieldName);
+ }
+
+ if (GetterCache.TryGetValue(fieldName, out var getter))
+ {
+ return ((Func) getter)();
}
return (TField) propertyInfo.GetValue(GeneratedObject)!;
@@ -70,14 +105,16 @@ public void SetField(string fieldName, TField value)
{
if (!PropertyCache.TryGetValue(fieldName, out var propertyInfo))
{
- propertyInfo = AccessTools.Property(GeneratedType, fieldName);
- if (propertyInfo == null)
- {
- throw new MissingMemberException($"Could not find field '{fieldName}' in type '{GeneratedType}'.");
- }
- PropertyCache[fieldName] = propertyInfo;
+ propertyInfo = CacheProperty(fieldName);
+ }
+
+ if (SetterCache.TryGetValue(fieldName, out var setter))
+ {
+ ((Action) setter)(value);
+ return;
}
propertyInfo.SetValue(GeneratedObject, value);
}
}
+