add hover display rootzip
This commit is contained in:
parent
70cd97e92b
commit
b76187e12d
5 changed files with 154 additions and 32 deletions
|
@ -67,7 +67,35 @@ ImColor imcol(const f32 col[3], f32 brighten_amt = 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal
|
internal
|
||||||
u32 mem_edit_bg_color_fn(const u8 *, u64 off, void *user_data)
|
u32 highlight_zstd_header(const u8 *data, u64 off, App_State &app, f32 brighten)
|
||||||
|
{
|
||||||
|
if (app.viewer.highlight_zstd_headers) {
|
||||||
|
static const u32 ZSTD_HEADER = 0xFD2FB528;
|
||||||
|
|
||||||
|
if (app.viewer.last_seen_zstd_header_start > 0) {
|
||||||
|
if (off - app.viewer.last_seen_zstd_header_start <= sizeof(ZSTD_HEADER)) {
|
||||||
|
return imcol(app.viewer.col_highlight, brighten);
|
||||||
|
}
|
||||||
|
app.viewer.last_seen_zstd_header_start = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (off < app.inspected_file.size - sizeof(ZSTD_HEADER) && data[off] == 0x28) {
|
||||||
|
u32 bytes;
|
||||||
|
memcpy(&bytes, data + off, sizeof(ZSTD_HEADER));
|
||||||
|
if (bytes == ZSTD_HEADER) {
|
||||||
|
app.viewer.last_seen_zstd_header_start = off;
|
||||||
|
return imcol(app.viewer.col_highlight, brighten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.viewer.last_seen_zstd_header_start = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal
|
||||||
|
u32 mem_edit_bg_color_fn(const u8 *data, u64 off, void *user_data)
|
||||||
{
|
{
|
||||||
App_State *app = reinterpret_cast<App_State *>(user_data);
|
App_State *app = reinterpret_cast<App_State *>(user_data);
|
||||||
off += app->base_display_addr;
|
off += app->base_display_addr;
|
||||||
|
@ -76,6 +104,10 @@ u32 mem_edit_bg_color_fn(const u8 *, u64 off, void *user_data)
|
||||||
if (app->viewer.hovered_range.start <= off && off < app->viewer.hovered_range.end())
|
if (app->viewer.hovered_range.start <= off && off < app->viewer.hovered_range.end())
|
||||||
brighten = 0.3;
|
brighten = 0.3;
|
||||||
|
|
||||||
|
u32 col = highlight_zstd_header(data, off, *app, brighten);
|
||||||
|
if (app->viewer.last_seen_zstd_header_start)
|
||||||
|
return col;
|
||||||
|
|
||||||
i64 hilite_cluster = app->viewer.highlight_cluster ? app->viewer.highlighted_cluster : -1;
|
i64 hilite_cluster = app->viewer.highlight_cluster ? app->viewer.highlighted_cluster : -1;
|
||||||
Section section = find_section(*app, off, hilite_cluster);
|
Section section = find_section(*app, off, hilite_cluster);
|
||||||
|
|
||||||
|
@ -186,6 +218,26 @@ void viewer_jump_to_cluster(App_State &app, u64 cluster_idx)
|
||||||
viewer_jump_to(app, page->range.start);
|
viewer_jump_to(app, page->range.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal
|
||||||
|
void imgui_render_string_tree(Arena *arena, String8_Node *root, u16 indent = 0)
|
||||||
|
{
|
||||||
|
String8 indent_str;
|
||||||
|
if (indent) {
|
||||||
|
indent_str = { arena_push_array_nozero<u8>(arena, indent + 1), indent };
|
||||||
|
indent_str.str[indent] = 0;
|
||||||
|
memset(indent_str.str, ' ', indent);
|
||||||
|
} else {
|
||||||
|
indent_str = str8("");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String8_Node *node = root; node; node = node->next) {
|
||||||
|
ImGui::Text("%s%s", indent_str.c(), node->str.c());
|
||||||
|
|
||||||
|
for (String8_Node *child = node->first_child; child; child = child->next)
|
||||||
|
imgui_render_string_tree(arena, child, indent + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal
|
internal
|
||||||
void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
|
void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
|
||||||
{
|
{
|
||||||
|
@ -327,6 +379,7 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Highlight cluster", &app.viewer.highlight_cluster);
|
ImGui::Checkbox("Highlight cluster", &app.viewer.highlight_cluster);
|
||||||
}
|
}
|
||||||
|
ImGui::Checkbox("Highlight ZSTD headers", &app.viewer.highlight_zstd_headers);
|
||||||
|
|
||||||
// ---------------------------------
|
// ---------------------------------
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
@ -335,17 +388,7 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
|
||||||
{
|
{
|
||||||
Section hovered_section = find_section(app, app.viewer.hovered_off - 1);
|
Section hovered_section = find_section(app, app.viewer.hovered_off - 1);
|
||||||
Sec_Hover_Info hover_info = get_section_hover_info(scratch.arena, hovered_section, app.viewer.hovered_off - 1, app.inspected_file.mem);
|
Sec_Hover_Info hover_info = get_section_hover_info(scratch.arena, hovered_section, app.viewer.hovered_off - 1, app.inspected_file.mem);
|
||||||
u8 indent = 0;
|
imgui_render_string_tree(scratch.arena, hover_info.desc);
|
||||||
for (String8_Node *node = hover_info.desc; node; node = node->next) {
|
|
||||||
String8 fmt = str8("%s");
|
|
||||||
if (indent) {
|
|
||||||
// create indent
|
|
||||||
fmt = push_str8f(scratch.arena, "%%%ds", indent + node->str.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Text(fmt.c(), node->str.c());
|
|
||||||
indent += 2;
|
|
||||||
}
|
|
||||||
app.viewer.hovered_range = hover_info.rng;
|
app.viewer.hovered_range = hover_info.rng;
|
||||||
} else {
|
} else {
|
||||||
app.viewer.hovered_range = {};
|
app.viewer.hovered_range = {};
|
||||||
|
|
|
@ -12,6 +12,9 @@ struct Viewer {
|
||||||
|
|
||||||
b8 highlight_cluster;
|
b8 highlight_cluster;
|
||||||
u64 highlighted_cluster;
|
u64 highlighted_cluster;
|
||||||
|
|
||||||
|
b8 highlight_zstd_headers;
|
||||||
|
u64 last_seen_zstd_header_start;
|
||||||
|
|
||||||
u64 latest_page_gone_to;
|
u64 latest_page_gone_to;
|
||||||
u64 latest_key_gone_to;
|
u64 latest_key_gone_to;
|
||||||
|
|
|
@ -471,6 +471,7 @@ Section find_section(App_State &app, u64 off, i64 hilite_cluster = -1)
|
||||||
|
|
||||||
struct Sec_Hover_Info {
|
struct Sec_Hover_Info {
|
||||||
Byte_Range rng;
|
Byte_Range rng;
|
||||||
|
// A string tree where children are more indented than parents
|
||||||
String8_Node *desc;
|
String8_Node *desc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -483,19 +484,19 @@ template <typename T>
|
||||||
String8_Node *hover_display_val_be(Arena *arena, String8_Node *prev, const char *fmt, T val)
|
String8_Node *hover_display_val_be(Arena *arena, String8_Node *prev, const char *fmt, T val)
|
||||||
{
|
{
|
||||||
val = bswap_if_needed(val);
|
val = bswap_if_needed(val);
|
||||||
return push_str8_node(arena, prev, fmt, val);
|
return push_str8_node_child(arena, prev, fmt, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
String8_Node *hover_display_val_be(Arena *arena, String8_Node *prev, const char *fmt, String8 val)
|
String8_Node *hover_display_val_be(Arena *arena, String8_Node *prev, const char *fmt, String8 val)
|
||||||
{
|
{
|
||||||
return push_str8_node(arena, prev, fmt, val.c());
|
return push_str8_node_child(arena, prev, fmt, val.c());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
String8_Node *hover_display_val_le(Arena *arena, String8_Node *prev, const char *fmt, T val)
|
String8_Node *hover_display_val_le(Arena *arena, String8_Node *prev, const char *fmt, T val)
|
||||||
{
|
{
|
||||||
return push_str8_node(arena, prev, fmt, val);
|
return push_str8_node_child(arena, prev, fmt, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal
|
internal
|
||||||
|
@ -511,7 +512,39 @@ String8_Node *hover_display_datetime_str(Arena *arena, String8_Node *prev, const
|
||||||
u32 hour = (datetime & 0x1'ffff) >> 12;
|
u32 hour = (datetime & 0x1'ffff) >> 12;
|
||||||
u32 min = (datetime & 0xfff) >> 6;
|
u32 min = (datetime & 0xfff) >> 6;
|
||||||
u32 sec = datetime & 0x3f;
|
u32 sec = datetime & 0x3f;
|
||||||
return push_str8_node(arena, prev, "%s%u/%02u/%02u %02u:%02u:%02u", fmt_pre, year, month, day, hour, min, sec);
|
return push_str8_node_child(arena, prev, "%s%u/%02u/%02u %02u:%02u:%02u", fmt_pre, year, month, day, hour, min, sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
if (src[0] == 'C' && src[1] == 'S' && src[2] == Z_DEFLATED) {
|
||||||
|
zip_method = str8("Old");
|
||||||
|
}
|
||||||
|
if (src[0] == 'X' && src[1] == 'Z' && src[2] == 0) {
|
||||||
|
zip_method = str8("LZMA");
|
||||||
|
}
|
||||||
|
if (src[0] == 'L' && src[1] == '4') {
|
||||||
|
zip_method = str8("LZ4");
|
||||||
|
}
|
||||||
|
if (src[0] == 'Z' && src[1] == 'S' && src[2] == 1) {
|
||||||
|
zip_method = str8("ZSTD");
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
|
||||||
|
return sn;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functor used by get_section_hover_info to describe the structure of a section and print data about it.
|
// Functor used by get_section_hover_info to describe the structure of a section and print data about it.
|
||||||
|
@ -524,9 +557,9 @@ struct Try_Sec_Hover_Fn {
|
||||||
u64 &cur_field_off;
|
u64 &cur_field_off;
|
||||||
|
|
||||||
template <typename TField_Type>
|
template <typename TField_Type>
|
||||||
bool field(const char *desc_fmt,
|
b8 field(const char *desc_fmt,
|
||||||
String8_Node *(*display_val)(Arena *, String8_Node *, const char *, TField_Type) = hover_display_val_be<TField_Type>
|
String8_Node *(*display_val)(Arena *, String8_Node *, const char *, TField_Type) = hover_display_val_be<TField_Type>
|
||||||
) const
|
) const
|
||||||
{
|
{
|
||||||
u64 field_len = sizeof(TField_Type);
|
u64 field_len = sizeof(TField_Type);
|
||||||
if (roff < cur_field_off + field_len) {
|
if (roff < cur_field_off + field_len) {
|
||||||
|
@ -541,9 +574,9 @@ struct Try_Sec_Hover_Fn {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
bool field<String8>(const char *desc_fmt,
|
b8 field<String8>(const char *desc_fmt,
|
||||||
String8_Node *(*display_val)(Arena *, String8_Node *, const char *, String8)
|
String8_Node *(*display_val)(Arena *, String8_Node *, const char *, String8)
|
||||||
) const
|
) const
|
||||||
{
|
{
|
||||||
u8 str_size = data[start + cur_field_off];
|
u8 str_size = data[start + cur_field_off];
|
||||||
if (roff < cur_field_off + 1 + str_size) {
|
if (roff < cur_field_off + 1 + str_size) {
|
||||||
|
@ -559,11 +592,24 @@ struct Try_Sec_Hover_Fn {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool range(const char *desc, u64 range_len) const
|
b8 range(const char *desc, u64 range_len) const
|
||||||
{
|
{
|
||||||
if (roff < cur_field_off + range_len) {
|
if (roff < cur_field_off + range_len) {
|
||||||
info.rng = { start + cur_field_off, range_len };
|
info.rng = { start + cur_field_off, range_len };
|
||||||
push_str8_node(arena, info.desc, "%s", desc);
|
push_str8_node_child(arena, info.desc, "%s", desc);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
cur_field_off += range_len;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
b8 range_data(const char *desc, u64 range_len,
|
||||||
|
String8_Node *(*display_val)(Arena *, String8_Node *, const char *, const u8 *)
|
||||||
|
) const
|
||||||
|
{
|
||||||
|
if (roff < cur_field_off + range_len) {
|
||||||
|
info.rng = { start + cur_field_off, range_len };
|
||||||
|
display_val(arena, info.desc, desc, data + start + cur_field_off);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
cur_field_off += range_len;
|
cur_field_off += range_len;
|
||||||
|
@ -572,7 +618,7 @@ struct Try_Sec_Hover_Fn {
|
||||||
};
|
};
|
||||||
|
|
||||||
internal
|
internal
|
||||||
bool hover_try_key(const Try_Sec_Hover_Fn &try_sec_hover, const u8 *data, u64 start)
|
b8 hover_try_key(const Try_Sec_Hover_Fn &try_sec_hover, const u8 *data, u64 start)
|
||||||
{
|
{
|
||||||
u16 version_be;
|
u16 version_be;
|
||||||
memcpy(&version_be, data + start + 4, sizeof(u16));
|
memcpy(&version_be, data + start + 4, sizeof(u16));
|
||||||
|
@ -584,7 +630,7 @@ bool hover_try_key(const Try_Sec_Hover_Fn &try_sec_hover, const u8 *data, u64 st
|
||||||
|| try_sec_hover.field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) {
|
|| try_sec_hover.field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) {
|
||||||
x = bswap(x);
|
x = bswap(x);
|
||||||
x -= (x > 1000) * 1000;
|
x -= (x > 1000) * 1000;
|
||||||
return push_str8_node(arena, prev, fmt, x);
|
return push_str8_node_child(arena, prev, fmt, x);
|
||||||
})
|
})
|
||||||
|| try_sec_hover.field<u32>("Obj Len: %u")
|
|| try_sec_hover.field<u32>("Obj Len: %u")
|
||||||
|| try_sec_hover.field<u32>("Datetime: ", hover_display_datetime_str)
|
|| try_sec_hover.field<u32>("Datetime: ", hover_display_datetime_str)
|
||||||
|
@ -601,7 +647,7 @@ bool hover_try_key(const Try_Sec_Hover_Fn &try_sec_hover, const u8 *data, u64 st
|
||||||
|| try_sec_hover.field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) {
|
|| try_sec_hover.field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) {
|
||||||
x = bswap(x);
|
x = bswap(x);
|
||||||
x -= (x > 1000) * 1000;
|
x -= (x > 1000) * 1000;
|
||||||
return push_str8_node(arena, prev, fmt, x);
|
return push_str8_node_child(arena, prev, fmt, x);
|
||||||
})
|
})
|
||||||
|| try_sec_hover.field<u32>("Obj Len: %u")
|
|| try_sec_hover.field<u32>("Obj Len: %u")
|
||||||
|| try_sec_hover.field<u32>("Datetime: ", hover_display_datetime_str)
|
|| try_sec_hover.field<u32>("Datetime: ", hover_display_datetime_str)
|
||||||
|
@ -616,6 +662,14 @@ bool hover_try_key(const Try_Sec_Hover_Fn &try_sec_hover, const u8 *data, u64 st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal
|
||||||
|
b8 hover_try_rootzip(const Try_Sec_Hover_Fn &try_sec_hover, const u8 *data, u64 start)
|
||||||
|
{
|
||||||
|
// TODO boundary checks
|
||||||
|
|
||||||
|
return try_sec_hover.range_data("Zipped Block", 9, display_val_rootzip);
|
||||||
|
}
|
||||||
|
|
||||||
// `off` is the absolute offset into `data`.
|
// `off` is the absolute offset into `data`.
|
||||||
internal
|
internal
|
||||||
Sec_Hover_Info get_section_hover_info(Arena *arena, Section section, u64 off, const u8 *data)
|
Sec_Hover_Info get_section_hover_info(Arena *arena, Section section, u64 off, const u8 *data)
|
||||||
|
@ -638,7 +692,7 @@ Sec_Hover_Info get_section_hover_info(Arena *arena, Section section, u64 off, co
|
||||||
|| try_sec_hover.field<u32>("Object len: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) {
|
|| try_sec_hover.field<u32>("Object len: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) {
|
||||||
x = bswap(x);
|
x = bswap(x);
|
||||||
x -= 0x4000'0000;
|
x -= 0x4000'0000;
|
||||||
return push_str8_node(arena, prev, fmt, x);
|
return push_str8_node_child(arena, prev, fmt, x);
|
||||||
})
|
})
|
||||||
|| try_sec_hover.field<u16>("Class version: %u")
|
|| try_sec_hover.field<u16>("Class version: %u")
|
||||||
|| try_sec_hover.field<u16>("Version Epoch: %u")
|
|| try_sec_hover.field<u16>("Version Epoch: %u")
|
||||||
|
@ -667,7 +721,7 @@ Sec_Hover_Info get_section_hover_info(Arena *arena, Section section, u64 off, co
|
||||||
|| try_sec_hover.field<u32>("ROOT version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) {
|
|| try_sec_hover.field<u32>("ROOT version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) {
|
||||||
x = bswap(x);
|
x = bswap(x);
|
||||||
x -= 1000000;
|
x -= 1000000;
|
||||||
return push_str8_node(arena, prev, fmt, x);
|
return push_str8_node_child(arena, prev, fmt, x);
|
||||||
})
|
})
|
||||||
|| try_sec_hover.field<u32>("fBEGIN: 0x%lX")
|
|| try_sec_hover.field<u32>("fBEGIN: 0x%lX")
|
||||||
|| try_sec_hover.field<u64>("fEND: 0x%lX")
|
|| try_sec_hover.field<u64>("fEND: 0x%lX")
|
||||||
|
@ -714,7 +768,7 @@ Sec_Hover_Info get_section_hover_info(Arena *arena, Section section, u64 off, co
|
||||||
ok = ok || try_sec_hover.field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) {
|
ok = ok || try_sec_hover.field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) {
|
||||||
x = bswap(x);
|
x = bswap(x);
|
||||||
x -= 1000;
|
x -= 1000;
|
||||||
return push_str8_node(arena, prev, fmt, x);
|
return push_str8_node_child(arena, prev, fmt, x);
|
||||||
})
|
})
|
||||||
|| try_sec_hover.field<u32>("Created: ", hover_display_datetime_str)
|
|| try_sec_hover.field<u32>("Created: ", hover_display_datetime_str)
|
||||||
|| try_sec_hover.field<u32>("Modified: ", hover_display_datetime_str)
|
|| try_sec_hover.field<u32>("Modified: ", hover_display_datetime_str)
|
||||||
|
@ -748,6 +802,7 @@ Sec_Hover_Info get_section_hover_info(Arena *arena, Section section, u64 off, co
|
||||||
case Sec_Page_List:
|
case Sec_Page_List:
|
||||||
case Sec_Page: {
|
case Sec_Page: {
|
||||||
hover_try_key(try_sec_hover, data, start)
|
hover_try_key(try_sec_hover, data, start)
|
||||||
|
|| hover_try_rootzip(try_sec_hover, data, start)
|
||||||
|| try_sec_hover.range("Payload", section.range.len - section.post_size) // TODO: improve
|
|| try_sec_hover.range("Payload", section.range.len - section.post_size) // TODO: improve
|
||||||
|| try_sec_hover.field<u64>("Checksum: 0x%lX", hover_display_val_le)
|
|| try_sec_hover.field<u64>("Checksum: 0x%lX", hover_display_val_le)
|
||||||
;
|
;
|
||||||
|
|
23
src/str.cpp
23
src/str.cpp
|
@ -38,7 +38,6 @@ String8 to_pretty_size(Arena *arena, u64 bytes)
|
||||||
return push_str8f(arena, "%zu B", bytes);
|
return push_str8f(arena, "%zu B", bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal
|
internal
|
||||||
String8_Node *push_str8_node(Arena *arena, String8_Node *prev, const char *fmt, ...)
|
String8_Node *push_str8_node(Arena *arena, String8_Node *prev, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
|
@ -54,3 +53,25 @@ String8_Node *push_str8_node(Arena *arena, String8_Node *prev, const char *fmt,
|
||||||
|
|
||||||
return snode;
|
return snode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal
|
||||||
|
String8_Node *push_str8_node_child(Arena *arena, String8_Node *parent, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
assert(parent);
|
||||||
|
|
||||||
|
String8_Node *snode = arena_push<String8_Node>(arena);
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
snode->str = push_str8fv(arena, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
if (parent->first_child) {
|
||||||
|
parent->last_child->next = snode;
|
||||||
|
parent->last_child = snode;
|
||||||
|
} else {
|
||||||
|
parent->last_child = parent->first_child = snode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return snode;
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,6 @@ struct String8 {
|
||||||
|
|
||||||
struct String8_Node {
|
struct String8_Node {
|
||||||
String8_Node *next;
|
String8_Node *next;
|
||||||
|
String8_Node *first_child, *last_child;
|
||||||
String8 str;
|
String8 str;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue