using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Logging; using Microsoft.CodeAnalysis; using On.RoR2; using On.RoR2.UI; using R2API; using RoR2; using RoR2.ConVar; using RoR2.UI; using TMPro; using UnityEngine; using UnityEngine.Events; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("MonitorSwitcher")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+73a3aebcfd7d8d588f675aa4448a9ae6d4d5b8c6")] [assembly: AssemblyProduct("MonitorSwitcher")] [assembly: AssemblyTitle("MonitorSwitcher")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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 MonitorSwitcher { internal static class DisplayMonitorConVar { private sealed class MonitorConVarImpl : BaseConVar { public MonitorConVarImpl() : base("display_monitor", (ConVarFlags)4, (string)null, "Which display the game window is shown on. Valid values are DISPLAY1, DISPLAY2, ... as listed by the Display Monitor option in the settings menu.") { } public override string GetString() { return MonitorManager.GetCurrentDisplayDeviceName() ?? string.Empty; } public override void SetString(string newValue) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) Log.Info($"MonitorSwitcher: SetString('{newValue}') loadFinished={RoR2Application.loadFinished}"); string error2; if (string.IsNullOrEmpty(newValue)) { MonitorManager.TryMoveToMonitor(null, out var _); } else if (!RoR2Application.loadFinished) { MonitorSwitcherPlugin.PendingStartupMonitor = newValue; } else if (!MonitorManager.TryMoveToMonitor(newValue, out error2)) { throw new ConCommandException(error2 ?? ("Unknown display '" + newValue + "'.")); } } } public const string Name = "display_monitor"; public static readonly BaseConVar Instance = (BaseConVar)(object)new MonitorConVarImpl(); private static bool _registered; public static void Register(Console console) { if (_registered) { return; } if (console.FindConVar("display_monitor") != null) { _registered = true; return; } _registered = true; MethodInfo method = typeof(Console).GetMethod("RegisterConVarInternal", BindingFlags.Instance | BindingFlags.NonPublic); if (method == null) { Log.Error("MonitorSwitcher: could not find Console.RegisterConVarInternal"); return; } method.Invoke(console, new object[1] { Instance }); Log.Info("MonitorSwitcher: registered convar 'display_monitor'"); } } internal static class Localization { public const string SettingNameToken = "MONITOR_SWITCHER_SETTING_NAME"; public const string ChoiceTokenPrefix = "MONITOR_SWITCHER_DISPLAY_"; public static void Init() { LanguageAPI.Add("MONITOR_SWITCHER_SETTING_NAME", "Display Monitor"); } public static string ChoiceToken(int index) { return "MONITOR_SWITCHER_DISPLAY_" + index; } } internal static class Log { private static ManualLogSource _logSource; internal static void Init(ManualLogSource logSource) { _logSource = logSource; } internal static void Debug(object data) { _logSource.LogDebug(data); } internal static void Error(object data) { _logSource.LogError(data); } internal static void Fatal(object data) { _logSource.LogFatal(data); } internal static void Info(object data) { _logSource.LogInfo(data); } internal static void Message(object data) { _logSource.LogMessage(data); } internal static void Warning(object data) { _logSource.LogWarning(data); } } internal class MonitorDropdownControl : BaseSettingsControl { internal static string PendingSettingName; public MPDropdown dropdown; private DisplayInfo[] _displays = Array.Empty(); private bool _syncing; protected void Awake() { if (string.IsNullOrEmpty(base.settingName)) { base.settingName = PendingSettingName; } ((BaseSettingsControl)this).Awake(); } protected override void OnUpdateControls() { RefreshOptions(); } internal void Init(MPDropdown targetDropdown) { dropdown = targetDropdown; if (Object.op_Implicit((Object)(object)dropdown)) { ((UnityEventBase)((TMP_Dropdown)dropdown).onValueChanged).RemoveAllListeners(); ((UnityEvent)(object)((TMP_Dropdown)dropdown).onValueChanged).AddListener((UnityAction)OnDropdownValueChanged); } RefreshOptions(); } private void RefreshOptions() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown if (!Object.op_Implicit((Object)(object)dropdown)) { return; } _displays = MonitorManager.Enumerate().ToArray(); string currentValue = ((BaseSettingsControl)this).GetCurrentValue(); int valueWithoutNotify = 0; List list = new List(_displays.Length); for (int i = 0; i < _displays.Length; i++) { list.Add(new OptionData(_displays[i].Label)); if (string.Equals(_displays[i].DeviceName, currentValue, StringComparison.OrdinalIgnoreCase)) { valueWithoutNotify = i; } } _syncing = true; ((TMP_Dropdown)dropdown).ClearOptions(); ((TMP_Dropdown)dropdown).AddOptions(list); ((TMP_Dropdown)dropdown).SetValueWithoutNotify(valueWithoutNotify); _syncing = false; } private void OnDropdownValueChanged(int value) { if (!_syncing && value >= 0 && value < _displays.Length) { ((BaseSettingsControl)this).SubmitSetting(_displays[value].DeviceName); } } } internal struct NativeRect { public int Left; public int Top; public int Right; public int Bottom; public int Width => Right - Left; public int Height => Bottom - Top; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct MonitorInfoEx { public int CbSize; public NativeRect RcMonitor; public NativeRect RcWork; public uint DwFlags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string SzDevice; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct DisplayDevice { public int Cb; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string DeviceName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string DeviceString; public uint StateFlags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string DeviceID; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string DeviceKey; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)] internal struct DevMode { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string DeviceName; public ushort SpecVersion; public ushort DriverVersion; public ushort Size; public ushort DriverExtra; public uint Fields; public int PositionX; public int PositionY; public uint DisplayOrientation; public uint DisplayFixedOutput; public short Color; public short Duplex; public short YResolution; public short TTOption; public short Collate; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string FormName; public ushort LogPixels; public uint BitsPerPel; public uint PelsWidth; public uint PelsHeight; public uint DisplayFlags; public uint DisplayFrequency; public uint ICMethod; public uint ICMIntent; public uint MediaType; public uint DitherType; public uint Reserved1; public uint Reserved2; public uint PanningWidth; public uint PanningHeight; } internal struct DisplayModeInfo { public int Width; public int Height; public int RefreshRate; } internal sealed class DisplayInfo { private const string DeviceNamePrefix = "DISPLAY"; public string DeviceName; public string FullName; public string FriendlyName; public NativeRect Bounds; public NativeRect WorkArea; public bool IsPrimary; public string Label => $"Display {DisplayNumber} ({Bounds.Width} x {Bounds.Height})"; private string DisplayNumber { get { if (DeviceName != null && DeviceName.StartsWith("DISPLAY", StringComparison.OrdinalIgnoreCase) && int.TryParse(DeviceName.Substring("DISPLAY".Length), out var result)) { return result.ToString(); } return "?"; } } } internal static class MonitorManager { private delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref NativeRect lprcMonitor, IntPtr dwData); private static class Native { [DllImport("user32.dll")] internal static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData); [DllImport("user32.dll", CharSet = CharSet.Unicode)] internal static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfoEx lpmi); [DllImport("user32.dll", CharSet = CharSet.Unicode)] internal static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DisplayDevice lpDisplayDevice, uint dwFlags); [DllImport("user32.dll", CharSet = CharSet.Unicode)] internal static extern bool EnumDisplaySettings(string lpszDeviceName, uint iModeNum, ref DevMode lpDevMode); [DllImport("user32.dll")] internal static extern IntPtr GetActiveWindow(); [DllImport("user32.dll")] internal static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags); [DllImport("user32.dll")] internal static extern bool GetWindowRect(IntPtr hWnd, out NativeRect lpRect); [DllImport("user32.dll")] internal static extern bool MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool bRepaint); [DllImport("user32.dll")] internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); } private const uint MONITORINFOF_PRIMARY = 1u; private const uint MONITOR_DEFAULTTONEAREST = 2u; private const uint SWP_NOACTIVATE = 16u; private const uint SWP_SHOWWINDOW = 64u; private static readonly Dictionary FriendlyNameCache = new Dictionary(); public static event Action DisplayChanged; public static List Enumerate() { List list = new List(); MonitorEnumProc lpfnEnum = delegate(IntPtr hMonitor, IntPtr hdc, ref NativeRect rc, IntPtr data) { MonitorInfoEx lpmi = new MonitorInfoEx { CbSize = Marshal.SizeOf(typeof(MonitorInfoEx)) }; if (Native.GetMonitorInfo(hMonitor, ref lpmi)) { list.Add(new DisplayInfo { DeviceName = NormalizeDeviceName(lpmi.SzDevice), FullName = lpmi.SzDevice, Bounds = lpmi.RcMonitor, WorkArea = lpmi.RcWork, IsPrimary = ((lpmi.DwFlags & 1) != 0), FriendlyName = GetFriendlyName(lpmi.SzDevice) }); } return true; }; Native.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, lpfnEnum, IntPtr.Zero); return list; } public static string GetCurrentDisplayDeviceName() { IntPtr gameWindowHandle = GetGameWindowHandle(); if (gameWindowHandle != IntPtr.Zero) { IntPtr intPtr = Native.MonitorFromWindow(gameWindowHandle, 2u); if (intPtr != IntPtr.Zero) { MonitorInfoEx lpmi = new MonitorInfoEx { CbSize = Marshal.SizeOf(typeof(MonitorInfoEx)) }; if (Native.GetMonitorInfo(intPtr, ref lpmi)) { return NormalizeDeviceName(lpmi.SzDevice); } } } return GetPrimary()?.DeviceName; } public static string GetCurrentDisplayFullName() { IntPtr gameWindowHandle = GetGameWindowHandle(); if (gameWindowHandle != IntPtr.Zero) { IntPtr intPtr = Native.MonitorFromWindow(gameWindowHandle, 2u); if (intPtr != IntPtr.Zero) { MonitorInfoEx lpmi = new MonitorInfoEx { CbSize = Marshal.SizeOf(typeof(MonitorInfoEx)) }; if (Native.GetMonitorInfo(intPtr, ref lpmi)) { return lpmi.SzDevice; } } } return GetPrimary()?.FullName ?? string.Empty; } public static List GetModesForMonitor(string fullDeviceName) { List list = new List(); DevMode lpDevMode = NewDevMode(); for (uint num = 0u; Native.EnumDisplaySettings(fullDeviceName, num, ref lpDevMode); num++) { if (lpDevMode.PelsWidth != 0 && lpDevMode.PelsHeight != 0 && lpDevMode.DisplayFrequency != 0) { list.Add(new DisplayModeInfo { Width = (int)lpDevMode.PelsWidth, Height = (int)lpDevMode.PelsHeight, RefreshRate = (int)lpDevMode.DisplayFrequency }); } lpDevMode = NewDevMode(); } return list; } public static DisplayModeInfo? GetCurrentMode(string fullDeviceName) { DevMode lpDevMode = NewDevMode(); if (Native.EnumDisplaySettings(fullDeviceName, uint.MaxValue, ref lpDevMode) && lpDevMode.PelsWidth != 0 && lpDevMode.PelsHeight != 0) { return new DisplayModeInfo { Width = (int)lpDevMode.PelsWidth, Height = (int)lpDevMode.PelsHeight, RefreshRate = (int)lpDevMode.DisplayFrequency }; } return null; } private static DevMode NewDevMode() { return new DevMode { Size = (ushort)Marshal.SizeOf(typeof(DevMode)) }; } public static bool TryMoveToMonitor(string deviceName, out string error) { //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Invalid comparison between Unknown and I4 //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Invalid comparison between Unknown and I4 //IL_015c: Unknown result type (might be due to invalid IL or missing references) error = null; string normalized = NormalizeDeviceName(deviceName); List list = Enumerate(); DisplayInfo displayInfo = ((!string.IsNullOrEmpty(normalized)) ? list.Find((DisplayInfo d) => string.Equals(d.DeviceName, normalized, StringComparison.OrdinalIgnoreCase)) : null); displayInfo = displayInfo ?? list.Find((DisplayInfo d) => d.IsPrimary); if (displayInfo == null) { error = "No displays are currently connected."; return false; } IntPtr gameWindowHandle = GetGameWindowHandle(); Log.Info($"MonitorSwitcher: move to '{normalized}' hwnd={gameWindowHandle} target={displayInfo.DeviceName} bounds={displayInfo.Bounds.Left},{displayInfo.Bounds.Top} {displayInfo.Bounds.Width}x{displayInfo.Bounds.Height} mode={Screen.fullScreenMode}"); if (gameWindowHandle == IntPtr.Zero) { error = "Could not find the game window."; return false; } FullScreenMode fullScreenMode = Screen.fullScreenMode; if ((int)fullScreenMode == 0) { Screen.fullScreenMode = (FullScreenMode)3; PlaceWindow(gameWindowHandle, displayInfo.Bounds); Screen.fullScreenMode = fullScreenMode; MonitorManager.DisplayChanged?.Invoke(); return true; } if ((int)fullScreenMode == 1) { PlaceWindow(gameWindowHandle, displayInfo.Bounds); Log.Info("MonitorSwitcher: post-move current display = '" + GetCurrentDisplayFullName() + "'"); MonitorManager.DisplayChanged?.Invoke(); return true; } if (Native.GetWindowRect(gameWindowHandle, out var lpRect)) { int num = Mathf.Clamp(lpRect.Width, 0, displayInfo.WorkArea.Width); int num2 = Mathf.Clamp(lpRect.Height, 0, displayInfo.WorkArea.Height); int x = displayInfo.WorkArea.Left + (displayInfo.WorkArea.Width - num) / 2; int y = displayInfo.WorkArea.Top + (displayInfo.WorkArea.Height - num2) / 2; Native.MoveWindow(gameWindowHandle, x, y, num, num2, bRepaint: true); } else { Native.MoveWindow(gameWindowHandle, displayInfo.WorkArea.Left, displayInfo.WorkArea.Top, displayInfo.WorkArea.Width, displayInfo.WorkArea.Height, bRepaint: true); } MonitorManager.DisplayChanged?.Invoke(); return true; } private static void PlaceWindow(IntPtr hwnd, NativeRect bounds) { int x = bounds.Left; int y = bounds.Top; int width = bounds.Width; int height = bounds.Height; bool flag = Native.SetWindowPos(hwnd, IntPtr.Zero, x, y, width, height, 80u); Log.Info($"MonitorSwitcher: SetWindowPos({x},{y},{width}x{height}) result={flag}"); RoR2Application.onNextUpdate += delegate { if (hwnd != IntPtr.Zero) { Native.SetWindowPos(hwnd, IntPtr.Zero, x, y, width, height, 80u); } }; } private static DisplayInfo GetPrimary() { return Enumerate().Find((DisplayInfo d) => d.IsPrimary); } private static string NormalizeDeviceName(string deviceName) { if (deviceName != null && deviceName.StartsWith("\\\\.\\", StringComparison.Ordinal)) { return deviceName.Substring(4); } return deviceName; } private static IntPtr GetGameWindowHandle() { try { IntPtr mainWindowHandle = Process.GetCurrentProcess().MainWindowHandle; if (mainWindowHandle != IntPtr.Zero) { return mainWindowHandle; } } catch { } return Native.GetActiveWindow(); } private static string GetFriendlyName(string deviceName) { if (FriendlyNameCache.TryGetValue(deviceName, out var value)) { return value; } DisplayDevice lpDisplayDevice = new DisplayDevice { Cb = Marshal.SizeOf(typeof(DisplayDevice)) }; if (Native.EnumDisplayDevices(deviceName, 1u, ref lpDisplayDevice, 0u) && !string.IsNullOrEmpty(lpDisplayDevice.DeviceString)) { FriendlyNameCache[deviceName] = lpDisplayDevice.DeviceString; return lpDisplayDevice.DeviceString; } uint num = 0u; while (true) { DisplayDevice lpDisplayDevice2 = new DisplayDevice { Cb = Marshal.SizeOf(typeof(DisplayDevice)) }; if (!Native.EnumDisplayDevices(null, num, ref lpDisplayDevice2, 0u)) { break; } if (string.Equals(lpDisplayDevice2.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(lpDisplayDevice2.DeviceString)) { FriendlyNameCache[deviceName] = lpDisplayDevice2.DeviceString; return lpDisplayDevice2.DeviceString; } num++; } FriendlyNameCache[deviceName] = deviceName; return deviceName; } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("Ovalsquare.MonitorSwitcher", "MonitorSwitcher", "1.0.0")] public class MonitorSwitcherPlugin : BaseUnityPlugin { [CompilerGenerated] private static class <>O { public static hook_Start <0>__OnSettingsPanelStart; public static hook_OnEnable <1>__OnSettingsPanelEnable; public static Action <2>__OnDisplayChanged; } public const string PluginAuthor = "Ovalsquare"; public const string PluginName = "MonitorSwitcher"; public const string PluginGUID = "Ovalsquare.MonitorSwitcher"; public const string PluginVersion = "1.0.0"; internal static string PendingStartupMonitor; private void Awake() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown Log.Init(((BaseUnityPlugin)this).Logger); Localization.Init(); Console.CacheConVars += new hook_CacheConVars(OnCacheConVars); Console.LoadStartupConfigs += new hook_LoadStartupConfigs(OnLoadStartupConfigs); object obj = <>O.<0>__OnSettingsPanelStart; if (obj == null) { hook_Start val = SettingsRowInjector.OnSettingsPanelStart; <>O.<0>__OnSettingsPanelStart = val; obj = (object)val; } SettingsPanelController.Start += (hook_Start)obj; object obj2 = <>O.<1>__OnSettingsPanelEnable; if (obj2 == null) { hook_OnEnable val2 = SettingsRowInjector.OnSettingsPanelEnable; <>O.<1>__OnSettingsPanelEnable = val2; obj2 = (object)val2; } SettingsPanelController.OnEnable += (hook_OnEnable)obj2; RoR2Application.OnMainMenuControllerInitialized += new hook_OnMainMenuControllerInitialized(OnMainMenuControllerInitialized); MonitorManager.DisplayChanged += OnDisplayChanged; ResolutionListFix.Hook(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Ovalsquare.MonitorSwitcher AWAKE_COMPLETE"); } private static void OnDisplayChanged() { ResolutionListFix.RefreshActiveControls(); } private void OnCacheConVars(orig_CacheConVars orig, Console self) { orig.Invoke(self); DisplayMonitorConVar.Register(self); } private void OnLoadStartupConfigs(orig_LoadStartupConfigs orig, Console self) { DisplayMonitorConVar.Register(self); orig.Invoke(self); } private void OnMainMenuControllerInitialized(orig_OnMainMenuControllerInitialized orig, RoR2Application self) { orig.Invoke(self); ApplyPendingStartupMonitor(); } private static void ApplyPendingStartupMonitor() { string pendingStartupMonitor = PendingStartupMonitor; PendingStartupMonitor = null; if (!string.IsNullOrEmpty(pendingStartupMonitor)) { if (MonitorManager.TryMoveToMonitor(pendingStartupMonitor, out var error)) { Log.Info("MonitorSwitcher: applied saved monitor " + pendingStartupMonitor + "."); } else { Log.Warning("MonitorSwitcher: could not apply saved monitor " + pendingStartupMonitor + ": " + error); } } } private void OnDestroy() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_0035: 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_0040: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown Console.CacheConVars -= new hook_CacheConVars(OnCacheConVars); Console.LoadStartupConfigs -= new hook_LoadStartupConfigs(OnLoadStartupConfigs); object obj = <>O.<0>__OnSettingsPanelStart; if (obj == null) { hook_Start val = SettingsRowInjector.OnSettingsPanelStart; <>O.<0>__OnSettingsPanelStart = val; obj = (object)val; } SettingsPanelController.Start -= (hook_Start)obj; object obj2 = <>O.<1>__OnSettingsPanelEnable; if (obj2 == null) { hook_OnEnable val2 = SettingsRowInjector.OnSettingsPanelEnable; <>O.<1>__OnSettingsPanelEnable = val2; obj2 = (object)val2; } SettingsPanelController.OnEnable -= (hook_OnEnable)obj2; RoR2Application.OnMainMenuControllerInitialized -= new hook_OnMainMenuControllerInitialized(OnMainMenuControllerInitialized); MonitorManager.DisplayChanged -= OnDisplayChanged; ResolutionListFix.Unhook(); Log.Info("Ovalsquare.MonitorSwitcher unloaded"); } } internal static class ResolutionListFix { [CompilerGenerated] private static class <>O { public static hook_GenerateResolutionOptions <0>__OnGenerateResolutionOptions; } private static readonly FieldInfo ResolutionOptionsField = typeof(ResolutionControl).GetField("resolutionOptions", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly Type ResolutionOptionType = typeof(ResolutionControl).GetNestedType("ResolutionOption", BindingFlags.NonPublic); private static readonly ConstructorInfo ResolutionOptionCtor = ResolutionOptionType?.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); private static readonly FieldInfo SizeField = ResolutionOptionType?.GetField("size", BindingFlags.Instance | BindingFlags.Public); private static readonly FieldInfo RefreshRatesField = ResolutionOptionType?.GetField("supportedRefreshRates", BindingFlags.Instance | BindingFlags.Public); public static void Hook() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown object obj = <>O.<0>__OnGenerateResolutionOptions; if (obj == null) { hook_GenerateResolutionOptions val = OnGenerateResolutionOptions; <>O.<0>__OnGenerateResolutionOptions = val; obj = (object)val; } ResolutionControl.GenerateResolutionOptions += (hook_GenerateResolutionOptions)obj; } public static void Unhook() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown object obj = <>O.<0>__OnGenerateResolutionOptions; if (obj == null) { hook_GenerateResolutionOptions val = OnGenerateResolutionOptions; <>O.<0>__OnGenerateResolutionOptions = val; obj = (object)val; } ResolutionControl.GenerateResolutionOptions -= (hook_GenerateResolutionOptions)obj; } public static void RefreshActiveControls() { ResolutionControl[] array = Object.FindObjectsOfType(); foreach (ResolutionControl self in array) { try { RebuildFromCurrentMonitor(self); } catch (Exception data) { Log.Error(data); } } } private static void OnGenerateResolutionOptions(orig_GenerateResolutionOptions orig, ResolutionControl self) { orig.Invoke(self); try { RebuildFromCurrentMonitor(self); } catch (Exception data) { Log.Error(data); } } private static void RebuildFromCurrentMonitor(ResolutionControl self) { //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Expected O, but got Unknown //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) if (ResolutionOptionsField == null || ResolutionOptionType == null || ResolutionOptionCtor == null || SizeField == null || RefreshRatesField == null || !Object.op_Implicit((Object)(object)self.resolutionDropdown)) { return; } string currentDisplayFullName = MonitorManager.GetCurrentDisplayFullName(); if (string.IsNullOrEmpty(currentDisplayFullName)) { return; } List modesForMonitor = MonitorManager.GetModesForMonitor(currentDisplayFullName); if (modesForMonitor.Count == 0) { return; } List list = (from m in modesForMonitor orderby (long)m.Width * (long)m.Height descending, m.RefreshRate descending select m).ToList(); List list2 = new List(); Dictionary> dictionary = new Dictionary>(); Vector2Int val = default(Vector2Int); foreach (DisplayModeInfo item in list) { ((Vector2Int)(ref val))..ctor(item.Width, item.Height); if (!dictionary.TryGetValue(val, out var value)) { value = (dictionary[val] = new List()); list2.Add(val); } if (!value.Contains(item.RefreshRate)) { value.Add(item.RefreshRate); } } Array array = Array.CreateInstance(ResolutionOptionType, list2.Count); OptionData[] array2 = (OptionData[])(object)new OptionData[list2.Count]; for (int num = 0; num < list2.Count; num++) { object obj = ResolutionOptionCtor.Invoke(null); SizeField.SetValue(obj, list2[num]); List list4 = (List)RefreshRatesField.GetValue(obj); list4.AddRange(dictionary[list2[num]]); array.SetValue(obj, num); int num2 = num; Vector2Int val2 = list2[num]; object arg = ((Vector2Int)(ref val2)).x; val2 = list2[num]; array2[num2] = new OptionData($"{arg}x{((Vector2Int)(ref val2)).y}"); } ResolutionOptionsField.SetValue(self, array); ((TMP_Dropdown)self.resolutionDropdown).ClearOptions(); ((TMP_Dropdown)self.resolutionDropdown).AddOptions(array2.ToList()); Resolution currentResolution = Screen.currentResolution; int width = ((Resolution)(ref currentResolution)).width; currentResolution = Screen.currentResolution; Vector2Int current2 = new Vector2Int(width, ((Resolution)(ref currentResolution)).height); int num3 = list2.FindIndex((Vector2Int s) => s == current2); if (num3 < 0) { DisplayModeInfo? currentMode = MonitorManager.GetCurrentMode(currentDisplayFullName); if (currentMode.HasValue) { num3 = list2.FindIndex((Vector2Int s) => s == new Vector2Int(currentMode.Value.Width, currentMode.Value.Height)); } } if (num3 < 0) { num3 = 0; } ((TMP_Dropdown)self.resolutionDropdown).value = num3; DisplayModeInfo displayModeInfo = list[0]; Log.Info($"MonitorSwitcher: resolution list rebuilt for {currentDisplayFullName} ({modesForMonitor.Count} modes, largest {displayModeInfo.Width}x{displayModeInfo.Height}@{displayModeInfo.RefreshRate})"); } } internal static class SettingsRowInjector { private static readonly FieldInfo SettingsControllersField = typeof(SettingsPanelController).GetField("settingsControllers", BindingFlags.Instance | BindingFlags.NonPublic); public static void OnSettingsPanelStart(orig_Start orig, SettingsPanelController self) { InjectRow(self); orig.Invoke(self); RefreshSettingsControllersSnapshot(self); } public static void OnSettingsPanelEnable(orig_OnEnable orig, SettingsPanelController self) { orig.Invoke(self); Log.Info("MonitorSwitcher: settings panel enabled: " + ((Object)((Component)self).gameObject).name); } private static void InjectRow(SettingsPanelController panel) { //IL_016e: Unknown result type (might be due to invalid IL or missing references) try { ResolutionControl componentInChildren = ((Component)panel).GetComponentInChildren(true); if (!Object.op_Implicit((Object)(object)componentInChildren)) { return; } Transform parent = ((Component)componentInChildren).transform.parent; Transform val = (((Object)(object)parent != (Object)null) ? parent.parent : null); if ((Object)(object)val == (Object)null) { Log.Warning("MonitorSwitcher: could not find the Video settings row list; skipping row injection."); return; } GameObject val2 = Object.Instantiate(((Component)parent).gameObject, val); ((Object)val2).name = "Option, Display Monitor"; val2.transform.SetAsLastSibling(); ResolutionControl componentInChildren2 = val2.GetComponentInChildren(true); if (Object.op_Implicit((Object)(object)componentInChildren2)) { Object.Destroy((Object)(object)componentInChildren2); } LanguageTextMeshController componentInChildren3 = val2.GetComponentInChildren(true); if (Object.op_Implicit((Object)(object)componentInChildren3)) { componentInChildren3.token = "MONITOR_SWITCHER_SETTING_NAME"; } MPDropdown val3 = null; MPDropdown[] componentsInChildren = val2.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { if (i == 0) { val3 = componentsInChildren[i]; continue; } ((Component)componentsInChildren[i]).gameObject.SetActive(false); Object.Destroy((Object)(object)componentsInChildren[i]); } if ((Object)(object)val3 == (Object)null) { Log.Warning("MonitorSwitcher: no MPDropdown found in cloned row; skipping row injection."); Object.Destroy((Object)(object)val2); return; } MonitorDropdownControl.PendingSettingName = "display_monitor"; MonitorDropdownControl monitorDropdownControl = val2.AddComponent(); ((BaseSettingsControl)monitorDropdownControl).nameToken = "MONITOR_SWITCHER_SETTING_NAME"; ((BaseSettingsControl)monitorDropdownControl).nameLabel = componentInChildren3; ((BaseSettingsControl)monitorDropdownControl).settingSource = (SettingSource)0; ((BaseSettingsControl)monitorDropdownControl).useConfirmationDialog = true; monitorDropdownControl.Init(val3); Log.Info($"MonitorSwitcher: injected Display Monitor dropdown as last row of '{((Object)val).name}' with {MonitorManager.Enumerate().Count} display(s)."); } catch (Exception data) { Log.Error(data); } } private static void RefreshSettingsControllersSnapshot(SettingsPanelController panel) { try { if (SettingsControllersField != null) { BaseSettingsControl[] componentsInChildren = ((Component)panel).GetComponentsInChildren(true); SettingsControllersField.SetValue(panel, componentsInChildren); } } catch (Exception data) { Log.Error(data); } } } }