/* radare - LGPL - Copyright 2009-2026 - pancake, maijin, thestr4ng3r */

#include <r_core.h>
#include <r_vec.h>

#define NAME_BUF_SIZE    64
#define NAME_LEN_MAX     4096
#define BASE_CLASSES_MAX 32

typedef struct rtti_complete_object_locator_t {
	ut32 signature;
	ut32 vtable_offset;         // offset of the vtable within class
	ut32 cd_offset;             // constructor displacement offset
	ut32 type_descriptor_addr;  // only a relative offset for 64bit
	ut32 class_descriptor_addr; // only a relative offset for 64bit
	ut32 object_base;           // only for 64bit, see rtti_msvc_read_complete_object_locator()
} rtti_complete_object_locator;

typedef struct rtti_class_hierarchy_descriptor_t {
	ut32 signature;
	ut32 attributes;            // bit 0 set = multiple inheritance, bit 1 set = virtual inheritance
	ut32 num_base_classes;
	ut32 base_class_array_addr; // only a relative offset for 64bit
} rtti_class_hierarchy_descriptor;

typedef struct rtti_base_class_descriptor_t {
	ut32 type_descriptor_addr;  // only a relative offset for 64bit
	ut32 num_contained_bases;
	struct {
		st32 mdisp;             // member displacement
		st32 pdisp;             // vbtable displacement
		st32 vdisp;             // displacement inside vbtable
	} where;
	ut32 attributes;
} rtti_base_class_descriptor;

typedef struct rtti_type_descriptor_t {
	ut64 vtable_addr;
	ut64 spare;
	char *name;
} rtti_type_descriptor;

typedef struct recovery_type_descriptor_t RecoveryTypeDescriptor;

typedef struct recovery_base_descriptor_t {
	rtti_base_class_descriptor *bcd;
	RecoveryTypeDescriptor *td;
} RecoveryBaseDescriptor;

R_VEC_TYPE (RVecRecoveryBaseDescriptor, RecoveryBaseDescriptor);

typedef struct recovery_complete_object_locator_t {
	ut64 addr;
	bool valid;
	RVTableInfo *vtable;
	rtti_complete_object_locator col;
	RecoveryTypeDescriptor *td;
	rtti_class_hierarchy_descriptor chd;
	RList *bcd; // <rtti_base_class_descriptor>
	RVecRecoveryBaseDescriptor base_td;
} RecoveryCompleteObjectLocator;

static void recovery_complete_object_locator_free(RecoveryCompleteObjectLocator *col);
static void recovery_type_descriptor_free(RecoveryTypeDescriptor *td);

static void rtti_msvc_vtable_info_fini(RVTableInfo **info) {
	r_anal_vtable_info_free (*info);
}

static void rtti_msvc_col_fini(RecoveryCompleteObjectLocator **col) {
	recovery_complete_object_locator_free (*col);
}

static void rtti_msvc_td_fini(RecoveryTypeDescriptor **td) {
	recovery_type_descriptor_free (*td);
}

R_VEC_TYPE_WITH_FINI (RVecVTableInfo, RVTableInfo *, rtti_msvc_vtable_info_fini);
R_VEC_TYPE_WITH_FINI (RVecRecoveryCompleteObjectLocator, RecoveryCompleteObjectLocator *, rtti_msvc_col_fini);
R_VEC_TYPE_WITH_FINI (RVecRecoveryTypeDescriptor, RecoveryTypeDescriptor *, rtti_msvc_td_fini);

typedef struct rtti_msvc_anal_context_t {
	RVTableContext *vt_context;
	RVecVTableInfo vtables; // <RVTableInfo>
	RVecRecoveryCompleteObjectLocator complete_object_locators; // <RecoveryCompleteObjectLocator>
	HtUP *addr_col; // <ut64, RecoveryCompleteObjectLocator *>
	RVecRecoveryTypeDescriptor type_descriptors; // <RecoveryTypeDescriptor>
	HtUP *addr_td; // <ut64, RecoveryTypeDescriptor *>
	HtUP *col_td_classes; // <ut64, char *> contains already recovered classes for col (or td) addresses
} RRTTIMSVCAnalContext;

RecoveryTypeDescriptor *recovery_anal_type_descriptor(RRTTIMSVCAnalContext *context, ut64 addr, RecoveryCompleteObjectLocator *col);
static const char *recovery_apply_complete_object_locator(RRTTIMSVCAnalContext *context, RecoveryCompleteObjectLocator *col);
static const char *recovery_apply_type_descriptor(RRTTIMSVCAnalContext *context, RecoveryTypeDescriptor *td);

static void rtti_type_descriptor_fini(rtti_type_descriptor *td) {
	free (td->name);
	td->name = NULL;
}

static inline ut64 rtti_msvc_addr(RVTableContext *context, ut64 col_addr, ut64 col_base, ut32 addr) {
	if (context->word_size != 8) {
		return addr;
	}
	return addr + (col_addr - col_base);
}

static bool rtti_msvc_read_complete_object_locator(RVTableContext *context, ut64 addr, rtti_complete_object_locator *col) {
	if (addr == UT64_MAX) {
		return false;
	}

	ut8 buf[6 * sizeof (ut32)];
	int colSize = 5 * sizeof (ut32);
	if (context->word_size == 8) {
		colSize += sizeof (ut32);
	}
	if (colSize > sizeof (buf)) {
		return false;
	}

	if (context->anal->iob.read_at (context->anal->iob.io, addr, buf, colSize) != colSize) {
		return false;
	}
	const bool be = R_ARCH_CONFIG_IS_BIG_ENDIAN (context->anal->config);

	ut32 (*read_at_32)(const void *src, size_t offset) = be? r_read_at_be32 : r_read_at_le32;
	col->signature = read_at_32 (buf, 0);
	col->vtable_offset = read_at_32 (buf, 4);
	col->cd_offset = read_at_32 (buf, 8);

	int offsetSize = R_MIN (context->word_size, 4);
	col->type_descriptor_addr = (ut32) r_read_ble (buf + 12, be, offsetSize * 8);
	col->class_descriptor_addr = (ut32) r_read_ble (buf + 12 + offsetSize, be, offsetSize * 8);
	if (context->word_size == 8) {
		// 64bit is special:
		// Type Descriptor and Class Hierarchy Descriptor addresses are computed
		// by 32 bit values *(col+12) + *(col+0x14)
		// and *(col+16) + *(col+0x14) respectively
		col->object_base = read_at_32 (buf, 20);
	} else {
		col->object_base = 0;
	}

	return true;
}

static bool rtti_msvc_read_class_hierarchy_descriptor(RVTableContext *context, ut64 addr, rtti_class_hierarchy_descriptor *chd) {
	if (addr == UT64_MAX) {
		return false;
	}

	ut8 buf[4 * sizeof (ut32)];
	int chdSize = 3 * sizeof (ut32) + R_MIN (4, context->word_size);
	if (chdSize > sizeof (buf)) {
		return false;
	}

	if (context->anal->iob.read_at (context->anal->iob.io, addr, buf, chdSize) != chdSize) {
		return false;
	}

	const bool be = R_ARCH_CONFIG_IS_BIG_ENDIAN (context->anal->config);
	ut32 (*read_at_32)(const void *src, size_t offset) = be? r_read_at_be32 : r_read_at_le32;
	chd->signature = read_at_32 (buf, 0);
	chd->attributes = read_at_32 (buf, 4);
	chd->num_base_classes = read_at_32 (buf, 8);
	if (context->word_size <= 4) {
		chd->base_class_array_addr = (ut32) r_read_ble (buf + 12, be, context->word_size * 8);
	} else {
		// 64bit is special, like in Complete Object Locator.
		// Only the offset from the base from Complete Object Locator
		// is contained in Class Hierarchy Descriptor
		chd->base_class_array_addr = read_at_32 (buf, 12);
	}
	return true;
}

static ut64 rtti_msvc_base_class_descriptor_size(RVTableContext *context) {
	return context->word_size + 5 * sizeof (ut32);
}

static bool rtti_msvc_read_base_class_descriptor(RVTableContext *context, ut64 addr, rtti_base_class_descriptor *bcd) {
	if (addr == UT64_MAX) {
		return false;
	}

	ut8 buf[sizeof (ut64) + 5 * sizeof (ut32)];
	int bcdSize = (int) rtti_msvc_base_class_descriptor_size (context);
	if (bcdSize > sizeof (buf)) {
		return false;
	}

	if (context->anal->iob.read_at (context->anal->iob.io, addr, buf, bcdSize) != bcdSize) {
		return false;
	}
	const bool be = R_ARCH_CONFIG_IS_BIG_ENDIAN (context->anal->config);
	ut32 (*read_at_32)(const void *src, size_t offset) = be? r_read_at_be32 : r_read_at_le32;
	int typeDescriptorAddrSize = R_MIN (context->word_size, 4);
	bcd->type_descriptor_addr = (ut32) r_read_ble (buf, be, typeDescriptorAddrSize * 8);
	size_t offset = (size_t) typeDescriptorAddrSize;
	bcd->num_contained_bases = read_at_32 (buf, offset);
	bcd->where.mdisp = read_at_32 (buf, offset + sizeof (ut32));
	bcd->where.pdisp = read_at_32 (buf, offset + 2 * sizeof (ut32));
	bcd->where.vdisp = read_at_32 (buf, offset + 3 * sizeof (ut32));
	bcd->attributes = read_at_32 (buf, offset + 4 * sizeof (ut32));
	return true;
}

static RList *rtti_msvc_read_base_class_array(RVTableContext *context, ut32 num_base_classes, ut64 base, ut32 offset) {
	if (base == UT64_MAX || offset == UT32_MAX || num_base_classes == UT32_MAX) {
		return NULL;
	}

	RList *ret = r_list_newf (free);
	RCore *core = context->anal->coreb.core;
	RCons *cons = core->cons;

	ut64 addr = base + offset;
	ut64 stride = R_MIN (context->word_size, 4);

	if (num_base_classes > BASE_CLASSES_MAX) {
		if (context->anal->verbose) {
			R_LOG_WARN ("Length of base class array at 0x%08"PFMT64x" exceeds %d", addr, BASE_CLASSES_MAX);
		}
		num_base_classes = BASE_CLASSES_MAX;
	}

	r_cons_break_push (cons, NULL, NULL);
	while (num_base_classes > 0) {
		if (r_cons_is_breaked (cons)) {
			break;
		}

		ut64 bcdAddr;
		if (context->word_size <= 4) {
			if (!context->read_addr (context->anal, addr, &bcdAddr)) {
				break;
			}
			if (bcdAddr == UT32_MAX) {
				break;
			}
		} else {
			// special offset calculation for 64bit
			ut8 tmp[4] = {0};
			if (context->anal->iob.read_at (context->anal->iob.io, addr, tmp, 4) != 4) {
				break;
			}
			const bool be = R_ARCH_CONFIG_IS_BIG_ENDIAN (context->anal->config);
			ut32 bcdOffset = r_read_ble32 (tmp, be);
			if (bcdOffset == UT32_MAX) {
				break;
			}
			bcdAddr = base + bcdOffset;
		}

		rtti_base_class_descriptor *bcd = malloc (sizeof (rtti_base_class_descriptor));
		if (!bcd) {
			break;
		}
		if (!rtti_msvc_read_base_class_descriptor (context, bcdAddr, bcd)) {
			free (bcd);
			break;
		}
		r_list_append (ret, bcd);
		addr += stride;
		num_base_classes--;
	}
	r_cons_break_pop (cons);

	if (num_base_classes > 0) {
		// there was an error in the loop above
		r_list_free (ret);
		return NULL;
	}

	return ret;
}

static char *rtti_msvc_read_name(RVTableContext *context, ut64 addr) {
	RStrBuf sb;
	r_strbuf_init (&sb);
	ut8 buf[NAME_BUF_SIZE];
	ut64 off = 0;
	while (r_strbuf_length (&sb) < NAME_LEN_MAX) {
		if (context->anal->iob.read_at (context->anal->iob.io, addr + off, buf, sizeof (buf)) != sizeof (buf)) {
			break;
		}
		size_t i;
		for (i = 0; i < sizeof (buf); i++) {
			if (buf[i] == 0xff) {
				goto err;
			}
			if (buf[i] == '\0') {
				r_strbuf_append_n (&sb, (const char *)buf, i);
				return r_strbuf_drain_nofree (&sb);
			}
		}
		r_strbuf_append_n (&sb, (const char *)buf, sizeof (buf));
		off += sizeof (buf);
	}
err:
	r_strbuf_fini (&sb);
	return NULL;
}

static bool rtti_msvc_read_type_descriptor(RVTableContext *context, ut64 addr, rtti_type_descriptor *td) {
	if (addr == UT64_MAX) {
		return false;
	}
	if (!context->read_addr (context->anal, addr, &td->vtable_addr)) {
		return false;
	}
	if (!context->read_addr (context->anal, addr + context->word_size, &td->spare)) {
		return false;
	}
	td->name = rtti_msvc_read_name (context, addr + 2 * context->word_size);
	return td->name != NULL;
}

static void rtti_msvc_print_complete_object_locator(rtti_complete_object_locator *col, ut64 addr, const char *prefix) {
	RCons *cons = r_cons_singleton ();
	r_cons_printf (cons, "%sComplete Object Locator at 0x%08"PFMT64x":\n"
				   "%s\tsignature: %#x\n"
				   "%s\tvftableOffset: %#x\n"
				   "%s\tcdOffset: %#x\n"
				   "%s\ttypeDescriptorAddr: 0x%08"PFMT32x"\n"
				   "%s\tclassDescriptorAddr: 0x%08"PFMT32x"\n",
				   prefix, addr,
				   prefix, col->signature,
				   prefix, col->vtable_offset,
				   prefix, col->cd_offset,
				   prefix, col->type_descriptor_addr,
				   prefix, col->class_descriptor_addr);
	r_cons_printf (cons, "%s\tobjectBase: 0x%08"PFMT32x"\n\n",
				   prefix, col->object_base);
}

static void rtti_msvc_print_complete_object_locator_json(PJ *pj, rtti_complete_object_locator *col) {
	pj_o (pj);
	pj_kn (pj, "signature", col->signature);
	pj_kn (pj, "vftable_offset", col->vtable_offset);
	pj_kn (pj, "cd_offset", col->cd_offset);
	pj_kn (pj, "type_desc_addr", col->type_descriptor_addr);
	pj_kn (pj, "class_desc_addr", col->class_descriptor_addr);
	pj_kn (pj, "object_base", col->object_base);
	pj_end (pj);
}

static void rtti_msvc_print_type_descriptor(rtti_type_descriptor *td, ut64 addr, const char *prefix) {
	RCons *cons = r_cons_singleton ();
	r_cons_printf (cons, "%sType Descriptor at 0x%08"PFMT64x":\n"
				   "%s\tvtableAddr: 0x%08"PFMT64x"\n"
				   "%s\tspare: 0x%08"PFMT64x"\n"
				   "%s\tname: %s\n\n",
				   prefix, addr,
				   prefix, td->vtable_addr,
				   prefix, td->spare,
				   prefix, td->name);
}

static void rtti_msvc_print_type_descriptor_json(PJ *pj, rtti_type_descriptor *td) {
	pj_o (pj);
	pj_kn (pj, "vtable_addr", td->vtable_addr);
	pj_kn (pj, "spare", td->spare);
	pj_ks (pj, "name", td->name);
	pj_end (pj);
}

static void rtti_msvc_print_class_hierarchy_descriptor(rtti_class_hierarchy_descriptor *chd, ut64 addr, const char *prefix) {
	RCons *cons = r_cons_singleton ();
	r_cons_printf (cons, "%sClass Hierarchy Descriptor at 0x%08"PFMT64x":\n"
				   "%s\tsignature: %#x\n"
				   "%s\tattributes: %#x\n"
				   "%s\tnumBaseClasses: %#x\n"
				   "%s\tbaseClassArrayAddr: 0x%08"PFMT32x"\n\n",
				   prefix, addr,
				   prefix, chd->signature,
				   prefix, chd->attributes,
				   prefix, chd->num_base_classes,
				   prefix, chd->base_class_array_addr);
}

static void rtti_msvc_print_class_hierarchy_descriptor_json(PJ *pj, rtti_class_hierarchy_descriptor *chd) {
	pj_o (pj);
	pj_kn (pj, "signature", chd->signature);
	pj_kn (pj, "attributes", chd->attributes);
	pj_kn (pj, "num_base_classes", chd->num_base_classes);
	pj_kn (pj, "base_class_array_addr", chd->base_class_array_addr);
	pj_end (pj);
}

static void rtti_msvc_print_base_class_descriptor(rtti_base_class_descriptor *bcd, const char *prefix) {
	RCons *cons = r_cons_singleton ();
	r_cons_printf (cons, "%sBase Class Descriptor:\n"
				   "%s\ttypeDescriptorAddr: 0x%08"PFMT32x"\n"
				   "%s\tnumContainedBases: %#x\n"
				   "%s\twhere:\n"
				   "%s\t\tmdisp: %d\n"
				   "%s\t\tpdisp: %d\n"
				   "%s\t\tvdisp: %d\n"
				   "%s\tattributes: %#x\n\n",
				   prefix,
				   prefix, bcd->type_descriptor_addr,
				   prefix, bcd->num_contained_bases,
				   prefix,
				   prefix, bcd->where.mdisp,
				   prefix, bcd->where.pdisp,
				   prefix, bcd->where.vdisp,
				   prefix, bcd->attributes);
}

static void rtti_msvc_print_base_class_descriptor_json(PJ *pj, rtti_base_class_descriptor *bcd) {
	pj_o (pj);
	pj_kn (pj, "type_desc_addr", bcd->type_descriptor_addr);
	pj_kn (pj, "num_contained_bases", bcd->num_contained_bases);
	pj_ko (pj, "where");
	pj_ki (pj, "mdisp", bcd->where.mdisp);
	pj_ki (pj, "pdisp", bcd->where.pdisp);
	pj_ki (pj, "vdisp", bcd->where.vdisp);
	pj_end (pj);
	pj_kn (pj, "attributes", bcd->attributes);
	pj_end (pj);
}


/**
 * Demangle a class name as found in MSVC RTTI type descriptors.
 *
 * Examples:
 * .?AVClassA@@
 * => ClassA
 * .?AVClassInInnerNamespace@InnerNamespace@OuterNamespace@@
 * => OuterNamespace::InnerNamespace::AVClassInInnerNamespace
 */
R_API char *r_anal_rtti_msvc_demangle_class_name(RVTableContext *context, const char *name) {
	if (!name) {
		return NULL;
	}
	size_t original_len = strlen (name);
	if (original_len < 7
		|| (strncmp (name, ".?AV", 4) != 0 && strncmp (name, ".?AU", 4) != 0)
		|| strncmp (name + original_len - 2, "@@", 2) != 0) {
		return NULL;
	}
	char *ret = context->anal->binb.demangle (NULL, "msvc", name, 0, false);
	// drop the "class " or "struct " keyword, builtin types like __m128d have none
	char *n = ret? strchr (ret, ' '): NULL;
	if (n) {
		memmove (ret, n + 1, strlen (n + 1) + 1);
	}
	if (R_STR_ISEMPTY (ret)) {
		R_FREE (ret);
	}
	return ret;
}

R_API void r_anal_rtti_msvc_print_complete_object_locator(RVTableContext *context, ut64 addr, int mode) {
	rtti_complete_object_locator col;
	if (!rtti_msvc_read_complete_object_locator (context, addr, &col)) {
		R_LOG_ERROR ("Failed to parse Complete Object Locator at 0x%08"PFMT64x, addr);
		return;
	}

	if (mode == 'j') {
		PJ *pj = pj_new ();
		if (!pj) {
			return;
		}
		rtti_msvc_print_complete_object_locator_json (pj, &col);
		RCons *cons = r_cons_singleton ();
		r_cons_print (cons, pj_string (pj));
		pj_free (pj);
	} else {
		rtti_msvc_print_complete_object_locator (&col, addr, "");
	}
}

R_API void r_anal_rtti_msvc_print_type_descriptor(RVTableContext *context, ut64 addr, int mode) {
	rtti_type_descriptor td = {0};
	if (!rtti_msvc_read_type_descriptor (context, addr, &td)) {
		R_LOG_ERROR ("Failed to parse Type Descriptor at 0x%08"PFMT64x, addr);
		return;
	}

	if (mode == 'j') {
		PJ *pj = pj_new ();
		if (!pj) {
			return;
		}
		RCons *cons = r_cons_singleton ();
		rtti_msvc_print_type_descriptor_json (pj, &td);
		r_cons_print (cons, pj_string (pj));
		pj_free (pj);
	} else {
		rtti_msvc_print_type_descriptor (&td, addr, "");
	}

	rtti_type_descriptor_fini (&td);
}

R_API void r_anal_rtti_msvc_print_class_hierarchy_descriptor(RVTableContext *context, ut64 addr, int mode) {
	rtti_class_hierarchy_descriptor chd;
	if (!rtti_msvc_read_class_hierarchy_descriptor (context, addr, &chd)) {
		R_LOG_ERROR ("Failed to parse Class Hierarchy Descriptor at 0x%08"PFMT64x, addr);
		return;
	}

	if (mode == 'j') {
		PJ *pj = pj_new ();
		if (!pj) {
			return;
		}
		rtti_msvc_print_class_hierarchy_descriptor_json (pj, &chd);
		RCons *cons = r_cons_singleton ();
		r_cons_print (cons, pj_string (pj));
		pj_free (pj);
	} else {
		rtti_msvc_print_class_hierarchy_descriptor (&chd, addr, "");
	}
}

R_API void r_anal_rtti_msvc_print_base_class_descriptor(RVTableContext *context, ut64 addr, int mode) {
	rtti_base_class_descriptor bcd;
	if (!rtti_msvc_read_base_class_descriptor (context, addr, &bcd)) {
		R_LOG_ERROR ("Failed to parse Base Class Descriptor at 0x%08"PFMT64x, addr);
		return;
	}

	if (mode == 'j') {
		PJ *pj = pj_new ();
		if (!pj) {
			return;
		}
		RCons *cons = r_cons_singleton ();
		rtti_msvc_print_base_class_descriptor_json (pj, &bcd);
		r_cons_print (cons, pj_string (pj));
		pj_free (pj);
	} else {
		rtti_msvc_print_base_class_descriptor (&bcd, "");
	}
}

static bool rtti_msvc_print_complete_object_locator_recurse(RVTableContext *context, ut64 atAddress, int mode, bool strict) {
	bool use_json = mode == 'j';
	PJ *pj = NULL;

	ut64 colRefAddr = atAddress - context->word_size;
	ut64 colAddr;
	if (!context->read_addr (context->anal, colRefAddr, &colAddr)) {
		return false;
	}

	// complete object locator
	rtti_complete_object_locator col;
	if (!rtti_msvc_read_complete_object_locator (context, colAddr, &col)) {
		if (!strict) {
			R_LOG_ERROR ("Failed to parse Complete Object Locator at 0x%08"PFMT64x" (referenced from 0x%08"PFMT64x")", colAddr, colRefAddr);
		}
		return false;
	}

	// type descriptor
	ut64 typeDescriptorAddr = rtti_msvc_addr (context, colAddr, col.object_base, col.type_descriptor_addr);
	rtti_type_descriptor td = {0};
	if (!rtti_msvc_read_type_descriptor (context, typeDescriptorAddr, &td)) {
		if (!strict) {
			R_LOG_ERROR ("Failed to parse Type Descriptor at 0x%08"PFMT64x, typeDescriptorAddr);
		}
		return false;
	}

	// class hierarchy descriptor
	ut64 classHierarchyDescriptorAddr = rtti_msvc_addr (context, colAddr, col.object_base, col.class_descriptor_addr);
	rtti_class_hierarchy_descriptor chd;
	if (!rtti_msvc_read_class_hierarchy_descriptor (context, classHierarchyDescriptorAddr, &chd)) {
		if (!strict) {
			R_LOG_ERROR ("Failed to parse Class Hierarchy Descriptor at 0x%08"PFMT64x, classHierarchyDescriptorAddr);
		}
		rtti_type_descriptor_fini (&td);
		return false;
	}

	ut64 base = chd.base_class_array_addr;
	ut32 baseClassArrayOffset = 0;
	if (context->word_size == 8) {
		base = colAddr - col.object_base;
		baseClassArrayOffset = chd.base_class_array_addr;
	}

	RList *baseClassArray = rtti_msvc_read_base_class_array (context, chd.num_base_classes, base, baseClassArrayOffset);
	if (!baseClassArray) {
		if (!strict) {
			R_LOG_ERROR ("Failed to parse Base Class Array starting at 0x%08"PFMT64x, base + baseClassArrayOffset);
		}
		rtti_type_descriptor_fini (&td);
		return false;
	}


	// print
	if (use_json) {
		pj = pj_new ();
		if (!pj) {
			return false;
		}
		pj_o (pj);
		pj_k (pj, "complete_object_locator");
		rtti_msvc_print_complete_object_locator_json (pj, &col);
		pj_k (pj, "type_desc");
		rtti_msvc_print_type_descriptor_json (pj, &td);
		pj_k (pj, "class_hierarchy_desc");
		rtti_msvc_print_class_hierarchy_descriptor_json (pj, &chd);
		pj_ka (pj, "base_classes");
	} else {
		rtti_msvc_print_complete_object_locator (&col, colAddr, "");
		rtti_msvc_print_type_descriptor (&td, typeDescriptorAddr, "\t");
		rtti_msvc_print_class_hierarchy_descriptor (&chd, classHierarchyDescriptorAddr, "\t");
	}


	// base classes
	RListIter *bcdIter;
	rtti_base_class_descriptor *bcd;
	r_list_foreach (baseClassArray, bcdIter, bcd) {
		if (use_json) {
			pj_o (pj);
			pj_k (pj, "desc");
			rtti_msvc_print_base_class_descriptor_json (pj, bcd);
		} else {
			rtti_msvc_print_base_class_descriptor (bcd, "\t\t");
		}

		ut64 baseTypeDescriptorAddr = rtti_msvc_addr (context, colAddr, col.object_base, bcd->type_descriptor_addr);
		rtti_type_descriptor btd = {0};
		if (rtti_msvc_read_type_descriptor (context, baseTypeDescriptorAddr, &btd)) {
			if (use_json) {
				pj_k (pj, "type_desc");
				rtti_msvc_print_type_descriptor_json (pj, &btd);
			} else {
				rtti_msvc_print_type_descriptor (&btd, baseTypeDescriptorAddr, "\t\t\t");
			}
			rtti_type_descriptor_fini (&btd);
		} else {
			if (!strict) {
				R_LOG_ERROR ("Failed to parse Type Descriptor at 0x%08"PFMT64x, baseTypeDescriptorAddr);
			}
		}

		if (use_json) {
			pj_end (pj);
		}
	}
	if (use_json) {
		pj_end (pj);
		pj_end (pj);
		RCons *cons = r_cons_singleton ();
		r_cons_print (cons, pj_string (pj));
		pj_free (pj);
	}

	rtti_type_descriptor_fini (&td);
	return true;
}

R_API bool r_anal_rtti_msvc_print_at_vtable(RVTableContext *context, ut64 addr, int mode, bool strict) {
	return rtti_msvc_print_complete_object_locator_recurse (context, addr, mode, strict);
}

static RecoveryCompleteObjectLocator *recovery_complete_object_locator_new(void) {
	RecoveryCompleteObjectLocator *col = R_NEW0 (RecoveryCompleteObjectLocator);
	RVecRecoveryBaseDescriptor_init (&col->base_td);
	return col;
}

static void recovery_complete_object_locator_free(RecoveryCompleteObjectLocator *col) {
	r_list_free (col->bcd);
	RVecRecoveryBaseDescriptor_fini (&col->base_td);
	free (col);
}

struct recovery_type_descriptor_t {
	ut64 addr;
	bool valid;
	rtti_type_descriptor td;
	RecoveryCompleteObjectLocator *col;
};

RecoveryTypeDescriptor *recovery_type_descriptor_new() {
	RecoveryTypeDescriptor *td = R_NEW (RecoveryTypeDescriptor);
	if (!td) {
		return NULL;
	}

	td->addr = 0;
	td->valid = false;
	memset (&td->td, 0, sizeof (td->td));
	td->col = NULL;
	//td->vtable = NULL;
	return td;
}

static void recovery_type_descriptor_free(RecoveryTypeDescriptor *td) {
	rtti_type_descriptor_fini (&td->td);
	free (td);
}


RecoveryCompleteObjectLocator *recovery_anal_complete_object_locator(RRTTIMSVCAnalContext *context, ut64 addr, RVTableInfo *vtable) {
	RecoveryCompleteObjectLocator *col = ht_up_find (context->addr_col, addr, NULL);
	if (col) {
		return col;
	}

	col = recovery_complete_object_locator_new ();
	if (!col) {
		return NULL;
	}
	RVecRecoveryCompleteObjectLocator_push_back (&context->complete_object_locators, &col);
	ht_up_insert (context->addr_col, addr, col);
	col->addr = addr;
	col->valid = rtti_msvc_read_complete_object_locator (context->vt_context, addr, &col->col);
	if (!col->valid) {
		return col;
	}
	col->vtable = vtable;


	ut64 td_addr = rtti_msvc_addr (context->vt_context, col->addr, col->col.object_base, col->col.type_descriptor_addr);
	col->td = recovery_anal_type_descriptor (context, td_addr, col);
	if (!col->td->valid) {
		col->valid = false;
		return col;
	}
	col->td->col = col;


	ut64 chd_addr = rtti_msvc_addr (context->vt_context, col->addr, col->col.object_base, col->col.class_descriptor_addr);
	col->valid &= rtti_msvc_read_class_hierarchy_descriptor (context->vt_context, chd_addr, &col->chd);
	if (!col->valid) {
		return col;
	}


	ut64 base = col->chd.base_class_array_addr;
	ut32 baseClassArrayOffset = 0;
	if (context->vt_context->word_size == 8) {
		base = col->addr - col->col.object_base;
		baseClassArrayOffset = col->chd.base_class_array_addr;
	}

	col->bcd = rtti_msvc_read_base_class_array (context->vt_context, col->chd.num_base_classes, base, baseClassArrayOffset);
	if (!col->bcd) {
		col->valid = false;
		return col;
	}

	if (!RVecRecoveryBaseDescriptor_reserve (&col->base_td, (size_t)col->bcd->length)) {
		r_list_free (col->bcd);
		col->bcd = NULL;
		return NULL;
	}
	RListIter *bcdIter;
	rtti_base_class_descriptor *bcd;
	r_list_foreach (col->bcd, bcdIter, bcd) {
		ut64 base_td_addr = rtti_msvc_addr (context->vt_context, col->addr, col->col.object_base, bcd->type_descriptor_addr);
		RecoveryTypeDescriptor *td = recovery_anal_type_descriptor (context, base_td_addr, NULL);
		if (td == col->td) {
			continue;
		}
		if (!td->valid) {
			if (context->vt_context->anal->verbose) {
				R_LOG_WARN ("type descriptor of base is invalid");
			}
			continue;
		}
		RecoveryBaseDescriptor *base_desc = RVecRecoveryBaseDescriptor_emplace_back (&col->base_td);
		base_desc->bcd = bcd;
		base_desc->td = td;
	}

	return col;
}

RecoveryTypeDescriptor *recovery_anal_type_descriptor(RRTTIMSVCAnalContext *context, ut64 addr, RecoveryCompleteObjectLocator *col) {
	RecoveryTypeDescriptor *td = ht_up_find (context->addr_td, addr, NULL);
	if (td) {
		if (col) {
			td->col = col;
		}
		return td;
	}

	td = recovery_type_descriptor_new ();
	if (!td) {
		return NULL;
	}
	RVecRecoveryTypeDescriptor_push_back (&context->type_descriptors, &td);
	ht_up_insert (context->addr_td, addr, td);
	td->addr = addr;
	td->valid = rtti_msvc_read_type_descriptor (context->vt_context, addr, &td->td);
	if (!td->valid) {
		return td;
	}

	td->col = col;

	return td;
}

static char *unique_class_name(RAnal *anal, const char *original_name) {
	if (!r_anal_class_exists (anal, original_name)) {
		return strdup (original_name);
	}

	char *name = NULL;
	if (anal->verbose) {
		R_LOG_WARN ("class name %s already taken!", original_name);
	}
	int i = 1;

	do {
		free (name);
		name = r_str_newf ("%s.%d", original_name, i++);
		if (!name) {
			return NULL;
		}
	} while (r_anal_class_exists (anal, name));

	return name;
}

static void recovery_apply_vtable(RAnal *anal, const char *class_name, RVTableInfo *vtable_info, ut64 class_offset) {
	if (!vtable_info) {
		return;
	}

	RAnalVTable vtable = {0};
	vtable.addr = vtable_info->saddr;
	vtable.offset = class_offset;
	const size_t vtable_len = RVecRVTableMethodInfo_length (&vtable_info->methods);
	const ut32 word_size = anal->config->bits / 8;
	if (vtable_len && word_size) {
		vtable.size = vtable_len * word_size;
		r_anal_class_vtable_set (anal, class_name, &vtable);
		r_anal_class_vtable_fini (&vtable);

		RVTableMethodInfo *vmeth;
		R_VEC_FOREACH (&vtable_info->methods, vmeth) {
			RAnalMethod meth;
			meth.addr = vmeth->addr;
			meth.vtable_offset = vmeth->vtable_offset;
			meth.vtable_addr = vtable_info->saddr;
			meth.name = class_offset
				? r_str_newf ("virtual_%"PFMT64u"_%"PFMT64d, class_offset, meth.vtable_offset)
				: r_str_newf ("virtual_%"PFMT64d, meth.vtable_offset);
			r_anal_class_method_set (anal, class_name, &meth);
			r_anal_class_method_fini (&meth);
		}
	}
}

static void recovery_apply_bases(RRTTIMSVCAnalContext *context, const char *class_name, RVecRecoveryBaseDescriptor *base_descs) {
	RecoveryBaseDescriptor *base_desc;
	R_VEC_FOREACH (base_descs, base_desc) {
		RecoveryTypeDescriptor *base_td = base_desc->td;
		if (!base_td->valid) {
			R_LOG_WARN ("Invalid base td");
			continue;
		}

		const char *base_class_name;
		if (!base_td->col) {
			if (context->vt_context->anal->verbose) {
				R_LOG_WARN ("Base td %s has no col. Falling back to recovery from td only", base_td->td.name);
			}
			base_class_name = recovery_apply_type_descriptor (context, base_td);
		} else {
			base_class_name = recovery_apply_complete_object_locator (context, base_td->col);
		}

		if (!base_class_name) {
			if (context->vt_context->anal->verbose) {
				R_LOG_WARN ("Failed to convert !base td->col or td to a class");
			}
			continue;
		}

		RAnalBaseClass base;
		base.id = NULL;
		base.offset = (ut64)base_desc->bcd->where.mdisp;
		base.class_name = strdup (base_class_name);
		r_anal_class_base_set (context->vt_context->anal, class_name, &base);
		r_anal_class_base_fini (&base);
	}
}

static const char *recovery_apply_complete_object_locator(RRTTIMSVCAnalContext *context, RecoveryCompleteObjectLocator *col) {
	if (!col->valid) {
		return NULL;
	}

	if (!col->td) {
		if (context->vt_context->anal->verbose) {
			R_LOG_WARN ("no td for col at 0x%"PFMT64x, col->addr);
		}
		return NULL;
	}

	RAnal *anal = context->vt_context->anal;

	const char *existing = ht_up_find (context->col_td_classes, col->addr, NULL);
	if (existing) {
		return existing;
	}

	char *name = r_anal_rtti_msvc_demangle_class_name (context->vt_context, col->td->td.name);
	if (!name) {
		if (context->vt_context->anal->verbose) {
			R_LOG_ERROR ("Failed to demangle a class name: \"%s\"", col->td->td.name);
		}
		name = strdup (col->td->td.name);
		if (!name) {
			return NULL;
		}
	}

	char *tmp = name;
	name = unique_class_name (anal, name);
	free (tmp);
	if (!name) {
		return NULL;
	}

	r_anal_class_create (anal, name);
	ht_up_insert (context->col_td_classes, col->addr, name);

	recovery_apply_vtable (anal, name, col->vtable, col->col.vtable_offset);
	recovery_apply_bases (context, name, &col->base_td);

	return name;
}

static const char *recovery_apply_type_descriptor(RRTTIMSVCAnalContext *context, RecoveryTypeDescriptor *td) {
	if (!td->valid) {
		return NULL;
	}

	RAnal *anal = context->vt_context->anal;

	const char *existing = ht_up_find (context->col_td_classes, td->addr, NULL);
	if (existing) {
		return existing;
	}

	char *name = r_anal_rtti_msvc_demangle_class_name (context->vt_context, td->td.name);
	if (!name) {
		if (context->vt_context->anal->verbose) {
			R_LOG_ERROR ("Failed to demangle a class name: \"%s\"", td->td.name);
		}
		name = strdup (td->td.name);
		if (!name) {
			return NULL;
		}
	}

	r_anal_class_create (anal, name);
	ht_up_insert (context->col_td_classes, td->addr, name);

	if (!td->col || !td->col->valid) {
		return name;
	}

	recovery_apply_vtable (anal, name, td->col->vtable, td->col->col.vtable_offset);
	recovery_apply_bases (context, name, &td->col->base_td);

	return name;
}

static inline void str_value_free(HtUPKv *kv) {
	if (kv) {
		R_FREE (kv->value);
		// free (kv); ???
	}
}

R_API void r_anal_rtti_msvc_recover_all(RVTableContext *vt_context, RList *vtables) {
	RRTTIMSVCAnalContext context;
	context.vt_context = vt_context;
	RVecVTableInfo_init (&context.vtables);

	RVecRecoveryCompleteObjectLocator_init (&context.complete_object_locators);
	context.addr_col = ht_up_new0 ();
	RVecRecoveryTypeDescriptor_init (&context.type_descriptors);
	context.addr_td = ht_up_new0 ();

	context.col_td_classes = ht_up_new (NULL, (HtUPKvFreeFunc)str_value_free, (HtUPCalcSizeV)strlen);

	RListIter *vtableIter;
	RVTableInfo *table;
	r_list_foreach (vtables, vtableIter, table) {
		ut64 colRefAddr = table->saddr - vt_context->word_size;
		ut64 colAddr;
		if (!vt_context->read_addr (vt_context->anal, colRefAddr, &colAddr)) {
			continue;
		}
		recovery_anal_complete_object_locator (&context, colAddr, table);
	}

#if USE_TD_RECOVERY
	RecoveryTypeDescriptor **td_it;
	R_VEC_FOREACH (&context.type_descriptors, td_it) {
		RecoveryTypeDescriptor *td = *td_it;
		if (!td->valid) {
			continue;
		}
		recovery_apply_type_descriptor (&context, td);
	}
#else
	RecoveryCompleteObjectLocator **col_it;
	R_VEC_FOREACH (&context.complete_object_locators, col_it) {
		RecoveryCompleteObjectLocator *col = *col_it;
		if (!col->valid) {
			continue;
		}
		recovery_apply_complete_object_locator (&context, col);
	}
#endif
	RVecVTableInfo_fini (&context.vtables);
	RVecRecoveryCompleteObjectLocator_fini (&context.complete_object_locators);
	ht_up_free (context.addr_col);
	RVecRecoveryTypeDescriptor_fini (&context.type_descriptors);
	ht_up_free (context.addr_td);
	ht_up_free (context.col_td_classes);
}
