/* Capstone Disassembly Engine */
/* By Nguyen Anh Quynh, 2018 */

#include <string.h>
#include <stddef.h> // offsetof macro
                    // alternatively #include "../../utils.h" like everyone else

#include "EVMDisassembler.h"
#include "EVMMapping.h"

static const short opcodes[256] = {
	EVM_INS_STOP,
	EVM_INS_ADD,
	EVM_INS_MUL,
	EVM_INS_SUB,
	EVM_INS_DIV,
	EVM_INS_SDIV,
	EVM_INS_MOD,
	EVM_INS_SMOD,
	EVM_INS_ADDMOD,
	EVM_INS_MULMOD,
	EVM_INS_EXP,
	EVM_INS_SIGNEXTEND,
	-1,
	-1,
	-1,
	-1,
	EVM_INS_LT,
	EVM_INS_GT,
	EVM_INS_SLT,
	EVM_INS_SGT,
	EVM_INS_EQ,
	EVM_INS_ISZERO,
	EVM_INS_AND,
	EVM_INS_OR,
	EVM_INS_XOR,
	EVM_INS_NOT,
	EVM_INS_BYTE,
	-1,
	-1,
	-1,
	-1,
	-1,
	EVM_INS_SHA3,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	EVM_INS_ADDRESS,
	EVM_INS_BALANCE,
	EVM_INS_ORIGIN,
	EVM_INS_CALLER,
	EVM_INS_CALLVALUE,
	EVM_INS_CALLDATALOAD,
	EVM_INS_CALLDATASIZE,
	EVM_INS_CALLDATACOPY,
	EVM_INS_CODESIZE,
	EVM_INS_CODECOPY,
	EVM_INS_GASPRICE,
	EVM_INS_EXTCODESIZE,
	EVM_INS_EXTCODECOPY,
	EVM_INS_RETURNDATASIZE,
	EVM_INS_RETURNDATACOPY,
	-1,
	EVM_INS_BLOCKHASH,
	EVM_INS_COINBASE,
	EVM_INS_TIMESTAMP,
	EVM_INS_NUMBER,
	EVM_INS_DIFFICULTY,
	EVM_INS_GASLIMIT,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	EVM_INS_POP,
	EVM_INS_MLOAD,
	EVM_INS_MSTORE,
	EVM_INS_MSTORE8,
	EVM_INS_SLOAD,
	EVM_INS_SSTORE,
	EVM_INS_JUMP,
	EVM_INS_JUMPI,
	EVM_INS_PC,
	EVM_INS_MSIZE,
	EVM_INS_GAS,
	EVM_INS_JUMPDEST,
	-1,
	-1,
	-1,
	-1,
	EVM_INS_PUSH1,
	EVM_INS_PUSH2,
	EVM_INS_PUSH3,
	EVM_INS_PUSH4,
	EVM_INS_PUSH5,
	EVM_INS_PUSH6,
	EVM_INS_PUSH7,
	EVM_INS_PUSH8,
	EVM_INS_PUSH9,
	EVM_INS_PUSH10,
	EVM_INS_PUSH11,
	EVM_INS_PUSH12,
	EVM_INS_PUSH13,
	EVM_INS_PUSH14,
	EVM_INS_PUSH15,
	EVM_INS_PUSH16,
	EVM_INS_PUSH17,
	EVM_INS_PUSH18,
	EVM_INS_PUSH19,
	EVM_INS_PUSH20,
	EVM_INS_PUSH21,
	EVM_INS_PUSH22,
	EVM_INS_PUSH23,
	EVM_INS_PUSH24,
	EVM_INS_PUSH25,
	EVM_INS_PUSH26,
	EVM_INS_PUSH27,
	EVM_INS_PUSH28,
	EVM_INS_PUSH29,
	EVM_INS_PUSH30,
	EVM_INS_PUSH31,
	EVM_INS_PUSH32,
	EVM_INS_DUP1,
	EVM_INS_DUP2,
	EVM_INS_DUP3,
	EVM_INS_DUP4,
	EVM_INS_DUP5,
	EVM_INS_DUP6,
	EVM_INS_DUP7,
	EVM_INS_DUP8,
	EVM_INS_DUP9,
	EVM_INS_DUP10,
	EVM_INS_DUP11,
	EVM_INS_DUP12,
	EVM_INS_DUP13,
	EVM_INS_DUP14,
	EVM_INS_DUP15,
	EVM_INS_DUP16,
	EVM_INS_SWAP1,
	EVM_INS_SWAP2,
	EVM_INS_SWAP3,
	EVM_INS_SWAP4,
	EVM_INS_SWAP5,
	EVM_INS_SWAP6,
	EVM_INS_SWAP7,
	EVM_INS_SWAP8,
	EVM_INS_SWAP9,
	EVM_INS_SWAP10,
	EVM_INS_SWAP11,
	EVM_INS_SWAP12,
	EVM_INS_SWAP13,
	EVM_INS_SWAP14,
	EVM_INS_SWAP15,
	EVM_INS_SWAP16,
	EVM_INS_LOG0,
	EVM_INS_LOG1,
	EVM_INS_LOG2,
	EVM_INS_LOG3,
	EVM_INS_LOG4,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	-1,
	EVM_INS_CREATE,
	EVM_INS_CALL,
	EVM_INS_CALLCODE,
	EVM_INS_RETURN,
	EVM_INS_DELEGATECALL,
	EVM_INS_CALLBLACKBOX,
	-1,
	-1,
	-1,
	-1,
	EVM_INS_STATICCALL,
	-1,
	-1,
	EVM_INS_REVERT,
	-1,
	EVM_INS_SUICIDE,
};

bool EVM_getInstruction(csh ud, const uint8_t *code, size_t code_len,
	MCInst *MI, uint16_t *size, uint64_t address, void *inst_info)
{
	unsigned char opcode;

	if (code_len == 0)
		return false;

	opcode = code[0];
	if (opcodes[opcode] == -1) {
		// invalid opcode
		return false;
	}

	// valid opcode
	MI->address = address;
	MI->OpcodePub = MI->Opcode = opcode;

	if (opcode >= EVM_INS_PUSH1 && opcode <= EVM_INS_PUSH32) {
		unsigned char len = (opcode - EVM_INS_PUSH1 + 1);
		if (code_len < 1 + len) {
			// not enough data
			return false;
		}

		*size = 1 + len;
		memcpy(MI->evm_data, code + 1, len);
	} else
		*size = 1;

	if (MI->flat_insn->detail) {
		memset(MI->flat_insn->detail, 0, offsetof(cs_detail, evm)+sizeof(cs_evm));
		EVM_get_insn_id((cs_struct *)ud, MI->flat_insn, opcode);

		if (MI->flat_insn->detail->evm.pop) {
			MI->flat_insn->detail->groups[MI->flat_insn->detail->groups_count] = EVM_GRP_STACK_READ;
			MI->flat_insn->detail->groups_count++;
		}

		if (MI->flat_insn->detail->evm.push) {
			MI->flat_insn->detail->groups[MI->flat_insn->detail->groups_count] = EVM_GRP_STACK_WRITE;
			MI->flat_insn->detail->groups_count++;
		}

		// setup groups
		switch(opcode) {
			default:
				break;
			case EVM_INS_ADD:
			case EVM_INS_MUL:
			case EVM_INS_SUB:
			case EVM_INS_DIV:
			case EVM_INS_SDIV:
			case EVM_INS_MOD:
			case EVM_INS_SMOD:
			case EVM_INS_ADDMOD:
			case EVM_INS_MULMOD:
			case EVM_INS_EXP:
			case EVM_INS_SIGNEXTEND:
				MI->flat_insn->detail->groups[MI->flat_insn->detail->groups_count] = EVM_GRP_MATH;
				MI->flat_insn->detail->groups_count++;
				break;

			case EVM_INS_MSTORE:
			case EVM_INS_MSTORE8:
			case EVM_INS_CALLDATACOPY:
			case EVM_INS_CODECOPY:
			case EVM_INS_EXTCODECOPY:
				MI->flat_insn->detail->groups[MI->flat_insn->detail->groups_count] = EVM_GRP_MEM_WRITE;
				MI->flat_insn->detail->groups_count++;
				break;

			case EVM_INS_MLOAD:
			case EVM_INS_CREATE:
			case EVM_INS_CALL:
			case EVM_INS_CALLCODE:
			case EVM_INS_RETURN:
			case EVM_INS_DELEGATECALL:
			case EVM_INS_REVERT:
				MI->flat_insn->detail->groups[MI->flat_insn->detail->groups_count] = EVM_GRP_MEM_READ;
				MI->flat_insn->detail->groups_count++;
				break;

			case EVM_INS_SSTORE:
				MI->flat_insn->detail->groups[MI->flat_insn->detail->groups_count] = EVM_GRP_STORE_WRITE;
				MI->flat_insn->detail->groups_count++;
				break;

			case EVM_INS_SLOAD:
				MI->flat_insn->detail->groups[MI->flat_insn->detail->groups_count] = EVM_GRP_STORE_READ;
				MI->flat_insn->detail->groups_count++;
				break;

			case EVM_INS_JUMP:
			case EVM_INS_JUMPI:
				MI->flat_insn->detail->groups[MI->flat_insn->detail->groups_count] = EVM_GRP_JUMP;
				MI->flat_insn->detail->groups_count++;
				break;

			case EVM_INS_STOP:
			case EVM_INS_SUICIDE:
				MI->flat_insn->detail->groups[MI->flat_insn->detail->groups_count] = EVM_GRP_HALT;
				MI->flat_insn->detail->groups_count++;
				break;

		}
	}

	return true;
}
