diff --git a/CHANGELOG.md b/CHANGELOG.md index e22f765f6..e257efecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ All notable changes to this project will be documented in this file. - Export FLA context menu on SWFs - Window icons for various dialogs including save/open/export/import - [#873] Context menu items are organized with separators and the order is more intuitive +- [#1644] Save all button - has priority over standard Save button ### Fixed - Debugger - getting children of top level variables @@ -99,6 +100,9 @@ All notable changes to this project will be documented in this file. - Run/Debug command - executed SWF temp files (`~ffdec_run...swf` etc.) are now generated in the directory where original SWF resides to allow loading relative assets - [#2228] AS1/2/3 bitwise operations use hexadecimal operands +- Save to EXE moved to tools tab +- Save (not save as) button are now available only when there's anything to save + when the selected SWF is modified. Similar for Save all button. ### Removed - Proxy feature. It was not working since today almost every page uses HTTPS. Also Flash is limited in browsers. @@ -3467,6 +3471,7 @@ Major version of SWF to XML export changed to 2. [#2260]: https://www.free-decompiler.com/flash/issues/2260 [#1290]: https://www.free-decompiler.com/flash/issues/1290 [#873]: https://www.free-decompiler.com/flash/issues/873 +[#1644]: https://www.free-decompiler.com/flash/issues/1644 [#2149]: https://www.free-decompiler.com/flash/issues/2149 [#2172]: https://www.free-decompiler.com/flash/issues/2172 [#2174]: https://www.free-decompiler.com/flash/issues/2174 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index ab6a279d4..b2f33d5d3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -1244,6 +1244,31 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { os.write(newCompressedData); } } + + public void saveNestedDefineBinaryData() { + Map chtags = getCharacters(false); + for (CharacterTag t : chtags.values()) { + if (t instanceof DefineBinaryDataTag) { + DefineBinaryDataTag dbd = (DefineBinaryDataTag) t; + if (dbd.innerSwf != null && dbd.innerSwf.isModified()) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + SWF swf = (SWF) dbd.innerSwf; + swf.saveNestedDefineBinaryData(); + swf.saveTo(baos); + byte[] data = baos.toByteArray(); + swf.binaryData.setDataBytes(new ByteArrayRange(data)); + swf.binaryData.setModified(true); + swf.binaryData.getTopLevelBinaryData().pack(); + dbd.innerSwf.clearModified(); + } catch (IOException ex) { + Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "Cannot save SWF", ex); + } + dbd.setModified(true); + } + } + } + } public byte[] getHeaderBytes() { return getHeaderBytes(compression, gfx, encrypted); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java index 6e922c6e9..dccde2baa 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java @@ -280,5 +280,5 @@ public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInter @Override public String getClassExportFileName(String className) { return className; - } + } } diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index 05e159839..6ddf3c986 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -145,6 +145,12 @@ public abstract class MainFrameMenu implements MenuBuilder { Main.startSaving(savedFile); Bundle bundle = openable.getOpenableList().bundle; if (!bundle.isReadOnly()) { + + if (openable instanceof SWF) { + SWF swf = (SWF) openable; + swf.saveNestedDefineBinaryData(); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { openable.saveTo(baos); @@ -159,8 +165,9 @@ public abstract class MainFrameMenu implements MenuBuilder { } else if ((openable instanceof SWF) && ((SWF) openable).binaryData != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - openable.saveTo(baos); SWF swf = (SWF) openable; + swf.saveNestedDefineBinaryData(); + swf.saveTo(baos); byte[] data = baos.toByteArray(); swf.binaryData.setDataBytes(new ByteArrayRange(data)); swf.binaryData.setModified(true); @@ -172,6 +179,10 @@ public abstract class MainFrameMenu implements MenuBuilder { } else if (openable.getFile() == null) { saved = saveAs(openable, SaveFileMode.SAVEAS); } else { + if (openable instanceof SWF) { + SWF swf = (SWF) openable; + swf.saveNestedDefineBinaryData(); + } try { Main.saveFile(openable, openable.getFile()); saved = true; @@ -203,6 +214,30 @@ public abstract class MainFrameMenu implements MenuBuilder { } return saveOpenable(openable); } + + protected boolean saveAllActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return false; + } + if (mainFrame.getPanel().checkEdited()) { + return false; + } + List openableLists = mainFrame.getPanel().getSwfs(); + List allOpenables = new ArrayList<>(); + for (OpenableList list : openableLists) { + for (Openable openable : list.items) { + allOpenables.add(openable); + } + } + for (Openable openable : allOpenables) { + if (openable.isModified()) { + if (!saveOpenable(openable)) { + return false; + } + } + } + return true; + } protected boolean saveAsActionPerformed(ActionEvent evt) { if (Main.isWorking()) { @@ -984,6 +1019,8 @@ public abstract class MainFrameMenu implements MenuBuilder { MainPanel mainPanel = mainFrame.getPanel(); boolean swfLoaded = mainPanel != null ? !mainPanel.getSwfs().isEmpty() : false; boolean swfIsNew = openableSelected && openable.getOpenableList() != null && openable.getOpenableList().sourceInfo.isEmpty(); + boolean anythingModified = mainPanel != null ? mainPanel.isModified() : false; + boolean swfModified = swf == null ? false : swf.isModified(); boolean allSameSwf = true; boolean allSameOpenable = true; @@ -1026,11 +1063,12 @@ public abstract class MainFrameMenu implements MenuBuilder { setMenuEnabled("_/open", !isWorking); setMenuEnabled("/file/open", !isWorking); - setMenuEnabled("_/save", openableSelected && !isWorking); - setMenuEnabled("/file/save", openableSelected && !isWorking); + setMenuEnabled("_/save", openableSelected && !isWorking && swfModified); + setMenuEnabled("/file/save", openableSelected && !isWorking && swfModified); setMenuEnabled("_/saveAs", openableSelected && !isWorking); setMenuEnabled("/file/saveAs", openableSelected && !isWorking); - setMenuEnabled("/file/saveAsExe", swfSelected && !isWorking); + setMenuEnabled("_/saveAll", !isWorking && anythingModified); + setMenuEnabled("/file/saveAll", !isWorking && anythingModified); setMenuEnabled("_/close", openableOrListSelected && !isWorking); setMenuEnabled("/file/close", openableOrListSelected && !isWorking); setMenuEnabled("_/closeAll", swfLoaded && !isWorking); @@ -1074,6 +1112,8 @@ public abstract class MainFrameMenu implements MenuBuilder { setMenuEnabled("/tools/abcExplorer", isAs3); setMenuEnabled("/tools/gotoDocumentClass", hasAbc); + setMenuEnabled("/tools/saveAsExe", swfSelected && !isWorking); + /*setMenuEnabled("/tools/debugger/debuggerSwitch", hasAbc); setMenuChecked("/tools/debugger/debuggerSwitch", hasDebugger); setMenuEnabled("/tools/debugger/debuggerReplaceTrace", hasAbc && hasDebugger);*/ @@ -1139,6 +1179,7 @@ public abstract class MainFrameMenu implements MenuBuilder { addMenuItem("_", null, null, null, 0, null, false, null, false); addMenuItem("_/open", translate("menu.file.open"), "open32", this::openActionPerformed, PRIORITY_TOP, this::loadRecent, false, null, false); addMenuItem("_/save", translate("menu.file.save"), "save32", this::saveActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("_/saveAll", translate("menu.file.saveAll"), "saveall32", this::saveAllActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("_/saveAs", translate("menu.file.saveas"), "saveas32", this::saveAsActionPerformed, PRIORITY_TOP, null, true, null, false); addSeparator("_"); addMenuItem("_/exportFla", translate("menu.file.export.fla"), "exportfla32", this::exportFlaActionPerformed, PRIORITY_TOP, null, true, null, false); @@ -1165,13 +1206,15 @@ public abstract class MainFrameMenu implements MenuBuilder { finishMenu("/file/open"); } - addMenuItem("/file/save", translate("menu.file.save"), "save32", this::saveActionPerformed, PRIORITY_TOP, null, true, new HotKey("CTRL+SHIFT+S"), false); + addMenuItem("/file/new", translate("menu.file.new"), "newswf32", this::newActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("/file/saveAll", translate("menu.file.saveAll"), "saveall32", this::saveAllActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("/file/save", translate("menu.file.save"), "save16", this::saveActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+S"), false); addMenuItem("/file/saveAs", translate("menu.file.saveas"), "saveas16", this::saveAsActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+A"), false); - addMenuItem("/file/saveAsExe", translate("menu.file.saveasexe"), "saveasexe16", this::saveAsExeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); addMenuItem("/file/reload", translate("menu.file.reload"), "reload16", this::reloadActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+R"), false); addMenuItem("/file/reloadAll", translate("menu.file.reloadAll"), "reload16", this::reloadAllActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/new", translate("menu.file.new"), "newswf32", this::newActionPerformed, PRIORITY_TOP, null, true, null, false); - + addMenuItem("/file/close", translate("menu.file.close"), "close32", this::closeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/closeAll", translate("menu.file.closeAll"), "closeall32", this::closeAllActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+X"), false); + addSeparator("/file"); addMenuItem("/file/export", translate("menu.export"), null, null, 0, null, false, null, false); @@ -1212,9 +1255,7 @@ public abstract class MainFrameMenu implements MenuBuilder { finishMenu("/file/view"); addSeparator("/file"); - addMenuItem("/file/close", translate("menu.file.close"), "close32", this::closeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/closeAll", translate("menu.file.closeAll"), "closeall32", this::closeAllActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+X"), false); - + if (!supportsAppMenu()) { addSeparator("/file"); addMenuItem("/file/exit", translate("menu.file.exit"), "exit32", this::exitActionPerformed, PRIORITY_TOP, null, true, null, false); @@ -1268,6 +1309,7 @@ public abstract class MainFrameMenu implements MenuBuilder { if (Platform.isWindows()) { addMenuItem("/tools/searchMemory", translate("menu.tools.searchMemory"), "loadmemory16", this::searchMemoryActionPerformed, PRIORITY_MEDIUM, null, true, null, false); } + addMenuItem("/tools/saveAsExe", translate("menu.file.saveasexe"), "saveasexe16", this::saveAsExeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); //addMenuItem("/tools/searchCache", translate("menu.tools.searchCache"), "loadcache16", this::searchCacheActionPerformed, PRIORITY_MEDIUM, null, true, null); addMenuItem("/tools/deobfuscation", translate("menu.tools.deobfuscation"), "deobfuscate16", null, 0, null, false, null, false); diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 1c6bafc76..0e5a2969e 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -4624,6 +4624,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se reload(true); updateMissingNeededCharacters(); pinsPanel.refresh(); + updateUiWithCurrentOpenable(); } public void refreshDecompiled() { @@ -4921,8 +4922,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se try { Tag newTag = new ImageImporter().importImage(it, data, create ? -1 : 0); SWF swf = it.getSwf(); - if (newTag != null) { - refreshTree(swf); + refreshTree(swf); + if (newTag != null) { setTagTreeSelectedNode(getCurrentTree(), newTag); } swf.clearImageCache(); @@ -4963,8 +4964,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } } SWF swf = st.getSwf(); - if (newTag != null) { - refreshTree(swf); + refreshTree(swf); + if (newTag != null) { setTagTreeSelectedNode(getCurrentTree(), newTag); } @@ -6190,6 +6191,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se tagTree.repaint(); tagListTree.repaint(); reload(true); + updateUiWithCurrentOpenable(); } public void showGenericTag(Tag tag) { diff --git a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java index a815ff2b3..acc8d0737 100644 --- a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java @@ -1300,7 +1300,7 @@ public class ActionPanel extends JPanel implements SearchListener