Files
jpexs-decompiler/src/com/jpexs/decompiler/flash/action/ActionListReader.java

930 lines
39 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.action;
import com.jpexs.decompiler.flash.AppStrings;
import com.jpexs.decompiler.flash.DisassemblyListener;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.action.model.ConstantPool;
import com.jpexs.decompiler.flash.action.model.DirectValueActionItem;
import com.jpexs.decompiler.flash.action.special.ActionEnd;
import com.jpexs.decompiler.flash.action.special.ActionNop;
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.ActionPush;
import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool;
import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction;
import com.jpexs.decompiler.flash.action.swf5.ActionEquals2;
import com.jpexs.decompiler.flash.action.swf5.ActionStoreRegister;
import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.ecma.EcmaScript;
import com.jpexs.decompiler.flash.ecma.Null;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.graph.Graph;
import com.jpexs.decompiler.graph.GraphSourceItem;
import com.jpexs.decompiler.graph.GraphSourceItemContainer;
import com.jpexs.decompiler.graph.GraphTargetItem;
import com.jpexs.decompiler.graph.NotCompileTimeItem;
import com.jpexs.decompiler.graph.TranslateException;
import com.jpexs.decompiler.graph.model.LocalData;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack;
import java.util.TreeMap;
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;
/**
* Class for reading data from SWF file
*
* @author JPEXS
*/
public class ActionListReader {
private static final Logger logger = Logger.getLogger(SWFInputStream.class.getName());
/**
* Reads list of actions from the stream. Reading ends with
* ActionEndFlag(=0) or end of the stream.
*
* @param listeners
* @param sis
* @param version
* @param ip
* @param endIp
* @param path
* @return List of actions
* @throws IOException
* @throws java.lang.InterruptedException
* @throws java.util.concurrent.TimeoutException
*/
public static List<Action> readActionListTimeout(final List<DisassemblyListener> listeners, final SWFInputStream sis, final int version, final int ip, final int endIp, final String path) throws IOException, InterruptedException, TimeoutException {
try {
return CancellableWorker.call(new Callable<List<Action>>() {
@Override
public List<Action> call() throws IOException, InterruptedException {
return readActionList(listeners, sis, version, ip, endIp, path);
}
}, Configuration.decompilationTimeoutSingleMethod.get(), TimeUnit.SECONDS);
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
if (cause instanceof InterruptedException) {
throw (InterruptedException) cause;
} else if (cause instanceof InterruptedException) {
throw (IOException) cause;
} else {
Logger.getLogger(ActionListReader.class.getName()).log(Level.SEVERE, null, ex);
}
}
return new ArrayList<>();
}
/**
* Reads list of actions from the stream. Reading ends with
* ActionEndFlag(=0) or end of the stream.
*
* @param listeners
* @param sis
* @param version
* @param ip
* @param endIp
* @param path
* @return List of actions
* @throws IOException
* @throws java.lang.InterruptedException
*/
private static List<Action> readActionList(List<DisassemblyListener> listeners, SWFInputStream sis, int version, int ip, int endIp, String path) throws IOException, InterruptedException {
ConstantPool cpool = new ConstantPool();
// Map of the actions. Use TreeMap to sort the keys in ascending order
Map<Long, Action> actionMap = new TreeMap<>();
Map<Long,Long> nextOffsets = new HashMap<>();
Action entryAction = readActionListAtPos(listeners, cpool,
sis, actionMap, nextOffsets,
ip, ip, endIp, version, path, false, new ArrayList<Long>());
List<Action> actions = new ArrayList<>();
if (actionMap.isEmpty()) {
return actions;
}
Map<Action, List<Action>> containerLastActions = new HashMap<>();
List<Long> addresses = new ArrayList<>(actionMap.keySet());
getContainerLastActions(actionMap, addresses, containerLastActions);
// jump to the entry action when it is diffrent from the first action in the map
long index = addresses.get(0);
if (index != -1 && entryAction != actionMap.get(index)) {
ActionJump jump = new ActionJump(0);
int size = jump.getTotalActionLength();
jump.setJumpOffset((int) (entryAction.getAddress() - size));
actions.add(jump);
}
// remove nulls
index = getNearAddress(addresses, index, true);
while (index > -1) {
Action action = actionMap.get(index);
long nextOffset = nextOffsets.get(index);
long nextIndex = getNearAddress(addresses, index + 1, true);
actions.add(action);
if (nextIndex != -1 && nextOffset != nextIndex) {
if (!action.isExit() && !(action instanceof ActionJump)) {
ActionJump jump = new ActionJump(0);
jump.setAddress(action.getAddress(), version);
int size = jump.getTotalActionLength();
jump.setJumpOffset((int) (nextOffset - action.getAddress() - size));
actions.add(jump);
}
}
index = nextIndex;
}
// Map for storing the targers of the "jump" actions
// "jump" action can be ActionIf, ActionJump and any ActionStore
Map<Action, Action> jumps = new HashMap<>();
getJumps(actions, jumps);
long endAddress = updateAddresses(actions, 0, version);
// add end action
Action lastAction = actions.get(actions.size() - 1);
Action aEnd = new ActionEnd();
if (!(lastAction instanceof ActionEnd)) {
aEnd.setAddress(endAddress, version);
actions.add(aEnd);
} else {
endAddress -= aEnd.getTotalActionLength();
}
updateJumps(actions, jumps, containerLastActions, endAddress, version);
updateActionStores(actions, jumps);
updateContainerSizes(actions, containerLastActions);
updateActionLengths(actions, version);
if (Configuration.autoDeobfuscate.get()) {
try {
actions = deobfuscateActionList(listeners, actions, version, 0, path);
updateActionLengths(actions, version);
removeZeroJumps(actions, version);
} catch (OutOfMemoryError | StackOverflowError | TranslateException ex) {
// keep orignal (not deobfuscated) actions
Logger.getLogger(ActionListReader.class.getName()).log(Level.SEVERE, null, ex);
}
}
return actions;
}
/**
* Reads list of actions from the stream. Reading ends with
* ActionEndFlag(=0) or end of the stream.
*
* @param listeners
* @param actions
* @param version
* @param ip
* @param path
* @return List of actions
* @throws IOException
* @throws java.lang.InterruptedException
*/
private static List<Action> deobfuscateActionList(List<DisassemblyListener> listeners, List<Action> actions, int version, int ip, String path) throws IOException, InterruptedException {
if (actions.isEmpty()) {
return actions;
}
Action lastAction = actions.get(actions.size() - 1);
int endIp = (int) lastAction.getAddress();
List<Action> retdups = new ArrayList<>(endIp);
for (int i = 0; i < endIp; i++) {
Action a = new ActionNop();
a.setAddress(i, version);
retdups.add(a);
}
List<Action> actionMap = new ArrayList<>(endIp);
for (int i = 0; i <= endIp; i++) {
actionMap.add(null);
}
for (Action a : actions) {
actionMap.set((int) a.getAddress(), a);
}
ConstantPool cpool = new ConstantPool();
Stack<GraphTargetItem> stack = new Stack<>();
ActionLocalData localData = new ActionLocalData();
int maxRecursionLevel = 0;
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
if (a instanceof ActionIf || a instanceof GraphSourceItemContainer) {
maxRecursionLevel++;
}
if (a instanceof ActionIf) {
ActionIf aif = (ActionIf) a;
aif.ignoreUsed = false;
aif.jumpUsed = false;
}
}
deobfustaceActionListAtPosRecursive(listeners, new ArrayList<GraphTargetItem>(), new HashMap<Long, List<GraphSourceItemContainer>>(), localData, stack, cpool, actionMap, ip, retdups, ip, endIp, path, new HashMap<Integer, Integer>(), false, new HashMap<Integer, HashMap<String, GraphTargetItem>>(), version, 0, maxRecursionLevel);
List<Action> ret = new ArrayList<>();
Action last = null;
for (Action a : retdups) {
if (a != last) {
ret.add(a);
}
last = a;
}
ret = Action.removeNops(0, ret, version, path);
List<Action> reta = new ArrayList<>();
for (Object o : ret) {
if (o instanceof Action) {
reta.add((Action) o);
}
}
return reta;
}
private static long getNearAddress(List<Long> addresses, long address, boolean next) {
int min = 0;
int max = addresses.size() - 1;
while (max >= min) {
int mid = (min + max) / 2;
long midValue = addresses.get(mid);
if(midValue == address)
return address;
else if (midValue < address)
min = mid + 1;
else
max = mid - 1;
}
return next
? (min < addresses.size() ? addresses.get(min) : -1)
: (max > 0 ? addresses.get(max) : -1);
}
private static Map<Long, Action> actionListToMap(List<Action> actions) {
Map<Long, Action> map = new HashMap<>(actions.size());
for (Action a : actions) {
long address = a.getAddress();
// There are multiple actions in the same address (2nd action is a jump for obfuscated code)
// So this check is required
if (!map.containsKey(address)) {
map.put(a.getAddress(), a);
}
}
return map;
}
private static void getJumps(List<Action> actions, Map<Action, Action> jumps) {
Map<Long, Action> actionMap = actionListToMap(actions);
for (Action a : actions) {
long target = -1;
if (a instanceof ActionIf) {
ActionIf aIf = (ActionIf) a;
target = aIf.getAddress() + a.getTotalActionLength() + aIf.getJumpOffset();
} else if (a instanceof ActionJump) {
ActionJump aJump = (ActionJump) a;
target = aJump.getAddress() + a.getTotalActionLength() + aJump.getJumpOffset();
} else if (a instanceof ActionStore) {
ActionStore aStore = (ActionStore) a;
int storeSize = aStore.getStoreSize();
// skip storeSize + 1 actions (+1 is the current action)
Action targetAction = a;
for (int i = 0; i <= storeSize; i++) {
long address = targetAction.getAddress() + targetAction.getTotalActionLength();
targetAction = actionMap.get(address);
if (targetAction == null) {
break;
}
}
jumps.put(a, targetAction);
}
if (target >= 0) {
Action targetAction = actionMap.get(target);
jumps.put(a, targetAction);
}
}
}
private static void getContainerLastActions(Map<Long, Action> actionMap, List<Long> addresses, Map<Action, List<Action>> lastActions) {
for (Long address : actionMap.keySet()) {
Action a = actionMap.get(address);
if (a instanceof GraphSourceItemContainer) {
GraphSourceItemContainer container = (GraphSourceItemContainer) a;
List<Long> sizes = container.getContainerSizes();
long endAddress = a.getAddress() + container.getHeaderSize();
List<Action> lasts = new ArrayList<>(sizes.size());
for (long size : sizes) {
endAddress += size;
long lastActionIndex = getNearAddress(addresses, endAddress - 1, false);
Action lastAction = null;
if (lastActionIndex != -1) {
lastAction = actionMap.get(lastActionIndex);
}
lasts.add(lastAction);
}
lastActions.put(a, lasts);
}
}
}
private static long updateAddresses(List<Action> actions, long address, int version) {
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
a.setAddress(address, version);
int length = a.getBytes(version).length;
if ((i != actions.size() - 1) && (a instanceof ActionEnd)) {
// placeholder for jump action
length = new ActionJump(0).getTotalActionLength();
}
address += length;
}
return address;
}
private static void updateActionLengths(List<Action> actions, int version) {
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
int length = a.getBytes(version).length;
a.actionLength = length - 1 - ((a.actionCode >= 0x80) ? 2 : 0);
}
}
private static void updateActionStores(List<Action> actions, Map<Action, Action> jumps) {
Map<Long, Action> actionMap = actionListToMap(actions);
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
if (a instanceof ActionStore) {
ActionStore aStore = (ActionStore) a;
Action nextActionAfterStore = jumps.get(a);
Action a1 = a;
List<Action> store = new ArrayList<>();
while (true) {
long address = a1.getAddress() + a1.getTotalActionLength();
a1 = actionMap.get(address);
if (a1 == null || a1 == nextActionAfterStore) {
break;
}
store.add(a1);
}
aStore.setStore(store);
}
}
}
private static void updateContainerSizes(List<Action> actions, Map<Action, List<Action>> containerLastActions) {
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
if (a instanceof GraphSourceItemContainer) {
GraphSourceItemContainer container = (GraphSourceItemContainer) a;
List<Action> lastActions = containerLastActions.get(a);
long startAddress = a.getAddress() + container.getHeaderSize();
for (int j = 0; j < lastActions.size(); j++) {
Action lastAction = lastActions.get(j);
int length = (int) (lastAction.getAddress() + lastAction.getTotalActionLength() - startAddress);
container.setContainerSize(j, length);
startAddress += length;
}
}
}
}
private static void replaceJumpTargets(Map<Action, Action> jumps, Action oldTarget, Action newTarget) {
for (Action a : jumps.keySet()) {
if (jumps.get(a) == oldTarget) {
jumps.put(a, newTarget);
}
}
}
private static void replaceContainerLastActions(Map<Action, List<Action>> containerLastActions, Action oldTarget, Action newTarget) {
for (Action a : containerLastActions.keySet()) {
List<Action> targets = containerLastActions.get(a);
for (int i = 0; i < targets.size(); i++) {
if (targets.get(i) == oldTarget) {
targets.set(i, newTarget);
}
}
}
}
private static void updateJumps(List<Action> actions, Map<Action, Action> jumps, Map<Action, List<Action>> containerLastActions, long endAddress, int version) {
if (actions.isEmpty()) {
return;
}
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
if ((i != actions.size() - 1) && (a instanceof ActionEnd)) {
ActionJump aJump = new ActionJump(0);
aJump.setJumpOffset((int) (endAddress - a.getAddress() - aJump.getTotalActionLength()));
aJump.setAddress(a.getAddress(), version);
replaceJumpTargets(jumps, a, aJump);
replaceContainerLastActions(containerLastActions, a, aJump);
a = aJump;
actions.set(i, a);
} else if (a instanceof ActionIf) {
ActionIf aIf = (ActionIf) a;
Action target = jumps.get(a);
long offset;
if (target != null) {
offset = target.getAddress() - a.getAddress() - a.getTotalActionLength();
} else {
offset = endAddress - a.getAddress() - a.getTotalActionLength();
}
aIf.setJumpOffset((int) offset);
} else if (a instanceof ActionJump) {
ActionJump aJump = (ActionJump) a;
Action target = jumps.get(a);
long offset;
if (target != null) {
offset = target.getAddress() - a.getAddress() - a.getTotalActionLength();
} else {
offset = endAddress - a.getAddress() - a.getTotalActionLength();
}
aJump.setJumpOffset((int) offset);
}
}
}
private static void removeZeroJumps(List<Action> actions, int version) {
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
if (a instanceof ActionJump) {
ActionJump aJump = (ActionJump) a;
if (aJump.getJumpOffset() == 0) {
if (removeAction(actions, i, version, false)) {
i--;
}
}
}
}
}
/**
* Removes an action from the action list, and updates all references
*
* @param actions
* @param index
* @param version
* @param removeWhenLast
* @return
*/
private static boolean removeAction(List<Action> actions, int index, int version, boolean removeWhenLast) {
if (index < 0 || actions.size() <= index) {
return false;
}
long startIp = actions.get(0).getAddress();
Action lastAction = actions.get(actions.size() - 1);
long endAddress = lastAction.getAddress() + lastAction.getTotalActionLength();
Map<Long, Action> actionMap = new TreeMap<>();
for (Action a : actions) {
actionMap.put(a.getAddress(), a);
}
List<Long> addresses = new ArrayList<>(actionMap.keySet());
Map<Action, List<Action>> containerLastActions = new HashMap<>();
getContainerLastActions(actionMap, addresses, containerLastActions);
Map<Action, Action> jumps = new HashMap<>();
getJumps(actions, jumps);
Action prevAction = index > 0 ? actions.get(index - 1) : null;
Action nextAction = index + 1 < actions.size() ? actions.get(index + 1) : null;
Action actionToRemove = actions.get(index);
for (Action a : containerLastActions.keySet()) {
List<Action> lastActions = containerLastActions.get(a);
for (int i = 0; i < lastActions.size(); i++) {
if (lastActions.get(i) == actionToRemove) {
if (!removeWhenLast) {
return false;
}
lastActions.set(i, prevAction);
}
}
}
for (Action a : jumps.keySet()) {
Action targetAction = jumps.get(a);
if (targetAction == actionToRemove) {
jumps.put(a, nextAction);
}
}
if (containerLastActions.containsKey(actionToRemove)) {
containerLastActions.remove(actionToRemove);
}
if (jumps.containsKey(actionToRemove)) {
jumps.remove(actionToRemove);
}
actions.remove(index);
updateAddresses(actions, startIp, version);
updateJumps(actions, jumps, containerLastActions, endAddress, version);
updateActionStores(actions, jumps);
updateContainerSizes(actions, containerLastActions);
updateActionLengths(actions, version);
return true;
}
private static Action readActionListAtPos(List<DisassemblyListener> listeners, ConstantPool cpool,
SWFInputStream sis, Map<Long, Action> actions, Map<Long, Long> nextOffsets,
long ip, long startIp, long endIp, int version, String path, boolean indeterminate, List<Long> visitedContainers) throws IOException {
Action entryAction = null;
if (visitedContainers.contains(ip)) {
return null;
}
visitedContainers.add(ip);
Queue<Long> jumpQueue = new LinkedList<>();
jumpQueue.add(ip);
while (!jumpQueue.isEmpty()) {
ip = jumpQueue.remove();
while (endIp == -1 || endIp > ip) {
sis.seek((int) ip);
Action a;
if ((a = sis.readAction(cpool)) == null) {
break;
}
int actionLengthWithHeader = a.getTotalActionLength();
// unknown action, replace with jump
if (a instanceof ActionNop) {
ActionJump aJump = new ActionJump(0);
int jumpLength = aJump.getTotalActionLength();
aJump.setAddress(a.getAddress(), version);
aJump.setJumpOffset(actionLengthWithHeader - jumpLength);
a = aJump;
actionLengthWithHeader = a.getTotalActionLength();
}
if (entryAction == null) {
entryAction = a;
}
Action existingAction = actions.get(ip);
if (existingAction != null) {
break;
}
actions.put(ip, a);
nextOffsets.put(ip, ip + actionLengthWithHeader);
long pos = sis.getPos();
long length = pos + sis.available();
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).progress(AppStrings.translate("disassemblingProgress.reading"), pos, length);
}
a.setAddress(ip, version, false);
if (a instanceof ActionPush && cpool != null) {
((ActionPush) a).constantPool = cpool.constants;
} else if (a instanceof ActionConstantPool) {
if (cpool == null) {
cpool = new ConstantPool();
}
cpool.setNew(((ActionConstantPool) a).constantPool);
} else if (a instanceof ActionIf) {
ActionIf aIf = (ActionIf) a;
long nIp = ip + actionLengthWithHeader + aIf.getJumpOffset();
if (nIp >= 0) {
jumpQueue.add(nIp);
}
} else if (a instanceof ActionJump) {
ActionJump aJump = (ActionJump) a;
long nIp = ip + actionLengthWithHeader + aJump.getJumpOffset();
if (nIp >= 0) {
jumpQueue.add(nIp);
}
break;
} else if (a instanceof GraphSourceItemContainer) {
GraphSourceItemContainer cnt = (GraphSourceItemContainer) a;
String cntName = cnt.getName();
String newPath = path + (cntName == null ? "" : "/" + cntName);
for (long size : cnt.getContainerSizes()) {
if (size != 0) {
long ip2 = ip + actionLengthWithHeader;
//long endIp2 = ip + actionLengthWithHeader + size;
readActionListAtPos(listeners, cpool,
sis, actions, nextOffsets,
ip2, startIp, endIp, version, newPath, indeterminate, visitedContainers);
actionLengthWithHeader += size;
}
}
}
ip += actionLengthWithHeader;
if (a.isExit()) {
break;
}
}
}
return entryAction;
}
private static void deobfustaceActionListAtPosRecursive(List<DisassemblyListener> listeners, List<GraphTargetItem> output, HashMap<Long, List<GraphSourceItemContainer>> containers, ActionLocalData localData, Stack<GraphTargetItem> stack, ConstantPool cpool, List<Action> actions, int ip, List<Action> ret, int startIp, int endip, String path, Map<Integer, Integer> visited, boolean indeterminate, Map<Integer, HashMap<String, GraphTargetItem>> decisionStates, int version, int recursionLevel, int maxRecursionLevel) throws IOException, InterruptedException {
boolean debugMode = false;
boolean decideBranch = false;
if (recursionLevel > maxRecursionLevel + 1) {
throw new TranslateException("deobfustaceActionListAtPosRecursive max recursion level reached.");
}
Action a;
Scanner sc = new Scanner(System.in);
loopip:
while (((endip == -1) || (endip > ip)) && (a = actions.get(ip)) != null) {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
int actionLen = a.getTotalActionLength();
if (!visited.containsKey(ip)) {
visited.put(ip, 0);
}
int curVisited = visited.get(ip);
curVisited++;
visited.put(ip, curVisited);
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).progress(AppStrings.translate("disassemblingProgress.deobfuscating"), ip, actions.size());
}
int info = a.getTotalActionLength();
if (a instanceof ActionPush) {
if (cpool != null) {
((ActionPush) a).constantPool = cpool.constants;
}
}
if (debugMode) {
String atos = a.getASMSource(new ArrayList<GraphSourceItem>(), new ArrayList<Long>(), cpool.constants, version, ScriptExportMode.PCODE);
if (a instanceof GraphSourceItemContainer) {
atos = a.toString();
}
System.err.println("readActionListAtPos ip: " + (ip - startIp) + " (0x" + Helper.formatAddress(ip - startIp) + ") " + " action(len " + a.actionLength + "): " + atos + (a.isIgnored() ? " (ignored)" : "") + " stack:" + Helper.stackToString(stack, LocalData.create(cpool)) + " " + Helper.byteArrToString(a.getBytes(version)));
System.err.print("variables: ");
for (Map.Entry<String, GraphTargetItem> v : localData.variables.entrySet()) {
System.err.print("'" + v + "' = " + v.getValue().toString(LocalData.create(cpool)) + ", ");
}
System.err.println();
String add = "";
if (a instanceof ActionIf) {
add = " change: " + ((ActionIf) a).getJumpOffset();
}
if (a instanceof ActionJump) {
add = " change: " + ((ActionJump) a).getJumpOffset();
}
System.err.println(add);
}
int newip = -1;
if (a instanceof ActionConstantPool) {
if (cpool == null) {
cpool = new ConstantPool();
}
cpool.setNew(((ActionConstantPool) a).constantPool);
}
ActionIf aif = null;
boolean goaif = false;
if (!a.isIgnored()) {
String varname = null;
if (a instanceof StoreTypeAction) {
StoreTypeAction sta = (StoreTypeAction) a;
varname = sta.getVariableName(stack, cpool);
}
try {
if (a instanceof ActionIf) {
aif = (ActionIf) a;
GraphTargetItem top = stack.pop();
int nip = ip + actionLen + aif.getJumpOffset();
if (decideBranch) {
System.out.print("newip " + nip + ", ");
System.out.print("Action: jump(j),ignore(i),compute(c)?");
String next = sc.next();
switch (next) {
case "j":
newip = nip;
break;
case "i":
break;
case "c":
goaif = true;
break;
}
} else if (top.isCompileTime() && (!top.hasSideEffect())) {
if (debugMode) {
System.err.print("is compiletime -> ");
}
if (EcmaScript.toBoolean(top.getResult())) {
newip = nip;
aif.jumpUsed = true;
if (debugMode) {
System.err.println("jump");
}
} else {
aif.ignoreUsed = true;
if (debugMode) {
System.err.println("ignore");
}
}
} else {
if (debugMode) {
System.err.println("goaif");
}
goaif = true;
}
} else if (a instanceof ActionJump) {
newip = ip + actionLen + ((ActionJump) a).getJumpOffset();
} else if (!(a instanceof GraphSourceItemContainer)) {
//return in for..in, TODO:Handle this better way
if (((a instanceof ActionEquals) || (a instanceof ActionEquals2)) && (stack.size() == 1) && (stack.peek() instanceof DirectValueActionItem)) {
stack.push(new DirectValueActionItem(null, 0, new Null(), new ArrayList<String>()));
}
if ((a instanceof ActionStoreRegister) && stack.isEmpty()) {
stack.push(new DirectValueActionItem(null, 0, new Null(), new ArrayList<String>()));
}
a.translate(localData, stack, output, Graph.SOP_USE_STATIC/*Graph.SOP_SKIP_STATIC*/, path);
}
} catch (RuntimeException ex) {
logger.log(Level.SEVERE, "Disassembly exception", ex);
break;
}
HashMap<String, GraphTargetItem> vars = localData.variables;
if (varname != null) {
GraphTargetItem varval = vars.get(varname);
if (varval != null && varval.isCompileTime() && indeterminate) {
vars.put(varname, new NotCompileTimeItem(null, varval));
}
}
}
int nopos = -1;
for (int i = 0; i < actionLen; i++) {
if (a instanceof ActionNop) {
int prevPos = (int) a.getAddress();
a = new ActionNop();
a.setAddress(prevPos, version);
nopos++;
if (nopos > 0) {
a.setAddress(a.getAddress() + 1, version);
}
}
ret.set(ip + i, a);
}
if (a instanceof GraphSourceItemContainer) {
GraphSourceItemContainer cnt = (GraphSourceItemContainer) a;
if (a instanceof Action) {
long endAddr = a.getAddress() + cnt.getHeaderSize();
String cntName = cnt.getName();
List<List<GraphTargetItem>> output2s = new ArrayList<>();
for (long size : cnt.getContainerSizes()) {
if (size == 0) {
output2s.add(new ArrayList<GraphTargetItem>());
continue;
}
ActionLocalData localData2;
List<GraphTargetItem> output2 = new ArrayList<>();
if ((cnt instanceof ActionDefineFunction) || (cnt instanceof ActionDefineFunction2)) {
localData2 = new ActionLocalData();
} else {
localData2 = localData;
}
deobfustaceActionListAtPosRecursive(listeners, output2, containers, localData2, new Stack<GraphTargetItem>(), cpool, actions, (int) endAddr, ret, startIp, (int) (endAddr + size), path + (cntName == null ? "" : "/" + cntName), visited, indeterminate, decisionStates, version, recursionLevel + 1, maxRecursionLevel);
output2s.add(output2);
endAddr += size;
}
cnt.translateContainer(output2s, stack, output, localData.regNames, localData.variables, localData.functions);
ip = (int) endAddr;
continue;
}
}
if (a instanceof ActionEnd) {
break;
}
if (goaif) {
aif.ignoreUsed = true;
aif.jumpUsed = true;
indeterminate = true;
HashMap<String, GraphTargetItem> vars = localData.variables;
boolean stateChanged = false;
if (decisionStates.containsKey(ip)) {
HashMap<String, GraphTargetItem> oldstate = decisionStates.get(ip);
if (oldstate.size() != vars.size()) {
stateChanged = true;
} else {
for (String k : vars.keySet()) {
if (!oldstate.containsKey(k)) {
stateChanged = true;
break;
}
if (!vars.get(k).isCompileTime() && oldstate.get(k).isCompileTime()) {
stateChanged = true;
break;
}
}
}
}
HashMap<String, GraphTargetItem> curstate = new HashMap<>();
curstate.putAll(vars);
decisionStates.put(ip, curstate);
if ((!stateChanged) && curVisited > 1) {
List<Integer> branches = new ArrayList<>();
branches.add(ip + actionLen + aif.getJumpOffset());
branches.add(ip + actionLen);
for (int br : branches) {
int visc = 0;
if (visited.containsKey(br)) {
visc = visited.get(br);
}
if (visc == 0) {//<curVisited){
ip = br;
continue loopip;
}
}
break loopip;
}
@SuppressWarnings("unchecked")
Stack<GraphTargetItem> substack = (Stack<GraphTargetItem>) stack.clone();
deobfustaceActionListAtPosRecursive(listeners, output, containers, prepareLocalBranch(localData), substack, cpool, actions, ip + actionLen + aif.getJumpOffset(), ret, startIp, endip, path, visited, indeterminate, decisionStates, version, recursionLevel + 1, maxRecursionLevel);
}
if (newip > -1) {
ip = newip;
} else {
ip += info;
}
if (a.isExit()) {
break;
}
}
for (DisassemblyListener listener : listeners) {
listener.progress(AppStrings.translate("disassemblingProgress.deobfuscating"), ip, actions.size());
}
}
private static ActionLocalData prepareLocalBranch(ActionLocalData localData) {
return new ActionLocalData(new HashMap<>(localData.regNames),
new HashMap<>(localData.variables), new HashMap<>(localData.functions));
}
}