/* radare - LGPL - Copyright 2011-2026 - pancake, h4ng3r */

#include <r_bin.h>
#include "../i/private.h"
#include "dex/dex.h"
// TODO: use rhash/crypto/trans api instead
#define r_hash_adler32 __adler32
#include "../../muta/hash/adler32.c"

#define DBG_END_SEQUENCE          0x00
#define DBG_ADVANCE_PC            0x01
#define DBG_ADVANCE_LINE          0x02
#define DBG_START_LOCAL           0x03
#define DBG_START_LOCAL_EXTENDED  0x04
#define DBG_END_LOCAL             0x05
#define DBG_RESTART_LOCAL         0x06
#define DBG_SET_PROLOGUE_END      0x07
#define DBG_SET_EPILOGUE_BEGIN    0x08
#define DBG_SET_FILE              0x09
#define DBG_FIRST_SPECIAL         0x0A
#define DEX_PROTO_STACK_PARAMS    64

#define DBG_LINE_BASE             -4
#define DBG_LINE_RANGE            15

#define pf(f, ...) r_strbuf_appendf (sb, f, ##__VA_ARGS__)
#define ps(s) r_strbuf_append (sb, s)

extern struct r_bin_dbginfo_t r_bin_dbginfo_dex;

static bool class_names_only(RBinFile *bf) {
	return bf && bf->rbin && bf->rbin->options.classes_names_only;
}

static void free_class_tail(RVecRBinClass *classes, int kept) {
	size_t i, len = RVecRBinClass_length (classes);
	for (i = kept; i < len; i++) {
		RBinClass *cls = RVecRBinClass_at (classes, i);
		r_bin_class_fini (cls);
	}
	free (classes->_start);
	RVecRBinClass_init (classes);
}

static void detect_dex_subsystem(RBinDexObj *dex, const char *class_name) {
	if (dex->dexSubsystem || R_STR_ISEMPTY (class_name)) {
		return;
	}
	if (strstr (class_name, "wearable/view")) {
		dex->dexSubsystem = "android-wear";
	} else if (strstr (class_name, "android/view/View")) {
		dex->dexSubsystem = "android";
	}
}

static ut64 get_method_attr(ut64 MA) {
	ut64 flags = 0;
	if (MA & R_DEX_METH_PUBLIC) {
		flags |= R_BIN_ATTR_PUBLIC;
	}
	if (MA & R_DEX_METH_PRIVATE) {
		flags |= R_BIN_ATTR_PRIVATE;
	}
	if (MA & R_DEX_METH_PROTECTED) {
		flags |= R_BIN_ATTR_PROTECTED;
	}
	if (MA & R_DEX_METH_STATIC) {
		flags |= R_BIN_ATTR_STATIC;
	}
	if (MA & R_DEX_METH_FINAL) {
		flags |= R_BIN_ATTR_FINAL;
	}
	if (MA & R_DEX_METH_SYNCHRONIZED) {
		flags |= R_BIN_ATTR_SYNCHRONIZED;
	}
	if (MA & R_DEX_METH_BRIDGE) {
		flags |= R_BIN_ATTR_BRIDGE;
	}
	if (MA & R_DEX_METH_VARARGS) {
		flags |= R_BIN_ATTR_VARARGS;
	}
	if (MA & R_DEX_METH_NATIVE) {
		flags |= R_BIN_ATTR_NATIVE;
	}
	if (MA & R_DEX_METH_ABSTRACT) {
		flags |= R_BIN_ATTR_ABSTRACT;
	}
	if (MA & R_DEX_METH_STRICT) {
		flags |= R_BIN_ATTR_STRICT;
	}
	if (MA & R_DEX_METH_SYNTHETIC) {
		flags |= R_BIN_ATTR_SYNTHETIC;
	}
	if (MA & R_DEX_METH_MIRANDA) {
		flags |= R_BIN_ATTR_MIRANDA;
	}
	if (MA & R_DEX_METH_CONSTRUCTOR) {
		flags |= R_BIN_ATTR_CONSTRUCTOR;
	}
	if (MA & R_DEX_METH_DECLARED_SYNCHRONIZED) {
		flags |= R_BIN_ATTR_SYNCHRONIZED;
	}
	return flags;
}

static ut64 method_addr(RBinFile *bf, int idx) {
	RBinDexObj *dex = bf->bo->bin_obj;
	return sdb_num_getf (dex->mdb, NULL, "method.%d", idx);
}

static ut64 dex_field_offset(RBinDexObj *bin, int fid) {
	return bin->header.fields_offset + (fid * 8); // (sizeof (DexField) * fid);
}

static const char *getstr(RBinDexObj *dex, int idx) {
	ut8 buf[LEB_MAX_SIZE];
	const ut32 strings_size = dex->header.strings_size;
	if (idx < 0 || idx >= strings_size || !dex->strings) {
		return NULL;
	}
	char **cs = dex->cal_strings;
	if (cs) {
		const char *p = cs[idx];
		if (p) {
			return p;
		}
	} else {
		cs = R_NEWS0 (char *, strings_size);
		if (!cs) {
			return NULL;
		}
		dex->cal_strings = cs;
		dex->cal_strings_size = strings_size;
	}
	const ut32 string_index = dex->strings[idx];
	if (string_index >= dex->size) {
		return NULL;
	}
	RBuffer *b = dex->b;
	if (r_buf_read_at (b, string_index, buf, sizeof (buf)) != sizeof (buf)) {
		return NULL;
	}
	ut64 len;
	int uleblen = r_uleb128 (buf, sizeof (buf), &len, NULL) - buf;
	if (!uleblen || len >= dex->size) {
		return NULL;
	}
	char *ptr = r_buf_get_string (b, (ut64)string_index + uleblen);
	if (ptr) {
		if (len != r_utf8_strlen ((const ut8 *)ptr)) {
			free (ptr);
			return NULL;
		}
		cs[idx] = ptr;
		return ptr;
	}
	return NULL;
}

typedef enum {
	kAccessForClass = 0,
	kAccessForMethod = 1,
	kAccessForField = 2,
	kAccessForMAX
} AccessFor;

static char *createAccessFlagStr(ut32 flags, AccessFor forWhat) {
	#define NUM_FLAGS 18
	static const char *kAccessStrings[kAccessForMAX][NUM_FLAGS] = {
		{
			/* class, inner class */
			"public", /* 0x0001 */
			"private", /* 0x0002 */
			"protected", /* 0x0004 */
			"static", /* 0x0008 */
			"final", /* 0x0010 */
			"?", /* 0x0020 */
			"?", /* 0x0040 */
			"?", /* 0x0080 */
			"?", /* 0x0100 */
			"interface", /* 0x0200 */
			"abstract", /* 0x0400 */
			"?", /* 0x0800 */
			"synthetic", /* 0x1000 */
			"annotation", /* 0x2000 */
			"enum", /* 0x4000 */
			"?", /* 0x8000 */
			"verified", /* 0x10000 */
			"optimized", /* 0x20000 */
		},
		{
			/* method */
			"public", /* 0x0001 */
			"private", /* 0x0002 */
			"protected", /* 0x0004 */
			"static", /* 0x0008 */
			"final", /* 0x0010 */
			"synchronized", /* 0x0020 */
			"bridge", /* 0x0040 */
			"varargs", /* 0x0080 */
			"native", /* 0x0100 */
			"?", /* 0x0200 */
			"abstract", /* 0x0400 */
			"strict", /* 0x0800 */
			"synthetic", /* 0x1000 */
			"?", /* 0x2000 */
			"?", /* 0x4000 */
			"miranda", /* 0x8000 */
			"constructor", /* 0x10000 */
			"declared_synchronized", /* 0x20000 */
		},
		{
			/* field */
			"public", /* 0x0001 */
			"private", /* 0x0002 */
			"protected", /* 0x0004 */
			"static", /* 0x0008 */
			"final", /* 0x0010 */
			"?", /* 0x0020 */
			"volatile", /* 0x0040 */
			"transient", /* 0x0080 */
			"?", /* 0x0100 */
			"?", /* 0x0200 */
			"?", /* 0x0400 */
			"?", /* 0x0800 */
			"synthetic", /* 0x1000 */
			"?", /* 0x2000 */
			"enum", /* 0x4000 */
			"?", /* 0x8000 */
			"?", /* 0x10000 */
			"?", /* 0x20000 */
		},
	};
	size_t i, count = r_bits_popcount32 (flags);
	// produces a huge number????
	if (count < 1) {
		return NULL;
	}
	RStrBuf *sb = r_strbuf_new ("");
	for (i = 0; i < NUM_FLAGS; i++) {
		if (flags & 1) {
			const char *accessStr = kAccessStrings[forWhat][i];
			if (!r_strbuf_is_empty (sb)) {
				r_strbuf_append (sb, " ");
			}
			r_strbuf_append (sb, accessStr);
		}
		flags >>= 1;
	}
	return r_strbuf_drain (sb);
}

static const char *dex_type_descriptor(RBinDexObj *dex, int type_idx) {
	if (type_idx < 0 || type_idx >= dex->header.types_size) {
		return NULL;
	}
	return getstr (dex, dex->types[type_idx].descriptor_id);
}

static ut16 type_desc(RBinDexObj *bin, ut16 type_idx) {
	if (type_idx >= bin->header.types_size || type_idx >= bin->size) {
		return UT16_MAX;
	}
	return bin->types[type_idx].descriptor_id;
}

static const char *dex_get_proto(RBinDexObj *bin, int proto_id) {
	if (proto_id < 0 || proto_id >= bin->header.prototypes_size) {
		return NULL;
	}
	const ut32 protos_size = bin->header.prototypes_size;
	char **cp = bin->cal_protos;
	if (cp) {
		const char *p = cp[proto_id];
		if (p) {
			return p;
		}
	} else {
		cp = R_NEWS0 (char *, protos_size);
		if (!cp) {
			return NULL;
		}
		bin->cal_protos = cp;
		bin->cal_protos_size = protos_size;
	}
	const DexProto *p = &bin->protos[proto_id];
	ut32 params_off = p->parameters_off;
	if (params_off >= bin->size) {
		return NULL;
	}
	ut32 type_id = p->return_type_id;
	if (type_id >= bin->header.types_size) {
		return NULL;
	}
	const char *return_type = getstr (bin, bin->types[type_id].descriptor_id);
	if (!return_type) {
		return NULL;
	}
	if (!params_off) {
		cp[proto_id] = r_str_newf ("()%s", return_type);
		return cp[proto_id];
	}
	ut8 params_buf[sizeof (ut32)];
	if (params_off + sizeof (params_buf) > bin->size || params_off + sizeof (params_buf) < params_off) {
		return NULL;
	}
	if (r_buf_read_at (bin->b, params_off, params_buf, sizeof (params_buf)) != sizeof (params_buf)) {
		return NULL;
	}
	// size of the list, in 16 bit entries
	ut32 list_size = r_read_le32 (params_buf);
	ut64 params_data = params_off + sizeof (params_buf);
	ut64 typeidx_bufsize = (ut64)list_size * sizeof (ut16);
	if (typeidx_bufsize > bin->size - params_data) {
		R_LOG_DEBUG ("truncated typeidx buffer from %"PFMT64u" to %"PFMT64u,
			params_data + typeidx_bufsize, (ut64)bin->size);
		return NULL;
	}
	ut8 stack_typeidx_buf[DEX_PROTO_STACK_PARAMS * sizeof (ut16)];
	ut8 *heap_typeidx_buf = NULL;
	ut8 *typeidx_buf = stack_typeidx_buf;
	if (typeidx_bufsize > sizeof (stack_typeidx_buf)) {
		heap_typeidx_buf = malloc (typeidx_bufsize);
		if (!heap_typeidx_buf) {
			return NULL;
		}
		typeidx_buf = heap_typeidx_buf;
	}
	if (typeidx_bufsize > 0 && r_buf_read_at (bin->b, params_data, typeidx_buf, typeidx_bufsize) != typeidx_bufsize) {
		free (heap_typeidx_buf);
		return NULL;
	}
	RStrBuf *sig = r_strbuf_new ("(");
	if (!sig) {
		free (heap_typeidx_buf);
		return NULL;
	}
	ut32 i;
	for (i = 0; i < list_size; i++) {
		ut16 type_idx = r_read_le16 (typeidx_buf + (i * sizeof (ut16)));
		ut16 type_desc_id = type_desc (bin, type_idx);
		if (type_desc_id == UT16_MAX) {
			r_strbuf_append (sig, "?;");
		} else {
			const char *buff = getstr (bin, type_desc_id);
			r_strbuf_append (sig, r_str_get_fail (buff, "?;"));
		}
	}
	free (heap_typeidx_buf);
	r_strbuf_appendf (sig, ")%s", return_type);
	cp[proto_id] = r_strbuf_drain (sig);
	return cp[proto_id];
}

static ut16 dex_method_ret_slots(RBinDexObj *bin, int method_idx) {
	if (method_idx < 0 || method_idx >= (int)bin->header.method_size) {
		return 1;
	}
	RBinDexMethod *method = RVecDexMethod_at (&bin->dex_methods, method_idx);
	if (!method || method->proto_id >= bin->header.prototypes_size) {
		return 1;
	}
	const DexProto *p = &bin->protos[method->proto_id];
	if (p->return_type_id >= bin->header.types_size) {
		return 1;
	}
	const char *rt = getstr (bin, bin->types[p->return_type_id].descriptor_id);
	if (!rt) {
		return 1;
	}
	switch (rt[0]) {
	case 'V': return 0;
	case 'J': case 'D': return 2;
	default: return 1;
	}
}

static const char *dex_method_signature(RBinDexObj *bin, int method_idx) {
	if (method_idx < 0 || method_idx >= bin->header.method_size) {
		return NULL;
	}
	RBinDexMethod *method = RVecDexMethod_at (&bin->dex_methods, method_idx);
	return method? dex_get_proto (bin, method->proto_id): NULL;
}

static RList *dex_method_signature2(RBinDexObj *bin, int method_idx) {
	ut16 type_idx;
	size_t i;

	RList *params = r_list_new ();
	if (!params) {
		return NULL;
	}
	if (method_idx < 0 || method_idx >= bin->header.method_size) {
		goto out_error;
	}
	RBinDexMethod *method = RVecDexMethod_at (&bin->dex_methods, method_idx);
	if (!method) {
		goto out_error;
	}
	ut32 proto_id = method->proto_id;
	if (proto_id >= bin->header.prototypes_size) {
		goto out_error;
	}
	ut32 params_off = bin->protos[proto_id].parameters_off;
	if (params_off >= bin->size) {
		goto out_error;
	}
	if (!params_off) {
		return params;
	}
	ut32 list_size = r_buf_read_le32_at (bin->b, params_off);
	for (i = 0; i < list_size; i++) {
		ut64 of = params_off + 4 + (i * 2);
		if (of >= bin->size || of < params_off) {
			break;
		}
		type_idx = r_buf_read_le16_at (bin->b, of);
		if (type_idx >= bin->header.types_size || type_idx > bin->size) {
			break;
		}
		const char *buff = getstr (bin, bin->types[type_idx].descriptor_id);
		if (!buff) {
			break;
		}
		r_list_append (params, (void *)buff);
	}
	return params;
out_error:
	r_list_free (params);
	return NULL;
}

// https://github.com/pxb1988/dex2jar/blob/2.x/dex-reader/src/main/java/com/googlecode/d2j/reader/DexFileReader.java#L1023
static void dex_parse_debug_item(RBinFile *bf, RBinDexClass *c, int MI, int MA, int paddr, int ins_size, int insns_size, const char *class_name, int regsz, int debug_info_off, bool add_addrline) {
	RBinDexObj *dex = bf->bo->bin_obj;
	RBuffer *b = bf->buf;
	RStrBuf *sb = dex->sb;
	if (debug_info_off >= r_buf_size (b)) {
		return;
	}
	ut64 line_start;
	ut64 parameters_size;
	ut64 param_type_idx;
	ut16 argReg = regsz - ins_size;
	ut64 source_file_idx = c->source_file;
	bool keep = true;
	if (argReg > regsz) {
		return; // this return breaks tests
	}
	r_buf_seek (b, debug_info_off, R_BUF_SET);
	ut64 res;
	if (r_buf_uleb128 (b, &res) < 1) {
		return;
	}
	line_start = res;
	if (r_buf_uleb128 (b, &res) < 1) {
		return;
	}
	parameters_size = res;

	// TODO: check when we should use source_file
	// The state machine consists of five registers
	ut32 address = 0;
	ut32 line = line_start;
	struct dex_debug_local_t *debug_locals = R_NEWS0 (struct dex_debug_local_t, regsz + 1);
	if (!debug_locals) {
		return;
	}
	RList *debug_positions = r_list_newf ((RListFree)free);
	RList *emitted_debug_locals = r_list_newf ((RListFree)free);

	if (!(MA & R_DEX_METH_STATIC)) {
		debug_locals[argReg].name = "this";
		debug_locals[argReg].descriptor = r_str_newf ("%s;", class_name);
		debug_locals[argReg].startAddress = 0;
		debug_locals[argReg].signature = NULL;
		debug_locals[argReg].live = true;
		argReg++;
	}
	RList *params = dex_method_signature2 (dex, MI);

	RListIter *iter;
	const char *name;
	char *type;
	int reg;

	r_list_foreach (params, iter, type) {
		if ((argReg >= regsz) || !type || parameters_size <= 0) {
			goto beach;
		}
		if (r_buf_uleb128 (b, &res) < 1) {
			goto beach;
		}
		param_type_idx = res - 1;
		name = getstr (dex, param_type_idx);
		reg = argReg;
		switch (type[0]) {
		case 'D':
		case 'J':
			argReg += 2;
			break;
		default:
			argReg += 1;
			break;
		}
		if (R_STR_ISEMPTY (name)) {
			struct dex_debug_local_t *dl = &debug_locals[reg];
			dl->name = name;
			dl->descriptor = type;
			dl->signature = NULL;
			dl->startAddress = address;
			dl->live = true;
		}
		parameters_size--;
	}
	ut8 opcode = 0;
	if (r_buf_read (b, &opcode, 1) != 1) {
		goto beach;
	}
	while (keep) {
		switch (opcode) {
		case DBG_END_SEQUENCE:
			keep = false;
			break;
		case DBG_ADVANCE_PC:
			{
			ut64 addr_diff;
			if (r_buf_uleb128 (b, &addr_diff) < 1) {
				goto beach;
			}
			address += addr_diff;
			}
			break;
		case DBG_ADVANCE_LINE:
			{
			st64 line_diff;
			r_buf_sleb128 (b, &line_diff);
			line += line_diff;
			}
			break;
		case DBG_START_LOCAL:
			{
			ut64 register_num, name_idx, type_idx;
			if (r_buf_uleb128 (b, &register_num) < 1) {
				goto beach;
			}
			if (r_buf_uleb128 (b, &name_idx) < 1) {
				goto beach;
			}
			if (r_buf_uleb128 (b, &type_idx) < 1) {
				goto beach;
			}
			name_idx--;
			type_idx--;
			if (register_num >= regsz) {
				goto beach;
			}
			struct dex_debug_local_t *dl = &debug_locals[register_num];
			// Emit what was previously there, if anything
			// emitLocalCbIfLive
			if (dl->live) {
				struct dex_debug_local_t *local = malloc (
					sizeof (struct dex_debug_local_t));
				if (!local) {
					keep = false;
					break;
				}
				local->name = dl->name;
				local->descriptor = dl->descriptor;
				local->startAddress = dl->startAddress;
				local->signature = dl->signature;
				local->live = true;
				local->reg = register_num;
				local->endAddress = address;
				r_list_append (emitted_debug_locals, local);
			}
			dl->name = getstr (dex, name_idx);
			dl->descriptor = dex_type_descriptor (dex, type_idx);
			dl->startAddress = address;
			dl->signature = NULL;
			dl->live = true;
			//eprintf("DBG_START_LOCAL %x %x %x\n", register_num, name_idx, type_idx);
			}
			break;
		case DBG_START_LOCAL_EXTENDED:
			{
			ut64 register_num, name_idx, type_idx, sig_idx;
			if (r_buf_uleb128 (b, &register_num) < 1) {
				goto beach;
			}
			if (r_buf_uleb128 (b, &name_idx) < 1) {
				goto beach;
			}
			if (r_buf_uleb128 (b, &type_idx) < 1) {
				goto beach;
			}
			if (r_buf_uleb128 (b, &sig_idx) < 1) {
				goto beach;
			}
			sig_idx--;
			type_idx--;
			name_idx--;
			if (register_num >= regsz) {
				goto beach;
			}
			struct dex_debug_local_t *dl = &debug_locals[register_num];
			// Emit what was previously there, if anything
			// emitLocalCbIfLive
			if (dl->live) {
				struct dex_debug_local_t *local = malloc (
					sizeof (struct dex_debug_local_t));
				if (!local) {
					keep = false;
					break;
				}
				local->name = dl->name;
				local->descriptor = dl->descriptor;
				local->startAddress = dl->startAddress;
				local->signature = dl->signature;
				local->live = true;
				local->reg = register_num;
				local->endAddress = address;
				r_list_append (emitted_debug_locals, local);
			}
			dl->name = getstr (dex, name_idx);
			dl->descriptor = dex_type_descriptor (dex, type_idx);
			dl->startAddress = address;
			dl->signature = getstr (dex, sig_idx);
			dl->live = true;
			}
			break;
		case DBG_END_LOCAL:
			{
			ut64 register_num;
			if (r_buf_uleb128 (b, &register_num) < 1) {
				goto beach;
			}
			// emitLocalCbIfLive
			if (register_num >= regsz) {
				goto beach;
			}
			struct dex_debug_local_t *dl = &debug_locals[register_num];
			if (dl->live) {
				struct dex_debug_local_t *local = malloc (
					sizeof (struct dex_debug_local_t));
				if (!local) {
					keep = false;
					break;
				}
				local->name = dl->name;
				local->descriptor = dl->descriptor;
				local->startAddress = dl->startAddress;
				local->signature = dl->signature;
				local->live = true;
				local->reg = register_num;
				local->endAddress = address;
				r_list_append (emitted_debug_locals, local);
			}
			dl->live = false;
			}
			break;
		case DBG_RESTART_LOCAL:
			{
			ut64 register_num;
			if (r_buf_uleb128 (b, &register_num) < 1) {
				goto beach;
			}
			if (register_num >= regsz) {
				goto beach;
			}
			struct dex_debug_local_t *dl = &debug_locals[register_num];
			if (!dl->live) {
				dl->startAddress = address;
				dl->live = true;
			}
			}
			break;
		case DBG_SET_PROLOGUE_END:
			// ignored
			break;
		case DBG_SET_EPILOGUE_BEGIN:
			// ignored
			break;
		case DBG_SET_FILE:
			{
				ut64 res;
				if (r_buf_uleb128 (b, &res) < 1) {
					goto beach;
				}
				source_file_idx = res - 1;
			}
			break;
		default:
			if (opcode >= DBG_FIRST_SPECIAL) {
				int adjusted_opcode = opcode - DBG_FIRST_SPECIAL;
				int addr_delta = adjusted_opcode / DBG_LINE_RANGE;
				int line_delta = DBG_LINE_BASE + (adjusted_opcode % DBG_LINE_RANGE);
				address += addr_delta;
				line += line_delta;
				if (sb) {
					struct dex_debug_position_t *position =
						R_NEW0 (struct dex_debug_position_t);
					position->source_file_idx = source_file_idx;
					position->address = address;
					position->line = line;
					r_list_append (debug_positions, position);
				}
				if (add_addrline) {
					bf->addrline.al_add (&bf->addrline, address + paddr, getstr (dex, source_file_idx), NULL, line, 0);
				}
			} else {
				R_LOG_ERROR ("unknown dex debug opcode: 0x%02x", opcode);
			}
			break;
		}
		if (r_buf_read (b, &opcode, 1) != 1) {
			break;
		}
	}

	if (!sb) {
		goto beach;
	}

	RListIter *iter2;
	struct dex_debug_position_t *position;

	ps ("      positions     :\n");
	r_list_foreach (debug_positions, iter2, position) {
		pf ("        0x%04"PFMT64x" line=%"PFMT64u"\n", position->address, position->line);
	}

	ps ("      locals        :\n");

	RListIter *iter3;
	struct dex_debug_local_t *local;
	r_list_foreach (emitted_debug_locals, iter3, local) {
		if (local->signature) {
			pf ("        0x%04x - 0x%04x reg=%d %s %s %s\n",
				local->startAddress, local->endAddress,
				local->reg, local->name, local->descriptor,
				local->signature);
		} else {
			pf ("        0x%04x - 0x%04x reg=%d %s %s\n",
				local->startAddress, local->endAddress,
				local->reg, local->name, local->descriptor);
		}
	}

	for (reg = 0; reg < regsz; reg++) {
		struct dex_debug_local_t *dl = &debug_locals[reg];
		if (!dl->name || !dl->live) {
			continue;
		}
		if (dl->signature) {
			pf ("        0x%04x - 0x%04x reg=%d %s %s %s\n",
				dl->startAddress, insns_size, reg,
				dl->name, dl->descriptor, dl->signature);
		} else {
			pf ("        0x%04x - 0x%04x reg=%d %s %s\n",
				dl->startAddress, insns_size, reg,
				dl->name, dl->descriptor);
		}
	}
beach:
	r_list_free (debug_positions);
	r_list_free (emitted_debug_locals);
	r_list_free (params);
	free (debug_locals);
}

static Sdb *get_sdb(RBinFile *bf) {
	R_RETURN_VAL_IF_FAIL (bf && bf->bo, NULL);
	RBinObject *o = bf->bo;
	R_RETURN_VAL_IF_FAIL (o && o->bin_obj, NULL);
	struct r_bin_dex_obj_t *bin = (struct r_bin_dex_obj_t *) o->bin_obj;
	return bin->kv;
}

static bool load(RBinFile *bf, RBuffer *buf, ut64 loadaddr) {
	bf->bo->bin_obj = r_bin_dex_new_buf (buf, bf->rbin->options.verbose);
	return bf->bo->bin_obj != NULL;
}

static bool check(RBinFile *bf, RBuffer *buf) {
	ut8 tmp[8];
	int r = r_buf_read_at (buf, 0, tmp, sizeof (tmp));
	if (r < sizeof (tmp)) {
		return false;
	}
	// Non-extended opcode dex file
	if (!memcmp (tmp, "dex\n035\0", 8)) {
		return true;
	}
	// Extended (jumnbo) opcode dex file, ICS+ only (sdk level 14+)
	if (!memcmp (tmp, "dex\n036\0", 8)) {
		return true;
	}
	// Two new opcodes: invoke-polymorphic and invoke-custom (sdk level 26+)
	if (!memcmp (tmp, "dex\n038\0", 8)) {
		return true;
	}
	// M3 (Nov-Dec 07)
	if (!memcmp (tmp, "dex\n009\0", 8)) {
		return true;
	}
	// M5 (Feb-Mar 08)
	if (!memcmp (tmp, "dex\n009\0", 8)) {
		return true;
	}
	// Default fall through, should still be a dex file
	if (!memcmp (tmp, "dex\n", 4)) {
		return true;
	}
	return false;
}

static RBinInfo *info(RBinFile *bf) {
	RBinHash *h;
	RBinInfo *ret = R_NEW0 (RBinInfo);
	RBinDexObj *dex = bf->bo->bin_obj;
	ret->file = bf->file? strdup (bf->file): NULL;
	ret->type = strdup ("DEX CLASS");
	ret->has_va = true;
	ret->has_lit = true;
	ret->bclass = r_bin_dex_get_version (dex);
	ret->rclass = strdup ("class");
	ret->os = strdup ("linux");
	ret->subsystem = strdup (r_str_get_fail (dex->dexSubsystem, "java"));
	ret->machine = strdup ("Dalvik VM");
	h = &ret->sum[0];
	h->type = "sha1";
	h->len = 20;
	h->addr = 12;
	h->from = 12;
	h->to = r_buf_size (bf->buf) - 32;
	r_buf_read_at (bf->buf, 12, h->buf, 20);
	h = &ret->sum[1];
	h->type = "adler32";
	h->len = 4;
	h->addr = 8;
	h->from = 12;
	h->to = r_buf_size (bf->buf) - h->from;
	r_buf_read_at (bf->buf, 8, h->buf, 12);
	h = &ret->sum[2];
	h->type = 0;
	r_buf_read_at (bf->buf, 8, h->buf, 4);
	// this is slow but computed once, so we can use r_buf_data or just do r_buf_read()
	// not sure if we want to expose the computed checksum everytime we open the file
	// also the checksum is computed by other methods in RBin, so maybe good to generalize
	{
		ut32 fc = r_buf_read_le32_at (bf->buf, 8);
		ut64 tmpsz;
		const ut8 *tmp = r_buf_data (bf->buf, &tmpsz);
		ut32 cc = __adler32 (tmp + 12, tmpsz - 12);
		if (fc != cc) {
			eprintf ("# adler32 checksum doesn't match. Type this to fix it:\n");
			eprintf ("wx `ph sha1 $s-32 @32` @12 ; wx `ph adler32 $s-12 @12` @8\n");
		}
	}
	ret->arch = strdup ("dalvik");
	ret->lang = "dalvik";
	ret->bits = 32;
	ret->big_endian = 0;
	ret->dbg_info = 0; //1 | 4 | 8; /* Stripped | LineNums | Syms */
	return ret;
}

static RVecRBinString *strings(RBinFile *bf) {
	R_RETURN_VAL_IF_FAIL (bf && bf->bo, NULL);
	RVecRBinString *ret = NULL;
	int i;
	ut64 len;
	ut8 buf[LEB_MAX_SIZE];
	ut64 off;
	struct r_bin_dex_obj_t *bin = (struct r_bin_dex_obj_t *)bf->bo->bin_obj;
	if (!bin || !bin->strings) {
		return NULL;
	}
	if (!(ret = RVecRBinString_new ())) {
		return NULL;
	}
	for (i = 0; i < bin->header.strings_size; i++) {
		if (bin->strings[i] > bin->size || bin->strings[i] + 6 > bin->size) {
			goto out_error;
		}
		r_buf_read_at (bin->b, bin->strings[i], buf, sizeof (buf));
		r_uleb128 (buf, sizeof (buf), &len, NULL);

		if (len > 5 && len < R_BIN_SIZEOF_STRINGS) {
			RBinString bs = { 0 };
			bs.string = malloc (len + 1);
			if (!bs.string) {
				goto out_error;
			}
			off = bin->strings[i] + r_uleb128_len (buf, sizeof (buf));
			if (off + len >= bin->size || off + len < len) {
				free (bs.string);
				goto out_error;
			}
			r_buf_read_at (bin->b, off, (ut8*)bs.string, len);
			bs.string[len] = 0;
			if ((bs.string[0] == 'L' && strchr (bs.string, '/')) || !strncmp (bs.string, "[L", 2)) {
				free (bs.string);
				continue;
			}
			bs.paddr = bin->strings[i];
			bs.vaddr = bs.paddr;
			bs.size = len;
			bs.length = len;
			bs.ordinal = i + 1;
			RBinString *dst = RVecRBinString_emplace_back (ret);
			if (!dst) {
				r_bin_string_fini (&bs);
				goto out_error;
			}
			*dst = bs;
		}
	}
	return ret;
out_error:
	RVecRBinString_free (ret);
	return NULL;
}

static const char *dex_method_name(RBinDexObj *bin, int idx) {
	if (idx < 0 || idx >= bin->header.method_size) {
		return NULL;
	}
	RBinDexMethod *method = RVecDexMethod_at (&bin->dex_methods, idx);
	if (!method) {
		return NULL;
	}
	ut16 cid = method->class_id;
	if (cid >= bin->header.strings_size) {
		return NULL;
	}
	int tid = method->name_id;
	if (tid < 0 || tid >= bin->header.strings_size) {
		return NULL;
	}
	return getstr (bin, tid);
}

static char *simplify(char *s) {
	char *p = (char *)r_str_rchr (s, NULL, '/');
	if (p) {
		r_str_cpy (s, p + 1);
	}
	r_str_replace_char (s, '/', '.');
	return s;
}

static char *dex_class_name_byid(RBinDexObj *dex, int cid) {
	R_RETURN_VAL_IF_FAIL (dex && dex->types, NULL);
	if (cid < 0 || cid >= dex->header.types_size) {
		return NULL;
	}
	int tid = dex->types[cid].descriptor_id;
	const char *s = getstr (dex, tid);
	if (s) {
		char *r = strdup (s);
		if (dex->simplifiedDemangling) {
			simplify (r);
		}
		return r;
	}
	return NULL;
}

static char *dex_class_name(RBinDexObj *dex, RBinDexClass *c) {
	char *s = dex_class_name_byid (dex, c->class_id);
	if (dex->simplifiedDemangling) {
		simplify (s);
		if (*s == 'L') {
			r_str_cpy (s, s + 1);
		}
	}
	return s;
}

static char *dex_field_name(RBinDexObj *dex, int fid) {
	R_RETURN_VAL_IF_FAIL (dex && dex->fields, NULL);

	if (fid < 0 || fid >= dex->header.fields_size) {
		return NULL;
	}
	const DexField *f = &dex->fields[fid];
	const ut16 cid = f->class_id;
	if (cid >= dex->header.types_size) {
		return NULL;
	}
	const ut16 type_id = f->type_id;
	if (type_id >= dex->header.types_size) {
		return NULL;
	}
	const int tid = f->name_id;
	const ut32 cid_did = dex->types[cid].descriptor_id;
	const ut32 type_did = dex->types[type_id].descriptor_id;
	const char *a = getstr (dex, cid_did);
	const char *b = getstr (dex, tid);
	const char *c = getstr (dex, type_did);
	if (dex->simplifiedDemangling) {
		if (a && b && c) {
			char *_a = simplify (strdup (a));
			char *_b = simplify (strdup (b));
			char *_c = simplify (strdup (c));
			char *str = r_str_newf ("(%s) %s.%s", _c, _a, _b);
			free (_a);
			free (_b);
			free (_c);
			return str;
		}
		return r_str_newf ("(%d) %d.%d", type_did, tid, cid_did);
	}
	return (a && b && c)
		? r_str_newf ("%s->%s %s", a, b, c)
		: r_str_newf ("%d->%d %d", cid_did, tid, type_did);
}

static char *dex_method_fullname(RBinDexObj *dex, int method_idx) {
	R_RETURN_VAL_IF_FAIL (dex && dex->types, NULL);
	if (method_idx < 0 || method_idx >= dex->header.method_size) {
		return NULL;
	}
	RBinDexMethod *method = RVecDexMethod_at (&dex->dex_methods, method_idx);
	if (!method) {
		return NULL;
	}
	const ut16 cid = method->class_id;
	if (cid >= dex->header.types_size || cid >= dex->header.strings_size) {
		return NULL;
	}
	const int name_id = method->name_id;
	if (name_id < 0 || name_id >= dex->header.strings_size) {
		return NULL;
	}
	const char *name = getstr (dex, name_id);
	if (!name) {
		return NULL;
	}
	char *flagname = NULL;

	char *class_name = dex_class_name_byid (dex, cid);
	if (!class_name) {
		class_name = strdup ("???");
	}
	r_str_replace_char (class_name, ';', 0);
	const char *signature = dex_get_proto (dex, method->proto_id);
	if (signature) {
		flagname = r_str_newf ("%s.%s%s", class_name, name, signature);
	} else {
		flagname = r_str_newf ("%s.%s%s", class_name, name, "???");
	}
	free (class_name);
	if (flagname && dex->simplifiedDemangling) {
		char *p = strchr (flagname, '(');
		if (p) {
			*p = 0;
			char *q = strchr (p + 1, ')');
			if (q) {
				simplify (q + 1);
				r_str_cpy (p, q + 1);
			}
			simplify (flagname);
		}
	}
	return flagname;
}

static ut64 dex_get_type_offset(RBinFile *bf, int type_idx) {
	RBinDexObj *bin = (RBinDexObj*) bf->bo->bin_obj;
	if (!bin || !bin->types) {
		return 0;
	}
	if (type_idx < 0 || type_idx >= bin->header.types_size) {
		return 0;
	}
	return bin->header.types_offset + type_idx * 0x04; //&bin->types[type_idx];
}

static const char *dex_class_super_name(RBinDexObj *bin, RBinDexClass *c) {
	R_RETURN_VAL_IF_FAIL (bin && bin->types && c, NULL);
	int cid = c->super_class;
	if (cid < 0 || cid >= bin->header.types_size) {
		return NULL;
	}
	int tid = bin->types[cid].descriptor_id;
	return getstr (bin, tid);
}

static ut64 peek_uleb(RBuffer *b, bool *err, size_t *nn) {
	ut64 n = UT64_MAX;
	int len = r_buf_uleb128 (b, &n);
	if (len < 1) {
		if (err) {
			*err |= true;
		}
	} else {
		*nn += len;
	}
	return n;
}

static void parse_dex_class_fields(RBinFile *bf, RBinDexClass *c, RBinClass *cls, int *sym_count, ut64 fields_count, bool is_sfield) {
	RBinDexObj *dex = bf->bo->bin_obj;
	RBuffer *b = bf->buf;
	RStrBuf *sb = dex->sb;
	const ut64 fields_offset = dex->header.fields_offset;
	ut64 lastIndex = 0;
	ut8 ff[sizeof (DexField)] = {0};
	int total, tid;
	DexField field;
	size_t i, skip = 0;

	for (i = 0; i < fields_count; i++) {
		bool err = false;
		ut64 fieldIndex = peek_uleb (b, &err, &skip);
		ut64 accessFlags = peek_uleb (b, &err, &skip);
		if (err) {
			break;
		}
		fieldIndex += lastIndex;
		total = fields_offset + (sizeof (DexField) * fieldIndex);
		if (total >= dex->size || total < fields_offset) {
			break;
		}
		if (r_buf_read_at (b, total, ff, sizeof (DexField)) != sizeof (DexField)) {
			break;
		}
		field.class_id = r_read_le16 (ff);
		field.type_id = r_read_le16 (ff + 2);
		field.name_id = r_read_le32 (ff + 4);
		const char *fieldName = getstr (dex, field.name_id);
		if (field.type_id >= dex->header.types_size) {
			break;
		}
		tid = dex->types[field.type_id].descriptor_id;
		const char *type_str = getstr (dex, tid);
		const char *cls_name = r_bin_name_tostring (cls->name);
		RBinSymbol sym = {0};
		const char *ftype = is_sfield ? "sfield": "ifield";
		char *s = r_str_newf ("%s.%s_%s:%s", cls_name, ftype, fieldName, type_str);
		sym.name = r_bin_name_new (s);
		sym.type = is_sfield? "STATIC": "FIELD";
		s = r_str_replace (s, "method.", "", 0);
		r_str_replace_char (s, ';', 0);
		r_bin_name_filtered (sym.name, s);
		free (s);
		sym.paddr = total;
		sym.vaddr = sym.paddr; //  + baddr;
		sym.attr.lang = R_BIN_LANG_JAVA;
		sym.ordinal = (*sym_count)++;

		if (sb) {
			char *accessStr = createAccessFlagStr (accessFlags, kAccessForField);
			pf ("    #%u              : (in %s;)\n", (unsigned int)i, cls_name);
			pf ("      name          : '%s'\n", fieldName);
			pf ("      type          : '%s'\n", type_str);
			pf ("      access        : 0x%04x (%s)\n",
					 (ut32)accessFlags, r_str_get (accessStr));
			free (accessStr);
		}
		RVecRBinSymbol_push_back (&dex->symbols_vec, &sym);

		RBinField *field = RVecRBinField_emplace_back (&cls->fields);
		field->vaddr = field->paddr = sym.paddr;
		field->name = r_bin_name_clone (sym.name);
		field->attr.flags = get_method_attr (accessFlags);
		lastIndex = fieldIndex;
	}
}

// TODO: refactor this method
static void parse_dex_class_method(RBinFile *bf, RBinDexClass *c, RBinClass *cls, int *sym_count, ut64 DM, int *methods, bool is_direct) {
	RBinDexObj *dex = bf->bo->bin_obj;
	RBuffer *b = bf->buf;
	RStrBuf *sb = dex->sb;
	const bool bin_dbginfo = bf->rbin->want_dbginfo;
	const bool dump_dbginfo = bin_dbginfo && sb;
	ut64 omi = 0;
	ut16 regsz = 0, ins_size = 0, outs_size = 0, tries_size = 0;
	ut16 insn_count = 0;
	ut32 start_addr;
	ut32 debug_info_off = 0, insns_size = 0;

	size_t skip = 0;
	const ut64 bufsz = r_buf_size (b);
	ut64 encoded_method_addr;
	bool err = false;
	ut64 MI, MA, MC;
	const char *cls_name = r_bin_name_tostring (cls->name);
	ut64 i;
	for (i = 0; i < DM; i++) {
		err = false;
		skip = 0;
		// Needed because theres another rbufseek call inside this loop. must be fixed
		encoded_method_addr = r_buf_tell (b);
		MI = peek_uleb (b, &err, &skip);
		if (err) {
			R_LOG_ERROR ("peek-uleb0");
			break;
		}
		MI += omi;
		omi = MI;
		MA = peek_uleb (b, &err, &skip);
		if (err) {
			R_LOG_ERROR ("peek-uleb1");
			break;
		}
		MC = peek_uleb (b, &err, &skip);
		if (err) {
			R_LOG_ERROR ("peek-uleb2");
			break;
		}
		// TODO: MOVE CHECKS OUTSIDE!
		if (MI < dex->header.method_size) {
			if (methods) {
				methods[MI] = 1;
			}
		}
		const char *method_name = dex_method_name (dex, MI);
		if (!method_name) {
			// method_name = strdup ("unknown");
			continue;
		}
		const char *signature = dex_method_signature (dex, MI);
		char *flag_name = r_str_newf ("%s.method.%s%s", cls_name, method_name, signature);
		if (!flag_name || !*flag_name) {
			R_FREE (flag_name);
			continue;
		}
		// TODO: check size
		// ut64 prolog_size = 2 + 2 + 2 + 2 + 4 + 4;
		ut64 t = 0;
		if (MC > 0) {
			if (MC + 16 >= dex->size || MC + 16 < MC) {
				R_FREE (flag_name);
				continue;
			}
			if (bufsz < MC || bufsz < MC + 16) {
				R_FREE (flag_name);
				continue;
			}
			regsz = r_buf_read_le16_at (b, MC);
			if (regsz == UT16_MAX) {
				R_FREE (flag_name);
				break;
			}
			ins_size = r_buf_read_le16_at (b, MC + 2);
			if (ins_size == UT16_MAX) {
				R_FREE (flag_name);
				break;
			}
			outs_size = r_buf_read_le16_at (b, MC + 4);
			tries_size = r_buf_read_le16_at (b, MC + 6);
			if (tries_size == UT16_MAX) {
				R_FREE (flag_name);
				break;
			}
			debug_info_off = r_buf_read_le32_at (b, MC + 8);
			insns_size = r_buf_read_le32_at (b, MC + 12);
			int padd = 0;
			if (tries_size > 0 && insns_size % 2) {
				padd = 2;
			}
			t = 16 + 2 * insns_size + padd;
		}
		if (sb) {
			char* accessStr = createAccessFlagStr (MA, kAccessForMethod);
			pf ("    #%" PFMT64d "              : (in %s;)\n", i, cls_name);
			pf ("      name          : '%s'\n", method_name);
			pf ("      type          : '%s'\n", signature);
			pf ("      access        : 0x%04x (%s)\n", (ut32)MA, accessStr);
			free (accessStr);
		}

		if (MC > 0) {
			if (sb) {
				ps ("      code          -\n");
				pf ("      registers     : %d\n", regsz);
				pf ("      ins           : %d\n", ins_size);
				pf ("      outs          : %d\n", outs_size);
				pf ("      insns size    : %d 16-bit code units\n", insns_size);
				if (tries_size > 0) {
					pf ("      catches       : %d\n", tries_size);
				} else {
					ps ("      catches       : (none)\n");
				}
			}
			if (tries_size > 0) {
				int j, m = 0;
				ut64 offorig = r_buf_tell (b);
				for (j = 0; j < tries_size && dex->trycatch_budget > 0; j++) {
					dex->trycatch_budget--; // a catch-all only try adds a region too
					ut64 offset = MC + t + j * 8;
					if (offset >= dex->size || offset < MC) {
						break;
					}
					if (bufsz < offset || bufsz < offset + 8) {
						break;
					}
					start_addr = r_buf_read_le32_at (b, offset);
					insn_count = r_buf_read_le16_at (b, offset + 4);
					ut64 handler_off = r_buf_read_le16_at (b, offset + 6);
					ut64 method_offset = MC + 16;
					ut64 try_from = ((ut64)start_addr * 2) + method_offset;
					ut64 try_to = try_from + (insn_count * 2);
					bool valid_range = start_addr <= insns_size && insn_count <= insns_size - start_addr;
					if (sb) {
						pf ("        0x%04x - 0x%04x\n", start_addr, (start_addr + insn_count));
					}

					ut64 handlers_base = MC + t + ((ut64)tries_size * 8);
					ut64 off = handlers_base + handler_off;
					if (handlers_base < MC || off < handlers_base || off >= dex->size) {
						break;
					}
					st64 size;
					if (r_buf_seek (b, off, R_BUF_SET) == -1) {
						break;
					}
					int r = r_buf_sleb128 (b, &size);
					if (r <= 0) {
						break;
					}
					bool catch_all = size <= 0;
					if (catch_all) {
						size = -size;
					}

					for (m = 0; m < size && dex->trycatch_budget > 0; m++) {
						dex->trycatch_budget--;
						ut64 handler_type;
						r = r_buf_uleb128 (b, &handler_type);
						if (r <= 0) {
							break;
						}
						ut64 handler_addr;
						r = r_buf_uleb128 (b, &handler_addr);
						if (r <= 0) {
							break;
						}
						if (valid_range && handler_addr < insns_size) {
							ut64 handler = method_offset + (handler_addr * 2);
							RBinTrycatch *tc = r_bin_trycatch_add (&dex->trycatch,
								method_offset, try_from, try_to, handler, 0);
							if (tc) {
								tc->kind = R_BIN_TRYCATCH_CATCH;
								tc->type_filter = handler_type;
								if (handler_type < dex->header.types_size) {
									const char *tn = getstr (dex, dex->types[handler_type].descriptor_id);
									if (tn) {
										tc->type = strdup (tn);
										dex->trycatch_budget -= R_MIN (dex->trycatch_budget, strlen (tn));
									}
								}
							}
						}
						if (sb) {
							if (handler_type < dex->header.types_size) {
								const char *s = getstr (dex, dex->types[handler_type].descriptor_id);
								pf ("          %s -> 0x%04"PFMT64x"\n", s, handler_addr);
							} else {
								pf ("          (error) -> 0x%04"PFMT64x"\n", handler_addr);
							}
						}
					}
					if (!dex->trycatch_budget) {
						break;
					}
					if (catch_all) {
						ut64 handler_addr;
						r = r_buf_uleb128 (b, &handler_addr);
						if (r <= 0) {
							break;
						}
						if (valid_range && handler_addr < insns_size) {
							ut64 handler = method_offset + (handler_addr * 2);
							RBinTrycatch *tc = r_bin_trycatch_add (&dex->trycatch,
								method_offset, try_from, try_to, handler, 0);
							if (tc) {
								tc->kind = R_BIN_TRYCATCH_CATCH;
								tc->catch_all = true;
							}
						}
						if (sb) {
							pf ("          <any> -> 0x%04"PFMT64x"\n", handler_addr);
						}
					}
				}
				r_buf_seek (b, offorig, R_BUF_SET);
			}
		} else if (sb) {
			ps ("      code          : (none)\n");
		}
		if (*flag_name) {
			RBinSymbol symb = {0};
			RBinSymbol *sym = &symb;
			sym->name = r_bin_name_new (flag_name);
			R_FREE (flag_name);
			// is_direct is no longer used
			// if method has code *addr points to code
			// otherwise it points to the encoded method
			if (MC > 0) {
				sym->type = R_BIN_TYPE_FUNC_STR;
				sym->paddr = MC;// + 0x10;
				if (ins_size <= regsz) {
					sym->arg_first = regsz - ins_size;
					sym->arg_count = ins_size;
					sym->arg_prefix = "v";
					sym->ret_count = dex_method_ret_slots (dex, MI);
				}
			} else {
				sym->type = R_BIN_TYPE_METH_STR;
				sym->paddr = encoded_method_addr;
			}
			sym->vaddr = sym->paddr;
			// sym->vaddr += bf->bo->baddr;
			sym->attr.lang = R_BIN_LANG_JAVA;
			sym->bind = ((MA & 1) == 1) ? R_BIN_BIND_GLOBAL_STR : R_BIN_BIND_LOCAL_STR;
			sym->attr.flags = get_method_attr (MA);
			sym->ordinal = (*sym_count)++;
			if (MC > 0) {
				if (bufsz < MC || bufsz < MC + 16) {
					r_bin_symbol_fini (sym);
					continue;
				}
				ut16 tries_size = r_buf_read_le16_at (b, MC + 6);
				ut32 insns_size = r_buf_read_le32_at (b, MC + 12);
				ut64 prolog_size = 2 + 2 + 2 + 2 + 4 + 4;
				if (tries_size > 0) {
					//prolog_size += 2 + 8*tries_size; // we need to parse all so the catch info...
				}
				// TODO: prolog_size
				sym->paddr = MC + prolog_size;// + 0x10;
				sym->vaddr = sym->paddr; //  + baddr;
				sym->hsize = prolog_size;
				//if (is_direct) {
				sym->attr.size = insns_size * 2;
				//}
				//eprintf("%s (0x%x-0x%x) size=%d\nregsz=%d\ninsns_size=%d\nouts_size=%d\ntries_size=%d\ninsns_size=%d\n", flag_name, sym->vaddr, sym->vaddr+sym->size, prolog_size, regsz, ins_size, outs_size, tries_size, insns_size);
				RVecRBinSymbol_push_back (&dex->symbols_vec, sym);
				sym = RVecRBinSymbol_last (&dex->symbols_vec);
				// XXX keep class method vaddr consistent with symbol
				RBinSymbol *method = RVecRBinSymbol_emplace_back (&cls->methods);
				r_bin_symbol_copy (method, sym);
				method->paddr = method->vaddr;

				if (dex->code_from == UT64_MAX || dex->code_from > sym->paddr) {
					dex->code_from = sym->paddr;
				}
				ut64 code_end = sym->paddr + sym->attr.size;
				if (code_end > sym->paddr && dex->code_to < code_end) {
					dex->code_to = code_end;
				}

				if (!dex->mdb) {
					dex->mdb = sdb_new0 ();
				}
				sdb_num_setf (dex->mdb, sym->paddr, 0, "method.%"PFMT64u, MI);
				// -----------------
				// WORK IN PROGRESS
				// -----------------
#if 0
				if (0) {
					if (MA & 0x10000) { //ACC_CONSTRUCTOR
						if (!cdb) {
							cdb = sdb_new0 ();
						}
						sdb_num_setf (cdb, sym->paddr, 0, "%d", c->class_id);
					}
				}
#endif
			} else {
				sym->attr.size = 0;
				RVecRBinSymbol_push_back (&dex->symbols_vec, sym);
				sym = RVecRBinSymbol_last (&dex->symbols_vec);
				sym->attr.lang = R_BIN_LANG_JAVA;
				RBinSymbol *method = RVecRBinSymbol_emplace_back (&cls->methods);
				r_bin_symbol_copy (method, sym);
				method->paddr = method->vaddr;
			}
			if (MC > 0 && debug_info_off > 0 && dex->header.data_offset < debug_info_off &&
				debug_info_off < dex->header.data_offset + dex->header.data_size) {
				if (dump_dbginfo) {
					ut64 addr = r_buf_tell (b);
					dex_parse_debug_item (bf, c, MI, MA, sym->paddr, ins_size,
							insns_size, cls_name, regsz, debug_info_off, false);
					r_buf_seek (b, addr, R_BUF_SET);
				}
			} else if (MC > 0 && sb) {
				ps ("      positions     :\n");
				ps ("      locals        :\n");
			}
		} else {
			R_FREE (flag_name);
		}
	}
}

static void parse_class(RBinFile *bf, RBinDexClass *c, int class_index, int *methods, int *sym_count) {
	R_RETURN_IF_FAIL (bf && bf->bo && c);

	RBinDexObj *dex = bf->bo->bin_obj;
	RBuffer *b = bf->buf;
	const DexHeader *hdr = &dex->header;
	RStrBuf *sb = dex->sb;
	const bool names_only = class_names_only (bf);
	int z;
	RBinClass clz = {0};
	RBinClass *cls = &clz;
	cls->attr.lang = R_BIN_LANG_JAVA;
	cls->origin = R_BIN_CLASS_ORIGIN_BIN;
	char *cls_name = dex_class_name (dex, c);
	if (!cls_name) {
		goto beach;
	}
	detect_dex_subsystem (dex, cls_name);
	r_str_replace_char (cls_name, ';', 0);
	cls->name = r_bin_name_new (cls_name);
	cls->index = class_index;
	cls->addr = hdr->class_offset + (class_index * DEX_CLASS_SIZE);
	const char *super = dex_class_super_name (dex, c);
	if (super) {
		cls->super = r_list_newf ((void*)r_bin_name_free);
		r_list_append (cls->super, r_bin_name_new (super));
	}
	RVecRBinSymbol_init (&cls->methods);
	RVecRBinField_init (&cls->fields);
	cls->visibility_str = createAccessFlagStr (c->access_flags, kAccessForClass);
	RVecRBinClass_push_back (&dex->classes_vec, cls);
	cls = RVecRBinClass_last (&dex->classes_vec);
	if (sb) {
		pf ("  Class descriptor  : '%s;'\n", cls_name);
		pf ("  Access flags      : 0x%04x (%s)\n", c->access_flags, r_str_get (cls->visibility_str));
		if (cls->super) {
			RBinName *bn;
			RListIter *iter;
			ps ("  Superclass        : '");
			r_list_foreach (cls->super, iter, bn) {
				pf ("%s%s", iter->n? ",": "", r_bin_name_tostring (bn));
			}
			ps ("'\n");
		}
		ps ("  Interfaces        -\n");
	}
	if (names_only) {
		goto beach;
	}

	const ut32 ifoff = c->interfaces_offset;
	if (ifoff > 0 && hdr->data_offset < ifoff && ifoff < hdr->data_offset + hdr->data_size) {
		int types_list_size = r_buf_read_le32_at (b, ifoff);
		if (types_list_size < 0 || types_list_size >= hdr->types_size) {
			goto beach;
		}
		if (sb) {
			for (z = 0; z < types_list_size; z++) {
				ut32 off = ifoff + 4 + (z * 2);
				int t = r_buf_read_le16_at (b, off);
				if (t > 0 && t < hdr->types_size) {
					int tid = dex->types[t].descriptor_id;
					const char *cn = getstr (dex, tid);
					pf ("    #%d              : '%s'\n", z, cn);
				}
			}
		}
	}
	// TODO: this is quite ugly
	if (!c->class_data_offset) {
		if (sb) {
			ps ("  Static fields     -\n"
				"  Instance fields   -\n"
				"  Direct methods    -\n"
				"  Virtual methods   -\n");
		}
	} else {
		// class_data_offset => [class_offset, class_defs_off+class_defs_size*32]
		if (hdr->class_offset > c->class_data_offset ||
		    c->class_data_offset < hdr->class_offset + hdr->class_size * DEX_CLASS_SIZE) {
			goto beach;
		}

		RBinDexClassData *dc = R_NEW0 (RBinDexClassData);

		bool err = false;
		size_t skip = 0;
		r_buf_seek (b, c->class_data_offset, R_BUF_SET);
		dc->static_fields_size   = peek_uleb (b, &err, &skip);
		dc->instance_fields_size = peek_uleb (b, &err, &skip);
		dc->direct_methods_size  = peek_uleb (b, &err, &skip);
		dc->virtual_methods_size = peek_uleb (b, &err, &skip);
		if (err) {
			free (dc);
			goto beach;
		}
		free (c->class_data);
		c->class_data = dc;

		if (sb) {
			ps ("  Static fields     -\n");
		}
		parse_dex_class_fields (bf, c, cls, sym_count, dc->static_fields_size, true);

		if (sb) {
			ps ("  Instance fields   -\n");
		}
		parse_dex_class_fields (bf, c, cls, sym_count, dc->instance_fields_size, false);

		if (sb) {
			ps ("  Direct methods    -\n");
		}
		parse_dex_class_method (bf, c, cls, sym_count,
			dc->direct_methods_size, methods, true);

		if (sb) {
			ps ("  Virtual methods   -\n");
		}
		parse_dex_class_method (bf, c, cls, sym_count,
			dc->virtual_methods_size, methods, false);
	}

	if (sb) {
		const char *source_file = getstr (dex, c->source_file);
		if (source_file) {
			pf ("  source_file_idx   : %d (%s)\n\n", c->source_file, source_file);
		} else {
			pf ("  source_file_idx   : %d (unknown)\n\n", c->source_file);
		}
	}
beach:
	R_FREE (cls_name);
}

static bool dex_loadcode(RBinFile *bf) {
	R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, false);
	RBinDexObj *dex = bf->bo->bin_obj;
	RStrBuf *sb = dex->sb;
	dex->verbose = true;
	size_t i;
	int *methods = NULL;
	size_t methods_size = 0;
	int sym_count = 0;
	const int limit = bf->rbin->options.limit;
	const bool names_only = class_names_only (bf);
	// doublecheck??
	if (!RVecRBinSymbol_empty (&dex->symbols_vec)) {
		return false;
	}
	if (!dex->version) {
		dex->version = r_bin_dex_get_version (dex);
	}
	dex->code_from = UT64_MAX;
	dex->code_to = 0;
	RVecRBinImport_fini (&dex->imports_vec);
	RVecRBinClass_fini (&dex->classes_vec);
	if (!dex->lines_list) {
		dex->lines_list = r_list_newf ((RListFree)free);
		if (!dex->lines_list) {
			return false;
		}
	}

	// r_bin_dex_new_buf clamps every header size below dex->size at parse time
	dex->dexSubsystem = NULL;

	ut64 want = R_MIN ((ut64)dex->header.method_size, (ut64)dex->size / 8);
	if (want > 0) {
		RVecRBinSymbol_reserve (&dex->symbols_vec, want);
	}

	dex->trycatch_loaded = true;
	// try items can share a handler list and methods can share a code item, so bound the whole expansion
	dex->trycatch_budget = dex->size;
	if (dex->classes) {
		if (!names_only) {
			ut64 amount = sizeof (int) * dex->header.method_size;
			if (amount > UT32_MAX || amount < dex->header.method_size) {
				return false;
			}
			methods_size = amount + 1;
			methods = calloc (1, methods_size);
		}
		for (i = 0; i < dex->header.class_size; i++) {
			struct dex_class_t *c = &dex->classes[i];
			if (sb) {
				pf ("Class #%u            -\n", (unsigned int)i);
			}
			parse_class (bf, c, i, methods, &sym_count);
		}
		if (!dex->trycatch_budget) {
			R_LOG_WARN ("Too many catch handlers, ignoring the remaining ones");
		}
	}
	if (methods) {
		int import_count = 0;
		int sym_count = RVecRBinSymbol_length (&dex->symbols_vec);
		const ut32 method_size = dex->header.method_size;
		const ut32 types_size = dex->header.types_size;
		RBitset *has_code = r_bitset_new ();
		if (!has_code) {
			free (methods);
			return false;
		}
		if (dex->classes) {
			const ut32 class_size = dex->header.class_size;
			ut32 j;
			for (j = 0; j < class_size; j++) {
				r_bitset_set (has_code, dex->classes[j].class_id);
			}
		}
		for (i = 0; i < method_size; i++) {
			if (limit > 0 && import_count >= limit && sym_count >= limit) {
				break;
			}
			if (methods[i]) {
				continue;
			}
			RBinDexMethod *method = RVecDexMethod_at (&dex->dex_methods, i);
			if (!method) {
				continue;
			}
			const ut16 cid = method->class_id;
			if (cid >= types_size) {
				continue;
			}
			if (r_bitset_test (has_code, cid)) {
				continue;
			}
			const char *className = getstr (dex, dex->types[cid].descriptor_id);
			if (R_STR_ISEMPTY (className)) {
				continue;
			}
			char *class_name = strdup (className);
			if (!class_name) {
				continue;
			}
			detect_dex_subsystem (dex, class_name);
			if (class_name[0] == '\0') {
				free (class_name);
				continue;
			}
			r_str_replace_char (class_name, ';', 0);
			const char *method_name = dex_method_name (dex, i);
			const char *signature = dex_method_signature (dex, i);
			if (!R_STR_ISEMPTY (method_name)) {
				RBinImport imp = {0};
				char *s = r_str_newf ("%s.method.%s%s", class_name, method_name, signature);
				imp.name = r_bin_name_new (s);
				free (s);
				imp.type = "FUNC";
				imp.bind = "NONE";
				imp.ordinal = import_count++;
				RVecRBinImport_push_back (&dex->imports_vec, &imp);

				RBinSymbol sym = {0};
				sym.name = r_bin_name_clone (imp.name);
				sym.is_imported = true;
				sym.type = R_BIN_TYPE_FUNC_STR;
				sym.bind = "NONE";
				sym.paddr = dex->header.method_offset + (sizeof (struct dex_method_t) * i);
				sym.vaddr = sym.paddr;
				sym.ordinal = sym_count++;
				sym.attr.lang = R_BIN_LANG_JAVA;
				RVecRBinSymbol_push_back (&dex->symbols_vec, &sym);
				sdb_num_setf (dex->mdb, sym.paddr, 0, "method.%"PFMT64u, (ut64)i);
			}
			free (class_name);
		}
		r_bitset_free (has_code);
		free (methods);
	}
	return true;
}

static bool imports_vec(RBinFile *bf) {
	R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, false);
	RBinDexObj *dex = (RBinDexObj*) bf->bo->bin_obj;
	if (!RVecRBinImport_empty (&bf->bo->imports_vec)) {
		return true;
	}
	if (RVecRBinImport_empty (&dex->imports_vec)) {
		dex_loadcode (bf);
	}
	if (!RVecRBinImport_empty (&dex->imports_vec)) {
		bf->bo->imports_vec = dex->imports_vec;
		RVecRBinImport_init (&dex->imports_vec);
		return true;
	}
	return false;
}

static RVecRBinTrycatch *trycatch(RBinFile *bf) {
	R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, NULL);
	RBinDexObj *bin = (RBinDexObj*) bf->bo->bin_obj;
	if (!bin->trycatch_loaded) {
		dex_loadcode (bf);
	}
	RVecRBinTrycatch_shrink_to_fit (&bin->trycatch);
	return &bin->trycatch;
}

static bool symbols_vec(RBinFile *bf) {
	R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, false);
	r_bin_file_add_language (bf, R_BIN_LANG_JAVA);
	RBinDexObj *bin = (RBinDexObj*) bf->bo->bin_obj;
	if (!RVecRBinSymbol_empty (&bf->bo->symbols_vec)) {
		return true;
	}
	if (RVecRBinSymbol_empty (&bin->symbols_vec)) {
		dex_loadcode (bf);
	}
	if (!RVecRBinSymbol_empty (&bin->symbols_vec)) {
		bf->bo->symbols_vec = bin->symbols_vec;
		RVecRBinSymbol_init (&bin->symbols_vec);
		return true;
	}
	return false;
}

static void dex_skip_class_fields(RBuffer *b, ut64 fields_count, bool *err) {
	ut64 i;
	for (i = 0; i < fields_count; i++) {
		size_t skip = 0;
		peek_uleb (b, err, &skip);
		peek_uleb (b, err, &skip);
		if (err && *err) {
			return;
		}
	}
}

static void dex_parse_class_method_addrline(RBinFile *bf, RBinDexClass *c, ut64 methods_count, const char *class_name) {
	RBinDexObj *dex = bf->bo->bin_obj;
	RBuffer *b = bf->buf;
	const ut64 bufsz = r_buf_size (b);
	ut64 omi = 0;
	ut64 i;
	for (i = 0; i < methods_count; i++) {
		bool err = false;
		size_t skip = 0;
		ut64 MI = peek_uleb (b, &err, &skip);
		if (err) {
			break;
		}
		if (MI > UT64_MAX - omi) {
			break;
		}
		MI += omi;
		omi = MI;
		ut64 MA = peek_uleb (b, &err, &skip);
		if (err) {
			break;
		}
		ut64 MC = peek_uleb (b, &err, &skip);
		if (err) {
			break;
		}
		if (MI >= dex->header.method_size) {
			continue;
		}
		if (MC == 0 || MC > UT64_MAX - 16 || MC + 16 >= dex->size) {
			continue;
		}
		if (bufsz < MC || bufsz - MC < 16) {
			continue;
		}
		ut16 regsz = r_buf_read_le16_at (b, MC);
		ut16 ins_size = r_buf_read_le16_at (b, MC + 2);
		ut32 debug_info_off = r_buf_read_le32_at (b, MC + 8);
		ut32 insns_size = r_buf_read_le32_at (b, MC + 12);
		if (regsz == UT16_MAX || ins_size == UT16_MAX || !debug_info_off) {
			continue;
		}
		if (dex->header.data_offset >= debug_info_off ||
			debug_info_off >= dex->header.data_offset + dex->header.data_size) {
			continue;
		}
		ut64 addr = r_buf_tell (b);
		if (addr > ST64_MAX) {
			continue;
		}
		dex_parse_debug_item (bf, c, MI, MA, MC + 16, ins_size,
			insns_size, class_name, regsz, debug_info_off, true);
		r_buf_seek (b, (st64)addr, R_BUF_SET);
	}
}

static void dex_parse_class_addrline(RBinFile *bf, RBinDexClass *c) {
	RBinDexObj *dex = bf->bo->bin_obj;
	RBuffer *b = bf->buf;
	const DexHeader *hdr = &dex->header;
	if (!c->class_data_offset) {
		return;
	}
	if (hdr->class_offset > c->class_data_offset ||
	    c->class_data_offset < hdr->class_offset + hdr->class_size * DEX_CLASS_SIZE) {
		return;
	}
	if (r_buf_seek (b, c->class_data_offset, R_BUF_SET) < 0) {
		return;
	}
	bool err = false;
	size_t skip = 0;
	ut64 static_fields_size = peek_uleb (b, &err, &skip);
	ut64 instance_fields_size = peek_uleb (b, &err, &skip);
	ut64 direct_methods_size = peek_uleb (b, &err, &skip);
	ut64 virtual_methods_size = peek_uleb (b, &err, &skip);
	if (err) {
		return;
	}
	dex_skip_class_fields (b, static_fields_size, &err);
	dex_skip_class_fields (b, instance_fields_size, &err);
	if (err) {
		return;
	}
	char *class_name = dex_class_name (dex, c);
	if (!class_name) {
		return;
	}
	r_str_replace_char (class_name, ';', 0);
	dex_parse_class_method_addrline (bf, c, direct_methods_size, class_name);
	dex_parse_class_method_addrline (bf, c, virtual_methods_size, class_name);
	free (class_name);
}

static void dex_load_addrline(RBinFile *bf) {
	R_RETURN_IF_FAIL (bf && bf->bo && bf->bo->bin_obj);
	RBinDexObj *dex = bf->bo->bin_obj;
	if (dex->addrline_loaded || !bf->rbin->want_dbginfo) {
		return;
	}
	dex->addrline_loaded = true;
	bf->addrline.used = true;
	if (!dex->classes) {
		return;
	}
	ut32 i;
	for (i = 0; i < dex->header.class_size; i++) {
		dex_parse_class_addrline (bf, &dex->classes[i]);
	}
}

static char *dex_cmd(RBinFile *bf, const char *command) {
	R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, NULL);
	if (!command || strcmp (command, "addrline")) {
		return NULL;
	}
	dex_load_addrline (bf);
	return bf->addrline.used? strdup (""): NULL;
}

static RList *classes(RBinFile *bf) {
	R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, NULL);
	r_bin_file_add_language (bf, R_BIN_LANG_JAVA);
	RBinDexObj *bin = (RBinDexObj*) bf->bo->bin_obj;
	const int limit = bf->rbin->options.limit;
	int count = 0;
	if (RVecRBinClass_empty (&bin->classes_vec)) {
		dex_loadcode (bf);
	}
	// Convert vec to list for backward compatibility
	RList *ret = r_list_newf ((RListFree)r_bin_class_free);
	if (!ret) {
		return NULL;
	}
	RBinClass *cls;
	R_VEC_FOREACH (&bin->classes_vec, cls) {
		if (limit > 0 && count >= limit) {
			break;
		}
		RBinClass *cloned = R_NEW0 (RBinClass);
		*cloned = *cls;
		r_list_append (ret, cloned);
		count++;
	}
	free_class_tail (&bin->classes_vec, count);
	return ret;
}

static bool already_entry(RList *entries, ut64 vaddr) {
	RBinAddr *e;
	RListIter *iter;
	r_list_foreach (entries, iter, e) {
		if (e->vaddr == vaddr) {
			return true;
		}
	}
	return false;
}

static RList *entries(RBinFile *bf) {
	RBinAddr *ptr;
	const int limit = bf->rbin->options.limit;

	R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, NULL);

	RBinDexObj *bin = (RBinDexObj*) bf->bo->bin_obj;
	RList *ret = r_list_newf ((RListFree)free);

	if (RVecRBinSymbol_empty (&bin->symbols_vec)) {
		dex_loadcode (bf);
	}

	// STEP 1. ".onCreate(Landroid/os/Bundle;)V"
	RBinSymbol *m;
	R_VEC_FOREACH (&bin->symbols_vec, m) {
		if (limit_reached (ret, limit)) {
			break;
		}
		// skip abstract/interface methods: their paddr points to the
		// encoded_method record, not to actual bytecode
		if (m->attr.size < 1 || !m->type || strcmp (m->type, R_BIN_TYPE_FUNC_STR)) {
			continue;
		}
		const char *oname = r_bin_name_tostring2 (m->name, 'o');
		if (strlen (oname) > 30 && m->bind \
				&& (!strcmp (m->bind, R_BIN_BIND_LOCAL_STR) || !strcmp (m->bind, R_BIN_BIND_GLOBAL_STR)) \
				&& !strcmp (oname + strlen (oname) - 31, ".onCreate(Landroid/os/Bundle;)V")) {
			if (!already_entry (ret, m->paddr)) {
				ptr = R_NEW0 (RBinAddr);
				ptr->paddr = ptr->vaddr = m->paddr;
				r_list_append (ret, ptr);
			}
		}
	}

	// STEP 2. ".main([Ljava/lang/String;)V"
	if (r_list_empty (ret)) {
		R_VEC_FOREACH (&bin->symbols_vec, m) {
			if (limit_reached (ret, limit)) {
				break;
			}
			if (m->attr.size < 1 || !m->type || strcmp (m->type, R_BIN_TYPE_FUNC_STR)) {
				continue;
			}
			const char *oname = r_bin_name_tostring2 (m->name, 'o');
			if (strlen (oname) > 26 && !strcmp (oname + strlen (oname) - 27, ".main([Ljava/lang/String;)V")) {
				if (!already_entry (ret, m->paddr)) {
					ptr = R_NEW0 (RBinAddr);
					ptr->paddr = m->paddr;
					ptr->vaddr = ptr->paddr;
					r_list_append (ret, ptr);
				}
			}
		}
	}
#if 0
	// this is now done by r2 in a generic way
	// STEP 3. NOTHING FOUND POINT TO CODE_INIT
	if (r_list_empty (ret)) {
		if (!already_entry (ret, bin->code_from)) {
			ptr = R_NEW0 (RBinAddr);
			if (ptr) {
				ptr->paddr = ptr->vaddr = bin->code_from;
				r_list_append (ret, ptr);
			}
		}
	}
#endif
	return ret;
}

static ut64 getoffset(RBinFile *bf, int type, int idx) {
	struct r_bin_dex_obj_t *dex = bf->bo->bin_obj;
	ut64 off = UT64_MAX;
	switch (type) {
	case 'm': // methods
		// TODO: ADD CHECK
		off = method_addr (bf, idx);
		break;
	case 'f':
		off = dex_field_offset (dex, idx);
		break;
	case 'o': // objects
		R_LOG_TODO ("getoffset object");
		return 0; // //chdex_object_offset (dex, idx);
	case 's': // strings
		if (dex->header.strings_size > idx) {
			if (dex->strings) {
				off = dex->strings[idx];
			}
		}
		break;
	case 't': // type
	case 'c': // class
		off = dex_get_type_offset (bf, idx);
		break;
	}
	return (off == UT64_MAX)? UT64_MAX: r_bin_file_get_vaddr (bf, off, off);
}

static const char *get_cc(RBinFile *bf, ut64 vaddr) {
	R_RETURN_VAL_IF_FAIL (bf && bf->rbin, NULL);
	RBinSymbol *m = r_bin_object_get_symbol_at (bf->bo, vaddr);
	if (!m || !m->arg_prefix) {
		return NULL;
	}
	const char *pfx = m->arg_prefix;
	const bool instance = !(m->attr.flags & R_BIN_ATTR_STATIC);
	RStrBuf *sb = r_strbuf_new ("dyncc:");
	if (!sb) {
		return NULL;
	}
	if (m->arg_count > 0) {
		r_strbuf_appendf (sb, "%s%u+%u", pfx, m->arg_first, m->arg_count);
	}
	r_strbuf_append (sb, ":");
	if (m->ret_count == 1) {
		r_strbuf_append (sb, "result");
	} else if (m->ret_count > 1) {
		r_strbuf_append (sb, "result,result_hi");
	}
	if (instance) {
		if (m->arg_count > 0) {
			r_strbuf_append (sb, "!T0");
		} else {
			r_strbuf_appendf (sb, "!T%s%u", pfx, m->arg_first);
		}
	}
	char *s = r_strbuf_drain (sb);
	const char *ret = r_str_constpool_get (&bf->rbin->constpool, s);
	free (s);
	return ret;
}

static const char *getname(RBinFile *bf, int type, int idx, bool sd) {
	RBinDexObj *dex = bf->bo->bin_obj;
	dex->simplifiedDemangling = sd;
	switch (type) {
	case 'm': // methods
		return dex_method_fullname (dex, idx);
	case 'c': // classes
		return dex_class_name_byid (dex, idx);
	case 'f': // fields
		return dex_field_name (dex, idx);
	case 'p': // proto
		return dex_get_proto (dex, idx);
	}
	return NULL;
}

typedef struct {
	ut64 addr;
	ut64 size;
} Section;

static RBinSection *add_section(RBinFile *bf, ut64 baddr, const char *name, Section s, int perm, char *format) {
	R_RETURN_VAL_IF_FAIL (bf && name, NULL);
	R_RETURN_VAL_IF_FAIL (s.addr < UT32_MAX, NULL);
	R_RETURN_VAL_IF_FAIL (s.size > 0 && s.size < UT32_MAX, NULL);
	RBinSection *ptr = RVecRBinSection_emplace_back (&bf->bo->sections_vec);
	ptr->name = strdup (name);
	ptr->size = ptr->vsize = s.size;
	ptr->paddr = s.addr;
	ptr->vaddr = s.addr;
	ptr->perm = perm;
	ptr->add = false;
	ptr->format = format;
	return ptr;
}

static RBinSection *add_limited_section(RBinFile *bf, int limit, ut64 baddr, const char *name, Section s, int perm, char *format) {
	if (limit > 0 && RVecRBinSection_length (&bf->bo->sections_vec) >= (size_t)limit) {
		free (format);
		return NULL;
	}
	return add_section (bf, baddr, name, s, perm, format);
}

static void add_segment(RBinFile *bf, ut64 baddr, const char *name, Section s, int perm) {
	RBinSection *bs = add_section (bf, baddr, name, s, perm, NULL);
	if (bs) {
		bs->is_segment = true;
		bs->add = true;
	}
}

static void add_limited_segment(RBinFile *bf, int limit, ut64 baddr, const char *name, Section s, int perm) {
	if (limit < 1 || RVecRBinSection_length (&bf->bo->sections_vec) < (size_t)limit) {
		add_segment (bf, baddr, name, s, perm);
	}
}

static bool validate_section(const char *name, Section *pre, Section *cur, Section *nex, Section *all) {
	R_RETURN_VAL_IF_FAIL (cur && all, false);
	if (pre && cur->addr < (pre->addr + pre->size)) {
		R_LOG_WARN ("%s Section starts before the previous", name);
	}
	if (cur->addr >= all->size) {
		R_LOG_WARN ("%s section starts beyond the end of the file", name);
		return false;
	}
	if (cur->addr == UT64_MAX) {
		R_LOG_WARN ("%s invalid region size", name);
		return false;
	}
	if ((cur->addr + cur->size) > all->size) {
		R_LOG_WARN ("%s truncated section because of file size", name);
		cur->size = all->size - cur->addr;
	}
	if (nex) {
		if (cur->addr >= nex->addr) {
			R_LOG_WARN ("invalid %s section address", name);
			return false;
		}
		if ((cur->addr + cur->size) > nex->addr) {
			R_LOG_WARN ("truncated %s with next section size", name);
			cur->size = nex->addr - cur->addr;
		}
	}
	return cur->size > 0;
}

static void fast_code_size(RBinFile *bf) {
	const size_t bs = r_buf_size (bf->buf);
	ut64 ns;
	ut64 fsym = 0LL;
	ut64 fsymsz = 0LL;
	RBinDexObj *bin = bf->bo->bin_obj;
	if (RVecRBinSymbol_empty (&bin->symbols_vec)) {
		dex_loadcode (bf);
	}
	RBinSymbol *m;
	R_VEC_FOREACH (&bin->symbols_vec, m) {
		if (m->attr.size < 1 || !m->type || strcmp (m->type, R_BIN_TYPE_FUNC_STR)) {
			continue;
		}
		if (!fsym || m->paddr < fsym) {
			fsym = m->paddr;
		}
		ns = m->paddr + m->attr.size;
		if (ns > bs || m->paddr > bs || m->attr.size > bs) {
			continue;
		}
		if (ns > fsymsz) {
			fsymsz = ns;
		}
	}
	bin->code_from = fsym;
	bin->code_to = fsymsz;
}

static bool sections_vec(RBinFile *bf) {
	struct r_bin_dex_obj_t *bin = bf->bo->bin_obj;
	const int limit = bf->rbin->options.limit;

	/* find the last method */
	const size_t bs = r_buf_size (bf->buf);
	if (!bin->code_from || !bin->code_to) {
		fast_code_size (bf);
	}
	RVecRBinSection_clear (&bf->bo->sections_vec);

	/* initial section boundary assumptions */
	Section s_head = { 0, sizeof (struct dex_header_t) };
	Section s_pool = { s_head.size, bin->code_from - sizeof (struct dex_header_t)};
	Section s_code = { bin->code_from, bin->code_to - bin->code_from };
	Section s_data = { bin->code_to, bs - bin->code_to};
	Section s_file = { 0, bs };

	const ut64 baddr = bf->bo->baddr;
	/* sanity bound checks and section registrations */
	if (validate_section ("header", NULL, &s_head, NULL, &s_file)) {
		add_limited_section (bf, limit, baddr, "header", s_head, R_PERM_R, NULL);
	}
	if (validate_section ("constpool", &s_head, &s_pool, &s_code, &s_file)) {
		char *s_pool_format = r_str_newf ("Cd %d[%"PFMT64d"]", 4, (ut64) s_pool.size / 4);
		add_limited_section (bf, limit, baddr, "constpool", s_pool, R_PERM_R, s_pool_format);
	}
	if (validate_section ("code", &s_pool, &s_code, &s_data, &s_file)) {
		add_limited_section (bf, limit, baddr, "code", s_code, R_PERM_RX, NULL);
	}
	if (validate_section ("data", &s_code, &s_data, NULL, &s_file)) {
		add_limited_section (bf, limit, baddr, "data", s_data, R_PERM_RX, NULL);
	}
	add_limited_section (bf, limit, baddr, "file", s_file, R_PERM_R, NULL);

	/* add segments */
	if (s_code.size > 0) {
		add_limited_segment (bf, limit, baddr, "code", s_code, R_PERM_RX);
	}
	add_limited_segment (bf, limit, baddr, "file", s_file, R_PERM_R);
	return true;
}

// iH
static char *dex_header(RBinFile *bf, int mode) {
	RBinDexObj *dex = bf->bo->bin_obj;
	DexHeader *hdr = &dex->header;
	r_strbuf_free (dex->sb);
	dex->sb = r_strbuf_new ("");
#define p(f,...) r_strbuf_appendf (dex->sb, f, ##__VA_ARGS__)
	p ("DEX file header:\n");
	p ("magic               : 'dex\\n035\\0'\n");
	p ("checksum            : %x\n", hdr->checksum);
	p ("signature           : %02x%02x...%02x%02x\n",
		hdr->signature[0], hdr->signature[1], hdr->signature[18], hdr->signature[19]);
	p ("file_size           : %d\n", hdr->size);
	p ("header_size         : %d\n", hdr->header_size);
	p ("link_size           : %d\n", hdr->linksection_size);
	p ("link_off            : %d (0x%06x)\n", hdr->linksection_offset, hdr->linksection_offset);
	p ("string_ids_size     : %d\n", hdr->strings_size);
	p ("string_ids_off      : %d (0x%06x)\n", hdr->strings_offset, hdr->strings_offset);
	p ("type_ids_size       : %d\n", hdr->types_size);
	p ("type_ids_off        : %d (0x%06x)\n", hdr->types_offset, hdr->types_offset);
	p ("proto_ids_size      : %d\n", hdr->prototypes_size);
	p ("proto_ids_off       : %d (0x%06x)\n", hdr->prototypes_offset, hdr->prototypes_offset);
	p ("field_ids_size      : %d\n", hdr->fields_size);
	p ("field_ids_off       : %d (0x%06x)\n", hdr->fields_offset, hdr->fields_offset);
	p ("method_ids_size     : %d\n", hdr->method_size);
	p ("method_ids_off      : %d (0x%06x)\n", hdr->method_offset, hdr->method_offset);
	p ("class_defs_size     : %d\n", hdr->class_size);
	p ("class_defs_off      : %d (0x%06x)\n", hdr->class_offset, hdr->class_offset);
	p ("data_size           : %d\n", hdr->data_size);
	p ("data_off            : %d (0x%06x)\n\n", hdr->data_offset, hdr->data_offset);
#undef p
	// TODO: print information stored in the RBIN not this ugly fix
	// Free previously allocated resources before reloading
	RVecRBinSymbol_fini (&dex->symbols_vec);
	RVecRBinImport_fini (&dex->imports_vec);
	RVecRBinClass_fini (&dex->classes_vec);
	r_list_free (dex->lines_list);
	dex->lines_list = NULL;
	free (dex->version);
	dex->version = NULL;
	dex_loadcode (bf);
	char *ret = r_strbuf_drain (dex->sb);
	dex->sb = NULL;
	return ret;
}

static ut64 size(RBinFile *bf) {
	ut8 u32s[sizeof (ut32)] = {0};

	int ret = r_buf_read_at (bf->buf, 108, u32s, 4);
	if (ret != 4) {
		return 0;
	}
	ut32 off = r_read_le32 (u32s);
	ret = r_buf_read_at (bf->buf, 104, u32s, 4);
	if (ret != 4) {
		return 0;
	}
	return off + r_read_le32 (u32s);
}

// iH*
static RList *dex_fields(RBinFile *bf) {
	RList *ret = r_list_new ();
	if (!ret) {
		return NULL;
	}
	ret->free = free;
	ut64 addr = 0;

#define ROW(nam,siz,val,fmt) \
	r_list_append (ret, r_bin_field_new (addr, addr, val, siz, nam, NULL, fmt, false)); \
	addr += siz;

	r_buf_seek (bf->buf, 0, R_BUF_SET);
	ut64 magic = r_buf_read_le64 (bf->buf);
	ROW ("dex_magic", 8, magic, "[8]c");
	ut32 checksum = r_buf_read_le32 (bf->buf);
	ROW ("dex_checksum", 4, checksum, "x");
	ROW ("dex_signature", 20, 0, "[20]c");
	r_buf_seek (bf->buf, addr, R_BUF_SET);
	ut32 size = r_buf_read_le32 (bf->buf);
	ROW ("dex_size", 4, size, "x");
	ut32 header_size = r_buf_read_le32 (bf->buf);
	ROW ("dex_header_size", 4, header_size, "x");
	ut32 endian = r_buf_read_le32 (bf->buf);
	ROW ("dex_endian", 4, endian, "x");
/*
	ROW ("hdr.cputype", 4, mh->cputype, "x");
	ROW ("hdr.cpusubtype", 4, mh->cpusubtype, "x");
	ROW ("hdr.filetype", 4, mh->filetype, "x");
	ROW ("hdr.nbcmds", 4, mh->ncmds, "x");
	ROW ("hdr.sizeofcmds", 4, mh->sizeofcmds, "x");
*/
	return ret;
}

static int cmp_path(const void *a, const void *b) {
	if (!a || !b) {
		return 0;
	}
	return strcmp ((const char*)a, (const char*)b);
}

static bool is_classes_dex(const char *filename) {
	return r_str_startswith (filename, "classes") \
		&& r_str_endswith (filename, ".dex");
}

static RList* libs(RBinFile *bf) {
	R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, NULL);
	char *path = r_file_dirname (bf->file);
	if (r_str_startswith (path, "./")) {
		// avoids stuff like .//.//.//.//.//
		free (path);
		return NULL;
	}
	RList *files = r_sys_dir (path);
	if (!files) {
		free (path);
		return NULL;
	}
	RList *ret = r_list_newf (free);
	if (!ret) {
		free (path);
		r_list_free (files);
		return NULL;
	}
	/* opening dex files in order. */
	r_list_sort (files, cmp_path);
	RListIter *iter;
	char *file;
	r_list_foreach (files, iter, file) {
		if (is_classes_dex (file)) {
			char *n = r_str_newf ("%s%s%s", path, R_SYS_DIR, file);
			if (strcmp (n, bf->file)) {
				r_list_append (ret, n);
			} else {
				free (n);
			}
		}
	}
	r_list_free (files);
	free (path);
	return ret;
}

static void destroy(RBinFile *bf) {
	R_RETURN_IF_FAIL (bf && bf->bo);
	RBinDexObj *obj = bf->bo->bin_obj;
	r_bin_dex_free (obj);
}

static ut64 baddr(RBinFile *bf) {
	return 0;
}

RBinPlugin r_bin_plugin_dex = {
	.meta = {
		.name = "dex",
		.desc = "Dalvik Executable format",
		.license = "LGPL-3.0-only",
	},
	.destroy = &destroy,
	.get_sdb = &get_sdb,
	.load = &load,
	.check = check,
	.entries = entries,
	.classes = classes,
	.sections_vec = &sections_vec,
	.symbols_vec = &symbols_vec,
	.imports_vec = &imports_vec,
	.baddr = baddr,
	.trycatch = trycatch,
	.strings = strings,
	.info = &info,
	.header = dex_header,
	.fields = dex_fields,
	.libs = &libs,
	.size = &size,
	.get_offset = &getoffset,
	.get_name = &getname,
	.get_cc = &get_cc,
	.cmd = &dex_cmd,
//	.dbginfo = &r_bin_dbginfo_dex,
};

#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
	.type = R_LIB_TYPE_BIN,
	.data = &r_bin_plugin_dex,
	.version = R2_VERSION
};
#endif
