Files
MAG160C/analysis/reverse_20260813_full.md
T
ZXCLI fe91a82762 原汁原味补全官方管线细节:0x8c CDF 中心、temporal 移植、uniform 分支、状态标志
- 抓取官方会话控制字段确认实际启用路径:0x109c0→0x11ee0→0x19740;
  temporal(0x41fe0=1)/帧差(0x230=0)/隔行平滑(0x41fe8=0)/0x30=0 全关
- 实现 0x8c=1 分支:CDF 中心=max(均值bin, 累计75%bin),lres 545→631
  (官方 633,跨帧差 2 bin);用官方 cdf 离线验证 LUT 差异降至 123/1024
  (集中在灰度过渡区,跨帧异步所致)
- 移植 temporal filter(0x18001e920,0x41fe0>1 启用,默认关=官方)
- 补 uniform 帧分支(0x109c0:fmin==fmax 时 LUT/gray 置中灰)
- 补官方标志维护(0x4203c/0x42038/0x42004)
- 实机确认:ref=15502(type1)、d=-2390、win/stats 正常
2026-08-14 16:33:13 +08:00

237 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.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=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. **0x8c 分支(live=1**:找平滑后直方图累计 >= 总计数×3/4 的第一个 bin
CDF 中心 = max(均值bin, 该bin)(实测 545 → 631,官方 633,跨帧差 2)
4. 双向 CDF(从中心向两侧累积):
- 向下:每累积 u21(=total>>12) 个计数 +0x100
- 向上:<8*u21 时每 u21 个 +0x100>=8*u21 时每 bin +0x200
5. 对比度曲线 curve[k] = max(1, (0x300000 + k*0x800)>>13)
6. 中心值二分搜索(fmin/fmax 对应 LUT 端约束)
7. center 时域平滑 + 重填
验证:用官方同帧 cdf + 中心搜索,LUT 123/1024 差异,全部集中在 lres 上方
灰度过渡区(±1-7 级),来源是官方 SDK 抓帧的跨帧异步(cdf/lut 不同帧),
非算法差异。官方会话实测启用路径 = 0x109c0 → 0x11ee0 → 0x19740
temporal(0x41fe0=1)/帧差(0x230=0)/隔行平滑(0x41fe8=0)/0x30 gray 路径(0x30=0)
全部关闭,demo3 默认一致,temporal 已移植作备用开关。
## 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.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 复制、表/盲元/快门/窗口抓取)