Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Colorblind Mode and Comfort Options v1.0.1
ComfortOptions.dll
Decompiled 9 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using OffTheClock.Lib; 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("ComfortOptions")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1")] [assembly: AssemblyProduct("ComfortOptions")] [assembly: AssemblyTitle("ComfortOptions")] [assembly: AssemblyVersion("1.0.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 ComfortOptions { [BepInPlugin("offtheclock.ror2comfort", "Colorblind Mode and Comfort Options", "1.0.1")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static ConfigEntry<float> RecoilScale; internal static ConfigEntry<bool> HideMinionLevelUpFlash; internal static ConfigEntry<bool> HideDeathScreenEffect; internal static ConfigEntry<bool> ColorblindColors; internal static ConfigEntry<bool> TierWords; internal static OtcSelfCheck SC; private void Awake() { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; SC = new OtcSelfCheck(Log, "ComfortOptions"); SC.Expect("tier_colors", "start the game (read back at load)"); SC.Expect("chat_sent_normal", "with colorblind colors on, play until the chat says an item was gained (Void Fields, Simulacrum or a Scavenger)", optional: true); RecoilScale = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "RecoilScale", 1f, new ConfigDescription("How much your camera kicks when you fire (weapon recoil). 1 = normal, 0.5 = half, 0 = none. The game's own Screen Shake Scale slider (Settings > Gameplay) does not cover this.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); HideMinionLevelUpFlash = ((BaseUnityPlugin)this).Config.Bind<bool>("Flashes", "HideMinionLevelUpFlash", true, "On (default) = no level-up flash on drones, turrets and other allies (with many drones they flash constantly). Your own character still shows it."); HideDeathScreenEffect = ((BaseUnityPlugin)this).Config.Bind<bool>("Flashes", "HideDeathScreenEffect", true, "On (default) = no full-screen color effect on your camera when you die."); ColorblindColors = ((BaseUnityPlugin)this).Config.Bind<bool>("Colors", "ColorblindItemColors", false, "On = red-green safe item tier colors (Uncommon sky blue, Legendary vermillion, Boss yellow, Equipment orange, Lunar dark blue, Void reddish purple). Needs a game restart. Item icon backgrounds keep their colors."); TierWords = ((BaseUnityPlugin)this).Config.Bind<bool>("Colors", "TierWordOnItemNames", false, "On = item and equipment names get their tier in brackets, e.g. 'Soldier's Syringe (Common)', so you don't need colors to tell tiers apart. Applies right away."); Harmony harmony = new Harmony("offtheclock.ror2comfort"); List<string> list = new List<string>(); if (PatchGuard.Apply(harmony, Log, "Colorblind Mode and Comfort Options", "RoR2.CameraTargetParams", "AddRecoil", new Type[4] { typeof(float), typeof(float), typeof(float), typeof(float) }, PatchGuard.Own(typeof(Patches), "AddRecoil_Pre"), null)) { list.Add("recoil x" + Steps.F(RecoilScale.Value)); } if (PatchGuard.Apply(harmony, Log, "Colorblind Mode and Comfort Options", "RoR2.LevelUpEffectManager", "OnCharacterLevelUp", new Type[1] { typeof(CharacterBody) }, PatchGuard.Own(typeof(Patches), "OnCharacterLevelUp_Pre"), null)) { list.Add("minion level-up flash " + (HideMinionLevelUpFlash.Value ? "hidden" : "shown")); } if (PatchGuard.Apply(harmony, Log, "Colorblind Mode and Comfort Options", "RoR2.LocalCameraEffect", "Start", Type.EmptyTypes, PatchGuard.Own(typeof(Patches), "LocalCameraEffect_Start_Pre"), null)) { list.Add("death screen effect " + (HideDeathScreenEffect.Value ? "hidden" : "shown")); } if (PatchGuard.Apply(harmony, Log, "Colorblind Mode and Comfort Options", "RoR2.Language", "GetLocalizedStringByToken", new Type[1] { typeof(string) }, null, PatchGuard.Own(typeof(TierNames), "GetLocalizedStringByToken_Post"))) { TierNames.Init(); list.Add("tier words " + (TierWords.Value ? "on" : "off")); } list.Add(TierColors.Apply(ColorblindColors.Value)); TierColors.SelfCheck(ColorblindColors.Value); if (TierColors.Applied) { bool flag = PatchGuard.Apply(harmony, Log, "Colorblind Mode and Comfort Options", "RoR2.Chat", "SendBroadcastChat", new Type[2] { typeof(ChatMessageBase), typeof(int) }, PatchGuard.Own(typeof(ChatColors), "SendBroadcastChat_Pre"), null); PatchGuard.Apply(harmony, Log, "Colorblind Mode and Comfort Options", "RoR2.Chat+PlayerPickupChatMessage", "ConstructChatString", Type.EmptyTypes, PatchGuard.Own(typeof(ChatColors), "ConstructChatString_Pre"), null); list.Add(flag ? "item chat to other players in the game's colors" : "item chat colors NOT protected"); if (!flag) { SC.Fail("chat_sent_normal", "Chat.SendBroadcastChat could not be patched: hosted item messages would carry the palette"); } } else { SC.Wait("chat_sent_normal", "colorblind colors are off: chat messages already use the game's colors"); } ((BaseUnityPlugin)this).Config.SettingChanged += delegate(object _, SettingChangedEventArgs e) { Log.LogInfo((object)(string.Format("{0}: setting {1} = {2}", "Colorblind Mode and Comfort Options", e.ChangedSetting.Definition.Key, e.ChangedSetting.BoxedValue) + (((object)e.ChangedSetting == ColorblindColors) ? " (restart the game to apply)." : " (applies right away)."))); }; Log.LogInfo((object)("Colorblind Mode and Comfort Options 1.0.1 loaded - " + string.Join(", ", list) + ". Visual only, works in any lobby.")); } } internal static class Patches { private static bool _loggedRecoil; private static bool _loggedFlash; private static bool _loggedDeath; internal static void AddRecoil_Pre(ref float verticalMin, ref float verticalMax, ref float horizontalMin, ref float horizontalMax) { try { float num = Plugin.RecoilScale.Value; if (!(num >= 0.999f)) { if (num < 0f) { num = 0f; } verticalMin *= num; verticalMax *= num; horizontalMin *= num; horizontalMax *= num; if (!_loggedRecoil) { _loggedRecoil = true; Plugin.Log.LogInfo((object)("Colorblind Mode and Comfort Options: camera recoil scaled x" + Steps.F(num) + " (first shot).")); } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Colorblind Mode and Comfort Options: recoil skipped once (" + ex.Message + ").")); } } internal static bool OnCharacterLevelUp_Pre(CharacterBody characterBody, bool __runOriginal) { try { if (!__runOriginal) { return false; } if (!Plugin.HideMinionLevelUpFlash.Value || !Object.op_Implicit((Object)(object)characterBody) || characterBody.isPlayerControlled) { return true; } if (!_loggedFlash) { _loggedFlash = true; Plugin.Log.LogInfo((object)("Colorblind Mode and Comfort Options: level-up flash hidden on " + characterBody.GetDisplayName() + " (not a player).")); } return false; } catch (Exception ex) { Plugin.Log.LogDebug((object)("Colorblind Mode and Comfort Options: level-up flash check skipped (" + ex.Message + ").")); return __runOriginal; } } internal static bool LocalCameraEffect_Start_Pre(LocalCameraEffect __instance, bool __runOriginal) { try { if (!__runOriginal) { return false; } if (!Plugin.HideDeathScreenEffect.Value || !Object.op_Implicit((Object)(object)__instance)) { return true; } if (!((Object)((Component)__instance).gameObject).name.StartsWith("PlayerDeathEffect", StringComparison.Ordinal)) { return true; } if (Object.op_Implicit((Object)(object)__instance.effectRoot)) { __instance.effectRoot.SetActive(false); } Object.Destroy((Object)(object)((Component)__instance).gameObject); if (!_loggedDeath) { _loggedDeath = true; Plugin.Log.LogInfo((object)"Colorblind Mode and Comfort Options: death screen effect hidden."); } return false; } catch (Exception ex) { Plugin.Log.LogDebug((object)("Colorblind Mode and Comfort Options: death effect check skipped (" + ex.Message + ").")); return __runOriginal; } } } internal static class TierColors { private static readonly (ColorIndex index, Color32 color)[] Preset = new(ColorIndex, Color32)[12] { ((ColorIndex)2, new Color32((byte)86, (byte)180, (byte)233, byte.MaxValue)), ((ColorIndex)19, new Color32((byte)45, (byte)108, (byte)150, byte.MaxValue)), ((ColorIndex)3, new Color32((byte)213, (byte)94, (byte)0, byte.MaxValue)), ((ColorIndex)20, new Color32((byte)128, (byte)56, (byte)0, byte.MaxValue)), ((ColorIndex)13, new Color32((byte)240, (byte)228, (byte)66, byte.MaxValue)), ((ColorIndex)22, new Color32((byte)150, (byte)142, (byte)40, byte.MaxValue)), ((ColorIndex)5, new Color32((byte)230, (byte)159, (byte)0, byte.MaxValue)), ((ColorIndex)33, new Color32((byte)140, (byte)96, (byte)0, byte.MaxValue)), ((ColorIndex)4, new Color32((byte)0, (byte)114, (byte)178, byte.MaxValue)), ((ColorIndex)21, new Color32((byte)0, (byte)64, (byte)104, byte.MaxValue)), ((ColorIndex)25, new Color32((byte)204, (byte)121, (byte)167, byte.MaxValue)), ((ColorIndex)26, new Color32((byte)122, (byte)70, (byte)100, byte.MaxValue)) }; internal static bool Applied; internal static string Apply(bool on) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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) //IL_009c: 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_00a2: Expected I4, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) if (!on) { return "tier colors normal"; } try { Color32[] array = AccessTools.Field(typeof(ColorCatalog), "indexToColor32")?.GetValue(null) as Color32[]; string[] array2 = AccessTools.Field(typeof(ColorCatalog), "indexToHexString")?.GetValue(null) as string[]; if (array == null || array2 == null) { Plugin.Log.LogWarning((object)"Colorblind Mode and Comfort Options: ColorCatalog tables not found (game update?) - tier colors stay normal."); return "tier colors NOT changed"; } int num = 0; (ColorIndex, Color32)[] preset = Preset; for (int i = 0; i < preset.Length; i++) { (ColorIndex, Color32) tuple = preset[i]; ColorIndex item = tuple.Item1; Color32 item2 = tuple.Item2; int num2 = (int)item; if (num2 > 0 && num2 < array.Length && num2 < array2.Length) { ChatColors.Remember(array[num2], item2); array[num2] = item2; array2[num2] = Util.RGBToHex(item2); num++; } } Plugin.Log.LogInfo((object)string.Format("{0}: colorblind-safe tier colors applied ({1} entries).", "Colorblind Mode and Comfort Options", num)); Applied = num > 0; return "colorblind tier colors on"; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Colorblind Mode and Comfort Options: could not change tier colors (" + ex.Message + ") - they stay normal.")); return "tier colors NOT changed"; } } internal static void SelfCheck(bool on) { //IL_0001: 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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_00a7: Unknown result type (might be due to invalid IL or missing references) try { Color32 color = ColorCatalog.GetColor((ColorIndex)2); Color32 item = Preset[0].color; bool flag = color.r == item.r && color.g == item.g && color.b == item.b; if (!on) { Plugin.SC.Check("tier_colors", !flag, "setting off: Uncommon reads #{0} (the game's own)", Util.RGBToHex(color)); return; } Plugin.SC.Check("tier_colors", Applied && flag && ChatColors.Count > 0, "Uncommon reads #{0} (palette #{1}); {2} colors go to other players in the game's own colors", Util.RGBToHex(color), Util.RGBToHex(item), ChatColors.Count); } catch (Exception ex) { Plugin.SC.Fail("tier_colors", "read-back failed ({0})", ex.Message); } } } internal static class ChatColors { private static readonly Dictionary<uint, Color32> ToGame = new Dictionary<uint, Color32>(); private static readonly Dictionary<uint, Color32> ToPalette = new Dictionary<uint, Color32>(); private static string _sentToken; private static Color32 _sentColor; private static bool _loggedSend; private static bool _loggedShow; internal static int Count => ToGame.Count; private static uint Key(Color32 c) { //IL_0000: 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_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) return (uint)((c.r << 24) | (c.g << 16) | (c.b << 8) | c.a); } private static bool Same(Color32 a, Color32 b) { //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_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (a.r == b.r && a.g == b.g && a.b == b.b) { return a.a == b.a; } return false; } internal static void Remember(Color32 game, Color32 palette) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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_003a: Unknown result type (might be due to invalid IL or missing references) uint key = Key(game); uint key2 = Key(palette); if (!ToGame.ContainsKey(key2)) { ToGame[key2] = game; } if (!ToPalette.ContainsKey(key)) { ToPalette[key] = palette; } } internal static void SendBroadcastChat_Pre(ChatMessageBase message, bool __runOriginal) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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) //IL_0038: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) try { if (!__runOriginal) { return; } PlayerPickupChatMessage val = (PlayerPickupChatMessage)(object)((message is PlayerPickupChatMessage) ? message : null); if (val != null && ToGame.TryGetValue(Key(val.pickupColor), out var value)) { Color32 pickupColor = val.pickupColor; val.pickupColor = value; _sentToken = val.pickupToken; _sentColor = value; if (!_loggedSend) { _loggedSend = true; Plugin.Log.LogInfo((object)("Colorblind Mode and Comfort Options: item chat message (" + ((SubjectChatMessage)val).baseToken + ") sent to all players in the game's color #" + Util.RGBToHex(value) + " instead of #" + Util.RGBToHex(pickupColor) + " (first one; this PC still shows the palette).")); } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Colorblind Mode and Comfort Options: chat color check skipped once (" + ex.Message + ").")); } } internal static void ConstructChatString_Pre(PlayerPickupChatMessage __instance, bool __runOriginal) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_009e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) try { if (!__runOriginal || __instance == null) { return; } Color32 pickupColor = __instance.pickupColor; if (_sentToken != null && __instance.pickupToken == _sentToken && Plugin.SC.Want("chat_sent_normal")) { Plugin.SC.Check("chat_sent_normal", Same(pickupColor, _sentColor), "'{0}' arrived from the network as #{1} (the game's color: #{2})", __instance.pickupToken, Util.RGBToHex(pickupColor), Util.RGBToHex(_sentColor)); _sentToken = null; } if (ToPalette.TryGetValue(Key(pickupColor), out var value)) { __instance.pickupColor = value; if (!_loggedShow) { _loggedShow = true; Plugin.Log.LogInfo((object)"Colorblind Mode and Comfort Options: item chat shown here in the palette (first one)."); } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Colorblind Mode and Comfort Options: chat color display skipped once (" + ex.Message + ").")); } } } internal static class TierNames { private static readonly Dictionary<string, string> WordByToken = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly Dictionary<string, KeyValuePair<string, string>> Cache = new Dictionary<string, KeyValuePair<string, string>>(StringComparer.Ordinal); private static bool _logged; internal static void Init() { try { ((ResourceAvailability)(ref ItemCatalog.availability)).CallWhenAvailable((Action)BuildItems); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Colorblind Mode and Comfort Options: tier words for items unavailable (" + ex.Message + ").")); } try { ((ResourceAvailability)(ref EquipmentCatalog.availability)).CallWhenAvailable((Action)BuildEquipment); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Colorblind Mode and Comfort Options: tier words for equipment unavailable (" + ex2.Message + ").")); } } private static string Word(ItemTier tier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected I4, but got Unknown switch ((int)tier) { case 0: return "Common"; case 1: return "Uncommon"; case 2: return "Legendary"; case 4: return "Boss"; case 3: return "Lunar"; case 6: case 7: case 8: case 9: return "Void"; default: return null; } } private static void BuildItems() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) try { int num = 0; for (int i = 0; i < ItemCatalog.itemCount; i++) { ItemDef itemDef = ItemCatalog.GetItemDef((ItemIndex)i); if (Object.op_Implicit((Object)(object)itemDef) && !itemDef.hidden && !string.IsNullOrEmpty(itemDef.nameToken)) { string text = Word(itemDef.tier); if (text != null) { WordByToken[itemDef.nameToken] = text; num++; } } } Cache.Clear(); Plugin.Log.LogInfo((object)string.Format("{0}: tier words ready for {1} items.", "Colorblind Mode and Comfort Options", num)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Colorblind Mode and Comfort Options: tier words for items failed (" + ex.Message + ").")); } } private static void BuildEquipment() { try { int num = 0; for (int i = 0; i < EquipmentCatalog.equipmentCount; i++) { EquipmentDef equipmentDef = EquipmentCatalog.GetEquipmentDef((EquipmentIndex)i); if (Object.op_Implicit((Object)(object)equipmentDef) && !string.IsNullOrEmpty(equipmentDef.nameToken)) { WordByToken[equipmentDef.nameToken] = (equipmentDef.isLunar ? "Lunar Equipment" : "Equipment"); num++; } } Cache.Clear(); Plugin.Log.LogInfo((object)string.Format("{0}: tier words ready for {1} equipment.", "Colorblind Mode and Comfort Options", num)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Colorblind Mode and Comfort Options: tier words for equipment failed (" + ex.Message + ").")); } } internal static void GetLocalizedStringByToken_Post(string token, ref string __result) { try { if (!Plugin.TierWords.Value || token == null || __result == null || !WordByToken.TryGetValue(token, out var value)) { return; } if (Cache.TryGetValue(token, out var value2) && (object)value2.Key == __result) { __result = value2.Value; return; } string text = __result; string text2 = text + " (" + value + ")"; Cache[token] = new KeyValuePair<string, string>(text, text2); __result = text2; if (!_logged) { _logged = true; Plugin.Log.LogInfo((object)("Colorblind Mode and Comfort Options: tier word shown (e.g. '" + text2 + "').")); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Colorblind Mode and Comfort Options: tier word skipped once (" + ex.Message + ").")); } } } internal static class PluginInfo { public const string PLUGIN_GUID = "offtheclock.ror2comfort"; public const string PLUGIN_NAME = "Colorblind Mode and Comfort Options"; public const string PLUGIN_VERSION = "1.0.1"; } } namespace OffTheClock.Lib { internal sealed class OtcSelfCheck { private sealed class Entry { public string State = "WAITING"; public int Misses; public int Grace = 1; public int Lines; public readonly HashSet<string> Reasons = new HashSet<string>(); public bool Optional; } internal const string Version = "1.0"; internal const int MaxLinesPerCheck = 5; internal const int MaxWaitReasons = 3; internal const int MaxDetail = 300; private readonly Dictionary<string, Entry> _checks = new Dictionary<string, Entry>(); private readonly List<string> _order = new List<string>(); private readonly Action<string> _sink; internal string Mod { get; } internal IReadOnlyList<string> Ids => _order; internal OtcSelfCheck(ManualLogSource log, string mod) : this(mod, (log == null) ? null : new Action<string>(log.LogInfo)) { } internal OtcSelfCheck(string mod, Action<string> sink) { Mod = SafeId(string.IsNullOrEmpty(mod) ? "Mod" : mod); _sink = sink; } internal OtcSelfCheck Expect(string id, string how, bool optional = false, int grace = 1) { id = SafeId(id); if (_checks.ContainsKey(id)) { return this; } _checks[id] = new Entry { Grace = Math.Max(1, grace), Optional = optional }; _order.Add(id); Emit(id, "WAITING", (optional ? "(optional) " : "") + "how: " + (how ?? "?")); return this; } internal bool Want(string id) { if (_checks.TryGetValue(id ?? "", out var value)) { return value.State == "WAITING"; } return false; } internal string State(string id) { if (!_checks.TryGetValue(id ?? "", out var value)) { return null; } return value.State; } internal bool Pass(string id, string detail = null) { return SetState(id, "PASS", detail, "FAIL", " (after an earlier FAIL)"); } internal bool Pass(string id, string format, params object[] args) { return Pass(id, Fmt(format, args)); } internal bool Fail(string id, string detail = null) { return SetState(id, "FAIL", detail, "PASS", " (after an earlier PASS)"); } internal bool Fail(string id, string format, params object[] args) { return Fail(id, Fmt(format, args)); } internal bool Check(string id, bool ok, string detail = null) { if (!_checks.TryGetValue(id ?? "", out var value) || value.State != "WAITING") { return false; } if (ok) { return Pass(id, detail); } value.Misses++; if (value.Misses >= value.Grace) { return Fail(id, string.Format(CultureInfo.InvariantCulture, "{0} ({1} time{2})", detail ?? "", value.Misses, (value.Misses == 1) ? "" : "s")); } return false; } internal bool Check(string id, bool ok, string format, params object[] args) { return Check(id, ok, Fmt(format, args)); } internal bool Wait(string id, string reason) { if (!_checks.TryGetValue(id ?? "", out var value) || value.State != "WAITING") { return false; } string text = Clean(reason); if (value.Reasons.Contains(text) || value.Reasons.Count >= 3) { return false; } value.Reasons.Add(text); Emit(id, "WAITING", text); return true; } internal bool Wait(string id, string format, params object[] args) { return Wait(id, Fmt(format, args)); } internal int FailOpen(string reason) { int num = 0; foreach (string item in _order) { if (!(_checks[item].State != "WAITING")) { Fail(item, reason ?? ""); num++; } } return num; } internal int FailOpen(string format, params object[] args) { return FailOpen(Fmt(format, args)); } private bool SetState(string id, string state, string detail, string earlier, string earlierText) { if (!_checks.TryGetValue(id ?? "", out var value) || value.State == state) { return false; } string state2 = value.State; value.State = state; Emit(id, state, detail + ((state2 == earlier) ? earlierText : "")); return true; } private void Emit(string id, string state, string detail) { if (_checks.TryGetValue(id, out var value)) { if (value.Lines >= 5) { return; } value.Lines++; } string text = "[SELFCHECK] " + Mod + " " + id + " " + state; if (!string.IsNullOrEmpty(detail)) { text = text + " " + Clean(detail); } try { _sink?.Invoke(text); } catch { } } internal static string Clean(string s) { s = Regex.Replace(s ?? "", "[\r\n]+", " "); if (s.Length > 300) { s = s.Substring(0, 297) + "..."; } return s; } internal static string SafeId(string id) { return Regex.Replace(id ?? "?", "\\s+", "_"); } private static string Fmt(string format, object[] args) { if (format == null) { return ""; } if (args == null || args.Length == 0) { return format; } try { return string.Format(CultureInfo.InvariantCulture, format, args); } catch (FormatException) { return format + " [format error]"; } } } } 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]; } } }