diff options
Diffstat (limited to 'extras/wram.py')
-rw-r--r-- | extras/wram.py | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/extras/wram.py b/extras/wram.py new file mode 100644 index 0000000..b6d7fc6 --- /dev/null +++ b/extras/wram.py @@ -0,0 +1,313 @@ +# coding: utf-8 +""" +RGBDS BSS section and constant parsing. +""" + +import os + + +def separate_comment(line): + if ';' in line: + i = line.find(';') + return line[:i], line[i:] + return line, None + + +def rgbasm_to_py(text): + return text.replace('$', '0x').replace('%', '0b') + + +def make_wram_labels(wram_sections): + wram_labels = {} + for section in wram_sections: + for label in section['labels']: + if label['address'] not in wram_labels.keys(): + wram_labels[label['address']] = [] + wram_labels[label['address']] += [label['label']] + return wram_labels + +def bracket_value(string, i=0): + return string.split('[')[1 + i*2].split(']')[0] + +class BSSReader: + """ + Read rgbasm BSS/WRAM sections and associate labels with addresses. + Also reads constants/variables, even in macros. + """ + sections = [] + section = None + address = None + macros = {} + constants = {} + + section_types = { + 'VRAM': 0x8000, + 'SRAM': 0xa000, + 'WRAM0': 0xc000, + 'WRAMX': 0xd000, + 'HRAM': 0xff80, + } + + def __init__(self, *args, **kwargs): + self.__dict__.update(kwargs) + + def read_bss_line(self, l): + parts = l.strip().split(' ') + token = parts[0].strip() + params = ' '.join(parts[1:]).split(',') + + if token in ['ds', 'db', 'dw']: + if any(params): + length = eval(rgbasm_to_py(params[0]), self.constants.copy()) + else: + length = {'ds': 1, 'db': 1, 'dw': 2}[token] + self.address += length + # assume adjacent labels to use the same space + for label in self.section['labels'][::-1]: + if label['length'] == 0: + label['length'] = length + else: + break + + elif token in self.macros.keys(): + macro_text = '\n'.join(self.macros[token]) + '\n' + for i, p in enumerate(params): + macro_text = macro_text.replace('\\'+str(i+1),p) + macro_text = macro_text.split('\n') + macro_reader = BSSReader( + sections = list(self.sections), + section = dict(self.section), + address = self.address, + constants = self.constants, + ) + macro_sections = macro_reader.read_bss_sections(macro_text) + self.section = macro_sections[-1] + if self.section['labels']: + self.address = self.section['labels'][-1]['address'] + self.section['labels'][-1]['length'] + + + def read_bss_sections(self, bss): + + if self.section is None: + self.section = { + "labels": [], + } + + if type(bss) is str: + bss = bss.split('\n') + + macro = False + macro_name = None + for line in bss: + line = line.lstrip() + line, comment = separate_comment(line) + line = line.strip() + split_line = line.split() + split_line_upper = map(str.upper, split_line) + + if not line: + pass + + elif line[-4:].upper() == 'ENDM': + macro = False + macro_name = None + + elif macro: + self.macros[macro_name] += [line] + + elif line[-5:].upper() == 'MACRO': + macro_name = line.split(':')[0] + macro = True + self.macros[macro_name] = [] + + elif 'INCLUDE' == line[:7].upper(): + filename = line.split('"')[1] + self.read_bss_sections(open(filename).readlines()) + + elif 'SECTION' == line[:7].upper(): + if self.section: # previous + self.sections += [self.section] + + section_def = line.split(',') + name = section_def[0].split('"')[1] + type_ = section_def[1].strip() + if len(section_def) > 2: + bank = bracket_value(section_def[2]) + else: + bank = None + + if '[' in type_: + self.address = int(rgbasm_to_py(bracket_value(type_)), 16) + else: + if self.address == None or bank != self.section['bank'] or self.section['type'] != type_: + self.address = self.section_types.get(type_, self.address) + # else: keep going from this address + + self.section = { + 'name': name, + 'type': type_, + 'bank': bank, + 'start': self.address, + 'labels': [], + } + + elif ':' in line: + # rgbasm allows labels without :, but prefer convention + label = line[:line.find(':')] + if '\\' in label: + raise Exception, line + ' ' + label + if ';' not in label: + section_label = { + 'label': label, + 'address': self.address, + 'length': 0, + } + self.section['labels'] += [section_label] + self.read_bss_line(line.split(':')[-1]) + + elif any(x in split_line_upper for x in ['EQU', '=', 'SET']): # TODO: refactor + for x in ['EQU', '=', 'SET']: + if x in split_line_upper: + index = split_line_upper.index(x) + real = split_line[index] + name, value = map(' '.join, [split_line[:index], split_line[index+1:]]) + value = rgbasm_to_py(value) + self.constants[name] = eval(value, self.constants.copy()) + + else: + self.read_bss_line(line) + + self.sections += [self.section] + return self.sections + +def read_bss_sections(bss): + reader = BSSReader() + return reader.read_bss_sections(bss) + + +def constants_to_dict(constants): + """Deprecated. Use BSSReader.""" + return dict((eval(rgbasm_to_py(constant[constant.find('EQU')+3:constant.find(';')])), constant[:constant.find('EQU')].strip()) for constant in constants) + +def scrape_constants(text): + if type(text) is not list: + text = text.split('\n') + bss = BSSReader() + bss.read_bss_sections(text) + constants = bss.constants + return {v: k for k, v in constants.items()} + +def read_constants(filepath): + """ + Load lines from a file and grab any constants using BSSReader. + """ + lines = [] + if os.path.exists(filepath): + with open(filepath, "r") as file_handler: + lines = file_handler.readlines() + return scrape_constants(lines) + +class WRAMProcessor(object): + """ + RGBDS BSS section and constant parsing. + """ + + def __init__(self, config): + """ + Setup for WRAM parsing. + """ + self.config = config + + self.paths = {} + + if hasattr(self.config, "wram"): + self.paths["wram"] = self.config.wram + else: + self.paths["wram"] = os.path.join(self.config.path, "wram.asm") + + if hasattr(self.config, "hram"): + self.paths["hram"] = self.config.hram + else: + self.paths["hram"] = os.path.join(self.config.path, "hram.asm") + + if hasattr(self.config, "gbhw"): + self.paths["gbhw"] = self.config.gbhw + else: + self.paths["gbhw"] = os.path.join(self.config.path, "gbhw.asm") + + def initialize(self): + """ + Read constants. + """ + self.setup_wram_sections() + self.setup_wram_labels() + self.setup_hram_constants() + self.setup_gbhw_constants() + + self.reformat_wram_labels() + + def read_wram_sections(self): + """ + Opens the wram file and calls read_bss_sections. + """ + wram_content = None + wram_file_path = self.paths["wram"] + + with open(wram_file_path, "r") as wram: + wram_content = wram.readlines() + + wram_sections = read_bss_sections(wram_content) + return wram_sections + + def setup_wram_sections(self): + """ + Call read_wram_sections and set a variable. + """ + self.wram_sections = self.read_wram_sections() + return self.wram_sections + + def setup_wram_labels(self): + """ + Make wram labels based on self.wram_sections as input. + """ + self.wram_labels = make_wram_labels(self.wram_sections) + return self.wram_labels + + def read_hram_constants(self): + """ + Read constants from hram.asm using read_constants. + """ + hram_constants = read_constants(self.paths["hram"]) + return hram_constants + + def setup_hram_constants(self): + """ + Call read_hram_constants and set a variable. + """ + self.hram_constants = self.read_hram_constants() + return self.hram_constants + + def read_gbhw_constants(self): + """ + Read constants from gbhw.asm using read_constants. + """ + gbhw_constants = read_constants(self.paths["gbhw"]) + return gbhw_constants + + def setup_gbhw_constants(self): + """ + Call read_gbhw_constants and set a variable. + """ + self.gbhw_constants = self.read_gbhw_constants() + return self.gbhw_constants + + def reformat_wram_labels(self): + """ + Flips the wram_labels dictionary the other way around to access + addresses by label. + """ + self.wram = {} + + for (address, labels) in self.wram_labels.iteritems(): + for label in labels: + self.wram[label] = address |