/* * Copyright (C) 2010-2016 JPEXS, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.jpexs.decompiler.flash.exporters.script; 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; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; 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; /** * * @author JPEXS */ 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<String, String> 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<GraphTargetItem> 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, 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("</").append(tagName).append(">"); } else { out.append(" />"); } } } } } } } //DeclarationAVM2->assignment=SetLocalAVM2->value=coerceAVM2Item-ConstructProp-FullMultiname //setprop - object localreg, propertyName, value value //setprop } catch (InterruptedException ex) { //? logger.log(Level.SEVERE, null, ex); } return out.toString(); } private String handleMxmlArrMethod(Map<String, String> 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<GraphTargetItem> 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.log(Level.SEVERE, null, ex); } return out.toString(); } private String getTagName(ScriptPack pack, int classMIndex, int nameMindex, Map<String, String> 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, 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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + System.lineSeparator(); Map<String, String> 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<GraphTargetItem> 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, 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("</").append(subtagName).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, true))) { 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("</").append(tagName).append(">"); } } catch (InterruptedException ex) { logger.log(Level.SEVERE, null, ex); } } } return hdr + prettyFormatXML(out.toString()); } public List<File> exportActionScript3(SWF swf, AbortRetryIgnoreHandler handler, String outdir, List<ScriptPack> as3scripts, ScriptExportSettings exportSettings, boolean parallel, EventListener evl) { final List<File> ret = new ArrayList<>(); final List<ScriptPack> packs = as3scripts != null ? as3scripts : swf.getAS3Packs(); List<String> ignoredClasses = new ArrayList<>(); List<String> ignoredNss = new ArrayList<>(); String flexClass = null; if (Configuration._enableFlexExport.get()) { flexClass = swf.getFlexMainClass(ignoredClasses, ignoredNss); } int cnt = 1; List<ExportPackTask> tasks = new ArrayList<>(); Set<String> files = new HashSet<>(); for (ScriptPack item : packs) { 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 = null; if (!exportSettings.singleFile) { file = item.getExportFile(outdir, exportSettings); String filePath = file.getPath(); if (files.contains(filePath.toLowerCase())) { String parentPath = file.getParent(); String fileName = file.getName(); String extension = Path.getExtension(fileName); String fileNameWithoutExtension = Path.getFileNameWithoutExtension(file); int i = 2; do { filePath = Path.combine(parentPath, fileNameWithoutExtension + "_" + i++ + extension); } while (files.contains(filePath.toLowerCase())); file = new File(filePath); } files.add(filePath.toLowerCase()); } tasks.add(new ExportPackTask(handler, cnt++, packs.size(), item.getClassPath(), item, file, exportSettings, parallel, evl)); } if (!parallel || tasks.size() < 2) { try { CancellableWorker.call(new Callable<Void>() { @Override public Void call() throws Exception { for (ExportPackTask task : tasks) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } ret.add(task.call()); } return null; } }, Configuration.exportTimeout.get(), TimeUnit.SECONDS); } catch (TimeoutException ex) { logger.log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached", ex); } catch (ExecutionException | InterruptedException ex) { logger.log(Level.SEVERE, "Error during ABC export", ex); } } else { ExecutorService executor = Executors.newFixedThreadPool(Configuration.getParallelThreadCount()); List<Future<File>> futureResults = new ArrayList<>(); for (ExportPackTask task : tasks) { Future<File> future = executor.submit(task); futureResults.add(future); } try { executor.shutdown(); if (!executor.awaitTermination(Configuration.exportTimeout.get(), TimeUnit.SECONDS)) { logger.log(Level.SEVERE, Helper.formatTimeToText(Configuration.exportTimeout.get()) + " ActionScript export limit reached"); } } catch (InterruptedException ex) { } finally { executor.shutdownNow(); } for (int f = 0; f < futureResults.size(); f++) { try { if (futureResults.get(f).isDone()) { ret.add(futureResults.get(f).get()); } } catch (InterruptedException ex) { } catch (ExecutionException ex) { logger.log(Level.SEVERE, "Error during ABC export", ex); } } } return ret; } }