diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index 8f0a74377..4cbbebf5a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -80,10 +80,14 @@ import com.jpexs.decompiler.flash.tags.ABCContainerTag; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DoInitActionTag; +import com.jpexs.decompiler.flash.tags.EnableDebugger2Tag; +import com.jpexs.decompiler.flash.tags.EnableDebuggerTag; import com.jpexs.decompiler.flash.tags.EndTag; import com.jpexs.decompiler.flash.tags.ExportAssetsTag; import com.jpexs.decompiler.flash.tags.FileAttributesTag; import com.jpexs.decompiler.flash.tags.JPEGTablesTag; +import com.jpexs.decompiler.flash.tags.MetadataTag; +import com.jpexs.decompiler.flash.tags.ProtectTag; import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; import com.jpexs.decompiler.flash.tags.SymbolClassTag; @@ -3086,4 +3090,51 @@ public final class SWF implements SWFContainerItem, Timelined { } } } + + /** + * Enables debugging. Adds tags to enable debugging and injects debugline + * and debugfile instructions to AS3 code + * + * @param injectCode Modify AS3 code with debugfile / debugline ? + */ + public void enableDebugging(boolean injectCode) { + + if (injectCode) { + List packs = getAS3Packs(); + for (ScriptPack s : packs) { + if (s.isSimple) { + s.injectDebugInfo(); + } + } + } + + int pos = 0; + + for (int i = 0; i < tags.size(); i++) { + Tag t = tags.get(i); + if (t instanceof MetadataTag) { + pos = i + 1; + } + if (t instanceof FileAttributesTag) { + pos = i + 1; + } + if (version >= 6 && (t instanceof EnableDebugger2Tag)) { + return; + } + if (version == 5 && (t instanceof EnableDebuggerTag)) { + return; + } + if (version < 5 && (t instanceof ProtectTag)) { + return; + } + } + + if (version >= 6) { + tags.add(pos, new EnableDebugger2Tag(this)); + } else if (version == 5) { + tags.add(pos, new EnableDebuggerTag(this)); + } else { + tags.add(pos, new ProtectTag(this)); + } + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ScriptPack.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ScriptPack.java index d7f5ebbcc..620d314eb 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ScriptPack.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ScriptPack.java @@ -17,11 +17,18 @@ package com.jpexs.decompiler.flash.abc; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +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.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; @@ -29,6 +36,8 @@ 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.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; @@ -40,8 +49,14 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; 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; @@ -293,4 +308,86 @@ public class ScriptPack extends AS3ClassTreeItem { 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() { + Map> bodyToPosToLine = new HashMap<>(); + try { + CachedDecompilation decompiled = SWF.getCached(this); + int line = 1; + String txt = decompiled.text; + txt = txt.replace("\r", ""); + for (int i = 0; i < txt.length(); i++) { + if (txt.charAt(i) == '\n') { + line++; + } + Highlighting cls = Highlighting.searchPos(decompiled.classHilights, i); + if (cls == null) { + continue; + } + Highlighting trt = Highlighting.searchPos(decompiled.traitHilights, i); + if (trt == null) { + continue; + } + Highlighting method = Highlighting.searchPos(decompiled.methodHilights, i); + if (method == null) { + continue; + } + Highlighting instr = Highlighting.searchPos(decompiled.instructionHilights, i); + if (instr == null) { + continue; + } + int classIndex = (int) cls.getProperties().index; + int methodIndex = (int) method.getProperties().index; + int bodyIndex = abc.findBodyIndex(methodIndex); + if (bodyIndex == -1) { + continue; + } + long instrOffset = instr.getProperties().offset; + 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 + } + int pos = -1; + try { + abc.bodies.get(bodyIndex).getCode().adr2pos(instrOffset); + } catch (ConvertException cex) { + //ignore + } + if (pos == -1) { + continue; + } + if (!bodyToPosToLine.containsKey(bodyIndex)) { + bodyToPosToLine.put(bodyIndex, new HashMap<>()); + } + bodyToPosToLine.get(bodyIndex).put(pos, line); + + } + } catch (InterruptedException ex) { + Logger.getLogger(ScriptPack.class.getName()).log(Level.SEVERE, "Cannot decompile", ex); + } + + String filename = path.toString().replace('.', '/') + ".as"; + + for (int bodyIndex : bodyToPosToLine.keySet()) { + MethodBody b = abc.bodies.get(bodyIndex); + b.insertInstruction(0, new AVM2Instruction(0, AVM2Instructions.DebugFile, new int[]{abc.constants.getStringId(filename, true)}), true); + List pos = new ArrayList<>(bodyToPosToLine.get(bodyIndex).keySet()); + Collections.sort(pos); + Collections.reverse(pos); + for (int i : pos) { + int line = bodyToPosToLine.get(bodyIndex).get(i); + b.insertInstruction(i, new AVM2Instruction(0, AVM2Instructions.DebugLine, new int[]{line})); + } + } + ((Tag) abc.parentTag).setModified(true); + } } diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index e5a6648b5..0735d5ab7 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -459,6 +459,13 @@ public class CommandLineArgumentParser { out.println(" ...WARNING: The deobfuscation result is still probably far enough to be openable by other decompilers."); } + if (filter == null || filter.equals("enabledebugging")) { + out.println(" " + (cnt++) + ") -enabledebugging [-injectas3] "); + out.println(" ...Enables debugging for and saves result to "); + out.println(" ...When optional -injectas3 parameter specified, debugfile and debugline instructions are injected into the code to match decompiled source."); + out.println(" ...WARNING: not everything works yet"); + } + printCmdLineUsageExamples(out, filter); } @@ -514,6 +521,11 @@ public class CommandLineArgumentParser { exampleFound = true; } + if (filter == null || filter.equals("enabledebugging")) { + out.println("java -jar ffdec.jar -enabledebugging -injectas3 myas3file.swf myas3file_debug.swf"); + exampleFound = true; + } + if (!exampleFound) { out.println("Sorry, no example found for command " + filter + ", Let us know in issue tracker when you need it."); } @@ -664,6 +676,8 @@ public class CommandLineArgumentParser { parseDumpAS2(args); } else if (command.equals("dumpas3")) { parseDumpAS3(args); + } else if (command.equals("enabledebugging")) { + parseEnableDebugging(args); } else if (command.equals("flashpaper2pdf")) { parseFlashPaperToPdf(selection, zoom, args); } else if (command.equals("replace")) { @@ -2768,6 +2782,46 @@ public class CommandLineArgumentParser { } } + private static void parseEnableDebugging(Stack args) { + if (args.size() < 2) { + badArguments("enabledebugging"); + } + + boolean injectas3 = false; + String file = args.pop(); + if (file.equals("-injectas3")) { + if (args.size() < 2) { + badArguments("enabledebugging"); + } + file = args.pop(); + injectas3 = true; + } + String outfile = args.pop(); + try { + System.out.print("Working..."); + FileInputStream fis = new FileInputStream(file); + SWF swf = new SWF(fis, Configuration.parallelSpeedUp.get()); + fis.close(); + swf.enableDebugging(injectas3); + FileOutputStream fos = new FileOutputStream(outfile); + swf.saveTo(fos); + fos.close(); + System.out.println("OK"); + } catch (FileNotFoundException ex) { + Logger.getLogger(CommandLineArgumentParser.class.getName()).log(Level.SEVERE, "Cannot read " + file); + System.exit(1); + } catch (IOException ex) { + Logger.getLogger(CommandLineArgumentParser.class.getName()).log(Level.SEVERE, "Reading error " + file); + System.exit(2); + } catch (InterruptedException ex) { + Logger.getLogger(CommandLineArgumentParser.class.getName()).log(Level.SEVERE, "Cancelled " + file); + System.exit(3); + } + + System.out.println("Finished"); + System.exit(0); + } + private static void parseDumpAS3(Stack args) { if (args.isEmpty()) { badArguments("dumpas3");