rizin/librz/bin/format/mtk/md1img.c
Dmitry Opokin 9d37b7cdf2
Add MediaTek md1img and GFH firmware image parsers (#5974)
- Introduced md1img.h and md1img.c for parsing MediaTek md1img container format.
- Implemented mtk.h and mtk.c for parsing MediaTek GFH firmware images (md1rom).
- Added plugin support for md1img and mtk formats in bin_md1img.c and bin_mtk.c.
- Updated meson.build to include new source files and plugins.
- Enhanced RzBuffer utility with LZMA alone decompression support.

---------

Co-authored-by: Giovanni <561184+wargio@users.noreply.github.com>
2026-06-22 20:25:48 +00:00

665 lines
20 KiB
C

// SPDX-FileCopyrightText: 2025 godcodehunter
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file Parser for MediaTek md1img container format.
*
* The md1img format is a simple section-based container used by MediaTek
* to package modem firmware components: md1rom (GFH firmware image),
* debug info (CATI format), DSP images, certificates, etc.
*
* Each section has a header with magic 0x58881688, a name, data size,
* memory address, and other metadata, followed by the section data
* and alignment padding.
*
* The md1rom section is parsed internally using the mtk GFH parser to
* provide proper base address, entry points, and code/header sections.
* Debug symbols are extracted from the CATI-format dbginfo section.
*
* References:
* - https://github.com/nccgroup/mtk_bp/blob/main/mtk_structs/mtk_img.ksy (md1img)
* - https://github.com/nccgroup/mtk_bp/blob/main/mtk_structs/mtk_dbg_info.ksy (CATI)
*/
#include "md1img.h"
// --- CATI debug info parsing ---
static void mtk_dbg_symbol_free(MtkDbgSymbol *sym) {
if (sym) {
free(sym->name);
free(sym);
}
}
/**
* \brief Parse symbols from a single CATI debug entry.
*
* The debug entry format (after "CATI" magic + type):
* unk3(u32), unk_str1(strz), unk_str2(strz), unk_str3(strz), date_str(strz),
* symbols_start(u32), files_start(u32),
* symbol_entries: [strz name, u32 addr1, u32 addr2] ... until empty name
*/
static bool md1img_parse_cati_debug(RzBuffer *b, RzPVector /*<MtkDbgSymbol *>*/ *symbols) {
// Skip unk3
rz_buf_seek(b, 4, RZ_BUF_CUR);
// Skip 4 strz fields (unk_str1, unk_str2, unk_str3, date_str)
for (int i = 0; i < 4; i++) {
ut64 len = rz_buf_read_string(b, NULL);
if (len == 0) {
return false;
}
}
// Skip symbols_start and files_start
rz_buf_seek(b, 8, RZ_BUF_CUR);
// Parse symbol entries (limit guards against corrupted/missing terminator)
int count = 0;
while (count < 500000) {
char *name = NULL;
ut64 len = rz_buf_read_string(b, &name);
if (len == 0 || !name) {
free(name);
break;
}
// Empty string = terminator
if (name[0] == '\0') {
free(name);
break;
}
ut32 addr1 = 0, addr2 = 0;
if (!rz_buf_read_le32(b, &addr1) || !rz_buf_read_le32(b, &addr2)) {
free(name);
break;
}
MtkDbgSymbol *sym = RZ_NEW0(MtkDbgSymbol);
if (!sym) {
free(name);
break;
}
sym->name = name;
sym->addr = addr1;
// For corrupted entries
sym->size = (addr2 > addr1) ? (addr2 - addr1) : 0;
rz_pvector_push(symbols, sym);
count++;
}
return count > 0;
}
/**
* \brief Parse a CATI container wrapping one or more debug entries.
*/
static bool md1img_parse_cati_container(RzBuffer *b, RzPVector /*<MtkDbgSymbol *>*/ *symbols) {
// unk1(u32), size(u32)
ut32 unk1 = 0, container_size = 0;
if (!rz_buf_read_le32(b, &unk1) || !rz_buf_read_le32(b, &container_size)) {
return false;
}
// 0x10 = CATI header size (magic[4] + type[4] + unk1[4] + size[4])
if (container_size < 0x10) {
return false;
}
// The nested entries are within (container_size - 0x10) bytes
ut64 entries_end = rz_buf_tell(b) + (container_size - 0x10);
ut64 buf_size = rz_buf_size(b);
// Clamp to actual buffer size in case container_size is corrupted
if (entries_end > buf_size) {
entries_end = buf_size;
}
// Parse nested CATI headers
while (rz_buf_tell(b) + 8 <= entries_end) {
ut8 magic[MTK_CATI_MAGIC_SIZE];
if (rz_buf_read(b, magic, MTK_CATI_MAGIC_SIZE) != MTK_CATI_MAGIC_SIZE ||
memcmp(magic, MTK_CATI_MAGIC, MTK_CATI_MAGIC_SIZE) != 0) {
break;
}
ut32 cati_type = 0;
if (!rz_buf_read_le32(b, &cati_type)) {
break;
}
if (cati_type != MTK_CATI_TYPE_DEBUG && cati_type != MTK_CATI_TYPE_DEBUG_DSP) {
break;
}
md1img_parse_cati_debug(b, symbols);
}
return rz_pvector_len(symbols) > 0;
}
/**
* \brief Parse a CATI debug info buffer and extract symbols.
*/
static RzPVector /*<MtkDbgSymbol *>*/ *md1img_parse_dbginfo(RzBuffer *b) {
rz_return_val_if_fail(b, NULL);
ut8 magic[MTK_CATI_MAGIC_SIZE];
if (rz_buf_read_at(b, 0, magic, MTK_CATI_MAGIC_SIZE) != MTK_CATI_MAGIC_SIZE) {
return NULL;
}
if (memcmp(magic, MTK_CATI_MAGIC, MTK_CATI_MAGIC_SIZE) != 0) {
return NULL;
}
rz_buf_seek(b, 0 + MTK_CATI_MAGIC_SIZE, RZ_BUF_SET);
ut32 cati_type = 0;
if (!rz_buf_read_le32(b, &cati_type)) {
return NULL;
}
RzPVector *symbols = rz_pvector_new((RzPVectorFree)mtk_dbg_symbol_free);
if (!symbols) {
return NULL;
}
bool ok = false;
if (cati_type == MTK_CATI_TYPE_CONTAINER) {
ok = md1img_parse_cati_container(b, symbols);
} else if (cati_type == MTK_CATI_TYPE_DEBUG || cati_type == MTK_CATI_TYPE_DEBUG_DSP) {
ok = md1img_parse_cati_debug(b, symbols);
}
if (!ok || rz_pvector_len(symbols) == 0) {
rz_pvector_free(symbols);
return NULL;
}
return symbols;
}
// --- md1img container parsing ---
static bool md1img_read_section_hdr(RzBuffer *b, ut64 section_start, Md1imgSection *sec) {
ut64 offset = section_start;
// Check magic
ut8 magic[MD1IMG_MAGIC_SIZE];
if (!rz_buf_read_offset(b, &offset, magic, MD1IMG_MAGIC_SIZE)) {
return false;
}
if (memcmp(magic, MD1IMG_MAGIC, MD1IMG_MAGIC_SIZE) != 0) {
return false;
}
// Read dsize
if (!rz_buf_read_le32_offset(b, &offset, &sec->dsize)) {
return false;
}
// Read name (32 bytes, null-terminated)
if (!rz_buf_read_offset(b, &offset, (ut8 *)sec->name, MD1IMG_NAME_SIZE)) {
return false;
}
sec->name[MD1IMG_NAME_SIZE - 1] = '\0';
// Read maddr and mode
if (!rz_buf_read_le32_offset(b, &offset, &sec->maddr) ||
!rz_buf_read_le32_offset(b, &offset, &sec->mode)) {
return false;
}
// Verify ext_magic
ut8 ext_magic[MD1IMG_EXT_MAGIC_SIZE];
if (!rz_buf_read_offset(b, &offset, ext_magic, MD1IMG_EXT_MAGIC_SIZE)) {
return false;
}
if (memcmp(ext_magic, MD1IMG_EXT_MAGIC, MD1IMG_EXT_MAGIC_SIZE) != 0) {
return false;
}
// Read remaining header fields
if (!rz_buf_read_le32_offset(b, &offset, &sec->hdr_size) ||
!rz_buf_read_le32_offset(b, &offset, &sec->hdr_version) ||
!rz_buf_read_le32_offset(b, &offset, &sec->img_type) ||
!rz_buf_read_le32_offset(b, &offset, &sec->img_list_end) ||
!rz_buf_read_le32_offset(b, &offset, &sec->align_size) ||
!rz_buf_read_le32_offset(b, &offset, &sec->dsize_extend) ||
!rz_buf_read_le32_offset(b, &offset, &sec->maddr_extend)) {
return false;
}
if (sec->hdr_size < MD1IMG_MIN_HDR_SIZE) {
return false;
}
sec->data_offset = section_start + sec->hdr_size;
return true;
}
RZ_IPI bool md1img_check_buffer(RZ_BORROW RZ_NONNULL RzBuffer *b) {
rz_return_val_if_fail(b, false);
if (rz_buf_size(b) < MD1IMG_MIN_HDR_SIZE) {
return false;
}
ut8 magic[MD1IMG_MAGIC_SIZE];
if (rz_buf_read_at(b, 0, magic, MD1IMG_MAGIC_SIZE) != MD1IMG_MAGIC_SIZE) {
return false;
}
return memcmp(magic, MD1IMG_MAGIC, MD1IMG_MAGIC_SIZE) == 0;
}
/**
* \brief Register the section's virtual file and run per-section parsers
* (GFH for md1rom, CATI for dbginfo). Takes ownership of \p vbuf.
*/
static void md1img_load_section(Md1imgObj *md1, int sec_idx, RZ_BORROW RZ_NONNULL const Md1imgSection *sec, RZ_OWN RZ_NONNULL RzBuffer *vbuf) {
RzBinVirtualFile *vfile = RZ_NEW0(RzBinVirtualFile);
if (!vfile) {
rz_buf_free(vbuf);
return;
}
vfile->buf = vbuf;
vfile->buf_owned = true;
vfile->name = rz_str_dup(sec->name);
rz_pvector_push(md1->vfiles, vfile);
// Parse md1rom section with mtk GFH parser
if (rz_str_casestr(sec->name, "md1rom") && md1->md1rom_idx < 0) {
md1->md1rom_idx = sec_idx;
// Search for GFH magic within md1rom data (may not be at offset 0)
ut64 gfh_off = 0;
ut8 magic_buf[4];
while (gfh_off + MTK_GFH_MIN_FILE_SIZE <= sec->dsize) {
if (rz_buf_read_at(vbuf, gfh_off, magic_buf, 4) != 4) {
break;
}
ut32 magic_val = rz_read_le32(magic_buf);
if ((magic_val & MTK_GFH_MAGIC_MASK) == MTK_GFH_MAGIC) {
break;
}
gfh_off += 4;
}
if (gfh_off + MTK_GFH_MIN_FILE_SIZE <= sec->dsize) {
RzBuffer *gfh_buf = rz_buf_new_slice(vbuf, gfh_off, sec->dsize - gfh_off);
if (gfh_buf) {
md1->mtk = mtk_obj_new(gfh_buf);
rz_buf_free(gfh_buf);
}
if (md1->mtk) {
md1->gfh_offset = gfh_off;
RZ_LOG_INFO("md1img: parsed GFH from '%s' at offset 0x%" PFMT64x " (load_addr=0x%x, entry=0x%x)\n",
sec->name, gfh_off, md1->mtk->file_info.load_addr, md1->mtk->entry_vaddr);
}
}
}
// Parse CATI debug info from dbginfo sections
if (rz_str_casestr(sec->name, "dbginfo") && sec->dsize > MTK_CATI_MAGIC_SIZE) {
RzPVector *syms = md1img_parse_dbginfo(vbuf);
// Try LZMA alone decompression if raw CATI parsing failed (dbginfo may be LZMA-compressed)
if (!syms) {
RzBuffer *decompressed = rz_buf_new_empty(0);
if (decompressed && rz_lzma_alone_dec_buf(vbuf, decompressed, 4096)) {
syms = md1img_parse_dbginfo(decompressed);
}
rz_buf_free(decompressed);
}
if (syms) {
if (!md1->dbg_symbols) {
md1->dbg_symbols = syms;
} else {
// Merge symbols from additional dbginfo sections
void **it;
rz_pvector_foreach (syms, it) {
rz_pvector_push(md1->dbg_symbols, *it);
}
// Disown elements before freeing (ownership transferred)
syms->v.free = NULL;
rz_pvector_free(syms);
}
RZ_LOG_INFO("md1img: loaded debug symbols from '%s'\n", sec->name);
}
}
}
RZ_IPI bool md1img_load_buffer(RZ_BORROW RZ_NONNULL RzBinFile *bf, RZ_BORROW RZ_NONNULL RzBinObject *obj, RZ_BORROW RZ_NONNULL RzBuffer *b, RZ_BORROW RZ_NULLABLE Sdb *sdb) {
rz_return_val_if_fail(bf && obj && b, false);
Md1imgObj *md1 = RZ_NEW0(Md1imgObj);
if (!md1) {
return false;
}
md1->sections = rz_vector_new(sizeof(Md1imgSection), NULL, NULL);
md1->vfiles = rz_pvector_new((RzPVectorFree)rz_bin_virtual_file_free);
md1->md1rom_idx = -1;
if (!md1->sections || !md1->vfiles) {
rz_vector_free(md1->sections);
rz_pvector_free(md1->vfiles);
free(md1);
return false;
}
ut64 buf_size = rz_buf_size(b);
ut64 offset = 0;
int count = 0;
// Limit iteration count as a safeguard against corrupted headers
while (offset + MD1IMG_MIN_HDR_SIZE <= buf_size && count < 256) {
Md1imgSection sec = { 0 };
if (!md1img_read_section_hdr(b, offset, &sec)) {
break;
}
// Validate that data fits in the buffer
if (sec.data_offset + sec.dsize > buf_size) {
RZ_LOG_WARN("md1img: section '%s' data exceeds file size, truncating\n", sec.name);
sec.dsize = buf_size - sec.data_offset;
}
int sec_idx = rz_vector_len(md1->sections);
rz_vector_push(md1->sections, &sec);
// Create a virtual file for this section's data and parse it
RzBuffer *vbuf = rz_buf_new_slice(b, sec.data_offset, sec.dsize);
if (vbuf) {
md1img_load_section(md1, sec_idx, &sec, vbuf);
}
// Advance to next section: data_offset + dsize + alignment padding
ut64 data_end = sec.data_offset + sec.dsize;
ut64 remainder = sec.align_size > 0 ? sec.dsize % sec.align_size : 0;
if (remainder > 0) {
offset = data_end + (sec.align_size - remainder);
} else {
offset = data_end;
}
count++;
}
if (offset < buf_size) {
RZ_LOG_WARN("md1img: %" PFMT64u " trailing bytes after last section header\n", buf_size - offset);
}
if (rz_vector_len(md1->sections) == 0) {
rz_vector_free(md1->sections);
rz_pvector_free(md1->vfiles);
free(md1);
return false;
}
obj->bin_obj = md1;
return true;
}
RZ_IPI void md1img_destroy(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
if (!bf || !bf->o || !bf->o->bin_obj) {
return;
}
Md1imgObj *md1 = bf->o->bin_obj;
rz_vector_free(md1->sections);
rz_pvector_free(md1->vfiles);
rz_pvector_free(md1->dbg_symbols);
mtk_obj_free(md1->mtk);
free(md1);
}
RZ_IPI RZ_OWN RzBinInfo *md1img_info(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
RzBinInfo *info = RZ_NEW0(RzBinInfo);
if (!info) {
return NULL;
}
info->file = bf->file ? rz_str_dup(bf->file) : NULL;
info->type = rz_str_dup("MediaTek md1img container");
info->machine = rz_str_dup("MediaTek Modem");
info->arch = rz_str_dup("mips");
info->cpu = rz_str_dup("nanomips");
info->rclass = rz_str_dup("firmware");
info->subsystem = rz_str_dup("modem");
info->has_va = true;
info->bits = 32;
info->big_endian = false;
return info;
}
RZ_IPI ut64 md1img_baddr(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
return MTK_MODEM_BADDR;
}
RZ_IPI RZ_OWN RzPVector /*<RzBinAddr *>*/ *md1img_entries(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL);
Md1imgObj *md1 = bf->o->bin_obj;
RzPVector *ret = rz_pvector_new(free);
if (!ret) {
return NULL;
}
if (md1->mtk) {
RzBinAddr *entry = RZ_NEW0(RzBinAddr);
if (entry) {
entry->vaddr = md1img_baddr(bf) + (md1->mtk->file_info.jump_offset - md1->mtk->code_offset);
entry->paddr = md1->gfh_offset + md1->mtk->file_info.jump_offset;
rz_pvector_push(ret, entry);
}
}
return ret;
}
RZ_IPI RZ_OWN RzPVector /*<RzBinVirtualFile *>*/ *md1img_virtual_files(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL);
Md1imgObj *md1 = bf->o->bin_obj;
RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_virtual_file_free);
if (!ret) {
return NULL;
}
void **it;
rz_pvector_foreach (md1->vfiles, it) {
RzBinVirtualFile *vf = *it;
RzBinVirtualFile *clone = rz_bin_virtual_file_clone(vf);
if (clone) {
rz_pvector_push(ret, clone);
}
}
return ret;
}
RZ_IPI RZ_OWN RzPVector /*<RzBinMap *>*/ *md1img_maps(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL);
Md1imgObj *md1 = bf->o->bin_obj;
RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_map_free);
if (!ret) {
return NULL;
}
// Only map the md1rom section into the virtual address space.
// Other sections (md1dsp, md1drdi, certs, etc.) belong to different
// processors or address spaces and would conflict with md1rom if mapped.
// They remain accessible as virtual files for raw data viewing.
if (md1->md1rom_idx >= 0) {
Md1imgSection *sec = rz_vector_index_ptr(md1->sections, md1->md1rom_idx);
if (md1->mtk) {
ut64 paddr = md1->gfh_offset + md1->mtk->code_offset;
mtk_append_maps(md1->mtk, paddr, sec->name, ret);
} else {
RzBinMap *map = RZ_NEW0(RzBinMap);
if (map) {
map->name = rz_str_dup(sec->name);
map->vfile_name = rz_str_dup(sec->name);
map->paddr = 0;
map->psize = sec->dsize;
map->vaddr = sec->maddr;
map->vsize = sec->dsize;
map->perm = RZ_PERM_RX;
rz_pvector_push(ret, map);
}
}
}
return ret;
}
RZ_IPI RZ_OWN RzPVector /*<RzBinSection *>*/ *md1img_sections(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL);
Md1imgObj *md1 = bf->o->bin_obj;
RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_section_free);
if (!ret) {
return NULL;
}
if (md1->mtk && md1->md1rom_idx >= 0 && md1->mtk->code_size > 0) {
Md1imgSection *sec = rz_vector_index_ptr(md1->sections, md1->md1rom_idx);
RzBinSection *code = RZ_NEW0(RzBinSection);
if (code) {
code->name = rz_str_newf("%s.code", sec->name);
code->paddr = md1->gfh_offset + md1->mtk->code_offset;
code->size = md1->mtk->code_size;
code->vsize = md1->mtk->code_size;
code->vaddr = md1img_baddr(bf);
code->perm = RZ_PERM_RX;
rz_pvector_push(ret, code);
}
}
return ret;
}
RZ_IPI RZ_OWN RzPVector /*<RzBinSymbol *>*/ *md1img_symbols(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL);
Md1imgObj *md1 = bf->o->bin_obj;
RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_symbol_free);
if (!ret) {
return NULL;
}
if (!md1->dbg_symbols) {
return ret;
}
void **it;
rz_pvector_foreach (md1->dbg_symbols, it) {
MtkDbgSymbol *dbg = *it;
RzBinSymbol *sym = RZ_NEW0(RzBinSymbol);
if (!sym) {
continue;
}
sym->name = rz_str_dup(dbg->name);
sym->vaddr = dbg->addr;
sym->size = dbg->size;
sym->type = RZ_BIN_TYPE_FUNC_STR;
// Compute paddr for symbols within mapped regions (kseg0 or kuseg).
if (md1->mtk) {
ut64 code_paddr = md1->gfh_offset + md1->mtk->code_offset;
ut64 kseg0_end = md1img_baddr(bf) + md1->mtk->code_size;
ut64 kuseg_base = md1->mtk->file_info.load_addr + md1->mtk->code_offset;
ut64 kuseg_end = kuseg_base + md1->mtk->code_size;
if (dbg->addr >= md1img_baddr(bf) && dbg->addr < kseg0_end) {
sym->paddr = code_paddr + (dbg->addr - md1img_baddr(bf));
} else if (dbg->addr >= kuseg_base && dbg->addr < kuseg_end) {
sym->paddr = code_paddr + (dbg->addr - kuseg_base);
}
}
rz_pvector_push(ret, sym);
}
return ret;
}
RZ_IPI RZ_OWN RzPVector /*<RzBinString *>*/ *md1img_strings(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
// Container format: suppress string scanning on the raw container buffer.
// Strings from the firmware payload are accessible via the mapped vfiles.
return rz_pvector_new(NULL);
}
RZ_IPI RZ_OWN RzStructuredData *md1img_structure(RZ_BORROW RZ_NONNULL RzBinFile *bf) {
rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL);
Md1imgObj *md1 = bf->o->bin_obj;
RzStructuredData *root = rz_structured_data_new_map();
if (!root) {
return NULL;
}
rz_structured_data_map_add_unsigned(root, "num_sections", rz_vector_len(md1->sections), false);
RzStructuredData *secs = rz_structured_data_map_add_array(root, "sections");
if (secs) {
Md1imgSection *sec;
rz_vector_foreach (md1->sections, sec) {
RzStructuredData *s = rz_structured_data_array_add_map(secs);
if (!s) {
continue;
}
rz_structured_data_map_add_string(s, "name", sec->name);
rz_structured_data_map_add_unsigned(s, "data_size", sec->dsize, true);
rz_structured_data_map_add_unsigned(s, "data_offset", sec->data_offset, true);
rz_structured_data_map_add_unsigned(s, "maddr", sec->maddr, true);
rz_structured_data_map_add_unsigned(s, "mode", sec->mode, true);
rz_structured_data_map_add_unsigned(s, "hdr_size", sec->hdr_size, true);
rz_structured_data_map_add_unsigned(s, "hdr_version", sec->hdr_version, true);
rz_structured_data_map_add_unsigned(s, "img_type", sec->img_type, true);
rz_structured_data_map_add_unsigned(s, "img_list_end", sec->img_list_end, true);
rz_structured_data_map_add_unsigned(s, "align_size", sec->align_size, true);
}
}
// GFH structure from md1rom section
if (md1->mtk) {
MtkObj *mtk = md1->mtk;
RzStructuredData *gfh = rz_structured_data_map_add_map(root, "gfh_file_info");
if (gfh) {
rz_structured_data_map_add_unsigned(gfh, "magic_version", mtk->first_common.magic_version, true);
rz_structured_data_map_add_unsigned(gfh, "header_block_size", mtk->first_common.size, false);
rz_structured_data_map_add_string(gfh, "header_type", mtk_gfh_type_str(mtk->first_common.type));
rz_structured_data_map_add_string(gfh, "name", mtk->file_info.name);
rz_structured_data_map_add_unsigned(gfh, "file_type", mtk->file_info.file_type, true);
rz_structured_data_map_add_unsigned(gfh, "flash_type", mtk->file_info.flash_type, true);
rz_structured_data_map_add_unsigned(gfh, "sig_type", mtk->file_info.sig_type, true);
rz_structured_data_map_add_unsigned(gfh, "load_addr", mtk->file_info.load_addr, true);
rz_structured_data_map_add_unsigned(gfh, "total_size", mtk->file_info.total_size, true);
rz_structured_data_map_add_unsigned(gfh, "max_size", mtk->file_info.max_size, true);
rz_structured_data_map_add_unsigned(gfh, "hdr_size", mtk->file_info.hdr_size, true);
rz_structured_data_map_add_unsigned(gfh, "sig_size", mtk->file_info.sig_size, true);
rz_structured_data_map_add_unsigned(gfh, "jump_offset", mtk->file_info.jump_offset, true);
rz_structured_data_map_add_unsigned(gfh, "entry_point", mtk->entry_vaddr, true);
}
if (rz_vector_len(mtk->extra_headers) > 0) {
RzStructuredData *hdrs = rz_structured_data_map_add_array(root, "gfh_headers");
if (hdrs) {
MtkGfhHeader *extra;
rz_vector_foreach (mtk->extra_headers, extra) {
RzStructuredData *h = rz_structured_data_array_add_map(hdrs);
if (!h) {
continue;
}
rz_structured_data_map_add_unsigned(h, "file_offset", extra->file_offset, true);
rz_structured_data_map_add_string(h, "type", mtk_gfh_type_str(extra->common.type));
rz_structured_data_map_add_unsigned(h, "type_id", extra->common.type, true);
rz_structured_data_map_add_unsigned(h, "size", extra->common.size, false);
}
}
}
}
if (md1->dbg_symbols) {
rz_structured_data_map_add_unsigned(root, "debug_symbols_count",
rz_pvector_len(md1->dbg_symbols), false);
}
return root;
}