From fbe4d0451fcb8d8b2877de00dbd503fc23d24b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Fri, 8 Nov 2024 00:36:22 +0100 Subject: [PATCH] Edit flash cookies (SOL) in the SWF tree. --- .../jpexs/decompiler/flash/sol/LsoTag.java | 2 +- .../decompiler/flash/gui/AppStrings.java | 4 + src/com/jpexs/decompiler/flash/gui/Main.java | 118 +++++- .../jpexs/decompiler/flash/gui/MainPanel.java | 6 + .../decompiler/flash/gui/PreviewPanel.java | 210 +++++++++- .../decompiler/flash/gui/TreeNodeType.java | 3 +- .../flash/gui/graphics/cookie16.png | Bin 0 -> 1027 bytes .../flash/gui/graphics/cookie32.png | Bin 0 -> 2254 bytes .../flash/gui/graphics/foldercookies16.png | Bin 0 -> 6140 bytes .../flash/gui/locales/MainFrame.properties | 3 +- .../flash/gui/soleditor/Cookie.java | 79 ++++ .../gui/soleditor/CookiesChangedListener.java | 28 ++ .../flash/gui/soleditor/FlashPlayerApi.java | 32 ++ .../gui/soleditor/SharedObjectsStorage.java | 387 ++++++++++++++++++ .../flash/gui/tagtree/AbstractTagTree.java | 5 + .../flash/gui/tagtree/TagTreeModel.java | 14 + 16 files changed, 876 insertions(+), 15 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cookie16.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/cookie32.png create mode 100644 src/com/jpexs/decompiler/flash/gui/graphics/foldercookies16.png create mode 100644 src/com/jpexs/decompiler/flash/gui/soleditor/Cookie.java create mode 100644 src/com/jpexs/decompiler/flash/gui/soleditor/CookiesChangedListener.java create mode 100644 src/com/jpexs/decompiler/flash/gui/soleditor/FlashPlayerApi.java create mode 100644 src/com/jpexs/decompiler/flash/gui/soleditor/SharedObjectsStorage.java 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 0000000000000000000000000000000000000000..81f66df45361e7eb51c724f3a8f1b768faad81a7 GIT binary patch literal 1027 zcmV+e1pNDnP)EX>4Tx04R}tkv&MmKpe$iQ>CI6K|6>zWT;LSii$W&6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?JWDYS_3;J6>}?mh0_0Yam~RI@7zsG4P@ z;xRFsTNMMZ=tBU5h$0{{Q%|H9Gw>W=_we!cF2b|C&;2?2mAuISpGZ8%bi*RvAfDN@ zbk6(4Ay$$U;&b8&gDyz?$aUG}H_kiNlJjQNECM zS>e3JS*_Gq>z@3D!MwJT<~q$$#Ib|~k`N)IhB7L!5TR8g#YBqsV;=qy$DbsZOs+B* zITlcb3d!+<|H1EW&BD~An-q)z-7mKNF$M&7fo9#dzmILZc>?&Kfh(=;uQq_$Ptxmc zEqnwFYy%h9ZB5<-E_Z<8CtWfmNAlAY3I*W(jJ_!c4BY~~Yi@6?eVjf38R}~J1~@nb z#*37_?(y!P&ffk#)9UXBcja=a9`pos00006VoOIv000300RL*)U>^Vg010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=nDrDGBc6Jc%A?N0su)wK~y-)&63Y*6Hye#zjN=M zOlGV}8i=WF5f#OM2r3p7(g$!MSOrlOSK_|(8Qh91pP#7U~wgJ#ipW#ypnUcGX9YUSPfkJ<>q z{M<}Q5MD^)N+XQuP~L|l1+=k<;uKO6BndVUZ!FWzA&D3oguVX4&sJyFk%Eqtau5Ur z^$>BYk@GWfl|r0qO0}*4xIfhG7%=m#YNLZhYglWMXoH~9K@_J0R02L%f8l#01lZmi z>eTR1%z4uj;~ta{V7ACQ3YSlop~PT^Ai}B1aqz(G+z5drf+Gdm-EBm%1`&`^5>cWN zMhQSbVa$c+Dp=lq_}vJ=nl_BJZ%x!%y+5&lpx(jP%{JIFjAa;O!NBTB1O}EM#HEWT z%8-I~avz__gP8#$Y&N?Hnq3?_Tzox3AV@s779aeq3)fL#0KI++DQM6$Ln+B)*-X%l zlEo2&oafYtXdyMcVnOI97VkHC9LbHRR)%f<;7>OK5gN@s^1v;;fD`&`n3Q6002ovPDHLkV1lkM-&Ozs literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f973d1018a03cd3559d48193cc05babdb9fc8eec GIT binary patch literal 2254 zcmV;<2r>7GP)EX>4Tx04R}tkv&MmKpe$iQ>CI6K|6>zWT;LSii$W&6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?JWDYS_3;J6>}?mh0_0Yam~RI@7zsG4P@ z;xRFsTNMMZ=tBU5h$0{{Q%|H9Gw>W=_we!cF2b|C&;2?2mAuISpGZ8%bi*RvAfDN@ zbk6(4Ay$$U;&b8&gDyz?$aUG}H_kiNlJjQNECM zS>e3JS*_Gq>z@3D!MwJT<~q$$#Ib|~k`N)IhB7L!5TR8g#YBqsV;=qy$DbsZOs+B* zITlcb3d!+<|H1EW&BD~An-q)z-7mKNF$M&7fo9#dzmILZc>?&Kfh(=;uQq_$Ptxmc zEqnwFYy%h9ZB5<-E_Z<8CtWfmNAlAY3I*W(jJ_!c4BY~~Yi@6?eVjf38R}~J1~@nb z#*37_?(y!P&ffk#)9UXBcja=a9`pos00006VoOIv000300RL*)U>^Vg010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=nDrDF*42U8_NIy2FpoAK~z}7<(El}Tvru_zjN;! zYUpnFWH*T&%K@AaB5VOMvcXvdA#k!Y3oqEfIub}`n>7noSWIFETM!FNBq)hOSRf%Q zHjs`L#n_#}j=LSZ=kA){z2~raRXw=c3Q;yl+|pfCy<7ME=Rf}$9{j&A`EL%LKYOAA zOaNa3&HyJ2K94bN0LC(6a{`xvSIl@zgp~^yuXH{Yfb(Zhc;FE5EO1WX8zQ6tW+u@L zRm03c1VKzh3V}-^@_U4rFuQ%>;+6P`0h~X3V#MI2nf=hzo^y^Q@s7+30vJXpB0(cj zjZUF7+6B>|Ud^cId2mktgz#&OOBXI)ZtP{pKfw4naL&wL=yZ#xS2kOIquIfUQLm(U z?@$FbB}5}cV`-zs+`=07)|(_EjMY<@c=tFa$AHesCl0=M>Dof)V*#8$dtwAQ2mDyD zC)b;8cVm8)J1b4ZjH$6IS?U4X;?OYGHoM$eZP6k0ETFdg9>q zOV<{94+L=jyI=FbY2XDfJefLNqtoM~)dt;OWUQVuGf^i?Jci)}1i?8$4a|&MCFQ_) zjnP_4A`a&eVA7CBfOk%Q;o!|n*A{GGGuh?7*&*P0CmhebIOl}%dWA=4#_1N3{rjq< z-eIaZKmqRr=R61Yl}1$|^@4Y<1PO@X3gx&V&jUArn?oY(r`ivIUu2#nOUg^spoog9 zmc|4`1kCmnubQD|m>SW_NEx6AYAygj0)Bhp;^hv2+wsTAsQSGkgrpY&Au48u7pNqT zO6o~mnE^2>onD~T4HQ9lVR2W#0EMGu9d%=9e4vf^uNI#%CsKWXZOiN=+C=jI=rdHKUp*h!cRXV;lqS z+^>N%t!~eMxU^2I6X+I^>9HDl>bHy=plYl&yL@nWgKiN~Gp0r>y8uZ4-10_;TZXpJ;OfTxB+;JtiyUp<${4oy;o$XG2W&)n8!Tj!iGHCpBILq-V3_(;CX`)wA4 zN|rE^rvx=BS%UY{_d<>i0T7YNdY&*|8v`k6bZ)oWOHYN7YDz61!}_B0ZkPh^g_-dh zdE$v`)bfPXxiT|^sXgmJNv%UfFx4`h?n^hb(mTI{`khAxzc~>`DjC%*A*vFBG5G9k zL2id|R%~$F4#7oLqS_$*h-~j|X$(ZV8>~p_loLT*`MrN)c@GBUU5o<*d__=J8*Q4M zf=Zf{u}=N2_Gt#a5Ls__Y4-xvJYl@PmnH-!Wl@u|dAvKs0KydmQv)?N zk$=yxu-WPjhXbS8DY!nr%Iy1#%q^_Z>V&O?(E*_tX8n+s{9YLX;NVp?im3gn?PB}EeZxc%l}4x7+Cg>enUvO1H9~)zQJC4QLm_ZZ-im5(MB}seW5ckY(VHYIaIh zd?icx+{5EMvVRos%ADS-;M6NAM;{)i2uk7|mCU34AoPmJ?WIlTmm7HRIXFG0<0JCt z5N%k5^-9`qw2PP3_;yr#)_W(lJf)VWTc!B^qBJN>wLFDY}TSE=!vo66dH_ zvUyee+tOycJv5N~b@rBh>&r)4saAjK#u@q^yh~%j+ z67ODKZ+8CB3U2ex^2YE)bmpl?&D7S^5R9WB`vgS#ZFsPXP+r{b2BH}QklHt1cGr@H zG);B1T3M`Q$&1}0{${n&xiS0R-LU6K_RZsm$y47|vw1b^s&U9*3Phwoenm5uHrmY1 zud=YxAV#B_C1nfNf%ni;P{b1N->ziIFTs9(^WMfgS8pyC51a{KpS{I1#}8Yc`i7~^ znb`+wOqg+46&KAY)VR0SAL*lK}D(@Y4|Di@$sA?Yno^ zTKch5@$0jxEb^f=v`Lele(hr`7JA-e~oX#Hh7u zo>;w_t$63=fPbj!Ya!}vjJmk6yx#8i3j52|JNBvG4ewvaj!b7}R_#~PPLgJwf6YDE c$p7^E4|&SbF=@3{!~g&Q07*qoM6N<$f=aMLM*si- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..211b5d518295440dfa1afc225fde553b0158638f GIT binary patch literal 6140 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 literal 0 HcmV?d00001 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);