yunsi-toolbox-vue/lib/partition-table/parser.ts

74 lines
2.6 KiB
TypeScript

import { readU16, readU32, readNullTermString } from '../shared/binary-reader';
import { PartitionEntry, PartitionTable, PartitionType } from './types';
import { ENTRY_SIZE, ENTRY_MAGIC, MD5_MAGIC, NAME_FIELD_SIZE, MAX_ENTRIES } from './constants';
import { md5 } from './md5';
/**
* Parse ESP32 partition table binary.
* @param data Raw binary data (the partition table region, typically 3KB at offset 0x8000)
*/
export function parseBinary(data: Uint8Array): PartitionTable {
const entries: PartitionEntry[] = [];
let md5Valid = false;
let corrupted = false;
let offset = 0;
for (let i = 0; i < MAX_ENTRIES && offset + ENTRY_SIZE <= data.length; i++) {
const magic = readU16(data, offset);
if (magic === 0xFFFF) {
// Empty entry — end of table (erased flash)
break;
}
if (magic === MD5_MAGIC) {
// MD5 checksum entry
// Bytes [16..31] contain the stored MD5 hash of all preceding bytes
const storedMd5 = data.subarray(offset + 16, offset + 32);
const computedMd5 = md5(data.subarray(0, offset));
md5Valid = storedMd5.length === 16 && computedMd5.length === 16 &&
storedMd5.every((v, j) => v === computedMd5[j]);
break;
}
if (magic !== ENTRY_MAGIC) {
// Unknown magic — binary is corrupted
corrupted = true;
break;
}
// Parse 32-byte entry:
// [0..1] magic (already read)
// [2] type
// [3] subtype
// [4..7] offset (LE uint32)
// [8..11] size (LE uint32)
// [12..27] name (null-terminated, 16 bytes)
// [28..31] flags (LE uint32)
const type = data[offset + 2] as PartitionType;
const subtype = data[offset + 3];
const partOffset = readU32(data, offset + 4);
const size = readU32(data, offset + 8);
const name = readNullTermString(data, offset + 12, NAME_FIELD_SIZE);
const flags = readU32(data, offset + 28);
entries.push({ name, type, subtype, offset: partOffset, size, flags });
offset += ENTRY_SIZE;
}
// When the table is exactly full (MAX_ENTRIES entries), the loop exits on
// i >= MAX_ENTRIES without ever seeing the MD5 slot. Check it explicitly.
if (!md5Valid && offset + ENTRY_SIZE <= data.length) {
const magic = readU16(data, offset);
if (magic === MD5_MAGIC) {
const storedMd5 = data.subarray(offset + 16, offset + 32);
const computedMd5 = md5(data.subarray(0, offset));
md5Valid = storedMd5.length === 16 && computedMd5.length === 16 &&
storedMd5.every((v, j) => v === computedMd5[j]);
}
}
return { entries, md5Valid, ...(corrupted ? { corrupted } : {}) };
}