rizin/librz/util/print.c
Khairul Azhar Kasmiran 3204f0c405
Use stdint types for ut64 and friends (#6276)
* Use `stdint` types for `ut64` and friends
* Use `inttypes.h` format specifiers
2026-04-22 21:43:04 +08:00

1564 lines
40 KiB
C

// SPDX-FileCopyrightText: 2007-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include <ctype.h>
#include <rz_util/rz_str.h>
#include <rz_list.h>
#include <rz_util/rz_regex.h>
#include <rz_types.h>
#include <rz_util/rz_assert.h>
#include <rz_util/rz_log.h>
#include <rz_util/rz_strbuf.h>
#include <rz_vector.h>
#include <rz_util/rz_print.h>
#define RZ_NO_ESIL 1
#include <rz_analysis.h>
#undef RZ_NO_ESIL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DFLT_ROWS 16
static const char hex[16] = "0123456789ABCDEF";
static int nullprinter(const char *a, ...) {
return 0;
}
static int libc_printf(const char *format, ...) {
va_list ap;
va_start(ap, format);
vprintf(format, ap);
va_end(ap);
return 0;
}
static RzPrintIsInterruptedCallback is_interrupted_cb = NULL;
RZ_API bool rz_print_is_interrupted(void) {
if (is_interrupted_cb) {
return is_interrupted_cb();
}
return false;
}
RZ_API void rz_print_set_is_interrupted_cb(RzPrintIsInterruptedCallback cb) {
is_interrupted_cb = cb;
}
RZ_API RzPrint *rz_print_new(void) {
RzPrint *p = RZ_NEW0(RzPrint);
if (!p) {
return NULL;
}
strcpy(p->datefmt, "%Y-%m-%d %H:%M:%S %z");
rz_io_bind_init(p->iob);
p->pairs = true;
p->resetbg = true;
p->cb_printf = libc_printf;
p->oprintf = nullprinter;
p->bits = 32;
p->stride = 0;
p->bytespace = 0;
p->big_endian = false;
p->datezone = 0;
p->col = 0;
p->width = 78;
p->cols = 16;
p->cur_enabled = false;
p->cur = p->ocur = -1;
p->flags =
RZ_PRINT_FLAGS_COLOR |
RZ_PRINT_FLAGS_OFFSET |
RZ_PRINT_FLAGS_HEADER;
p->seggrn = 4;
p->zoom = RZ_NEW0(RzPrintZoom);
p->reg = NULL;
p->get_register = NULL;
p->get_register_value = NULL;
p->calc_row_offsets = true;
p->row_offsets_sz = 0;
p->row_offsets = NULL;
p->vflush = true;
p->screen_bounds = 0;
p->esc_bslash = false;
p->strconv_mode = NULL;
memset(&p->consbind, 0, sizeof(p->consbind));
p->io_unalloc_ch = '.';
return p;
}
RZ_API RzPrint *rz_print_free(RzPrint *p) {
if (!p) {
return NULL;
}
RZ_FREE(p->strconv_mode);
if (p->zoom) {
free(p->zoom->buf);
free(p->zoom);
p->zoom = NULL;
}
RZ_FREE(p->row_offsets);
free(p);
return NULL;
}
// dummy setter can be removed
RZ_API void rz_print_set_flags(RzPrint *p, int _flags) {
p->flags = _flags;
}
RZ_API void rz_print_set_cursor(RzPrint *p, int enable, int ocursor, int cursor) {
if (!p) {
return;
}
p->cur_enabled = enable;
p->ocur = ocursor;
if (cursor < 0) {
cursor = 0;
}
p->cur = cursor;
}
RZ_API bool rz_print_have_cursor(RzPrint *p, int cur, int len) {
if (!p || !p->cur_enabled) {
return false;
}
if (p->ocur != -1) {
int from = p->ocur;
int to = p->cur;
rz_num_minmax_swap_i(&from, &to);
do {
if (cur + len - 1 >= from && cur + len - 1 <= to) {
return true;
}
} while (--len);
} else if (p->cur >= cur && p->cur <= cur + len - 1) {
return true;
}
return false;
}
RZ_API bool rz_print_cursor_pointer(RzPrint *p, int cur, int len) {
rz_return_val_if_fail(p, false);
if (!p->cur_enabled) {
return false;
}
int to = p->cur;
do {
if (cur + len - 1 == to) {
return true;
}
} while (--len);
return false;
}
RZ_API char *rz_print_hexpair(RzPrint *p, const char *str, int n) {
const char *s, *lastcol = Color_WHITE;
char *d, *dst = (char *)calloc((strlen(str) + 2), 32);
int colors = p->flags & RZ_PRINT_FLAGS_COLOR;
const char *color_0x00 = "";
const char *color_0x7f = "";
const char *color_0xff = "";
const char *color_text = "";
const char *color_other = "";
int bs = p->bytespace;
/* XXX That is hacky but it partially works */
/* TODO: Use rz_print_set_cursor for win support */
int cur = RZ_MIN(p->cur, p->ocur);
int ocur = RZ_MAX(p->cur, p->ocur);
int ch, i;
if (colors) {
#define P(x) (p->cons && p->cons->context->pal.x) ? p->cons->context->pal.x
color_0x00 = P(b0x00)
: Color_GREEN;
color_0x7f = P(b0x7f)
: Color_YELLOW;
color_0xff = P(b0xff)
: Color_RED;
color_text = P(btext)
: Color_MAGENTA;
color_other = P(other)
: "";
}
if (p->cur_enabled && cur == -1) {
cur = ocur;
}
ocur++;
d = dst;
// XXX: overflow here
// TODO: Use rz_cons primitives here
#define memcat(x, y) \
{ \
memcpy(x, y, strlen(y)); \
(x) += strlen(y); \
}
for (s = str, i = 0; s[0]; i++) {
int d_inc = 2;
if (p->cur_enabled) {
if (i == ocur - n) {
memcat(d, Color_RESET);
}
if (colors) {
memcat(d, lastcol);
}
if (i >= cur - n && i < ocur - n) {
memcat(d, Color_INVERT);
}
}
if (colors) {
if (s[0] == '0' && s[1] == '0') {
lastcol = color_0x00;
} else if (s[0] == '7' && s[1] == 'f') {
lastcol = color_0x7f;
} else if (s[0] == 'f' && s[1] == 'f') {
lastcol = color_0xff;
} else {
ch = rz_hex_pair2bin(s);
if (ch == -1) {
break;
}
if (IS_PRINTABLE(ch)) {
lastcol = color_text;
} else {
lastcol = color_other;
}
}
memcat(d, lastcol);
}
if (s[0] == '.') {
d_inc = 1;
}
memcpy(d, s, d_inc);
d += d_inc;
s += d_inc;
if (bs) {
memcat(d, " ");
}
}
if (colors || p->cur_enabled) {
if (p->resetbg) {
memcat(d, Color_RESET);
} else {
memcat(d, Color_RESET_NOBG);
}
}
*d = '\0';
return dst;
}
#define P(x) (p->cons && p->cons->context->pal.x) ? p->cons->context->pal.x
RZ_API const char *rz_print_byte_color(RzPrint *p, int ch) {
const bool use_color = p->flags & RZ_PRINT_FLAGS_COLOR;
if (!use_color) {
return NULL;
}
switch (ch) {
case 0x00: return P(b0x00)
: Color_GREEN;
case 0x7F: return P(b0x7f)
: Color_YELLOW;
case 0xFF: return P(b0xff)
: Color_RED;
default: return IS_PRINTABLE(ch) ? P(btext) : Color_MAGENTA : P(other)
: Color_WHITE;
}
return NULL;
}
static bool checkSparse(const ut8 *p, int len, int ch) {
int i;
ut8 q = *p;
if (ch && ch != q) {
return false;
}
for (i = 1; i < len; i++) {
if (p[i] != q) {
return false;
}
}
return true;
}
static bool isAllZeros(const ut8 *buf, int len) {
int i;
for (i = 0; i < len; i++) {
if (buf[i] != 0) {
return false;
}
}
return true;
}
#define Pal(x, y) (x->cons && x->cons->context->pal.y) ? x->cons->context->pal.y
RZ_API void rz_print_hexii(RzPrint *rp, ut64 addr, const ut8 *buf, int len, int step) {
PrintfCallback p = (PrintfCallback)rp->cb_printf;
bool c = rp->flags & RZ_PRINT_FLAGS_COLOR;
const char *color_0xff = c ? (Pal(rp, b0xff) : Color_RED)
: "";
const char *color_text = c ? (Pal(rp, btext) : Color_MAGENTA)
: "";
const char *color_other = c ? (Pal(rp, other) : Color_WHITE)
: "";
const char *color_reset = c ? Color_RESET : "";
int i, j;
bool show_offset = rp->show_offset;
if (rp->flags & RZ_PRINT_FLAGS_HEADER) {
p(" ");
for (i = 0; i < step; i++) {
p("%3X", i);
}
p("\n");
}
for (i = 0; i < len; i += step) {
int inc = RZ_MIN(step, (len - i));
if (isAllZeros(buf + i, inc)) {
continue;
}
if (show_offset) {
p("%8" PFMT64x ":", addr + i);
}
for (j = 0; j < inc; j++) {
ut8 ch = buf[i + j];
if (ch == 0x00) {
p(" ");
} else if (ch == 0xff) {
p("%s ##%s", color_0xff, color_reset);
} else if (IS_PRINTABLE(ch) && !(rp->flags & RZ_PRINT_FLAGS_NODOT)) {
p("%s .%c%s", color_text, ch, color_reset);
} else if (IS_PRINTABLE(ch) && (rp->flags & RZ_PRINT_FLAGS_NODOT)) {
p("%s %c%s", color_text, ch, color_reset);
} else {
p("%s %02x%s", color_other, ch, color_reset);
}
}
p("\n");
}
p("%8" PFMT64x ": ]\n", addr + i);
}
/**
* \brief Sets screen_bounds member of \p p to \p addr if the cursor is not visible on the screen.
* \param p RzPrint instance
* \param addr Address to set screen_bounds if not visible
*
* This function will only calculate the screen_bounds if the current value of screen_bounds is 1.
* It caches the number of rows visible on the first call to this function. The cache is invalidated when
* screen_bounds is set.
*/
RZ_API void rz_print_set_screenbounds(RzPrint *p, ut64 addr) {
rz_return_if_fail(p);
if (!p->screen_bounds) {
return;
}
if (!p->consbind.get_size) {
return;
}
if (!p->consbind.get_cursor) {
return;
}
if (p->screen_bounds == 1) {
int rc;
if (!p->rows) {
(void)p->consbind.get_size(&p->rows);
}
(void)p->consbind.get_cursor(&rc);
if (rc > p->rows - 1) {
p->screen_bounds = addr;
p->rows = 0;
}
}
}
static inline void print_addr(RzStrBuf *sb, RzPrint *p, ut64 addr) {
char space[32] = {
0
};
const char *white = "";
char *allocated = NULL;
#define PREOFF(x) (p && p->cons && p->cons->context && p->cons->context->pal.x) ? p->cons->context->pal.x
bool use_segoff = p ? (p->flags & RZ_PRINT_FLAGS_SEGOFF) : false;
bool use_color = p ? (p->flags & RZ_PRINT_FLAGS_COLOR) : false;
bool dec = p ? (p->flags & RZ_PRINT_FLAGS_ADDRDEC) : false;
char ch = ' ';
if (p && p->flags & RZ_PRINT_FLAGS_COMPACT && p->col == 1) {
ch = '|';
}
if (p && p->pava) {
ut64 va = p->iob.p2v(p->iob.io, addr);
if (va != UT64_MAX) {
addr = va;
}
}
if (use_segoff) {
ut32 s, a;
a = addr & 0xffff;
s = (addr - a) >> (p ? p->seggrn : 0);
if (dec) {
rz_strf(space, "%d:%d", s & 0xffff, a & 0xffff);
white = allocated = rz_str_pad(' ', 9 - strlen(space));
}
if (use_color) {
const char *pre = PREOFF(offset)
: Color_GREEN;
const char *fin = Color_RESET;
if (dec) {
rz_strbuf_appendf(sb, "%s%s%s%s%c", pre, white, space, fin, ch);
} else {
rz_strbuf_appendf(sb, "%s%04x:%04x%s%c", pre, s & 0xffff, a & 0xffff, fin, ch);
}
} else {
if (dec) {
rz_strbuf_appendf(sb, "%s%s%c", white, space, ch);
} else {
rz_strbuf_appendf(sb, "%04x:%04x%c", s & 0xffff, a & 0xffff, ch);
}
}
} else {
if (dec) {
rz_strf(space, "%" PFMT64d, addr);
int w = RZ_MAX(10 - strlen(space), 0);
white = allocated = rz_str_pad(' ', w);
}
if (use_color) {
const char *pre = PREOFF(offset)
: Color_GREEN;
const char *fin = Color_RESET;
if (dec) {
rz_strbuf_appendf(sb, "%s%s%" PFMT64d "%s%c", pre, white, addr, fin, ch);
} else {
if (p && p->wide_offsets) {
// TODO: make %016 depend on asm.bits
rz_strbuf_appendf(sb, "%s0x%016" PFMT64x "%s%c", pre, addr, fin, ch);
} else {
rz_strbuf_appendf(sb, "%s0x%08" PFMT64x "%s%c", pre, addr, fin, ch);
}
}
} else {
if (dec) {
rz_strbuf_appendf(sb, "%s%" PFMT64d "%c", white, addr, ch);
} else {
if (p && p->wide_offsets) {
// TODO: make %016 depend on asm.bits
rz_strbuf_appendf(sb, "0x%016" PFMT64x "%c", addr, ch);
} else {
rz_strbuf_appendf(sb, "0x%08" PFMT64x "%c", addr, ch);
}
}
}
}
free(allocated);
}
RZ_API void rz_print_addr(RzPrint *p, ut64 addr) {
rz_return_if_fail(p);
RzStrBuf sb;
rz_strbuf_init(&sb);
print_addr(&sb, p, addr);
char *s = rz_strbuf_drain_nofree(&sb);
p->cb_printf("%s", s);
free(s);
}
static inline void print_section(RzStrBuf *sb, RzPrint *p, ut64 at) {
bool use_section = p && p->flags & RZ_PRINT_FLAGS_SECTION;
if (!use_section) {
return;
}
const char *s = p->get_section_name(p->user, at);
if (!s) {
s = "";
}
rz_strbuf_appendf(sb, "%20s ", s);
}
RZ_API char *rz_print_section_str(RzPrint *p, ut64 at) {
rz_return_val_if_fail(p, NULL);
RzStrBuf sb;
rz_strbuf_init(&sb);
print_section(&sb, p, at);
return rz_strbuf_drain_nofree(&sb);
}
static inline void print_cursor_l(RzStrBuf *sb, RzPrint *p, int cur, int len) {
if (rz_print_have_cursor(p, cur, len)) {
rz_strbuf_append(sb, RZ_CONS_INVERT(1, 1));
}
}
static inline void print_cursor_r(RzStrBuf *sb, RzPrint *p, int cur, int len) {
if (rz_print_have_cursor(p, cur, len)) {
rz_strbuf_append(sb, RZ_CONS_INVERT(0, 1));
}
}
static inline void print_byte(RzStrBuf *sb, RzPrint *p, const char *fmt, int idx, ut8 ch) {
ut8 rch = ch;
if (!IS_PRINTABLE(ch) && fmt[0] == '%' && fmt[1] == 'c') {
rch = '.';
}
print_cursor_l(sb, p, idx, 1);
if (p && p->flags & RZ_PRINT_FLAGS_COLOR) {
const char *bytecolor = rz_print_byte_color(p, ch);
if (bytecolor) {
rz_strbuf_append(sb, bytecolor);
}
rz_strbuf_appendf(sb, fmt, rch);
if (bytecolor) {
rz_strbuf_append(sb, Color_RESET);
}
} else {
rz_strbuf_appendf(sb, fmt, rch);
}
print_cursor_r(sb, p, idx, 1);
}
RZ_API void rz_print_byte(RzPrint *p, const char *fmt, int idx, ut8 ch) {
rz_return_if_fail(p && fmt);
RzStrBuf sb;
rz_strbuf_init(&sb);
print_byte(&sb, p, fmt, idx, ch);
char *s = rz_strbuf_drain_nofree(&sb);
p->cb_printf("%s", s);
free(s);
}
/**
* \brief Prints a hexdump of \p buf at \p addr.
* \param p RzPrint instance
* \param addr Address of the buffer
* \param buf Buffer to print
* \param len Print only this many bytes
* \param base Byte print format ? (-10,-1,8,10,16,32,64)
* \param step Word size ?
* \param zoomsz Zoom size ?
* \return Hexdump string
*/
RZ_API RZ_OWN char *rz_print_hexdump_str(RZ_NONNULL RzPrint *p, ut64 addr, RZ_NONNULL const ut8 *buf,
int len, int base, int step, size_t zoomsz) {
rz_return_val_if_fail(p && buf && len > 0, NULL);
RzStrBuf *sb = rz_strbuf_new(NULL);
if (!sb) {
return NULL;
}
bool pairs = p->pairs;
bool use_sparse = p->flags & RZ_PRINT_FLAGS_SPARSE;
bool use_header = p->flags & RZ_PRINT_FLAGS_HEADER;
bool use_hdroff = p->flags & RZ_PRINT_FLAGS_HDROFF;
bool use_segoff = p->flags & RZ_PRINT_FLAGS_SEGOFF;
bool use_align = p->flags & RZ_PRINT_FLAGS_ALIGN;
bool use_offset = p->flags & RZ_PRINT_FLAGS_OFFSET;
bool hex_style = p->flags & RZ_PRINT_FLAGS_STYLE;
bool use_hexa = !(p->flags & RZ_PRINT_FLAGS_NONHEX);
bool use_unalloc = p->flags & RZ_PRINT_FLAGS_UNALLOC;
bool compact = p->flags & RZ_PRINT_FLAGS_COMPACT;
int inc = p->cols; // row width
int col = p->col; // selected column (0=none, 1=hex, 2=ascii)
int stride = p->stride;
size_t i, j;
int sparse_char = 0;
const char *bytefmt = "%02x";
const char *pre = "";
int last_sparse = 0;
const char *a, *b;
if (step < len) {
len = len - (len % step);
}
if (!use_hexa) {
inc *= 4;
}
if (step < 1) {
step = 1;
}
if (inc < 1) {
inc = 1;
}
if (zoomsz < 1) {
zoomsz = 1;
}
switch (base) {
case -10:
bytefmt = "0x%08x ";
pre = " ";
if (inc < 4) {
inc = 4;
}
break;
case -1:
bytefmt = "0x%08x ";
pre = " ";
if (inc < 4) {
inc = 4;
}
break;
case 8:
bytefmt = "%03o";
pre = " ";
break;
case 10:
bytefmt = "%3d";
pre = " ";
break;
case 16:
if (inc < 2) {
inc = 2;
use_header = false;
}
break;
case 32:
bytefmt = "0x%08x ";
pre = " ";
if (inc < 4) {
inc = 4;
}
break;
case 64:
bytefmt = "0x%016x ";
pre = " ";
if (inc < 8) {
inc = 8;
}
break;
}
const char *space = hex_style ? "." : " ";
// TODO: Use base to change %03o and so on
if (step == 1 && base < 0) {
use_header = false;
}
if (use_header) {
bool c = p->flags & RZ_PRINT_FLAGS_COLOR;
if (c) {
const char *color_title = Pal(p, offset)
: Color_MAGENTA;
rz_strbuf_append(sb, color_title);
}
if (base < 32) {
{ // XXX: use rz_print_addr_header
int i, delta;
char soff[32];
if (hex_style) {
rz_strbuf_append(sb, "..offset..");
} else {
rz_strbuf_append(sb, "- offset -");
if (p->wide_offsets) {
rz_strbuf_append(sb, " ");
}
}
if (use_segoff) {
ut32 s, a;
a = addr & 0xffff;
s = ((addr - a) >> p->seggrn) & 0xffff;
snprintf(soff, sizeof(soff), "%04x:%04x ", s, a);
delta = strlen(soff) - 10;
} else {
snprintf(soff, sizeof(soff), "0x%08" PFMT64x, addr);
delta = strlen(soff) - 9;
}
if (compact) {
delta--;
}
for (i = 0; i < delta; i++) {
rz_strbuf_append(sb, space);
}
}
ut32 K = 0;
ut32 k = 0;
/* column after number, before hex data */
rz_strbuf_append(sb, (col == 1) ? "|" : space);
if (use_hdroff) {
k = addr & 0xf;
K = (addr >> 4) & 0xf;
} else {
k = 0; // TODO: ??? SURE??? config.seek & 0xF;
}
if (use_hexa) {
/* extra padding for offsets > 8 digits */
for (i = 0; i < inc; i++) {
rz_strbuf_append(sb, pre);
if (base < 0) {
if (i & 1) {
rz_strbuf_append(sb, space);
}
}
if (use_hdroff) {
if (pairs) {
rz_strbuf_appendf(sb, "%c%c",
hex[(((i + k) >> 4) + K) % 16],
hex[(i + k) % 16]);
} else {
rz_strbuf_appendf(sb, " %c", hex[(i + k) % 16]);
}
} else {
rz_strbuf_appendf(sb, " %c", hex[(i + k) % 16]);
}
if (i & 1 || !pairs) {
if (!compact) {
rz_strbuf_append(sb, col != 1 ? space : ((i + 1) < inc) ? space
: "|");
}
}
}
}
/* ascii column */
if (compact) {
rz_strbuf_append(sb, col > 0 ? "|" : space);
} else {
rz_strbuf_append(sb, col == 2 ? "|" : space);
}
if (!(p->flags & RZ_PRINT_FLAGS_NONASCII)) {
for (i = 0; i < inc; i++) {
rz_strbuf_appendf(sb, "%c", hex[(i + k) % 16]);
}
}
if (col == 2) {
rz_strbuf_append(sb, "|");
}
/* print comment header*/
if (p->use_comments && !compact) {
if (col != 2) {
rz_strbuf_append(sb, " ");
}
if (!hex_style) {
rz_strbuf_append(sb, " comment");
}
}
rz_strbuf_append(sb, "\n");
}
if (c) {
rz_strbuf_append(sb, Color_RESET);
}
}
// is this necessary?
rz_print_set_screenbounds(p, addr);
int rowbytes;
int rows = 0;
int bytes = 0;
bool printValue = true;
bool oPrintValue = true;
bool isPxr = p->flags & RZ_PRINT_FLAGS_REFS;
for (i = j = 0; i < len; i += (stride ? stride : inc)) {
if (p->cons && p->cons->context && p->cons->context->breaked) {
break;
}
rowbytes = inc;
if (use_align) {
int sz = p->offsize ? p->offsize(p->user, addr + j) : -1;
if (sz > 0) { // flags with size 0 dont work
rowbytes = sz;
}
}
if (use_sparse) {
if (checkSparse(buf + i, inc, sparse_char)) {
if (i + inc >= len || checkSparse(buf + i + inc, inc, sparse_char)) {
if (i + inc + inc >= len ||
checkSparse(buf + i + inc + inc, inc, sparse_char)) {
sparse_char = buf[j];
last_sparse++;
if (last_sparse == 2) {
rz_strbuf_append(sb, " ...\n");
continue;
}
if (last_sparse > 2) {
continue;
}
}
}
} else {
last_sparse = 0;
}
}
ut64 at = addr + (j * zoomsz);
if (use_offset && (!isPxr || inc < 4)) {
print_section(sb, p, at);
print_addr(sb, p, at);
}
int row_have_cursor = -1;
ut64 row_have_addr = UT64_MAX;
if (use_hexa) {
if (!compact && !isPxr) {
rz_strbuf_append(sb, (col == 1) ? "|" : " ");
}
for (j = i; j < i + inc; j++) {
if (j != i && use_align && rowbytes == inc) {
int sz = p->offsize ? p->offsize(p->user, addr + j) : -1;
if (sz >= 0) {
rowbytes = bytes;
}
}
if (row_have_cursor == -1) {
if (rz_print_cursor_pointer(p, j, 1)) {
row_have_cursor = j - i;
row_have_addr = addr + j;
}
}
if (!compact && ((j >= len) || bytes >= rowbytes)) {
if (col == 1) {
if (j + 1 >= inc + i) {
rz_strbuf_append(sb, j % 2 ? " |" : "| ");
} else {
rz_strbuf_append(sb, j % 2 ? " " : " ");
}
} else {
if (base == 32) {
rz_strbuf_append(sb, (j % 4) ? " " : " ");
} else if (base == 10) {
rz_strbuf_append(sb, j % 2 ? " " : " ");
} else {
rz_strbuf_append(sb, j % 2 ? " " : " ");
}
}
continue;
}
const char *hl = (hex_style && p->offname(p->user, addr + j)) ? Color_INVERT : NULL;
if (hl) {
rz_strbuf_append(sb, hl);
}
if ((base == 32 || base == 64)) {
int left = len - i;
/* TODO: check step. it should be 2/4 for base(32) and 8 for
* base(64) */
size_t sz_n = (base == 64)
? sizeof(ut64)
: (step == 2)
? sizeof(ut16)
: sizeof(ut32);
sz_n = RZ_MIN(left, sz_n);
if (j + sz_n > len) {
// oob
j += sz_n;
continue;
}
ut64 n = rz_read_ble(buf + j, p->big_endian, sz_n * 8);
print_cursor_l(sb, p, j, sz_n);
// stub for colors
if (p->colorfor) {
if (!p->iob.addr_is_mapped(p->iob.io, addr + j)) {
a = p->cons->context->pal.ai_unmap;
} else {
a = p->colorfor(p->user, n, true);
}
if (a && *a) {
b = Color_RESET;
} else {
a = b = "";
}
} else {
a = b = "";
}
printValue = true;
bool hasNull = false;
if (isPxr) {
if (n == 0) {
if (oPrintValue) {
hasNull = true;
}
printValue = false;
}
}
if (printValue) {
if (use_offset && !hasNull && isPxr) {
print_section(sb, p, at);
print_addr(sb, p, addr + j * zoomsz);
}
if (base == 64) {
rz_strbuf_appendf(sb, "%s0x%016" PFMT64x "%s ", a, (ut64)n, b);
} else if (step == 2) {
rz_strbuf_appendf(sb, "%s0x%04x%s ", a, (ut16)n, b);
} else {
rz_strbuf_appendf(sb, "%s0x%08x%s ", a, (ut32)n, b);
}
} else {
if (hasNull) {
const char *n = p->offname(p->user, addr + j);
print_section(sb, p, at);
print_addr(sb, p, addr + j * zoomsz);
rz_strbuf_appendf(sb, "..[ null bytes ].. 00000000 %s\n", n ? n : "");
}
}
print_cursor_r(sb, p, j, sz_n);
oPrintValue = printValue;
j += step - 1;
} else if (base == -8) {
st64 w = rz_read_ble64(buf + j, p->big_endian);
print_cursor_l(sb, p, j, 8);
rz_strbuf_appendf(sb, "%23" PFMT64d " ", w);
print_cursor_r(sb, p, j, 8);
j += 7;
} else if (base == -1) {
st8 w = rz_read_ble8(buf + j);
print_cursor_l(sb, p, j, 1);
rz_strbuf_appendf(sb, "%4d ", w);
print_cursor_r(sb, p, j, 1);
} else if (base == -10) {
if (j + 1 < len) {
st16 w = rz_read_ble16(buf + j, p->big_endian);
print_cursor_l(sb, p, j, 2);
rz_strbuf_appendf(sb, "%7d ", w);
print_cursor_r(sb, p, j, 2);
}
j += 1;
} else if (base == 10) { // "pxd"
if (j + 3 < len) {
int w = rz_read_ble32(buf + j, p->big_endian);
print_cursor_l(sb, p, j, 4);
rz_strbuf_appendf(sb, "%13d ", w);
print_cursor_r(sb, p, j, 4);
}
j += 3;
} else {
if (j >= len) {
break;
}
if (use_unalloc && !p->iob.is_valid_offset(p->iob.io, addr + j, false)) {
char ch = p->io_unalloc_ch;
char dbl_ch_str[] = { ch, ch, 0 };
rz_strbuf_appendf(sb, "%s", dbl_ch_str);
} else {
print_byte(sb, p, bytefmt, j, buf[j]);
}
if (pairs && !compact && (inc & 1)) {
bool mustspace = (rows % 2) ? !(j & 1) : (j & 1);
if (mustspace) {
rz_strbuf_append(sb, " ");
}
} else if (bytes % 2 || !pairs) {
if (col == 1) {
if (j + 1 < inc + i) {
if (!compact) {
rz_strbuf_append(sb, " ");
}
} else {
rz_strbuf_append(sb, "|");
}
} else {
if (!compact) {
rz_strbuf_append(sb, " ");
}
}
}
}
if (hl) {
rz_strbuf_append(sb, Color_RESET);
}
bytes++;
}
}
if (printValue) {
if (compact) {
if (col == 0) {
rz_strbuf_append(sb, " ");
} else if (col == 1) {
// print (" ");
} else {
rz_strbuf_append(sb, (col == 2) ? "|" : "");
}
} else {
rz_strbuf_append(sb, (col == 2) ? "|" : " ");
}
if (!p || !(p->flags & RZ_PRINT_FLAGS_NONASCII)) {
bytes = 0;
size_t end = i + inc;
for (j = i; j < end; j++) {
if (j != i && use_align && bytes >= rowbytes) {
int sz = (p->offsize) ? p->offsize(p->user, addr + j) : -1;
if (sz >= 0) {
rz_strbuf_append(sb, " ");
break;
}
}
if (j >= len || (use_align && bytes >= rowbytes)) {
break;
}
ut8 ch = (use_unalloc && !p->iob.is_valid_offset(p->iob.io, addr + j, false))
? ' '
: buf[j];
print_byte(sb, p, "%c", j, ch);
bytes++;
}
}
/* ascii column */
if (col == 2) {
rz_strbuf_append(sb, "|");
}
bool eol = false;
if (!eol && p->flags & RZ_PRINT_FLAGS_REFS) {
ut64 off = UT64_MAX;
if (inc == 8) {
if (i + sizeof(ut64) - 1 < len) {
off = rz_read_le64(buf + i);
}
} else if (inc == 4) {
if (i + sizeof(ut32) - 1 < len) {
off = rz_read_le32(buf + i);
}
} else if (inc == 2 && base == 16) {
if (i + sizeof(ut16) - 1 < len) {
off = rz_read_le16(buf + i);
if (off == 0) {
off = UT64_MAX;
}
}
}
if (p->hasrefs && off != UT64_MAX) {
char *rstr = p->hasrefs(p->user, addr + i, false);
if (rstr && *rstr) {
rz_strbuf_appendf(sb, " @ %s", rstr);
}
free(rstr);
rstr = p->hasrefs(p->user, off, true);
if (rstr && *rstr) {
rz_strbuf_appendf(sb, " %s", rstr);
}
free(rstr);
}
}
if (!eol && p->use_comments) {
for (; j < i + inc; j++) {
rz_strbuf_append(sb, " ");
}
for (j = i; j < i + inc; j++) {
if (use_align && (j - i) >= rowbytes) {
break;
}
if (p->offname) {
a = p->offname(p->user, addr + j);
if (p->colorfor && a && *a) {
const char *color = p->colorfor(p->user, addr + j, true);
rz_strbuf_appendf(sb, "%s ; %s%s", color ? color : "", a,
color ? Color_RESET : "");
}
}
char *comment = p->get_comments(p->user, addr + j);
if (comment) {
if (p->colorfor) {
a = p->colorfor(p->user, addr + j, true);
if (!a || !*a) {
a = "";
}
} else {
a = "";
}
rz_strbuf_appendf(sb, "%s ; %s", a, comment);
free(comment);
}
}
}
if (use_align && rowbytes < inc && bytes >= rowbytes) {
i -= (inc - bytes);
}
rz_strbuf_append(sb, "\n");
}
rows++;
bytes = 0;
if (p->cfmt && *p->cfmt) {
if (row_have_cursor != -1) {
int i = 0;
rz_strbuf_append(sb, " _________");
if (!compact) {
rz_strbuf_append(sb, "_");
}
for (i = 0; i < row_have_cursor; i++) {
if (!pairs || (!compact && i % 2)) {
rz_strbuf_append(sb, "___");
} else {
rz_strbuf_append(sb, "__");
}
}
rz_strbuf_append(sb, "__|\n");
rz_strbuf_appendf(sb, "| cmd.hexcursor = %s\n", p->cfmt);
p->coreb.cmdf(p->coreb.core,
"%s @ 0x%08" PFMT64x, p->cfmt, row_have_addr);
}
}
}
return rz_strbuf_drain(sb);
}
static const char *getbytediff(RzPrint *p, char *fmt, size_t fmt_sz, ut8 a, ut8 b) {
if (*fmt) {
if (a == b) {
snprintf(fmt, fmt_sz, "%s%02x" Color_RESET, p->cons->context->pal.graph_true, a);
} else {
snprintf(fmt, fmt_sz, "%s%02x" Color_RESET, p->cons->context->pal.graph_false, a);
}
} else {
snprintf(fmt, fmt_sz, "%02x", a);
}
return fmt;
}
static const char *getchardiff(RzPrint *p, char *fmt, size_t fmt_sz, ut8 a, ut8 b) {
char ch = IS_PRINTABLE(a) ? a : '.';
if (*fmt) {
if (a == b) {
snprintf(fmt, fmt_sz, "%s%c" Color_RESET, p->cons->context->pal.graph_true, ch);
} else {
snprintf(fmt, fmt_sz, "%s%c" Color_RESET, p->cons->context->pal.graph_false, ch);
}
} else {
snprintf(fmt, fmt_sz, "%c", ch);
}
return fmt;
}
#define BD(a, b) getbytediff(p, fmt, sizeof(fmt), (a)[i + j], (b)[i + j])
#define CD(a, b) getchardiff(p, fmt, sizeof(fmt), (a)[i + j], (b)[i + j])
static ut8 *M(const ut8 *b, int len) {
ut8 *r = malloc(len + 16);
if (r) {
memset(r, 0xff, len + 16);
memcpy(r, b, len);
}
return r;
}
/**
* \brief Print hexdump diff between \p _a and \p _b.
* \param p RzPrint instance.
* \param aa Address of first buffer.
* \param _a First buffer.
* \param ba Address of second buffer.
* \param _b Second buffer.
* \param len Diff length.
* \param scndcol True If core->cons->columns > 123 ?
* \return Hexdump diff string.
*/
// TODO: add support for cursor
RZ_API RZ_OWN char *rz_print_hexdiff_str(RZ_NONNULL RzPrint *p, ut64 aa, RZ_NONNULL const ut8 *_a,
ut64 ba, RZ_NONNULL const ut8 *_b, int len, int scndcol) {
rz_return_val_if_fail(p && _a && _b && len > 0, NULL);
ut8 *a, *b;
char linediff, fmt[64];
int color = p->flags & RZ_PRINT_FLAGS_COLOR;
int diffskip = p->flags & RZ_PRINT_FLAGS_DIFFOUT;
int i, j, min;
if (!((a = M(_a, len)))) {
return NULL;
}
if (!((b = M(_b, len)))) {
free(a);
return NULL;
}
RzStrBuf *sb = rz_strbuf_new(NULL);
for (i = 0; i < len; i += 16) {
min = RZ_MIN(16, len - i);
linediff = (memcmp(a + i, b + i, min)) ? '!' : '|';
if (diffskip && linediff == '|') {
continue;
}
rz_strbuf_appendf(sb, "0x%08" PFMT64x " ", aa + i);
for (j = 0; j < min; j++) {
*fmt = color;
print_cursor_l(sb, p, i + j, 1);
rz_strbuf_appendf(sb, "%s", BD(a, b));
print_cursor_r(sb, p, i + j, 1);
}
rz_strbuf_append(sb, " ");
for (j = 0; j < min; j++) {
*fmt = color;
print_cursor_l(sb, p, i + j, 1);
rz_strbuf_appendf(sb, "%s", CD(a, b));
print_cursor_r(sb, p, i + j, 1);
}
if (scndcol) {
rz_strbuf_appendf(sb, " %c 0x%08" PFMT64x " ", linediff, ba + i);
for (j = 0; j < min; j++) {
*fmt = color;
print_cursor_r(sb, p, i + j, 1);
rz_strbuf_appendf(sb, "%s", BD(b, a));
print_cursor_r(sb, p, i + j, 1);
}
rz_strbuf_append(sb, " ");
for (j = 0; j < min; j++) {
*fmt = color;
print_cursor_r(sb, p, i + j, 1);
rz_strbuf_appendf(sb, "%s", CD(b, a));
print_cursor_r(sb, p, i + j, 1);
}
rz_strbuf_append(sb, "\n");
} else {
rz_strbuf_appendf(sb, " %c\n", linediff);
}
}
free(a);
free(b);
return rz_strbuf_drain(sb);
}
RZ_API void rz_print_bytes(RzPrint *p, const ut8 *buf, int len, const char *fmt) {
rz_return_if_fail(fmt);
int i;
if (p) {
for (i = 0; i < len; i++) {
p->cb_printf(fmt, buf[i]);
}
p->cb_printf("\n");
} else {
for (i = 0; i < len; i++) {
printf(fmt, buf[i]);
}
printf("\n");
}
}
RZ_API void rz_print_raw(RzPrint *p, ut64 addr, const ut8 *buf, int len) {
p->write(buf, len);
}
/* TODO: handle screen width */
// Probably move somewhere else. RzPrint doesn't need to know about the RZ_ANALYSIS_ enums
RZ_API const char *rz_print_color_op_type(RZ_NONNULL RzPrint *p, ut32 /* RzAnalaysisOpType */ analysis_type) {
rz_return_val_if_fail(p, NULL);
RzConsPrintablePalette *pal = &p->cons->context->pal;
switch (analysis_type & RZ_ANALYSIS_OP_TYPE_MASK) {
case RZ_ANALYSIS_OP_TYPE_NOP:
return pal->nop;
case RZ_ANALYSIS_OP_TYPE_ADD:
case RZ_ANALYSIS_OP_TYPE_SUB:
case RZ_ANALYSIS_OP_TYPE_MUL:
case RZ_ANALYSIS_OP_TYPE_DIV:
case RZ_ANALYSIS_OP_TYPE_MOD:
case RZ_ANALYSIS_OP_TYPE_LENGTH:
return pal->math;
case RZ_ANALYSIS_OP_TYPE_AND:
case RZ_ANALYSIS_OP_TYPE_OR:
case RZ_ANALYSIS_OP_TYPE_XOR:
case RZ_ANALYSIS_OP_TYPE_NOT:
case RZ_ANALYSIS_OP_TYPE_SHL:
case RZ_ANALYSIS_OP_TYPE_SAL:
case RZ_ANALYSIS_OP_TYPE_SAR:
case RZ_ANALYSIS_OP_TYPE_SHR:
case RZ_ANALYSIS_OP_TYPE_ROL:
case RZ_ANALYSIS_OP_TYPE_ROR:
case RZ_ANALYSIS_OP_TYPE_CPL:
return pal->bin;
case RZ_ANALYSIS_OP_TYPE_IO:
return pal->swi;
case RZ_ANALYSIS_OP_TYPE_JMP:
case RZ_ANALYSIS_OP_TYPE_UJMP:
return pal->ujmp;
case RZ_ANALYSIS_OP_TYPE_IJMP:
case RZ_ANALYSIS_OP_TYPE_RJMP:
case RZ_ANALYSIS_OP_TYPE_IRJMP:
case RZ_ANALYSIS_OP_TYPE_MJMP:
return pal->jmp;
case RZ_ANALYSIS_OP_TYPE_CJMP:
case RZ_ANALYSIS_OP_TYPE_UCJMP:
case RZ_ANALYSIS_OP_TYPE_SWITCH:
return pal->cjmp;
case RZ_ANALYSIS_OP_TYPE_CMP:
case RZ_ANALYSIS_OP_TYPE_ACMP:
return pal->cmp;
case RZ_ANALYSIS_OP_TYPE_UCALL:
return pal->ucall;
case RZ_ANALYSIS_OP_TYPE_ICALL:
case RZ_ANALYSIS_OP_TYPE_RCALL:
case RZ_ANALYSIS_OP_TYPE_IRCALL:
case RZ_ANALYSIS_OP_TYPE_UCCALL:
case RZ_ANALYSIS_OP_TYPE_CALL:
case RZ_ANALYSIS_OP_TYPE_CCALL:
return pal->call;
case RZ_ANALYSIS_OP_TYPE_NEW:
case RZ_ANALYSIS_OP_TYPE_SWI:
return pal->swi;
case RZ_ANALYSIS_OP_TYPE_ILL:
case RZ_ANALYSIS_OP_TYPE_TRAP:
return pal->trap;
case RZ_ANALYSIS_OP_TYPE_CRET:
case RZ_ANALYSIS_OP_TYPE_RET:
return pal->ret;
case RZ_ANALYSIS_OP_TYPE_CAST:
case RZ_ANALYSIS_OP_TYPE_MOV:
case RZ_ANALYSIS_OP_TYPE_LEA:
case RZ_ANALYSIS_OP_TYPE_CMOV: // TODO: add cmov cathegory?
return pal->mov;
case RZ_ANALYSIS_OP_TYPE_PUSH:
case RZ_ANALYSIS_OP_TYPE_UPUSH:
case RZ_ANALYSIS_OP_TYPE_RPUSH:
case RZ_ANALYSIS_OP_TYPE_LOAD:
return pal->push;
case RZ_ANALYSIS_OP_TYPE_POP:
case RZ_ANALYSIS_OP_TYPE_STORE:
return pal->pop;
case RZ_ANALYSIS_OP_TYPE_CRYPTO:
return pal->crypto;
case RZ_ANALYSIS_OP_TYPE_NULL:
return pal->other;
case RZ_ANALYSIS_OP_TYPE_UNK:
default:
return pal->invalid;
}
}
// reset the status of row_offsets
RZ_API void rz_print_init_rowoffsets(RzPrint *p) {
if (p->calc_row_offsets) {
RZ_FREE(p->row_offsets);
p->row_offsets_sz = 0;
}
}
// set the offset, from the start of the printing, of the i-th row
RZ_API void rz_print_set_rowoff(RzPrint *p, int i, ut32 offset, bool overwrite) {
if (!overwrite) {
return;
}
if (i < 0) {
return;
}
if (!p->row_offsets || !p->row_offsets_sz) {
const int initial_size = RZ_MAX(i + 1, DFLT_ROWS);
ut32 *row_offsets = RZ_NEWS(ut32, initial_size);
// keep the row offset state unchanged if first alloc fails.
if (!row_offsets) {
return;
}
p->row_offsets = row_offsets;
p->row_offsets_sz = initial_size;
}
if (i >= p->row_offsets_sz) {
int new_row_offsets_sz = p->row_offsets_sz;
// XXX dangerous
while (i >= new_row_offsets_sz) {
new_row_offsets_sz *= 2;
}
size_t new_size = sizeof(ut32) * new_row_offsets_sz;
ut32 *tmp = realloc(p->row_offsets, new_size);
// only grow the metadata after the backing arr was resized successfully.
if (!tmp) {
return;
}
p->row_offsets = tmp;
p->row_offsets_sz = new_row_offsets_sz;
}
p->row_offsets[i] = offset;
}
// return the offset, from the start of the printing, of the i-th row.
// if the line index is not valid, UT32_MAX is returned.
RZ_API ut32 rz_print_rowoff(RzPrint *p, int i) {
if (i < 0 || i >= p->row_offsets_sz) {
return UT32_MAX;
}
return p->row_offsets[i];
}
// return the index of the row that contains the given offset or -1 if
// that row doesn't exist.
RZ_API int rz_print_row_at_off(RzPrint *p, ut32 offset) {
int i = 0;
ut32 tt;
while ((tt = rz_print_rowoff(p, i)) != UT32_MAX && tt <= offset) {
i++;
}
return tt != UT32_MAX ? i - 1 : -1;
}
RZ_API int rz_print_get_cursor(RzPrint *p) {
return p->cur_enabled ? p->cur : 0;
}
/**
* \brief Print dump in json format
* \param p RzPrint instance
* \param buf Buffer to print to
* \param len Print only this many bytes
* \param wordsize Size of a word in bits
* \return Dump JSON string
*/
RZ_API RZ_OWN char *rz_print_jsondump_str(RZ_NONNULL RzPrint *p, RZ_NONNULL const ut8 *buf, int len, int wordsize) {
rz_return_val_if_fail(p && buf && len > 0 && wordsize > 0, 0);
int bytesize = wordsize / 8;
if (bytesize < 1) {
bytesize = 8;
}
PJ *j = pj_new();
if (!j) {
return NULL;
}
pj_a(j);
for (int i = 0; i + bytesize <= len; i += bytesize) {
ut64 word = rz_read_ble(buf + i, p->big_endian, wordsize);
pj_n(j, word);
}
pj_end(j);
char *str = rz_str_dup(pj_string(j));
pj_free(j);
return str;
}
/**
* \brief Colorizes a tokenized asm string.
*
* \param p The RzPrint struct. Used to retrieve the color palette.
* \param toks The tokenized asm string.
* \param opt Options for colorizing. E.g. reset background color, an address to highlight etc.
*
* \return The colorized asm string.
*/
RZ_API RZ_OWN RzStrBuf *rz_print_colorize_asm_str(RZ_BORROW RzPrint *p, const RzAsmTokenString *toks) {
rz_return_val_if_fail(p && toks, NULL);
// Color palette.
RzConsPrintablePalette palette = p->cons->context->pal;
// Black white asm string.
char *bw_str = rz_strbuf_get(toks->str);
rz_return_val_if_fail(bw_str, NULL);
char *reset = p->colorize_opts.reset_bg ? Color_RESET_NOBG : Color_RESET;
// mnemonic color
const char *mnem_col = rz_print_color_op_type(p, toks->op_type);
RzStrBuf *out = rz_strbuf_new("");
rz_return_val_if_fail(out, NULL);
const char *color;
void **it;
rz_pvector_foreach (toks->tokens, it) {
RzAsmToken *tok = *it;
switch (tok->type) {
default:
rz_strbuf_free(out);
rz_warn_if_reached();
return NULL;
case RZ_ASM_TOKEN_UNKNOWN:
color = palette.other;
break;
case RZ_ASM_TOKEN_MNEMONIC:
color = mnem_col;
break;
case RZ_ASM_TOKEN_NUMBER:
if (tok->val.number == p->colorize_opts.hl_addr && tok->val.number != 0) {
color = palette.func_var_type;
} else {
color = palette.num;
}
break;
case RZ_ASM_TOKEN_OPERATOR:
case RZ_ASM_TOKEN_SEPARATOR:
color = palette.other;
break;
case RZ_ASM_TOKEN_REGISTER:
color = palette.reg;
break;
case RZ_ASM_TOKEN_META:
color = palette.meta;
break;
}
rz_strbuf_append(out, color);
rz_strbuf_append_n(out, bw_str + tok->start, tok->len);
rz_strbuf_append(out, reset);
}
return out;
}
/**
* \brief Returns the maximum flag+arg length in \p options, and optionally the maximum description length.
*
* \param options Options string array, with 3 (default) or 4 strings per option depending on \p max_desc_len.
* \param options_len Length of \p options in strings, should be RZ_ARRAY_SIZE(options).
* \param max_desc_len If not NULL, this will contain the maximum description length in \p options, and function will
* assume that there are 4 strings per option.
* \return The maximum flag+arg length in \p options.
*/
static size_t options_get_max_len(const char **options, size_t options_len, RZ_NULLABLE size_t *max_desc_len) {
int items_per_opt = max_desc_len ? 4 : 3;
size_t max_flag_n_arg_len = 0;
if (max_desc_len) {
*max_desc_len = 0;
}
for (int i = 0; i < options_len; i += items_per_opt) {
size_t flag_len = options[i] ? strlen(options[i]) : 0;
size_t arg_len = options[i + 1] ? strlen(options[i + 1]) : 0;
size_t flag_n_arg_len = flag_len + arg_len;
if (flag_n_arg_len > max_flag_n_arg_len) {
max_flag_n_arg_len = flag_n_arg_len;
}
if (max_desc_len) {
size_t desc_len = strlen(options[i + 2]);
if (desc_len > *max_desc_len) {
*max_desc_len = desc_len;
}
}
}
return max_flag_n_arg_len;
}
/**
* \brief Prints a help option with example, with the option flag and arg strings colorized, and both description and
* example aligned to columns.
*
* \param flag Option flag, will be colored green.
* \param arg Option arg, will be colored yellow.
* \param desc Option description, will be aligned in the third column unless both \p flag and \p arg are NULL. If so,
* it will be aligned at the first column.
* \param max_flag_n_arg_len Width of column containing \p flag and \p arg. It needs to be calculated beforehand as the
* max over all options, perhaps via options_get_max_len().
* \param example Optional option example, will be aligned in a column after the description and prefixed with "; ".
* \param max_desc_len Width of column containing \p desc. It needs to be calculated beforehand as the max over all
* descriptions, perhaps via options_get_max_len().
*/
static void print_colored_help_option(RZ_NULLABLE const char *flag, RZ_NULLABLE const char *arg, const char *desc,
size_t max_flag_n_arg_len, RZ_NULLABLE const char *example, size_t max_desc_len) {
rz_return_if_fail(desc);
bool desc_start = !flag && !arg;
size_t max_opt_spaces = max_flag_n_arg_len + 2;
printf(" ");
if (!desc_start) {
if (!flag) {
flag = "";
}
if (!arg) {
arg = "";
}
printf(Color_GREEN "%s" Color_RESET, flag);
size_t remaining_spaces = max_opt_spaces - strlen(flag);
if (RZ_STR_ISNOTEMPTY(arg)) {
printf(Color_YELLOW " %s " Color_RESET, arg);
remaining_spaces -= strlen(arg) + 2;
if (arg[0] == '\b') {
remaining_spaces += 2;
}
}
printf("%*s", (int)remaining_spaces, "");
}
printf(Color_RESET "%s", desc);
if (example) {
size_t max_desc_spaces = (desc_start ? max_opt_spaces : 0) + max_desc_len;
size_t remaining_spaces = max_desc_spaces - strlen(desc);
printf("%*s ; %s", (int)remaining_spaces, "", example);
}
printf("\n");
}
/**
* \brief Print colored help from \p options.
*
* \param options Options string array, with 3 (default) or 4 strings per option depending on \p have_examples.
* \param options_len Length of \p options in strings, should be RZ_ARRAY_SIZE(options).
* \param have_examples If false, there are 3 strings per option: flag, argument and description. If true, there is a
* 4th string for each option that contains an example.
*/
RZ_API void rz_print_colored_help(const char **options, size_t options_len, bool have_examples) {
rz_return_if_fail(options_len % (have_examples ? 4 : 3) == 0);
size_t max_desc_len = 0;
size_t max_flag_n_arg_len = options_get_max_len(options, options_len, have_examples ? &max_desc_len : NULL);
for (int i = 0; i < options_len; i += have_examples ? 4 : 3) {
print_colored_help_option(options[i], options[i + 1], options[i + 2], max_flag_n_arg_len,
have_examples ? options[i + 3] : NULL, max_desc_len);
}
}