-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add free space utility from Crystal, build red-speedchoice rom
- Loading branch information
Showing
6 changed files
with
261 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
|
||
""" | ||
Usage: python3 free_space.py [BANK=none] [red-speedchoice.map] | ||
Calculate the free space in the ROM or its individual banks. | ||
The BANK argument allows printing free space in one, all, or none of the ROM's banks. | ||
Valid arguments are numbers (in decimal "42" or hexadecimal "0x2A"), "all" or "none". | ||
If not specified, defaults to "none". | ||
""" | ||
|
||
import sys | ||
|
||
from mapreader import MapReader | ||
|
||
def main(): | ||
print_bank = 'none' | ||
mapfile = 'red-speedchoice.map' | ||
|
||
if len(sys.argv) >= 2 and sys.argv[1].startswith('BANK='): | ||
print_bank = sys.argv[1].split('=', 1)[-1] | ||
if len(sys.argv) >= 3: | ||
mapfile = sys.argv[2] | ||
elif len(sys.argv) >= 3 and sys.argv[2].startswith('BANK='): | ||
print_bank = sys.argv[2].split('=', 1)[-1] | ||
mapfile = sys.argv[1] | ||
|
||
if print_bank not in {'all', 'none'}: | ||
try: | ||
if print_bank.startswith('0x') or print_bank.startswith('0X'): | ||
print_bank = int(print_bank[2:], 16) | ||
else: | ||
print_bank = int(print_bank) | ||
except ValueError: | ||
error = 'Error: invalid BANK: %s' % print_bank | ||
if print_bank.isalnum(): | ||
error += ' (did you mean: 0x%s?)' % print_bank | ||
print(error, file=sys.stderr) | ||
sys.exit(1) | ||
|
||
num_banks = 0x40 | ||
bank_size = 0x4000 # bytes | ||
total_size = num_banks * bank_size | ||
|
||
r = MapReader() | ||
with open(mapfile, 'r', encoding='utf-8') as f: | ||
l = f.readlines() | ||
r.read_map_data(l) | ||
|
||
free_space = 0 | ||
per_bank = [] | ||
default_bank_data = {'sections': [], 'used': 0, 'slack': bank_size} | ||
for bank in range(num_banks): | ||
bank_data = r.bank_data['ROM0 bank'] if bank == 0 else r.bank_data['ROMX bank'] | ||
data = bank_data.get(bank, default_bank_data) | ||
used = data['used'] | ||
slack = data['slack'] | ||
per_bank.append((used, slack)) | ||
free_space += slack | ||
|
||
print('Free space: %d/%d (%.2f%%)' % (free_space, total_size, free_space * 100.0 / total_size)) | ||
if print_bank != 'none': | ||
print() | ||
print('bank, used, free') | ||
for bank in range(num_banks): | ||
used, slack = per_bank[bank] | ||
if print_bank in {'all', bank}: | ||
print('$%02X, %d, %d' % (bank, used, slack)) | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
# -*- 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, }, | ||
'ROM0 bank': { 'size': 0x4000, 'banked': True, }, | ||
'ROMX 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']) |