diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe627b71..277fd73f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ All notable changes to this project will be documented in this file. - [PR194] Support for XDG base directory specification (env variable `XDG_CONFIG_HOME`) - FLA export - ImportAssets/2 tag support - FLA export - export in frame 1 flag support +- [#2260] GFX - Configure path resolving dialog for file paths that use prefixes like `data:` ### Fixed - Debugger - getting children of top level variables diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/CustomConfigurationKeys.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/CustomConfigurationKeys.java index bb14c988d..e8592ddc3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/CustomConfigurationKeys.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/CustomConfigurationKeys.java @@ -29,4 +29,5 @@ public class CustomConfigurationKeys { public static final String KEY_LOADED_IMPORT_ASSETS = "loadedImportAssets"; public static final String KEY_ABC_DEPENDENCIES = "abcDependencies"; public static final String KEY_BREAKPOINTS = "breakpoints"; + public static final String KEY_PATH_RESOLVING = "pathResolving"; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/AbstractGfxImageTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/AbstractGfxImageTag.java index 7d9f43d95..12c752bd7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/AbstractGfxImageTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/AbstractGfxImageTag.java @@ -17,6 +17,9 @@ package com.jpexs.decompiler.flash.tags.gfx; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.CustomConfigurationKeys; +import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration; import com.jpexs.decompiler.flash.gfx.TgaSupport; import com.jpexs.decompiler.flash.tags.base.ImageTag; import com.jpexs.decompiler.flash.tags.gfx.enums.FileFormatType; @@ -24,6 +27,7 @@ import com.jpexs.helpers.ByteArrayRange; import java.awt.image.BufferedImage; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import javax.imageio.ImageIO; @@ -47,9 +51,50 @@ public abstract class AbstractGfxImageTag extends ImageTag { } protected BufferedImage getExternalBufferedImage(String fileName, int bitmapFormat) { - Path imagePath = getSwf().getFile() == null ? null : Paths.get(getSwf().getFile()).getParent().resolve(Paths.get(fileName)); + Path imagePath = null; + + try { + imagePath = getSwf().getFile() == null ? null : Paths.get(getSwf().getFile()).getParent().resolve(Paths.get(fileName)); + } catch (InvalidPathException ip) { + //ignore + } if (imagePath == null || !imagePath.toFile().exists()) { - return null; + + SwfSpecificCustomConfiguration cc = Configuration.getSwfSpecificCustomConfiguration(getSwf().getShortPathTitle()); + if (cc == null) { + return null; + } + String paths = cc.getCustomData(CustomConfigurationKeys.KEY_PATH_RESOLVING, ""); + if (paths.trim().isEmpty()) { + return null; + } + String[] rows = paths.trim().split("\r\n"); + boolean found = false; + for (String row : rows) { + String prefix = ""; + String searchPath; + if (row.contains("|")) { + prefix = row.substring(0, row.indexOf("|")); + searchPath = row.substring(row.indexOf("|") + 1); + } else { + searchPath = row; + } + String fileNameNoPrefix = fileName; + if (!prefix.isEmpty() && fileName.startsWith(prefix)) { + fileNameNoPrefix = fileName.substring(prefix.length()); + } + if (!searchPath.isEmpty()) { + Path newImagePath = Paths.get(searchPath).resolve(fileNameNoPrefix); + if (newImagePath.toFile().exists()) { + found = true; + imagePath = newImagePath; + break; + } + } + } + if (!found) { + return null; + } } byte[] imageData; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalImage.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalImage.java index 9a65822c5..175e85e30 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalImage.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalImage.java @@ -172,7 +172,7 @@ public class DefineExternalImage extends AbstractGfxImageTag { if (shortFormat) { //Just guessing how this may work... return exportName + "." + FileFormatType.fileFormatExtension(bitmapFormat); - } + } return fileName; } diff --git a/src/com/jpexs/decompiler/flash/gui/PathResolvingDialog.java b/src/com/jpexs/decompiler/flash/gui/PathResolvingDialog.java new file mode 100644 index 000000000..57c1db617 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/PathResolvingDialog.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010-2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.CustomConfigurationKeys; +import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.FlowLayout; +import java.awt.Window; +import java.awt.event.ActionEvent; +import javax.swing.JButton; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +/** + * + * @author JPEXS + */ +public class PathResolvingDialog extends AppDialog { + private final JEditorPane editor; + private final JButton okButton = new JButton(translate("button.ok")); + private final JButton cancelButton = new JButton(translate("button.cancel")); + + private int result = ERROR_OPTION; + private final SWF swf; + + public PathResolvingDialog(SWF swf, Window owner) { + super(owner); + setTitle(translate("dialog.title")); + setDefaultCloseOperation(HIDE_ON_CLOSE); + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + cnt.add(new JLabel("" + translate("info") + ""), BorderLayout.NORTH); + editor = new JEditorPane(); + cnt.add(new JScrollPane(editor), BorderLayout.CENTER); + editor.setContentType("text/plain"); + + okButton.addActionListener(this::okButtonActionPerformed); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + buttonsPanel.add(okButton); + buttonsPanel.add(cancelButton); + cnt.add(buttonsPanel, BorderLayout.SOUTH); + + SwfSpecificCustomConfiguration cc = Configuration.getSwfSpecificCustomConfiguration(swf.getShortPathTitle()); + String pathResolving = ""; + if (cc != null) { + pathResolving = cc.getCustomData(CustomConfigurationKeys.KEY_PATH_RESOLVING, ""); + } + editor.setText(pathResolving); + + setSize(800, 600); + View.centerScreen(this); + View.setWindowIcon(this); + getRootPane().setDefaultButton(okButton); + setModal(true); + this.swf = swf; + } + + private void okButtonActionPerformed(ActionEvent evt) { + result = OK_OPTION; + SwfSpecificCustomConfiguration cc = Configuration.getOrCreateSwfSpecificCustomConfiguration(swf.getShortPathTitle()); + String txt = editor.getText(); + txt = txt.replace("\r\n", "\n"); + txt = txt.replace("\n", "\r\n"); + cc.setCustomData(CustomConfigurationKeys.KEY_PATH_RESOLVING, txt); + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + result = CANCEL_OPTION; + setVisible(false); + } + + public int showDialog() { + setVisible(true); + return result; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index bb0e19c87..fcf1ab86e 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1269,4 +1269,6 @@ warning.cleanAbc = This action will remove items from ABC which have zero usages Some kinds of obfuscated SWFs could be damaged this way.\r\n\ Use it at your own risk. Do you want to continue? -tagInfo.idType = Type of the id \ No newline at end of file +tagInfo.idType = Type of the id + +contextmenu.configurePathResolving = Configure path resolving... \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index 9a769f55d..316c9c849 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1244,4 +1244,6 @@ warning.cleanAbc = Tato akce odstran\u00ed z ABC polo\u017eky, kter\u00e9 maj\u0 N\u011bkter\u00e9 druhy obfuskovan\u00fdch SWF to m\u016f\u017ee po\u0161kodit.\r\n\ Pou\u017e\u00edvejte ji na vlastn\u00ed riziko. Chcete pokra\u010dovat? -tagInfo.idType = Typ id \ No newline at end of file +tagInfo.idType = Typ id + +contextmenu.configurePathResolving = Nastavit resolvov\u00e1n\u00ed cest... \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/PathResolvingDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/PathResolvingDialog.properties new file mode 100644 index 000000000..844a6a032 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/PathResolvingDialog.properties @@ -0,0 +1,22 @@ +# Copyright (C) 2024 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +dialog.title = Path resolving +info = Set directories where assets will be searched when not found in SWFs path.\r\n\ + You can use pipe "|" to separate prefix that the path must have, like "data:|C:\\MyData\\Dir"\ + when you have paths starting with "data:" prefix.\r\nOne path per line. This is currently used only in GFX tags. + +button.ok = OK +button.cancel = Cancel \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/PathResolvingDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/PathResolvingDialog_cs.properties new file mode 100644 index 000000000..33559365d --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/PathResolvingDialog_cs.properties @@ -0,0 +1,22 @@ +# Copyright (C) 2024 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +dialog.title = Resolvov\u00e1n\u00ed cest +info = Nastav\u00ed slo\u017eky kde budou hled\u00e1ny assety, kter\u00e9 se nepoda\u0159\u00ed nal\u00e9zt v cest\u011b SWF.\r\n\ + M\u016f\u017eete pou\u017e\u00edt rouru "|" pro odd\u011blen\u00ed p\u0159edpony, kterou cesta mus\u00ed m\u00edt jako "data:|C:\\MyData\\Dir"\ + pokud chcete m\u00edt cesty za\u010d\u00ednaj\u00edc\u00ed p\u0159edponou "data:". Co cesta to jeden \u0159\u00e1dek. Toto se moment\u00e1ln\u011b pou\u017e\u00edv\u00e1 jen v GFX taz\u00edch. + +button.ok = OK +button.cancel = Storno \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index e482edc83..bc2dce357 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -40,6 +40,7 @@ import com.jpexs.decompiler.flash.gui.ClipboardType; import com.jpexs.decompiler.flash.gui.CollectDepthAsSpritesDialog; import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.MainPanel; +import com.jpexs.decompiler.flash.gui.PathResolvingDialog; import com.jpexs.decompiler.flash.gui.ReplaceCharacterDialog; import com.jpexs.decompiler.flash.gui.SelectFramePositionDialog; import com.jpexs.decompiler.flash.gui.SelectTagPositionDialog; @@ -171,6 +172,8 @@ public class TagTreeContextMenu extends JPopupMenu { private final MainPanel mainPanel; + private JMenuItem configurePathResolvingMenuItem; + private JMenuItem setClassToCharacterMappingMenuItem; private JMenuItem expandRecursiveMenuItem; @@ -368,6 +371,12 @@ public class TagTreeContextMenu extends JPopupMenu { } add(changeCharsetMenu); + configurePathResolvingMenuItem = new JMenuItem(mainPanel.translate("contextmenu.configurePathResolving")); + configurePathResolvingMenuItem.addActionListener(this::configurePathResolvingActionPerformed); + configurePathResolvingMenuItem.setIcon(View.getIcon("settings16")); + add(configurePathResolvingMenuItem); + + removeMenuItem = new JMenuItem(mainPanel.translate("contextmenu.remove") + " (DEL)"); removeMenuItem.addActionListener((ActionEvent e) -> { removeItemActionPerformed(e, false); @@ -1125,6 +1134,7 @@ public class TagTreeContextMenu extends JPopupMenu { unpinAllMenuItem.setVisible(false); unpinOthersMenuItem.setVisible(false); + configurePathResolvingMenuItem.setVisible(false); removeMenuItem.setVisible(canRemove); removeWithDependenciesMenuItem.setVisible(canRemove && !allDoNotHaveDependencies); cloneMenuItem.setVisible(allSelectedIsTagOrFrame && allSelectedSameParent); @@ -1404,6 +1414,9 @@ public class TagTreeContextMenu extends JPopupMenu { abcExplorerMenuItem.setVisible(true); cleanAbcMenuItem.setVisible(true); } + if (swf.gfx) { + configurePathResolvingMenuItem.setVisible(true); + } } if (firstItem instanceof Tag) { @@ -5315,6 +5328,13 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.repaintTree(); } + + public void configurePathResolvingActionPerformed(ActionEvent evt) { + SWF item = (SWF) getCurrentItem(); + PathResolvingDialog dialog = new PathResolvingDialog(item, Main.getDefaultDialogsOwner()); + dialog.showDialog(); + } + public void changeCharsetActionPerformed(ActionEvent evt) { SWF item = (SWF) getCurrentItem(); String newCharset = ((JMenuItem) evt.getSource()).getText();