/* * 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.fastactionlist; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.ActionList; import com.jpexs.decompiler.flash.action.special.ActionStore; import com.jpexs.decompiler.flash.action.special.ActionUnknown; 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.graph.GraphSourceItemContainer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * * @author JPEXS */ public class FastActionList implements Collection<ActionItem> { private int size; private ActionItem firstItem; private final Map<Action, ActionItem> actionItemMap; private final Set<ActionItem> actionItemSet; public FastActionList(ActionList actions) { actionItemMap = new HashMap<>(actions.size()); actionItemSet = new HashSet<>(actions.size()); for (Action action : actions) { insertItemAfter(null, action); } size = actions.size(); getContainerLastActions(actions, actionItemMap); getJumps(actions, actionItemMap); } public final ActionItem insertItemBefore(ActionItem item, Action action) { ActionItem newItem = new ActionItem(action); return insertItemBefore(item, newItem); } public final ActionItem insertItemAfter(ActionItem item, Action action) { ActionItem newItem = new ActionItem(action); return insertItemAfter(item, newItem); } public final ActionItem insertItemBefore(ActionItem item, ActionItem newItem) { insertItemAfter(item.prev, newItem); if (item == firstItem) { firstItem = newItem; } return newItem; } public final ActionItem insertItemAfter(ActionItem item, ActionItem newItem) { if (item == null && firstItem == null) { firstItem = newItem; newItem.next = newItem; newItem.prev = newItem; } else { if (item == null) { // insert to the end item = firstItem.prev; } ActionItem oldNext = item.next; newItem.prev = item; newItem.next = oldNext; item.next = newItem; oldNext.prev = newItem; } size++; actionItemMap.put(newItem.action, newItem); actionItemSet.add(newItem); return newItem; } public ActionItem removeItem(ActionItem item) { ActionItem next = null; if (item == firstItem) { if (item.next == item) { // there is only 1 item firstItem = null; } else { next = item.next; firstItem = next; next.prev = item.prev; item.prev.next = next; } } else { next = item.next; item.prev.next = next; next.prev = item.prev; } size--; actionItemMap.remove(item.action); actionItemSet.remove(item); item.removeJumpTarget(); item.removeContainerLastActions(); if (item.jumpsHere != null) { for (ActionItem item1 : new ArrayList<>(item.jumpsHere)) { item1.setJumpTarget(item.next); } } if (item.lastActionOf != null) { for (ActionItem item1 : new ArrayList<>(item.lastActionOf)) { item1.replaceContainerLastAction(item, item.prev); } } return next; } public void removeItem(int index, int count) { FastActionListIterator iterator = new FastActionListIterator(this, index); for (int i = 0; i < count; i++) { iterator.next(); iterator.remove(); } } public ActionItem get(int index) { FastActionListIterator iterator = new FastActionListIterator(this, index); return iterator.next(); } public void replaceJumpTargets(ActionItem target, ActionItem newTarget) { if (target.jumpsHere != null) { for (ActionItem item : new ArrayList<>(target.jumpsHere)) { item.setJumpTarget(newTarget); } } } private void getContainerLastActions(ActionList actions, Map<Action, ActionItem> actionItemMap) { ActionItem item = firstItem; if (item == null) { return; } do { Action action = item.action; if (action instanceof GraphSourceItemContainer) { item.setContainerLastActions(getContainerLastActions(actions, action, actionItemMap)); } item = item.next; } while (item != firstItem); } private List<ActionItem> getContainerLastActions(ActionList actions, Action action, Map<Action, ActionItem> actionItemMap) { GraphSourceItemContainer container = (GraphSourceItemContainer) action; List<Long> sizes = container.getContainerSizes(); long endAddress = action.getAddress() + container.getHeaderSize(); List<ActionItem> lasts = new ArrayList<>(sizes.size()); for (long size : sizes) { endAddress += size; long lastActionAddress = getNearAddress(actions, endAddress - 1, false); Action lastAction = null; if (lastActionAddress != -1) { lastAction = actions.getByAddress(lastActionAddress); } if (lastAction != null) { lasts.add(actionItemMap.get(lastAction)); } else { lasts.add(null); } } return lasts; } private long getNearAddress(ActionList actions, long address, boolean next) { int min = 0; int max = actions.size() - 1; while (max >= min) { int mid = (min + max) / 2; long midValue = actions.get(mid).getAddress(); if (midValue == address) { return address; } else if (midValue < address) { min = mid + 1; } else { max = mid - 1; } } return next ? (min < actions.size() ? actions.get(min).getAddress() : -1) : (max >= 0 ? actions.get(max).getAddress() : -1); } private void getJumps(ActionList actions, Map<Action, ActionItem> actionItemMap) { ActionItem item = firstItem; if (item == null) { return; } do { Action action = item.action; long target = -1; if (action instanceof ActionIf) { target = ((ActionIf) action).getTargetAddress(); } else if (action instanceof ActionJump) { target = ((ActionJump) action).getTargetAddress(); } else if (action instanceof ActionStore) { ActionStore aStore = (ActionStore) action; int storeSize = aStore.getStoreSize(); // skip storeSize + 1 actions (+1 is the current action) Action targetAction = action; for (int i = 0; i <= storeSize; i++) { long address = targetAction.getAddress() + targetAction.getTotalActionLength(); targetAction = actions.getByAddress(address); if (targetAction == null) { break; } } item.setJumpTarget(actionItemMap.get(targetAction)); } if (target >= 0) { Action targetAction = actions.getByAddress(target); item.setJumpTarget(actionItemMap.get(targetAction)); } item = item.next; } while (item != firstItem); } private void updateActionAddressesAndLengths() { ActionItem item = firstItem; if (item == null) { return; } long address = item.action.getAddress(); do { Action action = item.action; action.setAddress(address); action.updateLength(); address += action.getTotalActionLength(); item = item.next; } while (item != firstItem); } private void updateJumps() { ActionItem item = firstItem; if (item == null) { return; } long endAddress = item.prev.action.getAddress(); do { Action action = item.action; if (action instanceof ActionIf) { ActionIf aIf = (ActionIf) action; Action target = item.getJumpTargetAction(); long offset; if (target != null) { offset = target.getAddress() - action.getAddress() - action.getTotalActionLength(); } else { offset = endAddress - action.getAddress() - action.getTotalActionLength(); } aIf.setJumpOffset((int) offset); } else if (action instanceof ActionJump) { ActionJump aJump = (ActionJump) action; Action target = item.getJumpTargetAction(); long offset; if (target != null) { offset = target.getAddress() - action.getAddress() - action.getTotalActionLength(); } else { offset = endAddress - action.getAddress() - action.getTotalActionLength(); } aJump.setJumpOffset((int) offset); } item = item.next; } while (item != firstItem); } private void updateActionStores() { ActionItem item = firstItem; if (item == null) { return; } do { Action action = item.action; if (action instanceof ActionStore) { ActionStore aStore = (ActionStore) action; Action nextActionAfterStore = item.getJumpTargetAction(); ActionItem item1 = item; List<Action> store = new ArrayList<>(); while (true) { item1 = item1.next; if (item1 == firstItem || item1.action == nextActionAfterStore) { break; } store.add(item1.action); } aStore.setStore(store); } item = item.next; } while (item != firstItem); } private void updateContainerSizes() { ActionItem item = firstItem; if (item == null) { return; } do { Action action = item.action; if (action instanceof GraphSourceItemContainer) { GraphSourceItemContainer container = (GraphSourceItemContainer) action; List<ActionItem> lastActions = item.getContainerLastActions(); long startAddress = action.getAddress() + container.getHeaderSize(); for (int j = 0; j < lastActions.size(); j++) { Action lastAction = lastActions.get(j).action; int length = (int) (lastAction.getAddress() + lastAction.getTotalActionLength() - startAddress); container.setContainerSize(j, length); startAddress += length; } } item = item.next; } while (item != firstItem); } public ActionItem getContainer(ActionItem item) { while (!(item.action instanceof GraphSourceItemContainer) && item != firstItem) { item = item.prev; } if (item.action instanceof GraphSourceItemContainer) { return item; } return null; } public void expandPushes() { ActionItem item = firstItem; if (item == null) { return; } do { Action action = item.action; if (action instanceof ActionPush) { ActionPush push = (ActionPush) action; if (push.values.size() > 1) { for (int i = 1; i < push.values.size(); i++) { Object value = push.values.get(i); ActionPush newPush = new ActionPush(value); newPush.constantPool = push.constantPool; insertItemAfter(item, newPush); item = item.next; } Object obj = push.values.get(0); push.values.clear(); push.values.add(obj); } } item = item.next; } while (item != firstItem); } public void removeUnknownActions() { ActionItem item = firstItem; if (item == null) { return; } do { Action action = item.action; if (action instanceof ActionUnknown) { item = removeItem(item); continue; } item = item.next; } while (item != firstItem); } public void removeZeroJumps() { ActionItem item = firstItem; if (item == null) { return; } do { Action action = item.action; if (action instanceof ActionJump) { if (item.getJumpTarget() == item.next && item.getJumpTarget() != firstItem) { item = removeItem(item); continue; } } item = item.next; } while (item != firstItem); } public void removeUnreachableActions() { ActionItem item = firstItem; if (item == null) { return; } updateReachableFlags(null, null); do { if (item.reachable == 0) { item = removeItem(item); continue; } item = item.next; } while (item != firstItem); } public void removeIncludedActions() { ActionItem item = firstItem; if (item == null) { return; } do { if (!item.excluded) { item = removeItem(item); continue; } item = item.next; } while (item != firstItem); } public int getUnreachableActionCount(ActionItem jump, ActionItem jumpTarget) { ActionItem item = firstItem; if (item == null) { return 0; } updateReachableFlags(jump, jumpTarget); jump.reachable = 0; int count = 0; do { if (item.reachable == 0) { count++; } item = item.next; } while (item != firstItem); return count; } private void clearReachableFlags() { ActionItem item = firstItem; if (item == null) { return; } do { item.reachable = 0; item = item.next; } while (item != firstItem); } public void setExcludedFlags(boolean value) { ActionItem item = firstItem; if (item == null) { return; } do { item.excluded = value; item = item.next; } while (item != firstItem); } private void updateReachableFlags(ActionItem jump, ActionItem jumpTarget) { if (firstItem == null) { return; } clearReachableFlags(); firstItem.reachable = 1; ActionItem firstItem2 = firstItem; boolean modified = true; while (modified) { modified = false; ActionItem item = firstItem2; do { ActionItem next = item.next; //ActionItem alternativeNext = null; Action action = item.action; if (item.reachable == 1) { item.reachable = 2; modified = true; if (item == firstItem2) { firstItem2 = next; } if (item == jump) { if (jumpTarget.reachable == 0) { jumpTarget.reachable = 1; //alternativeNext = jumpTarget; } } else { if (!action.isExit() && !(action instanceof ActionJump)) { if (next.reachable == 0) { next.reachable = 1; } } if (action instanceof GraphSourceItemContainer) { for (ActionItem lastActionItem : item.getContainerLastActions()) { if (lastActionItem != null && lastActionItem.next != null && lastActionItem.next.reachable == 0) { lastActionItem.next.reachable = 1; //alternativeNext = lastActionItem.next; } } } ActionItem target = item.getJumpTarget(); if (target != null) { if (target.reachable == 0) { target.reachable = 1; //alternativeNext = target; } } } } //item = alternativeNext == null || next.reachable == 1 ? next : alternativeNext; item = next; } while (item != firstItem); } } public ActionList updateActions() { List<Action> resultList = new ArrayList<>(size); ActionItem item = firstItem; if (item == null) { return new ActionList(resultList); } do { resultList.add(item.action); item = item.next; } while (item != firstItem); ActionList result = new ActionList(resultList); updateActionAddressesAndLengths(); updateJumps(); updateActionStores(); updateContainerSizes(); return result; } public ActionItem first() { return firstItem; } public ActionItem last() { return firstItem == null ? null : firstItem.prev; } public ActionList toActionList() { return updateActions(); } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public boolean contains(Object o) { if (o instanceof ActionItem) { return actionItemSet.contains(o); } else if (o instanceof Action) { return actionItemMap.containsKey((Action) o); } return false; } @Override public FastActionListIterator iterator() { return new FastActionListIterator(this); } @Override public Object[] toArray() { Object[] result = new Object[size]; ActionItem item = firstItem; if (item == null) { return result; } int i = 0; do { result[i] = item.action; item = item.next; i++; } while (item != firstItem); return null; } @Override @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length != size) { a = (T[]) new ActionItem[size]; } ActionItem item = firstItem; if (item == null) { return a; } int i = 0; do { a[i] = (T) item; item = item.next; i++; } while (item != firstItem); return null; } @Override public boolean add(ActionItem e) { insertItemAfter(null, e); return true; } @Override public boolean remove(Object o) { ActionItem item = null; if (o instanceof ActionItem) { item = (ActionItem) o; } else if (o instanceof Action) { item = actionItemMap.get((Action) o); } if (item == null) { return false; } removeItem(item); return true; } @Override public boolean containsAll(Collection<?> c) { for (Object c1 : c) { if (!contains(c1)) { return false; } } return true; } @Override public boolean addAll(Collection<? extends ActionItem> c) { for (ActionItem c1 : c) { insertItemAfter(null, c1); } return true; } @Override public boolean removeAll(Collection<?> c) { boolean result = false; for (Object c1 : c) { result |= remove(c1); } return result; } @Override public boolean retainAll(Collection<?> c) { ActionItem item = firstItem; if (item == null) { return false; } boolean modified = false; do { if (!c.contains(item)) { item = removeItem(item); modified = true; continue; } item = item.next; } while (item != firstItem); return modified; } @Override public void clear() { firstItem = null; size = 0; } }