diff --git a/CHANGELOG.md b/CHANGELOG.md index e16a695ce..534fa041e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file. - AS1/2 direct editation - delete operator on anything - AS2 - class detection of top level classes - AS2 - class detection - warning only if propertyname does not match getter/setter +- AS2 - some minor cases in class detection ## [14.6.0] - 2021-11-22 ### Added 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 855782a94..7aacb2f98 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 @@ -328,7 +328,7 @@ public class ActionGraph extends Graph { List ret = super.translate(localData, staticOperation, path); if (insideDoInitAction && !insideFunction) { ActionScript2ClassDetector detector = new ActionScript2ClassDetector(); - detector.checkClass(ret, path); + detector.checkClass(ret, ((ActionGraphSource) code).getVariables(), path); } makeDefineRegistersUp(ret); return ret; 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 3ac4ec7c5..a291f9648 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 @@ -195,4 +195,8 @@ public class ActionGraphSource extends GraphSource { return ret; } + public HashMap getVariables() { + return variables; + } + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionScript2ClassDetector.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionScript2ClassDetector.java index 04e05c304..e0bbc3765 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionScript2ClassDetector.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionScript2ClassDetector.java @@ -45,9 +45,14 @@ import com.jpexs.decompiler.graph.model.PushItem; import com.jpexs.decompiler.graph.model.ScriptEndItem; import com.jpexs.helpers.Reference; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @@ -271,7 +276,7 @@ public class ActionScript2ClassDetector { return mnDv.getAsString(); } - private boolean checkClassContent(List parts, int partsPos, int commandsStartPos, int commandsEndPos, List commands, List classNamePath, String scriptPath) { + private boolean checkClassContent(List parts, HashMap variables, int partsPos, int commandsStartPos, int commandsEndPos, List commands, List classNamePath, String scriptPath) { try { @@ -283,8 +288,63 @@ public class ActionScript2ClassDetector { GraphTargetItem classNameTargetPath = null; GraphTargetItem constructor = null; + Pattern regPattern = Pattern.compile("__register([0-9]+)"); + + Set definedRegisters = new HashSet<>(); + for (int i = partsPos; i < parts.size(); i++) { + item = parts.get(i); + if (item instanceof StoreRegisterActionItem) { + StoreRegisterActionItem sr = (StoreRegisterActionItem) item; + definedRegisters.add(sr.register.number); + } + } + + /* + Hack: + When register is not used after setting, then FFDec discards its value, + but the value is crucial as there is setmember, atc. + This will bypass the situation. + + This happens when there are no methods/vars, constructor only. + */ + int numr = 0; + for (String s : variables.keySet()) { + Matcher m = regPattern.matcher(s); + if (m.matches()) { + if (variables.get(s) instanceof TemporaryRegister) { + int regId = Integer.parseInt(m.group(1)); + if (!definedRegisters.contains(regId)) { + parts.add(partsPos + numr, variables.get(s).value); + numr++; + } + } + } + } + if (parts.size() > partsPos) { item = parts.get(partsPos); + + if (item instanceof SetMemberActionItem) { + List memPath = getSetMembersPath((SetMemberActionItem) item); + if (memPath != null) { + if (memPath.get(0).equals("_global")) { + memPath.remove(0); + } + if (memPath.equals(classNamePath)) { + if (item.value instanceof StoreRegisterActionItem) { + constructor = item.value.value; + partsPos++; + if (parts.size() > partsPos) { + item = parts.get(partsPos); + } else { + item = null; + } + } + + } + } + } + if (item instanceof ExtendsActionItem) { ExtendsActionItem et = (ExtendsActionItem) parts.get(partsPos); extendsOp = getWithoutGlobal(et.superclass); @@ -296,6 +356,26 @@ public class ActionScript2ClassDetector { } } + if (item instanceof SetMemberActionItem) { + SetMemberActionItem sm = (SetMemberActionItem) item; + List protoPath = new ArrayList<>(classNamePath); + protoPath.add("prototype"); + List smPath = getSetMembersPath(sm); + if (smPath.get(0).equals("_global")) { + smPath.remove(0); + } + if (smPath.equals(protoPath)) { + if (sm.value instanceof StoreRegisterActionItem) { + partsPos++; + if (parts.size() > partsPos) { + item = parts.get(partsPos); + } else { + item = null; + } + } + } + } + if (item instanceof StoreRegisterActionItem) { StoreRegisterActionItem sr = (StoreRegisterActionItem) item; instanceReg = sr.register.number; @@ -342,11 +422,10 @@ public class ActionScript2ClassDetector { } partsPos++; } - } else { - classNameTargetPath = new GetVariableActionItem(null, null, new DirectValueActionItem(classNamePath.get(0))); - for (int i = 1; i < classNamePath.size(); i++) { - classNameTargetPath = new GetMemberActionItem(null, null, classNameTargetPath, new DirectValueActionItem(classNamePath.get(i))); - } + } + classNameTargetPath = new GetVariableActionItem(null, null, new DirectValueActionItem(classNamePath.get(0))); + for (int i = 1; i < classNamePath.size(); i++) { + classNameTargetPath = new GetMemberActionItem(null, null, classNameTargetPath, new DirectValueActionItem(classNamePath.get(i))); } List> traits = new ArrayList<>(); List traitsStatic = new ArrayList<>(); @@ -453,7 +532,7 @@ public class ActionScript2ClassDetector { if (!classNamePath.equals(memPath)) { throw new AssertException("Invalid path of setmember:" + String.join(".", memPath)); } - classNameTargetPath = pathSource; + //classNameTargetPath = pathSource; if (!(sm2.value instanceof StoreRegisterActionItem)) { throw new AssertException("Not storeregister"); } @@ -669,7 +748,7 @@ public class ActionScript2ClassDetector { return false; } - private boolean checkIfVariants(List commands, int pos, String scriptPath) { + private boolean checkIfVariants(List commands, HashMap variables, int pos, String scriptPath) { /* @@ -725,7 +804,7 @@ public class ActionScript2ClassDetector { } List classPath = pathToSearchVariant1; classPath.remove(0); //remove _global - if (this.checkClassContent(ifItem.onTrue, 0, pos, checkPos, commands, classPath, scriptPath)) { + if (this.checkClassContent(ifItem.onTrue, variables, 0, pos, checkPos, commands, classPath, scriptPath)) { return true; } else { break check_variant1; @@ -792,7 +871,7 @@ public class ActionScript2ClassDetector { } } } - if (checkClassContent(parts, checkPos, pos, pos, commands, memPath, scriptPath)) { + if (checkClassContent(parts, variables, checkPos, pos, pos, commands, memPath, scriptPath)) { return true; } } @@ -801,9 +880,9 @@ public class ActionScript2ClassDetector { return false; } - public void checkClass(List commands, String scriptPath) { + public void checkClass(List commands, HashMap variables, String scriptPath) { for (int pos = 0; pos < commands.size(); pos++) { - checkIfVariants(commands, pos, scriptPath); + checkIfVariants(commands, variables, pos, scriptPath); } } } 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 f41e44e84..87266b963 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java @@ -24,6 +24,7 @@ import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.helpers.CodeFormatting; import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; import com.jpexs.decompiler.flash.tags.DoActionTag; +import com.jpexs.decompiler.flash.tags.DoInitActionTag; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; @@ -85,6 +86,28 @@ public class ActionScript2AssemblerTest extends ActionScript2TestBase { return null; } + private String decompileClassPcode(String pcode) { + try { + List actions = ASMParser.parse(0, true, pcode, swf.version, false); + + DoInitActionTag doi = getFirstInitActionTag(); + doi.setActionBytes(Action.actionsToBytes(actions, true, swf.version)); + HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); + + try { + Action.actionsToSource(doi, doi.getActions(), "", writer); + } catch (InterruptedException ex) { + fail(); + } + + return writer.toString(); + } catch (IOException | ActionParseException ex) { + fail(); + } + + return null; + } + @Test public void testModifiedConstantPools() { String actionsString = "ConstantPool \"ok\"\n" @@ -224,4 +247,50 @@ public class ActionScript2AssemblerTest extends ActionScript2TestBase { + "return 3;\n" + "}"); } + + @Test + public void testClassSpecial() { + String res = decompileClassPcode("ConstantPool\n" + + "Push \"_global\"\n" + + "GetVariable\n" + + "Push \"Guide\"\n" + + "GetMember\n" + + "Not\n" + + "Not\n" + + "If loc00d9\n" + + "Push \"_global\"\n" + + "GetVariable\n" + + "Push \"Guide\"\n" + + "DefineFunction \"\" 0 {\n" + + "Push \"hello\"\n" + + "Trace\n" + + "}\n" + + "StoreRegister 1\n" + + "SetMember\n" + + "Push \"_global\"\n" + + "GetVariable\n" + + "Push \"Guide\"\n" + + "GetMember\n" + + "Push \"prototype\" 0.0 \"MovieClip\"\n" + + "NewObject\n" + + "StoreRegister 2\n" + + "SetMember\n" + + "Push 1 null \"_global\"\n" + + "GetVariable\n" + + "Push \"Guide\"\n" + + "GetMember\n" + + "Push \"prototype\"\n" + + "GetMember\n" + + "Push 3 \"ASSetPropFlags\"\n" + + "CallFunction\n" + + "loc00d9:Pop"); + res = cleanPCode(res); + assertEquals(res, "class Guide\n" + + "{\n" + + "function Guide()\n" + + "{\n" + + "trace(\"hello\");\n" + + "}\n" + + "}"); + } } diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2TestBase.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2TestBase.java index a5a2e401f..540118a95 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2TestBase.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2TestBase.java @@ -12,10 +12,12 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash; import com.jpexs.decompiler.flash.tags.DoActionTag; +import com.jpexs.decompiler.flash.tags.DoInitActionTag; import com.jpexs.decompiler.flash.tags.Tag; /** @@ -34,4 +36,13 @@ public class ActionScript2TestBase extends ActionScriptTestBase { } return null; } + + protected DoInitActionTag getFirstInitActionTag() { + for (Tag t : swf.getTags()) { + if (t instanceof DoInitActionTag) { + return (DoInitActionTag) t; + } + } + return null; + } }