From dd52469377badb6b78436e599731b51e1ab420e4 Mon Sep 17 00:00:00 2001 From: ZXCLI Date: Wed, 19 Aug 2026 15:55:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=92=E6=A1=A3=E5=88=86=E6=9E=90=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E5=88=B0=20analysis/reverse=5Ftools=EF=BC=88compare?= =?UTF-8?q?=5Flive/verify=5Fnuc/verify=5Ffull/parse=5Fddt/parse=5Fpcap?= =?UTF-8?q?=EF=BC=89=EF=BC=8C=E6=9B=B4=E6=96=B0=20session=5Fstate=20?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analysis/reverse_tools/compare_live.py | 53 +++++++++++ analysis/reverse_tools/parse_ddt.py | 85 +++++++++++++++++ analysis/reverse_tools/parse_pcap.py | 31 ++++++ analysis/reverse_tools/verify_full.py | 126 +++++++++++++++++++++++++ analysis/reverse_tools/verify_nuc.py | 94 ++++++++++++++++++ analysis/session_state.md | 10 +- 6 files changed, 395 insertions(+), 4 deletions(-) create mode 100644 analysis/reverse_tools/compare_live.py create mode 100644 analysis/reverse_tools/parse_ddt.py create mode 100644 analysis/reverse_tools/parse_pcap.py create mode 100644 analysis/reverse_tools/verify_full.py create mode 100644 analysis/reverse_tools/verify_nuc.py diff --git a/analysis/reverse_tools/compare_live.py b/analysis/reverse_tools/compare_live.py new file mode 100644 index 0000000..ff5e9a1 --- /dev/null +++ b/analysis/reverse_tools/compare_live.py @@ -0,0 +1,53 @@ +import struct, sys + +# compare demo3 live capture vs official capture on the same static scene +b = open(r"C:\Project\MAG160C\build-artifacts\demo3_frame_capture.bin", "rb").read() +off = 0 +live = struct.unpack_from("<19200H", b, off); off += 38400 +ref = struct.unpack_from("<19200H", b, off); off += 38400 +nuc = struct.unpack_from("<19200H", b, off); off += 38400 +gray = b[off:off+19200]; off += 19200 +lut = b[off:off+1024]; off += 1024 +meta = struct.unpack_from("<8i", b, off) +print("demo3: win=[%d,%d] stats=(%d..%d m%d s%d) shutter=%d sel=%d" % meta) +print("demo3: live_mean=%.0f ref_mean=%.0f nuc_mean=%.0f" % (sum(live)/19200, sum(ref)/19200, sum(nuc)/19200)) + +d = r"C:\Project\MAG160C\analysis\pairs_final_20260813" +def load(path, fmt, n): + b = open(path, "rb").read() + return struct.unpack_from("<%d%s" % (n, fmt), b, 0) + +win = open(d + r"\pair_000.win", "rb").read() +hi, lo, fmax, fmin, mean, std, axmax, axmin = struct.unpack_from("<8I", win, 0) +gray_o = load(d + r"\pair_000.gray", "B", 320*240) +raw_o = load(d + r"\pair_000.raw", "H", 19200) +lut_o = open(d + r"\pair_000.lut", "rb").read() +print("official: win=[%d,%d] stats=(%d..%d m%d s%d)" % (lo, hi, fmin, fmax, mean, std)) +print("official: raw_mean=%.0f" % (sum(raw_o)/19200)) + +# gray comparison: demo3 gray160 vs official gray320 downsampled (2x parity) +g_o160 = [gray_o[y*320 + x] for y in range(0, 240, 2) for x in range(0, 320, 2)] +import collections +hd = collections.Counter(gray) +ho = collections.Counter(g_o160) +# histogram correlation +allk = set(hd) | set(ho) +n = sum(hd.values()) +# compare histograms as distributions +import math +def corr(a, b): + ka = set(a); kb = set(b) + ks = ka | kb + ma = sum(a[k] for k in ks)/n + mb = sum(b[k] for k in ks)/n + num = sum((a.get(k,0)-ma)*(b.get(k,0)-mb) for k in ks) + da = math.sqrt(sum((a.get(k,0)-ma)**2 for k in ks)) + db = math.sqrt(sum((b.get(k,0)-mb)**2 for k in ks)) + return num/(da*db) if da and db else 0 +print("gray histogram correlation: %.4f" % corr(hd, ho)) +print("demo3 gray range: %d..%d, official: %d..%d" % (min(gray), max(gray), min(g_o160), max(g_o160))) +# LUT comparison +dl = sum(1 for i in range(1024) if lut[i] != lut_o[i]) +print("demo3 lut vs official lut diff: %d/1024" % dl) +print("demo3 lut[0:8]:", list(lut[:8]), " official:", list(lut_o[:8])) +print("demo3 lut[1000:]:", list(lut[1000:]), " official:", list(lut_o[1000:])) diff --git a/analysis/reverse_tools/parse_ddt.py b/analysis/reverse_tools/parse_ddt.py new file mode 100644 index 0000000..8450623 --- /dev/null +++ b/analysis/reverse_tools/parse_ddt.py @@ -0,0 +1,85 @@ +import struct, sys, os + +path = r"C:\Project\MAG160C\analysis\ida\Core160043865.ddt" +b = open(path, "rb").read() +print(f"size: {len(b)}") + +def u32(o): return struct.unpack_from(">1)*2 if False else thr_off + (nsegs-1)*npix*2 + (c5c//2)*2 + print(f"EP{e}: thr@{thr_off:#x} gain@{g_off:#x} T={T[e]}") + t0 = i16(thr_off), i16(thr_off+2), i16(thr_off+4), i16(thr_off+19200*2) + g0 = u16(g_off), u16(g_off+2), u16(g_off+4), u16(g_off+19200*2), u16(g_off+2*19200*2) + print(f" thr[0..3]={t0} thr[1st pix row start]={t0[3]}") + print(f" gain seg0[0..3]={g0[0:4]} gain seg1[0]={g0[4]}") + ep += u26 + +# blind records: after all endpoint blocks +blind_off = ep +print(f"blind records at 0x{blind_off:x}, total {blind_total} bytes") +for e in range(count-1): + n = D[e] + print(f"EP{e} blind count={n}") + for i in range(min(n, 8)): + r = blind_off + e*0 + i*40 + target, typ = u32(r), u32(r+4) + neigh = [u32(r + 8 + j*4) for j in range(8)] + print(f" rec[{i}]: target={target} type={typ} neigh={neigh[:typ-2 if typ>=3 else 8]}") + blind_off += n*40 + +# check: what's after blind records (trailer) +print(f"trailer at 0x{blind_off:x}:") +for i in range(8): + print(f" +{i*4}: {u32(blind_off+i*4):#010x}") + +# compare with the captured working tables +gain = open(r"C:\Project\MAG160C\build-artifacts\mag160c_official_nuc_gain.bin", "rb").read() +thr = open(r"C:\Project\MAG160C\build-artifacts\mag160c_official_nuc_thr.bin", "rb").read() +print(f"\ncaptured working tables: gain={len(gain)} thr={len(thr)}") +print(f"thr[0:4]={[i16(i*2) for i in range(4)]} (from thr.bin)") +print(f"gain[0:4]={[u16(i*2) for i in range(4)]}") +print(f"gain[19200*2:19200*2+4]={[u16(19200*2+i*2) for i in range(4)]} (seg1)") + +# find which endpoint the working table corresponds to: interpolate check later in C diff --git a/analysis/reverse_tools/parse_pcap.py b/analysis/reverse_tools/parse_pcap.py new file mode 100644 index 0000000..403a225 --- /dev/null +++ b/analysis/reverse_tools/parse_pcap.py @@ -0,0 +1,31 @@ +import struct, sys + +# parse usbpcap file, extract bulk transfer payloads on EP 0x81, +# find 0x1bb1b11b markers and dump tail words +path = r"C:\Project\MAG160C\analysis\captures\official_hand.pcap" +data = open(path, "rb").read() +magic = data[:4] +print("pcap magic:", magic.hex()) +# USBPcap: linktype 249? global header 24 bytes: magic, vMaj, vMin, tz, sigfigs, snaplen, linktype +lt = struct.unpack_from(" 120: break +print("total markers scanned:", count) diff --git a/analysis/reverse_tools/verify_full.py b/analysis/reverse_tools/verify_full.py new file mode 100644 index 0000000..2dda20e --- /dev/null +++ b/analysis/reverse_tools/verify_full.py @@ -0,0 +1,126 @@ +import struct + +d = r"C:\Project\MAG160C\analysis\pairs_verify_20260813" +npix = 19200 + +def load(path, fmt, n): + b = open(path, "rb").read() + return struct.unpack_from("<%d%s" % (n, fmt), b, 0) + +# 1) verify interpolation: thrw vs interp(thr0, thr1, t) +thr0 = load(d + r"\pair_000.thr0", "h", 19200*2 + 512) # (nsegs-1)*npix + 1024/2 +thr1 = load(d + r"\pair_000.thr1", "h", 19200*2 + 512) +thrw = load(d + r"\pair_000.thrw", "h", 19200*2) +T = [8304, 18390, 28495, 33637, 38730, 47664] +dev54 = 29289 +sel = 2 +t = ((dev54 - T[sel]) << 12) // (T[sel+1] - T[sel]) +print("t =", t) +diff = 0 +for i in range(19200*2): + a = thr0[i] + b = thr1[i] + w = thrw[i] + calc = a + ((b - a) * t >> 12) + # clamp to int16 (out-of-range branch) + if calc > 32767: calc = 32767 + if calc < -32768: calc = -32768 + calc16 = struct.unpack("> 12) + if calc > 65535: calc = 65535 + if calc < 0: calc = 0 + if calc != w: + diff += 1 + if diff <= 5: + print(f" gain mismatch i={i} a={a} b={b} calc={calc} w={w}") +print(f"gain interp: diff={diff}/115200") + +# 2) NUC verify with working tables + blind +f20 = load(d + r"\pair_000.f20", "H", npix) +ref = load(d + r"\pair_000.ref", "H", npix) +raw = load(d + r"\pair_000.raw", "H", npix) +blind = open(d + r"\pair_000.blind", "rb").read() +recs = [] +for i in range(len(blind)//40): + target, typ = struct.unpack_from("> 1 + seg = 0 + if d2 > thrw[i*2]: seg = 1 + if seg == 1 and d2 > thrw[i*2+1]: seg = 2 + p = (seg*npix + i)*2 + v = gw[p+1] + ((gw[p] * d2) >> 12) + if v < 0: v = 0 + if v > 65535: v = 65535 + nuc[i] = v +out = list(nuc) +for (tgt, typ, ng) in recs: + if typ == 8: + out[tgt] = sum(out[ng[j]] for j in range(8)) >> 3 + elif typ == 7: + out[tgt] = sum(out[ng[j]] for j in range(7)) // 7 + elif typ == 6: + out[tgt] = sum(out[ng[j]] for j in range(6)) // 6 + elif typ == 5: + out[tgt] = sum(out[ng[j]] for j in range(5)) // 5 + elif typ == 4: + out[tgt] = sum(out[ng[j]] for j in range(4)) >> 2 + elif typ == 3: + out[tgt] = sum(out[ng[j]] for j in range(3)) // 3 +diff = sum(1 for i in range(npix) if out[i] != raw[i]) +sad = sum(abs(out[i]-raw[i]) for i in range(npix)) +print(f"NUC+blind vs official raw: diff={diff}/19200 MAE={sad/npix:.4f}") + +# 3) window + gray: LUT1024 + idx +win = open(d + r"\pair_000.win", "rb").read() +hi, lo, fmax, fmin, mean, std, axmax, axmin = struct.unpack_from("<8I", win, 0) +print(f"win: hi={hi} lo={lo} fmax={fmax} fmin={fmin} mean={mean} std={std}") +lut = open(d + r"\pair_000.lut", "rb").read() +S = 0xFFC00000 // (hi - lo) +gray = [] +for i in range(npix): + v = out[i] + if v <= lo: idx = 0 + elif v >= hi: idx = 1023 + else: idx = (v - lo) * S >> 22 + if idx > 1023: idx = 1023 + gray.append(lut[idx]) +# compare with official gray (320x240) - downsample center? official gray is 2x +g320 = load(d + r"\pair_000.gray", "B", 320*240) +# check the 2x relation: gray320[2y][2x] should equal gray160[y][x] (or the interp) +match = 0 +tot = 0 +for y in range(0, 120): + for x in range(0, 160): + g2 = g320[y*2*320 + x*2] + if g2 == gray[y*160 + x]: match += 1 + tot += 1 +print(f"gray 2x top-left parity: {match}/{tot}") +# what mapping? try LUT1024 idx directly vs gray320[0..] offset scan +best = None +for dy in range(4): + for dx in range(4): + m = 0 + for y in range(0, 120): + for x in range(0, 160): + yy = y*2+dy; xx = x*2+dx + if yy < 240 and xx < 320: + if g320[yy*320+xx] == gray[y*160+x]: m += 1 + if best is None or m > best[0]: + best = (m, dy, dx) +print("best offset:", best) diff --git a/analysis/reverse_tools/verify_nuc.py b/analysis/reverse_tools/verify_nuc.py new file mode 100644 index 0000000..20bffc7 --- /dev/null +++ b/analysis/reverse_tools/verify_nuc.py @@ -0,0 +1,94 @@ +import struct, os + +d = r"C:\Project\MAG160C\analysis\pairs_recheck_20260811" +npix = 19200 +nsegs = 3 + +def load16(name, n): + b = open(os.path.join(d, name), "rb").read() + return struct.unpack_from("<%dH" % n, b, 0) + +def loadi16(name, n): + b = open(os.path.join(d, name), "rb").read() + return struct.unpack_from("<%dh" % n, b, 0) + +f20 = load16("pair_000.f20", npix) +ref = load16("pair_000.ref", npix) +raw = load16("pair_000.raw", npix) + +# working tables (from build-artifacts, verified = interp of EP2/EP3) +gain = load16(r"C:\Project\MAG160C\build-artifacts\mag160c_official_nuc_gain.bin", npix*nsegs*2) +thr = loadi16(r"C:\Project\MAG160C\build-artifacts\mag160c_official_nuc_thr.bin", npix*(nsegs-1)) + +# blind records from DDT (all endpoints identical; use EP2) +ddt = open(r"C:\Project\MAG160C\analysis\ida\Core160043865.ddt", "rb").read() +thr_ep0 = 0x80 +block = (nsegs-1)*npix*2 + 1024 + nsegs*npix*4 +blind_off = thr_ep0 + 6*block +recs = [] +for i in range(32): + r = blind_off + i*40 + target, typ = struct.unpack_from("> 2 + elif typ == 5: + v = out[ng[0]] + out[ng[1]] + out[ng[2]] + out[ng[3]] + out[ng[4]] + out[t] = v // 5 + elif typ == 6: + v = sum(out[ng[j]] for j in range(6)) + out[t] = v // 6 + elif typ == 7: + v = sum(out[ng[j]] for j in range(7)) + out[t] = v // 7 + elif typ == 8: + v = sum(out[ng[j]] for j in range(8)) + out[t] = v >> 3 + return out + +# NUC with exact official math +nuc = [0]*npix +for i in range(npix): + d2 = (int(f20[i]) - int(ref[i])) >> 1 + seg = 0 + if d2 > thr[i*2]: + seg = 1 + if d2 > thr[i*2+1]: + seg = 2 + p = (seg*npix + i)*2 + v = (gain[p] * d2) >> 12 + gain[p+1] # CAREFUL: precedence! official: off + ((gain*d2)>>12) + v = gain[p+1] + ((gain[p] * d2) >> 12) + if v < 0: v = 0 + if v > 65535: v = 65535 + nuc[i] = v + +# stats before blind +diff = sum(1 for i in range(npix) if nuc[i] != raw[i]) +sad = sum(abs(nuc[i]-raw[i]) for i in range(npix)) +print(f"NUC only: diff={diff}/{npix} MAE={sad/npix:.4f}") + +# apply blind comp +out = blind_apply(nuc, recs) +diff2 = sum(1 for i in range(npix) if out[i] != raw[i]) +sad2 = sum(abs(out[i]-raw[i]) for i in range(npix)) +print(f"NUC+blind: diff={diff2}/{npix} MAE={sad2/npix:.4f}") + +# list mismatch pixels +mism = [(i, nuc[i], out[i], raw[i]) for i in range(npix) if out[i] != raw[i]] +print("mismatches:", len(mism)) +for m in mism[:15]: + print(" pix", m) + +# blind target pixels check +for (t, typ, ng) in recs: + print(f" rec target={t} type={typ} neigh={list(ng[:typ-2])}") diff --git a/analysis/session_state.md b/analysis/session_state.md index 70e1705..a4a0cfe 100644 --- a/analysis/session_state.md +++ b/analysis/session_state.md @@ -67,10 +67,12 @@ Stop-Process -Name mag160c_demo3 -Force -ErrorAction SilentlyContinue # 注意:tsdk_pair3 的 config.txt / pair_*.meta / pair_*.win 写在进程 CWD, # 跑完要把 C:\Project\MAG160C 下的 config.txt 和 pair_0*.meta/win 移回目录。 -# 对比/复算脚本(Python) -# C:\Users\ZXC\AppData\Local\Temp\opencode\compare_live.py demo3捕获 vs 官方同场景 -# C:\Users\ZXC\AppData\Local\Temp\opencode\verify_nuc.py 官方抓帧重建验证 -# C:\Users\ZXC\AppData\Local\Temp\opencode\verify_full.py 全链路验证 +# 对比/复算脚本(Python,已归档到 analysis/reverse_tools/) +# analysis/reverse_tools/compare_live.py demo3捕获 vs 官方同场景 +# analysis/reverse_tools/verify_nuc.py 官方抓帧重建验证 +# analysis/reverse_tools/verify_full.py 全链路验证 +# analysis/reverse_tools/parse_ddt.py DDT 文件解析 +# analysis/reverse_tools/parse_pcap.py pcap 抓包解析 # C:\Users\ZXC\AppData\Local\Temp\opencode\verify_c_pipeline.exe C实现离线复算 ```