From a1f6164ca0a6b242853343075955e073aafd2a86 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] 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; + } +}