/*
* 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.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.CallFunctionActionItem;
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.NewMethodActionItem;
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.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.Helper;
import com.jpexs.decompiler.flash.helpers.Highlighting;
import com.jpexs.decompiler.flash.helpers.collections.MyEntry;
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.NotItem;
import com.jpexs.decompiler.graph.model.ScriptEndItem;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
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 beforeInsert;
public Action afterInsert;
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));
}
if (a.beforeInsert != null) {
a.beforeInsert.setAddress(a.getAddress(), version, false);
ret.addAll(a.beforeInsert.getAllRefs(version));
}
List part = a.getAllRefs(version);
ret.addAll(part);
if (a.afterInsert != null) {
a.afterInsert.setAddress(a.getAddress(), version, false);
ret.addAll(a.afterInsert.getAllRefs(version));
}
}
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();
for (Action a : list) {
try {
baos.write(a.getBytes(version));
} catch (IOException e) {
}
}
if (addZero) {
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 ASM source as String
*/
public static String actionsToString(List listeners, long address, List list, List importantOffsets, int version, boolean hex, long swfPos, String path) {
return actionsToString(listeners, address, list, importantOffsets, new ArrayList(), version, hex, 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 ASM source as String
*/
public static String actionsToString(List listeners, long address, List list, List importantOffsets, List constantPool, int version, boolean hex, 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;
StringBuilder ret = new StringBuilder();
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 (hex) {
ret.append("");/* +"0x"+Helper.formatAddress(a.getFileAddress())+": "+*/;
ret.append(Helper.bytesToHexString(a.getBytes(version)));
ret.append("\r\n");
}
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++) {
ret.append("}\r\n");
GraphSourceItemContainer cnt = containers.get(offset).get(i);
int cntPos = containersPos.get(cnt);
ret.append(cnt.getASMSourceBetween(cntPos));
cntPos++;
containersPos.put(cnt, cntPos);
}
}
if (importantOffsets.contains(offset)) {
if (lastPush) {
ret.append("\r\n");
lastPush = false;
}
ret.append("loc");
ret.append(Helper.formatAddress(offset));
ret.append(":");
}
if (a.replaceWith != null) {
if (lastPush) {
ret.append("\r\n");
lastPush = false;
}
ret.append(Highlighting.hilighOffset("", offset));
ret.append(a.replaceWith.getASMSource(list, importantOffsets, constantPool, version, hex));
ret.append("\r\n");
} else if (a.isIgnored()) {
if (lastPush) {
ret.append("\r\n");
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++) {
ret.append("Nop\r\n");
}
}
} else {
if (a.beforeInsert != null) {
if (lastPush) {
ret.append("\r\n");
lastPush = false;
}
ret.append(a.beforeInsert.getASMSource(list, importantOffsets, constantPool, version, hex));
ret.append("\r\n");
}
//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) {
ret.append(" ");
ret.append(((ActionPush) a).paramsToStringReplaced(list, importantOffsets, constantPool, version, hex));
} else {
if (lastPush) {
ret.append("\r\n");
lastPush = false;
}
ret.append(Highlighting.hilighOffset("", 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) {
ret.append("ffdec_deobfuscatepop\r\n");
if (fixBranch == 0) { //jump
ret.append("jump loc");
ret.append(Helper.formatAddress(a.getAddress() + a.getBytes(version).length + ((ActionIf) a).getJumpOffset()));
} else {
//nojump, ignore
}
}
} else {
ret.append(a.getASMSourceReplaced(list, importantOffsets, constantPool, version, hex));
}
ret.append(a.isIgnored() ? "; ignored" : "");
ret.append(add);
ret.append((a instanceof ActionPush) ? "" : "\r\n");
}
if (a instanceof ActionPush) {
lastPush = true;
} else {
lastPush = false;
}
//}
if (a.afterInsert != null) {
if (lastPush) {
ret.append("\r\n");
lastPush = false;
}
ret.append(a.afterInsert.getASMSource(list, importantOffsets, constantPool, version, hex));
ret.append("\r\n");
}
}
offset += a.getBytes(version).length;
}
if (lastPush) {
ret.append("\r\n");
}
if (containers.containsKey(offset)) {
for (int i = 0; i < containers.get(offset).size(); i++) {
ret.append("}\r\n");
GraphSourceItemContainer cnt = containers.get(offset).get(i);
int cntPos = containersPos.get(cnt);
ret.append(cnt.getASMSourceBetween(cntPos));
cntPos++;
containersPos.put(cnt, cntPos);
}
}
if (importantOffsets.contains(offset)) {
ret.append("loc");
ret.append(Helper.formatAddress(offset));
ret.append(":\r\n");
}
return ret.toString();
}
/**
* 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, boolean hex) {
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 String actionsToSource(List actions, int version, String path) {
try {
//List tree = actionsToTree(new HashMap(), actions, version);
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);
return Graph.graphToString(tree);
} catch (Exception | OutOfMemoryError | StackOverflowError ex2) {
Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error", ex2);
if (ex2 instanceof OutOfMemoryError) {
System.gc();
}
return "/*\r\n * Decompilation error\r\n * Code may be obfuscated\r\n * Error type: " + ex2.getClass().getSimpleName() + "\r\n */";
}
}
/**
* 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