# Copyright (c) 2015-2017 Vector 35 LLC
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

import struct
import traceback
import os

from binaryninja.architecture import Architecture
from binaryninja.lowlevelil import LowLevelILLabel, LLIL_TEMP
from binaryninja.function import RegisterInfo, InstructionInfo, InstructionTextToken
from binaryninja.binaryview import BinaryView
from binaryninja.types import Symbol
from binaryninja.log import log_error
from binaryninja.enums import (BranchType, InstructionTextTokenType,
							LowLevelILOperation, LowLevelILFlagCondition, FlagRole, SegmentFlag, SymbolType)

# 2-3 compatibility
from binaryninja import range


InstructionNames = [
	"brk", "ora", None, None, None, "ora", "asl", None,  # 0x00
	"php", "ora", "asl@", None, None, "ora", "asl", None,  # 0x08
	"bpl", "ora", None, None, None, "ora", "asl", None,  # 0x10
	"clc", "ora", None, None, None, "ora", "asl", None,  # 0x18
	"jsr", "and", None, None, "bit", "and", "rol", None,  # 0x20
	"plp", "and", "rol@", None, "bit", "and", "rol", None,  # 0x28
	"bmi", "and", None, None, None, "and", "rol", None,  # 0x30
	"sec", "and", None, None, None, "and", "rol", None,  # 0x38
	"rti", "eor", None, None, None, "eor", "lsr", None,  # 0x40
	"pha", "eor", "lsr@", None, "jmp", "eor", "lsr", None,  # 0x48
	"bvc", "eor", None, None, None, "eor", "lsr", None,  # 0x50
	"cli", "eor", None, None, None, "eor", "lsr", None,  # 0x58
	"rts", "adc", None, None, None, "adc", "ror", None,  # 0x60
	"pla", "adc", "ror@", None, "jmp", "adc", "ror", None,  # 0x68
	"bvs", "adc", None, None, None, "adc", "ror", None,  # 0x70
	"sei", "adc", None, None, None, "adc", "ror", None,  # 0x78
	None, "sta", None, None, "sty", "sta", "stx", None,  # 0x80
	"dey", None, "txa", None, "sty", "sta", "stx", None,  # 0x88
	"bcc", "sta", None, None, "sty", "sta", "stx", None,  # 0x90
	"tya", "sta", "txs", None, None, "sta", None, None,  # 0x98
	"ldy", "lda", "ldx", None, "ldy", "lda", "ldx", None,  # 0xa0
	"tay", "lda", "tax", None, "ldy", "lda", "ldx", None,  # 0xa8
	"bcs", "lda", None, None, "ldy", "lda", "ldx", None,  # 0xb0
	"clv", "lda", "tsx", None, "ldy", "lda", "ldx", None,  # 0xb8
	"cpy", "cmp", None, None, "cpy", "cmp", "dec", None,  # 0xc0
	"iny", "cmp", "dex", None, "cpy", "cmp", "dec", None,  # 0xc8
	"bne", "cmp", None, None, None, "cmp", "dec", None,  # 0xd0
	"cld", "cmp", None, None, None, "cmp", "dec", None,  # 0xd8
	"cpx", "sbc", None, None, "cpx", "sbc", "inc", None,  # 0xe0
	"inx", "sbc", "nop", None, "cpx", "sbc", "inc", None,  # 0xe8
	"beq", "sbc", None, None, None, "sbc", "inc", None,  # 0xf0
	"sed", "sbc", None, None, None, "sbc", "inc", None  # 0xf8
]

NONE = 0
ABS = 1
ABS_DEST = 2
ABS_X = 3
ABS_X_DEST = 4
ABS_Y = 5
ABS_Y_DEST = 6
ACCUM = 7
ADDR = 8
IMMED = 9
IND = 10
IND_X = 11
IND_X_DEST = 12
IND_Y = 13
IND_Y_DEST = 14
REL = 15
ZERO = 16
ZERO_DEST = 17
ZERO_X = 18
ZERO_X_DEST = 19
ZERO_Y = 20
ZERO_Y_DEST = 21
InstructionOperandTypes = [
	NONE, IND_X, NONE, NONE, NONE, ZERO, ZERO_DEST, NONE,  # 0x00
	NONE, IMMED, ACCUM, NONE, NONE, ABS, ABS_DEST, NONE,  # 0x08
	REL, IND_Y, NONE, NONE, NONE, ZERO_X, ZERO_X_DEST, NONE,  # 0x10
	NONE, ABS_Y, NONE, NONE, NONE, ABS_X, ABS_X_DEST, NONE,  # 0x18
	ADDR, IND_X, NONE, NONE, ZERO, ZERO, ZERO_DEST, NONE,  # 0x20
	NONE, IMMED, ACCUM, NONE, ABS, ABS, ABS_DEST, NONE,  # 0x28
	REL, IND_Y, NONE, NONE, NONE, ZERO_X, ZERO_X_DEST, NONE,  # 0x30
	NONE, ABS_Y, NONE, NONE, NONE, ABS_X, ABS_X_DEST, NONE,  # 0x38
	NONE, IND_X, NONE, NONE, NONE, ZERO, ZERO_DEST, NONE,  # 0x40
	NONE, IMMED, ACCUM, NONE, ADDR, ABS, ABS_DEST, NONE,  # 0x48
	REL, IND_Y, NONE, NONE, NONE, ZERO_X, ZERO_X_DEST, NONE,  # 0x50
	NONE, ABS_Y, NONE, NONE, NONE, ABS_X, ABS_X_DEST, NONE,  # 0x58
	NONE, IND_X, NONE, NONE, NONE, ZERO, ZERO_DEST, NONE,  # 0x60
	NONE, IMMED, ACCUM, NONE, IND, ABS, ABS_DEST, NONE,  # 0x68
	REL, IND_Y, NONE, NONE, NONE, ZERO_X, ZERO_X_DEST, NONE,  # 0x70
	NONE, ABS_Y, NONE, NONE, NONE, ABS_X, ABS_X_DEST, NONE,  # 0x78
	NONE, IND_X_DEST, NONE, NONE, ZERO_DEST, ZERO_DEST, ZERO_DEST, NONE,  # 0x80
	NONE, NONE, NONE, NONE, ABS_DEST, ABS_DEST, ABS_DEST, NONE,  # 0x88
	REL, IND_Y_DEST, NONE, NONE, ZERO_X_DEST, ZERO_X_DEST, ZERO_Y_DEST, NONE,  # 0x90
	NONE, ABS_Y_DEST, NONE, NONE, NONE, ABS_X_DEST, NONE, NONE,  # 0x98
	IMMED, IND_X, IMMED, NONE, ZERO, ZERO, ZERO, NONE,  # 0xa0
	NONE, IMMED, NONE, NONE, ABS, ABS, ABS, NONE,  # 0xa8
	REL, IND_Y, NONE, NONE, ZERO_X, ZERO_X, ZERO_Y, NONE,  # 0xb0
	NONE, ABS_Y, NONE, NONE, ABS_X, ABS_X, ABS_Y, NONE,  # 0xb8
	IMMED, IND_X, NONE, NONE, ZERO, ZERO, ZERO_DEST, NONE,  # 0xc0
	NONE, IMMED, NONE, NONE, ABS, ABS, ABS_DEST, NONE,  # 0xc8
	REL, IND_Y, NONE, NONE, NONE, ZERO_X, ZERO_X_DEST, NONE,  # 0xd0
	NONE, ABS_Y, NONE, NONE, NONE, ABS_X, ABS_X_DEST, NONE,  # 0xd8
	IMMED, IND_X, NONE, NONE, ZERO, ZERO, ZERO_DEST, NONE,  # 0xe0
	NONE, IMMED, NONE, NONE, ABS, ABS, ABS_DEST, NONE,  # 0xe8
	REL, IND_Y, NONE, NONE, NONE, ZERO_X, ZERO_X_DEST, NONE,  # 0xf0
	NONE, ABS_Y, NONE, NONE, NONE, ABS_X, ABS_X_DEST, NONE  # 0xf8
]

OperandLengths = [
	0,  # NONE
	2,  # ABS
	2,  # ABS_DEST
	2,  # ABS_X
	2,  # ABS_X_DEST
	2,  # ABS_Y
	2,  # ABS_Y_DEST
	0,  # ACCUM
	2,  # ADDR
	1,  # IMMED
	2,  # IND
	1,  # IND_X
	1,  # IND_X_DEST
	1,  # IND_Y
	1,  # IND_Y_DEST
	1,  # REL
	1,  # ZERO
	1,  # ZREO_DEST
	1,  # ZERO_X
	1,  # ZERO_X_DEST
	1,  # ZERO_Y
	1   # ZERO_Y_DEST
]

OperandTokens = [
	lambda value: [],  # NONE
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value)],  # ABS
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value)],  # ABS_DEST
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "x")],  # ABS_X
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "x")],  # ABS_X_DEST
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "y")],  # ABS_Y
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "y")],  # ABS_Y_DEST
	lambda value: [InstructionTextToken(InstructionTextTokenType.RegisterToken, "a")],  # ACCUM
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value)],  # ADDR
	lambda value: [InstructionTextToken(InstructionTextTokenType.TextToken, "#"), InstructionTextToken(InstructionTextTokenType.IntegerToken, "$%.2x" % value, value)],  # IMMED
	lambda value: [InstructionTextToken(InstructionTextTokenType.TextToken, "["), InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, "]")],  # IND
	lambda value: [InstructionTextToken(InstructionTextTokenType.TextToken, "["), InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "x"),
		InstructionTextToken(InstructionTextTokenType.TextToken, "]")],  # IND_X
	lambda value: [InstructionTextToken(InstructionTextTokenType.TextToken, "["), InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "x"),
		InstructionTextToken(InstructionTextTokenType.TextToken, "]")],  # IND_X_DEST
	lambda value: [InstructionTextToken(InstructionTextTokenType.TextToken, "["), InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, "], "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "y")],  # IND_Y
	lambda value: [InstructionTextToken(InstructionTextTokenType.TextToken, "["), InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, "], "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "y")],  # IND_Y_DEST
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.4x" % value, value)],  # REL
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value)],  # ZERO
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value)],  # ZERO_DEST
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "x")],  # ZERO_X
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "x")],  # ZERO_X_DEST
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "y")],  # ZERO_Y
	lambda value: [InstructionTextToken(InstructionTextTokenType.PossibleAddressToken, "$%.2x" % value, value),
		InstructionTextToken(InstructionTextTokenType.TextToken, ", "), InstructionTextToken(InstructionTextTokenType.RegisterToken, "y")]  # ZERO_Y_DEST
]


def indirect_load(il, value):
	if (value & 0xff) == 0xff:
		lo_addr = il.const_pointer(2, value)
		hi_addr = il.const_pointer(2, (value & 0xff00) | ((value + 1) & 0xff))
		lo = il.zero_extend(2, il.load(1, lo_addr))
		hi = il.shift_left(2, il.zero_extend(2, il.load(1, hi_addr)), il.const(2, 8))
		return il.or_expr(2, lo, hi)
	return il.load(2, il.const_pointer(2, value))


def load_zero_page_16(il, value):
	if il[value].operation == LowLevelILOperation.LLIL_CONST:
		if il[value].constant == 0xff:
			lo = il.zero_extend(2, il.load(1, il.const_pointer(2, 0xff)))
			hi = il.shift_left(2, il.zero_extend(2, il.load(1, il.const_pointer(2, 0)), il.const(2, 8)))
			return il.or_expr(2, lo, hi)
		return il.load(2, il.const_pointer(2, il[value].constant))
	il.append(il.set_reg(1, LLIL_TEMP(0), value))
	value = il.reg(1, LLIL_TEMP(0))
	lo_addr = value
	hi_addr = il.add(1, value, il.const(1, 1))
	lo = il.zero_extend(2, il.load(1, lo_addr))
	hi = il.shift_left(2, il.zero_extend(2, il.load(1, hi_addr)), il.const(2, 8))
	return il.or_expr(2, lo, hi)


OperandIL = [
	lambda il, value: None,  # NONE
	lambda il, value: il.load(1, il.const_pointer(2, value)),  # ABS
	lambda il, value: il.const(2, value),  # ABS_DEST
	lambda il, value: il.load(1, il.add(2, il.const(2, value), il.zero_extend(2, il.reg(1, "x")))),  # ABS_X
	lambda il, value: il.add(2, il.const(2, value), il.zero_extend(2, il.reg(1, "x"))),  # ABS_X_DEST
	lambda il, value: il.load(1, il.add(2, il.const(2, value), il.zero_extend(2, il.reg(1, "y")))),  # ABS_Y
	lambda il, value: il.add(2, il.const(2, value), il.zero_extend(2, il.reg(1, "y"))),  # ABS_Y_DEST
	lambda il, value: il.reg(1, "a"),  # ACCUM
	lambda il, value: il.const_pointer(2, value),  # ADDR
	lambda il, value: il.const(1, value),  # IMMED
	lambda il, value: indirect_load(il, value),  # IND
	lambda il, value: il.load(1, load_zero_page_16(il, il.add(1, il.const(1, value), il.reg(1, "x")))),  # IND_X
	lambda il, value: load_zero_page_16(il, il.add(1, il.const(1, value), il.reg(1, "x"))),  # IND_X_DEST
	lambda il, value: il.load(1, il.add(2, load_zero_page_16(il, il.const(1, value)), il.reg(1, "y"))),  # IND_Y
	lambda il, value: il.add(2, load_zero_page_16(il, il.const(1, value)), il.reg(1, "y")),  # IND_Y_DEST
	lambda il, value: il.const_pointer(2, value),  # REL
	lambda il, value: il.load(1, il.const_pointer(2, value)),  # ZERO
	lambda il, value: il.const_pointer(2, value),  # ZERO_DEST
	lambda il, value: il.load(1, il.zero_extend(2, il.add(1, il.const(1, value), il.reg(1, "x")))),  # ZERO_X
	lambda il, value: il.zero_extend(2, il.add(1, il.const(1, value), il.reg(1, "x"))),  # ZERO_X_DEST
	lambda il, value: il.load(1, il.zero_extend(2, il.add(1, il.const(1, value), il.reg(1, "y")))),  # ZERO_Y
	lambda il, value: il.zero_extend(2, il.add(1, il.const(1, value), il.reg(1, "y")))  # ZERO_Y_DEST
]


def cond_branch(il, cond, dest):
	t = None
	if il[dest].operation == LowLevelILOperation.LLIL_CONST:
		t = il.get_label_for_address(Architecture['6502'], il[dest].constant)
	if t is None:
		t = LowLevelILLabel()
		indirect = True
	else:
		indirect = False
	f = LowLevelILLabel()
	il.append(il.if_expr(cond, t, f))
	if indirect:
		il.mark_label(t)
		il.append(il.jump(dest))
	il.mark_label(f)
	return None


def jump(il, dest):
	label = None
	if il[dest].operation == LowLevelILOperation.LLIL_CONST:
		label = il.get_label_for_address(Architecture['6502'], il[dest].constant)
	if label is None:
		il.append(il.jump(dest))
	else:
		il.append(il.goto(label))
	return None


def get_p_value(il):
	c = il.flag_bit(1, "c", 0)
	z = il.flag_bit(1, "z", 1)
	i = il.flag_bit(1, "i", 2)
	d = il.flag_bit(1, "d", 3)
	b = il.flag_bit(1, "b", 4)
	v = il.flag_bit(1, "v", 6)
	s = il.flag_bit(1, "s", 7)
	return il.or_expr(1, il.or_expr(1, il.or_expr(1, il.or_expr(1, il.or_expr(1,
	                  il.or_expr(1, c, z), i), d), b), v), s)


def set_p_value(il, value):
	il.append(il.set_reg(1, LLIL_TEMP(0), value))
	il.append(il.set_flag("c", il.test_bit(1, il.reg(1, LLIL_TEMP(0)), il.const(1, 0x01))))
	il.append(il.set_flag("z", il.test_bit(1, il.reg(1, LLIL_TEMP(0)), il.const(1, 0x02))))
	il.append(il.set_flag("i", il.test_bit(1, il.reg(1, LLIL_TEMP(0)), il.const(1, 0x04))))
	il.append(il.set_flag("d", il.test_bit(1, il.reg(1, LLIL_TEMP(0)), il.const(1, 0x08))))
	il.append(il.set_flag("b", il.test_bit(1, il.reg(1, LLIL_TEMP(0)), il.const(1, 0x10))))
	il.append(il.set_flag("v", il.test_bit(1, il.reg(1, LLIL_TEMP(0)), il.const(1, 0x40))))
	il.append(il.set_flag("s", il.test_bit(1, il.reg(1, LLIL_TEMP(0)), il.const(1, 0x80))))
	return None


def rti(il):
	set_p_value(il, il.pop(1))
	return il.ret(il.pop(2))


InstructionIL = {
	"adc": lambda il, operand: il.set_reg(1, "a", il.add_carry(1, il.reg(1, "a"), operand, il.flag("c"), flags = "*")),
	"asl": lambda il, operand: il.store(1, operand, il.shift_left(1, il.load(1, operand), il.const(1, 1), flags = "czs")),
	"asl@": lambda il, operand: il.set_reg(1, "a", il.shift_left(1, operand, il.const(1, 1), flags = "czs")),
	"and": lambda il, operand: il.set_reg(1, "a", il.and_expr(1, il.reg(1, "a"), operand, flags = "zs")),
	"bcc": lambda il, operand: cond_branch(il, il.flag_condition(LowLevelILFlagCondition.LLFC_UGE), operand),
	"bcs": lambda il, operand: cond_branch(il, il.flag_condition(LowLevelILFlagCondition.LLFC_ULT), operand),
	"beq": lambda il, operand: cond_branch(il, il.flag_condition(LowLevelILFlagCondition.LLFC_E), operand),
	"bit": lambda il, operand: il.and_expr(1, il.reg(1, "a"), operand, flags = "czs"),
	"bmi": lambda il, operand: cond_branch(il, il.flag_condition(LowLevelILFlagCondition.LLFC_NEG), operand),
	"bne": lambda il, operand: cond_branch(il, il.flag_condition(LowLevelILFlagCondition.LLFC_NE), operand),
	"bpl": lambda il, operand: cond_branch(il, il.flag_condition(LowLevelILFlagCondition.LLFC_POS), operand),
	"brk": lambda il, operand: il.system_call(),
	"bvc": lambda il, operand: cond_branch(il, il.not_expr(0, il.flag("v")), operand),
	"bvs": lambda il, operand: cond_branch(il, il.flag("v"), operand),
	"clc": lambda il, operand: il.set_flag("c", il.const(0, 0)),
	"cld": lambda il, operand: il.set_flag("d", il.const(0, 0)),
	"cli": lambda il, operand: il.set_flag("i", il.const(0, 0)),
	"clv": lambda il, operand: il.set_flag("v", il.const(0, 0)),
	"cmp": lambda il, operand: il.sub(1, il.reg(1, "a"), operand, flags = "czs"),
	"cpx": lambda il, operand: il.sub(1, il.reg(1, "x"), operand, flags = "czs"),
	"cpy": lambda il, operand: il.sub(1, il.reg(1, "y"), operand, flags = "czs"),
	"dec": lambda il, operand: il.store(1, operand, il.sub(1, il.load(1, operand), il.const(1, 1), flags = "zs")),
	"dex": lambda il, operand: il.set_reg(1, "x", il.sub(1, il.reg(1, "x"), il.const(1, 1), flags = "zs")),
	"dey": lambda il, operand: il.set_reg(1, "y", il.sub(1, il.reg(1, "y"), il.const(1, 1), flags = "zs")),
	"eor": lambda il, operand: il.set_reg(1, "a", il.xor_expr(1, il.reg(1, "a"), operand, flags = "zs")),
	"inc": lambda il, operand: il.store(1, operand, il.add(1, il.load(1, operand), il.const(1, 1), flags = "zs")),
	"inx": lambda il, operand: il.set_reg(1, "x", il.add(1, il.reg(1, "x"), il.const(1, 1), flags = "zs")),
	"iny": lambda il, operand: il.set_reg(1, "y", il.add(1, il.reg(1, "y"), il.const(1, 1), flags = "zs")),
	"jmp": lambda il, operand: jump(il, operand),
	"jsr": lambda il, operand: il.call(operand),
	"lda": lambda il, operand: il.set_reg(1, "a", operand, flags = "zs"),
	"ldx": lambda il, operand: il.set_reg(1, "x", operand, flags = "zs"),
	"ldy": lambda il, operand: il.set_reg(1, "y", operand, flags = "zs"),
	"lsr": lambda il, operand: il.store(1, operand, il.logical_shift_right(1, il.load(1, operand), il.const(1, 1), flags = "czs")),
	"lsr@": lambda il, operand: il.set_reg(1, "a", il.logical_shift_right(1, il.reg(1, "a"), il.const(1, 1), flags = "czs")),
	"nop": lambda il, operand: il.nop(),
	"ora": lambda il, operand: il.set_reg(1, "a", il.or_expr(1, il.reg(1, "a"), operand, flags = "zs")),
	"pha": lambda il, operand: il.push(1, il.reg(1, "a")),
	"php": lambda il, operand: il.push(1, get_p_value(il)),
	"pla": lambda il, operand: il.set_reg(1, "a", il.pop(1), flags = "zs"),
	"plp": lambda il, operand: set_p_value(il, il.pop(1)),
	"rol": lambda il, operand: il.store(1, operand, il.rotate_left_carry(1, il.load(1, operand), il.const(1, 1), il.flag("c"), flags = "czs")),
	"rol@": lambda il, operand: il.set_reg(1, "a", il.rotate_left_carry(1, il.reg(1, "a"), il.const(1, 1), il.flag("c"), flags = "czs")),
	"ror": lambda il, operand: il.store(1, operand, il.rotate_right_carry(1, il.load(1, operand), il.const(1, 1), il.flag("c"), flags = "czs")),
	"ror@": lambda il, operand: il.set_reg(1, "a", il.rotate_right_carry(1, il.reg(1, "a"), il.const(1, 1), il.flag("c"), flags = "czs")),
	"rti": lambda il, operand: rti(il),
	"rts": lambda il, operand: il.ret(il.add(2, il.pop(2), il.const(2, 1))),
	"sbc": lambda il, operand: il.set_reg(1, "a", il.sub_borrow(1, il.reg(1, "a"), operand, il.flag("c"), flags = "*")),
	"sec": lambda il, operand: il.set_flag("c", il.const(0, 1)),
	"sed": lambda il, operand: il.set_flag("d", il.const(0, 1)),
	"sei": lambda il, operand: il.set_flag("i", il.const(0, 1)),
	"sta": lambda il, operand: il.store(1, operand, il.reg(1, "a")),
	"stx": lambda il, operand: il.store(1, operand, il.reg(1, "x")),
	"sty": lambda il, operand: il.store(1, operand, il.reg(1, "y")),
	"tax": lambda il, operand: il.set_reg(1, "x", il.reg(1, "a"), flags = "zs"),
	"tay": lambda il, operand: il.set_reg(1, "y", il.reg(1, "a"), flags = "zs"),
	"tsx": lambda il, operand: il.set_reg(1, "x", il.reg(1, "s"), flags = "zs"),
	"txa": lambda il, operand: il.set_reg(1, "a", il.reg(1, "x"), flags = "zs"),
	"txs": lambda il, operand: il.set_reg(1, "s", il.reg(1, "x")),
	"tya": lambda il, operand: il.set_reg(1, "a", il.reg(1, "y"), flags = "zs")
}


class M6502(Architecture):
	name = "6502"
	address_size = 2
	default_int_size = 1
	instr_alignment = 1
	max_instr_length = 3
	regs = {
		"a": RegisterInfo("a", 1),
		"x": RegisterInfo("x", 1),
		"y": RegisterInfo("y", 1),
		"s": RegisterInfo("s", 1)
	}
	stack_pointer = "s"
	flags = ["c", "z", "i", "d", "b", "v", "s"]
	flag_write_types = ["*", "czs", "zvs", "zs"]
	flag_roles = {
		"c": FlagRole.SpecialFlagRole,  # Not a normal carry flag, subtract result is inverted
		"z": FlagRole.ZeroFlagRole,
		"v": FlagRole.OverflowFlagRole,
		"s": FlagRole.NegativeSignFlagRole
	}
	flags_required_for_flag_condition = {
		LowLevelILFlagCondition.LLFC_UGE: ["c"],
		LowLevelILFlagCondition.LLFC_ULT: ["c"],
		LowLevelILFlagCondition.LLFC_E: ["z"],
		LowLevelILFlagCondition.LLFC_NE: ["z"],
		LowLevelILFlagCondition.LLFC_NEG: ["s"],
		LowLevelILFlagCondition.LLFC_POS: ["s"]
	}
	flags_written_by_flag_write_type = {
		"*": ["c", "z", "v", "s"],
		"czs": ["c", "z", "s"],
		"zvs": ["z", "v", "s"],
		"zs": ["z", "s"]
	}

	def decode_instruction(self, data, addr):
		if len(data) < 1:
			return None, None, None, None
		opcode = ord(data[0])
		instr = InstructionNames[opcode]
		if instr is None:
			return None, None, None, None

		operand = InstructionOperandTypes[opcode]
		length = 1 + OperandLengths[operand]
		if len(data) < length:
			return None, None, None, None

		if OperandLengths[operand] == 0:
			value = None
		elif operand == REL:
			value = (addr + 2 + struct.unpack("b", data[1])[0]) & 0xffff
		elif OperandLengths[operand] == 1:
			value = ord(data[1])
		else:
			value = struct.unpack("<H", data[1:3])[0]

		return instr, operand, length, value

	def get_instruction_info(self, data, addr):
		instr, operand, length, value = self.decode_instruction(data, addr)
		if instr is None:
			return None

		result = InstructionInfo()
		result.length = length
		if instr == "jmp":
			if operand == ADDR:
				result.add_branch(BranchType.UnconditionalBranch, struct.unpack("<H", data[1:3])[0])
			else:
				result.add_branch(BranchType.UnresolvedBranch)
		elif instr == "jsr":
			result.add_branch(BranchType.CallDestination, struct.unpack("<H", data[1:3])[0])
		elif instr in ["rti", "rts"]:
			result.add_branch(BranchType.FunctionReturn)
		if instr in ["bcc", "bcs", "beq", "bmi", "bne", "bpl", "bvc", "bvs"]:
			dest = (addr + 2 + struct.unpack("b", data[1])[0]) & 0xffff
			result.add_branch(BranchType.TrueBranch, dest)
			result.add_branch(BranchType.FalseBranch, addr + 2)
		return result

	def get_instruction_text(self, data, addr):
		instr, operand, length, value = self.decode_instruction(data, addr)
		if instr is None:
			return None

		tokens = []
		tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, "%-7s " % instr.replace("@", "")))
		tokens += OperandTokens[operand](value)
		return tokens, length

	def get_instruction_low_level_il(self, data, addr, il):
		instr, operand, length, value = self.decode_instruction(data, addr)
		if instr is None:
			return None

		operand = OperandIL[operand](il, value)
		instr = InstructionIL[instr](il, operand)
		if isinstance(instr, list):
			for i in instr:
				il.append(i)
		elif instr is not None:
			il.append(instr)

		return length

	def get_flag_write_low_level_il(self, op, size, write_type, flag, operands, il):
		if flag == 'c':
			if (op == LowLevelILOperation.LLIL_SUB) or (op == LowLevelILOperation.LLIL_SBB):
				# Subtraction carry flag is inverted from the commom implementation
				return il.not_expr(0, self.get_default_flag_write_low_level_il(op, size, FlagRole.CarryFlagRole, operands, il))
			# Other operations use a normal carry flag
			return self.get_default_flag_write_low_level_il(op, size, FlagRole.CarryFlagRole, operands, il)
		return Architecture.get_flag_write_low_level_il(self, op, size, write_type, flag, operands, il)

	def is_never_branch_patch_available(self, data, addr):
		if (data[0] == "\x10") or (data[0] == "\x30") or (data[0] == "\x50") or (data[0] == "\x70") or (data[0] == "\x90") or (data[0] == "\xb0") or (data[0] == "\xd0") or (data[0] == "\xf0"):
			return True
		return False

	def is_invert_branch_patch_available(self, data, addr):
		if (data[0] == "\x10") or (data[0] == "\x30") or (data[0] == "\x50") or (data[0] == "\x70") or (data[0] == "\x90") or (data[0] == "\xb0") or (data[0] == "\xd0") or (data[0] == "\xf0"):
			return True
		return False

	def is_always_branch_patch_available(self, data, addr):
		return False

	def is_skip_and_return_zero_patch_available(self, data, addr):
		return (data[0] == "\x20") and (len(data) == 3)

	def is_skip_and_return_value_patch_available(self, data, addr):
		return (data[0] == "\x20") and (len(data) == 3)

	def convert_to_nop(self, data, addr):
		return "\xea" * len(data)

	def never_branch(self, data, addr):
		if (data[0] == "\x10") or (data[0] == "\x30") or (data[0] == "\x50") or (data[0] == "\x70") or (data[0] == "\x90") or (data[0] == "\xb0") or (data[0] == "\xd0") or (data[0] == "\xf0"):
			return "\xea" * len(data)
		return None

	def invert_branch(self, data, addr):
		if (data[0] == "\x10") or (data[0] == "\x30") or (data[0] == "\x50") or (data[0] == "\x70") or (data[0] == "\x90") or (data[0] == "\xb0") or (data[0] == "\xd0") or (data[0] == "\xf0"):
			return chr(ord(data[0]) ^ 0x20) + data[1:]
		return None

	def skip_and_return_value(self, data, addr, value):
		if (data[0] != "\x20") or (len(data) != 3):
			return None
		return "\xa9" + chr(value & 0xff) + "\xea"


class NESView(BinaryView):
	name = "NES"
	long_name = "NES ROM"

	def __init__(self, data):
		BinaryView.__init__(self, parent_view = data, file_metadata = data.file)
		self.platform = Architecture['6502'].standalone_platform

	@classmethod
	def is_valid_for_data(self, data):
		hdr = data.read(0, 16)
		if len(hdr) < 16:
			return False
		if hdr[0:4] != "NES\x1a":
			return False
		rom_banks = struct.unpack("B", hdr[4])[0]
		if rom_banks < (self.bank + 1):
			return False
		return True

	def init(self):
		try:
			hdr = self.parent_view.read(0, 16)
			self.rom_banks = struct.unpack("B", hdr[4])[0]
			self.vrom_banks = struct.unpack("B", hdr[5])[0]
			self.rom_flags = struct.unpack("B", hdr[6])[0]
			self.mapper_index = struct.unpack("B", hdr[7])[0] | (self.rom_flags >> 4)
			self.ram_banks = struct.unpack("B", hdr[8])[0]
			self.rom_offset = 16
			if self.rom_flags & 4:
				self.rom_offset += 512
			self.rom_length = self.rom_banks * 0x4000

			# Add mapping for RAM and hardware registers, not backed by file contents
			self.add_auto_segment(0, 0x8000, 0, 0, SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentExecutable)

			# Add ROM mappings
			self.add_auto_segment(0x8000, 0x4000, self.rom_offset + (self.__class__.bank * 0x4000), 0x4000,
				SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable)
			self.add_auto_segment(0xc000, 0x4000, self.rom_offset + self.rom_length - 0x4000, 0x4000,
				SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable)

			nmi = struct.unpack("<H", self.read(0xfffa, 2))[0]
			start = struct.unpack("<H", self.read(0xfffc, 2))[0]
			irq = struct.unpack("<H", self.read(0xfffe, 2))[0]
			self.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, nmi, "_nmi"))
			self.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, start, "_start"))
			self.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, irq, "_irq"))
			self.add_function(nmi)
			self.add_function(irq)
			self.add_entry_point(start)

			# Hardware registers
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x2000, "PPUCTRL"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x2001, "PPUMASK"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x2002, "PPUSTATUS"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x2003, "OAMADDR"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x2004, "OAMDATA"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x2005, "PPUSCROLL"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x2006, "PPUADDR"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x2007, "PPUDATA"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4000, "SQ1_VOL"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4001, "SQ1_SWEEP"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4002, "SQ1_LO"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4003, "SQ1_HI"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4004, "SQ2_VOL"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4005, "SQ2_SWEEP"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4006, "SQ2_LO"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4007, "SQ2_HI"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4008, "TRI_LINEAR"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x400a, "TRI_LO"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x400b, "TRI_HI"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x400c, "NOISE_VOL"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x400e, "NOISE_LO"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x400f, "NOISE_HI"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4010, "DMC_FREQ"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4011, "DMC_RAW"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4012, "DMC_START"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4013, "DMC_LEN"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4014, "OAMDMA"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4015, "SND_CHN"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4016, "JOY1"))
			self.define_auto_symbol(Symbol(SymbolType.DataSymbol, 0x4017, "JOY2"))

			sym_files = [self.file.filename + ".%x.nl" % self.__class__.bank,
					self.file.filename + ".ram.nl",
					self.file.filename + ".%x.nl" % (self.rom_banks - 1)]
			for f in sym_files:
				if os.path.exists(f):
					sym_contents = open(f, "r").read()
					lines = sym_contents.split('\n')
					for line in lines:
						sym = line.split('#')
						if len(sym) < 3:
							break
						addr = int(sym[0][1:], 16)
						name = sym[1]
						self.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, addr, name))
						if addr >= 0x8000:
							self.add_function(addr)

			return True
		except:
			log_error(traceback.format_exc())
			return False

	def perform_is_executable(self):
		return True

	def perform_get_entry_point(self):
		return struct.unpack("<H", str(self.perform_read(0xfffc, 2)))[0]


banks = []
for i in range(0, 32):
	class NESViewBank(NESView):
		bank = i
		name = "NES Bank %X" % i
		long_name = "NES ROM (bank %X)" % i

		def __init__(self, data):
			NESView.__init__(self, data)

	banks.append(NESViewBank)
	NESViewBank.register()

M6502.register()
