def explorerscript_save(self, filename, code, source_map: Optional[SourceMap] = None): """Save the ExplorerScript file and it's source map if given""" filename = self._explorerscript_resolve_filename( filename, EXPLORERSCRIPT_EXT) with open_utf8(filename, 'w') as f: f.write(code) if source_map: with open_utf8(filename + EXPLORERSCRIPT_SOURCE_MAP_SUFFIX, 'w') as f: f.write(source_map.serialize())
def set_standin_entities(self, mappings): with sprite_provider_lock: self._loaded__actor_placeholders = {} p = self._standin_entities_filepath() with open_utf8(p, 'w') as f: json.dump(mappings, f) self._loaded_standins = mappings
def _get_random_room_txt(self): from skytemple_randomizer.config import data_dir i = randrange(0, 1000) with open_utf8( os.path.join(data_dir(), 'fixed_floor_layouts', f'{i}.txt')) as f: return f.read().splitlines()
def resync(self, fn, list_breakpoints: List[int]): """ Re-sync the breakpoints for this file. This is triggered, after a ssb file was saved. If the file is still open in the ground engine, the new state is written to file and a temporary dict, but is not used yet. The Breakpoint register registers itself as a callback for that SSB file and waits until it is no longer loaded in the ground engine. If the file is not open in the ground engine, the changes are applied immediately. Callbacks for adding are NOT called as for add. """ logger.debug(f"{fn}: Breakpoint resync") ssb = self.file_manager.get(fn) mapping_to_write_to_file = self.breakpoint_mapping if ssb.ram_state_up_to_date: # We can just update. self.breakpoint_mapping[fn] = list_breakpoints else: # We need to use a temporary mapping for now! self.new_breakpoint_mapping[fn] = list_breakpoints ssb.register_reload_event_manager(self.wait_for_ssb_update) mapping_to_write_to_file = self.breakpoint_mapping.copy() mapping_to_write_to_file[fn] = list_breakpoints with open_utf8(self._breakpoint_filename, 'w') as f: # TODO: A breakpoint update in another file will just override this again... # we should probably keep track of two full sets of the state (current ROM / current RAM) json.dump(mapping_to_write_to_file, f)
def on_stats_export_clicked(self, *args): assert self._level_bin_entry is not None dialog = Gtk.FileChooserNative.new(_("Save CSV..."), MainController.window(), Gtk.FileChooserAction.SAVE, None, None) self._add_dialog_file_filters(dialog) response = dialog.run() fn = dialog.get_filename() dialog.destroy() if response == Gtk.ResponseType.ACCEPT: fn = add_extension_if_missing(fn, 'csv') try: rows: List[List[Any]] = [[ CSV_LEVEL, CSV_EXP_POINTS, CSV_HP, CSV_ATK, CSV_SP_ATK, CSV_DEF, CSV_SP_DEF ]] for i, level in enumerate(self._level_bin_entry.levels): rows.append([ i + 1, level.experience_required, level.hp_growth, level.attack_growth, level.special_attack_growth, level.defense_growth, level.special_defense_growth ]) with open_utf8(fn, 'w', newline='') as file: writer = csv.writer(file) writer.writerows(rows) except BaseException as err: display_error(sys.exc_info(), str(err), _("Error saving the CSV."))
def save_explorerscript_macro( self, abs_exps_path: str, code: str, changed_ssbs: List[SsbLoadedFile] ) -> Tuple[List[bool], List[Set[str]]]: """ Saves an ExplorerScript macro file. This will save the source file for the macro and also recompile all SSB models in the list of changed_ssbs. Returned is a list of "ready_to_reload" from save_from_explorerscript and a list of sets for ALL included files of those ssb files. """ logger.debug(f"{abs_exps_path}: Saving ExplorerScript macro") # Write ExplorerScript to file with open_utf8(abs_exps_path, 'w') as f: f.write(code) ready_to_reloads = [] included_files_list = [] project_fm = self.context.get_project_filemanager() for ssb in changed_ssbs: # Skip non-existing or not up to date exps: if not project_fm.explorerscript_exists(ssb.filename) or \ not project_fm.explorerscript_hash_up_to_date(ssb.filename, ssb.exps.ssb_hash): ready_to_reloads.append(False) included_files_list.append(set()) else: exps_source, _ = project_fm.explorerscript_load( ssb.filename, sourcemap=False) ready_to_reload, included_files = self.save_from_explorerscript( ssb.filename, exps_source) ready_to_reloads.append(ready_to_reload) included_files_list.append(included_files) return ready_to_reloads, included_files_list
def load_thread(): try: # 1. Load the epxs file exps_source, _ = self._ssb_fm.project_fm.explorerscript_load(self._relative_path, sourcemap=False) # 2. Load a list of all ssbs file from the inclusion map, # request them from the file manager and watch them inclusion_map_path = self._absolute_path + EXPLORERSCRIPT_INCLUSION_MAP_SUFFIX inclusion_map = [] if os.path.exists(inclusion_map_path): with open_utf8(inclusion_map_path, 'r') as f: inclusion_map = json.load(f) for ssb_filename in inclusion_map: logger.debug(f"Register macro for {ssb_filename}.") try: self._register_ssb_handler( self._ssb_fm.get(ssb_filename) ) except FileNotFoundError: # Ignore deleted ssbs pass except Exception as ex: logger.error(f"Error on load.", exc_info=ex) exc_info = sys.exc_info() GLib.idle_add(partial(exps_exception_callback, exc_info, ex)) else: GLib.idle_add(partial( load_view_callback, exps_source, True, 'exps' )) GLib.idle_add(partial(self._after_load, after_callback))
def explorerscript_hash_up_to_date(self, filename, hash_compare): filename = self._explorerscript_resolve_filename( filename, '.ssb.sha256') if not os.path.exists(filename): return False with open_utf8(filename, 'r') as f: hash_file = f.read() return hash_file == hash_compare
def __init__(self): self.config_dir = os.path.join(ProjectFileManager.shared_config_dir()) os.makedirs(self.config_dir, exist_ok=True) self.config_file = os.path.join(self.config_dir, CONFIG_FILE_NAME) self.loaded_config = configparser.ConfigParser() if os.path.exists(self.config_file): with open_utf8(self.config_file, 'r') as f: self.loaded_config.read_file(f)
def explorerscript_load_sourcemap(self, filename) -> SourceMap: filename = self._explorerscript_resolve_filename( filename, EXPLORERSCRIPT_EXT + EXPLORERSCRIPT_SOURCE_MAP_SUFFIX) if os.path.exists(filename): with open_utf8(filename, 'r') as f: source_map_code = f.read() source_map = SourceMap.deserialize(source_map_code) else: source_map = SourceMap.create_empty() return source_map
def explorerscript_load(self, filename, sourcemap=True) -> Tuple[str, SourceMap]: """Load the ExplorerScript file and it's source map if it exists, otherwise an empty map""" filename = self._explorerscript_resolve_filename( filename, EXPLORERSCRIPT_EXT) with open_utf8(filename, 'r') as f: source_code = f.read() sourcemap = self.explorerscript_load_sourcemap( filename) if sourcemap else SourceMap.create_empty() return source_code, sourcemap
def __init__(self): self.config_dir = os.path.join(ProjectFileManager.shared_config_dir()) os.makedirs(self.config_dir, exist_ok=True) self.config_file = os.path.join(self.config_dir, CONFIG_FILE_NAME) self.loaded_config = configparser.ConfigParser() if os.path.exists(self.config_file): try: with open_utf8(self.config_file, 'r') as f: self.loaded_config.read_file(f) except BaseException as err: logger.error("Error reading config, falling back to default.", exc_info=err)
def wait_for_ssb_update(self, ssb: SsbLoadedFile): if ssb.filename in self.new_breakpoint_mapping: # We can switch now update. self.breakpoint_mapping[ ssb.filename] = self.new_breakpoint_mapping[ssb.filename] del self.new_breakpoint_mapping[ssb.filename] with open_utf8(self._breakpoint_filename, 'w') as f: json.dump(self.breakpoint_mapping, f) ssb.unregister_reload_event_manager(self.wait_for_ssb_update)
def add(self, fn, op_off): logger.debug(f"{fn}: Breakpoint add: {op_off}") op_off = int(op_off) if fn not in self.breakpoint_mapping: self.breakpoint_mapping[fn] = [] if self._get(self.breakpoint_mapping[fn], op_off) is not None: return self.breakpoint_mapping[fn].append(op_off) for cb in self._callbacks_added: cb(fn, op_off) with open_utf8(self._breakpoint_filename, 'w') as f: json.dump(self.breakpoint_mapping, f)
def remove(self, fn, op_off): logger.debug(f"{fn}: Breakpoint remove: {op_off}") op_off = int(op_off) if fn not in self.breakpoint_mapping: return idx = self._get(self.breakpoint_mapping[fn], op_off) if idx is None: return del self.breakpoint_mapping[fn][idx] for cb in self._callbacks_remvoed: cb(fn, op_off) with open_utf8(self._breakpoint_filename, 'w') as f: json.dump(self.breakpoint_mapping, f)
def load_binaries(edition: str) -> List[Pmd2Binary]: version, region = edition.split("_") if version != GAME_VERSION_EOS: raise ValueError("This game version is not supported.") binaries: Dict[str, _IncompleteBinary] = {} files = glob(os.path.join(SYMBOLS_DIR, '*.yml')) # Make sure the arm and overlay files are read this: These are the main files. # They will contain the address, length and description. files.sort(key=lambda key: -1 if key.startswith('arm') or key.startswith('overlay') else 1) for yml_path in files: with open_utf8(yml_path, 'r') as f: _read(binaries, yaml.safe_load(f), region) with open_utf8( os.path.join(get_resources_dir(), "skytemple_pmdsky_debug_symbols.yml"), 'r') as f: _read(binaries, yaml.safe_load(f), region) return _build(binaries)
def assemble(self, patch_asm: str): with tempfile.TemporaryDirectory() as tmp: shutil.copytree(os.path.join(get_resources_dir(), 'patches', 'asm_patches', 'eos_move_effects'), tmp, dirs_exist_ok=True, symlinks=True) set_rw_permission_folder(tmp) # Write final asm file with open_utf8(os.path.join(tmp, ASM_ENTRYPOINT_FN), 'w') as file: file.write(patch_asm) # Run armips try: prefix = "" # Under Windows, try to load from SkyTemple _resources dir first. if sys.platform.startswith('win') and os.path.exists( os.path.join(get_resources_dir(), 'armips.exe')): prefix = os.path.join(get_resources_dir(), '') exec_name = os.getenv('SKYTEMPLE_ARMIPS_EXEC', f'{prefix}armips') result = subprocess.Popen([exec_name, ASM_ENTRYPOINT_FN], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tmp) retcode = result.wait() except FileNotFoundError as ex: raise make_user_err( ArmipsNotInstalledError, _("ARMIPS could not be found. Make sure, that " "'armips' is inside your system's PATH.")) from ex if retcode != 0: raise make_user_err( PatchError, _("ARMIPS reported an error while applying the patch."), str(result.stdout.read(), 'utf-8'), str(result.stderr.read(), 'utf-8') # type: ignore if result.stderr else '') out_bin_path = os.path.join(tmp, OUT_BIN) if not os.path.exists(out_bin_path): raise ValueError( f(_("The armips source file did not create a {OUT_BIN}."))) with open(out_bin_path, 'rb') as file: return file.read()
def get_standin_entities(self): if not self._loaded_standins: self._loaded_standins = STANDIN_ENTITIES_DEFAULT p = self._standin_entities_filepath() if os.path.exists(p): with open_utf8(p, 'r') as f: try: self._loaded_standins = { int(k): v for k, v in json.load(f).items() } # type: ignore except BaseException as err: logger.error( f"Failed to load standin sprites from {p}, falling back to default: {err}." ) return self._loaded_standins
def load(self, index: int, config_dir: str): # TODO: Not very efficient at the moment but ok. self.variables_changed_but_not_saved = False path = os.path.join(config_dir, f'vars.{index}.json') if not os.path.exists(path): return try: with open_utf8(path, 'r') as f: vars = json.load(f) for name, values in vars.items(): var_id = self.rom_data.script_data.game_variables__by_name[ name].id for i, value in enumerate(values): self._queue_variable_write(var_id, i, value) except BaseException as err: self.context.display_error(sys.exc_info(), str(err), "Unable to load variables!") return
def render_graph(self): self._render_graph = False if self._level_bin_entry is None: return stack: Gtk.Stack = self.builder.get_object('graph_stack') if self.item_id < len(self._waza_p.learnsets): learnset = self._waza_p.learnsets[self.item_id] else: learnset = MoveLearnset([], [], []) graph_provider = LevelUpGraphProvider( self.module.get_entry(self.item_id), self._level_bin_entry, learnset, self._string_provider.get_all(StringType.MOVE_NAMES)) svg = graph_provider.provide(dark=is_dark_theme( MainController.window()), disable_xml_declaration=True).render() with open_utf8(self.get_tmp_html_path(), 'w') as f: f.write( render_graph_template( f'{self._string_provider.get_value(StringType.POKEMON_NAMES, self.item_id)} {_("Stats Graph")} (SkyTemple)', svg)) if not self._support_webview: graph_fallbck_box: Gtk.Box = self.builder.get_object( 'graph_fallbck_box') first = True for child in graph_fallbck_box: if first: first = False continue graph_fallbck_box.remove(child) stack.set_visible_child(graph_fallbck_box) stream = Gio.MemoryInputStream.new_from_bytes( GLib.Bytes.new( cairosvg.svg2png(bytestring=bytes(svg, 'utf-8'), dpi=72))) pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, None) img = Gtk.Image.new_from_pixbuf(pixbuf) img.show() graph_fallbck_box.pack_start(img, True, True, 0) else: stack.set_visible_child( self.builder.get_object('graph_webkit_box')) self._webview.reload() # type: ignore
def on_btn_import_code_clicked(self, *args): dialog = Gtk.FileChooserNative.new( _("Import Special Process Effect ASM Code..."), MainController.window(), Gtk.FileChooserAction.OPEN, None, None ) filter = Gtk.FileFilter() filter.set_name(_("Any Files")) filter.add_pattern("*") dialog.add_filter(filter) filter = Gtk.FileFilter() filter.set_name(_("armips ASM patches (*.asm)")) filter.add_pattern("*.asm") dialog.add_filter(filter) filter = Gtk.FileFilter() filter.set_name(_("Raw code (*.bin)")) filter.add_pattern("*.bin") dialog.add_filter(filter) response = dialog.run() fn = dialog.get_filename() dialog.destroy() if response == Gtk.ResponseType.ACCEPT: try: if fn.split('.')[-1].lower() == 'asm': with open_utf8(fn, 'r') as file: self.sp_effects.import_armips_effect_code(self._get_current_effect(), file.read()) else: with open(fn, 'rb') as file: self.sp_effects.set_effect_code(self._get_current_effect(), file.read()) self.module.mark_sp_effects_as_modified() except Exception as err: display_error( sys.exc_info(), str(err), _("Error importing ASM code.") )
def __init__(self, breakpoint_filename: str, file_manager: SsbFileManager): self._breakpoint_filename = breakpoint_filename self.file_manager = file_manager # This temporary breakpoint is set by the debugger while stepping # (is_in_unionall, opcode offset, script target type, script target slot) self._temporary_breakpoints: List[Tuple[Optional[bool], Optional[int], SsbRoutineType, int]] = [] self.new_breakpoint_mapping: Dict[str, List[int]] = {} if not os.path.exists(breakpoint_filename): self.breakpoint_mapping: Dict[str, List[int]] = {} else: try: with open_utf8(breakpoint_filename, 'r') as f: self.breakpoint_mapping = json.load(f) except ValueError: self.breakpoint_mapping = {} self._callbacks_added = [] self._callbacks_remvoed = []
def on_export_clicked(self, *args): save_diag = Gtk.FileChooserNative.new( "Export configuration...", self.window, Gtk.FileChooserAction.SAVE, None, None ) filter = Gtk.FileFilter() filter.set_name("JSON configuration file (*.json)") filter.add_mime_type("application/json") filter.add_pattern("*.json") save_diag.add_filter(filter) response = save_diag.run() fn = save_diag.get_filename() if '.' not in fn: fn += '.json' save_diag.destroy() if response == Gtk.ResponseType.ACCEPT: with open_utf8(fn, 'w') as f: json.dump(self.ui_reader.read(), f, indent=4, cls=EnumJsonEncoder)
def on_stats_import_clicked(self, *args): dialog = Gtk.FileChooserNative.new(_("Import CSV..."), MainController.window(), Gtk.FileChooserAction.OPEN, None, None) self._add_dialog_file_filters(dialog) response = dialog.run() fn = dialog.get_filename() dialog.destroy() if response == Gtk.ResponseType.ACCEPT: try: with open_utf8(fn, mode='r') as csv_file: is_missing = _('is missing in the CSV') content = list(csv.DictReader(csv_file)) if CSV_LEVEL not in content[0]: raise ValueError(f'{CSV_LEVEL} {is_missing}.') if CSV_EXP_POINTS not in content[0]: raise ValueError(f'{CSV_EXP_POINTS} {is_missing}.') if CSV_HP not in content[0]: raise ValueError(f'{CSV_HP} {is_missing}.') if CSV_ATK not in content[0]: raise ValueError(f'{CSV_ATK} {is_missing}.') if CSV_SP_ATK not in content[0]: raise ValueError(f'{CSV_SP_ATK} {is_missing}.') if CSV_DEF not in content[0]: raise ValueError(f'{CSV_DEF} {is_missing}.') if CSV_SP_DEF not in content[0]: raise ValueError(f'{CSV_SP_DEF} {is_missing}.') try: levels = [int(row[CSV_LEVEL]) for row in content] for row in content: int(row[CSV_EXP_POINTS]) int(row[CSV_HP]) int(row[CSV_ATK]) int(row[CSV_SP_ATK]) int(row[CSV_DEF]) int(row[CSV_SP_DEF]) except ValueError: raise ValueError(_('All values must be numbers.')) all_levels = set(range(1, 101)) if len(levels) != len(all_levels) or set( levels) != all_levels: raise ValueError( _("The CSV must contain one entry per level.")) content.sort(key=lambda row: int(row[CSV_LEVEL])) except BaseException as err: display_error(sys.exc_info(), _("Invalid CSV file:\n") + str(err), _("Error reading the CSV.")) return try: store: Gtk.ListStore = self.builder.get_object('stats_store') store.clear() for row in content: store.append([ row[CSV_LEVEL], row[CSV_EXP_POINTS], row[CSV_HP], row[CSV_ATK], row[CSV_SP_ATK], row[CSV_DEF], row[CSV_SP_DEF] ]) except BaseException as err: display_error( sys.exc_info(), _("Warning: The stats view might be corrupted now,\n" "but the data in ROM is still unchanged,\n" "simply reload this view!\n") + str(err), _("Error reading the CSV.")) return self._rebuild_stats()
def _explorerscript_save_inclusion_map(self, filename, entries): with open_utf8(filename, 'w') as f: json.dump(entries, f, indent=0)
def apply(self, patch: Union[Pmd2Patch, Pmd2SimplePatch], binaries: Dict[str, Pmd2Binary], patch_file_dir: str, stub_path: str, game_id: str): try: with tempfile.TemporaryDirectory() as tmp: shutil.copytree(patch_file_dir, tmp, dirs_exist_ok=True) # Build ASM file to run asm_entrypoint = '' # First read in stub with open(os.path.join(tmp, stub_path)) as f: asm_entrypoint += f.read() + '\n' if isinstance(patch, Pmd2SimplePatch): for replace in patch.string_replacements: fn = os.path.join(tmp, replace.filename) game = None for game_candidate in replace.games: if game_candidate.game_id == game_id: game = game_candidate if game is not None: with open(os.path.join(tmp, fn), 'r') as f: new_content = replace.regexp.sub( game.replace, f.read()) with open(os.path.join(tmp, fn), 'w') as f: f.write(new_content) # If it's a simple patch just output and re-import all binaries. for binary_name, binary in binaries.items(): binary_path = os.path.join(tmp, binary_name.split('/')[-1]) # Write binary to tmp dir with open(binary_path, 'wb') as f: try: f.write( get_binary_from_rom_ppmdu( self.rom, binary)) except ValueError as err: if binary_name.split( '/')[-1] == 'overlay_0036.bin': continue # We ignore if End's extra overlay is missing. raise err # For simple patches we also output the overlay table as y9.bin: binary_path = os.path.join(tmp, Y9_BIN) # Write binary to tmp dir with open(binary_path, 'wb') as f: f.write(self.rom.arm9OverlayTable) # Then include other includes for include in patch.includes: asm_entrypoint += f'.include "{os.path.join(tmp, include.filename)}"\n' # Build binary blocks if isinstance(patch, Pmd2Patch): for open_bin in patch.open_bins: binary = binaries[open_bin.filepath] binary_path = os.path.join( tmp, open_bin.filepath.split('/')[-1]) os.makedirs(os.path.dirname(binary_path), exist_ok=True) # Write binary to tmp dir with open(binary_path, 'wb') as f: f.write(get_binary_from_rom_ppmdu( self.rom, binary)) asm_entrypoint += f'.open "{binary_path}", 0x{binary.loadaddress:0x}\n' for include in open_bin.includes: asm_entrypoint += f'.include "{os.path.join(tmp, include.filename)}"\n' asm_entrypoint += '.close\n' # Write final asm file with open_utf8(os.path.join(tmp, ASM_ENTRYPOINT_FN), 'w') as f: f.write(asm_entrypoint) # Run armips original_cwd = os.getcwd() os.chdir(tmp) try: prefix = "" # Under Windows, try to load from SkyTemple _resources dir first. if sys.platform.startswith('win') and os.path.exists( os.path.join(get_resources_dir(), 'armips.exe')): prefix = os.path.join(get_resources_dir(), '') result = subprocess.Popen( [f'{prefix}armips', ASM_ENTRYPOINT_FN], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) retcode = result.wait() except FileNotFoundError as ex: raise ArmipsNotInstalledError( "ARMIPS could not be found. Make sure, that " "'armips' is inside your system's PATH.") from ex finally: # Restore cwd os.chdir(original_cwd) if retcode != 0: raise PatchError( "ARMIPS reported an error while applying the patch.", str(result.stdout.read(), 'utf-8'), str(result.stderr.read(), 'utf-8') if result.stderr else '') # Load the binaries back into the ROM opened_binaries = {} if isinstance(patch, Pmd2SimplePatch): # Read in all binaries again opened_binaries = binaries # Also read in arm9OverlayTable binary_path = os.path.join(tmp, Y9_BIN) with open(binary_path, 'rb') as f: self.rom.arm9OverlayTable = f.read() else: # Read opened binaries again for open_bin in patch.open_bins: opened_binaries[open_bin.filepath] = binaries[ open_bin.filepath] for binary_name, binary in opened_binaries.items(): binary_path = os.path.join(tmp, binary_name.split('/')[-1]) with open(binary_path, 'rb') as f: try: set_binary_in_rom_ppmdu(self.rom, binary, f.read()) except ValueError as err: if binary_name.split( '/')[-1] == 'overlay_0036.bin': continue # We ignore if End's extra overlay is missing. raise err except (PatchError, ArmipsNotInstalledError): raise except BaseException as ex: raise RuntimeError(f"Error while applying the patch: {ex}") from ex
def load(cls, fn: str) -> RandomizerConfig: with open_utf8(fn) as f: return cls.load_from_dict(json.loads(f.read()))
def _save(self): with open_utf8(self.config_file, 'w') as f: self.loaded_config.write(f)
def apply(self, patch: Union[Pmd2Patch, Pmd2SimplePatch], binaries: Dict[str, Pmd2Binary], patch_file_dir: str, stub_path: str, game_id: str, parameter_values: Dict[str, Union[int, str]]): with tempfile.TemporaryDirectory() as tmp: try: shutil.copytree(patch_file_dir, tmp, symlinks=True, dirs_exist_ok=True) set_rw_permission_folder(tmp) # Build ASM file to run asm_entrypoint = '' # First read in stub with open(os.path.join(tmp, stub_path)) as fi: asm_entrypoint += fi.read() + '\n' if isinstance(patch, Pmd2SimplePatch): for replace in patch.string_replacements: fn = os.path.join(tmp, replace.filename) game = None for game_candidate in replace.games: if game_candidate.game_id == game_id: game = game_candidate if game is not None: with open_utf8(os.path.join(tmp, fn), 'r') as fi: new_content = replace.regexp.sub( game.replace, fi.read()) with open_utf8(os.path.join(tmp, fn), 'w') as fi: fi.write(new_content) # If it's a simple patch just output and re-import all binaries. for binary_name, binary in binaries.items(): binary_path = os.path.join(tmp, binary_name.split('/')[-1]) # Write binary to tmp dir with open(binary_path, 'wb') as fib: try: fib.write( get_binary_from_rom_ppmdu( self.rom, binary)) except ValueError as err: if binary_name.split( '/')[-1] == 'overlay_0036.bin': continue # We ignore if End's extra overlay is missing. raise err # For simple patches we also output the overlay table as y9.bin: binary_path = os.path.join(tmp, Y9_BIN) # Write binary to tmp dir with open(binary_path, 'wb') as fib: fib.write(self.rom.arm9OverlayTable) # Then include other includes for include in patch.includes: asm_entrypoint += f'.include "{os.path.join(tmp, include.filename)}"\n' # Build binary blocks if isinstance(patch, Pmd2Patch): for open_bin in patch.open_bins: binary = binaries[open_bin.filepath] binary_path = os.path.join( tmp, open_bin.filepath.split('/')[-1]) os.makedirs(os.path.dirname(binary_path), exist_ok=True) # Write binary to tmp dir with open(binary_path, 'wb') as fib: fib.write( get_binary_from_rom_ppmdu(self.rom, binary)) asm_entrypoint += f'.open "{binary_path}", 0x{binary.loadaddress:0x}\n' for include in open_bin.includes: asm_entrypoint += f'.include "{os.path.join(tmp, include.filename)}"\n' asm_entrypoint += '.close\n' # Write final asm file with open_utf8(os.path.join(tmp, ASM_ENTRYPOINT_FN), 'w') as fi: fi.write(asm_entrypoint) # Build parameters for equ parameters = [] for param_name, param_value in parameter_values.items(): parameters += [ '-equ', param_name, f'"{param_value}"' if isinstance( param_value, str) else str(param_value) ] # Run armips try: prefix = "" # Under Windows, try to load from SkyTemple _resources dir first. if sys.platform.startswith('win') and os.path.exists( os.path.join(get_resources_dir(), 'armips.exe')): prefix = os.path.join(get_resources_dir(), '') exec_name = os.getenv('SKYTEMPLE_ARMIPS_EXEC', f'{prefix}armips') cmd_line = [exec_name, ASM_ENTRYPOINT_FN] + parameters if os.getenv('SKYTEMPLE_DEBUG_ARMIPS_OUTPUT', False): print("ARMIPS CMDLINE:") print(cmd_line) result = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tmp) retcode = result.wait() except FileNotFoundError as ex: raise make_user_err( ArmipsNotInstalledError, _("ARMIPS could not be found. Make sure, that " "'armips' is inside your system's PATH.")) from ex if os.getenv('SKYTEMPLE_DEBUG_ARMIPS_OUTPUT', False): print("ARMIPS OUTPUT:") if result is not None: print(str(result.stdout.read(), 'utf-8')) # type: ignore print( str(result.stderr.read(), 'utf-8') if result.stderr else '') # type: ignore if retcode != 0: raise make_user_err( PatchError, _("ARMIPS reported an error while applying the patch." ), str(result.stdout.read(), 'utf-8'), str(result.stderr.read(), 'utf-8') # type: ignore if result.stderr else '') # type: ignore # Load the binaries back into the ROM opened_binaries = {} if isinstance(patch, Pmd2SimplePatch): # Read in all binaries again opened_binaries = binaries # Also read in arm9OverlayTable binary_path = os.path.join(tmp, Y9_BIN) with open(binary_path, 'rb') as fib: self.rom.arm9OverlayTable = fib.read() else: # Read opened binaries again for open_bin in patch.open_bins: opened_binaries[open_bin.filepath] = binaries[ open_bin.filepath] for binary_name, binary in opened_binaries.items(): binary_path = os.path.join(tmp, binary_name.split('/')[-1]) with open(binary_path, 'rb') as fib: try: set_binary_in_rom_ppmdu(self.rom, binary, fib.read()) except ValueError as err: if binary_name.split( '/')[-1] == 'overlay_0036.bin': continue # We ignore if End's extra overlay is missing. raise err except (PatchError, ArmipsNotInstalledError): raise except BaseException as ex: raise RuntimeError(f( _("Error while applying the patch: {ex}"))) from ex
def save(self, index: int, config_dir: str): self.variables_changed_but_not_saved = False vars = {k.name: v for k, v in self._variable_cache.items()} with open_utf8(os.path.join(config_dir, f'vars.{index}.json'), 'w') as f: json.dump(vars, f)