def update_hex_viewer_actions(self): self.ui.actionUSA.setDisabled(get_rom(RomVariant.USA) is None) self.ui.actionDEMO.setDisabled(get_rom(RomVariant.DEMO) is None) self.ui.actionJP.setDisabled(get_rom(RomVariant.JP) is None) self.ui.actionEU.setDisabled(get_rom(RomVariant.EU) is None) self.ui.actionDEMO_JP.setDisabled(get_rom(RomVariant.DEMO_JP) is None) self.ui.actionCUSTOM.setDisabled(get_rom(RomVariant.CUSTOM) is None) self.ui.actionCUSTOM_EU.setDisabled( get_rom(RomVariant.CUSTOM_EU) is None) self.ui.actionCUSTOM_JP.setDisabled( get_rom(RomVariant.CUSTOM_JP) is None) self.ui.actionCUSTOM_DEMO_USA.setDisabled( get_rom(RomVariant.CUSTOM_DEMO_USA) is None) self.ui.actionCUSTOM_DEMO_JP.setDisabled( get_rom(RomVariant.CUSTOM_DEMO_JP) is None)
def add_hex_editor_dock(self, rom_variant: RomVariant, object_name: str) -> HexViewerController: ''' Internally used to add a new or existing hex editor ''' rom = get_rom(rom_variant) if rom is None: QMessageBox.critical(self.parent, 'Load ROM', f'Unable to load rom {rom_variant}') return None dockWidget = HexViewerDock(self.parent, 'Hex Viewer ' + rom_variant) # Not only hide docks on close TODO still remove them here dockWidget.setAttribute(Qt.WA_DeleteOnClose) dockWidget.setObjectName(object_name) self.parent.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, dockWidget) controller = HexViewerController(dockWidget, rom_variant, rom) self.hex_viewer_manager.register_controller(controller) dock = Dock(object_name, dockWidget, rom_variant, controller) self.docks[object_name] = dock dockWidget.destroyed.connect(lambda: self.remove_dock(object_name)) return controller
def invalidate(self) -> None: ''' Invalidates all assumptions about the underlying data and requests a full repaint. Call this if the underlying rom changed. ''' self.rom = get_rom(self.rom_variant) self.request_repaint()
def slot_extract_data(self, text: str) -> None: if self.symbols is None: # First need to load symbols self.symbols = get_symbol_database().get_symbols(RomVariant.CUSTOM) if self.symbols is None: self.server_worker.slot_extracted_data({ 'status': 'error', 'text': 'No symbols for rom CUSTOM loaded' }) return if self.data_extractor_plugin is None: self.data_extractor_plugin = get_plugin('data_extractor', 'DataExtractorPlugin') if self.data_extractor_plugin is None: self.server_worker.slot_extracted_data({ 'status': 'error', 'text': 'Data Extractor plugin not loaded' }) return if self.rom is None: self.rom = get_rom(RomVariant.CUSTOM) if self.rom is None: self.server_worker.slot_extracted_data({ 'status': 'error', 'text': 'CUSTOM rom could not be loaded' }) return try: result = self.data_extractor_plugin.instance.extract_data( text, self.symbols, self.rom) if result is not None: self.server_worker.slot_extracted_data({ 'status': 'ok', 'text': result }) except Exception as e: traceback.print_exc() self.server_worker.slot_extracted_data({ 'status': 'error', 'text': str(e) })
def is_diffing(self, virtual_address: int) -> bool: # TODO cache this, optimize accesses of rom data data = None for variant in self.variants: local_address = self.constraint_manager.to_local( variant, virtual_address) if local_address == -1 or local_address > 0xffffff: # does count as a difference return True local_data = get_rom(variant).get_byte(local_address) if data is None: data = local_data continue if data != local_data: return True return False
def slot_script_addr(self, addr: int) -> None: print('ADDR: ', addr) if addr == 0: self.ui.labelCode.setText('No script executed in current context.') return rom = get_rom(RomVariant.CUSTOM) # receive the current instruction pointer #instruction_pointer = 0x8009b70 #instruction_pointer = 0x8009d42 instruction_pointer = addr symbols = get_symbol_database().get_symbols( RomVariant.CUSTOM) # Symbols for our custom USA rom symbol = symbols.get_symbol_at(instruction_pointer - ROM_OFFSET) script_name = symbol.name script_offset = instruction_pointer - ROM_OFFSET - symbol.address # Find file containing the script # TODO or statically find all script files? script_file = None for root, dirs, files in os.walk( os.path.join(settings.get_repo_location(), 'data', 'scripts')): if script_name + '.inc' in files: script_file = os.path.join(root, script_name + '.inc') break # TODO search the file contents for the script for file in files: path = os.path.join(root, file) with open(path, 'r') as f: if 'SCRIPT_START ' + script_name in f.read(): script_file = path if script_file is None: self.ui.labelCode.setText( f'ERROR: Count not find script file containing {script_name}') return self.ui.labelScriptName.setText(script_file) script_lines = [] with open(script_file, 'r') as file: script_lines = file.read().split('\n') # print(script_lines) # TODO for testing ifdefs: script_0800B200 # print('test') # print(symbol) # print(script_offset) # TODO only disassemble the number of bytes, the actual instructions are not interesting as they are read from the source file. (_, instructions) = disassemble_script( rom.get_bytes(symbol.address, symbol.address + symbol.length), symbol.address) output = '' current_instruction = 0 in_correct_script = False ifdef_stack = [True] for line in script_lines: stripped = line.strip() if stripped.startswith('SCRIPT_START'): in_correct_script = stripped == 'SCRIPT_START ' + script_name output += f'{line}\n' continue if not in_correct_script or stripped.startswith( '@') or stripped.endswith(':'): output += f'{line}\n' continue if '.ifdef' in stripped: if not ifdef_stack[-1]: ifdef_stack.append(False) output += f'{line}\n' continue # TODO check variant is_usa = stripped.split(' ')[1] == 'USA' ifdef_stack.append(is_usa) output += f'{line}\n' continue if '.ifndef' in stripped: if not ifdef_stack[-1]: ifdef_stack.append(False) output += f'{line}\n' continue is_usa = stripped.split(' ')[1] == 'USA' ifdef_stack.append(not is_usa) output += f'{line}\n' continue if '.else' in stripped: if ifdef_stack[-2]: # If the outermost ifdef is not true, this else does not change the validiness of this ifdef ifdef_stack[-1] = not ifdef_stack[-1] output += f'{line}\n' continue if '.endif' in stripped: ifdef_stack.pop() output += f'{line}\n' continue if not ifdef_stack[-1]: # Not defined for this variant output += f'{line}\n' continue if current_instruction >= len(instructions): # TODO maybe even not print additional lines? output += f'{line}\n' continue addr = instructions[current_instruction].addr prefix = '' if addr == script_offset: prefix = '>' output += f'{addr:03d}| {prefix}{line}\t\n' current_instruction += 1 if stripped.startswith('SCRIPT_END'): break self.ui.labelCode.setText(output)
def add_pointers_and_constraints(self, pointer: Pointer) -> bool: """ Add a pointer that is the same for all variants and the resulting constraints. Returns true if the constraint changes the relations between the files. """ # Found new_pointers = [pointer] new_constraints = [] virtual_address = self.constraint_manager.to_virtual( pointer.rom_variant, pointer.address) for variant in self.linked_variants: if variant != pointer.rom_variant: address = self.constraint_manager.to_local( variant, virtual_address) points_to = get_rom(variant).get_pointer(address) # Add a corresponding pointer for this variant new_pointers.append(Pointer( variant, address, points_to, pointer.certainty, pointer.author, pointer.note)) # Add a constraint for the places that these two pointers are pointing to, as the pointers should be the same # TODO check that it's actually a pointer into rom note = f'Pointer at {pointer.rom_variant} {hex(pointer.address)}' if pointer.note.strip() != '': note += '\n' + pointer.note # TODO test that adding the added constraints are not invalid # TODO It might be that a constraint that is added with the new_constraints invalidates some other newly added # constraint which then would need to be enabled. Need to test for all new_constraints whether they are actually still valid after adding them to the constraint manager? enabled = self.constraint_manager.to_virtual( pointer.rom_variant, pointer.points_to-ROM_OFFSET) != self.constraint_manager.to_virtual(variant, points_to-ROM_OFFSET) print(f'Add constraint {enabled}') new_constraints.append(Constraint(pointer.rom_variant, pointer.points_to-ROM_OFFSET, variant, points_to-ROM_OFFSET, pointer.certainty, pointer.author, note, enabled)) # Show dialog if one constraint was new one_enabled = False for constraint in new_constraints: if constraint.enabled: one_enabled = True break if one_enabled: # TODO we cannot be sure yet that the one enabled constraint does not interfere with the disabled constraint, # so just enable all constraints again (and disable them later via the constraint cleaner plugin) for constraint in new_constraints: constraint.enabled = True # Check whether the new constraint is invalid constraint_manager = ConstraintManager({RomVariant.USA, RomVariant.DEMO, RomVariant.EU, RomVariant.JP, RomVariant.DEMO_JP, RomVariant.CUSTOM, RomVariant.CUSTOM_EU, RomVariant.CUSTOM_JP, RomVariant.CUSTOM_DEMO_USA, RomVariant.CUSTOM_DEMO_JP}) constraint_manager.add_all_constraints( get_constraint_database().get_constraints()) try: constraint_manager.add_all_constraints(new_constraints) except InvalidConstraintError as e: raise e print('Adding to database') pointer_database = get_pointer_database() pointer_database.add_pointers(new_pointers) constraint_database = get_constraint_database() constraint_database.add_constraints(new_constraints) return one_enabled
def process(self) -> None: try: print('start') # Load shifted rom rom_original = get_rom(RomVariant.USA) print('Load shifted') rom_path = path.join(settings.get_repo_location(), 'tmc.gba') print(rom_path) if not path.isfile(rom_path): self.signal_fail.emit(f'Shifted rom expected at {rom_path}') return rom_shifted = Rom(rom_path) print('Shifted rom loaded') pointerlist = get_pointer_database().get_pointers(RomVariant.USA) END_OF_USED_DATA = 0xde7da4 errors = [] locations = [] shift_location = 0x108 shift_length = 0x10000 take_long_time = True progress = 0 for i in range(END_OF_USED_DATA): orig = rom_original.get_byte(i) shifted_i = i if i >= shift_location: #print('SHIFT') shifted_i += shift_length #print(i, shifted_i) shifted = rom_shifted.get_byte(shifted_i) if orig != shifted: pointers = pointerlist.get_pointers_at(i) # Test if pointer if len(pointers) > 0: assert shifted == orig + 1 # TODO parse the full pointer continue print(f'{hex(i-2)}\t{orig}\t{shifted}') errors.append((i, orig, shifted)) locations.append(i) #self.signal_fail.emit(f'Failed at {hex(i)}: {orig} {shifted}') #break else: if take_long_time: if rom_original.get_byte(i + 1) != 0x8: # Certainly not a pointer here continue pointers = pointerlist.get_pointers_at(i) if len(pointers) > 0: if pointers[0].address == i - 2: errors.append((i, orig, shifted)) locations.append(i) print(f'missing shift at {hex(i-2)}') #if len(pointers) > 0: # TODO test that pointer was shifted #pass new_progress = i * 100 // END_OF_USED_DATA if new_progress != progress: progress = new_progress self.signal_progress.emit(new_progress) if len(errors) == 0: self.signal_done.emit() else: self.signal_locations.emit(locations) self.signal_fail.emit(f'{len(errors)} errors found.') except Exception as e: print(e) self.signal_fail.emit('Caught exception')