diff --git a/CHANGELOG.md b/CHANGELOG.md index c4f5f6661..cd2db1316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. - Updated Flash player to SWF version map - Harman AIR 51 float support compatibility - FlashDevelop project export - option to export AIR project (select correct type in the file save dialog) +- FLA/FlashDevelop/IDEA export - A link to all classes (sound, font, images) is added so no class is missed during compilation ### Fixed - [#2266] StartSound/2 and VideoFrame tags, classNames not taken as dependencies (needed chars) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DecompilerPool.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DecompilerPool.java index b71071f3b..943d114c0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DecompilerPool.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DecompilerPool.java @@ -133,7 +133,7 @@ public class DecompilerPool { } boolean parallel = Configuration.parallelSpeedUp.get(); HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); - pack.toSource(abcIndex, writer, script == null ? null : script.traits.traits, new ConvertData(), ScriptExportMode.AS, parallel, false); + pack.toSource(abcIndex, writer, script == null ? null : script.traits.traits, new ConvertData(), ScriptExportMode.AS, parallel, false, false); writer.finishHilights(); HighlightedText result = new HighlightedText(writer); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ScriptPack.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ScriptPack.java index 5eeb1c99f..cea8e6d8b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ScriptPack.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ScriptPack.java @@ -297,9 +297,11 @@ public class ScriptPack extends AS3ClassTreeItem { * @param convertData Convert data * @param exportMode Export mode * @param parallel Parallel + * @param exportAllClasses Export all classes - reference it in document + * class * @throws InterruptedException On interrupt */ - private void appendTo(AbcIndexing abcIndex, GraphTextWriter writer, List traits, ConvertData convertData, ScriptExportMode exportMode, boolean parallel) throws InterruptedException { + private void appendTo(AbcIndexing abcIndex, GraphTextWriter writer, List traits, ConvertData convertData, ScriptExportMode exportMode, boolean parallel, boolean exportAllClasses) throws InterruptedException { boolean first = true; //script initializer int script_init = abc.script_info.get(scriptIndex).init_index; @@ -307,21 +309,21 @@ public class ScriptPack extends AS3ClassTreeItem { if (!isSimple && traitIndices.isEmpty()) { for (Trait t : abc.script_info.get(scriptIndex).traits.traits) { - + if (t instanceof TraitSlotConst) { continue; } - + String fullName = t.getName(abc).getNameWithNamespace(abc.constants, false).toPrintableString(true); writer.appendNoHilight("include \"" + fullName.replace(".", "/") + ".as\";").newLine(); } writer.newLine(); - } - + } + for (int t : traitIndices) { - + Trait trait = traits.get(t); - + if (trait instanceof TraitSlotConst) { continue; } @@ -342,7 +344,7 @@ public class ScriptPack extends AS3ClassTreeItem { } first = false; } - + List fullyQualifiedNames = new ArrayList<>(); if (!first) { writer.newLine(); @@ -354,16 +356,15 @@ public class ScriptPack extends AS3ClassTreeItem { Trait.writeImports(null, script_init, abcIndex, scriptIndex, -1, true, abc, writer, ignorePackage, fullyQualifiedNames); first = true; - //Slot const last for (int t : traitIndices) { - + Trait trait = traits.get(t); - + if (!(trait instanceof TraitSlotConst)) { continue; } - + if (convertData.assignedValues.containsKey((TraitSlotConst) trait)) { continue; } @@ -384,19 +385,19 @@ public class ScriptPack extends AS3ClassTreeItem { } first = false; } - + if (bodyIndex != -1 && (isSimple || traitIndices.isEmpty())) { //Note: There must be trait/method highlight even if the initializer is empty to TraitList in GUI to work correctly writer.startTrait(GraphTextWriter.TRAIT_SCRIPT_INITIALIZER); writer.startMethod(script_init, null); if (exportMode != ScriptExportMode.AS_METHOD_STUBS) { - if (!scriptInitializerIsEmpty) { + if (!scriptInitializerIsEmpty) { List callStack = new ArrayList<>(); callStack.add(abc.bodies.get(bodyIndex)); if (!first) { - writer.newLine(); + writer.newLine(); } - abc.bodies.get(bodyIndex).toString(callStack, abcIndex, path + "/.scriptinitializer", exportMode, abc, null, writer, fullyQualifiedNames, new HashSet<>()); + abc.bodies.get(bodyIndex).toString(callStack, abcIndex, path + "/.scriptinitializer", exportMode, abc, null, writer, fullyQualifiedNames, new HashSet<>()); } else { writer.append(""); } @@ -408,6 +409,16 @@ public class ScriptPack extends AS3ClassTreeItem { first = false; } } + + if (exportAllClasses) { + String documentClass = abc.getSwf().getDocumentClass(); + if (documentClass != null) { + if (path.toRawString().equals(documentClass)) { + writer.append("//Include all classes in the build").append("\r\n"); + writer.append("function __ffdec_include_classes() { FFDecIncludeClasses; }"); + } + } + } } /** @@ -420,9 +431,10 @@ public class ScriptPack extends AS3ClassTreeItem { * @param exportMode Export mode * @param parallel Parallel * @param ignoreFrameScripts Whether to ignore frame scripts + * @param exportAllClasses Export all classes * @throws InterruptedException On interrupt */ - public void toSource(AbcIndexing abcIndex, GraphTextWriter writer, final List traits, final ConvertData convertData, final ScriptExportMode exportMode, final boolean parallel, boolean ignoreFrameScripts) throws InterruptedException { + public void toSource(AbcIndexing abcIndex, GraphTextWriter writer, final List traits, final ConvertData convertData, final ScriptExportMode exportMode, final boolean parallel, boolean ignoreFrameScripts, boolean exportAllClasses) throws InterruptedException { writer.suspendMeasure(); int timeout = Configuration.decompilationTimeoutFile.get(); try { @@ -460,7 +472,7 @@ public class ScriptPack extends AS3ClassTreeItem { } writer.continueMeasure(); - appendTo(abcIndex, writer, traits, convertData, exportMode, parallel); + appendTo(abcIndex, writer, traits, convertData, exportMode, parallel, exportAllClasses); } /** @@ -492,7 +504,7 @@ public class ScriptPack extends AS3ClassTreeItem { convertData.exportEmbed = exportSettings.exportEmbed; convertData.exportEmbedFlaMode = exportSettings.exportEmbedFlaMode; convertData.assetsDir = exportSettings.assetsDir; - toSource(abcIndex, writer2, abc.script_info.get(scriptIndex).traits.traits, convertData, exportSettings.mode, parallel, exportSettings.ignoreFrameScripts); + toSource(abcIndex, writer2, abc.script_info.get(scriptIndex).traits.traits, convertData, exportSettings.mode, parallel, exportSettings.ignoreFrameScripts, exportSettings.includeAllClasses); } catch (FileNotFoundException ex) { logger.log(Level.SEVERE, "The file path is probably too long", ex); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index 4aca79d8f..30f3643be 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -1019,6 +1019,10 @@ public final class Configuration { @ConfigurationDefaultBoolean(true) @ConfigurationCategory("script") public static ConfigurationItem warningAddFunction = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("export") + public static ConfigurationItem linkAllClasses = null; private enum OSId { WINDOWS, OSX, UNIX diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/AS3ScriptExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/AS3ScriptExporter.java index 3b248eed3..8522cafdc 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/AS3ScriptExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/AS3ScriptExporter.java @@ -81,6 +81,7 @@ import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.ImageTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RemoveTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.decompiler.graph.ScopeStack; @@ -92,6 +93,7 @@ import com.jpexs.helpers.XmlPrettyFormat; import com.jpexs.helpers.utf8.Utf8Helper; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -381,6 +383,7 @@ public class AS3ScriptExporter { /** * Export ActionScript 3 scripts. + * * @param swf SWF * @param handler AbortRetryIgnoreHandler * @param outdir Output directory @@ -405,6 +408,56 @@ public class AS3ScriptExporter { int cnt = 1; List tasks = new ArrayList<>(); Set files = new HashSet<>(); + String documentClass = swf.getDocumentClass(); + StringBuffer includeClassesBuilder = new StringBuffer(); + String documentPkg = DottedChain.parseNoSuffix(documentClass).getWithoutLast().toPrintableString(true); + + StringBuilder importsBuilder = new StringBuilder(); + + for (ScriptPack item : packs) { + if (!item.isSimple && Configuration.ignoreCLikePackages.get()) { + continue; + } + if (ignoredClasses.contains(item.getClassPath().toRawString())) { + continue; + } + if (flexClass != null && item.getClassPath().toRawString().equals(flexClass)) { + continue; + } + + String rawClassName = item.getClassPath().toRawString(); + CharacterTag character = swf.getCharacterByClass(rawClassName); + + //For some reasons Sprites do not work... + boolean allowedType = (character instanceof SoundTag) + || (character instanceof ImageTag) + || (character instanceof FontTag); + + if (allowedType) { + if (!item.getClassPath().packageStr.isTopLevel()) { + importsBuilder.append(" import ").append(item.getClassPath().toString()).append(";\r\n"); + } + includeClassesBuilder.append(" ").append(item.getClassPath().toString()).append(";\r\n"); + } + } + + if (documentClass != null && !includeClassesBuilder.isEmpty()) { + StringBuilder prep = new StringBuilder(); + prep.append(" /**\r\n"); + prep.append(" * This class contains references to all decompiled sound/image/font classes.\r\n"); + prep.append(" * It is needed for compilation otherwise some classes will be missed.\r\n"); + prep.append(" */\r\n"); + prep.append(" public class FFDecIncludeClasses\r\n"); + prep.append(" {\r\n"); + includeClassesBuilder.insert(0, prep); + } + + //If no sound/image classes found, then do not include FFDecIncludeClasses at all + if (includeClassesBuilder.isEmpty()) { + exportSettings = exportSettings.clone(); + exportSettings.includeAllClasses = false; + } + for (ScriptPack item : packs) { if (!item.isSimple && Configuration.ignoreCLikePackages.get()) { continue; @@ -446,6 +499,35 @@ public class AS3ScriptExporter { tasks.add(new ExportPackTask(swf.getAbcIndex(), handler, cnt++, packs.size(), item.getClassPath(), item, file, exportSettings, parallel, evl)); } + if (!includeClassesBuilder.isEmpty()) { + includeClassesBuilder.append(" }\r\n"); + includeClassesBuilder.append("}\r\n"); + + StringBuilder prepend = new StringBuilder(); + prepend.append("package ").append(documentPkg).append("\r\n"); + prepend.append("{\r\n"); + prepend.append(importsBuilder.toString()); + prepend.append("\r\n"); + + includeClassesBuilder.insert(0, prepend.toString()); + + if (exportSettings.includeAllClasses) { + java.nio.file.Path outPath = new File(outdir).toPath(); + if (!documentPkg.isEmpty()) { + outPath = outPath.resolve(documentPkg); + } + File ffdecIncludeFilePath = outPath.resolve("FFDecIncludeClasses.as").toFile(); + + try (FileOutputStream fos = new FileOutputStream(ffdecIncludeFilePath)) { + fos.write(Utf8Helper.getBytes(includeClassesBuilder.toString())); + } catch (FileNotFoundException ex) { + Logger.getLogger(AS3ScriptExporter.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + Logger.getLogger(AS3ScriptExporter.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + if (!parallel || tasks.size() < 2) { try { CancellableWorker.call(new Callable() { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/settings/ScriptExportSettings.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/settings/ScriptExportSettings.java index 37ecc3c03..8500c3e2b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/settings/ScriptExportSettings.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/settings/ScriptExportSettings.java @@ -18,6 +18,8 @@ package com.jpexs.decompiler.flash.exporters.settings; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.helpers.FileTextWriter; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Script export settings. @@ -71,6 +73,11 @@ public class ScriptExportSettings { */ public String assetsDir; + /** + * Modify main class to reference all classes + */ + public boolean includeAllClasses = true; + /** * Constructor. * @param mode Mode @@ -88,7 +95,7 @@ public class ScriptExportSettings { boolean exportEmbedFlaMode, boolean resampleWav ) { - this(mode, singleFile, ignoreFrameScripts, exportEmbed, exportEmbedFlaMode, resampleWav, "/_assets/"); + this(mode, singleFile, ignoreFrameScripts, exportEmbed, exportEmbedFlaMode, resampleWav, "/_assets/", false); } public ScriptExportSettings( @@ -98,7 +105,8 @@ public class ScriptExportSettings { boolean exportEmbed, boolean exportEmbedFlaMode, boolean resampleWav, - String assetsDir + String assetsDir, + boolean includeAllClasses ) { this.mode = mode; this.singleFile = singleFile; @@ -107,6 +115,7 @@ public class ScriptExportSettings { this.exportEmbedFlaMode = exportEmbedFlaMode; this.resampleWav = resampleWav; this.assetsDir = assetsDir; + this.includeAllClasses = includeAllClasses; } public String getFileExtension() { @@ -127,4 +136,14 @@ public class ScriptExportSettings { throw new Error("Unsupported script export mode: " + mode); } } + + @Override + public ScriptExportSettings clone() { + try { + return (ScriptExportSettings) super.clone(); + } catch (CloneNotSupportedException ex) { + //ignored + } + return null; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfFlashDevelopExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfFlashDevelopExporter.java index 37f61e230..2324b852a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfFlashDevelopExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfFlashDevelopExporter.java @@ -343,7 +343,7 @@ public class SwfFlashDevelopExporter { } boolean parallel = Configuration.parallelSpeedUp.get(); - ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, false, true, false, false); + ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, false, true, false, false, "/_assets/", Configuration.linkAllClasses.get()); swf.exportActionScript(handler, outFile.toPath().getParent().resolve(srcPath).toFile().getAbsolutePath(), scriptExportSettings, parallel, eventListener); } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfIntelliJIdeaExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfIntelliJIdeaExporter.java index ec2085464..53899b592 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfIntelliJIdeaExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/swf/SwfIntelliJIdeaExporter.java @@ -316,7 +316,7 @@ public class SwfIntelliJIdeaExporter { } boolean parallel = Configuration.parallelSpeedUp.get(); - ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, false, true, false, false); + ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, false, true, false, false, "/_assets/", Configuration.linkAllClasses.get()); swf.exportActionScript(handler, new File(outDir, "src").getAbsolutePath(), scriptExportSettings, parallel, eventListener); } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java index c1bd2fa70..2264a427f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java @@ -1463,16 +1463,16 @@ public class XFLConverter { return date.getTime() / 1000; } - private void convertLibrary(Set charactersExportedInFirstFrame, Map characterImportLinkageURL, Set characters, Reference lastImportedId, Map characterNameMap, SWF swf, Map characterVariables, Map characterClasses, Map characterScriptPacks, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map placeToMaskedSymbol, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { + private void convertLibrary(Reference lastItemIdNumber, Set charactersExportedInFirstFrame, Map characterImportLinkageURL, Set characters, Reference lastImportedId, Map characterNameMap, SWF swf, Map characterVariables, Map characterClasses, Map characterScriptPacks, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map placeToMaskedSymbol, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { statusStack.pushStatus("media"); - convertMedia(charactersExportedInFirstFrame, lastImportedId, characterNameMap, characterImportLinkageURL, characters, swf, characterVariables, characterClasses, tags, files, datfiles, writer, statusStack); + convertMedia(lastItemIdNumber, charactersExportedInFirstFrame, lastImportedId, characterNameMap, characterImportLinkageURL, characters, swf, characterVariables, characterClasses, tags, files, datfiles, writer, statusStack); statusStack.popStatus(); statusStack.pushStatus("symbols"); - convertSymbols(charactersExportedInFirstFrame, characterImportLinkageURL, characters, lastImportedId, characterNameMap, swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, files, flaVersion, writer, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); + convertSymbols(lastItemIdNumber, charactersExportedInFirstFrame, characterImportLinkageURL, characters, lastImportedId, characterNameMap, swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, files, flaVersion, writer, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); statusStack.popStatus(); } - private void convertSymbols(Set charactersExportedInFirstFrame, Map characterImportLinkageURL, Set characters, Reference lastImportedId, Map characterNameMap, SWF swf, Map characterVariables, Map characterClasses, Map characterScriptPacks, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap files, FLAVersion flaVersion, XFLXmlWriter writer, Map placeToMaskedSymbol, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { + private void convertSymbols(Reference lastItemIdNumber, Set charactersExportedInFirstFrame, Map characterImportLinkageURL, Set characters, Reference lastImportedId, Map characterNameMap, SWF swf, Map characterVariables, Map characterClasses, Map characterScriptPacks, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap files, FLAVersion flaVersion, XFLXmlWriter writer, Map placeToMaskedSymbol, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { //boolean hasSymbol = false; Reference nextClipId = new Reference<>(-1); writer.writeStartElement("symbols"); @@ -1486,10 +1486,12 @@ public class XFLConverter { statusStack.pushStatus(symbol.toString()); XFLXmlWriter symbolStr = new XFLXmlWriter(); + String itemId = generateItemId(lastItemIdNumber); symbolStr.writeStartElement("DOMSymbolItem", new String[]{ "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", "xmlns", "http://ns.adobe.com/xfl/2008/", "name", getSymbolName(lastImportedId, characterNameMap, swf, symbol), + "itemID", itemId, "lastModified", Long.toString(getTimestamp(swf))}); //TODO:itemID if (characterImportLinkageURL.containsKey(symbol)) { symbolStr.writeAttribute("linkageImportForRS", "true"); @@ -1705,7 +1707,7 @@ public class XFLConverter { } final ScriptPack spriteScriptPack = characterScriptPacks.containsKey(sprite) ? characterScriptPacks.get(sprite) : null; - extractMultilevelClips(lastImportedId, characterNameMap, sprite.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); + extractMultilevelClips(lastItemIdNumber, lastImportedId, characterNameMap, sprite.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); convertTimelines(lastImportedId, characterNameMap, swf, swf.getAbcIndex(), sprite, characterVariables.get(sprite), nonLibraryShapes, tags, sprite.getTags(), getSymbolName(lastImportedId, characterNameMap, swf, symbol), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); @@ -1732,6 +1734,7 @@ public class XFLConverter { // write symbLink writer.writeStartElement("Include", new String[]{"href", symbolFile}); + writer.writeAttribute("itemID", itemId); if (itemIcon != null) { writer.writeAttribute("itemIcon", itemIcon); } @@ -1747,11 +1750,11 @@ public class XFLConverter { } statusStack.pushStatus("extracting multilevel clips"); - extractMultilevelClips(lastImportedId, characterNameMap, swf.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); + extractMultilevelClips(lastItemIdNumber, lastImportedId, characterNameMap, swf.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); statusStack.popStatus(); statusStack.pushStatus("converting multiusage morphshapes"); - extractMultiUsageMorphShapes(lastImportedId, characterNameMap, writer, swf, nonLibraryShapes, flaVersion, files, multiUsageMorphShapes, statusStack); + extractMultiUsageMorphShapes(lastItemIdNumber, lastImportedId, characterNameMap, writer, swf, nonLibraryShapes, flaVersion, files, multiUsageMorphShapes, statusStack); statusStack.popStatus(); /*if (hasSymbol) { @@ -1759,7 +1762,7 @@ public class XFLConverter { writer.writeEndElement(); } - private void convertSoundMedia(Map characterImportLinkageURL, SWF swf, ReadOnlyTagList tags, SoundTag symbol, XFLXmlWriter writer, HashMap files, HashMap datfiles) throws XMLStreamException { + private void convertSoundMedia(Reference lastItemIdNumber, Map characterImportLinkageURL, SWF swf, ReadOnlyTagList tags, SoundTag symbol, XFLXmlWriter writer, HashMap files, HashMap datfiles) throws XMLStreamException { int soundFormat = 0; int soundRate = 0; boolean soundType = false; @@ -1929,6 +1932,7 @@ public class XFLConverter { files.put(symbolFile, data); writer.writeStartElement("DOMSoundItem", new String[]{ "name", symbolFile, + "itemID", generateItemId(lastItemIdNumber), "sourceLastImported", Long.toString(getTimestamp(swf)), "externalFileSize", Integer.toString(data.length)}); if ((symbol instanceof CharacterTag) && characterImportLinkageURL.containsKey((CharacterTag) symbol)) { @@ -1945,7 +1949,7 @@ public class XFLConverter { writer.writeAttribute("sampleCount", soundSampleCount); } - private void convertMedia(Set charactersExportedInFirstFrame, Reference lastImportedId, Map characterNameMap, Map characterImportLinkageURL, Set characters, SWF swf, Map characterVariables, Map characterClasses, ReadOnlyTagList tags, HashMap files, HashMap datfiles, XFLXmlWriter writer, StatusStack statusStack) throws XMLStreamException { + private void convertMedia(Reference lastItemIdNumber, Set charactersExportedInFirstFrame, Reference lastImportedId, Map characterNameMap, Map characterImportLinkageURL, Set characters, SWF swf, Map characterVariables, Map characterClasses, ReadOnlyTagList tags, HashMap files, HashMap datfiles, XFLXmlWriter writer, StatusStack statusStack) throws XMLStreamException { boolean hasMedia = false; for (CharacterTag symbol : characters) { if (symbol instanceof ImageTag @@ -2003,8 +2007,9 @@ public class XFLConverter { ImageFormat format = imageTag.getImageFormat(); String symbolFile = getSymbolName(lastImportedId, characterNameMap, swf, symbol, "Bitmap") + imageTag.getImageFormat().getExtension(); files.put(symbolFile, imageBytes); - writer.writeStartElement("DOMBitmapItem", new String[]{ + writer.writeStartElement("DOMBitmapItem", new String[]{ "name", symbolFile, + "itemID", generateItemId(lastItemIdNumber), "sourceLastImported", Long.toString(getTimestamp(swf)), "externalFileSize", Integer.toString(imageBytes.length)}); @@ -2061,7 +2066,7 @@ public class XFLConverter { statusStack.popStatus(); } else if (symbol instanceof DefineSoundTag) { statusStack.pushStatus(symbol.toString()); - convertSoundMedia(characterImportLinkageURL, swf, tags, (DefineSoundTag) symbol, writer, files, datfiles); + convertSoundMedia(lastItemIdNumber, characterImportLinkageURL, swf, tags, (DefineSoundTag) symbol, writer, files, datfiles); if (characterImportLinkageURL.containsKey(symbol)) { writer.writeAttribute("linkageImportForRS", "true"); @@ -2148,6 +2153,7 @@ public class XFLConverter { files.put(symbolFile, data); writer.writeStartElement("DOMVideoItem", new String[]{ "name", symbolFile, + "itemID", generateItemId(lastItemIdNumber), "sourceLastImported", Long.toString(getTimestamp(swf)), "externalFileSize", Integer.toString(data.length)}); if (characterImportLinkageURL.containsKey(symbol)) { @@ -2188,7 +2194,7 @@ public class XFLConverter { SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) t; for (SoundStreamFrameRange range : head.getRanges()) { statusStack.pushStatus(range.toString()); - convertSoundMedia(characterImportLinkageURL, swf, tags, range, writer, files, datfiles); + convertSoundMedia(lastItemIdNumber, characterImportLinkageURL, swf, tags, range, writer, files, datfiles); writer.writeEndElement(); statusStack.popStatus(); } @@ -2200,7 +2206,7 @@ public class XFLConverter { SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) st; for (SoundStreamFrameRange range : head.getRanges()) { statusStack.pushStatus(range.toString()); - convertSoundMedia(characterImportLinkageURL, swf, sprite.getTags(), range, writer, files, datfiles); + convertSoundMedia(lastItemIdNumber, characterImportLinkageURL, swf, sprite.getTags(), range, writer, files, datfiles); writer.writeEndElement(); statusStack.popStatus(); } @@ -2675,7 +2681,7 @@ public class XFLConverter { } } - private static void convertFonts(Set charactersExportedInFirstFrame, Reference lastImportedId, Map characterNameMap, SWF swf, Set characters, Map characterClasses, XFLXmlWriter writer, StatusStack statusStack) throws XMLStreamException { + private static void convertFonts(Reference lastItemIdNumber, Set charactersExportedInFirstFrame, Reference lastImportedId, Map characterNameMap, SWF swf, Set characters, Map characterClasses, XFLXmlWriter writer, StatusStack statusStack) throws XMLStreamException { boolean hasFont = false; int fontCounter = 0; for (CharacterTag t : characters) { @@ -2758,6 +2764,7 @@ public class XFLConverter { fontCounter++; writer.writeStartElement("DOMFontItem", new String[]{ "name", getSymbolName(lastImportedId, characterNameMap, swf, font, "Font"), + "itemID", generateItemId(lastItemIdNumber), "font", fontName, "size", "0", "id", Integer.toString(fontCounter), @@ -3159,7 +3166,9 @@ public class XFLConverter { } private void addExtractedClip( - Reference lastImportedId, Map characterNameMap, + Reference lastItemIdNumber, + Reference lastImportedId, + Map characterNameMap, ReadOnlyTagList timelineTags, XFLXmlWriter writer, SWF swf, @@ -3174,7 +3183,7 @@ public class XFLConverter { ) throws XMLStreamException { XFLXmlWriter symbolStr = new XFLXmlWriter(); - extractMultilevelClips(lastImportedId, characterNameMap, timelineTags, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); + extractMultilevelClips(lastItemIdNumber, lastImportedId, characterNameMap, timelineTags, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); if (nextClipId.getVal() < 0) { nextClipId.setVal(swf.getNextCharacterId()); @@ -3183,11 +3192,12 @@ public class XFLConverter { } int objectId = nextClipId.getVal(); - + String itemId = generateItemId(lastItemIdNumber); symbolStr.writeStartElement("DOMSymbolItem", new String[]{ "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", "xmlns", "http://ns.adobe.com/xfl/2008/", "name", getMaskedSymbolName(objectId), + "itemID", itemId, "lastModified", Long.toString(getTimestamp(swf))}); symbolStr.writeAttribute("symbolType", "graphic"); @@ -3199,6 +3209,7 @@ public class XFLConverter { files.put(symbolFile, Utf8Helper.getBytes(symbolStr2)); writer.writeStartElement("Include", new String[]{"href", symbolFile}); + writer.writeAttribute("itemID", itemId); writer.writeAttribute("itemIcon", "1"); writer.writeAttribute("loadImmediate", false); if (flaVersion.ordinal() >= FLAVersion.CS5_5.ordinal()) { @@ -3263,7 +3274,9 @@ public class XFLConverter { } private void extractMultiUsageMorphShapes( - Reference lastImportedId, Map characterNameMap, + Reference lastItemIdNumber, + Reference lastImportedId, + Map characterNameMap, XFLXmlWriter writer, SWF swf, List nonLibraryShapes, @@ -3276,11 +3289,13 @@ public class XFLConverter { for (int objectId : multiUsageMorphShapes) { statusStack.pushStatus(swf.getCharacter(objectId).toString()); + String itemId = generateItemId(lastItemIdNumber); XFLXmlWriter symbolStr = new XFLXmlWriter(); symbolStr.writeStartElement("DOMSymbolItem", new String[]{ "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", "xmlns", "http://ns.adobe.com/xfl/2008/", "name", getSymbolName(lastImportedId, characterNameMap, swf, swf.getCharacter(objectId)), + "itemID", itemId, "lastModified", Long.toString(getTimestamp(swf))}); symbolStr.writeAttribute("symbolType", "graphic"); @@ -3308,6 +3323,7 @@ public class XFLConverter { files.put(symbolFile, Utf8Helper.getBytes(symbolStr2)); writer.writeStartElement("Include", new String[]{"href", symbolFile}); + writer.writeAttribute("itemID", itemId); writer.writeAttribute("itemIcon", "1"); writer.writeAttribute("loadImmediate", false); if (flaVersion.ordinal() >= FLAVersion.CS5_5.ordinal()) { @@ -3320,7 +3336,9 @@ public class XFLConverter { } private void extractMultilevelClips( - Reference lastImportedId, Map characterNameMap, + Reference lastItemIdNumber, + Reference lastImportedId, + Map characterNameMap, ReadOnlyTagList timelineTags, XFLXmlWriter writer, SWF swf, @@ -3520,7 +3538,7 @@ public class XFLConverter { //set timelined? delegatedTimeline.add(showFrame); } - addExtractedClip(lastImportedId, characterNameMap, new ReadOnlyTagList(delegatedTimeline), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); + addExtractedClip(lastItemIdNumber, lastImportedId, characterNameMap, new ReadOnlyTagList(delegatedTimeline), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); placeToMaskedSymbol.put(secondPlace, new MultiLevelClip(secondPlace, nextClipId.getVal(), numFrames)); } } @@ -4670,6 +4688,7 @@ public class XFLConverter { Set charactersExportedInFirstFrame = new LinkedIdentityHashSet<>(); Map characterImportLinkageURL = new IdentityHashMap<>(); + Reference lastItemIdNumber = new Reference<>(0); int frame = 1; for (Tag tag : swf.getTags()) { if (tag instanceof ImportTag) { @@ -4717,9 +4736,9 @@ public class XFLConverter { } } } - convertFonts(charactersExportedInFirstFrame, lastImportedId, characterNameMap, swf, characters, characterClasses, domDocument, statusStack); + convertFonts(lastItemIdNumber, charactersExportedInFirstFrame, lastImportedId, characterNameMap, swf, characters, characterClasses, domDocument, statusStack); - convertLibrary(charactersExportedInFirstFrame, characterImportLinkageURL, characters, lastImportedId, characterNameMap, swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), files, datfiles, flaVersion, domDocument, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); + convertLibrary(lastItemIdNumber, charactersExportedInFirstFrame, characterImportLinkageURL, characters, lastImportedId, characterNameMap, swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), files, datfiles, flaVersion, domDocument, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); //domDocument.writeStartElement("timelines"); ScriptPack documentScriptPack = null; @@ -5203,7 +5222,7 @@ public class XFLConverter { } if (useAS3 && settings.exportScript) { try { - ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, true, false, true, true); + ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, true, false, true, true, "/_assets/", Configuration.linkAllClasses.get()); swf.exportActionScript(handler, scriptsDir.getAbsolutePath(), scriptExportSettings, parallel, null); } catch (Exception ex) { logger.log(Level.SEVERE, "Error during ActionScript3 export", ex); @@ -5365,7 +5384,14 @@ public class XFLConverter { private static double twipToPixel(double tw) { return tw / SWF.unitDivisor; } - + + private static String generateItemId(Reference lastItemIdNumber) { + lastItemIdNumber.setVal(lastItemIdNumber.getVal() + 1); + String epochHex = String.format("%1$08x", Math.round(System.currentTimeMillis() / 1000)); + String numberHex = String.format("%1$08x", lastItemIdNumber.getVal()); + return epochHex + "-" + numberHex; + } + private static class HTMLTextParser extends DefaultHandler { public XFLXmlWriter result = new XFLXmlWriter(); diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java index f22c9777c..2025e8456 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3DeobfuscatorTest.java @@ -137,7 +137,7 @@ public class ActionScript3DeobfuscatorTest extends ActionScriptTestBase { HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false); par.addScript(str, "Test.as", 0, 0, swf.getDocumentClass(), abc); - abc.script_info.get(0).getPacks(abc, 0, "", new ArrayList<>()).get(0).toSource(swf.getAbcIndex(), writer, abc.script_info.get(0).traits.traits, new ConvertData(), ScriptExportMode.AS, false, false); + abc.script_info.get(0).getPacks(abc, 0, "", new ArrayList<>()).get(0).toSource(swf.getAbcIndex(), writer, abc.script_info.get(0).traits.traits, new ConvertData(), ScriptExportMode.AS, false, false, false); writer.finishHilights(); return writer.toString(); } diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/DirectEditingTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/DirectEditingTest.java index 85a8669b2..6e0cdaf80 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/DirectEditingTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/DirectEditingTest.java @@ -91,7 +91,7 @@ public class DirectEditingTest extends FileTestBase { System.out.println("Recompiling:" + classPathString + "..."); try { - en.toSource(swf.getAbcIndex(), htw, abc.script_info.get(s).traits.traits, new ConvertData(), ScriptExportMode.AS, false, false); + en.toSource(swf.getAbcIndex(), htw, abc.script_info.get(s).traits.traits, new ConvertData(), ScriptExportMode.AS, false, false, false); htw.finishHilights(); String original = htw.toString(); abc.replaceScriptPack(As3ScriptReplacerFactory.createFFDec() /*TODO: test the otherone*/, en, original, new ArrayList<>()); diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassTest.java index fdea0a0b7..80ea0e088 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassTest.java @@ -68,7 +68,7 @@ public class ActionScript3ClassTest extends ActionScript3DecompileTestBase { HighlightedTextWriter writer = null; try { writer = new HighlightedTextWriter(new CodeFormatting(), false); - scriptPack.toSource(swf.getAbcIndex(), writer, abc.script_info.get(scriptPack.scriptIndex).traits.traits, new ConvertData(), ScriptExportMode.AS, false, false); + scriptPack.toSource(swf.getAbcIndex(), writer, abc.script_info.get(scriptPack.scriptIndex).traits.traits, new ConvertData(), ScriptExportMode.AS, false, false, false); } catch (InterruptedException ex) { fail(); } diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index 0bc9f8b0e..b88a0c774 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -528,3 +528,6 @@ config.name.warningAbcClean=Warn on Abc clean action config.description.warningAbcClean=Show warning before doing Abc clean action config.name.warningAddFunction=Warn on adding new function in AS3 P-code config.description.warningAddFunction=Show warning before creating new function in AS3 P-code. It also shows some info how the action works. +#after 21.0.2 +config.name.linkAllClasses=Add link to all classes (sound, font, image) +config.description.linkAllClasses=Add special script that links all (sound, font, image) classes in the SWF. This is useful when no other script links them, to be still available in compiled file. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties index 548e36016..bfbc8882b 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties @@ -526,3 +526,6 @@ config.name.warningAbcClean=Varovat p\u0159i Abc \u010di\u0161t\u011bn\u00ed config.description.warningAbcClean=Zobrazovat varov\u00e1n\u00ed p\u0159ed proveden\u00edm Abc \u010di\u0161t\u011bn\u00ed config.name.warningAddFunction=Varovat p\u0159i p\u0159id\u00e1v\u00e1n\u00ed nov\u00e9 funkce v AS3 P-k\u00f3du config.description.warningAddFunction=Zobrazovat varov\u00e1n\u00ed p\u0159ed p\u0159id\u00e1n\u00edm nov\u00e9 funkce v AS3 P-k\u00f3du. Tak\u00e9 to zobrazuje n\u011bjak\u00e9 informace o tom, jak tato akce funguje. +#after 21.0.2 +config.name.linkAllClasses=P\u0159idat vazbu na v\u0161echny t\u0159\u00eddy (zvuky, p\u00edsma, obr\u00e1zky) +config.description.linkAllClasses=P\u0159id\u00e1 speci\u00e1ln\u00ed skript kter\u00fd odkazuje na v\u0161echny t\u0159\u00eddy (zvuky, p\u00edsma, obr\u00e1zky) v SWF souboru. Tohle je u\u017eite\u010dn\u00e9 pokud \u017e\u00e1dn\u00fd jin\u00fd skript na n\u011b neodkazuje, aby byly st\u00e1le sou\u010d\u00e1st\u00ed zkompilovan\u00e9ho souboru.