Decompiled source of PeglinMaxSpeed v1.0.0

PeglinMaxSpeed.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using Battle.PegBehaviour;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using DG.Tweening;
using HarmonyLib;
using I2.Loc;
using Microsoft.CodeAnalysis;
using PeglinMaxSpeed.Core;
using PeglinUI;
using PeglinUI.SettingsMenu;
using PeglinUI.SettingsMenu.Options;
using Rewired;
using UnityEngine;
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("PeglinMaxSpeed")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+bd6406681939266005cac183aa9615032b4a85ab")]
[assembly: AssemblyProduct("PeglinMaxSpeed")]
[assembly: AssemblyTitle("PeglinMaxSpeed")]
[assembly: AssemblyVersion("1.0.0.0")]
[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 PeglinMaxSpeed
{
	internal static class GameAccess
	{
		internal static FieldRef<TOwner, TField> Field<TOwner, TField>(string name) where TOwner : class
		{
			try
			{
				if (AccessTools.Field(typeof(TOwner), name) != null)
				{
					return AccessTools.FieldRefAccess<TOwner, TField>(name);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Couldn't read " + typeof(TOwner).Name + "." + name + ": " + ex.Message));
				return null;
			}
			Plugin.Log.LogWarning((object)(typeof(TOwner).Name + "." + name + " wasn't found; the part of the mod that uses it is off."));
			return null;
		}

		internal static Action<TOwner> Method<TOwner>(string name)
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(TOwner), name, Type.EmptyTypes, (Type[])null);
				if (methodInfo != null)
				{
					return AccessTools.MethodDelegate<Action<TOwner>>(methodInfo, (object)null, true);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Couldn't call " + typeof(TOwner).Name + "." + name + ": " + ex.Message));
				return null;
			}
			Plugin.Log.LogWarning((object)(typeof(TOwner).Name + "." + name + " wasn't found; the part of the mod that uses it is off."));
			return null;
		}
	}
	internal static class ModLog
	{
		public static void Info(string message)
		{
			if (Plugin.EnableLogging == null || Plugin.EnableLogging.Value)
			{
				Plugin.Log.LogInfo((object)message);
			}
		}
	}
	internal static class PhysicsSteps
	{
		public static object WaitSeconds(float seconds)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			if (!Plugin.FixPhysics.Value)
			{
				return (object)new WaitForSeconds(seconds);
			}
			return WaitSteps(PhysicsTiming.StepsFor(seconds, Time.fixedDeltaTime));
		}

		private static IEnumerator WaitSteps(int steps)
		{
			for (int i = 0; i < steps; i++)
			{
				yield return (object)new WaitForFixedUpdate();
			}
		}

		public static object NextStep()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			if (!Plugin.FixPhysics.Value || !(Time.timeScale > 0f))
			{
				return null;
			}
			return (object)new WaitForFixedUpdate();
		}
	}
	internal class FixedStepRunner : MonoBehaviour
	{
		private Component _owner;

		private Action _step;

		internal static void Attach(Component owner, Action step)
		{
			if (!owner.GetComponents<FixedStepRunner>().Any((FixedStepRunner runner) => (Object)(object)runner._owner == (Object)(object)owner))
			{
				FixedStepRunner fixedStepRunner = owner.gameObject.AddComponent<FixedStepRunner>();
				fixedStepRunner._owner = owner;
				fixedStepRunner._step = step;
			}
		}

		private void FixedUpdate()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.FixPhysics.Value && (Object)(object)_owner != (Object)null && ((Behaviour)_owner).enabled)
			{
				_step();
			}
		}
	}
	internal static class WaitTranspiler
	{
		private static readonly ConstructorInfo WaitForSecondsCtor = AccessTools.Constructor(typeof(WaitForSeconds), new Type[1] { typeof(float) }, false);

		internal static IEnumerable<CodeInstruction> ReplaceWaitForSeconds(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			int replaced = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Is(instruction, OpCodes.Newobj, (MemberInfo)WaitForSecondsCtor))
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = AccessTools.Method(typeof(PhysicsSteps), "WaitSeconds", (Type[])null, (Type[])null);
					replaced++;
				}
				yield return instruction;
			}
			if (replaced == 0)
			{
				Plugin.Log.LogWarning((object)("No timed wait found in " + original.DeclaringType?.Name + "; that fix isn't applied on this game version."));
			}
		}

		internal static IEnumerable<CodeInstruction> ReplaceFirstFrameWait(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			List<CodeInstruction> list = instructions.ToList();
			for (int i = 0; i < list.Count - 1; i++)
			{
				if (list[i].opcode == OpCodes.Ldnull && list[i + 1].opcode == OpCodes.Stfld && list[i + 1].operand is FieldInfo fieldInfo && fieldInfo.Name.Contains("current"))
				{
					list[i] = CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(PhysicsSteps), "NextStep", (Type[])null, (Type[])null)), list[i]);
					return list;
				}
			}
			Plugin.Log.LogWarning((object)("No frame wait found in " + original.DeclaringType?.Name + "; that fix isn't applied on this game version."));
			return list;
		}
	}
	[HarmonyPatch(typeof(LongPeg), "OnEnable")]
	internal static class LongPegTimerPatch
	{
		private static readonly Action<LongPeg> GameUpdate = GameAccess.Method<LongPeg>("Update");

		internal static readonly HashSet<LongPeg> OnPhysicsStep = new HashSet<LongPeg>();

		internal static bool Stepping;

		private static bool Prepare()
		{
			return GameUpdate != null;
		}

		private static void Postfix(LongPeg __instance)
		{
			OnPhysicsStep.RemoveWhere((LongPeg peg) => (Object)(object)peg == (Object)null);
			OnPhysicsStep.Add(__instance);
			FixedStepRunner.Attach((Component)(object)__instance, delegate
			{
				Stepping = true;
				try
				{
					GameUpdate(__instance);
				}
				finally
				{
					Stepping = false;
				}
			});
		}
	}
	[HarmonyPatch(typeof(LongPeg), "Update")]
	internal static class LongPegSkipFrameTimerPatch
	{
		private static bool Prefix(LongPeg __instance)
		{
			if (Plugin.FixPhysics.Value && !LongPegTimerPatch.Stepping)
			{
				return !LongPegTimerPatch.OnPhysicsStep.Contains(__instance);
			}
			return true;
		}
	}
	[HarmonyPatch]
	internal static class SlimeOnlyPegColliderWaitPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.Method(typeof(SlimeOnlyPeg), "WaitAndEnablePoppedColliders", (Type[])null, (Type[])null));
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			return WaitTranspiler.ReplaceWaitForSeconds(instructions, original);
		}
	}
	[HarmonyPatch(typeof(FlickeringPeg), "StartFlickerSequence")]
	internal static class FlickeringPegFadePatch
	{
		private static readonly Action<FlickeringPeg> GameUpdate = GameAccess.Method<FlickeringPeg>("Update");

		private static bool Prepare()
		{
			return GameUpdate != null;
		}

		private static void Postfix(FlickeringPeg __instance, Sequence ____fadeSeq)
		{
			if (Plugin.FixPhysics.Value)
			{
				if (____fadeSeq != null)
				{
					TweenSettingsExtensions.SetUpdate<Sequence>(____fadeSeq, (UpdateType)2);
				}
				FixedStepRunner.Attach((Component)(object)__instance, delegate
				{
					GameUpdate(__instance);
				});
			}
		}
	}
	[HarmonyPatch]
	internal static class FlickeringPegStartDelayPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.Method(typeof(FlickeringPeg), "DelayStartSeq", (Type[])null, (Type[])null));
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			return WaitTranspiler.ReplaceWaitForSeconds(instructions, original);
		}
	}
	[HarmonyPatch]
	internal static class BombSetupWaitPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.Method(typeof(Bomb), "Start", (Type[])null, (Type[])null));
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			return WaitTranspiler.ReplaceFirstFrameWait(instructions, original);
		}
	}
	[HarmonyPatch(typeof(PachinkoBall), "FixedUpdate")]
	internal static class CapOrbSpeedEachStep
	{
		private const float MaxOrbSpeed = 50f;

		private static void Postfix(PachinkoBall __instance, Rigidbody2D ____rigid)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			//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_003e: 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_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.FixPhysics.Value && !__instance.IsDummy && !((Object)(object)____rigid == (Object)null) && (int)__instance.CurrentState == 2)
			{
				Vector2 velocity = ____rigid.velocity;
				if (((Vector2)(ref velocity)).magnitude > 50f)
				{
					velocity = ____rigid.velocity;
					____rigid.velocity = ((Vector2)(ref velocity)).normalized * 50f;
				}
			}
		}
	}
	[HarmonyPatch(typeof(GhostOrbPachinko), "OnEnable")]
	internal static class GhostOrbStepPatch
	{
		private static bool Prepare()
		{
			return GhostOrbStepper.IsDummy != null;
		}

		private static void Postfix(GhostOrbPachinko __instance)
		{
			if ((Object)(object)((Component)__instance).GetComponent<GhostOrbStepper>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<GhostOrbStepper>();
			}
		}
	}
	internal class GhostOrbStepper : MonoBehaviour
	{
		internal static readonly FieldRef<GhostOrbPachinko, bool> IsDummy = GameAccess.Field<GhostOrbPachinko, bool>("_isDummy");

		private GhostOrbPachinko _ghostOrb;

		private PachinkoBall _orb;

		private void Awake()
		{
			_ghostOrb = ((Component)this).GetComponent<GhostOrbPachinko>();
			_orb = ((Component)this).GetComponent<PachinkoBall>();
		}

		private void FixedUpdate()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			bool flag = IsDummy.Invoke(_ghostOrb) || ((Object)(object)_orb != (Object)null && _orb.IsDummy);
			if (Plugin.FixPhysics.Value && (Object)(object)_ghostOrb != (Object)null && ((Behaviour)_ghostOrb).enabled && !flag)
			{
				_ghostOrb.DoUpdate(Physics2D.defaultPhysicsScene);
			}
		}
	}
	[HarmonyPatch(typeof(PachinkoBall), "WaitAndTrackLastPegHit")]
	internal static class TrackLastPegHitEachStep
	{
		private static bool Prefix(PachinkoBall __instance, GameObject hitPeg, ref IEnumerator __result)
		{
			if (!Plugin.FixPhysics.Value)
			{
				return true;
			}
			__result = TrackAfterPhysicsStep(__instance, hitPeg);
			return false;
		}

		private static IEnumerator TrackAfterPhysicsStep(PachinkoBall orb, GameObject hitPeg)
		{
			yield return (object)new WaitForFixedUpdate();
			orb.lastHitPeg = hitPeg;
		}
	}
	[BepInPlugin("PeglinMaxSpeed", "PeglinMaxSpeed", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "PeglinMaxSpeed";

		public const string Name = "PeglinMaxSpeed";

		public const string Version = "1.0.0";

		public const string SupportedGameVersion = "2.0.12";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> FixPhysics;

		internal static ConfigEntry<string> SpeedsSetting;

		internal static ConfigEntry<bool> HideSpeedWarning;

		internal static ConfigEntry<bool> CycleSpeeds;

		internal static ConfigEntry<bool> RememberSpeedup;

		internal static ConfigEntry<bool> EnableLogging;

		internal static List<float> Speeds = new List<float>();

		private void CheckGameVersion()
		{
			string version = Application.version;
			if (!(version == "2.0.12"))
			{
				Log.LogWarning((object)("PeglinMaxSpeed 1.0.0 was built for Peglin v2.0.12, but this is Peglin v" + version + ". The fixes and extra speeds may not work correctly until the mod is updated for this game version."));
			}
		}

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			CheckGameVersion();
			FixPhysics = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "FixPhysics", true, "Fix the collision bugs the game warns about when sped up to 3x or faster. Has no visible effect at 1x.");
			SpeedsSetting = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Speeds", "2,3,5,6", "Comma-separated speeds offered in Options > Speedup Amount and by the speedup key, in place of the game's own (1.5, 2, 3). 1x (speedup off) is always available. Up to " + SpeedOptions.Format(6f) + "x, the fastest the game can run at 60 fps. Leave empty to keep the game's own speeds.");
			HideSpeedWarning = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "HideSpeedWarning", true, "Hide the game's \"3x speed may cause bugs with physics collisions\" warning in the options menu. Only hidden while FixPhysics is on.");
			CycleSpeeds = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "CycleSpeeds", true, "Each press of the speedup key (or the speed button) moves to the next speed: 1x, then every speed in the options menu, then back to 1x. Only when Speedup Controls is set to toggle; when it's set to hold, the key works as normal.");
			RememberSpeedup = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RememberSpeedup", true, "Start each run (new or continued) sped up if you were sped up when you left, even after restarting the game. Peglin means to do this but always starts at 1x.");
			EnableLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableLogging", true, "Write details (speed options, saved speed changes) to BepInEx/LogOutput.log. Warnings and errors are always logged.");
			(List<float> Chosen, float[] Cycle, bool Unusable) tuple = SpeedOptions.Resolve(SpeedsSetting.Value);
			List<float> item = tuple.Chosen;
			float[] item2 = tuple.Cycle;
			bool item3 = tuple.Unusable;
			Speeds = item;
			SpeedCycle.Speeds = item2;
			if (item3)
			{
				Log.LogWarning((object)("Speeds \"" + SpeedsSetting.Value + "\" has no usable speeds (each must be above 1 and at most " + SpeedOptions.Format(6f) + "); using the game's own speeds."));
			}
			var (num, num2) = ApplyPatches();
			ModLog.Info($"Physics step {Time.fixedDeltaTime}s, max frame time {Time.maximumDeltaTime}s.");
			Log.LogInfo((object)((num2 == 0) ? string.Format("{0} {1} loaded ({2} patches applied).", "PeglinMaxSpeed", "1.0.0", num) : string.Format("{0} {1} loaded, but {2} of {3} patches weren't applied; see the warnings above.", "PeglinMaxSpeed", "1.0.0", num2, num + num2)));
		}

		private (int Applied, int NotApplied) ApplyPatches()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			Harmony val = new Harmony("PeglinMaxSpeed");
			int num = 0;
			int num2 = 0;
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				if (!type.IsDefined(typeof(HarmonyAttribute), inherit: true))
				{
					continue;
				}
				try
				{
					if (val.CreateClassProcessor(type).Patch().Count > 0)
					{
						num++;
						continue;
					}
					num2++;
					Log.LogWarning((object)("Skipped " + type.Name + ", so that part of the mod is off."));
				}
				catch (Exception ex)
				{
					num2++;
					Log.LogWarning((object)("Couldn't apply " + type.Name + ", so that part of the mod is off: " + ex.GetBaseException().Message));
				}
			}
			return (Applied: num, NotApplied: num2);
		}
	}
	internal static class SpeedCycle
	{
		internal const int SpeedupAction = 24;

		internal static float[] Speeds = SpeedOptions.GameSpeeds;

		private const string SpeedupLevelKey = "SpeedupLevel";

		private static readonly FieldRef<SettingsManager, float> LastSpeedupLevel = GameAccess.Field<SettingsManager, float>("_lastSpeedupLevel");

		internal static bool Active
		{
			get
			{
				if (Plugin.CycleSpeeds.Value && (Object)(object)SettingsManager.Instance != (Object)null)
				{
					return SettingsManager.Instance.SpeedupToggle;
				}
				return false;
			}
		}

		internal static void Step(TimescaleManager manager)
		{
			SettingsManager instance = SettingsManager.Instance;
			float num = SpeedOptions.NextInCycle(Speeds, manager.isSpedUp, instance.SpeedupLevel);
			if (num == 1f)
			{
				Time.timeScale = 1f;
				manager.isSpedUp = false;
				return;
			}
			instance.SpeedupLevel = num;
			if (LastSpeedupLevel != null)
			{
				LastSpeedupLevel.Invoke(instance) = num;
			}
			PlayerPrefs.SetFloat("SpeedupLevel", num);
			PlayerPrefs.Save();
			SettingsChangedEvent onSettingsChangedEvent = SettingsManager.OnSettingsChangedEvent;
			if (onSettingsChangedEvent != null)
			{
				onSettingsChangedEvent.Invoke();
			}
			Time.timeScale = num;
			manager.isSpedUp = true;
		}
	}
	[HarmonyPatch(typeof(TimescaleManager), "Update")]
	internal static class CycleOnSpeedupKeyPatch
	{
		private static bool Prefix(TimescaleManager __instance, Player ____player, bool ____speedupToggleBlocked)
		{
			if (!SpeedCycle.Active)
			{
				return true;
			}
			if (!____speedupToggleBlocked && !PauseMenu.Paused && ____player != null && ____player.GetButtonDown(24))
			{
				SpeedCycle.Step(__instance);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(SpeedupIndicator), "ToggleSpeedup")]
	internal static class CycleOnSpeedButtonPatch
	{
		private static readonly FieldRef<TimescaleManager, bool> ToggleBlocked = GameAccess.Field<TimescaleManager, bool>("_speedupToggleBlocked");

		private static bool Prepare()
		{
			return ToggleBlocked != null;
		}

		private static bool Prefix()
		{
			TimescaleManager instance = TimescaleManager.Instance;
			if (!SpeedCycle.Active || (Object)(object)instance == (Object)null)
			{
				return true;
			}
			if (!ToggleBlocked.Invoke(instance) && !PauseMenu.Paused)
			{
				SpeedCycle.Step(instance);
			}
			return false;
		}
	}
	internal static class SpeedMemory
	{
		internal const string SpedUpKey = "PeglinMaxSpeed.SpedUp";

		internal const string RunStartScene = "PostMainMenu";

		internal static bool HandlingSceneLoad;
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class RememberSpeedupStatePatch
	{
		private static void Postfix(TimescaleManager __instance, bool value)
		{
			if (Plugin.RememberSpeedup.Value && !SpeedMemory.HandlingSceneLoad && (Object)(object)__instance == (Object)(object)TimescaleManager.Instance)
			{
				PlayerPrefs.SetInt("PeglinMaxSpeed.SpedUp", value ? 1 : 0);
				PlayerPrefs.Save();
			}
		}
	}
	[HarmonyPatch(typeof(TimescaleManager), "HandleSceneLoaded")]
	internal static class RestoreSpeedupOnRunStartPatch
	{
		private static void Prefix()
		{
			SpeedMemory.HandlingSceneLoad = true;
		}

		private static void Postfix(TimescaleManager __instance, Scene scene)
		{
			if (!((Object)(object)__instance != (Object)(object)TimescaleManager.Instance) && !((Object)(object)SettingsManager.Instance == (Object)null) && RunStart.ShouldRestoreSpeedup(Plugin.RememberSpeedup.Value, ((Scene)(ref scene)).name == "PostMainMenu", __instance.isSpedUp, PlayerPrefs.GetInt("PeglinMaxSpeed.SpedUp", 0) == 1))
			{
				Time.timeScale = SettingsManager.Instance.SpeedupLevel;
				__instance.isSpedUp = true;
				ModLog.Info("Starting the run at " + SpeedOptions.Format(SettingsManager.Instance.SpeedupLevel) + "x, the speed you left at.");
			}
		}

		private static void Finalizer()
		{
			SpeedMemory.HandlingSceneLoad = false;
		}
	}
	[HarmonyPatch(typeof(OptionsMenu), "OnEnable")]
	internal static class SetSpeedOptionsPatch
	{
		private static void Prefix(OptionsMenu __instance)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			CarouselOptionsFloat speedupAmountOption = __instance.SpeedupAmountOption;
			if ((Object)(object)speedupAmountOption == (Object)null || speedupAmountOption.FloatValues == null || ((CarouselOptions)speedupAmountOption).textItems == null || speedupAmountOption.FloatValues.Length == 0 || speedupAmountOption.FloatValues.Length != ((CarouselOptions)speedupAmountOption).textItems.Length)
			{
				Plugin.Log.LogWarning((object)"The speed option in the options menu isn't laid out as expected; its speeds weren't changed.");
				return;
			}
			int num = speedupAmountOption.FloatValues.Length - 1;
			CarouselTextItem val = ((CarouselOptions)speedupAmountOption).textItems[num];
			string template = (val.isTranslatable ? LocalizationManager.GetTranslation(val.item, true, 0, true, false, (GameObject)null, (string)null, true) : val.item);
			float templateValue = speedupAmountOption.FloatValues[num];
			float[] floatValues = speedupAmountOption.FloatValues;
			(float[] Values, CarouselTextItem[] Labels) tuple = SpeedOptions.Choose<CarouselTextItem>(speedupAmountOption.FloatValues, ((CarouselOptions)speedupAmountOption).textItems, Plugin.Speeds, (float speed) => new CarouselTextItem
			{
				item = SpeedOptions.Label(template, templateValue, speed),
				isTranslatable = false
			});
			float[] item = tuple.Values;
			CarouselTextItem[] item2 = tuple.Labels;
			speedupAmountOption.FloatValues = item;
			((CarouselOptions)speedupAmountOption).textItems = item2;
			if (!item.SequenceEqual(floatValues))
			{
				ModLog.Info("Speed options: " + string.Join(", ", item.Select(SpeedOptions.Format)) + ".");
			}
		}
	}
	[HarmonyPatch(typeof(SettingsManager), "Awake")]
	internal static class SnapSavedSpeedPatch
	{
		private static void Postfix(SettingsManager __instance)
		{
			float speedupLevel = __instance.SpeedupLevel;
			float? num = SpeedOptions.SnapSaved(SpeedCycle.Speeds, speedupLevel);
			if (!((Object)(object)SettingsManager.Instance != (Object)(object)__instance) && num.HasValue)
			{
				__instance.SpeedupLevel = num.Value;
				ModLog.Info("Saved speed " + SpeedOptions.Format(speedupLevel) + "x isn't an option; using " + SpeedOptions.Format(num.Value) + "x.");
			}
		}
	}
	internal static class SpeedWarning
	{
		internal static void Hide(OptionsMenu menu)
		{
			if (Plugin.FixPhysics.Value && Plugin.HideSpeedWarning.Value && (Object)(object)menu.SpeedupWarning != (Object)null)
			{
				menu.SpeedupWarning.SetActive(false);
			}
		}
	}
	[HarmonyPatch(typeof(OptionsMenu), "OnEnable")]
	internal static class HideSpeedWarningOnOpenPatch
	{
		private static void Postfix(OptionsMenu __instance)
		{
			SpeedWarning.Hide(__instance);
		}
	}
	[HarmonyPatch(typeof(OptionsMenu), "ApplySpeedupAmount")]
	internal static class HideSpeedWarningOnChangePatch
	{
		private static void Postfix(OptionsMenu __instance)
		{
			SpeedWarning.Hide(__instance);
		}
	}
}
namespace PeglinMaxSpeed.Core
{
	public static class PhysicsTiming
	{
		public static int StepsFor(float seconds, float step)
		{
			if (seconds <= 0f || step <= 0f)
			{
				return 0;
			}
			double num = seconds / step;
			return (int)Math.Ceiling(num - Math.Max(0.0001, num * 1E-06));
		}
	}
	public static class RunStart
	{
		public static bool ShouldRestoreSpeedup(bool rememberSetting, bool isRunStartScene, bool alreadySpedUp, bool leftSpedUp)
		{
			return rememberSetting && isRunStartScene && !alreadySpedUp && leftSpedUp;
		}
	}
	public static class SpeedOptions
	{
		public const float MaxSpeed = 6f;

		public const string DefaultSpeeds = "2,3,5,6";

		public static readonly float[] GameSpeeds = new float[3] { 1.5f, 2f, 3f };

		public const float NormalSpeed = 1f;

		public static List<float> Parse(string csv)
		{
			List<float> list = new List<float>();
			if (string.IsNullOrWhiteSpace(csv))
			{
				return list;
			}
			string[] array = csv.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				if (float.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && result > 1f && result <= 6f && !list.Contains(result))
				{
					list.Add(result);
				}
			}
			list.Sort();
			return list;
		}

		public static (float[] Values, T[] Labels) Choose<T>(float[] values, T[] labels, IReadOnlyList<float> chosen, Func<float, T> makeLabel)
		{
			if (values.Length != labels.Length)
			{
				throw new ArgumentException("Every speed needs exactly one label.");
			}
			if (chosen.Count == 0)
			{
				return (Values: values, Labels: labels);
			}
			T[] item = chosen.Select(delegate(float speed)
			{
				int num = Array.IndexOf(values, speed);
				return (num < 0) ? makeLabel(speed) : labels[num];
			}).ToArray();
			return (Values: chosen.ToArray(), Labels: item);
		}

		public static (List<float> Chosen, float[] Cycle, bool Unusable) Resolve(string csv)
		{
			List<float> list = Parse(csv);
			float[] item = ((list.Count > 0) ? list.ToArray() : GameSpeeds.ToArray());
			bool item2 = list.Count == 0 && !string.IsNullOrWhiteSpace(csv);
			return (Chosen: list, Cycle: item, Unusable: item2);
		}

		public static float? SnapSaved(IReadOnlyList<float> offered, float saved)
		{
			if (offered.Count == 0 || offered.Contains(saved))
			{
				return null;
			}
			return Nearest(offered, saved);
		}

		public static float Nearest(IReadOnlyList<float> speeds, float speed)
		{
			if (speeds.Count == 0)
			{
				return speed;
			}
			return (from option in speeds
				orderby Math.Abs(option - speed), option
				select option).First();
		}

		public static string Label(string template, float templateValue, float value)
		{
			string text = Format(value);
			if (!string.IsNullOrEmpty(template))
			{
				Regex regex = new Regex("(?<![\\d.,])" + Regex.Escape(Format(templateValue)) + "(?![\\d.,])");
				if (regex.IsMatch(template))
				{
					return regex.Replace(template, text, 1);
				}
			}
			return text + "x";
		}

		public static float NextInCycle(IReadOnlyList<float> speeds, bool spedUp, float currentSpeed)
		{
			if (speeds.Count == 0)
			{
				return 1f;
			}
			if (!spedUp)
			{
				return speeds[0];
			}
			foreach (float speed in speeds)
			{
				if (speed > currentSpeed)
				{
					return speed;
				}
			}
			return 1f;
		}

		public static string Format(float value)
		{
			return value.ToString("0.##", CultureInfo.InvariantCulture);
		}
	}
}