// SPDX-FileCopyrightText: 2015 condret <condr3t@protonmail.com>
// 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>
#include <snes/snes_op_table.h>
#include <snes/snes.h>

static int snes_anop(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *data, int len, RzAnalysisOpMask mask) {
	struct snes_asm_flags *snesflags = (struct snes_asm_flags *)analysis->plugin_data;
	op->size = snes_op_get_size(snesflags->M, snesflags->X, &snes_op[data[0]]);
	if (op->size > len) {
		return op->size = 0;
	}
	op->nopcode = 1;
	op->addr = addr;
	op->type = RZ_ANALYSIS_OP_TYPE_UNK;
	switch (data[0]) {
	case 0xea: // nop
		op->type = RZ_ANALYSIS_OP_TYPE_NOP;
		break;
	case 0xfb: // xce
		op->type = RZ_ANALYSIS_OP_TYPE_XCHG;
		break;
	case 0x00: // brk
	case 0x02: // cop
		op->type = RZ_ANALYSIS_OP_TYPE_SWI;
		break;
	case 0x1b: // tcs
	case 0x3b: // tsc
	case 0x5b: // tcd
	case 0x7b: // tdc
	case 0x8a: // txa
	case 0x98: // tya
	case 0x9a: // txs
	case 0x9b: // txy
	case 0xa8: // tay
	case 0xaa: // tax
	case 0xba: // tsx
	case 0xbb: // tyx
		op->type = RZ_ANALYSIS_OP_TYPE_MOV;
		break;
	case 0x48: // pha
	case 0x8b: // phb
	case 0x0b: // phd
	case 0x4b: // phk
	case 0x08: // php
	case 0xda: // phx
	case 0x5a: // phy
		op->type = RZ_ANALYSIS_OP_TYPE_PUSH;
		break;
	case 0x68: // pla
	case 0xab: // plb
	case 0x2b: // pld
	case 0x28: // plp
	case 0xfa: // plx
	case 0x7a: // ply
		op->type = RZ_ANALYSIS_OP_TYPE_POP;
		break;
	// adc
	case 0x61:
	case 0x63:
	case 0x65:
	case 0x67:
	case 0x69:
	case 0x6d:
	case 0x6f:
	case 0x71:
	case 0x72:
	case 0x73:
	case 0x75:
	case 0x77:
	case 0x79:
	case 0x7d:
	case 0x7f:
	// inc
	case 0x1a:
	case 0xe6:
	case 0xee:
	case 0xf6:
	case 0xfe:
	case 0xe8: // inx
	case 0xc8: // iny
		op->type = RZ_ANALYSIS_OP_TYPE_ADD;
		break;
	// and
	case 0x23:
	case 0x25:
	case 0x27:
	case 0x29:
	case 0x2d:
	case 0x2f:
	case 0x31:
	case 0x32:
	case 0x33:
	case 0x35:
	case 0x37:
	case 0x39:
	case 0x3d:
	case 0x3f:
		op->type = RZ_ANALYSIS_OP_TYPE_AND;
		break;
	// bit
	case 0x24:
	case 0x2c:
	case 0x34:
	case 0x3c:
	case 0x89:
		op->type = RZ_ANALYSIS_OP_TYPE_ACMP;
		break;
	// cmp
	case 0xc1:
	case 0xc3:
	case 0xc5:
	case 0xc7:
	case 0xc9:
	case 0xcd:
	case 0xcf:
	case 0xd1:
	case 0xd2:
	case 0xd3:
	case 0xd5:
	case 0xd7:
	case 0xd9:
	case 0xdd:
	case 0xdf:
	// cpx
	case 0xe0:
	case 0xe4:
	case 0xec:
	// cpy
	case 0xc0:
	case 0xc4:
	case 0xcc:
		op->type = RZ_ANALYSIS_OP_TYPE_CMP;
		break;
	// ora
	case 0x01:
	case 0x03:
	case 0x05:
	case 0x07:
	case 0x09:
	case 0x0d:
	case 0x0f:
	case 0x11:
	case 0x12:
	case 0x13:
	case 0x15:
	case 0x17:
	case 0x19:
	case 0x1d:
	case 0x1f:
		op->type = RZ_ANALYSIS_OP_TYPE_OR;
		break;
	// eor
	case 0x41:
	case 0x43:
	case 0x45:
	case 0x47:
	case 0x49:
	case 0x4d:
	case 0x4f:
	case 0x51:
	case 0x52:
	case 0x53:
	case 0x55:
	case 0x57:
	case 0x59:
	case 0x5d:
	case 0x5f:
		op->type = RZ_ANALYSIS_OP_TYPE_XOR;
		break;
	// asl
	case 0x06:
	case 0x0a:
	case 0x0e:
	case 0x16:
	case 0x1e:
		op->type = RZ_ANALYSIS_OP_TYPE_SAL;
		break;
	// lsr
	case 0x46:
	case 0x4a:
	case 0x4e:
	case 0x56:
	case 0x5e:
		op->type = RZ_ANALYSIS_OP_TYPE_SHR;
		break;
	// rol
	case 0x26:
	case 0x2a:
	case 0x2e:
	case 0x36:
	case 0x3e:
		op->type = RZ_ANALYSIS_OP_TYPE_ROL;
		break;
	// ror
	case 0x66:
	case 0x6a:
	case 0x6e:
	case 0x76:
	case 0x7e:
		op->type = RZ_ANALYSIS_OP_TYPE_ROR;
		break;
	// sbc
	case 0xe1:
	case 0xe3:
	case 0xe5:
	case 0xe7:
	case 0xe9:
	case 0xed:
	case 0xef:
	case 0xf1:
	case 0xf2:
	case 0xf3:
	case 0xf5:
	case 0xf7:
	case 0xf9:
	case 0xfd:
	case 0xff:
	// dec
	case 0x3a:
	case 0xc6:
	case 0xce:
	case 0xd6:
	case 0xde:
	case 0xca: // dex
	case 0x88: // dey
		op->type = RZ_ANALYSIS_OP_TYPE_SUB;
		break;
	// sta
	case 0x81:
	case 0x83:
	case 0x85:
	case 0x87:
	case 0x8d:
	case 0x8f:
	case 0x91:
	case 0x92:
	case 0x93:
	case 0x95:
	case 0x97:
	case 0x99:
	case 0x9d:
	case 0x9f:
	// stx
	case 0x86:
	case 0x8e:
	case 0x96:
	// sty
	case 0x84:
	case 0x8c:
	case 0x94:
	// stz
	case 0x64:
	case 0x74:
	case 0x9c:
	case 0x9e:
		op->type = RZ_ANALYSIS_OP_TYPE_STORE;
		break;
	// lda
	case 0xa1:
	case 0xa3:
	case 0xa5:
	case 0xa7:
	case 0xa9:
	case 0xad:
	case 0xaf:
	case 0xb1:
	case 0xb2:
	case 0xb3:
	case 0xb5:
	case 0xb7:
	case 0xb9:
	case 0xbd:
	case 0xbf:
	// ldx
	case 0xa2:
	case 0xa6:
	case 0xae:
	case 0xb6:
	case 0xbe:
	// ldy
	case 0xa0:
	case 0xa4:
	case 0xac:
	case 0xb4:
	case 0xbc:
		op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
		break;
	case 0x4c: // jmp addr
		op->eob = true;
		op->jump = (addr & 0xFF0000) | rz_read_le16(data + 1);
		op->type = RZ_ANALYSIS_OP_TYPE_JMP;
		break;
	case 0x5c: // jmp long
		op->eob = true;
		op->jump = data[1] | data[2] << 8 | data[3] << 16;
		op->type = RZ_ANALYSIS_OP_TYPE_JMP;
		break;
	case 0x80: // bra
		op->eob = true;
		op->jump = addr + 2 + (st8)data[1];
		op->type = RZ_ANALYSIS_OP_TYPE_JMP;
		break;
	case 0x82: // brl
		op->eob = true;
		op->jump = addr + 3 + (st16)rz_read_le16(data + 1);
		op->type = RZ_ANALYSIS_OP_TYPE_JMP;
		break;
	case 0x6c: // jmp (addr)
	case 0x7c: // jmp (addr,X)
	case 0xdc: // jmp [addr]
		op->eob = true;
		op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
		break;
	case 0x10: // bpl
	case 0x30: // bmi
	case 0x50: // bvc
	case 0x70: // bvs
	case 0x90: // bcc
	case 0xb0: // bcs
		op->eob = true;
		op->jump = addr + 2 + (st8)data[1];
		op->fail = addr + 2;
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		break;
	case 0xd0: // bne
		op->eob = true;
		op->cond = RZ_TYPE_COND_NE;
		op->jump = addr + 2 + (st8)data[1];
		op->fail = addr + 2;
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		break;
	case 0xf0: // beq
		op->eob = true;
		op->cond = RZ_TYPE_COND_EQ;
		op->jump = addr + 2 + (st8)data[1];
		op->fail = addr + 2;
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		break;
	case 0x20: // jsr addr
		op->jump = (addr & 0xFF0000) | rz_read_le16(data + 1);
		op->type = RZ_ANALYSIS_OP_TYPE_CALL;
		break;
	case 0x22: // jsr long
		op->jump = data[1] | data[2] << 8 | data[3] << 16;
		op->type = RZ_ANALYSIS_OP_TYPE_CALL;
		break;
	case 0xfc: // jsr (addr,X)
		op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
		break;
	case 0x40: // rti
	case 0x60: // rts
	case 0x6b: // rtl
		op->eob = true;
		op->type = RZ_ANALYSIS_OP_TYPE_RET;
		break;
	case 0xc2: // rep
		if (((st8)data[1]) & 0x10) {
			snesflags->X = 0;
		}
		if (((st8)data[1]) & 0x20) {
			snesflags->M = 0;
		}
		break;
	case 0xe2: // sep
		if (((st8)data[1]) & 0x10) {
			snesflags->X = 1;
		}
		if (((st8)data[1]) & 0x20) {
			snesflags->M = 1;
		}
		break;
	}
	return op->size;
}

static bool snes_analysis_init(void **user) {
	*user = RZ_NEW0(struct snes_asm_flags);
	return *user != NULL;
}

static bool snes_analysis_fini(void *user) {
	rz_return_val_if_fail(user, false);
	free(user);
	return true;
}

static int snes_archinfo(RzAnalysis *a, RzAnalysisInfoType query) {
	switch (query) {
	case RZ_ANALYSIS_ARCHINFO_MIN_OP_SIZE:
		return 1;
	case RZ_ANALYSIS_ARCHINFO_MAX_OP_SIZE:
		return 4;
	case RZ_ANALYSIS_ARCHINFO_TEXT_ALIGN:
		return 1;
	case RZ_ANALYSIS_ARCHINFO_DATA_ALIGN:
		return 1;
	case RZ_ANALYSIS_ARCHINFO_CAN_USE_POINTERS:
		return true;
	default:
		return -1;
	}
}

RzAnalysisPlugin rz_analysis_plugin_snes = {
	.name = "snes",
	.desc = "SNES analysis plugin",
	.license = "LGPL3",
	.arch = "snes",
	.bits = 16,
	.init = snes_analysis_init,
	.fini = snes_analysis_fini,
	.op = &snes_anop,
	.archinfo = snes_archinfo,
};
