diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/SetPropertyAVM2Item.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/SetPropertyAVM2Item.java index a21bd145c..ef860a927 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/SetPropertyAVM2Item.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/SetPropertyAVM2Item.java @@ -41,8 +41,6 @@ public class SetPropertyAVM2Item extends AVM2Item implements SetTypeAVM2Item, As public GraphTargetItem propertyName; - public GraphTargetItem value; - @Override public GraphPart getFirstPart() { return value.getFirstPart(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index 6e664b970..e1e587663 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -565,6 +565,14 @@ public class Configuration { @ConfigurationCategory("script") public static final ConfigurationItem handleSkinPartsAutomatically = null; + @ConfigurationDefaultBoolean(false) + //@ConfigurationCategory("script") + public static final ConfigurationItem _ignoreAdditionalFlexClasses = null; + + @ConfigurationDefaultBoolean(false) + //@ConfigurationCategory("script") + public static final ConfigurationItem _enableFlexExport = null; + private enum OSId { WINDOWS, OSX, UNIX diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/AS3ScriptExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/AS3ScriptExporter.java index e4a0ab89e..7ebf49e2d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/AS3ScriptExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/AS3ScriptExporter.java @@ -20,15 +20,42 @@ import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler; import com.jpexs.decompiler.flash.EventListener; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.abc.ScriptPack; +import com.jpexs.decompiler.flash.abc.avm2.model.CallPropertyAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.CoerceAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ConstructPropAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.FullMultinameAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.GetPropertyAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.InitPropertyAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.LocalRegAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NewArrayAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.SetLocalAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.SetPropertyAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ThisAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.clauses.DeclarationAVM2Item; +import com.jpexs.decompiler.flash.abc.types.ConvertData; +import com.jpexs.decompiler.flash.abc.types.Multiname; +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.TraitMethodGetterSetter; import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.ecma.EcmaScript; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings; +import com.jpexs.decompiler.flash.helpers.NulWriter; +import com.jpexs.decompiler.graph.GraphTargetItem; +import com.jpexs.decompiler.graph.ScopeStack; import com.jpexs.helpers.CancellableWorker; import com.jpexs.helpers.Helper; import com.jpexs.helpers.Path; +import com.jpexs.helpers.utf8.Utf8Helper; import java.io.File; +import java.io.StringReader; +import java.io.StringWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -39,6 +66,14 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; /** * @@ -48,10 +83,278 @@ public class AS3ScriptExporter { private static final Logger logger = Logger.getLogger(AS3ScriptExporter.class.getName()); + private static String prettyFormatXML(String input) { + int indent = 5; + try { + Source xmlInput = new StreamSource(new StringReader(input)); + StringWriter stringWriter = new StringWriter(); + StreamResult xmlOutput = new StreamResult(stringWriter); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setAttribute("indent-number", indent); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.transform(xmlInput, xmlOutput); + return xmlOutput.getWriter().toString(); + } catch (TransformerFactoryConfigurationError | IllegalArgumentException | TransformerException e) { + logger.log(Level.SEVERE, "Pretty print error", e); + return input; + } + } + + private String handleMxmlMethod(Map namespaces, ScriptPack pack, int cindex, TraitMethodGetterSetter t) { + StringBuilder out = new StringBuilder(); + int method = t.method_info; + try { + pack.abc.findBody(method).convert(new ConvertData(), "??", ScriptExportMode.AS, false, method, pack.scriptIndex, cindex, pack.abc, t, new ScopeStack(), 0/*?*/, new NulWriter(), new ArrayList<>(), new ArrayList<>(), true); + + List ci = pack.abc.findBody(method).convertedItems; + if (!ci.isEmpty()) { + if (ci.get(0) instanceof DeclarationAVM2Item) { + GraphTargetItem asg = ((DeclarationAVM2Item) ci.get(0)).assignment; + if (asg instanceof SetLocalAVM2Item) { + if (asg.value instanceof CoerceAVM2Item) { + if (asg.value.value instanceof ConstructPropAVM2Item) { + ConstructPropAVM2Item cp = (ConstructPropAVM2Item) asg.value.value; + if (cp.propertyName instanceof FullMultinameAVM2Item) { + int name = ((FullMultinameAVM2Item) cp.propertyName).multinameIndex; + String tagName = getTagName(pack, name, name, namespaces); + StringBuilder props = new StringBuilder(); + StringBuilder tagContent = new StringBuilder(); + for (int i = 1; i < ci.size(); i++) { + if (ci.get(i) instanceof SetPropertyAVM2Item) { + SetPropertyAVM2Item sp = (SetPropertyAVM2Item) ci.get(i); + if (sp.object instanceof LocalRegAVM2Item) { + if (((SetLocalAVM2Item) asg).regIndex == ((LocalRegAVM2Item) sp.object).regIndex) { + GraphTargetItem val = sp.value; + if (sp.propertyName instanceof FullMultinameAVM2Item) { + String propName = pack.abc.constants.getMultiname(((FullMultinameAVM2Item) sp.propertyName).multinameIndex).getName(pack.abc.constants, new ArrayList<>(), true); + if (val instanceof CallPropertyAVM2Item) { + CallPropertyAVM2Item cap = (CallPropertyAVM2Item) val; + if (cp.propertyName instanceof FullMultinameAVM2Item) { + int name2 = ((FullMultinameAVM2Item) cap.propertyName).multinameIndex; + for (Trait ct : pack.abc.instance_info.get(cindex).instance_traits.traits) { + if (ct.name_index == name2 && (ct instanceof TraitMethodGetterSetter)) { + tagContent.append(handleMxmlMethod(namespaces, pack, cindex, (TraitMethodGetterSetter) ct)); + } + } + } + + } else if (val instanceof ConstructPropAVM2Item) { + //??? + } else { + props.append(" ").append(propName).append("=\"").append(EcmaScript.toString(val.getResult())).append("\""); + } + } + } + } + } + } + out.append("<").append(tagName).append(props); + if (tagContent.length() > 0) { + out.append(">"); + out.append(tagContent); + out.append(""); + } else { + out.append(" />"); + } + } + } + } + } + } + } + //DeclarationAVM2->assignment=SetLocalAVM2->value=coerceAVM2Item-ConstructProp-FullMultiname + //setprop - object localreg, propertyName, value value + //setprop + + } catch (InterruptedException ex) { + //? + Logger.getLogger(AS3ScriptExporter.class.getName()).log(Level.SEVERE, null, ex); + } + return out.toString(); + } + + private String handleMxmlArrMethod(Map namespaces, ScriptPack pack, int cindex, TraitMethodGetterSetter t) { + StringBuilder out = new StringBuilder(); + int method = t.method_info; + try { + pack.abc.findBody(method).convert(new ConvertData(), "??", ScriptExportMode.AS, false, method, pack.scriptIndex, cindex, pack.abc, t, new ScopeStack(), 0/*?*/, new NulWriter(), new ArrayList<>(), new ArrayList<>(), true); + + List ci = pack.abc.findBody(method).convertedItems; + if (!ci.isEmpty() && (ci.get(0) instanceof DeclarationAVM2Item)) { + GraphTargetItem asg = ((DeclarationAVM2Item) ci.get(0)).assignment; + if (asg instanceof SetLocalAVM2Item) { + if (((SetLocalAVM2Item) asg).value.getNotCoerced() instanceof NewArrayAVM2Item) { + NewArrayAVM2Item nav = (NewArrayAVM2Item) ((SetLocalAVM2Item) asg).value.getNotCoerced(); + for (GraphTargetItem v : nav.values) { + if (v instanceof CallPropertyAVM2Item) { + CallPropertyAVM2Item cp = (CallPropertyAVM2Item) v; + if (cp.receiver instanceof ThisAVM2Item) { + if (cp.propertyName instanceof FullMultinameAVM2Item) { + int name = ((FullMultinameAVM2Item) cp.propertyName).multinameIndex; + for (Trait ct : pack.abc.instance_info.get(cindex).instance_traits.traits) { + if (ct.name_index == name && (ct instanceof TraitMethodGetterSetter)) { + out.append(handleMxmlMethod(namespaces, pack, cindex, (TraitMethodGetterSetter) ct)); + } + } + } + } + } + } + } + } + } + //declaration->setlocal->(coreceavm)->NewArray[ callpropertyav ->receiver this, propName ] + } catch (InterruptedException ex) { + //? + Logger.getLogger(AS3ScriptExporter.class.getName()).log(Level.SEVERE, null, ex); + } + return out.toString(); + } + + private String getTagName(ScriptPack pack, int classMIndex, int nameMindex, Map namespaces) { + Multiname m = pack.abc.constants.getMultiname(classMIndex); + Multiname mn = pack.abc.constants.getMultiname(nameMindex); + String parentName = mn.getName(pack.abc.constants, new ArrayList<>(), true); + String pkg = m.getNamespace(pack.abc.constants).getName(pack.abc.constants).toRawString(); + pkg += ".*"; + String ns = null; + if (pkg.startsWith("spark.")) { + ns = "s"; + pkg = "library://ns.adobe.com/flex/spark"; + namespaces.put(ns, pkg); + } else if (pkg.startsWith("mx.")) { + ns = "mx"; + pkg = "library://ns.adobe.com/flex/mx"; + //TODO: all common SWC libraries + namespaces.put(ns, pkg); + } else if (namespaces.containsValue(pkg)) { + for (String k : namespaces.keySet()) { + if (namespaces.get(k).equals(pkg)) { + ns = k; + break; + } + } + } else { + String baseNs = "pkg"; + ns = baseNs; + int i = 1; + for (i++; namespaces.containsKey(ns); i++) { + ns = baseNs + i; + } + namespaces.put(ns, pkg); + } + return ns + ":" + parentName; + } + + private String generateMxml(ScriptPack pack) { + StringBuilder out = new StringBuilder(); + StringBuilder tagProp = new StringBuilder(); + StringBuilder tagContent = new StringBuilder(); + + String hdr = "" + System.lineSeparator(); + + Map namespaces = new HashMap<>(); + namespaces.put("fx", "http://ns.adobe.com/mxml/2009"); + + for (int ti : pack.traitIndices) { + Trait t = pack.abc.script_info.get(pack.scriptIndex).traits.traits.get(ti); + if (t instanceof TraitClass) { + + int cindex = ((TraitClass) t).class_info; + int mindex = pack.abc.instance_info.get(cindex).super_index; + + String tagName = getTagName(pack, mindex, mindex, namespaces); + + int iinit = pack.abc.instance_info.get(cindex).iinit_index; + + try { + pack.abc.findBody(iinit).convert(new ConvertData(), "??", ScriptExportMode.AS, false, iinit, pack.scriptIndex, cindex, pack.abc, t, new ScopeStack(), 0/*?*/, new NulWriter(), new ArrayList<>(), new ArrayList<>(), true); + List iinitBody = pack.abc.findBody(iinit).convertedItems; + for (GraphTargetItem it : iinitBody) { + if (it instanceof InitPropertyAVM2Item) { + InitPropertyAVM2Item ip = (InitPropertyAVM2Item) it; + if (ip.object instanceof ThisAVM2Item) { + String propName = pack.abc.constants.getMultiname(ip.propertyName.multinameIndex).getName(pack.abc.constants, new ArrayList<>(), true); + GraphTargetItem val = ((InitPropertyAVM2Item) it).value; + if (val instanceof CallPropertyAVM2Item) { + CallPropertyAVM2Item cp = (CallPropertyAVM2Item) val; + if (cp.propertyName instanceof FullMultinameAVM2Item) { + String subtagName = getTagName(pack, mindex, ip.propertyName.multinameIndex, namespaces); + tagContent.append("<").append(subtagName).append(">"); + int name = ((FullMultinameAVM2Item) cp.propertyName).multinameIndex; + for (Trait ct : pack.abc.instance_info.get(cindex).instance_traits.traits) { + if (ct.name_index == name && (ct instanceof TraitMethodGetterSetter)) { + tagContent.append(handleMxmlMethod(namespaces, pack, cindex, (TraitMethodGetterSetter) ct)); + } + } + tagContent.append(""); + } + + } else if (val instanceof ConstructPropAVM2Item) { + ConstructPropAVM2Item cp = (ConstructPropAVM2Item) val; + if (cp.propertyName instanceof FullMultinameAVM2Item) { + Multiname m = pack.abc.constants.getMultiname(((FullMultinameAVM2Item) cp.propertyName).multinameIndex); + if ("mx.core.DeferredInstanceFromFunction".equals("" + m.getNameWithNamespace(pack.abc.constants))) { + if (!cp.args.isEmpty()) { + if (cp.args.get(0) instanceof GetPropertyAVM2Item) { + GetPropertyAVM2Item gp = (GetPropertyAVM2Item) cp.args.get(0); + if (gp.object instanceof ThisAVM2Item) { + int name = ((FullMultinameAVM2Item) gp.propertyName).multinameIndex; + for (Trait ct : pack.abc.instance_info.get(cindex).instance_traits.traits) { + if (ct.name_index == name && (ct instanceof TraitMethodGetterSetter)) { + tagContent.append(handleMxmlArrMethod(namespaces, pack, cindex, (TraitMethodGetterSetter) ct)); + } + } + } + } + } + } + } + } else { + tagProp.append(" ").append(propName).append("=\"").append(EcmaScript.toString(val.getResult())).append("\""); + } + } + //System.err.println("" + ((InitPropertyAVM2Item) it).value); + } + } + out.append("<").append(tagName); + + for (String ns : namespaces.keySet()) { + out.append(" xmlns:").append(ns).append("=\"").append(namespaces.get(ns)).append("\""); + } + if (tagContent.length() == 0) { + out.append(" />"); + } else { + out.append(tagProp).append(">"); + out.append(tagContent); + + out.append(""); + } + + } catch (InterruptedException ex) { + Logger.getLogger(AS3ScriptExporter.class.getName()).log(Level.SEVERE, null, ex); + } + + } + } + + return hdr + prettyFormatXML(out.toString()); + } + public List exportActionScript3(SWF swf, AbortRetryIgnoreHandler handler, String outdir, List as3scripts, ScriptExportSettings exportSettings, boolean parallel, EventListener evl) { final List ret = new ArrayList<>(); final List packs = as3scripts != null ? as3scripts : swf.getAS3Packs(); + List ignoredClasses = new ArrayList<>(); + List ignoredNss = new ArrayList<>(); + + String flexClass = null; + if (Configuration._enableFlexExport.get()) { + flexClass = swf.getFlexMainClass(ignoredClasses, ignoredNss); + } + int cnt = 1; List tasks = new ArrayList<>(); Set files = new HashSet<>(); @@ -59,6 +362,19 @@ public class AS3ScriptExporter { if (!item.isSimple && Configuration.ignoreCLikePackages.get()) { continue; } + if (ignoredClasses.contains(item.getClassPath().toRawString())) { + continue; + } + if (flexClass != null && item.getClassPath().toRawString().equals(flexClass)) { + File file = item.getExportFile(outdir, ".mxml"); + String filePath = file.getPath(); + String mxml = generateMxml(item); + if (mxml != null) { + Helper.writeFile(filePath, Utf8Helper.getBytes(mxml)); + files.add(filePath.toLowerCase()); + continue; + } + } File file = item.getExportFile(outdir, exportSettings); String filePath = file.getPath(); diff --git a/src/com/jpexs/decompiler/flash/gui/AdvancedSettingsDialog.java b/src/com/jpexs/decompiler/flash/gui/AdvancedSettingsDialog.java index 81e4d2654..38b777494 100644 --- a/src/com/jpexs/decompiler/flash/gui/AdvancedSettingsDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/AdvancedSettingsDialog.java @@ -330,16 +330,29 @@ public class AdvancedSettingsDialog extends AppDialog { JPanel configPanel = new JPanel(new SpringLayout()); int itemCount = 0; List names = new ArrayList<>(categorized.get(cat).keySet()); + + final Map locNames = new HashMap<>(); + for (String name : names) { + String locName = "(Internal) " + name; + + if (resourceBundle.containsKey("config.name." + name)) { + locName = resourceBundle.getString("config.name." + name); + } else if (!name.startsWith("_")) { //must have _ prefix to be undocumented + throw new RuntimeException("Missing configuration name: " + name); + } + locNames.put(name, locName); + } + Collections.sort(names, new Comparator() { @Override public int compare(String name1, String name2) { - return resourceBundle.getString("config.name." + name1).compareTo(resourceBundle.getString("config.name." + name2)); + return locNames.get(name1).compareTo(locNames.get(name2)); } }); for (String name : names) { Field field = categorized.get(cat).get(name); - String locName = resourceBundle.getString("config.name." + name); + String locName = locNames.get(name); try { ConfigurationItem item = (ConfigurationItem) field.get(null); @@ -352,7 +365,10 @@ public class AdvancedSettingsDialog extends AppDialog { Class itemType = (Class) itemType2; - String description = resourceBundle.getString("config.description." + name); + String description = ""; + if (resourceBundle.containsKey("config.description." + name)) { + description = resourceBundle.getString("config.description." + name); + } Object defaultValue = Configuration.getDefaultValue(field); if (name.equals("gui.skin")) {