diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b3973ecd..62c8c6935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ All notable changes to this project will be documented in this file. - [#2477] AS1/2 deobfuscation - and/or operators, jumps before function start, jumps to function end, jumps in for..in return/break - [#2477] AS1/2 Switch in last statement of switch break labels +- [#2338] AS2 Reading large classes with incorrect if jumps on the beginning +- [#2338] AS1/2/3 Obfuscated code - jump to jump handling ### Changed - Icon of "Deobfuscation options" menu from pile of pills to medkit diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index cfeae4fb4..88d1001df 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -4751,7 +4751,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { } int version = swf == null ? SWF.DEFAULT_VERSION : swf.version; - ActionList list = ActionListReader.readActionListTimeout(listeners, rri, version, prevLength, prevLength + actionBytes.getLength(), src.toString()/*FIXME?*/, deobfuscationMode); + ActionList list = ActionListReader.readActionListTimeout(src, listeners, rri, version, prevLength, prevLength + actionBytes.getLength(), src.toString()/*FIXME?*/, deobfuscationMode); list.fileData = actionBytes.getArray(); list.deobfuscationMode = deobfuscationMode; if (swf != null) { @@ -6289,9 +6289,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { DoInitActionTag doi = (DoInitActionTag) src; String exportName = doi.getSwf().getCharacter(doi.getCharacterId()).getExportName(); if (exportName != null && exportName.startsWith("__Packages.")) { - if (uninitializedAs2ClassTraits == null) { - return true; - } + return true; } } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java index 25be32da5..e806a7219 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java @@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.BaseLocalData; import com.jpexs.decompiler.flash.DisassemblyListener; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.ValueTooLargeException; import com.jpexs.decompiler.flash.action.as2.Trait; import com.jpexs.decompiler.flash.action.deobfuscation.ActionDeobfuscator; import com.jpexs.decompiler.flash.action.model.ActionItem; @@ -639,8 +640,15 @@ public abstract class Action implements GraphSourceItem { writer.appendNoHilight(" "); } - byte[] bytes = a.getBytes(version); - writer.appendNoHilight(Helper.bytesToHexString(bytes)); + + byte[] bytes; + try { + bytes = a.getBytes(version); + writer.appendNoHilight(Helper.bytesToHexString(bytes)); + } catch (ValueTooLargeException tooLarge) { + bytes = new byte[0]; + writer.appendNoHilight("!TOO LARGE!"); + } if (Configuration.showOriginalBytesInPcodeHex.get()) { if (fileData != null && fileOffset != -1 && fileData.length > fileOffset + bytes.length - 1) { @@ -1074,7 +1082,7 @@ public abstract class Action implements GraphSourceItem { HashMap variablesBackup = new LinkedHashMap<>(variables); HashMap functionsBackup = new LinkedHashMap<>(functions); try { - return ActionGraph.translateViaGraph(needsUninitializedClassFieldsDetection, uninitializedClassTraits, null, insideDoInitAction, insideFunction, regNames, variables, functions, actions, version, staticOperation, path, charset, 0); + return ActionGraph.translateViaGraph(needsUninitializedClassFieldsDetection, uninitializedClassTraits, null, insideDoInitAction, insideFunction, regNames, variables, functions, fixActionsClassHeader(actions, needsUninitializedClassFieldsDetection, charset, path), version, staticOperation, path, charset, 0); } catch (SecondPassException spe) { variables.clear(); variables.putAll(variablesBackup); @@ -1083,6 +1091,20 @@ public abstract class Action implements GraphSourceItem { return ActionGraph.translateViaGraph(needsUninitializedClassFieldsDetection, uninitializedClassTraits, spe.getData(), insideDoInitAction, insideFunction, regNames, variables, functions, actions, version, staticOperation, path, charset, 0); } } + + private static List fixActionsClassHeader(List actions, boolean needsUninitializedClassFieldsDetection, String charset, String path) { + ActionList alist = new ActionList(actions, charset); + if (needsUninitializedClassFieldsDetection) { + try { + new ActionIncorrectClassHeaderRemover().actionListParsed(alist, null); + } catch (InterruptedException ex) { + //ignored + } catch (Throwable ex) { + Logger.getLogger(ActionGraphSource.class.getName()).log(Level.SEVERE, "Removing incorrect class header failed: " + path, ex); + } + } + return alist; + } /** * Translates this function to stack and output. diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java index d4df30160..4ae3046c5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java @@ -116,7 +116,7 @@ public class ActionGraph extends Graph { * trait */ private Map> uninitializedClassTraits; - + /** * Constructs ActionGraph * diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java index 8bd8ad110..cb210c232 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.action; import com.jpexs.decompiler.flash.BaseLocalData; +import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.graph.Graph; import com.jpexs.decompiler.graph.GraphPart; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionIncorrectClassHeaderRemover.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionIncorrectClassHeaderRemover.java new file mode 100644 index 000000000..30d9d4dd0 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionIncorrectClassHeaderRemover.java @@ -0,0 +1,88 @@ +/* + * 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.action; + +import com.jpexs.decompiler.flash.BaseLocalData; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.action.fastactionlist.ActionItem; +import com.jpexs.decompiler.flash.action.fastactionlist.FastActionList; +import com.jpexs.decompiler.flash.action.fastactionlist.FastActionListIterator; +import com.jpexs.decompiler.flash.action.swf4.ActionGetVariable; +import com.jpexs.decompiler.flash.action.swf4.ActionIf; +import com.jpexs.decompiler.flash.action.swf4.ActionJump; +import com.jpexs.decompiler.flash.action.swf4.ActionNot; +import com.jpexs.decompiler.flash.action.swf4.ActionPop; +import com.jpexs.decompiler.flash.action.swf4.ActionPush; +import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal; +import com.jpexs.decompiler.flash.action.swf5.ActionGetMember; +import com.jpexs.decompiler.flash.action.swf5.ActionNewObject; +import com.jpexs.decompiler.flash.action.swf5.ActionSetMember; +import com.jpexs.decompiler.flash.helpers.SWFDecompilerAdapter; +import com.jpexs.decompiler.graph.GraphTargetItem; +import com.jpexs.decompiler.graph.TranslateStack; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Incorrect class header remover. + * This neutralizes ActionIfs from the beginning of the class. + * Without this, the ifs can contain long and incorrect values. + * The AS2 class detector handles this properly. + * @author JPEXS + */ +public class ActionIncorrectClassHeaderRemover extends SWFDecompilerAdapter { + + @Override + public void actionListParsed(ActionList actions, SWF swf) throws InterruptedException { + FastActionList list = new FastActionList(actions); + int ip = 0; + BaseLocalData ld = new ActionLocalData(null, true, new HashMap<>()); + TranslateStack stack = new TranslateStack(""); + List output = new ArrayList<>(); + FastActionListIterator iterator = list.iterator(); + while (iterator.hasNext()) { + ActionItem ai = iterator.next(); + Action a = ai.action; + if ( + (a instanceof ActionPush) + || (a instanceof ActionGetVariable) + || (a instanceof ActionGetMember) + || (a instanceof ActionNot) + || (a instanceof ActionNewObject) + || (a instanceof ActionSetMember) + || (a instanceof ActionPop) + || (a instanceof ActionConstantPool) + || (a instanceof ActionDefineLocal) // obfuscated variable assignments + ) { + a.translate(ld, stack, output, ip, ""); + } else if (a instanceof ActionJump) { + ActionJump jump = (ActionJump) a; + iterator.setCurrent(ai.getJumpTarget());; + } else if (a instanceof ActionIf) { + iterator.add(new ActionPop()); + iterator.prev(); + iterator.remove(); + iterator.next(); + } else { + break; + } + } + actions.setActions(list.toActionList()); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionListReader.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionListReader.java index 21e6b8b73..e2efd71db 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionListReader.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionListReader.java @@ -28,10 +28,14 @@ import com.jpexs.decompiler.flash.action.swf4.ActionIf; import com.jpexs.decompiler.flash.action.swf4.ActionJump; import com.jpexs.decompiler.flash.action.swf4.ActionPush; import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; +import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; +import com.jpexs.decompiler.flash.tags.base.ASMSource; import com.jpexs.decompiler.graph.GraphSourceItemContainer; import com.jpexs.helpers.CancellableWorker; +import com.jpexs.helpers.Helper; import com.jpexs.helpers.stat.Statistics; import java.io.IOException; import java.util.ArrayList; @@ -40,6 +44,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.Stack; import java.util.TreeMap; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -70,6 +75,7 @@ public class ActionListReader { * Reads list of actions from the stream. Reading ends with * ActionEndFlag(=0) or end of the stream. * + * @param asm ASM source * @param listeners List of listeners * @param sis SWF input stream * @param version SWF version @@ -82,13 +88,13 @@ public class ActionListReader { * @throws InterruptedException On interrupt * @throws TimeoutException On timeout */ - public static ActionList readActionListTimeout(final List listeners, final SWFInputStream sis, final int version, final int ip, final int endIp, final String path, final int deobfuscationMode) throws IOException, InterruptedException, TimeoutException { + public static ActionList readActionListTimeout(ASMSource asm, final List listeners, final SWFInputStream sis, final int version, final int ip, final int endIp, final String path, final int deobfuscationMode) throws IOException, InterruptedException, TimeoutException { try { ActionList actions = CancellableWorker.call("script.readActionList", new Callable() { @Override public ActionList call() throws IOException, InterruptedException { - return readActionList(listeners, sis, version, ip, endIp, path, deobfuscationMode); + return readActionList(asm, listeners, sis, version, ip, endIp, path, deobfuscationMode); } }, Configuration.decompilationTimeoutSingleMethod.get(), TimeUnit.SECONDS); @@ -110,6 +116,7 @@ public class ActionListReader { * Reads list of actions from the stream. Reading ends with * ActionEndFlag(=0) or end of the stream. * + * @param asm ASM source * @param listeners List of listeners * @param sis SWF input stream * @param version SWF version @@ -121,7 +128,7 @@ public class ActionListReader { * @throws IOException On I/O error * @throws InterruptedException On interrupt */ - public static ActionList readActionList(List listeners, SWFInputStream sis, int version, int ip, int endIp, String path, int deobfuscationMode) throws IOException, InterruptedException { + public static ActionList readActionList(ASMSource asm, List listeners, SWFInputStream sis, int version, int ip, int endIp, String path, int deobfuscationMode) throws IOException, InterruptedException { // Map of the actions. Use TreeMap to sort the keys in ascending order // actionMap and nextOffsets should contain exactly the same keys Map actionMap = new TreeMap<>(); @@ -180,15 +187,14 @@ public class ActionListReader { System.err.println("loc" + Helper.formatAddress(a.getAddress()) + " (" + p + "): " + a.getASMSource(actions, new HashSet(), ScriptExportMode.PCODE)); p++; }*/ - //TODO: This cleaner needs to be executed only before actual decompilation, not when disassembly only try { - new ActionDefineFunctionPushRegistersCleaner().actionListParsed(actions, sis.getSwf()); + new ActionDefineFunctionPushRegistersCleaner().actionListParsed(actions, null); } catch (ThreadDeath | InterruptedException ex) { - throw ex; + //ignored } catch (Throwable ex) { - logger.log(Level.SEVERE, "Cleaning push registers in ActionDefineFunction failed: " + path, ex); - } - + Logger.getLogger(ActionGraphSource.class.getName()).log(Level.SEVERE, "Cleaning push registers in ActionDefineFunction failed: " + path, ex); + } + return actions; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/as2/ActionScript2ClassDetector.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/as2/ActionScript2ClassDetector.java index 86829bd07..e8f0c9186 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/as2/ActionScript2ClassDetector.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/as2/ActionScript2ClassDetector.java @@ -16,7 +16,6 @@ */ package com.jpexs.decompiler.flash.action.as2; -import com.jpexs.decompiler.flash.IdentifiersDeobfuscation; import com.jpexs.decompiler.flash.action.ActionGraphTargetDialect; import com.jpexs.decompiler.flash.action.model.CallFunctionActionItem; import com.jpexs.decompiler.flash.action.model.CallMethodActionItem; @@ -61,7 +60,9 @@ import java.util.logging.Logger; import java.util.regex.Pattern; /** - * Detects AS2 classes inside DoInitAction tags + * Detects AS2 classes inside DoInitAction tags. + * + * This requires ActionIncorrectClassHeaderRemover is used on action list. * * @author JPEXS */ @@ -913,61 +914,80 @@ public class ActionScript2ClassDetector { §§pop(); if(!_global.a.b.c.D) { - ..class_content... + ..class_content.. } §§pop(); */ + + /* + Stripped variant: (ActionIncorrectClassHeaderRemover used) + !!_global.a + _global.a = new Object(); + §§pop(); + !!_global.a.b + _global.a.b = new Object(); + §§pop(); + !!_global.a.b.c + _global.a.b.c = new Object(); + §§pop(); + !!_global.a.b.D + ..class_content.. + */ List pathToSearchVariant1 = new ArrayList<>(); pathToSearchVariant1.add("_global"); check_variant1: for (int checkPos = pos; checkPos < commands.size(); checkPos++) { GraphTargetItem t = commands.get(checkPos); - if (t instanceof IfItem) { - IfItem ifItem = (IfItem) t; - if (ifItem.expression instanceof NotItem) { - NotItem nti = (NotItem) ifItem.expression; - GraphTargetItem condType = nti.value; - Reference newMemberNameRef = new Reference<>(""); + //if (t instanceof IfItem) { + // IfItem ifItem = (IfItem) t; + // if (ifItem.expression instanceof NotItem) { + if (!(t instanceof NotItem)) { + break; + } + NotItem nti = (NotItem) t; //ifItem.expression; + GraphTargetItem condType = nti.value; + if (!(condType instanceof NotItem)) { + break; + } + condType = ((NotItem) condType).value; + + Reference newMemberNameRef = new Reference<>(""); - if (isMemberOfPath(condType, pathToSearchVariant1, newMemberNameRef)) { - pathToSearchVariant1.add(newMemberNameRef.getVal()); + if (!isMemberOfPath(condType, pathToSearchVariant1, newMemberNameRef)) { + break; + } + pathToSearchVariant1.add(newMemberNameRef.getVal()); - //_global.a.b.c = new Object(); - if ((ifItem.onTrue.size() == 1) && (ifItem.onTrue.get(0) instanceof SetMemberActionItem) && (((SetMemberActionItem) ifItem.onTrue.get(0)).value instanceof NewObjectActionItem)) { - //skip §§pop item if its there right after if - if (checkPos + 1 < commands.size()) { - GraphTargetItem tnext = commands.get(checkPos + 1); - if (tnext instanceof PopItem) { - checkPos++; - } - } - continue check_variant1; - } - List classPath = pathToSearchVariant1; - classPath.remove(0); //remove _global - if (ifItem.onTrue.isEmpty()) { //if can have zero offset as the code is larger than bytes limit. TODO: make this check also for variant 2 (?) - if (this.checkClassContent(uninitializedClassTraits, commands, variables, checkPos + 1, pos, commands.size() - 1, commands, classPath, scriptPath)) { - return true; - } else { - break check_variant1; - } - } else if (this.checkClassContent(uninitializedClassTraits, ifItem.onTrue, variables, 0, pos, checkPos, commands, classPath, scriptPath)) { - return true; - } else { - break check_variant1; - } - } else { - break check_variant1; + if (checkPos + 1 >= commands.size()) { + break; + } + checkPos++; + t = commands.get(checkPos); + //_global.a.b.c = new Object(); + if ((t instanceof SetMemberActionItem) && (((SetMemberActionItem) t).value instanceof NewObjectActionItem)) { + //skip §§pop item if its there right after if + if (checkPos + 1 < commands.size()) { + GraphTargetItem tnext = commands.get(checkPos + 1); + if (tnext instanceof PopItem) { + checkPos++; } - } else { - break check_variant1; //not an if ! } + continue check_variant1; + } + List classPath = pathToSearchVariant1; + classPath.remove(0); //remove _global + + List parts = new ArrayList<>(commands); + if (commands.get(commands.size() - 1) instanceof PopItem) { + parts.remove(parts.size() - 1); + } + if (this.checkClassContent(uninitializedClassTraits, parts, variables, checkPos, pos, commands.size() - 1, commands, classPath, scriptPath)) { + return true; } else { - break check_variant1; //not an if + break; } } //check_variant1 - /* Variant 2: @@ -986,42 +1006,56 @@ public class ActionScript2ClassDetector { { _global.a.b.c = new Object(); } - ..class_content.. + ..class_content.. } + + Stripped version: (ActionIncorrectClassHeaderRemover used) + !!a.b.c.D + !!a + _global.a = new Object(); + !!a.b + _global.a.b = new Object(); + !!a.b.c + _global.a.b.c = new Object(); + ..class_content.. */ List variant2CurrentPath = new ArrayList<>(); check_variant2: - if (commands.get(pos) instanceof IfItem) { - IfItem ifItem = (IfItem) commands.get(pos); - if (ifItem.expression instanceof NotItem) { - NotItem nti = (NotItem) ifItem.expression; + if (commands.get(pos) instanceof NotItem) { + NotItem nti = (NotItem) commands.get(pos); + if (nti.value instanceof NotItem) { + nti = (NotItem) nti.value; List memPath = getMembersPath(nti.value); if (memPath == null) { break check_variant2; } - if (ifItem.onTrue.size() < memPath.size()) { - break check_variant2; - } variant2CurrentPath.clear(); - List parts = ifItem.onTrue; int checkPos = 0; for (; checkPos < memPath.size() - 1; checkPos++) { variant2CurrentPath.add(memPath.get(checkPos)); - if (!(parts.get(checkPos) instanceof IfItem)) { + int realPos = pos + 1 + checkPos * 2; + if (!(commands.get(realPos) instanceof NotItem)) { break check_variant2; } - IfItem ifItem2 = (IfItem) parts.get(checkPos); - if (ifItem2.expression instanceof NotItem) { - NotItem nti2 = (NotItem) ifItem2.expression; - List if2Path = getMembersPath(nti2.value); - if (!variant2CurrentPath.equals(if2Path)) { - break check_variant2; - } + NotItem nti2 = (NotItem) commands.get(realPos); + if (!(nti2.value instanceof NotItem)) { + break check_variant2; } + nti2 = (NotItem) nti2.value; + + + List if2Path = getMembersPath(nti2.value); + if (!variant2CurrentPath.equals(if2Path)) { + break check_variant2; + } } - if (checkClassContent(uninitializedClassTraits, parts, variables, checkPos, pos, pos, commands, memPath, scriptPath)) { + List parts = new ArrayList<>(commands); + if (commands.get(commands.size() - 1) instanceof PopItem) { + parts.remove(parts.size() - 1); + } + if (checkClassContent(uninitializedClassTraits, parts, variables, pos + 1 + checkPos * 2, pos, commands.size() - 1, commands, memPath, scriptPath)) { return true; - } + } } } //check_variant2 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/deobfuscation/ActionDeobfuscator.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/deobfuscation/ActionDeobfuscator.java index 949630b72..331815aed 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/deobfuscation/ActionDeobfuscator.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/deobfuscation/ActionDeobfuscator.java @@ -77,6 +77,7 @@ import com.jpexs.decompiler.flash.action.swf5.ActionToString; import com.jpexs.decompiler.flash.action.swf5.ActionTypeOf; import com.jpexs.decompiler.flash.action.swf6.ActionGreater; import com.jpexs.decompiler.flash.action.swf6.ActionStringGreater; +import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.ecma.EcmaScript; import com.jpexs.decompiler.flash.helpers.SWFDecompilerAdapter; @@ -119,22 +120,82 @@ public class ActionDeobfuscator extends SWFDecompilerAdapter { boolean useVariables = false; while (changed) { while (changed) { - changed = removeGetTimes(fastActions); - changed |= removeObfuscationIfs(fastActions, fakeFunctions, useVariables); - actions.setActions(fastActions.toActionList()); - changed |= ActionListReader.fixConstantPools(null, actions); - if (!changed && !useVariables) { - useVariables = true; - changed = true; + while (changed) { + changed = removeGetTimes(fastActions); + changed |= removeObfuscationIfs(fastActions, fakeFunctions, useVariables); + actions.setActions(fastActions.toActionList()); + changed |= ActionListReader.fixConstantPools(null, actions); + if (!changed && !useVariables) { + useVariables = true; + changed = true; + } + } + if (Configuration.deobfuscateAs12RemoveInvalidNamesAssignments.get()) { + changed = removeObfuscatedUnusedVariables(fastActions); + actions.setActions(fastActions.toActionList()); } } - if (Configuration.deobfuscateAs12RemoveInvalidNamesAssignments.get()) { - changed = removeObfuscatedUnusedVariables(fastActions); - actions.setActions(fastActions.toActionList()); + changed = updateJumpsAfterFunction(fastActions); + if (changed) { + useVariables = false; } } } + private static boolean updateJumpsAfterFunction(FastActionList list) { + Stack functionEnds = new Stack<>(); + boolean changed = false; + FastActionListIterator iterator = list.iterator(); + while (iterator.hasNext()) { + ActionItem ai = iterator.next(); + Action a = ai.action; + ActionItem currentFunctionEnd = null; + + while (!functionEnds.isEmpty()) { + currentFunctionEnd = functionEnds.peek(); + + if (a.getAddress() >= currentFunctionEnd.action.getAddress()) { + functionEnds.pop(); + currentFunctionEnd = null; + } else { + break; + } + } + if (a instanceof ActionDefineFunction) { + //ActionDefineFunction f = (ActionDefineFunction) a; + functionEnds.push(ai.getContainerLastActions().get(0).next); + } + if (a instanceof ActionDefineFunction2) { + //ActionDefineFunction2 f = (ActionDefineFunction2) a; + functionEnds.push(ai.getContainerLastActions().get(0).next); + } + ai.getContainerLastActions(); + if (currentFunctionEnd != null) { + if (a instanceof ActionJump) { + ActionJump jump = (ActionJump) a; + long targetAddress = jump.getTargetAddress(); + if (targetAddress > currentFunctionEnd.action.getAddress()) { + //System.err.println("Jump after function at " + Helper.formatAddress(a.getFileOffset())); + //jump.setJumpOffset((int) (currentMaxAddress - (jump.getAddress() + jump.getBytesLength()))); + ai.setJumpTarget(currentFunctionEnd); + changed = true; + } + } + if (a instanceof ActionIf) { + ActionIf aIf = (ActionIf) a; + long targetAddress = aIf.getTargetAddress(); + if (targetAddress > currentFunctionEnd.action.getAddress()) { + //System.err.println("Jump after function at " + Helper.formatAddress(a.getFileOffset())); + //aIf.setJumpOffset((int) (currentMaxAddress - (aIf.getAddress() + aIf.getBytesLength()))); + ai.setJumpTarget(currentFunctionEnd); + changed = true; + } + } + } + } + return changed; + } + private boolean removeGetTimes(FastActionList actions) throws InterruptedException { if (actions.isEmpty()) { return false; @@ -148,7 +209,7 @@ public class ActionDeobfuscator extends SWFDecompilerAdapter { actions.removeUnreachableActions(); actions.removeZeroJumps(); getTimeCount = 0; - + // GetTime, If => Jump, assume GetTime > 0 FastActionListIterator iterator = actions.iterator(); while (iterator.hasNext()) { @@ -164,30 +225,28 @@ public class ActionDeobfuscator extends SWFDecompilerAdapter { ActionJump jump = new ActionJump(0, actions.getCharset()); ActionItem jumpItem = new ActionItem(jump); jumpItem.setJumpTarget(a2Item.getJumpTarget()); - + /* This simple approach does not work iterator.remove(); // GetTime iterator.next(); iterator.remove(); // If iterator.add(jumpItem); // replace If with Jump - */ - + */ //Must remove + add in this particular order, //otherwise if will map the jumpsTo after the new jumpItem. - iterator.next(); //to If iterator.add(jumpItem); // Add Jump after If - + //Now go back and remove GetTime, If... iterator.prev(); //to If iterator.prev(); //to GetTime iterator.remove(); //GetTime - iterator.next(); + iterator.next(); iterator.remove(); //If - iterator.next(); - + iterator.next(); + changed = true; ret = true; getTimeCount--; @@ -213,29 +272,27 @@ public class ActionDeobfuscator extends SWFDecompilerAdapter { iterator.next(); iterator.remove(); // If iterator.add(jumpItem); // replace If with Jump - */ - - + */ + //Must remove + add in this particular order, //otherwise if will map the jumpsTo after the new jumpItem. - iterator.next(); //to Increment iterator.next(); //to If iterator.add(jumpItem); // Add Jump after If - + //Now go back and remove GetTime, Increment, If... iterator.prev(); //to If iterator.prev(); //to Increment iterator.prev(); //to GetTime - + iterator.remove(); //GetTime - iterator.next(); + iterator.next(); iterator.remove(); //Increment iterator.next(); iterator.remove(); //If - + iterator.next(); - + changed = true; ret = true; } @@ -254,7 +311,7 @@ public class ActionDeobfuscator extends SWFDecompilerAdapter { boolean ret = false; actions.removeUnreachableActions(); actions.removeZeroJumps(); - + ActionConstantPool cPool = getConstantPool(actions); LocalDataArea localData = new LocalDataArea(new Stage(null), true); localData.stack = new FixItemCounterStack(); @@ -505,6 +562,7 @@ public class ActionDeobfuscator extends SWFDecompilerAdapter { /** * Check if the name is fake. + * * @param name Name * @return True if the name is fake */ @@ -514,6 +572,7 @@ public class ActionDeobfuscator extends SWFDecompilerAdapter { /** * Execute actions. + * * @param item Action item * @param localData Local data * @param constantPool Constant pool @@ -527,7 +586,7 @@ public class ActionDeobfuscator extends SWFDecompilerAdapter { if (item.action.isExit()) { return; } - + FixItemCounterStack stack = (FixItemCounterStack) localData.stack; int instructionsProcessed = 0; int skippedInstructions = 0; @@ -722,9 +781,9 @@ public class ActionDeobfuscator extends SWFDecompilerAdapter { if (isEnd) { break; - } + } } - + if (jumpedHere && item.prev.isContainerLastAction()) { break; } 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 a9a7424a6..9ff843b17 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -87,7 +87,7 @@ public class Graph { * Graph entry points */ public List heads; - + /** * Graph target dialect */ @@ -102,7 +102,7 @@ public class Graph { * Exceptions in the graph */ private final List exceptions; - + /** * Graph startIp */ @@ -128,7 +128,7 @@ public class Graph { * Debug flag to not process Ifs */ protected boolean debugDoNotProcess = false; - + /** * Logger */ @@ -1016,65 +1016,42 @@ public class Graph { } expandGotos(ret); processIfs(ret); - processSwitches2(ret); + processSwitchesAndLoops2(ret); finalProcessStack(stack, ret, path); makeAllCommands(ret, stack); finalProcessAll(null, ret, 0, getFinalData(localData, loops, throwStates), path); return ret; } - /** - * This is needed to avoid loop identifiers in AS1/2. AS3 supports them, but AS1/2 not. - * - * loop1: switch(a) { //has loop identifier - * case 1: - * trace("1"); - * break; - * case 2: //last case - * trace("2"); - * switch(b) { //last command is switch - * case 3: - * case 4: - * trace("4"); - * break loop1; //breaks parent loop - * case 5: - * trace("5"); - * break loop1; - * case 6: - * trace("6"); - * } - * } - * - * ==> - * - * switch(a) { - * case 1: - * trace("1"); - * break; - * case 2: - * trace("2"); - * switch(b) { - * case 3: - * case 4: - * trace("4"); - * break; - * case 5: - * trace("5"); - * break; - * case 6: - * trace("6"); - * } - * } + * This is needed to avoid loop identifiers in AS1/2. AS3 supports them, but + * AS1/2 not. + * + * loop1: switch(a) { //has loop identifier case 1: trace("1"); break; case + * 2: //last case trace("2"); switch(b) { //last command is switch case 3: + * case 4: trace("4"); break loop1; //breaks parent loop case 5: trace("5"); + * break loop1; case 6: trace("6"); } } + * + * ==> + * + * switch(a) { case 1: trace("1"); break; case 2: trace("2"); switch(b) { + * case 3: case 4: trace("4"); break; case 5: trace("5"); break; case 6: + * trace("6"); } } + * + * It also does similar thing to loops and continues. + * + * + * + * * @param list Items */ - protected void processSwitches2(List list) { + protected void processSwitchesAndLoops2(List list) { for (int i = 0; i < list.size(); i++) { GraphTargetItem item = list.get(i); if (item instanceof Block) { Block bl = (Block) item; for (List subList : bl.getSubs()) { - processSwitches2(subList); + processSwitchesAndLoops2(subList); } } if (item instanceof SwitchItem) { @@ -1086,21 +1063,190 @@ public class Graph { if (lastCase.isEmpty()) { continue; } + + //Replace breaks in lastCommands loops + for (List com : sw.caseCommands) { + List com2 = com; + if (com.isEmpty()) { + continue; + } + int last = com.size() - 1; + if (!((com.get(last) instanceof BreakItem) && (((BreakItem) com.get(last)).loopId == sw.loop.id))) { + continue; + } + com2 = new ArrayList<>(com); + com2.remove(com2.size() - 1); + + List> todos = new ArrayList<>(); + todos.add(com2); + + for (int j = 0; j < todos.size(); j++) { + List currentList = todos.get(j); + if (currentList.isEmpty()) { + continue; + } + GraphTargetItem lastCommand = currentList.get(currentList.size() - 1); + if (lastCommand instanceof LoopItem) { + if (!(lastCommand instanceof SwitchItem)) { + LoopItem innerLoop = (LoopItem) lastCommand; + changeBreakToBreak(innerLoop.getBaseBodyCommands(), sw.loop.id, innerLoop.loop.id); + //Detect While + if (innerLoop instanceof UniversalLoopItem) { + List body = innerLoop.getBaseBodyCommands(); + if (!body.isEmpty()) { + if (body.get(0) instanceof IfItem) { + IfItem ifi = (IfItem) body.get(0); + if (ifi.onFalse.isEmpty() && ifi.onTrue.size() == 1) { + if (ifi.onTrue.get(0) instanceof BreakItem) { + BreakItem br = (BreakItem) ifi.onTrue.get(0); + if (br.loopId == innerLoop.loop.id) { + List expr = new ArrayList<>(); + expr.add(ifi.expression.invert(null)); + body.remove(0); + WhileItem wh = new WhileItem(dialect, innerLoop.getSrc(), innerLoop.getLineStartItem(), innerLoop.loop, expr, body); + if (currentList == com2) { + com.set(com.size() - 2, wh); + } else { + currentList.set(currentList.size() - 1, wh); + } + } + } + } + } + } + } + } + } else if (lastCommand instanceof Block) { + Block blk = (Block) lastCommand; + List> newTodos = new ArrayList<>(blk.getSubs()); + if (!newTodos.isEmpty() && lastCommand instanceof SwitchItem) { + List> newTodos2 = new ArrayList<>(); + newTodos2.add(newTodos.get(newTodos.size() - 1)); + newTodos = newTodos2; + } + todos.addAll(newTodos); + } + } + } + + // Remove breaks of switch in last commands of last case: + List> todos = new ArrayList<>(); + todos.add(lastCase); + + for (int j = 0; j < todos.size(); j++) { + List currentList = todos.get(j); + if (currentList.isEmpty()) { + continue; + } + GraphTargetItem lastCommand = currentList.get(currentList.size() - 1); + if (lastCommand instanceof BreakItem) { + BreakItem bi = (BreakItem) lastCommand; + if (bi.loopId == sw.loop.id) { + currentList.remove(currentList.size() - 1); + } + } else if (lastCommand instanceof LoopItem) { + //ignore + } else if (lastCommand instanceof Block) { + Block blk = (Block) lastCommand; + List> newTodos = new ArrayList<>(blk.getSubs()); + if (!newTodos.isEmpty() && lastCommand instanceof SwitchItem) { + List> newTodos2 = new ArrayList<>(); + newTodos2.add(newTodos.get(newTodos.size() - 1)); + newTodos = newTodos2; + } + todos.addAll(newTodos); + } + } + //----------------------- + if (!(lastCase.get(lastCase.size() - 1) instanceof SwitchItem)) { - continue; + continue; } SwitchItem swInner = (SwitchItem) lastCase.get(lastCase.size() - 1); - + List breaks = swInner.getBreaks(); for (BreakItem br : breaks) { if (br.loopId == sw.loop.id) { br.loopId = swInner.loop.id; } } - } + } + if (item instanceof LoopItem) { + LoopItem li = (LoopItem) item; + if (li.hasBaseBody()) { + List body = li.getBaseBodyCommands(); + if (!body.isEmpty()) { + + List> todos = new ArrayList<>(); + todos.add(body); + + for (int j = 0; j < todos.size(); j++) { + List currentList = todos.get(j); + if (currentList.isEmpty()) { + continue; + } + GraphTargetItem lastCommand = currentList.get(currentList.size() - 1); + if (lastCommand instanceof LoopItem) { + LoopItem innerLoop = (LoopItem) lastCommand; + Block blk = (Block) lastCommand; + List> subs = blk.getSubs(); + + for (List subItems : subs) { + changeContinueToBreak(subItems, li.loop.id, innerLoop.loop.id); + } + } else if (lastCommand instanceof Block) { + Block blk = (Block) lastCommand; + List> newTodos = new ArrayList<>(blk.getSubs()); + if (!newTodos.isEmpty() && lastCommand instanceof SwitchItem) { + List> newTodos2 = new ArrayList<>(); + newTodos2.add(newTodos.get(newTodos.size() - 1)); + newTodos = newTodos2; + } + todos.addAll(newTodos); + } + } + } + } + } } } - + + private void changeContinueToBreak(List items, long continueLoopId, long breakLoopId) { + for (int i = 0; i < items.size(); i++) { + GraphTargetItem item = items.get(i); + if (item instanceof Block) { + Block blk = (Block) item; + for (List subItems : blk.getSubs()) { + changeContinueToBreak(subItems, continueLoopId, breakLoopId); + } + } + if (item instanceof ContinueItem) { + ContinueItem ci = (ContinueItem) item; + if (ci.loopId == continueLoopId) { + items.set(i, new BreakItem(dialect, ci.getSrc(), ci.getLineStartItem(), breakLoopId)); + } + } + } + } + + private void changeBreakToBreak(List items, long breakLoopIdFrom, long breakLoopIdTo) { + for (int i = 0; i < items.size(); i++) { + GraphTargetItem item = items.get(i); + if (item instanceof Block) { + Block blk = (Block) item; + for (List subItems : blk.getSubs()) { + changeBreakToBreak(subItems, breakLoopIdFrom, breakLoopIdTo); + } + } + if (item instanceof BreakItem) { + BreakItem ci = (BreakItem) item; + if (ci.loopId == breakLoopIdFrom) { + ci.loopId = breakLoopIdTo; + } + } + } + } + /** * Prepares second pass data. Can return null when no second pass will * happen. Override this method to prepare second pass data. @@ -1130,7 +1276,7 @@ public class Graph { protected final void processSwitches(List list) { processSwitches(list, -1); } - + /* while(something){ @@ -1555,7 +1701,7 @@ public class Graph { } else { break; } - } + } fori.firstCommands.addAll(0, forFirstCommands); } } @@ -1773,14 +1919,14 @@ public class Graph { if (blk instanceof SwitchItem) { return; } - + for (List sub : blk.getSubs()) { - processScriptEnd(sub); + processScriptEnd(sub); } } } } - + /** * Processes ifs. * @@ -2642,6 +2788,7 @@ public class Graph { } } currentLoop.loopBreak = winner; + currentLoop.breakCandidates.clear(); currentLoop.phase = 2; boolean start = false; for (int l = 0; l < loops.size(); l++) { @@ -2808,7 +2955,6 @@ public class Graph { } return true; } - /** * Gets if expression from stack. Can be overridden for custom handling @@ -2846,7 +2992,7 @@ public class Graph { protected final List printGraph(List foundGotos, Map> partCodes, Map partCodePos, Set visited, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List stopPartKind, List loops, List throwStates, int staticOperation, String path) throws InterruptedException { return printGraph(foundGotos, partCodes, partCodePos, visited, localData, stack, allParts, parent, part, stopPart, stopPartKind, loops, throwStates, null, staticOperation, path, 0); } - + /** * Walks graph parts and converts them to target items. * @@ -3002,7 +3148,7 @@ public class Graph { } } }*/ - + //isRealStopPart = stopPart.get(stopPart.size() - 1) == part; for (int i = stopPartKind.size() - 1; i >= 0; i--) { if (stopPartKind.get(i) == StopPartKind.OTHER && stopPart.get(i) == part) { @@ -3037,7 +3183,7 @@ public class Graph { if (vCanHandleVisited) { if (visited.contains(part)) { - String labelName = "addr" + Helper.formatAddress(code.pos2adr(part.start, true)); //was: part.start; + String labelName = "addr" + Helper.formatAddress(code.pos2adr(part.start, true)); // + "_ip" + part.start; List firstCode = partCodes.get(part); int firstCodePos = partCodePos.get(part); if (firstCodePos > firstCode.size()) { @@ -3433,7 +3579,7 @@ public class Graph { if ((!isEmpty) && (next != null)) { stopPart2.add(next); - stopPartKind2.add(StopPartKind.BLOCK_CLOSE); + stopPartKind2.add(StopPartKind.BLOCK_CLOSE); } List onTrue = new ArrayList<>(); @@ -3629,6 +3775,7 @@ public class Graph { boolean inverted = false; boolean breakpos2 = false; BreakItem addBreakItem = null; + ContinueItem addContinueItem = null; if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { BreakItem bi = (BreakItem) ifi.onTrue.get(0); if (bi.loopId == currentLoop.id) { @@ -3650,6 +3797,26 @@ public class Graph { if (bi.loopId != currentLoop.id) { //it's break of another parent loop addBreakItem = bi; //we must add it after the loop } + } else if ((ifi.onTrue.size() == 1) + && (ifi.onTrue.get(0) instanceof ContinueItem) + && (((ContinueItem) ifi.onTrue.get(0)).loopId != currentLoop.id)) { + addContinueItem = (ContinueItem) ifi.onTrue.get(0); + bodyBranch = ifi.onFalse; + inverted = true; + } else if ((ifi.onFalse.size() == 1) + && (ifi.onFalse.get(0) instanceof ContinueItem) + && (((ContinueItem) ifi.onFalse.get(0)).loopId != currentLoop.id)) { + addContinueItem = (ContinueItem) ifi.onFalse.get(0); + bodyBranch = ifi.onTrue; + } else if (loopItem.commands.size() == 2 + && (loopItem.commands.get(1) instanceof ContinueItem) + && (((ContinueItem) loopItem.commands.get(1)).loopId != currentLoop.id)) { + addContinueItem = (ContinueItem) loopItem.commands.get(1); + if (ifi.onTrue.isEmpty()) { + inverted = true; + } + bodyBranch = inverted ? ifi.onFalse : ifi.onTrue; + breakpos2 = true; } if (bodyBranch != null) { int index = ret.indexOf(loopItem); @@ -3703,6 +3870,9 @@ public class Graph { if (addBreakItem != null) { ret.add(index + 1, addBreakItem); } + if (addContinueItem != null) { + ret.add(index + 1, addContinueItem); + } loopTypeFound = true; } @@ -3836,6 +4006,53 @@ public class Graph { return null; } + private void flattenJumps(List heads, List allBlocks) { + boolean modified; + do { + modified = false; + for (int i = 0; i < allBlocks.size(); i++) { + GraphPart part = allBlocks.get(i); + if (heads.contains(part)) { + continue; + } + if (part.getHeight() != 1) { + continue; + } + if (part.start < 0) { + continue; + } + if (part.start >= code.size()) { + continue; + } + if (!code.get(part.start).isJump()) { + continue; + } + + if (part.nextParts.size() != 1) { + continue; + } + + GraphPart nextPart = part.nextParts.get(0); + + for (GraphPart r : part.refs) { + while (true) { + int ind = r.nextParts.indexOf(part); + if (ind == -1) { + break; + } + r.nextParts.set(ind, nextPart); + } + + } + + nextPart.refs.remove(part); + nextPart.refs.addAll(part.refs); + allBlocks.remove(i); + modified = true; + } + } while (modified); + } + /** * Makes connected set of GraphParts from GraphSource. * @@ -3861,6 +4078,7 @@ public class Graph { e1.path = new GraphPath("e"); ret.add(makeGraph(e1, new GraphPath("e"), code, pos, pos, allBlocks, refs, visited)); } + flattenJumps(ret, allBlocks); checkGraph(allBlocks); return ret; } @@ -3899,9 +4117,9 @@ public class Graph { if (parent != null) { ret.refs.add(parent); parent.nextParts.add(ret); - } + } while (ip < code.size()) { - ip = checkIp(ip); + ip = checkIp(ip); if (ip >= code.size()) { break; } @@ -3958,7 +4176,7 @@ public class Graph { } else if (ins.isJump()) { part.end = ip; allBlocks.add(part); - ip = ins.getBranches(code).get(0); + ip = ins.getBranches(code).get(0); ip = checkIp(ip); makeGraph(part, path, code, ip, lastIp, allBlocks, refs, visited); lastIp = -1; @@ -4173,7 +4391,7 @@ public class Graph { protected SwitchItem handleSwitch(GraphTargetItem switchedObject, GraphSourceItem switchStartItem, List foundGotos, Map> partCodes, Map partCodePos, Set visited, Set allParts, TranslateStack stack, List stopPart, List stopPartKind, List loops, List throwStates, BaseLocalData localData, int staticOperation, String path, List caseValuesMap, GraphPart defaultPart, List caseBodyParts, Reference nextRef, Reference tiRef) throws InterruptedException { - + boolean hasDefault = false; /* case 4: diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java index 315188bb1..9a3d6e546 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java @@ -75,7 +75,7 @@ public class ActionScript2AssemblerTest extends ActionScript2TestBase { HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); try { - Action.actionsToSource(new HashMap<>(),doa, doa.getActions(), "", writer, swf.getCharset()); + Action.actionsToSource(new HashMap<>(), doa, doa.getActions(), "", writer, swf.getCharset()); } catch (InterruptedException ex) { fail(); } @@ -126,7 +126,7 @@ public class ActionScript2AssemblerTest extends ActionScript2TestBase { DoActionTag doa = getFirstActionTag(); doa.setActionBytes(Action.actionsToBytes(actions, true, swf.version)); HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); - Action.actionsToSource(new HashMap<>(),doa, doa.getActions(), "", writer, swf.getCharset()); + Action.actionsToSource(new HashMap<>(), doa, doa.getActions(), "", writer, swf.getCharset()); writer.finishHilights(); String actualResult = writer.toString(); writer = new HighlightedTextWriter(new CodeFormatting(), false); @@ -175,6 +175,52 @@ public class ActionScript2AssemblerTest extends ActionScript2TestBase { + "}"); } + @Test + public void testJumpAfterFunctionFix() { + String res = decompilePcode("ConstantPool \"F\", \"A\", \"f\", \"B\", \"C\"\n" + + "DefineFunction \"f\", 0 {\n" + + "Push \"F\"\n" + + "Trace\n" + + "Push 1, 1\n" + + "Equals2\n" + + "If loc005b\n" + + "Push \"G\"\n" + + "Trace\n" + + "}\n" + + "Push \"A\"\n" + + "Trace\n" + + "Push 0\n" + + "Push \"f\"\n" + + "CallFunction\n" + + "Pop\n" + + "Push \"B\"\n" + + "Trace\n" + + "Push 1\n" + + "loc005b:Push 2\n" + + "Equals2\n" + + "If loc006e\n" + + "Push \"C\"\n" + + "Trace\n" + + "loc006e:Stop"); + res = cleanPCode(res); + assertEquals(res, "function f()\n" + + "{\n" + + "trace(\"F\");\n" + + "if(1 != 1)\n" + + "{\n" + + "trace(\"G\");\n" + + "}\n" + + "}\n" + + "trace(\"A\");\n" + + "f();\n" + + "trace(\"B\");\n" + + "if(1 != 2)\n" + + "{\n" + + "trace(\"C\");\n" + + "}\n" + + "stop();"); + } + @Test public void testDefineFuncRegCleaner() { String res = decompilePcode("ConstantPool \"Math\" \"randi\" \"random\" \"b\" \"a\" \"floor\" \"randf\" \"randa\" \"arguments\" \"rande\" \"sign\" \"abs\"\n" diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2DeobfuscatorTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2DeobfuscatorTest.java index 47ae71fba..c58acd5eb 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2DeobfuscatorTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2DeobfuscatorTest.java @@ -64,7 +64,8 @@ public class ActionScript2DeobfuscatorTest extends ActionScript2TestBase { HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); List actions = par.actionsFromString(str, Utf8Helper.charsetName); byte[] hex = Action.actionsToBytes(actions, true, SWF.DEFAULT_VERSION); - ActionList list = ActionListReader.readActionListTimeout(new ArrayList<>(), new SWFInputStream(swf, hex), SWF.DEFAULT_VERSION, 0, hex.length, "", 1); + DoActionTag doA = new DoActionTag(swf); + ActionList list = ActionListReader.readActionListTimeout(doA, new ArrayList<>(), new SWFInputStream(swf, hex), SWF.DEFAULT_VERSION, 0, hex.length, "", 1); Action.actionsToSource(new HashMap<>(), null, list, "", writer, Utf8Helper.charsetName); writer.finishHilights(); return writer.toString(); 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 fda0315fd..05ddcc313 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 @@ -226,7 +226,6 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile + "var d:* = undefined;\r\n" + "var e:* = undefined;\r\n" + "var a:int = 5;\r\n" - + "loop3:\r\n" + "switch(a)\r\n" + "{\r\n" + "case 57 * a:\r\n" @@ -240,7 +239,7 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile + "}\r\n" + "if(b == 15)\r\n" + "{\r\n" - + "break loop3;\r\n" + + "break;\r\n" + "}\r\n" + "b += 1;\r\n" + "}\r\n" @@ -1980,7 +1979,6 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile + "{\r\n" + "trace(\"A\");\r\n" + "}\r\n" - + "break;\r\n" + "}\r\n" + "trace(\"B\");\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 71d7711a2..23047d678 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 @@ -227,7 +227,6 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes + "var d:* = undefined;\r\n" + "var e:* = undefined;\r\n" + "var a:* = 5;\r\n" - + "loop3:\r\n" + "switch(a)\r\n" + "{\r\n" + "case 57 * a:\r\n" @@ -241,7 +240,7 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes + "}\r\n" + "if(b == 15)\r\n" + "{\r\n" - + "break loop3;\r\n" + + "break;\r\n" + "}\r\n" + "b += 1;\r\n" + "}\r\n" @@ -912,7 +911,6 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes + "break;\r\n" + "case \"c\":\r\n" + "trace(\"val c\");\r\n" - + "break;\r\n" + "}\r\n" + "trace(\"final\");\r\n" + "}\r\n", @@ -1965,7 +1963,6 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes + "if(a)\r\n" + "{\r\n" + "trace(\"A\");\r\n" - + "break;\r\n" + "}\r\n" + "}\r\n" + "trace(\"B\");\r\n",