diff --git a/PowerControl/Controller.cs b/PowerControl/Controller.cs
index ab42857..3446703 100644
--- a/PowerControl/Controller.cs
+++ b/PowerControl/Controller.cs
@@ -69,6 +69,34 @@ namespace PowerControl
rootMenu.VisibleChanged += delegate { updateOSD(); };
contextMenu.Items.Add(new ToolStripSeparator());
+ if (Settings.Default.EnableExperimentalFeatures)
+ {
+ var installEDIDItem = contextMenu.Items.Add("Install &Resolutions");
+ installEDIDItem.Click += delegate { Helpers.AMD.EDID.SetEDID(Resources.CRU_SteamDeck); };
+ var replaceEDIDItem = contextMenu.Items.Add("Replace &Resolutions");
+ replaceEDIDItem.Click += delegate { Helpers.AMD.EDID.SetEDID(new byte[0]); Helpers.AMD.EDID.SetEDID(Resources.CRU_SteamDeck); };
+ var uninstallEDIDItem = contextMenu.Items.Add("Revert &Resolutions");
+ uninstallEDIDItem.Click += delegate { Helpers.AMD.EDID.SetEDID(new byte[0]); };
+ contextMenu.Opening += delegate
+ {
+ if (ExternalHelpers.DisplayConfig.IsInternalConnected == true)
+ {
+ var edid = Helpers.AMD.EDID.GetEDID() ?? new byte[0];
+ var edidInstalled = Resources.CRU_SteamDeck.SequenceEqual(edid);
+ installEDIDItem.Visible = edid.Length <= 128;
+ replaceEDIDItem.Visible = !edidInstalled && edid.Length > 128;
+ uninstallEDIDItem.Visible = edid.Length > 128;
+ }
+ else
+ {
+ installEDIDItem.Visible = false;
+ replaceEDIDItem.Visible = false;
+ uninstallEDIDItem.Visible = false;
+ }
+ };
+ contextMenu.Items.Add(new ToolStripSeparator());
+ }
+
if (startupManager.IsAvailable)
{
var startupItem = new ToolStripMenuItem("Run On Startup");
diff --git a/PowerControl/Helpers/AMD/ADL.cs b/PowerControl/Helpers/AMD/ADL.cs
index 8f2b8a0..38f5db5 100644
--- a/PowerControl/Helpers/AMD/ADL.cs
+++ b/PowerControl/Helpers/AMD/ADL.cs
@@ -143,6 +143,30 @@ namespace PowerControl.Helpers.AMD
}
#endregion ADLDisplayInfo
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ADLDisplayEDIDData
+ {
+ internal int iSize;
+ internal int iFlag;
+ internal int iEDIDSize;
+ internal int iBlockIndex;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)ADL.ADL_MAX_EDIDDATA_SIZE)]
+ internal byte[] cEDIDData;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ internal int[] iReserved;
+ };
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ADLDisplayEDIDDataX2
+ {
+ internal int iSize;
+ internal int iFlag;
+ internal int iEDIDSize;
+ internal int iIgnored;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)2 * ADL.ADL_MAX_EDIDDATA_SIZE)]
+ internal byte[] cEDIDData;
+ };
+
#region Radeon Image Sharpening
[StructLayout(LayoutKind.Sequential)]
internal struct ADL_RIS_SETTINGS
@@ -192,6 +216,8 @@ namespace PowerControl.Helpers.AMD
internal const int ADL_MAX_GLSYNC_PORT_LEDS = 8;
/// Maximum number of ADLMOdes for the adapter
internal const int ADL_MAX_NUM_DISPLAYMODES = 1024;
+ internal const int ADL_MAX_EDIDDATA_SIZE = 256;
+ internal const int ADL_MAX_EDID_EXTENSION_BLOCKS = 3;
internal const int ADL_DISPLAY_DISPLAYINFO_DISPLAYCONNECTED = 0x00000001;
@@ -265,6 +291,12 @@ namespace PowerControl.Helpers.AMD
[DllImport(Atiadlxx_FileName)]
internal static extern int ADL2_RIS_Settings_Set(IntPtr context, int adapterIndex, ADL_RIS_SETTINGS settings, ADL_RIS_NOTFICATION_REASON reason);
+
+ [DllImport(Atiadlxx_FileName)]
+ internal static extern int ADL2_Display_EdidData_Get(IntPtr context, int adapterIndex, int displayIndex, ref ADLDisplayEDIDData edidData);
+
+ [DllImport(Atiadlxx_FileName)]
+ internal static extern int ADL2_Display_EdidData_Set(IntPtr context, int adapterIndex, int displayIndex, ref ADLDisplayEDIDDataX2 edidData);
#endregion DLLImport
#region ADL_Main_Memory_Alloc
diff --git a/PowerControl/Helpers/AMD/EDID.cs b/PowerControl/Helpers/AMD/EDID.cs
new file mode 100644
index 0000000..095684a
--- /dev/null
+++ b/PowerControl/Helpers/AMD/EDID.cs
@@ -0,0 +1,57 @@
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace PowerControl.Helpers.AMD
+{
+ internal class EDID
+ {
+ internal static byte[]? GetEDID(int displayIndex = ADL.ADL_DEFAULT_DISPLAY)
+ {
+ return ADLContext.WithSafe((context) =>
+ {
+ byte[] edid = new byte[0];
+
+ for (int block = 0; block < ADL.ADL_MAX_EDID_EXTENSION_BLOCKS; ++block)
+ {
+ ADLDisplayEDIDData displayEdidData = new ADLDisplayEDIDData()
+ {
+ iSize = Marshal.SizeOf(),
+ iBlockIndex = block,
+ };
+
+ int res = ADL.ADL2_Display_EdidData_Get(context.Context, ADL.ADL_DEFAULT_ADAPTER, ADL.ADL_DEFAULT_DISPLAY, ref displayEdidData);
+ if (res != 0)
+ break;
+
+ var blockBytes = displayEdidData.cEDIDData.Take(displayEdidData.iEDIDSize);
+ edid = edid.Concat(blockBytes).ToArray();
+ }
+
+ return edid;
+ });
+ }
+
+ internal static bool? SetEDID(byte[] value, int displayIndex = ADL.ADL_DEFAULT_DISPLAY)
+ {
+ return ADLContext.WithSafe((context) =>
+ {
+ var bytes = new byte[ADL.ADL_MAX_EDIDDATA_SIZE * 2];
+ value.CopyTo(bytes, 0);
+
+ var blockData = new ADLDisplayEDIDDataX2()
+ {
+ // TODO: Hack to send a full EDID at once
+ iSize = Marshal.SizeOf(),
+ cEDIDData = bytes,
+ iEDIDSize = value.Length,
+ };
+
+ int res = ADL.ADL2_Display_EdidData_Set(context.Context,
+ ADL.ADL_DEFAULT_ADAPTER, ADL.ADL_DEFAULT_DISPLAY,
+ ref blockData);
+
+ return res == 0;
+ });
+ }
+ }
+}
diff --git a/PowerControl/Resources.Designer.cs b/PowerControl/Resources.Designer.cs
index 48a2d72..0648e80 100644
--- a/PowerControl/Resources.Designer.cs
+++ b/PowerControl/Resources.Designer.cs
@@ -60,6 +60,16 @@ namespace PowerControl {
}
}
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] CRU_SteamDeck {
+ get {
+ object obj = ResourceManager.GetObject("CRU_SteamDeck", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
///
diff --git a/PowerControl/Resources.resx b/PowerControl/Resources.resx
index 716a078..fe2cdb2 100644
--- a/PowerControl/Resources.resx
+++ b/PowerControl/Resources.resx
@@ -118,6 +118,9 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ Resources\CRU_SteamDeck.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
Resources\traffic-light-outline_light.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
diff --git a/PowerControl/Resources/CRU_SteamDeck.bin b/PowerControl/Resources/CRU_SteamDeck.bin
new file mode 100644
index 0000000..575bc65
Binary files /dev/null and b/PowerControl/Resources/CRU_SteamDeck.bin differ
diff --git a/RELEASE.md b/RELEASE.md
index 0c03f04..d1815e9 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -9,6 +9,7 @@
## 0.6.x
+- PowerControl: Install custom resolutions (EDID) (experimental feature)
- SteamController: Add `X360: No Touchpads` profile
- All: Show `Missing RTSS` button to install RTSS
- PowerControl: Retain FPS Limit (proportion) on refresh rate change