diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fba8f306..c4f5f6661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ### Added - 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) ### Fixed - [#2266] StartSound/2 and VideoFrame tags, classNames not taken as dependencies (needed chars) diff --git a/libsrc/ffdec_lib/build.xml b/libsrc/ffdec_lib/build.xml index 90c1525a8..4dac3c4fc 100644 --- a/libsrc/ffdec_lib/build.xml +++ b/libsrc/ffdec_lib/build.xml @@ -34,7 +34,8 @@ - + + diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/FlashPlayerVersion.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/FlashPlayerVersion.java index b6faab155..285358eef 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/FlashPlayerVersion.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/FlashPlayerVersion.java @@ -25,7 +25,9 @@ import java.util.Map; */ public class FlashPlayerVersion { private static final Map flashPlayerToSwfVersion = new HashMap<>(); + private static final Map airToSwfVersion = new HashMap<>(); private static final Map swfVersionToFlashPlayer = new HashMap<>(); + private static final Map swfVersionToAir = new HashMap<>(); static { flashPlayerToSwfVersion.put("9.0", 9); //9.0.115.0 flashPlayerToSwfVersion.put("10.0", 10); @@ -66,6 +68,47 @@ public class FlashPlayerVersion { flashPlayerToSwfVersion.put("33.0", 44); flashPlayerToSwfVersion.put("33.1", 44); flashPlayerToSwfVersion.put("50.0", 50); + flashPlayerToSwfVersion.put("51.0", 51); + + airToSwfVersion.put("1.5", 10); + airToSwfVersion.put("2.0", 10); + airToSwfVersion.put("2.6", 11); + airToSwfVersion.put("2.7", 12); + airToSwfVersion.put("3.0", 13); + airToSwfVersion.put("3.1", 14); + airToSwfVersion.put("3.2", 15); + airToSwfVersion.put("3.3", 16); + airToSwfVersion.put("3.4", 17); + airToSwfVersion.put("3.5", 18); + airToSwfVersion.put("3.6", 19); + airToSwfVersion.put("3.7", 20); + airToSwfVersion.put("3.8", 21); + airToSwfVersion.put("3.9", 22); + airToSwfVersion.put("4.0", 23); + airToSwfVersion.put("13.0", 24); + airToSwfVersion.put("14.0", 25); + airToSwfVersion.put("15.0", 26); + airToSwfVersion.put("16.0", 27); + airToSwfVersion.put("17.0", 28); + airToSwfVersion.put("18.0", 29); + airToSwfVersion.put("19.0", 30); + airToSwfVersion.put("20.0", 31); + airToSwfVersion.put("21.0", 32); + airToSwfVersion.put("22.0", 33); + airToSwfVersion.put("23.0", 34); + airToSwfVersion.put("24.0", 35); + airToSwfVersion.put("25.0", 36); + airToSwfVersion.put("26.0", 37); + airToSwfVersion.put("27.0", 38); + airToSwfVersion.put("28.0", 39); + airToSwfVersion.put("29.0", 40); + airToSwfVersion.put("30.0", 41); + airToSwfVersion.put("31.0", 42); + airToSwfVersion.put("32.0", 43); + airToSwfVersion.put("33.0", 44); + airToSwfVersion.put("33.1", 44); + airToSwfVersion.put("50.0", 50); + airToSwfVersion.put("51.0", 51); for (String flashPlayer : flashPlayerToSwfVersion.keySet()) { int swfVersion = flashPlayerToSwfVersion.get(flashPlayer); @@ -73,6 +116,13 @@ public class FlashPlayerVersion { swfVersionToFlashPlayer.put(swfVersion, flashPlayer); } } + + for (String air : airToSwfVersion.keySet()) { + int swfVersion = airToSwfVersion.get(air); + if (!swfVersionToAir.containsKey(swfVersion)) { + swfVersionToAir.put(swfVersion, air); + } + } } /** @@ -81,8 +131,8 @@ public class FlashPlayerVersion { * @return Flash player version or null if not found */ public static String getFlashPlayerBySwfVersion(int swfVersion) { - if (swfVersion > 50) { - return "50.0"; //?? + if (swfVersion > 51) { + return "51.0"; //?? } return swfVersionToFlashPlayer.get(swfVersion); } @@ -98,4 +148,29 @@ public class FlashPlayerVersion { } return flashPlayerToSwfVersion.get(flashPlayerVersion); } + + + /** + * Gets Air version by SWF version + * @param swfVersion SWF version + * @return Air version or null if not found + */ + public static String getAirBySwfVersion(int swfVersion) { + if (swfVersion > 51) { + return "51.0"; //?? + } + return swfVersionToFlashPlayer.get(swfVersion); + } + + /** + * Gets SWF version by Air version. + * @param air Air version + * @return SWF version or -1 if not found + */ + public static int getSwfVersionByAir(String air) { + if (!airToSwfVersion.containsKey(air)) { + return -1; + } + return airToSwfVersion.get(air); + } } 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 ebfb516b8..37f61e230 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 @@ -31,10 +31,13 @@ import com.jpexs.decompiler.flash.tags.StartSoundTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.VideoFrameTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.helpers.Helper; import com.jpexs.helpers.utf8.Utf8Helper; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; /** * Exports SWF to FlashDevelop project. @@ -49,19 +52,17 @@ public class SwfFlashDevelopExporter { } //Cannot export if it has something on main timeline for (Tag t : swf.getTags()) { - if ( - (t instanceof PlaceObjectTypeTag) + if ((t instanceof PlaceObjectTypeTag) || (t instanceof SoundStreamBlockTag) || (t instanceof VideoFrameTag) || (t instanceof StartSoundTag) - || (t instanceof StartSound2Tag) - ) { + || (t instanceof StartSound2Tag)) { return false; } } return true; } - + private static String doubleToString(double d) { String ds = "" + d; if (ds.endsWith(".0")) { @@ -72,28 +73,32 @@ public class SwfFlashDevelopExporter { /** * Exports SWF to FlashDevelop project. + * * @param swf SWF to export * @param outFile Output file + * @param air * @param handler Handler for abort, retry, ignore * @throws IOException On I/O error */ - public void exportFlashDevelopProject(SWF swf, File outFile, AbortRetryIgnoreHandler handler) throws IOException { - exportFlashDevelopProject(swf, outFile, handler, null); + public void exportFlashDevelopProject(SWF swf, File outFile, boolean air, AbortRetryIgnoreHandler handler) throws IOException { + exportFlashDevelopProject(swf, outFile, air, handler, null); } /** * Exports SWF to FlashDevelop project. + * * @param swf SWF to export * @param outFile Output file + * @param air AIR * @param handler Handler for abort, retry, ignore * @param eventListener Event listener * @throws IOException On I/O error */ - public void exportFlashDevelopProject(SWF swf, File outFile, AbortRetryIgnoreHandler handler, EventListener eventListener) throws IOException { + public void exportFlashDevelopProject(SWF swf, File outFile, boolean air, AbortRetryIgnoreHandler handler, EventListener eventListener) throws IOException { if (!swf.isAS3()) { throw new IllegalArgumentException("SWF must be AS3"); } - + if (!canExportSwf(swf)) { throw new IllegalArgumentException("SWF must not contain main timeline"); } @@ -103,79 +108,240 @@ public class SwfFlashDevelopExporter { simpleName = simpleName.substring(0, simpleName.lastIndexOf(".")); } + String simpleNameNoSpaces = simpleName.replace(" ", ""); + SetBackgroundColorTag bgColorTag = swf.getBackgroundColor(); String documentClass = swf.getDocumentClass(); + String srcPath = "src"; + String project; + String flashPlayerVersion = FlashPlayerVersion.getFlashPlayerBySwfVersion(swf.version); String[] flashPlayerVersions = flashPlayerVersion.split("\\."); - - String srcPath = "src"; - String project = "\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + (documentClass == null - ? " \n" - : "\n") - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + ""; + + String airVersion = FlashPlayerVersion.getAirBySwfVersion(swf.version); + String[] airVersions = airVersion.split("\\."); + + if (air) { + + project = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + (documentClass == null + ? " \n" + : "\n") + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + } else { + project = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + (documentClass == null + ? " \n" + : "\n") + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + } try (FileOutputStream fos = new FileOutputStream(outFile)) { fos.write(Utf8Helper.getBytes(project)); } + if (air) { + String applicationXml = " \n" + + "\n" + + " \n" + + " " + simpleNameNoSpaces + " \n" + + " 1.0 \n" + + " " + simpleNameNoSpaces + " \n" + + " \n" + + " " + simpleName + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + simpleName + " \n" + + " " + simpleNameNoSpaces + ".swf \n" + + " standard \n" + + " false \n" + + " true \n" + + " true \n" + + " true \n" + + " true \n" + + " \n" + + " \n" + + " \n" + + ""; + try (FileOutputStream fos = new FileOutputStream(outFile.toPath().getParent().resolve("application.xml").toFile())) { + fos.write(Utf8Helper.getBytes(applicationXml)); + } + + Path batDirPath = outFile.toPath().getParent().resolve("bat"); + batDirPath.toFile().mkdir(); + String[] batFiles = new String[]{ + "CreateCertificate.bat", + "PackageApp.bat", + "Packager.bat", + "RunApp.bat", + "SetupApp.bat", + "SetupSDK.bat" + }; + for (String batFile : batFiles) { + InputStream is = SwfFlashDevelopExporter.class.getResourceAsStream("/com/jpexs/helpers/resource/fd_air/bat/" + batFile); + byte[] data = Helper.readStream(is); + String strData = Utf8Helper.decode(data); + strData = strData.replace("", simpleName); + strData = strData.replace("", simpleNameNoSpaces); + try (FileOutputStream fos = new FileOutputStream(batDirPath.resolve(batFile).toFile())) { + fos.write(Utf8Helper.getBytes(strData)); + } + } + } + boolean parallel = Configuration.parallelSpeedUp.get(); ScriptExportSettings scriptExportSettings = new ScriptExportSettings(ScriptExportMode.AS, false, false, true, false, false); swf.exportActionScript(handler, outFile.toPath().getParent().resolve(srcPath).toFile().getAbsolutePath(), scriptExportSettings, parallel, eventListener); diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/CreateCertificate.bat b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/CreateCertificate.bat new file mode 100644 index 000000000..f9a51a57f --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/CreateCertificate.bat @@ -0,0 +1,34 @@ +@echo off + +:: Set working dir +cd %~dp0 & cd .. + +set PAUSE_ERRORS=1 +call bat\SetupSDK.bat +call bat\SetupApp.bat + +:: Generate +echo. +echo Generating a self-signed certificate... +call adt -certificate -cn %CERT_NAME% 2048-RSA %CERT_FILE% %CERT_PASS% +if errorlevel 1 goto failed + +:succeed +echo. +echo Certificate created: %CERT_FILE% with password "%CERT_PASS%" +echo. +if "%CERT_PASS%" == "fd" echo Note: You did not change the default password +echo. +echo HINTS: +echo - you only need to generate this certificate once, +echo - wait a minute before using this certificate to package your AIR application. +echo. +goto end + +:failed +echo. +echo Certificate creation FAILED. +echo. + +:end +pause diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/PackageApp.bat b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/PackageApp.bat new file mode 100644 index 000000000..59aed98b6 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/PackageApp.bat @@ -0,0 +1,15 @@ +@echo off + +:: Set working dir +cd %~dp0 & cd .. + +set PAUSE_ERRORS=1 +call bat\SetupSDK.bat +call bat\SetupApp.bat + +set AIR_TARGET= +::set AIR_TARGET=-captive-runtime +set OPTIONS=-tsa none +call bat\Packager.bat + +pause diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/Packager.bat b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/Packager.bat new file mode 100644 index 000000000..03a520e1a --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/Packager.bat @@ -0,0 +1,39 @@ +@echo off + +:: Set working dir +cd %~dp0 & cd .. + +if not exist %CERT_FILE% goto certificate + +:: AIR output +if not exist %AIR_PATH% md %AIR_PATH% +set OUTPUT=%AIR_PATH%\%AIR_NAME%%AIR_TARGET%.air + +:: Package +echo. +echo Packaging %AIR_NAME%%AIR_TARGET%.air using certificate %CERT_FILE%... +call adt -package %OPTIONS% %SIGNING_OPTIONS% %OUTPUT% %APP_XML% %FILE_OR_DIR% +if errorlevel 1 goto failed +goto end + +:certificate +echo. +echo Certificate not found: %CERT_FILE% +echo. +echo Troubleshooting: +echo - generate a default certificate using 'bat\CreateCertificate.bat' +echo. +if %PAUSE_ERRORS%==1 pause +exit + +:failed +echo AIR setup creation FAILED. +echo. +echo Troubleshooting: +echo - verify AIR SDK target version in %APP_XML% +echo. +if %PAUSE_ERRORS%==1 pause +exit + +:end +echo. diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/RunApp.bat b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/RunApp.bat new file mode 100644 index 000000000..5b00357b4 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/RunApp.bat @@ -0,0 +1,21 @@ +@echo off + +:: Set working dir +cd %~dp0 & cd .. + +set PAUSE_ERRORS=1 +call bat\SetupSDK.bat +call bat\SetupApp.bat + +echo. +echo Starting AIR Debug Launcher... +echo. + +adl "%APP_XML%" "%APP_DIR%" +if errorlevel 1 goto error +goto end + +:error +pause + +:end diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/SetupApp.bat b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/SetupApp.bat new file mode 100644 index 000000000..21eecd47d --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/SetupApp.bat @@ -0,0 +1,47 @@ +:: Set working dir +cd %~dp0 & cd .. + +:user_configuration + +:: About AIR application packaging +:: http://livedocs.adobe.com/flex/3/html/help.html?content=CommandLineTools_5.html#1035959 +:: http://livedocs.adobe.com/flex/3/html/distributing_apps_4.html#1037515 + +:: NOTICE: all paths are relative to project root + +:: Your certificate information +set CERT_NAME="" +set CERT_PASS=fd +set CERT_FILE="bat\.p12" +set SIGNING_OPTIONS=-storetype pkcs12 -keystore %CERT_FILE% -storepass %CERT_PASS% + +:: Application descriptor +set APP_XML=application.xml + +:: Files to package +set APP_DIR=bin +set FILE_OR_DIR=-C %APP_DIR% . + +:: Your application ID (must match of Application descriptor) and remove spaces +for /f "tokens=3 delims=<>" %%a in ('findstr /R /C:"^[ ]*" %APP_XML%') do set APP_ID=%%a +set APP_ID=%APP_ID: =% + +:: Output +set AIR_PATH=air +set AIR_NAME= + +:validation +findstr /C:"%APP_ID%" "%APP_XML%" > NUL +if errorlevel 1 goto badid +goto end + +:badid +echo. +echo ERROR: +echo Application ID in 'bat\SetupApp.bat' (APP_ID) +echo does NOT match Application descriptor '%APP_XML%' (id) +echo. +if %PAUSE_ERRORS%==1 pause +exit + +:end diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/SetupSDK.bat b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/SetupSDK.bat new file mode 100644 index 000000000..9f9409f9f --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/resource/fd_air/bat/SetupSDK.bat @@ -0,0 +1,26 @@ +:: Set working dir +cd %~dp0 & cd .. + +:user_configuration + +:: Static path to Flex SDK +set FLEX_SDK=C:\flex + +:: Use FD supplied SDK path if executed from FD +if exist "%FD_CUR_SDK%" set FLEX_SDK=%FD_CUR_SDK% + +:validation +if not exist "%FLEX_SDK%\bin" goto flexsdk +goto succeed + +:flexsdk +echo. +echo ERROR: incorrect path to Flex SDK in 'bat\SetupSDK.bat' +echo. +echo Looking for: %FLEX_SDK%\bin +echo. +if %PAUSE_ERRORS%==1 pause +exit + +:succeed +set PATH=%FLEX_SDK%\bin;%PATH% diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index b6e155da7..8f59a9dbe 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -3328,7 +3328,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se fileName = swfShortName + ".as3proj"; } - FileFilter f = new FileFilter() { + FileFilter as3Filter = new FileFilter() { @Override public boolean accept(File f) { return f.isDirectory() || (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".as3proj")); @@ -3339,7 +3339,19 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se return translate("filter.as3proj"); } }; - fc.setFileFilter(f); + FileFilter airAs3Filter = new FileFilter() { + @Override + public boolean accept(File f) { + return f.isDirectory() || (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".as3proj")); + } + + @Override + public String getDescription() { + return translate("filter.air.as3proj"); + } + }; + fc.setFileFilter(as3Filter); + fc.addChoosableFileFilter(airAs3Filter); fc.setAcceptAllFileFilterUsed(false); fc.setSelectedFile(new File(selDir + fileName)); @@ -3380,7 +3392,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se EventListener evl = swf.getExportEventListener(); try { AbortRetryIgnoreHandler errorHandler = new GuiAbortRetryIgnoreHandler(); - exporter.exportFlashDevelopProject(swf, new File(fpath), errorHandler, evl); + exporter.exportFlashDevelopProject(swf, new File(fpath), fc.getFileFilter() == airAs3Filter, errorHandler, evl); } catch (Exception ex) { logger.log(Level.SEVERE, "FlashDevelop export error", ex); ViewMessages.showMessageDialog(MainPanel.this, translate("error.export") + ": " + ex.getClass().getName() + " " + ex.getLocalizedMessage(), translate("error"), JOptionPane.ERROR_MESSAGE); diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index ad6493554..1034bb0d3 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -968,7 +968,7 @@ tagInfo.idType=Type of the id contextmenu.configurePathResolving=Configure path resolving... contextmenu.setAsLinkage=Set AS linkage contextmenu.exportFlashDevelop=Export FlashDevelop project -filter.as3proj=FlashDevelop AS3 projects (*.as3proj) +filter.as3proj=FlashDevelop AS3 project (*.as3proj) work.exporting.flashDevelop=Exporting FlashDevelop project menu.file.export.flashDevelop=Export FD project contextmenu.exportIdea=Export IDEA project @@ -990,4 +990,7 @@ message.confirm.addfunction=This action creates a new MethodInfo object with \ which you can use as operand for newfunction instruction.\r\n\ For editation of body and parameters of such function, you must locate it then in ActionScript view. addfunction.result=New method info id generated: %method_info_index%. Press OK to copy it to clipboard. -addfunction.result.title=New method info \ No newline at end of file +addfunction.result.title=New method info + +#after 21.0.2 +filter.air.as3proj = FlashDevelop AIR AS3 project (*.as3proj) \ No newline at end of file