diff --git a/src/com/jpexs/decompiler/flash/SWF.java b/src/com/jpexs/decompiler/flash/SWF.java index a2294a891..b9a25fb69 100644 --- a/src/com/jpexs/decompiler/flash/SWF.java +++ b/src/com/jpexs/decompiler/flash/SWF.java @@ -1,2935 +1,2935 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash; - -import SevenZip.Compression.LZMA.Encoder; -import com.jpacker.JPacker; -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.ClassPath; -import com.jpexs.decompiler.flash.abc.RenameType; -import com.jpexs.decompiler.flash.abc.ScriptPack; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.ActionDeobfuscation; -import com.jpexs.decompiler.flash.action.ActionGraphSource; -import com.jpexs.decompiler.flash.action.ActionLocalData; -import com.jpexs.decompiler.flash.action.model.ConstantPool; -import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; -import com.jpexs.decompiler.flash.action.model.FunctionActionItem; -import com.jpexs.decompiler.flash.action.model.GetMemberActionItem; -import com.jpexs.decompiler.flash.action.model.GetVariableActionItem; -import com.jpexs.decompiler.flash.action.model.clauses.ClassActionItem; -import com.jpexs.decompiler.flash.action.model.clauses.InterfaceActionItem; -import com.jpexs.decompiler.flash.action.swf4.ActionEquals; -import com.jpexs.decompiler.flash.action.swf4.ActionGetVariable; -import com.jpexs.decompiler.flash.action.swf4.ActionIf; -import com.jpexs.decompiler.flash.action.swf4.ActionPush; -import com.jpexs.decompiler.flash.action.swf4.ActionSetVariable; -import com.jpexs.decompiler.flash.action.swf4.ConstantIndex; -import com.jpexs.decompiler.flash.action.swf5.ActionCallFunction; -import com.jpexs.decompiler.flash.action.swf5.ActionCallMethod; -import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool; -import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; -import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal; -import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal2; -import com.jpexs.decompiler.flash.action.swf5.ActionEquals2; -import com.jpexs.decompiler.flash.action.swf5.ActionGetMember; -import com.jpexs.decompiler.flash.action.swf5.ActionNewMethod; -import com.jpexs.decompiler.flash.action.swf5.ActionNewObject; -import com.jpexs.decompiler.flash.action.swf5.ActionSetMember; -import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.dumpview.DumpInfoSwfNode; -import com.jpexs.decompiler.flash.ecma.Null; -import com.jpexs.decompiler.flash.exporters.BinaryDataExporter; -import com.jpexs.decompiler.flash.exporters.FontExporter; -import com.jpexs.decompiler.flash.exporters.ImageExporter; -import com.jpexs.decompiler.flash.exporters.MorphShapeExporter; -import com.jpexs.decompiler.flash.exporters.MovieExporter; -import com.jpexs.decompiler.flash.exporters.ShapeExporter; -import com.jpexs.decompiler.flash.exporters.SoundExporter; -import com.jpexs.decompiler.flash.exporters.TextExporter; -import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; -import com.jpexs.decompiler.flash.exporters.modes.FramesExportMode; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.exporters.settings.BinaryDataExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.FontExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.FramesExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.MorphShapeExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.MovieExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.SoundExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.TextExportSettings; -import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; -import com.jpexs.decompiler.flash.helpers.collections.MyEntry; -import com.jpexs.decompiler.flash.tags.ABCContainerTag; -import com.jpexs.decompiler.flash.tags.DefineButton2Tag; -import com.jpexs.decompiler.flash.tags.DefineButtonTag; -import com.jpexs.decompiler.flash.tags.DefineSpriteTag; -import com.jpexs.decompiler.flash.tags.DoInitActionTag; -import com.jpexs.decompiler.flash.tags.EndTag; -import com.jpexs.decompiler.flash.tags.ExportAssetsTag; -import com.jpexs.decompiler.flash.tags.FileAttributesTag; -import com.jpexs.decompiler.flash.tags.JPEGTablesTag; -import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; -import com.jpexs.decompiler.flash.tags.ShowFrameTag; -import com.jpexs.decompiler.flash.tags.SymbolClassTag; -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.BoundedTag; -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.Container; -import com.jpexs.decompiler.flash.tags.base.ContainerItem; -import com.jpexs.decompiler.flash.tags.base.DrawableTag; -import com.jpexs.decompiler.flash.tags.base.FontTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; -import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; -import com.jpexs.decompiler.flash.tags.base.RemoveTag; -import com.jpexs.decompiler.flash.tags.base.ShapeTag; -import com.jpexs.decompiler.flash.tags.base.TextTag; -import com.jpexs.decompiler.flash.timeline.Clip; -import com.jpexs.decompiler.flash.timeline.DepthState; -import com.jpexs.decompiler.flash.timeline.Frame; -import com.jpexs.decompiler.flash.timeline.SvgClip; -import com.jpexs.decompiler.flash.timeline.Timeline; -import com.jpexs.decompiler.flash.timeline.Timelined; -import com.jpexs.decompiler.flash.treeitems.AS2PackageNodeItem; -import com.jpexs.decompiler.flash.treeitems.AS3PackageNodeItem; -import com.jpexs.decompiler.flash.treeitems.FrameNodeItem; -import com.jpexs.decompiler.flash.treeitems.SWFList; -import com.jpexs.decompiler.flash.treeitems.TreeItem; -import com.jpexs.decompiler.flash.treenodes.AS2PackageNode; -import com.jpexs.decompiler.flash.treenodes.ContainerNode; -import com.jpexs.decompiler.flash.treenodes.FrameNode; -import com.jpexs.decompiler.flash.treenodes.TagNode; -import com.jpexs.decompiler.flash.treenodes.TreeNode; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.RGB; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.filters.BEVELFILTER; -import com.jpexs.decompiler.flash.types.filters.BlendComposite; -import com.jpexs.decompiler.flash.types.filters.COLORMATRIXFILTER; -import com.jpexs.decompiler.flash.types.filters.CONVOLUTIONFILTER; -import com.jpexs.decompiler.flash.types.filters.DROPSHADOWFILTER; -import com.jpexs.decompiler.flash.types.filters.FILTER; -import com.jpexs.decompiler.flash.types.filters.GLOWFILTER; -import com.jpexs.decompiler.flash.types.filters.GRADIENTBEVELFILTER; -import com.jpexs.decompiler.flash.types.filters.GRADIENTGLOWFILTER; -import com.jpexs.decompiler.flash.xfl.FLAVersion; -import com.jpexs.decompiler.flash.xfl.XFLConverter; -import com.jpexs.decompiler.graph.Graph; -import com.jpexs.decompiler.graph.GraphSourceItem; -import com.jpexs.decompiler.graph.GraphSourceItemContainer; -import com.jpexs.decompiler.graph.GraphTargetItem; -import com.jpexs.decompiler.graph.model.LocalData; -import com.jpexs.helpers.Cache; -import com.jpexs.helpers.CancellableWorker; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.NulStream; -import com.jpexs.helpers.ProgressListener; -import com.jpexs.helpers.SerializableImage; -import com.jpexs.helpers.utf8.Utf8Helper; -import gnu.jpdf.PDFJob; -import java.awt.AlphaComposite; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.awt.print.PageFormat; -import java.awt.print.Paper; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EmptyStackException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.Stack; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.InflaterInputStream; -import javax.imageio.ImageIO; -import javax.imageio.stream.FileImageOutputStream; -import javax.imageio.stream.ImageOutputStream; -import javax.xml.bind.DatatypeConverter; -import net.kroo.elliot.GifSequenceWriter; -import org.monte.media.VideoFormatKeys; -import org.monte.media.avi.AVIWriter; - -/** - * Class representing SWF file - * - * @author JPEXS - */ -public final class SWF implements TreeItem, Timelined { - - /** - * Default version of SWF file format - */ - public static final int DEFAULT_VERSION = 10; - /** - * Tags inside of file - */ - public List tags = new ArrayList<>(); - public boolean hasEndTag; - /** - * ExportRectangle for the display - */ - public RECT displayRect; - /** - * Movie frame rate - */ - public int frameRate; - /** - * Number of frames in movie - */ - public int frameCount; - /** - * Version of SWF - */ - public int version; - /** - * Uncompressed size of the file - */ - public long fileSize; - /** - * Used compression mode - */ - public SWFCompression compression = SWFCompression.NONE; - /** - * Compressed size of the file (LZMA) - */ - public long compressedSize; - /** - * LZMA Properties - */ - public byte[] lzmaProperties; - public byte[] uncompressedData; - public FileAttributesTag fileAttributes; - /** - * ScaleForm GFx - */ - public boolean gfx = false; - - public SWFList swfList; - public String file; - public String fileTitle; - public boolean readOnly; - public boolean isAS3; - public Map characters = new HashMap<>(); - public List abcList; - public JPEGTablesTag jtt; - public Map sourceFontsMap = new HashMap<>(); - public static final double unitDivisor = 20; - - private Timeline timeline; - - public DumpInfoSwfNode dumpInfo; - - public void updateCharacters() { - characters.clear(); - parseCharacters(new ArrayList(tags)); - } - - public void resetTimelines(Timelined timelined) { - timelined.resetTimeline(); - List tags = timelined.getTimeline().tags; - for (int i = 0; i < tags.size(); i++) { - Tag t = tags.get(i); - if (t instanceof Timelined) { - resetTimelines((Timelined) t); - } - } - } - - private void parseCharacters(List list) { - for (ContainerItem t : list) { - if (t instanceof CharacterTag) { - characters.put(((CharacterTag) t).getCharacterId(), (CharacterTag) t); - } - if (t instanceof Container) { - parseCharacters(((Container) t).getSubItems()); - } - } - } - - /** - * Unresolve recursive sprites - */ - private void checkInvalidSprites() { - for (int i = 0; i < tags.size(); i++) { - Tag t = tags.get(i); - if (t instanceof DefineSpriteTag) { - if (!isSpriteValid((DefineSpriteTag) t, new ArrayList())) { - tags.set(i, new TagStub(this, t.getId(), "InvalidSprite", t.getOriginalRange(), null)); - } - } - } - } - - private boolean isSpriteValid(DefineSpriteTag sprite, List path) { - if (path.contains(sprite.spriteId)) { - return false; - } - path.add(sprite.spriteId); - for (Tag t : sprite.subTags) { - if (t instanceof DefineSpriteTag) { - if (!isSpriteValid((DefineSpriteTag) t, path)) { - return false; - } - } - } - path.remove((Integer) sprite.spriteId); - return true; - } - - @Override - public Timeline getTimeline() { - if (timeline == null) { - timeline = new Timeline(this); - } - return timeline; - } - - @Override - public void resetTimeline() { - timeline = null; - } - - /** - * Gets all tags with specified id - * - * @param tagId Identificator of tag type - * @return List of tags - */ - public List getTagData(int tagId) { - List ret = new ArrayList<>(); - for (Tag tag : tags) { - if (tag.getId() == tagId) { - ret.add(tag); - } - } - return ret; - } - - /** - * Saves this SWF into new file - * - * @param os OutputStream to save SWF in - * @throws IOException - */ - public void saveTo(OutputStream os) throws IOException { - saveTo(os, compression); - } - - /** - * 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 { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(baos, version); - sos.writeRECT(displayRect); - sos.writeUI8(0); - sos.writeUI8(frameRate); - sos.writeUI16(frameCount); - - sos.writeTags(tags); - if (hasEndTag) { - sos.writeUI16(0); - } - sos.close(); - if (compression == SWFCompression.LZMA) { - os.write('Z'); - } else if (compression == SWFCompression.ZLIB) { - os.write('C'); - } else { - if (gfx) { - os.write('G'); - } else { - os.write('F'); - } - } - if (gfx) { - os.write('F'); - os.write('X'); - } else { - os.write('W'); - os.write('S'); - } - os.write(version); - byte[] data = baos.toByteArray(); - sos = new SWFOutputStream(os, version); - sos.writeUI32(data.length + 8); - - if (compression == SWFCompression.LZMA) { - Encoder enc = new Encoder(); - int val = lzmaProperties[0] & 0xFF; - int lc = val % 9; - int remainder = val / 9; - int lp = remainder % 5; - int pb = remainder / 5; - int dictionarySize = 0; - for (int i = 0; i < 4; i++) { - dictionarySize += ((int) (lzmaProperties[1 + i]) & 0xFF) << (i * 8); - } - enc.SetDictionarySize(dictionarySize); - enc.SetLcLpPb(lc, lp, pb); - baos = new ByteArrayOutputStream(); - enc.SetEndMarkerMode(true); - enc.Code(new ByteArrayInputStream(data), baos, -1, -1, null); - data = baos.toByteArray(); - byte[] udata = new byte[4]; - udata[0] = (byte) (data.length & 0xFF); - udata[1] = (byte) ((data.length >> 8) & 0xFF); - udata[2] = (byte) ((data.length >> 16) & 0xFF); - udata[3] = (byte) ((data.length >> 24) & 0xFF); - os.write(udata); - os.write(lzmaProperties); - } else if (compression == SWFCompression.ZLIB) { - os = new DeflaterOutputStream(os); - } - os.write(data); - } finally { - if (os != null) { - os.close(); - } - } - } - - public void clearModified() { - for (Tag tag : tags) { - if (tag.isModified()) { - tag.createOriginalData(); - tag.setModified(false); - } - } - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - saveTo(baos, SWFCompression.NONE); - byte[] swfData = baos.toByteArray(); - uncompressedData = swfData; - } catch (IOException ex) { - Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "Cannot save SWF", ex); - } - } - - public SWF(InputStream is, boolean parallelRead) throws IOException, InterruptedException { - this(is, null, parallelRead); - } - - /** - * Construct SWF from stream - * - * @param is Stream to read SWF from - * @param listener - * @param parallelRead Use parallel threads? - * @throws IOException - * @throws java.lang.InterruptedException - */ - public SWF(InputStream is, ProgressListener listener, boolean parallelRead) throws IOException, InterruptedException { - this(is, listener, parallelRead, false); - } - - /** - * Faster constructor to check SWF only - * - * @param is - * @throws java.io.IOException - */ - public SWF(InputStream is) throws IOException { - decompress(is, new NulStream(), true); - } - - /** - * Construct SWF from stream - * - * @param is Stream to read SWF from - * @param listener - * @param parallelRead Use parallel threads? - * @param checkOnly Check only file validity - * @throws IOException - * @throws java.lang.InterruptedException - */ - public SWF(InputStream is, ProgressListener listener, boolean parallelRead, boolean checkOnly) throws IOException, InterruptedException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFHeader header = decompress(is, baos, true); - gfx = header.gfx; - compression = header.compression; - uncompressedData = baos.toByteArray(); - - SWFInputStream sis = new SWFInputStream(this, uncompressedData); - dumpInfo = new DumpInfoSwfNode(this, "rootswf", "", null, 0, 0); - if (Configuration.dumpInfoCollecting.get()) { - sis.dumpInfo = dumpInfo; - } - sis.readBytesEx(3, "signature"); // skip siganture - version = sis.readUI8("version"); - fileSize = sis.readUI32("fileSize"); - dumpInfo.lengthBytes = fileSize; - if (listener != null) { - sis.addPercentListener(listener); - } - sis.setPercentMax(fileSize); - displayRect = sis.readRECT("displayRect"); - // FIXED8 (16 bit fixed point) frameRate - sis.readUI8("tmpFirstByetOfFrameRate"); //tmpFirstByetOfFrameRate - frameRate = sis.readUI8("frameRate"); - frameCount = sis.readUI16("frameCount"); - List tags = sis.readTagList(this, 0, parallelRead, true, !checkOnly, gfx); - if (tags.get(tags.size() - 1).getId() == EndTag.ID) { - hasEndTag = true; - tags.remove(tags.size() - 1); - } - this.tags = tags; - if (!checkOnly) { - checkInvalidSprites(); - updateCharacters(); - assignExportNamesToSymbols(); - assignClassesToSymbols(); - findFileAttributes(); - findABCTags(); - } else { - boolean hasNonUnknownTag = false; - for (Tag tag : tags) { - if (tag.getOriginalDataLength() > 0 && Tag.getRequiredTags().contains(tag.getId())) { - hasNonUnknownTag = true; - } - } - if (!hasNonUnknownTag) { - throw new IOException("Invalid SWF file. No known tag found."); - } - } - } - - @Override - public SWF getSwf() { - return this; - } - - /** - * Get title of the file - * - * @return file title - */ - public String getFileTitle() { - if (fileTitle != null) { - return fileTitle; - } - return file; - } - - public String getShortFileName() { - String title = getFileTitle(); - if (title == null) { - return ""; - } - return new File(title).getName(); - } - - private void findABCTags() { - List objs = new ArrayList<>(); - objs.addAll(tags); - - ArrayList newAbcList = new ArrayList<>(); - getABCTags(objs, newAbcList); - this.abcList = newAbcList; - } - - private static void getABCTags(List list, List actionScripts) { - for (ContainerItem t : list) { - if (t instanceof Container) { - getABCTags(((Container) t).getSubItems(), actionScripts); - } - if (t instanceof ABCContainerTag) { - actionScripts.add((ABCContainerTag) t); - } - } - } - - private void findFileAttributes() { - for (Tag t : tags) { - if (t instanceof FileAttributesTag) { - fileAttributes = (FileAttributesTag) t; - break; - } - } - } - - private void assignExportNamesToSymbols() { - HashMap exportNames = new HashMap<>(); - for (Tag t : tags) { - if (t instanceof ExportAssetsTag) { - ExportAssetsTag eat = (ExportAssetsTag) t; - for (int i = 0; i < eat.tags.size(); i++) { - if ((!exportNames.containsKey(eat.tags.get(i))) && (!exportNames.containsValue(eat.names.get(i)))) { - exportNames.put(eat.tags.get(i), eat.names.get(i)); - } - } - } - } - for (Tag t : tags) { - if (t instanceof CharacterIdTag) { - CharacterIdTag ct = (CharacterIdTag) t; - if (exportNames.containsKey(ct.getCharacterId())) { - ct.setExportName(exportNames.get(ct.getCharacterId())); - } - } - } - } - - public void assignClassesToSymbols() { - HashMap classes = new HashMap<>(); - for (Tag t : tags) { - if (t instanceof SymbolClassTag) { - SymbolClassTag sct = (SymbolClassTag) t; - for (int i = 0; i < sct.tags.length; i++) { - if ((!classes.containsKey(sct.tags[i])) && (!classes.containsValue(sct.names[i]))) { - classes.put(sct.tags[i], sct.names[i]); - } - } - } - } - for (Tag t : tags) { - if (t instanceof CharacterIdTag) { - CharacterIdTag ct = (CharacterIdTag) t; - if (classes.containsKey(ct.getCharacterId())) { - ct.setClassName(classes.get(ct.getCharacterId())); - } - } - } - } - - /** - * Compress SWF file - * - * @param fis Input stream - * @param fos Output stream - * @return True on success - */ - public static boolean fws2cws(InputStream fis, OutputStream fos) { - 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(); - } catch (IOException ex) { - return false; - } - return true; - } - - public static boolean decompress(InputStream fis, OutputStream fos) { - try { - decompress(fis, fos, false); - return true; - } catch (IOException ex) { - return false; - } - } - - private static SWFHeader decompress(InputStream is, OutputStream os, boolean allowUncompressed) throws IOException { - byte[] hdr = new byte[8]; - - // SWFheader: signature, version and fileSize - if (is.read(hdr) != 8) { - throw new IOException("SWF header is too short"); - } - - String signature = new String(hdr, 0, 3, Utf8Helper.charset); - if (!Arrays.asList( - "FWS", //Uncompressed Flash - "CWS", //ZLib compressed Flash - "ZWS", //LZMA compressed Flash - "GFX", //Uncompressed ScaleForm GFx - "CFX" //Compressed ScaleForm GFx - ).contains(signature)) { - throw new IOException("Invalid SWF file"); - } - - int version = hdr[3]; - SWFInputStream sis = new SWFInputStream(null, Arrays.copyOfRange(hdr, 4, 8), 4, 4); - long fileSize = sis.readUI32("fileSize"); - SWFHeader header = new SWFHeader(); - header.version = version; - header.fileSize = fileSize; - - if (hdr[1] == 'F' && hdr[2] == 'X') { - header.gfx = true; - } - - try (SWFOutputStream sos = new SWFOutputStream(os, version)) { - sos.write(Utf8Helper.getBytes("FWS")); - sos.writeUI8(version); - sos.writeUI32(fileSize); - - switch (hdr[0]) { - case 'C': { // CWS, CFX - Helper.copyStream(new InflaterInputStream(is), os, fileSize - 8); - header.compression = SWFCompression.ZLIB; - break; - } - case 'Z': { // ZWS - 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"); - } - SevenZip.Compression.LZMA.Decoder decoder = new SevenZip.Compression.LZMA.Decoder(); - if (!decoder.SetDecoderProperties(lzmaProperties)) { - throw new IOException("LZMA:Incorrect stream properties"); - } - if (!decoder.Code(is, os, fileSize - 8)) { - throw new IOException("LZMA:Error in data stream"); - } - - header.compression = SWFCompression.LZMA; - break; - } - default: { // FWS, GFX - if (allowUncompressed) { - Helper.copyStream(is, os, fileSize - 8); - } else { - throw new IOException("SWF is not compressed"); - } - } - } - - return header; - } - } - - public static boolean renameInvalidIdentifiers(RenameType renameType, InputStream fis, OutputStream fos) { - try { - SWF swf = new SWF(fis, Configuration.parallelSpeedUp.get()); - int cnt = swf.deobfuscateIdentifiers(renameType); - swf.assignClassesToSymbols(); - System.out.println(cnt + " identifiers renamed."); - swf.saveTo(fos); - } catch (InterruptedException ex) { - return false; - } catch (IOException ex) { - return false; - } - return true; - } - - public boolean exportAS3Class(String className, String outdir, ScriptExportMode exportMode, boolean parallel) throws Exception { - List abcTags = new ArrayList<>(); - - for (Tag t : tags) { - if (t instanceof ABCContainerTag) { - ABCContainerTag cnt = (ABCContainerTag) t; - abcTags.add(cnt); - } - } - - boolean exported = false; - - for (int i = 0; i < abcTags.size(); i++) { - ABC abc = abcTags.get(i).getABC(); - List scrs = abc.findScriptPacksByPath(className); - for (ScriptPack scr : scrs) { - String cnt = ""; - if (abc.script_info.size() > 1) { - cnt = "script " + (i + 1) + "/" + abc.script_info.size() + " "; - } - String exStr = "Exporting " + "tag " + (i + 1) + "/" + abcTags.size() + " " + cnt + scr.getPath() + " ..."; - informListeners("exporting", exStr); - scr.export(outdir, abcTags, exportMode, parallel); - exStr = "Exported " + "tag " + (i + 1) + "/" + abcTags.size() + " " + cnt + scr.getPath() + " ..."; - informListeners("exported", exStr); - exported = true; - } - } - return exported; - } - - private List> uniqueAS3Packs(List> packs) { - List> ret = new ArrayList<>(); - for (MyEntry item : packs) { - for (MyEntry itemOld : ret) { - if (item.key.equals(itemOld.key)) { - Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "Duplicate pack path found (" + itemOld.key + ")!"); - break; - } - } - ret.add(item); - } - return ret; - } - - public List> getAS3Packs() { - List abcTags = new ArrayList<>(); - for (Tag t : tags) { - if (t instanceof ABCContainerTag) { - abcTags.add((ABCContainerTag) t); - } - } - List> packs = new ArrayList<>(); - for (int i = 0; i < abcTags.size(); i++) { - ABCContainerTag t = abcTags.get(i); - packs.addAll(t.getABC().getScriptPacks()); - } - return uniqueAS3Packs(packs); - } - - @Override - public RECT getRect() { - return displayRect; - } - - private class ExportPackTask implements Callable { - - ScriptPack pack; - String directory; - List abcList; - ScriptExportMode exportMode; - ClassPath path; - AtomicInteger index; - int count; - boolean parallel; - AbortRetryIgnoreHandler handler; - long startTime; - long stopTime; - - public ExportPackTask(AbortRetryIgnoreHandler handler, AtomicInteger index, int count, ClassPath path, ScriptPack pack, String directory, List abcList, ScriptExportMode exportMode, boolean parallel) { - this.pack = pack; - this.directory = directory; - this.abcList = abcList; - this.exportMode = exportMode; - this.path = path; - this.index = index; - this.count = count; - this.parallel = parallel; - this.handler = handler; - } - - @Override - public File call() throws Exception { - RunnableIOExResult rio = new RunnableIOExResult() { - @Override - public void run() throws IOException { - startTime = System.currentTimeMillis(); - this.result = pack.export(directory, abcList, exportMode, parallel); - stopTime = System.currentTimeMillis(); - } - }; - int currentIndex = index.getAndIncrement(); - synchronized (ABC.class) { - long time = stopTime - startTime; - informListeners("exporting", "Exporting script " + currentIndex + "/" + count + " " + path); - } - new RetryTask(rio, handler).run(); - synchronized (ABC.class) { - long time = stopTime - startTime; - informListeners("exported", "Exported script " + currentIndex + "/" + count + " " + path + ", " + Helper.formatTimeSec(time)); - } - return rio.result; - } - } - - public List exportActionScript2(AbortRetryIgnoreHandler handler, String outdir, ScriptExportMode exportMode, boolean parallel, EventListener evl) throws IOException { - List ret = new ArrayList<>(); - List list2 = new ArrayList<>(); - list2.addAll(tags); - List list = createASTagList(list2, null); - - TagNode.setExport(list, true); - if (!outdir.endsWith(File.separator)) { - outdir += File.separator; - } - outdir += "scripts" + File.separator; - ret.addAll(TagNode.exportNodeAS(handler, list, outdir, exportMode, evl)); - return ret; - } - - public List exportActionScript3(final AbortRetryIgnoreHandler handler, final String outdir, final ScriptExportMode exportMode, final boolean parallel) { - final AtomicInteger cnt = new AtomicInteger(1); - final List abcTags = new ArrayList<>(); - for (Tag t : tags) { - if (t instanceof ABCContainerTag) { - abcTags.add((ABCContainerTag) t); - } - } - - final List ret = new ArrayList<>(); - final List> packs = getAS3Packs(); - - if (!parallel || packs.size() < 2) { - try { - CancellableWorker.call(new Callable() { - @Override - public Void call() throws Exception { - for (MyEntry item : packs) { - ExportPackTask task = new ExportPackTask(handler, cnt, packs.size(), item.key, item.value, outdir, abcTags, exportMode, parallel); - ret.add(task.call()); - } - return null; - } - }, Configuration.exportTimeout.get(), TimeUnit.SECONDS); - } catch (TimeoutException ex) { - Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached", ex); - } catch (Exception ex) { - Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, "Error during ABC export", ex); - } - } else { - ExecutorService executor = Executors.newFixedThreadPool(Configuration.parallelThreadCount.get()); - List> futureResults = new ArrayList<>(); - for (MyEntry item : packs) { - Future future = executor.submit(new ExportPackTask(handler, cnt, packs.size(), item.key, item.value, outdir, abcTags, exportMode, parallel)); - futureResults.add(future); - } - - try { - executor.shutdown(); - if (!executor.awaitTermination(Configuration.exportTimeout.get(), TimeUnit.SECONDS)) { - Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached"); - } - } catch (InterruptedException ex) { - } finally { - executor.shutdownNow(); - } - - for (int f = 0; f < futureResults.size(); f++) { - try { - if (futureResults.get(f).isDone()) { - ret.add(futureResults.get(f).get()); - } - } catch (InterruptedException ex) { - } catch (ExecutionException ex) { - Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "Error during ABC export", ex); - } - } - } - - return ret; - } - - public List exportActionScript(AbortRetryIgnoreHandler handler, String outdir, ScriptExportMode exportMode, boolean parallel) throws Exception { - boolean asV3Found = false; - List ret = new ArrayList<>(); - final EventListener evl = new EventListener() { - @Override - public void handleEvent(String event, Object data) { - if (event.equals("exporting") || event.equals("exported")) { - informListeners(event, data); - } - } - }; - for (Tag t : tags) { - if (t instanceof ABCContainerTag) { - asV3Found = true; - } - } - - if (asV3Found) { - ret.addAll(exportActionScript3(handler, outdir, exportMode, parallel)); - } else { - ret.addAll(exportActionScript2(handler, outdir, exportMode, parallel, evl)); - } - return ret; - } - - public static List createASTagList(List list, Timelined parent) { - List ret = new ArrayList<>(); - int frame = 1; - List frames = new ArrayList<>(); - - List exportAssetsTags = new ArrayList<>(); - for (ContainerItem t : list) { - if (t instanceof ExportAssetsTag) { - exportAssetsTags.add((ExportAssetsTag) t); - } - TreeNode addNode = null; - if (t instanceof ShowFrameTag) { - // do not add PlaceObjects (+etc) to script nodes - FrameNode tti = new FrameNode(new FrameNodeItem(t.getSwf(), frame, parent, false), null, true); - - for (int r = ret.size() - 1; r >= 0; r--) { - if (!(ret.get(r).getItem() instanceof DefineSpriteTag)) { - if (!(ret.get(r).getItem() instanceof DefineButtonTag)) { - if (!(ret.get(r).getItem() instanceof DefineButton2Tag)) { - if (!(ret.get(r).getItem() instanceof DoInitActionTag)) { - if (!(ret.get(r).getItem() instanceof AS2PackageNodeItem)) { - tti.subNodes.add(ret.get(r)); - ret.remove(r); - } - } - } - } - } - } - frame++; - frames.add(tti); - } else if (t instanceof ASMSource) { - ContainerNode tti = new ContainerNode(t); - //ret.add(tti); - addNode = tti; - } else if (t instanceof Container) { - if (((Container) t).getItemCount() > 0) { - - ContainerNode tti = new ContainerNode(t); - List subItems = ((Container) t).getSubItems(); - - Timelined timelined = t instanceof Timelined ? (Timelined) t : null; - tti.subNodes = createASTagList(subItems, timelined); - addNode = tti; - //ret.add(tti); - } - } - if (addNode != null) { - if (addNode.getItem() instanceof CharacterIdTag) { - CharacterIdTag cit = (CharacterIdTag) addNode.getItem(); - String path = cit.getExportName(); - if (path == null) { - path = ""; - } - String[] pathParts; - if (path.contains(".")) { - pathParts = path.split("\\."); - } else { - pathParts = new String[]{path}; - } - List items = ret; - int pos = 0; - TreeNode selNode = null; - do { - if (pos == pathParts.length - 1) { - break; - } - selNode = null; - for (TreeNode node : items) { - if (node.getItem() instanceof AS2PackageNodeItem) { - AS2PackageNodeItem pkg = (AS2PackageNodeItem) node.getItem(); - if (pkg.packageName.equals(pathParts[pos])) { - selNode = node; - break; - } - } - } - int pkgCount = 0; - for (; pkgCount < items.size(); pkgCount++) { - if (items.get(pkgCount).getItem() instanceof AS3PackageNodeItem) { - AS2PackageNodeItem pkg = (AS2PackageNodeItem) items.get(pkgCount).getItem(); - if (pkg.packageName.compareTo(pathParts[pos]) > 0) { - break; - } - } else { - break; - } - } - if (selNode == null) { - items.add(pkgCount, selNode = new AS2PackageNode(new AS2PackageNodeItem(pathParts[pos], t.getSwf()))); - } - pos++; - if (selNode != null) { - items = selNode.subNodes; - } - - } while (selNode != null); - - int clsCount = 0; - for (; clsCount < items.size(); clsCount++) { - if (items.get(clsCount).getItem() instanceof CharacterIdTag) { - CharacterIdTag ct = (CharacterIdTag) items.get(clsCount).getItem(); - String expName = ct.getExportName(); - if (expName == null) { - expName = ""; - } - if (expName.contains(".")) { - expName = expName.substring(expName.lastIndexOf('.') + 1); - } - if ((ct.getClass().getName() + "_" + expName).compareTo(addNode.getItem().getClass().getName() + "_" + pathParts[pos]) > 0) { - break; - } - } - } - items.add(clsCount, addNode); - } else { - ret.add(addNode); - } - } - - } - ret.addAll(frames); - for (int i = ret.size() - 1; i >= 0; i--) { - if (ret.get(i).getItem() instanceof DefineSpriteTag) { - ((DefineSpriteTag) ret.get(i).getItem()).exportAssetsTags = exportAssetsTags; - } - if (ret.get(i).getItem() instanceof DefineButtonTag) { - ((DefineButtonTag) ret.get(i).getItem()).exportAssetsTags = exportAssetsTags; - } - if (ret.get(i).getItem() instanceof DefineButton2Tag) { - ((DefineButton2Tag) ret.get(i).getItem()).exportAssetsTags = exportAssetsTags; - } - /*if (ret.get(i).tag instanceof DoInitActionTag) { - //((DoInitActionTag) ret.get(i).tag).exportAssetsTags = exportAssetsTags; - }*/ - if (ret.get(i).getItem() instanceof ASMSource) { - ASMSource ass = (ASMSource) ret.get(i).getItem(); - if (ass.containsSource()) { - continue; - } - } - if (ret.get(i).subNodes.isEmpty()) { - ret.remove(i); - } - } - return ret; - } - - public static void getTagsFromTreeNodes(List treeNodes, List result) { - for (TreeNode treeNode : treeNodes) { - TreeItem treeItem = treeNode.getItem(); - if (treeItem instanceof Tag) { - result.add((Tag) treeItem); - } - getTagsFromTreeNodes(treeNode.subNodes, result); - } - } - - private final HashSet listeners = new HashSet<>(); - - public final void addEventListener(EventListener listener) { - listeners.add(listener); - for (Tag t : tags) { - if (t instanceof ABCContainerTag) { - (((ABCContainerTag) t).getABC()).addEventListener(listener); - } - } - } - - public final void removeEventListener(EventListener listener) { - listeners.remove(listener); - for (Tag t : tags) { - if (t instanceof ABCContainerTag) { - (((ABCContainerTag) t).getABC()).removeEventListener(listener); - } - } - } - - protected void informListeners(String event, Object data) { - for (EventListener listener : listeners) { - listener.handleEvent(event, data); - } - } - - public static boolean hasErrorHeader(byte[] data) { - if (data.length > 4) { - if ((data[0] & 0xff) == 0xff) { - if ((data[1] & 0xff) == 0xd9) { - if ((data[2] & 0xff) == 0xff) { - if ((data[3] & 0xff) == 0xd8) { - return true; - } - } - } - } - } - return false; - } - - public static void populateVideoFrames(int streamId, List tags, HashMap output) { - for (ContainerItem 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 exportMovies(AbortRetryIgnoreHandler handler, String outdir, MovieExportSettings settings) throws IOException { - new MovieExporter().exportMovies(handler, outdir, tags, settings); - } - - public void exportSounds(AbortRetryIgnoreHandler handler, String outdir, SoundExportSettings settings) throws IOException { - new SoundExporter().exportSounds(handler, outdir, tags, settings); - } - - public void exportFonts(AbortRetryIgnoreHandler handler, String outdir, FontExportSettings settings) throws IOException { - new FontExporter().exportFonts(handler, outdir, tags, settings); - } - - private static void writeLE(OutputStream os, long val, int size) throws IOException { - for (int i = 0; i < size; i++) { - os.write((int) (val & 0xff)); - val >>= 8; - } - } - - public static void createWavFromPcmData(OutputStream fos, int soundRateHz, boolean soundSize, boolean soundType, byte[] data) throws IOException { - ByteArrayOutputStream subChunk1Data = new ByteArrayOutputStream(); - int audioFormat = 1; //PCM - writeLE(subChunk1Data, audioFormat, 2); - int numChannels = soundType ? 2 : 1; - writeLE(subChunk1Data, numChannels, 2); - int[] rateMap = {5512, 11025, 22050, 44100}; - int sampleRate = soundRateHz;//rateMap[soundRate]; - writeLE(subChunk1Data, sampleRate, 4); - int bitsPerSample = soundSize ? 16 : 8; - int byteRate = sampleRate * numChannels * bitsPerSample / 8; - writeLE(subChunk1Data, byteRate, 4); - int blockAlign = numChannels * bitsPerSample / 8; - writeLE(subChunk1Data, blockAlign, 2); - writeLE(subChunk1Data, bitsPerSample, 2); - - ByteArrayOutputStream chunks = new ByteArrayOutputStream(); - chunks.write(Utf8Helper.getBytes("fmt ")); - byte[] subChunk1DataBytes = subChunk1Data.toByteArray(); - writeLE(chunks, subChunk1DataBytes.length, 4); - chunks.write(subChunk1DataBytes); - - chunks.write(Utf8Helper.getBytes("data")); - writeLE(chunks, data.length, 4); - chunks.write(data); - - fos.write(Utf8Helper.getBytes("RIFF")); - byte[] chunkBytes = chunks.toByteArray(); - writeLE(fos, 4 + chunkBytes.length, 4); - fos.write(Utf8Helper.getBytes("WAVE")); - fos.write(chunkBytes); - } - - private static void makeAVI(Iterator images, int frameRate, File file) throws IOException { - if (!images.hasNext()) { - return; - } - AVIWriter out = new AVIWriter(file); - BufferedImage img0 = images.next(); - out.addVideoTrack(VideoFormatKeys.ENCODING_AVI_PNG, 1, frameRate, img0.getWidth(), img0.getHeight(), 0, 0); - try { - while (images.hasNext()) { - out.write(0, images.next(), 1); - } - } finally { - out.close(); - } - - } - - private static void makeGIF(Iterator images, int frameRate, File file) throws IOException { - if (!images.hasNext()) { - return; - } - try (ImageOutputStream output = new FileImageOutputStream(file)) { - BufferedImage img0 = images.next(); - GifSequenceWriter writer = new GifSequenceWriter(output, img0.getType(), 1000 / frameRate, true); - writer.writeToSequence(img0); - - while (images.hasNext()) { - writer.writeToSequence(images.next()); - } - - writer.close(); - } - } - - private static String getTypePrefix(CharacterTag c) { - if (c instanceof ShapeTag) { - return "shape"; - } - if (c instanceof MorphShapeTag) { - return "morphshape"; - } - if (c instanceof DefineSpriteTag) { - return "sprite"; - } - if (c instanceof TextTag) { - return "text"; - } - if (c instanceof ButtonTag) { - return "button"; - } - if (c instanceof FontTag) { - return "font"; - } - if (c instanceof ImageTag) { - return "image"; - } - return "character"; - } - - public static void writeLibrary(SWF fswf, Set library, OutputStream fos) throws IOException { - for (int c : library) { - CharacterTag ch = fswf.characters.get(c); - if (ch instanceof FontTag) { - fos.write(Utf8Helper.getBytes("function " + getTypePrefix(ch) + c + "(ctx,ch,textColor){\r\n")); - fos.write(Utf8Helper.getBytes(((FontTag) ch).toHtmlCanvas(1))); - fos.write(Utf8Helper.getBytes("}\r\n\r\n")); - } else if (ch instanceof ImageTag) { - ImageTag image = (ImageTag) ch; - String format = image.getImageFormat(); - InputStream imageStream = image.getImageData(); - byte[] imageData; - if (imageStream != null) { - imageData = Helper.readStream(image.getImageData()); - } else { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - ImageIO.write(image.getImage().getBufferedImage(), format.toUpperCase(Locale.ENGLISH), baos); - } catch (IOException ex) { - } - imageData = baos.toByteArray(); - } - String base64ImgData = DatatypeConverter.printBase64Binary(imageData); - fos.write(Utf8Helper.getBytes("var image" + c + " = document.createElement(\"img\");\r\nimage" + c + ".src=\"data:image/" + format + ";base64," + base64ImgData + "\";\r\n")); - } else { - fos.write(Utf8Helper.getBytes("function " + getTypePrefix(ch) + c + "(ctx,ctrans,frame,ratio,time){\r\n")); - if (ch instanceof DrawableTag) { - fos.write(Utf8Helper.getBytes(((DrawableTag) ch).toHtmlCanvas(1))); - } - fos.write(Utf8Helper.getBytes("}\r\n\r\n")); - } - } - } - - public List exportFrames(AbortRetryIgnoreHandler handler, String outdir, int containerId, List frames, final FramesExportSettings settings) throws IOException { - final List ret = new ArrayList<>(); - if (tags.isEmpty()) { - return ret; - } - Timeline tim = null; - String path = ""; - if (containerId == 0) { - tim = getTimeline(); - } else { - tim = ((Timelined) characters.get(containerId)).getTimeline(); - path = File.separator + Helper.makeFileName(characters.get(containerId).getExportFileName()); - } - if (frames == null) { - int frameCnt = tim.frames.size(); - frames = new ArrayList<>(); - for (int i = 0; i < frameCnt; i++) { - frames.add(i); - } - } - - final File foutdir = new File(outdir + path); - if (!foutdir.exists()) { - if (!foutdir.mkdirs()) { - if (!foutdir.exists()) { - throw new IOException("Cannot create directory " + outdir); - } - } - } - - final List fframes = frames; - - Color backgroundColor = null; - if (settings.mode == FramesExportMode.AVI) { - for (Tag t : tags) { - if (t instanceof SetBackgroundColorTag) { - SetBackgroundColorTag sb = (SetBackgroundColorTag) t; - backgroundColor = sb.backgroundColor.toColor(); - } - } - } - - if (settings.mode == FramesExportMode.SVG) { - for (int i = 0; i < frames.size(); i++) { - final int fi = i; - final Timeline ftim = tim; - final Color fbackgroundColor = backgroundColor; - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - int frame = fframes.get(fi); - File f = new File(foutdir + File.separator + frame + ".svg"); - try (FileOutputStream fos = new FileOutputStream(f)) { - SVGExporter exporter = new SVGExporter(new ExportRectangle(ftim.displayRect)); - if (fbackgroundColor != null) { - exporter.setBackGroundColor(fbackgroundColor); - } - frameToSvg(ftim, frame, 0, null, 0, exporter, new ColorTransform(), 0); - fos.write(Utf8Helper.getBytes(exporter.getSVG())); - } - ret.add(f); - } - }, handler).run(); - } - return ret; - } - - if (settings.mode == FramesExportMode.CANVAS) { - final Timeline ftim = tim; - final Color fbackgroundColor = backgroundColor; - final SWF fswf = this; - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - File fcanvas = new File(foutdir + File.separator + "canvas.js"); - Helper.saveStream(SWF.class.getClassLoader().getResourceAsStream("com/jpexs/helpers/resource/canvas.js"), fcanvas); - ret.add(fcanvas); - - File f = new File(foutdir + File.separator + "frames.js"); - File fmin = new File(foutdir + File.separator + "frames.min.js"); - int width = (int) (ftim.displayRect.getWidth() / SWF.unitDivisor); - int height = (int) (ftim.displayRect.getHeight() / SWF.unitDivisor); - try (FileOutputStream fos = new FileOutputStream(f)) { - fos.write(Utf8Helper.getBytes("\r\n")); - Set library = new HashSet<>(); - ftim.getNeededCharacters(fframes, library); - - writeLibrary(fswf, library, fos); - - String currentName = ftim.id == 0 ? "main" : getTypePrefix(fswf.characters.get(ftim.id)) + ftim.id; - - fos.write(Utf8Helper.getBytes("function " + currentName + "(ctx,ctrans,frame,ratio,time){\r\n")); - fos.write(Utf8Helper.getBytes("\tctx.save();\r\n")); - fos.write(Utf8Helper.getBytes("\tctx.transform(1,0,0,1," + (-ftim.displayRect.Xmin / unitDivisor) + "," + (-ftim.displayRect.Ymin / unitDivisor) + ");\r\n")); - fos.write(Utf8Helper.getBytes(framesToHtmlCanvas(unitDivisor, ftim, fframes, 0, null, 0, ftim.displayRect, new ColorTransform(), fbackgroundColor))); - fos.write(Utf8Helper.getBytes("\tctx.restore();\r\n")); - fos.write(Utf8Helper.getBytes("}\r\n\r\n")); - - fos.write(Utf8Helper.getBytes("var frame = -1;\r\n")); - fos.write(Utf8Helper.getBytes("var time = 0;\r\n")); - fos.write(Utf8Helper.getBytes("var frames = [];\r\n")); - for (int i : fframes) { - fos.write(Utf8Helper.getBytes("frames.push(" + i + ");\r\n")); - } - fos.write(Utf8Helper.getBytes("\r\n")); - RGB backgroundColor = new RGB(255, 255, 255); - for (Tag t : fswf.tags) { - if (t instanceof SetBackgroundColorTag) { - SetBackgroundColorTag sb = (SetBackgroundColorTag) t; - backgroundColor = sb.backgroundColor; - } - } - - fos.write(Utf8Helper.getBytes("var backgroundColor = \"" + backgroundColor.toHexRGB() + "\";\r\n")); - fos.write(Utf8Helper.getBytes("var originalWidth = " + width + ";\r\n")); - fos.write(Utf8Helper.getBytes("var originalHeight= " + height + ";\r\n")); - fos.write(Utf8Helper.getBytes("function nextFrame(ctx,ctrans){\r\n")); - fos.write(Utf8Helper.getBytes("\tvar oldframe = frame;\r\n")); - fos.write(Utf8Helper.getBytes("\tframe = (frame+1)%frames.length;\r\n")); - fos.write(Utf8Helper.getBytes("\tif(frame==oldframe){time++;}else{time=0;};\r\n")); - fos.write(Utf8Helper.getBytes("\tdrawFrame();\r\n")); - fos.write(Utf8Helper.getBytes("}\r\n\r\n")); - - fos.write(Utf8Helper.getBytes("function drawFrame(){\r\n")); - fos.write(Utf8Helper.getBytes("\tctx.fillStyle = backgroundColor;\r\n")); - fos.write(Utf8Helper.getBytes("\tctx.fillRect(0,0,canvas.width,canvas.height);\r\n")); - fos.write(Utf8Helper.getBytes("\tctx.save();\r\n")); - fos.write(Utf8Helper.getBytes("\tctx.transform(canvas.width/originalWidth,0,0,canvas.height/originalHeight,0,0);\r\n")); - fos.write(Utf8Helper.getBytes("\t" + currentName + "(ctx,ctrans,frames[frame],0,time);\r\n")); - fos.write(Utf8Helper.getBytes("\tctx.restore();\r\n")); - fos.write(Utf8Helper.getBytes("}\r\n\r\n")); - fos.write(Utf8Helper.getBytes("window.setInterval(function(){nextFrame(ctx,ctrans);}," + (int) (1000.0 / ftim.swf.frameRate) + ");\r\n")); - } - - if (Configuration.packJavaScripts.get()) { - try { - JPacker.main(new String[]{"-q", "-b", "62", "-o", fmin.getAbsolutePath(), f.getAbsolutePath()}); - f.delete(); - } catch (Exception | Error e) { //Something wrong in the packer - Logger.getLogger(SWF.class.getName()).log(Level.WARNING, "JPacker: Cannot minimize script"); - f.renameTo(fmin); - } - } else { - f.renameTo(fmin); - } - - File fh = new File(foutdir + File.separator + "frames.html"); - try (FileOutputStream fos = new FileOutputStream(fh); FileInputStream fis = new FileInputStream(fmin)) { - fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getHtmlPrefix(width, height))); - fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getJsPrefix())); - byte buf[] = new byte[1000]; - int cnt; - while ((cnt = fis.read(buf)) > 0) { - fos.write(buf, 0, cnt); - } - if (Configuration.packJavaScripts.get()) { - fos.write(Utf8Helper.getBytes(";")); - } - fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getJsSuffix())); - fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getHtmlSuffix())); - } - fmin.delete(); - - ret.add(f); - } - }, handler).run(); - - return ret; - } - - final Timeline ftim = tim; - final Color fbackgroundColor = backgroundColor; - final Iterator frameImages = new Iterator() { - - private int pos = 0; - - @Override - public boolean hasNext() { - return fframes.size() > pos; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public BufferedImage next() { - if (!hasNext()) { - return null; - } - return frameToImageGet(ftim, fframes.get(pos++), 0, null, 0, ftim.displayRect, new Matrix(), new ColorTransform(), fbackgroundColor, false).getBufferedImage(); - } - }; - - switch (settings.mode) { - case GIF: - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - File f = new File(foutdir + File.separator + "frames.gif"); - makeGIF(frameImages, frameRate, f); - ret.add(f); - } - }, handler).run(); - break; - case PNG: - for (int i = 0; frameImages.hasNext(); i++) { - final int fi = i; - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - File f = new File(foutdir + File.separator + (fframes.get(fi)+1) + ".png"); - ImageIO.write(frameImages.next(), "PNG", f); - ret.add(f); - } - }, handler).run(); - } - break; - case PDF: - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - File f = new File(foutdir + File.separator + "frames.pdf"); - PDFJob job = new PDFJob(new FileOutputStream(f)); - PageFormat pf = new PageFormat(); - pf.setOrientation(PageFormat.PORTRAIT); - Paper p = new Paper(); - BufferedImage img0 = frameImages.next(); - p.setSize(img0.getWidth() + 10, img0.getHeight() + 10); - pf.setPaper(p); - - for (int i = 0; frameImages.hasNext(); i++) { - BufferedImage img = frameImages.next(); - Graphics g = job.getGraphics(pf); - g.drawImage(img, 5, 5, img.getWidth(), img.getHeight(), null); - g.dispose(); - } - - job.end(); - ret.add(f); - } - }, handler).run(); - break; - case AVI: - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - File f = new File(foutdir + File.separator + "frames.avi"); - makeAVI(frameImages, frameRate, f); - ret.add(f); - } - }, handler).run(); - break; - } - - return ret; - } - - public void exportTexts(AbortRetryIgnoreHandler handler, String outdir, TextExportSettings settings) throws IOException { - new TextExporter().exportTexts(handler, outdir, tags, settings); - } - - public void exportImages(AbortRetryIgnoreHandler handler, String outdir, ImageExportSettings settings) throws IOException { - new ImageExporter().exportImages(handler, outdir, tags, settings); - } - - public void exportShapes(AbortRetryIgnoreHandler handler, String outdir, ShapeExportSettings settings) throws IOException { - new ShapeExporter().exportShapes(handler, outdir, tags, settings); - } - - public void exportMorphShapes(AbortRetryIgnoreHandler handler, String outdir, MorphShapeExportSettings settings) throws IOException { - new MorphShapeExporter().exportMorphShapes(handler, outdir, tags, settings); - } - - public void exportBinaryData(AbortRetryIgnoreHandler handler, String outdir, BinaryDataExportSettings settings) throws IOException { - new BinaryDataExporter().exportBinaryData(handler, outdir, tags, settings); - } - - private final HashMap deobfuscated = new HashMap<>(); - private List> allVariableNames = new ArrayList<>(); - private List allFunctions = new ArrayList<>(); - private HashMap allStrings = new HashMap<>(); - private final HashMap usageTypes = new HashMap<>(); - private final ActionDeobfuscation deobfuscation = new ActionDeobfuscation(); - - private static void getVariables(ConstantPool constantPool, BaseLocalData localData, Stack stack, List output, ActionGraphSource code, int ip, List> variables, List functions, HashMap strings, List visited, HashMap usageTypes, String path) throws InterruptedException { - boolean debugMode = false; - while ((ip > -1) && ip < code.size()) { - if (visited.contains(ip)) { - break; - } - GraphSourceItem ins = code.get(ip); - - if (debugMode) { - System.err.println("Visit " + ip + ": ofs" + Helper.formatAddress(((Action) ins).getAddress()) + ":" + ((Action) ins).getASMSource(new ArrayList(), new ArrayList(), new ArrayList(), code.version, ScriptExportMode.PCODE) + " stack:" + Helper.stackToString(stack, LocalData.create(new ConstantPool()))); - } - if (ins.isExit()) { - break; - } - if (ins.isIgnored()) { - ip++; - continue; - } - - String usageType = "name"; - GraphTargetItem name = null; - if ((ins instanceof ActionGetVariable) - || (ins instanceof ActionGetMember) - || (ins instanceof ActionDefineLocal2) - || (ins instanceof ActionNewMethod) - || (ins instanceof ActionNewObject) - || (ins instanceof ActionCallMethod) - || (ins instanceof ActionCallFunction)) { - if (stack.isEmpty()) { - break; - } - name = stack.peek(); - } - - if ((ins instanceof ActionGetVariable) || (ins instanceof ActionDefineLocal2)) { - usageType = "variable"; - } - if (ins instanceof ActionGetMember) { - usageType = "member"; - } - if ((ins instanceof ActionNewMethod) || (ins instanceof ActionNewObject)) { - usageType = "class"; - } - if (ins instanceof ActionCallMethod) { - usageType = "function"; //can there be method? - } - if (ins instanceof ActionCallFunction) { - usageType = "function"; - } - - if ((ins instanceof ActionDefineFunction) || (ins instanceof ActionDefineFunction2)) { - functions.add(ins); - } - - if (ins instanceof GraphSourceItemContainer) { - GraphSourceItemContainer cnt = (GraphSourceItemContainer) ins; - List cntSizes = cnt.getContainerSizes(); - long addr = code.pos2adr(ip + 1); - ip = code.adr2pos(addr); - String cntName = cnt.getName(); - for (Long size : cntSizes) { - if (size == 0) { - continue; - } - ip = code.adr2pos(addr); - addr += size; - int nextip = code.adr2pos(addr); - getVariables(variables, functions, strings, usageTypes, new ActionGraphSource(code.getActions().subList(ip, nextip), code.version, new HashMap(), new HashMap(), new HashMap()), 0, path + (cntName == null ? "" : "/" + cntName)); - ip = nextip; - } - List> r = new ArrayList<>(); - r.add(new ArrayList()); - r.add(new ArrayList()); - r.add(new ArrayList()); - try { - ((GraphSourceItemContainer) ins).translateContainer(r, stack, output, new HashMap(), new HashMap(), new HashMap()); - } catch (EmptyStackException ex) { - } - //ip++; - continue; - } - - if ((ins instanceof ActionSetVariable) || (ins instanceof ActionSetMember) || (ins instanceof ActionDefineLocal)) { - if (stack.size() < 2) { - break; - } - name = stack.get(stack.size() - 2); - } - - if ((ins instanceof ActionSetVariable) || (ins instanceof ActionDefineLocal)) { - usageType = "variable"; - } - - if (ins instanceof ActionSetMember) { - usageType = "member"; - } - - if (name instanceof DirectValueActionItem) { - variables.add(new MyEntry<>((DirectValueActionItem) name, constantPool)); - usageTypes.put((DirectValueActionItem) name, usageType); - } - - //for..in return - if (((ins instanceof ActionEquals) || (ins instanceof ActionEquals2)) && (stack.size() == 1) && (stack.peek() instanceof DirectValueActionItem)) { - stack.push(new DirectValueActionItem(null, 0, new Null(), new ArrayList())); - } - - if (ins instanceof ActionConstantPool) { - constantPool = new ConstantPool(((ActionConstantPool) ins).constantPool); - } - int staticOperation = Graph.SOP_USE_STATIC; //(Boolean) Configuration.getConfig("autoDeobfuscate", true) ? Graph.SOP_SKIP_STATIC : Graph.SOP_USE_STATIC; - - try { - ins.translate(localData, stack, output, staticOperation, path); - } catch (EmptyStackException ex) { - // probably obfucated code, never executed branch - break; - } - if (ins.isExit()) { - break; - } - - if (ins instanceof ActionPush) { - if (!stack.isEmpty()) { - GraphTargetItem top = stack.peek(); - if (top instanceof DirectValueActionItem) { - DirectValueActionItem dvt = (DirectValueActionItem) top; - if ((dvt.value instanceof String) || (dvt.value instanceof ConstantIndex)) { - if (constantPool == null) { - constantPool = new ConstantPool(dvt.constants); - } - strings.put(dvt, constantPool); - } - } - } - } - - if (ins.isBranch() || ins.isJump()) { - if (ins instanceof ActionIf) { - if (stack.isEmpty()) { - break; - } - stack.pop(); - } - visited.add(ip); - List branches = ins.getBranches(code); - for (int b : branches) { - @SuppressWarnings("unchecked") - Stack brStack = (Stack) stack.clone(); - if (b >= 0) { - getVariables(constantPool, localData, brStack, output, code, b, variables, functions, strings, visited, usageTypes, path); - } else { - if (debugMode) { - System.out.println("Negative branch:" + b); - } - } - } - // } - break; - } - ip++; - }; - } - - private static void getVariables(List> variables, List functions, HashMap strings, HashMap usageType, ActionGraphSource code, int addr, String path) throws InterruptedException { - ActionLocalData localData = new ActionLocalData(); - getVariables(null, localData, new Stack(), new ArrayList(), code, code.adr2pos(addr), variables, functions, strings, new ArrayList(), usageType, path); - } - - private List> getVariables(List> variables, List functions, HashMap strings, HashMap usageType, ASMSource src, String path) throws InterruptedException { - List> ret = new ArrayList<>(); - List actions = src.getActions(); - actionsMap.put(src, actions); - getVariables(variables, functions, strings, usageType, new ActionGraphSource(actions, version, new HashMap(), new HashMap(), new HashMap()), 0, path); - return ret; - } - private HashMap> actionsMap = new HashMap<>(); - - private void getVariables(List objs, String path) throws InterruptedException { - List processed = new ArrayList<>(); - for (ContainerItem o : objs) { - if (o instanceof ASMSource) { - String infPath = path + "/" + o.toString(); - int pos = 1; - String infPath2 = infPath; - while (processed.contains(infPath2)) { - pos++; - infPath2 = infPath + "[" + pos + "]"; - } - processed.add(infPath2); - informListeners("getVariables", infPath2); - getVariables(allVariableNames, allFunctions, allStrings, usageTypes, (ASMSource) o, path); - } - if (o instanceof Container) { - getVariables(((Container) o).getSubItems(), path + "/" + o.toString()); - } - } - } - - public int deobfuscateAS3Identifiers(RenameType renameType) { - for (Tag tag : tags) { - if (tag instanceof ABCContainerTag) { - ((ABCContainerTag) tag).getABC().deobfuscateIdentifiers(deobfuscated, renameType, true); - tag.setModified(true); - } - } - for (Tag tag : tags) { - if (tag instanceof ABCContainerTag) { - ((ABCContainerTag) tag).getABC().deobfuscateIdentifiers(deobfuscated, renameType, false); - tag.setModified(true); - } - } - for (Tag tag : tags) { - if (tag instanceof SymbolClassTag) { - SymbolClassTag sc = (SymbolClassTag) tag; - for (int i = 0; i < sc.names.length; i++) { - String newname = deobfuscation.deobfuscateNameWithPackage(sc.names[i], deobfuscated, renameType, deobfuscated); - if (newname != null) { - sc.names[i] = newname; - } - } - sc.setModified(true); - } - } - deobfuscation.deobfuscateInstanceNames(deobfuscated, renameType, tags, new HashMap()); - return deobfuscated.size(); - } - - public int deobfuscateIdentifiers(RenameType renameType) throws InterruptedException { - findFileAttributes(); - if (fileAttributes == null) { - int cnt = 0; - cnt += deobfuscateAS2Identifiers(renameType); - cnt += deobfuscateAS3Identifiers(renameType); - return cnt; - } else { - if (fileAttributes.actionScript3) { - return deobfuscateAS3Identifiers(renameType); - } else { - return deobfuscateAS2Identifiers(renameType); - } - } - } - - public void renameAS2Identifier(String identifier, String newname) throws InterruptedException { - Map selected = new HashMap<>(); - selected.put(identifier, newname); - renameAS2Identifiers(null, selected); - } - - private int deobfuscateAS2Identifiers(RenameType renameType) throws InterruptedException { - return renameAS2Identifiers(renameType, null); - } - - private int renameAS2Identifiers(RenameType renameType, Map selected) throws InterruptedException { - actionsMap = new HashMap<>(); - allFunctions = new ArrayList<>(); - allVariableNames = new ArrayList<>(); - allStrings = new HashMap<>(); - - List objs = new ArrayList<>(); - int ret = 0; - objs.addAll(tags); - getVariables(objs, ""); - informListeners("rename", ""); - int fc = 0; - for (MyEntry it : allVariableNames) { - String name = it.key.toStringNoH(it.value); - deobfuscation.allVariableNamesStr.add(name); - } - - informListeners("rename", "classes"); - int classCount = 0; - for (Tag t : tags) { - if (t instanceof DoInitActionTag) { - classCount++; - } - } - int cnt = 0; - for (Tag t : tags) { - if (t instanceof DoInitActionTag) { - cnt++; - informListeners("rename", "class " + cnt + "/" + classCount); - DoInitActionTag dia = (DoInitActionTag) t; - String exportName = dia.getExportName(); - final String pkgPrefix = "__Packages."; - String[] classNameParts = null; - if ((exportName != null) && exportName.startsWith(pkgPrefix)) { - String className = exportName.substring(pkgPrefix.length()); - if (className.contains(".")) { - classNameParts = className.split("\\."); - } else { - classNameParts = new String[]{className}; - } - } - int staticOperation = Graph.SOP_USE_STATIC; //(Boolean) Configuration.getConfig("autoDeobfuscate", true) ? Graph.SOP_SKIP_STATIC : Graph.SOP_USE_STATIC; - List dec; - try { - dec = Action.actionsToTree(dia.getActions(), version, staticOperation, ""/*FIXME*/); - } catch (EmptyStackException ex) { - continue; - } - GraphTargetItem name = null; - for (GraphTargetItem it : dec) { - if (it instanceof ClassActionItem) { - ClassActionItem cti = (ClassActionItem) it; - List methods = new ArrayList<>(); - methods.addAll(cti.functions); - methods.addAll(cti.staticFunctions); - - for (GraphTargetItem gti : methods) { - if (gti instanceof FunctionActionItem) { - FunctionActionItem fun = (FunctionActionItem) gti; - if (fun.calculatedFunctionName instanceof DirectValueActionItem) { - DirectValueActionItem dvf = (DirectValueActionItem) fun.calculatedFunctionName; - String fname = dvf.toStringNoH(null); - String changed = deobfuscation.deobfuscateName(fname, false, "method", deobfuscated, renameType, selected); - if (changed != null) { - deobfuscated.put(fname, changed); - } - } - } - } - - List vars = new ArrayList<>(); - for (MyEntry item : cti.vars) { - vars.add(item.key); - } - for (MyEntry item : cti.staticVars) { - vars.add(item.key); - } - for (GraphTargetItem gti : vars) { - if (gti instanceof DirectValueActionItem) { - DirectValueActionItem dvf = (DirectValueActionItem) gti; - String vname = dvf.toStringNoH(null); - String changed = deobfuscation.deobfuscateName(vname, false, "attribute", deobfuscated, renameType, selected); - if (changed != null) { - deobfuscated.put(vname, changed); - } - } - } - - name = cti.className; - break; - } - if (it instanceof InterfaceActionItem) { - InterfaceActionItem ift = (InterfaceActionItem) it; - name = ift.name; - } - } - - if (name != null) { - int pos = 0; - while (name instanceof GetMemberActionItem) { - GetMemberActionItem mem = (GetMemberActionItem) name; - GraphTargetItem memberName = mem.memberName; - if (memberName instanceof DirectValueActionItem) { - DirectValueActionItem dvt = (DirectValueActionItem) memberName; - String nameStr = dvt.toStringNoH(null); - if (classNameParts != null) { - if (classNameParts.length - 1 - pos < 0) { - break; - } - } - String changedNameStr = nameStr; - if (classNameParts != null) { - changedNameStr = classNameParts[classNameParts.length - 1 - pos]; - } - String changedNameStr2 = deobfuscation.deobfuscateName(changedNameStr, pos == 0, pos == 0 ? "class" : "package", deobfuscated, renameType, selected); - if (changedNameStr2 != null) { - changedNameStr = changedNameStr2; - } - ret++; - deobfuscated.put(nameStr, changedNameStr); - pos++; - } - name = mem.object; - } - if (name instanceof GetVariableActionItem) { - GetVariableActionItem var = (GetVariableActionItem) name; - if (var.name instanceof DirectValueActionItem) { - DirectValueActionItem dvt = (DirectValueActionItem) var.name; - String nameStr = dvt.toStringNoH(null); - if (classNameParts != null) { - if (classNameParts.length - 1 - pos < 0) { - break; - } - } - String changedNameStr = nameStr; - if (classNameParts != null) { - changedNameStr = classNameParts[classNameParts.length - 1 - pos]; - } - String changedNameStr2 = deobfuscation.deobfuscateName(changedNameStr, pos == 0, pos == 0 ? "class" : "package", deobfuscated, renameType, selected); - if (changedNameStr2 != null) { - changedNameStr = changedNameStr2; - } - ret++; - deobfuscated.put(nameStr, changedNameStr); - pos++; - } - } - } - t.setModified(true); - } - } - - for (GraphSourceItem fun : allFunctions) { - fc++; - informListeners("rename", "function " + fc + "/" + allFunctions.size()); - if (fun instanceof ActionDefineFunction) { - ActionDefineFunction f = (ActionDefineFunction) fun; - if (f.functionName.isEmpty()) { //anonymous function, leave as is - continue; - } - String changed = deobfuscation.deobfuscateName(f.functionName, false, "function", deobfuscated, renameType, selected); - if (changed != null) { - f.replacedFunctionName = changed; - ret++; - } - } - if (fun instanceof ActionDefineFunction2) { - ActionDefineFunction2 f = (ActionDefineFunction2) fun; - if (f.functionName.isEmpty()) { //anonymous function, leave as is - continue; - } - String changed = deobfuscation.deobfuscateName(f.functionName, false, "function", deobfuscated, renameType, selected); - if (changed != null) { - f.replacedFunctionName = changed; - ret++; - } - } - } - - HashSet stringsNoVarH = new HashSet<>(); - List allVariableNamesDv = new ArrayList<>(); - for (MyEntry it : allVariableNames) { - allVariableNamesDv.add(it.key); - } - for (DirectValueActionItem ti : allStrings.keySet()) { - if (!allVariableNamesDv.contains(ti)) { - stringsNoVarH.add(System.identityHashCode(allStrings.get(ti)) + "_" + ti.toStringNoH(allStrings.get(ti))); - } - } - - int vc = 0; - for (MyEntry it : allVariableNames) { - vc++; - String name = it.key.toStringNoH(it.value); - String changed = deobfuscation.deobfuscateName(name, false, usageTypes.get(it.key), deobfuscated, renameType, selected); - if (changed != null) { - boolean addNew = false; - String h = System.identityHashCode(it.key) + "_" + name; - if (stringsNoVarH.contains(h)) { - addNew = true; - } - ActionPush pu = (ActionPush) it.key.src; - if (pu.replacement == null) { - pu.replacement = new ArrayList<>(); - pu.replacement.addAll(pu.values); - } - if (pu.replacement.get(it.key.pos) instanceof ConstantIndex) { - ConstantIndex ci = (ConstantIndex) pu.replacement.get(it.key.pos); - ConstantPool pool = it.value; - if (pool == null) { - continue; - } - if (pool.constants == null) { - continue; - } - if (addNew) { - pool.constants.add(changed); - ci.index = pool.constants.size() - 1; - } else { - pool.constants.set(ci.index, changed); - } - } else { - pu.replacement.set(it.key.pos, changed); - } - ret++; - } - } - for (ASMSource src : actionsMap.keySet()) { - actionsMap.put(src, Action.removeNops(0, actionsMap.get(src), version, ""/*FIXME path*/)); - src.setActions(actionsMap.get(src)); - src.setModified(); - } - deobfuscation.deobfuscateInstanceNames(deobfuscated, renameType, tags, selected); - return ret; - } - - public void exportFla(AbortRetryIgnoreHandler handler, String outfile, String swfName, String generator, String generatorVerName, String generatorVersion, boolean parallel, FLAVersion version) throws IOException { - XFLConverter.convertSWF(handler, this, swfName, outfile, true, generator, generatorVerName, generatorVersion, parallel, version); - } - - public void exportXfl(AbortRetryIgnoreHandler handler, String outfile, String swfName, String generator, String generatorVerName, String generatorVersion, boolean parallel, FLAVersion version) throws IOException { - XFLConverter.convertSWF(handler, this, swfName, outfile, false, generator, generatorVerName, generatorVersion, parallel, version); - } - - public static AffineTransform matrixToTransform(MATRIX mat) { - return new AffineTransform(mat.getScaleXFloat(), mat.getRotateSkew0Float(), - mat.getRotateSkew1Float(), mat.getScaleYFloat(), - mat.translateX, mat.translateY); - } - - private static Cache frameCache = Cache.getInstance(false); - - public static SerializableImage getFromCache(String key) { - if (frameCache.contains(key)) { - return (SerializableImage) SWF.frameCache.get(key); - } - return null; - } - - public static void putToCache(String key, SerializableImage img) { - if (Configuration.useFrameCache.get()) { - frameCache.put(key, img); - } - } - - public static void clearImageCache() { - frameCache.clear(); - } - - public static RECT fixRect(RECT rect) { - RECT ret = new RECT(); - ret.Xmin = rect.Xmin; - ret.Xmax = rect.Xmax; - ret.Ymin = rect.Ymin; - ret.Ymax = rect.Ymax; - - if (ret.Xmax <= 0) { - ret.Xmax = ret.getWidth(); - ret.Xmin = 0; - } - if (ret.Ymax <= 0) { - ret.Ymax = ret.getHeight(); - ret.Ymin = 0; - } - if (ret.Xmin < 0) { - ret.Xmax += (-ret.Xmin); - ret.Xmin = 0; - } - if (ret.Ymin < 0) { - ret.Ymax += (-ret.Ymin); - ret.Ymin = 0; - } - - if (ret.getWidth() < 1 || ret.getHeight() < 1) { - ret.Xmin = 0; - ret.Ymin = 0; - ret.Xmax = 20; - ret.Ymax = 20; - } - return ret; - } - - private static String jsArrColor(RGB rgb) { - return "[" + rgb.red + "," + rgb.green + "," + rgb.blue + "," + ((rgb instanceof RGBA) ? ((RGBA) rgb).getAlphaFloat() : 1) + "]"; - } - - public static String framesToHtmlCanvas(double unitDivisor, Timeline timeline, List frames, int time, DepthState stateUnderCursor, int mouseButton, RECT displayRect, ColorTransform colorTransform, Color backGroundColor) { - StringBuilder sb = new StringBuilder(); - if (frames == null) { - frames = new ArrayList<>(); - for (int i = 0; i < timeline.getFrameCount(); i++) { - frames.add(i); - } - } - sb.append("\tvar clips = [];\r\n"); - sb.append("\tvar frame_cnt = ").append(timeline.getFrameCount()).append(";\r\n"); - sb.append("\tframe = frame % frame_cnt;\r\n"); - sb.append("\tswitch(frame){\r\n"); - int maxDepth = timeline.getMaxDepth(); - Stack clipDepths = new Stack<>(); - for (int frame = 0; frame < frames.size(); frame++) { - sb.append("\t\tcase ").append(frame).append(":\r\n"); - Frame frameObj = timeline.frames.get(frame); - for (int i = 1; i <= maxDepth + 1; i++) { - while (!clipDepths.isEmpty() && clipDepths.peek() <= i) { - clipDepths.pop(); - sb.append("\t\t\tvar o = clips.pop();\r\n"); - sb.append("\t\t\tctx.globalCompositeOperation = \"destination-in\";\r\n"); - sb.append("\t\t\tctx.setTransform(1,0,0,1,0,0);\r\n"); - sb.append("\t\t\tctx.drawImage(o.clipCanvas,0,0);\r\n"); - sb.append("\t\t\tvar ms=o.ctx._matrices.slice();\r\n"); - sb.append("\t\t\to.ctx.setTransform(1,0,0,1,0,0);\r\n"); - sb.append("\t\t\to.ctx.globalCompositeOperation = \"source-over\";\r\n"); - sb.append("\t\t\to.ctx.drawImage(canvas,0,0);\r\n"); - sb.append("\t\t\to.ctx.applyTransforms(ms);\r\n"); - sb.append("\t\t\tctx = o.ctx;\r\n"); - sb.append("\t\t\tcanvas = o.canvas;\r\n"); - } - if (!frameObj.layers.containsKey(i)) { - continue; - } - DepthState layer = frameObj.layers.get(i); - if (!timeline.swf.characters.containsKey(layer.characterId)) { - continue; - } - if (!layer.isVisible) { - continue; - } - CharacterTag character = timeline.swf.characters.get(layer.characterId); - - if (colorTransform == null) { - colorTransform = new ColorTransform(); - } - Matrix placeMatrix = new Matrix(layer.matrix); - placeMatrix.scaleX /= unitDivisor; - placeMatrix.scaleY /= unitDivisor; - placeMatrix.rotateSkew0 /= unitDivisor; - placeMatrix.rotateSkew1 /= unitDivisor; - placeMatrix.translateX /= unitDivisor; - placeMatrix.translateY /= unitDivisor; - - int f = 0; - String fstr = "0"; - if (character instanceof DefineSpriteTag) { - DefineSpriteTag sp = (DefineSpriteTag) character; - Timeline tim = sp.getTimeline(); - if (tim.getFrameCount() > 0) { - f = layer.time % tim.getFrameCount(); - fstr = "(" + f + "+time)%" + tim.getFrameCount(); - } - } - - if (layer.clipDepth != -1) { - clipDepths.push(layer.clipDepth); - sb.append("\t\t\tclips.push({ctx:ctx,canvas:canvas});\r\n"); - sb.append("\t\t\tvar ccanvas = createCanvas(canvas.width,canvas.height);\r\n"); - sb.append("\t\t\tvar cctx = ccanvas.getContext(\"2d\");\r\n"); - sb.append("\t\t\tenhanceContext(cctx);\r\n"); - sb.append("\t\t\tcctx.applyTransforms(ctx._matrices);\r\n"); - sb.append("\t\t\tcanvas = ccanvas;\r\n"); - sb.append("\t\t\tctx = cctx;\r\n"); - } - - if (layer.filters != null && layer.filters.size() > 0) { - sb.append("\t\t\tvar oldctx = ctx;\r\n"); - sb.append("\t\t\tvar fcanvas = createCanvas(canvas.width,canvas.height);"); - sb.append("\t\t\tvar fctx = fcanvas.getContext(\"2d\");\r\n"); - sb.append("\t\t\tenhanceContext(fctx);\r\n"); - sb.append("\t\t\tfctx.applyTransforms(ctx._matrices);\r\n"); - sb.append("\t\t\tctx = fctx;\r\n"); - } - - ColorTransform ctrans = layer.colorTransForm; - String ctrans_str = "ctrans"; - if (ctrans == null) { - ctrans = new ColorTransform(); - } else { - ctrans_str = "ctrans.merge(new cxform(" - + ctrans.getRedAdd() + "," + ctrans.getGreenAdd() + "," + ctrans.getBlueAdd() + "," + ctrans.getAlphaAdd() + "," - + ctrans.getRedMulti() + "," + ctrans.getGreenMulti() + "," + ctrans.getBlueMulti() + "," + ctrans.getAlphaMulti() - + "))"; - } - sb.append("\t\t\tplace(\"").append(getTypePrefix(character)).append(layer.characterId).append("\",canvas,ctx,[").append(placeMatrix.scaleX).append(",") - .append(placeMatrix.rotateSkew0).append(",") - .append(placeMatrix.rotateSkew1).append(",") - .append(placeMatrix.scaleY).append(",") - .append(placeMatrix.translateX).append(",") - .append(placeMatrix.translateY).append("],").append(ctrans_str).append(",").append("").append(layer.blendMode < 1 ? 1 : layer.blendMode).append(",").append(fstr).append(",").append(layer.ratio < 0 ? 0 : layer.ratio).append(",time").append(");\r\n"); - - if (layer.filters != null && layer.filters.size() > 0) { - for (FILTER filter : layer.filters) { - if (filter instanceof COLORMATRIXFILTER) { - COLORMATRIXFILTER cmf = (COLORMATRIXFILTER) filter; - String mat = "["; - for (int k = 0; k < cmf.matrix.length; k++) { - if (k > 0) { - mat += ","; - } - mat += cmf.matrix[k]; - } - mat += "]"; - sb.append("\t\t\tfcanvas = Filters.colorMatrix(fcanvas,fcanvas.getContext(\"2d\"),").append(mat).append(");\r\n"); - } - - if (filter instanceof CONVOLUTIONFILTER) { - CONVOLUTIONFILTER cf = (CONVOLUTIONFILTER) filter; - int height = cf.matrix.length; - int width = cf.matrix[0].length; - float[] matrix2 = new float[width * height]; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - matrix2[y * width + x] = cf.matrix[x][y] * cf.divisor + cf.bias; - } - } - String mat = "["; - for (int k = 0; k < matrix2.length; k++) { - if (k > 0) { - mat += ","; - } - mat += matrix2[k]; - } - mat += "]"; - sb.append("\t\t\tfcanvas = Filters.convolution(fcanvas,fcanvas.getContext(\"2d\"),").append(mat).append(",false);\r\n"); - } - - if (filter instanceof GLOWFILTER) { - GLOWFILTER gf = (GLOWFILTER) filter; - sb.append("\t\t\tfcanvas = Filters.glow(fcanvas,fcanvas.getContext(\"2d\"),").append(gf.blurX).append(",").append(gf.blurY).append(",").append(gf.strength).append(",").append(jsArrColor(gf.glowColor)).append(",").append(gf.innerGlow ? "true" : "false").append(",").append(gf.knockout ? "true" : "false").append(",").append(gf.passes).append(");\r\n"); - } - - if (filter instanceof DROPSHADOWFILTER) { - DROPSHADOWFILTER ds = (DROPSHADOWFILTER) filter; - sb.append("\t\t\tfcanvas = Filters.dropShadow(fcanvas,fcanvas.getContext(\"2d\"),").append(ds.blurX).append(",").append(ds.blurY).append(",").append((int) (ds.angle * 180 / Math.PI)).append(",").append(ds.distance).append(",").append(jsArrColor(ds.dropShadowColor)).append(",").append(ds.innerShadow ? "true" : "false").append(",").append(ds.passes).append(",").append(ds.strength).append(",").append(ds.knockout ? "true" : "false").append(");\r\n"); - } - if (filter instanceof BEVELFILTER) { - BEVELFILTER bv = (BEVELFILTER) filter; - String type = "Filters.INNER"; - if (bv.onTop && !bv.innerShadow) { - type = "Filters.FULL"; - } else if (!bv.innerShadow) { - type = "Filters.OUTER"; - } - sb.append("\t\t\tfcanvas = Filters.bevel(fcanvas,fcanvas.getContext(\"2d\"),").append(bv.blurX).append(",").append(bv.blurY).append(",").append(bv.strength).append(",").append(type).append(",").append(jsArrColor(bv.highlightColor)).append(",").append(jsArrColor(bv.shadowColor)).append(",").append((int) (bv.angle * 180 / Math.PI)).append(",").append(bv.distance).append(",").append(bv.knockout ? "true" : "false").append(",").append(bv.passes).append(");\r\n"); - } - - if (filter instanceof GRADIENTBEVELFILTER) { - GRADIENTBEVELFILTER gbf = (GRADIENTBEVELFILTER) filter; - String colArr = "["; - String ratArr = "["; - for (int k = 0; k < gbf.gradientColors.length; k++) { - if (k > 0) { - colArr += ","; - ratArr += ","; - } - colArr += jsArrColor(gbf.gradientColors[k]); - ratArr += gbf.gradientRatio[k] / 255f; - } - colArr += "]"; - ratArr += "]"; - String type = "Filters.INNER"; - if (gbf.onTop && !gbf.innerShadow) { - type = "Filters.FULL"; - } else if (!gbf.innerShadow) { - type = "Filters.OUTER"; - } - - sb.append("\t\t\tfcanvas = Filters.gradientBevel(fcanvas,fcanvas.getContext(\"2d\"),").append(colArr).append(",").append(ratArr).append(",").append(gbf.blurX).append(",").append(gbf.blurY).append(",").append(gbf.strength).append(",").append(type).append(",").append((int) (gbf.angle * 180 / Math.PI)).append(",").append(gbf.distance).append(",").append(gbf.knockout ? "true" : "false").append(",").append(gbf.passes).append(");\r\n"); - } - - if (filter instanceof GRADIENTGLOWFILTER) { - GRADIENTGLOWFILTER ggf = (GRADIENTGLOWFILTER) filter; - String colArr = "["; - String ratArr = "["; - for (int k = 0; k < ggf.gradientColors.length; k++) { - if (k > 0) { - colArr += ","; - ratArr += ","; - } - colArr += jsArrColor(ggf.gradientColors[k]); - ratArr += ggf.gradientRatio[k] / 255f; - } - colArr += "]"; - ratArr += "]"; - String type = "Filters.INNER"; - if (ggf.onTop && !ggf.innerShadow) { - type = "Filters.FULL"; - } else if (!ggf.innerShadow) { - type = "Filters.OUTER"; - } - - sb.append("\t\t\tfcanvas = Filters.gradientGlow(fcanvas,fcanvas.getContext(\"2d\"),").append(ggf.blurX).append(",").append(ggf.blurY).append(",").append((int) (ggf.angle * 180 / Math.PI)).append(",").append(ggf.distance).append(",").append(colArr).append(",").append(ratArr).append(",").append(type).append(",").append(ggf.passes).append(",").append(ggf.strength).append(",").append(ggf.knockout ? "true" : "false").append(");\r\n"); - } - } - sb.append("\t\t\tctx = oldctx;\r\n"); - sb.append("\t\t\tvar ms=ctx._matrices;\r\n"); - sb.append("\t\t\tctx.setTransform(1,0,0,1,0,0);\r\n"); - sb.append("\t\t\tctx.drawImage(fcanvas,0,0);\r\n"); - sb.append("\t\t\tctx.applyTransforms(ms);\r\n"); - } - - if (layer.clipDepth != -1) { - sb.append("\t\t\tclips[clips.length-1].clipCanvas = canvas;\r\n"); - sb.append("\t\t\tcanvas = createCanvas(canvas.width,canvas.height);\r\n"); - sb.append("\t\t\tvar nctx = canvas.getContext(\"2d\");\r\n"); - sb.append("\t\t\tenhanceContext(nctx);\r\n"); - sb.append("\t\t\tnctx.applyTransforms(ctx._matrices);\r\n"); - sb.append("\t\t\tctx = nctx;\r\n"); - } - } - sb.append("\t\t\tbreak;\r\n"); - } - sb.append("\t}\r\n"); - return sb.toString(); - } - - public static void frameToSvg(Timeline timeline, int frame, int time, DepthState stateUnderCursor, int mouseButton, SVGExporter exporter, ColorTransform colorTransform, int level) throws IOException { - if (timeline.frames.size() <= frame) { - return; - } - Frame frameObj = timeline.frames.get(frame); - List clips = new ArrayList<>(); - List prevClips = new ArrayList<>(); - - int maxDepth = timeline.getMaxDepth(); - for (int i = 1; i <= maxDepth; i++) { - for (int c = 0; c < clips.size(); c++) { - if (clips.get(c).depth == i) { - exporter.setClip(prevClips.get(c)); - prevClips.remove(c); - clips.remove(c); - } - } - if (!frameObj.layers.containsKey(i)) { - continue; - } - DepthState layer = frameObj.layers.get(i); - if (!timeline.swf.characters.containsKey(layer.characterId)) { - continue; - } - if (!layer.isVisible) { - continue; - } - CharacterTag character = timeline.swf.characters.get(layer.characterId); - - if (colorTransform == null) { - colorTransform = new ColorTransform(); - } - - ColorTransform clrTrans = Helper.deepCopy(colorTransform); - if (layer.colorTransForm != null && layer.blendMode <= 1) { //Normal blend mode - clrTrans = colorTransform.merge(layer.colorTransForm); - } - - if (character instanceof DrawableTag) { - DrawableTag drawable = (DrawableTag) character; - - String assetName; - Tag drawableTag = (Tag) drawable; - if (exporter.exportedTags.containsKey(drawableTag)) { - assetName = exporter.exportedTags.get(drawableTag); - } else { - assetName = getTagIdPrefix(drawableTag, exporter); - exporter.exportedTags.put(drawableTag, assetName); - exporter.createDefGroup(new ExportRectangle(drawable.getRect()), assetName); - drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1); - exporter.endGroup(); - } - RECT boundRect = drawable.getRect(); - ExportRectangle rect = new ExportRectangle(boundRect); - - // TODO: if (layer.filters != null) - // TODO: if (layer.blendMode > 1) - if (layer.clipDepth > -1) { - String clipName = exporter.getUniqueId("clipPath"); - exporter.createClipPath(new Matrix(), clipName); - SvgClip clip = new SvgClip(clipName, layer.clipDepth); - clips.add(clip); - prevClips.add(exporter.getClip()); - Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); - exporter.addUse(mat, boundRect, assetName); - exporter.setClip(clip.shape); - exporter.endGroup(); - } else { - Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); - exporter.addUse(mat, boundRect, assetName); - } - } - } - } - - private static String getTagIdPrefix(Tag tag, SVGExporter exporter) { - if (tag instanceof ShapeTag) { - return exporter.getUniqueId("shape"); - } - if (tag instanceof MorphShapeTag) { - return exporter.getUniqueId("morphshape"); - } - if (tag instanceof DefineSpriteTag) { - return exporter.getUniqueId("sprite"); - } - if (tag instanceof TextTag) { - return exporter.getUniqueId("text"); - } - if (tag instanceof ButtonTag) { - return exporter.getUniqueId("button"); - } - return exporter.getUniqueId("tag"); - } - - public static SerializableImage frameToImageGet(Timeline timeline, int frame, int time, DepthState stateUnderCursor, int mouseButton, RECT displayRect, Matrix transformation, ColorTransform colorTransform, Color backGroundColor, boolean useCache) { - String key = "frame_" + frame + "_" + timeline.id + "_" + timeline.swf.hashCode(); - SerializableImage image; - if (useCache) { - image = getFromCache(key); - if (image != null) { - return image; - } - } - - if (timeline.frames.isEmpty()) { - return new SerializableImage(1, 1, SerializableImage.TYPE_INT_ARGB); - } - - RECT rect = displayRect; - image = new SerializableImage((int) (rect.getWidth() / SWF.unitDivisor) + 1, - (int) (rect.getHeight() / SWF.unitDivisor) + 1, SerializableImage.TYPE_INT_ARGB); - if (backGroundColor == null) { - image.fillTransparent(); - } else { - Graphics2D g = (Graphics2D) image.getBufferedImage().getGraphics(); - g.setComposite(AlphaComposite.Src); - g.setColor(backGroundColor); - g.fill(new Rectangle(image.getWidth(), image.getHeight())); - } - Matrix m = transformation.clone(); - m.translate(-rect.Xmin, -rect.Ymin); - frameToImage(timeline, frame, time, stateUnderCursor, mouseButton, image, m, colorTransform); - putToCache(key, image); - return image; - } - - public static void framesToImage(Timeline timeline, List ret, int startFrame, int stopFrame, DepthState stateUnderCursor, int mouseButton, RECT displayRect, int totalFrameCount, Stack visited, Matrix transformation, ColorTransform colorTransform) { - RECT rect = displayRect; - for (int f = 0; f < timeline.frames.size(); f++) { - SerializableImage image = new SerializableImage((int) (rect.getWidth() / SWF.unitDivisor) + 1, - (int) (rect.getHeight() / SWF.unitDivisor) + 1, SerializableImage.TYPE_INT_ARGB); - image.fillTransparent(); - Matrix m = new Matrix(); - m.translate(-rect.Xmin, -rect.Ymin); - frameToImage(timeline, f, 0, stateUnderCursor, mouseButton, image, m, colorTransform); - ret.add(image); - } - } - - public static void frameToImage(Timeline timeline, int frame, int time, DepthState stateUnderCursor, int mouseButton, SerializableImage image, Matrix transformation, ColorTransform colorTransform) { - float unzoom = (float) SWF.unitDivisor; - if (timeline.frames.size() <= frame) { - return; - } - Frame frameObj = timeline.frames.get(frame); - Graphics2D g = (Graphics2D) image.getGraphics(); - g.setPaint(frameObj.backgroundColor.toColor()); - g.fill(new Rectangle(image.getWidth(), image.getHeight())); - g.setTransform(transformation.toTransform()); - List clips = new ArrayList<>(); - List prevClips = new ArrayList<>(); - - int maxDepth = timeline.getMaxDepth(); - for (int i = 1; i <= maxDepth; i++) { - for (int c = 0; c < clips.size(); c++) { - if (clips.get(c).depth == i) { - g.setClip(prevClips.get(c)); - prevClips.remove(c); - clips.remove(c); - } - } - if (!frameObj.layers.containsKey(i)) { - continue; - } - DepthState layer = frameObj.layers.get(i); - if (!timeline.swf.characters.containsKey(layer.characterId)) { - continue; - } - if (!layer.isVisible) { - continue; - } - CharacterTag character = timeline.swf.characters.get(layer.characterId); - Matrix mat = new Matrix(layer.matrix); - mat = mat.preConcatenate(transformation); - - if (colorTransform == null) { - colorTransform = new ColorTransform(); - } - - ColorTransform clrTrans = Helper.deepCopy(colorTransform); - if (layer.colorTransForm != null && layer.blendMode <= 1) { //Normal blend mode - clrTrans = colorTransform.merge(layer.colorTransForm); - } - - boolean showPlaceholder = false; - if (character instanceof DrawableTag) { - DrawableTag drawable = (DrawableTag) character; - SerializableImage img; - Matrix drawMatrix = new Matrix(); - int drawableFrameCount = drawable.getNumFrames(); - if (drawableFrameCount == 0) { - drawableFrameCount = 1; - } - int dframe = (time + layer.time) % drawableFrameCount; - if (character instanceof ButtonTag) { - ButtonTag bt = (ButtonTag) character; - dframe = ButtonTag.FRAME_UP; - if (stateUnderCursor == layer) { - if (mouseButton > 0) { - dframe = ButtonTag.FRAME_DOWN; - } else { - dframe = ButtonTag.FRAME_OVER; - } - } - } - - RECT boundRect = drawable.getRect(); - ExportRectangle rect = new ExportRectangle(boundRect); - rect = mat.transform(rect); - Matrix m = mat.clone(); - if (layer.filters != null && layer.filters.size() > 0) { - // calculate size after applying the filters - double deltaXMax = 0; - double deltaYMax = 0; - for (FILTER filter : layer.filters) { - double x = filter.getDeltaX(); - double y = filter.getDeltaY(); - deltaXMax = Math.max(x, deltaXMax); - deltaYMax = Math.max(y, deltaYMax); - } - rect.xMin -= deltaXMax * SWF.unitDivisor; - rect.xMax += deltaXMax * SWF.unitDivisor; - rect.yMin -= deltaYMax * SWF.unitDivisor; - rect.yMax += deltaYMax * SWF.unitDivisor; - } - - rect.xMin -= SWF.unitDivisor; - rect.yMin -= SWF.unitDivisor; - rect.xMin = Math.max(0, rect.xMin); - rect.yMin = Math.max(0, rect.yMin); - - int newWidth = (int) (rect.getWidth() / SWF.unitDivisor); - int newHeight = (int) (rect.getHeight() / SWF.unitDivisor); - int deltaX = (int) (rect.xMin / SWF.unitDivisor); - int deltaY = (int) (rect.yMin / SWF.unitDivisor); - newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1; - newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1; - - if (newWidth <= 0 || newHeight <= 0) { - continue; - } - - img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB); - img.fillTransparent(); - - m.translate(-rect.xMin, -rect.yMin); - drawMatrix.translate(rect.xMin, rect.yMin); - - drawable.toImage(dframe, layer.time + time, layer.ratio, stateUnderCursor, mouseButton, img, m, clrTrans); - //if(stateUnderCursor == layer){ - /* if(true){ - Graphics2D gg = (Graphics2D)img.getGraphics(); - gg.setStroke(new BasicStroke(3)); - gg.setPaint(Color.red); - gg.setTransform(AffineTransform.getTranslateInstance(0, 0)); - gg.draw(SHAPERECORD.twipToPixelShape(drawable.getOutline(frame, layer.ratio, stateUnderCursor, mouseButton, m))); - }*/ - - if (layer.filters != null) { - for (FILTER filter : layer.filters) { - img = filter.apply(img); - } - } - if (layer.blendMode > 1) { - if (layer.colorTransForm != null) { - img = layer.colorTransForm.apply(img); - } - } - - drawMatrix.translateX /= unzoom; - drawMatrix.translateY /= unzoom; - AffineTransform trans = drawMatrix.toTransform(); - - switch (layer.blendMode) { - case 0: - case 1: - g.setComposite(AlphaComposite.SrcOver); - break; - case 2://Layer - g.setComposite(AlphaComposite.SrcOver); - break; - case 3: - g.setComposite(BlendComposite.Multiply); - break; - case 4: - g.setComposite(BlendComposite.Screen); - break; - case 5: - g.setComposite(BlendComposite.Lighten); - break; - case 6: - g.setComposite(BlendComposite.Darken); - break; - case 7: - g.setComposite(BlendComposite.Difference); - break; - case 8: - g.setComposite(BlendComposite.Add); - break; - case 9: - g.setComposite(BlendComposite.Subtract); - break; - case 10: - g.setComposite(BlendComposite.Invert); - break; - case 11: - g.setComposite(BlendComposite.Alpha); - break; - case 12: - g.setComposite(BlendComposite.Erase); - break; - case 13: - g.setComposite(BlendComposite.Overlay); - break; - case 14: - g.setComposite(BlendComposite.HardLight); - break; - default: //Not implemented - g.setComposite(AlphaComposite.SrcOver); - break; - } - - if (layer.clipDepth > -1) { - BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); - Graphics2D gm = (Graphics2D) mask.getGraphics(); - gm.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - gm.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - gm.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - gm.setComposite(AlphaComposite.Src); - gm.setColor(new Color(0, 0, 0, 0f)); - gm.fillRect(0, 0, image.getWidth(), image.getHeight()); - gm.setTransform(trans); - gm.drawImage(img.getBufferedImage(), 0, 0, null); - Clip clip = new Clip(Helper.imageToShape(mask), layer.clipDepth); //Maybe we can get current outline instead converting from image (?) - clips.add(clip); - prevClips.add(g.getClip()); - g.setTransform(AffineTransform.getTranslateInstance(0, 0)); - g.setClip(clip.shape); - } else { - g.setTransform(trans); - g.drawImage(img.getBufferedImage(), 0, 0, null); - } - } else if (character instanceof BoundedTag) { - showPlaceholder = true; - } - - if (showPlaceholder) { - mat.translateX /= unzoom; - mat.translateY /= unzoom; - AffineTransform trans = mat.toTransform(); - g.setTransform(trans); - BoundedTag b = (BoundedTag) character; - g.setPaint(new Color(255, 255, 255, 128)); - g.setComposite(BlendComposite.Invert); - RECT r = b.getRect(); - int div = (int) unzoom; - g.drawString(character.toString(), r.Xmin / div + 3, r.Ymin / div + 15); - g.draw(new Rectangle(r.Xmin / div, r.Ymin / div, r.getWidth() / div, r.getHeight() / div)); - g.drawLine(r.Xmin / div, r.Ymin / div, r.Xmax / div, r.Ymax / div); - g.drawLine(r.Xmax / div, r.Ymin / div, r.Xmin / div, r.Ymax / div); - g.setComposite(AlphaComposite.Dst); - } - } - - g.setTransform(AffineTransform.getScaleInstance(1, 1)); - } - - private void removeTagWithDependenciesFromTimeline(Tag toRemove, Timeline timeline) { - int characterId = 0; - if (toRemove instanceof CharacterTag) { - characterId = ((CharacterTag) toRemove).getCharacterId(); - } - Map stage = new HashMap<>(); - - Set dependingChars = new HashSet<>(); - if (characterId != 0) { - dependingChars.add(characterId); - for (int i = 0; i < timeline.tags.size(); i++) { - Tag t = timeline.tags.get(i); - if (t instanceof CharacterIdTag) { - CharacterIdTag c = (CharacterIdTag) t; - Set needed = new HashSet<>(); - t.getNeededCharacters(needed); - if (needed.contains(characterId)) { - dependingChars.add(c.getCharacterId()); - } - } - } - } - - for (int i = 0; i < timeline.tags.size(); i++) { - Tag t = timeline.tags.get(i); - if (t instanceof RemoveTag) { - RemoveTag rt = (RemoveTag) t; - int depth = rt.getDepth(); - if (stage.containsKey(depth)) { - int currentCharId = stage.get(depth); - stage.remove(depth); - if (dependingChars.contains(currentCharId)) { - timeline.tags.remove(i); - i--; - continue; - } - } - } - if (t instanceof PlaceObjectTypeTag) { - PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; - int placeCharId = po.getCharacterId(); - int depth = po.getDepth(); - if (placeCharId != 0) { - stage.put(depth, placeCharId); - if (dependingChars.contains(placeCharId)) { - timeline.tags.remove(i); - i--; - continue; - } - } - } - if (t instanceof CharacterIdTag) { - CharacterIdTag c = (CharacterIdTag) t; - if (dependingChars.contains(c.getCharacterId())) { - timeline.tags.remove(i); - i--; - continue; - } - } - Set needed = new HashSet<>(); - t.getNeededCharacters(needed); - for (int dep : dependingChars) { - if (needed.contains(dep)) { - timeline.tags.remove(i); - i--; - continue; - } - } - if (t == toRemove) { - timeline.tags.remove(i); - i--; - continue; - } - if (t instanceof Timelined) { - removeTagWithDependenciesFromTimeline(toRemove, ((Timelined) t).getTimeline()); - } - } - } - - private boolean removeTagFromTimeline(Tag toRemove, Timeline timeline) { - boolean modified = false; - int characterId = -1; - if (toRemove instanceof CharacterTag) { - characterId = ((CharacterTag) toRemove).getCharacterId(); - modified = timeline.removeCharacter(characterId); - } - for (int i = 0; i < timeline.tags.size(); i++) { - Tag t = timeline.tags.get(i); - if (t == toRemove) { - timeline.tags.remove(t); - i--; - continue; - } - - if (toRemove instanceof CharacterTag) { - if (t.removeCharacter(characterId)) { - modified = true; - i = -1; - continue; - } - } - - if (t instanceof DefineSpriteTag) { - DefineSpriteTag spr = (DefineSpriteTag) t; - boolean sprModified = removeTagFromTimeline(toRemove, spr.getTimeline()); - if (sprModified) { - spr.setModified(true); - } - modified |= sprModified; - } - } - return modified; - } - - public void removeTag(Tag t, boolean removeDependencies) { - Timelined timelined = t.getTimelined(); - if (t instanceof ShowFrameTag || ShowFrameTag.isNestedTagType(t.getId())) { - List tags; - if (timelined instanceof DefineSpriteTag) { - tags = ((DefineSpriteTag) timelined).getSubTags(); - } else { - tags = this.tags; - } - tags.remove(t); - timelined.resetTimeline(); - } else { - // timeline should be always the swf here - if (removeDependencies) { - removeTagWithDependenciesFromTimeline(t, timelined.getTimeline()); - } else { - removeTagFromTimeline(t, timeline); - } - } - resetTimelines(timelined); - updateCharacters(); - clearImageCache(); - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash; + +import SevenZip.Compression.LZMA.Encoder; +import com.jpacker.JPacker; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.ClassPath; +import com.jpexs.decompiler.flash.abc.RenameType; +import com.jpexs.decompiler.flash.abc.ScriptPack; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.ActionDeobfuscation; +import com.jpexs.decompiler.flash.action.ActionGraphSource; +import com.jpexs.decompiler.flash.action.ActionLocalData; +import com.jpexs.decompiler.flash.action.model.ConstantPool; +import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; +import com.jpexs.decompiler.flash.action.model.FunctionActionItem; +import com.jpexs.decompiler.flash.action.model.GetMemberActionItem; +import com.jpexs.decompiler.flash.action.model.GetVariableActionItem; +import com.jpexs.decompiler.flash.action.model.clauses.ClassActionItem; +import com.jpexs.decompiler.flash.action.model.clauses.InterfaceActionItem; +import com.jpexs.decompiler.flash.action.swf4.ActionEquals; +import com.jpexs.decompiler.flash.action.swf4.ActionGetVariable; +import com.jpexs.decompiler.flash.action.swf4.ActionIf; +import com.jpexs.decompiler.flash.action.swf4.ActionPush; +import com.jpexs.decompiler.flash.action.swf4.ActionSetVariable; +import com.jpexs.decompiler.flash.action.swf4.ConstantIndex; +import com.jpexs.decompiler.flash.action.swf5.ActionCallFunction; +import com.jpexs.decompiler.flash.action.swf5.ActionCallMethod; +import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal2; +import com.jpexs.decompiler.flash.action.swf5.ActionEquals2; +import com.jpexs.decompiler.flash.action.swf5.ActionGetMember; +import com.jpexs.decompiler.flash.action.swf5.ActionNewMethod; +import com.jpexs.decompiler.flash.action.swf5.ActionNewObject; +import com.jpexs.decompiler.flash.action.swf5.ActionSetMember; +import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSwfNode; +import com.jpexs.decompiler.flash.ecma.Null; +import com.jpexs.decompiler.flash.exporters.BinaryDataExporter; +import com.jpexs.decompiler.flash.exporters.FontExporter; +import com.jpexs.decompiler.flash.exporters.ImageExporter; +import com.jpexs.decompiler.flash.exporters.MorphShapeExporter; +import com.jpexs.decompiler.flash.exporters.MovieExporter; +import com.jpexs.decompiler.flash.exporters.ShapeExporter; +import com.jpexs.decompiler.flash.exporters.SoundExporter; +import com.jpexs.decompiler.flash.exporters.TextExporter; +import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; +import com.jpexs.decompiler.flash.exporters.modes.FramesExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.exporters.settings.BinaryDataExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.FontExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.FramesExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.MorphShapeExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.MovieExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.SoundExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.TextExportSettings; +import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; +import com.jpexs.decompiler.flash.helpers.collections.MyEntry; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.decompiler.flash.tags.DefineButton2Tag; +import com.jpexs.decompiler.flash.tags.DefineButtonTag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.DoInitActionTag; +import com.jpexs.decompiler.flash.tags.EndTag; +import com.jpexs.decompiler.flash.tags.ExportAssetsTag; +import com.jpexs.decompiler.flash.tags.FileAttributesTag; +import com.jpexs.decompiler.flash.tags.JPEGTablesTag; +import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; +import com.jpexs.decompiler.flash.tags.ShowFrameTag; +import com.jpexs.decompiler.flash.tags.SymbolClassTag; +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.BoundedTag; +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.Container; +import com.jpexs.decompiler.flash.tags.base.ContainerItem; +import com.jpexs.decompiler.flash.tags.base.DrawableTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.tags.base.RemoveTag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.timeline.Clip; +import com.jpexs.decompiler.flash.timeline.DepthState; +import com.jpexs.decompiler.flash.timeline.Frame; +import com.jpexs.decompiler.flash.timeline.SvgClip; +import com.jpexs.decompiler.flash.timeline.Timeline; +import com.jpexs.decompiler.flash.timeline.Timelined; +import com.jpexs.decompiler.flash.treeitems.AS2PackageNodeItem; +import com.jpexs.decompiler.flash.treeitems.AS3PackageNodeItem; +import com.jpexs.decompiler.flash.treeitems.FrameNodeItem; +import com.jpexs.decompiler.flash.treeitems.SWFList; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import com.jpexs.decompiler.flash.treenodes.AS2PackageNode; +import com.jpexs.decompiler.flash.treenodes.ContainerNode; +import com.jpexs.decompiler.flash.treenodes.FrameNode; +import com.jpexs.decompiler.flash.treenodes.TagNode; +import com.jpexs.decompiler.flash.treenodes.TreeNode; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.filters.BEVELFILTER; +import com.jpexs.decompiler.flash.types.filters.BlendComposite; +import com.jpexs.decompiler.flash.types.filters.COLORMATRIXFILTER; +import com.jpexs.decompiler.flash.types.filters.CONVOLUTIONFILTER; +import com.jpexs.decompiler.flash.types.filters.DROPSHADOWFILTER; +import com.jpexs.decompiler.flash.types.filters.FILTER; +import com.jpexs.decompiler.flash.types.filters.GLOWFILTER; +import com.jpexs.decompiler.flash.types.filters.GRADIENTBEVELFILTER; +import com.jpexs.decompiler.flash.types.filters.GRADIENTGLOWFILTER; +import com.jpexs.decompiler.flash.xfl.FLAVersion; +import com.jpexs.decompiler.flash.xfl.XFLConverter; +import com.jpexs.decompiler.graph.Graph; +import com.jpexs.decompiler.graph.GraphSourceItem; +import com.jpexs.decompiler.graph.GraphSourceItemContainer; +import com.jpexs.decompiler.graph.GraphTargetItem; +import com.jpexs.decompiler.graph.model.LocalData; +import com.jpexs.helpers.Cache; +import com.jpexs.helpers.CancellableWorker; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.NulStream; +import com.jpexs.helpers.ProgressListener; +import com.jpexs.helpers.SerializableImage; +import com.jpexs.helpers.utf8.Utf8Helper; +import gnu.jpdf.PDFJob; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.print.PageFormat; +import java.awt.print.Paper; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EmptyStackException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; +import javax.imageio.ImageIO; +import javax.imageio.stream.FileImageOutputStream; +import javax.imageio.stream.ImageOutputStream; +import javax.xml.bind.DatatypeConverter; +import net.kroo.elliot.GifSequenceWriter; +import org.monte.media.VideoFormatKeys; +import org.monte.media.avi.AVIWriter; + +/** + * Class representing SWF file + * + * @author JPEXS + */ +public final class SWF implements TreeItem, Timelined { + + /** + * Default version of SWF file format + */ + public static final int DEFAULT_VERSION = 10; + /** + * Tags inside of file + */ + public List tags = new ArrayList<>(); + public boolean hasEndTag; + /** + * ExportRectangle for the display + */ + public RECT displayRect; + /** + * Movie frame rate + */ + public int frameRate; + /** + * Number of frames in movie + */ + public int frameCount; + /** + * Version of SWF + */ + public int version; + /** + * Uncompressed size of the file + */ + public long fileSize; + /** + * Used compression mode + */ + public SWFCompression compression = SWFCompression.NONE; + /** + * Compressed size of the file (LZMA) + */ + public long compressedSize; + /** + * LZMA Properties + */ + public byte[] lzmaProperties; + public byte[] uncompressedData; + public FileAttributesTag fileAttributes; + /** + * ScaleForm GFx + */ + public boolean gfx = false; + + public SWFList swfList; + public String file; + public String fileTitle; + public boolean readOnly; + public boolean isAS3; + public Map characters = new HashMap<>(); + public List abcList; + public JPEGTablesTag jtt; + public Map sourceFontsMap = new HashMap<>(); + public static final double unitDivisor = 20; + + private Timeline timeline; + + public DumpInfoSwfNode dumpInfo; + + public void updateCharacters() { + characters.clear(); + parseCharacters(new ArrayList(tags)); + } + + public void resetTimelines(Timelined timelined) { + timelined.resetTimeline(); + List tags = timelined.getTimeline().tags; + for (int i = 0; i < tags.size(); i++) { + Tag t = tags.get(i); + if (t instanceof Timelined) { + resetTimelines((Timelined) t); + } + } + } + + private void parseCharacters(List list) { + for (ContainerItem t : list) { + if (t instanceof CharacterTag) { + characters.put(((CharacterTag) t).getCharacterId(), (CharacterTag) t); + } + if (t instanceof Container) { + parseCharacters(((Container) t).getSubItems()); + } + } + } + + /** + * Unresolve recursive sprites + */ + private void checkInvalidSprites() { + for (int i = 0; i < tags.size(); i++) { + Tag t = tags.get(i); + if (t instanceof DefineSpriteTag) { + if (!isSpriteValid((DefineSpriteTag) t, new ArrayList())) { + tags.set(i, new TagStub(this, t.getId(), "InvalidSprite", t.getOriginalRange(), null)); + } + } + } + } + + private boolean isSpriteValid(DefineSpriteTag sprite, List path) { + if (path.contains(sprite.spriteId)) { + return false; + } + path.add(sprite.spriteId); + for (Tag t : sprite.subTags) { + if (t instanceof DefineSpriteTag) { + if (!isSpriteValid((DefineSpriteTag) t, path)) { + return false; + } + } + } + path.remove((Integer) sprite.spriteId); + return true; + } + + @Override + public Timeline getTimeline() { + if (timeline == null) { + timeline = new Timeline(this); + } + return timeline; + } + + @Override + public void resetTimeline() { + timeline = null; + } + + /** + * Gets all tags with specified id + * + * @param tagId Identificator of tag type + * @return List of tags + */ + public List getTagData(int tagId) { + List ret = new ArrayList<>(); + for (Tag tag : tags) { + if (tag.getId() == tagId) { + ret.add(tag); + } + } + return ret; + } + + /** + * Saves this SWF into new file + * + * @param os OutputStream to save SWF in + * @throws IOException + */ + public void saveTo(OutputStream os) throws IOException { + saveTo(os, compression); + } + + /** + * 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 { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, version); + sos.writeRECT(displayRect); + sos.writeUI8(0); + sos.writeUI8(frameRate); + sos.writeUI16(frameCount); + + sos.writeTags(tags); + if (hasEndTag) { + sos.writeUI16(0); + } + sos.close(); + if (compression == SWFCompression.LZMA) { + os.write('Z'); + } else if (compression == SWFCompression.ZLIB) { + os.write('C'); + } else { + if (gfx) { + os.write('G'); + } else { + os.write('F'); + } + } + if (gfx) { + os.write('F'); + os.write('X'); + } else { + os.write('W'); + os.write('S'); + } + os.write(version); + byte[] data = baos.toByteArray(); + sos = new SWFOutputStream(os, version); + sos.writeUI32(data.length + 8); + + if (compression == SWFCompression.LZMA) { + Encoder enc = new Encoder(); + int val = lzmaProperties[0] & 0xFF; + int lc = val % 9; + int remainder = val / 9; + int lp = remainder % 5; + int pb = remainder / 5; + int dictionarySize = 0; + for (int i = 0; i < 4; i++) { + dictionarySize += ((int) (lzmaProperties[1 + i]) & 0xFF) << (i * 8); + } + enc.SetDictionarySize(dictionarySize); + enc.SetLcLpPb(lc, lp, pb); + baos = new ByteArrayOutputStream(); + enc.SetEndMarkerMode(true); + enc.Code(new ByteArrayInputStream(data), baos, -1, -1, null); + data = baos.toByteArray(); + byte[] udata = new byte[4]; + udata[0] = (byte) (data.length & 0xFF); + udata[1] = (byte) ((data.length >> 8) & 0xFF); + udata[2] = (byte) ((data.length >> 16) & 0xFF); + udata[3] = (byte) ((data.length >> 24) & 0xFF); + os.write(udata); + os.write(lzmaProperties); + } else if (compression == SWFCompression.ZLIB) { + os = new DeflaterOutputStream(os); + } + os.write(data); + } finally { + if (os != null) { + os.close(); + } + } + } + + public void clearModified() { + for (Tag tag : tags) { + if (tag.isModified()) { + tag.createOriginalData(); + tag.setModified(false); + } + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + saveTo(baos, SWFCompression.NONE); + byte[] swfData = baos.toByteArray(); + uncompressedData = swfData; + } catch (IOException ex) { + Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "Cannot save SWF", ex); + } + } + + public SWF(InputStream is, boolean parallelRead) throws IOException, InterruptedException { + this(is, null, parallelRead); + } + + /** + * Construct SWF from stream + * + * @param is Stream to read SWF from + * @param listener + * @param parallelRead Use parallel threads? + * @throws IOException + * @throws java.lang.InterruptedException + */ + public SWF(InputStream is, ProgressListener listener, boolean parallelRead) throws IOException, InterruptedException { + this(is, listener, parallelRead, false); + } + + /** + * Faster constructor to check SWF only + * + * @param is + * @throws java.io.IOException + */ + public SWF(InputStream is) throws IOException { + decompress(is, new NulStream(), true); + } + + /** + * Construct SWF from stream + * + * @param is Stream to read SWF from + * @param listener + * @param parallelRead Use parallel threads? + * @param checkOnly Check only file validity + * @throws IOException + * @throws java.lang.InterruptedException + */ + public SWF(InputStream is, ProgressListener listener, boolean parallelRead, boolean checkOnly) throws IOException, InterruptedException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFHeader header = decompress(is, baos, true); + gfx = header.gfx; + compression = header.compression; + uncompressedData = baos.toByteArray(); + + SWFInputStream sis = new SWFInputStream(this, uncompressedData); + dumpInfo = new DumpInfoSwfNode(this, "rootswf", "", null, 0, 0); + if (Configuration.dumpInfoCollecting.get()) { + sis.dumpInfo = dumpInfo; + } + sis.readBytesEx(3, "signature"); // skip siganture + version = sis.readUI8("version"); + fileSize = sis.readUI32("fileSize"); + dumpInfo.lengthBytes = fileSize; + if (listener != null) { + sis.addPercentListener(listener); + } + sis.setPercentMax(fileSize); + displayRect = sis.readRECT("displayRect"); + // FIXED8 (16 bit fixed point) frameRate + sis.readUI8("tmpFirstByetOfFrameRate"); //tmpFirstByetOfFrameRate + frameRate = sis.readUI8("frameRate"); + frameCount = sis.readUI16("frameCount"); + List tags = sis.readTagList(this, 0, parallelRead, true, !checkOnly, gfx); + if (tags.get(tags.size() - 1).getId() == EndTag.ID) { + hasEndTag = true; + tags.remove(tags.size() - 1); + } + this.tags = tags; + if (!checkOnly) { + checkInvalidSprites(); + updateCharacters(); + assignExportNamesToSymbols(); + assignClassesToSymbols(); + findFileAttributes(); + findABCTags(); + } else { + boolean hasNonUnknownTag = false; + for (Tag tag : tags) { + if (tag.getOriginalDataLength() > 0 && Tag.getRequiredTags().contains(tag.getId())) { + hasNonUnknownTag = true; + } + } + if (!hasNonUnknownTag) { + throw new IOException("Invalid SWF file. No known tag found."); + } + } + } + + @Override + public SWF getSwf() { + return this; + } + + /** + * Get title of the file + * + * @return file title + */ + public String getFileTitle() { + if (fileTitle != null) { + return fileTitle; + } + return file; + } + + public String getShortFileName() { + String title = getFileTitle(); + if (title == null) { + return ""; + } + return new File(title).getName(); + } + + private void findABCTags() { + List objs = new ArrayList<>(); + objs.addAll(tags); + + ArrayList newAbcList = new ArrayList<>(); + getABCTags(objs, newAbcList); + this.abcList = newAbcList; + } + + private static void getABCTags(List list, List actionScripts) { + for (ContainerItem t : list) { + if (t instanceof Container) { + getABCTags(((Container) t).getSubItems(), actionScripts); + } + if (t instanceof ABCContainerTag) { + actionScripts.add((ABCContainerTag) t); + } + } + } + + private void findFileAttributes() { + for (Tag t : tags) { + if (t instanceof FileAttributesTag) { + fileAttributes = (FileAttributesTag) t; + break; + } + } + } + + private void assignExportNamesToSymbols() { + HashMap exportNames = new HashMap<>(); + for (Tag t : tags) { + if (t instanceof ExportAssetsTag) { + ExportAssetsTag eat = (ExportAssetsTag) t; + for (int i = 0; i < eat.tags.size(); i++) { + if ((!exportNames.containsKey(eat.tags.get(i))) && (!exportNames.containsValue(eat.names.get(i)))) { + exportNames.put(eat.tags.get(i), eat.names.get(i)); + } + } + } + } + for (Tag t : tags) { + if (t instanceof CharacterIdTag) { + CharacterIdTag ct = (CharacterIdTag) t; + if (exportNames.containsKey(ct.getCharacterId())) { + ct.setExportName(exportNames.get(ct.getCharacterId())); + } + } + } + } + + public void assignClassesToSymbols() { + HashMap classes = new HashMap<>(); + for (Tag t : tags) { + if (t instanceof SymbolClassTag) { + SymbolClassTag sct = (SymbolClassTag) t; + for (int i = 0; i < sct.tags.length; i++) { + if ((!classes.containsKey(sct.tags[i])) && (!classes.containsValue(sct.names[i]))) { + classes.put(sct.tags[i], sct.names[i]); + } + } + } + } + for (Tag t : tags) { + if (t instanceof CharacterTag) { + CharacterTag ct = (CharacterTag) t; + if (classes.containsKey(ct.getCharacterId())) { + ct.setClassName(classes.get(ct.getCharacterId())); + } + } + } + } + + /** + * Compress SWF file + * + * @param fis Input stream + * @param fos Output stream + * @return True on success + */ + public static boolean fws2cws(InputStream fis, OutputStream fos) { + 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(); + } catch (IOException ex) { + return false; + } + return true; + } + + public static boolean decompress(InputStream fis, OutputStream fos) { + try { + decompress(fis, fos, false); + return true; + } catch (IOException ex) { + return false; + } + } + + private static SWFHeader decompress(InputStream is, OutputStream os, boolean allowUncompressed) throws IOException { + byte[] hdr = new byte[8]; + + // SWFheader: signature, version and fileSize + if (is.read(hdr) != 8) { + throw new IOException("SWF header is too short"); + } + + String signature = new String(hdr, 0, 3, Utf8Helper.charset); + if (!Arrays.asList( + "FWS", //Uncompressed Flash + "CWS", //ZLib compressed Flash + "ZWS", //LZMA compressed Flash + "GFX", //Uncompressed ScaleForm GFx + "CFX" //Compressed ScaleForm GFx + ).contains(signature)) { + throw new IOException("Invalid SWF file"); + } + + int version = hdr[3]; + SWFInputStream sis = new SWFInputStream(null, Arrays.copyOfRange(hdr, 4, 8), 4, 4); + long fileSize = sis.readUI32("fileSize"); + SWFHeader header = new SWFHeader(); + header.version = version; + header.fileSize = fileSize; + + if (hdr[1] == 'F' && hdr[2] == 'X') { + header.gfx = true; + } + + try (SWFOutputStream sos = new SWFOutputStream(os, version)) { + sos.write(Utf8Helper.getBytes("FWS")); + sos.writeUI8(version); + sos.writeUI32(fileSize); + + switch (hdr[0]) { + case 'C': { // CWS, CFX + Helper.copyStream(new InflaterInputStream(is), os, fileSize - 8); + header.compression = SWFCompression.ZLIB; + break; + } + case 'Z': { // ZWS + 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"); + } + SevenZip.Compression.LZMA.Decoder decoder = new SevenZip.Compression.LZMA.Decoder(); + if (!decoder.SetDecoderProperties(lzmaProperties)) { + throw new IOException("LZMA:Incorrect stream properties"); + } + if (!decoder.Code(is, os, fileSize - 8)) { + throw new IOException("LZMA:Error in data stream"); + } + + header.compression = SWFCompression.LZMA; + break; + } + default: { // FWS, GFX + if (allowUncompressed) { + Helper.copyStream(is, os, fileSize - 8); + } else { + throw new IOException("SWF is not compressed"); + } + } + } + + return header; + } + } + + public static boolean renameInvalidIdentifiers(RenameType renameType, InputStream fis, OutputStream fos) { + try { + SWF swf = new SWF(fis, Configuration.parallelSpeedUp.get()); + int cnt = swf.deobfuscateIdentifiers(renameType); + swf.assignClassesToSymbols(); + System.out.println(cnt + " identifiers renamed."); + swf.saveTo(fos); + } catch (InterruptedException ex) { + return false; + } catch (IOException ex) { + return false; + } + return true; + } + + public boolean exportAS3Class(String className, String outdir, ScriptExportMode exportMode, boolean parallel) throws Exception { + List abcTags = new ArrayList<>(); + + for (Tag t : tags) { + if (t instanceof ABCContainerTag) { + ABCContainerTag cnt = (ABCContainerTag) t; + abcTags.add(cnt); + } + } + + boolean exported = false; + + for (int i = 0; i < abcTags.size(); i++) { + ABC abc = abcTags.get(i).getABC(); + List scrs = abc.findScriptPacksByPath(className); + for (ScriptPack scr : scrs) { + String cnt = ""; + if (abc.script_info.size() > 1) { + cnt = "script " + (i + 1) + "/" + abc.script_info.size() + " "; + } + String exStr = "Exporting " + "tag " + (i + 1) + "/" + abcTags.size() + " " + cnt + scr.getPath() + " ..."; + informListeners("exporting", exStr); + scr.export(outdir, abcTags, exportMode, parallel); + exStr = "Exported " + "tag " + (i + 1) + "/" + abcTags.size() + " " + cnt + scr.getPath() + " ..."; + informListeners("exported", exStr); + exported = true; + } + } + return exported; + } + + private List> uniqueAS3Packs(List> packs) { + List> ret = new ArrayList<>(); + for (MyEntry item : packs) { + for (MyEntry itemOld : ret) { + if (item.key.equals(itemOld.key)) { + Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "Duplicate pack path found (" + itemOld.key + ")!"); + break; + } + } + ret.add(item); + } + return ret; + } + + public List> getAS3Packs() { + List abcTags = new ArrayList<>(); + for (Tag t : tags) { + if (t instanceof ABCContainerTag) { + abcTags.add((ABCContainerTag) t); + } + } + List> packs = new ArrayList<>(); + for (int i = 0; i < abcTags.size(); i++) { + ABCContainerTag t = abcTags.get(i); + packs.addAll(t.getABC().getScriptPacks()); + } + return uniqueAS3Packs(packs); + } + + @Override + public RECT getRect() { + return displayRect; + } + + private class ExportPackTask implements Callable { + + ScriptPack pack; + String directory; + List abcList; + ScriptExportMode exportMode; + ClassPath path; + AtomicInteger index; + int count; + boolean parallel; + AbortRetryIgnoreHandler handler; + long startTime; + long stopTime; + + public ExportPackTask(AbortRetryIgnoreHandler handler, AtomicInteger index, int count, ClassPath path, ScriptPack pack, String directory, List abcList, ScriptExportMode exportMode, boolean parallel) { + this.pack = pack; + this.directory = directory; + this.abcList = abcList; + this.exportMode = exportMode; + this.path = path; + this.index = index; + this.count = count; + this.parallel = parallel; + this.handler = handler; + } + + @Override + public File call() throws Exception { + RunnableIOExResult rio = new RunnableIOExResult() { + @Override + public void run() throws IOException { + startTime = System.currentTimeMillis(); + this.result = pack.export(directory, abcList, exportMode, parallel); + stopTime = System.currentTimeMillis(); + } + }; + int currentIndex = index.getAndIncrement(); + synchronized (ABC.class) { + long time = stopTime - startTime; + informListeners("exporting", "Exporting script " + currentIndex + "/" + count + " " + path); + } + new RetryTask(rio, handler).run(); + synchronized (ABC.class) { + long time = stopTime - startTime; + informListeners("exported", "Exported script " + currentIndex + "/" + count + " " + path + ", " + Helper.formatTimeSec(time)); + } + return rio.result; + } + } + + public List exportActionScript2(AbortRetryIgnoreHandler handler, String outdir, ScriptExportMode exportMode, boolean parallel, EventListener evl) throws IOException { + List ret = new ArrayList<>(); + List list2 = new ArrayList<>(); + list2.addAll(tags); + List list = createASTagList(list2, null); + + TagNode.setExport(list, true); + if (!outdir.endsWith(File.separator)) { + outdir += File.separator; + } + outdir += "scripts" + File.separator; + ret.addAll(TagNode.exportNodeAS(handler, list, outdir, exportMode, evl)); + return ret; + } + + public List exportActionScript3(final AbortRetryIgnoreHandler handler, final String outdir, final ScriptExportMode exportMode, final boolean parallel) { + final AtomicInteger cnt = new AtomicInteger(1); + final List abcTags = new ArrayList<>(); + for (Tag t : tags) { + if (t instanceof ABCContainerTag) { + abcTags.add((ABCContainerTag) t); + } + } + + final List ret = new ArrayList<>(); + final List> packs = getAS3Packs(); + + if (!parallel || packs.size() < 2) { + try { + CancellableWorker.call(new Callable() { + @Override + public Void call() throws Exception { + for (MyEntry item : packs) { + ExportPackTask task = new ExportPackTask(handler, cnt, packs.size(), item.key, item.value, outdir, abcTags, exportMode, parallel); + ret.add(task.call()); + } + return null; + } + }, Configuration.exportTimeout.get(), TimeUnit.SECONDS); + } catch (TimeoutException ex) { + Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached", ex); + } catch (Exception ex) { + Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, "Error during ABC export", ex); + } + } else { + ExecutorService executor = Executors.newFixedThreadPool(Configuration.parallelThreadCount.get()); + List> futureResults = new ArrayList<>(); + for (MyEntry item : packs) { + Future future = executor.submit(new ExportPackTask(handler, cnt, packs.size(), item.key, item.value, outdir, abcTags, exportMode, parallel)); + futureResults.add(future); + } + + try { + executor.shutdown(); + if (!executor.awaitTermination(Configuration.exportTimeout.get(), TimeUnit.SECONDS)) { + Logger.getLogger(ABC.class.getName()).log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached"); + } + } catch (InterruptedException ex) { + } finally { + executor.shutdownNow(); + } + + for (int f = 0; f < futureResults.size(); f++) { + try { + if (futureResults.get(f).isDone()) { + ret.add(futureResults.get(f).get()); + } + } catch (InterruptedException ex) { + } catch (ExecutionException ex) { + Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "Error during ABC export", ex); + } + } + } + + return ret; + } + + public List exportActionScript(AbortRetryIgnoreHandler handler, String outdir, ScriptExportMode exportMode, boolean parallel) throws Exception { + boolean asV3Found = false; + List ret = new ArrayList<>(); + final EventListener evl = new EventListener() { + @Override + public void handleEvent(String event, Object data) { + if (event.equals("exporting") || event.equals("exported")) { + informListeners(event, data); + } + } + }; + for (Tag t : tags) { + if (t instanceof ABCContainerTag) { + asV3Found = true; + } + } + + if (asV3Found) { + ret.addAll(exportActionScript3(handler, outdir, exportMode, parallel)); + } else { + ret.addAll(exportActionScript2(handler, outdir, exportMode, parallel, evl)); + } + return ret; + } + + public static List createASTagList(List list, Timelined parent) { + List ret = new ArrayList<>(); + int frame = 1; + List frames = new ArrayList<>(); + + List exportAssetsTags = new ArrayList<>(); + for (ContainerItem t : list) { + if (t instanceof ExportAssetsTag) { + exportAssetsTags.add((ExportAssetsTag) t); + } + TreeNode addNode = null; + if (t instanceof ShowFrameTag) { + // do not add PlaceObjects (+etc) to script nodes + FrameNode tti = new FrameNode(new FrameNodeItem(t.getSwf(), frame, parent, false), null, true); + + for (int r = ret.size() - 1; r >= 0; r--) { + if (!(ret.get(r).getItem() instanceof DefineSpriteTag)) { + if (!(ret.get(r).getItem() instanceof DefineButtonTag)) { + if (!(ret.get(r).getItem() instanceof DefineButton2Tag)) { + if (!(ret.get(r).getItem() instanceof DoInitActionTag)) { + if (!(ret.get(r).getItem() instanceof AS2PackageNodeItem)) { + tti.subNodes.add(ret.get(r)); + ret.remove(r); + } + } + } + } + } + } + frame++; + frames.add(tti); + } else if (t instanceof ASMSource) { + ContainerNode tti = new ContainerNode(t); + //ret.add(tti); + addNode = tti; + } else if (t instanceof Container) { + if (((Container) t).getItemCount() > 0) { + + ContainerNode tti = new ContainerNode(t); + List subItems = ((Container) t).getSubItems(); + + Timelined timelined = t instanceof Timelined ? (Timelined) t : null; + tti.subNodes = createASTagList(subItems, timelined); + addNode = tti; + //ret.add(tti); + } + } + if (addNode != null) { + if (addNode.getItem() instanceof CharacterIdTag) { + CharacterIdTag cit = (CharacterIdTag) addNode.getItem(); + String path = cit.getExportName(); + if (path == null) { + path = ""; + } + String[] pathParts; + if (path.contains(".")) { + pathParts = path.split("\\."); + } else { + pathParts = new String[]{path}; + } + List items = ret; + int pos = 0; + TreeNode selNode = null; + do { + if (pos == pathParts.length - 1) { + break; + } + selNode = null; + for (TreeNode node : items) { + if (node.getItem() instanceof AS2PackageNodeItem) { + AS2PackageNodeItem pkg = (AS2PackageNodeItem) node.getItem(); + if (pkg.packageName.equals(pathParts[pos])) { + selNode = node; + break; + } + } + } + int pkgCount = 0; + for (; pkgCount < items.size(); pkgCount++) { + if (items.get(pkgCount).getItem() instanceof AS3PackageNodeItem) { + AS2PackageNodeItem pkg = (AS2PackageNodeItem) items.get(pkgCount).getItem(); + if (pkg.packageName.compareTo(pathParts[pos]) > 0) { + break; + } + } else { + break; + } + } + if (selNode == null) { + items.add(pkgCount, selNode = new AS2PackageNode(new AS2PackageNodeItem(pathParts[pos], t.getSwf()))); + } + pos++; + if (selNode != null) { + items = selNode.subNodes; + } + + } while (selNode != null); + + int clsCount = 0; + for (; clsCount < items.size(); clsCount++) { + if (items.get(clsCount).getItem() instanceof CharacterIdTag) { + CharacterIdTag ct = (CharacterIdTag) items.get(clsCount).getItem(); + String expName = ct.getExportName(); + if (expName == null) { + expName = ""; + } + if (expName.contains(".")) { + expName = expName.substring(expName.lastIndexOf('.') + 1); + } + if ((ct.getClass().getName() + "_" + expName).compareTo(addNode.getItem().getClass().getName() + "_" + pathParts[pos]) > 0) { + break; + } + } + } + items.add(clsCount, addNode); + } else { + ret.add(addNode); + } + } + + } + ret.addAll(frames); + for (int i = ret.size() - 1; i >= 0; i--) { + if (ret.get(i).getItem() instanceof DefineSpriteTag) { + ((DefineSpriteTag) ret.get(i).getItem()).exportAssetsTags = exportAssetsTags; + } + if (ret.get(i).getItem() instanceof DefineButtonTag) { + ((DefineButtonTag) ret.get(i).getItem()).exportAssetsTags = exportAssetsTags; + } + if (ret.get(i).getItem() instanceof DefineButton2Tag) { + ((DefineButton2Tag) ret.get(i).getItem()).exportAssetsTags = exportAssetsTags; + } + /*if (ret.get(i).tag instanceof DoInitActionTag) { + //((DoInitActionTag) ret.get(i).tag).exportAssetsTags = exportAssetsTags; + }*/ + if (ret.get(i).getItem() instanceof ASMSource) { + ASMSource ass = (ASMSource) ret.get(i).getItem(); + if (ass.containsSource()) { + continue; + } + } + if (ret.get(i).subNodes.isEmpty()) { + ret.remove(i); + } + } + return ret; + } + + public static void getTagsFromTreeNodes(List treeNodes, List result) { + for (TreeNode treeNode : treeNodes) { + TreeItem treeItem = treeNode.getItem(); + if (treeItem instanceof Tag) { + result.add((Tag) treeItem); + } + getTagsFromTreeNodes(treeNode.subNodes, result); + } + } + + private final HashSet listeners = new HashSet<>(); + + public final void addEventListener(EventListener listener) { + listeners.add(listener); + for (Tag t : tags) { + if (t instanceof ABCContainerTag) { + (((ABCContainerTag) t).getABC()).addEventListener(listener); + } + } + } + + public final void removeEventListener(EventListener listener) { + listeners.remove(listener); + for (Tag t : tags) { + if (t instanceof ABCContainerTag) { + (((ABCContainerTag) t).getABC()).removeEventListener(listener); + } + } + } + + protected void informListeners(String event, Object data) { + for (EventListener listener : listeners) { + listener.handleEvent(event, data); + } + } + + public static boolean hasErrorHeader(byte[] data) { + if (data.length > 4) { + if ((data[0] & 0xff) == 0xff) { + if ((data[1] & 0xff) == 0xd9) { + if ((data[2] & 0xff) == 0xff) { + if ((data[3] & 0xff) == 0xd8) { + return true; + } + } + } + } + } + return false; + } + + public static void populateVideoFrames(int streamId, List tags, HashMap output) { + for (ContainerItem 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 exportMovies(AbortRetryIgnoreHandler handler, String outdir, MovieExportSettings settings) throws IOException { + new MovieExporter().exportMovies(handler, outdir, tags, settings); + } + + public void exportSounds(AbortRetryIgnoreHandler handler, String outdir, SoundExportSettings settings) throws IOException { + new SoundExporter().exportSounds(handler, outdir, tags, settings); + } + + public void exportFonts(AbortRetryIgnoreHandler handler, String outdir, FontExportSettings settings) throws IOException { + new FontExporter().exportFonts(handler, outdir, tags, settings); + } + + private static void writeLE(OutputStream os, long val, int size) throws IOException { + for (int i = 0; i < size; i++) { + os.write((int) (val & 0xff)); + val >>= 8; + } + } + + public static void createWavFromPcmData(OutputStream fos, int soundRateHz, boolean soundSize, boolean soundType, byte[] data) throws IOException { + ByteArrayOutputStream subChunk1Data = new ByteArrayOutputStream(); + int audioFormat = 1; //PCM + writeLE(subChunk1Data, audioFormat, 2); + int numChannels = soundType ? 2 : 1; + writeLE(subChunk1Data, numChannels, 2); + int[] rateMap = {5512, 11025, 22050, 44100}; + int sampleRate = soundRateHz;//rateMap[soundRate]; + writeLE(subChunk1Data, sampleRate, 4); + int bitsPerSample = soundSize ? 16 : 8; + int byteRate = sampleRate * numChannels * bitsPerSample / 8; + writeLE(subChunk1Data, byteRate, 4); + int blockAlign = numChannels * bitsPerSample / 8; + writeLE(subChunk1Data, blockAlign, 2); + writeLE(subChunk1Data, bitsPerSample, 2); + + ByteArrayOutputStream chunks = new ByteArrayOutputStream(); + chunks.write(Utf8Helper.getBytes("fmt ")); + byte[] subChunk1DataBytes = subChunk1Data.toByteArray(); + writeLE(chunks, subChunk1DataBytes.length, 4); + chunks.write(subChunk1DataBytes); + + chunks.write(Utf8Helper.getBytes("data")); + writeLE(chunks, data.length, 4); + chunks.write(data); + + fos.write(Utf8Helper.getBytes("RIFF")); + byte[] chunkBytes = chunks.toByteArray(); + writeLE(fos, 4 + chunkBytes.length, 4); + fos.write(Utf8Helper.getBytes("WAVE")); + fos.write(chunkBytes); + } + + private static void makeAVI(Iterator images, int frameRate, File file) throws IOException { + if (!images.hasNext()) { + return; + } + AVIWriter out = new AVIWriter(file); + BufferedImage img0 = images.next(); + out.addVideoTrack(VideoFormatKeys.ENCODING_AVI_PNG, 1, frameRate, img0.getWidth(), img0.getHeight(), 0, 0); + try { + while (images.hasNext()) { + out.write(0, images.next(), 1); + } + } finally { + out.close(); + } + + } + + private static void makeGIF(Iterator images, int frameRate, File file) throws IOException { + if (!images.hasNext()) { + return; + } + try (ImageOutputStream output = new FileImageOutputStream(file)) { + BufferedImage img0 = images.next(); + GifSequenceWriter writer = new GifSequenceWriter(output, img0.getType(), 1000 / frameRate, true); + writer.writeToSequence(img0); + + while (images.hasNext()) { + writer.writeToSequence(images.next()); + } + + writer.close(); + } + } + + private static String getTypePrefix(CharacterTag c) { + if (c instanceof ShapeTag) { + return "shape"; + } + if (c instanceof MorphShapeTag) { + return "morphshape"; + } + if (c instanceof DefineSpriteTag) { + return "sprite"; + } + if (c instanceof TextTag) { + return "text"; + } + if (c instanceof ButtonTag) { + return "button"; + } + if (c instanceof FontTag) { + return "font"; + } + if (c instanceof ImageTag) { + return "image"; + } + return "character"; + } + + public static void writeLibrary(SWF fswf, Set library, OutputStream fos) throws IOException { + for (int c : library) { + CharacterTag ch = fswf.characters.get(c); + if (ch instanceof FontTag) { + fos.write(Utf8Helper.getBytes("function " + getTypePrefix(ch) + c + "(ctx,ch,textColor){\r\n")); + fos.write(Utf8Helper.getBytes(((FontTag) ch).toHtmlCanvas(1))); + fos.write(Utf8Helper.getBytes("}\r\n\r\n")); + } else if (ch instanceof ImageTag) { + ImageTag image = (ImageTag) ch; + String format = image.getImageFormat(); + InputStream imageStream = image.getImageData(); + byte[] imageData; + if (imageStream != null) { + imageData = Helper.readStream(image.getImageData()); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(image.getImage().getBufferedImage(), format.toUpperCase(Locale.ENGLISH), baos); + } catch (IOException ex) { + } + imageData = baos.toByteArray(); + } + String base64ImgData = DatatypeConverter.printBase64Binary(imageData); + fos.write(Utf8Helper.getBytes("var image" + c + " = document.createElement(\"img\");\r\nimage" + c + ".src=\"data:image/" + format + ";base64," + base64ImgData + "\";\r\n")); + } else { + fos.write(Utf8Helper.getBytes("function " + getTypePrefix(ch) + c + "(ctx,ctrans,frame,ratio,time){\r\n")); + if (ch instanceof DrawableTag) { + fos.write(Utf8Helper.getBytes(((DrawableTag) ch).toHtmlCanvas(1))); + } + fos.write(Utf8Helper.getBytes("}\r\n\r\n")); + } + } + } + + public List exportFrames(AbortRetryIgnoreHandler handler, String outdir, int containerId, List frames, final FramesExportSettings settings) throws IOException { + final List ret = new ArrayList<>(); + if (tags.isEmpty()) { + return ret; + } + Timeline tim = null; + String path = ""; + if (containerId == 0) { + tim = getTimeline(); + } else { + tim = ((Timelined) characters.get(containerId)).getTimeline(); + path = File.separator + Helper.makeFileName(characters.get(containerId).getExportFileName()); + } + if (frames == null) { + int frameCnt = tim.frames.size(); + frames = new ArrayList<>(); + for (int i = 0; i < frameCnt; i++) { + frames.add(i); + } + } + + final File foutdir = new File(outdir + path); + if (!foutdir.exists()) { + if (!foutdir.mkdirs()) { + if (!foutdir.exists()) { + throw new IOException("Cannot create directory " + outdir); + } + } + } + + final List fframes = frames; + + Color backgroundColor = null; + if (settings.mode == FramesExportMode.AVI) { + for (Tag t : tags) { + if (t instanceof SetBackgroundColorTag) { + SetBackgroundColorTag sb = (SetBackgroundColorTag) t; + backgroundColor = sb.backgroundColor.toColor(); + } + } + } + + if (settings.mode == FramesExportMode.SVG) { + for (int i = 0; i < frames.size(); i++) { + final int fi = i; + final Timeline ftim = tim; + final Color fbackgroundColor = backgroundColor; + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + int frame = fframes.get(fi); + File f = new File(foutdir + File.separator + frame + ".svg"); + try (FileOutputStream fos = new FileOutputStream(f)) { + SVGExporter exporter = new SVGExporter(new ExportRectangle(ftim.displayRect)); + if (fbackgroundColor != null) { + exporter.setBackGroundColor(fbackgroundColor); + } + frameToSvg(ftim, frame, 0, null, 0, exporter, new ColorTransform(), 0); + fos.write(Utf8Helper.getBytes(exporter.getSVG())); + } + ret.add(f); + } + }, handler).run(); + } + return ret; + } + + if (settings.mode == FramesExportMode.CANVAS) { + final Timeline ftim = tim; + final Color fbackgroundColor = backgroundColor; + final SWF fswf = this; + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + File fcanvas = new File(foutdir + File.separator + "canvas.js"); + Helper.saveStream(SWF.class.getClassLoader().getResourceAsStream("com/jpexs/helpers/resource/canvas.js"), fcanvas); + ret.add(fcanvas); + + File f = new File(foutdir + File.separator + "frames.js"); + File fmin = new File(foutdir + File.separator + "frames.min.js"); + int width = (int) (ftim.displayRect.getWidth() / SWF.unitDivisor); + int height = (int) (ftim.displayRect.getHeight() / SWF.unitDivisor); + try (FileOutputStream fos = new FileOutputStream(f)) { + fos.write(Utf8Helper.getBytes("\r\n")); + Set library = new HashSet<>(); + ftim.getNeededCharacters(fframes, library); + + writeLibrary(fswf, library, fos); + + String currentName = ftim.id == 0 ? "main" : getTypePrefix(fswf.characters.get(ftim.id)) + ftim.id; + + fos.write(Utf8Helper.getBytes("function " + currentName + "(ctx,ctrans,frame,ratio,time){\r\n")); + fos.write(Utf8Helper.getBytes("\tctx.save();\r\n")); + fos.write(Utf8Helper.getBytes("\tctx.transform(1,0,0,1," + (-ftim.displayRect.Xmin / unitDivisor) + "," + (-ftim.displayRect.Ymin / unitDivisor) + ");\r\n")); + fos.write(Utf8Helper.getBytes(framesToHtmlCanvas(unitDivisor, ftim, fframes, 0, null, 0, ftim.displayRect, new ColorTransform(), fbackgroundColor))); + fos.write(Utf8Helper.getBytes("\tctx.restore();\r\n")); + fos.write(Utf8Helper.getBytes("}\r\n\r\n")); + + fos.write(Utf8Helper.getBytes("var frame = -1;\r\n")); + fos.write(Utf8Helper.getBytes("var time = 0;\r\n")); + fos.write(Utf8Helper.getBytes("var frames = [];\r\n")); + for (int i : fframes) { + fos.write(Utf8Helper.getBytes("frames.push(" + i + ");\r\n")); + } + fos.write(Utf8Helper.getBytes("\r\n")); + RGB backgroundColor = new RGB(255, 255, 255); + for (Tag t : fswf.tags) { + if (t instanceof SetBackgroundColorTag) { + SetBackgroundColorTag sb = (SetBackgroundColorTag) t; + backgroundColor = sb.backgroundColor; + } + } + + fos.write(Utf8Helper.getBytes("var backgroundColor = \"" + backgroundColor.toHexRGB() + "\";\r\n")); + fos.write(Utf8Helper.getBytes("var originalWidth = " + width + ";\r\n")); + fos.write(Utf8Helper.getBytes("var originalHeight= " + height + ";\r\n")); + fos.write(Utf8Helper.getBytes("function nextFrame(ctx,ctrans){\r\n")); + fos.write(Utf8Helper.getBytes("\tvar oldframe = frame;\r\n")); + fos.write(Utf8Helper.getBytes("\tframe = (frame+1)%frames.length;\r\n")); + fos.write(Utf8Helper.getBytes("\tif(frame==oldframe){time++;}else{time=0;};\r\n")); + fos.write(Utf8Helper.getBytes("\tdrawFrame();\r\n")); + fos.write(Utf8Helper.getBytes("}\r\n\r\n")); + + fos.write(Utf8Helper.getBytes("function drawFrame(){\r\n")); + fos.write(Utf8Helper.getBytes("\tctx.fillStyle = backgroundColor;\r\n")); + fos.write(Utf8Helper.getBytes("\tctx.fillRect(0,0,canvas.width,canvas.height);\r\n")); + fos.write(Utf8Helper.getBytes("\tctx.save();\r\n")); + fos.write(Utf8Helper.getBytes("\tctx.transform(canvas.width/originalWidth,0,0,canvas.height/originalHeight,0,0);\r\n")); + fos.write(Utf8Helper.getBytes("\t" + currentName + "(ctx,ctrans,frames[frame],0,time);\r\n")); + fos.write(Utf8Helper.getBytes("\tctx.restore();\r\n")); + fos.write(Utf8Helper.getBytes("}\r\n\r\n")); + fos.write(Utf8Helper.getBytes("window.setInterval(function(){nextFrame(ctx,ctrans);}," + (int) (1000.0 / ftim.swf.frameRate) + ");\r\n")); + } + + if (Configuration.packJavaScripts.get()) { + try { + JPacker.main(new String[]{"-q", "-b", "62", "-o", fmin.getAbsolutePath(), f.getAbsolutePath()}); + f.delete(); + } catch (Exception | Error e) { //Something wrong in the packer + Logger.getLogger(SWF.class.getName()).log(Level.WARNING, "JPacker: Cannot minimize script"); + f.renameTo(fmin); + } + } else { + f.renameTo(fmin); + } + + File fh = new File(foutdir + File.separator + "frames.html"); + try (FileOutputStream fos = new FileOutputStream(fh); FileInputStream fis = new FileInputStream(fmin)) { + fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getHtmlPrefix(width, height))); + fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getJsPrefix())); + byte buf[] = new byte[1000]; + int cnt; + while ((cnt = fis.read(buf)) > 0) { + fos.write(buf, 0, cnt); + } + if (Configuration.packJavaScripts.get()) { + fos.write(Utf8Helper.getBytes(";")); + } + fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getJsSuffix())); + fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getHtmlSuffix())); + } + fmin.delete(); + + ret.add(f); + } + }, handler).run(); + + return ret; + } + + final Timeline ftim = tim; + final Color fbackgroundColor = backgroundColor; + final Iterator frameImages = new Iterator() { + + private int pos = 0; + + @Override + public boolean hasNext() { + return fframes.size() > pos; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public BufferedImage next() { + if (!hasNext()) { + return null; + } + return frameToImageGet(ftim, fframes.get(pos++), 0, null, 0, ftim.displayRect, new Matrix(), new ColorTransform(), fbackgroundColor, false).getBufferedImage(); + } + }; + + switch (settings.mode) { + case GIF: + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + File f = new File(foutdir + File.separator + "frames.gif"); + makeGIF(frameImages, frameRate, f); + ret.add(f); + } + }, handler).run(); + break; + case PNG: + for (int i = 0; frameImages.hasNext(); i++) { + final int fi = i; + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + File f = new File(foutdir + File.separator + (fframes.get(fi)+1) + ".png"); + ImageIO.write(frameImages.next(), "PNG", f); + ret.add(f); + } + }, handler).run(); + } + break; + case PDF: + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + File f = new File(foutdir + File.separator + "frames.pdf"); + PDFJob job = new PDFJob(new FileOutputStream(f)); + PageFormat pf = new PageFormat(); + pf.setOrientation(PageFormat.PORTRAIT); + Paper p = new Paper(); + BufferedImage img0 = frameImages.next(); + p.setSize(img0.getWidth() + 10, img0.getHeight() + 10); + pf.setPaper(p); + + for (int i = 0; frameImages.hasNext(); i++) { + BufferedImage img = frameImages.next(); + Graphics g = job.getGraphics(pf); + g.drawImage(img, 5, 5, img.getWidth(), img.getHeight(), null); + g.dispose(); + } + + job.end(); + ret.add(f); + } + }, handler).run(); + break; + case AVI: + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + File f = new File(foutdir + File.separator + "frames.avi"); + makeAVI(frameImages, frameRate, f); + ret.add(f); + } + }, handler).run(); + break; + } + + return ret; + } + + public void exportTexts(AbortRetryIgnoreHandler handler, String outdir, TextExportSettings settings) throws IOException { + new TextExporter().exportTexts(handler, outdir, tags, settings); + } + + public void exportImages(AbortRetryIgnoreHandler handler, String outdir, ImageExportSettings settings) throws IOException { + new ImageExporter().exportImages(handler, outdir, tags, settings); + } + + public void exportShapes(AbortRetryIgnoreHandler handler, String outdir, ShapeExportSettings settings) throws IOException { + new ShapeExporter().exportShapes(handler, outdir, tags, settings); + } + + public void exportMorphShapes(AbortRetryIgnoreHandler handler, String outdir, MorphShapeExportSettings settings) throws IOException { + new MorphShapeExporter().exportMorphShapes(handler, outdir, tags, settings); + } + + public void exportBinaryData(AbortRetryIgnoreHandler handler, String outdir, BinaryDataExportSettings settings) throws IOException { + new BinaryDataExporter().exportBinaryData(handler, outdir, tags, settings); + } + + private final HashMap deobfuscated = new HashMap<>(); + private List> allVariableNames = new ArrayList<>(); + private List allFunctions = new ArrayList<>(); + private HashMap allStrings = new HashMap<>(); + private final HashMap usageTypes = new HashMap<>(); + private final ActionDeobfuscation deobfuscation = new ActionDeobfuscation(); + + private static void getVariables(ConstantPool constantPool, BaseLocalData localData, Stack stack, List output, ActionGraphSource code, int ip, List> variables, List functions, HashMap strings, List visited, HashMap usageTypes, String path) throws InterruptedException { + boolean debugMode = false; + while ((ip > -1) && ip < code.size()) { + if (visited.contains(ip)) { + break; + } + GraphSourceItem ins = code.get(ip); + + if (debugMode) { + System.err.println("Visit " + ip + ": ofs" + Helper.formatAddress(((Action) ins).getAddress()) + ":" + ((Action) ins).getASMSource(new ArrayList(), new ArrayList(), new ArrayList(), code.version, ScriptExportMode.PCODE) + " stack:" + Helper.stackToString(stack, LocalData.create(new ConstantPool()))); + } + if (ins.isExit()) { + break; + } + if (ins.isIgnored()) { + ip++; + continue; + } + + String usageType = "name"; + GraphTargetItem name = null; + if ((ins instanceof ActionGetVariable) + || (ins instanceof ActionGetMember) + || (ins instanceof ActionDefineLocal2) + || (ins instanceof ActionNewMethod) + || (ins instanceof ActionNewObject) + || (ins instanceof ActionCallMethod) + || (ins instanceof ActionCallFunction)) { + if (stack.isEmpty()) { + break; + } + name = stack.peek(); + } + + if ((ins instanceof ActionGetVariable) || (ins instanceof ActionDefineLocal2)) { + usageType = "variable"; + } + if (ins instanceof ActionGetMember) { + usageType = "member"; + } + if ((ins instanceof ActionNewMethod) || (ins instanceof ActionNewObject)) { + usageType = "class"; + } + if (ins instanceof ActionCallMethod) { + usageType = "function"; //can there be method? + } + if (ins instanceof ActionCallFunction) { + usageType = "function"; + } + + if ((ins instanceof ActionDefineFunction) || (ins instanceof ActionDefineFunction2)) { + functions.add(ins); + } + + if (ins instanceof GraphSourceItemContainer) { + GraphSourceItemContainer cnt = (GraphSourceItemContainer) ins; + List cntSizes = cnt.getContainerSizes(); + long addr = code.pos2adr(ip + 1); + ip = code.adr2pos(addr); + String cntName = cnt.getName(); + for (Long size : cntSizes) { + if (size == 0) { + continue; + } + ip = code.adr2pos(addr); + addr += size; + int nextip = code.adr2pos(addr); + getVariables(variables, functions, strings, usageTypes, new ActionGraphSource(code.getActions().subList(ip, nextip), code.version, new HashMap(), new HashMap(), new HashMap()), 0, path + (cntName == null ? "" : "/" + cntName)); + ip = nextip; + } + List> r = new ArrayList<>(); + r.add(new ArrayList()); + r.add(new ArrayList()); + r.add(new ArrayList()); + try { + ((GraphSourceItemContainer) ins).translateContainer(r, stack, output, new HashMap(), new HashMap(), new HashMap()); + } catch (EmptyStackException ex) { + } + //ip++; + continue; + } + + if ((ins instanceof ActionSetVariable) || (ins instanceof ActionSetMember) || (ins instanceof ActionDefineLocal)) { + if (stack.size() < 2) { + break; + } + name = stack.get(stack.size() - 2); + } + + if ((ins instanceof ActionSetVariable) || (ins instanceof ActionDefineLocal)) { + usageType = "variable"; + } + + if (ins instanceof ActionSetMember) { + usageType = "member"; + } + + if (name instanceof DirectValueActionItem) { + variables.add(new MyEntry<>((DirectValueActionItem) name, constantPool)); + usageTypes.put((DirectValueActionItem) name, usageType); + } + + //for..in return + if (((ins instanceof ActionEquals) || (ins instanceof ActionEquals2)) && (stack.size() == 1) && (stack.peek() instanceof DirectValueActionItem)) { + stack.push(new DirectValueActionItem(null, 0, new Null(), new ArrayList())); + } + + if (ins instanceof ActionConstantPool) { + constantPool = new ConstantPool(((ActionConstantPool) ins).constantPool); + } + int staticOperation = Graph.SOP_USE_STATIC; //(Boolean) Configuration.getConfig("autoDeobfuscate", true) ? Graph.SOP_SKIP_STATIC : Graph.SOP_USE_STATIC; + + try { + ins.translate(localData, stack, output, staticOperation, path); + } catch (EmptyStackException ex) { + // probably obfucated code, never executed branch + break; + } + if (ins.isExit()) { + break; + } + + if (ins instanceof ActionPush) { + if (!stack.isEmpty()) { + GraphTargetItem top = stack.peek(); + if (top instanceof DirectValueActionItem) { + DirectValueActionItem dvt = (DirectValueActionItem) top; + if ((dvt.value instanceof String) || (dvt.value instanceof ConstantIndex)) { + if (constantPool == null) { + constantPool = new ConstantPool(dvt.constants); + } + strings.put(dvt, constantPool); + } + } + } + } + + if (ins.isBranch() || ins.isJump()) { + if (ins instanceof ActionIf) { + if (stack.isEmpty()) { + break; + } + stack.pop(); + } + visited.add(ip); + List branches = ins.getBranches(code); + for (int b : branches) { + @SuppressWarnings("unchecked") + Stack brStack = (Stack) stack.clone(); + if (b >= 0) { + getVariables(constantPool, localData, brStack, output, code, b, variables, functions, strings, visited, usageTypes, path); + } else { + if (debugMode) { + System.out.println("Negative branch:" + b); + } + } + } + // } + break; + } + ip++; + }; + } + + private static void getVariables(List> variables, List functions, HashMap strings, HashMap usageType, ActionGraphSource code, int addr, String path) throws InterruptedException { + ActionLocalData localData = new ActionLocalData(); + getVariables(null, localData, new Stack(), new ArrayList(), code, code.adr2pos(addr), variables, functions, strings, new ArrayList(), usageType, path); + } + + private List> getVariables(List> variables, List functions, HashMap strings, HashMap usageType, ASMSource src, String path) throws InterruptedException { + List> ret = new ArrayList<>(); + List actions = src.getActions(); + actionsMap.put(src, actions); + getVariables(variables, functions, strings, usageType, new ActionGraphSource(actions, version, new HashMap(), new HashMap(), new HashMap()), 0, path); + return ret; + } + private HashMap> actionsMap = new HashMap<>(); + + private void getVariables(List objs, String path) throws InterruptedException { + List processed = new ArrayList<>(); + for (ContainerItem o : objs) { + if (o instanceof ASMSource) { + String infPath = path + "/" + o.toString(); + int pos = 1; + String infPath2 = infPath; + while (processed.contains(infPath2)) { + pos++; + infPath2 = infPath + "[" + pos + "]"; + } + processed.add(infPath2); + informListeners("getVariables", infPath2); + getVariables(allVariableNames, allFunctions, allStrings, usageTypes, (ASMSource) o, path); + } + if (o instanceof Container) { + getVariables(((Container) o).getSubItems(), path + "/" + o.toString()); + } + } + } + + public int deobfuscateAS3Identifiers(RenameType renameType) { + for (Tag tag : tags) { + if (tag instanceof ABCContainerTag) { + ((ABCContainerTag) tag).getABC().deobfuscateIdentifiers(deobfuscated, renameType, true); + tag.setModified(true); + } + } + for (Tag tag : tags) { + if (tag instanceof ABCContainerTag) { + ((ABCContainerTag) tag).getABC().deobfuscateIdentifiers(deobfuscated, renameType, false); + tag.setModified(true); + } + } + for (Tag tag : tags) { + if (tag instanceof SymbolClassTag) { + SymbolClassTag sc = (SymbolClassTag) tag; + for (int i = 0; i < sc.names.length; i++) { + String newname = deobfuscation.deobfuscateNameWithPackage(sc.names[i], deobfuscated, renameType, deobfuscated); + if (newname != null) { + sc.names[i] = newname; + } + } + sc.setModified(true); + } + } + deobfuscation.deobfuscateInstanceNames(deobfuscated, renameType, tags, new HashMap()); + return deobfuscated.size(); + } + + public int deobfuscateIdentifiers(RenameType renameType) throws InterruptedException { + findFileAttributes(); + if (fileAttributes == null) { + int cnt = 0; + cnt += deobfuscateAS2Identifiers(renameType); + cnt += deobfuscateAS3Identifiers(renameType); + return cnt; + } else { + if (fileAttributes.actionScript3) { + return deobfuscateAS3Identifiers(renameType); + } else { + return deobfuscateAS2Identifiers(renameType); + } + } + } + + public void renameAS2Identifier(String identifier, String newname) throws InterruptedException { + Map selected = new HashMap<>(); + selected.put(identifier, newname); + renameAS2Identifiers(null, selected); + } + + private int deobfuscateAS2Identifiers(RenameType renameType) throws InterruptedException { + return renameAS2Identifiers(renameType, null); + } + + private int renameAS2Identifiers(RenameType renameType, Map selected) throws InterruptedException { + actionsMap = new HashMap<>(); + allFunctions = new ArrayList<>(); + allVariableNames = new ArrayList<>(); + allStrings = new HashMap<>(); + + List objs = new ArrayList<>(); + int ret = 0; + objs.addAll(tags); + getVariables(objs, ""); + informListeners("rename", ""); + int fc = 0; + for (MyEntry it : allVariableNames) { + String name = it.key.toStringNoH(it.value); + deobfuscation.allVariableNamesStr.add(name); + } + + informListeners("rename", "classes"); + int classCount = 0; + for (Tag t : tags) { + if (t instanceof DoInitActionTag) { + classCount++; + } + } + int cnt = 0; + for (Tag t : tags) { + if (t instanceof DoInitActionTag) { + cnt++; + informListeners("rename", "class " + cnt + "/" + classCount); + DoInitActionTag dia = (DoInitActionTag) t; + String exportName = dia.getExportName(); + final String pkgPrefix = "__Packages."; + String[] classNameParts = null; + if ((exportName != null) && exportName.startsWith(pkgPrefix)) { + String className = exportName.substring(pkgPrefix.length()); + if (className.contains(".")) { + classNameParts = className.split("\\."); + } else { + classNameParts = new String[]{className}; + } + } + int staticOperation = Graph.SOP_USE_STATIC; //(Boolean) Configuration.getConfig("autoDeobfuscate", true) ? Graph.SOP_SKIP_STATIC : Graph.SOP_USE_STATIC; + List dec; + try { + dec = Action.actionsToTree(dia.getActions(), version, staticOperation, ""/*FIXME*/); + } catch (EmptyStackException ex) { + continue; + } + GraphTargetItem name = null; + for (GraphTargetItem it : dec) { + if (it instanceof ClassActionItem) { + ClassActionItem cti = (ClassActionItem) it; + List methods = new ArrayList<>(); + methods.addAll(cti.functions); + methods.addAll(cti.staticFunctions); + + for (GraphTargetItem gti : methods) { + if (gti instanceof FunctionActionItem) { + FunctionActionItem fun = (FunctionActionItem) gti; + if (fun.calculatedFunctionName instanceof DirectValueActionItem) { + DirectValueActionItem dvf = (DirectValueActionItem) fun.calculatedFunctionName; + String fname = dvf.toStringNoH(null); + String changed = deobfuscation.deobfuscateName(fname, false, "method", deobfuscated, renameType, selected); + if (changed != null) { + deobfuscated.put(fname, changed); + } + } + } + } + + List vars = new ArrayList<>(); + for (MyEntry item : cti.vars) { + vars.add(item.key); + } + for (MyEntry item : cti.staticVars) { + vars.add(item.key); + } + for (GraphTargetItem gti : vars) { + if (gti instanceof DirectValueActionItem) { + DirectValueActionItem dvf = (DirectValueActionItem) gti; + String vname = dvf.toStringNoH(null); + String changed = deobfuscation.deobfuscateName(vname, false, "attribute", deobfuscated, renameType, selected); + if (changed != null) { + deobfuscated.put(vname, changed); + } + } + } + + name = cti.className; + break; + } + if (it instanceof InterfaceActionItem) { + InterfaceActionItem ift = (InterfaceActionItem) it; + name = ift.name; + } + } + + if (name != null) { + int pos = 0; + while (name instanceof GetMemberActionItem) { + GetMemberActionItem mem = (GetMemberActionItem) name; + GraphTargetItem memberName = mem.memberName; + if (memberName instanceof DirectValueActionItem) { + DirectValueActionItem dvt = (DirectValueActionItem) memberName; + String nameStr = dvt.toStringNoH(null); + if (classNameParts != null) { + if (classNameParts.length - 1 - pos < 0) { + break; + } + } + String changedNameStr = nameStr; + if (classNameParts != null) { + changedNameStr = classNameParts[classNameParts.length - 1 - pos]; + } + String changedNameStr2 = deobfuscation.deobfuscateName(changedNameStr, pos == 0, pos == 0 ? "class" : "package", deobfuscated, renameType, selected); + if (changedNameStr2 != null) { + changedNameStr = changedNameStr2; + } + ret++; + deobfuscated.put(nameStr, changedNameStr); + pos++; + } + name = mem.object; + } + if (name instanceof GetVariableActionItem) { + GetVariableActionItem var = (GetVariableActionItem) name; + if (var.name instanceof DirectValueActionItem) { + DirectValueActionItem dvt = (DirectValueActionItem) var.name; + String nameStr = dvt.toStringNoH(null); + if (classNameParts != null) { + if (classNameParts.length - 1 - pos < 0) { + break; + } + } + String changedNameStr = nameStr; + if (classNameParts != null) { + changedNameStr = classNameParts[classNameParts.length - 1 - pos]; + } + String changedNameStr2 = deobfuscation.deobfuscateName(changedNameStr, pos == 0, pos == 0 ? "class" : "package", deobfuscated, renameType, selected); + if (changedNameStr2 != null) { + changedNameStr = changedNameStr2; + } + ret++; + deobfuscated.put(nameStr, changedNameStr); + pos++; + } + } + } + t.setModified(true); + } + } + + for (GraphSourceItem fun : allFunctions) { + fc++; + informListeners("rename", "function " + fc + "/" + allFunctions.size()); + if (fun instanceof ActionDefineFunction) { + ActionDefineFunction f = (ActionDefineFunction) fun; + if (f.functionName.isEmpty()) { //anonymous function, leave as is + continue; + } + String changed = deobfuscation.deobfuscateName(f.functionName, false, "function", deobfuscated, renameType, selected); + if (changed != null) { + f.replacedFunctionName = changed; + ret++; + } + } + if (fun instanceof ActionDefineFunction2) { + ActionDefineFunction2 f = (ActionDefineFunction2) fun; + if (f.functionName.isEmpty()) { //anonymous function, leave as is + continue; + } + String changed = deobfuscation.deobfuscateName(f.functionName, false, "function", deobfuscated, renameType, selected); + if (changed != null) { + f.replacedFunctionName = changed; + ret++; + } + } + } + + HashSet stringsNoVarH = new HashSet<>(); + List allVariableNamesDv = new ArrayList<>(); + for (MyEntry it : allVariableNames) { + allVariableNamesDv.add(it.key); + } + for (DirectValueActionItem ti : allStrings.keySet()) { + if (!allVariableNamesDv.contains(ti)) { + stringsNoVarH.add(System.identityHashCode(allStrings.get(ti)) + "_" + ti.toStringNoH(allStrings.get(ti))); + } + } + + int vc = 0; + for (MyEntry it : allVariableNames) { + vc++; + String name = it.key.toStringNoH(it.value); + String changed = deobfuscation.deobfuscateName(name, false, usageTypes.get(it.key), deobfuscated, renameType, selected); + if (changed != null) { + boolean addNew = false; + String h = System.identityHashCode(it.key) + "_" + name; + if (stringsNoVarH.contains(h)) { + addNew = true; + } + ActionPush pu = (ActionPush) it.key.src; + if (pu.replacement == null) { + pu.replacement = new ArrayList<>(); + pu.replacement.addAll(pu.values); + } + if (pu.replacement.get(it.key.pos) instanceof ConstantIndex) { + ConstantIndex ci = (ConstantIndex) pu.replacement.get(it.key.pos); + ConstantPool pool = it.value; + if (pool == null) { + continue; + } + if (pool.constants == null) { + continue; + } + if (addNew) { + pool.constants.add(changed); + ci.index = pool.constants.size() - 1; + } else { + pool.constants.set(ci.index, changed); + } + } else { + pu.replacement.set(it.key.pos, changed); + } + ret++; + } + } + for (ASMSource src : actionsMap.keySet()) { + actionsMap.put(src, Action.removeNops(0, actionsMap.get(src), version, ""/*FIXME path*/)); + src.setActions(actionsMap.get(src)); + src.setModified(); + } + deobfuscation.deobfuscateInstanceNames(deobfuscated, renameType, tags, selected); + return ret; + } + + public void exportFla(AbortRetryIgnoreHandler handler, String outfile, String swfName, String generator, String generatorVerName, String generatorVersion, boolean parallel, FLAVersion version) throws IOException { + XFLConverter.convertSWF(handler, this, swfName, outfile, true, generator, generatorVerName, generatorVersion, parallel, version); + } + + public void exportXfl(AbortRetryIgnoreHandler handler, String outfile, String swfName, String generator, String generatorVerName, String generatorVersion, boolean parallel, FLAVersion version) throws IOException { + XFLConverter.convertSWF(handler, this, swfName, outfile, false, generator, generatorVerName, generatorVersion, parallel, version); + } + + public static AffineTransform matrixToTransform(MATRIX mat) { + return new AffineTransform(mat.getScaleXFloat(), mat.getRotateSkew0Float(), + mat.getRotateSkew1Float(), mat.getScaleYFloat(), + mat.translateX, mat.translateY); + } + + private static Cache frameCache = Cache.getInstance(false); + + public static SerializableImage getFromCache(String key) { + if (frameCache.contains(key)) { + return (SerializableImage) SWF.frameCache.get(key); + } + return null; + } + + public static void putToCache(String key, SerializableImage img) { + if (Configuration.useFrameCache.get()) { + frameCache.put(key, img); + } + } + + public static void clearImageCache() { + frameCache.clear(); + } + + public static RECT fixRect(RECT rect) { + RECT ret = new RECT(); + ret.Xmin = rect.Xmin; + ret.Xmax = rect.Xmax; + ret.Ymin = rect.Ymin; + ret.Ymax = rect.Ymax; + + if (ret.Xmax <= 0) { + ret.Xmax = ret.getWidth(); + ret.Xmin = 0; + } + if (ret.Ymax <= 0) { + ret.Ymax = ret.getHeight(); + ret.Ymin = 0; + } + if (ret.Xmin < 0) { + ret.Xmax += (-ret.Xmin); + ret.Xmin = 0; + } + if (ret.Ymin < 0) { + ret.Ymax += (-ret.Ymin); + ret.Ymin = 0; + } + + if (ret.getWidth() < 1 || ret.getHeight() < 1) { + ret.Xmin = 0; + ret.Ymin = 0; + ret.Xmax = 20; + ret.Ymax = 20; + } + return ret; + } + + private static String jsArrColor(RGB rgb) { + return "[" + rgb.red + "," + rgb.green + "," + rgb.blue + "," + ((rgb instanceof RGBA) ? ((RGBA) rgb).getAlphaFloat() : 1) + "]"; + } + + public static String framesToHtmlCanvas(double unitDivisor, Timeline timeline, List frames, int time, DepthState stateUnderCursor, int mouseButton, RECT displayRect, ColorTransform colorTransform, Color backGroundColor) { + StringBuilder sb = new StringBuilder(); + if (frames == null) { + frames = new ArrayList<>(); + for (int i = 0; i < timeline.getFrameCount(); i++) { + frames.add(i); + } + } + sb.append("\tvar clips = [];\r\n"); + sb.append("\tvar frame_cnt = ").append(timeline.getFrameCount()).append(";\r\n"); + sb.append("\tframe = frame % frame_cnt;\r\n"); + sb.append("\tswitch(frame){\r\n"); + int maxDepth = timeline.getMaxDepth(); + Stack clipDepths = new Stack<>(); + for (int frame = 0; frame < frames.size(); frame++) { + sb.append("\t\tcase ").append(frame).append(":\r\n"); + Frame frameObj = timeline.frames.get(frame); + for (int i = 1; i <= maxDepth + 1; i++) { + while (!clipDepths.isEmpty() && clipDepths.peek() <= i) { + clipDepths.pop(); + sb.append("\t\t\tvar o = clips.pop();\r\n"); + sb.append("\t\t\tctx.globalCompositeOperation = \"destination-in\";\r\n"); + sb.append("\t\t\tctx.setTransform(1,0,0,1,0,0);\r\n"); + sb.append("\t\t\tctx.drawImage(o.clipCanvas,0,0);\r\n"); + sb.append("\t\t\tvar ms=o.ctx._matrices.slice();\r\n"); + sb.append("\t\t\to.ctx.setTransform(1,0,0,1,0,0);\r\n"); + sb.append("\t\t\to.ctx.globalCompositeOperation = \"source-over\";\r\n"); + sb.append("\t\t\to.ctx.drawImage(canvas,0,0);\r\n"); + sb.append("\t\t\to.ctx.applyTransforms(ms);\r\n"); + sb.append("\t\t\tctx = o.ctx;\r\n"); + sb.append("\t\t\tcanvas = o.canvas;\r\n"); + } + if (!frameObj.layers.containsKey(i)) { + continue; + } + DepthState layer = frameObj.layers.get(i); + if (!timeline.swf.characters.containsKey(layer.characterId)) { + continue; + } + if (!layer.isVisible) { + continue; + } + CharacterTag character = timeline.swf.characters.get(layer.characterId); + + if (colorTransform == null) { + colorTransform = new ColorTransform(); + } + Matrix placeMatrix = new Matrix(layer.matrix); + placeMatrix.scaleX /= unitDivisor; + placeMatrix.scaleY /= unitDivisor; + placeMatrix.rotateSkew0 /= unitDivisor; + placeMatrix.rotateSkew1 /= unitDivisor; + placeMatrix.translateX /= unitDivisor; + placeMatrix.translateY /= unitDivisor; + + int f = 0; + String fstr = "0"; + if (character instanceof DefineSpriteTag) { + DefineSpriteTag sp = (DefineSpriteTag) character; + Timeline tim = sp.getTimeline(); + if (tim.getFrameCount() > 0) { + f = layer.time % tim.getFrameCount(); + fstr = "(" + f + "+time)%" + tim.getFrameCount(); + } + } + + if (layer.clipDepth != -1) { + clipDepths.push(layer.clipDepth); + sb.append("\t\t\tclips.push({ctx:ctx,canvas:canvas});\r\n"); + sb.append("\t\t\tvar ccanvas = createCanvas(canvas.width,canvas.height);\r\n"); + sb.append("\t\t\tvar cctx = ccanvas.getContext(\"2d\");\r\n"); + sb.append("\t\t\tenhanceContext(cctx);\r\n"); + sb.append("\t\t\tcctx.applyTransforms(ctx._matrices);\r\n"); + sb.append("\t\t\tcanvas = ccanvas;\r\n"); + sb.append("\t\t\tctx = cctx;\r\n"); + } + + if (layer.filters != null && layer.filters.size() > 0) { + sb.append("\t\t\tvar oldctx = ctx;\r\n"); + sb.append("\t\t\tvar fcanvas = createCanvas(canvas.width,canvas.height);"); + sb.append("\t\t\tvar fctx = fcanvas.getContext(\"2d\");\r\n"); + sb.append("\t\t\tenhanceContext(fctx);\r\n"); + sb.append("\t\t\tfctx.applyTransforms(ctx._matrices);\r\n"); + sb.append("\t\t\tctx = fctx;\r\n"); + } + + ColorTransform ctrans = layer.colorTransForm; + String ctrans_str = "ctrans"; + if (ctrans == null) { + ctrans = new ColorTransform(); + } else { + ctrans_str = "ctrans.merge(new cxform(" + + ctrans.getRedAdd() + "," + ctrans.getGreenAdd() + "," + ctrans.getBlueAdd() + "," + ctrans.getAlphaAdd() + "," + + ctrans.getRedMulti() + "," + ctrans.getGreenMulti() + "," + ctrans.getBlueMulti() + "," + ctrans.getAlphaMulti() + + "))"; + } + sb.append("\t\t\tplace(\"").append(getTypePrefix(character)).append(layer.characterId).append("\",canvas,ctx,[").append(placeMatrix.scaleX).append(",") + .append(placeMatrix.rotateSkew0).append(",") + .append(placeMatrix.rotateSkew1).append(",") + .append(placeMatrix.scaleY).append(",") + .append(placeMatrix.translateX).append(",") + .append(placeMatrix.translateY).append("],").append(ctrans_str).append(",").append("").append(layer.blendMode < 1 ? 1 : layer.blendMode).append(",").append(fstr).append(",").append(layer.ratio < 0 ? 0 : layer.ratio).append(",time").append(");\r\n"); + + if (layer.filters != null && layer.filters.size() > 0) { + for (FILTER filter : layer.filters) { + if (filter instanceof COLORMATRIXFILTER) { + COLORMATRIXFILTER cmf = (COLORMATRIXFILTER) filter; + String mat = "["; + for (int k = 0; k < cmf.matrix.length; k++) { + if (k > 0) { + mat += ","; + } + mat += cmf.matrix[k]; + } + mat += "]"; + sb.append("\t\t\tfcanvas = Filters.colorMatrix(fcanvas,fcanvas.getContext(\"2d\"),").append(mat).append(");\r\n"); + } + + if (filter instanceof CONVOLUTIONFILTER) { + CONVOLUTIONFILTER cf = (CONVOLUTIONFILTER) filter; + int height = cf.matrix.length; + int width = cf.matrix[0].length; + float[] matrix2 = new float[width * height]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + matrix2[y * width + x] = cf.matrix[x][y] * cf.divisor + cf.bias; + } + } + String mat = "["; + for (int k = 0; k < matrix2.length; k++) { + if (k > 0) { + mat += ","; + } + mat += matrix2[k]; + } + mat += "]"; + sb.append("\t\t\tfcanvas = Filters.convolution(fcanvas,fcanvas.getContext(\"2d\"),").append(mat).append(",false);\r\n"); + } + + if (filter instanceof GLOWFILTER) { + GLOWFILTER gf = (GLOWFILTER) filter; + sb.append("\t\t\tfcanvas = Filters.glow(fcanvas,fcanvas.getContext(\"2d\"),").append(gf.blurX).append(",").append(gf.blurY).append(",").append(gf.strength).append(",").append(jsArrColor(gf.glowColor)).append(",").append(gf.innerGlow ? "true" : "false").append(",").append(gf.knockout ? "true" : "false").append(",").append(gf.passes).append(");\r\n"); + } + + if (filter instanceof DROPSHADOWFILTER) { + DROPSHADOWFILTER ds = (DROPSHADOWFILTER) filter; + sb.append("\t\t\tfcanvas = Filters.dropShadow(fcanvas,fcanvas.getContext(\"2d\"),").append(ds.blurX).append(",").append(ds.blurY).append(",").append((int) (ds.angle * 180 / Math.PI)).append(",").append(ds.distance).append(",").append(jsArrColor(ds.dropShadowColor)).append(",").append(ds.innerShadow ? "true" : "false").append(",").append(ds.passes).append(",").append(ds.strength).append(",").append(ds.knockout ? "true" : "false").append(");\r\n"); + } + if (filter instanceof BEVELFILTER) { + BEVELFILTER bv = (BEVELFILTER) filter; + String type = "Filters.INNER"; + if (bv.onTop && !bv.innerShadow) { + type = "Filters.FULL"; + } else if (!bv.innerShadow) { + type = "Filters.OUTER"; + } + sb.append("\t\t\tfcanvas = Filters.bevel(fcanvas,fcanvas.getContext(\"2d\"),").append(bv.blurX).append(",").append(bv.blurY).append(",").append(bv.strength).append(",").append(type).append(",").append(jsArrColor(bv.highlightColor)).append(",").append(jsArrColor(bv.shadowColor)).append(",").append((int) (bv.angle * 180 / Math.PI)).append(",").append(bv.distance).append(",").append(bv.knockout ? "true" : "false").append(",").append(bv.passes).append(");\r\n"); + } + + if (filter instanceof GRADIENTBEVELFILTER) { + GRADIENTBEVELFILTER gbf = (GRADIENTBEVELFILTER) filter; + String colArr = "["; + String ratArr = "["; + for (int k = 0; k < gbf.gradientColors.length; k++) { + if (k > 0) { + colArr += ","; + ratArr += ","; + } + colArr += jsArrColor(gbf.gradientColors[k]); + ratArr += gbf.gradientRatio[k] / 255f; + } + colArr += "]"; + ratArr += "]"; + String type = "Filters.INNER"; + if (gbf.onTop && !gbf.innerShadow) { + type = "Filters.FULL"; + } else if (!gbf.innerShadow) { + type = "Filters.OUTER"; + } + + sb.append("\t\t\tfcanvas = Filters.gradientBevel(fcanvas,fcanvas.getContext(\"2d\"),").append(colArr).append(",").append(ratArr).append(",").append(gbf.blurX).append(",").append(gbf.blurY).append(",").append(gbf.strength).append(",").append(type).append(",").append((int) (gbf.angle * 180 / Math.PI)).append(",").append(gbf.distance).append(",").append(gbf.knockout ? "true" : "false").append(",").append(gbf.passes).append(");\r\n"); + } + + if (filter instanceof GRADIENTGLOWFILTER) { + GRADIENTGLOWFILTER ggf = (GRADIENTGLOWFILTER) filter; + String colArr = "["; + String ratArr = "["; + for (int k = 0; k < ggf.gradientColors.length; k++) { + if (k > 0) { + colArr += ","; + ratArr += ","; + } + colArr += jsArrColor(ggf.gradientColors[k]); + ratArr += ggf.gradientRatio[k] / 255f; + } + colArr += "]"; + ratArr += "]"; + String type = "Filters.INNER"; + if (ggf.onTop && !ggf.innerShadow) { + type = "Filters.FULL"; + } else if (!ggf.innerShadow) { + type = "Filters.OUTER"; + } + + sb.append("\t\t\tfcanvas = Filters.gradientGlow(fcanvas,fcanvas.getContext(\"2d\"),").append(ggf.blurX).append(",").append(ggf.blurY).append(",").append((int) (ggf.angle * 180 / Math.PI)).append(",").append(ggf.distance).append(",").append(colArr).append(",").append(ratArr).append(",").append(type).append(",").append(ggf.passes).append(",").append(ggf.strength).append(",").append(ggf.knockout ? "true" : "false").append(");\r\n"); + } + } + sb.append("\t\t\tctx = oldctx;\r\n"); + sb.append("\t\t\tvar ms=ctx._matrices;\r\n"); + sb.append("\t\t\tctx.setTransform(1,0,0,1,0,0);\r\n"); + sb.append("\t\t\tctx.drawImage(fcanvas,0,0);\r\n"); + sb.append("\t\t\tctx.applyTransforms(ms);\r\n"); + } + + if (layer.clipDepth != -1) { + sb.append("\t\t\tclips[clips.length-1].clipCanvas = canvas;\r\n"); + sb.append("\t\t\tcanvas = createCanvas(canvas.width,canvas.height);\r\n"); + sb.append("\t\t\tvar nctx = canvas.getContext(\"2d\");\r\n"); + sb.append("\t\t\tenhanceContext(nctx);\r\n"); + sb.append("\t\t\tnctx.applyTransforms(ctx._matrices);\r\n"); + sb.append("\t\t\tctx = nctx;\r\n"); + } + } + sb.append("\t\t\tbreak;\r\n"); + } + sb.append("\t}\r\n"); + return sb.toString(); + } + + public static void frameToSvg(Timeline timeline, int frame, int time, DepthState stateUnderCursor, int mouseButton, SVGExporter exporter, ColorTransform colorTransform, int level) throws IOException { + if (timeline.frames.size() <= frame) { + return; + } + Frame frameObj = timeline.frames.get(frame); + List clips = new ArrayList<>(); + List prevClips = new ArrayList<>(); + + int maxDepth = timeline.getMaxDepth(); + for (int i = 1; i <= maxDepth; i++) { + for (int c = 0; c < clips.size(); c++) { + if (clips.get(c).depth == i) { + exporter.setClip(prevClips.get(c)); + prevClips.remove(c); + clips.remove(c); + } + } + if (!frameObj.layers.containsKey(i)) { + continue; + } + DepthState layer = frameObj.layers.get(i); + if (!timeline.swf.characters.containsKey(layer.characterId)) { + continue; + } + if (!layer.isVisible) { + continue; + } + CharacterTag character = timeline.swf.characters.get(layer.characterId); + + if (colorTransform == null) { + colorTransform = new ColorTransform(); + } + + ColorTransform clrTrans = Helper.deepCopy(colorTransform); + if (layer.colorTransForm != null && layer.blendMode <= 1) { //Normal blend mode + clrTrans = colorTransform.merge(layer.colorTransForm); + } + + if (character instanceof DrawableTag) { + DrawableTag drawable = (DrawableTag) character; + + String assetName; + Tag drawableTag = (Tag) drawable; + if (exporter.exportedTags.containsKey(drawableTag)) { + assetName = exporter.exportedTags.get(drawableTag); + } else { + assetName = getTagIdPrefix(drawableTag, exporter); + exporter.exportedTags.put(drawableTag, assetName); + exporter.createDefGroup(new ExportRectangle(drawable.getRect()), assetName); + drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1); + exporter.endGroup(); + } + RECT boundRect = drawable.getRect(); + ExportRectangle rect = new ExportRectangle(boundRect); + + // TODO: if (layer.filters != null) + // TODO: if (layer.blendMode > 1) + if (layer.clipDepth > -1) { + String clipName = exporter.getUniqueId("clipPath"); + exporter.createClipPath(new Matrix(), clipName); + SvgClip clip = new SvgClip(clipName, layer.clipDepth); + clips.add(clip); + prevClips.add(exporter.getClip()); + Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); + exporter.addUse(mat, boundRect, assetName); + exporter.setClip(clip.shape); + exporter.endGroup(); + } else { + Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); + exporter.addUse(mat, boundRect, assetName); + } + } + } + } + + private static String getTagIdPrefix(Tag tag, SVGExporter exporter) { + if (tag instanceof ShapeTag) { + return exporter.getUniqueId("shape"); + } + if (tag instanceof MorphShapeTag) { + return exporter.getUniqueId("morphshape"); + } + if (tag instanceof DefineSpriteTag) { + return exporter.getUniqueId("sprite"); + } + if (tag instanceof TextTag) { + return exporter.getUniqueId("text"); + } + if (tag instanceof ButtonTag) { + return exporter.getUniqueId("button"); + } + return exporter.getUniqueId("tag"); + } + + public static SerializableImage frameToImageGet(Timeline timeline, int frame, int time, DepthState stateUnderCursor, int mouseButton, RECT displayRect, Matrix transformation, ColorTransform colorTransform, Color backGroundColor, boolean useCache) { + String key = "frame_" + frame + "_" + timeline.id + "_" + timeline.swf.hashCode(); + SerializableImage image; + if (useCache) { + image = getFromCache(key); + if (image != null) { + return image; + } + } + + if (timeline.frames.isEmpty()) { + return new SerializableImage(1, 1, SerializableImage.TYPE_INT_ARGB); + } + + RECT rect = displayRect; + image = new SerializableImage((int) (rect.getWidth() / SWF.unitDivisor) + 1, + (int) (rect.getHeight() / SWF.unitDivisor) + 1, SerializableImage.TYPE_INT_ARGB); + if (backGroundColor == null) { + image.fillTransparent(); + } else { + Graphics2D g = (Graphics2D) image.getBufferedImage().getGraphics(); + g.setComposite(AlphaComposite.Src); + g.setColor(backGroundColor); + g.fill(new Rectangle(image.getWidth(), image.getHeight())); + } + Matrix m = transformation.clone(); + m.translate(-rect.Xmin, -rect.Ymin); + frameToImage(timeline, frame, time, stateUnderCursor, mouseButton, image, m, colorTransform); + putToCache(key, image); + return image; + } + + public static void framesToImage(Timeline timeline, List ret, int startFrame, int stopFrame, DepthState stateUnderCursor, int mouseButton, RECT displayRect, int totalFrameCount, Stack visited, Matrix transformation, ColorTransform colorTransform) { + RECT rect = displayRect; + for (int f = 0; f < timeline.frames.size(); f++) { + SerializableImage image = new SerializableImage((int) (rect.getWidth() / SWF.unitDivisor) + 1, + (int) (rect.getHeight() / SWF.unitDivisor) + 1, SerializableImage.TYPE_INT_ARGB); + image.fillTransparent(); + Matrix m = new Matrix(); + m.translate(-rect.Xmin, -rect.Ymin); + frameToImage(timeline, f, 0, stateUnderCursor, mouseButton, image, m, colorTransform); + ret.add(image); + } + } + + public static void frameToImage(Timeline timeline, int frame, int time, DepthState stateUnderCursor, int mouseButton, SerializableImage image, Matrix transformation, ColorTransform colorTransform) { + float unzoom = (float) SWF.unitDivisor; + if (timeline.frames.size() <= frame) { + return; + } + Frame frameObj = timeline.frames.get(frame); + Graphics2D g = (Graphics2D) image.getGraphics(); + g.setPaint(frameObj.backgroundColor.toColor()); + g.fill(new Rectangle(image.getWidth(), image.getHeight())); + g.setTransform(transformation.toTransform()); + List clips = new ArrayList<>(); + List prevClips = new ArrayList<>(); + + int maxDepth = timeline.getMaxDepth(); + for (int i = 1; i <= maxDepth; i++) { + for (int c = 0; c < clips.size(); c++) { + if (clips.get(c).depth == i) { + g.setClip(prevClips.get(c)); + prevClips.remove(c); + clips.remove(c); + } + } + if (!frameObj.layers.containsKey(i)) { + continue; + } + DepthState layer = frameObj.layers.get(i); + if (!timeline.swf.characters.containsKey(layer.characterId)) { + continue; + } + if (!layer.isVisible) { + continue; + } + CharacterTag character = timeline.swf.characters.get(layer.characterId); + Matrix mat = new Matrix(layer.matrix); + mat = mat.preConcatenate(transformation); + + if (colorTransform == null) { + colorTransform = new ColorTransform(); + } + + ColorTransform clrTrans = Helper.deepCopy(colorTransform); + if (layer.colorTransForm != null && layer.blendMode <= 1) { //Normal blend mode + clrTrans = colorTransform.merge(layer.colorTransForm); + } + + boolean showPlaceholder = false; + if (character instanceof DrawableTag) { + DrawableTag drawable = (DrawableTag) character; + SerializableImage img; + Matrix drawMatrix = new Matrix(); + int drawableFrameCount = drawable.getNumFrames(); + if (drawableFrameCount == 0) { + drawableFrameCount = 1; + } + int dframe = (time + layer.time) % drawableFrameCount; + if (character instanceof ButtonTag) { + ButtonTag bt = (ButtonTag) character; + dframe = ButtonTag.FRAME_UP; + if (stateUnderCursor == layer) { + if (mouseButton > 0) { + dframe = ButtonTag.FRAME_DOWN; + } else { + dframe = ButtonTag.FRAME_OVER; + } + } + } + + RECT boundRect = drawable.getRect(); + ExportRectangle rect = new ExportRectangle(boundRect); + rect = mat.transform(rect); + Matrix m = mat.clone(); + if (layer.filters != null && layer.filters.size() > 0) { + // calculate size after applying the filters + double deltaXMax = 0; + double deltaYMax = 0; + for (FILTER filter : layer.filters) { + double x = filter.getDeltaX(); + double y = filter.getDeltaY(); + deltaXMax = Math.max(x, deltaXMax); + deltaYMax = Math.max(y, deltaYMax); + } + rect.xMin -= deltaXMax * SWF.unitDivisor; + rect.xMax += deltaXMax * SWF.unitDivisor; + rect.yMin -= deltaYMax * SWF.unitDivisor; + rect.yMax += deltaYMax * SWF.unitDivisor; + } + + rect.xMin -= SWF.unitDivisor; + rect.yMin -= SWF.unitDivisor; + rect.xMin = Math.max(0, rect.xMin); + rect.yMin = Math.max(0, rect.yMin); + + int newWidth = (int) (rect.getWidth() / SWF.unitDivisor); + int newHeight = (int) (rect.getHeight() / SWF.unitDivisor); + int deltaX = (int) (rect.xMin / SWF.unitDivisor); + int deltaY = (int) (rect.yMin / SWF.unitDivisor); + newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1; + newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1; + + if (newWidth <= 0 || newHeight <= 0) { + continue; + } + + img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB); + img.fillTransparent(); + + m.translate(-rect.xMin, -rect.yMin); + drawMatrix.translate(rect.xMin, rect.yMin); + + drawable.toImage(dframe, layer.time + time, layer.ratio, stateUnderCursor, mouseButton, img, m, clrTrans); + //if(stateUnderCursor == layer){ + /* if(true){ + Graphics2D gg = (Graphics2D)img.getGraphics(); + gg.setStroke(new BasicStroke(3)); + gg.setPaint(Color.red); + gg.setTransform(AffineTransform.getTranslateInstance(0, 0)); + gg.draw(SHAPERECORD.twipToPixelShape(drawable.getOutline(frame, layer.ratio, stateUnderCursor, mouseButton, m))); + }*/ + + if (layer.filters != null) { + for (FILTER filter : layer.filters) { + img = filter.apply(img); + } + } + if (layer.blendMode > 1) { + if (layer.colorTransForm != null) { + img = layer.colorTransForm.apply(img); + } + } + + drawMatrix.translateX /= unzoom; + drawMatrix.translateY /= unzoom; + AffineTransform trans = drawMatrix.toTransform(); + + switch (layer.blendMode) { + case 0: + case 1: + g.setComposite(AlphaComposite.SrcOver); + break; + case 2://Layer + g.setComposite(AlphaComposite.SrcOver); + break; + case 3: + g.setComposite(BlendComposite.Multiply); + break; + case 4: + g.setComposite(BlendComposite.Screen); + break; + case 5: + g.setComposite(BlendComposite.Lighten); + break; + case 6: + g.setComposite(BlendComposite.Darken); + break; + case 7: + g.setComposite(BlendComposite.Difference); + break; + case 8: + g.setComposite(BlendComposite.Add); + break; + case 9: + g.setComposite(BlendComposite.Subtract); + break; + case 10: + g.setComposite(BlendComposite.Invert); + break; + case 11: + g.setComposite(BlendComposite.Alpha); + break; + case 12: + g.setComposite(BlendComposite.Erase); + break; + case 13: + g.setComposite(BlendComposite.Overlay); + break; + case 14: + g.setComposite(BlendComposite.HardLight); + break; + default: //Not implemented + g.setComposite(AlphaComposite.SrcOver); + break; + } + + if (layer.clipDepth > -1) { + BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); + Graphics2D gm = (Graphics2D) mask.getGraphics(); + gm.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + gm.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + gm.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gm.setComposite(AlphaComposite.Src); + gm.setColor(new Color(0, 0, 0, 0f)); + gm.fillRect(0, 0, image.getWidth(), image.getHeight()); + gm.setTransform(trans); + gm.drawImage(img.getBufferedImage(), 0, 0, null); + Clip clip = new Clip(Helper.imageToShape(mask), layer.clipDepth); //Maybe we can get current outline instead converting from image (?) + clips.add(clip); + prevClips.add(g.getClip()); + g.setTransform(AffineTransform.getTranslateInstance(0, 0)); + g.setClip(clip.shape); + } else { + g.setTransform(trans); + g.drawImage(img.getBufferedImage(), 0, 0, null); + } + } else if (character instanceof BoundedTag) { + showPlaceholder = true; + } + + if (showPlaceholder) { + mat.translateX /= unzoom; + mat.translateY /= unzoom; + AffineTransform trans = mat.toTransform(); + g.setTransform(trans); + BoundedTag b = (BoundedTag) character; + g.setPaint(new Color(255, 255, 255, 128)); + g.setComposite(BlendComposite.Invert); + RECT r = b.getRect(); + int div = (int) unzoom; + g.drawString(character.toString(), r.Xmin / div + 3, r.Ymin / div + 15); + g.draw(new Rectangle(r.Xmin / div, r.Ymin / div, r.getWidth() / div, r.getHeight() / div)); + g.drawLine(r.Xmin / div, r.Ymin / div, r.Xmax / div, r.Ymax / div); + g.drawLine(r.Xmax / div, r.Ymin / div, r.Xmin / div, r.Ymax / div); + g.setComposite(AlphaComposite.Dst); + } + } + + g.setTransform(AffineTransform.getScaleInstance(1, 1)); + } + + private void removeTagWithDependenciesFromTimeline(Tag toRemove, Timeline timeline) { + int characterId = 0; + if (toRemove instanceof CharacterTag) { + characterId = ((CharacterTag) toRemove).getCharacterId(); + } + Map stage = new HashMap<>(); + + Set dependingChars = new HashSet<>(); + if (characterId != 0) { + dependingChars.add(characterId); + for (int i = 0; i < timeline.tags.size(); i++) { + Tag t = timeline.tags.get(i); + if (t instanceof CharacterIdTag) { + CharacterIdTag c = (CharacterIdTag) t; + Set needed = new HashSet<>(); + t.getNeededCharacters(needed); + if (needed.contains(characterId)) { + dependingChars.add(c.getCharacterId()); + } + } + } + } + + for (int i = 0; i < timeline.tags.size(); i++) { + Tag t = timeline.tags.get(i); + if (t instanceof RemoveTag) { + RemoveTag rt = (RemoveTag) t; + int depth = rt.getDepth(); + if (stage.containsKey(depth)) { + int currentCharId = stage.get(depth); + stage.remove(depth); + if (dependingChars.contains(currentCharId)) { + timeline.tags.remove(i); + i--; + continue; + } + } + } + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; + int placeCharId = po.getCharacterId(); + int depth = po.getDepth(); + if (placeCharId != 0) { + stage.put(depth, placeCharId); + if (dependingChars.contains(placeCharId)) { + timeline.tags.remove(i); + i--; + continue; + } + } + } + if (t instanceof CharacterIdTag) { + CharacterIdTag c = (CharacterIdTag) t; + if (dependingChars.contains(c.getCharacterId())) { + timeline.tags.remove(i); + i--; + continue; + } + } + Set needed = new HashSet<>(); + t.getNeededCharacters(needed); + for (int dep : dependingChars) { + if (needed.contains(dep)) { + timeline.tags.remove(i); + i--; + continue; + } + } + if (t == toRemove) { + timeline.tags.remove(i); + i--; + continue; + } + if (t instanceof Timelined) { + removeTagWithDependenciesFromTimeline(toRemove, ((Timelined) t).getTimeline()); + } + } + } + + private boolean removeTagFromTimeline(Tag toRemove, Timeline timeline) { + boolean modified = false; + int characterId = -1; + if (toRemove instanceof CharacterTag) { + characterId = ((CharacterTag) toRemove).getCharacterId(); + modified = timeline.removeCharacter(characterId); + } + for (int i = 0; i < timeline.tags.size(); i++) { + Tag t = timeline.tags.get(i); + if (t == toRemove) { + timeline.tags.remove(t); + i--; + continue; + } + + if (toRemove instanceof CharacterTag) { + if (t.removeCharacter(characterId)) { + modified = true; + i = -1; + continue; + } + } + + if (t instanceof DefineSpriteTag) { + DefineSpriteTag spr = (DefineSpriteTag) t; + boolean sprModified = removeTagFromTimeline(toRemove, spr.getTimeline()); + if (sprModified) { + spr.setModified(true); + } + modified |= sprModified; + } + } + return modified; + } + + public void removeTag(Tag t, boolean removeDependencies) { + Timelined timelined = t.getTimelined(); + if (t instanceof ShowFrameTag || ShowFrameTag.isNestedTagType(t.getId())) { + List tags; + if (timelined instanceof DefineSpriteTag) { + tags = ((DefineSpriteTag) timelined).getSubTags(); + } else { + tags = this.tags; + } + tags.remove(t); + timelined.resetTimeline(); + } else { + // timeline should be always the swf here + if (removeDependencies) { + removeTagWithDependenciesFromTimeline(t, timelined.getTimeline()); + } else { + removeTagFromTimeline(t, timeline); + } + } + resetTimelines(timelined); + updateCharacters(); + clearImageCache(); + } +} diff --git a/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java b/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java index 9a7b67718..9c68d415d 100644 --- a/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java +++ b/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java @@ -1,401 +1,402 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.abc.CopyOutputStream; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.Container; -import com.jpexs.decompiler.flash.tags.base.ContainerItem; -import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.CLIPACTIONS; -import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.filters.FILTER; -import com.jpexs.helpers.ByteArrayRange; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * Extends the functionality of the PlaceObject2Tag - * - * @author JPEXS - */ -public class PlaceObject2Tag extends CharacterIdTag implements Container, PlaceObjectTypeTag { - - /** - * @since SWF 5 Has clip actions (sprite characters only) - */ - public boolean placeFlagHasClipActions; - /** - * Has clip depth - */ - public boolean placeFlagHasClipDepth; - /** - * Has name - */ - public boolean placeFlagHasName; - /** - * Has ratio - */ - public boolean placeFlagHasRatio; - /** - * Has color transform - */ - public boolean placeFlagHasColorTransform; - /** - * Has matrix - */ - public boolean placeFlagHasMatrix; - /** - * Places a character - */ - public boolean placeFlagHasCharacter; - /** - * Defines a character to be moved - */ - public boolean placeFlagMove; - /** - * Depth of character - */ - @SWFType(BasicType.UI16) - public int depth; - /** - * If PlaceFlagHasCharacter, ID of character to place - */ - @SWFType(BasicType.UI16) - @Conditional("placeFlagHasCharacter") - public int characterId; - /** - * If PlaceFlagHasMatrix, Transform matrix data - */ - @Conditional("placeFlagHasMatrix") - public MATRIX matrix; - /** - * If PlaceFlagHasColorTransform, Color transform data - */ - @Conditional("placeFlagHasColorTransform") - public CXFORMWITHALPHA colorTransform; - /** - * If PlaceFlagHasRatio, ratio - */ - @SWFType(BasicType.UI16) - @Conditional("placeFlagHasRatio") - public int ratio; - /** - * If PlaceFlagHasName, Name of character - */ - @Conditional("placeFlagHasName") - public String name; - /** - * If PlaceFlagHasClipDepth, Clip depth - */ - @Conditional("placeFlagHasClipDepth") - public int clipDepth; - /** - * @since SWF 5 If PlaceFlagHasClipActions, Clip Actions Data - */ - @Conditional("placeFlagHasClipActions") - @Internal //TODO: make editable - public CLIPACTIONS clipActions; - public static final int ID = 26; - - @Override - public int getClipDepth() { - if (placeFlagHasClipDepth) { - return clipDepth; - } - return -1; - } - - @Override - public List getFilters() { - return null; - } - - /** - * Gets data bytes - * - * @return Bytes of data - */ - @Override - public byte[] getData() { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - OutputStream os = baos; - if (Configuration.debugCopy.get()) { - os = new CopyOutputStream(os, new ByteArrayInputStream(getOriginalData())); - } - SWFOutputStream sos = new SWFOutputStream(os, getVersion()); - try { - sos.writeUB(1, placeFlagHasClipActions ? 1 : 0); - sos.writeUB(1, placeFlagHasClipDepth ? 1 : 0); - sos.writeUB(1, placeFlagHasName ? 1 : 0); - sos.writeUB(1, placeFlagHasRatio ? 1 : 0); - sos.writeUB(1, placeFlagHasColorTransform ? 1 : 0); - sos.writeUB(1, placeFlagHasMatrix ? 1 : 0); - sos.writeUB(1, placeFlagHasCharacter ? 1 : 0); - sos.writeUB(1, placeFlagMove ? 1 : 0); - sos.writeUI16(depth); - if (placeFlagHasCharacter) { - sos.writeUI16(characterId); - } - if (placeFlagHasMatrix) { - sos.writeMatrix(matrix); - } - if (placeFlagHasColorTransform) { - sos.writeCXFORMWITHALPHA(colorTransform); - } - if (placeFlagHasRatio) { - sos.writeUI16(ratio); - } - if (placeFlagHasName) { - sos.writeString(name); - } - if (placeFlagHasClipDepth) { - sos.writeUI16(clipDepth); - } - if (placeFlagHasClipActions) { - sos.writeCLIPACTIONS(clipActions); - } - sos.close(); - } catch (IOException e) { - } - return baos.toByteArray(); - } - - public PlaceObject2Tag(SWF swf, boolean placeFlagHasClipActions, boolean placeFlagHasClipDepth, boolean placeFlagHasName, boolean placeFlagHasRatio, boolean placeFlagHasColorTransform, boolean placeFlagHasMatrix, boolean placeFlagHasCharacter, boolean placeFlagMove, int depth, int characterId, MATRIX matrix, CXFORMWITHALPHA colorTransform, int ratio, String name, int clipDepth, CLIPACTIONS clipActions) { - super(swf, ID, "PlaceObject2", null); - this.placeFlagHasClipActions = placeFlagHasClipActions; - this.placeFlagHasClipDepth = placeFlagHasClipDepth; - this.placeFlagHasName = placeFlagHasName; - this.placeFlagHasRatio = placeFlagHasRatio; - this.placeFlagHasColorTransform = placeFlagHasColorTransform; - this.placeFlagHasMatrix = placeFlagHasMatrix; - this.placeFlagHasCharacter = placeFlagHasCharacter; - this.placeFlagMove = placeFlagMove; - this.depth = depth; - this.characterId = characterId; - this.matrix = matrix; - this.colorTransform = colorTransform; - this.ratio = ratio; - this.name = name; - this.clipDepth = clipDepth; - this.clipActions = clipActions; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public PlaceObject2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, "PlaceObject2", data); - placeFlagHasClipActions = sis.readUB(1, "placeFlagHasClipActions") == 1; - placeFlagHasClipDepth = sis.readUB(1, "placeFlagHasClipDepth") == 1; - placeFlagHasName = sis.readUB(1, "placeFlagHasName") == 1; - placeFlagHasRatio = sis.readUB(1, "placeFlagHasRatio") == 1; - placeFlagHasColorTransform = sis.readUB(1, "placeFlagHasColorTransform") == 1; - placeFlagHasMatrix = sis.readUB(1, "placeFlagHasMatrix") == 1; - placeFlagHasCharacter = sis.readUB(1, "placeFlagHasCharacter") == 1; - placeFlagMove = sis.readUB(1, "placeFlagMove") == 1; - depth = sis.readUI16("depth"); - if (placeFlagHasCharacter) { - characterId = sis.readUI16("characterId"); - } - if (placeFlagHasMatrix) { - matrix = sis.readMatrix("matrix"); - } - if (placeFlagHasColorTransform) { - colorTransform = sis.readCXFORMWITHALPHA("colorTransform"); - } - if (placeFlagHasRatio) { - ratio = sis.readUI16("ratio"); - } - if (placeFlagHasName) { - name = sis.readString("name"); - } - if (placeFlagHasClipDepth) { - clipDepth = sis.readUI16("clipDepth"); - } - if (placeFlagHasClipActions) { - clipActions = sis.readCLIPACTIONS(swf, this, "clipActions"); - } - } - - /** - * Returns all sub-items - * - * @return List of sub-items - */ - @Override - public List getSubItems() { - List ret = new ArrayList<>(); - if (placeFlagHasClipActions) { - ret.addAll(clipActions.clipActionRecords); - } - return ret; - } - - /** - * Returns number of sub-items - * - * @return Number of sub-items - */ - @Override - public int getItemCount() { - if (!placeFlagHasClipActions) { - return 0; - } - return clipActions.clipActionRecords.size(); - } - - @Override - public void getNeededCharacters(Set needed) { - if (placeFlagHasCharacter) { - needed.add(characterId); - } - } - - @Override - public boolean removeCharacter(int characterId) { - // the place object tag will be removed - return false; - } - - @Override - public int getCharacterId() { - if (placeFlagHasCharacter) { - return characterId; - } else { - return -1; - } - } - - @Override - public int getDepth() { - return depth; - } - - @Override - public MATRIX getMatrix() { - if (placeFlagHasMatrix) { - return matrix; - } else { - return null; - } - } - - @Override - public String getInstanceName() { - if (placeFlagHasName) { - return name; - } - return null; - } - - @Override - public ColorTransform getColorTransform() { - if (placeFlagHasColorTransform) { - return colorTransform; - } else { - return null; - } - } - - @Override - public int getBlendMode() { - return 0; - } - - @Override - public boolean cacheAsBitmap() { - return false; - } - - @Override - public String getClassName() { - return null; - } - - @Override - public boolean isVisible() { - return true; - } - - @Override - public RGBA getBackgroundColor() { - return null; - } - - @Override - public boolean flagMove() { - return placeFlagMove; - } - - @Override - public int getRatio() { - if (!placeFlagHasRatio) { - return -1; - } - return ratio; - } - - @Override - public void setInstanceName(String name) { - placeFlagHasName = true; - this.name = name; - } - - @Override - public void setClassName(String className) { - //not supported - } - - @Override - public String toString() { - if (placeFlagHasName) { - return super.toString() + " (" + name + ")"; - } else { - return super.toString(); - } - } - - @Override - public CLIPACTIONS getClipActions() { - if (placeFlagHasClipActions) { - return clipActions; - } else { - return null; - } - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.abc.CopyOutputStream; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.Container; +import com.jpexs.decompiler.flash.tags.base.ContainerItem; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.CLIPACTIONS; +import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.filters.FILTER; +import com.jpexs.helpers.ByteArrayRange; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Extends the functionality of the PlaceObject2Tag + * + * @author JPEXS + */ +public class PlaceObject2Tag extends CharacterIdTag implements Container, PlaceObjectTypeTag { + + /** + * @since SWF 5 Has clip actions (sprite characters only) + */ + public boolean placeFlagHasClipActions; + /** + * Has clip depth + */ + public boolean placeFlagHasClipDepth; + /** + * Has name + */ + public boolean placeFlagHasName; + /** + * Has ratio + */ + public boolean placeFlagHasRatio; + /** + * Has color transform + */ + public boolean placeFlagHasColorTransform; + /** + * Has matrix + */ + public boolean placeFlagHasMatrix; + /** + * Places a character + */ + public boolean placeFlagHasCharacter; + /** + * Defines a character to be moved + */ + public boolean placeFlagMove; + /** + * Depth of character + */ + @SWFType(BasicType.UI16) + public int depth; + /** + * If PlaceFlagHasCharacter, ID of character to place + */ + @SWFType(BasicType.UI16) + @Conditional("placeFlagHasCharacter") + public int characterId; + /** + * If PlaceFlagHasMatrix, Transform matrix data + */ + @Conditional("placeFlagHasMatrix") + public MATRIX matrix; + /** + * If PlaceFlagHasColorTransform, Color transform data + */ + @Conditional("placeFlagHasColorTransform") + public CXFORMWITHALPHA colorTransform; + /** + * If PlaceFlagHasRatio, ratio + */ + @SWFType(BasicType.UI16) + @Conditional("placeFlagHasRatio") + public int ratio; + /** + * If PlaceFlagHasName, Name of character + */ + @Conditional("placeFlagHasName") + public String name; + /** + * If PlaceFlagHasClipDepth, Clip depth + */ + @Conditional("placeFlagHasClipDepth") + public int clipDepth; + /** + * @since SWF 5 If PlaceFlagHasClipActions, Clip Actions Data + */ + @Conditional("placeFlagHasClipActions") + @Internal //TODO: make editable + public CLIPACTIONS clipActions; + public static final int ID = 26; + + @Override + public int getClipDepth() { + if (placeFlagHasClipDepth) { + return clipDepth; + } + return -1; + } + + @Override + public List getFilters() { + return null; + } + + /** + * Gets data bytes + * + * @return Bytes of data + */ + @Override + public byte[] getData() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStream os = baos; + if (Configuration.debugCopy.get()) { + os = new CopyOutputStream(os, new ByteArrayInputStream(getOriginalData())); + } + SWFOutputStream sos = new SWFOutputStream(os, getVersion()); + try { + sos.writeUB(1, placeFlagHasClipActions ? 1 : 0); + sos.writeUB(1, placeFlagHasClipDepth ? 1 : 0); + sos.writeUB(1, placeFlagHasName ? 1 : 0); + sos.writeUB(1, placeFlagHasRatio ? 1 : 0); + sos.writeUB(1, placeFlagHasColorTransform ? 1 : 0); + sos.writeUB(1, placeFlagHasMatrix ? 1 : 0); + sos.writeUB(1, placeFlagHasCharacter ? 1 : 0); + sos.writeUB(1, placeFlagMove ? 1 : 0); + sos.writeUI16(depth); + if (placeFlagHasCharacter) { + sos.writeUI16(characterId); + } + if (placeFlagHasMatrix) { + sos.writeMatrix(matrix); + } + if (placeFlagHasColorTransform) { + sos.writeCXFORMWITHALPHA(colorTransform); + } + if (placeFlagHasRatio) { + sos.writeUI16(ratio); + } + if (placeFlagHasName) { + sos.writeString(name); + } + if (placeFlagHasClipDepth) { + sos.writeUI16(clipDepth); + } + if (placeFlagHasClipActions) { + sos.writeCLIPACTIONS(clipActions); + } + sos.close(); + } catch (IOException e) { + } + return baos.toByteArray(); + } + + public PlaceObject2Tag(SWF swf, boolean placeFlagHasClipActions, boolean placeFlagHasClipDepth, boolean placeFlagHasName, boolean placeFlagHasRatio, boolean placeFlagHasColorTransform, boolean placeFlagHasMatrix, boolean placeFlagHasCharacter, boolean placeFlagMove, int depth, int characterId, MATRIX matrix, CXFORMWITHALPHA colorTransform, int ratio, String name, int clipDepth, CLIPACTIONS clipActions) { + super(swf, ID, "PlaceObject2", null); + this.placeFlagHasClipActions = placeFlagHasClipActions; + this.placeFlagHasClipDepth = placeFlagHasClipDepth; + this.placeFlagHasName = placeFlagHasName; + this.placeFlagHasRatio = placeFlagHasRatio; + this.placeFlagHasColorTransform = placeFlagHasColorTransform; + this.placeFlagHasMatrix = placeFlagHasMatrix; + this.placeFlagHasCharacter = placeFlagHasCharacter; + this.placeFlagMove = placeFlagMove; + this.depth = depth; + this.characterId = characterId; + this.matrix = matrix; + this.colorTransform = colorTransform; + this.ratio = ratio; + this.name = name; + this.clipDepth = clipDepth; + this.clipActions = clipActions; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public PlaceObject2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, "PlaceObject2", data); + placeFlagHasClipActions = sis.readUB(1, "placeFlagHasClipActions") == 1; + placeFlagHasClipDepth = sis.readUB(1, "placeFlagHasClipDepth") == 1; + placeFlagHasName = sis.readUB(1, "placeFlagHasName") == 1; + placeFlagHasRatio = sis.readUB(1, "placeFlagHasRatio") == 1; + placeFlagHasColorTransform = sis.readUB(1, "placeFlagHasColorTransform") == 1; + placeFlagHasMatrix = sis.readUB(1, "placeFlagHasMatrix") == 1; + placeFlagHasCharacter = sis.readUB(1, "placeFlagHasCharacter") == 1; + placeFlagMove = sis.readUB(1, "placeFlagMove") == 1; + + depth = sis.readUI16("depth"); + if (placeFlagHasCharacter) { + characterId = sis.readUI16("characterId"); + } + if (placeFlagHasMatrix) { + matrix = sis.readMatrix("matrix"); + } + if (placeFlagHasColorTransform) { + colorTransform = sis.readCXFORMWITHALPHA("colorTransform"); + } + if (placeFlagHasRatio) { + ratio = sis.readUI16("ratio"); + } + if (placeFlagHasName) { + name = sis.readString("name"); + } + if (placeFlagHasClipDepth) { + clipDepth = sis.readUI16("clipDepth"); + } + if (placeFlagHasClipActions) { + clipActions = sis.readCLIPACTIONS(swf, this, "clipActions"); + } + } + + /** + * Returns all sub-items + * + * @return List of sub-items + */ + @Override + public List getSubItems() { + List ret = new ArrayList<>(); + if (placeFlagHasClipActions) { + ret.addAll(clipActions.clipActionRecords); + } + return ret; + } + + /** + * Returns number of sub-items + * + * @return Number of sub-items + */ + @Override + public int getItemCount() { + if (!placeFlagHasClipActions) { + return 0; + } + return clipActions.clipActionRecords.size(); + } + + @Override + public void getNeededCharacters(Set needed) { + if (placeFlagHasCharacter) { + needed.add(characterId); + } + } + + @Override + public boolean removeCharacter(int characterId) { + // the place object tag will be removed + return false; + } + + @Override + public int getCharacterId() { + if (placeFlagHasCharacter) { + return characterId; + } else { + return -1; + } + } + + @Override + public int getDepth() { + return depth; + } + + @Override + public MATRIX getMatrix() { + if (placeFlagHasMatrix) { + return matrix; + } else { + return null; + } + } + + @Override + public String getInstanceName() { + if (placeFlagHasName) { + return name; + } + return null; + } + + @Override + public ColorTransform getColorTransform() { + if (placeFlagHasColorTransform) { + return colorTransform; + } else { + return null; + } + } + + @Override + public int getBlendMode() { + return 0; + } + + @Override + public boolean cacheAsBitmap() { + return false; + } + + @Override + public String getClassName() { + return null; + } + + @Override + public boolean isVisible() { + return true; + } + + @Override + public RGBA getBackgroundColor() { + return null; + } + + @Override + public boolean flagMove() { + return placeFlagMove; + } + + @Override + public int getRatio() { + if (!placeFlagHasRatio) { + return -1; + } + return ratio; + } + + @Override + public void setInstanceName(String name) { + placeFlagHasName = true; + this.name = name; + } + + @Override + public void setClassName(String className) { + //not supported + } + + @Override + public String toString() { + if (placeFlagHasName) { + return super.toString() + " (" + name + ")"; + } else { + return super.toString(); + } + } + + @Override + public CLIPACTIONS getClipActions() { + if (placeFlagHasClipActions) { + return clipActions; + } else { + return null; + } + } +} diff --git a/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java b/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java index bee84a0ad..2e1584a6d 100644 --- a/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java +++ b/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java @@ -1,530 +1,530 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.tags; - -import com.jpexs.decompiler.flash.EndOfStreamException; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.abc.CopyOutputStream; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.Container; -import com.jpexs.decompiler.flash.tags.base.ContainerItem; -import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.CLIPACTIONS; -import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.Reserved; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.filters.FILTER; -import com.jpexs.helpers.ByteArrayRange; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * Extends the functionality of the PlaceObject2Tag - * - * @author JPEXS - */ -public class PlaceObject3Tag extends CharacterIdTag implements Container, PlaceObjectTypeTag { - - /** - * @since SWF 5 has clip actions (sprite characters only) - */ - public boolean placeFlagHasClipActions; - /** - * Has clip depth - */ - public boolean placeFlagHasClipDepth; - /** - * Has name - */ - public boolean placeFlagHasName; - /** - * Has ratio - */ - public boolean placeFlagHasRatio; - /** - * Has color transform - */ - public boolean placeFlagHasColorTransform; - /** - * Has matrix - */ - public boolean placeFlagHasMatrix; - /** - * Places a character - */ - public boolean placeFlagHasCharacter; - /** - * Defines a character to be moved - */ - public boolean placeFlagMove; - /** - * Has class name or character ID of bitmap to place. If - * PlaceFlagHasClassName, use ClassName. If PlaceFlagHasCharacter, use - * CharacterId - */ - public boolean placeFlagHasImage; - /** - * Has class name of object to place - */ - public boolean placeFlagHasClassName; - /** - * Enables bitmap caching - */ - public boolean placeFlagHasCacheAsBitmap; - /** - * Has blend mode - */ - public boolean placeFlagHasBlendMode; - /** - * Has filter list - */ - public boolean placeFlagHasFilterList; - /** - * Has opaque background. SWF 11 and higher. - */ - public boolean placeFlagOpaqueBackground; - /** - * Has visibility flag. SWF 11 and higher. - */ - public boolean placeFlagHasVisible; - /** - * Depth of character - */ - @SWFType(BasicType.UI16) - public int depth; - /** - * If PlaceFlagHasClassName or (PlaceFlagHasImage and - * PlaceFlagHasCharacter), Name of the class to place - */ - @Conditional("placeFlagHasClassName|(placeFlagHasImage,placeFlagHasCharacter)") - public String className; - /** - * If PlaceFlagHasCharacter, ID of character to place - */ - @SWFType(BasicType.UI16) - @Conditional("placeFlagHasCharacter") - public int characterId; - /** - * If PlaceFlagHasMatrix, Transform matrix data - */ - @Conditional("placeFlagHasMatrix") - public MATRIX matrix; - /** - * If PlaceFlagHasColorTransform, Color transform data - */ - @Conditional("placeFlagHasColorTransform") - public CXFORMWITHALPHA colorTransform; - /** - * If PlaceFlagHasRatio, Ratio - */ - @SWFType(BasicType.UI16) - @Conditional("placeFlagHasRatio") - public int ratio; - /** - * If PlaceFlagHasName, Name of character - */ - @Conditional("placeFlagHasName") - public String name; - /** - * If PlaceFlagHasClipDepth, Clip depth - */ - @SWFType(BasicType.UI16) - @Conditional("placeFlagHasClipDepth") - public int clipDepth; - /** - * If PlaceFlagHasFilterList, List of filters on this object - */ - @Conditional("placeFlagHasFilterList") - public List surfaceFilterList; - /** - * If PlaceFlagHasBlendMode, Blend mode - */ - @SWFType(BasicType.UI8) - @Conditional("placeFlagHasBlendMode") - public int blendMode; - /** - * If PlaceFlagHasCacheAsBitmap, 0 = Bitmap cache disabled, 1-255 = Bitmap - * cache enabled - */ - @SWFType(BasicType.UI8) - @Conditional("placeFlagHasCacheAsBitmap") - public int bitmapCache; - /** - * @since SWF 5 If PlaceFlagHasClipActions, Clip Actions Data - */ - @Conditional(value = "placeFlagHasClipActions", minSwfVersion = 5) - @Internal //TODO: make editable - public CLIPACTIONS clipActions; - /** - * If PlaceFlagHasVisible, 0 = Place invisible, 1 = Place visible - */ - @Conditional("placeFlagHasVisible") - public int visible; - /** - * If PlaceFlagHasVisible, Background color - */ - @Conditional("placeFlagOpaqueBackground") - public RGBA backgroundColor; - // FIXME bug found in ecoDrive.swf, - @Internal - private boolean bitmapCacheBug; - @Reserved - public boolean reserved; - public static final int ID = 70; - - @Override - public List getFilters() { - if (placeFlagHasFilterList) { - return surfaceFilterList; - } else { - return null; - } - } - - @Override - public int getClipDepth() { - if (placeFlagHasClipDepth) { - return clipDepth; - } - return -1; - } - - /** - * Gets data bytes - * - * @return Bytes of data - */ - @Override - public byte[] getData() { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - OutputStream os = baos; - if (Configuration.debugCopy.get()) { - os = new CopyOutputStream(os, new ByteArrayInputStream(getOriginalData())); - } - SWFOutputStream sos = new SWFOutputStream(os, getVersion()); - try { - sos.writeUB(1, placeFlagHasClipActions ? 1 : 0); - sos.writeUB(1, placeFlagHasClipDepth ? 1 : 0); - sos.writeUB(1, placeFlagHasName ? 1 : 0); - sos.writeUB(1, placeFlagHasRatio ? 1 : 0); - sos.writeUB(1, placeFlagHasColorTransform ? 1 : 0); - sos.writeUB(1, placeFlagHasMatrix ? 1 : 0); - sos.writeUB(1, placeFlagHasCharacter ? 1 : 0); - sos.writeUB(1, placeFlagMove ? 1 : 0); - sos.writeUB(1, reserved ? 1 : 0); - sos.writeUB(1, placeFlagOpaqueBackground ? 1 : 0); //SWF11 - sos.writeUB(1, placeFlagHasVisible ? 1 : 0); //SWF11 - sos.writeUB(1, placeFlagHasImage ? 1 : 0); - sos.writeUB(1, placeFlagHasClassName ? 1 : 0); - sos.writeUB(1, placeFlagHasCacheAsBitmap ? 1 : 0); - sos.writeUB(1, placeFlagHasBlendMode ? 1 : 0); - sos.writeUB(1, placeFlagHasFilterList ? 1 : 0); - sos.writeUI16(depth); - - if (placeFlagHasClassName || (placeFlagHasImage && placeFlagHasCharacter)) { - sos.writeString(className); - } - if (placeFlagHasCharacter) { - sos.writeUI16(characterId); - } - if (placeFlagHasMatrix) { - sos.writeMatrix(matrix); - } - if (placeFlagHasColorTransform) { - sos.writeCXFORMWITHALPHA(colorTransform); - } - if (placeFlagHasRatio) { - sos.writeUI16(ratio); - } - if (placeFlagHasName) { - sos.writeString(name); - } - if (placeFlagHasClipDepth) { - sos.writeUI16(clipDepth); - } - if (placeFlagHasFilterList) { - sos.writeFILTERLIST(surfaceFilterList); - } - if (placeFlagHasBlendMode) { - sos.writeUI8(blendMode); - } - if (placeFlagHasCacheAsBitmap) { - if (!bitmapCacheBug) { - sos.writeUI8(bitmapCache); - } - } - if (placeFlagHasVisible) { - sos.writeUI8(visible); - } - if (placeFlagOpaqueBackground) { - sos.writeRGBA(backgroundColor); - } - if (placeFlagHasClipActions) { - sos.writeCLIPACTIONS(clipActions); - } - sos.close(); - } catch (IOException ex) { - } - return baos.toByteArray(); - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public PlaceObject3Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, "PlaceObject3", data); - placeFlagHasClipActions = sis.readUB(1, "placeFlagHasClipActions") == 1; - placeFlagHasClipDepth = sis.readUB(1, "placeFlagHasClipDepth") == 1; - placeFlagHasName = sis.readUB(1, "placeFlagHasName") == 1; - placeFlagHasRatio = sis.readUB(1, "placeFlagHasRatio") == 1; - placeFlagHasColorTransform = sis.readUB(1, "placeFlagHasColorTransform") == 1; - placeFlagHasMatrix = sis.readUB(1, "placeFlagHasMatrix") == 1; - placeFlagHasCharacter = sis.readUB(1, "placeFlagHasCharacter") == 1; - placeFlagMove = sis.readUB(1, "placeFlagMove") == 1; - reserved = sis.readUB(1, "reserved") == 1; - placeFlagOpaqueBackground = sis.readUB(1, "placeFlagOpaqueBackground") == 1; //SWF11 - placeFlagHasVisible = sis.readUB(1, "placeFlagHasVisible") == 1; //SWF11 - placeFlagHasImage = sis.readUB(1, "placeFlagHasImage") == 1; - placeFlagHasClassName = sis.readUB(1, "placeFlagHasClassName") == 1; - placeFlagHasCacheAsBitmap = sis.readUB(1, "placeFlagHasCacheAsBitmap") == 1; - placeFlagHasBlendMode = sis.readUB(1, "placeFlagHasBlendMode") == 1; - placeFlagHasFilterList = sis.readUB(1, "placeFlagHasFilterList") == 1; - - depth = sis.readUI16("depth"); - if (placeFlagHasClassName || (placeFlagHasImage && placeFlagHasCharacter)) { - className = sis.readString("className"); - } - if (placeFlagHasCharacter) { - characterId = sis.readUI16("characterId"); - } - if (placeFlagHasMatrix) { - matrix = sis.readMatrix("matrix"); - } - if (placeFlagHasColorTransform) { - colorTransform = sis.readCXFORMWITHALPHA("colorTransform"); - } - if (placeFlagHasRatio) { - ratio = sis.readUI16("ratio"); - } - if (placeFlagHasName) { - name = sis.readString("name"); - } - if (placeFlagHasClipDepth) { - clipDepth = sis.readUI16("clipDepth"); - } - if (placeFlagHasFilterList) { - surfaceFilterList = sis.readFILTERLIST("surfaceFilterList"); - } - if (placeFlagHasBlendMode) { - blendMode = sis.readUI8("blendMode"); - } - bitmapCacheBug = false; - if (placeFlagHasCacheAsBitmap) { - try { - bitmapCache = sis.readUI8("bitmapCache"); - } catch (EndOfStreamException eex) { - bitmapCacheBug = true; - bitmapCache = 1; - } - } - - if (placeFlagHasVisible) { - visible = sis.readUI8("visible"); - } - if (placeFlagOpaqueBackground) { - backgroundColor = sis.readRGBA("backgroundColor"); - } - - if (placeFlagHasClipActions) { - clipActions = sis.readCLIPACTIONS(swf, this, "clipActions"); - } - } - - /** - * Returns all sub-items - * - * @return List of sub-items - */ - @Override - public List getSubItems() { - List ret = new ArrayList<>(); - if (placeFlagHasClipActions) { - ret.addAll(clipActions.clipActionRecords); - } - return ret; - } - - /** - * Returns number of sub-items - * - * @return Number of sub-items - */ - @Override - public int getItemCount() { - if (!placeFlagHasClipActions) { - return 0; - } - return clipActions.clipActionRecords.size(); - } - - @Override - public void getNeededCharacters(Set needed) { - if (placeFlagHasCharacter) { - needed.add(characterId); - } - } - - @Override - public boolean removeCharacter(int characterId) { - // the place object tag will be removed - return false; - } - - @Override - public int getCharacterId() { - if (placeFlagHasCharacter) { - return characterId; - } else { - return -1; - } - } - - @Override - public int getDepth() { - return depth; - } - - @Override - public MATRIX getMatrix() { - if (placeFlagHasMatrix) { - return matrix; - } else { - return null; - } - } - - @Override - public String getInstanceName() { - if (placeFlagHasName) { - return name; - } - return null; - } - - @Override - public ColorTransform getColorTransform() { - if (placeFlagHasColorTransform) { - return colorTransform; - } else { - return null; - } - } - - @Override - public int getBlendMode() { - return blendMode; - } - - @Override - public String getClassName() { - if (placeFlagHasClassName) { - return className; - } - return null; - } - - @Override - public boolean cacheAsBitmap() { - return placeFlagHasCacheAsBitmap; - } - - @Override - public boolean isVisible() { - if (placeFlagHasVisible) { - return visible == 1; - } - return true; - } - - @Override - public RGBA getBackgroundColor() { - if (placeFlagOpaqueBackground) { - return backgroundColor; - } - return null; - } - - @Override - public boolean flagMove() { - return placeFlagMove; - } - - @Override - public int getRatio() { - if (!placeFlagHasRatio) { - return -1; - } - return ratio; - } - - @Override - public void setInstanceName(String name) { - placeFlagHasName = true; - this.name = name; - } - - @Override - public void setClassName(String className) { - placeFlagHasClassName = true; - this.className = className; - } - - @Override - public String toString() { - if (placeFlagHasName) { - return super.toString() + " (" + name + ")"; - } else { - return super.toString(); - } - } - - @Override - public CLIPACTIONS getClipActions() { - if (placeFlagHasClipActions) { - return clipActions; - } else { - return null; - } - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags; + +import com.jpexs.decompiler.flash.EndOfStreamException; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.abc.CopyOutputStream; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.Container; +import com.jpexs.decompiler.flash.tags.base.ContainerItem; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.CLIPACTIONS; +import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.Reserved; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.filters.FILTER; +import com.jpexs.helpers.ByteArrayRange; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Extends the functionality of the PlaceObject2Tag + * + * @author JPEXS + */ +public class PlaceObject3Tag extends CharacterIdTag implements Container, PlaceObjectTypeTag { + + /** + * @since SWF 5 has clip actions (sprite characters only) + */ + public boolean placeFlagHasClipActions; + /** + * Has clip depth + */ + public boolean placeFlagHasClipDepth; + /** + * Has name + */ + public boolean placeFlagHasName; + /** + * Has ratio + */ + public boolean placeFlagHasRatio; + /** + * Has color transform + */ + public boolean placeFlagHasColorTransform; + /** + * Has matrix + */ + public boolean placeFlagHasMatrix; + /** + * Places a character + */ + public boolean placeFlagHasCharacter; + /** + * Defines a character to be moved + */ + public boolean placeFlagMove; + /** + * Has class name or character ID of bitmap to place. If + * PlaceFlagHasClassName, use ClassName. If PlaceFlagHasCharacter, use + * CharacterId + */ + public boolean placeFlagHasImage; + /** + * Has class name of object to place + */ + public boolean placeFlagHasClassName; + /** + * Enables bitmap caching + */ + public boolean placeFlagHasCacheAsBitmap; + /** + * Has blend mode + */ + public boolean placeFlagHasBlendMode; + /** + * Has filter list + */ + public boolean placeFlagHasFilterList; + /** + * Has opaque background. SWF 11 and higher. + */ + public boolean placeFlagOpaqueBackground; + /** + * Has visibility flag. SWF 11 and higher. + */ + public boolean placeFlagHasVisible; + /** + * Depth of character + */ + @SWFType(BasicType.UI16) + public int depth; + /** + * If PlaceFlagHasClassName or (PlaceFlagHasImage and + * PlaceFlagHasCharacter), Name of the class to place + */ + @Conditional("placeFlagHasClassName") + public String className; + /** + * If PlaceFlagHasCharacter, ID of character to place + */ + @SWFType(BasicType.UI16) + @Conditional("placeFlagHasCharacter") + public int characterId; + /** + * If PlaceFlagHasMatrix, Transform matrix data + */ + @Conditional("placeFlagHasMatrix") + public MATRIX matrix; + /** + * If PlaceFlagHasColorTransform, Color transform data + */ + @Conditional("placeFlagHasColorTransform") + public CXFORMWITHALPHA colorTransform; + /** + * If PlaceFlagHasRatio, Ratio + */ + @SWFType(BasicType.UI16) + @Conditional("placeFlagHasRatio") + public int ratio; + /** + * If PlaceFlagHasName, Name of character + */ + @Conditional("placeFlagHasName") + public String name; + /** + * If PlaceFlagHasClipDepth, Clip depth + */ + @SWFType(BasicType.UI16) + @Conditional("placeFlagHasClipDepth") + public int clipDepth; + /** + * If PlaceFlagHasFilterList, List of filters on this object + */ + @Conditional("placeFlagHasFilterList") + public List surfaceFilterList; + /** + * If PlaceFlagHasBlendMode, Blend mode + */ + @SWFType(BasicType.UI8) + @Conditional("placeFlagHasBlendMode") + public int blendMode; + /** + * If PlaceFlagHasCacheAsBitmap, 0 = Bitmap cache disabled, 1-255 = Bitmap + * cache enabled + */ + @SWFType(BasicType.UI8) + @Conditional("placeFlagHasCacheAsBitmap") + public int bitmapCache; + /** + * @since SWF 5 If PlaceFlagHasClipActions, Clip Actions Data + */ + @Conditional(value = "placeFlagHasClipActions", minSwfVersion = 5) + @Internal //TODO: make editable + public CLIPACTIONS clipActions; + /** + * If PlaceFlagHasVisible, 0 = Place invisible, 1 = Place visible + */ + @Conditional("placeFlagHasVisible") + public int visible; + /** + * If PlaceFlagHasVisible, Background color + */ + @Conditional("placeFlagOpaqueBackground") + public RGBA backgroundColor; + // FIXME bug found in ecoDrive.swf, + @Internal + private boolean bitmapCacheBug; + @Reserved + public boolean reserved; + public static final int ID = 70; + + @Override + public List getFilters() { + if (placeFlagHasFilterList) { + return surfaceFilterList; + } else { + return null; + } + } + + @Override + public int getClipDepth() { + if (placeFlagHasClipDepth) { + return clipDepth; + } + return -1; + } + + /** + * Gets data bytes + * + * @return Bytes of data + */ + @Override + public byte[] getData() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStream os = baos; + if (Configuration.debugCopy.get()) { + os = new CopyOutputStream(os, new ByteArrayInputStream(getOriginalData())); + } + SWFOutputStream sos = new SWFOutputStream(os, getVersion()); + try { + sos.writeUB(1, placeFlagHasClipActions ? 1 : 0); + sos.writeUB(1, placeFlagHasClipDepth ? 1 : 0); + sos.writeUB(1, placeFlagHasName ? 1 : 0); + sos.writeUB(1, placeFlagHasRatio ? 1 : 0); + sos.writeUB(1, placeFlagHasColorTransform ? 1 : 0); + sos.writeUB(1, placeFlagHasMatrix ? 1 : 0); + sos.writeUB(1, placeFlagHasCharacter ? 1 : 0); + sos.writeUB(1, placeFlagMove ? 1 : 0); + sos.writeUB(1, reserved ? 1 : 0); + sos.writeUB(1, placeFlagOpaqueBackground ? 1 : 0); //SWF11 + sos.writeUB(1, placeFlagHasVisible ? 1 : 0); //SWF11 + sos.writeUB(1, placeFlagHasImage ? 1 : 0); + sos.writeUB(1, placeFlagHasClassName ? 1 : 0); + sos.writeUB(1, placeFlagHasCacheAsBitmap ? 1 : 0); + sos.writeUB(1, placeFlagHasBlendMode ? 1 : 0); + sos.writeUB(1, placeFlagHasFilterList ? 1 : 0); + sos.writeUI16(depth); + + if (placeFlagHasClassName) { + sos.writeString(className); + } + if (placeFlagHasCharacter) { + sos.writeUI16(characterId); + } + if (placeFlagHasMatrix) { + sos.writeMatrix(matrix); + } + if (placeFlagHasColorTransform) { + sos.writeCXFORMWITHALPHA(colorTransform); + } + if (placeFlagHasRatio) { + sos.writeUI16(ratio); + } + if (placeFlagHasName) { + sos.writeString(name); + } + if (placeFlagHasClipDepth) { + sos.writeUI16(clipDepth); + } + if (placeFlagHasFilterList) { + sos.writeFILTERLIST(surfaceFilterList); + } + if (placeFlagHasBlendMode) { + sos.writeUI8(blendMode); + } + if (placeFlagHasCacheAsBitmap) { + if (!bitmapCacheBug) { + sos.writeUI8(bitmapCache); + } + } + if (placeFlagHasVisible) { + sos.writeUI8(visible); + } + if (placeFlagOpaqueBackground) { + sos.writeRGBA(backgroundColor); + } + if (placeFlagHasClipActions) { + sos.writeCLIPACTIONS(clipActions); + } + sos.close(); + } catch (IOException ex) { + } + return baos.toByteArray(); + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public PlaceObject3Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, "PlaceObject3", data); + placeFlagHasClipActions = sis.readUB(1, "placeFlagHasClipActions") == 1; + placeFlagHasClipDepth = sis.readUB(1, "placeFlagHasClipDepth") == 1; + placeFlagHasName = sis.readUB(1, "placeFlagHasName") == 1; + placeFlagHasRatio = sis.readUB(1, "placeFlagHasRatio") == 1; + placeFlagHasColorTransform = sis.readUB(1, "placeFlagHasColorTransform") == 1; + placeFlagHasMatrix = sis.readUB(1, "placeFlagHasMatrix") == 1; + placeFlagHasCharacter = sis.readUB(1, "placeFlagHasCharacter") == 1; + placeFlagMove = sis.readUB(1, "placeFlagMove") == 1; + reserved = sis.readUB(1, "reserved") == 1; + placeFlagOpaqueBackground = sis.readUB(1, "placeFlagOpaqueBackground") == 1; //SWF11 + placeFlagHasVisible = sis.readUB(1, "placeFlagHasVisible") == 1; //SWF11 + placeFlagHasImage = sis.readUB(1, "placeFlagHasImage") == 1; + placeFlagHasClassName = sis.readUB(1, "placeFlagHasClassName") == 1; + placeFlagHasCacheAsBitmap = sis.readUB(1, "placeFlagHasCacheAsBitmap") == 1; + placeFlagHasBlendMode = sis.readUB(1, "placeFlagHasBlendMode") == 1; + placeFlagHasFilterList = sis.readUB(1, "placeFlagHasFilterList") == 1; + + depth = sis.readUI16("depth"); + if (placeFlagHasClassName) { + className = sis.readString("className"); + } + if (placeFlagHasCharacter) { + characterId = sis.readUI16("characterId"); + } + if (placeFlagHasMatrix) { + matrix = sis.readMatrix("matrix"); + } + if (placeFlagHasColorTransform) { + colorTransform = sis.readCXFORMWITHALPHA("colorTransform"); + } + if (placeFlagHasRatio) { + ratio = sis.readUI16("ratio"); + } + if (placeFlagHasName) { + name = sis.readString("name"); + } + if (placeFlagHasClipDepth) { + clipDepth = sis.readUI16("clipDepth"); + } + if (placeFlagHasFilterList) { + surfaceFilterList = sis.readFILTERLIST("surfaceFilterList"); + } + if (placeFlagHasBlendMode) { + blendMode = sis.readUI8("blendMode"); + } + bitmapCacheBug = false; + if (placeFlagHasCacheAsBitmap) { + try { + bitmapCache = sis.readUI8("bitmapCache"); + } catch (EndOfStreamException eex) { + bitmapCacheBug = true; + bitmapCache = 1; + } + } + + if (placeFlagHasVisible) { + visible = sis.readUI8("visible"); + } + if (placeFlagOpaqueBackground) { + backgroundColor = sis.readRGBA("backgroundColor"); + } + + if (placeFlagHasClipActions) { + clipActions = sis.readCLIPACTIONS(swf, this, "clipActions"); + } + } + + /** + * Returns all sub-items + * + * @return List of sub-items + */ + @Override + public List getSubItems() { + List ret = new ArrayList<>(); + if (placeFlagHasClipActions) { + ret.addAll(clipActions.clipActionRecords); + } + return ret; + } + + /** + * Returns number of sub-items + * + * @return Number of sub-items + */ + @Override + public int getItemCount() { + if (!placeFlagHasClipActions) { + return 0; + } + return clipActions.clipActionRecords.size(); + } + + @Override + public void getNeededCharacters(Set needed) { + if (placeFlagHasCharacter) { + needed.add(characterId); + } + } + + @Override + public boolean removeCharacter(int characterId) { + // the place object tag will be removed + return false; + } + + @Override + public int getCharacterId() { + if (placeFlagHasCharacter) { + return characterId; + } else { + return -1; + } + } + + @Override + public int getDepth() { + return depth; + } + + @Override + public MATRIX getMatrix() { + if (placeFlagHasMatrix) { + return matrix; + } else { + return null; + } + } + + @Override + public String getInstanceName() { + if (placeFlagHasName) { + return name; + } + return null; + } + + @Override + public ColorTransform getColorTransform() { + if (placeFlagHasColorTransform) { + return colorTransform; + } else { + return null; + } + } + + @Override + public int getBlendMode() { + return blendMode; + } + + @Override + public String getClassName() { + if (placeFlagHasClassName) { + return className; + } + return null; + } + + @Override + public boolean cacheAsBitmap() { + return placeFlagHasCacheAsBitmap; + } + + @Override + public boolean isVisible() { + if (placeFlagHasVisible) { + return visible == 1; + } + return true; + } + + @Override + public RGBA getBackgroundColor() { + if (placeFlagOpaqueBackground) { + return backgroundColor; + } + return null; + } + + @Override + public boolean flagMove() { + return placeFlagMove; + } + + @Override + public int getRatio() { + if (!placeFlagHasRatio) { + return -1; + } + return ratio; + } + + @Override + public void setInstanceName(String name) { + placeFlagHasName = true; + this.name = name; + } + + @Override + public void setClassName(String className) { + placeFlagHasClassName = true; + this.className = className; + } + + @Override + public String toString() { + if (placeFlagHasName) { + return super.toString() + " (" + name + ")"; + } else { + return super.toString(); + } + } + + @Override + public CLIPACTIONS getClipActions() { + if (placeFlagHasClipActions) { + return clipActions; + } else { + return null; + } + } +} diff --git a/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java b/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java index 9dbb91824..a32fa9dd1 100644 --- a/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java +++ b/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java @@ -1,532 +1,533 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.tags; - -import com.jpexs.decompiler.flash.EndOfStreamException; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.abc.CopyOutputStream; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.Container; -import com.jpexs.decompiler.flash.tags.base.ContainerItem; -import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.CLIPACTIONS; -import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.Reserved; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.filters.FILTER; -import com.jpexs.helpers.ByteArrayRange; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * Same as PlaceObject3Tag except additional AMF data - * - * @author JPEXS - */ -public class PlaceObject4Tag extends CharacterIdTag implements Container, PlaceObjectTypeTag { - - /** - * @since SWF 5 has clip actions (sprite characters only) - */ - public boolean placeFlagHasClipActions; - /** - * Has clip depth - */ - public boolean placeFlagHasClipDepth; - /** - * Has name - */ - public boolean placeFlagHasName; - /** - * Has ratio - */ - public boolean placeFlagHasRatio; - /** - * Has color transform - */ - public boolean placeFlagHasColorTransform; - /** - * Has matrix - */ - public boolean placeFlagHasMatrix; - /** - * Places a character - */ - public boolean placeFlagHasCharacter; - /** - * Defines a character to be moved - */ - public boolean placeFlagMove; - /** - * Has class name or character ID of bitmap to place. If - * PlaceFlagHasClassName, use ClassName. If PlaceFlagHasCharacter, use - * CharacterId - */ - public boolean placeFlagHasImage; - /** - * Has class name of object to place - */ - public boolean placeFlagHasClassName; - /** - * Enables bitmap caching - */ - public boolean placeFlagHasCacheAsBitmap; - /** - * Has blend mode - */ - public boolean placeFlagHasBlendMode; - /** - * Has filter list - */ - public boolean placeFlagHasFilterList; - /** - * Has opaque background. SWF 11 and higher. - */ - public boolean placeFlagOpaqueBackground; - /** - * Has visibility flag. SWF 11 and higher. - */ - public boolean placeFlagHasVisible; - /** - * Depth of character - */ - @SWFType(BasicType.UI16) - public int depth; - /** - * If PlaceFlagHasClassName or (PlaceFlagHasImage and - * PlaceFlagHasCharacter), Name of the class to place - */ - @Conditional("placeFlagHasClassName|(placeFlagHasImage,placeFlagHasCharacter)") - public String className; - /** - * If PlaceFlagHasCharacter, ID of character to place - */ - @SWFType(BasicType.UI16) - @Conditional("placeFlagHasCharacter") - public int characterId; - /** - * If PlaceFlagHasMatrix, Transform matrix data - */ - @Conditional("placeFlagHasMatrix") - public MATRIX matrix; - /** - * If PlaceFlagHasColorTransform, Color transform data - */ - @Conditional("placeFlagHasColorTransform") - public CXFORMWITHALPHA colorTransform; - /** - * If PlaceFlagHasRatio, Ratio - */ - @SWFType(BasicType.UI16) - @Conditional("placeFlagHasRatio") - public int ratio; - /** - * If PlaceFlagHasName, Name of character - */ - @Conditional("placeFlagHasName") - public String name; - /** - * If PlaceFlagHasClipDepth, Clip depth - */ - @SWFType(BasicType.UI16) - @Conditional("placeFlagHasClipDepth") - public int clipDepth; - /** - * If PlaceFlagHasFilterList, List of filters on this object - */ - @Conditional("placeFlagHasFilterList") - public List surfaceFilterList; - /** - * If PlaceFlagHasBlendMode, Blend mode - */ - @SWFType(BasicType.UI8) - @Conditional("placeFlagHasBlendMode") - public int blendMode; - /** - * If PlaceFlagHasCacheAsBitmap, 0 = Bitmap cache disabled, 1-255 = Bitmap - * cache enabled - */ - @SWFType(BasicType.UI8) - @Conditional("placeFlagHasCacheAsBitmap") - public int bitmapCache; - /** - * @since SWF 5 If PlaceFlagHasClipActions, Clip Actions Data - */ - @Conditional(value = "placeFlagHasClipActions", minSwfVersion = 5) - @Internal //TODO: make editable - public CLIPACTIONS clipActions; - /** - * If PlaceFlagHasVisible, 0 = Place invisible, 1 = Place visible - */ - @Conditional("placeFlagHasVisible") - public int visible; - /** - * If PlaceFlagHasVisible, Background color - */ - @Conditional("placeFlagOpaqueBackground") - public RGBA backgroundColor; - // FIXME bug found in ecoDrive.swf, - @Internal - private boolean bitmapCacheBug; - @Reserved - public boolean reserved; - - public static final int ID = 94; - public byte[] amfData; //TODO: Parse AMF data? - - @Override - public List getFilters() { - if (placeFlagHasFilterList) { - return surfaceFilterList; - } else { - return null; - } - } - - @Override - public int getClipDepth() { - if (placeFlagHasClipDepth) { - return clipDepth; - } - return -1; - } - - /** - * Gets data bytes - * - * @return Bytes of data - */ - @Override - public byte[] getData() { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - OutputStream os = baos; - if (Configuration.debugCopy.get()) { - os = new CopyOutputStream(os, new ByteArrayInputStream(getOriginalData())); - } - SWFOutputStream sos = new SWFOutputStream(os, getVersion()); - try { - sos.writeUB(1, placeFlagHasClipActions ? 1 : 0); - sos.writeUB(1, placeFlagHasClipDepth ? 1 : 0); - sos.writeUB(1, placeFlagHasName ? 1 : 0); - sos.writeUB(1, placeFlagHasRatio ? 1 : 0); - sos.writeUB(1, placeFlagHasColorTransform ? 1 : 0); - sos.writeUB(1, placeFlagHasMatrix ? 1 : 0); - sos.writeUB(1, placeFlagHasCharacter ? 1 : 0); - sos.writeUB(1, placeFlagMove ? 1 : 0); - sos.writeUB(1, reserved ? 1 : 0); - sos.writeUB(1, placeFlagOpaqueBackground ? 1 : 0); //SWF11 - sos.writeUB(1, placeFlagHasVisible ? 1 : 0); //SWF11 - sos.writeUB(1, placeFlagHasImage ? 1 : 0); - sos.writeUB(1, placeFlagHasClassName ? 1 : 0); - sos.writeUB(1, placeFlagHasCacheAsBitmap ? 1 : 0); - sos.writeUB(1, placeFlagHasBlendMode ? 1 : 0); - sos.writeUB(1, placeFlagHasFilterList ? 1 : 0); - sos.writeUI16(depth); - if (placeFlagHasClassName || (placeFlagHasImage && placeFlagHasCharacter)) { - sos.writeString(className); - } - if (placeFlagHasCharacter) { - sos.writeUI16(characterId); - } - if (placeFlagHasMatrix) { - sos.writeMatrix(matrix); - } - if (placeFlagHasColorTransform) { - sos.writeCXFORMWITHALPHA(colorTransform); - } - if (placeFlagHasRatio) { - sos.writeUI16(ratio); - } - if (placeFlagHasName) { - sos.writeString(name); - } - if (placeFlagHasClipDepth) { - sos.writeUI16(clipDepth); - } - if (placeFlagHasFilterList) { - sos.writeFILTERLIST(surfaceFilterList); - } - if (placeFlagHasBlendMode) { - sos.writeUI8(blendMode); - } - if (placeFlagHasCacheAsBitmap) { - if (!bitmapCacheBug) { - sos.writeUI8(bitmapCache); - } - } - if (placeFlagHasVisible) { - sos.writeUI8(visible); - } - if (placeFlagOpaqueBackground) { - sos.writeRGBA(backgroundColor); - } - if (placeFlagHasClipActions) { - sos.writeCLIPACTIONS(clipActions); - } - sos.close(); - } catch (IOException ex) { - } - return baos.toByteArray(); - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public PlaceObject4Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, "PlaceObject4", data); - placeFlagHasClipActions = sis.readUB(1, "placeFlagHasClipActions") == 1; - placeFlagHasClipDepth = sis.readUB(1, "placeFlagHasClipDepth") == 1; - placeFlagHasName = sis.readUB(1, "placeFlagHasName") == 1; - placeFlagHasRatio = sis.readUB(1, "placeFlagHasRatio") == 1; - placeFlagHasColorTransform = sis.readUB(1, "placeFlagHasColorTransform") == 1; - placeFlagHasMatrix = sis.readUB(1, "placeFlagHasMatrix") == 1; - placeFlagHasCharacter = sis.readUB(1, "placeFlagHasCharacter") == 1; - placeFlagMove = sis.readUB(1, "placeFlagMove") == 1; - reserved = sis.readUB(1, "reserved") == 1; - placeFlagOpaqueBackground = sis.readUB(1, "placeFlagOpaqueBackground") == 1; //SWF11 - placeFlagHasVisible = sis.readUB(1, "placeFlagHasVisible") == 1; //SWF11 - placeFlagHasImage = sis.readUB(1, "placeFlagHasImage") == 1; - placeFlagHasClassName = sis.readUB(1, "placeFlagHasClassName") == 1; - placeFlagHasCacheAsBitmap = sis.readUB(1, "placeFlagHasCacheAsBitmap") == 1; - placeFlagHasBlendMode = sis.readUB(1, "placeFlagHasBlendMode") == 1; - placeFlagHasFilterList = sis.readUB(1, "placeFlagHasFilterList") == 1; - - depth = sis.readUI16("depth"); - if (placeFlagHasClassName || (placeFlagHasImage && placeFlagHasCharacter)) { - className = sis.readString("className"); - } - if (placeFlagHasCharacter) { - characterId = sis.readUI16("characterId"); - } - if (placeFlagHasMatrix) { - matrix = sis.readMatrix("matrix"); - } - if (placeFlagHasColorTransform) { - colorTransform = sis.readCXFORMWITHALPHA("colorTransform"); - } - if (placeFlagHasRatio) { - ratio = sis.readUI16("ratio"); - } - if (placeFlagHasName) { - name = sis.readString("name"); - } - if (placeFlagHasClipDepth) { - clipDepth = sis.readUI16("clipDepth"); - } - if (placeFlagHasFilterList) { - surfaceFilterList = sis.readFILTERLIST("surfaceFilterList"); - } - if (placeFlagHasBlendMode) { - blendMode = sis.readUI8("blendMode"); - } - bitmapCacheBug = false; - if (placeFlagHasCacheAsBitmap) { - try { - bitmapCache = sis.readUI8("bitmapCache"); - } catch (EndOfStreamException eex) { - bitmapCacheBug = true; - bitmapCache = 1; - } - } - - if (placeFlagHasVisible) { - visible = sis.readUI8("visible"); - } - if (placeFlagOpaqueBackground) { - backgroundColor = sis.readRGBA("backgroundColor"); - } - - if (placeFlagHasClipActions) { - clipActions = sis.readCLIPACTIONS(swf, this, "clipActions"); - } - amfData = sis.readBytesEx(sis.available(), "amfData"); - } - - /** - * Returns all sub-items - * - * @return List of sub-items - */ - @Override - public List getSubItems() { - List ret = new ArrayList<>(); - if (placeFlagHasClipActions) { - ret.addAll(clipActions.clipActionRecords); - } - return ret; - } - - /** - * Returns number of sub-items - * - * @return Number of sub-items - */ - @Override - public int getItemCount() { - if (!placeFlagHasClipActions) { - return 0; - } - return clipActions.clipActionRecords.size(); - } - - @Override - public void getNeededCharacters(Set needed) { - if (placeFlagHasCharacter) { - needed.add(characterId); - } - } - - @Override - public boolean removeCharacter(int characterId) { - // the place object tag will be removed - return false; - } - - @Override - public int getCharacterId() { - if (placeFlagHasCharacter) { - return characterId; - } else { - return -1; - } - } - - @Override - public int getDepth() { - return depth; - } - - @Override - public MATRIX getMatrix() { - if (placeFlagHasMatrix) { - return matrix; - } else { - return null; - } - } - - @Override - public String getInstanceName() { - if (placeFlagHasName) { - return name; - } - return null; - } - - @Override - public ColorTransform getColorTransform() { - if (placeFlagHasColorTransform) { - return colorTransform; - } else { - return null; - } - } - - @Override - public int getBlendMode() { - return blendMode; - } - - @Override - public String getClassName() { - if (placeFlagHasClassName) { - return className; - } - return null; - } - - @Override - public boolean cacheAsBitmap() { - return placeFlagHasCacheAsBitmap; - } - - @Override - public boolean isVisible() { - if (placeFlagHasVisible) { - return visible == 1; - } - return true; - } - - @Override - public RGBA getBackgroundColor() { - if (placeFlagOpaqueBackground) { - return backgroundColor; - } - return null; - } - - @Override - public boolean flagMove() { - return placeFlagMove; - } - - @Override - public int getRatio() { - if (!placeFlagHasRatio) { - return -1; - } - return ratio; - } - - @Override - public void setInstanceName(String name) { - placeFlagHasName = true; - this.name = name; - } - - @Override - public void setClassName(String className) { - placeFlagHasClassName = true; - this.className = className; - } - - @Override - public String toString() { - if (placeFlagHasName) { - return super.toString() + " (" + name + ")"; - } else { - return super.toString(); - } - } - - @Override - public CLIPACTIONS getClipActions() { - if (placeFlagHasClipActions) { - return clipActions; - } else { - return null; - } - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags; + +import com.jpexs.decompiler.flash.EndOfStreamException; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.abc.CopyOutputStream; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.Container; +import com.jpexs.decompiler.flash.tags.base.ContainerItem; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.CLIPACTIONS; +import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.Reserved; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.filters.FILTER; +import com.jpexs.helpers.ByteArrayRange; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Same as PlaceObject3Tag except additional AMF data + * + * @author JPEXS + */ +public class PlaceObject4Tag extends CharacterIdTag implements Container, PlaceObjectTypeTag { + + /** + * @since SWF 5 has clip actions (sprite characters only) + */ + public boolean placeFlagHasClipActions; + /** + * Has clip depth + */ + public boolean placeFlagHasClipDepth; + /** + * Has name + */ + public boolean placeFlagHasName; + /** + * Has ratio + */ + public boolean placeFlagHasRatio; + /** + * Has color transform + */ + public boolean placeFlagHasColorTransform; + /** + * Has matrix + */ + public boolean placeFlagHasMatrix; + /** + * Places a character + */ + public boolean placeFlagHasCharacter; + /** + * Defines a character to be moved + */ + public boolean placeFlagMove; + /** + * Has class name or character ID of bitmap to place. If + * PlaceFlagHasClassName, use ClassName. If PlaceFlagHasCharacter, use + * CharacterId + */ + public boolean placeFlagHasImage; + /** + * Has class name of object to place + */ + public boolean placeFlagHasClassName; + /** + * Enables bitmap caching + */ + public boolean placeFlagHasCacheAsBitmap; + /** + * Has blend mode + */ + public boolean placeFlagHasBlendMode; + /** + * Has filter list + */ + public boolean placeFlagHasFilterList; + /** + * Has opaque background. SWF 11 and higher. + */ + public boolean placeFlagOpaqueBackground; + /** + * Has visibility flag. SWF 11 and higher. + */ + public boolean placeFlagHasVisible; + /** + * Depth of character + */ + @SWFType(BasicType.UI16) + public int depth; + /** + * If PlaceFlagHasClassName or (PlaceFlagHasImage and + * PlaceFlagHasCharacter), Name of the class to place + */ + @Conditional("placeFlagHasClassName") + public String className; + /** + * If PlaceFlagHasCharacter, ID of character to place + */ + @SWFType(BasicType.UI16) + @Conditional("placeFlagHasCharacter") + public int characterId; + /** + * If PlaceFlagHasMatrix, Transform matrix data + */ + @Conditional("placeFlagHasMatrix") + public MATRIX matrix; + /** + * If PlaceFlagHasColorTransform, Color transform data + */ + @Conditional("placeFlagHasColorTransform") + public CXFORMWITHALPHA colorTransform; + /** + * If PlaceFlagHasRatio, Ratio + */ + @SWFType(BasicType.UI16) + @Conditional("placeFlagHasRatio") + public int ratio; + /** + * If PlaceFlagHasName, Name of character + */ + @Conditional("placeFlagHasName") + public String name; + /** + * If PlaceFlagHasClipDepth, Clip depth + */ + @SWFType(BasicType.UI16) + @Conditional("placeFlagHasClipDepth") + public int clipDepth; + /** + * If PlaceFlagHasFilterList, List of filters on this object + */ + @Conditional("placeFlagHasFilterList") + public List surfaceFilterList; + /** + * If PlaceFlagHasBlendMode, Blend mode + */ + @SWFType(BasicType.UI8) + @Conditional("placeFlagHasBlendMode") + public int blendMode; + /** + * If PlaceFlagHasCacheAsBitmap, 0 = Bitmap cache disabled, 1-255 = Bitmap + * cache enabled + */ + @SWFType(BasicType.UI8) + @Conditional("placeFlagHasCacheAsBitmap") + public int bitmapCache; + /** + * @since SWF 5 If PlaceFlagHasClipActions, Clip Actions Data + */ + @Conditional(value = "placeFlagHasClipActions", minSwfVersion = 5) + @Internal //TODO: make editable + public CLIPACTIONS clipActions; + /** + * If PlaceFlagHasVisible, 0 = Place invisible, 1 = Place visible + */ + @Conditional("placeFlagHasVisible") + public int visible; + /** + * If PlaceFlagHasVisible, Background color + */ + @Conditional("placeFlagOpaqueBackground") + public RGBA backgroundColor; + // FIXME bug found in ecoDrive.swf, + @Internal + private boolean bitmapCacheBug; + @Reserved + public boolean reserved; + + public static final int ID = 94; + public byte[] amfData; //TODO: Parse AMF data? + + @Override + public List getFilters() { + if (placeFlagHasFilterList) { + return surfaceFilterList; + } else { + return null; + } + } + + @Override + public int getClipDepth() { + if (placeFlagHasClipDepth) { + return clipDepth; + } + return -1; + } + + /** + * Gets data bytes + * + * @return Bytes of data + */ + @Override + public byte[] getData() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStream os = baos; + if (Configuration.debugCopy.get()) { + os = new CopyOutputStream(os, new ByteArrayInputStream(getOriginalData())); + } + SWFOutputStream sos = new SWFOutputStream(os, getVersion()); + try { + sos.writeUB(1, placeFlagHasClipActions ? 1 : 0); + sos.writeUB(1, placeFlagHasClipDepth ? 1 : 0); + sos.writeUB(1, placeFlagHasName ? 1 : 0); + sos.writeUB(1, placeFlagHasRatio ? 1 : 0); + sos.writeUB(1, placeFlagHasColorTransform ? 1 : 0); + sos.writeUB(1, placeFlagHasMatrix ? 1 : 0); + sos.writeUB(1, placeFlagHasCharacter ? 1 : 0); + sos.writeUB(1, placeFlagMove ? 1 : 0); + sos.writeUB(1, reserved ? 1 : 0); + sos.writeUB(1, placeFlagOpaqueBackground ? 1 : 0); //SWF11 + sos.writeUB(1, placeFlagHasVisible ? 1 : 0); //SWF11 + sos.writeUB(1, placeFlagHasImage ? 1 : 0); + sos.writeUB(1, placeFlagHasClassName ? 1 : 0); + sos.writeUB(1, placeFlagHasCacheAsBitmap ? 1 : 0); + sos.writeUB(1, placeFlagHasBlendMode ? 1 : 0); + sos.writeUB(1, placeFlagHasFilterList ? 1 : 0); + sos.writeUI16(depth); + + if (placeFlagHasClassName) { + sos.writeString(className); + } + if (placeFlagHasCharacter) { + sos.writeUI16(characterId); + } + if (placeFlagHasMatrix) { + sos.writeMatrix(matrix); + } + if (placeFlagHasColorTransform) { + sos.writeCXFORMWITHALPHA(colorTransform); + } + if (placeFlagHasRatio) { + sos.writeUI16(ratio); + } + if (placeFlagHasName) { + sos.writeString(name); + } + if (placeFlagHasClipDepth) { + sos.writeUI16(clipDepth); + } + if (placeFlagHasFilterList) { + sos.writeFILTERLIST(surfaceFilterList); + } + if (placeFlagHasBlendMode) { + sos.writeUI8(blendMode); + } + if (placeFlagHasCacheAsBitmap) { + if (!bitmapCacheBug) { + sos.writeUI8(bitmapCache); + } + } + if (placeFlagHasVisible) { + sos.writeUI8(visible); + } + if (placeFlagOpaqueBackground) { + sos.writeRGBA(backgroundColor); + } + if (placeFlagHasClipActions) { + sos.writeCLIPACTIONS(clipActions); + } + sos.close(); + } catch (IOException ex) { + } + return baos.toByteArray(); + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public PlaceObject4Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, "PlaceObject4", data); + placeFlagHasClipActions = sis.readUB(1, "placeFlagHasClipActions") == 1; + placeFlagHasClipDepth = sis.readUB(1, "placeFlagHasClipDepth") == 1; + placeFlagHasName = sis.readUB(1, "placeFlagHasName") == 1; + placeFlagHasRatio = sis.readUB(1, "placeFlagHasRatio") == 1; + placeFlagHasColorTransform = sis.readUB(1, "placeFlagHasColorTransform") == 1; + placeFlagHasMatrix = sis.readUB(1, "placeFlagHasMatrix") == 1; + placeFlagHasCharacter = sis.readUB(1, "placeFlagHasCharacter") == 1; + placeFlagMove = sis.readUB(1, "placeFlagMove") == 1; + reserved = sis.readUB(1, "reserved") == 1; + placeFlagOpaqueBackground = sis.readUB(1, "placeFlagOpaqueBackground") == 1; //SWF11 + placeFlagHasVisible = sis.readUB(1, "placeFlagHasVisible") == 1; //SWF11 + placeFlagHasImage = sis.readUB(1, "placeFlagHasImage") == 1; + placeFlagHasClassName = sis.readUB(1, "placeFlagHasClassName") == 1; + placeFlagHasCacheAsBitmap = sis.readUB(1, "placeFlagHasCacheAsBitmap") == 1; + placeFlagHasBlendMode = sis.readUB(1, "placeFlagHasBlendMode") == 1; + placeFlagHasFilterList = sis.readUB(1, "placeFlagHasFilterList") == 1; + + depth = sis.readUI16("depth"); + if (placeFlagHasClassName) { + className = sis.readString("className"); + } + if (placeFlagHasCharacter) { + characterId = sis.readUI16("characterId"); + } + if (placeFlagHasMatrix) { + matrix = sis.readMatrix("matrix"); + } + if (placeFlagHasColorTransform) { + colorTransform = sis.readCXFORMWITHALPHA("colorTransform"); + } + if (placeFlagHasRatio) { + ratio = sis.readUI16("ratio"); + } + if (placeFlagHasName) { + name = sis.readString("name"); + } + if (placeFlagHasClipDepth) { + clipDepth = sis.readUI16("clipDepth"); + } + if (placeFlagHasFilterList) { + surfaceFilterList = sis.readFILTERLIST("surfaceFilterList"); + } + if (placeFlagHasBlendMode) { + blendMode = sis.readUI8("blendMode"); + } + bitmapCacheBug = false; + if (placeFlagHasCacheAsBitmap) { + try { + bitmapCache = sis.readUI8("bitmapCache"); + } catch (EndOfStreamException eex) { + bitmapCacheBug = true; + bitmapCache = 1; + } + } + + if (placeFlagHasVisible) { + visible = sis.readUI8("visible"); + } + if (placeFlagOpaqueBackground) { + backgroundColor = sis.readRGBA("backgroundColor"); + } + + if (placeFlagHasClipActions) { + clipActions = sis.readCLIPACTIONS(swf, this, "clipActions"); + } + amfData = sis.readBytesEx(sis.available(), "amfData"); + } + + /** + * Returns all sub-items + * + * @return List of sub-items + */ + @Override + public List getSubItems() { + List ret = new ArrayList<>(); + if (placeFlagHasClipActions) { + ret.addAll(clipActions.clipActionRecords); + } + return ret; + } + + /** + * Returns number of sub-items + * + * @return Number of sub-items + */ + @Override + public int getItemCount() { + if (!placeFlagHasClipActions) { + return 0; + } + return clipActions.clipActionRecords.size(); + } + + @Override + public void getNeededCharacters(Set needed) { + if (placeFlagHasCharacter) { + needed.add(characterId); + } + } + + @Override + public boolean removeCharacter(int characterId) { + // the place object tag will be removed + return false; + } + + @Override + public int getCharacterId() { + if (placeFlagHasCharacter) { + return characterId; + } else { + return -1; + } + } + + @Override + public int getDepth() { + return depth; + } + + @Override + public MATRIX getMatrix() { + if (placeFlagHasMatrix) { + return matrix; + } else { + return null; + } + } + + @Override + public String getInstanceName() { + if (placeFlagHasName) { + return name; + } + return null; + } + + @Override + public ColorTransform getColorTransform() { + if (placeFlagHasColorTransform) { + return colorTransform; + } else { + return null; + } + } + + @Override + public int getBlendMode() { + return blendMode; + } + + @Override + public String getClassName() { + if (placeFlagHasClassName) { + return className; + } + return null; + } + + @Override + public boolean cacheAsBitmap() { + return placeFlagHasCacheAsBitmap; + } + + @Override + public boolean isVisible() { + if (placeFlagHasVisible) { + return visible == 1; + } + return true; + } + + @Override + public RGBA getBackgroundColor() { + if (placeFlagOpaqueBackground) { + return backgroundColor; + } + return null; + } + + @Override + public boolean flagMove() { + return placeFlagMove; + } + + @Override + public int getRatio() { + if (!placeFlagHasRatio) { + return -1; + } + return ratio; + } + + @Override + public void setInstanceName(String name) { + placeFlagHasName = true; + this.name = name; + } + + @Override + public void setClassName(String className) { + placeFlagHasClassName = true; + this.className = className; + } + + @Override + public String toString() { + if (placeFlagHasName) { + return super.toString() + " (" + name + ")"; + } else { + return super.toString(); + } + } + + @Override + public CLIPACTIONS getClipActions() { + if (placeFlagHasClipActions) { + return clipActions; + } else { + return null; + } + } +} diff --git a/src/com/jpexs/decompiler/flash/tags/base/CharacterIdTag.java b/src/com/jpexs/decompiler/flash/tags/base/CharacterIdTag.java index eaa1dce21..219ab1b2d 100644 --- a/src/com/jpexs/decompiler/flash/tags/base/CharacterIdTag.java +++ b/src/com/jpexs/decompiler/flash/tags/base/CharacterIdTag.java @@ -1,88 +1,76 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.tags.base; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.tags.ExportAssetsTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.helpers.ByteArrayRange; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author JPEXS - */ -public abstract class CharacterIdTag extends Tag { - - public CharacterIdTag(SWF swf, int id, String name, ByteArrayRange data) { - super(swf, id, name, data); - } - - public abstract int getCharacterId(); - /** - * List of ExportAssetsTag used for converting to String - */ - @Internal - public List exportAssetsTags = new ArrayList<>(); - protected String className; - protected String exportName; - - public void setExportName(String exportName) { - this.exportName = exportName; - } - - public void setClassName(String className) { - this.className = className; - } - - public String getClassName() { - return className; - } - - @Override - public String getName() { - String nameAppend = ""; - if (exportName != null) { - nameAppend = ": " + exportName; - } - if (className != null) { - nameAppend = ": " + className; - } - if (getCharacterId() != -1) { - return super.getName() + " (" + getCharacterId() + nameAppend + ")"; - } - if (!nameAppend.isEmpty()) { - return super.getName() + " (" + nameAppend + ")"; - } - return super.getName(); - } - - @Override - public String getExportFileName() { - return super.getName() + "_" + getCharacterId() + (((exportName != null) && (!exportName.isEmpty())) ? "_" + exportName : ""); - } - - public String getCharacterExportFileName() { - return getCharacterId() + (((exportName != null) && (!exportName.isEmpty())) ? "_" + exportName : ""); - } - - public String getExportName() { - return exportName; - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.tags.ExportAssetsTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.helpers.ByteArrayRange; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public abstract class CharacterIdTag extends Tag { + + public CharacterIdTag(SWF swf, int id, String name, ByteArrayRange data) { + super(swf, id, name, data); + } + + public abstract int getCharacterId(); + /** + * List of ExportAssetsTag used for converting to String + */ + @Internal + public List exportAssetsTags = new ArrayList<>(); + protected String exportName; + + public void setExportName(String exportName) { + this.exportName = exportName; + } + + @Override + public String getName() { + String nameAppend = ""; + if (exportName != null) { + nameAppend = ": " + exportName; + } + if (getCharacterId() != -1) { + return super.getName() + " (" + getCharacterId() + nameAppend + ")"; + } + if (!nameAppend.isEmpty()) { + return super.getName() + " (" + nameAppend + ")"; + } + return super.getName(); + } + + @Override + public String getExportFileName() { + return super.getName() + "_" + getCharacterId() + (((exportName != null) && (!exportName.isEmpty())) ? "_" + exportName : ""); + } + + public String getCharacterExportFileName() { + return getCharacterId() + (((exportName != null) && (!exportName.isEmpty())) ? "_" + exportName : ""); + } + + public String getExportName() { + return exportName; + } +} diff --git a/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java b/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java index 40ff74a35..dd9a341c8 100644 --- a/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java +++ b/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java @@ -1,31 +1,59 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.tags.base; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.helpers.ByteArrayRange; - -/** - * - * @author JPEXS - */ -public abstract class CharacterTag extends CharacterIdTag { - - public CharacterTag(SWF swf, int id, String name, ByteArrayRange data) { - super(swf, id, name, data); - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.helpers.ByteArrayRange; + +/** + * + * @author JPEXS + */ +public abstract class CharacterTag extends CharacterIdTag { + + protected String className; + + public CharacterTag(SWF swf, int id, String name, ByteArrayRange data) { + super(swf, id, name, data); + } + + public void setClassName(String className) { + this.className = className; + } + + public String getClassName() { + return className; + } + + @Override + public String getName() { + String nameAppend = ""; + if (exportName != null) { + nameAppend = ": " + exportName; + } + if (className != null) { + nameAppend = ": " + className; + } + if (getCharacterId() != -1) { + return tagName + " (" + getCharacterId() + nameAppend + ")"; + } + if (!nameAppend.isEmpty()) { + return tagName + " (" + nameAppend + ")"; + } + return tagName; + } +}