Compare commits
69
Commits
9739a1972c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee6859877a | ||
|
|
6ba90f2bb6 | ||
|
|
e3f0729218 | ||
|
|
f465831d8e | ||
|
|
4c940fe773 | ||
|
|
9e64c74ffe | ||
|
|
e43e93025e | ||
|
|
f41668d5d1 | ||
|
|
4ebdca109a | ||
|
|
47037f3a23 | ||
|
|
376b8389ea | ||
|
|
4cb6e69ea4 | ||
|
|
15a7127287 | ||
|
|
3704ee797b | ||
|
|
f7bca894ba | ||
|
|
464cfcb5e8 | ||
|
|
0919f5e186 | ||
|
|
8e1312a137 | ||
|
|
512508e629 | ||
|
|
f8b3200464 | ||
|
|
c34940e6fd | ||
|
|
b7a928e23d | ||
|
|
656d419f81 | ||
|
|
06c1f30b13 | ||
|
|
087e15cb19 | ||
|
|
e69a97436b | ||
|
|
4ba98f62cd | ||
|
|
623d62b641 | ||
|
|
b29d7fd377 | ||
|
|
8ecd50228a | ||
|
|
1a2fc7abb8 | ||
|
|
0f2aa11196 | ||
|
|
dbb4f24608 | ||
|
|
ee317c7b25 | ||
|
|
fe8595f747 | ||
|
|
ca7e29a647 | ||
|
|
456285c71b | ||
|
|
add3d06713 | ||
|
|
7437ce5e85 | ||
|
|
fabea4c664 | ||
|
|
b1a3c38a69 | ||
|
|
d0b133a895 | ||
|
|
2c38537303 | ||
|
|
798e180683 | ||
|
|
96b92a8d26 | ||
|
|
8d22969f6d | ||
|
|
17fcb7b94c | ||
|
|
f8eab28857 | ||
|
|
430bf373e4 | ||
|
|
6d0805af90 | ||
|
|
4fd2e5bf79 | ||
|
|
a07a49b437 | ||
|
|
3bca426c99 | ||
|
|
29a9b85cbe | ||
|
|
ed0a0882d8 | ||
|
|
6297a06b97 | ||
|
|
5b41a6aa9b | ||
|
|
dd52469377 | ||
|
|
0d83c625d1 | ||
|
|
0dcb2830c6 | ||
|
|
70bde0fa86 | ||
|
|
9db940a615 | ||
|
|
5c22bfe78c | ||
|
|
df6c6dd444 | ||
|
|
51c8ba9174 | ||
|
|
fe91a82762 | ||
|
|
004f28910f | ||
|
|
03ed936a5f | ||
|
|
0bfb926892 |
@@ -41,3 +41,8 @@
|
||||
*.win filter=lfs diff=lfs merge=lfs -text
|
||||
*.win2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.ddt filter=lfs diff=lfs merge=lfs -text
|
||||
*.i64 filter=lfs diff=lfs merge=lfs -text
|
||||
*.h1024 filter=lfs diff=lfs merge=lfs -text
|
||||
*.cdf filter=lfs diff=lfs merge=lfs -text
|
||||
*.meta filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# 运行产物(编译/运行/调试生成,不入库)
|
||||
build-artifacts/*.log
|
||||
build-artifacts/*.txt
|
||||
build-artifacts/*.bin
|
||||
build-artifacts/*.bmp
|
||||
build-artifacts/*.png
|
||||
build-artifacts/*.rgb
|
||||
build-artifacts/*.exe
|
||||
build-artifacts/*.dll
|
||||
build-artifacts/demo3_*
|
||||
build-artifacts/tsdk_*
|
||||
build-artifacts/official_*
|
||||
build-artifacts/type1_*
|
||||
|
||||
# 但保留核心数据文件(git add -f 或下面白名单)
|
||||
!build-artifacts/mag160c_official.ddt
|
||||
!build-artifacts/mag160c_official_nuc_gain.bin
|
||||
!build-artifacts/mag160c_official_nuc_thr.bin
|
||||
!build-artifacts/mag160c_demo3.exe
|
||||
!build-artifacts/tsdk_pair2.exe
|
||||
!build-artifacts/tsdk_pair2_fixed.exe
|
||||
!build-artifacts/tsdk_pair3.exe
|
||||
!build-artifacts/libusb-1.0.dll
|
||||
!build-artifacts/libusb0.dll
|
||||
|
||||
# 临时/系统
|
||||
*.tmp
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# ZCode 会话目录
|
||||
.zcode/
|
||||
|
||||
# Java build output for the manual figure generator
|
||||
docs/android_app/manual_images/*.class
|
||||
@@ -1,38 +1,78 @@
|
||||
# MAG160C 热像仪逆向工程
|
||||
# MAG160C 热像仪统一工程
|
||||
|
||||
本仓库记录 MAG160C 热像仪的 USB 协议、官方 Windows/Android SDK 逆向结果、纯 C SDK、官方渲染管线分析、抓帧数据和 demo3 实机调试过程。
|
||||
MAG160C 热像仪(160×120,15fps,USB VID `0x833C`)的逆向工程与双端实现:
|
||||
**C 参考实现**(Linux/Windows,源自官方 PC SDK 逆向)与**安卓统一 App**
|
||||
(Kotlin + Compose,适配 Android 16,整合官方 4 个 App 的功能)。
|
||||
|
||||
## 从这里开始
|
||||
## 从这里开始(新接手者阅读顺序)
|
||||
|
||||
新接手者先阅读:
|
||||
1. `docs/android_app/HANDOFF_DEVELOPMENT.md` — 安卓 App 总交接(必读)
|
||||
2. `docs/android_app/session_state.md` — 会话心跳与待办(最新进度)
|
||||
3. `docs/android_app/app_manual.md` — **App 操作手册**(界面/设置/方向/排障)
|
||||
4. `analysis/README.md` — 逆向工程总目录索引(Linux/Windows/Android 三条线)
|
||||
5. `analysis/magcx_official_flow.md` — **权威 USB 协议结论**(官方源码逐行核对版)
|
||||
6. `analysis/protocol_spec.md` — 早期 PC 端协议逆向(硬件验证记录)
|
||||
7. `csdk/README.md` — C SDK 文档(构建/API/工具/测试)
|
||||
|
||||
1. `交接_完整逆向工程说明_20260811.md`
|
||||
2. `analysis/handoff_20260813.md`
|
||||
3. `analysis/handoff_20260811_full.md`
|
||||
4. `analysis/protocol_spec.md`
|
||||
5. `csdk/tools/mag160c_demo3.c`
|
||||
## 仓库结构
|
||||
|
||||
完整交接文档包含目录说明、环境部署、官方管线结论、当前源码状态、已失败方案、剩余问题和给下一位 AI 的接手提示词。
|
||||
| 路径 | 用途 |
|
||||
|---|---|
|
||||
| `android/` | **安卓统一 App**(Kotlin + Compose,Gradle 工程,package `com.mag160c.thermal`):实时预览、拍照 MDT、录像、媒体库、离线分析、PDF 报告、任务巡检、设置 |
|
||||
| `csdk/` | 纯 C SDK(官方管线 C 复刻:渲染/温度/IR 会话/FFC 调度,见 `csdk/README.md`)|
|
||||
| `analysis/` | 逆向工程总目录(Linux/Windows/Android 三条 SDK 线的解包产物与结论,见 `analysis/README.md`)|
|
||||
| `IR_Camera_SDK-1.0.1/` | 原厂 Windows/Android/Linux SDK 原始包 |
|
||||
| `app/` | 官方安卓 APK 存档(普通版/专业版/ThermoScope/demo)|
|
||||
| `build-artifacts/` | 构建产物(安卓 APK、PC demo3、DDT 标定数据)|
|
||||
| `docs/android_app/` | 安卓 App 文档:`app_manual.md` 操作手册 / `HANDOFF_DEVELOPMENT.md` 交接 / `session_state.md` 进度心跳 / 逆向功能总表 |
|
||||
| `vendor-docs/` | 厂商资料 |
|
||||
| `tools/` | 会话恢复脚本 |
|
||||
|
||||
## 当前硬件
|
||||
## 快速入口
|
||||
|
||||
- 设备:MAG160C 热像仪
|
||||
- 分辨率:160×120
|
||||
- USB VID:`0x833C`
|
||||
- 常见 PID:`0x0001`
|
||||
- 原始帧:19200 个小端 `uint16`
|
||||
- 实时帧端点:`0x81`
|
||||
- 命令端点:`0x03`,响应端点:`0x82`
|
||||
### 安卓 App(构建/测试)
|
||||
```powershell
|
||||
cd C:\Project\MAG160C\android
|
||||
$env:JAVA_HOME = "C:\Tools\jdk-21"
|
||||
.\gradlew.bat :app:assembleDebug test --no-daemon
|
||||
# 产物:build-artifacts\mag160c-app-debug.apk
|
||||
```
|
||||
|
||||
## 当前演示程序
|
||||
### C 管线复刻 demo3
|
||||
```powershell
|
||||
gcc -O2 -w -DMAG160C_STATIC -I"csdk\third_party\libusb\win64" -I"csdk\include" -I"csdk\src" `
|
||||
-o build-artifacts\mag160c_demo3.exe csdk\tools\mag160c_demo3.c `
|
||||
csdk\src\mag160c_display.c csdk\src\mag160c_error.c `
|
||||
csdk\third_party\libusb\win64\libusb-1.0.x64.a -lgdi32 -luser32
|
||||
Start-Process "build-artifacts\mag160c_demo3.exe" -WorkingDirectory "build-artifacts"
|
||||
```
|
||||
|
||||
- 源码:`csdk/tools/mag160c_demo3.c`
|
||||
- 程序:`build-artifacts/mag160c_demo3.exe`
|
||||
- 运行目录:`build-artifacts`
|
||||
- 官方 NUC 表:`build-artifacts/mag160c_official_nuc_gain.bin`、`build-artifacts/mag160c_official_nuc_thr.bin`
|
||||
## 硬件
|
||||
|
||||
编译和启动命令见完整交接文档。不要在没有阅读交接文档的情况下继续调窗口、调色板或显示层去鬼影。
|
||||
- 设备:MAG160C 热像仪机芯,160×120 @15fps
|
||||
- USB:VID `0x833C` PID `0x0001`;端点 0x03 命令 / 0x82 响应 / 0x81 图像流 /
|
||||
0x84 标定文件 / 0x05 标定上传
|
||||
- 协议细节:`analysis/magcx_official_flow.md`(权威)
|
||||
|
||||
## 里程碑
|
||||
|
||||
- 2026-08:官方 PC SDK(libmagcore/CoreSDKLib)逆向 + C 管线逐像素复刻(demo3)
|
||||
- 2026-09-06~09:安卓 App(纯 Kotlin,无 NDK):渲染管线移植(逐字节验证)、
|
||||
USB 层(官方协议 Java 源码逐行复刻)、相机式 UI、拍照/录像/媒体库/分析/PDF
|
||||
- 2026-09-10:真机联调打通(根因:USB 命令字节序反转,已修复并用单测锁死);
|
||||
官方 App 全量解包入库(jadx + Ghidra)
|
||||
- 2026-09-11~12:真机多轮联调(无线 adb)。修复根因级的三个问题:
|
||||
**launchMode 缺失**导致 MIUI 重复广播 ATTACHED、建出多个 Activity 争抢相机
|
||||
(重连风暴的真凶);`SET_CONFIGURATION` 复位设备导致相机反复重枚举;
|
||||
**实时画面卡顿**——热像走 `lockCanvas` 软件画布,每帧在 CPU 上放大整屏,
|
||||
实测只有 6.5 帧/秒、单帧 134ms,改用硬件画布后 **15.1 帧/秒、单帧 6.4ms**。
|
||||
同期完成:官方 7×7 局部细节增强移植(含混叠系数修正与黄金值测试)、
|
||||
测温点标注风格重做(准星+描边字,去白底)、拍照/录像方向跟随握持角度、
|
||||
录像 3× 分辨率、分析页与照片标记完全对齐、设置持久化
|
||||
- 文档:`docs/android_app/app_manual.md`(操作手册)、
|
||||
`docs/android_app/session_state.md`(第 18~24 轮修复记录与排查方法)
|
||||
|
||||
## 仓库说明
|
||||
|
||||
仓库包含官方 SDK、第三方依赖、二进制样本和抓帧数据。部分内容可能受原厂许可限制,仅用于本项目的互操作性研究和设备调试。大型文件使用 Git LFS。
|
||||
仓库包含官方 SDK、反编译产物、二进制样本和抓帧数据。部分内容可能受原厂许可
|
||||
限制,仅用于本项目的互操作性研究和设备调试。
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# analysis/ — MAG160C 逆向工程总目录
|
||||
|
||||
> 本目录汇总了 MAG160C 相关的**全部逆向工程材料**,覆盖三条产品线:
|
||||
> ① Linux SDK(libmagcore.so)② Windows SDK(CoreSDKLib.dll / ThermalSDK.dll)
|
||||
> ③ 官方安卓 App(MAG-Cx 普通版:Java 层 + libcxsdk.so / libcoresdk.so)。
|
||||
> 阅读顺序建议:先读本 README → `magcx_official_flow.md`(权威协议结论)
|
||||
> → 按需深入各分目录。
|
||||
|
||||
## 顶层文件(结论与工具)
|
||||
|
||||
| 文件 | 内容 |
|
||||
|---|---|
|
||||
| `magcx_official_flow.md` | **权威协议结论**(2026-09-10,官方 Java 源码逐行核对版):命令表、BasePara/CaliInfo 布局、连接序列、标定文件下载、FFC 逻辑 |
|
||||
| `protocol_spec.md` | 早期 PC 端逆向协议(libmagcore/CoreSDKLib,2026-08-10 硬件验证),历史参考 |
|
||||
| `gen_kotlin_tables.py` | 生成 `OfficialTables.kt`(palette256/T2E/E2T)的脚本 |
|
||||
| `render_offline.c` | PC 端参考渲染管线(gcc 编译),Kotlin 移植的逐字节差分基准 |
|
||||
| `official_render_frame.rgb` / `official_t2e_table.txt` / `t2e_table.json` / `test_ref_stats.txt` | PC 差分验证数据 |
|
||||
| `revtools.py` / `reverse_tools/` | 逆向辅助脚本 |
|
||||
|
||||
## sdk_re/ — 按 SDK 产品线归档
|
||||
|
||||
### `sdk_re/linux_libmagcore/` — Linux SDK(libmagcore.so.2.1.1,x86-64)
|
||||
- `magcore_disasm.txt`(全量反汇编)+ `magcore_defined.txt` / `magcore_dynsyms.txt` / `magcore_relocs.txt`
|
||||
- `disasm_*.txt`:关键函数级反汇编(LinkCameraEx / StartProcessImage / Raw2Temperature /
|
||||
ConvertResponse2Temp / ReviseTemperature / ffc / frameParse / AccumulatorPushFrame 等)
|
||||
- `all_functions_decomp_libmagcore.so_00100000.txt`:Ghidra 全量伪代码
|
||||
|
||||
### `sdk_re/windows_sdk/` — Windows SDK(CoreSDKLib.dll / ThermalSDK.dll)
|
||||
- `refs_CoreSDKLib.dll.txt` / `exports_decomp_CoreSDKLib.dll.txt` / `keyfuncs_decomp_CoreSDKLib.dll.txt`
|
||||
/ `callers_decomp_CoreSDKLib.dll.txt` / `coresdk_keyfuncs_disasm.txt` / `coresdk_windows_disasm.txt`
|
||||
- `exports_decomp_ThermalSDK.dll.txt` + `thermalsdk_dll/` + `thermalsdk_so/`(ThermalSDK 包装层)
|
||||
|
||||
### `sdk_re/android_app/` — 官方安卓 App(普通版 MAG-Cx,本管线上真机跑通的代码)
|
||||
- `jadx_magcx/`:**全量 Java 源码**(jadx 1.5.1)。核心:`cn/com/magnity/magnitycx/sdk/UsbCommunication.java`
|
||||
(USB 协议真身)、`P2DCmd.java`/`D2PCmd.java`(命令表)、`DeviceController.java`(JNI 封装)
|
||||
- `libcxsdk_decomp.txt`:Ghidra 全量伪代码(libcxsdk.so,1290 函数;Controller::StartProcess /
|
||||
PushFrame / LoadCalibrationTable 已核对)
|
||||
- `all_functions_decomp_libcoresdk_arm64.so_00100000.txt`:libcoresdk(arm64) 全量伪代码
|
||||
(专业版/网络路径,CNetComm 命令层)
|
||||
- `coresdk_arm64_defined.txt`:ARM64 库符号表
|
||||
|
||||
## history/ — 过程记录(按时间)
|
||||
`task_plan_20260714` → `findings_20260810` / `progress_20260810` / `handoff_20260810..13` /
|
||||
`交接_完整逆向工程说明_20260811`(PC SDK 全流程报告)→ `reverse_20260813_full` /
|
||||
`progress_reverse`。历史参考,协议细节以 `magcx_official_flow.md` 为准。
|
||||
|
||||
## preview/ — 渲染验证截图
|
||||
demo2_v3..v7 系列(管线复刻过程对照)、preview_*.png(安卓 UI 验证)。
|
||||
|
||||
## 与代码库的关系
|
||||
- `csdk/src/`:依据 Linux SDK 逆向写出的 C 参考实现(渲染/温度/IR 会话/FFC 调度),
|
||||
Kotlin 管线(android/app)与其逐字节差分验证一致。
|
||||
- `android/app/src/main/java/com/mag160c/thermal/usb/IrSession.kt`:官方
|
||||
UsbCommunication.java 的 Kotlin 等价实现(含小端命令、800ms 超时、0x84 标定下载)。
|
||||
- `docs/android_app/HANDOFF_DEVELOPMENT.md` §4.2 指向本目录。
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate Kotlin tables from csdk C headers (palette256, t2e)."""
|
||||
import re, sys, io
|
||||
|
||||
CSDK = r"C:\Project\MAG160C\csdk\src"
|
||||
OUT = r"C:\Project\MAG160C\android\app\src\main\kotlin\com\mag160c\thermal\core\OfficialTables.kt"
|
||||
|
||||
def read(fn):
|
||||
with open(fn, encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
def parse_matrix(text, rows=256):
|
||||
vals = []
|
||||
for m in re.finditer(r"\{\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\}", text):
|
||||
vals.append((int(m.group(3)), int(m.group(2)), int(m.group(1)))) # R,G,B from (B,G,R,0)
|
||||
if len(vals) == rows:
|
||||
break
|
||||
return vals
|
||||
|
||||
def parse_int32(text):
|
||||
body = text[text.index("{"):]
|
||||
return [int(x) for x in re.findall(r"-?\d+", body)]
|
||||
|
||||
pal_txt = read(rf"{CSDK}\mag160c_official_palette256.h")
|
||||
t2e_txt = read(rf"{CSDK}\mag160c_official_t2e.h")
|
||||
tables_txt = read(rf"{CSDK}\mag160c_tables.h")
|
||||
|
||||
pal = parse_matrix(pal_txt)
|
||||
assert len(pal) == 256, f"palette entries: {len(pal)}"
|
||||
t2e = parse_int32(t2e_txt)
|
||||
assert len(t2e) == 646, f"t2e entries: {len(t2e)}"
|
||||
|
||||
# mag160c_t2e (274 entries, hex) + mag160c_e2t_acc_q10 (274 entries)
|
||||
def parse_hex_array(text, name):
|
||||
m = re.search(rf"static const uint32_t {name}\[MAG160C_TEMP_CURVE_ENTRIES\] = \{{(.*?)\}};", text, re.S)
|
||||
assert m, name
|
||||
return [int(x, 16) for x in re.findall(r"0x[0-9a-fA-F]+", m.group(1))]
|
||||
|
||||
t2e274 = parse_hex_array(tables_txt, "mag160c_t2e")
|
||||
assert len(t2e274) == 274, f"t2e274: {len(t2e274)}"
|
||||
e2t = parse_hex_array(tables_txt, "mag160c_e2t_acc_q10")
|
||||
assert len(e2t) == 274, f"e2t: {len(e2t)}"
|
||||
|
||||
# palette ARGB int per gray index
|
||||
lines = []
|
||||
lines.append("package com.mag160c.thermal.core")
|
||||
lines.append("")
|
||||
lines.append("/** Generated from csdk C headers by analysis/gen_kotlin_tables.py. DO NOT EDIT. */")
|
||||
lines.append("object OfficialTables {")
|
||||
lines.append(" /** Official default palette (256 gray levels -> ARGB int), source: CoreSDKLib dev+0xb18. */")
|
||||
lines.append(" val PALETTE256_ARGB = intArrayBuilder {")
|
||||
for i, (r, g, b) in enumerate(pal):
|
||||
argb = (0xFF << 24) | (r << 16) | (g << 8) | b
|
||||
lines.append(f" add({argb}) // {i}")
|
||||
lines.append(" }")
|
||||
lines.append("")
|
||||
lines.append(" /** Official T2E table (646 int32), temp = slope*diff>>12 + (i<<12) - 0x249f0. */")
|
||||
lines.append(" val T2E = intArrayBuilder {")
|
||||
for i in range(0, len(t2e), 8):
|
||||
chunk = t2e[i:i+8]
|
||||
lines.append(" add(" + ", ".join(str(v) for v in chunk) + (")" if i + 8 >= len(t2e) else ""))
|
||||
lines.append(" }")
|
||||
lines.append("}")
|
||||
kt = "\n".join(lines) + "\n"
|
||||
|
||||
# intArrayBuilder trick is overkill; use direct arrays with chunked lines instead.
|
||||
argb_lines = []
|
||||
def signed(v):
|
||||
return v if v < 2**31 else v - 2**32
|
||||
for i in range(0, len(pal), 6):
|
||||
row = ", ".join(str(signed((0xFF << 24) | (r << 16) | (g << 8) | b)) for (r, g, b) in pal[i:i+6])
|
||||
argb_lines.append(" " + row + ",")
|
||||
t2e_lines = []
|
||||
for i in range(0, len(t2e), 8):
|
||||
row = ", ".join(str(v) for v in t2e[i:i+8])
|
||||
t2e_lines.append(" " + row + ("," if i + 8 < len(t2e) else ""))
|
||||
|
||||
t2e274_lines = []
|
||||
for i in range(0, len(t2e274), 8):
|
||||
row = ", ".join(f"0x{v:08x}" for v in t2e274[i:i+8])
|
||||
t2e274_lines.append(" " + row + ("," if i + 8 < len(t2e274) else ""))
|
||||
|
||||
e2t_lines = []
|
||||
for i in range(0, len(e2t), 8):
|
||||
row = ", ".join(f"0x{v:08x}" for v in e2t[i:i+8])
|
||||
e2t_lines.append(" " + row + ("," if i + 8 < len(e2t) else ""))
|
||||
|
||||
kt = f"""package com.mag160c.thermal.core
|
||||
|
||||
/** Generated from csdk C headers by analysis/gen_kotlin_tables.py. DO NOT EDIT. */
|
||||
object OfficialTables {{
|
||||
/** Official default palette: 256 gray levels -> ARGB int (from CoreSDKLib dev+0xb18, B,G,R order swapped). */
|
||||
val PALETTE256_ARGB = intArrayOf(
|
||||
{chr(10).join(argb_lines)}
|
||||
)
|
||||
|
||||
/** Official T2E table (646 int32): temp = slope*diff>>12 + (i<<12) - 0x249f0. */
|
||||
val T2E = intArrayOf(
|
||||
{chr(10).join(t2e_lines)}
|
||||
)
|
||||
|
||||
/** Vendor T2E curve from libcoresdk.so ARM64 @0x402010 (274 uint32 entries). */
|
||||
val T2E274 = intArrayOf(
|
||||
{chr(10).join(t2e274_lines)}
|
||||
)
|
||||
|
||||
/** Vendor E2TAccQ10 curve from libcoresdk.so ARM64 @0x40245c (274 uint32 entries). */
|
||||
val E2T_ACC_Q10 = intArrayOf(
|
||||
{chr(10).join(e2t_lines)}
|
||||
)
|
||||
}}
|
||||
"""
|
||||
with open(OUT, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(kt)
|
||||
print(f"written {OUT}: palette {len(pal)} entries, t2e {len(t2e)} entries")
|
||||
@@ -0,0 +1,182 @@
|
||||
# MAG160C 瀹樻柟绠$嚎瀹屾暣閫嗗悜缁撴灉锛圙hidra 鍏ㄩ噺锛?026-08-13锛?
|
||||
> 鏈枃妗h褰?CoreSDKLib.dll 瀹樻柟娓叉煋绠$嚎鐨勫畬鏁村弽缂栬瘧缁撹銆侀€愬儚绱犻獙璇佺粨鏋滐紝
|
||||
> 浠ュ強 demo3 鏃х増"楝煎奖"闂鐨勬牴鍥犲垎鏋愩€傛墍鏈夌粨璁哄潎鏈?Ghidra 鍙嶇紪璇戜唬鐮佸拰
|
||||
> 瀹樻柟娲讳綋鎶撳抚鏁版嵁鐨勫弻閲嶈瘉鎹紝涓嶆槸鎺ㄦ祴銆?>
|
||||
> 宸ュ叿閾撅細Ghidra 12.1.2锛坔eadless 鎵瑰鐞嗗弽缂栬瘧锛? IDA Free 8.4锛堜氦浜掔‘璁わ級銆?> 鍙嶇紪璇戜骇鐗╋細`analysis/ida/export/ghidra_dump/`锛坘eyfuncs / callers / exports锛夈€?
|
||||
---
|
||||
|
||||
## 1. 瀹樻柟绠$嚎锛圙hidra 鍙嶇紪璇戠‘璁わ級
|
||||
|
||||
```
|
||||
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锛團FC 鐘舵€佹満 + 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锛?-tap 鍙岀嚎鎬?+ 杈圭紭澶栨帹, 瑙?搂6锛? 鈫?璋冭壊鏉?dev+0xb18 (256脳4 BGR)
|
||||
```
|
||||
|
||||
## 2. DDT 鏍″噯鏂囦欢锛堣〃鐨勬暟鎹簮锛?
|
||||
- 浣嶇疆锛歚%TEMP%\Core<搴忓垪鍙?`锛堟棤鎵╁睍鍚嶏級锛岀敱 app 鐢?MAG_SaveDDT 淇濆瓨銆? MAG_StartProcessImage 鈫?0x18000f130 鍦ㄥ惎鍔ㄦ椂鍔犺浇锛涙枃浠惰矾寰勫瓨 dev+0x4134c銆?- 鏈満鏂囦欢宸插鍒讹細`build-artifacts/mag160c_official.ddt`锛?,856,416 瀛楄妭锛寁3锛夈€?
|
||||
```
|
||||
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 鏃跺簭锛?x180009ee0 / 0x180016ae0锛?
|
||||
### 3.1 蹇棬娓╁害 = 璁惧娓╁害浠g悊
|
||||
- 蹇棬 = 甯у熬瀛楋紙width=160 鏃?frame[len+0x24]锛夛紝姣忓抚杩涘叆 metadata銆?- dev+0x5c = 褰撳墠蹇棬锛宒ev+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锛圱[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 璇樊锛実ain 0/115200 璇樊銆?
|
||||
### 3.4 ref 閲囬泦鏃跺簭锛堝叧閿紒锛?```
|
||||
FFC(0) 鍚庡抚璁℃暟澶嶄綅:
|
||||
idx 1..5 : 琛ㄩ噸寤猴紙鍒嗗潡锛? idx 6..9 : 閲囬泦 ref 鈥斺€?4 涓?type=1 鏍″噯甯х殑鍧囧€硷紙绱姞 >> 2锛? 锛坕dx 6 澶嶄綅骞虫粦鍣紝idx 9 鍙?FFC(1)锛? idx 10..13 : 琛ㄩ噸寤? idx 14+ : 姝e父娓叉煋锛坮ef 鍐荤粨锛岀洿鍒颁笅娆?FFC锛?```
|
||||
- **ref = 4 涓?type=1 甯х殑鍧囧€?*锛屼笉鏄満鏅抚锛?- type=1 甯у潎鍊煎疄娴?~15000-16300锛宼ype=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锛圡AE 0.118锛墊
|
||||
| NUC + 鐩插厓琛ュ伩 | **diff 0/19200锛圡AE 0.0000锛?* |
|
||||
| 绐楀彛 [lo,hi] | 涓庡畼鏂瑰畬鍏ㄤ竴鑷?|
|
||||
| 鐏板害 (LUT+idx) | 160 灞?19200/19200 |
|
||||
| 2x 鍗囬噰鏍?| **diff 0/76800** |
|
||||
| demo3 C 瀹炵幇绂荤嚎澶嶇畻瀹樻柟鏁版嵁 | NUC/绐楀彛/鐏板害鍏ㄩ儴 0 璇樊 |
|
||||
|
||||
LUT 濉厖鍏紡锛氬湪姝g‘ center 涓?834/1024 绮剧‘锛屽叾浣?卤1~卤58 涓?璺ㄥ抚鎹曡幏鍋忓樊锛坔1024/cdf/lut 涓嶅悓甯э級+ center 鏃跺煙骞虫粦鎵€鑷淬€?
|
||||
## 5. LUT1024 閲嶅缓绠楁硶锛?x180011950 + 0x180011ee0锛?
|
||||
1. 1024-bin 鐩存柟鍥撅細bin = ((nuc-lo) * 0xffc00000/(hi-lo)) >> 22锛坲32 绠楁湳锛?2. 3-tap 灏卞湴骞虫粦锛坆in 0..1022锛?3. **0x8c 鍒嗘敮锛坙ive=1锛?*锛氭壘骞虫粦鍚庣洿鏂瑰浘绱 >= 鎬昏鏁懊?/4 鐨勭涓€涓?bin锛? CDF 涓績 = max(鍧囧€糱in, 璇in)锛堝疄娴?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 + 涓績鎼滅储锛孡UT 123/1024 宸紓锛屽叏閮ㄩ泦涓湪 lres 涓婃柟
|
||||
鐏板害杩囨浮鍖猴紙卤1-7 绾э級锛屾潵婧愭槸瀹樻柟 SDK 鎶撳抚鐨勮法甯у紓姝ワ紙cdf/lut 涓嶅悓甯э級锛?闈炵畻娉曞樊寮傘€傚畼鏂逛細璇濆疄娴嬪惎鐢ㄨ矾寰?= 0x109c0 鈫?0x11ee0 鈫?0x19740锛?temporal(0x41fe0=1)/甯у樊(0x230=0)/闅旇骞虫粦(0x41fe8=0)/0x30 gray 璺緞(0x30=0)
|
||||
鍏ㄩ儴鍏抽棴锛宒emo3 榛樿涓€鑷达紝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锛坴4锛孲BNUC 鐗堬級涓庡畼鏂圭殑宸紓锛屾瘡涓€鏉¢兘鏈変笂闈㈢殑璇佹嵁锛?
|
||||
### 7.1 ref 璇箟瀹屽叏涓嶅悓锛堟渶鏍规湰锛?| | 瀹樻柟 | 鏃?demo3 |
|
||||
|---|---|---|
|
||||
| ref 鏉ユ簮 | FFC 绐楀彛 4 涓?**type=1 鏍″噯甯?*鍧囧€?| 鍚姩鍦烘櫙 12 甯т腑鍊?|
|
||||
| ref 鍊煎煙 | 鍦烘櫙 + ~2200 counts锛坉 鎭掕礋锛墊 鈮?鍦烘櫙锛坉 鈮?0锛墊
|
||||
| 鍒锋柊鏃舵満 | **姣忔 FFC 瀵逛箣鍚?* | 浠呭惎鍔ㄤ竴娆★紙FFC 鍚庡彧 rebase 鍏ㄥ眬鍋忕疆锛墊
|
||||
| FFC 涔嬮棿 | 鍐荤粨 | 鍐荤粨 + SBNUC 鑷剤 |
|
||||
|
||||
鍚庢灉锛?- 瀹樻柟 NUC 杈撳嚭 鈮?9800锛坥ff 娈甸厤鍚?d 鎭掕礋锛夛紝鏃?demo3 杈撳嚭 鈮?10900+锛坉鈮?锛夛紝
|
||||
涓よ€呭鍚屼竴鍦烘櫙鐨勭敾闈㈢瓑绾ч兘涓嶅悓锛屾洿璋堜笉涓婃畫褰辫涓轰竴鑷淬€?- 鏃?demo3 鍚姩閲囬泦涓€娆?ref 鍚庢案涓嶅埛鏂?鈫?鍦烘櫙鍩虹嚎婕傜Щ锛堝璁惧鍗囨俯銆? 鐜娓╁害鍙樺寲锛夊悗 ref 澶遍厤 鈫?鏃т綅缃嚭鐜?榛戞礊/榛戞嫋褰?锛屼笖鍙兘绛夋墜鍔?FFC銆?- 瀹樻柟姣忔 FFC 閲嶉噰 ref 鈫?娈嬪奖鏈€澶氭寔缁埌涓嬫 FFC锛堝懆鏈?120s 鎴栧揩闂ㄦ紓绉?250 瑙﹀彂锛夛紝
|
||||
涓旇〃涔熸寜鏂板揩闂ㄩ噸寤猴紝鍖归厤濮嬬粓鎴愮珛銆?
|
||||
### 7.2 NUC 琛ㄤ笉鏄潤鎬佺殑
|
||||
- 瀹樻柟锛氳〃 = DDT 绔偣鎸夊揩闂ㄦ俯搴?Q12 鎻掑€硷紝FFC 鍚庨噸寤恒€?- 鏃?demo3锛氶潤鎬佹姄鍙栬〃锛堟煇娆′細璇濈殑鎻掑€肩粨鏋滐級锛屾俯搴︽紓绉诲悗澶遍厤锛? 琛ㄧ幇涓?灏忔俯宸畫褰辨摝闄ゆ參"鈥斺€旀湰璐ㄦ槸琛ㄤ笌褰撳墠娓╁害涓嶅尮閰嶉€犳垚鐨勬畫宸紝
|
||||
涓嶆槸鐪熸鐨勬樉绀哄眰楝煎奖銆?
|
||||
### 7.3 SBNUC 鏄潪瀹樻柟 hack锛屽紩鍏ユ绾ч褰?- 鏃?demo3 鐨?鏇剧儹鍙樺喎蹇€熷惛鏀?浣庢俯闈欐涓嶅惛鏀?鐘舵€佹満锛屼細鎶婁綆娓╃墿浣? 鎺掗櫎鍦?ref 涔嬪 鈫?鐗╀綋绂诲紑鍚庣暀涓嬩寒娈嬪奖锛涙妸鏇剧儹鍍忕礌鍐欏洖 ref 鈫? 鏀瑰彉灞€閮ㄥ熀绾?鈫?鏂伴粦娲炪€?- 瀹樻柟娌℃湁 SBNUC锛歳ef 鍐荤粨 + 瀹氭湡 FFC 鍒锋柊鏄畼鏂瑰敮涓€鐨?鍘婚褰?鏈哄埗銆?
|
||||
### 7.4 鐩插厓琛ュ伩缂哄け
|
||||
- 瀹樻柟鏈?32 鏉″浐瀹氱洸鍏冭褰曪紙閭诲煙骞冲潎锛夛紝鏃?demo3 瀹屽叏娌℃帴 鈫?姝诲儚绱? 鐩存帴鏄剧ず锛? 鍊奸粦鐐癸級锛屾浘鐢ㄥ姩鎬?badmap 灏濊瘯 鈫?鍙嶈€屾妸鍦烘櫙缁撴瀯閽夊湪
|
||||
灞忓箷鍧愭爣锛圔ADMAP_ENABLE=0 鍚庢畫鐣欓粦鐐癸級銆?
|
||||
### 7.5 绐楀彛/鐏板害宸紓
|
||||
- 瀹樻柟绐楀彛 = [min(fmin, mean-312), max(fmax, mean+312)]锛? 鏃?demo3 鐢?P2..P98 鑷€傚簲 鈫?瀵规瘮搴﹁涓轰笉鍚岋紝浣嗕笉鏄褰变富鍥犮€?
|
||||
### 7.6 缁撹
|
||||
鏃?demo3 鐨?楝煎奖"鏄?ref 璇箟閿欒 + ref/琛ㄤ笉闅?FFC 鍒锋柊 + 闈欐€佽〃 +
|
||||
闈炲畼鏂?SBNUC 鍏卞悓閫犳垚鐨勶紝涓庡畼鏂?鍐荤粨 ref 鐨勮交寰棫浣嶇疆鍋忔殫"涓嶆槸
|
||||
鍚屼竴涓幇璞°€備慨澶?= 瀹屾暣澶嶅埢瀹樻柟绠$嚎锛坉emo3 v5 宸插疄鐜帮紝瑙佷笅锛夈€?
|
||||
## 8. demo3 v5锛堝畼鏂瑰鍒伙級瀹炵幇鐘舵€?
|
||||
### 8.1 瀹樻柟 0x18000ca30 璋冪敤閾惧疄鐜板鐓ц〃锛?026-08-14 瀹¤锛?
|
||||
| 瀹樻柟鐜妭 | 鎺у埗瀛楁(live) | demo3 | 璇存槑 |
|
||||
|---|---|---|---|
|
||||
| 甯ч鏍囧織娓呴浂 0x4203c/40/44/04 | 鈥?| 鉁?| 宸茶ˉ |
|
||||
| c950: f20 骞虫粦(mode=1 鐩撮€?+NUC+blind | 41fdc=1 | 鉁?| NUC+blind 0/19200 璇樊 |
|
||||
| temporal 0x18001e920 | 41fe0=1(鍏? | 鉁?宸茬Щ妞?| 0x41fe0>1 鍚敤锛岄粯璁ゅ叧=瀹樻柟 |
|
||||
| 甯у樊璋冩暣 0x1800175a0 | 230=0(鍏? | 猬?鏈Щ妞嶄富浣?| 瀹樻柟鍏筹紝绛変环 |
|
||||
| 闅旇骞虫粦 0x1800184f0/660 | 41fe8=0(鍏? | 猬?鏈Щ妞嶄富浣?| 瀹樻柟鍏筹紝绛変环 |
|
||||
| 缁熻 0x180010780 | 鈥?| 鉁?| min/max/mean/std 涓€鑷?|
|
||||
| 绐楀彛+LUT+gray 0x1800109c0/0x11ee0 | 90=0, 8c=1 | 鉁?| 鍚?0x8c 75% 涓績銆乽niform 鍒嗘敮 |
|
||||
| 0x30 gray 瑕嗙洊璺緞 0x180017770/0x17c40 | 30=0(鍏? | 猬?鏈Щ妞嶄富浣?| 瀹樻柟鍏筹紝绛変环 |
|
||||
| 鍗囬噰鏍?0x19xxx | 2x | 鉁?2x | 4x/8x/1.5x 鏈Щ妞嶏紙褰撳墠 2x锛墊
|
||||
| 娓╁害鎹㈢畻 0x18000d2a0 | 鈥?| 鉁?counts_to_c | 鎺㈤拡/鏈€楂樻俯鏄剧ず |
|
||||
| FFC 鐘舵€佹満 0x180009ee0 | 鈥?| 鉁?| 鍛ㄦ湡1800/婕傜Щ250/甯?5寮哄埗 |
|
||||
| ref 骞虫粦鍣?0x1800011a0(mode=4) | 41fdc=1 | 鉁?| 4脳type1 甯у潎鍊?|
|
||||
| 琛ㄩ噸寤?0x18000c760/16ae0/dd0/f10 | 鈥?| 鉁?| 蹇棬椹卞姩绔偣+Q12 |
|
||||
| 绗簩骞虫粦鍣?0x41f90 | 41fbc=1 | 猬?鏃犺皟鐢?| 瀹樻柟鏃犳覆鏌撹皟鐢紝绛変环 |
|
||||
|
||||
### 8.2 2026-08-14 瀹屾暣瀵规瘮缁撴灉锛坉emo3 vs 瀹樻柟鍚屽満鏅法浼氳瘽锛?
|
||||
- NUC+鐩插厓锛?/19200 鍍忕礌璇樊锛堢绾匡紝瀹樻柟鍚屽抚鏁版嵁锛?- 绐楀彛/缁熻锛氬畬鍏ㄤ竴鑷达紙绂荤嚎锛?- gray+2x锛?/76800 鍍忕礌璇樊锛堢绾匡級
|
||||
- LUT 缁撴瀯锛歞emo3 [500]=41 vs 瀹樻柟 40锛孾900]=242 vs 242锛堝舰鎬佸悓鏋勶級
|
||||
- gray 鐩存柟鍥剧浉鍏虫€э細0.966锛堝疄鏈鸿法浼氳瘽锛?- 闆跺儚绱狅細涓よ竟鍧囦负 0
|
||||
- 宸紓鏉ユ簮锛氫細璇濋棿 ref/蹇棬宸紓锛堝畼鏂圭墿鐞嗘満鍒讹級
|
||||
|
||||
## 9. demo3 v5 杩愯渚濊禆涓庝骇鐗?
|
||||
- DDT 鍔犺浇锛坴3 瑙f瀽锛屽惈绔偣琛?鐩插厓璁板綍锛?- 绔偣閫夋嫨 + Q12 鎻掑€硷紙蹇棬椹卞姩锛孎FC 鍚庨噸寤猴級
|
||||
- ref = FFC 绐楀彛 4 涓?type=1 甯у潎鍊硷紙u32 绱姞鍣級
|
||||
- NUC + 鐩插厓琛ュ伩锛?2 鏉″浐瀹氳褰曪級
|
||||
- 绐楀彛/缁熻锛堝畼鏂瑰叕寮忥級
|
||||
- LUT1024 閲嶅缓锛堢洿鏂瑰浘鍧囪 + center 骞虫粦锛?- 鐏板害 + 2x 鍗囬噰鏍?+ 瀹樻柟璋冭壊鏉?- FFC锛氬懆鏈?1800 / 蹇棬婕傜Щ 250 / 13 甯ч殣钘忓懆鏈?- 瀹炴満杩愯锛歴el=2銆丗FC 鑷姩瑙﹀彂姝e父銆佺敾闈㈢粺璁℃帴杩戝畼鏂? 锛坉emo3 mean 9377 vs 瀹樻柟 9640锛?
|
||||
### 閬楃暀闂
|
||||
1. ~~demo3 NUC 杈撳嚭鏈?2 涓浂鍍忕礌~~ **宸蹭慨澶嶏紙2026-08-13 鏅氾級**锛? 鏍瑰洜鏄?demo3 `load_ddt` 璇诲彇甯冨眬閿欒鈥斺€攂lind 璁板綍鍖哄湪鏂囦欢鎵€鏈夌鐐瑰潡涔嬪悗锛? 浠g爜鍗村湪姣忎釜绔偣鍧楀唴璇讳簡 blind锛屽鑷?EP1+ 琛ㄦ暣浣撻敊浣?1280 瀛楄妭銆? 閿欎綅琛ㄥ湪姝诲儚绱犲垪闄勮繎 seg0 gain=35321锛堝簲涓?~4000锛夛紝d2 韪╀腑鍚?NUC clamp 鍒?0銆? 淇鍚庡叏甯?0 涓浂鍍忕礌锛?65,25) nuc=9814锛実ain=4000 姝e父鍊笺€?2. LUT 閲嶅缓鐨勮法甯т竴鑷存€э細绠楁硶宸叉寜鍙嶇紪璇戝疄鐜板苟瀹炴満杩愯锛屽悓鍦烘櫙 gray 鐩存柟鍥? 涓庡畼鏂圭浉鍏虫€?0.9197锛堣法浼氳瘽锛夛紝鍓╀綑宸紓鏉ヨ嚜浼氳瘽闂?ref/蹇棬娓╁害宸紓锛? 灞炲畼鏂圭墿鐞嗘満鍒讹紝涓嶆槸 bug銆?
|
||||
## 10. 鍏抽敭鏂囦欢
|
||||
|
||||
- 鍙嶇紪璇戝叏閲忥細`analysis/ida/export/ghidra_dump/`
|
||||
- `keyfuncs_decomp_CoreSDKLib.dll.txt`锛堟牳蹇冨嚱鏁颁吉浠g爜锛? - `callers_decomp_CoreSDKLib.dll.txt`锛堣皟鐢ㄥ叧绯?+ 涓荤绾匡級
|
||||
- `exports_decomp_CoreSDKLib.dll.txt`锛堝叏閮ㄥ鍑哄嚱鏁帮級
|
||||
- `exports_decomp_ThermalSDK.dll.txt`
|
||||
- `all_functions_decomp_libcoresdk_arm64.so_00100000.txt`锛圓RM64 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 澶嶅埗銆佽〃/鐩插厓/蹇棬/绐楀彛鎶撳彇锛?
|
||||
@@ -0,0 +1,85 @@
|
||||
# MAG160C 会话状态与恢复点(2026-08-13)
|
||||
|
||||
> 本文件是"心跳"锚点:每个里程碑更新一次。若会话中断,新会话先读本文件 +
|
||||
> `analysis/reverse_20260813_full.md`,然后运行 `tools/resume_rev.ps1` 继续。
|
||||
|
||||
## 当前阶段
|
||||
|
||||
**阶段 7/7:LiThermal 对接评估 + 抽象层完成(2026-08-19)**
|
||||
|
||||
## 已完成的里程碑
|
||||
|
||||
- [x] 工具链:Ghidra 12.1.2(headless)+ IDA Free 8.4 部署,5 个二进制全量导出
|
||||
- [x] 官方管线完整反编译(DDT 加载 / 端点选择 / Q12 插值 / ref=4×type1 帧均值 /
|
||||
NUC / 盲元 / 窗口 / LUT1024 / 2x 升采样 / FFC 状态机)
|
||||
- [x] DDT 文件解析(%TEMP%\Core160043865 → build-artifacts/mag160c_official.ddt)
|
||||
- [x] 逐像素验证:NUC+盲元 0/19200、插值 0 误差、2x 0/76800、窗口一致
|
||||
- [x] 鬼影根因文档:analysis/reverse_20260813_full.md §7
|
||||
- [x] demo3 v5 官方复刻实现并实机运行
|
||||
- [x] 零像素根因定位与修复(load_ddt 表错位)
|
||||
- [x] **颗粒感修复**:demo3 显示层改为官方同款 320×240(2x 平滑灰度 +
|
||||
调色板 + HALFTONE 放大),同场景 RGB 直方图相关性 0.9796
|
||||
- [x] **FFC 频率对齐**:补官方"启动强制 FFC"逻辑(mode 0 帧 75 无条件
|
||||
FFC(0),trace 实测启动阶段 FFC(0) 间隔 59~394 帧 ≈ 4~26s,
|
||||
稳定后 1800 帧 ≈ 120s,demo3 已一致)
|
||||
- [x] 官方附加环节全部移植(df6c6dd):0x230 帧差 / 0x41fe8 隔行平滑 /
|
||||
0x30 gray 覆盖(auto/manual)/ Nx 升采样,编译开关 OFF_IMPL_*
|
||||
- [x] **平台无关渲染模块 mag160c_render**(5c22bfe):官方管线封装为无
|
||||
平台依赖 API(输入 USB 帧 → 输出 320×240 RGB24),FFC 命令走回调
|
||||
- [x] **Linux 参考程序 mag160c_linux_demo**(5c22bfe):libusb + render API
|
||||
+ 显示后端抽象(fbdev/DRM/LVGL),Windows 可编译验证,实测正常
|
||||
- [x] **demo3 迁移到 render 库**(9db940a):删内联管线 ~700 行,附加环节
|
||||
经 post_nuc/post_gray 钩子挂接;FFC 时序/画面与 v5 一致,无回归
|
||||
- [x] 移植评估文档 docs/linux_port_plan.md:芯片推荐 T113-S3(QFP128 非
|
||||
BGA ~20 元)、动画库 LVGL+rlottie(LiThermal 同款)、整机成本估算
|
||||
- [x] 仓库整理 + csdk/README.md 完整文档 + 本地 commit 0bfb926…9db940a(7 个)
|
||||
- [ ] push(等服务器上线,用户手动)
|
||||
|
||||
## 颗粒感问题(已解决,2026-08-14)
|
||||
|
||||
- 现象:官方画面比 demo3 清晰,demo3 颗粒感重。
|
||||
- 根因:demo3 v5 的显示层渲染的是 160×120 原始灰度(最近邻放大到 640×480),
|
||||
而官方输出是 2x 升采样后的 320×240(2-tap 平滑本身抑噪),官方 app 显示
|
||||
的也是这个 2x 缓冲。
|
||||
- 排查过程(已排除的嫌疑):FUN_1800175a0(0x230)是帧差偏移调整非平滑;
|
||||
0x41fe8 后处理(FUN_1800184f0/660 隔行平滑)官方会话 =0 未启用;
|
||||
temporal filter 0x41fe0=1 未启用;f20 mode=1 直通。
|
||||
- 修复:render_bmp 改用 g_gray320(官方 2x 缓冲)上色,BMP/窗口改 320×240,
|
||||
StretchBlt 加 HALFTONE。
|
||||
- 验证:320×240 BMP 与官方同场景 pair_000.rgb 直方图相关性 0.9796,
|
||||
颜色数 227 = 官方 227(之前 160×120 版只有 ~161 色)。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```powershell
|
||||
# 编译 demo3 v6(render 库版)
|
||||
gcc -O2 -w -DMAG160C_STATIC -I"C:\Project\MAG160C\csdk\third_party\libusb\win64" -I"C:\Project\MAG160C\csdk\include" -I"C:\Project\MAG160C\csdk\src" -o "C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe" "C:\Project\MAG160C\csdk\tools\mag160c_demo3.c" "C:\Project\MAG160C\csdk\src\mag160c_render.c" "C:\Project\MAG160C\csdk\src\mag160c_display.c" "C:\Project\MAG160C\csdk\src\mag160c_error.c" "C:\Project\MAG160C\csdk\third_party\libusb\win64\libusb-1.0.x64.a" -lgdi32 -luser32
|
||||
|
||||
# Linux 参考程序(Windows 可编译运行,出 BMP)
|
||||
gcc -O2 -w -DMAG160C_STATIC -I"C:\Project\MAG160C\csdk\third_party\libusb\win64" -I"C:\Project\MAG160C\csdk\include" -I"C:\Project\MAG160C\csdk\src" -o "C:\Project\MAG160C\build-artifacts\mag160c_linux_demo.exe" "C:\Project\MAG160C\csdk\tools\mag160c_linux_demo.c" "C:\Project\MAG160C\csdk\src\mag160c_render.c" "C:\Project\MAG160C\csdk\src\mag160c_ir.c" "C:\Project\MAG160C\csdk\src\mag160c_frame.c" "C:\Project\MAG160C\csdk\src\mag160c_temp.c" "C:\Project\MAG160C\csdk\src\mag160c_tcm.c" "C:\Project\MAG160C\csdk\src\mag160c_error.c" "C:\Project\MAG160C\csdk\src\mag160c_display.c" "C:\Project\MAG160C\csdk\third_party\libusb\win64\libusb-1.0.x64.a"
|
||||
|
||||
# 运行/停止
|
||||
Start-Process "C:\Project\MAG160C\build-artifacts\mag160c_demo3.exe" -WorkingDirectory "C:\Project\MAG160C\build-artifacts"
|
||||
Stop-Process -Name mag160c_demo3 -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# 官方采集(先停 demo3)
|
||||
& "C:\Project\MAG160C\build-artifacts\tsdk_pair3.exe" "C:\Project\MAG160C\analysis\pairs_final_20260813" 8 6000
|
||||
# 注意:tsdk_pair3 的 config.txt / pair_*.meta / pair_*.win 写在进程 CWD,
|
||||
# 跑完要把 C:\Project\MAG160C 下的 config.txt 和 pair_0*.meta/win 移回目录。
|
||||
|
||||
# 对比/复算脚本(Python,已归档到 analysis/reverse_tools/)
|
||||
# analysis/reverse_tools/compare_live.py demo3捕获 vs 官方同场景
|
||||
# analysis/reverse_tools/verify_nuc.py 官方抓帧重建验证
|
||||
# analysis/reverse_tools/verify_full.py 全链路验证
|
||||
# analysis/reverse_tools/parse_ddt.py DDT 文件解析
|
||||
# analysis/reverse_tools/parse_pcap.py pcap 抓包解析
|
||||
# C:\Users\ZXC\AppData\Local\Temp\opencode\verify_c_pipeline.exe C实现离线复算
|
||||
```
|
||||
|
||||
## 心跳约定
|
||||
|
||||
- 每完成一个里程碑,更新本文件"已完成的里程碑"勾选。
|
||||
- 会话中断恢复流程:
|
||||
1. 读本文件 + `analysis/reverse_20260813_full.md`
|
||||
2. 运行 `tools/resume_rev.ps1`(自动检查设备/进程/编译/采集)
|
||||
3. 按"零像素问题现场"继续
|
||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,51 @@
|
||||
# IDA / Ghidra 逆向工具链(2026-08-13 部署)
|
||||
|
||||
## 已安装工具
|
||||
|
||||
| 工具 | 位置 | 用途 |
|
||||
|---|---|---|
|
||||
| IDA Free 8.4 | `C:\Program Files\IDA Freeware 8.4\ida64.exe` | x86/x64 交互分析,F5 反编译(GUI 内),不支持批处理 |
|
||||
| Ghidra 12.1.2 | `C:\Tools\ghidra_12.1.2_PUBLIC` | ARM64/任意架构批处理反编译,headless 自动建库 |
|
||||
| Java | `C:\Program Files\Zulu\zulu-21` | Ghidra 需要,不在 PATH,需设 `JAVA_HOME` |
|
||||
|
||||
## 限制说明
|
||||
|
||||
- IDA Free 8.4 只支持 x86/x64(官方页面确认),不支持 ARM;不支持批处理模式(`-A` 直接报 "does not support the batch mode")。
|
||||
- IDA Free 的 IDC 是半残的:`get_func`/`get_first_func`/`decompile()` 文本导出不可用(`decompile` 返回不透明对象,无法转文本)。可用的迭代 API 是 `get_next_func(ea)` / `get_prev_func(ea)`,配 `get_func_attr(ea, FUNCATTR_START/END)`。
|
||||
- IDA Free 反编译器(hexx64/hexarm64)已注册,但只能在 GUI 里按 F5 查看,无法脚本导出伪代码。伪代码批量导出请用 Ghidra。
|
||||
|
||||
## 目录内容
|
||||
|
||||
- `CoreSDKLib.dll.i64` / `ThermalSDK.dll.i64` / `libthermalSDK.so.i64`:IDA 数据库(GUI 打开 .i64 即可,分析已跑完)。
|
||||
- `export/coresdk_keyfuncs_disasm.txt`:CoreSDKLib.dll 12 个关键函数反汇编 + xrefs(NUC 0x180017200、盲元 0x180017330、温度端点 0x180016ae0、阈值插值 0x180016dd0、gain/off 插值 0x180016f10、表重建 0x18000c760、ref 推送 0x18000c620、平滑器 0x1800011a0、temporal 0x18001e920、FFC 0x18000a1d2/0x180002f50、温度 0x180010780)。
|
||||
- `export/thermalsdk_dll/`:ThermalSDK.dll 全量(368 函数)。
|
||||
- `export/thermalsdk_so/`:libthermalSDK.so 全量(5150 函数,带完整符号:`MAG_Initialize`、`MAG_NewChannel`、`ThermalController::*` 等)。
|
||||
- `export/ghidra_dump/`:Ghidra 伪代码全量导出。
|
||||
- `all_functions_decomp_libcoresdk_arm64.so_00100000.txt`(21.6MB,7117 函数):ARM64 全量 C 伪代码,含 JNI(`Java_cn_com_magnity_coresdk_MagDevice_*`)和温度函数(`ConvertInnerTemp2mC`、`GetTemperatureProbe`、`GetLineTemperatureInfo` 等)。
|
||||
- `all_functions_decomp_libmagcore.so_00100000.txt`(937KB,841 函数)。
|
||||
|
||||
## 使用方式
|
||||
|
||||
### IDA GUI(交互分析 x86/x64)
|
||||
|
||||
```powershell
|
||||
Start-Process "C:\Program Files\IDA Freeware 8.4\ida64.exe" -ArgumentList '"C:\Project\MAG160C\analysis\ida\CoreSDKLib.dll.i64"'
|
||||
```
|
||||
|
||||
打开 .i64 后:F5 看伪代码,按地址跳转 `G`,交叉引用 `X`。
|
||||
要重新对某个函数导出反汇编文本:Alt+F7 运行 `export_core_disasm.idc`(会重写 export/coresdk_keyfuncs_disasm.txt)。
|
||||
新 DLL 建库:File→Open 选 .dll,默认选项,等 Idle 后 Alt+F7 运行 `export_all.idc`(全量导出到 export/),Ctrl+W 保存。
|
||||
|
||||
### Ghidra headless(批处理任意架构)
|
||||
|
||||
```powershell
|
||||
$env:JAVA_HOME = "C:\Program Files\Zulu\zulu-21"
|
||||
& "C:\Tools\ghidra_12.1.2_PUBLIC\support\analyzeHeadless.bat" "C:\Tools\ghidra_proj" mag160c -import "文件路径" -overwrite
|
||||
& "C:\Tools\ghidra_12.1.2_PUBLIC\support\analyzeHeadless.bat" "C:\Tools\ghidra_proj" mag160c -process "文件名" -noanalysis -scriptPath "C:\Tools\ghidra_12.1.2_PUBLIC\scripts" -postScript ExportAllDecomp
|
||||
```
|
||||
|
||||
导出脚本:`C:\Tools\ghidra_12.1.2_PUBLIC\scripts\ExportAllDecomp.java`(全函数伪代码 + 函数表,按程序名+基址命名输出到 export/ghidra_dump/)。
|
||||
|
||||
## 与旧 objdump 反汇编的关系
|
||||
|
||||
`analysis/disasm/` 下旧的 `coresdk_windows_disasm.txt`(193k 行 objdump 输出)仍有效,与本目录 IDA 导出相互印证。地址基址一致:Windows DLL 0x180000000,ARM64 .so 0x100000。
|
||||
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,85 @@
|
||||
#include <idc.idc>
|
||||
|
||||
static first_func()
|
||||
{
|
||||
auto seed, ea;
|
||||
seed = get_func_attr(0x180001000, FUNCATTR_START);
|
||||
if (seed == BADADDR)
|
||||
{
|
||||
ea = get_first_seg();
|
||||
while (ea != BADADDR)
|
||||
{
|
||||
if (get_segm_name(ea) == ".text" || get_segm_name(ea) == "CODE" ||
|
||||
get_segm_name(ea) == "text" || get_segm_name(ea) == ".plt")
|
||||
break;
|
||||
ea = get_next_seg(ea);
|
||||
}
|
||||
if (ea != BADADDR)
|
||||
seed = ea;
|
||||
}
|
||||
if (seed == BADADDR)
|
||||
seed = get_func_attr(get_imagebase() + 0x1000, FUNCATTR_START);
|
||||
if (seed == BADADDR)
|
||||
{
|
||||
warning("cannot find seed function");
|
||||
return BADADDR;
|
||||
}
|
||||
ea = get_prev_func(seed);
|
||||
while (ea != BADADDR)
|
||||
{
|
||||
seed = ea;
|
||||
ea = get_prev_func(seed);
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
static dump_func_disasm(f, from, to)
|
||||
{
|
||||
auto ea;
|
||||
ea = from;
|
||||
while (ea != BADADDR && ea < to)
|
||||
{
|
||||
fprintf(f, "%08X: %s\n", ea, generate_disasm_line(ea, 0));
|
||||
ea = next_head(ea, to);
|
||||
}
|
||||
fprintf(f, "\n");
|
||||
}
|
||||
|
||||
static main()
|
||||
{
|
||||
auto f, of, ea, n, end;
|
||||
f = fopen("C:\\Project\\MAG160C\\analysis\\ida\\export\\all_functions_disasm.txt", "w");
|
||||
if (f == 0)
|
||||
{
|
||||
warning("cannot open output");
|
||||
return;
|
||||
}
|
||||
of = fopen("C:\\Project\\MAG160C\\analysis\\ida\\export\\all_functions.txt", "w");
|
||||
if (of == 0)
|
||||
{
|
||||
warning("cannot open map output");
|
||||
fclose(f);
|
||||
return;
|
||||
}
|
||||
ea = first_func();
|
||||
if (ea == BADADDR)
|
||||
{
|
||||
fclose(f);
|
||||
fclose(of);
|
||||
return;
|
||||
}
|
||||
n = 0;
|
||||
while (ea != BADADDR)
|
||||
{
|
||||
end = get_func_attr(ea, FUNCATTR_END);
|
||||
fprintf(f, "### FUNC %08X - %08X (%d bytes) name=%s\n", ea, end, end - ea, get_func_name(ea));
|
||||
dump_func_disasm(f, ea, end);
|
||||
fprintf(of, "%08X %08X %s\n", ea, end, get_func_name(ea));
|
||||
ea = get_next_func(ea);
|
||||
n = n + 1;
|
||||
}
|
||||
fprintf(of, "TOTAL %d\n", n);
|
||||
fclose(f);
|
||||
fclose(of);
|
||||
msg("all export done: %d funcs\n", n);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#include <idc.idc>
|
||||
|
||||
static dump_range(out, from, to)
|
||||
{
|
||||
auto ea;
|
||||
ea = from;
|
||||
while (ea != BADADDR && ea < to)
|
||||
{
|
||||
fprintf(out, "%08X: %s\n", ea, generate_disasm_line(ea, 0));
|
||||
ea = next_head(ea, to);
|
||||
}
|
||||
}
|
||||
|
||||
static dump_xrefs(out, ea)
|
||||
{
|
||||
auto x, caller, from_ea, to_ea;
|
||||
from_ea = ea - 0x80;
|
||||
to_ea = ea + 0x80;
|
||||
x = get_first_dref_to(ea);
|
||||
if (x != BADADDR)
|
||||
{
|
||||
fprintf(out, " data refs from:\n");
|
||||
while (x != BADADDR)
|
||||
{
|
||||
fprintf(out, " %08X\n", x);
|
||||
x = get_next_dref_to(ea, x);
|
||||
}
|
||||
}
|
||||
x = get_first_cref_to(ea);
|
||||
if (x != BADADDR)
|
||||
{
|
||||
fprintf(out, " code refs from:\n");
|
||||
while (x != BADADDR)
|
||||
{
|
||||
fprintf(out, " %08X (func %08X)\n", x, get_func_attr(x, FUNCATTR_START));
|
||||
x = get_next_cref_to(ea, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static dump_fn(out, ea, decomp)
|
||||
{
|
||||
auto f, end, s, i, name;
|
||||
f = get_func(ea);
|
||||
if (f == 0)
|
||||
{
|
||||
fprintf(out, "### %08X NOT A FUNCTION (dumping 0x200 bytes)\n", ea);
|
||||
dump_range(out, ea, ea + 0x200);
|
||||
fprintf(out, "\n==================================================================\n\n");
|
||||
return;
|
||||
}
|
||||
end = get_func_attr(f, FUNCATTR_END);
|
||||
name = get_func_name(ea);
|
||||
fprintf(out, "### FUNC %08X - %08X (%d bytes) name=%s\n", ea, end, end - ea, name);
|
||||
dump_range(out, ea, end);
|
||||
fprintf(out, "\n");
|
||||
dump_xrefs(out, ea);
|
||||
if (decomp)
|
||||
{
|
||||
s = decompile(ea);
|
||||
if (s != 0)
|
||||
{
|
||||
fprintf(out, "\n;; DECOMPILED:\n%s\n", s);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(out, "\n;; DECOMPILE FAILED\n");
|
||||
}
|
||||
}
|
||||
fprintf(out, "\n==================================================================\n\n");
|
||||
}
|
||||
|
||||
static main()
|
||||
{
|
||||
auto dir, out, f, i, ea;
|
||||
dir = "C:\\Project\\MAG160C\\analysis\\ida\\export\\";
|
||||
f = fopen(dir + "coresdk_keyfuncs.txt", "w");
|
||||
if (f == 0)
|
||||
{
|
||||
warning("cannot open output file");
|
||||
return;
|
||||
}
|
||||
dump_fn(f, 0x180017200, 1); // NUC lookup
|
||||
dump_fn(f, 0x180017330, 1); // blind pixel compensation
|
||||
dump_fn(f, 0x180016ae0, 1); // sensor temp endpoint selection
|
||||
dump_fn(f, 0x180016dd0, 1); // threshold Q12 interp
|
||||
dump_fn(f, 0x180016f10, 1); // gain/off Q12 interp
|
||||
dump_fn(f, 0x18000c760, 1); // table rebuild chunked write
|
||||
dump_fn(f, 0x18000c620, 1); // ref push / smoother feed
|
||||
dump_fn(f, 0x1800011a0, 1); // generic smoother
|
||||
dump_fn(f, 0x18001e920, 1); // temporal filter
|
||||
dump_fn(f, 0x18000a1d2, 1); // FFC trigger A
|
||||
dump_fn(f, 0x180002f50, 1); // FFC trigger B
|
||||
dump_fn(f, 0x180010780, 1); // temperature
|
||||
fclose(f);
|
||||
|
||||
out = fopen(dir + "all_functions.txt", "w");
|
||||
if (out != 0)
|
||||
{
|
||||
ea = get_first_func();
|
||||
while (ea != BADADDR)
|
||||
{
|
||||
fprintf(out, "%08X %08X %s\n", ea, get_func_attr(ea, FUNCATTR_END),
|
||||
get_func_name(ea));
|
||||
ea = get_next_func(ea);
|
||||
}
|
||||
fclose(out);
|
||||
}
|
||||
msg("export done\n");
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#include <idc.idc>
|
||||
|
||||
static decomp_one(out, ea)
|
||||
{
|
||||
auto f, end, s, name;
|
||||
f = get_func_attr(ea, FUNCATTR_START);
|
||||
if (f == BADADDR)
|
||||
{
|
||||
fprintf(out, "### %08X NOT A FUNCTION\n", ea);
|
||||
return;
|
||||
}
|
||||
end = get_func_attr(f, FUNCATTR_END);
|
||||
name = get_func_name(f);
|
||||
fprintf(out, "### FUNC %08X - %08X (%d bytes) name=%s\n", f, end, end - f, name);
|
||||
s = decompile(f);
|
||||
if (s != 0)
|
||||
{
|
||||
fprintf(out, "%s\n", s);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(out, ";; DECOMPILE FAILED\n");
|
||||
}
|
||||
fprintf(out, "\n==================================================================\n\n");
|
||||
}
|
||||
|
||||
static main()
|
||||
{
|
||||
auto f;
|
||||
f = fopen("C:\\Project\\MAG160C\\analysis\\ida\\export\\coresdk_keyfuncs_decomp.txt", "w");
|
||||
if (f == 0)
|
||||
{
|
||||
warning("cannot open decomp output");
|
||||
return;
|
||||
}
|
||||
decomp_one(f, 0x180017200);
|
||||
decomp_one(f, 0x180017330);
|
||||
decomp_one(f, 0x180016ae0);
|
||||
decomp_one(f, 0x180016dd0);
|
||||
decomp_one(f, 0x180016f10);
|
||||
decomp_one(f, 0x18000c760);
|
||||
decomp_one(f, 0x18000c620);
|
||||
decomp_one(f, 0x1800011a0);
|
||||
decomp_one(f, 0x18001e920);
|
||||
decomp_one(f, 0x18000a1d2);
|
||||
decomp_one(f, 0x180002f50);
|
||||
decomp_one(f, 0x180010780);
|
||||
fclose(f);
|
||||
msg("decomp export done\n");
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#include <idc.idc>
|
||||
|
||||
static dump_range(out, from, to)
|
||||
{
|
||||
auto ea;
|
||||
ea = from;
|
||||
while (ea != BADADDR && ea < to)
|
||||
{
|
||||
fprintf(out, "%08X: %s\n", ea, generate_disasm_line(ea, 0));
|
||||
ea = next_head(ea, to);
|
||||
}
|
||||
}
|
||||
|
||||
static dump_xrefs(out, ea)
|
||||
{
|
||||
auto x;
|
||||
x = get_first_dref_to(ea);
|
||||
if (x != BADADDR)
|
||||
{
|
||||
fprintf(out, " data refs from:\n");
|
||||
while (x != BADADDR)
|
||||
{
|
||||
fprintf(out, " %08X\n", x);
|
||||
x = get_next_dref_to(ea, x);
|
||||
}
|
||||
}
|
||||
x = get_first_cref_to(ea);
|
||||
if (x != BADADDR)
|
||||
{
|
||||
fprintf(out, " code refs from:\n");
|
||||
while (x != BADADDR)
|
||||
{
|
||||
fprintf(out, " %08X (func %08X)\n", x, get_func_attr(x, FUNCATTR_START));
|
||||
x = get_next_cref_to(ea, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static dump_fn(out, ea)
|
||||
{
|
||||
auto f, end, name;
|
||||
f = get_func_attr(ea, FUNCATTR_START);
|
||||
if (f == BADADDR)
|
||||
{
|
||||
fprintf(out, "### %08X NOT A FUNCTION (dumping 0x200 bytes)\n", ea);
|
||||
dump_range(out, ea, ea + 0x200);
|
||||
fprintf(out, "\n==================================================================\n\n");
|
||||
return;
|
||||
}
|
||||
end = get_func_attr(f, FUNCATTR_END);
|
||||
name = get_func_name(f);
|
||||
fprintf(out, "### FUNC %08X - %08X (%d bytes) name=%s\n", f, end, end - f, name);
|
||||
dump_range(out, f, end);
|
||||
fprintf(out, "\n");
|
||||
dump_xrefs(out, f);
|
||||
fprintf(out, "\n==================================================================\n\n");
|
||||
}
|
||||
|
||||
static main()
|
||||
{
|
||||
auto dir, f, i, ea, n, seed, first;
|
||||
dir = "C:\\Project\\MAG160C\\analysis\\ida\\export\\";
|
||||
|
||||
f = fopen(dir + "coresdk_keyfuncs_disasm.txt", "w");
|
||||
if (f == 0)
|
||||
{
|
||||
warning("cannot open keyfuncs output");
|
||||
return;
|
||||
}
|
||||
dump_fn(f, 0x180017200);
|
||||
dump_fn(f, 0x180017330);
|
||||
dump_fn(f, 0x180016ae0);
|
||||
dump_fn(f, 0x180016dd0);
|
||||
dump_fn(f, 0x180016f10);
|
||||
dump_fn(f, 0x18000c760);
|
||||
dump_fn(f, 0x18000c620);
|
||||
dump_fn(f, 0x1800011a0);
|
||||
dump_fn(f, 0x18001e920);
|
||||
dump_fn(f, 0x18000a1d2);
|
||||
dump_fn(f, 0x180002f50);
|
||||
dump_fn(f, 0x180010780);
|
||||
fclose(f);
|
||||
|
||||
seed = get_func_attr(0x180001000, FUNCATTR_START);
|
||||
if (seed == BADADDR)
|
||||
seed = 0x180017200;
|
||||
first = seed;
|
||||
ea = get_prev_func(first);
|
||||
while (ea != BADADDR)
|
||||
{
|
||||
first = ea;
|
||||
ea = get_prev_func(first);
|
||||
}
|
||||
f = fopen(dir + "all_functions.txt", "w");
|
||||
if (f == 0)
|
||||
{
|
||||
warning("cannot open allfuncs output");
|
||||
return;
|
||||
}
|
||||
ea = first;
|
||||
n = 0;
|
||||
while (ea != BADADDR)
|
||||
{
|
||||
fprintf(f, "%08X %08X %s\n", ea, get_func_attr(ea, FUNCATTR_END), get_func_name(ea));
|
||||
ea = get_next_func(ea);
|
||||
n = n + 1;
|
||||
}
|
||||
fprintf(f, "TOTAL %d\n", n);
|
||||
fclose(f);
|
||||
msg("disasm export done: %d funcs\n", n);
|
||||
}
|
||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,91 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(mag160c LANGUAGES C CXX)
|
||||
|
||||
option(MAG160C_BUILD_TESTS "Build MAG160C tests" ON)
|
||||
option(MAG160C_BUILD_CLI "Build MAG160C CLI tools" ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(LIBUSB QUIET libusb-1.0)
|
||||
endif()
|
||||
|
||||
if(MAG160C_BUILD_CLI)
|
||||
add_executable(mag160c-cli tools/mag160c_cli.cpp)
|
||||
target_link_libraries(mag160c-cli PRIVATE mag160c_core)
|
||||
endif()
|
||||
|
||||
add_library(mag160c_core SHARED
|
||||
src/core/error.cpp
|
||||
src/core/context.cpp
|
||||
src/core/tcm_frame.cpp
|
||||
src/core/tcm_device.cpp
|
||||
src/core/device.cpp
|
||||
src/core/ir_device.cpp
|
||||
src/core/ir_frame.cpp
|
||||
src/c_api.cpp
|
||||
)
|
||||
|
||||
target_include_directories(mag160c_core
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
target_compile_definitions(mag160c_core PRIVATE MAG160C_BUILDING_LIBRARY)
|
||||
|
||||
if(LIBUSB_FOUND)
|
||||
set(MAG160C_HAS_LIBUSB_VALUE 1)
|
||||
target_compile_definitions(mag160c_core PRIVATE MAG160C_HAS_LIBUSB=1)
|
||||
target_include_directories(mag160c_core PRIVATE ${LIBUSB_INCLUDE_DIRS})
|
||||
target_link_directories(mag160c_core PRIVATE ${LIBUSB_LIBRARY_DIRS})
|
||||
target_link_libraries(mag160c_core PRIVATE ${LIBUSB_LIBRARIES})
|
||||
target_compile_options(mag160c_core PRIVATE ${LIBUSB_CFLAGS_OTHER})
|
||||
else()
|
||||
set(MAG160C_HAS_LIBUSB_VALUE 0)
|
||||
target_compile_definitions(mag160c_core PRIVATE MAG160C_HAS_LIBUSB=0)
|
||||
endif()
|
||||
|
||||
if(MAG160C_BUILD_TESTS)
|
||||
enable_testing()
|
||||
|
||||
add_executable(test_c_api tests/cpp/test_c_api.cpp)
|
||||
target_link_libraries(test_c_api PRIVATE mag160c_core)
|
||||
target_compile_definitions(test_c_api PRIVATE MAG160C_HAS_LIBUSB=${MAG160C_HAS_LIBUSB_VALUE})
|
||||
|
||||
add_test(NAME test_c_api COMMAND test_c_api)
|
||||
|
||||
add_executable(test_tcm_frame tests/cpp/test_tcm_frame.cpp)
|
||||
target_link_libraries(test_tcm_frame PRIVATE mag160c_core)
|
||||
target_include_directories(test_tcm_frame PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
add_test(NAME test_tcm_frame COMMAND test_tcm_frame)
|
||||
|
||||
add_executable(test_tcm_device tests/cpp/test_tcm_device.cpp)
|
||||
target_link_libraries(test_tcm_device PRIVATE mag160c_core)
|
||||
target_include_directories(test_tcm_device PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
add_test(NAME test_tcm_device COMMAND test_tcm_device)
|
||||
|
||||
add_executable(test_device_model tests/cpp/test_device_model.cpp)
|
||||
target_link_libraries(test_device_model PRIVATE mag160c_core)
|
||||
target_include_directories(test_device_model PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
add_test(NAME test_device_model COMMAND test_device_model)
|
||||
|
||||
add_executable(test_ir_skeleton tests/cpp/test_ir_skeleton.cpp)
|
||||
target_link_libraries(test_ir_skeleton PRIVATE mag160c_core)
|
||||
|
||||
add_test(NAME test_ir_skeleton COMMAND test_ir_skeleton)
|
||||
|
||||
add_executable(test_ir_frame tests/cpp/test_ir_frame.cpp)
|
||||
target_link_libraries(test_ir_frame PRIVATE mag160c_core)
|
||||
target_include_directories(test_ir_frame PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
add_test(NAME test_ir_frame COMMAND test_ir_frame)
|
||||
endif()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,340 +0,0 @@
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include "core/context.hpp"
|
||||
#include "core/device.hpp"
|
||||
#include "core/error.hpp"
|
||||
#include "core/ir_device.hpp"
|
||||
#include "core/tcm_device.hpp"
|
||||
#include "core/tcm_frame.hpp"
|
||||
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t MAX_TCM_PAYLOAD_SIZE = 0xffffU - 5U;
|
||||
|
||||
} // namespace
|
||||
|
||||
struct mag160c_context_t {
|
||||
mag160c::core::Context context;
|
||||
};
|
||||
|
||||
struct mag160c_ir_device_t {
|
||||
mag160c::core::IrDevice device;
|
||||
};
|
||||
|
||||
mag160c_error_t mag160c_init(mag160c_context_t** out_ctx) {
|
||||
if (out_ctx == nullptr) {
|
||||
mag160c::core::set_last_error("mag160c_init: out_ctx must not be null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*out_ctx = nullptr;
|
||||
|
||||
try {
|
||||
*out_ctx = new mag160c_context_t{};
|
||||
mag160c::core::clear_last_error();
|
||||
return MAG160C_OK;
|
||||
} catch (const std::bad_alloc&) {
|
||||
mag160c::core::set_last_error("mag160c_init: failed to allocate context");
|
||||
return MAG160C_ERR_INTERNAL;
|
||||
} catch (...) {
|
||||
mag160c::core::set_last_error("mag160c_init: unexpected context construction failure");
|
||||
return MAG160C_ERR_INTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
void mag160c_shutdown(mag160c_context_t* ctx) {
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
const char* mag160c_last_error(void) {
|
||||
return mag160c::core::last_error();
|
||||
}
|
||||
|
||||
const char* mag160c_error_name(mag160c_error_t code) {
|
||||
return mag160c::core::error_name(code);
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_list_devices(
|
||||
mag160c_context_t* ctx,
|
||||
mag160c_device_info_t** out_devices,
|
||||
size_t* out_count
|
||||
) {
|
||||
if (ctx == nullptr || out_devices == nullptr || out_count == nullptr) {
|
||||
mag160c::core::set_last_error(
|
||||
"mag160c_list_devices: ctx, out_devices, and out_count must not be null"
|
||||
);
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*out_devices = nullptr;
|
||||
*out_count = 0;
|
||||
|
||||
std::vector<mag160c::core::DeviceInfo> devices;
|
||||
const mag160c_error_t rc = mag160c::core::list_devices(&devices);
|
||||
if (rc != MAG160C_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (devices.empty()) {
|
||||
mag160c::core::clear_last_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_device_info_t* raw = new (std::nothrow) mag160c_device_info_t[devices.size()];
|
||||
if (raw == nullptr) {
|
||||
mag160c::core::set_last_error("mag160c_list_devices: allocation failed");
|
||||
return MAG160C_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < devices.size(); ++i) {
|
||||
raw[i] = devices[i].c_info;
|
||||
}
|
||||
|
||||
*out_devices = raw;
|
||||
*out_count = devices.size();
|
||||
mag160c::core::clear_last_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
void mag160c_free_device_list(mag160c_device_info_t* devices) {
|
||||
delete[] devices;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_open_first(
|
||||
mag160c_context_t* ctx,
|
||||
mag160c_ir_device_t** out_device
|
||||
) {
|
||||
if (ctx == nullptr || out_device == nullptr) {
|
||||
mag160c::core::set_last_error(
|
||||
"mag160c_ir_open_first: ctx and out_device must not be null"
|
||||
);
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*out_device = nullptr;
|
||||
mag160c_ir_device_t* device = new (std::nothrow) mag160c_ir_device_t{};
|
||||
if (device == nullptr) {
|
||||
mag160c::core::set_last_error("mag160c_ir_open_first: allocation failed");
|
||||
return MAG160C_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
*out_device = device;
|
||||
mag160c::core::clear_last_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
void mag160c_ir_close(mag160c_ir_device_t* device) {
|
||||
delete device;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_get_info(
|
||||
mag160c_ir_device_t* device,
|
||||
mag160c_ir_info_t* out_info
|
||||
) {
|
||||
if (device == nullptr || out_info == nullptr) {
|
||||
mag160c::core::set_last_error(
|
||||
"mag160c_ir_get_info: device and out_info must not be null"
|
||||
);
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*out_info = device->device.info();
|
||||
mag160c::core::clear_last_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_trigger_ffc(mag160c_ir_device_t* device) {
|
||||
if (device == nullptr) {
|
||||
mag160c::core::set_last_error("mag160c_ir_trigger_ffc: device must not be null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
return device->device.trigger_ffc();
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_read_raw_once(
|
||||
mag160c_ir_device_t* device,
|
||||
uint8_t* out_bytes,
|
||||
size_t out_capacity,
|
||||
size_t* out_size,
|
||||
int timeout_ms
|
||||
) {
|
||||
if (device == nullptr) {
|
||||
if (out_size != nullptr) {
|
||||
*out_size = 0;
|
||||
}
|
||||
mag160c::core::set_last_error("mag160c_ir_read_raw_once: device must not be null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
return device->device.read_raw_once(out_bytes, out_capacity, out_size, timeout_ms);
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_tcm_encode_frame(
|
||||
uint8_t main_cmd,
|
||||
uint8_t sub_cmd,
|
||||
uint16_t frame_id,
|
||||
const uint8_t* payload,
|
||||
size_t payload_size,
|
||||
uint8_t* out_bytes,
|
||||
size_t out_capacity,
|
||||
size_t* out_size
|
||||
) {
|
||||
if (out_size == nullptr) {
|
||||
mag160c::core::set_last_error("mag160c_tcm_encode_frame: out_size must not be null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (payload_size > MAX_TCM_PAYLOAD_SIZE) {
|
||||
*out_size = 0;
|
||||
mag160c::core::set_last_error("mag160c_tcm_encode_frame: TCM payload is too large");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (payload_size != 0 && payload == nullptr) {
|
||||
*out_size = 0;
|
||||
mag160c::core::set_last_error(
|
||||
"mag160c_tcm_encode_frame: payload must not be null when payload_size is nonzero"
|
||||
);
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> payload_vec;
|
||||
if (payload_size != 0) {
|
||||
payload_vec.assign(payload, payload + payload_size);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> encoded;
|
||||
try {
|
||||
encoded = mag160c::core::encode_tcm_frame(main_cmd, sub_cmd, frame_id, payload_vec);
|
||||
} catch (const std::length_error&) {
|
||||
*out_size = 0;
|
||||
mag160c::core::set_last_error("mag160c_tcm_encode_frame: TCM payload is too large");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
*out_size = encoded.size();
|
||||
|
||||
if (out_bytes == nullptr || out_capacity < encoded.size()) {
|
||||
mag160c::core::set_last_error("mag160c_tcm_encode_frame: output buffer is too small");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < encoded.size(); ++i) {
|
||||
out_bytes[i] = encoded[i];
|
||||
}
|
||||
|
||||
mag160c::core::clear_last_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_tcm_decode_header(
|
||||
const uint8_t* data,
|
||||
size_t size,
|
||||
uint8_t* out_main_cmd,
|
||||
uint8_t* out_sub_cmd,
|
||||
uint16_t* out_frame_id,
|
||||
size_t* out_payload_size
|
||||
) {
|
||||
if (out_main_cmd == nullptr || out_sub_cmd == nullptr || out_frame_id == nullptr ||
|
||||
out_payload_size == nullptr) {
|
||||
mag160c::core::set_last_error(
|
||||
"mag160c_tcm_decode_header: all output pointers must not be null"
|
||||
);
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
mag160c::core::TcmFrame frame;
|
||||
const mag160c_error_t rc = mag160c::core::decode_tcm_frame(data, size, &frame);
|
||||
if (rc != MAG160C_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
*out_main_cmd = frame.main_cmd;
|
||||
*out_sub_cmd = frame.sub_cmd;
|
||||
*out_frame_id = frame.frame_id;
|
||||
*out_payload_size = frame.payload.size();
|
||||
mag160c::core::clear_last_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
mag160c_error_t copy_frame_to_output(
|
||||
const std::vector<uint8_t>& frame,
|
||||
uint8_t* out_bytes,
|
||||
size_t out_capacity,
|
||||
size_t* out_size
|
||||
) {
|
||||
if (out_size == nullptr) {
|
||||
mag160c::core::set_last_error("TCM frame builder: out_size must not be null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*out_size = frame.size();
|
||||
if (out_bytes == nullptr || out_capacity < frame.size()) {
|
||||
mag160c::core::set_last_error("TCM frame builder: output buffer is too small");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < frame.size(); ++i) {
|
||||
out_bytes[i] = frame[i];
|
||||
}
|
||||
|
||||
mag160c::core::clear_last_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
mag160c_error_t mag160c_tcm_build_rotate_frame(
|
||||
int angle,
|
||||
uint8_t* out_bytes,
|
||||
size_t out_capacity,
|
||||
size_t* out_size
|
||||
) {
|
||||
mag160c::core::TcmCommandBuilder builder;
|
||||
return copy_frame_to_output(builder.build_rotate_frame(angle), out_bytes, out_capacity, out_size);
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_tcm_build_light_frame(
|
||||
mag160c_tcm_light_color_t color,
|
||||
mag160c_tcm_light_mode_t mode,
|
||||
uint8_t* out_bytes,
|
||||
size_t out_capacity,
|
||||
size_t* out_size
|
||||
) {
|
||||
switch (color) {
|
||||
case MAG160C_TCM_LIGHT_OFF:
|
||||
case MAG160C_TCM_LIGHT_RED:
|
||||
case MAG160C_TCM_LIGHT_GREEN:
|
||||
case MAG160C_TCM_LIGHT_BLUE:
|
||||
case MAG160C_TCM_LIGHT_YELLOW:
|
||||
break;
|
||||
default:
|
||||
if (out_size != nullptr) {
|
||||
*out_size = 0;
|
||||
}
|
||||
mag160c::core::set_last_error("mag160c_tcm_build_light_frame: invalid color");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case MAG160C_TCM_LIGHT_STEADY:
|
||||
case MAG160C_TCM_LIGHT_BLINK:
|
||||
case MAG160C_TCM_LIGHT_BREATH:
|
||||
break;
|
||||
default:
|
||||
if (out_size != nullptr) {
|
||||
*out_size = 0;
|
||||
}
|
||||
mag160c::core::set_last_error("mag160c_tcm_build_light_frame: invalid mode");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
mag160c::core::TcmCommandBuilder builder;
|
||||
return copy_frame_to_output(builder.build_light_frame(color, mode), out_bytes, out_capacity, out_size);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "core/context.hpp"
|
||||
|
||||
#ifndef MAG160C_HAS_LIBUSB
|
||||
#define MAG160C_HAS_LIBUSB 0
|
||||
#endif
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
Context::Context()
|
||||
: has_libusb_(MAG160C_HAS_LIBUSB != 0) {}
|
||||
|
||||
bool Context::has_libusb() const noexcept {
|
||||
return has_libusb_;
|
||||
}
|
||||
|
||||
} // namespace mag160c::core
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef MAG160C_CORE_CONTEXT_HPP
|
||||
#define MAG160C_CORE_CONTEXT_HPP
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
class Context {
|
||||
public:
|
||||
Context();
|
||||
|
||||
bool has_libusb() const noexcept;
|
||||
|
||||
private:
|
||||
bool has_libusb_;
|
||||
};
|
||||
|
||||
} // namespace mag160c::core
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
#include "core/device.hpp"
|
||||
|
||||
#include "core/error.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#if MAG160C_HAS_LIBUSB
|
||||
#include <libusb.h>
|
||||
#endif
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
bool is_in_endpoint(uint8_t address) {
|
||||
return (address & 0x80U) != 0;
|
||||
}
|
||||
|
||||
bool find_bulk_pair(const std::vector<EndpointDescriptor>& endpoints, EndpointPair* out) {
|
||||
if (out == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EndpointPair pair{};
|
||||
for (const EndpointDescriptor& endpoint : endpoints) {
|
||||
if (endpoint.type != EndpointType::Bulk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_in_endpoint(endpoint.address) && pair.bulk_in == 0) {
|
||||
pair.bulk_in = endpoint.address;
|
||||
} else if (!is_in_endpoint(endpoint.address) && pair.bulk_out == 0) {
|
||||
pair.bulk_out = endpoint.address;
|
||||
}
|
||||
}
|
||||
|
||||
if (pair.bulk_in == 0 || pair.bulk_out == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = pair;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MAG160C_HAS_LIBUSB
|
||||
|
||||
namespace {
|
||||
|
||||
EndpointType endpoint_type_from_libusb(uint8_t attributes) {
|
||||
switch (attributes & LIBUSB_TRANSFER_TYPE_MASK) {
|
||||
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
|
||||
return EndpointType::Isochronous;
|
||||
case LIBUSB_TRANSFER_TYPE_BULK:
|
||||
return EndpointType::Bulk;
|
||||
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
|
||||
return EndpointType::Interrupt;
|
||||
default:
|
||||
return EndpointType::Other;
|
||||
}
|
||||
}
|
||||
|
||||
void copy_string_descriptor(
|
||||
libusb_device_handle* handle,
|
||||
uint8_t index,
|
||||
char* out,
|
||||
size_t out_size
|
||||
) {
|
||||
if (out == nullptr || out_size == 0) {
|
||||
return;
|
||||
}
|
||||
out[0] = '\0';
|
||||
if (handle == nullptr || index == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char buffer[128] = {};
|
||||
const int rc = libusb_get_string_descriptor_ascii(handle, index, buffer, sizeof(buffer));
|
||||
if (rc <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t n = static_cast<size_t>(rc) < out_size - 1 ? static_cast<size_t>(rc) : out_size - 1;
|
||||
std::memcpy(out, buffer, n);
|
||||
out[n] = '\0';
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
|
||||
mag160c_error_t list_devices(std::vector<DeviceInfo>* out) {
|
||||
if (out == nullptr) {
|
||||
set_last_error("list_devices: out must not be null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
out->clear();
|
||||
|
||||
#if !MAG160C_HAS_LIBUSB
|
||||
set_last_error("list_devices: libusb-1.0 was not available at build time");
|
||||
return MAG160C_ERR_UNSUPPORTED;
|
||||
#else
|
||||
libusb_context* ctx = nullptr;
|
||||
int rc = libusb_init(&ctx);
|
||||
if (rc != 0) {
|
||||
set_last_error("list_devices: libusb_init failed");
|
||||
return MAG160C_ERR_USB;
|
||||
}
|
||||
|
||||
libusb_device** list = nullptr;
|
||||
const ssize_t count = libusb_get_device_list(ctx, &list);
|
||||
if (count < 0) {
|
||||
libusb_exit(ctx);
|
||||
set_last_error("list_devices: libusb_get_device_list failed");
|
||||
return MAG160C_ERR_USB;
|
||||
}
|
||||
|
||||
for (ssize_t i = 0; i < count; ++i) {
|
||||
libusb_device* device = list[i];
|
||||
libusb_device_descriptor desc{};
|
||||
if (libusb_get_device_descriptor(device, &desc) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (desc.idVendor != MAG_IR_VENDOR_ID || desc.idProduct != MAG_IR_PRODUCT_ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
libusb_config_descriptor* config = nullptr;
|
||||
if (libusb_get_active_config_descriptor(device, &config) != 0 || config == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool added = false;
|
||||
for (uint8_t iface_index = 0; iface_index < config->bNumInterfaces && !added; ++iface_index) {
|
||||
const libusb_interface& iface = config->interface[iface_index];
|
||||
for (int alt_index = 0; alt_index < iface.num_altsetting && !added; ++alt_index) {
|
||||
const libusb_interface_descriptor& alt = iface.altsetting[alt_index];
|
||||
std::vector<EndpointDescriptor> endpoints;
|
||||
endpoints.reserve(alt.bNumEndpoints);
|
||||
for (uint8_t ep_index = 0; ep_index < alt.bNumEndpoints; ++ep_index) {
|
||||
const libusb_endpoint_descriptor& ep = alt.endpoint[ep_index];
|
||||
endpoints.push_back({ep.bEndpointAddress, endpoint_type_from_libusb(ep.bmAttributes)});
|
||||
}
|
||||
|
||||
EndpointPair pair{};
|
||||
if (!find_bulk_pair(endpoints, &pair)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DeviceInfo info{};
|
||||
info.c_info.vendor_id = desc.idVendor;
|
||||
info.c_info.product_id = desc.idProduct;
|
||||
info.c_info.bus = static_cast<uint8_t>(libusb_get_bus_number(device));
|
||||
info.c_info.address = static_cast<uint8_t>(libusb_get_device_address(device));
|
||||
info.c_info.interface_number = alt.bInterfaceNumber;
|
||||
info.c_info.bulk_in_endpoint = pair.bulk_in;
|
||||
info.c_info.bulk_out_endpoint = pair.bulk_out;
|
||||
|
||||
libusb_device_handle* handle = nullptr;
|
||||
if (libusb_open(device, &handle) == 0) {
|
||||
copy_string_descriptor(handle, desc.iProduct, info.c_info.product, sizeof(info.c_info.product));
|
||||
copy_string_descriptor(
|
||||
handle,
|
||||
desc.iManufacturer,
|
||||
info.c_info.manufacturer,
|
||||
sizeof(info.c_info.manufacturer)
|
||||
);
|
||||
copy_string_descriptor(handle, desc.iSerialNumber, info.c_info.serial, sizeof(info.c_info.serial));
|
||||
libusb_close(handle);
|
||||
}
|
||||
|
||||
out->push_back(info);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
libusb_free_config_descriptor(config);
|
||||
}
|
||||
|
||||
libusb_free_device_list(list, 1);
|
||||
libusb_exit(ctx);
|
||||
clear_last_error();
|
||||
return MAG160C_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mag160c::core
|
||||
@@ -1,41 +0,0 @@
|
||||
#ifndef MAG160C_CORE_DEVICE_HPP
|
||||
#define MAG160C_CORE_DEVICE_HPP
|
||||
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
constexpr uint16_t MAG_IR_VENDOR_ID = 0x833c;
|
||||
constexpr uint16_t MAG_IR_PRODUCT_ID = 0x0001;
|
||||
|
||||
enum class EndpointType {
|
||||
Other,
|
||||
Bulk,
|
||||
Interrupt,
|
||||
Isochronous
|
||||
};
|
||||
|
||||
struct EndpointDescriptor {
|
||||
uint8_t address = 0;
|
||||
EndpointType type = EndpointType::Other;
|
||||
};
|
||||
|
||||
struct EndpointPair {
|
||||
uint8_t bulk_in = 0;
|
||||
uint8_t bulk_out = 0;
|
||||
};
|
||||
|
||||
struct DeviceInfo {
|
||||
mag160c_device_info_t c_info{};
|
||||
};
|
||||
|
||||
bool is_in_endpoint(uint8_t address);
|
||||
bool find_bulk_pair(const std::vector<EndpointDescriptor>& endpoints, EndpointPair* out);
|
||||
mag160c_error_t list_devices(std::vector<DeviceInfo>* out);
|
||||
|
||||
} // namespace mag160c::core
|
||||
|
||||
#endif
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "core/error.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace mag160c::core {
|
||||
namespace {
|
||||
|
||||
thread_local std::string g_last_error;
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* error_name(mag160c_error_t code) noexcept {
|
||||
switch (code) {
|
||||
case MAG160C_OK:
|
||||
return "MAG160C_OK";
|
||||
case MAG160C_ERR_INVALID_ARGUMENT:
|
||||
return "MAG160C_ERR_INVALID_ARGUMENT";
|
||||
case MAG160C_ERR_NO_DEVICE:
|
||||
return "MAG160C_ERR_NO_DEVICE";
|
||||
case MAG160C_ERR_PERMISSION:
|
||||
return "MAG160C_ERR_PERMISSION";
|
||||
case MAG160C_ERR_USB:
|
||||
return "MAG160C_ERR_USB";
|
||||
case MAG160C_ERR_TIMEOUT:
|
||||
return "MAG160C_ERR_TIMEOUT";
|
||||
case MAG160C_ERR_CHECKSUM:
|
||||
return "MAG160C_ERR_CHECKSUM";
|
||||
case MAG160C_ERR_PROTOCOL_UNKNOWN:
|
||||
return "MAG160C_ERR_PROTOCOL_UNKNOWN";
|
||||
case MAG160C_ERR_UNSUPPORTED:
|
||||
return "MAG160C_ERR_UNSUPPORTED";
|
||||
case MAG160C_ERR_INTERNAL:
|
||||
return "MAG160C_ERR_INTERNAL";
|
||||
default:
|
||||
return "MAG160C_ERR_UNKNOWN_CODE";
|
||||
}
|
||||
}
|
||||
|
||||
const char* last_error() noexcept {
|
||||
return g_last_error.c_str();
|
||||
}
|
||||
|
||||
void clear_last_error() {
|
||||
g_last_error.clear();
|
||||
}
|
||||
|
||||
void set_last_error(std::string message) {
|
||||
g_last_error = std::move(message);
|
||||
}
|
||||
|
||||
} // namespace mag160c::core
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef MAG160C_CORE_ERROR_HPP
|
||||
#define MAG160C_CORE_ERROR_HPP
|
||||
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
const char* error_name(mag160c_error_t code) noexcept;
|
||||
const char* last_error() noexcept;
|
||||
void clear_last_error();
|
||||
void set_last_error(std::string message);
|
||||
|
||||
} // namespace mag160c::core
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#include "core/ir_device.hpp"
|
||||
|
||||
#include "core/error.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mag160c::core {
|
||||
namespace {
|
||||
|
||||
void copy_literal(char* out, size_t out_size, const char* value) {
|
||||
if (out == nullptr || out_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::strncpy(out, value, out_size - 1);
|
||||
out[out_size - 1] = '\0';
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
IrDevice::IrDevice() {
|
||||
info_.width = 160;
|
||||
info_.height = 120;
|
||||
info_.output_width = 160;
|
||||
info_.output_height = 120;
|
||||
info_.max_fps = 25;
|
||||
info_.current_fps = 0;
|
||||
copy_literal(info_.name, sizeof(info_.name), "MAG160C");
|
||||
copy_literal(info_.type, sizeof(info_.type), "vendor-bulk-ir");
|
||||
}
|
||||
|
||||
const mag160c_ir_info_t& IrDevice::info() const noexcept {
|
||||
return info_;
|
||||
}
|
||||
|
||||
mag160c_error_t IrDevice::trigger_ffc() const {
|
||||
set_last_error(
|
||||
"IR FFC protocol recovered from libmagcore.so.2.1.1: write {u32 0x6bb6b672, u32 param} "
|
||||
"on bulk endpoint OUT 0x03, then read response on IN 0x82 (0x1000 max, 2000 ms timeout); "
|
||||
"live USB transport is not available in this build"
|
||||
);
|
||||
return MAG160C_ERR_UNSUPPORTED;
|
||||
}
|
||||
|
||||
mag160c_error_t IrDevice::read_raw_once(
|
||||
unsigned char* /*out_bytes*/,
|
||||
size_t /*out_capacity*/,
|
||||
size_t* out_size,
|
||||
int /*timeout_ms*/
|
||||
) const {
|
||||
if (out_size == nullptr) {
|
||||
set_last_error("mag160c_ir_read_raw_once: out_size must not be null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*out_size = 0;
|
||||
set_last_error(
|
||||
"IR raw read protocol recovered: start stream with {0x6bb6b673} on OUT 0x03, then read "
|
||||
"frame stream on IN 0x81 (markers 0x1bb1b11b/0x1bb1b11c, data at +0x1c, size 0x38+len); "
|
||||
"live USB transport is not available in this build"
|
||||
);
|
||||
return MAG160C_ERR_UNSUPPORTED;
|
||||
}
|
||||
|
||||
} // namespace mag160c::core
|
||||
@@ -1,27 +0,0 @@
|
||||
#ifndef MAG160C_CORE_IR_DEVICE_HPP
|
||||
#define MAG160C_CORE_IR_DEVICE_HPP
|
||||
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
class IrDevice {
|
||||
public:
|
||||
IrDevice();
|
||||
|
||||
const mag160c_ir_info_t& info() const noexcept;
|
||||
mag160c_error_t trigger_ffc() const;
|
||||
mag160c_error_t read_raw_once(
|
||||
unsigned char* out_bytes,
|
||||
size_t out_capacity,
|
||||
size_t* out_size,
|
||||
int timeout_ms
|
||||
) const;
|
||||
|
||||
private:
|
||||
mag160c_ir_info_t info_{};
|
||||
};
|
||||
|
||||
} // namespace mag160c::core
|
||||
|
||||
#endif
|
||||
@@ -1,107 +0,0 @@
|
||||
#include "core/ir_frame.hpp"
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
mag160c_error_t parse_ir_frame(
|
||||
const uint8_t* data,
|
||||
size_t size,
|
||||
IrFrameHeader* out_header,
|
||||
const uint16_t** out_pixels
|
||||
) {
|
||||
if (data == nullptr || out_header == nullptr || out_pixels == nullptr) {
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (size < MAG160C_IR_FRAME_DATA_OFFSET + 4) {
|
||||
return MAG160C_ERR_PROTOCOL_UNKNOWN;
|
||||
}
|
||||
|
||||
const uint32_t marker =
|
||||
static_cast<uint32_t>(data[0]) |
|
||||
(static_cast<uint32_t>(data[1]) << 8) |
|
||||
(static_cast<uint32_t>(data[2]) << 16) |
|
||||
(static_cast<uint32_t>(data[3]) << 24);
|
||||
if (marker != MAG160C_IR_FRAME_MARKER) {
|
||||
return MAG160C_ERR_PROTOCOL_UNKNOWN;
|
||||
}
|
||||
|
||||
auto rd32 = [&](size_t off) {
|
||||
return static_cast<uint32_t>(data[off]) |
|
||||
(static_cast<uint32_t>(data[off + 1]) << 8) |
|
||||
(static_cast<uint32_t>(data[off + 2]) << 16) |
|
||||
(static_cast<uint32_t>(data[off + 3]) << 24);
|
||||
};
|
||||
|
||||
IrFrameHeader h{};
|
||||
h.frame_counter = rd32(4);
|
||||
h.data_length = rd32(8);
|
||||
h.frame_type = rd32(12);
|
||||
h.period_shutter = rd32(16);
|
||||
|
||||
if (h.frame_type > 1) {
|
||||
return MAG160C_ERR_PROTOCOL_UNKNOWN;
|
||||
}
|
||||
if (size < MAG160C_IR_FRAME_OVERHEAD + h.data_length) {
|
||||
return MAG160C_ERR_PROTOCOL_UNKNOWN;
|
||||
}
|
||||
|
||||
const size_t trailing_offset = MAG160C_IR_FRAME_DATA_OFFSET + h.data_length;
|
||||
if (rd32(trailing_offset) != MAG160C_IR_FRAME_TRAILING_MARKER) {
|
||||
return MAG160C_ERR_PROTOCOL_UNKNOWN;
|
||||
}
|
||||
|
||||
h.marker = marker;
|
||||
*out_header = h;
|
||||
*out_pixels = reinterpret_cast<const uint16_t*>(data + MAG160C_IR_FRAME_DATA_OFFSET);
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
void calibrate_frame(
|
||||
const uint16_t* frame,
|
||||
const IrCalibrationTables& tables,
|
||||
uint16_t* out
|
||||
) {
|
||||
if (frame == nullptr || tables.coeff == nullptr || tables.thresholds == nullptr ||
|
||||
out == nullptr || tables.pixel_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t pixels = tables.pixel_count;
|
||||
const uint32_t bands = tables.band_count == 0 ? 1 : tables.band_count;
|
||||
const uint32_t search_bands = bands - 1;
|
||||
|
||||
for (uint32_t p = 0; p < pixels; ++p) {
|
||||
int32_t diff;
|
||||
if (tables.has_baseline && tables.baseline != nullptr) {
|
||||
diff = static_cast<int32_t>(static_cast<int16_t>(
|
||||
frame[p] - tables.baseline[p]));
|
||||
} else {
|
||||
diff = static_cast<int32_t>(frame[p]);
|
||||
}
|
||||
diff >>= 1;
|
||||
|
||||
uint32_t band = 0;
|
||||
if (search_bands > 0) {
|
||||
for (uint32_t i = 0; i < search_bands; ++i) {
|
||||
if (diff <= tables.thresholds[p * search_bands + i]) {
|
||||
band = i;
|
||||
break;
|
||||
}
|
||||
band = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t entry = (static_cast<size_t>(band) * pixels + p) * 2;
|
||||
const uint32_t coeff = tables.coeff[entry];
|
||||
const uint32_t offset = tables.coeff[entry + 1];
|
||||
int32_t v = static_cast<int32_t>(offset) + ((diff * static_cast<int32_t>(coeff)) >> 12);
|
||||
if (v < 0) {
|
||||
v = 0;
|
||||
}
|
||||
if (v > 0xffff) {
|
||||
v = 0xffff;
|
||||
}
|
||||
out[p] = static_cast<uint16_t>(v);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mag160c::core
|
||||
@@ -1,57 +0,0 @@
|
||||
#ifndef MAG160C_CORE_IR_FRAME_HPP
|
||||
#define MAG160C_CORE_IR_FRAME_HPP
|
||||
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
struct IrFrameHeader {
|
||||
uint32_t marker;
|
||||
uint32_t frame_counter;
|
||||
uint32_t data_length;
|
||||
uint32_t frame_type;
|
||||
uint32_t period_shutter;
|
||||
};
|
||||
|
||||
struct IrCalibrationTables {
|
||||
const int16_t* thresholds; /* per-pixel band thresholds, [bands] per pixel */
|
||||
const uint16_t* coeff; /* per-pixel per-band {coeff, offset} pairs, 2*bands per pixel */
|
||||
uint32_t pixel_count; /* 160*120 = 19200 */
|
||||
uint32_t band_count; /* thresholds per pixel (band_count-1 searched) */
|
||||
const uint16_t* baseline; /* optional baseline frame (may be null) */
|
||||
bool has_baseline;
|
||||
};
|
||||
|
||||
/*
|
||||
* Parse a frame stream chunk recovered from libmagcore.so.2.1.1:
|
||||
* marker 0x1bb1b11b @0, frame counter @4, data length @8, type @0xc, shutter @0x10,
|
||||
* pixel data @0x1c, trailing marker 0x1bb1b11c @0x1c+data_length, total 0x38+data_length.
|
||||
* Returns MAG160C_OK when a complete, valid frame is present.
|
||||
*/
|
||||
mag160c_error_t parse_ir_frame(
|
||||
const uint8_t* data,
|
||||
size_t size,
|
||||
IrFrameHeader* out_header,
|
||||
const uint16_t** out_pixels
|
||||
);
|
||||
|
||||
/*
|
||||
* Recovered CFunctions::Calibration piecewise-linear map (libcoresdk.so 0x6a41c):
|
||||
* diff = (int16)(frame[p] - baseline[p]) >> 1
|
||||
* band = first index i where diff <= thresholds[p*bands+i] (linear scan, max bands-1)
|
||||
* v = coeff[(band*pixels + p)*2 + 1] + ((diff * coeff[(band*pixels + p)*2]) >> 12)
|
||||
* out[p] = clamp(v, 0, 0xffff)
|
||||
* When no baseline is set, diff = frame[p] >> 1.
|
||||
*/
|
||||
void calibrate_frame(
|
||||
const uint16_t* frame,
|
||||
const IrCalibrationTables& tables,
|
||||
uint16_t* out
|
||||
);
|
||||
|
||||
} // namespace mag160c::core
|
||||
|
||||
#endif
|
||||
@@ -1,70 +0,0 @@
|
||||
#include "core/tcm_device.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace mag160c::core {
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t MAIN_TCM = 0x02;
|
||||
constexpr uint8_t SUB_ROTATE = 0x77;
|
||||
constexpr uint8_t SUB_LIGHT_STEADY = 0x31;
|
||||
constexpr uint8_t SUB_LIGHT_BLINK = 0x32;
|
||||
constexpr uint8_t SUB_LIGHT_BREATH = 0x33;
|
||||
|
||||
uint8_t light_subcommand(mag160c_tcm_light_mode_t mode) {
|
||||
switch (mode) {
|
||||
case MAG160C_TCM_LIGHT_STEADY:
|
||||
return SUB_LIGHT_STEADY;
|
||||
case MAG160C_TCM_LIGHT_BLINK:
|
||||
return SUB_LIGHT_BLINK;
|
||||
case MAG160C_TCM_LIGHT_BREATH:
|
||||
return SUB_LIGHT_BREATH;
|
||||
}
|
||||
return SUB_LIGHT_STEADY;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> light_payload(mag160c_tcm_light_color_t color) {
|
||||
switch (color) {
|
||||
case MAG160C_TCM_LIGHT_OFF:
|
||||
return {0x00, 0x00, 0x00, 0x00};
|
||||
case MAG160C_TCM_LIGHT_RED:
|
||||
return {0x01, 0xff, 0x00, 0x00};
|
||||
case MAG160C_TCM_LIGHT_GREEN:
|
||||
return {0x01, 0x00, 0xff, 0x00};
|
||||
case MAG160C_TCM_LIGHT_BLUE:
|
||||
return {0x01, 0x00, 0x00, 0xff};
|
||||
case MAG160C_TCM_LIGHT_YELLOW:
|
||||
return {0x01, 0xff, 0xff, 0x00};
|
||||
}
|
||||
return {0x00, 0x00, 0x00, 0x00};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TcmCommandBuilder::TcmCommandBuilder() : frame_id_(1) {}
|
||||
|
||||
uint16_t TcmCommandBuilder::next_frame_id() {
|
||||
const uint16_t current = frame_id_;
|
||||
++frame_id_;
|
||||
if (frame_id_ >= 0x8000) {
|
||||
frame_id_ = 1;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> TcmCommandBuilder::build_rotate_frame(int angle) {
|
||||
angle = std::clamp(angle, -128, 128);
|
||||
|
||||
const uint8_t direction = angle < 0 ? 0x01 : 0x00;
|
||||
const uint8_t magnitude = static_cast<uint8_t>(angle < 0 ? -angle : angle);
|
||||
return encode_tcm_frame(MAIN_TCM, SUB_ROTATE, next_frame_id(), {direction, magnitude});
|
||||
}
|
||||
|
||||
std::vector<uint8_t> TcmCommandBuilder::build_light_frame(
|
||||
mag160c_tcm_light_color_t color,
|
||||
mag160c_tcm_light_mode_t mode
|
||||
) {
|
||||
return encode_tcm_frame(MAIN_TCM, light_subcommand(mode), next_frame_id(), light_payload(color));
|
||||
}
|
||||
|
||||
} // namespace mag160c::core
|
||||
@@ -1,30 +0,0 @@
|
||||
#ifndef MAG160C_CORE_TCM_DEVICE_HPP
|
||||
#define MAG160C_CORE_TCM_DEVICE_HPP
|
||||
|
||||
#include "core/tcm_frame.hpp"
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
class TcmCommandBuilder {
|
||||
public:
|
||||
TcmCommandBuilder();
|
||||
|
||||
std::vector<uint8_t> build_rotate_frame(int angle);
|
||||
std::vector<uint8_t> build_light_frame(
|
||||
mag160c_tcm_light_color_t color,
|
||||
mag160c_tcm_light_mode_t mode
|
||||
);
|
||||
|
||||
private:
|
||||
uint16_t next_frame_id();
|
||||
|
||||
uint16_t frame_id_;
|
||||
};
|
||||
|
||||
} // namespace mag160c::core
|
||||
|
||||
#endif
|
||||
@@ -1,101 +0,0 @@
|
||||
#include "core/tcm_frame.hpp"
|
||||
|
||||
#include "core/error.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t MAX_TCM_PAYLOAD_SIZE = 0xffffU - 5U;
|
||||
|
||||
} // namespace
|
||||
|
||||
uint8_t checksum(const uint8_t* data, size_t begin, size_t end) {
|
||||
if (data == nullptr || begin > end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t sum = 0;
|
||||
for (size_t i = begin; i < end; ++i) {
|
||||
sum = static_cast<uint8_t>(sum + data[i]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> encode_tcm_frame(
|
||||
uint8_t main_cmd,
|
||||
uint8_t sub_cmd,
|
||||
uint16_t frame_id,
|
||||
const std::vector<uint8_t>& payload
|
||||
) {
|
||||
if (payload.size() > MAX_TCM_PAYLOAD_SIZE) {
|
||||
throw std::length_error("TCM payload is too large");
|
||||
}
|
||||
|
||||
const uint16_t body_length = static_cast<uint16_t>(payload.size() + 5U);
|
||||
std::vector<uint8_t> out(static_cast<size_t>(body_length) + 4U, 0);
|
||||
|
||||
out[0] = 0x7e;
|
||||
out[1] = static_cast<uint8_t>((body_length >> 8) & 0xff);
|
||||
out[2] = static_cast<uint8_t>(body_length & 0xff);
|
||||
out[3] = checksum(out.data(), 0, 3);
|
||||
|
||||
out[4] = main_cmd;
|
||||
out[5] = sub_cmd;
|
||||
out[6] = static_cast<uint8_t>((frame_id >> 8) & 0xff);
|
||||
out[7] = static_cast<uint8_t>(frame_id & 0xff);
|
||||
|
||||
for (size_t i = 0; i < payload.size(); ++i) {
|
||||
out[8 + i] = payload[i];
|
||||
}
|
||||
|
||||
out[out.size() - 1] = checksum(out.data(), 4, out.size() - 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
mag160c_error_t decode_tcm_frame(const uint8_t* data, size_t size, TcmFrame* out) {
|
||||
if (data == nullptr || out == nullptr) {
|
||||
set_last_error("decode_tcm_frame: data and out must not be null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (size < 9) {
|
||||
set_last_error("decode_tcm_frame: packet shorter than minimum TCM frame");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (data[0] != 0x7e) {
|
||||
set_last_error("decode_tcm_frame: missing 0x7e header byte");
|
||||
return MAG160C_ERR_CHECKSUM;
|
||||
}
|
||||
|
||||
if (data[3] != checksum(data, 0, 3)) {
|
||||
set_last_error("decode_tcm_frame: invalid header checksum");
|
||||
return MAG160C_ERR_CHECKSUM;
|
||||
}
|
||||
|
||||
const uint16_t body_length =
|
||||
static_cast<uint16_t>((static_cast<uint16_t>(data[1]) << 8) | data[2]);
|
||||
const size_t expected_size = static_cast<size_t>(body_length) + 4U;
|
||||
if (size != expected_size) {
|
||||
set_last_error("decode_tcm_frame: packet size does not match encoded body length");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (data[size - 1] != checksum(data, 4, size - 1)) {
|
||||
set_last_error("decode_tcm_frame: invalid body checksum");
|
||||
return MAG160C_ERR_CHECKSUM;
|
||||
}
|
||||
|
||||
out->main_cmd = data[4];
|
||||
out->sub_cmd = data[5];
|
||||
out->frame_id = static_cast<uint16_t>((static_cast<uint16_t>(data[6]) << 8) | data[7]);
|
||||
out->payload.assign(data + 8, data + size - 1);
|
||||
|
||||
clear_last_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
} // namespace mag160c::core
|
||||
@@ -1,30 +0,0 @@
|
||||
#ifndef MAG160C_CORE_TCM_FRAME_HPP
|
||||
#define MAG160C_CORE_TCM_FRAME_HPP
|
||||
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
struct TcmFrame {
|
||||
uint8_t main_cmd = 0;
|
||||
uint8_t sub_cmd = 0;
|
||||
uint16_t frame_id = 0;
|
||||
std::vector<uint8_t> payload;
|
||||
};
|
||||
|
||||
uint8_t checksum(const uint8_t* data, size_t begin, size_t end);
|
||||
std::vector<uint8_t> encode_tcm_frame(
|
||||
uint8_t main_cmd,
|
||||
uint8_t sub_cmd,
|
||||
uint16_t frame_id,
|
||||
const std::vector<uint8_t>& payload
|
||||
);
|
||||
mag160c_error_t decode_tcm_frame(const uint8_t* data, size_t size, TcmFrame* out);
|
||||
|
||||
} // namespace mag160c::core
|
||||
|
||||
#endif
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef MAG160C_CORE_TRANSPORT_HPP
|
||||
#define MAG160C_CORE_TRANSPORT_HPP
|
||||
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace mag160c::core {
|
||||
|
||||
class Transport {
|
||||
public:
|
||||
virtual ~Transport() = default;
|
||||
virtual mag160c_error_t write(const std::vector<uint8_t>& bytes) = 0;
|
||||
virtual mag160c_error_t read(std::vector<uint8_t>* out, int timeout_ms) = 0;
|
||||
};
|
||||
|
||||
} // namespace mag160c::core
|
||||
|
||||
#endif
|
||||
@@ -1,106 +0,0 @@
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
assert(std::strcmp(mag160c_error_name(MAG160C_OK), "MAG160C_OK") == 0);
|
||||
assert(std::strcmp(mag160c_error_name(MAG160C_ERR_PROTOCOL_UNKNOWN),
|
||||
"MAG160C_ERR_PROTOCOL_UNKNOWN") == 0);
|
||||
assert(std::strcmp(mag160c_error_name(static_cast<mag160c_error_t>(9999)),
|
||||
"MAG160C_ERR_UNKNOWN_CODE") == 0);
|
||||
|
||||
assert(mag160c_init(nullptr) == MAG160C_ERR_INVALID_ARGUMENT);
|
||||
assert(std::strstr(mag160c_last_error(), "out_ctx") != nullptr);
|
||||
|
||||
mag160c_context_t* ctx = nullptr;
|
||||
assert(mag160c_init(&ctx) == MAG160C_OK);
|
||||
assert(ctx != nullptr);
|
||||
assert(std::strcmp(mag160c_last_error(), "") == 0);
|
||||
|
||||
mag160c_shutdown(ctx);
|
||||
mag160c_shutdown(nullptr);
|
||||
|
||||
assert(mag160c_list_devices(nullptr, nullptr, nullptr) == MAG160C_ERR_INVALID_ARGUMENT);
|
||||
|
||||
ctx = nullptr;
|
||||
assert(mag160c_init(&ctx) == MAG160C_OK);
|
||||
mag160c_device_info_t* devices = reinterpret_cast<mag160c_device_info_t*>(0x1);
|
||||
size_t device_count = 42;
|
||||
#if MAG160C_HAS_LIBUSB
|
||||
assert(mag160c_list_devices(ctx, &devices, &device_count) == MAG160C_OK);
|
||||
mag160c_free_device_list(devices);
|
||||
#else
|
||||
assert(mag160c_list_devices(ctx, &devices, &device_count) == MAG160C_ERR_UNSUPPORTED);
|
||||
assert(devices == nullptr);
|
||||
assert(device_count == 0);
|
||||
assert(std::strstr(mag160c_last_error(), "libusb") != nullptr);
|
||||
mag160c_free_device_list(devices);
|
||||
#endif
|
||||
mag160c_shutdown(ctx);
|
||||
|
||||
const uint8_t payload[] = {0x00, 0x05};
|
||||
const uint8_t expected[] = {
|
||||
0x7e, 0x00, 0x07, 0x85, 0x02, 0x77, 0x00, 0x01, 0x00, 0x05, 0x7f};
|
||||
|
||||
size_t encoded_size = 0;
|
||||
assert(mag160c_tcm_encode_frame(0x02, 0x77, 0x0001, payload, sizeof(payload), nullptr, 0,
|
||||
&encoded_size) == MAG160C_ERR_INVALID_ARGUMENT);
|
||||
assert(encoded_size == sizeof(expected));
|
||||
|
||||
uint8_t encoded[sizeof(expected)] = {};
|
||||
assert(mag160c_tcm_encode_frame(0x02, 0x77, 0x0001, payload, sizeof(payload), encoded,
|
||||
sizeof(encoded), &encoded_size) == MAG160C_OK);
|
||||
assert(encoded_size == sizeof(expected));
|
||||
assert(std::memcmp(encoded, expected, sizeof(expected)) == 0);
|
||||
|
||||
uint8_t main_cmd = 0;
|
||||
uint8_t sub_cmd = 0;
|
||||
uint16_t frame_id = 0;
|
||||
size_t payload_size = 0;
|
||||
assert(mag160c_tcm_decode_header(encoded, encoded_size, &main_cmd, &sub_cmd, &frame_id,
|
||||
&payload_size) == MAG160C_OK);
|
||||
assert(main_cmd == 0x02);
|
||||
assert(sub_cmd == 0x77);
|
||||
assert(frame_id == 0x0001);
|
||||
assert(payload_size == sizeof(payload));
|
||||
|
||||
size_t rotate_size = 0;
|
||||
assert(mag160c_tcm_build_rotate_frame(5, nullptr, 0, &rotate_size) ==
|
||||
MAG160C_ERR_INVALID_ARGUMENT);
|
||||
assert(rotate_size == sizeof(expected));
|
||||
|
||||
uint8_t rotate_frame[sizeof(expected)] = {};
|
||||
assert(mag160c_tcm_build_rotate_frame(5, rotate_frame, sizeof(rotate_frame), &rotate_size) ==
|
||||
MAG160C_OK);
|
||||
assert(rotate_size == sizeof(expected));
|
||||
assert(std::memcmp(rotate_frame, expected, sizeof(expected)) == 0);
|
||||
|
||||
const uint8_t expected_green_blink[] = {
|
||||
0x7e, 0x00, 0x09, 0x87, 0x02, 0x32, 0x00, 0x01, 0x01, 0x00, 0xff, 0x00, 0x35};
|
||||
size_t light_size = 0;
|
||||
uint8_t light_frame[sizeof(expected_green_blink)] = {};
|
||||
assert(mag160c_tcm_build_light_frame(MAG160C_TCM_LIGHT_GREEN, MAG160C_TCM_LIGHT_BLINK,
|
||||
light_frame, sizeof(light_frame), &light_size) ==
|
||||
MAG160C_OK);
|
||||
assert(light_size == sizeof(expected_green_blink));
|
||||
assert(std::memcmp(light_frame, expected_green_blink, sizeof(expected_green_blink)) == 0);
|
||||
|
||||
assert(mag160c_tcm_build_light_frame(static_cast<mag160c_tcm_light_color_t>(99),
|
||||
MAG160C_TCM_LIGHT_BLINK, light_frame,
|
||||
sizeof(light_frame), &light_size) ==
|
||||
MAG160C_ERR_INVALID_ARGUMENT);
|
||||
assert(std::strstr(mag160c_last_error(), "color") != nullptr);
|
||||
|
||||
const std::vector<uint8_t> oversized_payload(65531U, 0xaa);
|
||||
encoded_size = 123U;
|
||||
assert(mag160c_tcm_encode_frame(0x02, 0x77, 0x0001, oversized_payload.data(),
|
||||
oversized_payload.size(), nullptr, 0, &encoded_size) ==
|
||||
MAG160C_ERR_INVALID_ARGUMENT);
|
||||
assert(encoded_size == 0);
|
||||
assert(std::strstr(mag160c_last_error(), "too large") != nullptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "core/device.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
using mag160c::core::EndpointDescriptor;
|
||||
using mag160c::core::EndpointPair;
|
||||
using mag160c::core::EndpointType;
|
||||
|
||||
const std::vector<EndpointDescriptor> endpoints = {
|
||||
{0x01, EndpointType::Interrupt},
|
||||
{0x82, EndpointType::Bulk},
|
||||
{0x03, EndpointType::Bulk},
|
||||
};
|
||||
|
||||
EndpointPair pair{};
|
||||
assert(mag160c::core::find_bulk_pair(endpoints, &pair));
|
||||
assert(pair.bulk_in == 0x82);
|
||||
assert(pair.bulk_out == 0x03);
|
||||
|
||||
const std::vector<EndpointDescriptor> missing_out = {
|
||||
{0x82, EndpointType::Bulk},
|
||||
};
|
||||
|
||||
pair = {};
|
||||
assert(!mag160c::core::find_bulk_pair(missing_out, &pair));
|
||||
assert(!mag160c::core::find_bulk_pair(endpoints, nullptr));
|
||||
|
||||
assert(mag160c::core::is_in_endpoint(0x82));
|
||||
assert(!mag160c::core::is_in_endpoint(0x03));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
#include "core/ir_frame.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
void test_parse_valid_frame() {
|
||||
// 2x1 frame: marker + counter + len + type + shutter + 2 pixels + trailing marker + tail
|
||||
std::vector<uint8_t> data(0x38 + 4, 0xaa);
|
||||
auto w32 = [&](size_t off, uint32_t v) {
|
||||
data[off] = static_cast<uint8_t>(v);
|
||||
data[off + 1] = static_cast<uint8_t>(v >> 8);
|
||||
data[off + 2] = static_cast<uint8_t>(v >> 16);
|
||||
data[off + 3] = static_cast<uint8_t>(v >> 24);
|
||||
};
|
||||
w32(0x00, 0x1bb1b11b);
|
||||
w32(0x04, 42);
|
||||
w32(0x08, 4); // data_length
|
||||
w32(0x0c, 1); // raw frame type
|
||||
w32(0x10, 1000); // shutter
|
||||
data[0x1c] = 0x34;
|
||||
data[0x1d] = 0x12;
|
||||
data[0x1e] = 0x78;
|
||||
data[0x1f] = 0x56;
|
||||
w32(0x1c + 4, 0x1bb1b11c);
|
||||
|
||||
mag160c::core::IrFrameHeader h{};
|
||||
const uint16_t* pixels = nullptr;
|
||||
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) == MAG160C_OK);
|
||||
assert(h.marker == 0x1bb1b11b);
|
||||
assert(h.frame_counter == 42);
|
||||
assert(h.data_length == 4);
|
||||
assert(h.frame_type == 1);
|
||||
assert(h.period_shutter == 1000);
|
||||
assert(pixels[0] == 0x1234);
|
||||
assert(pixels[1] == 0x5678);
|
||||
}
|
||||
|
||||
void test_parse_rejects_bad_marker() {
|
||||
std::vector<uint8_t> data(0x40, 0);
|
||||
data[0] = 0xab;
|
||||
|
||||
mag160c::core::IrFrameHeader h{};
|
||||
const uint16_t* pixels = nullptr;
|
||||
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) ==
|
||||
MAG160C_ERR_PROTOCOL_UNKNOWN);
|
||||
}
|
||||
|
||||
void test_parse_rejects_bad_type_and_trailer() {
|
||||
std::vector<uint8_t> data(0x40, 0);
|
||||
auto w32 = [&](size_t off, uint32_t v) {
|
||||
data[off] = static_cast<uint8_t>(v);
|
||||
data[off + 1] = static_cast<uint8_t>(v >> 8);
|
||||
data[off + 2] = static_cast<uint8_t>(v >> 16);
|
||||
data[off + 3] = static_cast<uint8_t>(v >> 24);
|
||||
};
|
||||
w32(0x00, 0x1bb1b11b);
|
||||
w32(0x08, 4);
|
||||
w32(0x0c, 2); // invalid type
|
||||
mag160c::core::IrFrameHeader h{};
|
||||
const uint16_t* pixels = nullptr;
|
||||
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) ==
|
||||
MAG160C_ERR_PROTOCOL_UNKNOWN);
|
||||
|
||||
w32(0x0c, 0);
|
||||
w32(0x1c + 4, 0xdeadbeef); // bad trailing marker
|
||||
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) ==
|
||||
MAG160C_ERR_PROTOCOL_UNKNOWN);
|
||||
}
|
||||
|
||||
void test_calibrate_flat_band() {
|
||||
// 4 pixels, 2 bands (1 search threshold), threshold = {5} per pixel
|
||||
const std::vector<uint16_t> frame{1000, 2000, 3000, 4000};
|
||||
const std::vector<int16_t> thresholds{5, 5, 5, 5};
|
||||
// per band per pixel {coeff, offset}: band0 = {0, 100}, band1 = {0, 200}
|
||||
const std::vector<uint16_t> coeff{
|
||||
0, 100, 0, 100, 0, 100, 0, 100,
|
||||
0, 200, 0, 200, 0, 200, 0, 200,
|
||||
};
|
||||
|
||||
mag160c::core::IrCalibrationTables tables{};
|
||||
tables.thresholds = thresholds.data();
|
||||
tables.coeff = coeff.data();
|
||||
tables.pixel_count = 4;
|
||||
tables.band_count = 2;
|
||||
tables.baseline = nullptr;
|
||||
tables.has_baseline = false;
|
||||
|
||||
std::vector<uint16_t> out(4);
|
||||
mag160c::core::calibrate_frame(frame.data(), tables, out.data());
|
||||
|
||||
// diff = frame >> 1 = 500..2000, all > 5 → band 1 → offset 200
|
||||
for (auto v : out) {
|
||||
assert(v == 200);
|
||||
}
|
||||
}
|
||||
|
||||
void test_calibrate_interpolation_and_clamp() {
|
||||
// 2 pixels, 2 bands, threshold = {500}
|
||||
const std::vector<uint16_t> frame{2000, 0x8000};
|
||||
const std::vector<uint16_t> baseline{0, 0x100};
|
||||
const std::vector<int16_t> thresholds{500, 500};
|
||||
// band0: {coeff=0x1000 (4096), offset=1000}; band1: {coeff=0x100, offset=2000}
|
||||
const std::vector<uint16_t> coeff{
|
||||
0x1000, 1000, 0x1000, 1000,
|
||||
0x0100, 2000, 0x0100, 2000,
|
||||
};
|
||||
|
||||
mag160c::core::IrCalibrationTables tables{};
|
||||
tables.thresholds = thresholds.data();
|
||||
tables.coeff = coeff.data();
|
||||
tables.pixel_count = 2;
|
||||
tables.band_count = 2;
|
||||
tables.baseline = baseline.data();
|
||||
tables.has_baseline = true;
|
||||
|
||||
std::vector<uint16_t> out(2);
|
||||
mag160c::core::calibrate_frame(frame.data(), tables, out.data());
|
||||
|
||||
// pixel 0: diff = (2000-0)>>1 = 1000 > 500 → band1: 2000 + (1000*0x100 >> 12) = 2000 + 62
|
||||
assert(out[0] == 2062);
|
||||
// pixel 1: diff = (0x8000-0x100)>>1 = 0x3f80 (16256) > 500 → band1: 2000 + (16256*256 >> 12)
|
||||
// = 2000 + 1016 = 3016
|
||||
assert(out[1] == 3016);
|
||||
}
|
||||
|
||||
void test_calibrate_negative_diff_zero() {
|
||||
const std::vector<uint16_t> frame{100};
|
||||
const std::vector<uint16_t> baseline{200};
|
||||
const std::vector<int16_t> thresholds{5};
|
||||
const std::vector<uint16_t> coeff{0x1000, 1000, 0x1000, 1000};
|
||||
|
||||
mag160c::core::IrCalibrationTables tables{};
|
||||
tables.thresholds = thresholds.data();
|
||||
tables.coeff = coeff.data();
|
||||
tables.pixel_count = 1;
|
||||
tables.band_count = 2;
|
||||
tables.baseline = baseline.data();
|
||||
tables.has_baseline = true;
|
||||
|
||||
std::vector<uint16_t> out(1);
|
||||
mag160c::core::calibrate_frame(frame.data(), tables, out.data());
|
||||
|
||||
// diff = (int16)(100-200) >> 1 = -50; negative coeff*offset path clamps to >= 0:
|
||||
// band0 (diff <= 5): 1000 + (-50 * 4096 >> 12) = 1000 - 50 = 950
|
||||
assert(out[0] == 950);
|
||||
}
|
||||
|
||||
void test_parse_rejects_truncated_frame() {
|
||||
std::vector<uint8_t> data(0x38 + 4, 0);
|
||||
auto w32 = [&](size_t off, uint32_t v) {
|
||||
data[off] = static_cast<uint8_t>(v);
|
||||
data[off + 1] = static_cast<uint8_t>(v >> 8);
|
||||
data[off + 2] = static_cast<uint8_t>(v >> 16);
|
||||
data[off + 3] = static_cast<uint8_t>(v >> 24);
|
||||
};
|
||||
w32(0x00, 0x1bb1b11b);
|
||||
w32(0x08, 4);
|
||||
|
||||
mag160c::core::IrFrameHeader h{};
|
||||
const uint16_t* pixels = nullptr;
|
||||
data.resize(data.size() - 1); // truncate
|
||||
assert(mag160c::core::parse_ir_frame(data.data(), data.size(), &h, &pixels) ==
|
||||
MAG160C_ERR_PROTOCOL_UNKNOWN);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
test_parse_valid_frame();
|
||||
test_parse_rejects_bad_marker();
|
||||
test_parse_rejects_bad_type_and_trailer();
|
||||
test_parse_rejects_truncated_frame();
|
||||
test_calibrate_flat_band();
|
||||
test_calibrate_interpolation_and_clamp();
|
||||
test_calibrate_negative_diff_zero();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
int main() {
|
||||
assert(mag160c_ir_open_first(nullptr, nullptr) == MAG160C_ERR_INVALID_ARGUMENT);
|
||||
assert(std::strstr(mag160c_last_error(), "ctx") != nullptr);
|
||||
|
||||
mag160c_ir_close(nullptr);
|
||||
|
||||
assert(mag160c_ir_get_info(nullptr, nullptr) == MAG160C_ERR_INVALID_ARGUMENT);
|
||||
assert(std::strstr(mag160c_last_error(), "device") != nullptr);
|
||||
|
||||
mag160c_context_t* ctx = nullptr;
|
||||
assert(mag160c_init(&ctx) == MAG160C_OK);
|
||||
|
||||
mag160c_ir_device_t* ir = nullptr;
|
||||
assert(mag160c_ir_open_first(ctx, &ir) == MAG160C_OK);
|
||||
assert(ir != nullptr);
|
||||
|
||||
mag160c_ir_info_t info{};
|
||||
assert(mag160c_ir_get_info(ir, &info) == MAG160C_OK);
|
||||
assert(info.width == 160);
|
||||
assert(info.height == 120);
|
||||
assert(info.output_width == 160);
|
||||
assert(info.output_height == 120);
|
||||
assert(info.max_fps == 25);
|
||||
assert(info.current_fps == 0);
|
||||
assert(std::strcmp(info.name, "MAG160C") == 0);
|
||||
assert(std::strcmp(info.type, "vendor-bulk-ir") == 0);
|
||||
|
||||
assert(mag160c_ir_trigger_ffc(ir) == MAG160C_ERR_UNSUPPORTED);
|
||||
assert(std::strstr(mag160c_last_error(), "0x6bb6b672") != nullptr);
|
||||
assert(std::strstr(mag160c_last_error(), "0x03") != nullptr);
|
||||
|
||||
size_t raw_size = 123;
|
||||
assert(mag160c_ir_read_raw_once(ir, nullptr, 0, &raw_size, 100) ==
|
||||
MAG160C_ERR_UNSUPPORTED);
|
||||
assert(raw_size == 0);
|
||||
assert(std::strstr(mag160c_last_error(), "0x1bb1b11b") != nullptr);
|
||||
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#include "core/tcm_device.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
void test_rotate_positive_payload() {
|
||||
mag160c::core::TcmCommandBuilder builder;
|
||||
const std::vector<uint8_t> expected{
|
||||
0x7e, 0x00, 0x07, 0x85, 0x02, 0x77, 0x00, 0x01, 0x00, 0x05, 0x7f};
|
||||
|
||||
const auto encoded = builder.build_rotate_frame(5);
|
||||
|
||||
assert(encoded == expected);
|
||||
}
|
||||
|
||||
void test_rotate_negative_payload_and_frame_increment() {
|
||||
mag160c::core::TcmCommandBuilder builder;
|
||||
(void)builder.build_rotate_frame(5);
|
||||
|
||||
const std::vector<uint8_t> expected{
|
||||
0x7e, 0x00, 0x07, 0x85, 0x02, 0x77, 0x00, 0x02, 0x01, 0x03, 0x7f};
|
||||
|
||||
const auto encoded = builder.build_rotate_frame(-3);
|
||||
|
||||
assert(encoded == expected);
|
||||
}
|
||||
|
||||
void test_green_blink_payload() {
|
||||
mag160c::core::TcmCommandBuilder builder;
|
||||
const std::vector<uint8_t> expected{
|
||||
0x7e, 0x00, 0x09, 0x87, 0x02, 0x32, 0x00, 0x01, 0x01, 0x00, 0xff, 0x00, 0x35};
|
||||
|
||||
const auto encoded = builder.build_light_frame(MAG160C_TCM_LIGHT_GREEN, MAG160C_TCM_LIGHT_BLINK);
|
||||
|
||||
assert(encoded == expected);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
test_rotate_positive_payload();
|
||||
test_rotate_negative_payload_and_frame_increment();
|
||||
test_green_blink_payload();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
#include "core/tcm_frame.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
void test_rotate_frame_encoding() {
|
||||
const std::vector<uint8_t> payload{0x00, 0x05};
|
||||
const std::vector<uint8_t> expected{
|
||||
0x7e, 0x00, 0x07, 0x85, 0x02, 0x77, 0x00, 0x01, 0x00, 0x05, 0x7f};
|
||||
|
||||
const auto encoded = mag160c::core::encode_tcm_frame(0x02, 0x77, 0x0001, payload);
|
||||
|
||||
assert(encoded == expected);
|
||||
}
|
||||
|
||||
void test_decode_rejects_bad_header_checksum() {
|
||||
auto encoded = mag160c::core::encode_tcm_frame(0x02, 0x77, 0x0001, {0x00, 0x05});
|
||||
encoded[3] ^= 0xff;
|
||||
|
||||
mag160c::core::TcmFrame decoded;
|
||||
assert(mag160c::core::decode_tcm_frame(encoded.data(), encoded.size(), &decoded) ==
|
||||
MAG160C_ERR_CHECKSUM);
|
||||
}
|
||||
|
||||
void test_decode_roundtrip() {
|
||||
const std::vector<uint8_t> payload{0x10, 0x20, 0x30};
|
||||
const auto encoded = mag160c::core::encode_tcm_frame(0xa1, 0xb2, 0xc3d4, payload);
|
||||
|
||||
mag160c::core::TcmFrame decoded;
|
||||
assert(mag160c::core::decode_tcm_frame(encoded.data(), encoded.size(), &decoded) == MAG160C_OK);
|
||||
|
||||
assert(decoded.main_cmd == 0xa1);
|
||||
assert(decoded.sub_cmd == 0xb2);
|
||||
assert(decoded.frame_id == 0xc3d4);
|
||||
assert(decoded.payload == payload);
|
||||
}
|
||||
|
||||
void test_encode_rejects_oversized_payload() {
|
||||
const std::vector<uint8_t> payload(65531U, 0xaa);
|
||||
|
||||
bool threw_length_error = false;
|
||||
try {
|
||||
(void)mag160c::core::encode_tcm_frame(0x02, 0x77, 0x0001, payload);
|
||||
} catch (const std::length_error&) {
|
||||
threw_length_error = true;
|
||||
}
|
||||
|
||||
assert(threw_length_error);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
test_rotate_frame_encoding();
|
||||
test_decode_rejects_bad_header_checksum();
|
||||
test_decode_roundtrip();
|
||||
test_encode_rejects_oversized_payload();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
void print_usage() {
|
||||
std::cout << "usage:\n"
|
||||
<< " mag160c-cli probe\n"
|
||||
<< " mag160c-cli tcm-rotate --dry-run ANGLE\n"
|
||||
<< " mag160c-cli tcm-light --dry-run COLOR MODE\n"
|
||||
<< " mag160c-cli ir-info\n";
|
||||
}
|
||||
|
||||
std::string lower(std::string value) {
|
||||
for (char& ch : value) {
|
||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void print_hex(const uint8_t* bytes, size_t size) {
|
||||
std::ios old_state(nullptr);
|
||||
old_state.copyfmt(std::cout);
|
||||
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
if (i != 0) {
|
||||
std::cout << ' ';
|
||||
}
|
||||
std::cout << std::hex << std::setfill('0') << std::setw(2)
|
||||
<< static_cast<unsigned int>(bytes[i]);
|
||||
}
|
||||
std::cout << '\n';
|
||||
std::cout.copyfmt(old_state);
|
||||
}
|
||||
|
||||
int parse_int(const char* text, int* out) {
|
||||
if (text == nullptr || out == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* end = nullptr;
|
||||
const long value = std::strtol(text, &end, 10);
|
||||
if (end == text || *end != '\0') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*out = static_cast<int>(value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int command_tcm_rotate(int argc, char** argv) {
|
||||
if (argc != 4 || std::string(argv[2]) != "--dry-run") {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int angle = 0;
|
||||
if (!parse_int(argv[3], &angle)) {
|
||||
std::cerr << "invalid angle\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t frame[64] = {};
|
||||
size_t frame_size = 0;
|
||||
const mag160c_error_t rc = mag160c_tcm_build_rotate_frame(
|
||||
angle, frame, sizeof(frame), &frame_size
|
||||
);
|
||||
if (rc != MAG160C_OK) {
|
||||
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
|
||||
return 2;
|
||||
}
|
||||
|
||||
print_hex(frame, frame_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_color(const std::string& value, mag160c_tcm_light_color_t* out) {
|
||||
const std::string v = lower(value);
|
||||
if (v == "off") { *out = MAG160C_TCM_LIGHT_OFF; return 1; }
|
||||
if (v == "red") { *out = MAG160C_TCM_LIGHT_RED; return 1; }
|
||||
if (v == "green") { *out = MAG160C_TCM_LIGHT_GREEN; return 1; }
|
||||
if (v == "blue") { *out = MAG160C_TCM_LIGHT_BLUE; return 1; }
|
||||
if (v == "yellow") { *out = MAG160C_TCM_LIGHT_YELLOW; return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_mode(const std::string& value, mag160c_tcm_light_mode_t* out) {
|
||||
const std::string v = lower(value);
|
||||
if (v == "steady") { *out = MAG160C_TCM_LIGHT_STEADY; return 1; }
|
||||
if (v == "blink") { *out = MAG160C_TCM_LIGHT_BLINK; return 1; }
|
||||
if (v == "breath") { *out = MAG160C_TCM_LIGHT_BREATH; return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
int command_tcm_light(int argc, char** argv) {
|
||||
if (argc != 5 || std::string(argv[2]) != "--dry-run") {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
mag160c_tcm_light_color_t color = MAG160C_TCM_LIGHT_OFF;
|
||||
mag160c_tcm_light_mode_t mode = MAG160C_TCM_LIGHT_STEADY;
|
||||
if (!parse_color(argv[3], &color)) {
|
||||
std::cerr << "invalid color\n";
|
||||
return 1;
|
||||
}
|
||||
if (!parse_mode(argv[4], &mode)) {
|
||||
std::cerr << "invalid mode\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t frame[64] = {};
|
||||
size_t frame_size = 0;
|
||||
const mag160c_error_t rc = mag160c_tcm_build_light_frame(
|
||||
color, mode, frame, sizeof(frame), &frame_size
|
||||
);
|
||||
if (rc != MAG160C_OK) {
|
||||
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
|
||||
return 2;
|
||||
}
|
||||
|
||||
print_hex(frame, frame_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int command_ir_info() {
|
||||
mag160c_context_t* ctx = nullptr;
|
||||
mag160c_ir_device_t* ir = nullptr;
|
||||
mag160c_ir_info_t info{};
|
||||
|
||||
mag160c_error_t rc = mag160c_init(&ctx);
|
||||
if (rc != MAG160C_OK) {
|
||||
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
|
||||
return 2;
|
||||
}
|
||||
|
||||
rc = mag160c_ir_open_first(ctx, &ir);
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_get_info(ir, &info);
|
||||
}
|
||||
|
||||
if (rc != MAG160C_OK) {
|
||||
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::cout << "name: " << info.name << '\n'
|
||||
<< "type: " << info.type << '\n'
|
||||
<< "size: " << info.width << "x" << info.height << '\n'
|
||||
<< "output: " << info.output_width << "x" << info.output_height << '\n'
|
||||
<< "protocol: vid 0x833c config=2 iface=0; cmd EP OUT 0x03 / IN 0x82;\n"
|
||||
<< " stream EP IN 0x81 (marker 0x1bb1b11b, data at +0x1c, size 0x38+len);\n"
|
||||
<< " cmds 0x6bb6b66b..0x6bb6b677 (start 0x6bb6b673, stop 0x6bb6b674,\n"
|
||||
<< " ffc 0x6bb6b672); responses 0x5bb5b55b..0x5bb5b57b\n";
|
||||
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int command_probe() {
|
||||
mag160c_context_t* ctx = nullptr;
|
||||
mag160c_error_t rc = mag160c_init(&ctx);
|
||||
if (rc != MAG160C_OK) {
|
||||
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
|
||||
return 2;
|
||||
}
|
||||
|
||||
mag160c_device_info_t* devices = nullptr;
|
||||
size_t count = 0;
|
||||
rc = mag160c_list_devices(ctx, &devices, &count);
|
||||
if (rc != MAG160C_OK) {
|
||||
std::cerr << mag160c_error_name(rc) << ": " << mag160c_last_error() << '\n';
|
||||
mag160c_shutdown(ctx);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::cout << "devices: " << count << '\n';
|
||||
std::ios old_state(nullptr);
|
||||
old_state.copyfmt(std::cout);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const mag160c_device_info_t& d = devices[i];
|
||||
std::cout << "[" << i << "] vid:pid "
|
||||
<< std::hex << std::setfill('0') << std::setw(4) << d.vendor_id
|
||||
<< ':' << std::setw(4) << d.product_id
|
||||
<< std::dec << " bus " << static_cast<unsigned int>(d.bus)
|
||||
<< " address " << static_cast<unsigned int>(d.address)
|
||||
<< " interface " << static_cast<unsigned int>(d.interface_number)
|
||||
<< " bulk-in 0x" << std::hex << static_cast<unsigned int>(d.bulk_in_endpoint)
|
||||
<< " bulk-out 0x" << static_cast<unsigned int>(d.bulk_out_endpoint)
|
||||
<< '\n';
|
||||
}
|
||||
std::cout.copyfmt(old_state);
|
||||
|
||||
mag160c_free_device_list(devices);
|
||||
mag160c_shutdown(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string command = argv[1];
|
||||
if (command == "probe") {
|
||||
return command_probe();
|
||||
}
|
||||
if (command == "tcm-rotate") {
|
||||
return command_tcm_rotate(argc, argv);
|
||||
}
|
||||
if (command == "tcm-light") {
|
||||
return command_tcm_light(argc, argv);
|
||||
}
|
||||
if (command == "ir-info") {
|
||||
return command_ir_info();
|
||||
}
|
||||
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
# MAG160C 官方 MAG-Cx 完整逆向结论(2026-09-10,jadx + Ghidra 全量解包)
|
||||
|
||||
> 产物来源:
|
||||
> - Java 层:`analysis/sdk_re/android_app/jadx_magcx/`(jadx 1.5.1 反编译官方普通版 MAG-Cx.apk 全量源码)
|
||||
> - Native 层:`analysis/sdk_re/android_app/libcxsdk_decomp.txt`
|
||||
> (Ghidra 11.3.2 headless 全量反编译 lib/armeabi/libcxsdk.so,1290 函数)
|
||||
> - libcoresdk(arm64) 反编译已在 `analysis/sdk_re/android_app/`(专业版/网络路径)
|
||||
> - 本文结论全部来自以上反编译源码,与真机日志交叉验证(vivo V2509A, Android 16)。
|
||||
|
||||
## 1. USB 协议权威版(以官方 Java UsbCommunication.java 为准)
|
||||
|
||||
### 1.1 端点与超时
|
||||
| 端点 | 方向 | 用途 | 超时 |
|
||||
|---|---|---|---|
|
||||
| 0x03 | OUT | 命令(4B 小端 magic;FFC/激光/帧率带参数) | 800ms |
|
||||
| 0x82 | IN | 命令响应/ack(读 ≤64B) | 800ms |
|
||||
| 0x81 | IN | 图像流(28B 头 + 38400B + 28B 尾) | 800ms |
|
||||
| 0x84 | IN | 标定文件下载(16KB 块) | 800ms/块 |
|
||||
| 0x05 | OUT | 向相机发送标定(SendCaliFile 上传方向,本项目暂不用) | 800ms |
|
||||
|
||||
**全部 800ms**(官方 `TIMEOUT` 常量)。响应缓冲 64B。
|
||||
|
||||
### 1.2 命令集(P2DCmd,全部 4 字节小端)
|
||||
| 值 | 官方名 | 说明 |
|
||||
|---|---|---|
|
||||
| 0x6BB6B66B | GetParameter1 | → 0x5BB5B55B BasePara1(60B) |
|
||||
| 0x6BB6B66C | GetParameter2 | → 0x5BB5B55C BasePara2(60B) |
|
||||
| 0x6BB6B66D | SetParameter1 | 带 ack(64B 读) |
|
||||
| 0x6BB6B66E | SetParameter2 | 带 ack |
|
||||
| 0x6BB6B66F | GetCaliInfo | → 0x5BB5B55E {i32 size, i32 reserved, i64 date} |
|
||||
| 0x6BB6B670 | GetCaliFile | ack 0x5BB5B55D,随后相机在 0x84 推标定文件 |
|
||||
| 0x6BB6B671 | SendCaliFile | 上传方向(配 0x05 OUT) |
|
||||
| 0x6BB6B672 | SetShutterState | FFC!8B {magic LE, u32 on LE} |
|
||||
| 0x6BB6B673 | StartTransferImg | 开始推流,带 ack |
|
||||
| 0x6BB6B674 | StopTransferImg | 停流,带 ack |
|
||||
| 0x6BB6B675 | GetLifeTime | → 0x5BB5B561 {magic, i32 开机毫秒} |
|
||||
| 0x6BB6B676 | SetLaserState | 8B(激光,非省电!) |
|
||||
| 0x6BB6B677 | PowerSave | 8B |
|
||||
| 0x6BB6B678 | GetFPAState | → 0x5BB5B562 |
|
||||
| 0x6BB6B679 | SetFrameRate | 12B {magic, i32 interFrame, i32 interLine} |
|
||||
|
||||
D2P 响应魔数:5B=SendParameter1、5C=SendParameter2、5D=SendCaliFile、
|
||||
5E=SendCaliInfo、61=SendLifeTime(均在 0x5BB5B5xx 空间)。
|
||||
|
||||
**注意**:0x6BB6B66B 是回文数(字节对称),因此历史上"66b 有响应、其余全无"
|
||||
正是字节序反了的特征——非回文命令全部变成无效字节。`MagProtocolTest` 已锁死。
|
||||
|
||||
### 1.3 BasePara1(0x5BB5B55B payload,14×i32 = 56B)
|
||||
int[0] serialNumber;int[1] hwVersion(低24位)|devType(高8位);
|
||||
int[2] swVersion;int[4] fpaWidth;int[5] fpaHeight;int[6] fps;
|
||||
int[8] fpaGain;int[9] fpaFlip;int[10] interFrame;int[11] interLine。
|
||||
devType:0=c1, 1=c3, 2=c3p, **3=core160(MAG160C)**, 5=c1pro, 6=c1prolite。
|
||||
|
||||
### 1.4 官方连接序列(connect() + startTransfer())
|
||||
```
|
||||
1. write GetParameter1 → read 64B → BasePara1(失败则整个 connect 放弃)
|
||||
2. write GetParameter2 → read → BasePara2
|
||||
3. write GetCaliInfo → read → {size, reserved, date}
|
||||
4. 缓存判定:{caliDir}/{productType}.{serial}.{date} 存在且长度==size
|
||||
├─ 命中:直接 startTransfer()
|
||||
└─ 未命中:启动 ThreadCaliRecv(0x84 连续读,16KB/块,无数据 5s 放弃)
|
||||
→ sleep 50ms → write GetCaliFile(670)(waitForCmdAck 读 ack)
|
||||
→ 收满 size 字节存盘 → startTransfer(caliPath)
|
||||
5. startTransfer:
|
||||
a. native startProcess(w, h, serial, devType, shutterClose/OpenSpeed,
|
||||
ffcTriggerFrame, ffcTriggerTemperature, paletteIndex, baseLineAcc,
|
||||
caliPath) ← libcxsdk Controller::StartProcess → LoadCalibrationTable
|
||||
(caliPath),加载失败即 startProcess 失败并删除缓存文件
|
||||
b. native setEX / getExtParameter+setExtParameter(dwFlip) / setAutoEnlarge
|
||||
(全部 native,不占 USB)
|
||||
c. 启动 ThreadImgRecv + ThreadImgProcess → sleep 5ms
|
||||
→ write StartTransferImg(673)(waitForCmdAck 读 ack)
|
||||
```
|
||||
|
||||
### 1.5 图像流帧结构(与 FrameStream/管线一致)
|
||||
28B 头:0x1BB1B11B, frameIndex, dataLen(=w*h*2), type(0/1), period, 保留×3。
|
||||
38400B u16LE 像素。
|
||||
28B 尾:0x1BB1B11C, frameIndex, fpaTemp, intDrop(=type), reserved[2]。
|
||||
官方 ThreadImgRecv:搜头→凑满 frameBytes→校验尾→drop∈{0,1} 才交给处理线程;
|
||||
fpaTemp 在尾 +8(devType0/5/6/3),camTemp=fpaTemp-500。
|
||||
|
||||
### 1.6 FFC 触发(官方逻辑)
|
||||
- 双击/消息(MSG_TRIGGER_FFC)→ SetShutterState(672);
|
||||
- 每 16 帧查 GetLifeTime(675):devType1/2 按 6.5s/13s/19.5s/26s 档位触发 FFC;
|
||||
**devType0/5/6/3(含 core160)该路径不自动 FFC**(仅温度稳定标志)。
|
||||
- 本项目管线内部 FFC 调度(onFfc→672)为等效实现,且 672 相机随时接受。
|
||||
|
||||
## 2. 本项目复刻状态(IrSession.kt = 官方 UsbCommunication 等价物)
|
||||
- 连接序列 1-5 已逐行复刻(含 0x84 拉取、缓存 files/cali/{product}.{serial}.{date});
|
||||
- 命令打包小端(与官方 intToByteArray 一致)——**历史上 cmd4/cmd8 用
|
||||
ByteBuffer.putInt 默认大端导致非回文命令全部反转**(16 轮黑屏的根因,
|
||||
MagProtocolTest 锁死);
|
||||
- usfs 坑:bulk 读超时后端点被标 halted,后续传输全部瞬间失败,须每次失败后
|
||||
CLEAR_HALT(IrSession.diagnoseEndpoint);
|
||||
- 会话互斥(同一时刻仅一个 IrSession 持有相机)+ DETACHED 释放 + 去抖。
|
||||
|
||||
## 3. 工具与环境(2026-09-10 本机)
|
||||
- jadx 1.5.1:C:\Tools\jadx-1.5.1(反编译产物 C:\Tools\jadx-out,已拷贝入库
|
||||
analysis/sdk_re/android_app/jadx_magcx/)
|
||||
- Ghidra 11.3.2:C:\Tools\ghidra_11.3.2_PUBLIC;工程 C:\Tools\ghidra_proj\MagCX;
|
||||
全量反编译脚本 C:\Tools\ghidra_scripts_user\DumpAllDecomp.java
|
||||
- 重跑命令(PowerShell/cmd):
|
||||
`set JAVA_HOME=C:\Tools\jdk-21&& C:\Tools\ghidra_11.3.2_PUBLIC\support\analyzeHeadless.bat C:\Tools\ghidra_proj MagCX -process libcxsdk.so -scriptPath C:\Tools\ghidra_scripts_user -postScript DumpAllDecomp.java <输出文件>`
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
0 0 208192682 10843 0 45340
|
||||
1 0 208110489 10839 0 45340
|
||||
2 0 208188334 10843 0 45340
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user