XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
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;
            }
        }
    }
}