docs: 官方温度链 IDA 反编译取证(放射学分支+波段映射+修正项;DDT 尾部即标定记录;系数待钉)+ IDA MCP 接入记录

This commit is contained in:
ZXCLI
2026-10-06 02:38:56 +08:00
parent ce27a1bb82
commit 3d4bb65247
2 changed files with 172 additions and 0 deletions
+40
View File
@@ -489,6 +489,46 @@
"未连接"分支显示,正常出图时用户看不到任何反馈)。
- [x] 单测 66 → **76 项全绿**;debug + release(R8) 双构建通过;APK 已更新。
## 用户反馈修复 第二十七轮(2026-10-06,官方温度链反编译:按 IDA 结果重建,系数待钉)
用户要求:"修复这些的时候要和 ida 或者 Ghidra 反编译的结果对的上,不要自己瞎猜,并且是
全局代码对的上;机器上已经有 ida 了,你自己装个 mcp 就能用"。
详细报告:`docs/android_app/temperature_chain_re_20261006.md`。
### 1) 用 IDA 建立了地面真值(不是猜)
- IDA Professional 9.4 批处理(`idat.exe -A -S`)+ 运行时 ctypes 直读官方 DLL 内存,
反编译并核对:`sub_180016290`(核心换算三分支)、`sub_180014e70`(探针,含邻域平均)、
`sub_18000d2a0`(每帧两点标定端点)、`sub_180015b90`(修正,含 `K=f8·f4·ε` 与
环境辐亮度反演)、`sub_180015840/8b0/A40/940`(曲线 A 前后向插值)。
- **根因确定**:本机 `dev+0x41880=1` → 官方走**放射学分支 + 每波段线性映射**,
而 App 用的是 `countsToTempMc`(T2E 表分支)——**分支都不同**,不是"表差一点"。
- 拿到全部运行时参数:8 个波段阈值(9389/9995/10328/10581/10797/11160/11430/11772)、
每波段 (gain,offset)、`shift=12`、发射率 1.0、环境温度 29.45℃、曲线 A 与斜率表地址等;
并确认**这些参数就在 DDT 文件尾部**(App 解析 DDT 即可获得,无需新指令)。
### 2) 验证到什么程度
- 官方同帧数据(`MAG_GetTemperatureData` + `dev+0x270` 计数 + 记录快照)逐像素比对:
**波段映射结构正确**——flat 背景所在 band 的拟合残差仅 **3.2 mC**;
但整体仍差 **≈230 mC**,对应尚未对齐的**环境补偿/距离大气修正项**
(这两项按反编译加上去反而变差 ≈980 mC,说明字段语义还有一处没读对)。
- 结论:**结构已定、系数待钉**;下一步只做这两项的单变量对照,不再猜公式。
### 3) 工具链落地
- 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 端口。
- 批处理脚本与运行时读数脚本、官方同帧基准数据都在 `%TEMP%\mag160c_wincmp\`,未入库。
### 4) 下一步
1. 用单变量对照钉死环境补偿 (v50) 与距离/大气项(`dev+0xA8`、`dev+0x44CB8`、
`rec+0x208/0x20C`)。
2. Kotlin 落地官方放射学分支(波段映射 + 修正),并补导曲线 A 的斜率表与 DDT 尾部解析。
3. 用本轮官方同帧数据做回归断言(band 1 应到 mC 级),再替换 App 各温度读数点。
## 用户反馈修复 第二十六轮(2026-10-06,官方 PC 软件同帧对比:渲染逐像素一致,温度曲线有系统差)
用户把热像仪接上 PC,要求"调用项目里官方给的 win 软件拍图,看和逆向出来的 sdk 的图
@@ -0,0 +1,132 @@
# 官方温度链逆向(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 端口。