Skip to content

Instantly share code, notes, and snippets.

@fuzyll
Created July 25, 2016 04:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save fuzyll/29596bf2a5e5b5397d826c77ff534bf1 to your computer and use it in GitHub Desktop.
Save fuzyll/29596bf2a5e5b5397d826c77ff534bf1 to your computer and use it in GitHub Desktop.
Updated version of Binary Ninja's NES plugin (tries to handle more mappers).
# Copyright (c) 2015-2016 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.
# FIXME: no way to directly link two banks right now, so you can't double-click
# a function implemented in another bank and have it navigate there
# FIXME: no current way to prevent it from making "functions" in the current bank
# that should be in another bank
# FIXME: should be able to ask for cross-references to the MMC3 memory-mapped registers
# by using get_code_refs and giving it the register address
import traceback
import sys
import os
import platform
import struct
try:
from binaryninja import *
except ImportError:
if platform.mac_ver()[0]:
sys.path.append("/Applications/Binary Ninja.app/Contents/Resources/python")
from binaryninja import *
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(PossibleAddressToken, "$%.4x" % value, value)], # ABS
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.4x" % value, value)], # ABS_DEST
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.4x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "x")], # ABS_X
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.4x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "x")], # ABS_X_DEST
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.4x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "y")], # ABS_Y
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.4x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "y")], # ABS_Y_DEST
lambda value: [InstructionTextToken(RegisterToken, "a")], # ACCUM
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.4x" % value, value)], # ADDR
lambda value: [InstructionTextToken(TextToken, "#"), InstructionTextToken(IntegerToken, "$%.2x" % value, value)], # IMMED
lambda value: [InstructionTextToken(TextToken, "["), InstructionTextToken(PossibleAddressToken, "$%.4x" % value, value),
InstructionTextToken(TextToken, "]")], # IND
lambda value: [InstructionTextToken(TextToken, "["), InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "x"),
InstructionTextToken(TextToken, "]")], # IND_X
lambda value: [InstructionTextToken(TextToken, "["), InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "x"),
InstructionTextToken(TextToken, "]")], # IND_X_DEST
lambda value: [InstructionTextToken(TextToken, "["), InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value),
InstructionTextToken(TextToken, "], "), InstructionTextToken(RegisterToken, "y")], # IND_Y
lambda value: [InstructionTextToken(TextToken, "["), InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value),
InstructionTextToken(TextToken, "], "), InstructionTextToken(RegisterToken, "y")], # IND_Y_DEST
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.4x" % value, value)], # REL
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value)], # ZERO
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value)], # ZERO_DEST
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "x")], # ZERO_X
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "x")], # ZERO_X_DEST
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "y")], # ZERO_Y
lambda value: [InstructionTextToken(PossibleAddressToken, "$%.2x" % value, value),
InstructionTextToken(TextToken, ", "), InstructionTextToken(RegisterToken, "y")] # ZERO_Y_DEST
]
def indirect_load(il, value):
if (value & 0xff) == 0xff:
lo_addr = il.const(2, value)
hi_addr = il.const(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(2, value))
def load_zero_page_16(il, value):
if il[value].operation == "LLIL_CONST":
if il[value].value == 0xff:
lo = il.zero_extend(2, il.load(1, il.const(2, 0xff)))
hi = il.shift_left(2, il.zero_extend(2, il.load(1, il.const(2, 0)), il.const(2, 8)))
return il.or_expr(2, lo, hi)
return il.load(2, il.const(2, il[value].value))
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(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(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(2, value), # REL
lambda il, value: il.load(1, il.const(2, value)), # ZERO
lambda il, value: il.const(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 == LLIL_CONST:
t = il.get_label_for_address(Architecture['6502'], il[dest].value)
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 == LLIL_CONST:
label = il.get_label_for_address(Architecture['6502'], il[dest].value)
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, 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(LLFC_UGE), operand),
"bcs": lambda il, operand: cond_branch(il, il.flag_condition(LLFC_ULT), operand),
"beq": lambda il, operand: cond_branch(il, il.flag_condition(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(LLFC_NEG), operand),
"bne": lambda il, operand: cond_branch(il, il.flag_condition(LLFC_NE), operand),
"bpl": lambda il, operand: cond_branch(il, il.flag_condition(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), flags = "czs")),
"rol@": lambda il, operand: il.set_reg(1, "a", il.rotate_left_carry(1, il.reg(1, "a"), il.const(1, 1), flags = "czs")),
"ror": lambda il, operand: il.store(1, operand, il.rotate_right_carry(1, il.load(1, operand), il.const(1, 1), flags = "czs")),
"ror@": lambda il, operand: il.set_reg(1, "a", il.rotate_right_carry(1, il.reg(1, "a"), il.const(1, 1), 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, 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
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": SpecialFlagRole, # Not a normal carry flag, subtract result is inverted
"z": ZeroFlagRole,
"v": OverflowFlagRole,
"s": NegativeSignFlagRole
}
flags_required_for_flag_condition = {
LLFC_UGE: ["c"],
LLFC_ULT: ["c"],
LLFC_E: ["z"],
LLFC_NE: ["z"],
LLFC_NEG: ["s"],
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 perform_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(UnconditionalBranch, struct.unpack("<H", data[1:3])[0])
else:
result.add_branch(UnresolvedBranch)
elif instr == "jsr":
result.add_branch(CallDestination, struct.unpack("<H", data[1:3])[0])
elif instr in ["rti", "rts"]:
result.add_branch(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(TrueBranch, dest)
result.add_branch(FalseBranch, addr + 2)
return result
def perform_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(TextToken, "%-7s " % instr.replace("@", "")))
tokens += OperandTokens[operand](value)
return tokens, length
def perform_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 perform_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 perform_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 perform_is_always_branch_patch_available(self, data, addr):
return False
def perform_is_skip_and_return_zero_patch_available(self, data, addr):
return (data[0] == "\x20") and (len(data) == 3)
def perform_is_skip_and_return_value_patch_available(self, data, addr):
return (data[0] == "\x20") and (len(data) == 3)
def perform_convert_to_nop(self, data, addr):
return "\xea" * len(data)
def perform_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 perform_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 perform_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 NESViewUpdateNotification(BinaryDataNotification):
def __init__(self, view):
self.view = view
def data_written(self, view, offset, length):
addr = offset - self.view.rom_offset
while length > 0:
bank_ofs = addr & 0x3fff
if (bank_ofs + length) > 0x4000:
to_read = 0x4000 - bank_ofs
else:
to_read = length
if length < to_read:
to_read = length
if (addr >= (bank_ofs + (self.view.__class__.bank * 0x4000))) and (addr < (bank_ofs + ((self.view.__class__.bank + 1) * 0x4000))):
self.view.notify_data_written(0x8000 + bank_ofs, to_read)
elif (addr >= (bank_ofs + (self.view.rom_length - 0x4000))) and (addr < (bank_ofs + self.view.rom_length)):
self.view.notify_data_written(0xc000 + bank_ofs, to_read)
length -= to_read
addr += to_read
def data_inserted(self, view, offset, length):
self.view.notify_data_written(0x8000, 0x8000)
def data_removed(self, view, offset, length):
self.view.notify_data_written(0x8000, 0x8000)
class NESView(BinaryView):
name = "NES"
long_name = "NES ROM"
def __init__(self, data):
BinaryView.__init__(self, data.file)
self.data = data
self.notification = NESViewUpdateNotification(self)
self.data.register_notification(self.notification)
@classmethod
def is_valid_for_data(self, data):
# ensure enough data exists for a header and that the magic is valid
hdr = data.read(0, 16)
if len(hdr) < 16:
return False
if hdr[0:4] != "NES\x1a":
return False
# get the iNES mapper version for this ROM
mapper = struct.unpack("B", hdr[7])[0] | (struct.unpack("B", hdr[6])[0] >> 4)
# ensure that our ROM bank isn't out-of-range
rom_banks = struct.unpack("B", hdr[4])[0]
if mapper == 4:
rom_banks *= 2 # ROM banks are half-size on the MMC3
if rom_banks < (self.bank + 1):
return False
return True
def init(self):
try:
# unpack the ROM header
# FIXME: iNES 1.0 flags NYI, iNES 2.0 fields NYI
# FIXME: trainer NYI
hdr = self.data.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 = struct.unpack("B", hdr[7])[0] | (self.rom_flags >> 4)
self.ram_banks = struct.unpack("B", hdr[8])[0]
self.rom_start = 16
if self.rom_flags & 4: # skip the optional trainer if it exists
self.rom_start += 512
self.mmc3_bank_mode = 0 # default to $C000 bank being fixed
if self.mapper == 4:
# ROM banks are half-size on the MMC3
self.rom_bank_size = 0x2000
self.rom_banks *= 2
else:
self.rom_bank_size = 0x4000
self.rom_end = self.rom_banks * self.rom_bank_size
# define the 3 CPU hardware vectors and set their functions
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(FunctionSymbol, nmi, "_nmi"))
self.define_auto_symbol(Symbol(FunctionSymbol, start, "_start"))
self.define_auto_symbol(Symbol(FunctionSymbol, irq, "_irq"))
self.add_function(Architecture['6502'].standalone_platform, nmi)
self.add_function(Architecture['6502'].standalone_platform, irq)
self.add_entry_point(Architecture['6502'].standalone_platform, start)
# FIXME: horrible heuristic for trying to detect which MMC3 bank mode is used for a given ROM
# appears to work properly for SMB2 and SMB3, but possibly not Mega Man III?
if self.mapper == 4:
start_code = self.read(start, 0x10000 - start)
if "\xa9\x40\x8d\x00\x80" in start_code:
log_warn("MMC3 bank mode switched to 1")
# if lda #$40 -> sta $8000, we need to change the MMC3 bank mode
self.mmc3_bank_mode = 1
# define the PPU and APU hardware vectors
self.define_auto_symbol(Symbol(DataSymbol, 0x2000, "PPUCTRL"))
self.define_auto_symbol(Symbol(DataSymbol, 0x2001, "PPUMASK"))
self.define_auto_symbol(Symbol(DataSymbol, 0x2002, "PPUSTATUS"))
self.define_auto_symbol(Symbol(DataSymbol, 0x2003, "OAMADDR"))
self.define_auto_symbol(Symbol(DataSymbol, 0x2004, "OAMDATA"))
self.define_auto_symbol(Symbol(DataSymbol, 0x2005, "PPUSCROLL"))
self.define_auto_symbol(Symbol(DataSymbol, 0x2006, "PPUADDR"))
self.define_auto_symbol(Symbol(DataSymbol, 0x2007, "PPUDATA"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4000, "SQ1_VOL"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4001, "SQ1_SWEEP"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4002, "SQ1_LO"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4003, "SQ1_HI"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4004, "SQ2_VOL"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4005, "SQ2_SWEEP"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4006, "SQ2_LO"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4007, "SQ2_HI"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4008, "TRI_LINEAR"))
self.define_auto_symbol(Symbol(DataSymbol, 0x400a, "TRI_LO"))
self.define_auto_symbol(Symbol(DataSymbol, 0x400b, "TRI_HI"))
self.define_auto_symbol(Symbol(DataSymbol, 0x400c, "NOISE_VOL"))
self.define_auto_symbol(Symbol(DataSymbol, 0x400e, "NOISE_LO"))
self.define_auto_symbol(Symbol(DataSymbol, 0x400f, "NOISE_HI"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4010, "DMC_FREQ"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4011, "DMC_RAW"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4012, "DMC_START"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4013, "DMC_LEN"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4014, "OAMDMA"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4015, "SND_CHN"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4016, "JOY1"))
self.define_auto_symbol(Symbol(DataSymbol, 0x4017, "JOY2"))
# define the hardware vectors for extra chips
if self.mapper == 4:
self.define_auto_symbol(Symbol(DataSymbol, 0x8000, "MMC3_BANK_SEL"))
self.define_auto_symbol(Symbol(DataSymbol, 0x8001, "MMC3_BANK_DAT"))
# read and apply symbol files (if any exist)
sym_files = [self.data.file.filename + ".%x.nl" % self.__class__.bank,
self.data.file.filename + ".ram.nl",
self.data.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(FunctionSymbol, addr, name))
if addr >= 0x8000:
self.add_function(Architecture['6502'].standalone_platform, addr)
return True
except:
log_error(traceback.format_exc())
return False
def perform_is_valid_offset(self, addr):
# valid ROM addresses are the upper-half of the address space
if (addr >= 0x8000) and (addr < 0x10000):
return True
return False
def perform_read(self, addr, length):
# fail if address is out-of-bounds
if addr < 0x8000:
return None
if addr >= (0x8000 + self.rom_end):
return None
# truncate read length to stay in bounds
if (addr + length) > 0x10000:
length = 0x10000 - addr
# read all data from ROM
result = ""
while length > 0:
# calculate the offset and the amount we still need to read
bank_ofs = addr & (self.rom_bank_size - 1)
if (bank_ofs + length) > self.rom_bank_size:
to_read = self.rom_bank_size - bank_ofs
else:
to_read = length
# start reading
if self.mapper == 4:
if addr >= 0xe000:
# $E000-$FFFF is always fixed to the last bank in the ROM for this mapper
rom_ofs = self.rom_start + bank_ofs + (self.rom_end - self.rom_bank_size)
elif (addr >= 0xc000 and self.mmc3_bank_mode == 0) or (addr <= 0x9fff and self.mmc3_bank_mode == 1):
# $C000-$DFFF is fixed to the next-to-last bank in the ROM if the bank mode is 0
# $8000-$9FFF is fixed to the next-to-last bank in the ROM if the bank mode is 1
rom_ofs = self.rom_start + bank_ofs + (self.rom_end - (self.rom_bank_size * 2))
elif (addr >= 0xc000 and self.mmc3_bank_mode == 1) or (addr >= 0xa000 and self.mmc3_bank_mode == 0):
if self.__class__.bank >= self.rom_banks - 2:
# FIXME: assume the second ROM bank if we're one of the two fixed banks
rom_ofs = self.rom_start + bank_ofs + self.rom_bank_size
else:
# FIXME: assume the next ROM bank is always mapped here (don't actually know which will be)
rom_ofs = self.rom_start + bank_ofs + ((self.__class__.bank + 1) * self.rom_bank_size)
elif (addr >= 0xa000 and self.mmc3_bank_mode == 1) or (addr >= 0x8000 and self.mmc3_bank_mode == 0):
if self.__class__.bank >= (self.rom_banks - 2):
# FIXME: assume the first ROM bank if we're one of the two fixed banks
rom_ofs = self.rom_start + bank_ofs
else:
# FIXME: assume the current ROM bank is always mapped here (don't actually know which will be)
rom_ofs = self.rom_start + bank_ofs + (self.__class__.bank * self.rom_bank_size)
else:
# default handler for mappers: 0, 1, 2, 3
# FIXME: for mapper 1, this code doesn't properly handle bank switch modes 2 or 3 (switch upper bank)
if addr >= 0xc000:
rom_ofs = self.rom_start + bank_ofs + (self.rom_end - self.rom_bank_size)
else:
rom_ofs = self.rom_start + bank_ofs + (self.__class__.bank * self.rom_bank_size)
data = self.data.read(rom_ofs, to_read)
result += data
if len(data) < to_read:
break
length -= to_read
addr += to_read
return result
def perform_write(self, addr, value):
# FIXME: mapper 4 NYI - code below assumes mapper 0
# fail if address is out-of-bounds
if addr < 0x8000:
return 0
if addr >= (0x8000 + self.rom_end):
return 0
# truncate write length to stay in bounds
if (addr + len(value)) > (0x8000 + self.rom_end):
length = (0x8000 + self.rom_end) - addr
else:
length = len(value)
if (addr + length) > 0x10000:
length = 0x10000 - addr
# start writing
offset = 0
while length > 0:
bank_ofs = addr & 0x3fff
if (bank_ofs + length) > 0x4000:
to_write = 0x4000 - bank_ofs
else:
to_write = length
if addr < 0xc000:
written = self.data.write(self.rom_start + bank_ofs + (self.__class__.bank * self.rom_bank_size), value[offset : offset + to_write])
else:
written = self.data.write(self.rom_start + bank_ofs + self.rom_end - self.rom_bank_size, value[offset : offset + to_write])
if written < to_write:
break
length -= to_write
addr += to_write
offset += to_write
return offset
def perform_get_start(self):
return 0 # 0x0000 is always the lowest address in memory
def perform_get_length(self):
return 0x10000 # 0xFFFF is always the highest address in memory
def perform_is_executable(self):
return True
def perform_get_entry_point(self):
# 0xFFFC is the "reset vector" on the NES (CPU always starts here)
return struct.unpack("<H", str(self.perform_read(0xfffc, 2)))[0]
banks = []
for i in xrange(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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment