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
Exemple #3
0
 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)
Exemple #5
0
    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."))
Exemple #6
0
    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
Exemple #9
0
 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
Exemple #12
0
 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)
Exemple #16
0
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)
Exemple #17
0
    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
Exemple #19
0
 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
Exemple #20
0
    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
Exemple #21
0
    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)
Exemple #24
0
    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)
Exemple #26
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
Exemple #27
0
 def load(cls, fn: str) -> RandomizerConfig:
     with open_utf8(fn) as f:
         return cls.load_from_dict(json.loads(f.read()))
Exemple #28
0
 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
Exemple #30
0
 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)