diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BinarySWFBundle.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BinarySWFBundle.java index b854922e9..69ba5d645 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BinarySWFBundle.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BinarySWFBundle.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash; import com.jpexs.helpers.SwfHeaderStreamSearch; @@ -81,4 +82,14 @@ public class BinarySWFBundle implements SWFBundle { public String getExtension() { return "bin"; } + + @Override + public boolean isReadOnly() { + return true; + } + + @Override + public boolean putSWF(String key,InputStream is) { + throw new UnsupportedOperationException("Save not supported for this type of bundle"); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWC.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWC.java index 207fbf967..d80a37a0b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWC.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWC.java @@ -16,10 +16,9 @@ */ package com.jpexs.decompiler.flash; +import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.xml.parsers.SAXParser; @@ -36,38 +35,44 @@ public class SWC extends ZippedSWFBundle { public SWC(InputStream is) throws IOException { super(is); + } + + public SWC(File filename) throws IOException { + super(filename); + } + + @Override + protected void initBundle(InputStream is, File filename) throws IOException { + super.initBundle(is, filename); keySet.clear(); this.is.reset(); ZipInputStream zip = new ZipInputStream(this.is); ZipEntry entry; - try { - while ((entry = zip.getNextEntry()) != null) { - if (entry.getName().equals("catalog.xml")) { - try { - SAXParserFactory factory = SAXParserFactory.newInstance(); - SAXParser saxParser = factory.newSAXParser(); - DefaultHandler handler = new DefaultHandler() { - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equalsIgnoreCase("library")) { - String path = attributes.getValue("path"); - if (path != null) { - keySet.add(path); - } + while ((entry = zip.getNextEntry()) != null) { + if (entry.getName().equals("catalog.xml")) { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser saxParser = factory.newSAXParser(); + DefaultHandler handler = new DefaultHandler() { + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equalsIgnoreCase("library")) { + String path = attributes.getValue("path"); + if (path != null) { + keySet.add(path); } } + } - }; - saxParser.parse(zip, handler); - } catch (Exception ex) { + }; + saxParser.parse(zip, handler); + } catch (Exception ex) { - } - return; } + return; } - } catch (IOException ex) { - Logger.getLogger(SWC.class.getName()).log(Level.SEVERE, null, ex); } } 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 2948c96f7..de0f5f104 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -276,6 +276,9 @@ public final class SWF implements SWFContainerItem, Timelined { private static final Logger logger = Logger.getLogger(SWF.class.getName()); + @Internal + public SWFBundle bundle; + @Internal private Timeline timeline; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFBundle.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFBundle.java index bfb268dba..3ec3bf294 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFBundle.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFBundle.java @@ -12,11 +12,13 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash; import com.jpexs.helpers.streams.SeekableInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.Map; import java.util.Set; @@ -35,4 +37,9 @@ public interface SWFBundle { public Map getAll() throws IOException; public String getExtension(); + + public boolean isReadOnly(); + + public boolean putSWF(String key,InputStream is) throws IOException; + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFSourceInfo.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFSourceInfo.java index 8985c81a1..8fc7fbce5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFSourceInfo.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFSourceInfo.java @@ -88,14 +88,13 @@ public class SWFSourceInfo { } String extension = Path.getExtension(new File(file)); - InputStream is = new BufferedInputStream(new FileInputStream(file)); switch (extension) { case ".swc": - return new SWC(is); + return new SWC(new File(file)); case ".zip": - return new ZippedSWFBundle(is); + return new ZippedSWFBundle(new File(file)); default: - return new BinarySWFBundle(is, noCheck, searchMode); + return new BinarySWFBundle(new BufferedInputStream(new FileInputStream(file)), noCheck, searchMode); } } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/ZippedSWFBundle.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/ZippedSWFBundle.java index 90d165e5b..8283fcd00 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/ZippedSWFBundle.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/ZippedSWFBundle.java @@ -20,6 +20,10 @@ import com.jpexs.helpers.Helper; import com.jpexs.helpers.MemoryInputStream; import com.jpexs.helpers.ReReadableInputStream; import com.jpexs.helpers.streams.SeekableInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -30,6 +34,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; /** * @@ -39,23 +44,39 @@ public class ZippedSWFBundle implements SWFBundle { protected Set keySet = new HashSet<>(); - private final Map cachedSWFs = new HashMap<>(); - + //private final Map cachedSWFs = new HashMap<>(); + protected FileInputStream fis; protected ReReadableInputStream is; + protected File filename; - public ZippedSWFBundle(InputStream is) { + public ZippedSWFBundle(InputStream is) throws IOException { + this(is, null); + } + + public ZippedSWFBundle(File filename) throws IOException { + this(null, filename); + } + + protected ZippedSWFBundle(InputStream is, File filename) throws IOException { + initBundle(is, filename); + } + + protected void initBundle(InputStream is, File filename) throws IOException { + if (filename != null) { + fis = new FileInputStream(filename); + is = fis; + } + this.filename = filename; this.is = new ReReadableInputStream(is); ZipInputStream zip = new ZipInputStream(this.is); ZipEntry entry; - try { - while ((entry = zip.getNextEntry()) != null) { - if (entry.getName().toLowerCase().endsWith(".swf") - || entry.getName().toLowerCase().endsWith(".gfx")) { - keySet.add(entry.getName()); - } + keySet.clear(); + + while ((entry = zip.getNextEntry()) != null) { + if (entry.getName().toLowerCase().endsWith(".swf") + || entry.getName().toLowerCase().endsWith(".gfx")) { + keySet.add(entry.getName()); } - } catch (IOException ex) { - Logger.getLogger(ZippedSWFBundle.class.getName()).log(Level.SEVERE, null, ex); } } @@ -74,38 +95,98 @@ public class ZippedSWFBundle implements SWFBundle { if (!keySet.contains(key)) { return null; } - if (!cachedSWFs.containsKey(key)) { + //if (!cachedSWFs.containsKey(key)) { + SeekableInputStream ret = null; + this.is.reset(); + ZipInputStream zip = new ZipInputStream(this.is); + ZipEntry entry; - this.is.reset(); - ZipInputStream zip = new ZipInputStream(this.is); - ZipEntry entry; - try { - while ((entry = zip.getNextEntry()) != null) { - if (entry.getName().equals(key)) { - MemoryInputStream mis = new MemoryInputStream(Helper.readStream(zip)); - cachedSWFs.put(key, mis); - break; - } - zip.closeEntry(); - } - } catch (IOException ex) { - Logger.getLogger(ZippedSWFBundle.class.getName()).log(Level.SEVERE, null, ex); + while ((entry = zip.getNextEntry()) != null) { + if (entry.getName().equals(key)) { + MemoryInputStream mis = new MemoryInputStream(Helper.readStream(zip)); + ret = mis; + //cachedSWFs.put(key, mis); + break; } - + zip.closeEntry(); } - return cachedSWFs.get(key); + + return ret; + //return cachedSWFs.get(key); } @Override public Map getAll() throws IOException { + Map ret = new HashMap<>(); for (String key : getKeys()) { // cache everything first - getSWF(key); + ret.put(key, getSWF(key)); } - return cachedSWFs; + return ret; } @Override public String getExtension() { return "zip"; } + + @Override + public boolean isReadOnly() { + return this.filename == null || !this.filename.canWrite(); + } + + @Override + public boolean putSWF(String key, InputStream swfIs) throws IOException { + if (this.isReadOnly()) { + return false; + } + if (key == null) { + return false; + } + if (!getKeys().contains(key)) { //replace only existing keys + return false; + } + //Write to temp file first + File tempFile = new File((filename.getAbsolutePath()) + ".tmp"); + + try { + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tempFile)); + this.is.reset(); + ZipInputStream zis = new ZipInputStream(this.is); + ZipEntry entryIn; + ZipEntry entryOut; + + byte swfData[] = Helper.readStream(swfIs); + + try { + while ((entryIn = zis.getNextEntry()) != null) { + InputStream src; + if (entryIn.getName().equals(key)) { + entryOut = new ZipEntry(entryIn); + entryOut.setSize(swfData.length); + src = new ByteArrayInputStream(swfData); + } else { + src = zis; + entryOut = entryIn; + } + zos.putNextEntry(entryOut); + Helper.copyStream(src, zos, entryOut.getSize() == -1 ? Long.MAX_VALUE : entryOut.getSize()); + } + } finally { + zis.closeEntry(); + zis.close(); + zos.closeEntry(); + zos.close(); + } + this.is.close(); + this.fis.close(); + } catch (IOException ex) { + tempFile.delete(); + throw ex; + } + filename.delete(); + tempFile.renameTo(filename); + initBundle(null, filename); + + return true; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/treeitems/SWFList.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/treeitems/SWFList.java index 0d00d8e9d..8840cacc0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/treeitems/SWFList.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/treeitems/SWFList.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.treeitems; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFBundle; import com.jpexs.decompiler.flash.SWFContainerItem; import com.jpexs.decompiler.flash.SWFSourceInfo; import java.util.ArrayList; @@ -33,9 +34,11 @@ public class SWFList implements List, SWFContainerItem { public String name; - public boolean isBundle; + //public boolean isBundle; + + public SWFBundle bundle; - public Class bundleClass; + //public Class bundleClass; public SWFSourceInfo sourceInfo; @@ -48,7 +51,7 @@ public class SWFList implements List, SWFContainerItem { @Override public String toString() { - if (isBundle) { + if (bundle != null) { return name; } else { return swfs.get(0).getFileTitle(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/treeitems/TreeItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/treeitems/TreeItem.java index fba9f8e36..a94c70d83 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/treeitems/TreeItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/treeitems/TreeItem.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.treeitems; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFBundle; /** * diff --git a/resources/ffdec.sh b/resources/ffdec.sh old mode 100755 new mode 100644 diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index 41050ca89..c5cad702c 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -319,8 +319,9 @@ public class Main { Stopwatch sw = Stopwatch.startNew(); if (bundle != null) { - result.isBundle = true; - result.bundleClass = bundle.getClass(); + //result.isBundle = true; + //result.bundleClass = bundle.getClass(); + result.bundle = bundle; result.name = new File(sourceInfo.getFileTitleOrName()).getName(); for (Entry streamEntry : bundle.getAll().entrySet()) { InputStream stream = streamEntry.getValue(); @@ -331,6 +332,7 @@ public class Main { startWork(AppStrings.translate("work.reading.swf"), p); } }, Configuration.parallelSpeedUp.get()); + swf.bundle = bundle; result.add(swf); } } else { @@ -409,7 +411,7 @@ public class Main { } public static void saveFile(SWF swf, String outfile, SaveFileMode mode) throws IOException { - if (mode == SaveFileMode.SAVEAS && !swf.swfList.isBundle) { + if (mode == SaveFileMode.SAVEAS && swf.swfList.bundle==null) { swf.setFile(outfile); swf.swfList.sourceInfo.setFile(outfile); } @@ -503,7 +505,7 @@ public class Main { loadingDialog.setVisible(false); shouldCloseWhenClosingLoadingDialog = false; - final SWF fswf = firstSWF; + final SWF fswf = firstSWF; View.execInEventDispatch(new Runnable() { @Override public void run() { diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameClassicMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameClassicMenu.java index 535ccd1cb..6d340b201 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameClassicMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameClassicMenu.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFBundle; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.console.ContextMenuTools; import com.jpexs.decompiler.flash.tags.ABCContainerTag; diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index f5cbe1bf2..989b7d3b1 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.ApplicationInfo; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFBundle; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.gui.helpers.CheckResources; import com.jpexs.helpers.ByteArrayRange; @@ -29,6 +30,7 @@ import java.awt.KeyEventDispatcher; import java.awt.KeyboardFocusManager; import java.awt.ScrollPane; import java.awt.event.KeyEvent; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; @@ -48,6 +50,7 @@ public abstract class MainFrameMenu { private final MainFrame mainFrame; private SWF swf; + private SWFBundle bundle; public abstract boolean isInternalFlashViewerSelected(); @@ -66,9 +69,20 @@ public abstract class MainFrameMenu { } protected boolean save() { - if (swf != null) { + if (swf != null) { boolean saved = false; - if (swf.binaryData != null) { + if(swf.bundle!=null){ + if(!swf.bundle.isReadOnly()){ + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + swf.saveTo(baos); + saved = swf.bundle.putSWF(swf.getFileTitle(), new ByteArrayInputStream(baos.toByteArray())); + } catch (IOException ex) { + Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, "Cannot save SWF", ex); + } + } + } + else if (swf.binaryData != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { swf.saveTo(baos); @@ -101,7 +115,7 @@ public abstract class MainFrameMenu { protected boolean saveAs() { if (swf != null) { - if (saveAs(swf, SaveFileMode.SAVEAS)) { + if (saveAs(swf,SaveFileMode.SAVEAS)) { swf.clearModified(); } diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java index 1ed141bdd..e99049333 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFBundle; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.console.ContextMenuTools; import com.jpexs.decompiler.flash.tags.ABCContainerTag; @@ -728,8 +729,8 @@ public class MainFrameRibbonMenu extends MainFrameMenu implements ActionListener closeFileMenu.setEnabled(swfLoaded); closeAllFilesMenu.setEnabled(swfLoaded); - boolean isBundle = swfLoaded && (swf.swfList != null) && swf.swfList.isBundle; - saveCommandButton.setEnabled(swfLoaded && !isBundle); + boolean isBundle = swfLoaded && (swf.swfList != null) && (swf.swfList.bundle != null); + saveCommandButton.setEnabled(swfLoaded && ((!isBundle) || (!swf.swfList.bundle.isReadOnly()))); saveasCommandButton.setEnabled(swfLoaded); saveasexeCommandButton.setEnabled(swfLoaded); exportAllCommandButton.setEnabled(swfLoaded); diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 23fe841e5..087e73e87 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -2232,7 +2232,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec TreeItem treeItem = (TreeItem) e.getPath().getLastPathComponent(); if (!(treeItem instanceof SWFList)) { - SWF swf = treeItem.getSwf(); + SWF swf = treeItem.getSwf(); if (swfs.isEmpty()) { // show welcome panel after closing swfs updateUi(); diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java index 750622c25..1c1d4c288 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java @@ -313,10 +313,10 @@ public class TagTree extends JTree { if (t instanceof SWFList) { SWFList slist = (SWFList) t; - if (slist.bundleClass != null) { - if (slist.bundleClass == ZippedSWFBundle.class) { + if (slist.bundle != null) { + if (slist.bundle.getClass() == ZippedSWFBundle.class) { return TreeNodeType.BUNDLE_ZIP; - } else if (slist.bundleClass == SWC.class) { + } else if (slist.bundle.getClass() == SWC.class) { return TreeNodeType.BUNDLE_SWC; } else { return TreeNodeType.BUNDLE_BINARY; diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 6af97c928..3a482cd47 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -275,7 +275,7 @@ public class TagTreeContextMenu extends JPopupMenu implements ActionListener { } else if (item instanceof SWF) { SWF swf = (SWF) item; // Do not allow to close SWF in bundle - if (swf.swfList != null && swf.swfList.isBundle) { + if (swf.swfList != null && swf.swfList.bundle!=null) { allSelectedIsSwf = false; } } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java index 641dea047..5add2c69c 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java @@ -95,7 +95,7 @@ public class TagTreeModel implements TreeModel { this.tagScriptCache = new HashMap<>(); Main.startWork(AppStrings.translate("work.buildingscripttree") + "..."); for (SWFList swfList : swfs) { - if (swfList.isBundle) { + if (swfList.bundle != null) { this.swfs.add(swfList); for (SWF swf : swfList) { createTagList(swf);