/*
 * Convert from ESIL to REIL (Reverse Engineering Intermediate Language)
 * Contributor: sushant94
 */

#include <r_anal.h>

#define REIL_TEMP_PREFIX "V"
#define REIL_REG_PREFIX "R_"
#define REGBUFSZ 32

void reil_flag_spew_inst(RAnalEsil *esil, const char *flag);
static const char *ops[] = { FOREACHOP(REIL_OP_STRING) };

// Get size of a register.
static ut8 esil_internal_sizeof_reg(RAnalEsil *esil, const char *r) {
	if (!esil || !esil->anal || !esil->anal->reg || !r) {
		return false;
	}
	RRegItem *i = r_reg_get(esil->anal->reg, r, -1);
	return i ? (ut8)i->size : 0;
}

RAnalReilArgType reil_get_arg_type(RAnalEsil *esil, char *s) {
	if (!strncmp (s, REIL_TEMP_PREFIX, strlen (REIL_TEMP_PREFIX))) {
		return ARG_TEMP;
	}
	int type = r_anal_esil_get_parm_type(esil, s);
	switch (type) {
	case R_ANAL_ESIL_PARM_REG:
		return ARG_REG;
	case R_ANAL_ESIL_PARM_NUM:
		return ARG_CONST;
	case R_ANAL_ESIL_PARM_INTERNAL:
		return ARG_ESIL_INTERNAL;
	default:
		return ARG_NONE;
	}
}

// Marshall the struct into a string
void reil_push_arg(RAnalEsil *esil, RAnalReilArg *op) {
	char *s = r_str_newf ("%s:%d", op->name, op->size);
	r_anal_esil_push (esil, s);
	free (s);
}

// Unmarshall the string in stack to the struct.
RAnalReilArg *reil_pop_arg(RAnalEsil *esil) {
	RAnalReilArg *op;
	int i, j = 0, flag = 0, len;
	char tmp_buf[REGBUFSZ];
	char *buf = r_anal_esil_pop(esil);
	if (!buf) {
		return NULL;
	}
	len = strlen (buf);
	op = R_NEW0(RAnalReilArg);
	for (i = 0; i < len; i++) {
		if (buf[i] == ':') {
			tmp_buf[j] = '\0';
			strncpy(op->name, tmp_buf, sizeof(op->name) - 1);
			memset(tmp_buf, 0, sizeof(tmp_buf));
			j = 0;
			flag = 1;
			continue;
		}
		// Strip all spaces
		if (buf[i] == ' ') continue;
		tmp_buf[j] = buf[i];
		j++;
	}
	tmp_buf[j] = '\0';

	// If we have not encountered a ':' we don't know the size yet.
	if (!flag) {
		strncpy(op->name, tmp_buf, sizeof(op->name) - 1);
		op->type = reil_get_arg_type(esil, op->name);
		if (op->type == ARG_REG) {
			op->size = esil_internal_sizeof_reg(esil, op->name);
		} else if (op->type == ARG_CONST) {
			op->size = esil->anal->bits;
		}
		free(buf);
		return op;
	}

	op->size = strtoll(tmp_buf, NULL, 10);
	op->type = reil_get_arg_type(esil, op->name);
	free(buf);
	return op;
}

// Get the next available temp register.
void get_next_temp_reg(RAnalEsil *esil, char *buf) {
	r_snprintf (buf, REGBUFSZ, REIL_TEMP_PREFIX"_%02"PFMT64u,
		esil->Reil->reilNextTemp);
	esil->Reil->reilNextTemp++;
}

void reil_make_arg(RAnalEsil *esil, RAnalReilArg *arg, char *name) {
	if (!arg) return;
	RAnalReilArgType type;
	type = reil_get_arg_type(esil, name);
	arg->size = 0;
	arg->type = type;
	memset(arg->name, 0, sizeof(arg->name));
	strncpy(arg->name, name, sizeof(arg->name) - 1);
}

// Free ins and all its arguments
void reil_free_inst(RAnalReilInst *ins) {
	if (!ins) return;
	if (ins->arg[0]) R_FREE(ins->arg[0]);
	if (ins->arg[1]) R_FREE(ins->arg[1]);
	if (ins->arg[2]) R_FREE(ins->arg[2]);
	R_FREE(ins);
}

// Automatically increments the seq_num of the instruction.
void reil_print_inst(RAnalEsil *esil, RAnalReilInst *ins) {
	char tmp_buf[REGBUFSZ];
	int i;

	if ((!ins) || (!esil)) {
		return;
	}
	esil->anal->cb_printf("%04"PFMT64x".%02"PFMT64x": %8s",
		esil->Reil->addr, esil->Reil->seq_num++, ops[ins->opcode]);
	for (i = 0; i < 3; i++) {
		if (i > 0) {
			esil->anal->cb_printf (" ,");
		}
		if (!ins->arg[i]) {
			continue;
		}
		if (ins->arg[i]->type == ARG_NONE) {
			esil->anal->cb_printf ("%10s   ", ins->arg[i]->name);
			continue;
		}
		if (ins->arg[i]->type == ARG_REG) {
			strncpy (tmp_buf, REIL_REG_PREFIX, sizeof (tmp_buf) - 1);
			strncat (tmp_buf, ins->arg[i]->name, sizeof (tmp_buf) - strlen (tmp_buf) - 1);
			esil->anal->cb_printf ("%10s:%02d", tmp_buf, ins->arg[i]->size);
			continue;
		}
		esil->anal->cb_printf ("%10s:%02d", ins->arg[i]->name, ins->arg[i]->size);
	}
	esil->anal->cb_printf("\n");
}

// Used to cast sizes during assignment. OR is used for casting.
// Pushes the new *casted* src onto stack. Warning: Frees the original src!
void reil_cast_size(RAnalEsil *esil, RAnalReilArg *src, RAnalReilArg *dst) {
	char tmp_buf[REGBUFSZ];
	RAnalReilInst *ins;

	if (!src || !dst) {
		return;
	}
	// No need to case sizes if dst and src are of same size.
	if (src->size == dst->size) {
		reil_push_arg(esil, src);
		return;
	}
	snprintf (tmp_buf, REGBUFSZ-1, "0:%d", dst->size);
	r_anal_esil_push (esil, tmp_buf);
	ins = R_NEW0 (RAnalReilInst);
	if (!ins) return;
	ins->opcode = REIL_OR;
	ins->arg[0] = src;
	ins->arg[1] = reil_pop_arg (esil);
	ins->arg[2] = R_NEW0(RAnalReilArg);
	get_next_temp_reg (esil, tmp_buf);
	reil_make_arg (esil, ins->arg[2], tmp_buf);
	if (ins->arg[2])
		ins->arg[2]->size = dst->size;
	reil_print_inst (esil, ins);
	if (ins->arg[2])
		reil_push_arg (esil, ins->arg[2]);
	reil_free_inst (ins);
}

// Here start translation functions!
static int reil_eq(RAnalEsil *esil) {
	RAnalReilInst *ins;
	char tmp_buf[REGBUFSZ];
	RAnalReilArgType src_type, dst_type;
	RAnalReilArg *dst, *src;

	dst = reil_pop_arg (esil);
	if (!dst) return false;
	src = reil_pop_arg (esil);
	if (!src) {
		R_FREE (dst);
		return false;
	}

	src_type = src->type;
	// Check if the src is an internal var. If it is, we need to resolve it.
	if (src_type == ARG_ESIL_INTERNAL) {
		reil_flag_spew_inst (esil, src->name + 1);
		R_FREE (src);
		src = reil_pop_arg (esil);
	} else if (src_type == ARG_REG) {
		// No direct register to register transfer.
		ins = R_NEW0 (RAnalReilInst);
		if (!ins) return false;
		ins->opcode = REIL_STR;
		ins->arg[0] = src;
		ins->arg[1] = R_NEW0 (RAnalReilArg);
		if (!ins->arg[1]) {
			reil_free_inst (ins);
			return false;
		}
		ins->arg[2] = R_NEW0(RAnalReilArg);
		if (!ins->arg[2]) {
			reil_free_inst (ins);
			return false;
		}
		reil_make_arg (esil, ins->arg[1], " ");
		get_next_temp_reg (esil, tmp_buf);
		reil_make_arg (esil, ins->arg[2], tmp_buf);
		ins->arg[2]->size = ins->arg[0]->size;
		reil_print_inst (esil, ins);
		reil_push_arg( esil, ins->arg[2]);
		reil_free_inst (ins);
		src = reil_pop_arg (esil);
	}

	// First, make a copy of the dst. We will need this to set the flags later on.
	ins = R_NEW0 (RAnalReilInst);
	if (!ins) {
		R_FREE (dst);
		R_FREE (src);
		return false;
	}
	dst_type = dst->type;
	if (src_type != ARG_ESIL_INTERNAL && dst_type == ARG_REG) {
		ins->opcode = REIL_STR;
		ins->arg[0] = dst;
		ins->arg[1] = R_NEW0 (RAnalReilArg);
		if (!ins->arg[1]) {
			reil_free_inst (ins);
			R_FREE (src);
			return false;
		}
		ins->arg[2] = R_NEW0 (RAnalReilArg);
		if (!ins->arg[2]) {
			reil_free_inst (ins);
			R_FREE (src);
			return false;
		}
		reil_make_arg (esil, ins->arg[1], " ");
		get_next_temp_reg (esil, tmp_buf);
		reil_make_arg (esil, ins->arg[2], tmp_buf);
		ins->arg[2]->size = ins->arg[0]->size;
		reil_print_inst (esil, ins);

		// Used for setting the flags
		r_snprintf (esil->Reil->old, sizeof (esil->Reil->old) - 1, "%s:%d",
				ins->arg[2]->name, ins->arg[2]->size);
		r_snprintf (esil->Reil->cur, sizeof (esil->Reil->cur) - 1, "%s:%d", dst->name,
				dst->size);
		esil->Reil->lastsz = dst->size;

		R_FREE (ins->arg[1]);
		R_FREE (ins->arg[2]);
	}

	// If we are modifying the Instruction Pointer, then we need to emit JCC instead.
	if (!strcmp(esil->Reil->pc, dst->name)) {
		ins->opcode = REIL_JCC;
		r_anal_esil_push (esil, "1:1");
		ins->arg[0] = reil_pop_arg (esil);
		ins->arg[1] = R_NEW0 (RAnalReilArg);
		reil_make_arg (esil, ins->arg[1], " ");
		ins->arg[2] = src;
		reil_print_inst (esil, ins);
		reil_free_inst (ins);
		R_FREE (dst);
		return true;
	}

	reil_cast_size (esil, src, dst);
	ins->opcode = REIL_STR;
	ins->arg[0] = reil_pop_arg (esil);
	if (!ins->arg[0]) {
		R_FREE (dst);
		reil_free_inst (ins);
		return false;
	}

	ins->arg[2] = dst;
	ins->arg[1] = R_NEW0 (RAnalReilArg);
	reil_make_arg (esil, ins->arg[1], " ");
	reil_print_inst (esil, ins);
	reil_free_inst (ins);
	return true;
}

// General function for operations that take 2 operands
static int reil_binop(RAnalEsil *esil, RAnalReilOpcode opcode) {
	RAnalReilInst *ins;
	char tmp_buf[REGBUFSZ];
	ut8 dst_size;
	RAnalReilArg *op2, *op1;

	op2 = reil_pop_arg(esil);
	if (!op2) return false;
	op1 = reil_pop_arg(esil);
	if (!op1) {
		R_FREE (op2);
		return false;
	}

	ins = R_NEW0 (RAnalReilInst);
	if (!ins) {
		R_FREE (op1);
		R_FREE (op2);
		return false;
	}
	ins->opcode = opcode;
	ins->arg[0] = op2;
	ins->arg[1] = op1;
	if (!ins->arg[1]) {
		reil_free_inst (ins);
		return false;
	}
	ins->arg[2] = R_NEW0(RAnalReilArg);
	if (!ins->arg[2])  {
		reil_free_inst (ins);
		return false;
	}
	get_next_temp_reg(esil, tmp_buf);
	reil_make_arg(esil, ins->arg[2], tmp_buf);
	// Choose the larger of the two sizes as the size of dst
	dst_size = ins->arg[0]->size;
	if (dst_size < ins->arg[1]->size)
		dst_size = ins->arg[1]->size;
	// REIL_LT has a dst_size of 1.
	if (opcode == REIL_LT)
		dst_size = 1;
	ins->arg[2]->size = dst_size;
	reil_print_inst(esil, ins);
	reil_push_arg(esil, ins->arg[2]);
	reil_free_inst(ins);
	return true;
}

// General function for operations which re-assign to dst. Example, addeq.
static int reil_bineqop(RAnalEsil *esil, RAnalReilOpcode opcode) {
	int ret = 1;
	RAnalReilArg *op = reil_pop_arg(esil);
	if (!op) return false;

	reil_push_arg(esil, op);
	ret &= reil_binop(esil, opcode);
	reil_push_arg(esil, op);
	ret &= reil_eq(esil);
	R_FREE(op);
	return ret;
}

static int reil_add(RAnalEsil *esil)     { return reil_binop (esil, REIL_ADD);   }
static int reil_addeq(RAnalEsil *esil)   { return reil_bineqop (esil, REIL_ADD); }
static int reil_mul(RAnalEsil *esil)     { return reil_binop (esil, REIL_MUL);   }
static int reil_muleq(RAnalEsil *esil)   { return reil_bineqop (esil, REIL_MUL); }
static int reil_sub(RAnalEsil *esil)     { return reil_binop (esil, REIL_SUB);   }
static int reil_subeq(RAnalEsil *esil)   { return reil_bineqop (esil, REIL_SUB); }
static int reil_div(RAnalEsil *esil)     { return reil_binop (esil, REIL_DIV);   }
static int reil_diveq(RAnalEsil *esil)   { return reil_bineqop (esil, REIL_DIV); }
static int reil_xor(RAnalEsil *esil)     { return reil_binop (esil, REIL_XOR);   }
static int reil_xoreq(RAnalEsil *esil)   { return reil_bineqop (esil, REIL_XOR); }
static int reil_and(RAnalEsil *esil)     { return reil_binop (esil, REIL_AND);   }
static int reil_andeq(RAnalEsil *esil)   { return reil_bineqop (esil, REIL_AND); }
static int reil_or(RAnalEsil *esil)      { return reil_binop (esil, REIL_OR);    }
static int reil_oreq(RAnalEsil *esil)    { return reil_bineqop (esil, REIL_OR);  }
static int reil_lsl(RAnalEsil *esil)     { return reil_binop (esil, REIL_SHL);   }
static int reil_lsleq(RAnalEsil *esil)   { return reil_bineqop (esil, REIL_SHL); }
static int reil_lsr(RAnalEsil *esil)     { return reil_binop (esil, REIL_SHR);   }
static int reil_lsreq(RAnalEsil *esil)   { return reil_bineqop (esil, REIL_SHR); }
static int reil_smaller(RAnalEsil *esil) { return reil_binop (esil, REIL_LT);    }

static int reil_cmp(RAnalEsil *esil) {
	RAnalReilInst *ins;
	char tmp_buf[REGBUFSZ];
	RAnalReilArg *op2, *op1;

	op2 = reil_pop_arg (esil);
	if (!op2) return false;
	op1 = reil_pop_arg (esil);
	if (!op1) {
		R_FREE (op2);
		return false;
	}

	ins = R_NEW0 (RAnalReilInst);
	if (!ins) {
		R_FREE (op1);
		R_FREE (op2);
		return false;
	}
	ins->opcode = REIL_EQ;
	ins->arg[0] = op2;
	ins->arg[1] = op1;
	ins->arg[2] = R_NEW0 (RAnalReilArg);
	if (!ins->arg[2]) {
		reil_free_inst (ins);
		return false;
	}
	get_next_temp_reg (esil, tmp_buf);
	reil_make_arg (esil, ins->arg[2], tmp_buf);
	ins->arg[2]->size = 1;
	reil_print_inst (esil, ins);
	// Set vars needed to determine flags.
	r_snprintf (esil->Reil->cur, sizeof (esil->Reil->old) - 1, "%s:%d",
			ins->arg[2]->name, ins->arg[2]->size);
	r_snprintf (esil->Reil->old, sizeof (esil->Reil->cur) - 1, "%s:%d",
			op2->name, op2->size);
	if (r_reg_get (esil->anal->reg, op2->name, -1)) {
		esil->Reil->lastsz = op2->size;
	} else if (r_reg_get (esil->anal->reg, op1->name, -1)) {
		esil->Reil->lastsz = op1->size;
	}
	reil_push_arg (esil, ins->arg[2]);
	reil_free_inst (ins);
	return true;
}

static int reil_smaller_equal(RAnalEsil *esil) {
	RAnalReilArg *op2, *op1;

	op2 = reil_pop_arg(esil);
	if (!op2) return false;
	op1 = reil_pop_arg(esil);
	if (!op1) {
		R_FREE (op2);
		return false;
	}

	reil_push_arg(esil, op1);
	reil_push_arg(esil, op2);
	reil_smaller(esil);
	reil_push_arg(esil, op1);
	reil_push_arg(esil, op2);
	reil_cmp(esil);
	reil_or(esil);

	R_FREE(op1);
	R_FREE(op2);
	return true;
}

static int reil_larger(RAnalEsil *esil) {
	RAnalReilArg *op2 = reil_pop_arg(esil);
	if (!op2) {
		return false;
	}
	RAnalReilArg *op1 = reil_pop_arg(esil);
	if (!op1) {
		R_FREE (op2);
		return false;
	}
	reil_push_arg (esil, op2);
	reil_push_arg (esil, op1);
	reil_smaller (esil);
	R_FREE (op1);
	R_FREE (op2);
	return true;
}

static int reil_larger_equal(RAnalEsil *esil) {
	RAnalReilArg *op2, *op1;

	op2 = reil_pop_arg(esil);
	if (!op2) return false;
	op1 = reil_pop_arg(esil);
	if (!op1) {
		R_FREE (op2);
		return false;
	}

	reil_push_arg (esil, op2);
	reil_push_arg (esil, op1);
	reil_smaller_equal (esil);
	R_FREE (op1);
	R_FREE (op2);
	return true;
}

static int reil_dec(RAnalEsil *esil) {
	RAnalReilArg *op = reil_pop_arg(esil);
	if (!op) {
		return false;
	}
	r_anal_esil_pushnum (esil, 1);
	reil_push_arg (esil, op);
	reil_sub (esil);
	R_FREE (op);
	return true;
}

static int reil_deceq(RAnalEsil *esil) {
	RAnalReilArg *op1 = reil_pop_arg(esil);
	if (!op1) {
		return false;
	}
	reil_push_arg (esil, op1);
	reil_dec (esil);
	reil_push_arg (esil, op1);
	reil_eq (esil);
	R_FREE (op1);
	return true;
}

static int reil_inc(RAnalEsil *esil) {
	RAnalReilArg *op = reil_pop_arg(esil);
	if (!op) return false;

	r_anal_esil_pushnum(esil, 1);
	reil_push_arg(esil, op);
	reil_add(esil);
	R_FREE(op);
	return true;
}

static int reil_inceq(RAnalEsil *esil) {
	RAnalReilArg *op = reil_pop_arg(esil);
	if (!op) {
		return false;
	}
	reil_push_arg (esil, op);
	reil_inc (esil);
	reil_push_arg (esil, op);
	reil_eq (esil);
	R_FREE (op);
	return true;
}

static int reil_neg(RAnalEsil *esil) {
	char tmp_buf[REGBUFSZ];
	RAnalReilInst *ins;
	RAnalReilArg *op = reil_pop_arg (esil);
	if (!op) {
		return false;
	}
	ins = R_NEW0 (RAnalReilInst);
	if (!ins) {
		R_FREE (op);
		return false;
	}
	ins->opcode = REIL_EQ;
	ins->arg[0] = op;
	r_anal_esil_pushnum (esil, 0);
	ins->arg[1] = reil_pop_arg(esil);
	if (!ins->arg[1]) {
		reil_free_inst (ins);
		return false;
	}
	ins->arg[2] = R_NEW0 (RAnalReilArg);
	if (!ins->arg[2]) {
		reil_free_inst (ins);
		return false;
	}
	get_next_temp_reg (esil, tmp_buf);
	reil_make_arg(esil, ins->arg[2], tmp_buf);
	if (ins->arg[0]->size < ins->arg[1]->size)
		ins->arg[1]->size = ins->arg[0]->size;

	ins->arg[2]->size = 1;
	reil_print_inst (esil, ins);
	reil_push_arg (esil, ins->arg[2]);
	reil_free_inst (ins);
	return true;
}

static int reil_negeq(RAnalEsil *esil) {
	RAnalReilArg *op = reil_pop_arg(esil);
	if (!op) return false;
	reil_push_arg (esil, op);
	reil_neg (esil);
	reil_push_arg (esil, op);
	reil_eq (esil);
	free (op);
	return true;
}

static int reil_not(RAnalEsil *esil) {
	char tmp_buf[REGBUFSZ];
	RAnalReilInst *ins;
	RAnalReilArg *op = reil_pop_arg (esil);
	if (!op) return false;

	ins = R_NEW0 (RAnalReilInst);
	if (!ins) {
		R_FREE (op);
		return false;
	}
	ins->opcode = REIL_NOT;
	ins->arg[0] = op;
	ins->arg[1] = R_NEW0 (RAnalReilArg);
	if (!ins->arg[1]) {
		reil_free_inst (ins);
		return false;
	}
	ins->arg[2] = R_NEW0 (RAnalReilArg);
	if (!ins->arg[2]) {
		reil_free_inst (ins);
		return false;
	}
	reil_make_arg (esil, ins->arg[1], " ");
	get_next_temp_reg (esil, tmp_buf);
	reil_make_arg (esil, ins->arg[2], tmp_buf);
	ins->arg[2]->size = ins->arg[0]->size;
	reil_print_inst (esil, ins);
	reil_push_arg (esil, ins->arg[2]);
	reil_free_inst (ins);
	return true;
}

static int reil_if(RAnalEsil *esil) {
	RAnalReilInst *ins;
	RAnalReilArg *op2, *op1;

	op2 = reil_pop_arg (esil);
	if (!op2) return false;
	op1 = reil_pop_arg (esil);
	if (!op1) {
		R_FREE (op2);
		return false;
	}

	ins = R_NEW0 (RAnalReilInst);
	if (!ins) {
		R_FREE (op2);
		R_FREE (op1);
		return false;
	}
	ins->opcode = REIL_JCC;
	ins->arg[0] = op1;
	ins->arg[2] = op2;
	ins->arg[1] = R_NEW0 (RAnalReilArg);
	if (!ins->arg[1]) {
		reil_free_inst (ins);
		return false;
	}
	reil_make_arg (esil, ins->arg[1], " ");
	reil_print_inst (esil, ins);
	reil_free_inst (ins);
	return true;
}

static int reil_if_end(RAnalEsil *esil) { return true; }

static int reil_peek(RAnalEsil *esil) {
	RAnalReilInst *ins;
	char tmp_buf[REGBUFSZ];
	RAnalReilArg *op1 = reil_pop_arg(esil);
	if (!op1) return false;

	ins = R_NEW0 (RAnalReilInst);
	if (!ins) {
		R_FREE (op1);
		return false;
	}
	ins->opcode = REIL_LDM;
	ins->arg[0] = op1;
	ins->arg[1] = R_NEW0(RAnalReilArg);
	if (!ins->arg[1]) {
		reil_free_inst (ins);
		return false;
	}
	ins->arg[2] = R_NEW0(RAnalReilArg);
	if (!ins->arg[2]) {
		reil_free_inst (ins);
		return false;
	}
	reil_make_arg(esil, ins->arg[1], " ");
	get_next_temp_reg(esil, tmp_buf);
	reil_make_arg(esil, ins->arg[2], tmp_buf);
	ins->arg[2]->size = ins->arg[0]->size;
	reil_print_inst(esil, ins);
	reil_push_arg(esil, ins->arg[2]);
	reil_free_inst(ins);
	return true;
}

// n = 8, 4, 2, 1
static int reil_peekn(RAnalEsil *esil, ut8 n) {
	RAnalReilArg *op2;
	RAnalReilArg *op1 = reil_pop_arg (esil);
	if (!op1) return false;

	reil_push_arg (esil, op1);
	reil_peek (esil);
	// No need to cast if n = 0
	if (n == 0) {
		R_FREE (op1);
		return true;
	}

	R_FREE (op1);
	op1 = reil_pop_arg (esil);
	if (!op1) return false;

	op2 = R_NEW0 (RAnalReilArg);
	if (!op2) {
		R_FREE (op1);
		return false;
	}
	op2->size = n * 8;
	op2->type = ARG_TEMP;
	get_next_temp_reg (esil, op2->name);
	reil_cast_size (esil, op1, op2);
	esil->Reil->lastsz = 8 * n;

	R_FREE (op2);
	return true;
}

static int reil_peek1(RAnalEsil *esil) { return reil_peekn(esil, 1); }
static int reil_peek2(RAnalEsil *esil) { return reil_peekn(esil, 2); }
static int reil_peek4(RAnalEsil *esil) { return reil_peekn(esil, 4); }
static int reil_peek8(RAnalEsil *esil) { return reil_peekn(esil, 8); }

// n = 8, 4, 2, 1
static int reil_poken(RAnalEsil *esil, ut8 n) {
	char tmp_buf[REGBUFSZ];
	RAnalReilInst *ins;
	RAnalReilArg *op2, *op1;

	op2 = reil_pop_arg (esil);
	if (!op2) return false;
	op1 = reil_pop_arg (esil);
	if (!op1) {
		R_FREE (op2);
		return false;
	}

	if (op1->type != ARG_ESIL_INTERNAL) {
		ins = R_NEW0 (RAnalReilInst);
		if (!ins) {
			R_FREE (op2);
			R_FREE (op1);
			return false;
		}
		ins->opcode = REIL_LDM;
		ins->arg[0] = op2;
		ins->arg[1] = R_NEW0(RAnalReilArg);
		if (!ins->arg[1]) {
			R_FREE (op1);
			reil_free_inst (ins);
			return false;
		}
		ins->arg[2] = R_NEW0(RAnalReilArg);
		if (!ins->arg[2]) {
			R_FREE (op1);
			reil_free_inst (ins);
			return false;
		}
		reil_make_arg (esil, ins->arg[1], " ");
		get_next_temp_reg (esil, tmp_buf);
		reil_make_arg (esil, ins->arg[2], tmp_buf);
		ins->arg[2]->size = ins->arg[0]->size;
		reil_print_inst (esil, ins);
		r_snprintf (esil->Reil->old, sizeof (esil->Reil->old) - 1, "%s:%d",
				ins->arg[2]->name, ins->arg[2]->size);
		r_snprintf (esil->Reil->cur, sizeof (esil->Reil->cur) - 1, "%s:%d",
				op2->name, op2->size);
		esil->lastsz = n * 8;
		reil_push_arg (esil, op1);
		reil_push_arg (esil, op2);
		R_FREE (op1);
		reil_free_inst (ins);
	} else {
		reil_flag_spew_inst (esil, op1->name + 1);
		R_FREE (op1);
		op1 = reil_pop_arg (esil);
		reil_push_arg (esil, op2);
		reil_push_arg (esil, op1);
		R_FREE (op2);
		R_FREE (op1);
	}

	ins = R_NEW0 (RAnalReilInst);
	if (!ins) return false;
	ins->opcode = REIL_STM;
	ins->arg[2] = reil_pop_arg (esil);
	ins->arg[0] = reil_pop_arg (esil);
	ins->arg[1] = R_NEW0 (RAnalReilArg);
	if (!ins->arg[1]) {
		reil_free_inst (ins);
		return false;
	}
	reil_make_arg(esil, ins->arg[1], " ");
	reil_print_inst(esil, ins);
	reil_free_inst(ins);
	return true;
}

static int reil_poke(RAnalEsil *esil) {
	return reil_poken (esil, esil->anal->bits / 8);
}

static int reil_poke1(RAnalEsil *esil) { return reil_poken(esil, 1); }
static int reil_poke2(RAnalEsil *esil) { return reil_poken(esil, 2); }
static int reil_poke4(RAnalEsil *esil) { return reil_poken(esil, 4); }
static int reil_poke8(RAnalEsil *esil) { return reil_poken(esil, 8); }

// Generic function to handle all mem_*eq_n functions. Example, mem_oreq_n
static int reil_mem_bineq_n(RAnalEsil *esil, RAnalReilOpcode opcode, ut8 size) {
	int ret = 1;
	RAnalReilArg *op2, *op1;

	op2 = reil_pop_arg (esil);
	if (!op2) return false;
	op1 = reil_pop_arg (esil);
	if (!op1) {
		R_FREE (op2);
		return false;
	}

	reil_push_arg(esil, op2);
	ret &= reil_peekn(esil, size);
	reil_push_arg(esil, op1);
	ret &= reil_binop(esil, opcode);
	reil_push_arg(esil, op2);
	ret &= reil_poken(esil, size);

	free (op2);
	free (op1);
	return ret;
}

static int reil_mem_oreq(RAnalEsil *esil)  { return reil_mem_bineq_n(esil, REIL_OR, esil->anal->bits / 8); }
static int reil_mem_oreq1(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_OR, 1); }
static int reil_mem_oreq2(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_OR, 2); }
static int reil_mem_oreq4(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_OR, 4); }
static int reil_mem_oreq8(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_OR, 8); }

static int reil_mem_andeq(RAnalEsil *esil)  { return reil_mem_bineq_n(esil, REIL_AND, esil->anal->bits / 8); }
static int reil_mem_andeq1(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_AND, 1); }
static int reil_mem_andeq2(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_AND, 2); }
static int reil_mem_andeq4(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_AND, 4); }
static int reil_mem_andeq8(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_AND, 8); }

static int reil_mem_xoreq(RAnalEsil *esil)  { return reil_mem_bineq_n(esil, REIL_XOR, esil->anal->bits / 8); }
static int reil_mem_xoreq1(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_XOR, 1); }
static int reil_mem_xoreq2(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_XOR, 2); }
static int reil_mem_xoreq4(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_XOR, 4); }
static int reil_mem_xoreq8(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_XOR, 8); }

static int reil_mem_addeq(RAnalEsil *esil)  { return reil_mem_bineq_n(esil, REIL_ADD, esil->anal->bits / 8); }
static int reil_mem_addeq1(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_ADD, 1); }
static int reil_mem_addeq2(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_ADD, 2); }
static int reil_mem_addeq4(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_ADD, 4); }
static int reil_mem_addeq8(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_ADD, 8); }

static int reil_mem_subeq(RAnalEsil *esil)  { return reil_mem_bineq_n(esil, REIL_SUB, esil->anal->bits / 8); }
static int reil_mem_subeq1(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_SUB, 1); }
static int reil_mem_subeq2(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_SUB, 2); }
static int reil_mem_subeq4(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_SUB, 4); }
static int reil_mem_subeq8(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_SUB, 8); }

static int reil_mem_muleq(RAnalEsil *esil)  { return reil_mem_bineq_n(esil, REIL_MUL, esil->anal->bits / 8); }
static int reil_mem_muleq1(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_MUL, 1); }
static int reil_mem_muleq2(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_MUL, 2); }
static int reil_mem_muleq4(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_MUL, 4); }
static int reil_mem_muleq8(RAnalEsil *esil) { return reil_mem_bineq_n(esil, REIL_MUL, 8); }

static int reil_mem_inceq_n(RAnalEsil *esil, ut8 size) {
	int ret = 1;
	RAnalReilArg *op1 = reil_pop_arg(esil);
	if (!op1) return false;

	r_anal_esil_pushnum(esil, 1);
	reil_push_arg(esil, op1);
	ret &= reil_mem_bineq_n(esil, REIL_ADD, size);

	free (op1);
	return ret;
}

static int reil_mem_inceq(RAnalEsil *esil) {
	return reil_mem_inceq_n(esil, esil->anal->bits / 8);
}
static int reil_mem_inceq1(RAnalEsil *esil) { return reil_mem_inceq_n(esil, 1); }
static int reil_mem_inceq2(RAnalEsil *esil) { return reil_mem_inceq_n(esil, 2); }
static int reil_mem_inceq4(RAnalEsil *esil) { return reil_mem_inceq_n(esil, 4); }
static int reil_mem_inceq8(RAnalEsil *esil) { return reil_mem_inceq_n(esil, 8); }

static int reil_mem_deceq_n(RAnalEsil *esil, ut8 size) {
	int ret = 1;
	RAnalReilArg *op1 = reil_pop_arg(esil);
	if (!op1) return false;

	r_anal_esil_pushnum(esil, 1);
	reil_push_arg(esil, op1);
	ret &= reil_mem_bineq_n(esil, REIL_SUB, size);

	free (op1);
	return ret;
}

static int reil_mem_deceq(RAnalEsil *esil) {
	return reil_mem_deceq_n(esil, esil->anal->bits / 8);
}
static int reil_mem_deceq1(RAnalEsil *esil) { return reil_mem_deceq_n(esil, 1); }
static int reil_mem_deceq2(RAnalEsil *esil) { return reil_mem_deceq_n(esil, 2); }
static int reil_mem_deceq4(RAnalEsil *esil) { return reil_mem_deceq_n(esil, 4); }
static int reil_mem_deceq8(RAnalEsil *esil) { return reil_mem_deceq_n(esil, 8); }

// Functions to resolve internal vars.
// performs (2 << op) - 1
void reil_generate_mask(RAnalEsil *esil) {
	r_anal_esil_pushnum(esil, 2);
	reil_lsl(esil);
	reil_dec(esil);
}

void reil_generate_borrow_flag(RAnalEsil *esil, ut8 bit) {
	RAnalReilArg *op1;

	r_anal_esil_pushnum(esil, bit);
	r_anal_esil_pushnum(esil, 0x3f);
	reil_and(esil);
	r_anal_esil_pushnum(esil, 0x3f);
	reil_add(esil);
	r_anal_esil_pushnum(esil, 0x3f);
	reil_and(esil);
	// Generate the mask. 2 << bits - 1
	reil_generate_mask(esil);
	op1 = reil_pop_arg(esil);
	// old & mask
	r_anal_esil_push(esil, esil->Reil->old);
	reil_push_arg(esil, op1);
	reil_and(esil);
	// cur & mask
	r_anal_esil_push(esil, esil->Reil->cur);
	reil_push_arg(esil, op1);
	reil_and(esil);
	// Check
	reil_larger(esil);

	free (op1);
}

void reil_generate_carry_flag(RAnalEsil *esil, ut8 bit) {
	RAnalReilArg *op1;

	r_anal_esil_pushnum(esil, bit);
	r_anal_esil_pushnum(esil, 0x3f);
	reil_and(esil);
	// Generate the mask. 2 << bits - 1
	reil_generate_mask(esil);
	op1 = reil_pop_arg(esil);
	// old & mask
	r_anal_esil_push(esil, esil->Reil->old);
	reil_push_arg(esil, op1);
	reil_and(esil);
	// cur & mask
	r_anal_esil_push(esil, esil->Reil->cur);
	reil_push_arg(esil, op1);
	reil_and(esil);
	// Check
	reil_smaller(esil);

	free (op1);
}

void reil_generate_partity_flag(RAnalEsil *esil) {
	// Generation of parity flag taken from openreil README example.
	RAnalReilArg *op;
	r_anal_esil_push(esil, esil->Reil->cur);
	r_anal_esil_pushnum(esil, 0xff);
	reil_and(esil);
	op = reil_pop_arg(esil);
	if (!op) return;

	r_anal_esil_pushnum(esil, 7);
	reil_push_arg(esil, op);
	reil_lsr(esil);
	r_anal_esil_pushnum(esil, 6);
	reil_push_arg(esil, op);
	reil_lsr(esil);
	reil_xor(esil);
	r_anal_esil_pushnum(esil, 5);
	reil_push_arg(esil, op);
	reil_lsr(esil);
	r_anal_esil_pushnum(esil, 4);
	reil_push_arg(esil, op);
	reil_lsr(esil);
	reil_xor(esil);
	reil_xor(esil);
	r_anal_esil_pushnum(esil, 3);
	reil_push_arg(esil, op);
	reil_lsr(esil);
	r_anal_esil_pushnum(esil, 2);
	reil_push_arg(esil, op);
	reil_lsr(esil);
	reil_xor(esil);
	r_anal_esil_pushnum(esil, 1);
	reil_push_arg(esil, op);
	reil_lsr(esil);
	reil_push_arg(esil, op);
	reil_xor(esil);
	reil_xor(esil);
	reil_xor(esil);
	r_anal_esil_pushnum(esil, 1);
	reil_and(esil);
	reil_not(esil);

	free (op);
}

void reil_generate_signature(RAnalEsil *esil) {
	if (!esil->Reil->lastsz || esil->Reil->lastsz == 0) {
		r_anal_esil_pushnum(esil, 0);
		return;
	}

	RAnalReilArg *op;

	r_anal_esil_pushnum(esil, esil->Reil->lastsz - 1);
	r_anal_esil_pushnum(esil, 1);
	reil_lsl(esil);
	r_anal_esil_push(esil, esil->Reil->cur);
	reil_and(esil);

	op = reil_pop_arg(esil);
	if (!op) return;

	r_anal_esil_pushnum(esil, esil->Reil->lastsz - 1);
	reil_push_arg(esil, op);
	reil_lsr(esil);

	free (op);
}

void reil_generate_overflow_flag(RAnalEsil *esil) {
	if (esil->Reil->lastsz < 2)
		r_anal_esil_pushnum(esil, 0);

	reil_generate_borrow_flag(esil, esil->Reil->lastsz);
	reil_generate_carry_flag(esil, esil->Reil->lastsz - 2);
	reil_xor(esil);
}

void reil_flag_spew_inst(RAnalEsil *esil, const char *flag) {
	ut8 bit;
	switch (flag[0]) {
		case 'z': // zero-flag
			r_anal_esil_push(esil, esil->Reil->cur);
			break;
		case 'b':
			bit = (ut8)r_num_get(NULL, &flag[1]);
			reil_generate_borrow_flag(esil, bit);
			break;
		case 'c':
			bit = (ut8)r_num_get(NULL, &flag[1]);
			reil_generate_carry_flag(esil, bit);
			break;
		case 'o':
			reil_generate_overflow_flag(esil);
			break;
		case 'p':
			reil_generate_partity_flag(esil);
			break;
		case 'r':
			r_anal_esil_pushnum(esil, esil->anal->bits / 8);
			break;
		case 's':
			reil_generate_signature(esil);
			break;
		default:
			return;
	}

	return;
}

/* Callback hook for command_hook */
static int setup_reil_ins(RAnalEsil *esil, const char *op) {
	esil->Reil->addr++;      // Increment the address location.
	esil->Reil->seq_num = 0; // Reset the sequencing.
	return 0;
}

R_API int r_anal_esil_to_reil_setup(RAnalEsil *esil, RAnal *anal, int romem,
		int stats) {
	if (!esil) {
		return false;
	}
	esil->verbose = 2;
	esil->anal = anal;
	esil->trap = 0;
	esil->trap_code = 0;

	/* Set up a callback for hook_command */
	esil->cb.hook_command = setup_reil_ins;

	esil->Reil = R_NEW0(RAnalReil);
	if (!esil->Reil) {
		return false;
	}
	esil->Reil->reilNextTemp = 0;
	esil->Reil->addr = -1;
	esil->Reil->seq_num = 0;
	esil->Reil->skip = 0;

	// Store the pc
	const char *name = r_reg_get_name (esil->anal->reg, r_reg_get_name_idx ("PC"));
	strncpy (esil->Reil->pc, name, sizeof (esil->Reil->pc) - 1);

	r_anal_esil_mem_ro(esil, romem);

	r_anal_esil_set_op(esil, "=", reil_eq);
	r_anal_esil_set_op(esil, "+", reil_add);
	r_anal_esil_set_op(esil, "+=", reil_addeq);
	r_anal_esil_set_op(esil, "-", reil_sub);
	r_anal_esil_set_op(esil, "-=", reil_subeq);
	r_anal_esil_set_op(esil, "*", reil_mul);
	r_anal_esil_set_op(esil, "*=", reil_muleq);
	r_anal_esil_set_op(esil, "/", reil_div);
	r_anal_esil_set_op(esil, "/=", reil_diveq);
	r_anal_esil_set_op(esil, "^", reil_xor);
	r_anal_esil_set_op(esil, "^=", reil_xoreq);
	r_anal_esil_set_op(esil, "|", reil_or);
	r_anal_esil_set_op(esil, "|=", reil_oreq);
	r_anal_esil_set_op(esil, "&", reil_and);
	r_anal_esil_set_op(esil, "&=", reil_andeq);
	r_anal_esil_set_op(esil, "<<", reil_lsl);
	r_anal_esil_set_op(esil, "<<=", reil_lsleq);
	r_anal_esil_set_op(esil, ">>", reil_lsr);
	r_anal_esil_set_op(esil, ">>=", reil_lsreq);
	r_anal_esil_set_op(esil, "++=", reil_inceq);
	r_anal_esil_set_op(esil, "++", reil_inc);
	r_anal_esil_set_op(esil, "--=", reil_deceq);
	r_anal_esil_set_op(esil, "--", reil_dec);
	r_anal_esil_set_op(esil, "!", reil_neg);
	r_anal_esil_set_op(esil, "!=", reil_negeq);
	r_anal_esil_set_op(esil, "==", reil_cmp);
	r_anal_esil_set_op(esil, "<", reil_smaller);
	r_anal_esil_set_op(esil, ">", reil_larger);
	r_anal_esil_set_op(esil, "<=", reil_smaller_equal);
	r_anal_esil_set_op(esil, ">=", reil_larger_equal);
	r_anal_esil_set_op(esil, "[]", reil_peek);
	r_anal_esil_set_op(esil, "=[]", reil_poke);
	r_anal_esil_set_op(esil, "|=[]", reil_mem_oreq);
	r_anal_esil_set_op(esil, "^=[]", reil_mem_xoreq);
	r_anal_esil_set_op(esil, "&=[]", reil_mem_andeq);
	r_anal_esil_set_op(esil, "+=[]", reil_mem_addeq);
	r_anal_esil_set_op(esil, "-=[]", reil_mem_subeq);
	r_anal_esil_set_op(esil, "*=[]", reil_mem_muleq);
	r_anal_esil_set_op(esil, "++=[]", reil_mem_inceq);
	r_anal_esil_set_op(esil, "--=[]", reil_mem_deceq);
	r_anal_esil_set_op(esil, "=[1]", reil_poke1);
	r_anal_esil_set_op(esil, "=[2]", reil_poke2);
	r_anal_esil_set_op(esil, "=[4]", reil_poke4);
	r_anal_esil_set_op(esil, "=[8]", reil_poke8);
	r_anal_esil_set_op(esil, "[1]", reil_peek1);
	r_anal_esil_set_op(esil, "[2]", reil_peek2);
	r_anal_esil_set_op(esil, "[4]", reil_peek4);
	r_anal_esil_set_op(esil, "[8]", reil_peek8);
	r_anal_esil_set_op(esil, "|=[1]", reil_mem_oreq1);
	r_anal_esil_set_op(esil, "|=[2]", reil_mem_oreq2);
	r_anal_esil_set_op(esil, "|=[4]", reil_mem_oreq4);
	r_anal_esil_set_op(esil, "|=[8]", reil_mem_oreq8);
	r_anal_esil_set_op(esil, "^=[1]", reil_mem_xoreq1);
	r_anal_esil_set_op(esil, "^=[2]", reil_mem_xoreq2);
	r_anal_esil_set_op(esil, "^=[4]", reil_mem_xoreq4);
	r_anal_esil_set_op(esil, "^=[8]", reil_mem_xoreq8);
	r_anal_esil_set_op(esil, "&=[1]", reil_mem_andeq1);
	r_anal_esil_set_op(esil, "&=[2]", reil_mem_andeq2);
	r_anal_esil_set_op(esil, "&=[4]", reil_mem_andeq4);
	r_anal_esil_set_op(esil, "&=[8]", reil_mem_andeq8);
	r_anal_esil_set_op(esil, "+=[1]", reil_mem_addeq1);
	r_anal_esil_set_op(esil, "+=[2]", reil_mem_addeq2);
	r_anal_esil_set_op(esil, "+=[4]", reil_mem_addeq4);
	r_anal_esil_set_op(esil, "+=[8]", reil_mem_addeq8);
	r_anal_esil_set_op(esil, "-=[1]", reil_mem_subeq1);
	r_anal_esil_set_op(esil, "-=[2]", reil_mem_subeq2);
	r_anal_esil_set_op(esil, "-=[4]", reil_mem_subeq4);
	r_anal_esil_set_op(esil, "-=[8]", reil_mem_subeq8);
	r_anal_esil_set_op(esil, "*=[1]", reil_mem_muleq1);
	r_anal_esil_set_op(esil, "*=[2]", reil_mem_muleq2);
	r_anal_esil_set_op(esil, "*=[4]", reil_mem_muleq4);
	r_anal_esil_set_op(esil, "*=[8]", reil_mem_muleq8);
	r_anal_esil_set_op(esil, "++=[1]", reil_mem_inceq1);
	r_anal_esil_set_op(esil, "++=[2]", reil_mem_inceq2);
	r_anal_esil_set_op(esil, "++=[4]", reil_mem_inceq4);
	r_anal_esil_set_op(esil, "++=[8]", reil_mem_inceq8);
	r_anal_esil_set_op(esil, "--=[1]", reil_mem_deceq1);
	r_anal_esil_set_op(esil, "--=[2]", reil_mem_deceq2);
	r_anal_esil_set_op(esil, "--=[4]", reil_mem_deceq4);
	r_anal_esil_set_op(esil, "--=[8]", reil_mem_deceq8);
	r_anal_esil_set_op(esil, "?{", reil_if);
	r_anal_esil_set_op(esil, "}", reil_if_end);

	return true;
}
