diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/LsoTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/LsoTag.java index 8eccecca6..2728c3a40 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/LsoTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/sol/LsoTag.java @@ -98,7 +98,7 @@ public class LsoTag extends Tag { List stringTable = new ArrayList<>(); while (ais.available() > 0) { - String varName = ais.readUtf8Vr("varName", new ArrayList<>()); + String varName = ais.readUtf8Vr("varName", stringTable); try { Object varValue = ais.readValue("varValue", new HashMap<>(), objectsTable, traitsTable, stringTable); amfValues.put(varName, varValue); diff --git a/src/com/jpexs/decompiler/flash/gui/AppStrings.java b/src/com/jpexs/decompiler/flash/gui/AppStrings.java index 473468104..d7a577c6f 100644 --- a/src/com/jpexs/decompiler/flash/gui/AppStrings.java +++ b/src/com/jpexs/decompiler/flash/gui/AppStrings.java @@ -49,6 +49,10 @@ public class AppStrings { ResourceBundle b = ResourceBundle.getBundle(bundle); return b.getString(key); } + + public static String translate(Class bundleClass, String key) { + return translate(getResourcePath(bundleClass), key); + } public static void updateLanguage() { resourceBundle = ResourceBundle.getBundle(getResourcePath(resourceClass)); diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index 13cbfb8fd..17701bcad 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -50,6 +50,8 @@ import com.jpexs.decompiler.flash.gui.debugger.DebugAdapter; import com.jpexs.decompiler.flash.gui.debugger.DebugLoaderDataModified; import com.jpexs.decompiler.flash.gui.debugger.DebuggerTools; import com.jpexs.decompiler.flash.gui.pipes.FirstInstance; +import com.jpexs.decompiler.flash.gui.soleditor.CookiesChangedListener; +import com.jpexs.decompiler.flash.gui.soleditor.SharedObjectsStorage; import com.jpexs.decompiler.flash.gui.soleditor.SolEditorFrame; import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; @@ -105,6 +107,8 @@ import java.net.URLConnection; import java.net.URLDecoder; import java.nio.charset.Charset; import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; @@ -191,6 +195,8 @@ public class Main { private static File runTempFile; + private static CookiesChangedListener runCookieListener; + private static List runTempFiles = new ArrayList<>(); private static WatchService watcher; @@ -219,6 +225,10 @@ public class Main { return runningSWF; } + public static WatchService getWatcher() { + return watcher; + } + public static boolean isSwfAir(Openable openable) { SwfSpecificCustomConfiguration conf = Configuration.getSwfSpecificCustomConfiguration(openable.getShortPathTitle()); if (conf != null) { @@ -252,6 +262,7 @@ public class Main { public static void freeRun() { synchronized (Main.class) { if (runTempFile != null) { + deleteCookiesAfterRun(runTempFile); runTempFile.delete(); runTempFile = null; } @@ -638,6 +649,7 @@ public class Main { return; } if (tempFile != null) { + prepareCookiesForRun(tempFile, swf); synchronized (Main.class) { runTempFile = tempFile; runTempFiles = tempFiles; @@ -648,6 +660,73 @@ public class Main { runPlayer(AppStrings.translate("work.running"), playerLocation, tempFile.getAbsolutePath(), flashVars); } } + + private static void deleteCookiesAfterRun(File tempFile) { + SharedObjectsStorage.removeChangedListener(tempFile, runCookieListener); + File solDir = SharedObjectsStorage.getSolDirectoryForLocalFile(tempFile); + if (solDir == null) { + return; + } + if (solDir.exists()) { + for (File f : solDir.listFiles()) { + f.delete(); + } + solDir.delete(); + } + synchronized (Main.class) { + runCookieListener = null; + } + getMainFrame().getPanel().refreshTree(); + } + + private static void prepareCookiesForRun(File tempFile, SWF swf) { + if (swf.getFile() == null) { + return; + } + File origSolDir = SharedObjectsStorage.getSolDirectoryForLocalFile(new File(swf.getFile())); + if (origSolDir == null) { + return; + } + File tempSolDir = SharedObjectsStorage.getSolDirectoryForLocalFile(tempFile); + if (tempSolDir == null) { + return; + } + tempSolDir.mkdirs(); + if (origSolDir.exists()) { + for (File f : origSolDir.listFiles()) { + try { + Files.copy(f.toPath(), tempSolDir.toPath().resolve(f.getName()), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ex) { + //ignored + } + } + } + + runCookieListener = new CookiesChangedListener() { + @Override + public void cookiesChanged(File swfFile, List cookies) { + File origSolDir = SharedObjectsStorage.getSolDirectoryForLocalFile(new File(swf.getFile())); + if (cookies.isEmpty() && !origSolDir.exists()) { + return; + } + origSolDir.mkdirs(); + for (File f : origSolDir.listFiles()) { + f.delete(); + } + for (File f : cookies) { + if (!f.exists()) { + continue; + } + try { + Files.copy(f.toPath(), origSolDir.toPath().resolve(f.getName()), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ex) { + //ignored + } + } + } + }; + SharedObjectsStorage.addChangedListener(tempFile,runCookieListener); + } public static void runDebug(SWF swf, final boolean doPCode) { String flashVars = ""; //key=val&key2=val2 @@ -700,6 +779,7 @@ public class Main { @Override protected void done() { + prepareCookiesForRun(fTempFile, swf); synchronized (Main.class) { runTempFile = fTempFile; runProcessDebug = true; @@ -1900,6 +1980,12 @@ public class Main { String fileName = si.getFile(); if (fileName != null) { Configuration.addRecentFile(fileName); + SharedObjectsStorage.addChangedListener(new File(fileName), new CookiesChangedListener() { + @Override + public void cookiesChanged(File swfFile, List cookies) { + getMainFrame().getPanel().refreshTree(); + } + }); if (watcher != null) { try { File dir = new File(fileName).getParentFile(); @@ -2437,6 +2523,19 @@ public class Main { java.nio.file.Path filename = ev.context(); + if (SharedObjectsStorage.watchedCookieDirectories.containsKey(key)) { + File dir = SharedObjectsStorage.watchedCookieDirectories.get(key); + java.nio.file.Path child = dir.toPath().resolve(filename); + File fullPath = child.toFile(); + + View.execInEventDispatchLater(new Runnable() { + @Override + public void run() { + SharedObjectsStorage.watchedDirectoryChanged(fullPath); + } + }); + } + if (watchedDirectories.containsKey(key)) { File dir = watchedDirectories.get(key); java.nio.file.Path child = dir.toPath().resolve(filename); @@ -2474,10 +2573,10 @@ public class Main { } boolean valid = key.reset(); if (!valid) { - break; + SharedObjectsStorage.watchedCookieDirectories.remove(key); + watchedDirectories.remove(key); } - } - return null; + } } }; watcherWorker.execute(); @@ -2918,12 +3017,11 @@ public class Main { * @throws IOException On error */ public static void main(String[] args) throws IOException { - decodeLaunch5jArgs(args); + decodeLaunch5jArgs(args); setSessionLoaded(false); - System.setProperty("sun.io.serialization.extendedDebugInfo", "true"); - + clearTemp(); try { @@ -3014,16 +3112,16 @@ public class Main { openFile(sourceInfos, (Openable openable) -> { mainFrame.getPanel().tagTree.setSelectionPathString(Configuration.lastSessionSelection.get()); mainFrame.getPanel().tagListTree.setSelectionPathString(Configuration.lastSessionTagListSelection.get()); - + Set allSwfs = mainFrame.getPanel().getAllSwfs(); - + for (SWF s : allSwfs) { String name = s.getFile() + "|" + s.getFileTitle(); if (name.equals(Configuration.lastSessionEasySwf.get())) { mainFrame.getPanel().getEasyPanel().setSwf(s); } } - + setSessionLoaded(true); mainFrame.getPanel().reload(true); mainFrame.getPanel().updateUiWithCurrentOpenable(); @@ -3425,7 +3523,7 @@ public class Main { } return null; } - + public static void openSolEditor() { SolEditorFrame solEdit = new SolEditorFrame(); solEdit.setVisible(true); diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 137d89d68..51d5af837 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -109,6 +109,7 @@ import com.jpexs.decompiler.flash.gui.editor.LineMarkedEditorPane; import com.jpexs.decompiler.flash.gui.helpers.CollectionChangedAction; import com.jpexs.decompiler.flash.gui.helpers.ObservableList; import com.jpexs.decompiler.flash.gui.player.FlashPlayerPanel; +import com.jpexs.decompiler.flash.gui.soleditor.Cookie; import com.jpexs.decompiler.flash.gui.taglistview.TagListTree; import com.jpexs.decompiler.flash.gui.taglistview.TagListTreeModel; import com.jpexs.decompiler.flash.gui.tagtree.AbstractTagTree; @@ -5852,6 +5853,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } else if (treeItem instanceof MetadataTag) { MetadataTag metadataTag = (MetadataTag) treeItem; previewPanel.showMetaDataPanel(metadataTag); + } else if (treeItem instanceof Cookie) { + previewPanel.showCookiePanel((Cookie) treeItem); } else if (treeItem instanceof BinaryDataInterface) { BinaryDataInterface binary = (BinaryDataInterface) treeItem; previewPanel.showBinaryPanel(binary); @@ -6264,6 +6267,9 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } else if (treeItem instanceof MetadataTag) { showPreview(treeItem, previewPanel, -1, null); showCard(CARDPREVIEWPANEL); + } else if (treeItem instanceof Cookie) { + showPreview(treeItem, previewPanel, -1, null); + showCard(CARDPREVIEWPANEL); } else if (treeItem instanceof BinaryDataInterface) { showPreview(treeItem, previewPanel, -1, null); showCard(CARDPREVIEWPANEL); diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java index 0bf2bbbba..5351c87de 100644 --- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java @@ -22,6 +22,8 @@ import com.jpexs.decompiler.flash.SWFHeader; import com.jpexs.decompiler.flash.action.parser.ActionParseException; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.PreviewExporter; +import com.jpexs.decompiler.flash.exporters.amf.amf0.Amf0Exporter; +import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter; import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; import com.jpexs.decompiler.flash.gfx.GfxConvertor; @@ -32,7 +34,13 @@ import com.jpexs.decompiler.flash.gui.hexview.HexView; import com.jpexs.decompiler.flash.gui.player.FlashPlayerPanel; import com.jpexs.decompiler.flash.gui.player.MediaDisplay; import com.jpexs.decompiler.flash.gui.player.PlayerControls; +import com.jpexs.decompiler.flash.gui.soleditor.Cookie; +import com.jpexs.decompiler.flash.gui.soleditor.SolEditorFrame; +import com.jpexs.decompiler.flash.importers.amf.AmfParseException; +import com.jpexs.decompiler.flash.importers.amf.amf0.Amf0Importer; +import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3Importer; import com.jpexs.decompiler.flash.math.BezierUtils; +import com.jpexs.decompiler.flash.sol.SolFile; import com.jpexs.decompiler.flash.tags.DefineMorphShape2Tag; import com.jpexs.decompiler.flash.tags.DefineShape4Tag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; @@ -106,6 +114,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.Timer; @@ -115,14 +124,18 @@ import java.util.logging.Logger; import javax.swing.Box; import javax.swing.ButtonGroup; import javax.swing.JButton; +import javax.swing.JEditorPane; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSplitPane; +import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.JTree; import javax.swing.SwingConstants; import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.event.TreeSelectionEvent; @@ -157,6 +170,8 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private static final String EMPTY_CARD = "EMPTY"; + private static final String COOKIE_CARD = "COOKIE"; + private static final String CARDTEXTPANEL = "Text card"; private static final String CARDFONTPANEL = "Font card"; @@ -179,6 +194,12 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private BinaryPanel binaryPanel; + private LineMarkedEditorPane cookieEditor; + + private JTextField cookieFilenameField; + + private JLabel amfVersionLabel; + private LineMarkedEditorPane metadataEditor; private GenericTagPanel genericTagPanel; @@ -250,6 +271,12 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private JToggleButton displayEditShowEndButton; + private JButton cookieEditButton; + + private JButton cookieSaveButton; + + private JButton cookieCancelButton; + private Component morphShowSpace; private JPanel parametersPanel; @@ -262,6 +289,8 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private MetadataTag metadataTag; + private Cookie cookie; + private boolean readOnly = false; private ImagePanel displayEditImagePanel; @@ -315,6 +344,8 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel private JPersistentSplitPane displayEditTransformSplitPane; private JPersistentSplitPane imageTransformSplitPane; + + private DocumentListener cookieDocumentListener; public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; @@ -338,6 +369,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel viewerCards.add(createProductInfoCard(), PRODUCTINFO_TAG_CARD); viewerCards.add(createUnknownCard(), UNKNOWN_TAG_CARD); viewerCards.add(createMetadataCard(), METADATA_TAG_CARD); + viewerCards.add(createCookieCard(), COOKIE_CARD); viewerCards.add(createGenericTagCard(), GENERIC_TAG_CARD); viewerCards.add(createDisplayEditTagCard(), DISPLAYEDIT_TAG_CARD); viewerCards.add(createEmptyCard(), EMPTY_CARD); @@ -669,11 +701,20 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel return metadataSaveButton.isVisible() && metadataSaveButton.isEnabled(); } + private boolean isCookieModified() { + return cookieSaveButton.isVisible() && cookieSaveButton.isEnabled(); + } + private void setMetadataModified(boolean value) { metadataSaveButton.setEnabled(value); metadataCancelButton.setEnabled(value); } + private void setCookieModified(boolean value) { + cookieSaveButton.setEnabled(value); + cookieCancelButton.setEnabled(value); + } + private void metadataTextChanged() { setMetadataModified(true); mainPanel.setEditingStatus(); @@ -689,6 +730,16 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel metadataCancelButton.setEnabled(metadataModified || !editorMode); } + private void updateCookieButtonsVisibility() { + boolean edit = cookieEditor.isEditable(); + boolean editorMode = Configuration.editorMode.get(); + cookieEditButton.setVisible(!readOnly && !edit); + cookieSaveButton.setVisible(!readOnly && edit); + boolean cookieModified = isCookieModified(); + cookieCancelButton.setVisible(!readOnly && edit); + cookieCancelButton.setEnabled(cookieModified || !editorMode); + } + private JPanel createBinaryCard() { JPanel binaryCard = new JPanel(new BorderLayout()); binaryPanel = new BinaryPanel(mainPanel); @@ -697,6 +748,129 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel return binaryCard; } + private JPanel createCookieCard() { + JPanel cookieCard = new JPanel(new BorderLayout()); + cookieFilenameField = new JTextField(30); + amfVersionLabel = new JLabel(); + cookieEditor = new LineMarkedEditorPane(); + + + JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + topPanel.add(new JLabel(AppStrings.translate(SolEditorFrame.class, "filename"))); + topPanel.add(cookieFilenameField); + topPanel.add(new JLabel(AppStrings.translate(SolEditorFrame.class, "amfVersion"))); + topPanel.add(amfVersionLabel); + + cookieCard.add(topPanel, BorderLayout.NORTH); + cookieCard.add(new FasterScrollPane(cookieEditor), BorderLayout.CENTER); + cookieCard.add(createCookieButtonsPanel(), BorderLayout.SOUTH); + + cookieEditor.setContentType("text/javascript"); + + cookieDocumentListener = new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + setCookieModified(true); + } + + @Override + public void removeUpdate(DocumentEvent e) { + setCookieModified(true); + } + + @Override + public void changedUpdate(DocumentEvent e) { + setCookieModified(true); + } + + }; + return cookieCard; + } + + private JPanel createCookieButtonsPanel() { + cookieEditButton = new JButton(mainPanel.translate("button.edit"), View.getIcon("edit16")); + cookieEditButton.setMargin(new Insets(3, 3, 3, 10)); + cookieEditButton.addActionListener(this::editCookieButtonActionPerformed); + cookieSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16")); + cookieSaveButton.setMargin(new Insets(3, 3, 3, 10)); + cookieSaveButton.addActionListener(this::saveCookieButtonActionPerformed); + cookieSaveButton.setVisible(false); + cookieCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16")); + cookieCancelButton.setMargin(new Insets(3, 3, 3, 10)); + cookieCancelButton.addActionListener(this::cancelCookieButtonActionPerformed); + cookieCancelButton.setVisible(false); + + ButtonsPanel metadataTagButtonsPanel = new ButtonsPanel(); + metadataTagButtonsPanel.add(cookieEditButton); + metadataTagButtonsPanel.add(cookieSaveButton); + metadataTagButtonsPanel.add(cookieCancelButton); + return metadataTagButtonsPanel; + } + + private void editCookieButtonActionPerformed(ActionEvent evt) { + TreeItem item = mainPanel.getCurrentTree().getCurrentTreeItem(); + if (item == null) { + return; + } + + if (item instanceof Cookie) { + cookieEditor.setEditable(true); + cookieFilenameField.setEditable(true); + updateCookieButtonsVisibility(); + mainPanel.setEditingStatus(); + } + } + + private void saveCookieButtonActionPerformed(ActionEvent evt) { + //cookie.setModified(true); + + String amfText = cookieEditor.getText(); + int amfVersion = Integer.parseInt(amfVersionLabel.getText()); + Map amfValues = null; + try { + switch (amfVersion) { + case 0: + Amf0Importer a0i = new Amf0Importer(); + amfValues = a0i.stringToAmfMap(amfText); + break; + case 3: + Amf3Importer a3i = new Amf3Importer(); + amfValues = a3i.stringToAmfMap(amfText); + break; + } + + SolFile solFile = new SolFile(cookieFilenameField.getText(), amfVersion, amfValues); + try (FileOutputStream fos = new FileOutputStream(cookie.getSolFile())) { + solFile.writeTo(fos); + } + } catch (AmfParseException ex) { + cookieEditor.gotoLine((int) ex.line); + cookieEditor.markError(); + ViewMessages.showMessageDialog(this, AppStrings.translate(SolEditorFrame.class, "error.parse").replace("%reason%", ex.text).replace("%line%", "" + ex.line), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + return; + } catch (IOException ex) { + ViewMessages.showMessageDialog(this, ex.getLocalizedMessage(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + return; + } + + cookieEditor.setEditable(Configuration.editorMode.get()); + cookieFilenameField.setEditable(false); + setCookieModified(false); + updateCookieButtonsVisibility(); + mainPanel.repaintTree(); + mainPanel.clearEditingStatus(); + } + + private void cancelCookieButtonActionPerformed(ActionEvent evt) { + cookieEditor.setEditable(false); + cookieFilenameField.setEditable(false); + readCookie(); + metadataEditor.setEditable(Configuration.editorMode.get()); + setCookieModified(false); + updateCookieButtonsVisibility(); + mainPanel.clearEditingStatus(); + } + private JPanel createProductInfoCard() { JPanel productInfoCard = new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel tablePanel = new JPanel(new GridBagLayout()); @@ -1639,6 +1813,37 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel } } + private void readCookie() { + try (FileInputStream fis = new FileInputStream(cookie.getSolFile())) { + SolFile solFile = new SolFile(fis); + switch (solFile.getAmfVersion()) { + case 0: + cookieEditor.setText(Amf0Exporter.amfMapToString(solFile.getAmfValues(), 0, "\r\n")); + break; + case 3: + cookieEditor.setText(Amf3Exporter.amfMapToString(solFile.getAmfValues(), " ", "\r\n", 0)); + break; + } + cookieFilenameField.setText(solFile.getFileName()); + amfVersionLabel.setText("" + solFile.getAmfVersion()); + } catch (Exception ex) { + cookieEditor.setText("//Error: " + ex.getLocalizedMessage()); + } + } + + public void showCookiePanel(Cookie cookie) { + showCardLeft(COOKIE_CARD); + this.cookie = cookie; + cookieEditor.setEditable(!readOnly && Configuration.editorMode.get()); + cookieFilenameField.setEditable(!readOnly && Configuration.editorMode.get()); + readCookie(); + cookieEditor.getDocument().addDocumentListener(cookieDocumentListener); + cookieFilenameField.getDocument().addDocumentListener(cookieDocumentListener); + setCookieModified(false); + updateCookieButtonsVisibility(); + parametersPanel.setVisible(false); + } + public void showMetaDataPanel(MetadataTag metadataTag) { showCardLeft(METADATA_TAG_CARD); this.metadataTag = metadataTag; @@ -2250,7 +2455,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel PlaceObjectTypeTag placeTag = (PlaceObjectTypeTag) displayEditTag; Matrix origMatrix = new Matrix(placeTag.getMatrix()); placeTag.setMatrix(matrix.concatenate(origMatrix).toMATRIX()); - placeTag.setPlaceFlagHasMatrix(true); + placeTag.setPlaceFlagHasMatrix(true); } if (displayEditTag instanceof ShapeTag) { ShapeTag shape = (ShapeTag) displayEditTag; @@ -2783,7 +2988,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel public boolean isModified() { return false; } - + @Override public ReadOnlyTagList getTags() { if (cachedTags == null) { @@ -3018,6 +3223,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel || (genericSaveButton.isVisible() && genericSaveButton.isEnabled()) || (metadataSaveButton.isVisible() && metadataSaveButton.isEnabled()) || (displayEditSaveButton.isVisible() && displayEditSaveButton.isEnabled()) + || (cookieSaveButton.isVisible() && cookieSaveButton.isEnabled()) || fontPanel.isEditing() || imageTransformSaveButton.isVisible(); } diff --git a/src/com/jpexs/decompiler/flash/gui/TreeNodeType.java b/src/com/jpexs/decompiler/flash/gui/TreeNodeType.java index 43c8bbb84..40f2a3013 100644 --- a/src/com/jpexs/decompiler/flash/gui/TreeNodeType.java +++ b/src/com/jpexs/decompiler/flash/gui/TreeNodeType.java @@ -64,5 +64,6 @@ public enum TreeNodeType { SCALING_GRID, END, ERROR, - ABC + ABC, + COOKIE } diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/cookie16.png b/src/com/jpexs/decompiler/flash/gui/graphics/cookie16.png new file mode 100644 index 000000000..81f66df45 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/cookie16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/cookie32.png b/src/com/jpexs/decompiler/flash/gui/graphics/cookie32.png new file mode 100644 index 000000000..f973d1018 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/cookie32.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/foldercookies16.png b/src/com/jpexs/decompiler/flash/gui/graphics/foldercookies16.png new file mode 100644 index 000000000..211b5d518 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/foldercookies16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index c99d59787..392705ef2 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1013,4 +1013,5 @@ work.decompiling.allScripts.ucf = Decompiling all scripts to get uninitialized c menu.file.view.easy = Simple editor #after 21.1.3 -menu.tools.solEditor = Sol editor \ No newline at end of file +menu.tools.solEditor = Sol editor +node.cookies = cookies \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/soleditor/Cookie.java b/src/com/jpexs/decompiler/flash/gui/soleditor/Cookie.java new file mode 100644 index 000000000..a2e58e1ed --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/soleditor/Cookie.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.soleditor; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.treeitems.Openable; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import java.io.File; +import java.util.Objects; + +/** + * + * @author JPEXS + */ +public class Cookie implements TreeItem { + + private final SWF swf; + private final File solFile; + + public Cookie(SWF swf, File solFile) { + this.swf = swf; + this.solFile = solFile; + } + + public File getSolFile() { + return solFile; + } + + @Override + public Openable getOpenable() { + return swf; + } + + @Override + public boolean isModified() { + return false; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + Objects.hashCode(this.solFile); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Cookie other = (Cookie) obj; + return Objects.equals(this.solFile, other.solFile); + } + + @Override + public String toString() { + return solFile.getName(); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/soleditor/CookiesChangedListener.java b/src/com/jpexs/decompiler/flash/gui/soleditor/CookiesChangedListener.java new file mode 100644 index 000000000..7318edc2c --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/soleditor/CookiesChangedListener.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.soleditor; + +import java.io.File; +import java.util.List; + +/** + * + * @author JPEXS + */ +public interface CookiesChangedListener { + public void cookiesChanged(File swfFile, List cookies); +} diff --git a/src/com/jpexs/decompiler/flash/gui/soleditor/FlashPlayerApi.java b/src/com/jpexs/decompiler/flash/gui/soleditor/FlashPlayerApi.java new file mode 100644 index 000000000..4bd2d2c21 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/soleditor/FlashPlayerApi.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.soleditor; + +/** + * Apis for embedding flash + * @author JPEXS + */ +public enum FlashPlayerApi { + /** + * NP api + */ + NPAPI, + /** + * Pepper api + */ + PPAPI +} diff --git a/src/com/jpexs/decompiler/flash/gui/soleditor/SharedObjectsStorage.java b/src/com/jpexs/decompiler/flash/gui/soleditor/SharedObjectsStorage.java new file mode 100644 index 000000000..55c1602b7 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/soleditor/SharedObjectsStorage.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.soleditor; + +import com.jpexs.decompiler.flash.gui.Main; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchKey; +import java.util.ArrayList; +import java.util.Arrays; +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; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Service for accesing storage of Shared objects (.SOL files) + * + * @author JPEXS + */ +public class SharedObjectsStorage { + + public static Map watchedCookieDirectories = new HashMap<>(); + + + private static Map> swfFileToListeners = new LinkedHashMap<>(); + + public static void addChangedListener(File file, CookiesChangedListener listener) { + if (!swfFileToListeners.containsKey(file)) { + swfFileToListeners.put(file, new ArrayList<>()); + } + swfFileToListeners.get(file).add(listener); + + File solDir = getSolDirectoryForLocalFile(file); + if (solDir == null) { + return; + } + while (!solDir.exists()) { + solDir = solDir.getParentFile(); + } + if (!watchedCookieDirectories.containsValue(solDir)) { + watchDir(solDir); + } + } + + public static void removeChangedListener(File file, CookiesChangedListener listener) { + if (!swfFileToListeners.containsKey(file)) { + return; + } + swfFileToListeners.get(file).remove(listener); + } + + + private static void watchDir(File dir) { + try { + WatchKey key = dir.toPath().register(Main.getWatcher(), StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); + watchedCookieDirectories.put(key, dir); + //System.err.println("started monitoring " + dir.getAbsolutePath()); + } catch (IOException ex) { + //ignored + //ex.printStackTrace(); + } + } + + private enum OSId { + WINDOWS, OSX, UNIX + } + + private static OSId getOSId() { + String OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); + if ((OS.contains("mac")) || (OS.contains("darwin"))) { + return OSId.OSX; + } else if (OS.contains("win")) { + return OSId.WINDOWS; + } else { + return OSId.UNIX; + } + } + + public static List getSolFilesForLocalFile(File file) { + File solDirectory = getSolDirectoryForLocalFile(file); + if (solDirectory == null) { + return new ArrayList<>(); + } + if (!solDirectory.exists()) { + return new ArrayList<>(); + } + File[] retArr = solDirectory.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isFile() && pathname.getName().toLowerCase(Locale.ENGLISH).endsWith(".sol"); + } + }); + return Arrays.asList(retArr); + } + + public static List getSolFilesForUrl(String url, FlashPlayerApi api) { + File solDirectory = getSolDirectoryForUrl(url, api); + if (solDirectory == null) { + return new ArrayList<>(); + } + File[] retArr = solDirectory.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isFile() && pathname.getName().toLowerCase(Locale.ENGLISH).endsWith(".sol"); + } + }); + return Arrays.asList(retArr); + } + + public static File getSolDirectoryForLocalFile(File file) { + File parentDir = getDirectory(); + if (parentDir == null) { + return null; + } + String absPath = file.getAbsolutePath(); + absPath = absPath.replace("\\", "/"); + if (absPath.startsWith("//?/") || absPath.startsWith("//./")) { + absPath = absPath.substring(4); + } else if (absPath.matches("^[a-zA-Z]:/.*")) { + absPath = absPath.substring(3); + } else if (absPath.startsWith("/")) { + absPath = absPath.substring(1); + } + + absPath = absPath.replace("/", File.separator); + + return new File(parentDir, "localhost" + File.separator + encodeLocalPath(absPath)); + } + + private static String encodeUrl(String path) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < path.length(); i++) { + char c = path.charAt(i); + if (c > 127) { + byte[] bytes = Utf8Helper.getBytes("" + c); + for (int b = 0; b < bytes.length; b++) { + int d = bytes[b] & 0xFF; + String hex = String.format("%02X", d); + sb.append("%").append(hex); + } + } else { + sb.append(c); + } + } + return sb.toString(); + } + + private static String encodeLocalPath(String path) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < path.length(); i++) { + char c = path.charAt(i); + if (c > 127) { + byte[] bytes = Utf8Helper.getBytes("" + c); + for (int b = 0; b < bytes.length; b++) { + int d = bytes[b] & 0xFF; + String hex = String.format("%02X", d); + hex = hex.charAt(0) + "#" + hex.charAt(1); + sb.append(hex); + } + } else { + sb.append(c); + } + } + return sb.toString(); + } + + public static File getSolDirectoryForUrl(String url, FlashPlayerApi api) { + File parentDir = getDirectory(api); + if (parentDir == null) { + return null; + } + + Pattern urlPattern = Pattern.compile("^https?://(?[^/]+)/(?[^?#]+)(\\?.*)?(#.*)?$"); + Matcher m = urlPattern.matcher(url); + if (!m.matches()) { + throw new IllegalArgumentException("Not a valid URL: " + url); + } + String host = m.group("host"); + String path = m.group("path"); + path = path.replace("/", File.separator); + + if (host.equals("localhost")) { + host = "#" + host; + } + + return new File(parentDir, host + File.separator + encodeUrl(path)); + } + + public static File getDirectory() { + return getDirectory(FlashPlayerApi.NPAPI); + } + + public static File getDirectory(FlashPlayerApi api) { + switch (api) { + case NPAPI: + return getNpApiDirectory(); + case PPAPI: + return getPpApiDirectory(); + } + return null; + } + + public static File getPpApiDirectory() { + String userHome = null; + try { + userHome = System.getProperty("user.home"); + } catch (SecurityException ignore) { + //ignored + } + + File sharedObjectsDir = null; + + switch (getOSId()) { + case WINDOWS: + File winLocalAppDataDir = null; + try { + String appDataEV = System.getenv("LOCALAPPDATA"); + if ((appDataEV != null) && (appDataEV.length() > 0)) { + winLocalAppDataDir = new File(appDataEV); + } + } catch (SecurityException ignore) { + //ignored + } + if (winLocalAppDataDir == null) { + return null; + } + sharedObjectsDir = new File(winLocalAppDataDir, "Google\\Chrome\\User Data\\Default\\Pepper Data\\Shockwave Flash\\WritableRoot\\#SharedObjects"); + break; + case OSX: + if (userHome == null) { + return null; + } + sharedObjectsDir = new File(userHome, "Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects"); + break; + case UNIX: + if (userHome == null) { + return null; + } + sharedObjectsDir = new File(userHome, ".config/google-chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects"); + break; + } + + if (sharedObjectsDir == null) { + return null; + } + if (!sharedObjectsDir.exists()) { + return null; + } + File[] subDirs = sharedObjectsDir.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + }); + + if (subDirs.length == 0) { + return null; + } + + return subDirs[0]; + } + + public static File getNpApiDirectory() { + String userHome = null; + try { + userHome = System.getProperty("user.home"); + } catch (SecurityException ignore) { + //ignored + } + + File sharedObjectsDir = null; + + switch (getOSId()) { + case WINDOWS: + File winAppDataDir = null; + try { + String appDataEV = System.getenv("APPDATA"); + if ((appDataEV != null) && (appDataEV.length() > 0)) { + winAppDataDir = new File(appDataEV); + } + } catch (SecurityException ignore) { + //ignored + } + if ((winAppDataDir != null) && winAppDataDir.isDirectory()) { + sharedObjectsDir = new File(winAppDataDir, "Macromedia\\Flash Player\\#SharedObjects"); + } else { + if (userHome == null) { + return null; + } + sharedObjectsDir = new File(userHome, "Application Data\\Macromedia\\Flash Player\\#SharedObjects"); + } + break; + case OSX: + if (userHome == null) { + return null; + } + sharedObjectsDir = new File(userHome, "Library/Preferences/Macromedia/Flash Player/#SharedObjects"); + break; + case UNIX: + if (userHome == null) { + return null; + } + sharedObjectsDir = new File(userHome, ".macromedia/Flash_Player/#SharedObjects"); + break; + } + + if (sharedObjectsDir == null) { + return null; + } + if (!sharedObjectsDir.exists()) { + return null; + } + File[] subDirs = sharedObjectsDir.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + }); + + if (subDirs.length == 0) { + return null; + } + + return subDirs[0]; + } + + public static void watchedDirectoryChanged(File file) { + //System.err.println("changed file: " + file.getAbsolutePath()); + if (!file.exists()) { + //System.err.println("no exist"); + //return; + } + + List swfFiles = new ArrayList<>(swfFileToListeners.keySet()); + for (File swfFile : swfFiles) { + File solDir = getSolDirectoryForLocalFile(swfFile); + if (file.equals(solDir) || file.getParentFile().equals(solDir)) { + fireChanged(swfFile, getSolFilesForLocalFile(swfFile)); + } else { + if (solDir.exists()) { + continue; + } + while (!solDir.exists()) { + solDir = solDir.getParentFile(); + } + if (solDir.equals(file)) { + if (!watchedCookieDirectories.containsValue(file)) { + watchDir(file); + } + } + } + } + } + + private static void fireChanged(File swfFile, List files) { + //System.err.println("- firing changed " + swfFile.getAbsolutePath()); + List listeners = swfFileToListeners.get(swfFile); + if (listeners != null) { + for (CookiesChangedListener l:listeners) { + l.cookiesChanged(swfFile, files); + } + } + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java index 06c226943..2b27a971b 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java @@ -30,6 +30,7 @@ import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.gui.MainPanel; import com.jpexs.decompiler.flash.gui.TreeNodeType; import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.gui.soleditor.Cookie; import com.jpexs.decompiler.flash.iggy.conversion.IggySwfBundle; import com.jpexs.decompiler.flash.tags.CSMTextSettingsTag; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; @@ -448,6 +449,10 @@ public abstract class AbstractTagTree extends JTree { if (t instanceof ABC) { return TreeNodeType.ABC; } + + if (t instanceof Cookie) { + return TreeNodeType.COOKIE; + } return TreeNodeType.FOLDER; } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java index 9344b0609..7bd646783 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java @@ -24,6 +24,8 @@ import com.jpexs.decompiler.flash.gui.TreeNodeType; import com.jpexs.decompiler.flash.gui.abc.ClassesListTreeModel; import com.jpexs.decompiler.flash.gui.helpers.CollectionChangedAction; import com.jpexs.decompiler.flash.gui.helpers.CollectionChangedEvent; +import com.jpexs.decompiler.flash.gui.soleditor.Cookie; +import com.jpexs.decompiler.flash.gui.soleditor.SharedObjectsStorage; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DoInitActionTag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; @@ -51,6 +53,7 @@ import com.jpexs.decompiler.flash.treeitems.HeaderItem; import com.jpexs.decompiler.flash.treeitems.Openable; import com.jpexs.decompiler.flash.treeitems.OpenableList; import com.jpexs.decompiler.flash.treeitems.TreeItem; +import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -66,6 +69,8 @@ import javax.swing.tree.TreePath; */ public class TagTreeModel extends AbstractTagTreeModel { + public static final String FOLDER_COOKIES = "cookies"; + public static final String FOLDER_SHAPES = "shapes"; public static final String FOLDER_MORPHSHAPES = "morphshapes"; @@ -277,9 +282,18 @@ public class TagTreeModel extends AbstractTagTreeModel { } } } + + List cookies = new ArrayList<>(); + if (swf.getFile() != null) { + List solFiles = SharedObjectsStorage.getSolFilesForLocalFile(new File(swf.getFile())); + for (File f : solFiles) { + cookies.add(new Cookie(swf, f)); + } + } nodeList.add(new HeaderItem(swf, translate("node.header"))); + addFolderItem(nodeList, emptyFolders, addAllFolders, translate("node.cookies"), FOLDER_COOKIES, swf, cookies); addFolderItem(nodeList, emptyFolders, addAllFolders, translate("node.shapes"), FOLDER_SHAPES, swf, shapes); addFolderItem(nodeList, emptyFolders, addAllFolders, translate("node.morphshapes"), FOLDER_MORPHSHAPES, swf, morphShapes); addFolderItem(nodeList, emptyFolders, addAllFolders, translate("node.sprites"), FOLDER_SPRITES, swf, sprites);