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;
}
}
}