feat(xml export): allow external as1/2 scripts, images and defineSounds (#2707)

This commit is contained in:
Jindra Petřík
2026-05-10 16:09:46 +02:00
parent f8652d236a
commit 5bb8a2c23d
24 changed files with 995 additions and 115 deletions

View File

@@ -1240,6 +1240,10 @@ public final class Configuration {
@ConfigurationDefaultBoolean(true)
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> showLoadingSpinner = null;
@ConfigurationDefaultString("")
@ConfigurationName("xmlExport.formats")
public static ConfigurationItem<String> lastSelectedXmlExportFormats = null;
private static Map<String, String> configurationDescriptions = new LinkedHashMap<>();
private static Map<String, String> configurationTitles = new LinkedHashMap<>();

View File

@@ -56,6 +56,41 @@ import javax.imageio.ImageIO;
* @author JPEXS
*/
public class ImageExporter {
private static ImageFormat getExportFormat(ImageTag imageTag, ImageExportSettings settings) {
ImageFormat fileFormat = imageTag.getOriginalImageFormat();
boolean hasSeparateAlpha = false;
if (imageTag instanceof HasSeparateAlphaChannel) {
HasSeparateAlphaChannel hsac = (HasSeparateAlphaChannel) imageTag;
hasSeparateAlpha = hsac.hasAlphaChannel();
}
if (settings.mode == ImageExportMode.PNG_GIF_JPEG && hasSeparateAlpha) {
fileFormat = ImageFormat.PNG;
}
if (settings.mode == ImageExportMode.PNG) {
fileFormat = ImageFormat.PNG;
}
if (settings.mode == ImageExportMode.JPEG) {
fileFormat = ImageFormat.JPEG;
}
if (settings.mode == ImageExportMode.BMP) {
fileFormat = ImageFormat.BMP;
}
if (settings.mode == ImageExportMode.WEBP) {
fileFormat = ImageFormat.WEBP;
}
return fileFormat;
}
public static String getExportExtension(ImageTag imageTag, ImageExportSettings settings) {
ImageFormat fileFormat = getExportFormat(imageTag, settings);
return ImageHelper.getImageFormatString(fileFormat);
}
public List<File> exportImages(AbortRetryIgnoreHandler handler, String outdir, ReadOnlyTagList tags, ImageExportSettings settings, EventListener evl) throws IOException, InterruptedException {
List<File> ret = new ArrayList<>();
@@ -90,31 +125,9 @@ public class ImageExporter {
final ImageTag imageTag = (ImageTag) t;
ImageFormat fileFormat = imageTag.getOriginalImageFormat();
ImageFormat originalFormat = fileFormat;
boolean hasSeparateAlpha = false;
if (imageTag instanceof HasSeparateAlphaChannel) {
HasSeparateAlphaChannel hsac = (HasSeparateAlphaChannel) imageTag;
hasSeparateAlpha = hsac.hasAlphaChannel();
}
if (settings.mode == ImageExportMode.PNG_GIF_JPEG && hasSeparateAlpha) {
fileFormat = ImageFormat.PNG;
}
if (settings.mode == ImageExportMode.PNG) {
fileFormat = ImageFormat.PNG;
}
if (settings.mode == ImageExportMode.JPEG) {
fileFormat = ImageFormat.JPEG;
}
if (settings.mode == ImageExportMode.BMP) {
fileFormat = ImageFormat.BMP;
}
if (settings.mode == ImageExportMode.WEBP) {
fileFormat = ImageFormat.WEBP;
}
ImageFormat originalFormat = imageTag.getOriginalImageFormat();
ImageFormat fileFormat = getExportFormat(imageTag, settings);
final File file = new File(outdir + File.separator + Helper.makeFileName(imageTag.getCharacterExportFileName() + "." + ImageHelper.getImageFormatString(fileFormat)));

View File

@@ -64,6 +64,27 @@ import java.util.Set;
*/
public class SoundExporter {
public static String getExportExtension(SoundTag soundTag, SoundExportSettings settings) {
String ext = "wav";
SoundFormat fmt = soundTag.getSoundFormat();
switch (fmt.getNativeExportFormat()) {
case MP3:
if (settings.mode.hasMP3()) {
ext = "mp3";
}
break;
case FLV:
if (settings.mode.hasFlv()) {
ext = "flv";
}
break;
}
if (settings.mode == SoundExportMode.FLV) {
ext = "flv";
}
return ext;
}
public List<File> exportSounds(AbortRetryIgnoreHandler handler, String outdir, ReadOnlyTagList tags, final SoundExportSettings settings, EventListener evl) throws IOException, InterruptedException {
List<SoundTag> sounds = new ArrayList<>();
for (Tag t : tags) {
@@ -72,7 +93,7 @@ public class SoundExporter {
}
}
return exportSounds(handler, outdir, sounds, settings, evl);
}
}
public List<File> exportSounds(AbortRetryIgnoreHandler handler, String outdir, List<SoundTag> tags, final SoundExportSettings settings, EventListener evl) throws IOException, InterruptedException {
List<File> ret = new ArrayList<>();
@@ -97,24 +118,7 @@ public class SoundExporter {
evl.handleExportingEvent("sound", currentIndex, tags.size(), st.getName());
}
String ext = ".wav";
SoundFormat fmt = st.getSoundFormat();
switch (fmt.getNativeExportFormat()) {
case MP3:
if (settings.mode.hasMP3()) {
ext = ".mp3";
}
break;
case FLV:
if (settings.mode.hasFlv()) {
ext = ".flv";
}
break;
}
if (settings.mode == SoundExportMode.FLV) {
ext = ".flv";
}
String ext = "." + getExportExtension(st, settings);
final File file = new File(outdir + File.separator + Helper.makeFileName(st.getCharacterExportFileName()) + ext);
new RetryTask(() -> {
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2010-2026 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.exporters.settings;
import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.exporters.modes.SoundExportMode;
/**
*
* @author JPEXS
*/
public class XmlSwfExportSettings {
public ScriptExportMode as12ExportMode;
public ImageExportMode imageExportMode;
public SoundExportMode defineSoundExportMode;
public XmlSwfExportSettings() {
}
public XmlSwfExportSettings(ScriptExportMode as12ExportMode, ImageExportMode imageExportMode, SoundExportMode defineSoundExportMode) {
if (as12ExportMode != null && as12ExportMode != ScriptExportMode.AS) {
throw new IllegalArgumentException("Unsupported script export mode");
}
this.as12ExportMode = as12ExportMode;
if (
imageExportMode != null
&& imageExportMode != ImageExportMode.PNG_GIF_JPEG
&& imageExportMode != ImageExportMode.PNG_GIF_JPEG_ALPHA
) {
throw new IllegalArgumentException("Unsupported image export mode");
}
this.imageExportMode = imageExportMode;
if (defineSoundExportMode != null && defineSoundExportMode != SoundExportMode.MP3_WAV_FLV) {
throw new IllegalArgumentException("Unsupported sound export mode");
}
this.defineSoundExportMode = defineSoundExportMode;
}
}

View File

@@ -16,12 +16,31 @@
*/
package com.jpexs.decompiler.flash.exporters.swf;
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
import com.jpexs.decompiler.flash.ApplicationInfo;
import com.jpexs.decompiler.flash.EventListener;
import com.jpexs.decompiler.flash.ReadOnlyTagList;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.exporters.ImageExporter;
import com.jpexs.decompiler.flash.exporters.SoundExporter;
import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.exporters.script.AS2ScriptExporter;
import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings;
import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings;
import com.jpexs.decompiler.flash.exporters.settings.SoundExportSettings;
import com.jpexs.decompiler.flash.exporters.settings.XmlSwfExportSettings;
import com.jpexs.decompiler.flash.helpers.InternalClass;
import com.jpexs.decompiler.flash.helpers.LazyObject;
import com.jpexs.decompiler.flash.tags.DefineButtonTag;
import com.jpexs.decompiler.flash.tags.DefineSoundTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.UnknownTag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.ButtonAction;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.base.SoundTag;
import com.jpexs.decompiler.flash.types.annotations.Conditional;
import com.jpexs.decompiler.flash.types.annotations.Internal;
import com.jpexs.decompiler.flash.types.annotations.parser.AnnotationParseException;
@@ -39,8 +58,12 @@ import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
@@ -62,9 +85,11 @@ public class SwfXmlExporter {
*/
public static final int XML_EXPORT_VERSION_MAJOR = 2;
public static final int XML_EXPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES = 3;
/**
* XML export version minor.
*
*
* Version 2 - export only fields that meet conditions
*/
public static final int XML_EXPORT_VERSION_MINOR = 2;
@@ -81,16 +106,100 @@ public class SwfXmlExporter {
* @throws IOException On I/O error
*/
public void exportXml(SWF swf, File outFile) throws IOException {
exportXml(swf, outFile, new XmlSwfExportSettings(), null, new AbortRetryIgnoreHandler() {
@Override
public int handle(Throwable thrown) {
return AbortRetryIgnoreHandler.ABORT;
}
@Override
public AbortRetryIgnoreHandler getNewInstance() {
return this;
}
});
}
/**
* Exports SWF to XML.
*
* @param swf SWF to export
* @param outFile Target file to save to
* @param settings Export settings
* @param evl Event listener
* @param handler Abort/Retry/Ignore handler
*
* @throws IOException On I/O error
*/
public void exportXml(SWF swf, File outFile, XmlSwfExportSettings settings, EventListener evl, AbortRetryIgnoreHandler handler) throws IOException {
try {
File tmp = File.createTempFile("FFDEC", "XML");
String assetsDirName = outFile.getName();
if (assetsDirName.contains(".")) {
assetsDirName = assetsDirName.substring(0, assetsDirName.lastIndexOf("."));
}
assetsDirName = assetsDirName + "_assets";
Map<ASMSource, String> asmExternalFiles = new HashMap<>();
if (settings.as12ExportMode != null) {
Map<String, ASMSource> externalNameToAsm = swf.getASMs(true);
Set<String> existingNames = new HashSet<>();
for (String key : externalNameToAsm.keySet()) {
ASMSource asm = externalNameToAsm.get(key);
String currentOutDir = key + "/";
currentOutDir = new File(currentOutDir).getParentFile().toString();
currentOutDir = currentOutDir.replace("\\", "/");
if (!"/".equals(currentOutDir)) {
currentOutDir += "/";
}
String name = Helper.makeFileName(asm.getExportFileName());
int i = 1;
String baseName = name;
while (existingNames.contains(currentOutDir + name)) {
i++;
name = baseName + "_" + i;
}
existingNames.add(currentOutDir + name);
asmExternalFiles.put(asm, assetsDirName + "/scripts" + currentOutDir + name + ".as");
}
}
Map<Tag, String> tagExternalFiles = new IdentityHashMap<>();
List<Tag> imagesList = new ArrayList<>();
if (settings.imageExportMode != null) {
ImageExportSettings imageExportSetttings = new ImageExportSettings(settings.imageExportMode);
Map<Integer, CharacterTag> chars = swf.getCharacters(false);
for (int charId : chars.keySet()) {
CharacterTag ch = chars.get(charId);
if (ch instanceof ImageTag) {
ImageTag imageTag = (ImageTag) ch;
tagExternalFiles.put(imageTag, assetsDirName + "/images/" + Helper.makeFileName(imageTag.getCharacterExportFileName()) + "." + ImageExporter.getExportExtension(imageTag, imageExportSetttings));
imagesList.add(imageTag);
}
}
}
List<SoundTag> soundList = new ArrayList<>();
if (settings.defineSoundExportMode != null) {
SoundExportSettings soundExportSetttings = new SoundExportSettings(settings.defineSoundExportMode);
Map<Integer, CharacterTag> chars = swf.getCharacters(false);
for (int charId : chars.keySet()) {
CharacterTag ch = chars.get(charId);
if (ch instanceof DefineSoundTag) {
DefineSoundTag soundTag = (DefineSoundTag) ch;
tagExternalFiles.put(soundTag, assetsDirName + "/sounds/" + Helper.makeFileName(soundTag.getCharacterExportFileName()) + "." + SoundExporter.getExportExtension(soundTag, soundExportSetttings));
soundList.add(soundTag);
}
}
}
try (Writer writer = new Utf8OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(tmp)))) {
XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
xmlWriter.writeStartDocument();
xmlWriter.writeComment("\r\nWARNING: The structure of this XML is not final.\r\nIn later versions of FFDec it can be changed.\r\nMake sure you use compatible reader/writer based on _xmlExportMajor/_xmlExportMinor keys.\r\n");
exportXml(swf, xmlWriter);
exportXml(asmExternalFiles, tagExternalFiles, swf, xmlWriter);
xmlWriter.writeEndDocument();
xmlWriter.flush();
@@ -106,6 +215,27 @@ public class SwfXmlExporter {
logger.log(Level.SEVERE, "Cannot prettyformat XML");
}
tmp.delete();
if (settings.as12ExportMode != null) {
AS2ScriptExporter exporter = new AS2ScriptExporter();
exporter.exportActionScript2(swf, handler, outFile.getParentFile().toPath().resolve(assetsDirName + "/scripts").toFile().getAbsolutePath(), new ScriptExportSettings(settings.as12ExportMode, false, false, false, false), true, evl);
}
if (settings.imageExportMode != null) {
ImageExporter exporter = new ImageExporter();
try {
exporter.exportImages(handler, outFile.getParentFile().toPath().resolve(assetsDirName + "/images").toFile().getAbsolutePath(), new ReadOnlyTagList(imagesList), new ImageExportSettings(settings.imageExportMode), evl);
} catch (InterruptedException ex) {
return;
}
}
if (settings.defineSoundExportMode != null) {
SoundExporter exporter = new SoundExporter();
try {
exporter.exportSounds(handler, outFile.getParentFile().toPath().resolve(assetsDirName + "/sounds").toFile().getAbsolutePath(), soundList, new SoundExportSettings(settings.defineSoundExportMode), evl);
} catch (InterruptedException ex) {
return;
}
}
} catch (XMLStreamException ex) {
logger.log(Level.SEVERE, null, ex);
}
@@ -114,13 +244,30 @@ public class SwfXmlExporter {
/**
* Exports SWF to XML.
*
* @param asmExternalFiles ASM external files
* @param tagExternalFiles Tag external files
* @param swf SWF to export
* @param writer XML writer
* @throws IOException On I/O error
* @throws XMLStreamException On XML error
*/
public void exportXml(SWF swf, XMLStreamWriter writer) throws IOException, XMLStreamException {
generateXml(swf, null, writer, "swf", swf, false);
private void exportXml(
Map<ASMSource, String> asmExternalFiles,
Map<Tag, String> tagExternalFiles,
SWF swf,
XMLStreamWriter writer
) throws IOException, XMLStreamException {
generateXml(
asmExternalFiles.isEmpty() && tagExternalFiles.isEmpty() ? XML_EXPORT_VERSION_MAJOR : XML_EXPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES,
asmExternalFiles,
tagExternalFiles,
swf,
null,
writer,
"swf",
swf,
false
);
}
public List<Field> getSwfFieldsCached(Class cls) {
@@ -173,7 +320,17 @@ public class SwfXmlExporter {
return cls != null && (cls.isArray() || List.class.isAssignableFrom(cls));
}
private void generateXml(SWF swf, Tag currentTag, XMLStreamWriter writer, String name, Object obj, boolean isListItem) throws XMLStreamException {
private void generateXml(
int major,
Map<ASMSource, String> asmExternalFiles,
Map<Tag, String> tagExternalFiles,
SWF swf,
Tag currentTag,
XMLStreamWriter writer,
String name,
Object obj,
boolean isListItem
) throws XMLStreamException {
Class cls = obj != null ? obj.getClass() : null;
/*if (obj != null && cls == String.class) {
@@ -221,7 +378,7 @@ public class SwfXmlExporter {
writer.writeStartElement(name);
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
generateXml(swf, currentTag, writer, "item", Array.get(value, i), true);
generateXml(major, asmExternalFiles, tagExternalFiles, swf, currentTag, writer, "item", Array.get(value, i), true);
}
writer.writeEndElement();
} else if (obj != null) {
@@ -239,16 +396,12 @@ public class SwfXmlExporter {
writer.writeStartElement(name);
if (obj instanceof SWF) {
writer.writeAttribute("_xmlExportMajor", "" + XML_EXPORT_VERSION_MAJOR);
writer.writeAttribute("_xmlExportMajor", "" + major);
writer.writeAttribute("_xmlExportMinor", "" + XML_EXPORT_VERSION_MINOR);
writer.writeAttribute("_generator", ApplicationInfo.applicationVerName);
swf = (SWF) obj;
}
if (obj instanceof Tag) {
currentTag = (Tag) obj;
}
writer.writeAttribute("type", clazz.getSimpleName());
if (obj instanceof UnknownTag) {
@@ -258,8 +411,23 @@ public class SwfXmlExporter {
writer.writeAttribute("charset", ((SWF) obj).getCharset());
}
boolean isExternal = false;
if (obj instanceof Tag) {
currentTag = (Tag) obj;
if (tagExternalFiles.containsKey((Tag) obj)) {
writer.writeAttribute("_externalFile", tagExternalFiles.get((Tag) obj));
isExternal = true;
}
}
for (Field f : fields) {
//Multiline multilineA = f.getAnnotation(Multiline.class);
//Multiline multilineA = f.getAnnotation(Multiline.class);
if (isExternal && !"characterID".equals(f.getName()) && !"soundId".equals(f.getName())) {
continue;
}
Conditional cond = f.getAnnotation(Conditional.class);
if (cond != null) {
@@ -297,7 +465,26 @@ public class SwfXmlExporter {
try {
f.setAccessible(true);
generateXml(swf, currentTag, writer, f.getName(), f.get(obj), false);
Object value = f.get(obj);
if ("actionBytes".equals(f.getName())) {
if (obj instanceof ASMSource && asmExternalFiles.containsKey((ASMSource) obj)) {
value = new ByteArrayRange("00");
writer.writeAttribute("_externalActions", asmExternalFiles.get((ASMSource) obj));
} else if (obj instanceof DefineButtonTag) {
for (ASMSource s : asmExternalFiles.keySet()) {
if (s instanceof ButtonAction) {
ButtonAction ba = (ButtonAction) s;
if (ba.getSourceTag() == obj) {
value = new ByteArrayRange("00");
writer.writeAttribute("_externalActions", asmExternalFiles.get(s));
break;
}
}
}
}
}
generateXml(major, asmExternalFiles, tagExternalFiles, swf, currentTag, writer, f.getName(), value, false);
} catch (IllegalArgumentException | IllegalAccessException ex) {
logger.log(Level.SEVERE, null, ex);
}

View File

@@ -44,7 +44,6 @@ public class AS2ScriptImporter {
private static final Logger logger = Logger.getLogger(AS2ScriptImporter.class.getName());
/**
* Constructor.
*/
@@ -52,8 +51,58 @@ public class AS2ScriptImporter {
}
/**
* Imports actionScript 1/2 (not P-code) from given file
*
* @param fileName File to import
* @param asm Target to import into
* @param listener Import listener
* @return True on success
* @throws InterruptedException
*/
public boolean importActionScript(String fileName, ASMSource asm, ScriptImporterProgressListener listener) throws InterruptedException {
asm.getSwf().informListeners("importing_as", fileName);
String txt = Helper.readTextFile(fileName);
ActionScript2Parser par = new ActionScript2Parser(asm.getSwf(), asm);
boolean errored = false;
try {
asm.setActions(par.actionsFromString(txt, asm.getSwf().getCharset()));
} catch (ValueTooLargeException ex) {
logger.log(Level.SEVERE, "Script or some of its functions are too large, file: {0}", fileName);
errored = true;
} catch (ActionParseException ex) {
logger.log(Level.SEVERE, "%error% on line %line%, file: %file%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)).replace("%file%", fileName), ex);
errored = true;
} catch (CompilationException ex) {
logger.log(Level.SEVERE, "%error% on line %line%, file: %file%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)).replace("%file%", fileName), ex);
errored = true;
} catch (IOException ex) {
logger.log(Level.SEVERE, "error during script import, file: %file%".replace("%file%", fileName), ex);
errored = true;
} catch (InterruptedException ex) {
throw ex;
} catch (Exception ex) {
logger.log(Level.SEVERE, "error during script import, file: %file%".replace("%file%", fileName), ex);
errored = true;
}
if (!errored) {
asm.setModified();
if (listener != null) {
listener.scriptImported();
}
} else {
if (listener != null) {
listener.scriptImportError();
}
}
return !errored;
}
/**
* Imports scripts from given folder.
*
* @param scriptsFolder Folder with scripts
* @param asms Map of ASMSource objects
* @return Number of imported scripts
@@ -65,6 +114,7 @@ public class AS2ScriptImporter {
/**
* Imports scripts from given folder.
*
* @param scriptsFolder Folder with scripts
* @param asms Map of ASMSource objects
* @param listener Progress listener
@@ -104,42 +154,12 @@ public class AS2ScriptImporter {
String fileName = Path.combine(currentOutDir, name) + ".as";
if (new File(fileName).exists()) {
asm.getSwf().informListeners("importing_as", fileName);
String txt = Helper.readTextFile(fileName);
ActionScript2Parser par = new ActionScript2Parser(asm.getSwf(), asm);
boolean errored = false;
try {
asm.setActions(par.actionsFromString(txt, asm.getSwf().getCharset()));
} catch (ValueTooLargeException ex) {
logger.log(Level.SEVERE, "Script or some of its functions are too large, file: {0}", fileName);
errored = true;
} catch (ActionParseException ex) {
logger.log(Level.SEVERE, "%error% on line %line%, file: %file%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)).replace("%file%", fileName), ex);
errored = true;
} catch (CompilationException ex) {
logger.log(Level.SEVERE, "%error% on line %line%, file: %file%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)).replace("%file%", fileName), ex);
errored = true;
} catch (IOException ex) {
logger.log(Level.SEVERE, "error during script import, file: %file%".replace("%file%", fileName), ex);
errored = true;
if (importActionScript(fileName, asm, listener)) {
importCount++;
}
} catch (InterruptedException ex) {
return importCount;
} catch (Exception ex) {
logger.log(Level.SEVERE, "error during script import, file: %file%".replace("%file%", fileName), ex);
errored = true;
}
if (!errored) {
asm.setModified();
importCount++;
if (listener != null) {
listener.scriptImported();
}
} else {
if (listener != null) {
listener.scriptImportError();
}
}
}

View File

@@ -37,11 +37,17 @@ import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter;
import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst;
import com.jpexs.decompiler.flash.abc.types.traits.Traits;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.exporters.swf.SwfXmlExporter;
import com.jpexs.decompiler.flash.tags.CSMSettingsTag;
import com.jpexs.decompiler.flash.tags.DefineButtonTag;
import com.jpexs.decompiler.flash.tags.DefineSoundTag;
import com.jpexs.decompiler.flash.tags.DefineSpriteTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.TagTypeInfo;
import com.jpexs.decompiler.flash.tags.UnknownTag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.base.SoundImportException;
import com.jpexs.decompiler.flash.types.ALPHABITMAPDATA;
import com.jpexs.decompiler.flash.types.ALPHACOLORMAPDATA;
import com.jpexs.decompiler.flash.types.ARGB;
@@ -108,12 +114,16 @@ import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.decompiler.flash.types.sound.SoundFormat;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.HashArrayList;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.IdentityKey;
import com.jpexs.helpers.ReflectionTools;
import com.jpexs.helpers.utf8.Utf8InputStreamReader;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
@@ -124,7 +134,9 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -145,7 +157,12 @@ public class SwfXmlImporter {
/**
* Maximum XML import version major.
*/
public static final int MAX_XML_IMPORT_VERSION_MAJOR = 2;
public static final int MAX_XML_IMPORT_VERSION_MAJOR = 3;
/**
* Minimum version for using external files - attributes _externalActions, _externalFile
*/
public static final int XML_IMPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES = 3;
private static final Logger logger = Logger.getLogger(SwfXmlImporter.class.getName());
@@ -221,11 +238,15 @@ public class SwfXmlImporter {
* Imports SWF from input stream.
* @param swf SWF object
* @param in Input stream
* @param directory Directory where XML resides for external files resolving
* @throws IOException On I/O error
*/
public void importSwf(SWF swf, InputStream in) throws IOException {
public void importSwf(SWF swf, InputStream in, File directory) throws IOException {
XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
Map<IdentityKey<Object>, String> asmExternalActions = new LinkedHashMap<>();
Map<IdentityKey<Tag>, String> tagExternalFiles = new LinkedHashMap<>();
try {
try (Reader reader = new Utf8InputStreamReader(new BufferedInputStream(in))) {
XMLStreamReader xmlReader = xmlFactory.createXMLStreamReader(reader);
@@ -233,7 +254,7 @@ public class SwfXmlImporter {
xmlReader.nextTag();
xmlReader.require(XMLStreamConstants.START_ELEMENT, null, "swf");
processElement(xmlReader, swf, swf, null, MAX_XML_IMPORT_VERSION_MAJOR);
processElement(xmlReader, swf, swf, null, MAX_XML_IMPORT_VERSION_MAJOR, asmExternalActions, tagExternalFiles);
}
swf.clearAllCache();
@@ -241,16 +262,78 @@ public class SwfXmlImporter {
} catch (XMLStreamException ex) {
logger.log(Level.SEVERE, null, ex);
}
if (!asmExternalActions.isEmpty()) {
for (IdentityKey<Object> objKey : asmExternalActions.keySet()) {
ASMSource asm = null;
String fileName = asmExternalActions.get(objKey);
Object obj = objKey.get();
if (obj instanceof ASMSource) {
asm = (ASMSource) obj;
}
if (obj instanceof DefineButtonTag) {
DefineButtonTag defineButton = (DefineButtonTag) obj;
asm = defineButton.getSubItems().get(0);
}
if (asm != null) {
AS2ScriptImporter importer = new AS2ScriptImporter();
try {
importer.importActionScript(directory.toPath().resolve(fileName).toFile().getAbsolutePath(), asm, null);
} catch (InterruptedException ex) {
break;
}
}
}
}
if (!tagExternalFiles.isEmpty()) {
for (IdentityKey<Tag> tagKey : tagExternalFiles.keySet()) {
String fileName = tagExternalFiles.get(tagKey);
Tag tag = tagKey.get();
if (tag == null) {
continue;
}
if (tag instanceof ImageTag) {
ImageTag imageTag = (ImageTag) tag;
ImageImporter importer = new ImageImporter();
importer.importImage(imageTag, Helper.readFile(directory.toPath().resolve(fileName).toFile().getAbsolutePath()), -1);
String baseName = new File(fileName).getName();
if (baseName.contains(".")) {
baseName = baseName.substring(0, baseName.lastIndexOf("."));
}
String alphaFile = new File(fileName).getParentFile().getAbsolutePath() + "/" + baseName + ".alpha.png";
if (new File(alphaFile).exists()) {
importer.importImageAlpha(imageTag, Helper.readFile(alphaFile));
}
} else if (tag instanceof DefineSoundTag) {
DefineSoundTag defineSoundTag = (DefineSoundTag) tag;
SoundImporter importer = new SoundImporter();
int format = SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN;
if (fileName.toLowerCase(Locale.ENGLISH).endsWith(".mp3")) {
format = SoundFormat.FORMAT_MP3;
}
try (FileInputStream fis = new FileInputStream(directory.toPath().resolve(fileName).toFile().getAbsolutePath())) {
importer.importDefineSound(defineSoundTag, fis, format);
} catch (SoundImportException ex) {
logger.log(Level.SEVERE, "Cannot import sound", ex);
} catch (IOException ex) {
logger.log(Level.SEVERE, "Cannot read sound", ex);
}
} else {
logger.log(Level.WARNING, "Unrecognized tag type for external file: {0}", tag.getTagName());
}
}
}
}
private void setSwfAndTimelined(SWF swf) {
for (Tag t : swf.getTags()) {
t.setSwf(swf);
t.setSwf(swf, true);
t.setTimelined(swf);
if (t instanceof DefineSpriteTag) {
DefineSpriteTag s = (DefineSpriteTag) t;
for (Tag st : s.getTags()) {
st.setSwf(swf);
st.setSwf(swf, true);
st.setTimelined(s);
}
}
@@ -269,7 +352,7 @@ public class SwfXmlImporter {
XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
try {
XMLStreamReader reader = xmlFactory.createXMLStreamReader(new StringReader(xml));
return processObject(reader, requiredType, swf, null, 1);
return processObject(reader, requiredType, swf, null, 1, new HashMap<>(), new HashMap<>());
} catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException | InstantiationException
| InvocationTargetException | XMLStreamException ex) {
Logger.getLogger(SwfXmlImporter.class.getName()).log(Level.SEVERE, null, ex);
@@ -311,7 +394,7 @@ public class SwfXmlImporter {
}*/
}
private void processElement(XMLStreamReader reader, Object obj, SWF swf, Tag tag, int xmlExportMajor) throws XMLStreamException {
private void processElement(XMLStreamReader reader, Object obj, SWF swf, Tag tag, int xmlExportMajor, Map<IdentityKey<Object>, String> asmExternalActions, Map<IdentityKey<Tag>, String> tagExternalFiles) throws XMLStreamException {
// Check if element started and start if needed
if (!reader.isStartElement()) {
reader.nextTag();
@@ -369,6 +452,30 @@ public class SwfXmlImporter {
if (name.equals("reserved3") && "FileAttributesTag".equals(attributes.get("type"))) {
name = "reservedB";
}
if (name.equals("_externalActions")) {
if (xmlExportMajor < XML_IMPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES) {
logger.log(Level.WARNING, "For _externalActions attribute _xmlExportMajor must be >= 3. The attribute is ignored.");
continue;
}
asmExternalActions.put(new IdentityKey<>(obj), val);
continue;
}
if (obj instanceof Tag && name.equals("_externalFile")) {
if (xmlExportMajor < XML_IMPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES) {
logger.log(Level.WARNING, "For _externalFile attribute _xmlExportMajor must be >= 3. The attribute is ignored.");
continue;
}
tagExternalFiles.put(new IdentityKey<>((Tag) obj), val);
continue;
}
if (name.equals("actionBytes") && attributes.containsKey("_externalActions")) {
if (xmlExportMajor >= XML_IMPORT_VERSION_MAJOR_WITH_EXTERNAL_FILES) {
continue;
}
}
if (!name.equals("type")) {
try {
@@ -397,7 +504,7 @@ public class SwfXmlImporter {
// Check for list item elements
reader.nextTag();
while (reader.isStartElement()) {
Object childObj = processObject(reader, reqType, swf, tag, xmlExportMajor);
Object childObj = processObject(reader, reqType, swf, tag, xmlExportMajor, asmExternalActions, tagExternalFiles);
list.add(childObj);
reader.nextTag();
@@ -414,7 +521,7 @@ public class SwfXmlImporter {
setFieldValue(field, obj, value);
} else {
Object childObj = processObject(reader, null, swf, tag, xmlExportMajor);
Object childObj = processObject(reader, null, swf, tag, xmlExportMajor, asmExternalActions, tagExternalFiles);
setFieldValue(field, obj, childObj);
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException
@@ -432,7 +539,7 @@ public class SwfXmlImporter {
}
}
private Object processObject(XMLStreamReader reader, Class requiredType, SWF swf, Tag tag, int xmlExportMajor) throws IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, XMLStreamException {
private Object processObject(XMLStreamReader reader, Class requiredType, SWF swf, Tag tag, int xmlExportMajor, Map<IdentityKey<Object>, String> asmExternalActions, Map<IdentityKey<Tag>, String> tagExternalFiles) throws IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException, XMLStreamException {
// Check if element started and start if needed
if (!reader.isStartElement()) {
reader.nextTag();
@@ -465,7 +572,7 @@ public class SwfXmlImporter {
tag = (Tag) childObj;
}
processElement(reader, childObj, swf, tag, xmlExportMajor);
processElement(reader, childObj, swf, tag, xmlExportMajor, asmExternalActions, tagExternalFiles);
ret = childObj;
} else {
String isNullAttr = attributes.get("isNull");

View File

@@ -377,4 +377,17 @@ public class DefineButton2Tag extends ButtonTag implements ASMSourceContainer {
needed.add(rec.characterId);
}
}
@Override
public void setSwf(SWF swf, boolean deep) {
super.setSwf(swf, deep);
if (deep) {
if (actions != null) {
for (BUTTONCONDACTION action : actions) {
action.setSourceTag(this);
}
}
}
}
}

View File

@@ -329,5 +329,5 @@ public class DefineButtonTag extends ButtonTag implements ASMSourceContainer {
for (BUTTONRECORD rec : characters) {
needed.add(rec.characterId);
}
}
}
}

View File

@@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3Value;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.types.CLIPACTIONRECORD;
import com.jpexs.decompiler.flash.types.CLIPACTIONS;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.MATRIX;
@@ -361,4 +362,19 @@ public abstract class PlaceObjectTypeTag extends Tag implements CharacterIdTag,
result += "_" + getDepth();
return result;
}
@Override
public void setSwf(SWF swf, boolean deep) {
super.setSwf(swf, deep);
if (deep) {
CLIPACTIONS clipActions = getClipActions();
if (clipActions != null) {
for (CLIPACTIONRECORD rec : clipActions.clipActionRecords) {
rec.setParentClipActions(clipActions);
rec.setSourceTag(this);
}
}
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2010-2026 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.helpers;
/**
*
* @author JPEXS
*/
public class IdentityKey<T> {
private final T value;
public IdentityKey(T value) {
this.value = value;
}
@Override
public int hashCode() {
return System.identityHashCode(value);
}
@Override
public boolean equals(Object obj) {
return obj instanceof IdentityKey
&& ((IdentityKey<?>) obj).value == value;
}
public T get() {
return value;
}
}

View File

@@ -98,7 +98,7 @@ public class SwfXmlExportImportTest extends FileTestBase {
SWF swf2 = new SWF();
try ( FileInputStream fis = new FileInputStream(outFile)) {
new SwfXmlImporter().importSwf(swf2, fis);
new SwfXmlImporter().importSwf(swf2, fis, outFile.getParentFile());
}
if (swf.getTags().size() != swf2.getTags().size()) {

View File

@@ -102,6 +102,7 @@ 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.settings.XmlSwfExportSettings;
import com.jpexs.decompiler.flash.exporters.swf.SwfToSwcExporter;
import com.jpexs.decompiler.flash.exporters.swf.SwfXmlExporter;
import com.jpexs.decompiler.flash.flexsdk.MxmlcAs3ScriptReplacer;
@@ -701,7 +702,7 @@ public class CommandLineArgumentParser {
parseDecrypt(args);
System.exit(0);
} else if (command.equals("swf2xml")) {
parseSwf2Xml(args, charset);
parseSwf2Xml(args, charset, handler);
System.exit(0);
} else if (command.equals("xml2swf")) {
parseXml2Swf(args, charset);
@@ -2711,15 +2712,58 @@ public class CommandLineArgumentParser {
System.exit(result ? 0 : 1);
}
private static void parseSwf2Xml(Stack<String> args, String charset) {
private static void parseSwf2Xml(Stack<String> args, String charset, AbortRetryIgnoreHandler handler) {
if (args.size() < 2) {
badArguments("swf2xml");
}
ScriptExportMode scriptExportMode = null;
ImageExportMode imageExportMode = null;
SoundExportMode soundExportMode = null;
String arg = args.pop();
if ("-external".equals(arg.toLowerCase(Locale.ENGLISH))) {
if (args.size() < 3) {
badArguments("swf2xml");
}
String ext = args.pop();
String[] parts = ext.split(",", -1);
for (String part : parts) {
switch (part) {
case "as12script":
case "as12script:as":
scriptExportMode = ScriptExportMode.AS;
break;
case "image:png_gif_jpeg":
imageExportMode = ImageExportMode.PNG_GIF_JPEG;
break;
case "image":
case "image:png_gif_jpeg_alpha":
imageExportMode = ImageExportMode.PNG_GIF_JPEG_ALPHA;
break;
case "definesound":
case "definesound:mp3_wav_flv":
soundExportMode = SoundExportMode.MP3_WAV_FLV;
break;
case "all":
scriptExportMode = ScriptExportMode.AS;
imageExportMode = ImageExportMode.PNG_GIF_JPEG_ALPHA;
soundExportMode = SoundExportMode.MP3_WAV_FLV;
break;
default:
System.err.println("Unsupported external value: \"" + part + "\"");
badArguments("swf2xml");
}
}
} else {
args.push(arg);
}
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()));
new SwfXmlExporter().exportXml(swf, new File(args.pop()), new XmlSwfExportSettings(scriptExportMode, imageExportMode, soundExportMode), null, handler);
} catch (FileNotFoundException ex) {
System.err.println("File not found.");
System.exit(1);
@@ -2738,8 +2782,10 @@ public class CommandLineArgumentParser {
try {
SWF swf = new SWF(charset);
try (StdInAwareFileInputStream in = new StdInAwareFileInputStream(args.pop())) {
new SwfXmlImporter().importSwf(swf, in);
String fileName = args.pop();
try (StdInAwareFileInputStream in = new StdInAwareFileInputStream(fileName)) {
File file = new File(StdInAwareFileInputStream.STDIN_PATH.equals(fileName) ? "." : fileName);
new SwfXmlImporter().importSwf(swf, in, file.getParentFile());
}
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(args.pop())))) {
swf.saveTo(fos);

View File

@@ -72,9 +72,19 @@ alias /?
Decrypt HARMAN Air encrypted file.
Decrypt HARMAN Air encrypted file <infile> and saves it to <outfile>.
-swf2xml <infile> <outfile>
-swf2xml [-external <formats>] <infile> <outfile>
Convert SWF to XML.
Convert the <infile> SWF to <outfile> XML file.
-external parameter sets which items will be available externally
Values for <formats>: Use comma separated list of following:
as12script:as
as12script (same as "as12script:as")
image:png_gif_jpeg
image:png_gif_jpeg_alpha
image (same as "image:png_gif_jpeg_alpha")
definesound:mp3_wav_flv
definesound (same as "definesound:mp3_wav_flv")
all (same as "as12script,image,definesound")
-xml2swf <infile> <outfile>
Convert XML to SWF.
@@ -448,7 +458,7 @@ Pre-options:
When it has .bin extension, legacy storage is used.
-onerror (abort|retry <N>|ignore)
Applies to: -export, -importScript
Applies to: -export, -importScript, -swf2xml
Error handling mode.
"abort" stops the exporting
"retry" tries the exporting N times

View File

@@ -92,6 +92,7 @@ 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.settings.XmlSwfExportSettings;
import com.jpexs.decompiler.flash.exporters.swf.SwfFlashDevelopExporter;
import com.jpexs.decompiler.flash.exporters.swf.SwfIntelliJIdeaExporter;
import com.jpexs.decompiler.flash.exporters.swf.SwfJavaExporter;
@@ -4618,6 +4619,12 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
public void exportSwfXml(List<TreeItem> items) {
View.checkAccess();
XmlExportDialog dialog = new XmlExportDialog(Main.getDefaultDialogsOwner());
if (dialog.showExportDialog() != AppDialog.OK_OPTION) {
return;
}
Set<SWF> usedOpenables = new LinkedHashSet<>();
Set<OpenableList> usedOpenableLists = new HashSet<>();
@@ -4678,6 +4685,9 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
return;
}
selFile = Helper.fixDialogFile(fc.getSelectedFile()).getAbsolutePath();
Configuration.lastExportDir.set(new File(selFile).getParentFile().getAbsolutePath());
if (!selFile.toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
selFile = selFile + ".xml";
}
@@ -4714,7 +4724,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
try {
new RetryTask(() -> {
File outFile = new File(selFile2);
new SwfXmlExporter().exportXml(openable, outFile);
XmlSwfExportSettings settings = new XmlSwfExportSettings(dialog.getEnumValue(ScriptExportMode.class), dialog.getEnumValue(ImageExportMode.class), dialog.getEnumValue(SoundExportMode.class));
new SwfXmlExporter().exportXml(openable, outFile, settings, openable.getExportEventListener(), new GuiAbortRetryIgnoreHandler());
}, handler).run();
} catch (IOException ex) {
@@ -4760,7 +4771,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
File selfile = Helper.fixDialogFile(selectedFile);
try {
try (FileInputStream fis = new FileInputStream(selfile)) {
new SwfXmlImporter().importSwf(swf, fis);
new SwfXmlImporter().importSwf(swf, fis, selfile.getParentFile());
}
swf.clearAllCache();
swf.assignExportNamesToSymbols();
@@ -4769,8 +4780,9 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
}
}
Main.stopWork();
}
}
}
public void renameIdentifiers(final Openable openable) {

View File

@@ -0,0 +1,279 @@
/*
* Copyright (C) 2010-2026 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 <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.gui;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.exporters.modes.SoundExportMode;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
/**
*
* @author JPEXS
*/
public class XmlExportDialog extends AppDialog {
private int result = ERROR_OPTION;
private final Map<Class<? extends Enum>, JComboBox<ComboValue>> combos = new HashMap<>();
private final Map<Class<? extends Enum>, String> enumNames = new HashMap<>();
private final List<Object> allDefaults = Arrays.asList(ScriptExportMode.AS, ImageExportMode.PNG_GIF_JPEG_ALPHA, SoundExportMode.MP3_WAV_FLV);
public XmlExportDialog(Window owner) {
super(owner);
enumNames.put(ScriptExportMode.class, "as12script");
enumNames.put(ImageExportMode.class, "image");
enumNames.put(SoundExportMode.class, "definesound");
setTitle(translate("dialog.title"));
JPanel buttonsPanel = new JPanel(new FlowLayout());
JButton okButton = new JButton(translate("button.ok"));
okButton.addActionListener(this::okButtonActionPerformed);
JButton cancelButton = new JButton(translate("button.cancel"));
cancelButton.addActionListener(this::cancelButtonActionPerformed);
buttonsPanel.add(okButton);
buttonsPanel.add(cancelButton);
JPanel centralPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(1, 2, 1, 2);
JLabel selectLabel = new JLabel(translate("selectExternal"));
gbc.gridwidth = 4;
centralPanel.add(selectLabel, gbc);
gbc.gridwidth = 1;
gbc.gridy++;
DefaultComboBoxModel<ComboValue> as12ScriptsModel = new DefaultComboBoxModel<>();
as12ScriptsModel.addElement(new ComboValue(null, translate("xml")));
as12ScriptsModel.addElement(new ComboValue(ScriptExportMode.AS, translateExport("scripts.as")));
addComboRow(centralPanel, gbc, translate("as12scripts"), "as", as12ScriptsModel, ScriptExportMode.class);
DefaultComboBoxModel<ComboValue> imageModel = new DefaultComboBoxModel<>();
imageModel.addElement(new ComboValue(null, translate("xml")));
imageModel.addElement(new ComboValue(ImageExportMode.PNG_GIF_JPEG, translateExport("images.png_gif_jpeg")));
imageModel.addElement(new ComboValue(ImageExportMode.PNG_GIF_JPEG_ALPHA, translateExport("images.png_gif_jpeg_alpha")));
addComboRow(centralPanel, gbc, translateExport("images"), "image", imageModel, ImageExportMode.class);
DefaultComboBoxModel<ComboValue> soundModel = new DefaultComboBoxModel<>();
soundModel.addElement(new ComboValue(null, translate("xml")));
soundModel.addElement(new ComboValue(SoundExportMode.MP3_WAV_FLV, translateExport("sounds.mp3_wav_flv")));
addComboRow(centralPanel, gbc, translate("defineSound"), "sound", soundModel, SoundExportMode.class);
String config = Configuration.lastSelectedXmlExportFormats.get();
if (!config.isEmpty()) {
String[] parts = config.split(",", -1);
for (String part : parts) {
String[] parts2 = part.split("\\.", -1);
if (parts2.length != 2) {
continue;
}
String name = parts2[0];
String value = parts2[1];
for (Class cls : combos.keySet()) {
if (enumNames.get(cls).equals(name)) {
JComboBox<ComboValue> combo = combos.get(cls);
DefaultComboBoxModel<ComboValue> model = (DefaultComboBoxModel<ComboValue>) combo.getModel();
for (int i = 0; i < model.getSize(); i++) {
ComboValue cv = model.getElementAt(i);
if (cv.value == null && value.equals("none")) {
combo.setSelectedIndex(0);
break;
}
if (cv.value != null && cv.value.toString().toLowerCase(Locale.ENGLISH).equals(value)) {
combo.setSelectedIndex(i);
break;
}
}
break;
}
}
}
}
JButton allButton = new JButton(translate("all"));
allButton.addActionListener(this::allButtonActionPerformed);
JButton noneButton = new JButton(translate("none"));
noneButton.addActionListener(this::noneButtonActionPerformed);
gbc.gridx = 2;
gbc.insets = new Insets(5, 2, 1, 2);
gbc.anchor = GridBagConstraints.CENTER;
centralPanel.add(noneButton, gbc);
gbc.gridx++;
centralPanel.add(allButton, gbc);
Container cnt = getContentPane();
cnt.setLayout(new BorderLayout());
cnt.add(centralPanel, BorderLayout.CENTER);
cnt.add(buttonsPanel, BorderLayout.SOUTH);
pack();
View.centerScreen(this);
View.setWindowIcon(this, "exportxml");
getRootPane().setDefaultButton(okButton);
setModal(true);
}
private void addComboRow(JPanel centralPanel, GridBagConstraints gbc, String label, String icon, ComboBoxModel<ComboValue> model, Class<? extends Enum> enumClass) {
gbc.fill = GridBagConstraints.NONE;
JLabel scriptsLabel = new JLabel(label);
scriptsLabel.setIcon(View.getIcon(icon + "16"));
scriptsLabel.setHorizontalTextPosition(SwingConstants.LEFT);
gbc.anchor = GridBagConstraints.LINE_END;
centralPanel.add(scriptsLabel, gbc);
gbc.gridx++;
JLabel arrowLabel = new JLabel(translateExport("arrow"));
gbc.insets = new Insets(1, 5, 1, 5);
gbc.anchor = GridBagConstraints.CENTER;
centralPanel.add(arrowLabel, gbc);
gbc.insets = new Insets(1, 2, 1, 2);
gbc.gridx++;
gbc.gridwidth = 2;
JComboBox<ComboValue> combo = new JComboBox<>(model);
gbc.anchor = GridBagConstraints.LINE_START;
gbc.fill = GridBagConstraints.BOTH;
combos.put(enumClass, combo);
centralPanel.add(combo, gbc);
gbc.gridy++;
gbc.gridwidth = 1;
gbc.gridx = 0;
gbc.fill = GridBagConstraints.NONE;
}
public <T extends Enum<T>> T getEnumValue(Class<T> cls) {
if (!combos.containsKey(cls)) {
return null;
}
ComboValue cv = (ComboValue) combos.get(cls).getSelectedItem();
if (cv == null) {
return null;
}
@SuppressWarnings("unchecked")
T ret = (T) cv.value;
return ret;
}
private void okButtonActionPerformed(ActionEvent evt) {
List<String> vals = new ArrayList<>();
for (Class cls : combos.keySet()) {
String name = enumNames.get(cls);
ComboValue cv = (ComboValue) combos.get(cls).getSelectedItem();
if (cv == null || cv.value == null) {
vals.add(name + "." + "none");
} else {
vals.add(name + "." + cv.value.toString().toLowerCase());
}
}
Configuration.lastSelectedXmlExportFormats.set(String.join(",", vals));
result = OK_OPTION;
setVisible(false);
}
private void noneButtonActionPerformed(ActionEvent evt) {
for (Class cls : combos.keySet()) {
JComboBox<ComboValue> combo = combos.get(cls);
combo.setSelectedIndex(0);
}
}
private void allButtonActionPerformed(ActionEvent evt) {
for (Class cls : combos.keySet()) {
JComboBox<ComboValue> combo = combos.get(cls);
DefaultComboBoxModel<ComboValue> model = (DefaultComboBoxModel<ComboValue>) combo.getModel();
for (int i = 0; i < model.getSize(); i++) {
ComboValue value = model.getElementAt(i);
if (value.value != null && allDefaults.contains(value.value)) {
combo.setSelectedIndex(i);
break;
}
}
}
}
private void cancelButtonActionPerformed(ActionEvent evt) {
result = CANCEL_OPTION;
setVisible(false);
}
public int showExportDialog() {
setVisible(true);
return result;
}
private String translateExport(String key) {
return AppDialog.translateForDialog(key, ExportDialog.class);
}
private class ComboValue {
public Object value;
public String text;
public ComboValue(Object value, String text) {
this.value = value;
this.text = text;
}
@Override
public String toString() {
return text;
}
}
}

View File

@@ -698,3 +698,6 @@ config.description.useMinimumStrokeWidth1Px = Use 1 pixel as minimal stroke widt
#after 26.0.0
config.name.showLoadingSpinner = Show loading spinner
config.description.showLoadingSpinner = Displays animated loading indicator in status bar
config.name.xmlExport.formats = (Internal) Xml export external formats
config.description.xmlExport.formats = Last used Xml export external formats.

View File

@@ -694,3 +694,10 @@ config.description.msaaGridForExport = Velikost m\u0159\u00ed\u017eky v\u00edcen
config.name.useMinimumStrokeWidth1Px = Minim\u00e1ln\u00ed \u0161\u00ed\u0159ka tahu 1 pixel (jako ve Flashi)
config.description.useMinimumStrokeWidth1Px = Pou\u017e\u00edt 1 pixel jako minim\u00e1ln\u00ed \u0161\u00ed\u0159ku tahu. Flash vykresluje tahy t\u00edmto zp\u016fsobem. Vypn\u011bte pro umo\u017en\u011bn\u00ed ten\u010d\u00edch tah\u016f.
#after 26.0.0
config.name.showLoadingSpinner = Zobrazit indik\u00e1tor na\u010d\u00edt\u00e1n\u00ed
config.description.showLoadingSpinner = Zobraz\u00ed animovan\u00fd indik\u00e1tor na\u010d\u00edt\u00e1n\u00ed ve stavov\u00e9m \u0159\u00e1dku
config.name.xmlExport.formats = (Intern\u00ed) Form\u00e1ty extern\u00edho XML exportu
config.description.xmlExport.formats = Naposledy pou\u017eit\u00e9 form\u00e1ty extern\u00edho XML exportu.

View File

@@ -623,3 +623,10 @@ config.description.msaaGridForExport = Rastergr\u00f6\u00dfe NxN pro Multisample
config.name.useMinimumStrokeWidth1Px = Minimale Strichbreite von 1 Pixel (wie in Flash)
config.description.useMinimumStrokeWidth1Px = 1 Pixel als minimale Strichbreite verwenden. Flash rendert Striche auf diese Weise. Deaktivieren, um d\u00fcnnere Striche zu erm\u00f6glichen.
#after 26.0.0
config.name.showLoadingSpinner = Ladeanzeige anzeigen
config.description.showLoadingSpinner = Zeigt eine animierte Ladeanzeige in der Statusleiste an
config.name.xmlExport.formats = (Intern) Formate f\u00fcr externen XML-Export
config.description.xmlExport.formats = Zuletzt verwendete Formate f\u00fcr externen XML-Export.

View File

@@ -694,3 +694,10 @@ config.description.msaaGridForExport = Ve\u013ekos\u0165 mrie\u017eky viacn\u00e
config.name.useMinimumStrokeWidth1Px = Minim\u00e1lna \u0161\u00edrka \u0165ahu 1 pixel (ako vo Flashi)
config.description.useMinimumStrokeWidth1Px = Pou\u017ei\u0165 1 pixel ako minim\u00e1lnu \u0161\u00edrku \u0165ahu. Flash vykres\u013euje \u0165ahy t\u00fdmto sp\u00f4sobom. Vypnite pre umo\u017enenie ten\u0161\u00edch \u0165ahov.
#after 26.0.0
config.name.showLoadingSpinner = Zobrazi\u0165 indik\u00e1tor na\u010d\u00edtania
config.description.showLoadingSpinner = Zobraz\u00ed animovan\u00fd indik\u00e1tor na\u010d\u00edtania v stavovom riadku
config.name.xmlExport.formats = (Intern\u00e9) Form\u00e1ty extern\u00e9ho XML exportu
config.description.xmlExport.formats = Naposledy pou\u017eit\u00e9 form\u00e1ty extern\u00e9ho XML exportu.

View File

@@ -0,0 +1,12 @@
dialog.title = Export XML
button.ok = OK
button.cancel = Cancel
selectExternal = Select items which will be available externally:
all = Select all
none = Select none
as12scripts = AS1/2 scripts
defineSound = DefineSounds
xml = XML (no external)

View File

@@ -0,0 +1,12 @@
dialog.title = Exportovat XML
button.ok = OK
button.cancel = Storno
selectExternal = Vyberte polo\u017eky, kter\u00e9 budou dostupn\u00e9 extern\u011b:
all = Vybrat v\u0161e
none = Nevybrat nic
as12scripts = AS1/2 skripty
defineSound = DefineSounds
xml = XML (ne extern\u00ed)

View File

@@ -0,0 +1,12 @@
dialog.title = XML exportieren
button.ok = OK
button.cancel = Abbrechen
selectExternal = W\u00e4hlen Sie die Elemente aus, die extern verf\u00fcgbar sein sollen:
all = Alles ausw\u00e4hlen
none = Nichts ausw\u00e4hlen
as12scripts = AS1/2-Skripte
defineSound = DefineSounds
xml = XML (nicht extern)

View File

@@ -0,0 +1,12 @@
dialog.title = Exportova\u0165 XML
button.ok = OK
button.cancel = Zru\u0161i\u0165
selectExternal = Vyberte polo\u017eky, ktor\u00e9 bud\u00fa dostupn\u00e9 externe:
all = Vybra\u0165 v\u0161etko
none = Nevybra\u0165 ni\u010d
as12scripts = AS1/2 skripty
defineSound = DefineSounds
xml = XML (nie extern\u00e9)