Decompiled source of QuickTimers v0.1.2

plugins/MycopunkQuickTimers/MycopunkQuickTimers.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using MycopunkShared;
using Pigeon.Movement;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("MycopunkQuickTimers")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+fe8cef1291afd0a7f7ea8a9ddfd28d3607c4cdd5")]
[assembly: AssemblyProduct("MycopunkQuickTimers")]
[assembly: AssemblyTitle("MycopunkQuickTimers")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 MycopunkQuickTimers
{
	internal static class MenuText
	{
		private static readonly Dictionary<string, Dictionary<string, string>> Names = new Dictionary<string, Dictionary<string, string>>
		{
			["en"] = new Dictionary<string, string>
			{
				["quick.startup"] = "Skip startup animations",
				["quick.drop"] = "Shorten fixed-map drop",
				["quick.extraction"] = "Shorten extraction flight",
				["quick.mission_start"] = "Shorten mission start delay",
				["quick.mission_end"] = "Shorten mission end delay"
			},
			["ja"] = new Dictionary<string, string>
			{
				["quick.startup"] = "起動時の演出をスキップ",
				["quick.drop"] = "固定マップの降下待ちを短縮",
				["quick.extraction"] = "離脱時のドロップポッドの帰還演出を短縮",
				["quick.mission_start"] = "ミッション開始前の待ちを短縮",
				["quick.mission_end"] = "ミッション終了後の待ちを短縮"
			}
		};

		private static readonly Dictionary<string, Dictionary<string, string>> Descriptions = new Dictionary<string, Dictionary<string, string>> { ["ja"] = new Dictionary<string, string>
		{
			["quick.startup"] = "起動時の演出をスキップし、音声の読み込み中にタイトルを表示します。変更は次回のゲーム起動時から反映されます。",
			["quick.drop"] = "固定マップの標準の降下時間を短縮します。ホストの設定が全員に適用され、地形生成は待ちます。",
			["quick.extraction"] = "ホストがミッション統計を受信し、参加者全員が生存してドロップポッド内にいる場合に、離脱時の帰還演出を短縮します。それ以外は元の時間になります。",
			["quick.mission_start"] = "全員の読み込みを待った後の固定時間を短縮します。ホストの設定が全員に適用されます。",
			["quick.mission_end"] = "ホストが全員のミッション統計を集めた後、報酬処理前の自分の待ち時間を短縮します。確認できない場合は元の待ち時間になります。"
		} };

		public static object[] Label(string key)
		{
			return ModsMenuTag.Label(key, (IDictionary<string, Dictionary<string, string>>)Names, (IDictionary<string, Dictionary<string, string>>)Descriptions);
		}
	}
	internal static class MissionDelays
	{
		[HarmonyPatch]
		private static class StatsSyncPatch
		{
			[HarmonyPatch(typeof(MissionManager), "OnMissionCompleteCoroutine")]
			[HarmonyPrefix]
			private static void ResetStats()
			{
				_statsReceived = false;
			}

			[HarmonyPatch(typeof(DropPod), "SetMissionData_ClientRpc")]
			[HarmonyPostfix]
			private static void ReceiveStats()
			{
				_statsReceived = true;
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static class DropPatch
		{
			[HarmonyTranspiler]
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				return Drop(instructions);
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static class ExtractionPatch
		{
			[HarmonyTranspiler]
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				return Extraction(instructions);
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static class StartPatch
		{
			[HarmonyTranspiler]
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				return Start(instructions);
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static class EndPatch
		{
			[HarmonyTranspiler]
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				return End(instructions);
			}
		}

		private static bool _statsSyncAvailable;

		private static bool _statsReceived;

		private static bool _extractionFaulted;

		private static readonly FieldInfo LabelsField = AccessTools.Field(typeof(CodeInstruction), "labels");

		internal static void Setup(Harmony harmony)
		{
			try
			{
				harmony.CreateClassProcessor(typeof(StatsSyncPatch)).Patch();
				_statsSyncAvailable = true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Mission end delay: stats synchronization unavailable; using original delay. " + ex.Message));
			}
			Apply(harmony, typeof(DropPatch), "FakeWaitToContinueDrop");
			Apply(harmony, typeof(ExtractionPatch), "LaunchFromMissionCoroutine");
			Apply(harmony, typeof(StartPatch), "StartFirstObjective");
			Apply(harmony, typeof(EndPatch), "OnMissionCompleteCoroutine");
		}

		private static void Apply(Harmony harmony, Type patch, string coroutineName)
		{
			try
			{
				harmony.CreateClassProcessor(patch).Patch();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Mission delay " + coroutineName + " unchanged: " + ex.Message));
			}
		}

		private static bool HasLabels(CodeInstruction instruction)
		{
			if (!(LabelsField == null) && LabelsField.GetValue(instruction) is IList list)
			{
				return list.Count != 0;
			}
			return true;
		}

		private static bool IsLessThan(CodeInstruction instruction)
		{
			if (!(instruction.opcode == OpCodes.Blt))
			{
				return instruction.opcode == OpCodes.Blt_S;
			}
			return true;
		}

		private static bool IsTimeField(FieldInfo field)
		{
			return field.Name.StartsWith("<time>", StringComparison.Ordinal);
		}

		private static bool IsTimeField(CodeInstruction instruction)
		{
			if (instruction.opcode == OpCodes.Ldfld && instruction.operand is FieldInfo field)
			{
				return IsTimeField(field);
			}
			return false;
		}

		private static IEnumerable<CodeInstruction> Drop(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> code = instructions.ToList();
			MethodInfo overrideDuration = AccessTools.Method(typeof(Mission), "OverrideDropDuration", (Type[])null, (Type[])null);
			List<int> matches = (from i in Enumerable.Range(2, code.Count - 5)
				where code[i].opcode == OpCodes.Ldc_R4 && object.Equals(code[i].operand, 6f) && (code[i - 1].opcode == OpCodes.Brtrue || code[i - 1].opcode == OpCodes.Brtrue_S) && CodeInstructionExtensions.Calls(code[i - 2], overrideDuration) && (code[i + 1].opcode == OpCodes.Br || code[i + 1].opcode == OpCodes.Br_S) && code[i + 3].opcode == OpCodes.Stfld && code[i + 3].operand is FieldInfo field && IsTimeField(field)
				select i).ToList();
			ReplaceConstant(code, matches, "DropDuration", "drop");
			return code;
		}

		private static IEnumerable<CodeInstruction> Start(IEnumerable<CodeInstruction> instructions)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			List<CodeInstruction> code = instructions.ToList();
			FieldInfo field = AccessTools.Field(typeof(MissionManager), "timeBeforeStartingMission");
			List<int> list = (from i in Enumerable.Range(1, code.Count - 2)
				where field != null && CodeInstructionExtensions.LoadsField(code[i], field, false) && IsLessThan(code[i + 1]) && i >= 2 && IsTimeField(code[i - 2])
				select i).ToList();
			if (list.Count != 1 || HasLabels(code[list[0] + 1]))
			{
				Plugin.Log.LogWarning((object)"Mission start delay: expected code not found; unchanged.");
				return code;
			}
			code.Insert(list[0] + 1, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(MissionDelays), "StartDuration", (Type[])null, (Type[])null)));
			return code;
		}

		private static IEnumerable<CodeInstruction> Extraction(IEnumerable<CodeInstruction> instructions)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			List<CodeInstruction> code = instructions.ToList();
			FieldInfo field = AccessTools.Field(typeof(DropPod), "extractionTime");
			List<int> list = (from i in Enumerable.Range(2, code.Count - 3)
				where field != null && CodeInstructionExtensions.LoadsField(code[i], field, false) && IsLessThan(code[i + 1]) && IsTimeField(code[i - 2])
				select i).ToList();
			if (list.Count != 1 || HasLabels(code[list[0] + 1]))
			{
				Plugin.Log.LogWarning((object)"Extraction delay: expected code not found; unchanged.");
				return code;
			}
			code.Insert(list[0] + 1, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(MissionDelays), "ExtractionDuration", (Type[])null, (Type[])null)));
			return code;
		}

		private static IEnumerable<CodeInstruction> End(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> code = instructions.ToList();
			FieldInfo rewards = AccessTools.Field(typeof(Global), "rewardInfoBuffer");
			List<int> matches = (from i in Enumerable.Range(1, code.Count - 3)
				where code[i].opcode == OpCodes.Ldc_R4 && object.Equals(code[i].operand, 3.5f) && IsTimeField(code[i - 1]) && IsLessThan(code[i + 1]) && rewards != null && CodeInstructionExtensions.LoadsField(code[i + 2], rewards, false)
				select i).ToList();
			ReplaceConstant(code, matches, "EndDuration", "end");
			return code;
		}

		private static void ReplaceConstant(List<CodeInstruction> code, List<int> matches, string method, string name)
		{
			if (matches.Count != 1)
			{
				Plugin.Log.LogWarning((object)("Mission " + name + " delay: expected code not found; unchanged."));
				return;
			}
			code[matches[0]].opcode = OpCodes.Call;
			code[matches[0]].operand = AccessTools.Method(typeof(MissionDelays), method, (Type[])null, (Type[])null);
		}

		private static float DropDuration()
		{
			if (!Plugin.FastDrop.Value)
			{
				return 6f;
			}
			return 0.01f;
		}

		private static float StartDuration(float original)
		{
			if (!Plugin.FastMissionStart.Value)
			{
				return original;
			}
			return Math.Min(original, 0.01f);
		}

		private static float ExtractionDuration(float original)
		{
			if (!Plugin.FastExtraction.Value || _extractionFaulted || !_statsSyncAvailable || !_statsReceived)
			{
				return original;
			}
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				DropPod instance = DropPod.Instance;
				List<Player> players = GameManager.players;
				if ((Object)(object)singleton == (Object)null || !singleton.IsServer || !singleton.IsListening || (Object)(object)instance == (Object)null || players == null || players.Count == 0 || players.Count < singleton.ConnectedClientsList.Count)
				{
					return original;
				}
				foreach (Player item in players)
				{
					if ((Object)(object)item == (Object)null || !item.IsAlive || !instance.IsPlayerInside(item, false, 16f))
					{
						return original;
					}
				}
				return Math.Min(original, 1f);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Extraction delay check failed; using original duration. " + ex.Message));
				_extractionFaulted = true;
				return original;
			}
		}

		private static float EndDuration()
		{
			if (!Plugin.FastMissionEnd.Value || !_statsSyncAvailable || !_statsReceived)
			{
				return 3.5f;
			}
			return 0.01f;
		}
	}
	[BepInPlugin("f1w3.mycopunk.quicktimers", "Mycopunk Quick Timers", "0.1.2")]
	[BepInDependency("doku_macaron.mycopunk.macaroncore", "1.1.0")]
	[MycoMod(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "f1w3.mycopunk.quicktimers";

		public const string Name = "Mycopunk Quick Timers";

		public const string Version = "0.1.2";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> FastDrop;

		internal static ConfigEntry<bool> FastMissionStart;

		internal static ConfigEntry<bool> FastMissionEnd;

		internal static ConfigEntry<bool> FastExtraction;

		private static ConfigEntry<bool> _fastStartup;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Expected O, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Expected O, but got Unknown
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_fastStartup = ((BaseUnityPlugin)this).Config.Bind<bool>("Startup", "ShortenLogos", true, new ConfigDescription("Skip startup animations and show the title while sound banks load. Changes take effect the next time the game starts.", (AcceptableValueBase)null, MenuText.Label("quick.startup")));
			FastDrop = ((BaseUnityPlugin)this).Config.Bind<bool>("Mission", "ShortenDrop", false, new ConfigDescription("Shorten the default fixed-map drop delay. The host controls this for the lobby; terrain generation is still awaited.", (AcceptableValueBase)null, MenuText.Label("quick.drop")));
			FastExtraction = ((BaseUnityPlugin)this).Config.Bind<bool>("Mission", "ShortenExtraction", false, new ConfigDescription("Shorten the return flight when the host has received mission statistics and all connected players are alive inside the pod. Otherwise retain the original duration.", (AcceptableValueBase)null, MenuText.Label("quick.extraction")));
			FastMissionStart = ((BaseUnityPlugin)this).Config.Bind<bool>("Mission", "ShortenStart", false, new ConfigDescription("Shorten the delay after players have loaded. The host controls this for the lobby.", (AcceptableValueBase)null, MenuText.Label("quick.mission_start")));
			FastMissionEnd = ((BaseUnityPlugin)this).Config.Bind<bool>("Mission", "ShortenEnd", false, new ConfigDescription("Shorten the local delay after the host confirms all mission statistics. If confirmation is unavailable, retain the original delay.", (AcceptableValueBase)null, MenuText.Label("quick.mission_end")));
			_harmony = new Harmony("f1w3.mycopunk.quicktimers");
			StartupTiming.Setup(_harmony);
			MissionDelays.Setup(_harmony);
			if (_fastStartup.Value)
			{
				StartupLogos.Setup(_harmony);
			}
			Log.LogInfo((object)("Mycopunk Quick Timers 0.1.2 loaded (startup=" + _fastStartup.Value + ", drop=" + FastDrop.Value + ", start=" + FastMissionStart.Value + ", end=" + FastMissionEnd.Value + ", extraction=" + FastExtraction.Value + ")."));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class StartupLogos
	{
		private static readonly FieldInfo LabelsField = AccessTools.Field(typeof(CodeInstruction), "labels");

		internal static void Setup(Harmony harmony)
		{
			try
			{
				harmony.CreateClassProcessor(typeof(StartupLogos)).Patch();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Startup logos unchanged: " + ex.Message));
			}
		}

		private static bool IsTimeField(FieldInfo field)
		{
			return field.Name.StartsWith("<time>", StringComparison.Ordinal);
		}

		private static bool HasLabel(CodeInstruction instruction, object label)
		{
			if (LabelsField?.GetValue(instruction) is IList list)
			{
				return list.Contains(label);
			}
			return false;
		}

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0706: Unknown result type (might be due to invalid IL or missing references)
			//IL_0710: Expected O, but got Unknown
			//IL_075f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0769: Expected O, but got Unknown
			List<CodeInstruction> code = instructions.ToList();
			List<int> list = new List<int>();
			List<int> list2 = new List<int>();
			for (int i = 1; i + 3 < code.Count; i++)
			{
				if (code[i].opcode == OpCodes.Ldc_R4 && code[i].operand is float num && code[i - 1].opcode == OpCodes.Ldfld && code[i - 1].operand is FieldInfo field && IsTimeField(field) && (code[i + 1].opcode == OpCodes.Blt || code[i + 1].opcode == OpCodes.Blt_S))
				{
					if (num == 3f)
					{
						list.Add(i);
					}
					else if (num == 1f)
					{
						list2.Add(i);
					}
				}
			}
			FieldInfo fieldInfo = AccessTools.Field(typeof(StartMenu), "pigeonsAtPlayIcon");
			FieldInfo fieldInfo2 = AccessTools.Field(typeof(StartMenu), "devolverIcon");
			MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(Global), "DisablePCFeatures");
			MethodInfo banksLoaded = AccessTools.PropertyGetter(typeof(Global), "AreAllBanksLoaded");
			FieldInfo wipe = AccessTools.Field(typeof(StartMenu), "initializeWipe");
			MethodInfo methodInfo2 = AccessTools.Method(typeof(PlayerInput), "UnlockCursor", Type.EmptyTypes, (Type[])null);
			int num2 = code.FindIndex((CodeInstruction c) => c.opcode == OpCodes.Ldstr && object.Equals(c.operand, "DisableStartup"));
			int num3 = num2 + 9;
			int num4 = ((num3 < code.Count) ? code.FindIndex(num3 + 1, (CodeInstruction c) => CodeInstructionExtensions.Calls(c, banksLoaded)) : (-1));
			int bankBranch = num4 + 1;
			int num5 = ((list.Count == 2) ? code.FindIndex(list[0] + 1, list[1] - list[0] - 1, (CodeInstruction c) => c.opcode == OpCodes.Ldc_R4 && c.operand is float num6 && num6 == 0.6f) : (-1));
			if (list.Count != 2 || list2.Count != 2 || fieldInfo == null || fieldInfo2 == null || methodInfo == null || banksLoaded == null || wipe == null || methodInfo2 == null || !CodeInstructionExtensions.Calls(code[list2[0] + 2], methodInfo2) || !CodeInstructionExtensions.LoadsField(code[list2[1] + 3], wipe, false) || !CodeInstructionExtensions.LoadsField(code[list[0] + 3], fieldInfo, false) || !CodeInstructionExtensions.Calls(code[list[1] + 2], methodInfo) || num5 < 1 || num5 + 3 >= code.Count || !CodeInstructionExtensions.LoadsField(code[num5 + 3], fieldInfo2, false) || code[num5 - 1].opcode != OpCodes.Ldfld || !(code[num5 - 1].operand is FieldInfo field2) || !IsTimeField(field2) || (code[num5 + 1].opcode != OpCodes.Blt && code[num5 + 1].opcode != OpCodes.Blt_S) || num2 < 2 || num3 >= code.Count || !CodeInstructionExtensions.Calls(code[num2 - 2], banksLoaded) || code[num3 - 1].opcode != OpCodes.And || (code[num3].opcode != OpCodes.Brtrue && code[num3].opcode != OpCodes.Brtrue_S) || code[num3].operand == null || num4 < 0 || bankBranch >= code.Count || !HasLabel(code[num4], code[num3].operand) || (code[bankBranch].opcode != OpCodes.Brtrue && code[bankBranch].opcode != OpCodes.Brtrue_S) || code[bankBranch].operand == null || !code.Skip(bankBranch + 1).Select((CodeInstruction instruction, int index) => new { instruction, index }).Any(x => HasLabel(x.instruction, code[bankBranch].operand) && bankBranch + 2 + x.index < code.Count && CodeInstructionExtensions.LoadsField(code[bankBranch + 2 + x.index], wipe, false)))
			{
				Plugin.Log.LogWarning((object)"Startup sequence: expected code not found; unchanged.");
				return code;
			}
			foreach (int item in list)
			{
				code[item].operand = 0f;
			}
			foreach (int item2 in list2)
			{
				code[item2].operand = 0f;
			}
			code[num5].operand = 0f;
			object operand = code[bankBranch].operand;
			code[bankBranch].opcode = OpCodes.Pop;
			code[bankBranch].operand = null;
			code.Insert(bankBranch + 1, new CodeInstruction(OpCodes.Br, operand));
			object operand2 = code[num3].operand;
			code[num3].opcode = OpCodes.Pop;
			code[num3].operand = null;
			code.Insert(num3 + 1, new CodeInstruction(OpCodes.Br, operand2));
			Plugin.Log.LogInfo((object)"Startup sequence: showing the title while sound banks load in the background.");
			return code;
		}
	}
	[HarmonyPatch]
	internal static class StartupTiming
	{
		private static float _firstCheck = -1f;

		private static bool _reported;

		internal static void Setup(Harmony harmony)
		{
			try
			{
				harmony.CreateClassProcessor(typeof(StartupTiming)).Patch();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Startup timing unavailable: " + ex.Message));
			}
		}

		[HarmonyPatch(typeof(Global), "BankCallback")]
		[HarmonyPostfix]
		private static void BankLoaded()
		{
			if (!_reported && _firstCheck >= 0f && (Object)(object)StartMenu.Instance != (Object)null)
			{
				BanksChecked(Global.AreAllBanksLoaded);
			}
		}

		[HarmonyPatch(typeof(StartMenu), "OnMusicLoaded")]
		[HarmonyPostfix]
		private static void MusicLoaded()
		{
			if (!_reported && (Object)(object)StartMenu.Instance != (Object)null)
			{
				Plugin.Log.LogInfo((object)$"Startup timing: menu music ready at {Time.realtimeSinceStartup:F2} s since Unity startup.");
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		private static void BanksChecked(bool __result)
		{
			if (!_reported && !((Object)(object)StartMenu.Instance == (Object)null))
			{
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (_firstCheck < 0f)
				{
					_firstCheck = realtimeSinceStartup;
					Plugin.Log.LogInfo((object)$"Startup timing: first required-bank check at {realtimeSinceStartup:F2} s; ready={__result}.");
				}
				if (__result)
				{
					_reported = true;
					Plugin.Log.LogInfo((object)$"Startup timing: required sound banks ready at {realtimeSinceStartup:F2} s; waited {realtimeSinceStartup - _firstCheck:F2} s after first check.");
				}
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}