// SPDX-FileCopyrightText: 2009-2021 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_asm.h>
#include <rz_cmd.h>
#include <rz_core.h>
#include <rz_config.h>
#include <rz_util.h>
#include <rz_type.h>
#include <rz_types.h>

#include "../core_private.h"

#define PF_USAGE_STR "pf[.k[.f[=v]]|[v]]|[n]|[0|cnt][fmt] [a0 a1 ...]"

static const ut32 colormap[256] = {
	0x000000,
	0x560000,
	0x640000,
	0x750000,
	0x870000,
	0x9b0000,
	0xb00000,
	0xc60000,
	0xdd0000,
	0xf50000,
	0xff0f0f,
	0xff2828,
	0xff4343,
	0xff5e5e,
	0xff7979,
	0xfe9595,
	0x4c1600,
	0x561900,
	0x641e00,
	0x752300,
	0x872800,
	0x9b2e00,
	0xb03400,
	0xc63b00,
	0xdd4200,
	0xf54900,
	0xff570f,
	0xff6928,
	0xff7b43,
	0xff8e5e,
	0xffa179,
	0xfeb595,
	0x4c3900,
	0x564000,
	0x644b00,
	0x755700,
	0x876500,
	0x9b7400,
	0xb08400,
	0xc69400,
	0xdda600,
	0xf5b800,
	0xffc30f,
	0xffc928,
	0xffd043,
	0xffd65e,
	0xffdd79,
	0xfee495,
	0x4c4c00,
	0x565600,
	0x646400,
	0x757500,
	0x878700,
	0x9b9b00,
	0xb0b000,
	0xc6c600,
	0xdddd00,
	0xf5f500,
	0xffff0f,
	0xffff28,
	0xffff43,
	0xffff5e,
	0xffff79,
	0xfffe95,
	0x324c00,
	0x395600,
	0x426400,
	0x4e7500,
	0x5a8700,
	0x679b00,
	0x75b000,
	0x84c600,
	0x93dd00,
	0xa3f500,
	0xafff0f,
	0xb7ff28,
	0xc0ff43,
	0xc9ff5e,
	0xd2ff79,
	0xdbfe95,
	0x1f4c00,
	0x235600,
	0x296400,
	0x307500,
	0x388700,
	0x409b00,
	0x49b000,
	0x52c600,
	0x5cdd00,
	0x66f500,
	0x73ff0f,
	0x82ff28,
	0x91ff43,
	0xa1ff5e,
	0xb1ff79,
	0xc1fe95,
	0x004c00,
	0x005600,
	0x006400,
	0x007500,
	0x008700,
	0x009b00,
	0x00b000,
	0x00c600,
	0x00dd00,
	0x00f500,
	0x0fff0f,
	0x28ff28,
	0x43ff43,
	0x5eff5e,
	0x79ff79,
	0x95fe95,
	0x004c19,
	0x00561c,
	0x006421,
	0x007527,
	0x00872d,
	0x009b33,
	0x00b03a,
	0x00c642,
	0x00dd49,
	0x00f551,
	0x0fff5f,
	0x28ff70,
	0x43ff81,
	0x5eff93,
	0x79ffa6,
	0x95feb8,
	0x004c4c,
	0x005656,
	0x006464,
	0x007575,
	0x008787,
	0x009b9b,
	0x00b0b0,
	0x00c6c6,
	0x00dddd,
	0x00f5f5,
	0x0ffffe,
	0x28fffe,
	0x43fffe,
	0x5efffe,
	0x79ffff,
	0x95fffe,
	0x00394c,
	0x004056,
	0x004b64,
	0x005775,
	0x006587,
	0x00749b,
	0x0084b0,
	0x0094c6,
	0x00a6dd,
	0x00b8f5,
	0x0fc3ff,
	0x28c9ff,
	0x43d0ff,
	0x5ed6ff,
	0x79ddff,
	0x95e4fe,
	0x00264c,
	0x002b56,
	0x003264,
	0x003a75,
	0x004387,
	0x004d9b,
	0x0058b0,
	0x0063c6,
	0x006edd,
	0x007af5,
	0x0f87ff,
	0x2893ff,
	0x43a1ff,
	0x5eaeff,
	0x79bcff,
	0x95cafe,
	0x00134c,
	0x001556,
	0x001964,
	0x001d75,
	0x002187,
	0x00269b,
	0x002cb0,
	0x0031c6,
	0x0037dd,
	0x003df5,
	0x0f4bff,
	0x285eff,
	0x4372ff,
	0x5e86ff,
	0x799aff,
	0x95b0fe,
	0x19004c,
	0x1c0056,
	0x210064,
	0x270075,
	0x2d0087,
	0x33009b,
	0x3a00b0,
	0x4200c6,
	0x4900dd,
	0x5100f5,
	0x5f0fff,
	0x7028ff,
	0x8143ff,
	0x935eff,
	0xa679ff,
	0xb895fe,
	0x33004c,
	0x390056,
	0x420064,
	0x4e0075,
	0x5a0087,
	0x67009b,
	0x7500b0,
	0x8400c6,
	0x9300dd,
	0xa300f5,
	0xaf0fff,
	0xb728ff,
	0xc043ff,
	0xc95eff,
	0xd279ff,
	0xdb95fe,
	0x4c004c,
	0x560056,
	0x640064,
	0x750075,
	0x870087,
	0x9b009b,
	0xb000b0,
	0xc600c6,
	0xdd00dd,
	0xf500f5,
	0xfe0fff,
	0xfe28ff,
	0xfe43ff,
	0xfe5eff,
	0xfe79ff,
	0xfe95fe,
	0x4c0032,
	0x560039,
	0x640042,
	0x75004e,
	0x87005a,
	0x9b0067,
	0xb00075,
	0xc60084,
	0xdd0093,
	0xf500a3,
	0xff0faf,
	0xff28b7,
	0xff43c0,
	0xff5ec9,
	0xff79d2,
	0xffffff,
};

static inline char *get_section_name(RzCore *core, ut64 offset) {
	char *csection = rz_core_get_section_name(core, offset);
	if (RZ_STR_ISEMPTY(csection)) {
		free(csection);
		return rz_str_dup("unknown");
	}
	rz_str_trim(csection);
	if (RZ_STR_ISEMPTY(csection)) {
		free(csection);
		return rz_str_dup("unknown");
	}
	return csection;
}

static void colordump(RzCore *core, const ut8 *block, int len) {
	const char *chars = " .,:;!O@#";
	bool square = rz_config_get_i(core->config, "scr.square");
	int i, j;
	char ch, ch2, *color;
	int cols = rz_config_get_i(core->config, "hex.cols");
	bool show_color = rz_config_get_i(core->config, "scr.color");
	bool show_flags = rz_config_get_i(core->config, "asm.flags");
	bool show_section = rz_config_get_i(core->config, "hex.section");
	bool show_offset = rz_config_get_i(core->config, "hex.offset");
	bool show_cursor = core->print->cur_enabled;
	bool show_unalloc = core->print->flags & RZ_PRINT_FLAGS_UNALLOC;
	if (cols < 1 || cols > 0xfffff) {
		cols = 32;
	}
	for (i = 0; i < len; i += cols) {
		if (show_section) {
			char *name = get_section_name(core, core->offset + i);
			rz_cons_printf("%20s ", name ? name : "");
			free(name);
		}
		if (show_offset) {
			rz_print_addr(core->print, core->offset + i);
		}
		for (j = i; j < i + cols; j++) {
			if (j >= len) {
				break;
			}
			if (show_color) {
				ut32 color_val = colormap[block[j]];
				// Brightness weights are based on
				// https://twitter.com/DanHollick/status/1417895189239123968
				// (normalized to red). I'm aware that max
				// brightness is greater than 255 * 3.
				int brightness = ((color_val & 0xff0000) >> 16) + 2 * ((color_val & 0xff00) >> 8) + (color_val & 0xff) / 3;
				char *str = rz_str_newf("rgb:%s rgb:%06x",
					brightness <= 0x7f * 3 ? "fff" : "000", color_val);
				color = rz_cons_pal_parse(str, NULL);
				free(str);
				if (show_cursor && core->print->cur == j) {
					ch = '_';
				} else {
					ch = ' ';
				}
			} else {
				color = rz_str_dup("");
				if (show_cursor && core->print->cur == j) {
					ch = '_';
				} else {
					const int idx = ((float)block[j] / 255) * (strlen(chars) - 1);
					ch = chars[idx];
				}
			}
			if (show_unalloc &&
				!core->print->iob.is_valid_offset(core->print->iob.io, core->offset + j, false)) {
				ch = core->print->io_unalloc_ch;
				if (show_color) {
					free(color);
					color = rz_str_dup(Color_RESET);
					if (ch == ' ') {
						ch = '.';
					}
				} else {
					ch = strchr(chars, ch) ? '?' : ch;
				}
			}
			if (square) {
				if (show_flags) {
					RzFlagItem *fi = rz_flag_get_i(core->flags, core->offset + j);
					if (fi) {
						if (fi->name[1]) {
							ch = fi->name[0];
							ch2 = fi->name[1];
						} else {
							ch = ' ';
							ch2 = fi->name[0];
						}
					} else {
						ch2 = ch;
					}
				} else {
					ch2 = ch;
				}
				rz_cons_printf("%s%c%c", color, ch, ch2);
			} else {
				rz_cons_printf("%s%c", color, ch);
			}
			free(color);
		}
		if (show_color) {
			rz_cons_printf(Color_RESET);
		}
		rz_cons_newline();
	}
}

static void findMethodBounds(RzList /*<RzBinSymbol *>*/ *methods, ut64 *min, ut64 *max) {
	RzBinSymbol *sym;
	RzListIter *iter;
	ut64 at_min = UT64_MAX;
	ut64 at_max = 0LL;

	rz_list_foreach (methods, iter, sym) {
		if (sym->vaddr) {
			if (sym->vaddr < at_min) {
				at_min = sym->vaddr;
			}
			if (sym->vaddr + sym->size > at_max) {
				at_max = sym->vaddr + sym->size;
			}
		}
	}
	*min = at_min;
	*max = at_max;
}

static ut64 findClassBounds(RzCore *core, int *len) {
	ut64 min = 0, max = 0;
	void **iter;
	RzBinClass *c;

	RzBinObject *bin_obj = rz_bin_cur_object(core->bin);
	const RzPVector *cs = rz_bin_object_get_classes(bin_obj);
	if (cs) {
		rz_pvector_foreach (cs, iter) {
			c = *iter;
			if (!c || !c->name || !c->name[0]) {
				continue;
			}
			findMethodBounds(c->methods, &min, &max);
			if (len) {
				*len = (max - min);
			}
			return min;
		}
	}
	return 0;
}

RZ_IPI RzCmdStatus rz_cmd_print_timestamp_unix_handler(RzCore *core, int argc, const char **argv) {
	char *date = NULL;
	const ut8 *block = core->block;
	ut32 len = core->blocksize;
	bool big_endian = rz_config_get_b(core->config, "cfg.bigendian");
	int timezone = (int)rz_config_get_i(core->config, "time.zone");
	if (len < sizeof(ut32)) {
		RZ_LOG_ERROR("The block size is less than 4.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	for (ut64 i = 0; i < len; i += sizeof(ut32)) {
		ut32 dt = rz_read_ble32(block + i, big_endian);
		// add timezone
		dt += timezone * (60 * 60);
		date = rz_time_date_unix_to_string(dt);
		rz_cons_printf("%s\n", date);
		free(date);
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_timestamp_current_handler(RzCore *core, int argc, const char **argv) {
	char *now = rz_time_date_now_to_string();
	rz_cons_printf("%s\n", now);
	free(now);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_timestamp_dos_handler(RzCore *core, int argc, const char **argv) {
	char *date = NULL;
	const ut8 *block = core->block;
	ut32 len = core->blocksize;
	if (len < sizeof(ut32)) {
		RZ_LOG_ERROR("The block size is less than 4.\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (ut64 i = 0; i < len; i += sizeof(ut32)) {
		ut32 dt = rz_read_le32(block + i);
		date = rz_time_date_dos_to_string(dt);
		rz_cons_printf("%s\n", date);
		free(date);
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_timestamp_hfs_handler(RzCore *core, int argc, const char **argv) {
	char *date = NULL;
	const ut8 *block = core->block;
	ut32 len = core->blocksize;
	bool big_endian = rz_config_get_b(core->config, "cfg.bigendian");
	int timezone = (int)rz_config_get_i(core->config, "time.zone");
	if (len < sizeof(ut32)) {
		RZ_LOG_ERROR("The block size is less than 4.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	for (ut64 i = 0; i < len; i += sizeof(ut32)) {
		ut32 dt = rz_read_ble32(block + i, big_endian);
		// add timezone
		dt += timezone * (60 * 60);
		date = rz_time_date_hfs_to_string(dt);
		rz_cons_printf("%s\n", date);
		free(date);
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_timestamp_ntfs_handler(RzCore *core, int argc, const char **argv) {
	char *date = NULL;
	const ut8 *block = core->block;
	ut32 len = core->blocksize;
	bool big_endian = rz_config_get_b(core->config, "cfg.bigendian");
	if (len < sizeof(ut64)) {
		RZ_LOG_ERROR("The block size is less than 8.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	for (ut64 i = 0; i < len; i += sizeof(ut64)) {
		ut64 dt = rz_read_ble64(block + i, big_endian);
		date = rz_time_date_w32_to_string(dt);
		rz_cons_printf("%s\n", date);
		free(date);
	}
	return RZ_CMD_STATUS_OK;
}

// > pxa
/* In this function, most of the buffers have 4 times
 * the required length. This is because we supports colours,
 * that are 4 chars long. */
#define append(x, y) \
	{ \
		strcat(x, y); \
		x += strlen(y); \
	}

#define Pal(x, y) (x->cons && x->cons->context->pal.y) ? x->cons->context->pal.y

static void annotated_hexdump(RzCore *core, int len) {
	if (!len) {
		return;
	}
	const int usecolor = rz_config_get_i(core->config, "scr.color");
	int nb_cols = rz_config_get_i(core->config, "hex.cols");
	core->print->use_comments = rz_config_get_i(core->config, "hex.comments");
	int flagsz = rz_config_get_i(core->config, "hex.flagsz");
	bool showSection = rz_config_get_i(core->config, "hex.section");
	const ut8 *buf = core->block;
	ut64 addr = core->offset;
	int color_idx = 0;
	char *bytes, *chars;
	char *ebytes, *echars; // They'll walk over the vars above
	ut64 fend = UT64_MAX;
	int i, j, low, max, here, rows;
	bool marks = false, setcolor = true, hascolor = false;
	ut8 ch = 0;
	char tmpbuf[20] = { 0 };
	char *colors[10] = { NULL };
	for (i = 0; i < 10; i++) {
		colors[i] = rz_cons_rainbow_get(i, 10, false);
	}
	const int col = core->print->col;
	RzFlagItem *flag, *current_flag = NULL;
	char **note;
	int html = rz_config_get_i(core->config, "scr.html");
	int nb_cons_cols;
	bool compact = false;

	if (core->print) {
		compact = core->print->flags & RZ_PRINT_FLAGS_COMPACT;
	}
	char *format = compact ? " %X %X" : " %X %X ";
	int step = compact ? 4 : 5;

	// Adjust the number of columns
	if (nb_cols < 1) {
		nb_cols = 16;
	}
	nb_cols -= (nb_cols % 2); // nb_cols should be even
	if (nb_cols < 1) {
		return;
	}

	nb_cons_cols = 12 + nb_cols * 2 + (nb_cols / 2);
	nb_cons_cols += 17;
	rows = len / nb_cols;

	chars = calloc(nb_cols * 40, sizeof(char));
	if (!chars)
		goto err_chars;
	note = calloc(nb_cols, sizeof(char *));
	if (!note)
		goto err_note;
	bytes = calloc(nb_cons_cols * 40, sizeof(char));
	if (!bytes)
		goto err_bytes;
	int addrpadlen = strlen(rz_strf(tmpbuf, "%08" PFMT64x, addr)) - 8;
	char addrpad[32];
	if (addrpadlen > 0) {
		memset(addrpad, ' ', addrpadlen);
		addrpad[addrpadlen] = 0;
		// Compute, then show the legend
		strcpy(bytes, addrpad);
	} else {
		*addrpad = 0;
		addrpadlen = 0;
	}
	strcpy(bytes + addrpadlen, "- offset -  ");
	j = strlen(bytes);
	for (i = 0; i < nb_cols; i += 2) {
		sprintf(bytes + j, format, (i & 0xf), (i + 1) & 0xf);
		j += step;
	}
	j--;
	strcpy(bytes + j, "     ");
	j += 2;
	for (i = 0; i < nb_cols; i++) {
		sprintf(bytes + j + i, "%0X", i % 17);
	}
	if (usecolor) {
		const char *color_title = Pal(core, offset)
		    : Color_MAGENTA;
		rz_cons_strcat(color_title);
		rz_cons_strcat(bytes);
		rz_cons_strcat(Color_RESET);
	} else {
		rz_cons_strcat(bytes);
	}
	rz_cons_newline();

	// hexdump
	for (i = 0; i < rows; i++) {
		bytes[0] = '\0';
		chars[0] = '\0';
		ebytes = bytes;
		echars = chars;
		hascolor = false;
		ut64 ea = addr;
		if (core->print->pava) {
			ut64 va = rz_io_p2v(core->io, addr);
			if (va != UT64_MAX) {
				ea = va;
			}
		}

		if (usecolor) {
			append(ebytes, core->cons->context->pal.offset);
		}
		if (showSection) {
			char *name = get_section_name(core, ea);
			char *s = rz_str_newf("%20s ", name);
			append(ebytes, s);
			free(s);
			free(name);
		}
		ebytes += sprintf(ebytes, "0x%08" PFMT64x, ea);
		if (usecolor) {
			append(ebytes, Color_RESET);
		}
		append(ebytes, (col == 1) ? " |" : "  ");
		bool hadflag = false;
		for (j = 0; j < nb_cols; j++) {
			setcolor = true;
			RZ_FREE(note[j]);

			// TODO: in pava mode we should read addr or ea? // imho ea. but wat about hdrs and such
			RzIntervalNode *meta_node = rz_meta_get_in(core->analysis, ea + j, RZ_META_TYPE_FORMAT);
			RzAnalysisMetaItem *meta = meta_node ? meta_node->data : NULL;
			if (meta && meta->type == RZ_META_TYPE_FORMAT && meta_node->start == addr + j) {
				rz_cons_printf(".format %s ; size=", meta->str);
				// TODO: Convert to the API
				rz_core_cmdf(core, "pfs %s", meta->str);
				char *r = rz_core_print_format(core, meta->str, RZ_PRINT_MUSTSEE, meta_node->start);
				rz_cons_print(r);
				free(r);
				if (usecolor) {
					append(ebytes, Color_INVERT);
					append(echars, Color_INVERT);
				}
				hadflag = true;
			}
			if (meta) {
				meta = NULL;
			}
			// collect comments
			const char *comment = rz_meta_get_string(core->analysis, RZ_META_TYPE_COMMENT, addr + j);
			if (comment) {
				note[j] = rz_str_newf(";%s", comment);
				marks = true;
			}

			// collect flags
			flag = rz_flag_get_i(core->flags, addr + j);
			if (flag) { // Beginning of a flag
				if (flagsz) {
					fend = addr + flagsz; // core->blocksize;
				} else {
					fend = addr + j + flag->size;
				}
				free(note[j]);
				note[j] = rz_str_prepend(rz_str_dup(flag->name), "/");
				marks = true;
				color_idx++;
				color_idx %= 10;
				current_flag = flag;
				if (showSection) {
					rz_cons_printf("%20s ", "");
				}
				if (flag->offset == addr + j) {
					if (usecolor) {
						append(ebytes, Color_INVERT);
						append(echars, Color_INVERT);
					}
					hadflag = true;
				}
			} else {
				// Are we past the current flag?
				if (current_flag && addr + j > (current_flag->offset + current_flag->size)) {
					setcolor = false;
					current_flag = NULL;
				}
				// Turn colour off if we're at the end of the current flag
				if (fend == UT64_MAX || fend <= addr + j) {
					setcolor = false;
				}
			}
			if (usecolor) {
				if (!setcolor) {
					const char *bytecolor = rz_print_byte_color(core->print, ch);
					if (bytecolor) {
						append(ebytes, bytecolor);
						append(echars, bytecolor);
						hascolor = true;
					}
				} else if (!hascolor) {
					hascolor = true;
					if (current_flag && current_flag->color) {
						char *ansicolor = rz_cons_pal_parse(current_flag->color, NULL);
						if (ansicolor) {
							append(ebytes, ansicolor);
							append(echars, ansicolor);
							free(ansicolor);
						}
					} else { // Use "random" colours
						append(ebytes, colors[color_idx]);
						append(echars, colors[color_idx]);
					}
				}
			}
			here = RZ_MIN((i * nb_cols) + j, core->blocksize);
			ch = buf[here];
			if (core->print->ocur != -1) {
				low = RZ_MIN(core->print->cur, core->print->ocur);
				max = RZ_MAX(core->print->cur, core->print->ocur);
			} else {
				low = max = core->print->cur;
			}
			if (core->print->cur_enabled) {
				if (low == max) {
					if (low == here) {
						if (html || !usecolor) {
							append(ebytes, "[");
							append(echars, "[");
						} else {
							append(echars, Color_INVERT);
							append(ebytes, Color_INVERT);
						}
					}
				} else {
					if (here >= low && here < max) {
						if (html || !usecolor) {
							append(ebytes, "[");
							append(echars, "[");
						} else {
							if (usecolor) {
								append(ebytes, Color_INVERT);
								append(echars, Color_INVERT);
							}
						}
					}
				}
			}
			if (!ebytes) { // this check is just to silence the compiler
				goto err_ebytes;
			}
			sprintf(ebytes, "%02x", ch);
			// rz_print_byte (core->print, "%02x ", j, ch);
			ebytes += strlen(ebytes);
			if (hadflag) {
				if (usecolor) {
					append(ebytes, Color_INVERT_RESET);
					append(echars, Color_INVERT_RESET);
				}
				hadflag = false;
			}
			sprintf(echars, "%c", IS_PRINTABLE(ch) ? ch : '.');
			echars++;
			if (core->print->cur_enabled && max == here) {
				if (!html && usecolor) {
					append(ebytes, Color_RESET);
					append(echars, Color_RESET);
				}
				hascolor = false;
			}

			if (j < (nb_cols - 1) && (j % 2) && !compact) {
				append(ebytes, " ");
			}

			if (fend != UT64_MAX && fend == addr + j + 1) {
				if (!html && usecolor) {
					append(ebytes, Color_RESET);
					append(echars, Color_RESET);
				}
				fend = UT64_MAX;
				hascolor = false;
			}
		}
		if (!html && usecolor) {
			append(ebytes, Color_RESET);
			append(echars, Color_RESET);
		}
		append(ebytes, (col == 1) ? "| " : (col == 2) ? " |"
							      : "  ");
		if (col == 2) {
			append(echars, "|");
		}

		if (marks) { // show comments and flags
			int hasline = 0;
			int out_sz = nb_cons_cols + 20;
			char *out = calloc(out_sz, sizeof(char));
			memset(out, ' ', nb_cons_cols - 1);
			for (j = 0; j < nb_cols; j++) {
				if (note[j]) {
					int off = (j * 3) - (j / 2) + 13;
					int notej_len = strlen(note[j]);
					int sz = RZ_MIN(notej_len, nb_cons_cols - off);
					if (compact) {
						off -= (j / 2);
					} else {
						if (j % 2) {
							off--;
						}
					}
					memcpy(out + off, note[j], sz);
					if (sz < notej_len) {
						out[off + sz - 2] = '.';
						out[off + sz - 1] = '.';
					}
					hasline = (out[off] != ' ');
					RZ_FREE(note[j]);
				}
			}
			out[out_sz - 1] = 0;
			if (hasline) {
				rz_cons_strcat(addrpad);
				rz_cons_strcat(out);
				rz_cons_newline();
			}
			marks = false;
			free(out);
		}
		rz_cons_strcat(bytes);
		rz_cons_strcat(chars);

		if (core->print->use_comments) {
			for (j = 0; j < nb_cols; j++) {
				const char *comment = core->print->get_comments(core->print->user, addr + j);
				if (comment) {
					rz_cons_printf(" ; %s", comment);
				}
			}
		}

		rz_cons_newline();
		addr += nb_cols;
	}

err_ebytes:
	free(bytes);
err_bytes:
	free(note);
err_note:
	free(chars);
err_chars:
	for (i = 0; i < RZ_ARRAY_SIZE(colors); i++) {
		free(colors[i]);
	}
}

static bool cmd_print_pxA(RzCore *core, int len, RzOutputMode mode) {
	if (!len) {
		return false;
	}
	RzConsPrintablePalette *pal = &core->cons->context->pal;
	int show_offset = true;
	int cols = rz_config_get_i(core->config, "hex.cols");
	int show_color = rz_config_get_i(core->config, "scr.color");
	int onechar = rz_config_get_i(core->config, "hex.onechar");
	bool hex_offset = rz_config_get_i(core->config, "hex.offset");
	int bgcolor_in_heap = false;
	bool show_cursor = core->print->cur_enabled;
	char buf[2];
	char *bgcolor, *fgcolor, *text;
	ut64 i, c, oi;
	RzAnalysisOp op = { 0 };
	ut8 *data;
	int datalen;
	switch (mode) {
	case RZ_OUTPUT_MODE_LONG:
		datalen = cols * 8 * core->cons->rows;
		data = malloc(datalen);
		rz_io_read_at_mapped(core->io, core->offset, data, datalen);
		len = datalen;
		break;
	case RZ_OUTPUT_MODE_STANDARD:
		data = core->block;
		datalen = core->blocksize;
		break;
	default:
		rz_warn_if_reached();
		return false;
	}
	if (len < 1) {
		len = datalen;
	}
	if (len < 0 || len > datalen) {
		RZ_LOG_ERROR("core: Invalid length\n");
		return false;
	}
	if (onechar) {
		cols *= 4;
	} else {
		cols *= 2;
	}
	if (show_offset) {
		char offstr[128];
		snprintf(offstr, sizeof(offstr),
			"0x%08" PFMT64x "  ", core->offset);
		if (strlen(offstr) > 12) {
			cols -= ((strlen(offstr) - 12) * 2);
		}
	}
	for (oi = i = c = 0; i < len; c++) {
		if (i && (cols != 0) && !(c % cols)) {
			show_offset = true;
			rz_cons_printf("  %" PFMT64u "\n", i - oi);
			oi = i;
		}
		if (show_offset && hex_offset) {
			rz_cons_printf("0x%08" PFMT64x "  ", core->offset + i);
			show_offset = false;
		}
		if (bgcolor_in_heap) {
			free(bgcolor);
			bgcolor_in_heap = false;
		}
		bgcolor = Color_BGBLACK;
		fgcolor = Color_WHITE;
		text = NULL;
		rz_analysis_op_init(&op);
		if (rz_analysis_op(core->analysis, &op, core->offset + i, data + i, len - i, RZ_ANALYSIS_OP_MASK_BASIC) <= 0) {
			op.type = 0;
			bgcolor = Color_BGRED;
			op.size = 1;
		}
		switch (op.type) {
		case RZ_ANALYSIS_OP_TYPE_LEA:
		case RZ_ANALYSIS_OP_TYPE_MOV:
		case RZ_ANALYSIS_OP_TYPE_CAST:
		case RZ_ANALYSIS_OP_TYPE_LENGTH:
		case RZ_ANALYSIS_OP_TYPE_CMOV:
			text = "mv";
			bgcolor = pal->mov;
			fgcolor = Color_YELLOW;
			break;
		case RZ_ANALYSIS_OP_TYPE_PUSH:
		case RZ_ANALYSIS_OP_TYPE_UPUSH:
		case RZ_ANALYSIS_OP_TYPE_RPUSH:
			bgcolor = pal->push;
			fgcolor = Color_WHITE;
			text = "->";
			break;
		case RZ_ANALYSIS_OP_TYPE_IO:
			bgcolor = pal->swi;
			fgcolor = Color_WHITE;
			text = "io";
			break;
		case RZ_ANALYSIS_OP_TYPE_TRAP:
		case RZ_ANALYSIS_OP_TYPE_SWI:
		case RZ_ANALYSIS_OP_TYPE_NEW:
			// bgcolor = Color_BGRED;
			bgcolor = pal->trap; // rz_cons_swap_ground (pal->trap);
			fgcolor = Color_WHITE;
			text = "$$";
			break;
		case RZ_ANALYSIS_OP_TYPE_POP:
			text = "<-";
			bgcolor = rz_cons_swap_ground(pal->pop);
			bgcolor_in_heap = true;
			fgcolor = Color_WHITE;
			break;
		case RZ_ANALYSIS_OP_TYPE_NOP:
			fgcolor = Color_WHITE;
			bgcolor = rz_cons_swap_ground(pal->nop);
			bgcolor_in_heap = true;
			text = "..";
			break;
		case RZ_ANALYSIS_OP_TYPE_MUL:
			fgcolor = Color_BLACK;
			bgcolor = rz_cons_swap_ground(pal->math);
			bgcolor_in_heap = true;
			text = "_*";
			break;
		case RZ_ANALYSIS_OP_TYPE_DIV:
			bgcolor = rz_cons_swap_ground(pal->math);
			bgcolor_in_heap = true;
			fgcolor = Color_BLACK;
			text = "_/";
			break;
		case RZ_ANALYSIS_OP_TYPE_AND:
			bgcolor = rz_cons_swap_ground(pal->bin);
			bgcolor_in_heap = true;
			fgcolor = Color_BLACK;
			text = "_&";
			break;
		case RZ_ANALYSIS_OP_TYPE_XOR:
			bgcolor = rz_cons_swap_ground(pal->bin);
			bgcolor_in_heap = true;
			fgcolor = Color_BLACK;
			text = "_^";
			break;
		case RZ_ANALYSIS_OP_TYPE_OR:
			bgcolor = rz_cons_swap_ground(pal->bin);
			bgcolor_in_heap = true;
			fgcolor = Color_BLACK;
			text = "_|";
			break;
		case RZ_ANALYSIS_OP_TYPE_SHR:
			bgcolor = rz_cons_swap_ground(pal->bin);
			bgcolor_in_heap = true;
			fgcolor = Color_BLACK;
			text = ">>";
			break;
		case RZ_ANALYSIS_OP_TYPE_SHL:
			bgcolor = rz_cons_swap_ground(pal->bin);
			bgcolor_in_heap = true;
			fgcolor = Color_BLACK;
			text = "<<";
			break;
		case RZ_ANALYSIS_OP_TYPE_SUB:
			bgcolor = rz_cons_swap_ground(pal->math);
			bgcolor_in_heap = true;
			fgcolor = Color_WHITE;
			text = "--";
			break;
		case RZ_ANALYSIS_OP_TYPE_ADD:
			bgcolor = rz_cons_swap_ground(pal->math);
			bgcolor_in_heap = true;
			fgcolor = Color_WHITE;
			text = "++";
			break;
		case RZ_ANALYSIS_OP_TYPE_JMP:
		case RZ_ANALYSIS_OP_TYPE_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:
			bgcolor = rz_cons_swap_ground(pal->jmp);
			bgcolor_in_heap = true;
			fgcolor = Color_BLACK;
			text = "_J";
			break;
		case RZ_ANALYSIS_OP_TYPE_CJMP:
		case RZ_ANALYSIS_OP_TYPE_UCJMP:
			bgcolor = rz_cons_swap_ground(pal->cjmp);
			bgcolor_in_heap = true;
			fgcolor = Color_BLACK;
			text = "cJ";
			break;
		case RZ_ANALYSIS_OP_TYPE_CALL:
		case RZ_ANALYSIS_OP_TYPE_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:
			bgcolor = rz_cons_swap_ground(pal->call);
			bgcolor_in_heap = true;
			fgcolor = Color_WHITE;
			text = "_C";
			break;
		case RZ_ANALYSIS_OP_TYPE_ACMP:
		case RZ_ANALYSIS_OP_TYPE_CMP:
			bgcolor = rz_cons_swap_ground(pal->cmp);
			bgcolor_in_heap = true;
			fgcolor = Color_BLACK;
			text = "==";
			break;
		case RZ_ANALYSIS_OP_TYPE_RET:
			bgcolor = rz_cons_swap_ground(pal->ret);
			bgcolor_in_heap = true;
			fgcolor = Color_WHITE;
			text = "_R";
			break;
		case -1:
		case RZ_ANALYSIS_OP_TYPE_ILL:
		case RZ_ANALYSIS_OP_TYPE_UNK:
			bgcolor = rz_cons_swap_ground(pal->invalid);
			bgcolor_in_heap = true;
			fgcolor = Color_WHITE;
			text = "XX";
			break;
		}
		int opsz = RZ_MAX(op.size, 1);
		if (show_cursor) {
			if (core->print->cur >= i && core->print->cur < i + opsz) {
				rz_cons_invert(1, 1);
			}
		}
		if (onechar) {
			if (text) {
				if (text[0] == '_' || text[0] == '.') {
					buf[0] = text[1];
				} else {
					buf[0] = text[0];
				}
			} else {
				buf[0] = '.';
			}
			buf[1] = 0;
			text = buf;
		}
		if (show_color) {
			if (!text) {
				text = "  ";
			}
			rz_cons_printf("%s%s%s\x1b[0m", bgcolor, fgcolor, text);
		} else {
			if (text) {
				rz_cons_print(text);
			} else {
				rz_cons_print("  ");
			}
		}
		if (show_cursor) {
			if (core->print->cur >= i && core->print->cur < i + opsz) {
				rz_cons_invert(0, 1);
			}
		}
		i += opsz;
		rz_analysis_op_fini(&op);
	}
	rz_cons_printf("  %" PFMT64d "\n", i - oi);
	if (bgcolor_in_heap) {
		free(bgcolor);
	}
	if (data != core->block) {
		free(data);
	}

	return true;
}

/* Uses data from clipboard if value is NULL */
static bool print_operation_transform(RzCore *core, RzCoreWriteOp op, RZ_NULLABLE const char *val) {
	ut8 *hex = NULL;
	size_t hexlen = 0, buflen = 0;
	if (val) {
		hex = RZ_NEWS(ut8, (strlen(val) + 1) / 2);
		if (!hex) {
			return false;
		}
		hexlen = rz_hex_str2bin(val, hex);
	}
	ut8 *buf = rz_core_transform_op(core, core->offset, op, hex, hexlen, &buflen);
	free(hex);
	rz_core_print_hexdump(core, core->offset, buf, buflen, 16, 1, 1);
	free(buf);
	return true;
}

static void handle_entropy(RzCore *core, const char *name, const ut8 *block, int len) {
	RzHashSize digest_size = 0;
	ut8 *digest = rz_hash_cfg_calculate_small_block(core->hash, name, block, len, &digest_size);
	if (!digest) {
		return;
	}
	double entropy = rz_read_be_double(digest);
	rz_cons_printf("%f\n", entropy);
	free(digest);
}

static void handle_temperature(RzCore *core, const char *name, const ut8 *block, int len) {
	RzHashSize digest_size = 0;
	ut8 *digest = rz_hash_cfg_calculate_small_block(core->hash, name, block, len, &digest_size);
	if (!digest) {
		return;
	}
	double temperature = rz_read_be_double(digest);
	rz_cons_printf("%f\n", temperature);
	free(digest);
}

static void handle_ssdeep(RzCore *core, const char *name, const ut8 *block, int len) {
	RzHashSize digest_size = 0;
	char *digest = (char *)rz_hash_cfg_calculate_small_block(core->hash, name, block, len, &digest_size);
	if (!digest) {
		return;
	}
	rz_cons_printf("%s\n", digest);
	free(digest);
}

static inline void hexprint(const ut8 *data, int len) {
	if (!data || len < 1) {
		return;
	}
	for (int i = 0; i < len; i++) {
		rz_cons_printf("%02x", data[i]);
	}
	rz_cons_newline();
}

static void handle_hash_cfg(RzCore *core, const char *name, const ut8 *block, int len) {
	RzHashSize digest_size = 0;
	ut8 *digest = rz_hash_cfg_calculate_small_block(core->hash, name, block, len, &digest_size);
	hexprint(digest, digest_size);
	free(digest);
}

RZ_IPI RzCmdStatus rz_cmd_print_hash_cfg_handler(RzCore *core, int argc, const char **argv) {
	const RzHashPlugin *plugin = rz_hash_plugin_by_name(core->hash, argv[1]);

	if (!plugin) {
		RZ_LOG_ERROR("algorithm '%s' does not exists\n", argv[1]);
		return RZ_CMD_STATUS_ERROR;
	}

	if (!strncmp(plugin->name, "entropy", strlen("entropy"))) {
		handle_entropy(core, plugin->name, core->block, core->blocksize);
	} else if (!strncmp(plugin->name, "temperature", strlen("temperature"))) {
		handle_temperature(core, plugin->name, core->block, core->blocksize);
	} else if (!strcmp(plugin->name, "ssdeep")) {
		handle_ssdeep(core, plugin->name, core->block, core->blocksize);
	} else {
		handle_hash_cfg(core, plugin->name, core->block, core->blocksize);
	}

	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_hash_cfg_algo_list_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_core_hash_plugins_print(core->hash, state);
}

RZ_IPI RzCmdStatus rz_cmd_print_magic_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	RzList *hits = rz_core_search_magic(core, NULL, argc > 1 ? argv[1] : NULL);
	if (!hits) {
		RZ_LOG_ERROR("Searching for magics failed.\n");
		return RZ_CMD_STATUS_ERROR;
	}
	RzListIter *it;
	RzSearchHit *hit;
	if (mode == RZ_OUTPUT_MODE_JSON) {
		PJ *pj = pj_new();
		pj_a(pj);
		rz_list_foreach (hits, it, hit) {
			char *detail = rz_search_hit_detail_as_string(hit);
			pj_o(pj);
			pj_kn(pj, "address", hit->address);
			pj_ks(pj, "magic", rz_str_get(detail));
			pj_end(pj);
			free(detail);
		}
		pj_end(pj);
		rz_cons_println(pj_string(pj));
		pj_free(pj);
	} else {
		rz_list_foreach (hits, it, hit) {
			char *detail = rz_search_hit_detail_as_string(hit);
			rz_cons_printf("0x%08" PFMT64x " %s\n", hit->address, rz_str_get(detail));
			free(detail);
		}
	}
	rz_list_free(hits);
	return RZ_CMD_STATUS_OK;
}

static int bbcmp(RzAnalysisBlock *a, RzAnalysisBlock *b, void *user) {
	return a->addr - b->addr;
}

/* TODO: integrate this into rz_analysis */
static void _pointer_table(RzCore *core, ut64 origin, ut64 offset, const ut8 *buf, int len, int step, RzOutputMode mode) {
	if (step < 1) {
		step = 4;
	}
	if (!rz_io_is_valid_offset(core->io, origin, 0) ||
		!rz_io_is_valid_offset(core->io, offset, 0)) {
		return;
	}
	for (size_t i = 0; (i + sizeof(st32)) <= len; i += step) {
		st32 delta = rz_read_le32(buf + i);
		ut64 addr = offset + delta;
		if (!rz_io_is_valid_offset(core->io, addr, 0)) {
			// Lets check for jmptbl with not relative addresses
			// Like: jmp dword [eax*4 + jmptbl.0x5435345]
			if (!rz_io_is_valid_offset(core->io, delta, 0)) {
				break;
			}
			addr = delta;
		}
		rz_cons_printf("0x%08" PFMT64x " -> 0x%08" PFMT64x "\n", offset + i, addr);
	}
}

static void pr_bb(RzCore *core, RzAnalysisFunction *fcn, RzAnalysisBlock *b, bool emu, ut64 saved_gp, ut8 *saved_arena, char p_type, bool fromHere) {
	bool show_flags = rz_config_get_i(core->config, "asm.flags");
	const bool orig_bb_middle = rz_config_get_b(core->config, "asm.bb.middle");
	RzReg *rreg = rz_analysis_get_reg(core->analysis);
	rz_analysis_set_gp(core->analysis, saved_gp);
	if (fromHere) {
		if (b->addr < core->offset) {
			core->cons->null = true;
		} else {
			core->cons->null = false;
		}
	}
	if (emu) {
		if (b->parent_reg_arena) {
			ut64 gp;
			rz_reg_arena_poke(rreg, b->parent_reg_arena);
			RZ_FREE(b->parent_reg_arena);
			gp = rz_reg_getv(rreg, "gp");
			if (gp) {
				rz_analysis_set_gp(core->analysis, gp);
			}
		} else {
			rz_reg_arena_poke(rreg, saved_arena);
		}
	}
	rz_config_set_b(core->config, "asm.bb.middle", false);
	p_type == 'D'
		? rz_core_cmdf(core, "pD %" PFMT64u " @ 0x%" PFMT64x, b->size, b->addr)
		: rz_core_cmdf(core, "pI %" PFMT64u " @ 0x%" PFMT64x, b->size, b->addr);
	rz_config_set_b(core->config, "asm.bb.middle", orig_bb_middle);

	if (b->jump != UT64_MAX) {
		if (b->jump > b->addr) {
			RzAnalysisBlock *jumpbb = rz_analysis_get_block_at(b->analysis, b->jump);
			if (jumpbb && rz_list_contains(jumpbb->fcns, fcn)) {
				ut8 *last_disasm_reg = rz_analysis_get_last_disasm_reg(core->analysis);
				if (emu && last_disasm_reg && !jumpbb->parent_reg_arena) {
					jumpbb->parent_reg_arena = rz_reg_arena_dup(rreg, last_disasm_reg);
				}
			}
		}
		if (p_type == 'D' && show_flags) {
			rz_cons_printf("| ----------- true: 0x%08" PFMT64x, b->jump);
		}
	}
	if (b->fail != UT64_MAX) {
		if (b->fail > b->addr) {
			RzAnalysisBlock *failbb = rz_analysis_get_block_at(b->analysis, b->fail);
			if (failbb && rz_list_contains(failbb->fcns, fcn)) {
				ut8 *last_disasm_reg = rz_analysis_get_last_disasm_reg(core->analysis);
				if (emu && last_disasm_reg && !failbb->parent_reg_arena) {
					failbb->parent_reg_arena = rz_reg_arena_dup(rreg, last_disasm_reg);
				}
			}
		}
		if (p_type == 'D' && show_flags) {
			rz_cons_printf("  false: 0x%08" PFMT64x, b->fail);
		}
	}
	if (p_type == 'D' && show_flags) {
		rz_cons_newline();
	}
}

static void handle_default_disasm_print_mode(const RzCore *core, const ut64 addr, const bool ret_val,
	const char *m_intr, RzStrBuf *buf) {
	const bool show_color = rz_config_get_i(core->config, "scr.color");

	if (show_color) {
		const char *offsetColor = rz_cons_singleton()->context->pal.offset;
		if (!ret_val) {
			rz_cons_printf("%s0x%08" PFMT64x Color_RESET "  %10s %s\n",
				offsetColor, addr, "", m_intr);
		} else {
			rz_strbuf_appendf(buf, "%s0x%08" PFMT64x Color_RESET "  %10s %s\n",
				offsetColor, addr, "", m_intr);
		}
	} else {
		if (!ret_val) {
			rz_cons_printf("0x%08" PFMT64x "  %10s %s\n", addr, "", m_intr);
		} else {
			rz_strbuf_appendf(buf, "0x%08" PFMT64x "  %10s %s\n", addr, "", m_intr);
		}
	}
}

static void disasm_print_ret(const RzCore *core, const RzOutputMode mode, const char *m_intr, const ut64 addr,
	const bool ret_val, RzStrBuf *buf) {
	if (!buf && ret_val) {
		return;
	}
	switch (mode) {
	case RZ_OUTPUT_MODE_QUIET:
		if (!ret_val) {
			rz_cons_printf("%s\n", m_intr);
		} else {
			rz_strbuf_append(buf, m_intr);
		}
		break;
	default:
		handle_default_disasm_print_mode(core, addr, ret_val, m_intr, buf);
		break;
	}
}

/**
 * \brief Disassemble from \p addr until \p limit no of instructions and
 * prints or returns the disassembled string in \p buf
 *
 * \param core Pointer to the RzCore
 * \param addr Address to start disassembling
 * \param limit Maximum number of instructions to disassemble
 * \param mode The mode in which data has to be printed/crafted
 * \param ret_val If true, the disassembled string is stored in \p buf. Otherwise it is streamed to stdout
 * \param buf Pointer to the RzStrBuf to store the disassembled string
 *
 * \return True if there are no errors during disassembling and crafting the response. False otherwise.
 */
RZ_API bool rz_core_disasm_until_ret(RZ_NONNULL RzCore *core, ut64 addr, const int limit,
	const RzOutputMode mode, const bool ret_val, RZ_NULLABLE RZ_OUT RzStrBuf *buf) {
	rz_return_val_if_fail(core, false);
	if (!buf && ret_val) {
		return false;
	}
	for (int i = 0; i < limit; i++) {
		RzAnalysisOp *op = rz_core_analysis_op(core, addr, RZ_ANALYSIS_OP_MASK_BASIC | RZ_ANALYSIS_OP_MASK_DISASM);
		if (!op) {
			RZ_LOG_ERROR("Cannot get op at 0x%08" PFMT64x "\n", addr);
			rz_analysis_op_free(op);
			break;
		}
		const char *mnem = op->mnemonic;
		disasm_print_ret(core, mode, mnem, addr, ret_val, buf);
		switch (op->type & RZ_ANALYSIS_OP_MASK_WILDCARD) {
		case RZ_ANALYSIS_OP_TYPE_RET:
		case RZ_ANALYSIS_OP_TYPE_UJMP:
			rz_analysis_op_free(op);
			goto beach;
			break;
		default:
			break;
		}
		if (op->type == RZ_ANALYSIS_OP_TYPE_JMP) {
			addr = op->jump;
		} else {
			addr += op->size;
		}
		if (ret_val) {
			rz_strbuf_append(buf, "\n");
		}
		rz_analysis_op_free(op);
	}
beach:
	return true;
}

static void func_walk_blocks(RzCore *core, RzAnalysisFunction *f, bool fromHere, RzCmdStateOutput *state) {
	const bool orig_bb_middle = rz_config_get_b(core->config, "asm.bb.middle");
	rz_config_set_b(core->config, "asm.bb.middle", false);

	rz_pvector_sort(f->bbs, (RzPVectorComparator)bbcmp, NULL);

	RzAnalysisBlock *b;
	void **iter;

	if (state->mode == RZ_OUTPUT_MODE_JSON) {
		rz_cmd_state_output_array_start(state);

		rz_pvector_foreach (f->bbs, iter) {
			b = (RzAnalysisBlock *)*iter;
			if (fromHere) {
				if (b->addr < core->offset) {
					core->cons->null = true;
				} else {
					core->cons->null = false;
				}
			}
			ut8 *buf = malloc(b->size);
			if (!buf) {
				RZ_LOG_ERROR("core: cannot allocate %" PFMT64u " byte(s)\n", b->size);
				return;
			}
			rz_io_read_at_mapped(core->io, b->addr, buf, b->size);
			rz_core_print_disasm_json(core, b->addr, buf, b->size, 0, state->d.pj);
			free(buf);
		}
		rz_cmd_state_output_array_end(state);
	} else {
		RzReg *rreg = rz_analysis_get_reg(core->analysis);
		bool asm_lines = rz_config_get_i(core->config, "asm.lines.bb");
		bool emu = rz_config_get_i(core->config, "asm.emu");
		ut64 saved_gp = 0;
		ut8 *saved_arena = NULL;
		if (emu) {
			saved_gp = rz_analysis_get_gp(core->analysis);
			saved_arena = rz_reg_arena_peek(rreg);
		}
		rz_config_set_i(core->config, "asm.lines.bb", 0);

		rz_pvector_foreach (f->bbs, iter) {
			b = (RzAnalysisBlock *)*iter;
			pr_bb(core, f, b, emu, saved_gp, saved_arena, 'I', fromHere);
		}
		if (emu) {
			rz_analysis_set_gp(core->analysis, saved_gp);
			if (saved_arena) {
				rz_reg_arena_poke(rreg, saved_arena);
				RZ_FREE(saved_arena);
			}
		}
		rz_config_set_i(core->config, "asm.lines.bb", asm_lines);
	}
	rz_config_set_b(core->config, "asm.bb.middle", orig_bb_middle);
}

static inline char cmd_pxb_p(char input) {
	return IS_PRINTABLE(input) ? input : '.';
}

static inline ut32 cmd_pxb_k(const ut8 *buffer, int x) {
	return ((ut32)buffer[3 - x]) << (8 * x);
}

static void print_json_string(RzCore *core, const ut8 *block, ut32 len, RzStrEnc encoding, bool stop_at_nil, bool stop_at_unprintable) {
	char *section = get_section_name(core, core->offset);
	if (!section) {
		return;
	}
	ut32 dlength = 0;
	RzStrStringifyOpt opt = { 0 };
	opt.buffer = block;
	opt.length = len;
	opt.encoding = encoding;
	opt.json = true;
	opt.stop_at_nil = stop_at_nil;
	opt.stop_at_unprintable = stop_at_unprintable;
	char *dstring = rz_str_stringify_raw_buffer(&opt, &dlength);
	if (!dstring) {
		free(section);
		return;
	}

	PJ *pj = pj_new();
	if (!pj) {
		free(section);
		free(dstring);
		return;
	}

	const char *enc_name = rz_str_enc_as_string(encoding);
	pj_o(pj);
	pj_k(pj, "string");
	pj_raw(pj, "\"");
	pj_raw(pj, dstring);
	pj_raw(pj, "\"");
	pj_kn(pj, "offset", core->offset);
	pj_ks(pj, "section", section);
	pj_kn(pj, "length", dlength);
	pj_ks(pj, "type", enc_name);
	pj_end(pj);
	rz_cons_println(pj_string(pj));
	pj_free(pj);
	free(section);
	free(dstring);
}

static char *__op_refs(RzCore *core, RzAnalysisOp *op, int n) {
	RzStrBuf *sb = rz_strbuf_new("");
	if (n) {
		// RzList *list = rz_analysis_xrefs_get_from (core->analysis, op->addr);
		RzList *list = rz_analysis_xrefs_get_to(core->analysis, op->addr);
		RzAnalysisXRef *xref;
		RzListIter *iter;
		rz_list_foreach (list, iter, xref) {
			rz_strbuf_appendf(sb, "0x%08" PFMT64x " ", xref->to);
		}
		rz_list_free(list);
	} else {
		if (op->jump != UT64_MAX) {
			rz_strbuf_appendf(sb, "0x%08" PFMT64x " ", op->jump);
		}
		if (op->fail != UT64_MAX) {
			rz_strbuf_appendf(sb, "0x%08" PFMT64x " ", op->fail);
		}
		if (op->ptr != UT64_MAX) {
			if (rz_io_is_valid_offset(core->io, op->ptr, false)) {
				rz_strbuf_appendf(sb, "0x%08" PFMT64x " ", op->ptr);
			}
		}
	}
	char *res = rz_strbuf_drain(sb);
	rz_str_trim(res);
	return res;
}

static inline char *__refs(RzCore *core, ut64 x) {
	if (!core->print->hasrefs) {
		return NULL;
	}

	char *refs = core->print->hasrefs(core->print->user, x, RZ_OUTPUT_MODE_STANDARD);
	if (RZ_STR_ISNOTEMPTY(refs)) {
		rz_str_trim(refs);
	} else {
		RZ_FREE(refs);
	}
	return refs;
}

static bool cmd_pxr(RzCore *core, ut64 at, int len, RzCmdStateOutput *state, int wordsize, const char *query) {
	if (!len) {
		return true;
	}
	RzStrBuf *sb = rz_strbuf_new(NULL);
	if (!sb) {
		return false;
	}

	const ut8 *buf = core->block;
	bool be = rz_asm_is_big_endian_set(core->rasm);
	int end = RZ_MIN(core->blocksize, len);
	int bitsize = wordsize * 8;
	RzOutputMode mode = state->mode;
	if (mode == RZ_OUTPUT_MODE_TABLE) {
		RzTable *t = state->d.t;
		rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "addr");
		rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "value");
		rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_STRING, "refs");
		for (ut64 i = 0; i + wordsize < end; i += wordsize) {
			ut64 addr = at + i;
			ut64 val = rz_read_ble(buf + i, be, bitsize);
			char *refs = __refs(core, val);
			rz_table_add_rowf(t, "xxs", addr, val, refs);
			RZ_FREE(refs);
		}
		rz_table_query(t, query);
	} else if (mode == RZ_OUTPUT_MODE_JSON) {
		PJ *pj = state->d.pj;
		const int hex_depth = (int)rz_config_get_i(core->config, "hex.depth");
		pj_a(pj);
		for (ut64 i = 0; i + wordsize < end; i += wordsize) {
			ut64 addr = at + i;
			ut64 val = rz_read_ble(buf + i, be, bitsize);
			pj_o(pj);
			pj_kn(pj, "addr", addr);
			pj_kn(pj, "value", val);
			char *refs = __refs(core, val);
			if (refs) {
				char *refstr = rz_str_escape(refs);
				pj_ks(pj, "refstr", rz_str_trim_head_ro(refstr));
				free(refstr);

				pj_k(pj, "ref");
				free(rz_core_analysis_hasrefs_to_depth(core, val, pj, hex_depth));
			}
			pj_end(pj);
		}
		pj_end(pj);
	} else if (mode == RZ_OUTPUT_MODE_QUIET) {
		for (ut64 i = 0; i + wordsize < end; i += wordsize) {
			ut64 val = rz_read_ble(buf + i, be, bitsize);
			char *refs = __refs(core, val);
			rz_strbuf_appendf(sb, "%s\n", refs);
		}
	} else if (mode == RZ_OUTPUT_MODE_STANDARD) {
		char *hexdump_str = rz_core_print_hexdump_refs(core, core->offset, len, wordsize);
		rz_strbuf_append(sb, hexdump_str);
		free(hexdump_str);
	} else {
		rz_warn_if_reached();
		rz_strbuf_free(sb);
		return false;
	}
	if (mode == RZ_OUTPUT_MODE_STANDARD || mode == RZ_OUTPUT_MODE_QUIET) {
		char *res = rz_strbuf_drain(sb);
		rz_cons_print(res);
		free(res);
	} else {
		rz_strbuf_free(sb);
	}
	return true;
}

static void core_print_2bpp_row(const ut8 *buf, bool useColor) {
	const char *symbols = "#=-.";
	for (ut32 i = 0, c = 0; i < 8; i++) {
		if (buf[1] & ((1 << 7) >> i)) {
			c = 2;
		}
		if (buf[0] & ((1 << 7) >> i)) {
			c++;
		}
		if (useColor) {
			char *color = "";
			switch (c) {
			case 0:
				color = Color_BGWHITE;
				break;
			case 1:
				color = Color_BGRED;
				break;
			case 2:
				color = Color_BGBLUE;
				break;
			case 3:
				color = Color_BGBLACK;
				break;
			}
			rz_cons_printf("%s  ", color);
		} else {
			const char ch = symbols[c % 4];
			rz_cons_printf("%c%c", ch, ch);
		}
		c = 0;
	}
}

static void core_print_2bpp_tiles(RzCore *core, ut32 tiles) {
	const ut8 *buf = core->block;
	bool useColor = rz_config_get_i(core->config, "scr.color") > 0;
	for (ut32 i = 0; i < 8; i++) {
		for (ut32 r = 0; r < tiles; r++) {
			core_print_2bpp_row(buf + 2 * i + r * 16, useColor);
		}
		if (useColor) {
			rz_cons_printf(Color_RESET "\n");
		} else {
			rz_cons_printf("\n");
		}
	}
}

static void core_print_raw_buffer(RzStrStringifyOpt *opt) {
	char *str = rz_str_stringify_raw_buffer(opt, NULL);
	if (str) {
		rz_cons_strcat(str);
		free(str);
	}
}

static RzCmdStatus core_print_string_in_block(RzCore *core, bool stop_at_nil, bool stop_at_unprintable, ut32 offset, RzOutputMode mode, RzStrEnc str_encoding) {
	const ut8 *buffer = core->block + offset;
	const ut32 length = core->blocksize - offset;
	RzStrEnc encoding = str_encoding == RZ_STRING_ENC_SETTINGS ? core->bin->str_search_cfg.string_encoding : str_encoding;
	RzStrStringifyOpt opt = { 0 };

	if (encoding == RZ_STRING_ENC_GUESS) {
		encoding = rz_str_guess_encoding_from_buffer(buffer, length);
	}

	switch (mode) {
	case RZ_OUTPUT_MODE_STANDARD:
		opt.buffer = buffer;
		opt.length = length;
		opt.encoding = encoding;
		opt.stop_at_nil = stop_at_nil;
		opt.stop_at_unprintable = stop_at_unprintable;
		opt.user_unprintable = core->bin->str_search_cfg.user_unprintable;
		core_print_raw_buffer(&opt);
		break;
	case RZ_OUTPUT_MODE_JSON:
		print_json_string(core, buffer, length, encoding, stop_at_nil, stop_at_unprintable);
		break;
	default:
		RZ_LOG_ERROR("core: unsupported output mode\n");
		return RZ_CMD_STATUS_ERROR;
	}
	return RZ_CMD_STATUS_OK;
}

// "ps"
RZ_IPI RzCmdStatus rz_print_string_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	bool stop_at_nil = !strcmp(argv[2], "null");
	bool stop_at_unprintable = !strcmp(argv[2], "unprintable");
	RzStrEnc enc = rz_str_enc_string_as_type(argv[1]);
	return core_print_string_in_block(core, stop_at_nil, stop_at_unprintable, 0, mode, enc);
}

// "ps+"
RZ_IPI RzCmdStatus rz_print_string_as_libcpp_string_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	ut32 bitness = (ut32)rz_config_get_i(core->config, "asm.bits");
	bool big_endian = rz_config_get_b(core->config, "cfg.bigendian");

	switch (bitness) {
	case 32:
		/* fall-thru */
	case 64:
		break;
	default:
		RZ_LOG_ERROR("core: %u bits are not supported by %s\n", bitness, argv[0]);
		return RZ_CMD_STATUS_ERROR;
	}

	ut32 min_size = (bitness / 8) * 3;
	if (core->blocksize < 2 || core->blocksize < min_size) {
		RZ_LOG_ERROR("core: the block size is too small to read string (expected at least %u but got %u bytes).\n", core->blocksize, min_size);
		return RZ_CMD_STATUS_ERROR;
	}

	RzCmdStatus status = RZ_CMD_STATUS_ERROR;
	if (*core->block & 0x1) { // "long" string
		const ut8 *ptr = core->block + (bitness / 8) * 2;
		ut64 old_offset = core->offset;
		ut64 new_offset = rz_read_ble(ptr, big_endian, bitness);

		rz_core_seek(core, new_offset, SEEK_SET);
		rz_core_block_read(core);

		status = core_print_string_in_block(core, true, false, 0, mode, RZ_STRING_ENC_SETTINGS);

		rz_core_seek(core, old_offset, SEEK_SET);
		rz_core_block_read(core);
	} else {
		status = core_print_string_in_block(core, true, false, 1, mode, RZ_STRING_ENC_SETTINGS);
	}

	return status;
}

// "psb"
RZ_IPI RzCmdStatus rz_print_strings_current_block_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	RzListIter *it = NULL;
	RzDetectedString *detected = NULL;
	RzBin *bin = core->bin;
	RzUtilStrScanOptions scan_opt = {
		.max_str_length = core->blocksize,
		.min_str_length = bin->str_search_cfg.min_length,
		.prefer_big_endian = false,
		.check_ascii_freq = bin->str_search_cfg.check_ascii_freq,
	};

	RzList *found = rz_list_newf((RzListFree)rz_detected_string_free);
	if (!found) {
		RZ_LOG_ERROR("core: failed to allocate RzList\n");
		return RZ_CMD_STATUS_ERROR;
	}

	if (rz_scan_strings_raw(core->block, found, &scan_opt, 0, core->blocksize, RZ_STRING_ENC_GUESS) < 0) {
		rz_list_free(found);
		return RZ_CMD_STATUS_ERROR;
	}

	rz_list_foreach (found, it, detected) {
		ut64 address = core->offset + detected->addr;
		if (mode != RZ_OUTPUT_MODE_QUIET) {
			rz_print_offset(core->print, address, 0, 0, 0, 0, NULL);
		}
		RzStrEscOptions eopts = { 0 };
		eopts.keep_printable = true;
		char *escaped = rz_str_escape_utf8(detected->string, &eopts);
		rz_cons_printf("%s", escaped);
		free(escaped);
		rz_cons_newline();
	}

	rz_list_free(found);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_first_string_current_block_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	RzDetectedString *detected = NULL;
	RzBin *bin = core->bin;
	RzUtilStrScanOptions scan_opt = {
		.max_str_length = core->blocksize,
		.min_str_length = bin->str_search_cfg.min_length,
		.prefer_big_endian = false,
		.check_ascii_freq = bin->str_search_cfg.check_ascii_freq,
	};

	RzList *found = rz_list_newf((RzListFree)rz_detected_string_free);
	if (!found) {
		RZ_LOG_ERROR("core: failed to allocate RzList\n");
		return RZ_CMD_STATUS_ERROR;
	}

	if (rz_scan_strings_raw(core->block, found, &scan_opt, 0, core->blocksize, RZ_STRING_ENC_GUESS) < 0) {
		rz_list_free(found);
		return RZ_CMD_STATUS_ERROR;
	}

	detected = rz_list_first_val(found);
	if (detected) {
		rz_cons_memcat(detected->string, detected->size);
		rz_cons_newline();
	}

	rz_list_free(found);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pascal_string_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	RzStrStringifyOpt opt = { 0 };
	bool big_endian = rz_config_get_b(core->config, "cfg.bigendian");
	ut64 string_len = 0;
	ut32 offset = 0;

	if (!strcmp(argv[1], "8")) {
		string_len = (ut64)core->block[0];
		offset = 1;
	} else if (!strcmp(argv[1], "16")) {
		string_len = rz_read_ble16(core->block, big_endian);
		offset = 2;
	} else if (!strcmp(argv[1], "32")) {
		string_len = rz_read_ble32(core->block, big_endian);
		offset = 4;
	} else {
		string_len = rz_read_ble64(core->block, big_endian);
		offset = 8;
	}

	if (string_len < 1) {
		RZ_LOG_ERROR("core: string length is zero\n");
		return RZ_CMD_STATUS_ERROR;
	}

	if ((string_len + offset) > core->blocksize) {
		RZ_LOG_ERROR("core: string length exceeds block size\n");
		return RZ_CMD_STATUS_ERROR;
	}

	switch (mode) {
	case RZ_OUTPUT_MODE_STANDARD:
		opt.buffer = core->block + offset;
		opt.length = string_len;
		opt.encoding = RZ_STRING_ENC_8BIT;
		opt.stop_at_nil = true;
		opt.stop_at_unprintable = true;
		core_print_raw_buffer(&opt);
		break;
	case RZ_OUTPUT_MODE_JSON:
		print_json_string(core, core->block + offset, string_len, RZ_STRING_ENC_8BIT, true, true);
		break;
	default:
		RZ_LOG_ERROR("core: unsupported output mode\n");
		return RZ_CMD_STATUS_ERROR;
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_string_wrap_width_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	int h, w = rz_cons_get_size(&h);
	int colwidth = rz_config_get_i(core->config, "hex.cols") * 2;
	int width = (colwidth == 32) ? w : colwidth; // w;
	ut64 blocksize = core->blocksize;

	ut64 len = (h * w) / 3;
	rz_core_block_size(core, len);

	RzStrStringifyOpt opt = { 0 };
	opt.buffer = core->block;
	opt.length = len;
	opt.encoding = RZ_STRING_ENC_8BIT;
	opt.wrap_at = width;
	core_print_raw_buffer(&opt);
	rz_core_block_size(core, blocksize);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_string_escaped_newlines_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	RzStrStringifyOpt opt = { 0 };
	opt.buffer = core->block;
	opt.length = core->blocksize;
	opt.encoding = RZ_STRING_ENC_8BIT;
	opt.escape_nl = true;
	core_print_raw_buffer(&opt);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_string_c_cpp_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	char *str = rz_core_print_string_c_cpp(core);
	if (!str) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_println(str);
	free(str);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_hex_of_assembly_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	char *buf = rz_core_hex_of_assembly(core, argv[1]);
	if (!buf) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_println(buf);
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_esil_of_assembly_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	char *buf = rz_core_esil_of_assembly(core, argv[1]);
	if (!buf) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_print(buf);
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_assembly_of_hex_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	ut8 *hex = calloc(1, strlen(argv[1]) + 1);
	if (!hex) {
		RZ_LOG_ERROR("Fail to allocate memory\n");
		return RZ_CMD_STATUS_ERROR;
	}
	int len = rz_hex_str2bin(argv[1], hex);
	if (len < 1) {
		RZ_LOG_ERROR("rz_hex_str2bin: invalid hexstr\n");
		free(hex);
		return RZ_CMD_STATUS_ERROR;
	}
	char *buf = rz_core_assembly_of_hex(core, hex, len);
	free(hex);
	if (!buf) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_print(buf);
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_assembly_of_hex_alias_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	return rz_assembly_of_hex_handler(core, argc, argv, mode);
}

RZ_IPI RzCmdStatus rz_print_instructions_handler(RzCore *core, int argc, const char **argv) {
	if (argc <= 1) {
		RZ_LOG_ERROR("Invalid arguments\n");
		return RZ_CMD_STATUS_ERROR;
	}
	ut64 len = rz_num_math(core->num, argv[1]);
	if (len == 0) {
		RZ_LOG_ERROR("The argument cannot be zero\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_core_print_disasm_instructions(core, len, 0);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_instructions_function_handler(RzCore *core, int argc, const char **argv) {
	const RzAnalysisFunction *f = rz_analysis_get_fcn_in(core->analysis, core->offset,
		RZ_ANALYSIS_FCN_TYPE_FCN | RZ_ANALYSIS_FCN_TYPE_SYM);
	if (!f) {
		RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}
	ut64 fcn_size = rz_analysis_function_linear_size((RzAnalysisFunction *)f);
	if (fcn_size == 0) {
		RZ_LOG_ERROR("The function size is zero\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_core_print_disasm_instructions(core, fcn_size, 0);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_esil_of_hex_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	ut8 *hex = calloc(1, strlen(argv[1]) + 1);
	if (!hex) {
		RZ_LOG_ERROR("Fail to allocate memory\n");
		return RZ_CMD_STATUS_ERROR;
	}
	int len = rz_hex_str2bin(argv[1], hex);
	if (len < 1) {
		RZ_LOG_ERROR("rz_hex_str2bin: invalid hexstr\n");
		free(hex);
		return RZ_CMD_STATUS_ERROR;
	}
	char *buf = rz_core_esil_of_hex(core, hex, len);
	if (!buf) {
		// rz_core_esil_of_hex outputs the error message
		free(hex);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_print(buf);
	free(buf);
	free(hex);
	return RZ_CMD_STATUS_OK;
}

static int lenof(ut64 off, int two) {
	char buf[64];
	buf[0] = 0;
	if (two) {
		snprintf(buf, sizeof(buf), "+0x%" PFMT64x, off);
	} else {
		snprintf(buf, sizeof(buf), "0x%08" PFMT64x, off);
	}
	return strlen(buf);
}

RZ_API void rz_print_offset_sg(RzPrint *p, ut64 off, int invert, int offseg, int seggrn, int offdec, int delta, const char *label) {
	char space[32] = {
		0
	};
	const char *reset = p->resetbg ? Color_RESET : Color_RESET_NOBG;
	bool show_color = p->flags & RZ_PRINT_FLAGS_COLOR;
	if (show_color) {
		const char *k = rz_cons_singleton()->context->pal.offset; // TODO etooslow. must cache
		const char *inv = invert ? RZ_CONS_INVERT(true, true) : "";
		if (offseg) {
			ut32 s, a;
			a = off & 0xffff;
			s = ((off - a) >> seggrn) & 0xffff;
			if (offdec) {
				snprintf(space, sizeof(space), "%d:%d", s, a);
				rz_cons_printf("%s%s%9s%s", k, inv, space, reset);
			} else {
				rz_cons_printf("%s%s%04x:%04x%s", k, inv, s, a, reset);
			}
		} else {
			int sz = lenof(off, 0);
			int sz2 = lenof(delta, 1);
			if (delta > 0 || label) {
				if (label) {
					const int label_padding = 10;
					if (delta > 0) {
						char *pad = rz_str_pad(' ', sz - sz2 + label_padding);
						if (offdec) {
							rz_cons_printf("%s%s%s%s+%d%s", k, inv, label, reset, delta, pad);
						} else {
							rz_cons_printf("%s%s%s%s+0x%x%s", k, inv, label, reset, delta, pad);
						}
						free(pad);
					} else {
						char *pad = rz_str_pad(' ', sz + label_padding);
						rz_cons_printf("%s%s%s%s%s", k, inv, label, reset, pad);
						free(pad);
					}
				} else {
					char *pad = rz_str_pad(' ', sz - sz2);
					if (offdec) {
						rz_cons_printf("%s+%d%s", pad, delta, reset);
					} else {
						rz_cons_printf("%s+0x%x%s", pad, delta, reset);
					}
					free(pad);
				}
			} else {
				if (offdec) {
					snprintf(space, sizeof(space), "%" PFMT64u, off);
					rz_cons_printf("%s%s%10s%s", k, inv, space, reset);
				} else {
					if (p->wide_offsets) {
						rz_cons_printf("%s%s0x%016" PFMT64x "%s", k, inv, off, reset);
					} else {
						rz_cons_printf("%s%s0x%08" PFMT64x "%s", k, inv, off, reset);
					}
				}
			}
		}
		rz_cons_print(" ");
	} else {
		if (offseg) {
			ut32 s, a;
			a = off & 0xffff;
			s = (off - a) >> seggrn;
			if (offdec) {
				snprintf(space, sizeof(space), "%d:%d", s & 0xffff, a & 0xffff);
				rz_cons_printf("%9s%s", space, reset);
			} else {
				rz_cons_printf("%04x:%04x", s & 0xFFFF, a & 0xFFFF);
			}
		} else {
			int sz = lenof(off, 0);
			int sz2 = lenof(delta, 1);
			if (delta > 0) {
				char *pad = rz_str_pad(' ', sz - 5 - sz2 - 3);
				if (offdec) {
					rz_cons_printf("%s+%d%s", pad, delta, reset);
				} else {
					rz_cons_printf("%s+0x%x%s", pad, delta, reset);
				}
				free(pad);
			} else {
				if (offdec) {
					snprintf(space, sizeof(space), "%" PFMT64u, off);
					rz_cons_printf("%10s", space);
				} else {
					rz_cons_printf("0x%08" PFMT64x " ", off);
				}
			}
		}
	}
}

// TODO : move to rz_util? .. depends on rz_cons...
// XXX: dupe of rz_print_addr
RZ_API void rz_print_offset(RzPrint *p, ut64 off, int invert, int offseg, int offdec, int delta, const char *label) {
	rz_print_offset_sg(p, off, invert, offseg, 4, offdec, delta, label);
}

RZ_IPI RzCmdStatus rz_print_utf8_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	return core_print_string_in_block(core, true, false, 0, mode, RZ_STRING_ENC_UTF8);
}

RZ_IPI RzCmdStatus rz_print_utf16le_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	return core_print_string_in_block(core, true, false, 0, mode, RZ_STRING_ENC_UTF16LE);
}

RZ_IPI RzCmdStatus rz_print_utf32le_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	return core_print_string_in_block(core, true, false, 0, mode, RZ_STRING_ENC_UTF32LE);
}

RZ_IPI RzCmdStatus rz_print_utf16be_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	return core_print_string_in_block(core, true, false, 0, mode, RZ_STRING_ENC_UTF16BE);
}

RZ_IPI RzCmdStatus rz_print_utf32be_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	return core_print_string_in_block(core, true, false, 0, mode, RZ_STRING_ENC_UTF32BE);
}

RZ_IPI RzCmdStatus rz_print_hexdump_annotated_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	if (len % 16) {
		len += 16 - (len % 16);
	}
	annotated_hexdump(core, len);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_op_analysis_color_map_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	return bool2status(cmd_print_pxA(core, len, state->mode));
}

void print_cursor(RzPrint *p, int cur, int len, int set) {
	if (rz_print_have_cursor(p, cur, len)) {
		rz_cons_printf("%s", RZ_CONS_INVERT(set, 1));
	}
}

RZ_IPI RzCmdStatus rz_print_hexdump_bits_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	if (!len) {
		return RZ_CMD_STATUS_OK;
	}

	char buf[32];
	for (int i = 0, c = 0; i < len; i++, c++) {
		if (c == 0) {
			ut64 ea = core->offset + i;
			if (core->print->pava) {
				ut64 va = rz_io_p2v(core->io, ea);
				if (va != UT64_MAX) {
					ea = va;
				}
			}
			char *string = rz_print_section_str(core->print, ea);
			rz_cons_print(string);
			free(string);
			rz_print_offset(core->print, ea, 0, 0, 0, 0, NULL);
		}
		rz_str_bits(buf, core->block + i, 8, NULL);

		// split bits
		memmove(buf + 5, buf + 4, 5);
		buf[4] = 0;

		print_cursor(core->print, i, 1, 1);
		rz_cons_printf("%s.%s  ", buf, buf + 5);
		print_cursor(core->print, i, 1, 0);
		if (c == 3) {
			const ut8 *b = core->block + i - 3;
			ut32 (*k)(const ut8 *, int) = cmd_pxb_k;
			char (*p)(char) = cmd_pxb_p;

			ut32 n = k(b, 0) | k(b, 1) | k(b, 2) | k(b, 3);
			rz_cons_printf("0x%08x  %c%c%c%c\n",
				n, p(b[0]), p(b[1]), p(b[2]), p(b[3]));
			c = -1;
		}
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_hexdump_comments_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	return bool2status(rz_core_print_hexdump_or_hexdiff(core, RZ_OUTPUT_MODE_STANDARD, core->offset, len, true));
}

RZ_IPI RzCmdStatus rz_print_hexdump_signed_integer_common_handler(RzCore *core, int argc, const char **argv,
	RzCmdStateOutput *state, ut8 n) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	return bool2status(rz_core_print_dump(core, state->mode, core->offset, n, len, RZ_CORE_PRINT_FORMAT_TYPE_INTEGER));
}

RZ_IPI RzCmdStatus rz_print_hexdump_signed_integer_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_signed_integer_common_handler(core, argc, argv, state, 1);
}
RZ_IPI RzCmdStatus rz_print_hexdump_signed_integer2_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_signed_integer_common_handler(core, argc, argv, state, 2);
}
RZ_IPI RzCmdStatus rz_print_hexdump_signed_integer4_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_signed_integer_common_handler(core, argc, argv, state, 4);
}
RZ_IPI RzCmdStatus rz_print_hexdump_signed_integer8_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_signed_integer_common_handler(core, argc, argv, state, 8);
}

RZ_IPI RzCmdStatus rz_print_hexdump_emoji_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	if (!len) {
		return RZ_CMD_STATUS_OK;
	}
	static const char emoji[] = {
		'\x8c', '\x80', '\x8c', '\x82', '\x8c', '\x85', '\x8c', '\x88',
		'\x8c', '\x99', '\x8c', '\x9e', '\x8c', '\x9f', '\x8c', '\xa0',
		'\x8c', '\xb0', '\x8c', '\xb1', '\x8c', '\xb2', '\x8c', '\xb3',
		'\x8c', '\xb4', '\x8c', '\xb5', '\x8c', '\xb7', '\x8c', '\xb8',
		'\x8c', '\xb9', '\x8c', '\xba', '\x8c', '\xbb', '\x8c', '\xbc',
		'\x8c', '\xbd', '\x8c', '\xbe', '\x8c', '\xbf', '\x8d', '\x80',
		'\x8d', '\x81', '\x8d', '\x82', '\x8d', '\x83', '\x8d', '\x84',
		'\x8d', '\x85', '\x8d', '\x86', '\x8d', '\x87', '\x8d', '\x88',
		'\x8d', '\x89', '\x8d', '\x8a', '\x8d', '\x8b', '\x8d', '\x8c',
		'\x8d', '\x8d', '\x8d', '\x8e', '\x8d', '\x8f', '\x8d', '\x90',
		'\x8d', '\x91', '\x8d', '\x92', '\x8d', '\x93', '\x8d', '\x94',
		'\x8d', '\x95', '\x8d', '\x96', '\x8d', '\x97', '\x8d', '\x98',
		'\x8d', '\x9c', '\x8d', '\x9d', '\x8d', '\x9e', '\x8d', '\x9f',
		'\x8d', '\xa0', '\x8d', '\xa1', '\x8d', '\xa2', '\x8d', '\xa3',
		'\x8d', '\xa4', '\x8d', '\xa5', '\x8d', '\xa6', '\x8d', '\xa7',
		'\x8d', '\xa8', '\x8d', '\xa9', '\x8d', '\xaa', '\x8d', '\xab',
		'\x8d', '\xac', '\x8d', '\xad', '\x8d', '\xae', '\x8d', '\xaf',
		'\x8d', '\xb0', '\x8d', '\xb1', '\x8d', '\xb2', '\x8d', '\xb3',
		'\x8d', '\xb4', '\x8d', '\xb5', '\x8d', '\xb6', '\x8d', '\xb7',
		'\x8d', '\xb8', '\x8d', '\xb9', '\x8d', '\xba', '\x8d', '\xbb',
		'\x8d', '\xbc', '\x8e', '\x80', '\x8e', '\x81', '\x8e', '\x82',
		'\x8e', '\x83', '\x8e', '\x84', '\x8e', '\x85', '\x8e', '\x88',
		'\x8e', '\x89', '\x8e', '\x8a', '\x8e', '\x8b', '\x8e', '\x8c',
		'\x8e', '\x8d', '\x8e', '\x8e', '\x8e', '\x8f', '\x8e', '\x92',
		'\x8e', '\x93', '\x8e', '\xa0', '\x8e', '\xa1', '\x8e', '\xa2',
		'\x8e', '\xa3', '\x8e', '\xa4', '\x8e', '\xa5', '\x8e', '\xa6',
		'\x8e', '\xa7', '\x8e', '\xa8', '\x8e', '\xa9', '\x8e', '\xaa',
		'\x8e', '\xab', '\x8e', '\xac', '\x8e', '\xad', '\x8e', '\xae',
		'\x8e', '\xaf', '\x8e', '\xb0', '\x8e', '\xb1', '\x8e', '\xb2',
		'\x8e', '\xb3', '\x8e', '\xb4', '\x8e', '\xb5', '\x8e', '\xb7',
		'\x8e', '\xb8', '\x8e', '\xb9', '\x8e', '\xba', '\x8e', '\xbb',
		'\x8e', '\xbd', '\x8e', '\xbe', '\x8e', '\xbf', '\x8f', '\x80',
		'\x8f', '\x81', '\x8f', '\x82', '\x8f', '\x83', '\x8f', '\x84',
		'\x8f', '\x86', '\x8f', '\x87', '\x8f', '\x88', '\x8f', '\x89',
		'\x8f', '\x8a', '\x90', '\x80', '\x90', '\x81', '\x90', '\x82',
		'\x90', '\x83', '\x90', '\x84', '\x90', '\x85', '\x90', '\x86',
		'\x90', '\x87', '\x90', '\x88', '\x90', '\x89', '\x90', '\x8a',
		'\x90', '\x8b', '\x90', '\x8c', '\x90', '\x8d', '\x90', '\x8e',
		'\x90', '\x8f', '\x90', '\x90', '\x90', '\x91', '\x90', '\x92',
		'\x90', '\x93', '\x90', '\x94', '\x90', '\x95', '\x90', '\x96',
		'\x90', '\x97', '\x90', '\x98', '\x90', '\x99', '\x90', '\x9a',
		'\x90', '\x9b', '\x90', '\x9c', '\x90', '\x9d', '\x90', '\x9e',
		'\x90', '\x9f', '\x90', '\xa0', '\x90', '\xa1', '\x90', '\xa2',
		'\x90', '\xa3', '\x90', '\xa4', '\x90', '\xa5', '\x90', '\xa6',
		'\x90', '\xa7', '\x90', '\xa8', '\x90', '\xa9', '\x90', '\xaa',
		'\x90', '\xab', '\x90', '\xac', '\x90', '\xad', '\x90', '\xae',
		'\x90', '\xaf', '\x90', '\xb0', '\x90', '\xb1', '\x90', '\xb2',
		'\x90', '\xb3', '\x90', '\xb4', '\x90', '\xb5', '\x90', '\xb6',
		'\x90', '\xb7', '\x90', '\xb8', '\x90', '\xb9', '\x90', '\xba',
		'\x90', '\xbb', '\x90', '\xbc', '\x90', '\xbd', '\x90', '\xbe',
		'\x91', '\x80', '\x91', '\x82', '\x91', '\x83', '\x91', '\x84',
		'\x91', '\x85', '\x91', '\x86', '\x91', '\x87', '\x91', '\x88',
		'\x91', '\x89', '\x91', '\x8a', '\x91', '\x8b', '\x91', '\x8c',
		'\x91', '\x8d', '\x91', '\x8e', '\x91', '\x8f', '\x91', '\x90',
		'\x91', '\x91', '\x91', '\x92', '\x91', '\x93', '\x91', '\x94',
		'\x91', '\x95', '\x91', '\x96', '\x91', '\x97', '\x91', '\x98',
		'\x91', '\x99', '\x91', '\x9a', '\x91', '\x9b', '\x91', '\x9c',
		'\x91', '\x9d', '\x91', '\x9e', '\x91', '\x9f', '\x91', '\xa0',
		'\x91', '\xa1', '\x91', '\xa2', '\x91', '\xa3', '\x91', '\xa4',
		'\x91', '\xa5', '\x91', '\xa6', '\x91', '\xa7', '\x91', '\xa8',
		'\x91', '\xa9', '\x91', '\xaa', '\x91', '\xae', '\x91', '\xaf',
		'\x91', '\xba', '\x91', '\xbb', '\x91', '\xbc', '\x91', '\xbd',
		'\x91', '\xbe', '\x91', '\xbf', '\x92', '\x80', '\x92', '\x81',
		'\x92', '\x82', '\x92', '\x83', '\x92', '\x84', '\x92', '\x85'
	};
	int cols = core->print->cols;
	if (cols < 1) {
		cols = 1;
	}
	for (int i = 0; i < len; i += cols) {
		rz_print_addr(core->print, core->offset + i);
		for (int j = i; j < i + cols; j += 1) {
			ut8 *p = (ut8 *)core->block + j;
			if (j < len) {
				rz_cons_printf("\xf0\x9f%c%c ", emoji[*p * 2], emoji[*p * 2 + 1]);
			} else {
				rz_cons_print("  ");
			}
		}
		rz_cons_print(" ");
		for (int j = i; j < len && j < i + cols; j += 1) {
			ut8 *p = (ut8 *)core->block + j;
			rz_print_byte(core->print, "%c", j, *p);
		}
		rz_cons_newline();
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_hexdump_function_handler(RzCore *core, int argc, const char **argv) {
	RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, core->offset, RZ_ANALYSIS_FCN_TYPE_ROOT);
	if (!function) {
		function = rz_analysis_get_fcn_in(core->analysis, core->offset, 0);
	}
	if (!function) {
		RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}
	// Disable printing header for RzPrint
	int old_flags = core->print->flags;
	core->print->flags &= ~RZ_PRINT_FLAGS_HEADER;

	RzAnalysisBlock *b;
	void **iter;
	rz_pvector_foreach (function->bbs, iter) {
		b = (RzAnalysisBlock *)*iter;
		ut8 *buf = malloc(b->size);
		if (!buf) {
			RZ_LOG_ERROR("core: cannot allocate %" PFMT64u " byte(s)\n", b->size);
			core->print->flags = old_flags;
			return RZ_CMD_STATUS_ERROR;
		}
		rz_io_read_at_mapped(core->io, b->addr, buf, b->size);
		rz_core_print_hexdump(core, b->addr, buf, b->size, 0, 16, 0);
		free(buf);
	}
	core->print->flags = old_flags;
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_hexdump_hexii_handler(RzCore *core, int argc, const char **argv) {
	core->print->show_offset = rz_config_get_i(core->config, "hex.offset");
	rz_print_hexii(core->print, core->offset, core->block,
		(int)core->blocksize, rz_config_get_i(core->config, "hex.cols"));
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_hexword_references_common_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state, int wordsize) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	const char *query = argc > 2 ? argv[2] : NULL;
	switch (wordsize) {
	case 1:
	case 2:
	case 4:
	case 8:
		cmd_pxr(core, core->offset, len, state, wordsize, query);
		break;
	default:
		rz_warn_if_reached();
		return RZ_CMD_STATUS_ERROR;
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_hexword_references_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int wordsize = rz_analysis_get_address_bits(core->analysis) / 8;
	return rz_print_hexword_references_common_handler(core, argc, argv, state, wordsize);
}

RZ_IPI RzCmdStatus rz_print_hexword_references_1_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexword_references_common_handler(core, argc, argv, state, 1);
}

RZ_IPI RzCmdStatus rz_print_hexword_references_2_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexword_references_common_handler(core, argc, argv, state, 2);
}

RZ_IPI RzCmdStatus rz_print_hexword_references_4_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexword_references_common_handler(core, argc, argv, state, 4);
}

RZ_IPI RzCmdStatus rz_print_hexword_references_8_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexword_references_common_handler(core, argc, argv, state, 8);
}

RZ_IPI RzCmdStatus rz_print_hexdump_sparse_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	if (!len) {
		return RZ_CMD_STATUS_OK;
	}
	core->print->flags |= RZ_PRINT_FLAGS_SPARSE;
	rz_core_print_hexdump(core, core->offset, core->block, len, 16, 1, 1);
	core->print->flags &= (int)(((ut32)-1) & (~RZ_PRINT_FLAGS_SPARSE));
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_delta_pointer_table_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	if (!len) {
		return RZ_CMD_STATUS_OK;
	}
	ut64 origin = argc > 2 ? rz_num_math(core->num, argv[2]) : core->offset;
	// _pointer_table does rz_core_cmd with @, so it modifies core->block
	// and this results in an UAF access when iterating over the jmptable
	// so we do a new allocation to avoid that issue
	ut8 *block = calloc(len, 1);
	if (!block) {
		return RZ_CMD_STATUS_ERROR;
	}
	memcpy(block, core->block, len);
	_pointer_table(core, origin, core->offset, block, len, 4, state->mode);
	free(block);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_hexdump_hexless_bytes_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	core->print->flags |= RZ_PRINT_FLAGS_NONHEX;
	rz_core_print_hexdump(core, core->offset,
		core->block, len, 8, 1, 1);
	core->print->flags &= ~RZ_PRINT_FLAGS_NONHEX;
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_hexdump_hexless_words_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	if (!len) {
		return RZ_CMD_STATUS_OK;
	}
	ut8 *buf = calloc(len, 4);
	if (!buf) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_io_read_at_mapped(core->io, core->offset, buf, len * 4);
	core->print->flags |= RZ_PRINT_FLAGS_NONHEX;
	rz_core_print_hexdump(core, core->offset, buf, len * 4, 8, 1, 1);
	core->print->flags &= ~RZ_PRINT_FLAGS_NONHEX;
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_hexdump_hexpair_bytes_handler(RzCore *core, int argc, const char **argv) {
	int len = (int)rz_str_nlen((const char *)core->block, core->blocksize);
	if (!len) {
		return RZ_CMD_STATUS_OK;
	}
	rz_print_bytes(core->print, core->block, len, "%02x");
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_hexdump_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	return bool2status(rz_core_print_hexdump_or_hexdiff(core, state->mode, core->offset, len, false));
}

RZ_IPI RzCmdStatus rz_print_hexdump_n_lines_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	return bool2status(rz_core_print_hexdump_or_hexdiff(core, state->mode, core->offset, core->print->cols * len, false));
}

RZ_IPI RzCmdStatus rz_print_hexdump_hex_common_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state, ut8 n) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	return bool2status(rz_core_print_dump(core, state->mode, core->offset, n, len, RZ_CORE_PRINT_FORMAT_TYPE_HEXADECIMAL));
}

RZ_IPI RzCmdStatus rz_print_hexdump_hex2_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_hex_common_handler(core, argc, argv, state, 2);
}
RZ_IPI RzCmdStatus rz_print_hexdump_hex4_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_hex_common_handler(core, argc, argv, state, 4);
}
RZ_IPI RzCmdStatus rz_print_hexdump_hex8_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_hex_common_handler(core, argc, argv, state, 8);
}

RZ_IPI RzCmdStatus rz_print_hexdump_hexl_common_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state, ut8 n) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	bool hex_offset = rz_config_get_b(core->config, "hex.offset");
	bool quiet = state->mode == RZ_OUTPUT_MODE_QUIET || state->mode == RZ_OUTPUT_MODE_QUIETEST;
	return bool2status(rz_core_print_hexdump_byline(core, !quiet && hex_offset, core->offset, len, n));
}

RZ_IPI RzCmdStatus rz_print_hexdump_hex2l_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_hexl_common_handler(core, argc, argv, state, 2);
}
RZ_IPI RzCmdStatus rz_print_hexdump_hex4l_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_hexl_common_handler(core, argc, argv, state, 4);
}
RZ_IPI RzCmdStatus rz_print_hexdump_hex8l_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_hexl_common_handler(core, argc, argv, state, 8);
}

RZ_IPI RzCmdStatus rz_print_hexdump_oct_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)core->blocksize;
	return bool2status(rz_core_print_dump(core, RZ_OUTPUT_MODE_STANDARD, core->offset, 1, len, RZ_CORE_PRINT_FORMAT_TYPE_OCTAL));
}

#define CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(name, type) \
	RZ_IPI RzCmdStatus name(RzCore *core, int argc, const char **argv) { \
		const int size = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize; \
		if (size > core->blocksize_max) { \
			RZ_LOG_ERROR("Size exceeds max size (%u)\n", core->blocksize_max); \
			return RZ_CMD_STATUS_ERROR; \
		} \
		if (size <= 0) { \
			RZ_LOG_ERROR("Size must be greater than 0\n"); \
			return RZ_CMD_STATUS_ERROR; \
		} \
		char *code = rz_lang_byte_array(core->block, size, type); \
		if (RZ_STR_ISNOTEMPTY(code)) { \
			rz_cons_println(code); \
		} \
		RzCmdStatus result = code ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR; \
		free(code); \
		return result; \
	}

#define CMD_PRINT_BYTE_ARRAY_HANDLER_ENDIAN(name, type) \
	RZ_IPI RzCmdStatus name(RzCore *core, int argc, const char **argv) { \
		bool big_endian = rz_config_get_b(core->config, "cfg.bigendian"); \
		char *code = rz_lang_byte_array(core->block, core->blocksize, big_endian ? type##_BE : type##_LE); \
		if (RZ_STR_ISNOTEMPTY(code)) { \
			rz_cons_println(code); \
		} \
		RzCmdStatus result = code ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR; \
		free(code); \
		return result; \
	}

CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_rizin_handler, RZ_LANG_BYTE_ARRAY_RIZIN);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_asm_handler, RZ_LANG_BYTE_ARRAY_ASM);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_bash_handler, RZ_LANG_BYTE_ARRAY_BASH);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_c_cpp_bytes_handler, RZ_LANG_BYTE_ARRAY_C_CPP_BYTES);
CMD_PRINT_BYTE_ARRAY_HANDLER_ENDIAN(rz_cmd_print_byte_array_c_cpp_half_word_handler, RZ_LANG_BYTE_ARRAY_C_CPP_HALFWORDS);
CMD_PRINT_BYTE_ARRAY_HANDLER_ENDIAN(rz_cmd_print_byte_array_c_cpp_word_handler, RZ_LANG_BYTE_ARRAY_C_CPP_WORDS);
CMD_PRINT_BYTE_ARRAY_HANDLER_ENDIAN(rz_cmd_print_byte_array_c_cpp_double_word_handler, RZ_LANG_BYTE_ARRAY_C_CPP_DOUBLEWORDS);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_golang_handler, RZ_LANG_BYTE_ARRAY_GOLANG);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_java_handler, RZ_LANG_BYTE_ARRAY_JAVA);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_json_handler, RZ_LANG_BYTE_ARRAY_JSON);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_kotlin_handler, RZ_LANG_BYTE_ARRAY_KOTLIN);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_nodejs_handler, RZ_LANG_BYTE_ARRAY_NODEJS);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_objc_handler, RZ_LANG_BYTE_ARRAY_OBJECTIVE_C);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_python_handler, RZ_LANG_BYTE_ARRAY_PYTHON);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_rust_handler, RZ_LANG_BYTE_ARRAY_RUST);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_swift_handler, RZ_LANG_BYTE_ARRAY_SWIFT);
CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL(rz_cmd_print_byte_array_yara_handler, RZ_LANG_BYTE_ARRAY_YARA);
#undef CMD_PRINT_BYTE_ARRAY_HANDLER_NORMAL
#undef CMD_PRINT_BYTE_ARRAY_HANDLER_ENDIAN

RZ_IPI RzCmdStatus rz_cmd_print_byte_array_with_inst_handler(RzCore *core, int argc, const char **argv) {
	rz_core_block_read(core);
	const int size = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (size > core->blocksize_max) {
		RZ_LOG_ERROR("Size exceeds max size (%u)\n", core->blocksize_max);
		return RZ_CMD_STATUS_ERROR;
	}
	if (size <= 0) {
		RZ_LOG_ERROR("Size must be greater 0\n");
		return RZ_CMD_STATUS_ERROR;
	}
	char *code = rz_core_print_bytes_with_inst(core, core->block, core->offset, size);
	if (!code) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_println(code);
	free(code);
	return RZ_CMD_STATUS_OK;
}

static void disassembly_as_table(RzTable *t, RzCore *core, ut64 addr, int n_instrs, int n_bytes) {
	ut8 buffer[256];
	rz_table_set_columnsf(t, "snssssss", "name", "addr", "bytes", "disasm", "comment", "esil", "refs", "xrefs");
	const int minopsz = 1;
	const int options = RZ_ANALYSIS_OP_MASK_BASIC | RZ_ANALYSIS_OP_MASK_HINT | RZ_ANALYSIS_OP_MASK_DISASM | RZ_ANALYSIS_OP_MASK_ESIL;
	ut64 offset = addr;
	ut64 inc = 0;
	for (int i = 0, j = 0; rz_disasm_check_end(n_instrs, i, n_bytes, j); i++, offset += inc, j += inc) {
		RzAnalysisOp *op = rz_core_analysis_op(core, offset, options);
		if (!op || op->size < 1) {
			i += minopsz;
			inc = minopsz;
			continue;
		}
		const char *comment = rz_meta_get_string(core->analysis, RZ_META_TYPE_COMMENT, offset);
		if (!comment) {
			comment = "";
		}
		rz_io_read_at_mapped(core->io, offset, buffer, RZ_MIN(op->size, sizeof(buffer)));
		char *bytes = rz_hex_bin2strdup(buffer, op->size);
		RzFlagItem *flag = rz_flag_get_i(core->flags, offset);
		char *function_name = flag ? flag->name : "";
		const char *esil = RZ_STRBUF_SAFEGET(&op->esil);
		char *refs = __op_refs(core, op, 0);
		char *xrefs = __op_refs(core, op, 1);
		rz_table_add_rowf(t, "sXssssss", function_name, offset, bytes, op->mnemonic, comment, esil, refs, xrefs);
		free(bytes);
		free(xrefs);
		free(refs);
		inc = op->size;
		rz_analysis_op_free(op);
	}
}

static bool core_disassembly(RzCore *core, int n_bytes, int n_instrs, RzCmdStateOutput *state, bool cbytes) {
	ut64 offset = rz_core_backward_offset(core, core->offset, &n_instrs, &n_bytes);

	if (n_bytes == 0) {
		int max_op_size = rz_analysis_archinfo(core->analysis, RZ_ANALYSIS_ARCHINFO_MAX_OP_SIZE);
		if (max_op_size < 1) {
			// Just assume some random default value.
			max_op_size = 1;
		}
		n_bytes = max_op_size * n_instrs;
	}

	RZ_LOG_VERBOSE("disassembly at: 0x%" PFMT64x " "
		       "blocksize: %" PFMT32d " "
		       "n_bytes: %" PFMT32d " "
		       "n_instrs: %" PFMT32d "\n",
		core->offset, core->blocksize, n_bytes, n_instrs);
	RzCoreDisasmOptions disasm_options = {
		.cbytes = cbytes,
	};

	ut8 *buf = RZ_NEWS0(ut8, n_bytes + 1);
	if (!buf) {
		RZ_LOG_ERROR("Failed to allocate memory\n");
		return false;
	}
	bool ret = true;
	if (!rz_io_read_at_mapped(core->io, offset, buf, n_bytes + 1)) {
		// Even with an error, keep on disassembling as at least some data may be read successfully
		RZ_LOG_WARN("Failed to read chunk of size 0x%" PFMT64x " at 0x%" PFMT64x " from io.\n", (ut64)(n_bytes + 1), offset);
		ret = false;
	}
	switch (state->mode) {
	case RZ_OUTPUT_MODE_STANDARD:
		rz_core_print_disasm(core, offset, buf, n_bytes,
			n_bytes > 0 && !n_instrs ? n_bytes : n_instrs, state, &disasm_options);
		break;
	case RZ_OUTPUT_MODE_TABLE:
		disassembly_as_table(state->d.t, core, offset, n_instrs, n_bytes);
		break;
	case RZ_OUTPUT_MODE_JSON:
		rz_cmd_state_output_array_start(state);
		rz_core_print_disasm_json(core, offset, buf, n_bytes, n_instrs, state->d.pj);
		rz_cmd_state_output_array_end(state);
		break;
	case RZ_OUTPUT_MODE_QUIET:
		rz_core_disasm_pdi(core, n_instrs, n_bytes, 0);
		break;
	default:
		rz_warn_if_reached();
		break;
	}
	free(buf);

	return ret;
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_n_bytes_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	ut64 n_bytes = argc > 1 ? (ut64)rz_num_math(core->num, argv[1]) : core->blocksize;
	if (n_bytes == 0) {
		RZ_LOG_ERROR("The argument cannot be zero\n");
		return RZ_CMD_STATUS_ERROR;
	}
	return bool2status(core_disassembly(core, n_bytes, 0, state, true));
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_n_instructions_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int n_instrs = 0;
	if (argc <= 1) {
		// when no arg, we always use the rows number of the terminal
		// no args must be always positive, if is not we use 16
		n_instrs = rz_num_get(core->num, "$r");
		if (n_instrs < 1) {
			n_instrs = 16;
		}
	} else {
		n_instrs = (int)rz_num_math(core->num, argv[1]);
		if (n_instrs == 0) {
			RZ_LOG_ERROR("The argument cannot be zero\n");
			return RZ_CMD_STATUS_ERROR;
		}
	}
	return bool2status(core_disassembly(core, argc > 1 ? (int)core->blocksize : 0, n_instrs, state, false));
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_all_possible_opcodes_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	ut64 n_bytes = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	ut8 *buffer = RZ_NEWS0(ut8, n_bytes);
	RzPVector *vec = NULL;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	if (!buffer) {
		goto fail;
	}
	if (!rz_io_read_at_mapped(core->io, core->offset, buffer, n_bytes)) {
		goto fail;
	}
	vec = rz_core_disasm_all_possible_opcodes(core, buffer, core->offset, n_bytes);
	if (!vec) {
		goto fail;
	}

	bool color = rz_config_get_i(core->config, "scr.color") > 0;
	void **p;
	rz_cmd_state_output_array_start(state);
	rz_cons_break_push(NULL, NULL);
	rz_pvector_foreach (vec, p) {
		RzCoreDisasmOp *op = *p;
		switch (state->mode) {
		case RZ_OUTPUT_MODE_STANDARD:
			rz_cons_printf("0x%08" PFMT64x " %20s  %s\n", op->offset, op->hex, color ? op->assembly_colored : op->assembly);
			break;
		case RZ_OUTPUT_MODE_JSON:
			pj_o(state->d.pj);
			pj_kn(state->d.pj, "addr", op->offset);
			pj_ks(state->d.pj, "bytes", op->hex);
			pj_ks(state->d.pj, "inst", op->assembly);
			pj_end(state->d.pj);
			break;
		case RZ_OUTPUT_MODE_QUIET:
			rz_cons_printf("%s\n", color ? op->assembly_colored : op->assembly);
			break;
		default:
			rz_warn_if_reached();
			break;
		}
	}
	rz_cons_break_pop();
	rz_cmd_state_output_array_end(state);

ret:
	free(buffer);
	rz_pvector_free(vec);
	return res;
fail:
	res = RZ_CMD_STATUS_ERROR;
	goto ret;
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_all_possible_opcodes_treeview_handler(RzCore *core, int argc, const char **argv) {
#define TREEVIEW_N_BYTES 28
	ut8 buffer[TREEVIEW_N_BYTES];
	RzPVector *vec = NULL;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	if (!rz_io_read_at_mapped(core->io, core->offset, buffer, TREEVIEW_N_BYTES)) {
		goto fail;
	}
	vec = rz_core_disasm_all_possible_opcodes(core, buffer, core->offset, TREEVIEW_N_BYTES);
	if (!vec) {
		goto fail;
	}

	bool color = rz_config_get_i(core->config, "scr.color") > 0;
	void **p;
	int position = 0;
	rz_pvector_foreach (vec, p) {
		RzCoreDisasmOp *op = *p;
		if (op->size < 1) {
			continue;
		}
		int padding = position * 2;
		int space = 60 - padding;
		if ((position + op->size) >= 30) {
			ut32 last = (30 - position) * 2;
			op->hex[last - 1] = '.';
			op->hex[last] = 0;
		}
		rz_cons_printf("0x%08" PFMT64x " %*s%*s %s\n", op->offset, padding, "", -space, op->hex, color ? op->assembly_colored : op->assembly);
		position++;
	}

ret:
	rz_pvector_free(vec);
	return res;
fail:
	res = RZ_CMD_STATUS_ERROR;
	goto ret;
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_basic_block_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzAnalysisBlock *b = rz_analysis_find_most_relevant_block_in(core->analysis, core->offset);
	core->num->value = 0;
	if (!b) {
		RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}

	ut8 *block = malloc(b->size + 1);
	if (!block) {
		RZ_LOG_ERROR("Cannot allocate buffer\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_io_read_at_mapped(core->io, b->addr, block, b->size);
	RzCoreDisasmOptions disasm_options = {
		.cbytes = 2,
	};
	rz_cmd_state_output_array_start(state);
	switch (state->mode) {
	case RZ_OUTPUT_MODE_STANDARD:
		core->num->value = rz_core_print_disasm(core, b->addr, block, b->size, 9999, state, &disasm_options);
		break;
	case RZ_OUTPUT_MODE_JSON:
		core->num->value = 1;
		rz_core_print_disasm_json(core, b->addr, block, b->size, 0, state->d.pj);
		break;
	default:
		rz_warn_if_reached();
		break;
	}
	rz_cmd_state_output_array_end(state);

	free(block);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_basic_block_as_text_json_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzAnalysisBlock *b = rz_analysis_find_most_relevant_block_in(core->analysis, core->offset);
	core->num->value = 0;
	if (!b) {
		RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}

	ut8 *block = malloc(b->size + 1);
	if (!block) {
		RZ_LOG_ERROR("Cannot allocate buffer\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_io_read_at_mapped(core->io, b->addr, block, b->size);
	RzCoreDisasmOptions disasm_options = {
		.cbytes = 2,
	};
	core->num->value = rz_core_print_disasm(core, b->addr, block, b->size, 9999, state, &disasm_options);

	free(block);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_comments_in_n_instructions_handler(RzCore *core, int argc, const char **argv) {
	st64 parsed = argc > 1 ? (st64)rz_num_math(core->num, argv[1]) : core->blocksize;
	if (parsed > ST16_MAX || parsed < ST16_MIN) {
		RZ_LOG_ERROR("the number of instructions is too big (%d < n_instrs < %d).\n", ST16_MAX, ST16_MIN);
		return RZ_CMD_STATUS_ERROR;
	}
	int n_instrs = parsed;
	if (rz_core_disasm_pdi(core, n_instrs, 0, 'C') < 0) {
		return RZ_CMD_STATUS_ERROR;
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_n_instructions_with_flow_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	st64 parsed = argc > 1 ? (st64)rz_num_math(core->num, argv[1]) : (core->blocksize / 4);
	if (parsed > ST16_MAX || parsed < ST16_MIN) {
		RZ_LOG_ERROR("the number of instructions is too big (%d < n_instrs < %d).\n", ST16_MAX, ST16_MIN);
		return RZ_CMD_STATUS_ERROR;
	}
	int n_instrs = parsed;
	// this command is going to be removed when esil will be removed.
	rz_core_disasm_pde(core, n_instrs, state);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_function_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	core->num->value = 0;
	ut32 old_blocksize = core->blocksize;
	RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, core->offset, RZ_ANALYSIS_FCN_TYPE_ROOT);
	if (!function) {
		function = rz_analysis_get_fcn_in(core->analysis, core->offset, 0);
	}
	if (!function) {
		RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}

	if (state->mode == RZ_OUTPUT_MODE_JSON) {
		bool ret = rz_core_print_function_disasm_json(core, function, state->d.pj);
		rz_core_block_size(core, old_blocksize);
		return ret ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR;
	}

	ut64 linear_size = rz_analysis_function_linear_size(function);
	ut64 max_real_size = rz_analysis_function_realsize(function) + 4096;
	if (max_real_size < linear_size) {
		RZ_LOG_ERROR("Linear size differs too much from the bbsum, please use pdr instead.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	ut64 start = function->addr; // For pdf, start disassembling at the entrypoint
	ut64 end = rz_analysis_function_max_addr(function);
	if (end <= start) {
		RZ_LOG_ERROR("Cannot print function because the end offset is less or equal to the start offset\n");
		return RZ_CMD_STATUS_ERROR;
	}

	ut64 size = end - start;
	ut8 *bytes = malloc(size);
	if (!bytes) {
		RZ_LOG_ERROR("Cannot allocate buffer\n");
		return RZ_CMD_STATUS_ERROR;
	}

	(void)rz_io_read_at_mapped(core->io, start, bytes, size);
	RzCoreDisasmOptions disasm_options = {
		.cbytes = 1,
		.function = function,
	};
	core->num->value = rz_core_print_disasm(core, start, bytes, size, size, state, &disasm_options);
	free(bytes);

	rz_core_block_size(core, old_blocksize);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_current_block_json_handler(RzCore *core, int argc, const char **argv) {
	rz_return_val_if_fail(core, RZ_CMD_STATUS_ERROR);
	bool enable_color = rz_config_get_i(core->config, "escr.color") != 0;
	char *res = rz_print_json_indent((const char *)core->block, enable_color, "  ", NULL);
	if (RZ_STR_ISEMPTY(res)) {
		free(res);
		RZ_LOG_ERROR("Couldn't find a JSON string.\n");
		return RZ_CMD_STATUS_ERROR;
	} else {
		rz_cons_printf("%s\n", res);
	}
	free(res);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_function_rzil_handler(RzCore *core, int argc, const char **argv) {
	RzAnalysisFunction *f = rz_analysis_first_function_in(core->analysis, core->offset);
	if (!f) {
		goto exit;
	}

	RzIterator *ops = rz_core_analysis_op_function_iter(core, f, RZ_ANALYSIS_OP_MASK_IL);
	if (!ops) {
		goto exit;
	}

	rz_core_il_cons_print(core, ops, false, false);
	rz_iterator_free(ops);
	return RZ_CMD_STATUS_OK;
exit:
	return RZ_CMD_STATUS_ERROR;
}

RZ_IPI RzCmdStatus rz_print_function_rzil_enriched_handler(RzCore *core, int argc, const char **argv) {
	if (!rz_config_get_b(core->config, "scr.utf8")) {
		RZ_LOG_ERROR("Config variable 'scr.utf8' is not set\n");
		goto exit;
	}
	RzAnalysisFunction *f = rz_analysis_first_function_in(core->analysis, core->offset);
	if (!f) {
		goto exit;
	}

	RzIterator *ops = rz_core_analysis_op_function_iter(core, f, RZ_ANALYSIS_OP_MASK_IL);
	if (!ops) {
		goto exit;
	}

	rz_core_il_cons_print(core, ops, false, true);
	rz_iterator_free(ops);
	return RZ_CMD_STATUS_OK;
exit:
	return RZ_CMD_STATUS_ERROR;
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_function_summary_handler(RzCore *core, int argc, const char **argv) {
	ut32 old_blocksize = core->blocksize;
	RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, core->offset, RZ_ANALYSIS_FCN_TYPE_FCN | RZ_ANALYSIS_FCN_TYPE_SYM);
	if (!function) {
		RZ_LOG_ERROR("cannot find function at 0x%08" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}
	ut32 rs = rz_analysis_function_realsize(function);
	ut32 fs = rz_analysis_function_linear_size(function);
	rz_core_block_size(core, RZ_MAX(rs, fs));
	char *string = rz_core_print_disasm_strings(core, RZ_CORE_DISASM_STRINGS_MODE_INST, 0, function);
	rz_core_block_size(core, old_blocksize);
	if (!string) {
		RZ_LOG_ERROR("failed summarize %" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_print(string);
	free(string);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_n_instrs_as_text_json_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	ut32 old_blocksize = core->blocksize;
	ut64 old_offset = core->offset;
	core->num->value = 0;

	st64 parsed = argc > 1 ? (st64)rz_num_math(core->num, argv[1]) : 0;
	if (parsed > ST16_MAX || parsed < ST16_MIN) {
		RZ_LOG_ERROR("the number of instructions is too big (%d < n_instrs < %d).\n", ST16_MAX, ST16_MIN);
		return RZ_CMD_STATUS_ERROR;
	}
	int n_instrs = parsed;
	if (n_instrs < 0) {
		ut64 new_offset = old_offset;
		if (!rz_core_prevop_addr(core, old_offset, -n_instrs, &new_offset)) {
			new_offset = rz_core_prevop_addr_force(core, old_offset, -n_instrs);
		}
		ut32 new_blocksize = new_offset - old_blocksize;
		if (new_blocksize > old_blocksize) {
			rz_core_block_size(core, new_blocksize);
		}
		rz_core_seek(core, new_offset, true);
	} else {
		rz_core_block_read(core);
	}

	state->mode = RZ_OUTPUT_MODE_JSON;

	if (rz_cons_singleton()->is_html) {
		rz_cons_singleton()->is_html = false;
		rz_cons_singleton()->was_html = true;
	}
	RzCoreDisasmOptions disasm_options = {
		.cbytes = 1,
	};
	core->num->value = rz_core_print_disasm(core, core->offset, core->block, core->blocksize, RZ_ABS(n_instrs), state, &disasm_options);

	if (n_instrs < 0) {
		rz_core_block_size(core, old_blocksize);
		rz_core_seek(core, old_offset, true);
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_disassembly_all_methods_class_handler(RzCore *core, int argc, const char **argv) {
	ut32 old_blocksize = core->blocksize;
	ut64 old_offset = core->offset;

	int len = 0;
	ut64 at = findClassBounds(core, &len);
	if (!at) {
		RZ_LOG_ERROR("Cannot find class at 0x%" PFMT64x ".\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}

	rz_core_seek(core, at, true);

	// TODO: remove this and use C api.
	// on success returns 0 else negative
	int ret = rz_core_cmdf(core, "pD %d", len);

	rz_core_block_size(core, old_blocksize);
	rz_core_seek(core, old_offset, true);
	return ret >= 0 ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR;
}

RZ_IPI RzCmdStatus rz_cmd_sizes_of_n_instructions_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	ut32 old_blocksize = core->blocksize;
	ut64 old_offset = core->offset;
	st64 ret = 0;

	st64 parsed = argc > 1 ? (st64)rz_num_math(core->num, argv[1]) : (core->blocksize / 4);
	if (parsed > ST16_MAX || parsed < ST16_MIN) {
		RZ_LOG_ERROR("the number of instructions is too big (%d < n_instrs < %d).\n", ST16_MAX, ST16_MIN);
		return RZ_CMD_STATUS_ERROR;
	}
	int n_instrs = parsed;
	if (n_instrs < 0) {
		ut64 new_offset = old_offset;
		if (!rz_core_prevop_addr(core, old_offset, -n_instrs, &new_offset)) {
			new_offset = rz_core_prevop_addr_force(core, old_offset, -n_instrs);
		}
		ut32 new_blocksize = new_offset - old_blocksize;
		if (new_blocksize > old_blocksize) {
			rz_core_block_size(core, new_blocksize);
		}
		rz_core_seek(core, new_offset, true);
	} else {
		rz_core_block_read(core);
	}

	rz_cmd_state_output_array_start(state);
	rz_cons_break_push(NULL, NULL);
	for (ut32 i = 0, j = 0; i < core->blocksize && j < RZ_ABS(n_instrs); i += ret, j++) {
		RzAsmOp asm_op = { 0 };
		ret = rz_asm_disassemble(core->rasm, &asm_op, core->block + i, core->blocksize - i);
		rz_asm_op_fini(&asm_op);
		if (rz_cons_is_breaked()) {
			break;
		}
		// be sure to return 0 when it fails to disassemble the
		// instruction to uniform the output across all disassemblers.
		int op_size = ret < 1 ? 0 : ret;
		switch (state->mode) {
		case RZ_OUTPUT_MODE_STANDARD:
			rz_cons_printf("%d\n", op_size);
			break;
		case RZ_OUTPUT_MODE_JSON:
			pj_N(state->d.pj, op_size);
			break;
		default:
			rz_warn_if_reached();
			return RZ_CMD_STATUS_ERROR;
		}
		if (ret < 1) {
			ret = 1;
		}
	}
	rz_cons_break_pop();
	rz_cmd_state_output_array_end(state);

	if (n_instrs < 0) {
		rz_core_block_size(core, old_blocksize);
		rz_core_seek(core, old_offset, true);
	}
	return RZ_CMD_STATUS_OK;
}

static void disassemble_till_return_is_found(RzCore *core, ut64 offset, ut64 limit, RzCmdStateOutput *state) {
	bool src_color = rz_config_get_i(core->config, "scr.color") > 0;
	const char *off_color = src_color ? rz_cons_singleton()->context->pal.b0x00 : "";
	const char *ret_color = src_color ? rz_cons_singleton()->context->pal.jmp : "";
	const char *end_color = src_color ? Color_RESET : "";

	for (ut64 i = 0; i < limit; i++) {
		RzAnalysisOp *op = rz_core_analysis_op(core, offset, RZ_ANALYSIS_OP_MASK_BASIC | RZ_ANALYSIS_OP_MASK_DISASM);
		if (!op) {
			return;
		}

		switch (state->mode) {
		case RZ_OUTPUT_MODE_QUIET:
			rz_cons_printf("%s%s%s\n", ret_color, op->mnemonic, end_color);
			break;
		case RZ_OUTPUT_MODE_STANDARD:
			rz_cons_printf("%s0x%08" PFMT64x "%s %-11s%s\n", off_color, core->offset + i, ret_color, op->mnemonic, end_color);
			break;
		case RZ_OUTPUT_MODE_JSON:
			pj_o(state->d.pj);
			pj_ks(state->d.pj, "mnemonic", op->mnemonic);
			pj_kn(state->d.pj, "address", core->offset + i);
			pj_end(state->d.pj);
			break;
		default:
			rz_warn_if_reached();
			break;
		}

		if (!(op->type & (RZ_ANALYSIS_OP_TYPE_RET | RZ_ANALYSIS_OP_TYPE_UJMP))) {
			rz_analysis_op_free(op);
			return;
		}

		if (op->type == RZ_ANALYSIS_OP_TYPE_JMP) {
			offset = op->jump;
		} else {
			offset += op->size;
		}

		rz_analysis_op_free(op);
	}
}

RZ_IPI RzCmdStatus rz_cmd_disassemble_ropchain_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	ut64 limit = argc > 1 ? rz_num_math(core->num, argv[1]) : 1024;
	if (limit > 1024) {
		RZ_LOG_ERROR("the limit value exceeds the max value (1024).\n");
		return RZ_CMD_STATUS_ERROR;
	}
	ut64 asm_bits = rz_asm_get_bits(core->rasm);
	bool big_endian = rz_config_get_b(core->config, "cfg.bigendian");
	bool src_color = rz_config_get_i(core->config, "scr.color") > 0;

	const char *off_color = src_color ? rz_cons_singleton()->context->pal.offset : "";
	const char *num_color = src_color ? rz_cons_singleton()->context->pal.num : "";
	const char *end_color = src_color ? Color_RESET : "";

	if (asm_bits < 64) {
		asm_bits = 32;
	}

	ut32 asm_bytes = asm_bits / 8;
	if (core->blocksize < asm_bytes) {
		RZ_LOG_ERROR("block size is not enough big to host a word (needs to be >= %u bytes).\n", asm_bytes);
		return RZ_CMD_STATUS_ERROR;
	}

	ut8 *bytes = RZ_NEWS0(ut8, core->blocksize);
	if (!bytes) {
		RZ_LOG_ERROR("cannot allocate buffer.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	(void)rz_io_read_at_mapped(core->io, core->offset, bytes, core->blocksize);

	rz_cmd_state_output_array_start(state);
	for (ut32 i = 0; i < core->blocksize - asm_bytes; i += asm_bytes) {
		ut64 number = rz_read_ble(bytes + i, big_endian, asm_bits);
		switch (state->mode) {
		case RZ_OUTPUT_MODE_QUIET:
			rz_cons_printf("%s0x%08" PFMT64x "%s %s0x%08" PFMT64x "%s\n", off_color, core->offset + i, end_color, num_color, number, end_color);
			disassemble_till_return_is_found(core, core->offset + i, limit, state);
			break;
		case RZ_OUTPUT_MODE_STANDARD:
			rz_cons_printf("[%s0x%08" PFMT64x "%s] %s0x%08" PFMT64x "%s\n", off_color, core->offset + i, end_color, num_color, number, end_color);
			disassemble_till_return_is_found(core, core->offset + i, limit, state);
			break;
		case RZ_OUTPUT_MODE_JSON:
			pj_o(state->d.pj);
			pj_kn(state->d.pj, "address", core->offset + i);
			pj_kn(state->d.pj, "bits", asm_bits);
			pj_kn(state->d.pj, "word", number);
			pj_ka(state->d.pj, "opcodes");
			disassemble_till_return_is_found(core, core->offset + i, limit, state);
			pj_end(state->d.pj);
			pj_end(state->d.pj);
			break;
		default:
			rz_warn_if_reached();
			return RZ_CMD_STATUS_ERROR;
		}
	}
	rz_cmd_state_output_array_end(state);

	return RZ_CMD_STATUS_OK;
}

static bool core_walk_function_blocks(RzCore *core, RzAnalysisFunction *f, RzCmdStateOutput *state, char type_print, bool fromHere) {
	void **iter;
	RzAnalysisBlock *b = NULL;
	const bool orig_bb_middle = rz_config_get_b(core->config, "asm.bb.middle");
	rz_config_set_b(core->config, "asm.bb.middle", false);

	if (rz_pvector_len(f->bbs) >= 1) {
		ut32 fcn_size = rz_analysis_function_realsize(f);
		b = rz_pvector_tail(f->bbs);
		if (b->size > fcn_size) {
			b->size = fcn_size;
		}
	}

	rz_pvector_sort(f->bbs, (RzPVectorComparator)bbcmp, NULL);
	if (state->mode == RZ_OUTPUT_MODE_JSON) {
		rz_pvector_foreach (f->bbs, iter) {
			b = (RzAnalysisBlock *)*iter;
			ut8 *buf = malloc(b->size);
			if (!buf) {
				RZ_LOG_ERROR("cannot allocate %" PFMT64u " byte(s)\n", b->size);
				return false;
			}
			(void)rz_io_read_at_mapped(core->io, b->addr, buf, b->size);
			rz_core_print_disasm_json(core, b->addr, buf, b->size, 0, state->d.pj);
			free(buf);
		}
	} else {
		RzReg *rreg = rz_analysis_get_reg(core->analysis);
		bool asm_lines = rz_config_get_i(core->config, "asm.lines.bb");
		bool emu = rz_config_get_i(core->config, "asm.emu");
		ut64 saved_gp = 0;
		ut8 *saved_arena = NULL;
		if (emu) {
			saved_gp = rz_analysis_get_gp(core->analysis);
			saved_arena = rz_reg_arena_peek(rreg);
		}
		rz_config_set_i(core->config, "asm.lines.bb", 0);
		rz_pvector_foreach (f->bbs, iter) {
			b = (RzAnalysisBlock *)*iter;
			pr_bb(core, f, b, emu, saved_gp, saved_arena, type_print, fromHere);
		}
		if (emu) {
			rz_analysis_set_gp(core->analysis, saved_gp);
			if (saved_arena) {
				rz_reg_arena_poke(rreg, saved_arena);
				RZ_FREE(saved_arena);
			}
		}
		rz_config_set_i(core->config, "asm.lines.bb", asm_lines);
	}
	rz_config_set_b(core->config, "asm.bb.middle", orig_bb_middle);
	return true;
}

RZ_IPI RzCmdStatus rz_cmd_disassemble_recursively_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, core->offset, 0);
	// RZ_ANALYSIS_FCN_TYPE_FCN|RZ_ANALYSIS_FCN_TYPE_SYM);
	if (!function) {
		RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}

	rz_cmd_state_output_array_start(state);
	bool ret = core_walk_function_blocks(core, function, state, 'D', false);
	rz_cmd_state_output_array_end(state);
	return ret ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR;
}

RZ_IPI RzCmdStatus rz_cmd_disassemble_recursively_from_current_block_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, core->offset, 0);
	// RZ_ANALYSIS_FCN_TYPE_FCN|RZ_ANALYSIS_FCN_TYPE_SYM);
	if (!function) {
		RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}

	rz_cmd_state_output_array_start(state);
	bool ret = core_walk_function_blocks(core, function, state, 'D', true);
	rz_cmd_state_output_array_end(state);
	return ret ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR;
}

RZ_IPI RzCmdStatus rz_cmd_disassemble_recursively_no_function_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	ut64 old_offset = core->offset;
	RzAnalysisOp aop = { 0 };
	ut32 aop_type;
	ut64 aop_jump;
	int aop_size;

	rz_cmd_state_output_array_start(state);
	for (ut64 count = core->blocksize, offset = core->offset; count > 0; count--) {
		rz_core_seek(core, offset, true);

		rz_analysis_op_init(&aop);
		int ret = rz_analysis_op(core->analysis, &aop, offset, core->block, core->blocksize, RZ_ANALYSIS_OP_MASK_BASIC);
		if (ret > 0) {
			aop_type = aop.type;
			aop_jump = aop.jump;
			aop_size = aop.size;
		}
		rz_analysis_op_fini(&aop);

		if (ret < 1 || aop_size < 1) {
			offset++;
			continue;
		}

		core_disassembly(core, core->blocksize, 1, state, false);

		switch (aop_type) {
		case RZ_ANALYSIS_OP_TYPE_JMP:
			offset = aop_jump;
			continue;
		case RZ_ANALYSIS_OP_TYPE_UCJMP:
			break;
		case RZ_ANALYSIS_OP_TYPE_RET:
			count = 1; // stop disassembling when hitting RET
			break;
		default:
			break;
		}
		offset += aop_size;
	}
	rz_cmd_state_output_array_end(state);

	rz_core_seek(core, old_offset, true);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_disassemble_summarize_n_bytes_handler(RzCore *core, int argc, const char **argv) {
	if (argc <= 1) {
		RZ_LOG_ERROR("Invalid argument.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	ut64 n_bytes = rz_num_math(core->num, argv[1]);
	// small patch to reuse rz_core_print_disasm_strings which
	// needs to be rewritten entirely
	char *string = rz_core_print_disasm_strings(core, argc > 1 ? RZ_CORE_DISASM_STRINGS_MODE_BYTES : RZ_CORE_DISASM_STRINGS_MODE_INST, n_bytes, NULL);
	if (!string) {
		RZ_LOG_ERROR("failed summarize bytes %" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_print(string);
	free(string);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_disassemble_summarize_function_handler(RzCore *core, int argc, const char **argv) {
	char *string = rz_core_print_disasm_strings(core, RZ_CORE_DISASM_STRINGS_MODE_FUNCTION, 0, NULL);
	if (!string) {
		RZ_LOG_ERROR("failed summarize function %" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_print(string);
	free(string);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_disassemble_summarize_block_handler(RzCore *core, int argc, const char **argv) {
	// small patch to reuse rz_core_print_disasm_strings which
	// needs to be rewritten entirely
	char *string = rz_core_print_disasm_strings(core, RZ_CORE_DISASM_STRINGS_MODE_BLOCK, 0, NULL);
	if (!string) {
		RZ_LOG_ERROR("failed summarize block %" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_print(string);
	free(string);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_base64_encode_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	char *buf = rz_base64_encode_dyn((const unsigned char *)core->block, core->blocksize);
	if (!buf) {
		RZ_LOG_ERROR("rz_base64_encode_dyn: error\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_println((const char *)buf);
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_base64_decode_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	ut8 *buf = rz_base64_decode_dyn((const char *)core->block, core->blocksize);
	if (!buf) {
		RZ_LOG_ERROR("rz_base64_decode_dyn: error\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_println((const char *)buf);
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_bitstream_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	ut64 old_offset = core->offset;
	st32 len = (st32)rz_num_math(core->num, argv[1]);
	st32 skip = (st32)rz_num_math(core->num, argv[2]);
	if (len < 0 || (!len && !skip)) {
		RZ_LOG_ERROR("`len` should be a positive number\n");
		return RZ_CMD_STATUS_ERROR;
	} else if (skip < 0) {
		RZ_LOG_ERROR("`skip` should be a positive number\n");
		return RZ_CMD_STATUS_ERROR;
	}
	// `pb len skip` means skip <skip> bits then print <len> bits
	const size_t skip_n_chars = skip & 7;
	const size_t max_n_bits = ((size_t)core->blocksize) << 3;
	const size_t buf_len = ((size_t)len) + (skip_n_chars ? 8 : 0);
	if (buf_len > max_n_bits) {
		RZ_LOG_ERROR("cannot print %" PFMT32d " bits when current block size is %" PFMTSZu " bits\n", len, max_n_bits);
		return RZ_CMD_STATUS_ERROR;
	}

	char *buf = RZ_NEWS0(char, buf_len + 1);
	if (!buf) {
		RZ_LOG_ERROR("Fail to allocate memory\n");
		return RZ_CMD_STATUS_ERROR;
	}

	if (skip > 7) {
		const size_t n_bytes = (skip >> 3);
		rz_core_seek(core, old_offset + n_bytes, true);
	}

	rz_str_bits(buf, core->block, buf_len, NULL);

	if (skip_n_chars) {
		// we always show `len` bits, just shifted.
		// so we trim the last chars.
		buf[buf_len - (8 - skip_n_chars)] = 0;
	}

	rz_cons_println(buf + skip_n_chars);
	free(buf);

	if (skip > 7) {
		rz_core_seek(core, old_offset, true);
	}
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_byte_bitstream_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	ut64 start = core->offset;
	int len = (int)rz_num_math(core->num, argv[1]);
	if (len < 0) {
		start = core->offset + len;
		len *= -1;
	}
	ut8 *bit_buf = RZ_NEWS0(ut8, len);
	char *str_buf = RZ_NEWS0(char, len * 8 + 1);
	if (!bit_buf || !str_buf) {
		RZ_LOG_ERROR("Fail to allocate memory\n");
		free(bit_buf);
		free(str_buf);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_io_read_at_mapped(core->io, start, bit_buf, len);
	rz_str_bits(str_buf, (const ut8 *)bit_buf, len * 8, NULL);
	rz_cons_println(str_buf);
	free(bit_buf);
	free(str_buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_asn1_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
	RzASN1Object *asn1 = rz_asn1_object_parse(core->block, core->blocksize);
	if (!asn1) {
		RZ_LOG_ERROR("core: Malformed object: did you supply enough data?\ntry to change the block size (see b? or @!<size>)\n");
		return RZ_CMD_STATUS_ERROR;
	}
	char *res = rz_asn1_to_string(asn1);
	rz_asn1_object_free(asn1);
	if (!res) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_printf("%s", res);
	free(res);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_asn1_structure_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzASN1Object *asn1 = rz_asn1_object_parse(core->block, core->blocksize);
	if (!asn1) {
		RZ_LOG_ERROR("core: Malformed object: did you supply enough data?\ntry to change the block size (see b? or @!<size>)\n");
		return RZ_CMD_STATUS_ERROR;
	}
	RzStructuredData *sd = rz_asn1_to_structure(asn1, state->mode == RZ_OUTPUT_MODE_QUIET);
	rz_asn1_object_free(asn1);
	if (!sd) {
		RZ_LOG_ERROR("core: failed to create RzStructuredData from ASN1 data.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	switch (state->mode) {
	case RZ_OUTPUT_MODE_JSON:
		rz_structured_data_to_pj(sd, state->d.pj);
		break;
	default: {
		char *res = rz_structured_data_to_yaml(sd);
		if (res) {
			rz_cons_println(res);
			free(res);
		}
		break;
	}
	}
	rz_structured_data_free(sd);

	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_protobuf_standard_handler(RzCore *core, int argc, const char **argv) {
	char *s = rz_protobuf_decode(core->block, core->blocksize, false);
	if (!s) {
		RZ_LOG_ERROR("core: Malformed object: did you supply enough data?\ntry to change the block size (see b? or @!<size>)\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_printf("%s", s);
	free(s);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_protobuf_verbose_handler(RzCore *core, int argc, const char **argv) {
	char *s = rz_protobuf_decode(core->block, core->blocksize, true);
	if (!s) {
		RZ_LOG_ERROR("core: Malformed object: did you supply enough data?\ntry to change the block size (see b? or @!<size>)\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_printf("%s", s);
	free(s);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_pkcs7_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzCMS *cms = rz_pkcs7_cms_parse(core->block, core->blocksize);
	if (!cms) {
		RZ_LOG_ERROR("core: Malformed object: did you supply enough data?\ntry to change the block size (see b? or @!<size>)\n");
		return RZ_CMD_STATUS_ERROR;
	}

	RzStructuredData *sd = rz_pkcs7_cms_to_structure(cms);
	rz_pkcs7_cms_free(cms);
	if (!sd) {
		RZ_LOG_ERROR("core: failed to create RzStructuredData from CMS data.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	switch (state->mode) {
	case RZ_OUTPUT_MODE_JSON:
		rz_structured_data_to_pj(sd, state->d.pj);
		break;
	default: {
		char *res = rz_structured_data_to_yaml(sd);
		if (res) {
			rz_cons_println(res);
			free(res);
		}
		break;
	}
	}
	rz_structured_data_free(sd);

	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_x509_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzX509Certificate *x509 = rz_x509_certificate_parse2(core->block, core->blocksize);
	if (!x509) {
		RZ_LOG_ERROR("core: Malformed object: did you supply enough data?\ntry to change the block size (see b? or @!<size>)\n");
		return RZ_CMD_STATUS_ERROR;
	}

	RzStructuredData *sd = rz_x509_certificate_to_structure(x509);
	rz_x509_certificate_free(x509);
	if (!sd) {
		RZ_LOG_ERROR("core: failed to create RzStructuredData from x509 data.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	switch (state->mode) {
	case RZ_OUTPUT_MODE_JSON:
		rz_structured_data_to_pj(sd, state->d.pj);
		break;
	default: {
		char *res = rz_structured_data_to_yaml(sd);
		if (res) {
			rz_cons_println(res);
			free(res);
		}
		break;
	}
	}
	rz_structured_data_free(sd);

	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_pkcs8_pkey_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzPrivateKeyInfo *pki = rz_pkcs8_private_key_info_parse(core->block, core->blocksize);
	if (!pki) {
		RZ_LOG_ERROR("core: Malformed object: did you supply enough data?\ntry to change the block size (see b? or @!<size>)\n");
		return RZ_CMD_STATUS_ERROR;
	}

	RzStructuredData *sd = rz_pkcs8_private_key_info_to_structure(pki);
	rz_pkcs8_private_key_info_free(pki);
	if (!sd) {
		RZ_LOG_ERROR("core: failed to create RzStructuredData from PrivateKeyInfo data.\n");
		return RZ_CMD_STATUS_ERROR;
	}

	switch (state->mode) {
	case RZ_OUTPUT_MODE_JSON:
		rz_structured_data_to_pj(sd, state->d.pj);
		break;
	default: {
		char *res = rz_structured_data_to_yaml(sd);
		if (res) {
			rz_cons_println(res);
			free(res);
		}
		break;
	}
	}
	rz_structured_data_free(sd);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_axml_handler(RzCore *core, int argc, const char **argv) {
	char *s = rz_axml_decode(core->block, core->blocksize);
	if (!s) {
		RZ_LOG_ERROR("core: Malformed object: did you supply enough data?\ntry to change the block size (see b? or @!<size>)\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_printf("%s", s);
	free(s);
	return RZ_CMD_STATUS_OK;
}

typedef struct {
	ut64 size;
	ut64 repeat;
	bool useBytes;
} PrintValueOptions;

static void print_value_single(RzCore *core, PrintValueOptions *opts, ut64 address, ut64 value, RzCmdStateOutput *state) {
	switch (state->mode) {
	case RZ_OUTPUT_MODE_STANDARD:
		switch (opts->size) {
		case 1:
			rz_cons_printf("0x%02" PFMT64x "\n", value);
			break;
		case 2:
			rz_cons_printf("0x%04" PFMT64x "\n", value);
			break;
		case 4:
			rz_cons_printf("0x%08" PFMT64x "\n", value);
			break;
		case 8:
			rz_cons_printf("0x%016" PFMT64x "\n", value);
			break;
		default:
			rz_warn_if_reached();
			break;
		}
		break;
	case RZ_OUTPUT_MODE_JSON: {
		// TODO: Use API instead of the command
		char *str = rz_core_cmd_str(core, "ps");
		rz_str_trim(str);
		char *p = str;
		if (p) {
			while (*p) {
				if (*p == '\\' && p[1] == 'x') {
					memmove(p, p + 4, strlen(p + 4) + 1);
				}
				p++;
			}
		}
		pj_o(state->d.pj);
		pj_k(state->d.pj, "value");
		switch (opts->size) {
		case 1:
		case 2:
			pj_i(state->d.pj, value);
			break;
		case 4:
		case 8:
			pj_n(state->d.pj, value);
			break;
		default:
			rz_warn_if_reached();
			break;
		}
		pj_ks(state->d.pj, "string", str);
		free(str);
		pj_end(state->d.pj);
		break;
	}
	default:
		rz_warn_if_reached();
		break;
	}
}

static bool print_value(RzCore *core, PrintValueOptions *opts, RzCmdStateOutput *state) {
	ut64 old_at = core->offset;
	ut8 *block = core->block;
	int blocksize = core->blocksize;
	ut8 *block_end = core->block + blocksize;

	bool big_endian = rz_config_get_b(core->config, "cfg.bigendian");
	if (block + 8 >= block_end) {
		RZ_LOG_ERROR("core: block is truncated.\n");
		return false;
	}
	ut64 repeat = opts->repeat;
	if (opts->useBytes && opts->size > 0 && repeat > 0) {
		repeat /= opts->size;
	}
	ut64 at = old_at;
	rz_cmd_state_output_array_start(state);
	do {
		rz_core_seek(core, at, false);

		ut64 v = 0;
		switch (opts->size) {
		case 1:
			v = rz_read_ble8(block);
			block += opts->size;
			break;
		case 2:
			v = rz_read_ble16(block, big_endian);
			block += opts->size;
			break;
		case 4:
			v = rz_read_ble32(block, big_endian);
			block += opts->size;
			break;
		case 8:
			v = rz_read_ble64(block, big_endian);
			block += opts->size;
			break;
		case 0:
			v = rz_read_ble64(block, big_endian);
			opts->size = rz_asm_get_bits(core->rasm) / 8;
			switch (opts->size) {
			case 1: v &= UT8_MAX; break;
			case 2: v &= UT16_MAX; break;
			case 4: v &= UT32_MAX; break;
			case 8: v &= UT64_MAX; break;
			default: break;
			}
			block += opts->size;
			break;
		}
		print_value_single(core, opts, at, v, state);
		repeat--;
		at += opts->size;
	} while (repeat > 0);
	rz_cmd_state_output_array_end(state);
	rz_core_seek(core, old_at, false);
	return true;
}

static RzCmdStatus print_value_size(RzCore *core, RzCmdStateOutput *state, int argc, const char **argv, ut64 size) {
	int repeat = argc > 1 ? rz_num_math(NULL, argv[1]) : 1;
	if (repeat <= 0) {
		return RZ_CMD_STATUS_ERROR;
	}
	PrintValueOptions opts = {
		.size = size,
		.repeat = repeat,
		.useBytes = false
	};
	return bool2status(print_value(core, &opts, state));
}

RZ_IPI RzCmdStatus rz_print_value_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return print_value_size(core, state, argc, argv, 0);
}

RZ_IPI RzCmdStatus rz_print_value1_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return print_value_size(core, state, argc, argv, 1);
}

RZ_IPI RzCmdStatus rz_print_value2_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return print_value_size(core, state, argc, argv, 2);
}

RZ_IPI RzCmdStatus rz_print_value4_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return print_value_size(core, state, argc, argv, 4);
}

RZ_IPI RzCmdStatus rz_print_value8_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return print_value_size(core, state, argc, argv, 8);
}

RZ_IPI RzCmdStatus rz_print_url_encode_handler(RzCore *core, int argc, const char **argv) {
	ut64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	RzStrStringifyOpt opt = { 0 };
	opt.buffer = core->block;
	opt.length = len;
	opt.encoding = RZ_STRING_ENC_8BIT;
	opt.urlencode = true;
	core_print_raw_buffer(&opt);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_url_encode_wide_handler(RzCore *core, int argc, const char **argv) {
	ut64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	RzStrStringifyOpt opt = { 0 };
	opt.buffer = core->block;
	opt.length = len;
	opt.encoding = RZ_STRING_ENC_UTF16LE;
	opt.urlencode = true;
	core_print_raw_buffer(&opt);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_url_encode_zero_handler(RzCore *core, int argc, const char **argv) {
	ut64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	RzStrStringifyOpt opt = { 0 };
	opt.buffer = core->block;
	opt.length = len;
	opt.stop_at_nil = true;
	opt.stop_at_unprintable = true;
	opt.encoding = RZ_STRING_ENC_8BIT;
	opt.urlencode = true;
	core_print_raw_buffer(&opt);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern0_handler(RzCore *core, int argc, const char **argv) {
	st64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 1) {
		RZ_LOG_ERROR("Invalid pattern length\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (st64 i = 0; i < len; i++) {
		rz_cons_print("00");
	}
	rz_cons_newline();
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern1_handler(RzCore *core, int argc, const char **argv) {
	st8 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 1) {
		RZ_LOG_ERROR("Invalid pattern length\n");
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 min = (core->offset & 0xff);
	for (ut8 i = 0; i < len; i++) {
		rz_cons_printf("%02x", i + min);
	}
	rz_cons_newline();
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern2_handler(RzCore *core, int argc, const char **argv) {
	st16 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 1) {
		RZ_LOG_ERROR("Invalid pattern length\n");
		return RZ_CMD_STATUS_ERROR;
	}
	// TODO: honor cfg.bigendian
	ut16 min = (core->offset & 0xffff);
	for (ut16 i = 0; i < len; i++) {
		rz_cons_printf("%04x", i + min);
	}
	rz_cons_newline();
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern4_handler(RzCore *core, int argc, const char **argv) {
	st32 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 1) {
		RZ_LOG_ERROR("Invalid pattern length\n");
		return RZ_CMD_STATUS_ERROR;
	}
	// TODO: honor cfg.bigendian
	ut32 min = (core->offset & UT32_MAX);
	for (ut32 i = 0; i < len; i++) {
		rz_cons_printf("%08" PFMT32x, i + min);
	}
	rz_cons_newline();
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern8_handler(RzCore *core, int argc, const char **argv) {
	st64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 1) {
		RZ_LOG_ERROR("Invalid pattern length\n");
		return RZ_CMD_STATUS_ERROR;
	}
	// TODO: honor cfg.bigendian
	ut64 min = (core->offset);
	for (ut64 i = 0; i < len; i++) {
		rz_cons_printf("%016" PFMT64x, i + min);
	}
	rz_cons_newline();
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern_latin_alphabet_handler(RzCore *core, int argc, const char **argv) {
	st64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 1) {
		RZ_LOG_ERROR("Invalid pattern length\n");
		return RZ_CMD_STATUS_ERROR;
	}
	size_t bs = 4;
	ut8 *buf = calloc(bs, 1);
	if (!buf) {
		RZ_LOG_ERROR("Cannot allocate buffer\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (st64 i = 0; i < len; i++) {
		incAlphaBuffer(buf, bs);
		for (st64 j = 0; j < bs; j++) {
			rz_cons_printf("%c", buf[j] ? buf[j] : 'A');
		}
		rz_cons_printf(" ");
	}
	rz_cons_newline();
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern_debrujin_handler(RzCore *core, int argc, const char **argv) {
	st64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 1) {
		RZ_LOG_ERROR("Invalid pattern length\n");
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *buf = (ut8 *)rz_debruijn_pattern(len, 0, NULL);
	if (!buf) {
		RZ_LOG_ERROR("Cannot generate De Brujin pattern\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (st64 i = 0; i < len; i++) {
		rz_cons_printf("%02x", buf[i]);
	}
	rz_cons_newline();
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern_debrujin_find_handler(RzCore *core, int argc, const char **argv) {
	ut64 value = rz_num_math(core->num, argv[1]);
	bool big_endian = rz_config_get_b(core->config, "cfg.bigendian");
	int offset = rz_debruijn_offset(0, NULL, value, big_endian);
	if (offset < 0) {
		RZ_LOG_ERROR("Could not find value %" PFMT64x " in Debrujn sequence.\n", value);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_printf("%d\n", offset);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern_oxff_handler(RzCore *core, int argc, const char **argv) {
	st64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 1) {
		RZ_LOG_ERROR("Invalid pattern length\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (st64 i = 0; i < len; i++) {
		rz_cons_print("ff");
	}
	rz_cons_newline();
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_pattern_num_handler(RzCore *core, int argc, const char **argv) {
	st64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 1) {
		RZ_LOG_ERROR("Invalid pattern length\n");
		return RZ_CMD_STATUS_ERROR;
	}
	size_t bs = 4;
	ut8 *buf = calloc(bs, 1);
	if (!buf) {
		RZ_LOG_ERROR("Cannot allocate buffer\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (st64 i = 0; i < len; i++) {
		incDigitBuffer(buf, bs);
		for (st64 j = 0; j < bs; j++) {
			rz_cons_printf("%c", buf[j] ? buf[j] : '0');
		}
		rz_cons_printf(" ");
	}
	rz_cons_newline();
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_operation_2swap_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_BYTESWAP2, NULL));
}

RZ_IPI RzCmdStatus rz_print_operation_4swap_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_BYTESWAP4, NULL));
}

RZ_IPI RzCmdStatus rz_print_operation_8swap_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_BYTESWAP8, NULL));
}

RZ_IPI RzCmdStatus rz_print_operation_add_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_ADD, argv[1]));
}

RZ_IPI RzCmdStatus rz_print_operation_and_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_AND, argv[1]));
}

RZ_IPI RzCmdStatus rz_print_operation_div_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_DIV, argv[1]));
}

RZ_IPI RzCmdStatus rz_print_operation_shl_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_SHIFT_LEFT, argv[1]));
}

RZ_IPI RzCmdStatus rz_print_operation_mul_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_MUL, argv[1]));
}

RZ_IPI RzCmdStatus rz_print_operation_or_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_OR, argv[1]));
}

RZ_IPI RzCmdStatus rz_print_operation_shr_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_SHIFT_RIGHT, argv[1]));
}

RZ_IPI RzCmdStatus rz_print_operation_sub_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_SUB, argv[1]));
}

RZ_IPI RzCmdStatus rz_print_operation_xor_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(print_operation_transform(core, RZ_CORE_WRITE_OP_XOR, argv[1]));
}

RZ_IPI RzCmdStatus rz_print_key_randomart_handler(RzCore *core, int argc, const char **argv) {
	ut64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len == 0) {
		return RZ_CMD_STATUS_ERROR;
	}
	len = len > core->blocksize ? core->blocksize : len;
	char *s = rz_hash_cfg_randomart(core->block, len, core->offset);
	rz_cons_println(s);
	free(s);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_key_mosaic_handler(RzCore *core, int argc, const char **argv) {
	ut64 len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len == 0) {
		return RZ_CMD_STATUS_ERROR;
	}
	len = len > core->blocksize ? core->blocksize : len;
	int w, h;
	RzConsCanvas *c;
	w = rz_cons_get_size(&h);
	ut64 offset0 = core->offset;
	int cols = (w / 20);
	int rows = (h / 12);
	int i, j;
	char *s;
	if (rows < 1) {
		rows = 1;
	}
	c = rz_cons_canvas_new(w, rows * 11);
	for (i = 0; i < rows; i++) {
		for (j = 0; j < cols; j++) {
			rz_cons_canvas_gotoxy(c, j * 20, i * 11);
			core->offset += len;
			rz_io_read_at_mapped(core->io, core->offset, core->block, len);
			s = rz_hash_cfg_randomart(core->block, len, core->offset);
			rz_cons_canvas_write(c, s);
			free(s);
		}
	}
	rz_cons_canvas_print(c);
	rz_cons_canvas_free(c);
	rz_io_read_at_mapped(core->io, offset0, core->block, len);
	core->offset = offset0;
	rz_cons_printf("\n");
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_instr_handler(RzCore *core, int argc, const char **argv) {
	if (argc <= 1) {
		RZ_LOG_ERROR("Invalid arguments\n");
		return RZ_CMD_STATUS_ERROR;
	}
	ut64 N = rz_num_math(core->num, argv[1]);
	if (N == 0) {
		RZ_LOG_ERROR("The argument cannot be zero\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_core_print_disasm_instructions(core, 0, N);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_instr_opcodes_handler(RzCore *core, int argc, const char **argv) {
	ut64 N = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (N == 0) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_core_print_disasm_all(core, core->offset, N, N);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_instr_block_handler(RzCore *core, int argc, const char **argv) {
	RzAnalysisBlock *b = rz_analysis_find_most_relevant_block_in(core->analysis, core->offset);
	if (!b) {
		RZ_LOG_ERROR("core: Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		core->num->value = 0;
		return RZ_CMD_STATUS_ERROR;
	}
	rz_core_print_disasm_instructions(core, b->size - (core->offset - b->addr), 0);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_instr_esil_handler(RzCore *core, int argc, const char **argv) {
	ut64 N = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (N == 0) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_core_disasm_pdi(core, N, 0, 'e');
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_instr_function_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	if (state->mode == RZ_OUTPUT_MODE_JSON) {
		return rz_cmd_disassembly_function_handler(core, argc, argv, state);
	}
	RzAnalysisFunction *f = rz_analysis_get_fcn_in(core->analysis, core->offset,
		RZ_ANALYSIS_FCN_TYPE_FCN | RZ_ANALYSIS_FCN_TYPE_SYM);
	if (!f) {
		return RZ_CMD_STATUS_ERROR;
	}

	ut32 bsz = core->blocksize;
	// int fsz = rz_analysis_function_realsize (f);
	int fsz = rz_analysis_function_linear_size(f); // we want max-min here
	rz_core_block_size(core, fsz);
	rz_core_print_disasm_instructions(core, fsz, 0);
	rz_core_block_size(core, bsz);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_calls_function_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzListIter *iter;
	RzAnalysisXRef *xrefi;
	RzList *refs = NULL;

	rz_cmd_state_output_array_start(state);
	// get function in current offset
	RzAnalysisFunction *f = rz_analysis_get_fcn_in(core->analysis, core->offset,
		RZ_ANALYSIS_FCN_TYPE_FCN | RZ_ANALYSIS_FCN_TYPE_SYM);

	if (!f) {
		rz_cmd_state_output_array_end(state);
		return RZ_CMD_STATUS_ERROR;
	}

	// get all the calls of the function
	refs = rz_core_analysis_fcn_get_calls(core, f);
	if (rz_list_empty(refs)) {
		rz_cmd_state_output_array_end(state);
		rz_list_free(refs);
		return RZ_CMD_STATUS_OK;
	}

	// store current configurations
	RzConfigHold *hc = rz_config_hold_new(core->config);
	rz_config_hold_var(hc, "asm.offset", NULL);
	rz_config_hold_var(hc, "asm.comments", NULL);
	rz_config_hold_var(hc, "asm.tabs", NULL);
	rz_config_hold_var(hc, "asm.bytes", NULL);
	rz_config_hold_var(hc, "emu.str", NULL);

	// temporarily replace configurations
	rz_config_set_i(core->config, "asm.offset", false);
	rz_config_set_i(core->config, "asm.comments", false);
	rz_config_set_i(core->config, "asm.tabs", 0);
	rz_config_set_i(core->config, "asm.bytes", false);
	rz_config_set_i(core->config, "emu.str", false);

	// iterate over all call references
	rz_list_foreach (refs, iter, xrefi) {
		if (state->mode == RZ_OUTPUT_MODE_JSON) {
			RzAnalysisFunction *f = rz_analysis_get_fcn_in(core->analysis, xrefi->to,
				RZ_ANALYSIS_FCN_TYPE_FCN | RZ_ANALYSIS_FCN_TYPE_SYM);
			char *dst = rz_str_newf((f ? f->name : "0x%08" PFMT64x), xrefi->to);
			char *dst2 = NULL;
			RzAnalysisOp *op = rz_core_analysis_op(core, xrefi->to, RZ_ANALYSIS_OP_MASK_BASIC);
			RzBinReloc *rel = rz_core_getreloc(core, xrefi->to, op->size);
			if (rel) {
				if (rel && rel->import && rel->import->name) {
					dst2 = rel->import->name;
				} else if (rel && rel->symbol && rel->symbol->name) {
					dst2 = rel->symbol->name;
				}
			} else {
				dst2 = dst;
			}
			pj_o(state->d.pj);
			pj_ks(state->d.pj, "dest", dst2);
			pj_kn(state->d.pj, "addr", xrefi->to);
			pj_kn(state->d.pj, "at", xrefi->from);
			pj_end(state->d.pj);
			rz_analysis_op_free(op);
			free(dst);
		} else {
			ut64 off = core->offset;
			rz_core_seek(core, xrefi->from, true);
			core_disassembly(core, 1, 1, state, false);
			rz_core_seek(core, off, true);
		}
	}

	rz_list_free(refs);

	// restore saved configuration
	rz_config_hold_restore(hc);
	rz_config_hold_free(hc);

	rz_cmd_state_output_array_end(state);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_instr_recursive_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzAnalysisFunction *f = rz_analysis_get_fcn_in(core->analysis, core->offset,
		RZ_ANALYSIS_FCN_TYPE_FCN | RZ_ANALYSIS_FCN_TYPE_SYM);
	if (!f) {
		RZ_LOG_ERROR("core: Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		core->num->value = 0;
		return RZ_CMD_STATUS_ERROR;
	}
	func_walk_blocks(core, f, false, state);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_instr_recursive_at_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	RzAnalysisFunction *f = rz_analysis_get_fcn_in(core->analysis, core->offset,
		RZ_ANALYSIS_FCN_TYPE_FCN | RZ_ANALYSIS_FCN_TYPE_SYM);
	if (!f) {
		RZ_LOG_ERROR("core: Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		core->num->value = 0;
		return RZ_CMD_STATUS_ERROR;
	}
	func_walk_blocks(core, f, true, state);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_instr_until_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	const ut64 limit = argc > 1 ? rz_num_math(core->num, argv[1]) : 1024;
	if (!rz_core_disasm_until_ret(core, core->offset, limit, state->mode, false, NULL)) {
		return RZ_CMD_STATUS_ERROR;
	}
	return RZ_CMD_STATUS_OK;
}

typedef struct rz_core_analysis_stats_range_t {
	RzCoreAnalysisStats *as;
	ut64 from;
	ut64 to;
	ut64 piece;
} RzCoreAnalysisStatsRange;

static RzCoreAnalysisStatsRange *analysis_stats_range(RzCore *core, int width) {
	int cols = rz_config_get_i(core->config, "hex.cols");
	int w = RZ_MAX(cols, width);

	ut64 from = UT64_MAX;
	ut64 to = 0;

	RzList *list = rz_core_get_boundaries_select(core, "search.from", "search.to", "search.in");
	if (rz_list_empty(list)) {
		RZ_LOG_ERROR("No range to calculate stats for.\n");
		rz_list_free(list);
		return NULL;
	}
	RzCoreAnalysisStatsRange *srange = RZ_NEW0(RzCoreAnalysisStatsRange);
	if (!srange) {
		rz_list_free(list);
		return NULL;
	}
	RzListIter *iter;
	RzIOMap *map;
	rz_list_foreach (list, iter, map) {
		ut64 f = rz_itv_begin(map->itv);
		ut64 t = rz_itv_end(map->itv);
		if (f < from) {
			from = f;
		}
		if (t > to) {
			to = t;
		}
	}
	rz_list_free(list);
	srange->from = from;
	srange->to = to;

	ut64 piece = RZ_MAX((to - from) / w, 1);
	if (piece * w != to - from) {
		// add 1 to compute `piece = ceil((to - from) / w)` instead
		piece++;
	}
	srange->piece = piece;

	srange->as = rz_core_analysis_get_stats(core, from, to - 1, piece);
	return srange;
}

static void analysis_stats_range_free(RzCoreAnalysisStatsRange *srange) {
	if (!srange) {
		return;
	}
	rz_core_analysis_stats_free(srange->as);
	free(srange);
}

static void analysis_stats_standard_info(RzCore *core, RzCoreAnalysisStatsRange *srange, RzCoreAnalysisStatsItem *sitem, ut64 blockidx, bool use_color) {
	ut64 at = rz_core_analysis_stats_get_block_from(srange->as, blockidx);
	ut64 ate = rz_core_analysis_stats_get_block_to(srange->as, blockidx) + 1;
	if (core->offset >= at && core->offset < ate) {
		rz_cons_memcat("^", 1);
	} else {
		RzIOMap *s = rz_io_map_get(core->io, at);
		if (use_color) {
			if (s) {
				if (s->perm & RZ_PERM_X) {
					rz_cons_print(rz_cons_singleton()->context->pal.graph_ujump);
				} else {
					rz_cons_print(rz_cons_singleton()->context->pal.graph_true);
				}
			} else {
				rz_cons_print(rz_cons_singleton()->context->pal.graph_false);
			}
		}
		if (sitem->strings > 0) {
			rz_cons_memcat("z", 1);
		} else if (sitem->signatures) {
			rz_cons_memcat("S", 1);
		} else if (sitem->imports > 0) {
			rz_cons_memcat("i", 1);
		} else if (sitem->symbols > 0) {
			rz_cons_memcat("s", 1);
		} else if (sitem->functions > 0) {
			rz_cons_memcat("F", 1);
		} else if (sitem->comments > 0) {
			rz_cons_memcat("c", 1);
		} else if (sitem->flags > 0) {
			rz_cons_memcat(".", 1);
		} else if (sitem->in_functions > 0) {
			rz_cons_memcat("f", 1);
		} else {
			rz_cons_memcat("_", 1);
		}
	}
	if (use_color) {
		rz_cons_print(Color_RESET);
	}
}

static void analysis_stats_json_info(RzCore *core, RzCoreAnalysisStats *as, RzCoreAnalysisStatsItem *sitem, ut64 blockidx, RzCmdStateOutput *state) {
	ut64 at = rz_core_analysis_stats_get_block_from(as, blockidx);
	ut64 ate = rz_core_analysis_stats_get_block_to(as, blockidx) + 1;
	pj_o(state->d.pj);
	if ((sitem->flags) || (sitem->functions) || (sitem->comments) || (sitem->symbols) || (sitem->perm) || (sitem->strings) || (sitem->signatures) || (sitem->imports)) {
		pj_kn(state->d.pj, "offset", at);
		pj_kn(state->d.pj, "size", ate - at);
	}
	if (sitem->flags) {
		pj_ki(state->d.pj, "flags", sitem->flags);
	}
	if (sitem->functions) {
		pj_ki(state->d.pj, "functions", sitem->functions);
	}
	if (sitem->in_functions) {
		pj_ki(state->d.pj, "in_functions", sitem->in_functions);
	}
	if (sitem->comments) {
		pj_ki(state->d.pj, "comments", sitem->comments);
	}
	if (sitem->symbols) {
		pj_ki(state->d.pj, "symbols", sitem->symbols);
	}
	if (sitem->strings) {
		pj_ki(state->d.pj, "strings", sitem->strings);
	}
	if (sitem->signatures) {
		pj_ki(state->d.pj, "signatures", sitem->signatures);
	}
	if (sitem->imports) {
		pj_ki(state->d.pj, "imports", sitem->imports);
	}
	if (sitem->perm) {
		pj_ks(state->d.pj, "perm", rz_str_rwx_i(sitem->perm));
	}
	pj_end(state->d.pj);
}

static void analysis_stats_table_info(RzCore *core, RzCoreAnalysisStats *as, RzCoreAnalysisStatsItem *sitem, ut64 blockidx, RzCmdStateOutput *state) {
	ut64 at = rz_core_analysis_stats_get_block_from(as, blockidx);
	if ((sitem->flags) || (sitem->functions) || (sitem->comments) || (sitem->symbols) || (sitem->strings) || (sitem->signatures) || (sitem->imports)) {
		rz_table_add_rowf(state->d.t, "xddddddd", at, sitem->flags,
			sitem->functions, sitem->comments, sitem->symbols, sitem->strings, sitem->signatures, sitem->imports);
	}
}

static void analysis_stats_entropy_info(RzCore *core, RzCoreAnalysisStats *as, ut64 blockidx, bool use_color) {
	ut64 at = rz_core_analysis_stats_get_block_from(as, blockidx);
	ut64 ate = rz_core_analysis_stats_get_block_to(as, blockidx) + 1;
	ut8 *blockptr = malloc(ate - at);
	if (!blockptr) {
		return;
	}
	if (rz_io_read_at_mapped(core->io, at, blockptr, (ate - at))) {
		ut8 entropy = (ut8)(rz_hash_entropy_fraction(blockptr, (ate - at)) * 255);
		entropy = 9 * entropy / 200; // normalize entropy from 0 to 9
		if (use_color) {
			const char *color =
				(entropy > 6) ? Color_BGRED : (entropy > 3) ? Color_BGGREEN
									    : Color_BGBLUE;
			rz_cons_printf("%s%d" Color_RESET, color, entropy);
		} else {
			rz_cons_printf("%d", entropy);
		}
	}
	free(blockptr);
	if (use_color) {
		rz_cons_print(Color_RESET);
	}
}

RZ_IPI RzCmdStatus rz_print_minus_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int width = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)(core->print->cols * 2.7);
	RzCoreAnalysisStatsRange *srange = analysis_stats_range(core, width);
	if (!srange) {
		RZ_LOG_ERROR("Cannot find valid range for calculating the analysis information\n");
		return RZ_CMD_STATUS_ERROR;
	}
	bool use_color = rz_config_get_i(core->config, "scr.color");
	switch (state->mode) {
	case RZ_OUTPUT_MODE_JSON:
		pj_o(state->d.pj);
		pj_kn(state->d.pj, "from", srange->from);
		pj_kn(state->d.pj, "to", srange->to);
		pj_ki(state->d.pj, "blocksize", srange->piece);
		pj_ka(state->d.pj, "blocks");
		for (size_t i = 0; i < rz_vector_len(&srange->as->blocks); i++) {
			RzCoreAnalysisStatsItem *sitem = rz_vector_index_ptr(&srange->as->blocks, i);
			analysis_stats_json_info(core, srange->as, sitem, i, state);
		}
		pj_end(state->d.pj);
		pj_end(state->d.pj);
		break;
	case RZ_OUTPUT_MODE_STANDARD:
		rz_cons_printf("0x%08" PFMT64x " [", srange->from);
		for (size_t i = 0; i < rz_vector_len(&srange->as->blocks); i++) {
			RzCoreAnalysisStatsItem *sitem = rz_vector_index_ptr(&srange->as->blocks, i);
			analysis_stats_standard_info(core, srange, sitem, i, use_color);
		}
		rz_cons_printf("] 0x%08" PFMT64x "\n", srange->to);
		break;
	default:
		rz_warn_if_reached();
		break;
	}
	analysis_stats_range_free(srange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_minus_entropy_handler(RzCore *core, int argc, const char **argv) {
	int width = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)(core->print->cols * 2.7);
	RzCoreAnalysisStatsRange *srange = analysis_stats_range(core, width);
	if (!srange) {
		RZ_LOG_ERROR("Cannot find valid range for calculating the analysis information\n");
		return RZ_CMD_STATUS_ERROR;
	}
	bool use_color = rz_config_get_i(core->config, "scr.color");
	rz_cons_printf("0x%08" PFMT64x " [", srange->from);
	for (size_t i = 0; i < rz_vector_len(&srange->as->blocks); i++) {
		analysis_stats_entropy_info(core, srange->as, i, use_color);
	}
	rz_cons_printf("] 0x%08" PFMT64x "\n", srange->to);
	analysis_stats_range_free(srange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_minus_table_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int width = argc > 1 ? (int)rz_num_math(core->num, argv[1]) : (int)(core->print->cols * 2.7);
	RzCoreAnalysisStatsRange *srange = analysis_stats_range(core, width);
	if (!srange) {
		RZ_LOG_ERROR("Cannot find valid range for calculating the analysis information\n");
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cmd_state_output_array_start(state);
	rz_cmd_state_output_set_columnsf(state, "xddddddd", "offset", "flags", "funcs", "cmts", "syms", "str", "sigs", "imps");
	state->d.t->showSum = true;
	state->d.t->showFancy = true;
	for (size_t i = 0; i < rz_vector_len(&srange->as->blocks); i++) {
		RzCoreAnalysisStatsItem *sitem = rz_vector_index_ptr(&srange->as->blocks, i);
		analysis_stats_table_info(core, srange->as, sitem, i, state);
	}
	rz_cmd_state_output_array_end(state);
	analysis_stats_range_free(srange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_columns_disassembly_handler(RzCore *core, int argc, const char **argv) {
	int h, w = rz_cons_get_size(&h);
	int colwidth = rz_config_get_i(core->config, "hex.cols") * 2.5;
	if (colwidth < 1) {
		colwidth = 16;
	}
	int i, columns = w / colwidth;
	int user_rows = argc > 1 ? rz_num_math(core->num, argv[1]) : -1;
	int rows = user_rows > 0 ? user_rows : h - 2;

	RzConfigHold *ch = rz_config_hold_new(core->config);
	rz_config_hold_var(ch, "asm.offset", "asm.bytes", NULL);
	if (rz_config_get_i(core->config, "asm.minicols")) {
		rz_config_set_b(core->config, "asm.offset", false);
	}
	rz_config_set_b(core->config, "asm.bytes", false);

	RzConsCanvas *c = rz_cons_canvas_new(w, rows);
	ut64 osek = core->offset;
	int pos_i = 0;
	c->color = rz_config_get_i(core->config, "scr.color");
	for (i = 0; i < columns; i++) {
		(void)rz_cons_canvas_gotoxy(c, i * (w / columns), 0);
		// TODO: Use the API directly
		char *cmd = rz_str_newf("pdq %d @i:%d", rows, pos_i);
		char *dis = rz_core_cmd_str(core, cmd);
		if (dis) {
			RzList *dis_lines = rz_str_split_duplist_n(dis, "\n", 0, false);
			ut32 n_lines = rz_list_length(dis_lines);
			rz_list_free(dis_lines);

			// If the output contains more lines than expected, do not move
			// forward the whole chunk as some data will be hidden.
			if (n_lines > rows) {
				pos_i -= (n_lines - rows - 1);
			}

			rz_cons_canvas_write(c, dis);
		}
		free(cmd);
		free(dis);

		pos_i += rows;
	}
	rz_core_seek(core, osek, true);

	rz_cons_canvas_print(c);
	rz_cons_canvas_free(c);
	rz_cons_printf("\n");

	rz_config_hold_restore(ch);
	rz_config_hold_free(ch);
	return RZ_CMD_STATUS_OK;
}

static void print_stack(RzCore *core) {
	RzCmdStateOutput so;
	ut64 sp_addr = rz_core_reg_getv_by_role_or_name(core, "SP");
	if (rz_config_get_b(core->config, "dbg.slow")) {
		rz_cmd_state_output_init(&so, RZ_OUTPUT_MODE_STANDARD, core);
		int wordsize = rz_analysis_get_address_bits(core->analysis) / 8;
		cmd_pxr(core, sp_addr, 128, &so, wordsize, NULL);
		rz_cmd_state_output_print(&so);
		rz_cmd_state_output_fini(&so);
	} else if (rz_config_get_b(core->config, "stack.bytes")) {
		char *string = rz_core_print_hexdump_or_hexdiff_str(core, RZ_OUTPUT_MODE_STANDARD, sp_addr, 128, false);
		if (!string) {
			RZ_LOG_ERROR("fail to print hexdump at 0x%" PFMT64x "\n", sp_addr);
			return; // TODO: free stuff
		}
		rz_cons_print(string);
	} else if (rz_asm_is_bits(core->rasm, 64)) {
		rz_core_print_dump(core, RZ_OUTPUT_MODE_STANDARD, sp_addr, 8, 128, RZ_CORE_PRINT_FORMAT_TYPE_HEXADECIMAL);
	} else if (rz_asm_is_bits(core->rasm, 32)) {
		rz_core_print_dump(core, RZ_OUTPUT_MODE_STANDARD, sp_addr, 4, 128, RZ_CORE_PRINT_FORMAT_TYPE_HEXADECIMAL);
	}
	rz_cmd_state_output_init(&so, RZ_OUTPUT_MODE_STANDARD, core);
	core_disassembly(core, core->blocksize, 0, &so, false);
	rz_cmd_state_output_fini(&so);
}

RZ_IPI RzCmdStatus rz_print_columns_debug_handler(RzCore *core, int argc, const char **argv) {
	if (!rz_config_get_b(core->config, "cfg.debug")) {
		RZ_LOG_ERROR("Command works only in debug mode\n");
		return RZ_CMD_STATUS_ERROR;
	}
	int h, w = rz_cons_get_size(&h);
	int rows = h - 2;
	int obsz = core->blocksize;
	int user_rows = argc > 1 ? rz_num_math(core->num, argv[1]) : -1;
	if (user_rows > 0) {
		rows = user_rows;
	}
	bool asm_minicols = rz_config_get_i(core->config, "asm.minicols");
	char *o_ao = rz_str_dup(rz_config_get(core->config, "asm.offset"));
	char *o_ab = rz_str_dup(rz_config_get(core->config, "asm.bytes"));
	if (asm_minicols) {
		// Set asm.offset and asm.bytes configs to false to avoid printing them
		rz_config_set_b(core->config, "asm.offset", false);
		rz_config_set_b(core->config, "asm.bytes", false);
	}
	rz_config_set_b(core->config, "asm.bytes", false);
	RzConsCanvas *c = rz_cons_canvas_new(w, rows);
	ut64 osek = core->offset;
	c->color = rz_config_get_i(core->config, "scr.color");
	rz_core_block_size(core, rows * 32);

	// Left column
	RzCmdStateOutput so;
	(void)rz_cons_canvas_gotoxy(c, 0, 0);
	rz_cons_push();
	rz_debug_regs_args_handler(core, 0, NULL, RZ_OUTPUT_MODE_STANDARD);
	rz_cons_print("\nbacktrace:\n");
	rz_cmd_state_output_init(&so, RZ_OUTPUT_MODE_STANDARD, core);
	rz_cmd_debug_display_bt_handler(core, 0, NULL, &so);
	rz_cmd_state_output_print(&so);
	rz_cmd_state_output_fini(&so);
	rz_cons_canvas_write(c, rz_cons_get_buffer());
	rz_cons_pop();

	// Right column
	(void)rz_cons_canvas_gotoxy(c, RZ_MAX(w / 3, 28), 0);
	rz_cons_push();
	print_stack(core);
	rz_cons_canvas_write(c, rz_cons_get_buffer());
	rz_cons_pop();

	rz_core_block_size(core, obsz);
	rz_core_seek(core, osek, true);
	rz_cons_canvas_print(c);
	rz_cons_canvas_free(c);

	if (asm_minicols) {
		rz_config_set(core->config, "asm.offset", o_ao);
		rz_config_set(core->config, "asm.bytes", o_ab);
	}
	rz_config_set(core->config, "asm.bytes", o_ab);
	free(o_ao);
	free(o_ab);
	rz_cons_printf("\n");
	return RZ_CMD_STATUS_OK;
}

static bool print_hexdump_columns(RzCore *core, int user_rows, bool has_header, const char *xcmd) {
	int h, w = rz_cons_get_size(&h);
	int hex_cols = rz_config_get_i(core->config, "hex.cols");
	int colwidth = hex_cols * 5;
	int i, columns = w / (colwidth * 0.9);
	int rows = user_rows > 0 ? user_rows : h - 2;

	RzConfigHold *ch = rz_config_hold_new(core->config);
	rz_config_hold_var(ch, "hex.cols", NULL);
	rz_config_set_i(core->config, "hex.cols", colwidth / 5);

	// Add one more line for the hexdump header
	int canvas_rows = rows + (has_header ? 1 : 0);
	RzConsCanvas *c = rz_cons_canvas_new(w, canvas_rows);
	if (!c) {
		RZ_LOG_ERROR("core: Couldn't allocate a canvas with %d rows\n", rows);
		rz_config_set_i(core->config, "hex.cols", hex_cols);
		return false;
	}

	ut64 tsek = core->offset;
	c->color = rz_config_get_i(core->config, "scr.color");
	int bsize = hex_cols * rows;
	if (!strcmp(xcmd, "pxAl")) {
		bsize *= 12;
	}
	for (i = 0; i < columns; i++) {
		(void)rz_cons_canvas_gotoxy(c, i * (w / columns), 0);
		char *cmd = rz_str_newf("%s %d @ %" PFMT64u, xcmd, bsize, tsek);
		char *dis = rz_core_cmd_str(core, cmd);
		if (dis) {
			RzList *dis_lines = rz_str_split_duplist_n(dis, "\n", 0, false);
			// Count the lines do not contain actual data and handle them for
			// the next column
			RzListIter *it;
			char *line;
			int i = 0, diff_lines = 0;
			rz_list_foreach (dis_lines, it, line) {
				if (line[0] == ' ' && i < canvas_rows) {
					diff_lines++;
				}
				i++;
			}
			rz_list_free(dis_lines);
			if (!UT64_MUL_OVFCHK(diff_lines, hex_cols)) {
				tsek -= diff_lines * hex_cols;
			}

			rz_cons_canvas_write(c, dis);
			free(dis);
		}
		free(cmd);
		tsek += bsize;
	}

	rz_cons_canvas_print(c);
	rz_cons_canvas_free(c);
	rz_cons_printf("\n");

	rz_config_hold_restore(ch);
	rz_config_hold_free(ch);
	return true;
}

RZ_IPI RzCmdStatus rz_print_columns_hex_annotated_handler(RzCore *core, int argc, const char **argv) {
	int user_rows = argc > 1 ? rz_num_math(core->num, argv[1]) : -1;
	return bool2status(print_hexdump_columns(core, user_rows, true, "pxa"));
}

RZ_IPI RzCmdStatus rz_print_columns_hex_op_colored_handler(RzCore *core, int argc, const char **argv) {
	int user_rows = argc > 1 ? rz_num_math(core->num, argv[1]) : -1;
	return bool2status(print_hexdump_columns(core, user_rows, false, "pxAl"));
}

RZ_IPI RzCmdStatus rz_print_columns_hex_handler(RzCore *core, int argc, const char **argv) {
	int user_rows = argc > 1 ? rz_num_math(core->num, argv[1]) : -1;
	return bool2status(print_hexdump_columns(core, user_rows, true, "px"));
}

RZ_IPI RzCmdStatus rz_print_columns_hex_words_handler(RzCore *core, int argc, const char **argv) {
	int user_rows = argc > 1 ? rz_num_math(core->num, argv[1]) : -1;
	return bool2status(print_hexdump_columns(core, user_rows, false, "pxw"));
}

RZ_IPI RzCmdStatus rz_print_equal_d_handler(RzCore *core, int argc, const char **argv) {
	int min = -1, max = 0, dict = 0, range = 0;
	bool histogram[256] = { 0 };
	const ut8 *block = core->block;
	ut32 bsz = core->blocksize;
	for (size_t i = 0; i < bsz; i++) {
		histogram[block[i]] = true;
	}
	for (size_t i = 0; i < 256; i++) {
		if (histogram[i]) {
			if (min == -1) {
				min = i;
			}
			max = i;
			dict++;
		}
	}
	range = max - min;
	rz_cons_printf("min:              %d  0x%x\n", min, min);
	rz_cons_printf("max:              %d  0x%x\n", max, max);
	rz_cons_printf("unique (count):   %d  0x%x\n", dict, dict);
	rz_cons_printf("range (max-min):  %d  0x%x\n", range, range);
	rz_cons_printf("size (of block):  %d  0x%x\n", bsz, bsz);
	return RZ_CMD_STATUS_OK;
}

typedef struct core_block_range_t {
	ut64 from;
	ut64 to;
	ut64 totalsize;
	int nblocks;
	st64 blocksize;
	int skipblocks;
} CoreBlockRange;

static CoreBlockRange *calculate_blocks_range(RzCore *core, ut64 from, ut64 to, ut64 totalsize, int nblocks, int skipblocks) {
	if (nblocks == 0) {
		return NULL;
	}
	CoreBlockRange *brange = RZ_NEW0(CoreBlockRange);
	if (!brange) {
		return NULL;
	}

	if (totalsize == UT64_MAX) {
		if (rz_config_get_b(core->config, "cfg.debug")) {
			RzDebugMap *map = rz_debug_map_get(core->dbg, core->offset);
			if (map) {
				brange->totalsize = map->addr_end - map->addr;
				brange->from = map->addr;
			}
		} else {
			if (core->file && core->io) {
				brange->totalsize = rz_io_fd_size(core->io, core->file->fd);
				if ((st64)brange->totalsize < 1) {
					brange->totalsize = UT64_MAX;
				}
			}
			if (brange->totalsize == UT64_MAX) {
				RZ_LOG_ERROR("core: Cannot determine file size\n");
				free(brange);
				return NULL;
			}
		}
	} else {
		brange->totalsize = totalsize;
	}
	st64 blocksize = core->blocksize;
	// If we are not in the debug mode - use only current mapped ranges
	if (!rz_config_get_b(core->config, "cfg.debug")) {
		RzList *boundaries = rz_core_get_boundaries_select(core, "zoom.from", "zoom.to", "zoom.in");
		if (!boundaries) {
			free(brange);
			return NULL;
		}
		RzIOMap *map = rz_list_first_val(boundaries);
		if (map) {
			brange->from = map->itv.addr;
			RzIOMap *m;
			RzListIter *iter;
			rz_list_foreach (boundaries, iter, m) {
				brange->to = rz_itv_end(m->itv);
			}
			brange->totalsize = brange->to - brange->from;
		} else {
			brange->from = core->offset;
		}
		rz_list_free(boundaries);
	}
	if (nblocks < 1) {
		brange->nblocks = brange->totalsize / blocksize;
	} else {
		blocksize = brange->totalsize / nblocks;
		brange->nblocks = nblocks;
	}
	if (skipblocks > 0) {
		brange->skipblocks = skipblocks;
	}
	brange->blocksize = blocksize;
	return brange;
}

typedef enum {
	HISTOGRAM_ANALYSIS_BASIC_BLOCKS,
	HISTOGRAM_ANALYSIS_INVALID_INSTRUCTIONS,
	HISTOGRAM_ANALYSIS_CALL_INSTRUCTIONS,
	HISTOGRAM_ANALYSIS_JUMP_INSTRUCTIONS,
	HISTOGRAM_ANALYSIS_PRIV_INSTRUCTIONS,
} CoreAnalysisHistogramType;

static inline void data_array_increment_element(ut8 *data, int i) {
	if (data[i] < 0xff) {
		data[i]++;
	}
}

static bool if_aop_match_hist_type(RzAnalysisOp *op, CoreAnalysisHistogramType t) {
	switch (t) {
	case HISTOGRAM_ANALYSIS_BASIC_BLOCKS:
		/* We do not need to check the op type in this case */
		return false;
	case HISTOGRAM_ANALYSIS_CALL_INSTRUCTIONS: {
		switch (op->type) {
		case RZ_ANALYSIS_OP_TYPE_RCALL:
		case RZ_ANALYSIS_OP_TYPE_UCALL:
		case RZ_ANALYSIS_OP_TYPE_CALL:
			return true;
		}
		break;
	}
	case HISTOGRAM_ANALYSIS_PRIV_INSTRUCTIONS: {
		if (op->family == RZ_ANALYSIS_OP_FAMILY_PRIV) {
			return true;
		}
		switch (op->type) {
		case RZ_ANALYSIS_OP_TYPE_SWI:
			return true;
		}
		break;
	}
	case HISTOGRAM_ANALYSIS_INVALID_INSTRUCTIONS: {
		switch (op->type) {
		case RZ_ANALYSIS_OP_TYPE_TRAP:
		case RZ_ANALYSIS_OP_TYPE_ILL:
			return true;
		}
		break;
	}
	case HISTOGRAM_ANALYSIS_JUMP_INSTRUCTIONS: {
		switch (op->type) {
		case RZ_ANALYSIS_OP_TYPE_JMP:
		// case RZ_ANALYSIS_OP_TYPE_RJMP:
		// case RZ_ANALYSIS_OP_TYPE_UJMP:
		case RZ_ANALYSIS_OP_TYPE_CJMP:
			return true;
		default:
			break;
		}
		break;
	}
	}
	return false;
}

// Counts all analysis metainformation units per block
static ut8 *analysis_stats_histogram_data(RzCore *core, CoreBlockRange *brange) {
	ut8 *data = calloc(1, brange->nblocks);
	if (!data) {
		RZ_LOG_ERROR("core: Failed to allocate memory\n");
		return NULL;
	}
	// FIXME: Should we just use the value from brange instead?
	ut64 to = brange->from + (brange->blocksize * brange->nblocks) - 1;
	if (to < brange->from) {
		free(data);
		return NULL;
	}
	RzCoreAnalysisStats *as = rz_core_analysis_get_stats(core, brange->from, to, brange->blocksize);
	if (!as) {
		free(data);
		return NULL;
	}
	for (size_t i = 0; i < RZ_MIN(brange->nblocks, rz_vector_len(&as->blocks)); i++) {
		int value = 0;
		RzCoreAnalysisStatsItem *block = rz_vector_index_ptr(&as->blocks, i);
		value += block->functions;
		value += block->in_functions;
		value += block->comments;
		value += block->symbols;
		value += block->flags;
		value += block->strings;
		value += block->blocks;
		data[i] = RZ_MIN(255, value);
	}
	rz_core_analysis_stats_free(as);
	return data;
}

static ut8 *analysis_histogram_data(RzCore *core, CoreBlockRange *brange, CoreAnalysisHistogramType hist_type) {
	size_t j, i = 0;
	ut8 *data = calloc(1, brange->nblocks);
	if (!data) {
		RZ_LOG_ERROR("core: Failed to allocate memory\n");
		return NULL;
	}
	for (i = 0; i < brange->nblocks; i++) {
		if (rz_cons_is_breaked()) {
			break;
		}
		ut64 off = brange->from + (i + brange->skipblocks) * brange->blocksize;
		for (j = 0; j < brange->blocksize; j++) {
			if (hist_type == HISTOGRAM_ANALYSIS_BASIC_BLOCKS) {
				RzAnalysisFunction *fcn = rz_analysis_get_fcn_in(core->analysis, off + j, 0);
				if (fcn) {
					data[i] = rz_pvector_len(fcn->bbs);
				}
				continue;
			}
			RzAnalysisOp *op = rz_core_analysis_op(core, off + j, RZ_ANALYSIS_OP_MASK_BASIC);
			if (op) {
				if (op->size < 1) {
					// do nothing
					if (hist_type == HISTOGRAM_ANALYSIS_INVALID_INSTRUCTIONS) {
						data_array_increment_element(data, i);
					}
				} else {
					if (if_aop_match_hist_type(op, hist_type)) {
						data_array_increment_element(data, i);
					}
				}
				if (op->size > 0) {
					j += op->size - 1;
				}
				rz_analysis_op_free(op);
			} else {
				if (hist_type == HISTOGRAM_ANALYSIS_INVALID_INSTRUCTIONS) {
					data_array_increment_element(data, i);
				}
			}
		}
	}
	return data;
}

// Build an RzHistogramOptions filled from config-driven defaults.
static RzHistogramOptions default_histogram_opts(RzConfig *config, RzConsPrintablePalette *pal, ut64 offset) {
	return (RzHistogramOptions){
		.unicode = rz_config_get_b(config, "scr.utf8"),
		.thinline = !rz_config_get_b(config, "scr.hist.block"),
		.ruler = rz_config_get_b(config, "scr.hist.ruler"),
		.legend = false,
		.offset = rz_config_get_b(config, "hex.offset"),
		.cursor = false,
		.color = rz_config_get_i(config, "scr.color"),
		.offpos = offset,
		.curpos = 0,
		.pal = pal,
	};
}

static bool print_histogram(RzCore *core, RZ_NONNULL RzHistogramOptions *opts, const ut8 *data, int width, int step, bool vertical) {
	bool hex_offset = rz_config_get_i(core->config, "hex.offset");
	core->print->num = core->num;
	if (hex_offset) {
		// TODO: Currently this option doesn't affect horizontal histograms
		core->print->flags |= RZ_PRINT_FLAGS_OFFSET;
	} else {
		core->print->flags &= ~RZ_PRINT_FLAGS_OFFSET;
	}
	// Compute the histogram screen size from configuration, clamped to the
	// current terminal dimensions so it never overflows the screen.
	int term_rows = 0;
	int term_cols = rz_cons_get_size(&term_rows);
	if (term_cols <= 0) {
		term_cols = 80;
	}
	if (term_rows <= 0) {
		term_rows = 24;
	}
	ut64 cfg_w = rz_config_get_i(core->config, "scr.hist.width");
	ut64 cfg_h = rz_config_get_i(core->config, "scr.hist.height");
	// Reserve a few columns/rows so the chart doesn't touch the terminal
	// edge: the Y-axis ruler gutter (up to ~6 cols for " 0.00|"), the
	// right-aligned X-axis end label (which would otherwise wrap when it
	// lands on the very last column), the 2 X-axis ruler lines and the
	// prompt that comes back on the next line.
	int avail_cols = term_cols > 10 ? term_cols - 8 : term_cols;
	int avail_rows = term_rows > 6 ? term_rows - 4 : term_rows;
	ut32 hist_cols = (cfg_w > 0) ? (ut32)RZ_MIN((int)cfg_w, avail_cols) : 78;
	ut32 hist_rows = (cfg_h > 0) ? (ut32)RZ_MIN((int)cfg_h, avail_rows) : 14;
	if (cfg_w > 0 && hist_cols < 4) {
		hist_cols = 4;
	}
	if (cfg_h > 0 && hist_rows < 2) {
		hist_rows = 2;
	}
	if (opts->cols == 0) {
		opts->cols = hist_cols;
	}
	if (opts->blocksize == 0 && step > 0) {
		opts->blocksize = (ut64)step;
	}
	RzStrBuf *strbuf = vertical
		? rz_histogram_vertical(opts, data, width, step)
		: rz_histogram_horizontal(opts, data, width, hist_rows);
	if (!strbuf) {
		return false;
	}
	char *histogram = rz_strbuf_drain(strbuf);
	rz_cons_print(histogram);
	free(histogram);
	return true;
}

static void showcursor(RzCore *core, int x) {
	if (!x) {
		int wheel = rz_config_get_i(core->config, "scr.wheel");
		if (wheel) {
			rz_cons_enable_mouse(true);
		}
	} else {
		rz_cons_enable_mouse(false);
	}
	rz_cons_show_cursor(x);
}

// Re-read the config-driven opts fields so `:` `e scr.hist.minimap=...` (and
// scr.hist.block / scr.utf8 / hex.offset / scr.color) take effect on the next
// redraw. Scenario-specific fields (value_max, data_f, ...) are preserved.
static void refresh_visual_opts_from_config(RzCore *core, RzHistogramOptions *opts) {
	if (!opts) {
		return;
	}
	opts->unicode = rz_config_get_b(core->config, "scr.utf8");
	opts->thinline = !rz_config_get_b(core->config, "scr.hist.block");
	opts->minimap = rz_config_get_b(core->config, "scr.hist.minimap");
	opts->offset = rz_config_get_b(core->config, "hex.offset");
	opts->color = rz_config_get_i(core->config, "scr.color");
}

static RZ_OWN RzHistogramOptions *default_visual_opts(RzCore *core, ut64 offset) {
	RzHistogramOptions *opts = rz_histogram_options_new();
	if (!opts) {
		return NULL;
	}
	opts->legend = false;
	opts->ruler = true;
	opts->offpos = offset;
	opts->cursor = false;
	opts->curpos = 0;
	opts->pal = &core->cons->context->pal;
	refresh_visual_opts_from_config(core, opts);
	return opts;
}

static const char *help_msg_visual_hist[] = {
	"?", "", "show this help",
	"hl", "", "move cursor left / right one bar",
	"+/-", "", "zoom in / out",
	"PgUp/PgDn", "", "jump left/right by 1/10 of the histogram width",
	":cmd", "", "run a rizin command",
	"q", "", "back to Visual mode (or Q / Space)",
	NULL
};

static RzCmdStatus print_visual_bytes(RzCore *core, RZ_OWN RZ_NONNULL RzHistogramOptions *opts, RZ_NONNULL const ut8 *data, RZ_NONNULL CoreBlockRange *brange) {
	if (!rz_cons_is_interactive()) {
		RZ_LOG_ERROR("core: visual mode requires scr.interactive=true.\n");
		rz_histogram_options_free(opts);
		return RZ_CMD_STATUS_ERROR;
	}
	RzConsCanvas *can;
	bool exit_histogram = false, is_error = false;
	RzConfigHold *hc = rz_config_hold_new(core->config);
	if (!hc) {
		rz_histogram_options_free(opts);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_config_hold_var(hc, "asm.pseudo", "asm.esil", "asm.cmt.right", NULL);

	int h, w = rz_cons_get_size(&h);
	can = rz_cons_canvas_new(w, h);
	if (!can) {
		w = 80;
		h = 25;
		can = rz_cons_canvas_new(w, h);
		if (!can) {
			RZ_LOG_ERROR("core: cannot create RzCons.canvas context. Invalid screen "
				     "size? See scr.columns + scr.rows\n");
			rz_config_hold_restore(hc);
			rz_config_hold_free(hc);
			rz_histogram_options_free(opts);
			return RZ_CMD_STATUS_ERROR;
		}
	}
	can->color = rz_config_get_i(core->config, "scr.color");

	RzHistogramInteractive *hist = rz_histogram_interactive_new(can, opts);
	if (!hist) {
		rz_config_hold_restore(hc);
		rz_config_hold_free(hc);
		rz_cons_canvas_free(can);
		rz_histogram_options_free(opts);
		return RZ_CMD_STATUS_ERROR;
	}
	hist->size = brange->nblocks;
	hist->blocksize = brange->blocksize;

	int okey, key;
	while (!exit_histogram && !is_error && !rz_cons_is_breaked()) {
		// Re-read scr.hist.minimap / scr.hist.block / scr.utf8 / scr.color /
		// hex.offset from config every iteration so that `:` + `e ...` <Enter>
		// from inside the visual histogram takes effect on the next redraw.
		refresh_visual_opts_from_config(core, hist->opts);
		can->color = rz_config_get_i(core->config, "scr.color");
		showcursor(core, false);
		w = rz_cons_get_size(&h);
		if (!rz_cons_canvas_resize(hist->can, w, h)) {
			rz_histogram_options_free(hist->opts);
			rz_config_hold_restore(hc);
			rz_config_hold_free(hc);
			rz_cons_canvas_free(can);
			return RZ_CMD_STATUS_ERROR;
		}
		hist->w = w;
		hist->h = h;
		// Pre-fetch up to 32 bytes at the cursor for the hex preview panel;
		// zero-initialised so a short or failed read simply shows zero bytes.
		ut8 cursor_bytes_buf[32] = { 0 };
		ut64 cursor_off = brange->from + (ut64)hist->barnumber * brange->blocksize;
		rz_io_read_at_mapped(core->io, cursor_off, cursor_bytes_buf, sizeof(cursor_bytes_buf));
		hist->cursor_bytes = cursor_bytes_buf;
		hist->cursor_bytes_len = sizeof(cursor_bytes_buf);
		RzStrBuf *str = rz_histogram_interactive_horizontal(hist, data);
		hist->cursor_bytes = NULL;
		hist->cursor_bytes_len = 0;
		rz_cons_canvas_write(hist->can, str->ptr);
		rz_strbuf_free(str);
		rz_cons_canvas_print_region(hist->can);
		rz_cons_newline();
		rz_cons_visual_flush();
		okey = rz_cons_readchar();
		key = rz_cons_arrow_to_hjkl(okey);
		switch (key) {
		case '?': {
			rz_cons_clear00();
			RzStrBuf *help = rz_strbuf_new(NULL);
			if (help) {
				rz_core_visual_append_help(help, "Visual histogram keybindings", help_msg_visual_hist);
				rz_cons_less_str(rz_strbuf_get(help), "?");
				rz_strbuf_free(help);
			}
			break;
		}
		case ':':
			rz_core_visual_prompt_input(core);
			break;
		case 'h':
			hist->barnumber = (hist->barnumber > 0) ? (hist->barnumber - 1) : (brange->nblocks - 1);
			break;
		case 'J': {
			size_t jump = brange->nblocks / 10;
			hist->barnumber = (hist->barnumber + jump) % brange->nblocks;
			break;
		}
		case 'K': {
			size_t jump = brange->nblocks / 10;
			hist->barnumber = (hist->barnumber - (jump % brange->nblocks) + brange->nblocks) % brange->nblocks;
			break;
		}
		case 'l':
			hist->barnumber = (hist->barnumber == brange->nblocks - 1) ? (0) : (hist->barnumber + 1);
			break;
		case '+':
			rz_histogram_interactive_zoom_in(hist);
			break;
		case '-':
			rz_histogram_interactive_zoom_out(hist);
			break;
		case 'q':
		case 'Q':
		case ' ':
			exit_histogram = true;
			break;
		default:
			break;
		}
		rz_cons_clear00();
	}
	rz_cons_break_pop();
	core->cons->event_resize = NULL;
	core->cons->event_data = NULL;
	core->keep_asmqjmps = false;
	rz_config_hold_restore(hc);
	rz_config_hold_free(hc);
	rz_histogram_interactive_free(hist);
	rz_cons_show_cursor(true);
	rz_cons_enable_mouse(false);

	return RZ_CMD_STATUS_OK;
}

static CoreBlockRange *parse_args_calculate_range(RzCore *core, int argc, const char **argv) {
	int nblocks = argc > 1 ? rz_num_math(core->num, argv[1]) : -1;
	ut64 totalsize = argc > 2 ? rz_num_math(core->num, argv[2]) : UT64_MAX;
	int skipblocks = argc > 3 ? rz_num_math(core->num, argv[3]) : -1;
	CoreBlockRange *brange = calculate_blocks_range(core, 0, 0, totalsize, nblocks, skipblocks);
	if (!brange) {
		RZ_LOG_ERROR("Cannot calculate blocks range\n");
	}
	return brange;
}

static RzCmdStatus print_histogram_bytes(RzCore *core, int argc, const char **argv, bool vertical, bool isinteractive) {
	CoreBlockRange *brange = parse_args_calculate_range(core, argc, argv);
	if (!brange) {
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *data = calloc(1, brange->nblocks);
	// Sample one byte at the start of each block (not nblocks contiguous bytes
	// from core->offset, which for blocksize > 1 rendered only the file's first
	// bytes; #4431). Byte-by-byte keeps huge/sparse files cheap.
	for (size_t i = 0; i < brange->nblocks; i++) {
		ut64 off = brange->from + (ut64)i * brange->blocksize;
		if (!rz_io_read_at_mapped(core->io, off, &data[i], 1)) {
			// Render an unreadable block as a zero sample.
			data[i] = 0;
		}
	}
	if (isinteractive) {
		RzHistogramOptions *opts = default_visual_opts(core, brange->from);
		if (!opts) {
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
		opts->value_max = 255;
		if (print_visual_bytes(core, opts, data, brange) != RZ_CMD_STATUS_OK) {
			RZ_LOG_ERROR("Cannot generate interactive histogram\n");
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
	} else {
		RzHistogramOptions opts = default_histogram_opts(core->config, &core->cons->context->pal, brange->from);
		opts.value_max = 255;
		if (!print_histogram(core, &opts, data, brange->nblocks, brange->blocksize, vertical)) {
			RZ_LOG_ERROR("Cannot generate %s histogram\n", vertical ? "vertical" : "horizontal");
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
	}
	free(brange);
	free(data);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_equal_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_bytes(core, argc, argv, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_bytes(core, argc, argv, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_bytes(core, argc, argv, false, true);
}

typedef double (*RzBlockMetricFn)(const ut8 *data, ut64 len);

/**
 * \brief Configuration for the generic per-block scalar histogram (p= / p==).
 */
typedef struct {
	RzBlockMetricFn fn; ///< Per-block metric (e.g. rz_hash_entropy, rz_hash_chisquare).
	double range_min; ///< Lower bound of the display range (ignored when autoscale is set).
	double range_max; ///< Upper bound of the display range (ignored when autoscale is set).
	bool autoscale; ///< Derive the upper bound from the largest value currently in view.
	int precision; ///< Number of decimals shown on the ruler labels.
} RzBlockMetricSpec;

// Generic per-block histogram for a scalar byte statistic (entropy, chi-square, index
// of coincidence, min-entropy, serial correlation, ...). The per-block value and its
// display range come from spec: bounded metrics use the fixed [range_min, range_max];
// unbounded ones (autoscale) rescale to the largest value currently in view so the
// bars stay meaningful regardless of block size.
static RzCmdStatus print_histogram_metric(RzCore *core, int argc, const char **argv, bool vertical, bool isinteractive, const RzBlockMetricSpec *spec) {
	CoreBlockRange *brange = parse_args_calculate_range(core, argc, argv);
	if (!brange) {
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *data = calloc(1, brange->nblocks);
	// fp metric values feed the static horizontal ruler via opts->data_f; the
	// ut8 copy keeps the visual / vertical paths working.
	double *fdata = RZ_NEWS0(double, brange->nblocks);
	ut8 *tmp = malloc(brange->blocksize);
	if (!tmp || !fdata || !data) {
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		free(tmp);
		free(fdata);
		free(data);
		free(brange);
		return RZ_CMD_STATUS_ERROR;
	}
	for (size_t i = 0; i < brange->nblocks; i++) {
		ut64 off = brange->from + (brange->blocksize * (i + brange->skipblocks));
		rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize);
		fdata[i] = spec->fn(tmp, brange->blocksize);
	}
	free(tmp);
	double vmin = spec->range_min;
	double vmax = spec->range_max;
	if (spec->autoscale) {
		vmin = 0.0;
		vmax = 0.0;
		for (size_t i = 0; i < brange->nblocks; i++) {
			if (fdata[i] > vmax) {
				vmax = fdata[i];
			}
		}
		if (vmax <= 0.0) {
			vmax = 1.0;
		}
	}
	double span = vmax - vmin;
	if (span <= 0.0) {
		span = 1.0;
	}
	for (size_t i = 0; i < brange->nblocks; i++) {
		double q = (fdata[i] - vmin) / span;
		if (q < 0.0) {
			q = 0.0;
		} else if (q > 1.0) {
			q = 1.0;
		}
		data[i] = (ut8)(255 * q);
	}
	// Integer ruler bounds, computed without libm: floor(vmin) and ceil(vmax).
	int imin = (int)vmin;
	if ((double)imin > vmin) {
		imin--;
	}
	int imax = (int)vmax;
	if ((double)imax < vmax) {
		imax++;
	}
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	if (isinteractive) {
		RzHistogramOptions *opts = default_visual_opts(core, brange->from);
		if (!opts) {
			free(brange);
			free(fdata);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
		opts->value_max = 8;
		opts->value_precision = 1;
		opts->data_f = fdata; // visual fp path uses Shannon directly
		if (print_visual_bytes(core, opts, data, brange) != RZ_CMD_STATUS_OK) {
			RZ_LOG_ERROR("Cannot generate interactive histogram\n");
			res = RZ_CMD_STATUS_ERROR;
		}
	} else {
		RzHistogramOptions opts = default_histogram_opts(core->config, &core->cons->context->pal, brange->from);
		opts.value_min = imin;
		opts.value_max = imax;
		opts.value_precision = spec->precision;
		// The static horizontal path can show full-precision fp values.
		if (!vertical) {
			opts.data_f = fdata;
		}
		if (!print_histogram(core, &opts, data, brange->nblocks, brange->blocksize, vertical)) {
			RZ_LOG_ERROR("Cannot generate %s histogram\n", vertical ? "vertical" : "horizontal");
			res = RZ_CMD_STATUS_ERROR;
		}
	}
	free(data);
	free(fdata);
	free(brange);
	return res;
}

static RzCmdStatus print_histogram_entropy(RzCore *core, int argc, const char **argv, bool vertical, bool isinteractive) {
	// Shannon entropy in bits-per-byte (0..8 for ut8 data).
	static const RzBlockMetricSpec spec = { rz_hash_entropy, 0.0, 8.0, false, 1 };
	return print_histogram_metric(core, argc, argv, vertical, isinteractive, &spec);
}

static bool print_rising_and_falling_entropy_table(RzCore *core, RzCmdStateOutput *state, CoreBlockRange *brange, ut8 *tmp, double fallingthreshold, double risingthreshold) {
	bool resetFlag = 1;
	st8 lastEdge = 0;
	RzTable *t = state->d.t;
	rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "addr");
	rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "index");
	rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_STRING, "edge_type");
	rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "entropy_value");
	for (int i = 0; i < brange->nblocks; i++) {
		ut64 off = brange->from + (brange->blocksize * (i));
		if (!rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize))
			return false;
		double data = rz_hash_entropy_fraction(tmp, brange->blocksize);
		// reseting flag if goes above falling threshold and below rising threshold
		if (resetFlag == 0 && lastEdge == 0 && data > fallingthreshold) {
			resetFlag = 1;
		} else if (resetFlag == 0 && lastEdge == 1 && data < risingthreshold) {
			resetFlag = 1;
		}
		// if reset flag is true
		// than if entopy goes above threshold printing rising entropy edge
		// and if entropy goes below threshold printing falling entropy edge
		if (resetFlag == 1 && data >= risingthreshold) {
			// rising edge print
			resetFlag = 0;
			lastEdge = 1;
			rz_table_add_rowf(t, "xnsf", off, i, "rising entropy edge", data);
		} else if (resetFlag == 1 && data <= fallingthreshold) {
			// falling edge print
			resetFlag = 0;
			lastEdge = 0;
			rz_table_add_rowf(t, "xnsf", off, i, "falling entropy edge", data);
		}
	}
	return true;
}

static bool print_rising_and_falling_entropy_JSON(RzCore *core, RzCmdStateOutput *state, CoreBlockRange *brange, ut8 *tmp, double fallingthreshold, double risingthreshold) {
	bool resetFlag = 1;
	st8 lastEdge = 0;
	PJ *pj = state->d.pj;
	pj_a(pj);
	for (int i = 0; i < brange->nblocks; i++) {
		ut64 off = brange->from + (brange->blocksize * (i));
		if (!rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize))
			return false;
		double data = rz_hash_entropy_fraction(tmp, brange->blocksize);
		// reseting flag if goes above falling threshold and below rising threshold
		if (resetFlag == 0 && lastEdge == 0 && data > fallingthreshold) {
			resetFlag = 1;
		} else if (resetFlag == 0 && lastEdge == 1 && data < risingthreshold) {
			resetFlag = 1;
		}
		// if reset flag is true
		// than if entopy goes above threshold printing rising entropy edge
		// and if entropy goes below threshold printing falling entropy edge
		if (resetFlag == 1 && data >= risingthreshold) {
			// rising edge print
			resetFlag = 0;
			lastEdge = 1;
			pj_o(pj);
			pj_kn(pj, "addr", off);
			pj_kn(pj, "index", i);
			pj_ks(pj, "edge_type", "rising entropy edge");
			pj_kd(pj, "entropy_value", data);
			pj_end(pj);
		} else if (resetFlag == 1 && data <= fallingthreshold) {
			// falling edge print
			resetFlag = 0;
			lastEdge = 0;
			pj_o(pj);
			pj_kn(pj, "addr", off);
			pj_kn(pj, "index", i);
			pj_ks(pj, "edge_type", "falling entropy edge");
			pj_kd(pj, "entropy_value", data);
			pj_end(pj);
		}
	}
	pj_end(pj);
	return true;
}

static bool print_rising_and_falling_entropy_quiet(RzCore *core, CoreBlockRange *brange, ut8 *tmp, double fallingthreshold, double risingthreshold) {
	RzStrBuf *buf = rz_strbuf_new("");
	if (!buf) {
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		return false;
	}
	bool resetFlag = 1;
	st8 lastEdge = 0;
	for (int i = 0; i < brange->nblocks; i++) {
		ut64 off = brange->from + (brange->blocksize * (i));
		if (!rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize)) {
			rz_strbuf_free(buf);
			return false;
		}
		double data = rz_hash_entropy_fraction(tmp, brange->blocksize);
		// reseting flag if goes above falling threshold and below rising threshold
		if (resetFlag == 0 && lastEdge == 0 && data > fallingthreshold) {
			resetFlag = 1;
		} else if (resetFlag == 0 && lastEdge == 1 && data < risingthreshold) {
			resetFlag = 1;
		}
		// if reset flag is true
		// than if entopy goes above threshold printing rising entropy edge
		// and if entropy goes below threshold printing falling entropy edge
		if (resetFlag == 1 && data >= risingthreshold) {
			// rising edge print
			resetFlag = 0;
			lastEdge = 1;
			rz_strbuf_appendf(buf, "0x%08" PFMT64x "\n", off);
		} else if (resetFlag == 1 && data <= fallingthreshold) {
			// falling edge print
			resetFlag = 0;
			lastEdge = 0;
			rz_strbuf_appendf(buf, "0x%08" PFMT64x "\n", off);
		}
	}
	char *res = rz_strbuf_drain(buf);
	rz_cons_print(res);
	free(res);
	return true;
}

static bool print_rising_and_falling_entropy_standard(RzCore *core, CoreBlockRange *brange, ut8 *tmp, double fallingthreshold, double risingthreshold) {
	RzStrBuf *buf = rz_strbuf_new("");
	if (!buf) {
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		return false;
	}
	bool resetFlag = 1;
	st8 lastEdge = 0;
	for (int i = 0; i < brange->nblocks; i++) {
		ut64 off = brange->from + (brange->blocksize * (i));
		if (!rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize)) {
			rz_strbuf_free(buf);
			return false;
		}
		double data = rz_hash_entropy_fraction(tmp, brange->blocksize);
		// reseting flag if goes above falling threshold and below rising threshold
		if (resetFlag == 0 && lastEdge == 0 && data > fallingthreshold) {
			resetFlag = 1;
		} else if (resetFlag == 0 && lastEdge == 1 && data < risingthreshold) {
			resetFlag = 1;
		}
		// if reset flag is true
		// than if entopy goes above threshold printing rising entropy edge
		// and if entropy goes below threshold printing falling entropy edge
		if (resetFlag == 1 && data >= risingthreshold) {
			// rising edge print
			resetFlag = 0;
			lastEdge = 1;
			rz_strbuf_appendf(buf, "0x%08" PFMT64x " Rising entropy edge\n", off);
		} else if (resetFlag == 1 && data <= fallingthreshold) {
			// falling edge print
			resetFlag = 0;
			lastEdge = 0;
			rz_strbuf_appendf(buf, "0x%08" PFMT64x " Falling entropy edge\n", off);
		}
	}
	char *res = rz_strbuf_drain(buf);
	rz_cons_print(res);
	free(res);
	return true;
}

static bool print_rising_and_falling_entropy_long(RzCore *core, CoreBlockRange *brange, ut8 *tmp, double fallingthreshold, double risingthreshold) {
	RzStrBuf *buf = rz_strbuf_new("");
	if (!buf) {
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		return false;
	}
	bool resetFlag = 1;
	st8 lastEdge = 0;
	for (int i = 0; i < brange->nblocks; i++) {
		ut64 off = brange->from + (brange->blocksize * (i));
		if (!rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize)) {
			rz_strbuf_free(buf);
			return false;
		}
		double data = rz_hash_entropy_fraction(tmp, brange->blocksize);
		// reseting flag if goes above falling threshold and below rising threshold
		if (resetFlag == 0 && lastEdge == 0 && data > fallingthreshold) {
			resetFlag = 1;
		} else if (resetFlag == 0 && lastEdge == 1 && data < risingthreshold) {
			resetFlag = 1;
		}
		// if reset flag is true
		// than if entopy goes above threshold printing rising entropy edge
		// and if entropy goes below threshold printing falling entropy edge
		if (resetFlag == 1 && data >= risingthreshold) {
			// rising edge print
			resetFlag = 0;
			lastEdge = 1;
			rz_strbuf_appendf(buf, "0x%08" PFMT64x " ", off);
			rz_strbuf_appendf(buf, "%03x Rising entropy edge (%8lf)\n", i, data);
		} else if (resetFlag == 1 && data <= fallingthreshold) {
			// falling edge print
			resetFlag = 0;
			lastEdge = 0;
			rz_strbuf_appendf(buf, "0x%08" PFMT64x " ", off);
			rz_strbuf_appendf(buf, "%03x Falling entropy edge (%8lf)\n", i, data);
		}
	}
	char *res = rz_strbuf_drain(buf);
	rz_cons_print(res);
	free(res);
	return true;
}

static RzCmdStatus print_rising_and_falling_entropy(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	double risingthreshold = 0.95;
	double fallingthreshold = 0.85;
	if (argc >= 3) {
		risingthreshold = rz_num_get_float(core->num, argv[1]);
		fallingthreshold = rz_num_get_float(core->num, argv[2]);
	}
	if (fallingthreshold > risingthreshold) {
		RZ_LOG_ERROR("falling threshold is greater than rising threshold\n");
		return RZ_CMD_STATUS_ERROR;
	}
	if (risingthreshold > 1) {
		RZ_LOG_ERROR("threshold can't be greater than 1\n");
		return RZ_CMD_STATUS_ERROR;
	}
	int nblocks = -1;
	ut64 totalsize = UT64_MAX;
	int skipblocks = -1;
	CoreBlockRange *brange = calculate_blocks_range(core, 0, 0, totalsize, nblocks, skipblocks);
	if (!brange) {
		RZ_LOG_ERROR("Cannot calculate blocks range\n");
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *tmp = malloc(brange->blocksize);
	if (!tmp) {
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		free(brange);
		return RZ_CMD_STATUS_ERROR;
	}
	switch (state->mode) {
	case RZ_OUTPUT_MODE_TABLE:
		if (!print_rising_and_falling_entropy_table(core, state, brange, tmp, fallingthreshold, risingthreshold)) {
			free(tmp);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
		break;
	case RZ_OUTPUT_MODE_JSON:
		if (!print_rising_and_falling_entropy_JSON(core, state, brange, tmp, fallingthreshold, risingthreshold)) {
			free(tmp);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
		break;
	case RZ_OUTPUT_MODE_QUIET:
		if (!print_rising_and_falling_entropy_quiet(core, brange, tmp, fallingthreshold, risingthreshold)) {
			free(tmp);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
		break;
	case RZ_OUTPUT_MODE_STANDARD:
		if (!print_rising_and_falling_entropy_standard(core, brange, tmp, fallingthreshold, risingthreshold)) {
			free(tmp);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
		break;
	case RZ_OUTPUT_MODE_LONG:
		if (!print_rising_and_falling_entropy_long(core, brange, tmp, fallingthreshold, risingthreshold)) {
			free(tmp);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
		break;
	default:
		rz_warn_if_reached();
		free(tmp);
		free(brange);
		return RZ_CMD_STATUS_ERROR;
	}
	free(tmp);
	free(brange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_equal_entropy_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_entropy(core, argc, argv, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_entropy_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_entropy(core, argc, argv, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_entropy_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_entropy(core, argc, argv, false, true);
}

static const RzBlockMetricSpec metric_chisquare = { rz_hash_chisquare, 0.0, 0.0, true, 1 };

RZ_IPI RzCmdStatus rz_print_equal_chisquare_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, true, false, &metric_chisquare);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_chisquare_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, false, false, &metric_chisquare);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_chisquare_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, false, true, &metric_chisquare);
}

static const RzBlockMetricSpec metric_ioc = { rz_hash_ioc, 0.0, 1.0, false, 4 };

RZ_IPI RzCmdStatus rz_print_equal_ioc_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, true, false, &metric_ioc);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_ioc_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, false, false, &metric_ioc);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_ioc_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, false, true, &metric_ioc);
}

static const RzBlockMetricSpec metric_minentropy = { rz_hash_min_entropy, 0.0, 8.0, false, 2 };

RZ_IPI RzCmdStatus rz_print_equal_minentropy_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, true, false, &metric_minentropy);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_minentropy_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, false, false, &metric_minentropy);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_minentropy_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, false, true, &metric_minentropy);
}

static const RzBlockMetricSpec metric_serialcorr = { rz_hash_serial_correlation, -1.0, 1.0, false, 2 };

RZ_IPI RzCmdStatus rz_print_equal_serialcorr_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, true, false, &metric_serialcorr);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_serialcorr_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, false, false, &metric_serialcorr);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_serialcorr_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_metric(core, argc, argv, false, true, &metric_serialcorr);
}

RZ_IPI RzCmdStatus rz_print_rising_and_falling_entropy_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return print_rising_and_falling_entropy(core, argc, argv, state);
}

static RzCmdStatus print_histogram_marks(RzCore *core, int argc, const char **argv, bool vertical, bool isinteractive) {
	CoreBlockRange *brange = parse_args_calculate_range(core, argc, argv);
	if (!brange) {
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *data = calloc(1, brange->nblocks);
	ut8 *tmp = malloc(brange->blocksize);
	if (!tmp) {
		free(data);
		free(brange);
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (size_t i = 0; i < brange->nblocks; i++) {
		ut64 off = brange->from + (brange->blocksize * (i + brange->skipblocks));
		for (size_t j = 0; j < brange->blocksize; j++) {
			if (rz_flag_get_at(core->flags, off + j, false)) {
				data_array_increment_element(data, i);
			}
		}
	}
	free(tmp);
	if (isinteractive) {
		RzHistogramOptions *opts = default_visual_opts(core, brange->from);
		if (!opts) {
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
		if (print_visual_bytes(core, opts, data, brange) != RZ_CMD_STATUS_OK) {
			RZ_LOG_ERROR("Cannot generate interactive histogram\n");
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
	} else {
		RzHistogramOptions opts = default_histogram_opts(core->config, &core->cons->context->pal, brange->from);
		opts.value_max = 255;
		if (!print_histogram(core, &opts, data, brange->nblocks, brange->blocksize, vertical)) {
			RZ_LOG_ERROR("Cannot generate %s histogram\n", vertical ? "vertical" : "horizontal");
			free(data);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
	}
	free(data);
	free(brange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_equal_m_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_marks(core, argc, argv, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_m_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_marks(core, argc, argv, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_m_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_marks(core, argc, argv, false, true);
}

static RzCmdStatus print_histogram_0x00(RzCore *core, int argc, const char **argv, bool vertical, bool isinteractive) {
	CoreBlockRange *brange = parse_args_calculate_range(core, argc, argv);
	if (!brange) {
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *data = calloc(1, brange->nblocks);
	ut8 *tmp = malloc(brange->blocksize);
	if (!tmp) {
		free(data);
		free(brange);
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (size_t i = 0; i < brange->nblocks; i++) {
		int k = 0;
		ut64 off = brange->from + (brange->blocksize * (i + brange->skipblocks));
		rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize);
		for (size_t j = k = 0; j < brange->blocksize; j++) {
			if (!tmp[j]) {
				k++;
			}
		}
		data[i] = 256 * k / brange->blocksize;
	}
	free(tmp);
	if (isinteractive) {
		RzHistogramOptions *opts = default_visual_opts(core, brange->from);
		if (!opts) {
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
		if (print_visual_bytes(core, opts, data, brange) != RZ_CMD_STATUS_OK) {
			RZ_LOG_ERROR("Cannot generate interactive histogram\n");
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
	} else {
		RzHistogramOptions opts = default_histogram_opts(core->config, &core->cons->context->pal, brange->from);
		opts.value_max = 100;
		opts.value_unit = "%";
		if (!print_histogram(core, &opts, data, brange->nblocks, brange->blocksize, vertical)) {
			RZ_LOG_ERROR("Cannot generate %s histogram\n", vertical ? "vertical" : "horizontal");
			free(data);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
	}
	free(data);
	free(brange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_equal_0x00_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_0x00(core, argc, argv, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_0x00_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_0x00(core, argc, argv, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_0x00_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_0x00(core, argc, argv, false, true);
}

static RzCmdStatus print_histogram_0xff(RzCore *core, int argc, const char **argv, bool vertical, bool isinteractive) {
	CoreBlockRange *brange = parse_args_calculate_range(core, argc, argv);
	if (!brange) {
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *data = calloc(1, brange->nblocks);
	ut8 *tmp = malloc(brange->blocksize);
	if (!tmp) {
		free(data);
		free(brange);
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (size_t i = 0; i < brange->nblocks; i++) {
		int k = 0;
		ut64 off = brange->from + (brange->blocksize * (i + brange->skipblocks));
		rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize);
		for (size_t j = k = 0; j < brange->blocksize; j++) {
			if (tmp[j] == 0xff) {
				k++;
			}
		}
		data[i] = 256 * k / brange->blocksize;
	}
	free(tmp);
	if (isinteractive) {
		RzHistogramOptions *opts = default_visual_opts(core, brange->from);
		if (!opts) {
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
		if (print_visual_bytes(core, opts, data, brange) != RZ_CMD_STATUS_OK) {
			RZ_LOG_ERROR("Cannot generate interactive histogram\n");
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
	} else {
		RzHistogramOptions opts = default_histogram_opts(core->config, &core->cons->context->pal, brange->from);
		opts.value_max = 100;
		opts.value_unit = "%";
		if (!print_histogram(core, &opts, data, brange->nblocks, brange->blocksize, vertical)) {
			RZ_LOG_ERROR("Cannot generate %s histogram\n", vertical ? "vertical" : "horizontal");
			free(data);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
	}
	free(data);
	free(brange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_equal_0xff_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_0xff(core, argc, argv, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_0xff_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_0xff(core, argc, argv, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_0xff_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_0xff(core, argc, argv, false, true);
}

static RzCmdStatus print_histogram_printable(RzCore *core, int argc, const char **argv, bool vertical, bool isinteractive) {
	CoreBlockRange *brange = parse_args_calculate_range(core, argc, argv);
	if (!brange) {
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *data = calloc(1, brange->nblocks);
	ut8 *tmp = malloc(brange->blocksize);
	if (!tmp) {
		free(data);
		free(brange);
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		return RZ_CMD_STATUS_ERROR;
	}
	for (size_t i = 0; i < brange->nblocks; i++) {
		int k = 0;
		ut64 off = brange->from + (brange->blocksize * (i + brange->skipblocks));
		rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize);
		for (size_t j = k = 0; j < brange->blocksize; j++) {
			if (IS_PRINTABLE(tmp[j])) {
				k++;
			}
		}
		data[i] = 256 * k / brange->blocksize;
	}
	free(tmp);
	if (isinteractive) {
		RzHistogramOptions *opts = default_visual_opts(core, brange->from);
		if (!opts) {
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
		if (print_visual_bytes(core, opts, data, brange) != RZ_CMD_STATUS_OK) {
			RZ_LOG_ERROR("Cannot generate interactive histogram\n");
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
	} else {
		RzHistogramOptions opts = default_histogram_opts(core->config, &core->cons->context->pal, brange->from);
		opts.value_max = 100;
		opts.value_unit = "%";
		if (!print_histogram(core, &opts, data, brange->nblocks, brange->blocksize, vertical)) {
			RZ_LOG_ERROR("Cannot generate %s histogram\n", vertical ? "vertical" : "horizontal");
			free(data);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
	}
	free(data);
	free(brange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_equal_printable_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_printable(core, argc, argv, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_printable_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_printable(core, argc, argv, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_printable_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_printable(core, argc, argv, false, true);
}

static RzCmdStatus print_histogram_z(RzCore *core, int argc, const char **argv, bool vertical, bool isinteractive) {
	CoreBlockRange *brange = parse_args_calculate_range(core, argc, argv);
	if (!brange) {
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *data = calloc(1, brange->nblocks);
	ut8 *tmp = malloc(brange->blocksize);
	if (!tmp) {
		free(data);
		free(brange);
		RZ_LOG_ERROR("core: failed to malloc memory\n");
		return RZ_CMD_STATUS_ERROR;
	}
	size_t len = 0;
	for (size_t i = 0; i < brange->nblocks; i++) {
		int k = 0;
		ut64 off = brange->from + (brange->blocksize * (i + brange->skipblocks));
		rz_io_read_at_mapped(core->io, off, tmp, brange->blocksize);
		for (size_t j = k = 0; j < brange->blocksize; j++) {
			if (IS_PRINTABLE(tmp[j])) {
				if ((j + 1) < brange->blocksize && tmp[j + 1] == 0) {
					k++;
					j++;
				}
				if (len++ > 8) {
					k++;
				}
			} else {
				len = 0;
			}
		}
		data[i] = 256 * k / brange->blocksize;
	}
	free(tmp);
	if (isinteractive) {
		RzHistogramOptions *opts = default_visual_opts(core, brange->from);
		if (!opts) {
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
		if (print_visual_bytes(core, opts, data, brange) != RZ_CMD_STATUS_OK) {
			RZ_LOG_ERROR("Cannot generate interactive histogram\n");
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
	} else {
		RzHistogramOptions opts = default_histogram_opts(core->config, &core->cons->context->pal, brange->from);
		opts.value_max = 100;
		opts.value_unit = "%";
		if (!print_histogram(core, &opts, data, brange->nblocks, brange->blocksize, vertical)) {
			free(data);
			free(brange);
			RZ_LOG_ERROR("Cannot generate %s histogram\n", vertical ? "vertical" : "horizontal");
			return RZ_CMD_STATUS_ERROR;
		}
	}
	free(data);
	free(brange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_equal_z_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_z(core, argc, argv, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_z_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_z(core, argc, argv, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_z_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_z(core, argc, argv, false, true);
}

static RzCmdStatus print_histogram_stats(RzCore *core, int argc, const char **argv, bool vertical, bool isinteractive) {
	CoreBlockRange *brange = parse_args_calculate_range(core, argc, argv);
	if (!brange) {
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *data = analysis_stats_histogram_data(core, brange);
	if (!data) {
		free(brange);
		RZ_LOG_ERROR("core: failed to access analysis stats");
		return RZ_CMD_STATUS_ERROR;
	}
	if (isinteractive) {
		RzHistogramOptions *opts = default_visual_opts(core, brange->from);
		if (!opts) {
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
		if (print_visual_bytes(core, opts, data, brange) != RZ_CMD_STATUS_OK) {
			RZ_LOG_ERROR("Cannot generate interactive histogram\n");
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
	} else {
		RzHistogramOptions opts = default_histogram_opts(core->config, &core->cons->context->pal, brange->from);
		opts.value_max = 255;
		if (!print_histogram(core, &opts, data, brange->nblocks, brange->blocksize, vertical)) {
			RZ_LOG_ERROR("Cannot generate %s histogram\n", vertical ? "vertical" : "horizontal");
			free(data);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
	}
	free(data);
	free(brange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_equal_stats_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_stats(core, argc, argv, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_stats_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_stats(core, argc, argv, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_stats_visual_handler(RzCore *core, int argc, const char **argv) {
	return print_histogram_stats(core, argc, argv, false, true);
}

static RzCmdStatus analysis_hist_handler(RzCore *core, int argc, const char **argv, CoreAnalysisHistogramType hist_type, bool vertical, bool isinteractive) {
	CoreBlockRange *brange = parse_args_calculate_range(core, argc, argv);
	if (!brange) {
		return RZ_CMD_STATUS_ERROR;
	}
	ut8 *data = analysis_histogram_data(core, brange, hist_type);
	if (!data) {
		free(brange);
		RZ_LOG_ERROR("core: failed to access analyzed instructions for specified range");
		return RZ_CMD_STATUS_ERROR;
	}
	if (isinteractive) {
		RzHistogramOptions *opts = default_visual_opts(core, brange->from);
		if (!opts) {
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
		if (print_visual_bytes(core, opts, data, brange) != RZ_CMD_STATUS_OK) {
			RZ_LOG_ERROR("Cannot generate interactive histogram\n");
			free(brange);
			free(data);
			return RZ_CMD_STATUS_ERROR;
		}
	} else {
		RzHistogramOptions opts = default_histogram_opts(core->config, &core->cons->context->pal, brange->from);
		opts.value_max = 255;
		if (!print_histogram(core, &opts, data, brange->nblocks, brange->blocksize, vertical)) {
			RZ_LOG_ERROR("Cannot generate %s histogram\n", vertical ? "vertical" : "horizontal");
			free(data);
			free(brange);
			return RZ_CMD_STATUS_ERROR;
		}
	}
	free(data);
	free(brange);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_print_equal_bbs_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_BASIC_BLOCKS, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_call_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_CALL_INSTRUCTIONS, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_jump_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_JUMP_INSTRUCTIONS, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_priv_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_PRIV_INSTRUCTIONS, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_invalid_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_INVALID_INSTRUCTIONS, true, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_bbs_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_BASIC_BLOCKS, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_call_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_CALL_INSTRUCTIONS, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_jump_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_JUMP_INSTRUCTIONS, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_priv_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_PRIV_INSTRUCTIONS, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_invalid_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_INVALID_INSTRUCTIONS, false, false);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_bbs_visual_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_BASIC_BLOCKS, false, true);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_call_visual_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_CALL_INSTRUCTIONS, false, true);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_jump_visual_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_JUMP_INSTRUCTIONS, false, true);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_priv_visual_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_PRIV_INSTRUCTIONS, false, true);
}

RZ_IPI RzCmdStatus rz_print_equal_equal_invalid_visual_handler(RzCore *core, int argc, const char **argv) {
	return analysis_hist_handler(core, argc, argv, HISTOGRAM_ANALYSIS_INVALID_INSTRUCTIONS, false, true);
}

RZ_IPI RzCmdStatus rz_print_equal_two_handler(RzCore *core, int argc, const char **argv) {
	short *word = (short *)core->block;
	size_t i, words = core->blocksize / 2;
	int step = rz_num_math(core->num, argv[1]);
	ut64 oldword = 0;
	for (i = 0; i < words; i++) {
		ut64 word64 = word[i] + ST16_MAX;
		rz_cons_printf("0x%08" PFMT64x " %8d  ", core->offset + (i * 2), word[i]);
		RzBarOptions baropts = {
			.unicode = rz_config_get_b(core->config, "scr.utf8"),
			.thinline = !rz_config_get_b(core->config, "scr.hist.block"),
			.legend = false,
			.offset = rz_config_get_b(core->config, "hex.offset"),
			.offpos = 0,
			.cursor = false,
			.curpos = 0,
			.color = rz_config_get_i(core->config, "scr.color")
		};
		RzStrBuf *strbuf = rz_progressbar(&baropts, word64 * 100 / UT16_MAX, 60);
		if (!strbuf) {
			RZ_LOG_ERROR("Cannot generate vertical histogram\n");
		} else {
			char *bar = rz_strbuf_drain(strbuf);
			rz_cons_print(bar);
			free(bar);
		}
		rz_cons_printf(" %" PFMT64d, word64 - oldword);
		oldword = word64;
		rz_cons_newline();
		i += step;
	}
	return RZ_CMD_STATUS_OK;
}

static void printraw(RzCore *core, int len, bool stop_at_null) {
	ut8 *data = malloc(len);
	if (!data) {
		return;
	}
	if (rz_io_read_at_mapped(core->io, core->offset, data, len)) {
		if (stop_at_null) {
			len = rz_str_nlen((const char *)data, len);
		}
		rz_print_raw(core->print, core->offset, data, len);
	}
	free(data);
	core->cons->newline = core->cmd_in_backticks ? false : true;
}

RZ_IPI RzCmdStatus rz_cmd_print_raw_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 0) {
		return RZ_CMD_STATUS_ERROR;
	}
	printraw(core, len, false);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_raw_colors_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 0) {
		return RZ_CMD_STATUS_ERROR;
	}
	colordump(core, core->block, len);
	return RZ_CMD_STATUS_OK;
}

static bool gunzip_and_print_block(RzCore *core, bool verbose) {
	int outlen = 0;
	int inConsumed = 0;
	ut8 *out;
	out = rz_inflate(core->block, core->blocksize, &inConsumed, &outlen);
	if (!out) {
		return false;
	}
	if (verbose) {
		rz_cons_printf("consumed: %d produced: %d\n", inConsumed, outlen);
	}
	rz_cons_memcat((const char *)out, outlen);
	free(out);
	return true;
}

RZ_IPI RzCmdStatus rz_cmd_print_raw_gunzip_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(gunzip_and_print_block(core, false));
}

RZ_IPI RzCmdStatus rz_cmd_print_raw_gunzip_verbose_handler(RzCore *core, int argc, const char **argv) {
	return bool2status(gunzip_and_print_block(core, true));
}

RZ_IPI RzCmdStatus rz_cmd_print_raw_printable_handler(RzCore *core, int argc, const char **argv) {
	int a = rz_config_get_i(core->config, "hex.bytes");
	rz_config_set_i(core->config, "hex.bytes", false);
	// TODO: Use the API instead of commands
	rz_core_cmd0(core, "pxx");
	rz_config_set_i(core->config, "hex.bytes", a);
	return RZ_CMD_STATUS_OK;
}

static RzCmdStatus print_format(RzCore *core, const char *fmt, int mode, RzCmdStateOutput *state) {
	rz_return_val_if_fail(fmt, RZ_CMD_STATUS_ERROR);

	if (state) {
		switch (state->mode) {
		case RZ_OUTPUT_MODE_JSON:
			mode = RZ_PRINT_JSON;
			break;
		case RZ_OUTPUT_MODE_QUIET:
			mode |= RZ_PRINT_QUIET | RZ_PRINT_MUSTSEE;
			break;
		default:
			break;
		}
		// Temporary workaround until `librz/type/format.c` is rewritten properly
		if (state->mode != RZ_OUTPUT_MODE_JSON) {
			rz_cmd_state_output_array_start(state);
		}
	}
	char *format = rz_core_print_format(core, fmt, mode, core->offset);
	if (!format) {
		return RZ_CMD_STATUS_ERROR;
	}
	if (state) {
		// Temporary workaround until `librz/type/format.c` is rewritten properly
		if (state->mode != RZ_OUTPUT_MODE_JSON) {
			rz_cmd_state_output_array_end(state);
		}
	}
	rz_cons_print(format);
	free(format);
	return RZ_CMD_STATUS_OK;
}

static RzCmdStatus print_format_write(RzCore *core, const char *fmt, const char *value) {
	rz_return_val_if_fail(fmt, RZ_CMD_STATUS_ERROR);
	char *format = rz_core_print_format_write(core, fmt, value, core->offset);
	if (!format) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_print(format);
	free(format);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_raw_string_handler(RzCore *core, int argc, const char **argv) {
	int len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len < 0) {
		return RZ_CMD_STATUS_ERROR;
	}
	printraw(core, len, true);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_format_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int mode = RZ_PRINT_MUSTSEE;
	return print_format(core, argv[1], mode, state);
}

RZ_IPI RzCmdStatus rz_cmd_print_format_delete_handler(RzCore *core, int argc, const char **argv) {
	RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
	rz_type_db_format_delete(typedb, argv[1]);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_format_delete_all_handler(RzCore *core, int argc, const char **argv) {
	RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
	rz_type_db_format_purge(typedb);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_format_apply_handler(RzCore *core, int argc, const char **argv) {
	if (argc < 2) {
		return RZ_CMD_STATUS_WRONG_ARGS;
	}

	const char *typename = argv[1];
	ut64 addr = argc > 2 ? rz_num_math(core->num, argv[2]) : core->offset;

	RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
	const char *fmt = rz_type_db_format_get(typedb, typename);
	if (RZ_STR_ISEMPTY(fmt)) {
		RZ_LOG_ERROR("Format with \"%s\" name not found\n", typename);
		return RZ_CMD_STATUS_ERROR;
	}

	ut64 old_offset = core->offset;
	rz_core_seek(core, addr, true);

	RzCmdStatus status = print_format(core, fmt, RZ_PRINT_MUSTSEE, NULL);

	rz_core_seek(core, old_offset, true);
	return status;
}

RZ_IPI RzCmdStatus rz_cmd_print_format_named_dot_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	int mode = RZ_PRINT_MUSTSEE;
	/* pf. is the "show data using a NAMED format" command. The leading
	 * dot is the marker. If the argument doesn't resolve to a registered
	 * format (and isn't a sub-field reference like `name.field`), bail
	 * out with a clear error instead of falling through to raw-format
	 * parsing, which would emit a string of "unknown specifier" warnings
	 * that obscure the real problem. */
	if (argc < 2) {
		return print_format(core, "", mode, state);
	}
	RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
	const char *arg = argv[1];
	const char *dot = strchr(arg, '.');
	size_t name_len = dot ? (size_t)(dot - arg) : strlen(arg);
	char *name = rz_str_ndup(arg, name_len);
	if (!name) {
		return RZ_CMD_STATUS_ERROR;
	}
	const char *fmt = rz_type_db_format_get(typedb, name);
	if (RZ_STR_ISEMPTY(fmt)) {
		RZ_LOG_ERROR("pf.: no registered format named '%s'. "
			     "Run `pfn` to list known formats, or `pfo` to load a "
			     "format definition file (e.g. `pfo elf64` then "
			     "`pf. elf_header`).\n",
			name);
		free(name);
		return RZ_CMD_STATUS_ERROR;
	}
	free(name);
	return print_format(core, arg, mode, state);
}

static int compare_type_formats(const RzTypeFormat *a, const RzTypeFormat *b, RZ_UNUSED void *user) {
	return strcmp(a->name, b->name);
}

RZ_IPI RzCmdStatus rz_cmd_print_format_named_handler(RzCore *core, int argc, const char **argv) {
	RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
	if (argc < 2) {
		// List all named formats
		RzListIter *iter;
		RzTypeFormat *fmt = NULL;
		RzList *formats = rz_type_db_format_all(typedb);
		rz_list_sort(formats, (RzListComparator)compare_type_formats, NULL);
		rz_list_foreach (formats, iter, fmt) {
			rz_cons_printf("%s \"%s\"\n", fmt->name, fmt->body);
		}
		rz_list_free(formats);
		return RZ_CMD_STATUS_OK;
	} else if (argc < 3) {
		// Show the format string for a named format
		const char *fmt = rz_type_db_format_get(typedb, argv[1]);
		if (RZ_STR_ISEMPTY(fmt)) {
			RZ_LOG_ERROR("Format with \"%s\" name not found\n", argv[1]);
			return RZ_CMD_STATUS_ERROR;
		}
		rz_cons_printf("%s\n", fmt);
		return RZ_CMD_STATUS_OK;
	}
	rz_type_db_format_set(typedb, argv[1], argv[2]);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_format_c_handler(RzCore *core, int argc, const char **argv) {
	int mode = RZ_PRINT_STRUCT;
	return print_format(core, argv[1], mode, NULL);
}

RZ_IPI RzCmdStatus rz_cmd_print_format_dot_handler(RzCore *core, int argc, const char **argv) {
	int mode = RZ_PRINT_DOT;
	return print_format(core, argv[1], mode, NULL);
}

RZ_IPI RzCmdStatus rz_cmd_print_format_file_handler(RzCore *core, int argc, const char **argv) {
	if (argc < 2) {
		RzList *files;
		RzListIter *iter;
		const char *fn;
		/* De-duplicate file names that appear in both the home FDF dir
		 * and the system FDF dir (common when the same file is
		 * installed in both locations). */
		HtSU *seen = ht_su_new(HT_STR_DUP);
		char *home = rz_path_home_prefix(RZ_SDB_FORMAT);
		if (home) {
			files = rz_sys_dir(home);
			rz_list_foreach (files, iter, fn) {
				if (*fn && *fn != '.' && (!seen || !ht_su_find(seen, fn, NULL))) {
					rz_cons_println(fn);
					if (seen) {
						ht_su_insert(seen, fn, 1);
					}
				}
			}
			rz_list_free(files);
			free(home);
		}
		char *path = rz_path_system(core->sys_path, RZ_SDB_FORMAT);
		if (path) {
			files = rz_sys_dir(path);
			rz_list_foreach (files, iter, fn) {
				if (*fn && *fn != '.' && (!seen || !ht_su_find(seen, fn, NULL))) {
					rz_cons_println(fn);
					if (seen) {
						ht_su_insert(seen, fn, 1);
					}
				}
			}
			rz_list_free(files);
			free(path);
		}
		ht_su_free(seen);
		return RZ_CMD_STATUS_OK;
	}
	char *home_formats = rz_path_home_prefix(RZ_SDB_FORMAT);
	char *home = rz_file_path_join(home_formats, argv[1]);
	free(home_formats);
	char *system_formats = rz_path_system(core->sys_path, RZ_SDB_FORMAT);
	char *path = rz_file_path_join(system_formats, argv[1]);
	free(system_formats);
	if (rz_str_endswith(argv[1], ".h")) {
		char *error_msg = NULL;
		const char *dir = rz_config_get(core->config, "dir.types");
		RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
		int result = rz_type_parse_file(typedb, path, dir, &error_msg);
		if (!result) {
			// TODO: Use the API here
			rz_core_cmd0(core, ".ts*");
		} else {
			RZ_LOG_ERROR("core: Parse error: %s\n", error_msg);
			free(error_msg);
		}
	} else {
		if (!rz_core_cmd_file(core, home) && !rz_core_cmd_file(core, path)) {
			if (!rz_core_cmd_file(core, argv[1])) {
				RZ_LOG_ERROR("core: pfo: cannot open format file at '%s'\n", path);
			}
		}
	}
	free(home);
	free(path);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_format_size_handler(RzCore *core, int argc, const char **argv) {
	RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
	const char *format = rz_type_db_format_get(typedb, argv[1]);
	if (!format) {
		RZ_LOG_ERROR("Format \"%s\" could not be found\n", argv[1]);
		return RZ_CMD_STATUS_ERROR;
	}
	int fmtsz = rz_type_format_struct_size(typedb, format, RZ_PRINT_MUSTSEE, 0);
	rz_cons_printf("%d\n", fmtsz);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_format_value_handler(RzCore *core, int argc, const char **argv) {
	int mode = RZ_PRINT_VALUE | RZ_PRINT_MUSTSEE;
	return print_format(core, argv[1], mode, NULL);
}

RZ_IPI RzCmdStatus rz_cmd_print_format_write_handler(RzCore *core, int argc, const char **argv) {
	return print_format_write(core, argv[1], argv[2]);
}

RZ_IPI RzCmdStatus rz_cmd_print_2bpp_tiles_handler(RzCore *core, int argc, const char **argv) {
	size_t len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len == 0) {
		return RZ_CMD_STATUS_OK;
	}
	core_print_2bpp_tiles(core, len / 16);
	return RZ_CMD_STATUS_OK;
}

static RzCmdStatus print_8bit_hexpair(RzCore *core, ut64 addr, size_t len) {
	ut8 *buf = malloc(len);
	if (!buf) {
		RZ_LOG_ERROR("core: cannot allocate %zu byte(s)\n", len);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_io_read_at_mapped(core->io, addr, buf, len);
	rz_print_bytes(core->print, buf, len, "%02x");
	free(buf);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_8bit_hexpair_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	size_t len = argc > 1 ? rz_num_math(core->num, argv[1]) : core->blocksize;
	if (len == 0) {
		return RZ_CMD_STATUS_OK;
	}
	if (state->mode == RZ_OUTPUT_MODE_STANDARD) {
		return print_8bit_hexpair(core, core->offset, len);
	}
	char *code = rz_lang_byte_array(core->block, len, RZ_LANG_BYTE_ARRAY_JSON);
	if (RZ_STR_ISEMPTY(code)) {
		free(code);
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_print(code);
	free(code);
	return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_cmd_print_8bit_hexpair_function_handler(RzCore *core, int argc, const char **argv) {
	RzAnalysisFunction *f = rz_analysis_first_function_in(core->analysis, core->offset);
	if (!f) {
		RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset);
		return RZ_CMD_STATUS_ERROR;
	}
	size_t len = rz_analysis_function_linear_size(f);
	ut64 addr = rz_analysis_function_min_addr(f);
	return print_8bit_hexpair(core, addr, len);
}

// "x"
RZ_IPI RzCmdStatus rz_print_hexdump_alias_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	return rz_print_hexdump_handler(core, argc, argv, state);
}

// "xc"
RZ_IPI RzCmdStatus rz_print_hexdump_comments_alias_handler(RzCore *core, int argc, const char **argv) {
	return rz_print_hexdump_comments_handler(core, argc, argv);
}

static size_t format_width_to_num(size_t arch_bits, const char *width) {
	rz_return_val_if_fail(width, 0);
	switch (width[0]) {
	default:
		rz_warn_if_reached();
		return 0;
	case 'b': // # Byte = 1 bytes
		return 1;
	case 'h': // # Half word = 2 bytes
		return 2;
	case 'w': // # Word = 4 bytes
		return 4;
	case 'd': // # Double word = 8 bytes
		return 8;
	case 'a': // # Architecture width
		return arch_bits > 0 ? arch_bits / 8 : 1;
	}
}

// "pxF"
RZ_IPI RzCmdStatus rz_print_hexdump_format_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	ut64 num = rz_num_math(core->num, argv[1]);
	if (!num) {
		return RZ_CMD_STATUS_OK;
	}
	const char *format = argv[2];
	size_t width = format_width_to_num(rz_asm_get_bits(core->rasm), argv[3]);
	switch (format[0]) {
	default:
		RZ_LOG_ERROR("Invalid format: '%s'.\n", format);
		return RZ_CMD_STATUS_ERROR;
	case 'x':
		return bool2status(rz_core_print_dump(core, state->mode, core->offset, width, num * width, RZ_CORE_PRINT_FORMAT_TYPE_HEXADECIMAL));
	case 'o':
		// Parameter n must be 1 here. Don't ask me why :( The rabbit whole is deep and defuse.
		return bool2status(rz_core_print_dump(core, state->mode, core->offset, 1, num * width, RZ_CORE_PRINT_FORMAT_TYPE_OCTAL));
	case 'd':
		return bool2status(rz_core_print_dump(core, state->mode, core->offset, width, num * width, RZ_CORE_PRINT_FORMAT_TYPE_INTEGER));
	case 'f': {
		char *format = RZ_NEWS0(char, num + 1);
		for (size_t i = 0; i < num; ++i) {
			format[i] = 'f';
		}
		RzCmdStatus status = print_format(core, format, RZ_PRINT_MUSTSEE, state);
		free(format);
		return status;
	}
	case 's': {
		size_t block_size_bak = core->blocksize;
		size_t new_block_size = num * width;
		if (!rz_core_block_size(core, new_block_size)) {
			RZ_LOG_ERROR("Failed to set block size to %" PFMTSZu " bytes.\n", new_block_size);
			return RZ_CMD_STATUS_ERROR;
		}
		const char *psb = "psb";
		RzCmdStatus status = rz_print_strings_current_block_handler(core, 1, &psb, state->mode);
		if (!rz_core_block_size(core, block_size_bak)) {
			RZ_LOG_ERROR("Failed to reset block size to %" PFMTSZu " bytes.\n", block_size_bak);
			return RZ_CMD_STATUS_ERROR;
		}
		return status;
	}
	}
	return RZ_CMD_STATUS_ERROR;
}
