diff --git a/trunk/src/com/jpexs/decompiler/flash/FLVOutputStream.java b/trunk/src/com/jpexs/decompiler/flash/FLVOutputStream.java new file mode 100644 index 000000000..cb01207e4 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/FLVOutputStream.java @@ -0,0 +1,123 @@ +package com.jpexs.decompiler.flash; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * + * @author JPEXS + */ +public class FLVOutputStream extends OutputStream { + + private OutputStream os; + private int bitPos = 0; + private int tempByte = 0; + private long pos = 0; + + public FLVOutputStream(OutputStream os) { + this.os = os; + } + + public long getPos() { + return pos; + } + + /** + * Writes byte to the stream + * + * @param b byte to write + * @throws IOException + */ + @Override + public void write(int b) throws IOException { + alignByte(); + os.write(b); + pos++; + } + + private void alignByte() throws IOException { + if (bitPos > 0) { + bitPos = 0; + write(tempByte); + tempByte = 0; + } + } + + /** + * Writes UI8 (Unsigned 8bit integer) value to the stream + * + * @param val UI8 value to write + * @throws IOException + */ + public void writeUI8(int val) throws IOException { + write(val); + } + + /** + * Writes UI24 (Unsigned 24bit integer) value to the stream + * + * @param value UI32 value + * @throws IOException + */ + public void writeUI24(long value) throws IOException { + write((int) ((value >> 16) & 0xff)); + write((int) ((value >> 8) & 0xff)); + write((int) (value & 0xff)); + + } + + /** + * Writes UI32 (Unsigned 32bit integer) value to the stream + * + * @param value UI32 value + * @throws IOException + */ + public void writeUI32(long value) throws IOException { + write((int) ((value >> 24) & 0xff)); + write((int) ((value >> 16) & 0xff)); + write((int) ((value >> 8) & 0xff)); + write((int) (value & 0xff)); + } + + /** + * Writes UI16 (Unsigned 16bit integer) value to the stream + * + * @param value UI16 value + * @throws IOException + */ + public void writeUI16(int value) throws IOException { + write((int) ((value >> 8) & 0xff)); + write((int) (value & 0xff)); + } + + /** + * Writes UB[nBits] (Unsigned-bit value) value to the stream + * + * @param nBits Number of bits which represent value + * @param value Unsigned value to write + * @throws IOException + */ + public void writeUB(int nBits, long value) throws IOException { + for (int bit = 0; bit < nBits; bit++) { + int nb = (int) ((value >> (nBits - 1 - bit)) & 1); + tempByte += nb * (1 << (7 - bitPos)); + bitPos++; + if (bitPos == 8) { + bitPos = 0; + write(tempByte); + tempByte = 0; + } + } + } + + public void writeHeader(boolean audio, boolean video) throws IOException { + write("FLV".getBytes()); + write(1); //version + writeUB(5, 0); //must be 0 + writeUB(1, audio?1:0); //audio present + writeUB(1, 0); //reserved + writeUB(1, video?1:0); //video present + writeUI32(9); //header size + writeUI32(0); + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/Main.java b/trunk/src/com/jpexs/decompiler/flash/Main.java index 6fabbd935..a754d1964 100644 --- a/trunk/src/com/jpexs/decompiler/flash/Main.java +++ b/trunk/src/com/jpexs/decompiler/flash/Main.java @@ -411,8 +411,8 @@ public class Main { System.out.println(" ...opens SWF file with the decompiler GUI"); System.out.println(" 3) -proxy (-PXXX)"); System.out.println(" ...auto start proxy in the tray. Optional parameter -P specifies port for proxy. Defaults to 55555. "); - System.out.println(" 4) -export (as|pcode|image|shape) outdirectory infile"); - System.out.println(" ...export infile sources to outdirectory as AsctionScript code (\"as\" argument) or as PCode (\"pcode\" argument), images or shapes"); + System.out.println(" 4) -export (as|pcode|image|shape|movie) outdirectory infile"); + System.out.println(" ...export infile sources to outdirectory as AsctionScript code (\"as\" argument) or as PCode (\"pcode\" argument), images, shapes or movies"); System.out.println(" 5) -dumpSWF infile"); System.out.println(" ...dumps list of SWF tags to console"); System.out.println(" 6) -compress infile outfile"); @@ -515,8 +515,10 @@ public class Main { if (!exportFormat.toLowerCase().equals("pcode")) { if (!exportFormat.toLowerCase().equals("image")) { if (!exportFormat.toLowerCase().equals("shape")) { + if (!exportFormat.toLowerCase().equals("movie")) { System.err.println("Invalid export format:" + exportFormat); badArguments(); + } } } } @@ -548,7 +550,10 @@ public class Main { exportOK = true; } else if (exportFormat.equals("as") || exportFormat.equals("pcode")) { exportOK = exfile.exportActionScript(outDir.getAbsolutePath(), exportFormat.equals("pcode")); - } else { + } else if (exportFormat.equals("movie")) { + exfile.exportVideos(outDir.getAbsolutePath()); + exportOK=true; + }else { exportOK = false; } } catch (Exception ex) { diff --git a/trunk/src/com/jpexs/decompiler/flash/SWF.java b/trunk/src/com/jpexs/decompiler/flash/SWF.java index ed596c60d..e9cabf7d6 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWF.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWF.java @@ -24,14 +24,18 @@ import com.jpexs.decompiler.flash.tags.DefineBitsJPEG4Tag; import com.jpexs.decompiler.flash.tags.DefineBitsLossless2Tag; import com.jpexs.decompiler.flash.tags.DefineBitsLosslessTag; import com.jpexs.decompiler.flash.tags.DefineBitsTag; +import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.DoABCTag; import com.jpexs.decompiler.flash.tags.JPEGTablesTag; import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.VideoFrameTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.Container; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.types.RECT; import java.io.*; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.zip.DeflaterOutputStream; @@ -434,6 +438,101 @@ public class SWF { return false; } + public void populateVideoFrames(int streamId, List tags, HashMap output) { + for (Object t : tags) { + if (t instanceof VideoFrameTag) { + output.put(((VideoFrameTag) t).frameNum, (VideoFrameTag) t); + } + if (t instanceof Container) { + populateVideoFrames(streamId, ((Container) t).getSubItems(), output); + } + } + } + + public void exportVideos(String outdir) throws IOException { + exportVideos(outdir, tags); + } + + public void exportVideos(String outdir, List tags) throws IOException { + if (!(new File(outdir)).exists()) { + (new File(outdir)).mkdirs(); + } + List os = new ArrayList(this.tags); + for (Tag t : tags) { + if (t instanceof DefineVideoStreamTag) { + DefineVideoStreamTag videoStream = (DefineVideoStreamTag) t; + HashMap frames = new HashMap(); + populateVideoFrames(videoStream.characterID, os, frames); + + FileOutputStream fos = null; + try { + fos = new FileOutputStream(outdir + File.separator + ((DefineVideoStreamTag) t).characterID + ".flv"); + FLVOutputStream flv = new FLVOutputStream(fos); + flv.writeHeader(false, true); + int ms = (int) (1000.0f / ((float) frameRate)); + for (int i = 0; i < frames.size(); i++) { + if (i == 1000) { + break; + } + long posBefore = flv.getPos(); + flv.writeUI8(9); //type video + VideoFrameTag tag = frames.get(i); + flv.writeUI24(1 + tag.videoData.length); + long timeStamp = i * ms; + long ts1 = timeStamp & 0xffffff; + int ts2 = (int) (timeStamp >> 24); + flv.writeUI24(ts1); + flv.writeUI8(ts2); + flv.writeUI24(0); //streamId + int frameType = 0; + if (videoStream.codecID == 2) { //H263 + SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(tag.videoData), SWF.DEFAULT_VERSION); + sis.readUB(17);//pictureStartCode + sis.readUB(5); //version + sis.readUB(8); //temporalReference + int pictureSize = (int) sis.readUB(3); //pictureSize + if (pictureSize == 0) { + sis.readUB(8); //customWidth + sis.readUB(8); //customHeight + } + if (pictureSize == 1) { + sis.readUB(16); //customWidth + sis.readUB(16); //customHeight + } + int pictureType = (int) sis.readUB(2); + switch (pictureType) { + case 0: //intra + frameType = 1; //keyframe + break; + case 1://inter + frameType = 2; + break; + case 2: //disposable + frameType = 3; + break; + } + } + flv.writeUB(4, frameType); + flv.writeUB(4, videoStream.codecID); + flv.write(tag.videoData); + long posAfter = flv.getPos(); + flv.writeUI32(posAfter - posBefore); + } + + + } finally { + if (fos != null) { + try { + fos.close(); + } catch (Exception ex) { + //ignore + } + } + } + } + } + } + public static void exportShapes(String outdir, List tags) throws IOException { if (!(new File(outdir)).exists()) { (new File(outdir)).mkdirs(); diff --git a/trunk/src/com/jpexs/decompiler/flash/SWFInputStream.java b/trunk/src/com/jpexs/decompiler/flash/SWFInputStream.java index 2bd36c881..c03d3d677 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWFInputStream.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWFInputStream.java @@ -210,7 +210,7 @@ public class SWFInputStream extends InputStream { baos.write(r); } } - + /** * Reads one UI32 (Unsigned 32bit integer) value from the stream * diff --git a/trunk/src/com/jpexs/decompiler/flash/SWFOutputStream.java b/trunk/src/com/jpexs/decompiler/flash/SWFOutputStream.java index 60050a99a..bc3afe10f 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWFOutputStream.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWFOutputStream.java @@ -47,6 +47,8 @@ public class SWFOutputStream extends OutputStream { private OutputStream os; private int version; private long pos = 0; + private int bitPos = 0; + private int tempByte = 0; public long getPos() { return pos; @@ -104,7 +106,7 @@ public class SWFOutputStream extends OutputStream { write(value.getBytes("utf8")); write(0); } - + /** * Writes UI32 (Unsigned 32bit integer) value to the stream * @@ -256,8 +258,7 @@ public class SWFOutputStream extends OutputStream { value = value >> 7; } while (loop); } - private int bitPos = 0; - private int tempByte = 0; + /** * Flushes data to underlying stream diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java b/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java index 188801efb..e147d998a 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java @@ -19,6 +19,7 @@ public class ExportDialog extends JDialog { JComboBox images; JComboBox shapes; JComboBox actionScript; + JComboBox movies; boolean cancelled = false; public ExportDialog() { @@ -29,7 +30,7 @@ public class ExportDialog extends JDialog { cancelled = true; } }); - setSize(200, 175); + setSize(200, 200); setLayout(null); JLabel shapesLabel = new JLabel("Shapes"); shapesLabel.setBounds(10, 10, 60, 25); @@ -41,10 +42,15 @@ public class ExportDialog extends JDialog { images = new JComboBox(new String[]{"PNG/JPEG"}); images.setBounds(75, 35, 95, 25); + JLabel moviesLabel = new JLabel("Movies"); + moviesLabel.setBounds(10, 60, 60, 25); + movies = new JComboBox(new String[]{"FLV (No audio)"}); + movies.setBounds(75, 60, 95, 25); + JLabel actionScriptLabel = new JLabel("ActionScript"); - actionScriptLabel.setBounds(10, 60, 60, 25); + actionScriptLabel.setBounds(10, 85, 60, 25); actionScript = new JComboBox(new String[]{"AS", "PCODE"}); - actionScript.setBounds(75, 60, 95, 25); + actionScript.setBounds(75, 85, 95, 25); JButton okButton = new JButton("OK"); okButton.addActionListener(new ActionListener() { @@ -53,7 +59,7 @@ public class ExportDialog extends JDialog { setVisible(false); } }); - okButton.setBounds(20, 95, 75, 25); + okButton.setBounds(20, 120, 75, 25); JButton cancelButton = new JButton("Cancel"); cancelButton.addActionListener(new ActionListener() { @@ -63,12 +69,14 @@ public class ExportDialog extends JDialog { setVisible(false); } }); - cancelButton.setBounds(95, 95, 75, 25); + cancelButton.setBounds(95, 120, 75, 25); Container cnt = getContentPane(); cnt.add(shapes); cnt.add(shapesLabel); cnt.add(images); cnt.add(imagesLabel); + cnt.add(movies); + cnt.add(moviesLabel); cnt.add(actionScript); cnt.add(actionScriptLabel); cnt.add(okButton); diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java b/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java index 61585328b..e4233e7ea 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java @@ -52,6 +52,7 @@ import com.jpexs.decompiler.flash.tags.DefineShapeTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DefineText2Tag; import com.jpexs.decompiler.flash.tags.DefineTextTag; +import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.DoABCTag; import com.jpexs.decompiler.flash.tags.DoInitActionTag; import com.jpexs.decompiler.flash.tags.EndTag; @@ -805,6 +806,10 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi if (t instanceof ShowFrameTag) { return "showframe"; } + + if (t instanceof DefineVideoStreamTag) { + return "movie"; + } return "folder"; } @@ -888,6 +893,7 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi List images = getTagNodesWithType(list, "image", parent, true); List fonts = getTagNodesWithType(list, "font", parent, true); List texts = getTagNodesWithType(list, "text", parent, true); + List movies = getTagNodesWithType(list, "movie", parent, true); List actionScript = new ArrayList(); for (TagNode n : sprites) { @@ -920,6 +926,9 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi TagNode imagesNode = new TagNode("images"); imagesNode.subItems.addAll(images); + TagNode moviesNode = new TagNode("movies"); + moviesNode.subItems.addAll(movies); + TagNode fontsNode = new TagNode("fonts"); fontsNode.subItems.addAll(fonts); @@ -957,6 +966,9 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi if (!imagesNode.subItems.isEmpty()) { ret.add(imagesNode); } + if (!moviesNode.subItems.isEmpty()) { + ret.add(moviesNode); + } if (!buttonsNode.subItems.isEmpty()) { ret.add(buttonsNode); } @@ -968,6 +980,8 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi } + + if (abcPanel != null) { actionScriptNode.subItems.clear(); actionScriptNode.tag = abcPanel.classTree.getModel(); @@ -1066,6 +1080,7 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi } List images = new ArrayList(); List shapes = new ArrayList(); + List movies = new ArrayList(); List actionNodes = new ArrayList(); for (Object d : sel) { if (d instanceof TagNode) { @@ -1079,6 +1094,9 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi if ("as".equals(getTagType(n.tag))) { actionNodes.add(n); } + if ("movie".equals(getTagType(n.tag))) { + movies.add((Tag) n.tag); + } } if (d instanceof TreeElement) { if (((TreeElement) d).isLeaf()) { @@ -1088,6 +1106,7 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi } SWF.exportImages(selFile + File.separator + "images", images, jtt); SWF.exportShapes(selFile + File.separator + "shapes", shapes); + swf.exportVideos(selFile + File.separator + "movies", movies); if (abcPanel != null) { for (int i = 0; i < tlsList.size(); i++) { TreeLeafScript tls = tlsList.get(i); @@ -1107,6 +1126,7 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi } else { Main.swf.exportImages(selFile + File.separator + "images"); Main.swf.exportShapes(selFile + File.separator + "shapes"); + swf.exportVideos(selFile + File.separator + "movies"); Main.swf.exportActionScript(selFile, isPcode); } } catch (Exception ignored) { @@ -1397,7 +1417,9 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi } else { showDetail(DETAILCARDEMPTYPANEL); } - if (tagObj instanceof ASMSource) { + if (tagObj instanceof DefineVideoStreamTag) { + showCard(CARDEMPTYPANEL); + } else if (tagObj instanceof ASMSource) { showCard(CARDACTIONSCRIPTPANEL); actionPanel.setSource((ASMSource) tagObj); } else if (tagObj instanceof DefineBitsTag) { @@ -1426,7 +1448,7 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi } tempFile = File.createTempFile("temp", ".swf"); tempFile.deleteOnExit(); - + FileOutputStream fos = new FileOutputStream(tempFile); SWFOutputStream sos = new SWFOutputStream(fos, 10); sos.write("FWS".getBytes()); @@ -1481,16 +1503,16 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi PlaceObjectTypeTag pot = (PlaceObjectTypeTag) t; int chid = pot.getCharacterId(); int depth = pot.getDepth(); - MATRIX mat = pot.getMatrix(); + MATRIX mat = pot.getMatrix(); if (mat == null) { mat = new MATRIX(); } - mat=(MATRIX)Helper.deepCopy(mat); - if(parent instanceof BoundedTag){ - RECT r=((BoundedTag)parent).getRect(characters); - mat.translateX = mat.translateX +width / 2 - r.getWidth()/2; - mat.translateY = mat.translateY + height/2 - r.getHeight()/2; - }else{ + mat = (MATRIX) Helper.deepCopy(mat); + if (parent instanceof BoundedTag) { + RECT r = ((BoundedTag) parent).getRect(characters); + mat.translateX = mat.translateX + width / 2 - r.getWidth() / 2; + mat.translateY = mat.translateY + height / 2 - r.getHeight() / 2; + } else { mat.translateX = mat.translateX + width / 2; mat.translateY = mat.translateY + height / 2; } diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/graphics/movie16.png b/trunk/src/com/jpexs/decompiler/flash/gui/graphics/movie16.png new file mode 100644 index 000000000..ddcb76eeb Binary files /dev/null and b/trunk/src/com/jpexs/decompiler/flash/gui/graphics/movie16.png differ