Decompiled source of Sailwind Fast Forward v1.1.1

plugins/SailwindFastForward/SailwindFastForward.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("SailwindFastForward")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.1+fd3ba275d1762b0de1dfa2114564b1f6906f2754")]
[assembly: AssemblyProduct("SailwindFastForward")]
[assembly: AssemblyTitle("SailwindFastForward")]
[assembly: AssemblyVersion("1.1.1.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 SailwindFastForward
{
	internal static class AutosavePatch
	{
		private sealed class Branch
		{
			internal readonly int Offset;

			internal Branch(int offset)
			{
				Offset = offset;
			}
		}

		private sealed class Shape
		{
			internal readonly int Offset;

			internal readonly OpCode Opcode;

			internal readonly object Operand;

			internal Shape(int offset, OpCode opcode, object operand)
			{
				Offset = offset;
				Opcode = opcode;
				Operand = operand;
			}
		}

		private const BindingFlags Members = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static FieldInfo Field(Type type, string name)
		{
			return type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		}

		internal static IEnumerable<CodeInstruction> Rewrite(IEnumerable<CodeInstruction> instructions, MethodInfo saveMethod, MethodInfo autosaveMethod)
		{
			List<CodeInstruction> code = instructions.Select((Func<CodeInstruction, CodeInstruction>)((CodeInstruction instruction) => new CodeInstruction(instruction))).ToList();
			Shape[] array = ExpectedUpdate(saveMethod);
			if (code.Count != array.Length)
			{
				throw new InvalidOperationException("Unexpected SaveLoadManager.Update instruction count.");
			}
			int i;
			for (i = 0; i < code.Count; i++)
			{
				Shape shape = array[i];
				object operand = shape.Operand;
				Branch branch = operand as Branch;
				bool flag;
				if (branch != null)
				{
					int num = Array.FindIndex(array, (Shape entry) => entry.Offset == branch.Offset);
					IEnumerable enumerable = ((num < 0) ? null : ((IEnumerable)typeof(CodeInstruction).GetField("labels").GetValue(code[num])));
					flag = enumerable != null && code[i].operand?.GetType().FullName == "System.Reflection.Emit.Label" && enumerable.Cast<object>().Any((object label) => object.Equals(label, code[i].operand));
				}
				else
				{
					flag = object.Equals(code[i].operand, shape.Operand);
				}
				if ((!(code[i].opcode == shape.Opcode) && (!(shape.Operand is Branch) || !(CanonicalBranch(code[i].opcode) == CanonicalBranch(shape.Opcode)))) || !flag)
				{
					throw new InvalidOperationException($"Unexpected SaveLoadManager.Update shape at IL_{shape.Offset:x4}.");
				}
			}
			int index = Array.FindIndex(array, (Shape entry) => entry.Offset == 141);
			code[index].opcode = OpCodes.Call;
			code[index].operand = autosaveMethod;
			return code;
		}

		private static OpCode CanonicalBranch(OpCode opcode)
		{
			if (opcode == OpCodes.Brfalse_S)
			{
				return OpCodes.Brfalse;
			}
			if (opcode == OpCodes.Brtrue_S)
			{
				return OpCodes.Brtrue;
			}
			if (opcode == OpCodes.Ble_S)
			{
				return OpCodes.Ble;
			}
			if (opcode == OpCodes.Bgt_Un_S)
			{
				return OpCodes.Bgt_Un;
			}
			return opcode;
		}

		private static Shape[] ExpectedUpdate(MethodInfo save)
		{
			FieldInfo operand = Field(typeof(SaveLoadManager), "save");
			FieldInfo operand2 = Field(typeof(SaveLoadManager), "saveCompressed");
			FieldInfo operand3 = Field(typeof(SaveLoadManager), "load");
			FieldInfo operand4 = Field(typeof(SaveLoadManager), "gctimer");
			FieldInfo operand5 = Field(typeof(Settings), "autosaveInterval");
			return new Shape[60]
			{
				S(0, OpCodes.Ldarg_0),
				S(1, OpCodes.Ldfld, operand),
				S(6, OpCodes.Brfalse_S, new Branch(22)),
				S(8, OpCodes.Ldarg_0),
				S(9, OpCodes.Ldc_I4_0),
				S(10, OpCodes.Stfld, operand),
				S(15, OpCodes.Ldarg_0),
				S(16, OpCodes.Ldc_I4_0),
				S(17, OpCodes.Call, save),
				S(22, OpCodes.Ldarg_0),
				S(23, OpCodes.Ldfld, operand2),
				S(28, OpCodes.Brfalse_S, new Branch(44)),
				S(30, OpCodes.Ldarg_0),
				S(31, OpCodes.Ldc_I4_0),
				S(32, OpCodes.Stfld, operand2),
				S(37, OpCodes.Ldarg_0),
				S(38, OpCodes.Ldc_I4_1),
				S(39, OpCodes.Call, save),
				S(44, OpCodes.Ldarg_0),
				S(45, OpCodes.Ldfld, operand3),
				S(50, OpCodes.Brfalse_S, new Branch(66)),
				S(52, OpCodes.Ldarg_0),
				S(53, OpCodes.Ldc_I4_0),
				S(54, OpCodes.Stfld, operand3),
				S(59, OpCodes.Ldarg_0),
				S(60, OpCodes.Ldc_I4_0),
				S(61, OpCodes.Call, typeof(SaveLoadManager).GetMethod("LoadGame", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(int) }, null)),
				S(66, OpCodes.Ldsfld, operand5),
				S(71, OpCodes.Ldc_I4_0),
				S(72, OpCodes.Ble_S, new Branch(164)),
				S(74, OpCodes.Ldarg_0),
				S(75, OpCodes.Ldfld, operand4),
				S(80, OpCodes.Ldc_R4, 0f),
				S(85, OpCodes.Bgt_Un_S, new Branch(146)),
				S(87, OpCodes.Ldsfld, Field(typeof(CrateInventoryUI), "instance")),
				S(92, OpCodes.Ldfld, Field(typeof(CrateInventoryUI), "showingUI")),
				S(97, OpCodes.Brtrue_S, new Branch(146)),
				S(99, OpCodes.Ldarg_0),
				S(100, OpCodes.Ldsfld, operand5),
				S(105, OpCodes.Conv_R4),
				S(106, OpCodes.Ldc_R4, 60f),
				S(111, OpCodes.Mul),
				S(112, OpCodes.Stfld, operand4),
				S(117, OpCodes.Ldarg_0),
				S(118, OpCodes.Ldfld, Field(typeof(SaveLoadManager), "enableAutosave")),
				S(123, OpCodes.Brfalse_S, new Branch(146)),
				S(125, OpCodes.Ldsfld, Field(typeof(GameState), "sleeping")),
				S(130, OpCodes.Brtrue_S, new Branch(146)),
				S(132, OpCodes.Ldsfld, Field(typeof(GameState), "recovering")),
				S(137, OpCodes.Brtrue_S, new Branch(146)),
				S(139, OpCodes.Ldarg_0),
				S(140, OpCodes.Ldc_I4_1),
				S(141, OpCodes.Call, save),
				S(146, OpCodes.Ldarg_0),
				S(147, OpCodes.Ldarg_0),
				S(148, OpCodes.Ldfld, operand4),
				S(153, OpCodes.Call, typeof(Time).GetProperty("deltaTime", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetGetMethod(nonPublic: true)),
				S(158, OpCodes.Sub),
				S(159, OpCodes.Stfld, operand4),
				S(164, OpCodes.Ret)
			};
		}

		private static Shape S(int offset, OpCode opcode, object operand = null)
		{
			return new Shape(offset, opcode, operand);
		}
	}
	internal sealed class AutosaveState
	{
		private enum Phase
		{
			Idle,
			AwaitingPrefix,
			AwaitingCancelledPrefix,
			Saving,
			WaitingForCompletion,
			ManualHoldSave
		}

		private Phase phase;

		internal bool TryEnterSave(bool allowManualHold = false, bool busy = false)
		{
			if (phase == Phase.AwaitingPrefix)
			{
				phase = Phase.Saving;
				return true;
			}
			if (phase == Phase.Idle && allowManualHold && !busy)
			{
				phase = Phase.ManualHoldSave;
				return true;
			}
			Reset();
			return false;
		}

		internal void Begin(bool keepFastForward)
		{
			phase = ((keepFastForward && phase == Phase.Idle) ? Phase.AwaitingPrefix : Phase.AwaitingCancelledPrefix);
		}

		internal void End(bool busy)
		{
			phase = ((phase == Phase.Saving && busy) ? Phase.WaitingForCompletion : Phase.Idle);
		}

		internal bool BlocksBusySave(bool busy, bool cancelOnAutosave = false, bool eligibleManualHold = false)
		{
			if (!busy || ((phase == Phase.ManualHoldSave) ? (!eligibleManualHold) : cancelOnAutosave))
			{
				Reset();
			}
			if (busy && phase != Phase.WaitingForCompletion)
			{
				return phase != Phase.ManualHoldSave;
			}
			return false;
		}

		internal void Reset()
		{
			phase = Phase.Idle;
		}
	}
	internal sealed class BackgroundExecution
	{
		private readonly Func<bool> read;

		private readonly Action<bool> write;

		private bool changed;

		internal BackgroundExecution(Func<bool> read, Action<bool> write)
		{
			this.read = read;
			this.write = write;
		}

		internal void Enable()
		{
			if (!changed && !read())
			{
				write(obj: true);
				changed = true;
			}
		}

		internal void Restore()
		{
			if (changed)
			{
				changed = false;
				if (read())
				{
					write(obj: false);
				}
			}
		}
	}
	internal static class GameCompatibility
	{
		internal const string AssemblySha256 = "978A21A680F42C89EBCB3530F9A99EF074960BE6377DAF8E85893134A5E5CE23";

		internal static void RequireSupported(string assemblyPath)
		{
			using FileStream assembly = File.OpenRead(assemblyPath);
			if (!IsSupported(assembly))
			{
				throw new NotSupportedException("This Sailwind assembly has not been verified for fast-forward save handling. Fast-forward is disabled until the game build is reinspected.");
			}
		}

		internal static bool IsSupported(Stream assembly)
		{
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(assembly)).Replace("-", "") == "978A21A680F42C89EBCB3530F9A99EF074960BE6377DAF8E85893134A5E5CE23";
		}
	}
	internal sealed class HoldInput
	{
		private enum Phase
		{
			Idle,
			Active,
			Suppressed
		}

		private Phase phase;

		private bool configured;

		private KeyboardShortcut previousHold;

		private int previousSpeed;

		private int previousMaximum;

		private bool sampledMainHeld;

		internal bool Active => phase == Phase.Active;

		internal void Invalidate()
		{
			phase = Phase.Suppressed;
		}

		internal bool HasIntent(float current, float selected, bool focused, KeyboardShortcut hold, Func<KeyCode, bool> getKeyDown, Func<KeyCode, bool> getKey)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			if (current == selected && HotkeyInput.IsHeld(((KeyboardShortcut)(ref hold)).MainKey, ((KeyboardShortcut)(ref hold)).Modifiers, getKey))
			{
				if (!Active)
				{
					if (phase == Phase.Idle && focused)
					{
						return getKeyDown(((KeyboardShortcut)(ref hold)).MainKey);
					}
					return false;
				}
				return true;
			}
			return false;
		}

		internal SpeedInputAction Dispatch(SpeedOwnership speed, float current, int maximum, int activityMaximum, int effectiveMaximum, bool focused, string blocked, KeyboardShortcut cycle, KeyboardShortcut reset, KeyboardShortcut hold, int holdSpeed, Func<KeyCode, bool> getKeyDown, Func<KeyCode, bool> getKey, Action<string> cancel, Action resetAction)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: 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_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			bool flag = (sampledMainHeld = (int)((KeyboardShortcut)(ref hold)).MainKey != 0 && getKey(((KeyboardShortcut)(ref hold)).MainKey));
			bool flag2 = HotkeyInput.IsHeld(((KeyboardShortcut)(ref hold)).MainKey, ((KeyboardShortcut)(ref hold)).Modifiers, getKey);
			int num;
			if (configured)
			{
				ref KeyboardShortcut reference = ref previousHold;
				object obj = hold;
				num = ((!((object)Unsafe.As<KeyboardShortcut, KeyboardShortcut>(ref reference)/*cast due to .constrained prefix*/).Equals(obj) || previousSpeed != holdSpeed || previousMaximum != maximum) ? 1 : 0);
			}
			else
			{
				num = 0;
			}
			bool flag3 = (byte)num != 0;
			configured = true;
			previousHold = hold;
			previousSpeed = holdSpeed;
			previousMaximum = maximum;
			if (phase == Phase.Suppressed && !flag)
			{
				phase = Phase.Idle;
			}
			if (focused && HotkeyInput.IsDown(((KeyboardShortcut)(ref reset)).MainKey, ((KeyboardShortcut)(ref reset)).Modifiers, getKeyDown, getKey))
			{
				Invalidate();
				resetAction();
				RearmAfterObservedRelease();
				return SpeedInputAction.Reset;
			}
			if (blocked != null)
			{
				return Cancel(cancel, blocked);
			}
			if (speed.Active && current != speed.SelectedSpeed)
			{
				return Cancel(cancel, "timescale changed externally");
			}
			if (speed.SelectedSpeed > (float)maximum)
			{
				return Cancel(cancel, "configured maximum lowered");
			}
			if (flag3 && (Active || flag))
			{
				return Cancel(cancel, "hold settings changed");
			}
			if (Active)
			{
				if (!flag2)
				{
					return Cancel(cancel, "hold released");
				}
				if (!speed.TryLimit(current, Math.Min(holdSpeed, maximum)))
				{
					return SpeedInputAction.None;
				}
				return SpeedInputAction.Limit;
			}
			if (!focused)
			{
				if (flag)
				{
					Invalidate();
				}
				if (!speed.TryLimit(current, effectiveMaximum))
				{
					return SpeedInputAction.None;
				}
				return SpeedInputAction.Limit;
			}
			if (flag)
			{
				if (phase == Phase.Idle && flag2 && getKeyDown(((KeyboardShortcut)(ref hold)).MainKey))
				{
					int requested = Math.Min(holdSpeed, maximum);
					if (speed.TrySelect(current, requested))
					{
						phase = Phase.Active;
						return SpeedInputAction.Hold;
					}
					Invalidate();
					if (!speed.TryLimit(current, effectiveMaximum))
					{
						return SpeedInputAction.Unavailable;
					}
					return SpeedInputAction.Limit;
				}
				Invalidate();
				if (flag2)
				{
					if (!speed.TryLimit(current, effectiveMaximum))
					{
						return SpeedInputAction.None;
					}
					return SpeedInputAction.Limit;
				}
			}
			return HotkeyInput.Dispatch(speed, current, effectiveMaximum, focused, cycle, reset, getKeyDown, getKey, resetAction);
		}

		private SpeedInputAction Cancel(Action<string> cancel, string reason)
		{
			Invalidate();
			cancel(reason);
			RearmAfterObservedRelease();
			return SpeedInputAction.Cancel;
		}

		private void RearmAfterObservedRelease()
		{
			if (!sampledMainHeld)
			{
				phase = Phase.Idle;
			}
		}
	}
	internal enum HoldBoundary
	{
		SleepOrBed,
		Recovery,
		Shipyard,
		Economy,
		CursorMenu,
		Pause,
		Loading,
		OutsideGameplay,
		SaveUnavailable
	}
	internal static class HoldPolicy
	{
		internal static bool CanContinue(HoldInput hold, SpeedOwnership speed, float current, bool chordHeld, bool hardBlocked)
		{
			return !hardBlocked && hold.Active && speed.Active && current == speed.SelectedSpeed && chordHeld;
		}

		internal static bool Blocks(HoldBoundary boundary, bool intentionalHold)
		{
			if ((uint)boundary <= 4u)
			{
				return !intentionalHold;
			}
			return true;
		}
	}
	internal enum SpeedInputAction
	{
		None,
		Reset,
		Limit,
		Cycle,
		Hold,
		Cancel,
		Unavailable
	}
	internal static class HotkeyInput
	{
		internal static bool IsHeld(KeyCode mainKey, IEnumerable<KeyCode> modifiers, Func<KeyCode, bool> getKey)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: 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)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			if ((int)mainKey == 0 || !getKey(mainKey))
			{
				return false;
			}
			foreach (KeyCode modifier in modifiers)
			{
				if (!getKey(modifier))
				{
					return false;
				}
			}
			return true;
		}

		internal static SpeedInputAction Dispatch(SpeedOwnership speed, float current, int effectiveMaximum, bool focused, KeyboardShortcut cycle, KeyboardShortcut reset, Func<KeyCode, bool> getKeyDown, Func<KeyCode, bool> getKey, Action cancel)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (focused && IsDown(((KeyboardShortcut)(ref reset)).MainKey, ((KeyboardShortcut)(ref reset)).Modifiers, getKeyDown, getKey))
			{
				cancel();
				return SpeedInputAction.Reset;
			}
			if (speed.TryLimit(current, effectiveMaximum))
			{
				return SpeedInputAction.Limit;
			}
			if (!focused || !IsDown(((KeyboardShortcut)(ref cycle)).MainKey, ((KeyboardShortcut)(ref cycle)).Modifiers, getKeyDown, getKey) || effectiveMaximum == 1)
			{
				return SpeedInputAction.None;
			}
			if (!speed.TryCycle(current, effectiveMaximum))
			{
				return SpeedInputAction.Unavailable;
			}
			return SpeedInputAction.Cycle;
		}

		internal static bool IsDown(KeyCode mainKey, IEnumerable<KeyCode> modifiers, Func<KeyCode, bool> getKeyDown, Func<KeyCode, bool> getKey)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: 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)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			if ((int)mainKey == 0 || !getKeyDown(mainKey))
			{
				return false;
			}
			foreach (KeyCode modifier in modifiers)
			{
				if (!getKey(modifier))
				{
					return false;
				}
			}
			return true;
		}
	}
	[BepInPlugin("local.sailwind.fastforward", "Sailwind Fast Forward", "1.1.1")]
	[BepInProcess("Sailwind.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal const string Id = "local.sailwind.fastforward";

		private readonly SpeedOwnership speed = new SpeedOwnership();

		private readonly HoldInput holdInput = new HoldInput();

		private readonly AutosaveState autosave = new AutosaveState();

		private readonly BackgroundExecution background = new BackgroundExecution(() => Application.runInBackground, delegate(bool value)
		{
			Application.runInBackground = value;
		});

		private PluginSettings settings;

		private Action resetHotkey;

		private Action<string> cancelInput;

		private Action saveError;

		private Harmony harmony;

		private FieldInfo saveBusy;

		private bool ready;

		private GUIStyle indicatorStyle;

		private static Plugin instance;

		private void Awake()
		{
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Expected O, but got Unknown
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Expected O, but got Unknown
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Expected O, but got Unknown
			//IL_0277: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			instance = this;
			resetHotkey = delegate
			{
				Cancel("reset hotkey");
			};
			cancelInput = Cancel;
			saveError = delegate
			{
				Cancel("save error");
			};
			settings = new PluginSettings(((BaseUnityPlugin)this).Config, delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)message);
			});
			try
			{
				GameCompatibility.RequireSupported(typeof(SaveLoadManager).Assembly.Location);
				saveBusy = AccessTools.Field(typeof(SaveLoadManager), "busy");
				if (saveBusy == null || saveBusy.FieldType != typeof(bool))
				{
					throw new MissingFieldException("SaveLoadManager.busy");
				}
				harmony = new Harmony("local.sailwind.fastforward");
				PatchBoundary(typeof(StartMenu), "GameToSettings", Type.EmptyTypes);
				PatchBoundary(typeof(SaveLoadManager), "SaveGame", new Type[1] { typeof(bool) });
				PatchBoundary(typeof(SaveLoadManager), "LoadGame", new Type[1] { typeof(int) });
				PatchBoundary(typeof(Sleep), "FallAsleep", Type.EmptyTypes);
				MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(SaveLoadManager), "Update", Type.EmptyTypes, (Type[])null);
				if (methodInfo == null)
				{
					throw new MissingMethodException("SaveLoadManager.Update");
				}
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "MarkAutosave", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				Type nestedType = typeof(SaveLoadManager).GetNestedType("<DoSaveGame>d__27", BindingFlags.NonPublic);
				MethodInfo methodInfo2 = ((nestedType == null) ? null : AccessTools.DeclaredMethod(nestedType, "MoveNext", Type.EmptyTypes, (Type[])null));
				if (methodInfo2 == null || methodInfo2.ReturnType != typeof(bool))
				{
					throw new MissingMethodException("SaveLoadManager.<DoSaveGame>d__27.MoveNext");
				}
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "AfterSaveError", (Type[])null), (HarmonyMethod)null);
				SceneManager.activeSceneChanged += OnActiveSceneChanged;
				SceneManager.sceneLoaded += OnSceneLoaded;
				ready = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Ready. {settings.Hotkey.Value} to cycle. {settings.ResetHotkey.Value} to return to 1x. Hold {settings.HoldHotkey.Value} for {Math.Min(settings.HoldSpeed.Value, settings.MaxSpeed.Value)}x.");
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Initialization failed. Fast-forward disabled: {arg}");
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				((Behaviour)this).enabled = false;
			}
		}

		private void PatchBoundary(Type type, string name, Type[] arguments)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			MethodInfo methodInfo = AccessTools.DeclaredMethod(type, name, arguments, (Type[])null);
			if (methodInfo == null)
			{
				throw new MissingMethodException(type.FullName, name);
			}
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Plugin), "BeforeBoundary", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (!(type == typeof(SaveLoadManager)) || !(name == "SaveGame")) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Plugin), "AfterSaveError", (Type[])null), (HarmonyMethod)null);
		}

		private static void BeforeBoundary(MethodBase __originalMethod, object __instance)
		{
			if (!((Object)(object)instance == (Object)null) && instance.ready && instance.settings != null && (!(__originalMethod.DeclaringType == typeof(SaveLoadManager)) || !(__originalMethod.Name == "SaveGame") || !instance.autosave.TryEnterSave(instance.CanContinueHold(), (bool)instance.saveBusy.GetValue(__instance))) && (!(__originalMethod.DeclaringType == typeof(Sleep)) || !(__originalMethod.Name == "FallAsleep") || HoldPolicy.Blocks(HoldBoundary.SleepOrBed, instance.HoldIntent())))
			{
				instance.Cancel(__originalMethod.DeclaringType.Name + "." + __originalMethod.Name);
			}
		}

		private static IEnumerable<CodeInstruction> MarkAutosave(IEnumerable<CodeInstruction> instructions)
		{
			return AutosavePatch.Rewrite(instructions, AccessTools.DeclaredMethod(typeof(SaveLoadManager), "SaveGame", new Type[1] { typeof(bool) }, (Type[])null), AccessTools.DeclaredMethod(typeof(Plugin), "SaveAutosave", (Type[])null, (Type[])null));
		}

		private static Exception AfterSaveError(Exception __exception)
		{
			return SaveErrorBoundary.Handle(__exception, instance?.saveError);
		}

		private static void SaveAutosave(SaveLoadManager manager, bool compressed)
		{
			Plugin plugin = instance;
			if ((Object)(object)plugin == (Object)null || !plugin.ready)
			{
				manager.SaveGame(compressed);
				return;
			}
			bool flag = (bool)plugin.saveBusy.GetValue(manager);
			if (plugin.settings.CancelOnAutosave.Value)
			{
				plugin.Cancel("timer autosave");
			}
			plugin.autosave.Begin(!plugin.settings.CancelOnAutosave.Value && plugin.speed.Active && !flag);
			try
			{
				manager.SaveGame(compressed);
			}
			catch (Exception error)
			{
				SaveErrorBoundary.Handle(error, plugin.saveError);
				throw;
			}
			finally
			{
				plugin.autosave.End((bool)plugin.saveBusy.GetValue(manager));
			}
		}

		private static string HardBlockReason()
		{
			if (!GameState.playing)
			{
				return "outside gameplay";
			}
			if (GameState.currentlyLoading)
			{
				return "save loading";
			}
			if (GameState.justStarted || GameState.changingStartRegion)
			{
				return "world transition";
			}
			if (GameState.wasInSettingsMenu)
			{
				return "settings menu";
			}
			return null;
		}

		private string BlockReason()
		{
			string text = HardBlockReason();
			if (text != null)
			{
				return text;
			}
			bool intentionalHold = HoldIntent();
			if ((GameState.sleeping || GameState.eyesFullyClosed || Sleep.timeskipSleep || GameState.justWokeUp || Object.op_Implicit((Object)(object)GameState.inBed)) && HoldPolicy.Blocks(HoldBoundary.SleepOrBed, intentionalHold))
			{
				return "sleep/bed";
			}
			if (GameState.recovering && HoldPolicy.Blocks(HoldBoundary.Recovery, intentionalHold))
			{
				return "recovery";
			}
			if (Object.op_Implicit((Object)(object)GameState.currentShipyard) && HoldPolicy.Blocks(HoldBoundary.Shipyard, intentionalHold))
			{
				return "shipyard";
			}
			if (Object.op_Implicit((Object)(object)EconomyUI.instance) && EconomyUI.instance.uiActive && HoldPolicy.Blocks(HoldBoundary.Economy, intentionalHold))
			{
				return "economy menu";
			}
			if (GameState.inCursorMenu && !InventoryOpen() && HoldPolicy.Blocks(HoldBoundary.CursorMenu, intentionalHold))
			{
				return "cursor menu";
			}
			if (!Object.op_Implicit((Object)(object)SaveLoadManager.instance))
			{
				return "save unavailable";
			}
			if (autosave.BlocksBusySave((bool)saveBusy.GetValue(SaveLoadManager.instance), settings.CancelOnAutosave.Value, CanContinueHold()))
			{
				return "save busy";
			}
			return null;
		}

		private bool HoldIntent()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (settings != null && HardBlockReason() == null)
			{
				return holdInput.HasIntent(Time.timeScale, speed.SelectedSpeed, Application.isFocused, settings.HoldHotkey.Value, (Func<KeyCode, bool>)Input.GetKeyDown, (Func<KeyCode, bool>)Input.GetKey);
			}
			return false;
		}

		private bool CanContinueHold()
		{
			//IL_001c: 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_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			HoldInput hold = holdInput;
			SpeedOwnership speedOwnership = speed;
			float timeScale = Time.timeScale;
			KeyboardShortcut value = settings.HoldHotkey.Value;
			KeyCode mainKey = ((KeyboardShortcut)(ref value)).MainKey;
			value = settings.HoldHotkey.Value;
			return HoldPolicy.CanContinue(hold, speedOwnership, timeScale, HotkeyInput.IsHeld(mainKey, ((KeyboardShortcut)(ref value)).Modifiers, (Func<KeyCode, bool>)Input.GetKey), HardBlockReason() != null);
		}

		private static bool InventoryOpen()
		{
			if (Object.op_Implicit((Object)(object)PlayerNeedsUI.instance))
			{
				return PlayerNeedsUI.instance.IsActive();
			}
			return false;
		}

		private static bool MovementRequested()
		{
			OVRPlayerController ovrController = Refs.ovrController;
			if (!Application.isFocused || !Object.op_Implicit((Object)(object)ovrController) || !((Behaviour)ovrController).isActiveAndEnabled || !ovrController.EnableLinearMovement)
			{
				return false;
			}
			bool flag = false;
			ovrController.GetHaltUpdateMovement(ref flag);
			if (flag)
			{
				return false;
			}
			if (!GameInput.GetKey((InputName)0) && !GameInput.GetKey((InputName)1) && !GameInput.GetKey((InputName)2) && !GameInput.GetKey((InputName)3) && !GameInput.GetKey((InputName)4) && !(Mathf.Abs(GameInput.GetPrimaryHorizontal()) > 0.1f))
			{
				return Mathf.Abs(GameInput.GetPrimaryVertical()) > 0.1f;
			}
			return true;
		}

		private void Update()
		{
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			if (!ready)
			{
				return;
			}
			try
			{
				string text = BlockReason();
				bool flag = text == null && InventoryOpen();
				bool flag2 = text == null && !flag && MovementRequested();
				int effectiveMaximum = ((flag || flag2) ? Math.Min(settings.MaxSpeed.Value, settings.MovementInventoryMaxSpeed.Value) : settings.MaxSpeed.Value);
				float timeScale = Time.timeScale;
				switch (holdInput.Dispatch(speed, timeScale, settings.MaxSpeed.Value, settings.MovementInventoryMaxSpeed.Value, effectiveMaximum, Application.isFocused, text, settings.Hotkey.Value, settings.ResetHotkey.Value, settings.HoldHotkey.Value, settings.HoldSpeed.Value, (Func<KeyCode, bool>)Input.GetKeyDown, (Func<KeyCode, bool>)Input.GetKey, cancelInput, resetHotkey))
				{
				case SpeedInputAction.Limit:
					ApplySelectedSpeed(timeScale, flag ? "inventory speed limit" : "movement speed limit");
					break;
				case SpeedInputAction.Cycle:
					ApplySelectedSpeed(timeScale, "hotkey");
					break;
				case SpeedInputAction.Hold:
					ApplySelectedSpeed(timeScale, "hold hotkey");
					break;
				case SpeedInputAction.Unavailable:
					((BaseUnityPlugin)this).Logger.LogInfo((object)$"Fast-forward unavailable at native/external scale {Time.timeScale}.");
					break;
				}
			}
			catch (Exception arg)
			{
				Cancel("runtime error");
				((BaseUnityPlugin)this).Logger.LogError((object)$"Fast-forward disabled: {arg}");
				((Behaviour)this).enabled = false;
			}
		}

		private void ApplySelectedSpeed(float previous, string reason)
		{
			Time.timeScale = speed.SelectedSpeed;
			if (speed.Active)
			{
				background.Enable();
			}
			else
			{
				background.Restore();
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"{previous}x -> {Time.timeScale}x ({reason})");
			LogNeeds();
		}

		private void Cancel(string reason)
		{
			holdInput.Invalidate();
			autosave.Reset();
			if (speed.Active)
			{
				float timeScale = Time.timeScale;
				if (speed.Release(timeScale))
				{
					Time.timeScale = 1f;
				}
				background.Restore();
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Fast-forward off ({reason}). Scale {timeScale} -> {Time.timeScale}.");
				LogNeeds();
			}
		}

		private void LogNeeds()
		{
			if (GameState.playing && Object.op_Implicit((Object)(object)Sun.sun) && Object.op_Implicit((Object)(object)PlayerNeeds.instance))
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)($"Needs: realSeconds={Time.realtimeSinceStartup:F1}, day={GameState.day}, hour={Sun.sun.globalTime:F3}, " + $"food={PlayerNeeds.food:F2}, water={PlayerNeeds.water:F2}, rest={PlayerNeeds.sleep:F2}, " + $"sunRate={Sun.sun.timescale:F5}, godMode={PlayerNeeds.instance.godMode}."));
			}
		}

		private void OnGUI()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Expected O, but got Unknown
			if (ready && speed.Active && Time.timeScale == speed.SelectedSpeed && settings.ShowIndicator.Value)
			{
				if (indicatorStyle == null)
				{
					indicatorStyle = new GUIStyle(GUI.skin.box)
					{
						fontSize = 16,
						alignment = (TextAnchor)4
					};
				}
				GUI.Box(new Rect((float)(Screen.width - 76), 16f, 60f, 28f), $"{speed.SelectedSpeed}×", indicatorStyle);
			}
		}

		private void OnActiveSceneChanged(Scene previous, Scene next)
		{
			Cancel("active scene changed");
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if ((int)mode == 0)
			{
				Cancel("world scene loaded");
			}
		}

		private void OnApplicationQuit()
		{
			Cancel("quit");
		}

		private void OnDisable()
		{
			Cancel("plugin disabled");
		}

		private void OnDestroy()
		{
			ready = false;
			Cancel("plugin destroyed");
			SceneManager.activeSceneChanged -= OnActiveSceneChanged;
			SceneManager.sceneLoaded -= OnSceneLoaded;
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
		}
	}
	internal sealed class PluginSettings
	{
		private const string ShortcutHelp = " Add modifiers with +, for example LeftShift + Mouse4. Names ignore capitalization and accept spaces, such as left shift + mouse 4. Leave the value blank to disable this shortcut.";

		internal ShortcutSetting Hotkey { get; }

		internal ShortcutSetting ResetHotkey { get; }

		internal ShortcutSetting HoldHotkey { get; }

		internal ConfigEntry<bool> ShowIndicator { get; }

		internal ConfigEntry<int> MaxSpeed { get; }

		internal ConfigEntry<int> HoldSpeed { get; }

		internal ConfigEntry<int> MovementInventoryMaxSpeed { get; }

		internal ConfigEntry<bool> CancelOnAutosave { get; }

		internal PluginSettings(ConfigFile config, Action<string> warning = null)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Invalid comparison between Unknown and I4
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Invalid comparison between Unknown and I4
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Invalid comparison between Unknown and I4
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Expected O, but got Unknown
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Expected O, but got Unknown
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Expected O, but got Unknown
			Hotkey = new ShortcutSetting(config, "Controls", "Hotkey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Cycle through the allowed fast-forward speeds, then return to normal speed. Add modifiers with +, for example LeftShift + Mouse4. Names ignore capitalization and accept spaces, such as left shift + mouse 4. Leave the value blank to disable this shortcut.", warning);
			KeyboardShortcut value = Hotkey.Value;
			KeyboardShortcut defaultValue = default(KeyboardShortcut);
			((KeyboardShortcut)(ref defaultValue))..ctor((KeyCode)(((int)((KeyboardShortcut)(ref value)).MainKey != 289) ? 289 : 0), Array.Empty<KeyCode>());
			ResetHotkey = new ShortcutSetting(config, "Controls", "ResetHotkey", defaultValue, "Return immediately to normal speed. This takes priority over the cycle and hold shortcuts. Add modifiers with +, for example LeftShift + Mouse4. Names ignore capitalization and accept spaces, such as left shift + mouse 4. Leave the value blank to disable this shortcut.", warning);
			value = Hotkey.Value;
			int num;
			if ((int)((KeyboardShortcut)(ref value)).MainKey != 290)
			{
				value = ResetHotkey.Value;
				if ((int)((KeyboardShortcut)(ref value)).MainKey != 290)
				{
					num = 290;
					goto IL_00ba;
				}
			}
			num = 0;
			goto IL_00ba;
			IL_00ba:
			KeyboardShortcut defaultValue2 = default(KeyboardShortcut);
			((KeyboardShortcut)(ref defaultValue2))..ctor((KeyCode)num, Array.Empty<KeyCode>());
			HoldHotkey = new ShortcutSetting(config, "Controls", "HoldHotkey", defaultValue2, "Fast-forward at HoldSpeed while this key is held. Release it to return to normal speed. Add modifiers with +, for example LeftShift + Mouse4. Names ignore capitalization and accept spaces, such as left shift + mouse 4. Leave the value blank to disable this shortcut.", warning);
			ShowIndicator = config.Bind<bool>("Display", "ShowIndicator", true, "Show the active fast-forward speed.");
			MaxSpeed = config.Bind<int>("Simulation", "MaxSpeed", 4, new ConfigDescription("Maximum fast-forward speed for cycling and holding.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[3] { 2, 4, 8 }), Array.Empty<object>()));
			HoldSpeed = config.Bind<int>("Simulation", "HoldSpeed", 4, new ConfigDescription("Speed requested while holding HoldHotkey, bounded by MaxSpeed. Holding ignores movement and inventory limits.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[3] { 2, 4, 8 }), Array.Empty<object>()));
			MovementInventoryMaxSpeed = config.Bind<int>("Simulation", "MovementInventoryMaxSpeed", 2, new ConfigDescription("Cycle-mode speed limit while moving or viewing inventory, also bounded by MaxSpeed. Holding ignores this limit. 1 turns cycle-mode fast-forward off. 8 adds no lower limit. Ending the activity does not raise speed automatically.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[4] { 1, 2, 4, 8 }), Array.Empty<object>()));
			CancelOnAutosave = config.Bind<bool>("Simulation", "CancelOnAutosave", false, "Turn off fast-forward when an autosave starts, including while holding. When disabled, active fast-forward continues during autosaves. Loading always cancels fast-forward. Manual saves cancel cycle mode but allow an active hold to continue.");
		}
	}
	internal static class SaveErrorBoundary
	{
		internal static Exception Handle(Exception error, Action cancel)
		{
			if (error == null)
			{
				return null;
			}
			try
			{
				cancel?.Invoke();
			}
			catch
			{
			}
			return error;
		}
	}
	internal static class ShortcutParser
	{
		private static readonly Dictionary<string, KeyCode> Names = ((IEnumerable<string>)Enum.GetNames(typeof(KeyCode))).ToDictionary((Func<string, string>)((string name) => name), (Func<string, KeyCode>)((string name) => (KeyCode)Enum.Parse(typeof(KeyCode), name)), (IEqualityComparer<string>?)StringComparer.OrdinalIgnoreCase);

		internal static bool TryParse(string raw, out KeyboardShortcut shortcut, out string error)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			shortcut = KeyboardShortcut.Empty;
			error = null;
			if (string.IsNullOrWhiteSpace(raw))
			{
				return true;
			}
			List<KeyCode> list = new List<KeyCode>();
			string[] array = raw.Split(new char[4] { '+', ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text in array)
			{
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				if (TryKey(text, out var key))
				{
					list.Add(key);
					continue;
				}
				string[] array2 = text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
				foreach (string text2 in array2)
				{
					if (!TryKey(text2, out key))
					{
						error = "Unknown key name '" + text2 + "'. Use names such as O, LeftShift or Mouse4. Separate keys with +.";
						return false;
					}
					list.Add(key);
				}
			}
			if (list.Count == 0)
			{
				error = "No key name was provided. Leave the value blank to disable this shortcut.";
				return false;
			}
			if (list.Contains((KeyCode)0))
			{
				if (list.Count == 1)
				{
					return true;
				}
				error = "None must be used alone to disable a shortcut.";
				return false;
			}
			list = list.Distinct().ToList();
			KeyCode[] array3 = list.Where((KeyCode key2) => !IsModifier(key2)).ToArray();
			KeyCode main = (KeyCode)((array3.Length == 1) ? ((int)array3[0]) : ((int)list[0]));
			shortcut = new KeyboardShortcut(main, list.Where((KeyCode val) => val != main).ToArray());
			return true;
		}

		private static bool TryKey(string text, out KeyCode key)
		{
			return Names.TryGetValue(string.Concat(text.Where((char character) => !char.IsWhiteSpace(character))), out key);
		}

		private static bool IsModifier(KeyCode key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			if (key - 303 <= 10)
			{
				return true;
			}
			return false;
		}
	}
	internal sealed class ShortcutSetting
	{
		private readonly ConfigEntry<string> entry;

		private readonly Action<string> warning;

		private KeyboardShortcut parsed;

		private string lastRaw;

		private bool initialized;

		internal KeyboardShortcut Value
		{
			get
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				return parsed;
			}
			set
			{
				entry.Value = ((KeyboardShortcut)(ref value)).Serialize();
			}
		}

		internal ShortcutSetting(ConfigFile config, string section, string key, KeyboardShortcut defaultValue, string description, Action<string> warning)
		{
			this.warning = warning;
			entry = config.Bind<string>(section, key, ((KeyboardShortcut)(ref defaultValue)).Serialize(), description);
			entry.SettingChanged += delegate
			{
				Refresh();
			};
			Refresh();
		}

		private void Refresh()
		{
			string value = entry.Value;
			if (!initialized || !string.Equals(value, lastRaw, StringComparison.Ordinal))
			{
				initialized = true;
				lastRaw = value;
				if (!ShortcutParser.TryParse(value, out parsed, out var error))
				{
					warning?.Invoke("[" + ((ConfigEntryBase)entry).Definition.Section + "] " + ((ConfigEntryBase)entry).Definition.Key + " is disabled because its shortcut is invalid. The value '" + value + "' was kept unchanged. " + error);
				}
			}
		}
	}
	internal sealed class SpeedOwnership
	{
		internal float SelectedSpeed { get; private set; } = 1f;

		internal bool Active => SelectedSpeed > 1f;

		internal bool TrySelect(float current, int requested)
		{
			if ((requested != 2 && requested != 4 && requested != 8) || current != SelectedSpeed)
			{
				return false;
			}
			SelectedSpeed = requested;
			return true;
		}

		internal bool TryCycle(float current, int maximum)
		{
			if ((maximum != 2 && maximum != 4 && maximum != 8) || current != SelectedSpeed)
			{
				return false;
			}
			SelectedSpeed = ((SelectedSpeed >= (float)maximum) ? 1f : (SelectedSpeed * 2f));
			return true;
		}

		internal bool Release(float current)
		{
			bool result = Active && current == SelectedSpeed;
			SelectedSpeed = 1f;
			return result;
		}

		internal bool TryLimit(float current, int maximum)
		{
			if ((maximum != 1 && maximum != 2 && maximum != 4 && maximum != 8) || !Active || current != SelectedSpeed || SelectedSpeed <= (float)maximum)
			{
				return false;
			}
			SelectedSpeed = maximum;
			return true;
		}
	}
}