diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bbf3af10..f990d0c2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ All notable changes to this project will be documented in this file. - Highlighter nullpointer - AS3 search slot name Index out of bounds - AS text search - not being able to cancel search over multiple swf files +- [#2486] AS3 switches detection in some cases ### Changed - Icon of "Deobfuscation options" menu from pile of pills to medkit diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorPushFalseIfFalse.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorPushFalseIfFalse.java new file mode 100644 index 000000000..2a27cc600 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorPushFalseIfFalse.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2010-2025 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.abc.avm2.deobfuscation; + +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; +import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfFalseIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.other.NopIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushFalseIns; +import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.helpers.SWFDecompilerAdapter; +import java.util.List; +import java.util.Map; + +/** + * This will fix some if(false) in Flex code in switch default statement. + * It is not a real deobufscation, just fix for specific case. + * @author JPEXS + */ +public class AVM2DeobfuscatorPushFalseIfFalse extends SWFDecompilerAdapter { + + @Override + public void avm2CodeRemoveTraps(String path, int classIndex, boolean isStatic, int scriptIndex, ABC abc, Trait trait, int methodInfo, MethodBody body) throws InterruptedException { + Map> refs = body.getCode().visitCode(body); + AVM2Code code = body.getCode(); + for (int ip = 1; ip < code.code.size(); ip++) { + AVM2Instruction ins = code.code.get(ip); + AVM2Instruction insPrev = code.code.get(ip - 1); + if (!(ins.definition instanceof IfFalseIns)) { + continue; + } + if (!(insPrev.definition instanceof PushFalseIns)) { + continue; + } + if (refs.containsKey(ip) && refs.get(ip).size() > 1) { + continue; + } + insPrev.definition = new NopIns(); + ins.definition = new JumpIns(); + } + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java index eb1c48bfd..92fce04eb 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java @@ -27,7 +27,9 @@ import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewCatchIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.debug.DebugLineIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfFalseIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfStrictEqIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfStrictNeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.LookupSwitchIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.DecLocalIIns; @@ -44,6 +46,7 @@ import com.jpexs.decompiler.flash.abc.avm2.instructions.other.decimalsupport.Dec import com.jpexs.decompiler.flash.abc.avm2.instructions.other.decimalsupport.IncLocalPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PopIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushByteIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushFalseIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushScopeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.CoerceAIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ConvertIIns; @@ -84,6 +87,7 @@ import com.jpexs.decompiler.flash.abc.avm2.model.clauses.TryAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.operations.EqAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.operations.NeqAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.operations.StrictEqAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.StrictNeqAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.parser.script.AbcIndexing; import com.jpexs.decompiler.flash.abc.types.ABCException; import com.jpexs.decompiler.flash.abc.types.MethodBody; @@ -110,13 +114,17 @@ import com.jpexs.decompiler.graph.model.BinaryOpItem; import com.jpexs.decompiler.graph.model.BreakItem; import com.jpexs.decompiler.graph.model.CommaExpressionItem; import com.jpexs.decompiler.graph.model.ContinueItem; +import com.jpexs.decompiler.graph.model.DefaultItem; import com.jpexs.decompiler.graph.model.ExitItem; import com.jpexs.decompiler.graph.model.FalseItem; import com.jpexs.decompiler.graph.model.GotoItem; import com.jpexs.decompiler.graph.model.IfItem; +import com.jpexs.decompiler.graph.model.IntegerValueItem; +import com.jpexs.decompiler.graph.model.IntegerValueTypeItem; import com.jpexs.decompiler.graph.model.LoopItem; import com.jpexs.decompiler.graph.model.NotItem; import com.jpexs.decompiler.graph.model.OrItem; +import com.jpexs.decompiler.graph.model.PopItem; import com.jpexs.decompiler.graph.model.PushItem; import com.jpexs.decompiler.graph.model.SwitchItem; import com.jpexs.decompiler.graph.model.TernarOpItem; @@ -129,6 +137,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -1735,11 +1744,8 @@ public class AVM2Graph extends Graph { Loop currentLoop, int staticOperation, String path) throws InterruptedException { List ret = null; - /*if (ret != null) { - return ret; - }*/ //Detect switch - if ((part.nextParts.size() == 2) && (!stack.isEmpty()) && (stack.peek() instanceof StrictEqAVM2Item)) { + if ((part.nextParts.size() == 2) && (!stack.isEmpty()) && ((stack.peek() instanceof StrictEqAVM2Item) || (stack.peek() instanceof StrictNeqAVM2Item))) { GraphSourceItem switchStartItem = code.get(part.start); GraphTargetItem switchedObject = null; @@ -1750,29 +1756,82 @@ public class AVM2Graph extends Graph { } List caseValuesMapLeft = new ArrayList<>(); List caseValuesMapRight = new ArrayList<>(); + List> outs = new ArrayList<>(); - StrictEqAVM2Item set = (StrictEqAVM2Item) stack.pop(); - StrictEqAVM2Item firstSet = set; + outs.add(new ArrayList<>()); + + BinaryOpItem set = (BinaryOpItem) stack.pop(); + BinaryOpItem firstSet = set; caseValuesMapLeft.add(set.leftSide); caseValuesMapRight.add(set.rightSide); + int branchNum; + GraphPart origPart = part; List caseBodyParts = new ArrayList<>(); - caseBodyParts.add(part.nextParts.get(0)); + + if (((AVM2Instruction) code.get(part.end)).definition instanceof IfStrictNeIns) { + branchNum = 0; + caseBodyParts.add(part.nextParts.get(1)); + } else { + branchNum = 1; + caseBodyParts.add(part.nextParts.get(0)); + } GraphTargetItem top = null; int cnt = 1; + int branchCount = 2; try { - while (part.nextParts.size() > 1 - && part.nextParts.get(1).getHeight() > 1 - && ((AVM2Instruction) code.get(part.nextParts.get(1).end >= code.size() ? code.size() - 1 : part.nextParts.get(1).end)).definition instanceof IfStrictEqIns - && ((top = translatePartGetStack(localData, part.nextParts.get(1), stack, staticOperation)) instanceof StrictEqAVM2Item)) { - cnt++; - part = part.nextParts.get(1); - caseBodyParts.add(part.nextParts.get(0)); + while (true) { + List out = new ArrayList<>(); + //Special: In Flex (not air) there are these blocks sometimes: + // if(false) { + // §§push(5); + // break; + //} + //AVM2DeobfuscatorPushFalseIfFalse changes it to + // nop + // jump + // (to first case to work) + if (part.nextParts.size() == branchCount + && part.nextParts.get(branchNum).getHeight() == 2 + && ((AVM2Instruction) code.get(part.nextParts.get(branchNum).start >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).start)).definition instanceof NopIns + && ((AVM2Instruction) code.get(part.nextParts.get(branchNum).end >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).end)).definition instanceof JumpIns) { + part = part.nextParts.get(branchNum); + branchNum = 0; + branchCount = 1; + } else if (part.nextParts.size() == branchCount + && part.nextParts.get(branchNum).getHeight() > 1 + && ((AVM2Instruction) code.get(part.nextParts.get(branchNum).end >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).end)).definition instanceof IfStrictEqIns + && ((top = translatePartGetStack(localData, part.nextParts.get(branchNum), stack, staticOperation, out)) instanceof StrictEqAVM2Item)) { + cnt++; + part = part.nextParts.get(branchNum); + caseBodyParts.add(part.nextParts.get(0)); + branchNum = 1; - set = (StrictEqAVM2Item) top; - caseValuesMapLeft.add(set.leftSide); - caseValuesMapRight.add(set.rightSide); + outs.add(out); + out = new ArrayList<>(); + set = (StrictEqAVM2Item) top; + caseValuesMapLeft.add(set.leftSide); + caseValuesMapRight.add(set.rightSide); + branchCount = 2; + } else if (part.nextParts.size() == branchCount + && part.nextParts.get(branchNum).getHeight() > 1 + && ((AVM2Instruction) code.get(part.nextParts.get(branchNum).end >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).end)).definition instanceof IfStrictNeIns + && ((top = translatePartGetStack(localData, part.nextParts.get(branchNum), stack, staticOperation, out)) instanceof StrictNeqAVM2Item)) { + cnt++; + part = part.nextParts.get(branchNum); + caseBodyParts.add(part.nextParts.get(1)); + branchNum = 0; + + outs.add(out); + out = new ArrayList<>(); + set = (StrictNeqAVM2Item) top; + caseValuesMapLeft.add(set.leftSide); + caseValuesMapRight.add(set.rightSide); + branchCount = 2; + } else { + break; + } } } catch (GraphPartChangeException gpc) { //ignore @@ -1818,10 +1877,17 @@ public class AVM2Graph extends Graph { otherSide = caseValuesMapRight; } + for (int i = 0; i < caseValuesMap.size(); i++) { + if (!outs.get(i).isEmpty()) { + outs.get(i).add(caseValuesMap.get(i)); + caseValuesMap.set(i, new CommaExpressionItem(dialect, null, null, outs.get(i))); + } + } + if ((leftReg < 0 && rightReg < 0) || (cnt == 1)) { stack.push(firstSet); } else { - part = part.nextParts.get(1); + part = part.nextParts.get(branchNum); GraphPart defaultPart = part; if (code.size() > defaultPart.start && ((AVM2Instruction) code.get(defaultPart.start)).definition instanceof JumpIns && defaultPart.refs.size() == 1 @@ -2443,9 +2509,11 @@ public class AVM2Graph extends Graph { if (item instanceof IntegerValueAVM2Item) { return true; } + /* if ((item instanceof PushItem) && (item.value instanceof IntegerValueAVM2Item)) { return true; } + */ return false; } @@ -2454,6 +2522,159 @@ public class AVM2Graph extends Graph { if (debugDoNotProcess) { return; } + + loopi: + for (int i = 1 /*not first*/; i < list.size(); i++) { + GraphTargetItem item = list.get(i); + + GraphTargetItem prevItem = list.get(i - 1); + if ((item instanceof SwitchItem) && (prevItem instanceof SwitchItem)) { + SwitchItem thisSwitch = (SwitchItem) item; + SwitchItem prevSwitch = (SwitchItem) prevItem; + if (thisSwitch.switchedObject instanceof PopItem) { + List caseValues = new ArrayList<>(); + Map expressionsMap = new LinkedHashMap<>(); + List defaultCommands = null; + for (int k = 0; k < thisSwitch.caseValues.size(); k++) { + if (thisSwitch.caseValues.get(k) instanceof DefaultItem) { + continue; + } + if (!(thisSwitch.caseValues.get(k) instanceof IntegerValueTypeItem)) { + continue loopi; + } + } + + int defaultInt = -1; + + for (int j = 0; j < prevSwitch.caseCommands.size(); j++) { + + int valueIndex = prevSwitch.valuesMapping.indexOf(j); + + if (valueIndex == -1) { + continue loopi; + } + + GraphTargetItem currentExpression = prevSwitch.caseValues.get(valueIndex); + + if (prevSwitch.caseCommands.get(j).size() > 2) { + continue loopi; + } + int delta = 0; + if (currentExpression instanceof DefaultItem && prevSwitch.caseCommands.size() >= 2) { + //Special case - in flex (not air), the default clause has weird + // if (false) { + // §§push(5); + // } + // §§push(5); + if (prevSwitch.caseCommands.get(j).get(0) instanceof IfItem) { + IfItem ifi = (IfItem) prevSwitch.caseCommands.get(j).get(0); + if (ifi.expression instanceof FalseItem) { + delta++; + } + } + } + if (j == prevSwitch.caseCommands.size() - 1 && prevSwitch.caseCommands.get(j).size() == delta + 1) { + //empty + } else if (prevSwitch.caseCommands.get(j).size() != 2 + delta) { + continue loopi; + } else { + if (!(prevSwitch.caseCommands.get(j).get(delta + 1) instanceof BreakItem)) { + continue loopi; + } + BreakItem br = (BreakItem) prevSwitch.caseCommands.get(j).get(delta + 1); + if (br.loopId != prevSwitch.loop.id) { + continue loopi; + } + } + if (!(prevSwitch.caseCommands.get(j).get(delta) instanceof PushItem)) { + continue loopi; + } + PushItem pi = (PushItem) prevSwitch.caseCommands.get(j).get(delta); + if (!(pi.value instanceof IntegerValueTypeItem)) { + continue loopi; + } + Integer pushedInt = ((IntegerValueTypeItem) pi.value).intValue(); + expressionsMap.put(pushedInt, currentExpression); + if (currentExpression instanceof DefaultItem) { + defaultInt = pushedInt; + } + } + + List thisExpressions = new ArrayList<>(); + int defaultIndex = -1; + for (int k = 0; k < thisSwitch.caseValues.size(); k++) { + if (thisSwitch.caseValues.get(k) instanceof DefaultItem) { + defaultIndex = k; + continue; + } + if (!(thisSwitch.caseValues.get(k) instanceof IntegerValueTypeItem)) { + continue loopi; + } + int cv = ((IntegerValueTypeItem) thisSwitch.caseValues.get(k)).intValue(); + if (!expressionsMap.containsKey(cv)) { + continue loopi; + } + thisExpressions.add(cv); + } + + List noCaseExpressions = new ArrayList<>(); + + for (int key : expressionsMap.keySet()) { + if (!thisExpressions.contains(key)) { + if (defaultIndex == -1 && defaultInt != key) { + continue loopi; + } + noCaseExpressions.add(key); + } + } + noCaseExpressions.sort(new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o1 - o2; + } + }); + + for (int k = 0; k < thisSwitch.caseValues.size(); k++) { + if (thisSwitch.caseValues.get(k) instanceof DefaultItem) { + int m = thisSwitch.valuesMapping.get(k); + thisSwitch.caseValues.remove(k); + thisSwitch.valuesMapping.remove(k); + + for (int e : noCaseExpressions) { + thisSwitch.caseValues.add(k, expressionsMap.get(e)); + thisSwitch.valuesMapping.add(k, m); + k++; + } + k--; + + boolean foundElsewhere = false; + for (int h = 0; h < thisSwitch.valuesMapping.size(); h++) { + if (thisSwitch.valuesMapping.get(h) == m) { + foundElsewhere = true; + break; + } + } + if (!foundElsewhere) { + thisSwitch.caseCommands.remove(m); + for (int h = 0; h < thisSwitch.valuesMapping.size(); h++) { + if (thisSwitch.valuesMapping.get(h) > m) { + thisSwitch.valuesMapping.set(h, thisSwitch.valuesMapping.get(h) - 1); + } + } + } + continue; + } + int cv = ((IntegerValueTypeItem) thisSwitch.caseValues.get(k)).intValue(); + thisSwitch.caseValues.set(k, expressionsMap.get(cv)); + } + + thisSwitch.switchedObject = prevSwitch.switchedObject; + list.remove(i - 1); + i--; + } + } + } + if (level == 0) { if (!list.isEmpty()) { if (list.get(list.size() - 1) instanceof ReturnVoidAVM2Item) { @@ -2890,7 +3111,7 @@ public class AVM2Graph extends Graph { }); i = newI.getVal(); } - */ + */ //Handle for loops at the end: super.finalProcess(parent, list, level, localData, path); } @@ -2931,7 +3152,7 @@ public class AVM2Graph extends Graph { AVM2LocalData avm2LocalData = (AVM2LocalData) localData; SetLocalAVM2Item setLocal = (SetLocalAVM2Item) output.get(output.size() - 1); int setLocalIp = InstructionDefinition.getItemIp(avm2LocalData, setLocal); - ; + Set allUsages = new HashSet<>(avm2LocalData.getSetLocalUsages(setLocalIp)); boolean isOtherSideReg = false; for (GraphTargetItem otherSide : otherSides) { @@ -3263,5 +3484,5 @@ public class AVM2Graph extends Graph { } return ternar; - } + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/MethodBody.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/MethodBody.java index 301b33e55..8257817fd 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/MethodBody.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/MethodBody.java @@ -23,6 +23,7 @@ import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; import com.jpexs.decompiler.flash.abc.avm2.CodeStats; import com.jpexs.decompiler.flash.abc.avm2.UnknownInstructionCodeException; +import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorPushFalseIfFalse; import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.DeobfuscationLevel; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; import com.jpexs.decompiler.flash.abc.avm2.parser.script.AbcIndexing; @@ -642,6 +643,11 @@ public final class MethodBody implements Cloneable { code.fixJumps(path, body); return body; } + } else { + //This needs to be done otherwise some switch testcases fail. (Flex) + try (Statistics s = new Statistics("AVM2DeobfuscatorPushFalseIfFalse")) { + new AVM2DeobfuscatorPushFalseIfFalse().avm2CodeRemoveTraps(path, classIndex, isStatic, scriptIndex, abc, trait, method_info, body); + } } lastConvertedBody = body; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java index 0d286f6e9..778dff996 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -27,6 +27,7 @@ import com.jpexs.decompiler.graph.model.BinaryOpItem; import com.jpexs.decompiler.graph.model.BranchStackResistant; import com.jpexs.decompiler.graph.model.BreakItem; import com.jpexs.decompiler.graph.model.CommaExpressionItem; +import com.jpexs.decompiler.graph.model.CommentItem; import com.jpexs.decompiler.graph.model.ContinueItem; import com.jpexs.decompiler.graph.model.DefaultItem; import com.jpexs.decompiler.graph.model.DoWhileItem; @@ -1383,7 +1384,7 @@ public class Graph { * @param list List of GraphTargetItems * @param lastLoopId Last loop id */ - protected final void processSwitches(List list, long lastLoopId) { + protected void processSwitches(List list, long lastLoopId) { loopi: for (int i = 0; i < list.size(); i++) { GraphTargetItem item = list.get(i); @@ -2311,6 +2312,25 @@ public class Graph { translatePart(localData, part, stack, staticOperation, null); return stack.pop(); } + + /** + * Translates part and get its stack with output + * + * @param localData Local data + * @param part Part + * @param stack Translate stack + * @param staticOperation Unused + * @return Top of the stack + * @throws InterruptedException On interrupt + * @throws GraphPartChangeException On graph part change + */ + //@SuppressWarnings("unchecked") + protected final GraphTargetItem translatePartGetStack(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation, List output) throws InterruptedException, GraphPartChangeException { + stack = (TranslateStack) stack.clone(); + output.clear(); + output.addAll(translatePart(localData, part, stack, staticOperation, null)); + return stack.pop(); + } /** * Translates part. @@ -3194,6 +3214,7 @@ public class Graph { if (debugPrintGraph) { System.err.println("Adding break"); } + makeAllCommands(ret, stack); ret.add(new BreakItem(dialect, null, localData.lineStartInstruction, el.id)); return ret; } @@ -3204,6 +3225,7 @@ public class Graph { if (debugPrintGraph) { System.err.println("Adding precontinue"); } + makeAllCommands(ret, stack); ret.add(new ContinueItem(dialect, null, localData.lineStartInstruction, el.id)); return ret; } @@ -3214,6 +3236,7 @@ public class Graph { if (debugPrintGraph) { System.err.println("Adding continue"); } + makeAllCommands(ret, stack); ret.add(new ContinueItem(dialect, null, localData.lineStartInstruction, el.id)); return ret; } @@ -3267,8 +3290,10 @@ public class Graph { } } - if (code.size() <= part.start) { - ret.add(new ScriptEndItem(dialect)); + if (code.size() <= part.start) { + if (!(!ret.isEmpty() && ret.get(ret.size() - 1) instanceof ExitItem)) { + ret.add(new ScriptEndItem(dialect)); + } return ret; } @@ -3428,7 +3453,9 @@ public class Graph { } } while (exHappened); if ((part.end >= code.size() - 1) && getNextParts(localData, part).isEmpty()) { - output.add(new ScriptEndItem(dialect)); + if (!(!output.isEmpty() && output.get(output.size() - 1) instanceof ExitItem)) { + output.add(new ScriptEndItem(dialect)); + } } } @@ -4651,7 +4678,8 @@ public class Graph { stopPart2x.add(breakPart); stopPartKind2x.add(StopPartKind.OTHER); } - currentCaseCommands = printGraph(foundGotos, partCodes, partCodePos, visited, localData, stack, allParts, null, caseBodies.get(i), stopPart2x, stopPartKind2x, loops, throwStates, staticOperation, path); + TranslateStack subStack = (TranslateStack) stack.clone(); + currentCaseCommands = printGraph(foundGotos, partCodes, partCodePos, visited, localData, subStack, allParts, null, caseBodies.get(i), stopPart2x, stopPartKind2x, loops, throwStates, staticOperation, path); if (willHaveBreak) { if (!currentCaseCommands.isEmpty()) { GraphTargetItem last = currentCaseCommands.get(currentCaseCommands.size() - 1); @@ -4660,8 +4688,7 @@ public class Graph { } } } - caseCommands.add(currentCaseCommands); - makeAllCommands(currentCaseCommands, stack); + caseCommands.add(currentCaseCommands); } /* diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java index 27b800d2e..853a151c7 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java @@ -17,7 +17,6 @@ package com.jpexs.decompiler.flash; import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.ScriptPack; import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorGroupParts; import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorJumps; @@ -169,10 +168,10 @@ public class ActionScript3DeobfuscatorTest extends ActionScriptTestBase { code.removeTraps(null, 0, b, abc, 0, -1, true, pCode); code.removeLabelsAndDebugLine(b); HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); - String actual = b.toSource(10, new ArrayList<>(), swf.getAbcIndex(), 0, new HashSet<>()); + String actual = b.toSource(10, new ArrayList<>(), swf.getAbcIndex(), 0, new HashSet<>()); actual = actual.replace("\r\n", "\n"); assertEquals(actual, expected); - } + } private String recompilePCode(String str, SWFDecompilerAdapter deobfuscator) throws IOException, AVM2ParseException, InterruptedException { str = "code\r\n" @@ -543,37 +542,383 @@ public class ActionScript3DeobfuscatorTest extends ActionScriptTestBase { @Test public void testWhileTrue() throws Exception { decompilePCode( -" getlex QName(PackageNamespace(\"\"),\"Math\")\n" + -" getlex QName(PackageNamespace(\"\"),\"Math\")\n" + -" debugline 8\n" + -" callproperty QName(PackageNamespace(\"\"),\"random\"), 0\n" + -" pushbyte 6\n" + -" multiply\n" + -" callproperty QName(PackageNamespace(\"\"),\"floor\"), 1\n" + -" convert_i\n" + -" setlocal1\n" + -" getlocal1\n" + -" debugline 10\n" + -" pushbyte 4\n" + -" ifngt ofs0034\n" + -" jump ofs0030\n" + -" ofs002d:\n" + -" label\n" + -" debugline 11\n" + -" ofs0030:\n" + -" jump ofs002d\n" + -" ofs0034:\n" + -" ", " param1 = Math.floor(Math.random() * 6);\n" + -" if(param1 <= 4)\n" + -" {\n" + -" return;\n" + -" }\n" + -" while(true)\n" + -" {\n" + -" }\n"); - + " getlex QName(PackageNamespace(\"\"),\"Math\")\n" + + " getlex QName(PackageNamespace(\"\"),\"Math\")\n" + + " debugline 8\n" + + " callproperty QName(PackageNamespace(\"\"),\"random\"), 0\n" + + " pushbyte 6\n" + + " multiply\n" + + " callproperty QName(PackageNamespace(\"\"),\"floor\"), 1\n" + + " convert_i\n" + + " setlocal1\n" + + " getlocal1\n" + + " debugline 10\n" + + " pushbyte 4\n" + + " ifngt ofs0034\n" + + " jump ofs0030\n" + + " ofs002d:\n" + + " label\n" + + " debugline 11\n" + + " ofs0030:\n" + + " jump ofs002d\n" + + " ofs0034:\n" + + " ", " param1 = Math.floor(Math.random() * 6);\n" + + " if(param1 <= 4)\n" + + " {\n" + + " return;\n" + + " }\n" + + " while(true)\n" + + " {\n" + + " }\n"); + } - + + @Test + public void testObfuscatedSwitch() throws Exception { + decompilePCode("getlocal0\n" + + " pushbyte 0\n" + + " initproperty QName(PackageNamespace(\"\"),\"testA\")\n" + + " jump ofs0012\n" + + " call 7\n" + + " throw\n" + + " callmethod 1413, 7\n" + + " ofs0012:\n" + + " jump ofs001a\n" + + " ifge ofs001a\n" + + " ofs001a:\n" + + " jump ofs0024\n" + + " equals\n" + + " pushwith\n" + + " newfunction 30\n" + + " pop\n" + + " setlocal2\n" + + " ofs0024:\n" + + " jump ofs0132\n" + + " pushwith\n" + + " setlocal3\n" + + " ofs002a:\n" + + " label\n" + + " pushtrue\n" + + " iftrue ofs0036\n" + + " returnvoid\n" + + " popscope\n" + + " callsuper QName(PackageNamespace(\"aaa\"),\"xxx\"), 10\n" + + " pushwith\n" + + " ofs0036:\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " jump ofs01ff\n" + + " pushwith\n" + + " setlocal0\n" + + " ofs0040:\n" + + " label\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " getlocal0\n" + + " pushtrue\n" + + " iftrue ofs0052\n" + + " returnvoid\n" + + " popscope\n" + + " pushwith\n" + + " newfunction 48\n" + + " pop\n" + + " divide\n" + + " ofs0052:\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " jump ofs01ff\n" + + " ifgt ofs0061\n" + + " ofs0061:\n" + + " label\n" + + " jump ofs006a\n" + + " ifstricteq ofs006a\n" + + " ofs006a:\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " jump ofs0078\n" + + " popscope\n" + + " pushwith\n" + + " newfunction 4\n" + + " throw\n" + + " checkfilter\n" + + " ofs0078:\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " jump ofs01ff\n" + + " pushwith\n" + + " setlocal0\n" + + " getlocal3\n" + + " ofs0083:\n" + + " label\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " pushfalse\n" + + " iffalse ofs0094\n" + + " returnvoid\n" + + " popscope\n" + + " equals\n" + + " setlocal1\n" + + " newactivation\n" + + " increment\n" + + " decrement\n" + + " ofs0094:\n" + + " jump ofs01ff\n" + + " pushwith\n" + + " setlocal2\n" + + " ofs009a:\n" + + " label\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " getlocal0\n" + + " jump ofs00a9\n" + + " modulo\n" + + " callsupervoid QName(PackageInternalNs(\"xxxx\"),\"tmp\"), 5\n" + + " pushwith\n" + + " ofs00a9:\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " jump ofs00b4\n" + + " ifge ofs00b4\n" + + " ofs00b4:\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " jump ofs00c1\n" + + " popscope\n" + + " callproplex QName(PackageNamespace(\"\"),\"xxx\"), 18\n" + + " setlocal2\n" + + " ofs00c1:\n" + + " jump ofs01ff\n" + + " ifstrictne ofs00c9\n" + + " ofs00c9:\n" + + " label\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " jump ofs00d8\n" + + " nextname\n" + + " dxns \"position\"\n" + + " pushwith\n" + + " setlocal2\n" + + " returnvalue\n" + + " ofs00d8:\n" + + " jump ofs01ff\n" + + " pushwith\n" + + " setlocal3\n" + + " getlocal1\n" + + " ofs00df:\n" + + " label\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " getlocal0\n" + + " jump ofs00ef\n" + + " popscope\n" + + " pushwith\n" + + " newfunction 24\n" + + " pop\n" + + " setlocal3\n" + + " ofs00ef:\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " jump ofs00fa\n" + + " ifne ofs00fa\n" + + " ofs00fa:\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " jump ofs0107\n" + + " popscope\n" + + " callproplex TypeName(QName(PackageNamespace(\"__AS3__.vec\"),\"Vector\")), 15\n" + + " throw\n" + + " ofs0107:\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " pushtrue\n" + + " iftrue ofs0117\n" + + " returnvoid\n" + + " checkfilter\n" + + " pushwith\n" + + " newfunction 51\n" + + " throw\n" + + " instanceof\n" + + " ofs0117:\n" + + " getlocal0\n" + + " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n" + + " pushfalse\n" + + " iffalse ofs0127\n" + + " returnvoid\n" + + " popscope\n" + + " setlocal1\n" + + " istypelate\n" + + " setlocal1\n" + + " setlocal2\n" + + " divide\n" + + " ofs0127:\n" + + " jump ofs01ff\n" + + " pushwith\n" + + " setlocal2\n" + + " getlocal1\n" + + " jump ofs0132\n" + + " ofs0132:\n" + + " getlex QName(PackageNamespace(\"\"),\"Abc\")\n" + + " getproperty QName(PackageNamespace(\"\"),\"value\")\n" + + " setlocal1\n" + + " jump ofs013f\n" + + " ifle ofs013f\n" + + " ofs013f:\n" + + " pushbyte 1\n" + + " getlocal1\n" + + " ifstricteq ofs014a\n" + + " jump ofs015e\n" + + " ofs014a:\n" + + " pushbyte 0\n" + + " jump ofs0156\n" + + " pushscope\n" + + " setlocal0\n" + + " istypelate\n" + + " nextvalue\n" + + " istypelate\n" + + " decrement\n" + + " ofs0156:\n" + + " jump ofs01e3\n" + + " ifgt ofs015e\n" + + " ofs015e:\n" + + " pushbyte 2\n" + + " getlocal1\n" + + " ifstricteq ofs0169\n" + + " jump ofs0178\n" + + " ofs0169:\n" + + " pushbyte 1\n" + + " jump ofs01e3\n" + + " jump ofs0173\n" + + " ofs0173:\n" + + " dup\n" + + " callsuper QName(PackageNamespace(\"xxx\"),\"aaa\"), 27\n" + + " pop\n" + + " ofs0178:\n" + + " pushbyte 3\n" + + " getlocal1\n" + + " ifstricteq ofs0183\n" + + " jump ofs018b\n" + + " ofs0183:\n" + + " pushbyte 2\n" + + " jump ofs01e3\n" + + " pushwith\n" + + " setlocal0\n" + + " ofs018b:\n" + + " pushbyte 4\n" + + " getlocal1\n" + + " ifstrictne ofs01a2\n" + + " pushbyte 3\n" + + " jump ofs01e3\n" + + " jump ofs019c\n" + + " ofs019c:\n" + + " convert_s\n" + + " construct 110\n" + + " pushwith\n" + + " nextvalue\n" + + " setlocal2\n" + + " ofs01a2:\n" + + " pushbyte 5\n" + + " getlocal1\n" + + " ifstricteq ofs01ad\n" + + " jump ofs01b5\n" + + " ofs01ad:\n" + + " pushbyte 4\n" + + " jump ofs01e3\n" + + " pushwith\n" + + " setlocal2\n" + + " ofs01b5:\n" + + " pushbyte 6\n" + + " jump ofs01bf\n" + + " ifgt ofs01bf\n" + + " ofs01bf:\n" + + " getlocal1\n" + + " ifstrictne ofs01d4\n" + + " pushbyte 5\n" + + " jump ofs01e3\n" + + " jump ofs01ce\n" + + " ofs01ce:\n" + + " popscope\n" + + " pushwith\n" + + " newfunction 49\n" + + " pop\n" + + " checkfilter\n" + + " ofs01d4:\n" + + " jump ofs01e1\n" + + " throw\n" + + " setlocal3\n" + + " getlocal2\n" + + " pushbyte 6\n" + + " jump ofs01e1\n" + + " ofs01e1:\n" + + " pushbyte 6\n" + + " ofs01e3:\n" + + " kill 1\n" + + " lookupswitch ofs00df, [ofs002a, ofs0040, ofs0061, ofs0083, ofs009a, ofs00c9, ofs00df]\n" + + " ofs01ff:\n" + + " getlocal0\n" + + " pushbyte 0\n" + + " initproperty QName(PackageNamespace(\"\"),\"testX\")\n" + + " jump ofs020e\n" + + " popscope\n" + + " pushwith\n" + + " setlocal1\n" + + " bitxor\n" + + " convert_d\n" + + " setlocal2\n" + + " ofs020e:\n" + + " pushfalse\n" + + " iffalse ofs021a\n" + + " returnvoid\n" + + " typeof\n" + + " checkfilter\n" + + " bitand\n" + + " in\n" + + " convert_s\n" + + " nextvalue\n" + + " ofs021a:\n" + + " jump ofs0222\n" + + " ifgt ofs0222\n" + + " ofs0222:\n" + + " pushtrue\n" + + " iftrue ofs022d\n" + + " returnvoid\n" + + " setlocal2\n" + + " callsuper QName(PackageNamespace(\"xxx\"),\"aaa\"), 15\n" + + " throw\n" + + " ofs022d:", " this.testA = 0;\n" + + " switch(Abc.value)\n" + + " {\n" + + " case 1:\n" + + " this.test();\n" + + " break;\n" + + " case 2:\n" + + " this.test();\n" + + " this.test();\n" + + " this.test();\n" + + " break;\n" + + " case 3:\n" + + " this.test();\n" + + " this.test();\n" + + " break;\n" + + " case 4:\n" + + " this.test();\n" + + " break;\n" + + " case 5:\n" + + " this.test();\n" + + " this.test();\n" + + " this.test();\n" + + " break;\n" + + " case 6:\n" + + " this.test();\n" + + " break;\n" + + " default:\n" + + " this.test();\n" + + " this.test();\n" + + " this.test();\n" + + " this.test();\n" + + " this.test();\n" + + " }\n" + + " this.testX = 0;\n"); + } + // TODO: JPEXS @Test public void testNotRemoveParams() throws Exception { String res = recompile("function tst(p1,p2){" diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicAirDecompileTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicAirDecompileTest.java index f22313df0..9d67cd2b8 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicAirDecompileTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicAirDecompileTest.java @@ -1937,6 +1937,35 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile false); } + @Test + public void testSwitchBig() { + decompileMethod("classic_air", "testSwitchBig", "var k:int = 10;\r\n" + + "switch(k)\r\n" + + "{\r\n" + + "case \"A\":\r\n" + + "trace(\"A\");\r\n" + + "break;\r\n" + + "case \"B\":\r\n" + + "case \"C\":\r\n" + + "trace(\"BC\");\r\n" + + "break;\r\n" + + "case \"D\":\r\n" + + "default:\r\n" + + "case \"E\":\r\n" + + "trace(\"D-default-E\");\r\n" + + "break;\r\n" + + "case \"F\":\r\n" + + "trace(\"F no break\");\r\n" + + "case \"G\":\r\n" + + "trace(\"G\");\r\n" + + "break;\r\n" + + "case \"H\":\r\n" + + "trace(\"H last\");\r\n" + + "}\r\n" + + "trace(\"after switch\");\r\n", + false); + } + @Test public void testSwitchComma() { decompileMethod("classic_air", "testSwitchComma", "var b:int = 5;\r\n" @@ -1948,7 +1977,7 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile + "break;\r\n" + "case \"B\":\r\n" + "trace(\"is B\");\r\n" - + "case \"C\":\r\n" + + "case 7, \"C\":\r\n" + "trace(\"is C\");\r\n" + "}\r\n", false); diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicDecompileTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicDecompileTest.java index f47839db4..fd21650be 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicDecompileTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicDecompileTest.java @@ -424,8 +424,8 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes decompileMethod("classic", "testDefaultNotLastGrouped", "var k:* = 10;\r\n" + "switch(k)\r\n" + "{\r\n" - + "case \"six\":\r\n" + "default:\r\n" + + "case \"six\":\r\n" + "trace(\"def and 6\");\r\n" + "case \"five\":\r\n" + "trace(\"def and 6 and 5\");\r\n" @@ -1924,6 +1924,35 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testSwitchBig() { + decompileMethod("classic", "testSwitchBig", "var k:* = 10;\r\n" + + "switch(k)\r\n" + + "{\r\n" + + "case \"A\":\r\n" + + "trace(\"A\");\r\n" + + "break;\r\n" + + "case \"B\":\r\n" + + "case \"C\":\r\n" + + "trace(\"BC\");\r\n" + + "break;\r\n" + + "case \"D\":\r\n" + + "default:\r\n" + + "case \"E\":\r\n" + + "trace(\"D-default-E\");\r\n" + + "break;\r\n" + + "case \"F\":\r\n" + + "trace(\"F no break\");\r\n" + + "case \"G\":\r\n" + + "trace(\"G\");\r\n" + + "break;\r\n" + + "case \"H\":\r\n" + + "trace(\"H last\");\r\n" + + "}\r\n" + + "trace(\"after switch\");\r\n", + false); + } + @Test public void testSwitchComma() { decompileMethod("classic", "testSwitchComma", "var b:int = 5;\r\n" diff --git a/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.air.swf b/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.air.swf index 9771373ba..e4ffa1267 100644 Binary files a/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.air.swf and b/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.air.swf differ diff --git a/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.flex.swf b/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.flex.swf index d175f4b6a..546fb0dd9 100644 Binary files a/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.flex.swf and b/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.flex.swf differ diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/Main.as b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as index 3d74b6a86..81e75345a 100644 --- a/libsrc/ffdec_lib/testdata/as3_new/src/Main.as +++ b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as @@ -118,6 +118,7 @@ package TestStringConcat; TestStrings; TestSwitch; + TestSwitchBig; TestSwitchContinue; TestSwitchComma; TestSwitchDefault; diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestSwitchBig.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestSwitchBig.as new file mode 100644 index 000000000..007492623 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestSwitchBig.as @@ -0,0 +1,34 @@ +package tests +{ + + public class TestSwitchBig + { + public function run():* + { + var k:* = 10; + switch (k) + { + case "A": + trace("A"); + break; + case "B": + case "C": + trace("BC"); + break; + case "D": + default: + case "E": + trace("D-default-E"); + break; + case "F": + trace("F no break"); + case "G": + trace("G"); + break; + case "H": + trace("H last"); + } + trace("after switch"); + } + } +}