/* radare - LGPL - Copyright 2010-2024 - pancake */

#include <r_arch.h>
#include "opcode.h"

#define GETWIDE "32,v%u,<<,v%u,|"
#define SETWIDE "DUP,v%u,=,32,SWAP,>>,v%u,="

static inline ut64 _anal_get_offset(RArch *a, int type, int idx) {
	if (a && a->binb.bin && a->binb.get_offset) {
		return a->binb.get_offset (a->binb.bin, type, idx);
	}
	return UT64_MAX;
}

static inline char *_anal_get_name(RArch *a, int type, int idx) {
	if (a && a->binb.bin && a->binb.get_name) {
		return (char *)a->binb.get_name (a->binb.bin, type, idx, false);
				// (bool)a->coreb.cfggeti (a->coreb.core, "asm.pseudo"));
	}
	return NULL;
}

static const char *getCond(ut8 cond) {
	switch (cond) {
	case 0x32: // if-eq
	case 0x38: // if-eqz
		return "-,!";
	case 0x33: // if-ne
	case 0x39: // if-nez
		return "-";
	case 0x34: // if-lt
	case 0x3a: // if-ltz
		return "<";
	case 0x35: // if-ge
	case 0x3b: // if-gez
		return "<,!";
	case 0x36: // if-gt
	case 0x3c: // if-gtz
		return "<=,!";
	case 0x37: // if-le
	case 0x3d: // if-lez
		return "<=";
	}
	return "";
}

typedef enum {
	OP_INT,
	OP_LONG,
	OP_LONG_SHFT,
	OP_FLOAT,
	OP_DOUBLE
} OperandType;

/*static void format10t(int len, const ut8* data, ut32* dst) {
	if (len > 1) {
		*dst = data[1];
	}
}*/

static void format11x(int len, const ut8* data, ut32* dst) {
	if (len > 1) {
		*dst = data[1] & 0x0F;
	}
}

static void format11n(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 1) {
		*dst = data[1] & 0x0F;
		*src = (st32)((st8)((data[1] & 0xF0) >> 4)); // uhhh
	}
}

static void format12x(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 1) {
		*dst = data[1] & 0x0F;
		*src = (data[1] & 0xF0) >> 4;
	}
}

/*static void format20t(int len, const ut8* data, ut32* dst) {
	if (len > 3) {
		*dst = r_read_le16 (data + 2);
	}
}*/

static void format21t(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 3) {
		*dst = data[1];
		*src = 2*r_read_le16 (data + 2);
	}
}

static void format21s(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 3) {
		*dst = data[1];
		*src = (st32)(st16)r_read_le16 (data + 2);
	}
}

static void format21hw(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 3) {
		*dst = data[1];
		*src = (ut32)((st16)r_read_le16 (data + 2)) << 16;
	}
}

static void format21hd(int len, const ut8* data, ut32* dst, st64* src) {
	if (len > 3) {
		*dst = data[1];
		*src = (ut64)((st64)(st16)r_read_le16 (data + 2)) << 48;
	}
}

static void format21c(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 3) {
		*dst = data[1];
		*src = r_read_le16 (data + 2);
	}
}

static void format22c (int len, const ut8* data, ut32* dst, ut32* src, ut32* ref) {
	if (len > 3) {
		*dst = data[1] & 0x0F;
		*src = (data[1] & 0xF0) >> 4;
		*ref = r_read_le16 (data + 2);
	}
}

// same as 21c but not for literals
static void format22x(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 3) {
		*dst = data[1];
		*src = r_read_le16 (data + 2);
	}
}

static void format22t(int len, const ut8* data, ut32* dst, ut32* src, ut32* ref) {
	if (len > 3) {
		*dst = data[1] & 0x0F;
		*src = (data[1] & 0xF0) >> 4;
		*ref = 2*r_read_le16 (data + 2);
	}
}

static void format22s(int len, const ut8* data, ut32* dst, ut32* src, ut32* ref) {
	if (len > 3) {
		*dst = data[1] & 0x0F;
		*src = (data[1] & 0xF0) >> 4;
		*ref = (st32)(st16)r_read_le16 (data + 2);
	}
}

static void format22b(int len, const ut8* data, ut32* dst, ut32* src, ut32* ref) {
	if (len > 3) {
		*dst = data[1];
		*src = data[2];
		*ref = (st32)(*(st8*)(data+3));
	}
}

static void format23x(int len, const ut8* data, ut32* dst, ut32* src, ut32* ref) {
	if (len > 1) {
		*dst = data[1];
		*src = data[2];
		*ref = data[3];
	}
}

/*static void format30t(int len, const ut8* data, ut32* dst) {
	if (len > 5) {
		*dst = r_read_le32(data+2);
	}
}*/

static void format31i(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 5) {
		*dst = data[1];
		*src = r_read_le32(data+2);
	}
}

static void format31c(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 5) {
		*dst = data[1];
		*src = r_read_le32(data+2);
	}
}

static void format32x(int len, const ut8* data, ut32* dst, ut32* src) {
	if (len > 5) {
		*dst = r_read_le16 (data + 2);
		*src = r_read_le16 (data + 4);
	}
}

/*static void format3rc(int len, const ut8* data, ut32* dst, ut32* src, ut32* ref) {
	if (len > 5) {
		*src = data[1] - 1;
		*dst = r_read_le16 (data + 2);
		*ref = r_read_le16 (data + 4);
	}
}

static void format4rcc(int len, const ut8* data, ut32* dst, ut32* src, ut32* ref1, ut32* ref2) {
	if (len > 7) {
		*src  = data[1] - 1;
		*dst  = r_read_le16 (data + 2);
		*ref1 = r_read_le16 (data + 4);
		*ref2 = r_read_le16 (data + 6);
	}
}*/

static void format51l(int len, const ut8* data, ut32* dst, st64* src) {
	if (len > 9) {
		*dst = data[1];
		*src = (st64)r_read_le64 (data + 2);
	}
}

#define OPCALL(x, y, z) dalvik_math_op(op, data, len, mask, x, y, z)
static void dalvik_math_op(RAnalOp* op, const ut8* data, int len, RAnalOpMask mask, char* operation, unsigned int optype, OperandType ot) {
	ut32 vA = 0, vB = 0, vC = 0;
	op->type = optype;
	if (ot == OP_FLOAT || ot == OP_DOUBLE) {
		op->family = R_ANAL_OP_FAMILY_FPU;
	}

	char* v = "v";
	if (data[0] < 0xb0) {
		format23x(len, data, &vA, &vB, &vC);
	} else if (data[0] < 0xd0) {
		format12x(len, data, &vA, &vB);
		vC = vB;
		vB = vA;
	} else if (data[0] < 0xd8) {
		format22s(len, data, &vA, &vB, &vC);
		v = ""; // value is literal not register
	} else if (data[0] < 0xe3) {
		format22b(len, data, &vA, &vB, &vC);
		v = ""; // value is literal not register
	}

	if (mask & R_ARCH_OP_MASK_ESIL) {
		if (ot == OP_INT) {
			if (optype == R_ANAL_OP_TYPE_DIV || optype == R_ANAL_OP_TYPE_MOD) {
				esilprintf (op, "32,%s%d,~,32,v%u,~,%s,v%u,=", v, vC, vB, operation, vA);
			} else {
				esilprintf (op, "%s%d,v%u,%s,v%u,=", v, vC, vB, operation, vA);
			}
		} else if (ot == OP_LONG) {
			esilprintf (op, GETWIDE "," GETWIDE ",%s," SETWIDE,
				vC+1, vC, vB+1, vB, operation, vA, vA+1);
		} else if (ot == OP_LONG_SHFT) {
			esilprintf (op, "v%u," GETWIDE ",%s," SETWIDE,
				vC, vB+1, vB, operation, vA, vA+1);
		} else if (ot == OP_FLOAT) {
			esilprintf (op, "32,32,v%u,F2D,32,v%u,F2D,F%s,D2F,v%u,=", vC, vB, operation, vA);
		} else if (ot == OP_DOUBLE) {
			esilprintf (op, GETWIDE "," GETWIDE ",F%s," SETWIDE,
				vC+1, vC, vB+1, vB, operation, vA, vA+1);
		}
	}
}

static int dalvik_disassemble(RArchSession *as, RAnalOp *op, ut64 addr, const ut8 *buf, int len, int size) {
	R_RETURN_VAL_IF_FAIL  (as && op && buf && len > 0, -1);

	int vA, vB, vC, vD, vE, vF, vG, vH, payload = 0;
	char str[1024], *strasm = NULL;
	char *flag_str = NULL;
	ut64 offset;
	ut8 opcode = buf[0];

	RArch *a = as->arch;
	as->config->dataalign = 2;

	if (buf[0] == 0x00) { /* nop */
		if (len < 2) {
			return -1;
		}
		op->nopcode = 2;
		switch (buf[1]) {
		case 0x01: /* packed-switch-payload */
			// ushort size
			// int first_key
			// int[size] = relative offsets
			if (len > 7) {
				ut16 array_size = buf[2] | (buf[3] << 8);
				int first_key = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24);
				op->mnemonic = r_str_newf ("packed-switch-payload %d, %d", array_size, first_key);
				size = 8;
				payload = 2 * (array_size * 2);
				len = 0;
			} else {
				return -1;
			}
			break;
		case 0x02: /* sparse-switch-payload */
			// ushort size
			// int[size] keys
			// int[size] relative offsets
			if (len > 3) {
				ut16 array_size = buf[2] | (buf[3] << 8);
				op->mnemonic = r_str_newf ("sparse-switch-payload %d", array_size);
				size = 4;
				payload = 2 * (array_size * 4);
				len = 0;
			} else {
				return -1;
			}
			break;
		case 0x03: /* fill-array-data-payload */
			// element_width = 2 bytes ushort little endian
			// size = 4 bytes uint
			// ([size*element_width+1)/2)+4
			if (len > 7) {
				ut16 elem_width = buf[2] | (buf[3] << 8);
				ut32 array_size = buf[4] | (buf[5] << 8) | (buf[6] << 16) | ((ut32)buf[7] << 24);
				op->mnemonic = r_str_newf ("fill-array-data-payload %d, %d", elem_width, array_size);
				payload = array_size * elem_width;
				size = 8;
				len = 0;
			} else {
				return -1;
			}
			break;
		default:
			/* nop */
			break;
		}
	}

	strasm = NULL;
	if (size <= len) {
		strasm = strdup (dalvik_opcodes[opcode].name);
		size = dalvik_opcodes[opcode].len;
		switch (dalvik_opcodes[opcode].fmt) {
		case fmtop: break;
		case fmtopvAvB:
			vA = buf[1] & 0x0f;
			vB = (buf[1] & 0xf0) >> 4;
			snprintf (str, sizeof (str), " v%i, v%i", vA, vB);
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAAvBBBB:
			vA = (int) buf[1];
			vB = (buf[3] << 8) | buf[2];
			snprintf (str, sizeof (str), " v%i, v%i", vA, vB);
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAAAAvBBBB: // buf[1] seems useless :/
			vA = (buf[3] << 8) | buf[2];
			vB = (buf[5] << 8) | buf[4];
			snprintf (str, sizeof (str), " v%i, v%i", vA, vB);
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAA:
			vA = (int) buf[1];
			snprintf (str, sizeof (str), " v%i", vA);
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAcB:
			vA = buf[1] & 0x0f;
			vB = (buf[1] & 0xf0) >> 4;
			snprintf (str, sizeof (str), " v%i, %#x", vA, vB);
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAAcBBBB:
			vA = (int) buf[1];
			{
				short sB = (buf[3] << 8) | buf[2];
				snprintf (str, sizeof (str), " v%i, %#04hx", vA, sB);
				strasm = r_str_append (strasm, str);
			}
			break;
		case fmtopvAAcBBBBBBBB:
			vA = (int) buf[1];
			vB = buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24);
			if (buf[0] == 0x17) { //const-wide/32
				snprintf (str, sizeof (str), " v%i:v%i, 0x%08x", vA, vA + 1, vB);
			} else { //const
				snprintf (str, sizeof (str), " v%i, 0x%08x", vA, vB);
			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAAcBBBB0000:
			vA = (int) buf[1];
			// vB = 0|(buf[3]<<16)|(buf[2]<<24);
			vB = 0 | (buf[2] << 16) | ((ut32)buf[3] << 24);
			if (buf[0] == 0x19) { // const-wide/high16
				snprintf (str, sizeof (str), " v%i:v%i, 0x%08x", vA, vA + 1, vB);
			} else {
				snprintf (str, sizeof (str), " v%i, 0x%08x", vA, vB);
			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAAcBBBBBBBBBBBBBBBB:
			vA = (int) buf[1];
			ut64 lB = (ut64)buf[2] | ((ut64)buf[3] << 8)|
				((ut64)buf[4] << 16) | ((ut64)buf[5] << 24)|
				((ut64)buf[6] << 32) | ((ut64)buf[7] << 40)|
				((ut64)(buf[8]&0xff) << 48) | ((ut64)(buf[9]&0xff) << 56);
			snprintf (str, sizeof (str), " v%i:v%i, 0x%"PFMT64x, vA, vA + 1, lB);
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAAvBBvCC:
			vA = (int) buf[1];
			vB = (int) buf[2];
			vC = (int) buf[3];
			snprintf (str, sizeof (str), " v%i, v%i, v%i", vA, vB, vC);
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAAvBBcCC:
			vA = (int) buf[1];
			vB = (int) buf[2];
			vC = (int) buf[3];
			snprintf (str, sizeof (str), " v%i, v%i, %#x", vA, vB, vC);
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAvBcCCCC:
			vA = buf[1] & 0x0f;
			vB = (buf[1] & 0xf0) >> 4;
			vC = (buf[3] << 8) | buf[2];
			snprintf (str, sizeof (str), " v%i, v%i, %#x", vA, vB, vC);
			strasm = r_str_append (strasm, str);
			break;
		case fmtoppAA:
			vA = (signed char) buf[1];
			//snprintf (str, sizeof (str), " %i", vA*2); // vA : word -> byte
			snprintf (str, sizeof (str), " 0x%08"PFMT64x, addr + (vA * 2)); // vA : word -> byte
			strasm = r_str_append (strasm, str);
			break;
		case fmtoppAAAA:
			vA = (short) (buf[3] << 8 | buf[2]);
			snprintf (str, sizeof (str), " 0x%08"PFMT64x, addr + (vA * 2)); // vA : word -> byte
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAApBBBB: // if-*z
			vA = (int) buf[1];
			vB = (int) (buf[3] << 8 | buf[2]);
			//snprintf (str, sizeof (str), " v%i, %i", vA, vB);
			snprintf (str, sizeof (str), " v%i, 0x%08"PFMT64x, vA, addr + (vB * 2));
			strasm = r_str_append (strasm, str);
			break;
		case fmtoppAAAAAAAA:
			vA = (int) (buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24));
			//snprintf (str, sizeof (str), " %#08x", vA*2); // vA: word -> byte
			snprintf (str, sizeof (str), " 0x%08"PFMT64x, addr + (vA*2)); // vA : word -> byte
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAvBpCCCC: // if-*
			vA = buf[1] & 0x0f;
			vB = (buf[1] & 0xf0) >> 4;
			vC = (int) (buf[3] << 8 | buf[2]);
			//snprintf (str, sizeof (str), " v%i, v%i, %i", vA, vB, vC);
			snprintf (str, sizeof (str)," v%i, v%i, 0x%08"PFMT64x, vA, vB, addr + (vC * 2));
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAApBBBBBBBB:
			vA = buf[1];
			vB = buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24);
			snprintf (str, sizeof (str), " v%i, 0x%08"PFMT64x, vA, addr + (vB * 2) + 8);
			strasm = r_str_append (strasm, str);
			break;
		case fmtoptinlineI:
			vA = (int) (buf[1] & 0x0f);
			vB = (buf[3] << 8) | buf[2];
			*str = 0;
			switch (vA) {
			case 1:
				snprintf (str, sizeof (str), " {v%i}", buf[4] & 0x0f);
				break;
			case 2:
				snprintf (str, sizeof (str), " {v%i, v%i}", buf[4] & 0x0f, (buf[4] & 0xf0) >> 4);
				break;
			case 3:
				snprintf (str, sizeof (str), " {v%i, v%i, v%i}", buf[4] & 0x0f, (buf[4] & 0xf0) >> 4, buf[5] & 0x0f);
				break;
			case 4:
				snprintf (str, sizeof (str), " {v%i, v%i, v%i, v%i}", buf[4] & 0x0f,
						(buf[4] & 0xf0) >> 4, buf[5] & 0x0f, (buf[5] & 0xf0) >> 4);
				break;
			default:
				snprintf (str, sizeof (str), " {}");
			}
			strasm = r_str_append (strasm, str);
			snprintf (str, sizeof (str), ", [%04x]", vB);
			strasm = r_str_append (strasm, str);
			break;
		case fmtoptinlineIR:
		case fmtoptinvokeVSR:
			vA = (int) buf[1];
			vB = (buf[3] << 8) | buf[2];
			vC = (buf[5] << 8) | buf[4];
			snprintf (str, sizeof (str), " {v%i..v%i}, [%04x]", vC, vC + vA - 1, vB);
			strasm = r_str_append (strasm, str);
			break;
		case fmtoptinvokeVS:
			vA = (int) (buf[1] & 0xf0) >> 4;
			vB = (buf[3] << 8) | buf[2];
			switch (vA) {
			case 1:
				snprintf (str, sizeof (str), " {v%i}", buf[4] & 0x0f);
				break;
			case 2:
				snprintf (str, sizeof (str), " {v%i, v%i}", buf[4] & 0x0f, (buf[4] & 0xf0) >> 4);
				break;
			case 3:
				snprintf (str, sizeof (str), " {v%i, v%i, v%i}", buf[4] & 0x0f,
						(buf[4] & 0xf0) >> 4, buf[5] & 0x0f);
				break;
			case 4:
				snprintf (str, sizeof (str), " {v%i, v%i, v%i, v%i}", buf[4] & 0x0f,
						(buf[4] & 0xf0) >> 4, buf[5] & 0x0f, (buf[5] & 0xf0) >> 4);
				break;
			default:
				snprintf (str, sizeof (str), " {}");
				break;
			}
			strasm = r_str_append (strasm, str);
			snprintf (str, sizeof (str), ", [%04x]", vB);
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAAtBBBB: // "sput-*"
			vA = (int) buf[1];
			vB = (buf[3] << 8) | buf[2];
			if (buf[0] == 0x1a) {
				offset = _anal_get_offset (a, 's', vB);
				if (offset == UT64_MAX) {
					snprintf (str, sizeof (str), " v%i, string+%i", vA, vB);
				} else {
					snprintf (str, sizeof (str), " v%i, 0x%"PFMT64x, vA, offset);
				}
			} else if (buf[0] == 0x1c || buf[0] == 0x1f || buf[0] == 0x22) {
				flag_str = _anal_get_name (a, 'c', vB);
				if (!flag_str) {
					snprintf (str, sizeof (str), " v%i, class+%i", vA, vB);
				} else {
					snprintf (str, sizeof (str), " v%i, %s", vA, flag_str);
				}
			} else {
				flag_str = _anal_get_name (a, 'f', vB);
				if (!flag_str) {
					snprintf (str, sizeof (str), " v%i, field+%i", vA, vB);
				} else {
					snprintf (str, sizeof (str), " v%i, %s", vA, flag_str);
				}
			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtoptopvAvBoCCCC:
			vA = (buf[1] & 0x0f);
			vB = (buf[1] & 0xf0) >> 4;
			vC = (buf[3]<<8) | buf[2];
			offset = _anal_get_offset (a, 'o', vC);
			if (offset == UT64_MAX) {
				snprintf (str, sizeof (str), " v%i, v%i, [obj+%04x]", vA, vB, vC);
			} else {
				snprintf (str, sizeof (str), " v%i, v%i, [0x%"PFMT64x"]", vA, vB, offset);
			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtopAAtBBBB:
			vA = (int) buf[1];
			vB = (buf[3] << 8) | buf[2];
			offset = _anal_get_offset (a, 't', vB);
			if (offset == UT64_MAX) {
				snprintf (str, sizeof (str), " v%i, thing+%i", vA, vB);
			} else {
				snprintf (str, sizeof (str), " v%i, 0x%"PFMT64x, vA, offset);
			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAvBtCCCC:
			vA = (buf[1] & 0x0f);
			vB = (buf[1] & 0xf0) >> 4;
			vC = (buf[3] << 8) | buf[2];
			if (buf[0] == 0x20 || buf[0] == 0x23) { //instance-of & new-array
				flag_str = _anal_get_name (a, 'c', vC);
				if (flag_str) {
					snprintf (str, sizeof (str), " v%i, v%i, %s", vA, vB, flag_str);
				} else {
					snprintf (str, sizeof (str), " v%i, v%i, class+%i", vA, vB, vC);
				}
			} else {
				flag_str = _anal_get_name (a, 'f', vC);
				if (flag_str) {
					snprintf (str, sizeof (str), " v%i, v%i, %s", vA, vB, flag_str);
				} else {
					snprintf (str, sizeof (str), " v%i, v%i, field+%i", vA, vB, vC);
				}
			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvAAtBBBBBBBB:
			vA = (int) buf[1];
			vB = (int) (buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24));
			offset = _anal_get_offset (a, 's', vB);
			if (offset == UT64_MAX) {
				snprintf (str, sizeof (str), " v%i, string+%i", vA, vB);
			} else {
				snprintf (str, sizeof (str), " v%i, 0x%"PFMT64x, vA, offset);
			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvCCCCmBBBB:
			vA = (int) buf[1];
			vB = (buf[3] << 8) | buf[2];
			vC = (buf[5] << 8) | buf[4];
			if (buf[0] == 0x25) { // filled-new-array/range
				flag_str = _anal_get_name (a, 'c', vB);
				if (flag_str) {
					snprintf (str, sizeof (str), " {v%i..v%i}, %s", vC, vC + vA - 1, flag_str);
				}
				else {
					snprintf (str, sizeof (str), " {v%i..v%i}, class+%i", vC, vC + vA - 1, vB);
				}
			} else if (buf[0] == 0xfd) { // invoke-custom/range
				flag_str = _anal_get_name (a, 's', vB);
				if (flag_str) {
					snprintf (str, sizeof (str), " {v%i..v%i}, %s", vC, vC + vA - 1, flag_str);
				}
				else {
					snprintf (str, sizeof (str), " {v%i..v%i}, call_site+%i", vC, vC + vA - 1, vB);
				}
			} else {
				flag_str = _anal_get_name (a, 'm', vB);
				if (flag_str) {
					snprintf (str, sizeof (str), " {v%i..v%i}, %s", vC, vC + vA - 1, flag_str);
				}
				else {
					snprintf (str, sizeof (str), " {v%i..v%i}, method+%i", vC, vC + vA - 1, vB);
				}
			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtopvXtBBBB:
			vA = (int) (buf[1] & 0xf0) >> 4;
			vB = (buf[3] << 8) | buf[2];
			switch (vA) {
				case 1:
					snprintf (str, sizeof (str), " {v%i}", buf[4] & 0x0f);
					break;
				case 2:
					snprintf (str, sizeof (str), " {v%i, v%i}", buf[4] & 0x0f, (buf[4] & 0xf0) >> 4);
					break;
				case 3:
					snprintf (str, sizeof (str), " {v%i, v%i, v%i}", buf[4] & 0x0f,
							(buf[4] & 0xf0) >> 4, buf[5] & 0x0f);
					break;
				case 4:
					snprintf (str, sizeof (str), " {v%i, v%i, v%i, v%i}", buf[4] & 0x0f,
							(buf[4] & 0xf0) >> 4, buf[5] & 0x0f, (buf[5] & 0xf0) >> 4);
					break;
				case 5:
					snprintf (str, sizeof (str), " {v%i, v%i, v%i, v%i, v%i}", buf[4] & 0x0f,
							(buf[4] & 0xf0) >> 4, buf[5] & 0x0f, (buf[5] & 0xf0) >> 4, buf[1] & 0x0f); // TOODO: recheck this
					break;
				default:
					snprintf (str, sizeof (str), " {}");
			}
			strasm = r_str_append (strasm, str);
			if (buf[0] == 0x24) { // filled-new-array
				flag_str = _anal_get_name (a, 'c', vB);
				if (flag_str) {
					snprintf (str, sizeof (str), ", %s ; 0x%x", flag_str, vB);
				} else {
					snprintf (str, sizeof (str), ", class+%i", vB);
				}
			} else if (buf[0] == 0xfc) { // invoke-custom
				flag_str = _anal_get_name (a, 's', vB);
				if (flag_str) {
					snprintf (str, sizeof (str), ", %s ; 0x%x", flag_str, vB);
				} else {
					snprintf (str, sizeof (str), ", call_site+%i", vB);
				}
			} else { // invoke-kind
				flag_str = _anal_get_name (a, 'm', vB);
				if (flag_str) {
					snprintf (str, sizeof (str), ", %s ; 0x%x", flag_str, vB);
				} else {
					snprintf (str, sizeof (str), ", method+%i", vB);
				}

			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtop45CC:
			vA = (buf[1] & 0xf0) >> 4;
			vG = (buf[1] & 0x0f);
			vB = (buf[3] << 8) | buf[2];
			vD = (buf[4] & 0xf0) >> 4;
			vC = (buf[4] & 0x0f);
			vF = (buf[5] & 0xf0) >> 4;
			vE = (buf[5] & 0x0f);
			vH = (buf[7] << 8) | buf[6];

			switch (vA) {
			case 1:
				snprintf (str, sizeof (str), " {v%d}", vC);
				break;
			case 2:
				snprintf (str, sizeof (str), " {v%d, v%d}", vC, vD);
				break;
			case 3:
				snprintf (str, sizeof (str), " {v%d, v%d, v%d}", vC, vD, vE);
				break;
			case 4:
				snprintf (str, sizeof (str), " {v%d, v%d, v%d, v%d}", vC, vD, vE, vF);
				break;
			case 5:
				snprintf (str, sizeof (str), " {v%d, v%d, v%d, v%d, v%d}", vC, vD, vE, vF, vG);
				break;
			default:
				snprintf (str, sizeof (str), " %d", vC);
				break;
			}
			strasm = r_str_append (strasm, str);

			flag_str = _anal_get_name (a, 'm', vB);
			if (flag_str) {
				strasm = r_str_appendf (strasm, ", %s", flag_str);
			} else {
				strasm = r_str_appendf (strasm, ", method+%i", vB);
			}

			flag_str = _anal_get_name (a, 'p', vH);
			if (flag_str) {
				strasm = r_str_appendf (strasm, ", %s", flag_str);
			} else {
				strasm = r_str_appendf (strasm, ", proto+%i", vH);
			}
			break;
		case fmtop4RCC:
			vA = (int) buf[1];
			vB = (buf[3] << 8) | buf[2];
			vC = (buf[5] << 8) | buf[4];
			vH = (buf[7] << 8) | buf[6];
			flag_str = _anal_get_name (a, 'm', vB);
			if (flag_str) {
				snprintf (str, sizeof (str), " {v%i..v%i}, %s", vC, vC + vA - 1, flag_str);
			} else {
				snprintf (str, sizeof (str), " {v%i..v%i}, method+%i", vC, vC + vA - 1, vB);
			}
			strasm = r_str_append (strasm, str);

			flag_str = _anal_get_name (a, 'p', vH);
			if (flag_str) {
				snprintf (str, sizeof (str), ", %s", flag_str);
			} else {
				snprintf (str, sizeof (str), ", proto+%i", vH);
			}
			strasm = r_str_append (strasm, str);
			break;
		case fmtoptinvokeI: // Any opcode has this formats
		case fmtoptinvokeIR:
		case fmt00:
		default:
			R_FREE (strasm);
			size = 2;
		}
		op->mnemonic = strdup (r_str_get_fail (strasm, "invalid"));
	} else if (len > 0) {
		op->mnemonic = strdup ("invalid");
		size = len;
	}

	if (size + payload < 0) {
		op->size = 0;
	} else if (size + payload >= len) {
		op->size = len;
	} else {
		op->size = size + payload;
	}

	free (strasm);
	free (flag_str);

	return size;
}

static bool decode(RArchSession *as, RAnalOp *op, RArchDecodeMask mask) {
	const ut64 addr = op->addr;
	const ut8 *data = op->bytes;
	const int len = op->size;
	R_RETURN_VAL_IF_FAIL (as && op && data && len > 0, -1);

	int sz = dalvik_opcodes[data[0]].len;
	if (!op || sz > len) {
		if (mask & R_ARCH_OP_MASK_DISASM) {
			op->mnemonic = strdup ("invalid");
		}
		return false;
	}

	op->addr = addr;
	op->size = sz;
	op->nopcode = 1; // Necessary??
	op->id = data[0];
	RArch *a = as->arch;

	ut32 vA = 0;
	ut32 vB = 0;
	ut32 vC = 0;
	if (len > 3) {
		vA = data[1];
		vB = data[2];
		vC = data[3];
	}
	switch (data[0]) {
	case 0x00:
		// TODO handle 2-byte instructions like in dalvik_disassemble()
		op->type = R_ANAL_OP_TYPE_NOP;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, ",");
		}
		break;
	case 0x01: // move
	case 0x07: // move-object
		{
			format12x(len, data, &vA, &vB);
			if (vA == vB) {
				op->type = R_ANAL_OP_TYPE_NOP;
				if (mask & R_ARCH_OP_MASK_ESIL) {
					esilprintf (op, ",");
				}
			} else {
				op->type = R_ANAL_OP_TYPE_MOV;
				//op->stackop = R_ANAL_STACK_SET;
				//op->ptr = -vA;
				if (mask & R_ARCH_OP_MASK_ESIL) {
					esilprintf (op, "v%u,v%u,=", vB, vA);
				}
			}
		}
		break;
	case 0x04: // mov-wide
		{
			format12x(len, data, &vA, &vB);
			if (vA == vB) {
				op->type = R_ANAL_OP_TYPE_NOP;
				if (mask & R_ARCH_OP_MASK_ESIL) {
					esilprintf (op, ",");
				}
			} else {
				op->type = R_ANAL_OP_TYPE_MOV;
				//op->stackop = R_ANAL_STACK_SET;
				//op->ptr = -vA;
				if (mask & R_ARCH_OP_MASK_ESIL) {
					esilprintf (op, "v%u,v%u,=,v%u,v%u,=", vB, vA, vB+1, vA+1);
				}
			}
		}
		break;
	case 0x02: // move/from16
	case 0x08: // move-object/from16
		op->type = R_ANAL_OP_TYPE_MOV;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format22x(len, data, &vA, &vB);
			esilprintf (op, "v%u,v%u,=", vB, vA);
		}
		break;
	case 0x05: // move-wide/from16
		op->type = R_ANAL_OP_TYPE_MOV;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format22x(len, data, &vA, &vB);
			esilprintf (op, "v%u,v%u,=,v%u,v%u,=", vB, vA, vB+1, vA+1);
		}
		break;
	case 0x03: // move/16
	case 0x09: // move-object/16
		op->type = R_ANAL_OP_TYPE_MOV;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format32x(len, data, &vA, &vB);
			esilprintf (op, "v%u,v%u,=", vB, vA);
		}
		break;
	case 0x06: // mov-wide/16
		op->type = R_ANAL_OP_TYPE_MOV;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format32x(len, data, &vA, &vB);
			esilprintf (op, "v%u,v%u,=,v%u,v%u,=", vB, vA, vB+1, vA+1);
		}
		break;
	case 0x0a: // move-result
	case 0x0b: // move-result-wide
	case 0x0c: // move-result-object
	case 0x0d: // move-exception
	 	// TODO: add MOVRET OP TYPE ??
		op->type = R_ANAL_OP_TYPE_MOV;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format11x(len, data, &vA);
			esilprintf (op, "sp,v%u,=[8],8,sp,+=,8", vA);
		}
		break;
	case 0x0e: // return-void
	case 0x0f: // return
	case 0x10: // return-wide
	case 0x11: // return-object
	case 0xf1: // return-void-barrier
		op->type = R_ANAL_OP_TYPE_RET;
		op->eob = true;
		//TODO: handle return if (0x0e) {} else {}
		if (mask & R_ARCH_OP_MASK_ESIL) {
			if (data[0] == 0x0e) {// return-void
				esilprintf (op, "sp,[8],ip,=,8,sp,+=");
			} else {
				ut32 vA = data[1];
				esilprintf (op, "sp,[8],ip,=,8,sp,+=,8,sp,-=,v%d,sp,=[8]", vA);
			}
		}
		break;
	case 0x12: // const/4
		{
			op->type = R_ANAL_OP_TYPE_MOV;
			format11n(len, data, &vA, &vB);
			op->val = vB;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				esilprintf (op, "%d,v%u,=", (st32)vB, vA);
			}
		}
		break;
	case 0x13: // const/16
		op->type = R_ANAL_OP_TYPE_MOV;
		format21s(len, data, &vA, &vB);
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, "%d,v%u,=", (st32)vB, vA);
		}
		op->val = vB;
		break;
	case 0x14: // const
		op->type = R_ANAL_OP_TYPE_MOV;
		format31i(len, data, &vA, &vB);
		op->val = vB;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, "%d,v%u,=", (st32)vB, vA);
		}
		break;
	case 0x15: // const/high16
		op->type = R_ANAL_OP_TYPE_MOV;
		format21hw(len, data, &vA, &vB);
		op->val = vB;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, "%d,v%u,=", (st32)vB, vA);
		}
		break;
	case 0x16: // const-wide/16
		op->type = R_ANAL_OP_TYPE_MOV;
		format21s(len, data, &vA, &vB);
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, "%" PFMT64d "," SETWIDE, (st64)(st32)vB, vA, vA+1);
		}
		op->val = vB;
		break;
	case 0x17: // const-wide/32
		op->type = R_ANAL_OP_TYPE_MOV;
		format31i(len, data, &vA, &vB);
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, "%" PFMT64d "," SETWIDE, (st64)(st32)vB, vA, vA+1);
		}
		op->val = vB;
		break;
	case 0x18: // const-wide
		{
			op->type = R_ANAL_OP_TYPE_MOV;
			st64 vB = 0;
			format51l(len, data, &vA, &vB);
			if (mask & R_ARCH_OP_MASK_ESIL) {
				esilprintf (op, "%" PFMT64d "," SETWIDE, vB, vA, vA+1);
			}
			op->val = vB;
			break;
		}
	case 0x19: // const-wide/high16
		{
			// 180001000101.  const-wide v0:v1, 0x18201cd01010001
			op->type = R_ANAL_OP_TYPE_MOV;
			st64 vB = 0;
			format21hd(len, data, &vA, &vB);
			if (mask & R_ARCH_OP_MASK_ESIL) {
				esilprintf (op, "%" PFMT64d "," SETWIDE, vB, vA, vA+1);
			}
			op->val = vB;
			break;
		}
	case 0x1a: // const-string
		op->type = R_ANAL_OP_TYPE_MOV;
		op->datatype = R_ANAL_DATATYPE_STRING;
		if (len > 2) {
			format21c(len, data, &vA, &vB);
			ut64 offset = _anal_get_offset (a, 's', vB);
			op->ptr = offset;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				esilprintf (op, "0x%"PFMT64x",v%u,=", offset, vA);
			}
		}
		break;
	case 0x1b: // const-string
		{
			op->type = R_ANAL_OP_TYPE_MOV;
			op->datatype = R_ANAL_DATATYPE_STRING;
			format31c(len, data, &vA, &vB);
			ut64 offset = _anal_get_offset (a, 's', vB);
			op->ptr = offset;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				esilprintf (op, "0x%"PFMT64x",v%u,=", offset, vA);
			}
			break;
		}
	case 0x1c: // const-class
		{
			op->type = R_ANAL_OP_TYPE_MOV;
			op->datatype = R_ANAL_DATATYPE_CLASS;
			format21c(len, data, &vA, &vB);
			ut64 offset = _anal_get_offset (a, 's', vB);
			op->ptr = offset;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				esilprintf (op, "0x%"PFMT64x",v%u,=", offset, vA);
			}
			break;
		}
	case 0x1d: // monitor-enter
		op->type = R_ANAL_OP_TYPE_PUSH;
		op->stackop = R_ANAL_STACK_INC;
		op->stackptr = 1;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, ",");
		}
		break;
	case 0x1e: // monitor-exit /// wrong type?
		op->type = R_ANAL_OP_TYPE_POP;
		op->stackop = R_ANAL_STACK_INC;
		op->stackptr = -1;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, ",");
		}
		break;
	// we are going to completely ignore exception stuff
	case 0x1f: // check-cast
		op->type = R_ANAL_OP_TYPE_CMP;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, ",");
		}
		break;
	case 0x20: // instance-of
		op->type = R_ANAL_OP_TYPE_CMP;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, "%d,instanceof,%d,-,!,v%u,=", vC, vB, vA);
		}
		break;
	case 0x21: // array-length
		op->type = R_ANAL_OP_TYPE_LENGTH;
		op->datatype = R_ANAL_DATATYPE_ARRAY;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format12x(len, data, &vA, &vB);
			esilprintf (op, "v%d,arraylength,v%d,=", vB, vA);
		}
		break;
	case 0x22: // new-instance
		op->type = R_ANAL_OP_TYPE_NEW;

		// resolve class name for vB
		format21c(len, data, &vA, &vB);
		ut64 off = _anal_get_offset (a, 't', vB);
		op->ptr = off;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, "%" PFMT64u ",new,v%d,=", off, vA);
		}
		break;
	case 0x23: // new-array
		op->type = R_ANAL_OP_TYPE_NEW;
		// 0x1c, 0x1f, 0x22
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format22c (len, data, &vA, &vB, &vC);
			esilprintf (op, "%u,%u,newarray,v%u,=",vC, vB, vA);
		}
		break;
	case 0x24: // filled-new-array
	case 0x25: // filled-new-array-range
	case 0x26: // filled-new-array-data
		op->type = R_ANAL_OP_TYPE_NEW;
		// 0x1c, 0x1f, 0x22
		/*if (len > 2 && mask & R_ARCH_OP_MASK_ESIL) {
			format35c(data, &vA, &vB, &vC);
			esilprintf (op, "%u,%u,newarray,v%u,=",vC, vB, vA);
		}*/
		break;
	case 0x27: // throw
		op->type = R_ANAL_OP_TYPE_TRAP;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format11x (len, data, &vA);
			esilprintf (op, "0, v%u,TRAP", vA);
		}
		break;
	case 0x28: // goto
		if (len > 1) {
			op->jump = addr + ((char)data[1])*2;
			op->type = R_ANAL_OP_TYPE_JMP;
			op->eob = true;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				esilprintf (op, "0x%"PFMT64x",ip,=", op->jump);
			}
		}
		break;
	case 0x29: // goto/16
		if (len > 3) {
			op->jump = addr + (short)(data[2]|data[3]<<8)*2;
			op->type = R_ANAL_OP_TYPE_JMP;
			op->eob = true;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				esilprintf (op, "0x%"PFMT64x",ip,=", op->jump);
			}
		}
		break;
	case 0x2a: // goto/32
		if (len > 5) {
			st64 dst = (st64)(data[2]|(data[3]<<8)|(data[4]<<16)|((ut32)data[5]<<24));
			op->jump = addr + (dst * 2);
			op->type = R_ANAL_OP_TYPE_JMP;
			op->eob = true;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				esilprintf (op, "0x%"PFMT64x",ip,=", op->jump);
			}
		}
		break;
	case 0x2b:
	case 0x2c:
		op->type = R_ANAL_OP_TYPE_SWITCH;
		break;
	case 0x2d: // cmpl-float
	case 0x2e: // cmpg-float
	//case 0x3f: // cmpg-float // ???? wrong disasm imho 2e0f12003f0f
		op->type = R_ANAL_OP_TYPE_CMP;
		op->family = R_ANAL_OP_FAMILY_FPU;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format23x(len, data, &vA, &vB, &vC);
			esilprintf (op, "32,v%u,F2D,32,v%u,F2D,F<=,?{,32,v%u,F2D,32,v%u,F2D,F==,!,-1,*,}{,1,},v%u,=", vC, vB, vC, vB, vA);
		}
		break;
	case 0x2f: // cmpl-double
	case 0x30: // cmlg-double
		op->type = R_ANAL_OP_TYPE_CMP;
		op->family = R_ANAL_OP_FAMILY_FPU;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format23x(len, data, &vA, &vB, &vC);
			esilprintf (op, "v%u,v%u,F<=,?{,v%u,v%u,F==,!,-1,*,}{,1,},v%u,=", vC, vB, vC, vB, vA);
		}
		break;
	case 0x31: // cmp-long
		op->type = R_ANAL_OP_TYPE_CMP;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format23x(len, data, &vA, &vB, &vC);
			// weird expression but should work
			esilprintf (op, GETWIDE "," GETWIDE ",-,DUP,0,<=,?{,1,}{,-1,},SWAP,!,?{,0,},v%u,=", vC+1, vC, vB+1, vB, vA);
		}
		break;
	case 0x32: // if-eq
	case 0x33: // if-ne
	case 0x34: // if-lt
	case 0x35: // if-ge
	case 0x36: // if-gt
	case 0x37: // if-le
		op->type = R_ANAL_OP_TYPE_CJMP;
		//XXX fix this better the check is to avoid an oob
		if (len > 2) {
			format22t(len, data, &vA, &vB, &vC);
			op->jump = addr + vC; //(len>3?(short)(data[2]|data[3]<<8)*2 : 0);
			op->fail = addr + sz;
			op->eob = true;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				const char *cond = getCond (data[0]);
				esilprintf (op, "v%u,v%u,%s,?{,%"PFMT64d",ip,=,}", vB, vA, cond, op->jump);
			}
		}
		break;
	case 0x38: // if-eqz
	case 0x39: // if-nez
	case 0x3a: // if-ltz
	case 0x3b: // if-gez
	case 0x3c: // if-gtz
	case 0x3d: // if-lez
	//case 0x3e: // glitch 0 width instruction .. invalid instruction
		op->type = R_ANAL_OP_TYPE_CJMP;
		//XXX fix this better the check is to avoid an oob
		if (len > 2) {
			format21t(len, data, &vA, &vB);
			op->jump = addr + vB; //(len>3?(short)(data[2]|data[3]<<8)*2 : 0);
			op->fail = addr + sz;
			op->eob = true;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				const char *cond = getCond (data[0]);
				esilprintf (op, "0,v%u,%s,?{,%"PFMT64d",ip,=,}", vA, cond, op->jump);
			}
		}
		break;
	case 0x43:
		op->type = R_ANAL_OP_TYPE_ILL;
		op->size = 1;
		op->eob = true;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, ",");
		}
		break;
	case 0x44: // aget
	case 0x45: // aget-bool
	case 0x46:
	case 0x47: // aget-bool
	case 0x48: // aget-byte
	case 0x49: // aget-char
	case 0x4a: // aget-short
	case 0x52: // get
	case 0x58: // iget-short
	case 0x53: // iget-wide
	case 0x56: // iget-byte
	case 0x57: // iget-char
	case 0xea: // sget-wide-volatile
	case 0xf4: // iget-byte
	case 0x66: // sget-short
	case 0xfd: // sget-object
	case 0x55: // iget-bool
	case 0x60: // sget
	case 0x61: //
	case 0x64: // sget-byte
	case 0x65: // sget-char
	case 0xe3: // iget-volatile
	case 0xe4: //
	case 0xe5: // sget
	case 0xe6: // sget
	case 0xe7: // iget-object-volatile
	case 0xe8: // iget-bool
	case 0xf3: // iget-bool
	case 0xf8: // iget-bool
	case 0xf2: // iget-quick
		//op->type = R_ANAL_OP_TYPE_LOAD;
		//break;
	case 0x54: // iget-object
		op->type = R_ANAL_OP_TYPE_LOAD;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			ut32 vA = (data[1] & 0x0f);
			ut32 vB = (data[1] & 0xf0) >> 4;
			ut32 vC = (data[2] & 0x0f);
			esilprintf (op, "%d,v%d,iget,v%d,=", vC, vB, vA);
		}
		break;
	case 0x62: // sget-object
		{
			op->datatype = R_ANAL_DATATYPE_OBJECT;
			op->type = R_ANAL_OP_TYPE_LOAD;
			ut32 vC = len > 3?(data[3] << 8) | data[2] : 0;
			op->ptr = _anal_get_offset (a, 'f', vC);
			if (mask & R_ARCH_OP_MASK_ESIL) {
				ut32 vA = (data[1] & 0x0f);
				esilprintf (op, "%" PFMT64d ",v%d,=", op->ptr, vA);
			}
		}
		break;
	case 0x63: // sget-boolean
		op->datatype = R_ANAL_DATATYPE_BOOLEAN;
		op->type = R_ANAL_OP_TYPE_LOAD;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			ut32 vA = (data[1] & 0x0f);
			ut32 vB = (data[1] & 0xf0) >> 4;
			ut32 vC = (data[2] & 0x0f);
			const char *vT = "-boolean";
			esilprintf (op, "%d,%d,sget%s,v%d,=", vC, vB, vT, vA);
		}
		break;
	case 0x4b: // aput
	case 0x4c: // aput-wide
	case 0x4d: // aput-object
	case 0x4e: // aput-bool
	case 0x4f: // aput-byte
	case 0x50: // aput-char
	case 0x51: // aput-short
	case 0x5c: // iput-bool
	case 0x5e: // iput-char
	case 0x5f: // iput-wide
	case 0x59: // iput-wide
	case 0x5a: // iput-wide
	case 0x5b: // iput-wide
	case 0x5d: // iput-wide
	case 0x67: // iput-wide
	case 0x68: // sput-wide
	case 0x69: // sput-object
	case 0x6a: // sput-boolean
	case 0x6b: // sput-byte
	case 0x6c: // sput-wide
	case 0x6d: // sput-short
	case 0xe9: // iput-wide-volatile
	case 0xeb: // sput-wide-volatile
	case 0xf5: // iput-quick
	case 0xf6:
	case 0xfc:
	case 0xfe:
		op->type = R_ANAL_OP_TYPE_STORE;
		vC = len > 3?(data[3] << 8) | data[2] : 0;
		op->ptr = _anal_get_offset (a, 'f', vC);
		if (mask & R_ARCH_OP_MASK_ESIL) {
			ut32 vA = (data[1] & 0x0f);
			esilprintf (op, "%" PFMT64d ",v%u,=", op->ptr, vA);
		}
		break;
	case 0x7b: // neg-int
		op->type = R_ANAL_OP_TYPE_NOT;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format12x(len, data, &vA, &vB);
			esilprintf (op, "v%u,0,-,0xffffffff,&,v%u,=", vB, vA);
		}
		break;
	case 0x7c: // not-int
		op->type = R_ANAL_OP_TYPE_NOT;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format12x(len, data, &vA, &vB);
			esilprintf (op, "0xffffffff,v%u,^,v%u,=", vB, vA);
		}
		break;
	case 0x7d: // neg-long
		op->type = R_ANAL_OP_TYPE_NOT;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format12x(len, data, &vA, &vB);
			esilprintf (op, GETWIDE ",0,-," SETWIDE, vB+1, vB, vA, vA+1);
		}
		break;
	case 0x7e: // not-long
		op->type = R_ANAL_OP_TYPE_NOT;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format12x(len, data, &vA, &vB);
			esilprintf (op, "-1," GETWIDE ",^," SETWIDE, vB+1, vB, vA, vA+1);
		}
		break;
	case 0x7f: // neg-float
		op->type = R_ANAL_OP_TYPE_NOT;
		op->family = R_ANAL_OP_FAMILY_FPU;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format12x(len, data, &vA, &vB);
			esilprintf (op, "32,32,v%u,F2D,0,I2D,F-,D2F,v%u,=", vB, vA);
		}
		break;
	case 0x80: // neg-double
		op->type = R_ANAL_OP_TYPE_NOT;
		op->family = R_ANAL_OP_FAMILY_FPU;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			format12x(len, data, &vA, &vB);
			esilprintf (op, GETWIDE ",0,I2D,F-," SETWIDE, vB+1, vB, vA, vA+1);
		}
		break;
	case 0x81: // int-to-long
	case 0x82: // int-to-float
	case 0x83: // int-to-double
	case 0x84: // long-to-int
	case 0x85: // long-to-float
	case 0x86: // long-to-double
	case 0x87: // float-to-int
	case 0x88: // float-to-long
	case 0x89: // float-to-double
	case 0x8a: // double-to-int
	case 0x8b: // double-to-long
	case 0x8c: // double-to-float
		op->family = R_ANAL_OP_FAMILY_FPU;
		/* fall through */
	case 0x8d: // int-to-byte
	case 0x8e: // int-to-char
	case 0x8f: // int-to-short
		op->type = R_ANAL_OP_TYPE_CAST;
		format12x(len, data, &vA, &vB);

		// do all the casting here
		if (mask & R_ARCH_OP_MASK_ESIL) {
			// op->refptr = 0;
			// many of these might need sign extensions
			switch (data[0]) {
				case 0x81:
					esilprintf (op, "32,v%u,~," SETWIDE, vB, vA, vA+1);
					break;
				case 0x82:
					esilprintf (op, "32,v%u,I2D,D2F,v%u,=", vB, vA);
					break;
				case 0x83:
					esilprintf (op, "v%u,I2D," SETWIDE, vB, vA, vA+1);
					break;
				case 0x84:
					esilprintf (op, "v%u,0xffffffff,&,v%u,=", vB, vA);
					break;
				case 0x85:
					esilprintf (op, "32," GETWIDE ",I2D,D2F,v%u,=", vB+1, vB, vA);
					break;
				case 0x86:
					esilprintf (op, GETWIDE ",I2D," SETWIDE, vB+1, vB, vA, vA+1);
					break;
				case 0x87:
					esilprintf (op, "32,v%u,F2D,D2I,v%u,=", vB, vA);
					break;
				case 0x88:
					esilprintf (op, "32,v%u,F2D,D2I," SETWIDE, vB, vA, vA+1);
					break;
				case 0x89:
					esilprintf (op, "32,v%u,F2D,v%u,=", vB, vA);
					break;
				case 0x8a:
					esilprintf (op, GETWIDE ",D2I,v%u,=", vB+1, vB, vA);
					break;
				case 0x8b:
					esilprintf (op, GETWIDE ",D2I," SETWIDE, vB+1, vB, vA, vA+1);
					break;
				case 0x8c:
					esilprintf (op, "32," GETWIDE ",D2F,v%u,=", vB+1, vB, vA);
					break;
				case 0x8d:
				case 0x8e:
					esilprintf (op, "v%d,0xff,&,v%d,=", vB, vA);
					break;
				case 0x8f:
					esilprintf (op, "v%u,0xffff,&,v%u,=", vB, vA);
					break;
				default:
					break;
			}
		}
		break;
	case 0x90: // add-int
	case 0xb0:
	case 0xd0:
	case 0xd8:
		OPCALL ("+", R_ANAL_OP_TYPE_ADD, OP_INT);
		break;
	case 0x91: // sub-int
	case 0xb1:
	case 0xd1:
	case 0xd9:
		OPCALL ("-", R_ANAL_OP_TYPE_SUB, OP_INT);
		break;
	case 0x92: // mul-int
	case 0xb2:
	case 0xd2:
	case 0xda:
		OPCALL ("*", R_ANAL_OP_TYPE_MUL, OP_INT);
		break;
	case 0x93: // div-int
	case 0xb3:
	case 0xd3:
	case 0xdb:
		OPCALL ("~/", R_ANAL_OP_TYPE_DIV, OP_INT);
		break;
	case 0x94:  // rem-int
	case 0xb4:
	case 0xd4:
	case 0xdc:
		OPCALL ("~%", R_ANAL_OP_TYPE_MOD, OP_INT);
		break;
	case 0x95: // and-int
	case 0xb5:
	case 0xd5:
	case 0xdd:
		OPCALL ("&", R_ANAL_OP_TYPE_AND, OP_INT);
		break;
	case 0x96: // or-int
	case 0xb6:
	case 0xd6:
	case 0xde:
		OPCALL ("|", R_ANAL_OP_TYPE_OR, OP_INT);
		break;
	case 0x97: // xor-int
	case 0xb7:
	case 0xd7:
	case 0xdf:
		OPCALL ("^", R_ANAL_OP_TYPE_XOR, OP_INT);
		break;
	case 0x98: // shl-int
	case 0xb8:
	case 0xe0:
		OPCALL ("<<", R_ANAL_OP_TYPE_SHL, OP_INT);
		break;
	case 0x99: // shr-int
	case 0xb9:
	case 0xe1:
		OPCALL ("ASR", R_ANAL_OP_TYPE_SHR, OP_INT);
		break;
	case 0x9a: // ushr-int
	case 0xba:
	case 0xe2:
		OPCALL (">>", R_ANAL_OP_TYPE_SHR, OP_INT);
		break;
	case 0xbb:
	case 0x9b: // add-long
		OPCALL ("+", R_ANAL_OP_TYPE_ADD, OP_LONG);
		break;
	case 0x9c: // sub-long
	case 0xbc:
		OPCALL ("-", R_ANAL_OP_TYPE_SUB, OP_LONG);
		break;
	case 0x9d: // mul-long
	case 0xbd:
		OPCALL ("*", R_ANAL_OP_TYPE_MUL, OP_LONG);
		break;
	case 0x9e: // div-long
	case 0xbe:
		OPCALL ("~/", R_ANAL_OP_TYPE_DIV, OP_LONG);
		break;
	case 0x9f:  // rem-long
	case 0xbf:
		OPCALL ("~%", R_ANAL_OP_TYPE_MOD, OP_LONG);
		break;
	case 0xa0: // and-long
	case 0xc0:
		OPCALL ("&", R_ANAL_OP_TYPE_AND, OP_LONG);
		break;
	case 0xa1: // or-long
	case 0xc1:
		OPCALL ("|", R_ANAL_OP_TYPE_OR, OP_LONG);
		break;
	case 0xa2: // xor-long
	case 0xc2:
		OPCALL ("^", R_ANAL_OP_TYPE_XOR, OP_LONG);
		break;
	case 0xa3: // shl-long
	case 0xc3:
		OPCALL ("<<", R_ANAL_OP_TYPE_SHL, OP_LONG_SHFT);
		break;
	case 0xa4: // shr-long
	case 0xc4:
		OPCALL ("ASR", R_ANAL_OP_TYPE_SHR, OP_LONG_SHFT);
		break;
	case 0xa5: // ushr-long
	case 0xc5:
		OPCALL (">>", R_ANAL_OP_TYPE_SHR, OP_LONG_SHFT);
		break;
	case 0xa6: // add-float
	case 0xc6:
		OPCALL ("+", R_ANAL_OP_TYPE_ADD, OP_FLOAT);
		break;
	case 0xa7: // sub-float
	case 0xc7:
		OPCALL ("-", R_ANAL_OP_TYPE_SUB, OP_FLOAT);
		break;
	case 0xa8: // mul-float
	case 0xc8:
		OPCALL ("*", R_ANAL_OP_TYPE_MUL, OP_FLOAT);
		break;
	case 0xa9: // div-float
	case 0xaa:
	case 0xc9:
	case 0xca:
		OPCALL ("/", R_ANAL_OP_TYPE_DIV, OP_FLOAT);
		break;
	case 0xab: // add-double
	case 0xcb:
		OPCALL ("+", R_ANAL_OP_TYPE_ADD, OP_DOUBLE);
		break;
	case 0xac: // sub-double
	case 0xcc:
		OPCALL ("-", R_ANAL_OP_TYPE_SUB, OP_DOUBLE);
		break;
	case 0xad: // mul-double
	case 0xcd:
		OPCALL ("*", R_ANAL_OP_TYPE_MUL, OP_DOUBLE);
		break;
	case 0xae: // div-double
	case 0xaf:
	case 0xce:
	case 0xcf:
		OPCALL ("/", R_ANAL_OP_TYPE_DIV, OP_DOUBLE);
		break;

	case 0xec: // breakpoint
		op->type = R_ANAL_OP_TYPE_TRAP;
		if (mask & R_ARCH_OP_MASK_ESIL) {
			esilprintf (op, "TRAP");
		}
		break;
	case 0x73: // invalid
		break;
	case 0x6f: // invoke-super
	case 0x70: // invoke-direct
	case 0x71: // invoke-static
	case 0x72: // invoke-interface
	case 0x77: //
	case 0x6e: // invoke-virtual
		if (len > 2) {
			//XXX fix this better since the check avoid an oob
			//but the jump will be incorrect
			ut32 vB = len > 3?(data[3] << 8) | data[2] : 0;
			ut64 dst = _anal_get_offset (a, 'm', vB);
			if (dst == 0) {
				op->type = R_ANAL_OP_TYPE_UCALL;
			} else {
				op->type = R_ANAL_OP_TYPE_CALL;
				op->jump = dst;
			}
			op->fail = addr + sz;
			if (mask & R_ARCH_OP_MASK_ESIL) {
				// TODO: handle /range instructions
				esilprintf (op, "8,sp,-=,0x%"PFMT64x",sp,=[8],0x%"PFMT64x",ip,=", op->fail, op->jump);
			}
		}
		break;
	case 0x78: // invokeinterface/range
	case 0xf0: // invoke-object-init-range
	case 0xf9: // invoke-virtual-quick/range
	case 0xfb: // invoke-super-quick/range
	case 0x74: // invoke-virtual/range
	case 0x75: // invoke-super/range
	case 0x76: // invoke-direct/range
	case 0xfa: // invoke-super-quick // invoke-polymorphic
		if (len > 2) {
			//XXX fix this better since the check avoid an oob
			//but the jump will be incorrect
			// ut32 vB = len > 3?(data[3] << 8) | data[2] : 3;
			//op->jump = _anal_get_offset (a, 'm', vB);
			op->fail = addr + sz;
			// op->type = R_ANAL_OP_TYPE_CALL;
			op->type = R_ANAL_OP_TYPE_UCALL;
			// TODO: handle /range instructions
			// NOP esilprintf (op, "8,sp,-=,0x%"PFMT64x",sp,=[8],0x%"PFMT64x",ip,=", addr);
		}
		if (mask & R_ARCH_OP_MASK_ESIL) {
			// TODO: handle /range instructions
			esilprintf (op, "8,sp,-=,0x%"PFMT64x",sp,=[8],0x%"PFMT64x",ip,=", op->fail, op->jump);
		}
		break;
	case 0xee: // execute-inline
	case 0xef: // execute-inline/range
		op->type = R_ANAL_OP_TYPE_SWI;
		break;
	case 0xed: // throw-verification-error
		op->type = R_ANAL_OP_TYPE_TRAP;
		break;
	}

	if (mask & R_ARCH_OP_MASK_DISASM) {
		dalvik_disassemble (as, op, addr, data, len, sz);
	}

	return sz > 0;
}

static char *regs(RArchSession *as) {
	RStrBuf *sb = r_strbuf_new (
		"=PC	ip\n"
		"=SP	sp\n"
		"=BP	bp\n"
		"=A0	v0\n"
		"=A1	v1\n"
		"=A2	v2\n"
		"=A3	v3\n"
		"=SN	v0\n"
	);
	int i;
	for (i = 0; i < 256; i++) {
		r_strbuf_appendf (sb, "gpr\tv%d\t.32\t%d\t0\n", i, i * 4);
	}
	r_strbuf_appendf (sb, "gpr\tip\t.32\t%d\t0\n", 256 * 4);
	r_strbuf_appendf (sb, "gpr\tsp\t.32\t%d\t0\n", 256 * 4 + 4);
	r_strbuf_appendf (sb, "gpr\tbp\t.32\t%d\t0\n", 256 * 4 + 8);
	return r_strbuf_drain (sb);
}

static int archinfo(RArchSession *as, ut32 q) {
	switch (q) {
	case R_ARCH_INFO_CODE_ALIGN:
		return 1;
	case R_ARCH_INFO_MAXOP_SIZE:
		return 6;
	case R_ARCH_INFO_INVOP_SIZE:
		return 2;
	case R_ARCH_INFO_MINOP_SIZE:
		return 2;
	case R_ARCH_INFO_ISVM:
		return R_ARCH_INFO_ISVM;
	}
	return -1;
}

const RArchPlugin r_arch_plugin_dalvik = {
	.meta = {
		.name = "dalvik",
		.author = "pancake",
		.desc = "Dalvik (Android VM) bytecode",
		.license = "LGPL-3.0-only",
	},
	.arch = "dalvik",
	.bits = R_SYS_BITS_PACK1 (32),
	.regs = regs,
	.info = archinfo,
	.decode = decode,
};

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