diff --git a/CHANGELOG.md b/CHANGELOG.md index 59c46ffa2..36a44599f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ All notable changes to this project will be documented in this file. - AS1/2 - cannot use globalfunc/const variable names - AS2 - class detection when no constructor found - AS1/2 - subtract precedence +- AS2 - getters and setters decompilation and editing +- AS1/2 - definefunction2 suppresssuper parameter ## [14.6.0] - 2021-11-22 ### Added 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 48f61a58e..e20ead1b3 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 @@ -26,6 +26,7 @@ import com.jpexs.decompiler.flash.action.model.GetVariableActionItem; import com.jpexs.decompiler.flash.action.model.ImplementsOpActionItem; import com.jpexs.decompiler.flash.action.model.NewMethodActionItem; import com.jpexs.decompiler.flash.action.model.NewObjectActionItem; +import com.jpexs.decompiler.flash.action.model.ReturnActionItem; import com.jpexs.decompiler.flash.action.model.SetMemberActionItem; import com.jpexs.decompiler.flash.action.model.SetVariableActionItem; import com.jpexs.decompiler.flash.action.model.StoreRegisterActionItem; @@ -40,6 +41,7 @@ import com.jpexs.decompiler.graph.model.IfItem; import com.jpexs.decompiler.graph.model.NotItem; import com.jpexs.decompiler.graph.model.PopItem; 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.List; @@ -504,7 +506,19 @@ public class ActionScript2ClassDetector { if (!(getterNameStr.equals("__get__" + propertyNameStr))) { throw new AssertException("getter does not match property name"); } - //TODO: handle getter HERE + + for (MyEntry trait : traits) { + if (trait.getKey() instanceof DirectValueActionItem) { + if (((DirectValueActionItem) trait.getKey()).isString()) { + if (((DirectValueActionItem) trait.getKey()).toString().equals(getterNameStr)) { + if (trait.getValue() instanceof FunctionActionItem) { + FunctionActionItem func = (FunctionActionItem) trait.getValue(); + func.isGetter = true; + } + } + } + } + } } else if (propertyGetter instanceof FunctionActionItem) { FunctionActionItem getterFunc = (FunctionActionItem) propertyGetter; @@ -525,7 +539,38 @@ public class ActionScript2ClassDetector { if (!(setterNameStr.equals("__set__" + propertyNameStr))) { throw new AssertException("setter does not match property name"); } - //TODO: handle setter HERE + + for (MyEntry trait : traits) { + if (trait.getKey() instanceof DirectValueActionItem) { + if (((DirectValueActionItem) trait.getKey()).isString()) { + if (((DirectValueActionItem) trait.getKey()).toString().equals(setterNameStr)) { + if (trait.getValue() instanceof FunctionActionItem) { + FunctionActionItem func = (FunctionActionItem) trait.getValue(); + func.isSetter = true; + + //There is return getter added at the end of every setter, gotta remove it, since it won't compile + //as setter must not return a value + if (!func.actions.isEmpty()) { + int pos = func.actions.size() - 1; + if (func.actions.get(pos) instanceof ScriptEndItem) { + pos--; + } + if (pos >= 0 && func.actions.get(pos) instanceof ReturnActionItem) { + GraphTargetItem val = func.actions.get(pos); + if (val.value instanceof CallMethodActionItem) { + if (((CallMethodActionItem) val.value).methodName instanceof DirectValueActionItem) { + if (((CallMethodActionItem) val.value).methodName.toString().startsWith("__get__")) { + func.actions.remove(pos); + } + } + } + } + } + } + } + } + } + } } else if (propertySetter instanceof FunctionActionItem) { FunctionActionItem setterFunc = (FunctionActionItem) propertySetter; if (!(setterFunc.actions.isEmpty() && setterFunc.functionName.isEmpty() && ((FunctionActionItem) propertySetter).paramNames.isEmpty())) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/model/FunctionActionItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/model/FunctionActionItem.java index c1e11832d..db290f541 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/model/FunctionActionItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/model/FunctionActionItem.java @@ -51,6 +51,10 @@ import java.util.Set; */ public class FunctionActionItem extends ActionItem implements BranchStackResistant { + public boolean isGetter = false; + + public boolean isSetter = false; + public List actions; public List constants; @@ -92,6 +96,10 @@ public class FunctionActionItem extends ActionItem implements BranchStackResista } + public void addVariable(VariableActionItem variable) { + variables.add(variable); + } + public FunctionActionItem() { super(null, null, PRECEDENCE_PRIMARY); @@ -119,21 +127,43 @@ public class FunctionActionItem extends ActionItem implements BranchStackResista srcData.localName = n; srcData.declaration = true; } + writer.append("function"); + if (isGetter) { + writer.append(" get"); + } + if (isSetter) { + writer.append(" set"); + } if (calculatedFunctionName != null) { writer.append(" "); String fname = calculatedFunctionName.toStringNoQuotes(localData); + if (isGetter && fname.startsWith("__get__")) { + fname = fname.substring(7); + } + if (isSetter && fname.startsWith("__set__")) { + fname = fname.substring(7); + } + if (!IdentifiersDeobfuscation.isValidName(false, fname)) { IdentifiersDeobfuscation.appendObfuscatedIdentifier(fname, writer); } else { - calculatedFunctionName.appendToNoQuotes(writer, localData); + writer.append(fname); + //calculatedFunctionName.appendToNoQuotes(writer, localData); } } else if (!functionName.isEmpty()) { + String fname = functionName; + if (isGetter && fname.startsWith("__get__")) { + fname = fname.substring(7); + } + if (isSetter && fname.startsWith("__set__")) { + fname = fname.substring(7); + } writer.append(" "); - if (!IdentifiersDeobfuscation.isValidName(false, functionName)) { - IdentifiersDeobfuscation.appendObfuscatedIdentifier(functionName, writer); + if (!IdentifiersDeobfuscation.isValidName(false, fname)) { + IdentifiersDeobfuscation.appendObfuscatedIdentifier(fname, writer); } else { - writer.append(functionName); + writer.append(fname); } } writer.spaceBeforeCallParenthesies(paramNames.size()); @@ -262,7 +292,7 @@ public class FunctionActionItem extends ActionItem implements BranchStackResista boolean preloadThisFlag = false; boolean preloadGlobalFlag = false; - boolean suppressParentFlag = false; + boolean suppressSuperFlag = false; boolean suppressArgumentsFlag = false; boolean suppressThisFlag = false; @@ -288,7 +318,10 @@ public class FunctionActionItem extends ActionItem implements BranchStackResista preloadSuperFlag = true; needsFun2 = true; registerNames.add("super"); + } else { + suppressSuperFlag = true; } + if (usedNames.contains("_root")) { preloadRootFlag = true; needsFun2 = true; @@ -298,8 +331,6 @@ public class FunctionActionItem extends ActionItem implements BranchStackResista preloadParentFlag = true; needsFun2 = true; registerNames.add("_parent"); - } else { - suppressParentFlag = true; } if (usedNames.contains("_global")) { needsFun2 = true; @@ -404,7 +435,7 @@ public class FunctionActionItem extends ActionItem implements BranchStackResista ret.add(0, new ActionDefineFunction2(functionName, preloadParentFlag, preloadRootFlag, - suppressParentFlag, + suppressSuperFlag, preloadSuperFlag, suppressArgumentsFlag, preloadArgumentsFlag, diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2Parser.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2Parser.java index 2d12a6f74..79c7c7617 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2Parser.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2Parser.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.action.parser.script; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SourceGeneratorLocalData; +import com.jpexs.decompiler.flash.abc.avm2.model.CallMethodAVM2Item; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.model.AsciiToCharActionItem; import com.jpexs.decompiler.flash.action.model.CallActionItem; @@ -447,6 +448,8 @@ public class ActionScript2Parser { looptrait: while (true) { s = lex(); + boolean isGetter = false; + boolean isSetter = false; boolean isStatic = false; while (s.isType(SymbolType.STATIC, SymbolType.PUBLIC, SymbolType.PRIVATE)) { if (s.type == SymbolType.STATIC) { @@ -457,6 +460,15 @@ public class ActionScript2Parser { switch (s.type) { case FUNCTION: s = lex(); + + if (s.type == SymbolType.SET) { + isSetter = true; + s = lex(); + } else if (s.type == SymbolType.GET) { + isGetter = true; + s = lex(); + } + expectedIdentifier(s, lexer.yyline()); String fname = s.value.toString(); if (fname.equals(classNameStr)) { //constructor @@ -466,16 +478,37 @@ public class ActionScript2Parser { if (isStatic) { FunctionActionItem ft = function(!isInterface, "", true, variables, functions, inTellTarget, hasEval); ft.calculatedFunctionName = pushConst(fname); + ft.isSetter = isSetter; + ft.isGetter = isGetter; //staticFunctions.add(ft); traits.add(new MyEntry<>(ft.calculatedFunctionName, ft)); traitsStatic.add(true); + + if (isSetter) { + //add return getter automatically + GraphTargetItem callM = new CallMethodActionItem(null, null, nameStr, pushConst("__get__" + fname), new ArrayList<>()); + GraphTargetItem retV = new ReturnActionItem(null, null, callM); + ft.actions.add(retV); + } } else { FunctionActionItem ft = function(!isInterface, "", true, variables, functions, inTellTarget, hasEval); ft.calculatedFunctionName = pushConst(fname); + ft.isSetter = isSetter; + ft.isGetter = isGetter; //instanceFunctions.add(ft); traits.add(new MyEntry<>(ft.calculatedFunctionName, ft)); traitsStatic.add(false); + + if (isSetter) { + //add return getter automatically + GraphTargetItem thisVar = new VariableActionItem("this", null, false); + ft.addVariable((VariableActionItem) thisVar); + GraphTargetItem callM = new CallMethodActionItem(null, null, thisVar, pushConst("__get__" + fname), new ArrayList<>()); + GraphTargetItem retV = new ReturnActionItem(null, null, callM); + ft.actions.add(retV); + } } + } break; case VAR: @@ -651,6 +684,7 @@ public class ActionScript2Parser { SymbolType.EACH, SymbolGroup.GLOBALFUNC, SymbolType.NUMBER_OP, SymbolType.STRING_OP); } + private void expectedIdentifier(ParsedSymbol s, int line, Object... exceptions) throws IOException, ActionParseException { for (Object ex : exceptions) { if (s.isType(ex)) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionSourceGenerator.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionSourceGenerator.java index 8465ac457..5b2245311 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionSourceGenerator.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionSourceGenerator.java @@ -33,6 +33,7 @@ import com.jpexs.decompiler.flash.action.swf4.ActionSetVariable; import com.jpexs.decompiler.flash.action.swf4.ConstantIndex; import com.jpexs.decompiler.flash.action.swf4.RegisterNumber; import com.jpexs.decompiler.flash.action.swf5.ActionCallFunction; +import com.jpexs.decompiler.flash.action.swf5.ActionCallMethod; import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; import com.jpexs.decompiler.flash.action.swf5.ActionGetMember; import com.jpexs.decompiler.flash.action.swf5.ActionNewObject; @@ -70,7 +71,10 @@ import com.jpexs.decompiler.graph.model.WhileItem; import com.jpexs.helpers.Helper; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; /** * @@ -711,6 +715,16 @@ public class ActionSourceGenerator implements SourceGenerator { constr.add(new ActionStoreRegister(1)); constr = (typeToActions(globalClassTypeStr, constr)); } + + Set properties = new LinkedHashSet<>(); + Set setters = new HashSet<>(); + Set getters = new HashSet<>(); + + Set staticProperties = new LinkedHashSet<>(); + Set staticSetters = new HashSet<>(); + Set staticGetters = new HashSet<>(); + + if (!isInterface) { for (int pass = 1; pass <= 2; pass++) { //two passes, methods first, then variables for (int t = 0; t < traits.size(); t++) { @@ -721,8 +735,20 @@ public class ActionSourceGenerator implements SourceGenerator { boolean isFunc = (en.getValue() instanceof FunctionActionItem); if (pass == 1 && isFunc) { //Add methods in first pass FunctionActionItem fi = (FunctionActionItem) en.getValue(); + if (fi.isGetter || fi.isSetter) { + (traitsStatic.get(t) ? staticProperties : properties).add(fi.calculatedFunctionName.toString()); + } + String prefix = ""; + if (fi.isGetter) { + (traitsStatic.get(t) ? staticGetters : getters).add(fi.calculatedFunctionName.toString()); + prefix = "__get__"; + } + if (fi.isSetter) { + (traitsStatic.get(t) ? staticSetters : setters).add(fi.calculatedFunctionName.toString()); + prefix = "__set__"; + } ifbody.add(new ActionPush(new RegisterNumber(traitsStatic.get(t) ? 1 : 2))); - ifbody.add(pushConst(getName(en.getKey()))); + ifbody.add(pushConst(prefix + getName(en.getKey()))); ifbody.addAll(toActionList(fi.toSource(localData, this))); ifbody.add(new ActionSetMember()); } else if (pass == 2 && !isFunc) { //add variables in second pass @@ -735,6 +761,41 @@ public class ActionSourceGenerator implements SourceGenerator { } } + for (String prop : staticProperties) { + if (staticSetters.contains(prop)) { + ifbody.add(new ActionPush(new Object[]{new RegisterNumber(1), "__set__" + prop})); + ifbody.add(new ActionGetMember()); + } else { + ifbody.add(new ActionDefineFunction("", new ArrayList(), 0, swfVersion)); + } + if (staticGetters.contains(prop)) { + ifbody.add(new ActionPush(new Object[]{new RegisterNumber(1), "__get__" + prop})); + ifbody.add(new ActionGetMember()); + } else { + ifbody.add(new ActionDefineFunction("", new ArrayList(), 0, swfVersion)); + } + ifbody.add(new ActionPush(new Object[]{prop, 3, new RegisterNumber(1), "addProperty"})); + ifbody.add(new ActionCallMethod()); + } + + for (String prop : properties) { + if (setters.contains(prop)) { + ifbody.add(new ActionPush(new Object[]{new RegisterNumber(2), "__set__" + prop})); + ifbody.add(new ActionGetMember()); + } else { + ifbody.add(new ActionDefineFunction("", new ArrayList(), 0, swfVersion)); + } + if (getters.contains(prop)) { + ifbody.add(new ActionPush(new Object[]{new RegisterNumber(2), "__get__" + prop})); + ifbody.add(new ActionGetMember()); + } else { + ifbody.add(new ActionDefineFunction("", new ArrayList(), 0, swfVersion)); + } + ifbody.add(new ActionPush(new Object[]{prop, 3, new RegisterNumber(2), "addProperty"})); + ifbody.add(new ActionCallMethod()); + } + + if (!isInterface) { ifbody.add(new ActionPush((Long) 1L)); ifbody.add(new ActionPush(Null.INSTANCE));