/* * Copyright (C) 2010-2023 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.SWFCompression; import com.jpexs.decompiler.flash.OpenableSourceInfo; import com.jpexs.decompiler.flash.SearchMode; import com.jpexs.decompiler.flash.SwfOpenException; import com.jpexs.decompiler.flash.ValueTooLargeException; 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.SymbolClassExporter; 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.ExeExportMode; 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.SymbolClassExportMode; 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.SymbolClassExportSettings; 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.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.OpenableList; 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.Graphics; import java.awt.print.PageFormat; import java.awt.print.Paper; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; 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.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; 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 com.jpexs.decompiler.flash.Bundle; import com.jpexs.decompiler.flash.exporters.DualPdfGraphics2D; import com.jpexs.decompiler.flash.exporters.Font4Exporter; import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; import com.jpexs.decompiler.flash.exporters.modes.Font4ExportMode; import com.jpexs.decompiler.flash.exporters.settings.Font4ExportSettings; import com.jpexs.decompiler.flash.gui.translator.Translator; import com.jpexs.decompiler.flash.importers.MovieImporter; import com.jpexs.decompiler.flash.importers.SoundImporter; import com.jpexs.decompiler.flash.importers.SymbolClassImporter; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.base.HasSeparateAlphaChannel; import com.jpexs.decompiler.flash.tags.base.RenderContext; import com.jpexs.decompiler.flash.tags.base.SoundImportException; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.tags.base.UnsupportedSamplingRateException; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.helpers.SerializableImage; import gnu.jpdf.PDFGraphics; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Point; /** * * @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(" font4 - DefineFont4 (Default format: CFF)"); 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(" symbolClass - Symbol-Class mapping (Default format: CSV)"); 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(" image:png_gif_jpeg_alpha - PNG/GIF/JPEG+ALPHA 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(" font4:cff - CFF format for DefineFont4"); 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("exportembed")) { out.println(" " + (cnt++) + ") -exportembed"); out.println(" ...Allows exporting embedded assets via [Embed tag]"); out.println(" Use in combination with -export -format script:as"); } 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("decrypt")) { out.println(" " + (cnt++) + ") -decrypt "); out.println(" ...Decrypts HARMAN Air encrypted file 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, Sound 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"); out.println(" ...use -1 as characterId to replace main timeline SoundStreamHead"); out.println(" " + (cnt++) + ") -replace "); out.println(" ... same as -replace command, but the rest of arguments is read as lines from a text file "); } 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 character 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 character 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("importsymbolclass")) { out.println(" " + (cnt++) + ") -importSymbolClass "); out.println(" ...imports Symbol-Class mapping to and saves the result to "); } if (filter == null || filter.equals("importmovies")) { out.println(" " + (cnt++) + ") -importMovies "); out.println(" ...imports movies to and saves the result to "); } if (filter == null || filter.equals("importsounds")) { out.println(" " + (cnt++) + ") -importSounds "); out.println(" ...imports sounds to and saves the result to "); } if (filter == null || filter.equals("importshapes")) { out.println(" " + (cnt++) + ") -importShapes [nofill] "); out.println(" ...imports shapes to and saves the result to "); } if (filter == null || filter.equals("importimages")) { out.println(" " + (cnt++) + ") -importImages "); out.println(" ...imports images to and saves the result to "); } if (filter == null || filter.equals("importtext")) { out.println(" " + (cnt++) + ") -importText "); out.println(" ...imports texts to and saves the result to "); } 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: traps/2/max, 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"); } if (filter == null || filter.equals("swf2exe")) { out.println(" " + (cnt++) + ") -swf2exe "); out.println(" ...export SWF to executable file"); out.println(" ...: wrapper|projector_win||projector_mac|projector_linux"); } if (filter == null || filter.equals("charset")) { out.println(" " + (cnt++) + ") -charset "); out.println(" ...sets desired character set for reading/writing SWF files with SWF version <= 5"); out.println(" (use in combination with other commands)"); } if (filter == null || filter.equals("air")) { out.println(" " + (cnt++) + ") -air"); out.println(" ...use AIR (airglobal.swc) for AS3 compilation instead of playerglobal.swc"); out.println(" (use in combination with other commands)"); } printCmdLineUsageExamples(out, filter); System.out.println("You can use special value \"/dev/stdin\" for input files to read data from standard input (even on Windows)"); } 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 (filter == null || filter.equals("swf2exe")) { out.println(PREFIX + "-swf2exe wrapper result.exe myfile.swf"); } 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; String charset = Charset.defaultCharset().name(); boolean cliMode = false; boolean air = false; boolean exportEmbed = 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 "-air": air = true; break; case "-cli": cliMode = true; break; case "-selectid": selectionIds = parseSelect(args); break; case "-select": selection = parseSelect(args); break; case "-selectclass": selectionClasses = parseSelectClass(args); break; case "-exportembed": exportEmbed = true; break; case "-zoom": zoom = parseZoom(args); break; case "-format": format = parseFormat(args); break; case "-charset": charset = parseCharset(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, charset); System.exit(0); 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("translator")) { Translator.main(new String[]{}); } else if (command.equals("swf2exe")) { parseSwf2Exe(args, charset); System.exit(0); } else if (command.equals("abcmerge")) { parseAbcMerge(args, charset); System.exit(0); } else if (command.equals("swf2swc")) { parseSwf2Swc(args, charset); System.exit(0); } else if (command.equals("linkreport")) { parseLinkReport(selectionClasses, args, charset); System.exit(0); } else if (command.equals("getinstancemetadata")) { parseGetInstanceMetadata(args, charset); System.exit(0); } else if (command.equals("setinstancemetadata")) { parseSetInstanceMetadata(args, charset); System.exit(0); } else if (command.equals("removeinstancemetadata")) { parseRemoveInstanceMetadata(args, charset); System.exit(0); } 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, charset, exportEmbed); System.exit(0); } else if (command.equals("compress")) { parseCompress(args); System.exit(0); } else if (command.equals("decompress")) { parseDecompress(args); System.exit(0); } else if (command.equals("decrypt")) { parseDecrypt(args); System.exit(0); } else if (command.equals("swf2xml")) { parseSwf2Xml(args, charset); System.exit(0); } else if (command.equals("xml2swf")) { parseXml2Swf(args, charset); System.exit(0); } else if (command.equals("extract")) { parseExtract(args); System.exit(0); } else if (command.equals("memorysearch")) { parseMemorySearch(args); System.exit(0); } else if (command.equals("deobfuscate")) { parseDeobfuscate(args, charset); System.exit(0); } else if (command.equals("renameinvalididentifiers")) { parseRenameInvalidIdentifiers(args); System.exit(0); } else if (command.equals("dumpswf")) { parseDumpSwf(args); System.exit(0); } else if (command.equals("dumpas2")) { parseDumpAS2(args, charset); System.exit(0); } else if (command.equals("dumpas3")) { parseDumpAS3(args, charset); System.exit(0); } else if (command.equals("enabledebugging")) { parseEnableDebugging(args, charset); System.exit(0); } else if (command.equals("flashpaper2pdf")) { parseFlashPaperToPdf(selection, zoom, args, charset); System.exit(0); } else if (command.equals("replace")) { parseReplace(args, charset, air); System.exit(0); } else if (command.equals("replacealpha")) { parseReplaceAlpha(args, charset); System.exit(0); } else if (command.equals("replacecharacter")) { parseReplaceCharacter(args, charset); System.exit(0); } else if (command.equals("replacecharacterid")) { parseReplaceCharacterId(args, charset); System.exit(0); } else if (command.equals("convert")) { parseConvert(args, charset); System.exit(0); } else if (command.equals("remove")) { parseRemove(args, charset); System.exit(0); } else if (command.equals("removecharacter")) { parseRemoveCharacter(args, false, charset); System.exit(0); } else if (command.equals("removecharacterwithdependencies")) { parseRemoveCharacter(args, true, charset); System.exit(0); } else if (command.equals("doc")) { parseDoc(args); } else if (command.equals("importsymbolclass")) { parseImportSymbolClass(args, charset); System.exit(0); } else if (command.equals("importmovies")) { parseImportMovies(args, charset); System.exit(0); } else if (command.equals("importsounds")) { parseImportSounds(args, charset); System.exit(0); } else if (command.equals("importshapes")) { parseImportShapes(args, charset); System.exit(0); } else if (command.equals("importimages")) { parseImportImages(args, charset); System.exit(0); } else if (command.equals("importtext")) { parseImportText(args, charset); System.exit(0); } else if (command.equals("importscript")) { parseImportScript(args, charset, air); System.exit(0); } else if (command.equals("as3compiler")) { ActionScript3Parser.compile(null /*?*/, args.pop(), args.pop(), 0, 0); } else if (nextParam.equals("--debugtool")) { parseDebugTool(args, charset); } else if (nextParam.equals("--compareresources")) { parseCompareResources(args); System.exit(0); } else if (nextParam.equals("--resourcedates")) { parseResourceDates(args); System.exit(0); } 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); System.exit(0); } 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 parseSwf2Exe(Stack args, String charset) { if (args.size() != 3) { badArguments("swf2exe"); } final String type = args.pop(); final File outFile = new File(args.pop()); final File swfFile = new File(args.pop()); ExeExportMode exportMode = enumFromStr(type, ExeExportMode.class); processReadSWF(swfFile, null, (SWF swf, OutputStream stdout) -> { Main.saveFileToExe(swf, exportMode, outFile); }, charset); } private static void parseAbcMerge(Stack args, String charset) { 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)); } } }, charset); } private static void parseSwf2Swc(Stack args, String charset) { 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); } }, charset); } private static void parseLinkReport(List selectionClasses, Stack args, String charset) { 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"); } }, charset); } private static void parseGetInstanceMetadata(Stack args, String charset) { 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; } }, charset); } private static void parseSetInstanceMetadata(Stack args, String charset) { 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; } }, charset); } private static void parseRemoveInstanceMetadata(Stack args, String charset) { 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; } }, charset); } 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 charset) { 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) { OpenableSourceInfo sourceInfo = new OpenableSourceInfo(null, file.getAbsolutePath(), file.getName()); try { SWF swf = new SWF(new FileInputStream(file), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get(), charset); swf.openableList = new OpenableList(); swf.openableList.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) { OpenableSourceInfo sourceInfo = new OpenableSourceInfo(null, file.getAbsolutePath(), file.getName()); try { SWF swf = new SWF(new FileInputStream(file), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get(), charset); swf.openableList = new OpenableList(); swf.openableList.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, String charset, boolean exportEmbed) { if (args.size() < 3) { badArguments("export"); } String[] validExportItems = new String[]{ "script", "script_as2", "script_as3", "image", "shape", "morphshape", "movie", "font", "font4", "frame", "sprite", "button", "sound", "binarydata", "symbolclass", "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()); String inFileOrFolderStr = args.pop(); File inFileOrFolder = new File(inFileOrFolderStr); if (!StdInAwareFileInputStream.STDIN_PATH.equals(inFileOrFolderStr) && !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()); } OpenableSourceInfo sourceInfo = new OpenableSourceInfo(null, inFile.getAbsolutePath(), inFile.getName()); SWF swf; try { swf = new SWF(new BufferedInputStream(new StdInAwareFileInputStream(inFile)), sourceInfo.getFile(), sourceInfo.getFileTitle(), null, Configuration.parallelSpeedUp.get(), false, true, charset); } 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.openableList = new OpenableList(); swf.openableList.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("font4")) { System.out.println("Exporting DefineFont4s..."); new Font4Exporter().exportFonts(handler, outDir + (multipleExportTypes ? File.separator + FontExportSettings.EXPORT_FOLDER_NAME : ""), new ReadOnlyTagList(extags), new Font4ExportSettings(enumFromStr(formats.get("font4"), Font4ExportMode.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("symbolclass")) { System.out.println("Exporting symbolClass..."); new SymbolClassExporter().exportNames(handler, outDir + (multipleExportTypes ? File.separator + SymbolClassExportSettings.EXPORT_FOLDER_NAME : ""), new ReadOnlyTagList(extags), new SymbolClassExportSettings(enumFromStr(formats.get("symbolclass"), SymbolClassExportMode.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, exportEmbed, 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, String charset) { if (args.size() < 3) { badArguments("deobfuscate"); } String mode = args.pop(); DeobfuscationLevel lev; switch (mode) { case "controlflow": case "3": System.err.println("WARNING: Control flow level(3) is not implemented - it is the same as remove traps (2) level."); lev = DeobfuscationLevel.LEVEL_REMOVE_TRAPS; break; case "traps": case "max": 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 (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile); FileOutputStream fos = new FileOutputStream(outFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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 StdInAwareFileInputStream(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 parseDecrypt(Stack args) { if (args.size() < 2) { badArguments("decrypt"); } boolean result = false; try { try (InputStream fis = new BufferedInputStream(new StdInAwareFileInputStream(args.pop())); OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { result = SWF.decrypt(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 parseDecompress(Stack args) { if (args.size() < 2) { badArguments("decompress"); } boolean result = false; try { try (InputStream fis = new BufferedInputStream(new StdInAwareFileInputStream(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, String charset) { if (args.size() < 2) { badArguments("swf2xml"); } try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(args.pop())) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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); } } private static void parseXml2Swf(Stack args, String charset) { if (args.size() < 2) { badArguments("xml2swf"); } try { SWF swf = new SWF(charset); try (StdInAwareFileInputStream in = new StdInAwareFileInputStream(args.pop())) { new SwfXmlImporter().importSwf(swf, in); } try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(args.pop())))) { swf.saveTo(fos); } } catch (IOException ex) { logger.log(Level.SEVERE, null, ex); } } 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 { OpenableSourceInfo sourceInfo = new OpenableSourceInfo(null, fileName, null); if (!sourceInfo.isBundle()) { System.err.println("Error: should be a bundle. (ZIP or non SWF binary file)"); System.exit(1); } Bundle 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); } } 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."); } } 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 StdInAwareFileInputStream(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 String parseCharset(Stack args) { if (args.size() < 1) { badArguments("charset"); } String charsetName = args.pop(); boolean charsetValid = false; try { if (Charset.isSupported(charsetName)) { charsetValid = true; } } catch (Exception ex) { charsetValid = false; } if (!charsetValid) { System.err.println("Specified charset is not valid"); badArguments("charset"); } return charsetName; } 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, String charset) { if (args.size() < 2) { badArguments("flashpaper2pdf"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); printHeader(); try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { PDFJob job = null; SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); int totalPages = 0; for (Tag t : swf.getTags()) { if (t instanceof DefineSpriteTag) { DefineSpriteTag ds = (DefineSpriteTag) t; String exportName = ds.getExportName(); if (exportName != null && exportName.matches("^page[0-9]+$")) { int pageNum = Integer.parseInt(exportName.substring(4)); if (pageNum > totalPages) { totalPages = pageNum; } } } } PageFormat pf = new PageFormat(); pf.setOrientation(PageFormat.PORTRAIT); Paper p = new Paper(); 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 + "..."); Timeline tim = ds.getTimeline(); RECT rect = tim.displayRect; double w = (rect.getWidth() * zoom / SWF.unitDivisor); double h = (rect.getHeight() * zoom / SWF.unitDivisor); p.setSize(w, h); pf.setPaper(p); Matrix m = new Matrix(); m.translate(-rect.Xmin * zoom, -rect.Ymin * zoom); m.scale(zoom); Matrix transformation = m; Map existingFonts = new HashMap<>(); int fframe = 0; final Graphics2D g = (Graphics2D) job.getGraphics(pf); SerializableImage image = new SerializableImage((int) w + 1, (int) h + 1, SerializableImage.TYPE_INT_ARGB_PRE) { private Graphics2D compositeGraphics; @Override public Graphics getGraphics() { if (compositeGraphics != null) { return compositeGraphics; } final Graphics2D parentGraphics = (Graphics2D) super.getGraphics(); compositeGraphics = new DualPdfGraphics2D(parentGraphics, (PDFGraphics) g, existingFonts); return compositeGraphics; } @Override public void fillTransparent() { } }; int imgWidth = (int) (rect.getWidth() * zoom / SWF.unitDivisor) + 1; int imgHeight = (int) (rect.getHeight() * zoom / SWF.unitDivisor) + 1; RenderContext renderContext = new RenderContext(); renderContext.cursorPosition = new Point(-1, -1); renderContext.mouseButton = 0; renderContext.stateUnderCursor = new ArrayList<>(); try { tim.toImage(fframe, fframe, renderContext, image, image, false, m, new Matrix(), m, null, zoom, true, new ExportRectangle(rect), m, true, Timeline.DRAW_MODE_ALL, 0, true); } catch (Exception ex) { ex.printStackTrace(); } g.dispose(); if (Thread.currentThread().isInterrupted()) { return; } 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); } } private static void parseReplace(Stack args, String charset, boolean air) { if (args.size() < 3) { badArguments("replace"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); if (args.size() == 1) { System.out.println("Replacing - only single argument passed, taking it as file to load replacements from"); try { List lines = Files.readAllLines(Paths.get(args.pop()), StandardCharsets.UTF_8); Collections.reverse(lines); args.clear(); args.addAll(lines); } catch (IOException e) { System.err.println("I/O Error during reading replacements file"); System.exit(1); } if (args.isEmpty()) { System.err.println("Replacements file is empty."); System.exit(1); } } try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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"); } SoundStreamHeadTypeTag soundStreamHead = null; CharacterTag characterTag = null; if (characterId == -1) { //replacing soundstreamhead on main timeline } else if (swf.getCharacters().containsKey(characterId)) { characterTag = swf.getCharacter(characterId); } else { System.err.println("CharacterId does not exist"); System.exit(1); } String repFile = args.pop(); byte[] data = Helper.readFile(repFile); String ext = Path.getExtension(repFile); int soundFormat = SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN; if (".mp3".equals(ext)) { soundFormat = SoundFormat.FORMAT_MP3; } if ((characterTag == null || (characterTag instanceof DefineSpriteTag)) && (".wav".equals(ext) || ".mp3".equals(ext))) { ReadOnlyTagList tags = (characterTag == null) ? swf.getTags() : ((DefineSpriteTag) characterTag).getTags(); for (Tag t : tags) { if (t instanceof SoundStreamHeadTypeTag) { soundStreamHead = (SoundStreamHeadTypeTag) t; break; } } if (soundStreamHead == null) { System.err.println((characterTag == null ? "Main timeline " : "DefineSprite(" + characterId + ")") + " does not contain any SoundStreamHead"); System.exit(1); } boolean ok = false; try { ok = new SoundImporter().importSoundStream(soundStreamHead, new ByteArrayInputStream(data), soundFormat); } catch (UnsupportedSamplingRateException usre) { List supportedRatesStr = new ArrayList<>(); for (int i : usre.getSupportedRates()) { supportedRatesStr.add("" + i); } System.err.println("Import FAILED. Input file has unsupported sampling rate (" + usre.getSoundRate() + "). Supported rates for this sound format: " + String.join(", ", supportedRatesStr) + "."); System.exit(2); } if (!ok) { System.err.println("Import FAILED. Maybe unsuppoted media type? Only MP3 and uncompressed WAV are available."); System.exit(1); } } else 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; boolean ok = false; SoundImporter soundImporter = new SoundImporter(); try { ok = soundImporter.importSound(st, new ByteArrayInputStream(data), soundFormat); } catch (UnsupportedSamplingRateException usre) { List supportedRatesStr = new ArrayList<>(); for (int i : usre.getSupportedRates()) { supportedRatesStr.add("" + i); } System.err.println("Import FAILED. Input file has unsupported sampling rate (" + usre.getSoundRate() + "). Supported rates for this sound format: " + String.join(", ", supportedRatesStr) + "."); System.exit(2); } catch (SoundImportException sie) { ok = false; } if (!ok) { System.err.println("Import FAILED. Maybe unsuppoted media type? Only MP3 and uncompressed WAV are available."); System.exit(1); } } else if (characterTag instanceof DefineVideoStreamTag) { DefineVideoStreamTag movie = (DefineVideoStreamTag) characterTag; try { new MovieImporter().importMovie(movie, data); } catch (IOException iex) { System.err.println("Import FAILED: " + iex.getMessage()); 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, air); } 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, String charset) { if (args.size() < 4) { badArguments("replacealpha"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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 HasSeparateAlphaChannel) { 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, String charset) { if (args.size() < 4) { badArguments("replacecharacter"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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, String charset) { if (args.size() < 3) { badArguments("replacecharacterid"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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, String charset) { if (args.size() < 4) { badArguments("convert"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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, String charset) { if (args.size() < 3) { badArguments("remove"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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, String charset) { if (args.size() < 3) { badArguments("removecharacter"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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, null); 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 parseImportSymbolClass(Stack args, String charset) { if (args.size() < 3) { badArguments("importsymbolclass"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); String selFile = args.pop(); File importFile = new File(Path.combine(selFile, SymbolClassExporter.SYMBOL_CLASS_EXPORT_FILENAME)); SymbolClassImporter importer = new SymbolClassImporter(); if (!importFile.exists()) { System.err.println("Symbol class file " + importFile + " does not exist."); System.exit(1); } importer.importSymbolClasses(importFile, swf); try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { swf.saveTo(fos); } } catch (IOException | InterruptedException e) { System.err.println("I/O error during writing"); System.exit(2); } } private static void parseImportShapes(Stack args, String charset) { if (args.size() < 3) { badArguments("importshapes"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); boolean noFill = false; if (args.size() > 1) { if (args.pop().equals("nofill")) { noFill = true; } else { badArguments("importshapes"); } } try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); System.out.println("Source file opened"); String selFile = args.pop(); File shapesDir = new File(Path.combine(selFile, ShapeExportSettings.EXPORT_FOLDER_NAME)); if (shapesDir.exists()) { System.out.println("Using the directory: " + shapesDir.getAbsolutePath()); } else { shapesDir = new File(selFile); } if (!shapesDir.exists()) { System.err.println("Shapes directory does not exist: " + shapesDir.getAbsolutePath()); System.exit(1); } ShapeImporter shapeImporter = new ShapeImporter(); int shapeCount = shapeImporter.bulkImport(shapesDir, swf, noFill, true); System.out.println("Writing outfile"); try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { swf.saveTo(fos); } System.out.println("" + shapeCount + " shapes successfully imported"); } catch (IOException | InterruptedException e) { System.err.println("I/O error during writing"); System.exit(2); } } private static void parseImportMovies(Stack args, String charset) { if (args.size() < 3) { badArguments("importmovies"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); System.out.println("Source file opened"); String selFile = args.pop(); File moviesDir = new File(Path.combine(selFile, MovieExportSettings.EXPORT_FOLDER_NAME)); if (moviesDir.exists()) { System.out.println("Using the directory: " + moviesDir.getAbsolutePath()); } else { moviesDir = new File(selFile); } if (!moviesDir.exists()) { System.err.println("Movies directory does not exist: " + moviesDir.getAbsolutePath()); System.exit(1); } MovieImporter movieImporter = new MovieImporter(); int movieCount = movieImporter.bulkImport(moviesDir, swf, true); System.out.println("Writing outfile"); try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { swf.saveTo(fos); } System.out.println("" + movieCount + " movies successfully imported"); } catch (IOException | InterruptedException e) { System.err.println("I/O error during writing"); System.exit(2); } } private static void parseImportSounds(Stack args, String charset) { if (args.size() < 3) { badArguments("importsounds"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); System.out.println("Source file opened"); String selFile = args.pop(); File soundsDir = new File(Path.combine(selFile, SoundExportSettings.EXPORT_FOLDER_NAME)); if (soundsDir.exists()) { System.out.println("Using the directory: " + soundsDir.getAbsolutePath()); } else { soundsDir = new File(selFile); } if (!soundsDir.exists()) { System.err.println("Sounds directory does not exist: " + soundsDir.getAbsolutePath()); System.exit(1); } SoundImporter soundImporter = new SoundImporter(); int soundCount = soundImporter.bulkImport(soundsDir, swf, true); System.out.println("Writing outfile"); try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { swf.saveTo(fos); } System.out.println("" + soundCount + " sounds successfully imported"); } catch (IOException | InterruptedException e) { System.err.println("I/O error during writing"); System.exit(2); } } private static void parseImportImages(Stack args, String charset) { if (args.size() < 3) { badArguments("importimages"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); System.out.println("Source file opened"); String selFile = args.pop(); File imagesDir = new File(Path.combine(selFile, ImageExportSettings.EXPORT_FOLDER_NAME)); if (imagesDir.exists()) { System.out.println("Using the directory: " + imagesDir.getAbsolutePath()); } else { imagesDir = new File(selFile); } if (!imagesDir.exists()) { System.err.println("Images directory does not exist: " + imagesDir.getAbsolutePath()); System.exit(1); } ImageImporter imageImporter = new ImageImporter(); int imageCount = imageImporter.bulkImport(imagesDir, swf, true); System.out.println("Writing outfile"); try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { swf.saveTo(fos); } System.out.println("" + imageCount + " images successfully imported"); } catch (IOException | InterruptedException e) { System.err.println("I/O error during writing"); System.exit(2); } } private static void parseImportText(Stack args, String charset) { if (args.size() < 3) { badArguments("importtext"); } File inFile = new File(args.pop()); File outFile = new File(args.pop()); try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); String selFile = args.pop(); boolean textsFolderExists = new File(Path.combine(selFile, TextExportSettings.EXPORT_FOLDER_NAME)).exists(); File textsFile = new File(Path.combine(selFile, TextExportSettings.EXPORT_FOLDER_NAME, TextExporter.TEXT_EXPORT_FILENAME_FORMATTED)); if (textsFolderExists) { System.out.println("Using the directory: " + new File(Path.combine(selFile, TextExportSettings.EXPORT_FOLDER_NAME)).getAbsolutePath()); } else { textsFile = new File(Path.combine(selFile, TextExporter.TEXT_EXPORT_FILENAME_FORMATTED)); } TextImporter textImporter = new TextImporter(new MissingCharacterHandler() { @Override public boolean getIgnoreMissingCharacters() { return true; } @Override public boolean handle(TextTag textTag, final FontTag font, final char character) { String fontName = font.getSwf().sourceFontNamesMap.get(font.getFontId()); if (fontName == null) { fontName = font.getFontName(); } final Font f = FontTag.getInstalledFontsByName().get(fontName); if (f == null || !f.canDisplay(character)) { String msg = AppStrings.translate("error.font.nocharacter").replace("%char%", "" + character); logger.log(Level.SEVERE, "{0} FontId: {1} TextId: {2}", new Object[]{msg, font.getCharacterId(), textTag.getCharacterId()}); return false; } return font.addCharacter(character, f); } }, new TextImportErrorHandler() { private String getTextTagInfo(TextTag textTag) { StringBuilder ret = new StringBuilder(); if (textTag != null) { ret.append(" TextId: ").append(textTag.getCharacterId()).append(" (").append(String.join(", ", textTag.getTexts())).append(")"); } return ret.toString(); } @Override public boolean handle(TextTag textTag) { String msg = AppStrings.translate("error.text.import"); logger.log(Level.SEVERE, "{0}{1}", new Object[]{msg, getTextTagInfo(textTag)}); return true; } @Override public boolean handle(TextTag textTag, String message, long line) { String msg = AppStrings.translate("error.text.invalid.continue").replace("%text%", message).replace("%line%", Long.toString(line)); logger.log(Level.SEVERE, "{0}{1}", new Object[]{msg, getTextTagInfo(textTag)}); return true; } }); // try to import formatted texts if (textsFile.exists()) { textImporter.importTextsSingleFileFormatted(textsFile, swf); } else { textsFile = new File(Path.combine(selFile, TextExportSettings.EXPORT_FOLDER_NAME, TextExporter.TEXT_EXPORT_FILENAME_PLAIN)); if (!textsFolderExists) { textsFile = new File(Path.combine(selFile, TextExporter.TEXT_EXPORT_FILENAME_PLAIN)); } // try to import plain texts if (textsFile.exists()) { textImporter.importTextsSingleFile(textsFile, swf); } else { textImporter.importTextsMultipleFiles(selFile, swf); } } try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { swf.saveTo(fos); } } catch (IOException | InterruptedException e) { System.err.println("I/O error during writing"); System.exit(2); } } private static void parseImportScript(Stack args, String charset, boolean air) { 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 (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); String baseFolder = args.pop(); String scriptsFolder = Path.combine(baseFolder, ScriptExportSettings.EXPORT_FOLDER_NAME); if (new File(scriptsFolder).exists()) { System.out.println("Using the directory: " + new File(scriptsFolder).getAbsolutePath()); } else { scriptsFolder = baseFolder; } new AS2ScriptImporter().importScripts(scriptsFolder, swf.getASMs(true)); new AS3ScriptImporter().importScripts(As3ScriptReplacerFactory.createByConfig(air), 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 { OpenableSourceInfo sourceInfo = new OpenableSourceInfo(null, fileName, null); Main.parseOpenable(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, src.getSwf().getCharset())); } 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, src.getSwf().getCharset())); } catch (ValueTooLargeException ex) { System.err.println("Script or some of its functions are too large"); System.exit(1); } 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, boolean air) throws IOException, InterruptedException { System.out.println("Replacing: " + asp); System.out.println("Warning: This feature is EXPERIMENTAL"); As3ScriptReplacerInterface scriptReplacer = As3ScriptReplacerFactory.createByConfig(air); 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, String charset) 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: Bundle bundle; String sfile = a; File file = new File(sfile); try { OpenableSourceInfo sourceInfo = new OpenableSourceInfo(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(), charset); 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 { StdInAwareFileInputStream fis = new StdInAwareFileInputStream(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(), charset); 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); } } 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); OpenableSourceInfo sourceInfo = new OpenableSourceInfo(null, args.pop(), null); Main.parseOpenable(sourceInfo); } catch (Exception ex) { logger.log(Level.SEVERE, null, ex); System.exit(1); } } private static void parseDumpAS2(Stack args, String charset) { if (args.isEmpty()) { badArguments("dumpas2"); } File file = new File(args.pop()); try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(file)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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, String charset) { 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..."); StdInAwareFileInputStream fis = new StdInAwareFileInputStream(file); SWF swf = new SWF(fis, Configuration.parallelSpeedUp.get(), charset); 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 StdInAwareFileInputStream(outfile); swf = new SWF(fis, Configuration.parallelSpeedUp.get(), charset); 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"); } private static void parseDumpAS3(Stack args, String charset) { if (args.isEmpty()) { badArguments("dumpas3"); } File file = new File(args.pop()); try { try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(file)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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, String charset) { 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 (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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, String charset) { 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 (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile); FileOutputStream fos = new FileOutputStream(outFile)) { SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); 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 } } } } } }