diff --git a/CHANGELOG.md b/CHANGELOG.md index 085474ab9..188ecf6c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ All notable changes to this project will be documented in this file. - "Starting Flash content debugger" in status bar when debugging starts - Simple editor - edit parameters of items inside buttons - Simple editor - add/remove frames in buttons, button timeline header +- Configuration is now stored in easily readable/editable textual format (TOML) + (saved also to older binary format, but loading is preffered from + the new TOML file, when exists) ### Fixed - [#2456] FLA export - NullPointer exception while exporting to CS4 or lower via commandline diff --git a/lib/antlr-runtime-4.11.1.jar b/lib/antlr-runtime-4.11.1.jar new file mode 100644 index 000000000..7d57b59c0 Binary files /dev/null and b/lib/antlr-runtime-4.11.1.jar differ diff --git a/lib/tomlj-1.1.1.jar b/lib/tomlj-1.1.1.jar new file mode 100644 index 000000000..68b70b1b6 Binary files /dev/null and b/lib/tomlj-1.1.1.jar differ diff --git a/libsrc/ffdec_lib/README.md b/libsrc/ffdec_lib/README.md index 0358dc39e..ae179feac 100644 --- a/libsrc/ffdec_lib/README.md +++ b/libsrc/ffdec_lib/README.md @@ -32,6 +32,8 @@ These include: * Miter clip - modified openjdk8 Stroker - `miterclip.jar` - Support for miter clip join style in shapes * FlexSDK Decimal128 class - `decimal.jar` - Working with decimal type in AS3 * FLA Compound Document Tools - `flacomdoc.jar` - Exporting FLA to CS4 or lower +* TomlJ - `tomlj-1.1.1.jar` - Storing configuration +* ANTLR - `antlr-runtime-4.11.1.jar` - Storing configuration ## Basic library usage ```java @@ -140,6 +142,8 @@ And also links to these libraries: * [Java Native Access - JNA] (Registry association, Process memory reading) - LGPL * [Open Imaging GIF Decoder] (GIF file importing) - Apache License 2.0 * [FLA Compound Document Tools] (Exporting to FLA CS4 and below) - LGPLv2.1 +* [TomlJ] (Storing configuration) - Apache License 2.0 +* [ANTLR] (Storing configuration) - BSD 3-Clause [sfntly]: https://code.google.com/p/sfntly/ [JLayer]: http://www.javazoom.net/javalayer/javalayer.html @@ -160,3 +164,5 @@ And also links to these libraries: [openjdk8 Stroker]: https://github.com/JetBrains/jdk8u_jdk [Apache Flex SDK]: https://github.com/apache/flex-sdk [FLA Compound Document Tools]: https://github.com/jindrapetrik/flacomdoc +[TomlJ]: https://github.com/tomlj/tomlj +[ANTLR]: https://www.antlr.org/ diff --git a/libsrc/ffdec_lib/lib/antlr-runtime-4.11.1.jar b/libsrc/ffdec_lib/lib/antlr-runtime-4.11.1.jar new file mode 100644 index 000000000..7d57b59c0 Binary files /dev/null and b/libsrc/ffdec_lib/lib/antlr-runtime-4.11.1.jar differ diff --git a/libsrc/ffdec_lib/lib/tomlj-1.1.1.jar b/libsrc/ffdec_lib/lib/tomlj-1.1.1.jar new file mode 100644 index 000000000..68b70b1b6 Binary files /dev/null and b/libsrc/ffdec_lib/lib/tomlj-1.1.1.jar differ diff --git a/libsrc/ffdec_lib/nbproject/project.xml b/libsrc/ffdec_lib/nbproject/project.xml index 6e1580dee..da10de318 100644 --- a/libsrc/ffdec_lib/nbproject/project.xml +++ b/libsrc/ffdec_lib/nbproject/project.xml @@ -242,7 +242,7 @@ auxiliary.show.customizer.message= src - ../../src;lib/LZMA.jar;lib/avi.jar;lib/cmykjpeg.jar;lib/ddsreader.jar;lib/gif.jar;lib/gnujpdf.jar;lib/jlayer-1.0.2.jar;lib/jpacker.jar;lib/nellymoser.jar;lib/sfntly.jar;lib/tga.jar;lib/ttf.jar;lib/vlcj-4.7.3.jar;lib/vlcj-natives-4.7.0.jar;lib/flashdebugger.jar;lib/jna-3.5.1.jar;lib/jna-platform-3.5.1.jar;lib/gifreader.jar;lib/miterstroke.jar;lib/decimal.jar;lib/flacomdoc.jar + ../../src;lib/LZMA.jar;lib/avi.jar;lib/cmykjpeg.jar;lib/ddsreader.jar;lib/gif.jar;lib/gnujpdf.jar;lib/jlayer-1.0.2.jar;lib/jpacker.jar;lib/nellymoser.jar;lib/sfntly.jar;lib/tga.jar;lib/ttf.jar;lib/vlcj-4.7.3.jar;lib/vlcj-natives-4.7.0.jar;lib/flashdebugger.jar;lib/jna-3.5.1.jar;lib/jna-platform-3.5.1.jar;lib/gifreader.jar;lib/miterstroke.jar;lib/decimal.jar;lib/flacomdoc.jar;lib/tomlj-1.1.1.jar build reports dist 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 5df8c633c..f193934e6 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 @@ -44,6 +44,7 @@ import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -61,11 +62,13 @@ import javax.swing.JOptionPane; */ public final class Configuration { - private static final String CONFIG_NAME = "config.bin"; + private static final ConfigurationStorage legacyStorage = new LegacyConfigurationStorage(); - private static final File unspecifiedFile = new File("unspecified"); + private static final ConfigurationStorage storage = new TomlConfigurationStorage(); - private static File directory = unspecifiedFile; + private static final File UNSPECIFIED_FILE = new File("unspecified"); + + private static File directory = UNSPECIFIED_FILE; /** * Log level @@ -168,6 +171,7 @@ public final class Configuration { public static ConfigurationItem openFolderAfterFlaExport = null; @ConfigurationCategory("export") + @ConfigurationDefaultString("") public static ConfigurationItem overrideTextExportFileName = null; @ConfigurationDefaultBoolean(false) @@ -1144,10 +1148,32 @@ public final class Configuration { @ConfigurationCategory("display") public static ConfigurationItem snapAlignCenterAlignmentVertical = null; + + private static Map configurationDescriptions = new LinkedHashMap<>(); + private static Map configurationTitles = new LinkedHashMap<>(); + private enum OSId { WINDOWS, OSX, UNIX } + + public static void setConfigurationDescriptions(Map configurationDescriptions) { + Configuration.configurationDescriptions = configurationDescriptions; + } + + public static void setConfigurationTitles(Map configurationTitles) { + Configuration.configurationTitles = configurationTitles; + } + + + public static String getConfigurationTitle(String confirationName) { + return configurationTitles.get(confirationName); + } + + public static String getConfigurationDescription(String confirationName) { + return configurationDescriptions.get(confirationName); + } + private static OSId getOSId() { String OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); if ((OS.indexOf("mac") >= 0) || (OS.indexOf("darwin") >= 0)) { @@ -1165,7 +1191,7 @@ public final class Configuration { * @return FFDec home directory */ public static String getFFDecHome() { - if (directory == unspecifiedFile) { + if (directory == UNSPECIFIED_FILE) { directory = null; String userHome = null; try { @@ -1379,52 +1405,24 @@ public final class Configuration { } private static String getConfigFile() throws IOException { - return getFFDecHome() + CONFIG_NAME; + return getFFDecHome() + storage.getConfigName(); } - - private static HashMap loadFromFile(String file) { - try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { - - @SuppressWarnings("unchecked") - HashMap cfg = (HashMap) ois.readObject(); - return cfg; - } catch (ClassNotFoundException | IOException ex) { - // ignore - } - - return new HashMap<>(); + + private static String getLegacyConfigFile() throws IOException { + return getFFDecHome() + legacyStorage.getConfigName(); } - - private static void saveToFile(String file) { - HashMap config = new HashMap<>(); - for (Entry entry : getConfigurationFields(false, true).entrySet()) { - try { - String name = entry.getKey(); - Field field = entry.getValue(); - ConfigurationItem item = (ConfigurationItem) field.get(null); - if (item.hasValue) { - config.put(name, item.get()); - } - } catch (IllegalArgumentException | IllegalAccessException ex) { - Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); - } - } - try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { - oos.writeObject(config); - } catch (IOException ex) { - //TODO: move this to GUI - ex.printStackTrace(); - JOptionPane.showMessageDialog(null, "Cannot save configuration.", "Error", JOptionPane.ERROR_MESSAGE); - Logger.getLogger(Configuration.class.getName()).severe("Configuration directory is read only."); - } - } - + /** * Save configuration to file */ public static void saveConfig() { try { - saveToFile(getConfigFile()); + storage.saveToFile(getConfigFile()); + } catch (IOException ex) { + // ignore + } + try { + legacyStorage.saveToFile(getLegacyConfigFile()); } catch (IOException ex) { // ignore } @@ -1449,7 +1447,14 @@ public final class Configuration { @SuppressWarnings("unchecked") public static void setConfigurationFields() { try { - HashMap config = loadFromFile(getConfigFile()); + Map config; + + if (new File(getConfigFile()).exists()) { + config = storage.loadFromFile(getConfigFile()); + } else { + config = legacyStorage.loadFromFile(getLegacyConfigFile()); + } + for (Entry entry : getConfigurationFields(false, true).entrySet()) { String name = entry.getKey(); Field field = entry.getValue(); @@ -1544,6 +1549,18 @@ public final class Configuration { defaultValue = Color.black; } } + + if (defaultValue == null) { + Class type = ConfigurationItem.getConfigurationFieldType(field); + if (type.isEnum()) { + @SuppressWarnings("unchecked") + Class> enumClass = (Class>) type; + defaultValue = enumClass.getEnumConstants()[0]; + } + if (type == String.class) { + defaultValue = ""; + } + } return defaultValue; } @@ -1562,7 +1579,7 @@ public final class Configuration { */ public static Map getConfigurationFields(boolean lowerCaseNames, boolean alsoRemoved) { Field[] fields = Configuration.class.getDeclaredFields(); - Map result = new HashMap<>(); + Map result = new LinkedHashMap<>(); for (Field field : fields) { if (!alsoRemoved) { ConfigurationRemoved removedAnnotation = field.getAnnotation(ConfigurationRemoved.class); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/ConfigurationStorage.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/ConfigurationStorage.java new file mode 100644 index 000000000..c7b8d7ded --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/ConfigurationStorage.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010-2024 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.configuration; + +import java.util.Map; + +/** + * + * @author JPEXS + */ +public interface ConfigurationStorage { + public String getConfigName(); + + public Map loadFromFile(String file); + + public void saveToFile(String file); +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/LegacyConfigurationStorage.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/LegacyConfigurationStorage.java new file mode 100644 index 000000000..895dc02b6 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/LegacyConfigurationStorage.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2010-2024 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.configuration; + +import java.io.BufferedOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JOptionPane; + +/** + * + * @author JPEXS + */ +public class LegacyConfigurationStorage implements ConfigurationStorage { + + @Override + public String getConfigName() { + return "config.bin"; + } + + @Override + public Map loadFromFile(String file) { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { + + @SuppressWarnings("unchecked") + Map cfg = (HashMap) ois.readObject(); + return cfg; + } catch (ClassNotFoundException | IOException ex) { + // ignore + } + + return new HashMap<>(); + } + + public void saveToFile(String file) { + Map config = new HashMap<>(); + for (Map.Entry entry : Configuration.getConfigurationFields(false, true).entrySet()) { + try { + String name = entry.getKey(); + Field field = entry.getValue(); + ConfigurationItem item = (ConfigurationItem) field.get(null); + if (item.hasValue) { + config.put(name, item.get()); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); + } + } + try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { + oos.writeObject(config); + } catch (IOException ex) { + //TODO: move this to GUI + ex.printStackTrace(); + JOptionPane.showMessageDialog(null, "Cannot save configuration.", "Error", JOptionPane.ERROR_MESSAGE); + Logger.getLogger(Configuration.class.getName()).severe("Configuration directory is read only."); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/SwfSpecificCustomConfiguration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/SwfSpecificCustomConfiguration.java index 9c479eb88..f0f0e7d6a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/SwfSpecificCustomConfiguration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/SwfSpecificCustomConfiguration.java @@ -36,6 +36,10 @@ public class SwfSpecificCustomConfiguration implements Serializable { public static final String LIST_SEPARATOR = "{*sep*}"; + public Map getAllCustomData() { + return customData; + } + public List getCustomDataAsList(String key) { String data = getCustomData(key, ""); String[] parts = (data + LIST_SEPARATOR).split(Pattern.quote(LIST_SEPARATOR)); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/TomlConfigurationStorage.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/TomlConfigurationStorage.java new file mode 100644 index 000000000..2dd7d5a26 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/TomlConfigurationStorage.java @@ -0,0 +1,556 @@ +/* + * Copyright (C) 2010-2024 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.configuration; + +import com.jpexs.decompiler.flash.AppResources; +import com.jpexs.decompiler.flash.ApplicationInfo; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.helpers.Helper; +import java.awt.Color; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.tomlj.Toml; +import org.tomlj.TomlParseError; +import org.tomlj.TomlParseResult; +import org.tomlj.TomlTable; + +/** + * + * @author JPEXS + */ +public class TomlConfigurationStorage implements ConfigurationStorage { + + public String getConfigName() { + return "config.toml"; + } + + @Override + public Map loadFromFile(String file) { + Map result = new LinkedHashMap<>(); + TomlParseResult tomlResult; + try { + tomlResult = Toml.parse(Paths.get(file)); + } catch (IOException ex) { + return result; + } + + if (!tomlResult.errors().isEmpty()) { + System.err.println("Error parsing configuration file:"); + for (TomlParseError error : tomlResult.errors()) { + System.err.println("- " + error); + } + } + + TomlTable configurationTable = tomlResult.getTable("configuration"); + if (configurationTable == null) { + return result; + } + + for (Map.Entry entry : Configuration.getConfigurationFields(false, true).entrySet()) { + try { + String name = entry.getKey(); + Field field = entry.getValue(); + ConfigurationItem item = (ConfigurationItem) field.get(null); + ConfigurationName nameAnnotation = field.getAnnotation(ConfigurationName.class); + if (nameAnnotation != null) { + name = nameAnnotation.value(); + } + String key = name; + if (key.contains(".")) { + key = "\"" + key + "\""; + } + + Object value = null; + + switch (name) { + case "fontPairingMap": + TomlTable fontPairingTable = configurationTable.getTable(key); + if (fontPairingTable != null) { + Map fontPairingMap = new LinkedHashMap<>(); + for (Map.Entry fontEntry : fontPairingTable.entrySet()) { + if (fontEntry.getValue() instanceof String) { + fontPairingMap.put(fontEntry.getKey(), (String) fontEntry.getValue()); + } + } + value = fontPairingMap; + } + break; + case "swfSpecificConfigs": + TomlTable swfSpecificConfigsTable = configurationTable.getTable(key); + if (swfSpecificConfigsTable != null) { + Map swfSpecificConfigs = new LinkedHashMap<>(); + for (Map.Entry swfEntry : swfSpecificConfigsTable.entrySet()) { + String swfKey = swfEntry.getKey(); + if (swfEntry.getValue() instanceof TomlTable) { + SwfSpecificConfiguration swfSpecificConfig = new SwfSpecificConfiguration(); + TomlTable configsTable = (TomlTable) swfEntry.getValue(); + TomlTable swfSpecificFontPairingTable = configsTable.getTable("fontPairingMap"); + Map swfSpecificFontPairingMap = new LinkedHashMap<>(); + for (Map.Entry fontEntry : swfSpecificFontPairingTable.entrySet()) { + if (fontEntry.getValue() instanceof String) { + swfSpecificFontPairingMap.put(fontEntry.getKey(), (String) fontEntry.getValue()); + } + } + swfSpecificConfig.fontPairingMap = swfSpecificFontPairingMap; + swfSpecificConfig.lastSelectedPath = configsTable.getString("lastSelectedPath"); + swfSpecificConfigs.put(swfKey, swfSpecificConfig); + } + } + value = swfSpecificConfigs; + } + break; + case "swfSpecificCustomConfigs": + TomlTable swfSpecificCustomConfigsTable = configurationTable.getTable(key); + if (swfSpecificCustomConfigsTable != null) { + Map swfSpecificCustomConfigs = new LinkedHashMap<>(); + for (Map.Entry swfEntry : swfSpecificCustomConfigsTable.entrySet()) { + String swfKey = swfEntry.getKey(); + if (swfEntry.getValue() instanceof TomlTable) { + SwfSpecificCustomConfiguration swfSpecificCustomConfig = new SwfSpecificCustomConfiguration(); + Map swfSpecificCustomConfigMap = swfSpecificCustomConfig.getAllCustomData(); + TomlTable configsTable = (TomlTable) swfEntry.getValue(); + + for (Map.Entry customDataEntry : configsTable.entrySet()) { + if (customDataEntry.getValue() instanceof String) { + swfSpecificCustomConfigMap.put(customDataEntry.getKey(), (String) customDataEntry.getValue()); + } + } + swfSpecificCustomConfigs.put(swfKey, swfSpecificCustomConfig); + } + } + value = swfSpecificCustomConfigs; + } + break; + default: + Type type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + if (type instanceof Class) { + switch (((Class) type).getSimpleName()) { + case "Boolean": + value = configurationTable.getBoolean(key); + break; + case "Calendar": + OffsetDateTime offsetDateTime = configurationTable.getOffsetDateTime(key); + if (offsetDateTime != null) { + Date date = Date.from(offsetDateTime.toInstant()); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + value = calendar; + } + break; + case "Color": + String colorString = configurationTable.getString(key); + if (colorString != null) { + Pattern colorPattern = Pattern.compile("#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$"); + Matcher m = colorPattern.matcher(colorString); + if (m.matches()) { + value = new Color(Integer.parseInt(m.group(1), 16), Integer.parseInt(m.group(2), 16), Integer.parseInt(m.group(3), 16)); + } + } + break; + case "Double": + value = configurationTable.getDouble(key); + break; + case "Integer": + Long longValue = configurationTable.getLong(key); + if (longValue != null) { + value = (Integer) (int) (long) longValue; + } + break; + case "String": + value = configurationTable.getString(key); + break; + } + } + } + if (value != null) { + result.put(name, value); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); + } + } + return result; + + } + + private static List wordWrap(String text, int maxLineLength) { + List lines = new ArrayList<>(); + String[] words = text.split(" "); + StringBuilder line = new StringBuilder(); + + for (String word : words) { + if (line.length() + word.length() + 1 > maxLineLength) { + lines.add(line.toString().trim()); + line = new StringBuilder(); + } + line.append(word).append(" "); + } + + if (!line.toString().isEmpty()) { + lines.add(line.toString().trim()); + } + + return lines; + } + + private static String stringOfChar(char c, int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(c); + } + return sb.toString(); + } + + private static String comment(String comment) { + return "# " + String.join(System.lineSeparator() + "# ", wordWrap(comment, 80 - 2)); + } + + @Override + public void saveToFile(String file) { + boolean showComments = false; + boolean modifiedOnly = true; + if (new File(file).exists()) { + TomlParseResult tomlResult; + try { + tomlResult = Toml.parse(Paths.get(file)); + TomlTable metaTable = tomlResult.getTable("meta"); + if (metaTable != null) { + showComments = metaTable.getBoolean("showComments") == Boolean.TRUE; + modifiedOnly = metaTable.getBoolean("modifiedOnly") == Boolean.TRUE; + } + + } catch (IOException ex) { + //ignore + } + } + try ( + Writer w = new FileWriter(file); PrintWriter pw = new PrintWriter(w)) { + String header = AppResources.translate("configurationFile").replace("%app%", ApplicationInfo.APPLICATION_NAME); + String splitter = stringOfChar('-', header.length()); + pw.println("# " + splitter); + pw.println("# " + header); + pw.println("# " + splitter); + pw.println(); + pw.println(comment(AppResources.translate("configurationFile.comment"))); + pw.println(); + pw.println(comment(AppResources.translate("configurationFile.modify"))); + pw.println(); + pw.println(comment(AppResources.translate("configurationFile.meta"))); + pw.println("[meta]"); + pw.println(); + Calendar generatedCalendarValue = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssXXX"); + sdf.setTimeZone(generatedCalendarValue.getTimeZone()); + String generatedValue = sdf.format(generatedCalendarValue.getTime()); + pw.println(comment(AppResources.translate("configurationFile.meta.saveDate"))); + pw.println("saveDate = " + generatedValue); + pw.println(); + pw.println(comment(AppResources.translate("configurationFile.meta.appVersion"))); + pw.println("appVersion = \"" + ApplicationInfo.version + "\""); + pw.println(); + + pw.println(comment(AppResources.translate("configurationFile.meta.showComments"))); + pw.println("showComments = " + (showComments ? "true" : "false") + ""); + pw.println(); + + pw.println(comment(AppResources.translate("configurationFile.meta.modifiedOnly"))); + pw.println("modifiedOnly = " + (modifiedOnly ? "true" : "false") + ""); + pw.println(); + + pw.println(comment(AppResources.translate("configurationFile.configuration"))); + pw.println("[configuration]"); + pw.println(); + + for (Map.Entry entry : Configuration.getConfigurationFields(false, true).entrySet()) { + try { + String name = entry.getKey(); + Field field = entry.getValue(); + ConfigurationItem item = (ConfigurationItem) field.get(null); + ConfigurationName nameAnnotation = field.getAnnotation(ConfigurationName.class); + if (nameAnnotation != null) { + name = nameAnnotation.value(); + } + String key = name; + if (key.contains(".")) { + key = "\"" + key + "\""; + } + + if (!item.hasValue && modifiedOnly) { + continue; + } + + String title = Configuration.getConfigurationTitle(name); + String description = Configuration.getConfigurationDescription(name); + + if (showComments) { + if (title != null && !title.isEmpty()) { + pw.println("# " + title.replace("\n", "\n# ")); + } + if (description != null && !description.isEmpty()) { + List descriptionLines = wordWrap(description, 80 - 4); + boolean first = true; + for (String descriptionLine : descriptionLines) { + pw.println("#" + (first ? " - " : " ") + descriptionLine); + first = false; + } + } + pw.println("#"); + } + Object value = item.get(); + Object defaultValue = Configuration.getDefaultValue(field); + String savedValue = null; + String savedDefaultValue = null; + StringBuilder sb = new StringBuilder(); + boolean first = true; + + String valueType = null; + if (value == null) { + savedValue = ""; + } else { + switch (name) { + case "fontPairingMap": + @SuppressWarnings("unchecked") HashMap fontPairingMap = (HashMap) value; + sb.append("{"); + for (String fontKey : fontPairingMap.keySet()) { + if (!first) { + sb.append(", "); + } + sb.append("\""); + sb.append(Helper.escapeJavaString(fontKey)); + sb.append("\" = \""); + sb.append(Helper.escapeJavaString(fontPairingMap.get(fontKey))); + sb.append("\""); + first = false; + } + sb.append("}"); + break; + case "swfSpecificConfigs": + @SuppressWarnings("unchecked") HashMap swfSpecificConfigs = (HashMap) value; + sb.append("{"); + for (String swfKey : swfSpecificConfigs.keySet()) { + if (!first) { + sb.append(", "); + } + sb.append("\""); + sb.append(Helper.escapeJavaString(swfKey)); + sb.append("\" = {"); + SwfSpecificConfiguration swfSpecificConf = swfSpecificConfigs.get(swfKey); + sb.append("fontPairingMap = {"); + boolean first2 = true; + for (String fontKey : swfSpecificConf.fontPairingMap.keySet()) { + if (!first2) { + sb.append(", "); + } + sb.append("\""); + sb.append(Helper.escapeJavaString(fontKey)); + sb.append("\" = \""); + sb.append(Helper.escapeJavaString(swfSpecificConf.fontPairingMap.get(fontKey))); + sb.append("\""); + first2 = false; + } + sb.append("}, lastSelectedPath = \""); + if (swfSpecificConf.lastSelectedPath != null) { + sb.append(Helper.escapeJavaString(swfSpecificConf.lastSelectedPath)); + } + sb.append("\""); + sb.append("}"); + first = false; + } + sb.append("}"); + savedValue = sb.toString(); + break; + case "swfSpecificCustomConfigs": + @SuppressWarnings("unchecked") HashMap swfSpecificCustomConfigs = (HashMap) value; + sb.append("{"); + for (String swfKey : swfSpecificCustomConfigs.keySet()) { + if (!first) { + sb.append(", "); + } + sb.append("\""); + sb.append(Helper.escapeJavaString(swfKey)); + sb.append("\" = {"); + SwfSpecificCustomConfiguration swfSpecificCustomConf = swfSpecificCustomConfigs.get(swfKey); + boolean first2 = true; + for (String customKey : swfSpecificCustomConf.getAllCustomData().keySet()) { + if (!first2) { + sb.append(", "); + } + sb.append("\""); + sb.append(Helper.escapeJavaString(customKey)); + sb.append("\" = \""); + sb.append(Helper.escapeJavaString(swfSpecificCustomConf.getAllCustomData().get(customKey))); + sb.append("\""); + first2 = false; + } + sb.append("}"); + first = false; + } + sb.append("}"); + savedValue = sb.toString(); + break; + default: + Type type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + if (type instanceof Class) { + switch (((Class) type).getSimpleName()) { + case "Boolean": + Boolean booleanValue = (Boolean) value; + savedValue = booleanValue == Boolean.TRUE ? "true" : "false"; + savedDefaultValue = defaultValue == Boolean.TRUE ? "true" : "false"; + valueType = "Boolean"; + break; + case "Calendar": + Calendar calendarValue = (Calendar) value; + sdf.setTimeZone(calendarValue.getTimeZone()); + savedValue = sdf.format(calendarValue.getTime()); + if (defaultValue != null) { + savedDefaultValue = sdf.format(((Calendar) defaultValue).getTime()); + } + valueType = "Calendar"; + break; + case "Color": + Color colorValue = (Color) value; + savedValue = "\"" + new RGB(colorValue).toHexRGB() + "\""; + if (defaultValue != null) { + savedDefaultValue = "\"" + new RGB((Color) defaultValue).toHexRGB() + "\""; + } + valueType = "Color"; + break; + case "Double": + savedValue = "" + value; + if (defaultValue != null) { + savedDefaultValue = "" + defaultValue; + } + valueType = "Double"; + break; + case "Integer": + savedValue = "" + value; + if (defaultValue != null) { + savedDefaultValue = "" + defaultValue; + } + valueType = "Integer"; + break; + case "String": + String stringValue = value.toString(); + if (stringValue.contains("\\") && !stringValue.matches("^.*[\b\t\n\f\r\"'\\x00-\\x08\\x1A-\\x1F\\x7F].*$")) { + savedValue = "'" + stringValue + "'"; + } else { + savedValue = "\"" + Helper.escapeJavaString(stringValue) + "\""; + } + if (defaultValue != null) { + stringValue = defaultValue.toString(); + if (stringValue.contains("\\") && !stringValue.matches("^.*[\b\t\n\f\r\"'\\x00-\\x08\\x1A-\\x1F\\x7F].*$")) { + savedDefaultValue = "'" + stringValue + "'"; + } else { + savedDefaultValue = "\"" + Helper.escapeJavaString(stringValue) + "\""; + } + } + valueType = "String"; + break; + default: + String stringOtherValue = value.toString(); + savedValue = "\"" + Helper.escapeJavaString(stringOtherValue) + "\""; + if (defaultValue != null) { + savedDefaultValue = "" + "\"" + Helper.escapeJavaString(defaultValue.toString()) + "\""; + } + break; + } + } + + } + } + + if (showComments && valueType != null) { + pw.println("# " + AppResources.translate("valueType") + " " + AppResources.translate("valueType." + valueType)); + } + + Type type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + if (showComments && type instanceof Class) { + Class c = (Class) type; + if (c.isEnum()) { + @SuppressWarnings("unchecked") + Class> enumType = (Class>) type; + Enum[] values = enumType.getEnumConstants(); + sb = new StringBuilder(); + first = true; + for (Enum enumValue : values) { + if (!first) { + sb.append(", "); + } + sb.append("\""); + sb.append(enumValue.toString()); + sb.append("\""); + first = false; + } + pw.println("# " + AppResources.translate("valueType") + " " + enumType.getSimpleName()); + pw.println("# " + AppResources.translate("possibleValues") + " " + sb.toString()); + } + } + + if (showComments && savedDefaultValue != null) { + pw.println("# " + AppResources.translate("defaultValue") + " " + savedDefaultValue); + } + + ConfigurationRemoved removed = field.getAnnotation(ConfigurationRemoved.class); + + if (showComments && removed != null) { + pw.println("# " + AppResources.translate("configuration.removed")); + } + + if (showComments) { + pw.println(); + } + if (!item.hasValue || savedValue.isEmpty()) { + pw.print("# "); + } + pw.print(key + " = "); + if (item.hasValue) { + pw.println(savedValue); + } else { + pw.println(savedDefaultValue); + } + if (showComments) { + pw.println(); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex); + } + } + } catch (IOException ex) { + Logger.getLogger(TomlConfigurationStorage.class.getName()).log(Level.SEVERE, null, ex); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties index bc2ed851a..3329e3f66 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties @@ -41,3 +41,23 @@ error.swf.invalid = Invalid SWF file, wrong signature. error.swf.headerTooShort = SWF header is too short. error.abc.invalid = Invalid ABC file. error.swf.decryptionProblem = Invalid SWF file, decryption failed. +#after 23.0.1 +defaultValue = Default value: +possibleValues = Possible values: +valueType = Value type: +valueType.Double = Floating point +valueType.Boolean = true/false +valueType.Calendar = DateTime +valueType.Color = Color +valueType.Integer = Integer +valueType.String = String +configurationFile = Configuration file of %app% +configurationFile.comment = Line starting with hash is a comment +configurationFile.modify = WARNING: This file is overwritten on every application exit. If you make changes to values, make sure FFDec is not running. +configurationFile.meta = Section - Meta info of how configuration is stored +configurationFile.meta.saveDate = When this file was last modified by the app +configurationFile.meta.appVersion = Last version of tha app that modified this file +configurationFile.meta.showComments = Show configuration comments - set to true (and exit app again to resave) to show configuration titles and descriptions. +configurationFile.meta.modifiedOnly = Store modified items only in this file - set to false (and exit app again to resave) to show all. +configurationFile.configuration = Section - Actual configuration +configuration.removed = WARNING: This configuration was REMOVED. It is unused. \ No newline at end of file diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties index 85526cb94..82772d5b5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties @@ -41,4 +41,24 @@ error.swf.invalid = Neplatn\u00fd SWF soubor, \u0161patn\u00e1 signatura. error.swf.headerTooShort = SWF hlavi\u010dka je p\u0159\u00edli\u0161 kr\u00e1tk\u00e1. error.abc.invalid = Neplatn\u00fd ABC soubor. #after 18.4.1 -error.swf.decryptionProblem = Neplatn\u00fd SWF soubor, selhalo de\u0161ifrov\u00e1n\u00ed. \ No newline at end of file +error.swf.decryptionProblem = Neplatn\u00fd SWF soubor, selhalo de\u0161ifrov\u00e1n\u00ed. +#after 23.0.1 +defaultValue = V\u00fdchoz\u00ed hodnota: +possibleValues = Mo\u017en\u00e9 hodnoty: +valueType = Typ hodnoty: +valueType.Double = Desetinn\u00e9 \u010d\u00edslo +valueType.Boolean = true/false +valueType.Calendar = Datum + \u010das +valueType.Color = Barva +valueType.Integer = Cel\u00e9 \u010d\u00edslo +valueType.String = \u0158et\u011bzec +configurationFile = Konfigura\u010dn\u00ed soubor programu %app% +configurationFile.comment = \u0158\u00e1dek za\u010d\u00ednaj\u00edc\u00ed na m\u0159\u00ed\u017eku je koment\u00e1\u0159 +configurationFile.modify = VAROV\u00c1N\u00cd: Tento soubor je p\u0159episov\u00e1n p\u0159i ka\u017ed\u00e9m ukon\u010den\u00ed aplikace. Pokud prov\u00e1d\u00edte zm\u011bny hodnot, ujist\u011bte se, \u017ee aplikace neb\u011b\u017e\u00ed. +configurationFile.meta = Sekce - Metainformace o tom, jak je konfigurace ulo\u017een\u00e1 +configurationFile.meta.saveDate = Kdy byl tento soubor naposledy upraven aplikac\u00ed +configurationFile.meta.appVersion = Posledn\u00ed verze aplikace, kter\u00e1 tento soubor upravila +configurationFile.meta.showComments = Zobrazit koment\u00e1\u0159e ke konfiguraci - nastavte to na true (a ukon\u010dete aplikaci pro nov\u00e9 ulo\u017een\u00ed) pro zobrazen\u00ed n\u00e1zv\u016f a popis\u016f konfigurace. +configurationFile.meta.modifiedOnly = Ukl\u00e1dat do tohoto souboru pouze zm\u011bn\u011bn\u00e9 hodnoty - nastavte na false (a ukon\u010dete aplikaci pro nov\u00e9 ulo\u017een\u00ed) pro zobrazen\u00ed v\u0161eho. +configurationFile.configuration = Sekce - Vlastn\u00ed konfigurace +configuration.removed = VAROV\u00c1N\u00cd: Tato konfigurace byla ODSTRAN\u011aNA. Nepou\u017e\u00edv\u00e1 se. \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index 2a51b29e8..da7e32516 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -132,6 +132,7 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.ResourceBundle; import java.util.Set; import java.util.Timer; import java.util.TimerTask; @@ -3068,6 +3069,21 @@ public class Main { AppStrings.updateLanguage(); Helper.decompilationErrorAdd = AppStrings.translate(Configuration.autoDeobfuscate.get() ? "deobfuscation.comment.failed" : "deobfuscation.comment.tryenable"); + + ResourceBundle advancedSettingsBundle = ResourceBundle.getBundle(AppStrings.getResourcePath(AdvancedSettingsDialog.class)); + Set confirationNames = Configuration.getConfigurationFields(false, true).keySet(); + Map titles = new LinkedHashMap<>(); + Map descriptions = new LinkedHashMap<>(); + for (String name : confirationNames) { + if (advancedSettingsBundle.containsKey("config.name." + name)) { + titles.put(name, advancedSettingsBundle.getString("config.name." + name)); + } + if (advancedSettingsBundle.containsKey("config.description." + name)) { + descriptions.put(name, advancedSettingsBundle.getString("config.description." + name)); + } + } + Configuration.setConfigurationDescriptions(descriptions); + Configuration.setConfigurationTitles(titles); } /** @@ -3421,6 +3437,7 @@ public class Main { } String latestTagName = latestVersionInfoJson.asObject().get("tag_name").asString(); if (currentTagName.equals(latestTagName) || stableTagName.equals(latestTagName)) { + Configuration.lastUpdatesCheckDate.set(Calendar.getInstance()); //no new version return false; } 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 363ea3ff0..dfe3ac5d3 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties @@ -199,7 +199,7 @@ config.description.tagTreeShowEmptyFolders = Zobrazovat pr\u00e1zdn\u00e9 slo\u0 config.name.autoLoadEmbeddedSwfs = Automaticky na\u010d\u00edtat vlo\u017een\u00e1 SWF config.description.autoLoadEmbeddedSwfs = Automaticky na\u010d\u00edtat vlo\u017een\u00e1 SWF z DefineBinaryData tag\u016f. config.name.overrideTextExportFileName = Zm\u011bnit n\u00e1zev souboru pro export textu -config.description.overrideTextExportFileName = M\u016f\u017eete p\u0159\u00edzp\u016fsovit n\u00e1zev souboru exportovan\u00e9ho textu. Pou\u017eijte z\u00e1stupn\u00fd text {filename} pro pou\u017eit\u00ed n\u00e1zvu aktu\u00e1ln\u00edho SWF. +config.description.overrideTextExportFileName = M\u016f\u017eete p\u0159\u00edzp\u016fsobit n\u00e1zev souboru exportovan\u00e9ho textu. Pou\u017eijte z\u00e1stupn\u00fd text {filename} pro pou\u017eit\u00ed n\u00e1zvu aktu\u00e1ln\u00edho SWF. config.name.showOldTextDuringTextEditing = Zobrazovat star\u00fd text b\u011bhem editace textu config.description.showOldTextDuringTextEditing = Zobrazit origin\u00e1ln\u00ed text z textov\u00e9ho tagu \u0161edivou barvou v oblasti n\u00e1hledu. config.group.name.import = Import