- 逆向: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 规则
11 KiB
11 KiB
MAG160C 官方管线完整逆向结果(Ghidra 全量,2026-08-13)
本文档记录 CoreSDKLib.dll 官方渲染管线的完整反编译结论、逐像素验证结果, 以及 demo3 旧版"鬼影"问题的根因分析。所有结论均有 Ghidra 反编译代码和 官方活体抓帧数据的双重证据,不是推测。
工具链:Ghidra 12.1.2(headless 批处理反编译)+ 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}
→ 帧调度 0x180009ee0(FFC 状态机 + 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 升采样 0x180019740(2-tap 双线性 + 边缘外推, 见 §6)
→ 调色板 dev+0xb18 (256×4 BGR)
2. DDT 校准文件(表的数据源)
- 位置:
%TEMP%\Core<序列号>(无扩展名),由 app 用 MAG_SaveDDT 保存、 MAG_StartProcessImage → 0x18000f130 在启动时加载;文件路径存 dev+0x4134c。 - 本机文件已复制:
build-artifacts/mag160c_official.ddt(1,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=2(T[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-16300,type=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/19200(MAE 0.118) |
| NUC + 盲元补偿 | diff 0/19200(MAE 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)
- 1024-bin 直方图:bin = ((nuc-lo) * 0xffc00000/(hi-lo)) >> 22(u32 算术)
- 3-tap 就地平滑(bin 0..1022)
- 双向 CDF(从 mean-bin 向两侧累积):
- 向下:每累积 u21(=total>>12) 个计数 +0x100
- 向上:<8u21 时每 u21 个 +0x100,>=8u21 时每 bin +0x200
- 对比度曲线 curve[k] = max(1, (0x300000 + k*0x800)>>13)
- 中心值二分搜索(fmin/fmax 对应 LUT 端约束)
- 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 counts(d 恒负) | ≈ 场景(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)
遗留问题
demo3 NUC 输出有 2 个零像素已修复(2026-08-13 晚): 根因是 demo3load_ddt读取布局错误——blind 记录区在文件所有端点块之后, 代码却在每个端点块内读了 blind,导致 EP1+ 表整体错位 1280 字节。 错位表在死像素列附近 seg0 gain=35321(应为 ~4000),d2 踩中后 NUC clamp 到 0。 修复后全帧 0 个零像素,(65,25) nuc=9814,gain=4000 正常值。- 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.txtall_functions_decomp_libcoresdk_arm64.so_00100000.txt(ARM64 21.6MB)
- DDT 文件:
build-artifacts/mag160c_official.ddt、analysis/ida/Core160043865.ddt - 官方抓帧:
analysis/pairs_verify_20260813/、analysis/pairs_final_20260813/ - demo3 v5:
csdk/tools/mag160c_demo3.c - 采集工具:
csdk/tools/tsdk_pair3.c(含 DDT 复制、表/盲元/快门/窗口抓取)