From 8eba39dd52a2a731225067fa4c3ae42cfb5b232f Mon Sep 17 00:00:00 2001 From: Rangi Date: Wed, 11 Mar 2020 15:06:34 -0400 Subject: [PATCH] Add tools/used_space.py to visualize the ROM based on the .map file --- tools/mapreader.py | 172 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 tools/mapreader.py diff --git a/tools/mapreader.py b/tools/mapreader.py new file mode 100644 index 000000000..8a29e5235 --- /dev/null +++ b/tools/mapreader.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- + +# A library for parsing the pokecrystal.map file output by rgbds. + +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'])