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 017984d3f..482554ccf 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -633,53 +633,53 @@ public final class SWF implements SWFContainerItem, Timelined { * @throws IOException */ public void saveTo(OutputStream os) throws IOException { - saveTo(os, compression); + byte[] uncompressedData = saveToByteArray(); + compress(new ByteArrayInputStream(uncompressedData), os, compression, lzmaProperties); } - public String getHeaderBytes() { + public byte[] getHeaderBytes() { return getHeaderBytes(compression, gfx); } - private String getHeaderBytes(SWFCompression compression, boolean gfx) { + private static byte[] getHeaderBytes(SWFCompression compression, boolean gfx) { if (compression == SWFCompression.LZMA_ABC) { - return "ABC"; + return new byte[]{'A', 'B', 'C'}; } - String ret = ""; + byte[] ret = new byte[3]; + if (compression == SWFCompression.LZMA) { - ret += 'Z'; + ret[0] = 'Z'; } else if (compression == SWFCompression.ZLIB) { - ret += 'C'; + ret[0] = 'C'; } else { if (gfx) { - ret += 'G'; + ret[0] = 'G'; } else { - ret += 'F'; + ret[0] = 'F'; } } + if (gfx) { - ret += 'F'; - ret += 'X'; + ret[1] = 'F'; + ret[2] = 'X'; } else { - ret += 'W'; - ret += 'S'; + ret[1] = 'W'; + ret[2] = 'S'; } + return ret; } - /** - * Saves this SWF into new file - * - * @param os OutputStream to save SWF in - * @param compression - * @throws IOException - */ - public void saveTo(OutputStream os, SWFCompression compression) throws IOException { - try { - fixCharactersOrder(false); + private byte[] saveToByteArray() throws IOException { + fixCharactersOrder(false); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(baos, version); + byte[] data; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, version)) { + sos.write(getHeaderBytes(SWFCompression.NONE, gfx)); + sos.writeUI8(version); + sos.writeUI32(0); // placeholder for file length sos.writeRECT(displayRect); sos.writeUI8(0); sos.writeUI8(frameRate); @@ -690,16 +690,68 @@ public final class SWF implements SWFContainerItem, Timelined { sos.writeUI16(0); } - sos.close(); - os.write(Utf8Helper.getBytes(getHeaderBytes(compression, gfx))); - os.write(version); - byte[] data = baos.toByteArray(); - sos = new SWFOutputStream(os, version); - sos.writeUI32(data.length + 8); + data = baos.toByteArray(); + } + + // update file size + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, version)) { + sos.writeUI32(data.length); + byte[] lengthData = baos.toByteArray(); + System.arraycopy(lengthData, 0, data, 4, lengthData.length); + } + + return data; + } + + /** + * Compress SWF file + * + * @param is InputStream + * @param os OutputStream to save SWF in + * @param compression + * @param lzmaProperties + * @throws IOException + */ + public static void compress(InputStream is, OutputStream os, SWFCompression compression, byte[] lzmaProperties) throws IOException { + try { + byte[] hdr = new byte[8]; + + // SWFheader: signature, version and fileSize + if (is.read(hdr) != 8) { + throw new SwfOpenException("SWF header is too short"); + } + + boolean uncompressed = hdr[0] == 'F' || hdr[0] == 'G'; // FWS or GFX + if (!uncompressed) { + // fisrt decompress, then compress to the given format + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + decompress(is, baos, false); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + compress(bais, os, compression, lzmaProperties); + return; + } + + boolean gfx = hdr[1] == 'F' && hdr[2] == 'X'; + int version = hdr[3]; + long fileSize; + try (SWFInputStream sis = new SWFInputStream(null, Arrays.copyOfRange(hdr, 4, 8), 4, 4)) { + fileSize = sis.readUI32("fileSize"); + } + + SWFOutputStream sos = new SWFOutputStream(os, version); + sos.write(getHeaderBytes(compression, gfx)); + sos.writeUI8(version); + sos.writeUI32(fileSize); if (compression == SWFCompression.LZMA || compression == SWFCompression.LZMA_ABC) { - long uncompressedLength = data.length; + long uncompressedLength = fileSize - 8; Encoder enc = new Encoder(); + if (lzmaProperties == null) { + // todo: the bytes are from a sample swf + lzmaProperties = new byte[]{93, 0, 0, 32, 0}; + } + int val = lzmaProperties[0] & 0xFF; int lc = val % 9; int remainder = val / 9; @@ -714,10 +766,10 @@ public final class SWF implements SWFContainerItem, Timelined { } enc.SetDictionarySize(dictionarySize); enc.SetLcLpPb(lc, lp, pb); - baos = new ByteArrayOutputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); enc.SetEndMarkerMode(true); - enc.Code(new ByteArrayInputStream(data), baos, -1, -1, null); - data = baos.toByteArray(); + enc.Code(is, baos, -1, -1, null); + byte[] data = baos.toByteArray(); if (compression == SWFCompression.LZMA) { byte[] udata = new byte[4]; udata[0] = (byte) (data.length & 0xFF); @@ -739,11 +791,13 @@ public final class SWF implements SWFContainerItem, Timelined { udata[7] = (byte) ((uncompressedLength >> 56) & 0xFF); os.write(udata); } + os.write(data); } else if (compression == SWFCompression.ZLIB) { os = new DeflaterOutputStream(os); + Helper.copyStream(is, os, Long.MAX_VALUE); + } else { + Helper.copyStream(is, os, Long.MAX_VALUE); } - - os.write(data); } finally { if (os != null) { os.close(); @@ -768,11 +822,8 @@ public final class SWF implements SWFContainerItem, Timelined { } } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - saveTo(baos, SWFCompression.NONE); - byte[] swfData = baos.toByteArray(); - uncompressedData = swfData; + uncompressedData = saveToByteArray(); } catch (IOException ex) { logger.log(Level.SEVERE, "Cannot save SWF", ex); } @@ -1059,25 +1110,9 @@ public final class SWF implements SWFContainerItem, Timelined { * @param fos Output stream * @return True on success */ - public static boolean fws2cws(InputStream fis, OutputStream fos) { + public static boolean compress(InputStream fis, OutputStream fos, SWFCompression compression) { try { - byte[] swfHead = new byte[8]; - fis.read(swfHead); - - if (swfHead[0] != 'F') { - fis.close(); - return false; - } - swfHead[0] = 'C'; - fos.write(swfHead); - fos = new DeflaterOutputStream(fos); - int i; - while ((i = fis.read()) != -1) { - fos.write(i); - } - - fis.close(); - fos.close(); + compress(fis, fos, compression, null); } catch (IOException ex) { return false; } @@ -1124,18 +1159,18 @@ public final class SWF implements SWFContainerItem, Timelined { } int version = hdr[3]; - SWFInputStream sis = new SWFInputStream(null, Arrays.copyOfRange(hdr, 4, 8), 4, 4); - long fileSize = sis.readUI32("fileSize"); + long fileSize; + try (SWFInputStream sis = new SWFInputStream(null, Arrays.copyOfRange(hdr, 4, 8), 4, 4)) { + fileSize = sis.readUI32("fileSize"); + } + SWFHeader header = new SWFHeader(); header.version = version; header.fileSize = fileSize; - - if (hdr[1] == 'F' && hdr[2] == 'X') { - header.gfx = true; - } + header.gfx = hdr[1] == 'F' && hdr[2] == 'X'; try (SWFOutputStream sos = new SWFOutputStream(os, version)) { - sos.write(Utf8Helper.getBytes(header.gfx ? "GFX" : "FWS")); + sos.write(getHeaderBytes(SWFCompression.NONE, header.gfx)); sos.writeUI8(version); sos.writeUI32(fileSize); @@ -1148,19 +1183,21 @@ public final class SWF implements SWFContainerItem, Timelined { case 'Z': { // ZWS byte[] lzmaprop = new byte[9]; is.read(lzmaprop); - sis = new SWFInputStream(null, lzmaprop); - sis.readUI32("LZMAsize"); // compressed LZMA data size = compressed SWF - 17 byte, - // where 17 = 8 byte header + this 4 byte + 5 bytes decoder properties - int propertiesSize = 5; - byte[] lzmaProperties = sis.readBytes(propertiesSize, "lzmaproperties"); - if (lzmaProperties.length != propertiesSize) { - throw new IOException("LZMA:input .lzma file is too short"); + try (SWFInputStream sis = new SWFInputStream(null, lzmaprop)) { + sis.readUI32("LZMAsize"); // compressed LZMA data size = compressed SWF - 17 byte, + // where 17 = 8 byte header + this 4 byte + 5 bytes decoder properties + + int propertiesSize = 5; + byte[] lzmaProperties = sis.readBytes(propertiesSize, "lzmaproperties"); + if (lzmaProperties.length != propertiesSize) { + throw new IOException("LZMA:input .lzma file is too short"); + } + + decodeLZMAStream(is, os, lzmaProperties, fileSize); + + header.compression = SWFCompression.LZMA; + header.lzmaProperties = lzmaProperties; } - - decodeLZMAStream(is, os, lzmaProperties, fileSize); - - header.compression = SWFCompression.LZMA; - header.lzmaProperties = lzmaProperties; break; } case 'A': { // ABC diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java index c407b5c99..fb6b11930 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java @@ -57,7 +57,7 @@ public class ShapeImporter { int idx = swf.tags.indexOf(st); if (idx != -1) { - swf.tags.add(idx, imageTag); + swf.tags.add(0, imageTag); } else { swf.tags.add(imageTag); } diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index bd32dc39d..3a8299e2f 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.ApplicationInfo; import com.jpexs.decompiler.flash.EventListener; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFBundle; +import com.jpexs.decompiler.flash.SWFCompression; import com.jpexs.decompiler.flash.SWFSourceInfo; import com.jpexs.decompiler.flash.SearchMode; import com.jpexs.decompiler.flash.abc.ABC; @@ -265,8 +266,8 @@ public class CommandLineArgumentParser { out.println(" ...dumps list of AS1/2 sctipts to console"); out.println(" " + (cnt++) + ") -dumpAS3 "); out.println(" ...dumps list of AS3 sctipts to console"); - out.println(" " + (cnt++) + ") -compress "); - out.println(" ...Compress SWF and save it to "); + out.println(" " + (cnt++) + ") -compress [(zlib|lzma)]"); + out.println(" ...Compress SWF and save it to . If is already compressed, it will be re-compressed. Default compression method is ZLIB"); out.println(" " + (cnt++) + ") -decompress "); out.println(" ...Decompress and save it to "); out.println(" " + (cnt++) + ") -swf2xml "); @@ -1226,7 +1227,24 @@ public class CommandLineArgumentParser { try { try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { - if (SWF.fws2cws(fis, fos)) { + SWFCompression compression = SWFCompression.ZLIB; + String compressionString = !args.isEmpty() ? args.pop() : null; + if (compressionString != null) { + switch (compressionString.toLowerCase()) { + case "zlib": + compression = SWFCompression.ZLIB; + break; + case "lzma": + compression = SWFCompression.LZMA; + break; + default: + System.out.println("Unsupported compression method: " + compressionString); + System.exit(0); + break; + } + } + + if (SWF.compress(fis, fos, compression)) { System.out.println("OK"); } else { System.err.println("FAIL"); diff --git a/src/com/jpexs/decompiler/flash/gui/HeaderInfoPanel.java b/src/com/jpexs/decompiler/flash/gui/HeaderInfoPanel.java index 31604e00c..c1533640b 100644 --- a/src/com/jpexs/decompiler/flash/gui/HeaderInfoPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/HeaderInfoPanel.java @@ -242,7 +242,7 @@ public class HeaderInfoPanel extends JPanel implements TagEditorPanel { public void load(SWF swf) { this.swf = swf; - signatureLabel.setText(swf.getHeaderBytes()); + signatureLabel.setText(new String(swf.getHeaderBytes())); switch (swf.compression) { case LZMA: compressionLabel.setText(AppStrings.translate("header.compression.lzma"));