diff --git a/WIN10_WinISP_SrcCode_For_MOKA_20250416_1/WinISP/BaseClass/MTKDebugCmd.cs b/WIN10_WinISP_SrcCode_For_MOKA_20250416_1/WinISP/BaseClass/MTKDebugCmd.cs
index 7fe420a..23b9753 100644
--- a/WIN10_WinISP_SrcCode_For_MOKA_20250416_1/WinISP/BaseClass/MTKDebugCmd.cs
+++ b/WIN10_WinISP_SrcCode_For_MOKA_20250416_1/WinISP/BaseClass/MTKDebugCmd.cs
@@ -1,4 +1,6 @@
using System;
+using System.Linq;
+using WinISP;
namespace MTK
{
@@ -72,6 +74,12 @@ namespace MTK
strData = "";
if (_i2cCtrl.ReadData(recvBuf, wrCmds) == true)
{
+ frmISP.WriteLog(
+ $"[DDC_Read RAW] cmd=0x{data[1]:X2} recvLen={recvLen} " +
+ $"raw=[{string.Join(" ", recvBuf.Select(b => b.ToString("X2")))}]",
+ "debug");
+
+ // ────────
for (int i = 0; i < recvBuf.Length; ++i)
strData += recvBuf[i].ToString("X2") + " ";
@@ -102,6 +110,29 @@ namespace MTK
return null;
}
+ ///
+ /// 从 DDC 0x6F 继续读取数据(不重新发送命令)。
+ /// 用于 N卡 DP AUX 通道 16 字节限制导致截断时的分段续读。
+ /// 在 DDC_Read 成功返回后立即调用,尝试读取响应缓冲区中尚未消费的剩余字节。
+ ///
+ /// 要读取的字节数
+ /// 读取到的字节数组,失败返回 null
+ public static Byte[] DDC_ReadContinue(int recvLen)
+ {
+ const Byte dest = 0x6F;
+ Byte[] wrCmds = { dest };
+ Byte[] recvBuf = new Byte[recvLen];
+ if (_i2cCtrl.ReadData(recvBuf, wrCmds))
+ {
+ frmISP.WriteLog(
+ $"[DDC_ReadContinue] rdLen={recvLen} raw=[{string.Join(" ", recvBuf.Select(b => b.ToString("X2")))}]",
+ "debug");
+ return recvBuf;
+ }
+ frmISP.WriteLog($"[DDC_ReadContinue] rdLen={recvLen} read failed", "debug");
+ return null;
+ }
+
private static Byte CalCheckSum(Byte[] data, int startIdx = 0)
{
Byte chkSum = 0;
diff --git a/WIN10_WinISP_SrcCode_For_MOKA_20250416_1/WinISP/FormISP_MSI.cs b/WIN10_WinISP_SrcCode_For_MOKA_20250416_1/WinISP/FormISP_MSI.cs
index 2dcb4d0..12a876e 100644
--- a/WIN10_WinISP_SrcCode_For_MOKA_20250416_1/WinISP/FormISP_MSI.cs
+++ b/WIN10_WinISP_SrcCode_For_MOKA_20250416_1/WinISP/FormISP_MSI.cs
@@ -275,7 +275,7 @@ namespace WinISP
if (display) MessageBox.Show(msg, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
- ///
+ ///
/// Send a 2-byte DDC command and parse the ASCII string returned by the monitor firmware.
/// recv layout: [hdr0][hdr1][char0][char1]...[\0]
///
@@ -283,11 +283,15 @@ namespace WinISP
{
Byte[] cmd = { 0xCC, subCmd };
int[] readLens = { 36, 32, 24, 20, 16, 12, 8 };
- string bestPartial = String.Empty;
+ string bestPartial = string.Empty;
+ bool segReadTried = false; // 分段续读只尝试一次
+
+ // N卡驱动已知的单次有效数据上限(超过此长度才信任 isComplete 判断)
+ // 低于或等于此值时,即使找到\0也可能是驱动填充,不能轻易认为完整
+ const int NV_SUSPECT_TRUNCATE_LEN = 15;
foreach (int readLen in readLens)
{
- // Retry each readLen up to 3 times to handle NVIDIA DP AUX bus instability.
for (int attempt = 0; attempt < 3; attempt++)
{
Byte[] recv = MTKDebugCmd.DDC_Read(cmd, readLen, delayTime);
@@ -297,16 +301,22 @@ namespace WinISP
continue;
}
- // Error response pattern in logs: 6E 80 BE ...
+ // Error response: 6E 80 BE ...
if (recv[1] == 0x80)
{
WinIOLib.DelayMs(20);
continue;
}
- int len = 0;
- while ((2 + len) < recv.Length && recv[2 + len] != 0)
- len++;
+ int reportedDataLen = recv[1] & 0x7F; // DDC响应头中声明的数据长度
+
+ // 先按原始方式扫描整个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)
{
@@ -315,28 +325,137 @@ namespace WinISP
}
string value = System.Text.Encoding.ASCII.GetString(recv, 2, len).Trim();
+ value = TrimNonStandardDdcSuffix(value);
if (string.IsNullOrWhiteSpace(value))
{
WinIOLib.DelayMs(20);
continue;
}
- // Only return when the null terminator was found inside the buffer (complete string).
- bool isComplete = (2 + len) < recv.Length;
+ // 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;
- // Truncated (buffer full, no null found) - keep as best partial and keep retrying.
+ // ── 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);
}
}
- // Return longest partial result if we never got a complete one.
+ // 所有 readLen 都未能读到完整值,返回最长截断结果
+ // TryExpandField 负责前缀补全
+ if (!string.IsNullOrEmpty(bestPartial))
+ {
+ frmISP.WriteLog(
+ $"[DDC] cmd=0x{subCmd:X2} all readLens exhausted, best='{bestPartial}'",
+ "debug");
+ }
+
return bestPartial;
}
+ ///
+ /// 滤除 DDC 字符串尾部的非标准字符。
+ /// AMD ADL DDC 偶尔返回损坏的响应头(reportedDataLen 被放大),
+ /// 导致字符串尾部夹带垃圾字节(如 checksum + 下一字节)。
+ /// 监视器字段名合法字符集:[A-Za-z0-9 _.\\-/]
+ ///
+ 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 bool TryReadMonitorIdentity(int retryCount = 5, int delayMs = 80)
{
// Name commands are more stable in non-tool mode on some GPU/DDC paths.
@@ -538,37 +657,77 @@ namespace WinISP
///
/// AMD/Intel 友好的型号读取:带响应有效性校验 + 多轮重试。
/// AMD ADL DDC 读命中率低(约 4 错 1 对),需要足够重试次数才能读到完整字符串。
+ /// 采用"众数策略"而非"取最长":AMD 偶尔返回损坏的响应头会拉长字符串,
+ /// 统计上正确的值出现频率最高,长度众数 + 值众数投票能过滤掉偶发损坏。
///
private string ReadMonitorFieldRobust(byte subCmd, int expectLen, int maxRounds = 30)
{
- string best = string.Empty;
- int stableCount = 0; // 连续读到相同最长值的次数
+ var candidates = new System.Collections.Generic.List();
+ string bestEver = string.Empty;
for (int round = 0; round < maxRounds; round++)
{
string v = DDC_GetMonitorName(subCmd, 300);
if (!string.IsNullOrWhiteSpace(v))
{
- if (v.Length > best.Length)
+ // ── AMD 响应头损坏防御 ───────────────────────────────
+ // AMD ADL DDC 偶尔返回损坏的响应头(reportedDataLen 被放大),
+ // 导致字符串尾部夹带垃圾字节。虽然 DDC_GetMonitorName 已做
+ // 尾部字符过滤,但仍需在调用方以 expectLen 作为硬上限:
+ // 任何超过预期最大长度的值一律拒绝,不作为有效候选。
+ if (v.Length > expectLen)
{
- best = v;
- stableCount = 0; // 读到更长的,重置稳定计数
- frmISP.WriteLog($"[AMD-Identity] cmd=0x{subCmd:X2} round#{round} got='{v}' (len={v.Length})", "debug");
- }
- else if (v.Length == best.Length && v == best)
- {
- stableCount++; // 连续读到相同的完整值
+ 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 响应头损坏防御 ──────────────────────────
- // 完整性判断(二选一即退出):
- // 1) 读到的长度已接近期望最大长度 → 认为读全
- // 2) 同一个完整值连续稳定读到 2 次 → 认为读全(应对实际长度 < expectLen 的字段)
- if (best.Length >= expectLen - 1 || stableCount >= 2)
- break;
+ 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);
}
- return best;
+
+ 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()
@@ -1567,7 +1726,7 @@ namespace WinISP
// 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.StartsWith(fwTrim, StringComparison.Ordinal))
+ if (fwTrim.Length < 6 || !binTrim.Equals(fwTrim, StringComparison.Ordinal))
{
frmISP.WriteLog($"FW:{fwTrim},Bin:{binTrim}","debug");
errMsg = "Model not match.";
@@ -1581,7 +1740,7 @@ namespace WinISP
// 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.StartsWith(fwChipTrim, StringComparison.OrdinalIgnoreCase))
+ if (!binChipTrim.Equals(fwChipTrim, StringComparison.OrdinalIgnoreCase))
{
frmISP.WriteLog($"FW:{fwChipTrim},Bin:{binChipTrim}", "debug");
errMsg = "Chip not match.";
@@ -1603,7 +1762,7 @@ namespace WinISP
// 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.StartsWith(fwPanelTrim, StringComparison.Ordinal))
+ if (fwPanelTrim.Length < 6 || !binPanelTrim.Equals(fwPanelTrim, StringComparison.Ordinal))
{
frmISP.WriteLog($"FW:{fwPanelTrim},Bin:{binPanelTrim}", "debug");
errMsg = "Panel not match.";