diff --git a/CommandLineArgumentParser.java b/CommandLineArgumentParser.java new file mode 100644 index 000000000..0cc93c8e9 --- /dev/null +++ b/CommandLineArgumentParser.java @@ -0,0 +1,4161 @@ +/* + * Copyright (C) 2010-2021 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.console; + +import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler; +import com.jpexs.decompiler.flash.ApplicationInfo; +import com.jpexs.decompiler.flash.EventListener; +import com.jpexs.decompiler.flash.IdentifiersDeobfuscation; +import com.jpexs.decompiler.flash.ReadOnlyTagList; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFBundle; +import com.jpexs.decompiler.flash.SWFCompression; +import com.jpexs.decompiler.flash.SWFSourceInfo; +import com.jpexs.decompiler.flash.SearchMode; +import com.jpexs.decompiler.flash.SwfOpenException; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.RenameType; +import com.jpexs.decompiler.flash.abc.ScriptPack; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.DeobfuscationLevel; +import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; +import com.jpexs.decompiler.flash.abc.avm2.parser.pcode.ASM3Parser; +import com.jpexs.decompiler.flash.abc.avm2.parser.pcode.MissingSymbolHandler; +import com.jpexs.decompiler.flash.abc.avm2.parser.script.ActionScript3Parser; +import com.jpexs.decompiler.flash.abc.types.Decimal; +import com.jpexs.decompiler.flash.abc.types.Float4; +import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.action.parser.ActionParseException; +import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; +import com.jpexs.decompiler.flash.action.parser.script.ActionScript2Parser; +import com.jpexs.decompiler.flash.amf.amf3.Amf3InputStream; +import com.jpexs.decompiler.flash.amf.amf3.Amf3OutputStream; +import com.jpexs.decompiler.flash.amf.amf3.Amf3Value; +import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException; +import com.jpexs.decompiler.flash.amf.amf3.Traits; +import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.ConfigurationItem; +import com.jpexs.decompiler.flash.docs.As3PCodeDocs; +import com.jpexs.decompiler.flash.exporters.BinaryDataExporter; +import com.jpexs.decompiler.flash.exporters.FontExporter; +import com.jpexs.decompiler.flash.exporters.FrameExporter; +import com.jpexs.decompiler.flash.exporters.ImageExporter; +import com.jpexs.decompiler.flash.exporters.MorphShapeExporter; +import com.jpexs.decompiler.flash.exporters.MovieExporter; +import com.jpexs.decompiler.flash.exporters.ShapeExporter; +import com.jpexs.decompiler.flash.exporters.SoundExporter; +import com.jpexs.decompiler.flash.exporters.TextExporter; +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.modes.BinaryDataExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ButtonExportMode; +import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; +import com.jpexs.decompiler.flash.exporters.modes.FrameExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode; +import com.jpexs.decompiler.flash.exporters.modes.MorphShapeExportMode; +import com.jpexs.decompiler.flash.exporters.modes.MovieExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; +import com.jpexs.decompiler.flash.exporters.modes.SoundExportMode; +import com.jpexs.decompiler.flash.exporters.modes.SpriteExportMode; +import com.jpexs.decompiler.flash.exporters.modes.TextExportMode; +import com.jpexs.decompiler.flash.exporters.script.LinkReportExporter; +import com.jpexs.decompiler.flash.exporters.settings.BinaryDataExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ButtonExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.FontExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.FrameExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.MorphShapeExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.MovieExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.SoundExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.SpriteExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.TextExportSettings; +import com.jpexs.decompiler.flash.exporters.swf.SwfToSwcExporter; +import com.jpexs.decompiler.flash.exporters.swf.SwfXmlExporter; +import com.jpexs.decompiler.flash.flexsdk.MxmlcAs3ScriptReplacer; +import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.Main; +import com.jpexs.decompiler.flash.gui.SearchInMemory; +import com.jpexs.decompiler.flash.gui.SearchInMemoryListener; +import com.jpexs.decompiler.flash.gui.SwfInMemory; +import com.jpexs.decompiler.flash.gui.helpers.CheckResources; +import com.jpexs.decompiler.flash.helpers.FileTextWriter; +import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; +import com.jpexs.decompiler.flash.importers.AS2ScriptImporter; +import com.jpexs.decompiler.flash.importers.AS3ScriptImporter; +import com.jpexs.decompiler.flash.importers.As3ScriptReplaceException; +import com.jpexs.decompiler.flash.importers.As3ScriptReplaceExceptionItem; +import com.jpexs.decompiler.flash.importers.As3ScriptReplacerFactory; +import com.jpexs.decompiler.flash.importers.As3ScriptReplacerInterface; +import com.jpexs.decompiler.flash.importers.BinaryDataImporter; +import com.jpexs.decompiler.flash.importers.FFDecAs3ScriptReplacer; +import com.jpexs.decompiler.flash.importers.FontImporter; +import com.jpexs.decompiler.flash.importers.ImageImporter; +import com.jpexs.decompiler.flash.importers.MorphShapeImporter; +import com.jpexs.decompiler.flash.importers.ShapeImporter; +import com.jpexs.decompiler.flash.importers.SwfXmlImporter; +import com.jpexs.decompiler.flash.importers.TextImporter; +import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3Importer; +import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3ParseException; +import com.jpexs.decompiler.flash.importers.svg.SvgImporter; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; +import com.jpexs.decompiler.flash.tags.DefineBitsJPEG3Tag; +import com.jpexs.decompiler.flash.tags.DefineBitsJPEG4Tag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.FileAttributesTag; +import com.jpexs.decompiler.flash.tags.JPEGTablesTag; +import com.jpexs.decompiler.flash.tags.PlaceObject4Tag; +import com.jpexs.decompiler.flash.tags.ScriptLimitsTag; +import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.MissingCharacterHandler; +import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.decompiler.flash.tags.base.TextImportErrorHandler; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.timeline.Timelined; +import com.jpexs.decompiler.flash.treeitems.SWFList; +import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.sound.SoundFormat; +import com.jpexs.decompiler.flash.xfl.FLAVersion; +import com.jpexs.decompiler.flash.xfl.XFLExportSettings; +import com.jpexs.decompiler.graph.CompilationException; +import com.jpexs.decompiler.graph.DottedChain; +import com.jpexs.helpers.CancellableWorker; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.MemoryInputStream; +import com.jpexs.helpers.Path; +import com.jpexs.helpers.ProgressListener; +import com.jpexs.helpers.stat.StatisticData; +import com.jpexs.helpers.stat.Statistics; +import com.jpexs.helpers.streams.SeekableInputStream; +import com.jpexs.helpers.utf8.Utf8Helper; +import com.jpexs.process.Process; +import com.jpexs.process.ProcessTools; +import com.sun.jna.Platform; +import com.sun.jna.platform.win32.Kernel32; +import gnu.jpdf.PDFJob; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.awt.print.PageFormat; +import java.awt.print.Paper; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Stack; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.io.FileWriter; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Scanner; + +/** + * + * @author JPEXS + */ +public class CommandLineArgumentParser { + + private static final Logger logger = Logger.getLogger(CommandLineArgumentParser.class.getName()); + + private static boolean commandLineMode = false; + + private static boolean showStat = false; + + private static String stdOut = null; + + private static String stdErr = null; + + private static final String METADATA_FORMAT_JSLIKE = "jslike"; + + private static final String METADATA_FORMAT_RAW = "raw"; + + public static boolean isCommandLineMode() { + return commandLineMode; + } + + public static void printConfigurationSettings() { + Map fields = Configuration.getConfigurationFields(); + String[] keys = new String[fields.size()]; + keys = fields.keySet().toArray(keys); + Arrays.sort(keys); + + System.out.println("Available keys[current setting]-type:"); + for (String name : keys) { + Field field = fields.get(name); + if (ConfigurationItem.isInternal(field)) { + continue; + } + + ConfigurationItem item = ConfigurationItem.getItem(field); + Object value = item.get(); + Class type = ConfigurationItem.getConfigurationFieldType(field); + String valueString = objectToString(value, type); + String typeString = objectTypeToString(type); + + if (typeString != null) { + System.out.println(name + "[" + valueString + "]-" + typeString); + } + } + } + + private static String objectTypeToString(Class type) { + if (type == String.class) { + return "string"; + } else if (type == Calendar.class) { + return "date"; + } else if ((type == Integer.class) || (type == Long.class)) { + return "integer"; + } else if ((type == Double.class) || (type == Float.class)) { + return "float"; + } else if (type == Boolean.class) { + return "bool"; + } else if (type.isEnum()) { + return "enum"; + } + + return null; + } + + private static String objectToString(Object obj, Class type) { + if (obj == null) { + return "null"; + } + + if (type == String.class) { + //return '"' + obj.toString() + '"'; + return obj.toString(); + } else if (type == Calendar.class) { + return new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(((Calendar) obj).getTime()); + } else if ((type == Integer.class) || (type == Long.class) || (type == Double.class) || (type == Float.class)) { + return obj.toString(); + } else if (type == Boolean.class) { + return ((boolean) (Boolean) obj) ? "true" : "false"; + } else if (type.isEnum()) { + return obj.toString(); + } + + return null; + } + + public static void printCmdLineUsage(String filter, boolean webHelp) { + printCmdLineUsage(System.out, webHelp, filter); + } + + public static void printCmdLineUsage(PrintStream out, boolean webHelp, String filter) { + int cnt = 1; + out.println("Commandline arguments:"); + if (filter == null) { + out.println(" " + (cnt++) + ") -help | --help | /?"); + out.println(" ...shows commandline arguments (this help)"); + out.println(" " + (cnt++) + ") [ ...]"); + out.println(" ...opens SWF file(s) with the decompiler GUI"); + } + + if (filter == null || filter.equals("proxy")) { + out.println(" " + (cnt++) + ") -proxy [-P]"); + out.println(" ...auto start proxy in the tray. Optional parameter -P specifies port for proxy. Defaults to 55555. "); + } + + if (filter == null || filter.equals("export")) { + out.println(" " + (cnt++) + ") -export "); + out.println(" ...export sources to ."); + out.println(" Exports all files from when it is a folder."); + out.println(" Values for parameter:"); + out.println(" script - Scripts (Default format: ActionScript source)"); + out.println(" image - Images (Default format: PNG/JPEG)"); + out.println(" shape - Shapes (Default format: SVG)"); + out.println(" morphshape - MorphShapes (Default format: SVG)"); + out.println(" movie - Movies (Default format: FLV without sound)"); + out.println(" font - Fonts (Default format: TTF)"); + out.println(" frame - Frames (Default format: PNG)"); + out.println(" sprite - Sprites (Default format: PNG)"); + out.println(" button - Buttons (Default format: PNG)"); + out.println(" sound - Sounds (Default format: MP3/WAV/FLV only sound)"); + out.println(" binaryData - Binary data (Default format: Raw data)"); + out.println(" text - Texts (Default format: Plain text)"); + out.println(" all - Every resource (but not FLA and XFL)"); + out.println(" fla - Everything to FLA compressed format"); + out.println(" xfl - Everything to uncompressed FLA format (XFL)"); + out.println(" You can export multiple types of items by using colon \",\""); + out.println(" DO NOT PUT space between comma (,) and next value."); + out.println(); + } + + if (filter == null || filter.equals("format")) { + out.println(" " + (cnt++) + ") -format "); + out.println(" ...sets output formats for export"); + out.println(" Values for parameter:"); + out.println(" script:as - ActionScript source"); + out.println(" script:pcode - ActionScript P-code"); + out.println(" script:pcodehex - ActionScript P-code with hex"); + out.println(" script:hex - ActionScript Hex only"); + out.println(" shape:svg - SVG format for Shapes"); + out.println(" shape:png - PNG format for Shapes"); + out.println(" shape:canvas - HTML5 Canvas format for Shapes"); + out.println(" shape:bmp - BMP format for Shapes"); + out.println(" morphshape:svg - SVG format for MorphShapes"); + out.println(" morphshape:canvas - HTML5 Canvas format for MorphShapes"); + out.println(" frame:png - PNG format for Frames"); + out.println(" frame:gif - GIF format for Frames"); + out.println(" frame:avi - AVI format for Frames"); + out.println(" frame:svg - SVG format for Frames"); + out.println(" frame:canvas - HTML5 Canvas format for Frames"); + out.println(" frame:pdf - PDF format for Frames"); + out.println(" frame:bmp - BMP format for Frames"); + out.println(" sprite:png - PNG format for Sprites"); + out.println(" sprite:gif - GIF format for Sprites"); + out.println(" sprite:avi - AVI format for Sprites"); + out.println(" sprite:svg - SVG format for Sprites"); + out.println(" sprite:canvas - HTML5 Canvas format for Sprites"); + out.println(" sprite:pdf - PDF format for Sprites"); + out.println(" sprite:bmp - BMP format for Sprites"); + out.println(" button:png - PNG format for Buttons"); + out.println(" button:svg - SVG format for Buttons"); + out.println(" button:bmp - BMP format for Buttons"); + out.println(" image:png_gif_jpeg - PNG/GIF/JPEG format for Images"); + out.println(" image:png - PNG format for Images"); + out.println(" image:jpeg - JPEG format for Images"); + out.println(" image:bmp - BMP format for Images"); + out.println(" text:plain - Plain text format for Texts"); + out.println(" text:formatted - Formatted text format for Texts"); + out.println(" text:svg - SVG format for Texts"); + out.println(" sound:mp3_wav_flv - MP3/WAV/FLV format for Sounds"); + out.println(" sound:mp3_wav - MP3/WAV format for Sounds"); + out.println(" sound:wav - WAV format for Sounds"); + out.println(" sound:flv - FLV format for Sounds"); + out.println(" font:ttf - TTF format for Fonts"); + out.println(" font:woff - WOFF format for Fonts"); + out.println(" fla: or xfl: - Specify FLA format version"); + out.println(" - values for : cs5,cs5.5,cs6,cc"); + out.println(" You can set multiple formats at once using comma (,)"); + out.println(" DO NOT PUT space between comma (,) and next value."); + out.println(" The prefix with colon (:) is neccessary."); + } + + if (filter == null || filter.equals("cli")) { + out.println(" " + (cnt++) + ") -cli"); + out.println(" ...Command line mode. Parses the SWFs without opening the GUI"); + } + + if (filter == null || filter.equals("select")) { + out.println(" " + (cnt++) + ") -select "); + out.println(" ...selects frames/pages for export"); + out.println(" Example formats:"); + out.println(" 1-5"); + out.println(" 2,3"); + out.println(" 2-5,7,9-"); + out.println(" DO NOT PUT space between comma (,) and next ramge."); + out.println(" " + (cnt++) + ") -selectid "); + out.println(" ...selects characters for export by character id"); + out.println(" format is same as in -select"); + } + + if (filter == null || filter.equals("selectclass")) { + out.println(" " + (cnt++) + ") -selectclass "); + out.println(" ...selects scripts to export by class name (ActionScript 3 ONLY)"); + out.println(" format:"); + out.println(" com.example.MyClass"); + out.println(" com.example.+ (all classes in package \"com.example\")"); + out.println(" com.++,net.company.MyClass (all classes in package \"com\" and all subpackages, class net.company.MyClass)"); + out.println(" DO NOT PUT space between comma (,) and next class."); + } + + if (filter == null || filter.equals("dumpswf")) { + out.println(" " + (cnt++) + ") -dumpSWF "); + out.println(" ...dumps list of SWF tags to console"); + } + + if (filter == null || filter.equals("dumpas2")) { + out.println(" " + (cnt++) + ") -dumpAS2 "); + out.println(" ...dumps list of AS1/2 scripts to console"); + } + + if (filter == null || filter.equals("dumpas3")) { + out.println(" " + (cnt++) + ") -dumpAS3 "); + out.println(" ...dumps list of AS3 scripts to console"); + } + + if (filter == null || filter.equals("compress")) { + out.println(" " + (cnt++) + ") -compress [(zlib|lzma)]"); + out.println(" ...Compress SWF and save it to . If is already compressed, it will be re-compressed. Default compression method is ZLIB"); + } + + if (filter == null || filter.equals("decompress")) { + out.println(" " + (cnt++) + ") -decompress "); + out.println(" ...Decompress and save it to "); + } + + if (filter == null || filter.equals("swf2xml")) { + out.println(" " + (cnt++) + ") -swf2xml "); + out.println(" ...Converts the SWF to XML file"); + } + + if (filter == null || filter.equals("xml2swf")) { + out.println(" " + (cnt++) + ") -xml2swf "); + out.println(" ...Converts the XML to SWF file"); + } + + if (filter == null || filter.equals("extract")) { + out.println(" " + (cnt++) + ") -extract [-o |] [nocheck] [(all|biggest|smallest|first|last)]"); + out.println(" ...Extracts SWF files from ZIP or other binary files"); + out.println(" ...-o parameter should contain a file path when \"biggest\" or \"first\" parameter is specified"); + out.println(" ...-o parameter should contain a folder path when no extaction mode or \"all\" parameter is specified"); + } + + if (filter == null || filter.equals("memorysearch")) { + out.println(" " + (cnt++) + ") -memorySearch (|) (|)..."); + out.println(" ...Search SWF files in the memory"); + } + + if (filter == null || filter.equals("renameinvalididentifiers")) { + out.println(" " + (cnt++) + ") -renameInvalidIdentifiers (typeNumber|randomWord) "); + out.println(" ...Renames the invalid identifiers in and save it to "); + } + + if (filter == null || filter.equals("config")) { + out.println(" " + (cnt++) + ") -config key=value[,key2=value2][,key3=value3...] [other parameters]"); + out.print(" ...Sets configuration values. Use -listconfigs command to list the available configuration settings."); + out.println(); + out.println(" Values are boolean, you can use 0/1, true/false, on/off or yes/no."); + out.println(" If no other parameters passed, configuration is saved. Otherwise it is used only once."); + out.println(" DO NOT PUT space between comma (,) and next value."); + } + + if (filter == null || filter.equals("onerror")) { + out.println(" " + (cnt++) + ") -onerror (abort|retryN|ignore)"); + out.println(" ...error handling mode. \"abort\" stops the exporting, \"retry\" tries the exporting N times, \"ignore\" ignores the current file"); + } + + if (filter == null || filter.equals("timeout")) { + out.println(" " + (cnt++) + ") -timeout "); + out.println(" ...decompilation timeout for a single method in AS3 or single action in AS1/2 in seconds"); + } + + if (filter == null || filter.equals("exporttimeout")) { + out.println(" " + (cnt++) + ") -exportTimeout "); + out.println(" ...total export timeout in seconds"); + } + + if (filter == null || filter.equals("exportfiletimeout")) { + out.println(" " + (cnt++) + ") -exportFileTimeout "); + out.println(" ...export timeout for a single AS3 class in seconds"); + } + + if (filter == null || filter.equals("stat")) { + out.println(" " + (cnt++) + ") -stat"); + out.println(" ...show export performance statistics"); + } + + if (filter == null || filter.equals("flashpaper2pdf")) { + out.println(" " + (cnt++) + ") -flashpaper2pdf "); + out.println(" ...converts FlashPaper SWF file to PDF . Use -zoom parameter to specify image quality."); + } + + if (filter == null || filter.equals("zoom")) { + out.println(" " + (cnt++) + ") -zoom "); + out.println(" ...apply zoom during export"); + } + + if (filter == null || filter.equals("replace")) { + out.println(" " + (cnt++) + ") -replace (|) [nofill] ([][]) [(|) [nofill] ([][])]..."); + out.println(" ...replaces the data of the specified BinaryData, Image, Shape, Text, DefineSound tag or Script"); + out.println(" ...nofill parameter can be specified only for shape replace"); + out.println(" ... parameter can be specified for Image and Shape tags"); + out.println(" ...valid formats: lossless, lossless2, jpeg2, jpeg3, jpeg4"); + out.println(" ... parameter should be specified if and only if the imported entity is an AS3 P-Code"); + } + + if (filter == null || filter.equals("replacealpha")) { + out.println(" " + (cnt++) + ") -replaceAlpha [ ]..."); + out.println(" ...replaces the alpha channel of the specified JPEG3 or JPEG4 tag"); + } + + if (filter == null || filter.equals("replacecharacter")) { + out.println(" " + (cnt++) + ") -replaceCharacter [ ]..."); + out.println(" ...replaces a character tag with another chatacter tag from the same SWF"); + } + + if (filter == null || filter.equals("replacecharacterid")) { + out.println(" " + (cnt++) + ") -replaceCharacterId ,,,... or"); + out.println(" " + (cnt++) + ") -replaceCharacterId (pack|sort)"); + out.println(" ...replaces the character id with "); + out.println(" ...pack: removes the spaces between the character ids (1,4,3 => 1,3,2)"); + out.println(" ...sort: assigns increasing IDs to the chatacter tags + pack (1,4,3 => 1,2,3)"); + out.println(" DO NOT PUT space between comma (,) and next value."); + } + + if (filter == null || filter.equals("remove")) { + out.println(" " + (cnt++) + ") -remove []..."); + out.println(" ...removes a tag from the SWF"); + } + + if (filter == null || filter.equals("removecharacter")) { + out.println(" " + (cnt++) + ") -removeCharacter[WithDependencies] []..."); + out.println(" ...removes a character tag from the SWF"); + } + + if (filter == null || filter.equals("importscript")) { + out.println(" " + (cnt++) + ") -importScript "); + out.println(" ...imports scripts to and saves the result to "); + } + + if (filter == null || filter.equals("deobfuscate")) { + out.println(" " + (cnt++) + ") -deobfuscate "); + out.println(" ...Deobfuscates AS3 P-code in and saves result to "); + out.println(" ... can be one of: controlflow/3/max, traps/2, deadcode/1"); + 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|-generateswd] [-pcode] "); + out.println(" ...Enables debugging for and saves result to "); + out.println(" ...-injectas3 (optional) causes debugfile and debugline instructions to be injected into the code to match decompiled/pcode source."); + out.println(" ...-generateswd (optional) parameter creates SWD file needed for AS1/2 debugging. for , is generated"); + out.println(" ...-pcode (optional) parameter specified after -injectas3 or -generateswd causes lines to be handled as lines in P-code => All P-code lines are injected, etc."); + out.println(" ...WARNING: Injected/SWD script filenames may be different than from standard compiler"); + } + + if (filter == null || filter.equals("custom")) { + out.println(" " + (cnt++) + ") -custom []..."); + out.println(" ...Forwards all parameters after the -custom parameter to the plugins"); + } + + if (filter == null || filter.equals("doc")) { + out.println(" " + (cnt++) + ") -doc -type [-out ] [-format ] [-locale ]"); + out.println(" ...Generate documentation"); + out.println(" ...-type Selects documentation type"); + out.println(" ... can be currently only: as3.pcode.instructions for list of ActionScript3 AVM2 instructions"); + out.println(" ...-out (optional) If specified, output is written to instead of stdout"); + out.println(" ...-format (optional, html is default) Selects output format"); + out.println(" ... is currently only html"); + out.println(" ...-locale (optional) Override default locale"); + out.println(" ... is localization identifier, en for english for example"); + out.println(" ... is currently only html"); + } + + if (filter == null || filter.equals("getinstancemetadata")) { + out.println(" " + (cnt++) + ") -getInstanceMetadata -instance [-outputFormat ] [-key ] [-datafile ] "); + out.println(" ...reads instance metadata"); + out.println(" ...-instance : name of instance to fetch metadata from"); + out.println(" ...-outputFormat (optional): format of output - one of: jslike|raw. Default is jslike."); + out.println(" ...- key (optional): name of subkey to display. When present, only value from subkey is shown, whole object value otherwise."); + out.println(" ...-datafile (optional): File to write the data to. If ommited, stdout is used."); + out.println(" ...: SWF file to read metadata from"); + } + + if (filter == null || filter.equals("setinstancemetadata")) { + out.println(" " + (cnt++) + ") -setInstanceMetadata -instance [-inputFormat ] [-key ] [-value | -datafile ] [-outfile ] "); + out.println(" ...adds metadata to instance"); + out.println(" ...-instance : name of instance to replace data in"); + out.println(" ...-inputFormat : format of input data - one of: jslike|raw. Default is jslike."); + out.println(" ...- key (optional): name of subkey to use. When present, the value is set as object property with the name."); + out.println(" Otherwise the value is set directly to the instance without any subkeys."); + out.println(" ...-value (optional): value to set."); + out.println(" ...-datafile (optional): value to set from file."); + out.println(" ...If no -value or -infile parameter present, the value to set is taken from stdin."); + out.println(" ...-outfile (optional): Where to save resulting file. If ommited, original SWF file is overwritten."); + out.println(" ...: SWF file to search instance in"); + } + + if (filter == null || filter.equals("removeinstancemetadata")) { + out.println(" " + (cnt++) + ") -removeInstanceMetadata -instance [-key ] [-outfile ] "); + out.println(" ...removes metadata from instance"); + out.println(" ...-instance : name of instance to remove data from"); + out.println(" ...- key (optional): name of subkey to remove. When present, only the value from subkey of the AMF object is removed."); + out.println(" Otherwise all metadata are removed from the instance."); + out.println(" ...-outfile (optional): Where to save resulting file. If ommited, original SWF file is overwritten."); + out.println(" ...: SWF file to search instance in"); + } + + if (filter == null || filter.equals("linkreport")) { + out.println(" " + (cnt++) + ") -linkReport [-outfile ] "); + out.println(" ...generates linker report for the swffile"); + out.println(" ...-outfile (optional): Saves XML report to . When ommited, the report is printed to stdout."); + out.println(" ...: SWF file to search instance in"); + } + + if (filter == null || filter.equals("swf2swc")) { + out.println(" " + (cnt++) + ") -swf2swc "); + out.println(" ...generates SWC file from SWF"); + out.println(" ...: Where to save SWC file"); + out.println(" ...: Input SWF file"); + } + + if (filter == null || filter.equals("abcmerge")) { + out.println(" " + (cnt++) + ") -abcmerge "); + out.println(" ...merge all ABC tags in SWF file to one"); + out.println(" ...: Where to save merged file"); + out.println(" ...: Input SWF file"); + } + + printCmdLineUsageExamples(out, filter); + } + + private static void printCmdLineUsageExamples(PrintStream out, String filter) { + out.println(); + out.println("Examples:"); + + final String PREFIX = "java -jar ffdec.jar "; + + boolean exampleFound = false; + if (filter == null) { + out.println(PREFIX + "myfile.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("proxy")) { + out.println(PREFIX + "-proxy"); + out.println(PREFIX + "-proxy -P1234"); + exampleFound = true; + } + + if (filter == null || filter.equals("export") || filter.equals("format") || filter.equals("selectclass") || filter.equals("onerror")) { + out.println(PREFIX + "-export script \"C:\\decompiled\" myfile.swf"); + out.println(PREFIX + "-selectclass com.example.MyClass,com.example.SecondClass -export script \"C:\\decompiled\" myfile.swf"); + out.println(PREFIX + "-format script:pcode -export script \"C:\\decompiled\" myfile.swf"); + out.println(PREFIX + "-format script:pcode,text:plain -export script,text,image \"C:\\decompiled\" myfile.swf"); + out.println(PREFIX + "-format fla:cs5.5 -export fla \"C:\\sources\\myfile.fla\" myfile.swf"); + out.println(PREFIX + "-onerror ignore -export script \"C:\\decompiled\" myfile.swf"); + out.println(PREFIX + "-onerror retry 5 -export script \"C:\\decompiled\" myfile.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("cli")) { + out.println(PREFIX + "-cli myfile.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("dumpswf")) { + out.println(PREFIX + "-dumpSWF myfile.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("compress")) { + out.println(PREFIX + "-compress myfile.swf myfilecomp.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("decompress")) { + out.println(PREFIX + "-decompress myfile.swf myfiledec.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("config")) { + out.println(PREFIX + "-config autoDeobfuscate=1,parallelSpeedUp=0 -export script \"C:\\decompiled\" myfile.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("deobfuscate")) { + out.println(PREFIX + "-deobfuscate max myas3file_secure.swf myas3file.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("enabledebugging")) { + out.println(PREFIX + "-enabledebugging -injectas3 myas3file.swf myas3file_debug.swf"); + out.println(PREFIX + "-enabledebugging -generateswd myas2file.swf myas2file_debug.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("doc")) { + out.println(PREFIX + "-doc -type as3.pcode.instructions -format html"); + out.println(PREFIX + "-doc -type as3.pcode.instructions -format html -locale en -out as3_docs_en.html"); + exampleFound = true; + } + + if (filter == null || filter.equals("getinstancemetadata")) { + out.println(PREFIX + "-getInstanceMetadata -instance myobj -key keyone myfile.swf"); + out.println(PREFIX + "-getInstanceMetadata -instance myobj2 -outputFormat raw -outfile out.amf myfile.swf"); + exampleFound = true; + } + if (filter == null || filter.equals("setinstancemetadata")) { + out.println(PREFIX + "-setInstanceMetadata -instance myobj -key mykey -value 1234 myfile.swf"); + out.println(PREFIX + "-setInstanceMetadata -instance myobj -key my -inputFormat raw -datafile value.amf -outfile modified.swf myfile.swf"); + exampleFound = true; + } + + if (filter == null || filter.equals("removeinstancemetadata")) { + out.println(PREFIX + "-removeInstanceMetadata -instance myobj -key mykey -outfile result.swf myfile.swf"); + out.println(PREFIX + "-removeInstanceMetadata -instance myobj myfile.swf"); + exampleFound = true; + } + + if (!exampleFound) { + out.println("Sorry, no example found for command " + filter + ", Let us know in issue tracker when you need it."); + } + + out.println(); + out.println("Instead of \"java -jar ffdec.jar\" you can use ffdec.bat on Windows, ffdec.sh on Linux/MacOs"); + } + + /** + * Parses the console arguments + * + * @param arguments Arguments + * @return paths to the file which should be opened or null + * @throws java.io.IOException On error + */ + public static String[] parseArguments(String[] arguments) throws IOException { + Level traceLevel = Level.WARNING; + Stack args = new Stack<>(); + for (int i = arguments.length - 1; i >= 0; i--) { + String arg = arguments[i]; + if (arg.length() > 0) { + args.add(arg); + } + } + + AbortRetryIgnoreHandler handler = null; + Map format = new HashMap<>(); + double zoom = 1; + boolean cliMode = false; + Selection selection = new Selection(); + Selection selectionIds = new Selection(); + List selectionClasses = null; + String nextParam = null, nextParamOriginal = null; + OUTER: + while (true) { + nextParamOriginal = args.pop(); + if (nextParamOriginal != null) { + nextParam = nextParamOriginal.toLowerCase(Locale.ENGLISH); + } + if (nextParam == null) { + nextParam = ""; + } + switch (nextParam) { + case "-cli": + cliMode = true; + break; + case "-selectid": + selectionIds = parseSelect(args); + break; + case "-select": + selection = parseSelect(args); + break; + case "-selectclass": + selectionClasses = parseSelectClass(args); + break; + case "-zoom": + zoom = parseZoom(args); + break; + case "-format": + format = parseFormat(args); + break; + case "-config": + parseConfig(args); + if (args.isEmpty()) { + Configuration.saveConfig(); + System.out.println("Configuration saved"); + return null; + } + break; + case "-onerror": + handler = parseOnError(args); + break; + case "-timeout": + parseTimeout(args); + break; + case "-exporttimeout": + parseExportTimeout(args); + break; + case "-exportfiletimeout": + parseExportFileTimeout(args); + break; + case "-stat": + parseStat(args); + break; + case "-info": + parseInfo(args); + break; + case "-stdout": + parseStdOut(args); + break; + case "-stderr": + parseStdErr(args); + break; + case "-affinity": + parseAffinity(args); + break; + case "-priority": + parsePriority(args); + break; + case "-verbose": + traceLevel = Level.FINE; + break; + case "-debug": + for (int i = 0; i < arguments.length; i++) { + System.out.println(i + ".:" + arguments[i]); + } + Configuration._debugMode.set(true); + break; + default: + break OUTER; + } + if (args.isEmpty()) { + return null; + } + } + + String command = ""; + if (nextParam == null) { + nextParam = ""; + } + if (nextParam.startsWith("-")) { + command = nextParam.substring(1); + } + + if (command.equals("abcmerge")) { + parseAbcMerge(args); + } else if (command.equals("swf2swc")) { + parseSwf2Swc(args); + } else if (command.equals("linkreport")) { + parseLinkReport(selectionClasses, args); + } else if (command.equals("getinstancemetadata")) { + parseGetInstanceMetadata(args); + } else if (command.equals("setinstancemetadata")) { + parseSetInstanceMetadata(args); + } else if (command.equals("removeinstancemetadata")) { + parseRemoveInstanceMetadata(args); + } else if (command.equals("removefromcontextmenu")) { + if (!args.isEmpty()) { + badArguments(command); + } + ContextMenuTools.addToContextMenu(false, true); + System.exit(0); + } else if (command.equals("addtocontextmenu")) { + if (!args.isEmpty()) { + badArguments(command); + } + ContextMenuTools.addToContextMenu(true, true); + System.exit(0); + } else if (command.equals("proxy")) { + parseProxy(args); + } else if (command.equals("export")) { + parseExport(selectionClasses, selection, selectionIds, args, handler, traceLevel, format, zoom); + } else if (command.equals("compress")) { + parseCompress(args); + } else if (command.equals("decompress")) { + parseDecompress(args); + } else if (command.equals("swf2xml")) { + parseSwf2Xml(args); + } else if (command.equals("xml2swf")) { + parseXml2Swf(args); + } else if (command.equals("extract")) { + parseExtract(args); + } else if (command.equals("memorysearch")) { + parseMemorySearch(args); + } else if (command.equals("deobfuscate")) { + parseDeobfuscate(args); + } else if (command.equals("renameinvalididentifiers")) { + parseRenameInvalidIdentifiers(args); + } else if (command.equals("dumpswf")) { + parseDumpSwf(args); + } else if (command.equals("dumpas2")) { + 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")) { + parseReplace(args); + } else if (command.equals("replacealpha")) { + parseReplaceAlpha(args); + } else if (command.equals("replacecharacter")) { + parseReplaceCharacter(args); + } else if (command.equals("replacecharacterid")) { + parseReplaceCharacterId(args); + } else if (command.equals("convert")) { + parseConvert(args); + } else if (command.equals("remove")) { + parseRemove(args); + } else if (command.equals("removecharacter")) { + parseRemoveCharacter(args, false); + } else if (command.equals("removecharacterwithdependencies")) { + parseRemoveCharacter(args, true); + } else if (command.equals("doc")) { + parseDoc(args); + } else if (command.equals("importscript")) { + parseImportScript(args); + } else if (command.equals("as3compiler")) { + ActionScript3Parser.compile(null /*?*/, args.pop(), args.pop(), 0, 0); + } else if (nextParam.equals("--debugtool")) { + parseDebugTool(args); + } else if (nextParam.equals("--compareresources")) { + parseCompareResources(args); + } else if (nextParam.equals("--resourcedates")) { + parseResourceDates(args); + } else if (nextParam.equals("-listconfigs")) { + printHeader(); + printConfigurationSettings(); + System.exit(0); + } else if (nextParam.equals("-help") || nextParam.equals("--help") || nextParam.equals("/?") || nextParam.equals("\\_") /* /? translates as this on windows */) { + printHeader(); + printCmdLineUsage(null, false); + System.exit(0); + } else if (nextParam.equals("--webhelp")) { //for generating commandline usage on webpages + ByteArrayOutputStream whbaos = new ByteArrayOutputStream(); + printCmdLineUsage(new PrintStream(whbaos, true), true, null); + String wh = new String(whbaos.toByteArray()); + wh = wh.replace("<", "<").replace(">", ">"); + System.out.println(wh); + } else { + args.push(nextParamOriginal); // file names should be the original one + List fileNames = new ArrayList<>(); + boolean allParamIsAFile = true; + while (!args.isEmpty()) { + String arg = args.pop(); + if (arg.equals("-custom")) { + parseCustom(args); + break; + } + + fileNames.add(arg); + File file = new File(arg); + if (!file.exists() || !file.isFile()) { + allParamIsAFile = false; + } + } + + if (allParamIsAFile) { + String[] fileNamesArray = fileNames.toArray(new String[fileNames.size()]); + if (cliMode) { + loadFiles(fileNamesArray); + return null; + } else { + return fileNamesArray; + } + } else { + badArguments(); + } + } + + return null; + } + + public static void printHeader() { + System.out.println(ApplicationInfo.applicationVerName); + for (int i = 0; i < ApplicationInfo.applicationVerName.length(); i++) { + System.out.print("-"); + } + System.out.println(); + } + + public static void badArguments() { + badArguments(null); + } + + public static void badArguments(String command) { + System.err.println("Error: Bad Commandline Arguments!"); + printCmdLineUsage(command, false); + System.exit(1); + } + + @SuppressWarnings("unchecked") + private static void setConfigurations(String cfgStr) { + String[] cfgs; + if (cfgStr.contains(",")) { + cfgs = cfgStr.split(","); + } else { + cfgs = new String[]{cfgStr}; + } + + Map fields = Configuration.getConfigurationFields(true); + for (String c : cfgs) { + String[] cp = c.split("="); + if (cp.length == 1) { + cp = new String[]{cp[0], "1"}; + } + + Field field = fields.get(cp[0].toLowerCase(Locale.ENGLISH)); + ConfigurationItem item = ConfigurationItem.getItem(field); + String stringValue = cp[1]; + Class type = ConfigurationItem.getConfigurationFieldType(field); + + if (type == String.class) { + System.out.println("Config " + item.getName() + " set to " + stringValue); + ((ConfigurationItem) item).set(stringValue); + } else if (type == Calendar.class) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + Date dateValue; + try { + dateValue = dateFormat.parse(stringValue); + GregorianCalendar calendarValue = new GregorianCalendar(); + calendarValue.setTime(dateValue); + ((ConfigurationItem) item).set(calendarValue); + } catch (ParseException ex) { + Logger.getLogger(CommandLineArgumentParser.class.getName()).log(Level.SEVERE, null, ex); + } + } else if (type == Integer.class) { + int intValue = Integer.parseInt(stringValue); + ((ConfigurationItem) item).set(intValue); + } else if ((type == Integer.class) || (type == Long.class)) { + long longValue = Long.parseLong(stringValue); + ((ConfigurationItem) item).set(longValue); + } else if (type == Double.class) { + double doubleValue = Double.parseDouble(stringValue); + ((ConfigurationItem) item).set(doubleValue); + } else if (type == Float.class) { + float floatValue = Float.parseFloat(stringValue); + ((ConfigurationItem) item).set(floatValue); + } else if (type == Boolean.class) { + Boolean boolValue = parseBooleanConfigValue(stringValue); + if (boolValue != null) { + System.out.println("Config " + item.getName() + " set to " + boolValue); + ((ConfigurationItem) item).set(boolValue); + } else { + System.out.println("Invalid config value for " + item.getName() + ": " + stringValue); + } + } else if (type.isEnum()) { + Enum enumValue = Enum.valueOf((Class) type, stringValue); + ConfigurationItem uncheckedItem = (ConfigurationItem) item; + uncheckedItem.set(enumValue); + } + } + } + + private static Boolean parseBooleanConfigValue(String value) { + if (value == null) { + return null; + } + + Boolean bValue = null; + value = value.toLowerCase(Locale.ENGLISH); + if (value.equals("0") || value.equals("false") || value.equals("no") || value.equals("off")) { + bValue = false; + } + + if (value.equals("1") || value.equals("true") || value.equals("yes") || value.equals("on")) { + bValue = true; + } + + return bValue; + } + + private static void parseConfig(Stack args) { + if (args.isEmpty()) { + System.err.println("Config values expected"); + badArguments("config"); + } + + setConfigurations(args.pop()); + } + + private static void parseAbcMerge(Stack args) { + if (args.size() < 2) { + badArguments("abcmerge"); + } + final File outFile = new File(args.pop()); + final File swfFile = new File(args.pop()); + processModifySWF(swfFile, outFile, null, (SWF swf, OutputStream stdout) -> { + List abcList = swf.getAbcList(); + if (!abcList.isEmpty() && abcList.size() > 1) { + ABC firstAbc = abcList.get(0).getABC(); + for (int i = 1; i < abcList.size(); i++) { + firstAbc.mergeABC(abcList.get(i).getABC()); + } + for (int i = 1; i < abcList.size(); i++) { + swf.removeTag((Tag) abcList.get(i)); + } + } + }); + + } + + private static void parseSwf2Swc(Stack args) { + if (args.size() < 2) { + badArguments("swf2swc"); + } + final File outFile = new File(args.pop()); + final File swfFile = new File(args.pop()); + processReadSWF(swfFile, null, (SWF swf, OutputStream stdout) -> { + SwfToSwcExporter exporter = new SwfToSwcExporter(); + try { + exporter.exportSwf(swf, outFile, false); + } catch (IOException | InterruptedException ex) { + Logger.getLogger(CommandLineArgumentParser.class.getName()).log(Level.SEVERE, null, ex); + } + }); + } + + private static void parseLinkReport(List selectionClasses, Stack args) { + if (args.isEmpty()) { + badArguments("linkreport"); + } + File stdOutFile = null; + File swfFile = null; + while (!args.isEmpty()) { + String paramName = args.pop().toLowerCase(Locale.ENGLISH); + switch (paramName) { + case "-outfile": + if (args.empty()) { + System.err.println("Missing output file"); + badArguments("linkreport"); + } + stdOutFile = new File(args.pop()); + break; + default: + if (!args.isEmpty()) { + badArguments("linkreport"); + } + swfFile = new File(paramName); + } + } + if (swfFile == null) { + System.err.println("No SWF file specified"); + badArguments("getinstancemetadata"); + } + processReadSWF(swfFile, stdOutFile, (SWF swf, OutputStream stdout) -> { + + LinkReportExporter lre = new LinkReportExporter(); + + List reportPacks; + try { + reportPacks = selectionClasses != null ? swf.getScriptPacksByClassNames(selectionClasses) : swf.getAS3Packs(); + } catch (Exception ex) { + System.err.println("Error while getting packs"); + System.exit(1); + return; + } + try { + String reportStr = lre.generateReport(swf, reportPacks, null); + stdout.write(reportStr.getBytes("UTF-8")); + } catch (InterruptedException ex) { + System.err.println("Report generation interrupted"); + } + }); + } + + private static void parseGetInstanceMetadata(Stack args) { + if (args.size() < 3) { + badArguments("getinstancemetadata"); + } + Set processedParams = new HashSet<>(); + String format = METADATA_FORMAT_JSLIKE; + String key = null; + String instance = null; + File stdOutFile = null; + File swfFile = null; + + while (!args.empty()) { + String paramName = args.pop().toLowerCase(Locale.ENGLISH); + if (processedParams.contains(paramName)) { + System.err.println("Parameter " + paramName + " can appear only once."); + } + switch (paramName) { + case "-instance": + if (args.isEmpty()) { + System.err.println("Missing instance name"); + badArguments("getinstancemetadata"); + } + instance = args.pop(); + break; + case "-outputformat": + if (args.empty()) { + System.err.println("Missing format value"); + badArguments("getinstancemetadata"); + } + format = args.pop(); + if (!Arrays.asList(METADATA_FORMAT_RAW, METADATA_FORMAT_JSLIKE).contains(format)) { + System.err.println("Invalid output format"); + badArguments("getinstancemetadata"); + } + break; + case "-key": + if (args.empty()) { + System.err.println("Missing key value"); + badArguments("getinstancemetadata"); + } + + key = args.pop(); + break; + case "-datafile": + if (args.empty()) { + System.err.println("Missing datafile file"); + badArguments("getinstancemetadata"); + } + stdOutFile = new File(args.pop()); + break; + default: + if (!args.isEmpty()) { + badArguments("getinstancemetadata"); + } + swfFile = new File(paramName); + paramName = null; + } + if (paramName != null) { + processedParams.add(paramName); + } + } + if (instance == null) { + System.err.println("No instance specified"); + badArguments("getinstancemetadata"); + } + if (swfFile == null) { + System.err.println("No SWF file specified"); + badArguments("getinstancemetadata"); + } + + final String fInstance = instance; + final String fKey = key; + final String fFormat = format; + + processReadSWF(swfFile, stdOutFile, new SwfAction() { + @Override + public void swfAction(SWF swf, OutputStream stdout) throws IOException { + if (!processTimelined(swf, stdout)) { + System.err.println("No instance with name " + fInstance + " found"); + System.exit(0); + } + } + + private boolean processTimelined(Timelined tim, OutputStream stdout) throws IOException { + ReadOnlyTagList rtl = tim.getTags(); + for (int i = 0; i < rtl.size(); i++) { + Tag t = rtl.get(i); + if (t instanceof Timelined) { + if (processTimelined((Timelined) t, stdout)) { + return true; + } + } + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t; + String instanceName = pt.getInstanceName(); + if (fInstance.equals(instanceName)) { + Amf3Value oldValue = pt.getAmfData(); + if (oldValue == null) { + System.err.println("No metadata for instance " + instanceName + " found"); + System.exit(1); //TODO? Different exit code + } + Object actualValue = oldValue.getValue(); + + Object displayVal = actualValue; + if (fKey != null) { + if (actualValue instanceof ObjectType) { + ObjectType ot = (ObjectType) actualValue; + if (ot.containsDynamicMember(fKey)) { + displayVal = ot.getDynamicMember(fKey); + } else { + System.err.println("No value with key " + fKey + " exists"); + System.err.println("Available keys: " + String.join(",", ot.dynamicMembersKeySet())); + System.exit(1); + } + } else { + System.err.println("Metadata present, but not as Object type, cannot get key " + fKey); + System.exit(1); + } + } + + switch (fFormat) { + case METADATA_FORMAT_JSLIKE: + stdout.write(Utf8Helper.getBytes(Amf3Exporter.amfToString(displayVal, " ", System.lineSeparator()) + System.lineSeparator())); + break; + case METADATA_FORMAT_RAW: + Amf3OutputStream aos = new Amf3OutputStream(stdout); + try { + aos.writeValue(displayVal); + } catch (NoSerializerExistsException ex) { + //should not happen + } + break; + } + return true; + } + } + } + return false; + } + }); + System.exit(0); + } + + private static void parseSetInstanceMetadata(Stack args) { + if (args.size() < 3) { + badArguments("setinstancemetadata"); + } + Set processedParams = new HashSet<>(); + String format = METADATA_FORMAT_JSLIKE; + String key = null; + String instance = null; + File outFile = null; + File swfFile = null; + String value = null; + File valueFile = null; + + while (!args.empty()) { + String paramName = args.pop().toLowerCase(Locale.ENGLISH); + if (processedParams.contains(paramName)) { + System.err.println("Parameter " + paramName + " can appear only once."); + } + switch (paramName) { + case "-instance": + if (args.isEmpty()) { + System.err.println("Missing instance name"); + badArguments("setinstancemetadata"); + } + instance = args.pop(); + break; + case "-inputformat": + if (args.empty()) { + System.err.println("Missing format value"); + badArguments("setinstancemetadata"); + } + format = args.pop(); + if (!Arrays.asList(METADATA_FORMAT_RAW, METADATA_FORMAT_JSLIKE).contains(format)) { + System.err.println("Invalid output format"); + badArguments("setinstancemetadata"); + } + break; + case "-key": + if (args.empty()) { + System.err.println("Missing key value"); + badArguments("setinstancemetadata"); + } + + key = args.pop(); + break; + case "-value": + if (args.empty()) { + System.err.println("Missing value"); + badArguments("setinstancemetadata"); + } + + value = args.pop(); + break; + case "-outfile": + if (args.empty()) { + System.err.println("Missing outFile"); + badArguments("setinstancemetadata"); + } + outFile = new File(args.pop()); + break; + + case "-datafile": + if (args.empty()) { + System.err.println("Missing datafile file"); + badArguments("setinstancemetadata"); + } + valueFile = new File(args.pop()); + break; + default: + if (!args.isEmpty()) { + badArguments("setinstancemetadata"); + } + swfFile = new File(paramName); + paramName = null; + } + if (paramName != null) { + processedParams.add(paramName); + } + } + if (instance == null) { + System.err.println("No instance specified"); + badArguments("getinstancemetadata"); + } + if (swfFile == null) { + System.err.println("No SWF file specified"); + badArguments("getinstancemetadata"); + } + if (outFile == null) { + outFile = swfFile; + } + + byte[] valueBytes = new byte[]{}; + if (valueFile != null) { + try { + valueBytes = Helper.readFileEx(valueFile.getAbsolutePath()); + } catch (IOException ex) { + System.err.println("Cannot read value: " + ex.getMessage()); + System.exit(1); + return; + } + } else if (value != null) { + valueBytes = Utf8Helper.getBytes(value); + } + + if (valueBytes.length == 0) { + valueBytes = Helper.readStream(System.in); + } + + if (valueBytes.length < 1) { + System.err.println("No value to set specified"); + System.exit(1); + } + + Object amfValue = null; + try { + switch (format) { + case METADATA_FORMAT_JSLIKE: + Amf3Importer importer = new Amf3Importer(); + amfValue = importer.stringToAmf(value); + break; + case METADATA_FORMAT_RAW: + Amf3InputStream ais = new Amf3InputStream(new MemoryInputStream(valueBytes)); + amfValue = ais.readValue("val"); + break; + } + } catch (IOException | Amf3ParseException | NoSerializerExistsException ex) { + System.err.println("Error parsing input value: " + ex.getMessage()); + System.exit(1); + return; + } + + final String fInstance = instance; + final String fKey = key; + final Object fAmfValue = amfValue; + + processModifySWF(swfFile, outFile, null, new SwfAction() { + @Override + public void swfAction(SWF swf, OutputStream stdout) throws IOException { + if (!processTimelined(swf, stdout)) { + System.err.println("No instance with name " + fInstance + " found"); + System.exit(0); + } + } + + private boolean processTimelined(Timelined tim, OutputStream stdout) throws IOException { + ReadOnlyTagList rtl = tim.getTags(); + for (int i = 0; i < rtl.size(); i++) { + Tag t = rtl.get(i); + if (t instanceof Timelined) { + if (processTimelined((Timelined) t, stdout)) { + return true; + } + } + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t; + String instanceName = pt.getInstanceName(); + if (fInstance.equals(instanceName)) { + + Amf3Value oldValue = pt.getAmfData(); + if (oldValue != null && oldValue.getValue() == null) { + oldValue = null; + } + if (oldValue != null && fKey != null) { //it has AMFData and we are going to set key + Object actualValue = oldValue.getValue(); + if (actualValue instanceof ObjectType) { //add it to ObjectType + ObjectType ot = (ObjectType) actualValue; + ot.putDynamicMember(fKey, fAmfValue); + t.setModified(true); + oldValue.setValue(ot); + System.out.println("Key " + fKey + " added"); + System.out.println("New instance data for " + instanceName + ":"); + System.out.println(Amf3Exporter.amfToString(ot, " ", System.lineSeparator())); + return true; + } + } + + PlaceObject4Tag pt4; + if (pt instanceof PlaceObject4Tag) { + pt4 = (PlaceObject4Tag) pt; + } else { + pt4 = new PlaceObject4Tag( + pt.getSwf(), pt.flagMove(), pt.getDepth(), pt.getClassName(), pt.getCharacterId(), pt.getMatrix(), pt.getColorTransform() == null ? null : new CXFORMWITHALPHA(pt.getColorTransform()), pt.getRatio(), + pt.getInstanceName(), pt.getClipDepth(), pt.getFilters(), pt.getBlendMode(), pt.getBitmapCache(), pt.getVisible(), pt.getBackgroundColor(), pt.getClipActions(), pt.getAmfData(), pt.hasImage()); + tim.replaceTag(i, pt4); + } + + Object newValue; + if (fKey != null) { + ObjectType ot = new ObjectType(new Traits("", true, new ArrayList<>())); + ot.put(fKey, fAmfValue); + newValue = ot; + } else { + newValue = fAmfValue; + } + pt4.amfData = new Amf3Value(newValue); + pt4.setModified(true); + + System.out.println("New instance data for " + instanceName + ":"); + System.out.println(Amf3Exporter.amfToString(newValue, " ", System.lineSeparator())); + + return true; + } + } + } + return false; + } + }); + System.exit(0); + } + + private static void parseRemoveInstanceMetadata(Stack args) { + if (args.size() < 2) { + badArguments("removeinstancemetadata"); + } + + Set processedParams = new HashSet<>(); + String key = null; + String instance = null; + File swfFile = null; + File outFile = null; + while (!args.empty()) { + String paramName = args.pop().toLowerCase(Locale.ENGLISH); + if (processedParams.contains(paramName)) { + System.err.println("Parameter " + paramName + " can appear only once."); + } + switch (paramName) { + case "-instance": + if (args.isEmpty()) { + System.err.println("Missing instance name"); + badArguments("removeinstancemetadata"); + } + instance = args.pop(); + break; + case "-key": + if (args.empty()) { + System.err.println("Missing key value"); + badArguments("removeinstancemetadata"); + } + + key = args.pop(); + break; + case "-outfile": + if (args.empty()) { + System.err.println("Missing outFile"); + badArguments("removeinstancemetadata"); + } + outFile = new File(args.pop()); + break; + default: + if (!args.isEmpty()) { + badArguments("removeinstancemetadata"); + } + swfFile = new File(paramName); + paramName = null; + } + if (paramName != null) { + processedParams.add(paramName); + } + } + if (instance == null) { + System.err.println("No instance specified"); + badArguments("removeinstancemetadata"); + } + if (swfFile == null) { + System.err.println("No SWF file specified"); + badArguments("removeinstancemetadata"); + } + if (outFile == null) { + outFile = swfFile; + } + + final String fInstance = instance; + final String fKey = key; + + processModifySWF(swfFile, outFile, null, new SwfAction() { + @Override + public void swfAction(SWF swf, OutputStream stdout) throws IOException { + if (!processTimelined(swf, stdout)) { + System.err.println("No instance with name " + fInstance + " found"); + System.exit(0); + } + } + + private boolean processTimelined(Timelined tim, OutputStream stdout) throws IOException { + ReadOnlyTagList rtl = tim.getTags(); + for (int i = 0; i < rtl.size(); i++) { + Tag t = rtl.get(i); + if (t instanceof Timelined) { + if (processTimelined((Timelined) t, stdout)) { + return true; + } + } + if (t instanceof PlaceObject4Tag) { + PlaceObject4Tag pt4 = (PlaceObject4Tag) t; + String instanceName = pt4.getInstanceName(); + if (fInstance.equals(instanceName)) { + Amf3Value oldValue = pt4.getAmfData(); + if (oldValue == null) { + System.err.println("No metadata for instance " + instanceName + " found"); + System.exit(1); //TODO? Different exit code + } + Object actualValue = oldValue.getValue(); + + if (fKey != null) { + if (actualValue instanceof ObjectType) { + ObjectType ot = (ObjectType) actualValue; + if (ot.containsDynamicMember(fKey)) { + ot.remove(fKey); + oldValue.setValue(ot); + System.out.println("Key " + fKey + " removed"); + System.out.println("New instance data for " + instanceName + ":"); + System.out.println(Amf3Exporter.amfToString(ot, " ", System.lineSeparator())); + pt4.setModified(true); + return true; + } else { + System.err.println("No value with key " + fKey + " exists"); + System.err.println("Available keys: " + String.join(",", ot.dynamicMembersKeySet())); + System.exit(1); + } + } else { + System.err.println("Metadata present, but not as Object type, cannot remove key " + fKey); + System.exit(1); + } + } else { + pt4.amfData = null; + pt4.setModified(true); + System.out.println("Whole metadata removed for instance " + instanceName); + } + + return true; + } + } + } + return false; + } + }); + System.exit(0); + + } + + private static class Range { + + public Integer min; + + public Integer max; + + public Range(Integer min, Integer max) { + this.min = min; + this.max = max; + } + + public boolean contains(int index) { + int minimum = min == null ? Integer.MIN_VALUE : min; + int maximum = max == null ? Integer.MAX_VALUE : max; + + return index >= minimum && index <= maximum; + } + } + + private static class Selection { + + public List ranges; + + public Selection() { + this.ranges = new ArrayList<>(); + this.ranges.add(new Range(null, null)); + } + + public Selection(List ranges) { + this.ranges = ranges; + } + + public boolean contains(int index) { + for (Range r : ranges) { + if (r.contains(index)) { + return true; + } + } + return false; + } + } + + private static Selection parseSelect(Stack args) { + List ret = new ArrayList<>(); + if (args.isEmpty()) { + System.err.println("range parameter expected"); + badArguments("select"); + } + String range = args.pop(); + String[] ranges; + if (range.contains(",")) { + ranges = range.split(","); + } else { + ranges = new String[]{range}; + } + for (String r : ranges) { + Integer min = null; + Integer max = null; + if (r.contains("-")) { + String[] ps = r.split("\\-"); + if (ps.length != 2) { + System.err.println("invalid range"); + badArguments("select"); + } + try { + if (!"".equals(ps[0])) { + min = Integer.parseInt(ps[0]); + } + if (!"".equals(ps[1])) { + max = Integer.parseInt(ps[1]); + } + } catch (NumberFormatException nfe) { + System.err.println("invalid range"); + badArguments("select"); + } + } else { + try { + min = Integer.parseInt(r); + max = min; + } catch (NumberFormatException nfe) { + System.err.println("invalid range"); + badArguments("select"); + } + } + ret.add(new Range(min, max)); + } + return new Selection(ret); + } + + private static double parseZoom(Stack args) { + if (args.isEmpty()) { + System.err.println("zoom parameter expected"); + badArguments("zoom"); + } + try { + return Double.parseDouble(args.pop()); + } catch (NumberFormatException nfe) { + System.err.println("invalid zoom"); + badArguments("zoom"); + } + return 1; + } + + private static AbortRetryIgnoreHandler parseOnError(Stack args) { + int errorMode = AbortRetryIgnoreHandler.UNDEFINED; + int retryCount = 0; + + if (args.isEmpty()) { + System.err.println("onerror parameter expected"); + badArguments("onerror"); + } + String errorModeParameter = args.pop(); + switch (errorModeParameter) { + case "abort": + errorMode = AbortRetryIgnoreHandler.ABORT; + break; + case "retry": + errorMode = AbortRetryIgnoreHandler.RETRY; + if (args.isEmpty()) { + System.err.println("onerror retry count parameter expected"); + badArguments("onerror"); + } + + try { + retryCount = Integer.parseInt(args.pop()); + } catch (NumberFormatException nex) { + System.err.println("Bad retry count number"); + } + break; + case "ignore": + errorMode = AbortRetryIgnoreHandler.IGNORE; + break; + } + + return new ConsoleAbortRetryIgnoreHandler(errorMode, retryCount); + } + + private static void parseTimeout(Stack args) { + if (args.isEmpty()) { + System.err.println("timeout parameter expected"); + badArguments("timeout"); + } + try { + int timeout = Integer.parseInt(args.pop()); + Configuration.decompilationTimeoutSingleMethod.set(timeout); + } catch (NumberFormatException nex) { + System.err.println("Bad timeout value"); + } + } + + private static void parseExportTimeout(Stack args) { + if (args.isEmpty()) { + System.err.println("timeout parameter expected"); + badArguments("exporttimeout"); + } + try { + int timeout = Integer.parseInt(args.pop()); + Configuration.exportTimeout.set(timeout); + } catch (NumberFormatException nex) { + System.err.println("Bad timeout value"); + } + } + + private static void parseExportFileTimeout(Stack args) { + if (args.isEmpty()) { + System.err.println("timeout parameter expected"); + badArguments("exportfiletimeout"); + } + try { + int timeout = Integer.parseInt(args.pop()); + Configuration.decompilationTimeoutFile.set(timeout); + } catch (NumberFormatException nex) { + System.err.println("Bad timeout value"); + } + } + + private static void parseStat(Stack args) { + showStat = true; + Configuration.showStat = showStat; + } + + private static void parseStdOut(Stack args) { + if (args.isEmpty()) { + System.err.println("stdOut parameter expected"); + badArguments("stdout"); + } + + stdOut = args.pop(); + } + + private static void parseStdErr(Stack args) { + if (args.isEmpty()) { + System.err.println("stdErr parameter expected"); + badArguments("stderr"); + } + + stdErr = args.pop(); + } + + private static void parseAffinity(Stack args) { + if (Platform.isWindows()) { + if (args.isEmpty()) { + System.err.println("affinity parameter expected"); + badArguments("affinity"); + } + try { + int affinityMask = Integer.parseInt(args.pop()); + Kernel32.INSTANCE.SetProcessAffinityMask(Kernel32.INSTANCE.GetCurrentProcess(), affinityMask); + } catch (NumberFormatException nex) { + System.err.println("Bad affinityMask value"); + } + } else { + System.err.println("Process affinity setting is only available on Windows platform."); + } + } + + private static void parsePriority(Stack args) { + if (Platform.isWindows()) { + if (args.isEmpty()) { + System.err.println("priority parameter expected"); + badArguments("priority"); + } + String priority = args.pop(); + int priorityClass = 0; + switch (priority) { + case "low": + priorityClass = Kernel32.IDLE_PRIORITY_CLASS; + break; + case "belownormal": + priorityClass = Kernel32.BELOW_NORMAL_PRIORITY_CLASS; + break; + case "normal": + priorityClass = Kernel32.NORMAL_PRIORITY_CLASS; + break; + case "abovenormal": + priorityClass = Kernel32.ABOVE_NORMAL_PRIORITY_CLASS; + break; + case "high": + priorityClass = Kernel32.HIGH_PRIORITY_CLASS; + break; + case "realtime": + priorityClass = Kernel32.REALTIME_PRIORITY_CLASS; + break; + default: + System.err.println("Bad affinityMask value"); + } + if (priorityClass != 0) { + Kernel32.INSTANCE.SetPriorityClass(Kernel32.INSTANCE.GetCurrentProcess(), priorityClass); + } + } else { + System.err.println("Process priority setting is only available on Windows platform."); + } + } + + private static void parseDebugTool(Stack args) { + String cmd = args.pop().toLowerCase(Locale.ENGLISH); + switch (cmd) { + case "findtag": { + String folder = args.pop(); + String tagIdOrName = args.pop(); + int tagId; + try { + tagId = Integer.parseInt(tagIdOrName); + } catch (NumberFormatException e) { + tagId = Tag.getKnownClassesByName().get(tagIdOrName).getId(); + } + + PrintStream oldOut = System.out; + PrintStream oldErr = System.err; + PrintStream nullStream = new PrintStream(new OutputStream() { + @Override + public void write(int b) { + } + }); + System.setOut(nullStream); + System.setErr(nullStream); + Main.initLogging(Configuration._debugMode.get()); + + File[] files = new File(folder).listFiles(getSwfFilter()); + for (File file : files) { + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, file.getAbsolutePath(), file.getName()); + try { + SWF swf = new SWF(new FileInputStream(file), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get()); + swf.swfList = new SWFList(); + swf.swfList.sourceInfo = sourceInfo; + boolean found = false; + for (Tag tag : swf.getTags()) { + if (tag.getId() == tagId) { + found = true; + break; + } + } + + if (found) { + oldOut.println("Tag found in file: " + file.getAbsolutePath()); + } + } catch (IOException | InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + System.setOut(oldOut); + System.setErr(oldErr); + Main.initLogging(Configuration._debugMode.get()); + break; + } + case "finderrorheader": { + String folder = args.pop(); + + PrintStream oldOut = System.out; + PrintStream oldErr = System.err; + PrintStream nullStream = new PrintStream(new OutputStream() { + @Override + public void write(int b) { + } + }); + System.setOut(nullStream); + System.setErr(nullStream); + Main.initLogging(Configuration._debugMode.get()); + + File[] files = new File(folder).listFiles(getSwfFilter()); + for (File file : files) { + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, file.getAbsolutePath(), file.getName()); + try { + SWF swf = new SWF(new FileInputStream(file), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get()); + swf.swfList = new SWFList(); + swf.swfList.sourceInfo = sourceInfo; + boolean found = false; + for (Tag tag : swf.getTags()) { + if (tag instanceof JPEGTablesTag) { + JPEGTablesTag jtt = (JPEGTablesTag) tag; + if (ImageTag.hasErrorHeader(jtt.jpegData)) { + found = true; + break; + } + } else if (tag instanceof DefineBitsJPEG2Tag) { + DefineBitsJPEG2Tag jpeg = (DefineBitsJPEG2Tag) tag; + if (ImageTag.hasErrorHeader(jpeg.imageData)) { + found = true; + break; + } + } else if (tag instanceof DefineBitsJPEG3Tag) { + DefineBitsJPEG3Tag jpeg = (DefineBitsJPEG3Tag) tag; + if (ImageTag.hasErrorHeader(jpeg.imageData)) { + found = true; + break; + } + } + } + + if (found) { + oldOut.println("Tag found in file: " + file.getAbsolutePath()); + } + } catch (IOException | InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + System.setOut(oldOut); + System.setErr(oldErr); + Main.initLogging(Configuration._debugMode.get()); + break; + } + } + } + + private static void parseCompareResources(Stack args) { + String revision = args.pop().toLowerCase(Locale.ENGLISH); + String revision2 = null; + if (!args.isEmpty()) { + revision2 = args.pop(); + } + + CheckResources.compareResources(System.out, revision, revision2); + } + + private static void parseResourceDates(Stack args) { + CheckResources.checkTranslationDate(System.out); + } + + private static void parseProxy(Stack args) { + int port = 55555; + String portStr = args.peek(); + if (portStr != null && portStr.startsWith("-P")) { + args.pop(); + try { + port = Integer.parseInt(portStr.substring(2)); + } catch (NumberFormatException nex) { + System.err.println("Bad port number"); + } + } + Main.startProxy(port); + } + + private static List parseSelectClass(Stack args) { + if (args.size() < 1) { + badArguments("selectclass"); + } + List ret = new ArrayList<>(); + String classesStr = args.pop(); + String classes[]; + if (classesStr.contains(",")) { + classes = classesStr.split(","); + } else { + classes = new String[]{classesStr}; + } + ret.addAll(Arrays.asList(classes)); + return ret; + + } + + private static void parseExport(List selectionClasses, Selection selection, Selection selectionIds, Stack args, AbortRetryIgnoreHandler handler, Level traceLevel, Map formats, double zoom) { + if (args.size() < 3) { + badArguments("export"); + } + String[] validExportItems = new String[]{ + "script", + "script_as2", + "script_as3", + "image", + "shape", + "morphshape", + "movie", + "font", + "frame", + "sprite", + "button", + "sound", + "binarydata", + "text", + "all", + "fla", + "xfl" + }; + + String[] removedExportFormats = new String[]{ + "as", "pcode", "hex", "pcodehex", "all_as", "all_pcode", "all_pcodehex", "all_hex", "textplain" + }; + + if (handler == null) { + handler = new ConsoleAbortRetryIgnoreHandler(AbortRetryIgnoreHandler.UNDEFINED, 0); + } + + String exportFormatString = args.pop().toLowerCase(Locale.ENGLISH); + List exportFormats = Arrays.asList(exportFormatString.split(",")); + long startTime = System.currentTimeMillis(); + + File outDirBase = new File(args.pop()); + File inFileOrFolder = new File(args.pop()); + if (!inFileOrFolder.exists()) { + System.err.println("Input SWF file does not exist!"); + badArguments("export"); + } + + if (!args.isEmpty() && args.peek().equals("-selectas3class")) { + System.err.println("Error: -selectas3class parameter was REMOVED. Please use -selectclass instead. See --help for usage."); + System.exit(1); + } + + printHeader(); + boolean exportOK = true; + + List as3classes = new ArrayList<>(); + if (selectionClasses != null) { + as3classes.addAll(selectionClasses); + } + + Map stat = new HashMap<>(); + + try { + File[] inFiles; + boolean singleFile = true; + if (inFileOrFolder.isDirectory()) { + singleFile = false; + inFiles = inFileOrFolder.listFiles(getSwfFilter()); + } else { + inFiles = new File[]{inFileOrFolder}; + } + + for (File inFile : inFiles) { + String inFileName = Path.getFileNameWithoutExtension(inFile); + if (stdOut != null) { + String outFilePath = stdOut.replace("{swfFile}", inFileName); + Path.createDirectorySafe(new File(outFilePath).getParentFile()); + System.setOut(new PrintStream(new FileOutputStream(outFilePath, true))); + } + + if (stdErr != null) { + String errFilePath = stdErr.replace("{swfFile}", inFileName); + Path.createDirectorySafe(new File(errFilePath).getParentFile()); + System.setErr(new PrintStream(new FileOutputStream(errFilePath, true))); + Main.initLogging(Configuration._debugMode.get()); + } + + long startTimeSwf = 0; + if (!singleFile) { + startTimeSwf = System.currentTimeMillis(); + System.out.println("Start exporting " + inFile.getName()); + } + + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, inFile.getAbsolutePath(), inFile.getName()); + SWF swf; + try { + swf = new SWF(new FileInputStream(inFile), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get()); + } catch (FileNotFoundException | SwfOpenException ex) { + // FileNotFoundException when anti virus software blocks to open the file + logger.log(Level.SEVERE, "Failed to open swf: " + inFile.getName(), ex); + continue; + } + + swf.swfList = new SWFList(); + swf.swfList.sourceInfo = sourceInfo; + String outDir = outDirBase.getAbsolutePath(); + if (!singleFile) { + outDir = Path.combine(outDir, inFile.getName()); + } + + List extags = new ArrayList<>(); + for (Tag t : swf.getTags()) { + if (t instanceof CharacterIdTag) { + CharacterIdTag c = (CharacterIdTag) t; + if (selectionIds.contains(c.getCharacterId())) { + extags.add(t); + } + } else if (selectionIds.contains(0)) { + extags.add(t); + } + } + + final Level level = traceLevel; + swf.addEventListener(new EventListener() { + @Override + public void handleExportingEvent(String type, int index, int count, Object data) { + if (level.intValue() <= Level.FINE.intValue()) { + String text = "Exporting "; + if (type != null && type.length() > 0) { + text += type + " "; + } + System.out.println(text + index + "/" + count + " " + data); + } + } + + @Override + public void handleExportedEvent(String type, int index, int count, Object data) { + String text = "Exported "; + if (type != null && type.length() > 0) { + text += type + " "; + } + System.out.println(text + index + "/" + count + " " + data); + } + + @Override + public void handleEvent(String event, Object data) { + } + }); + + // First check all the specified export formats + for (String exportFormat : exportFormats) { + if (Arrays.asList(removedExportFormats).contains(exportFormat)) { + System.err.println("Error: Export format : " + exportFormat + " was REMOVED. Run application with --help parameter to see available formats."); + System.exit(1); + } else if (!Arrays.asList(validExportItems).contains(exportFormat)) { + System.err.println("Invalid export item:" + exportFormat); + badArguments("export"); + } + } + + // Here the exportFormats array should contain only validitems + commandLineMode = true; + boolean exportAll = exportFormats.contains("all"); + boolean multipleExportTypes = exportAll || exportFormats.size() > 1; + EventListener evl = swf.getExportEventListener(); + + if (exportAll || exportFormats.contains("image")) { + System.out.println("Exporting images..."); + new ImageExporter().exportImages(handler, outDir + (multipleExportTypes ? File.separator + ImageExportSettings.EXPORT_FOLDER_NAME : ""), new ReadOnlyTagList(extags), new ImageExportSettings(enumFromStr(formats.get("image"), ImageExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("shape")) { + System.out.println("Exporting shapes..."); + new ShapeExporter().exportShapes(handler, outDir + (multipleExportTypes ? File.separator + ShapeExportSettings.EXPORT_FOLDER_NAME : ""), swf, new ReadOnlyTagList(extags), new ShapeExportSettings(enumFromStr(formats.get("shape"), ShapeExportMode.class), zoom), evl, zoom); + } + + if (exportAll || exportFormats.contains("morphshape")) { + System.out.println("Exporting morphshapes..."); + new MorphShapeExporter().exportMorphShapes(handler, outDir + (multipleExportTypes ? File.separator + MorphShapeExportSettings.EXPORT_FOLDER_NAME : ""), new ReadOnlyTagList(extags), new MorphShapeExportSettings(enumFromStr(formats.get("morphshape"), MorphShapeExportMode.class), zoom), evl); + } + + if (exportAll || exportFormats.contains("movie")) { + System.out.println("Exporting movies..."); + new MovieExporter().exportMovies(handler, outDir + (multipleExportTypes ? File.separator + MovieExportSettings.EXPORT_FOLDER_NAME : ""), new ReadOnlyTagList(extags), new MovieExportSettings(enumFromStr(formats.get("movie"), MovieExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("font")) { + System.out.println("Exporting fonts..."); + new FontExporter().exportFonts(handler, outDir + (multipleExportTypes ? File.separator + FontExportSettings.EXPORT_FOLDER_NAME : ""), new ReadOnlyTagList(extags), new FontExportSettings(enumFromStr(formats.get("font"), FontExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("sound")) { + System.out.println("Exporting sounds..."); + new SoundExporter().exportSounds(handler, outDir + (multipleExportTypes ? File.separator + SoundExportSettings.EXPORT_FOLDER_NAME : ""), new ReadOnlyTagList(extags), new SoundExportSettings(enumFromStr(formats.get("sound"), SoundExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("binarydata")) { + System.out.println("Exporting binaryData..."); + new BinaryDataExporter().exportBinaryData(handler, outDir + (multipleExportTypes ? File.separator + BinaryDataExportSettings.EXPORT_FOLDER_NAME : ""), new ReadOnlyTagList(extags), new BinaryDataExportSettings(enumFromStr(formats.get("binarydata"), BinaryDataExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("text")) { + System.out.println("Exporting texts..."); + Boolean singleTextFile = parseBooleanConfigValue(formats.get("singletext")); + if (singleTextFile == null) { + singleTextFile = Configuration.textExportSingleFile.get(); + } + new TextExporter().exportTexts(handler, outDir + (multipleExportTypes ? File.separator + TextExportSettings.EXPORT_FOLDER_NAME : ""), new ReadOnlyTagList(extags), new TextExportSettings(enumFromStr(formats.get("text"), TextExportMode.class), singleTextFile, zoom), evl); + } + + FrameExporter frameExporter = new FrameExporter(); + + if (exportAll || exportFormats.contains("frame")) { + System.out.println("Exporting frames..."); + List frames = new ArrayList<>(); + for (int i = 0; i < swf.frameCount; i++) { + if (selection.contains(i + 1)) { + frames.add(i); + } + } + FrameExportSettings fes = new FrameExportSettings(enumFromStr(formats.get("frame"), FrameExportMode.class), zoom); + frameExporter.exportFrames(handler, outDir + (multipleExportTypes ? File.separator + FrameExportSettings.EXPORT_FOLDER_NAME : ""), swf, 0, frames, fes, evl); + } + + if (exportAll || exportFormats.contains("sprite")) { + System.out.println("Exporting sprite..."); + SpriteExportSettings ses = new SpriteExportSettings(enumFromStr(formats.get("sprite"), SpriteExportMode.class), zoom); + for (Tag t : extags) { + if (t instanceof DefineSpriteTag) { + frameExporter.exportSpriteFrames(handler, outDir + (multipleExportTypes ? File.separator + SpriteExportSettings.EXPORT_FOLDER_NAME : ""), swf, ((DefineSpriteTag) t).getCharacterId(), null, ses, evl); + } + } + } + + if (exportAll || exportFormats.contains("button")) { + System.out.println("Exporting buttons..."); + ButtonExportSettings bes = new ButtonExportSettings(enumFromStr(formats.get("button"), ButtonExportMode.class), zoom); + for (Tag t : extags) { + if (t instanceof ButtonTag) { + frameExporter.exportButtonFrames(handler, outDir + (multipleExportTypes ? File.separator + ButtonExportSettings.EXPORT_FOLDER_NAME : ""), swf, ((ButtonTag) t).getCharacterId(), null, bes, evl); + } + } + } + + boolean parallel = Configuration.parallelSpeedUp.get(); + Boolean singleScriptFile = parseBooleanConfigValue(formats.get("singlescript")); + if (singleScriptFile == null) { + singleScriptFile = Configuration.scriptExportSingleFile.get(); + } + + if (parallel && singleScriptFile) { + logger.log(Level.WARNING, AppStrings.translate("export.script.singleFilePallelModeWarning")); + singleScriptFile = false; + } + + ScriptExportSettings scriptExportSettings = new ScriptExportSettings(enumFromStr(formats.get("script"), ScriptExportMode.class), singleScriptFile, false); + boolean exportAllScript = exportAll || exportFormats.contains("script"); + boolean exportAs2Script = exportAllScript || exportFormats.contains("script_as2"); + boolean exportAs3Script = exportAllScript || exportFormats.contains("script_as3"); + if (exportAs2Script || exportAs3Script) { + System.out.println("Exporting scripts..."); + + String scriptsFolder = Path.combine(outDir, ScriptExportSettings.EXPORT_FOLDER_NAME); + Path.createDirectorySafe(new File(scriptsFolder)); + String singleFileName = Path.combine(scriptsFolder, swf.getShortFileName() + scriptExportSettings.getFileExtension()); + try (FileTextWriter writer = scriptExportSettings.singleFile ? new FileTextWriter(Configuration.getCodeFormatting(), new FileOutputStream(singleFileName)) : null) { + scriptExportSettings.singleFileWriter = writer; + List as3packs = as3classes.isEmpty() ? null : swf.getScriptPacksByClassNames(as3classes); + exportOK = swf.exportActionScript(handler, scriptsFolder, as3classes.isEmpty() ? null : as3packs, scriptExportSettings, parallel, evl, exportAs2Script, exportAs3Script) != null && exportOK; + } + + if (showStat) { + Statistics.print(); + Statistics.addToMap(stat); + Statistics.clear(); + } + } + + if (exportFormats.contains("fla")) { + System.out.println("Exporting FLA..."); + exportFla(true, outDir, inFile, swf, multipleExportTypes, formats, handler); + } + + if (exportFormats.contains("xfl")) { + System.out.println("Exporting XFL..."); + exportFla(false, outDir, inFile, swf, multipleExportTypes, formats, handler); + } + + if (!singleFile) { + long stopTimeSwf = System.currentTimeMillis(); + long time = stopTimeSwf - startTimeSwf; + System.out.println("Export finished: " + inFile.getName() + " Export time: " + Helper.formatTimeSec(time)); + } + + swf.clearAllCache(); + CancellableWorker.cancelBackgroundThreads(); + } + } catch (OutOfMemoryError | Exception ex) { + System.err.print("FAIL: Exporting Failed on Exception - "); + logger.log(Level.SEVERE, null, ex); + System.exit(1); + } + + if (showStat) { + Statistics.print(stat); + } + + long stopTime = System.currentTimeMillis(); + long time = stopTime - startTime; + System.out.println("Export finished. Total export time: " + Helper.formatTimeSec(time)); + System.out.println(exportOK ? "OK" : "FAIL"); + System.exit(exportOK ? 0 : 1); + } + + private static void exportFla(boolean compressed, String outDir, File inFile, SWF swf, boolean multipleExportTypes, Map formats, AbortRetryIgnoreHandler handler) throws IOException, InterruptedException { + String exportFormat = compressed ? "fla" : "xfl"; + String format = formats.get(exportFormat); + boolean exportScript = true; + if (format != null && format.endsWith("_noscript")) { + format = format.substring(0, format.length() - 9); + exportScript = false; + } + + FLAVersion flaVersion = FLAVersion.fromString(format); + if (flaVersion == null) { + flaVersion = FLAVersion.CS6; //Defaults to CS6 + } + + String outFile = outDir; + if (multipleExportTypes) { + outFile = Path.combine(outFile, exportFormat); + }; + + String outFileName = inFile.getName().toLowerCase(Locale.ENGLISH).endsWith(".swf") ? inFile.getName().substring(0, inFile.getName().length() - 3) + exportFormat : inFile.getName(); + outFile = Path.combine(outFile, outFileName); + XFLExportSettings settings = new XFLExportSettings(); + settings.compressed = compressed; + settings.exportScript = exportScript; + + try { + if (Configuration.setFFDecVersionInExportedFont.get()) { + swf.exportXfl(handler, outFile, inFile.getName(), ApplicationInfo.APPLICATION_NAME, ApplicationInfo.applicationVerName, ApplicationInfo.version, Configuration.parallelSpeedUp.get(), flaVersion, settings); + } else { + swf.exportXfl(handler, outFile, inFile.getName(), ApplicationInfo.APPLICATION_NAME, ApplicationInfo.APPLICATION_NAME, "1.0.0", Configuration.parallelSpeedUp.get(), flaVersion, settings); + } + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error during XFL/FLA export", ex); + } + } + + private static void parseDeobfuscate(Stack args) { + if (args.size() < 3) { + badArguments("deobfuscate"); + } + String mode = args.pop(); + DeobfuscationLevel lev; + switch (mode) { + case "controlflow": + case "max": + case "3": + lev = DeobfuscationLevel.LEVEL_RESTORE_CONTROL_FLOW; + break; + case "traps": + case "2": + lev = DeobfuscationLevel.LEVEL_REMOVE_TRAPS; + break; + case "deadcode": + case "1": + lev = DeobfuscationLevel.LEVEL_REMOVE_DEAD_CODE; + break; + default: + System.err.println("Invalid level, must be one of: controlflow,traps,deadcode or 1,2,3/max"); + System.exit(1); + return; + } + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + File tmpFile = null; + if (inFile.equals(outFile)) { + try { + tmpFile = File.createTempFile("ffdec_deobf_", ".swf"); + outFile = tmpFile; + } catch (IOException ex) { + System.err.println("Unable to create temp file"); + System.exit(1); + } + } + try (FileInputStream is = new FileInputStream(inFile); + FileOutputStream fos = new FileOutputStream(outFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + if (!swf.isAS3()) { + System.out.println("Warning: The file is not AS3. Only AS3 deobfuscation from commandline is available."); + System.exit(0); + } + swf.deobfuscate(lev); + swf.saveTo(fos); + if (tmpFile != null) { + inFile.delete(); + tmpFile.renameTo(inFile); + tmpFile = null; + System.out.println(inFile + " overwritten."); + } + System.out.println("OK"); + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + System.exit(1); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + System.exit(1); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error", ex); + System.exit(1); + } finally { + if (tmpFile != null && tmpFile.exists()) { + tmpFile.delete(); + } + } + } + + private static void parseCompress(Stack args) { + if (args.size() < 2) { + badArguments("compress"); + } + + boolean result = false; + try { + SWFCompression compression = SWFCompression.ZLIB; + String compressionString = !args.isEmpty() ? args.pop() : null; + if (compressionString != null) { + switch (compressionString.toLowerCase(Locale.ENGLISH)) { + case "zlib": + compression = SWFCompression.ZLIB; + break; + case "lzma": + compression = SWFCompression.LZMA; + break; + default: + System.out.println("Unsupported compression method: " + compressionString); + System.exit(0); + break; + } + } + + try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); + OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { + result = SWF.compress(fis, fos, compression); + System.out.println(result ? "OK" : "FAIL"); + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + System.exit(1); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(result ? 0 : 1); + } + + private static void parseDecompress(Stack args) { + if (args.size() < 2) { + badArguments("decompress"); + } + + boolean result = false; + try { + try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); + OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { + result = SWF.decompress(fis, fos); + System.out.println(result ? "OK" : "FAIL"); + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + System.exit(1); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(result ? 0 : 1); + } + + private static void parseSwf2Xml(Stack args) { + if (args.size() < 2) { + badArguments("swf2xml"); + } + + try { + try (FileInputStream is = new FileInputStream(args.pop())) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + new SwfXmlExporter().exportXml(swf, new File(args.pop())); + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + System.exit(1); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(0); + } + + private static void parseXml2Swf(Stack args) { + if (args.size() < 2) { + badArguments("xml2swf"); + } + + try { + File inFile = new File(args.pop()); + SWF swf = new SWF(); + new SwfXmlImporter().importSwf(swf, inFile); + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(args.pop())))) { + swf.saveTo(fos); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(0); + } + + private static void parseExtract(Stack args) { + if (args.size() < 1) { + badArguments("extract"); + } + + String fileName = args.pop(); + SearchMode mode = SearchMode.ALL; + + boolean noCheck = false; + String output = null; + + if (args.size() > 0 && args.peek().toLowerCase(Locale.ENGLISH).equals("-o")) { + args.pop(); + if (args.size() < 1) { + badArguments("extract"); + } + output = args.pop(); + } + + if (args.size() > 0 && args.peek().toLowerCase(Locale.ENGLISH).equals("nocheck")) { + noCheck = true; + args.pop(); + } + + if (args.size() > 0) { + String modeStr = args.pop().toLowerCase(Locale.ENGLISH); + switch (modeStr) { + case "biggest": + mode = SearchMode.BIGGEST; + break; + case "smallest": + mode = SearchMode.SMALLEST; + break; + case "first": + mode = SearchMode.FIRST; + break; + case "last": + mode = SearchMode.LAST; + break; + } + } + + try { + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, fileName, null); + if (!sourceInfo.isBundle()) { + System.err.println("Error: should be a bundle. (ZIP or non SWF binary file)"); + System.exit(1); + } + SWFBundle bundle = sourceInfo.getBundle(noCheck, mode); + List> streamsToExtract = new ArrayList<>(); + for (Map.Entry streamEntry : bundle.getAll().entrySet()) { + InputStream stream = streamEntry.getValue(); + stream.reset(); + streamsToExtract.add(streamEntry); + } + + for (Map.Entry streamEntry : streamsToExtract) { + InputStream stream = streamEntry.getValue(); + stream.reset(); + String fileNameOut; + if (mode != SearchMode.ALL) { + if (output == null) { + fileNameOut = Path.getFileNameWithoutExtension(new File(fileName)) + ".swf"; + } else { + fileNameOut = output; + } + } else { + fileNameOut = streamEntry.getKey() + ".swf"; + if (output != null) { + fileNameOut = Path.combine(output, fileNameOut); + } + } + + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fileNameOut))) { + byte[] swfData = new byte[stream.available()]; + int cnt = stream.read(swfData); + fos.write(swfData, 0, cnt); + } + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(0); + } + + private static void parseMemorySearch(Stack args) { + if (args.size() < 1) { + badArguments("memorysearch"); + } + + if (Platform.isWindows()) { + AtomicInteger cnt = new AtomicInteger(); + List procs = new ArrayList<>(); + List processList = ProcessTools.listProcesses(); + while (args.size() > 0) { + String arg = args.pop(); + if (arg.matches("\\d+")) { + int processId = 0; + try { + processId = Integer.parseInt(arg); + } catch (NumberFormatException nfe) { + System.err.println("ProcessId should be integer"); + badArguments("memorysearch"); + } + + boolean found = false; + if (processList != null) { + for (Process process : processList) { + if (process.getPid() == processId) { + if (!procs.contains(process)) { + procs.add(process); + } + + found = true; + break; // only 1 process can have this process id + } + } + } + + if (!found) { + System.out.println("Process id=" + processId + " was not found."); + } + } else { + boolean found = false; + if (processList != null) { + for (Process process : processList) { + if (process.getFileName().equals(arg)) { + if (!procs.contains(process)) { + procs.add(process); + } + + found = true; + } + } + } + + if (!found) { + System.out.println("Process name=" + arg + " was not found."); + } + } + } + + try { + new SearchInMemory(new SearchInMemoryListener() { + @Override + public void publish(Object... chunks) { + for (Object s : chunks) { + if (s instanceof SwfInMemory) { + SwfInMemory swf = (SwfInMemory) s; + String fileName = cnt.getAndIncrement() + ".swf"; + System.out.println("SWF found (" + fileName + "). Version: " + swf.version + ", file size: " + swf.fileSize + ", address: " + swf.address); + Helper.writeFile(fileName, swf.is); + } + } + } + + @Override + public void setProgress(int progress) { + // ignore + } + }).search(procs); + } catch (Exception ex) { + logger.log(Level.SEVERE, null, ex); + } + } else { + System.err.println("Memory search is only available on Windows platform."); + } + + System.exit(0); + } + + private static void parseRenameInvalidIdentifiers(Stack args) { + if (args.size() < 3) { + badArguments("renameinvalididentifiers"); + } + + String renameTypeStr = args.pop(); + RenameType renameType; + switch (renameTypeStr.toLowerCase(Locale.ENGLISH)) { + case "typenumber": + renameType = RenameType.TYPENUMBER; + break; + case "randomword": + renameType = RenameType.RANDOMWORD; + break; + default: + System.err.println("Invalid rename type:" + renameTypeStr); + badArguments("renameinvalididentifiers"); + return; + } + + boolean result = false; + try { + try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); + OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { + result = SWF.renameInvalidIdentifiers(renameType, fis, fos); + System.out.println(result ? "OK" : "FAIL"); + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + System.exit(1); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(result ? 0 : 1); + } + + private static Map parseFormat(Stack args) { + if (args.size() < 1) { + badArguments("format"); + } + String fmtStr = args.pop(); + String[] fmts; + if (fmtStr.contains(",")) { + fmts = fmtStr.split(","); + } else { + fmts = new String[]{fmtStr}; + } + Map ret = new HashMap<>(); + for (String fmt : fmts) { + if (!fmt.contains(":")) { + badArguments("format"); + } + String[] parts = fmt.split(":"); + ret.put(parts[0].toLowerCase(Locale.ENGLISH), parts[1].toLowerCase(Locale.ENGLISH)); + } + return ret; + } + + private static void parseFlashPaperToPdf(Selection selection, double zoom, Stack args) { + if (args.size() < 2) { + badArguments("flashpaper2pdf"); + } + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + printHeader(); + + try (FileInputStream is = new FileInputStream(inFile)) { + + PDFJob job = null; + + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + int totalPages = 0; + + for (Tag t : swf.getTags()) { + if (t instanceof DefineSpriteTag) { + DefineSpriteTag ds = (DefineSpriteTag) t; + if ("page1".equals(ds.getExportName())) { + totalPages = 1; + } else if (totalPages > 0) { + totalPages++; + } + } + } + + int page = 0; + + for (Tag t : swf.getTags()) { + if (t instanceof DefineSpriteTag) { + DefineSpriteTag ds = (DefineSpriteTag) t; + if ("page1".equals(ds.getExportName())) { + page = 1; + job = new PDFJob(new BufferedOutputStream(new FileOutputStream(outFile))); + } else if (page > 0) { + page++; + } + if (("page" + page).equals(ds.getExportName())) { + if (!selection.contains(page)) { + continue; + } + System.out.print("Page " + page + "/" + totalPages + "..."); + RECT displayRect = new RECT(ds.getTimeline().displayRect); + BufferedImage img = SWF.frameToImageGet(ds.getTimeline(), 0, 0, null, 0, displayRect, new Matrix(), null, Color.white, zoom).getBufferedImage(); + PageFormat pf = new PageFormat(); + pf.setOrientation(PageFormat.PORTRAIT); + Paper p = new Paper(); + p.setSize(img.getWidth(), img.getHeight()); + pf.setPaper(p); + if (job != null) { + Graphics g = job.getGraphics(pf); + g.drawImage(img, 0, 0, img.getWidth(), img.getHeight(), null); + g.dispose(); + } + System.out.println("OK"); + + } + } + } + + if (job == null) { + System.err.println("No pages found. Maybe it is not a FlashPaper file"); + System.exit(2); + } + job.end(); + + } catch (FileNotFoundException ex) { + System.err.println("File not found"); + System.exit(1); + } catch (IOException | InterruptedException ex) { + System.err.println("I/O error during reading"); + System.exit(2); + } + System.exit(0); + } + + private static void parseReplace(Stack args) { + if (args.size() == 3) { + badArguments("replace"); + } + + System.out.println("Replacing in: " + args.lastElement()); + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + + try { + List lines = Files.readAllLines(Paths.get(args.pop()), StandardCharsets.UTF_8); + Collections.reverse(lines); + + args.clear(); + args.addAll(lines); + + } catch (IOException e) { + e.printStackTrace(System.out); + } + + if (args.isEmpty()) { + System.err.println("Replacments file is empty."); + System.exit(1); + } + + try { + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + while (true) { + String objectToReplace = args.pop(); + + if (objectToReplace.matches("\\d+")) { + // replace character tag + int characterId = 0; + try { + characterId = Integer.parseInt(objectToReplace); + } catch (NumberFormatException nfe) { + System.err.println("CharacterId should be integer"); + badArguments("replace"); + } + if (!swf.getCharacters().containsKey(characterId)) { + System.err.println("CharacterId does not exist"); + System.exit(1); + } + + CharacterTag characterTag = swf.getCharacter(characterId); + String repFile = args.pop(); + byte[] data = Helper.readFile(repFile); + String ext = Path.getExtension(repFile); + if (characterTag instanceof DefineBinaryDataTag) { + DefineBinaryDataTag defineBinaryData = (DefineBinaryDataTag) characterTag; + new BinaryDataImporter().importData(defineBinaryData, data); + } else if (characterTag instanceof ImageTag) { + int format = parseImageFormat(args); + ImageTag imageTag = (ImageTag) characterTag; + new ImageImporter().importImage(imageTag, data, format); + } else if (characterTag instanceof ShapeTag) { + boolean fill = true; + if (!args.isEmpty() && args.peek().equals("nofill")) { + args.pop(); + fill = false; + } + ShapeTag shapeTag = (ShapeTag) characterTag; + + if (".svg".equals(ext)) { + String svgText = Helper.readTextFile(repFile); + new SvgImporter().importSvg(shapeTag, svgText); + } else { + int format = parseImageFormat(args); + new ShapeImporter().importImage(shapeTag, data, format, fill); + } + } else if (characterTag instanceof TextTag) { + TextTag textTag = (TextTag) characterTag; + new TextImporter(new MissingCharacterHandler(), new TextImportErrorHandler() { + @Override + public boolean handle(TextTag textTag) { + String msg = "Error during text import."; + logger.log(Level.SEVERE, msg); + return false; + } + + @Override + public boolean handle(TextTag textTag, String message, long line) { + String msg = "Error during text import: %text% on line %line%".replace("%text%", message).replace("%line%", Long.toString(line)); + logger.log(Level.SEVERE, msg); + return false; + } + }).importText(textTag, new String(data, Utf8Helper.charset)); + } else if (characterTag instanceof SoundTag) { + SoundTag st = (SoundTag) characterTag; + int soundFormat = SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN; + if (repFile.toLowerCase(Locale.ENGLISH).endsWith(".mp3")) { + soundFormat = SoundFormat.FORMAT_MP3; + } + boolean ok = st.setSound(new ByteArrayInputStream(data), soundFormat); + if (!ok) { + System.err.println("Import FAILED. Maybe unsuppoted media type? Only MP3 and uncompressed WAV are available."); + System.exit(1); + } + } else { + System.err.println("The specified tag type is not supported for import"); + System.exit(1); + } + } else { + Map asms = swf.getASMs(false); + boolean found = false; + if (asms.containsKey(objectToReplace)) { + found = true; + // replace AS1/2 + String repFile = args.pop(); + String repText = Helper.readTextFile(repFile); + ASMSource src = asms.get(objectToReplace); + if (Path.getExtension(repFile).equals(".as")) { + replaceAS2(repText, src); + } else { + replaceAS2PCode(repText, src); + } + } else { + List packs = swf.getAS3Packs(); + + for (ScriptPack entry : packs) { + if (entry.getClassPath().toString().equals(objectToReplace)) { + found = true; + // replace AS3 + String repFile = args.pop(); + String repText = Helper.readTextFile(repFile); + ScriptPack pack = entry; + if (Path.getExtension(repFile).equals(".as")) { + replaceAS3(repFile, repText, pack); + } else { + // todo: get traits + if (args.isEmpty()) { + badArguments("replace"); + } + + int bodyIndex = Integer.parseInt(args.pop()); + ABC abc = pack.abc; + List resultTraits = abc.getMethodIndexing().findMethodTraits(pack, bodyIndex); + + //int classIndex = 0; + //int traitId = 0; + Trait trait = null; //abc.findTraitByTraitId(classIndex, traitId); + if (resultTraits.size() == 1) { + trait = resultTraits.get(0); + } + + replaceAS3PCode(repText, abc, bodyIndex, trait); + } + } + } + } + + if (!found) { + System.err.println(objectToReplace + " is not reocginized as a CharacterId or a script name."); + System.exit(1); + } + } + + if (args.isEmpty() || args.peek().startsWith("-")) { + break; + } + } + + try { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + } catch (IOException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static int parseImageFormat(Stack args) { + if (args.isEmpty()) { + return 0; + } + + int res = ImageImporter.getImageTagType(args.peek().toLowerCase(Locale.ENGLISH)); + if (res != 0) { + args.pop(); + } + + return res; + } + + private static void parseReplaceAlpha(Stack args) { + if (args.size() < 4) { + badArguments("replacealpha"); + } + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + while (true) { + String objectToReplace = args.pop(); + + int imageId = 0; + try { + imageId = Integer.parseInt(objectToReplace); + } catch (NumberFormatException nfe) { + System.err.println("ImageId should be integer"); + badArguments("replacealpha"); + } + if (!swf.getCharacters().containsKey(imageId)) { + System.err.println("ImageId does not exist"); + System.exit(1); + } + + CharacterTag characterTag = swf.getCharacter(imageId); + String repFile = args.pop(); + byte[] data = Helper.readFile(repFile); + if (characterTag instanceof DefineBitsJPEG3Tag || characterTag instanceof DefineBitsJPEG4Tag) { + ImageTag imageTag = (ImageTag) characterTag; + new ImageImporter().importImageAlpha(imageTag, data); + } else { + System.err.println("The specified tag type is not supported for alpha channel import"); + badArguments("replacealpha"); + } + + if (args.isEmpty() || args.peek().startsWith("-")) { + break; + } + } + + try { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + } catch (IOException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void parseReplaceCharacter(Stack args) { + if (args.size() < 4) { + badArguments("replacecharacter"); + } + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + while (true) { + String objectToReplace = args.pop(); + + int characterId = 0; + try { + characterId = Integer.parseInt(objectToReplace); + } catch (NumberFormatException nfe) { + System.err.println("CharacterId should be integer"); + badArguments("replacecharacter"); + } + if (!swf.getCharacters().containsKey(characterId)) { + System.err.println("CharacterId does not exist"); + System.exit(1); + } + + CharacterTag characterTag = swf.getCharacter(characterId); + String newCharacterIdStr = args.pop(); + + int newCharacterId = 0; + try { + newCharacterId = Integer.parseInt(newCharacterIdStr); + } catch (NumberFormatException nfe) { + System.err.println("NewCharacterId should be integer"); + badArguments("replacecharacter"); + } + if (!swf.getCharacters().containsKey(newCharacterId)) { + System.err.println("NewCharacterId does not exist"); + System.exit(1); + } + + swf.replaceCharacterTags(characterTag, newCharacterId); + + if (args.isEmpty() || args.peek().startsWith("-")) { + break; + } + } + + try { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + } catch (IOException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void parseReplaceCharacterId(Stack args) { + if (args.size() < 3) { + badArguments("replacecharacterid"); + } + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + String arg = args.pop().toLowerCase(Locale.ENGLISH); + if (arg.equals("pack")) { + swf.packCharacterIds(); + } else if (arg.equals("sort")) { + swf.sortCharacterIds(); + } else { + String[] characterIdsStr = arg.split(","); + if (characterIdsStr.length % 2 != 0) { + System.err.println("CharacterId count should be an even number"); + badArguments("replacecharacterid"); + } + + List characterIds = new ArrayList<>(); + for (int i = 0; i < characterIdsStr.length; i++) { + int characterId; + try { + characterId = Integer.parseInt(characterIdsStr[i]); + characterIds.add(characterId); + } catch (NumberFormatException nfe) { + System.err.println("CharacterId should be integer"); + badArguments("replacecharacterid"); + } + } + + for (int i = 0; i < characterIds.size(); i += 2) { + int oldCharacterId = characterIds.get(i); + int newCharacterId = characterIds.get(i + 1); + swf.replaceCharacter(oldCharacterId, newCharacterId); + } + } + + try { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + } catch (IOException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void parseConvert(Stack args) { + if (args.size() < 4) { + badArguments("convert"); + } + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + + String objectToConvert = args.pop(); + + int characterId = 0; + try { + characterId = Integer.parseInt(objectToConvert); + } catch (NumberFormatException nfe) { + System.err.println("CharacterId should be integer"); + badArguments("convert"); + } + if (!swf.getCharacters().containsKey(characterId)) { + System.err.println("CharacterId does not exist"); + System.exit(1); + } + + CharacterTag characterTag = swf.getCharacter(characterId); + String targetType = args.pop().toLowerCase(Locale.ENGLISH); + if (characterTag instanceof ImageTag) { + int format = ImageImporter.getImageTagType(targetType); + ImageTag imageTag = (ImageTag) characterTag; + new ImageImporter().convertImage(imageTag, format); + } else if (characterTag instanceof ShapeTag) { + int format = ShapeImporter.getShapeTagType(targetType); + ShapeTag shapeTag = (ShapeTag) characterTag; + System.err.println("Converting shape tag is currently not supported"); + } else if (characterTag instanceof MorphShapeTag) { + int format = MorphShapeImporter.getMorphShapeTagType(targetType); + MorphShapeTag morphShapeTag = (MorphShapeTag) characterTag; + System.err.println("Converting morph shape tag is currently not supported"); + } else if (characterTag instanceof FontTag) { + int format = FontImporter.getFontTagType(targetType); + FontTag fontTag = (FontTag) characterTag; + System.err.println("Converting font tag is currently not supported"); + } else if (characterTag instanceof TextTag) { + int format = TextImporter.getTextTagType(targetType); + TextTag textTag = (TextTag) characterTag; + System.err.println("Converting text tag is currently not supported"); + } else { + System.err.println("The specified tag type is not supported for import"); + System.exit(1); + } + + try { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + } catch (IOException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void parseRemove(Stack args) { + if (args.size() < 3) { + badArguments("remove"); + } + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + List tagNumbersToRemove = new ArrayList<>(); + while (true) { + String tagNoToRemoveStr = args.pop(); + + int tagNo; + try { + tagNo = Integer.parseInt(tagNoToRemoveStr); + } catch (NumberFormatException nfe) { + System.err.println("Tag number should be integer"); + System.exit(1); + return; + } + if (tagNo < 0 || tagNo >= swf.getTags().size()) { + System.err.println("Tag number does not exist. Tag number should be between 0 and " + (swf.getTags().size() - 1)); + System.exit(1); + } + + if (!tagNumbersToRemove.contains(tagNo)) { + tagNumbersToRemove.add(tagNo); + } + + if (args.isEmpty() || args.peek().startsWith("-")) { + break; + } + } + + Collections.sort(tagNumbersToRemove); + for (int i = tagNumbersToRemove.size() - 1; i >= 0; i--) { + swf.removeTag((int) tagNumbersToRemove.get(i)); + } + + try { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + } catch (IOException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void parseDoc(Stack args) { + String type = null; + String format = null; + String out = null; + String locale = null; + boolean nightMode = false; + while (!args.isEmpty()) { + String arg = args.pop(); + switch (arg) { + case "-night": + nightMode = true; + break; + case "-out": + if (args.isEmpty() || out != null) { + badArguments("doc"); + } + out = args.pop(); + break; + case "-type": + if (args.isEmpty() || type != null) { + badArguments("doc"); + } + type = args.pop(); + break; + case "-format": + if (args.isEmpty() || format != null) { + badArguments("doc"); + } + format = args.pop(); + break; + case "-locale": + if (args.isEmpty() || locale != null) { + badArguments("doc"); + } + locale = args.pop(); + + break; + } + } + + if (format == null) { + format = "html"; + } + if (type == null) { + badArguments("doc"); + } else if (!type.equals("as3.pcode.instructions")) { + badArguments("doc"); + } + + if (!format.equals("html")) { + badArguments("doc"); + } + if (locale != null) { + Locale.setDefault(Locale.forLanguageTag(locale)); + } + + String doc = As3PCodeDocs.getAllInstructionDocs(nightMode); + + PrintStream outStream; + + if (out == null) { + outStream = System.out; + } else { + try { + outStream = new PrintStream(out, "UTF-8"); + } catch (UnsupportedEncodingException | FileNotFoundException ex) { + Logger.getLogger(CommandLineArgumentParser.class.getName()).log(Level.SEVERE, ex.getLocalizedMessage()); + System.exit(1); + return; + } + } + + outStream.print(doc); + } + + private static void parseRemoveCharacter(Stack args, boolean removeDependencies) { + if (args.size() < 3) { + badArguments("removecharacter"); + } + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + while (true) { + String objectToRemove = args.pop(); + + int characterId = 0; + try { + characterId = Integer.parseInt(objectToRemove); + } catch (NumberFormatException nfe) { + System.err.println("CharacterId should be integer"); + badArguments("removecharacter"); + } + if (!swf.getCharacters().containsKey(characterId)) { + System.err.println("CharacterId does not exist"); + System.exit(1); + } + + CharacterTag characterTag = swf.getCharacter(characterId); + swf.removeTag(characterTag, removeDependencies); + + if (args.isEmpty() || args.peek().startsWith("-")) { + break; + } + } + + try { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + } catch (IOException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void parseImportScript(Stack args) { + + String flexLocation = Configuration.flexSdkLocation.get(); + if (Configuration.useFlexAs3Compiler.get() && (flexLocation.isEmpty() || (!new File(flexLocation).exists()))) { + System.err.println("Flex AS3 compiler enabled but Flex SDK path not set"); + System.exit(1); + } + + if (args.size() < 3) { + badArguments("importscript"); + } + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + String scriptsFolder = Path.combine(args.pop(), ScriptExportSettings.EXPORT_FOLDER_NAME); + new AS2ScriptImporter().importScripts(scriptsFolder, swf.getASMs(true)); + new AS3ScriptImporter().importScripts(As3ScriptReplacerFactory.createByConfig(), scriptsFolder, swf.getAS3Packs()); + + try { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + } catch (IOException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void parseCustom(Stack args) { + String[] customParameters = new String[args.size()]; + for (int i = 0; i < customParameters.length; i++) { + customParameters[i] = args.pop(); + } + + SWFDecompilerPlugin.customParameters = customParameters; + } + + private static void loadFiles(String[] fileNames) { + boolean result = true; + for (String fileName : fileNames) { + try { + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, fileName, null); + Main.parseSWF(sourceInfo); + } catch (Exception ex) { + logger.log(Level.SEVERE, null, ex); + result = false; + } + } + + System.exit(result ? 0 : 1); + } + + private static void replaceAS2PCode(String text, ASMSource src) throws IOException, InterruptedException { + System.out.println("Replace AS1/2 PCode"); + if (text.trim().startsWith(Helper.hexData)) { + src.setActionBytes(Helper.getBytesFromHexaText(text)); + } else { + try { + src.setActions(ASMParser.parse(0, true, text, src.getSwf().version, false)); + } catch (ActionParseException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } + } + src.setModified(); + } + + private static void replaceAS2(String as, ASMSource src) throws IOException, InterruptedException { + System.out.println("Replace AS1/2"); + System.out.println("Warning: This feature is EXPERIMENTAL"); + ActionScript2Parser par = new ActionScript2Parser(src.getSwf(), src); + try { + src.setActions(par.actionsFromString(as)); + } catch (ActionParseException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } catch (CompilationException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } + src.setModified(); + } + + private static void replaceAS3PCode(String text, ABC abc, int bodyIndex, Trait trait) throws IOException, InterruptedException { + System.out.println("Replace AS3 PCode"); + if (text.trim().startsWith(Helper.hexData)) { + byte[] data = Helper.getBytesFromHexaText(text); + MethodBody mb = abc.bodies.get(bodyIndex); + mb.setCodeBytes(data); + } else { + try { + AVM2Code acode = ASM3Parser.parse(abc, new StringReader(text), trait, new MissingSymbolHandler() { + //no longer ask for adding new constants + @Override + public boolean missingString(String value) { + return true; + } + + @Override + public boolean missingInt(long value) { + return true; + } + + @Override + public boolean missingUInt(long value) { + return true; + } + + @Override + public boolean missingDouble(double value) { + return true; + } + + @Override + public boolean missingDecimal(Decimal value) { + return true; + } + + @Override + public boolean missingFloat(float value) { + return true; + } + + @Override + public boolean missingFloat4(Float4 value) { + return true; + } + }, abc.bodies.get(bodyIndex), abc.method_info.get(abc.bodies.get(bodyIndex).method_info)); + //acode.getBytes(abc.bodies.get(bodyIndex).getCodeBytes()); + abc.bodies.get(bodyIndex).setCode(acode); + } catch (AVM2ParseException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } + } + ((Tag) abc.parentTag).setModified(true); + } + + private static void replaceAS3(String asp, String as, ScriptPack pack) throws IOException, InterruptedException { + System.out.println("Replaceing: " + asp); + System.out.println("Warning: This feature is EXPERIMENTAL"); + As3ScriptReplacerInterface scriptReplacer = As3ScriptReplacerFactory.createByConfig(); + if (!scriptReplacer.isAvailable()) { + System.err.println("Current script replacer is not available."); + if (scriptReplacer instanceof FFDecAs3ScriptReplacer) { + System.err.println("Current replacer: FFDec"); + final String adobePage = "http://www.adobe.com/support/flashplayer/downloads.html"; + System.err.println("For ActionScript 3 direct editation, a library called \"PlayerGlobal.swc\" needs to be downloaded from Adobe homepage:"); + System.err.println(adobePage); + System.err.println("Download the library called PlayerGlobal(.swc), and place it to directory"); + System.err.println(Configuration.getFlashLibPath().getAbsolutePath()); + } else if (scriptReplacer instanceof MxmlcAs3ScriptReplacer) { + System.err.println("Current replacer: Flex SDK"); + final String flexPage = "http://www.adobe.com/devnet/flex/flex-sdk-download.html"; + System.err.println("For ActionScript 3 direct editation, Flex SDK needs to be download"); + System.err.println(flexPage); + System.err.println("Download FLEX Sdk, unzip it to some directory and set its directory path in the configuration"); + + } else { + + } + System.exit(1); + } + + try { + pack.abc.replaceScriptPack(scriptReplacer, pack, as); + } catch (As3ScriptReplaceException asre) { + for (As3ScriptReplaceExceptionItem item : asre.getExceptionItems()) { + String r = "%error% on line %line%, column %col%, file: %file%".replace("%error%", "" + item.getMessage()); + r = r.replace("%line%", Long.toString(item.getLine())); + r = r.replace("%file%", "" + item.getFile()); + r = r.replace("%col%", "" + item.getCol()); + logger.log(Level.SEVERE, r); + } + System.exit(1); + } + } + + private static void parseInfo(Stack args) throws FileNotFoundException { + File out; + PrintWriter pw = new PrintWriter(System.out); + boolean found = false; + boolean detectBundle = true; + while (!args.isEmpty()) { + String a = args.pop(); + switch (a) { + case "-out": + if (args.isEmpty()) { + badArguments("info"); + } + out = new File(args.pop()); + if (out.isDirectory()) { + logger.log(Level.SEVERE, "File is a directory"); + System.exit(1); + } else { + pw = new PrintWriter(out); + } + break; + case "-nobundle": + detectBundle = false; + break; + default: + SWFBundle bundle; + String sfile = a; + File file = new File(sfile); + try { + + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, sfile, sfile, detectBundle); + bundle = sourceInfo.getBundle(false, SearchMode.ALL); + logger.log(Level.INFO, "Load file: {0}", sourceInfo.getFile()); + + if (bundle != null) { + int subcnt = 0; + for (Entry streamEntry : bundle.getAll().entrySet()) { + InputStream stream = streamEntry.getValue(); + stream.reset(); + CancellableWorker worker = new CancellableWorker() { + @Override + public SWF doInBackground() throws Exception { + final CancellableWorker worker = this; + SWF swf = new SWF(stream, null, streamEntry.getKey(), new ProgressListener() { + @Override + public void progress(int p) { + //... + } + }, Configuration.parallelSpeedUp.get()); + return swf; + } + }; + //loadingDialog.setWroker(worker); + worker.execute(); + + subcnt++; + try { + proccessInfoSWF(file, worker.get(), pw); + } catch (CancellationException | ExecutionException | InterruptedException ex) { + logger.log(Level.WARNING, "Loading SWF {0} was cancelled.", streamEntry.getKey()); + } + } + if (subcnt > 0) { + found = true; + } else { + System.err.println("No SWFs found in \"" + file + "\""); + } + } else { + FileInputStream fis = new FileInputStream(file); + BufferedInputStream inputStream = new BufferedInputStream(fis); + + InputStream fInputStream = inputStream; + CancellableWorker worker = new CancellableWorker() { + @Override + public SWF doInBackground() throws Exception { + final CancellableWorker worker = this; + SWF swf = new SWF(fInputStream, sourceInfo.getFile(), sourceInfo.getFileTitle(), new ProgressListener() { + @Override + public void progress(int p) { + //startWork(AppStrings.translate("work.reading.swf"), p, worker); + } + }, Configuration.parallelSpeedUp.get()); + return swf; + } + }; + + worker.execute(); + + try { + proccessInfoSWF(null, worker.get(), pw); + } catch (CancellationException | ExecutionException | InterruptedException ex) { + logger.log(Level.WARNING, "Loading SWF was cancelled."); + } + found = true; + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Cannot read."); + System.exit(1); + } + } + } + if (!found) { + System.exit(1); + } + System.exit(0); + } + + private static String doubleToString(double d) { + String ds = "" + d; + if (ds.endsWith(".0")) { + ds = ds.substring(0, ds.length() - 2); + } + return ds; + } + + private static void proccessInfoSWF(File bundle, SWF swf, PrintWriter pw) throws IOException { + pw.println("[swf]"); + pw.println("file=" + (bundle == null ? swf.getFile() : bundle + ":" + swf.getFileTitle())); + pw.println("fileSize=" + swf.fileSize); + pw.println("version=" + swf.version); + pw.println("compression=" + swf.compression); + pw.println("gfx=" + swf.gfx); + pw.println("width=" + doubleToString(swf.displayRect.getWidth() / SWF.unitDivisor)); + pw.println("height=" + doubleToString(swf.displayRect.getHeight() / SWF.unitDivisor)); + pw.println("frameCount=" + swf.frameCount); + pw.println("frameRate=" + doubleToString(swf.frameRate)); + for (Tag t : swf.getTags()) { + if (t instanceof SetBackgroundColorTag) { + pw.println("backgroundColor=" + ((SetBackgroundColorTag) t).backgroundColor.toHexRGB()); + } + if (t instanceof ScriptLimitsTag) { + pw.println("maxRecursionDepth=" + ((ScriptLimitsTag) t).maxRecursionDepth); + pw.println("scriptTimeoutSeconds=" + ((ScriptLimitsTag) t).scriptTimeoutSeconds); + } + } + pw.println(); + + pw.println("[tags]"); + pw.println("tagCount=" + swf.getTags().size()); + pw.println("hasEndTag=" + swf.hasEndTag); + pw.println("characterCount=" + (swf.getCharacters().size())); + pw.println("maxCharacterId=" + (swf.getNextCharacterId() - 1)); + pw.println(); + + FileAttributesTag fa = swf.getFileAttributes(); + if (fa != null) { + pw.println("[attributes]"); + pw.println("actionScript3=" + fa.actionScript3); + pw.println("hasMetadata=" + fa.hasMetadata); + pw.println("noCrossDomainCache=" + fa.noCrossDomainCache); + pw.println("useDirectBlit=" + fa.useDirectBlit); + pw.println("useGPU=" + fa.useGPU); + pw.println("useNetwork=" + fa.useNetwork); + pw.println(); + } + + pw.println("[as2]"); + pw.println("scriptsCount=" + swf.getASMs(true).size()); + pw.println(); + + pw.println("[as3]"); + pw.println("ABCtagCount=" + swf.getAbcList().size()); + pw.println("packsCount=" + swf.getAS3Packs().size()); + String dcs = swf.getDocumentClass(); + if (dcs != null) { + if (dcs.contains(".")) { + DottedChain dc = DottedChain.parseWithSuffix(dcs); + pw.println("documentClass=" + dc.toPrintableString(true)); + } else { + pw.println("documentClass=" + IdentifiersDeobfuscation.printIdentifier(true, dcs)); + } + } else { + pw.println("documentClass="); + } + + pw.println(); + pw.flush(); + } + + private static void parseDumpSwf(Stack args) { + if (args.isEmpty()) { + badArguments("dumpswf"); + } + try { + Configuration.dumpTags.set(true); + Configuration.parallelSpeedUp.set(false); + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, args.pop(), null); + Main.parseSWF(sourceInfo); + } catch (Exception ex) { + logger.log(Level.SEVERE, null, ex); + System.exit(1); + } + System.exit(0); + } + + private static void parseDumpAS2(Stack args) { + if (args.isEmpty()) { + badArguments("dumpas2"); + } + File file = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(file)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + Map asms = swf.getASMs(false); + for (String as2 : asms.keySet()) { + System.out.println(as2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void parseEnableDebugging(Stack args) { + if (args.size() < 2) { + badArguments("enabledebugging"); + } + + boolean injectas3 = false; + boolean doPCode = false; + boolean generateSwd = false; + String file = args.pop(); + if (file.equals("-generateswd")) { + if (args.size() < 2) { + badArguments("enabledebugging"); + } + file = args.pop(); + generateSwd = true; + } + if (file.equals("-injectas3")) { + if (args.size() < 2) { + badArguments("enabledebugging"); + } + file = args.pop(); + injectas3 = true; + } + if (file.equals("-pcode")) { + if (args.size() < 2) { + badArguments("enabledebugging"); + } + doPCode = true; + file = args.pop(); + } + String outfile = args.pop(); + try { + System.out.print("Working..."); + FileInputStream fis = new FileInputStream(file); + SWF swf = new SWF(fis, Configuration.parallelSpeedUp.get()); + fis.close(); + if (swf.isAS3()) { + swf.enableDebugging(injectas3, new File(outfile).getParentFile(), doPCode); + } else { + swf.enableDebugging(); + } + FileOutputStream fos = new FileOutputStream(outfile); + swf.saveTo(fos); + fos.close(); + if (!swf.isAS3()) { + if (generateSwd) { + fis = new FileInputStream(outfile); + swf = new SWF(fis, Configuration.parallelSpeedUp.get()); + fis.close(); + String outSwd = outfile; + if (outSwd.toLowerCase(Locale.ENGLISH).endsWith(".swf")) { + outSwd = outSwd.substring(0, outSwd.length() - 4) + ".swd"; + } else { + outSwd = outSwd + ".swd"; + } + if (doPCode) { + if (!swf.generatePCodeSwdFile(new File(outSwd), new HashMap<>())) { + System.err.println("Generating SWD failed"); + } + } else if (!swf.generateSwdFile(new File(outSwd), new HashMap<>())) { + System.err.println("Generating SWD failed"); + } + } + } else if (generateSwd) { + System.err.println("WARNING: Cannot generate SWD for AS3 file"); + } + System.out.println("OK"); + } catch (FileNotFoundException ex) { + logger.log(Level.SEVERE, "Cannot read {0}", file); + System.exit(1); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Reading error {0}", file); + System.exit(2); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "Cancelled {0}", file); + System.exit(3); + } + + System.out.println("Finished"); + System.exit(0); + } + + private static void parseDumpAS3(Stack args) { + if (args.isEmpty()) { + badArguments("dumpas3"); + } + File file = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(file)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + List packs = swf.getAS3Packs(); + for (ScriptPack entry : packs) { + System.out.println(entry.getClassPath().toString() + " " + entry.scriptIndex); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static FilenameFilter getSwfFilter() { + return (File dir, String name) -> name.toLowerCase(Locale.ENGLISH).endsWith(".swf"); + } + + private static E enumFromStr(String str, Class cls) { + E[] vals = cls.getEnumConstants(); + if (str == null) { + return vals[0]; + } + for (E e : vals) { + if (e.toString().toLowerCase(Locale.ENGLISH).replace("_", "").equals(str.toLowerCase(Locale.ENGLISH).replace("_", ""))) { + return e; + } + } + return vals[0]; + } + + private static interface SwfAction { + + public void swfAction(SWF swf, OutputStream stdout) throws IOException; + } + + private static void processReadSWF(File inFile, File stdOutFile, SwfAction action) { + OutputStream stdout = null; + + try { + if (stdOutFile != null) { + try { + stdout = new FileOutputStream(stdOutFile); + } catch (FileNotFoundException ex) { + System.err.println("File not found: " + ex.getMessage()); + System.exit(1); + } + } else { + stdout = System.out; + } + + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + action.swfAction(swf, stdout); + } catch (FileNotFoundException ex) { + System.err.println("File not found: " + ex.getMessage()); + System.exit(1); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + System.exit(1); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error", ex); + System.exit(1); + } + } finally { + if (stdOutFile != null) { + if (stdout != null) { + try { + stdout.close(); + } catch (IOException ex) { + //ignore + } + } + } + } + } + + private static void processModifySWF(File inFile, File outFile, File stdOutFile, SwfAction action) { + + OutputStream stdout = null; + + try { + if (stdOutFile != null) { + try { + stdout = new FileOutputStream(stdOutFile); + } catch (FileNotFoundException ex) { + System.err.println("File not found: " + ex.getMessage()); + System.exit(1); + } + } else { + stdout = System.out; + } + + File tmpFile = null; + if (inFile.equals(outFile)) { + try { + tmpFile = File.createTempFile("ffdec_modify_", ".swf"); + outFile = tmpFile; + } catch (IOException ex) { + System.err.println("Unable to create temp file"); + System.exit(1); + } + } + try (FileInputStream is = new FileInputStream(inFile); + FileOutputStream fos = new FileOutputStream(outFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + action.swfAction(swf, stdout); + swf.saveTo(fos); + } catch (FileNotFoundException ex) { + System.err.println("File not found: " + ex.getMessage()); + System.exit(1); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + System.exit(1); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error", ex); + System.exit(1); + } + + if (tmpFile != null) { + try { + if (!inFile.delete()) { + System.err.println("Cannot overwrite original file"); + System.exit(1); + } + if (!tmpFile.renameTo(inFile)) { + System.err.println("Cannot rename tempfile to original file"); + System.exit(1); + } + tmpFile = null; + System.out.println(inFile + " overwritten."); + } finally { + if (tmpFile != null && tmpFile.exists()) { + tmpFile.delete(); + } + } + } + } finally { + if (stdOutFile != null) { + if (stdout != null) { + try { + stdout.close(); + } catch (IOException ex) { + //ignore + } + } + } + } + } +}