diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/disasm_coverage.py | 78 | ||||
-rw-r--r-- | tools/dump_names.py | 52 | ||||
-rw-r--r-- | tools/dump_text.py | 298 | ||||
-rw-r--r-- | tools/mapreader.py | 171 | ||||
-rw-r--r-- | tools/read_charmap.py | 42 | ||||
-rw-r--r-- | tools/tests/README.txt | 1 | ||||
-rw-r--r-- | tools/tests/charmap.asm | 293 | ||||
-rw-r--r-- | tools/tests/dump_test.cc.txt | 26 | ||||
-rw-r--r-- | tools/tests/dump_test.cc_endless.txt | 33 | ||||
-rw-r--r-- | tools/tests/dump_test.cc_tc.txt | 27 | ||||
-rw-r--r-- | tools/tests/dump_test.cc_tc_endless.txt | 35 | ||||
-rw-r--r-- | tools/tests/dump_test.endless.txt | 9 | ||||
-rw-r--r-- | tools/tests/dump_test.tc.txt | 4 | ||||
-rw-r--r-- | tools/tests/dump_test.tc_endless.txt | 11 | ||||
-rw-r--r-- | tools/tests/dump_test.txt | 3 | ||||
-rw-r--r-- | tools/tests/dump_text_test.bin | bin | 0 -> 69 bytes |
16 files changed, 997 insertions, 86 deletions
diff --git a/tools/disasm_coverage.py b/tools/disasm_coverage.py new file mode 100644 index 0000000..0b19ab0 --- /dev/null +++ b/tools/disasm_coverage.py @@ -0,0 +1,78 @@ +# #!/usr/bin/env python3 +# coding: utf-8 + +from __future__ import division + +import os +import sys +import argparse +import png +from mapreader import MapReader +from colorsys import hls_to_rgb + +if __name__ == "__main__": + # argument parser + ap = argparse.ArgumentParser() + ap.add_argument("-o", dest="filename", default="coverage.png") + ap.add_argument("-s", dest="statsname", default="coverage.log") + ap.add_argument("-m", dest="mapfile", required=True) + ap.add_argument("-b", dest="num_banks", required=True, type=lambda x: int(x, 0)) + args = ap.parse_args() + + bank_mask = 0x3FFF + bank_size = 0x4000 # bytes + width = 256 # pixels per row + bpp = 8 # bytes per pixel + + rom_size = args.num_banks * bank_size # bytes + height = (args.num_banks * bank_size + (width * bpp - 1)) // (width * bpp) # pixels + rows_per_bank = bank_size // (width * bpp) + + r = MapReader() + with open(args.mapfile, 'r') as f: + l = f.readlines() + r.read_map_data(l) + + hit_data = [[0] * width for _ in range(height)] + default_bank_data = {'sections': [], 'used': 0, 'slack': bank_size} + for bank in range(args.num_banks): + data = r.bank_data['ROM Bank'].get(bank, default_bank_data) + for s in data['sections']: + beg = (s['beg'] & bank_mask) + bank * bank_size + end = (s['end'] & bank_mask) + bank * bank_size + y_beg = beg // (width * bpp) + x_beg = (beg % (width * bpp)) // bpp + y_end = end // (width * bpp) + x_end = (end % (width * bpp)) // bpp + #print('beg {0} end {1}: {2}/{3} -- {4}/{5}'.format(beg, end, y_beg, x_beg, y_end, x_end)) + # special case y_beg/x_beg and y_end/x_end + if (y_beg == y_end and x_beg == x_end): + hit_data[y_beg][x_beg] += end - beg + 1 + else: + hit_data[y_beg][x_beg] += bpp - ((beg % (width * bpp)) - x_beg * bpp) + hit_data[y_end][x_end] += ((end % (width * bpp)) - x_end * bpp + 1) + # regular case + for y in range(y_beg, y_end + 1): + x_line_beg = 0 if y_beg != y else x_beg + 1 + x_line_end = width - 1 if y_end != y else x_end - 1 + for x in range(x_line_beg, x_line_end + 1): + hit_data[y][x] += bpp + + with open(args.statsname, 'w') as stats: + # TODO: write stats + pass + + png_data = [] + for i, row in enumerate(hit_data): + bank = i // rows_per_bank + hue = 0 if bank % 2 else 120 + row_png_data = () + for col in row: + hls = (hue/360.0, 1.0 - (col/bpp * (100 - 15))/100.0, 1.0) + rgb = tuple(255 * x for x in hls_to_rgb(*hls)) + row_png_data += rgb + png_data.append(row_png_data) + + with open(args.filename, 'wb') as f: + w = png.Writer(width, height) + w.write(f, png_data) diff --git a/tools/dump_names.py b/tools/dump_names.py index bd2078e..81f357a 100644 --- a/tools/dump_names.py +++ b/tools/dump_names.py @@ -1,52 +1,30 @@ #!/usr/bin/env python import sys, os, io +from read_charmap import read_charmap -def parse_int(s): - s = s.strip() - if s.startswith('$'): - return int(s[1:], 16) - if s.startswith('%'): - return int(s[1:], 2) - return int(s) +def calc_bank(p): + return p // 0x4000 -def parse_string(s): - # assumes strings are literal, no STRCAT() etc - return s.strip('" ') +def calc_address(p): + b = calc_bank(p) + o = b * 0x4000 + return 0x4000 + p - o -def strip_comment(s): - # assumes ";" is not in the charmap - return s.split(';')[0].rstrip() +def get_sym_loc(p): + b, a = calc_bank(p), calc_address(p) + return '%02x:%04x' % (b, a) def get_project_dir(): script_path = os.path.realpath(__file__) script_dir = os.path.dirname(script_path) project_dir = os.path.join(script_dir, '..') return os.path.normpath(project_dir) - -def get_charmap_path(): - project_dir = get_project_dir() - return os.path.join(project_dir, 'charmap.asm') - + def get_baserom_path(): project_dir = get_project_dir() return os.path.join(project_dir, 'baserom.gb') -def read_charmap(): - charmap_path = get_charmap_path() - charmap = {} - with io.open(charmap_path, 'r', encoding='utf-8') as f: - lines = f.readlines() - for line in lines: - line = strip_comment(line).lstrip() - if not line.startswith('charmap '): - continue - char, value = line[len('charmap '):].rsplit(',', 1) - char = parse_string(char) - value = parse_int(value) - charmap[value] = char - return charmap - def dump_strings(data): charmap = read_charmap() ss = [] @@ -84,9 +62,7 @@ def read_data(bank, address, n): data.append(v) return data -#data = read_data(0x0E, 0x4D90, 64) # TrainerClassNames -#data = read_data(0x01, 0x6FEC, 255) # ItemNames -#data = read_data(0x10, 0x52A1, 251) # MoveNames -data = read_data(0x14, 0x6D75, 251) # PokemonNames - +p = 0xfcaaf # Landmarks +print get_sym_loc(p) +data = read_data(calc_bank(p), calc_address(p), 45) dump_strings(data) diff --git a/tools/dump_text.py b/tools/dump_text.py index 721d953..057de04 100644 --- a/tools/dump_text.py +++ b/tools/dump_text.py @@ -1,50 +1,252 @@ #!/usr/bin/python3 -from sys import argv, stdout - - -char_table = [ - "?", "イ゛", "ヴ", "エ゛", "オ゛", "ガ", "ギ", "グ", "ゲ", "ゴ", "ザ", "ジ", "ズ", "ゼ", "ゾ", "ダ", - "ヂ", "ヅ", "デ", "ド", "<PLAY_G>", "<0x15>", "<0x16>", "ネ゛", "<JP_18>", "バ", "ビ", "ブ", "ボ", "<NI>", "<TTE>", "<WO>", - "ィ゛", "あ゛", "<TA!>", "<KOUGEKI>", "<WA>", "<NO>", "が", "ぎ", "ぐ", "げ", "ご", "ざ", "じ", "ず", "ぜ", "ぞ", - "だ", "ぢ", "づ", "で", "ど", "<ROUTE>", "<WATASHI>", "<KOKO_WA>", "<RED>", "<GREEN>", "ば", "び", "ぶ", "べ", "ぼ", "<ENEMY>", - "パ", "ピ", "プ", "ポ", "ぱ", "ぴ", "ぷ", "ぺ", "ぽ", "<MOM>", "<GA>", "<_CONT>", "<SCROLL>", "も゜", "<NEXT>", "<LINE>", - "@", "<PARA>", "<PLAYER>", "<RIVAL>", "#", "<CONT>", "<……>", "<DONE>", "<PROMPT>", "<TARGET>", "<USER>", "<PC>", "<TM>", "<TRAINER>", "<ROCKET>", "<DEXEND>", - "■", "▲", "☎", "D", "E", "F", "G", "H", "I", "V", "S", "L", "M", ":", "ぃ", "ぅ", - "「", "」", "『", "』", "・", "<・・・>", "ぁ", "ぇ", "ぉ", "┌", "─", "┐", "│", "└", "┘", " ", - "ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "サ", "シ", "ス", "セ", "ソ", "タ", - "チ", "ツ", "テ", "ト", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "ヒ", "フ", "ホ", "マ", "ミ", "ム", - "メ", "モ", "ヤ", "ユ", "ヨ", "ラ", "ル", "レ", "ロ", "ワ", "ヲ", "ン", "ッ", "ャ", "ュ", "ョ", - "ィ", "あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ", - "た", "ち", "つ", "て", "と", "な", "に", "ぬ", "ね", "の", "は", "ひ", "ふ", "へ", "ほ", "ま", - "み", "む", "め", "も", "や", "ゆ", "よ", "ら", "り", "る", "れ", "ろ", "わ", "を", "ん", "っ", - "ゃ", "ゅ", "ょ", "ー", "゜", "゛", "?", "!", "。", "ァ", "ゥ", "ェ", "▷", "▶", "▼", "♂", - "円", "×", ".", "/", "ォ", "♀", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" -] - -if len(argv) != 4: - print(f"Usage: {argv[0]} path/to/ROM.gb start_offset end_offset\noffsets are in the form bank:address (both hex), and end_offset is *not* included.") - exit(1) - - -try: - start_bank,start_addr = [ int(s, 16) for s in argv[2].split(':') ] - end_bank, end_addr = [ int(s, 16) for s in argv[3].split(':') ] - if start_bank != 0: - start_addr += (start_bank - 1) * 0x4000 - if end_bank != 0: - end_addr += (end_bank - 1) * 0x4000 -except Error: - print("Please specify valid offsets (bank:address, both hex)") - exit(1) - - -with open(argv[1], "rb") as f: - f.seek(start_addr) - - string = "" - while start_addr < end_addr: - string += char_table[ int.from_bytes(f.read(1), "little") ] - start_addr += 1 - - stdout.buffer.write( f"db \"{string}\"\n".encode('utf-8') ) +import argparse +import sys +from read_charmap import read_charmap + +charmap = {} + +bank_size = 0x4000 + +textcodes = { + '<NEXT>' : {'label': 'next', 'fin': False}, + '<LINE>' : {'label': 'line', 'fin': False}, + '<PARA>' : {'label': 'para', 'fin': False}, + '<CONT>' : {'label': 'cont', 'fin': False}, + '<DONE>' : {'label': 'done', 'fin': True}, + '<PROMPT>': {'label': 'prompt', 'fin': True}, +} + +# codes that end text control code 0x00 +# True if this code exits control code parsing as well +end_codes = { + '@' : False, + '<DONE>' : True, + '<PROMPT>': True, +} + +def conv_address(x): + if ':' in x: + bank, addr = [ int(s, 16) for s in x.split(':') ] + if (addr < bank_size and bank != 0): + raise argparse.ArgumentTypeError('Illegal ROM bank 0x00 address {0:02X}:{1:04X}. ' + 'Bank 0x{0:02X} must be 0x00.'.format(bank, addr)) + elif (addr >= bank_size and bank == 0): + raise argparse.ArgumentTypeError('Illegal ROM bank 0x00 address {0:02X}:{1:04X}. ' + 'Address 0x{1:04X} > 0x{2:04X}.'.format(bank, addr, bank_size - 1)) + elif (addr >= 2*bank_size): + raise argparse.ArgumentTypeError('Illegal ROM bank address {0:02X}:{1:04X}. ' + 'Address 0x{1:04X} > 0x{2:04X}.'.format(bank, addr, 2*bank_size - 1)) + return bank * 0x4000 + (addr & 0x3fff) + return int(x, 0) + +def addr2gb(addr): + bank = addr // bank_size + offset = addr % bank_size + if (bank > 0): + offset += bank_size + return bank, offset + +def transform_char(char, do_transform, is_begin): + result = '' + if (do_transform and char in textcodes): + replace = textcodes[char] + if (not is_begin): + result += '"\n' + result += replace['label'] + if (not replace['fin']): + result += ' "' + return replace['fin'], result + else: + if (is_begin): + result += '"' + return False, (result + char) + +def dump_asm(data): + + result = 'start_asm ; text dumper cannot dump asm\n' + result += ' ; Try dumping asm from the following offset and\n' + result += ' ; then continue dumping text in control mode.\n' + return True, result + +def dump_control(data, label, signature): + + lengths = {'b': 1, 'n': 1, 'w': 2} + + required_bytes = sum([lengths.get(c, 0) for c in signature]) + + if (data['len'] - data['offset'] < required_bytes): + #silently drop split control code + return True, '' + + result = '' + + for c in signature: + if result != '': + result += ', ' + if c == 'b': + byte = data['bytes'][data['offset']] + data['offset'] += 1 + result += '${0:02x}'.format(byte) + elif c == 'n': + byte = data['bytes'][data['offset']] + data['offset'] += 1 + result += '${0:01x}, ${1:01x}'.format(byte >> 4, byte & 0x0f) + elif c == 'w': + word = data['bytes'][data['offset']] + data['offset'] += 1 + word |= data['bytes'][data['offset']] << 8 + data['offset'] += 1 + result += '${0:02x}'.format(word) + else: + raise ValueError('Unknown signature char in {0:s}\'s signature "{1:s}".'.format(label, signature)) + + if (result != ''): + result = ' ' + result + return False, label + result + +def dump_text(data): + + string = '' + exit_control = False + done = False + while (not done): + + byte = data['bytes'][data['offset']] + data['offset'] += 1 + + char = charmap[byte] + fin, tchar = transform_char(char, data['textmode'], string == '') + string += tchar + if (char in end_codes): + done = True + exit_control = end_codes[char] + # end string if textmode didn't do it + if not data['textmode'] or not fin: + string += '"' + if (data['offset'] >= data['len']): + done = True + string += '"' + + return exit_control, string + +def dump_text_control(data): + + res, text = dump_text(data) + return res, 'text ' + text + +control_codes = { + 0x00: dump_text_control, + 0x01: lambda data: dump_control(data, 'text_from_ram' , 'w' ), + 0x02: lambda data: dump_control(data, 'text_bcd' , 'wb' ), + 0x03: lambda data: dump_control(data, 'text_move' , 'w' ), + 0x04: lambda data: dump_control(data, 'text_box' , 'wbb' ), + 0x05: lambda data: dump_control(data, 'text_low' , '' ), + 0x06: lambda data: dump_control(data, 'text_waitbutton' , '' ), + 0x07: lambda data: dump_control(data, 'text_scroll' , '' ), + 0x08: dump_asm, + 0x09: lambda data: dump_control(data, 'deciram' , 'wn' ), + 0x0A: lambda data: dump_control(data, 'text_exit' , '' ), + 0x0B: lambda data: dump_control(data, 'sound_dex_fanfare_50_79' , '' ), + 0x0C: lambda data: dump_control(data, 'text_dots' , 'b' ), + 0x0D: lambda data: dump_control(data, 'link_wait_button' , '' ), + 0x0E: lambda data: dump_control(data, 'sound_dex_fanfare_20_49' , '' ), + 0x0F: lambda data: dump_control(data, 'sound_item' , '' ), + 0x10: lambda data: dump_control(data, 'sound_caught_mon' , '' ), + 0x11: lambda data: dump_control(data, 'sound_dex_fanfare_80_109', '' ), + 0x12: lambda data: dump_control(data, 'sound_fanfare' , '' ), + 0x13: lambda data: dump_control(data, 'sound_slot_machine_start', '' ), + 0x14: lambda data: dump_control(data, 'cry_nidorina' , '' ), + 0x15: lambda data: dump_control(data, 'cry_pigeot' , '' ), + 0x16: lambda data: dump_control(data, 'cry_jugon' , '' ), + 0x50: lambda data: (True, 'text_end\n'), +} + +def print_location(data): + return '.loc_{0:04X}:\n'.format(data['offset']) + +if __name__ == '__main__': + # argument parser + ap = argparse.ArgumentParser() + ap.add_argument('--cc', dest='ccmode', action='store_true', + help='dump in control code mode, implies text code macro mode' + ) + ap.add_argument('--endless', dest='endless', action='store_true', + help='continue dumping even if string end text code was reached' + ) + ap.add_argument('--tc', dest='textmode', action='store_true', + help='dump text codes (line breaks, prompt etc) as macros instead of inline text' + ) + ap.add_argument('-o', dest='outfile', default=sys.stdout, help='output file name') + ap.add_argument('-m', dest='charmap', default='../charmap.asm', help='charmap file name') + ap.add_argument('rom', help='path to ROM') + ap.add_argument('start', help='start offset', type=conv_address) + ap.add_argument('end', help='end offset', type=conv_address, nargs='?') + + args = ap.parse_args() + romname = args.rom + start_addr = args.start + end_addr = start_addr + bank_size if args.end is None else args.end + ccmode = args.ccmode + endless = args.endless + outfile = args.outfile + charmap = read_charmap(args.charmap) + + if (end_addr < start_addr): + print('End address 0x{0:06X} ({1:02X}:{2:04X}) is before ' + 'start address 0x{3:06X} ({4:02X}:{5:04X}).'.format( + end_addr, + *addr2gb(end_addr), + start_addr, + *addr2gb(start_addr) + ), + file=sys.stderr + ) + sys.exit(-1) + + bank_addr = start_addr & (~(bank_size - 1)) + offset = start_addr - bank_addr + end_offset = end_addr - bank_addr + + with open(romname, 'rb') as f: + f.seek(bank_addr) + bank_data = f.read(bank_size) + + data = {'offset': offset, 'bytes': bank_data, 'len': min(end_offset, len(bank_data)), 'textmode': args.textmode} + + with open(outfile, 'wb') if outfile != sys.stdout else outfile.buffer as f: + string = print_location(data) + while(data['offset'] < data['len']): + if (not ccmode): + # dumb mode + _, text = dump_text(data) + # start with db unless starting with a control code + # in textmode + if text[0] == '"' and data['textmode']: + string += '\tdb ' + elif text[0] == '"': + string += '\tdb ' + string += text.replace('\n', '\n\t') + string += '\n{0:s}'.format(print_location(data)) + if (not endless): + break + else: + # control code mode + control_byte = data['bytes'][data['offset']] + data['offset'] += 1 + + if (control_byte in control_codes): + res, text = control_codes[control_byte](data) + string += '\t' + text.replace('\n', '\n\t') + string += '\n' + if (res): + string += print_location(data) + # exit out of control code parsing + if (res and not endless): + break + else: + print('Encountered unknown control code 0x{0:02X}. Abort...'.format(control_byte), file=sys.stderr) + break + + f.write(string.encode('utf-8')) + f.close() diff --git a/tools/mapreader.py b/tools/mapreader.py new file mode 100644 index 0000000..41ad935 --- /dev/null +++ b/tools/mapreader.py @@ -0,0 +1,171 @@ +# #!/usr/bin/env python3 +# coding: utf-8 + +import re + +class MapReader: + + # {'ROM Bank': { 0: { 'sections': [ { 'beg': 1234, + # 'end': 5678, + # 'name': 'Section001', + # 'symbols': [ { 'symbol': 'Function1234', + # 'address: 1234, + # }, + # ] + # }, + # ], + # 'used': 1234, + # 'slack': 4567, + # }, + # }, + # 'OAM': { 'sections': [ { 'beg': 1234, + # 'end': 5678, + # 'name': 'Section002', + # 'symbols': [ { 'symbol': 'Data1234', + # 'address: 1234, + # }, + # ] + # }, + # ], + # 'used': 1234, + # 'slack': 4567, + # }, + # } + # + bank_data = {} + + bank_types = { + 'HRAM' : { 'size': 0x80, 'banked': False, }, + 'OAM' : { 'size': 0xA0, 'banked': False, }, + 'ROM Bank' : { 'size': 0x4000, 'banked': True, }, + 'SRAM Bank': { 'size': 0x2000, 'banked': True, }, + 'VRAM Bank': { 'size': 0x1000, 'banked': True, }, + 'WRAM Bank': { 'size': 0x2000, 'banked': True, }, + } + + # FSM states + INIT, BANK, SECTION = range(3) + + # $506D-$519A ($012E bytes) ["Type Matchups"] + section_header_regex = re.compile('\$([0-9A-Fa-f]{4})-\$([0-9A-Fa-f]{4}) \(.*\) \["(.*)"\]') + # $506D = TypeMatchups + section_data_regex = re.compile('\$([0-9A-Fa-f]{4}) = (.*)') + # $3ED2 bytes + slack_regex = re.compile('\$([0-9A-Fa-f]{4}) bytes') + + def __init__(self, *args, **kwargs): + self.__dict__.update(kwargs) + + def _parse_init(self, line): + + line = line.split(':', 1)[0] + parts = line.split(' #', 1) + + if (parts[0] in self.bank_types): + self._cur_bank_name = parts[0] + self._cur_bank_type = self.bank_types[self._cur_bank_name] + if (self._cur_bank_type['banked'] and len(parts) > 1): + parts[1] = parts[1].split(':', 1)[0] + parts[1] = parts[1].split(' ', 1)[0] + self._cur_bank = int(parts[1], 10) + if self._cur_bank_name not in self.bank_data: + self.bank_data[self._cur_bank_name] = {} + if self._cur_bank_type['banked']: + if self._cur_bank not in self.bank_data[self._cur_bank_name]: + self.bank_data[self._cur_bank_name][self._cur_bank] = {} + self._cur_data = self.bank_data[self._cur_bank_name][self._cur_bank] + else: + self._cur_data = self.bank_data[self._cur_bank_name] + + if ({} == self._cur_data): + self._cur_data['sections'] = [] + self._cur_data['used'] = 0 + self._cur_data['slack'] = self._cur_bank_type['size'] + return True + + return False + + def _parse_section_header(self, header): + + section_data = self.section_header_regex.match(header) + if section_data is not None: + beg = int(section_data.group(1), 16) + end = int(section_data.group(2), 16) + name = section_data.group(3) + self._cur_section = {'beg': beg, 'end': end, 'name': name, 'symbols': []} + self._cur_data['sections'].append(self._cur_section) + return True + return False + + def _parse_slack(self, data): + + slack_data = self.slack_regex.match(data) + slack_bytes = int(slack_data.group(1), 16) + self._cur_data['slack'] = slack_bytes + + used_bytes = 0 + + for s in self._cur_data['sections']: + used_bytes += s['end'] - s['beg'] + 1 + + self._cur_data['used'] = used_bytes + + def read_map_data(self, map): + + if type(map) is str: + map = map.split('\n') + + self._state = MapReader.INIT + self._cur_bank_name = '' + self._cur_bank_type = {} + self._cur_bank = 0 + self._cur_data = {} + + for line in map: + + line = line.rstrip() + if (MapReader.INIT == self._state): + + if (self._parse_init(line)): + self._state = MapReader.BANK + + elif (MapReader.BANK == self._state or MapReader.SECTION == self._state): + + if ('' == line): + self._state = MapReader.INIT + else: + + line = line.lstrip() + parts = line.split(': ', 1) + + if (MapReader.SECTION == self._state): + section_data = self.section_data_regex.match(parts[0]) + if section_data is not None: + address = int(section_data.group(1), 16) + name = section_data.group(2) + self._cur_section['symbols'].append({'name': name, 'address': address}) + continue + + if ('SECTION' == parts[0]): + if (self._parse_section_header(parts[1])): + self._state = MapReader.SECTION + elif ('SLACK' == parts[0]): + self._parse_slack(parts[1]) + self._state = MapReader.INIT + elif ('EMPTY' == parts[0]): + self._cur_data = {'sections': [], 'used': 0, 'slack': self._cur_bank_type['size']} + self._state = MapReader.INIT + + else: + pass + + for k, v in self.bank_data.items(): + if (self.bank_types[k]['banked']): + for _, vv in v.items(): + vv['sections'].sort(key=lambda x: x['beg']) + for vvv in vv['sections']: + vvv['symbols'].sort(key=lambda x: x['address']) + else: + v['sections'].sort(key=lambda x: x['beg']) + for vv in v['sections']: + vv['symbols'].sort(key=lambda x: x['address'])
\ No newline at end of file diff --git a/tools/read_charmap.py b/tools/read_charmap.py new file mode 100644 index 0000000..af290c3 --- /dev/null +++ b/tools/read_charmap.py @@ -0,0 +1,42 @@ +import os, io +from re import compile +from sys import stderr + +charmap_regex = compile('[ \t]*charmap[ \t]+"(.*?)",[ \t]*(\$[0-9A-Fa-f]{2}|%[01]{8}|[0-9]{3})') +# A charmap line is +# [ \t]* - zero or more space chars +# charmap - literal charmap +# [ \t]+ - one or more space chars +# "(.*?)" - a lazily-matched text identifier in quotes +# , - literal comma +# [ \t]* - zero or more space chars +# ( - either of +# \$[0-9A-Fa-f]{2} - two hexadecimal digits preceeded by literal $ +# %[01]{8} - eight dual digits preceeded by literal % +# [0-9]{3} - three decimal digits +# ) + +def parse_int(s): + # assumes integers are literal; no +-*/, etc + s = s.strip() + if s.startswith('$'): + return int(s[1:], 16) + if s.startswith('%'): + return int(s[1:], 2) + return int(s) + +def read_charmap(charmap_path): + charmap = {} + with io.open(charmap_path, 'r', encoding='utf-8') as f: + lines = f.readlines() + for line in lines: + m = charmap_regex.match(line) + if m is None: + continue + char = m.group(1) + value = parse_int(m.group(2)) + if value in charmap: + print('Value {0:s} already in charmap, dropping it in favor of first charmap entry'.format(m.group(2))) + continue + charmap[value] = char + return charmap diff --git a/tools/tests/README.txt b/tools/tests/README.txt new file mode 100644 index 0000000..750482e --- /dev/null +++ b/tools/tests/README.txt @@ -0,0 +1 @@ +python dump_text.py -o dump_test.txt -m charmap.asm dump_text_test.bin 00:0000 diff --git a/tools/tests/charmap.asm b/tools/tests/charmap.asm new file mode 100644 index 0000000..228bb6b --- /dev/null +++ b/tools/tests/charmap.asm @@ -0,0 +1,293 @@ + charmap "<NULL>", $00 + + charmap "イ゛", $01 + charmap "ヴ", $02 + charmap "エ゛", $03 + charmap "オ゛", $04 + + charmap "ガ", $05 + charmap "ギ", $06 + charmap "グ", $07 + charmap "ゲ", $08 + charmap "ゴ", $09 + charmap "ザ", $0a + charmap "ジ", $0b + charmap "ズ", $0c + charmap "ゼ", $0d + charmap "ゾ", $0e + charmap "ダ", $0f + charmap "ヂ", $10 + charmap "ヅ", $11 + charmap "デ", $12 + charmap "ド", $13 + + charmap "<PLAY_G>", $14 ; "<PLAYER>くん" or "<PLAYER>ちゃん" + + charmap "<15>", $15 ; nothing + charmap "<16>", $16 ; nothing + + charmap "ネ゛", $17 + charmap "ノ゛", $18 + + charmap "バ", $19 + charmap "ビ", $1a + charmap "ブ", $1b + charmap "ボ", $1c + + charmap "<NI>", $1d ; "に " + charmap "<TTE>", $1e ; "って" + charmap "<WO>", $1f ; "を " + + charmap "ィ゛", $20 + charmap "あ゛", $21 + + charmap "<TA!>", $22 ; "た!" + charmap "<KOUGEKI>", $23 ; "こうげき" + charmap "<WA>", $24 ; "は " + charmap "<NO>", $25 ; "の " + + charmap "が", $26 + charmap "ぎ", $27 + charmap "ぐ", $28 + charmap "げ", $29 + charmap "ご", $2a + charmap "ざ", $2b + charmap "じ", $2c + charmap "ず", $2d + charmap "ぜ", $2e + charmap "ぞ", $2f + charmap "だ", $30 + charmap "ぢ", $31 + charmap "づ", $32 + charmap "で", $33 + charmap "ど", $34 + + charmap "<ROUTE>", $35 ; "ばん どうろ" + charmap "<WATASHI>", $36 ; "わたし" + charmap "<KOKO_WA>", $37 ; "ここは" + charmap "<RED>", $38 ; wRedsName + charmap "<GREEN>", $39 ; wGreensName + + charmap "ば", $3a + charmap "び", $3b + charmap "ぶ", $3c + charmap "べ", $3d + charmap "ぼ", $3e + + charmap "<ENEMY>", $3f + + charmap "パ", $40 + charmap "ピ", $41 + charmap "プ", $42 + charmap "ポ", $43 + charmap "ぱ", $44 + charmap "ぴ", $45 + charmap "ぷ", $46 + charmap "ぺ", $47 + charmap "ぽ", $48 + + charmap "<MOM>", $49 ; wMomsName + charmap "<GA>", $4a ; "が " + charmap "<_CONT>", $4b ; implements "<CONT>" + charmap "<SCROLL>", $4c + + charmap "も゜", $4d + + charmap "<NEXT>", $4e + charmap "<LINE>", $4f + charmap "@", $50 ; string terminator + charmap "<PARA>", $51 + charmap "<PLAYER>", $52 ; wPlayerName + charmap "<RIVAL>", $53 ; wRivalName + charmap "#", $54 ; "POKé" + charmap "<CONT>", $55 + charmap "<……>", $56 ; "……" + charmap "<DONE>", $57 + charmap "<PROMPT>", $58 + charmap "<TARGET>", $59 + charmap "<USER>", $5a + charmap "<PC>", $5b ; "PC" + charmap "<TM>", $5c ; "TM" + charmap "<TRAINER>", $5d ; "TRAINER" + charmap "<ROCKET>", $5e ; "ROCKET" + charmap "<DEXEND>", $5f + + charmap "■", $60 + charmap "▲", $61 + charmap "☎", $62 + + charmap "D", $63 + charmap "E", $64 + charmap "F", $65 + charmap "G", $66 + charmap "H", $67 + charmap "I", $68 + charmap "V", $69 + charmap "S", $6a + charmap "L", $6b + charmap "M", $6c + + charmap ":", $6d + + charmap "ぃ", $6e + charmap "ぅ", $6f + + charmap "「", $70 + charmap "」", $71 + charmap "『", $72 + charmap "』", $73 + charmap "・", $74 + charmap "…", $75 + + charmap "ぁ", $76 + charmap "ぇ", $77 + charmap "ぉ", $78 + + charmap "┌", $79 + charmap "─", $7a + charmap "┐", $7b + charmap "│", $7c + charmap "└", $7d + charmap "┘", $7e + + charmap " ", $7f + + charmap "ア", $80 + charmap "イ", $81 + charmap "ウ", $82 + charmap "エ", $83 + charmap "オ", $84 + charmap "カ", $85 + charmap "キ", $86 + charmap "ク", $87 + charmap "ケ", $88 + charmap "コ", $89 + charmap "サ", $8a + charmap "シ", $8b + charmap "ス", $8c + charmap "セ", $8d + charmap "ソ", $8e + charmap "タ", $8f + charmap "チ", $90 + charmap "ツ", $91 + charmap "テ", $92 + charmap "ト", $93 + charmap "ナ", $94 + charmap "ニ", $95 + charmap "ヌ", $96 + charmap "ネ", $97 + charmap "ノ", $98 + charmap "ハ", $99 + charmap "ヒ", $9a + charmap "フ", $9b + charmap "ホ", $9c + charmap "マ", $9d + charmap "ミ", $9e + charmap "ム", $9f + charmap "メ", $a0 + charmap "モ", $a1 + charmap "ヤ", $a2 + charmap "ユ", $a3 + charmap "ヨ", $a4 + charmap "ラ", $a5 + charmap "ル", $a6 + charmap "レ", $a7 + charmap "ロ", $a8 + charmap "ワ", $a9 + charmap "ヲ", $aa + charmap "ン", $ab + + charmap "ッ", $ac + charmap "ャ", $ad + charmap "ュ", $ae + charmap "ョ", $af + charmap "ィ", $b0 + + charmap "あ", $b1 + charmap "い", $b2 + charmap "う", $b3 + charmap "え", $b4 + charmap "お", $b5 + charmap "か", $b6 + charmap "き", $b7 + charmap "く", $b8 + charmap "け", $b9 + charmap "こ", $ba + charmap "さ", $bb + charmap "し", $bc + charmap "す", $bd + charmap "せ", $be + charmap "そ", $bf + charmap "た", $c0 + charmap "ち", $c1 + charmap "つ", $c2 + charmap "て", $c3 + charmap "と", $c4 + charmap "な", $c5 + charmap "に", $c6 + charmap "ぬ", $c7 + charmap "ね", $c8 + charmap "の", $c9 + charmap "は", $ca + charmap "ひ", $cb + charmap "ふ", $cc + charmap "へ", $cd + charmap "ほ", $ce + charmap "ま", $cf + charmap "み", $d0 + charmap "む", $d1 + charmap "め", $d2 + charmap "も", $d3 + charmap "や", $d4 + charmap "ゆ", $d5 + charmap "よ", $d6 + charmap "ら", $d7 + charmap "り", $d8 + charmap "る", $d9 + charmap "れ", $da + charmap "ろ", $db + charmap "わ", $dc + charmap "を", $dd + charmap "ん", $de + + charmap "っ", $df + charmap "ゃ", $e0 + charmap "ゅ", $e1 + charmap "ょ", $e2 + + charmap "ー", $e3 + + charmap "゚", $e4 + charmap "゙", $e5 + + charmap "?", $e6 + charmap "!", $e7 + charmap "。", $e8 + + charmap "ァ", $e9 + charmap "ゥ", $ea + charmap "ェ", $eb + + charmap "▷", $ec + charmap "▶", $ed + charmap "▲", $ed + charmap "▼", $ee + charmap "♂", $ef + charmap "円", $f0 + charmap "×", $f1 + charmap ".", $f2 + charmap "/", $f3 + + charmap "ォ", $f4 + + charmap "♀", $f5 + charmap "0", $f6 + charmap "1", $f7 + charmap "2", $f8 + charmap "3", $f9 + charmap "4", $fa + charmap "5", $fb + charmap "6", $fc + charmap "7", $fd + charmap "8", $fe + charmap "9", $ff diff --git a/tools/tests/dump_test.cc.txt b/tools/tests/dump_test.cc.txt new file mode 100644 index 0000000..4dfd14a --- /dev/null +++ b/tools/tests/dump_test.cc.txt @@ -0,0 +1,26 @@ +.loc_0000: + text "ほんとにりセットしますか?<LINE>@" + text_from_ram $ce33 + text_bcd $ce34, $78 + text_move $ce35 + text_box $cc36, $08, $12 + text_low + text_waitbutton + text_scroll + deciram $cc37, $6, $5 + text_exit + sound_dex_fanfare_50_79 + text_dots $14 + link_wait_button + sound_dex_fanfare_20_49 + sound_item + sound_caught_mon + sound_dex_fanfare_80_109 + sound_fanfare + sound_slot_machine_start + cry_nidorina + cry_pigeot + cry_jugon + text_end + +.loc_0035: diff --git a/tools/tests/dump_test.cc_endless.txt b/tools/tests/dump_test.cc_endless.txt new file mode 100644 index 0000000..5c59830 --- /dev/null +++ b/tools/tests/dump_test.cc_endless.txt @@ -0,0 +1,33 @@ +.loc_0000: + text "ほんとにりセットしますか?<LINE>@" + text_from_ram $ce33 + text_bcd $ce34, $78 + text_move $ce35 + text_box $cc36, $08, $12 + text_low + text_waitbutton + text_scroll + deciram $cc37, $6, $5 + text_exit + sound_dex_fanfare_50_79 + text_dots $14 + link_wait_button + sound_dex_fanfare_20_49 + sound_item + sound_caught_mon + sound_dex_fanfare_80_109 + sound_fanfare + sound_slot_machine_start + cry_nidorina + cry_pigeot + cry_jugon + text_end + +.loc_0035: + text "ほんとにりセットしますか?<DONE>" +.loc_0044: + start_asm ; text dumper cannot dump asm + ; Try dumping asm from the following offset and + ; then continue dumping text in control mode. + +.loc_0045: diff --git a/tools/tests/dump_test.cc_tc.txt b/tools/tests/dump_test.cc_tc.txt new file mode 100644 index 0000000..e49a010 --- /dev/null +++ b/tools/tests/dump_test.cc_tc.txt @@ -0,0 +1,27 @@ +.loc_0000: + text "ほんとにりセットしますか?" + line "@" + text_from_ram $ce33 + text_bcd $ce34, $78 + text_move $ce35 + text_box $cc36, $08, $12 + text_low + text_waitbutton + text_scroll + deciram $cc37, $6, $5 + text_exit + sound_dex_fanfare_50_79 + text_dots $14 + link_wait_button + sound_dex_fanfare_20_49 + sound_item + sound_caught_mon + sound_dex_fanfare_80_109 + sound_fanfare + sound_slot_machine_start + cry_nidorina + cry_pigeot + cry_jugon + text_end + +.loc_0035: diff --git a/tools/tests/dump_test.cc_tc_endless.txt b/tools/tests/dump_test.cc_tc_endless.txt new file mode 100644 index 0000000..dbf8e8b --- /dev/null +++ b/tools/tests/dump_test.cc_tc_endless.txt @@ -0,0 +1,35 @@ +.loc_0000: + text "ほんとにりセットしますか?" + line "@" + text_from_ram $ce33 + text_bcd $ce34, $78 + text_move $ce35 + text_box $cc36, $08, $12 + text_low + text_waitbutton + text_scroll + deciram $cc37, $6, $5 + text_exit + sound_dex_fanfare_50_79 + text_dots $14 + link_wait_button + sound_dex_fanfare_20_49 + sound_item + sound_caught_mon + sound_dex_fanfare_80_109 + sound_fanfare + sound_slot_machine_start + cry_nidorina + cry_pigeot + cry_jugon + text_end + +.loc_0035: + text "ほんとにりセットしますか?" + done +.loc_0044: + start_asm ; text dumper cannot dump asm + ; Try dumping asm from the following offset and + ; then continue dumping text in control mode. + +.loc_0045: diff --git a/tools/tests/dump_test.endless.txt b/tools/tests/dump_test.endless.txt new file mode 100644 index 0000000..fbbdeef --- /dev/null +++ b/tools/tests/dump_test.endless.txt @@ -0,0 +1,9 @@ +.loc_0000: + db "<NULL>ほんとにりセットしますか?<LINE>@" +.loc_0010: + db "イ゛でほヴどほぉエ゛<ROUTE>ほオ゛<WATASHI>ふゲデガギグゴ<KOKO_WA>ふFザジズ<PLAY_G>ゼゾダヂヅデド<PLAY_G><15><16>@" +.loc_0035: + db "<NULL>ほんとにりセットしますか?<DONE>" +.loc_0044: + db "ゲ" +.loc_0045: diff --git a/tools/tests/dump_test.tc.txt b/tools/tests/dump_test.tc.txt new file mode 100644 index 0000000..82c276d --- /dev/null +++ b/tools/tests/dump_test.tc.txt @@ -0,0 +1,4 @@ +.loc_0000: + db "<NULL>ほんとにりセットしますか?" + line "@" +.loc_0010: diff --git a/tools/tests/dump_test.tc_endless.txt b/tools/tests/dump_test.tc_endless.txt new file mode 100644 index 0000000..37bdda0 --- /dev/null +++ b/tools/tests/dump_test.tc_endless.txt @@ -0,0 +1,11 @@ +.loc_0000: + db "<NULL>ほんとにりセットしますか?" + line "@" +.loc_0010: + db "イ゛でほヴどほぉエ゛<ROUTE>ほオ゛<WATASHI>ふゲデガギグゴ<KOKO_WA>ふFザジズ<PLAY_G>ゼゾダヂヅデド<PLAY_G><15><16>@" +.loc_0035: + db "<NULL>ほんとにりセットしますか?" + done +.loc_0044: + db "ゲ" +.loc_0045: diff --git a/tools/tests/dump_test.txt b/tools/tests/dump_test.txt new file mode 100644 index 0000000..a181744 --- /dev/null +++ b/tools/tests/dump_test.txt @@ -0,0 +1,3 @@ +.loc_0000: + db "<NULL>ほんとにりセットしますか?<LINE>@" +.loc_0010: diff --git a/tools/tests/dump_text_test.bin b/tools/tests/dump_text_test.bin Binary files differnew file mode 100644 index 0000000..778a446 --- /dev/null +++ b/tools/tests/dump_text_test.bin |