Files
MAG160C/docs/android_app/temperature_chain_re_20261006.md
T

133 lines
8.9 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.
# 官方温度链逆向(IDA 反编译为准绳)2026-10-06
> 目标:把官方 SDK 的"计数 → 温度"换算**按反编译结果**完整复刻到我们 App,
> 不用拟合、不猜。本轮已完成反编译取证、运行时参数读取、官方基准数据采集与
> 波段模型验证;**最后两个修正项(环境/距离项)的字段含义尚未对齐**,下轮继续。
>
> 工具链:IDA Professional 9.4(批处理 `idat.exe -A -S<脚本>` + Hex-Rays)、
> 官方 DLL 运行时内存读取(Python ctypes)、官方 ThermalSDK 同帧采集(tsdk_pair3)。
## 1. 官方温度链(函数级,已反编译)
调用关系(CoreSDKLib.dll,基址 0x180000000):
| 函数 | 作用 |
|---|---|
| `MAG_GetTemperatureData(chan,buf,bytes,flag)` @0x180003fc0 | 主路径:逐像素 `sub_180016290(counts, pixelIdx, 1)`,flag≠0 或 `dev+0x78≠0` 时再套 `sub_180015b90` |
| `MAG_GetTemperatureData_Raw(...)` @0x180004030 | **两点线性标定**:横坐标取当帧 `counts[idxA]`、`counts[idxB]`(`dev+0x44cc0/4`),
纵坐标取 `dev+0x44cb0/4`(由 `sub_18000d2a0` 每帧用 `sub_180016290` 算出),逐像素线性插值 |
| `MAG_GetTemperatureProbe(chan,x,y,mode)` @0x1800045a0 | 探针:先 `sub_180014e70` 做**邻域平均**(mode→1×1/3×3/5×5),再同上 |
| `sub_180016290(dev,counts,pixIdx,flag)` @0x180016290 | **核心换算**,三个分支(见下) |
| `sub_180015b90(dev,T,float)` @0x180015b90 | 修正(发射率/透过率/环境)——装配代码见 §3 |
| `sub_180015840` / `sub_1800158b0` | 曲线 A 的前向(温度域→辐亮度域)/ 逆向插值 |
| `sub_180015A40` / `sub_180015940` | 在曲线 A 上做"增量"换算(把辐亮度增量变回温度增量) |
| `sub_18000d2a0` @0x18000d2a0 | 每帧温度收尾:算 `dev+0x44cb0/4/8/ccc/cd0`(两点标定端点、均值温度),并按 `dev+0x78` 决定是否修正 |
### `sub_180016290` 的三分支(决定我们该走哪条)
```
if (dev+0x41880 == 0):
if (dev+0x47950 != 0): # T2E 表分支
v = counts << (7 - dev+0x47954) # 注意:不是 counts*3-5797!
二分查找 curveA(646 项) → (v-A[i])*S[i]>>12 + (i<<12) - 150000
else: # 线性分支
return dev+0x47948 + (counts << dev+0x4794c)
else: # 放射学分支(本机 = 这条)
eps = *(float*)(rec+0x204)
if eps<=0.01 or eps>=0.99: # 本机 eps=1.0 → 进入
若 rec+0xAC/B0/B4/BC 均有效(>=1/>0) 且 flag!=0: 走"fix"路径(本机这些字段=0 → 跳过)
else: ... 发射率反演 ...
波段映射:band = 阈值(rec+0x40 起 8 个)分段
T = off[band] + ((counts * gain[band]) >> rec+0x30(=12)) # gain/off 在 rec+0xD4 起,步长 8
环境补偿(v44..v54,见 §2)→ v54
if flag: 距离/大气项(dev+0xA8、dev+0x44CB8 等,见 §2)
return T + sub_180015A40(T, v54)
```
**关键结论**:本机 `dev+0x41880 = 1`,即官方走**放射学分支 + 波段映射**,
而我们 App 现在用的是 `TempMath.countsToTempMc`(= T2E 表分支)——**分支都不同**,
这就是温度系统差的根因(而不是"表差一点")。
## 2. 本机运行时读到的参数(同一帧,ctypes 直读官方设备对象)
| 字段 | 值 | 含义 |
|---|---|---|
| `dev+0x41880` | 1 | 走放射学分支 |
| `dev+0x47950 / 0x4794c / 0x47948` | 0 / 3 / −50000 | 表分支关闭;线性分支参数(−50000 与 DDT 头部字段一致) |
| `rec = dev+0x41888`(540B/记录,索引 `dev+0x98`,上限 `dev+0x41884`=1) | | |
| `rec+0x30` | 12 | gain 右移位数 |
| `rec+0x34 / 0x38` | 29917 / 29417 | 差 = −500(与 DDT 第二组锚点 −500 呼应),用于环境补偿 |
| `rec+0x3c` | 8 | 波段数 |
| `rec+0x40…0x5C` | 9389, 9995, 10328, 10581, 10797, 11160, 11430, 11772 | 波段阈值(计数域) |
| `rec+0xD4…0x124` | (33486,−51932) (33174,−51216) (36271,−58774) (32637,−49610) (36280,−59022) (32842,−49958) (29110,−39789) (35059,−56391) | 每波段 (gain, offset),单位 mC |
| `rec+0x204` | 1.0 | 发射率(1.0 → 不走发射率反演) |
| `rec+0x208 / 0x20C` | 19790 / 44930 | 距离/大气项阈值对 |
| `dev+0x54 / 0x58 / 0x5C / 0x60 / 0x64` | 34412 / 33912 / 34382 / 33882 / 1 | 环境补偿输入(传感器/环境温度对) |
| `dev+0xA8`(float) | −0.0279 | 距离/大气系数(非 0 → 该项参与) |
| `dev+0x44CB8` | 29564 | 当帧**均值温度**(`sub_18000d2a0` 写入) |
| `dev+0xCC / 0x78 / 0xD4 / 0xD8` | 2 / 0 / 0.98 / 29.45 ℃ | 修正开关 / 修正使能 / 调用方传入的 ε·τ 因子 / 环境温度 |
| `dev+0xF4 / 0xF8` | 0.99991 / 1.0 | 修正公式里的两个 (0,1] 因子(K = f8·f4·a3) |
| 曲线 A `0x180099000`(646×i32) | 51,70,94,125,162,208,… | **= 我们 OfficialTables.T2E 已经一致** |
| 斜率表 `0x180099A20`(646×i32) | | 逆向插值用的局部斜率(我们表里未导出,需补) |
| `dev+0xAC…0xC8`(8×i32) | 全 0 | 环境温度修正查表(本机为 0) |
**DDT 文件尾部就是这条记录**:`official.ddt`(1856416B) 从 1855856 起可对齐到
rec 的波段阈值(1855940=9389…)、gain/offset(1856088=33486…)、
`0x34/0x38`(1855932/1855928 = 29917/29417)、`rec+0x204` 区(1856372 起 eps=1.0、19790、44930)。
→ App 只要解析 DDT 尾部即可拿到全部温度标定参数(无需额外设备指令)。
## 3. `sub_180015b90`(修正)汇编要点
```
if (dev+0xCC == 0) return T; # 开关
需要 0 < dev+0xF4 <= 1、0 < a3(传入 0.98) <= 1、0 < dev+0xF8 <= 1,否则原样返回
K = dev+0xF8 * dev+0xF4 * a3
Lobj = sub_180015840(T) # 温度域 → 曲线 A 的"辐亮度"域
Lamb = sub_180015840( (int)(dev+0xD8 * 1000) ) # 环境温度 → 辐亮度
Teff = sub_1800158b0( (Lobj - (1-K)*Lamb) / K ) # 反演回温度
Teff = max(Teff, -273150)
idx = clamp((dev+0x5C - 15000)/5000, 0, 6) # 环境温度查表(本机表全 0 → 无修正)
Tout = Teff + tab[idx] + (tab[idx+1]-tab[idx])*(dev+0x5C-5000*(idx+3))/5000
若 dev+0x6C != 0: Tout += (int)(Tout*dev+0xEC) - (int)(dev+0xF0 * 0.4f)
```
(这就是官方"温度修正参数"里的发射率/透过率/环境温度的落点。)
## 4. 验证状态(官方同帧数据)
- 采集:`tsdk_pair3` + ctypes 直读(`dev+0x270` 计数缓冲、`dev+0x41888` 记录、
`MAG_GetTemperatureData(flag=1)` 输出)。
- **配对必须同帧**:`MAG_GetTemperatureData` 是按"当前帧"现算的;先用
`ThermalSDK.Stop()` 冻结缓冲可保证同帧(但 Stop 后温度接口返回失败,故只能
"先取温度、立刻取计数、并校验同计数下温度唯一(spread=0)")。
- 结果:
- **波段映射单独就复现了官方输出的主体**:flat 背景所在的 band 1
拟合残差仅 **3.2 mC**,说明"阈值 + 每波段 gain/offset"结构完全正确;
- 但整体仍有 **≈230 mC 的平均差**,且残差随计数平滑变化——对应 §2 里
还没对齐的**环境补偿 (v50) 与距离/大气项**;
- 我按反编译把这两项加上后误差反而变大(≈980 mC),说明其中某个字段的
语义还没读对(`dev+0xA8`/`dev+0x44CB8`/`rec+0x208/0x20C` 的角色待确认)。
- 结论:**结构已定,系数待钉**。下一步只需在这几个字段上做单变量对照
(逐项开关 + 与官方输出比对),不需要再猜公式。
## 5. 我们 App 侧要改什么(等模型钉死后再动手)
1. `TempMath` 增加"官方放射学分支"实现:波段映射(参数取自 DDT 尾部)+ §3 修正;
保留 `countsToTempMc` 作为回退(T2E 表分支)。
2. 需要新增导出的表:曲线 A 的**斜率表** `0x180099A20`(我们只导了 T2E);
以及 DDT 尾部的波段记录解析。
3. 温度读数/分析/远程/PDF 全部改用新路径(`RenderPipeline.probeTemp`、
`LiveViewModel` 的最高最低中心温、`AnalyzeViewModel`、`RemoteViewerViewModel`)。
4. 回归:用本轮采集的官方同帧数据做逐像素断言(band 1 应到 mC 级)。
## 6. 复现与工具(都在临时目录,未入库)
- IDA 批处理:`idat.exe -A -S<script.py> -Llog.txt <db.i64>`(脚本见
`%TEMP%\mag160c_wincmp\ida\dump_temp.py|dump_helpers.py|dump_revise.py|dis_revise.py`)。
仓库内 `analysis/ida/CoreSDKLib.dll.i64` 是原始库;批处理请先复制一份再跑(GUI/批处理会互锁)。
- 运行时参数/内存:`%TEMP%\mag160c_wincmp\offdump2.py`、`offpair.py`、`offmodel.py`。
- 官方同帧基准:`%TEMP%\mag160c_wincmp\off20\`、`offmodel.pkl`、`offpair.pkl`、`offverify.pkl`。
- IDA MCP(供交互式使用):`pip install "mcp<2" ida-pro-mcp` 已装,插件已放进
`%APPDATA%\Hex-Rays\IDA Pro\plugins\mcp-plugin.py`,ZCode 用户配置
`~/.zcode/cli/config.json` 已加 `mcp.servers."ida-pro"`;**需重启 ZCode 生效**,
且 IDA 里要先 `Edit → Plugins → MCP`(Ctrl+Alt+M)启动 13337 端口。