mirror of
https://github.com/NuclearPowered/Reactor.git
synced 2026-10-20 01:46:21 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04100970b6 | ||
|
|
16c8c3e553 | ||
|
|
c3775ff33f | ||
|
|
6381d79a18 | ||
|
|
5818041b27 | ||
|
|
a1f6164ca0 |
No files matched your search
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
|
|
||||||
<VersionPrefix>2.5.0</VersionPrefix>
|
<VersionPrefix>2.5.1</VersionPrefix>
|
||||||
<VersionSuffix>dev</VersionSuffix>
|
<VersionSuffix>dev</VersionSuffix>
|
||||||
|
|
||||||
<Authors>NuclearPowered</Authors>
|
<Authors>NuclearPowered</Authors>
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
global using AmongUsClient_CoFindGame = AmongUsClient._CoFindGame_d__54;
|
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using AmongUs.Data;
|
using AmongUs.Data;
|
||||||
using AmongUs.InnerNet.GameDataMessages;
|
using AmongUs.InnerNet.GameDataMessages;
|
||||||
using BepInEx.Unity.IL2CPP.Utils;
|
using BepInEx.Unity.IL2CPP.Utils;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using Hazel;
|
using Hazel;
|
||||||
using Il2CppInterop.Runtime;
|
using Il2CppInterop.Runtime;
|
||||||
|
using Il2CppInterop.Runtime.InteropTypes;
|
||||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||||
using InnerNet;
|
using InnerNet;
|
||||||
using Reactor.Networking.Extensions;
|
using Reactor.Networking.Extensions;
|
||||||
using Reactor.Networking.Messages;
|
using Reactor.Networking.Messages;
|
||||||
|
using Reactor.Utilities;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using IEnumerator = System.Collections.IEnumerator;
|
using IEnumerator = System.Collections.IEnumerator;
|
||||||
|
|
||||||
@@ -30,15 +33,22 @@ internal static class ClientPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPatch(typeof(InnerNetClient._HandleGameDataInner_d__165), nameof(InnerNetClient._HandleGameDataInner_d__165.MoveNext))]
|
[HarmonyPatch]
|
||||||
public static class HandleGameDataInnerPatch
|
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;
|
return Il2CppStateMachineWrapper<InnerNetClient>.GetStateMachineMoveNext(nameof(InnerNetClient.HandleGameDataInner))!;
|
||||||
var reader = __instance.reader;
|
}
|
||||||
|
|
||||||
if (__instance.__1__state != 0) return true;
|
public static bool Prefix(Il2CppObjectBase __instance, ref bool __result)
|
||||||
|
{
|
||||||
|
var wrapper = new Il2CppStateMachineWrapper<InnerNetClient>(__instance);
|
||||||
|
|
||||||
|
var innerNetClient = wrapper.Instance;
|
||||||
|
var reader = wrapper.GetParameter<MessageReader>("reader");
|
||||||
|
|
||||||
|
if (wrapper.State != 0) return true;
|
||||||
|
|
||||||
if (reader.Tag == byte.MaxValue)
|
if (reader.Tag == byte.MaxValue)
|
||||||
{
|
{
|
||||||
@@ -162,14 +172,22 @@ internal static class ClientPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPatch(typeof(InnerNetClient._CoSendSceneChange_d__156), nameof(InnerNetClient._CoSendSceneChange_d__156.MoveNext))]
|
[HarmonyPatch]
|
||||||
public static class CoSendSceneChangePatch
|
public static class CoSendSceneChangePatch
|
||||||
{
|
{
|
||||||
public static bool Prefix(InnerNetClient._CoSendSceneChange_d__156 __instance, ref bool __result)
|
public static MethodBase TargetMethod()
|
||||||
{
|
{
|
||||||
|
return Il2CppStateMachineWrapper<InnerNetClient>.GetStateMachineMoveNext(nameof(InnerNetClient.CoSendSceneChange))!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Prefix(Il2CppObjectBase __instance, ref bool __result)
|
||||||
|
{
|
||||||
|
var wrapper = new Il2CppStateMachineWrapper<InnerNetClient>(__instance);
|
||||||
|
|
||||||
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return true;
|
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return true;
|
||||||
|
|
||||||
var innerNetClient = __instance.__4__this;
|
var innerNetClient = wrapper.Instance;
|
||||||
|
var sceneName = wrapper.GetParameter<string>("sceneName");
|
||||||
|
|
||||||
// Check for the conditions when the scene change message should be sent
|
// Check for the conditions when the scene change message should be sent
|
||||||
if (!innerNetClient.AmHost &&
|
if (!innerNetClient.AmHost &&
|
||||||
@@ -184,7 +202,7 @@ internal static class ClientPatches
|
|||||||
writer.Write(innerNetClient.GameId);
|
writer.Write(innerNetClient.GameId);
|
||||||
writer.StartMessage((byte) GameDataTypes.SceneChangeFlag);
|
writer.StartMessage((byte) GameDataTypes.SceneChangeFlag);
|
||||||
writer.WritePacked(innerNetClient.ClientId);
|
writer.WritePacked(innerNetClient.ClientId);
|
||||||
writer.Write(__instance.sceneName);
|
writer.Write(sceneName);
|
||||||
|
|
||||||
// PATCH - Inject ReactorHandshakeC2S
|
// PATCH - Inject ReactorHandshakeC2S
|
||||||
Debug("Injecting ReactorHandshakeC2S to CoSendSceneChange");
|
Debug("Injecting ReactorHandshakeC2S to CoSendSceneChange");
|
||||||
@@ -197,10 +215,10 @@ internal static class ClientPatches
|
|||||||
writer.Recycle();
|
writer.Recycle();
|
||||||
|
|
||||||
// Create a new coroutine to let AmongUsClient handle scene changes too
|
// 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
|
// Cancel this coroutine
|
||||||
__instance.__1__state = -1;
|
wrapper.State = -1;
|
||||||
__result = false;
|
__result = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -210,16 +228,24 @@ internal static class ClientPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPatch(typeof(InnerNetClient._CoHandleSpawn_d__166), nameof(InnerNetClient._CoHandleSpawn_d__166.MoveNext))]
|
[HarmonyPatch]
|
||||||
public static class CoHandleSpawnPatch
|
public static class CoHandleSpawnPatch
|
||||||
{
|
{
|
||||||
public static void Postfix(InnerNetClient._CoHandleSpawn_d__166 __instance, bool __result)
|
public static MethodBase TargetMethod()
|
||||||
|
{
|
||||||
|
return Il2CppStateMachineWrapper<InnerNetClient>.GetStateMachineMoveNext(nameof(InnerNetClient.CoHandleSpawn))!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Postfix(Il2CppObjectBase __instance, bool __result)
|
||||||
{
|
{
|
||||||
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return;
|
if (ReactorConnection.Instance!.Syncer != Syncer.Host) return;
|
||||||
|
|
||||||
if (!__result && !AmongUsClient.Instance.AmHost && __instance._ownerId_5__2 == AmongUsClient.Instance.ClientId)
|
var wrapper = new Il2CppStateMachineWrapper<InnerNetClient>(__instance);
|
||||||
|
var ownerId = wrapper.GetParameter<int>("_ownerId_5__2");
|
||||||
|
|
||||||
|
if (!__result && !AmongUsClient.Instance.AmHost && ownerId == AmongUsClient.Instance.ClientId)
|
||||||
{
|
{
|
||||||
var reader = __instance.reader;
|
var reader = wrapper.GetParameter<MessageReader>("reader");
|
||||||
if (reader.BytesRemaining >= ReactorHeader.Size && ReactorHeader.Read(reader))
|
if (reader.BytesRemaining >= ReactorHeader.Size && ReactorHeader.Read(reader))
|
||||||
{
|
{
|
||||||
ModdedHandshakeS2C.Deserialize(reader, out var serverName, out var serverVersion, out _);
|
ModdedHandshakeS2C.Deserialize(reader, out var serverName, out var serverVersion, out _);
|
||||||
@@ -228,7 +254,7 @@ internal static class ClientPatches
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug("Host is not modded");
|
Debug("Host is not modded");
|
||||||
if (!Mod.Validate(ModList.Current, Array.Empty<Mod>(), out var reason))
|
if (!Mod.Validate(ModList.Current, [], out var reason))
|
||||||
{
|
{
|
||||||
AmongUsClient.Instance.DisconnectWithReason(reason);
|
AmongUsClient.Instance.DisconnectWithReason(reason);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using System.Reflection;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using InnerNet;
|
using InnerNet;
|
||||||
|
using Reactor.Utilities;
|
||||||
|
|
||||||
namespace Reactor.Networking.Patches;
|
namespace Reactor.Networking.Patches;
|
||||||
|
|
||||||
@@ -20,13 +22,16 @@ public class ReactorConnection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static ReactorConnection? Instance { get; private set; }
|
public static ReactorConnection? Instance { get; private set; }
|
||||||
|
|
||||||
|
// CoConnect(string) was inlined, so we patch the MoveNext method instead.
|
||||||
[HarmonyPatch]
|
[HarmonyPatch]
|
||||||
private static class Patches
|
internal static class CoConnectPatch
|
||||||
{
|
{
|
||||||
// CoConnect(string) was inlined, so we patch the MoveNext method instead.
|
public static MethodBase TargetMethod()
|
||||||
[HarmonyPatch(typeof(InnerNetClient._CoConnect_d__65), nameof(InnerNetClient._CoConnect_d__65.MoveNext))]
|
{
|
||||||
[HarmonyPrefix]
|
return Il2CppStateMachineWrapper<InnerNetClient>.GetStateMachineMoveNext(nameof(InnerNetClient.CoConnect))!;
|
||||||
public static void CoConnect()
|
}
|
||||||
|
|
||||||
|
public static void Prefix()
|
||||||
{
|
{
|
||||||
if (Instance == null)
|
if (Instance == null)
|
||||||
{
|
{
|
||||||
@@ -34,10 +39,12 @@ public class ReactorConnection
|
|||||||
Instance = new ReactorConnection();
|
Instance = new ReactorConnection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))]
|
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))]
|
||||||
[HarmonyPostfix]
|
internal static class InnerNetClientDisconnectPatch
|
||||||
public static void DisconnectInternalPostfix()
|
{
|
||||||
|
public static void Postfix()
|
||||||
{
|
{
|
||||||
Debug("ReactorConnection disconnected");
|
Debug("ReactorConnection disconnected");
|
||||||
Instance = null;
|
Instance = null;
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
|
using System.Reflection;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using Reactor.Utilities;
|
||||||
|
|
||||||
namespace Reactor.Patches.Fixes;
|
namespace Reactor.Patches.Fixes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fixes Game Lists not working on servers using legacy matchmaking.
|
/// Fixes Game Lists not working on servers using legacy matchmaking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HarmonyPatch(typeof(AmongUsClient_CoFindGame), nameof(AmongUsClient_CoFindGame.MoveNext))]
|
[HarmonyPatch]
|
||||||
internal static class CoFindGamePatch
|
internal static class CoFindGamePatch
|
||||||
{
|
{
|
||||||
|
public static MethodBase TargetMethod()
|
||||||
|
{
|
||||||
|
return Il2CppStateMachineWrapper<AmongUsClient>.GetStateMachineMoveNext(nameof(AmongUsClient.CoFindGame))!;
|
||||||
|
}
|
||||||
|
|
||||||
public static void Prefix()
|
public static void Prefix()
|
||||||
{
|
{
|
||||||
if (AmongUsClient.Instance.LastDisconnectReason == DisconnectReasons.Unknown)
|
if (AmongUsClient.Instance.LastDisconnectReason == DisconnectReasons.Unknown)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using Il2CppInterop.Runtime.InteropTypes;
|
||||||
|
using Reactor.Utilities;
|
||||||
|
|
||||||
namespace Reactor.Patches.Miscellaneous;
|
namespace Reactor.Patches.Miscellaneous;
|
||||||
|
|
||||||
[HarmonyPatch]
|
|
||||||
internal static class CustomServersPatch
|
internal static class CustomServersPatch
|
||||||
{
|
{
|
||||||
private static bool IsCurrentServerOfficial()
|
private static bool IsCurrentServerOfficial()
|
||||||
@@ -16,32 +19,55 @@ internal static class CustomServersPatch
|
|||||||
regionInfo.Servers.All(serverInfo => serverInfo.Ip.EndsWith(Domain, StringComparison.Ordinal));
|
regionInfo.Servers.All(serverInfo => serverInfo.Ip.EndsWith(Domain, StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPatch(typeof(AuthManager._CoConnect_d__4), nameof(AuthManager._CoConnect_d__4.MoveNext))]
|
[HarmonyPatch]
|
||||||
[HarmonyPatch(typeof(AuthManager._CoWaitForNonce_d__6), nameof(AuthManager._CoWaitForNonce_d__6.MoveNext))]
|
public static class DisableAuthServerPatch
|
||||||
[HarmonyPrefix]
|
|
||||||
public static bool DisableAuthServer(ref bool __result)
|
|
||||||
{
|
{
|
||||||
if (IsCurrentServerOfficial())
|
public static IEnumerable<MethodBase> TargetMethods() =>
|
||||||
{
|
[
|
||||||
return true;
|
Il2CppStateMachineWrapper<AuthManager>.GetStateMachineMoveNext(nameof(AuthManager.CoConnect))!,
|
||||||
}
|
Il2CppStateMachineWrapper<AuthManager>.GetStateMachineMoveNext(nameof(AuthManager.CoWaitForNonce))!
|
||||||
|
];
|
||||||
|
|
||||||
__result = false;
|
public static bool Prefix(ref bool __result)
|
||||||
return false;
|
{
|
||||||
|
if (IsCurrentServerOfficial())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPatch(typeof(AmongUsClient._CoJoinOnlinePublicGame_d__49), nameof(AmongUsClient._CoJoinOnlinePublicGame_d__49.MoveNext))]
|
[HarmonyPatch]
|
||||||
[HarmonyPrefix]
|
public static class EnableUdpPatch
|
||||||
public static void EnableUdpMatchmaking(AmongUsClient._CoJoinOnlinePublicGame_d__49 __instance)
|
|
||||||
{
|
{
|
||||||
// Skip to state 1 which just calls CoJoinOnlineGameDirect
|
public static MethodBase TargetMethod()
|
||||||
if (__instance.__1__state == 0 && !ServerManager.Instance.IsHttp)
|
|
||||||
{
|
{
|
||||||
__instance.__1__state = 1;
|
return Il2CppStateMachineWrapper<AmongUsClient>.GetStateMachineMoveNext(nameof(AmongUsClient.CoJoinOnlinePublicGame))!;
|
||||||
__instance.__8__1 = new AmongUsClient.__c__DisplayClass49_0
|
}
|
||||||
|
|
||||||
|
public static void Prefix(Il2CppObjectBase __instance)
|
||||||
|
{
|
||||||
|
var stateMachine = new Il2CppStateMachineWrapper<AmongUsClient>(__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<Il2CppObjectBase>("__8__1").GetType();
|
||||||
|
var newDisplayClassObject = Activator.CreateInstance(lambdaType);
|
||||||
|
if (newDisplayClassObject == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Could not create display class of type '{lambdaType}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrappedDisplayClassObject = new Il2CppCompilerGeneratedObjectWrapper(newDisplayClassObject);
|
||||||
|
wrappedDisplayClassObject.SetField("matchmakerToken", string.Empty);
|
||||||
|
|
||||||
|
stateMachine.SetParameter("__8__1", newDisplayClassObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Reactor.Utilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A safe wrapper around IL2CPP compiler generated classes like DisplayClass or IEnumerator state machines.
|
||||||
|
/// This is useful for gracefully handling changes across different game versions and updates, which may affect
|
||||||
|
/// compiler generated names.
|
||||||
|
/// To use this class, pass the instance object of the compiler generated class into the constructor.
|
||||||
|
/// Then you can use the GetField and SetField methods to access the fields of the compiler generated class
|
||||||
|
/// by their original names.
|
||||||
|
/// </summary>
|
||||||
|
public class Il2CppCompilerGeneratedObjectWrapper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the compiler generated object.
|
||||||
|
/// </summary>
|
||||||
|
public object GeneratedObject { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the compiler generated object.
|
||||||
|
/// </summary>
|
||||||
|
protected Type GeneratedType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the property info cache for faster lookups.
|
||||||
|
/// </summary>
|
||||||
|
protected Dictionary<string, PropertyInfo> PropertyCache { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the getter cache for faster property access.
|
||||||
|
/// </summary>
|
||||||
|
protected Dictionary<string, Delegate> GetterCache { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the setter cache for faster property access.
|
||||||
|
/// </summary>
|
||||||
|
protected Dictionary<string, Delegate> SetterCache { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Il2CppCompilerGeneratedObjectWrapper"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generatedObject">An instance of the compiler generated object.</param>
|
||||||
|
public Il2CppCompilerGeneratedObjectWrapper(object generatedObject)
|
||||||
|
{
|
||||||
|
GeneratedObject = generatedObject;
|
||||||
|
GeneratedType = generatedObject.GetType();
|
||||||
|
|
||||||
|
PropertyCache = [];
|
||||||
|
GetterCache = [];
|
||||||
|
SetterCache = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caches the property info, getter, and setter for a given field name and type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldName">The name of the field to cache.</param>
|
||||||
|
/// <typeparam name="T">The expected type of the field.</typeparam>
|
||||||
|
/// <returns>The cached <see cref="PropertyInfo"/> for the specified field.</returns>
|
||||||
|
/// <exception cref="MissingMemberException">Thrown if the field does not exist in the compiler generated type.</exception>
|
||||||
|
/// <exception cref="InvalidCastException">Thrown if the field exists but is not of the expected type.</exception>
|
||||||
|
public PropertyInfo CacheProperty<T>(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<T>);
|
||||||
|
GetterCache[fieldName] = propertyInfo.GetMethod!.CreateDelegate(funcType, GeneratedObject);
|
||||||
|
|
||||||
|
var actionType = typeof(Action<T>);
|
||||||
|
SetterCache[fieldName] = propertyInfo.SetMethod!.CreateDelegate(actionType, GeneratedObject);
|
||||||
|
|
||||||
|
return propertyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of a field in the compiler generated object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldName">The name of the field to get.</param>
|
||||||
|
/// <typeparam name="TField">The type of the field.</typeparam>
|
||||||
|
/// <returns>>The value of the field.</returns>
|
||||||
|
/// <exception cref="MissingMemberException">Thrown if the field does not exist.</exception>
|
||||||
|
public TField GetField<TField>(string fieldName)
|
||||||
|
{
|
||||||
|
if (!PropertyCache.TryGetValue(fieldName, out var propertyInfo))
|
||||||
|
{
|
||||||
|
propertyInfo = CacheProperty<TField>(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetterCache.TryGetValue(fieldName, out var getter))
|
||||||
|
{
|
||||||
|
return ((Func<TField>) getter)();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TField) propertyInfo.GetValue(GeneratedObject)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the value of a field in the compiler generated object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldName">The name of the field to set.</param>
|
||||||
|
/// <param name="value">The value to set.</param>
|
||||||
|
/// <typeparam name="TField">The type of the field.</typeparam>
|
||||||
|
/// <exception cref="MissingMemberException">Thrown if the field does not exist.</exception>
|
||||||
|
public void SetField<TField>(string fieldName, TField value)
|
||||||
|
{
|
||||||
|
if (!PropertyCache.TryGetValue(fieldName, out var propertyInfo))
|
||||||
|
{
|
||||||
|
propertyInfo = CacheProperty<TField>(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SetterCache.TryGetValue(fieldName, out var setter))
|
||||||
|
{
|
||||||
|
((Action<TField>) setter)(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyInfo.SetValue(GeneratedObject, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Reactor.Utilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A wrapper for IL2CPP state machine objects to access their parent instance and state.
|
||||||
|
/// This class is useful for working with IL2CPP state machines across different game versions.
|
||||||
|
/// To use it, pass the instance object of the state machine into the constructor. To access the parent class,
|
||||||
|
/// use the Instance property. To access the current state of the state machine, use the State property.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the parent class that owns the state machine.</typeparam>
|
||||||
|
public class Il2CppStateMachineWrapper<T> : Il2CppCompilerGeneratedObjectWrapper
|
||||||
|
{
|
||||||
|
// normally it is fields, but IL2CPP turns them into properties
|
||||||
|
private readonly PropertyInfo _thisProperty;
|
||||||
|
private readonly PropertyInfo _stateProperty;
|
||||||
|
|
||||||
|
private T? _parentInstance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance of the parent class that owns the state machine.
|
||||||
|
/// </summary>
|
||||||
|
public T Instance => _parentInstance ??= (T) _thisProperty.GetValue(GeneratedObject)!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current state of the state machine.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The current state as an integer.</returns>
|
||||||
|
public int State
|
||||||
|
{
|
||||||
|
get => (int) _stateProperty.GetValue(GeneratedObject)!;
|
||||||
|
set => _stateProperty.SetValue(GeneratedObject, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Il2CppStateMachineWrapper{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stateMachine">The instance object of the state machine being wrapped.</param>
|
||||||
|
public Il2CppStateMachineWrapper(object stateMachine) : base(stateMachine)
|
||||||
|
{
|
||||||
|
// The names of these properties are implementation details of the IL compiler used by Unity.
|
||||||
|
// They should be stable as long as Unity sticks to mono.
|
||||||
|
_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}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a parameter from the state machine by its name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameterName">The name of the parameter to retrieve.</param>
|
||||||
|
/// <typeparam name="TField">The type of the parameter to retrieve.</typeparam>
|
||||||
|
/// <returns>>The value of the specified parameter.</returns>
|
||||||
|
/// <exception cref="MissingFieldException">Thrown if the specified parameter does not exist.</exception>
|
||||||
|
public TField GetParameter<TField>(string parameterName)
|
||||||
|
{
|
||||||
|
return GetField<TField>(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a parameter in the state machine by its name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameterName">The name of the parameter to set.</param>
|
||||||
|
/// <param name="value">The value to set for the parameter.</param>
|
||||||
|
/// <typeparam name="TField">The type of the parameter to set.</typeparam>
|
||||||
|
/// <exception cref="MissingFieldException">Thrown if the specified parameter does not exist.</exception>
|
||||||
|
public void SetParameter<TField>(string parameterName, TField value)
|
||||||
|
{
|
||||||
|
SetField(parameterName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the MoveNext method of a state machine for the specified method name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodName">The name of the method whose state machine MoveNext method is to be retrieved.</param>
|
||||||
|
/// <returns>The MoveNext <see cref="MethodBase"/> if found; otherwise, null.</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user