using System;
using System.Collections;
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 System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using Valheim.SettingsGui;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("BorderlessWindowed")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Riintouge")]
[assembly: AssemblyProduct("BorderlessWindowed")]
[assembly: AssemblyCopyright("Copyright © 2024")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("0da70214-2c9c-40a9-9b69-43f07bf97314")]
[assembly: AssemblyFileVersion("1.1.1.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.1.0")]
[module: UnverifiableCode]
namespace BorderlessWindowed;
[BepInPlugin("com.riintouge.borderlesswindowed", "Borderless Windowed", "1.1.1")]
[BepInProcess("valheim.exe")]
public class BorderlessWindowed : BaseUnityPlugin
{
[HarmonyPatch(typeof(GraphicsModeManager))]
private class GraphicsModeManagerPatch
{
[HarmonyPatch("ApplyMode")]
[HarmonyPostfix]
private static void ApplyModePostfix(bool __result, GraphicsQualityMode mode)
{
if (__result)
{
((MonoBehaviour)Instance).StartCoroutine(Instance.CoUpdateBorder());
}
}
}
public static BorderlessWindowed Instance;
public static ConfigEntry<bool> LoadOnStart;
public static ConfigEntry<bool> ShowBorder;
private readonly Harmony Harmony = new Harmony("com.riintouge.borderlesswindowed");
private readonly BorderHelper BorderHelper = new BorderHelper();
private void Awake()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
LoadOnStart = ((BaseUnityPlugin)this).Config.Bind<bool>("0 - Core", "LoadOnStart", true, "Whether this plugin loads on game start.");
ShowBorder = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "ShowBorder", true, "Whether the border should be shown on the game window.");
if (LoadOnStart.Value)
{
Instance = this;
Harmony.PatchAll();
((BaseUnityPlugin)this).Config.SettingChanged += Config_SettingChanged;
}
}
}
private void Config_SettingChanged(object sender, SettingChangedEventArgs e)
{
if (e.ChangedSetting == ShowBorder)
{
((MonoBehaviour)Instance).StartCoroutine(Instance.CoUpdateBorder());
}
}
internal IEnumerator CoUpdateBorder()
{
return BorderHelper.UpdateBorder(ShowBorder.Value);
}
}
internal class BorderHelper
{
private const uint GameWindowBorderFlags = 13565952u;
private static object UpdateBorderLock = new object();
private NativeMethods.WindowRect? outerBorderThickness;
private NativeMethods.WindowRect? innerWindowedBorderThickness;
private NativeMethods.WindowRect? innerMaximizedBorderThickness;
public IEnumerator UpdateBorder(bool enable)
{
if (Monitor.TryEnter(UpdateBorderLock))
{
try
{
yield return UpdateBorderCore(enable);
}
finally
{
Monitor.Exit(UpdateBorderLock);
}
}
}
private IEnumerator UpdateBorderCore(bool enable)
{
bool gameIsStarting = (Object)(object)Console.instance == (Object)null;
yield return null;
if (Screen.fullScreen)
{
yield break;
}
IntPtr hWnd = FindGameWindowHandle();
if (hWnd == IntPtr.Zero)
{
yield break;
}
bool flag = GameWindowHasBorder(hWnd);
if (enable == flag)
{
yield break;
}
DebugMessage(">>> BEGIN");
DebugMessage($"SetGameWindowBorder({enable})");
bool isMaximized = NativeMethods.IsZoomed(hWnd);
bool isResolutionPreset = Screen.resolutions.Any((Resolution x) => Screen.width == ((Resolution)(ref x)).width && Screen.height == ((Resolution)(ref x)).height);
int sceneWidth = Screen.width;
int sceneHeight = Screen.height;
DebugMessage(string.Format("Scene extents, {0}: {1}x{2} ({3}a preset)", isMaximized ? "maximized" : "windowed", Screen.width, Screen.height, isResolutionPreset ? "" : "not "));
GetGameWindowRects(hWnd, out var outer, out var innerInitial);
DebugMessage($"Outer, initial: ({outer.Left},{outer.Top}) to ({outer.Right},{outer.Bottom})");
DebugMessage($"Inner, initial: ({innerInitial.Left},{innerInitial.Top}) to ({innerInitial.Right},{innerInitial.Bottom})");
if (!outerBorderThickness.HasValue)
{
if (!flag)
{
yield break;
}
outerBorderThickness = ComputeBorderThickness(outer, sceneWidth, sceneHeight);
}
if (!isMaximized && !innerWindowedBorderThickness.HasValue)
{
if (!flag)
{
yield break;
}
innerWindowedBorderThickness = ComputeBorderThickness(innerInitial, sceneWidth, sceneHeight);
}
if (isMaximized && !innerMaximizedBorderThickness.HasValue)
{
if (!flag)
{
yield break;
}
innerMaximizedBorderThickness = ComputeBorderThickness(innerInitial, sceneWidth, sceneHeight);
}
SetGameWindowBorder(hWnd, enable);
yield return null;
int left = innerInitial.Left;
int top = innerInitial.Top;
int num = sceneWidth;
int num2 = sceneHeight;
if (enable)
{
NativeMethods.WindowRect value = outerBorderThickness.Value;
left -= value.Left;
top -= value.Top;
num = sceneWidth + value.Left + value.Right;
if (isResolutionPreset)
{
num2 += value.Top + value.Bottom;
}
else if (!isMaximized)
{
NativeMethods.WindowRect value2 = innerWindowedBorderThickness.Value;
num2 += value.Top - value2.Top + (value.Bottom - value2.Bottom);
}
}
else
{
NativeMethods.WindowRect windowRect = (isMaximized ? innerMaximizedBorderThickness.Value : innerWindowedBorderThickness.Value);
left += windowRect.Left;
top += windowRect.Top;
if (gameIsStarting)
{
left--;
}
else if (!isResolutionPreset)
{
num2 += windowRect.Top + windowRect.Bottom;
}
}
uint flags = 52u;
DebugMessage($"SetWindowPos( ... , {left} , {top} , {num} , {num2} , ... );");
if (num2 == sceneHeight && num != sceneWidth)
{
NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, left, top, num, num2 - 1, flags);
}
NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, left, top, num, num2, flags);
yield return null;
DebugMessage("<<< END\n");
}
private NativeMethods.WindowRect ComputeBorderThickness(NativeMethods.WindowRect outer, int width, int height)
{
NativeMethods.WindowRect result = default(NativeMethods.WindowRect);
result.Left = (outer.Right - outer.Left - width) / 2;
result.Right = result.Left;
result.Bottom = outer.Bottom - outer.Top - height;
DebugMessage($"Computed border thickness: LR {result.Left},{result.Right} to TB {result.Top},{result.Bottom}");
return result;
}
private static void DebugMessage(string message)
{
}
private static IntPtr FindGameWindowHandle()
{
IntPtr gameWindowHandle = IntPtr.Zero;
NativeMethods.EnumDesktopWindows(lpfn: delegate(IntPtr hWnd, int lParam)
{
StringBuilder stringBuilder = new StringBuilder(255);
NativeMethods.GetWindowText(hWnd, stringBuilder, stringBuilder.Capacity + 1);
string text = stringBuilder.ToString();
if (NativeMethods.IsWindowVisible(hWnd) && text == "Valheim")
{
gameWindowHandle = hWnd;
return false;
}
return true;
}, hDesktop: IntPtr.Zero, lParam: IntPtr.Zero);
return gameWindowHandle;
}
private static bool GameWindowHasBorder(IntPtr hWnd)
{
if (hWnd != IntPtr.Zero)
{
return (NativeMethods.GetWindowLong(hWnd, -16) & 0xCF0000) != 0;
}
return false;
}
private unsafe static void GetGameWindowRects(IntPtr hWnd, out NativeMethods.WindowRect outer, out NativeMethods.WindowRect inner)
{
NativeMethods.GetWindowRect(hWnd, out var lpRect);
outer = lpRect;
NativeMethods.WindowRect windowRect = default(NativeMethods.WindowRect);
NativeMethods.DwmGetWindowAttribute(hWnd, 9, &windowRect, sizeof(NativeMethods.WindowRect));
inner = windowRect;
}
private static void SetGameWindowBorder(IntPtr hWnd, bool enable)
{
if (!(hWnd == IntPtr.Zero))
{
uint windowLong = NativeMethods.GetWindowLong(hWnd, -16);
bool flag = (windowLong & 0xCF0000) != 0;
if (enable != flag)
{
windowLong = ((!enable) ? (windowLong &= 0xFF30FFFFu) : (windowLong |= 0xCF0000u));
NativeMethods.SetWindowLong(hWnd, -16, windowLong);
}
}
}
}
public class NativeMethods
{
public delegate bool EnumDesktopWindowsDelegate(IntPtr hwnd, int lParam);
public struct WindowRect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public const int DWMWA_EXTENDED_FRAME_BOUNDS = 9;
public const int GWL_STYLE = -16;
public const uint WS_MAXIMIZEBOX = 65536u;
public const uint WS_MINIMIZEBOX = 131072u;
public const uint WS_THICKFRAME = 262144u;
public const uint WS_SYSMENU = 524288u;
public const uint WS_DLGFRAME = 4194304u;
public const uint WS_BORDER = 8388608u;
public const uint SWP_ASYNCWINDOWPOS = 16384u;
public const uint SWP_DEFERERASE = 8192u;
public const uint SWP_DRAWFRAME = 32u;
public const uint SWP_FRAMECHANGED = 32u;
public const uint SWP_HIDEWINDOW = 128u;
public const uint SWP_NOACTIVATE = 16u;
public const uint SWP_NOCOPYBITS = 256u;
public const uint SWP_NOMOVE = 2u;
public const uint SWP_NOOWNERZORDER = 512u;
public const uint SWP_NOREDRAW = 8u;
public const uint SWP_NOREPOSITION = 512u;
public const uint SWP_NOSENDCHANGING = 1024u;
public const uint SWP_NOSIZE = 1u;
public const uint SWP_NOZORDER = 4u;
public const uint SWP_SHOWWINDOW = 64u;
[DllImport("dwmapi.dll")]
public unsafe static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, void* pvAttribute, int cbAttribute);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumDesktopWindowsDelegate lpfn, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsZoomed(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint flags);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out WindowRect lpRect);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, [Out] StringBuilder lpString, int nMaxCount);
}