Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # -*- coding: utf-8 -*-
- """
- GBC disassembler
- """
- import os
- import argparse
- from ctypes import c_int8
- import configuration
- from wram import read_constants
- z80_table = [
- ('nop', 0), # 00
- ('ld bc, {}', 2), # 01
- ('ld [bc], a', 0), # 02
- ('inc bc', 0), # 03
- ('inc b', 0), # 04
- ('dec b', 0), # 05
- ('ld b, ${:02x}', 1), # 06
- ('rlca', 0), # 07
- ('ld [{}], sp', 2), # 08
- ('add hl, bc', 0), # 09
- ('ld a, [bc]', 0), # 0a
- ('dec bc', 0), # 0b
- ('inc c', 0), # 0c
- ('dec c', 0), # 0d
- ('ld c, ${:02x}', 1), # 0e
- ('rrca', 0), # 0f
- ('db $10', 0), # 10
- ('ld de, {}', 2), # 11
- ('ld [de], a', 0), # 12
- ('inc de', 0), # 13
- ('inc d', 0), # 14
- ('dec d', 0), # 15
- ('ld d, ${:02x}', 1), # 16
- ('rla', 0), # 17
- ('jr {}', 1), # 18
- ('add hl, de', 0), # 19
- ('ld a, [de]', 0), # 1a
- ('dec de', 0), # 1b
- ('inc e', 0), # 1c
- ('dec e', 0), # 1d
- ('ld e, ${:02x}', 1), # 1e
- ('rra', 0), # 1f
- ('jr nz, {}', 1), # 20
- ('ld hl, {}', 2), # 21
- ('ld [hli], a', 0), # 22
- ('inc hl', 0), # 23
- ('inc h', 0), # 24
- ('dec h', 0), # 25
- ('ld h, ${:02x}', 1), # 26
- ('daa', 0), # 27
- ('jr z, {}', 1), # 28
- ('add hl, hl', 0), # 29
- ('ld a, [hli]', 0), # 2a
- ('dec hl', 0), # 2b
- ('inc l', 0), # 2c
- ('dec l', 0), # 2d
- ('ld l, ${:02x}', 1), # 2e
- ('cpl', 0), # 2f
- ('jr nc, {}', 1), # 30
- ('ld sp, {}', 2), # 31
- ('ld [hld], a', 0), # 32
- ('inc sp', 0), # 33
- ('inc [hl]', 0), # 34
- ('dec [hl]', 0), # 35
- ('ld [hl], ${:02x}', 1), # 36
- ('scf', 0), # 37
- ('jr c, {}', 1), # 38
- ('add hl, sp', 0), # 39
- ('ld a, [hld]', 0), # 3a
- ('dec sp', 0), # 3b
- ('inc a', 0), # 3c
- ('dec a', 0), # 3d
- ('ld a, ${:02x}', 1), # 3e
- ('ccf', 0), # 3f
- ('ld b, b', 0), # 40
- ('ld b, c', 0), # 41
- ('ld b, d', 0), # 42
- ('ld b, e', 0), # 43
- ('ld b, h', 0), # 44
- ('ld b, l', 0), # 45
- ('ld b, [hl]', 0), # 46
- ('ld b, a', 0), # 47
- ('ld c, b', 0), # 48
- ('ld c, c', 0), # 49
- ('ld c, d', 0), # 4a
- ('ld c, e', 0), # 4b
- ('ld c, h', 0), # 4c
- ('ld c, l', 0), # 4d
- ('ld c, [hl]', 0), # 4e
- ('ld c, a', 0), # 4f
- ('ld d, b', 0), # 50
- ('ld d, c', 0), # 51
- ('ld d, d', 0), # 52
- ('ld d, e', 0), # 53
- ('ld d, h', 0), # 54
- ('ld d, l', 0), # 55
- ('ld d, [hl]', 0), # 56
- ('ld d, a', 0), # 57
- ('ld e, b', 0), # 58
- ('ld e, c', 0), # 59
- ('ld e, d', 0), # 5a
- ('ld e, e', 0), # 5b
- ('ld e, h', 0), # 5c
- ('ld e, l', 0), # 5d
- ('ld e, [hl]', 0), # 5e
- ('ld e, a', 0), # 5f
- ('ld h, b', 0), # 60
- ('ld h, c', 0), # 61
- ('ld h, d', 0), # 62
- ('ld h, e', 0), # 63
- ('ld h, h', 0), # 64
- ('ld h, l', 0), # 65
- ('ld h, [hl]', 0), # 66
- ('ld h, a', 0), # 67
- ('ld l, b', 0), # 68
- ('ld l, c', 0), # 69
- ('ld l, d', 0), # 6a
- ('ld l, e', 0), # 6b
- ('ld l, h', 0), # 6c
- ('ld l, l', 0), # 6d
- ('ld l, [hl]', 0), # 6e
- ('ld l, a', 0), # 6f
- ('ld [hl], b', 0), # 70
- ('ld [hl], c', 0), # 71
- ('ld [hl], d', 0), # 72
- ('ld [hl], e', 0), # 73
- ('ld [hl], h', 0), # 74
- ('ld [hl], l', 0), # 75
- ('halt', 0), # 76
- ('ld [hl], a', 0), # 77
- ('ld a, b', 0), # 78
- ('ld a, c', 0), # 79
- ('ld a, d', 0), # 7a
- ('ld a, e', 0), # 7b
- ('ld a, h', 0), # 7c
- ('ld a, l', 0), # 7d
- ('ld a, [hl]', 0), # 7e
- ('ld a, a', 0), # 7f
- ('add b', 0), # 80
- ('add c', 0), # 81
- ('add d', 0), # 82
- ('add e', 0), # 83
- ('add h', 0), # 84
- ('add l', 0), # 85
- ('add [hl]', 0), # 86
- ('add a', 0), # 87
- ('adc b', 0), # 88
- ('adc c', 0), # 89
- ('adc d', 0), # 8a
- ('adc e', 0), # 8b
- ('adc h', 0), # 8c
- ('adc l', 0), # 8d
- ('adc [hl]', 0), # 8e
- ('adc a', 0), # 8f
- ('sub b', 0), # 90
- ('sub c', 0), # 91
- ('sub d', 0), # 92
- ('sub e', 0), # 93
- ('sub h', 0), # 94
- ('sub l', 0), # 95
- ('sub [hl]', 0), # 96
- ('sub a', 0), # 97
- ('sbc b', 0), # 98
- ('sbc c', 0), # 99
- ('sbc d', 0), # 9a
- ('sbc e', 0), # 9b
- ('sbc h', 0), # 9c
- ('sbc l', 0), # 9d
- ('sbc [hl]', 0), # 9e
- ('sbc a', 0), # 9f
- ('and b', 0), # a0
- ('and c', 0), # a1
- ('and d', 0), # a2
- ('and e', 0), # a3
- ('and h', 0), # a4
- ('and l', 0), # a5
- ('and [hl]', 0), # a6
- ('and a', 0), # a7
- ('xor b', 0), # a8
- ('xor c', 0), # a9
- ('xor d', 0), # aa
- ('xor e', 0), # ab
- ('xor h', 0), # ac
- ('xor l', 0), # ad
- ('xor [hl]', 0), # ae
- ('xor a', 0), # af
- ('or b', 0), # b0
- ('or c', 0), # b1
- ('or d', 0), # b2
- ('or e', 0), # b3
- ('or h', 0), # b4
- ('or l', 0), # b5
- ('or [hl]', 0), # b6
- ('or a', 0), # b7
- ('cp b', 0), # b8
- ('cp c', 0), # b9
- ('cp d', 0), # ba
- ('cp e', 0), # bb
- ('cp h', 0), # bc
- ('cp l', 0), # bd
- ('cp [hl]', 0), # be
- ('cp a', 0), # bf
- ('ret nz', 0), # c0
- ('pop bc', 0), # c1
- ('jp nz, {}', 2), # c2
- ('jp {}', 2), # c3
- ('call nz, {}', 2), # c4
- ('push bc', 0), # c5
- ('add ${:02x}', 1), # c6
- ('rst $0', 0), # c7
- ('ret z', 0), # c8
- ('ret', 0), # c9
- ('jp z, {}', 2), # ca
- ('bitops', 1), # cb
- ('call z, {}', 2), # cc
- ('call {}', 2), # cd
- ('adc ${:02x}', 1), # ce
- ('rst $8', 0), # cf
- ('ret nc', 0), # d0
- ('pop de', 0), # d1
- ('jp nc, ${:04x}', 2), # d2
- ('db $d3', 0), # d3
- ('call nc, {}', 2), # d4
- ('push de', 0), # d5
- ('sub ${:02x}', 1), # d6
- ('rst $10', 0), # d7
- ('ret c', 0), # d8
- ('reti', 0), # d9
- ('jp c, ${:04x}', 2), # da
- ('db $db', 0), # db
- ('call c, {}', 2), # dc
- ('db $dd', 2), # dd
- ('sbc ${:02x}', 1), # de
- ('rst $18', 0), # df
- ('ld [{}], a', 1), # e0
- ('pop hl', 0), # e1
- ('ld [$ff00+c], a', 0), # e2
- ('db $e3', 0), # e3
- ('db $e4', 0), # e4
- ('push hl', 0), # e5
- ('and ${:02x}', 1), # e6
- ('rst $20', 0), # e7
- ('add sp, ${:02x}', 1), # e8
- ('jp [hl]', 0), # e9
- ('ld [{}], a', 2), # ea
- ('db $eb', 0), # eb
- ('db $ec', 2), # ec
- ('db $ed', 2), # ed
- ('xor ${:02x}', 1), # ee
- ('rst $28', 0), # ef
- ('ld a, [{}]', 1), # f0
- ('pop af', 0), # f1
- ('db $f2', 0), # f2
- ('di', 0), # f3
- ('db $f4', 0), # f4
- ('push af', 0), # f5
- ('or ${:02x}', 1), # f6
- ('rst $30', 0), # f7
- ('ld hl, sp+${:02x}', 1), # f8
- ('ld sp, [hl]', 0), # f9
- ('ld a, [{}]', 2), # fa
- ('ei', 0), # fb
- ('db $fc', 2), # fc
- ('db $fd', 2), # fd
- ('cp ${:02x}', 1), # fe
- ('rst $38', 0), # ff
- ]
- bit_ops_table = [
- "rlc b", "rlc c", "rlc d", "rlc e", "rlc h", "rlc l", "rlc [hl]", "rlc a", # $00 - $07
- "rrc b", "rrc c", "rrc d", "rrc e", "rrc h", "rrc l", "rrc [hl]", "rrc a", # $08 - $0f
- "rl b", "rl c", "rl d", "rl e", "rl h", "rl l", "rl [hl]", "rl a", # $10 - $17
- "rr b", "rr c", "rr d", "rr e", "rr h", "rr l", "rr [hl]", "rr a", # $18 - $1f
- "sla b", "sla c", "sla d", "sla e", "sla h", "sla l", "sla [hl]", "sla a", # $20 - $27
- "sra b", "sra c", "sra d", "sra e", "sra h", "sra l", "sra [hl]", "sra a", # $28 - $2f
- "swap b", "swap c", "swap d", "swap e", "swap h", "swap l", "swap [hl]", "swap a", # $30 - $37
- "srl b", "srl c", "srl d", "srl e", "srl h", "srl l", "srl [hl]", "srl a", # $38 - $3f
- "bit 0, b", "bit 0, c", "bit 0, d", "bit 0, e", "bit 0, h", "bit 0, l", "bit 0, [hl]", "bit 0, a", # $40 - $47
- "bit 1, b", "bit 1, c", "bit 1, d", "bit 1, e", "bit 1, h", "bit 1, l", "bit 1, [hl]", "bit 1, a", # $48 - $4f
- "bit 2, b", "bit 2, c", "bit 2, d", "bit 2, e", "bit 2, h", "bit 2, l", "bit 2, [hl]", "bit 2, a", # $50 - $57
- "bit 3, b", "bit 3, c", "bit 3, d", "bit 3, e", "bit 3, h", "bit 3, l", "bit 3, [hl]", "bit 3, a", # $58 - $5f
- "bit 4, b", "bit 4, c", "bit 4, d", "bit 4, e", "bit 4, h", "bit 4, l", "bit 4, [hl]", "bit 4, a", # $60 - $67
- "bit 5, b", "bit 5, c", "bit 5, d", "bit 5, e", "bit 5, h", "bit 5, l", "bit 5, [hl]", "bit 5, a", # $68 - $6f
- "bit 6, b", "bit 6, c", "bit 6, d", "bit 6, e", "bit 6, h", "bit 6, l", "bit 6, [hl]", "bit 6, a", # $70 - $77
- "bit 7, b", "bit 7, c", "bit 7, d", "bit 7, e", "bit 7, h", "bit 7, l", "bit 7, [hl]", "bit 7, a", # $78 - $7f
- "res 0, b", "res 0, c", "res 0, d", "res 0, e", "res 0, h", "res 0, l", "res 0, [hl]", "res 0, a", # $80 - $87
- "res 1, b", "res 1, c", "res 1, d", "res 1, e", "res 1, h", "res 1, l", "res 1, [hl]", "res 1, a", # $88 - $8f
- "res 2, b", "res 2, c", "res 2, d", "res 2, e", "res 2, h", "res 2, l", "res 2, [hl]", "res 2, a", # $90 - $97
- "res 3, b", "res 3, c", "res 3, d", "res 3, e", "res 3, h", "res 3, l", "res 3, [hl]", "res 3, a", # $98 - $9f
- "res 4, b", "res 4, c", "res 4, d", "res 4, e", "res 4, h", "res 4, l", "res 4, [hl]", "res 4, a", # $a0 - $a7
- "res 5, b", "res 5, c", "res 5, d", "res 5, e", "res 5, h", "res 5, l", "res 5, [hl]", "res 5, a", # $a8 - $af
- "res 6, b", "res 6, c", "res 6, d", "res 6, e", "res 6, h", "res 6, l", "res 6, [hl]", "res 6, a", # $b0 - $b7
- "res 7, b", "res 7, c", "res 7, d", "res 7, e", "res 7, h", "res 7, l", "res 7, [hl]", "res 7, a", # $b8 - $bf
- "set 0, b", "set 0, c", "set 0, d", "set 0, e", "set 0, h", "set 0, l", "set 0, [hl]", "set 0, a", # $c0 - $c7
- "set 1, b", "set 1, c", "set 1, d", "set 1, e", "set 1, h", "set 1, l", "set 1, [hl]", "set 1, a", # $c8 - $cf
- "set 2, b", "set 2, c", "set 2, d", "set 2, e", "set 2, h", "set 2, l", "set 2, [hl]", "set 2, a", # $d0 - $d7
- "set 3, b", "set 3, c", "set 3, d", "set 3, e", "set 3, h", "set 3, l", "set 3, [hl]", "set 3, a", # $d8 - $df
- "set 4, b", "set 4, c", "set 4, d", "set 4, e", "set 4, h", "set 4, l", "set 4, [hl]", "set 4, a", # $e0 - $e7
- "set 5, b", "set 5, c", "set 5, d", "set 5, e", "set 5, h", "set 5, l", "set 5, [hl]", "set 5, a", # $e8 - $ef
- "set 6, b", "set 6, c", "set 6, d", "set 6, e", "set 6, h", "set 6, l", "set 6, [hl]", "set 6, a", # $f0 - $f7
- "set 7, b", "set 7, c", "set 7, d", "set 7, e", "set 7, h", "set 7, l", "set 7, [hl]", "set 7, a" # $f8 - $ff
- ]
- unconditional_returns = [0xc9, 0xd9]
- absolute_jumps = [0xc3, 0xc2, 0xca, 0xd2, 0xda]
- call_commands = [0xcd, 0xc4, 0xcc, 0xd4, 0xdc]
- relative_jumps = [0x18, 0x20, 0x28, 0x30, 0x38]
- unconditional_jumps = [0xc3, 0x18]
- def asm_label(address):
- """
- Return a local label name for asm at <address>.
- """
- return '.asm_%x' % address
- def data_label(address):
- """
- Return a local label name for data at <address>.
- """
- return '.data_%x' % address
- def get_local_address(address):
- """
- Return the local address of a rom address.
- """
- bank = address / 0x4000
- address &= 0x3fff
- if bank:
- return address + 0x4000
- return address
- def get_global_address(address, bank):
- """
- Return the rom address of a local address and bank.
- This accounts for a quirk in mbc3 where 0:4000-7fff resolves to 1:4000-7fff.
- """
- if address < 0x8000:
- if address >= 0x4000 and bank > 0:
- return address + (bank - 1) * 0x4000
- return address
- def created_but_unused_labels_exist(byte_labels):
- """
- Check whether a label has been created but not used.
- If so, then that means it has to be called or specified later.
- """
- return (False in [label["definition"] for label in byte_labels.values()])
- def all_byte_labels_are_defined(byte_labels):
- """
- Check whether all labels have already been defined.
- """
- return (False not in [label["definition"] for label in byte_labels.values()])
- def load_rom(path='baserom.gbc'):
- return bytearray(open(path, 'rb').read())
- def read_symfile(path='baserom.sym'):
- """
- Return a list of dicts of label data from an rgbds .sym file.
- """
- symbols = []
- for line in open(path):
- line = line.strip().split(';')[0]
- if line:
- bank_address, label = line.split(' ')[:2]
- bank, address = bank_address.split(':')
- symbols += [{
- 'label': label,
- 'bank': int(bank, 16),
- 'address': int(address, 16),
- }]
- return symbols
- def load_symbols(path):
- sym = {}
- reverse_sym = {}
- wram_sym = {}
- sram_sym = {}
- symbols = read_symfile(path)
- for symbol in symbols:
- bank = symbol['bank']
- address = symbol['address']
- label = symbol['label']
- if 0x0000 <= address < 0x8000:
- if not sym.has_key(bank):
- sym[bank] = {}
- sym[bank][address] = label
- reverse_sym[label] = get_global_address(address, bank)
- elif 0xa000 <= address < 0xc000:
- if not sram_sym.has_key(bank):
- sram_sym[bank] = {}
- sram_sym[bank][address] = label
- elif address < 0xe000:
- if not wram_sym.has_key(bank):
- wram_sym[bank] = {}
- wram_sym[bank][address] = label
- else:
- raise ValueError("Unsupported symfile label type.")
- return sym, reverse_sym, wram_sym, sram_sym
- def get_symbol(sym, address, bank=0):
- if sym:
- if 0x0000 <= address < 0x4000:
- return sym.get(0, {}).get(address)
- else:
- return sym.get(bank, {}).get(address)
- return None
- def get_banked_ram_sym(sym, address):
- #if sym:
- # if 0xc000 <= address < 0xd000:
- # return sym.get(0, {}).get(address)
- # else:
- # return sym.get(bank, {}).get(address)
- if sym:
- for bank in sym.keys():
- temp_sym = sym.get(bank, {}).get(address)
- if temp_sym:
- return temp_sym
- return None
- def create_address_comment(offset):
- comment_bank = offset / 0x4000
- if comment_bank != 0:
- comment_bank_addr = (offset % 0x4000) + 0x4000
- else:
- comment_bank_addr = offset
- return " ; %x (%x:%x)" % (offset, comment_bank, comment_bank_addr)
- def offset_is_used(labels, offset):
- if offset in labels.keys():
- return 0 < labels[offset]["usage"]
- class Disassembler(object):
- """
- GBC disassembler
- """
- def __init__(self, config):
- """
- Setup the class instance.
- """
- self.config = config
- self.spacing = '\t'
- self.rom = None
- self.sym = None
- self.rsym = None
- self.gbhw = None
- self.vram = None
- self.sram = None
- self.hram = None
- self.wram = None
- def initialize(self, rom, symfile):
- """
- Setup the disassembler.
- """
- path = os.path.join(self.config.path, rom)
- self.rom = load_rom(path)
- path = os.path.join(self.config.path, symfile)
- if os.path.exists(path):
- self.sym, self.rsym, self.wram, self.sram = load_symbols(path)
- path = os.path.join(self.config.path, 'gbhw.asm')
- if os.path.exists(path):
- self.gbhw = read_constants(path)
- else:
- path = os.path.join(self.config.path, "constants/hardware_constants.asm")
- if os.path.exists(path):
- self.gbhw = read_constants(path)
- path = os.path.join(self.config.path, 'vram.asm')
- if os.path.exists(path):
- self.vram = read_constants(path)
- path = os.path.join(self.config.path, 'hram.asm')
- if os.path.exists(path):
- self.hram = read_constants(path)
- def find_label(self, address, bank=0):
- if type(address) is str:
- address = int(address.replace('$', '0x'), 16)
- elif address is None:
- return address
- if 0x0000 <= address < 0x8000:
- label = self.get_symbol(address, bank)
- elif address < 0xa000 and self.vram:
- label = self.vram.get(address)
- elif address < 0xc000:
- label = self.get_sram(address)
- elif address < 0xe000:
- label = self.get_wram(address)
- elif ((0xff00 <= address < 0xff80) or (address == 0xffff)) and self.gbhw:
- label = self.gbhw.get(address)
- elif (0xff80 <= address < 0xffff) and self.hram:
- label = self.hram.get(address)
- else:
- label = None
- return label
- def get_symbol(self, address, bank):
- symbol = get_symbol(self.sym, address, bank)
- if symbol == 'NULL' and address == 0 and bank == 0:
- return None
- return symbol
- def get_wram(self, address):
- symbol = get_banked_ram_sym(self.wram, address)
- if symbol == 'NULL' and address == 0:
- return None
- return symbol
- def get_sram(self, address):
- symbol = get_banked_ram_sym(self.sram, address)
- if symbol == 'NULL' and address == 0:
- return None
- return symbol
- def find_address_from_label(self, label):
- if self.rsym:
- return self.rsym.get(label)
- return None
- def output_bank_opcodes(self, start_offset, stop_offset, hard_stop=False, parse_data=False, include_last_address=True):
- """
- Output bank opcodes.
- fs = current_address
- b = bank_byte
- in = input_data -- rom
- bank_size = byte_count
- i = offset
- ad = end_address
- a, oa = current_byte_number
- stop_at can be used to supply a list of addresses to not disassemble
- over. This is useful if you know in advance that there are a lot of
- fall-throughs.
- """
- debug = False
- bank_id = start_offset / 0x4000
- stop_offset_undefined = False
- # check if stop_offset isn't defined
- if stop_offset is None:
- stop_offset_undefined = True
- # stop at the end of the current bank if stop_offset is not defined
- stop_offset = (bank_id + 1) * 0x4000 - 1
- if debug:
- print "bank id is: " + str(bank_id)
- rom = self.rom
- offset = start_offset
- current_byte_number = 0 #start from the beginning
- byte_labels = {}
- data_tables = {}
- output = "Func_%x:%s\n" % (start_offset,create_address_comment(start_offset))
- is_data = False
- while True:
- #first check if this byte already has a label
- #if it does, use the label
- #if not, generate a new label
- local_offset = get_local_address(offset)
- data_label_used = offset_is_used(data_tables, local_offset)
- byte_label_used = offset_is_used(byte_labels, local_offset)
- data_label_created = local_offset in data_tables.keys()
- byte_label_created = local_offset in byte_labels.keys()
- if byte_label_created:
- # if a byte label exists, remove any significance if there is a data label that exists
- if data_label_created:
- data_line_label = data_tables[local_offset]["name"]
- data_tables[local_offset]["usage"] = 0
- else:
- data_line_label = data_label(offset)
- data_tables[local_offset] = {}
- data_tables[local_offset]["name"] = data_line_label
- data_tables[local_offset]["usage"] = 0
- line_label = byte_labels[local_offset]["name"]
- byte_labels[local_offset]["usage"] += 1
- output += "\n"
- elif data_label_created and parse_data:
- # go add usage to a data label if it exists
- data_line_label = data_tables[local_offset]["name"]
- data_tables[local_offset]["usage"] += 1
- line_label = asm_label(offset)
- byte_labels[local_offset] = {}
- byte_labels[local_offset]["name"] = line_label
- byte_labels[local_offset]["usage"] = 0
- output += "\n"
- else:
- # create both a data and byte label if neither exist
- data_line_label = data_label(offset)
- data_tables[local_offset] = {}
- data_tables[local_offset]["name"] = data_line_label
- data_tables[local_offset]["usage"] = 0
- line_label = asm_label(offset)
- byte_labels[local_offset] = {}
- byte_labels[local_offset]["name"] = line_label
- byte_labels[local_offset]["usage"] = 0
- # any labels created not above are now used, so mark them as "defined"
- byte_labels[local_offset]["definition"] = True
- data_tables[local_offset]["definition"] = True
- # for now, output the byte and data labels (unused labels will be removed later
- output += line_label + "\n" + data_line_label + "\n"
- # get the current byte
- opcode_byte = rom[offset]
- # process the current byte if this is code or parse data has not been set
- if not is_data or not parse_data:
- # fetch the opcode string from a predefined table
- opcode_str = z80_table[opcode_byte][0]
- # fetch the number of arguments
- opcode_nargs = z80_table[opcode_byte][1]
- # get opcode arguments in advance (may not be used)
- opcode_arg_1 = rom[offset+1]
- opcode_arg_2 = rom[offset+2]
- if opcode_nargs == 0:
- # set output string simply as the opcode
- opcode_output_str = opcode_str
- elif opcode_nargs == 1:
- # opcodes with 1 argument
- if opcode_byte != 0xcb: # bit opcodes are handled separately
- if opcode_byte in relative_jumps:
- # if the current opcode is a relative jump, generate a label for the address we're jumping to
- # get the address of the location to jump to
- target_address = offset + 2 + c_int8(opcode_arg_1).value
- # get the local address to use as a key for byte_labels and data_tables
- local_target_address = get_local_address(target_address)
- if local_target_address in byte_labels.keys():
- # if the label has already been created, increase the usage and set output to the already created label
- byte_labels[local_target_address]["usage"] += 1
- opcode_output_str = byte_labels[local_target_address]["name"]
- elif target_address < start_offset:
- # if we're jumping to an address that is located before the start offset, assume it is a function
- opcode_output_str = "Func_%x" % target_address
- else:
- # create a new label
- opcode_output_str = asm_label(target_address)
- byte_labels[local_target_address] = {}
- byte_labels[local_target_address]["name"] = opcode_output_str
- # we know the label is used once, so set the usage to 1
- byte_labels[local_target_address]["usage"] = 1
- # since the label has not been output yet, mark it as "not defined"
- byte_labels[local_target_address]["definition"] = False
- # check if the target address conflicts with any data labels
- if local_target_address in data_tables.keys():
- # if so, remove any instances of it being used and set it as defined
- data_tables[local_target_address]["usage"] = 0
- data_tables[local_target_address]["definition"] = True
- # format the resulting argument into the output string
- opcode_output_str = opcode_str.format(opcode_output_str)
- # debug function
- if created_but_unused_labels_exist(byte_labels) and debug:
- output += create_address_comment(offset)
- elif opcode_byte == 0xe0 or opcode_byte == 0xf0:
- # handle gameboy hram read/write opcodes
- # create the address
- high_ram_address = 0xff00 + opcode_arg_1
- # search for an hram constant if possible
- high_ram_label = self.find_label(high_ram_address, bank_id)
- # if we couldn't find one, default to the address
- if high_ram_label is None:
- high_ram_label = "$%x" % high_ram_address
- # format the resulting argument into the output string
- opcode_output_str = opcode_str.format(high_ram_label)
- else:
- # if this isn't a relative jump or hram read/write, just format the byte into the opcode string
- opcode_output_str = opcode_str.format(opcode_arg_1)
- else:
- # handle bit opcodes by fetching the opcode from a separate table
- opcode_output_str = bit_ops_table[opcode_arg_1]
- elif opcode_nargs == 2:
- # opcodes with a pointer as an argument
- # format the two arguments into a little endian 16-bit pointer
- local_target_offset = opcode_arg_2 << 8 | opcode_arg_1
- # get the global offset of the pointer
- target_offset = get_global_address(local_target_offset, bank_id)
- # attempt to look for a matching label
- target_label = self.find_label(target_offset, bank_id)
- if opcode_byte in call_commands + absolute_jumps:
- if target_label is None:
- # if this is a call or jump opcode and the target label is not defined, create an undocumented label descriptor
- target_label = "Func_%x" % target_offset
- else:
- # anything that isn't a call or jump is a load-based command
- if target_label is None:
- # handle the case of a label for the current address not existing
- # first, check if this is a byte label
- if offset_is_used(byte_labels, local_target_offset):
- # fetch the already created byte label
- target_label = byte_labels[local_target_offset]["name"]
- # prevent this address from being treated as a data label
- if local_target_offset in data_tables.keys():
- data_tables[local_target_offset]["usage"] = 0
- else:
- data_tables[local_target_offset] = {}
- data_tables[local_target_offset]["name"] = target_label
- data_tables[local_target_offset]["usage"] = 0
- data_tables[local_target_offset]["definition"] = True
- elif local_target_offset >= 0x8000 or not parse_data:
- # do not create a label if this is a wram label or parse_data is not set
- target_label = "$%x" % local_target_offset
- elif local_target_offset in data_tables.keys():
- # if the target offset has been created as a data label, increase usage and use the already defined name
- data_tables[local_target_offset]["usage"] += 1
- target_label = data_tables[local_target_offset]["name"]
- else:
- # for now, treat this as a data label, but do not set it as used (will be replaced later if unused)
- target_label = data_label(target_offset)
- data_tables[local_target_offset] = {}
- data_tables[local_target_offset]["name"] = target_label
- data_tables[local_target_offset]["usage"] = 0
- data_tables[local_target_offset]["definition"] = False
- # format the label that was created into the opcode string
- opcode_output_str = opcode_str.format(target_label)
- else:
- # error checking
- raise ValueError("Invalid amount of args.")
- # append the formatted opcode output string to the output
- output += self.spacing + opcode_output_str + "\n" #+ " ; " + hex(offset)
- # increase the current byte number and offset by the amount of arguments plus 1 (opcode itself)
- current_byte_number += opcode_nargs + 1
- offset += opcode_nargs + 1
- else:
- # output a single lined db, using the current byte
- output += self.spacing + "db ${:02x}\n".format(opcode_byte) #+ " ; " + hex(offset)
- # manually increment offset and current byte number
- offset += 1
- current_byte_number += 1
- # stop treating the current code as data if we're parsing over a byte label
- if get_local_address(offset) in byte_labels.keys():
- is_data = False
- # update the local offset
- local_offset = get_local_address(offset)
- # stop processing regardless of function end if we've passed the stop offset and the hard stop (dry run) flag is set
- if hard_stop and offset >= stop_offset:
- break
- # check if this is the end of the function, or we're processing data
- elif (opcode_byte in unconditional_jumps + unconditional_returns) or is_data:
- # define data if it is located at the current offset
- if local_offset not in byte_labels.keys() and local_offset in data_tables.keys() and created_but_unused_labels_exist(data_tables) and parse_data:
- is_data = True
- #stop reading at a jump, relative jump or return
- elif all_byte_labels_are_defined(byte_labels) and (offset >= stop_offset or stop_offset_undefined):
- break
- # otherwise, add some spacing
- output += "\n"
- # before returning output, we need to clean up some things
- # first, clean up on unused byte labels
- for label_line in byte_labels.values():
- if label_line["usage"] == 0:
- output = output.replace((label_line["name"] + "\n"), "")
- # clean up on unused data labels
- # this is slightly trickier to do as arguments for two byte variables use data labels
- # create a list of the output lines including the newlines
- output_lines = [e+"\n" for e in output.split("\n") if e != ""]
- # go through each label
- for label_addr in data_tables.keys():
- # get the label dict
- label_line = data_tables[label_addr]
- # check if this label is unused
- if label_line["usage"] == 0:
- # get label name
- label_name = label_line["name"]
- # loop over all output lines
- for i, line in enumerate(output_lines):
- if line.startswith(label_name):
- # remove line if it starts with the current label
- output_lines.pop(i)
- elif label_name in line:
- # if the label is used in a load-based opcode, replace it with the raw hex reference
- output_lines[i] = output_lines[i].replace(label_name, "$%x" % get_local_address(label_addr))
- # convert the modified list of lines into a string
- output = "".join(output_lines)
- # tone down excessive spacing
- output = output.replace("\n\n\n","\n\n")
- # add the offset of the final location
- if include_last_address:
- output += "; " + hex(offset)
- return [output, offset, stop_offset, byte_labels, data_tables]
- def get_raw_addr(addr):
- if addr:
- if ":" in addr:
- addr = addr.split(":")
- addr = int(addr[0], 16)*0x4000+(int(addr[1], 16)%0x4000)
- else:
- label_addr = disasm.find_address_from_label(addr)
- if label_addr:
- addr = label_addr
- else:
- addr = int(addr, 16)
- return addr
- if __name__ == "__main__":
- # argument parser
- ap = argparse.ArgumentParser()
- ap.add_argument("-r", dest="rom", default="baserom.gbc")
- ap.add_argument("-o", dest="filename", default="gbz80disasm_output.asm")
- ap.add_argument("-s", dest="symfile")
- ap.add_argument("-dp", "--use-disasm-path", dest="use_disasm_path", action="store_true")
- ap.add_argument("-q", "--quiet", dest="quiet", action="store_true")
- ap.add_argument("-nw", "--no-write", dest="no_write", action="store_true")
- ap.add_argument("-d", "--dry-run", dest="dry_run", action="store_true")
- ap.add_argument("-pd", "--parse_data", dest="parse_data", action="store_true")
- ap.add_argument('offset')
- ap.add_argument('end', nargs='?')
- args = ap.parse_args()
- # if symfile is unspecified, use the rom name as the symfile name
- if args.symfile is None:
- args.symfile = args.rom.split(".")[0] + ".sym"
- # flag to determine whether to use the extras submodule path for labels/constants or the disassembly path
- if args.use_disasm_path:
- conf = configuration.Config(path=os.path.abspath(os.path.join(os.getcwd(),"../..")))
- else:
- conf = configuration.Config()
- # initialize disassembler
- disasm = Disassembler(conf)
- disasm.initialize(args.rom, args.symfile)
- # get global address of the start and stop offsets
- start_addr = get_raw_addr(args.offset)
- stop_addr = get_raw_addr(args.end)
- # run the disassembler and return the output
- output = disasm.output_bank_opcodes(start_addr,stop_addr,hard_stop=args.dry_run,parse_data=args.parse_data)[0]
- # suppress output if quiet flag is set
- if not args.quiet:
- print output
- # only write to the output file if the no write flag is unset
- if not args.no_write:
- with open(args.filename, "w") as f:
- f.write(output)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement