diff --git a/src/com/jpexs/decompiler/flash/SWF.java b/src/com/jpexs/decompiler/flash/SWF.java index b9a25fb69..6a307618b 100644 --- a/src/com/jpexs/decompiler/flash/SWF.java +++ b/src/com/jpexs/decompiler/flash/SWF.java @@ -1326,25 +1326,26 @@ public final class SWF implements TreeItem, Timelined { fos.write(Utf8Helper.getBytes("function " + getTypePrefix(ch) + c + "(ctx,ch,textColor){\r\n")); fos.write(Utf8Helper.getBytes(((FontTag) ch).toHtmlCanvas(1))); fos.write(Utf8Helper.getBytes("}\r\n\r\n")); - } else if (ch instanceof ImageTag) { - ImageTag image = (ImageTag) ch; - String format = image.getImageFormat(); - InputStream imageStream = image.getImageData(); - byte[] imageData; - if (imageStream != null) { - imageData = Helper.readStream(image.getImageData()); - } else { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - ImageIO.write(image.getImage().getBufferedImage(), format.toUpperCase(Locale.ENGLISH), baos); - } catch (IOException ex) { - } - imageData = baos.toByteArray(); - } - String base64ImgData = DatatypeConverter.printBase64Binary(imageData); - fos.write(Utf8Helper.getBytes("var image" + c + " = document.createElement(\"img\");\r\nimage" + c + ".src=\"data:image/" + format + ";base64," + base64ImgData + "\";\r\n")); } else { fos.write(Utf8Helper.getBytes("function " + getTypePrefix(ch) + c + "(ctx,ctrans,frame,ratio,time){\r\n")); + if (ch instanceof ImageTag) { + ImageTag image = (ImageTag) ch; + String format = image.getImageFormat(); + InputStream imageStream = image.getImageData(); + byte[] imageData; + if (imageStream != null) { + imageData = Helper.readStream(image.getImageData()); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(image.getImage().getBufferedImage(), format.toUpperCase(Locale.ENGLISH), baos); + } catch (IOException ex) { + } + imageData = baos.toByteArray(); + } + String base64ImgData = DatatypeConverter.printBase64Binary(imageData); + fos.write(Utf8Helper.getBytes("var imageObj" + c + " = document.createElement(\"img\");\r\nimageObj" + c + ".src=\"data:image/" + format + ";base64," + base64ImgData + "\";\r\n")); + } if (ch instanceof DrawableTag) { fos.write(Utf8Helper.getBytes(((DrawableTag) ch).toHtmlCanvas(1))); } diff --git a/src/com/jpexs/decompiler/flash/action/Action.java b/src/com/jpexs/decompiler/flash/action/Action.java index de1305fba..689d587c0 100644 --- a/src/com/jpexs/decompiler/flash/action/Action.java +++ b/src/com/jpexs/decompiler/flash/action/Action.java @@ -1,1324 +1,1324 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.action; - -import com.jpexs.decompiler.flash.AppStrings; -import com.jpexs.decompiler.flash.BaseLocalData; -import com.jpexs.decompiler.flash.DisassemblyListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.action.model.ActionItem; -import com.jpexs.decompiler.flash.action.model.ConstantPool; -import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; -import com.jpexs.decompiler.flash.action.model.ExtendsActionItem; -import com.jpexs.decompiler.flash.action.model.FunctionActionItem; -import com.jpexs.decompiler.flash.action.model.GetMemberActionItem; -import com.jpexs.decompiler.flash.action.model.GetPropertyActionItem; -import com.jpexs.decompiler.flash.action.model.GetVariableActionItem; -import com.jpexs.decompiler.flash.action.model.ImplementsOpActionItem; -import com.jpexs.decompiler.flash.action.model.NewObjectActionItem; -import com.jpexs.decompiler.flash.action.model.SetMemberActionItem; -import com.jpexs.decompiler.flash.action.model.SetPropertyActionItem; -import com.jpexs.decompiler.flash.action.model.SetVariableActionItem; -import com.jpexs.decompiler.flash.action.model.StoreRegisterActionItem; -import com.jpexs.decompiler.flash.action.model.TemporaryRegister; -import com.jpexs.decompiler.flash.action.model.UnsupportedActionItem; -import com.jpexs.decompiler.flash.action.model.clauses.ClassActionItem; -import com.jpexs.decompiler.flash.action.model.clauses.InterfaceActionItem; -import com.jpexs.decompiler.flash.action.parser.ParseException; -import com.jpexs.decompiler.flash.action.parser.pcode.ASMParsedSymbol; -import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; -import com.jpexs.decompiler.flash.action.parser.pcode.FlasmLexer; -import com.jpexs.decompiler.flash.action.parser.script.VariableActionItem; -import com.jpexs.decompiler.flash.action.special.ActionEnd; -import com.jpexs.decompiler.flash.action.special.ActionStore; -import com.jpexs.decompiler.flash.action.swf4.ActionEquals; -import com.jpexs.decompiler.flash.action.swf4.ActionIf; -import com.jpexs.decompiler.flash.action.swf4.ActionJump; -import com.jpexs.decompiler.flash.action.swf4.ActionNot; -import com.jpexs.decompiler.flash.action.swf4.ActionPush; -import com.jpexs.decompiler.flash.action.swf4.RegisterNumber; -import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; -import com.jpexs.decompiler.flash.action.swf5.ActionEquals2; -import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.ecma.Null; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.flash.helpers.HilightedTextWriter; -import com.jpexs.decompiler.flash.helpers.NulWriter; -import com.jpexs.decompiler.flash.helpers.collections.MyEntry; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.graph.Graph; -import com.jpexs.decompiler.graph.GraphSource; -import com.jpexs.decompiler.graph.GraphSourceItem; -import com.jpexs.decompiler.graph.GraphSourceItemContainer; -import com.jpexs.decompiler.graph.GraphTargetItem; -import com.jpexs.decompiler.graph.TranslateException; -import com.jpexs.decompiler.graph.model.CommentItem; -import com.jpexs.decompiler.graph.model.IfItem; -import com.jpexs.decompiler.graph.model.LocalData; -import com.jpexs.decompiler.graph.model.NotItem; -import com.jpexs.decompiler.graph.model.ScriptEndItem; -import com.jpexs.helpers.CancellableWorker; -import com.jpexs.helpers.Helper; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EmptyStackException; -import java.util.HashMap; -import java.util.List; -import java.util.Stack; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Represents one ACTIONRECORD, also has some static method to work with Actions - */ -public class Action implements GraphSourceItem { - - public Action replaceWith; - private boolean ignored = false; - /** - * Action type identifier - */ - public int actionCode; - /** - * Length of action data - */ - public int actionLength; - private long address; - - public static final String[] reservedWords = { - "as", "break", "case", "catch", "class", "const", "continue", "default", "delete", "do", "each", "else", - "extends", "false", "finally", "for", "function", "get", "if", "implements", "import", "in", "instanceof", - "interface", "internal", "is", "native", "new", "null", "override", "package", "private", "protected", "public", - "return", "set", "super", "switch", "this", "throw", "true", "try", "typeof", "use", "var", /*"void",*/ "while", - "with", "dynamic", "default", "final", "in"}; - - public static boolean isReservedWord(String s) { - if (s == null) { - return false; - } - for (String rw : reservedWords) { - if (rw.equals(s.trim())) { - return true; - } - } - return false; - } - - - /** - * Names of ActionScript properties - */ - public static final String[] propertyNames = new String[]{ - "_X", - "_Y", - "_xscale", - "_yscale", - "_currentframe", - "_totalframes", - "_alpha", - "_visible", - "_width", - "_height", - "_rotation", - "_target", - "_framesloaded", - "_name", - "_droptarget", - "_url", - "_highquality", - "_focusrect", - "_soundbuftime", - "_quality", - "_xmouse", - "_ymouse" - }; - public static final List propertyNamesList = Arrays.asList(propertyNames); - private static final Logger logger = Logger.getLogger(Action.class.getName()); - - /** - * Constructor - * - * @param actionCode Action type identifier - * @param actionLength Length of action data - */ - public Action(int actionCode, int actionLength) { - this.actionCode = actionCode; - this.actionLength = actionLength; - } - - public Action() { - } - - /** - * Returns address of this action - * - * @return address of this action - */ - public long getAddress() { - return address; - } - - /** - * Gets all addresses which are referenced from this action and/or - * subactions - * - * @param version SWF version - * @return List of addresses - */ - public List getAllRefs(int version) { - List ret = new ArrayList<>(); - return ret; - } - - /** - * Gets all ActionIf or ActionJump actions from subactions - * - * @return List of actions - */ - public List getAllIfsOrJumps() { - List ret = new ArrayList<>(); - return ret; - } - - /** - * Gets all ActionIf or ActionJump actions from list of actions - * - * @param list List of actions - * @return List of actions - */ - public static List getActionsAllIfsOrJumps(List list) { - List ret = new ArrayList<>(); - for (Action a : list) { - List part = a.getAllIfsOrJumps(); - ret.addAll(part); - } - return ret; - } - - /** - * Gets all addresses which are referenced from the list of actions - * - * @param list List of actions - * @param version SWF version - * @return List of addresses - */ - public static List getActionsAllRefs(List list, int version) { - List ret = new ArrayList<>(); - for (Action a : list) { - if (a.replaceWith != null) { - a.replaceWith.setAddress(a.getAddress(), version, false); - ret.addAll(a.replaceWith.getAllRefs(version)); - } - List part = a.getAllRefs(version); - ret.addAll(part); - } - return ret; - } - - /** - * Sets address of this instruction - * - * @param address Address - * @param version SWF version - */ - public final void setAddress(long address, int version) { - setAddress(address, version, true); - } - - public void setAddress(long address, int version, boolean recursive) { - this.address = address; - } - - /** - * Returns a string representation of the object - * - * @return a string representation of the object. - */ - @Override - public String toString() { - return "Action" + actionCode; - } - - /** - * Reads String from FlasmLexer - * - * @param lex FlasmLexer - * @return String value - * @throws IOException - * @throws ParseException When read object is not String - */ - protected String lexString(FlasmLexer lex) throws IOException, ParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_STRING) { - throw new ParseException("String expected", lex.yyline()); - } - return (String) symb.value; - } - - /** - * Reads Block startServer from FlasmLexer - * - * @param lex FlasmLexer - * @throws IOException - * @throws ParseException When read object is not Block startServer - */ - protected void lexBlockOpen(FlasmLexer lex) throws IOException, ParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_BLOCK_START) { - throw new ParseException("Block startServer ", lex.yyline()); - } - } - - /** - * Reads Identifier from FlasmLexer - * - * @param lex FlasmLexer - * @return Identifier name - * @throws IOException - * @throws ParseException When read object is not Identifier - */ - protected String lexIdentifier(FlasmLexer lex) throws IOException, ParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_IDENTIFIER) { - throw new ParseException("Identifier expected", lex.yyline()); - } - return (String) symb.value; - } - - /** - * Reads long value from FlasmLexer - * - * @param lex FlasmLexer - * @return long value - * @throws IOException - * @throws ParseException When read object is not long value - */ - protected long lexLong(FlasmLexer lex) throws IOException, ParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_INTEGER) { - throw new ParseException("Integer expected", lex.yyline()); - } - return (Long) symb.value; - } - - /** - * Reads boolean value from FlasmLexer - * - * @param lex FlasmLexer - * @return boolean value - * @throws IOException - * @throws ParseException When read object is not boolean value - */ - protected boolean lexBoolean(FlasmLexer lex) throws IOException, ParseException { - ASMParsedSymbol symb = lex.yylex(); - if (symb.type != ASMParsedSymbol.TYPE_BOOLEAN) { - throw new ParseException("Boolean expected", lex.yyline()); - } - return (Boolean) symb.value; - } - - /** - * Gets action converted to bytes - * - * @param version SWF version - * @return Array of bytes - */ - public byte[] getBytes(int version) { - return surroundWithAction(new byte[0], version); - } - - /** - * Surrounds byte array with Action header - * - * @param data Byte array - * @param version SWF version - * @return Byte array - */ - protected byte[] surroundWithAction(byte[] data, int version) { - ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(baos2, version); - try { - sos2.writeUI8(actionCode); - if (actionCode >= 0x80) { - sos2.writeUI16(data.length); - } - sos2.write(data); - sos2.close(); - } catch (IOException e) { - } - return baos2.toByteArray(); - } - - /** - * Converts list of Actions to bytes - * - * @param list List of actions - * @param addZero Whether or not to add 0 UI8 value to the end - * @param version SWF version - * @return Array of bytes - */ - public static byte[] actionsToBytes(List list, boolean addZero, int version) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Action lastAction = null; - for (Action a : list) { - try { - lastAction = a; - baos.write(a.getBytes(version)); - } catch (IOException e) { - } - } - if (addZero && (lastAction == null || !(lastAction instanceof ActionEnd))) { - baos.write(0); - } - return baos.toByteArray(); - } - - /** - * Set addresses of actions in the list - * - * @param list List of actions - * @param baseAddress Address of first action in the list - * @param version SWF version - */ - public static void setActionsAddresses(List list, long baseAddress, int version) { - long offset = baseAddress; - for (Action a : list) { - a.setAddress(offset, version); - offset += a.getBytes(version).length; - } - } - - /** - * Converts list of actions to ASM source - * - * @param listeners - * @param address - * @param list List of actions - * @param importantOffsets List of important offsets to mark as labels - * @param version SWF version - * @param exportMode PCode or hex? - * @param writer - * @param path - * @return HilightedTextWriter - */ - public static GraphTextWriter actionsToString(List listeners, long address, List list, List importantOffsets, int version, ScriptExportMode exportMode, GraphTextWriter writer, String path) { - return actionsToString(listeners, address, list, importantOffsets, new ArrayList(), version, exportMode, writer, path); - } - - /** - * Converts list of actions to ASM source - * - * @param listeners - * @param address - * @param list List of actions - * @param importantOffsets List of important offsets to mark as labels - * @param constantPool Constant pool - * @param version SWF version - * @param hex Add hexadecimal? - * @param path - * @return HilightedTextWriter - */ - private static GraphTextWriter actionsToString(List listeners, long address, List list, List importantOffsets, List constantPool, int version, ScriptExportMode exportMode, GraphTextWriter writer, String path) { - long offset; - if (importantOffsets == null) { - //setActionsAddresses(list, 0, version); - importantOffsets = getActionsAllRefs(list, version); - } - /*List cps = SWFInputStream.getConstantPool(new ArrayList(), new ActionGraphSource(list, version, new HashMap(), new HashMap(), new HashMap()), 0, version, path); - if (!cps.isEmpty()) { - setConstantPool(list, cps.get(cps.size() - 1)); - }*/ - HashMap> containers = new HashMap<>(); - HashMap containersPos = new HashMap<>(); - offset = address; - int pos = -1; - boolean lastPush = false; - for (GraphSourceItem s : list) { - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).progress(AppStrings.translate("disassemblingProgress.toString"), pos + 2, list.size()); - } - Action a = null; - if (s instanceof Action) { - a = (Action) s; - } - pos++; - if (exportMode == ScriptExportMode.PCODE_HEX) { - if (lastPush) { - writer.newLine(); - lastPush = false; - } - writer.appendNoHilight("; "); - writer.appendNoHilight(Helper.bytesToHexString(a.getBytes(version))); - writer.newLine(); - } - offset = a.getAddress(); - - if ((!(a.isIgnored())) && (a instanceof GraphSourceItemContainer)) { - GraphSourceItemContainer cnt = (GraphSourceItemContainer) a; - containersPos.put(cnt, 0); - List sizes = cnt.getContainerSizes(); - long addr = ((Action) cnt).getAddress() + cnt.getHeaderSize(); - for (Long size : sizes) { - addr += size; - if (size == 0) { - continue; - } - if (!containers.containsKey(addr)) { - containers.put(addr, new ArrayList()); - } - containers.get(addr).add(cnt); - } - } - - if (containers.containsKey(offset)) { - for (int i = 0; i < containers.get(offset).size(); i++) { - writer.appendNoHilight("}").newLine(); - GraphSourceItemContainer cnt = containers.get(offset).get(i); - int cntPos = containersPos.get(cnt); - writer.appendNoHilight(cnt.getASMSourceBetween(cntPos)); - cntPos++; - containersPos.put(cnt, cntPos); - } - } - - if (Configuration.showAllAddresses.get() || importantOffsets.contains(offset)) { - if (lastPush) { - writer.newLine(); - lastPush = false; - } - writer.appendNoHilight("loc"); - writer.appendNoHilight(Helper.formatAddress(offset)); - writer.appendNoHilight(":"); - } - - if (a.replaceWith != null) { - if (lastPush) { - writer.newLine(); - lastPush = false; - } - writer.append("", offset); - writer.appendNoHilight(a.replaceWith.getASMSource(list, importantOffsets, constantPool, version, exportMode)); - writer.newLine(); - } else if (a.isIgnored()) { - if (lastPush) { - writer.newLine(); - lastPush = false; - } - int len = 0; - if (pos + 1 < list.size()) { - len = (int) (((Action) (list.get(pos + 1))).getAddress() - a.getAddress()); - } else { - len = a.getBytes(version).length; - } - if (!(a instanceof ActionEnd)) { - for (int i = 0; i < len; i++) { - writer.appendNoHilight("Nop").newLine(); - } - } - } else { - //if (!(a instanceof ActionNop)) { - String add = ""; - if (a instanceof ActionIf) { - add = " change: " + ((ActionIf) a).getJumpOffset(); - } - if (a instanceof ActionJump) { - add = " change: " + ((ActionJump) a).getJumpOffset(); - } - add = "; ofs" + Helper.formatAddress(offset) + add; - add = ""; - if ((a instanceof ActionPush) && lastPush) { - writer.appendNoHilight(" "); - ((ActionPush) a).paramsToStringReplaced(list, importantOffsets, constantPool, version, exportMode, writer); - } else { - if (lastPush) { - writer.newLine(); - lastPush = false; - } - - writer.append("", offset); - - int fixBranch = -1; - if (a instanceof ActionIf) { - ActionIf aif = (ActionIf) a; - if (aif.jumpUsed && !aif.ignoreUsed) { - fixBranch = 0; - } - if (!aif.jumpUsed && aif.ignoreUsed) { - fixBranch = 1; - } - } - - if (fixBranch > -1) { - writer.appendNoHilight("FFDec_DeobfuscatePop").newLine(); - if (fixBranch == 0) { //jump - writer.appendNoHilight("Jump loc"); - writer.appendNoHilight(Helper.formatAddress(a.getAddress() + a.getBytes(version).length + ((ActionIf) a).getJumpOffset())); - } else { - //nojump, ignore - } - } else { - a.getASMSourceReplaced(list, importantOffsets, constantPool, version, exportMode, writer); - } - writer.appendNoHilight(a.isIgnored() ? "; ignored" : ""); - writer.appendNoHilight(add); - if (!(a instanceof ActionPush)) { - writer.newLine(); - } - } - if (a instanceof ActionPush) { - lastPush = true; - } else { - lastPush = false; - } - //} - } - offset += a.getBytes(version).length; - } - if (lastPush) { - writer.newLine(); - } - if (containers.containsKey(offset)) { - for (int i = 0; i < containers.get(offset).size(); i++) { - writer.appendNoHilight("}"); - writer.newLine(); - GraphSourceItemContainer cnt = containers.get(offset).get(i); - int cntPos = containersPos.get(cnt); - writer.appendNoHilight(cnt.getASMSourceBetween(cntPos)); - cntPos++; - containersPos.put(cnt, cntPos); - } - } - if (importantOffsets.contains(offset)) { - writer.appendNoHilight("loc"); - writer.appendNoHilight(Helper.formatAddress(offset)); - writer.appendNoHilight(":"); - writer.newLine(); - } - return writer; - } - - /** - * Convert action to ASM source - * - * @param container - * @param knownAddreses List of important offsets to mark as labels - * @param constantPool Constant pool - * @param version SWF version - * @param exportMode PCode or hex? - * @return String of P-code source - */ - public String getASMSource(List container, List knownAddreses, List constantPool, int version, ScriptExportMode exportMode) { - return toString(); - } - - /** - * Translates this function to stack and output. - * - * @param stack Stack - * @param output Output - * @param regNames Register names - * @param variables Variables - * @param functions Functions - * @param staticOperation the value of staticOperation - * @param path the value of path - * @throws java.lang.InterruptedException - */ - public void translate(Stack stack, List output, java.util.HashMap regNames, HashMap variables, HashMap functions, int staticOperation, String path) throws InterruptedException { - } - - /** - * Pops long value off the stack - * - * @param stack Stack - * @return long value - */ - protected long popLong(Stack stack) { - GraphTargetItem item = stack.pop(); - if (item instanceof DirectValueActionItem) { - if (((DirectValueActionItem) item).value instanceof Long) { - return (long) (Long) ((DirectValueActionItem) item).value; - } - } - return 0; - } - - /** - * Converts action index to address in the specified list of actions - * - * @param actions List of actions - * @param ip Action index - * @param version SWF version - * @return address - */ - public static long ip2adr(List actions, int ip, int version) { - /* List actions=new ArrayList(); - for(GraphSourceItem s:sources){ - if(s instanceof Action){ - actions.add((Action)s); - } - }*/ - if (ip >= actions.size()) { - if (actions.isEmpty()) { - return 0; - } - return actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getBytes(version).length; - } - if (ip == -1) { - return 0; - } - return actions.get(ip).getAddress(); - } - - /** - * Converts address to action index in the specified list of actions - * - * @param actions List of actions - * @param addr Address - * @param version SWF version - * @return action index - */ - public static int adr2ip(List actions, long addr, int version) { - for (int ip = 0; ip < actions.size(); ip++) { - if (actions.get(ip).getAddress() == addr) { - return ip; - } - } - if (actions.size() > 0) { - long outpos = actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getBytes(version).length; - if (addr == outpos) { - return actions.size(); - } - } - return -1; - } - - public static List actionsToTree(List actions, int version, int staticOperation, String path) throws InterruptedException { - return actionsToTree(new HashMap(), new HashMap(), new HashMap(), actions, version, staticOperation, path); - } - - /** - * Converts list of actions to ActionScript source code - * - * @param asm - * @param actions List of actions - * @param path - * @param writer - * @throws java.lang.InterruptedException - */ - public static void actionsToSource(final ASMSource asm, final List actions, final String path, GraphTextWriter writer) throws InterruptedException { - writer.suspendMeasure(); - List tree = null; - Throwable convertException = null; - int timeout = Configuration.decompilationTimeoutSingleMethod.get(); - try { - tree = CancellableWorker.call(new Callable>() { - @Override - public List call() throws Exception { - int staticOperation = Graph.SOP_USE_STATIC; //(Boolean) Configuration.getConfig("autoDeobfuscate", true) ? Graph.SOP_SKIP_STATIC : Graph.SOP_USE_STATIC; - List tree = actionsToTree(new HashMap(), new HashMap(), new HashMap(), actions, asm.getSwf().version, staticOperation, path); - Graph.graphToString(tree, new NulWriter(), new LocalData()); - return tree; - } - }, timeout, TimeUnit.SECONDS); - } catch (TimeoutException | ExecutionException | OutOfMemoryError | TranslateException | StackOverflowError ex) { - Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error", ex); - convertException = ex; - if (ex instanceof ExecutionException && ex.getCause() instanceof Exception) { - convertException = (Exception) ex.getCause(); - } - } - writer.continueMeasure(); - - asm.getActionSourcePrefix(writer); - if (convertException == null) { - Graph.graphToString(tree, writer, new LocalData()); - } else if (convertException instanceof TimeoutException) { - Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error", convertException); - Helper.appendTimeoutComment(writer, timeout); - } else { - Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error", convertException); - Helper.appendErrorComment(writer, convertException); - } - asm.getActionSourceSuffix(writer); - } - - /** - * Converts list of actions to List of treeItems - * - * @param regNames Register names - * @param variables - * @param functions - * @param actions List of actions - * @param version SWF version - * @param staticOperation - * @param path - * @return List of treeItems - * @throws java.lang.InterruptedException - */ - public static List actionsToTree(HashMap regNames, HashMap variables, HashMap functions, List actions, int version, int staticOperation, String path) throws InterruptedException { - //Stack stack = new Stack(); - return ActionGraph.translateViaGraph(regNames, variables, functions, actions, version, staticOperation, path); - //return actionsToTree(regNames, stack, actions, 0, actions.size() - 1, version); - } - - @Override - public void translate(BaseLocalData localData, Stack stack, List output, int staticOperation, String path) throws InterruptedException { - ActionLocalData aLocalData = (ActionLocalData) localData; - translate(stack, output, aLocalData.regNames, aLocalData.variables, aLocalData.functions, staticOperation, path); - } - - @Override - public boolean isJump() { - return false; - } - - @Override - public boolean isBranch() { - return false; - } - - @Override - public boolean isExit() { - return false; - } - - @Override - public long getOffset() { - return getAddress(); - } - - @Override - public List getBranches(GraphSource code) { - return new ArrayList<>(); - } - - @Override - public boolean isIgnored() { - return ignored; - } - - @Override - public void setIgnored(boolean ignored, int pos) { - this.ignored = ignored; - } - - private static class Loop { - - public long loopContinue; - public long loopBreak; - public int continueCount = 0; - public int breakCount = 0; - - public Loop(long loopContinue, long loopBreak) { - this.loopContinue = loopContinue; - this.loopBreak = loopBreak; - } - - @Override - public String toString() { - return "[Loop continue:" + loopContinue + ", break:" + loopBreak + "]"; - } - } - - private static void log(String s) { - logger.fine(s); - } - - public static List actionsPartToTree(HashMap registerNames, HashMap variables, HashMap functions, Stack stack, List actions, int start, int end, int version, int staticOperation, String path) throws InterruptedException { - if (start < actions.size() && (end > 0) && (start > 0)) { - log("Entering " + start + "-" + end + (actions.size() > 0 ? (" (" + actions.get(start).toString() + " - " + actions.get(end == actions.size() ? end - 1 : end) + ")") : "")); - } - ActionLocalData localData = new ActionLocalData(registerNames, variables, functions); - List output = new ArrayList<>(); - int ip = start; - boolean isWhile = false; - boolean isForIn = false; - GraphTargetItem inItem = null; - int loopStart = 0; - loopip: - while (ip <= end) { - - long addr = ip2adr(actions, ip, version); - if (ip > end) { - break; - } - if (ip >= actions.size()) { - output.add(new ScriptEndItem()); - break; - } - Action action = actions.get(ip); - if (action.isIgnored()) { - ip++; - continue; - } - if (action instanceof GraphSourceItemContainer) { - GraphSourceItemContainer cnt = (GraphSourceItemContainer) action; - //List out=actionsPartToTree(new HashMap(), new HashMap(),new HashMap(), new Stack(), src, ip+1,endip-1 , version); - long endAddr = action.getAddress() + cnt.getHeaderSize(); - String cntName = cnt.getName(); - List> outs = new ArrayList<>(); - HashMap variables2 = Helper.deepCopy(variables); - if (cnt instanceof ActionDefineFunction || cnt instanceof ActionDefineFunction2) { - for (int r = 0; r < 256; r++) { - if (variables2.containsKey("__register" + r)) { - variables2.remove("__register" + r); - } - } - } - for (long size : cnt.getContainerSizes()) { - if (size == 0) { - outs.add(new ArrayList()); - continue; - } - List out; - try { - out = ActionGraph.translateViaGraph(cnt.getRegNames(), variables2, functions, actions.subList(adr2ip(actions, endAddr, version), adr2ip(actions, endAddr + size, version)), version, staticOperation, path + (cntName == null ? "" : "/" + cntName)); - } catch (OutOfMemoryError | TranslateException | StackOverflowError ex2) { - Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error in: " + path, ex2); - if (ex2 instanceof OutOfMemoryError) { - Helper.freeMem(); - } - out = new ArrayList<>(); - out.add(new CommentItem(new String[]{ - "", - " * " + AppStrings.translate("decompilationError"), - " * " + AppStrings.translate("decompilationError.obfuscated"), - " * " + AppStrings.translate("decompilationError.errorType") + ": " - + ex2.getClass().getSimpleName(), - ""})); - } - outs.add(out); - endAddr += size; - } - ((GraphSourceItemContainer) action).translateContainer(outs, stack, output, registerNames, variables, functions); - ip = adr2ip(actions, endAddr, version); - continue; - } - - //return in for..in - if ((action instanceof ActionPush) && (((ActionPush) action).values.size() == 1) && (((ActionPush) action).values.get(0) instanceof Null)) { - if (ip + 3 <= end) { - if ((actions.get(ip + 1) instanceof ActionEquals) || (actions.get(ip + 1) instanceof ActionEquals2)) { - if (actions.get(ip + 2) instanceof ActionNot) { - if (actions.get(ip + 3) instanceof ActionIf) { - ActionIf aif = (ActionIf) actions.get(ip + 3); - if (adr2ip(actions, ip2adr(actions, ip + 4, version) + aif.getJumpOffset(), version) == ip) { - ip += 4; - continue; - } - } - } - } - } - } - - /*ActionJump && ActionIf removed*/ - /*if ((action instanceof ActionEnumerate2) || (action instanceof ActionEnumerate)) { - loopStart = ip + 1; - isForIn = true; - ip += 4; - action.translate(localData, stack, output); - EnumerateActionItem en = (EnumerateActionItem) stack.peek(); - inItem = en.object; - continue; - } else*/ /*if (action instanceof ActionTry) { - ActionTry atry = (ActionTry) action; - List tryCommands = ActionGraph.translateViaGraph(registerNames, variables, functions, atry.tryBody, version); - ActionItem catchName; - if (atry.catchInRegisterFlag) { - catchName = new DirectValueActionItem(atry, -1, new RegisterNumber(atry.catchRegister), new ArrayList()); - } else { - catchName = new DirectValueActionItem(atry, -1, atry.catchName, new ArrayList()); - } - List catchExceptions = new ArrayList(); - catchExceptions.add(catchName); - List> catchCommands = new ArrayList>(); - catchCommands.add(ActionGraph.translateViaGraph(registerNames, variables, functions, atry.catchBody, version)); - List finallyCommands = ActionGraph.translateViaGraph(registerNames, variables, functions, atry.finallyBody, version); - output.add(new TryActionItem(tryCommands, catchExceptions, catchCommands, finallyCommands)); - } else if (action instanceof ActionWith) { - ActionWith awith = (ActionWith) action; - List withCommands = ActionGraph.translateViaGraph(registerNames, variables, functions,new ArrayList() , version); //TODO:parse with actions - output.add(new WithActionItem(action, stack.pop(), withCommands)); - } else */ if (false) { - } /*if (action instanceof ActionStoreRegister) { - if ((ip + 1 <= end) && (actions.get(ip + 1) instanceof ActionPop)) { - action.translate(localData, stack, output); - stack.pop(); - ip++; - } else { - try { - action.translate(localData, stack, output); - } catch (Exception ex) { - //ignore - } - } - } */ /*else if (action instanceof ActionStrictEquals) { - if ((ip + 1 < actions.size()) && (actions.get(ip + 1) instanceof ActionIf)) { - List caseValues = new ArrayList(); - List> caseCommands = new ArrayList>(); - caseValues.add(stack.pop()); - ActionItem switchedObject = stack.pop(); - if (output.size() > 0) { - if (output.get(output.size() - 1) instanceof StoreRegisterActionItem) { - output.remove(output.size() - 1); - } - } - int caseStart = ip + 2; - List caseBodyIps = new ArrayList(); - long defaultAddr = 0; - caseBodyIps.add(adr2ip(actions, ((ActionIf) actions.get(ip + 1)).getRef(version), version)); - ip++; - do { - ip++; - if ((actions.get(ip - 1) instanceof ActionStrictEquals) && (actions.get(ip) instanceof ActionIf)) { - caseValues.add(actionsToStackTree(registerNames, jumpsOrIfs, actions, constants, caseStart, ip - 2, version).pop()); - caseStart = ip + 1; - caseBodyIps.add(adr2ip(actions, ((ActionIf) actions.get(ip)).getRef(version), version)); - if (actions.get(ip + 1) instanceof ActionJump) { - defaultAddr = ((ActionJump) actions.get(ip + 1)).getRef(version); - ip = adr2ip(actions, defaultAddr, version); - break; - } - } - } while (ip < end); - - for (int i = 0; i < caseBodyIps.size(); i++) { - int caseEnd = ip - 1; - if (i < caseBodyIps.size() - 1) { - caseEnd = caseBodyIps.get(i + 1) - 1; - } - caseCommands.add(actionsToTree(registerNames, unknownJumps, loopList, jumpsOrIfs, stack, constants, actions, caseBodyIps.get(i), caseEnd, version)); - } - output.add(new SwitchActionItem(action, defaultAddr, switchedObject, caseValues, caseCommands, null)); - continue; - } else { - action.translate(stack, constants, output, registerNames); - } - } */ else { - - if (action instanceof ActionStore) { - ActionStore store = (ActionStore) action; - store.setStore(actions.subList(ip + 1, ip + 1 + store.getStoreSize())); - ip = ip + 1 + store.getStoreSize() - 1/*ip++ will be next*/; - } - - try { - action.translate(localData, stack, output, staticOperation, path); - } catch (EmptyStackException ese) { - Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error in: " + path, ese); - output.add(new UnsupportedActionItem(action, "Empty stack")); - } - - } - - ip++; - } - //output = checkClass(output); - log("Leaving " + start + "-" + end); - return output; - } - - public static GraphTargetItem getWithoutGlobal(GraphTargetItem ti) { - GraphTargetItem t = ti; - if (!(t instanceof GetMemberActionItem)) { - return ti; - } - GetMemberActionItem lastMember = null; - while (((GetMemberActionItem) t).object instanceof GetMemberActionItem) { - lastMember = (GetMemberActionItem) t; - t = ((GetMemberActionItem) t).object; - } - if (((GetMemberActionItem) t).object instanceof GetVariableActionItem) { - GetVariableActionItem v = (GetVariableActionItem) ((GetMemberActionItem) t).object; - if (v.name instanceof DirectValueActionItem) { - if (((DirectValueActionItem) v.name).value instanceof String) { - if (((DirectValueActionItem) v.name).value.equals("_global")) { - GetVariableActionItem gvt = new GetVariableActionItem(null, ((GetMemberActionItem) t).memberName); - if (lastMember == null) { - return gvt; - } else { - lastMember.object = gvt; - } - } - } - } - } - return ti; - } - - public static List checkClass(List output) { - if (true) { - //return output; - } - List ret = new ArrayList<>(); - List functions = new ArrayList<>(); - List staticFunctions = new ArrayList<>(); - List> vars = new ArrayList<>(); - List> staticVars = new ArrayList<>(); - GraphTargetItem className; - GraphTargetItem extendsOp = null; - List implementsOp = new ArrayList<>(); - boolean ok = true; - int prevCount = 0; - for (GraphTargetItem t : output) { - if (t instanceof IfItem) { - IfItem it = (IfItem) t; - if (it.expression instanceof NotItem) { - NotItem nti = (NotItem) it.expression; - if ((nti.value instanceof GetMemberActionItem) || (nti.value instanceof GetVariableActionItem)) { - if (true) { //it.onFalse.isEmpty()){ //||(it.onFalse.get(0) instanceof UnsupportedActionItem)) { - if ((it.onTrue.size() == 1) && (it.onTrue.get(0) instanceof SetMemberActionItem) && (((SetMemberActionItem) it.onTrue.get(0)).value instanceof NewObjectActionItem)) { - //ignore - } else { - List parts = it.onTrue; - className = getWithoutGlobal(nti.value); - if (parts.size() >= 1) { - int ipos = 0; - while ((parts.get(ipos) instanceof IfItem) - && ((((IfItem) parts.get(ipos)).onTrue.size() == 1) && (((IfItem) parts.get(ipos)).onTrue.get(0) instanceof SetMemberActionItem) && (((SetMemberActionItem) ((IfItem) parts.get(ipos)).onTrue.get(0)).value instanceof NewObjectActionItem))) { - - ipos++; - } - if (parts.get(ipos) instanceof ExtendsActionItem) { - ExtendsActionItem et = (ExtendsActionItem) parts.get(ipos); - extendsOp = getWithoutGlobal(et.superclass); - ipos++; - } - if (parts.get(ipos) instanceof StoreRegisterActionItem) { - StoreRegisterActionItem sr = (StoreRegisterActionItem) parts.get(ipos); - int instanceReg = sr.register.number; - if (sr.value instanceof GetMemberActionItem) { - GetMemberActionItem gm = (GetMemberActionItem) sr.value; - //gm.memberName should be "prototype" - if (gm.object instanceof TemporaryRegister) { - TemporaryRegister tm = (TemporaryRegister) gm.object; - int classReg = tm.getRegId(); - if (tm.value instanceof SetMemberActionItem) { - SetMemberActionItem sm = (SetMemberActionItem) tm.value; - if (sm.value instanceof StoreRegisterActionItem) { - sr = (StoreRegisterActionItem) sm.value; - if (sr.value instanceof FunctionActionItem) { - ((FunctionActionItem) (sr.value)).calculatedFunctionName = (className instanceof GetMemberActionItem) ? ((GetMemberActionItem) className).memberName : className; - functions.add((FunctionActionItem) sr.value); - - for (; ipos < parts.size(); ipos++) { - if (parts.get(ipos) instanceof ImplementsOpActionItem) { - ImplementsOpActionItem io = (ImplementsOpActionItem) parts.get(ipos); - implementsOp = io.superclasses; - continue; - } - if (parts.get(ipos) instanceof SetMemberActionItem) { - sm = (SetMemberActionItem) parts.get(ipos); - int rnum = -1; - if (sm.object instanceof DirectValueActionItem) { - DirectValueActionItem dv = (DirectValueActionItem) sm.object; - if (dv.value instanceof RegisterNumber) { - RegisterNumber rn = (RegisterNumber) dv.value; - rnum = rn.number; - } - } - if (sm.object instanceof TemporaryRegister) { - rnum = ((TemporaryRegister) sm.object).getRegId(); - } - if (rnum == instanceReg) { - if (sm.value instanceof FunctionActionItem) { - ((FunctionActionItem) sm.value).calculatedFunctionName = sm.objectName; - functions.add((FunctionActionItem) sm.value); - } else { - vars.add(new MyEntry<>(sm.objectName, sm.value)); - } - } else if (rnum == classReg) { - if (sm.value instanceof FunctionActionItem) { - ((FunctionActionItem) sm.value).calculatedFunctionName = sm.objectName; - staticFunctions.add((FunctionActionItem) sm.value); - } else { - staticVars.add(new MyEntry<>(sm.objectName, sm.value)); - } - } - - } - } - - } - - } - } - List output2 = new ArrayList<>(); - for (int i = 0; i < prevCount; i++) { - output2.add(output.get(i)); - } - output2.add(new ClassActionItem(className, extendsOp, implementsOp, null/*FIXME*/, functions, vars, staticFunctions, staticVars)); - return output2; - } - } - } else if (parts.get(ipos) instanceof SetMemberActionItem) { - SetMemberActionItem sm = (SetMemberActionItem) parts.get(0); - if (sm.value instanceof FunctionActionItem) { - FunctionActionItem f = (FunctionActionItem) sm.value; - if (f.actions.isEmpty()) { - if (parts.size() == 2) { - if (parts.get(1) instanceof ImplementsOpActionItem) { - ImplementsOpActionItem iot = (ImplementsOpActionItem) parts.get(1); - implementsOp = iot.superclasses; - } else { - ok = false; - break; - } - } - List output2 = new ArrayList<>(); - for (int i = 0; i < prevCount; i++) { - output2.add(output.get(i)); - } - output2.add(new InterfaceActionItem(sm.objectName, implementsOp)); - return output2; - } - } - } - } - } - } else { - ok = false; - } - } else { - ok = false; - } - } else { - ok = false; - } - } else { - prevCount++; - //ok = false; - } - if (!ok) { - break; - } - } - return output; - } - - @Override - public boolean ignoredLoops() { - return false; - } - - public static List removeNops(long address, List actions, int version, String path) { - List ret = actions; - if (true) { - //return ret; - } - String s = null; - try { - HilightedTextWriter writer = new HilightedTextWriter(Configuration.getCodeFormatting(), false); - Action.actionsToString(new ArrayList(), address, ret, null, version, ScriptExportMode.PCODE, writer, path); - s = writer.toString(); - ret = ASMParser.parse(address, true, s, SWF.DEFAULT_VERSION, false); - } catch (IOException | ParseException ex) { - Logger.getLogger(SWFInputStream.class.getName()).log(Level.SEVERE, "parsing error. path: " + path, ex); - } - return ret; - } - - public static void setConstantPool(List actions, ConstantPool cpool) { - for (GraphSourceItem a : actions) { - if (a instanceof ActionPush) { - if (cpool != null) { - ((ActionPush) a).constantPool = cpool.constants; - } - } - if (a instanceof ActionDefineFunction) { - if (cpool != null) { - //((ActionDefineFunction) a).setConstantPool(cpool.constants,actions); - } - } - if (a instanceof ActionDefineFunction2) { - if (cpool != null) { - //((ActionDefineFunction2) a).setConstantPool(cpool.constants,actions); - } - } - } - } - - public GraphTextWriter getASMSourceReplaced(List container, List knownAddreses, List constantPool, int version, ScriptExportMode exportMode, GraphTextWriter writer) { - writer.appendNoHilight(getASMSource(container, knownAddreses, constantPool, version, exportMode)); - return writer; - } - - public static double toFloatPoint(Object o) { - if (o instanceof Double) { - return (Double) o; - } - if (o instanceof Integer) { - return (Integer) o; - } - if (o instanceof Long) { - return (Long) o; - } - if (o instanceof String) { - try { - return Double.parseDouble((String) o); - } catch (NumberFormatException nfe) { - return 0; - } - } - return 0; - } - - public static GraphTargetItem gettoset(GraphTargetItem get, GraphTargetItem value, List variables) { - GraphTargetItem ret = get; - boolean boxed = false; - if (get instanceof VariableActionItem) { - boxed = true; - ret = ((VariableActionItem) ret).getBoxedValue(); - } - if (ret instanceof GetVariableActionItem) { - GetVariableActionItem gv = (GetVariableActionItem) ret; - ret = new SetVariableActionItem(null, gv.name, value); - } else if (ret instanceof GetMemberActionItem) { - GetMemberActionItem mem = (GetMemberActionItem) ret; - ret = new SetMemberActionItem(null, mem.object, mem.memberName, value); - } else if ((ret instanceof DirectValueActionItem) && ((DirectValueActionItem) ret).value instanceof RegisterNumber) { - ret = new StoreRegisterActionItem(null, (RegisterNumber) ((DirectValueActionItem) ret).value, value, false); - } else if (ret instanceof GetPropertyActionItem) { - GetPropertyActionItem gp = (GetPropertyActionItem) ret; - ret = new SetPropertyActionItem(null, gp.target, gp.propertyIndex, value); - } - if (boxed) { - GraphTargetItem b = ret; - ret = new VariableActionItem(((VariableActionItem) get).getVariableName(), value, ((VariableActionItem) get).isDefinition()); - ((VariableActionItem) ret).setBoxedValue((ActionItem) b); - variables.remove((VariableActionItem) get); - variables.add((VariableActionItem) ret); - } - return ret; - } - - @Override - public boolean isDeobfuscatePop() { - return false; - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.action; + +import com.jpexs.decompiler.flash.AppStrings; +import com.jpexs.decompiler.flash.BaseLocalData; +import com.jpexs.decompiler.flash.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.action.model.ActionItem; +import com.jpexs.decompiler.flash.action.model.ConstantPool; +import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; +import com.jpexs.decompiler.flash.action.model.ExtendsActionItem; +import com.jpexs.decompiler.flash.action.model.FunctionActionItem; +import com.jpexs.decompiler.flash.action.model.GetMemberActionItem; +import com.jpexs.decompiler.flash.action.model.GetPropertyActionItem; +import com.jpexs.decompiler.flash.action.model.GetVariableActionItem; +import com.jpexs.decompiler.flash.action.model.ImplementsOpActionItem; +import com.jpexs.decompiler.flash.action.model.NewObjectActionItem; +import com.jpexs.decompiler.flash.action.model.SetMemberActionItem; +import com.jpexs.decompiler.flash.action.model.SetPropertyActionItem; +import com.jpexs.decompiler.flash.action.model.SetVariableActionItem; +import com.jpexs.decompiler.flash.action.model.StoreRegisterActionItem; +import com.jpexs.decompiler.flash.action.model.TemporaryRegister; +import com.jpexs.decompiler.flash.action.model.UnsupportedActionItem; +import com.jpexs.decompiler.flash.action.model.clauses.ClassActionItem; +import com.jpexs.decompiler.flash.action.model.clauses.InterfaceActionItem; +import com.jpexs.decompiler.flash.action.parser.ParseException; +import com.jpexs.decompiler.flash.action.parser.pcode.ASMParsedSymbol; +import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; +import com.jpexs.decompiler.flash.action.parser.pcode.FlasmLexer; +import com.jpexs.decompiler.flash.action.parser.script.VariableActionItem; +import com.jpexs.decompiler.flash.action.special.ActionEnd; +import com.jpexs.decompiler.flash.action.special.ActionStore; +import com.jpexs.decompiler.flash.action.swf4.ActionEquals; +import com.jpexs.decompiler.flash.action.swf4.ActionIf; +import com.jpexs.decompiler.flash.action.swf4.ActionJump; +import com.jpexs.decompiler.flash.action.swf4.ActionNot; +import com.jpexs.decompiler.flash.action.swf4.ActionPush; +import com.jpexs.decompiler.flash.action.swf4.RegisterNumber; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; +import com.jpexs.decompiler.flash.action.swf5.ActionEquals2; +import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.ecma.Null; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.helpers.HilightedTextWriter; +import com.jpexs.decompiler.flash.helpers.NulWriter; +import com.jpexs.decompiler.flash.helpers.collections.MyEntry; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.graph.Graph; +import com.jpexs.decompiler.graph.GraphSource; +import com.jpexs.decompiler.graph.GraphSourceItem; +import com.jpexs.decompiler.graph.GraphSourceItemContainer; +import com.jpexs.decompiler.graph.GraphTargetItem; +import com.jpexs.decompiler.graph.TranslateException; +import com.jpexs.decompiler.graph.model.CommentItem; +import com.jpexs.decompiler.graph.model.IfItem; +import com.jpexs.decompiler.graph.model.LocalData; +import com.jpexs.decompiler.graph.model.NotItem; +import com.jpexs.decompiler.graph.model.ScriptEndItem; +import com.jpexs.helpers.CancellableWorker; +import com.jpexs.helpers.Helper; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EmptyStackException; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Represents one ACTIONRECORD, also has some static method to work with Actions + */ +public class Action implements GraphSourceItem { + + public Action replaceWith; + private boolean ignored = false; + /** + * Action type identifier + */ + public int actionCode; + /** + * Length of action data + */ + public int actionLength; + private long address; + + public static final String[] reservedWords = { + "as", "break", "case", "catch", "class", "const", "continue", "default", "delete", "do", "each", "else", + "extends", "false", "finally", "for", "function", "get", "if", "implements", "import", "in", "instanceof", + "interface", "internal", "is", "native", "new", "null", "override", "package", "private", "protected", "public", + "return", "set", "super", "switch", "this", "throw", "true", "try", "typeof", "use", "var", /*"void",*/ "while", + "with", "dynamic", "default", "final", "in"}; + + public static boolean isReservedWord(String s) { + if (s == null) { + return false; + } + for (String rw : reservedWords) { + if (rw.equals(s.trim())) { + return true; + } + } + return false; + } + + + /** + * Names of ActionScript properties + */ + public static final String[] propertyNames = new String[]{ + "_X", + "_Y", + "_xscale", + "_yscale", + "_currentframe", + "_totalframes", + "_alpha", + "_visible", + "_width", + "_height", + "_rotation", + "_target", + "_framesloaded", + "_name", + "_droptarget", + "_url", + "_highquality", + "_focusrect", + "_soundbuftime", + "_quality", + "_xmouse", + "_ymouse" + }; + public static final List propertyNamesList = Arrays.asList(propertyNames); + private static final Logger logger = Logger.getLogger(Action.class.getName()); + + /** + * Constructor + * + * @param actionCode Action type identifier + * @param actionLength Length of action data + */ + public Action(int actionCode, int actionLength) { + this.actionCode = actionCode; + this.actionLength = actionLength; + } + + public Action() { + } + + /** + * Returns address of this action + * + * @return address of this action + */ + public long getAddress() { + return address; + } + + /** + * Gets all addresses which are referenced from this action and/or + * subactions + * + * @param version SWF version + * @return List of addresses + */ + public List getAllRefs(int version) { + List ret = new ArrayList<>(); + return ret; + } + + /** + * Gets all ActionIf or ActionJump actions from subactions + * + * @return List of actions + */ + public List getAllIfsOrJumps() { + List ret = new ArrayList<>(); + return ret; + } + + /** + * Gets all ActionIf or ActionJump actions from list of actions + * + * @param list List of actions + * @return List of actions + */ + public static List getActionsAllIfsOrJumps(List list) { + List ret = new ArrayList<>(); + for (Action a : list) { + List part = a.getAllIfsOrJumps(); + ret.addAll(part); + } + return ret; + } + + /** + * Gets all addresses which are referenced from the list of actions + * + * @param list List of actions + * @param version SWF version + * @return List of addresses + */ + public static List getActionsAllRefs(List list, int version) { + List ret = new ArrayList<>(); + for (Action a : list) { + if (a.replaceWith != null) { + a.replaceWith.setAddress(a.getAddress(), version, false); + ret.addAll(a.replaceWith.getAllRefs(version)); + } + List part = a.getAllRefs(version); + ret.addAll(part); + } + return ret; + } + + /** + * Sets address of this instruction + * + * @param address Address + * @param version SWF version + */ + public final void setAddress(long address, int version) { + setAddress(address, version, true); + } + + public void setAddress(long address, int version, boolean recursive) { + this.address = address; + } + + /** + * Returns a string representation of the object + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return "Action" + actionCode; + } + + /** + * Reads String from FlasmLexer + * + * @param lex FlasmLexer + * @return String value + * @throws IOException + * @throws ParseException When read object is not String + */ + protected String lexString(FlasmLexer lex) throws IOException, ParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_STRING) { + throw new ParseException("String expected", lex.yyline()); + } + return (String) symb.value; + } + + /** + * Reads Block startServer from FlasmLexer + * + * @param lex FlasmLexer + * @throws IOException + * @throws ParseException When read object is not Block startServer + */ + protected void lexBlockOpen(FlasmLexer lex) throws IOException, ParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_BLOCK_START) { + throw new ParseException("Block startServer ", lex.yyline()); + } + } + + /** + * Reads Identifier from FlasmLexer + * + * @param lex FlasmLexer + * @return Identifier name + * @throws IOException + * @throws ParseException When read object is not Identifier + */ + protected String lexIdentifier(FlasmLexer lex) throws IOException, ParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_IDENTIFIER) { + throw new ParseException("Identifier expected", lex.yyline()); + } + return (String) symb.value; + } + + /** + * Reads long value from FlasmLexer + * + * @param lex FlasmLexer + * @return long value + * @throws IOException + * @throws ParseException When read object is not long value + */ + protected long lexLong(FlasmLexer lex) throws IOException, ParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_INTEGER) { + throw new ParseException("Integer expected", lex.yyline()); + } + return (Long) symb.value; + } + + /** + * Reads boolean value from FlasmLexer + * + * @param lex FlasmLexer + * @return boolean value + * @throws IOException + * @throws ParseException When read object is not boolean value + */ + protected boolean lexBoolean(FlasmLexer lex) throws IOException, ParseException { + ASMParsedSymbol symb = lex.yylex(); + if (symb.type != ASMParsedSymbol.TYPE_BOOLEAN) { + throw new ParseException("Boolean expected", lex.yyline()); + } + return (Boolean) symb.value; + } + + /** + * Gets action converted to bytes + * + * @param version SWF version + * @return Array of bytes + */ + public byte[] getBytes(int version) { + return surroundWithAction(new byte[0], version); + } + + /** + * Surrounds byte array with Action header + * + * @param data Byte array + * @param version SWF version + * @return Byte array + */ + protected byte[] surroundWithAction(byte[] data, int version) { + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(baos2, version); + try { + sos2.writeUI8(actionCode); + if (actionCode >= 0x80) { + sos2.writeUI16(data.length); + } + sos2.write(data); + sos2.close(); + } catch (IOException e) { + } + return baos2.toByteArray(); + } + + /** + * Converts list of Actions to bytes + * + * @param list List of actions + * @param addZero Whether or not to add 0 UI8 value to the end + * @param version SWF version + * @return Array of bytes + */ + public static byte[] actionsToBytes(List list, boolean addZero, int version) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Action lastAction = null; + for (Action a : list) { + try { + lastAction = a; + baos.write(a.getBytes(version)); + } catch (IOException e) { + } + } + if (addZero && (lastAction == null || !(lastAction instanceof ActionEnd))) { + baos.write(0); + } + return baos.toByteArray(); + } + + /** + * Set addresses of actions in the list + * + * @param list List of actions + * @param baseAddress Address of first action in the list + * @param version SWF version + */ + public static void setActionsAddresses(List list, long baseAddress, int version) { + long offset = baseAddress; + for (Action a : list) { + a.setAddress(offset, version); + offset += a.getBytes(version).length; + } + } + + /** + * Converts list of actions to ASM source + * + * @param listeners + * @param address + * @param list List of actions + * @param importantOffsets List of important offsets to mark as labels + * @param version SWF version + * @param exportMode PCode or hex? + * @param writer + * @param path + * @return HilightedTextWriter + */ + public static GraphTextWriter actionsToString(List listeners, long address, List list, List importantOffsets, int version, ScriptExportMode exportMode, GraphTextWriter writer, String path) { + return actionsToString(listeners, address, list, importantOffsets, new ArrayList(), version, exportMode, writer, path); + } + + /** + * Converts list of actions to ASM source + * + * @param listeners + * @param address + * @param list List of actions + * @param importantOffsets List of important offsets to mark as labels + * @param constantPool Constant pool + * @param version SWF version + * @param hex Add hexadecimal? + * @param path + * @return HilightedTextWriter + */ + private static GraphTextWriter actionsToString(List listeners, long address, List list, List importantOffsets, List constantPool, int version, ScriptExportMode exportMode, GraphTextWriter writer, String path) { + long offset; + if (importantOffsets == null) { + //setActionsAddresses(list, 0, version); + importantOffsets = getActionsAllRefs(list, version); + } + /*List cps = SWFInputStream.getConstantPool(new ArrayList(), new ActionGraphSource(list, version, new HashMap(), new HashMap(), new HashMap()), 0, version, path); + if (!cps.isEmpty()) { + setConstantPool(list, cps.get(cps.size() - 1)); + }*/ + HashMap> containers = new HashMap<>(); + HashMap containersPos = new HashMap<>(); + offset = address; + int pos = -1; + boolean lastPush = false; + for (GraphSourceItem s : list) { + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).progress(AppStrings.translate("disassemblingProgress.toString"), pos + 2, list.size()); + } + Action a = null; + if (s instanceof Action) { + a = (Action) s; + } + pos++; + if (exportMode == ScriptExportMode.PCODE_HEX) { + if (lastPush) { + writer.newLine(); + lastPush = false; + } + writer.appendNoHilight("; "); + writer.appendNoHilight(Helper.bytesToHexString(a.getBytes(version))); + writer.newLine(); + } + offset = a.getAddress(); + + if ((!(a.isIgnored())) && (a instanceof GraphSourceItemContainer)) { + GraphSourceItemContainer cnt = (GraphSourceItemContainer) a; + containersPos.put(cnt, 0); + List sizes = cnt.getContainerSizes(); + long addr = ((Action) cnt).getAddress() + cnt.getHeaderSize(); + for (Long size : sizes) { + addr += size; + if (size == 0) { + continue; + } + if (!containers.containsKey(addr)) { + containers.put(addr, new ArrayList()); + } + containers.get(addr).add(cnt); + } + } + + if (containers.containsKey(offset)) { + for (int i = 0; i < containers.get(offset).size(); i++) { + writer.appendNoHilight("}").newLine(); + GraphSourceItemContainer cnt = containers.get(offset).get(i); + int cntPos = containersPos.get(cnt); + writer.appendNoHilight(cnt.getASMSourceBetween(cntPos)); + cntPos++; + containersPos.put(cnt, cntPos); + } + } + + if (Configuration.showAllAddresses.get() || importantOffsets.contains(offset)) { + if (lastPush) { + writer.newLine(); + lastPush = false; + } + writer.appendNoHilight("loc"); + writer.appendNoHilight(Helper.formatAddress(offset)); + writer.appendNoHilight(":"); + } + + if (a.replaceWith != null) { + if (lastPush) { + writer.newLine(); + lastPush = false; + } + writer.append("", offset); + writer.appendNoHilight(a.replaceWith.getASMSource(list, importantOffsets, constantPool, version, exportMode)); + writer.newLine(); + } else if (a.isIgnored()) { + if (lastPush) { + writer.newLine(); + lastPush = false; + } + int len = 0; + if (pos + 1 < list.size()) { + len = (int) (((Action) (list.get(pos + 1))).getAddress() - a.getAddress()); + } else { + len = a.getBytes(version).length; + } + if (!(a instanceof ActionEnd)) { + for (int i = 0; i < len; i++) { + writer.appendNoHilight("Nop").newLine(); + } + } + } else { + //if (!(a instanceof ActionNop)) { + String add = ""; + if (a instanceof ActionIf) { + add = " change: " + ((ActionIf) a).getJumpOffset(); + } + if (a instanceof ActionJump) { + add = " change: " + ((ActionJump) a).getJumpOffset(); + } + add = "; ofs" + Helper.formatAddress(offset) + add; + add = ""; + if ((a instanceof ActionPush) && lastPush) { + writer.appendNoHilight(" "); + ((ActionPush) a).paramsToStringReplaced(list, importantOffsets, constantPool, version, exportMode, writer); + } else { + if (lastPush) { + writer.newLine(); + lastPush = false; + } + + writer.append("", offset); + + int fixBranch = -1; + if (a instanceof ActionIf) { + ActionIf aif = (ActionIf) a; + if (aif.jumpUsed && !aif.ignoreUsed) { + fixBranch = 0; + } + if (!aif.jumpUsed && aif.ignoreUsed) { + fixBranch = 1; + } + } + + if (fixBranch > -1) { + writer.appendNoHilight("FFDec_DeobfuscatePop").newLine(); + if (fixBranch == 0) { //jump + writer.appendNoHilight("Jump loc"); + writer.appendNoHilight(Helper.formatAddress(a.getAddress() + a.getBytes(version).length + ((ActionIf) a).getJumpOffset())); + } else { + //nojump, ignore + } + } else { + a.getASMSourceReplaced(list, importantOffsets, constantPool, version, exportMode, writer); + } + writer.appendNoHilight(a.isIgnored() ? "; ignored" : ""); + writer.appendNoHilight(add); + if (!(a instanceof ActionPush)) { + writer.newLine(); + } + } + if (a instanceof ActionPush) { + lastPush = true; + } else { + lastPush = false; + } + //} + } + offset += a.getBytes(version).length; + } + if (lastPush) { + writer.newLine(); + } + if (containers.containsKey(offset)) { + for (int i = 0; i < containers.get(offset).size(); i++) { + writer.appendNoHilight("}"); + writer.newLine(); + GraphSourceItemContainer cnt = containers.get(offset).get(i); + int cntPos = containersPos.get(cnt); + writer.appendNoHilight(cnt.getASMSourceBetween(cntPos)); + cntPos++; + containersPos.put(cnt, cntPos); + } + } + if (importantOffsets.contains(offset)) { + writer.appendNoHilight("loc"); + writer.appendNoHilight(Helper.formatAddress(offset)); + writer.appendNoHilight(":"); + writer.newLine(); + } + return writer; + } + + /** + * Convert action to ASM source + * + * @param container + * @param knownAddreses List of important offsets to mark as labels + * @param constantPool Constant pool + * @param version SWF version + * @param exportMode PCode or hex? + * @return String of P-code source + */ + public String getASMSource(List container, List knownAddreses, List constantPool, int version, ScriptExportMode exportMode) { + return toString(); + } + + /** + * Translates this function to stack and output. + * + * @param stack Stack + * @param output Output + * @param regNames Register names + * @param variables Variables + * @param functions Functions + * @param staticOperation the value of staticOperation + * @param path the value of path + * @throws java.lang.InterruptedException + */ + public void translate(Stack stack, List output, java.util.HashMap regNames, HashMap variables, HashMap functions, int staticOperation, String path) throws InterruptedException { + } + + /** + * Pops long value off the stack + * + * @param stack Stack + * @return long value + */ + protected long popLong(Stack stack) { + GraphTargetItem item = stack.pop(); + if (item instanceof DirectValueActionItem) { + if (((DirectValueActionItem) item).value instanceof Long) { + return (long) (Long) ((DirectValueActionItem) item).value; + } + } + return 0; + } + + /** + * Converts action index to address in the specified list of actions + * + * @param actions List of actions + * @param ip Action index + * @param version SWF version + * @return address + */ + public static long ip2adr(List actions, int ip, int version) { + /* List actions=new ArrayList(); + for(GraphSourceItem s:sources){ + if(s instanceof Action){ + actions.add((Action)s); + } + }*/ + if (ip >= actions.size()) { + if (actions.isEmpty()) { + return 0; + } + return actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getBytes(version).length; + } + if (ip == -1) { + return 0; + } + return actions.get(ip).getAddress(); + } + + /** + * Converts address to action index in the specified list of actions + * + * @param actions List of actions + * @param addr Address + * @param version SWF version + * @return action index + */ + public static int adr2ip(List actions, long addr, int version) { + for (int ip = 0; ip < actions.size(); ip++) { + if (actions.get(ip).getAddress() == addr) { + return ip; + } + } + if (actions.size() > 0) { + long outpos = actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getBytes(version).length; + if (addr == outpos) { + return actions.size(); + } + } + return -1; + } + + public static List actionsToTree(List actions, int version, int staticOperation, String path) throws InterruptedException { + return actionsToTree(new HashMap(), new HashMap(), new HashMap(), actions, version, staticOperation, path); + } + + /** + * Converts list of actions to ActionScript source code + * + * @param asm + * @param actions List of actions + * @param path + * @param writer + * @throws java.lang.InterruptedException + */ + public static void actionsToSource(final ASMSource asm, final List actions, final String path, GraphTextWriter writer) throws InterruptedException { + writer.suspendMeasure(); + List tree = null; + Throwable convertException = null; + int timeout = Configuration.decompilationTimeoutSingleMethod.get(); + try { + tree = CancellableWorker.call(new Callable>() { + @Override + public List call() throws Exception { + int staticOperation = Graph.SOP_USE_STATIC; //(Boolean) Configuration.getConfig("autoDeobfuscate", true) ? Graph.SOP_SKIP_STATIC : Graph.SOP_USE_STATIC; + List tree = actionsToTree(new HashMap(), new HashMap(), new HashMap(), actions, asm.getSwf().version, staticOperation, path); + Graph.graphToString(tree, new NulWriter(), new LocalData()); + return tree; + } + }, timeout, TimeUnit.SECONDS); + } catch (TimeoutException | ExecutionException | OutOfMemoryError | TranslateException | StackOverflowError ex) { + Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error", ex); + convertException = ex; + if (ex instanceof ExecutionException && ex.getCause() instanceof Exception) { + convertException = (Exception) ex.getCause(); + } + } + writer.continueMeasure(); + + asm.getActionSourcePrefix(writer); + if (convertException == null) { + Graph.graphToString(tree, writer, new LocalData()); + } else if (convertException instanceof TimeoutException) { + Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error", convertException); + Helper.appendTimeoutComment(writer, timeout); + } else { + Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error", convertException); + Helper.appendErrorComment(writer, convertException); + } + asm.getActionSourceSuffix(writer); + } + + /** + * Converts list of actions to List of treeItems + * + * @param regNames Register names + * @param variables + * @param functions + * @param actions List of actions + * @param version SWF version + * @param staticOperation + * @param path + * @return List of treeItems + * @throws java.lang.InterruptedException + */ + public static List actionsToTree(HashMap regNames, HashMap variables, HashMap functions, List actions, int version, int staticOperation, String path) throws InterruptedException { + //Stack stack = new Stack(); + return ActionGraph.translateViaGraph(regNames, variables, functions, actions, version, staticOperation, path); + //return actionsToTree(regNames, stack, actions, 0, actions.size() - 1, version); + } + + @Override + public void translate(BaseLocalData localData, Stack stack, List output, int staticOperation, String path) throws InterruptedException { + ActionLocalData aLocalData = (ActionLocalData) localData; + translate(stack, output, aLocalData.regNames, aLocalData.variables, aLocalData.functions, staticOperation, path); + } + + @Override + public boolean isJump() { + return false; + } + + @Override + public boolean isBranch() { + return false; + } + + @Override + public boolean isExit() { + return false; + } + + @Override + public long getOffset() { + return getAddress(); + } + + @Override + public List getBranches(GraphSource code) { + return new ArrayList<>(); + } + + @Override + public boolean isIgnored() { + return ignored; + } + + @Override + public void setIgnored(boolean ignored, int pos) { + this.ignored = ignored; + } + + private static class Loop { + + public long loopContinue; + public long loopBreak; + public int continueCount = 0; + public int breakCount = 0; + + public Loop(long loopContinue, long loopBreak) { + this.loopContinue = loopContinue; + this.loopBreak = loopBreak; + } + + @Override + public String toString() { + return "[Loop continue:" + loopContinue + ", break:" + loopBreak + "]"; + } + } + + private static void log(String s) { + logger.fine(s); + } + + public static List actionsPartToTree(HashMap registerNames, HashMap variables, HashMap functions, Stack stack, List actions, int start, int end, int version, int staticOperation, String path) throws InterruptedException { + if (start < actions.size() && (end > 0) && (start > 0)) { + log("Entering " + start + "-" + end + (actions.size() > 0 ? (" (" + actions.get(start).toString() + " - " + actions.get(end == actions.size() ? end - 1 : end) + ")") : "")); + } + ActionLocalData localData = new ActionLocalData(registerNames, variables, functions); + List output = new ArrayList<>(); + int ip = start; + boolean isWhile = false; + boolean isForIn = false; + GraphTargetItem inItem = null; + int loopStart = 0; + loopip: + while (ip <= end) { + + long addr = ip2adr(actions, ip, version); + if (ip > end) { + break; + } + if (ip >= actions.size()) { + output.add(new ScriptEndItem()); + break; + } + Action action = actions.get(ip); + if (action.isIgnored()) { + ip++; + continue; + } + if (action instanceof GraphSourceItemContainer) { + GraphSourceItemContainer cnt = (GraphSourceItemContainer) action; + //List out=actionsPartToTree(new HashMap(), new HashMap(),new HashMap(), new Stack(), src, ip+1,endip-1 , version); + long endAddr = action.getAddress() + cnt.getHeaderSize(); + String cntName = cnt.getName(); + List> outs = new ArrayList<>(); + HashMap variables2 = Helper.deepCopy(variables); + if (cnt instanceof ActionDefineFunction || cnt instanceof ActionDefineFunction2) { + for (int r = 0; r < 256; r++) { + if (variables2.containsKey("__register" + r)) { + variables2.remove("__register" + r); + } + } + } + for (long size : cnt.getContainerSizes()) { + if (size == 0) { + outs.add(new ArrayList()); + continue; + } + List out; + try { + out = ActionGraph.translateViaGraph(cnt.getRegNames(), variables2, functions, actions.subList(adr2ip(actions, endAddr, version), adr2ip(actions, endAddr + size, version)), version, staticOperation, path + (cntName == null ? "" : "/" + cntName)); + } catch (OutOfMemoryError | TranslateException | StackOverflowError ex2) { + Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error in: " + path, ex2); + if (ex2 instanceof OutOfMemoryError) { + Helper.freeMem(); + } + out = new ArrayList<>(); + out.add(new CommentItem(new String[]{ + "", + " * " + AppStrings.translate("decompilationError"), + " * " + AppStrings.translate("decompilationError.obfuscated"), + " * " + AppStrings.translate("decompilationError.errorType") + ": " + + ex2.getClass().getSimpleName(), + ""})); + } + outs.add(out); + endAddr += size; + } + ((GraphSourceItemContainer) action).translateContainer(outs, stack, output, registerNames, variables, functions); + ip = adr2ip(actions, endAddr, version); + continue; + } + + //return in for..in + if ((action instanceof ActionPush) && (((ActionPush) action).values.size() == 1) && (((ActionPush) action).values.get(0) instanceof Null)) { + if (ip + 3 <= end) { + if ((actions.get(ip + 1) instanceof ActionEquals) || (actions.get(ip + 1) instanceof ActionEquals2)) { + if (actions.get(ip + 2) instanceof ActionNot) { + if (actions.get(ip + 3) instanceof ActionIf) { + ActionIf aif = (ActionIf) actions.get(ip + 3); + if (adr2ip(actions, ip2adr(actions, ip + 4, version) + aif.getJumpOffset(), version) == ip) { + ip += 4; + continue; + } + } + } + } + } + } + + /*ActionJump && ActionIf removed*/ + /*if ((action instanceof ActionEnumerate2) || (action instanceof ActionEnumerate)) { + loopStart = ip + 1; + isForIn = true; + ip += 4; + action.translate(localData, stack, output); + EnumerateActionItem en = (EnumerateActionItem) stack.peek(); + inItem = en.object; + continue; + } else*/ /*if (action instanceof ActionTry) { + ActionTry atry = (ActionTry) action; + List tryCommands = ActionGraph.translateViaGraph(registerNames, variables, functions, atry.tryBody, version); + ActionItem catchName; + if (atry.catchInRegisterFlag) { + catchName = new DirectValueActionItem(atry, -1, new RegisterNumber(atry.catchRegister), new ArrayList()); + } else { + catchName = new DirectValueActionItem(atry, -1, atry.catchName, new ArrayList()); + } + List catchExceptions = new ArrayList(); + catchExceptions.add(catchName); + List> catchCommands = new ArrayList>(); + catchCommands.add(ActionGraph.translateViaGraph(registerNames, variables, functions, atry.catchBody, version)); + List finallyCommands = ActionGraph.translateViaGraph(registerNames, variables, functions, atry.finallyBody, version); + output.add(new TryActionItem(tryCommands, catchExceptions, catchCommands, finallyCommands)); + } else if (action instanceof ActionWith) { + ActionWith awith = (ActionWith) action; + List withCommands = ActionGraph.translateViaGraph(registerNames, variables, functions,new ArrayList() , version); //TODO:parse with actions + output.add(new WithActionItem(action, stack.pop(), withCommands)); + } else */ if (false) { + } /*if (action instanceof ActionStoreRegister) { + if ((ip + 1 <= end) && (actions.get(ip + 1) instanceof ActionPop)) { + action.translate(localData, stack, output); + stack.pop(); + ip++; + } else { + try { + action.translate(localData, stack, output); + } catch (Exception ex) { + //ignore + } + } + } */ /*else if (action instanceof ActionStrictEquals) { + if ((ip + 1 < actions.size()) && (actions.get(ip + 1) instanceof ActionIf)) { + List caseValues = new ArrayList(); + List> caseCommands = new ArrayList>(); + caseValues.add(stack.pop()); + ActionItem switchedObject = stack.pop(); + if (output.size() > 0) { + if (output.get(output.size() - 1) instanceof StoreRegisterActionItem) { + output.remove(output.size() - 1); + } + } + int caseStart = ip + 2; + List caseBodyIps = new ArrayList(); + long defaultAddr = 0; + caseBodyIps.add(adr2ip(actions, ((ActionIf) actions.get(ip + 1)).getRef(version), version)); + ip++; + do { + ip++; + if ((actions.get(ip - 1) instanceof ActionStrictEquals) && (actions.get(ip) instanceof ActionIf)) { + caseValues.add(actionsToStackTree(registerNames, jumpsOrIfs, actions, constants, caseStart, ip - 2, version).pop()); + caseStart = ip + 1; + caseBodyIps.add(adr2ip(actions, ((ActionIf) actions.get(ip)).getRef(version), version)); + if (actions.get(ip + 1) instanceof ActionJump) { + defaultAddr = ((ActionJump) actions.get(ip + 1)).getRef(version); + ip = adr2ip(actions, defaultAddr, version); + break; + } + } + } while (ip < end); + + for (int i = 0; i < caseBodyIps.size(); i++) { + int caseEnd = ip - 1; + if (i < caseBodyIps.size() - 1) { + caseEnd = caseBodyIps.get(i + 1) - 1; + } + caseCommands.add(actionsToTree(registerNames, unknownJumps, loopList, jumpsOrIfs, stack, constants, actions, caseBodyIps.get(i), caseEnd, version)); + } + output.add(new SwitchActionItem(action, defaultAddr, switchedObject, caseValues, caseCommands, null)); + continue; + } else { + action.translate(stack, constants, output, registerNames); + } + } */ else { + + if (action instanceof ActionStore) { + ActionStore store = (ActionStore) action; + store.setStore(actions.subList(ip + 1, ip + 1 + store.getStoreSize())); + ip = ip + 1 + store.getStoreSize() - 1/*ip++ will be next*/; + } + + try { + action.translate(localData, stack, output, staticOperation, path); + } catch (EmptyStackException ese) { + Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error in: " + path, ese); + output.add(new UnsupportedActionItem(action, "Empty stack")); + } + + } + + ip++; + } + //output = checkClass(output); + log("Leaving " + start + "-" + end); + return output; + } + + public static GraphTargetItem getWithoutGlobal(GraphTargetItem ti) { + GraphTargetItem t = ti; + if (!(t instanceof GetMemberActionItem)) { + return ti; + } + GetMemberActionItem lastMember = null; + while (((GetMemberActionItem) t).object instanceof GetMemberActionItem) { + lastMember = (GetMemberActionItem) t; + t = ((GetMemberActionItem) t).object; + } + if (((GetMemberActionItem) t).object instanceof GetVariableActionItem) { + GetVariableActionItem v = (GetVariableActionItem) ((GetMemberActionItem) t).object; + if (v.name instanceof DirectValueActionItem) { + if (((DirectValueActionItem) v.name).value instanceof String) { + if (((DirectValueActionItem) v.name).value.equals("_global")) { + GetVariableActionItem gvt = new GetVariableActionItem(null, ((GetMemberActionItem) t).memberName); + if (lastMember == null) { + return gvt; + } else { + lastMember.object = gvt; + } + } + } + } + } + return ti; + } + + public static List checkClass(List output) { + if (true) { + //return output; + } + List ret = new ArrayList<>(); + List functions = new ArrayList<>(); + List staticFunctions = new ArrayList<>(); + List> vars = new ArrayList<>(); + List> staticVars = new ArrayList<>(); + GraphTargetItem className; + GraphTargetItem extendsOp = null; + List implementsOp = new ArrayList<>(); + boolean ok = true; + int prevCount = 0; + for (GraphTargetItem t : output) { + if (t instanceof IfItem) { + IfItem it = (IfItem) t; + if (it.expression instanceof NotItem) { + NotItem nti = (NotItem) it.expression; + if ((nti.value instanceof GetMemberActionItem) || (nti.value instanceof GetVariableActionItem)) { + if (true) { //it.onFalse.isEmpty()){ //||(it.onFalse.get(0) instanceof UnsupportedActionItem)) { + if ((it.onTrue.size() == 1) && (it.onTrue.get(0) instanceof SetMemberActionItem) && (((SetMemberActionItem) it.onTrue.get(0)).value instanceof NewObjectActionItem)) { + //ignore + } else { + List parts = it.onTrue; + className = getWithoutGlobal(nti.value); + if (parts.size() >= 1) { + int ipos = 0; + while ((parts.get(ipos) instanceof IfItem) + && ((((IfItem) parts.get(ipos)).onTrue.size() == 1) && (((IfItem) parts.get(ipos)).onTrue.get(0) instanceof SetMemberActionItem) && (((SetMemberActionItem) ((IfItem) parts.get(ipos)).onTrue.get(0)).value instanceof NewObjectActionItem))) { + + ipos++; + } + if (parts.get(ipos) instanceof ExtendsActionItem) { + ExtendsActionItem et = (ExtendsActionItem) parts.get(ipos); + extendsOp = getWithoutGlobal(et.superclass); + ipos++; + } + if (parts.get(ipos) instanceof StoreRegisterActionItem) { + StoreRegisterActionItem sr = (StoreRegisterActionItem) parts.get(ipos); + int instanceReg = sr.register.number; + if (sr.value instanceof GetMemberActionItem) { + GetMemberActionItem gm = (GetMemberActionItem) sr.value; + //gm.memberName should be "prototype" + if (gm.object instanceof TemporaryRegister) { + TemporaryRegister tm = (TemporaryRegister) gm.object; + int classReg = tm.getRegId(); + if (tm.value instanceof SetMemberActionItem) { + SetMemberActionItem sm = (SetMemberActionItem) tm.value; + if (sm.value instanceof StoreRegisterActionItem) { + sr = (StoreRegisterActionItem) sm.value; + if (sr.value instanceof FunctionActionItem) { + ((FunctionActionItem) (sr.value)).calculatedFunctionName = (className instanceof GetMemberActionItem) ? ((GetMemberActionItem) className).memberName : className; + functions.add((FunctionActionItem) sr.value); + + for (; ipos < parts.size(); ipos++) { + if (parts.get(ipos) instanceof ImplementsOpActionItem) { + ImplementsOpActionItem io = (ImplementsOpActionItem) parts.get(ipos); + implementsOp = io.superclasses; + continue; + } + if (parts.get(ipos) instanceof SetMemberActionItem) { + sm = (SetMemberActionItem) parts.get(ipos); + int rnum = -1; + if (sm.object instanceof DirectValueActionItem) { + DirectValueActionItem dv = (DirectValueActionItem) sm.object; + if (dv.value instanceof RegisterNumber) { + RegisterNumber rn = (RegisterNumber) dv.value; + rnum = rn.number; + } + } + if (sm.object instanceof TemporaryRegister) { + rnum = ((TemporaryRegister) sm.object).getRegId(); + } + if (rnum == instanceReg) { + if (sm.value instanceof FunctionActionItem) { + ((FunctionActionItem) sm.value).calculatedFunctionName = sm.objectName; + functions.add((FunctionActionItem) sm.value); + } else { + vars.add(new MyEntry<>(sm.objectName, sm.value)); + } + } else if (rnum == classReg) { + if (sm.value instanceof FunctionActionItem) { + ((FunctionActionItem) sm.value).calculatedFunctionName = sm.objectName; + staticFunctions.add((FunctionActionItem) sm.value); + } else { + staticVars.add(new MyEntry<>(sm.objectName, sm.value)); + } + } + + } + } + + } + + } + } + List output2 = new ArrayList<>(); + for (int i = 0; i < prevCount; i++) { + output2.add(output.get(i)); + } + output2.add(new ClassActionItem(className, extendsOp, implementsOp, null/*FIXME*/, functions, vars, staticFunctions, staticVars)); + return output2; + } + } + } else if (parts.get(ipos) instanceof SetMemberActionItem) { + SetMemberActionItem sm = (SetMemberActionItem) parts.get(0); + if (sm.value instanceof FunctionActionItem) { + FunctionActionItem f = (FunctionActionItem) sm.value; + if (f.actions.isEmpty()) { + if (parts.size() == 2) { + if (parts.get(1) instanceof ImplementsOpActionItem) { + ImplementsOpActionItem iot = (ImplementsOpActionItem) parts.get(1); + implementsOp = iot.superclasses; + } else { + ok = false; + break; + } + } + List output2 = new ArrayList<>(); + for (int i = 0; i < prevCount; i++) { + output2.add(output.get(i)); + } + output2.add(new InterfaceActionItem(sm.objectName, implementsOp)); + return output2; + } + } + } + } + } + } else { + ok = false; + } + } else { + ok = false; + } + } else { + ok = false; + } + } else { + prevCount++; + //ok = false; + } + if (!ok) { + break; + } + } + return output; + } + + @Override + public boolean ignoredLoops() { + return false; + } + + public static List removeNops(long address, List actions, int version, String path) { + List ret = actions; + if (true) { + //return ret; + } + String s = null; + try { + HilightedTextWriter writer = new HilightedTextWriter(Configuration.getCodeFormatting(), false); + Action.actionsToString(new ArrayList(), address, ret, null, version, ScriptExportMode.PCODE, writer, path); + s = writer.toString(); + ret = ASMParser.parse(address, true, s, SWF.DEFAULT_VERSION, false); + } catch (IOException | ParseException ex) { + Logger.getLogger(SWFInputStream.class.getName()).log(Level.SEVERE, "parsing error. path: " + path, ex); + } + return ret; + } + + public static void setConstantPool(List actions, ConstantPool cpool) { + for (GraphSourceItem a : actions) { + if (a instanceof ActionPush) { + if (cpool != null) { + ((ActionPush) a).constantPool = cpool.constants; + } + } + if (a instanceof ActionDefineFunction) { + if (cpool != null) { + //((ActionDefineFunction) a).setConstantPool(cpool.constants,actions); + } + } + if (a instanceof ActionDefineFunction2) { + if (cpool != null) { + //((ActionDefineFunction2) a).setConstantPool(cpool.constants,actions); + } + } + } + } + + public GraphTextWriter getASMSourceReplaced(List container, List knownAddreses, List constantPool, int version, ScriptExportMode exportMode, GraphTextWriter writer) { + writer.appendNoHilight(getASMSource(container, knownAddreses, constantPool, version, exportMode)); + return writer; + } + + public static double toFloatPoint(Object o) { + if (o instanceof Double) { + return (Double) o; + } + if (o instanceof Integer) { + return (Integer) o; + } + if (o instanceof Long) { + return (Long) o; + } + if (o instanceof String) { + try { + return Double.parseDouble((String) o); + } catch (NumberFormatException nfe) { + return 0; + } + } + return 0; + } + + public static GraphTargetItem gettoset(GraphTargetItem get, GraphTargetItem value, List variables) { + GraphTargetItem ret = get; + boolean boxed = false; + if (get instanceof VariableActionItem) { + boxed = true; + ret = ((VariableActionItem) ret).getBoxedValue(); + } + if (ret instanceof GetVariableActionItem) { + GetVariableActionItem gv = (GetVariableActionItem) ret; + ret = new SetVariableActionItem(null, gv.name, value); + } else if (ret instanceof GetMemberActionItem) { + GetMemberActionItem mem = (GetMemberActionItem) ret; + ret = new SetMemberActionItem(null, mem.object, mem.memberName, value); + } else if ((ret instanceof DirectValueActionItem) && ((DirectValueActionItem) ret).value instanceof RegisterNumber) { + ret = new StoreRegisterActionItem(null, (RegisterNumber) ((DirectValueActionItem) ret).value, value, false); + } else if (ret instanceof GetPropertyActionItem) { + GetPropertyActionItem gp = (GetPropertyActionItem) ret; + ret = new SetPropertyActionItem(null, gp.target, gp.propertyIndex, value); + } + if (boxed) { + GraphTargetItem b = ret; + ret = new VariableActionItem(((VariableActionItem) get).getVariableName(), value, ((VariableActionItem) get).isDefinition()); + ((VariableActionItem) ret).setBoxedValue((ActionItem) b); + variables.remove((VariableActionItem) get); + variables.add((VariableActionItem) ret); + } + return ret; + } + + @Override + public boolean isDeobfuscatePop() { + return false; + } +} diff --git a/src/com/jpexs/decompiler/flash/action/swf4/ActionJump.java b/src/com/jpexs/decompiler/flash/action/swf4/ActionJump.java index 3a27a927a..92a378796 100644 --- a/src/com/jpexs/decompiler/flash/action/swf4/ActionJump.java +++ b/src/com/jpexs/decompiler/flash/action/swf4/ActionJump.java @@ -1,124 +1,124 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.action.swf4; - -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.ActionGraphSource; -import com.jpexs.decompiler.flash.action.parser.ParseException; -import com.jpexs.decompiler.flash.action.parser.pcode.FlasmLexer; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.graph.GraphSource; -import com.jpexs.decompiler.graph.GraphSourceItem; -import com.jpexs.helpers.Helper; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class ActionJump extends Action { - - private int offset; - public String identifier; - public boolean isContinue = false; - public boolean isBreak = false; - - public int getJumpOffset() { - return offset; - } - - public final void setJumpOffset(int offset) { - this.offset = offset; - } - - public ActionJump(int offset) { - super(0x99, 2); - setJumpOffset(offset); - } - - public ActionJump(int actionLength, SWFInputStream sis) throws IOException { - super(0x99, actionLength); - setJumpOffset(sis.readSI16("offset")); - } - - @Override - public List getAllRefs(int version) { - List ret = new ArrayList<>(); - ret.add(getRef(version)); - return ret; - } - - public long getRef(int version) { - return getAddress() + getBytes(version).length + offset; - } - - @Override - public byte[] getBytes(int version) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(baos, version); - try { - sos.writeSI16(offset); - sos.close(); - } catch (IOException e) { - } - return surroundWithAction(baos.toByteArray(), version); - } - - @Override - public String getASMSource(List container, List knownAddreses, List constantPool, int version, ScriptExportMode exportMode) { - String ofsStr = Helper.formatAddress(getAddress() + getBytes(version).length + offset); - return "Jump loc" + ofsStr; - } - - public ActionJump(FlasmLexer lexer) throws IOException, ParseException { - super(0x99, 0); - identifier = lexIdentifier(lexer); - } - - @Override - public List getAllIfsOrJumps() { - List ret = new ArrayList<>(); - ret.add(this); - return ret; - } - - @Override - public String toString() { - return "Jump " + offset; - } - - @Override - public boolean isJump() { - return true; - } - - @Override - public List getBranches(GraphSource code) { - List ret = super.getBranches(code); - int ofs = code.adr2pos(getAddress() + getBytes(((ActionGraphSource) code).version).length + offset); - if (ofs == -1) { - ofs = code.adr2pos(getAddress() + getBytes(((ActionGraphSource) code).version).length); - new Exception().printStackTrace(); - Logger.getLogger(ActionJump.class.getName()).log(Level.SEVERE, "Invalid jump to ofs" + Helper.formatAddress(getAddress() + getBytes(((ActionGraphSource) code).version).length + offset) + " from ofs" + Helper.formatAddress(getAddress())); - } - ret.add(ofs); - return ret; - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.action.swf4; + +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.ActionGraphSource; +import com.jpexs.decompiler.flash.action.parser.ParseException; +import com.jpexs.decompiler.flash.action.parser.pcode.FlasmLexer; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.graph.GraphSource; +import com.jpexs.decompiler.graph.GraphSourceItem; +import com.jpexs.helpers.Helper; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ActionJump extends Action { + + private int offset; + public String identifier; + public boolean isContinue = false; + public boolean isBreak = false; + + public int getJumpOffset() { + return offset; + } + + public final void setJumpOffset(int offset) { + this.offset = offset; + } + + public ActionJump(int offset) { + super(0x99, 2); + setJumpOffset(offset); + } + + public ActionJump(int actionLength, SWFInputStream sis) throws IOException { + super(0x99, actionLength); + setJumpOffset(sis.readSI16("offset")); + } + + @Override + public List getAllRefs(int version) { + List ret = new ArrayList<>(); + ret.add(getRef(version)); + return ret; + } + + public long getRef(int version) { + return getAddress() + getBytes(version).length + offset; + } + + @Override + public byte[] getBytes(int version) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, version); + try { + sos.writeSI16(offset); + sos.close(); + } catch (IOException e) { + } + return surroundWithAction(baos.toByteArray(), version); + } + + @Override + public String getASMSource(List container, List knownAddreses, List constantPool, int version, ScriptExportMode exportMode) { + String ofsStr = Helper.formatAddress(getAddress() + getBytes(version).length + offset); + return "Jump loc" + ofsStr; + } + + public ActionJump(FlasmLexer lexer) throws IOException, ParseException { + super(0x99, 0); + identifier = lexIdentifier(lexer); + } + + @Override + public List getAllIfsOrJumps() { + List ret = new ArrayList<>(); + ret.add(this); + return ret; + } + + @Override + public String toString() { + return "Jump " + offset; + } + + @Override + public boolean isJump() { + return true; + } + + @Override + public List getBranches(GraphSource code) { + List ret = super.getBranches(code); + int ofs = code.adr2pos(getAddress() + getBytes(((ActionGraphSource) code).version).length + offset); + if (ofs == -1) { + ofs = code.adr2pos(getAddress() + getBytes(((ActionGraphSource) code).version).length); + new Exception().printStackTrace(); + Logger.getLogger(ActionJump.class.getName()).log(Level.SEVERE, "Invalid jump to ofs" + Helper.formatAddress(getAddress() + getBytes(((ActionGraphSource) code).version).length + offset) + " from ofs" + Helper.formatAddress(getAddress())); + } + ret.add(ofs); + return ret; + } +} diff --git a/src/com/jpexs/decompiler/flash/exporters/shape/CanvasShapeExporter.java b/src/com/jpexs/decompiler/flash/exporters/shape/CanvasShapeExporter.java index bad390108..ebe866279 100644 --- a/src/com/jpexs/decompiler/flash/exporters/shape/CanvasShapeExporter.java +++ b/src/com/jpexs/decompiler/flash/exporters/shape/CanvasShapeExporter.java @@ -1,513 +1,513 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.exporters.shape; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.Point; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.FILLSTYLE; -import com.jpexs.decompiler.flash.types.GRADIENT; -import com.jpexs.decompiler.flash.types.GRADRECORD; -import com.jpexs.decompiler.flash.types.LINESTYLE2; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.RGB; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.SHAPE; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.SerializableImage; -import java.awt.Color; - -/** - * - * @author JPEXS - */ -public class CanvasShapeExporter extends ShapeExporterBase { - - protected static final String DRAW_COMMAND_M = "M"; - protected static final String DRAW_COMMAND_L = "L"; - protected static final String DRAW_COMMAND_Q = "Q"; - protected String currentDrawCommand = ""; - - protected String pathData = ""; - protected String shapeData = ""; - protected String html = ""; - protected String strokeData = ""; - protected String fillData = ""; - protected double deltaX = 0; - protected double deltaY = 0; - protected Matrix fillMatrix = null; - protected String lastRadColor = null; - protected SWF swf; - protected int repeatCnt = 0; - protected double unitDivisor; - protected RGB basicFill; - protected String lineFillData = null; - protected String lineLastRadColor = null; - protected Matrix lineFillMatrix = null; - protected int lineRepeatCnt = 0; - protected int fillWidth = 0; - protected int fillHeight = 0; - - public static String getJsPrefix() { - return "" - + "" - + "" - + "\r\n" - + "\r\n" - + "
\r\n" - + "Your browser does not support the HTML5 canvas tag.\r\n" - + "
 
 
" - //+ "
 
" - + "
\r\n" - + "\r\n"; - - } - - public static String getJsSuffix() { - return "\r\n"; - } - - public static String getHtmlSuffix() { - return "\r\n" - + ""; - } - - public static String getDrawJs(int width, int height, String data) { - return "var originalWidth=" + width + ";\r\nvar originalHeight=" + height + ";\r\n function drawFrame(){\r\n" - + "\tctx.save();\r\n\tctx.transform(canvas.width/originalWidth,0,0,canvas.height/originalHeight,0,0);\r\n" + data + "\tctx.restore();\r\n}\r\n\tdrawFrame();\r\n"; - } - - public String getHtml(String needed) { - RECT r = shape.getBounds(); - int width = (int) (r.getWidth() / unitDivisor); - int height = (int) (r.getHeight() / unitDivisor); - - return getHtmlPrefix(width, height) + getJsPrefix() + needed + getDrawJs(width, height, shapeData) + getJsSuffix() + getHtmlSuffix(); - } - - public String getShapeData() { - return shapeData; - } - - public CanvasShapeExporter(RGB basicFill, double unitDivisor, SWF swf, SHAPE shape, ColorTransform colorTransform, int deltaX, int deltaY) { - super(shape, colorTransform); - this.swf = swf; - this.unitDivisor = unitDivisor; - this.basicFill = basicFill; - this.deltaX = deltaX; - this.deltaY = deltaY; - } - - @Override - public void beginShape() { - shapeData = ""; - } - - @Override - public void endShape() { - } - - @Override - public void beginFills() { - } - - @Override - public void endFills() { - } - - @Override - public void beginLines() { - } - - @Override - public void endLines() { - finalizePath(); - } - - @Override - public void beginFill(RGB color) { - finalizePath(); - if (color == null) { - fillData += "\tctx.fillStyle=defaultFill;\r\n"; - } else { - fillData += "\tctx.fillStyle=" + color(color) + ";\r\n"; - } - } - - @Override - public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { - finalizePath(); - - //TODO: How many repeats is ideal? - final int REPEAT_CNT = 5; - - repeatCnt = spreadMethod == GRADIENT.SPREAD_PAD_MODE ? 0 : REPEAT_CNT; - - if (type == FILLSTYLE.LINEAR_GRADIENT) { - Point start = matrix.transform(new Point(-16384 - 32768 * repeatCnt, 0)); - Point end = matrix.transform(new Point(16384 + 32768 * repeatCnt, 0)); - start.x += deltaX; - start.y += deltaY; - end.x += deltaX; - end.y += deltaY; - fillData += "\tvar grd=ctx.createLinearGradient(" + Double.toString(start.x / unitDivisor) + "," + Double.toString(start.y / unitDivisor) + "," + Double.toString(end.x / unitDivisor) + "," + Double.toString(end.y / unitDivisor) + ");\r\n"; - } else { - matrix.translateX /= unitDivisor; - matrix.translateY /= unitDivisor; - matrix.scaleX /= unitDivisor; - matrix.scaleY /= unitDivisor; - matrix.rotateSkew0 /= unitDivisor; - matrix.rotateSkew1 /= unitDivisor; - fillMatrix = matrix; - - matrix.translateX += deltaX / unitDivisor; - matrix.translateY += deltaY / unitDivisor; - - fillData += "\tvar grd=ctx.createRadialGradient(" + focalPointRatio * 16384 + ",0,0,0,0," + (16384 + 32768 * repeatCnt) + ");\r\n"; - } - int repeatTotal = repeatCnt * 2 + 1; - double oneHeight = 1.0 / repeatTotal; - double pos = 0; - boolean revert = false; - if (type != FILLSTYLE.LINEAR_GRADIENT && spreadMethod == GRADIENT.SPREAD_REFLECT_MODE) { - revert = true; - } - for (int i = 0; i < repeatTotal; i++) { - if (spreadMethod == GRADIENT.SPREAD_REFLECT_MODE) { - revert = !revert; - } - for (GRADRECORD r : gradientRecords) { - fillData += "\tgrd.addColorStop(" + Double.toString(pos + (oneHeight * (revert ? 255 - r.ratio : r.ratio) / 255.0)) + "," + color(r.color) + ");\r\n"; - lastRadColor = color(r.color); - } - pos += oneHeight; - } - fillData += "\tctx.fillStyle = grd;\r\n"; - } - - public static String color(Color color) { - return color(new RGBA(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha())); - } - - public static String color(RGB rgb) { - if ((rgb instanceof RGBA) && (((RGBA) rgb).alpha < 255)) { - RGBA rgba = (RGBA) rgb; - return "tocolor(ctrans.apply([" + rgba.red + "," + rgba.green + "," + rgba.blue + "," + rgba.getAlphaFloat() + "]))"; - } else { - return "tocolor(ctrans.apply([" + rgb.red + "," + rgb.green + "," + rgb.blue + ",1]))"; - } - - } - - @Override - public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) { - finalizePath(); - ImageTag image = null; - for (Tag t : swf.tags) { - if (t instanceof ImageTag) { - ImageTag i = (ImageTag) t; - if (i.getCharacterId() == bitmapId) { - image = i; - SerializableImage im = i.getImage(); - fillWidth = im.getWidth(); - fillHeight = im.getHeight(); - break; - } - } - } - if (image != null) { - SerializableImage img = image.getImage(); - if (img != null) { - colorTransform.apply(img); - if (matrix != null) { - matrix.translateX /= unitDivisor; - matrix.translateY /= unitDivisor; - matrix.scaleX /= unitDivisor; - matrix.scaleY /= unitDivisor; - matrix.rotateSkew0 /= unitDivisor; - matrix.rotateSkew1 /= unitDivisor; - matrix.translateX += deltaX / unitDivisor; - matrix.translateY += deltaY / unitDivisor; - fillMatrix = matrix; - } - - fillData += "\tvar fimg = ctrans.applyToImage(image" + bitmapId + ");\r\n"; - fillData += "\tvar pat=ctx.createPattern(fimg,\"repeat\");\r\n"; - fillData += "\tctx.fillStyle = pat;\r\n"; - } - } - } - - @Override - public void endFill() { - finalizePath(); - } - - @Override - public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, int miterLimit) { - finalizePath(); - thickness /= unitDivisor; - - if (color != null) { //gradient lines have no color - strokeData += "\tctx.strokeStyle=" + color(color) + ";\r\n"; - } - strokeData += "\tctx.lineWidth=" + Double.toString(thickness == 0 ? 1 : thickness) + ";\r\n"; - switch (startCaps) { - case LINESTYLE2.NO_CAP: - strokeData += "\tctx.lineCap=\"butt\";\r\n"; - break; - case LINESTYLE2.SQUARE_CAP: - strokeData += "\tctx.lineCap=\"square\";\r\n"; - break; - default: - strokeData += "\tctx.lineCap=\"round\";\r\n"; - break; - } - switch (joints) { - case LINESTYLE2.BEVEL_JOIN: - strokeData += "\tctx.lineJoin=\"bevel\";\r\n"; - break; - case LINESTYLE2.ROUND_JOIN: - strokeData += "\tctx.lineJoin=\"round\";\r\n"; - break; - default: - strokeData += "\tctx.lineJoin=\"miter\";\r\n"; - strokeData += "\tctx.miterLimit=" + Integer.toString(miterLimit) + ";\r\n"; - break; - } - } - - @Override - public void lineGradientStyle(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { - lineFillData = ""; - - //TODO: How many repeats is ideal? - final int REPEAT_CNT = 5; - - lineRepeatCnt = spreadMethod == GRADIENT.SPREAD_PAD_MODE ? 0 : REPEAT_CNT; - - if (type == FILLSTYLE.LINEAR_GRADIENT) { - Point start = matrix.transform(new Point(-16384 - 32768 * lineRepeatCnt, 0)); - Point end = matrix.transform(new Point(16384 + 32768 * lineRepeatCnt, 0)); - start.x += deltaX; - start.y += deltaY; - end.x += deltaX; - end.y += deltaY; - lineFillData += "\tvar grd=ctx.createLinearGradient(" + Double.toString(start.x / unitDivisor) + "," + Double.toString(start.y / unitDivisor) + "," + Double.toString(end.x / unitDivisor) + "," + Double.toString(end.y / unitDivisor) + ");\r\n"; - } else { - matrix.translateX /= unitDivisor; - matrix.translateY /= unitDivisor; - matrix.scaleX /= unitDivisor; - matrix.scaleY /= unitDivisor; - matrix.rotateSkew0 /= unitDivisor; - matrix.rotateSkew1 /= unitDivisor; - lineFillMatrix = matrix; - - matrix.translateX += deltaX / unitDivisor; - matrix.translateY += deltaY / unitDivisor; - - lineFillData += "\tvar grd=ctx.createRadialGradient(" + focalPointRatio * 16384 + ",0,0,0,0," + (16384 + 32768 * lineRepeatCnt) + ");\r\n"; - } - int repeatTotal = lineRepeatCnt * 2 + 1; - double oneHeight = 1.0 / repeatTotal; - double pos = 0; - boolean revert = false; - if (type != FILLSTYLE.LINEAR_GRADIENT && spreadMethod == GRADIENT.SPREAD_REFLECT_MODE) { - revert = true; - } - for (int i = 0; i < repeatTotal; i++) { - if (spreadMethod == GRADIENT.SPREAD_REFLECT_MODE) { - revert = !revert; - } - for (GRADRECORD r : gradientRecords) { - lineFillData += "\tgrd.addColorStop(" + Double.toString(pos + (oneHeight * (revert ? 255 - r.ratio : r.ratio) / 255.0)) + "," + color(r.color) + ");\r\n"; - lineLastRadColor = color(r.color); - } - pos += oneHeight; - } - lineFillData += "\tctx.fillStyle = grd;\r\n"; - - String preStrokeData = ""; - - preStrokeData += "\tvar lcanvas = document.createElement(\"canvas\");\r\n"; - preStrokeData += "\tlcanvas.width = canvas.width;\r\n"; - preStrokeData += "\tlcanvas.height=canvas.height;\r\n"; - preStrokeData += "\tvar lctx = lcanvas.getContext(\"2d\");\r\n"; - preStrokeData += "\tenhanceContext(lctx);\r\n"; - preStrokeData += "\tlctx.applyTransforms(ctx._matrices);\r\n"; - preStrokeData += "\tctx = lctx;\r\n"; - strokeData = preStrokeData + strokeData; - } - - @Override - public void moveTo(double x, double y) { - currentDrawCommand = DRAW_COMMAND_M; - pathData += currentDrawCommand + " "; - x += deltaX; - y += deltaY; - pathData += Helper.doubleStr(x / unitDivisor) + " " - + Helper.doubleStr(y / unitDivisor) + " "; - } - - @Override - public void lineTo(double x, double y) { - if (!currentDrawCommand.equals(DRAW_COMMAND_L)) { - currentDrawCommand = DRAW_COMMAND_L; - pathData += currentDrawCommand + " "; - } - x += deltaX; - y += deltaY; - pathData += Helper.doubleStr(x / unitDivisor) + " " - + Helper.doubleStr(y / unitDivisor) + " "; - } - - @Override - public void curveTo(double controlX, double controlY, double anchorX, double anchorY) { - if (!currentDrawCommand.equals(DRAW_COMMAND_Q)) { - currentDrawCommand = DRAW_COMMAND_Q; - pathData += currentDrawCommand + " "; - } - controlX += deltaX; - anchorX += deltaX; - controlY += deltaY; - anchorY += deltaY; - pathData += Helper.doubleStr(controlX / unitDivisor) + " " - + Helper.doubleStr(controlY / unitDivisor) + " " - + Helper.doubleStr(anchorX / unitDivisor) + " " - + Helper.doubleStr(anchorY / unitDivisor) + " "; - } - - protected void finalizePath() { - if (!"".equals(pathData)) { - pathData = "\tdrawPath(ctx,\"" + pathData + "\");\r\n"; - - if (lineFillData != null) { - String preLineFillData = ""; - preLineFillData += "\tvar oldctx = ctx;\r\n"; - preLineFillData += "\tctx.save();\r\n"; - preLineFillData += strokeData; - preLineFillData += pathData; - preLineFillData += "\tctx.stroke();\r\n"; - preLineFillData += "\tvar lfcanvas = document.createElement(\"canvas\");\r\n"; - preLineFillData += "\tlfcanvas.width = canvas.width;\r\n"; - preLineFillData += "\tlfcanvas.height=canvas.height;\r\n"; - preLineFillData += "\tvar lfctx = lfcanvas.getContext(\"2d\");\r\n"; - preLineFillData += "\tenhanceContext(lfctx);\r\n"; - preLineFillData += "\tlfctx.applyTransforms(ctx._matrices);\r\n"; - preLineFillData += "\tctx = lfctx;"; - if (lineLastRadColor != null) { - preLineFillData += "\tctx.fillStyle=" + lineLastRadColor + ";\r\n\tctx.fill(\"evenodd\");\r\n"; - } - - preLineFillData += "\tctx.transform(" + Helper.doubleStr(lineFillMatrix.scaleX) + "," + Helper.doubleStr(lineFillMatrix.rotateSkew0) + "," + Helper.doubleStr(lineFillMatrix.rotateSkew1) + "," + Helper.doubleStr(lineFillMatrix.scaleY) + "," + Helper.doubleStr(lineFillMatrix.translateX) + "," + Helper.doubleStr(lineFillMatrix.translateY) + ");\r\n"; - lineFillData = preLineFillData + lineFillData; - lineFillData += "\tctx.fillRect(" + (-16384 - 32768 * lineRepeatCnt) + "," + (-16384 - 32768 * lineRepeatCnt) + "," + (2 * 16384 + 32768 * 2 * lineRepeatCnt) + "," + (2 * 16384 + 32768 * 2 * lineRepeatCnt) + ");\r\n"; - - lineFillData += "\tctx = oldctx;\r\n"; - - //lcanvas - stroke - //lfcanvas - stroke background - lineFillData += "\tvar limgd = lctx.getImageData(0, 0, lcanvas.width, lcanvas.height);\r\n" - + "\tvar lpix = limgd.data;\r\n" - + "\tvar lfimgd = lfctx.getImageData(0, 0, lfcanvas.width, lfcanvas.height);\r\n" - + "\tvar lfpix = lfimgd.data;\r\n" - + "\tvar imgd = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n" - + "\tvar pix = imgd.data;\r\n" - + "\tfor (var i = 0; i < lpix.length; i += 4) {\r\n" - + "\t\tif(lpix[i+3]>0){ pix[i] = lfpix[i]; pix[i+1] = lfpix[i+1]; pix[i+2] = lfpix[i+2]; pix[i+3] = lfpix[i+3];}\r\n" - + "\t}\r\n" - + "\tctx.putImageData(imgd, 0, 0);\r\n"; - lineFillData += "\tctx.restore();\r\n"; - strokeData = ""; - } else { - pathData += strokeData; - } - if (fillMatrix != null) { - if (lastRadColor != null) { - pathData += "\tctx.fillStyle=" + lastRadColor + ";\r\n\tctx.fill(\"evenodd\");\r\n"; - } - pathData += "\tctx.save();\r\n"; - pathData += "\tctx.clip();\r\n"; - pathData += "\tctx.transform(" + Helper.doubleStr(fillMatrix.scaleX) + "," + Helper.doubleStr(fillMatrix.rotateSkew0) + "," + Helper.doubleStr(fillMatrix.rotateSkew1) + "," + Helper.doubleStr(fillMatrix.scaleY) + "," + Helper.doubleStr(fillMatrix.translateX) + "," + Helper.doubleStr(fillMatrix.translateY) + ");\r\n"; - if (fillWidth > 0) {//repeating bitmap glitch fix - //make bitmap 1px wider - double s_w = (fillWidth + 1) / (double) fillWidth; - double s_h = (fillHeight + 1) / (double) fillHeight; - - pathData += "\tctx.transform(" + (s_w) + ",0,0," + s_h + ",-0.5,-0.5);\r\n"; - } - pathData += fillData; - pathData += "\tctx.fillRect(" + (-16384 - 32768 * repeatCnt) + "," + (-16384 - 32768 * repeatCnt) + "," + (2 * 16384 + 32768 * 2 * repeatCnt) + "," + (2 * 16384 + 32768 * 2 * repeatCnt) + ");\r\n"; - pathData += "\tctx.restore();\r\n"; - shapeData += pathData; - } else { - if (!"".equals(fillData)) { - pathData += "\tctx.fill(\"evenodd\");\r\n"; - } - shapeData += fillData + pathData; - } - if (!"".equals(strokeData)) { - shapeData += "\tctx.stroke();\r\n"; - } else if (lineFillData != null) { - shapeData += lineFillData; - } - } - - repeatCnt = 0; - - pathData = ""; - fillData = ""; - strokeData = ""; - fillMatrix = null; - lastRadColor = null; - - lineRepeatCnt = 0; - lineFillData = null; - lineLastRadColor = null; - lineFillMatrix = null; - - fillWidth = 0; - fillHeight = 0; - } - -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.exporters.shape; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.Point; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.FILLSTYLE; +import com.jpexs.decompiler.flash.types.GRADIENT; +import com.jpexs.decompiler.flash.types.GRADRECORD; +import com.jpexs.decompiler.flash.types.LINESTYLE2; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.SerializableImage; +import java.awt.Color; + +/** + * + * @author JPEXS + */ +public class CanvasShapeExporter extends ShapeExporterBase { + + protected static final String DRAW_COMMAND_M = "M"; + protected static final String DRAW_COMMAND_L = "L"; + protected static final String DRAW_COMMAND_Q = "Q"; + protected String currentDrawCommand = ""; + + protected String pathData = ""; + protected String shapeData = ""; + protected String html = ""; + protected String strokeData = ""; + protected String fillData = ""; + protected double deltaX = 0; + protected double deltaY = 0; + protected Matrix fillMatrix = null; + protected String lastRadColor = null; + protected SWF swf; + protected int repeatCnt = 0; + protected double unitDivisor; + protected RGB basicFill; + protected String lineFillData = null; + protected String lineLastRadColor = null; + protected Matrix lineFillMatrix = null; + protected int lineRepeatCnt = 0; + protected int fillWidth = 0; + protected int fillHeight = 0; + + public static String getJsPrefix() { + return "" + + "" + + "" + + "\r\n" + + "\r\n" + + "
\r\n" + + "Your browser does not support the HTML5 canvas tag.\r\n" + + "
 
 
" + //+ "
 
" + + "
\r\n" + + "\r\n"; + + } + + public static String getJsSuffix() { + return "\r\n"; + } + + public static String getHtmlSuffix() { + return "\r\n" + + ""; + } + + public static String getDrawJs(int width, int height, String data) { + return "var originalWidth=" + width + ";\r\nvar originalHeight=" + height + ";\r\n function drawFrame(){\r\n" + + "\tctx.save();\r\n\tctx.transform(canvas.width/originalWidth,0,0,canvas.height/originalHeight,0,0);\r\n" + data + "\tctx.restore();\r\n}\r\n\tdrawFrame();\r\n"; + } + + public String getHtml(String needed) { + RECT r = shape.getBounds(); + int width = (int) (r.getWidth() / unitDivisor); + int height = (int) (r.getHeight() / unitDivisor); + + return getHtmlPrefix(width, height) + getJsPrefix() + needed + getDrawJs(width, height, shapeData) + getJsSuffix() + getHtmlSuffix(); + } + + public String getShapeData() { + return shapeData; + } + + public CanvasShapeExporter(RGB basicFill, double unitDivisor, SWF swf, SHAPE shape, ColorTransform colorTransform, int deltaX, int deltaY) { + super(shape, colorTransform); + this.swf = swf; + this.unitDivisor = unitDivisor; + this.basicFill = basicFill; + this.deltaX = deltaX; + this.deltaY = deltaY; + } + + @Override + public void beginShape() { + shapeData = ""; + } + + @Override + public void endShape() { + } + + @Override + public void beginFills() { + } + + @Override + public void endFills() { + } + + @Override + public void beginLines() { + } + + @Override + public void endLines() { + finalizePath(); + } + + @Override + public void beginFill(RGB color) { + finalizePath(); + if (color == null) { + fillData += "\tctx.fillStyle=defaultFill;\r\n"; + } else { + fillData += "\tctx.fillStyle=" + color(color) + ";\r\n"; + } + } + + @Override + public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + finalizePath(); + + //TODO: How many repeats is ideal? + final int REPEAT_CNT = 5; + + repeatCnt = spreadMethod == GRADIENT.SPREAD_PAD_MODE ? 0 : REPEAT_CNT; + + if (type == FILLSTYLE.LINEAR_GRADIENT) { + Point start = matrix.transform(new Point(-16384 - 32768 * repeatCnt, 0)); + Point end = matrix.transform(new Point(16384 + 32768 * repeatCnt, 0)); + start.x += deltaX; + start.y += deltaY; + end.x += deltaX; + end.y += deltaY; + fillData += "\tvar grd=ctx.createLinearGradient(" + Double.toString(start.x / unitDivisor) + "," + Double.toString(start.y / unitDivisor) + "," + Double.toString(end.x / unitDivisor) + "," + Double.toString(end.y / unitDivisor) + ");\r\n"; + } else { + matrix.translateX /= unitDivisor; + matrix.translateY /= unitDivisor; + matrix.scaleX /= unitDivisor; + matrix.scaleY /= unitDivisor; + matrix.rotateSkew0 /= unitDivisor; + matrix.rotateSkew1 /= unitDivisor; + fillMatrix = matrix; + + matrix.translateX += deltaX / unitDivisor; + matrix.translateY += deltaY / unitDivisor; + + fillData += "\tvar grd=ctx.createRadialGradient(" + focalPointRatio * 16384 + ",0,0,0,0," + (16384 + 32768 * repeatCnt) + ");\r\n"; + } + int repeatTotal = repeatCnt * 2 + 1; + double oneHeight = 1.0 / repeatTotal; + double pos = 0; + boolean revert = false; + if (type != FILLSTYLE.LINEAR_GRADIENT && spreadMethod == GRADIENT.SPREAD_REFLECT_MODE) { + revert = true; + } + for (int i = 0; i < repeatTotal; i++) { + if (spreadMethod == GRADIENT.SPREAD_REFLECT_MODE) { + revert = !revert; + } + for (GRADRECORD r : gradientRecords) { + fillData += "\tgrd.addColorStop(" + Double.toString(pos + (oneHeight * (revert ? 255 - r.ratio : r.ratio) / 255.0)) + "," + color(r.color) + ");\r\n"; + lastRadColor = color(r.color); + } + pos += oneHeight; + } + fillData += "\tctx.fillStyle = grd;\r\n"; + } + + public static String color(Color color) { + return color(new RGBA(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha())); + } + + public static String color(RGB rgb) { + if ((rgb instanceof RGBA) && (((RGBA) rgb).alpha < 255)) { + RGBA rgba = (RGBA) rgb; + return "tocolor(ctrans.apply([" + rgba.red + "," + rgba.green + "," + rgba.blue + "," + rgba.getAlphaFloat() + "]))"; + } else { + return "tocolor(ctrans.apply([" + rgb.red + "," + rgb.green + "," + rgb.blue + ",1]))"; + } + + } + + @Override + public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) { + finalizePath(); + ImageTag image = null; + for (Tag t : swf.tags) { + if (t instanceof ImageTag) { + ImageTag i = (ImageTag) t; + if (i.getCharacterId() == bitmapId) { + image = i; + SerializableImage im = i.getImage(); + fillWidth = im.getWidth(); + fillHeight = im.getHeight(); + break; + } + } + } + if (image != null) { + SerializableImage img = image.getImage(); + if (img != null) { + colorTransform.apply(img); + if (matrix != null) { + matrix.translateX /= unitDivisor; + matrix.translateY /= unitDivisor; + matrix.scaleX /= unitDivisor; + matrix.scaleY /= unitDivisor; + matrix.rotateSkew0 /= unitDivisor; + matrix.rotateSkew1 /= unitDivisor; + matrix.translateX += deltaX / unitDivisor; + matrix.translateY += deltaY / unitDivisor; + fillMatrix = matrix; + } + + fillData += "\tvar fimg = ctrans.applyToImage(imageObj" + bitmapId + ");\r\n"; + fillData += "\tvar pat=ctx.createPattern(fimg,\"repeat\");\r\n"; + fillData += "\tctx.fillStyle = pat;\r\n"; + } + } + } + + @Override + public void endFill() { + finalizePath(); + } + + @Override + public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, int miterLimit) { + finalizePath(); + thickness /= unitDivisor; + + if (color != null) { //gradient lines have no color + strokeData += "\tctx.strokeStyle=" + color(color) + ";\r\n"; + } + strokeData += "\tctx.lineWidth=" + Double.toString(thickness == 0 ? 1 : thickness) + ";\r\n"; + switch (startCaps) { + case LINESTYLE2.NO_CAP: + strokeData += "\tctx.lineCap=\"butt\";\r\n"; + break; + case LINESTYLE2.SQUARE_CAP: + strokeData += "\tctx.lineCap=\"square\";\r\n"; + break; + default: + strokeData += "\tctx.lineCap=\"round\";\r\n"; + break; + } + switch (joints) { + case LINESTYLE2.BEVEL_JOIN: + strokeData += "\tctx.lineJoin=\"bevel\";\r\n"; + break; + case LINESTYLE2.ROUND_JOIN: + strokeData += "\tctx.lineJoin=\"round\";\r\n"; + break; + default: + strokeData += "\tctx.lineJoin=\"miter\";\r\n"; + strokeData += "\tctx.miterLimit=" + Integer.toString(miterLimit) + ";\r\n"; + break; + } + } + + @Override + public void lineGradientStyle(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + lineFillData = ""; + + //TODO: How many repeats is ideal? + final int REPEAT_CNT = 5; + + lineRepeatCnt = spreadMethod == GRADIENT.SPREAD_PAD_MODE ? 0 : REPEAT_CNT; + + if (type == FILLSTYLE.LINEAR_GRADIENT) { + Point start = matrix.transform(new Point(-16384 - 32768 * lineRepeatCnt, 0)); + Point end = matrix.transform(new Point(16384 + 32768 * lineRepeatCnt, 0)); + start.x += deltaX; + start.y += deltaY; + end.x += deltaX; + end.y += deltaY; + lineFillData += "\tvar grd=ctx.createLinearGradient(" + Double.toString(start.x / unitDivisor) + "," + Double.toString(start.y / unitDivisor) + "," + Double.toString(end.x / unitDivisor) + "," + Double.toString(end.y / unitDivisor) + ");\r\n"; + } else { + matrix.translateX /= unitDivisor; + matrix.translateY /= unitDivisor; + matrix.scaleX /= unitDivisor; + matrix.scaleY /= unitDivisor; + matrix.rotateSkew0 /= unitDivisor; + matrix.rotateSkew1 /= unitDivisor; + lineFillMatrix = matrix; + + matrix.translateX += deltaX / unitDivisor; + matrix.translateY += deltaY / unitDivisor; + + lineFillData += "\tvar grd=ctx.createRadialGradient(" + focalPointRatio * 16384 + ",0,0,0,0," + (16384 + 32768 * lineRepeatCnt) + ");\r\n"; + } + int repeatTotal = lineRepeatCnt * 2 + 1; + double oneHeight = 1.0 / repeatTotal; + double pos = 0; + boolean revert = false; + if (type != FILLSTYLE.LINEAR_GRADIENT && spreadMethod == GRADIENT.SPREAD_REFLECT_MODE) { + revert = true; + } + for (int i = 0; i < repeatTotal; i++) { + if (spreadMethod == GRADIENT.SPREAD_REFLECT_MODE) { + revert = !revert; + } + for (GRADRECORD r : gradientRecords) { + lineFillData += "\tgrd.addColorStop(" + Double.toString(pos + (oneHeight * (revert ? 255 - r.ratio : r.ratio) / 255.0)) + "," + color(r.color) + ");\r\n"; + lineLastRadColor = color(r.color); + } + pos += oneHeight; + } + lineFillData += "\tctx.fillStyle = grd;\r\n"; + + String preStrokeData = ""; + + preStrokeData += "\tvar lcanvas = document.createElement(\"canvas\");\r\n"; + preStrokeData += "\tlcanvas.width = canvas.width;\r\n"; + preStrokeData += "\tlcanvas.height=canvas.height;\r\n"; + preStrokeData += "\tvar lctx = lcanvas.getContext(\"2d\");\r\n"; + preStrokeData += "\tenhanceContext(lctx);\r\n"; + preStrokeData += "\tlctx.applyTransforms(ctx._matrices);\r\n"; + preStrokeData += "\tctx = lctx;\r\n"; + strokeData = preStrokeData + strokeData; + } + + @Override + public void moveTo(double x, double y) { + currentDrawCommand = DRAW_COMMAND_M; + pathData += currentDrawCommand + " "; + x += deltaX; + y += deltaY; + pathData += Helper.doubleStr(x / unitDivisor) + " " + + Helper.doubleStr(y / unitDivisor) + " "; + } + + @Override + public void lineTo(double x, double y) { + if (!currentDrawCommand.equals(DRAW_COMMAND_L)) { + currentDrawCommand = DRAW_COMMAND_L; + pathData += currentDrawCommand + " "; + } + x += deltaX; + y += deltaY; + pathData += Helper.doubleStr(x / unitDivisor) + " " + + Helper.doubleStr(y / unitDivisor) + " "; + } + + @Override + public void curveTo(double controlX, double controlY, double anchorX, double anchorY) { + if (!currentDrawCommand.equals(DRAW_COMMAND_Q)) { + currentDrawCommand = DRAW_COMMAND_Q; + pathData += currentDrawCommand + " "; + } + controlX += deltaX; + anchorX += deltaX; + controlY += deltaY; + anchorY += deltaY; + pathData += Helper.doubleStr(controlX / unitDivisor) + " " + + Helper.doubleStr(controlY / unitDivisor) + " " + + Helper.doubleStr(anchorX / unitDivisor) + " " + + Helper.doubleStr(anchorY / unitDivisor) + " "; + } + + protected void finalizePath() { + if (!"".equals(pathData)) { + pathData = "\tdrawPath(ctx,\"" + pathData + "\");\r\n"; + + if (lineFillData != null) { + String preLineFillData = ""; + preLineFillData += "\tvar oldctx = ctx;\r\n"; + preLineFillData += "\tctx.save();\r\n"; + preLineFillData += strokeData; + preLineFillData += pathData; + preLineFillData += "\tctx.stroke();\r\n"; + preLineFillData += "\tvar lfcanvas = document.createElement(\"canvas\");\r\n"; + preLineFillData += "\tlfcanvas.width = canvas.width;\r\n"; + preLineFillData += "\tlfcanvas.height=canvas.height;\r\n"; + preLineFillData += "\tvar lfctx = lfcanvas.getContext(\"2d\");\r\n"; + preLineFillData += "\tenhanceContext(lfctx);\r\n"; + preLineFillData += "\tlfctx.applyTransforms(ctx._matrices);\r\n"; + preLineFillData += "\tctx = lfctx;"; + if (lineLastRadColor != null) { + preLineFillData += "\tctx.fillStyle=" + lineLastRadColor + ";\r\n\tctx.fill(\"evenodd\");\r\n"; + } + + preLineFillData += "\tctx.transform(" + Helper.doubleStr(lineFillMatrix.scaleX) + "," + Helper.doubleStr(lineFillMatrix.rotateSkew0) + "," + Helper.doubleStr(lineFillMatrix.rotateSkew1) + "," + Helper.doubleStr(lineFillMatrix.scaleY) + "," + Helper.doubleStr(lineFillMatrix.translateX) + "," + Helper.doubleStr(lineFillMatrix.translateY) + ");\r\n"; + lineFillData = preLineFillData + lineFillData; + lineFillData += "\tctx.fillRect(" + (-16384 - 32768 * lineRepeatCnt) + "," + (-16384 - 32768 * lineRepeatCnt) + "," + (2 * 16384 + 32768 * 2 * lineRepeatCnt) + "," + (2 * 16384 + 32768 * 2 * lineRepeatCnt) + ");\r\n"; + + lineFillData += "\tctx = oldctx;\r\n"; + + //lcanvas - stroke + //lfcanvas - stroke background + lineFillData += "\tvar limgd = lctx.getImageData(0, 0, lcanvas.width, lcanvas.height);\r\n" + + "\tvar lpix = limgd.data;\r\n" + + "\tvar lfimgd = lfctx.getImageData(0, 0, lfcanvas.width, lfcanvas.height);\r\n" + + "\tvar lfpix = lfimgd.data;\r\n" + + "\tvar imgd = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n" + + "\tvar pix = imgd.data;\r\n" + + "\tfor (var i = 0; i < lpix.length; i += 4) {\r\n" + + "\t\tif(lpix[i+3]>0){ pix[i] = lfpix[i]; pix[i+1] = lfpix[i+1]; pix[i+2] = lfpix[i+2]; pix[i+3] = lfpix[i+3];}\r\n" + + "\t}\r\n" + + "\tctx.putImageData(imgd, 0, 0);\r\n"; + lineFillData += "\tctx.restore();\r\n"; + strokeData = ""; + } else { + pathData += strokeData; + } + if (fillMatrix != null) { + if (lastRadColor != null) { + pathData += "\tctx.fillStyle=" + lastRadColor + ";\r\n\tctx.fill(\"evenodd\");\r\n"; + } + pathData += "\tctx.save();\r\n"; + pathData += "\tctx.clip();\r\n"; + pathData += "\tctx.transform(" + Helper.doubleStr(fillMatrix.scaleX) + "," + Helper.doubleStr(fillMatrix.rotateSkew0) + "," + Helper.doubleStr(fillMatrix.rotateSkew1) + "," + Helper.doubleStr(fillMatrix.scaleY) + "," + Helper.doubleStr(fillMatrix.translateX) + "," + Helper.doubleStr(fillMatrix.translateY) + ");\r\n"; + if (fillWidth > 0) {//repeating bitmap glitch fix + //make bitmap 1px wider + double s_w = (fillWidth + 1) / (double) fillWidth; + double s_h = (fillHeight + 1) / (double) fillHeight; + + pathData += "\tctx.transform(" + (s_w) + ",0,0," + s_h + ",-0.5,-0.5);\r\n"; + } + pathData += fillData; + pathData += "\tctx.fillRect(" + (-16384 - 32768 * repeatCnt) + "," + (-16384 - 32768 * repeatCnt) + "," + (2 * 16384 + 32768 * 2 * repeatCnt) + "," + (2 * 16384 + 32768 * 2 * repeatCnt) + ");\r\n"; + pathData += "\tctx.restore();\r\n"; + shapeData += pathData; + } else { + if (!"".equals(fillData)) { + pathData += "\tctx.fill(\"evenodd\");\r\n"; + } + shapeData += fillData + pathData; + } + if (!"".equals(strokeData)) { + shapeData += "\tctx.stroke();\r\n"; + } else if (lineFillData != null) { + shapeData += lineFillData; + } + } + + repeatCnt = 0; + + pathData = ""; + fillData = ""; + strokeData = ""; + fillMatrix = null; + lastRadColor = null; + + lineRepeatCnt = 0; + lineFillData = null; + lineLastRadColor = null; + lineFillMatrix = null; + + fillWidth = 0; + fillHeight = 0; + } + +} diff --git a/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java b/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java index 55efe27d1..38210dbae 100644 --- a/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java +++ b/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java @@ -1,85 +1,188 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.tags.base; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Color; -import java.io.IOException; -import java.io.InputStream; - -/** - * - * @author JPEXS - */ -public abstract class ImageTag extends CharacterTag { - - public ImageTag(SWF swf, int id, String name, ByteArrayRange data) { - super(swf, id, name, data); - } - - public abstract InputStream getImageData(); - - public abstract SerializableImage getImage(); - - public abstract void setImage(byte[] data) throws IOException; - - public abstract String getImageFormat(); - - public boolean importSupported() { - return true; - } - - public static String getImageFormat(byte[] data) { - if (SWF.hasErrorHeader(data)) { - return "jpg"; - } - if (data.length > 2 && ((data[0] & 0xff) == 0xff) && ((data[1] & 0xff) == 0xd8)) { - return "jpg"; - } - if (data.length > 6 && ((data[0] & 0xff) == 0x47) && ((data[1] & 0xff) == 0x49) && ((data[2] & 0xff) == 0x46) && ((data[3] & 0xff) == 0x38) && ((data[4] & 0xff) == 0x39) && ((data[5] & 0xff) == 0x61)) { - return "gif"; - } - - if (data.length > 8 && ((data[0] & 0xff) == 0x89) && ((data[1] & 0xff) == 0x50) && ((data[2] & 0xff) == 0x4e) && ((data[3] & 0xff) == 0x47) && ((data[4] & 0xff) == 0x0d) && ((data[5] & 0xff) == 0x0a) && ((data[6] & 0xff) == 0x1a) && ((data[7] & 0xff) == 0x0a)) { - return "png"; - } - - return "unk"; - } - - protected static int max255(float val) { - if (val > 255) { - return 255; - } - return (int) val; - } - - protected static Color intToColor(int val) { - return new Color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); - } - - protected static int colorToInt(Color c) { - return (c.getAlpha() << 24) | (c.getBlue() << 16) | (c.getGreen() << 8) | c.getRed(); - } - - protected static Color multiplyAlpha(Color c) { - float multiplier = c.getAlpha() == 0 ? 0 : 255.0f / c.getAlpha(); - return new Color(max255(c.getRed() * multiplier), max255(c.getGreen() * multiplier), max255(c.getBlue() * multiplier), c.getAlpha()); - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; +import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; +import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; +import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter; +import com.jpexs.decompiler.flash.timeline.DepthState; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.FILLSTYLE; +import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; +import com.jpexs.decompiler.flash.types.LINESTYLE; +import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; +import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Color; +import java.awt.Shape; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +/** + * + * @author JPEXS + */ +public abstract class ImageTag extends CharacterTag implements DrawableTag { + + public ImageTag(SWF swf, int id, String name, ByteArrayRange data) { + super(swf, id, name, data); + } + + public abstract InputStream getImageData(); + + public abstract SerializableImage getImage(); + + public abstract void setImage(byte[] data) throws IOException; + + public abstract String getImageFormat(); + + public boolean importSupported() { + return true; + } + + public static String getImageFormat(byte[] data) { + if (SWF.hasErrorHeader(data)) { + return "jpg"; + } + if (data.length > 2 && ((data[0] & 0xff) == 0xff) && ((data[1] & 0xff) == 0xd8)) { + return "jpg"; + } + if (data.length > 6 && ((data[0] & 0xff) == 0x47) && ((data[1] & 0xff) == 0x49) && ((data[2] & 0xff) == 0x46) && ((data[3] & 0xff) == 0x38) && ((data[4] & 0xff) == 0x39) && ((data[5] & 0xff) == 0x61)) { + return "gif"; + } + + if (data.length > 8 && ((data[0] & 0xff) == 0x89) && ((data[1] & 0xff) == 0x50) && ((data[2] & 0xff) == 0x4e) && ((data[3] & 0xff) == 0x47) && ((data[4] & 0xff) == 0x0d) && ((data[5] & 0xff) == 0x0a) && ((data[6] & 0xff) == 0x1a) && ((data[7] & 0xff) == 0x0a)) { + return "png"; + } + + return "unk"; + } + + protected static int max255(float val) { + if (val > 255) { + return 255; + } + return (int) val; + } + + protected static Color intToColor(int val) { + return new Color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); + } + + protected static int colorToInt(Color c) { + return (c.getAlpha() << 24) | (c.getBlue() << 16) | (c.getGreen() << 8) | c.getRed(); + } + + protected static Color multiplyAlpha(Color c) { + float multiplier = c.getAlpha() == 0 ? 0 : 255.0f / c.getAlpha(); + return new Color(max255(c.getRed() * multiplier), max255(c.getGreen() * multiplier), max255(c.getBlue() * multiplier), c.getAlpha()); + } + + private SHAPEWITHSTYLE getShape() { + SHAPEWITHSTYLE shape = new SHAPEWITHSTYLE(); + shape.fillStyles = new FILLSTYLEARRAY(); + shape.fillStyles.fillStyles = new FILLSTYLE[1]; + FILLSTYLE fillStyle = new FILLSTYLE(); + fillStyle.fillStyleType = FILLSTYLE.REPEATING_BITMAP; + fillStyle.bitmapId = getCharacterId(); + MATRIX matrix = new MATRIX(); + matrix.hasScale = true; + matrix.scaleX = ((int) SWF.unitDivisor) << 16; + matrix.scaleY = matrix.scaleX; + fillStyle.bitmapMatrix = matrix; + shape.fillStyles.fillStyles[0] = fillStyle; + + shape.lineStyles = new LINESTYLEARRAY(); + shape.lineStyles.lineStyles = new LINESTYLE[0]; + shape.shapeRecords = new ArrayList<>(); + StyleChangeRecord style = new StyleChangeRecord(); + style.stateFillStyle0 = true; + style.fillStyle0 = 1; + style.stateMoveTo = true; + shape.shapeRecords.add(style); + RECT rect = getRect(); + StraightEdgeRecord top = new StraightEdgeRecord(); + top.generalLineFlag = true; + top.deltaX = rect.getWidth(); + StraightEdgeRecord right = new StraightEdgeRecord(); + right.generalLineFlag = true; + right.deltaY = rect.getHeight(); + StraightEdgeRecord bottom = new StraightEdgeRecord(); + bottom.generalLineFlag = true; + bottom.deltaX = -rect.getWidth(); + StraightEdgeRecord left = new StraightEdgeRecord(); + left.generalLineFlag = true; + left.deltaY = -rect.getHeight(); + shape.shapeRecords.add(top); + shape.shapeRecords.add(right); + shape.shapeRecords.add(bottom); + shape.shapeRecords.add(left); + shape.shapeRecords.add(new EndShapeRecord()); + return shape; + } + + @Override + public RECT getRect() { + SerializableImage image = getImage(); + int widthInTwips = (int) (image.getWidth() * SWF.unitDivisor); + int heightInTwips = (int) (image.getHeight() * SWF.unitDivisor); + return new RECT(0, widthInTwips, 0, heightInTwips); + } + + @Override + public void toImage(int frame, int time, int ratio, DepthState stateUnderCursor, int mouseButton, SerializableImage image, Matrix transformation, ColorTransform colorTransform) { + BitmapExporter.export(swf, getShape(), null, image, transformation, colorTransform); + } + + @Override + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException { + SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, getShape(), exporter, null, colorTransform); + shapeExporter.export(); + } + + @Override + public String toHtmlCanvas(double unitDivisor) { + CanvasShapeExporter cse = new CanvasShapeExporter(null, unitDivisor, swf, getShape(), new ColorTransform(), 0, 0); + cse.export(); + return cse.getShapeData(); + } + + @Override + public int getNumFrames() { + return 1; + } + + @Override + public boolean isSingleFrame() { + return true; + } + + @Override + public Shape getOutline(int frame, int time, int ratio, DepthState stateUnderCursor, int mouseButton, Matrix transformation) { + return transformation.toTransform().createTransformedShape(getShape().getOutline()); + } +} diff --git a/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java b/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java index ad5974611..b3f71c17c 100644 --- a/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java +++ b/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java @@ -1,80 +1,80 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.tags.base; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; -import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; -import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; -import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter; -import com.jpexs.decompiler.flash.timeline.DepthState; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Shape; -import java.io.IOException; -import java.util.Set; - -/** - * - * @author JPEXS - */ -public abstract class ShapeTag extends CharacterTag implements BoundedTag, DrawableTag { - - public ShapeTag(SWF swf, int id, String name, ByteArrayRange data) { - super(swf, id, name, data); - } - - public abstract SHAPEWITHSTYLE getShapes(); - - public abstract int getShapeNum(); - - @Override - public Shape getOutline(int frame, int time, int ratio, DepthState stateUnderCursor, int mouseButton, Matrix transformation) { - return transformation.toTransform().createTransformedShape(getShapes().getOutline()); - } - - @Override - public void toImage(int frame, int time, int ratio, DepthState stateUnderCursor, int mouseButton, SerializableImage image, Matrix transformation, ColorTransform colorTransform) { - BitmapExporter.export(swf, getShapes(), null, image, transformation, colorTransform); - } - - @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException { - SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, getShapes(), exporter, null, colorTransform); - shapeExporter.export(); - } - - @Override - public String toHtmlCanvas(double unitDivisor) { - CanvasShapeExporter cse = new CanvasShapeExporter(null, unitDivisor, swf, getShapes(), new ColorTransform(), 0, 0); - cse.export(); - return cse.getShapeData(); - } - - @Override - public void getNeededCharacters(Set needed) { - getShapes().getNeededCharacters(needed); - } - - @Override - public boolean removeCharacter(int characterId) { - return getShapes().removeCharacter(characterId); - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; +import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; +import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; +import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter; +import com.jpexs.decompiler.flash.timeline.DepthState; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Shape; +import java.io.IOException; +import java.util.Set; + +/** + * + * @author JPEXS + */ +public abstract class ShapeTag extends CharacterTag implements DrawableTag { + + public ShapeTag(SWF swf, int id, String name, ByteArrayRange data) { + super(swf, id, name, data); + } + + public abstract SHAPEWITHSTYLE getShapes(); + + public abstract int getShapeNum(); + + @Override + public Shape getOutline(int frame, int time, int ratio, DepthState stateUnderCursor, int mouseButton, Matrix transformation) { + return transformation.toTransform().createTransformedShape(getShapes().getOutline()); + } + + @Override + public void toImage(int frame, int time, int ratio, DepthState stateUnderCursor, int mouseButton, SerializableImage image, Matrix transformation, ColorTransform colorTransform) { + BitmapExporter.export(swf, getShapes(), null, image, transformation, colorTransform); + } + + @Override + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException { + SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, getShapes(), exporter, null, colorTransform); + shapeExporter.export(); + } + + @Override + public String toHtmlCanvas(double unitDivisor) { + CanvasShapeExporter cse = new CanvasShapeExporter(null, unitDivisor, swf, getShapes(), new ColorTransform(), 0, 0); + cse.export(); + return cse.getShapeData(); + } + + @Override + public void getNeededCharacters(Set needed) { + getShapes().getNeededCharacters(needed); + } + + @Override + public boolean removeCharacter(int characterId) { + return getShapes().removeCharacter(characterId); + } +} diff --git a/src/com/jpexs/decompiler/flash/tags/base/TextTag.java b/src/com/jpexs/decompiler/flash/tags/base/TextTag.java index 69b5741bc..90b1bff36 100644 --- a/src/com/jpexs/decompiler/flash/tags/base/TextTag.java +++ b/src/com/jpexs/decompiler/flash/tags/base/TextTag.java @@ -1,490 +1,490 @@ -/* - * Copyright (C) 2010-2014 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.tags.base; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.exporters.FontExporter; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; -import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; -import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; -import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; -import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.text.ParseException; -import com.jpexs.decompiler.flash.timeline.DepthState; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.FILLSTYLE; -import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; -import com.jpexs.decompiler.flash.types.GLYPHENTRY; -import com.jpexs.decompiler.flash.types.LINESTYLE; -import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.RGB; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.SHAPE; -import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; -import com.jpexs.decompiler.flash.types.TEXTRECORD; -import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; -import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.SerializableImage; -import java.awt.Color; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.font.LineMetrics; -import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.w3c.dom.Element; - -/** - * - * @author JPEXS - */ -public abstract class TextTag extends CharacterTag implements BoundedTag, DrawableTag { - - public TextTag(SWF swf, int id, String name, ByteArrayRange data) { - super(swf, id, name, data); - } - - public abstract MATRIX getTextMatrix(); - - public abstract String getText(String separator); - - public abstract List getFontIds(); - - public abstract String getFormattedText(); - - // use the texts from the "texts" argument when it is not null - public abstract boolean setFormattedText(MissingCharacterHandler missingCharHandler, String formattedText, String[] texts) throws ParseException; - - @Override - public abstract int getCharacterId(); - - public abstract RECT getBounds(); - - public abstract void setBounds(RECT r); - - private static void updateRect(RECT ret, int x, int y) { - if (x < ret.Xmin) { - ret.Xmin = x; - } - if (x > ret.Xmax) { - ret.Xmax = x; - } - if (y < ret.Ymin) { - ret.Ymin = y; - } - if (y > ret.Ymax) { - ret.Ymax = y; - } - } - - public static Map getTextRecordsAttributes(List list, List tags) { - Map att = new HashMap<>(); - RECT textBounds = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE); - FontTag font = null; - int x = 0; - int y = 0; - int textHeight = 12; - int lineSpacing = 0; - double leading = 0; - double ascent = 0; - double descent = 0; - double lineDistance = 0; - - List glyphs = new ArrayList<>(); - boolean firstLine = true; - double top = 0; - List allLeftMargins = new ArrayList<>(); - List allLetterSpacings = new ArrayList<>(); - FontMetrics fontMetrics; - BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); - Graphics graphics = bi.getGraphics(); - Font aFont = null; - int currentLeftMargin; - for (int r = 0; r < list.size(); r++) { - TEXTRECORD rec = list.get(r); - if (rec.styleFlagsHasFont) { - for (Tag t : tags) { - if (t instanceof FontTag) { - FontTag ft = (FontTag) t; - if (ft.getFontId() == rec.fontId) { - font = ft; - } - } - } - textHeight = rec.textHeight; - glyphs = font.getGlyphShapeTable(); - - if (!font.hasLayout()) { - String fontName = FontTag.getFontNameWithFallback(font.getFontName()); - aFont = new Font(fontName, font.getFontStyle(), (int) (textHeight / SWF.unitDivisor)); - fontMetrics = graphics.getFontMetrics(aFont); - LineMetrics lm = fontMetrics.getLineMetrics("A", graphics); - ascent = lm.getAscent(); - descent = lm.getDescent(); - leading = lm.getLeading(); - lineDistance = ascent + descent; - } else { - leading = ((double) font.getLeading() * textHeight / 1024.0 / font.getDivider() / SWF.unitDivisor); - ascent = ((double) font.getAscent() * textHeight / 1024.0 / font.getDivider() / SWF.unitDivisor); - descent = ((double) font.getDescent() * textHeight / 1024.0 / font.getDivider() / SWF.unitDivisor); - lineDistance = ascent + descent; - } - } - currentLeftMargin = 0; - if (rec.styleFlagsHasXOffset) { - x = rec.xOffset; - currentLeftMargin = x; - } - if (rec.styleFlagsHasYOffset) { - if (!firstLine) { - top += ascent + descent; - int topint = (int) (Math.round(top) * SWF.unitDivisor); - lineSpacing = rec.yOffset - topint; - top += lineSpacing / SWF.unitDivisor; - } else { - top = ascent; - } - y = rec.yOffset; - } - firstLine = false; - allLeftMargins.add(currentLeftMargin); - int letterSpacing = 0; - for (int e = 0; e < rec.glyphEntries.length; e++) { - GLYPHENTRY entry = rec.glyphEntries[e]; - GLYPHENTRY nextEntry = null; - if (e < rec.glyphEntries.length - 1) { - nextEntry = rec.glyphEntries[e + 1]; - } - RECT rect = SHAPERECORD.getBounds(glyphs.get(entry.glyphIndex).shapeRecords); - rect.Xmax = (int) Math.round(((double) rect.Xmax * textHeight) / (font.getDivider() * 1024)); - rect.Xmin = (int) Math.round(((double) rect.Xmin * textHeight) / (font.getDivider() * 1024)); - rect.Ymax = (int) Math.round(((double) rect.Ymax * textHeight) / (font.getDivider() * 1024)); - rect.Ymin = (int) Math.round(((double) rect.Ymin * textHeight) / (font.getDivider() * 1024)); - updateRect(textBounds, x + rect.Xmin, y + rect.Ymin); - updateRect(textBounds, x + rect.Xmax, y + rect.Ymax); - int adv = entry.glyphAdvance; - - int defaultAdvance; - if (font.hasLayout()) { - int kerningAdjustment = 0; - if (nextEntry != null) { - kerningAdjustment = font.getGlyphKerningAdjustment(entry.glyphIndex, nextEntry.glyphIndex); - } - defaultAdvance = (int) Math.round(textHeight * (font.getGlyphAdvance(entry.glyphIndex) + kerningAdjustment) / 1024.0); - } else { - defaultAdvance = (int) Math.round(SWF.unitDivisor * FontTag.getSystemFontAdvance(aFont, font.glyphToChar(entry.glyphIndex), nextEntry == null ? null : font.glyphToChar(nextEntry.glyphIndex))); - } - letterSpacing = adv - defaultAdvance; - x += adv / (font.getDivider()); - } - allLetterSpacings.add(letterSpacing); - } - att.put("indent", 0); //? - att.put("rightMargin", 0); //? - att.put("lineSpacing", lineSpacing); - att.put("textBounds", textBounds); - att.put("allLeftMargins", allLeftMargins); - att.put("allLetterSpacings", allLetterSpacings); - return att; - } - - public static SHAPE getBorderShape(RGB borderColor, RGB fillColor, RECT rect) { - SHAPEWITHSTYLE shape = new SHAPEWITHSTYLE(); - shape.fillStyles = new FILLSTYLEARRAY(); - if (fillColor != null) { - shape.fillStyles.fillStyles = new FILLSTYLE[1]; - FILLSTYLE fillStyle = new FILLSTYLE(); - fillStyle.fillStyleType = FILLSTYLE.SOLID; - fillStyle.color = fillColor; - shape.fillStyles.fillStyles[0] = fillStyle; - } else { - shape.fillStyles.fillStyles = new FILLSTYLE[0]; - } - shape.lineStyles = new LINESTYLEARRAY(); - shape.lineStyles.lineStyles = new LINESTYLE[1]; - LINESTYLE lineStyle = new LINESTYLE(); - lineStyle.color = borderColor; - lineStyle.width = 20; - shape.lineStyles.lineStyles[0] = lineStyle; - shape.shapeRecords = new ArrayList<>(); - StyleChangeRecord style = new StyleChangeRecord(); - style.lineStyle = 1; - style.stateLineStyle = true; - if (fillColor != null) { - style.stateFillStyle0 = true; - style.fillStyle0 = 1; - } - style.stateMoveTo = true; - shape.shapeRecords.add(style); - StraightEdgeRecord top = new StraightEdgeRecord(); - top.generalLineFlag = true; - top.deltaX = rect.getWidth(); - StraightEdgeRecord right = new StraightEdgeRecord(); - right.generalLineFlag = true; - right.deltaY = rect.getHeight(); - StraightEdgeRecord bottom = new StraightEdgeRecord(); - bottom.generalLineFlag = true; - bottom.deltaX = -rect.getWidth(); - StraightEdgeRecord left = new StraightEdgeRecord(); - left.generalLineFlag = true; - left.deltaY = -rect.getHeight(); - shape.shapeRecords.add(top); - shape.shapeRecords.add(right); - shape.shapeRecords.add(bottom); - shape.shapeRecords.add(left); - shape.shapeRecords.add(new EndShapeRecord()); - return shape; - } - - public static void drawBorder(SWF swf, SerializableImage image, RGB borderColor, RGB fillColor, RECT rect, MATRIX textMatrix, Matrix transformation, ColorTransform colorTransform) { - Graphics2D g = (Graphics2D) image.getGraphics(); - Matrix mat = transformation.clone(); - mat = mat.concatenate(new Matrix(textMatrix)); - BitmapExporter.export(swf, getBorderShape(borderColor, fillColor, rect), null, image, mat, colorTransform); - } - - public static void staticTextToImage(SWF swf, List textRecords, int numText, SerializableImage image, MATRIX textMatrix, Matrix transformation, ColorTransform colorTransform) { - Color textColor = new Color(0, 0, 0); - FontTag font = null; - int textHeight = 12; - int x = 0; - int y = 0; - List glyphs = new ArrayList<>(); - for (TEXTRECORD rec : textRecords) { - if (rec.styleFlagsHasColor) { - if (numText == 2) { - textColor = colorTransform.apply(rec.textColorA.toColor()); - } else { - textColor = colorTransform.apply(rec.textColor.toColor()); - } - } - if (rec.styleFlagsHasFont) { - font = (FontTag) swf.characters.get(rec.fontId); - glyphs = font.getGlyphShapeTable(); - textHeight = rec.textHeight; - } - if (rec.styleFlagsHasXOffset) { - x = rec.xOffset; - } - if (rec.styleFlagsHasYOffset) { - y = rec.yOffset; - } - - double rat = textHeight / 1024.0 / font.getDivider(); - - for (GLYPHENTRY entry : rec.glyphEntries) { - Matrix mat = transformation.clone(); - mat = mat.concatenate(new Matrix(textMatrix)); - Matrix matTr = Matrix.getTranslateInstance(x, y); - mat = mat.concatenate(matTr); - mat = mat.concatenate(Matrix.getScaleInstance(rat)); - if (entry.glyphIndex != -1) { - // shapeNum: 1 - SHAPE shape = glyphs.get(entry.glyphIndex); - BitmapExporter.export(swf, shape, textColor, image, mat, colorTransform); - x += entry.glyphAdvance; - } - } - } - } - - public static String staticTextToHtmlCanvas(double unitDivisor, SWF swf, List textRecords, int numText, RECT bounds, MATRIX textMatrix, ColorTransform colorTransform) { - Color textColor = new Color(0, 0, 0); - String ret = ""; - FontTag font = null; - int fontId = -1; - int textHeight = 12; - int x = 0; - int y = 0; - - List glyphs = new ArrayList<>(); - for (TEXTRECORD rec : textRecords) { - if (rec.styleFlagsHasColor) { - if (numText == 2) { - textColor = colorTransform.apply(rec.textColorA.toColor()); - } else { - textColor = colorTransform.apply(rec.textColor.toColor()); - } - } - if (rec.styleFlagsHasFont) { - font = (FontTag) swf.characters.get(rec.fontId); - fontId = rec.fontId; - glyphs = font.getGlyphShapeTable(); - textHeight = rec.textHeight; - } - if (rec.styleFlagsHasXOffset) { - x = rec.xOffset; - } - if (rec.styleFlagsHasYOffset) { - y = rec.yOffset; - } - - double rat = textHeight / 1024.0 / font.getDivider(); - - ret += "\tvar textColor = " + CanvasShapeExporter.color(textColor) + ";\r\n"; - for (GLYPHENTRY entry : rec.glyphEntries) { - Matrix mat = (new Matrix(textMatrix).concatenate(Matrix.getTranslateInstance(x, y))).concatenate(Matrix.getScaleInstance(rat)); - if (entry.glyphIndex != -1) { - // shapeNum: 1 - ret += "\tctx.save();\r\n"; - ret += "\tctx.transform(" + mat.scaleX + "," + mat.rotateSkew0 + "," + mat.rotateSkew1 + "," + mat.scaleY + "," + mat.translateX + "," + mat.translateY + ");\r\n"; - ret += "\tfont" + fontId + "(ctx,\"" + ("" + font.glyphToChar(entry.glyphIndex)).replace("\\", "\\\\").replace("\"", "\\\"") + "\",textColor);\r\n"; - ret += "\tctx.restore();\r\n"; - x += entry.glyphAdvance; - } - } - } - return ret; - } - - public static void staticTextToSVG(SWF swf, List textRecords, int numText, SVGExporter exporter, RECT bounds, MATRIX textMatrix, ColorTransform colorTransform) { - Color textColor = new Color(0, 0, 0); - FontTag font = null; - int textHeight = 12; - int x = 0; - int y = 0; - List glyphs = new ArrayList<>(); - for (TEXTRECORD rec : textRecords) { - if (rec.styleFlagsHasColor) { - if (numText == 2) { - textColor = colorTransform.apply(rec.textColorA.toColor()); - } else { - textColor = colorTransform.apply(rec.textColor.toColor()); - } - } - if (rec.styleFlagsHasFont) { - font = (FontTag) swf.characters.get(rec.fontId); - glyphs = font.getGlyphShapeTable(); - textHeight = rec.textHeight; - } - int offsetX = 0; - int offsetY = 0; - if (rec.styleFlagsHasXOffset) { - offsetX = rec.xOffset; - x = offsetX; - } - if (rec.styleFlagsHasYOffset) { - offsetY = rec.yOffset; - y = offsetY; - } - - double rat = textHeight / 1024.0 / font.getDivider(); - - exporter.createSubGroup(new Matrix(textMatrix), null); - if (exporter.useTextTag) { - StringBuilder text = new StringBuilder(); - int totalAdvance = 0; - for (GLYPHENTRY entry : rec.glyphEntries) { - if (entry.glyphIndex != -1) { - char ch = font.glyphToChar(entry.glyphIndex); - text.append(ch); - totalAdvance += entry.glyphAdvance; - } - } - - boolean hasOffset = offsetX != 0 || offsetY != 0; - if (hasOffset) { - exporter.createSubGroup(Matrix.getTranslateInstance(offsetX, offsetY), null); - } - - Element textElement = exporter.createElement("text"); - textElement.setAttribute("font-size", Double.toString(rat * 1024)); - textElement.setAttribute("font-family", font.getFontName()); - textElement.setAttribute("textLength", Double.toString(totalAdvance / SWF.unitDivisor)); - textElement.setAttribute("lengthAdjust", "spacing"); - textElement.setTextContent(text.toString()); - - if (textColor != null) { - RGBA colorA = new RGBA(textColor); - textElement.setAttribute("fill", colorA.toHexRGB()); - if (colorA.alpha != 255) { - textElement.setAttribute("fill-opacity", Float.toString(colorA.getAlphaFloat())); - } - } - - exporter.addToGroup(textElement); - FontExportMode fontExportMode = FontExportMode.WOFF; - exporter.addStyle(font.getFontName(), new FontExporter().exportFont(font, fontExportMode), fontExportMode); - - if (hasOffset) { - exporter.endGroup(); - } - } else { - for (GLYPHENTRY entry : rec.glyphEntries) { - Matrix mat = Matrix.getTranslateInstance(x, y).concatenate(Matrix.getScaleInstance(rat)); - if (entry.glyphIndex != -1) { - // shapeNum: 1 - SHAPE shape = glyphs.get(entry.glyphIndex); - char ch = font.glyphToChar(entry.glyphIndex); - - String charId = null; - Map chs; - if (exporter.exportedChars.containsKey(font)) { - chs = exporter.exportedChars.get(font); - if (chs.containsKey(ch)) { - charId = chs.get(ch); - } - } else { - chs = new HashMap<>(); - exporter.exportedChars.put(font, chs); - } - - if (charId == null) { - charId = exporter.getUniqueId(Helper.getValidHtmlId("font_" + font.getFontName() + "_" + ch)); - exporter.createDefGroup(null, charId); - SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shape, exporter, null, colorTransform); - shapeExporter.export(); - exporter.endGroup(); - chs.put(ch, charId); - } - - Element charImage = exporter.addUse(mat, bounds, charId); - if (textColor != null) { - RGBA colorA = new RGBA(textColor); - charImage.setAttribute("fill", colorA.toHexRGB()); - if (colorA.alpha != 255) { - charImage.setAttribute("fill-opacity", Float.toString(colorA.getAlphaFloat())); - } - } - x += entry.glyphAdvance; - } - } - } - exporter.endGroup(); - } - } - - @Override - public Shape getOutline(int frame, int time, int ratio, DepthState stateUnderCursor, int mouseButton, Matrix transformation) { - RECT r = getBounds(); - return new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight()); //TODO: match character shapes - } -} +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.FontExporter; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; +import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; +import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; +import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; +import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.text.ParseException; +import com.jpexs.decompiler.flash.timeline.DepthState; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.FILLSTYLE; +import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; +import com.jpexs.decompiler.flash.types.GLYPHENTRY; +import com.jpexs.decompiler.flash.types.LINESTYLE; +import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; +import com.jpexs.decompiler.flash.types.TEXTRECORD; +import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.SerializableImage; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.LineMetrics; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.w3c.dom.Element; + +/** + * + * @author JPEXS + */ +public abstract class TextTag extends CharacterTag implements DrawableTag { + + public TextTag(SWF swf, int id, String name, ByteArrayRange data) { + super(swf, id, name, data); + } + + public abstract MATRIX getTextMatrix(); + + public abstract String getText(String separator); + + public abstract List getFontIds(); + + public abstract String getFormattedText(); + + // use the texts from the "texts" argument when it is not null + public abstract boolean setFormattedText(MissingCharacterHandler missingCharHandler, String formattedText, String[] texts) throws ParseException; + + @Override + public abstract int getCharacterId(); + + public abstract RECT getBounds(); + + public abstract void setBounds(RECT r); + + private static void updateRect(RECT ret, int x, int y) { + if (x < ret.Xmin) { + ret.Xmin = x; + } + if (x > ret.Xmax) { + ret.Xmax = x; + } + if (y < ret.Ymin) { + ret.Ymin = y; + } + if (y > ret.Ymax) { + ret.Ymax = y; + } + } + + public static Map getTextRecordsAttributes(List list, List tags) { + Map att = new HashMap<>(); + RECT textBounds = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE); + FontTag font = null; + int x = 0; + int y = 0; + int textHeight = 12; + int lineSpacing = 0; + double leading = 0; + double ascent = 0; + double descent = 0; + double lineDistance = 0; + + List glyphs = new ArrayList<>(); + boolean firstLine = true; + double top = 0; + List allLeftMargins = new ArrayList<>(); + List allLetterSpacings = new ArrayList<>(); + FontMetrics fontMetrics; + BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); + Graphics graphics = bi.getGraphics(); + Font aFont = null; + int currentLeftMargin; + for (int r = 0; r < list.size(); r++) { + TEXTRECORD rec = list.get(r); + if (rec.styleFlagsHasFont) { + for (Tag t : tags) { + if (t instanceof FontTag) { + FontTag ft = (FontTag) t; + if (ft.getFontId() == rec.fontId) { + font = ft; + } + } + } + textHeight = rec.textHeight; + glyphs = font.getGlyphShapeTable(); + + if (!font.hasLayout()) { + String fontName = FontTag.getFontNameWithFallback(font.getFontName()); + aFont = new Font(fontName, font.getFontStyle(), (int) (textHeight / SWF.unitDivisor)); + fontMetrics = graphics.getFontMetrics(aFont); + LineMetrics lm = fontMetrics.getLineMetrics("A", graphics); + ascent = lm.getAscent(); + descent = lm.getDescent(); + leading = lm.getLeading(); + lineDistance = ascent + descent; + } else { + leading = ((double) font.getLeading() * textHeight / 1024.0 / font.getDivider() / SWF.unitDivisor); + ascent = ((double) font.getAscent() * textHeight / 1024.0 / font.getDivider() / SWF.unitDivisor); + descent = ((double) font.getDescent() * textHeight / 1024.0 / font.getDivider() / SWF.unitDivisor); + lineDistance = ascent + descent; + } + } + currentLeftMargin = 0; + if (rec.styleFlagsHasXOffset) { + x = rec.xOffset; + currentLeftMargin = x; + } + if (rec.styleFlagsHasYOffset) { + if (!firstLine) { + top += ascent + descent; + int topint = (int) (Math.round(top) * SWF.unitDivisor); + lineSpacing = rec.yOffset - topint; + top += lineSpacing / SWF.unitDivisor; + } else { + top = ascent; + } + y = rec.yOffset; + } + firstLine = false; + allLeftMargins.add(currentLeftMargin); + int letterSpacing = 0; + for (int e = 0; e < rec.glyphEntries.length; e++) { + GLYPHENTRY entry = rec.glyphEntries[e]; + GLYPHENTRY nextEntry = null; + if (e < rec.glyphEntries.length - 1) { + nextEntry = rec.glyphEntries[e + 1]; + } + RECT rect = SHAPERECORD.getBounds(glyphs.get(entry.glyphIndex).shapeRecords); + rect.Xmax = (int) Math.round(((double) rect.Xmax * textHeight) / (font.getDivider() * 1024)); + rect.Xmin = (int) Math.round(((double) rect.Xmin * textHeight) / (font.getDivider() * 1024)); + rect.Ymax = (int) Math.round(((double) rect.Ymax * textHeight) / (font.getDivider() * 1024)); + rect.Ymin = (int) Math.round(((double) rect.Ymin * textHeight) / (font.getDivider() * 1024)); + updateRect(textBounds, x + rect.Xmin, y + rect.Ymin); + updateRect(textBounds, x + rect.Xmax, y + rect.Ymax); + int adv = entry.glyphAdvance; + + int defaultAdvance; + if (font.hasLayout()) { + int kerningAdjustment = 0; + if (nextEntry != null) { + kerningAdjustment = font.getGlyphKerningAdjustment(entry.glyphIndex, nextEntry.glyphIndex); + } + defaultAdvance = (int) Math.round(textHeight * (font.getGlyphAdvance(entry.glyphIndex) + kerningAdjustment) / 1024.0); + } else { + defaultAdvance = (int) Math.round(SWF.unitDivisor * FontTag.getSystemFontAdvance(aFont, font.glyphToChar(entry.glyphIndex), nextEntry == null ? null : font.glyphToChar(nextEntry.glyphIndex))); + } + letterSpacing = adv - defaultAdvance; + x += adv / (font.getDivider()); + } + allLetterSpacings.add(letterSpacing); + } + att.put("indent", 0); //? + att.put("rightMargin", 0); //? + att.put("lineSpacing", lineSpacing); + att.put("textBounds", textBounds); + att.put("allLeftMargins", allLeftMargins); + att.put("allLetterSpacings", allLetterSpacings); + return att; + } + + public static SHAPE getBorderShape(RGB borderColor, RGB fillColor, RECT rect) { + SHAPEWITHSTYLE shape = new SHAPEWITHSTYLE(); + shape.fillStyles = new FILLSTYLEARRAY(); + if (fillColor != null) { + shape.fillStyles.fillStyles = new FILLSTYLE[1]; + FILLSTYLE fillStyle = new FILLSTYLE(); + fillStyle.fillStyleType = FILLSTYLE.SOLID; + fillStyle.color = fillColor; + shape.fillStyles.fillStyles[0] = fillStyle; + } else { + shape.fillStyles.fillStyles = new FILLSTYLE[0]; + } + shape.lineStyles = new LINESTYLEARRAY(); + shape.lineStyles.lineStyles = new LINESTYLE[1]; + LINESTYLE lineStyle = new LINESTYLE(); + lineStyle.color = borderColor; + lineStyle.width = 20; + shape.lineStyles.lineStyles[0] = lineStyle; + shape.shapeRecords = new ArrayList<>(); + StyleChangeRecord style = new StyleChangeRecord(); + style.lineStyle = 1; + style.stateLineStyle = true; + if (fillColor != null) { + style.stateFillStyle0 = true; + style.fillStyle0 = 1; + } + style.stateMoveTo = true; + shape.shapeRecords.add(style); + StraightEdgeRecord top = new StraightEdgeRecord(); + top.generalLineFlag = true; + top.deltaX = rect.getWidth(); + StraightEdgeRecord right = new StraightEdgeRecord(); + right.generalLineFlag = true; + right.deltaY = rect.getHeight(); + StraightEdgeRecord bottom = new StraightEdgeRecord(); + bottom.generalLineFlag = true; + bottom.deltaX = -rect.getWidth(); + StraightEdgeRecord left = new StraightEdgeRecord(); + left.generalLineFlag = true; + left.deltaY = -rect.getHeight(); + shape.shapeRecords.add(top); + shape.shapeRecords.add(right); + shape.shapeRecords.add(bottom); + shape.shapeRecords.add(left); + shape.shapeRecords.add(new EndShapeRecord()); + return shape; + } + + public static void drawBorder(SWF swf, SerializableImage image, RGB borderColor, RGB fillColor, RECT rect, MATRIX textMatrix, Matrix transformation, ColorTransform colorTransform) { + Graphics2D g = (Graphics2D) image.getGraphics(); + Matrix mat = transformation.clone(); + mat = mat.concatenate(new Matrix(textMatrix)); + BitmapExporter.export(swf, getBorderShape(borderColor, fillColor, rect), null, image, mat, colorTransform); + } + + public static void staticTextToImage(SWF swf, List textRecords, int numText, SerializableImage image, MATRIX textMatrix, Matrix transformation, ColorTransform colorTransform) { + Color textColor = new Color(0, 0, 0); + FontTag font = null; + int textHeight = 12; + int x = 0; + int y = 0; + List glyphs = new ArrayList<>(); + for (TEXTRECORD rec : textRecords) { + if (rec.styleFlagsHasColor) { + if (numText == 2) { + textColor = colorTransform.apply(rec.textColorA.toColor()); + } else { + textColor = colorTransform.apply(rec.textColor.toColor()); + } + } + if (rec.styleFlagsHasFont) { + font = (FontTag) swf.characters.get(rec.fontId); + glyphs = font.getGlyphShapeTable(); + textHeight = rec.textHeight; + } + if (rec.styleFlagsHasXOffset) { + x = rec.xOffset; + } + if (rec.styleFlagsHasYOffset) { + y = rec.yOffset; + } + + double rat = textHeight / 1024.0 / font.getDivider(); + + for (GLYPHENTRY entry : rec.glyphEntries) { + Matrix mat = transformation.clone(); + mat = mat.concatenate(new Matrix(textMatrix)); + Matrix matTr = Matrix.getTranslateInstance(x, y); + mat = mat.concatenate(matTr); + mat = mat.concatenate(Matrix.getScaleInstance(rat)); + if (entry.glyphIndex != -1) { + // shapeNum: 1 + SHAPE shape = glyphs.get(entry.glyphIndex); + BitmapExporter.export(swf, shape, textColor, image, mat, colorTransform); + x += entry.glyphAdvance; + } + } + } + } + + public static String staticTextToHtmlCanvas(double unitDivisor, SWF swf, List textRecords, int numText, RECT bounds, MATRIX textMatrix, ColorTransform colorTransform) { + Color textColor = new Color(0, 0, 0); + String ret = ""; + FontTag font = null; + int fontId = -1; + int textHeight = 12; + int x = 0; + int y = 0; + + List glyphs = new ArrayList<>(); + for (TEXTRECORD rec : textRecords) { + if (rec.styleFlagsHasColor) { + if (numText == 2) { + textColor = colorTransform.apply(rec.textColorA.toColor()); + } else { + textColor = colorTransform.apply(rec.textColor.toColor()); + } + } + if (rec.styleFlagsHasFont) { + font = (FontTag) swf.characters.get(rec.fontId); + fontId = rec.fontId; + glyphs = font.getGlyphShapeTable(); + textHeight = rec.textHeight; + } + if (rec.styleFlagsHasXOffset) { + x = rec.xOffset; + } + if (rec.styleFlagsHasYOffset) { + y = rec.yOffset; + } + + double rat = textHeight / 1024.0 / font.getDivider(); + + ret += "\tvar textColor = " + CanvasShapeExporter.color(textColor) + ";\r\n"; + for (GLYPHENTRY entry : rec.glyphEntries) { + Matrix mat = (new Matrix(textMatrix).concatenate(Matrix.getTranslateInstance(x, y))).concatenate(Matrix.getScaleInstance(rat)); + if (entry.glyphIndex != -1) { + // shapeNum: 1 + ret += "\tctx.save();\r\n"; + ret += "\tctx.transform(" + mat.scaleX + "," + mat.rotateSkew0 + "," + mat.rotateSkew1 + "," + mat.scaleY + "," + mat.translateX + "," + mat.translateY + ");\r\n"; + ret += "\tfont" + fontId + "(ctx,\"" + ("" + font.glyphToChar(entry.glyphIndex)).replace("\\", "\\\\").replace("\"", "\\\"") + "\",textColor);\r\n"; + ret += "\tctx.restore();\r\n"; + x += entry.glyphAdvance; + } + } + } + return ret; + } + + public static void staticTextToSVG(SWF swf, List textRecords, int numText, SVGExporter exporter, RECT bounds, MATRIX textMatrix, ColorTransform colorTransform) { + Color textColor = new Color(0, 0, 0); + FontTag font = null; + int textHeight = 12; + int x = 0; + int y = 0; + List glyphs = new ArrayList<>(); + for (TEXTRECORD rec : textRecords) { + if (rec.styleFlagsHasColor) { + if (numText == 2) { + textColor = colorTransform.apply(rec.textColorA.toColor()); + } else { + textColor = colorTransform.apply(rec.textColor.toColor()); + } + } + if (rec.styleFlagsHasFont) { + font = (FontTag) swf.characters.get(rec.fontId); + glyphs = font.getGlyphShapeTable(); + textHeight = rec.textHeight; + } + int offsetX = 0; + int offsetY = 0; + if (rec.styleFlagsHasXOffset) { + offsetX = rec.xOffset; + x = offsetX; + } + if (rec.styleFlagsHasYOffset) { + offsetY = rec.yOffset; + y = offsetY; + } + + double rat = textHeight / 1024.0 / font.getDivider(); + + exporter.createSubGroup(new Matrix(textMatrix), null); + if (exporter.useTextTag) { + StringBuilder text = new StringBuilder(); + int totalAdvance = 0; + for (GLYPHENTRY entry : rec.glyphEntries) { + if (entry.glyphIndex != -1) { + char ch = font.glyphToChar(entry.glyphIndex); + text.append(ch); + totalAdvance += entry.glyphAdvance; + } + } + + boolean hasOffset = offsetX != 0 || offsetY != 0; + if (hasOffset) { + exporter.createSubGroup(Matrix.getTranslateInstance(offsetX, offsetY), null); + } + + Element textElement = exporter.createElement("text"); + textElement.setAttribute("font-size", Double.toString(rat * 1024)); + textElement.setAttribute("font-family", font.getFontName()); + textElement.setAttribute("textLength", Double.toString(totalAdvance / SWF.unitDivisor)); + textElement.setAttribute("lengthAdjust", "spacing"); + textElement.setTextContent(text.toString()); + + if (textColor != null) { + RGBA colorA = new RGBA(textColor); + textElement.setAttribute("fill", colorA.toHexRGB()); + if (colorA.alpha != 255) { + textElement.setAttribute("fill-opacity", Float.toString(colorA.getAlphaFloat())); + } + } + + exporter.addToGroup(textElement); + FontExportMode fontExportMode = FontExportMode.WOFF; + exporter.addStyle(font.getFontName(), new FontExporter().exportFont(font, fontExportMode), fontExportMode); + + if (hasOffset) { + exporter.endGroup(); + } + } else { + for (GLYPHENTRY entry : rec.glyphEntries) { + Matrix mat = Matrix.getTranslateInstance(x, y).concatenate(Matrix.getScaleInstance(rat)); + if (entry.glyphIndex != -1) { + // shapeNum: 1 + SHAPE shape = glyphs.get(entry.glyphIndex); + char ch = font.glyphToChar(entry.glyphIndex); + + String charId = null; + Map chs; + if (exporter.exportedChars.containsKey(font)) { + chs = exporter.exportedChars.get(font); + if (chs.containsKey(ch)) { + charId = chs.get(ch); + } + } else { + chs = new HashMap<>(); + exporter.exportedChars.put(font, chs); + } + + if (charId == null) { + charId = exporter.getUniqueId(Helper.getValidHtmlId("font_" + font.getFontName() + "_" + ch)); + exporter.createDefGroup(null, charId); + SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shape, exporter, null, colorTransform); + shapeExporter.export(); + exporter.endGroup(); + chs.put(ch, charId); + } + + Element charImage = exporter.addUse(mat, bounds, charId); + if (textColor != null) { + RGBA colorA = new RGBA(textColor); + charImage.setAttribute("fill", colorA.toHexRGB()); + if (colorA.alpha != 255) { + charImage.setAttribute("fill-opacity", Float.toString(colorA.getAlphaFloat())); + } + } + x += entry.glyphAdvance; + } + } + } + exporter.endGroup(); + } + } + + @Override + public Shape getOutline(int frame, int time, int ratio, DepthState stateUnderCursor, int mouseButton, Matrix transformation) { + RECT r = getBounds(); + return new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight()); //TODO: match character shapes + } +}