// SPDX-License-Identifier: LGPL-3.0-only

#include <string.h>
#include <rz_types.h>
#include <rz_lib.h>
#include <rz_asm.h>
#include <rz_analysis.h>

/* DEPRECATE ?? */
#include "wine-arm.h"
#include "../asm/arch/arm/asm-arm.h"
#include "../asm/arch/arm/winedbg/be_arm.h"
#include "./analysis_arm_hacks.inc"

static unsigned int disarm_branch_offset(unsigned int pc, unsigned int insoff) {
	unsigned int add = insoff << 2;
	/* zero extend if higher is 1 (0x02000000) */
	if ((add & 0x02000000) == 0x02000000) {
		add |= 0xFC000000;
	}
	return add + pc + 8;
}

#define IS_BRANCH(x)  (((x)&ARM_BRANCH_I_MASK) == ARM_BRANCH_I)
#define IS_BRANCHL(x) (IS_BRANCH(x) && ((x)&ARM_BRANCH_LINK) == ARM_BRANCH_LINK)
#define IS_RETURN(x)  (((x) & (ARM_DTM_I_MASK | ARM_DTM_LOAD | (1 << 15))) == (ARM_DTM_I | ARM_DTM_LOAD | (1 << 15)))
// if ( (inst & ( ARM_DTX_I_MASK | ARM_DTX_LOAD  | ( ARM_DTX_RD_MASK ) ) ) == ( ARM_DTX_LOAD | ARM_DTX_I | ( ARM_PC << 12 ) ) )
#define IS_UNKJMP(x)    ((((ARM_DTX_RD_MASK))) == (ARM_DTX_LOAD | ARM_DTX_I | (ARM_PC << 12)))
#define IS_LOAD(x)      (((x)&ARM_DTX_LOAD) == (ARM_DTX_LOAD))
#define IS_CONDAL(x)    (((x)&ARM_COND_MASK) == ARM_COND_AL)
#define IS_EXITPOINT(x) (IS_BRANCH(x) || IS_RETURN(x) || IS_UNKJMP(x))

#define API static

static int op_thumb(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *data, int len) {
	int op_code;
	ut16 *_ins = (ut16 *)data;
	ut16 ins = *_ins;
	ut32 *_ins32 = (ut32 *)data;
	ut32 ins32 = *_ins32;

	struct winedbg_arm_insn *arminsn = arm_new();
	arm_set_thumb(arminsn, true);
	arm_set_input_buffer(arminsn, data);
	arm_set_pc(arminsn, addr);
	op->delay = 0;
	op->size = arm_disasm_one_insn(arminsn);
	op->jump = arminsn->jmp;
	op->fail = arminsn->fail;
	arm_free(arminsn);

	// TODO: handle 32bit instructions (branches are not correctly decoded //

	/* CMP */
	if (((ins & B4(B1110, 0, 0, 0)) == B4(B0010, 0, 0, 0)) && (1 == (ins & B4(1, B1000, 0, 0)) >> 11)) { // dp3
		op->type = RZ_ANALYSIS_OP_TYPE_CMP;
		return op->size;
	}
	if ((ins & B4(B1111, B1100, 0, 0)) == B4(B0100, 0, 0, 0)) {
		op_code = (ins & B4(0, B0011, B1100, 0)) >> 6;
		if (op_code == 8 || op_code == 10) { // dp5
			op->type = RZ_ANALYSIS_OP_TYPE_CMP;
			return op->size;
		}
	}
	if ((ins & B4(B1111, B1100, 0, 0)) == B4(B0100, B0100, 0, 0)) {
		op_code = (ins & B4(0, B0011, 0, 0)) >> 8; // dp8
		if (op_code == 1) {
			op->type = RZ_ANALYSIS_OP_TYPE_CMP;
			return op->size;
		}
	}
	if (ins == 0xbf) {
		// TODO: add support for more NOP instructions
		op->type = RZ_ANALYSIS_OP_TYPE_NOP;
	} else if (((op_code = ((ins & B4(B1111, B1000, 0, 0)) >> 11)) >= 12 &&
			   op_code <= 17)) {
		if (op_code % 2) {
			op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
		} else {
			op->type = RZ_ANALYSIS_OP_TYPE_STORE;
		}
	} else if ((ins & B4(B1111, 0, 0, 0)) == B4(B0101, 0, 0, 0)) {
		op_code = (ins & B4(0, B1110, 0, 0)) >> 9;
		if (op_code % 2) {
			op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
		} else {
			op->type = RZ_ANALYSIS_OP_TYPE_STORE;
		}
	} else if ((ins & B4(B1111, 0, 0, 0)) == B4(B1101, 0, 0, 0)) {
		// BNE..
		int delta = (ins & B4(0, 0, B1111, B1111));
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		op->jump = addr + 4 + (delta << 1);
		op->fail = addr + 4;
	} else if ((ins & B4(B1111, B1000, 0, 0)) == B4(B1110, 0, 0, 0)) {
		// B
		int delta = (ins & B4(0, 0, B1111, B1111));
		op->type = RZ_ANALYSIS_OP_TYPE_JMP;
		op->jump = addr + 4 + (delta << 1);
		op->fail = addr + 4;
	} else if ((ins & B4(B1111, B1111, B1000, 0)) ==
		B4(B0100, B0111, B1000, 0)) {
		// BLX
		op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
		op->fail = addr + 4;
	} else if ((ins & B4(B1111, B1111, B1000, 0)) ==
		B4(B0100, B0111, 0, 0)) {
		// BX
		op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
		op->fail = addr + 4;
	} else if ((ins & B4(B1111, B1000, 0, 0)) == B4(B1111, 0, 0, 0)) {
		// BL The long branch with link, it's in 2 instructions:
		// prefix: 11110[offset]
		// suffix: 11111[offset] (11101[offset] for blx)
		ut16 nextins = (ins32 & 0xFFFF0000) >> 16;
		ut32 high = (ins & B4(0, B0111, B1111, B1111)) << 12;
		if (ins & B4(0, B0100, 0, 0)) {
			high |= B4(B1111, B1000, 0, 0) << 16;
		}
		int delta = high + ((nextins & B4(0, B0111, B1111, B1111)) * 2);
		op->jump = (int)(addr + 4 + (delta));
		op->type = RZ_ANALYSIS_OP_TYPE_CALL;
		op->fail = addr + 4;
	} else if ((ins & B4(B1111, B1111, 0, 0)) == B4(B1011, B1110, 0, 0)) {
		op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
		op->val = (ut64)(ins >> 8);
	} else if ((ins & B4(B1111, B1111, 0, 0)) == B4(B1101, B1111, 0, 0)) {
		op->type = RZ_ANALYSIS_OP_TYPE_SWI;
		op->val = (ut64)(ins >> 8);
	}
	return op->size;
}

#if 0
"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
"hi", "ls", "ge", "lt", "gt", "le", "al", "nv",
#endif
static int iconds[] = {
	RZ_ANALYSIS_COND_EQ,
	RZ_ANALYSIS_COND_NE,
	0, // cs
	0, // cc
	0, // mi
	0, // pl
	0, // vs
	0, // vc

	0, // hi
	0, // ls
	RZ_ANALYSIS_COND_GE,
	RZ_ANALYSIS_COND_LT,
	RZ_ANALYSIS_COND_GT,
	RZ_ANALYSIS_COND_LE,
	RZ_ANALYSIS_COND_AL,
	RZ_ANALYSIS_COND_NV,
};

static int op_cond(const ut8 *data) {
	ut8 b = data[3] >> 4;
	if (b == 0xf) {
		return 0;
	}
	return iconds[b];
}

static int arm_op32(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *data, int len) {
	const ut8 *b = (ut8 *)data;
	ut8 ndata[4];
	ut32 branch_dst_addr, i = 0;
	ut32 *code = (ut32 *)data;
	struct winedbg_arm_insn *arminsn;

	if (!data) {
		return 0;
	}
	arminsn = arm_new();
	arm_set_thumb(arminsn, false);
	arm_set_input_buffer(arminsn, data);
	arm_set_pc(arminsn, addr);
	op->addr = addr;
	op->type = RZ_ANALYSIS_OP_TYPE_UNK;

	if (analysis->big_endian) {
		b = data = ndata;
		ndata[0] = data[3];
		ndata[1] = data[2];
		ndata[2] = data[1];
		ndata[3] = data[0];
	}
	if (analysis->bits == 16) {
		arm_free(arminsn);
		return op_thumb(analysis, op, addr, data, len);
	}
	op->size = 4;
	op->cond = op_cond(data);
	if (b[2] == 0x8f && b[3] == 0xe2) {
		op->type = RZ_ANALYSIS_OP_TYPE_ADD;
#define ROR(x, y) ((int)((x) >> (y)) | (((x) << (32 - (y)))))
		op->ptr = addr + ROR(b[0], (b[1] & 0xf) << 1) + 8;
	} else if (b[2] >= 0x9c && b[2] <= 0x9f) { // load instruction
		char ch = b[3] & 0xf;
		switch (ch) {
		case 5:
			if ((b[3] & 0xf) == 5) {
				op->ptr = 8 + addr + b[0] + ((b[1] & 0xf) << 8);
				// XXX: if set it breaks the visual disasm
				// op->refptr = true;
			}
		case 4:
		case 6:
		case 7:
		case 8:
		case 9: op->type = RZ_ANALYSIS_OP_TYPE_LOAD; break;
		}
	} else // 0x000037b8  00:0000   0             800000ef  svc 0x00000080
		if (b[2] == 0xa0 && b[3] == 0xe1) {
		int n = (b[0] << 16) + b[1];
		op->type = RZ_ANALYSIS_OP_TYPE_MOV;
		switch (n) {
		case 0:
		case 0x0110:
		case 0x0220:
		case 0x0330:
		case 0x0440:
		case 0x0550:
		case 0x0660:
		case 0x0770:
		case 0x0880:
		case 0x0990:
		case 0x0aa0:
		case 0x0bb0:
		case 0x0cc0:
			op->type = RZ_ANALYSIS_OP_TYPE_NOP;
			break;
		}
	} else if (b[3] == 0xef) {
		op->type = RZ_ANALYSIS_OP_TYPE_SWI;
		op->val = (b[0] | (b[1] << 8) | (b[2] << 2));
	} else if ((b[3] & 0xf) == 5) { // [reg,0xa4]
#if 0
		0x00000000      a4a09fa4 ldrge sl, [pc], 0xa4
		0x00000000      a4a09fa5 ldrge sl, [pc, 0xa4]
		0x00000000      a4a09fa6 ldrge sl, [pc], r4, lsr 1
		0x00000000      a4a09fa7 ldrge sl, [pc, r4, lsr 1]
		0x00000000      a4a09fe8 ldm pc, {
			r2, r5, r7, sp, pc
		}; < UNPREDICT
#endif
		if ((b[1] & 0xf0) == 0xf0) {
			// ldr pc, [pc, #1] ;
			// op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
			op->type = RZ_ANALYSIS_OP_TYPE_RET; // FAKE FOR FUN
			// op->stackop = RZ_ANALYSIS_STACK_SET;
			op->jump = 1234;
			// op->ptr = 4+addr+b[0]; // sure? :)
			// op->ptrptr = true;
		}
		// eprintf("0x%08x\n", code[i] & ARM_DTX_LOAD);
		// 0x0001B4D8,           1eff2fe1        bx    lr
	} else if (b[3] == 0xe2 && b[2] == 0x8d && b[1] == 0xd0) {
		// ADD SP, SP, ...
		op->type = RZ_ANALYSIS_OP_TYPE_ADD;
		op->stackop = RZ_ANALYSIS_STACK_INC;
		op->val = -b[0];
	} else if (b[3] == 0xe2 && b[2] == 0x4d && b[1] == 0xd0) {
		// SUB SP, SP, ..
		op->type = RZ_ANALYSIS_OP_TYPE_SUB;
		op->stackop = RZ_ANALYSIS_STACK_INC;
		op->val = b[0];
	} else if (b[3] == 0xe2 && b[2] == 0x4c && b[1] == 0xb0) {
		// SUB SP, FP, ..
		op->type = RZ_ANALYSIS_OP_TYPE_SUB;
		op->stackop = RZ_ANALYSIS_STACK_INC;
		op->val = -b[0];
	} else if (b[3] == 0xe2 && b[2] == 0x4b && b[1] == 0xd0) {
		// SUB SP, IP, ..
		op->type = RZ_ANALYSIS_OP_TYPE_SUB;
		op->stackop = RZ_ANALYSIS_STACK_INC;
		op->val = -b[0];
	} else if ((code[i] == 0x1eff2fe1) ||
		(code[i] == 0xe12fff1e)) { // bx lr
		op->type = RZ_ANALYSIS_OP_TYPE_RET;
	} else if ((code[i] & ARM_DTX_LOAD)) { // IS_LOAD(code[i])) {
		ut32 ptr = 0;
		op->type = RZ_ANALYSIS_OP_TYPE_MOV;
		if (b[2] == 0x1b) {
			/* XXX pretty incomplete */
			op->stackop = RZ_ANALYSIS_STACK_GET;
			op->ptr = b[0];
			// var_add_access(addr, -b[0], 1, 0); // TODO: set/get (the last 0)
		} else {
			// ut32 oaddr = addr+8+b[0];
			// XXX TODO ret = rizin_read_at(oaddr, (ut8*)&ptr, 4);
			if (analysis->bits == 32) {
				b = (ut8 *)&ptr;
				op->ptr = b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24);
				// XXX data_xrefs_add(oaddr, op->ptr, 1);
				// TODO change data type to pointer
			} else {
				op->ptr = 0;
			}
		}
	}

	if (IS_LOAD(code[i])) {
		op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
		op->refptr = 4;
	}
	if (((((code[i] & 0xff) >= 0x10 && (code[i] & 0xff) < 0x20)) &&
		    ((code[i] & 0xffffff00) == 0xe12fff00)) ||
		IS_EXITPOINT(code[i])) {
		// if (IS_EXITPOINT (code[i])) {
		b = data;
		branch_dst_addr = disarm_branch_offset(
			addr, b[0] | (b[1] << 8) | (b[2] << 16)); // code[i]&0x00FFFFFF);
		op->ptr = 0;
		if ((((code[i] & 0xff) >= 0x10 && (code[i] & 0xff) < 0x20)) &&
			((code[i] & 0xffffff00) == 0xe12fff00)) {
			op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
		} else if (IS_BRANCHL(code[i])) {
			if (IS_BRANCH(code[i])) {
				op->type = RZ_ANALYSIS_OP_TYPE_CALL;
				op->jump = branch_dst_addr;
				op->fail = addr + 4;
			} else {
				op->type = RZ_ANALYSIS_OP_TYPE_RET;
			}
		} else if (IS_BRANCH(code[i])) {
			if (IS_CONDAL(code[i])) {
				op->type = RZ_ANALYSIS_OP_TYPE_JMP;
				op->jump = branch_dst_addr;
				op->fail = UT64_MAX;
			} else {
				op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
				op->jump = branch_dst_addr;
				op->fail = addr + 4;
			}
		} else {
			// unknown jump o return
			// op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
			// op->type = RZ_ANALYSIS_OP_TYPE_NOP;
		}
	}
	// op->jump = arminsn->jmp;
	// op->fail = arminsn->fail;
	arm_free(arminsn);
	return op->size;
}

static ut64 getaddr(ut64 addr, const ut8 *d) {
	if (d[2] >> 7) {
		/// st32 n = (d[0] + (d[1] << 8) + (d[2] << 16) + (0xff << 24));
		st32 n = (d[0] + (d[1] << 8) + (d[2] << 16) + ((ut64)(0xff) << 24)); // * 16777216));
		n = -n;
		return addr - (n * 4);
	}
	return addr + (4 * (d[0] + (d[1] << 8) + (d[2] << 16)));
}

static int arm_op64(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *d, int len) {
	if (d[3] == 0) {
		return -1; // invalid
	}
	int haa = hackyArmAnal(analysis, op, d, len);
	if (haa > 0) {
		return haa;
	}
	op->size = 4;
	op->type = RZ_ANALYSIS_OP_TYPE_NULL;
	if (d[0] == 0xc0 && d[3] == 0xd6) {
		// defaults to x30 reg. but can be different
		op->type = RZ_ANALYSIS_OP_TYPE_RET;
	}
	switch (d[3]) {
	case 0x71:
	case 0xeb:
		op->type = RZ_ANALYSIS_OP_TYPE_CMP;
		break;
	case 0xb8:
	case 0xb9:
	case 0xf8:
	case 0xa9: // ldp/stp
	case 0xf9: // ldr/str
		op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
		break;
	case 0x91: // mov
	case 0x52: // mov
	case 0x94: // bl A
	case 0x97: // bl A
		op->type = RZ_ANALYSIS_OP_TYPE_CALL;
		op->jump = getaddr(addr, d);
		op->fail = addr + 4;
		break;
	case 0x54: // beq A
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		op->jump = addr + (4 * ((d[0] >> 4) | (d[1] << 8) | (d[2] << 16)));
		op->fail = addr + 4;
		break;
	case 0x17: // b A
	case 0x14: // b A
		op->type = RZ_ANALYSIS_OP_TYPE_JMP;
		op->jump = getaddr(addr, d);
		op->fail = addr + 4;
		break;
	}
	return op->size;
}

static int arm_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *data, int len, RzAnalysisOpMask mask) {
	if (analysis->bits == 64) {
		return arm_op64(analysis, op, addr, data, len);
	}
	return arm_op32(analysis, op, addr, data, len);
}

static bool set_reg_profile(RzAnalysis *analysis) {
	// TODO: support 64bit profile
	const char *p32 =
		"=PC	r15\n"
		"=SP	r13\n"
		"=BP	r14\n" // XXX
		"=A0	r0\n"
		"=A1	r1\n"
		"=A2	r2\n"
		"=A3	r3\n"
		"gpr	lr	.32	56	0\n" // r14
		"gpr	pc	.32	60	0\n" // r15

		"gpr	r0	.32	0	0\n"
		"gpr	r1	.32	4	0\n"
		"gpr	r2	.32	8	0\n"
		"gpr	r3	.32	12	0\n"
		"gpr	r4	.32	16	0\n"
		"gpr	r5	.32	20	0\n"
		"gpr	r6	.32	24	0\n"
		"gpr	r7	.32	28	0\n"
		"gpr	r8	.32	32	0\n"
		"gpr	r9	.32	36	0\n"
		"gpr	r10	.32	40	0\n"
		"gpr	r11	.32	44	0\n"
		"gpr	r12	.32	48	0\n"
		"gpr	r13	.32	52	0\n"
		"gpr	r14	.32	56	0\n"
		"gpr	r15	.32	60	0\n"
		"gpr	r16	.32	64	0\n"
		"gpr	r17	.32	68	0\n"
		"gpr	cpsr	.32	72	0\n";
	return rz_reg_set_profile_string(analysis->reg, p32);
}

static int archinfo(RzAnalysis *analysis, int q) {
	if (q == RZ_ANALYSIS_ARCHINFO_ALIGN) {
		if (analysis && analysis->bits == 16) {
			return 2;
		}
		return 4;
	}
	if (q == RZ_ANALYSIS_ARCHINFO_MAX_OP_SIZE) {
		return 4;
	}
	if (q == RZ_ANALYSIS_ARCHINFO_MIN_OP_SIZE) {
		if (analysis && analysis->bits == 16) {
			return 2;
		}
		return 4;
	}
	return 4; // XXX
}

RzAnalysisPlugin rz_analysis_plugin_arm_gnu = {
	.name = "arm.gnu",
	.arch = "arm",
	.license = "LGPL3",
	.bits = 16 | 32 | 64,
	.desc = "ARM code analysis plugin",
	.archinfo = archinfo,
	.op = &arm_op,
	.set_reg_profile = set_reg_profile,
};

#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
	.type = RZ_LIB_TYPE_ANALYSIS,
	.data = &rz_analysis_plugin_arm_gnu,
	.version = RZ_VERSION
};
#endif
