Decompiled source of Slow Motion and Fast Forward Game Speed v1.0.0

GameSpeed.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using OffTheClock.Shared;
using Rewired;
using RoR2;
using RoR2.UI;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("GameSpeed")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("GameSpeed")]
[assembly: AssemblyTitle("GameSpeed")]
[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 GameSpeed
{
	[BepInPlugin("offtheclock.ror2gamespeed", "Slow Motion and Fast Forward - Game Speed", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		private ConfigEntry<float> _speed;

		private ConfigEntry<string> _stepsText;

		private ConfigEntry<KeyCode> _slowerKey;

		private ConfigEntry<KeyCode> _fasterKey;

		private ConfigEntry<KeyCode> _resetKey;

		private ConfigEntry<bool> _showNotice;

		private float[] _steps;

		private Notice _notice;

		private float _applied = 1f;

		private bool _inRunSeen;

		private bool _refusalLogged;

		private bool _keysBroken;

		private bool _applyBroken;

		private static readonly float[] DefaultSteps = new float[7] { 0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 2f };

		private void Awake()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Expected O, but got Unknown
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			_notice = new Notice("Slow Motion and Fast Forward - Game Speed");
			_speed = ((BaseUnityPlugin)this).Config.Bind<float>("Speed", "Speed", 1f, new ConfigDescription("Game speed in solo runs. 1 = normal, 0.5 = half speed (slow motion), 2 = double speed. The keys below change this value and it is saved. Everything runs at this speed, including enemies and the difficulty timer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), Array.Empty<object>()));
			_stepsText = ((BaseUnityPlugin)this).Config.Bind<string>("Speed", "Steps", "0.25, 0.5, 0.75, 1, 1.25, 1.5, 2", "Speeds the Slower/Faster keys step through (0.1 to 3, comma-separated).");
			_slowerKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keys", "SlowerKey", (KeyCode)269, "Step down to the next slower speed. None = no key.");
			_fasterKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keys", "FasterKey", (KeyCode)270, "Step up to the next faster speed. None = no key.");
			_resetKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keys", "NormalSpeedKey", (KeyCode)256, "Back to normal speed (1x). Numpad digits need Num Lock on. None = no key.");
			_showNotice = ((BaseUnityPlugin)this).Config.Bind<bool>("Keys", "ShowOnScreenNotice", true, "Show the new speed on screen for a moment after a key press (it is always written to the log).");
			_steps = Steps.Parse(_stepsText.Value, 0.1f, 3f, DefaultSteps, Log, "Slow Motion and Fast Forward - Game Speed", "Steps");
			((BaseUnityPlugin)this).Config.SettingChanged += delegate(object _, SettingChangedEventArgs e)
			{
				if ((object)e.ChangedSetting == _stepsText)
				{
					_steps = Steps.Parse(_stepsText.Value, 0.1f, 3f, DefaultSteps, Log, "Slow Motion and Fast Forward - Game Speed", "Steps");
				}
			};
			Honesty.MarkRunChanging(new Harmony("offtheclock.ror2gamespeed"), Log, "Slow Motion and Fast Forward - Game Speed");
			Log.LogInfo((object)("Slow Motion and Fast Forward - Game Speed 1.0.0 loaded - speed x" + Steps.F(_speed.Value) + " in solo runs; " + KeyCheck.KeyName(_slowerKey.Value) + " slower, " + KeyCheck.KeyName(_fasterKey.Value) + " faster, " + KeyCheck.KeyName(_resetKey.Value) + " normal (steps " + Steps.Text(_steps) + "); online and split-screen stay at normal speed."));
		}

		private void Update()
		{
			ReadKeys();
			Apply();
		}

		private void ReadKeys()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if (_keysBroken)
			{
				return;
			}
			try
			{
				float value = _speed.Value;
				float num = value;
				if (KeyCheck.Pressed(_slowerKey.Value))
				{
					num = Steps.Down(_steps, value);
				}
				else if (KeyCheck.Pressed(_fasterKey.Value))
				{
					num = Steps.Up(_steps, value);
				}
				else
				{
					if (!KeyCheck.Pressed(_resetKey.Value))
					{
						return;
					}
					num = 1f;
				}
				_speed.Value = num;
				bool flag = Session.InRun && Session.IsSolo;
				string text = ((Session.InRun && !flag) ? (" - not applied: " + Session.Describe() + " (solo only)") : "");
				Log.LogInfo((object)("Slow Motion and Fast Forward - Game Speed: speed now x" + Steps.F(num) + text + "."));
				_notice.Show((Session.InRun && !flag) ? ("Speed x" + Steps.F(num) + " (solo only)") : ("Speed x" + Steps.F(num)));
			}
			catch (Exception ex)
			{
				_keysBroken = true;
				Log.LogWarning((object)("Slow Motion and Fast Forward - Game Speed: key reading stopped (" + ex.Message + "); the config value still works."));
			}
		}

		private void Apply()
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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)
			if (_applyBroken)
			{
				return;
			}
			try
			{
				bool inRun = Session.InRun;
				if (inRun && !_inRunSeen)
				{
					_inRunSeen = true;
					_refusalLogged = false;
					KeyCheck.Check(Log, "Slow Motion and Fast Forward - Game Speed", new List<KeyValuePair<string, KeyCode>>
					{
						new KeyValuePair<string, KeyCode>("slower", _slowerKey.Value),
						new KeyValuePair<string, KeyCode>("faster", _fasterKey.Value),
						new KeyValuePair<string, KeyCode>("normal speed", _resetKey.Value)
					});
				}
				else if (!inRun && _inRunSeen)
				{
					_inRunSeen = false;
				}
				float num = Mathf.Clamp(_speed.Value, 0.1f, 3f);
				bool flag = inRun && Session.IsSolo;
				if (inRun && !flag && !Mathf.Approximately(num, 1f) && !_refusalLogged)
				{
					_refusalLogged = true;
					Log.LogInfo((object)("Slow Motion and Fast Forward - Game Speed: off in this run (" + Session.Describe() + ") - the game runs at normal speed. Game speed works in solo runs only."));
					_notice.Show("Game Speed: solo only - normal speed here");
				}
				if (!flag || Mathf.Approximately(num, 1f))
				{
					if (!Mathf.Approximately(_applied, 1f))
					{
						if (Mathf.Approximately(Time.timeScale, _applied))
						{
							Time.timeScale = 1f;
						}
						Log.LogInfo((object)("Slow Motion and Fast Forward - Game Speed: normal speed (" + (flag ? "x1 selected" : Session.Describe()) + ")."));
						_applied = 1f;
					}
				}
				else if (Time.timeScale != 0f && !Mathf.Approximately(Time.timeScale, num))
				{
					Time.timeScale = num;
					if (!Mathf.Approximately(_applied, num))
					{
						Log.LogInfo((object)("Slow Motion and Fast Forward - Game Speed: applied x" + Steps.F(num) + " (solo run)."));
					}
					_applied = num;
				}
			}
			catch (Exception ex)
			{
				_applyBroken = true;
				Log.LogWarning((object)("Slow Motion and Fast Forward - Game Speed: stopped changing the speed (" + ex.Message + ")."));
				try
				{
					if (!Mathf.Approximately(_applied, 1f) && Mathf.Approximately(Time.timeScale, _applied))
					{
						Time.timeScale = 1f;
					}
				}
				catch
				{
				}
			}
		}

		private void OnGUI()
		{
			_notice.Draw(Log, _showNotice.Value);
		}

		private void OnDestroy()
		{
			try
			{
				if (!Mathf.Approximately(_applied, 1f) && Mathf.Approximately(Time.timeScale, _applied))
				{
					Time.timeScale = 1f;
				}
			}
			catch
			{
			}
		}
	}
	internal static class PluginInfo
	{
		public const string PLUGIN_GUID = "offtheclock.ror2gamespeed";

		public const string PLUGIN_NAME = "Slow Motion and Fast Forward - Game Speed";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace OffTheClock.Shared
{
	internal static class Honesty
	{
		private static ManualLogSource _log;

		private static string _feature;

		private static bool _loggedBlock;

		internal static void MarkRunChanging(Harmony harmony, ManualLogSource log, string feature)
		{
			_log = log;
			_feature = feature;
			bool flag = false;
			bool flag2 = false;
			try
			{
				RoR2Application.isModded = true;
				flag = true;
			}
			catch (Exception ex)
			{
				log.LogWarning((object)(feature + ": could not set the game's modded flag: " + ex.Message));
			}
			try
			{
				MethodInfo methodInfo = AccessTools.PropertySetter(typeof(Console), "sessionCheatsEnabled");
				if (methodInfo != null)
				{
					methodInfo.Invoke(null, new object[1] { true });
					flag2 = Console.sessionCheatsEnabled;
				}
				if (!flag2)
				{
					log.LogWarning((object)(feature + ": the game's cheat flag (Console.sessionCheatsEnabled) could not be set; the leaderboard lock below still applies."));
				}
			}
			catch (Exception ex2)
			{
				log.LogWarning((object)(feature + ": could not set the game's cheat flag: " + ex2.Message));
			}
			bool flag3 = PatchGuard.Apply(harmony, log, feature, "RoR2.WeeklyRun", "GetLeaderboardName", new Type[2]
			{
				typeof(int),
				typeof(uint)
			}, null, PatchGuard.Own(typeof(Honesty), "GetLeaderboardName_Post"));
			log.LogInfo((object)(feature + ": Prismatic Trials leaderboard blocked for this session (modded flag " + (flag ? "set" : "NOT set") + ", cheat flag " + (flag2 ? "set" : "NOT set") + ", leaderboard lock " + (flag3 ? "on" : "NOT on") + ")."));
		}

		private static void GetLeaderboardName_Post(ref string __result)
		{
			try
			{
				__result = null;
				if (!_loggedBlock)
				{
					_loggedBlock = true;
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogInfo((object)(_feature + ": Prismatic Trials leaderboard name refused (modded session: no score is submitted and the leaderboard view stays empty)."));
					}
				}
			}
			catch
			{
			}
		}
	}
	internal static class KeyCheck
	{
		internal static void Check(ManualLogSource log, string feature, IList<KeyValuePair<string, KeyCode>> keys)
		{
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!ReInput.isReady)
				{
					return;
				}
				List<string> list = new List<string>();
				foreach (LocalUser readOnlyLocalUsers in LocalUserManager.readOnlyLocalUsersList)
				{
					Player val = ((readOnlyLocalUsers != null) ? readOnlyLocalUsers.inputPlayer : null);
					if (val == null)
					{
						continue;
					}
					foreach (ControllerMap allMap in val.controllers.maps.GetAllMaps((ControllerType)0))
					{
						if (allMap == null || !allMap.enabled)
						{
							continue;
						}
						foreach (ActionElementMap allMap2 in allMap.AllMaps)
						{
							if (allMap2 == null || (int)allMap2.keyCode == 0)
							{
								continue;
							}
							foreach (KeyValuePair<string, KeyCode> key in keys)
							{
								if ((int)key.Value != 0 && allMap2.keyCode == key.Value)
								{
									string text = "action " + allMap2.actionId;
									try
									{
										InputAction action = ReInput.mapping.GetAction(allMap2.actionId);
										text = ((action != null) ? action.name : null) ?? text;
									}
									catch
									{
									}
									list.Add(KeyName(key.Value) + " (" + key.Key + ") is also bound to the game's '" + text + "'");
								}
							}
						}
					}
				}
				if (list.Count == 0)
				{
					log.LogInfo((object)(feature + ": key check - " + List(keys) + " not used by your game key bindings."));
				}
				else
				{
					log.LogWarning((object)(feature + ": key clash - " + string.Join("; ", list) + ". Change the key in the config or in the game's Settings > Keyboard."));
				}
			}
			catch (Exception ex)
			{
				log.LogInfo((object)(feature + ": key check skipped (" + ex.Message + ")."));
			}
		}

		internal static string List(IList<KeyValuePair<string, KeyCode>> keys)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, KeyCode> key in keys)
			{
				if ((int)key.Value != 0)
				{
					list.Add(KeyName(key.Value));
				}
			}
			if (list.Count != 0)
			{
				return string.Join(", ", list);
			}
			return "(no keys)";
		}

		internal unsafe static string KeyName(KeyCode k)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected I4, but got Unknown
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between Unknown and I4
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Invalid comparison between Unknown and I4
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected I4, but got Unknown
			if ((int)k != 0)
			{
				switch (k - 266)
				{
				case 2:
					return "Numpad *";
				case 1:
					return "Numpad /";
				case 4:
					return "Numpad +";
				case 3:
					return "Numpad -";
				case 0:
					return "Numpad .";
				case 5:
					return "Numpad Enter";
				default:
					if ((int)k >= 256 && (int)k <= 265)
					{
						return "Numpad " + (k - 256);
					}
					return ((object)(*(KeyCode*)(&k))/*cast due to .constrained prefix*/).ToString();
				}
			}
			return "(no key)";
		}

		internal static bool Pressed(KeyCode k)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			if ((int)k != 0 && Input.GetKeyDown(k))
			{
				return !Session.IsTyping;
			}
			return false;
		}
	}
	internal sealed class Notice
	{
		private const float Seconds = 2.5f;

		private readonly string _feature;

		private string _text;

		private float _until;

		private bool _broken;

		internal Notice(string feature)
		{
			_feature = feature;
		}

		internal void Show(string text)
		{
			_text = text;
			_until = Time.unscaledTime + 2.5f;
		}

		internal void Draw(ManualLogSource log, bool enabled)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			if (_broken || !enabled || _text == null || Time.unscaledTime > _until)
			{
				return;
			}
			try
			{
				float num = Mathf.Max(320f, 14f * (float)_text.Length);
				float num2 = 44f;
				Rect val = new Rect(((float)Screen.width - num) / 2f, (float)Screen.height * 0.12f, num, num2);
				GUI.Box(val, "");
				GUI.Box(val, "");
				GUIStyle label = GUI.skin.label;
				int fontSize = label.fontSize;
				TextAnchor alignment = label.alignment;
				label.fontSize = 24;
				label.alignment = (TextAnchor)4;
				GUI.Label(val, _text, label);
				label.fontSize = fontSize;
				label.alignment = alignment;
			}
			catch (Exception ex)
			{
				_broken = true;
				log.LogWarning((object)(_feature + ": on-screen notice unavailable (" + ex.Message + "); new values are shown in the log only."));
			}
		}
	}
	internal static class PatchGuard
	{
		internal static bool Apply(Harmony harmony, ManualLogSource log, string feature, string typeName, string methodName, Type[] parameters, MethodInfo prefix, MethodInfo postfix)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Type type = AccessTools.TypeByName(typeName);
				if (type == null)
				{
					log.LogWarning((object)(feature + ": game class " + typeName + " not found - this part is skipped (game update?)."));
					return false;
				}
				MethodInfo methodInfo = ((parameters == null) ? AccessTools.Method(type, methodName, (Type[])null, (Type[])null) : AccessTools.Method(type, methodName, parameters, (Type[])null));
				if (methodInfo == null)
				{
					log.LogWarning((object)(feature + ": game method " + typeName + "." + methodName + " not found - this part is skipped (game update?)."));
					return false;
				}
				harmony.Patch((MethodBase)methodInfo, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(prefix), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				log.LogInfo((object)(feature + ": patched " + typeName + "." + methodName + "."));
				return true;
			}
			catch (Exception ex)
			{
				log.LogWarning((object)(feature + ": could not patch " + typeName + "." + methodName + " - this part is skipped (game update?): " + ex.Message));
				return false;
			}
		}

		internal static MethodInfo Own(Type patchClass, string name)
		{
			return AccessTools.Method(patchClass, name, (Type[])null, (Type[])null);
		}
	}
	internal static class Session
	{
		internal static bool InRun
		{
			get
			{
				try
				{
					return Object.op_Implicit((Object)(object)Run.instance);
				}
				catch
				{
					return false;
				}
			}
		}

		internal static bool IsSolo
		{
			get
			{
				try
				{
					return RoR2Application.isInSinglePlayer;
				}
				catch
				{
					return false;
				}
			}
		}

		internal static bool IsOffline
		{
			get
			{
				try
				{
					return NetworkServer.active && NetworkServer.dontListen;
				}
				catch
				{
					return false;
				}
			}
		}

		internal static bool IsTyping
		{
			get
			{
				try
				{
					return Object.op_Implicit((Object)(object)ConsoleWindow.instance) || ChatBox.IsActive;
				}
				catch
				{
					return false;
				}
			}
		}

		internal static string Describe()
		{
			try
			{
				if (!InRun)
				{
					return "not in a run";
				}
				if (IsSolo)
				{
					return "solo";
				}
				if (IsOffline)
				{
					return "local split-screen";
				}
				return "online lobby";
			}
			catch (Exception)
			{
				return "unknown";
			}
		}
	}
	internal static class Steps
	{
		internal static float[] Parse(string text, float min, float max, float[] fallback, ManualLogSource log, string feature, string name)
		{
			List<float> list = new List<float>();
			string[] array = (text ?? "").Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				if (float.TryParse(array[i].Trim().TrimStart('x', 'X'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && result >= min && result <= max && !list.Contains(result))
				{
					list.Add(result);
				}
			}
			if (list.Count == 0)
			{
				log.LogWarning((object)(feature + ": " + name + " has no valid values (" + F(min) + " to " + F(max) + "), using " + Text(fallback) + "."));
				return fallback;
			}
			list.Sort();
			return list.ToArray();
		}

		internal static string F(float v)
		{
			return v.ToString("0.##", CultureInfo.InvariantCulture);
		}

		internal static string Text(float[] steps)
		{
			List<string> list = new List<string>();
			foreach (float v in steps)
			{
				list.Add("x" + F(v));
			}
			return string.Join("/", list);
		}

		internal static float NextCycle(float[] steps, float current)
		{
			foreach (float num in steps)
			{
				if (num > current + 0.001f)
				{
					return num;
				}
			}
			return steps[0];
		}

		internal static float Up(float[] steps, float current)
		{
			foreach (float num in steps)
			{
				if (num > current + 0.001f)
				{
					return num;
				}
			}
			return steps[^1];
		}

		internal static float Down(float[] steps, float current)
		{
			for (int num = steps.Length - 1; num >= 0; num--)
			{
				if (steps[num] < current - 0.001f)
				{
					return steps[num];
				}
			}
			return steps[0];
		}
	}
}