/* * Copyright (C) 2010-2015 JPEXS, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.jpexs.decompiler.flash; import SevenZip.Compression.LZMA.Decoder; import SevenZip.Compression.LZMA.Encoder; import com.jpexs.debugger.flash.SWD; import com.jpexs.decompiler.flash.abc.ABC; import com.jpexs.decompiler.flash.abc.CachedDecompilation; 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.abc.avm2.AVM2Code; import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.DeobfuscationLevel; import com.jpexs.decompiler.flash.abc.types.ConvertData; import com.jpexs.decompiler.flash.abc.types.MethodBody; import com.jpexs.decompiler.flash.abc.types.ScriptInfo; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.ActionGraphSource; import com.jpexs.decompiler.flash.action.ActionList; import com.jpexs.decompiler.flash.action.ActionListReader; import com.jpexs.decompiler.flash.action.ActionLocalData; import com.jpexs.decompiler.flash.action.CachedScript; 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.DumpInfo; import com.jpexs.decompiler.flash.dumpview.DumpInfoSwfNode; import com.jpexs.decompiler.flash.ecma.Null; 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.ScriptExportMode; import com.jpexs.decompiler.flash.exporters.script.AS2ScriptExporter; import com.jpexs.decompiler.flash.exporters.script.AS3ScriptExporter; import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings; import com.jpexs.decompiler.flash.exporters.shape.ShapeExportData; import com.jpexs.decompiler.flash.helpers.HighlightedText; import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; import com.jpexs.decompiler.flash.helpers.collections.MyEntry; import com.jpexs.decompiler.flash.helpers.hilight.Highlighting; import com.jpexs.decompiler.flash.tags.ABCContainerTag; import com.jpexs.decompiler.flash.tags.DebugIDTag; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DoInitActionTag; import com.jpexs.decompiler.flash.tags.EnableDebugger2Tag; import com.jpexs.decompiler.flash.tags.EnableDebuggerTag; import com.jpexs.decompiler.flash.tags.EnableTelemetryTag; 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.MetadataTag; import com.jpexs.decompiler.flash.tags.ProtectTag; 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.ASMSourceContainer; 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.DrawableTag; import com.jpexs.decompiler.flash.tags.base.Exportable; 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.RenderContext; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.tags.enums.ImageFormat; import com.jpexs.decompiler.flash.timeline.AS2Package; 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.FrameScript; import com.jpexs.decompiler.flash.timeline.SvgClip; import com.jpexs.decompiler.flash.timeline.TagScript; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.treeitems.SWFList; import com.jpexs.decompiler.flash.treeitems.TreeItem; 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.SHAPE; import com.jpexs.decompiler.flash.types.annotations.Internal; import com.jpexs.decompiler.flash.types.filters.BlendComposite; import com.jpexs.decompiler.flash.types.filters.FILTER; import com.jpexs.decompiler.flash.xfl.FLAVersion; import com.jpexs.decompiler.flash.xfl.XFLConverter; import com.jpexs.decompiler.graph.DottedChain; 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.TranslateStack; import com.jpexs.decompiler.graph.model.LocalData; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Cache; 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 java.awt.AlphaComposite; import java.awt.Color; 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.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; 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.Collection; import java.util.Collections; import java.util.Date; import java.util.EmptyStackException; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.Stack; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; /** * Class representing SWF file * * @author JPEXS */ public final class SWF implements SWFContainerItem, Timelined { // big object for testing cleanup //BigObject bigObj = new BigObject(); /** * Default version of SWF file format */ public static final int DEFAULT_VERSION = 10; /** * Maximum SWF file format version Needs to be fixed when SWF versions * reaches this value */ public static final int MAX_VERSION = 30; /** * Tags inside of file */ public List tags = new ArrayList<>(); @Internal public boolean hasEndTag = true; /** * ExportRectangle for the display */ public RECT displayRect; /** * Movie frame rate */ public float frameRate; /** * Number of frames in movie */ public int frameCount; /** * Version of SWF */ public int version; /** * Uncompressed size of the file */ @Internal public long fileSize; /** * Used compression mode */ public SWFCompression compression = SWFCompression.NONE; /** * Compressed size of the file (LZMA) */ @Internal public long compressedSize; /** * LZMA Properties */ public byte[] lzmaProperties; @Internal public byte[] uncompressedData; @Internal public byte[] originalUncompressedData; /** * ScaleForm GFx */ public boolean gfx = false; @Internal public SWFList swfList; @Internal private String file; @Internal private String fileTitle; @Internal private volatile Map characters; @Internal private volatile Map> dependentCharacters; @Internal private volatile List abcList; @Internal private volatile JPEGTablesTag jtt; @Internal public Map sourceFontNamesMap = new HashMap<>(); public static final double unitDivisor = 20; private static final Logger logger = Logger.getLogger(SWF.class.getName()); @Internal private Timeline timeline; @Internal public DumpInfoSwfNode dumpInfo; @Internal public DefineBinaryDataTag binaryData; @Internal private final HashMap deobfuscated = new HashMap<>(); @Internal private final IdentifiersDeobfuscation deobfuscation = new IdentifiersDeobfuscation(); @Internal private final Cache frameCache = Cache.getInstance(false, false, "frame"); @Internal private final Cache rectCache = Cache.getInstance(true, true, "rect"); @Internal private final Cache shapeExportDataCache = Cache.getInstance(true, true, "shapeExportData"); @Internal private final Cache soundCache = Cache.getInstance(false, false, "sound"); @Internal private final Cache as2PcodeCache = Cache.getInstance(true, true, "as2pcode"); @Internal private final Cache as2Cache = Cache.getInstance(true, false, "as2"); @Internal private final Cache as3Cache = Cache.getInstance(true, false, "as3"); public static List swfSignatures = Arrays.asList( "FWS", // Uncompressed Flash "CWS", // ZLib compressed Flash "ZWS", // LZMA compressed Flash "GFX", // Uncompressed ScaleForm GFx "CFX", // Compressed ScaleForm GFx "ABC" // Non-standard LZMA compressed Flash ); public void updateCharacters() { characters = null; } public void clearTagSwfs() { resetTimelines(this); updateCharacters(); for (Tag tag : tags) { if (tag instanceof DefineSpriteTag) { DefineSpriteTag spriteTag = (DefineSpriteTag) tag; for (Tag tag1 : spriteTag.subTags) { tag1.setSwf(null); } spriteTag.subTags.clear(); } if (tag instanceof DefineBinaryDataTag) { DefineBinaryDataTag binaryTag = (DefineBinaryDataTag) tag; if (binaryTag.innerSwf != null) { binaryTag.innerSwf.clearTagSwfs(); } } tag.setSwf(null); } tags.clear(); if (abcList != null) { abcList.clear(); } if (swfList != null) { swfList.swfs.clear(); } as2PcodeCache.clear(); as2Cache.clear(); as3Cache.clear(); frameCache.clear(); soundCache.clear(); timeline = null; clearDumpInfo(dumpInfo); dumpInfo = null; jtt = null; binaryData = null; } private void clearDumpInfo(DumpInfo di) { for (DumpInfo childInfo : di.getChildInfos()) { clearDumpInfo(childInfo); } di.getChildInfos().clear(); } public Map getCharacters() { if (characters == null) { synchronized (this) { if (characters == null) { Map chars = new HashMap<>(); parseCharacters(tags, chars); characters = Collections.unmodifiableMap(chars); } } } return characters; } public Map> getDependentCharacters() { if (dependentCharacters == null) { synchronized (this) { if (dependentCharacters == null) { Map> dep = new HashMap<>(); for (Tag tag : tags) { if (tag instanceof CharacterTag) { int characterId = ((CharacterTag) tag).getCharacterId(); Set needed = new HashSet<>(); tag.getNeededCharacters(needed); for (Integer needed1 : needed) { Set s = dep.get(needed1); if (s == null) { s = new HashSet<>(); dep.put(needed1, s); } s.add(characterId); } } } dependentCharacters = dep; } } } return dependentCharacters; } public Set getDependentCharacters(int characterId) { Set visited = new HashSet<>(); Set dependents2 = new LinkedHashSet<>(); Set deps = getDependentCharacters().get(characterId); if (deps != null) { dependents2.addAll(deps); } while (visited.size() != dependents2.size()) { for (int chId : dependents2) { if (!visited.contains(chId)) { visited.add(chId); if (getCharacters().containsKey(chId)) { deps = getDependentCharacters().get(chId); if (deps != null) { dependents2.addAll(deps); } break; } } } } Set dependents = new LinkedHashSet<>(); for (Integer chId : dependents2) { if (getCharacters().containsKey(chId)) { dependents.add(chId); } } return dependents; } public CharacterTag getCharacter(int characterId) { return getCharacters().get(characterId); } public String getExportName(int characterId) { CharacterTag characterTag = getCharacters().get(characterId); String exportName = characterTag != null ? characterTag.getExportName() : null; return exportName; } public FontTag getFont(int fontId) { CharacterTag characterTag = getCharacters().get(fontId); if (characterTag instanceof FontTag) { return (FontTag) characterTag; } if (characterTag != null) { logger.log(Level.SEVERE, "CharacterTag should be a FontTag. characterId: {0}", fontId); } return null; } public ImageTag getImage(int imageId) { CharacterTag characterTag = getCharacters().get(imageId); if (characterTag instanceof ImageTag) { return (ImageTag) characterTag; } if (characterTag != null) { logger.log(Level.SEVERE, "CharacterTag should be an ImageTag. characterId: {0}", imageId); } return null; } public TextTag getText(int textId) { CharacterTag characterTag = getCharacters().get(textId); if (characterTag instanceof TextTag) { return (TextTag) characterTag; } if (characterTag != null) { logger.log(Level.SEVERE, "CharacterTag should be a TextTag. characterId: {0}", textId); } return null; } public List getAbcList() { if (abcList == null) { synchronized (this) { if (abcList == null) { ArrayList newAbcList = new ArrayList<>(); getAbcTags(tags, newAbcList); abcList = newAbcList; } } } return abcList; } public boolean isAS3() { FileAttributesTag fileAttributes = getFileAttributes(); return (fileAttributes != null && fileAttributes.actionScript3) || (fileAttributes == null && !getAbcList().isEmpty()); } public MetadataTag getMetadata() { for (Tag t : tags) { if (t instanceof MetadataTag) { return (MetadataTag) t; } } return null; } public FileAttributesTag getFileAttributes() { for (Tag t : tags) { if (t instanceof FileAttributesTag) { return (FileAttributesTag) t; } } return null; } public EnableTelemetryTag getEnableTelemetry() { for (Tag t : tags) { if (t instanceof EnableTelemetryTag) { return (EnableTelemetryTag) t; } } return null; } public int getNextCharacterId() { int max = 0; for (int characterId : getCharacters().keySet()) { if (characterId > max) { max = characterId; } } return max + 1; } public synchronized JPEGTablesTag getJtt() { if (jtt == null) { synchronized (this) { if (jtt == null) { for (Tag t : tags) { if (t instanceof JPEGTablesTag) { jtt = (JPEGTablesTag) t; break; } } } } } return jtt; } public String getDocumentClass() { for (Tag t : tags) { if (t instanceof SymbolClassTag) { SymbolClassTag sc = (SymbolClassTag) t; for (int i = 0; i < sc.tags.size(); i++) { if (sc.tags.get(i) == 0) { return sc.names.get(i); } } } } return null; } public void fixCharactersOrder(boolean checkAll) { Set addedCharacterIds = new HashSet<>(); Set movedTags = new HashSet<>(); for (int i = 0; i < tags.size(); i++) { Tag tag = tags.get(i); if (checkAll || tag.isModified()) { Set needed = new HashSet<>(); tag.getNeededCharacters(needed); if (tag instanceof CharacterTag) { CharacterTag characterTag = (CharacterTag) tag; needed.remove(characterTag.getCharacterId()); } boolean moved = false; for (Integer id : needed) { if (!addedCharacterIds.contains(id)) { CharacterTag neededCharacter = getCharacter(id); if (neededCharacter == null) { continue; } if (movedTags.contains(neededCharacter)) { logger.log(Level.SEVERE, "Fixing characters order failed, recursion detected."); return; } // move the needed character to the current position tags.remove(neededCharacter); tags.add(i, neededCharacter); movedTags.add(neededCharacter); moved = true; } } if (moved) { i--; continue; } } if (tag instanceof CharacterTag) { addedCharacterIds.add(((CharacterTag) tag).getCharacterId()); } } } public void resetTimelines(Timelined timelined) { timelined.resetTimeline(); if (timelined instanceof SWF) { for (Tag t : ((SWF) timelined).tags) { if (t instanceof Timelined) { resetTimelines((Timelined) t); } } } } private void parseCharacters(List list, Map characters) { for (Tag t : list) { if (t instanceof CharacterTag) { int characterId = ((CharacterTag) t).getCharacterId(); if (characters.containsKey(characterId)) { logger.log(Level.SEVERE, "SWF already contains characterId={0}", characterId); } if (characterId != 0) { characters.put(characterId, (CharacterTag) t); } } if (t instanceof DefineSpriteTag) { parseCharacters(((DefineSpriteTag) t).getSubTags(), characters); } } } /** * 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() { if (timeline != null) { timeline.reset(this); } } /** * 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 { byte[] uncompressedData = saveToByteArray(); compress(new ByteArrayInputStream(uncompressedData), os, compression, lzmaProperties); } public byte[] getHeaderBytes() { return getHeaderBytes(compression, gfx); } private static byte[] getHeaderBytes(SWFCompression compression, boolean gfx) { if (compression == SWFCompression.LZMA_ABC) { return new byte[]{'A', 'B', 'C'}; } byte[] ret = new byte[3]; if (compression == SWFCompression.LZMA) { ret[0] = 'Z'; } else if (compression == SWFCompression.ZLIB) { ret[0] = 'C'; } else { if (gfx) { ret[0] = 'G'; } else { ret[0] = 'F'; } } if (gfx) { ret[1] = 'F'; ret[2] = 'X'; } else { ret[1] = 'W'; ret[2] = 'S'; } return ret; } private byte[] saveToByteArray() throws IOException { fixCharactersOrder(false); byte[] data; try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); SWFOutputStream sos = new SWFOutputStream(baos, version)) { sos.write(getHeaderBytes(SWFCompression.NONE, gfx)); sos.writeUI8(version); sos.writeUI32(0); // placeholder for file length sos.writeRECT(displayRect); sos.writeFIXED8(frameRate); sos.writeUI16(frameCount); sos.writeTags(tags); if (hasEndTag) { sos.writeUI16(0); } data = baos.toByteArray(); } // update file size try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); SWFOutputStream sos = new SWFOutputStream(baos, version)) { sos.writeUI32(data.length); byte[] lengthData = baos.toByteArray(); System.arraycopy(lengthData, 0, data, 4, lengthData.length); } return data; } /** * Compress SWF file * * @param is InputStream * @param os OutputStream to save SWF in * @param compression * @param lzmaProperties * @throws IOException */ private static void compress(InputStream is, OutputStream os, SWFCompression compression, byte[] lzmaProperties) throws IOException { byte[] hdr = new byte[8]; is.mark(8); // SWFheader: signature, version and fileSize if (is.read(hdr) != 8) { throw new SwfOpenException("SWF header is too short"); } boolean uncompressed = hdr[0] == 'F' || hdr[0] == 'G'; // FWS or GFX if (!uncompressed) { // fisrt decompress, then compress to the given format is.reset(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); decompress(is, baos, false); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); compress(bais, os, compression, lzmaProperties); return; } boolean gfx = hdr[1] == 'F' && hdr[2] == 'X'; int version = hdr[3]; long fileSize; try (SWFInputStream sis = new SWFInputStream(null, Arrays.copyOfRange(hdr, 4, 8), 4, 4)) { fileSize = sis.readUI32("fileSize"); } SWFOutputStream sos = new SWFOutputStream(os, version); sos.write(getHeaderBytes(compression, gfx)); sos.writeUI8(version); sos.writeUI32(fileSize); if (compression == SWFCompression.LZMA || compression == SWFCompression.LZMA_ABC) { long uncompressedLength = fileSize - 8; Encoder enc = new Encoder(); if (lzmaProperties == null) { // todo: the bytes are from a sample swf lzmaProperties = new byte[]{93, 0, 0, 32, 0}; } int val = lzmaProperties[0] & 0xFF; int lc = val % 9; int remainder = val / 9; 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); } if (Configuration.lzmaFastBytes.get() > 0) { enc.SetNumFastBytes(Configuration.lzmaFastBytes.get()); } enc.SetDictionarySize(dictionarySize); enc.SetLcLpPb(lc, lp, pb); ByteArrayOutputStream baos = new ByteArrayOutputStream(); enc.SetEndMarkerMode(true); enc.Code(is, baos, -1, -1, null); byte[] data = baos.toByteArray(); if (compression == SWFCompression.LZMA) { byte[] udata = new byte[4]; udata[0] = (byte) (data.length & 0xFF); udata[1] = (byte) ((data.length >> 8) & 0xFF); udata[2] = (byte) ((data.length >> 16) & 0xFF); udata[3] = (byte) ((data.length >> 24) & 0xFF); os.write(udata); } enc.WriteCoderProperties(os); if (compression == SWFCompression.LZMA_ABC) { byte[] udata = new byte[8]; udata[0] = (byte) (uncompressedLength & 0xFF); udata[1] = (byte) ((uncompressedLength >> 8) & 0xFF); udata[2] = (byte) ((uncompressedLength >> 16) & 0xFF); udata[3] = (byte) ((uncompressedLength >> 24) & 0xFF); udata[4] = (byte) ((uncompressedLength >> 32) & 0xFF); udata[5] = (byte) ((uncompressedLength >> 40) & 0xFF); udata[6] = (byte) ((uncompressedLength >> 48) & 0xFF); udata[7] = (byte) ((uncompressedLength >> 56) & 0xFF); os.write(udata); } os.write(data); } else if (compression == SWFCompression.ZLIB) { DeflaterOutputStream dos = new DeflaterOutputStream(os); try { Helper.copyStream(is, dos); } finally { dos.finish(); } } else { Helper.copyStream(is, os); } } @Override public boolean isModified() { for (Tag tag : tags) { if (tag.isModified()) { return true; } } return false; } public void clearModified() { for (Tag tag : tags) { if (tag.isModified()) { tag.createOriginalData(); tag.setModified(false); } } try { uncompressedData = saveToByteArray(); } catch (IOException ex) { logger.log(Level.SEVERE, "Cannot save SWF", ex); } } /** * Constructs an empty SWF */ public SWF() { } /** * Construct SWF from stream * * @param is Stream to read SWF from * @param parallelRead Use parallel threads? * @throws IOException * @throws java.lang.InterruptedException */ public SWF(InputStream is, boolean parallelRead) throws IOException, InterruptedException { this(is, null, null, null, parallelRead, false, true); } /** * Construct SWF from stream * * @param is Stream to read SWF from * @param parallelRead Use parallel threads? * @param lazy * @throws IOException * @throws java.lang.InterruptedException */ public SWF(InputStream is, boolean parallelRead, boolean lazy) throws IOException, InterruptedException { this(is, null, null, null, parallelRead, false, lazy); } /** * Construct SWF from stream * * @param is Stream to read SWF from * @param file Path to the file * @param fileTitle Title of the SWF * @param parallelRead Use parallel threads? * @throws IOException * @throws java.lang.InterruptedException */ public SWF(InputStream is, String file, String fileTitle, boolean parallelRead) throws IOException, InterruptedException { this(is, file, fileTitle, null, parallelRead, false, true); } /** * 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, null, null, listener, parallelRead, false, true); } /** * Construct SWF from stream * * @param is Stream to read SWF from * @param file Path to the file * @param fileTitle Title of the SWF * @param listener * @param parallelRead Use parallel threads? * @throws IOException * @throws java.lang.InterruptedException */ public SWF(InputStream is, String file, String fileTitle, ProgressListener listener, boolean parallelRead) throws IOException, InterruptedException { this(is, file, fileTitle, listener, parallelRead, false, true); } /** * 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 file Path to the file * @param fileTitle Title of the SWF * @param listener * @param parallelRead Use parallel threads? * @param checkOnly Check only file validity * @param lazy * @throws IOException * @throws java.lang.InterruptedException */ public SWF(InputStream is, String file, String fileTitle, ProgressListener listener, boolean parallelRead, boolean checkOnly, boolean lazy) throws IOException, InterruptedException { this.file = file; this.fileTitle = fileTitle; ByteArrayOutputStream baos = new ByteArrayOutputStream(); SWFHeader header = decompress(is, baos, true); gfx = header.gfx; compression = header.compression; lzmaProperties = header.lzmaProperties; uncompressedData = baos.toByteArray(); originalUncompressedData = uncompressedData; SWFInputStream sis = new SWFInputStream(this, uncompressedData); dumpInfo = new DumpInfoSwfNode(this, "rootswf", "", null, 0, 0); sis.dumpInfo = dumpInfo; sis.skipBytesEx(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"); frameRate = sis.readFIXED8("frameRate"); frameCount = sis.readUI16("frameCount"); List tags = sis.readTagList(this, 0, parallelRead, true, !checkOnly, lazy); if (tags.size() > 0 && tags.get(tags.size() - 1).getId() == EndTag.ID) { tags.remove(tags.size() - 1); } else { hasEndTag = false; } this.tags = tags; if (!checkOnly) { checkInvalidSprites(); updateCharacters(); assignExportNamesToSymbols(); assignClassesToSymbols(); SWFDecompilerPlugin.fireSwfParsed(this); } 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."); } } getASMs(true); // Add scriptNames to ASMs } @Override public SWF getSwf() { return this; } public SWF getRootSwf() { SWF result = this; while (result.binaryData != null) { result = result.binaryData.getSwf(); } return result; } public String getFile() { return file; } /** * 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(); } public void setFile(String file) { this.file = file; fileTitle = null; } public Date getFileModificationDate() { try { if (swfList != null && swfList.sourceInfo != null) { String fileName = swfList.sourceInfo.getFile(); if (fileName != null) { long lastModified = new File(fileName).lastModified(); if (lastModified > 0) { return new Date(lastModified); } } } } catch (SecurityException sex) { } return new Date(); } private static void getAbcTags(List list, List actionScripts) { for (Tag t : list) { if (t instanceof DefineSpriteTag) { getAbcTags(((DefineSpriteTag) t).getSubTags(), actionScripts); } if (t instanceof ABCContainerTag) { actionScripts.add((ABCContainerTag) t); } } } public 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++) { Integer tagId = eat.tags.get(i); String name = eat.names.get(i); if ((!exportNames.containsKey(tagId)) && (!exportNames.containsValue(name))) { exportNames.put(tagId, name); } } } } for (Tag t : tags) { if (t instanceof CharacterTag) { CharacterTag ct = (CharacterTag) 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.size(); i++) { if ((!classes.containsKey(sct.tags.get(i))) && (!classes.containsValue(sct.names.get(i)))) { classes.put(sct.tags.get(i), sct.names.get(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 * @param compression * @return True on success */ public static boolean compress(InputStream fis, OutputStream fos, SWFCompression compression) { try { compress(fis, fos, compression, null); } 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 void decodeLZMAStream(InputStream is, OutputStream os, byte[] lzmaProperties, long fileSize) throws IOException { Decoder decoder = new 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"); } } 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 SwfOpenException("SWF header is too short"); } String signature = new String(hdr, 0, 3, Utf8Helper.charset); if (!swfSignatures.contains(signature)) { throw new SwfOpenException("Invalid SWF file, wrong signature."); } int version = hdr[3]; long fileSize; try (SWFInputStream sis = new SWFInputStream(null, Arrays.copyOfRange(hdr, 4, 8), 4, 4)) { fileSize = sis.readUI32("fileSize"); } SWFHeader header = new SWFHeader(); header.version = version; header.fileSize = fileSize; header.gfx = hdr[1] == 'F' && hdr[2] == 'X'; try (SWFOutputStream sos = new SWFOutputStream(os, version)) { sos.write(getHeaderBytes(SWFCompression.NONE, header.gfx)); 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 byte[] lzmaprop = new byte[9]; is.read(lzmaprop); try (SWFInputStream sis = new SWFInputStream(null, lzmaprop)) { sis.readUI32("LZMAsize"); // compressed LZMA data size = compressed SWF - 17 byte, // where 17 = 8 byte header + this 4 byte + 5 bytes decoder properties int propertiesSize = 5; byte[] lzmaProperties = sis.readBytes(propertiesSize, "lzmaproperties"); if (lzmaProperties.length != propertiesSize) { throw new IOException("LZMA:input .lzma file is too short"); } decodeLZMAStream(is, os, lzmaProperties, fileSize); header.compression = SWFCompression.LZMA; header.lzmaProperties = lzmaProperties; } break; } case 'A': { // ABC byte[] lzmaProperties = new byte[5]; is.read(lzmaProperties); byte[] uncompressedLength = new byte[8]; is.read(uncompressedLength); decodeLZMAStream(is, os, lzmaProperties, fileSize); header.compression = SWFCompression.LZMA_ABC; header.lzmaProperties = lzmaProperties; 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 | IOException ex) { return false; } return true; } public List getScriptPacksByClassNames(List classNames) throws Exception { Set resultSet = new HashSet<>(); List abcList = getAbcList(); List allAbcList = new ArrayList<>(); for (int i = 0; i < abcList.size(); i++) { allAbcList.add(abcList.get(i).getABC()); } for (String className : classNames) { for (int i = 0; i < abcList.size(); i++) { ABC abc = abcList.get(i).getABC(); List scrs = abc.findScriptPacksByPath(className, allAbcList); for (int j = 0; j < scrs.size(); j++) { ScriptPack scr = scrs.get(j); resultSet.add(scr); } } } return new ArrayList<>(resultSet); } private List uniqueAS3Packs(List packs) { List ret = new ArrayList<>(); Set classPaths = new HashSet<>(); for (ScriptPack item : packs) { ClassPath key = item.getClassPath(); if (classPaths.contains(key)) { logger.log(Level.SEVERE, "Duplicate pack path found (" + key + ")!"); } else { classPaths.add(key); ret.add(item); } } return ret; } public List getAS3Packs() { List packs = new ArrayList<>(); List abcList = getAbcList(); List allAbcList = new ArrayList<>(); for (int i = 0; i < abcList.size(); i++) { allAbcList.add(abcList.get(i).getABC()); } for (ABCContainerTag abcTag : abcList) { packs.addAll(abcTag.getABC().getScriptPacks(null, allAbcList)); } return uniqueAS3Packs(packs); } @Override public RECT getRect() { return displayRect; } @Override public RECT getRect(Set added) { return displayRect; } public EventListener getExportEventListener() { EventListener evl = new EventListener() { @Override public void handleExportingEvent(String type, int index, int count, Object data) { for (EventListener listener : listeners) { listener.handleExportingEvent(type, index, count, data); } } @Override public void handleExportedEvent(String type, int index, int count, Object data) { for (EventListener listener : listeners) { listener.handleExportedEvent(type, index, count, data); } } @Override public void handleEvent(String event, Object data) { informListeners(event, data); } }; return evl; } public List exportActionScript(AbortRetryIgnoreHandler handler, String outdir, ScriptExportSettings exportSettings, boolean parallel, EventListener evl) throws IOException { return exportActionScript(handler, outdir, null, exportSettings, parallel, evl, true, true); } public List exportActionScript(AbortRetryIgnoreHandler handler, String outdir, List as3scripts, ScriptExportSettings exportSettings, boolean parallel, EventListener evl, boolean as2, boolean as3) throws IOException { List ret = new ArrayList<>(); if (isAS3()) { if (as3) { ret.addAll(new AS3ScriptExporter().exportActionScript3(this, handler, outdir, as3scripts, exportSettings, parallel, evl)); } } else { if (as2) { ret.addAll(new AS2ScriptExporter().exportAS2Scripts(handler, outdir, getASMs(true), exportSettings, parallel, evl)); } } return ret; } public Map getASMs(boolean exportFileNames) { return getASMs(exportFileNames, new ArrayList<>(), true); } public Map getASMs(boolean exportFileNames, List nodesToExport, boolean exportAll) { Map asmsToExport = new HashMap<>(); for (TreeItem treeItem : getFirstLevelASMNodes(null)) { getASMs(exportFileNames, treeItem, nodesToExport, exportAll, asmsToExport, File.separator + getASMPath(exportFileNames, treeItem)); } return asmsToExport; } private void getASMs(boolean exportFileNames, TreeItem treeItem, List nodesToExport, boolean exportAll, Map asmsToExport, String path) { boolean exportNode = nodesToExport.contains(treeItem); TreeItem realItem = treeItem instanceof TagScript ? ((TagScript) treeItem).getTag() : treeItem; if (realItem instanceof ASMSource && (exportAll || exportNode)) { String npath = path; String exPath = path; int ppos = 1; while (asmsToExport.containsKey(npath)) { ppos++; npath = path + (exportFileNames ? "[" + ppos + "]" : "_" + ppos); exPath = path + "[" + ppos + "]"; } ((ASMSource) realItem).setScriptName(exPath); asmsToExport.put(npath, (ASMSource) realItem); } if (treeItem instanceof TagScript) { TagScript tagScript = (TagScript) treeItem; for (TreeItem subItem : tagScript.getFrames()) { getASMs(exportFileNames, subItem, nodesToExport, exportAll, asmsToExport, path + File.separator + getASMPath(exportFileNames, subItem)); } } else if (treeItem instanceof FrameScript) { FrameScript frameScript = (FrameScript) treeItem; Frame parentFrame = frameScript.getFrame(); for (TreeItem subItem : parentFrame.actionContainers) { getASMs(exportFileNames, getASMWrapToTagScript(subItem), nodesToExport, exportAll || exportNode, asmsToExport, path + File.separator + getASMPath(exportFileNames, subItem)); } for (TreeItem subItem : parentFrame.actions) { getASMs(exportFileNames, getASMWrapToTagScript(subItem), nodesToExport, exportAll || exportNode, asmsToExport, path + File.separator + getASMPath(exportFileNames, subItem)); } } else if (treeItem instanceof AS2Package) { AS2Package as2Package = (AS2Package) treeItem; for (TreeItem subItem : as2Package.subPackages.values()) { getASMs(exportFileNames, subItem, nodesToExport, exportAll, asmsToExport, path + File.separator + getASMPath(exportFileNames, subItem)); } for (TreeItem subItem : as2Package.scripts.values()) { getASMs(exportFileNames, subItem, nodesToExport, exportAll, asmsToExport, path + File.separator + getASMPath(exportFileNames, subItem)); } } } private String getASMPath(boolean exportFileName, TreeItem treeItem) { if (!exportFileName) { return treeItem.toString(); } String result; if (treeItem instanceof Exportable) { result = ((Exportable) treeItem).getExportFileName(); } else { result = treeItem.toString(); } return Helper.makeFileName(result); } private TreeItem getASMWrapToTagScript(TreeItem treeItem) { if (treeItem instanceof Tag) { Tag resultTag = (Tag) treeItem; List subNodes = new ArrayList<>(); if (treeItem instanceof ASMSourceContainer) { for (ASMSource item : ((ASMSourceContainer) treeItem).getSubItems()) { subNodes.add(item); } } TagScript tagScript = new TagScript(treeItem.getSwf(), resultTag, subNodes); return tagScript; } return treeItem; } public List getFirstLevelASMNodes(Map tagScriptCache) { Timeline timeline = getTimeline(); List subNodes = new ArrayList<>(); List subFrames = new ArrayList<>(); subNodes.addAll(timeline.getAS2RootPackage().subPackages.values()); subNodes.addAll(timeline.getAS2RootPackage().scripts.values()); for (Tag tag : timeline.otherTags) { boolean hasInnerFrames = false; List tagSubNodes = new ArrayList<>(); if (tag instanceof Timelined) { Timeline timeline2 = ((Timelined) tag).getTimeline(); for (Frame frame : timeline2.getFrames()) { if (!frame.actions.isEmpty() || !frame.actionContainers.isEmpty()) { FrameScript frameScript = new FrameScript(this, frame); tagSubNodes.add(frameScript); hasInnerFrames = true; } } } if (tag instanceof ASMSourceContainer) { for (ASMSource asm : ((ASMSourceContainer) tag).getSubItems()) { tagSubNodes.add(asm); } } if (!tagSubNodes.isEmpty()) { TagScript ts = new TagScript(this, tag, tagSubNodes); if (tagScriptCache != null) { tagScriptCache.put(tag, ts); } if (hasInnerFrames) { subFrames.add(ts); } else { subNodes.add(ts); } } } subNodes.addAll(subFrames); for (Frame frame : timeline.getFrames()) { if (!frame.actions.isEmpty() || !frame.actionContainers.isEmpty()) { FrameScript frameScript = new FrameScript(this, frame); subNodes.add(frameScript); } } return subNodes; } 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 void populateVideoFrames(int streamId, List tags, HashMap output) { for (Tag t : tags) { if (t instanceof VideoFrameTag) { output.put(((VideoFrameTag) t).frameNum, (VideoFrameTag) t); } if (t instanceof DefineSpriteTag) { populateVideoFrames(streamId, ((DefineSpriteTag) t).getSubTags(), output); } } } 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); } public 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.getCharacter(c); if (ch instanceof FontTag) { StringBuilder sb = new StringBuilder(); sb.append("function ").append(getTypePrefix(ch)).append(c).append("(ctx,ch,textColor){\r\n"); ((FontTag) ch).toHtmlCanvas(sb, 1); sb.append("}\r\n\r\n"); fos.write(Utf8Helper.getBytes(sb.toString())); } else { if (ch instanceof ImageTag) { ImageTag image = (ImageTag) ch; ImageFormat format = image.getImageFormat(); byte[] imageData = Helper.readStream(image.getImageData()); String base64ImgData = Helper.byteArrayToBase64String(imageData); fos.write(Utf8Helper.getBytes("var imageObj" + c + " = document.createElement(\"img\");\r\nimageObj" + c + ".src=\"data:image/" + format + ";base64," + base64ImgData + "\";\r\n")); } fos.write(Utf8Helper.getBytes("function " + getTypePrefix(ch) + c + "(ctx,ctrans,frame,ratio,time){\r\n")); if (ch instanceof DrawableTag) { StringBuilder sb = new StringBuilder(); ((DrawableTag) ch).toHtmlCanvas(sb, 1); fos.write(Utf8Helper.getBytes(sb.toString())); } fos.write(Utf8Helper.getBytes("}\r\n\r\n")); } } } private static void getVariables(ConstantPool constantPool, BaseLocalData localData, TranslateStack 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 ActionList(), new HashSet<>(), 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<>()); ((GraphSourceItemContainer) ins).translateContainer(r, ins, stack, output, new HashMap<>(), new HashMap<>(), new HashMap<>()); 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, null, 0, Null.INSTANCE, 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; int requiredStackSize = ins.getStackPopCount(localData, stack); if (stack.size() < requiredStackSize) { // probably obfucated code, never executed branch break; } ins.translate(localData, stack, output, staticOperation, path); 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) { TranslateStack brStack = (TranslateStack) 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 usageTypes, ActionGraphSource code, int addr, String path) throws InterruptedException { ActionLocalData localData = new ActionLocalData(); getVariables(null, localData, new TranslateStack(path), new ArrayList<>(), code, code.adr2pos(addr), variables, functions, strings, new ArrayList<>(), usageTypes, path); } private List> getVariables(List> variables, HashMap actionsMap, List functions, HashMap strings, HashMap usageTypes, ASMSource src, String path) throws InterruptedException { List> ret = new ArrayList<>(); ActionList actions = src.getActions(); actionsMap.put(src, actions); getVariables(variables, functions, strings, usageTypes, new ActionGraphSource(actions, version, new HashMap<>(), new HashMap<>(), new HashMap<>()), 0, path); return ret; } private void getVariables(List tags, String path, List> variables, HashMap actionsMap, List functions, HashMap strings, HashMap usageTypes) throws InterruptedException { List processed = new ArrayList<>(); for (Tag t : tags) { String subPath = path + "/" + t.toString(); if (t instanceof ASMSource) { addVariable((ASMSource) t, subPath, processed, variables, actionsMap, functions, strings, usageTypes); } if (t instanceof ASMSourceContainer) { List processed2 = new ArrayList<>(); for (ASMSource asm : ((ASMSourceContainer) t).getSubItems()) { addVariable(asm, subPath + "/" + asm.toString(), processed2, variables, actionsMap, functions, strings, usageTypes); } } if (t instanceof DefineSpriteTag) { getVariables(((DefineSpriteTag) t).getSubTags(), path + "/" + t.toString(), variables, actionsMap, functions, strings, usageTypes); } } } private void addVariable(ASMSource asm, String path, List processed, List> variables, HashMap actionsMap, List functions, HashMap strings, HashMap usageTypes) throws InterruptedException { int pos = 1; String infPath2 = path; while (processed.contains(infPath2)) { pos++; infPath2 = path + "[" + pos + "]"; } processed.add(infPath2); informListeners("getVariables", infPath2); getVariables(variables, actionsMap, functions, strings, usageTypes, asm, path); } public boolean as3StringConstantExists(String str) { for (ABCContainerTag abcTag : getAbcList()) { ABC abc = abcTag.getABC(); for (int i = 1; i < abc.constants.getStringCount(); i++) { if (abc.constants.getString(i).equals(str)) { return true; } } } return false; } public void fixAS3Code() { for (ABCContainerTag abcTag : getAbcList()) { ABC abc = abcTag.getABC(); for (MethodBody body : abc.bodies) { AVM2Code code = body.getCode(); body.setCodeBytes(code.getBytes()); } ((Tag) abcTag).setModified(true); } } 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.size(); i++) { String newname = deobfuscation.deobfuscateNameWithPackage(true, sc.names.get(i), deobfuscated, renameType, deobfuscated); if (newname != null) { sc.names.set(i, newname); } } sc.setModified(true); } } deobfuscation.deobfuscateInstanceNames(true, deobfuscated, renameType, tags, new HashMap<>()); return deobfuscated.size(); } public int deobfuscateIdentifiers(RenameType renameType) throws InterruptedException { FileAttributesTag fileAttributes = getFileAttributes(); 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(DottedChain.parse(identifier), DottedChain.parse(newname)); renameAS2Identifiers(null, selected); } private int deobfuscateAS2Identifiers(RenameType renameType) throws InterruptedException { return renameAS2Identifiers(renameType, null); } private int renameAS2Identifiers(RenameType renameType, Map selected) throws InterruptedException { HashMap actionsMap = new HashMap<>(); List allFunctions = new ArrayList<>(); List> allVariableNames = new ArrayList<>(); HashMap allStrings = new HashMap<>(); HashMap usageTypes = new HashMap<>(); int ret = 0; getVariables(tags, "", allVariableNames, actionsMap, allFunctions, allStrings, usageTypes); informListeners("rename", ""); int fc = 0; for (MyEntry it : allVariableNames) { String name = it.getKey().toStringNoH(it.getValue()); 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 = getExportName(dia.spriteId); exportName = exportName != null ? exportName : "_unk_"; final String pkgPrefix = "__Packages."; String[] classNameParts = null; if (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(false, fname, false, "method", deobfuscated, renameType, selected); if (changed != null) { deobfuscated.put(DottedChain.parse(fname), DottedChain.parse(changed)); } } } } List vars = new ArrayList<>(); for (MyEntry item : cti.vars) { vars.add(item.getKey()); } for (MyEntry item : cti.staticVars) { vars.add(item.getKey()); } for (GraphTargetItem gti : vars) { if (gti instanceof DirectValueActionItem) { DirectValueActionItem dvf = (DirectValueActionItem) gti; String vname = dvf.toStringNoH(null); String changed = deobfuscation.deobfuscateName(false, vname, false, "attribute", deobfuscated, renameType, selected); if (changed != null) { deobfuscated.put(DottedChain.parse(vname), DottedChain.parse(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(false, changedNameStr, pos == 0, pos == 0 ? "class" : "package", deobfuscated, renameType, selected); if (changedNameStr2 != null) { changedNameStr = changedNameStr2; } ret++; deobfuscated.put(DottedChain.parse(nameStr), DottedChain.parse(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(false, changedNameStr, pos == 0, pos == 0 ? "class" : "package", deobfuscated, renameType, selected); if (changedNameStr2 != null) { changedNameStr = changedNameStr2; } ret++; deobfuscated.put(DottedChain.parse(nameStr), DottedChain.parse(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(false, 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(false, 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.getKey()); } 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.getKey().toStringNoH(it.getValue()); String changed = deobfuscation.deobfuscateName(false, name, false, usageTypes.get(it.getKey()), deobfuscated, renameType, selected); if (changed != null) { boolean addNew = false; String h = System.identityHashCode(it.getKey()) + "_" + name; if (stringsNoVarH.contains(h)) { addNew = true; } ActionPush pu = (ActionPush) it.getKey().getSrc(); if (pu.replacement == null) { pu.replacement = new ArrayList<>(); pu.replacement.addAll(pu.values); } if (pu.replacement.get(it.getKey().pos) instanceof ConstantIndex) { ConstantIndex ci = (ConstantIndex) pu.replacement.get(it.getKey().pos); ConstantPool pool = it.getValue(); 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.getKey().pos, changed); } ret++; } } for (ASMSource src : actionsMap.keySet()) { actionsMap.get(src).removeNops(); src.setActions(actionsMap.get(src)); src.setModified(); } deobfuscation.deobfuscateInstanceNames(false, deobfuscated, renameType, tags, selected); return ret; } public IdentifiersDeobfuscation getDeobfuscation() { return deobfuscation; } public void exportFla(AbortRetryIgnoreHandler handler, String outfile, String swfName, String generator, String generatorVerName, String generatorVersion, boolean parallel, FLAVersion version) throws IOException, InterruptedException { XFLConverter.convertSWF(handler, this, swfName, outfile, true, generator, generatorVerName, generatorVersion, parallel, version); clearAllCache(); } public void exportXfl(AbortRetryIgnoreHandler handler, String outfile, String swfName, String generator, String generatorVerName, String generatorVersion, boolean parallel, FLAVersion version) throws IOException, InterruptedException { XFLConverter.convertSWF(handler, this, swfName, outfile, false, generator, generatorVerName, generatorVersion, parallel, version); clearAllCache(); } public static AffineTransform matrixToTransform(MATRIX mat) { return new AffineTransform(mat.getScaleXFloat(), mat.getRotateSkew0Float(), mat.getRotateSkew1Float(), mat.getScaleYFloat(), mat.translateX, mat.translateY); } public SerializableImage getFromCache(String key) { if (frameCache.contains(key)) { return frameCache.get(key); } return null; } public byte[] getFromCache(SoundTag soundTag) { if (soundCache.contains(soundTag)) { return soundCache.get(soundTag); } return null; } public void putToCache(String key, SerializableImage img) { if (Configuration.useFrameCache.get()) { frameCache.put(key, img); } } public void putToCache(SoundTag soundTag, byte[] data) { soundCache.put(soundTag, data); } public void clearImageCache() { frameCache.clear(); rectCache.clear(); for (Tag tag : tags) { if (tag instanceof ImageTag) { ((ImageTag) tag).clearCache(); } } } public void clearScriptCache() { as2PcodeCache.clear(); as2Cache.clear(); as3Cache.clear(); IdentifiersDeobfuscation.clearCache(); } public void clearAllCache() { characters = null; abcList = null; timeline = null; clearImageCache(); clearScriptCache(); Cache.clearAll(); Helper.clearShapeCache(); System.gc(); } public static void uncache(ASMSource src) { if (src != null) { SWF swf = src.getSwf(); swf.as2Cache.remove(src); swf.as2PcodeCache.remove(src); } } public static void uncache(ScriptPack pack) { if (pack != null) { pack.getSwf().as3Cache.remove(pack); } } public static boolean isCached(ASMSource src) { return src.getSwf().as2Cache.contains(src); } public static boolean isCached(ScriptPack pack) { return pack.getSwf().as3Cache.contains(pack); } public static ActionList getCachedActionList(ASMSource src, final List listeners) throws InterruptedException { synchronized (src) { SWF swf = src.getSwf(); int deobfuscationMode = Configuration.autoDeobfuscate.get() ? 1 : 0; if (swf != null && swf.as2PcodeCache.contains(src)) { ActionList result = swf.as2PcodeCache.get(src); if (result.deobfuscationMode == deobfuscationMode) { return result; } } try { ByteArrayRange actionBytes = src.getActionBytes(); int prevLength = actionBytes.getPos(); SWFInputStream rri = new SWFInputStream(swf, actionBytes.getArray()); if (prevLength != 0) { rri.seek(prevLength); } int version = swf == null ? SWF.DEFAULT_VERSION : swf.version; ActionList list = ActionListReader.readActionListTimeout(listeners, rri, version, prevLength, prevLength + actionBytes.getLength(), src.toString()/*FIXME?*/, deobfuscationMode); list.deobfuscationMode = deobfuscationMode; if (swf != null) { swf.as2PcodeCache.put(src, list); } return list; } catch (InterruptedException ex) { throw ex; } catch (Exception ex) { Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, null, ex); return new ActionList(); } } } public static CachedScript getCached(ASMSource src, ActionList actions) throws InterruptedException { SWF swf = src.getSwf(); if (swf.as2Cache.contains(src)) { return swf.as2Cache.get(src); } if (actions == null) { actions = src.getActions(); } HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); Action.actionsToSource(src, actions, src.toString()/*FIXME?*/, writer); List hilights = writer.instructionHilights; String srcNoHex = writer.toString(); CachedScript res = new CachedScript(srcNoHex, hilights); swf.as2Cache.put(src, res); return res; } public static CachedDecompilation getCached(ScriptPack pack) throws InterruptedException { SWF swf = pack.getSwf(); if (swf.as3Cache.contains(pack)) { return swf.as3Cache.get(pack); } int scriptIndex = pack.scriptIndex; ScriptInfo script = null; if (scriptIndex > -1) { script = pack.abc.script_info.get(scriptIndex); } boolean parallel = Configuration.parallelSpeedUp.get(); HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); pack.toSource(writer, script == null ? null : script.traits.traits, new ConvertData(), ScriptExportMode.AS, parallel); HighlightedText hilightedCode = new HighlightedText(writer); CachedDecompilation res = new CachedDecompilation(hilightedCode); swf.as3Cache.put(pack, res); return res; } public Cache getRectCache() { return rectCache; } public Cache getShapeExportDataCache() { return shapeExportDataCache; } 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; } public static void frameToSvg(Timeline timeline, int frame, int time, DepthState stateUnderCursor, int mouseButton, SVGExporter exporter, ColorTransform colorTransform, int level, double zoom) throws IOException { if (timeline.getFrameCount() <= frame) { return; } Frame frameObj = timeline.getFrame(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.getCharacters().containsKey(layer.characterId)) { continue; } if (!layer.isVisible) { continue; } CharacterTag character = timeline.swf.getCharacter(layer.characterId); if (colorTransform == null) { colorTransform = new ColorTransform(); } ColorTransform clrTrans = colorTransform.clone(); 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; RECT boundRect = drawable.getRect(); if (exporter.exportedTags.containsKey(drawableTag)) { assetName = exporter.exportedTags.get(drawableTag); } else { assetName = getTagIdPrefix(drawableTag, exporter); exporter.exportedTags.put(drawableTag, assetName); exporter.createDefGroup(new ExportRectangle(boundRect), assetName); drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1, zoom); exporter.endGroup(); } 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, layer.instanceName); 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, layer.instanceName); } } } } 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, double zoom) { SWF swf = timeline.swf; String key = "frame_" + frame + "_" + timeline.id + "_" + swf.hashCode() + "_" + zoom; SerializableImage image; if (useCache) { image = swf.getFromCache(key); if (image != null) { return image; } } if (timeline.getFrameCount() == 0) { return new SerializableImage(1, 1, SerializableImage.TYPE_INT_ARGB); } RECT rect = displayRect; image = new SerializableImage((int) (rect.getWidth() * zoom / SWF.unitDivisor) + 1, (int) (rect.getHeight() * zoom / 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 * zoom, -rect.Ymin * zoom); m.scale(zoom); RenderContext renderContext = new RenderContext(); renderContext.stateUnderCursor = stateUnderCursor; renderContext.mouseButton = mouseButton; frameToImage(timeline, frame, time, renderContext, image, m, colorTransform); if (useCache) { swf.putToCache(key, image); } return image; } public static void framesToImage(Timeline timeline, List ret, int startFrame, int stopFrame, RenderContext renderContext, RECT displayRect, int totalFrameCount, Stack visited, Matrix transformation, ColorTransform colorTransform, double zoom) { RECT rect = displayRect; for (int f = 0; f < timeline.getFrameCount(); 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, renderContext, image, m, colorTransform); ret.add(image); } } public static void frameToImage(Timeline timeline, int frame, int time, RenderContext renderContext, SerializableImage image, Matrix transformation, ColorTransform colorTransform) { double unzoom = SWF.unitDivisor; if (timeline.getFrameCount() <= frame) { return; } Frame frameObj = timeline.getFrame(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.getCharacters().containsKey(layer.characterId)) { continue; } if (!layer.isVisible) { continue; } CharacterTag character = timeline.swf.getCharacter(layer.characterId); Matrix mat = new Matrix(layer.matrix); mat = mat.preConcatenate(transformation); if (colorTransform == null) { colorTransform = new ColorTransform(); } ColorTransform clrTrans = colorTransform.clone(); 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; Matrix drawMatrix = new Matrix(); int drawableFrameCount = drawable.getNumFrames(); if (drawableFrameCount == 0) { drawableFrameCount = 1; } int dframe; if (timeline.fontFrameNum != -1) { dframe = timeline.fontFrameNum; } else { dframe = (time + layer.time) % drawableFrameCount; } if (character instanceof ButtonTag) { dframe = ButtonTag.FRAME_UP; if (renderContext.stateUnderCursor == layer) { if (renderContext.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 * unzoom; rect.xMax += deltaXMax * unzoom; rect.yMin -= deltaYMax * unzoom; rect.yMax += deltaYMax * unzoom; } rect.xMin -= 1 * unzoom; rect.yMin -= 1 * unzoom; rect.xMin = Math.max(0, rect.xMin); rect.yMin = Math.max(0, rect.yMin); int newWidth = (int) (rect.getWidth() / unzoom); int newHeight = (int) (rect.getHeight() / unzoom); int deltaX = (int) (rect.xMin / unzoom); int deltaY = (int) (rect.yMin / unzoom); newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1; newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1; if (newWidth <= 0 || newHeight <= 0) { continue; } m.translate(-rect.xMin, -rect.yMin); drawMatrix.translate(rect.xMin, rect.yMin); SerializableImage img = null; String cacheKey = null; if (drawable instanceof ShapeTag) { cacheKey = ((ShapeTag) drawable).getCharacterId() + m.toString() + clrTrans.toString(); img = renderContext.shapeCache.get(cacheKey); } if (img == null) { img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB); img.fillTransparent(); drawable.toImage(dframe, layer.time + time, layer.ratio, renderContext, img, m, clrTrans); if (cacheKey != null) { renderContext.shapeCache.put(cacheKey, img); } } /*//if (renderContext.stateUnderCursor == layer) { if (true) { BufferedImage bi = img.getBufferedImage(); ColorModel cm = bi.getColorModel(); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); WritableRaster raster = bi.copyData(null); img = new SerializableImage(new BufferedImage(cm, raster, isAlphaPremultiplied, null)); 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(dframe, layer.time + time, layer.ratio, renderContext, 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) { Map stage = new HashMap<>(); Set dependingChars = new HashSet<>(); if (toRemove instanceof CharacterTag) { int characterId = ((CharacterTag) toRemove).getCharacterId(); 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 removeTags(Collection tags, boolean removeDependencies) { Set timelineds = new HashSet<>(); for (Tag tag : tags) { Timelined timelined = tag.getTimelined(); timelineds.add(timelined); removeTagInternal(timelined, tag, removeDependencies); } for (Timelined timelined : timelineds) { resetTimelines(timelined); } updateCharacters(); clearImageCache(); } public void removeTag(Tag tag, boolean removeDependencies) { Timelined timelined = tag.getTimelined(); removeTagInternal(timelined, tag, removeDependencies); resetTimelines(timelined); updateCharacters(); clearImageCache(); } private void removeTagInternal(Timelined timelined, Tag tag, boolean removeDependencies) { if (tag instanceof ShowFrameTag || ShowFrameTag.isNestedTagType(tag.getId())) { List tags; if (timelined instanceof DefineSpriteTag) { DefineSpriteTag sprite = (DefineSpriteTag) timelined; tags = sprite.getSubTags(); } else { tags = this.tags; } tags.remove(tag); if (timelined instanceof DefineSpriteTag) { DefineSpriteTag sprite = (DefineSpriteTag) timelined; sprite.setModified(true); } timelined.resetTimeline(); } else { // timeline should be always the swf here if (removeDependencies) { removeTagWithDependenciesFromTimeline(tag, timelined.getTimeline()); if (timelined instanceof DefineSpriteTag) { DefineSpriteTag sprite = (DefineSpriteTag) timelined; sprite.setModified(true); } } else { removeTagFromTimeline(tag, timelined.getTimeline()); } } } public void packCharacterIds() { int maxId = getNextCharacterId(); int id = 1; for (int i = 1; i < maxId; i++) { CharacterTag charactertag = getCharacter(i); if (charactertag != null) { if (i != id) { replaceCharacter(i, id); } id++; } else { // make sure that the id is not referenced in the tags replaceCharacter(i, 0); } } } public void sortCharacterIds() { int maxId = Math.max(tags.size(), getNextCharacterId()); int id = maxId; // first set the chatacter ids to surely not used ids for (Tag tag : tags) { if (tag instanceof CharacterTag) { CharacterTag characterTag = (CharacterTag) tag; replaceCharacter(characterTag.getCharacterId(), id++); } } // then set them to 1,2,3... id = 1; for (Tag tag : tags) { if (tag instanceof CharacterTag) { CharacterTag characterTag = (CharacterTag) tag; replaceCharacter(characterTag.getCharacterId(), id++); } } } public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { boolean modified = false; for (Tag tag : tags) { boolean modified2 = false; if (tag instanceof CharacterIdTag) { CharacterIdTag characterIdTag = (CharacterIdTag) tag; if (characterIdTag.getCharacterId() == oldCharacterId) { characterIdTag.setCharacterId(newCharacterId); modified2 = true; } } modified2 |= tag.replaceCharacter(oldCharacterId, newCharacterId); if (modified2) { tag.setModified(true); } modified |= modified2; } return modified; } public void replaceCharacterTags(CharacterTag characterTag, int newCharacterId) { int characterId = characterTag.getCharacterId(); CharacterTag newCharacter = getCharacter(newCharacterId); newCharacter.setCharacterId(characterId); characterTag.setCharacterId(newCharacterId); newCharacter.setModified(true); characterTag.setModified(true); assignExportNamesToSymbols(); assignClassesToSymbols(); clearImageCache(); updateCharacters(); } @Override public String toString() { return getShortFileName(); } public void deobfuscate(DeobfuscationLevel level) throws InterruptedException { List atags = getAbcList(); for (ABCContainerTag tag : atags) { if (level == DeobfuscationLevel.LEVEL_REMOVE_DEAD_CODE) { tag.getABC().removeDeadCode(); } else if (level == DeobfuscationLevel.LEVEL_REMOVE_TRAPS) { tag.getABC().removeTraps(); } else if (level == DeobfuscationLevel.LEVEL_RESTORE_CONTROL_FLOW) { tag.getABC().removeTraps(); tag.getABC().restoreControlFlow(); } ((Tag) tag).setModified(true); } } /** * Enables debugging. Adds tags to enable debugging and injects debugline * and debugfile instructions to AS3 code * * @param injectCode Modify AS3 code with debugfile / debugline ? * @param decompileDir Directory to virtual decompile (will affect * debugfile) */ public void enableDebugging(boolean injectCode, File decompileDir) { enableDebugging(injectCode, decompileDir, false); } /** * Enables debugging. Adds tags to enable debugging and injects debugline * and debugfile instructions to AS3 code. Optionally enables Telemetry * * @param injectCode Modify AS3 code with debugfile / debugline ? * @param decompileDir Directory to virtual decompile (will affect * debugfile) * @param telemetry Enable telemetry info? */ public void enableDebugging(boolean injectCode, File decompileDir, boolean telemetry) { if (injectCode) { List packs = getAS3Packs(); for (ScriptPack s : packs) { if (s.isSimple) { s.injectDebugInfo(decompileDir); } } } int pos = 0; boolean hasEnabled = false; for (int i = 0; i < tags.size(); i++) { Tag t = tags.get(i); if (t instanceof MetadataTag) { pos = i + 1; } if (t instanceof FileAttributesTag) { pos = i + 1; } if (version >= 6 && (t instanceof EnableDebugger2Tag)) { hasEnabled = true; break; } if (version == 5 && (t instanceof EnableDebuggerTag)) { hasEnabled = true; break; } if (version < 5 && (t instanceof ProtectTag)) { hasEnabled = true; break; } } if (!hasEnabled) { if (version >= 6) { tags.add(pos, new EnableDebugger2Tag(this)); } else if (version == 5) { tags.add(pos, new EnableDebuggerTag(this)); } else { tags.add(pos, new ProtectTag(this)); } } addDebugId(); } public DebugIDTag getDebugId() { for (Tag t : tags) { if (t instanceof DebugIDTag) { return (DebugIDTag) t; } } return null; } public DebugIDTag addDebugId() { DebugIDTag r = getDebugId(); if (r == null) { for (int i = 0; i < tags.size(); i++) { Tag t = tags.get(i); if ((t instanceof EnableDebuggerTag) || (t instanceof EnableDebugger2Tag)) { r = new DebugIDTag(this); tags.add(i + 1, r); new Random().nextBytes(r.debugId); break; } } } return r; } public boolean generateSwdFile(File file, Map> breakpoints) throws IOException { DebugIDTag dit = getDebugId(); if (dit == null) { return false; } List items = new ArrayList<>(); Map asms = getASMs(true); try { items.add(new SWD.DebugId(dit.debugId)); Random rnd = new Random(); //Map moduleIds = new HashMap<>(); List swdOffsets = new ArrayList<>(); List swfBps = new ArrayList<>(); int moduleId = 0; List names = new ArrayList<>(asms.keySet()); Collections.sort(names); //Collections.reverse(names); for (String name : names) { moduleId++; CachedScript cs; try { cs = SWF.getCached(asms.get(name), asms.get(name).getActions()); } catch (InterruptedException ex) { return false; } String txt = cs.text.replace("\r", ""); int line = 1; Map lineToOffset = new HashMap<>(); Map regNames = new HashMap<>(); for (int pos = 0; pos < txt.length(); pos++) { Highlighting h = Highlighting.searchPos(cs.hilights, pos); if (h != null) { int firstLineOffset = (int) h.getProperties().firstLineOffset; if (firstLineOffset != -1) { if (h.getProperties().declaration && h.getProperties().regIndex > -1) { regNames.put(h.getProperties().regIndex, h.getProperties().localName); /*List curRegIndexes = new ArrayList<>(regNames.keySet()); List curRegNames = new ArrayList<>(); for (int i = 0; i < curRegIndexes.size(); i++) { curRegNames.add(regNames.get(i)); } items.add(new SWD.DebugRegisters((int) h.getProperties().firstLineOffset, curRegIndexes, curRegNames));*/ } } if (firstLineOffset != -1 && !lineToOffset.containsKey(line)) { lineToOffset.put(line, firstLineOffset); } } if (txt.charAt(pos) == '\n') { line++; } } Map offSetToLine = new TreeMap<>(); for (Map.Entry en : lineToOffset.entrySet()) { offSetToLine.put(en.getValue(), en.getKey()); } //final String NONAME = "[No instance name assigned]"; String sname = name; int bitmap = SWD.bitmapAction; /* Matcher m; int bitmap = SWD.bitmapAction; m = Pattern.compile("^\\\\frame_([0-9]+)\\\\DoAction$").matcher(sname); if (m.matches()) { //TODO: scenes?, layers? sname = "Actions for Scene 1: Frame " + m.group(1) + " of Layer Name Layer 1"; } else if ((m = Pattern.compile("^\\\\__Packages\\\\(.*)$").matcher(sname)).matches()) { sname = m.group(1).replace("\\", ".") + ": .\\" + m.group(1) + ".as"; } else { continue; //FIXME! } m = Pattern.compile("^\\\\DefineSprite_([0-9])+\\\\frame_([0-9]+)\\\\DoAction$").matcher(sname); if (m.matches()) { //TODO: layers? //sname = "Actions for Symbol " + m.group(1) + ": Frame " + m.group(2) + " of Layer Name Layer 1"; continue; //FIXME! } //TODO: handle onxxx together ? m = Pattern.compile("^\\\\DefineButton2?_([0-9]+)\\\\on\\(.*$").matcher(sname); if (m.matches()) { //bitmap = SWD.bitmapOnAction; //sname = "Actions for " + NONAME + " (Symbol " + m.group(1) + ")"; continue; //FIXME! } //TODO: handle onClipEvent together ? m = Pattern.compile("^\\\\frame_([0-9]+)\\\\PlaceObject[2-3]?_([0-9]+)_[^\\\\]*\\\\onClipEvent\\(.*$").matcher(sname); if (m.matches()) { //bitmap = SWD.bitmapOnClipAction; //sname = "Actions for " + NONAME + " (Symbol " + m.group(2) + ")"; continue; //FIXME! }//*/ items.add(new SWD.DebugScript(moduleId, bitmap, sname, txt)); for (int ofs : offSetToLine.keySet()) { items.add(new SWD.DebugOffset(moduleId, offSetToLine.get(ofs), ofs)); } if (breakpoints.containsKey(name)) { Set bplines = breakpoints.get(name); for (int bpline : bplines) { if (lineToOffset.containsKey(bpline)) { items.add(new SWD.DebugBreakpoint(moduleId, bpline)); } } } //moduleId++; } //items.addAll(swdOffsets); //items.addAll(swfBps); } catch (Throwable t) { Logger.getLogger(SWF.class.getName()).log(Level.SEVERE, "message", t); return false; } SWD swd = new SWD(7, items); try (FileOutputStream fis = new FileOutputStream(file)) { swd.saveTo(fis); } return true; } public boolean enableTelemetry(String password) { EnableTelemetryTag et = getEnableTelemetry(); if (et == null) { FileAttributesTag fat = getFileAttributes(); if (fat == null) { return false; } int insertTo = tags.indexOf(fat) + 1; MetadataTag mt = getMetadata(); if (mt != null) { insertTo = tags.indexOf(mt) + 1; } et = new EnableTelemetryTag(this); tags.add(insertTo, et); } et.setPassword(password); //TODO: SWFs with tag 92 (signed) are unsupported return true; } }