Compare commits

...
69 Commits
Author SHA1 Message Date
ZXCLI ee6859877a android+docs: debug 日志默认关闭;操作手册升级为插图版
debug 日志:DebugLog 增加总开关 enabled(默认 false),log() 直接返回、
init() 不装崩溃钩子、startFile() 不建文件。要在设备上排查时改成 true 重新构建
即可,其它代码不动。关闭后崩溃仍按安卓常规方式可见(logcat AndroidRuntime +
系统弹窗),只是不再有落在相册目录旁的 debug_*.log。

真机烟雾测试(小米 22041211AC,相机插着):出流正常、拍照成功(相册 19→20,
新增 MAG160C_20260912_153522.jpg)、崩溃 0、App 自身日志行数 0、无新日志文件。

插图版手册 docs/android_app/app_manual.md + manual_images/:
- 02_markers:标记结构解剖(准星/读数/引线/描边字),真机照片放大 3 倍并标注
- 03/04:横屏 960x720 与竖屏 720x960 的真实照片
- 05/06/07:设置、分析(含底部测量面板)、相册 真机截图
- 01_live_schematic:实时界面改为**自绘示意图**,因为实时 OSD 文字按握持角度预旋转
  (用户要求的可读性设计),手册拍摄时手机横放,截图里文字全是侧躺的无法阅读;
  其余界面文字本就水平,直接用截图。

图由 manual_images/MakeFigures.java 生成(裁剪缩放/放大标注/示意图绘制),
界面改动后重跑即可;原始截图存为 JPEG(PNG→JPEG 省约 8MB)作为可再生输入。
.class 已加入 .gitignore。

README 阅读顺序加入操作手册、结构表更新、里程碑补记根因级修复。105 项测试全绿。
2026-09-12 15:36:34 +08:00
ZXCLI 6ba90f2bb6 docs: 修正操作手册里的标记示意图(四臂+引线+右侧读数,与实际绘制一致) 2026-09-12 15:14:35 +08:00
ZXCLI e3f0729218 docs: 新增 App 操作手册 + README 里程碑/阅读顺序更新
docs/android_app/app_manual.md(新,223 行):
- 安装与首次连接(含"重启后 USB 授权失效需重新确认")
- 界面总览图、实时界面各控件、测温点增删
- 标记怎么读(准星/描边字/MAX-MIN/引线的含义)
- 相册、分析(离线测温)逐项操作
- 设置逐项说明,并标出**是否真正生效**:发射率/报警温度/语言目前仅保存未接入,
  云同步为占位——避免文档承诺 App 做不到的事
- 方向规则(文件跟随握持、预览锁定、录像方向在按下瞬间确定)
- 文件位置与 MDT 容器格式、远程预览双机用法、常见问题、已知限制
  (相册不列录像、红热调色板为近似、PDF/巡检未接入界面)

README.md:阅读顺序加入操作手册;仓库结构表更新;里程碑补记 2026-09-11~12
真机多轮联调的三个根因级修复(launchMode / SET_CONFIGURATION / 软件画布卡顿,
6.5→15.1fps)与同期完成项。

session_state.md 第二十四轮条目补记本轮新增文档。
2026-09-12 15:14:05 +08:00
ZXCLI f465831d8e android: 修正横屏拍摄方向——文件旋转跟随握持角度,不再只跟显示旋转
用户:"切到横屏拍照片和视频方向又不对了"。

根因:上一轮把文件旋转定成显示旋转(锁定 90° + 手动旋转),而这与手机物理姿态无关。
相机模组固定在手机上,手机转 90° 场景就在传感器画面里转 90°,文件必须跟着转,
否则横屏拍出来就是"竖幅文件里装着横躺的场景"。

修法(普通相机做法):文件旋转 = 显示旋转 + 握持角度(PhotoSaver.captureRotation,
握持角取自 DeviceOrientation.deg)。竖直/倒置 → 竖幅 720x960;两个横屏姿态 → 横幅
960x720,且两者相差 180°。录像在开始录制时采样一次(编码尺寸固定)。预览仍保持锁定
(用户此前明确要求),所以横屏文件不是预览截图,而是预览再转一个握持角。
真机复现时设备恰在 270° 姿态(gx=9.88, gy=-1.02 即 deg=270):
修复前 720x960 竖幅;修复后 960x720 横幅,把修复前文件转 -90° 与新文件逐像素比对
平均亮度差 5.06、转 +90° 为 40.94 —— 方向正确且只差这一转。录像 tkhd 960x720
97帧 0 丢弃;分析页读横幅照片 rot=0:MIN/MAX 各一个且与烧录标记重合。

新增 savedFileRotationFollowsTheGrip / landscapeGripsProduceLandscapeFiles。
105 项测试全绿。文档补记教训:文件方向是世界坐标问题、预览方向是屏幕坐标问题,
两者可以不同;这类"方向正确"必须用同一场景在不同姿态下可复现拍摄来验证。
2026-09-12 15:02:03 +08:00
ZXCLI 4c940fe773 android: 照片改为竖屏方向 + 标注风格重做(去白底,准星+描边字)+ 录像 3x 编码
照片方向(推翻上一轮的"传感器朝向"决定,用户明确否掉):
把显示旋转(锁定 90° + 手动旋转)与手动翻转一起烘进 JPEG,竖屏持机拍出来就是
竖屏 720x960,顺序与实时渲染器一致(先镜像再旋转)。温度数据不跟着转——probes/
NUC/extremes 仍在原始传感器空间,旋转只存在于呈现层;BLOCK_RENDER 升到 version 2
多存 rotDeg,旧文件(8B v1 块)解析为 rot=0,符合它们确实是传感器朝向的事实。
分析页映射改为直接复用 PhotoSaver.sensorToPhoto(...,1,1),杜绝两处各算一套。

标注风格(参考 FLIR/Testo 的仪表做法):
- 图标改为细线方形准星 + 四短臂(原为圆环+圆点):方框限定测量区域、四臂指明确切
  像素、中心镂空不遮挡被测点。
- 文字白字+深色描边(先描边后填充),去掉白色底板——白底挡住被测画面且像消费级 App;
  描边让文字在黑冷端和白色热端都读得清。
- 极值改用 MAX/MIN 大写 + 引线把读数连到自己的准星;颜色仍取白色(彩色标点在铁虹
  橙黄区会糊掉,区分靠文字)。

标注尺寸统一:AnnotSpec 的 320 单位是图像长边,实时界面原用 viewport.width()(竖屏
时是短边)去除 320,使实时标点只有照片的 3/4 大——这正是用户早先"照片标点太大"的
由来。现四处统一 max(w,h)/320。

录像清晰度:编码尺寸改为传感器 x3(竖屏 720x960),码率随像素数放大;帧的翻转+旋转
用一个 Matrix 一次 drawBitmap 完成,标点经 sensorToPhoto 落在同一变换下(矩阵复合
结果与 sensorToPhoto 按坐标推导核对一致)。实测 tkhd 720x960 / avc1 / 96帧 0丢失。

分析页"每个标记出现两次":不是坐标错,而是标签避让与绘制顺序有关——拍摄按
probes→MAX→MIN、分析按 MAX→MIN→probes,避让把标签推到不同位置。改为同序后叠加
完全重合(真机裁剪对比确认)。

排查方法:验证"文件里标记位置是否正确"不靠肉眼看截图叠加,直接解析 MDT 算期望像素
再统计该处中性白色像素数——据此一次证伪"旋转没生效":rot=90 处 markerPixels=
204/219/296/719,rot=0 处全为 0。

103 项测试全绿(新增 mirror x rotation 往返、旋转角点、v1→v2 渲染块兼容)。
2026-09-12 14:34:54 +08:00
ZXCLI 9e64c74ffe android: 录像标注日志给出完整判据 + 补充重启后真机验证记录
录像首帧日志原来只打印标记数量,无法判断"少了的标记是为什么少的"。现在一并
打印判据(probes 数量、trace 模式、minPos/maxPos、minC/maxC),录像里缺
max/min 时可直接从日志定位原因(追踪被关、极值尚未算出、重启清空了测点等)。

排查记录(重要):本轮一度以为录像丢了极值标记,实测日志
`annotation: 2 markers ... trace=BOTH minPos=159 maxPos=16269` 说明 probes=0、
2 个标记正好是 max+min ——标记本身是齐的,是我的读法错了(中途重装 APK 清空了
测点,我误把 2 当成"两个测点")。因此保留并强化了这条日志。

补充真机验证(重启冷启动 + 60s 稳定性):
- 重启后 USB 权限被系统清空、重新授权后一次成功,15.1fps、无重枚举
- 录像 99 帧 / 0 丢弃 / 1.6MB,极值标记确认烧入
- 长时间运行 15.1fps 稳定、开增强(8 档)单帧 9.2~9.4ms
- 照片/分析面板/底部面板三方数值完全一致
  (同帧照片 max 36.0 min 20.4 Pt1 21.7 Pt2 26.1 ↔ 面板 36.0/20.4/24.5 + 21.7/26.1)
- 文档记录:判断自绘画面帧率不能用 dumpsys gfxinfo(看不到 lockCanvas 绘制),
  必须用内置 MAG160C/render 日志。99 项测试全绿。
2026-09-12 13:46:08 +08:00
ZXCLI e43e93025e android: 修正增强档位说明(写反了)+ 分析页测温点沿用照片数值
上一提交把档位含义写成了「越高纳入越多弱对比区域」,方向是反的。按公式
amplitude = strength*(center-mean)/divisor, divisor = max(max-mean, mean-min, mapStrength),
mapStrength = strength*gain*2>>8 重新推导:
- 弱对比区域 divisor 被 mapStrength 兜住 → 幅度 = 128/gain,与档位无关;
- 强边缘 divisor 由对比度主导 → 幅度随档位线性增长;
- 门槛 mapStrength <= range*32 → 档位越高弱对比区域被整个排除(正是噪声所在处)。
即高档位是「更锐但更挑」。设置页文案与 DetailEnhance 文档已改正,
并加 theLevelRaisesTheContrastGateAndDropsWeakDetail 锁定
(range=60 窗口:8 档 mapStrength 1500 通过、16 档 3000 被拒)。
真机 HF 实测 0.365(关) / 0.438(6档) / 0.433(8档),与推导一致。

分析页测温点原先前先重新测量 NUC,于是照片上烧录的 Pt2 26.1℃ 与面板 25.9℃
不一致(与极值同一类漂移)。现在优先用文件记录的 tempMc——照片上印的就是它,
仅在记录缺失时才测量。真机确认照片/面板/极值三方数值完全一致。

真机(小米 22041211AC/Android12/MIUI,重启后冷启动):15.1fps 稳定、单帧 6.1~6.6ms、
6/8 档 9.9~10.3ms、拍照 extremes=2、分析页只有一个 min/max 且与照片重合、
底部面板可见(36.0/20.4/24.5)。101 项测试全绿。
2026-09-12 13:36:10 +08:00
ZXCLI f41668d5d1 android: 修正 7x7 细节增强的混叠系数(原实现把增益乘了两遍,画面满噪点)
官方 FilterDetailEnhancement_Simple 把「增益缩放后」的 k = strength*gain*2>>8 只
传给 map(决定阈值与 divisor 下限),混叠系数用的是**原始 strength**。本移植最初
两处都用 k,等于把细节再乘一遍增益:真机 level 2 满屏噪点。改为原始 strength 后
HF 能量与关闭档基本相同(0.47-0.51 vs 0.46-0.47),边缘仍被增强。

细节校正:
- 一个有用的推论:每档的边缘强度其实与档位无关(化简为 128/gain),档位只决定
  哪些窗口通过对比度阈值——所以调高档位是"纳入更多弱对比区域",不是"推得更狠"。
  据此把档位从 0-4 扩到 0/1/2/3/4/6/8(官方 MAG_SetDetailEnhancement 钳 0..32)。
- 官方 App 自身不调用 SetDetailEnhancement(jadx 全量搜索无此符号),SetEX 是数码
  变焦 ROI 与增强无关;故无法据此对齐官方默认档,保持用户可见设置、默认关闭。

测试:
- 新增 blendUsesTheRawStrengthNotTheGainScaledOne 黄金值用例(推导写在注释里),
  把 bug 改回去会立刻失败(已实测验证)。
- 原 gainScalesTheEffect 改为 gainDoesNotAmplifyNoiseOnAFlatField + gainBelowOneIsANoOp,
  因为修正后增益不再缩放混叠幅度。98 项测试全绿。

顺带修复(同一轮真机复现):
- no_device 死锁:首次 connect 撞上相机重枚举时停在「无相机」,且紧随其后的
  ATTACHED 广播被 800ms 防抖丢弃 → 永久无画面。防抖/退避改为延后重试,no_device
  也安排 1.5s 重试(scheduleRetry 单槽位、最新优先)。
- 手机平放时加速度计 x/y≈0,迟滞逻辑保留旧姿态角导致 OSD 文字整体转 90°;
  改为 tilt<4m/s² 判定平放、姿态归零。
- 录像帧缺 max/min 标记:probesAsMarks() 现在一并返回,录像与实时画面一致。

真机(小米 22041211AC/Android12/MIUI):15.1fps 稳定、单帧 6-9ms(开增强 11ms)、
拍照与分析页标记一致且各只有一个 min/max、设置跨冷启动持久化、
屏幕熄灭时渲染停止无空转(CPU 3.7%)。
2026-09-12 05:04:50 +08:00
ZXCLI 4ebdca109a android: 实时画面卡顿根因是软件画布(6.5→15.1fps),标注统一 + 7x7 细节增强
卡顿: 热像走 SurfaceView.lockCanvas 的软件画布, 每帧在 CPU 上做 320x240 ->
~810x1080 双线性放大 + 清整屏, 实测只有 6.5 帧/秒、单帧 134ms。dumpsys gfxinfo
看不到这条路径(只统计 Compose), 故给渲染线程加自测量日志才定位到。改用
lockHardwareCanvas(API29+, 旧系统回退) 后 15.1 帧/秒、单帧 6.4ms。
同时消除每帧垃圾: 复用翻转缓冲/标记列表, 色条改预渲染位图; 温度扫描从主线程
移到 Dispatchers.Default。

标注统一: 新增 AnnotSpec(唯一几何定义) + MarkerPainter(唯一绘制例程), 实时/
分析/照片/视频共用, 极端值标签改用 max/min; 照片按传感器朝向 3x 分辨率保存。
修 placeLabel 不判重叠(min 与 max 印成一团), 现支持让位。

分析对齐: 极端值漏了 sensorToPhoto 映射(落在左上角几十像素内), annotateJpeg
忽略照片镜像(另存时标记镜像到另一侧), 两处已修。

新增 BLOCK_EXTREMES(0x5BB5B562) 存拍摄时的极值位置/温度: 分析页重扫 NUC 不可能
逐位复现 live 结果, 真机出现过两个相距几像素的 min。

分析面板被导航栏遮挡: UiInsets.navPx 改可观察状态并加 padding。

7x7 局部细节增强(官方 FilterDetailEnhancement_Simple 逐行移植), 强度换算
level shl 3 经 MAG_SetDetailEnhancement(钳 0..32)核对一致; 设置页新增
"图像增强" 0-4 级, 默认关闭(无官方参考输出), 不影响逐位基线。

真机验证(小米 22041211AC/Android12/MIUI): 15.1fps、拍照含 2 探针+2 极值、
照片与分析页标记位置/风格一致且各只有一个 min/max、分析数值与照片吻合、
增强 2 级细节明显且不掉帧。测试 96 项全绿。
2026-09-12 04:20:16 +08:00
ZXCLI 47037f3a23 android: unify temperature annotations (one spec, sharp 3x photos), sensor-orientation photos, video annotations, palette cache + render loop, Android 8 API guards 2026-09-12 03:06:27 +08:00
ZXCLI 376b8389ea android: fix launchMode (singleTask) root cause of reconnect storm; drop SET_CONFIGURATION reset; fix analysis temp units, NUC block size, viewer back keys 2026-09-12 02:30:05 +08:00
ZXCLI 4cb6e69ea4 android: fix analysis temperatures (store calibrated NUC data), marker sizing, bottom data panel; split album (zoom/delete) from analysis tab 2026-09-12 00:05:49 +08:00
ZXCLI 15a7127287 android: fix recording crash+save, gallery viewer unreachable, back key, settings persistence; photo orientation+annotations, analyzable MDT probes, trace mode 2026-09-11 23:26:27 +08:00
ZXCLI 3704ee797b android: settings screen scrollable + wire dead rows (language/about) + instant orientation apply 2026-09-11 03:59:55 +08:00
ZXCLI f7bca894ba android: revert image to locked orientation (user decision); keep official-style manual rotate/flip settings 2026-09-11 03:50:56 +08:00
ZXCLI 464cfcb5e8 android: fix real-device defects (temp double-conversion, FFC temp jump, image orientation per grip, remote raw+metadata stream) 2026-09-11 03:01:13 +08:00
ZXCLI 0919f5e186 docs+analysis: independent verification guide for phases A-F (calibrated scanners, self-tested commands) 2026-09-11 01:10:18 +08:00
ZXCLI 8e1312a137 android: phase Z wrap-up (docs, release build, camera feature optional, APK) 2026-09-11 00:57:46 +08:00
ZXCLI 512508e629 android: LAN remote preview (UDP discovery + raw-frame TCP stream, client-side rendering) 2026-09-11 00:54:13 +08:00
ZXCLI f8b3200464 android: visible-light PIP overlay (camera2, draggable, 3 sizes) 2026-09-11 00:31:00 +08:00
ZXCLI c34940e6fd android: cloud module scaffold (retrofit, opt-in, disabled by default) 2026-09-11 00:22:25 +08:00
ZXCLI b7a928e23d android: vendor palette extraction tooling (+exact tables if found) 2026-09-11 00:15:39 +08:00
ZXCLI 656d419f81 android: MDT temperature decode + analyzer probe UI 2026-09-10 23:43:19 +08:00
ZXCLI 06c1f30b13 android: lifetime query + cali consistency check + real-device checklist 2026-09-10 23:34:10 +08:00
ZXCLI 087e15cb19 docs: execution plan for remaining backlog (written for no-vision executor model) 2026-09-10 23:18:49 +08:00
ZXCLI e69a97436b chore: reorganize analysis/ by SDK lineage (linux/win/android) with README index, move legacy notes to history/, refresh root README, ignore .zcode/ 2026-09-10 03:09:13 +08:00
ZXCLI 4ba98f62cd analysis: full official-app reverse artifacts (jadx sources, ghidra libcxsdk decomp, authoritative protocol conclusions) + docs 2026-09-10 02:51:37 +08:00
ZXCLI 623d62b641 android: CRITICAL fix - commands were byte-reversed (ByteBuffer big-endian); official app sends little-endian, palindrome 66b masked it for 16 rounds 2026-09-10 02:39:19 +08:00
ZXCLI b29d7fd377 android: port official MAG-Cx UsbCommunication handshake verbatim (66b/66c/66f 800ms, cali file fetch via 670+EP0x84, BasePara parse) 2026-09-10 02:28:46 +08:00
ZXCLI 8ecd50228a android: match official libcoresdk handshake - 66f cali-info, 670+EP0x84 cali fetch w/ cache, drop legacy 66b/pre-start FFC 2026-09-10 02:01:26 +08:00
ZXCLI 1a2fc7abb8 android: clear usbfs endpoint-halt after every timed-out transfer (stream killer); drop async reader, add START re-kick 2026-09-10 01:38:25 +08:00
ZXCLI 0f2aa11196 android: USB hardening from device logs - async stream reader, CLEAR_HALT recovery, session mutex, detach handling, fast init 2026-09-10 01:21:29 +08:00
ZXCLI dbb4f24608 android: fix startup crash (MediaStore rejects text/plain in DCIM); log sink fallback DCIM->Download->app-private, logging never throws 2026-09-10 00:48:56 +08:00
ZXCLI ee317c7b25 android: fix black screen (missing FFC(0)x2+START pre-stream sequence) + field debug log to DCIM/MAG160C 2026-09-10 00:01:25 +08:00
ZXCLI fe8595f747 android: remove demo synthetic thermal image (real-device display test); rebuild toolchain+APK on this machine 2026-09-09 19:23:19 +08:00
ZXCLI ca7e29a647 chore: commit working tree (tools/SDK/csdk/analysis churn) for user to review & prune 2026-09-09 17:51:46 +08:00
ZXCLI 456285c71b docs: refresh handoff for next model - final layout conventions + next-round prompt 2026-09-09 17:42:41 +08:00
ZXCLI add3d06713 android: include safe-drawing top inset in uiTopPx (image top clipped on punch-hole phones) + smaller shutter row 2026-09-07 20:16:39 +08:00
ZXCLI 7437ce5e85 android: camera-style controls - 4-item top bar (ffc/zoom/trace/palette), shutter row (gallery/photo/record) above nav 2026-09-07 19:22:49 +08:00
ZXCLI fabea4c664 android: fix inverted device-orientation mapping (real accelerometer reads world-up; emulator uses gravity convention) 2026-09-07 19:09:57 +08:00
ZXCLI b1a3c38a69 android: rotate bar icons/text + OSD labels by physical grip angle (accelerometer), image region stays glued 2026-09-07 13:37:34 +08:00
ZXCLI d0b133a895 android: lock portrait - UI composition glued to phone frame (top/bottom bars + image area absolutely fixed) 2026-09-07 13:17:19 +08:00
ZXCLI 2c38537303 android: 顶栏/底栏位置固定+图像区域永不旋转(只转文字)+挖孔屏安全区适配+完整交接文档 2026-09-07 12:59:26 +08:00
ZXCLI 798e180683 android: 顶栏控制项+图像世界固定旋转+状态栏沉浸+inset感知渲染+乱码二次修复 2026-09-07 12:43:35 +08:00
ZXCLI 96b92a8d26 android: 心跳更新(第二轮UI反馈修复) 2026-09-07 00:30:09 +08:00
ZXCLI 8d22969f6d android: 竖屏旋转热像图填满屏宽/横屏原生方向+控制栏横屏移至右侧+大号圆圈标记与色标+fullSensor方向跟随 2026-09-07 00:29:16 +08:00
ZXCLI 17fcb7b94c android: 心跳更新(用户反馈修复完成) 2026-09-06 23:15:57 +08:00
ZXCLI f8eab28857 android: 相册页接线+演示模式(真实管线渲染合成帧+demo温度显示映射) 2026-09-06 23:15:05 +08:00
ZXCLI 430bf373e4 android: 修复源码GBK编码导致的中文乱码+重绘图标+USB插入自动授权+媒体权限+多点测温/色标条/追踪开关 2026-09-06 21:56:34 +08:00
ZXCLI 6d0805af90 android: 恢复脚本+APK产物 2026-09-06 20:41:41 +08:00
ZXCLI 4fd2e5bf79 android: 图标+预测性返回+心跳更新 阶段7预验收 2026-09-06 20:41:16 +08:00
ZXCLI a07a49b437 android: 设置页+任务巡检解析器(sqlite/xml) 阶段6a 2026-09-06 20:34:21 +08:00
ZXCLI 3bca426c99 android: PDF巡检报告生成(PdfDocument) 阶段5b 2026-09-06 20:04:06 +08:00
ZXCLI 29a9b85cbe android: 媒体库+MDT离线分析查看器(缩放/调色板/备注) 阶段4-5a 2026-09-06 19:55:22 +08:00
ZXCLI ed0a0882d8 android: 拍照MDT容器保存(MediaStore DCIM/MAG160C)+MP4录像(Surface编码) 阶段3b 2026-09-06 19:40:56 +08:00
ZXCLI 6297a06b97 android: USB层+实时画面+UI骨架(阶段3a) 2026-09-06 19:16:35 +08:00
ZXCLI 5b41a6aa9b android: 统一APP工程骨架 + 官方渲染管线Kotlin移植(与C参考逐字节一致) 2026-09-06 18:44:02 +08:00
ZXCLI dd52469377 归档分析脚本到 analysis/reverse_tools(compare_live/verify_nuc/verify_full/parse_ddt/parse_pcap),更新 session_state 引用路径 2026-08-19 15:55:21 +08:00
ZXCLI 0d83c625d1 清理仓库:删除验证期中间产物(约 225MB)
- 删除 analysis/ 下 pairs_*(采集对比数据)、reverse-cache*、captures
  (pcap)、usbnoise、ourdump、legacy-cpp 等逆向验证中间产物
- 保留:analysis/ida(.i64 逆向数据库+二进制)、disasm、history、
  reverse_tools、关键文档(reverse_20260813_full/session_state/protocol)
- 保留:IR_Camera_SDK-1.0.1(官方 SDK 参考)、app/(官方安装包)、
  vendor-docs/(官方手册)、.tools/(capstone/pyelftools)
2026-08-19 15:50:35 +08:00
ZXCLI 0dcb2830c6 修复调色板 BGR/RGB 顺序:render 输出标准 RGB24(官方 palette 为 BGR 存储)
- mag160c_render_frame 之前把 palette[gv][0]=B 直接写入 out_rgb[0],
  导致 R/B 互换,高温区域显示为蓝色
- 改为 out_rgb = {palette[2], palette[1], palette[0]} 标准 RGB24
- linux_demo 的 BMP 输出相应改为写回 BGR(BMP 原生顺序)
- 实测:最热像素 (255,255,150) 亮黄白,低温区域蓝黑,色温方向正确
2026-08-19 15:46:21 +08:00
ZXCLI 70bde0fa86 更新 session_state.md 心跳:阶段 7 完成(LiThermal 评估 + render 抽象层 + demo3 迁移) 2026-08-19 15:42:07 +08:00
ZXCLI 9db940a615 demo3 迁移到 mag160c_render 抽象库(v6);render 库加手动 FFC/NUC 访问器/钩子
- mag160c_demo3.c 从内联管线重写为 render API 调用:删除 DDT 加载/NUC/
  LUT/gray/2x/FFC 状态机实现(~700 行),保留 USB 取帧/GDI/探针/附加环节
- 附加环节(0x230 帧差/0x41fe8 隔行平滑/0x30 gray 覆盖)经 render 的
  post_nuc/post_gray 钩子挂接,编译开关与运行时按键 0/8/9 不变
- mag160c_render 新增 mag160c_render_request_ffc / get_nuc 访问器
- 实测无回归:FFC 时序 init@20→FFC(1)@28→漂移触发,与 v5 一致;
  stats 正常,auto BMP 生成正常;全开关版本编译通过
- linux_demo 修帧组装(38400px+28B 尾),快门=31492 sel=2 正常
2026-08-19 15:41:34 +08:00
ZXCLI 5c22bfe78c 新增平台无关渲染模块 mag160c_render + Linux 参考程序 + 移植评估文档
- csdk/include/mag160c/mag160c_render.h + csdk/src/mag160c_render.c:
  官方渲染管线(快门提取/FFC 状态机/ref 采集/表重建/NUC/盲元/窗口/
  LUT(0x8c)/gray/2x/调色板)封装为无平台依赖 API,FFC 命令经回调发出
- csdk/tools/mag160c_linux_demo.c:Linux 风格参考程序(libusb + render
  API + 显示后端抽象 fbdev/DRM/LVGL),Windows 可编译验证(BMP 输出)
- 修复帧组装:第二段 bulk 读 38428B(像素+尾),尾部含快门字
  实测 shutter=31492 sel=2 stats 正常
- docs/linux_port_plan.md:芯片选型(推荐 T113-S3 QFP128 非 BGA ~20元)、
  动画库(LVGL+rlottie 参考 LiThermal)、显示方案、接口抽象设计
- csdk/README 增加 Linux 移植章节
2026-08-14 17:08:38 +08:00
ZXCLI df6c6dd444 移植官方附加环节全部路径:帧差/隔行平滑/gray覆盖/Nx升采样(编译开关+按键测试)
- OFF_IMPL_230: 帧差调整 0x175a0 逐行移植(0x278 官方 NULL 时 no-op)
- OFF_IMPL_41FE8: 隔行平滑 0x184f0(2x)/0x18660(4x) 逐行翻译(4相位加权插值)
- OFF_IMPL_30: gray 覆盖路径——0x17c40 manual 逐行翻译(8x8窗口4x4采样
  对比度增强累积→gray);0x17770 auto 双边滤波结构移植(官方空间权重表
  在 .data 被裁剪无法提取,用标准高斯核近似,文档注明)
- OFF_IMPL_UPSCALE: Nx 升采样 4x/8x(2x 已验证模式推广,线性插值+边缘外推)
- 运行时按键切换:0=41fe8 模式、8=0x230、9=0x30 模式
- 验证:默认版无回归;全开版运行正常,41fe8=1 平滑生效(nuc std 250→95)
- 文档:csdk/README 开关说明、csdk/README 验证状态更新
2026-08-14 16:52:52 +08:00
ZXCLI 51c8ba9174 完整审计:0x18000ca30 调用链实现对照表 + 全链路复验
- 审计 0xca30 全链:核心渲染(统计/窗口/LUT/gray/2x/FFC/ref/表重建)全部等价实现
- 补帧首标志清零(0x4203c/40/44/04)
- 对照表写入 reverse_20260813_full.md §8.1
- 复验(pairs_ctrl/pairs_final2 新数据):
  - NUC+blind 0/19200、窗口一致、gray+2x 0/76800(离线)
  - LUT 形态同构([500]=41 vs 40, [900]=242 vs 242)
  - 实机同场景 gray 直方图相关性 0.966
  - 零像素两边均为 0
- 未移植主体(官方当前全关,等价):0x230 帧差、0x41fe8 隔行平滑、
  0x30 gray 覆盖路径、多比例升采样
2026-08-14 16:41:50 +08:00
ZXCLI fe91a82762 原汁原味补全官方管线细节:0x8c CDF 中心、temporal 移植、uniform 分支、状态标志
- 抓取官方会话控制字段确认实际启用路径:0x109c0→0x11ee0→0x19740;
  temporal(0x41fe0=1)/帧差(0x230=0)/隔行平滑(0x41fe8=0)/0x30=0 全关
- 实现 0x8c=1 分支:CDF 中心=max(均值bin, 累计75%bin),lres 545→631
  (官方 633,跨帧差 2 bin);用官方 cdf 离线验证 LUT 差异降至 123/1024
  (集中在灰度过渡区,跨帧异步所致)
- 移植 temporal filter(0x18001e920,0x41fe0>1 启用,默认关=官方)
- 补 uniform 帧分支(0x109c0:fmin==fmax 时 LUT/gray 置中灰)
- 补官方标志维护(0x4203c/0x42038/0x42004)
- 实机确认:ref=15502(type1)、d=-2390、win/stats 正常
2026-08-14 16:33:13 +08:00
ZXCLI 004f28910f demo3 补官方启动强制 FFC(帧 75),FFC 频率与官方完全对齐
- 官方 0x180009ee0 状态机:mode 0/3/5/6 在帧 75 无条件 FFC(0);
  mode 1/2/4 在帧 117 强制(最多 5 次)。demo3 此前只实现周期 1800 +
  快门漂移 250 两个条件
- trace 实测:官方启动升温阶段 FFC(0) 间隔 59~394 帧(4~26s 咔哒声密集),
  热稳定后 1800 帧(120s);demo3 已一致
- 验证:idx=75 强制 FFC 触发(demo3_ffc.txt forced75)
2026-08-14 16:23:37 +08:00
ZXCLI 03ed936a5f 修复 demo3 颗粒感:显示层改用官方同款 320x240 2x 平滑输出
- 根因:demo3 之前渲染 160x120 原始灰度并最近邻放大,官方输出是
  2x 升采样后的 320x240(2-tap 平滑本身抑噪)
- 已排除:0x230(FUN_1800175a0 帧差调整)、0x41fe8 隔行平滑(=0)、
  temporal filter 0x41fe0(=1) 均未启用,f20 mode=1 直通
- 修复:render_bmp 改用 g_gray320 上色,BMP/窗口改 320x240,
  StretchBlt 加 HALFTONE,BMP 头支持 >255 尺寸
- 验证:同场景与官方 pair_000.rgb 直方图相关性 0.9796,颜色数 227=官方
2026-08-14 16:16:10 +08:00
ZXCLI 0bfb926892 完成官方管线全量逆向与 demo3 v5 复刻,清理仓库
- 逆向:Ghidra/IDA 全量反编译 CoreSDKLib.dll/ThermalSDK.dll/libthermalSDK.so/
  libcoresdk.so(ARM64)/libmagcore.so,导出 analysis/ida/export/
- 解码官方渲染管线:DDT 校准表加载->快门端点选择->Q12 插值->ref(4x type1 帧
  均值)->NUC 查表->盲元补偿->窗口->LUT1024 重建->2x 升采样->调色板
- 逐像素验证:NUC+盲元 0/19200、插值 0 误差、2x 0/76800、窗口一致
- demo3 v5:完整复刻官方管线(含 DDT 解析、FFC 状态机、快门温度驱动),
  修复 load_ddt 表错位导致的零像素问题
- 鬼影根因分析写入 analysis/reverse_20260813_full.md
- 心跳/恢复机制:analysis/session_state.md + tools/resume_rev.ps1
- 新增 tsdk_pair3 增强采集工具;历史工具归档 csdk/tools/legacy/;
  根目录抓帧残留删除,历史文档归档 analysis/history/
- csdk/README.md 完整使用文档;.gitignore/.gitattributes 补 LFS 规则
2026-08-13 23:16:12 +08:00
2268 changed files with 954461 additions and 134250 deletions
+5
View File
@@ -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
View File
@@ -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
+65 -25
View File
@@ -1,38 +1,78 @@
# MAG160C 热像仪逆向工程
# MAG160C 热像仪统一工程
本仓库记录 MAG160C 热像仪的 USB 协议、官方 Windows/Android SDK 逆向结果、纯 C SDK、官方渲染管线分析、抓帧数据和 demo3 实机调试过程。
MAG160C 热像仪160×12015fpsUSB 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 + ComposeGradle 工程,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
- USBVID `0x833C` PID `0x0001`;端点 0x03 命令 / 0x82 响应 / 0x81 图像流 /
0x84 标定文件 / 0x05 标定上传
- 协议细节:`analysis/magcx_official_flow.md`(权威)
## 里程碑
- 2026-08:官方 PC SDKlibmagcore/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、反编译产物、二进制样本和抓帧数据。部分内容可能受原厂许可
限制,仅用于本项目的互操作性研究和设备调试。
+55
View File
@@ -0,0 +1,55 @@
# analysis/ — MAG160C 逆向工程总目录
> 本目录汇总了 MAG160C 相关的**全部逆向工程材料**,覆盖三条产品线:
> ① Linux SDKlibmagcore.so)② Windows SDKCoreSDKLib.dll / ThermalSDK.dll
> ③ 官方安卓 AppMAG-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/CoreSDKLib2026-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 SDKlibmagcore.so.2.1.1x86-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 SDKCoreSDKLib.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.so1290 函数;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.
+115
View File
@@ -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")
+182
View File
@@ -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 澶嶅埗銆佽〃/鐩插厓/蹇棬/绐楀彛鎶撳彇锛?
+85
View File
@@ -0,0 +1,85 @@
# MAG160C 会话状态与恢复点(2026-08-13
> 本文件是"心跳"锚点:每个里程碑更新一次。若会话中断,新会话先读本文件 +
> `analysis/reverse_20260813_full.md`,然后运行 `tools/resume_rev.ps1` 继续。
## 当前阶段
**阶段 7/7LiThermal 对接评估 + 抽象层完成(2026-08-19**
## 已完成的里程碑
- [x] 工具链:Ghidra 12.1.2headless+ 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 帧 ≈ 120sdemo3 已一致)
- [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-S3QFP128 非
BGA ~20 元)、动画库 LVGL+rlottieLiThermal 同款)、整机成本估算
- [x] 仓库整理 + csdk/README.md 完整文档 + 本地 commit 0bfb926…9db940a7 个)
- [ ] 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 v6render 库版)
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.
Binary file not shown.
Binary file not shown.
+51
View File
@@ -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 个关键函数反汇编 + xrefsNUC 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.6MB7117 函数):ARM64 全量 C 伪代码,含 JNI`Java_cn_com_magnity_coresdk_MagDevice_*`)和温度函数(`ConvertInnerTemp2mC``GetTemperatureProbe``GetLineTemperatureInfo` 等)。
- `all_functions_decomp_libmagcore.so_00100000.txt`937KB841 函数)。
## 使用方式
### 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 0x180000000ARM64 .so 0x100000。
Binary file not shown.
Binary file not shown.
+85
View File
@@ -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);
}
+110
View File
@@ -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");
}
+50
View File
@@ -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");
}
+111
View File
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-91
View File
@@ -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.
-340
View File
@@ -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);
}
-17
View File
@@ -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
-19
View File
@@ -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
-185
View File
@@ -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
-41
View File
@@ -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
-51
View File
@@ -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
-18
View File
@@ -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
-107
View File
@@ -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
-57
View File
@@ -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
-101
View File
@@ -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;
}
-232
View File
@@ -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;
}
+103
View File
@@ -0,0 +1,103 @@
# MAG160C 官方 MAG-Cx 完整逆向结论(2026-09-10jadx + 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.so1290 函数)
> - libcoresdk(arm64) 反编译已在 `analysis/sdk_re/android_app/`(专业版/网络路径)
> - 本文结论全部来自以上反编译源码,与真机日志交叉验证(vivo V2509A, Android 16)。
## 1. USB 协议权威版(以官方 Java UsbCommunication.java 为准)
### 1.1 端点与超时
| 端点 | 方向 | 用途 | 超时 |
|---|---|---|---|
| 0x03 | OUT | 命令(4B 小端 magicFFC/激光/帧率带参数) | 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 BasePara160B |
| 0x6BB6B66C | GetParameter2 | → 0x5BB5B55C BasePara260B |
| 0x6BB6B66D | SetParameter1 | 带 ack64B 读) |
| 0x6BB6B66E | SetParameter2 | 带 ack |
| 0x6BB6B66F | GetCaliInfo | → 0x5BB5B55E {i32 size, i32 reserved, i64 date} |
| 0x6BB6B670 | GetCaliFile | ack 0x5BB5B55D,随后相机在 0x84 推标定文件 |
| 0x6BB6B671 | SendCaliFile | 上传方向(配 0x05 OUT |
| 0x6BB6B672 | SetShutterState | FFC8B {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 BasePara10x5BB5B55B payload14×i32 = 56B
int[0] serialNumberint[1] hwVersion(低24位)|devType(高8位)
int[2] swVersionint[4] fpaWidthint[5] fpaHeightint[6] fps
int[8] fpaGainint[9] fpaFlipint[10] interFrameint[11] interLine。
devType0=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()
└─ 未命中:启动 ThreadCaliRecv0x84 连续读,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 在尾 +8devType0/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_HALTIrSession.diagnoseEndpoint);
- 会话互斥(同一时刻仅一个 IrSession 持有相机)+ DETACHED 释放 + 去抖。
## 3. 工具与环境(2026-09-10 本机)
- jadx 1.5.1C:\Tools\jadx-1.5.1(反编译产物 C:\Tools\jadx-out,已拷贝入库
analysis/sdk_re/android_app/jadx_magcx/
- Ghidra 11.3.2C:\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 <输出文件>`
Binary file not shown.
Binary file not shown.
Binary file not shown.
-3
View File
@@ -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