struct Sec_Hover_Info { Byte_Range rng; // A string tree where children are more indented than parents String8_Node *desc; String8_Node *highlighted_desc; }; template <typename T> T bswap_if_needed(T x) { return x; } template <> u16 bswap_if_needed(u16 x) { return bswap(x); } template <> u32 bswap_if_needed(u32 x) { return bswap(x); } template <> u64 bswap_if_needed(u64 x) { return bswap(x); } template <typename T> String8_Node *hover_display_val_be(Arena *arena, String8_Node *prev, const char *fmt, T val) { static_assert(!std::is_same_v<T, String8>); val = bswap_if_needed(val); return push_str8_node_child(arena, prev, fmt, val); } String8_Node *hover_display_val_str8(Arena *arena, String8_Node *prev, const char *fmt, String8 val) { return push_str8_node_child(arena, prev, fmt, val.str ? val.c() : ""); } template <typename T> String8_Node *hover_display_val_le(Arena *arena, String8_Node *prev, const char *fmt, T val) { return push_str8_node_child(arena, prev, fmt, val); } template <typename T> String8_Node *hover_display_val_le_abs(Arena *arena, String8_Node *prev, const char *fmt, T val) { return push_str8_node_child(arena, prev, fmt, std::abs(val)); } String8_Node *hover_display_generic_range(Arena *arena, String8_Node *prev, const char *desc, const u8 *) { return push_str8_node_child(arena, prev, desc); } internal String8_Node *hover_display_datetime_str(Arena *arena, String8_Node *prev, const char *fmt_pre, u32 datetime) { datetime = bswap(datetime); // datetime: // year (6b) | month (4b) | day (5b) | hour (5b) | min (6b) | sec (6b) u32 year = (datetime >> 26) + 1995; u32 month = ((datetime & 0x3ff'ffff) >> 22); u32 day = (datetime & 0x3f'ffff) >> 17; u32 hour = (datetime & 0x1'ffff) >> 12; u32 min = (datetime & 0xfff) >> 6; u32 sec = datetime & 0x3f; return push_str8_node_child(arena, prev, "%s%u/%02u/%02u %02u:%02u:%02u", fmt_pre, year, month, day, hour, min, sec); } // Returns null is `src` doesn't point to a zipped block internal String8_Node *display_val_rootzip(Arena *arena, String8_Node *prev, const char *fmt, const u8 *src) { const u8 Z_DEFLATED = 8; String8 zip_method; if (src[0] == 'Z' && src[1] == 'L' && src[2] == Z_DEFLATED) { zip_method = str8("ZLIB"); } else if (src[0] == 'C' && src[1] == 'S' && src[2] == Z_DEFLATED) { zip_method = str8("Old"); } else if (src[0] == 'X' && src[1] == 'Z' && src[2] == 0) { zip_method = str8("LZMA"); } else if (src[0] == 'L' && src[1] == '4') { zip_method = str8("LZ4"); } else if (src[0] == 'Z' && src[1] == 'S' && src[2] == 1) { zip_method = str8("ZSTD"); } else { return nullptr; } u32 comp_size = src[3] | (src[4] << 8) | (src[5] << 16); u32 uncomp_size = src[6] | (src[7] << 8) | (src[8] << 16); String8_Node *sn = push_str8_node_child(arena, prev, "%s", fmt); sn = push_str8_node_child(arena, sn, "Zip method: %s", zip_method.c()); sn = push_str8_node(arena, sn, "Compressed size: %s", to_pretty_size(arena, comp_size).c()); sn = push_str8_node(arena, sn, "Uncompressed size: %s", to_pretty_size(arena, uncomp_size).c()); sn = push_str8_node(arena, sn, "Comp. ratio: %.2f", (f32)comp_size / uncomp_size); return sn; } template <typename T> using Display_Fn = String8_Node *(*)(Arena *, String8_Node *, const char *, T); enum Hover_Section_Flags { HoverSec_None = 0, HoverSec_HideIfNotHovered = 1, }; // Functor used by get_section_hover_info to describe the structure of a section and print data about it. struct Sec_Hover_Fn { u64 start; // the start of the section (including the pre_size, e.g. the TKey) u64 roff; // the offset relative to the section start const u8 *data; // the entire file data const Section §ion; Arena *arena; Sec_Hover_Info &info; u64 &cur_field_off; b8 display_grouped; b8 ended = false; u8 cur_section_nesting = 0; u8 innermost_section_highlighted = 0; template <typename F> void titled_section(const char *title, F &&sec_body_fn, u64 flags = 0) { // if (ended) // return; ++cur_section_nesting; String8_Node *prev_desc = info.desc; info.desc = push_str8_node_child(arena, prev_desc, title); u64 sec_start = cur_field_off; sec_body_fn(); assert(cur_field_off >= sec_start); b8 hovered = roff >= sec_start && roff <= cur_field_off; if (!hovered & (flags & HoverSec_HideIfNotHovered)) { pop_str8_node_child(prev_desc, info.desc); } else if (display_grouped) { // if we're in display_grouped mode, we want to highlight the entire range of the section; u64 sec_len = cur_field_off - sec_start; info.rng = { start + sec_start, sec_len }; // In case of nested sections, only highlight the innermost // FIXME: sometimes no section ends up highlighted! if (innermost_section_highlighted <= cur_section_nesting) { info.highlighted_desc = info.desc; } innermost_section_highlighted = max(cur_section_nesting, innermost_section_highlighted); // ended = true; } --cur_section_nesting; info.desc = prev_desc; return; } // returns true if `val_read` was read template <typename T> b8 field(const char *desc_fmt, Display_Fn<T> display_val, T *val_read = nullptr) { static_assert(!std::is_same_v<T, String8>, "use field_str8 instead."); // if (ended) // return false; u64 field_len = sizeof(T); b8 hovered = cur_field_off <= roff && roff < cur_field_off + field_len; T val; memcpy(&val, (u8 *)data + start + cur_field_off, field_len); String8_Node *desc = display_val(arena, info.desc, desc_fmt, val); if (hovered && !display_grouped) info.highlighted_desc = desc; if (val_read) *val_read = val; if (display_grouped || hovered) { info.rng = { start + cur_field_off, field_len }; // truncate the hovered section here if we're not in display_grouped mode. // ended = !display_grouped; } cur_field_off += field_len; return true; } template <typename TStrSize> void field_str8(const char *desc_fmt, Display_Fn<String8> display_val = hover_display_val_str8) { // if (ended) // return; // String size can be stored as different types, like u8 (by ROOT I/O) or u32 (by RNTuple). TStrSize str_size; memcpy(&str_size, data + start + cur_field_off, sizeof(TStrSize)); // DEBUG if (str_size > 1000) { printf("read str_size = %u at offset 0x%lX!\n", str_size, start + cur_field_off); // ended = true; return; } u64 field_len = sizeof(TStrSize) + (u64)str_size; b8 hovered = cur_field_off <= roff && roff < cur_field_off + field_len; u8 *buf = arena_push_array_nozero<u8>(arena, str_size + 1); memcpy(buf, data + start + cur_field_off + sizeof(TStrSize), str_size); buf[str_size] = 0; String8 s = { buf, str_size }; String8_Node *desc = display_val(arena, info.desc, desc_fmt, s); if (hovered && !display_grouped) info.highlighted_desc = desc; if (display_grouped || hovered) { info.rng = { start + cur_field_off, field_len }; // ended = !display_grouped; } cur_field_off += field_len; } template <typename T> b8 field_be(const char *desc_fmt, T *val_read = nullptr) { return field<T>(desc_fmt, hover_display_val_be<T>, val_read); } template <typename T> b8 field_le(const char *desc_fmt, T *val_read = nullptr) { return field<T>(desc_fmt, hover_display_val_le<T>, val_read); } // An unspecified range of bytes void range(const char *desc, u64 range_len, Display_Fn<const u8 *> display_val = hover_display_generic_range) { // if (ended) // return; b8 hovered = cur_field_off <= roff && roff < cur_field_off + range_len; String8_Node *dsc = display_val(arena, info.desc, desc, data + start + cur_field_off); if (hovered && !display_grouped) info.highlighted_desc = dsc; if (display_grouped || hovered) { info.rng = { start + cur_field_off, range_len }; // ended = !display_grouped; } cur_field_off += range_len; } // Returns true if `was_zipped` was read. b8 maybe_rootzip(b8 *was_zipped = nullptr) { // if (ended) // return false; // TODO boundary checks const u64 range_len = 9; b8 hovered = cur_field_off <= roff && roff < cur_field_off + range_len; if (display_val_rootzip(arena, info.desc, "Zipped Block", data + start + cur_field_off)) { if (display_grouped || hovered) { if (was_zipped) *was_zipped = true; info.rng = { start + cur_field_off, range_len }; if (hovered) info.highlighted_desc = info.desc; // ended = !display_grouped; } cur_field_off += range_len; } else if (was_zipped) { *was_zipped = false; } return true; } void tkey() { titled_section("TKey", [this] { u16 version_be; memcpy(&version_be, data + start + 4, sizeof(u16)); u32 version = bswap(version_be); b8 is_big = version > 1000; field_be<u32>("NBytes: %u"); field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { x = bswap(x); x -= (x > 1000) * 1000; return push_str8_node_child(arena, prev, fmt, x); }); field_be<u32>("Obj Len: %u"); field<u32>("Datetime: ", hover_display_datetime_str); field_be<u16>("Key Len: %u"); field_be<u16>("Cycle: %u"); if (is_big) { field_be<u64>("Seek Key: 0x%" PRIX64); field_be<u64>("Seek Pdir: 0x%" PRIX64); } else { field_be<u32>("Seek Key: 0x%" PRIX64); field_be<u32>("Seek Pdir: 0x%" PRIX64); } field_str8<u8>("Class Name: %s"); field_str8<u8>("Obj Name: %s"); field_str8<u8>("Obj Title: %s"); }, HoverSec_HideIfNotHovered); } void envelope_preamble() { static const char *const envelope_names[] = { "INVALID", "Header", "Footer", "Page List" }; titled_section("Envelope Preamble", [this] { field<u16>("Envelope type: %s", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 val) { const char *name = (val >= countof(envelope_names)) ? "Unknown" : envelope_names[val]; return push_str8_node_child(arena, prev, fmt, name); }); range("Envelope size: %s", 6, [] (Arena *arena, String8_Node *prev, const char *fmt, const u8 *payload) { u64 size; memcpy(&size, payload, 6); return push_str8_node_child(arena, prev, fmt, to_pretty_size(arena, size)); }); }); } enum Frame_Type { Frame_INVALID, Frame_Record, Frame_List }; Frame_Type frame_header(u64 &size, u32 *n_items = nullptr, const char *title = nullptr) { String8 titlestr = title ? push_str8f(arena, "Frame Header: %s", title) : str8("Frame Header"); Frame_Type frame_type = Frame_INVALID; titled_section(titlestr.c(), [this, &frame_type, &frame_size = size, n_items] { i64 size; memcpy(&size, data + start + cur_field_off, sizeof(size)); if (size >= 0) { frame_type = Frame_Record; field<i64>("Record frame size: %" PRIi64 " B", hover_display_val_le_abs<i64>); } else { if (!n_items) { // Since the caller didn't pass us a pointer to n_items, they think we're supposed // to be parsing a record frame. But it turns out this is actually a frame list! // Something fishy is going on, so just bail out. frame_type = Frame_INVALID; } else { frame_type = Frame_List; memcpy(n_items, data + start + cur_field_off + sizeof(i64), sizeof(u32)); titled_section("List Frame", [this] { field<i64>("Size: %" PRIi64 " B", hover_display_val_le_abs<i64>); field_le<u32>("N Items: %u"); }); } } frame_size = std::abs(size); }); return frame_type; } void field_desc(const char *title) { static const char *const field_struct_names[] = { "Leaf", "Collection", "Record", "Variant", "Unsplit" }; frame<Frame_Record>(title, [this] { field_le<u32>("Field version: %u"); field_le<u32>("Type version: %u"); field_le<u32>("On-disk parent id: %u"); field<u16>("Field structure: %s", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 type) { const char *name = (type >= countof(field_struct_names)) ? "Unknown" : field_struct_names[type]; return push_str8_node_child(arena, prev, fmt, name); }); u16 flags; if (!field<u16>("Flags: 0b%b", hover_display_val_le, &flags)) return; if (flags & RNTupleSerializer::kFlagRepetitiveField) field_le<u64>("N Repetitions: %" PRIu64); if (flags & RNTupleSerializer::kFlagProjectedField) field_le<u32>("On disk proj.src id: %u"); if (flags & RNTupleSerializer::kFlagHasTypeChecksum) field_le<u32>("Checksum: %u"); field_str8<u32>("Name: %s"); field_str8<u32>("Type Name: %s"); field_str8<u32>("Type Alias: %s"); field_str8<u32>("Description: %s"); }, HoverSec_HideIfNotHovered); } void column_desc(const char *title) { frame<Frame_Record>(title, [this] { field<u16>("Column type: %s", [](Arena *arena, String8_Node *prev, const char *fmt, u16 val) { const char *readable_col_type = get_column_type_name(val); return push_str8_node_child(arena, prev, fmt, readable_col_type); }); field_le<u16>("Bits on storage: %u"); field_le<u32>("Field ID: %u"); u16 flags; if (!field<u16>("Flags: 0b%b", hover_display_val_le, &flags)) return; field_le<u16>("Representation idx: %u"); if (flags & RNTupleSerializer::kFlagDeferredColumn) { field_le<u64>("First element: %" PRIu64); } // if (flags & RNTupleSerializer::kFlagHasValueRange) { // field_le<double>("Value Min: %f"); // field_le<double>("Value Max: %f"); // } }, HoverSec_HideIfNotHovered); } void schema_description(const char *title) { titled_section(title, [this] { // TODO: Columns and alias columns are not the same frame<Frame_List>("Fields", [this] (u32 idx) { field_desc(push_str8f(arena, "Field %u", idx).c()); }); frame<Frame_List>("Columns", [this] (u32 idx) { column_desc(push_str8f(arena, "Column %u", idx).c()); }); frame<Frame_List>("Alias Columns", [this] (u32 idx) { column_desc(push_str8f(arena, "Alias Column %u", idx).c()); }); frame<Frame_List>("Extra Type Infos", [this] (u32) { field_le<u32>("Content identifier: %lu"); field_le<u32>("Type version from: %lu"); field_le<u32>("Type version to: %lu"); }); }); } void locator(const char *title) { // TODO // return titled_section(title, [this] { // return true; // }); } void cluster_group() { frame<Frame_List>("Cluster Group", [this] (u32) { field_le<u64>("Min Entry: %" PRIu64); field_le<u64>("Entry Span: %" PRIu64); field_le<u32>("N Clusters: %u"); field_le<u64>("Env.Link Len: %" PRIu64); locator("Env.Link Locator"); }); } void rntuple_anchor() { titled_section("RNTuple Anchor", [this] { tkey(); titled_section("Data", [this] { field<u32>("Object len: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) { x = bswap(x); x -= 0x4000'0000; return push_str8_node_child(arena, prev, fmt, x); }); field_be<u16>("Class version: %u"); field_be<u16>("Version Epoch: %u"); field_be<u16>("Version Major: %u"); field_be<u16>("Version Minor: %u"); field_be<u16>("Version Patch: %u"); field_be<u64>("Seek Header: 0x%" PRIX64); field_be<u64>("NBytes Header: %u"); field_be<u64>("Len Header: %u"); field_be<u64>("Seek Footer: 0x%" PRIX64); field_be<u64>("NBytes Footer: %u"); field_be<u64>("Len Footer: %u"); field_be<u64>("Max Key Size: %u"); field_le<u64>("Checksum: 0x%" PRIX64); }, HoverSec_HideIfNotHovered); }); } void rntuple_header() { titled_section("RNTuple Header", [this] { tkey(); titled_section("Data", [this] { b8 zipped; if (!maybe_rootzip(&zipped)) return; if (zipped) { // XXX: why -1? range("Compressed payload", section.range.len - section.post_size - sizeof(u64) - 1); field_le<u64>("Checksum: 0x%" PRIX64); } else { envelope_preamble(); // NOTE: flags in principle require a more complex handling, but for now they are unused, // so they're always occupying only 8 bytes. field_le<u64>("Flags: 0x%" PRIX64); field_str8<u32>("Name: %s"); field_str8<u32>("Description: %s"); field_str8<u32>("ROOT version: %s"); schema_description("Schema Description"); field_le<u64>("Checksum: 0x%" PRIX64); } }, HoverSec_HideIfNotHovered); }); } void rntuple_footer() { titled_section("RNTuple Footer", [this] { tkey(); titled_section("Data", [this] { b8 zipped; if (!maybe_rootzip(&zipped)) return; if (zipped) { // XXX: why -1? range("Payload", section.range.len - section.post_size - sizeof(u64) - 1); field_le<u64>("Checksum: 0x%" PRIX64); } else { envelope_preamble(); // NOTE: flags in principle require a more complex handling, but for now they are unused, // so they're always occupying only 8 bytes. field_le<u64>("Flags: 0x%" PRIX64); field_le<u64>("Header checksum: 0x%" PRIX64); schema_description("Schema Extension"); // - list of column group record frames (TODO) //frame_header("Column Groups"); // - list of cluster group record frames (TODO) //frame_header("Cluster Groups"); range("Payload", section.range.len - cur_field_off); field_le<u64>("Checksum: 0x%" PRIX64); } }, HoverSec_HideIfNotHovered); }); } void cluster_summary() { frame<Frame_Record>("Cluster Summary", [this] { field_le<u64>("First Entry: %" PRIu64); field<u64>("", [] (Arena *arena, String8_Node *prev, const char *, u64 x) { u64 entries = (x << 8) >> 8; u8 flags = x >> 56; String8_Node *sn = push_str8_node_child(arena, prev, "Entries: %" PRIu64, entries); return push_str8_node(arena, sn, "Flags: 0b%b", flags); }); }); } void cluster() { frame<Frame_List>("Cluster", [this] (u32 col_idx) { titled_section(push_str8f(arena, "Column %u", col_idx).c(), [this] { frame<Frame_List>("Pages", [this] (u32 page_idx) { titled_section(push_str8f(arena, "Page %u", page_idx).c(), [this] { field<i32>("", [] (Arena *arena, String8_Node *prev, const char *, i32 n_elems) { b8 has_checksum = n_elems < 0; String8_Node *sn = push_str8_node_child(arena, prev, "N Elements: %u", std::abs(n_elems)); return push_str8_node(arena, sn, "Has Checksum: %s", has_checksum ? "yes" : "no"); }); locator("Element Locator"); }, HoverSec_HideIfNotHovered); }); i64 n_cols; if (!field<i64>("", [] (Arena *arena, String8_Node *prev, const char *, i64 n_cols) { if (n_cols < 0) { return push_str8_node_child(arena, prev, "Element Offset: <suppressed>"); } return push_str8_node_child(arena, prev, "Element Offset: %" PRIi64, n_cols); }, &n_cols)) { return; } if (n_cols >= 0) field_le<i32>("Compression Settings: %d"); }, HoverSec_HideIfNotHovered); }); } template <Frame_Type FType, typename F> void frame(const char *title, F &&frame_body_fn, u64 sec_flags = 0) { u64 start_off = cur_field_off; u64 size; titled_section(title, [this, title, start_off, &size, &frame_body_fn] { u32 n_items = 0; Frame_Type ftype = frame_header(size, &n_items); if (ftype != FType) return; if constexpr (FType == Frame_List) { for (u32 i = 0; i < n_items; ++i) frame_body_fn(i); } else { frame_body_fn(); } assert(cur_field_off >= start_off); u64 allocated_size = cur_field_off - start_off; if (size < allocated_size) { fprintf(stderr, "Frame %s told us its size was %" PRIu64 " but we accounted for %" PRIu64 " bytes!\n", title, size, allocated_size); } u64 extra_size = size - allocated_size; if (extra_size > 0) range("Unknown", extra_size); }, sec_flags); cur_field_off = start_off + size; } void page_list() { titled_section("Page List", [this] { tkey(); titled_section("Data", [this] { b8 zipped; if (!maybe_rootzip(&zipped)) return; if (zipped) { // XXX: why -1? range("Payload", section.range.len - section.post_size - sizeof(u64) - 1); field_le<u64>("Checksum: 0x%" PRIX64); } else { envelope_preamble(); field_le<u64>("Header checksum: 0x%" PRIX64); frame<Frame_List>("Cluster Summaries", [this] (u32) { cluster_summary(); }); frame<Frame_List>("Clusters", [this] (u32) { cluster(); }); } }, HoverSec_HideIfNotHovered); }); } void tfile_header() { titled_section("TFile Header", [this] { u32 root_version_be; memcpy(&root_version_be, data + start + 4, sizeof(u32)); u32 root_version = bswap(root_version_be); b8 is_big = root_version > 1000'000; field_be<u32>("ROOT magic number"); field<u32>("ROOT version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) { x = bswap(x); x -= (x > 1000'000) * 1000'000; return push_str8_node_child(arena, prev, fmt, x); }); field_be<u32>("fBEGIN: 0x%" PRIX64); if (is_big) { field_be<u64>("fEND: 0x%" PRIX64); field_be<u64>("Seek Free: 0x%" PRIX64); } else { field_be<u32>("fEND: 0x%" PRIX64); field_be<u32>("Seek Free: 0x%" PRIX64); } field_be<u32>("NBytes Free: %u"); field_be<u32>("N Free: %u"); field_be<u32>("NBytes Name: %u"); field_be<u8>("Units: %u"); field_be<u32>("Compression: %u"); if (is_big) field_be<u64>("Seek Info: 0x%" PRIX64); else field_be<u32>("Seek Info: 0x%" PRIX64); field_be<u32>("NBytes Info: %u"); range("Padding", section.post_size); }); } void tfile_object() { titled_section("TFile Object", [this] { tkey(); field_str8<u8>("File Name: %s"); field_str8<u8>("File Title: %s"); u16 version_be; memcpy(&version_be, data + cur_field_off, sizeof(u16)); u16 version = bswap(version_be); b8 is_big = version > 1000; field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { x = bswap(x); x -= (x > 1000) * 1000; return push_str8_node_child(arena, prev, fmt, x); }); field<u32>("Created: ", hover_display_datetime_str); field<u32>("Modified: ", hover_display_datetime_str); field_be<u32>("NBytes Key: %u"); field_be<u32>("NBytes Name: %u"); if (is_big) { field_be<u64>("Seek Dir: 0x%" PRIX64) ; field_be<u64>("Seek Parent: 0x%" PRIX64) ; field_be<u64>("Seek Keys: 0x%" PRIX64) ; } else { field_be<u32>("Seek Dir: 0x%" PRIX64) ; field_be<u32>("Seek Parent: 0x%" PRIX64) ; field_be<u32>("Seek Keys: 0x%" PRIX64) ; } field_be<u16>("UUID Vers.Class: %u"); field_le<u16>("UUID: %u"); if (!is_big) range("Padding", 3 * sizeof(u32)); }); } void tfile_info() { titled_section("TFile Info", [this] { tkey(); b8 zipped; if (!maybe_rootzip(&zipped)) return; if (zipped) { range("Compressed Payload", section.range.len); } else { field<u32>("Byte Count: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) { x = bswap(x); x -= 0x400000000; return push_str8_node_child(arena, prev, fmt, x); }); field_be<u16>("Version: %u"); // hover_try_object(hover) field_be<u8>("Name: %u"); field_be<u32>("N Objects: %u");; } }); } void tfile_freelist() { titled_section("TFile FreeList", [this] { tkey(); u16 version_be; memcpy(&version_be, data + start + cur_field_off, sizeof(u16)); u32 version = bswap(version_be); b8 is_big = version > 1000; field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { x = bswap(x); x -= (x > 1000) * 1000; return push_str8_node_child(arena, prev, fmt, x); }); if (is_big) { field_be<u64>("First: 0x%" PRIX64); field_be<u64>("Last: 0x%" PRIX64); } else { field_be<u32>("First: 0x%X"); field_be<u32>("Last: 0x%X"); } }); } }; // `off` is the absolute offset into `data`. internal Sec_Hover_Info get_section_hover_info(Arena *arena, Section section, u64 off, const u8 *data, b8 display_grouped) { Sec_Hover_Info info {}; // printf("off: 0x%" PRIX64 ", sec start - pre_size: (0x%" PRIX64 " - %" PRIu64 ") = 0x%" PRIX64 "\n", off, section.range.start, section.pre_size, section.range.start - section.pre_size); assert(off >= section.range.start - section.pre_size); // Hover info header String8 sec_name = section_names[section.id]; if (section.id == Sec_Page && section.info) { Page_Info_Node *pinfo = (Page_Info_Node *)section.info; info.desc = push_str8_node(arena, nullptr, "%s [%s]", sec_name.c(), pinfo->elem_type_name.c()); push_str8_node_child(arena, info.desc, "Field: %s", pinfo->owner_field_name.c()); push_str8_node_child(arena, info.desc, "N. Elems: %d", abs(pinfo->n_elems)); push_str8_node_child(arena, info.desc, "Bits per elem: %u", pinfo->bits_per_elem); push_str8_node_child(arena, info.desc, "-----------"); } else { info.desc = push_str8_node(arena, nullptr, "%s", sec_name.c()); } u64 start = section.range.start - section.pre_size; u64 roff = off - start; // offset relative to `section` u64 cur_field_off = 0; Sec_Hover_Fn hover { start, roff, data, section, arena, info, cur_field_off, display_grouped }; switch (section.id) { case Sec_RNTuple_Anchor: { hover.rntuple_anchor(); } break; case Sec_TFile_Header: { hover.tfile_header(); } break; case Sec_TFile_Object: { hover.tfile_object(); } break; case Sec_RNTuple_Header: { hover.rntuple_header(); } break; case Sec_RNTuple_Footer: { hover.rntuple_footer(); } break; case Sec_Page_List: { hover.page_list(); } break; #if 0 case Sec_Page: { // only try hovering a key if this is the first page of the cluster (<=> pre_size != 0) b8 ok = section.pre_size && hover.tkey(); ok = ok || hover.maybe_rootzip() || hover.range("Payload", section.range.len - section.post_size) // TODO: improve || hover.field_le<u64>("Checksum: 0x%" PRIX64) ; } break; case Sec_TFile_Info: { hover.tfile_info(); } break; #endif case Sec_TFile_FreeList: { hover.tfile_freelist(); } break; default:; } return info; }