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

249 lines
8.9 KiB
C

/* Real-time thermal viewer: Win32 window showing live 160x120 frames
with pseudo-color. Esc or close window to stop. */
#define _WIN32_WINNT 0x0601
#include <windows.h>
#include <stdio.h>
#include <libusb.h>
static libusb_device_handle *g_h;
static unsigned char g_frame[40000];
static CRITICAL_SECTION g_lock;
static volatile int g_new_frame;
static volatile int g_running;
static unsigned char g_bmp[54 + 160 * 120 * 3];
static char g_title[128];
static int sendcmd(libusb_device_handle *h, 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;
int rc = libusb_bulk_transfer(h, 0x03, cmd, len, &xfer, 2000);
if (rc) return rc;
unsigned char resp[0x1000];
rc = libusb_bulk_transfer(h, 0x82, resp, sizeof(resp), &xfer, 2000);
return rc;
}
static void render(const unsigned char *data, unsigned mn, unsigned mx) {
unsigned char *px = g_bmp + 54;
for (int y = 0; y < 120; ++y) {
for (int x = 0; x < 160; ++x) {
int o = (y * 160 + x) * 2 + 28;
unsigned v = (unsigned)data[o] | ((unsigned)data[o+1] << 8);
unsigned idx = (v - mn) * 255 / (mx - mn + 1);
if (idx > 255) idx = 255;
unsigned char r, g, b;
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 = (119 - y) * 160 * 3 + x * 3;
px[dst+0] = b; px[dst+1] = g; px[dst+2] = r;
}
}
}
static DWORD WINAPI reader_thread(LPVOID p) {
(void)p;
unsigned char hdr[64];
unsigned prev = 0xffffffff;
int frames = 0;
int ffc_done = 0;
while (g_running) {
int xfer = 0;
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 2000) || 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 == prev) continue;
prev = c;
if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 2000) || xfer < 38400) continue;
frames++;
/* switch to type=0 after the 3rd complete frame (device is streaming) */
if (!ffc_done && frames == 3) {
sendcmd(g_h, 0x6bb6b672, 1, 8);
ffc_done = 1;
Sleep(400);
continue;
}
unsigned mn = 0xffff, mx = 0;
for (int k = 0; k < 38400; k += 2) {
unsigned v = (unsigned)g_frame[k] | ((unsigned)g_frame[k+1] << 8);
if (v < mn) mn = v;
if (v > mx) mx = v;
}
EnterCriticalSection(&g_lock);
render(g_frame, mn, mx);
snprintf(g_title, sizeof(g_title), "MAG160C thermal live - frame %u type=%u range [%u..%u]",
c, (unsigned)hdr[12], mn, mx);
g_new_frame = 1;
LeaveCriticalSection(&g_lock);
}
return 0;
}
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, 160, 120);
HGDIOBJ old = SelectObject(mem, bm);
SetDIBitsToDevice(mem, 0, 0, 160, 120, 0, 0, 0, 120, g_bmp + 54,
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
StretchBlt(dc, 0, 0, 640, 480, mem, 0, 0, 160, 120, SRCCOPY);
SelectObject(mem, old);
DeleteObject(bm);
DeleteDC(mem);
EndPaint(hw, &ps);
break;
}
case WM_ERASEBKGND:
return 1;
case WM_KEYDOWN:
if (wp == VK_ESCAPE) { DestroyWindow(hw); return 0; }
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hw, msg, wp, lp);
}
int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) {
(void)prev; (void)cmd; (void)show;
libusb_context *ctx = NULL;
libusb_init(&ctx);
libusb_device **list = NULL;
ssize_t cnt = libusb_get_device_list(ctx, &list);
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
struct libusb_device_descriptor d;
libusb_get_device_descriptor(list[i], &d);
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
}
if (!g_h) {
MessageBoxA(NULL, "no MAG160C device found", "MAG160C", MB_ICONERROR);
return 1;
}
libusb_set_configuration(g_h, 2);
libusb_set_configuration(g_h, 1);
libusb_claim_interface(g_h, 0);
sendcmd(g_h, 0x6bb6b66b, 0, 4);
sendcmd(g_h, 0x6bb6b66c, 0, 4);
sendcmd(g_h, 0x6bb6b66f, 0, 4);
sendcmd(g_h, 0x6bb6b672, 0, 8);
sendcmd(g_h, 0x6bb6b672, 0, 8);
Sleep(300);
sendcmd(g_h, 0x6bb6b673, 0, 4);
Sleep(500);
/* FFC(1) is issued from the reader thread after the 3rd frame */
WNDCLASSA wc = {0};
wc.lpfnWndProc = wndproc;
wc.hInstance = inst;
wc.lpszClassName = "ThermalView";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClassA(&wc);
HWND hw = CreateWindowA("ThermalView", "MAG160C thermal live",
WS_OVERLAPPEDWINDOW, 100, 100, 656, 520,
NULL, NULL, inst, NULL);
/* BMP header */
unsigned sz = 54 + 160*120*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]=160; g_bmp[19]=0; g_bmp[22]=120; g_bmp[23]=0;
g_bmp[26]=1; g_bmp[28]=24;
InitializeCriticalSection(&g_lock);
g_running = 1;
ShowWindow(hw, SW_SHOW);
/* single-threaded: read one frame, render, pump messages */
int nread = 0;
unsigned prev2 = 0xffffffff;
int ffc2 = 0;
for (;;) {
MSG msg;
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 = 0;
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) continue;
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 == prev2) continue;
prev2 = c;
if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 200) || xfer < 38400) continue;
nread++;
if (!ffc2 && nread == 3) {
sendcmd(g_h, 0x6bb6b672, 1, 8);
ffc2 = 1;
Sleep(400);
continue;
}
unsigned hist[65536] = {0};
unsigned nz = 0;
for (int k = 0; k < 38400; k += 2) {
unsigned v = (unsigned)g_frame[k] | ((unsigned)g_frame[k+1] << 8);
if (v == 0) continue;
hist[v]++;
nz++;
}
unsigned mn = 0, mx = 0;
unsigned acc = 0;
unsigned lo = (unsigned)(nz * 2 / 100);
unsigned hi = (unsigned)(nz * 98 / 100);
for (unsigned k = 0; k < 65536; ++k) {
acc += hist[k];
if (acc >= lo && mn == 0) mn = k;
if (acc >= hi && mx == 0) { mx = k; break; }
}
if (mx <= mn + 50) { mn = 0; mx = 65535; }
EnterCriticalSection(&g_lock);
render(g_frame, mn, mx);
snprintf(g_title, sizeof(g_title),
"MAG160C thermal live - frame %u type=%u range [%u..%u]",
c, (unsigned)hdr[12], mn, mx);
LeaveCriticalSection(&g_lock);
SetWindowTextA(hw, g_title);
InvalidateRect(hw, NULL, FALSE);
UpdateWindow(hw);
}
}
done:;
g_running = 0;
g_running = 0;
Sleep(300);
libusb_clear_halt(g_h, 0x03);
libusb_clear_halt(g_h, 0x82);
sendcmd(g_h, 0x6bb6b674, 0, 4);
libusb_close(g_h);
libusb_exit(ctx);
return 0;
}