/* * 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.abc; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.abc.avm2.ConvertException; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instructions; import com.jpexs.decompiler.flash.abc.avm2.instructions.debug.DebugFileIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.debug.DebugIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.debug.DebugLineIns; import com.jpexs.decompiler.flash.abc.types.ConvertData; import com.jpexs.decompiler.flash.abc.types.MethodBody; import com.jpexs.decompiler.flash.abc.types.Multiname; import com.jpexs.decompiler.flash.abc.types.Namespace; 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; import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; import com.jpexs.decompiler.flash.abc.types.traits.Traits; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings; import com.jpexs.decompiler.flash.helpers.FileTextWriter; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import com.jpexs.decompiler.flash.helpers.HighlightedText; import com.jpexs.decompiler.flash.helpers.NulWriter; import com.jpexs.decompiler.flash.helpers.hilight.Highlighting; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.treeitems.AS3ClassTreeItem; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.decompiler.graph.ScopeStack; import com.jpexs.helpers.CancellableWorker; import com.jpexs.helpers.Helper; import com.jpexs.helpers.Path; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author JPEXS */ public class ScriptPack extends AS3ClassTreeItem { private static final Logger logger = Logger.getLogger(ScriptPack.class.getName()); public final ABC abc; public List<ABC> allABCs; public final int scriptIndex; public final List<Integer> traitIndices; private final ClassPath path; public boolean isSimple = false; public boolean scriptInitializerIsEmpty = false; @Override public SWF getSwf() { return abc.getSwf(); } public ClassPath getClassPath() { return path; } public ScriptPack(ClassPath path, ABC abc, List<ABC> allAbcs, int scriptIndex, List<Integer> traitIndices) { super(path.className, path.namespaceSuffix, path); this.abc = abc; this.scriptIndex = scriptIndex; this.traitIndices = traitIndices; this.path = path; this.allABCs = allAbcs; } public DottedChain getPathPackage() { DottedChain packageName = DottedChain.TOPLEVEL; for (int t : traitIndices) { Multiname name = abc.script_info.get(scriptIndex).traits.traits.get(t).getName(abc); Namespace ns = name.getNamespace(abc.constants); if ((ns.kind == Namespace.KIND_PACKAGE) || (ns.kind == Namespace.KIND_PACKAGE_INTERNAL)) { packageName = ns.getName(abc.constants); // assume not null } } return packageName; } public String getPathScriptName() { String scriptName = ""; for (int t : traitIndices) { Multiname name = abc.script_info.get(scriptIndex).traits.traits.get(t).getName(abc); Namespace ns = name.getNamespace(abc.constants); if ((ns.kind == Namespace.KIND_PACKAGE) || (ns.kind == Namespace.KIND_PACKAGE_INTERNAL)) { scriptName = name.getName(abc.constants, null, false, true); } } return scriptName; } public File getExportFile(String directory, String extension) { String scriptName = getPathScriptName(); DottedChain packageName = getPathPackage(); File outDir = new File(directory + File.separatorChar + packageName.toFilePath()); String fileName = outDir.toString() + File.separator + Helper.makeFileName(scriptName) + extension; return new File(fileName); } public File getExportFile(String directory, ScriptExportSettings exportSettings) { if (exportSettings.singleFile) { return null; } return getExportFile(directory, exportSettings.getFileExtension()); } /*public String getPath() { String packageName = ""; String scriptName = ""; for (int t : traitIndices) { Multiname name = abc.script_info[scriptIndex].traits.traits.get(t).getName(abc); Namespace ns = name.getNamespace(abc.constants); if ((ns.kind == Namespace.KIND_PACKAGE) || (ns.kind == Namespace.KIND_PACKAGE_INTERNAL)) { packageName = ns.getName(abc.constants); scriptName = name.getName(abc.constants, new ArrayList<>()); } } return packageName.equals("") ? scriptName : packageName + "." + scriptName; }*/ public void convert(final NulWriter writer, final List<Trait> traits, final ConvertData convertData, final ScriptExportMode exportMode, final boolean parallel) throws InterruptedException { int sinit_index = abc.script_info.get(scriptIndex).init_index; int sinit_bodyIndex = abc.findBodyIndex(sinit_index); if (sinit_bodyIndex != -1) { List<Traits> ts = new ArrayList<>(); //initialize all classes traits for (Trait t : traits) { if (t instanceof TraitClass) { ts.add(abc.class_info.get(((TraitClass) t).class_info).static_traits); } } ts.add(abc.script_info.get(scriptIndex).traits); writer.mark(); abc.bodies.get(sinit_bodyIndex).convert(convertData, path +/*packageName +*/ "/.scriptinitializer", exportMode, true, sinit_index, scriptIndex, -1, abc, null, new ScopeStack(), GraphTextWriter.TRAIT_SCRIPT_INITIALIZER, writer, new ArrayList<>(), ts, true); scriptInitializerIsEmpty = !writer.getMark(); } for (int t : traitIndices) { Trait trait = traits.get(t); Multiname name = trait.getName(abc); Namespace ns = name.getNamespace(abc.constants); if ((ns.kind == Namespace.KIND_PACKAGE) || (ns.kind == Namespace.KIND_PACKAGE_INTERNAL)) { trait.convertPackaged(null, convertData, "", abc, false, exportMode, scriptIndex, -1, writer, new ArrayList<>(), parallel); } else { trait.convert(null, convertData, "", abc, false, exportMode, scriptIndex, -1, writer, new ArrayList<>(), parallel); } } } private void appendTo(GraphTextWriter writer, List<Trait> traits, ConvertData convertData, ScriptExportMode exportMode, boolean parallel) throws InterruptedException { boolean first = true; //script initializer int script_init = abc.script_info.get(scriptIndex).init_index; int bodyIndex = abc.findBodyIndex(script_init); if (bodyIndex != -1 && Configuration.enableScriptInitializerDisplay.get()) { //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.startTrait(GraphTextWriter.TRAIT_SCRIPT_INITIALIZER); writer.startMethod(script_init); 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(); if (!scriptInitializerIsEmpty) { writer.newLine(); } first = false; } else { //"/*classInitializer*/"; } for (int t : traitIndices) { if (!first) { writer.newLine(); } Trait trait = traits.get(t); Multiname name = trait.getName(abc); Namespace ns = name.getNamespace(abc.constants); if ((ns.kind == Namespace.KIND_PACKAGE) || (ns.kind == Namespace.KIND_PACKAGE_INTERNAL)) { trait.toStringPackaged(null, convertData, "", abc, false, exportMode, scriptIndex, -1, writer, new ArrayList<>(), parallel); } else { trait.toString(null, convertData, "", abc, false, exportMode, scriptIndex, -1, writer, new ArrayList<>(), parallel); } first = false; } } public void toSource(GraphTextWriter writer, final List<Trait> traits, final ConvertData convertData, final ScriptExportMode exportMode, final boolean parallel) throws InterruptedException { writer.suspendMeasure(); int timeout = Configuration.decompilationTimeoutFile.get(); try { CancellableWorker.call(new Callable<Void>() { @Override public Void call() throws Exception { convert(new NulWriter(), traits, convertData, exportMode, parallel); return null; } }, timeout, TimeUnit.SECONDS); } catch (TimeoutException ex) { writer.continueMeasure(); logger.log(Level.SEVERE, "Decompilation timeout", ex); Helper.appendTimeoutCommentAs3(writer, timeout, 0); return; } catch (ExecutionException ex) { writer.continueMeasure(); Exception convertException = ex; Throwable cause = ex.getCause(); if (ex instanceof ExecutionException && cause instanceof Exception) { convertException = (Exception) cause; } logger.log(Level.SEVERE, "Decompilation error", convertException); Helper.appendErrorComment(writer, convertException); return; } writer.continueMeasure(); appendTo(writer, traits, convertData, exportMode, parallel); } public File export(File file, ScriptExportSettings exportSettings, boolean parallel) throws IOException, InterruptedException { if (!exportSettings.singleFile) { if (file.exists() && !Configuration.overwriteExistingFiles.get()) { return file; } } if (file != null) { Path.createDirectorySafe(file.getParentFile()); } try (FileTextWriter writer = exportSettings.singleFile ? null : new FileTextWriter(Configuration.getCodeFormatting(), new FileOutputStream(file))) { FileTextWriter writer2 = exportSettings.singleFile ? exportSettings.singleFileWriter : writer; toSource(writer2, abc.script_info.get(scriptIndex).traits.traits, new ConvertData(), exportSettings.mode, parallel); } catch (FileNotFoundException ex) { logger.log(Level.SEVERE, "The file path is probably too long", ex); } return file; } @Override public int hashCode() { int hash = 7; hash = 79 * hash + System.identityHashCode(abc); hash = 79 * hash + scriptIndex; hash = 79 * hash + Objects.hashCode(path); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final ScriptPack other = (ScriptPack) obj; if (abc != other.abc) { return false; } if (scriptIndex != other.scriptIndex) { return false; } return Objects.equals(path, other.path); } @Override public boolean isModified() { return abc.script_info.get(scriptIndex).isModified(); } /** * Injects debugfile, debugline instructions into the code * * Based on idea of Jacob Thompson * http://securityevaluators.com/knowledge/flash/ */ public void injectDebugInfo(File directoryPath) { Map<Integer, Map<Integer, Integer>> bodyToPosToLine = new HashMap<>(); Map<Integer, Map<Integer, Integer>> bodyLineToPos = new HashMap<>(); Map<Integer, Map<Integer, String>> bodyToRegToName = new HashMap<>(); Map<Integer, Map<Integer, Integer>> bodyToRegToLine = new HashMap<>(); Set<Integer> lonelyBody = new HashSet<>(); try { HighlightedText decompiled = SWF.getCached(this); int line = 1; String txt = decompiled.text; txt = txt.replace("\r", ""); for (int i = 0; i < txt.length(); i++) { blk: { Highlighting sh = Highlighting.searchPos(decompiled.getSpecialHighlights(), i); Highlighting cls = Highlighting.searchPos(decompiled.getClassHighlights(), i); /*if (cls == null) { continue; }*/ Highlighting trt = Highlighting.searchPos(decompiled.getTraitHighlights(), i); /*if (trt == null) { continue; }*/ Highlighting method = Highlighting.searchPos(decompiled.getMethodHighlights(), i); if (method == null) { break blk; } Highlighting instr = Highlighting.searchPos(decompiled.getInstructionHighlights(), i); //h /*if (instr == null) { continue; }*/ int classIndex = cls == null ? -1 : (int) cls.getProperties().index; int methodIndex = (int) method.getProperties().index; int bodyIndex = abc.findBodyIndex(methodIndex); if (bodyIndex == -1) { break blk; } int pos = -1; int regIndex = -1; String regName = null; if (sh != null && sh.getProperties().declaration && sh.getProperties().regIndex > -1) { regIndex = sh.getProperties().regIndex; regName = sh.getProperties().localName; } if (instr != null) { if (instr.getProperties().declaration && instr.getProperties().regIndex > -1) { regIndex = instr.getProperties().regIndex; regName = instr.getProperties().localName; } long instrOffset = instr.getProperties().firstLineOffset; if (trt != null && cls != null) { int traitIndex = (int) trt.getProperties().index; Trait trait = abc.findTraitByTraitId(classIndex, traitIndex); if (((trait instanceof TraitMethodGetterSetter) && (((TraitMethodGetterSetter) trait).method_info != methodIndex)) || ((trait instanceof TraitFunction) && (((TraitFunction) trait).method_info != methodIndex))) { continue; //inner anonymous function - ignore. TODO: make work } } if (instrOffset == -1) { lonelyBody.add(bodyIndex); break blk; } try { pos = abc.bodies.get(bodyIndex).getCode().adr2pos(instrOffset); } catch (ConvertException cex) { //ignore } if (pos == -1) { lonelyBody.add(bodyIndex); break blk; } if (!bodyToPosToLine.containsKey(bodyIndex)) { bodyToPosToLine.put(bodyIndex, new HashMap<>()); bodyLineToPos.put(bodyIndex, new HashMap<>()); } //int origPos = bodyLineToPos.get(bodyIndex).containsKey(line) ? bodyLineToPos.get(bodyIndex).get(line) : -1; bodyToPosToLine.get(bodyIndex).put(pos, line); bodyLineToPos.get(bodyIndex).put(line, pos); } else { lonelyBody.add(bodyIndex); } if (regIndex > -1 && regName != null) { if (!bodyToRegToName.containsKey(bodyIndex)) { bodyToRegToName.put(bodyIndex, new HashMap<>()); bodyToRegToLine.put(bodyIndex, new HashMap<>()); } if (!bodyToRegToName.get(bodyIndex).containsKey(regIndex)) { bodyToRegToName.get(bodyIndex).put(regIndex, regName); bodyToRegToLine.get(bodyIndex).put(regIndex, line); } } } if (txt.charAt(i) == '\n') { line++; } } } catch (InterruptedException ex) { logger.log(Level.SEVERE, "Cannot decompile", ex); } int scriptInitBody = abc.findBodyIndex(abc.script_info.get(scriptIndex).init_index); if (!bodyToRegToName.containsKey(scriptInitBody)) { lonelyBody.add(scriptInitBody); } //String filepath = path.toString().replace('.', '/') + ".as"; String pkg = path.packageStr.toString(); String cls = path.className; String filename = new File(directoryPath, path.packageStr.toFilePath()) + ";" + pkg.replace(".", File.separator) + ";" + cls + ".as"; //Remove debug info from lonely bodies for (int bodyIndex : lonelyBody) { if (!bodyToPosToLine.keySet().contains(bodyIndex)) { MethodBody b = abc.bodies.get(bodyIndex); List<AVM2Instruction> code = b.getCode().code; for (int i = 0; i < code.size(); i++) { AVM2Instruction ins = code.get(i); if (ins.definition instanceof DebugLineIns) { b.removeInstruction(i); i--; } else if (ins.definition instanceof DebugFileIns) { b.removeInstruction(i); i--; } else if (ins.definition instanceof DebugIns) { b.removeInstruction(i); i--; } } b.setModified(); } } for (int bodyIndex : bodyToPosToLine.keySet()) { List<AVM2Instruction> delIns = new ArrayList<>(); MethodBody b = abc.bodies.get(bodyIndex); List<AVM2Instruction> code = b.getCode().code; //add old debug instructions to TOREMOVE list for (AVM2Instruction ins : code) { if (ins.definition instanceof DebugLineIns) { delIns.add(ins); } if (ins.definition instanceof DebugFileIns) { delIns.add(ins); } if (ins.definition instanceof DebugIns) { delIns.add(ins); } } int dpos = 0; b.insertInstruction(0, new AVM2Instruction(0, AVM2Instructions.DebugFile, new int[]{abc.constants.getStringId(filename, true)}), true); dpos++; Set<Integer> regs = bodyToRegToName.containsKey(bodyIndex) ? bodyToRegToName.get(bodyIndex).keySet() : new TreeSet<>(); for (int r : regs) { String name = bodyToRegToName.get(bodyIndex).get(r); int line = bodyToRegToLine.get(bodyIndex).get(r); b.insertInstruction(dpos++, new AVM2Instruction(0, AVM2Instructions.Debug, new int[]{1, abc.constants.getStringId(name, true), r - 1, line})); } List<Integer> pos = new ArrayList<>(bodyToPosToLine.get(bodyIndex).keySet()); Collections.sort(pos); Collections.reverse(pos); Set<Integer> addedLines = new HashSet<>(); loopi: for (int i : pos) { int line = bodyToPosToLine.get(bodyIndex).get(i); if (addedLines.contains(line)) { continue; } addedLines.add(line); logger.log(Level.FINE, "Script " + path + ": Insert debugline(" + line + ") at pos " + i + " to body " + bodyIndex); b.insertInstruction(i + dpos, new AVM2Instruction(0, AVM2Instructions.DebugLine, new int[]{line})); } //remove old debug instructions for (int i = 0; i < code.size(); i++) { AVM2Instruction ins = code.get(i); for (AVM2Instruction d : delIns) { if (ins == d) { b.removeInstruction(i); i--; break; } } } b.setModified(); } ((Tag) abc.parentTag).setModified(true); } public void injectPCodeDebugInfo(int abcIndex) { Map<Integer, String> bodyToIdentifier = new HashMap<>(); try { HighlightedText decompiled = SWF.getCached(this); String txt = decompiled.text; txt = txt.replace("\r", ""); for (int i = 0; i < txt.length(); i++) { blk: { Highlighting sh = Highlighting.searchPos(decompiled.getSpecialHighlights(), i); Highlighting cls = Highlighting.searchPos(decompiled.getClassHighlights(), i); Highlighting trt = Highlighting.searchPos(decompiled.getTraitHighlights(), i); Highlighting method = Highlighting.searchPos(decompiled.getMethodHighlights(), i); if (method == null) { break blk; } int classIndex = cls == null ? -1 : (int) cls.getProperties().index; int methodIndex = (int) method.getProperties().index; int bodyIndex = abc.findBodyIndex(methodIndex); if (bodyIndex == -1) { break blk; } Trait trait; int traitIndex = -10; if (trt != null && cls != null) { traitIndex = (int) trt.getProperties().index; trait = abc.findTraitByTraitId(classIndex, traitIndex); if (((trait instanceof TraitMethodGetterSetter) && (((TraitMethodGetterSetter) trait).method_info != methodIndex)) || ((trait instanceof TraitFunction) && (((TraitFunction) trait).method_info != methodIndex))) { continue; //inner anonymous function - ignore. TODO: make work } } bodyToIdentifier.put(bodyIndex, "abc:" + abcIndex + ",script:" + scriptIndex + ",class:" + classIndex + ",trait:" + traitIndex + ",method:" + methodIndex + ",body:" + bodyIndex); } } } catch (InterruptedException ex) { logger.log(Level.SEVERE, "Cannot decompile", ex); } int scriptInitBody = abc.findBodyIndex(abc.script_info.get(scriptIndex).init_index); if (!bodyToIdentifier.containsKey(scriptInitBody)) { bodyToIdentifier.put(scriptInitBody, "abc:" + abcIndex + ",script:" + scriptIndex + ",class:-1,trait:-3,method:" + abc.script_info.get(scriptIndex).init_index); } String pkg = path.packageStr.toString(); String cls = path.className; for (int bodyIndex : bodyToIdentifier.keySet()) { String bodyName = bodyToIdentifier.get(bodyIndex); MethodBody b = abc.bodies.get(bodyIndex); List<AVM2Instruction> list = b.getCode().code; int siz = list.size(); for (int i = 0; i < siz; i++) { b.insertInstruction(i * 2, new AVM2Instruction(0, AVM2Instructions.DebugLine, new int[]{i + 1})); } for (int i = 1 /*odd, even are new debuglines*/; i < list.size(); i += 2) { if (list.get(i).definition instanceof DebugLineIns) { b.removeInstruction(i); b.removeInstruction(i - 1); //remove its new debugline too i -= 2; //for loop to work correctly } else if (list.get(i).definition instanceof DebugFileIns) { b.removeInstruction(i); b.removeInstruction(i - 1); i -= 2; } else if (list.get(i).definition instanceof DebugIns) { b.removeInstruction(i); b.removeInstruction(i - 1); i -= 2; } } String filename = "#PCODE " + bodyName + ";" + pkg.replace(".", File.separator) + ";" + cls + ".as"; b.insertInstruction(0, new AVM2Instruction(0, AVM2Instructions.DebugFile, new int[]{abc.constants.getStringId(filename, true)})); b.setModified(); } ((Tag) abc.parentTag).setModified(true); } }