diff --git a/trunk/src/com/jpexs/decompiler/flash/BinarySWFBundle.java b/trunk/src/com/jpexs/decompiler/flash/BinarySWFBundle.java new file mode 100644 index 000000000..71592e209 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/BinarySWFBundle.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 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; + +import com.jpexs.helpers.StreamSearch; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * + * @author JPEXS + */ +public class BinarySWFBundle implements SWFBundle { + private SWFSearch search; + public BinarySWFBundle(InputStream is){ + search=new SWFSearch(new StreamSearch(is)); + } + + @Override + public int length() { + return search.length(); + } + + @Override + public Set getKeys() { + Set ret=new HashSet<>(); + for(int i=0;i=length()){ + return null; + } + return search.get(null, index); + }catch(IOException iex){ + return null; + }catch(NumberFormatException nfe){ + return null; + } + } + + @Override + public Map getAll() { + Map ret=new HashMap<>(); + for(String key:getKeys()){ + ret.put(key, getSWF(key)); + } + return ret; + } + + @Override + public String getExtension() { + return "bin"; + } + +} diff --git a/trunk/src/com/jpexs/decompiler/flash/SWC.java b/trunk/src/com/jpexs/decompiler/flash/SWC.java new file mode 100644 index 000000000..004b9b272 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/SWC.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 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; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Set; +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; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * + * @author JPEXS + */ +public class SWC extends ZippedSWFBundle { + + public SWC(InputStream is) { + super(is); + keySet.clear(); + 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); + } + } + } + + }; + saxParser.parse(zip, handler); + } catch (Exception ex) { + + } + return; + } + } + } catch (IOException ex) { + Logger.getLogger(ZippedSWFBundle.class.getName()).log(Level.SEVERE, null, ex); + } + } + + @Override + public String getExtension() { + return "swc"; + } + + +} diff --git a/trunk/src/com/jpexs/decompiler/flash/SWFBundle.java b/trunk/src/com/jpexs/decompiler/flash/SWFBundle.java new file mode 100644 index 000000000..5d4b374c4 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/SWFBundle.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 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; + +import java.util.Map; +import java.util.Set; + +/** + * + * @author JPEXS + */ +public interface SWFBundle { + public int length(); + public Set getKeys(); + public SWF getSWF(String key); + public Map getAll(); + public String getExtension(); +} diff --git a/trunk/src/com/jpexs/decompiler/flash/SWFSearch.java b/trunk/src/com/jpexs/decompiler/flash/SWFSearch.java new file mode 100644 index 000000000..623172146 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/SWFSearch.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2014 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; + +import com.jpexs.helpers.LimitedInputStream; +import com.jpexs.helpers.PosMarkedInputStream; +import com.jpexs.helpers.ProgressListener; +import com.jpexs.helpers.ReReadableInputStream; +import com.jpexs.helpers.Searchable; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class SWFSearch { + + protected Searchable s; + private boolean processed = false; + private Set listeners = new HashSet<>(); + private List swfStreams = new ArrayList<>(); + private Map cachedSWFs = new HashMap(); + + public SWFSearch(Searchable s) { + this.s = s; + } + + public void addProgressListener(ProgressListener l) { + listeners.add(l); + } + + public void removeProgressListener(ProgressListener l) { + listeners.remove(l); + } + + private void setProgress(int p) { + for (ProgressListener l : listeners) { + l.progress(p); + } + } + + public void process() { + Map ret = new HashMap<>(); + ret = s.search(new ProgressListener() { + @Override + public void progress(int p) { + setProgress(p); + } + }, + "FWS".getBytes(), //Uncompressed Flash + "CWS".getBytes(), //ZLib compressed Flash + "ZWS".getBytes(), //LZMA compressed Flash + "GFX".getBytes(), //Uncompressed ScaleForm GFx + "CFX".getBytes()); //Compressed ScaleForm GFx + int pos = 0; + for (Long addr : ret.keySet()) { + setProgress(pos * 100 / ret.size()); + pos++; + try { + PosMarkedInputStream pmi = new PosMarkedInputStream(ret.get(addr)); + ReReadableInputStream is = new ReReadableInputStream(pmi); + SWF swf = new SWF(is, null, false, true); + long limit = pmi.getPos(); + is.seek(0); + is = new ReReadableInputStream(new LimitedInputStream(is, limit)); + if (swf.fileSize > 0 && swf.version > 0 && !swf.tags.isEmpty() && swf.version < 25/*Needs to be fixed when SWF versions reaches this value*/) { + swfStreams.add(is); + } + + } catch (OutOfMemoryError ome) { + System.gc(); + } catch (Exception | Error ex) { + } + + } + setProgress(100); + processed = true; + } + + public SWF get(ProgressListener listener,int index) throws IOException { + if(!processed){ + return null; + } + if(index<0 || index>=swfStreams.size()){ + return null; + } + if(!cachedSWFs.containsKey(index)){ + try { + cachedSWFs.put(index, new SWF(swfStreams.get(index), listener, false)); + } catch (InterruptedException ex) { + Logger.getLogger(SWFSearch.class.getName()).log(Level.SEVERE, null, ex); + return null; + } + } + return cachedSWFs.get(index); + } + + public int length() { + if (!processed) { + return 0; + } + return swfStreams.size(); + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/ZippedSWFBundle.java b/trunk/src/com/jpexs/decompiler/flash/ZippedSWFBundle.java new file mode 100644 index 000000000..ac693b436 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/ZippedSWFBundle.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2014 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; + +import com.jpexs.helpers.ReReadableInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * + * @author JPEXS + */ +public class ZippedSWFBundle implements SWFBundle { + protected Set keySet=new HashSet(); + private Map cachedSWFs=new HashMap<>(); + protected ReReadableInputStream is; + + + public ZippedSWFBundle(InputStream is){ + 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()); + } + //streamMap.put(, is) + } + } catch (IOException ex) { + Logger.getLogger(ZippedSWFBundle.class.getName()).log(Level.SEVERE, null, ex); + } + } + + @Override + public int length() { + return keySet.size(); + } + + @Override + public Set getKeys() { + return keySet; + } + + @Override + public SWF getSWF(String key) { + if(!keySet.contains(key)){ + return null; + } + if(!cachedSWFs.containsKey(key)){ + + ZipInputStream zip=new ZipInputStream(this.is); + ZipEntry entry; + try { + while((entry = zip.getNextEntry())!=null) + { + if(entry.getName().equals(key)){ + try { + cachedSWFs.put(key, new SWF(zip, null,false)); + } catch (IOException ex) { + Logger.getLogger(ZippedSWFBundle.class.getName()).log(Level.SEVERE, null, ex); + } catch (InterruptedException ex) { + Logger.getLogger(ZippedSWFBundle.class.getName()).log(Level.SEVERE, null, ex); + } + break; + } + } + } catch (IOException ex) { + Logger.getLogger(ZippedSWFBundle.class.getName()).log(Level.SEVERE, null, ex); + } + + + } + return cachedSWFs.get(key); + } + + @Override + public Map getAll() { + for(String key:getKeys()){ //cache everything first + getSWF(key); + } + return cachedSWFs; + } + + @Override + public String getExtension() { + return "zip"; + } + +} diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/Main.java b/trunk/src/com/jpexs/decompiler/flash/gui/Main.java index 01cdc656a..f929b22b4 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/Main.java @@ -343,6 +343,7 @@ public class Main { protected Object doInBackground() throws Exception { boolean first = true; for (SWFSourceInfo sourceInfo : sourceInfos) { + //TODO: Handle non SWF filetypes (SWC,ZIP,Binary search) SWF swf = null; try { Main.startWork(AppStrings.translate("work.reading.swf") + "..."); @@ -574,6 +575,8 @@ public class Main { public boolean accept(File f) { return (f.getName().toLowerCase().toLowerCase().endsWith(".swf")) || (f.getName().toLowerCase().toLowerCase().endsWith(".gfx")) + || (f.getName().toLowerCase().toLowerCase().endsWith(".swc")) + || (f.getName().toLowerCase().toLowerCase().endsWith(".zip")) || (f.isDirectory()); } @@ -595,6 +598,20 @@ public class Main { } }; fc.addChoosableFileFilter(swfFilter); + + FileFilter swcFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return (f.getName().toLowerCase().endsWith(".swc")) || (f.isDirectory()); + } + + @Override + public String getDescription() { + return AppStrings.translate("filter.swc"); + } + }; + fc.addChoosableFileFilter(swcFilter); + FileFilter gfxFilter = new FileFilter() { @Override public boolean accept(File f) { @@ -605,9 +622,37 @@ public class Main { public String getDescription() { return AppStrings.translate("filter.gfx"); } - }; + }; fc.addChoosableFileFilter(gfxFilter); - fc.setAcceptAllFileFilterUsed(true); + + + FileFilter zipFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return (f.getName().toLowerCase().endsWith(".zip")) || (f.isDirectory()); + } + + @Override + public String getDescription() { + return AppStrings.translate("filter.zip"); + } + }; + fc.addChoosableFileFilter(zipFilter); + + FileFilter binaryFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return true; + } + + @Override + public String getDescription() { + return AppStrings.translate("filter.binary"); + } + }; + fc.addChoosableFileFilter(binaryFilter); + + fc.setAcceptAllFileFilterUsed(false); JFrame f = new JFrame(); View.setWindowIcon(f); int returnVal = fc.showOpenDialog(f); @@ -803,7 +848,7 @@ public class Main { } if (args.length == 0) { - initGui(); + initGui(); showModeFrame(); } else { String fileToOpen = CommandLineArgumentParser.parseArguments(args); diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/trunk/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 20bca5a73..6f230df3e 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/trunk/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -370,7 +370,7 @@ button.no.all = No to all message.font.add.exists = Character %char% already exists in the font tag.\nDo you want to replace it? filter.gfx = ScaleForm GFx files (*.gfx) -filter.supported = All supported filetypes (*.swf, *.gfx) +filter.supported = All supported filetypes (*.swf, *.gfx; *.swc; *.zip) work.canceled = Canceled work.restoringControlFlow = Restoring control flow menu.advancedsettings.advancedsettings = Advanced Settings @@ -424,3 +424,7 @@ decompilerMark = decompiler mark fontNotFound = Font with id=%fontId% was not found. contextmenu.moveTag = Move tag to + +filter.swc = SWC component files (*.swc) +filter.zip = ZIP compressed files (*.zip) +filter.binary = Binary search - all files (*.*) diff --git a/trunk/src/com/jpexs/helpers/FoundInputStream.java b/trunk/src/com/jpexs/helpers/FoundInputStream.java new file mode 100644 index 000000000..f47e8c9b5 --- /dev/null +++ b/trunk/src/com/jpexs/helpers/FoundInputStream.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 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.helpers; + +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author JPEXS + */ +public class FoundInputStream extends ReReadableInputStream{ + + private long startPos; + private boolean started=false; + + public FoundInputStream(long startPos,InputStream is) { + super(is); + this.startPos = startPos; + } + + @Override + public int read() throws IOException { + if(!started){ + seek(0); + started = true; + } + return super.read(); + } + + + + @Override + public void seek(long pos) throws IOException { + super.seek(pos+startPos); + } + + @Override + public long getPos() { + return super.getPos()-startPos; + } +} diff --git a/trunk/src/com/jpexs/helpers/Searchable.java b/trunk/src/com/jpexs/helpers/Searchable.java new file mode 100644 index 000000000..aa44141ce --- /dev/null +++ b/trunk/src/com/jpexs/helpers/Searchable.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 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.helpers; + +import java.io.InputStream; +import java.util.Map; + +/** + * + * @author JPEXS + */ +public interface Searchable { + /** + * Searches for byte sequences + * @param data + * @return Map Position=>Input stream + */ + public Map search(byte[]... data); + + /** + * Searches for byte sequences with progress listener + * @param progListener Listener + * @param data + * @return Map Position=>Input stream + */ + public Map search(ProgressListener progListener, byte[]... data); +} diff --git a/trunk/src/com/jpexs/helpers/StreamSearch.java b/trunk/src/com/jpexs/helpers/StreamSearch.java new file mode 100644 index 000000000..89ccd6cd4 --- /dev/null +++ b/trunk/src/com/jpexs/helpers/StreamSearch.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014 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.helpers; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class StreamSearch implements Searchable { + + private ReReadableInputStream is; + + public StreamSearch(InputStream is) { + this.is = new ReReadableInputStream(is); + } + + @Override + public Map search(byte[] + ... data) { + return search(null, data); + } + + @Override + public Map search(ProgressListener progListener, byte[] + ... data) { + Map ret = new HashMap<>(); + int maxFindLen = 0; + for (int i = 0; i < data.length; i++) { + if (data[i].length > maxFindLen) { + maxFindLen = data[i].length; + } + } + try { + is.seek(0); + + byte buf[] = new byte[4096]; + byte last[] = null; + int cnt = 0; + long pos=0; + while ((cnt = is.read(buf)) > 0) { + + for (int i = -maxFindLen + 1; i < cnt; i++) { + + loopdata:for (byte onedata[] : data) { + boolean match = true; + for (int d = 0; d < onedata.length; d++) { + byte b; + if (i + d < 0) { + if (last != null) { + b = last[last.length + i + d]; + } else { + continue; + } + } else if (i + d >= buf.length) { + continue; + } else { + b = buf[i + d]; + } + + if (b != onedata[d]) { + match = false; + } + } + if (match) { + ret.put(pos+i, new FoundInputStream(pos+i,is)); + continue loopdata; + } + } + + } + pos = pos + cnt; + } + + + + } catch (IOException ex) { + Logger.getLogger(StreamSearch.class.getName()).log(Level.SEVERE, null, ex); + } + return ret; + } + +} diff --git a/trunk/src/com/jpexs/process/Process.java b/trunk/src/com/jpexs/process/Process.java index abac01350..514ea73f6 100644 --- a/trunk/src/com/jpexs/process/Process.java +++ b/trunk/src/com/jpexs/process/Process.java @@ -17,6 +17,7 @@ package com.jpexs.process; import com.jpexs.helpers.ProgressListener; +import com.jpexs.helpers.Searchable; import java.awt.image.BufferedImage; import java.io.InputStream; import java.util.Map; @@ -25,7 +26,7 @@ import java.util.Map; * * @author JPEXS */ -public interface Process extends Comparable { +public interface Process extends Comparable, Searchable{ public String getFilePath(); @@ -38,7 +39,9 @@ public interface Process extends Comparable { public String getPid(); + @Override public Map search(byte[]... data); + @Override public Map search(ProgressListener progListener, byte[]... data); }