/* Capstone Disassembly Engine */
/* MOS65XX Backend by Sebastian Macke <sebastian@macke.de> 2018 */

#include "capstone/mos65xx.h"
#include "MOS65XXDisassembler.h"
#include "MOS65XXDisassemblerInternals.h"

typedef struct OpInfo {
	mos65xx_insn ins;
	mos65xx_address_mode am;
	int operand_bytes;
} OpInfo;

static const struct OpInfo OpInfoTable[] = {

#include "m6502.inc"
#include "m65c02.inc"
#include "mw65c02.inc"
#include "m65816.inc"

};

#ifndef CAPSTONE_DIET
static const char *const RegNames[] = { "invalid", "A",	 "X", "Y", "P",
					"SP",	   "DP", "B", "K" };

static const char *const GroupNames[] = {
	NULL, "jump", "call", "ret", "int", "iret", "branch_relative"
};

typedef struct InstructionInfo {
	const char *name;
	mos65xx_group_type group_type;
	mos65xx_reg write, read;
	bool modifies_status;
} InstructionInfo;

static const struct InstructionInfo InstructionInfoTable[] = {

#include "instruction_info.inc"

};
#endif

#ifndef CAPSTONE_DIET
static void fillDetails(MCInst *MI, struct OpInfo opinfo, int cpu_type)
{
	int i;
	cs_detail *detail = MI->flat_insn->detail;

	InstructionInfo insinfo = InstructionInfoTable[opinfo.ins];

	detail->mos65xx.am = opinfo.am;
	detail->mos65xx.modifies_flags = insinfo.modifies_status;
	detail->groups_count = 0;
	detail->regs_read_count = 0;
	detail->regs_write_count = 0;
	detail->mos65xx.op_count = 0;

	if (insinfo.group_type != MOS65XX_GRP_INVALID) {
		detail->groups[detail->groups_count] = insinfo.group_type;
		detail->groups_count++;
	}

	if (opinfo.am == MOS65XX_AM_REL || opinfo.am == MOS65XX_AM_ZP_REL) {
		detail->groups[detail->groups_count] =
			MOS65XX_GRP_BRANCH_RELATIVE;
		detail->groups_count++;
	}

	if (insinfo.read != MOS65XX_REG_INVALID) {
		detail->regs_read[detail->regs_read_count++] = insinfo.read;
	} else
		switch (opinfo.am) {
		case MOS65XX_AM_ACC:
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_ACC;
			break;
		case MOS65XX_AM_ZP_Y:
		case MOS65XX_AM_ZP_IND_Y:
		case MOS65XX_AM_ABS_Y:
		case MOS65XX_AM_ZP_IND_LONG_Y:
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_Y;
			break;

		case MOS65XX_AM_ZP_X:
		case MOS65XX_AM_ZP_X_IND:
		case MOS65XX_AM_ABS_X:
		case MOS65XX_AM_ABS_X_IND:
		case MOS65XX_AM_ABS_LONG_X:
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_X;
			break;

		case MOS65XX_AM_SR:
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_SP;
			break;
		case MOS65XX_AM_SR_IND_Y:
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_SP;
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_Y;
			break;

		default:
			break;
		}

	if (insinfo.write != MOS65XX_REG_INVALID) {
		detail->regs_write[detail->regs_write_count++] = insinfo.write;
	} else if (opinfo.am == MOS65XX_AM_ACC) {
		detail->regs_write[detail->regs_write_count++] =
			MOS65XX_REG_ACC;
	}

	switch (opinfo.ins) {
	case MOS65XX_INS_ADC:
	case MOS65XX_INS_SBC:
	case MOS65XX_INS_ROL:
	case MOS65XX_INS_ROR:
		/* these read carry flag (and decimal for ADC/SBC) */
		detail->regs_read[detail->regs_read_count++] = MOS65XX_REG_P;
		break;
	/* stack operations */
	case MOS65XX_INS_JSL:
	case MOS65XX_INS_JSR:
	case MOS65XX_INS_PEA:
	case MOS65XX_INS_PEI:
	case MOS65XX_INS_PER:
	case MOS65XX_INS_PHA:
	case MOS65XX_INS_PHB:
	case MOS65XX_INS_PHD:
	case MOS65XX_INS_PHK:
	case MOS65XX_INS_PHP:
	case MOS65XX_INS_PHX:
	case MOS65XX_INS_PHY:
	case MOS65XX_INS_PLA:
	case MOS65XX_INS_PLB:
	case MOS65XX_INS_PLD:
	case MOS65XX_INS_PLP:
	case MOS65XX_INS_PLX:
	case MOS65XX_INS_PLY:
	case MOS65XX_INS_RTI:
	case MOS65XX_INS_RTL:
	case MOS65XX_INS_RTS:
		detail->regs_read[detail->regs_read_count++] = MOS65XX_REG_SP;
		detail->regs_write[detail->regs_write_count++] = MOS65XX_REG_SP;
		break;
	default:
		break;
	}

	if (cpu_type == MOS65XX_CPU_TYPE_65816) {
		switch (opinfo.am) {
		case MOS65XX_AM_ZP:
		case MOS65XX_AM_ZP_X:
		case MOS65XX_AM_ZP_Y:
		case MOS65XX_AM_ZP_IND:
		case MOS65XX_AM_ZP_X_IND:
		case MOS65XX_AM_ZP_IND_Y:
		case MOS65XX_AM_ZP_IND_LONG:
		case MOS65XX_AM_ZP_IND_LONG_Y:
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_DP;
			break;
		case MOS65XX_AM_BLOCK:
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_ACC;
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_X;
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_Y;
			detail->regs_write[detail->regs_write_count++] =
				MOS65XX_REG_ACC;
			detail->regs_write[detail->regs_write_count++] =
				MOS65XX_REG_X;
			detail->regs_write[detail->regs_write_count++] =
				MOS65XX_REG_Y;
			detail->regs_write[detail->regs_write_count++] =
				MOS65XX_REG_B;
			break;
		default:
			break;
		}

		switch (opinfo.am) {
		case MOS65XX_AM_ZP_IND:
		case MOS65XX_AM_ZP_X_IND:
		case MOS65XX_AM_ZP_IND_Y:
		case MOS65XX_AM_ABS:
		case MOS65XX_AM_ABS_X:
		case MOS65XX_AM_ABS_Y:
		case MOS65XX_AM_ABS_X_IND:
			/* these depend on the databank to generate a 24-bit address */
			/* exceptions: PEA, PEI, and JMP (abs) */
			if (opinfo.ins == MOS65XX_INS_PEI ||
			    opinfo.ins == MOS65XX_INS_PEA)
				break;
			detail->regs_read[detail->regs_read_count++] =
				MOS65XX_REG_B;
			break;
		default:
			break;
		}
	}

	if (insinfo.modifies_status) {
		detail->regs_write[detail->regs_write_count++] = MOS65XX_REG_P;
	}

	switch (opinfo.am) {
	case MOS65XX_AM_IMP:
		break;
	case MOS65XX_AM_IMM:
		detail->mos65xx.operands[detail->mos65xx.op_count].type =
			MOS65XX_OP_IMM;
		detail->mos65xx.operands[detail->mos65xx.op_count].imm =
			MI->Operands[0].ImmVal;
		detail->mos65xx.op_count++;
		break;
	case MOS65XX_AM_ACC:
		detail->mos65xx.operands[detail->mos65xx.op_count].type =
			MOS65XX_OP_REG;
		detail->mos65xx.operands[detail->mos65xx.op_count].reg =
			MOS65XX_REG_ACC;
		detail->mos65xx.op_count++;
		break;
	case MOS65XX_AM_REL: {
		int value = MI->Operands[0].ImmVal;
		if (MI->op1_size == 1)
			value = 2 + (signed char)value;
		else
			value = 3 + (signed short)value;
		detail->mos65xx.operands[detail->mos65xx.op_count].type =
			MOS65XX_OP_MEM;
		detail->mos65xx.operands[detail->mos65xx.op_count].mem =
			(MI->address + value) & 0xffff;
		detail->mos65xx.op_count++;
		break;
	}
	case MOS65XX_AM_ZP_REL: {
		int value = 3 + (signed char)MI->Operands[1].ImmVal;
		/* BBR0, zp, rel  and BBS0, zp, rel */
		detail->mos65xx.operands[detail->mos65xx.op_count].type =
			MOS65XX_OP_MEM;
		detail->mos65xx.operands[detail->mos65xx.op_count].mem =
			MI->Operands[0].ImmVal;
		detail->mos65xx.operands[detail->mos65xx.op_count + 1].type =
			MOS65XX_OP_MEM;
		detail->mos65xx.operands[detail->mos65xx.op_count + 1].mem =
			(MI->address + value) & 0xffff;
		detail->mos65xx.op_count += 2;
		break;
	}
	default:
		for (i = 0; i < MI->size; ++i) {
			detail->mos65xx.operands[detail->mos65xx.op_count].type =
				MOS65XX_OP_MEM;
			detail->mos65xx.operands[detail->mos65xx.op_count].mem =
				MI->Operands[i].ImmVal;
			detail->mos65xx.op_count++;
		}
		break;
	}
}
#endif

void MOS65XX_printInst(MCInst *MI, struct SStream *O, void *PrinterInfo)
{
#ifndef CAPSTONE_DIET
	unsigned int value;
	unsigned opcode = MCInst_getOpcode(MI);
	mos65xx_info *info = (mos65xx_info *)PrinterInfo;

	OpInfo opinfo = OpInfoTable[opcode];

	const char *prefix = info->hex_prefix ? info->hex_prefix : "0x";

	SStream_concat0(O, InstructionInfoTable[opinfo.ins].name);
	switch (opinfo.ins) {
	/* special case - bit included as part of the instruction name */
	case MOS65XX_INS_BBR:
	case MOS65XX_INS_BBS:
	case MOS65XX_INS_RMB:
	case MOS65XX_INS_SMB:
		SStream_concat(O, "%" PRId8, (opcode >> 4) & 0x07);
		break;
	default:
		break;
	}

	value = MI->Operands[0].ImmVal;

	switch (opinfo.am) {
	default:
		break;

	case MOS65XX_AM_IMP:
		break;

	case MOS65XX_AM_ACC:
		SStream_concat0(O, " a");
		break;

	case MOS65XX_AM_IMM:
		if (MI->imm_size == 1)
			SStream_concat(O, " #%s%02" PRIx32, prefix, value);
		else
			SStream_concat(O, " #%s%04" PRIx32, prefix, value);
		break;

	case MOS65XX_AM_ZP:
		SStream_concat(O, " %s%02" PRIx32, prefix, value);
		break;

	case MOS65XX_AM_ABS:
		SStream_concat(O, " %s%04" PRIx32, prefix, value);
		break;

	case MOS65XX_AM_ABS_LONG_X:
		SStream_concat(O, " %s%06" PRIx32 ", x", prefix, value);
		break;

	case MOS65XX_AM_INT:
		SStream_concat(O, " %s%02" PRIx32, prefix, value);
		break;

	case MOS65XX_AM_ABS_X:
		SStream_concat(O, " %s%04" PRIx32 ", x", prefix, value);
		break;

	case MOS65XX_AM_ABS_Y:
		SStream_concat(O, " %s%04" PRIx32 ", y", prefix, value);
		break;

	case MOS65XX_AM_ABS_LONG:
		SStream_concat(O, " %s%06" PRIx32, prefix, value);
		break;

	case MOS65XX_AM_ZP_X:
		SStream_concat(O, " %s%02" PRIx32 ", x", prefix, value);
		break;

	case MOS65XX_AM_ZP_Y:
		SStream_concat(O, " %s%02" PRIx32 ", y", prefix, value);
		break;

	case MOS65XX_AM_REL: {
		if (MI->op1_size == 1)
			value = 2 + (int8_t)value;
		else
			value = 3 + (int16_t)value;

		uint32_t addr = MI->address;
		SStream_concat(O, " %s%04" PRIx16, prefix,
			       (addr + value) & 0xffff);

		break;
	}
	case MOS65XX_AM_ABS_IND:
		SStream_concat(O, " (%s%04" PRIx32 ")", prefix, value);
		break;

	case MOS65XX_AM_ABS_X_IND:
		SStream_concat(O, " (%s%04" PRIx32 ", x)", prefix, value);
		break;

	case MOS65XX_AM_ABS_IND_LONG:
		SStream_concat(O, " [%s%04" PRIx32 "]", prefix, value);
		break;

	case MOS65XX_AM_ZP_IND:
		SStream_concat(O, " (%s%02" PRIx32 ")", prefix, value);
		break;

	case MOS65XX_AM_ZP_X_IND:
		SStream_concat(O, " (%s%02" PRIx32 ", x)", prefix, value);
		break;

	case MOS65XX_AM_ZP_IND_Y:
		SStream_concat(O, " (%s%02" PRIx32 "), y", prefix, value);
		break;

	case MOS65XX_AM_ZP_IND_LONG:
		SStream_concat(O, " [%s%02" PRIx32 "]", prefix, value);
		break;

	case MOS65XX_AM_ZP_IND_LONG_Y:
		SStream_concat(O, " [%s%02" PRIx32 "], y", prefix, value);
		break;

	case MOS65XX_AM_SR:
		SStream_concat(O, " %s%02" PRIx32 ", s", prefix, value);
		break;

	case MOS65XX_AM_SR_IND_Y:
		SStream_concat(O, " (%s%02" PRIx32 ", s), y", prefix, value);
		break;

	case MOS65XX_AM_BLOCK:
		SStream_concat(O, " %s%02" PRIx32 ", %s%02" PRIx32, prefix,
			       (uint32_t)MI->Operands[0].ImmVal, prefix,
			       (uint32_t)MI->Operands[1].ImmVal);
		break;

	case MOS65XX_AM_ZP_REL: {
		value = 3 + (int8_t)MI->Operands[1].ImmVal;
		uint32_t addr = MI->address;
		uint32_t target = (addr + value) & 0xffff;
		/* BBR0, zp, rel  and BBS0, zp, rel */
		SStream_concat(O, " %s%02" PRIx32 ", %s%04" PRIx32, prefix,
			       (uint32_t)MI->Operands[0].ImmVal, prefix,
			       target);
		break;
	}
	}
#endif
}

bool MOS65XX_getInstruction(csh ud, const uint8_t *code, size_t code_len,
			    MCInst *MI, uint16_t *size, uint64_t address,
			    void *inst_info)
{
	int i;
	unsigned char opcode;
	unsigned char len;
	unsigned cpu_offset = 0;
	int cpu_type = MOS65XX_CPU_TYPE_6502;
	cs_struct *handle = MI->csh;
	mos65xx_info *info = (mos65xx_info *)handle->printer_info;
	OpInfo opinfo;

	if (code_len == 0) {
		*size = 1;
		return false;
	}

	cpu_type = info->cpu_type;
	cpu_offset = cpu_type * 256;

	opcode = code[0];
	opinfo = OpInfoTable[cpu_offset + opcode];
	if (opinfo.ins == MOS65XX_INS_INVALID) {
		*size = 1;
		return false;
	}

	len = opinfo.operand_bytes + 1;

	if (cpu_type == MOS65XX_CPU_TYPE_65816 && opinfo.am == MOS65XX_AM_IMM) {
		switch (opinfo.ins) {
		case MOS65XX_INS_CPX:
		case MOS65XX_INS_CPY:
		case MOS65XX_INS_LDX:
		case MOS65XX_INS_LDY:
			if (info->long_x)
				++len;
			break;
		case MOS65XX_INS_ADC:
		case MOS65XX_INS_AND:
		case MOS65XX_INS_BIT:
		case MOS65XX_INS_CMP:
		case MOS65XX_INS_EOR:
		case MOS65XX_INS_LDA:
		case MOS65XX_INS_ORA:
		case MOS65XX_INS_SBC:
			if (info->long_m)
				++len;
			break;
		default:
			break;
		}
	}

	if (code_len < len) {
		*size = 1;
		return false;
	}

	MI->address = address;

	MCInst_setOpcode(MI, cpu_offset + opcode);
	MCInst_setOpcodePub(MI, opinfo.ins);

	*size = len;

	/* needed to differentiate relative vs relative long */
	MI->op1_size = len - 1;
	if (opinfo.ins == MOS65XX_INS_NOP) {
		for (i = 1; i < len; ++i)
			MCOperand_CreateImm0(MI, code[i]);
	}

	switch (opinfo.am) {
	case MOS65XX_AM_ZP_REL:
		MCOperand_CreateImm0(MI, code[1]);
		MCOperand_CreateImm0(MI, code[2]);
		break;
	case MOS65XX_AM_BLOCK:
		MCOperand_CreateImm0(MI, code[2]);
		MCOperand_CreateImm0(MI, code[1]);
		break;
	case MOS65XX_AM_IMP:
	case MOS65XX_AM_ACC:
		break;

	case MOS65XX_AM_IMM:
		MI->has_imm = 1;
		MI->imm_size = len - 1;
		/* 65816 immediate is either 1 or 2 bytes */
		/* drop through */
	default:
		if (len == 2)
			MCOperand_CreateImm0(MI, code[1]);
		else if (len == 3)
			MCOperand_CreateImm0(MI, (code[2] << 8) | code[1]);
		else if (len == 4)
			MCOperand_CreateImm0(
				MI, (code[3] << 16) | (code[2] << 8) | code[1]);
		break;
	}

#ifndef CAPSTONE_DIET
	if (MI->flat_insn->detail) {
		fillDetails(MI, opinfo, cpu_type);
	}
#endif

	return true;
}

const char *MOS65XX_insn_name(csh handle, unsigned int id)
{
#ifdef CAPSTONE_DIET
	return NULL;
#else
	if (id >= ARR_SIZE(InstructionInfoTable)) {
		return NULL;
	}
	return InstructionInfoTable[id].name;
#endif
}

const char *MOS65XX_reg_name(csh handle, unsigned int reg)
{
#ifdef CAPSTONE_DIET
	return NULL;
#else
	if (reg >= ARR_SIZE(RegNames)) {
		return NULL;
	}
	return RegNames[(int)reg];
#endif
}

void MOS65XX_get_insn_id(cs_struct *h, cs_insn *insn, unsigned int id)
{
	/* id is cpu_offset + opcode */
	if (id < ARR_SIZE(OpInfoTable)) {
		insn->id = OpInfoTable[id].ins;
	}
}

const char *MOS65XX_group_name(csh handle, unsigned int id)
{
#ifdef CAPSTONE_DIET
	return NULL;
#else
	if (id >= ARR_SIZE(GroupNames)) {
		return NULL;
	}
	return GroupNames[(int)id];
#endif
}
