PikalaxALT icon

gbz80disasm.py-v1.6.0-298-g0e17989

PikalaxALT | PRO | 06/04/16 08:35:19 PM UTC | 0 ⭐ | 615 👁️ | Never ⏰ | []
Python |

33.1 KB

|

None

|

0 👍

/

0 👎

# -*- 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)

Comments