using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Hitcontinue")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Hitcontinue")] [assembly: AssemblyTitle("Hitcontinue")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Hitcontinue { [BepInPlugin("com.clippton.hitcontinue", "Hitcontinue", "1.0.0")] public sealed class HitcontinuePlugin : BaseUnityPlugin { private struct RigidbodyState { private readonly Rigidbody rb; private readonly bool isKinematic; private readonly Vector3 velocity; private readonly Vector3 angularVelocity; public RigidbodyState(Rigidbody rb) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) this.rb = rb; isKinematic = rb.isKinematic; velocity = rb.velocity; angularVelocity = rb.angularVelocity; } public void Restore() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)rb == (Object)null)) { rb.isKinematic = isKinematic; rb.velocity = velocity; rb.angularVelocity = angularVelocity; } } } private struct AnimatorState { private readonly Animator animator; private readonly float speed; public AnimatorState(Animator animator) { this.animator = animator; speed = animator.speed; } public void Restore() { if ((Object)(object)animator != (Object)null) { animator.speed = speed; } } } private struct AgentState { private readonly NavMeshAgent agent; private readonly bool isStopped; private readonly Vector3 velocity; public AgentState(NavMeshAgent agent) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) this.agent = agent; isStopped = agent.isStopped; velocity = agent.velocity; } public void Restore() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)agent == (Object)null) && agent.isOnNavMesh) { agent.isStopped = isStopped; agent.velocity = velocity; } } } [CompilerGenerated] private sealed class d__28 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public HitcontinuePlugin <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__28(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown int num = <>1__state; HitcontinuePlugin hitcontinuePlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if (!hitcontinuePlugin.cacheBuilt) { hitcontinuePlugin.FindPlayerRoots(); if (hitcontinuePlugin.playerRoots.Count > 0) { hitcontinuePlugin.BuildObjectCache(); hitcontinuePlugin.cacheBuilt = true; hitcontinuePlugin.cacheRoutine = null; return false; } <>2__current = (object)new WaitForSecondsRealtime(1f); <>1__state = 1; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__33 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float length; public object timeController; public bool trueStop; public HitcontinuePlugin <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__33(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown int num = <>1__state; HitcontinuePlugin hitcontinuePlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(length); <>1__state = 1; return true; case 1: { <>1__state = -1; float num2 = (float)currentStopField.GetValue(timeController); if (length < num2) { return false; } hitstopActive = false; Time.timeScale = GetNormalTimeScale(timeController); if (trueStop) { CallSetAllPitch(timeController, 1f); } currentStopField.SetValue(timeController, 0f); hitcontinuePlugin.RestoreObjects(); return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string Guid = "com.clippton.hitcontinue"; private static HitcontinuePlugin instance; private static readonly Harmony harmony = new Harmony("com.clippton.hitcontinue"); private static ConfigEntry timeControllerPatchEnabled; private static ConfigEntry shotgunHammerPatchEnabled; private static FieldInfo currentStopField; private static bool hitstopActive; private readonly List playerRoots = new List(); private readonly List cachedRigidbodies = new List(); private readonly List cachedAnimators = new List(); private readonly List cachedAgents = new List(); private readonly List frozenRigidbodies = new List(); private readonly List frozenAnimators = new List(); private readonly List frozenAgents = new List(); private Coroutine cacheRoutine; private bool cacheBuilt; private bool frozen; private void Awake() { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown instance = this; timeControllerPatchEnabled = ((BaseUnityPlugin)this).Config.Bind("Hitstop", "TimeControllerPatch", true, "Convert regular TimeController HitStop/TrueStop calls into selective hitstop."); shotgunHammerPatchEnabled = ((BaseUnityPlugin)this).Config.Bind("Hitstop", "ShotgunHammerPatch", true, "Convert ShotgunHammer TrueStop calls into selective hitstop, even if the general TimeController patch is disabled."); Type type = AccessTools.TypeByName("TimeController"); if (type == null) { ((BaseUnityPlugin)this).Logger.LogError((object)"Could not find TimeController."); return; } currentStopField = AccessTools.Field(type, "currentStop"); if (currentStopField == null) { ((BaseUnityPlugin)this).Logger.LogError((object)"Could not find TimeController.currentStop."); return; } harmony.Patch((MethodBase)AccessTools.Method(type, "HitStop", (Type[])null, (Type[])null), new HarmonyMethod(typeof(HitcontinuePlugin), "HitStopPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(type, "TrueStop", (Type[])null, (Type[])null), new HarmonyMethod(typeof(HitcontinuePlugin), "TrueStopPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); PatchGameUpdateMethods(); SceneManager.sceneLoaded += OnSceneLoaded; StartCacheScan(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Hitcontinue loaded."); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; harmony.UnpatchSelf(); RestoreObjects(); StopCacheScan(); hitstopActive = false; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { RestoreObjects(); ClearCache(); cacheBuilt = false; hitstopActive = false; StartCacheScan(); } private void PatchGameUpdateMethods() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown HarmonyMethod prefix = new HarmonyMethod(typeof(HitcontinuePlugin), "UpdateFreezePrefix", (Type[])null); HarmonyMethod prefix2 = new HarmonyMethod(typeof(HitcontinuePlugin), "CoroutineFreezePrefix", (Type[])null); int num = 0; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (assembly.GetName().Name != "Assembly-CSharp") { continue; } Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } Type[] array = types; foreach (Type type in array) { if (type == null) { continue; } if (typeof(MonoBehaviour).IsAssignableFrom(type)) { if (typeof(BaseUnityPlugin).IsAssignableFrom(type)) { continue; } num += PatchUpdateMethod(type, "Update", prefix); num += PatchUpdateMethod(type, "FixedUpdate", prefix); num += PatchUpdateMethod(type, "LateUpdate", prefix); } num += PatchCoroutineMethods(type, prefix2); } } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Hitcontinue patched " + num + " update/coroutine methods.")); } private int PatchUpdateMethod(Type type, string methodName, HarmonyMethod prefix) { MethodInfo methodInfo = AccessTools.DeclaredMethod(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { return 0; } if (methodInfo.IsStatic || methodInfo.GetParameters().Length != 0 || methodInfo.ReturnType != typeof(void)) { return 0; } try { harmony.Patch((MethodBase)methodInfo, prefix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return 1; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not patch " + type.FullName + "." + methodName + ": " + ex.Message)); return 0; } } private int PatchCoroutineMethods(Type type, HarmonyMethod prefix) { if (type == null || !typeof(IEnumerator).IsAssignableFrom(type)) { return 0; } MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "MoveNext", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo.ReturnType != typeof(bool)) { return 0; } try { harmony.Patch((MethodBase)methodInfo, prefix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return 1; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not patch coroutine " + type.FullName + ".MoveNext: " + ex.Message)); return 0; } } private static bool UpdateFreezePrefix(MonoBehaviour __instance) { if (!hitstopActive || (Object)(object)instance == (Object)null || (Object)(object)__instance == (Object)null) { return true; } return !instance.ShouldFreeze(((Component)__instance).gameObject); } private static bool CoroutineFreezePrefix(object __instance) { if (!hitstopActive || (Object)(object)instance == (Object)null || __instance == null) { return true; } if (!TryGetCoroutineOwner(__instance, out var owner)) { return true; } if ((Object)(object)owner == (Object)null) { return true; } return !instance.ShouldFreeze(((Component)owner).gameObject); } private static bool TryGetCoroutineOwner(object coroutine, out MonoBehaviour owner) { owner = null; FieldInfo[] fields = coroutine.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (typeof(MonoBehaviour).IsAssignableFrom(fieldInfo.FieldType)) { object? value = fieldInfo.GetValue(coroutine); owner = (MonoBehaviour)((value is MonoBehaviour) ? value : null); if ((Object)(object)owner != (Object)null) { return true; } } } return false; } private void StartCacheScan() { StopCacheScan(); cacheRoutine = ((MonoBehaviour)this).StartCoroutine(CacheWhenPlayerExistsRoutine()); } private void StopCacheScan() { if (cacheRoutine != null) { ((MonoBehaviour)this).StopCoroutine(cacheRoutine); cacheRoutine = null; } } [IteratorStateMachine(typeof(d__28))] private IEnumerator CacheWhenPlayerExistsRoutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__28(0) { <>4__this = this }; } private static bool HitStopPrefix(object __instance, float length) { if ((Object)(object)instance == (Object)null || !timeControllerPatchEnabled.Value) { return true; } instance.DoStop(__instance, length, trueStop: false); return false; } private static bool TrueStopPrefix(object __instance, float length) { if ((Object)(object)instance == (Object)null) { return true; } if (!timeControllerPatchEnabled.Value && (!shotgunHammerPatchEnabled.Value || !IsShotgunHammerTrueStopCall())) { return true; } instance.DoStop(__instance, length, trueStop: true); return false; } private static bool IsShotgunHammerTrueStopCall() { StackTrace stackTrace = new StackTrace(); for (int i = 0; i < stackTrace.FrameCount; i++) { MethodBase method = stackTrace.GetFrame(i).GetMethod(); if (!(method == null) && !(method.DeclaringType == null)) { Type declaringType = method.DeclaringType; if (declaringType.Name == "ShotgunHammer") { return true; } if (declaringType.FullName != null && declaringType.FullName.Contains("ShotgunHammer")) { return true; } } } return false; } private void DoStop(object timeController, float length, bool trueStop) { float num = (float)currentStopField.GetValue(timeController); if (length <= num) { return; } currentStopField.SetValue(timeController, length); if (!cacheBuilt) { FindPlayerRoots(); if (playerRoots.Count <= 0) { Time.timeScale = GetNormalTimeScale(timeController); return; } BuildObjectCache(); cacheBuilt = true; } if (!frozen) { FreezeObjects(); } hitstopActive = true; Time.timeScale = GetNormalTimeScale(timeController); if (trueStop) { CallSetAllPitch(timeController, 0f); } ((MonoBehaviour)this).StartCoroutine(StopRoutine(timeController, length, trueStop)); } [IteratorStateMachine(typeof(d__33))] private IEnumerator StopRoutine(object timeController, float length, bool trueStop) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__33(0) { <>4__this = this, timeController = timeController, length = length, trueStop = trueStop }; } private void FindPlayerRoots() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) playerRoots.Clear(); GameObject[] array = GameObject.FindGameObjectsWithTag("Player"); foreach (GameObject val in array) { if (!((Object)(object)val != (Object)null)) { continue; } Scene scene = val.scene; if (((Scene)(ref scene)).IsValid()) { scene = val.scene; if (((Scene)(ref scene)).isLoaded) { playerRoots.Add(val.transform); } } } } private void BuildObjectCache() { cachedRigidbodies.Clear(); cachedAnimators.Clear(); cachedAgents.Clear(); Rigidbody[] array = Object.FindObjectsOfType(); foreach (Rigidbody val in array) { if ((Object)(object)val != (Object)null && ShouldFreeze(((Component)val).gameObject)) { cachedRigidbodies.Add(val); } } Animator[] array2 = Object.FindObjectsOfType(); foreach (Animator val2 in array2) { if ((Object)(object)val2 != (Object)null && ShouldFreeze(((Component)val2).gameObject)) { cachedAnimators.Add(val2); } } NavMeshAgent[] array3 = Object.FindObjectsOfType(); foreach (NavMeshAgent val3 in array3) { if ((Object)(object)val3 != (Object)null && ShouldFreeze(((Component)val3).gameObject)) { cachedAgents.Add(val3); } } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Hitcontinue cache built: " + playerRoots.Count + " player roots, " + cachedRigidbodies.Count + " rigidbodies, " + cachedAnimators.Count + " animators, " + cachedAgents.Count + " nav agents.")); } private void ClearCache() { playerRoots.Clear(); cachedRigidbodies.Clear(); cachedAnimators.Clear(); cachedAgents.Clear(); } private void FreezeObjects() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) frozen = true; foreach (Rigidbody cachedRigidbody in cachedRigidbodies) { if (!((Object)(object)cachedRigidbody == (Object)null)) { frozenRigidbodies.Add(new RigidbodyState(cachedRigidbody)); cachedRigidbody.velocity = Vector3.zero; cachedRigidbody.angularVelocity = Vector3.zero; cachedRigidbody.isKinematic = true; } } foreach (Animator cachedAnimator in cachedAnimators) { if (!((Object)(object)cachedAnimator == (Object)null)) { frozenAnimators.Add(new AnimatorState(cachedAnimator)); cachedAnimator.speed = 0f; } } foreach (NavMeshAgent cachedAgent in cachedAgents) { if (!((Object)(object)cachedAgent == (Object)null)) { frozenAgents.Add(new AgentState(cachedAgent)); if (cachedAgent.isOnNavMesh) { cachedAgent.isStopped = true; cachedAgent.velocity = Vector3.zero; } } } } private void RestoreObjects() { foreach (RigidbodyState frozenRigidbody in frozenRigidbodies) { frozenRigidbody.Restore(); } foreach (AnimatorState frozenAnimator in frozenAnimators) { frozenAnimator.Restore(); } foreach (AgentState frozenAgent in frozenAgents) { frozenAgent.Restore(); } frozenRigidbodies.Clear(); frozenAnimators.Clear(); frozenAgents.Clear(); frozen = false; } private bool ShouldFreeze(GameObject obj) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)obj != (Object)null) { Scene scene = obj.scene; if (((Scene)(ref scene)).IsValid()) { scene = obj.scene; if (((Scene)(ref scene)).isLoaded) { return !IsPlayerOrChild(obj); } } } return false; } private bool IsPlayerOrChild(GameObject obj) { Transform val = obj.transform; while ((Object)(object)val != (Object)null) { foreach (Transform playerRoot in playerRoots) { if ((Object)(object)playerRoot != (Object)null && (Object)(object)val == (Object)(object)playerRoot) { return true; } } val = val.parent; } return false; } private static float GetNormalTimeScale(object timeController) { float num = (float)AccessTools.Field(timeController.GetType(), "timeScale").GetValue(timeController); float num2 = (float)AccessTools.Field(timeController.GetType(), "timeScaleModifier").GetValue(timeController); return num * num2; } private static void CallSetAllPitch(object timeController, float pitch) { MethodInfo methodInfo = AccessTools.Method(timeController.GetType(), "SetAllPitch", (Type[])null, (Type[])null); if (methodInfo != null) { methodInfo.Invoke(timeController, new object[1] { pitch }); } } } }