using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.IO;
using MTK;
using System.Diagnostics;
using WinISP.BaseClass;
using System.Management;

namespace WinISP
{
    public partial class FormISP_MSI : Form
    {
        public int ErrorCode = 0;
        string _DirExeStarUp = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + "\\";
        bool IsConnect = false;
#if AUTO_SEARCH
        int StressTest_Count = 1;
#endif
        int Transfer_Time = 0;
        ulong Total_Transfer_Time = 0;
        ulong Total_UpdateFW_Time = 0;
        GraphicCardI2CCtrl gci2cCtrl = null;
        public FormISP_MSI()
        {
            InitializeComponent();
        }

        private String _binFilePath = String.Empty;
        private String _eddFilePath = String.Empty;
        private string _u8ModelName = String.Empty;
        private string _u8PanelName = String.Empty;
        private string _panelTypeName = String.Empty;
         private string _u8FWVersion = String.Empty;
        private string _chipName = String.Empty;
        private string _u8ChipName = String.Empty;
        private string _u8BoardName = String.Empty;
        private string _monitorModelName = String.Empty;
        private string _monitorChipName = String.Empty;
        private string _monitorPanelName = String.Empty;
        private string _monitorBoardName = String.Empty;
        private string _monitorVersionFromDDC = String.Empty;
        //private string _fwModelName = String.Empty;
        //private string _fwChipName = String.Empty;
        //private string _fwPanelName = String.Empty;
        //private string _fwBoardName = String.Empty;
        private bool _IsRunISP = false;
        ISimI2CCtrl _simI2cCtrl;
        private bool _IsHDMIConnect = false;
        const int _flashSectorSize = 0x1000;
        private Byte _chipType = 0;
        private Byte _panelType = 0;
        private Byte _clientType = 0;
        private const int WM_DEVICECHANGE = 0x219;
        private const int DBT_DEVNODES_CHANGED = 0x07;
        private const byte MStarCommand = 0xCC;
        private const byte CmdGetModelName = 0x36;
        private const byte CmdGetChipName = 0x41;
        private const byte CmdGetPanelName = 0x42;
        private const byte CmdGetBoardName = 0x45;
        private const int MonitorModelNameLength = 20;
        private const int MonitorChipNameLength = 20;
        private const int MonitorPanelNameLength = 30;
        private const int MonitorBoardNameLength = 20;
        private const string PlaceholderMonitorModelName = "MST9U6_PanelCMIM236HGJ_L21";
        private const string LegacyMonitorModelName = "MAG 321UPD E14";
        private const byte MS_GET_MODEL_NAME = 0x36;
        private const byte MS_GET_CHIP_NAME = 0x41;
        private const byte MS_GET_PANEL_NAME = 0x42;
        private const byte MS_GET_BOARD_NAME = 0x45;
        private const byte MS_GET_FW_VERSION  = 0x47;
        private string _fwVersionFromDDC = String.Empty;
        private bool _suppressSendFileFailMessage = false;
        object _lockObj = new object();
        int _numberOfMonitors;
        int _lastNumOfMonitors = 0;
        int defaultPktLen = 59;

        // Internal replacements for UI controls that exist in frmISP but not in FormISP_MSI.
        // They preserve the original logic flow without requiring extra widgets on the MSI layout.
        private int _selectedMonitorIndex = 0;
        private string _pktDelayTimeText = "0";

        public void UpdateNumofConnectedMonitor()
        {
            lock (_lockObj)
            {
                string strQuery = "SELECT * FROM Win32_PnPEntity WHERE Service = \"monitor\"";
                ManagementObjectSearcher searcher = new ManagementObjectSearcher("\\root\\CIMV2", strQuery);
                _numberOfMonitors = searcher.Get().Count;
            }
        }

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            switch (m.Msg)
            {
                case WM_DEVICECHANGE:
                    switch ((int)m.WParam)
                    {
                        case DBT_DEVNODES_CHANGED:
                            BackgroundWorker bw = new BackgroundWorker();
                            bw.DoWork += (s, args) =>
                            {
                                this.Invoke(new MethodInvoker(() => UpdateNumofConnectedMonitor()));
                                Application.DoEvents();
                            };
                            bw.RunWorkerAsync();
                            break;
                    }
                    break;
            }

        }

        public void CheckSingleInstance()
        {
            Process[] thisnameprocesslist;
            string modulename, processname;
            Process p = Process.GetCurrentProcess();
            modulename = p.MainModule.ModuleName.ToString();
            processname = System.IO.Path.GetFileNameWithoutExtension(modulename);
            thisnameprocesslist = Process.GetProcessesByName(processname);
            if (thisnameprocesslist.Length > 1)
            {
                Application.Exit();
            }
        }

#if DEBUG
        string debugversion = "[Debug Version]";
#else
        string debugversion = "";
#endif
        private bool _isStartupInitializing = false;
        private void FormISP_MSI_Load(object sender, EventArgs e)
        {
            BeginStartupLoadingAnimation();
            BeginInvoke(new Action(InitializeAfterFormLoaded));
        }

        private void InitializeAfterFormLoaded()
        {
            if (_isStartupInitializing)
            {
                return;
            }

            _isStartupInitializing = true;
            try
            {
                UpdateNumofConnectedMonitor();
                // lblDisplayNumber.Text = _numberOfMonitors.ToString();

                CenterToScreen();
                if (_simI2cCtrl == null)
                    _simI2cCtrl = new GraphicCardI2CCtrl();
                MTKDebugCmd.InitI2CCtrl(_simI2cCtrl);

                CheckSingleInstance();

                SetStatusMsg("Ready");

                string betaString = "";
                System.Diagnostics.FileVersionInfo fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location);
                this.Text += " [Program Version] : " + fvi.ProductVersion + " " + betaString /*+ " (FW ver : " + ver + ") "*/ + debugversion;
                frmISP.WriteLog("Program version: " + fvi.ProductVersion, "debug");
#if DEBUG
                {
                    // this.label21.Visible = true;
                    // this.lblPktDelayTime.Visible = true;
                    // this.txtPktDelayTime.Visible = true;
                    // this.label20.Visible = true;
                    // this.lblMTKChipType.Visible = true;
                }
#endif
                TopMost = true;
                btnAuto.Enabled = false;
                Connect();
                _lastNumOfMonitors = _numberOfMonitors;
                Application.Idle += Application_Idle;
            }
            finally
            {
                EndStartupLoadingAnimation();
                _isStartupInitializing = false;
            }
        }

        private void FormISP_MSI_Shown(object sender, EventArgs e)
        {
        }

        private void FormISP_MSI_FormClosed(object sender, FormClosedEventArgs e)
        {
        }

        bool _IsAutoRunISP = true;

        private void Application_Idle(object sender, EventArgs e)
        {
            if (_numberOfMonitors != _lastNumOfMonitors)
                _lastNumOfMonitors = _numberOfMonitors;
            //_cf = AccessIni.ReadFwConfig();
#if AUTO_SEARCH
            //search bin file from current folder
            FileInfo fi = SearchFile("*.bin");
            if (fi != null)
            {
                _binFilePath = fi.FullName;
                tsslFilePath.Text = fi.Name;
            }
            else
            {
                frmISP.WriteLog("No bin file in current folder.", "debug");
                Application.Idle -= Application_Idle;
                this.Close();
            }
            Application.Idle -= Application_Idle;
            btnAuto_Click(this, EventArgs.Empty);
#else
#endif
        }

        private FileInfo SearchFile(String extName)
        {
            string currentDirName = Directory.GetCurrentDirectory();
            string[] files = Directory.GetFiles(currentDirName, extName);
            foreach (string s in files)
            {
                FileInfo fi = null;
                try
                {
                    fi = new FileInfo(s);
                    frmISP.WriteLog("FileInfo=" + fi.Name, "debug");
                    return fi;
                }
                catch (FileNotFoundException e)
                {
                    continue;
                }
            }

            return null;
        }

        private int GetTickCount()
        {
            return Environment.TickCount & Int32.MaxValue;
        }

        private void BeginStartupLoadingAnimation()
        {
            prbProgress.Style = ProgressBarStyle.Marquee;
            prbProgress.MarqueeAnimationSpeed = 30;
            SetStatusMsg("程序加载中，请稍候...");
            btnAuto.Enabled = false;
            btnLoadFile.Enabled = false;
        }

        private void EndStartupLoadingAnimation()
        {
            prbProgress.MarqueeAnimationSpeed = 0;
            prbProgress.Style = ProgressBarStyle.Blocks;
            prbProgress.Value = 0;
            btnLoadFile.Enabled = true;
        }

        private void OnUpdateProgress(int total, int done, String msg)
        {
            if (msg != String.Empty)
            {
                SetStatusMsg(msg);
            }

            if (prbProgress.Maximum != total)
            {
                prbProgress.Maximum = (int)total;
            }

            if (prbProgress.Value != done)
            {
                prbProgress.Value = done;
            }
            Application.DoEvents();
        }

        private void SetISPStatus(bool isRunning, string err = "")
        {
            if (!string.IsNullOrEmpty(err))
            {
                MessageBox.Show(err, "Tip", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            _IsRunISP = isRunning;
            btnAuto.Enabled = !isRunning;
            btnLoadFile.Enabled = !isRunning;
            tsslIspStatus.Text = isRunning ? "ISP ON" : "ISP OFF";
        }

        private void SetStatusMsg(String msg, bool display = false)
        {
            lblStatus.Text = msg;
            Application.DoEvents();

            if (display) MessageBox.Show(msg, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        /// <summary>
        /// Send a 2-byte DDC command and parse the ASCII string returned by the monitor firmware.
        /// recv layout: [hdr0][hdr1][char0][char1]...[\0]
        /// </summary>
        private string DDC_GetMonitorName(byte subCmd, int delayTime = 1000)
        {
            Byte[] cmd = { 0xCC, subCmd };
            int[] readLens = { 36, 32, 24, 20, 16, 12, 8 };
            string bestPartial = string.Empty;
            bool segReadTried = false;  // 分段续读只尝试一次

            // N卡驱动已知的单次有效数据上限（超过此长度才信任 isComplete 判断）
            // 低于或等于此值时，即使找到\0也可能是驱动填充，不能轻易认为完整
            const int NV_SUSPECT_TRUNCATE_LEN = 15;

            foreach (int readLen in readLens)
            {
                for (int attempt = 0; attempt < 3; attempt++)
                {
                    Byte[] recv = MTKDebugCmd.DDC_Read(cmd, readLen, delayTime);
                    if (recv == null || recv.Length < 3)
                    {
                        WinIOLib.DelayMs(20);
                        continue;
                    }

                    // Error response: 6E 80 BE ...
                    if (recv[1] == 0x80)
                    {
                        WinIOLib.DelayMs(20);
                        continue;
                    }

                    // ── DDC XOR 校验和验证 ──────────────────────────────
                    // DDC/CI 协议: XOR(recv[0]..recv[checksumPos]) 必须 = 0x00。
                    // AMD ADL 偶尔返回损坏的响应头（reportedDataLen 被放大），
                    // 导致 recv[1] 和后续字节与实际数据不匹配，校验和必定失败。
                    // 校验成功 = 数据完整可信；校验失败 = 丢弃此轮读取。
                    int reportedDataLen = recv[1] & 0x7F;
                    int checksumPos = 2 + reportedDataLen;
                    bool checksumOk = false;
                    if (recv.Length > checksumPos)
                    {
                        byte xor = 0;
                        for (int i = 0; i <= checksumPos; i++)
                            xor ^= recv[i];
                        checksumOk = (xor == 0);
                    }
                    // 如果 recv 太短不包含校验和（如 N卡截断），仍继续使用数据
                    if (!checksumOk && recv.Length > checksumPos)
                    {
                        frmISP.WriteLog(
                            $"[DDC] cmd=0x{subCmd:X2} checksum FAIL, reportedDataLen={reportedDataLen}, " +
                            $"recvLen={recv.Length} — corrupted response discarded",
                            "debug");
                        WinIOLib.DelayMs(20);
                        continue;
                    }
                    // ── End 校验和验证 ──────────────────────────────────

                    // 先按原始方式扫描整个buffer找\0（用于完整性判断）
                    // 然后截断到 reportedDataLen，排除末尾DDC校验和字节
                    int rawLen = 0;
                    while ((2 + rawLen) < recv.Length && recv[2 + rawLen] != 0)
                        rawLen++;

                    int len = Math.Min(rawLen, reportedDataLen);

                    if (len <= 0)
                    {
                        WinIOLib.DelayMs(20);
                        continue;
                    }

                    string value = System.Text.Encoding.ASCII.GetString(recv, 2, len).Trim();
                    value = TrimNonStandardDdcSuffix(value);
                    if (string.IsNullOrWhiteSpace(value))
                    {
                        WinIOLib.DelayMs(20);
                        continue;
                    }

                    // nullTerminatorFound: 基于原始扫描长度判断（rawLen 可能超过 reportedDataLen，
                    // 因为扫过了 checksum 字节才在 padding 区遇到 \0）
                    bool nullTerminatorFound = (2 + rawLen) < recv.Length;

                    // ── 排除"驱动填充零"误判 ──────────
                    // 条件：\0确实存在 AND 读到的有效长度 > 驱动截断嫌疑值
                    // 即：len > NV_SUSPECT_TRUNCATE_LEN 才认为是真正的字符串结束符
                    // len <= NV_SUSPECT_TRUNCATE_LEN 时，\0 很可能是驱动填充，不可信
                    bool isComplete = nullTerminatorFound && len > NV_SUSPECT_TRUNCATE_LEN;

                    if (isComplete)
                        return value;

                    // ── N卡截断修复：尝试分段续读 ──────────────────────────
                    // 当读到的有效长度 <= NV_SUSPECT_TRUNCATE_LEN，且 DDC 响应头声明
                    // 还有更多数据时，尝试从 0x6F 续读剩余数据。
                    // N卡 DP AUX 通道单次 I2C 读取上限约 16 字节（2头 + 14数据），
                    // 导致长字符串（如 PanelName/BoardName）被截断。
                    if (!segReadTried && len <= NV_SUSPECT_TRUNCATE_LEN)
                    {
                        if (reportedDataLen > len)
                        {
                            segReadTried = true;  // 标记已尝试，不再重复
                            int remainDataLen = reportedDataLen - len;
                            // 续读剩余数据 + 校验和（多读1字节余量）
                            Byte[] cont = MTKDebugCmd.DDC_ReadContinue(remainDataLen + 1);

                            if (cont != null && cont.Length >= 1)
                            {
                                // 续读数据需排除"新响应头"误判：
                                // 只有 [0x6E] + [Len|0x80] 同时匹配才算监视器重置了指针。
                                // 单独一个 0x6E（ASCII 'n'）可能是字符串内容，不能误判。
                                bool contIsNewResponse = (cont.Length >= 2
                                    && cont[0] == 0x6E && (cont[1] & 0x80) != 0);

                                if (!contIsNewResponse)
                                {
                                    // 续读数据不是新响应头 → 可能是真正的续传数据
                                    // 只取可打印ASCII (0x20-0x7E)，校验和字节(>=0x80)会被自然截断
                                    int contLen = 0;
                                    while (contLen < cont.Length
                                        && cont[contLen] >= 0x20 && cont[contLen] <= 0x7E)
                                        contLen++;

                                    if (contLen > 0)
                                    {
                                        string contStr = TrimNonStandardDdcSuffix(
                                            Encoding.ASCII.GetString(cont, 0, contLen).Trim());
                                        string fullValue = TrimNonStandardDdcSuffix(value + contStr);

                                        frmISP.WriteLog(
                                            $"[DDC] cmd=0x{subCmd:X2} 分段续读成功: '{value}' + '{contStr}' = '{fullValue}' " +
                                            $"(first={len}, cont={contLen}, reported={reportedDataLen})",
                                            "debug");

                                        return fullValue;
                                    }
                                }
                                else
                                {
                                    // 续读返回新响应头 → 监视器重置了指针，不支持续读
                                    frmISP.WriteLog(
                                        $"[DDC] cmd=0x{subCmd:X2} 分段续读: 监视器重置指针，续读返回新响应头 " +
                                        $"(first={len}, reported={reportedDataLen})",
                                        "debug");
                                }
                            }
                        }
                    }
                    // ── End N卡截断修复 ──────────────────────────────────────

                    // 未通过完整性校验，记录为最优截断值，继续尝试更大 readLen
                    if (value.Length > bestPartial.Length)
                    {
                        bestPartial = value;
                        frmISP.WriteLog(
                            $"[DDC] cmd=0x{subCmd:X2} readLen={readLen} attempt={attempt} " +
                            $"partial='{value}' (len={len}, nullFound={nullTerminatorFound})",
                            "debug");
                    }

                    WinIOLib.DelayMs(20);
                }
            }

            // 所有 readLen 都未能读到完整值，返回最长截断结果
            // TryExpandField 负责前缀补全
            if (!string.IsNullOrEmpty(bestPartial))
            {
                frmISP.WriteLog(
                    $"[DDC] cmd=0x{subCmd:X2} all readLens exhausted, best='{bestPartial}'",
                    "debug");
            }

            return bestPartial;
        }

        /// <summary>
        /// 滤除 DDC 字符串尾部的非标准字符。
        /// AMD ADL DDC 偶尔返回损坏的响应头（reportedDataLen 被放大），
        /// 导致字符串尾部夹带垃圾字节（如 checksum + 下一字节）。
        /// 监视器字段名合法字符集：[A-Za-z0-9 _.\\-/]
        /// </summary>
        private static string TrimNonStandardDdcSuffix(string raw)
        {
            if (string.IsNullOrEmpty(raw))
                return raw;
            int end = raw.Length;
            while (end > 0)
            {
                char c = raw[end - 1];
                if (char.IsLetterOrDigit(c) || c == '_' || c == ' ' || c == '.' || c == '-' || c == '/')
                    break;
                end--;
            }
            if (end < raw.Length)
            {
                frmISP.WriteLog(
                    $"[DDC-Sanitize] trimmed {raw.Length - end} trailing char(s): '{raw}' → '{raw.Substring(0, end)}'",
                    "debug");
            }
            return raw.Substring(0, end);
        }

      

        private void FormISP_MSI_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_IsRunISP)
            {
                SetStatusMsg("Firmware is updating, please do not turn off application.");
                e.Cancel = true;
            }
        }

        private static string NormalizeMonitorInfo(string value)
        {
            return String.IsNullOrWhiteSpace(value) ? String.Empty : value.Trim();
        }

        private string NormalizeMonitorModelName(string value)
        {
            string normalizedValue = NormalizeMonitorInfo(value);
            if (string.Equals(normalizedValue, PlaceholderMonitorModelName, StringComparison.OrdinalIgnoreCase))
                return LegacyMonitorModelName;

            return normalizedValue;
        }

        private void btnLoadFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.InitialDirectory = "c:\\";
            dlg.Filter = "bin files (*.bin)|*.bin";
            dlg.FilterIndex = 1;
            dlg.RestoreDirectory = true;

            if (dlg.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    FileInfo fi = new FileInfo(dlg.FileName);
                    _binFilePath = fi.FullName;
                    tsslFilePath.Text = fi.Name;
                    tsslFileSize.Text = InitBinaryFile(_binFilePath).Length.ToString();

                    // 解析bin文件中的u8ModelName、u8ChipName、u8PanelName、u8FWVersion
                    const long infoAddr = 0x0041380;
                    const int modenameLen = 20; // 每个名字最大长度
                    const int chipnameLen = 20;
                    const int panelnameLen = 30;
                    const int fwversionLen = 20;
                    const int totalLen = modenameLen + chipnameLen + panelnameLen + fwversionLen;
                    byte[] infoBytes = ReadBinSection(_binFilePath, infoAddr, totalLen);
                    if (infoBytes.Length < totalLen)
                        throw new InvalidDataException("Bin metadata section is shorter than expected.");
                    _u8ModelName = Encoding.ASCII.GetString(infoBytes, 0, modenameLen).TrimEnd('\0');
                    _u8ChipName = Encoding.ASCII.GetString(infoBytes, modenameLen, chipnameLen).TrimEnd('\0');
                    _u8PanelName = Encoding.ASCII.GetString(infoBytes, modenameLen + chipnameLen, panelnameLen).TrimEnd('\0');
                    _u8FWVersion = Encoding.ASCII.GetString(infoBytes, modenameLen + chipnameLen + panelnameLen, fwversionLen).TrimEnd('\0');
                    _u8BoardName = String.Empty;
                    frmISP.WriteLog("BIN parsed: Model=[" + _u8ModelName + "] Chip=[" + _u8ChipName + "] Panel=[" + _u8PanelName + "] FW=[" + _u8FWVersion + "]", "debug");
               //     SetStatusMsg("Read Bin OK. Model: " + _u8ModelName + ", Chip: " + _u8ChipName + ", Panel: " + _u8PanelName + ", FW: " + _u8FWVersion);
                }
                catch (Exception ex)
                {
                    frmISP.WriteLog("btnLoadFile_Click exception: " + ex.GetType().Name + ": " + ex.Message, "debug");
                    MessageBox.Show(this, "加载文件失败: " + ex.GetType().Name + "\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    SetStatusMsg("Could not read file from disk.", true);
                    btnAuto.Enabled = false;
                }

                if (_binFilePath == String.Empty)
                    return;

                if (flashSectorInfo != null && flashSectorInfo.Count > 0)
                    flashSectorInfo.Clear();
                // 已连接时跳过重连，避免DDC偶发握手失败触发错误弹窗
                if (!IsConnect)
                    Connect();

                if (IsConnect)
                {
                    string verifyErr;
                    if (!ValidateMonitorVsBin(out verifyErr))
                    {
                        SetStatusMsg(verifyErr, true);
                        SetStatusMsg("Please select a F/W file.");
                        btnAuto.Enabled = false;
                    }
                    else
                    {
                        SetStatusMsg("BIN loaded. Ready to upgrade.");
                        btnAuto.Enabled = true;
                    }
                }
            }
        }

        private void QueryConnectedMonitorInfo()
        {
            try
            {
                if (IsAmdOrIntelPath())
                {
                    // ── A卡 / I卡：走稳健多轮读取 ────────────────────────────
                    string rawModelName = ReadMonitorFieldRobust(CmdGetModelName, MonitorModelNameLength);

                    if (string.Equals(rawModelName.Trim(), PlaceholderMonitorModelName, StringComparison.OrdinalIgnoreCase))
                    {
                        WriteLog("Placeholder model detected, legacy remapping ...", "debug");
                        _monitorModelName = LegacyMonitorModelName;
                        _monitorChipName = "MST9U";
                        _monitorPanelName = "Panel_SG315GD01_2_eDP";
                    }
                    else
                    {
                        _monitorModelName = NormalizeMonitorModelName(rawModelName);
                        _monitorChipName = ReadMonitorFieldRobust(CmdGetChipName, MonitorChipNameLength);
                        _monitorPanelName = ReadMonitorFieldRobust(CmdGetPanelName, MonitorPanelNameLength);
                    }

                    _monitorBoardName = ReadMonitorFieldRobust(CmdGetBoardName, MonitorBoardNameLength);
                }
                else
                {
                    // ── N卡：走 TryReadMonitorIdentity，结果直接写入 _monitor* ─
                    TryReadMonitorIdentity(10, 100);
                }

                WriteLog("MonitorModelName=" + _monitorModelName, "info");
                WriteLog("MonitorChipName=" + _monitorChipName, "info");
                WriteLog("MonitorPanelName=" + _monitorPanelName, "info");
                WriteLog("MonitorBoardName=" + _monitorBoardName, "info");
            }
            catch (Exception ex)
            {
                WriteLog("QueryConnectedMonitorInfo Exception: " + ex.Message, "error");
            }
        }

        private bool TryReadMonitorIdentity(int retryCount = 5, int delayMs = 80)
        {
            DDC_SetToolFlag(false);

            for (int i = 0; i < retryCount; i++)
            {
                // ── ModelName ─────────────────────────────────────────────────
                if (string.IsNullOrWhiteSpace(_monitorModelName))
                {
                    string rawModel = DDC_GetMonitorName(MS_GET_MODEL_NAME);
                    if (string.Equals(NormalizeMonitorInfo(rawModel), PlaceholderMonitorModelName,
                            StringComparison.OrdinalIgnoreCase))
                    {
                        WriteLog("Placeholder model detected, applying legacy field remapping", "debug");
                        _monitorModelName = LegacyMonitorModelName;
                        _monitorChipName = "MST9U";
                        _monitorPanelName = "Panel_SG315GD01_2_eDP";
                        _monitorVersionFromDDC = "FW.033";
                        return true;  // Placeholder 场景字段已全部填充，直接返回
                    }
                    _monitorModelName = NormalizeMonitorModelName(rawModel);
                }

                // ── ChipName ──────────────────────────────────────────────────
                if (string.IsNullOrWhiteSpace(_monitorChipName))
                    _monitorChipName = DDC_GetMonitorName(MS_GET_CHIP_NAME);

                // ── PanelName ─────────────────────────────────────────────────
                if (string.IsNullOrWhiteSpace(_monitorPanelName))
                {
                    WinIOLib.DelayMs(50);
                    _monitorPanelName = DDC_GetMonitorName(MS_GET_PANEL_NAME);
                }

                // ── BoardName ─────────────────────────────────────────────────
                if (string.IsNullOrWhiteSpace(_monitorBoardName))
                    _monitorBoardName = DDC_GetMonitorName(MS_GET_BOARD_NAME);

                // ── FwVersion ─────────────────────────────────────────────────
                if (string.IsNullOrWhiteSpace(_monitorVersionFromDDC))
                {
                    _monitorVersionFromDDC = DDC_GetMonitorName(MS_GET_FW_VERSION);
                    frmISP.WriteLog($"DDC_GetMonitorName:_monitorVersionFromDDC={_monitorVersionFromDDC}", "debug");
                }

                frmISP.WriteLog(
                    $"[NV-Identity] retry#{i} " +
                    $"Model={_monitorModelName}, Chip={_monitorChipName}, " +
                    $"Panel={_monitorPanelName}, Board={_monitorBoardName}, Ver={_monitorVersionFromDDC}",
                    "debug");

                // ModelName 是必须字段
                if (!string.IsNullOrWhiteSpace(_monitorModelName))
                    return true;

                WinIOLib.DelayMs(delayMs);
            }

            return !string.IsNullOrWhiteSpace(_monitorModelName);
        }

        /// <summary>
        /// AMD/Intel 友好的型号读取：带响应有效性校验 + 多轮重试。
        /// AMD ADL DDC 读命中率低（约 4 错 1 对），需要足够重试次数才能读到完整字符串。
        /// 采用"众数策略"而非"取最长"：AMD 偶尔返回损坏的响应头会拉长字符串，
        /// 统计上正确的值出现频率最高，长度众数 + 值众数投票能过滤掉偶发损坏。
        /// </summary>
        private string ReadMonitorFieldRobust(byte subCmd, int expectLen, int maxRounds = 30)
        {
            var candidates = new System.Collections.Generic.List<string>();
            string bestEver = string.Empty;

            for (int round = 0; round < maxRounds; round++)
            {
                string v = DDC_GetMonitorName(subCmd, 300);
                if (!string.IsNullOrWhiteSpace(v))
                {
                    // ── AMD 响应头损坏防御 ───────────────────────────────
                    // AMD ADL DDC 偶尔返回损坏的响应头（reportedDataLen 被放大），
                    // 导致字符串尾部夹带垃圾字节。虽然 DDC_GetMonitorName 已做
                    // 尾部字符过滤，但仍需在调用方以 expectLen 作为硬上限：
                    // 任何超过预期最大长度的值一律拒绝，不作为有效候选。
                    if (v.Length > expectLen)
                    {
                        frmISP.WriteLog(
                            $"[AMD-Reject] cmd=0x{subCmd:X2} round#{round} value too long " +
                            $"({v.Length} > expectLen={expectLen}): '{v}'",
                            "debug");
                        WinIOLib.DelayMs(50);
                        continue;
                    }
                    // ── End AMD 响应头损坏防御 ──────────────────────────

                    candidates.Add(v);
                    frmISP.WriteLog($"[AMD-Identity] cmd=0x{subCmd:X2} round#{round} got='{v}' (len={v.Length})", "debug");

                    if (v.Length > bestEver.Length)
                        bestEver = v;

                    // 提前退出优化：
                    // 1) 读到接近 expectLen 的值 → 大概率完整
                    // 2) 同一值已稳定出现 3 次 → 确信正确
                    if (v.Length >= expectLen - 1)
                        return v;

                    int sameCount = 0;
                    for (int i = candidates.Count - 1; i >= 0 && sameCount < 3; i--)
                        if (candidates[i] == v) sameCount++;
                    if (sameCount >= 3)
                        return v;
                }
                WinIOLib.DelayMs(50);
            }

            if (candidates.Count == 0)
                return bestEver;

            // ── 众数投票：选出现最频繁的长度，再取该长度下出现最频繁的值 ──
            int modeLen = candidates.GroupBy(s => s.Length)
                .OrderByDescending(g => g.Count())
                .First().Key;

            string modeValue = candidates.Where(s => s.Length == modeLen)
                .GroupBy(s => s)
                .OrderByDescending(g => g.Count())
                .First().Key;

            if (modeValue.Length < bestEver.Length)
            {
                frmISP.WriteLog(
                    $"[AMD-Vote] cmd=0x{subCmd:X2} mode='{modeValue}' (len={modeLen}) < bestEver='{bestEver}' (len={bestEver.Length})",
                    "debug");
            }

            return modeValue;
        }

        private void ClearConnectedMonitorInfo()
        {
            _monitorModelName = string.Empty;
            _monitorChipName = string.Empty;
            _monitorPanelName = string.Empty;
            _monitorBoardName = string.Empty;
        }

        void Connect(int delayTime = 0)
        {
            SetStatusMsg("Connect MTK Monitor...");
            //Application.DoEvents();
            Byte[] buf = { 0xCC, 0x90, 0x00, 0x00 };

            WinIOLib.DelayMs(delayTime);

            if (_simI2cCtrl.Init() == true)
            {
                frmISP.WriteLog("simI2cCtrl.Init() = true", "debug");

                // >>> 改动：缓存卡类型，避免反复强转 + 反复 DllGetGraphicCardType 调用
                GraphicCardI2CCtrl.GRAPHIC_CARD cardType = ((GraphicCardI2CCtrl)_simI2cCtrl).GetGraphicCardType;
                bool isAmd = (cardType == GraphicCardI2CCtrl.GRAPHIC_CARD.AMD);
                bool isIntel = (cardType == GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL)
                            || (cardType == GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL_IGCL);

                switch (cardType)
                {
                    case GraphicCardI2CCtrl.GRAPHIC_CARD.AMD: { frmISP.WriteLog("GraphicCard=AMD", "info"); } break;
                    case GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL: { frmISP.WriteLog("GraphicCard=INTEL", "info"); } break;
                    case GraphicCardI2CCtrl.GRAPHIC_CARD.NVIDIA: { frmISP.WriteLog("GraphicCard=NVIDIA", "info"); } break;
                    case GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL_IGCL: { frmISP.WriteLog("GraphicCard=INTEL-IGCL", "info"); } break;
                    case GraphicCardI2CCtrl.GRAPHIC_CARD.NA: { frmISP.WriteLog("GraphicCard=UNKNOWN", "warn"); } break;
                }

                if (((GraphicCardI2CCtrl)_simI2cCtrl).GetConnectedPortType == GraphicCardI2CCtrl.PortTypes.HDMI)
                {
                    frmISP.WriteLog("_IsHDMIConnect = true;", "debug");
                    _IsHDMIConnect = true;
                }
                else
                {
                    frmISP.WriteLog("_IsHDMIConnect = false;", "debug");
                    _IsHDMIConnect = false;
                }

                MTKDebugCmd.InitI2CCtrl(_simI2cCtrl);
                DDC_SetToolFlag(true);
                WinIOLib.DelayMs(10);
                //     DDC2CI_SetR2Reset51Flag(0x01);
                Byte chipType = 0;
                IsConnect = false;
                MTKDebugCmd.DDC_Write(buf);
                //if (IsConnect)
                {
                    //WriteLog("IsConnect = true", "debug");
                    btnAuto.Enabled = !String.IsNullOrEmpty(_binFilePath);
                    if (String.IsNullOrEmpty(_binFilePath))
                        SetStatusMsg("Please select a F/W file.");
                    else
                        SetStatusMsg("BIN loaded. Ready to upgrade.");

                    String nameFromGPU = "";
                    List<String> list = ((GraphicCardI2CCtrl)_simI2cCtrl).InitConnectedMonitor;
                    frmISP.WriteLog("InitConnectedMonitor count = " + list.Count, "debug");

                    // >>> 改动：AMD count=0 时不再切去 NVIDIA，而是在 AMD 控制器上"等待+重新枚举"
                    //     原因：经日志确认本机就是 AMD 且 DDC 读写功能正常，count=0 的真因是通道未就绪，
                    //     切换控制器是误诊，反复重建句柄反而引入不稳定。
                    if (list.Count == 0 && isAmd)
                    {
                        const int MaxEnumRetry = 8;
                        const int EnumRetryDelayMs = 400;
                        for (int i = 0; i < MaxEnumRetry && list.Count == 0; i++)
                        {
                            frmISP.WriteLog($"[AMD-Enum] count=0, wait {EnumRetryDelayMs}ms then retry #{i + 1} (channel may not be ready)", "debug");
                            WinIOLib.DelayMs(EnumRetryDelayMs);
                            list = ((GraphicCardI2CCtrl)_simI2cCtrl).InitConnectedMonitor;
                        }
                        frmISP.WriteLog($"[AMD-Enum] final count = {list.Count} after retries", list.Count > 0 ? "info" : "error");
                    }

                    // NVIDIA DP AUX channel may not be ready on first launch – retry with delay.
                    // (NVIDIA/其它卡路径保持原逻辑不变)
                    {
                        int mntRetry = 0;
                        while (list.Count == 0 && mntRetry++ < 3)
                        {
                            frmISP.WriteLog("InitConnectedMonitor = 0, waiting 1500ms before retry " + mntRetry + " ...", "debug");
                            WinIOLib.DelayMs(1500);
                            list = ((GraphicCardI2CCtrl)_simI2cCtrl).InitConnectedMonitor;
                            frmISP.WriteLog("InitConnectedMonitor retry " + mntRetry + " count = " + list.Count, "debug");
                        }
                        // If all retries exhausted and still no monitor, recreate the GPU control and try once more.
                        if (list.Count == 0)
                        {
                            frmISP.WriteLog("InitConnectedMonitor still 0 – calling ReCreateGraphicCardCtrl ...", "warn");
                            ((GraphicCardI2CCtrl)_simI2cCtrl).ReCreateGraphicCardCtrl();
                            WinIOLib.DelayMs(1500);
                            list = ((GraphicCardI2CCtrl)_simI2cCtrl).InitConnectedMonitor;
                            frmISP.WriteLog("InitConnectedMonitor after ReCreate count = " + list.Count, "warn");
                        }
                    }

                    for (int idxDisp = 0; idxDisp < list.Count; idxDisp++)
                    {
                        int retry = 0; // Reset retry counter for each monitor index
                        nameFromGPU = list[idxDisp].ToUpper();
                        frmISP.WriteLog("nameFromGPU[" + idxDisp + "] = " + nameFromGPU, "debug");
                        _selectedMonitorIndex = idxDisp;

                        ((GraphicCardI2CCtrl)_simI2cCtrl).SetConnectedMonitorIndex((Byte)idxDisp);

                        // >>> 改动：握手重试循环。关键修复——AMD 路径在每次失败后加延时，
                        //     给 DDC 通道就绪时间；其它卡保持原"快速 30 连试"行为不变。
                        while ((IsConnect == false) && (retry++ < 30))
                        {
                            IsConnect = DDC_CheckMTKDevice(ref chipType);
                            if (IsConnect)
                                break;

                            if (isAmd)
                            {
                                // AMD: 通道刚枚举出可能未就绪，慢节奏重试（前几次短，逐步拉长）
                                int wait = (retry < 5) ? 150 : 300;
                                frmISP.WriteLog($"[AMD-Ready] DDC_CheckMTKDevice not ready, retry#{retry}, wait {wait}ms", "debug");
                                WinIOLib.DelayMs(wait);
                            }
                        }
                        frmISP.WriteLog("idxDisp=" + idxDisp + " IsConnect=" + IsConnect + " chipType=" + chipType
                            + " retryUsed=" + retry, IsConnect ? "info" : "warn");   // >>> 改动：成功/失败分级 + 记录用了几次重试
                        if (IsConnect == true)
                        {
                            break;
                        }
                    }

                    // >>> 改动：AMD/Intel 路径若最终仍未连上，明确记一条 error，便于现场区分
                    if (!IsConnect && (isAmd || isIntel))
                    {
                        frmISP.WriteLog("[Connect] AMD/Intel handshake FAILED on all monitors. "
                            + "DDC channel likely not ready or wrong adapter/display index.", "error");
                    }

                    // Intel IGCL and AMD can complete the FE handshake but still time out on optional
                    // identity queries during connect. Skip those reads here so connect stays usable.
                    // 所有显卡都要读，不要忽视
                    //QueryConnectedMonitorInfo();                   
                }

                if (IsConnect == false)
                {
                    frmISP.WriteLog("IsConnect = false", "error");   // >>> 改动：升级为 error
                    btnAuto.Enabled = false;
                    ClearConnectedMonitorInfo();
                    SetStatusMsg("Current F/W version is not compatible with this tool.", true);
                }
                _chipType = chipType;
                switch (chipType)
                {
                    case 0: { frmISP.WriteLog("MTKChipType=Not Defined", "warn"); } break;   // >>> 改动：未识别提为 warn
                    case 1: { frmISP.WriteLog("MTKChipType=TSUM", "info"); } break;
                    case 2: { frmISP.WriteLog("MTKChipType=MST9U", "info"); } break;
                }
                Application.DoEvents();

                // 不忽视，都要读取
                DDC_GetVersion();
               
                DDC_SetToolFlag(false);

                int pktDelayTime = 0;
                switch (cardType)   // >>> 改动：用缓存的 cardType
                {
                    case GraphicCardI2CCtrl.GRAPHIC_CARD.AMD:
                        {
                            if (_chipType == 2)
                            {
                                //MST9U
                                pktDelayTime = 200;
                            }
                            else
                            {
                                //TSUMxxG : 當maxPktLen=59時，低於1000時SendDataToDram會失敗。
                                pktDelayTime = 1500;
                            }
                            frmISP.WriteLog("GRAPHIC_CARD.AMD", "info");
                        }
                        break;
                    case GraphicCardI2CCtrl.GRAPHIC_CARD.NVIDIA:
                        {
                            if (_chipType == 2)
                            {
                                //MST9U
                                pktDelayTime = 200;
                            }
                            else
                            {
                                //TSUMxxG : 當maxPktLen=59時，低於1000時SendDataToDram會失敗。
                                pktDelayTime = 1000;
                            }
                            frmISP.WriteLog("GRAPHIC_CARD.NVIDIA", "info");
                        }
                        break;
                    case GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL:
                    case GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL_IGCL:
                        {
                            //defaultPktLen = 10;
                            if (_IsHDMIConnect)
                            {
                                //INTEL HDMI 的DDC大部分只有量到10KHz, Bus已經很慢了 所以不需要delay time.
                                pktDelayTime = 0;
                            }
                            else
                            {
                                //INTEL DP
                                if (_chipType == 2)
                                {
                                    //MST9U
                                    pktDelayTime = 0;
                                }
                                else
                                {
                                    //TSUMxxG
                                    pktDelayTime = 0;
                                }
                            }
                            frmISP.WriteLog("GRAPHIC_CARD.INTEL/INTEL_IGCL", "info");
                        }
                        break;
                }
                _pktDelayTimeText = pktDelayTime.ToString();
            }
            else
            {
                // GPU DDC init failed – driver not installed, GPU not supported, or monitor not connected.
                frmISP.WriteLog("simI2cCtrl.Init() = false: GPU DDC init failed. Check GPU driver and monitor connection.", "error");   // >>> 改动：升级为 error
                btnAuto.Enabled = false;
                SetStatusMsg("GPU DDC init failed. Check driver and monitor cable.");
                return;
            }

            if (IsConnect)
            {
                if (String.IsNullOrEmpty(_binFilePath))
                    SetStatusMsg("Please select a F/W file.");
                else
                    SetStatusMsg("BIN loaded. Ready to upgrade.");
            }
            else
                SetStatusMsg("Ready");
        }

        private Byte _ver0 = 0;
        private Byte _ver1 = 0;
        private Byte _ver2 = 0;

        private void DDC_GetVersion()
        {
            Byte[] cmd1 = { 0xCC, 0x3A };
            Byte[] recv = MTKDebugCmd.DDC_Read(cmd1, 7);
            if (recv != null)
            {
                Byte[] verData = new Byte[5];
                Array.Copy(recv, 2, verData, 0, 5);
                var str = System.Text.Encoding.Default.GetString(verData);
                frmISP.WriteLog("FW Version = " + str, "debug");
                if ((0x2E == verData[1]) && (0x2E == verData[3]))
                {
                    _ver0 = verData[0];
                    _ver1 = verData[2];
                    _ver2 = verData[4];
                }
            }
        }
        public static byte DDC2CI_GetToolFlag()
        {
            Byte[] buf = { 0xCC, 0xA9 };

            byte flag = 0;
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 3);
            if (recv != null)
                flag = recv[2];
            return flag;
        }
        private bool IsAmdOrIntelPath()
        {
            var gpuType = ((GraphicCardI2CCtrl)_simI2cCtrl).GetGraphicCardType;
            return gpuType == GraphicCardI2CCtrl.GRAPHIC_CARD.AMD
                || gpuType == GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL
                || gpuType == GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL_IGCL;
        }

        private void DDC_SetToolFlag(bool en)
        {
            //// AMD and Intel DDC paths do not support the tool-flag handshake command.
            //// Sending it causes repeated DDC timeout errors, so skip entirely.
            //if (IsAmdOrIntelPath())
            //{
            //    frmISP.WriteLog("DDC_SetToolFlag skipped on AMD/Intel path.", "debug");
            //    return;
            //}

            Byte[] buf = { 0xCC, 0x90, 0x00 };
            if (en)
                buf[2] = 0x01;

            MTKDebugCmd.DDC_Write(buf);
            WinIOLib.DelayMs(10);

            int RetryCnt = 0;
            while (RetryCnt <= 6)
            {
                if (Convert.ToBoolean(DDC2CI_GetToolFlag()) == en)
                {
                    RetryCnt = 0;
                    break;
                }

                WinIOLib.DelayMs(30);
                RetryCnt++;

                if ((RetryCnt % 3) == 0)
                    MTKDebugCmd.DDC_Write(buf);
            }
            if (RetryCnt > 0)
            {
                frmISP.WriteLog("DDC_SetToolFlag fail.", "debug");
            }
        }
        public static byte DDC2CI_GetR2Reset51Flag()
        {
            Byte[] buf = { 0xCC, 0x89, 0x00 };

            byte flag = 0;
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 3);
            if (recv != null)
                flag = recv[2];
            return flag;
        }
        public static void DDC2CI_SetR2Reset51Flag(byte en)
        {
            Byte[] buf = { 0xCC, 0x89, 0x01, 0x00 };

            if (en == 1)
                buf[3] = en;

            MTKDebugCmd.DDC_Write(buf);
            WinIOLib.DelayMs(10);

            int RetryCnt = 0;
            while ((DDC2CI_GetR2Reset51Flag() != en) && (RetryCnt <= 3))
            {
                WinIOLib.DelayMs(30);
                RetryCnt++;

                if ((RetryCnt % 3) == 0)
                    MTKDebugCmd.DDC_Write(buf);
            }
        }
        private void DDC_GetCRC(ref UInt32 checkSum)
        {
            Byte[] buf = { 0xCC, 0xF1 };

            //check point
            //NV HDMI 1000
            //NV DP 3000
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 6);
            if (recv != null)
            {
                checkSum = (UInt32)recv[2] + (((UInt32)recv[3]) << 8) + +(((UInt32)recv[4]) << 16) + (((UInt32)recv[5]) << 24);
            }
        }

        private void DDC_SetRecvInfo(int recvSize)
        {
            Byte[] buf = { 0xCC, 0xF2, 0x00, 0x00, 0x00, 0x00 };
            buf[2] = (byte)(recvSize & 0xFF);
            buf[3] = (byte)((recvSize >> 8) & 0xFF);
            buf[4] = (byte)((recvSize >> 16) & 0xFF);
            buf[5] = (byte)((recvSize >> 24) & 0xFF);
            MTKDebugCmd.DDC_Write(buf);
        }

        private void DDC_GetRecvInfo(ref int recvSize)
        {
            Byte[] buf = { 0xCC, 0xF3 };
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 6);
            recvSize = 0;
            if (recv != null)
            {
                recvSize = (int)recv[2] + (((int)recv[3]) << 8) + +(((int)recv[4]) << 16) + (((int)recv[5]) << 24);
            }
        }

        UInt32 errDDCWr = 0;
        private void DDC_SetData(Byte[] data)
        {

            Byte[] buf = new Byte[data.Length + 2];
            buf[0] = 0xCC;
            buf[1] = 0xF4;
            Array.Copy(data, 0, buf, 2, data.Length);
            if (MTKDebugCmd.DDC_Write(buf))
            {
            }
            else
            {
                errDDCWr++;
            }
        }

        private void DDC_GetSentInfo(ref int checkSum, ref int pktCnt, int delayTime)
        {
            Byte[] buf = { 0xCC, 0xF5 };
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 8, delayTime);
            checkSum = 0;
            pktCnt = 0;
            if (recv != null)
            {
                checkSum = (int)recv[2] + (((int)recv[3]) << 8);
                pktCnt = (int)recv[4] + (((int)recv[5]) << 8) + +(((int)recv[6]) << 16) + (((int)recv[7]) << 24);
            }
        }

        private void DDC_GetISPStatus(ref int status, int delayTime = 1000)
        {
            Byte[] buf = { 0xCC, 0xF8 };
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 3, delayTime);
            status = 0;
            if (recv != null)
            {
                status = (int)recv[2];// +(((int)recv[3]) << 8);
                //pktCnt = (int)recv[4] + (((int)recv[5]) << 8) + +(((int)recv[6]) << 16) + (((int)recv[7]) << 24);
            }
        }

        private void DDC_GetImgOneCheckSum(ref int checkSum)
        {
            Byte[] buf = { 0xCC, 0xFD };
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 4, 500);
            if (recv != null)
            {
                checkSum = (int)recv[2] + (((int)recv[3]) << 8);
            }
        }

        private void DDC_SetISPImgShadow(UInt16 checkSum)
        {
            Byte[] buf = { 0xCC, 0xFC, (Byte)(checkSum & 0xFF), (Byte)(checkSum >> 8) };
            MTKDebugCmd.DDC_Write(buf);
        }

        private void DDC_GetISPImgNum(ref int imgNum)
        {
            Byte[] buf = { 0xCC, 0xF9 };
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 3);
            imgNum = 0;
            if (recv != null)
            {
                imgNum = (int)recv[2];// +(((int)recv[3]) << 8);
                //pktCnt = (int)recv[4] + (((int)recv[5]) << 8) + +(((int)recv[6]) << 16) + (((int)recv[7]) << 24);
            }
        }

        private void DDC_SetISPImgOne()
        {
            Byte[] buf = { 0xCC, 0xFA };
            MTKDebugCmd.DDC_Write(buf);
        }

        private void DDC_SetISPReboot()
        {
            Byte[] buf = { 0xCC, 0xFB };
            MTKDebugCmd.DDC_Write(buf);
        }

        private void DDC_SetSentInfo(int pktCnt)
        {
            Byte[] buf = { 0xCC, 0xF6, 0x00, 0x00, 0x00, 0x00 };
            buf[2] = (byte)(pktCnt & 0xFF);
            buf[3] = (byte)((pktCnt >> 8) & 0xFF);
            buf[4] = (byte)((pktCnt >> 16) & 0xFF);
            buf[5] = (byte)((pktCnt >> 24) & 0xFF);
            MTKDebugCmd.DDC_Write(buf);
        }

        private bool DDC_CheckMTKDevice(ref Byte chipType, int delayTime = 1000)
        {
            Random rdn = new Random();
            Byte value1 = (Byte)rdn.Next();
            Byte value2 = (Byte)rdn.Next();
            Byte sum = (Byte)(value1 + value2);
            Byte[] buf = { 0xCC, 0xFE, value1, value2 };
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 4, delayTime);

            if ((recv != null) && (sum == recv[3]))
            {
                //chipType ==> 0 : Not Define, 1: TSUM, 2:MST9U 
                chipType = recv[2];
                if (chipType == 2)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            return false;
        }
        private bool DDC_CheckClientInfo(ref Byte ClientInfo, int delayTime = 1000)
        {
            Random rdn = new Random();
            Byte value1 = (Byte)rdn.Next();
            Byte value2 = (Byte)rdn.Next();
            Byte sum = (Byte)(value1 + value2);
            Byte[] buf = { 0xCC, 0xE0, value1, value2 };
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 4, delayTime);

            if ((recv != null) && (sum == recv[3]))
            {
                //Client info ==> 0: MSI, 1 : ACER
                ClientInfo = recv[2];
                if (ClientInfo == 0)
                {
                    return true;
                }
                else if (ClientInfo == 1)
                {
                    return false;
                }
            }
            return false;
        }
        private bool DDC_CheckPanelInfo(ref Byte PanelInfo, int delayTime = 1000)
        {
            Random rdn = new Random();
            Byte value1 = (Byte)rdn.Next();
            Byte value2 = (Byte)rdn.Next();
            Byte sum = (Byte)(value1 + value2);
            Byte[] buf = { 0xCC, 0xE1, value1, value2 };
            Byte[] recv = MTKDebugCmd.DDC_Read(buf, 4, delayTime);

            if ((recv != null) && (sum == recv[3]))
            {
                //Panel info ==> 118: Panel_SG2701D02_1_eDP, 1 : ACER
                PanelInfo = recv[2];
                if (PanelInfo == 118)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            return false;
        }
        private void DDC_RunISP(UInt16 checkSum)
        {
            Byte[] buf = { 0xCC, 0xF7, (Byte)(checkSum & 0xFF), (Byte)(checkSum >> 8) };
            MTKDebugCmd.DDC_Write(buf);
        }

        class FlashSectorInfo
        {
            public byte[] data;
            public bool IsEmpty;

            public FlashSectorInfo()
            {
                data = new byte[_flashSectorSize];
                Array.Clear(data, 0, data.Length);
                IsEmpty = false;
            }

            public void Clear()
            {
                for (int idx = 0; idx < _flashSectorSize; idx++)
                {
                    data[idx] = 0xFF;
                }
            }

            public void CheckEmpty()
            {
                IsEmpty = true;
                for (int idx = 0; idx < _flashSectorSize; idx++)
                {
                    if (data[idx] != 0xFF)
                    {
                        IsEmpty = false;
                        break;
                    }
                }
            }

            public int CheckSum()
            {
                int checkSum = 0;
                for (int idx = 0; idx < _flashSectorSize; idx++)
                {
                    checkSum += data[idx];
                }
                return checkSum & 0xFFFF;
            }
        }

        private bool SetSentInfo(int sentLen)
        {
            const int MaxRetryCnt = 10;
            int retryCnt = 0;
            int rdCheckSum = 0;
            int rdPktCnt = 0;
            while (retryCnt++ < MaxRetryCnt)
            {
                DDC_SetSentInfo(sentLen);
                WinIOLib.DelayMs(1);
                DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                if (sentLen == rdPktCnt)
                {
                    return true;
                }
            }
            return false;
        }

        private void AdjustPacketParam(int defaultPktLen, ref int PktLen, int defaultPktDelayTime, ref int pktDelayTime, bool bOnlyAdjustDelaytime = false) //Decide PktLen first as it's limitation of GraphicsCard
        {
            Random rdn = new Random();
            int checkSum = 0;
            int rdCheckSum = 0, rdPktCnt = 0;
            int prePktLen = 0, maxPktLen = defaultPktLen, minPktLen = 10;
            int prePktDelayTime = defaultPktDelayTime, maxPktDelayTime = defaultPktDelayTime, minPktDelayTime = 0;
            PktLen = defaultPktLen;

            try
            {
                if (!bOnlyAdjustDelaytime)
                {
                    while (prePktLen != PktLen)
                    {
                        frmISP.WriteLog("Adaptive PktLen: " + PktLen.ToString(), "debug");
                        prePktLen = PktLen;
                        checkSum = 0;
                        byte[] data = new byte[PktLen];
                        for (int i = 0; i < PktLen; i++)
                        {
                            data[i] = (Byte)rdn.Next();
                            checkSum += data[i];
                        }
                        DDC_SetData(data);
                        WinIOLib.DelayUs(maxPktDelayTime);
                        DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                        if ((PktLen != rdPktCnt) || (checkSum != rdCheckSum))
                        {
                            DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                        }
                        if ((PktLen != rdPktCnt) || (checkSum != rdCheckSum))
                        {
                            DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                        }
                        if ((PktLen != rdPktCnt) || (checkSum != rdCheckSum))
                        {
                            DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                        }
                        //reset sent info
                        if (SetSentInfo(0) == false)
                        {
                            SetStatusMsg("Adaptive PktLen fail.");
                            SetISPStatus(false, "Adaptive PktLen fail.");
                            return;
                        }

                        if ((PktLen != rdPktCnt) || (checkSum != rdCheckSum))
                        {
                            maxPktLen = PktLen;
                            PktLen = (PktLen + minPktLen) / 2;
                        }
                        else if (PktLen == defaultPktLen)
                            break;
                        else
                        {
                            minPktLen = PktLen;
                            PktLen = (PktLen + maxPktLen) / 2;
                        }
                    }
                    if ((PktLen != rdPktCnt) || (checkSum != rdCheckSum))
                    {
                        PktLen = 10;
                    }
                }

                int adjustCount = 10;
                pktDelayTime = 0;
                while (pktDelayTime < defaultPktDelayTime && (prePktDelayTime != pktDelayTime))
                {
                    prePktDelayTime = pktDelayTime;
                    checkSum = 0;

                    byte[] data = new byte[PktLen];
                    for (int i = 0; i < PktLen; i++)
                    {
                        data[i] = (Byte)rdn.Next();
                        checkSum += data[i];
                    }
                    for (int i = 0; i < adjustCount; i++)
                    {
                        DDC_SetData(data);
                        WinIOLib.DelayUs(pktDelayTime);
                    }
                    checkSum *= adjustCount;
                    checkSum = checkSum & 0xFFFF;

                    DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                    if ((PktLen * adjustCount != rdPktCnt) || (checkSum != rdCheckSum))
                    {
                        DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                    }
                    if ((PktLen * adjustCount != rdPktCnt) || (checkSum != rdCheckSum))
                    {
                        DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                    }
                    if ((PktLen * adjustCount != rdPktCnt) || (checkSum != rdCheckSum))
                    {
                        DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                    }
                    if ((PktLen * adjustCount != rdPktCnt) || (checkSum != rdCheckSum))
                    {
                        minPktDelayTime = pktDelayTime;
                        pktDelayTime = (pktDelayTime + maxPktDelayTime) / 2;
                    }
                    else
                    {
                        maxPktDelayTime = pktDelayTime;
                        pktDelayTime = (pktDelayTime + minPktDelayTime) / 2;
                    }
                    //reset sent info
                    if (SetSentInfo(0) == false)
                    {
                        SetStatusMsg("Adaptive pktDelayTime fail.");
                        SetISPStatus(false, "Adaptive pktDelayTime fail.");
                        return;
                    }
                    frmISP.WriteLog("Adaptive pktDelayTime: " + pktDelayTime.ToString() + "us.", "debug");
                }
            }
            catch (Exception e)
            {
                frmISP.WriteLog("Exception: " + e.Message, "debug");
                ((GraphicCardI2CCtrl)_simI2cCtrl).ReCreateGraphicCardCtrl();
                defaultPktLen = 10;
                int fallbackDelayHint = Math.Max(Convert.ToInt32(_pktDelayTimeText) * 3, 300);
                AdjustPacketParam(defaultPktLen, ref maxPktLen, fallbackDelayHint, ref pktDelayTime, true);
            }
        }

        private int SendSectorData(FlashSectorInfo si, int failCnt) //leave it for debug
        {
            int maxPktLen = 59;
            int pktDelayTime = 1000;
            bool canCheckIdleStatus = false;
            switch (((GraphicCardI2CCtrl)_simI2cCtrl).GetGraphicCardType)
            {
                case GraphicCardI2CCtrl.GRAPHIC_CARD.AMD:
                    {
                        if (_IsHDMIConnect)
                        {
                        }
                        else
                        {
                            maxPktLen = 11; //Need to check
                        }

                        if (_chipType == 2)
                        {
                            pktDelayTime = 200;
                        }
                        else
                        {
                            //當maxPktLen=59時，低於1000時SendDataToDram會失敗。
                            pktDelayTime = 1500;
                        }
                    }
                    break;
                case GraphicCardI2CCtrl.GRAPHIC_CARD.NVIDIA:
                    {
                        if (_chipType == 2)
                        {
                            pktDelayTime = 200;
                        }
                        else
                        {
                            //當maxPktLen=59時，低於1000時SendDataToDram會失敗。
                            pktDelayTime = 1000;
                        }
                    }
                    break;
                case GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL_IGCL:
                case GraphicCardI2CCtrl.GRAPHIC_CARD.INTEL:
                    if (_IsHDMIConnect)
                    {
                        //INTEL HDMI 的DDC大部分只有量到10KHz, Bus已經很慢了 所以不需要delay time.
                        pktDelayTime = 0;
                        maxPktLen = 11;
                    }
                    else
                    {
                        maxPktLen = 11; //Need to check
                        //INTEL DP
                        if (_chipType == 2)
                        {
                            pktDelayTime = 0;
                        }
                        else
                        {
                            pktDelayTime = 0;
                        }
                    }
                    break;
            }

            //Adaptive packet lenght adjustment for different OS/GPU.
            if (failCnt > 0)
            {
                maxPktLen = maxPktLen - failCnt * 5;
            }

            if (maxPktLen < 10) //6E 51 Length CC F4 Data Chksum
            {
                maxPktLen = 10;
            }

            int pktLen = 0;
            int sentLen = 0;
            while (sentLen < si.data.Length)
            {
                if ((si.data.Length - sentLen) > maxPktLen)
                    pktLen = maxPktLen;
                else
                    pktLen = si.data.Length - sentLen;

                byte[] data = new byte[pktLen];

                Array.Copy(si.data, sentLen, data, 0, pktLen);
                DDC_SetData(data);
                if (canCheckIdleStatus)
                {
                    CheckISPIdleStatus();
                }
                else
                {
                    WinIOLib.DelayUs(pktDelayTime);
                }

                sentLen += pktLen;
            }
            return si.CheckSum();
        }
        private int SendSectorData(FlashSectorInfo si, int failCnt, int maxPktLen, int pktDelayTime)
        {
            bool canCheckIdleStatus = false;

            //Adaptive packet lenght adjustment for different OS/GPU.
            if (failCnt > 0)
            {
                if (pktDelayTime == 0)
                    pktDelayTime = 200;

                pktDelayTime = pktDelayTime + 100 * failCnt;
            }

            int pktLen = 0;
            int sentLen = 0;
            while (sentLen < si.data.Length)
            {
                if ((si.data.Length - sentLen) > maxPktLen)
                    pktLen = maxPktLen;
                else
                    pktLen = si.data.Length - sentLen;

                byte[] data = new byte[pktLen];

                Array.Copy(si.data, sentLen, data, 0, pktLen);
                DDC_SetData(data);
                if (canCheckIdleStatus)
                {
                    CheckISPIdleStatus();
                }
                else
                {
                    WinIOLib.DelayUs(pktDelayTime);
                }

                sentLen += pktLen;
            }
            return si.CheckSum();
        }        
        private bool IsFieldMatch(string binValue, string fwValue)
        {
            return string.Equals((binValue ?? string.Empty).Trim(), (fwValue ?? string.Empty).Trim(), StringComparison.Ordinal);
        }

        private string NormalizeFwVersionForCompare(string value)
        {
            // Compare the first 4 characters of the version string (e.g. "FW.0") as a
            // product-family identifier.  This ensures that a BIN labelled "FG.034" is
            // rejected on a monitor running "FW.034" (different prefix = different family),
            // while allowing version-number upgrades within the same family
            // (e.g. "FW.033" monitor can be flashed with "FW.034" BIN).
            string normalized = (value ?? string.Empty).Trim().ToUpperInvariant();
            if (string.IsNullOrEmpty(normalized))
                return string.Empty;

            return normalized.Length >= 4 ? normalized.Substring(0, 4) : normalized;
        }

        private bool ValidateMonitorVsBin(out string errMsg)
        {
            errMsg = String.Empty;

            prbProgress.Style = ProgressBarStyle.Marquee;
            prbProgress.MarqueeAnimationSpeed = 30;
            SetStatusMsg("Verifying monitor firmware information, please wait...");

            try
            {
                bool isAmdOrIntel = IsAmdOrIntelPath();          
            
                QueryConnectedMonitorInfo();
                      
                if (string.IsNullOrWhiteSpace(_fwVersionFromDDC))
                {
                    _fwVersionFromDDC = DDC_GetMonitorName(MS_GET_FW_VERSION);
                    frmISP.WriteLog($"DDC_GetMonitorName:_fwVersionFromDDC{_fwVersionFromDDC}", "debug");
                }
        
                // -----------------------------------------------------------------------
                // Legacy-batch fallback: old monitors shipped before DDC identity commands
                // were implemented do not respond to MS_GET_MODEL_NAME / CHIP / PANEL / BOARD.
                // When no reply is received, substitute the known identifiers from the legacy
                // batch BIN file: MAG 321UPD E14_V8-MM802MS-G01V033_20260429_8ECF.bin
                //
                // Also treat firmware that returns the placeholder "MST9U6_PanelCMIM236HGJ_L21"
                // as legacy: the tool_model_panel_name macro was never updated to the real product
                // name in early firmware versions, so use the same hardcoded identifiers.
                // -----------------------------------------------------------------------
                const string LegacyModel = "MAG 321UPD E14";
                const string LegacyChip  = "MST9U";
                const string LegacyPanel = "Panel_SG315GD01_2_eDP";
                const string LegacyFWVer = "FW.033";   // from MAG 321UPD E14_V8-MM802MS-G01V033_20260429_8ECF.bin
                const string PlaceholderModelName = "MST9U6_PanelCM";

                // Placeholder detection: match exact string or any valid prefix of it (DDC reads may be truncated).
                string _fwModelTrim = (_monitorModelName ?? string.Empty).Trim();
                bool fwModelIsPlaceholder =
                    (_fwModelTrim.Length >= 8 && PlaceholderModelName.StartsWith(_fwModelTrim, StringComparison.OrdinalIgnoreCase))
                    || string.Equals(_fwModelTrim, PlaceholderModelName, StringComparison.OrdinalIgnoreCase);
                if (fwModelIsPlaceholder)
                    frmISP.WriteLog("Monitor firmware返回占位符model名 '" + _monitorModelName + "'，使用LegacyFallback进行四项校验", "debug");

                bool usingLegacyFallback = string.IsNullOrWhiteSpace(_monitorModelName) || fwModelIsPlaceholder;

                string effModel = usingLegacyFallback ? LegacyModel : _monitorModelName;
                string effChip  = !string.IsNullOrWhiteSpace(_monitorChipName)  ? _monitorChipName
                                : (usingLegacyFallback ? LegacyChip  : string.Empty);
                string effPanel = !string.IsNullOrWhiteSpace(_monitorPanelName) ? _monitorPanelName
                                : (usingLegacyFallback ? LegacyPanel : string.Empty);

                // effVer: prefer actual DDC read; fall back to LegacyFWVer when in legacy-fallback mode
                // (covers AMD/Intel path where DDC identity commands are not supported).
                string effVer   = !string.IsNullOrWhiteSpace(_fwVersionFromDDC) ? _fwVersionFromDDC
                                : (usingLegacyFallback ? LegacyFWVer : string.Empty);

                if (string.IsNullOrWhiteSpace(_u8ModelName))
                {
                    errMsg = "无法解析bin中的Model信息，请重新选择bin文件。";
                    return false;
                }

                string src = usingLegacyFallback ? "LegacyFallback" : "FW";

                if (!IsFieldMatch(_u8ModelName, effModel))
                {
                    // Fallback: accept fw model as prefix of bin model name.
                    // NVIDIA DP AUX can truncate DDC reads, so the fw name may be a prefix of the full name.
                    string fwTrim  = (effModel ?? string.Empty).Trim();
                    string binTrim = (_u8ModelName ?? string.Empty).Trim();
                    if (fwTrim.Length < 6 || !binTrim.Equals(fwTrim, StringComparison.Ordinal))
                    {
                        frmISP.WriteLog($"FW:{fwTrim},Bin:{binTrim}","debug");
                        errMsg = "Model not match.";
                        return false;
                    }
                }
                // Only validate chip/panel when both sides have the value.
                if (!string.IsNullOrWhiteSpace(effChip) && !string.IsNullOrWhiteSpace(_u8ChipName)
                    && !IsFieldMatch(_u8ChipName, effChip))
                {
                    // Prefix-match fallback: BIN chip "MST9U6" should pass against legacy effChip "MST9U".
                    string fwChipTrim  = (effChip ?? string.Empty).Trim();
                    string binChipTrim = (_u8ChipName ?? string.Empty).Trim();
                    if (!binChipTrim.Equals(fwChipTrim, StringComparison.OrdinalIgnoreCase))
                    {
                        frmISP.WriteLog($"FW:{fwChipTrim},Bin:{binChipTrim}", "debug");
                        errMsg = "Chip not match.";
                        return false;
                    }
                    frmISP.WriteLog("ValidateMonitorVsBin: Chip prefix-match accepted: FW=" + effChip + " BIN=" + _u8ChipName, "debug");
                }

                if (!string.IsNullOrWhiteSpace(effPanel))
                {
                    if (string.IsNullOrWhiteSpace(_u8PanelName))
                    {
                        // BIN has no panel info but monitor expects a specific panel – block upgrade.
                        errMsg = "Panel not match. (BIN has no panel info)";
                        return false;
                    }
                    if (!IsFieldMatch(_u8PanelName, effPanel))
                    {
                        // Same prefix-match fallback as Model: DDC truncation may deliver only a prefix.
                        string fwPanelTrim  = (effPanel ?? string.Empty).Trim();
                        string binPanelTrim = (_u8PanelName ?? string.Empty).Trim();
                        if (fwPanelTrim.Length < 6 || !binPanelTrim.Equals(fwPanelTrim, StringComparison.Ordinal))
                        {
                            frmISP.WriteLog($"FW:{fwPanelTrim},Bin:{binPanelTrim}", "debug");
                            errMsg = "Panel not match.";
                            return false;
                        }
                    }
                }
                // Board name is NOT stored in the BIN ISP info struct (field 4 is u8FWversion);
                // board validation is therefore skipped.

                // Validate FW version prefix (first 4 chars, e.g. "FW.0").
                // FW.033 vs FW.034 => same prefix => allowed.
                // FG.034 vs FW.034 => different prefix => rejected.
                frmISP.WriteLog("ValidateMonitorVsBin: monVer(DDC)=" + (string.IsNullOrWhiteSpace(effVer) ? "(empty)" : effVer)
                       + " binVer=" + (string.IsNullOrWhiteSpace(_u8FWVersion) ? "(empty)" : _u8FWVersion), "debug");
                if (!string.IsNullOrWhiteSpace(_u8FWVersion))
                {
                    if (string.IsNullOrWhiteSpace(effVer))
                    {
                        // BIN has a version but monitor version is unreadable – treat as mismatch.
                        frmISP.WriteLog("ValidateMonitorVsBin: effVer is empty, cannot verify FW version.", "debug");
                        errMsg = "FW Version not match." ;
                        return false;
                    }

                    string normalizedMonVer = NormalizeFwVersionForCompare(effVer);
                    string normalizedBinVer = NormalizeFwVersionForCompare(_u8FWVersion);
                    frmISP.WriteLog("ValidateMonitorVsBin: normalizedMonVer=" + normalizedMonVer + " normalizedBinVer=" + normalizedBinVer, "debug");
                    if (!string.Equals(normalizedMonVer, normalizedBinVer, StringComparison.OrdinalIgnoreCase))
                    {
                        frmISP.WriteLog($"FW:{normalizedMonVer},Bin:{normalizedBinVer}", "debug");
                        errMsg = "FW Version not match.";
                        return false;
                    }
                }
                else
                {
                    frmISP.WriteLog("ValidateMonitorVsBin: FW version check skipped (_u8FWVersion is empty).", "debug");
                }

                return true;
                }
            finally
            {
                prbProgress.MarqueeAnimationSpeed = 0;
                prbProgress.Style = ProgressBarStyle.Blocks;
                prbProgress.Value = 0;
            }
        }

        List<FlashSectorInfo> flashSectorInfo = new List<FlashSectorInfo>();
        private void btnAuto_Click(object sender, EventArgs e)
        {
            frmISP.WriteLog("pktDelayTime: " + _pktDelayTimeText, "debug");
            DDC_SetToolFlag(true);
            if (_binFilePath == String.Empty)
            {
                frmISP.WriteLog("binFilePath is NULL", "error");
                return;
            }
            bool suppressSendFileFail = _suppressSendFileFailMessage;
            _suppressSendFileFailMessage = false;
            if (suppressSendFileFail)
                SetStatusMsg("Ready");
            frmISP.WriteLog("===== Start ISP =====", "info");
            frmISP.WriteLog("binFilePath = " + _binFilePath + ", simI2cCtrl = " + _simI2cCtrl, "info");
            frmISP.WriteLog("[ENV] GPU = " + GetGpuInfoSnapshot(), "info");

            //int totalFailCnt1 = 0;
            int failCnt = 0;
            int checkSumFailCnt = 0;
            int lenFailCnt = 0;

            SetISPStatus(true);
            const int MaxRetryCnt = 30;
            MTKDebugCmd.InitI2CCtrl(_simI2cCtrl);
            Byte[] bin = InitBinaryFile(_binFilePath);
            int retryCnt = 0;
            int rdCheckSum = 0, rdPktCnt = 0;
            int checkSum = 0, sentLen = 0, pktLen = 0;
            int totalBytes = bin.Length;
            frmISP.WriteLog("Bin loaded. totalBytes = " + totalBytes + " (" + (totalBytes / 1024) + " KB)", "info");

            int start = GetTickCount();
            int end = 0;

            if (flashSectorInfo.Count == 0)
            {
                for (int idx = 0; idx < bin.Length; idx += _flashSectorSize)
                {
                    if ((bin.Length - idx) >= _flashSectorSize)
                        pktLen = _flashSectorSize;
                    else
                        pktLen = bin.Length - idx;

                    FlashSectorInfo sector = new FlashSectorInfo();
                    sector.Clear();
                    Array.Copy(bin, idx, sector.data, 0, pktLen);
                    sector.CheckEmpty();
                    flashSectorInfo.Add(sector);
                }
                frmISP.WriteLog("flashSectorInfo built. sectorCount = " + flashSectorInfo.Count + ", sectorSize = " + _flashSectorSize, "debug");
            }
                  
            prbProgress.Maximum = flashSectorInfo.Count;
            prbProgress.Value = 0;
            int imgNum = 0;
            retryCnt = 0;
            DDC_GetISPImgNum(ref imgNum);
            frmISP.WriteLog("DDC_GetISPImgNum -> imgNum = " + imgNum, "debug");
            if (imgNum == 1)
            {
                MessageBox.Show(this, "Update firmware will continue without reboot at startup. Display will reboot after update success.", this.Text);
            }

            IsConnect = false;
            Byte chipType = 0;
            while ((IsConnect == false) && (retryCnt++ < MaxRetryCnt))
            {
                bool rMtk = DDC_CheckMTKDevice(ref chipType);
                IsConnect = rMtk;
                if (IsConnect)
                {
                    frmISP.WriteLog("[Connect] try#" + retryCnt + " hit by DDC_CheckMTKDevice, chipType=" + chipType, "info");
                    break;
                }
                bool rClient = DDC_CheckClientInfo(ref chipType);
                IsConnect = rClient;
                if (IsConnect)
                {
                    frmISP.WriteLog("[Connect] try#" + retryCnt + " hit by DDC_CheckClientInfo, chipType=" + chipType, "info");
                    break;
                }
                bool rPanel = DDC_CheckPanelInfo(ref chipType);
                IsConnect = rPanel;
                if (IsConnect)
                {
                    frmISP.WriteLog("[Connect] try#" + retryCnt + " hit by DDC_CheckPanelInfo, chipType=" + chipType, "info");
                    break;
                }
                frmISP.WriteLog("[Connect] try#" + retryCnt + " ALL FAIL. MTK=" + rMtk + " Client=" + rClient + " Panel=" + rPanel + " chipType=" + chipType, "debug");
                WinIOLib.DelayMs(1000);
            }

            if (IsConnect == false)
            {
                frmISP.WriteLog("[Connect] FAIL after " + (retryCnt - 1) + " retries. chipType=" + chipType, "error");
                SetStatusMsg("Initialize ISP connection fail.");
                SetISPStatus(false, "Initialize ISP connection fail.");
                return;
            }

            _chipType = chipType;
            switch (chipType)
            {
                case 0: { frmISP.WriteLog("MTKChipType=Not Defined", "warn"); } break;
                case 1: { frmISP.WriteLog("MTKChipType=TSUM", "info"); } break;
                case 2: { frmISP.WriteLog("MTKChipType=MST9U", "info"); } break;
            }
          
            Application.DoEvents();
            //init file info            
            int rdSize = 0;
            retryCnt = 0;
            while (retryCnt++ < MaxRetryCnt)
            {
                DDC_SetRecvInfo(bin.Length);
                WinIOLib.DelayMs(500);
                DDC_GetRecvInfo(ref rdSize);
                if (rdSize == bin.Length)
                    break;
                frmISP.WriteLog("[InitFileInfo] try#" + retryCnt + " mismatch. expect=" + bin.Length + " rdSize=" + rdSize, "debug");
            }

            if (rdSize != bin.Length)
            {
                frmISP.WriteLog("[InitFileInfo] FAIL after retries. expect=" + bin.Length + " rdSize=" + rdSize, "error");
                SetStatusMsg("Initialize file Information fail.");
                SetISPStatus(false, "Initialize file Information fail.");
                return;
            }
            frmISP.WriteLog("[InitFileInfo] OK. rdSize=" + rdSize, "info");

            ///////Compare ISP//////////////////
            List<FlashSectorInfo> rootFlash = new List<FlashSectorInfo>();
            Boolean canExecuteCompareISP = false;
            UInt16 imgCheckSum = 0;
            String path = Directory.GetCurrentDirectory();
            path += "\\rootimage.bin";
            if (System.IO.File.Exists(path))
            {
                frmISP.WriteLog("[CompareISP] rootimage.bin found, building rootFlash...", "info");
                Byte[] rootBin = InitBinaryFile(path);
                for (int idx = 0; idx < rootBin.Length; idx += _flashSectorSize)
                {
                    if ((rootBin.Length - idx) >= _flashSectorSize)
                        pktLen = _flashSectorSize;
                    else
                        pktLen = rootBin.Length - idx;

                    for (int sidx = 0; sidx < pktLen; sidx++)
                    {
                        imgCheckSum += rootBin[idx + sidx];
                    }
                    FlashSectorInfo sector = new FlashSectorInfo();
                    sector.Clear();
                    Array.Copy(rootBin, idx, sector.data, 0, pktLen);
                    sector.CheckEmpty();
                    rootFlash.Add(sector);
                }

                UInt32 crc = 0, rdCrc = 0;
                int rdImgOneCheckSum = 0;
                for (int idx = 0; idx < 21; idx++)
                {
                    crc += rootFlash[255].data[0xFE0 + idx];
                }
                for (int retry = 0; retry < 4; retry++)
                {
                    DDC_GetCRC(ref rdCrc);
                    WinIOLib.DelayMs(500);
                    if (crc == rdCrc)
                    {
                        canExecuteCompareISP = true;
                        break;
                    }
                    frmISP.WriteLog("[CompareISP] CRC mismatch retry#" + retry + " expCRC=" + crc.ToString("X8") + " rdCRC=" + rdCrc.ToString("X8"), "debug");
                }
                frmISP.WriteLog("[CompareISP] CRC check result canExecuteCompareISP=" + canExecuteCompareISP + " expCRC=" + crc.ToString("X8") + " rdCRC=" + rdCrc.ToString("X8"), "info");

                if (canExecuteCompareISP)
                {
                    DDC_SetISPImgShadow(imgCheckSum);
                    SetStatusMsg("Initalize Fast ISP procedure...");
                    WinIOLib.DelayMs(17000);
                    for (int retry = 0; retry < 20; retry++)
                    {
                        DDC_GetImgOneCheckSum(ref rdImgOneCheckSum);
                        if (imgCheckSum == rdImgOneCheckSum)
                        {
                            canExecuteCompareISP = true;
                            break;
                        }
                        else
                        {
                            canExecuteCompareISP = false;
                            frmISP.WriteLog("[CompareISP] ImgOneCheckSum mismatch retry#" + retry + " expCS=" + imgCheckSum.ToString("X4") + " rdCS=" + rdImgOneCheckSum.ToString("X4"), "debug");
                            WinIOLib.DelayMs(1000);
                        }
                    }
                    frmISP.WriteLog("[CompareISP] FastISP shadow result canExecuteCompareISP=" + canExecuteCompareISP + " imgCheckSum=" + imgCheckSum.ToString("X4"), "info");
                }
            }
            else
            {
                frmISP.WriteLog("[CompareISP] rootimage.bin NOT found, full write mode.", "info");
            }

            //     SetStatusMsg("Transfer Parameter adjusting ...");
            int maxPktLen = 0, pktDelayTime = 0;
            int maxDelayHint = Math.Max(Convert.ToInt32(_pktDelayTimeText) * 3, 300);
            AdjustPacketParam(defaultPktLen, ref maxPktLen, maxDelayHint, ref pktDelayTime);
            frmISP.WriteLog("maxPktLen = " + Convert.ToString(maxPktLen) + ", pktDelayTime = " + Convert.ToString(pktDelayTime) + "", "info");
#if DEBUG
    if (Convert.ToInt32(_pktDelayTimeText) > 0)
    {
        pktDelayTime = Convert.ToInt32(_pktDelayTimeText);
        frmISP.WriteLog("[DEBUG] pktDelayTime overridden by _pktDelayTimeText = " + pktDelayTime, "debug");
    }
#endif
            retryCnt = 0;
            while (retryCnt++ < MaxRetryCnt)
            {
                DDC_SetRecvInfo(bin.Length);
                WinIOLib.DelayMs(500);
                DDC_GetRecvInfo(ref rdSize);
                if (rdSize == bin.Length)
                    break;
                frmISP.WriteLog("[ReInitFileInfo] try#" + retryCnt + " mismatch. expect=" + bin.Length + " rdSize=" + rdSize, "debug");
            }
            //Send data          
            FlashSectorInfo si;
            FlashSectorInfo rootSi;
            Boolean IsDiffSector = false;
            start = GetTickCount();
            int wroteSectorCnt = 0;
            retryCnt = 0;
            frmISP.WriteLog("===== Begin transfer. totalSector=" + flashSectorInfo.Count + " fastISP=" + canExecuteCompareISP + " =====", "info");
            for (int cnt = 0; cnt < flashSectorInfo.Count; cnt++)
            {
                IsDiffSector = false;
                si = flashSectorInfo[cnt];

                if (canExecuteCompareISP)
                {
                    rootSi = rootFlash[cnt];
                    for (int sIdx = 0; sIdx < 4096; sIdx++)
                    {
                        if (si.data[sIdx] != rootSi.data[sIdx])
                        {
                            IsDiffSector = true;
                            break;
                        }
                    }

                    if (IsDiffSector)
                    {
                        checkSum += SendSectorData(si, failCnt, maxPktLen, pktDelayTime);
                        checkSum &= 0xFFFF;
                        wroteSectorCnt++;
                    }
                    else
                    {
                        //bypass empty sector
                        if (SetSentInfo(sentLen + _flashSectorSize) == false)
                        {
                            frmISP.WriteLog("[Transfer] SetSentInfo(bypass-diff) FAIL at sector " + cnt + " sentLen=" + sentLen, "error");
                            if (!suppressSendFileFail)
                            {
                                SetStatusMsg("Send file fail.");
                                SetISPStatus(false, "Send file fail.");
                            }
                            else
                            {
                                SetISPStatus(false);
                            }
                            _suppressSendFileFailMessage = true;
                            return;
                        }
                        checkSum = 0;
                        //WinIOLib.DelayMs(50);
                    }
                }
                else
                {
                    if (si.IsEmpty)
                    {
                        //bypass empty sector
                        if (SetSentInfo(sentLen + _flashSectorSize) == false)
                        {
                            frmISP.WriteLog("[Transfer] SetSentInfo(bypass-empty) FAIL at sector " + cnt + " sentLen=" + sentLen, "error");
                            if (!suppressSendFileFail)
                            {
                                SetStatusMsg("Send file fail.");
                                SetISPStatus(false, "Send file fail.");
                            }
                            else
                            {
                                SetISPStatus(false);
                            }
                            _suppressSendFileFailMessage = true;
                            return;
                        }
                        checkSum = 0;
                    }
                    else
                    {
                        checkSum += SendSectorData(si, failCnt, maxPktLen, pktDelayTime);
                        checkSum &= 0xFFFF;
                    }
                }

                sentLen += _flashSectorSize;

                DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                if ((sentLen != rdPktCnt) || (checkSum != rdCheckSum))
                {
                    DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                }
                if ((sentLen != rdPktCnt) || (checkSum != rdCheckSum))
                {
                    DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                }
                if ((sentLen != rdPktCnt) || (checkSum != rdCheckSum))
                {
                    DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt, 500);
                }
                if (sentLen != rdPktCnt)
                {
                    lenFailCnt++;
                }
                if (checkSum != rdCheckSum)
                {
                    checkSumFailCnt++;
                }
                if ((sentLen != rdPktCnt) || (checkSum != rdCheckSum))
                {
                    // 仅在 mismatch 时打印诊断三元组，避免高频日志拖垮 UI 线程
                    frmISP.WriteLog("[Transfer] MISMATCH sector=" + cnt
                        + " expLen=" + sentLen + " rdLen=" + rdPktCnt
                        + " expCS=" + checkSum.ToString("X4") + " rdCS=" + rdCheckSum.ToString("X4")
                        + " retry=" + retryCnt + " failCnt=" + failCnt, "warn");

                    retryCnt++;
                    failCnt++;
                    sentLen -= _flashSectorSize;
                    checkSum = 0;

                    if (sentLen < 0)
                        sentLen = 0;

                    if (cnt >= 0)
                        cnt -= 1;

                    //reset sent info
                    if (SetSentInfo(sentLen) == false)
                    {
                        frmISP.WriteLog("[Transfer] SetSentInfo(reset) FAIL. sentLen=" + sentLen + " sector=" + cnt, "error");
                        if (!suppressSendFileFail)
                        {
                            SetStatusMsg("Send file fail.");
                            SetISPStatus(false, "Send file fail.");
                        }
                        else
                        {
                            SetISPStatus(false);
                        }
                        _suppressSendFileFailMessage = true;
                        return;
                    }

                    if (retryCnt > MaxRetryCnt)
                    {
                        frmISP.WriteLog("[Transfer] FAIL: retryCnt(" + retryCnt + ") > MaxRetryCnt(" + MaxRetryCnt + ")."
                            + " lenFail=" + lenFailCnt + " csFail=" + checkSumFailCnt + " failCnt=" + failCnt, "error");
                        if (!suppressSendFileFail)
                        {
                            SetStatusMsg("Send file fail.");
                            SetISPStatus(false, "Send file fail.");
                        }
                        else
                        {
                            SetISPStatus(false);
                        }
                        _suppressSendFileFailMessage = true;
                        return;
                    }
                }
                else
                {
                    retryCnt = 0;
                }

                if (cnt < 0)
                    prbProgress.Value = 0;
                else
                    prbProgress.Value = cnt;
                end = GetTickCount() - start;

                if (sentLen >= bin.Length)
                    sentLen = bin.Length;

                if ((cnt == 0) && (end > 4000)) //terminate ISP when 4K bytes per sec large than 4 sec 
                {
                    frmISP.WriteLog("[Transfer] first sector slow: end=" + end + "ms (threshold 4000ms). NOT terminated (commented out).", "warn");
                    //SetStatusMsg("Procedure is terminated, because data transfer speed is abnormal.");
                    //btnUploadFlash.Enabled = true;
                    //SetISPStatus(false);
                    //return;
                }

                SetStatusMsg("Updating firmware..., " + "Elapsed Time : " + (end / 1000).ToString() + " s");
                Application.DoEvents();
            }

            UInt16 fileCheckSum = 0;
            frmISP.WriteLog("TransferTime = " + end.ToString() + " ms, wroteSector=" + wroteSectorCnt + "/" + flashSectorInfo.Count
                + " lenFail=" + lenFailCnt + " csFail=" + checkSumFailCnt + " failCnt=" + failCnt, "info");
#if AUTO_SEARCH
    Transfer_Time = end;
    Total_Transfer_Time += (ulong)end;
#endif
            for (int idx = 0; idx < bin.Length; idx++)
            {
                fileCheckSum += bin[idx];
            }
            frmISP.WriteLog("fileCheckSum = " + fileCheckSum.ToString("X4"), "debug");

            //Execute ISP   
            DDC_RunISP(fileCheckSum);
            frmISP.WriteLog("DDC_RunISP issued. fileCheckSum=" + fileCheckSum.ToString("X4"), "info");
            WinIOLib.DelayMs(1000);

            prbProgress.Value = flashSectorInfo.Count;
            Application.DoEvents();
            end = GetTickCount() - start;
            //         SetStatusMsg("Sent Bytes : " + sentLen.ToString() + ", " + "Elapsed Time : " + end.ToString() + " ms");
            //SetStatusMsg("Sent Bytes : " + sentLen.ToString() + ", " + "Elapsed Time : " + end.ToString() + " ms");
            // SetStatusMsg("Read Bin OK. Model: " + _u8ModelName);
            Application.DoEvents();

            //check ddc is alive until system update flash finish
            retryCnt = 0;
            UInt32 tmpCRC = 0x89ABCDEF;
            int status = 0;
            int binLen = bin.Length / 1024;
            int waitUpdateFWCnt = 120;
            if (binLen <= 512)
            {
                waitUpdateFWCnt = 120;
            }
            else if (binLen <= 1024)
            {
                waitUpdateFWCnt = 240;
            }
            else if (binLen <= 2048)
            {
                waitUpdateFWCnt = 480;
            }
            else if (binLen <= 4096)
            {
                waitUpdateFWCnt = 960;
            }
            frmISP.WriteLog("[WaitUpdate] binLen=" + binLen + "KB, waitUpdateFWCnt=" + waitUpdateFWCnt + "s", "info");

            while (retryCnt++ < waitUpdateFWCnt)
            {
                WinIOLib.DelayMs(1000);

                SetStatusMsg("Updating firmware..., " + "Elapsed Time : " + (end / 1000).ToString() + " s");
                Application.DoEvents();
                //int pktDelayTime = Convert.ToInt32(txtPktDelayTime.Text);
                IsConnect = DDC_CheckMTKDevice(ref chipType/*, pktDelayTime*/);
                if (IsConnect == false)
                    continue;
                //DDC_GetSentInfo(ref rdCheckSum, ref rdPktCnt);
                DDC_GetISPStatus(ref status/*, pktDelayTime*/);
                if ((status == 2) || (status == 1))//0:Init, 1: fail, 2: success
                {
                    frmISP.WriteLog("[WaitUpdate] break at try#" + retryCnt + " status=" + status, "info");
                    break;
                }
            }

            //how to check fw is update success? 
            end = GetTickCount() - start;
            if (status == 2)
            {
                frmISP.WriteLog("===== ISP SUCCESS. status=" + status + " totalTime=" + end + "ms =====", "info");
                // Do NOT call Connect() here: the monitor firmware is rebooting and DDC is
                // not ready yet, which would cause a spurious "not compatible" error message.
                SetStatusMsg("Update firmware success. Please reset the power of display.");
                //MessageBox.Show(this, "Update firmware success. Please reset the power of display.", this.Text);
            }
            else
            {
                frmISP.WriteLog("===== ISP FAIL. status=" + status + " totalTime=" + end + "ms"
                    + " lenFail=" + lenFailCnt + " csFail=" + checkSumFailCnt + " failCnt=" + failCnt + " =====", "error");
                SetStatusMsg("Update firmware fail, " + "Elapsed Time : " + (end / 1000).ToString() + " s", true);
            }

            SetISPStatus(false);

            if (status == 2)
            {
                MessageBox.Show(this, "Update firmware success. Please reset the power of display.", this.Text);
                DDC_SetISPReboot();
            }
        }

        Boolean CheckISPIdleStatus()
        {
            int status = 0;
            int cnt = 0;
            while ((status != 0x99) && (cnt++ < 10))
            {
                DDC_GetISPStatus(ref status, 100);
            }

            if (status == 0x99)
                return true;
            else
                return false;
        }

        private static string resultFolder = System.IO.Directory.GetCurrentDirectory() + "\\";
        private static System.IO.StreamWriter _logWriter = null;
        private static string _logPath = null;
        [Conditional("DEBUG")]
        public static void WriteLog(string str, string fileName)
        {
            if (!System.IO.Directory.Exists(resultFolder))
                System.IO.Directory.CreateDirectory(resultFolder);

            string path = resultFolder + (fileName == "" ? "" : fileName + "_") + DateTime.Now.ToString("yyyy-MM-dd") + ".log";
            try
            {
                if (_logWriter == null || _logPath != path)
                {
                    if (_logWriter != null)
                    {
                        _logWriter.Flush();
                        _logWriter.Close();
                    }
                    _logPath = path;
                    var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                    _logWriter = new System.IO.StreamWriter(stream);
                }
                _logWriter.WriteLine(DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss.fff: ") + str);
                _logWriter.Flush();
            }
            catch (IOException)
            {
                // Ignore logging failures caused by file locks.
            }
        }

        public static Byte[] InitBinaryFile(String filepath)
        {
            Byte[] rdBuf = null;

            if (File.Exists(filepath) == false)
                return null;

            UInt32 lastDataPosition = 0;
            using (BinaryReader reader = new BinaryReader(File.Open(filepath, FileMode.Open)))
            {
                rdBuf = reader.ReadBytes((int)reader.BaseStream.Length);
            }

            UInt32 cnt = 0;
            UInt16 checkSum = 0;
            Byte data = 0;
            while (cnt < rdBuf.Length)
            {
                data = rdBuf[cnt];
                checkSum += data;
                if (data != 0xFF)
                {
                    lastDataPosition = cnt;
                }
                cnt++;
            }

            if ((lastDataPosition & 0xFFF) > 0)
                lastDataPosition = (lastDataPosition + 0x1000) & 0xFFF000;
            UInt32 _progSize = 0;
            _progSize = lastDataPosition;
            Byte[] wrBuf = new Byte[_progSize];
            for (int idx = 0; idx < wrBuf.Length; idx++)
            {
                wrBuf[idx] = 0xFF;
            }
            int copyLen = rdBuf.Length;
            if (wrBuf.Length < rdBuf.Length)
            {
                copyLen = wrBuf.Length;
                System.Windows.Forms.MessageBox.Show("rd len : " + rdBuf.Length.ToString() + ", wr len" + wrBuf.Length.ToString());
            }
            Array.Copy(rdBuf, wrBuf, copyLen);
            return wrBuf;
        }

        public static byte[] ReadBinSection(string filePath, long offset, int length)
        {
            if (!File.Exists(filePath))
                throw new FileNotFoundException("Bin file not found.", filePath);


            byte[] buffer = new byte[length];
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                if (offset > fs.Length - 1)
                    throw new ArgumentOutOfRangeException("offset", "Offset is beyond file length.");

                fs.Seek(offset, SeekOrigin.Begin);
                int bytesRead = fs.Read(buffer, 0, length);
                if (bytesRead < length)
                {
                    // If file is shorter than expected, resize buffer
                    Array.Resize(ref buffer, bytesRead);
                }
            }
            return buffer;
        }

        /// <summary>
        /// 通过 WMI 采集所有显卡信息快照，用于诊断不同 GPU 厂商(N/A/I)下的 ISP 行为差异。
        /// 该方法自带异常拦截，任何失败都不影响主升级流程。
        /// </summary>
        private string GetGpuInfoSnapshot()
        {
            var sb = new System.Text.StringBuilder();
            try
            {
                using (var searcher = new System.Management.ManagementObjectSearcher(
                    "SELECT Name, AdapterCompatibility, DriverVersion, AdapterRAM, " +
                    "VideoProcessor, PNPDeviceID, Status FROM Win32_VideoController"))
                {
                    int idx = 0;
                    foreach (var mo in searcher.Get())
                    {
                        idx++;
                        string name = SafeWmi(mo, "Name");
                        string vendor = SafeWmi(mo, "AdapterCompatibility"); // 厂商: NVIDIA / Advanced Micro Devices / Intel
                        string driverVer = SafeWmi(mo, "DriverVersion");
                        string ramStr = SafeWmi(mo, "AdapterRAM");
                        string gpuProc = SafeWmi(mo, "VideoProcessor");
                        string pnp = SafeWmi(mo, "PNPDeviceID");
                        string status = SafeWmi(mo, "Status");

                        // 归一化厂商标识，方便日志里一眼区分 N/A/I 卡
                        string vendorTag = ClassifyGpuVendor(vendor, name, pnp);

                        string ramMB = "N/A";
                        long ramBytes;
                        if (long.TryParse(ramStr, out ramBytes) && ramBytes > 0)
                            ramMB = (ramBytes / 1024 / 1024) + "MB";

                        sb.Append("[GPU#" + idx + "] tag=" + vendorTag
                            + " name=" + name
                            + " vendor=" + vendor
                            + " driver=" + driverVer
                            + " ram=" + ramMB
                            + " proc=" + gpuProc
                            + " status=" + status
                            + " pnp=" + pnp
                            + " | ");
                    }
                    if (idx == 0)
                        sb.Append("No Win32_VideoController found.");
                }
            }
            catch (Exception ex)
            {
                sb.Append("GetGpuInfoSnapshot EXCEPTION: " + ex.Message);
            }
            return sb.ToString();
        }

        /// <summary>WMI 字段安全读取，避免某些字段为 null 抛异常。</summary>
        private string SafeWmi(System.Management.ManagementBaseObject mo, string prop)
        {
            try
            {
                var v = mo[prop];
                return v == null ? "null" : v.ToString().Trim();
            }
            catch
            {
                return "err";
            }
        }

        /// <summary>根据厂商字符串/名称/PNPDeviceID 的 VEN_ 归类为 N/A/I/Other。</summary>
        private string ClassifyGpuVendor(string vendor, string name, string pnp)
        {
            string s = ((vendor ?? "") + " " + (name ?? "")).ToUpperInvariant();
            string p = (pnp ?? "").ToUpperInvariant();

            // 优先用 PCI VendorID 判断，最准
            if (p.Contains("VEN_10DE")) return "N(NVIDIA)";
            if (p.Contains("VEN_1002")) return "A(AMD)";
            if (p.Contains("VEN_8086")) return "I(Intel)";

            // 退化到字符串匹配
            if (s.Contains("NVIDIA")) return "N(NVIDIA)";
            if (s.Contains("AMD") || s.Contains("ADVANCED MICRO") || s.Contains("RADEON") || s.Contains("ATI")) return "A(AMD)";
            if (s.Contains("INTEL")) return "I(Intel)";

            return "Other";
        }
    }
}
