- 逆向: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 规则
218 lines
18 KiB
Markdown
218 lines
18 KiB
Markdown
# MAG160C Reverse Engineering Findings
|
|
|
|
## Initial Inventory
|
|
|
|
- Workspace root: C:/Project/MAG160C
|
|
- Top-level materials:
|
|
- app/ contains Windows and Android application packages.
|
|
- IR_Camera_SDK-1.0.1/ contains Windows and Android SDK files.
|
|
- 【模块文档】MAG160CORE.pdf is likely module hardware documentation.
|
|
- Windows SDK includes C# WPF example source, C headers/libs/DLLs, OpenCV DLLs, and libusb-win32 driver tools.
|
|
- Android SDK includes demo source, AARs, native libcoresdk.so, OpenCV, and a libuvc/libusb source tree.
|
|
|
|
## Confirmed Hardware / API Facts
|
|
|
|
- IR thermal camera USB identity from Hardware Guide: Vendor ID 0x833C, Product ID 0x0001.
|
|
- Android ThermalController checks Mag IR devices by vendor ID 33596 decimal, which equals 0x833C.
|
|
- Android ThermalController checks TCM/control-board devices by vendor ID 1027 decimal, which equals FTDI 0x0403.
|
|
- SDK Guide states the kit contains:
|
|
- IR camera.
|
|
- RGB camera.
|
|
- TCM control board for motor rotation and proximity sensor.
|
|
- SDK Guide states RGB camera APIs are demo-level third-party integrations; ISVs are encouraged to use their own RGB camera and face tracking APIs.
|
|
- Therefore Linux core support should prioritize IR camera + TCM, with RGB/UVC as optional.
|
|
|
|
## Windows SDK Evidence
|
|
|
|
- CameraSDK.h exposes only:
|
|
- OpenCamera(num, width, height)
|
|
- CloseCamera()
|
|
- SetNewUVCFrame(callback)
|
|
- ThermalSDK.h exposes:
|
|
- lifecycle: GetSDKVersion, Start, Stop, IsWorking
|
|
- IR callback: SetNewIRFrameDelegate
|
|
- temperature reads: ReadTemperatureAtPoint, ReadTemperatureInRect
|
|
- configuration: SetTempBoundary, SetTempOffset, SetUnitMode, SetEmissivity
|
|
- hardware controls/status: Rotate, Trigger, GetInnerIRCameraTemp, GetTCMVersion, firmware update APIs
|
|
- Windows demo opens RGB/UVC as 640x480 and handles thermal IR frames as width x height x 3 RGB data.
|
|
- Windows binary linkage:
|
|
- ThermalSDK.dll is a higher-level wrapper that imports CoreSDKLib.dll MAG_* functions such as MAG_Initialize, MAG_EnumCameras, MAG_NewChannel, MAG_LinkCamera, MAG_StartProcessImage, MAG_GetTemperatureProbe, MAG_GetRectTemperatureInfo, MAG_TriggerFFC, and MAG_DisLinkCamera.
|
|
- CoreSDKLib.dll exports the MAG_* C ABI directly and imports libusb0.dll functions including usb_init, usb_find_busses, usb_find_devices, usb_get_busses, usb_open, usb_set_configuration, usb_claim_interface, usb_bulk_write, usb_bulk_read, usb_clear_halt, usb_release_interface, and usb_close.
|
|
- CoreSDKLib.dll strings include the diagnostic format "usb error: usb_bulk_read=%d, %s", confirming the Windows IR camera path is also bulk-transfer based, via libusb-win32/libusb0.
|
|
- ThermalSDK.dll contains TCM firmware update strings such as beginUpdate/completeUpdate timeout and logs "Main:%.2X Sub:%.2X Frame:%d", matching the Android TCM framed command model.
|
|
|
|
## Android SDK Evidence
|
|
|
|
- Public ThermalController API mirrors Windows ThermalSDK.
|
|
- ThermalConst values:
|
|
- units: metric=0, US=1
|
|
- invalid temp=-1.0e8
|
|
- default fever=37.0, low=32.0, high=40.0
|
|
- judgements: illegal=-2, beyond=-1, normal=0, fever=1
|
|
- ThermalController internal constants and allocations show IR_WIDTH=160, IR_HEIGHT=120, allTempData length=19200.
|
|
- Android MagDevice exposes a native API around:
|
|
- link/unlink camera
|
|
- start/stop process image
|
|
- get output image/RGBA/YUV/raw data
|
|
- get temperature data/probe/rect/line/ellipse/region info
|
|
- get/set correction parameters
|
|
- trigger FFC
|
|
- save/load DDT/MDT
|
|
- libcoresdk.so exports many MAG_* C symbols and uses libusb_* APIs, including libusb_bulk_transfer, so the IR camera is vendor-protocol USB rather than plain UVC.
|
|
- Android libcoresdk.so additionally exports/contains MAG_GetTemperatureData_Raw, MAG_GetOutputRawData, MAG_GetFilteredRaw, MAG_TransferPulseImage, MAG_StartProcessPulseImage, and low-level CFunctions conversion symbols such as Raw2Temperature, ConvertResponse2Temperature, ReviseTemperature, ConvertInnerTemp2mC, and ConvertmC2InnerTemp. This suggests the device returns raw/response words that the proprietary SDK converts to millidegree-C-like integer units.
|
|
- TCM Java bytecode shows:
|
|
- motor rotate command main=2 sub=119, payload [direction, magnitude], direction 0=positive and 1=negative, magnitude capped to 128.
|
|
- proximity command main=2 sub=120 for distance, sub=121 for gesture.
|
|
- firmware update commands main=2, get-version=4, begin=5, stop=6, transmit=7.
|
|
- FTDI/TCM command framing from `com.elotouch.ftdi.FTDICommand`:
|
|
- header protocol byte is `0x7e`.
|
|
- header length is 4 bytes: `[0]=0x7e`, `[1..2]=big-endian command-body length`, `[3]=checksum over bytes 0..2`.
|
|
- command body length is `payload_length + 5`.
|
|
- body indexes relative to whole packet: main command at 4, sub command at 5, frame/index at 6..7 big-endian, payload starts at 8, final byte is checksum over body bytes from index 4 through the byte before final checksum.
|
|
- The receive parser validates the header byte/checksum, then validates the body checksum, then dispatches by main/sub and frame id.
|
|
- Android TCM bulk communication class searches USB interfaces for any interface containing both a bulk OUT endpoint and a bulk IN endpoint, claims it, then starts read/write threads. This supports implementing TCM over generic Linux libusb bulk endpoints in addition to/without Android D2XX.
|
|
- Additional TCM command table recovered from Java bytecode:
|
|
- All observed TCM main commands use main 0x02.
|
|
- motor rotate: sub 0x77, payload [direction, magnitude], with signed input clamped to [-128, 128].
|
|
- proximity: sub 0x78 reports distance; sub 0x79 reports gesture/event byte.
|
|
- light: sub 0x31 steady, 0x32 blink, 0x33 breath; payload is 4 bytes [on, red, green, blue] where 0xff means channel enabled at full intensity in the demo.
|
|
- power manager: sub 0x41, payload [value2, value1] as passed to updateDevicePower(byte, byte).
|
|
- audio: sub 0x50 beep volume, sub 0x51 USB audio volume; payload is one byte clamped to 0..16.
|
|
- schedule task: sub 0x21 set time, 0x22 get time, 0x23 add action, 0x24 remove action, 0x25 get all actions, 0x26 remove all actions.
|
|
- firmware update: sub 0x04 get version, 0x05 begin, 0x06 complete/stop, 0x07 transmit; flash status notifications include sub 0x0a, 0x0b, 0x0c.
|
|
- TCM command frame id is stored as a hex string preference named index under Android shared preferences HEMATESTAPP; default is 0x0001, increments after each response, and wraps to 1 when it reaches 0x8000.
|
|
- Android MagDevice JNI public shape:
|
|
- MagUsb opens Android USB by device id and passes UsbDeviceConnection.getFileDescriptor() into native LinkCamera(fd).
|
|
- Almost all native methods take the native channel handle as their first parameter.
|
|
- CameraInfo exposes fpaWidth, fpaHeight, name, type, maxFps, curFps, bmpWidth, bmpHeight.
|
|
- Correction/temperature APIs use integer temperature units in the native layer; Java wrappers return int temperature values for probes/regions and arrays.
|
|
|
|
## Tooling Attempts
|
|
|
|
- Existing usable tools:
|
|
- C:/mingw64/bin/strings.exe
|
|
- C:/mingw64/bin/readelf.exe
|
|
- C:/mingw64/bin/objdump.exe for PE/exports, but not ARM64 ELF disassembly
|
|
- C:/Program Files/Zulu/zulu-21/bin/javap.exe
|
|
- Python 3.10 with pdfplumber
|
|
- Attempted installation:
|
|
- Tried to install capstone and pyelftools into C:/Project/MAG160C/.tools/python-revlibs.
|
|
- Both sandboxed and escalated pip attempts failed because the network/proxy was unavailable.
|
|
- No new reverse-engineering software was successfully installed.
|
|
- Generated analysis cache:
|
|
- C:/Project/MAG160C/analysis/reverse-cache/thermallib_aar contains extracted thermallib-release.aar contents for inspection.
|
|
|
|
## Linux SDK Evidence
|
|
|
|
- A Linux SDK has been added under `IR_Camera_SDK-1.0.1/linux` with versioned packages for 1.0.0, 1.0.1, 1.2.1, and 1.2.2.
|
|
- Version 1.2.2 is already extracted and contains a Qt demo, Linux headers, firmware blob, and Linux shared libraries:
|
|
- `libthermalSDK.so`
|
|
- `libcameraSDK.so`
|
|
- `libmagcore.so.2.1.1`
|
|
- `libfaceTrackingSDK.so`
|
|
- Early inventory confirms Linux headers and demo sources exist in `IR_Camera_SDK-1.0.1/linux/1.2.2/1.2.2/EloThermal/qtDemo`.
|
|
- Need to inspect Linux docs, headers, demo calls, and ELF symbols before deciding whether the private IR command/streaming gap can be narrowed from this SDK.
|
|
- Linux SDK 1.2.2 `ThermalSDK.h` largely mirrors the Windows high-level API and adds/keeps `IsTCMOpen`, `IsIROpen`, distance/error callbacks, limit events, `LimitReset`, `SoftResetTCM`, preset calibration storage, and `GetDefaultCalibration`.
|
|
- Linux SDK `CameraSDK.h` exposes RGB/UVC `OpenCamera`, `CloseCamera`, `SetNewUVCFrame`, and `SetCameraLightParameter`; RGB remains separate from the IR vendor-bulk path.
|
|
- Linux `Def.h` confirms the same IR dimensions and constants: `IR_WIDTH=160`, `IR_HEIGHT=120`, metric/US units, invalid temp `-1e8`, and fever/boundary defaults.
|
|
- Linux Qt `.pro` links `libthermalSDK`, `libmagcore`, `libcameraSDK`, `libfaceTrackingSDK`, OpenMP, and OpenCV; `libmagcore` is therefore the IR MAG/libusb layer.
|
|
- Linux `libmagcore.so.2.1.1` is x86-64 ELF and exports the same useful `MAG_*` API surface seen on Windows/Android, plus bundled `libusb_*` symbols including `libusb_bulk_transfer`, `libusb_control_transfer`, and descriptor/open/claim functions.
|
|
- Linux `libthermalSDK.so` exported/demangled symbols show TCM-side classes such as `TCMVirtualDevice`, `ThermalTCMDevice`, `EloMotorDevice`, `EloProximityDevice`, `EloEEROMDevice`, `EloTCMUpdaterDevice`, `EloTCMHeartBeatDevice`, `SerialPort`, and `FTDICommand`.
|
|
- Linux TCM transport appears to use a serial-port abstraction (`SerialPort`, `serial::list_ports`, `TCMDevice::open(std::string)`), which differs from Android's USB bulk communication but should carry the same `FTDICommand` frame model.
|
|
- New TCM high-level features visible in Linux symbols: EEPROM preset read/write, limit reset/event, heartbeat/failure notification, firmware update, soft reset, proximity, motor rotate.
|
|
- Linux Quick Start says to grant both generic USB device permissions and `ttyUSB*` permissions, matching the split IR-via-libusb plus TCM-via-serial model.
|
|
- Linux SDK Guide documents only high-level API behavior; it does not publish low-level IR bulk commands or raw frame packet layout.
|
|
- Linux demo startup sequence: `OpenCamera(cameraIndex, 640, 480)` + `SetNewUVCFrame`, then `Start()` + `SetNewIRFrameDelegate`, `SetNewDistanceDelegate`, `SetDistanceError`, `SetLimitEventListener`, and `SetUpdateCallback`.
|
|
- Linux demo discovers RGB camera index by picking the first `/dev/video*` entry from `ls -al /dev/video*`; this should not be copied as-is, but confirms RGB is treated as ordinary V4L/OpenCV camera input.
|
|
- Linux IR frame callback receives RGB888 rendered IR frames (`channel == 3`), then high-level calls like `ReadTemperatureAtPoint(80,40, ...)` or `ReadTemperatureInRect(...)` fetch calculated temperatures from SDK state.
|
|
- Linux demo uses `GetDefaultCalibration` or stored TCM preset offsets, then applies them in RGB/IR overlay conversion; calibration is for aligning RGB face boxes to 160x120 IR coordinates.
|
|
- Linux demo exposes safe user actions for TCM: rotate, trigger FFC, limit reset, firmware update, preset write, and soft reset.
|
|
- Linux `MAG_LinkCamera` disassembly confirms the default MAG camera link path hard-codes VID `0x833c` before calling `MAG_LinkCameraEx`; no new public PID constant was exposed there, so hardware identity remains `0x833c/0x0001` from the hardware guide until a live descriptor says otherwise.
|
|
- Linux `libmagcore` bulk-transfer call sites reveal default IR endpoints used internally: OUT endpoint `0x03`, IN endpoint `0x82` for command/response-style reads up to `0x1000` bytes, IN endpoint `0x84` for large reads up to `0x80000` bytes with `0xea60` ms timeout, and IN endpoint `0x81` for another small/status stream with `0x1f4` ms timeout.
|
|
- Linux frame-stream parsing searches for 32-bit markers `0x1bb1b11b` and `0x1bb1b11c` in data read from endpoint `0x81`; this is a useful raw stream framing clue but not yet enough to implement decoded temperature/image parity.
|
|
- Linux `EloMotorDevice::rotate(int)` confirms TCM rotate command main `0x02`, sub `0x77`, payload `[direction, magnitude]`. Linux clamps public rotate input to `[-250,250]`; Android bytecode previously showed `[-128,128]`, so our first-release dry-run helper should either document the conservative Android clamp or be adjusted deliberately after tests.
|
|
- Linux TCM send path constructs an `FTDICommand`, calls `FTDICommand::getCommand()`, then writes the resulting bytes through an abstract data communication object; this independently validates our TCM frame-builder architecture.
|
|
|
|
## Working Hypotheses
|
|
|
|
- The RGB camera likely exposes USB Video Class and can be handled by Linux V4L2/libuvc.
|
|
- The IR camera uses a vendor-specific libusb protocol on VID 0x833C/PID 0x0001.
|
|
- A useful first Linux deliverable can be structured as a libusb-based skeleton with descriptor probing, endpoint discovery, TCM command framing, public SDK-compatible API, and raw-frame capture hooks.
|
|
- Full temperature parity with the proprietary SDK requires either ARM64 disassembly of libcoresdk.so or live USB traffic capture, because the vendor bulk command protocol and raw frame packet format are not fully exposed in headers/docs.
|
|
|
|
## Remaining Unknowns
|
|
|
|
- Exact libusb bulk endpoints and command packets for MAG_LinkCamera / StartProcessImage / TriggerFFC / temperature frame transfer.
|
|
- Exact raw IR frame packet format before libcoresdk converts it to 160x120 temperatures and rendered RGB bitmap.
|
|
- Whether a camera is physically connected and accessible for descriptor/traffic capture.
|
|
|
|
## 2026-08-10 Deep-Disassembly Session (capstone/pyelftools)
|
|
|
|
All previously "unknown" protocol facts were recovered by disassembling the x86-64
|
|
`libmagcore.so.2.1.1` and the ARM64 `libcoresdk.so` (which exports the internal
|
|
`CFunctions`/`CAccumulator`/`CMagDevice` classes). Full spec in
|
|
`analysis/protocol_spec.md`; key facts:
|
|
|
|
- Link: VID 0x833C, set_configuration(2), claim_interface(0), auto_detach on.
|
|
- Command channel: EP OUT 0x03 + EP IN 0x82 (0x1000, 2000 ms); 8-byte packets
|
|
`{u32 magic, u32 param}`; 0x3c-byte packets for DDT.
|
|
- Command magic table: 66b/66c prepare, 66d/66e DDT rw, 66f camera info,
|
|
670 version, 672 FFC, 673 start, 674 stop, 676/677 set params.
|
|
- Response magic table: 5bb5b55b (info, 0x38 B → pid/width@0x10/height@0x14),
|
|
55c/55d/55f (0x38 B blocks), 55e (16 B two qwords), 57b (file header).
|
|
- Frame stream EP 0x81: marker 0x1bb1b11b @0, frame counter @4, data length @8,
|
|
type @0xc (0/1), shutter @0x10, pixel data @0x1c (uint16 LE), trailing
|
|
0x1bb1b11c @0x1c+len; total = 0x38+len; width 0xa0 (160) or 0x180 (384).
|
|
- Temperature pipeline (ARM64 symbols): PushFrame → CAccumulator::PushFrame
|
|
(baseline avg) → Raw2Temperature / ConvertResponse2Temperature →
|
|
BlindCompensation. Calibration = per-pixel PWL: diff=(raw-baseline)>>1,
|
|
band search on per-pixel threshold table, v = off[i][p] + (diff*coeff[i][p]>>12),
|
|
clamp [0,0xffff]. ConvertResponse2Temperature: base=(50000-gain)>>shift +
|
|
diff*coeff with auto-gain reduction on >0x101 overflow pixels.
|
|
- Probe reads: GetTemperature = window average (radius 1..3) → ReviseTemperature
|
|
(T2E PWL table, 0x112 entries, sensor-temp/emissivity float mixing, binary
|
|
search inverse map) → CorrectTemperature (offset segmentation k=(v-15000)/100
|
|
clamped 0..6, float gains at +0xc4/+0xcc/+0xd0). T2E table dumped to
|
|
analysis/t2e_table.json (monotonic 1000..737212).
|
|
- DDT saved file format: 0x5bb5b57b header + aligned payload + 5bb5b55b/55c/55d/
|
|
55e/55f typed blocks.
|
|
- Error domain: custom errno 0xE4AE0001..0xE4AE0206.
|
|
- Tools used this session: pip-installed capstone 5.0.7, pyelftools 0.33 into
|
|
.tools/python-revlibs (network was available this time); analysis helper
|
|
analysis/revtools.py (ELF loader, ARM64 PLT resolver, C++-lite demangler,
|
|
xref scan); objdump/readelf/nm/strings from C:/mingw64/bin.
|
|
|
|
## Handoff Summary
|
|
|
|
- The key protocol facts to carry into a new conversation are:
|
|
- IR camera VID/PID: `0x833C/0x0001`.
|
|
- TCM frame format: `0x7e` header + big-endian body length + header checksum + main/sub/frame + payload + body checksum.
|
|
- Observed TCM main command: `0x02`.
|
|
- TCM subcommands already mapped for rotate, proximity, light, audio, power, schedule, and firmware update.
|
|
- Windows and Android evidence agree that the IR camera path is vendor-specific USB over libusb-style bulk transfers, not plain UVC.
|
|
- Linux implementation should keep IR protocol support as a skeleton/probe path until live USB traffic or better disassembly is available.
|
|
- The user already chose the deliverable shape:
|
|
- C/C++ library
|
|
- CLI sample
|
|
- Python-callable wrapper/tooling
|
|
- The workspace now has enough evidence to continue implementation without redoing the reverse-engineering discovery work.
|
|
|
|
## 2026-08-10 Live Hardware Verification - Protocol Corrections
|
|
|
|
### Verified command format (this unit, WinUSB/libusb-win32)
|
|
- 4-byte commands: 0x6bb6b66b/66c/66f (init), 0x6bb6b673 (START),
|
|
0x6bb6b674 (STOP). FFC = 0x6bb6b672 + 4-byte param (8-byte packet).
|
|
- Init order: 66b -> 66c -> 66f, then FFC(0) x2, sleep 300ms, START.
|
|
- Camera info comes from the 0x6bb6b66b response (0x5bb5b55b, 0x38-byte
|
|
payload): pid@+0x00, width=160@+0x10, height=120@+0x14, fps=15@+0x18.
|
|
The 0x5bb5b55c block (from 66c) has a different layout (width@+0x10=5,
|
|
height@+0x14=4 on this unit) - do not parse it as camera info.
|
|
- FFC param toggles frame type: FFC(1) -> type 0, FFC(0) -> type 1.
|
|
The official demo alternates FFC(0)/FFC(1) every ~10 frames during
|
|
streaming; FFC must be sent after a complete frame (header+payload),
|
|
not between header and payload.
|
|
- STOP: clear_halt(0x03), clear_halt(0x82), then 0x6bb6b674.
|
|
- Temperature signal: hand vs background ~1322 counts over most of the FOV
|
|
(~147 counts/C under a 34C-vs-25C assumption). Frame-difference display
|
|
(current - reference) makes the hand clearly visible.
|