diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj
index 78afc6f2..6e6ff5ab 100644
--- a/Assembly-CSharp.csproj
+++ b/Assembly-CSharp.csproj
@@ -241,6 +241,7 @@
+
@@ -250,6 +251,7 @@
+
diff --git a/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs b/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs
index c0043b6f..76b69428 100644
--- a/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs
+++ b/Assets.Scripts.Core.Buriko/BurikoScriptFile.cs
@@ -2776,6 +2776,19 @@ public BurikoVariable OperationMODClearArtsets()
return BurikoVariable.Null;
}
+ private void ShowSetupMenuIfRequired()
+ {
+ if (MODAudioSet.Instance.HasAudioSetsDefined() && !MODAudioSet.Instance.GetCurrentAudioSet(out _))
+ {
+ GameSystem.Instance.MainUIController.modMenu.PushSubMenuAndShow(ModSubMenu.AudioSetup);
+ }
+
+ if (!MODWindowManager.FullscreenLockConfigured())
+ {
+ GameSystem.Instance.MainUIController.modMenu.PushSubMenuAndShow(ModSubMenu.WindowSetup);
+ }
+ }
+
public BurikoVariable OperationMODGenericCall()
{
SetOperationType("MODGenericCall");
@@ -2784,11 +2797,7 @@ public BurikoVariable OperationMODGenericCall()
switch(callID)
{
case "ShowSetupMenuIfRequired":
- if(MODAudioSet.Instance.HasAudioSetsDefined() && !MODAudioSet.Instance.GetCurrentAudioSet(out _))
- {
- GameSystem.Instance.MainUIController.modMenu.SetSubMenu(ModSubMenu.AudioSetup);
- GameSystem.Instance.MainUIController.modMenu.Show();
- }
+ ShowSetupMenuIfRequired();
break;
case "LipSyncSettings":
diff --git a/Assets.Scripts.Core.State/StateNormal.cs b/Assets.Scripts.Core.State/StateNormal.cs
index 9c14ecf6..a26394ca 100644
--- a/Assets.Scripts.Core.State/StateNormal.cs
+++ b/Assets.Scripts.Core.State/StateNormal.cs
@@ -189,21 +189,7 @@ public bool InputHandler()
// Fullscreen
if (Input.GetKeyDown(KeyCode.F))
{
- if (GameSystem.Instance.IsFullscreen)
- {
- int num14 = PlayerPrefs.GetInt("width");
- int num15 = PlayerPrefs.GetInt("height");
- if (num14 == 0 || num15 == 0)
- {
- num14 = 640;
- num15 = 480;
- }
- GameSystem.Instance.DeFullscreen(width: num14, height: num15);
- }
- else
- {
- GameSystem.Instance.GoFullscreen();
- }
+ MODWindowManager.FullscreenToggle(showToast: true);
}
// Toggle Language
diff --git a/Assets.Scripts.Core/GameSystem.cs b/Assets.Scripts.Core/GameSystem.cs
index ea9ad470..8f68a96e 100644
--- a/Assets.Scripts.Core/GameSystem.cs
+++ b/Assets.Scripts.Core/GameSystem.cs
@@ -158,10 +158,6 @@ public class GameSystem : MonoBehaviour
private int SystemInit = 1;
- private Resolution fullscreenResolution;
-
- private int screenModeSet = -1;
-
private Stack stateStack = new Stack();
public bool HasFocus;
@@ -178,13 +174,6 @@ public class GameSystem : MonoBehaviour
// Unity will attempt to deserialize public properties and these aren't in the AssetBundle,
// so use private ones with public accessors
- private bool _isFullscreen;
- public bool IsFullscreen
- {
- get => _isFullscreen;
- private set => _isFullscreen = value;
- }
-
private float _configMenuFontSize = 0;
public float ConfigMenuFontSize
{
@@ -247,34 +236,17 @@ private void Initialize()
{
PlayerPrefs.SetInt("height", 720);
}
- if (PlayerPrefs.GetInt("width") < 640)
+ if (PlayerPrefs.GetInt("width") < 853)
{
- PlayerPrefs.SetInt("width", 640);
+ PlayerPrefs.SetInt("width", 853);
}
if (PlayerPrefs.GetInt("height") < 480)
{
PlayerPrefs.SetInt("height", 480);
}
- IsFullscreen = PlayerPrefs.GetInt("is_fullscreen", 0) == 1;
- fullscreenResolution.width = 0;
- fullscreenResolution.height = 0;
- fullscreenResolution = GetFullscreenResolution();
- if (IsFullscreen)
- {
- Screen.SetResolution(fullscreenResolution.width, fullscreenResolution.height, fullscreen: true);
- }
- else if (PlayerPrefs.HasKey("height") && PlayerPrefs.HasKey("width"))
- {
- int width = PlayerPrefs.GetInt("width");
- int height = PlayerPrefs.GetInt("height");
- Debug.Log("Requesting window size " + width + "x" + height + " based on config file");
- Screen.SetResolution(width, height, fullscreen: false);
- }
- if ((Screen.width < 640 || Screen.height < 480) && !IsFullscreen)
- {
- Screen.SetResolution(640, 480, fullscreen: false);
- }
+ MODWindowManager.GameSystemInitSetResolution();
+
Debug.Log("Starting compile thread...");
CompileThread = new Thread(CompileScripts)
{
@@ -326,12 +298,7 @@ public void PostLoading()
public void UpdateAspectRatio(float newratio)
{
AspectRatio = newratio;
- if (!IsFullscreen)
- {
- int width = Mathf.RoundToInt((float)Screen.height * AspectRatio);
- Screen.SetResolution(width, Screen.height, fullscreen: false);
- }
- PlayerPrefs.SetInt("width", Mathf.RoundToInt(PlayerPrefs.GetInt("height") * AspectRatio));
+ MODWindowManager.RefreshWindowAspect();
MainUIController.UpdateBlackBars();
SceneController.UpdateScreenSize();
}
@@ -816,37 +783,6 @@ public void UpdateWaits()
});
}
- public IEnumerator FrameWaitForFullscreen(int width, int height, bool fullscreen)
- {
- yield return (object)new WaitForEndOfFrame();
- yield return (object)new WaitForFixedUpdate();
- IsFullscreen = fullscreen;
- PlayerPrefs.SetInt("is_fullscreen", fullscreen ? 1 : 0);
- Screen.SetResolution(width, height, fullscreen);
- while (Screen.width != width || Screen.height != height)
- {
- yield return (object)null;
- }
- }
-
- public void GoFullscreen()
- {
- IsFullscreen = true;
- PlayerPrefs.SetInt("is_fullscreen", 1);
- Resolution resolution = GetFullscreenResolution();
- Screen.SetResolution(resolution.width, resolution.height, fullscreen: true);
- Debug.Log(resolution.width + " , " + resolution.height);
- PlayerPrefs.SetInt("fullscreen_width", resolution.width);
- PlayerPrefs.SetInt("fullscreen_height", resolution.height);
- }
-
- public void DeFullscreen(int width, int height)
- {
- IsFullscreen = false;
- PlayerPrefs.SetInt("is_fullscreen", 0);
- Screen.SetResolution(width, height, fullscreen: false);
- }
-
private void OnApplicationFocus(bool focusStatus)
{
HasFocus = focusStatus;
@@ -854,17 +790,7 @@ private void OnApplicationFocus(bool focusStatus)
private void LateUpdate()
{
- if (screenModeSet == -1)
- {
- screenModeSet = 0;
- fullscreenResolution = Screen.currentResolution;
- if (PlayerPrefs.HasKey("fullscreen_width") && PlayerPrefs.HasKey("fullscreen_height") && Screen.fullScreen)
- {
- fullscreenResolution.width = PlayerPrefs.GetInt("fullscreen_width");
- fullscreenResolution.height = PlayerPrefs.GetInt("fullscreen_height");
- }
- Debug.Log("Fullscreen Resolution: " + fullscreenResolution.width + ", " + fullscreenResolution.height);
- }
+ MODWindowManager.GetFullScreenResolutionLateUpdate();
}
private bool CheckInitialization()
@@ -1008,6 +934,11 @@ private void OnApplicationQuit()
{
SteamController.Close();
}
+
+ if(CanExit)
+ {
+ MODWindowManager.OnApplicationReallyQuit("GameSystem.OnApplicationQuit()");
+ }
}
///
@@ -1028,80 +959,6 @@ public T ChooseJapaneseEnglish(T japanese, T english)
}
}
- public Resolution GetFullscreenResolution()
- {
- Resolution resolution = new Resolution();
- string source = "";
- // Try to guess resolution from Screen.currentResolution
- if (!Screen.fullScreen || Application.platform == RuntimePlatform.OSXPlayer)
- {
- resolution.width = this.fullscreenResolution.width = Screen.currentResolution.width;
- resolution.height = this.fullscreenResolution.height = Screen.currentResolution.height;
- source = "Screen.currentResolution";
- }
- else if (this.fullscreenResolution.width > 0 && this.fullscreenResolution.height > 0)
- {
- resolution.width = this.fullscreenResolution.width;
- resolution.height = this.fullscreenResolution.height;
- source = "Stored fullscreenResolution";
- }
- else if (PlayerPrefs.HasKey("fullscreen_width") && PlayerPrefs.HasKey("fullscreen_height"))
- {
- resolution.width = PlayerPrefs.GetInt("fullscreen_width");
- resolution.height = PlayerPrefs.GetInt("fullscreen_height");
- source = "PlayerPrefs";
- }
- else
- {
- resolution.width = Screen.currentResolution.width;
- resolution.height = Screen.currentResolution.height;
- source = "Screen.currentResolution as Fallback";
- }
-
- // Above can be glitchy on Linux, so also check the maximum resolution of a single monitor
- // If it's bigger than that, then switch over
- // Note that this (from what I can tell) gives you the biggest resolution of any of your monitors,
- // not just the one the game is running under, so it could *also* be wrong, which is why we check both methods
- if (Screen.resolutions.Length > 0)
- {
- int index = 0;
- Resolution best = Screen.resolutions[0];
- for (int i = 1; i < Screen.resolutions.Length; i++)
- {
- if (Screen.resolutions[i].height * Screen.resolutions[i].width > best.height * best.width)
- {
- best = Screen.resolutions[i];
- index = i;
- }
- }
- if (best.width <= resolution.width && best.height <= resolution.height) {
- resolution = best;
- source = "Screen.resolutions #" + index;
- }
- }
- if (!PlayerPrefs.HasKey("fullscreen_width_override"))
- {
- PlayerPrefs.SetInt("fullscreen_width_override", 0);
- }
- if (!PlayerPrefs.HasKey("fullscreen_height_override"))
- {
- PlayerPrefs.SetInt("fullscreen_height_override", 0);
- }
-
- if (PlayerPrefs.GetInt("fullscreen_width_override") > 0)
- {
- resolution.width = PlayerPrefs.GetInt("fullscreen_width_override");
- source += " + Width Override";
- }
- if (PlayerPrefs.GetInt("fullscreen_height_override") > 0)
- {
- resolution.height = PlayerPrefs.GetInt("fullscreen_height_override");
- source += " + Height Override";
- }
- Debug.Log("Using resolution " + resolution.width + "x" + resolution.height + " as the fullscreen resolution based on " + source + ".");
- return resolution;
- }
-
///
/// Gets the amount you should offset gui elements to center them properly based on the current aspect ratio.
/// Add this number to GUI elements' positions to center them, subtract it from window positions.
@@ -1113,18 +970,7 @@ public float GetGUIOffset() {
~GameSystem()
{
- // Fixes an issue where Unity would write garbage values to its saved state on Linux
- // If we do this while the game is running, Unity will overwrite the values
- // So do it in the finalizer, which will run as the game quits and the GameSystem is deallocated
- if (PlayerPrefs.HasKey("width") && PlayerPrefs.HasKey("height"))
- {
- int width = PlayerPrefs.GetInt("width");
- int height = PlayerPrefs.GetInt("height");
- PlayerPrefs.SetInt("Screenmanager Resolution Width", width);
- PlayerPrefs.SetInt("Screenmanager Resolution Height", height);
- PlayerPrefs.SetInt("is_fullscreen", IsFullscreen ? 1 : 0);
- PlayerPrefs.SetInt("Screenmanager Is Fullscreen mode", 0);
- }
+ MODWindowManager.OnApplicationReallyQuit("GameSystem Destructor");
}
static GameSystem()
diff --git a/Assets.Scripts.Core/KeyHook.cs b/Assets.Scripts.Core/KeyHook.cs
index 2cee1bbc..fe019d4f 100644
--- a/Assets.Scripts.Core/KeyHook.cs
+++ b/Assets.Scripts.Core/KeyHook.cs
@@ -1,3 +1,4 @@
+using MOD.Scripts.Core;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
@@ -46,14 +47,7 @@ private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
int num = Marshal.ReadInt32(lParam);
if (num == 13 && GameSystem.Instance.HasFocus)
{
- if (GameSystem.Instance.IsFullscreen)
- {
- GameSystem.Instance.DeFullscreen(PlayerPrefs.GetInt("width"), PlayerPrefs.GetInt("height"));
- }
- else
- {
- GameSystem.Instance.GoFullscreen();
- }
+ MODWindowManager.FullscreenToggle(showToast: true);
return (IntPtr)1;
}
}
diff --git a/Assets.Scripts.UI.Config/ScreenSwitcherButton.cs b/Assets.Scripts.UI.Config/ScreenSwitcherButton.cs
index 0a54159e..7e41b4d0 100644
--- a/Assets.Scripts.UI.Config/ScreenSwitcherButton.cs
+++ b/Assets.Scripts.UI.Config/ScreenSwitcherButton.cs
@@ -1,4 +1,5 @@
using Assets.Scripts.Core;
+using MOD.Scripts.Core;
using UnityEngine;
namespace Assets.Scripts.UI.Config
@@ -7,6 +8,9 @@ public class ScreenSwitcherButton : MonoBehaviour
{
public int Width;
+ // This variable doesn't actually change/doesn't really relate to the game's fullscreen state
+ // It is actually used to indicate which button is labelled as "fullscreen" (it is true only for that button)
+
public bool IsFullscreen;
private UIButton button;
@@ -31,26 +35,24 @@ private int Height()
private void OnClick()
{
- if (IsFullscreen)
+ if(IsFullscreen)
{
- GameSystem.Instance.GoFullscreen();
+ MODWindowManager.FullscreenToggle(showToast: true);
}
else
{
- int height = Height();
- int width = Mathf.RoundToInt(height * GameSystem.Instance.AspectRatio);
- GameSystem.Instance.DeFullscreen(width: width, height: height);
- PlayerPrefs.SetInt("width", width);
- PlayerPrefs.SetInt("height", height);
+ MODWindowManager.SetResolution(Height(), showToast: true);
}
}
private bool ShouldBeDown()
{
+ // Make the 'fullscreen' button always clickable because it toggles fullscreen
if (IsFullscreen)
{
- return GameSystem.Instance.IsFullscreen;
+ return false;
}
+
return Screen.height == Height();
}
diff --git a/Assets.Scripts.UI.Config/SwitchButton.cs b/Assets.Scripts.UI.Config/SwitchButton.cs
index cc91918a..7d03d644 100644
--- a/Assets.Scripts.UI.Config/SwitchButton.cs
+++ b/Assets.Scripts.UI.Config/SwitchButton.cs
@@ -131,7 +131,7 @@ public void Click()
}
break;
case ConfigButtonType.FullscreenMode:
- GameSystem.Instance.GoFullscreen();
+ MODWindowManager.GoFullscreen(showToast: true);
break;
case ConfigButtonType.ClickToCutVoice:
GameSystem.Instance.StopVoiceOnClick = !GameSystem.Instance.StopVoiceOnClick;
diff --git a/MOD.Scripts.Core/MODWindowManager.cs b/MOD.Scripts.Core/MODWindowManager.cs
new file mode 100644
index 00000000..30bc98c2
--- /dev/null
+++ b/MOD.Scripts.Core/MODWindowManager.cs
@@ -0,0 +1,416 @@
+using Assets.Scripts.Core;
+using MOD.Scripts.UI;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+using System.Linq;
+
+namespace MOD.Scripts.Core
+{
+ class MODWindowManager
+ {
+ static string playerPrefsCrashDetectorKey = "crash_detector";
+ const string FULLSCREEN_LOCK_KEY = "fullscreen_lock";
+
+ private static bool _IsFullscreen;
+ public static bool IsFullscreen { get { return _IsFullscreen; } }
+ private static Resolution fullscreenResolution;
+ private static int screenModeSet = -1;
+
+ private static int lastSetWidth = 853;
+ private static int lastSetHeight = 480;
+
+ private static string[] prefsToPrint = {
+ "width",
+ "height",
+ "is_fullscreen",
+ "fullscreen_width",
+ "fullscreen_height",
+ "Screenmanager Resolution Width",
+ "Screenmanager Resolution Height",
+ "Screenmanager Is Fullscreen mode",
+ playerPrefsCrashDetectorKey
+ };
+
+ private static void PrintPlayerPrefs(string caption)
+ {
+ string PrintPref(string key)
+ {
+ return $"{key}: {(PlayerPrefs.HasKey(key) ? PlayerPrefs.GetInt(key).ToString() : "")}";
+ }
+
+ // Print out certain playerprefs values for debugging
+ Debug.Log($"PlayerPrefs [{caption}]:\n{string.Join("\n", prefsToPrint.Select(key => PrintPref(key)).ToArray())}");
+ }
+
+ // Toggle between windowed and fullscreen.
+ // Windowed mode will use the last windowed resolution.
+ // Fullscreen mode will use the detected fullscreen resolution.
+ public static void FullscreenToggle(bool showToast=false)
+ {
+ SetResolution(maybe_width: null, maybe_height: null, maybe_fullscreen: !IsFullscreen, showToast: showToast);
+ }
+
+ // Set the screen resolution, where the width will be set according to the current AspectRatio
+ // The windowed/fullscreen state won't be changed.
+ public static void SetResolution(int height, bool showToast = false)
+ {
+ SetResolution(maybe_width: null, maybe_height: height, maybe_fullscreen: null, showToast: showToast);
+ }
+
+ // Go fullscreen. The new resolution will be detected automatically.
+ public static void GoFullscreen(bool showToast = false)
+ {
+ SetResolution(maybe_width: null, maybe_height: null, maybe_fullscreen: true, showToast: showToast);
+ }
+
+ // This function does the following:
+ // - If full screen is enabled, the resolution will set according to the monitor resolution
+ // - If windowed, the window width will be set according to the height and AspectRatio
+ public static void RefreshWindowAspect(bool showToast = false)
+ {
+ SetResolution(maybe_width: null, maybe_height: null, maybe_fullscreen: null, showToast: showToast);
+ }
+
+ public static void SetResolution(int? maybe_width, int? maybe_height, bool? maybe_fullscreen, bool showToast=false)
+ {
+ int height = 480;
+ int width = 853;
+
+ // Default to keeping current fullscreen state if fullscreen not specified
+ TrySetIsFullscreen(maybe_fullscreen ?? IsFullscreen, "SetResolution");
+
+ if (maybe_width == null && maybe_height == null)
+ {
+ if (IsFullscreen)
+ {
+ // If going fullscreen, and width and height wasn't specified, use detected fullscreen resolution
+ Resolution resolution = GetFullscreenResolution();
+ height = resolution.height;
+ width = resolution.width;
+ }
+ else if(PlayerPrefs.HasKey("height") && PlayerPrefs.HasKey("width"))
+ {
+ // If width and height both not specified, use saved player prefs width and height
+ int player_prefs_height = PlayerPrefs.GetInt("height");
+ int player_prefs_width = PlayerPrefs.GetInt("width");
+
+ if (player_prefs_width != 0 && player_prefs_height != 0)
+ {
+ height = player_prefs_height;
+ width = player_prefs_width;
+ }
+ }
+ }
+ else if (maybe_width == null && maybe_height != null)
+ {
+ // If only height specified, use aspect ratio to set width
+ height = maybe_height.Value;
+ width = Mathf.RoundToInt(height * GameSystem.Instance.AspectRatio);
+ }
+ else if (maybe_width != null && maybe_height != null)
+ {
+ // If both specified, just use directly (ignore current aspect ratio)
+ height = maybe_height.Value;
+ width = maybe_width.Value;
+ }
+
+ // Do some sanity checks on the width and height
+ if (height < 480)
+ {
+ MODToaster.Show("Height too small - must be at least 480 pixels");
+ Debug.Log("Height too small - must be at least 480 pixels");
+ height = 480;
+ }
+ else if (height > 15360)
+ {
+ MODToaster.Show("Height too big - must be less than 15360 pixels");
+ Debug.Log("Height too big - must be less than 15360 pixels");
+ height = 15360;
+ }
+
+ if (width < 853)
+ {
+ MODToaster.Show("Width too small - must be at least 853 pixels");
+ Debug.Log("Width too small - must be at least 853 pixels");
+ width = 853;
+ }
+ else if (width > 15360)
+ {
+ MODToaster.Show("Width too big - must be less than 15360 pixels");
+ Debug.Log("Width too big - must be less than 15360 pixels");
+ width = 15360;
+ }
+
+ Screen.SetResolution(width, height, IsFullscreen);
+
+ lastSetWidth = width;
+ lastSetHeight = height;
+
+ // Update playerprefs (won't be saved until game exits or PlayerPrefs.Save() is called
+ SetPlayerPrefs();
+
+ if (showToast)
+ {
+ string prefix = "Set Res";
+ if(maybe_fullscreen == false && FullscreenLocked())
+ {
+ prefix = "Fullscreen Locked";
+ }
+ MODToaster.Show($"{prefix}: {width}x{height}");
+ }
+ }
+
+ // NOTE: this function does not save playerprefs
+ // playerprefs are saved when the game exits cleanly, or on manual calls to PlayerPrefs.Save()
+ private static void SetPlayerPrefs()
+ {
+ PlayerPrefs.SetInt(IsFullscreen ? "fullscreen_width" : "width", lastSetWidth);
+ PlayerPrefs.SetInt(IsFullscreen ? "fullscreen_height" : "height", lastSetHeight);
+ PlayerPrefs.SetInt("is_fullscreen", IsFullscreen ? 1 : 0);
+
+ PlayerPrefs.SetInt("Screenmanager Resolution Width", lastSetWidth);
+ PlayerPrefs.SetInt("Screenmanager Resolution Height", lastSetHeight);
+
+ // This used to be always set false, but on Linux Gnome this caused
+ // TODO: decide whether to set this to IsFullscreen, or to just always set true.
+ // On Windows this doesn't seem to make any difference
+ PlayerPrefs.SetInt("Screenmanager Is Fullscreen mode", 1);
+ }
+
+ private static bool PlayerPrefsNeedsReset()
+ {
+ // If there was a crash, then assume playerprefs needs reset to prevent further crashes
+ if (PlayerPrefs.HasKey(playerPrefsCrashDetectorKey))
+ {
+ return true;
+ }
+
+ // TODO: not sure if should enable this, as if Unity resets 'Screenmanager Is Fullscreen mode' to windowed
+ // because the player was playing in windowed, and our mod wasn't able to override it,
+ // some users will have their settings reset unexpectedly.
+ //
+ // Additionally, the above crash detection seems sufficient to fix the Gnome crash bug.
+ // On Linux, if 'Screenmanager Is Fullscreen mode' is set to 0, assume game needs playerprefs reset?
+ //if (Application.platform == RuntimePlatform.LinuxPlayer)
+ //{
+ // if (PlayerPrefs.HasKey("Screenmanager Is Fullscreen mode"))
+ // {
+ // return PlayerPrefs.GetInt("Screenmanager Is Fullscreen mode") == 0;
+ // }
+ // else
+ // {
+ // return true;
+ // }
+ //}
+
+ return false;
+ }
+
+ public static void GameSystemInitSetResolution()
+ {
+ PrintPlayerPrefs("On Startup");
+
+ // On OS other than Linux, default to disabling fullscreen lock rather than asking the user,
+ // as it doesn't help with any known bugs on Windows/MacOS
+ if (Application.platform != RuntimePlatform.LinuxPlayer && !FullscreenLockConfigured())
+ {
+ SetFullScreenLock(false);
+ }
+
+ // If crash detected on linux, force fullscreen mode, in case crash was caused by gnome windowed mode bug.
+ // Also, show the window setup menu again so user can configure fullscreen lock.
+ if (Application.platform == RuntimePlatform.LinuxPlayer && PlayerPrefsNeedsReset())
+ {
+ ForceUnconfigureFullscreenLock();
+ // TODO: could fully reset playerprefs by calling PlayerPrefs.DeleteAll(), but not sure if such drastic measures are necessary?
+ GoFullscreen();
+ Debug.Log("WARNING: Crash or corrupted playerprefs detected. Reverting to fullscreen mode!");
+ PrintPlayerPrefs("After Fixing due to Crash or Corrupted PlayerPrefs");
+ }
+
+ PlayerPrefs.SetInt(playerPrefsCrashDetectorKey, 1);
+
+ // Restore IsFullscreen variable from playerprefs
+ TrySetIsFullscreen(PlayerPrefs.GetInt("is_fullscreen", 1) == 1, "On Startup");
+
+ // Restore fullscreenResolution variable using GetFullscreenResolution()
+ fullscreenResolution.width = 0;
+ fullscreenResolution.height = 0;
+ fullscreenResolution = GetFullscreenResolution();
+
+ // TODO: fix this when restoring from fullscreen on startup
+ // Now that variables restored set the actual resolution
+ SetResolution(null, null, null);
+
+ // If the playerprefs is corrupted, the game will crash shortly after starting up
+ // This prevents us from repairing the playerprefs, because the game normally only saves when the game exits cleanly
+ // To fix this, save the playerprefs immediately as soon as the game starts up
+ PlayerPrefs.Save();
+
+ PrintPlayerPrefs("After Init Resolution");
+ }
+
+ public static void GetFullScreenResolutionLateUpdate()
+ {
+ if (screenModeSet == -1)
+ {
+ screenModeSet = 0;
+ fullscreenResolution = Screen.currentResolution;
+ if (PlayerPrefs.HasKey("fullscreen_width") && PlayerPrefs.HasKey("fullscreen_height") && Screen.fullScreen)
+ {
+ fullscreenResolution.width = PlayerPrefs.GetInt("fullscreen_width");
+ fullscreenResolution.height = PlayerPrefs.GetInt("fullscreen_height");
+ }
+ Debug.Log("Fullscreen Resolution: " + fullscreenResolution.width + ", " + fullscreenResolution.height);
+ }
+ }
+
+ // This is inserted into both:
+ // - ~GameSystem(), which DOES NOT execute on Windows when the program exits (only Linux?)
+ // - OnApplicationQuit, to be called only if the application is really quitting, which runs on Windows
+ // - Calling this function more than once shouldn't cause any problems.
+ //
+ // Also note that on Windows:
+ //
+ // - if DISPLAY CONFIRMATION is OFF, and the 'task' is ended via normal task manager (not via the process list)
+ // this function is still called, because Windows will first try to close the program normally.
+ //
+ // - if DISPLAY CONFIRMATION is ON, since the program can't close on its own, windows will then
+ // force close the program which is detected as a crash
+ //
+ // - Additionally, if you go to the process instead of the program and kill it that way, it will always
+ // be detected as a crash
+ //
+ // On Linux, closing via task manager generally is treated as a crash.
+ public static void OnApplicationReallyQuit(string context)
+ {
+ // Fixes an issue where Unity would write garbage values to its saved state on Linux
+ // If we do this while the game is running, Unity will overwrite the values
+ // So do it in the finalizer, which will run as the game quits and the GameSystem is deallocated
+ //
+ // This also allows us to override the Screenmanager playerprefs variables on Windows,
+ // which are normally overwritten when the game exits
+ SetPlayerPrefs();
+
+ // Clear the crash detector key if program closed normally
+ PlayerPrefs.DeleteKey(playerPrefsCrashDetectorKey);
+
+ PrintPlayerPrefs($"OnApplicationReallyQuit() called from {context}");
+ }
+
+ public static void SetFullScreenLock(bool enableLock)
+ {
+ PlayerPrefs.SetInt(FULLSCREEN_LOCK_KEY, enableLock ? 1 : 0);
+ if(FullscreenLocked())
+ {
+ GoFullscreen();
+ }
+ }
+
+ public static bool FullscreenLocked()
+ {
+ return PlayerPrefs.GetInt(FULLSCREEN_LOCK_KEY, 0) != 0;
+ }
+
+ public static bool FullscreenLockConfigured()
+ {
+ return PlayerPrefs.HasKey(FULLSCREEN_LOCK_KEY);
+ }
+ public static void ForceUnconfigureFullscreenLock()
+ {
+ PlayerPrefs.DeleteKey(FULLSCREEN_LOCK_KEY);
+ }
+ public static void ClearPlayerPrefsFullscreenResolution()
+ {
+ PlayerPrefs.DeleteKey("fullscreen_width");
+ PlayerPrefs.DeleteKey("fullscreen_height");
+ }
+
+ private static Resolution GetFullscreenResolution()
+ {
+ Resolution resolution = new Resolution();
+ string source = "";
+ // Try to guess resolution from Screen.currentResolution
+ if (!Screen.fullScreen || Application.platform == RuntimePlatform.OSXPlayer)
+ {
+ resolution.width = fullscreenResolution.width = Screen.currentResolution.width;
+ resolution.height = fullscreenResolution.height = Screen.currentResolution.height;
+ source = "Screen.currentResolution";
+ }
+ else if (fullscreenResolution.width > 0 && fullscreenResolution.height > 0)
+ {
+ resolution.width = fullscreenResolution.width;
+ resolution.height = fullscreenResolution.height;
+ source = "Stored fullscreenResolution";
+ }
+ else if (PlayerPrefs.HasKey("fullscreen_width") && PlayerPrefs.HasKey("fullscreen_height"))
+ {
+ resolution.width = PlayerPrefs.GetInt("fullscreen_width");
+ resolution.height = PlayerPrefs.GetInt("fullscreen_height");
+ source = "PlayerPrefs";
+ }
+ else
+ {
+ resolution.width = Screen.currentResolution.width;
+ resolution.height = Screen.currentResolution.height;
+ source = "Screen.currentResolution as Fallback";
+ }
+
+ // Above can be glitchy on Linux, so also check the maximum resolution of a single monitor
+ // If it's bigger than that, then switch over
+ // Note that this (from what I can tell) gives you the biggest resolution of any of your monitors,
+ // not just the one the game is running under, so it could *also* be wrong, which is why we check both methods
+ if (Screen.resolutions.Length > 0)
+ {
+ int index = 0;
+ Resolution best = Screen.resolutions[0];
+ for (int i = 1; i < Screen.resolutions.Length; i++)
+ {
+ if (Screen.resolutions[i].height * Screen.resolutions[i].width > best.height * best.width)
+ {
+ best = Screen.resolutions[i];
+ index = i;
+ }
+ }
+ if (best.width <= resolution.width && best.height <= resolution.height)
+ {
+ resolution = best;
+ source = "Screen.resolutions #" + index;
+ }
+ }
+ if (!PlayerPrefs.HasKey("fullscreen_width_override"))
+ {
+ PlayerPrefs.SetInt("fullscreen_width_override", 0);
+ }
+ if (!PlayerPrefs.HasKey("fullscreen_height_override"))
+ {
+ PlayerPrefs.SetInt("fullscreen_height_override", 0);
+ }
+
+ if (PlayerPrefs.GetInt("fullscreen_width_override") > 0)
+ {
+ resolution.width = PlayerPrefs.GetInt("fullscreen_width_override");
+ source += " + Width Override";
+ }
+ if (PlayerPrefs.GetInt("fullscreen_height_override") > 0)
+ {
+ resolution.height = PlayerPrefs.GetInt("fullscreen_height_override");
+ source += " + Height Override";
+ }
+ Debug.Log("Using resolution " + resolution.width + "x" + resolution.height + " as the fullscreen resolution based on " + source + ".");
+ return resolution;
+ }
+ private static void TrySetIsFullscreen(bool isFullscreen, string context)
+ {
+ if (!isFullscreen && FullscreenLocked())
+ {
+ Debug.Log($"WARNING [{context}]: Attempted to change to windowed mode, but 'fullscreen lock' enabled, so staying in fullscreen mode!");
+ return;
+ }
+
+ _IsFullscreen = isFullscreen;
+ }
+ }
+}
diff --git a/MOD.Scripts.UI/MODMenu.cs b/MOD.Scripts.UI/MODMenu.cs
index 4c2fd81c..928d87d4 100644
--- a/MOD.Scripts.UI/MODMenu.cs
+++ b/MOD.Scripts.UI/MODMenu.cs
@@ -17,16 +17,46 @@ public enum ModSubMenu
{
Normal,
AudioSetup,
+ WindowSetup,
}
public class MODMenu
{
+ private class SubMenuManager
+ {
+ private Stack subMenuStack;
+
+ public SubMenuManager(MODMenuModuleInterface defaultItem)
+ {
+ this.subMenuStack = new Stack();
+ this.subMenuStack.Push(defaultItem);
+ }
+
+ public MODMenuModuleInterface CurrentMenu() => this.subMenuStack.Peek();
+ public void Push(MODMenuModuleInterface subMenu) => this.subMenuStack.Push(subMenu);
+
+ // Returns true if successfully removed an item off the stack
+ // Returns false if only the default item is left on the stack (no action taken)
+ public bool TryPop()
+ {
+ if (this.subMenuStack.Count > 1)
+ {
+ this.subMenuStack.Pop();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
private const int DEBUG_WINDOW_ID = 1;
private readonly GameSystem gameSystem;
public bool visible;
public bool debug;
- private bool lastMenuVisibleStatus;
+ private bool onBeforeMenuVisibleAlreadyCalled;
private MODSimpleTimer defaultToolTipTimer;
private MODSimpleTimer startupWatchdogTimer;
private bool startupFailed;
@@ -38,7 +68,9 @@ public class MODMenu
private MODMenuNormal normalMenu;
private MODMenuAudioOptions audioOptionsMenu;
private MODMenuAudioSetup audioSetupMenu;
- private MODMenuModuleInterface currentMenu; // The menu that is currently visible
+ private MODSubMenuWindowSetup windowSetupMenu;
+
+ private SubMenuManager subMenuManager;
string lastToolTip = String.Empty;
@@ -62,7 +94,7 @@ public MODMenu(GameSystem gameSystem)
this.gameSystem = gameSystem;
this.visible = false;
this.debug = false;
- this.lastMenuVisibleStatus = false;
+ this.onBeforeMenuVisibleAlreadyCalled = false;
this.defaultToolTipTimer = new MODSimpleTimer();
this.startupWatchdogTimer = new MODSimpleTimer();
this.startupFailed = false;
@@ -73,7 +105,9 @@ public MODMenu(GameSystem gameSystem)
this.audioOptionsMenu = new MODMenuAudioOptions(this);
this.normalMenu = new MODMenuNormal(this, this.audioOptionsMenu);
this.audioSetupMenu = new MODMenuAudioSetup(this, this.audioOptionsMenu);
- this.currentMenu = this.normalMenu;
+ this.windowSetupMenu = new MODSubMenuWindowSetup(this, this.normalMenu);
+
+ subMenuManager = new SubMenuManager(this.normalMenu);
this.debugWindowRect = new Rect(0, 0, Screen.width / 3, Screen.height - 50);
}
@@ -82,6 +116,7 @@ public void Update()
{
defaultToolTipTimer.Update();
startupWatchdogTimer.Update();
+ windowSetupMenu.Update();
}
public void LateUpdate()
@@ -210,14 +245,17 @@ public void OnGUIFragment()
GUILayout.EndArea();
}
+ // The rest of this function assumes currentMenu does not change while the function is executing,
+ // so this cached value of the current menu is used instead of subMenuManager.CurrentMenu()
+ MODMenuModuleInterface currentMenu = subMenuManager.CurrentMenu();
+
// If you need to initialize things just once before the menu opens, rather than every frame
// you can do it in the OnBeforeMenuVisible() function below.
- if (visible && !lastMenuVisibleStatus)
+ if (visible && !onBeforeMenuVisibleAlreadyCalled)
{
currentMenu.OnBeforeMenuVisible();
+ onBeforeMenuVisibleAlreadyCalled = true;
}
- lastMenuVisibleStatus = visible;
-
if (visible)
{
@@ -324,20 +362,45 @@ public void OverrideClickSound(GUISound sound)
buttonClickSound = sound;
}
- // The mod menu has different sub-menus, which can be switched between by calling this function.
+ // Switch to the given submenu, without discarding any previous submenus.
// If the sub-menus have any state, it will be retained during switching, and even if the menu is closed and reopened.
- public void SetSubMenu(ModSubMenu subMenu)
+ public void PushSubMenuAndShow(ModSubMenu subMenu)
{
+ onBeforeMenuVisibleAlreadyCalled = false;
+
+ MODMenuModuleInterface subMenuToPush = normalMenu;
+
switch (subMenu)
{
case ModSubMenu.AudioSetup:
- currentMenu = audioSetupMenu;
+ subMenuToPush = audioSetupMenu;
+ break;
+
+ case ModSubMenu.WindowSetup:
+ subMenuToPush = windowSetupMenu;
break;
case ModSubMenu.Normal:
- default:
- currentMenu = normalMenu;
+ subMenuToPush = normalMenu;
break;
+
+ default:
+ return;
+ }
+
+ subMenuManager.Push(subMenuToPush);
+
+ Show();
+ }
+
+ // Switch to the next submenu on the stack. If only the default submenu remains, just hide the entire menu.
+ public void PopSubMenu()
+ {
+ onBeforeMenuVisibleAlreadyCalled = false;
+
+ if (!subMenuManager.TryPop())
+ {
+ UserHide();
}
}
@@ -352,6 +415,7 @@ void ForceShow()
gameSystem.SetMODIgnoreInputs(menuStopsGameUpdate);
gameSystem.HideUIControls();
this.visible = true;
+ onBeforeMenuVisibleAlreadyCalled = false;
}
if (gameSystem.GameState == GameState.SaveLoadScreen)
@@ -378,7 +442,7 @@ void ForceShow()
///
public void UserHide()
{
- if (currentMenu.UserCanClose())
+ if (subMenuManager.CurrentMenu().UserCanClose())
{
ForceHide();
}
@@ -390,7 +454,7 @@ public void UserHide()
///
public void UserToggleVisibility()
{
- if (currentMenu.UserCanClose())
+ if (subMenuManager.CurrentMenu().UserCanClose())
{
ForceToggleVisibility();
}
diff --git a/MOD.Scripts.UI/MODMenuAudioSetup.cs b/MOD.Scripts.UI/MODMenuAudioSetup.cs
index df96c873..4bcf4a68 100644
--- a/MOD.Scripts.UI/MODMenuAudioSetup.cs
+++ b/MOD.Scripts.UI/MODMenuAudioSetup.cs
@@ -33,13 +33,12 @@ public void OnGUI()
if (GetGlobal("GAudioSet") != 0 && Button(new GUIContent("Click here when you're finished.")))
{
- modMenu.SetSubMenu(ModSubMenu.Normal);
- modMenu.ForceHide();
+ modMenu.PopSubMenu();
}
}
public bool UserCanClose() => false;
- public string Heading() => "First-Time Setup Menu";
+ public string Heading() => "Audio Setup Menu";
public string DefaultTooltip() => "Please choose the options on the left before continuing. You can hover over a button to view its description.";
}
}
diff --git a/MOD.Scripts.UI/MODMenuNormal.cs b/MOD.Scripts.UI/MODMenuNormal.cs
index 1180b3e7..7391010f 100644
--- a/MOD.Scripts.UI/MODMenuNormal.cs
+++ b/MOD.Scripts.UI/MODMenuNormal.cs
@@ -1,4 +1,5 @@
using Assets.Scripts.Core;
+using MOD.Scripts.Core;
using MOD.Scripts.Core.Audio;
using System;
using System.Collections.Generic;
@@ -31,6 +32,7 @@ class MODMenuNormal : MODMenuModuleInterface
private readonly MODRadio radioStretchBackgrounds;
private readonly MODRadio radioTextWindowModeAndCrop;
private readonly MODRadio radioForceComputedLipsync;
+ private readonly MODRadio radioFullscreenLock;
private readonly MODTabControl tabControl;
@@ -136,6 +138,11 @@ Sets the script censorship level
new GUIContent("Computed Always", "Always use computed lipsync for all voices. Any 'spectrum' files will be ignored.")
});
+ radioFullscreenLock = new MODRadio("Fullscreen Lock", new GUIContent[] {
+ new GUIContent("No Lock", "Allow switching to Windowed mode at any time"),
+ new GUIContent("Force Fullscreen Always", "Force fullscreen mode always - do not allow switching to windowed mode.")
+ });
+
tabControl = new MODTabControl(new List
{
new MODTabControl.TabProperties("Gameplay", "Voice Matching and Opening Videos", GameplayTabOnGUI),
@@ -282,6 +289,8 @@ private void GraphicsTabOnGUI()
HeadingLabel("Resolution");
resolutionMenu.OnGUI();
+
+ OnGUIFullscreenLock();
}
private void GameplayTabOnGUI()
@@ -485,6 +494,23 @@ private void OnGUIComputedLipsync()
}
}
+ public void OnGUIFullscreenLock()
+ {
+ HeadingLabel("Fullscreen Lock");
+
+ int currentValue = -1;
+ if(MODWindowManager.FullscreenLockConfigured())
+ {
+ currentValue = MODWindowManager.FullscreenLocked() ? 1 : 0;
+ }
+
+ if (this.radioFullscreenLock.OnGUIFragment(currentValue) is int newFullscreenlock)
+ {
+ MODWindowManager.SetFullScreenLock(newFullscreenlock == 0 ? false : true);
+ MODToaster.Show(MODWindowManager.FullscreenLocked() ? "Fullscreen lock enabled" : "Fullscreen lock disabled");
+ }
+ }
+
public bool UserCanClose() => true;
public string Heading() => "Mod Options Menu";
diff --git a/MOD.Scripts.UI/MODMenuResolution.cs b/MOD.Scripts.UI/MODMenuResolution.cs
index c8bc2f75..c4924f61 100644
--- a/MOD.Scripts.UI/MODMenuResolution.cs
+++ b/MOD.Scripts.UI/MODMenuResolution.cs
@@ -1,4 +1,5 @@
using Assets.Scripts.Core;
+using MOD.Scripts.Core;
using System;
using System.Collections.Generic;
using System.Text;
@@ -23,26 +24,30 @@ public void OnBeforeMenuVisible()
public void OnGUI()
{
- Label("Resolution Settings");
+ Label($"Resolution Settings (Current: {Screen.width}x{Screen.height})");
+
+ // I noticed a bug on Linux where going Windowed sometimes doesn't let you change window size,
+ // probably due res settings Playerprefs that Unity reads on startup.
+ // Restarting the game after changing settings fixes this.
+ if(Application.platform == RuntimePlatform.LinuxPlayer)
+ {
+ Label($"NOTE: You may need to restart the game after changing to Windowed!");
+ }
+
{
GUILayout.BeginHorizontal();
if (Button(new GUIContent("480p", "Set resolution to 853 x 480"))) { SetAndSaveResolution(480); }
if (Button(new GUIContent("720p", "Set resolution to 1280 x 720"))) { SetAndSaveResolution(720); }
if (Button(new GUIContent("1080p", "Set resolution to 1920 x 1080"))) { SetAndSaveResolution(1080); }
if (Button(new GUIContent("1440p", "Set resolution to 2560 x 1440"))) { SetAndSaveResolution(1440); }
- if (GameSystem.Instance.IsFullscreen)
+
+ GUIContent buttonContent = MODWindowManager.IsFullscreen ?
+ new GUIContent("Go Windowed", "Toggle Fullscreen") :
+ new GUIContent("Go Fullscreen", "Toggle Fullscreen");
+
+ if(Button(buttonContent))
{
- if (Button(new GUIContent("Windowed", "Toggle Fullscreen")))
- {
- GameSystem.Instance.DeFullscreen(PlayerPrefs.GetInt("width"), PlayerPrefs.GetInt("height"));
- }
- }
- else
- {
- if (Button(new GUIContent("Fullscreen", "Toggle Fullscreen")))
- {
- GameSystem.Instance.GoFullscreen();
- }
+ MODWindowManager.FullscreenToggle(showToast: true);
}
screenHeightString = GUILayout.TextField(screenHeightString);
@@ -51,44 +56,27 @@ public void OnGUI()
{
if (int.TryParse(screenHeightString, out int new_height))
{
- if (new_height < 480)
- {
- MODToaster.Show("Height too small - must be at least 480 pixels");
- new_height = 480;
- }
- else if (new_height > 15360)
- {
- MODToaster.Show("Height too big - must be less than 15360 pixels");
- new_height = 15360;
- }
- screenHeightString = $"{new_height}";
- int new_width = Mathf.RoundToInt(new_height * 16f / 9f);
- Screen.SetResolution(new_width, new_height, Screen.fullScreen);
- PlayerPrefs.SetInt("width", new_width);
- PlayerPrefs.SetInt("height", new_height);
+ SetAndSaveResolution(new_height);
}
}
GUILayout.EndHorizontal();
+
+ if (Button(new GUIContent(
+ "Reset Fullscreen Resolution",
+ "Force re-detection of the fullscreen resolution (matching your monitor's max resolution).\n\n" +
+ "You may want to do this if you change to a monitor with a different fullscreen resolution, and the game detects the wrong resolution.")
+ ))
+ {
+ MODWindowManager.ClearPlayerPrefsFullscreenResolution();
+ MODWindowManager.GoFullscreen();
+ }
}
}
private void SetAndSaveResolution(int height)
{
- if (height < 480)
- {
- MODToaster.Show("Height too small - must be at least 480 pixels");
- height = 480;
- }
- else if (height > 15360)
- {
- MODToaster.Show("Height too big - must be less than 15360 pixels");
- height = 15360;
- }
screenHeightString = $"{height}";
- int width = Mathf.RoundToInt(height * 16f / 9f);
- Screen.SetResolution(width, height, Screen.fullScreen);
- PlayerPrefs.SetInt("width", width);
- PlayerPrefs.SetInt("height", height);
+ MODWindowManager.SetResolution(height, showToast: true);
}
}
}
diff --git a/MOD.Scripts.UI/MODMenuSupport.cs b/MOD.Scripts.UI/MODMenuSupport.cs
index c0a94159..1ad4c2d8 100644
--- a/MOD.Scripts.UI/MODMenuSupport.cs
+++ b/MOD.Scripts.UI/MODMenuSupport.cs
@@ -99,6 +99,31 @@ public static void ShowSupportButtons(Func buttonRenderer)
}
GUILayout.EndHorizontal();
+
+ if(Application.platform == RuntimePlatform.WindowsPlayer)
+ {
+ string playerPrefsPath = $"Computer\\HKEY_CURRENT_USER\\SOFTWARE\\{Application.companyName}\\{Application.productName}";
+ if (buttonRenderer(new GUIContent(
+ "Copy PlayerPrefs Registry Path",
+ $"Click to copy the playerprefs registry folder to clipboard. This registry folder mainly contains Unity resolution settings.\n\n" +
+ $"Paste the path into the Windows Registry Editor (regedit) to view playerprefs.\n\n" +
+ $"Registry Path: {playerPrefsPath}"
+ )))
+ {
+ GUIUtility.systemCopyBuffer = playerPrefsPath;
+ }
+ }
+ else
+ {
+ string playerPrefsPath = Application.platform == RuntimePlatform.LinuxPlayer ?
+ $"~/.config/unity3d/{Application.companyName}/{Application.productName}" :
+ "~/Library/Preferences";
+
+ if (buttonRenderer(new GUIContent("Show PlayerPrefs Folder", $"Click to show the folder containing the playerprefs config file. This config file mainly contains Unity resolution settings.\n\nPath: {playerPrefsPath}")))
+ {
+ MODActions.ShowFile(playerPrefsPath);
+ }
+ }
}
MODMenuCommon.Label("Support Pages");
diff --git a/MOD.Scripts.UI/MODSubMenuWindowSetup.cs b/MOD.Scripts.UI/MODSubMenuWindowSetup.cs
new file mode 100644
index 00000000..57818744
--- /dev/null
+++ b/MOD.Scripts.UI/MODSubMenuWindowSetup.cs
@@ -0,0 +1,115 @@
+using MOD.Scripts.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+using static MOD.Scripts.UI.MODMenuCommon;
+
+namespace MOD.Scripts.UI
+{
+ class MODSubMenuWindowSetup : MODMenuModuleInterface
+ {
+ MODMenu modMenu;
+ MODMenuNormal normalMenu;
+ MODSimpleTimer setWindowAgainDelay;
+
+ public MODSubMenuWindowSetup(MODMenu modMenu, MODMenuNormal normalMenu)
+ {
+ this.modMenu = modMenu;
+ this.normalMenu = normalMenu;
+ this.setWindowAgainDelay = new MODSimpleTimer();
+ }
+
+ public void OnBeforeMenuVisible()
+ {
+
+ }
+
+ public void Update()
+ {
+ setWindowAgainDelay.Update();
+ }
+
+ public void OnGUI()
+ {
+ // Fix a bug where windowed resolution becomes 640x480 when set the first time game starts (if playerprefs doesn't exist).
+ // Resolution is set correctly if set again after a short delay.
+ // Bug was first noticed on Ep1 Native Ubuntu 22.04.
+ if(setWindowAgainDelay.Finished())
+ {
+ MODWindowManager.SetResolution(maybe_width: null, maybe_height: null, maybe_fullscreen: false, showToast: true);
+ setWindowAgainDelay.Cancel();
+ }
+
+ HeadingLabel("Linux Resolution/Windowed Mode Setup");
+
+ GUILayout.Space(20);
+
+ Label("Some Native Linux users experience crashes or softlocks when entering windowed mode, or when moving the window around (the 'Gnome Crash Bug').\n\n" +
+ "Please click the button below to enter windowed mode, then drag the window around.");
+
+ GUILayout.Space(20);
+
+ Label("NOTE: This may crash your entire desktop environment! Please save your work before this test!");
+
+ GUILayout.Space(20);
+
+ if (
+ Button(
+ new GUIContent(
+ MODWindowManager.IsFullscreen ? "Click here to test Windowed Mode (may flicker)" : "Now try dragging the window around!",
+ "This button will switch the game to Windowed mode.\n\n" +
+ "Please try moving the window around to see if the game crashes\n\n" +
+ "If the game freezes, you may need to force close it and open the game again."
+ ),
+ selected: !MODWindowManager.IsFullscreen
+ )
+ )
+ {
+ MODWindowManager.SetResolution(maybe_width: null, maybe_height: null, maybe_fullscreen: false, showToast: true);
+ setWindowAgainDelay.Start(1);
+ }
+
+ GUILayout.Space(20);
+
+ Label("If your game crashed in windowed mode, choose 'Force Fullscreen Always' to avoid future crashes.");
+
+ GUILayout.Space(20);
+
+ Label("If your game runs fine even when the window is dragged, choose 'No Lock' to run the game normally.");
+
+ GUILayout.Space(20);
+
+ normalMenu.OnGUIFullscreenLock();
+
+ GUILayout.Space(20);
+
+ if (MODWindowManager.FullscreenLockConfigured())
+ {
+ if(Button(new GUIContent("Click here when you're finished.")))
+ {
+ modMenu.PopSubMenu();
+ }
+ }
+ else
+ {
+ Label("You must choose an option to continue.");
+ }
+ }
+
+ public bool UserCanClose() => false;
+ public string Heading()
+ {
+ if (setWindowAgainDelay.Running())
+ {
+ return $"Please Wait ({setWindowAgainDelay.timeLeft:F1})";
+ }
+ else
+ {
+ return "Linux Resolution/Windowed Mode Setup Menu";
+ }
+ }
+
+ public string DefaultTooltip() => "Please choose the options on the left before continuing. You can hover over a button to view its description.";
+ }
+}