Exemple #1
0
 def export_sprite(self, wan: bytes, dir_fn: str):
     with tempfile.TemporaryDirectory() as tmp_path:
         tmp_path = os.path.join(tmp_path, 'tmp.wan')
         with open(tmp_path, 'wb') as f:
             f.write(wan)
         AsyncTaskRunner.instance().run_task(self._run_gfxcrunch([tmp_path, dir_fn]))
         self._run_window()
         if self.status != GfxcrunchStatus.SUCCESS:
             raise RuntimeError(_("The gfxcrunch process failed."))
Exemple #2
0
    def on_export_clicked(self, w: Gtk.MenuToolButton):
        dialog = Gtk.FileChooserNative.new(_("Export font in folder..."),
                                           MainController.window(),
                                           Gtk.FileChooserAction.SELECT_FOLDER,
                                           '_Save', None)

        response = dialog.run()
        fn = dialog.get_filename()
        dialog.destroy()

        if response == Gtk.ResponseType.ACCEPT:
            xml, tables = self.font.export_to_xml()
            with open(os.path.join(fn, f'char_tables.xml'), 'w') as f:
                f.write(prettify(xml))
            for i, table in tables.items():
                table.save(os.path.join(fn, f'table-{i}.png'))
Exemple #3
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 #4
0
    def export_a_sprite__wan(self, sprite: bytes):
        dialog = Gtk.FileChooserNative.new(_("Export WAN sprite..."),
                                           MainController.window(),
                                           Gtk.FileChooserAction.SAVE, None,
                                           None)
        filter = Gtk.FileFilter()
        filter.set_name(_("WAN sprite (*.wan)"))
        filter.add_pattern("*.wan")
        dialog.add_filter(filter)

        response = dialog.run()
        fn = dialog.get_filename()

        dialog.destroy()

        if response == Gtk.ResponseType.ACCEPT:
            fn = add_extension_if_missing(fn, 'wan')
            with open(fn, 'wb') as f:
                f.write(sprite)
Exemple #5
0
    def on_btn_export_clicked(self, *args):
        dialog = Gtk.FileChooserNative.new(
            _("Export dungeon tileset..."),
            MainController.window(),
            Gtk.FileChooserAction.SELECT_FOLDER,
            None, None
        )

        response = dialog.run()
        fn = dialog.get_filename()
        dialog.destroy()
        assert self.dtef is not None

        if response == Gtk.ResponseType.ACCEPT:
            try:
                # Write XML
                with open(os.path.join(fn, 'tileset.dtef.xml'), 'w') as f:
                    f.write(prettify(self.dtef.get_xml()))
                # Write Tiles
                var0, var1, var2, rest = self.dtef.get_tiles()
                var0fn, var1fn, var2fn, restfn = self.dtef.get_filenames()
                var0.save(os.path.join(fn, var0fn))
                var1.save(os.path.join(fn, var1fn))
                var2.save(os.path.join(fn, var2fn))
                rest.save(os.path.join(fn, restfn))
                shutil.copy(get_template_file(), os.path.join(fn, 'template.png'))

                md = SkyTempleMessageDialog(MainController.window(),
                                            Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO,
                                            Gtk.ButtonsType.OK, _("The tileset was successfully exported."),
                                            title=_("Success!"), is_success=True)
                md.run()
                md.destroy()
            except BaseException as e:
                display_error(
                    sys.exc_info(),
                    str(e),
                    _("Error exporting the tileset.")
                )
Exemple #6
0
    def on_btn_export_clicked(self, *args):
        dialog: Gtk.Dialog = self.builder.get_object('export_dialog')
        dialog.resize(640, 560)
        dialog.set_attached_to(SkyTempleMainController.window())
        dialog.set_transient_for(SkyTempleMainController.window())

        # Fill Pokémon tree
        store: Gtk.TreeStore = self.builder.get_object('export_dialog_store')
        store.clear()
        monster_entries_by_base_id: Dict[int, List[MdEntry]] = {}
        for entry in self.module.monster_md.entries:
            if entry.md_index_base not in monster_entries_by_base_id:
                monster_entries_by_base_id[entry.md_index_base] = []
            monster_entries_by_base_id[entry.md_index_base].append(entry)

        for baseid, entry_list in monster_entries_by_base_id.items():
            name = self.module.project.get_string_provider().get_value(StringType.POKEMON_NAMES, baseid)
            entry_main_tree = self.module.generate_entry__entity_root(baseid, name)
            ent_root = store.append(None, [
                -1, -1, False, entry_main_tree[0],
                entry_main_tree[1], False, False
            ])

            for entry in entry_list:
                entry_main_tree = self.module.generate_entry__entry(entry.md_index, entry.gender)
                store.append(
                    ent_root, [
                        entry_main_tree[4], -1, True, entry_main_tree[0],
                        entry_main_tree[1], False, False
                    ]
                )

        names, md_gender1, md_gender2, moveset, moveset2, stats, portraits, portraits2 = self.module.get_export_data(self.entry)
        we_are_gender1 = md_gender1 == self.entry

        if md_gender2 is None:
            sw: Gtk.Switch = self.builder.get_object('export_type_other_gender')
            sw.set_active(False)
            sw.set_sensitive(False)

        if portraits is None:
            sw: Gtk.Switch = self.builder.get_object('export_type_portraits_current_gender')
            sw.set_active(False)
            sw.set_sensitive(False)

        if portraits2 is None:
            sw: Gtk.Switch = self.builder.get_object('export_type_portraits_other_gender')
            sw.set_active(False)
            sw.set_sensitive(False)

        if stats is None:
            sw: Gtk.Switch = self.builder.get_object('export_type_stats')
            sw.set_active(False)
            sw.set_sensitive(False)

        if moveset is None:
            sw: Gtk.Switch = self.builder.get_object('export_type_moveset1')
            sw.set_active(False)
            sw.set_sensitive(False)

        if moveset2 is None:
            sw: Gtk.Switch = self.builder.get_object('export_type_moveset2')
            sw.set_active(False)
            sw.set_sensitive(False)

        resp = dialog.run()
        dialog.hide()

        if resp == Gtk.ResponseType.APPLY:
            # Create output XML

            if not self.builder.get_object('export_type_current_gender').get_active():
                if we_are_gender1:
                    md_gender1 = None
                else:
                    md_gender2 = None
            if not self.builder.get_object('export_type_other_gender').get_active():
                if not we_are_gender1:
                    md_gender1 = None
                else:
                    md_gender2 = None
            if not self.builder.get_object('export_type_names').get_active():
                names = None
            if not self.builder.get_object('export_type_stats').get_active():
                stats = None
            if not self.builder.get_object('export_type_moveset1').get_active():
                moveset = None
            if not self.builder.get_object('export_type_moveset2').get_active():
                moveset2 = None
            if not self.builder.get_object('export_type_portraits_current_gender').get_active():
                if we_are_gender1:
                    portraits = None
                else:
                    portraits2 = None
            if not self.builder.get_object('export_type_portraits_other_gender').get_active():
                if not we_are_gender1:
                    portraits = None
                else:
                    portraits2 = None

            xml = monster_xml_export(
                self.module.project.get_rom_module().get_static_data().game_version,
                md_gender1, md_gender2, names, moveset, moveset2, stats, portraits, portraits2
            )

            # 1. Export to file
            if self.builder.get_object('export_file_switch').get_active():
                save_diag = Gtk.FileChooserNative.new(
                    _("Export Pokémon as..."),
                    SkyTempleMainController.window(),
                    Gtk.FileChooserAction.SAVE,
                    None, None
                )

                add_dialog_xml_filter(save_diag)
                response = save_diag.run()
                fn = save_diag.get_filename()
                if '.' not in fn:
                    fn += '.xml'
                save_diag.destroy()

                if response == Gtk.ResponseType.ACCEPT:
                    with open(fn, 'w') as f:
                        f.write(prettify(xml))
                else:
                    md = SkyTempleMessageDialog(SkyTempleMainController.window(),
                                                Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING,
                                                Gtk.ButtonsType.OK, "Export was canceled.")
                    md.set_position(Gtk.WindowPosition.CENTER)
                    md.run()
                    md.destroy()
                    return

            # 2. Import to selected Pokémon
            selected_monsters: List[int] = []
            def collect_monsters_recurse(titer: Optional[Gtk.TreeIter]):
                for i in range(store.iter_n_children(titer)):
                    child = store.iter_nth_child(titer, i)
                    if store[child][2] and store[child][5]:  # is floor and is selected
                        selected_monsters.append(store[child][0])
                    collect_monsters_recurse(child)

            collect_monsters_recurse(None)
            self.module.import_from_xml(selected_monsters, xml)
Exemple #7
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