core (0.4): Implement separate display vs game scaling (thanks, NotNite!)
This commit is contained in:
parent
78ff318397
commit
ff12d438c6
16 changed files with 1126 additions and 597 deletions
5
.editorconfig
Normal file
5
.editorconfig
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# CS9084: Struct member returns 'this' or other instance members by reference
|
||||||
|
# While this would be a huge no-no in fully managed projects, we use structs to poke game memory.
|
||||||
|
dotnet_diagnostic.CS9084.severity = silent
|
|
@ -7,6 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomResolution2782", "Cus
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5ECEDCE5-D60F-4A8A-AB33-4131F5C7371C}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5ECEDCE5-D60F-4A8A-AB33-4131F5C7371C}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.editorconfig = .editorconfig
|
||||||
Directory.Build.props = Directory.Build.props
|
Directory.Build.props = Directory.Build.props
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
|
79
CustomResolution2782/Cmds/GameResCmd.cs
Normal file
79
CustomResolution2782/Cmds/GameResCmd.cs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CustomResolution.Cmds;
|
||||||
|
|
||||||
|
public sealed class GameResCmd : Cmd
|
||||||
|
{
|
||||||
|
public override string Name => "gres";
|
||||||
|
|
||||||
|
public override string HelpMessage => $"Open / adjust the CustomResolution game scale settings.\n" +
|
||||||
|
$"\tExamples:\n" +
|
||||||
|
$"\tTo open the settings:\n\t\t{FullName}\n" +
|
||||||
|
$"\tTo enable / disable it:\n\t\t{FullName} on\n\t\t{FullName} off\n\t\t{FullName} toggle\n" +
|
||||||
|
$"\tTo set the game scale:\n\t\t{FullName} 1.5\n" +
|
||||||
|
$"\tTo set the game resolution:\n\t\t{FullName} 1920 1080";
|
||||||
|
|
||||||
|
public override void Run(string arguments)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(arguments))
|
||||||
|
{
|
||||||
|
Service.PluginUI.SettingsVisible = !Service.PluginUI.SettingsVisible;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int indexOfSplit = arguments.IndexOf(' ');
|
||||||
|
|
||||||
|
if (indexOfSplit != -1)
|
||||||
|
{
|
||||||
|
if (!uint.TryParse(arguments.AsSpan(0, indexOfSplit), CultureInfo.InvariantCulture, out var width) ||
|
||||||
|
!uint.TryParse(arguments.AsSpan(indexOfSplit + 1), CultureInfo.InvariantCulture, out var height))
|
||||||
|
{
|
||||||
|
Service.PrintChat("Invalid parameters.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Service.Config._.Game.IsScale = false;
|
||||||
|
Service.Config._.Game.Width = width;
|
||||||
|
Service.Config._.Game.Height = height;
|
||||||
|
Service.Config.Save();
|
||||||
|
|
||||||
|
Service.PrintChat("Updated custom game resolution.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (arguments.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "on":
|
||||||
|
Service.Config._.Game.IsEnabled = true;
|
||||||
|
Service.Config.Save();
|
||||||
|
Service.PrintChat("Enabled custom game resolution.");
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "off":
|
||||||
|
Service.Config._.Game.IsEnabled = false;
|
||||||
|
Service.Config.Save();
|
||||||
|
Service.PrintChat("Disabled custom game resolution.");
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "toggle":
|
||||||
|
Service.Config._.Game.IsEnabled = !Service.Config._.Game.IsEnabled;
|
||||||
|
Service.Config.Save();
|
||||||
|
Service.PrintChat($"{(Service.Config._.Game.IsEnabled ? "Enabled" : "Disabled")} custom game resolution.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!float.TryParse(arguments, CultureInfo.InvariantCulture, out float value))
|
||||||
|
{
|
||||||
|
Service.PrintChat("Invalid parameters.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Service.Config._.Game.IsScale = true;
|
||||||
|
Service.Config._.Game.Scale = value;
|
||||||
|
Service.Config.Save();
|
||||||
|
|
||||||
|
Service.PrintChat("Updated custom game resolution scale.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,12 +7,12 @@ public sealed class MainCmd : Cmd
|
||||||
{
|
{
|
||||||
public override string Name => "cres";
|
public override string Name => "cres";
|
||||||
|
|
||||||
public override string HelpMessage => $"Open / adjust the CustomResolution settings.\n" +
|
public override string HelpMessage => $"Open / adjust the CustomResolution display scale settings.\n" +
|
||||||
$"\tExamples:\n" +
|
$"\tExamples:\n" +
|
||||||
$"\tTo open the settings:\n\t\t{FullName}\n" +
|
$"\tTo open the settings:\n\t\t{FullName}\n" +
|
||||||
$"\tTo enable / disable it:\n\t\t{FullName} on\n\t\t{FullName} off\n\t\t{FullName} toggle\n" +
|
$"\tTo enable / disable it:\n\t\t{FullName} on\n\t\t{FullName} off\n\t\t{FullName} toggle\n" +
|
||||||
$"\tTo set the scale:\n\t\t{FullName} 1.5\n" +
|
$"\tTo set the display scale:\n\t\t{FullName} 1.5\n" +
|
||||||
$"\tTo set the resolution:\n\t\t{FullName} 1920 1080";
|
$"\tTo set the display resolution:\n\t\t{FullName} 1920 1080";
|
||||||
|
|
||||||
public override void Run(string arguments)
|
public override void Run(string arguments)
|
||||||
{
|
{
|
||||||
|
@ -34,33 +34,33 @@ public sealed class MainCmd : Cmd
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Service.Config.IsScale = false;
|
Service.Config._.Display.IsScale = false;
|
||||||
Service.Config.Width = width;
|
Service.Config._.Display.Width = width;
|
||||||
Service.Config.Height = height;
|
Service.Config._.Display.Height = height;
|
||||||
Service.Config.Save();
|
Service.Config.Save();
|
||||||
|
|
||||||
Service.PrintChat("Updated custom resolution.");
|
Service.PrintChat("Updated custom display resolution.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (arguments.ToLowerInvariant())
|
switch (arguments.ToLowerInvariant())
|
||||||
{
|
{
|
||||||
case "on":
|
case "on":
|
||||||
Service.Config.IsEnabled = true;
|
Service.Config._.Display.IsEnabled = true;
|
||||||
Service.Config.Save();
|
Service.Config.Save();
|
||||||
Service.PrintChat("Enabled custom resolution.");
|
Service.PrintChat("Enabled custom display resolution.");
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "off":
|
case "off":
|
||||||
Service.Config.IsEnabled = false;
|
Service.Config._.Display.IsEnabled = false;
|
||||||
Service.Config.Save();
|
Service.Config.Save();
|
||||||
Service.PrintChat("Disabled custom resolution.");
|
Service.PrintChat("Disabled custom display resolution.");
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "toggle":
|
case "toggle":
|
||||||
Service.Config.IsEnabled = !Service.Config.IsEnabled;
|
Service.Config._.Display.IsEnabled = !Service.Config._.Display.IsEnabled;
|
||||||
Service.Config.Save();
|
Service.Config.Save();
|
||||||
Service.PrintChat($"{(Service.Config.IsEnabled ? "Enabled" : "Disabled")} custom resolution.");
|
Service.PrintChat($"{(Service.Config._.Display.IsEnabled ? "Enabled" : "Disabled")} custom display resolution.");
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "debugon":
|
case "debugon":
|
||||||
|
@ -95,10 +95,10 @@ public sealed class MainCmd : Cmd
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Service.Config.IsScale = true;
|
Service.Config._.Display.IsScale = true;
|
||||||
Service.Config.Scale = value;
|
Service.Config._.Display.Scale = value;
|
||||||
Service.Config.Save();
|
Service.Config.Save();
|
||||||
|
|
||||||
Service.PrintChat("Updated custom resolution scale.");
|
Service.PrintChat("Updated custom display resolution scale.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,151 +1,77 @@
|
||||||
using Dalamud.Configuration;
|
using Dalamud.Configuration;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace CustomResolution;
|
namespace CustomResolution;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class Configuration : IPluginConfiguration
|
public class Configuration : IPluginConfiguration
|
||||||
{
|
{
|
||||||
public int Version { get; set; } = 0;
|
public ConfigurationV1 V1 = new();
|
||||||
|
public int Version { get; set; } = 1;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public ref ConfigurationV1 _ => ref V1;
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
public bool IsEnabled = true;
|
public bool IsEnabled = true;
|
||||||
|
[Obsolete]
|
||||||
public bool IsScale = true;
|
public bool IsScale = true;
|
||||||
|
[Obsolete]
|
||||||
public float Scale = 1f;
|
public float Scale = 1f;
|
||||||
|
[Obsolete]
|
||||||
public uint Width = 1024;
|
public uint Width = 1024;
|
||||||
|
[Obsolete]
|
||||||
public uint Height = 1024;
|
public uint Height = 1024;
|
||||||
|
[Obsolete]
|
||||||
public VirtualKey HotkeyKey = VirtualKey.NO_KEY;
|
public VirtualKey HotkeyKey = VirtualKey.NO_KEY;
|
||||||
|
[Obsolete]
|
||||||
public ModifierKey HotkeyModifier = ModifierKey.NONE;
|
public ModifierKey HotkeyModifier = ModifierKey.NONE;
|
||||||
|
[Obsolete]
|
||||||
public DXVKDWMHackMode DXVKDWMHackMode = DXVKDWMHackMode.Off;
|
public DXVKDWMHackMode DXVKDWMHackMode = DXVKDWMHackMode.Off;
|
||||||
|
[Obsolete]
|
||||||
public MinSizeMode MinSizeMode = MinSizeMode.Unchanged;
|
public MinSizeMode MinSizeMode = MinSizeMode.Unchanged;
|
||||||
|
|
||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
private IDalamudPluginInterface? pluginInterface;
|
private IDalamudPluginInterface pluginInterface = null!;
|
||||||
|
|
||||||
internal void Initialize(IDalamudPluginInterface pluginInterface)
|
internal void Initialize(IDalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
this.pluginInterface = pluginInterface;
|
this.pluginInterface = pluginInterface;
|
||||||
|
Upgrade();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
pluginInterface!.SavePluginConfig(this);
|
_.Display.Clamp();
|
||||||
|
_.Game.Clamp();
|
||||||
|
pluginInterface.SavePluginConfig(this);
|
||||||
Service.PluginUI.UpdateFromConfig();
|
Service.PluginUI.UpdateFromConfig();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Must explicitly map to integer values, blame Newtonsoft.JSON
|
private void Upgrade()
|
||||||
public enum DXVKDWMHackMode
|
|
||||||
{
|
|
||||||
Off = 0,
|
|
||||||
UnsetPopup = 1,
|
|
||||||
SetClientEdgeResize = 3,
|
|
||||||
SetClientEdgeBorder = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DXVKDWMHackModeExt
|
|
||||||
{
|
|
||||||
public static string ToHumanNameString(this DXVKDWMHackMode mode) => mode switch
|
|
||||||
{
|
{
|
||||||
DXVKDWMHackMode.Off => "Off",
|
#pragma warning disable CS0612 // Type or member is obsolete, but we want to migrate from it.
|
||||||
DXVKDWMHackMode.UnsetPopup => "Best: -WS_POPUP",
|
#pragma warning disable CS0618 // Type or member is obsolete, but we want to migrate from it.
|
||||||
DXVKDWMHackMode.SetClientEdgeResize => "+WS_EX_CLIENTEDGE (resize)",
|
if (Version == 0)
|
||||||
DXVKDWMHackMode.SetClientEdgeBorder => "+WS_EX_CLIENTEDGE (border)",
|
|
||||||
_ => mode.ToString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static string? ToHumanInfoString(this DXVKDWMHackMode mode) => mode switch
|
|
||||||
{
|
|
||||||
DXVKDWMHackMode.UnsetPopup => "Least intrusive option, try this first.\nWorks best with NVIDIA GPUs.",
|
|
||||||
DXVKDWMHackMode.SetClientEdgeResize => "Extends the game window 1 pixel to the bottom.\nDon't use if it makes text look blurry!",
|
|
||||||
DXVKDWMHackMode.SetClientEdgeBorder => "Adds a 1 pixel border around the game.",
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
|
|
||||||
public static bool IsUnsetPopup(this DXVKDWMHackMode mode) => mode == DXVKDWMHackMode.UnsetPopup;
|
|
||||||
|
|
||||||
public static bool IsSetClientEdge(this DXVKDWMHackMode mode) => mode switch
|
|
||||||
{
|
|
||||||
DXVKDWMHackMode.SetClientEdgeResize => true,
|
|
||||||
DXVKDWMHackMode.SetClientEdgeBorder => true,
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ModifierKey
|
|
||||||
{
|
|
||||||
NONE = 0,
|
|
||||||
CTRL = 1,
|
|
||||||
ALT = 2,
|
|
||||||
SHIFT = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class VirtualKeyExt
|
|
||||||
{
|
|
||||||
private static readonly Dictionary<VirtualKey, string> _strings = new();
|
|
||||||
|
|
||||||
static VirtualKeyExt()
|
|
||||||
{
|
|
||||||
foreach (var field in typeof(VirtualKey).GetFields(BindingFlags.Public | BindingFlags.Static))
|
|
||||||
{
|
{
|
||||||
var key = (VirtualKey) field.GetValue(null)!;
|
Service.PluginLog.Info("Migrating config V0 to V1");
|
||||||
_strings[key] = key.GetFancyName();
|
V1 = new();
|
||||||
|
// V1.Display.IsEnabled = IsEnabled; // Was enabled by default before, but let's disable.
|
||||||
|
V1.Display.IsScale = IsScale;
|
||||||
|
V1.Display.Scale = Scale;
|
||||||
|
V1.Display.Width = Width;
|
||||||
|
V1.Display.Height = Height;
|
||||||
|
V1.Display.HotkeyKey = HotkeyKey;
|
||||||
|
V1.Display.HotkeyModifier = HotkeyModifier;
|
||||||
|
V1.DXVKDWMHackMode = DXVKDWMHackMode;
|
||||||
|
V1.MinSizeMode = MinSizeMode;
|
||||||
|
V1.Display.Clamp();
|
||||||
|
Version++;
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CS0612
|
||||||
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToHumanNameString(this VirtualKey mode)
|
|
||||||
{
|
|
||||||
return _strings[mode];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string? ToHumanInfoString(this VirtualKey mode) => mode switch
|
|
||||||
{
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ModifierKeyExt
|
|
||||||
{
|
|
||||||
public static string ToHumanNameString(this ModifierKey mode) => mode switch
|
|
||||||
{
|
|
||||||
ModifierKey.NONE => "None",
|
|
||||||
ModifierKey.CTRL => "Ctrl",
|
|
||||||
ModifierKey.ALT => "Alt",
|
|
||||||
ModifierKey.SHIFT => "Shift",
|
|
||||||
_ => mode.ToString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static string? ToHumanInfoString(this ModifierKey mode) => mode switch
|
|
||||||
{
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum MinSizeMode
|
|
||||||
{
|
|
||||||
Unchanged = 0,
|
|
||||||
Unlocked = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MinSizeModeExt
|
|
||||||
{
|
|
||||||
public static string ToHumanNameString(this MinSizeMode mode) => mode switch
|
|
||||||
{
|
|
||||||
MinSizeMode.Unchanged => "Unchanged (1024x768)",
|
|
||||||
MinSizeMode.Unlocked => "Unlocked",
|
|
||||||
_ => mode.ToString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static string? ToHumanInfoString(this MinSizeMode mode) => mode switch
|
|
||||||
{
|
|
||||||
MinSizeMode.Unchanged => "The game normally doesn't allow resizing to smaller than 1024x768.",
|
|
||||||
MinSizeMode.Unlocked => "Allow resizing the game in windowed mode to smaller than 1024x768. Works best with scaling over 100%.",
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
147
CustomResolution2782/ConfigurationEnums.cs
Normal file
147
CustomResolution2782/ConfigurationEnums.cs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
using Dalamud.Configuration;
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace CustomResolution;
|
||||||
|
|
||||||
|
|
||||||
|
// Must explicitly map to integer values, blame Newtonsoft.JSON
|
||||||
|
public enum DXVKDWMHackMode
|
||||||
|
{
|
||||||
|
Off = 0,
|
||||||
|
UnsetPopup = 1,
|
||||||
|
SetClientEdgeResize = 3,
|
||||||
|
SetClientEdgeBorder = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DXVKDWMHackModeExt
|
||||||
|
{
|
||||||
|
public static string ToHumanNameString(this DXVKDWMHackMode mode) => mode switch
|
||||||
|
{
|
||||||
|
DXVKDWMHackMode.Off => "Off",
|
||||||
|
DXVKDWMHackMode.UnsetPopup => "Best: -WS_POPUP",
|
||||||
|
DXVKDWMHackMode.SetClientEdgeResize => "+WS_EX_CLIENTEDGE (resize)",
|
||||||
|
DXVKDWMHackMode.SetClientEdgeBorder => "+WS_EX_CLIENTEDGE (border)",
|
||||||
|
_ => mode.ToString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string? ToHumanInfoString(this DXVKDWMHackMode mode) => mode switch
|
||||||
|
{
|
||||||
|
DXVKDWMHackMode.UnsetPopup => "Least intrusive option, try this first.\nWorks best with NVIDIA GPUs.",
|
||||||
|
DXVKDWMHackMode.SetClientEdgeResize => "Extends the game window 1 pixel to the bottom.\nDon't use if it makes text look blurry!",
|
||||||
|
DXVKDWMHackMode.SetClientEdgeBorder => "Adds a 1 pixel border around the game.",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool IsUnsetPopup(this DXVKDWMHackMode mode) => mode == DXVKDWMHackMode.UnsetPopup;
|
||||||
|
|
||||||
|
public static bool IsSetClientEdge(this DXVKDWMHackMode mode) => mode switch
|
||||||
|
{
|
||||||
|
DXVKDWMHackMode.SetClientEdgeResize => true,
|
||||||
|
DXVKDWMHackMode.SetClientEdgeBorder => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ModifierKey
|
||||||
|
{
|
||||||
|
NONE = 0,
|
||||||
|
CTRL = 1,
|
||||||
|
ALT = 2,
|
||||||
|
SHIFT = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class VirtualKeyExt
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<VirtualKey, string> _strings = new();
|
||||||
|
|
||||||
|
static VirtualKeyExt()
|
||||||
|
{
|
||||||
|
foreach (var field in typeof(VirtualKey).GetFields(BindingFlags.Public | BindingFlags.Static))
|
||||||
|
{
|
||||||
|
var key = (VirtualKey) field.GetValue(null)!;
|
||||||
|
_strings[key] = key.GetFancyName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToHumanNameString(this VirtualKey mode)
|
||||||
|
{
|
||||||
|
return _strings[mode];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? ToHumanInfoString(this VirtualKey mode) => mode switch
|
||||||
|
{
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ModifierKeyExt
|
||||||
|
{
|
||||||
|
public static string ToHumanNameString(this ModifierKey mode) => mode switch
|
||||||
|
{
|
||||||
|
ModifierKey.NONE => "None",
|
||||||
|
ModifierKey.CTRL => "Ctrl",
|
||||||
|
ModifierKey.ALT => "Alt",
|
||||||
|
ModifierKey.SHIFT => "Shift",
|
||||||
|
_ => mode.ToString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string? ToHumanInfoString(this ModifierKey mode) => mode switch
|
||||||
|
{
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MinSizeMode
|
||||||
|
{
|
||||||
|
Unchanged = 0,
|
||||||
|
Unlocked = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MinSizeModeExt
|
||||||
|
{
|
||||||
|
public static string ToHumanNameString(this MinSizeMode mode) => mode switch
|
||||||
|
{
|
||||||
|
MinSizeMode.Unchanged => "Unchanged (1024x768)",
|
||||||
|
MinSizeMode.Unlocked => "Unlocked",
|
||||||
|
_ => mode.ToString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string? ToHumanInfoString(this MinSizeMode mode) => mode switch
|
||||||
|
{
|
||||||
|
MinSizeMode.Unchanged => "The game normally doesn't allow resizing to smaller than 1024x768.",
|
||||||
|
MinSizeMode.Unlocked => "Allow resizing the game in windowed mode to smaller than 1024x768. Works best with scaling over 100%.",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ResolutionScalingMode
|
||||||
|
{
|
||||||
|
Fast = 0,
|
||||||
|
FSR = 1,
|
||||||
|
DLSS = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ResolutionScalingModeExt
|
||||||
|
{
|
||||||
|
public static string ToHumanNameString(this ResolutionScalingMode mode) => mode switch
|
||||||
|
{
|
||||||
|
ResolutionScalingMode.Fast => "Fast Pixelation",
|
||||||
|
ResolutionScalingMode.FSR => "AMD FSR",
|
||||||
|
ResolutionScalingMode.DLSS => "NVIDIA DLSS",
|
||||||
|
_ => mode.ToString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string? ToHumanInfoString(this ResolutionScalingMode mode) => mode switch
|
||||||
|
{
|
||||||
|
ResolutionScalingMode.Fast => "Lowest quality option which results in blurry pixelation.",
|
||||||
|
ResolutionScalingMode.FSR => "Upscaling which works on any GPU for low performance overhead.",
|
||||||
|
ResolutionScalingMode.DLSS => "DLSS in FFXIV is buggy, even without any plugins.",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool IsUnsupported(this ResolutionScalingMode mode) => mode == ResolutionScalingMode.DLSS;
|
||||||
|
}
|
66
CustomResolution2782/ConfigurationV1.cs
Normal file
66
CustomResolution2782/ConfigurationV1.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using Dalamud.Configuration;
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CustomResolution;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct ConfigurationV1
|
||||||
|
{
|
||||||
|
public SizeConfig Display = new();
|
||||||
|
public SizeConfig Game = new();
|
||||||
|
|
||||||
|
public ResolutionScalingMode ResolutionScalingMode = ResolutionScalingMode.FSR;
|
||||||
|
public DXVKDWMHackMode DXVKDWMHackMode = DXVKDWMHackMode.Off;
|
||||||
|
public MinSizeMode MinSizeMode = MinSizeMode.Unchanged;
|
||||||
|
|
||||||
|
public ConfigurationV1()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct SizeConfig
|
||||||
|
{
|
||||||
|
public bool IsEnabled = false;
|
||||||
|
public bool IsScale = true;
|
||||||
|
public float Scale = 1f;
|
||||||
|
[NonSerialized]
|
||||||
|
public unsafe fixed int WH[2];
|
||||||
|
public unsafe uint Width
|
||||||
|
{
|
||||||
|
get => (uint) WH[0];
|
||||||
|
set => WH[0] = (int) value;
|
||||||
|
}
|
||||||
|
public unsafe uint Height
|
||||||
|
{
|
||||||
|
get => (uint) WH[1];
|
||||||
|
set => WH[1] = (int) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualKey HotkeyKey = VirtualKey.NO_KEY;
|
||||||
|
public ModifierKey HotkeyModifier = ModifierKey.NONE;
|
||||||
|
|
||||||
|
public SizeConfig()
|
||||||
|
{
|
||||||
|
Width = 1024;
|
||||||
|
Height = 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clamp()
|
||||||
|
{
|
||||||
|
if (Width < 256)
|
||||||
|
{
|
||||||
|
Width = 256;
|
||||||
|
}
|
||||||
|
if (Height < 256)
|
||||||
|
{
|
||||||
|
Height = 256;
|
||||||
|
}
|
||||||
|
if (Scale < 0.001f)
|
||||||
|
{
|
||||||
|
Scale = 0.001f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>0x0ade</Authors>
|
<Authors>0x0ade</Authors>
|
||||||
<Company></Company>
|
<Company></Company>
|
||||||
<Version>0.3.2.0</Version>
|
<Version>0.4.0.0</Version>
|
||||||
<Description></Description>
|
<Description></Description>
|
||||||
<Copyright></Copyright>
|
<Copyright></Copyright>
|
||||||
<PackageProjectUrl></PackageProjectUrl>
|
<PackageProjectUrl></PackageProjectUrl>
|
||||||
|
|
407
CustomResolution2782/DisplaySizeState.cs
Normal file
407
CustomResolution2782/DisplaySizeState.cs
Normal file
|
@ -0,0 +1,407 @@
|
||||||
|
using CustomResolution.Hooks;
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Game.Config;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
using ImGuiNET;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
|
namespace CustomResolution;
|
||||||
|
|
||||||
|
public unsafe class DisplaySizeState : IDisposable
|
||||||
|
{
|
||||||
|
private bool _wasEnabled = false;
|
||||||
|
private HWND _currentHwnd;
|
||||||
|
private bool _currentHwndMismatch = false;
|
||||||
|
private RECT _currentClientRect;
|
||||||
|
private DXVKDWMHackMode _currentDXVKDWMHackMode = DXVKDWMHackMode.Off;
|
||||||
|
private bool _currentlyFakeResize = false;
|
||||||
|
private bool _requestedResolutionChange = false;
|
||||||
|
|
||||||
|
public HWND CurrentHWND => _currentHwnd;
|
||||||
|
|
||||||
|
public uint CurrentWidth { get; private set; }
|
||||||
|
public uint CurrentHeight { get; private set; }
|
||||||
|
public uint CurrentWindowWidth { get; private set; }
|
||||||
|
public uint CurrentWindowHeight { get; private set; }
|
||||||
|
|
||||||
|
public bool CurrentBorderlessFullscreen { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Service.Framework.RunOnFrameworkThread(Update);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConvertCoordsWinToGame(ref int x, ref int y)
|
||||||
|
{
|
||||||
|
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scaleX = CurrentWidth / (float) CurrentWindowWidth;
|
||||||
|
var scaleY = CurrentHeight / (float) CurrentWindowHeight;
|
||||||
|
|
||||||
|
x = (int) Math.Round(x * scaleX);
|
||||||
|
y = (int) Math.Round(y * scaleY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConvertCoordsGameToWin(ref int x, ref int y)
|
||||||
|
{
|
||||||
|
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scaleX = CurrentWindowWidth / (float) CurrentWidth;
|
||||||
|
var scaleY = CurrentWindowHeight / (float) CurrentHeight;
|
||||||
|
|
||||||
|
x = (int) Math.Round(x * scaleX);
|
||||||
|
y = (int) Math.Round(y * scaleY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConvertCoordsGlobalToGame(ref int x, ref int y)
|
||||||
|
{
|
||||||
|
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scaleX = CurrentWidth / (float) CurrentWindowWidth;
|
||||||
|
var scaleY = CurrentHeight / (float) CurrentWindowHeight;
|
||||||
|
|
||||||
|
var p = new POINT(x, y);
|
||||||
|
|
||||||
|
Service.CursorPosHooks.ScreenToClientOrig(_currentHwnd, &p);
|
||||||
|
|
||||||
|
p.x = (int) Math.Round(p.x * scaleX);
|
||||||
|
p.y = (int) Math.Round(p.y * scaleY);
|
||||||
|
|
||||||
|
Service.CursorPosHooks.ClientToScreenOrig(_currentHwnd, &p);
|
||||||
|
|
||||||
|
x = p.x;
|
||||||
|
y = p.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConvertCoordsGameToGlobal(ref int x, ref int y)
|
||||||
|
{
|
||||||
|
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scaleX = CurrentWindowWidth / (float) CurrentWidth;
|
||||||
|
var scaleY = CurrentWindowHeight / (float) CurrentHeight;
|
||||||
|
|
||||||
|
var p = new POINT(x, y);
|
||||||
|
|
||||||
|
Service.CursorPosHooks.ScreenToClientOrig(_currentHwnd, &p);
|
||||||
|
|
||||||
|
p.x = (int) Math.Round(p.x * scaleX);
|
||||||
|
p.y = (int) Math.Round(p.y * scaleY);
|
||||||
|
|
||||||
|
Service.CursorPosHooks.ClientToScreenOrig(_currentHwnd, &p);
|
||||||
|
|
||||||
|
x = p.x;
|
||||||
|
y = p.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
bool unloading = Service.Plugin.Unloading;
|
||||||
|
|
||||||
|
ref var cfg = ref Service.Config._.Display;
|
||||||
|
|
||||||
|
var dev = (DeviceEx*) Device.Instance();
|
||||||
|
var framework = Framework.Instance();
|
||||||
|
var win = framework->GameWindow;
|
||||||
|
|
||||||
|
_currentHwnd = (HWND) win->WindowHandle;
|
||||||
|
|
||||||
|
// As a safety measure, don't mess with the structs if we're reading garbage.
|
||||||
|
// This isn't perfect, but it's better than nothing.
|
||||||
|
if (_currentHwnd != dev->hWnd)
|
||||||
|
{
|
||||||
|
if (!_currentHwndMismatch)
|
||||||
|
{
|
||||||
|
_currentHwndMismatch = true;
|
||||||
|
Service.PluginLog.Error($"HWND MISMATCH between GameWindow and Device: 0x{(long) (IntPtr) (_currentHwnd):X16} vs 0x{(long) dev->hWnd:X16}");
|
||||||
|
Service.PluginLog.Info($"dev is at: 0x{(long) (IntPtr) dev:X16}");
|
||||||
|
CheckHWND("GameWindow", _currentHwnd);
|
||||||
|
CheckHWND("Device", (HWND) dev->hWnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentHwndMismatch = false;
|
||||||
|
|
||||||
|
fixed (RECT* currentClientRectPtr = &_currentClientRect)
|
||||||
|
{
|
||||||
|
Service.WindowRectHooks.GetClientRectOrig(_currentHwnd, currentClientRectPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rectWidth = _currentClientRect.right - _currentClientRect.left;
|
||||||
|
var rectHeight = _currentClientRect.bottom - _currentClientRect.top;
|
||||||
|
|
||||||
|
if ((rectWidth <= 0 || rectHeight <= 0) && !unloading)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var enabled = !unloading && cfg.IsEnabled;
|
||||||
|
|
||||||
|
uint width, height;
|
||||||
|
|
||||||
|
if (cfg.IsScale || !enabled)
|
||||||
|
{
|
||||||
|
var scale = enabled ? cfg.Scale : 1f;
|
||||||
|
|
||||||
|
width = (uint) Math.Round(rectWidth * scale);
|
||||||
|
height = (uint) Math.Round(rectHeight * scale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
width = cfg.Width;
|
||||||
|
height = cfg.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width < 256)
|
||||||
|
{
|
||||||
|
width = 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height < 256)
|
||||||
|
{
|
||||||
|
height = 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestedResolutionChange |= dev->RequestResolutionChange != 0;
|
||||||
|
|
||||||
|
if (!_currentlyFakeResize && (unloading || enabled || _wasEnabled || _requestedResolutionChange) &&
|
||||||
|
(width != dev->Width || height != dev->Height))
|
||||||
|
{
|
||||||
|
Service.PluginLog.Info($"Changing graphics resolution from {dev->Width} x {dev->Height} to {width} x {height}");
|
||||||
|
var mode = Service.DebugConfig.ForceSizeMode;
|
||||||
|
if (_requestedResolutionChange)
|
||||||
|
{
|
||||||
|
// Let's try to be cautious about other plugins (f.e. SimpleTweaks) changing the game resolution.
|
||||||
|
Service.PluginLog.Info($"Game resolution was changed externally - forcing resize via device, not window resize.");
|
||||||
|
mode = ForceSizeMode.LegacyRequestResolutionChange;
|
||||||
|
_requestedResolutionChange = false;
|
||||||
|
}
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case ForceSizeMode.Skip:
|
||||||
|
break;
|
||||||
|
case ForceSizeMode.LegacyRequestResolutionChange:
|
||||||
|
dev->NewWidth = width;
|
||||||
|
dev->NewHeight = height;
|
||||||
|
dev->RequestResolutionChange = 1;
|
||||||
|
Service.PluginLog.Info($"Changing game window from {win->WindowWidth} x {win->WindowHeight} to {width} x {height}");
|
||||||
|
win->WindowWidth = (int) width;
|
||||||
|
win->WindowHeight = (int) height;
|
||||||
|
break;
|
||||||
|
case ForceSizeMode.FakeWindowResize:
|
||||||
|
var adjustedClientRect = new RECT(0, 0, rectWidth, rectHeight);
|
||||||
|
AdjustWindowRect(&adjustedClientRect, (uint) GetWindowLongPtr(_currentHwnd, GWL.GWL_STYLE), false);
|
||||||
|
var adjustedWidth = adjustedClientRect.right - adjustedClientRect.left;
|
||||||
|
var adjustedHeight = adjustedClientRect.bottom - adjustedClientRect.top;
|
||||||
|
Service.PluginLog.Info($"Resizing window to {adjustedWidth} x {adjustedHeight}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_currentlyFakeResize = true;
|
||||||
|
SendMessage(_currentHwnd, WM.WM_ENTERSIZEMOVE, 0, 0);
|
||||||
|
SendMessage(_currentHwnd, WM.WM_SIZE, SIZE_MAXSHOW, WndProcHook.SizeToParam((uint) adjustedWidth, (uint) adjustedHeight));
|
||||||
|
SendMessage(_currentHwnd, WM.WM_PAINT, 0, 0);
|
||||||
|
SendMessage(_currentHwnd, WM.WM_EXITSIZEMOVE, 0, 0);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_currentlyFakeResize = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Service.PluginLog.Error($"Unknown ForceSizeMode: {Service.DebugConfig.ForceSizeMode}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_wasEnabled != enabled)
|
||||||
|
{
|
||||||
|
Service.PluginLog.Info($"Changed state to: {enabled}");
|
||||||
|
_wasEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Service.PluginLog.Debug($"NewWidth 0x{(long) (IntPtr) (&dev->NewWidth):X16}");
|
||||||
|
//Service.PluginLog.Debug($"GameWindow->Width 0x{(long) (IntPtr) (&win->WindowWidth):X16}");
|
||||||
|
|
||||||
|
//Service.PluginLog.Info($"Game window at {win->WindowWidth} x {win->WindowHeight}");
|
||||||
|
|
||||||
|
//Service.PluginLog.Info($"AAA {Service.GameConfig.System.GetUInt(nameof(SystemConfigOption.UiHighScale))}");
|
||||||
|
|
||||||
|
CurrentBorderlessFullscreen = win->Borderless;
|
||||||
|
|
||||||
|
if (Service.Config._.DXVKDWMHackMode != DXVKDWMHackMode.Off && !unloading)
|
||||||
|
{
|
||||||
|
SetDXVKDWMHack(Service.Config._.DXVKDWMHackMode);
|
||||||
|
}
|
||||||
|
else if (Service.Config._.DXVKDWMHackMode == DXVKDWMHackMode.Off && _currentDXVKDWMHackMode != DXVKDWMHackMode.Off)
|
||||||
|
{
|
||||||
|
SetDXVKDWMHack(DXVKDWMHackMode.Off);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentWidth = width;
|
||||||
|
CurrentHeight = height;
|
||||||
|
CurrentWindowWidth = (uint) rectWidth;
|
||||||
|
CurrentWindowHeight = (uint) rectHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetDXVKDWMHack(DXVKDWMHackMode mode)
|
||||||
|
{
|
||||||
|
/* Default maximized style / exstyle is 0x95000000 / 0.
|
||||||
|
* WS.WS_POPUP | WS.WS_VISIBLE | WS.WS_CLIPSIBLINGS | WS.WS_MAXIMIZE
|
||||||
|
* Default windowed style / exstyle is 0x14CF0000 / 0.
|
||||||
|
* WS.WS_VISIBLE | WS.WS_CLIPSIBLINGS | WS.WS_CAPTION | WS.WS_SYSMENU | WS.WS_THICKFRAME | WS.WS_MINIMIZEBOX | WS.WS_MAXIMIZEBOX
|
||||||
|
*/
|
||||||
|
var styleOrig = (uint) GetWindowLong(_currentHwnd, GWL.GWL_STYLE);
|
||||||
|
var exstyleOrig = (uint) GetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE);
|
||||||
|
|
||||||
|
var style = styleOrig;
|
||||||
|
var exstyle = exstyleOrig;
|
||||||
|
|
||||||
|
var fullscreen = (style & WS.WS_SYSMENU) == 0;
|
||||||
|
|
||||||
|
if (Service.DebugConfig.IsDebug)
|
||||||
|
{
|
||||||
|
Service.PluginLog.Info("--------");
|
||||||
|
Service.PluginLog.Info($"STYLE: 0x{style:X8}");
|
||||||
|
Service.PluginLog.Info($"EXSTYLE: 0x{GetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE):X8}");
|
||||||
|
|
||||||
|
Span<ushort> name = stackalloc ushort[256];
|
||||||
|
GetClassName(_currentHwnd, name.GetPointer(0), name.Length);
|
||||||
|
WNDCLASSEXW wce;
|
||||||
|
GetClassInfoEx(GetModuleHandle(null), name.GetPointer(0), &wce);
|
||||||
|
|
||||||
|
Service.PluginLog.Info($"CLASS: {new string((char*) name.GetPointer(0))}");
|
||||||
|
Service.PluginLog.Info($"CLASS.style: 0x{wce.style:X8}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullscreen)
|
||||||
|
{
|
||||||
|
if (mode == DXVKDWMHackMode.UnsetPopup)
|
||||||
|
{
|
||||||
|
style &= ~WS.WS_POPUP;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
style |= WS.WS_POPUP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullscreen && mode.IsSetClientEdge())
|
||||||
|
{
|
||||||
|
exstyle |= WS.WS_EX_CLIENTEDGE;
|
||||||
|
exstyle |= WS.WS_EX_COMPOSITED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exstyle &= ~(uint) WS.WS_EX_CLIENTEDGE;
|
||||||
|
exstyle &= ~(uint) WS.WS_EX_COMPOSITED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Service.DebugConfig.IsDebug)
|
||||||
|
{
|
||||||
|
Service.PluginLog.Info($"NEWSTYLE: 0x{style:X8}");
|
||||||
|
Service.PluginLog.Info($"NEWEXSTYLE: 0x{exstyle:X8}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style != styleOrig || exstyle != exstyleOrig || _currentDXVKDWMHackMode != mode)
|
||||||
|
{
|
||||||
|
if (Service.DebugConfig.IsDebug)
|
||||||
|
{
|
||||||
|
Service.PluginLog.Info("UPDATE");
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWindowLong(_currentHwnd, GWL.GWL_STYLE, (int) style);
|
||||||
|
SetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE, (int) exstyle);
|
||||||
|
|
||||||
|
SetWindowPos(_currentHwnd, HWND.NULL, 0, 0, 0, 0, SWP.SWP_NOZORDER | SWP.SWP_NOMOVE | SWP.SWP_NOSIZE | SWP.SWP_NOACTIVATE | SWP.SWP_DRAWFRAME);
|
||||||
|
ShowWindow(_currentHwnd, SW.SW_SHOW);
|
||||||
|
}
|
||||||
|
else if (Service.DebugConfig.IsDebug)
|
||||||
|
{
|
||||||
|
Service.PluginLog.Info("SAME");
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentDXVKDWMHackMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckHWND(string from, HWND hwnd)
|
||||||
|
{
|
||||||
|
RECT rect = default;
|
||||||
|
if (!GetClientRect(hwnd, &rect))
|
||||||
|
{
|
||||||
|
Service.PluginLog.Info($"{from} is sus: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
private struct DeviceEx
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public Device _;
|
||||||
|
|
||||||
|
public ref byte RequestResolutionChange => ref _.RequestResolutionChange;
|
||||||
|
|
||||||
|
public ref uint Width => ref _.Width;
|
||||||
|
|
||||||
|
public ref uint Height => ref _.Height;
|
||||||
|
|
||||||
|
#if CRES_CLEAN || true
|
||||||
|
public ref void* hWnd => ref _.hWnd;
|
||||||
|
|
||||||
|
public ref uint NewWidth => ref _.NewWidth;
|
||||||
|
|
||||||
|
public ref uint NewHeight => ref _.NewHeight;
|
||||||
|
#else
|
||||||
|
// 7.2 adds 1B8 before hWnd (previously 820, now 9D8)
|
||||||
|
[FieldOffset(0x9D8)]
|
||||||
|
public unsafe void* hWnd;
|
||||||
|
|
||||||
|
[FieldOffset(0x9D8 + 0x10)]
|
||||||
|
public uint NewWidth;
|
||||||
|
|
||||||
|
[FieldOffset(0x9D8 + 0x14)]
|
||||||
|
public uint NewHeight;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ForceSizeMode
|
||||||
|
{
|
||||||
|
Skip = -1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fake a window resize event.
|
||||||
|
/// </summary>
|
||||||
|
FakeWindowResize = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Legacy mode, went through more initial testing, works well except for conflicts with other plugins.
|
||||||
|
/// Might revert or remove depending on how FakeWindowResize testing / fixups proceed.
|
||||||
|
///
|
||||||
|
/// Update the graphics device width / height and set RequestResolutionChange.
|
||||||
|
/// This is the same method that other plugins such as SimpleTweaks or XIVWindowResizer use.
|
||||||
|
/// </summary>
|
||||||
|
LegacyRequestResolutionChange = 1
|
||||||
|
}
|
149
CustomResolution2782/GameSizeState.cs
Normal file
149
CustomResolution2782/GameSizeState.cs
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
using CustomResolution.Hooks;
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Game.Config;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
using ImGuiNET;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
|
namespace CustomResolution;
|
||||||
|
|
||||||
|
public unsafe class GameSizeState : IDisposable
|
||||||
|
{
|
||||||
|
private bool _wasEnabled = false;
|
||||||
|
|
||||||
|
private bool? _forceDynRezoConfig;
|
||||||
|
|
||||||
|
public bool ConfigDynRezo { get; private set; }
|
||||||
|
public ResolutionScalingMode ConfigGraphicsRezoType { get; private set; }
|
||||||
|
public float ConfigGraphicsRezoScale { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Service.Framework.RunOnFrameworkThread(Update);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ForceDynRezoConfig(bool value)
|
||||||
|
{
|
||||||
|
_forceDynRezoConfig = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
ref var cfg = ref Service.Config._.Game;
|
||||||
|
|
||||||
|
if (_forceDynRezoConfig.HasValue)
|
||||||
|
{
|
||||||
|
Service.GameConfig.System.Set(SystemConfigOption.DynamicRezoType.ToString(), _forceDynRezoConfig.Value ? 1U : 0U);
|
||||||
|
_forceDynRezoConfig = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigDynRezo = Service.GameConfig.System.GetUInt(SystemConfigOption.DynamicRezoType.ToString()) != 0U;
|
||||||
|
// GameConfig starts with FSR at 0; GraphicsConfig starts with FSR at 1
|
||||||
|
ConfigGraphicsRezoType = (ResolutionScalingMode) (Service.GameConfig.System.GetUInt(SystemConfigOption.GraphicsRezoUpscaleType.ToString()) + 1);
|
||||||
|
ConfigGraphicsRezoScale = Service.GameConfig.System.GetUInt(SystemConfigOption.GraphicsRezoScale.ToString()) / 100f;
|
||||||
|
|
||||||
|
var dev = Device.Instance();
|
||||||
|
var gfx = GraphicsConfig.Instance();
|
||||||
|
var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
|
||||||
|
|
||||||
|
//Service.PluginLog.Debug($"GraphicsRezoScale 0x{(long) (IntPtr) (&gfx->GraphicsRezoScale):X16}");
|
||||||
|
//Service.PluginLog.Debug($"RTM 0x{(long) (IntPtr) rtm:X16}");
|
||||||
|
|
||||||
|
bool unloading = Service.Plugin.Unloading;
|
||||||
|
bool enabled = !unloading && cfg.IsEnabled;
|
||||||
|
|
||||||
|
if (enabled || _wasEnabled)
|
||||||
|
{
|
||||||
|
gfx->GraphicsRezoScale = enabled ? cfg.Scale : ConfigGraphicsRezoScale;
|
||||||
|
gfx->DynamicRezoEnable = (byte) (ConfigDynRezo || enabled ? 1 : 0);
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
gfx->GraphicsRezoUpscaleType = (byte) (cfg.Scale <= 1f ? Service.Config._.ResolutionScalingMode : ResolutionScalingMode.Fast);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gfx->GraphicsRezoUpscaleType = (byte) ConfigGraphicsRezoType;
|
||||||
|
}
|
||||||
|
_wasEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
rtm->DynamicResolutionMinimumHeight = rtm->DynamicResolutionMaximumHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
rtm->Resolution_Width = (ushort) (dev->Width * gfx->GraphicsRezoScale);
|
||||||
|
rtm->Resolution_Height = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||||
|
rtm->DynamicResolutionActualTargetHeight = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||||
|
rtm->DynamicResolutionTargetHeight = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||||
|
rtm->DynamicResolutionMaximumHeight = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||||
|
rtm->DynamicResolutionMinimumHeight = (ushort) (dev->Height * gfx->GraphicsRezoScale);
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if false
|
||||||
|
Service.PluginLog.Debug($"DR {gfx->DynamicRezoEnable}");
|
||||||
|
Service.PluginLog.Debug($"RTM {rtm->Resolution_Width} x {rtm->Resolution_Height}");
|
||||||
|
Service.PluginLog.Debug($"RTM H {rtm->DynamicResolutionActualTargetHeight} {rtm->DynamicResolutionTargetHeight} {rtm->DynamicResolutionMaximumHeight} {rtm->DynamicResolutionMinimumHeight}");
|
||||||
|
Service.PluginLog.Debug($"RTM S {rtm->GraphicsRezoScales[0]} {rtm->GraphicsRezoScales[1]} {rtm->GraphicsRezoScales[2]} {rtm->GraphicsRezoScales[3]} {rtm->GraphicsRezoScales[4]}");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderTargetManager is updated very sporadically, and some fields we need flew out
|
||||||
|
// https://github.com/aers/FFXIVClientStructs/commit/589df2aa5cd9c98b4d62269034cd6da903f49b5f#diff-8e7d9b03cb91cb07a8d7b463b5be4672793a328703bde393e7acd890822a72cf
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
private struct RenderTargetManagerEx
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public RenderTargetManager _;
|
||||||
|
|
||||||
|
#if CRES_CLEAN || false
|
||||||
|
public ref uint Resolution_Width => ref _.Resolution_Width;
|
||||||
|
public ref uint Resolution_Height => ref _.Resolution_Height;
|
||||||
|
public ref ushort DynamicResolutionActualTargetHeight => ref _.DynamicResolutionActualTargetHeight;
|
||||||
|
public ref ushort DynamicResolutionTargetHeight => ref _.DynamicResolutionTargetHeight;
|
||||||
|
public ref ushort DynamicResolutionMaximumHeight => ref _.DynamicResolutionMaximumHeight;
|
||||||
|
public ref ushort DynamicResolutionMinimumHeight => ref _.DynamicResolutionMinimumHeight;
|
||||||
|
public ref float GraphicsRezoScale => ref _.GraphicsRezoScale;
|
||||||
|
#else
|
||||||
|
[FieldOffset(0x428)]
|
||||||
|
public uint Resolution_Width;
|
||||||
|
[FieldOffset(0x428 + 0x4)]
|
||||||
|
public uint Resolution_Height;
|
||||||
|
|
||||||
|
[FieldOffset(0x6F0 + 2 * 0)]
|
||||||
|
public ushort DynamicResolutionActualTargetHeight;
|
||||||
|
[FieldOffset(0x6F0 + 2 * 1)]
|
||||||
|
public ushort DynamicResolutionTargetHeight;
|
||||||
|
[FieldOffset(0x6F0 + 2 * 2)]
|
||||||
|
public ushort DynamicResolutionMaximumHeight;
|
||||||
|
[FieldOffset(0x6F0 + 2 * 3)]
|
||||||
|
public ushort DynamicResolutionMinimumHeight;
|
||||||
|
|
||||||
|
public const int GraphicsRezoScalesCount = 5;
|
||||||
|
[FieldOffset(0x70C)]
|
||||||
|
public fixed float GraphicsRezoScales[GraphicsRezoScalesCount];
|
||||||
|
[FieldOffset(0x70C + 4 * 0)]
|
||||||
|
public float GraphicsRezoScalePrev;
|
||||||
|
[FieldOffset(0x70C + 4 * 1)]
|
||||||
|
public float GraphicsRezoScaleGlassWidth;
|
||||||
|
[FieldOffset(0x70C + 4 * 2)]
|
||||||
|
public float GraphicsRezoScaleGlassHeight;
|
||||||
|
[FieldOffset(0x70C + 4 * 3)]
|
||||||
|
public float GraphicsRezoScaleUnk1;
|
||||||
|
[FieldOffset(0x70C + 4 * 4)]
|
||||||
|
public float GraphicsRezoScaleUnk2;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,7 +80,7 @@ public sealed unsafe class CursorPosHooks : IDisposable
|
||||||
Service.PluginLog.Debug($"GetCursorPos A @ {lpPoint->x} {lpPoint -> y}");
|
Service.PluginLog.Debug($"GetCursorPos A @ {lpPoint->x} {lpPoint -> y}");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Service.Plugin.ConvertCoordsGlobalToGame(ref lpPoint->x, ref lpPoint->y);
|
Service.DisplaySize.ConvertCoordsGlobalToGame(ref lpPoint->x, ref lpPoint->y);
|
||||||
|
|
||||||
#if false
|
#if false
|
||||||
Service.PluginLog.Debug($"GetCursorPos B @ {lpPoint->x} {lpPoint->y}");
|
Service.PluginLog.Debug($"GetCursorPos B @ {lpPoint->x} {lpPoint->y}");
|
||||||
|
@ -95,7 +95,7 @@ public sealed unsafe class CursorPosHooks : IDisposable
|
||||||
Service.PluginLog.Debug($"SetCursorPos A @ {x} {y}");
|
Service.PluginLog.Debug($"SetCursorPos A @ {x} {y}");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Service.Plugin.ConvertCoordsGameToGlobal(ref x, ref y);
|
Service.DisplaySize.ConvertCoordsGameToGlobal(ref x, ref y);
|
||||||
|
|
||||||
#if false
|
#if false
|
||||||
Service.PluginLog.Debug($"SetCursorPos B @ {x} {y}");
|
Service.PluginLog.Debug($"SetCursorPos B @ {x} {y}");
|
||||||
|
|
|
@ -50,9 +50,9 @@ public sealed unsafe class WindowRectHooks : IDisposable
|
||||||
Service.PluginLog.Debug($"GetClientRectDetour A @ {lpRect->left} {lpRect->top} {lpRect->right} {lpRect->bottom}");
|
Service.PluginLog.Debug($"GetClientRectDetour A @ {lpRect->left} {lpRect->top} {lpRect->right} {lpRect->bottom}");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (hWnd == Service.Plugin.CurrentHWND)
|
if (hWnd == Service.DisplaySize.CurrentHWND)
|
||||||
{
|
{
|
||||||
Service.Plugin.ConvertCoordsWinToGame(ref lpRect->right, ref lpRect->bottom);
|
Service.DisplaySize.ConvertCoordsWinToGame(ref lpRect->right, ref lpRect->bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if false
|
#if false
|
||||||
|
@ -73,9 +73,9 @@ public sealed unsafe class WindowRectHooks : IDisposable
|
||||||
Service.PluginLog.Debug($"SetWindowPosDetour A @ {X} {Y} {cx} {cy}");
|
Service.PluginLog.Debug($"SetWindowPosDetour A @ {X} {Y} {cx} {cy}");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (Service.DebugConfig.SetWindowSizeMode == SetWindowSizeMode.LegacyHookSetWindowPos && hWnd == Service.Plugin.CurrentHWND)
|
if (Service.DebugConfig.SetWindowSizeMode == SetWindowSizeMode.LegacyHookSetWindowPos && hWnd == Service.DisplaySize.CurrentHWND)
|
||||||
{
|
{
|
||||||
Service.Plugin.ConvertCoordsGameToWin(ref cx, ref cy);
|
Service.DisplaySize.ConvertCoordsGameToWin(ref cx, ref cy);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if false
|
#if false
|
||||||
|
|
|
@ -112,7 +112,7 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Message == WM.WM_WINDOWPOSCHANGING &&
|
if (args.Message == WM.WM_WINDOWPOSCHANGING &&
|
||||||
Service.Config.MinSizeMode == MinSizeMode.Unlocked)
|
Service.Config._.MinSizeMode == MinSizeMode.Unlocked)
|
||||||
{
|
{
|
||||||
args.SuppressCall = true;
|
args.SuppressCall = true;
|
||||||
}
|
}
|
||||||
|
@ -127,8 +127,8 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
{
|
{
|
||||||
ParamToCoords(args.LParam, out int x, out int y);
|
ParamToCoords(args.LParam, out int x, out int y);
|
||||||
Service.PluginLog.Debug($"WM_SIZE {x} x {y}");
|
Service.PluginLog.Debug($"WM_SIZE {x} x {y}");
|
||||||
Service.Plugin.Update();
|
Service.DisplaySize.Update();
|
||||||
args.LParam = SizeToParam(Service.Plugin.CurrentWidth, Service.Plugin.CurrentHeight);
|
args.LParam = SizeToParam(Service.DisplaySize.CurrentWidth, Service.DisplaySize.CurrentHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WM.WM_MOUSEFIRST <= args.Message && args.Message <= WM.WM_MOUSELAST)
|
if (WM.WM_MOUSEFIRST <= args.Message && args.Message <= WM.WM_MOUSELAST)
|
||||||
|
@ -139,7 +139,7 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
Service.PluginLog.Debug($"WM_MOUSE A @ {x} {y}");
|
Service.PluginLog.Debug($"WM_MOUSE A @ {x} {y}");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
plugin.ConvertCoordsWinToGame(ref x, ref y);
|
Service.DisplaySize.ConvertCoordsWinToGame(ref x, ref y);
|
||||||
|
|
||||||
#if false
|
#if false
|
||||||
Service.PluginLog.Debug($"WM_MOUSE B @ {x} {y}");
|
Service.PluginLog.Debug($"WM_MOUSE B @ {x} {y}");
|
||||||
|
@ -148,8 +148,8 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
args.LParam = CoordsToParam(x, y);
|
args.LParam = CoordsToParam(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Message == WM.WM_NCCALCSIZE && args.WParam != 0 && plugin.CurrentBorderlessFullscreen &&
|
if (args.Message == WM.WM_NCCALCSIZE && args.WParam != 0 && Service.DisplaySize.CurrentBorderlessFullscreen &&
|
||||||
Service.Config.DXVKDWMHackMode.IsSetClientEdge())
|
Service.Config._.DXVKDWMHackMode.IsSetClientEdge())
|
||||||
{
|
{
|
||||||
var ncsize = (NCCALCSIZE_PARAMS*) args.LParam;
|
var ncsize = (NCCALCSIZE_PARAMS*) args.LParam;
|
||||||
MONITORINFO monitorInfo = new()
|
MONITORINFO monitorInfo = new()
|
||||||
|
@ -157,12 +157,12 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
cbSize = (uint) sizeof(MONITORINFO)
|
cbSize = (uint) sizeof(MONITORINFO)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (MonitorFromWindow(plugin.CurrentHWND, MONITOR.MONITOR_DEFAULTTONEAREST) is { } monitor && monitor != HMONITOR.NULL &&
|
if (MonitorFromWindow(Service.DisplaySize.CurrentHWND, MONITOR.MONITOR_DEFAULTTONEAREST) is { } monitor && monitor != HMONITOR.NULL &&
|
||||||
GetMonitorInfo(monitor, &monitorInfo))
|
GetMonitorInfo(monitor, &monitorInfo))
|
||||||
{
|
{
|
||||||
ncsize->rgrc[0] = monitorInfo.rcMonitor;
|
ncsize->rgrc[0] = monitorInfo.rcMonitor;
|
||||||
|
|
||||||
switch (Service.Config.DXVKDWMHackMode)
|
switch (Service.Config._.DXVKDWMHackMode)
|
||||||
{
|
{
|
||||||
case DXVKDWMHackMode.SetClientEdgeResize:
|
case DXVKDWMHackMode.SetClientEdgeResize:
|
||||||
ncsize->rgrc[0].bottom += 1;
|
ncsize->rgrc[0].bottom += 1;
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Dalamud.Game.Config;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
using FFXIVClientStructs.Interop;
|
using FFXIVClientStructs.Interop;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
@ -11,6 +12,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Xml.Linq;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
using static TerraFX.Interop.Windows.Windows;
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
|
@ -20,28 +22,18 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||||
{
|
{
|
||||||
private object _disposeLock = new();
|
private object _disposeLock = new();
|
||||||
|
|
||||||
private readonly HRGN _invisibleRgn;
|
|
||||||
|
|
||||||
private readonly List<Cmd> _cmds;
|
private readonly List<Cmd> _cmds;
|
||||||
private bool _unloading = false;
|
|
||||||
private bool _wasEnabled = false;
|
|
||||||
private HWND _currentHwnd;
|
|
||||||
private bool _currentHwndMismatch = false;
|
|
||||||
private RECT _currentClientRect;
|
|
||||||
private DXVKDWMHackMode _currentDXVKDWMHackMode = DXVKDWMHackMode.Off;
|
|
||||||
private bool _ignoreConfigChanges = false;
|
private bool _ignoreConfigChanges = false;
|
||||||
private bool _currentlyFakeResize = false;
|
|
||||||
private bool _requestedResolutionChange = false;
|
|
||||||
|
|
||||||
public Plugin(IDalamudPluginInterface pluginInterface)
|
public Plugin(IDalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
_invisibleRgn = CreateRectRgn(0, 0, -1, -1);
|
|
||||||
|
|
||||||
pluginInterface.Create<Service>();
|
pluginInterface.Create<Service>();
|
||||||
|
|
||||||
Service.Plugin = this;
|
Service.Plugin = this;
|
||||||
|
|
||||||
Service.DebugConfig = new DebugConfiguration();
|
Service.DebugConfig = new();
|
||||||
|
Service.DisplaySize = new();
|
||||||
|
Service.GameSize = new();
|
||||||
|
|
||||||
Service.Config = Service.PluginInterface.GetPluginConfig() as Configuration ?? new();
|
Service.Config = Service.PluginInterface.GetPluginConfig() as Configuration ?? new();
|
||||||
Service.Config.Initialize(Service.PluginInterface);
|
Service.Config.Initialize(Service.PluginInterface);
|
||||||
|
@ -68,25 +60,18 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||||
|
|
||||||
public string Name => "CustomResolution";
|
public string Name => "CustomResolution";
|
||||||
|
|
||||||
public HWND CurrentHWND => _currentHwnd;
|
public bool Unloading { get; private set; } = false;
|
||||||
|
|
||||||
public uint CurrentWidth { get; private set; }
|
|
||||||
public uint CurrentHeight { get; private set; }
|
|
||||||
public uint CurrentWindowWidth { get; private set; }
|
|
||||||
public uint CurrentWindowHeight { get; private set; }
|
|
||||||
|
|
||||||
public bool CurrentBorderlessFullscreen { get; private set; }
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_unloading = true;
|
Unloading = true;
|
||||||
|
|
||||||
lock (_disposeLock)
|
lock (_disposeLock)
|
||||||
{
|
{
|
||||||
Service.Framework.Update -= OnFrameworkUpdate;
|
Service.Framework.Update -= OnFrameworkUpdate;
|
||||||
Service.GameConfig.SystemChanged -= OnSystemConfigChanged;
|
Service.GameConfig.SystemChanged -= OnSystemConfigChanged;
|
||||||
|
Service.DisplaySize.Dispose();
|
||||||
Service.Framework.RunOnFrameworkThread(Update);
|
Service.GameSize.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Cmd cmd in _cmds)
|
foreach (Cmd cmd in _cmds)
|
||||||
|
@ -103,326 +88,23 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||||
Service.Plugin = null!;
|
Service.Plugin = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConvertCoordsWinToGame(ref int x, ref int y)
|
private void OnFrameworkUpdateForSize(ref ConfigurationV1.SizeConfig size)
|
||||||
{
|
{
|
||||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
var io = ImGui.GetIO();
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scaleX = CurrentWidth / (float) CurrentWindowWidth;
|
if (size.HotkeyKey != VirtualKey.NO_KEY &&
|
||||||
var scaleY = CurrentHeight / (float) CurrentWindowHeight;
|
(ImGuiNative.igIsKeyPressed((ImGuiKey) size.HotkeyKey, 0) != 0 || Service.KeyState.GetRawValue(size.HotkeyKey) != 0) &&
|
||||||
|
(size.HotkeyModifier switch
|
||||||
x = (int) Math.Round(x * scaleX);
|
|
||||||
y = (int) Math.Round(y * scaleY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConvertCoordsGameToWin(ref int x, ref int y)
|
|
||||||
{
|
|
||||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scaleX = CurrentWindowWidth / (float) CurrentWidth;
|
|
||||||
var scaleY = CurrentWindowHeight / (float) CurrentHeight;
|
|
||||||
|
|
||||||
x = (int) Math.Round(x * scaleX);
|
|
||||||
y = (int) Math.Round(y * scaleY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConvertCoordsGlobalToGame(ref int x, ref int y)
|
|
||||||
{
|
|
||||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scaleX = CurrentWidth / (float) CurrentWindowWidth;
|
|
||||||
var scaleY = CurrentHeight / (float) CurrentWindowHeight;
|
|
||||||
|
|
||||||
var p = new POINT(x, y);
|
|
||||||
|
|
||||||
Service.CursorPosHooks.ScreenToClientOrig(_currentHwnd, &p);
|
|
||||||
|
|
||||||
p.x = (int) Math.Round(p.x * scaleX);
|
|
||||||
p.y = (int) Math.Round(p.y * scaleY);
|
|
||||||
|
|
||||||
Service.CursorPosHooks.ClientToScreenOrig(_currentHwnd, &p);
|
|
||||||
|
|
||||||
x = p.x;
|
|
||||||
y = p.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConvertCoordsGameToGlobal(ref int x, ref int y)
|
|
||||||
{
|
|
||||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scaleX = CurrentWindowWidth / (float) CurrentWidth;
|
|
||||||
var scaleY = CurrentWindowHeight / (float) CurrentHeight;
|
|
||||||
|
|
||||||
var p = new POINT(x, y);
|
|
||||||
|
|
||||||
Service.CursorPosHooks.ScreenToClientOrig(_currentHwnd, &p);
|
|
||||||
|
|
||||||
p.x = (int) Math.Round(p.x * scaleX);
|
|
||||||
p.y = (int) Math.Round(p.y * scaleY);
|
|
||||||
|
|
||||||
Service.CursorPosHooks.ClientToScreenOrig(_currentHwnd, &p);
|
|
||||||
|
|
||||||
x = p.x;
|
|
||||||
y = p.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update()
|
|
||||||
{
|
|
||||||
var dev = Device.Instance();
|
|
||||||
var framework = Framework.Instance();
|
|
||||||
var win = framework->GameWindow;
|
|
||||||
|
|
||||||
_currentHwnd = (HWND) win->WindowHandle;
|
|
||||||
|
|
||||||
#if CRES_CLEAN || true
|
|
||||||
var _dev_hWnd = (IntPtr*) &dev->hWnd;
|
|
||||||
var _dev_NewWidth = &dev->NewWidth;
|
|
||||||
var _dev_NewHeight = &dev->NewHeight;
|
|
||||||
var _dev_RequestResolutionChange = &dev->RequestResolutionChange;
|
|
||||||
#else
|
|
||||||
// 7.2 adds 1B8 before hWnd (previously 820, now 9D8)
|
|
||||||
var _dev_hWnd = (nint*) ((IntPtr) dev + 0x9D8);
|
|
||||||
var _dev_NewWidth = (uint*) ((IntPtr) dev + 0x9D8 + 0x10);
|
|
||||||
var _dev_NewHeight = (uint*) ((IntPtr) dev + 0x9D8 + 0x14);
|
|
||||||
var _dev_RequestResolutionChange = &dev->RequestResolutionChange;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// As a safety measure, don't mess with the structs if we're reading garbage.
|
|
||||||
// This isn't perfect, but it's better than nothing.
|
|
||||||
if (_currentHwnd != *_dev_hWnd)
|
|
||||||
{
|
|
||||||
if (!_currentHwndMismatch)
|
|
||||||
{
|
{
|
||||||
_currentHwndMismatch = true;
|
ModifierKey.NONE => !io.KeyCtrl && !io.KeyAlt && !io.KeyShift,
|
||||||
Service.PluginLog.Error($"HWND MISMATCH between GameWindow and Device: 0x{(long) (IntPtr) (_currentHwnd):X16} vs 0x{(long) *_dev_hWnd:X16}");
|
ModifierKey.CTRL => io.KeyCtrl && !io.KeyAlt && !io.KeyShift,
|
||||||
Service.PluginLog.Info($"dev is at: 0x{(long) (IntPtr) dev:X16}");
|
ModifierKey.ALT => !io.KeyCtrl && io.KeyAlt && !io.KeyShift,
|
||||||
CheckHWND("GameWindow", _currentHwnd);
|
ModifierKey.SHIFT => !io.KeyCtrl && !io.KeyAlt && io.KeyShift,
|
||||||
CheckHWND("Device", (HWND) (*_dev_hWnd));
|
_ => false,
|
||||||
}
|
}))
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentHwndMismatch = false;
|
|
||||||
|
|
||||||
fixed (RECT* currentClientRectPtr = &_currentClientRect)
|
|
||||||
{
|
{
|
||||||
Service.WindowRectHooks.GetClientRectOrig(_currentHwnd, currentClientRectPtr);
|
size.IsEnabled = !size.IsEnabled;
|
||||||
}
|
Service.Config.Save();
|
||||||
|
|
||||||
var rectWidth = _currentClientRect.right - _currentClientRect.left;
|
|
||||||
var rectHeight = _currentClientRect.bottom - _currentClientRect.top;
|
|
||||||
|
|
||||||
if ((rectWidth <= 0 || rectHeight <= 0) && !_unloading)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var enabled = !_unloading && Service.Config.IsEnabled;
|
|
||||||
|
|
||||||
uint width, height;
|
|
||||||
|
|
||||||
if (Service.Config.IsScale || !enabled)
|
|
||||||
{
|
|
||||||
var scale = enabled ? Service.Config.Scale : 1f;
|
|
||||||
|
|
||||||
width = (uint) Math.Round(rectWidth * scale);
|
|
||||||
height = (uint) Math.Round(rectHeight * scale);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
width = Service.Config.Width;
|
|
||||||
height = Service.Config.Height;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width < 256)
|
|
||||||
{
|
|
||||||
width = 256;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (height < 256)
|
|
||||||
{
|
|
||||||
height = 256;
|
|
||||||
}
|
|
||||||
|
|
||||||
_requestedResolutionChange |= (*_dev_RequestResolutionChange) != 0;
|
|
||||||
|
|
||||||
if (!_currentlyFakeResize && (_unloading || enabled || _wasEnabled || _requestedResolutionChange) &&
|
|
||||||
(width != dev->Width || height != dev->Height))
|
|
||||||
{
|
|
||||||
Service.PluginLog.Info($"Changing graphics resolution from {dev->Width} x {dev->Height} to {width} x {height}");
|
|
||||||
var mode = Service.DebugConfig.ForceSizeMode;
|
|
||||||
if (_requestedResolutionChange)
|
|
||||||
{
|
|
||||||
// Let's try to be cautious about other plugins (f.e. SimpleTweaks) changing the game resolution.
|
|
||||||
Service.PluginLog.Info($"Game resolution was changed externally - forcing resize via device, not window resize.");
|
|
||||||
mode = ForceSizeMode.LegacyRequestResolutionChange;
|
|
||||||
_requestedResolutionChange = false;
|
|
||||||
}
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
case ForceSizeMode.Skip:
|
|
||||||
break;
|
|
||||||
case ForceSizeMode.LegacyRequestResolutionChange:
|
|
||||||
*_dev_NewWidth = width;
|
|
||||||
*_dev_NewHeight = height;
|
|
||||||
*_dev_RequestResolutionChange = 1;
|
|
||||||
Service.PluginLog.Info($"Changing game window from {win->WindowWidth} x {win->WindowHeight} to {width} x {height}");
|
|
||||||
win->WindowWidth = (int) width;
|
|
||||||
win->WindowHeight = (int) height;
|
|
||||||
break;
|
|
||||||
case ForceSizeMode.FakeWindowResize:
|
|
||||||
var adjustedClientRect = new RECT(0, 0, rectWidth, rectHeight);
|
|
||||||
AdjustWindowRect(&adjustedClientRect, (uint) GetWindowLongPtr(_currentHwnd, GWL.GWL_STYLE), false);
|
|
||||||
var adjustedWidth = adjustedClientRect.right - adjustedClientRect.left;
|
|
||||||
var adjustedHeight = adjustedClientRect.bottom - adjustedClientRect.top;
|
|
||||||
Service.PluginLog.Info($"Resizing window to {adjustedWidth} x {adjustedHeight}");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_currentlyFakeResize = true;
|
|
||||||
SendMessage(_currentHwnd, WM.WM_ENTERSIZEMOVE, 0, 0);
|
|
||||||
SendMessage(_currentHwnd, WM.WM_SIZE, SIZE_MAXSHOW, WndProcHook.SizeToParam((uint) adjustedWidth, (uint) adjustedHeight));
|
|
||||||
SendMessage(_currentHwnd, WM.WM_PAINT, 0, 0);
|
|
||||||
SendMessage(_currentHwnd, WM.WM_EXITSIZEMOVE, 0, 0);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_currentlyFakeResize = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Service.PluginLog.Error($"Unknown ForceSizeMode: {Service.DebugConfig.ForceSizeMode}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_wasEnabled != enabled)
|
|
||||||
{
|
|
||||||
Service.PluginLog.Info($"Changed state to: {enabled}");
|
|
||||||
_wasEnabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Service.PluginLog.Debug($"NewWidth 0x{(long) (IntPtr) (&dev->NewWidth):X16}");
|
|
||||||
//Service.PluginLog.Debug($"GameWindow->Width 0x{(long) (IntPtr) (&win->WindowWidth):X16}");
|
|
||||||
|
|
||||||
//Service.PluginLog.Info($"Game window at {win->WindowWidth} x {win->WindowHeight}");
|
|
||||||
|
|
||||||
//Service.PluginLog.Info($"AAA {Service.GameConfig.System.GetUInt(nameof(SystemConfigOption.UiHighScale))}");
|
|
||||||
|
|
||||||
CurrentBorderlessFullscreen = win->Borderless;
|
|
||||||
|
|
||||||
if (Service.Config.DXVKDWMHackMode != DXVKDWMHackMode.Off && !_unloading)
|
|
||||||
{
|
|
||||||
SetDXVKDWMHack(Service.Config.DXVKDWMHackMode);
|
|
||||||
}
|
|
||||||
else if (Service.Config.DXVKDWMHackMode == DXVKDWMHackMode.Off && _currentDXVKDWMHackMode != DXVKDWMHackMode.Off)
|
|
||||||
{
|
|
||||||
SetDXVKDWMHack(DXVKDWMHackMode.Off);
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentWidth = width;
|
|
||||||
CurrentHeight = height;
|
|
||||||
CurrentWindowWidth = (uint) rectWidth;
|
|
||||||
CurrentWindowHeight = (uint) rectHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetDXVKDWMHack(DXVKDWMHackMode mode)
|
|
||||||
{
|
|
||||||
/* Default maximized style / exstyle is 0x95000000 / 0.
|
|
||||||
* WS.WS_POPUP | WS.WS_VISIBLE | WS.WS_CLIPSIBLINGS | WS.WS_MAXIMIZE
|
|
||||||
* Default windowed style / exstyle is 0x14CF0000 / 0.
|
|
||||||
* WS.WS_VISIBLE | WS.WS_CLIPSIBLINGS | WS.WS_CAPTION | WS.WS_SYSMENU | WS.WS_THICKFRAME | WS.WS_MINIMIZEBOX | WS.WS_MAXIMIZEBOX
|
|
||||||
*/
|
|
||||||
var styleOrig = (uint) GetWindowLong(_currentHwnd, GWL.GWL_STYLE);
|
|
||||||
var exstyleOrig = (uint) GetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE);
|
|
||||||
|
|
||||||
var style = styleOrig;
|
|
||||||
var exstyle = exstyleOrig;
|
|
||||||
|
|
||||||
var fullscreen = (style & WS.WS_SYSMENU) == 0;
|
|
||||||
|
|
||||||
if (Service.DebugConfig.IsDebug)
|
|
||||||
{
|
|
||||||
Service.PluginLog.Info("--------");
|
|
||||||
Service.PluginLog.Info($"STYLE: 0x{style:X8}");
|
|
||||||
Service.PluginLog.Info($"EXSTYLE: 0x{GetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE):X8}");
|
|
||||||
|
|
||||||
Span<ushort> name = stackalloc ushort[256];
|
|
||||||
GetClassName(_currentHwnd, name.GetPointer(0), name.Length);
|
|
||||||
WNDCLASSEXW wce;
|
|
||||||
GetClassInfoEx(GetModuleHandle(null), name.GetPointer(0), &wce);
|
|
||||||
|
|
||||||
Service.PluginLog.Info($"CLASS: {new string((char*) name.GetPointer(0))}");
|
|
||||||
Service.PluginLog.Info($"CLASS.style: 0x{wce.style:X8}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fullscreen)
|
|
||||||
{
|
|
||||||
if (mode == DXVKDWMHackMode.UnsetPopup)
|
|
||||||
{
|
|
||||||
style &= ~WS.WS_POPUP;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
style |= WS.WS_POPUP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fullscreen && mode.IsSetClientEdge())
|
|
||||||
{
|
|
||||||
exstyle |= WS.WS_EX_CLIENTEDGE;
|
|
||||||
exstyle |= WS.WS_EX_COMPOSITED;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
exstyle &= ~(uint) WS.WS_EX_CLIENTEDGE;
|
|
||||||
exstyle &= ~(uint) WS.WS_EX_COMPOSITED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Service.DebugConfig.IsDebug)
|
|
||||||
{
|
|
||||||
Service.PluginLog.Info($"NEWSTYLE: 0x{style:X8}");
|
|
||||||
Service.PluginLog.Info($"NEWEXSTYLE: 0x{exstyle:X8}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style != styleOrig || exstyle != exstyleOrig || _currentDXVKDWMHackMode != mode)
|
|
||||||
{
|
|
||||||
if (Service.DebugConfig.IsDebug)
|
|
||||||
{
|
|
||||||
Service.PluginLog.Info("UPDATE");
|
|
||||||
}
|
|
||||||
|
|
||||||
SetWindowLong(_currentHwnd, GWL.GWL_STYLE, (int) style);
|
|
||||||
SetWindowLong(_currentHwnd, GWL.GWL_EXSTYLE, (int) exstyle);
|
|
||||||
|
|
||||||
SetWindowPos(_currentHwnd, HWND.NULL, 0, 0, 0, 0, SWP.SWP_NOZORDER | SWP.SWP_NOMOVE | SWP.SWP_NOSIZE | SWP.SWP_NOACTIVATE | SWP.SWP_DRAWFRAME);
|
|
||||||
ShowWindow(_currentHwnd, SW.SW_SHOW);
|
|
||||||
}
|
|
||||||
else if (Service.DebugConfig.IsDebug)
|
|
||||||
{
|
|
||||||
Service.PluginLog.Info("SAME");
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentDXVKDWMHackMode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckHWND(string from, HWND hwnd)
|
|
||||||
{
|
|
||||||
RECT rect = default;
|
|
||||||
if (!GetClientRect(hwnd, &rect))
|
|
||||||
{
|
|
||||||
Service.PluginLog.Info($"{from} is sus: {Marshal.GetLastPInvokeErrorMessage()}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,24 +112,11 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||||
{
|
{
|
||||||
lock (_disposeLock)
|
lock (_disposeLock)
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
OnFrameworkUpdateForSize(ref Service.Config._.Display);
|
||||||
|
OnFrameworkUpdateForSize(ref Service.Config._.Game);
|
||||||
|
|
||||||
if (Service.Config.HotkeyKey != VirtualKey.NO_KEY &&
|
Service.DisplaySize.Update();
|
||||||
(ImGuiNative.igIsKeyPressed((ImGuiKey) Service.Config.HotkeyKey, 0) != 0 || Service.KeyState.GetRawValue(Service.Config.HotkeyKey) != 0) &&
|
Service.GameSize.Update();
|
||||||
(Service.Config.HotkeyModifier switch
|
|
||||||
{
|
|
||||||
ModifierKey.NONE => !io.KeyCtrl && !io.KeyAlt && !io.KeyShift,
|
|
||||||
ModifierKey.CTRL => io.KeyCtrl && !io.KeyAlt && !io.KeyShift,
|
|
||||||
ModifierKey.ALT => !io.KeyCtrl && io.KeyAlt && !io.KeyShift,
|
|
||||||
ModifierKey.SHIFT => !io.KeyCtrl && !io.KeyAlt && io.KeyShift,
|
|
||||||
_ => false,
|
|
||||||
}))
|
|
||||||
{
|
|
||||||
Service.Config.IsEnabled = !Service.Config.IsEnabled;
|
|
||||||
Service.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
Update();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -469,11 +138,12 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||||
case SystemConfigOption.ScreenHeight:
|
case SystemConfigOption.ScreenHeight:
|
||||||
if (Service.DebugConfig.SetWindowSizeMode == SetWindowSizeMode.InterceptSystemConfig)
|
if (Service.DebugConfig.SetWindowSizeMode == SetWindowSizeMode.InterceptSystemConfig)
|
||||||
{
|
{
|
||||||
|
var d = Service.DisplaySize;
|
||||||
var name = e.ConfigOption.ToString();
|
var name = e.ConfigOption.ToString();
|
||||||
var valueOrig = Service.GameConfig.System.GetUInt(name);
|
var valueOrig = Service.GameConfig.System.GetUInt(name);
|
||||||
|
|
||||||
var isW = e.ConfigOption == SystemConfigOption.ScreenWidth;
|
var isW = e.ConfigOption == SystemConfigOption.ScreenWidth;
|
||||||
var scale = (isW ? CurrentWindowWidth : CurrentWindowHeight) / (float) (isW ? CurrentWidth : CurrentHeight);
|
var scale = (isW ? d.CurrentWindowWidth : d.CurrentWindowHeight) / (float) (isW ? d.CurrentWidth : d.CurrentHeight);
|
||||||
var valueNew = (uint) Math.Round(valueOrig * scale);
|
var valueNew = (uint) Math.Round(valueOrig * scale);
|
||||||
|
|
||||||
Service.PluginLog.Info($"Intercepting config value change for {name}: {valueOrig} -> {valueNew}");
|
Service.PluginLog.Info($"Intercepting config value change for {name}: {valueOrig} -> {valueNew}");
|
||||||
|
@ -492,22 +162,3 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ForceSizeMode
|
|
||||||
{
|
|
||||||
Skip = -1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fake a window resize event.
|
|
||||||
/// </summary>
|
|
||||||
FakeWindowResize = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Legacy mode, went through more initial testing, works well except for conflicts with other plugins.
|
|
||||||
/// Might revert or remove depending on how FakeWindowResize testing / fixups proceed.
|
|
||||||
///
|
|
||||||
/// Update the graphics device width / height and set RequestResolutionChange.
|
|
||||||
/// This is the same method that other plugins such as SimpleTweaks or XIVWindowResizer use.
|
|
||||||
/// </summary>
|
|
||||||
LegacyRequestResolutionChange = 1
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,6 +10,9 @@ public sealed class Service
|
||||||
{
|
{
|
||||||
public static Plugin Plugin { get; internal set; } = null!;
|
public static Plugin Plugin { get; internal set; } = null!;
|
||||||
|
|
||||||
|
public static DisplaySizeState DisplaySize { get; internal set; } = null!;
|
||||||
|
public static GameSizeState GameSize { get; internal set; } = null!;
|
||||||
|
|
||||||
public static Configuration Config { get; internal set; } = null!;
|
public static Configuration Config { get; internal set; } = null!;
|
||||||
|
|
||||||
public static DebugConfiguration DebugConfig { get; internal set; } = null!;
|
public static DebugConfiguration DebugConfig { get; internal set; } = null!;
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
namespace CustomResolution.Windows;
|
namespace CustomResolution.Windows;
|
||||||
|
|
||||||
|
@ -11,17 +14,10 @@ public class ConfigWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
private VirtualKey[] _validKeys = new VirtualKey[] { VirtualKey.NO_KEY }.Union(Service.KeyState.GetValidVirtualKeys()).ToArray();
|
private VirtualKey[] _validKeys = new VirtualKey[] { VirtualKey.NO_KEY }.Union(Service.KeyState.GetValidVirtualKeys()).ToArray();
|
||||||
|
|
||||||
private int[] _displayCurrentWH = new int[2];
|
private readonly int[] _displayCurrentWH = new int[2];
|
||||||
private int[] _displayCurrentWindowWH = new int[2];
|
private readonly int[] _displayCurrentWindowWH = new int[2];
|
||||||
|
|
||||||
private bool _configIsEnabled;
|
private ConfigurationV1 _;
|
||||||
private VirtualKey _configHotkeyKey;
|
|
||||||
private ModifierKey _configHotkeyModifier;
|
|
||||||
private bool _configIsScale;
|
|
||||||
private float _configScale;
|
|
||||||
private int[] _configWH = new int[2];
|
|
||||||
private DXVKDWMHackMode _configDXVKDWMHackMode;
|
|
||||||
private MinSizeMode _configMinSizeMode;
|
|
||||||
|
|
||||||
public ConfigWindow() : base(
|
public ConfigWindow() : base(
|
||||||
"CustomResolution",
|
"CustomResolution",
|
||||||
|
@ -36,65 +32,152 @@ public class ConfigWindow : Window, IDisposable
|
||||||
|
|
||||||
public void UpdateFromConfig()
|
public void UpdateFromConfig()
|
||||||
{
|
{
|
||||||
var config = Service.Config;
|
_ = Service.Config._;
|
||||||
|
|
||||||
_configIsEnabled = config.IsEnabled;
|
|
||||||
_configHotkeyKey = config.HotkeyKey;
|
|
||||||
_configHotkeyModifier = config.HotkeyModifier;
|
|
||||||
_configIsScale = config.IsScale;
|
|
||||||
_configScale = config.Scale;
|
|
||||||
_configWH[0] = (int) config.Width;
|
|
||||||
_configWH[1] = (int) config.Height;
|
|
||||||
_configDXVKDWMHackMode = config.DXVKDWMHackMode;
|
|
||||||
_configMinSizeMode = config.MinSizeMode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateToConfig()
|
public void UpdateToConfig()
|
||||||
{
|
{
|
||||||
var config = Service.Config;
|
Service.Config._ = _;
|
||||||
|
Service.Config.Save();
|
||||||
config.IsEnabled = _configIsEnabled;
|
|
||||||
config.HotkeyKey = _configHotkeyKey;
|
|
||||||
config.HotkeyModifier = _configHotkeyModifier;
|
|
||||||
config.IsScale = _configIsScale;
|
|
||||||
config.Scale = _configScale;
|
|
||||||
config.Width = (uint) _configWH[0];
|
|
||||||
config.Height = (uint) _configWH[1];
|
|
||||||
config.DXVKDWMHackMode = _configDXVKDWMHackMode;
|
|
||||||
config.MinSizeMode = _configMinSizeMode;
|
|
||||||
|
|
||||||
config.Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
var plugin = Service.Plugin;
|
|
||||||
var save = false;
|
var save = false;
|
||||||
|
|
||||||
_displayCurrentWH[0] = (int) plugin.CurrentWidth;
|
_displayCurrentWH[0] = (int) Service.DisplaySize.CurrentWidth;
|
||||||
_displayCurrentWH[1] = (int) plugin.CurrentHeight;
|
_displayCurrentWH[1] = (int) Service.DisplaySize.CurrentHeight;
|
||||||
_displayCurrentWindowWH[0] = (int) plugin.CurrentWindowWidth;
|
_displayCurrentWindowWH[0] = (int) Service.DisplaySize.CurrentWindowWidth;
|
||||||
_displayCurrentWindowWH[1] = (int) plugin.CurrentWindowHeight;
|
_displayCurrentWindowWH[1] = (int) Service.DisplaySize.CurrentWindowHeight;
|
||||||
|
|
||||||
ImGui.BeginDisabled();
|
ImGui.BeginDisabled();
|
||||||
ImGui.InputInt2("Current window size", ref _displayCurrentWindowWH[0]);
|
ImGui.InputInt2("Current window size", ref _displayCurrentWindowWH[0]);
|
||||||
ImGui.InputInt2("Current render size", ref _displayCurrentWH[0]);
|
ImGui.InputInt2("Current display size", ref _displayCurrentWH[0]);
|
||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
ImGui.Checkbox("Enabled", ref _configIsEnabled);
|
using (var imTabBar = ImRaii.TabBar("MalfunctionMainTabs"))
|
||||||
|
{
|
||||||
|
DrawDisplayTab();
|
||||||
|
|
||||||
|
DrawGameTab();
|
||||||
|
|
||||||
|
DrawCommonTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Save and apply"))
|
||||||
|
{
|
||||||
|
save = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save)
|
||||||
|
{
|
||||||
|
UpdateToConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDisplayTab()
|
||||||
|
{
|
||||||
|
using var imTab = ImRaii.TabItem($"Display##DisplayTab");
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(@"The display resolution affects everything,
|
||||||
|
including screenshots taken in-game and via ReShade.
|
||||||
|
|
||||||
|
This works best with the ""High Resolution UI Settings""
|
||||||
|
in the system configuration, as it also affects the UI.
|
||||||
|
|
||||||
|
In general: Use it as a fake fullscreen / window resolution,
|
||||||
|
best suited for screenshots.
|
||||||
|
Changed via /cres");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imTab.Success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawSizeTabContents(ref _.Display, "Display");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGameTab()
|
||||||
|
{
|
||||||
|
using var imTab = ImRaii.TabItem($"Gameplay##GameTab");
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(@"The gameplay resolution affects only the quality of your character
|
||||||
|
and everything around them, leaving the HUD untouched.
|
||||||
|
This also doesn't touch the screenshot resolution.
|
||||||
|
|
||||||
|
This works as an advanced version of the in-game ""3D Resolution Scaling"" graphics options,
|
||||||
|
allowing you to go smaller than 0.5x for more FPS, or higher than 1x for higher quality.
|
||||||
|
|
||||||
|
This overrides dynamic resolution!
|
||||||
|
|
||||||
|
In general: Use it for gameplay, not for screenshots.
|
||||||
|
Changed via /gres");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imTab.Success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if false
|
||||||
|
if (Service.GameSize.CurrentDynRezoConfig)
|
||||||
|
{
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Border, 0xff00ffff))
|
||||||
|
{
|
||||||
|
if (ImGui.Button(@"This might not work properly while in-game dynamic resolution is enabled!
|
||||||
|
Click here to disable it."))
|
||||||
|
{
|
||||||
|
Service.GameSize.ForceDynRezoConfig(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeight() * 0.1f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DrawSizeTabContents(ref _.Game, "Game", scaleOnly: true);
|
||||||
|
|
||||||
|
if (ImGui.BeginCombo("Graphics Upscaling", _.ResolutionScalingMode.ToHumanNameString()))
|
||||||
|
{
|
||||||
|
foreach (var mode in Enum.GetValues<ResolutionScalingMode>())
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(mode.ToHumanNameString(), _.ResolutionScalingMode == mode, mode.IsUnsupported() ? ImGuiSelectableFlags.Disabled : ImGuiSelectableFlags.None))
|
||||||
|
{
|
||||||
|
_.ResolutionScalingMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && mode.ToHumanInfoString() is { } info)
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void DrawSizeTabContents(ref ConfigurationV1.SizeConfig size, string name, bool scaleOnly = false)
|
||||||
|
{
|
||||||
|
ImGui.Checkbox("Enabled", ref size.IsEnabled);
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Dummy(new Vector2(20, 0));
|
ImGui.Dummy(new Vector2(20, 0));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth(80);
|
ImGui.SetNextItemWidth(80);
|
||||||
if (ImGui.BeginCombo("##_configHotkeyModifier", _configHotkeyModifier.ToHumanNameString()))
|
if (ImGui.BeginCombo($"##_{name}_configHotkeyModifier", size.HotkeyModifier.ToHumanNameString()))
|
||||||
{
|
{
|
||||||
foreach (var key in Enum.GetValues<ModifierKey>())
|
foreach (var key in Enum.GetValues<ModifierKey>())
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(key.ToHumanNameString(), _configHotkeyModifier == key, ImGuiSelectableFlags.None))
|
if (ImGui.Selectable(key.ToHumanNameString(), size.HotkeyModifier == key, ImGuiSelectableFlags.None))
|
||||||
{
|
{
|
||||||
_configHotkeyModifier = key;
|
size.HotkeyModifier = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemHovered() && key.ToHumanInfoString() is { } info)
|
if (ImGui.IsItemHovered() && key.ToHumanInfoString() is { } info)
|
||||||
|
@ -108,13 +191,13 @@ public class ConfigWindow : Window, IDisposable
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth(160);
|
ImGui.SetNextItemWidth(160);
|
||||||
if (ImGui.BeginCombo("##_configHotkeyKey", _configHotkeyKey.ToHumanNameString()))
|
if (ImGui.BeginCombo($"##_{name}_configHotkeyKey", size.HotkeyKey.ToHumanNameString()))
|
||||||
{
|
{
|
||||||
foreach (var key in _validKeys)
|
foreach (var key in _validKeys)
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(key.ToHumanNameString(), _configHotkeyKey == key, ImGuiSelectableFlags.None))
|
if (ImGui.Selectable(key.ToHumanNameString(), size.HotkeyKey == key, ImGuiSelectableFlags.None))
|
||||||
{
|
{
|
||||||
_configHotkeyKey = key;
|
size.HotkeyKey = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemHovered() && key.ToHumanInfoString() is { } info)
|
if (ImGui.IsItemHovered() && key.ToHumanInfoString() is { } info)
|
||||||
|
@ -130,34 +213,27 @@ public class ConfigWindow : Window, IDisposable
|
||||||
ImGui.Text("Hotkey");
|
ImGui.Text("Hotkey");
|
||||||
|
|
||||||
|
|
||||||
ImGui.Checkbox("Use scale", ref _configIsScale);
|
if (!scaleOnly)
|
||||||
|
|
||||||
if (_configIsScale)
|
|
||||||
{
|
{
|
||||||
ImGui.InputFloat("Scale", ref _configScale, 0.01f, 0.1f, "%.3f", ImGuiInputTextFlags.EnterReturnsTrue);
|
ImGui.Checkbox("Use scale", ref size.IsScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size.IsScale || scaleOnly)
|
||||||
|
{
|
||||||
|
ImGui.InputFloat("Scale", ref size.Scale, 0.01f, 0.1f, "%.3f", ImGuiInputTextFlags.EnterReturnsTrue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.InputInt2("Size in pixels", ref _configWH[0]);
|
unsafe
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginCombo("Borderless window workaround", _configDXVKDWMHackMode.ToHumanNameString()))
|
|
||||||
{
|
|
||||||
foreach (var mode in Enum.GetValues<DXVKDWMHackMode>())
|
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(mode.ToHumanNameString(), _configDXVKDWMHackMode == mode, ImGuiSelectableFlags.None))
|
ImGui.InputInt2("Size in pixels", ref size.WH[0]);
|
||||||
{
|
|
||||||
_configDXVKDWMHackMode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.IsItemHovered() && mode.ToHumanInfoString() is { } info)
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip(info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCommonTab()
|
||||||
|
{
|
||||||
|
using var imTab = ImRaii.TabItem($"Common##CommonTab");
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
|
@ -172,13 +248,18 @@ Works even with the scaling above disabled.
|
||||||
Not intended to be used with proper fullscreen.");
|
Not intended to be used with proper fullscreen.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginCombo("Minimum window size", _configMinSizeMode.ToHumanNameString()))
|
if (!imTab.Success)
|
||||||
{
|
{
|
||||||
foreach (var mode in Enum.GetValues<MinSizeMode>())
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginCombo("Borderless window workaround", _.DXVKDWMHackMode.ToHumanNameString()))
|
||||||
|
{
|
||||||
|
foreach (var mode in Enum.GetValues<DXVKDWMHackMode>())
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(mode.ToHumanNameString(), _configMinSizeMode == mode, ImGuiSelectableFlags.None))
|
if (ImGui.Selectable(mode.ToHumanNameString(), _.DXVKDWMHackMode == mode, ImGuiSelectableFlags.None))
|
||||||
{
|
{
|
||||||
_configMinSizeMode = mode;
|
_.DXVKDWMHackMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemHovered() && mode.ToHumanInfoString() is { } info)
|
if (ImGui.IsItemHovered() && mode.ToHumanInfoString() is { } info)
|
||||||
|
@ -190,14 +271,28 @@ Not intended to be used with proper fullscreen.");
|
||||||
ImGui.EndCombo();
|
ImGui.EndCombo();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Button("Save and apply"))
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
save = true;
|
ImGui.SetTooltip(@"Smaller tweaks and fixes, f.e. for DXVK on Windows,
|
||||||
|
or to allow making the window smaller than 1024x768.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (save)
|
if (ImGui.BeginCombo("Minimum window size", _.MinSizeMode.ToHumanNameString()))
|
||||||
{
|
{
|
||||||
UpdateToConfig();
|
foreach (var mode in Enum.GetValues<MinSizeMode>())
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(mode.ToHumanNameString(), _.MinSizeMode == mode, ImGuiSelectableFlags.None))
|
||||||
|
{
|
||||||
|
_.MinSizeMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered() && mode.ToHumanInfoString() is { } info)
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue