diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/IdentifiersDeobfuscation.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/IdentifiersDeobfuscation.java index 135546c60..731347fe2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/IdentifiersDeobfuscation.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/IdentifiersDeobfuscation.java @@ -289,8 +289,8 @@ public class IdentifiersDeobfuscation { return "\u00A7" + escapeOIdentifier(s) + "\u00A7"; } - private static final Cache as3NameCache = Cache.getInstance(false); - private static final Cache as2NameCache = Cache.getInstance(false); + private static final Cache as3NameCache = Cache.getInstance(false,"as3_ident"); + private static final Cache as2NameCache = Cache.getInstance(false,"as2_ident"); /** * Ensures identifier is valid and if not, uses paragraph syntax 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 eccc5f207..c86688e98 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -278,9 +278,9 @@ public final class SWF implements SWFContainerItem, Timelined { public DumpInfoSwfNode dumpInfo; public DefineBinaryDataTag binaryData; - private static Cache frameCache = Cache.getInstance(false); - private final Cache as2Cache = Cache.getInstance(true); - private final Cache as3Cache = Cache.getInstance(true); + private static Cache frameCache = Cache.getInstance(false,"frame"); + private final Cache as2Cache = Cache.getInstance(true,"as2"); + private final Cache as3Cache = Cache.getInstance(true,"as3"); public void updateCharacters() { characters.clear(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButton2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButton2Tag.java index b7c8b361a..9a7f523e0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButton2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButton2Tag.java @@ -207,7 +207,7 @@ public class DefineButton2Tag extends ButtonTag implements Container { return modified; } - private static final Cache rectCache = Cache.getInstance(true); + private static final Cache rectCache = Cache.getInstance(true,"rect_button2"); @Override public RECT getRect(Set added) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java index 2b492113a..75e6a12a5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java @@ -245,7 +245,7 @@ public class DefineButtonTag extends ButtonTag implements ASMSource { return modified; } - private static final Cache rectCache = Cache.getInstance(true); + private static final Cache rectCache = Cache.getInstance(true,"rect_button"); @Override public RECT getRect(Set added) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java index 7ea1c2ae2..1facbf61d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java @@ -122,7 +122,7 @@ public class DefineSpriteTag extends CharacterTag implements Container, Drawable return ret; } - private static final Cache rectCache = Cache.getInstance(true); + private static final Cache rectCache = Cache.getInstance(true,"rect_sprite"); @Override public RECT getRect(Set added) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/Cache.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/Cache.java index 2e0a4bcbb..792efff94 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/Cache.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/Cache.java @@ -16,20 +16,14 @@ */ package com.jpexs.helpers; +import com.jpexs.decompiler.flash.helpers.Freed; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; -import java.util.logging.Level; -import java.util.logging.Logger; /** * @@ -37,16 +31,31 @@ import java.util.logging.Logger; * @param * @param */ -public class Cache { +public class Cache implements Freed { - private final Map cacheFiles; - private final Map cacheMemory; + private Map cache; private static final List instances = new ArrayList<>(); public static final int STORAGE_FILES = 1; public static final int STORAGE_MEMORY = 2; + private boolean weak; + private String name; - public static Cache getInstance(boolean weak) { - Cache instance = new Cache<>(weak); + static { + Runtime.getRuntime().addShutdownHook(new Thread(){ + + @Override + public void run() { + for (Cache c : instances) { + c.clear(); + c.free(); + } + } + + }); + } + + public static Cache getInstance(boolean weak,String name) { + Cache instance = new Cache<>(weak,name); instances.add(instance); return instance; } @@ -56,6 +65,7 @@ public class Cache { public static void clearAll() { for (Cache c : instances) { c.clear(); + c.initCache(); } } @@ -80,103 +90,66 @@ public class Cache { return storageType; } - private Cache(boolean weak) { - if (weak) { - cacheFiles = new WeakHashMap<>(); - cacheMemory = new WeakHashMap<>(); - } else { - cacheFiles = new HashMap<>(); - cacheMemory = new HashMap<>(); + private void initCache() { + int thisStorageType = storageType; + Map newCache = null; + if (thisStorageType == STORAGE_FILES) { + try { + newCache = new FileHashMap<>(File.createTempFile("ffdec_cache_"+name+"_", ".tmp")); + } catch (IOException ex) { + thisStorageType = STORAGE_MEMORY; + } } + if (thisStorageType == STORAGE_MEMORY) { + if (weak) { + newCache = new WeakHashMap<>(); + } else { + newCache = new HashMap<>(); + } + } + if (this.cache instanceof Freed) { + ((Freed) this.cache).free(); + } + this.cache = newCache; + } + + private Cache(boolean weak,String name) { + this.weak = weak; + this.name = name; + initCache(); } public synchronized boolean contains(K key) { - if (storageType == STORAGE_FILES) { - return cacheFiles.containsKey(key); - } else if (storageType == STORAGE_MEMORY) { - return cacheMemory.containsKey(key); - } - return false; + return cache.containsKey(key); } public synchronized void clear() { - cacheMemory.clear(); - for (File f : cacheFiles.values()) { - f.delete(); - } - cacheFiles.clear(); + cache.clear(); } public synchronized void remove(K key) { - if (storageType == STORAGE_FILES) { - if (cacheFiles.containsKey(key)) { - File f = cacheFiles.get(key); - f.delete(); - cacheFiles.remove(key); - } - } else if (storageType == STORAGE_MEMORY) { - if (cacheMemory.containsKey(key)) { - cacheMemory.remove(key); - } + if (cache.containsKey(key)) { + cache.remove(key); } - } public synchronized V get(K key) { - if (storageType == STORAGE_FILES) { - if (!cacheFiles.containsKey(key)) { - return null; - } - File f = cacheFiles.get(key); - try (FileInputStream fis = new FileInputStream(f)) { - ObjectInputStream ois = new ObjectInputStream(fis); - @SuppressWarnings("unchecked") - V item = (V) ois.readObject(); - return item; - } catch (IOException | ClassNotFoundException ex) { - Logger.getLogger(Cache.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } else if (storageType == STORAGE_MEMORY) { - if (cacheMemory.containsKey(key)) { - return cacheMemory.get(key); - } - return null; - } - return null; + return cache.get(key); } public synchronized void put(K key, V value) { - if (storageType == STORAGE_FILES) { - File temp = null; - try { - temp = File.createTempFile("ffdec_cache", ".tmp"); - } catch (IOException ex) { - Logger.getLogger(Cache.class.getName()).log(Level.SEVERE, null, ex); + cache.put(key, value); + } - return; - } - try { - temp.deleteOnExit(); - } catch (IllegalStateException iex) { - return; - } - try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(temp))) { - if (value instanceof Serializable) { - oos.writeObject(value); - } else { - // Object serialization not supported - return; - } - oos.flush(); + @Override + public boolean isFreeing() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } - cacheFiles.put(key, temp); - - } catch (IOException ex) { - //ignore - } - } else if (storageType == STORAGE_MEMORY) { - cacheMemory.put(key, value); + @Override + public void free() { + if(cache instanceof Freed){ + ((Freed)cache).free(); } } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/FileHashMap.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/FileHashMap.java new file mode 100644 index 000000000..683dab66f --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/FileHashMap.java @@ -0,0 +1,313 @@ +package com.jpexs.helpers; + +import com.jpexs.decompiler.flash.helpers.Freed; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.RandomAccessFile; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class FileHashMap extends AbstractMap implements Freed { + + private final Map lengths = new HashMap<>(); + private final Map offsets = new HashMap<>(); + private long fileLen = 0; + private final RandomAccessFile file; + private final File fileName; + private final Set gaps = new TreeSet<>(); + private int maxGapLen = 0; + private boolean deleted = false; + + + + private static class Gap implements Comparable { + + public long offset; + public int length; + + public Gap(long offset, int length) { + this.offset = offset; + this.length = length; + } + + @Override + public int compareTo(Gap o) { + return o.length - length; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + (int) (this.offset ^ (this.offset >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Gap other = (Gap) obj; + if (this.offset != other.offset) { + return false; + } + return true; + } + + } + + public static class FileEntry implements Map.Entry { + + private FileHashMap parent; + private K key; + + public FileEntry(FileHashMap parent, K key) { + this.parent = parent; + this.key = key; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return parent.get(key); + } + + @Override + public V setValue(V value) { + return parent.put(key, value); + } + + } + + public FileHashMap(File file) throws IOException { + this.file = new RandomAccessFile(file, "rw"); + this.file.setLength(0); + this.fileName = file; + file.deleteOnExit(); + } + + @Override + public boolean containsKey(Object key) { + if(deleted){ + throw new NullPointerException(); + } + return offsets.containsKey(key); + } + + @Override + public Set keySet() { + if(deleted){ + throw new NullPointerException(); + } + return offsets.keySet(); + } + + @Override + public V get(Object key) { + if(deleted){ + throw new NullPointerException(); + } + try { + if (!offsets.containsKey(key)) { + return null; + } + long ofs = offsets.get(key); + int len = lengths.get(key); + file.seek(ofs); + byte data[] = new byte[len]; + file.readFully(data); + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); + try { + @SuppressWarnings("unchecked") + V ret = (V) ois.readObject(); + return ret; + } catch (ClassNotFoundException ex) { + Logger.getLogger(FileHashMap.class.getName()).log(Level.SEVERE, null, ex); + return null; + } + } catch (IOException ex) { + Logger.getLogger(FileHashMap.class.getName()).log(Level.SEVERE, null, ex); + return null; + } + } + + @Override + public synchronized V put(K key, V value) { + if(deleted){ + throw new NullPointerException(); + } + ObjectOutputStream oos = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(baos); + oos.writeObject(value); + oos.flush(); + byte data[] = baos.toByteArray(); + if (offsets.containsKey(key)) { + long origOffset = offsets.get(key); + int origLen = lengths.get(key); + if (data.length <= origLen) { + file.seek(origOffset); + file.write(data); + lengths.put(key, data.length); + if (data.length < origLen) { + Gap g = new Gap(origOffset + data.length, origLen - data.length); + if (g.length > maxGapLen) { + maxGapLen = g.length; + } + gaps.add(g); + } + + return value; + } + } + if (data.length <= maxGapLen) { + + for (Iterator i = gaps.iterator(); i.hasNext();) { + Gap g = i.next(); + if (g.length >= data.length) { + file.seek(g.offset); + file.write(data); + offsets.put(key, g.offset); + lengths.put(key, g.length); + if (g.length > data.length) { + g.offset = g.offset + data.length; + g.length = g.length - data.length; + } else { + i.remove(); + } + } + } + } else { + file.seek(fileLen); + file.write(data); + offsets.put(key, fileLen); + lengths.put(key, data.length); + fileLen += data.length; + } + } catch (IOException ex) { + Logger.getLogger(FileHashMap.class.getName()).log(Level.SEVERE, null, ex); + } finally { + try { + oos.close(); + } catch (IOException ex) { + //ignore + } + } + return value; + } + + @Override + public V remove(Object objKey) { + if(deleted){ + throw new NullPointerException(); + } + @SuppressWarnings("unchecked") + K key=(K)objKey; + if (!containsKey(key)) { + return null; + } + V val = get((K) key); + Gap g = new Gap(offsets.get(key), lengths.get(key)); + offsets.remove(key); + lengths.remove(key); + if (g.offset + g.length == fileLen) { + fileLen -= g.length; + } else { + if (g.length > maxGapLen) { + maxGapLen = g.length; + } + gaps.add(g); + } + return val; + } + + @Override + public Set> entrySet() { + if(deleted){ + throw new NullPointerException(); + } + Set> ret=new HashSet>(); + for(K key:keySet()){ + ret.add(new FileEntry(this,key)); + } + return ret; + } + + @Override + public void clear() { + if(deleted){ + throw new NullPointerException(); + } + offsets.clear(); + lengths.clear(); + fileLen = 0; + maxGapLen = 0; + try { + file.setLength(0); + } catch (IOException ex) { + Logger.getLogger(FileHashMap.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public void delete() { + if(deleted){ + throw new NullPointerException(); + } + try { + file.close(); + } catch (IOException ex) { + Logger.getLogger(FileHashMap.class.getName()).log(Level.SEVERE, null, ex); + } + fileName.delete(); + deleted = true; + + } + + @Override + public boolean isFreeing() { + return !deleted; + } + + @Override + public void free() { + if(!deleted){ + delete(); + } + } + + @Override + public boolean isEmpty() { + return offsets.isEmpty(); + } + + @Override + public int size() { + return offsets.size(); + } + + +} diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/FileHashMapTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/FileHashMapTest.java new file mode 100644 index 000000000..7a8b102b9 --- /dev/null +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/FileHashMapTest.java @@ -0,0 +1,53 @@ + +package com.jpexs.decompiler.flash; + +import com.jpexs.helpers.FileHashMap; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.testng.Assert; +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.fail; + + +/** + * + * @author JPEXS + */ +public class FileHashMapTest { + @Test + public void testFileHashMap() throws Exception { + File tfile = new File("fmtest.bin"); + FileHashMap map = new FileHashMap<>(tfile); + assertTrue(map.isEmpty()); + assertEquals(map.size(), 0); + map.put("A", "cat"); + assertEquals(map.get("A"),"cat"); + assertNull(map.get("B")); + map.put("B", "dog"); + assertEquals(map.get("B"),"dog"); + assertEquals(map.get("A"),"cat"); + map.put("C", "parrot"); + assertTrue(map.containsKey("A")); + assertTrue(map.containsKey("B")); + assertTrue(map.containsKey("C")); + assertFalse(map.containsKey("D")); + assertEquals(map.size(), 3); + map.remove("A"); + assertFalse(map.containsKey("A")); + map.put("X", "tac"); + map.delete(); + try{ + map.get("A"); + fail(); + }catch(NullPointerException nfe){ + //okay + } + + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java index a5c450e20..92c6b9a7c 100644 --- a/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java @@ -78,7 +78,7 @@ public class FolderPreviewPanel extends JPanel { public FolderPreviewPanel(final MainPanel mainPanel, List items) { this.items = items; - cachedPreviews = Cache.getInstance(false); + cachedPreviews = Cache.getInstance(false,"preview"); addMouseListener(new MouseAdapter() { diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index ed64c3e0a..85c427477 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -67,6 +67,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -965,12 +966,41 @@ public class Main { Helper.decompilationErrorAdd = AppStrings.translate(Configuration.autoDeobfuscate.get() ? "deobfuscation.comment.failed" : "deobfuscation.comment.tryenable"); } + /** + * Clear old FFDec/JavactiveX temp files + */ + private static void clearTemp() { + String tempDirPath = System.getProperty("java.io.tmpdir"); + if (tempDirPath == null) { + return; + } + File tempDir = new File(tempDirPath); + if (!tempDir.exists()) { + return; + } + File delFiles[] = tempDir.listFiles(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + return name.matches("ffdec_cache.*\\.tmp") || name.matches("javactivex_.*\\.exe") || name.matches("temp[0-9]+\\.swf") || name.matches("ffdec_view_.*\\.swf"); + } + }); + for (File f : delFiles) { + try { + f.delete(); + } catch (Exception ex) { + //ignore + } + } + } + /** * @param args the command line arguments * @throws IOException */ public static void main(String[] args) throws IOException { + clearTemp(); String pluginPath = Configuration.pluginPath.get(); if (pluginPath != null && !pluginPath.isEmpty()) { try { diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java index 6db92b079..77b612617 100644 --- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java @@ -155,6 +155,21 @@ public class PreviewPanel extends JSplitPane implements ActionListener { super(JSplitPane.HORIZONTAL_SPLIT); this.mainPanel = mainPanel; this.flashPanel = flashPanel; + + Runtime.getRuntime().addShutdownHook(new Thread(){ + + @Override + public void run() { + if(tempFile!=null){ + try{ + tempFile.delete(); + }catch(Exception ex){ + + } + } + } + + }); addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() { @Override @@ -464,7 +479,7 @@ public class PreviewPanel extends JSplitPane implements ActionListener { if (tempFile != null) { tempFile.delete(); } - tempFile = File.createTempFile("temp", ".swf"); + tempFile = File.createTempFile("ffdec_view_", ".swf"); tempFile.deleteOnExit(); Color backgroundColor = View.swfBackgroundColor; @@ -937,8 +952,7 @@ public class PreviewPanel extends JSplitPane implements ActionListener { tempFile.delete(); } try { - tempFile = File.createTempFile("temp", ".swf"); - tempFile.deleteOnExit(); + tempFile = File.createTempFile("ffdec_view_", ".swf"); swf.saveTo(new BufferedOutputStream(new FileOutputStream(tempFile))); flashPanel.displaySWF(tempFile.getAbsolutePath(), backgroundColor, swf.frameRate); } catch (IOException iex) {