Files
MAG160C/analysis/reverse_20260813_full.md
T
ZXCLI 0bfb926892 完成官方管线全量逆向与 demo3 v5 复刻,清理仓库
- 逆向:Ghidra/IDA 全量反编译 CoreSDKLib.dll/ThermalSDK.dll/libthermalSDK.so/
  libcoresdk.so(ARM64)/libmagcore.so,导出 analysis/ida/export/
- 解码官方渲染管线:DDT 校准表加载->快门端点选择->Q12 插值->ref(4x type1 帧
  均值)->NUC 查表->盲元补偿->窗口->LUT1024 重建->2x 升采样->调色板
- 逐像素验证:NUC+盲元 0/19200、插值 0 误差、2x 0/76800、窗口一致
- demo3 v5:完整复刻官方管线(含 DDT 解析、FFC 状态机、快门温度驱动),
  修复 load_ddt 表错位导致的零像素问题
- 鬼影根因分析写入 analysis/reverse_20260813_full.md
- 心跳/恢复机制:analysis/session_state.md + tools/resume_rev.ps1
- 新增 tsdk_pair3 增强采集工具;历史工具归档 csdk/tools/legacy/;
  根目录抓帧残留删除,历史文档归档 analysis/history/
- csdk/README.md 完整使用文档;.gitignore/.gitattributes 补 LFS 规则
2026-08-13 23:16:12 +08:00

11 KiB
Raw Blame History

MAG160C 官方管线完整逆向结果(Ghidra 全量,2026-08-13

本文档记录 CoreSDKLib.dll 官方渲染管线的完整反编译结论、逐像素验证结果, 以及 demo3 旧版"鬼影"问题的根因分析。所有结论均有 Ghidra 反编译代码和 官方活体抓帧数据的双重证据,不是推测。

工具链:Ghidra 12.1.2headless 批处理反编译)+ IDA Free 8.4(交互确认)。 反编译产物:analysis/ida/export/ghidra_dump/keyfuncs / callers / exports)。


1. 官方管线(Ghidra 反编译确认)

USB 帧 (type=0, 0x1bb1b11b 标记)
  → 帧解析器 0x18001d930
     验证: [0x08]=len, [0x1c+len]=0x1bb1b11c, type∈{0,1}
     快门: width=160 时 = 帧尾字 frame[len+0x24]
     metadata 0x4c 字节: {0, counter, len, 1, 0, w, dev54, 8, 0, -50000,
                          shutter-500, shutter, 0, 0, type, 0, 0}
  → 帧调度 0x180009ee0FFC 状态机 + ref/表重建时序, 见 §3
  → 主渲染 0x18000ca30
      → 0x18000c950: f20 平滑器 0x41f10 (mode=1: 直通) → NUC
      → NUC 查表 0x180017200:
            d2 = (f20[i] - ref[i]) >> 1
            seg: 每像素 (nsegs-1) 个 signed 阈值顺序比较
            out = off + ((gain * d2) >> shift), shift=0x11c=12
            clamp [0, 65535]
      → 盲元补偿 0x180017330:
            固定记录表 dev+0x41878, 数量 dev+0x41654[sel]
            每条 40 字节: {u32 target, u32 type(3..8=邻域数), u32 neigh[8]}
            out[target] = mean(neigh)  (type 8/4 用移位, 其余用除法)
      → temporal filter 0x18001e920: 仅 dev+0x41fe0>1 时启用, 当前=1 跳过
      → 统计 0x180010780: min/max/mean/std → dev+0x42010..0x42024
      → 窗口 0x1800109c0 + 半宽 0x1800108a0:
            half = max(128, dev24*1000 >> dev4c_shift) / 2   (live: 5*1000>>3 /2 = 312)
            lo = min(fmin, mean-half) >= 0
            hi = max(fmax, mean+half) <= 65535
      → LUT1024 重建 0x180011950 + 0x180011ee0(直方图均衡, 见 §5
      → 灰度: gray = LUT[(nuc-lo) * 0xffc00000/(hi-lo) >> 22]
      → 2x 升采样 0x1800197402-tap 双线性 + 边缘外推, 见 §6)
      → 调色板 dev+0xb18 (256×4 BGR)

2. DDT 校准文件(表的数据源)

  • 位置:%TEMP%\Core<序列号>(无扩展名),由 app 用 MAG_SaveDDT 保存、 MAG_StartProcessImage → 0x18000f130 在启动时加载;文件路径存 dev+0x4134c。
  • 本机文件已复制:build-artifacts/mag160c_official.ddt1,856,416 字节,v3)。
v3 格式 (magic 0x5aa50003):
+0    u32 magic
+4    u32 width (160)
+8    u32 height (120)
+12   u32 端点数量 (0x41558, live=6)
+16   u32 nsegs (0x41554, live=3)
+20   u32 0x47944 (8, 2 的幂)
+24   u32 0x47948 (-50000)
+28   u32 0x4155c (1024)
+32   u32 0x41560 (0)
+36   i32  T[count]  端点温度: [8304, 18390, 28495, 33637, 38730, 47664]
+..   u32  0x415b4[count]  (偏移基准)
+..   u32  0x41604[count]
+..   u32  0x41654[count-1]  每端点盲元记录数 (live: 32×5)
+128  端点表块 × count:
        thr   (nsegs-1)*npix + 0x4155c/2 个 int16 (像素主序)
        gain  nsegs*npix*2 个 uint16 (seg 主序: [seg][pix].gain/.off)
+..   盲元记录 × count (每端点 40 字节 × 数量)
+..   trailer: 0x6bb60001, "lens f6.5"

3. 端点选择 + 表插值 + ref 时序(0x180009ee0 / 0x180016ae0

3.1 快门温度 = 设备温度代理

  • 快门 = 帧尾字(width=160 时 frame[len+0x24]),每帧进入 metadata。
  • dev+0x5c = 当前快门,dev+0x54 = 上次 FFC(0) 时的快门。
  • FFC 条件:idx >= N0+1800(周期)或 (|dev54 - 快门累加| > 250 && idx >= 冷却)

3.2 端点选择(实测验证)

sel = 0
while (sel < count-2 && 快门 > T[sel+1]) sel++

live 实测:快门 29289 → sel=2T[2]=28495 < 29289 ≤ T[3]=33637)✓

3.3 Q12 插值(0x180016dd0 / 0x180016f10

t = ((快门 - T[sel]) << 12) / (T[sel+1] - T[sel])   clamp ±0x3fff
work[i] = a[i] + ((b[i] - a[i]) * t >> 12)

验证:阈值 0/38400 误差,gain 0/115200 误差。

3.4 ref 采集时序(关键!)

FFC(0) 后帧计数复位:
  idx 1..5   : 表重建(分块)
  idx 6..9   : 采集 ref —— 4 个 type=1 校准帧的均值(累加 >> 2
               idx 6 复位平滑器,idx 9 发 FFC(1)
  idx 10..13 : 表重建
  idx 14+    : 正常渲染(ref 冻结,直到下次 FFC)
  • ref = 4 个 type=1 帧的均值,不是场景帧!
  • type=1 帧均值实测 ~15000-16300type=0 场景帧 ~11700-13700 因此官方 d = f20 - ref ≈ -2200 ~ -2700,恒 ≤ 0
  • ref 在每次 FFC 对之后重新采集;FFC 之间完全冻结。

4. 逐像素验证(官方抓帧 vs 反推重建)

用官方同帧抓帧(analysis/pairs_verify_20260813/+ DDT 离线复算:

环节 结果
阈值插值 (t 由快门算) diff 0/38400
gain/off 插值 diff 0/115200
NUC 查表 diff 30/19200MAE 0.118
NUC + 盲元补偿 diff 0/19200MAE 0.0000
窗口 [lo,hi] 与官方完全一致
灰度 (LUT+idx) 160 层 19200/19200
2x 升采样 diff 0/76800
demo3 C 实现离线复算官方数据 NUC/窗口/灰度全部 0 误差

LUT 填充公式:在正确 center 下 834/1024 精确,其余 ±1~±58 为 跨帧捕获偏差(h1024/cdf/lut 不同帧)+ center 时域平滑所致。

5. LUT1024 重建算法(0x180011950 + 0x180011ee0

  1. 1024-bin 直方图:bin = ((nuc-lo) * 0xffc00000/(hi-lo)) >> 22u32 算术)
  2. 3-tap 就地平滑(bin 0..1022
  3. 双向 CDF(从 mean-bin 向两侧累积):
    • 向下:每累积 u21(=total>>12) 个计数 +0x100
    • 向上:<8u21 时每 u21 个 +0x100>=8u21 时每 bin +0x200
  4. 对比度曲线 curve[k] = max(1, (0x300000 + k*0x800)>>13)
  5. 中心值二分搜索(fmin/fmax 对应 LUT 端约束)
  6. center 时域平滑 + 重填

6. 2x 升采样(0x180019740,逐像素验证)

内部:  out[2y][2x]=a  out[2y][2x+1]=(a+b)>>1  out[2y][2x+2]=b  out[2y][2x+3]=(c+b)>>1
       out[2y+1][2x]=(d+a)>>1  out[2y+1][2x+1]=(d+e+a+b)>>2
       out[2y+1][2x+2]=(e+b)>>1  out[2y+1][2x+3]=(f+e+c+b)>>2
右缘:  out[2y][2W-1] = (3a>>2)+(l>>2)(外推)
底缘:  out[2H-1][2x] = (3a>>2)+(u>>2)
角落:  (3a+l)>>2 / (3a+u)>>2 / 三者均值

7. 鬼影根因分析(为什么旧 demo3 有鬼影)

旧 demo3(v4,SBNUC 版)与官方的差异,每一条都有上面的证据:

7.1 ref 语义完全不同(最根本)

官方 旧 demo3
ref 来源 FFC 窗口 4 个 type=1 校准帧均值 启动场景 12 帧中值
ref 值域 场景 + ~2200 countsd 恒负) ≈ 场景(d ≈ 0
刷新时机 每次 FFC 对之后 仅启动一次(FFC 后只 rebase 全局偏置)
FFC 之间 冻结 冻结 + SBNUC 自愈

后果:

  • 官方 NUC 输出 ≈ 9800(off 段配合 d 恒负),旧 demo3 输出 ≈ 10900+(d≈0), 两者对同一场景的画面等级都不同,更谈不上残影行为一致。
  • 旧 demo3 启动采集一次 ref 后永不刷新 → 场景基线漂移(如设备升温、 环境温度变化)后 ref 失配 → 旧位置出现"黑洞/黑拖影",且只能等手动 FFC。
  • 官方每次 FFC 重采 ref → 残影最多持续到下次 FFC(周期 120s 或快门漂移 250 触发), 且表也按新快门重建,匹配始终成立。

7.2 NUC 表不是静态的

  • 官方:表 = DDT 端点按快门温度 Q12 插值,FFC 后重建。
  • 旧 demo3:静态抓取表(某次会话的插值结果),温度漂移后失配, 表现为"小温差残影擦除慢"——本质是表与当前温度不匹配造成的残差, 不是真正的显示层鬼影。

7.3 SBNUC 是非官方 hack,引入次级鬼影

  • 旧 demo3 的"曾热变冷快速吸收/低温静止不吸收"状态机,会把低温物体 排除在 ref 之外 → 物体离开后留下亮残影;把曾热像素写回 ref → 改变局部基线 → 新黑洞。
  • 官方没有 SBNUC:ref 冻结 + 定期 FFC 刷新是官方唯一的"去鬼影"机制。

7.4 盲元补偿缺失

  • 官方有 32 条固定盲元记录(邻域平均),旧 demo3 完全没接 → 死像素 直接显示(0 值黑点),曾用动态 badmap 尝试 → 反而把场景结构钉在 屏幕坐标(BADMAP_ENABLE=0 后残留黑点)。

7.5 窗口/灰度差异

  • 官方窗口 = [min(fmin, mean-312), max(fmax, mean+312)] 旧 demo3 用 P2..P98 自适应 → 对比度行为不同,但不是鬼影主因。

7.6 结论

旧 demo3 的"鬼影"是 ref 语义错误 + ref/表不随 FFC 刷新 + 静态表 + 非官方 SBNUC 共同造成的,与官方"冻结 ref 的轻微旧位置偏暗"不是 同一个现象。修复 = 完整复刻官方管线(demo3 v5 已实现,见下)。

8. demo3 v5(官方复刻)实现状态

  • DDT 加载(v3 解析,含端点表/盲元记录)
  • 端点选择 + Q12 插值(快门驱动,FFC 后重建)
  • ref = FFC 窗口 4 个 type=1 帧均值(u32 累加器)
  • NUC + 盲元补偿(32 条固定记录)
  • 窗口/统计(官方公式)
  • LUT1024 重建(直方图均衡 + center 平滑)
  • 灰度 + 2x 升采样 + 官方调色板
  • FFC:周期 1800 / 快门漂移 250 / 13 帧隐藏周期
  • 实机运行:sel=2、FFC 自动触发正常、画面统计接近官方 demo3 mean 9377 vs 官方 9640

遗留问题

  1. demo3 NUC 输出有 2 个零像素 已修复(2026-08-13 晚) 根因是 demo3 load_ddt 读取布局错误——blind 记录区在文件所有端点块之后, 代码却在每个端点块内读了 blind,导致 EP1+ 表整体错位 1280 字节。 错位表在死像素列附近 seg0 gain=35321(应为 ~4000),d2 踩中后 NUC clamp 到 0。 修复后全帧 0 个零像素,(65,25) nuc=9814gain=4000 正常值。
  2. LUT 重建的跨帧一致性:算法已按反编译实现并实机运行,同场景 gray 直方图 与官方相关性 0.9197(跨会话),剩余差异来自会话间 ref/快门温度差异, 属官方物理机制,不是 bug。

9. 关键文件

  • 反编译全量:analysis/ida/export/ghidra_dump/
    • keyfuncs_decomp_CoreSDKLib.dll.txt(核心函数伪代码)
    • callers_decomp_CoreSDKLib.dll.txt(调用关系 + 主管线)
    • exports_decomp_CoreSDKLib.dll.txt(全部导出函数)
    • exports_decomp_ThermalSDK.dll.txt
    • all_functions_decomp_libcoresdk_arm64.so_00100000.txtARM64 21.6MB
  • DDT 文件:build-artifacts/mag160c_official.ddtanalysis/ida/Core160043865.ddt
  • 官方抓帧:analysis/pairs_verify_20260813/analysis/pairs_final_20260813/
  • demo3 v5csdk/tools/mag160c_demo3.c
  • 采集工具:csdk/tools/tsdk_pair3.c(含 DDT 复制、表/盲元/快门/窗口抓取)