/*
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.action;
import com.jpexs.decompiler.flash.SWF;
import static com.jpexs.decompiler.flash.action.Action.actionsToSource;
import static com.jpexs.decompiler.flash.action.Action.actionsToString;
import com.jpexs.decompiler.flash.action.special.ActionNop;
import com.jpexs.decompiler.flash.action.special.ActionStore;
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.swf4.ConstantIndex;
import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.helpers.CodeFormatting;
import com.jpexs.decompiler.flash.helpers.FileTextWriter;
import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter;
import com.jpexs.decompiler.graph.GraphSourceItemContainer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author JPEXS
*/
public class ActionList extends ArrayList<Action> {
public int deobfuscationMode;
public byte[] fileData;
public ActionList() {
}
public ActionList(Collection<Action> actions) {
super(actions);
}
public void setActions(List<Action> list) {
clear();
addAll(list);
}
public void removeAction(int index) {
ActionListReader.removeAction(this, index, true);
}
public void removeActions(List<Action> actionsToRemove) {
ActionListReader.removeActions(this, actionsToRemove, true);
}
public void removeAction(int index, int count) {
if (size() <= index + count - 1) {
// Can't remove count elements, only size - index is available
count = size() - index;
}
for (int i = 0; i < count; i++) {
ActionListReader.removeAction(this, index, true);
}
}
public void addAction(int index, Action action) {
ActionListReader.addAction(this, index, action, false, false);
}
public void addActions(int index, List<Action> actions) {
ActionListReader.addActions(this, index, actions);
}
public void fixActionList() {
ActionListReader.fixActionList(this, null);
}
public List<Action> getContainerLastActions(Action action) {
return ActionListReader.getContainerLastActions(this, action);
}
public Iterator<Action> getReferencesFor(final Action target) {
return new Iterator<Action>() {
private final Iterator<Action> iterator = ActionList.this.iterator();
private Action action = getNext();
@Override
public boolean hasNext() {
return action != null;
}
@Override
public Action next() {
Action a = action;
action = getNext();
return a;
}
private Action getNext() {
while (iterator.hasNext()) {
Action a = iterator.next();
if (a instanceof ActionJump) {
ActionJump aJump = (ActionJump) a;
long ref = aJump.getTargetAddress();
if (target.getAddress() == ref) {
return aJump;
}
} else if (a instanceof ActionIf) {
ActionIf aIf = (ActionIf) a;
long ref = aIf.getTargetAddress();
if (target.getAddress() == ref) {
return aIf;
}
} else if (a instanceof ActionStore) {
ActionStore aStore = (ActionStore) a;
int storeSize = aStore.getStoreSize();
int idx = indexOf(a);
int idx2 = indexOf(target);
if (idx != -1 && idx2 == idx + storeSize) {
return a;
}
} else if (a instanceof GraphSourceItemContainer) {
GraphSourceItemContainer container = (GraphSourceItemContainer) a;
long ref = a.getAddress() + a.getTotalActionLength();
for (Long size : container.getContainerSizes()) {
ref += size;
if (target.getAddress() == ref) {
return a;
}
}
}
}
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public Iterable<ActionConstantPool> getConstantPools() {
return () -> new Iterator<ActionConstantPool>() {
private final Iterator<Action> iterator = ActionList.this.iterator();
private ActionConstantPool action = getNext();
@Override
public boolean hasNext() {
return action != null;
}
@Override
public ActionConstantPool next() {
ActionConstantPool a = action;
action = getNext();
return a;
}
private ActionConstantPool getNext() {
while (iterator.hasNext()) {
Action a = iterator.next();
if (a instanceof ActionConstantPool) {
return (ActionConstantPool) a;
}
}
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public Iterable<ActionPush> getPushes() {
return () -> new Iterator<ActionPush>() {
private final Iterator<Action> iterator = ActionList.this.iterator();
private ActionPush action = getNext();
@Override
public boolean hasNext() {
return action != null;
}
@Override
public ActionPush next() {
ActionPush a = action;
action = getNext();
return a;
}
private ActionPush getNext() {
while (iterator.hasNext()) {
Action a = iterator.next();
if (a instanceof ActionPush) {
return (ActionPush) a;
}
}
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int getConstantPoolIndexReferenceCount(int index) {
int count = 0;
for (Action action : this) {
if (action instanceof ActionPush) {
ActionPush push = (ActionPush) action;
for (Object value : push.values) {
if (value instanceof ConstantIndex) {
ConstantIndex constantIndex = (ConstantIndex) value;
if (constantIndex.index == index) {
count++;
}
}
}
}
}
return count;
}
public void inlineConstantPoolString(int index, String str) {
for (ActionPush push : getPushes()) {
for (int i = 0; i < push.values.size(); i++) {
Object value = push.values.get(i);
if (value instanceof ConstantIndex) {
ConstantIndex constantIndex = (ConstantIndex) value;
if (constantIndex.index == index) {
push.values.set(i, str);
}
}
}
}
}
public void removeNonReferencedConstantPoolItems() {
int maxSize = 0;
for (ActionConstantPool constantPool : getConstantPools()) {
maxSize = Math.max(maxSize, constantPool.constantPool.size());
}
boolean[] used = new boolean[maxSize];
for (ActionPush push : getPushes()) {
for (int i = 0; i < push.values.size(); i++) {
Object value = push.values.get(i);
if (value instanceof ConstantIndex) {
ConstantIndex constantIndex = (ConstantIndex) value;
int index = constantIndex.index;
if (index >= 0 && index < maxSize) {
used[index] = true;
}
}
}
}
int newIdx = 0;
for (int i = 0; i < maxSize; i++) {
if (used[i]) {
if (i != newIdx) {
for (ActionPush push : getPushes()) {
for (int j = 0; j < push.values.size(); j++) {
Object value = push.values.get(j);
if (value instanceof ConstantIndex) {
ConstantIndex constantIndex = (ConstantIndex) value;
if (constantIndex.index == i) {
constantIndex.index = newIdx;
}
}
}
}
}
newIdx++;
} else {
for (ActionConstantPool constantPool : getConstantPools()) {
if (constantPool.constantPool.size() > newIdx) {
constantPool.constantPool.remove(newIdx);
}
}
}
}
}
public void removeNops() {
for (int i = 0; i < size(); i++) {
if (get(i) instanceof ActionNop) {
removeAction(i);
}
}
}
public Action getByAddress(long address) {
int idx = getIndexByAddress(address);
return idx == -1 ? null : get(idx);
}
public int getIndexByAction(Action action) {
return getIndexByAddress(action.getAddress());
}
public int getIndexByAddress(long address) {
int min = 0;
int max = size() - 1;
while (max >= min) {
int mid = (min + max) / 2;
long midValue = get(mid).getAddress();
if (midValue == address) {
return mid;
} else if (midValue < address) {
min = mid + 1;
} else {
max = mid - 1;
}
}
return -1;
}
public GraphSourceItemContainer getContainer(int idx) {
Action action = get(idx);
int i = idx - 1;
while (i >= 0) {
Action a = get(i);
if (a instanceof GraphSourceItemContainer) {
List<Action> lastActions = getContainerLastActions(a);
Action lastAction = lastActions.get(lastActions.size() - 1);
if (lastAction.getAddress() >= action.getAddress()) {
return (GraphSourceItemContainer) a;
}
}
i--;
}
return null;
}
public int getContainerEndIndex(int idx) {
Action action = get(idx);
int i = idx - 1;
while (i >= 0) {
Action a = get(i);
if (a instanceof GraphSourceItemContainer) {
List<Action> lastActions = getContainerLastActions(a);
Action lastAction = lastActions.get(lastActions.size() - 1);
if (lastAction.getAddress() >= action.getAddress()) {
return getIndexByAddress(lastAction.getAddress());
}
}
i--;
}
return -1;
}
public List<Action> getUnreachableActions() {
int[] isReachable = getUnreachableActionsMap(-1, 0);
List<Action> unreachableActions = new ArrayList<>();
for (int i = 0; i < size(); i++) {
if (isReachable[i] == 0) {
unreachableActions.add(get(i));
}
}
if (unreachableActions.isEmpty()) {
unreachableActions = null;
}
return unreachableActions;
}
public List<Action> getUnreachableActions(int jumpIndex, int jumpTargetIndex) {
int[] isReachable = getUnreachableActionsMap(jumpIndex, jumpTargetIndex);
isReachable[jumpIndex] = 0;
List<Action> unreachableActions = new ArrayList<>();
for (int i = 0; i < size(); i++) {
if (isReachable[i] == 0) {
unreachableActions.add(get(i));
}
}
if (unreachableActions.isEmpty()) {
unreachableActions = null;
}
return unreachableActions;
}
private int[] getUnreachableActionsMap(int jumpIndex, int jumpTargetIndex) {
int size = size();
// one item for each action. 1 means reachable, 2 means reachable and processed
int[] isReachable = new int[size];
isReachable[0] = 1;
boolean modified = true;
while (modified) {
modified = false;
for (int i = 0; i < size; i++) {
Action action = get(i);
if (isReachable[i] == 1) {
isReachable[i] = 2;
modified = true;
if (i == jumpIndex) {
if (isReachable[jumpTargetIndex] == 0) {
isReachable[jumpTargetIndex] = 1;
}
continue;
}
if (!action.isExit() && !(action instanceof ActionJump) && i != size - 1) {
if (isReachable[i + 1] == 0) {
isReachable[i + 1] = 1;
}
}
if (action instanceof ActionJump) {
ActionJump aJump = (ActionJump) action;
long ref = aJump.getTargetAddress();
int targetIndex = getIndexByAddress(ref);
if (targetIndex != -1 && isReachable[targetIndex] == 0) {
isReachable[targetIndex] = 1;
}
} else if (action instanceof ActionIf) {
ActionIf aIf = (ActionIf) action;
long ref = aIf.getTargetAddress();
int targetIndex = getIndexByAddress(ref);
if (targetIndex != -1 && isReachable[targetIndex] == 0) {
isReachable[targetIndex] = 1;
}
} else if (action instanceof ActionStore) {
ActionStore aStore = (ActionStore) action;
int storeSize = aStore.getStoreSize();
if (size > i + storeSize) {
int targetIndex = i + storeSize;
if (isReachable[targetIndex] == 0) {
isReachable[targetIndex] = 1;
}
}
} else if (action instanceof GraphSourceItemContainer) {
GraphSourceItemContainer container = (GraphSourceItemContainer) action;
long ref = action.getAddress() + action.getTotalActionLength();
for (Long containerSize : container.getContainerSizes()) {
ref += containerSize;
int targetIndex = getIndexByAddress(ref);
if (targetIndex != -1 && isReachable[targetIndex] == 0) {
isReachable[targetIndex] = 1;
}
}
}
}
}
}
return isReachable;
}
public void combinePushes() {
for (int i = 0; i < size() - 1; i++) {
Action action = get(i);
Action action2 = get(i + 1);
if (action instanceof ActionPush && action2 instanceof ActionPush) {
if (!getReferencesFor(action2).hasNext()) {
ActionPush push = (ActionPush) action;
ActionPush push2 = (ActionPush) action2;
if (!(push.constantPool != null && push2.constantPool != null && push.constantPool != push2.constantPool)) {
ActionPush newPush = new ActionPush(0);
newPush.constantPool = push.constantPool == null ? push2.constantPool : push.constantPool;
newPush.values.clear();
newPush.values.addAll(push.values);
newPush.values.addAll(push2.values);
addAction(i + 1, newPush);
removeAction(i + 2);
removeAction(i);
i--;
}
}
}
}
}
public void expandPushes() {
for (int i = 0; i < size(); i++) {
Action action = get(i);
if (action instanceof ActionPush) {
ActionPush push = (ActionPush) action;
if (push.values.size() > 1) {
int j = 0;
for (Object value : push.values) {
j++;
ActionPush newPush = new ActionPush(value);
newPush.constantPool = push.constantPool;
addAction(i + j, newPush);
}
removeAction(i);
i += j - 1;
}
}
}
}
public void saveToFile(String fileName) {
File file = new File(fileName);
try (FileTextWriter writer = new FileTextWriter(Configuration.getCodeFormatting(), new FileOutputStream(file))) {
Action.actionsToString(new ArrayList<>(), 0, this, SWF.DEFAULT_VERSION, ScriptExportMode.PCODE, writer);
} catch (IOException ex) {
Logger.getLogger(ActionList.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public String toString() {
HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false);
actionsToString(new ArrayList<>(), 0, this, SWF.DEFAULT_VERSION, ScriptExportMode.PCODE, writer);
return writer.toString();
}
public String toSource() {
try {
HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false);
actionsToSource(null, this, "", writer);
return writer.toString();
} catch (InterruptedException ex) {
Logger.getLogger(ActionList.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}