class SftParser: def __init__(self, snapshot, sftfile, zfill=False, asm_hex=False, asm_lower=False): self.snapshot = snapshot self.disassembler = Disassembler(snapshot, zfill=zfill, asm_hex=asm_hex, asm_lower=asm_lower) self.sftfile = sftfile self.address_fmt = get_address_format(asm_hex, asm_lower) self.stack = [] self.disassemble = True def _parse_instruction(self, line): ctl = line[0] lengths = [] if line[1] == ';': inst_ctl = None start = None i = line.find(' ', 2) if i < 0: i = len(line.rstrip()) comment_index = get_int_param(line[2:i]) else: inst_ctl = line[1] if inst_ctl == 'I': i = find_unquoted(line.rstrip(), ' ', 2) address_end = j = find_unquoted(line, ';', 2, i) else: address_end = line.index(',', 2) i = find_unquoted(line.rstrip(), ' ', address_end + 1) j = find_unquoted(line, ';', address_end + 1, i) start = get_int_param(line[2:address_end]) if j == i: comment_index = -1 else: comment_index = get_int_param(line[j + 1:i]) if j > address_end + 1: params = split_unquoted(line[address_end + 1:j], ',') lengths = parse_params(inst_ctl, params, 0) elif inst_ctl != 'I': raise ValueError comment = line[i:].strip() return ctl, inst_ctl, start, lengths, comment_index, comment def _parse_asm_directive(self, directive): if parse_asm_block_directive(directive, self.stack): self.disassemble = True for _p, i in self.stack: if i != '-': self.disassemble = False break def _set_bytes(self, line): address = parse_int(line[1:6]) if address is not None: comment_index = find_unquoted(line, ';') operation = line[7:comment_index].strip() set_bytes(self.snapshot, address, operation) def _parse_sft(self, min_address, max_address): start_index = -1 lines = [] v_block_ctl = None f = open_file(self.sftfile) for line in f: if line.startswith('#'): # This line is a skool file template comment continue if not line.strip(): # This line is blank lines.append(VerbatimLine(line)) v_block_ctl = None continue if line.startswith(';'): # This line is an entry-level comment lines.append(VerbatimLine(line)) continue if line.startswith('@'): lines.append(VerbatimLine(line)) self._parse_asm_directive(line[1:].rstrip()) continue if not self.disassemble: # This line is inside a '+' block, so include it as is lines.append(VerbatimLine(line)) continue # Check whether we're in a block that should be restored verbatim if v_block_ctl is None and line.startswith(VERBATIM_BLOCKS): v_block_ctl = line[0] if v_block_ctl: if v_block_ctl == 'd': self._set_bytes(line) lines.append(VerbatimLine(line)) continue # Check whether the line starts with a valid character if line[0] not in VALID_CTLS: lines.append(VerbatimLine(line)) continue try: ctl, inst_ctl, start, lengths, comment_index, comment = self._parse_instruction( line) except (IndexError, ValueError): raise SftParsingError("Invalid line: {0}".format( line.split()[0])) if start is not None: # This line contains a control directive if start >= min_address > 0 and start_index < 0: start_index = len(lines) instructions = [] for length, sublengths in lengths: end = start + length if inst_ctl == 'C': base = sublengths[0][1] instructions += self.disassembler.disassemble( start, end, base) elif inst_ctl == 'W': instructions += self.disassembler.defw_range( start, end, sublengths) elif inst_ctl == 'T': instructions += self.disassembler.defm_range( start, end, sublengths) elif inst_ctl == 'S': instructions.append( self.disassembler.defs(start, end, sublengths)) else: instructions += self.disassembler.defb_range( start, end, sublengths) start += length if instructions: done = False for instruction in instructions: if instruction.address >= max_address: while lines and lines[-1].is_trimmable(): lines.pop() done = True break address = self.address_fmt.format(instruction.address) lines.append( InstructionLine(ctl, address, instruction.operation, comment_index, comment)) ctl = ' ' if done: break else: lines.append( InstructionLine(ctl, start, '', comment_index, comment)) else: # This line is an instruction-level comment continuation line lines.append( InstructionLine(comment_index=comment_index, comment=comment)) f.close() if start_index < 0: return lines if start_index < len(lines): if str(lines[start_index])[0] in DIRECTIVES: while start_index > 0 and not lines[start_index].is_blank(): start_index -= 1 else: while start_index < len( lines) and not lines[start_index].is_blank(): start_index += 1 return lines[start_index + 1:] return [] def write_skool(self, min_address=0, max_address=65536): for line in self._parse_sft(min_address, max_address): write_line(str(line))
class Disassembly: def __init__(self, snapshot, ctl_parser, config=None, final=False, defb_size=8, defb_mod=1, zfill=False, defm_width=66, asm_hex=False, asm_lower=False): ctl_parser.apply_asm_data_directives(snapshot) self.disassembler = Disassembler(snapshot, defb_size, defb_mod, zfill, defm_width, asm_hex, asm_lower) self.ctl_parser = ctl_parser if asm_hex: if asm_lower: self.address_fmt = '{0:04x}' else: self.address_fmt = '{0:04X}' else: self.address_fmt = '{0}' self.entry_map = {} self.config = config or {} self.build(final) def build(self, final=False): self.instructions = {} self.entries = [] self._create_entries() if self.entries: self.org = self.entries[0].address else: self.org = None if final: self._calculate_references() def _create_entries(self): for block in self.ctl_parser.get_blocks(): if block.start in self.entry_map: entry = self.entry_map[block.start] self.entries.append(entry) for instruction in entry.instructions: self.instructions[instruction.address] = instruction continue title = block.title if not title: ctl = block.ctl if ctl != 'i' or block.description or block.registers or block.blocks[ 0].header: name = 'Title-' + ctl title = format_template(self.config.get(name, ''), name, address=self._address_str( block.start)) for sub_block in block.blocks: address = sub_block.start if sub_block.ctl in 'cBT': base = sub_block.sublengths[0][1] instructions = self.disassembler.disassemble( sub_block.start, sub_block.end, base) elif sub_block.ctl in 'bgstuw': sublengths = sub_block.sublengths if sublengths[0][0]: if sub_block.ctl == 's': length = sublengths[0][0] else: length = sum([s[0] for s in sublengths]) else: length = sub_block.end - sub_block.start instructions = [] while address < sub_block.end: end = min(address + length, sub_block.end) if sub_block.ctl == 't': instructions += self.disassembler.defm_range( address, end, sublengths) elif sub_block.ctl == 'w': instructions += self.disassembler.defw_range( address, end, sublengths) elif sub_block.ctl == 's': instructions.append( self.disassembler.defs(address, end, sublengths)) else: instructions += self.disassembler.defb_range( address, end, sublengths) address += length else: instructions = self.disassembler.ignore( sub_block.start, sub_block.end) sub_block.instructions = instructions for instruction in instructions: self.instructions[instruction.address] = instruction instruction.asm_directives = sub_block.asm_directives.get( instruction.address, ()) sub_blocks = [] i = 0 while i < len(block.blocks): sub_block = block.blocks[i] i += 1 sub_blocks.append(sub_block) if sub_block.multiline_comment is not None: end, sub_block.comment = sub_block.multiline_comment while i < len( block.blocks) and block.blocks[i].start < end: next_sub_block = block.blocks[i] sub_block.instructions += next_sub_block.instructions sub_block.end = next_sub_block.end i += 1 entry = Entry(block.header, title, block.description, block.ctl, sub_blocks, block.registers, block.end_comment, block.footer, block.asm_directives, block.ignoreua_directives) self.entry_map[entry.address] = entry self.entries.append(entry) for i, entry in enumerate(self.entries[1:]): self.entries[i].next = entry def remove_entry(self, address): if address in self.entry_map: del self.entry_map[address] def _calculate_references(self): for entry in self.entries: for instruction in entry.instructions: instruction.referrers = [] for entry in self.entries: for instruction in entry.instructions: operation = instruction.operation if operation.upper().startswith( ('DJ', 'JR', 'JP', 'CA', 'RS')): addr_str = get_address(operation) if addr_str: callee = self.instructions.get(parse_int(addr_str)) if callee: callee.add_referrer(entry) def _address_str(self, address): return self.address_fmt.format(address)
class Disassembly: def __init__(self, snapshot, ctl_parser, final=False, defb_size=8, defb_mod=1, zfill=False, defm_width=66, asm_hex=False, asm_lower=False): self.disassembler = Disassembler(snapshot, defb_size, defb_mod, zfill, defm_width, asm_hex, asm_lower) self.ctl_parser = ctl_parser if asm_hex: if asm_lower: self.address_fmt = '{0:04x}' else: self.address_fmt = '{0:04X}' else: self.address_fmt = '{0}' self.entry_map = {} self.build(final) def build(self, final=False): self.instructions = {} self.entries = [] self._create_entries() self.org = self.entries[0].address if final: self._calculate_references() def _create_entries(self): for block in self.ctl_parser.get_blocks(): if block.start in self.entry_map: entry = self.entry_map[block.start] self.entries.append(entry) for instruction in entry.instructions: self.instructions[instruction.address] = instruction continue title = block.title if block.ctl == 'c': title = title or 'Routine at {}'.format(self._address_str(block.start)) elif block.ctl in 'bw': title = title or 'Data block at {}'.format(self._address_str(block.start)) elif block.ctl == 't': title = title or 'Message at {}'.format(self._address_str(block.start)) elif block.ctl == 'g': title = title or 'Game status buffer entry at {}'.format(self._address_str(block.start)) elif block.ctl in 'us': title = title or 'Unused' elif block.ctl == 'i' and (block.description or block.registers or block.blocks[0].header): title = title or 'Ignored' for sub_block in block.blocks: address = sub_block.start if sub_block.ctl in 'cBT': base = sub_block.sublengths[0][1] instructions = self.disassembler.disassemble(sub_block.start, sub_block.end, base) elif sub_block.ctl in 'bgstuw': sublengths = sub_block.sublengths if sublengths[0][0]: if sub_block.ctl == 's': length = sublengths[0][0] else: length = sum([s[0] for s in sublengths]) else: length = sub_block.end - sub_block.start instructions = [] while address < sub_block.end: end = min(address + length, sub_block.end) if sub_block.ctl == 't': instructions += self.disassembler.defm_range(address, end, sublengths) elif sub_block.ctl == 'w': instructions += self.disassembler.defw_range(address, end, sublengths) elif sub_block.ctl == 's': instructions.append(self.disassembler.defs(address, end, sublengths)) else: instructions += self.disassembler.defb_range(address, end, sublengths) address += length else: instructions = self.disassembler.ignore(sub_block.start, sub_block.end) sub_block.instructions = instructions for instruction in instructions: self.instructions[instruction.address] = instruction instruction.asm_directives = sub_block.asm_directives.get(instruction.address, ()) sub_blocks = [] i = 0 while i < len(block.blocks): sub_block = block.blocks[i] i += 1 sub_blocks.append(sub_block) if sub_block.multiline_comment is not None: end, sub_block.comment = sub_block.multiline_comment while i < len(block.blocks) and block.blocks[i].start < end: next_sub_block = block.blocks[i] sub_block.instructions += next_sub_block.instructions sub_block.end = next_sub_block.end i += 1 entry = Entry(title, block.description, block.ctl, sub_blocks, block.registers, block.end_comment, block.asm_directives, block.ignoreua_directives) self.entry_map[entry.address] = entry self.entries.append(entry) for i, entry in enumerate(self.entries[1:]): self.entries[i].next = entry def remove_entry(self, address): if address in self.entry_map: del self.entry_map[address] def contains_entry_asm_directive(self, asm_dir): for entry in self.entries: for directive, value in entry.asm_directives: if directive == asm_dir: return True def _calculate_references(self): for entry in self.entries: for instruction in entry.instructions: instruction.referrers = [] for entry in self.entries: for instruction in entry.instructions: operation = instruction.operation if operation.upper().startswith(('DJ', 'JR', 'JP', 'CA', 'RS')): addr_str = get_address(operation) if addr_str: callee = self.instructions.get(parse_int(addr_str)) if callee: callee.add_referrer(entry) def _address_str(self, address): return self.address_fmt.format(address)
class SftParser: def __init__(self, snapshot, sftfile, zfill=False, asm_hex=False, asm_lower=False): self.snapshot = snapshot self.disassembler = Disassembler(snapshot, zfill=zfill, asm_hex=asm_hex, asm_lower=asm_lower) self.sftfile = sftfile self.address_fmt = get_address_format(asm_hex, asm_lower) self.stack = [] self.disassemble = True def _parse_instruction(self, line): ctl = line[0] lengths = [] if line[1] == ';': inst_ctl = None start = None i = line.find(' ', 2) if i < 0: i = len(line.rstrip()) comment_index = get_int_param(line[2:i]) else: inst_ctl = line[1] if inst_ctl == 'I': i = find_unquoted(line.rstrip(), ' ', 2) address_end = j = find_unquoted(line, ';', 2, i) else: address_end = line.index(',', 2) i = find_unquoted(line.rstrip(), ' ', address_end + 1) j = find_unquoted(line, ';', address_end + 1, i) start = get_int_param(line[2:address_end]) if j == i: comment_index = -1 else: comment_index = get_int_param(line[j + 1:i]) if j > address_end + 1: params = split_unquoted(line[address_end + 1:j], ',') lengths = parse_params(inst_ctl, params, 0) elif inst_ctl != 'I': raise ValueError comment = line[i:].strip() return ctl, inst_ctl, start, lengths, comment_index, comment def _parse_asm_directive(self, directive): if parse_asm_block_directive(directive, self.stack): self.disassemble = True for _p, i in self.stack: if i != '-': self.disassemble = False break def _set_bytes(self, line): address = parse_int(line[1:6]) if address is not None: comment_index = find_unquoted(line, ';') operation = line[7:comment_index].strip() set_bytes(self.snapshot, address, operation) def _parse_sft(self, min_address, max_address): start_index = -1 lines = [] v_block_ctl = None f = open_file(self.sftfile) for line in f: if line.startswith('#'): # This line is a skool file template comment continue if not line.strip(): # This line is blank lines.append(VerbatimLine(line)) v_block_ctl = None continue if line.startswith(';'): # This line is an entry-level comment lines.append(VerbatimLine(line)) continue if line.startswith('@'): lines.append(VerbatimLine(line)) self._parse_asm_directive(line[1:].rstrip()) continue if not self.disassemble: # This line is inside a '+' block, so include it as is lines.append(VerbatimLine(line)) continue # Check whether we're in a block that should be restored verbatim if v_block_ctl is None and line[0] in 'dr' or (line[0] == 'i' and line[1] in '$0123456789'): v_block_ctl = line[0] if v_block_ctl: if v_block_ctl == 'd': self._set_bytes(line) lines.append(VerbatimLine(line)) continue # Check whether the line starts with a valid character if line[0] not in VALID_CTLS: lines.append(VerbatimLine(line)) continue try: ctl, inst_ctl, start, lengths, comment_index, comment = self._parse_instruction(line) except (IndexError, ValueError): raise SftParsingError("Invalid line: {0}".format(line.split()[0])) if start is not None: # This line contains a control directive if start >= min_address > 0 and start_index < 0: start_index = len(lines) instructions = [] for length, sublengths in lengths: end = start + length if inst_ctl == 'C': base = sublengths[0][1] instructions += self.disassembler.disassemble(start, end, base) elif inst_ctl == 'W': instructions += self.disassembler.defw_range(start, end, sublengths) elif inst_ctl == 'T': instructions += self.disassembler.defm_range(start, end, sublengths) elif inst_ctl == 'S': instructions.append(self.disassembler.defs(start, end, sublengths)) else: instructions += self.disassembler.defb_range(start, end, sublengths) start += length if instructions: done = False for instruction in instructions: if instruction.address >= max_address: while lines and lines[-1].is_trimmable(): lines.pop() done = True break address = self.address_fmt.format(instruction.address) lines.append(InstructionLine(ctl, address, instruction.operation, comment_index, comment)) ctl = ' ' if done: break else: lines.append(InstructionLine(ctl, start, '', comment_index, comment)) else: # This line is an instruction-level comment continuation line lines.append(InstructionLine(comment_index=comment_index, comment=comment)) f.close() if start_index < 0: return lines if start_index < len(lines): if str(lines[start_index])[0] in DIRECTIVES: while start_index > 0 and not lines[start_index].is_blank(): start_index -= 1 else: while start_index < len(lines) and not lines[start_index].is_blank(): start_index += 1 return lines[start_index + 1:] return [] def write_skool(self, min_address=0, max_address=65536): for line in self._parse_sft(min_address, max_address): write_line(str(line))
class Disassembly: def __init__(self, snapshot, ctl_parser, config=None, final=False, defb_size=8, defb_mod=1, zfill=False, defm_width=66, asm_hex=False, asm_lower=False): ctl_parser.apply_asm_data_directives(snapshot) self.disassembler = Disassembler(snapshot, defb_size, defb_mod, zfill, defm_width, asm_hex, asm_lower) self.ctl_parser = ctl_parser if asm_hex: if asm_lower: self.address_fmt = '{0:04x}' else: self.address_fmt = '{0:04X}' else: self.address_fmt = '{0}' self.entry_map = {} self.config = config or {} self.build(final) def build(self, final=False): self.instructions = {} self.entries = [] self._create_entries() if self.entries: self.org = self.entries[0].address else: self.org = None if final: self._calculate_references() def _create_entries(self): for block in self.ctl_parser.get_blocks(): if block.start in self.entry_map: entry = self.entry_map[block.start] self.entries.append(entry) for instruction in entry.instructions: self.instructions[instruction.address] = instruction continue title = block.title if not any(title): ctl = block.ctl if ctl != 'i' or block.description or block.registers or block.blocks[0].header: name = 'Title-' + ctl title = [format_template(self.config.get(name, ''), name, address=self._address_str(block.start))] for sub_block in block.blocks: address = sub_block.start if sub_block.ctl in 'cBT': base = sub_block.sublengths[0][1] instructions = self.disassembler.disassemble(sub_block.start, sub_block.end, base) elif sub_block.ctl in 'bgstuw': sublengths = sub_block.sublengths if sublengths[0][0]: if sub_block.ctl == 's': length = sublengths[0][0] else: length = sum([s[0] for s in sublengths]) else: length = sub_block.end - sub_block.start instructions = [] while address < sub_block.end: end = min(address + length, sub_block.end) if sub_block.ctl == 't': instructions += self.disassembler.defm_range(address, end, sublengths) elif sub_block.ctl == 'w': instructions += self.disassembler.defw_range(address, end, sublengths) elif sub_block.ctl == 's': instructions += self.disassembler.defs(address, end, sublengths) else: instructions += self.disassembler.defb_range(address, end, sublengths) address += length else: instructions = self.disassembler.ignore(sub_block.start, sub_block.end) self._add_instructions(sub_block, instructions) sub_blocks = [] i = 0 while i < len(block.blocks): sub_block = block.blocks[i] i += 1 sub_blocks.append(sub_block) if sub_block.multiline_comment is not None: end, sub_block.comment = sub_block.multiline_comment while i < len(block.blocks) and block.blocks[i].start < end: next_sub_block = block.blocks[i] sub_block.instructions += next_sub_block.instructions sub_block.end = next_sub_block.end i += 1 entry = Entry(block.header, title, block.description, block.ctl, sub_blocks, block.registers, block.end_comment, block.footer, block.asm_directives, block.ignoreua_directives) self.entry_map[entry.address] = entry self.entries.append(entry) for i, entry in enumerate(self.entries[1:]): self.entries[i].next = entry def remove_entry(self, address): if address in self.entry_map: del self.entry_map[address] def _add_instructions(self, sub_block, instructions): sub_block.instructions = instructions for instruction in instructions: self.instructions[instruction.address] = instruction instruction.asm_directives = sub_block.asm_directives.get(instruction.address, ()) instruction.label = None for asm_dir in instruction.asm_directives: if asm_dir.startswith(AD_LABEL + '='): instruction.label = asm_dir[6:] if instruction.label.startswith('*'): instruction.ctl = '*' break def _calculate_references(self): for entry in self.entries: for instruction in entry.instructions: instruction.referrers = [] for entry in self.entries: for instruction in entry.instructions: operation = instruction.operation if operation.upper().startswith(('DJ', 'JR', 'JP', 'CA', 'RS')): addr_str = get_address(operation) if addr_str: callee = self.instructions.get(parse_int(addr_str)) if callee and (entry.ctl != 'u' or callee.entry == entry) and callee.label != '': callee.add_referrer(entry) def _address_str(self, address): return self.address_fmt.format(address)