Files
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

444 lines
18 KiB
C

/* MAG160C Windows Demo (libusb0 = libusb-win32 backend, same as the official
* EloThermal demo). Uses the exact transfer layer the vendor demo ships
* with, so the type=0 (temperature) stream stays alive with FFC switching.
*
* Build:
* gcc -std=c11 -mwindows mag160c_demo_usb0.c libusb0.a -o mag160c_demo_usb0.exe
* copy libusb0.dll next to the exe.
*/
#define _WIN32_WINNT 0x0601
#include <windows.h>
#include <stdio.h>
#include <stdint.h>
/* ---- libusb-win32 (libusb0) API ---------------------------------------- */
typedef struct usb_bus {
struct usb_bus *next, *prev;
char dirname[512];
struct usb_device *devices;
unsigned long location;
struct usb_device *root_dev;
} usb_bus;
typedef struct usb_device_descriptor {
uint8_t bLength, bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass, bDeviceSubClass, bDeviceProtocol, bMaxPacketSize0;
uint16_t idVendor, idProduct, bcdDevice;
uint8_t iManufacturer, iProduct, iSerialNumber, bNumConfigurations;
} usb_device_descriptor;
typedef struct usb_device {
struct usb_device *next, *prev;
char filename[512];
struct usb_bus *bus;
usb_device_descriptor descriptor;
void *config;
void *dev;
uint8_t devnum;
unsigned char num_children;
struct usb_device **children;
} usb_device;
typedef struct usb_dev_handle usb_dev_handle;
typedef int (*fn_usb_init)(void);
typedef int (*fn_usb_find_busses)(void);
typedef int (*fn_usb_find_devices)(void);
typedef usb_bus *(*fn_usb_get_busses)(void);
typedef usb_dev_handle *(*fn_usb_open)(usb_device *);
typedef int (*fn_usb_set_configuration)(usb_dev_handle *, int);
typedef int (*fn_usb_claim_interface)(usb_dev_handle *, int);
typedef int (*fn_usb_bulk_write)(usb_dev_handle *, int, const char *, int, int);
typedef int (*fn_usb_bulk_read)(usb_dev_handle *, int, char *, int, int);
typedef int (*fn_usb_close)(usb_dev_handle *);
static fn_usb_init p_usb_init;
static fn_usb_find_busses p_usb_find_busses;
static fn_usb_find_devices p_usb_find_devices;
static fn_usb_get_busses p_usb_get_busses;
static fn_usb_open p_usb_open;
static fn_usb_set_configuration p_usb_set_configuration;
static fn_usb_claim_interface p_usb_claim_interface;
static fn_usb_bulk_write p_usb_bulk_write;
static fn_usb_bulk_read p_usb_bulk_read;
static fn_usb_close p_usb_close;
static usb_dev_handle *g_dev;
#define MAG_CMD_PREPARE1 0x6bb6b66b
#define MAG_CMD_PREPARE2 0x6bb6b66c
#define MAG_CMD_GET_INFO 0x6bb6b66f
#define MAG_CMD_FFC 0x6bb6b672
#define MAG_CMD_START 0x6bb6b673
#define MAG_CMD_STOP 0x6bb6b674
#define IR_W 160
#define IR_H 120
#define FRAME_LEN (IR_W * IR_H * 2)
static unsigned char g_frame[FRAME_LEN];
static unsigned char g_bmp[54 + IR_W * IR_H * 3];
static char g_status[512];
static volatile unsigned g_frame_count;
static volatile unsigned g_frame_type;
static int g_diff_mode = 1;
static unsigned short g_reference[IR_W * IR_H];
static int g_has_reference;
static double g_fps;
static int g_probe_x = -1, g_probe_y = -1;
static int g_max_x = -1, g_max_y = -1;
static double g_max_temp = -100.0;
static double g_center_temp = -100.0;
static FILE *g_log;
static int sendcmd(unsigned magic, unsigned param, int len) {
unsigned char cmd[8] = {0};
cmd[0] = (unsigned char)(magic);
cmd[1] = (unsigned char)(magic >> 8);
cmd[2] = (unsigned char)(magic >> 16);
cmd[3] = (unsigned char)(magic >> 24);
if (len >= 8) {
cmd[4] = (unsigned char)(param);
cmd[5] = (unsigned char)(param >> 8);
cmd[6] = (unsigned char)(param >> 16);
cmd[7] = (unsigned char)(param >> 24);
}
int xfer = 0;
if (p_usb_bulk_write(g_dev, 0x03, (char *)cmd, len, 2000) != len) return -1;
unsigned char resp[0x1000];
xfer = p_usb_bulk_read(g_dev, 0x82, (char *)resp, sizeof(resp), 2000);
if (xfer < 4) return -1;
return 0;
}
static void render(const unsigned char *frame) {
int hot = 0;
unsigned hotv = 0;
unsigned center_sum = 0;
unsigned char *px = g_bmp + 54;
for (int y = 0; y < IR_H; ++y) {
for (int x = 0; x < IR_W; ++x) {
int o = (y * IR_W + x) * 2;
unsigned v = (unsigned)frame[o] | ((unsigned)frame[o + 1] << 8);
int diffv = 0;
if (g_diff_mode && g_has_reference)
diffv = (int)v - (int)g_reference[y * IR_W + x];
if (x >= 70 && x < 90 && y >= 50 && y < 70) center_sum += v;
if (v > hotv && v != 0) { hotv = v; hot = y * IR_W + x; }
unsigned char r, g, b;
if (g_diff_mode && g_has_reference) {
int span = 2000;
if (diffv > 0) {
unsigned idx = (unsigned)(diffv * 255 / span);
if (idx > 255) idx = 255;
r = (unsigned char)(128 + idx / 2);
g = (unsigned char)(128 - idx / 2);
b = (unsigned char)(128 - idx / 2);
} else {
unsigned idx = (unsigned)(-diffv * 255 / span);
if (idx > 255) idx = 255;
r = (unsigned char)(128 - idx / 2);
g = (unsigned char)(128 - idx / 2);
b = (unsigned char)(128 + idx / 2);
}
} else {
/* absolute with fixed range (type=0 data ~ 8000..30000) */
unsigned idx = (v - 8000) * 255 / 22000;
if (idx > 255) idx = 255;
if (idx < 64) { r = 0; g = (unsigned char)(idx * 4); b = 255; }
else if (idx < 128) { r = 0; g = 255; b = (unsigned char)(255 - (idx - 64) * 4); }
else if (idx < 192) { r = (unsigned char)((idx - 128) * 4); g = 255; b = 0; }
else { r = 255; g = (unsigned char)(255 - (idx - 192) * 4); b = 0; }
}
int dst = (IR_H - 1 - y) * IR_W * 3 + x * 3;
px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r;
}
}
if (hot >= 0) {
g_max_x = hot % IR_W;
g_max_y = hot / IR_W;
g_max_temp = 25.0 + ((int)hotv - (int)g_reference[hot]) / 147.0;
int mx2 = g_max_x, my2 = IR_H - 1 - g_max_y;
for (int k = -2; k <= 2; ++k) {
if (mx2 + k >= 0 && mx2 + k < IR_W) {
int d2 = my2 * IR_W * 3 + (mx2 + k) * 3;
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
}
if (my2 + k >= 0 && my2 + k < IR_H) {
int d2 = (my2 + k) * IR_W * 3 + mx2 * 3;
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
}
}
}
g_center_temp = 25.0 + ((int)(center_sum / 400) - (int)g_reference[50 * IR_W + 70]) / 147.0;
}
static HWND g_hwnd;
static HFONT g_font;
static void set_status(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(g_status, sizeof(g_status), fmt, ap);
va_end(ap);
InvalidateRect(g_hwnd, NULL, TRUE);
}
static void save_bmp(void) {
char path[MAX_PATH];
SYSTEMTIME st;
GetLocalTime(&st);
snprintf(path, sizeof(path), "thermal_%04d%02d%02d_%02d%02d%02d.bmp",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
FILE *f = fopen(path, "wb");
if (f) { fwrite(g_bmp, 1, sizeof(g_bmp), f); fclose(f); set_status("saved %s", path); }
}
static void do_ffc(void) {
sendcmd(MAG_CMD_FFC, 1, 8);
Sleep(300);
sendcmd(MAG_CMD_FFC, 0, 8);
set_status("FFC triggered");
}
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC dc = BeginPaint(hw, &ps);
HDC mem = CreateCompatibleDC(dc);
HBITMAP bm = CreateCompatibleBitmap(dc, IR_W, IR_H);
HGDIOBJ old = SelectObject(mem, bm);
SetDIBitsToDevice(mem, 0, 0, IR_W, IR_H, 0, 0, 0, IR_H, g_bmp + 54,
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, IR_W, IR_H, SRCCOPY);
SelectObject(mem, old);
DeleteObject(bm);
DeleteDC(mem);
SelectObject(dc, g_font);
SetBkMode(dc, TRANSPARENT);
SetTextColor(dc, RGB(220, 220, 220));
int y = 20;
char line[256];
snprintf(line, sizeof(line), "frame : %u (type %u)", g_frame_count, g_frame_type);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
snprintf(line, sizeof(line), "fps : %.1f", g_fps);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
if (g_probe_x >= 0) {
double t = 25.0 + ((int)(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2] |
(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2 + 1] << 8)) -
(int)g_reference[g_probe_y * IR_W + g_probe_x]) / 147.0;
snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, t);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
}
if (g_max_x >= 0) {
snprintf(line, sizeof(line), "max : (%d,%d) %.2f C", g_max_x, g_max_y, g_max_temp);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
}
snprintf(line, sizeof(line), "center: %.2f C", g_center_temp);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
snprintf(line, sizeof(line), "mode : %s", g_diff_mode ? "DIFF" : "absolute");
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
SetTextColor(dc, RGB(120, 220, 120));
TextOutA(dc, 10, 500, g_status, (int)strlen(g_status));
EndPaint(hw, &ps);
break;
}
case WM_LBUTTONDOWN: {
int x = LOWORD(lp), y = HIWORD(lp);
if (x >= 10 && x < 650 && y >= 10 && y < 490) {
g_probe_x = (x - 10) * IR_W / 640;
g_probe_y = IR_H - 1 - (y - 10) * IR_H / 480;
InvalidateRect(hw, NULL, TRUE);
}
break;
}
case WM_COMMAND:
switch (LOWORD(wp)) {
case 1001: do_ffc(); break;
case 1002: save_bmp(); break;
case 1003: g_diff_mode = !g_diff_mode; break;
case 1004:
for (int i = 0; i < FRAME_LEN; i += 2)
g_reference[i / 2] = (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
g_has_reference = 1;
g_diff_mode = 1;
set_status("reference captured");
break;
case 1005: g_probe_x = -1; InvalidateRect(hw, NULL, TRUE); break;
}
break;
case WM_ERASEBKGND:
return 1;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hw, msg, wp, lp);
}
static int open_camera(void) {
p_usb_init();
p_usb_find_busses();
p_usb_find_devices();
for (usb_bus *bus = p_usb_get_busses(); bus; bus = bus->next) {
for (usb_device *dev = bus->devices; dev; dev = dev->next) {
if (dev->descriptor.idVendor == 0x833c) {
g_dev = p_usb_open(dev);
if (g_dev) {
p_usb_set_configuration(g_dev, 1);
if (p_usb_claim_interface(g_dev, 0) == 0) return 0;
p_usb_close(g_dev);
g_dev = NULL;
}
}
}
}
return -1;
}
static int init_camera(void) {
for (int attempt = 0; attempt < 4; ++attempt) {
if (g_log) { fprintf(g_log, "init attempt %d\n", attempt + 1); fflush(g_log); }
if (open_camera() != 0) { Sleep(2000); continue; }
if (sendcmd(MAG_CMD_PREPARE1, 0, 4)) { Sleep(2000); continue; }
if (sendcmd(MAG_CMD_PREPARE2, 0, 4)) { Sleep(2000); continue; }
if (sendcmd(MAG_CMD_GET_INFO, 0, 4)) { Sleep(2000); continue; }
if (sendcmd(MAG_CMD_FFC, 0, 8)) { Sleep(2000); continue; }
Sleep(100);
if (sendcmd(MAG_CMD_FFC, 0, 8)) { Sleep(2000); continue; }
Sleep(300);
if (sendcmd(MAG_CMD_START, 0, 4)) { Sleep(2000); continue; }
Sleep(700);
if (g_log) { fprintf(g_log, "init ok (attempt %d)\n", attempt + 1); fflush(g_log); }
return 0;
}
return -1;
}
int WINAPI WinMain(HINSTANCE inst, HINSTANCE hprev, LPSTR cmd, int show) {
(void)hprev; (void)cmd; (void)show;
g_log = fopen("C:/Users/ZXC/AppData/Local/Temp/opencode/demo_usb0_log.txt", "w");
if (g_log) { fprintf(g_log, "demo start\n"); fflush(g_log); }
HMODULE l0 = LoadLibraryA("libusb0.dll");
if (!l0) { MessageBoxA(NULL, "libusb0.dll not found", "MAG160C Demo", MB_ICONERROR); return 1; }
p_usb_init = (fn_usb_init)GetProcAddress(l0, "usb_init");
p_usb_find_busses = (fn_usb_find_busses)GetProcAddress(l0, "usb_find_busses");
p_usb_find_devices = (fn_usb_find_devices)GetProcAddress(l0, "usb_find_devices");
p_usb_get_busses = (fn_usb_get_busses)GetProcAddress(l0, "usb_get_busses");
p_usb_open = (fn_usb_open)GetProcAddress(l0, "usb_open");
p_usb_set_configuration = (fn_usb_set_configuration)GetProcAddress(l0, "usb_set_configuration");
p_usb_claim_interface = (fn_usb_claim_interface)GetProcAddress(l0, "usb_claim_interface");
p_usb_bulk_write = (fn_usb_bulk_write)GetProcAddress(l0, "usb_bulk_write");
p_usb_bulk_read = (fn_usb_bulk_read)GetProcAddress(l0, "usb_bulk_read");
p_usb_close = (fn_usb_close)GetProcAddress(l0, "usb_close");
if (init_camera() != 0) {
MessageBoxA(NULL, "camera init failed", "MAG160C Demo", MB_ICONERROR);
return 1;
}
WNDCLASSA wc = {0};
wc.lpfnWndProc = wndproc;
wc.hInstance = inst;
wc.lpszClassName = "Mag160cDemoUsb0";
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
RegisterClassA(&wc);
g_hwnd = CreateWindowA("Mag160cDemoUsb0", "MAG160C Thermal Demo (libusb0)",
WS_OVERLAPPEDWINDOW, 60, 40, 1000, 600,
NULL, NULL, inst, NULL);
g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,
0, 0, CLEARTYPE_QUALITY, 0, "Consolas");
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
670, 160, 120, 32, g_hwnd, (HMENU)1001, inst, NULL);
CreateWindowA("BUTTON", "Save BMP", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
800, 160, 120, 32, g_hwnd, (HMENU)1002, inst, NULL);
CreateWindowA("BUTTON", "Diff mode", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
670, 200, 140, 28, g_hwnd, (HMENU)1003, inst, NULL);
CreateWindowA("BUTTON", "Set reference", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
670, 240, 140, 32, g_hwnd, (HMENU)1004, inst, NULL);
CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
820, 240, 100, 32, g_hwnd, (HMENU)1005, inst, NULL);
unsigned sz = 54 + IR_W * IR_H * 3;
g_bmp[0] = 'B'; g_bmp[1] = 'M';
g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8);
g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24);
g_bmp[10] = 54; g_bmp[14] = 40;
g_bmp[18] = IR_W; g_bmp[19] = 0; g_bmp[22] = IR_H; g_bmp[23] = 0;
g_bmp[26] = 1; g_bmp[28] = 24;
ShowWindow(g_hwnd, SW_SHOW);
set_status("starting...");
unsigned prevcnt = 0xffffffff;
int frames = 0, ffc_done = 0;
unsigned long long ref_sum[IR_W * IR_H] = {0};
int ref_n = 0;
DWORD t0 = GetTickCount();
DWORD last_fps_t = t0;
unsigned last_fps_cnt = 0;
MSG msg;
for (;;) {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) goto done;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
unsigned char hdr[64];
int xfer = p_usb_bulk_read(g_dev, 0x81, (char *)hdr, sizeof(hdr), 100);
if (xfer < 28) continue;
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
if (m != 0x1bb1b11b) continue;
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
if (c == prevcnt) continue;
prevcnt = c;
xfer = p_usb_bulk_read(g_dev, 0x81, (char *)g_frame, sizeof(g_frame), 100);
if (xfer < FRAME_LEN) continue;
frames++;
if (!ffc_done && frames == 3) {
sendcmd(MAG_CMD_FFC, 1, 8); /* switch to type=0 (temperature) */
ffc_done = 1;
Sleep(400);
continue;
}
g_frame_count++;
g_frame_type = (unsigned)hdr[12];
if (!g_has_reference && frames >= 150 && ref_n < 30) {
for (int i = 0; i < FRAME_LEN; i += 2)
ref_sum[i / 2] += (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
ref_n++;
if (ref_n == 30) {
for (int i = 0; i < IR_W * IR_H; ++i) g_reference[i] = (unsigned short)(ref_sum[i] / 30);
g_has_reference = 1;
set_status("reference captured - hand shows red");
}
} else if (g_has_reference) {
for (int i = 0; i < FRAME_LEN; i += 2) {
unsigned v = (unsigned)g_frame[i] | ((unsigned)g_frame[i + 1] << 8);
unsigned r = g_reference[i / 2];
g_reference[i / 2] = (unsigned short)((r * 199 + v) / 200);
}
}
render(g_frame);
DWORD now = GetTickCount();
if (now - last_fps_t >= 1000) {
g_fps = (g_frame_count - last_fps_cnt) * 1000.0 / (now - last_fps_t);
last_fps_t = now;
last_fps_cnt = g_frame_count;
}
InvalidateRect(g_hwnd, NULL, FALSE);
UpdateWindow(g_hwnd);
}
done:
sendcmd(MAG_CMD_STOP, 0, 4);
p_usb_close(g_dev);
return 0;
}