/* * 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.graph; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.helpers.Highlighting; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author JPEXS */ public class Graph { public List heads; protected GraphSource code; public Graph(GraphSource code, List alternateEntries) { this.code = code; heads = makeGraph(code, new ArrayList(), alternateEntries); for (GraphPart head : heads) { fixGraph(head); makeMulti(head, new ArrayList()); } } protected static void populateParts(GraphPart part, List allParts) { if (allParts.contains(part)) { return; } allParts.add(part); for (GraphPart p : part.nextParts) { populateParts(p, allParts); } } private void fixGraph(GraphPart part) { while (fixGraphOnce(part, new ArrayList(), false)) { } } private boolean fixGraphOnce(GraphPart part, List visited, boolean doChildren) { if (visited.contains(part)) { return false; } visited.add(part); boolean fixed = false; int i = 0; GraphPath lastpref = null; boolean modify = true; int prvni = -1; if (!doChildren) { List uniqueRefs = new ArrayList<>(); for (GraphPart r : part.refs) { if (!uniqueRefs.contains(r)) { uniqueRefs.add(r); } } loopi: for (; i <= part.path.length(); i++) { lastpref = null; int pos = -1; for (GraphPart r : uniqueRefs) { pos++; if (r.path.rootName.equals("e") && !part.path.rootName.equals("e")) { continue; } if (part.leadsTo(code, r, new ArrayList())) { modify = false; continue; } prvni = pos; if (i > r.path.length()) { i--; break loopi; } if (lastpref == null) { lastpref = r.path.parent(i); } else { if (!r.path.startsWith(lastpref)) { i--; break loopi; } } } } if (i > part.path.length()) { i = part.path.length(); } if (modify && ((uniqueRefs.size() > 1) && (prvni >= 0))) { GraphPath prvniUniq = uniqueRefs.get(prvni).path; GraphPath newpath = prvniUniq.parent(i); if (!part.path.equals(newpath)) { if (part.path.startsWith(newpath) && ((newpath.length() == prvniUniq.length()) || (prvniUniq.getKey(newpath.length()) == part.path.getKey(newpath.length())))) { GraphPath origPath = part.path; GraphPart p = part; part.path = newpath; while (p.nextParts.size() == 1) { p = p.nextParts.get(0); if (!p.path.equals(origPath)) { break; } p.path = newpath; } fixGraphOnce(part, new ArrayList(), true); fixed = true; } } } } else { if (!fixed) { if (part.nextParts.size() == 1) { if (!(part.path.rootName.equals("e") && (!part.nextParts.get(0).path.rootName.equals("e")))) { if (part.nextParts.get(0).path.length() > part.path.length()) { part.nextParts.get(0).path = part.path; fixed = true; } } } if (part.nextParts.size() > 1) { for (int j = 0; j < part.nextParts.size(); j++) { GraphPart npart = part.nextParts.get(j); if (npart.path.length() > part.path.length() + 1) { npart.path = part.path.sub(j, part.end); fixed = true; } } } } } if (part.nextParts.size() == 2) { if (part.nextParts.get(1).leadsTo(code, part.nextParts.get(0), new ArrayList() /*visited*/)) { fixGraphOnce(part.nextParts.get(1), visited, doChildren); fixGraphOnce(part.nextParts.get(0), visited, doChildren); } else { fixGraphOnce(part.nextParts.get(0), visited, doChildren); fixGraphOnce(part.nextParts.get(1), visited, doChildren); } } else { for (int j = part.nextParts.size() - 1; j >= 0; j--) { GraphPart p = part.nextParts.get(j); fixGraphOnce(p, visited, doChildren); } } return fixed; } private void makeMulti(GraphPart part, List visited) { if (true) { return; } if (visited.contains(part)) { return; } visited.add(part); GraphPart p = part; List multiList = new ArrayList<>(); multiList.add(p); while ((p.nextParts.size() == 1) && (p.nextParts.get(0).refs.size() == 1)) { p = p.nextParts.get(0); multiList.add(p); } if (multiList.size() > 1) { GraphPartMulti gpm = new GraphPartMulti(multiList); gpm.refs = part.refs; GraphPart lastPart = multiList.get(multiList.size() - 1); gpm.nextParts = lastPart.nextParts; for (GraphPart next : gpm.nextParts) { int index = next.refs.indexOf(lastPart); if (index == -1) { continue; } next.refs.remove(lastPart); next.refs.add(index, gpm); } for (GraphPart parent : part.refs) { if (parent.start == -1) { continue; } int index = parent.nextParts.indexOf(part); if (index == -1) { continue; } parent.nextParts.remove(part); parent.nextParts.add(index, gpm); } } for (int i = 0; i < part.nextParts.size(); i++) { makeMulti(part.nextParts.get(i), visited); } } public GraphPart deepCopy(GraphPart part, List visited, List copies) { if (visited == null) { visited = new ArrayList<>(); } if (copies == null) { copies = new ArrayList<>(); } if (visited.contains(part)) { return copies.get(visited.indexOf(part)); } visited.add(part); GraphPart copy = new GraphPart(part.start, part.end); copy.path = part.path; copies.add(copy); copy.nextParts = new ArrayList<>(); for (int i = 0; i < part.nextParts.size(); i++) { copy.nextParts.add(deepCopy(part.nextParts.get(i), visited, copies)); } for (int i = 0; i < part.refs.size(); i++) { copy.refs.add(deepCopy(part.refs.get(i), visited, copies)); } return copy; } public void resetGraph(GraphPart part, List visited) { if (visited.contains(part)) { return; } visited.add(part); int pos = 0; for (GraphPart p : part.nextParts) { if (!visited.contains(p)) { p.path = part.path.sub(pos, p.end); } resetGraph(p, visited); pos++; } } private void getReachableParts(GraphPart part, List ret, List loops) { getReachableParts(part, ret, loops, true); } private void getReachableParts(GraphPart part, List ret, List loops, boolean first) { if (first) { for (Loop l : loops) { l.reachableMark = 0; } } Loop currentLoop = null; for (Loop l : loops) { if ((l.phase == 1) || (l.reachableMark == 1)) { if (l.loopContinue == part) { return; } if (l.loopBreak == part) { return; } if (l.loopPreContinue == part) { return; } } if (l.reachableMark == 0) { if (l.loopContinue == part) { l.reachableMark = 1; currentLoop = l; } } } List newparts = new ArrayList<>(); loopnext: for (GraphPart next : part.nextParts) { for (Loop l : loops) { if ((l.phase == 1) || (l.reachableMark == 1)) { if (l.loopContinue == next) { continue loopnext; } if (l.loopBreak == next) { continue loopnext; } if (l.loopPreContinue == next) { continue loopnext; } } } if (!ret.contains(next)) { newparts.add(next); } } ret.addAll(newparts); for (GraphPart next : newparts) { getReachableParts(next, ret, loops); } if (currentLoop != null) { if (currentLoop.loopBreak != null) { if (!ret.contains(currentLoop.loopBreak)) { ret.add(currentLoop.loopBreak); currentLoop.reachableMark = 2; getReachableParts(currentLoop.loopBreak, ret, loops); } } } } /* public GraphPart getNextCommonPart(GraphPart part, List loops) { return getNextCommonPart(part, new ArrayList(),loops); }*/ public GraphPart getNextCommonPart(GraphPart part, List loops) { return getCommonPart(part.nextParts, loops); } public GraphPart getCommonPart(List parts, List loops) { if (parts.isEmpty()) { return null; } List loopContinues = getLoopsContinues(loops); for (GraphPart p : parts) { if (loopContinues.contains(p)) { break; } boolean common = true; for (GraphPart q : parts) { if (q == p) { continue; } if (!q.leadsTo(code, p, loops)) { common = false; break; } } if (common) { return p; } } List> reachable = new ArrayList<>(); for (GraphPart p : parts) { List r1 = new ArrayList<>(); getReachableParts(p, r1, loops); r1.add(p); reachable.add(r1); } List first = reachable.get(0); for (GraphPart p : first) { /*if (ignored.contains(p)) { continue; }*/ boolean common = true; for (List r : reachable) { if (!r.contains(p)) { common = false; break; } } if (common) { return p; } } return null; } public GraphPart getNextNoJump(GraphPart part) { while (code.get(part.start).isJump()) { part = part.getSubParts().get(0).nextParts.get(0); } return part; } public static List translateViaGraph(List localData, String path, GraphSource code, List alternateEntries) { Graph g = new Graph(code, alternateEntries); return g.translate(localData); } public List translate(List localData) { try { List allParts = new ArrayList<>(); for (GraphPart head : heads) { populateParts(head, allParts); } Stack stack = new Stack<>(); List loops = new ArrayList<>(); getLoops(heads.get(0), loops, null); /*System.out.println(""); for (Loop el : loops) { System.out.println(el); } System.out.println("");*/ getPrecontinues(null, heads.get(0), loops, null); /*System.out.println(""); for (Loop el : loops) { System.out.println(el); } System.out.println("");*/ List ret = printGraph(new ArrayList(), localData, stack, allParts, null, heads.get(0), null, loops); processIfs(ret); finalProcessStack(stack, ret); finalProcessAll(ret, 0); return ret; } catch (StackOverflowError soe) { List ret = new ArrayList<>(); ret.add(new CommentItem("StackOverflowError")); Logger.getLogger(Graph.class.getName()).log(Level.SEVERE, "error during printGraph", soe); return ret; } } public void finalProcessStack(Stack stack, List output) { } private void finalProcessAll(List list, int level) { finalProcess(list, level); for (GraphTargetItem item : list) { if (item instanceof Block) { List> subs = ((Block) item).getSubs(); for (List sub : subs) { finalProcessAll(sub, level + 1); } } } } protected void finalProcess(List list, int level) { } private void processIfs(List list) { //if(true) return; for (int i = 0; i < list.size(); i++) { GraphTargetItem item = list.get(i); if (item instanceof Block) { List> subs = ((Block) item).getSubs(); for (List sub : subs) { processIfs(sub); } } if ((item instanceof LoopItem) && (item instanceof Block)) { List> subs = ((Block) item).getSubs(); for (List sub : subs) { processIfs(sub); checkContinueAtTheEnd(sub, ((LoopItem) item).loop); } } if (item instanceof IfItem) { IfItem ifi = (IfItem) item; List onTrue = ifi.onTrue; List onFalse = ifi.onFalse; if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) { if (onFalse.get(onFalse.size() - 1) instanceof ContinueItem) { if (((ContinueItem) onTrue.get(onTrue.size() - 1)).loopId == ((ContinueItem) onFalse.get(onFalse.size() - 1)).loopId) { onTrue.remove(onTrue.size() - 1); list.add(i + 1, onFalse.remove(onFalse.size() - 1)); } } } } if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { GraphTargetItem last = onTrue.get(onTrue.size() - 1); if ((last instanceof ExitItem) || (last instanceof ContinueItem) || (last instanceof BreakItem)) { list.addAll(i + 1, onFalse); onFalse.clear(); } } if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { if (onFalse.get(onFalse.size() - 1) instanceof ExitItem) { if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) { list.add(i + 1, onTrue.remove(onTrue.size() - 1)); } } } } } //Same continues in onTrue and onFalse gets continue on parent level } protected List getLoopsContinuesPreAndBreaks(List loops) { List ret = new ArrayList<>(); for (Loop l : loops) { if (l.loopContinue != null) { ret.add(l.loopContinue); } if (l.loopPreContinue != null) { ret.add(l.loopPreContinue); } if (l.loopBreak != null) { ret.add(l.loopBreak); } } return ret; } protected List getLoopsContinuesAndPre(List loops) { List ret = new ArrayList<>(); for (Loop l : loops) { if (l.loopContinue != null) { ret.add(l.loopContinue); } if (l.loopPreContinue != null) { ret.add(l.loopPreContinue); } } return ret; } protected List getLoopsContinues(List loops) { List ret = new ArrayList<>(); for (Loop l : loops) { if (l.loopContinue != null) { ret.add(l.loopContinue); } /*if (l.loopPreContinue != null) { ret.add(l.loopPreContinue); }*/ } return ret; } protected GraphTargetItem checkLoop(GraphPart part, List stopPart, List loops) { if (stopPart.contains(part)) { return null; } for (Loop l : loops) { if (l.loopContinue == part) { return (new ContinueItem(null, l.id)); } if (l.loopBreak == part) { return (new BreakItem(null, l.id)); } } return null; } private void checkContinueAtTheEnd(List commands, Loop loop) { if (!commands.isEmpty()) { int i = commands.size() - 1; for (; i >= 0; i--) { if (commands.get(i) instanceof ContinueItem) { continue; } if (commands.get(i) instanceof BreakItem) { continue; } break; } if (i < commands.size() - 1) { for (int k = i + 2; k < commands.size(); k++) { commands.remove(k); } } if (commands.get(commands.size() - 1) instanceof ContinueItem) { if (((ContinueItem) commands.get(commands.size() - 1)).loopId == loop.id) { commands.remove(commands.size() - 1); } } } } protected boolean isEmpty(List output) { if (output.isEmpty()) { return true; } if (output.size() == 1) { if (output.get(0) instanceof MarkItem) { return true; } } return false; } protected List check(GraphSource code, List localData, List allParts, Stack stack, GraphPart parent, GraphPart part, List stopPart, List loops, List output) { return null; } protected GraphPart checkPart(List localData, GraphPart part) { return part; } @SuppressWarnings("unchecked") protected GraphTargetItem translatePartGetStack(List localData, GraphPart part, Stack stack) { stack = (Stack) stack.clone(); translatePart(localData, part, stack); return stack.pop(); } protected List translatePart(List localData, GraphPart part, Stack stack) { List sub = part.getSubParts(); List ret = new ArrayList<>(); int end = 0; for (GraphPart p : sub) { if (p.end == -1) { p.end = code.size() - 1; } if (p.start == code.size()) { continue; } else if (p.end == code.size()) { p.end--; } end = p.end; int start = p.start; ret.addAll(code.translatePart(part, localData, stack, start, end)); } return ret; } private void markBranchEnd(List items) { if (!items.isEmpty()) { if (items.get(items.size() - 1) instanceof BreakItem) { return; } if (items.get(items.size() - 1) instanceof ContinueItem) { return; } if (items.get(items.size() - 1) instanceof ExitItem) { return; } } items.add(new MarkItem("finish")); } private static GraphTargetItem getLastNoEnd(List list) { if (list.isEmpty()) { return null; } if (list.get(list.size() - 1) instanceof ScriptEndItem) { if (list.size() >= 2) { return list.get(list.size() - 2); } return list.get(list.size() - 1); } return list.get(list.size() - 1); } private static void removeLastNoEnd(List list) { if (list.isEmpty()) { return; } if (list.get(list.size() - 1) instanceof ScriptEndItem) { if (list.size() >= 2) { list.remove(list.size() - 2); } return; } list.remove(list.size() - 1); } protected List printGraph(List visited, List localData, Stack stack, List allParts, GraphPart parent, GraphPart part, List stopPart, List loops) { return printGraph(visited, localData, stack, allParts, parent, part, stopPart, loops, null); } protected GraphTargetItem checkLoop(LoopItem loopItem, List localData, List loops) { return loopItem; } private void getPrecontinues(GraphPart parent, GraphPart part, List loops, List stopPart) { clearLoops(loops); getPrecontinues(parent, part, loops, stopPart, 0, new ArrayList()); clearLoops(loops); } private void getPrecontinues(GraphPart parent, GraphPart part, List loops, List stopPart, int level, List visited) { boolean debugMode = false; if (stopPart == null) { stopPart = new ArrayList<>(); } if (debugMode) { System.err.println("preco " + part); } if (stopPart.contains(part)) { return; } for (Loop el : loops) { if ((el.phase == 2) && (el.loopContinue == part)) { throw new RuntimeException("Phase 2 visited again:" + el); //return; } if (el.phase != 1) { if (debugMode) { //System.err.println("ignoring "+el); } continue; } if (el.loopContinue == part) { return; } if (el.loopPreContinue == part) { return; } if (el.loopBreak == part) { return; } } if (visited.contains(part)) { //(part.level > level) { List nextList = new ArrayList<>(); populateParts(part, nextList); Loop nearestLoop = null; loopn: for (GraphPart n : nextList) { for (Loop l : loops) { if (l.loopContinue == n) { nearestLoop = l; break loopn; } } } if ((nearestLoop != null) && (nearestLoop.loopContinue != part)) {// && (nearestLoop.loopBreak != null)) { if (nearestLoop.phase == 1) { if ((nearestLoop.loopPreContinue == null)) {// || (nearestLoop.loopPreContinue.leadsTo(code, part, getLoopsContinues(loops)))) { nearestLoop.loopPreContinue = part; return; } } } } if (!visited.contains(part)) { visited.add(part); } List loopContinues = getLoopsContinues(loops); boolean isLoop = false; Loop currentLoop = null; for (Loop el : loops) { if ((el.phase == 0) && (el.loopContinue == part)) { isLoop = true; currentLoop = el; el.phase = 1; break; } } if (part.nextParts.size() == 2) { GraphPart next = getNextCommonPart(part, loops);//part.getNextPartPath(new ArrayList()); List stopParts2 = new ArrayList<>(stopPart); if (next != null) { stopParts2.add(next); } if (next != part.nextParts.get(0)) { getPrecontinues(part, part.nextParts.get(0), loops, next == null ? stopPart : stopParts2, level + 1, visited); } if (next != part.nextParts.get(1)) { getPrecontinues(part, part.nextParts.get(1), loops, next == null ? stopPart : stopParts2, level + 1, visited); } if (next != null) { getPrecontinues(part, next, loops, stopPart, level, visited); } } if (part.nextParts.size() > 2) { GraphPart next = getNextCommonPart(part, loops); List vis = new ArrayList<>(); for (GraphPart p : part.nextParts) { if (vis.contains(p)) { continue; } List stopPart2 = new ArrayList<>(stopPart); if (next != null) { stopPart2.add(next); } for (GraphPart p2 : part.nextParts) { if (p2 == p) { continue; } List p12 = new ArrayList<>(); p12.add(p); p12.add(p2); GraphPart n = getCommonPart(p12, loops); if (!stopPart2.contains(n)) { stopPart2.add(n); } if (!stopPart2.contains(p2)) { stopPart2.add(p2); } } if (next != p) { getPrecontinues(part, p, loops, stopPart2, level + 1, visited); vis.add(p); } } if (next != null) { getPrecontinues(part, next, loops, stopPart, level, visited); } } if (part.nextParts.size() == 1) { getPrecontinues(part, part.nextParts.get(0), loops, stopPart, level, visited); } if (isLoop) { if (currentLoop.loopBreak != null) { currentLoop.phase = 2; getPrecontinues(null, currentLoop.loopBreak, loops, stopPart, level, visited); } } } private void clearLoops(List loops) { for (Loop l : loops) { l.phase = 0; } } private void getLoops(GraphPart part, List loops, List stopPart) { clearLoops(loops); getLoops(part, loops, stopPart, true,1); clearLoops(loops); } private void getLoops(GraphPart part, List loops, List stopPart, boolean first,int level) { boolean debugMode = false; if (stopPart == null) { stopPart = new ArrayList<>(); } if (part == null) { return; } if (debugMode) { System.err.println("getloops: " + part); } List loopContinues = getLoopsContinues(loops); Loop lastP1 = null; for (Loop el : loops) { if ((el.phase == 1) && el.loopBreak == null) { //break not found yet if (el.loopContinue != part) { lastP1 = el; } } } if (lastP1 != null) { if (lastP1.breakCandidates.contains(part)) { lastP1.breakCandidates.add(part); lastP1.breakCandidatesLevels.add(level); return; } else { List loopContinues2 = new ArrayList<>(loopContinues); loopContinues2.remove(lastP1.loopContinue); List loops2 = new ArrayList<>(loops); loops2.remove(lastP1); if (!part.leadsTo(code, lastP1.loopContinue, loops2)) { lastP1.breakCandidates.add(part); lastP1.breakCandidatesLevels.add(level); return; } } } for (Loop el : loops) { if (el.loopContinue == part) { return; } } if (stopPart.contains(part)) { return; } part.level = level; boolean isLoop = part.leadsTo(code, part, loops); Loop currentLoop = null; if (isLoop) { currentLoop = new Loop(loops.size(), part, null); currentLoop.phase = 1; loops.add(currentLoop); loopContinues.add(part); } if (part.nextParts.size() == 2) { GraphPart next = getNextCommonPart(part, loops);//part.getNextPartPath(loopContinues); List stopPart2 = new ArrayList<>(stopPart); if (next != null) { stopPart2.add(next); } if (next != part.nextParts.get(0)) { getLoops(part.nextParts.get(0), loops, stopPart2, false,level+1); } if (next != part.nextParts.get(1)) { getLoops(part.nextParts.get(1), loops, stopPart2, false,level+1); } if (next != null) { getLoops(next, loops, stopPart, false,level); } } if (part.nextParts.size() > 2) { GraphPart next = getNextCommonPart(part, loops); for (GraphPart p : part.nextParts) { List stopPart2 = new ArrayList<>(stopPart); if (next != null) { stopPart2.add(next); } for (GraphPart p2 : part.nextParts) { if (p2 == p) { continue; } if (!stopPart2.contains(p2)) { stopPart2.add(p2); } } if (next != p) { getLoops(p, loops, stopPart2, false,level+1); } } if (next != null) { getLoops(next, loops, stopPart, false,level); } } if (part.nextParts.size() == 1) { getLoops(part.nextParts.get(0), loops, stopPart, false,level); } if (isLoop) { GraphPart found; List backupCandidates = new ArrayList<>(); List spcheck = new ArrayList<>(stopPart); if (!spcheck.isEmpty()) { spcheck.remove(spcheck.size() - 1); } for (int i = currentLoop.breakCandidates.size() - 1; i >= 0; i--) { if (spcheck.contains(currentLoop.breakCandidates.get(i))) { currentLoop.breakCandidatesLevels.remove(i); backupCandidates.add(currentLoop.breakCandidates.remove(i)); } } Set removed = new HashSet<>(); /* Set newcommon=new HashSet<>(); for (GraphPart cand : currentLoop.breakCandidates) { for (GraphPart cand2 : currentLoop.breakCandidates) { if(cand==cand2){ continue; } List c=new ArrayList<>(); c.add(cand); c.add(cand2); GraphPart common=getCommonPart(c, loops); if(common!=null){ if(!currentLoop.breakCandidates.contains(common)){ newcommon.add(common); } } } } currentLoop.breakCandidates.addAll(newcommon);*/ do { found = null; loopcand: for (GraphPart cand : currentLoop.breakCandidates) { for (GraphPart cand2 : currentLoop.breakCandidates) { if (cand.leadsTo(code, cand2, loops)) { /*if (cand.path.equals(cand2.path)) { found = cand2; } else {*/ int lev1=Integer.MAX_VALUE; int lev2=Integer.MAX_VALUE; for(int i=0;i count = new HashMap<>(); GraphPart winner = null; int winnerCount = 0; boolean winnerBreakCandidate = true; for (GraphPart cand : currentLoop.breakCandidates) { if (!count.containsKey(cand)) { count.put(cand, 0); } count.put(cand, count.get(cand) + 1); boolean otherBreakCandidate = false; for (Loop el : loops) { if (el == currentLoop) { continue; } if (el.breakCandidates.contains(cand)) { otherBreakCandidate = true; break; } } /*if(winnerBreakCandidate && !otherBreakCandidate){ winnerCount = count.get(cand); winner = cand; winnerBreakCandidate=otherBreakCandidate; }else if(!winnerBreakCandidate && otherBreakCandidate){ */ if (otherBreakCandidate) { } else if (count.get(cand) > winnerCount) { winnerCount = count.get(cand); winner = cand; } else if (count.get(cand) == winnerCount) { if (cand.path.length() < winner.path.length()) { winner = cand; } } } if (winner == null) { if (!backupCandidates.isEmpty()) { winner = backupCandidates.get(backupCandidates.size() - 1); } } for (GraphPart cand : currentLoop.breakCandidates) { if (cand != winner) { removed.add(cand); } } currentLoop.loopBreak = winner; currentLoop.phase = 2; boolean start = false; for (int l = 0; l < loops.size(); l++) { Loop el = loops.get(l); if (start) { el.phase = 1; } if (el == currentLoop) { start = true; } } for (GraphPart r : removed) { getLoops(r, loops, stopPart, false,1/*FIXME?*/); } start = false; for (int l = 0; l < loops.size(); l++) { Loop el = loops.get(l); if (el == currentLoop) { start = true; } if (start) { el.phase = 2; } } //currentLoop.phase = 2; getLoops(currentLoop.loopBreak, loops, stopPart, false,level); } } protected List printGraph(List visited, List localData, Stack stack, List allParts, GraphPart parent, GraphPart part, List stopPart, List loops, List ret) { if (stopPart == null) { stopPart = new ArrayList<>(); } if (visited.contains(part)) { //return new ArrayList(); } else { visited.add(part); } if (ret == null) { ret = new ArrayList<>(); } //try { boolean debugMode = false; if (debugMode) { System.err.println("PART " + part); } /*while (((part != null) && (part.getHeight() == 1)) && (code.size() > part.start) && (code.get(part.start).isJump())) { //Parts with only jump in it gets ignored if (part == stopPart) { return ret; } GraphTargetItem lop = checkLoop(part.nextParts.get(0), stopPart, loops); if (lop == null) { part = part.nextParts.get(0); } else { break; } }*/ if (part == null) { return ret; } part = checkPart(localData, part); if (part == null) { return ret; } if (part.ignored) { return ret; } /* if ((parent != null) && (part.path.length() < parent.path.length())) { boolean can = true; for (Loop el : loops) { if (el.loopContinue == part) { can = false; break; } if (el.loopBreak == part) { can = false; break; } if (el.breakCandidates.containsKey(part)) { can = false; break; } } if (can) { if ((part != stopPart) && (part.refs.size() > 1)) { List nextList = new ArrayList<>(); populateParts(part, nextList); Loop nearestLoop = null; loopn: for (GraphPart n : nextList) { for (Loop l : loops) { if (l.loopContinue == n) { nearestLoop = l; break loopn; } } } if ((nearestLoop != null)) {// && (nearestLoop.loopBreak != null)) { List finalCommands = printGraph(visited, localData, stack, allParts, null, part, nearestLoop.loopContinue, loops); nearestLoop.loopContinue = part; forFinalCommands.put(nearestLoop, finalCommands); ContinueItem cti = new ContinueItem(null, nearestLoop.id); ret.add(cti); //ret.add(new CommentItem("CONTTEST")); return ret; } } } } */ List loopContinues = getLoopsContinues(loops); boolean isLoop = false; //part.leadsTo(code, part, loopContinues); Loop currentLoop = null; for (Loop el : loops) { if ((el.loopContinue == part) && (el.phase == 0)) { currentLoop = el; currentLoop.phase = 1; isLoop = true; break; } } /*Loop currentLoop = null; if (isLoop) { currentLoop = new Loop(loops.size(), part, null); loops.add(currentLoop); loopContinues.add(part); }*/ if (debugMode) { System.err.println("loopsize:" + loops.size()); } for (int l = loops.size() - 1; l >= 0; l--) { Loop el = loops.get(l); if (el == currentLoop) { if (debugMode) { System.err.println("ignoring current loop " + el); } continue; } if (el.phase != 1) { if (debugMode) { //System.err.println("ignoring loop "+el); } continue; } if (el.loopBreak == part) { ret.add(new BreakItem(null, el.id)); return ret; } if (el.loopPreContinue == part) { ret.add(new ContinueItem(null, el.id)); return ret; } if (el.loopContinue == part) { ret.add(new ContinueItem(null, el.id)); return ret; } } if (stopPart.contains(part)) { return ret; } if ((part != null) && (code.size() <= part.start)) { ret.add(new ScriptEndItem()); return ret; } if (currentLoop != null) { currentLoop.used = true; } List currentRet = ret; UniversalLoopItem loopItem = null; if (isLoop) { loopItem = new UniversalLoopItem(null, currentLoop); //loopItem.commands=printGraph(visited, localData, stack, allParts, parent, part, stopPart, loops); currentRet.add(loopItem); loopItem.commands = new ArrayList<>(); currentRet = loopItem.commands; //return ret; } boolean parseNext = true; //****************************DECOMPILING PART************* List output = new ArrayList<>(); List parts = new ArrayList<>(); if (part instanceof GraphPartMulti) { parts = ((GraphPartMulti) part).parts; } else { parts.add(part); } int end = part.end; for (GraphPart p : parts) { end = p.end; int start = p.start; try { output.addAll(code.translatePart(p, localData, stack, start, end)); if ((end >= code.size() - 1) && p.nextParts.isEmpty()) { output.add(new ScriptEndItem()); } } catch (Exception ex) { Logger.getLogger(Graph.class.getName()).log(Level.SEVERE, "error during printgraph", ex); return ret; } } //Assuming part with two nextparts is an IF /* //If with both branches empty if (part.nextParts.size() == 2) { if (part.nextParts.get(0) == part.nextParts.get(1)) { if (!stack.isEmpty()) { GraphTargetItem expr = stack.pop(); if (expr instanceof LogicalOpItem) { expr = ((LogicalOpItem) expr).invert(); } else { expr = new NotItem(null, expr); } output.add(new IfItem(null, expr, new ArrayList(), new ArrayList())); } part.nextParts.remove(0); } }*/ /** * AND / OR detection */ if (part.nextParts.size() == 2) { if ((stack.size() >= 2) && (stack.get(stack.size() - 1) instanceof NotItem) && (((NotItem) (stack.get(stack.size() - 1))).getOriginal().getNotCoerced() == stack.get(stack.size() - 2).getNotCoerced())) { currentRet.addAll(output); GraphPart sp0 = getNextNoJump(part.nextParts.get(0)); GraphPart sp1 = getNextNoJump(part.nextParts.get(1)); boolean reversed = false; loopContinues = getLoopsContinues(loops); loopContinues.add(part);//??? if (sp1.leadsTo(code, sp0, loops)) { } else if (sp0.leadsTo(code, sp1, loops)) { reversed = true; } GraphPart next = reversed ? sp0 : sp1; GraphTargetItem ti; if ((ti = checkLoop(next, stopPart, loops)) != null) { currentRet.add(ti); } else { List stopPart2 = new ArrayList<>(stopPart); stopPart2.add(reversed ? sp1 : sp0); printGraph(visited, localData, stack, allParts, parent, next, stopPart2, loops); GraphTargetItem second = stack.pop(); GraphTargetItem first = stack.pop(); if (!reversed) { AndItem a = new AndItem(null, first, second); stack.push(a); a.firstPart = part; if (second instanceof AndItem) { a.firstPart = ((AndItem) second).firstPart; } if (second instanceof OrItem) { a.firstPart = ((OrItem) second).firstPart; } } else { OrItem o = new OrItem(null, first, second); stack.push(o); o.firstPart = part; if (second instanceof AndItem) { o.firstPart = ((AndItem) second).firstPart; } if (second instanceof OrItem) { o.firstPart = ((OrItem) second).firstPart; } } next = reversed ? sp1 : sp0; if ((ti = checkLoop(next, stopPart, loops)) != null) { currentRet.add(ti); } else { currentRet.addAll(printGraph(visited, localData, stack, allParts, parent, next, stopPart, loops)); } } parseNext = false; //return ret; } else if ((stack.size() >= 2) && (stack.get(stack.size() - 1).getNotCoerced() == stack.get(stack.size() - 2).getNotCoerced())) { currentRet.addAll(output); GraphPart sp0 = getNextNoJump(part.nextParts.get(0)); GraphPart sp1 = getNextNoJump(part.nextParts.get(1)); boolean reversed = false; loopContinues = getLoopsContinues(loops); loopContinues.add(part);//??? if (sp1.leadsTo(code, sp0, loops)) { } else if (sp0.leadsTo(code, sp1, loops)) { reversed = true; } GraphPart next = reversed ? sp0 : sp1; GraphTargetItem ti; if ((ti = checkLoop(next, stopPart, loops)) != null) { currentRet.add(ti); } else { List stopPart2 = new ArrayList<>(stopPart); stopPart2.add(reversed ? sp1 : sp0); printGraph(visited, localData, stack, allParts, parent, next, stopPart2, loops); GraphTargetItem second = stack.pop(); GraphTargetItem first = stack.pop(); if (reversed) { AndItem a = new AndItem(null, first, second); stack.push(a); a.firstPart = part; if (second instanceof AndItem) { a.firstPart = ((AndItem) second).firstPart; } if (second instanceof OrItem) { a.firstPart = ((AndItem) second).firstPart; } } else { OrItem o = new OrItem(null, first, second); stack.push(o); o.firstPart = part; if (second instanceof OrItem) { o.firstPart = ((OrItem) second).firstPart; } if (second instanceof OrItem) { o.firstPart = ((OrItem) second).firstPart; } } next = reversed ? sp1 : sp0; if ((ti = checkLoop(next, stopPart, loops)) != null) { currentRet.add(ti); } else { currentRet.addAll(printGraph(visited, localData, stack, allParts, parent, next, stopPart, loops)); } } parseNext = false; //return ret; } } //********************************END PART DECOMPILING if (parseNext) { List retCheck = check(code, localData, allParts, stack, parent, part, stopPart, loops, output); if (retCheck != null) { if (!retCheck.isEmpty()) { currentRet.addAll(retCheck); } return ret; } else { currentRet.addAll(output); } if (part.nextParts.size() == 2) { //List ignore = new ArrayList<>(); //ignore.addAll(loopContinues); /*for (Loop el : loops) { if (el.loopContinue == next) { next = null; break; } if (el.loopBreak == next) { next = null; break; } }*/ GraphTargetItem expr = stack.pop(); if (expr instanceof LogicalOpItem) { expr = ((LogicalOpItem) expr).invert(); } else { expr = new NotItem(null, expr); } GraphPart next = getNextCommonPart(part, loops);//part.getNextPartPath(loopContinues); //loopContinues); @SuppressWarnings("unchecked") Stack trueStack = (Stack) stack.clone(); @SuppressWarnings("unchecked") Stack falseStack = (Stack) stack.clone(); int trueStackSizeBefore = trueStack.size(); int falseStackSizeBefore = falseStack.size(); List onTrue = new ArrayList<>(); boolean isEmpty = part.nextParts.get(0) == part.nextParts.get(1); if (isEmpty) { next = part.nextParts.get(0); } List stopPart2 = new ArrayList<>(stopPart); if (next != null) { stopPart2.add(next); } if (!isEmpty) { onTrue = printGraph(visited, localData, trueStack, allParts, part, part.nextParts.get(1), stopPart2, loops); } List onFalse = new ArrayList<>(); if (!isEmpty) { onFalse = printGraph(visited, localData, falseStack, allParts, part, part.nextParts.get(0), stopPart2, loops); } if (isEmpty(onTrue) && isEmpty(onFalse) && (trueStack.size() > trueStackSizeBefore) && (falseStack.size() > falseStackSizeBefore)) { stack.push(new TernarOpItem(null, expr, trueStack.pop(), falseStack.pop())); } else { currentRet.add(new IfItem(null, expr, onTrue, onFalse)); } if (next != null) { printGraph(visited, localData, stack, allParts, part, next, stopPart, loops, currentRet); //currentRet.addAll(); } } else if (part.nextParts.size() == 1) { printGraph(visited, localData, stack, allParts, part, part.nextParts.get(0), stopPart, loops, currentRet); } } if (isLoop) { currentLoop.phase = 2; LoopItem li = loopItem; boolean loopTypeFound = false; checkContinueAtTheEnd(loopItem.commands, currentLoop); //Loop with condition at the beginning (While) if (!loopTypeFound && (!loopItem.commands.isEmpty())) { if (loopItem.commands.get(0) instanceof IfItem) { IfItem ifi = (IfItem) loopItem.commands.get(0); List bodyBranch = null; boolean inverted = false; if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { BreakItem bi = (BreakItem) ifi.onTrue.get(0); if (bi.loopId == currentLoop.id) { bodyBranch = ifi.onFalse; inverted = true; } } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { BreakItem bi = (BreakItem) ifi.onFalse.get(0); if (bi.loopId == currentLoop.id) { bodyBranch = ifi.onTrue; } } if (bodyBranch != null) { int index = ret.indexOf(loopItem); ret.remove(index); List exprList = new ArrayList<>(); GraphTargetItem expr = ifi.expression; if (inverted) { if (expr instanceof LogicalOpItem) { expr = ((LogicalOpItem) expr).invert(); } else { expr = new NotItem(null, expr); } } exprList.add(expr); List commands = new ArrayList<>(); commands.addAll(bodyBranch); loopItem.commands.remove(0); commands.addAll(loopItem.commands); checkContinueAtTheEnd(commands, currentLoop); List finalComm = new ArrayList<>(); if (currentLoop.loopPreContinue != null) { GraphPart backup = currentLoop.loopPreContinue; currentLoop.loopPreContinue = null; List stopPart2 = new ArrayList<>(stopPart); stopPart2.add(currentLoop.loopContinue); finalComm = printGraph(visited, localData, new Stack(), allParts, null, backup, stopPart2, loops); currentLoop.loopPreContinue = backup; checkContinueAtTheEnd(finalComm, currentLoop); } if (!finalComm.isEmpty()) { ret.add(index, li = new ForTreeItem(null, currentLoop, new ArrayList(), exprList.get(exprList.size() - 1), finalComm, commands)); } else { ret.add(index, li = new WhileItem(null, currentLoop, exprList, commands)); } loopTypeFound = true; } } } //Loop with condition at the end (Do..While) if (!loopTypeFound && (!loopItem.commands.isEmpty())) { if (loopItem.commands.get(loopItem.commands.size() - 1) instanceof IfItem) { IfItem ifi = (IfItem) loopItem.commands.get(loopItem.commands.size() - 1); List bodyBranch = null; boolean inverted = false; if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { BreakItem bi = (BreakItem) ifi.onTrue.get(0); if (bi.loopId == currentLoop.id) { bodyBranch = ifi.onFalse; inverted = true; } } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { BreakItem bi = (BreakItem) ifi.onFalse.get(0); if (bi.loopId == currentLoop.id) { bodyBranch = ifi.onTrue; } } if (bodyBranch != null) { //Condition at the beginning int index = ret.indexOf(loopItem); ret.remove(index); List exprList = new ArrayList<>(); GraphTargetItem expr = ifi.expression; if (inverted) { if (expr instanceof LogicalOpItem) { expr = ((LogicalOpItem) expr).invert(); } else { expr = new NotItem(null, expr); } } checkContinueAtTheEnd(bodyBranch, currentLoop); List commands = new ArrayList<>(); loopItem.commands.remove(loopItem.commands.size() - 1); if (!bodyBranch.isEmpty()) { /*exprList.addAll(loopItem.commands); commands.addAll(bodyBranch); exprList.add(expr); checkContinueAtTheEnd(commands, currentLoop); ret.add(index, li = new WhileItem(null, currentLoop, exprList, commands));*/ } else { commands.addAll(loopItem.commands); commands.addAll(bodyBranch); exprList.add(expr); checkContinueAtTheEnd(commands, currentLoop); ret.add(index, li = new DoWhileItem(null, currentLoop, commands, exprList)); } loopTypeFound = true; } } } if (!loopTypeFound) { if (currentLoop.loopPreContinue != null) { loopTypeFound = true; GraphPart backup = currentLoop.loopPreContinue; currentLoop.loopPreContinue = null; List stopPart2 = new ArrayList<>(stopPart); stopPart2.add(currentLoop.loopContinue); List finalComm = printGraph(visited, localData, new Stack(), allParts, null, backup, stopPart2, loops); currentLoop.loopPreContinue = backup; checkContinueAtTheEnd(finalComm, currentLoop); if (!finalComm.isEmpty()) { if (finalComm.get(finalComm.size() - 1) instanceof IfItem) { IfItem ifi = (IfItem) finalComm.get(finalComm.size() - 1); boolean ok = false; boolean invert = false; if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem) && (((BreakItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) && ((ifi.onTrue.size() == 1) && (ifi.onFalse.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { ok = true; invert = true; } if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) && ((ifi.onTrue.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem) && (((BreakItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { ok = true; } if (ok) { finalComm.remove(finalComm.size() - 1); int index = ret.indexOf(loopItem); ret.remove(index); List exprList = new ArrayList<>(finalComm); GraphTargetItem expr = ifi.expression; if (invert) { if (expr instanceof LogicalOpItem) { expr = ((LogicalOpItem) expr).invert(); } else { expr = new NotItem(null, expr); } } exprList.add(expr); ret.add(index, li = new DoWhileItem(null, currentLoop, loopItem.commands, exprList)); } } } } } if (!loopTypeFound) { checkContinueAtTheEnd(loopItem.commands, currentLoop); } GraphTargetItem replaced = checkLoop(li, localData, loops); if (replaced != li) { int index = ret.indexOf(li); ret.remove(index); if (replaced != null) { ret.add(index, replaced); } } //loops.remove(currentLoop); if (currentLoop.loopBreak != null) { ret.addAll(printGraph(visited, localData, stack, allParts, part, currentLoop.loopBreak, stopPart, loops)); } } return ret; } private List makeGraph(GraphSource code, List allBlocks, List alternateEntries) { HashMap> refs = code.visitCode(alternateEntries); List ret = new ArrayList<>(); boolean visited[] = new boolean[code.size()]; ret.add(makeGraph(null, new GraphPath(), code, 0, 0, allBlocks, refs, visited)); for (int pos : alternateEntries) { GraphPart e1 = new GraphPart(-1, -1); e1.path = new GraphPath("e"); ret.add(makeGraph(e1, new GraphPath("e"), code, pos, pos, allBlocks, refs, visited)); } return ret; } protected int checkIp(int ip) { return ip; } private GraphPart makeGraph(GraphPart parent, GraphPath path, GraphSource code, int startip, int lastIp, List allBlocks, HashMap> refs, boolean visited2[]) { int ip = startip; for (GraphPart p : allBlocks) { if (p.start == ip) { p.refs.add(parent); return p; } } GraphPart g; GraphPart ret = new GraphPart(ip, -1); ret.path = path; GraphPart part = ret; while (ip < code.size()) { if (visited2[ip] || ((ip != startip) && (refs.get(ip).size() > 1))) { part.end = lastIp; GraphPart found = null; for (GraphPart p : allBlocks) { if (p.start == ip) { found = p; break; } } allBlocks.add(part); if (found != null) { part.nextParts.add(found); found.refs.add(part); break; } else { GraphPart gp = new GraphPart(ip, -1); gp.path = path; part.nextParts.add(gp); gp.refs.add(part); part = gp; } } ip = checkIp(ip); lastIp = ip; GraphSourceItem ins = code.get(ip); if (ins.isIgnored()) { ip++; continue; } if (ins instanceof GraphSourceItemContainer) { GraphSourceItemContainer cnt = (GraphSourceItemContainer) ins; if (ins instanceof Action) { //TODO: Remove dependency of AVM1 long endAddr = ((Action) ins).getAddress() + cnt.getHeaderSize(); for (long size : cnt.getContainerSizes()) { endAddr += size; } ip = code.adr2pos(endAddr); } continue; } else if (ins.isExit()) { part.end = ip; allBlocks.add(part); break; } else if (ins.isJump()) { part.end = ip; allBlocks.add(part); ip = ins.getBranches(code).get(0); part.nextParts.add(g = makeGraph(part, path, code, ip, lastIp, allBlocks, refs, visited2)); g.refs.add(part); break; } else if (ins.isBranch()) { part.end = ip; allBlocks.add(part); List branches = ins.getBranches(code); for (int i = 0; i < branches.size(); i++) { part.nextParts.add(g = makeGraph(part, path.sub(i, ip), code, branches.get(i), ip, allBlocks, refs, visited2)); g.refs.add(part); } break; } ip++; } if ((part.end == -1) && (ip >= code.size())) { if (part.start == code.size()) { part.end = code.size(); allBlocks.add(part); } else { part.end = ip - 1; for (GraphPart p : allBlocks) { if (p.start == ip) { p.refs.add(part); part.nextParts.add(p); allBlocks.add(part); return ret; } } GraphPart gp = new GraphPart(ip, ip); allBlocks.add(gp); gp.refs.add(part); part.nextParts.add(gp); allBlocks.add(part); } } return ret; } /** * String used to indent line when converting to string */ public static final String INDENTOPEN = "INDENTOPEN"; /** * String used to unindent line when converting to string */ public static final String INDENTCLOSE = "INDENTCLOSE"; private static final String INDENT_STRING = " "; private static String tabString(int len) { String ret = ""; for (int i = 0; i < len; i++) { ret += INDENT_STRING; } return ret; } /** * Converts list of TreeItems to string * * @param tree List of TreeItem * @return String */ public static String graphToString(List tree, Object... localData) { StringBuilder ret = new StringBuilder(); List localDataList = new ArrayList<>(); for (Object o : localData) { localDataList.add(o); } for (GraphTargetItem ti : tree) { if (!ti.isEmpty()) { ret.append(ti.toStringSemicoloned(localDataList)); ret.append("\r\n"); } } String parts[] = ret.toString().split("\r\n"); ret = new StringBuilder(); String labelPattern = "loop(switch)?[0-9]*:"; try { Stack loopStack = new Stack<>(); for (int p = 0; p < parts.length; p++) { String stripped = Highlighting.stripHilights(parts[p]); if (stripped.matches(labelPattern)) { loopStack.add(stripped.substring(0, stripped.length() - 1)); } if (stripped.startsWith("break ")) { if (stripped.equals("break " + loopStack.peek().replace("switch", "") + ";")) { parts[p] = parts[p].replace(" " + loopStack.peek().replace("switch", ""), ""); } } if (stripped.startsWith("continue ")) { if (loopStack.size() > 0) { int pos = loopStack.size() - 1; String loopname = ""; do { loopname = loopStack.get(pos); pos--; } while ((pos >= 0) && (loopname.startsWith("loopswitch"))); if (stripped.equals("continue " + loopname + ";")) { parts[p] = parts[p].replace(" " + loopname, ""); } } } if (stripped.startsWith(":")) { loopStack.pop(); } } } catch (Exception ex) { } int level = 0; for (int p = 0; p < parts.length; p++) { String strippedP = Highlighting.stripHilights(parts[p]).trim(); if (strippedP.matches(labelPattern)) {//endsWith(":") && (!strippedP.startsWith("case ")) && (!strippedP.equals("default:"))) { String loopname = strippedP.substring(0, strippedP.length() - 1); boolean dorefer = false; for (int q = p + 1; q < parts.length; q++) { String strippedQ = Highlighting.stripHilights(parts[q]).trim(); if (strippedQ.equals("break " + loopname.replace("switch", "") + ";")) { dorefer = true; break; } if (strippedQ.equals("continue " + loopname + ";")) { dorefer = true; break; } if (strippedQ.equals(":" + loopname)) { break; } } if (!dorefer) { continue; } } if (strippedP.startsWith(":")) { continue; } if (Highlighting.stripHilights(parts[p]).equals(INDENTOPEN)) { level++; continue; } if (Highlighting.stripHilights(parts[p]).equals(INDENTCLOSE)) { level--; continue; } if (Highlighting.stripHilights(parts[p]).equals("}")) { level--; } if (Highlighting.stripHilights(parts[p]).equals("};")) { level--; } ret.append(tabString(level)); ret.append(parts[p]); ret.append("\r\n"); if (Highlighting.stripHilights(parts[p]).equals("{")) { level++; } } return ret.toString(); } public List prepareBranchLocalData(List localData) { return localData; } }