Fixed AS2 - some minor cases in class detection

This commit is contained in:
Jindra Petřík
2021-11-27 09:45:30 +01:00
parent e33cd9ba99
commit e348df4bbe
6 changed files with 178 additions and 14 deletions

View File

@@ -328,7 +328,7 @@ public class ActionGraph extends Graph {
List<GraphTargetItem> 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;

View File

@@ -195,4 +195,8 @@ public class ActionGraphSource extends GraphSource {
return ret;
}
public HashMap<String, GraphTargetItem> getVariables() {
return variables;
}
}

View File

@@ -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<GraphTargetItem> parts, int partsPos, int commandsStartPos, int commandsEndPos, List<GraphTargetItem> commands, List<String> classNamePath, String scriptPath) {
private boolean checkClassContent(List<GraphTargetItem> parts, HashMap<String, GraphTargetItem> variables, int partsPos, int commandsStartPos, int commandsEndPos, List<GraphTargetItem> commands, List<String> 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<Integer> 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<String> 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<String> protoPath = new ArrayList<>(classNamePath);
protoPath.add("prototype");
List<String> 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<MyEntry<GraphTargetItem, GraphTargetItem>> traits = new ArrayList<>();
List<Boolean> 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<GraphTargetItem> commands, int pos, String scriptPath) {
private boolean checkIfVariants(List<GraphTargetItem> commands, HashMap<String, GraphTargetItem> variables, int pos, String scriptPath) {
/*
@@ -725,7 +804,7 @@ public class ActionScript2ClassDetector {
}
List<String> 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<GraphTargetItem> commands, String scriptPath) {
public void checkClass(List<GraphTargetItem> commands, HashMap<String, GraphTargetItem> variables, String scriptPath) {
for (int pos = 0; pos < commands.size(); pos++) {
checkIfVariants(commands, pos, scriptPath);
checkIfVariants(commands, variables, pos, scriptPath);
}
}
}

View File

@@ -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<Action> 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"
+ "}");
}
}

View File

@@ -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;
}
}