diff --git a/AquaMai/AquaMai.csproj b/AquaMai/AquaMai.csproj
index 9b5f785e..2e833770 100644
--- a/AquaMai/AquaMai.csproj
+++ b/AquaMai/AquaMai.csproj
@@ -314,6 +314,7 @@
+
diff --git a/AquaMai/AquaMai.toml b/AquaMai/AquaMai.toml
index fa18eac3..fa4c3bee 100644
--- a/AquaMai/AquaMai.toml
+++ b/AquaMai/AquaMai.toml
@@ -72,6 +72,12 @@ JudgeAdjustA=0.0
JudgeAdjustB=0.0
# Touch screen delay, unit is milliseconds, one second = 1000 milliseconds. Must be an integer
TouchDelay=0
+# Window the game
+Windowed=false
+# Width and height for windowed mode, rendering resolution for fullscreen mode
+# If set to 0, windowed mode will remember the user-set size, fullscreen mode will use the current display resolution
+Width=0
+Height=0
# ===================================
# Save some potentially unnecessary time
diff --git a/AquaMai/AquaMai.zh.toml b/AquaMai/AquaMai.zh.toml
index 512023c1..d5049b7e 100644
--- a/AquaMai/AquaMai.zh.toml
+++ b/AquaMai/AquaMai.zh.toml
@@ -88,6 +88,12 @@ JudgeAdjustA=0.0
JudgeAdjustB=0.0
# 触摸屏延迟,单位为毫秒,一秒 = 1000 毫秒。必须是整数
TouchDelay=0
+# 窗口化游戏
+Windowed=false
+# 宽度和高度窗口化时为游戏窗口大小,全屏时为渲染分辨率
+# 如果设为 0,窗口化将记住用户设定的大小,全屏时将使用当前显示器分辨率
+Width=0
+Height=0
# ===================================
# 节省一些不知道有用没用的时间
diff --git a/AquaMai/Config.cs b/AquaMai/Config.cs
index 5a59fa4c..746d7bc8 100644
--- a/AquaMai/Config.cs
+++ b/AquaMai/Config.cs
@@ -57,6 +57,9 @@ namespace AquaMai
public float JudgeAdjustA { get; set; }
public float JudgeAdjustB { get; set; }
public int TouchDelay { get; set; }
+ public bool Windowed { get; set; }
+ public int Width { get; set; }
+ public int Height { get; set; }
}
public class TimeSavingConfig
diff --git a/AquaMai/Main.cs b/AquaMai/Main.cs
index 4a132b4b..bd1c5d05 100644
--- a/AquaMai/Main.cs
+++ b/AquaMai/Main.cs
@@ -73,7 +73,6 @@ namespace AquaMai
{
Patch(directiveType);
}
- else MelonLogger.Error($"Type not found for {categoryProp.Name}.{settingProp.Name}");
}
}
}
@@ -119,6 +118,7 @@ namespace AquaMai
// Fixes that does not have side effects
// These don't need to be configurable
+ WindowState.Execute();
// Helpers
Patch(typeof(MessageHelper));
Patch(typeof(MusicDirHelper));
diff --git a/AquaMai/Utils/WindowState.cs b/AquaMai/Utils/WindowState.cs
new file mode 100644
index 00000000..f6b91028
--- /dev/null
+++ b/AquaMai/Utils/WindowState.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace AquaMai.Utils;
+
+public class WindowState
+{
+ private const int GWL_STYLE = -16;
+ private const int WS_WHATEVER = 0x14CF0000;
+
+ private static IntPtr hwnd = IntPtr.Zero;
+
+ public static void Execute()
+ {
+ if (AquaMai.AppConfig.Utils.Windowed)
+ {
+ var alreadyWindowed = Screen.fullScreenMode == FullScreenMode.Windowed;
+ if (AquaMai.AppConfig.Utils.Width == 0 || AquaMai.AppConfig.Utils.Height == 0)
+ {
+ Screen.fullScreenMode = FullScreenMode.Windowed;
+ }
+ else
+ {
+ alreadyWindowed = false;
+ Screen.SetResolution(AquaMai.AppConfig.Utils.Width, AquaMai.AppConfig.Utils.Height, FullScreenMode.Windowed);
+ }
+
+ hwnd = GetWindowHandle();
+ if(alreadyWindowed)
+ {
+ SetResizeable();
+ }
+ else
+ {
+ Task.Run(async () =>
+ {
+ await Task.Delay(3000);
+ // Screen.SetResolution has delay
+ SetResizeable();
+ });
+ }
+ }
+ else
+ {
+ var width = AquaMai.AppConfig.Utils.Width == 0 ? Display.main.systemWidth : AquaMai.AppConfig.Utils.Width;
+ var height = AquaMai.AppConfig.Utils.Height == 0 ? Display.main.systemHeight : AquaMai.AppConfig.Utils.Height;
+ Screen.SetResolution(width, height, FullScreenMode.FullScreenWindow);
+ }
+ }
+
+ public static void SetResizeable()
+ {
+ if (hwnd == IntPtr.Zero) return;
+ SetWindowLongPtr(hwnd, GWL_STYLE, WS_WHATEVER);
+ }
+
+ private delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
+
+ [DllImport("Kernel32.dll")]
+ static extern int GetCurrentThreadId();
+
+ static IntPtr GetWindowHandle()
+ {
+ IntPtr returnHwnd = IntPtr.Zero;
+ var threadId = GetCurrentThreadId();
+ EnumThreadWindows(threadId,
+ (hWnd, lParam) =>
+ {
+ if (returnHwnd == IntPtr.Zero) returnHwnd = hWnd;
+ return true;
+ }, IntPtr.Zero);
+ return returnHwnd;
+ }
+
+ [DllImport("user32.dll")]
+ static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, int dwNewLong);
+}