/*
* Copyright (C) 2010-2013 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.Configuration;
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.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.special.ActionEnd;
import com.jpexs.decompiler.flash.action.swf4.*;
import com.jpexs.decompiler.flash.action.swf5.*;
import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2;
import com.jpexs.decompiler.flash.ecma.Null;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.flash.helpers.HilightedText;
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.graph.ExportMode;
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.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.Helper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
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;
public long containerSWFOffset;
private long address;
public long getFileAddress() {
return containerSWFOffset + getAddress();
}
/**
* 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 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 hex Add hexadecimal?
* @param swfPos
* @param path
* @return HilightedTextWriter
*/
public static GraphTextWriter actionsToString(List listeners, long address, List list, List importantOffsets, int version, ExportMode exportMode, GraphTextWriter writer, long swfPos, String path) {
return actionsToString(listeners, address, list, importantOffsets, new ArrayList(), version, exportMode, writer, swfPos, 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 swfPos
* @param path
* @return HilightedTextWriter
*/
private static GraphTextWriter actionsToString(List listeners, long address, List list, List importantOffsets, List constantPool, int version, ExportMode exportMode, GraphTextWriter writer, long swfPos, 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("toString", pos + 2, list.size());
}
Action a = null;
if (s instanceof Action) {
a = (Action) s;
}
pos++;
if (exportMode == ExportMode.PCODEWITHHEX) {
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 (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);
if (a instanceof ActionIf) {
ActionIf aif = (ActionIf) a;
if (aif.jumpUsed && !aif.ignoreUsed) {
aif.setFixBranch(0);
}
if (!aif.jumpUsed && aif.ignoreUsed) {
aif.setFixBranch(1);
}
}
int fixBranch = a.getFixBranch();
if (fixBranch > -1) {
if (a instanceof ActionIf) {
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 hex Add hexadecimal
* @return String of P-code source
*/
public String getASMSource(List extends GraphSourceItem> container, List knownAddreses, List constantPool, int version, ExportMode 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
*/
public void translate(Stack stack, List output, java.util.HashMap regNames, HashMap variables, HashMap functions, int staticOperation, String path) {
}
/**
* 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) {
return actionsToTree(new HashMap(), new HashMap(), new HashMap(), actions, version, staticOperation, path);
}
/**
* Converts list of actions to ActionScript source code
*
* @param actions List of actions
* @param version SWF version
* @param path
* @return String with Source code
*/
public static HilightedText actionsToSource(final List actions, final int version, final String path, boolean highlight, int indent) {
List tree = null;
Throwable convertException = null;
int timeout = Configuration.getConfig("decompilationTimeoutSingleMethod", 60);
try {
tree = Helper.timedCall(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, version, staticOperation, path);
Graph.graphToString(tree, new NulWriter(), new LocalData());
return tree;
}
}, timeout, TimeUnit.SECONDS);
} catch (InterruptedException | TimeoutException | ExecutionException | OutOfMemoryError | 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();
}
}
HilightedTextWriter writer = new HilightedTextWriter(highlight, indent);
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);
writer.appendNoHilight("/*").newLine();
writer.appendNoHilight(" * Decompilation error").newLine();
writer.appendNoHilight(" * Timeout (" + Helper.formatTimeToText(timeout) + ") was reached").newLine();
writer.appendNoHilight(" */").newLine();
writer.appendNoHilight("throw new IllegalOperationError(\"Not decompiled due to timeout\");").newLine();
} else {
Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error", convertException);
writer.appendNoHilight("/*").newLine();
writer.appendNoHilight(" * Decompilation error").newLine();
writer.appendNoHilight(" * Code may be obfuscated").newLine();
writer.appendNoHilight(" * Error type: " + convertException.getClass().getSimpleName()).newLine();
writer.appendNoHilight(" */").newLine();
writer.appendNoHilight("throw new IllegalOperationError(\"Not decompiled due to error\");").newLine();
}
return new HilightedText(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
*/
public static List actionsToTree(HashMap regNames, HashMap variables, HashMap functions, List actions, int version, int staticOperation, String path) {
//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
@SuppressWarnings("unchecked")
public void translate(List