Decompiled source of CrossplayStutterFix v2.0.0

CrossplayStutterFix.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
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: AssemblyCompany("Rombusz")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Reduces PEAK character lifecycle hitches caused by initialization races and repeated diagnostic exceptions.")]
[assembly: AssemblyFileVersion("2.0.0.0")]
[assembly: AssemblyInformationalVersion("2.0.0")]
[assembly: AssemblyProduct("CrossplayStutterFix")]
[assembly: AssemblyTitle("CrossplayStutterFix")]
[assembly: AssemblyVersion("2.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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]
	[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 CrossplayStutterFix
{
	[BepInPlugin("Rombusz.CrossplayStutterFix", "CrossplayStutterFix", "2.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "Rombusz.CrossplayStutterFix";

		public const string Name = "CrossplayStutterFix";

		public const string Version = "2.0.0";

		internal static ManualLogSource? Log;

		private Harmony? _harmony;

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("Rombusz.CrossplayStutterFix");
			Type[] types = typeof(Plugin).Assembly.GetTypes();
			foreach (Type type in types)
			{
				if (Attribute.IsDefined(type, typeof(HarmonyPatch)))
				{
					try
					{
						_harmony.CreateClassProcessor(type).Patch();
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch " + type.Name + " could not be installed: " + ex.GetBaseException().Message));
					}
				}
			}
			((BaseUnityPlugin)this).Logger.LogMessage((object)"Plugin CrossplayStutterFix 2.0.0 is loaded!");
		}

		private void Update()
		{
			LifecycleDiagnostics.Tick();
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	[HarmonyPatch(typeof(Character), "WarpPlayer")]
	internal static class CharacterWarpPatch
	{
		private static readonly MethodInfo StopClimbing = AccessTools.Method(typeof(CharacterClimbing), "StopAnyClimbing", (Type[])null, (Type[])null);

		private static readonly MethodInfo SafeStopClimbing = AccessTools.Method(typeof(CharacterWarpPatch), "StopClimbingWhenReady", (Type[])null, (Type[])null);

		private static readonly MethodInfo LogError = AccessTools.Method(typeof(Debug), "LogError", new Type[1] { typeof(object) }, (Type[])null);

		private static readonly FieldRef<CharacterClimbing, Character>? ClimbingCharacter = SafeBindings.Field<CharacterClimbing, Character>("character");

		private static readonly MethodInfo Warn = AccessTools.Method(typeof(LifecycleDiagnostics), "WarpWarning", (Type[])null, (Type[])null);

		[HarmonyPrepare]
		private static bool Prepare()
		{
			if (ClimbingCharacter != null && StopClimbing != null)
			{
				return SafeStopClimbing != null;
			}
			return false;
		}

		[HarmonyPrefix]
		private static void TraceOverlap(Character __instance, Vector3 position)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			if (__instance.warping)
			{
				PhotonView photonView = ((MonoBehaviourPun)__instance).photonView;
				object arg = ((photonView != null) ? new int?(photonView.ViewID) : ((int?)null));
				CharacterData data = __instance.data;
				LifecycleDiagnostics.Warn("warp-overlap", $"Overlapping warp: view={arg}, dead={((data != null) ? new bool?(data.dead) : ((bool?)null))}, target={position}. Vanilla will stop its previous warp routine.");
			}
		}

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> GuardEarlyWarp(IEnumerable<CodeInstruction> instructions)
		{
			int climbingMatches = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, StopClimbing))
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = SafeStopClimbing;
					climbingMatches++;
				}
				else if (CodeInstructionExtensions.Calls(instruction, LogError))
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = Warn;
				}
				yield return instruction;
			}
			if (climbingMatches == 0)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"Warp climbing guard found no matching call; game code may have changed.");
				}
			}
		}

		private static void StopClimbingWhenReady(CharacterClimbing climbing)
		{
			if ((Object)(object)climbing != (Object)null && (Object)(object)ClimbingCharacter.Invoke(climbing) != (Object)null)
			{
				climbing.StopAnyClimbing();
			}
		}
	}
	[HarmonyPatch(typeof(Dynamite), "TestLightWick")]
	internal static class DynamiteProximityPatch
	{
		private static Character? _readyCharacter;

		[HarmonyPrefix]
		private static bool RunWhenLocalCharacterIsUsable()
		{
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null)
			{
				return false;
			}
			if ((Object)(object)localCharacter == (Object)(object)_readyCharacter)
			{
				return true;
			}
			if (localCharacter.refs != null && (Object)(object)localCharacter.refs.ragdoll != (Object)null && localCharacter.refs.ragdoll.partDict.TryGetValue((BodypartType)2, out var value) && (Object)(object)value != (Object)null && (Object)(object)value.transform != (Object)null)
			{
				_readyCharacter = localCharacter;
				return true;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Character), "RPCA_Fall")]
	internal static class FallDiagnosticPatch
	{
		private static readonly MethodInfo Log = AccessTools.Method(typeof(Debug), "Log", new Type[1] { typeof(object) }, (Type[])null);

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> RemoveRepeatedFallLog(IEnumerable<CodeInstruction> instructions)
		{
			int matches = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, Log))
				{
					matches++;
					instruction.opcode = OpCodes.Pop;
					instruction.operand = null;
				}
				yield return instruction;
			}
			if (matches == 0)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"Fall log suppression: no matching log call (possibly a log-stripped game build).");
				}
			}
		}
	}
	[HarmonyPatch(typeof(CharacterStats), "Record")]
	internal static class ReviveTimelineDiagnosticPatch
	{
		private static readonly MethodInfo LogError = AccessTools.Method(typeof(Debug), "LogError", new Type[1] { typeof(object) }, (Type[])null);

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> RemoveReviveErrorStack(IEnumerable<CodeInstruction> instructions)
		{
			int matches = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, LogError))
				{
					matches++;
					instruction.opcode = OpCodes.Pop;
					instruction.operand = null;
				}
				yield return instruction;
			}
			if (matches == 0)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"Revive log suppression: no matching error-log call (possibly a log-stripped game build).");
				}
			}
		}
	}
	internal static class SafeBindings
	{
		internal static FieldRef<T, F>? Field<T, F>(string name) where T : class
		{
			try
			{
				return AccessTools.FieldRefAccess<T, F>(name);
			}
			catch (Exception ex)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Disabled dependent patches: cannot bind " + typeof(T).Name + "." + name + ": " + ex.Message));
				}
				return null;
			}
		}
	}
	[HarmonyPatch(typeof(CharacterSpawner), "RPC_NewPlayerSpawn")]
	internal static class LateJoinSpawnPatch
	{
		private sealed class Flight
		{
			internal bool Running;
		}

		private static readonly ConditionalWeakTable<MonoBehaviour, Flight> Flights = new ConditionalWeakTable<MonoBehaviour, Flight>();

		private static readonly FieldRef<CharacterSpawner, bool>? HasSpawned = SafeBindings.Field<CharacterSpawner, bool>("hasSpawnedPlayer");

		private const float ReadinessTimeout = 120f;

		[HarmonyPrepare]
		private static bool Prepare()
		{
			return HasSpawned != null;
		}

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> WrapSpawnRoutine(IEnumerable<CodeInstruction> instructions)
		{
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Expected O, but got Unknown
			MethodInfo start = AccessTools.Method(typeof(MonoBehaviour), "StartCoroutine", new Type[1] { typeof(IEnumerator) }, (Type[])null);
			MethodInfo methodInfo = AccessTools.Method(typeof(LateJoinSpawnPatch), "StartOnce", (Type[])null, (Type[])null);
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			int count = list.FindAll((CodeInstruction i) => CodeInstructionExtensions.Calls(i, start)).Count;
			if (count != 1)
			{
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)$"Spawn coroutine guard not installed: expected one StartCoroutine call, found {count}. Vanilla spawning retained.");
				}
				return list;
			}
			int num = list.FindIndex((CodeInstruction i) => CodeInstructionExtensions.Calls(i, start));
			list[num].opcode = OpCodes.Ldarg_1;
			list[num].operand = null;
			list.InsertRange(num + 1, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3]
			{
				new CodeInstruction(OpCodes.Ldarg_3, (object)null),
				new CodeInstruction(OpCodes.Ldarg_S, (object)(byte)4),
				new CodeInstruction(OpCodes.Call, (object)methodInfo)
			});
			return list;
		}

		private static Coroutine? StartOnce(CharacterSpawner spawner, IEnumerator routine, Segment currentMapStage, int lastReviveSegment, PhotonMessageInfo message)
		{
			//IL_0041: 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)
			Flight orCreateValue = Flights.GetOrCreateValue((MonoBehaviour)(object)spawner);
			if (orCreateValue.Running)
			{
				(routine as IDisposable)?.Dispose();
				LifecycleDiagnostics.Warn("spawn-duplicate", "Ignored duplicate late-join spawn request while its original coroutine is still running.");
				return null;
			}
			orCreateValue.Running = true;
			try
			{
				return ((MonoBehaviour)spawner).StartCoroutine(Run(spawner, routine, orCreateValue, currentMapStage, lastReviveSegment, message.Sender));
			}
			catch
			{
				orCreateValue.Running = false;
				(routine as IDisposable)?.Dispose();
				throw;
			}
		}

		private static IEnumerator Run(CharacterSpawner spawner, IEnumerator routine, Flight flight, Segment currentMapStage, int lastReviveSegment, Player sender)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			Room room = PhotonNetwork.CurrentRoom;
			bool needsIsland = (int)currentMapStage != 0 || lastReviveSegment != int.MinValue;
			float started = Time.realtimeSinceStartup;
			bool waited = false;
			try
			{
				while (!((Object)(object)spawner == (Object)null) && ((Behaviour)spawner).isActiveAndEnabled && !HasSpawned.Invoke(spawner) && room != null && room == PhotonNetwork.CurrentRoom && sender != null && sender.IsMasterClient)
				{
					needsIsland |= GameHandler.IsOnIsland || ((Object)(object)GameHandler.Instance != (Object)null && GameHandler.IsInGameplayScene);
					if (!(needsIsland ? GameHandler.IsOnIslandAndInitialized : ((Object)(object)GameHandler.Instance != (Object)null && GameHandler.IsInMultiplayerScene)))
					{
						if (Time.realtimeSinceStartup - started >= 120f)
						{
							LifecycleDiagnostics.Warn("spawn-map-timeout", "Late-join spawn timed out waiting for scene/map readiness; no airport fallback was attempted. A fresh host request can retry.");
							break;
						}
						if (!waited)
						{
							waited = true;
							LifecycleDiagnostics.Warn("spawn-map-wait", $"Deferring late-join spawn until scene/map initialization is ready (segment={currentMapStage}, lastRevive={lastReviveSegment}).");
						}
						yield return null;
					}
					else
					{
						if (waited)
						{
							LifecycleDiagnostics.Warn("spawn-map-ready", "Scene/map initialized; resuming the original late-join spawn routine.");
							waited = false;
						}
						if (!routine.MoveNext())
						{
							break;
						}
						yield return routine.Current;
					}
				}
			}
			finally
			{
				flight.Running = false;
				(routine as IDisposable)?.Dispose();
			}
		}
	}
	internal static class LifecycleDiagnostics
	{
		private static readonly Dictionary<string, float> LastWarnings = new Dictionary<string, float>();

		private static Character? _lastCharacter;

		private static bool _lastDead;

		private static float _nextCheck;

		internal static void Warn(string key, string message)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!LastWarnings.TryGetValue(key, out var value) || !(realtimeSinceStartup - value < 5f))
			{
				LastWarnings[key] = realtimeSinceStartup;
				ManualLogSource? log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)message);
				}
			}
		}

		internal static void WarpWarning(object message)
		{
			Warn("warp-overlap", message?.ToString() ?? "Overlapping warp.");
		}

		internal static void Tick()
		{
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			if (Time.realtimeSinceStartup < _nextCheck)
			{
				return;
			}
			_nextCheck = Time.realtimeSinceStartup + 1f;
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null)
			{
				_lastCharacter = null;
			}
			else
			{
				if ((Object)(object)localCharacter.data == (Object)null || (Object)(object)localCharacter.refs?.ragdoll == (Object)null || !localCharacter.refs.ragdoll.partDict.TryGetValue((BodypartType)2, out var value) || (Object)(object)value == (Object)null || (Object)(object)value.transform == (Object)null)
				{
					return;
				}
				bool dead = localCharacter.data.dead;
				if ((Object)(object)_lastCharacter != (Object)(object)localCharacter || dead != _lastDead)
				{
					ManualLogSource? log = Plugin.Log;
					if (log != null)
					{
						object[] array = new object[5];
						PhotonView photonView = ((MonoBehaviourPun)localCharacter).photonView;
						array[0] = ((photonView != null) ? new int?(photonView.ViewID) : ((int?)null));
						array[1] = dead;
						array[2] = localCharacter.data.fullyPassedOut;
						array[3] = localCharacter.warping;
						array[4] = localCharacter.Center;
						log.LogInfo((object)(string.Format("Local lifecycle: view={0}, dead={1}, fullyPassedOut={2}, warping={3}, position={4}.", array) + (dead ? " Vanilla FixedUpdate pulls dead characters toward (0, 5000, -5000); this message does not mean death was illegitimate." : string.Empty)));
					}
					_lastCharacter = localCharacter;
					_lastDead = dead;
				}
			}
		}
	}
}