Export subclasses stubs to be compiled by Flex too.

WIP: merging ABCs
Test data (flexedit) with simple class hierarchy
This commit is contained in:
Jindra Petřík
2016-08-14 23:25:46 +02:00
parent ac7e0572c6
commit b750780cd9
18 changed files with 388 additions and 28 deletions
@@ -37,6 +37,7 @@ import com.jpexs.decompiler.flash.abc.types.Multiname;
import com.jpexs.decompiler.flash.abc.types.Namespace;
import com.jpexs.decompiler.flash.abc.types.NamespaceSet;
import com.jpexs.decompiler.flash.abc.types.ScriptInfo;
import com.jpexs.decompiler.flash.abc.types.ValueKind;
import com.jpexs.decompiler.flash.abc.types.traits.Trait;
import com.jpexs.decompiler.flash.abc.types.traits.TraitClass;
import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction;
@@ -1451,4 +1452,51 @@ public class ABC {
getSwf().clearScriptCache();
getMethodIndexing();
}
public void mergeABC(ABC secondABC) {
Map<Integer, Integer> namespaceMap = new HashMap<>();
Map<Integer, Integer> namespaceSetMap = new HashMap<>();
Map<Integer, Integer> multinameMap = new HashMap<>();
Map<Integer, Integer> methodInfoMap = new HashMap<>();
constants.merge(secondABC.constants, namespaceMap, namespaceSetMap, multinameMap);
for (int i = 0; i < secondABC.method_info.size(); i++) {
MethodInfo secondMethodInfo = secondABC.method_info.get(i);
int newParamTypes[] = new int[secondMethodInfo.param_types.length];
int newParamNames[] = new int[secondMethodInfo.paramNames.length];
int newRetType = multinameMap.get(secondMethodInfo.ret_type);
int newNameIndex = constants.getStringId(secondABC.constants.getString(secondMethodInfo.name_index), true);
ValueKind newOptional[] = new ValueKind[secondMethodInfo.optional.length];
for (int k = 0; k < secondMethodInfo.optional.length; k++) {
int vkind = secondMethodInfo.optional[k].value_kind;
int newValueIndex;
switch (vkind) {
case ValueKind.CONSTANT_ExplicitNamespace:
case ValueKind.CONSTANT_Namespace:
case ValueKind.CONSTANT_PackageInternalNs:
case ValueKind.CONSTANT_PackageNamespace:
case ValueKind.CONSTANT_ProtectedNamespace:
case ValueKind.CONSTANT_PrivateNs:
case ValueKind.CONSTANT_StaticProtectedNs:
newValueIndex = namespaceMap.get(secondMethodInfo.optional[k].value_index);
break;
default:
newValueIndex = secondMethodInfo.optional[k].value_index;
}
newOptional[k] = new ValueKind(newValueIndex, vkind);
}
MethodInfo newMethodInfo = new MethodInfo(newParamTypes, newRetType, newNameIndex, secondMethodInfo.flags, newOptional, newParamNames);
int newIndex = addMethodInfo(newMethodInfo);
methodInfoMap.put(i, newIndex);
}
for (MethodBody secondBody : secondABC.bodies) {
//TODO!!!
//MethodBody newBody = new MethodBody(this, mergeTraits(secondBody.traits), codeBytes, exceptions)
}
}
private Traits mergeTraits(Traits secondTraits) {
return null; //TODO
}
}
@@ -200,12 +200,14 @@ public class ScriptPack extends AS3ClassTreeItem {
//TODO: handle this better in GUI(?)
writer.startTrait(GraphTextWriter.TRAIT_SCRIPT_INITIALIZER);
writer.startMethod(script_init);
if (!scriptInitializerIsEmpty) {
writer.startBlock();
abc.bodies.get(bodyIndex).toString(path +/*packageName +*/ "/.scriptinitializer", exportMode, abc, null, writer, new ArrayList<>());
writer.endBlock();
} else {
writer.append(" ");
if (exportMode != ScriptExportMode.AS_METHOD_STUBS) {
if (!scriptInitializerIsEmpty) {
writer.startBlock();
abc.bodies.get(bodyIndex).toString(path +/*packageName +*/ "/.scriptinitializer", exportMode, abc, null, writer, new ArrayList<>());
writer.endBlock();
} else {
writer.append(" ");
}
}
writer.endMethod();
writer.endTrait();
@@ -377,6 +377,14 @@ public class AVM2ConstantPool implements Cloneable {
return null;
}
public int getDecimalId(Decimal val, boolean add) {
int id = getDecimalId(val);
if (add && id == -1) {
id = addDecimal(val);
}
return id;
}
public Float getFloat(int index) {
try {
return constant_float.get(index);
@@ -514,6 +522,10 @@ public class AVM2ConstantPool implements Cloneable {
return constant_float4.indexOf(value);
}
private int getDecimalId(Decimal value) {
return constant_decimal.indexOf(value);
}
private int getStringId(String value) {
return constant_string.indexOf(value);
}
@@ -755,4 +767,114 @@ public class AVM2ConstantPool implements Cloneable {
}
return null;
}
/**
* Merges second constantpool into this one
*
* @param secondPool
* @param namespaceMap Output map of merged namespaces. Index in second pool
* to index in this pool.
* @param namespaceSetMap Output map of merged namespace sets. Index in
* second pool to index in this pool.
* @param multinameMap Output map of merged multinames. Index in second pool
* to index in this pool.
*/
public void merge(AVM2ConstantPool secondPool, Map<Integer, Integer> namespaceMap, Map<Integer, Integer> namespaceSetMap, Map<Integer, Integer> multinameMap) {
for (String val : secondPool.constant_string) {
getStringId(val, true);
}
for (Long val : secondPool.constant_int) {
getIntId(val, true);
}
for (Long val : secondPool.constant_uint) {
getUIntId(val, true);
}
for (Double val : secondPool.constant_double) {
getDoubleId(val, true);
}
for (Float val : secondPool.constant_float) {
getFloatId(val, true);
}
for (Float4 val : secondPool.constant_float4) {
getFloat4Id(val, true);
}
for (Decimal val : secondPool.constant_decimal) {
getDecimalId(val, true);
}
namespaceMap.put(0, 0);
for (int i = 1; i < secondPool.constant_namespace.size(); i++) {
Namespace secondNamespace = secondPool.getNamespace(i);
String secondNsNameStr = secondNamespace.name_index == 0 ? null : secondPool.getString(secondNamespace.name_index);
int mappedId;
if (secondNamespace.kind == Namespace.KIND_PRIVATE) {//always add, this does not exists in this ABC. Conflicting private namespaces can have same names.
mappedId = addNamespace(secondNamespace.kind, getStringId(secondNsNameStr, true));
} else {
mappedId = getNamespaceId(secondNamespace.kind, secondNsNameStr, 0, true);
}
namespaceMap.put(i, mappedId);
}
namespaceSetMap.put(0, 0);
for (int i = 1; i < secondPool.constant_namespace_set.size(); i++) {
NamespaceSet secondNamespaceSet = secondPool.getNamespaceSet(i);
int mappedsNss[] = new int[secondNamespaceSet.namespaces.length];
for (int n = 0; n < secondNamespaceSet.namespaces.length; n++) {
mappedsNss[n] = namespaceMap.get(secondNamespaceSet.namespaces[n]);
}
int mappedId = getNamespaceSetId(mappedsNss, true);
namespaceSetMap.put(i, mappedId);
}
multinameMap.put(0, 0);
for (int i = 1; i < secondPool.constant_multiname.size(); i++) {
Multiname secondMultiname = secondPool.getMultiname(i);
Multiname importedMultiname = null;
int newNameIndex = secondMultiname.name_index <= 0 ? secondMultiname.name_index : getStringId(secondPool.getString(secondMultiname.name_index), true);
int newNsIndex = secondMultiname.namespace_index <= 0 ? secondMultiname.namespace_index : namespaceMap.get(secondMultiname.namespace_index);
int newNssIndex = secondMultiname.namespace_set_index <= 0 ? secondMultiname.namespace_set_index : namespaceSetMap.get(secondMultiname.namespace_set_index);
switch (secondMultiname.kind) {
case Multiname.MULTINAME:
importedMultiname = Multiname.createMultiname(false, newNameIndex, newNssIndex);
break;
case Multiname.MULTINAMEA:
importedMultiname = Multiname.createMultiname(true, newNameIndex, newNssIndex);
break;
case Multiname.MULTINAMEL:
importedMultiname = Multiname.createMultinameL(false, newNssIndex);
break;
case Multiname.MULTINAMELA:
importedMultiname = Multiname.createMultinameL(true, newNssIndex);
break;
case Multiname.QNAME:
importedMultiname = Multiname.createQName(false, newNameIndex, newNsIndex);
break;
case Multiname.QNAMEA:
importedMultiname = Multiname.createQName(true, newNameIndex, newNsIndex);
break;
case Multiname.RTQNAME:
importedMultiname = Multiname.createRTQName(false, newNameIndex);
break;
case Multiname.RTQNAMEA:
importedMultiname = Multiname.createRTQName(true, newNameIndex);
break;
case Multiname.RTQNAMEL:
importedMultiname = Multiname.createRTQNameL(false);
break;
case Multiname.RTQNAMELA:
importedMultiname = Multiname.createRTQNameL(true);
break;
case Multiname.TYPENAME:
int newQnameIndex = multinameMap.get(secondMultiname.qname_index);
int newParams[] = new int[secondMultiname.params.length];
for (int p = 0; p < secondMultiname.params.length; p++) {
newParams[p] = multinameMap.get(secondMultiname.params[p]);
}
importedMultiname = Multiname.createTypeName(newQnameIndex, newParams);
break;
}
int mappedId = getMultinameId(importedMultiname, true);
multinameMap.put(i, mappedId);
}
}
}
@@ -386,4 +386,17 @@ public class MethodInfo {
}
return writer.hilightSpecial(rname, HighlightSpecialType.RETURNS);
}
public String getReturnTypeRaw(AVM2ConstantPool constants, List<DottedChain> fullyQualifiedNames) {
String rname = "*";
if (ret_type > 0) {
Multiname multiname = constants.getMultiname(ret_type);
if (multiname.kind != Multiname.TYPENAME && multiname.name_index > 0 && constants.getString(multiname.name_index).equals("void")) {
rname = "void";
} else {
rname = multiname.getName(constants, fullyQualifiedNames, false);
}
}
return rname;
}
}
@@ -131,14 +131,16 @@ public class TraitClass extends Trait implements TraitWithSlot {
if (bodyIndex != -1) {
writer.startTrait(GraphTextWriter.TRAIT_CLASS_INITIALIZER);
writer.startMethod(classInfo.cinit_index);
if (!classInitializerIsEmpty) {
writer.startBlock();
abc.bodies.get(bodyIndex).toString(path +/*packageName +*/ "/" + instanceInfoName + ".staticinitializer", exportMode, abc, this, writer, fullyQualifiedNames);
writer.endBlock();
} else {
//Note: There must be trait/method highlight even if the initializer is empty to TraitList in GUI to work correctly
//TODO: handle this better in GUI(?)
writer.append(" ").newLine();
if (exportMode != ScriptExportMode.AS_METHOD_STUBS) {
if (!classInitializerIsEmpty) {
writer.startBlock();
abc.bodies.get(bodyIndex).toString(path +/*packageName +*/ "/" + instanceInfoName + ".staticinitializer", exportMode, abc, this, writer, fullyQualifiedNames);
writer.endBlock();
} else {
//Note: There must be trait/method highlight even if the initializer is empty to TraitList in GUI to work correctly
//TODO: handle this better in GUI(?)
writer.append(" ").newLine();
}
}
writer.endMethod();
writer.endTrait();
@@ -178,8 +180,10 @@ public class TraitClass extends Trait implements TraitWithSlot {
MethodBody body = bodyIndex == -1 ? null : abc.bodies.get(bodyIndex);
abc.method_info.get(instanceInfo.iinit_index).getParamStr(writer, abc.constants, body, abc, fullyQualifiedNames);
writer.appendNoHilight(")").startBlock();
if (body != null) {
body.toString(path +/*packageName +*/ "/" + instanceInfoName + ".initializer", exportMode, abc, this, writer, fullyQualifiedNames);
if (exportMode != ScriptExportMode.AS_METHOD_STUBS) {
if (body != null) {
body.toString(path +/*packageName +*/ "/" + instanceInfoName + ".initializer", exportMode, abc, this, writer, fullyQualifiedNames);
}
}
writer.endBlock().newLine();
@@ -27,6 +27,7 @@ import com.jpexs.decompiler.flash.exporters.script.Dependency;
import com.jpexs.decompiler.flash.exporters.script.DependencyParser;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.flash.helpers.NulWriter;
import com.jpexs.decompiler.flash.helpers.hilight.HighlightData;
import com.jpexs.decompiler.flash.helpers.hilight.HighlightSpecialType;
import com.jpexs.decompiler.graph.DottedChain;
import com.jpexs.decompiler.graph.ScopeStack;
@@ -112,9 +113,11 @@ public class TraitMethodGetterSetter extends Trait {
path = path + "." + getName(abc).getName(abc.constants, fullyQualifiedNames, false);
convertHeader(parent, convertData, path, abc, isStatic, exportMode, scriptIndex, classIndex, writer, fullyQualifiedNames, parallel);
int bodyIndex = abc.findBodyIndex(method_info);
if (!(classIndex != -1 && abc.instance_info.get(classIndex).isInterface() || bodyIndex == -1)) {
if (bodyIndex != -1) {
abc.bodies.get(bodyIndex).convert(convertData, path, exportMode, isStatic, method_info, scriptIndex, classIndex, abc, this, new ScopeStack(), 0, writer, fullyQualifiedNames, null, true);
if (exportMode != ScriptExportMode.AS_METHOD_STUBS) {
if (!(classIndex != -1 && abc.instance_info.get(classIndex).isInterface() || bodyIndex == -1)) {
if (bodyIndex != -1) {
abc.bodies.get(bodyIndex).convert(convertData, path, exportMode, isStatic, method_info, scriptIndex, classIndex, abc, this, new ScopeStack(), 0, writer, fullyQualifiedNames, null, true);
}
}
}
writer.endMethod();
@@ -135,8 +138,30 @@ public class TraitMethodGetterSetter extends Trait {
writer.appendNoHilight(";");
} else {
writer.startBlock();
if (bodyIndex != -1) {
abc.bodies.get(bodyIndex).toString(path, exportMode, abc, this, writer, fullyQualifiedNames);
if (exportMode != ScriptExportMode.AS_METHOD_STUBS) {
if (bodyIndex != -1) {
abc.bodies.get(bodyIndex).toString(path, exportMode, abc, this, writer, fullyQualifiedNames);
}
} else {
String retTypeRaw = abc.method_info.get(method_info).getReturnTypeRaw(abc.constants, fullyQualifiedNames);
switch (retTypeRaw) {
case "void":
break;
case "int":
case "uint":
writer.append("return 0; //autogenerated").newLine();
break;
case "double":
case "float":
writer.append("return 0.0; //autogenerated").newLine();
break;
case "String":
writer.append("return \"\"; //autogenerated").newLine();
break;
default:
writer.append("return null; //autogenerated").newLine();
break;
}
}
writer.endBlock();
}
@@ -106,7 +106,7 @@ public class TraitSlotConst extends Trait implements TraitWithSlot {
return writer;
}
public void getValueStr(Trait parent, ConvertData convertData, GraphTextWriter writer, ABC abc, List<DottedChain> fullyQualifiedNames) throws InterruptedException {
public void getValueStr(ScriptExportMode exportMode, Trait parent, ConvertData convertData, GraphTextWriter writer, ABC abc, List<DottedChain> fullyQualifiedNames) throws InterruptedException {
if (convertData.assignedValues.containsKey(this)) {
AssignedValue assignment = convertData.assignedValues.get(this);
@@ -119,8 +119,9 @@ public class TraitSlotConst extends Trait implements TraitWithSlot {
writer.appendNoHilight(assignment.method);
writer.newLine();
}
assignment.value.toString(writer, LocalData.create(abc.constants, new HashMap<>(), fullyQualifiedNames));
if (exportMode != ScriptExportMode.AS_METHOD_STUBS) {
assignment.value.toString(writer, LocalData.create(abc.constants, new HashMap<>(), fullyQualifiedNames));
}
writer.endMethod();
writer.endTrait();
return;
@@ -165,7 +166,7 @@ public class TraitSlotConst extends Trait implements TraitWithSlot {
getNameStr(writer, abc, fullyQualifiedNames);
if (value_kind != 0 || convertData.assignedValues.containsKey(this)) {
writer.appendNoHilight(" = ");
getValueStr(parent, convertData, writer, abc, fullyQualifiedNames);
getValueStr(exportMode, parent, convertData, writer, abc, fullyQualifiedNames);
}
return writer.appendNoHilight(";").newLine();
}
@@ -174,7 +175,7 @@ public class TraitSlotConst extends Trait implements TraitWithSlot {
public void convert(Trait parent, ConvertData convertData, String path, ABC abc, boolean isStatic, ScriptExportMode exportMode, int scriptIndex, int classIndex, NulWriter writer, List<DottedChain> fullyQualifiedNames, boolean parallel) throws InterruptedException {
getNameStr(writer, abc, fullyQualifiedNames);
if (value_kind != 0 || convertData.assignedValues.containsKey(this)) {
getValueStr(parent, convertData, writer, abc, fullyQualifiedNames);
getValueStr(exportMode, parent, convertData, writer, abc, fullyQualifiedNames);
}
}
@@ -22,5 +22,5 @@ package com.jpexs.decompiler.flash.exporters.modes;
*/
public enum ScriptExportMode {
AS, PCODE, PCODE_HEX, HEX, CONSTANTS;
AS, PCODE, PCODE_HEX, HEX, CONSTANTS, AS_METHOD_STUBS;
}
@@ -41,6 +41,7 @@ public class ScriptExportSettings {
public String getFileExtension() {
switch (mode) {
case AS:
case AS_METHOD_STUBS:
return ".as";
case PCODE:
case PCODE_HEX:
@@ -6,7 +6,10 @@ import com.jpexs.decompiler.flash.abc.ScriptPack;
import com.jpexs.decompiler.flash.abc.types.InstanceInfo;
import com.jpexs.decompiler.flash.abc.types.ScriptInfo;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.exporters.script.AS3ScriptExporter;
import com.jpexs.decompiler.flash.exporters.script.LinkReportExporter;
import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings;
import com.jpexs.decompiler.flash.exporters.swf.SwfToSwcExporter;
import com.jpexs.decompiler.flash.tags.ABCContainerTag;
import com.jpexs.decompiler.flash.tags.Tag;
@@ -112,21 +115,38 @@ public class As3ScriptReplacer extends MxmlcRunner {
}
}
//remove all subclasses
List<ScriptPack> removedPacks = new ArrayList<>();
//remove all subclasses from the SWC
for (ScriptPack sp : copyPacks) {
DottedChain dc = sp.getPathPackage().add(sp.getPathScriptName());
if (isParentDeleted(sp.abc, sp.allABCs, dc)) {
sp.abc.script_info.get(sp.scriptIndex).delete(sp.abc, true);
modAbcs.add(sp.abc);
removedPacks.add(sp);
}
}
//Export subclasses so they can be compiled by Flex, but ONLY STUBS.
//No method code to avoid code compilation problems.
//This compiled code won't be used at all in original SWF,
//it is used only by Flex to properly compile current script
AS3ScriptExporter ex = new AS3ScriptExporter();
ex.exportActionScript3(swfCopy, null, tempDir.getAbsolutePath(), removedPacks, new ScriptExportSettings(ScriptExportMode.AS_METHOD_STUBS, false), false, null);
//now really remove the classes from SWF copy
for (ABC a : modAbcs) {
a.pack();
}
//Generate SWC file from the modified SWF file.
//Flex then uses the code already present in the SWC, no need to decompile it (hurray!)
SwfToSwcExporter swcExport = new SwfToSwcExporter();
swcExport.exportSwf(swfCopy, swcFile, true);
//Write new script
Helper.writeFile(scriptFileToCompile.getAbsolutePath(), txt.getBytes("UTF-8"));
//Compile it (and subclasses stubs)
mxmlc("-include-inheritance-dependencies-only", "-warnings=false", "-library-path", swcFile.getAbsolutePath(), "-source-path", tempDir.getAbsolutePath(), "-output", compiledSwfFile.getAbsolutePath(), "-debug=true", scriptFileToCompile.getAbsolutePath());
try (FileInputStream fis = new FileInputStream(compiledSwfFile)) {
@@ -146,6 +166,7 @@ public class As3ScriptReplacer extends MxmlcRunner {
ABCContainerTag lastTag = newTags.get(newTags.size() - 1);
((Tag) lastTag).setSwf(swf);
swf.addTag(oldTagIndex + 1, (Tag) lastTag);
//TODO: looks like ABCs need to be merged. Parent class needs to be defined earlier than used :-(
((Tag) lastTag).setModified(true);
((Tag) oldPack.abc.parentTag).setModified(true);
}