From da91312ca9a9edc93277e39ed4e5a5409ad97209 Mon Sep 17 00:00:00 2001 From: kerms Date: Wed, 11 Mar 2026 11:03:57 +0100 Subject: [PATCH] feat(app-image): add custom app desc dump and SPI pin drive display --- components/app-image-viewer/AppImageViewer.vue | 13 +++++++++++++ lib/app-image/constants.ts | 6 ++++++ lib/app-image/parser.ts | 11 +++++++++++ lib/app-image/types.ts | 2 ++ 4 files changed, 32 insertions(+) diff --git a/components/app-image-viewer/AppImageViewer.vue b/components/app-image-viewer/AppImageViewer.vue index 30c9735..5f69864 100644 --- a/components/app-image-viewer/AppImageViewer.vue +++ b/components/app-image-viewer/AppImageViewer.vue @@ -34,6 +34,10 @@ function formatHex(val: number): string { return '0x' + val.toString(16).toUpperCase(); } +function formatHexDump(data: Uint8Array): string { + return Array.from(data).map(b => b.toString(16).padStart(2, '0')).join(' '); +} + function formatSha256(data: Uint8Array): string { // Check if all zeros (not computed) if (data.every(b => b === 0)) return '(未计算)'; @@ -110,6 +114,7 @@ async function handleOpenFile(file: File): Promise { {{ SPI_FLASH_SIZE_NAMES[imageInfo.header.spiSize] ?? formatHex(imageInfo.header.spiSize) }} {{ imageInfo.header.segmentCount }} {{ formatHex(imageInfo.extendedHeader.wpPin) }} + {{ imageInfo.extendedHeader.spiPinDrv.map(formatHex).join(' / ') }} {{ imageInfo.extendedHeader.minChipRevFull / 100 }} {{ imageInfo.extendedHeader.maxChipRevFull === 0xFFFF ? '不限' : imageInfo.extendedHeader.maxChipRevFull / 100 }} {{ imageInfo.extendedHeader.hashAppended ? '是' : '否' }} @@ -128,6 +133,14 @@ async function handleOpenFile(file: File): Promise { + + + diff --git a/lib/app-image/constants.ts b/lib/app-image/constants.ts index 95126a1..f85cabe 100644 --- a/lib/app-image/constants.ts +++ b/lib/app-image/constants.ts @@ -16,6 +16,12 @@ export const APP_DESC_MAGIC = 0xABCD5432; /** Size of esp_app_desc_t structure */ export const APP_DESC_SIZE = 256; +/** Offset of custom app desc within first segment data (immediately after esp_app_desc_t) */ +export const CUSTOM_DESC_OFFSET_IN_SEGMENT = APP_DESC_SIZE; // 256 + +/** How many raw bytes to extract for the custom app desc dump */ +export const CUSTOM_DESC_DUMP_SIZE = 64; + /** Chip ID to human-readable name */ export const CHIP_ID_NAMES: Record = { 0x0000: 'ESP32', diff --git a/lib/app-image/parser.ts b/lib/app-image/parser.ts index a401740..8604822 100644 --- a/lib/app-image/parser.ts +++ b/lib/app-image/parser.ts @@ -5,6 +5,7 @@ import type { import { IMAGE_MAGIC, IMAGE_HEADER_SIZE, EXTENDED_HEADER_SIZE, SEGMENT_HEADER_SIZE, APP_DESC_MAGIC, APP_DESC_SIZE, CHIP_ID_NAMES, + CUSTOM_DESC_OFFSET_IN_SEGMENT, CUSTOM_DESC_DUMP_SIZE, } from './constants'; /** @@ -93,11 +94,21 @@ export function parseAppImage(data: Uint8Array): AppImageInfo { } } + // ── Custom App Description — fixed offset in first segment ── + let customDescRawBytes: Uint8Array | null = null; + if (segDataOffsets.length > 0) { + const customOff = segDataOffsets[0] + CUSTOM_DESC_OFFSET_IN_SEGMENT; + if (customOff + CUSTOM_DESC_DUMP_SIZE <= data.length) { + customDescRawBytes = new Uint8Array(data.subarray(customOff, customOff + CUSTOM_DESC_DUMP_SIZE)); + } + } + return { header, extendedHeader, segments, appDescription, + customDescRawBytes, valid: segments.length === segmentCount, // false if image was truncated mid-segment chipName: CHIP_ID_NAMES[chipId] ?? `Unknown (0x${chipId.toString(16)})`, }; diff --git a/lib/app-image/types.ts b/lib/app-image/types.ts index 274cc9d..4367269 100644 --- a/lib/app-image/types.ts +++ b/lib/app-image/types.ts @@ -63,6 +63,8 @@ export interface AppImageInfo { extendedHeader: ExtendedHeader; segments: SegmentHeader[]; appDescription: AppDescription | null; + /** Raw bytes at the custom app desc location (null if first segment too short) */ + customDescRawBytes: Uint8Array | null; valid: boolean; chipName: string; }