diff --git a/CHANGELOG.md b/CHANGELOG.md index d026a26f3..d9f0bc741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ All notable changes to this project will be documented in this file. - Saving Harman encrypted SWFs - Editing encrypted flag on header panel - `-encrypt` command on CLI for Harman encryption +- Apply unpacker menu on binary data +- Harman unpacker for binary data +- Multilevel binary data unpacking is possible ### Fixed - [#2021], [#2000] Caret position in editors when using tabs and / or unicode 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 20ce51a43..c4c4152f9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -122,6 +122,7 @@ import com.jpexs.decompiler.flash.tags.TagStub; import com.jpexs.decompiler.flash.tags.VideoFrameTag; import com.jpexs.decompiler.flash.tags.base.ASMSource; import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.decompiler.flash.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; @@ -357,7 +358,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { public DumpInfoSwfNode dumpInfo; @Internal - public DefineBinaryDataTag binaryData; + public BinaryDataInterface binaryData; @Internal private final HashMap deobfuscated = new HashMap<>(); @@ -1756,7 +1757,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { @Override public String getShortPathTitle() { if (binaryData != null) { - return binaryData.getSwf().getShortPathTitle() + "/DefineBinaryData (" + binaryData.getCharacterId() + ")"; + return binaryData.getSwf().getShortPathTitle() + "/" + binaryData.getPathIdentifier(); } if (openableList != null) { if (openableList.isBundle()) { @@ -1774,7 +1775,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { @Override public String getFullPathTitle() { if (binaryData != null) { - return binaryData.getSwf().getFullPathTitle() + "/DefineBinaryData (" + binaryData.getCharacterId() + ")"; + return binaryData.getSwf().getFullPathTitle() + "/" + binaryData.getPathIdentifier(); } if (openableList != null) { if (openableList.isBundle()) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/BinaryDataExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/BinaryDataExporter.java index 2f1525f95..9050872d2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/BinaryDataExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/BinaryDataExporter.java @@ -24,6 +24,7 @@ import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.settings.BinaryDataExportSettings; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.helpers.Helper; import com.jpexs.helpers.Path; import java.io.BufferedOutputStream; @@ -44,69 +45,73 @@ import java.util.Set; public class BinaryDataExporter { public List exportBinaryData(AbortRetryIgnoreHandler handler, String outdir, ReadOnlyTagList tags, BinaryDataExportSettings settings, EventListener evl) throws IOException, InterruptedException { + List binaryDatas = new ArrayList<>(); + for (Tag t : tags) { + if (t instanceof BinaryDataInterface) { + binaryDatas.add((BinaryDataInterface) t); + } + } + return exportBinaryData(handler, outdir, binaryDatas, settings, evl); + } + + public List exportBinaryData(AbortRetryIgnoreHandler handler, String outdir, List binaryDatas, BinaryDataExportSettings settings, EventListener evl) throws IOException, InterruptedException { List ret = new ArrayList<>(); if (Thread.currentThread().isInterrupted()) { return ret; } - if (tags.isEmpty()) { + if (binaryDatas.isEmpty()) { return ret; } File foutdir = new File(outdir); Path.createDirectorySafe(foutdir); - int count = 0; - for (Tag t : tags) { - if (t instanceof DefineBinaryDataTag) { - count++; - } - } + int count = binaryDatas.size(); if (count == 0) { return ret; } int currentIndex = 1; - for (final Tag t : tags) { - if (t instanceof DefineBinaryDataTag) { - DefineBinaryDataTag bdt = (DefineBinaryDataTag) t; - if (evl != null) { - evl.handleExportingEvent("binarydata", currentIndex, count, t.getName()); - } - - String ext = bdt.innerSwf == null ? ".bin" : ".swf"; - final File file = new File(outdir + File.separator + Helper.makeFileName(bdt.getCharacterExportFileName() + ext)); - new RetryTask(() -> { - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { - fos.write(bdt.binaryData.getRangeData()); - } - }, handler).run(); - - Set classNames = bdt.getClassNames(); - if (Configuration.as3ExportNamesUseClassNamesOnly.get() && !classNames.isEmpty()) { - for (String className : classNames) { - File classFile = new File(outdir + File.separator + Helper.makeFileName(className + ext)); - new RetryTask(() -> { - Files.copy(file.toPath(), classFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - }, handler).run(); - ret.add(classFile); - } - file.delete(); - } else { - ret.add(file); - } - - if (Thread.currentThread().isInterrupted()) { - break; - } - - if (evl != null) { - evl.handleExportedEvent("binarydata", currentIndex, count, t.getName()); - } - - currentIndex++; + for (final BinaryDataInterface t : binaryDatas) { + if (evl != null) { + evl.handleExportingEvent("binarydata", currentIndex, count, t.getName()); } + + String ext = t.getInnerSwf() == null ? ".bin" : ".swf"; + final File file = new File(outdir + File.separator + Helper.makeFileName(t.getCharacterExportFileName() + ext)); + new RetryTask(() -> { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { + fos.write(t.getDataBytes().getRangeData()); + } + }, handler).run(); + + DefineBinaryDataTag bdt = (DefineBinaryDataTag) t.getTopLevelBinaryData(); + + Set classNames = bdt.getClassNames(); + if (Configuration.as3ExportNamesUseClassNamesOnly.get() && !classNames.isEmpty()) { + for (String className : classNames) { + File classFile = new File(outdir + File.separator + Helper.makeFileName(t.getClassExportFileName(className) + ext)); + new RetryTask(() -> { + Files.copy(file.toPath(), classFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + }, handler).run(); + ret.add(classFile); + } + file.delete(); + } else { + ret.add(file); + } + + if (Thread.currentThread().isInterrupted()) { + break; + } + + if (evl != null) { + evl.handleExportedEvent("binarydata", currentIndex, count, t.getName()); + } + + currentIndex++; } return ret; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanBinaryDataEncrypt.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanBinaryDataEncrypt.java index 60f2e4037..7830c49b1 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanBinaryDataEncrypt.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanBinaryDataEncrypt.java @@ -32,25 +32,7 @@ import javax.crypto.spec.SecretKeySpec; public class HarmanBinaryDataEncrypt { - private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated"; - - private static final String[] hexStringCache; - - static { - hexStringCache = new String[256]; - for (int i = 0; i < hexStringCache.length; i++) { - hexStringCache[i] = String.format("%02x", i); - } - } - - public static String byteArrayToHex(byte[] data) { - StringBuilder sb = new StringBuilder(data.length * 2); - for (byte b : data) { - sb.append(hexStringCache[b & 0xff]); - } - - return sb.toString(); - } + private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated"; public static byte[] encrypt(byte[] data) { byte[] result; @@ -153,6 +135,9 @@ public class HarmanBinaryDataEncrypt { } public static byte[] decrypt(byte[] data) { + if (data.length < 32) { + return null; + } long encryptedLen = data.length; long encryptedLenXorHash = unpack(data, 0); long decryptedLenXorRandom1 = unpack(data, 4); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanSwfEncrypt.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanSwfEncrypt.java index 7729f9fc8..70f8b1707 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanSwfEncrypt.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/harman/HarmanSwfEncrypt.java @@ -19,9 +19,6 @@ package com.jpexs.decompiler.flash.harman; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.security.InvalidAlgorithmParameterException; @@ -44,26 +41,7 @@ import javax.crypto.spec.SecretKeySpec; */ public class HarmanSwfEncrypt { - private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated"; - - - private static final String[] hexStringCache; - - static { - hexStringCache = new String[256]; - for (int i = 0; i < hexStringCache.length; i++) { - hexStringCache[i] = String.format("%02x", i); - } - } - - public static String byteArrayToHex(byte[] data) { - StringBuilder sb = new StringBuilder(data.length * 2); - for (byte b : data) { - sb.append(hexStringCache[b & 0xff]); - } - - return sb.toString(); - } + private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated"; private static int sum(byte[] data) { int s = 0; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPacker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPacker.java new file mode 100644 index 000000000..2fabb7671 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/HarmanAirPacker.java @@ -0,0 +1,79 @@ +/* + * 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.decompiler.flash.packers; + +import com.jpexs.decompiler.flash.harman.HarmanBinaryDataEncrypt; +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.helpers.Helper; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * + * @author JPEXS + */ +public class HarmanAirPacker implements Packer { + + @Override + public Boolean suitableForBinaryData(DefineBinaryDataTag dataTag) { + if (dataTag.binaryData.getLength() < 32) { + return false; + } + return null; + } + + @Override + public Boolean suitableForData(byte[] data) { + if (data.length < 32) { + return false; + } + return null; + } + + @Override + public boolean decrypt(InputStream is, OutputStream os) throws IOException { + byte[] encryptedData = Helper.readStream(is); + byte[] decryptedData = HarmanBinaryDataEncrypt.decrypt(encryptedData); + if (decryptedData == null) { + return false; + } + os.write(decryptedData); + return true; + } + + @Override + public boolean encrypt(InputStream is, OutputStream os) throws IOException { + byte[] data = Helper.readStream(is); + byte[] encryptedData = HarmanBinaryDataEncrypt.encrypt(data); + if (encryptedData == null) { + return false; + } + os.write(encryptedData); + return true; + } + + @Override + public String getName() { + return "Harman AIR SDK"; + } + + @Override + public String getIdentifier() { + return "harmanair"; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker.java index 6c1686474..641438720 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/MochiCryptPacker.java @@ -46,7 +46,12 @@ public class MochiCryptPacker implements Packer { if (!handleXor(payload)) { return false; } - Helper.copyStream(new InflaterInputStream(new ByteArrayInputStream(payload)), os); + try { + Helper.copyStreamEx(new InflaterInputStream(new ByteArrayInputStream(payload)), os); + } catch (IOException ex) { + return false; + } + return true; } @@ -100,7 +105,7 @@ public class MochiCryptPacker implements Packer { public boolean encrypt(InputStream is, OutputStream os) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DeflaterOutputStream def = new DeflaterOutputStream(baos); - Helper.copyStream(is, def); + Helper.copyStreamEx(is, def); def.finish(); byte[] payload = baos.toByteArray(); @@ -117,4 +122,14 @@ public class MochiCryptPacker implements Packer { public String getName() { return "MochiCrypt"; } + + @Override + public Boolean suitableForData(byte[] data) { + return null; + } + + @Override + public String getIdentifier() { + return "mochicrypt"; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/Packer.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/Packer.java index 9fb5112f3..234c293d6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/Packer.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/packers/Packer.java @@ -37,6 +37,16 @@ public interface Packer { * work */ public Boolean suitableForBinaryData(DefineBinaryDataTag dataTag); + + /** + * Is this data packed with this packer? + * + * @param data + * @return true = it definitely is encrypted with this, false = it + * definitely is not encrypted with this, null = it is unknown that it will + * work + */ + public Boolean suitableForData(byte[] data); /** * Unpack the data @@ -65,4 +75,6 @@ public interface Packer { * @return */ public String getName(); + + public String getIdentifier(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java index f56e89983..46476944f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBinaryDataTag.java @@ -22,9 +22,12 @@ import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.configuration.CustomConfigurationKeys; import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration; +import com.jpexs.decompiler.flash.packers.HarmanAirPacker; import com.jpexs.decompiler.flash.packers.MochiCryptPacker; import com.jpexs.decompiler.flash.packers.Packer; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.PackedBinaryData; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.annotations.Internal; import com.jpexs.decompiler.flash.types.annotations.Reserved; @@ -43,7 +46,7 @@ import java.nio.charset.Charset; * @author JPEXS */ @SWFVersion(from = 9) -public class DefineBinaryDataTag extends CharacterTag { +public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInterface { public static final int ID = 87; @@ -63,11 +66,19 @@ public class DefineBinaryDataTag extends CharacterTag { @Internal public Packer usedPacker; + + @Internal + private PackedBinaryData sub; - private final Packer[] PACKERS = { - new MochiCryptPacker() + private static final Packer[] PACKERS = { + new MochiCryptPacker(), + new HarmanAirPacker() }; + public static Packer[] getAvailablePackers() { + return PACKERS; + } + /** * Constructor * @@ -84,6 +95,25 @@ public class DefineBinaryDataTag extends CharacterTag { readData(sis, data, 0, false, false, false); } + public PackedBinaryData getSub() { + return sub; + } + + @Override + public boolean unpack(Packer packer) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + if (!packer.decrypt(new ByteArrayInputStream(binaryData.getRangeData()), baos)) { + return false; + } + } catch (IOException ex) { + return false; + } + sub = new PackedBinaryData(swf, this, new ByteArrayRange(baos.toByteArray())); + usedPacker = packer; + return true; + } + @Override public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { tag = sis.readUI16("tag"); @@ -100,16 +130,16 @@ public class DefineBinaryDataTag extends CharacterTag { InputStream is = new ByteArrayInputStream(binaryData.getArray(), binaryData.getPos(), binaryData.getLength()); detectPacker(); String packerAdd = ""; + BinaryDataInterface binaryData = this; if (usedPacker != null) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - usedPacker.decrypt(is, baos); - is = new ByteArrayInputStream(baos.toByteArray()); - packerAdd = " - " + usedPacker.getName(); + unpack(usedPacker); + is = new ByteArrayInputStream(sub.getDataBytes().getRangeData()); + binaryData = sub; } - SWF bswf = new SWF(is, null, "(SWF Data" + packerAdd + ")", Configuration.parallelSpeedUp.get(), charset); - innerSwf = bswf; - bswf.binaryData = this; + SWF bswf = new SWF(is, null, "(SWF Data)", Configuration.parallelSpeedUp.get(), charset); + binaryData.setInnerSwf(bswf); + bswf.binaryData = binaryData; } catch (IOException | InterruptedException ex) { // ignore } @@ -138,6 +168,7 @@ public class DefineBinaryDataTag extends CharacterTag { this.tag = characterId; } + @Override public void detectPacker() { for (Packer packer : PACKERS) { if (packer.suitableForBinaryData(this) == Boolean.TRUE) { @@ -147,6 +178,7 @@ public class DefineBinaryDataTag extends CharacterTag { } } + @Override public boolean isSwfData() { try { if (binaryData.getLength() > 8) { @@ -159,9 +191,7 @@ public class DefineBinaryDataTag extends CharacterTag { //ignored } - detectPacker(); - - return usedPacker != null; + return false; } @Override @@ -174,4 +204,77 @@ public class DefineBinaryDataTag extends CharacterTag { } return false; } + + @Override + public Packer getUsedPacker() { + return usedPacker; + } + + @Override + public void setDataBytes(ByteArrayRange data) { + this.binaryData = data; + } + + @Override + public ByteArrayRange getDataBytes() { + return binaryData; + } + + @Override + public boolean pack() { + if (sub == null) { + return false; + } + sub.pack(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + if (!usedPacker.encrypt(new ByteArrayInputStream(sub.getDataBytes().getRangeData()), baos)) { + return false; + } + } catch (IOException ex) { + return false; + } + setDataBytes(new ByteArrayRange(baos.toByteArray())); + return true; + } + + @Override + public void setInnerSwf(SWF swf) { + this.innerSwf = swf; + } + + @Override + public SWF getInnerSwf() { + return this.innerSwf; + } + + @Override + public String getPathIdentifier() { + return "DefineBinaryData (" + getCharacterId() + ")"; + } + + @Override + public String getStoragesPathIdentifier() { + return "binaryData[" + getCharacterId() + "]"; + } + + @Override + public BinaryDataInterface getTopLevelBinaryData() { + return this; + } + + @Override + public void setModified(boolean value) { + super.setModified(value); + if (!value) { + if (sub != null) { + sub.setModified(false); + } + } + } + + @Override + public String getClassExportFileName(String className) { + return className; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/BinaryDataInterface.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/BinaryDataInterface.java new file mode 100644 index 000000000..e12e7d5bb --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/BinaryDataInterface.java @@ -0,0 +1,66 @@ +/* + * 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.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.packers.Packer; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import com.jpexs.helpers.ByteArrayRange; + +/** + * + * @author JPEXS + */ +public interface BinaryDataInterface extends Exportable { + + public PackedBinaryData getSub(); + + public boolean isSwfData(); + + public boolean unpack(Packer packer); + + public void detectPacker(); + + public Packer getUsedPacker(); + + public void setDataBytes(ByteArrayRange data); + + public ByteArrayRange getDataBytes(); + + public void setModified(boolean value); + + public boolean pack(); + + public SWF getSwf(); + + public void setInnerSwf(SWF swf); + + public SWF getInnerSwf(); + + public String getPathIdentifier(); + + public String getStoragesPathIdentifier(); + + public BinaryDataInterface getTopLevelBinaryData(); + + public String getCharacterExportFileName(); + + public String getName(); + + public String getClassExportFileName(String className); + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PackedBinaryData.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PackedBinaryData.java new file mode 100644 index 000000000..beeefe9cd --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PackedBinaryData.java @@ -0,0 +1,246 @@ +/* + * 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.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.packers.Packer; +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.decompiler.flash.treeitems.Openable; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class PackedBinaryData implements TreeItem, BinaryDataInterface { + + private boolean modified = false; + private final SWF swf; + private final BinaryDataInterface parent; + private ByteArrayRange data; + private PackedBinaryData sub; + private Packer usedPacker; + private SWF innerSwf; + + public PackedBinaryData(SWF swf, BinaryDataInterface parent, ByteArrayRange data) { + this.swf = swf; + this.parent = parent; + this.data = data; + } + + @Override + public boolean unpack(Packer packer) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + if (!packer.decrypt(new ByteArrayInputStream(data.getRangeData()), baos)) { + return false; + } + } catch (IOException ex) { + return false; + } + sub = new PackedBinaryData(swf, this, new ByteArrayRange(baos.toByteArray())); + usedPacker = packer; + return true; + } + + @Override + public PackedBinaryData getSub() { + return sub; + } + + public BinaryDataInterface getParent() { + return parent; + } + + @Override + public Openable getOpenable() { + return swf; + } + + @Override + public SWF getSwf() { + return swf; + } + + @Override + public void setModified(boolean value) { + modified = value; + if (value) { + parent.setModified(value); + } else { + if (sub != null) { + sub.setModified(false); + } + } + } + + @Override + public boolean isModified() { + return modified; + } + + @Override + public boolean isSwfData() { + try { + if (data.getLength() > 8) { + String signature = new String(data.getRangeData(0, 3), Utf8Helper.charset); + if (SWF.swfSignatures.contains(signature)) { + return true; + } + } + } catch (Exception ex) { + //ignored + } + return false; + } + + @Override + public Packer getUsedPacker() { + return usedPacker; + } + + @Override + public void detectPacker() { + for (Packer packer : DefineBinaryDataTag.getAvailablePackers()) { + if (packer.suitableForData(data.getRangeData()) == Boolean.TRUE) { + usedPacker = packer; + break; + } + } + } + + @Override + public ByteArrayRange getDataBytes() { + return data; + } + + @Override + public void setDataBytes(ByteArrayRange data) { + this.data = data; + setModified(true); + } + + @Override + public boolean pack() { + if (sub == null) { + return false; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + if (!usedPacker.encrypt(new ByteArrayInputStream(sub.getDataBytes().getRangeData()), baos)) { + return false; + } + } catch (IOException ex) { + return false; + } + setDataBytes(new ByteArrayRange(baos.toByteArray())); + return true; + } + + @Override + public String toString() { + return "(Data - " + parent.getUsedPacker().getName() + ")"; + } + + @Override + public void setInnerSwf(SWF swf) { + this.innerSwf = swf; + } + + @Override + public SWF getInnerSwf() { + return this.innerSwf; + } + + @Override + public String getPathIdentifier() { + return "(Data - " + parent.getUsedPacker().getName() + ")"; + } + + @Override + public String getStoragesPathIdentifier() { + return "data-" + parent.getUsedPacker().getIdentifier(); + } + + @Override + public BinaryDataInterface getTopLevelBinaryData() { + PackedBinaryData packed = this; + while (packed.parent instanceof PackedBinaryData) { + packed = (PackedBinaryData) packed.parent; + } + return packed.parent; + } + + @Override + public String getExportFileName() { + List parts = new ArrayList<>(); + BinaryDataInterface binaryData = this; + while (binaryData instanceof PackedBinaryData) { + parts.add(0, ((PackedBinaryData) binaryData).parent.getUsedPacker().getIdentifier()); + binaryData = ((PackedBinaryData) binaryData).parent; + } + + parts.add(0, binaryData.getExportFileName()); + return String.join("_", parts); + } + + @Override + public String getCharacterExportFileName() { + List parts = new ArrayList<>(); + BinaryDataInterface binaryData = this; + while (binaryData instanceof PackedBinaryData) { + parts.add(0, ((PackedBinaryData) binaryData).parent.getUsedPacker().getIdentifier()); + binaryData = ((PackedBinaryData) binaryData).parent; + } + + parts.add(0, binaryData.getCharacterExportFileName()); + return String.join("_", parts); + } + + @Override + public String getName() { + List parts = new ArrayList<>(); + BinaryDataInterface binaryData = this; + while (binaryData instanceof PackedBinaryData) { + parts.add(0, ((PackedBinaryData) binaryData).parent.getUsedPacker().getName()); + binaryData = ((PackedBinaryData) binaryData).parent; + } + + parts.add(0, binaryData.getName()); + return String.join(" / ", parts); + } + + @Override + public String getClassExportFileName(String className) { + List parts = new ArrayList<>(); + BinaryDataInterface binaryData = this; + while (binaryData instanceof PackedBinaryData) { + parts.add(0, binaryData.getStoragesPathIdentifier()); + binaryData = ((PackedBinaryData) binaryData).parent; + } + + parts.add(0, binaryData.getClassExportFileName(className)); + return String.join("_", parts); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java index 72d6587ee..42ed89179 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java @@ -787,6 +787,15 @@ public class Helper { return baos.toByteArray(); } + public static void copyStreamEx(InputStream is, OutputStream os) throws IOException { + final int bufSize = 4096; + byte[] buf = new byte[bufSize]; + int cnt = 0; + while ((cnt = is.read(buf)) > 0) { + os.write(buf, 0, cnt); + } + } + public static void copyStream(InputStream is, OutputStream os) { try { final int bufSize = 4096; diff --git a/src/com/jpexs/decompiler/flash/gui/BinaryPanel.java b/src/com/jpexs/decompiler/flash/gui/BinaryPanel.java index 4520d7cf4..80ebdc846 100644 --- a/src/com/jpexs/decompiler/flash/gui/BinaryPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/BinaryPanel.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.decompiler.flash.gui.hexview.HexView; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; @@ -39,13 +40,13 @@ public final class BinaryPanel extends JPanel { private byte[] data; - private JPanel swfInsidePanel; + private JPanel swfOrPackedDataInsidePanel; - private DefineBinaryDataTag binaryDataTag = null; + private BinaryDataInterface binaryData = null; private final MainPanel mainPanel; - private final JLabel swfInsideLabel; + private final JLabel swfOrPackedDataInsideLabel; public BinaryPanel(final MainPanel mainPanel) { super(new BorderLayout()); @@ -66,44 +67,50 @@ public final class BinaryPanel extends JPanel { setBinaryData(binaryDataTag); } });*/ - swfInsideLabel = new JLabel(AppStrings.translate("binarydata.swfInside")); + swfOrPackedDataInsideLabel = new JLabel(AppStrings.translate("binarydata.swfInside")); - swfInsidePanel = new JPanel(); - swfInsidePanel.setBackground(new Color(253, 205, 137)); - swfInsidePanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - swfInsidePanel.add(swfInsideLabel); - swfInsidePanel.setFocusable(true); - swfInsidePanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED)); - swfInsidePanel.addMouseListener(new MouseAdapter() { + swfOrPackedDataInsidePanel = new JPanel(); + swfOrPackedDataInsidePanel.setBackground(new Color(253, 205, 137)); + swfOrPackedDataInsidePanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + swfOrPackedDataInsidePanel.add(swfOrPackedDataInsideLabel); + swfOrPackedDataInsidePanel.setFocusable(true); + swfOrPackedDataInsidePanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED)); + swfOrPackedDataInsidePanel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - mainPanel.loadFromBinaryTag(binaryDataTag); - swfInsidePanel.setVisible(false); + if (binaryData.getUsedPacker() != null) { + binaryData.unpack(binaryData.getUsedPacker()); + } + mainPanel.loadFromBinaryTag(binaryData); + swfOrPackedDataInsidePanel.setVisible(false); } }); - add(swfInsidePanel, BorderLayout.NORTH); - swfInsidePanel.setVisible(false); + add(swfOrPackedDataInsidePanel, BorderLayout.NORTH); + swfOrPackedDataInsidePanel.setVisible(false); } - public void setBinaryData(DefineBinaryDataTag binaryDataTag) { - this.binaryDataTag = binaryDataTag; - data = binaryDataTag == null ? null : binaryDataTag.binaryData.getRangeData(); + public void setBinaryData(BinaryDataInterface binaryData) { + this.binaryData = binaryData; + data = binaryData == null ? null : binaryData.getDataBytes().getRangeData(); if (data != null) { hexEditor.setData(data, null, null); - boolean isSwfData = binaryDataTag.isSwfData(); + boolean isSwfData = binaryData.isSwfData(); if (isSwfData) { - if (binaryDataTag.usedPacker != null) { - swfInsideLabel.setText(AppStrings.translate("binarydata.swfInside.packer").replace("%packer%", binaryDataTag.usedPacker.getName())); - } else { - swfInsideLabel.setText(AppStrings.translate("binarydata.swfInside")); + swfOrPackedDataInsideLabel.setText(AppStrings.translate("binarydata.swfInside")); + } else { + binaryData.detectPacker(); + if (binaryData.getUsedPacker() != null) { + swfOrPackedDataInsideLabel.setText(AppStrings.translate("binarydata.dataInside.packer").replace("%packer%", binaryData.getUsedPacker().getName())); } } - swfInsidePanel.setVisible(binaryDataTag.innerSwf == null && isSwfData); + swfOrPackedDataInsidePanel.setVisible( + (binaryData.getSub() == null && binaryData.getUsedPacker() != null) + || (isSwfData && binaryData.getInnerSwf() == null)); } else { hexEditor.setData(new byte[0], null, null); - swfInsidePanel.setVisible(false); + swfOrPackedDataInsidePanel.setVisible(false); } hexEditor.revalidate(); diff --git a/src/com/jpexs/decompiler/flash/gui/ExportDialog.java b/src/com/jpexs/decompiler/flash/gui/ExportDialog.java index 5d810506d..9c4d65b28 100644 --- a/src/com/jpexs/decompiler/flash/gui/ExportDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/ExportDialog.java @@ -38,6 +38,7 @@ import com.jpexs.decompiler.flash.tags.DefineFont4Tag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.ImageTag; @@ -100,7 +101,7 @@ public class ExportDialog extends AppDialog { {DefineVideoStreamTag.class}, {SoundTag.class}, {ASMSource.class, ScriptPack.class, TagScript.class}, - {DefineBinaryDataTag.class}, + {BinaryDataInterface.class}, {Frame.class}, {Frame.class}, {ButtonTag.class}, diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index 9d7cf08fd..a60691cf4 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -161,14 +161,10 @@ public abstract class MainFrameMenu implements MenuBuilder { try { openable.saveTo(baos); SWF swf = (SWF) openable; - byte[] data = baos.toByteArray(); - if (swf.binaryData.usedPacker != null) { - ByteArrayOutputStream encBaos = new ByteArrayOutputStream(); - swf.binaryData.usedPacker.encrypt(new ByteArrayInputStream(data), encBaos); - data = encBaos.toByteArray(); - } - swf.binaryData.binaryData = new ByteArrayRange(data); + byte[] data = baos.toByteArray(); + swf.binaryData.setDataBytes(new ByteArrayRange(data)); swf.binaryData.setModified(true); + swf.binaryData.getTopLevelBinaryData().pack(); saved = saveOpenable(swf.binaryData.getSwf()); //save parent swf } catch (IOException ex) { Logger.getLogger(MainFrameMenu.class.getName()).log(Level.SEVERE, "Cannot save SWF", ex); @@ -263,7 +259,7 @@ public abstract class MainFrameMenu implements MenuBuilder { SWF swf = (SWF) openable; if (swf.binaryData != null) { // embedded swf - swf.binaryData.innerSwf = null; + swf.binaryData.setInnerSwf(null); swf.clearTagSwfs(); binaryDataClosedSwfs.add(swf); } else { diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 955a8b881..df4cdfa1c 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -165,6 +165,7 @@ import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.TagInfo; import com.jpexs.decompiler.flash.tags.UnknownTag; import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.decompiler.flash.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; @@ -174,6 +175,7 @@ import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.ImageTag; import com.jpexs.decompiler.flash.tags.base.MissingCharacterHandler; import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.PackedBinaryData; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundImportException; @@ -2022,7 +2024,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se List sounds = new ArrayList<>(); List texts = new ArrayList<>(); List as12scripts = new ArrayList<>(); - List binaryData = new ArrayList<>(); + List binaryData = new ArrayList<>(); Map> frames = new HashMap<>(); List fonts = new ArrayList<>(); List fonts4 = new ArrayList<>(); @@ -2042,7 +2044,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } } - if (d instanceof Tag || d instanceof ASMSource) { + if (d instanceof Tag || d instanceof ASMSource || d instanceof BinaryDataInterface) { TreeNodeType nodeType = TagTree.getTreeNodeType(d); if (nodeType == TreeNodeType.IMAGE) { images.add((Tag) d); @@ -2074,7 +2076,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se sounds.add((Tag) d); } if (nodeType == TreeNodeType.BINARY_DATA) { - binaryData.add((Tag) d); + binaryData.add((BinaryDataInterface) d); } if (nodeType == TreeNodeType.TEXT) { texts.add((Tag) d); @@ -2169,7 +2171,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } if (export.isOptionEnabled(BinaryDataExportMode.class)) { - ret.addAll(new BinaryDataExporter().exportBinaryData(handler, selFile2 + File.separator + BinaryDataExportSettings.EXPORT_FOLDER_NAME, new ReadOnlyTagList(binaryData), + ret.addAll(new BinaryDataExporter().exportBinaryData(handler, selFile2 + File.separator + BinaryDataExportSettings.EXPORT_FOLDER_NAME, binaryData, new BinaryDataExportSettings(export.getValue(BinaryDataExportMode.class)), evl)); } @@ -2466,6 +2468,27 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se public List getSwfs() { return openables; } + + public Map getSwfsMap(SWF swf) { + Map result = new LinkedHashMap<>(); + populateSwfs(result, swf, swf.getShortFileName()); + return result; + } + + private void populateSwfs(Map result, SWF targetSwf, String name) { + for (Tag t : targetSwf.getTags()) { + if (t instanceof DefineBinaryDataTag) { + BinaryDataInterface binaryData = (BinaryDataInterface) t; + String bname = name; + do { + bname = bname + "/" + binaryData.getPathIdentifier(); + if (binaryData.getInnerSwf() != null) { + result.put(bname, binaryData.getInnerSwf()); + } + } while ((binaryData = binaryData.getSub()) != null); + } + } + } public OpenableList getCurrentSwfList() { SWF swf = getCurrentSwf(); @@ -5371,32 +5394,25 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se showCard(CARDDUMPVIEW); } - public void loadFromBinaryTag(final DefineBinaryDataTag binaryDataTag) { + public void loadFromBinaryTag(final BinaryDataInterface binaryDataTag) { loadFromBinaryTag(Arrays.asList(binaryDataTag)); } - public void loadFromBinaryTag(final List binaryDataTags) { + public void loadFromBinaryTag(final List binaryDataTags) { Main.loadingDialog.setVisible(true); new CancellableWorker() { @Override protected Void doInBackground() throws Exception { try { - for (DefineBinaryDataTag binaryDataTag : binaryDataTags) { - String path = binaryDataTag.getSwf().getShortPathTitle() + "/DefineBinaryData (" + binaryDataTag.getCharacterId() + ")"; + for (BinaryDataInterface binaryData : binaryDataTags) { + String path = binaryData.getSwf().getShortPathTitle() + "/" + binaryData.getPathIdentifier(); try { SwfSpecificCustomConfiguration conf = Configuration.getSwfSpecificCustomConfiguration(path); String charset = conf == null ? Charset.defaultCharset().name() : conf.getCustomData(CustomConfigurationKeys.KEY_CHARSET, Charset.defaultCharset().name()); - byte[] data = binaryDataTag.binaryData.getRangeData(); - String packerAdd = ""; - if (binaryDataTag.usedPacker != null) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - binaryDataTag.usedPacker.decrypt(new ByteArrayInputStream(data), baos); - data = baos.toByteArray(); - packerAdd = " - " + binaryDataTag.usedPacker.getName(); - } + byte[] data = binaryData.getDataBytes().getRangeData(); InputStream is = new ByteArrayInputStream(data); - SWF bswf = new SWF(is, null, "(SWF Data" + packerAdd + ")", new ProgressListener() { + SWF bswf = new SWF(is, null, "(SWF Data)", new ProgressListener() { @Override public void progress(int p) { Main.loadingDialog.setPercent(p); @@ -5406,8 +5422,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se public void status(String status) { } }, Configuration.parallelSpeedUp.get(), charset); - binaryDataTag.innerSwf = bswf; - bswf.binaryData = binaryDataTag; + binaryData.setInnerSwf(bswf); + bswf.binaryData = binaryData; } catch (IOException ex) { //ignore } @@ -5471,9 +5487,9 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } else if (treeItem instanceof MetadataTag) { MetadataTag metadataTag = (MetadataTag) treeItem; previewPanel.showMetaDataPanel(metadataTag); - } else if (treeItem instanceof DefineBinaryDataTag) { - DefineBinaryDataTag binaryTag = (DefineBinaryDataTag) treeItem; - previewPanel.showBinaryPanel(binaryTag); + } else if (treeItem instanceof BinaryDataInterface) { + BinaryDataInterface binary = (BinaryDataInterface) treeItem; + previewPanel.showBinaryPanel(binary); } else if (treeItem instanceof ProductInfoTag) { ProductInfoTag productInfoTag = (ProductInfoTag) treeItem; previewPanel.showProductInfoPanel(productInfoTag); @@ -5787,7 +5803,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } else if (treeItem instanceof MetadataTag) { showPreview(treeItem, previewPanel, -1, null); showCard(CARDPREVIEWPANEL); - } else if (treeItem instanceof DefineBinaryDataTag) { + } else if (treeItem instanceof BinaryDataInterface) { showPreview(treeItem, previewPanel, -1, null); showCard(CARDPREVIEWPANEL); } else if (treeItem instanceof UnknownTag) { diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java index de2951f1d..4063fc44d 100644 --- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java @@ -32,7 +32,6 @@ import com.jpexs.decompiler.flash.gui.player.FlashPlayerPanel; import com.jpexs.decompiler.flash.gui.player.MediaDisplay; import com.jpexs.decompiler.flash.gui.player.PlayerControls; import com.jpexs.decompiler.flash.math.BezierUtils; -import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineMorphShape2Tag; import com.jpexs.decompiler.flash.tags.DefineShape4Tag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; @@ -43,6 +42,7 @@ import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.UnknownTag; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.FontTag; @@ -1647,9 +1647,9 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel parametersPanel.setVisible(false); } - public void showBinaryPanel(DefineBinaryDataTag binaryDataTag) { + public void showBinaryPanel(BinaryDataInterface binaryData) { showCardLeft(BINARY_TAG_CARD); - binaryPanel.setBinaryData(binaryDataTag); + binaryPanel.setBinaryData(binaryData); parametersPanel.setVisible(false); } diff --git a/src/com/jpexs/decompiler/flash/gui/SearchResultsStorage.java b/src/com/jpexs/decompiler/flash/gui/SearchResultsStorage.java index e13311733..4d39cbf7f 100644 --- a/src/com/jpexs/decompiler/flash/gui/SearchResultsStorage.java +++ b/src/com/jpexs/decompiler/flash/gui/SearchResultsStorage.java @@ -78,7 +78,7 @@ public class SearchResultsStorage { String binaryDataSuffix = ""; while ((s instanceof SWF) && ((SWF) s).binaryData != null) { - binaryDataSuffix += "|binaryData[" + ((SWF) s).binaryData.getCharacterId() + "]"; + binaryDataSuffix += "|" + ((SWF) s).binaryData.getStoragesPathIdentifier(); s = ((SWF) s).binaryData.getSwf(); } diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 3fd0cb63a..09466a689 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1239,4 +1239,10 @@ debug.import.bytearray = Import byte array data... action.edit.flex = (Flex compiler) header.encrypted = Harman encrypted: -header.warning.unsupportedGfxEncryption = GFX does not support Harman encryption. \ No newline at end of file +header.warning.unsupportedGfxEncryption = GFX does not support Harman encryption. + +contextmenu.applyUnpacker = Apply unpacker + +binarydata.dataInside.packer = It looks like this binary data is packed with %packer%. Click here to unpack the binary data. + +error.wrong.packer = %item%\r\nCannot unpack binary data with %packer%.\r\nThe data is probably not packed with this packer. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index 252f248ea..d25123773 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1216,4 +1216,8 @@ debug.import.bytearray = Importovat byte array data... action.edit.flex = (kompil\u00e1tor Flex) header.encrypted = Harman \u0161ifrov\u00e1n\u00ed: -header.warning.unsupportedGfxEncryption = GFX nepodporuje Harman \u0161ifrov\u00e1n\u00ed. \ No newline at end of file +header.warning.unsupportedGfxEncryption = GFX nepodporuje Harman \u0161ifrov\u00e1n\u00ed. + +contextmenu.applyUnpacker = Pou\u017e\u00edt unpacker +binarydata.dataInside.packer = Vypad\u00e1 to, \u017ee tato bin\u00e1rn\u00ed data jsou zabalena pomoc\u00ed %packer%. Klikn\u011bte zde pro jejich rozbalen\u00ed. +error.wrong.packer = %item%\r\nNelze rozbalit bin\u00e1rn\u00ed data pomoc\u00ed %packer%.\r\nTato data pravd\u011bpodobn\u011b nebyla zabalena t\u00edmto packerem. diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java index 37879cbd6..0a4091cf1 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java @@ -72,6 +72,7 @@ import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.TagStub; import com.jpexs.decompiler.flash.tags.VideoFrameTag; import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.ImageTag; @@ -292,7 +293,7 @@ public abstract class AbstractTagTree extends JTree { return TreeNodeType.SOUND; } - if (t instanceof DefineBinaryDataTag) { + if (t instanceof BinaryDataInterface) { return TreeNodeType.BINARY_DATA; } @@ -594,7 +595,7 @@ public abstract class AbstractTagTree extends JTree { } } - if (d instanceof Tag || d instanceof ASMSource) { + if (d instanceof Tag || d instanceof ASMSource || d instanceof BinaryDataInterface) { TreeNodeType nodeType = TagTree.getTreeNodeType(d); if (nodeType == TreeNodeType.IMAGE) { ret.add(d); diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 7234ce252..06f0b0232 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -47,6 +47,7 @@ import com.jpexs.decompiler.flash.gui.abc.AddClassDialog; import com.jpexs.decompiler.flash.gui.abc.ClassesListTreeModel; import com.jpexs.decompiler.flash.gui.action.AddScriptDialog; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.packers.Packer; import com.jpexs.decompiler.flash.tags.ABCContainerTag; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineBitsLossless2Tag; @@ -73,13 +74,14 @@ import com.jpexs.decompiler.flash.tags.ShowFrameTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.TagTypeInfo; import com.jpexs.decompiler.flash.tags.UnknownTag; -import com.jpexs.decompiler.flash.tags.VideoFrameTag; import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.ImageTag; import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.PackedBinaryData; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RemoveTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; @@ -240,8 +242,10 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem pasteInsideMenuItem; + private JMenu applyUnpackerMenu; + private JMenuItem openSWFInsideTagMenuItem; - + private JMenuItem addAs12ScriptMenuItem; private JMenuItem addAs12FrameScriptMenuItem; @@ -598,6 +602,22 @@ public class TagTreeContextMenu extends JPopupMenu { pasteInsideMenuItem.addActionListener(this::pasteInsideActionPerformed); add(pasteInsideMenuItem); + applyUnpackerMenu = new JMenu(mainPanel.translate("contextmenu.applyUnpacker")); + applyUnpackerMenu.setIcon(View.getIcon("openinside16")); + + for (Packer packer : DefineBinaryDataTag.getAvailablePackers()) { + JMenuItem packerMenuItem = new JMenuItem(packer.getName()); + packerMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + applyUnpackerActionPerformed(packer); + } + }); + applyUnpackerMenu.add(packerMenuItem); + } + + add(applyUnpackerMenu); + openSWFInsideTagMenuItem = new JMenuItem(mainPanel.translate("contextmenu.openswfinside")); openSWFInsideTagMenuItem.setIcon(View.getIcon("openinside16")); openSWFInsideTagMenuItem.addActionListener(this::openSwfInsideActionPerformed); @@ -953,7 +973,7 @@ public class TagTreeContextMenu extends JPopupMenu { boolean allSelectedIsBinaryData = true; for (TreeItem item : items) { - if (!(item instanceof DefineBinaryDataTag)) { + if (!(item instanceof BinaryDataInterface)) { allSelectedIsBinaryData = false; break; } @@ -1069,6 +1089,7 @@ public class TagTreeContextMenu extends JPopupMenu { pasteAfterMenuItem.setVisible(false); pasteBeforeMenuItem.setVisible(false); pasteInsideMenuItem.setVisible(false); + applyUnpackerMenu.setVisible(false); openSWFInsideTagMenuItem.setVisible(false); addAs12ScriptMenuItem.setVisible(false); addAs12FrameScriptMenuItem.setVisible(false); @@ -1127,7 +1148,7 @@ public class TagTreeContextMenu extends JPopupMenu { replaceNoFillMenuItem.setVisible(true); } - if (canReplace.test(it -> it instanceof DefineBinaryDataTag)) { + if (canReplace.test(it -> it instanceof BinaryDataInterface)) { replaceMenuItem.setVisible(true); } @@ -1308,7 +1329,8 @@ public class TagTreeContextMenu extends JPopupMenu { if (mainPanel.getCurrentView() == MainPanel.VIEW_RESOURCES && !isFolder - && !(firstItem instanceof AS3Package)) { + && !(firstItem instanceof AS3Package) + && !(firstItem instanceof PackedBinaryData)) { showInTagListViewTagMenuItem.setVisible(true); } @@ -1471,17 +1493,19 @@ public class TagTreeContextMenu extends JPopupMenu { } if (allSelectedIsBinaryData) { + applyUnpackerMenu.setVisible(true); + boolean anyInnerSwf = false; for (TreeItem item : items) { - DefineBinaryDataTag binary = (DefineBinaryDataTag) item; + BinaryDataInterface binary = (BinaryDataInterface) item; // inner swf is not loaded yet - if (binary.innerSwf == null && binary.isSwfData()) { + if (binary.getInnerSwf() == null && binary.isSwfData()) { anyInnerSwf = true; } } - openSWFInsideTagMenuItem.setVisible(anyInnerSwf); + openSWFInsideTagMenuItem.setVisible(anyInnerSwf); } for (TreeItem item : items) { @@ -1521,7 +1545,7 @@ public class TagTreeContextMenu extends JPopupMenu { moveTagToWithDependenciesMenu.setVisible(false); cutTagToClipboardMenuItem.setVisible(false); cutTagToClipboardWithDependenciesMenuItem.setVisible(false); - openSWFInsideTagMenuItem.setVisible(false); + //openSWFInsideTagMenuItem.setVisible(false); } } } @@ -2009,17 +2033,28 @@ public class TagTreeContextMenu extends JPopupMenu { copyOrMoveTags(getDependenciesSet(items), true, timelined, position); } + private void applyUnpackerActionPerformed(Packer packer) { + List sel = getSelectedItems(); + for (TreeItem item : sel) { + BinaryDataInterface binaryData = (BinaryDataInterface) item; + if (!binaryData.unpack(packer)) { + ViewMessages.showMessageDialog(mainPanel, AppStrings.translate("error.wrong.packer").replace("%item%", item.toString()).replace("%packer%", packer.getName()), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + } + mainPanel.refreshTree(); + } + private void openSwfInsideActionPerformed(ActionEvent evt) { List sel = getSelectedItems(); - List binaryDatas = new ArrayList<>(); + List binaryDatas = new ArrayList<>(); for (TreeItem item : sel) { - DefineBinaryDataTag binaryData = (DefineBinaryDataTag) item; + BinaryDataInterface binaryData = (BinaryDataInterface) item; if (binaryData.isSwfData()) { - binaryDatas.add((DefineBinaryDataTag) item); + binaryDatas.add((BinaryDataInterface) item); } } - mainPanel.loadFromBinaryTag(binaryDatas); + mainPanel.loadFromBinaryTag(binaryDatas); } private void replaceWithTagActionPerformed(ActionEvent evt) { @@ -3201,7 +3236,7 @@ public class TagTreeContextMenu extends JPopupMenu { SWF swf = (SWF) item; if (swf.binaryData != null) { // embedded swf - swf.binaryData.innerSwf = null; + swf.binaryData.setInnerSwf(null); swf.clearTagSwfs(); } else { Main.closeFile(swf.openableList); @@ -3521,13 +3556,9 @@ public class TagTreeContextMenu extends JPopupMenu { swfItem.setIcon(View.getIcon("flash16")); menu.add(swfItem); - for (Tag t : targetSwf.getTags()) { - if (t instanceof DefineBinaryDataTag) { - DefineBinaryDataTag binaryData = (DefineBinaryDataTag) t; - if (binaryData.innerSwf != null) { - addCopyMoveToMenus(kind, menu, items, name + " / " + t.getTagName() + " (" + ((DefineBinaryDataTag) t).getCharacterId() + ")", binaryData.innerSwf); - } - } + Map binaryMap = mainPanel.getSwfsMap(targetSwf); + for (String key : binaryMap.keySet()) { + addCopyMoveToMenus(kind, menu, items, key, binaryMap.get(key)); } } @@ -3545,16 +3576,14 @@ public class TagTreeContextMenu extends JPopupMenu { }); swfItem.setIcon(View.getIcon("flash16")); menu.add(swfItem); - - for (Tag t : targetSwf.getTags()) { - if (t instanceof DefineBinaryDataTag) { - DefineBinaryDataTag binaryData = (DefineBinaryDataTag) t; - if (binaryData.innerSwf != null) { - addCopyMoveToMenus(kind, menu, items, name + " / " + t.getTagName() + " (" + ((DefineBinaryDataTag) t).getCharacterId() + ")", binaryData.innerSwf); - } - } - } + + Map binaryMap = mainPanel.getSwfsMap(targetSwf); + for (String key : binaryMap.keySet()) { + addCopyMoveToFramesMenus(kind, menu, items, key, binaryMap.get(key)); + } } + + private void attachTagActionPerformed(ActionEvent evt, TreeItem item, Class cl, TreeNodeType createNodeType) { int id = -1; diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java index 011d2d993..6ad49942f 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java @@ -24,7 +24,6 @@ import com.jpexs.decompiler.flash.gui.TreeNodeType; import com.jpexs.decompiler.flash.gui.abc.ClassesListTreeModel; import com.jpexs.decompiler.flash.gui.helpers.CollectionChangedAction; import com.jpexs.decompiler.flash.gui.helpers.CollectionChangedEvent; -import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DoInitActionTag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; @@ -32,6 +31,7 @@ import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.ASMSource; import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; +import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface; import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; @@ -527,10 +527,13 @@ public class TagTreeModel extends AbstractTagTreeModel { } else if (parentNode instanceof DefineSpriteTag) { result.addAll(((DefineSpriteTag) parentNode).getTimeline().getFrames()); return result; - } else if (parentNode instanceof DefineBinaryDataTag) { - DefineBinaryDataTag binaryDataTag = (DefineBinaryDataTag) parentNode; - if (binaryDataTag.innerSwf != null) { - result.add(((DefineBinaryDataTag) parentNode).innerSwf); + } else if (parentNode instanceof BinaryDataInterface) { + BinaryDataInterface binaryData = (BinaryDataInterface) parentNode; + if (binaryData.getInnerSwf() != null) { + result.add(binaryData.getInnerSwf()); + return result; + } else if (binaryData.getSub() != null) { + result.add(binaryData.getSub()); return result; } else { return new ArrayList<>(0); @@ -613,8 +616,12 @@ public class TagTreeModel extends AbstractTagTreeModel { return ((Frame) parentNode).innerTags.get(index); } else if (parentNode instanceof DefineSpriteTag) { return ((DefineSpriteTag) parentNode).getTimeline().getFrame(index); - } else if (parentNode instanceof DefineBinaryDataTag) { - return ((DefineBinaryDataTag) parentNode).innerSwf; + } else if (parentNode instanceof BinaryDataInterface) { + BinaryDataInterface binaryData = (BinaryDataInterface) parentNode; + if (binaryData.getInnerSwf() != null) { + return binaryData.getInnerSwf(); + } + return binaryData.getSub(); } else if (parentNode instanceof AS2Package) { return ((AS2Package) parentNode).getChild(index); } else if (parentNode instanceof FrameScript) { @@ -681,8 +688,12 @@ public class TagTreeModel extends AbstractTagTreeModel { return mappedSize + ((Frame) parentNode).innerTags.size(); } else if (parentNode instanceof DefineSpriteTag) { return mappedSize + ((DefineSpriteTag) parentNode).getTimeline().getFrameCount(); - } else if (parentNode instanceof DefineBinaryDataTag) { - return mappedSize + (((DefineBinaryDataTag) parentNode).innerSwf == null ? 0 : 1); + } else if (parentNode instanceof BinaryDataInterface) { + BinaryDataInterface binary = (BinaryDataInterface) parentNode; + if (binary.getInnerSwf() != null) { + return mappedSize + 1; + } + return mappedSize + (binary.getSub() == null ? 0 : 1); } else if (parentNode instanceof AS2Package) { return mappedSize + ((AS2Package) parentNode).getChildCount(); } else if (parentNode instanceof FrameScript) { @@ -748,7 +759,7 @@ public class TagTreeModel extends AbstractTagTreeModel { return indexOfAdd(baseIndex, ((Frame) parentNode).innerTags.indexOf(childNode)); } else if (parentNode instanceof DefineSpriteTag) { return indexOfAdd(baseIndex, ((Frame) childNode).frame); - } else if (parentNode instanceof DefineBinaryDataTag) { + } else if (parentNode instanceof BinaryDataInterface) { return indexOfAdd(baseIndex, 0); // binary data tag can have only 1 child } else if (parentNode instanceof AS2Package) { return indexOfAdd(baseIndex, ((AS2Package) parentNode).getIndexOfChild(childNode));