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;
+ }
+}