diff --git a/CHANGELOG.md b/CHANGELOG.md index 008b6d430..ad5d1c3a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,8 @@ All notable changes to this project will be documented in this file. - [PR193] Quoting JAR file in ffdec.sh - Refreshing class/exportname association on SymbolClass/ExportAssets deletion - Outputstreams position calculation (ABCOutputStream, ...) +- [#2260] Reading end of file on old GFX format (1.x) +- [#2260] DefineExternalImage on old GFX format (1.x) ### Changed - [#2185] MochiCrypt no longer offered for auto decrypt, user needs to choose variant from "Use unpacker" menu @@ -3441,6 +3443,7 @@ Major version of SWF to XML export changed to 2. [#2257]: https://www.free-decompiler.com/flash/issues/2257 [#2253]: https://www.free-decompiler.com/flash/issues/2253 [#2239]: https://www.free-decompiler.com/flash/issues/2239 +[#2260]: https://www.free-decompiler.com/flash/issues/2260 [#2206]: https://www.free-decompiler.com/flash/issues/2206 [#2100]: https://www.free-decompiler.com/flash/issues/2100 [#2123]: https://www.free-decompiler.com/flash/issues/2123 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 c4cdb9dc3..a1b87e7f1 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -2189,7 +2189,42 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { } default: { // FWS, GFX if (allowUncompressed) { - Helper.copyStream(is, os, fileSize - 8); + //In old versions of GFX format (I saw it in 1.02), the fileSize field + // does not contain size of header (signature + version + filesize = 8 bytes) + if (header.gfx && is.available() >= fileSize) { + final InputStream fis = is; + + //pass to outputstream all we read + InputStream copyIs = new InputStream() { + @Override + public int read() throws IOException { + int value = fis.read(); + os.write(value); + return value; + } + }; + //Use special constructor to pass InputStream + SWFInputStream sis = new SWFInputStream(copyIs); + sis.readRECT("displayRect"); + sis.readFIXED8("frameRate"); + sis.readUI16("frameCount"); + int tagIDTagLength = sis.readUI16("tagIDTagLength"); + long tagLength = (tagIDTagLength & 0x003F); + if (tagLength == 0x3f) { + sis.readSI32("tagLength"); + } + int tagID = (tagIDTagLength) >> 6; + if (tagID == ExporterInfo.ID) { + int exporterVersion = sis.readUI16("exporterInfo"); + if (exporterVersion < 0x200) { //assuming version 2 corrected this + Helper.copyStream(is, os, fileSize - sis.getPos()); + } + } else { + Helper.copyStream(is, os, fileSize - 8 - sis.getPos()); + } + } else { + Helper.copyStream(is, os, fileSize - 8); + } } else { throw new IOException("SWF is not compressed"); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java index 0faa17c08..dbecc6135 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java @@ -273,6 +273,7 @@ import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.FakeMemoryInputStream; import com.jpexs.helpers.Helper; import com.jpexs.helpers.ImmediateFuture; import com.jpexs.helpers.MemoryInputStream; @@ -282,6 +283,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; @@ -384,6 +386,19 @@ public class SWFInputStream implements AutoCloseable { this(swf, data, 0L, data.length); } + /** + * HACK: Special constructor to handle old GFX format + * - DO NOT USE for normal purposes - it won't read tags, etc... + * @param is + */ + public SWFInputStream(InputStream is) throws IOException { + this.swf = null; + this.startingPos = 0; + this.data = null; + this.limit = Integer.MAX_VALUE; + this.is = new FakeMemoryInputStream(is); + } + public SWF getSwf() { return swf; } @@ -810,10 +825,13 @@ public class SWFInputStream implements AutoCloseable { * @return ByteArrayRange object * @throws IOException */ - public ByteArrayRange readByteRangeEx(long count, String name, DumpInfoSpecialType specialType, Object specialValue) throws IOException { + public ByteArrayRange readByteRangeEx(long count, String name, DumpInfoSpecialType specialType, Object specialValue) throws IOException { if (count <= 0) { return ByteArrayRange.EMPTY; } + if (data == null) { + throw new RuntimeException("Data not available - use constructor with data rather than inputstream"); + } newDumpLevel(name, "bytes", specialType, specialValue); @@ -1599,6 +1617,9 @@ public class SWFInputStream implements AutoCloseable { * @throws java.lang.InterruptedException */ public Tag readTag(Timelined timelined, int level, long pos, boolean resolve, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException, InterruptedException { + if (data == null) { + throw new RuntimeException("Data not available - use constructor with data rather than inputstream"); + } int tagIDTagLength = readUI16("tagIDTagLength"); int tagID = (tagIDTagLength) >> 6; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalImage.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalImage.java index d117be42a..f8c378770 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalImage.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalImage.java @@ -19,11 +19,10 @@ package com.jpexs.decompiler.flash.tags.gfx; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.gfx.TgaSupport; import com.jpexs.decompiler.flash.helpers.ImageHelper; import com.jpexs.decompiler.flash.tags.TagInfo; -import com.jpexs.decompiler.flash.tags.base.ImageTag; import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.annotations.Conditional; import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.SerializableImage; @@ -33,12 +32,7 @@ import java.awt.Image; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Objects; -import javax.imageio.ImageIO; -import net.npe.dds.DDSReader; /** * @@ -56,10 +50,15 @@ public class DefineExternalImage extends AbstractGfxImageTag { public int targetHeight; + //I guess this probably depends on ExporterInfo version - version 1 probably has shortFormat + public boolean shortFormat = false; + public String exportName; + @Conditional(value = "shortFormat", revert = true) public String fileName; - + + @HideInRawEdit private SerializableImage serImage; @@ -79,7 +78,9 @@ public class DefineExternalImage extends AbstractGfxImageTag { sos.writeUI16(targetWidth); sos.writeUI16(targetHeight); sos.writeNetString(exportName); - sos.writeNetString(fileName); + if (!shortFormat) { + sos.writeNetString(fileName); + } } /** @@ -96,6 +97,7 @@ public class DefineExternalImage extends AbstractGfxImageTag { public DefineExternalImage(SWF swf) { super(swf, ID, NAME, null); + shortFormat = false; exportName = ""; fileName = ""; targetWidth = 1; @@ -111,7 +113,12 @@ public class DefineExternalImage extends AbstractGfxImageTag { targetWidth = sis.readUI16("targetWidth"); targetHeight = sis.readUI16("targetHeight"); exportName = sis.readNetString("exportName"); - fileName = sis.readNetString("fileName"); + if (sis.available() > 0) { + fileName = sis.readNetString("fileName"); + shortFormat = false; + } else { + shortFormat = true; + } } private void createFailedImage() { @@ -161,8 +168,31 @@ public class DefineExternalImage extends AbstractGfxImageTag { return new Dimension(targetWidth, targetHeight); } + private String getFilename() { + if (shortFormat) { + //Just guessing how this may work... + String extension = ".dds"; + switch (bitmapFormat) { + case BITMAP_FORMAT_DDS: + case BITMAP_FORMAT2_DDS: + extension = ".dds"; + break; + case BITMAP_FORMAT_TGA: + case BITMAP_FORMAT2_TGA: + extension = ".tga"; + break; + case BITMAP_FORMAT2_JPEG: + extension = ".jpg"; + break; + } + return exportName + extension; + } + return fileName; + } + private void initImage() { - if (Objects.equals(cachedImageFilename, fileName) + String fname = getFilename(); + if (Objects.equals(cachedImageFilename, fname) && serImage != null && (serImage.getWidth() == targetWidth && serImage.getHeight() == targetHeight)) { return; } @@ -173,7 +203,7 @@ public class DefineExternalImage extends AbstractGfxImageTag { return; } - BufferedImage bufImage = getExternalBufferedImage(fileName, bitmapFormat); + BufferedImage bufImage = getExternalBufferedImage(fname, bitmapFormat); if (bufImage == null) { createFailedImage(); return; @@ -182,7 +212,7 @@ public class DefineExternalImage extends AbstractGfxImageTag { bufImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB); bufImage.getGraphics().drawImage(scaled, 0, 0, null); serImage = new SerializableImage(bufImage); - cachedImageFilename = fileName; + cachedImageFilename = fname; } @Override @@ -194,8 +224,10 @@ public class DefineExternalImage extends AbstractGfxImageTag { public void getTagInfo(TagInfo tagInfo) { super.getTagInfo(tagInfo); - tagInfo.addInfo("general", "exportName", exportName); - tagInfo.addInfo("general", "fileName", fileName); + tagInfo.addInfo("general", "exportName", exportName); + if (!shortFormat) { + tagInfo.addInfo("general", "fileName", fileName); + } String bitmapFormatStr = "0x" + Integer.toHexString(bitmapFormat); switch (bitmapFormat) { case BITMAP_FORMAT_DEFAULT: diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/FakeMemoryInputStream.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/FakeMemoryInputStream.java new file mode 100644 index 000000000..e75a4dac7 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/FakeMemoryInputStream.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2010-2023 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.helpers; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Special MemoryInputStream that is not a MemoryInputStream in fact. + * Input stream to handle some edge cases + * @author JPEXS + */ +public class FakeMemoryInputStream extends MemoryInputStream { + + private long pos; + + private final int maxLength; + + private final InputStream is; + + public FakeMemoryInputStream(InputStream is) throws IOException { + super(new byte[0]); + this.maxLength = Integer.MAX_VALUE; + this.is = is; + } + + @Override + public byte[] getAllRead() { + throw new UnsupportedOperationException(); + } + + @Override + public long getPos() { + return pos; + } + + @Override + public void seek(long pos) throws IOException { + if (pos < 0) { + throw new IOException("Seek to negative position"); + } + this.pos = pos; + } + + @Override + public synchronized void reset() throws IOException { + seek(0); + } + + @Override + public int read() throws IOException { + if (pos < maxLength) { + pos++; + return is.read(); + } + + return -1; + } + + @Override + public int read(byte[] bytes) throws IOException { + if (pos < maxLength) { + int readCount = is.read(bytes); + pos += readCount; + return readCount; + } + + return -1; + } + + @Override + public int available() throws IOException { + return is.available(); + } +}