#define _CRT_SECURE_NO_WARNINGS
#define NOMINMAX

#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <exception>

#include "binaryninjaapi.h"
#include "arch_armv7.h"
#include "il.h"

using namespace BinaryNinja;
using namespace armv7;
using namespace std;


#if defined(_MSC_VER)
#define snprintf _snprintf
#endif

#define DISASM_SUCCESS 0
#define FAILED_TO_DISASSEMBLE_OPERAND 1
#define FAILED_TO_DISASSEMBLE_REGISTER 2

#define COALESCE_MAX_INSTRS 100

#define HANDLE_CASE(orig, opposite) case orig: case opposite: return (candidate == orig) || (candidate == opposite)

static bool IsRelatedCondition(Condition orig, Condition candidate)
{
	switch (orig)
	{
		HANDLE_CASE(COND_EQ, COND_NE);
		HANDLE_CASE(COND_CS, COND_CC);
		HANDLE_CASE(COND_MI, COND_PL);
		HANDLE_CASE(COND_VS, COND_VC);
		HANDLE_CASE(COND_HI, COND_LS);
		HANDLE_CASE(COND_GE, COND_LT);
		HANDLE_CASE(COND_GT, COND_LE);
		default: return false;
	}
}

static bool CanCoalesceAfterInstruction(Instruction& instr)
{
	switch (instr.operation)
	{
	case ARMV7_BX:
	case ARMV7_B:
		return false;

	case ARMV7_ADC:
	case ARMV7_ADD:
	case ARMV7_AND:
	case ARMV7_ASR:
	case ARMV7_BIC:
	case ARMV7_EOR:
	case ARMV7_LDR:
	case ARMV7_LSL:
	case ARMV7_LSR:
	case ARMV7_MOV:
	case ARMV7_MVN:
	case ARMV7_ORR:
	case ARMV7_ROR:
	case ARMV7_RRX:
	case ARMV7_RSB:
	case ARMV7_RSC:
	case ARMV7_SUB:
	case ARMV7_SBC:
	case ARMV7_MOVW:
	case ARMV7_MOVT:
	case ARMV7_LDRT:
	case ARMV7_LDRH:
	case ARMV7_LDRHT:
	case ARMV7_LDRB:
	case ARMV7_LDRBT:
	case ARMV7_LDRSH:
	case ARMV7_LDRSHT:
	case ARMV7_LDRSB:
	case ARMV7_LDRSBT:
	case ARMV7_LDRD:
	case ARMV7_ADR:
	case ARMV7_UBFX:
	case ARMV7_UXTAB:
	case ARMV7_UXTB:
	case ARMV7_UXTH:
	case ARMV7_MUL:
	case ARMV7_SDIV:
	case ARMV7_UDIV:
	case ARMV7_SBFX:
	case ARMV7_SXTB:
	case ARMV7_SXTH:
	case ARMV7_BFC:
	case ARMV7_BFI:
	case ARMV7_CLZ:
		if (instr.operands[0].cls == REG && instr.operands[0].reg == REG_PC)
			return false;
		return true;

	default:
		return true;
	}
}

enum MachoArmRelocationType : uint32_t
{
	ARM_RELOC_VANILLA = 0,
	ARM_RELOC_PAIR = 1,
	ARM_RELOC_SECTDIFF = 2,
	ARM_RELOC_LOCAL_SECTDIFF = 3,
	ARM_RELOC_PB_LA_PTR = 4,
	ARM_RELOC_BR24 = 5,
	ARM_THUMB_RELOC_BR22 = 6,
	ARM_THUMB_32BIT_BRANCH = 7,
	ARM_RELOC_HALF = 8,
	ARM_RELOC_HALF_SECTDIFF = 9,
	MACHO_MAX_ARM_RELOCATION
};

enum ElfArmRelocationType : uint32_t
{
	R_ARM_NONE               = 0,
	R_ARM_PC24               = 1,
	R_ARM_ABS32              = 2,
	R_ARM_REL32              = 3,
	R_ARM_LDR_PC_G0          = 4,
	R_ARM_ABS16              = 5,
	R_ARM_ABS12              = 6,
	R_ARM_THM_ABS5           = 7,
	R_ARM_ABS8               = 8,
	R_ARM_SBREL32            = 9,
	R_ARM_THM_CALL           = 10,
	R_ARM_THM_PC8            = 11,
	R_ARM_BREL_ADJ           = 12,
	R_ARM_TLS_DESC           = 13,
	R_ARM_THM_SWI8           = 14,
	R_ARM_XPC25              = 15,
	R_ARM_THM_XPC22          = 16,
	R_ARM_TLS_DTPMOD32       = 17,
	R_ARM_TLS_DTPOFF32       = 18,
	R_ARM_TLS_TPOFF32        = 19,
	R_ARM_COPY               = 20,
	R_ARM_GLOB_DAT           = 21,
	R_ARM_JUMP_SLOT          = 22,
	R_ARM_RELATIVE           = 23,
	R_ARM_GOTOFF32           = 24,
	R_ARM_BASE_PREL          = 25,
	R_ARM_GOT_BREL           = 26,
	R_ARM_PLT32              = 27,
	R_ARM_CALL               = 28,
	R_ARM_JUMP24             = 29,
	R_ARM_THM_JUMP24         = 30,
	R_ARM_BASE_ABS           = 31,
	R_ARM_ALU_PCREL_7_0      = 32,
	R_ARM_ALU_PCREL_15_8     = 33,
	R_ARM_ALU_PCREL_23_15    = 34,
	R_ARM_LDR_SBREL_11_0_NC  = 35,
	R_ARM_ALU_SBREL_19_12_NC = 36,
	R_ARM_ALU_SBREL_27_20_CK = 37,
	R_ARM_TARGET1            = 38,
	R_ARM_SBREL31            = 39,
	R_ARM_V4BX               = 40,
	R_ARM_TARGET2            = 41,
	R_ARM_PREL31             = 42,
	R_ARM_MOVW_ABS_NC        = 43,
	R_ARM_MOVT_ABS           = 44,
	R_ARM_MOVW_PREL_NC       = 45,
	R_ARM_MOVT_PREL          = 46,
	R_ARM_THM_MOVW_ABS_NC    = 47,
	R_ARM_THM_MOVT_ABS       = 48,
	R_ARM_THM_MOVW_PREL_NC   = 49,
	R_ARM_THM_MOVT_PREL      = 50,
	R_ARM_THM_JUMP19         = 51,
	R_ARM_THM_JUMP6          = 52,
	R_ARM_THM_ALU_PREL_11_0  = 53,
	R_ARM_THM_PC12           = 54,
	R_ARM_ABS32_NOI          = 55,
	R_ARM_REL32_NOI          = 56,
	R_ARM_ALU_PC_G0_NC       = 57,
	R_ARM_ALU_PC_G0          = 58,
	R_ARM_ALU_PC_G1_NC       = 59,
	R_ARM_ALU_PC_G1          = 60,
	R_ARM_ALU_PC_G2          = 61,
	R_ARM_LDR_PC_G1          = 62,
	R_ARM_LDR_PC_G2          = 63,
	R_ARM_LDRS_PC_G0         = 64,
	R_ARM_LDRS_PC_G1         = 65,
	R_ARM_LDRS_PC_G2         = 66,
	R_ARM_LDC_PC_G0          = 67,
	R_ARM_LDC_PC_G1          = 68,
	R_ARM_LDC_PC_G2          = 69,
	R_ARM_ALU_SB_G0_NC       = 70,
	R_ARM_ALU_SB_G0          = 71,
	R_ARM_ALU_SB_G1_NC       = 72,
	R_ARM_ALU_SB_G1          = 73,
	R_ARM_ALU_SB_G2          = 74,
	R_ARM_LDR_SB_G0          = 75,
	R_ARM_LDR_SB_G1          = 76,
	R_ARM_LDR_SB_G2          = 77,
	R_ARM_LDRS_SB_G0         = 78,
	R_ARM_LDRS_SB_G1         = 79,
	R_ARM_LDRS_SB_G2         = 80,
	R_ARM_LDC_SB_G0          = 81,
	R_ARM_LDC_SB_G1          = 82,
	R_ARM_LDC_SB_G2          = 83,
	R_ARM_MOVW_BREL_NC       = 84,
	R_ARM_MOVT_BREL          = 85,
	R_ARM_MOVW_BREL          = 86,
	R_ARM_THM_MOVW_BREL_NC   = 87,
	R_ARM_THM_MOVT_BREL      = 88,
	R_ARM_THM_MOVW_BREL      = 89,
	R_ARM_TLS_GOTDESC        = 90,
	R_ARM_TLS_CALL           = 91,
	R_ARM_TLS_DESCSEQ        = 92,
	R_ARM_THM_TLS_CALL       = 93,
	R_ARM_PLT32_ABS          = 94,
	R_ARM_GOT_ABS            = 95,
	R_ARM_GOT_PREL           = 96,
	R_ARM_GOT_BREL12         = 97,
	R_ARM_GOTOFF12           = 98,
	R_ARM_GOTRELAX           = 99,
	R_ARM_GNU_VTENTRY        = 100,
	R_ARM_GNU_VTINHERIT      = 101,
	R_ARM_THM_JUMP11         = 102,
	R_ARM_THM_JUMP8          = 103,
	R_ARM_TLS_GD32           = 104,
	R_ARM_TLS_LDM32          = 105,
	R_ARM_TLS_LDO32          = 106,
	R_ARM_TLS_IE32           = 107,
	R_ARM_TLS_LE32           = 108,
	R_ARM_TLS_LDO12          = 109,
	R_ARM_TLS_LE12           = 110,
	R_ARM_TLS_IE12GP         = 111,
	R_ARM_ME_TOO             = 128,
	R_ARM_THM_TLS_DESCSEQ16  = 129,
	R_ARM_THM_TLS_DESCSEQ32  = 130,
	R_ARM_THM_GOT_BREL12     = 131,
	R_ARM_THM_ALU_ABS_G0_NC  = 132,
	R_ARM_THM_ALU_ABS_G1_NC  = 133,
	R_ARM_THM_ALU_ABS_G2_NC  = 134,
	R_ARM_THM_ALU_ABS_G3     = 135,
	R_ARM_IRELATIVE          = 160,
	R_ARM_RXPC25             = 249,
	R_ARM_RSBREL32           = 250,
	R_ARM_THM_RPC22          = 251,
	R_ARM_RREL32             = 252,
	R_ARM_RABS32             = 253,
	R_ARM_RPC24              = 254,
	R_ARM_RBASE              = 255
};

enum PeArmRelocationType : uint32_t
{
	PE_IMAGE_REL_ARM_ABSOLUTE   = 0x0000, // The relocation is ignored.
	PE_IMAGE_REL_ARM_ADDR32     = 0x0001, // The 32-bit VA of the target.
	PE_IMAGE_REL_ARM_ADDR32NB   = 0x0002, // The 32-bit RVA of the target.
	PE_IMAGE_REL_ARM_BRANCH24   = 0x0003, // The 24-bit relative displacement to the target.
	PE_IMAGE_REL_ARM_BRANCH11   = 0x0004, // The reference to a subroutine call. The reference consists of two 16-bit instructions with 11-bit offsets.
	PE_IMAGE_REL_ARM_SECTION    = 0x000E, // The 16-bit section index of the section that contains the target. This is used to support debugging information.
	PE_IMAGE_REL_ARM_SECREL     = 0x000F, // The 32-bit offset of the target from the beginning of its section. This is used to support debugging information and static thread local storage.
	PE_IMAGE_REL_ARM_MOV32      = 0x0010, // The 32-bit VA of the target. This relocation is applied using a MOVW instruction for the low 16 bits followed by a MOVT for the high 16 bits.
	PE_IMAGE_REL_THUMB_MOV32    = 0x0011, // The 32-bit VA of the target. This relocation is applied using a MOVW instruction for the low 16 bits followed by a MOVT for the high 16 bits.
	PE_IMAGE_REL_THUMB_BRANCH20 = 0x0012, // The instruction is fixed up with the 21-bit relative displacement to the 2-byte aligned target. The least significant bit of the displacement is always zero and is not stored. This relocation corresponds to a Thumb-2 32-bit conditional B instruction.
	PE_IMAGE_REL_THUMB_UNUSED   = 0x0013, // Unused
	PE_IMAGE_REL_THUMB_BRANCH24 = 0x0014, // The instruction is fixed up with the 25-bit relative displacement to the 2-byte aligned target. The least significant bit of the displacement is zero and is not stored.This relocation corresponds to a Thumb-2 B instruction.
	PE_IMAGE_REL_THUMB_BLX23    = 0x0015, // The instruction is fixed up with the 25-bit relative displacement to the 4-byte aligned target. The low 2 bits of the displacement are zero and are not stored. This relocation corresponds to a Thumb-2 BLX instruction.
	PE_IMAGE_REL_ARM_PAIR       = 0x0016, // The relocation is valid only when it immediately follows a ARM_REFHI or THUMB_REFHI. Its SymbolTableIndex contains a displacement and not an index into the symbol table.
	MAX_ARM_PE_RELOCATION
};

static const char* GetRelocationString(MachoArmRelocationType rel)
{
	static const char* relocTable[] =
	{
		"ARM_RELOC_VANILLA",
		"ARM_RELOC_PAIR",
		"ARM_RELOC_SECTDIFF",
		"ARM_RELOC_LOCAL_SECTDIFF",
		"ARM_RELOC_PB_LA_PTR",
		"ARM_RELOC_BR24",
		"ARM_THUMB_RELOC_BR22",
		"ARM_THUMB_32BIT_BRANCH",
		"ARM_RELOC_HALF",
		"ARM_RELOC_HALF_SECTDIFF"
	};
	if (rel < MACHO_MAX_ARM_RELOCATION)
	{
		return relocTable[rel];
	}
	return "Unknown ARM relocation";
}


static const char* GetRelocationString(ElfArmRelocationType rel)
{
	static map<ElfArmRelocationType, const char*> relocTable =
	{
		{R_ARM_NONE, "R_ARM_NONE"},
		{R_ARM_PC24, "R_ARM_PC24"},
		{R_ARM_ABS32, "R_ARM_ABS32"},
		{R_ARM_REL32, "R_ARM_REL32"},
		{R_ARM_LDR_PC_G0, "R_ARM_LDR_PC_G0"},
		{R_ARM_ABS16, "R_ARM_ABS16"},
		{R_ARM_ABS12, "R_ARM_ABS12"},
		{R_ARM_THM_ABS5, "R_ARM_THM_ABS5"},
		{R_ARM_ABS8, "R_ARM_ABS8"},
		{R_ARM_SBREL32, "R_ARM_SBREL32"},
		{R_ARM_THM_CALL, "R_ARM_THM_CALL"},
		{R_ARM_THM_PC8, "R_ARM_THM_PC8"},
		{R_ARM_BREL_ADJ, "R_ARM_BREL_ADJ"},
		{R_ARM_TLS_DESC, "R_ARM_TLS_DESC"},
		{R_ARM_THM_SWI8, "R_ARM_THM_SWI8"},
		{R_ARM_XPC25, "R_ARM_XPC25"},
		{R_ARM_THM_XPC22, "R_ARM_THM_XPC22"},
		{R_ARM_TLS_DTPMOD32, "R_ARM_TLS_DTPMOD32"},
		{R_ARM_TLS_DTPOFF32, "R_ARM_TLS_DTPOFF32"},
		{R_ARM_TLS_TPOFF32, "R_ARM_TLS_TPOFF32"},
		{R_ARM_COPY, "R_ARM_COPY"},
		{R_ARM_GLOB_DAT, "R_ARM_GLOB_DAT"},
		{R_ARM_JUMP_SLOT, "R_ARM_JUMP_SLOT"},
		{R_ARM_RELATIVE, "R_ARM_RELATIVE"},
		{R_ARM_GOTOFF32, "R_ARM_GOTOFF32"},
		{R_ARM_BASE_PREL, "R_ARM_BASE_PREL"},
		{R_ARM_GOT_BREL, "R_ARM_GOT_BREL"},
		{R_ARM_PLT32, "R_ARM_PLT32"},
		{R_ARM_CALL, "R_ARM_CALL"},
		{R_ARM_JUMP24, "R_ARM_JUMP24"},
		{R_ARM_THM_JUMP24, "R_ARM_THM_JUMP24"},
		{R_ARM_BASE_ABS, "R_ARM_BASE_ABS"},
		{R_ARM_ALU_PCREL_7_0, "R_ARM_ALU_PCREL_7_0"},
		{R_ARM_ALU_PCREL_15_8, "R_ARM_ALU_PCREL_15_8"},
		{R_ARM_ALU_PCREL_23_15, "R_ARM_ALU_PCREL_23_15"},
		{R_ARM_LDR_SBREL_11_0_NC, "R_ARM_LDR_SBREL_11_0_NC"},
		{R_ARM_ALU_SBREL_19_12_NC, "R_ARM_ALU_SBREL_19_12_NC"},
		{R_ARM_ALU_SBREL_27_20_CK, "R_ARM_ALU_SBREL_27_20_CK"},
		{R_ARM_TARGET1, "R_ARM_TARGET1"},
		{R_ARM_SBREL31, "R_ARM_SBREL31"},
		{R_ARM_V4BX, "R_ARM_V4BX"},
		{R_ARM_TARGET2, "R_ARM_TARGET2"},
		{R_ARM_PREL31, "R_ARM_PREL31"},
		{R_ARM_MOVW_ABS_NC, "R_ARM_MOVW_ABS_NC"},
		{R_ARM_MOVT_ABS, "R_ARM_MOVT_ABS"},
		{R_ARM_MOVW_PREL_NC, "R_ARM_MOVW_PREL_NC"},
		{R_ARM_MOVT_PREL, "R_ARM_MOVT_PREL"},
		{R_ARM_THM_MOVW_ABS_NC, "R_ARM_THM_MOVW_ABS_NC"},
		{R_ARM_THM_MOVT_ABS, "R_ARM_THM_MOVT_ABS"},
		{R_ARM_THM_MOVW_PREL_NC, "R_ARM_THM_MOVW_PREL_NC"},
		{R_ARM_THM_MOVT_PREL, "R_ARM_THM_MOVT_PREL"},
		{R_ARM_THM_JUMP19, "R_ARM_THM_JUMP19"},
		{R_ARM_THM_JUMP6, "R_ARM_THM_JUMP6"},
		{R_ARM_THM_ALU_PREL_11_0, "R_ARM_THM_ALU_PREL_11_0"},
		{R_ARM_THM_PC12, "R_ARM_THM_PC12"},
		{R_ARM_ABS32_NOI, "R_ARM_ABS32_NOI"},
		{R_ARM_REL32_NOI, "R_ARM_REL32_NOI"},
		{R_ARM_ALU_PC_G0_NC, "R_ARM_ALU_PC_G0_NC"},
		{R_ARM_ALU_PC_G0, "R_ARM_ALU_PC_G0"},
		{R_ARM_ALU_PC_G1_NC, "R_ARM_ALU_PC_G1_NC"},
		{R_ARM_ALU_PC_G1, "R_ARM_ALU_PC_G1"},
		{R_ARM_ALU_PC_G2, "R_ARM_ALU_PC_G2"},
		{R_ARM_LDR_PC_G1, "R_ARM_LDR_PC_G1"},
		{R_ARM_LDR_PC_G2, "R_ARM_LDR_PC_G2"},
		{R_ARM_LDRS_PC_G0, "R_ARM_LDRS_PC_G0"},
		{R_ARM_LDRS_PC_G1, "R_ARM_LDRS_PC_G1"},
		{R_ARM_LDRS_PC_G2, "R_ARM_LDRS_PC_G2"},
		{R_ARM_LDC_PC_G0, "R_ARM_LDC_PC_G0"},
		{R_ARM_LDC_PC_G1, "R_ARM_LDC_PC_G1"},
		{R_ARM_LDC_PC_G2, "R_ARM_LDC_PC_G2"},
		{R_ARM_ALU_SB_G0_NC, "R_ARM_ALU_SB_G0_NC"},
		{R_ARM_ALU_SB_G0, "R_ARM_ALU_SB_G0"},
		{R_ARM_ALU_SB_G1_NC, "R_ARM_ALU_SB_G1_NC"},
		{R_ARM_ALU_SB_G1, "R_ARM_ALU_SB_G1"},
		{R_ARM_ALU_SB_G2, "R_ARM_ALU_SB_G2"},
		{R_ARM_LDR_SB_G0, "R_ARM_LDR_SB_G0"},
		{R_ARM_LDR_SB_G1, "R_ARM_LDR_SB_G1"},
		{R_ARM_LDR_SB_G2, "R_ARM_LDR_SB_G2"},
		{R_ARM_LDRS_SB_G0, "R_ARM_LDRS_SB_G0"},
		{R_ARM_LDRS_SB_G1, "R_ARM_LDRS_SB_G1"},
		{R_ARM_LDRS_SB_G2, "R_ARM_LDRS_SB_G2"},
		{R_ARM_LDC_SB_G0, "R_ARM_LDC_SB_G0"},
		{R_ARM_LDC_SB_G1, "R_ARM_LDC_SB_G1"},
		{R_ARM_LDC_SB_G2, "R_ARM_LDC_SB_G2"},
		{R_ARM_MOVW_BREL_NC, "R_ARM_MOVW_BREL_NC"},
		{R_ARM_MOVT_BREL, "R_ARM_MOVT_BREL"},
		{R_ARM_MOVW_BREL, "R_ARM_MOVW_BREL"},
		{R_ARM_THM_MOVW_BREL_NC, "R_ARM_THM_MOVW_BREL_NC"},
		{R_ARM_THM_MOVT_BREL, "R_ARM_THM_MOVT_BREL"},
		{R_ARM_THM_MOVW_BREL, "R_ARM_THM_MOVW_BREL"},
		{R_ARM_TLS_GOTDESC, "R_ARM_TLS_GOTDESC"},
		{R_ARM_TLS_CALL, "R_ARM_TLS_CALL"},
		{R_ARM_TLS_DESCSEQ, "R_ARM_TLS_DESCSEQ"},
		{R_ARM_THM_TLS_CALL, "R_ARM_THM_TLS_CALL"},
		{R_ARM_PLT32_ABS, "R_ARM_PLT32_ABS"},
		{R_ARM_GOT_ABS, "R_ARM_GOT_ABS"},
		{R_ARM_GOT_PREL, "R_ARM_GOT_PREL"},
		{R_ARM_GOT_BREL12, "R_ARM_GOT_BREL12"},
		{R_ARM_GOTOFF12, "R_ARM_GOTOFF12"},
		{R_ARM_GOTRELAX, "R_ARM_GOTRELAX"},
		{R_ARM_GNU_VTENTRY, "R_ARM_GNU_VTENTRY"},
		{R_ARM_GNU_VTINHERIT, "R_ARM_GNU_VTINHERIT"},
		{R_ARM_THM_JUMP11, "R_ARM_THM_JUMP11"},
		{R_ARM_THM_JUMP8, "R_ARM_THM_JUMP8"},
		{R_ARM_TLS_GD32, "R_ARM_TLS_GD32"},
		{R_ARM_TLS_LDM32, "R_ARM_TLS_LDM32"},
		{R_ARM_TLS_LDO32, "R_ARM_TLS_LDO32"},
		{R_ARM_TLS_IE32, "R_ARM_TLS_IE32"},
		{R_ARM_TLS_LE32, "R_ARM_TLS_LE32"},
		{R_ARM_TLS_LDO12, "R_ARM_TLS_LDO12"},
		{R_ARM_TLS_LE12, "R_ARM_TLS_LE12"},
		{R_ARM_TLS_IE12GP, "R_ARM_TLS_IE12GP"},
		{R_ARM_ME_TOO, "R_ARM_ME_TOO"},
		{R_ARM_THM_TLS_DESCSEQ16, "R_ARM_THM_TLS_DESCSEQ16"},
		{R_ARM_THM_TLS_DESCSEQ32, "R_ARM_THM_TLS_DESCSEQ32"},
		{R_ARM_THM_GOT_BREL12, "R_ARM_THM_GOT_BREL12"},
		{R_ARM_THM_ALU_ABS_G0_NC, "R_ARM_THM_ALU_ABS_G0_NC"},
		{R_ARM_THM_ALU_ABS_G1_NC, "R_ARM_THM_ALU_ABS_G1_NC"},
		{R_ARM_THM_ALU_ABS_G2_NC, "R_ARM_THM_ALU_ABS_G2_NC"},
		{R_ARM_THM_ALU_ABS_G3, "R_ARM_THM_ALU_ABS_G3"},
		{R_ARM_IRELATIVE, "R_ARM_IRELATIVE"},
		{R_ARM_RXPC25, "R_ARM_RXPC25"},
		{R_ARM_RSBREL32, "R_ARM_RSBREL32"},
		{R_ARM_THM_RPC22, "R_ARM_THM_RPC22"},
		{R_ARM_RREL32, "R_ARM_RREL32"},
		{R_ARM_RABS32, "R_ARM_RABS32"},
		{R_ARM_RPC24, "R_ARM_RPC24"},
		{R_ARM_RBASE, "R_ARM_RBASE"}
	};
	if (relocTable.count(rel))
		return relocTable.at(rel);
	return "Unknown ARM relocation";
}


static const char* GetRelocationString(PeArmRelocationType rel)
{
	static const char* relocTable[] =
	{
		"IMAGE_REL_ARM_ABSOLUTE",
		"IMAGE_REL_ARM_ADDR32",
		"IMAGE_REL_ARM_ADDR32NB",
		"IMAGE_REL_ARM_BRANCH24",
		"IMAGE_REL_ARM_BRANCH11",
		"IMAGE_REL_ARM_SECTION",
		"IMAGE_REL_ARM_SECREL",
		"IMAGE_REL_ARM_MOV32",
		"IMAGE_REL_THUMB_MOV32",
		"IMAGE_REL_THUMB_BRANCH20",
		"IMAGE_REL_THUMB_UNUSED",
		"IMAGE_REL_THUMB_BRANCH24",
		"IMAGE_REL_THUMB_BLX23",
		"IMAGE_REL_ARM_PAIR"
	};
	if (rel < MAX_ARM_PE_RELOCATION)
		return relocTable[rel];
	return "Unknown ARM relocation";
}

static bool IsELFDataRelocation(ElfArmRelocationType reloc)
{
	map<ElfArmRelocationType, bool> isDataMap =
	{
		{R_ARM_NONE, false},
		{R_ARM_PC24, false},
		{R_ARM_ABS32, true},
		{R_ARM_REL32, true},
		{R_ARM_LDR_PC_G0, false},
		{R_ARM_ABS16, true},
		{R_ARM_ABS12, false},
		{R_ARM_THM_ABS5, false},
		{R_ARM_ABS8, true},
		{R_ARM_SBREL32, true},
		{R_ARM_THM_CALL, false},
		{R_ARM_THM_PC8, false},
		{R_ARM_BREL_ADJ, true},
		{R_ARM_TLS_DESC, true},
		{R_ARM_THM_SWI8, false},
		{R_ARM_XPC25, false},
		{R_ARM_THM_XPC22, false},
		{R_ARM_TLS_DTPMOD32, true},
		{R_ARM_TLS_DTPOFF32, true},
		{R_ARM_TLS_TPOFF32, true},
		{R_ARM_COPY, true},
		{R_ARM_GLOB_DAT, true},
		{R_ARM_JUMP_SLOT, true},
		{R_ARM_RELATIVE, true},
		{R_ARM_GOTOFF32, true},
		{R_ARM_BASE_PREL, true},
		{R_ARM_GOT_BREL, true},
		{R_ARM_PLT32, false},
		{R_ARM_CALL, false},
		{R_ARM_JUMP24, false},
		{R_ARM_THM_JUMP24, false},
		{R_ARM_BASE_ABS, true},
		{R_ARM_ALU_PCREL_7_0, false},
		{R_ARM_ALU_PCREL_15_8, false},
		{R_ARM_ALU_PCREL_23_15, false},
		{R_ARM_LDR_SBREL_11_0_NC, false},
		{R_ARM_ALU_SBREL_19_12_NC, false},
		{R_ARM_ALU_SBREL_27_20_CK, false},
		{R_ARM_TARGET1, false},
		{R_ARM_SBREL31, true},
		{R_ARM_V4BX, false},
		{R_ARM_TARGET2, false},
		{R_ARM_PREL31, true},
		{R_ARM_MOVW_ABS_NC, false},
		{R_ARM_MOVT_ABS, false},
		{R_ARM_MOVW_PREL_NC, false},
		{R_ARM_MOVT_PREL, false},
		{R_ARM_THM_MOVW_ABS_NC, false},
		{R_ARM_THM_MOVT_ABS, false},
		{R_ARM_THM_MOVW_PREL_NC, false},
		{R_ARM_THM_MOVT_PREL, false},
		{R_ARM_THM_JUMP19, false},
		{R_ARM_THM_JUMP6, false},
		{R_ARM_THM_ALU_PREL_11_0, false},
		{R_ARM_THM_PC12, false},
		{R_ARM_ABS32_NOI, true},
		{R_ARM_REL32_NOI, true},
		{R_ARM_ALU_PC_G0_NC, false},
		{R_ARM_ALU_PC_G0, false},
		{R_ARM_ALU_PC_G1_NC, false},
		{R_ARM_ALU_PC_G1, false},
		{R_ARM_ALU_PC_G2, false},
		{R_ARM_LDR_PC_G1, false},
		{R_ARM_LDR_PC_G2, false},
		{R_ARM_LDRS_PC_G0, false},
		{R_ARM_LDRS_PC_G1, false},
		{R_ARM_LDRS_PC_G2, false},
		{R_ARM_LDC_PC_G0, false},
		{R_ARM_LDC_PC_G1, false},
		{R_ARM_LDC_PC_G2, false},
		{R_ARM_ALU_SB_G0_NC, false},
		{R_ARM_ALU_SB_G0, false},
		{R_ARM_ALU_SB_G1_NC, false},
		{R_ARM_ALU_SB_G1, false},
		{R_ARM_ALU_SB_G2, false},
		{R_ARM_LDR_SB_G0, false},
		{R_ARM_LDR_SB_G1, false},
		{R_ARM_LDR_SB_G2, false},
		{R_ARM_LDRS_SB_G0, false},
		{R_ARM_LDRS_SB_G1, false},
		{R_ARM_LDRS_SB_G2, false},
		{R_ARM_LDC_SB_G0, false},
		{R_ARM_LDC_SB_G1, false},
		{R_ARM_LDC_SB_G2, false},
		{R_ARM_MOVW_BREL_NC, false},
		{R_ARM_MOVT_BREL, false},
		{R_ARM_MOVW_BREL, false},
		{R_ARM_THM_MOVW_BREL_NC, false},
		{R_ARM_THM_MOVT_BREL, false},
		{R_ARM_THM_MOVW_BREL, false},
		{R_ARM_TLS_GOTDESC, true},
		{R_ARM_TLS_CALL, false},
		{R_ARM_TLS_DESCSEQ, false},
		{R_ARM_THM_TLS_CALL, false},
		{R_ARM_PLT32_ABS, true},
		{R_ARM_GOT_ABS, true},
		{R_ARM_GOT_PREL, true},
		{R_ARM_GOT_BREL12, false},
		{R_ARM_GOTOFF12, false},
		{R_ARM_GOTRELAX, false},
		{R_ARM_GNU_VTENTRY, true},
		{R_ARM_GNU_VTINHERIT, true},
		{R_ARM_THM_JUMP11, false},
		{R_ARM_THM_JUMP8, false},
		{R_ARM_TLS_GD32, true},
		{R_ARM_TLS_LDM32, true},
		{R_ARM_TLS_LDO32, true},
		{R_ARM_TLS_IE32, true},
		{R_ARM_TLS_LE32, false},
		{R_ARM_TLS_LDO12, false},
		{R_ARM_TLS_LE12, false},
		{R_ARM_TLS_IE12GP, false},
		{R_ARM_ME_TOO, false},
		{R_ARM_THM_TLS_DESCSEQ16, false},
		{R_ARM_THM_TLS_DESCSEQ32, false},
		{R_ARM_THM_GOT_BREL12, false},
		{R_ARM_THM_ALU_ABS_G0_NC, false},
		{R_ARM_THM_ALU_ABS_G1_NC, false},
		{R_ARM_THM_ALU_ABS_G2_NC, false},
		{R_ARM_THM_ALU_ABS_G3, false},
		{R_ARM_IRELATIVE, false},
		{R_ARM_RXPC25, false},
		{R_ARM_RSBREL32, false},
		{R_ARM_THM_RPC22, false},
		{R_ARM_RREL32, false},
		{R_ARM_RABS32, false},
		{R_ARM_RPC24, false},
		{R_ARM_RBASE, false}
	};
	if (!isDataMap.count(reloc))
		return false;
	return isDataMap.at(reloc);
}

static BNRegisterInfo RegisterInfo(uint32_t fullWidthReg, size_t offset, size_t size, bool zeroExtend = false)
{
	BNRegisterInfo result;
	result.fullWidthRegister = fullWidthReg;
	result.offset = offset;
	result.size = size;
	result.extend = zeroExtend ? ZeroExtendToFullWidth : NoExtend;
	return result;
}

class Armv7Architecture: public ArmCommonArchitecture
{
protected:
	virtual std::string GetAssemblerTriple() override
	{
		if(m_endian == BigEndian)
			return "armv7eb-none-none";

		return "armv7-none-none";
	}

	virtual bool Disassemble(const uint8_t* data, uint64_t addr, size_t maxLen, Instruction& result)
	{
		(void)addr;
		(void)maxLen;
		memset(&result, 0, sizeof(result));
		if (armv7_decompose(*(uint32_t*)data, &result, (uint32_t)addr, (uint32_t)(m_endian == BigEndian)) != 0)
			return false;
		return true;
	}

	void SetInstructionInfoForInstruction(uint64_t addr, const Instruction& instr, InstructionInfo& result)
	{
		result.length = 4;

		switch (instr.operation)
		{
		case ARMV7_BL:
			if (UNCONDITIONAL(instr.cond) && (instr.operands[0].cls == LABEL))
				result.AddBranch(CallDestination, instr.operands[0].imm, this);
			break;
		case ARMV7_BLX:
			result.archTransitionByTargetAddr = true;
			if (UNCONDITIONAL(instr.cond) && (instr.operands[0].cls == LABEL))
				result.AddBranch(CallDestination, instr.operands[0].imm, m_thumbArch);
			break;
		case ARMV7_BX:
			if (UNCONDITIONAL(instr.cond))
			{
				if (instr.operands[0].cls == REG && instr.operands[0].reg == REG_LR)
					result.AddBranch(FunctionReturn);
				else
				{
					result.AddBranch(UnresolvedBranch);
					result.archTransitionByTargetAddr = true;
				}
			}
			break;
		case ARMV7_B:
			if (UNCONDITIONAL(instr.cond))
				result.AddBranch(UnconditionalBranch, instr.operands[0].imm, this);
			else
			{
				result.AddBranch(TrueBranch, instr.operands[0].imm, this);
				result.AddBranch(FalseBranch, addr + 4, this);
			}
			break;
		case ARMV7_POP:
			//if this is an unconditional pop with PC in the register list treat as a return
			if (UNCONDITIONAL(instr.cond) &&
				instr.operands[0].cls == REG_LIST &&
				((instr.operands[0].reg & REG_LIST_PC) == REG_LIST_PC))
				result.AddBranch(FunctionReturn);
			break;
		case ARMV7_LDM:
		case ARMV7_LDMDA:
		case ARMV7_LDMDB:
		case ARMV7_LDMIA: // defaults to ARMV7_LDM
		case ARMV7_LDMIB:
			//if this is an unconditional load multiple with PC in the register list treat as a return
			if (UNCONDITIONAL(instr.cond))
			{
				if (instr.operands[1].cls == REG_LIST && ((RegisterList)instr.operands[1].reg == REG_LIST_PC))
				{
					result.archTransitionByTargetAddr = true;
					result.AddBranch(UnresolvedBranch);
				}
				else if (instr.operands[1].cls == REG_LIST && ((instr.operands[1].reg & REG_LIST_PC) == REG_LIST_PC))
					result.AddBranch(FunctionReturn);
			}
			break;
		case ARMV7_ADC:
		case ARMV7_ADD:
		case ARMV7_AND:
		case ARMV7_ASR:
		case ARMV7_BIC:
		case ARMV7_EOR:
		case ARMV7_LDR:
		case ARMV7_LSL:
		case ARMV7_LSR:
		case ARMV7_MOV:
		case ARMV7_MVN:
		case ARMV7_ORR:
		case ARMV7_ROR:
		case ARMV7_RRX:
		case ARMV7_RSB:
		case ARMV7_RSC:
		case ARMV7_SUB:
		case ARMV7_SBC:
			if (instr.operands[0].cls == REG && instr.operands[0].reg == REG_PC)
			{
				result.archTransitionByTargetAddr = true;
				result.AddBranch(UnresolvedBranch);
				if (!UNCONDITIONAL(instr.cond))
					result.AddBranch(FalseBranch, addr + 4, this);
			}
			break;
		case ARMV7_MOVW:
		case ARMV7_MOVT:
		case ARMV7_LDRT:
		case ARMV7_LDRH:
		case ARMV7_LDRHT:
		case ARMV7_LDRB:
		case ARMV7_LDRBT:
		case ARMV7_LDRSH:
		case ARMV7_LDRSHT:
		case ARMV7_LDRSB:
		case ARMV7_LDRSBT:
		case ARMV7_LDRD:
		case ARMV7_ADR:
		case ARMV7_UBFX:
		case ARMV7_UXTAB:
		case ARMV7_UXTB:
		case ARMV7_UXTH:
		case ARMV7_MUL:
		case ARMV7_SDIV:
		case ARMV7_UDIV:
		case ARMV7_SBFX:
		case ARMV7_SXTB:
		case ARMV7_SXTH:
		case ARMV7_BFC:
		case ARMV7_BFI:
		case ARMV7_CLZ:
			if (instr.operands[0].cls == REG && instr.operands[0].reg == REG_PC)
				result.AddBranch(UnresolvedBranch);
			break;
		case ARMV7_SVC:
			if (instr.operands[0].cls == IMM && instr.operands[0].imm == 0)
				result.AddBranch(SystemCall);
			break;
		default:
			break;
		}
	}

	uint32_t tokenize_shift(const InstructionOperand& op, vector<InstructionTextToken>& result)
	{
		char operand[64] = {0};
		if (op.shift != SHIFT_NONE)
		{
			const char* shiftStr = get_shift(op.shift);
			if (shiftStr == NULL)
				return FAILED_TO_DISASSEMBLE_OPERAND;

			result.emplace_back(TextToken, ", ");
			result.emplace_back(TextToken, shiftStr);
			snprintf(operand, sizeof(operand), "%#x", (uint32_t)op.imm);
			result.emplace_back(TextToken, " #");
			result.emplace_back(IntegerToken, operand, op.imm);
		}
		return DISASM_SUCCESS;
	}

	void tokenize_shifted_immediate(const InstructionOperand& op,  vector<InstructionTextToken>& result)
	{
		char operand[64] = {0};
		const char* sign = "";
		switch (op.cls)
		{
			case FIMM16:
			case FIMM32:
				{
				snprintf(operand, sizeof(operand), "%f", op.immf);
				result.emplace_back(TextToken, "#");
				result.emplace_back(FloatingPointToken, operand);
				break;
				}
			case FIMM64:
				{
				snprintf(operand, sizeof(operand), "%e", op.immd);
				result.emplace_back(TextToken, "#");
				result.emplace_back(FloatingPointToken, operand);
				break;
				}
			case IMM:
				snprintf(operand, sizeof(operand), "%s%#x", sign, (uint32_t)op.imm);
				result.emplace_back(TextToken, "#");
				result.emplace_back(IntegerToken, operand, op.imm);
				break;
			case IMM64:
				snprintf(operand, sizeof(operand), "%s%#" PRIx64, sign, op.imm64);
				result.emplace_back(TextToken, "#");
				result.emplace_back(IntegerToken, operand, op.imm64);
				break;
			case LABEL:
				snprintf(operand, sizeof(operand), "%#x", op.imm);
				result.emplace_back(PossibleAddressToken, operand, op.imm);
				break;
			default:
				return;
		}

		tokenize_shift(op, result);
	}

	uint32_t tokenize_shifted_register(
		const InstructionOperand& op,
		vector<InstructionTextToken>& result)
	{
		const char* reg = NULL;
		reg = GetRegisterName((enum Register)op.reg).c_str();
		if (reg == NULL)
			return FAILED_TO_DISASSEMBLE_REGISTER;


		result.emplace_back(RegisterToken, reg);
		tokenize_shift(op, result);
		return DISASM_SUCCESS;
	}


	bool GetCoalescedLowLevelIL(const uint8_t* data, uint64_t addr, size_t& len, LowLevelILFunction& il, Instruction& instr)
	{
		size_t remaining = len / 4;
		if (remaining > COALESCE_MAX_INSTRS)
			remaining = COALESCE_MAX_INSTRS;

		Condition cond = instr.cond;

		Instruction coalesced[COALESCE_MAX_INSTRS];
		bool liftInstruction[COALESCE_MAX_INSTRS];
		size_t disassembled = 1;

		coalesced[0] = instr;
		liftInstruction[0] = true;

		auto setsFlags = [](const Instruction& instr)
		{
			if (instr.setsFlags)
				return true;

			switch(instr.operation)
			{
				case ARMV7_CMP:
				case ARMV7_CMN:
				case ARMV7_TST:
					return true;

				case ARMV7_BL:
				case ARMV7_BLX:
					return true;

				default:
					return false;
			}
		};

		for (bool condValid[2] = {true, true}; (disassembled < remaining) && (condValid[0] || condValid[1]); disassembled++)
		{
			size_t consumed = disassembled * 4;
			auto& newInstr = coalesced[disassembled];

			if (!Disassemble(data + consumed, addr + consumed, len - consumed, newInstr))
				break;
			if (UNCONDITIONAL(newInstr.cond))
				break;
			if (!IsRelatedCondition(newInstr.cond, cond))
				break;

			liftInstruction[disassembled] = condValid[newInstr.cond != cond];
			if (!CanCoalesceAfterInstruction(newInstr))
				condValid[newInstr.cond != cond] = false;

			if (setsFlags(instr))
			{
				condValid[0] = true;
				condValid[1] = true;
			}
		}

		if (disassembled == 1)
		{
			len = 4;
			return GetLowLevelILForArmInstruction(this, addr, il, instr, GetAddressSize());
		}


		LowLevelILLabel doneLabel;
		LowLevelILLabel condLabels[2];

		BNLowLevelILLabel* doneLabelExisting = il.GetLabelForAddress(this, addr + (disassembled * 4));
		BNLowLevelILLabel* doneLabelToUse = doneLabelExisting ? doneLabelExisting : &doneLabel;

		for (size_t blockStart = 0; blockStart < disassembled;)
		{
			auto& beginInstr = coalesced[blockStart];
			size_t stateIdx = (beginInstr.cond != cond);

			// determine how many instructions to lift this iteration.
			// generally, this will be set to `disassembled`, but in the
			// event that cmp/cmn/tst instructions are used in the conditional
			// block, they each require re-evaluation of the condition on the side
			// that executed the flag setting instructions
			size_t nextFlagSet = blockStart;
			for (; nextFlagSet < disassembled; nextFlagSet++)
			{
				if (!liftInstruction[nextFlagSet])
					continue; // skip unreachable instructions

				if (setsFlags(coalesced[nextFlagSet]))
					break;
			}

			// figure out where the next block start for the *other* condition in the sequence is
			size_t otherCondNext = blockStart + 1;
			for (; otherCondNext < disassembled ; otherCondNext++)
			{
				if (!liftInstruction[otherCondNext])
					continue; // skip unreachable instructions

				if (coalesced[otherCondNext].cond != beginInstr.cond)
					break;
			}

			bool hasOtherPath = (otherCondNext < disassembled);

			il.SetCurrentAddress(this, addr + (blockStart * 4));
			il.AddInstruction(il.If(GetCondition(il, beginInstr.cond), condLabels[stateIdx],
						hasOtherPath ? condLabels[1 - stateIdx] : *doneLabelToUse));

			auto liftInstructions = [&](Condition liftCond)
			{
				size_t stateIdx = (liftCond != cond);

				il.MarkLabel(condLabels[stateIdx]);
				condLabels[stateIdx] = LowLevelILLabel();

				bool exhausted = true;
				for (size_t i = nextFlagSet + 1; (i < disassembled) && exhausted; i++)
					if (coalesced[i].cond == liftCond)
						exhausted = false;

				size_t liftIdx = blockStart;
				for (; (liftIdx <= nextFlagSet) && (liftIdx < disassembled); liftIdx++)
				{
					if (!liftInstruction[liftIdx])
						continue; // skip unreachable instructions

					auto& curInstr = coalesced[liftIdx];
					if (curInstr.cond != liftCond)
						continue;

					uint64_t instrAddr = addr + (liftIdx * 4);

					il.SetCurrentAddress(this, instrAddr);

					curInstr.cond = COND_NONE;
					GetLowLevelILForArmInstruction(this, instrAddr, il, curInstr, GetAddressSize());
					curInstr.cond = liftCond;
				}

				// CASE 1: last instr was a flag-setting instruction, do nothing, next lifting fixes it
				if ((nextFlagSet < disassembled) && (coalesced[nextFlagSet].cond == liftCond))
					return;

				// CASE 2: no further instructions with this cond exist: goto done
				else if (exhausted)
					il.AddInstruction(il.Goto(*doneLabelToUse));

				// CASE 3: last instr was not a flag-setting instruction, goto next block of this cond (or end)
				else
					il.AddInstruction(il.Goto(condLabels[stateIdx]));
			};

			bool liftAfter = false;
			if (hasOtherPath && (otherCondNext <= nextFlagSet))
			{
				// if we have two different cases to lift, and one of them contains a flag-setting
				// instruction, make sure the condition with the the flag-setting instruction is
				// lifted last. this lets us avoid an unnecessary LLIL_GOTO to the next if statement
				if ((nextFlagSet < disassembled) && (coalesced[nextFlagSet].cond == beginInstr.cond))
					liftInstructions(coalesced[otherCondNext].cond);
				else
					liftAfter = true;
			}

			liftInstructions(beginInstr.cond);

			if (liftAfter)
				liftInstructions(coalesced[otherCondNext].cond);

			blockStart = nextFlagSet + 1;
		}

		if (!doneLabelExisting)
			il.MarkLabel(doneLabel);

		len = disassembled * 4;
		return (doneLabelExisting == nullptr);
	}

public:
	Armv7Architecture(const char* arch, BNEndianness endian)
		: ArmCommonArchitecture(arch, endian)
	{
	}

	virtual size_t GetInstructionAlignment() const override
	{
		return 4;
	}

	virtual size_t GetMaxInstructionLength() const override
	{
		return 4;
	}

	virtual bool GetInstructionInfo(const uint8_t* data, uint64_t addr, size_t maxLen, InstructionInfo& result) override
	{
		if (maxLen < 4)
			return false;

		Instruction instr;
		if (!Disassemble(data, addr, maxLen, instr))
			return false;

		SetInstructionInfoForInstruction(addr, instr, result);
		return true;
	}

	static inline void GetImmToken(const InstructionOperand& op, vector<InstructionTextToken>& result)
	{
		char operand[32];
		snprintf(operand, sizeof(operand), "%#x", (uint32_t)op.imm);
		result.emplace_back(TextToken, " #");
		result.emplace_back(IntegerToken, operand, op.imm);
	}

	static inline void GetSignedImmToken(const InstructionOperand& op, vector<InstructionTextToken>& result)
	{
		char operand[32];
		const char* neg[2] = {"-", ""};
		snprintf(operand, sizeof(operand), "%s%#x", neg[op.flags.add == 1], (uint32_t)op.imm);
		result.emplace_back(TextToken, " #");
		result.emplace_back(IntegerToken, operand, op.imm);
	}

	virtual bool GetInstructionText(const uint8_t* data, uint64_t addr, size_t& len, vector<InstructionTextToken>& result) override
	{
		Instruction instr;
		char padding[9];

		const char* neg[2] = {"-", ""};
		const char* wb[2] = {"", "!"};
		const char* crt[2] = {"", " ^"};
		bool first = true;
		char tmpOperand[256];

		if (!Disassemble(data, addr, len, instr))
			return false;
		len = 4;
		memset(padding, 0x20, sizeof(padding));

		const char* operation = get_full_operation(tmpOperand, sizeof(tmpOperand), &instr);
		if (operation == NULL)
			return false;
		size_t operationLen = strlen(operation);
		if (operationLen < 8)
		{
			padding[8-operationLen] = '\0';
		}
		else
			padding[1] = '\0';

		result.emplace_back(InstructionToken, operation);
		result.emplace_back(TextToken, padding);

		try
		{
		for (size_t i = 0; i < MAX_OPERANDS; i++)
		{
			if (instr.operands[i].cls == NONE)
				return true;

			if (i != 0)
				result.emplace_back(OperandSeparatorToken, ", ");

			switch (instr.operands[i].cls)
			{
			case FIMM16:
			case FIMM32:
			case FIMM64:
			case IMM:
			case IMM64:
			case LABEL:
				tokenize_shifted_immediate(instr.operands[i], result);
				break;
			case REG:
				result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].reg));
				result.emplace_back(TextToken, wb[instr.operands[i].flags.wb]);
				if (instr.operands[i].shift == SHIFT_NONE)
				{
					if (instr.operands[i].flags.hasElements == 1)
					{
						result.emplace_back(TextToken, "[");
						sprintf(tmpOperand, "%d", instr.operands[i].imm);
						result.emplace_back(IntegerToken, tmpOperand, instr.operands[i].imm);
						result.emplace_back(TextToken, "]");
					}
				}
				else if (instr.operands[i].flags.offsetRegUsed == 1)
				{
					//Register shifted by register
					result.emplace_back(TextToken, ", ");
					result.emplace_back(TextToken, get_shift(instr.operands[i].shift));
					result.emplace_back(TextToken, " ");
					result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
				}
				else
				{
					//Register shifted by constant
					result.emplace_back(TextToken, ", ");
					result.emplace_back(TextToken, get_shift(instr.operands[i].shift));
					if (instr.operands[i].shift != SHIFT_RRX)
					{
						result.emplace_back(TextToken, " ");
						GetImmToken(instr.operands[i], result);
					}
				}
				break;
			case REG_LIST:
			case REG_LIST_SINGLE:
			case REG_LIST_DOUBLE:
				{
					result.emplace_back(TextToken, "{");
					first = true;
					uint32_t base = 0;
					if (instr.operands[i].cls == REG_LIST_SINGLE)
						base = REG_S0;
					else if (instr.operands[i].cls == REG_LIST_DOUBLE)
						base = REG_D0;

					for (int32_t j = 0; j < 16; j++)
					{
						if (((instr.operands[i].reg >> j) & 1) == 1)
						{
							if (!first)
								result.emplace_back(TextToken, ", ");
							result.emplace_back(RegisterToken, GetRegisterName((enum Register)(j + base)));
							first = false;
						}
					}
					result.emplace_back(TextToken, "}");
					result.emplace_back(TextToken, crt[instr.operands[i].flags.wb]);
				}
				break;
			case REG_SPEC:
				result.emplace_back(TextToken, get_spec_register_name(instr.operands[i].regs));
				break;
			case REG_BANKED:
				result.emplace_back(TextToken, get_banked_register_name(instr.operands[i].regb));
				break;
			case REG_COPROCP:
				result.emplace_back(TextToken, get_coproc_register_p_name(instr.operands[i].regp));
				break;
			case REG_COPROCC:
				result.emplace_back(TextToken, get_coproc_register_c_name(instr.operands[i].regc));
				break;
			case IFLAGS:
				result.emplace_back(TextToken, get_iflag(instr.operands[i].iflag));
				break;
			case ENDIAN_SPEC:
				result.emplace_back(TextToken, get_endian(instr.operands[i].endian));
				break;
			case DSB_OPTION:
				result.emplace_back(TextToken, get_dsb_option(instr.operands[i].dsbOpt));
				break;
			case MEM_ALIGNED:
				result.emplace_back(BeginMemoryOperandToken, "[");
				result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].reg));
				if (instr.operands[i].imm != 0)
				{
					result.emplace_back(TextToken, ":0x");
					sprintf(tmpOperand, ":%#x", instr.operands[i].imm);
					result.emplace_back(IntegerToken, tmpOperand, instr.operands[i].imm);
				}
				result.emplace_back(EndMemoryOperandToken, "]");
				result.emplace_back(TextToken, wb[instr.operands[i].flags.wb]);
				break;
			case MEM_OPTION:
				result.emplace_back(BeginMemoryOperandToken, "[");
				result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].reg));
				result.emplace_back(EndMemoryOperandToken, "], {");
				GetImmToken(instr.operands[i], result);
				result.emplace_back(TextToken, "}");
				break;
			case MEM_PRE_IDX:
				result.emplace_back(BeginMemoryOperandToken, "[");
				if (instr.operands[i].flags.offsetRegUsed == 1)
				{
					result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].reg));
					result.emplace_back(TextToken, ", ");
					result.emplace_back(TextToken, neg[instr.operands[i].flags.add == 1]);
					if (instr.operands[i].imm == 0)
						result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
					else if (instr.operands[i].shift == SHIFT_RRX)
					{
						result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
						result.emplace_back(TextToken, ", ");
						result.emplace_back(TextToken, get_shift(instr.operands[i].shift));
					}
					else
					{
						result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
						result.emplace_back(TextToken, ", ");
						result.emplace_back(TextToken, get_shift(instr.operands[i].shift));
						result.emplace_back(TextToken, " ");
						GetImmToken(instr.operands[i], result);
					}
				}
				else
				{
					result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].reg));
					result.emplace_back(TextToken, ", ");
					GetSignedImmToken(instr.operands[i], result);
				}
				result.emplace_back(EndMemoryOperandToken, "]!");
				break;
				break;
			case MEM_POST_IDX:
				result.emplace_back(BeginMemoryOperandToken, "[");
				result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].reg));
				result.emplace_back(EndMemoryOperandToken, "], ");
				if (instr.operands[i].flags.offsetRegUsed == 1)
				{
					result.emplace_back(TextToken, neg[instr.operands[i].flags.add == 1]);
					if (instr.operands[i].imm == 0)
						result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
					else if (instr.operands[i].shift == SHIFT_RRX)
					{
						result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
						result.emplace_back(TextToken, ", ");
						result.emplace_back(TextToken, get_shift(instr.operands[i].shift));
					}
					else
					{
						result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
						result.emplace_back(TextToken, ", ");
						result.emplace_back(TextToken, get_shift(instr.operands[i].shift));
						result.emplace_back(TextToken, " ");
						GetImmToken(instr.operands[i], result);
					}
				}
				else
				{
					GetSignedImmToken(instr.operands[i], result);
				}
				break;
			case MEM_IMM:
				result.emplace_back(BeginMemoryOperandToken, "[");
				result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].reg));
				switch (instr.operands[i].shift)
				{
					case SHIFT_NONE:
						if (instr.operands[i].flags.offsetRegUsed == 1)
						{
							result.emplace_back(TextToken, ", ");
							result.emplace_back(TextToken, neg[instr.operands[i].flags.add == 1]);
							result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
						}
						else if (instr.operands[i].imm != 0)// || instr.operands[i].flags.add == 0)
						{
							result.emplace_back(TextToken, ", ");
							GetSignedImmToken(instr.operands[i], result);
						}
						break;
					case SHIFT_RRX:
						result.emplace_back(TextToken, ", ");
						result.emplace_back(TextToken, neg[instr.operands[i].flags.add == 1]);
						result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
						result.emplace_back(TextToken, ", ");
						result.emplace_back(TextToken, get_shift(instr.operands[i].shift));
						break;
					default:
						result.emplace_back(TextToken, ", ");
						result.emplace_back(TextToken, neg[instr.operands[i].flags.add == 1]);
						result.emplace_back(RegisterToken, GetRegisterName(instr.operands[i].offset));
						result.emplace_back(TextToken, ", ");
						result.emplace_back(TextToken, get_shift(instr.operands[i].shift));
						result.emplace_back(TextToken, " ");
						GetImmToken(instr.operands[i], result);
				}
				result.emplace_back(EndMemoryOperandToken, "]");
				break;
			default:
				LogError("operandClass %d\n", instr.operands[i].cls);
				return false;
			}
		}
		}
		catch (exception&)
		{
			LogWarn("Failed to disassemble instruction with encoding: %" PRIx32 "\n", *(uint32_t*)data);
		}
		return true;
	}


	virtual string GetIntrinsicName(uint32_t intrinsic) override
	{
		switch (intrinsic)
		{
		case ARMV7_INTRIN_COPROC_GETONEWORD:
			return "Coproc_GetOneWord";
		case ARMV7_INTRIN_COPROC_GETTWOWORDS:
			return "Coproc_GetTwoWords";
		case ARMV7_INTRIN_COPROC_SENDONEWORD:
			return "Coproc_SendOneWord";
		case ARMV7_INTRIN_COPROC_SENDTWOWORDS:
			return "Coproc_SendTwoWords";
		case ARMV7_INTRIN_EXCLUSIVE_MONITORS_PASS:
			return "ExclusiveMonitorsPass";
		case ARMV7_INTRIN_SET_EXCLUSIVE_MONITORS:
			return "SetExclusiveMonitors";
		default:
			return "";
		}
	}

	virtual vector<uint32_t> GetAllIntrinsics() override
	{
		return vector<uint32_t> {
				ARMV7_INTRIN_COPROC_GETONEWORD,
				ARMV7_INTRIN_COPROC_GETTWOWORDS,
				ARMV7_INTRIN_COPROC_SENDONEWORD,
				ARMV7_INTRIN_COPROC_SENDTWOWORDS,
				ARMV7_INTRIN_EXCLUSIVE_MONITORS_PASS,
				ARMV7_INTRIN_SET_EXCLUSIVE_MONITORS,
		};
	}

	virtual vector<NameAndType> GetIntrinsicInputs(uint32_t intrinsic) override
	{
		switch (intrinsic)
		{
		case ARMV7_INTRIN_COPROC_GETONEWORD:
			return {
				NameAndType("cp", Type::IntegerType(1, false)),
				NameAndType(Type::IntegerType(1, false)),
				NameAndType("n", Type::IntegerType(1, false)),
				NameAndType("m", Type::IntegerType(1, false)),
				NameAndType(Type::IntegerType(1, false)),
			};
		case ARMV7_INTRIN_COPROC_GETTWOWORDS:
			return {
				NameAndType("cp", Type::IntegerType(1, false)),
				NameAndType(Type::IntegerType(1, false)),
				NameAndType("m", Type::IntegerType(1, false)),
			};
		case ARMV7_INTRIN_COPROC_SENDONEWORD:
			return {
				NameAndType(Type::IntegerType(4, false)),
				NameAndType("cp", Type::IntegerType(1, false)),
				NameAndType(Type::IntegerType(1, false)),
				NameAndType("n", Type::IntegerType(1, false)),
				NameAndType("m", Type::IntegerType(1, false)),
				NameAndType(Type::IntegerType(1, false)),
			};
		case ARMV7_INTRIN_COPROC_SENDTWOWORDS:
			return {
				NameAndType(Type::IntegerType(4, false)),
				NameAndType(Type::IntegerType(4, false)),
				NameAndType("cp", Type::IntegerType(1, false)),
				NameAndType(Type::IntegerType(1, false)),
				NameAndType("m", Type::IntegerType(1, false)),
			};
		case ARMV7_INTRIN_EXCLUSIVE_MONITORS_PASS:
		case ARMV7_INTRIN_SET_EXCLUSIVE_MONITORS:
			return {
				NameAndType("address", Type::PointerType(4, Confidence(Type::VoidType(), 0), Confidence(false), Confidence(false), PointerReferenceType)),
				NameAndType("size", Type::IntegerType(1, false)),
			};
		default:
			return vector<NameAndType>();
		}
	}

	virtual vector<Confidence<Ref<Type>>> GetIntrinsicOutputs(uint32_t intrinsic) override
	{
		switch (intrinsic)
		{
		case ARMV7_INTRIN_COPROC_GETONEWORD:
			return { Type::IntegerType(4, false) };
		case ARMV7_INTRIN_COPROC_GETTWOWORDS:
			return { Type::IntegerType(4, false), Type::IntegerType(4, false) };
		case ARMV7_INTRIN_EXCLUSIVE_MONITORS_PASS:
			return { Type::BoolType() };
		default:
			return vector<Confidence<Ref<Type>>>();
		}
	}

	virtual bool IsNeverBranchPatchAvailable(const uint8_t* data, uint64_t addr, size_t len) override
	{
		Instruction instr;
		if (!Disassemble(data, addr, len, instr))
			return false;

		return (instr.operation == ARMV7_B && CONDITIONAL(instr.cond));
	}

	virtual bool IsAlwaysBranchPatchAvailable(const uint8_t* data, uint64_t addr, size_t len) override
	{
		Instruction instr;
		if (!Disassemble(data, addr, len, instr))
			return false;

		return (instr.operation == ARMV7_B && CONDITIONAL(instr.cond));
	}

	virtual bool IsInvertBranchPatchAvailable(const uint8_t* data, uint64_t addr, size_t len) override
	{
		Instruction instr;
		if (!Disassemble(data, addr, len, instr))
			return false;

		return (instr.operation == ARMV7_B && CONDITIONAL(instr.cond));
	}

	virtual bool ConvertToNop(uint8_t* data, uint64_t, size_t len) override
	{
		uint32_t nop =  0xe1a00000;
		if (len < sizeof(nop))
			return false;
		for (size_t i = 0; i < len/sizeof(nop); i++)
			((uint32_t*)data)[i] = nop;
		return true;
	}

	virtual bool AlwaysBranch(uint8_t* data, uint64_t addr, size_t len) override
	{
		(void)addr;
		if (len < 4)
			return false;

		uint32_t *value = (uint32_t*)data;
		*value = (*value & 0x0fffffff) | (COND_NONE << 28);
		return true;
	}

	virtual bool InvertBranch(uint8_t* data, uint64_t addr, size_t len) override
	{
		(void)addr;
		if (len < sizeof(uint32_t))
			return false;
		uint32_t *value = (uint32_t*)data;
		Condition cond = COND_NONE;
		switch (*value >> 28)
		{
			case COND_EQ: cond = COND_NE; break;
			case COND_NE: cond = COND_EQ; break;
			case COND_CS: cond = COND_CC; break;
			case COND_CC: cond = COND_CS; break;
			case COND_MI: cond = COND_PL; break;
			case COND_PL: cond = COND_MI; break;
			case COND_VS: cond = COND_VC; break;
			case COND_VC: cond = COND_VS; break;
			case COND_HI: cond = COND_LS; break;
			case COND_LS: cond = COND_HI; break;
			case COND_GE: cond = COND_LT; break;
			case COND_LT: cond = COND_GE; break;
			case COND_GT: cond = COND_LE; break;
			case COND_LE: cond = COND_GT; break;
		}
		*value = (*value & 0x0fffffff) | (cond << 28);
		return true;
	}

	virtual bool IsSkipAndReturnZeroPatchAvailable(const uint8_t* data, uint64_t addr, size_t len) override
	{
		Instruction instr;
		if (!Disassemble(data, addr, len, instr))
			return false;

		return (instr.operation == ARMV7_BL) || (instr.operation == ARMV7_BLX);
	}

	virtual bool IsSkipAndReturnValuePatchAvailable(const uint8_t* data, uint64_t addr, size_t len) override
	{
		Instruction instr;
		if (!Disassemble(data, addr, len, instr))
			return false;

		return (instr.operation == ARMV7_BL) || (instr.operation == ARMV7_BLX);
	}

	virtual bool SkipAndReturnValue(uint8_t* data, uint64_t addr, size_t len, uint64_t value) override
	{
		(void)addr;
		//Return value is put in R0. The largest value that we can put into a single integer is 12 bits
		if (value > 0xfff || len > 4)
			return false;

		uint32_t movValueR0 = 0xe3a00000;
		uint32_t *inst = (uint32_t*)data;
		*inst = movValueR0 | (value & 0xfff);
		return true;
	}

	virtual bool GetInstructionLowLevelIL(const uint8_t* data, uint64_t addr, size_t& len, LowLevelILFunction& il) override
	{
		Instruction instr;
		if (!Disassemble(data, addr, len, instr))
		{
			il.AddInstruction(il.Undefined());
			return false;
		}

		if (!UNCONDITIONAL(instr.cond))
			return GetCoalescedLowLevelIL(data, addr, len, il, instr);

		len = 4;
		return GetLowLevelILForArmInstruction(this, addr, il, instr, GetAddressSize());
	}
};

ArmCommonArchitecture::ArmCommonArchitecture(const char* name, BNEndianness endian): Architecture(name), m_endian(endian)
{
}

void ArmCommonArchitecture::SetArmAndThumbArchitectures(Architecture* arm, Architecture* thumb)
{
	m_armArch = arm;
	m_thumbArch = thumb;
}

size_t ArmCommonArchitecture::GetAddressSize() const
{
	return 4;
}

BNEndianness ArmCommonArchitecture::GetEndianness() const
{
	return m_endian;
}

Ref<Architecture> ArmCommonArchitecture::GetAssociatedArchitectureByAddress(uint64_t& addr)
{
	if (addr & 1)
	{
		addr &= ~1LL;
		return m_thumbArch;
	}
	return m_armArch;
}

string ArmCommonArchitecture::GetFlagName(uint32_t flag)
{
	char result[32];
	switch (flag)
	{
	case IL_FLAG_N:
		return "n";
	case IL_FLAG_Z:
		return "z";
	case IL_FLAG_C:
		return "c";
	case IL_FLAG_V:
		return "v";
	case IL_FLAG_Q:
		return "q";
	default:
		sprintf(result, "flag%" PRIu32, flag);
		return result;
	}
}

string ArmCommonArchitecture::GetFlagWriteTypeName(uint32_t flags)
{
	switch (flags)
	{
		case IL_FLAGWRITE_ALL: return "*";
		case IL_FLAGWRITE_NZ: return "nz";
		default:
			return "";
	}
}

BNFlagRole ArmCommonArchitecture::GetFlagRole(uint32_t flag, uint32_t)
{
	switch (flag)
	{
	case IL_FLAG_N:
		return NegativeSignFlagRole;
	case IL_FLAG_Z:
		return ZeroFlagRole;
	case IL_FLAG_C:
		return CarryFlagRole;
	case IL_FLAG_V:
		return OverflowFlagRole;
	default:
		return SpecialFlagRole;
	}
}

vector<uint32_t> ArmCommonArchitecture::GetFlagsWrittenByFlagWriteType(uint32_t flags)
{
	switch (flags)
	{
	case IL_FLAGWRITE_ALL:
		return vector<uint32_t> { IL_FLAG_N, IL_FLAG_Z, IL_FLAG_C, IL_FLAG_V };
	case IL_FLAGWRITE_NZ:
		return vector<uint32_t> { IL_FLAG_N, IL_FLAG_Z };
	default:
		return vector<uint32_t> {};
	}
}

vector<uint32_t> ArmCommonArchitecture::GetFlagsRequiredForFlagCondition(BNLowLevelILFlagCondition cond, uint32_t)
{
	switch (cond)
	{
	case LLFC_E:
	case LLFC_NE:
		return vector<uint32_t>{ IL_FLAG_Z };
	case LLFC_SLT:
	case LLFC_SGE:
		return vector<uint32_t>{ IL_FLAG_N, IL_FLAG_V };
	case LLFC_ULT:
	case LLFC_UGE:
		return vector<uint32_t>{ IL_FLAG_C };
	case LLFC_SLE:
	case LLFC_SGT:
		return vector<uint32_t>{ IL_FLAG_Z, IL_FLAG_N, IL_FLAG_V };
	case LLFC_ULE:
	case LLFC_UGT:
		return vector<uint32_t>{ IL_FLAG_C, IL_FLAG_Z };
	case LLFC_NEG:
	case LLFC_POS:
		return vector<uint32_t>{ IL_FLAG_N };
	case LLFC_O:
	case LLFC_NO:
		return vector<uint32_t>{ IL_FLAG_V };
	default:
		return vector<uint32_t>();
	}
}

size_t ArmCommonArchitecture::GetFlagWriteLowLevelIL(BNLowLevelILOperation op, size_t size, uint32_t flagWriteType,
		uint32_t flag, BNRegisterOrConstant* operands, size_t operandCount, LowLevelILFunction& il)
{
	switch (op)
	{
	case LLIL_SBB:
		switch (flag)
		{
		case IL_FLAG_C:
			// Copied from arm64
			// r u< a || (r == a && flag_c)
			return il.Or(0,
					il.CompareUnsignedLessThan(size,
						il.GetExprForRegisterOrConstantOperation(op, size, operands, operandCount),
						il.GetExprForRegisterOrConstant(operands[0], size)),
					il.And(0,
						il.CompareEqual(size,
							il.GetExprForRegisterOrConstantOperation(op, size, operands, operandCount),
							il.GetExprForRegisterOrConstant(operands[0], size)),
						il.Flag(IL_FLAG_C)));
		case IL_FLAG_V:
			return il.CompareEqual(0,
					il.CompareSignedLessThan(size,
						il.GetExprForRegisterOrConstantOperation(op, size, operands, operandCount),
						il.GetExprForRegisterOrConstant(operands[0], size)),
					il.CompareEqual(size,
						il.GetExprForRegisterOrConstant(operands[0], size),
						il.Const(size, 0)));
		}
		break;
	default:
		break;
	}

	BNFlagRole role = GetFlagRole(flag, GetSemanticClassForFlagWriteType(flagWriteType));
	return GetDefaultFlagWriteLowLevelIL(op, size, role, operands, operandCount, il);
}

string ArmCommonArchitecture::GetRegisterName(uint32_t reg)
{
	if (reg >= REG_R0 && reg <= REG_Q15)
	{
		return get_register_name((enum Register)reg);
	}

	if (reg == FAKEREG_SYSCALL_INFO)
	{
		return "syscall_info";
	}

	LogError("Unknown Register: %x - Please report this as a bug.\n", reg);
	return "unknown";
}

vector<uint32_t> ArmCommonArchitecture::GetFullWidthRegisters()
{
	return vector<uint32_t>{
		REG_R0,   REG_R1,   REG_R2,   REG_R3,   REG_R4,   REG_R5,   REG_R6,   REG_R7,
		REG_R8,   REG_R9,   REG_R10,  REG_R11,  REG_R12,  REG_R13,  REG_R14,  REG_R15,
		REG_Q0,   REG_Q1,   REG_Q2,   REG_Q3,   REG_Q4,   REG_Q5,   REG_Q6,   REG_Q7,
		REG_Q8,   REG_Q9,   REG_Q10,  REG_Q11,  REG_Q12,  REG_Q13,  REG_Q14,  REG_Q15,
	};
}

vector<uint32_t> ArmCommonArchitecture::GetAllRegisters()
{
	return vector<uint32_t>{
		REG_R0,   REG_R1,   REG_R2,   REG_R3,   REG_R4,   REG_R5,   REG_R6,   REG_R7,
		REG_R8,   REG_R9,   REG_R10,  REG_R11,  REG_R12,  REG_R13,  REG_R14,  REG_R15,
		REG_S0,   REG_S1,   REG_S2,   REG_S3,   REG_S4,   REG_S5,   REG_S6,   REG_S7,
		REG_S8,   REG_S9,   REG_S10,  REG_S11,  REG_S12,  REG_S13,  REG_S14,  REG_S15,
		REG_S16,  REG_S17,  REG_S18,  REG_S19,  REG_S20,  REG_S21,  REG_S22,  REG_S23,
		REG_S24,  REG_S25,  REG_S26,  REG_S27,  REG_S28,  REG_S29,  REG_S30,  REG_S31,
		REG_D0,   REG_D1,   REG_D2,   REG_D3,   REG_D4,   REG_D5,   REG_D6,   REG_D7,
		REG_D8,   REG_D9,   REG_D10,  REG_D11,  REG_D12,  REG_D13,  REG_D14,  REG_D15,
		REG_D16,  REG_D17,  REG_D18,  REG_D19,  REG_D20,  REG_D21,  REG_D22,  REG_D23,
		REG_D24,  REG_D25,  REG_D26,  REG_D27,  REG_D28,  REG_D29,  REG_D30,  REG_D31,
		REG_Q0,   REG_Q1,   REG_Q2,   REG_Q3,   REG_Q4,   REG_Q5,   REG_Q6,   REG_Q7,
		REG_Q8,   REG_Q9,   REG_Q10,  REG_Q11,  REG_Q12,  REG_Q13,  REG_Q14,  REG_Q15,
		/* fake registers */
		FAKEREG_SYSCALL_INFO
	};
}

vector<uint32_t> ArmCommonArchitecture::GetAllFlags()
{
	return vector<uint32_t>{
		IL_FLAG_N, IL_FLAG_Z, IL_FLAG_C, IL_FLAG_V, IL_FLAG_Q
	};
}

vector<uint32_t> ArmCommonArchitecture::GetAllFlagWriteTypes()
{
	return vector<uint32_t>{
		IL_FLAGWRITE_ALL,
		IL_FLAGWRITE_NZ
	};
}

BNRegisterInfo ArmCommonArchitecture::GetRegisterInfo(uint32_t reg)
{
	switch (reg)
	{
		case REG_R0:
		case REG_R1:
		case REG_R2:
		case REG_R3:
		case REG_R4:
		case REG_R5:
		case REG_R6:
		case REG_R7:
		case REG_R8:
		case REG_R9:
		case REG_R10:
		case REG_R11:
		case REG_R12:
		case REG_R13:
		case REG_R14:
		case REG_R15:
				return RegisterInfo(reg, 0, 4);
		case REG_S0:
		case REG_S1:
		case REG_S2:
		case REG_S3:
		case REG_S4:
		case REG_S5:
		case REG_S6:
		case REG_S7:
		case REG_S8:
		case REG_S9:
		case REG_S10:
		case REG_S11:
		case REG_S12:
		case REG_S13:
		case REG_S14:
		case REG_S15:
		case REG_S16:
		case REG_S17:
		case REG_S18:
		case REG_S19:
		case REG_S20:
		case REG_S21:
		case REG_S22:
		case REG_S23:
		case REG_S24:
		case REG_S25:
		case REG_S26:
		case REG_S27:
		case REG_S28:
		case REG_S29:
		case REG_S30:
		case REG_S31:
			return RegisterInfo(REG_Q0+((reg-REG_S0)/4), ((reg-REG_S0)%4) * 4, 4);
		case REG_D0:
		case REG_D1:
		case REG_D2:
		case REG_D3:
		case REG_D4:
		case REG_D5:
		case REG_D6:
		case REG_D7:
		case REG_D8:
		case REG_D9:
		case REG_D10:
		case REG_D11:
		case REG_D12:
		case REG_D13:
		case REG_D14:
		case REG_D15:
		case REG_D16:
		case REG_D17:
		case REG_D18:
		case REG_D19:
		case REG_D20:
		case REG_D21:
		case REG_D22:
		case REG_D23:
		case REG_D24:
		case REG_D25:
		case REG_D26:
		case REG_D27:
		case REG_D28:
		case REG_D29:
		case REG_D30:
		case REG_D31:
			return RegisterInfo(REG_Q0+((reg-REG_D0)/2), ((reg-REG_D0)%2) * 8, 8);
		case REG_Q0:
		case REG_Q1:
		case REG_Q2:
		case REG_Q3:
		case REG_Q4:
		case REG_Q5:
		case REG_Q6:
		case REG_Q7:
		case REG_Q8:
		case REG_Q9:
		case REG_Q10:
		case REG_Q11:
		case REG_Q12:
		case REG_Q13:
		case REG_Q14:
		case REG_Q15:
			return RegisterInfo(reg, 0, 16);
		case FAKEREG_SYSCALL_INFO:
			return RegisterInfo(reg, 0, 4);
	}
	return RegisterInfo(0, 0, 0);
}

uint32_t ArmCommonArchitecture::GetStackPointerRegister()
{
	return REG_SP;
}

uint32_t ArmCommonArchitecture::GetLinkRegister()
{
	return REG_LR;
}

bool ArmCommonArchitecture::CanAssemble()
{
	return true;
}

bool ArmCommonArchitecture::Assemble(const string& code, uint64_t addr, DataBuffer& result, string& errors)
{
	(void)addr;

	char *instrBytes=NULL, *err=NULL;
	int instrBytesLen=0, errLen=0;

	int assembleResult;

	string triple = GetAssemblerTriple();
	LogDebug("%s() retrieves and uses triple %s\n", __func__, triple.c_str());

	BNLlvmServicesInit();

	errors.clear();
	assembleResult = BNLlvmServicesAssemble(code.c_str(), LLVM_SVCS_DIALECT_UNSPEC,
	  triple.c_str(), LLVM_SVCS_CM_DEFAULT, LLVM_SVCS_RM_STATIC,
	  &instrBytes, &instrBytesLen, &err, &errLen);

	if(assembleResult || errLen) {
		errors = err;
		BNLlvmServicesAssembleFree(instrBytes, err);
		return false;
	}

	result.Clear();
	result.Append(instrBytes, instrBytesLen);
	BNLlvmServicesAssembleFree(instrBytes, err);
	return true;
}

class ArmCallingConvention: public CallingConvention
{
public:
	ArmCallingConvention(Architecture* arch): CallingConvention(arch, "cdecl")
	{
	}

	virtual vector<uint32_t> GetIntegerArgumentRegisters() override
	{
		return vector<uint32_t>{ REG_R0, REG_R1, REG_R2, REG_R3 };
	}

	virtual vector<uint32_t> GetCallerSavedRegisters() override
	{
		return vector<uint32_t>{ REG_R0, REG_R1, REG_R2, REG_R3, REG_R12, REG_LR };
	}

	virtual vector<uint32_t> GetCalleeSavedRegisters() override
	{
		return vector<uint32_t>{ REG_R4, REG_R5, REG_R6, REG_R7, REG_R8, REG_R10, REG_R11 };
	}

	virtual uint32_t GetIntegerReturnValueRegister() override
	{
		return REG_R0;
	}

	virtual uint32_t GetHighIntegerReturnValueRegister() override
	{
		return REG_R1;
	}
};


class LinuxArmv7SystemCallConvention: public CallingConvention
{
public:
	LinuxArmv7SystemCallConvention(Architecture* arch): CallingConvention(arch, "linux-syscall")
	{
	}

	virtual vector<uint32_t> GetIntegerArgumentRegisters() override
	{
		return vector<uint32_t>{ REG_R7, REG_R0, REG_R1, REG_R2, REG_R3, REG_R4, REG_R5, REG_R6 };
	}

	virtual vector<uint32_t> GetCallerSavedRegisters() override
	{
		return vector<uint32_t>{ REG_R0 };
	}

	virtual vector<uint32_t> GetCalleeSavedRegisters() override
	{
		return vector<uint32_t>{ REG_R4, REG_R5, REG_R6, REG_R7, REG_R8, REG_R10, REG_R11 };
	}

	virtual uint32_t GetIntegerReturnValueRegister() override
	{
		return REG_R0;
	}

	virtual bool IsEligibleForHeuristics() override
	{
		return false;
	}
};


uint32_t bswap32(uint32_t x)
{
	return ((x & 0xff000000) >> 24) |
	       ((x & 0x00ff0000) >> 8) |
	       ((x & 0x0000ff00) << 8) |
	       ((x & 0x000000ff) << 24);
}

class ArmElfRelocationHandler: public RelocationHandler
{
public:
	virtual bool ApplyRelocation(Ref<BinaryView> view, Ref<Architecture> arch, Ref<Relocation> reloc, uint8_t* dest, size_t len) override
	{
		(void)view;
		auto info = reloc->GetInfo();
		if (len < info.size)
			return false;
		uint32_t target = (uint32_t)reloc->GetTarget();
		uint32_t* dest32 = (uint32_t*)dest;

		auto swap = [&arch](uint32_t x) { return (arch->GetEndianness() == LittleEndian)? x : bswap32(x); };
		switch (info.nativeType)
		{
			case R_ARM_COPY:
			case R_ARM_GLOB_DAT:
			case R_ARM_JUMP_SLOT:
			case R_ARM_BASE_PREL:
			case R_ARM_GOT_BREL:
				dest32[0] = swap(target);
				break;
			case R_ARM_RELATIVE:
			case R_ARM_ABS32:
				dest32[0] = swap(swap(dest32[0]) + target);
				break;
			case R_ARM_REL32:
				dest32[0] = swap((uint32_t)((target + (info.implicitAddend ? swap(dest32[0]) : info.addend)) - reloc->GetAddress()));
				break;
			case R_ARM_CALL:
			{
				if (target & 1)
				{
					LogError("Unsupported relocation R_ARM_CALL to thumb target");
					break;
				}
				struct _bl {
					int32_t imm:24;
					uint32_t group1:4;
					uint32_t cond:4;
				};
				_bl* bl = (_bl*) dest32;
				int64_t newTarget = (target + (info.implicitAddend ? ((bl->imm << 2) + 8) : info.addend)) - reloc->GetAddress();
				if ((newTarget - 8) > 0x3ffffff)
				{
					LogError("Unsupported relocation R_ARM_CALL @ 0x%" PRIx64 " with target greater than 0x3ffffff: 0x%" PRIx64, reloc->GetAddress(), newTarget - 8);
					break;
				}
				bl->imm = (newTarget - 8) >> 2;
				break;
			}
			case R_ARM_THM_CALL:
			case R_ARM_THM_JUMP24:
			{
				// TODO: not portable
				#pragma pack(push, 1)
				union _thumb32_bl_hw1 {
					uint16_t word;
					struct {
						uint16_t offHi:10; // 21-12
						uint16_t sign:1; // 31-24
						uint16_t group:5;
					};
				};

				union _thumb32_bl_hw2 {
					uint16_t word;
					struct {
						uint16_t offLo:11; //b11-1
						uint16_t j2:1; //b18
						uint16_t thumb:1;
						uint16_t j1:1; //b19
						uint16_t i2:1; //b22
						uint16_t i1:1; //b23
					};
				};
				#pragma pack(pop)

				_thumb32_bl_hw1* bl_hw1 = (_thumb32_bl_hw1*)dest;
				_thumb32_bl_hw2* bl_hw2 = (_thumb32_bl_hw2*)(dest + 2);
				int32_t curTarget = (bl_hw2->offLo << 1) | (bl_hw1->offHi << 12) | (bl_hw1->sign ? (0xffc << 20) : 0);
				int32_t newTarget = (int32_t)((target + (info.implicitAddend ? curTarget : info.addend)) - reloc->GetAddress());

				bl_hw1->sign = newTarget < 0 ? 1 : 0;
				bl_hw1->offHi = newTarget >> 12;
				bl_hw2->offLo = newTarget >> 1;

				// TODO: I/J bit handling conflicts with at least one Thumb2 supplement
				// bl_hw2->i1 = bl_hw1->sign ^ ((newTarget >> 23) & 1);
				// bl_hw2->i2 = bl_hw1->sign ^ ((newTarget >> 22) & 1);
				// bl_hw2->j1 = (newTarget >> 19) & 1;
				// bl_hw2->j2 = (newTarget >> 18) & 1;
				break;
			}
			case R_ARM_PREL31:
			{
				auto sym = reloc->GetSymbol();
				// if (sym)
				// {
				// 	LogError("%lx sym: %s dest32[0] %lx target: %lx", reloc->GetAddress(), sym->GetFullName().c_str(), dest32[0], target);
				// }
				// else
				// {
				// 	LogError("%lx sym: null dest32[0] %lx target: %lx", reloc->GetAddress(), dest32[0], target);
				// }
				dest32[0] = (info.implicitAddend ? dest32[0] : (uint32_t)info.addend) + (target & ~1) - (uint32_t)reloc->GetAddress();
				break;
			}
			case R_ARM_JUMP24:
			{
				if (target & 1)
				{
					LogError("Unsupported relocation R_ARM_JUMP24 to thumb target");
					break;
				}
				struct _b {
					int32_t imm:24;
					uint32_t group1:4;
					uint32_t cond:4;
				};
				_b* b = (_b*) dest32;
				int64_t newTarget = (target + (info.implicitAddend ? ((b->imm << 2) + 8) : info.addend)) - reloc->GetAddress();
				if ((newTarget - 8) > 0x3ffffff)
				{
					LogError("Unsupported relocation R_ARM_JUMP24 0x%" PRIx64 " with target greater than 0x3ffffff: 0x%" PRIx64, reloc->GetAddress(), newTarget - 8);
					break;
				}
				b->imm = (newTarget - 8) >> 2;
				break;
			}
			case R_ARM_MOVW_ABS_NC:
			{
				struct _mov {
					uint32_t imm12:12;
					uint32_t rd:4;
					uint32_t imm4:4;
					uint32_t group2:8;
					uint32_t cond:4;
				};
				_mov* mov = (_mov*)dest32;
				int64_t newTarget = (target + (info.implicitAddend ? (mov->imm4 << 12 | mov->imm12) : info.addend));
				mov->imm12 = newTarget & 0xfff;
				mov->imm4 = (newTarget >> 12) & 0xf;
				break;
			}
			case R_ARM_MOVT_ABS:
			{
				struct _mov {
					uint32_t imm12:12;
					uint32_t rd:4;
					uint32_t imm4:4;
					uint32_t group2:8;
					uint32_t cond:4;
				};
				_mov* mov = (_mov*)dest32;
				int64_t newTarget = (target + (info.implicitAddend ? (mov->imm4 << 12 | mov->imm12) : info.addend));
				mov->imm12 = (newTarget >> 16) & 0xfff;
				mov->imm4 = (newTarget >> 28) & 0xf;
				break;
			}
			default:
				break;
		}
		return false;
	}

	virtual bool GetRelocationInfo(Ref<BinaryView> view, Ref<Architecture> arch, vector<BNRelocationInfo>& result) override
	{
		(void)view;
		(void)arch;
		set<uint64_t> relocTypes;
		for (auto& reloc: result)
		{
			reloc.type = StandardRelocationType;
			reloc.size = 4;
			reloc.pcRelative = false;
			reloc.dataRelocation = IsELFDataRelocation((ElfArmRelocationType)reloc.nativeType);
			switch (reloc.nativeType)
			{
			case R_ARM_NONE:
				reloc.type = IgnoredRelocation;
				reloc.pcRelative = true;
				break;
			case R_ARM_PREL31:
			case R_ARM_RELATIVE:
				reloc.pcRelative = true;
				break;
			case R_ARM_ABS32:
			case R_ARM_BASE_PREL:
			case R_ARM_GOT_BREL:
				break;
			case R_ARM_CALL:
			case R_ARM_JUMP24:
			case R_ARM_THM_CALL:
			case R_ARM_THM_JUMP24:
				reloc.pcRelative = true;
				break;
			case R_ARM_COPY:
				reloc.type = ELFCopyRelocationType;
				break;
			case R_ARM_GLOB_DAT:
				reloc.type = ELFGlobalRelocationType;
				break;
			case R_ARM_JUMP_SLOT:
				reloc.type = ELFJumpSlotRelocationType;
				break;
			case R_ARM_MOVW_ABS_NC:
			case R_ARM_MOVT_ABS:
				break;
			case R_ARM_REL32:
				reloc.pcRelative = true;
				break;
			case R_ARM_SBREL31:
			case R_ARM_PC24:
			case R_ARM_LDR_PC_G0:
			case R_ARM_ABS16:
			case R_ARM_ABS12:
			case R_ARM_ABS8:
			case R_ARM_SBREL32:
			case R_ARM_BREL_ADJ:
			case R_ARM_TLS_DESC:
			case R_ARM_XPC25:
			case R_ARM_TLS_DTPMOD32:
			case R_ARM_TLS_DTPOFF32:
			case R_ARM_TLS_TPOFF32:
			case R_ARM_GOTOFF32:
			case R_ARM_PLT32:
			case R_ARM_BASE_ABS:
			case R_ARM_ALU_PCREL_7_0:
			case R_ARM_ALU_PCREL_15_8:
			case R_ARM_ALU_PCREL_23_15:
			case R_ARM_LDR_SBREL_11_0_NC:
			case R_ARM_ALU_SBREL_19_12_NC:
			case R_ARM_ALU_SBREL_27_20_CK:
			case R_ARM_TARGET1:
			case R_ARM_V4BX:
			case R_ARM_TARGET2:
			case R_ARM_MOVW_PREL_NC:
			case R_ARM_MOVT_PREL:
			case R_ARM_ABS32_NOI:
			case R_ARM_REL32_NOI:
			case R_ARM_ALU_PC_G0_NC:
			case R_ARM_ALU_PC_G0:
			case R_ARM_ALU_PC_G1_NC:
			case R_ARM_ALU_PC_G1:
			case R_ARM_ALU_PC_G2:
			case R_ARM_LDR_PC_G1:
			case R_ARM_LDR_PC_G2:
			case R_ARM_LDRS_PC_G0:
			case R_ARM_LDRS_PC_G1:
			case R_ARM_LDRS_PC_G2:
			case R_ARM_LDC_PC_G0:
			case R_ARM_LDC_PC_G1:
			case R_ARM_LDC_PC_G2:
			case R_ARM_ALU_SB_G0_NC:
			case R_ARM_ALU_SB_G0:
			case R_ARM_ALU_SB_G1_NC:
			case R_ARM_ALU_SB_G1:
			case R_ARM_ALU_SB_G2:
			case R_ARM_LDR_SB_G0:
			case R_ARM_LDR_SB_G1:
			case R_ARM_LDR_SB_G2:
			case R_ARM_LDRS_SB_G0:
			case R_ARM_LDRS_SB_G1:
			case R_ARM_LDRS_SB_G2:
			case R_ARM_LDC_SB_G0:
			case R_ARM_LDC_SB_G1:
			case R_ARM_LDC_SB_G2:
			case R_ARM_MOVW_BREL_NC:
			case R_ARM_MOVT_BREL:
			case R_ARM_MOVW_BREL:

			case R_ARM_THM_ABS5:
			case R_ARM_THM_PC8:
			case R_ARM_THM_SWI8:
			case R_ARM_THM_XPC22:
			case R_ARM_THM_MOVW_ABS_NC:
			case R_ARM_THM_MOVT_ABS:
			case R_ARM_THM_MOVW_PREL_NC:
			case R_ARM_THM_MOVT_PREL:
			case R_ARM_THM_JUMP19:
			case R_ARM_THM_JUMP6:
			case R_ARM_THM_ALU_PREL_11_0:
			case R_ARM_THM_PC12:
			case R_ARM_THM_MOVW_BREL_NC:
			case R_ARM_THM_MOVT_BREL:
			case R_ARM_THM_MOVW_BREL:
			case R_ARM_THM_JUMP11:
			case R_ARM_THM_JUMP8:
			case R_ARM_THM_TLS_DESCSEQ16:
			case R_ARM_THM_TLS_DESCSEQ32:
			case R_ARM_THM_RPC22:

			case R_ARM_TLS_GOTDESC:
			case R_ARM_TLS_CALL:
			case R_ARM_TLS_DESCSEQ:
			case R_ARM_THM_TLS_CALL:
			case R_ARM_PLT32_ABS:
			case R_ARM_GOT_ABS:
			case R_ARM_GOT_PREL:
			case R_ARM_GOT_BREL12:
			case R_ARM_GOTOFF12:
			case R_ARM_GOTRELAX:
			case R_ARM_GNU_VTENTRY:
			case R_ARM_GNU_VTINHERIT:
			case R_ARM_TLS_GD32:
			case R_ARM_TLS_LDM32:
			case R_ARM_TLS_LDO32:
			case R_ARM_TLS_LE32:
			case R_ARM_TLS_LDO12:
			case R_ARM_TLS_LE12:
			case R_ARM_TLS_IE12GP:
			case R_ARM_ME_TOO:
			case R_ARM_RXPC25:
			case R_ARM_RSBREL32:
			case R_ARM_RREL32:
			case R_ARM_RABS32:
			case R_ARM_RPC24:
			case R_ARM_RBASE:
			default:
				reloc.type = UnhandledRelocation;
				relocTypes.insert(reloc.nativeType);
				break;
			}
		}
		for (auto& reloc : relocTypes)
			LogWarn("Unsupported ELF relocation: %s", GetRelocationString((ElfArmRelocationType)reloc));
		return true;
	}
};


class ArmMachORelocationHandler: public RelocationHandler
{
public:
	virtual bool GetRelocationInfo(Ref<BinaryView> view, Ref<Architecture> arch, vector<BNRelocationInfo>& result) override
	{
		(void)view;
		(void)arch;
		set<uint64_t> relocTypes;
		for (auto& reloc: result)
		{
			reloc.type = UnhandledRelocation;
			relocTypes.insert(reloc.nativeType);
		}
		for (auto& reloc : relocTypes)
			LogWarn("Unsupported Mach-O relocation %s", GetRelocationString((MachoArmRelocationType)reloc));
		return false;
	};
};


class ArmPERelocationHandler: public RelocationHandler
{
public:
	virtual bool GetRelocationInfo(Ref<BinaryView> view, Ref<Architecture> arch, vector<BNRelocationInfo>& result) override
	{
		(void)view;
		(void)arch;
		set<uint64_t> relocTypes;
		for (auto& reloc: result)
		{
			reloc.type = UnhandledRelocation;
			relocTypes.insert(reloc.nativeType);
		}
		for (auto& reloc : relocTypes)
			LogWarn("Unsupported PE relocation %s", GetRelocationString((PeArmRelocationType)reloc));
		return false;
	}
};


static void RegisterArmArchitecture(const char* armName, const char* thumbName, BNEndianness endian)
{
	ArmCommonArchitecture* armv7 = new Armv7Architecture(armName, endian);
	ArmCommonArchitecture* thumb2 = InitThumb2Architecture(thumbName, endian);
	armv7->SetArmAndThumbArchitectures(armv7, thumb2);
	thumb2->SetArmAndThumbArchitectures(armv7, thumb2);

	Architecture::Register(armv7);
	Architecture::Register(thumb2);

	// Register calling convention
	Ref<CallingConvention> conv;
	conv = new ArmCallingConvention(armv7);
	armv7->RegisterCallingConvention(conv);
	armv7->SetDefaultCallingConvention(conv);
	armv7->SetCdeclCallingConvention(conv);
	armv7->SetFastcallCallingConvention(conv);
	armv7->SetStdcallCallingConvention(conv);

	conv = new LinuxArmv7SystemCallConvention(armv7);
	armv7->RegisterCallingConvention(conv);

	conv = new ArmCallingConvention(thumb2);
	thumb2->RegisterCallingConvention(conv);
	thumb2->SetDefaultCallingConvention(conv);
	thumb2->SetCdeclCallingConvention(conv);
	thumb2->SetFastcallCallingConvention(conv);
	thumb2->SetStdcallCallingConvention(conv);

	conv = new LinuxArmv7SystemCallConvention(thumb2);
	thumb2->RegisterCallingConvention(conv);

	// Register the architectures with the binary format parsers so that they know when to use
	// these architectures for disassembling an executable file
	BinaryViewType::RegisterArchitecture("Mach-O", 0xc, endian, armv7);
	BinaryViewType::RegisterArchitecture("ELF", 0x28, endian, armv7);
	BinaryViewType::RegisterArchitecture("PE", 0x1c0, endian, armv7); // ARM
	BinaryViewType::RegisterArchitecture("PE", 0x1c2, endian, armv7); // THUMB
	BinaryViewType::RegisterArchitecture("PE", 0x1c4, endian, armv7); // ARMv7

	armv7->SetBinaryViewTypeConstant("ELF", "R_COPY", 20);
	armv7->SetBinaryViewTypeConstant("ELF", "R_GLOBAL_DATA", 21);
	armv7->SetBinaryViewTypeConstant("ELF", "R_JUMP_SLOT", 22);

	if (endian == LittleEndian)
	{
		armv7->RegisterRelocationHandler("ELF", new ArmElfRelocationHandler());
		armv7->RegisterRelocationHandler("Mach-O", new ArmMachORelocationHandler());
		armv7->RegisterRelocationHandler("PE", new ArmPERelocationHandler());
		thumb2->RegisterRelocationHandler("ELF", new ArmElfRelocationHandler());
	}
	armv7->GetStandalonePlatform()->AddRelatedPlatform(thumb2, thumb2->GetStandalonePlatform());
	thumb2->GetStandalonePlatform()->AddRelatedPlatform(armv7, armv7->GetStandalonePlatform());
}


extern "C"
{
	BN_DECLARE_CORE_ABI_VERSION

#ifdef DEMO_VERSION
	bool ARMv7PluginInit()
#else
	BINARYNINJAPLUGIN bool CorePluginInit()
#endif
	{
		RegisterArmArchitecture("armv7", "thumb2", LittleEndian);
		RegisterArmArchitecture("armv7eb", "thumb2eb", BigEndian);
		return true;
	}
}
