diff --git a/Directory.Build.props b/Directory.Build.props
index ac800b3..9cc1b57 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,7 +8,7 @@
true
- 0.3.0
+ 0.3.1
dev
All Of Us, XtraCube, Angxl
diff --git a/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs b/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs
new file mode 100644
index 0000000..1324dde
--- /dev/null
+++ b/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs
@@ -0,0 +1,44 @@
+using System.Linq;
+using AmongUs.GameOptions;
+using HarmonyLib;
+using Il2CppSystem.Collections.Generic;
+using MiraAPI.Roles;
+using UnityEngine;
+
+namespace MiraAPI.Patches;
+
+[HarmonyPatch(typeof(DetectiveNotesMinigame), nameof(DetectiveNotesMinigame.OpenBodyPopup))]
+public static class DetectiveNotesMinigamePatch
+{
+ public static void Prefix(DetectiveNotesMinigame __instance, bool active)
+ {
+ __instance.impostorTypePopup.SetActive(active);
+ if (!active)
+ {
+ ControllerManager.Instance.CloseOverlayMenu(__instance.ImpostorOverlay);
+ ControllerManager.Instance.SetUpSelectables(ControllerManager.Instance.CurrentUiState, __instance.ControllerSelectable[0], __instance.ControllerSelectable);
+ ControllerManager.Instance.SetCurrentSelected(__instance.ControllerSelectable[2]);
+ return;
+ }
+ if (__instance.impostorButton != null)
+ {
+ ControllerManager.Instance.OpenOverlayMenu(__instance.ImpostorOverlay, null, __instance.ImpostorIconControllerSelectables[0], __instance.ImpostorIconControllerSelectables, false);
+ return;
+ }
+ __instance.impostorButton = new List();
+ int num = 0;
+ var allRoles = DestroyableSingleton.Instance.AllRoles;
+ var customRoles = CustomRoleManager.CustomRoleBehaviours;
+ foreach (var role in allRoles)
+ {
+ if (role.TeamType != RoleTeamTypes.Impostor || role.Role == RoleTypes.ImpostorGhost || customRoles.Contains(role) )
+ continue;
+ GameObject gameObject = Object.Instantiate(__instance.impostorTypePrefab, __instance.impostorTypeParent);
+ __instance.impostorButton.Add(gameObject);
+ gameObject.GetComponent().Initialize(__instance, role);
+ num++;
+ __instance.ImpostorIconControllerSelectables.Add(gameObject.GetComponent());
+ }
+ ControllerManager.Instance.OpenOverlayMenu(__instance.ImpostorOverlay, null, __instance.ImpostorIconControllerSelectables[0], __instance.ImpostorIconControllerSelectables, false);
+ }
+}
\ No newline at end of file
diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs
index 8105ad1..5495251 100644
--- a/MiraAPI/Patches/IntroCutscenePatches.cs
+++ b/MiraAPI/Patches/IntroCutscenePatches.cs
@@ -1,8 +1,12 @@
-using HarmonyLib;
+using System.Linq;
+using System.Reflection;
+using HarmonyLib;
+using Il2CppInterop.Runtime.InteropTypes;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Roles;
using MiraAPI.Utilities;
+using Reactor.Utilities;
namespace MiraAPI.Patches;
@@ -29,14 +33,29 @@ public static class IntroCutscenePatches
MiraEventManager.InvokeEvent(@event);
}
- [HarmonyPostfix]
- [HarmonyPatch(typeof(IntroCutscene._ShowRole_d__40), nameof(IntroCutscene._ShowRole_d__40.MoveNext))]
- [HarmonyPriority(Priority.Last)]
- public static void ShowRolePatch(IntroCutscene._ShowRole_d__40 __instance)
+ [HarmonyPatch]
+ public static class IntroCutsceneShowRolePatch
{
- var realInstance = __instance.__4__this;
- var @event = new IntroRoleRevealEvent(realInstance);
- MiraEventManager.InvokeEvent(@event);
+ public static MethodBase TargetMethod()
+ {
+ return Helpers.GetStateMachineMoveNext("ShowRole")!;
+ }
+
+ public static void Postfix(Il2CppObjectBase __instance)
+ {
+ var wrapper = new StateMachineWrapper(__instance);
+ // run before the first yield
+ if (wrapper.GetState() != 1)
+ {
+ return;
+ }
+
+ var introCutscene = wrapper.Instance;
+
+ Logger.Info("IntroCutscene ShowRole reached");
+ var @event = new IntroRoleRevealEvent(introCutscene);
+ MiraEventManager.InvokeEvent(@event);
+ }
}
[HarmonyPrefix]
@@ -66,21 +85,54 @@ public static class IntroCutscenePatches
}
}
- [HarmonyPostfix]
- [HarmonyPatch(typeof(IntroCutscene._CoBegin_d__34), nameof(IntroCutscene._CoBegin_d__34.MoveNext))]
- public static void GameBeginPatch(IntroCutscene._CoBegin_d__34 __instance)
+ [HarmonyPatch]
+ public static class IntroCutsceneDestroyPatch
{
- if (__instance.__1__state != -1)
+ private static bool _usedFallback;
+
+ public static MethodBase TargetMethod()
{
- return;
+ var onDestroy = AccessTools.Method(typeof(IntroCutscene), "OnDestroy");
+ if (onDestroy != null)
+ {
+ _usedFallback = false;
+ Logger.Info("Using OnDestroy for IntroCutsceneDestroyPatch");
+ return onDestroy;
+ }
+
+ _usedFallback = true;
+ return Helpers.GetStateMachineMoveNext("CoBegin")!;
}
- MiraEventManager.InvokeEvent(new IntroEndEvent(__instance.__4__this));
+ public static void Postfix(Il2CppObjectBase __instance)
+ {
+ IntroCutscene introCutscene;
- var @event = new BeforeRoundStartEvent(true);
- MiraEventManager.InvokeEvent(@event);
+ if (_usedFallback)
+ {
+ var wrapper = new StateMachineWrapper(__instance);
+ // run after the final yield
+ if (wrapper.GetState() != -1)
+ {
+ return;
+ }
+ introCutscene = wrapper.Instance;
+ }
+ else
+ {
+ introCutscene = __instance.Cast();
+ }
- if (@event.IsCancelled) return;
- MiraEventManager.InvokeEvent(new RoundStartEvent(true));
+
+ Logger.Info("IntroCutscene ended");
+
+ MiraEventManager.InvokeEvent(new IntroEndEvent(introCutscene));
+
+ var @event = new BeforeRoundStartEvent(true);
+ MiraEventManager.InvokeEvent(@event);
+
+ if (@event.IsCancelled) return;
+ MiraEventManager.InvokeEvent(new RoundStartEvent(true));
+ }
}
}
diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs
index a845fb4..3df7113 100644
--- a/MiraAPI/Utilities/Helpers.cs
+++ b/MiraAPI/Utilities/Helpers.cs
@@ -1,15 +1,16 @@
-using System;
-using System.Collections;
+using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
-using MiraAPI.Keybinds;
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
+using Reactor.Utilities;
using Rewired;
using TMPro;
using UnityEngine;
+using MethodBase = System.Reflection.MethodBase;
using Object = UnityEngine.Object;
namespace MiraAPI.Utilities;
@@ -38,6 +39,31 @@ public static class Helpers
return keybindIcon;
}
+ 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)
+ {
+ Logger.Error($"Failed to find {methodName} state machine for {typeName}");
+ return null;
+ }
+
+ var moveNext = AccessTools.Method(showRoleStateMachine, "MoveNext");
+ if (moveNext == null)
+ {
+ Logger.Error($"Failed to find MoveNext method for {typeName}.{methodName}");
+ return null;
+ }
+
+ Logger.Info($"Found {methodName}.MoveNext");
+ return moveNext;
+ }
+
///
/// Creates a draggable scroller. Add items into the scroller.Inner transform. This does not automatically position children.
///
diff --git a/MiraAPI/Utilities/StateMachineWrapper.cs b/MiraAPI/Utilities/StateMachineWrapper.cs
new file mode 100644
index 0000000..f8d9d8b
--- /dev/null
+++ b/MiraAPI/Utilities/StateMachineWrapper.cs
@@ -0,0 +1,42 @@
+using System.Reflection;
+using HarmonyLib;
+using Il2CppInterop.Runtime.InteropTypes;
+
+namespace MiraAPI.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 where T : Il2CppObjectBase
+{
+ private readonly Il2CppObjectBase _stateMachine;
+ 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(_stateMachine)!;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The state machine instance to wrap.
+ public StateMachineWrapper(Il2CppObjectBase stateMachine)
+ {
+ _stateMachine = stateMachine;
+
+ var type = _stateMachine.GetType();
+ _thisProperty = AccessTools.Property(type, "__4__this");
+ _stateProperty = AccessTools.Property(type, "__1__state");
+ }
+
+ ///
+ /// Gets the current state of the state machine.
+ ///
+ /// The current state as an integer.
+ public int GetState() => (int)_stateProperty.GetValue(_stateMachine)!;
+}