using System; 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 Photon.Pun; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 BreakFree { [BepInPlugin("com.breakfree.repo.breakfree", "break free", "1.0.0")] public sealed class BreakFreePlugin : BaseUnityPlugin { public const string PluginGuid = "com.breakfree.repo.breakfree"; public const string PluginName = "break free"; public const string PluginVersion = "1.0.0"; private static ConfigEntry? enableDiagnostics; private Harmony? harmony; private bool isPatched; private void Awake() { enableDiagnostics = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "EnableDiagnostics", true, "Enable break free diagnostic logging."); PatchPlugin(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"break free 1.0.0 loaded"); } public static void LogDiagnostic(string message) { ConfigEntry? obj = enableDiagnostics; if (obj == null || obj.Value) { Debug.Log((object)("[break free] " + message)); } } private void OnEnable() { PatchPlugin(); } private void OnDisable() { UnpatchPlugin(); } private void OnDestroy() { UnpatchPlugin(); } private void PatchPlugin() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (!isPatched) { if (harmony == null) { harmony = new Harmony("com.breakfree.repo.breakfree"); } harmony.PatchAll(); isPatched = true; LogDiagnostic("Harmony patches applied successfully"); } } private void UnpatchPlugin() { if (isPatched || harmony != null) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; isPatched = false; PlayerTumbleUpdatePatch.ClearAllStates(); } } } [HarmonyPatch(typeof(PlayerTumble), "Update")] internal static class PlayerTumbleUpdatePatch { private readonly struct PressState { public float LastJumpAt { get; } public float CooldownUntil { get; } public PressState(float lastJumpAt, float cooldownUntil) { LastJumpAt = lastJumpAt; CooldownUntil = cooldownUntil; } } private const float DoublePressWindowSeconds = 2f; private const float SuccessCooldownSeconds = 1f; private static readonly Dictionary PressStates = new Dictionary(); private static readonly HashSet InputTriggeredTumbles = new HashSet(); private static FieldRef? isTumblingRef; private static FieldRef? isPlayerInputTriggeredRef; private static FieldRef? physGrabObjectRef; private static FieldRef? isLocalRef; private static bool fieldRefsInitialized; private static bool fieldRefsUnavailable; private static void Postfix(PlayerTumble __instance) { if (!EnsureFieldRefsInitialized()) { return; } if (!TryGetBreakFreeContext(__instance, out PhysGrabObject physGrabObject)) { if (IsTumblingActive(__instance)) { ClearPressState(__instance); } else { ClearState(__instance); } return; } float time = Time.time; PressState state = GetState(__instance); if (state.CooldownUntil > time) { PressStates[__instance] = state; } else if (!SemiFunc.InputDown((InputKey)1)) { PressStates[__instance] = state; } else if (state.LastJumpAt >= 0f && time - state.LastJumpAt <= 2f) { BreakFreePlugin.LogDiagnostic($"double jump detected / releasing grabbers; grabberCount={CountOtherPlayerGrabbers(physGrabObject, __instance.playerAvatar)}"); ForceReleaseGrabbers(__instance, physGrabObject); RequestStandUp(__instance); PressStates[__instance] = new PressState(-1f, time + 1f); } else { BreakFreePlugin.LogDiagnostic($"first jump recorded; grabberCount={CountOtherPlayerGrabbers(physGrabObject, __instance.playerAvatar)}"); PressStates[__instance] = new PressState(time, 0f); } } public static void ClearAllStates() { PressStates.Clear(); InputTriggeredTumbles.Clear(); } public static void TrackTumbleSetRpcInput(PlayerTumble? tumble, bool isTumbling, bool playerInput) { if (!((Object)(object)tumble == (Object)null)) { bool flag = InputTriggeredTumbles.Contains(tumble); if (isTumbling && playerInput) { InputTriggeredTumbles.Add(tumble); } else { ClearState(tumble); } bool isLocal; bool flag2 = TryIsLocalTumble(tumble, out isLocal); bool flag3 = InputTriggeredTumbles.Contains(tumble); if (isLocal || (!flag2 && flag != flag3)) { BreakFreePlugin.LogDiagnostic(string.Format("TumbleSetRPC observed: isTumbling={0}, playerInput={1}, tracked={2}, local={3}", isTumbling, playerInput, flag3, flag2 ? isLocal.ToString() : "unknown")); } } } private static bool EnsureFieldRefsInitialized() { if (fieldRefsInitialized) { return true; } if (fieldRefsUnavailable) { return false; } try { isTumblingRef = AccessTools.FieldRefAccess("isTumbling"); isPlayerInputTriggeredRef = AccessTools.FieldRefAccess("isPlayerInputTriggered"); physGrabObjectRef = AccessTools.FieldRefAccess("physGrabObject"); isLocalRef = AccessTools.FieldRefAccess("isLocal"); fieldRefsInitialized = true; BreakFreePlugin.LogDiagnostic("FieldRef initialization succeeded"); return true; } catch (Exception arg) { fieldRefsUnavailable = true; Debug.LogWarning((object)$"[break free] PlayerTumble patch disabled because field access initialization failed: {arg}"); return false; } } private static bool TryGetBreakFreeContext(PlayerTumble? tumble, out PhysGrabObject? physGrabObject) { physGrabObject = null; FieldRef val = isTumblingRef; FieldRef val2 = isPlayerInputTriggeredRef; FieldRef val3 = physGrabObjectRef; FieldRef val4 = isLocalRef; if (val == null || val2 == null || val3 == null || val4 == null) { return false; } if ((Object)(object)tumble == (Object)null) { return false; } PlayerAvatar playerAvatar = tumble.playerAvatar; if ((Object)(object)playerAvatar == (Object)null || !val4.Invoke(playerAvatar)) { return false; } if (!val.Invoke(tumble) || (!val2.Invoke(tumble) && !IsTrackedInputTriggered(tumble))) { return false; } physGrabObject = val3.Invoke(tumble); if (physGrabObject?.playerGrabbing == null || physGrabObject.playerGrabbing.Count == 0) { return false; } PlayerAvatar localAvatar = playerAvatar; foreach (PhysGrabber item in physGrabObject.playerGrabbing) { if (IsOtherPlayerGrabber(item, localAvatar)) { return true; } } return false; } private static void ForceReleaseGrabbers(PlayerTumble tumble, PhysGrabObject physGrabObject) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar playerAvatar = tumble.playerAvatar; if ((Object)(object)playerAvatar == (Object)null || physGrabObject.playerGrabbing == null) { return; } bool flag = IsMultiplayerSafe(); PhysGrabber[] array = physGrabObject.playerGrabbing.ToArray(); foreach (PhysGrabber val in array) { if (!IsOtherPlayerGrabber(val, playerAvatar)) { continue; } try { if (flag && (Object)(object)val.photonView != (Object)null) { BreakFreePlugin.LogDiagnostic("releasing grabber via multiplayer RPC ReleaseObjectRPC(false, 1f, -1)"); val.photonView.RPC("ReleaseObjectRPC", (RpcTarget)0, new object[3] { false, 1f, -1 }); } else { BreakFreePlugin.LogDiagnostic("releasing grabber via fallback ReleaseObjectRPC(true, 1f, -1)"); val.ReleaseObjectRPC(true, 1f, -1, default(PhotonMessageInfo)); } } catch (Exception arg) { Debug.LogWarning((object)$"[break free] Failed to release grabber: {arg}"); } } } private static void RequestStandUp(PlayerTumble? tumble) { if ((Object)(object)tumble == (Object)null) { return; } try { BreakFreePlugin.LogDiagnostic("requesting native tumble exit via TumbleRequest(false, true)"); tumble.TumbleRequest(false, true); } catch (Exception arg) { Debug.LogWarning((object)$"[break free] Failed to request stand up after release: {arg}"); } } private static bool IsOtherPlayerGrabber(PhysGrabber? grabber, PlayerAvatar localAvatar) { if ((Object)(object)grabber != (Object)null && (Object)(object)grabber.playerAvatar != (Object)null) { return (Object)(object)grabber.playerAvatar != (Object)(object)localAvatar; } return false; } private static int CountOtherPlayerGrabbers(PhysGrabObject physGrabObject, PlayerAvatar? localAvatar) { if (physGrabObject.playerGrabbing == null || (Object)(object)localAvatar == (Object)null) { return 0; } int num = 0; foreach (PhysGrabber item in physGrabObject.playerGrabbing) { if (IsOtherPlayerGrabber(item, localAvatar)) { num++; } } return num; } private static bool IsMultiplayerSafe() { try { return SemiFunc.IsMultiplayer(); } catch { return false; } } private static PressState GetState(PlayerTumble tumble) { if (!PressStates.TryGetValue(tumble, out var value)) { return new PressState(-1f, 0f); } return value; } private static void ClearState(PlayerTumble? tumble) { if ((Object)(object)tumble != (Object)null) { PressStates.Remove(tumble); InputTriggeredTumbles.Remove(tumble); } } private static void ClearPressState(PlayerTumble? tumble) { if ((Object)(object)tumble != (Object)null) { PressStates.Remove(tumble); } } private static bool IsTumblingActive(PlayerTumble? tumble) { FieldRef val = isTumblingRef; if ((Object)(object)tumble != (Object)null && val != null) { return val.Invoke(tumble); } return false; } private static bool IsTrackedInputTriggered(PlayerTumble tumble) { return InputTriggeredTumbles.Contains(tumble); } private static bool TryIsLocalTumble(PlayerTumble tumble, out bool isLocal) { isLocal = false; if (!EnsureFieldRefsInitialized()) { return false; } FieldRef val = isLocalRef; PlayerAvatar playerAvatar = tumble.playerAvatar; if (val == null || (Object)(object)playerAvatar == (Object)null) { return false; } isLocal = val.Invoke(playerAvatar); return true; } } [HarmonyPatch(typeof(PlayerTumble), "TumbleSetRPC")] internal static class PlayerTumbleSetRpcPatch { private static void Postfix(PlayerTumble __instance, bool _isTumbling, bool _playerInput) { PlayerTumbleUpdatePatch.TrackTumbleSetRpcInput(__instance, _isTumbling, _playerInput); } } }