namespace XFESystem.Desktop;
/// <summary>Trusted EDID metadata for one exact framebuffer resolution, not a mode switch.</summary>
public readonly record struct DisplayTimingResult(
bool IsValid,
bool IsComplete,
string FailureReason,
int Width,
int Height,
int AdvertisedRefreshMilliHz,
int PhysicalWidthMillimeters,
int PhysicalHeightMillimeters,
string Source,
int MatchedTimingCount,
int DeclaredExtensionBlocks,
int ValidatedExtensionBlocks)
{
public bool HasAdvertisedTiming => AdvertisedRefreshMilliHz > 0;
}
/// <summary>
/// Bounded EDID 1.3/1.4, CTA detailed-timing and DisplayID 1.3 Type I decoding.
/// A monitor range limit is deliberately never treated as an advertised timing.
/// Valid base metadata survives missing or invalid extensions, which are reported
/// by IsComplete. Only independently validated extension blocks contribute modes.
/// </summary>
public static class DisplayTiming
{
public const int BlockLength = 128;
public const int MaximumExtensionBlocks = 8;
public static DisplayTimingResult Read(ReadOnlySpan<byte> edid, int nativeWidth, int nativeHeight)
{
if (nativeWidth <= 0 || nativeHeight <= 0 || nativeWidth > 65536 || nativeHeight > 65536)
return Invalid("Invalid native framebuffer dimensions", nativeWidth, nativeHeight);
if (edid.Length < BlockLength)
return Invalid("EDID base block is missing or truncated", nativeWidth, nativeHeight);
ReadOnlySpan<byte> baseBlock = edid[..BlockLength];
ReadOnlySpan<byte> signature = [0, 255, 255, 255, 255, 255, 255, 0];
if (!baseBlock[..8].SequenceEqual(signature))
return Invalid("Invalid EDID signature", nativeWidth, nativeHeight);
if (!ChecksumIsValid(baseBlock))
return Invalid("Invalid EDID base checksum", nativeWidth, nativeHeight);
if (baseBlock[18] != 1 || baseBlock[19] is < 3 or > 4)
return Invalid("Unsupported EDID base version", nativeWidth, nativeHeight);
var timings = new TimingAccumulator(nativeWidth, nativeHeight);
if (baseBlock[21] != 0 && baseBlock[22] != 0)
{
timings.PhysicalWidth = baseBlock[21] * 10;
timings.PhysicalHeight = baseBlock[22] * 10;
}
for (int offset = 54; offset + 18 <= 126; offset += 18)
ReadDetailedTiming(baseBlock.Slice(offset, 18), "EDID base DTD", ref timings);
int declared = baseBlock[126];
int available = Math.Max(0, edid.Length / BlockLength - 1);
int bounded = Math.Min(declared, MaximumExtensionBlocks);
int validated = 0;
bool complete = declared <= MaximumExtensionBlocks && available >= declared;
string reason = declared > MaximumExtensionBlocks ? "EDID extension count exceeds parser bound" :
available < declared ? "One or more declared EDID extensions are missing" : "";
for (int index = 1; index <= bounded && index <= available; index++)
{
ReadOnlySpan<byte> extension = edid.Slice(index * BlockLength, BlockLength);
if (!ChecksumIsValid(extension))
{
complete = false;
reason = "One or more EDID extension checksums are invalid";
continue;
}
bool valid = extension[0] switch
{
0x02 => CtaIsValid(extension),
0x70 => DisplayIdIsValid(extension),
_ => true // Unknown tags contribute no timings; their block checksum is still checked.
};
if (!valid)
{
complete = false;
reason = "One or more EDID extensions have unsupported versions or malformed lengths";
continue;
}
validated++;
if (extension[0] == 0x02)
{
int offset = extension[2];
if (offset != 0)
for (; offset + 18 <= 127; offset += 18)
ReadDetailedTiming(extension.Slice(offset, 18), $"EDID CTA DTD (extension {index})", ref timings);
}
else if (extension[0] == 0x70)
ReadDisplayId(extension, index, ref timings);
}
return new(true, complete, reason, nativeWidth, nativeHeight, timings.MaximumMilliHz,
timings.PhysicalWidth, timings.PhysicalHeight,
timings.MaximumMilliHz > 0 ? timings.Source : "No advertised timing for the exact framebuffer resolution",
timings.Matches, declared, validated);
}
private static DisplayTimingResult Invalid(string reason, int width, int height) =>
new(false, false, reason, width, height, 0, 0, 0, "Unavailable", 0, 0, 0);
private static bool ChecksumIsValid(ReadOnlySpan<byte> bytes)
{
int sum = 0;
foreach (byte value in bytes) sum += value;
return (sum & 255) == 0;
}
private static bool CtaIsValid(ReadOnlySpan<byte> block)
{
if (block[1] is < 1 or > 3) return false;
int end = block[2];
if (end == 0) return true;
if (end < 4 || end > 127) return false;
for (int offset = 4; offset < end;)
{
int length = block[offset] & 31;
if (offset + 1 + length > end) return false;
offset += 1 + length;
}
return true;
}
private static bool DisplayIdIsValid(ReadOnlySpan<byte> block)
{
if (block[1] != 0x13) return false;
int payloadEnd = 5 + block[2];
if (payloadEnd > 126 || !ChecksumIsValid(block.Slice(1, payloadEnd))) return false;
for (int offset = 5; offset < payloadEnd;)
{
if (AllZero(block.Slice(offset, payloadEnd - offset))) break;
if (offset + 3 > payloadEnd) return false;
int length = block[offset + 2];
if (length == 0 || offset + 3 + length > payloadEnd) return false;
if (block[offset] == 0x03 &&
((block[offset + 1] & 0xF8) != 0 || (block[offset + 1] & 7) > 1 || length % 20 != 0))
return false;
offset += 3 + length;
}
return true;
}
private static bool AllZero(ReadOnlySpan<byte> bytes)
{
foreach (byte value in bytes) if (value != 0) return false;
return true;
}
private static void ReadDetailedTiming(ReadOnlySpan<byte> timing, string source, ref TimingAccumulator result)
{
int clockUnits = timing[0] | timing[1] << 8;
// Descriptor metadata (including 0xFD range limits) has a zero pixel clock.
// Interlaced timings are excluded: the desktop framebuffer is progressive.
if (clockUnits == 0 || (timing[17] & 0x80) != 0) return;
int width = timing[2] | (timing[4] & 0xF0) << 4;
int horizontalBlank = timing[3] | (timing[4] & 0x0F) << 8;
int height = timing[5] | (timing[7] & 0xF0) << 4;
int verticalBlank = timing[6] | (timing[7] & 0x0F) << 8;
int physicalWidth = timing[12] | (timing[14] & 0xF0) << 4;
int physicalHeight = timing[13] | (timing[14] & 0x0F) << 8;
result.Add(width, height, horizontalBlank, verticalBlank, clockUnits * 10000L,
source, physicalWidth, physicalHeight);
}
private static void ReadDisplayId(ReadOnlySpan<byte> block, int extensionIndex, ref TimingAccumulator result)
{
int payloadEnd = 5 + block[2];
for (int offset = 5; offset + 3 <= payloadEnd;)
{
int length = block[offset + 2];
if (length == 0) break;
if (block[offset] == 0x03)
{
for (int position = offset + 3; position < offset + 3 + length; position += 20)
{
ReadOnlySpan<byte> timing = block.Slice(position, 20);
if ((timing[3] & 0x10) != 0) continue; // Interlaced.
// DisplayID Type I fields encode values minus one, including pixel clock.
// QEMU v11.1.0 writes the direct clock count, introducing <1mHz quantization
// difference at 4K240; decoding follows the protocol for physical monitors.
long clockUnits = (timing[0] | timing[1] << 8 | timing[2] << 16) + 1L;
result.Add(LittleEndian16(timing, 4) + 1, LittleEndian16(timing, 12) + 1,
LittleEndian16(timing, 6) + 1, LittleEndian16(timing, 14) + 1,
clockUnits * 10000, $"EDID DisplayID 1.3 Type I (extension {extensionIndex})", 0, 0);
}
}
offset += 3 + length;
}
}
private static int LittleEndian16(ReadOnlySpan<byte> bytes, int offset) => bytes[offset] | bytes[offset + 1] << 8;
private struct TimingAccumulator(int requestedWidth, int requestedHeight)
{
public int MaximumMilliHz;
public int PhysicalWidth;
public int PhysicalHeight;
public int Matches;
public string Source = "";
public void Add(int width, int height, int horizontalBlank, int verticalBlank, long pixelClockHz,
string source, int physicalWidth, int physicalHeight)
{
if (width != requestedWidth || height != requestedHeight || horizontalBlank <= 0 || verticalBlank <= 0)
return;
long totalPixels = (width + (long)horizontalBlank) * (height + (long)verticalBlank);
long milliHz = (pixelClockHz * 1000 + totalPixels / 2) / totalPixels;
if (milliHz <= 0 || milliHz > int.MaxValue) return;
Matches++;
if (milliHz > MaximumMilliHz)
{
MaximumMilliHz = (int)milliHz;
Source = source;
}
if (physicalWidth > 0 && physicalHeight > 0)
{
PhysicalWidth = physicalWidth;
PhysicalHeight = physicalHeight;
}
}
}
}
namespace XFESystem.Desktop;
/// <summary>Trusted EDID metadata for one exact framebuffer resolution, not a mode switch.</summary>
public readonly record struct DisplayTimingResult(
bool IsValid,
bool IsComplete,
string FailureReason,
int Width,
int Height,
int AdvertisedRefreshMilliHz,
int PhysicalWidthMillimeters,
int PhysicalHeightMillimeters,
string Source,
int MatchedTimingCount,
int DeclaredExtensionBlocks,
int ValidatedExtensionBlocks)
{
public bool HasAdvertisedTiming => AdvertisedRefreshMilliHz > 0;
}
/// <summary>
/// Bounded EDID 1.3/1.4, CTA detailed-timing and DisplayID 1.3 Type I decoding.
/// A monitor range limit is deliberately never treated as an advertised timing.
/// Valid base metadata survives missing or invalid extensions, which are reported
/// by IsComplete. Only independently validated extension blocks contribute modes.
/// </summary>
public static class DisplayTiming
{
public const int BlockLength = 128;
public const int MaximumExtensionBlocks = 8;
public static DisplayTimingResult Read(ReadOnlySpan<byte> edid, int nativeWidth, int nativeHeight)
{
if (nativeWidth <= 0 || nativeHeight <= 0 || nativeWidth > 65536 || nativeHeight > 65536)
return Invalid("Invalid native framebuffer dimensions", nativeWidth, nativeHeight);
if (edid.Length < BlockLength)
return Invalid("EDID base block is missing or truncated", nativeWidth, nativeHeight);
ReadOnlySpan<byte> baseBlock = edid[..BlockLength];
ReadOnlySpan<byte> signature = [0, 255, 255, 255, 255, 255, 255, 0];
if (!baseBlock[..8].SequenceEqual(signature))
return Invalid("Invalid EDID signature", nativeWidth, nativeHeight);
if (!ChecksumIsValid(baseBlock))
return Invalid("Invalid EDID base checksum", nativeWidth, nativeHeight);
if (baseBlock[18] != 1 || baseBlock[19] is < 3 or > 4)
return Invalid("Unsupported EDID base version", nativeWidth, nativeHeight);
var timings = new TimingAccumulator(nativeWidth, nativeHeight);
if (baseBlock[21] != 0 && baseBlock[22] != 0)
{
timings.PhysicalWidth = baseBlock[21] * 10;
timings.PhysicalHeight = baseBlock[22] * 10;
}
for (int offset = 54; offset + 18 <= 126; offset += 18)
ReadDetailedTiming(baseBlock.Slice(offset, 18), "EDID base DTD", ref timings);
int declared = baseBlock[126];
int available = Math.Max(0, edid.Length / BlockLength - 1);
int bounded = Math.Min(declared, MaximumExtensionBlocks);
int validated = 0;
bool complete = declared <= MaximumExtensionBlocks && available >= declared;
string reason = declared > MaximumExtensionBlocks ? "EDID extension count exceeds parser bound" :
available < declared ? "One or more declared EDID extensions are missing" : "";
for (int index = 1; index <= bounded && index <= available; index++)
{
ReadOnlySpan<byte> extension = edid.Slice(index * BlockLength, BlockLength);
if (!ChecksumIsValid(extension))
{
complete = false;
reason = "One or more EDID extension checksums are invalid";
continue;
}
bool valid = extension[0] switch
{
0x02 => CtaIsValid(extension),
0x70 => DisplayIdIsValid(extension),
_ => true // Unknown tags contribute no timings; their block checksum is still checked.
};
if (!valid)
{
complete = false;
reason = "One or more EDID extensions have unsupported versions or malformed lengths";
continue;
}
validated++;
if (extension[0] == 0x02)
{
int offset = extension[2];
if (offset != 0)
for (; offset + 18 <= 127; offset += 18)
ReadDetailedTiming(extension.Slice(offset, 18), $"EDID CTA DTD (extension {index})", ref timings);
}
else if (extension[0] == 0x70)
ReadDisplayId(extension, index, ref timings);
}
return new(true, complete, reason, nativeWidth, nativeHeight, timings.MaximumMilliHz,
timings.PhysicalWidth, timings.PhysicalHeight,
timings.MaximumMilliHz > 0 ? timings.Source : "No advertised timing for the exact framebuffer resolution",
timings.Matches, declared, validated);
}
private static DisplayTimingResult Invalid(string reason, int width, int height) =>
new(false, false, reason, width, height, 0, 0, 0, "Unavailable", 0, 0, 0);
private static bool ChecksumIsValid(ReadOnlySpan<byte> bytes)
{
int sum = 0;
foreach (byte value in bytes) sum += value;
return (sum & 255) == 0;
}
private static bool CtaIsValid(ReadOnlySpan<byte> block)
{
if (block[1] is < 1 or > 3) return false;
int end = block[2];
if (end == 0) return true;
if (end < 4 || end > 127) return false;
for (int offset = 4; offset < end;)
{
int length = block[offset] & 31;
if (offset + 1 + length > end) return false;
offset += 1 + length;
}
return true;
}
private static bool DisplayIdIsValid(ReadOnlySpan<byte> block)
{
if (block[1] != 0x13) return false;
int payloadEnd = 5 + block[2];
if (payloadEnd > 126 || !ChecksumIsValid(block.Slice(1, payloadEnd))) return false;
for (int offset = 5; offset < payloadEnd;)
{
if (AllZero(block.Slice(offset, payloadEnd - offset))) break;
if (offset + 3 > payloadEnd) return false;
int length = block[offset + 2];
if (length == 0 || offset + 3 + length > payloadEnd) return false;
if (block[offset] == 0x03 &&
((block[offset + 1] & 0xF8) != 0 || (block[offset + 1] & 7) > 1 || length % 20 != 0))
return false;
offset += 3 + length;
}
return true;
}
private static bool AllZero(ReadOnlySpan<byte> bytes)
{
foreach (byte value in bytes) if (value != 0) return false;
return true;
}
private static void ReadDetailedTiming(ReadOnlySpan<byte> timing, string source, ref TimingAccumulator result)
{
int clockUnits = timing[0] | timing[1] << 8;
// Descriptor metadata (including 0xFD range limits) has a zero pixel clock.
// Interlaced timings are excluded: the desktop framebuffer is progressive.
if (clockUnits == 0 || (timing[17] & 0x80) != 0) return;
int width = timing[2] | (timing[4] & 0xF0) << 4;
int horizontalBlank = timing[3] | (timing[4] & 0x0F) << 8;
int height = timing[5] | (timing[7] & 0xF0) << 4;
int verticalBlank = timing[6] | (timing[7] & 0x0F) << 8;
int physicalWidth = timing[12] | (timing[14] & 0xF0) << 4;
int physicalHeight = timing[13] | (timing[14] & 0x0F) << 8;
result.Add(width, height, horizontalBlank, verticalBlank, clockUnits * 10000L,
source, physicalWidth, physicalHeight);
}
private static void ReadDisplayId(ReadOnlySpan<byte> block, int extensionIndex, ref TimingAccumulator result)
{
int payloadEnd = 5 + block[2];
for (int offset = 5; offset + 3 <= payloadEnd;)
{
int length = block[offset + 2];
if (length == 0) break;
if (block[offset] == 0x03)
{
for (int position = offset + 3; position < offset + 3 + length; position += 20)
{
ReadOnlySpan<byte> timing = block.Slice(position, 20);
if ((timing[3] & 0x10) != 0) continue; // Interlaced.
// DisplayID Type I fields encode values minus one, including pixel clock.
// QEMU v11.1.0 writes the direct clock count, introducing <1mHz quantization
// difference at 4K240; decoding follows the protocol for physical monitors.
long clockUnits = (timing[0] | timing[1] << 8 | timing[2] << 16) + 1L;
result.Add(LittleEndian16(timing, 4) + 1, LittleEndian16(timing, 12) + 1,
LittleEndian16(timing, 6) + 1, LittleEndian16(timing, 14) + 1,
clockUnits * 10000, $"EDID DisplayID 1.3 Type I (extension {extensionIndex})", 0, 0);
}
}
offset += 3 + length;
}
}
private static int LittleEndian16(ReadOnlySpan<byte> bytes, int offset) => bytes[offset] | bytes[offset + 1] << 8;
private struct TimingAccumulator(int requestedWidth, int requestedHeight)
{
public int MaximumMilliHz;
public int PhysicalWidth;
public int PhysicalHeight;
public int Matches;
public string Source = "";
public void Add(int width, int height, int horizontalBlank, int verticalBlank, long pixelClockHz,
string source, int physicalWidth, int physicalHeight)
{
if (width != requestedWidth || height != requestedHeight || horizontalBlank <= 0 || verticalBlank <= 0)
return;
long totalPixels = (width + (long)horizontalBlank) * (height + (long)verticalBlank);
long milliHz = (pixelClockHz * 1000 + totalPixels / 2) / totalPixels;
if (milliHz <= 0 || milliHz > int.MaxValue) return;
Matches++;
if (milliHz > MaximumMilliHz)
{
MaximumMilliHz = (int)milliHz;
Source = source;
}
if (physicalWidth > 0 && physicalHeight > 0)
{
PhysicalWidth = physicalWidth;
PhysicalHeight = physicalHeight;
}
}
}
}