From 4097d6b8f21918ff593e06f1e889ba9af56d3669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Mon, 22 Feb 2021 21:48:08 +0100 Subject: [PATCH] #1595 History of script search results per SWF --- CHANGELOG.md | 1 + .../flash/search/ABCSearchResult.java | 66 ++++- .../flash/search/ActionSearchResult.java | 28 ++- .../flash/search/ScriptNotFoundException.java | 9 + src/com/jpexs/decompiler/flash/gui/Main.java | 12 +- .../flash/gui/MainFrameClassic.java | 10 +- .../flash/gui/MainFrameClassicMenu.java | 3 + .../decompiler/flash/gui/MainFrameMenu.java | 48 +++- .../flash/gui/MainFrameRibbonMenu.java | 55 ++++ .../jpexs/decompiler/flash/gui/MainPanel.java | 14 +- .../flash/gui/RecentSearchesButton.java | 33 +++ .../decompiler/flash/gui/SearchListener.java | 2 +- .../decompiler/flash/gui/SearchPanel.java | 2 +- .../flash/gui/SearchResultsDialog.java | 24 +- .../flash/gui/SearchResultsStorage.java | 235 ++++++++++++++++++ .../decompiler/flash/gui/abc/ABCPanel.java | 4 +- .../flash/gui/action/ActionPanel.java | 5 +- .../flash/gui/locales/MainFrame.properties | 4 + 18 files changed, 528 insertions(+), 27 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ScriptNotFoundException.java create mode 100644 src/com/jpexs/decompiler/flash/gui/RecentSearchesButton.java create mode 100644 src/com/jpexs/decompiler/flash/gui/SearchResultsStorage.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 813916180..8c964aa87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. - #428, #583, #1373 Exporting PDFs with selectable text - Goto address dialog in Hex view (Ctrl+G or via context menu) - AS3 P-code editation checking all referenced labels exist +- #1595 History of script search results per SWF ### Fixed - #1298 AS1/2 properly decompiled setProperty/getProperty diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ABCSearchResult.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ABCSearchResult.java index 3b5214334..0c57c7a49 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ABCSearchResult.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ABCSearchResult.java @@ -12,19 +12,33 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.search; import com.jpexs.decompiler.flash.AppResources; +import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.ClassPath; import com.jpexs.decompiler.flash.abc.ScriptPack; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; /** * * @author JPEXS */ -public class ABCSearchResult { +public class ABCSearchResult implements Serializable { public static String STR_INSTANCE_INITIALIZER = AppResources.translate("trait.instanceinitializer"); @@ -32,7 +46,7 @@ public class ABCSearchResult { public static String STR_SCRIPT_INITIALIZER = AppResources.translate("trait.scriptinitializer"); - private final ScriptPack scriptPack; + private ScriptPack scriptPack; private final boolean pcode; @@ -40,6 +54,52 @@ public class ABCSearchResult { private final int traitId; + @SuppressWarnings("unchecked") + public ABCSearchResult(SWF swf, InputStream is) throws IOException, ScriptNotFoundException { + ObjectInputStream ois = new ObjectInputStream(is); + int versionMajor = ois.read(); + ois.read(); //minor + if (versionMajor != -1) { + throw new IOException("Unknown search result version"); + } + + ClassPath cp; + List traitIndices; + try { + cp = (ClassPath) ois.readObject(); + traitIndices = (List) ois.readObject(); + } catch (ClassNotFoundException ex) { + Logger.getLogger(ABCSearchResult.class.getName()).log(Level.SEVERE, null, ex); + throw new IOException(); + } + + this.pcode = ois.readBoolean(); + this.classIndex = ois.readInt(); + this.traitId = ois.readInt(); + boolean packFound = false; + for (ScriptPack pack : swf.getAS3Packs()) { + if (cp.equals(pack.getClassPath()) && traitIndices.equals(pack.traitIndices)) { + this.scriptPack = pack; + packFound = true; + break; + } + } + if (!packFound) { + throw new ScriptNotFoundException(); + } + } + + public void save(OutputStream os) throws IOException { + ObjectOutputStream oos = new ObjectOutputStream(os); + oos.write(1); //version major + oos.write(0); //version minor + oos.writeObject(scriptPack.getClassPath()); + oos.writeObject(scriptPack.traitIndices); + oos.writeBoolean(pcode); + oos.writeInt(classIndex); + oos.writeInt(traitId); + } + public ABCSearchResult(ScriptPack scriptPack) { this.scriptPack = scriptPack; pcode = false; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ActionSearchResult.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ActionSearchResult.java index c58b1ed1d..3bb05bad5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ActionSearchResult.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ActionSearchResult.java @@ -12,10 +12,18 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.search; +import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.tags.base.ASMSource; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; /** * @@ -29,6 +37,24 @@ public class ActionSearchResult { private final String path; + public ActionSearchResult(SWF swf, InputStream is) throws IOException, ScriptNotFoundException { + Map asms = swf.getASMs(false); + DataInputStream dais = new DataInputStream(is); + path = dais.readUTF(); + if (asms.containsKey(path)) { + src = asms.get(path); + } else { + throw new ScriptNotFoundException(); + } + pcode = dais.readBoolean(); + } + + public void save(OutputStream os) throws IOException { + DataOutputStream daos = new DataOutputStream(os); + daos.writeUTF(path); + daos.writeBoolean(pcode); + } + public ActionSearchResult(ASMSource src, boolean pcode, String path) { this.src = src; this.pcode = pcode; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ScriptNotFoundException.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ScriptNotFoundException.java new file mode 100644 index 000000000..633c4edda --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/search/ScriptNotFoundException.java @@ -0,0 +1,9 @@ +package com.jpexs.decompiler.flash.search; + +/** + * + * @author JPEXS + */ +public class ScriptNotFoundException extends Exception { + +} diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index e5296a28c..8d232fc1d 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -188,6 +188,8 @@ public class Main { private static List savedFiles = Collections.synchronizedList(new ArrayList<>()); + public static SearchResultsStorage searchResultsStorage = new SearchResultsStorage(); + //This method makes file watcher to shut up during our own file saving public static void startSaving(File savedFile) { savedFiles.add(savedFile); @@ -1855,9 +1857,12 @@ public class Main { } }); flashDebugger.addConnectionListener(debugHandler); + + searchResultsStorage.load(); } catch (IOException ex) { - logger.log(Level.SEVERE, "eeex", ex); + //ignore } + }); } @@ -2265,6 +2270,11 @@ public class Main { } public static void exit() { + try { + searchResultsStorage.save(); + } catch (IOException ex) { + //ignore + } Configuration.saveConfig(); if (mainFrame != null && mainFrame.getPanel() != null) { mainFrame.getPanel().unloadFlashPlayer(); diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameClassic.java b/src/com/jpexs/decompiler/flash/gui/MainFrameClassic.java index 32bf1c9af..9045392c9 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameClassic.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameClassic.java @@ -46,10 +46,12 @@ public final class MainFrameClassic extends AppFrame implements MainFrame { FlashPlayerPanel flashPanel = null; FlashPlayerPanel flashPanel2 = null; - try { - flashPanel = new FlashPlayerPanel(this); - flashPanel2 = new FlashPlayerPanel(this); - } catch (FlashUnsupportedException fue) { + if (Configuration.useAdobeFlashPlayerForPreviews.get()) { + try { + flashPanel = new FlashPlayerPanel(this); + flashPanel2 = new FlashPlayerPanel(this); + } catch (FlashUnsupportedException fue) { + } } boolean externalFlashPlayerUnavailable = flashPanel == null; diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameClassicMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameClassicMenu.java index 12aa87f70..edead606d 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameClassicMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameClassicMenu.java @@ -293,6 +293,9 @@ public class MainFrameClassicMenu extends MainFrameMenu { if (path.startsWith("/file/recent")) { return; } + if (path.startsWith("/tools/recentsearch")) { + return; + } MenuElement me = menuElements.get(path); if (me instanceof JMenu) { JMenu jm = (JMenu) me; diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index 152a3da2a..12f4e740e 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -48,6 +48,7 @@ import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -433,6 +434,10 @@ public abstract class MainFrameMenu implements MenuBuilder { Configuration.recentFiles.set(null); } + protected void clearRecentSearchesActionPerformed(ActionEvent evt) { + Main.searchResultsStorage.clear(); + } + protected void removeNonScripts() { mainFrame.getPanel().removeNonScripts(swf); } @@ -769,7 +774,7 @@ public abstract class MainFrameMenu implements MenuBuilder { setMenuEnabled("/debugging/debug/stop", isRunningOrDebugging); //same as previous setPathVisible("/debugging", isDebugRunning); - setMenuEnabled("/debugging/debug", isDebugRunning); + setPathVisible("/debugging/debug", isDebugRunning); //setMenuEnabled("/debugging/debug/pause", isDebugRunning); setMenuEnabled("/debugging/debug/stepOver", isDebugPaused); setMenuEnabled("/debugging/debug/stepInto", isDebugPaused); @@ -908,7 +913,14 @@ public abstract class MainFrameMenu implements MenuBuilder { finishMenu("/debugging"); addMenuItem("/tools", translate("menu.tools"), null, null, 0, null, false, null, false); - addMenuItem("/tools/search", translate("menu.tools.search"), "search16", this::searchActionPerformed, PRIORITY_TOP, null, true, null, false); + addMenuItem("/tools/search", translate("menu.tools.search"), "search16", this::searchActionPerformed, PRIORITY_TOP, this::loadRecentSearches, !supportsMenuAction(), null, false); + + if (!supportsMenuAction()) { + addMenuItem("/tools/recentsearch", translate("menu.recentSearches"), null, null, 0, this::loadRecentSearches, false, null, false); + finishMenu("/tools/recentsearch"); + } else { + finishMenu("/tools/search"); + } addMenuItem("/tools/replace", translate("menu.tools.replace"), "replace32", this::replaceActionPerformed, PRIORITY_TOP, null, true, null, false); addToggleMenuItem("/tools/timeline", translate("menu.tools.timeline"), null, "timeline32", this::timelineActionPerformed, PRIORITY_TOP, null); @@ -967,6 +979,7 @@ public abstract class MainFrameMenu implements MenuBuilder { addMenuItem("/settings/advancedSettings", translate("menu.advancedsettings.advancedsettings"), null, null, 0, null, false, null, false); addMenuItem("/settings/advancedSettings/advancedSettings", translate("menu.advancedsettings.advancedsettings"), "settings32", this::advancedSettingsActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("/settings/advancedSettings/clearRecentFiles", translate("menu.tools.otherTools.clearRecentFiles"), "clearrecent16", this::clearRecentFilesActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/settings/advancedSettings/clearRecentSearches", translate("menu.tools.otherTools.clearRecentSearches"), "clearrecent16", this::clearRecentSearchesActionPerformed, PRIORITY_MEDIUM, null, true, null, false); finishMenu("/settings/advancedSettings"); finishMenu("/settings"); @@ -1021,7 +1034,7 @@ public abstract class MainFrameMenu implements MenuBuilder { } */ - /*int deobfuscationMode = Configuration.deobfuscationMode.get(); + /*int deobfuscationMode = Configuration.deobfuscationMode.get(); switch (deobfuscationMode) { case 0: setGroupSelection("deobfuscation", "/settings/deobfuscation/old"); @@ -1157,6 +1170,35 @@ public abstract class MainFrameMenu implements MenuBuilder { } } + protected void loadRecentSearches(ActionEvent evt) { + clearMenu("/tools/" + (supportsMenuAction() ? "search" : "recentsearch")); + SWF swf = Main.getMainFrame().getPanel().getCurrentSwf(); + + List indices = Main.searchResultsStorage.getIndicesForSwf(swf); + for (int i = 0; i < indices.size(); i++) { + String searched = Main.searchResultsStorage.getSearchedValueAt(i); + final int fi = i; + ActionListener a = (ActionEvent e) -> { + SearchResultsDialog sr; + if (swf.isAS3()) { + sr = new SearchResultsDialog<>(Main.getMainFrame().getWindow(), searched, Main.searchResultsStorage.isIgnoreCaseAt(fi), Main.searchResultsStorage.isRegExpAt(fi), Main.getMainFrame().getPanel().getABCPanel()); + sr.setResults(Main.searchResultsStorage.getAbcSearchResultsAt(swf, fi)); + } else { + sr = new SearchResultsDialog<>(Main.getMainFrame().getWindow(), searched, Main.searchResultsStorage.isIgnoreCaseAt(fi), Main.searchResultsStorage.isRegExpAt(fi), Main.getMainFrame().getPanel().getActionPanel()); + sr.setResults(Main.searchResultsStorage.getActionSearchResultsAt(swf, fi)); + } + sr.setVisible(true); + if (!Main.getMainFrame().getPanel().searchResultsDialogs.containsKey(swf)) { + Main.getMainFrame().getPanel().searchResultsDialogs.put(swf, new ArrayList<>()); + } + Main.getMainFrame().getPanel().searchResultsDialogs.get(swf).add(sr); + }; + addMenuItem("/tools/" + (supportsMenuAction() ? "search" : "recentsearch") + "/" + i, searched, null, a, 0, null, true, null, false); + } + + finishMenu("/tools/" + (supportsMenuAction() ? "search" : "recentsearch")); + } + protected void loadRecent(ActionEvent evt) { List recentFiles = Configuration.getRecentFiles(); clearMenu("/file/" + (supportsMenuAction() ? "open" : "recent")); diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java index 46a70ebb1..f2d699d71 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbonMenu.java @@ -16,7 +16,10 @@ */ package com.jpexs.decompiler.flash.gui; +import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.search.ABCSearchResult; +import com.jpexs.decompiler.flash.search.ActionSearchResult; import java.awt.BorderLayout; import java.awt.Color; import java.awt.event.ActionEvent; @@ -152,6 +155,58 @@ public class MainFrameRibbonMenu extends MainFrameMenu { return resizePolicies; } + @SuppressWarnings("unchecked") + @Override + protected void loadRecentSearches(ActionEvent evt) { + if (evt.getSource() instanceof JPanel) { + JPanel targetPanel = (JPanel) evt.getSource(); + targetPanel.removeAll(); + JCommandButtonPanel searchHistoryPanel = new JCommandButtonPanel(CommandButtonDisplayState.MEDIUM); + String groupName = translate("menu.recentSearches"); + searchHistoryPanel.addButtonGroup(groupName); + + SWF swf = Main.getMainFrame().getPanel().getCurrentSwf(); + List indices = Main.searchResultsStorage.getIndicesForSwf(swf); + + int j = 0; + for (int i = indices.size() - 1; i >= 0; i--) { + String searched = Main.searchResultsStorage.getSearchedValueAt(i); + RecentSearchesButton historyButton = new RecentSearchesButton(j + " " + searched, null); + historyButton.search = searched; + final int fi = i; + historyButton.addActionListener((ActionEvent ae) -> { + SearchResultsDialog sr; + if (swf.isAS3()) { + sr = new SearchResultsDialog<>(Main.getMainFrame().getWindow(), searched, Main.searchResultsStorage.isIgnoreCaseAt(fi), Main.searchResultsStorage.isRegExpAt(fi), Main.getMainFrame().getPanel().getABCPanel()); + sr.setResults(Main.searchResultsStorage.getAbcSearchResultsAt(swf, fi)); + } else { + sr = new SearchResultsDialog<>(Main.getMainFrame().getWindow(), searched, Main.searchResultsStorage.isIgnoreCaseAt(fi), Main.searchResultsStorage.isRegExpAt(fi), Main.getMainFrame().getPanel().getActionPanel()); + sr.setResults(Main.searchResultsStorage.getActionSearchResultsAt(swf, fi)); + } + sr.setVisible(true); + if (!Main.getMainFrame().getPanel().searchResultsDialogs.containsKey(swf)) { + Main.getMainFrame().getPanel().searchResultsDialogs.put(swf, new ArrayList<>()); + } + Main.getMainFrame().getPanel().searchResultsDialogs.get(swf).add(sr); + }); + j++; + historyButton.setHorizontalAlignment(SwingUtilities.LEFT); + searchHistoryPanel.addButtonToLastGroup(historyButton); + } + + if (indices.isEmpty()) { + JCommandButton emptyLabel = new JCommandButton(translate("menu.recentSearches.empty")); + emptyLabel.setHorizontalAlignment(SwingUtilities.LEFT); + emptyLabel.setEnabled(false); + searchHistoryPanel.addButtonToLastGroup(emptyLabel); + } + + searchHistoryPanel.setMaxButtonColumns(1); + targetPanel.setLayout(new BorderLayout()); + targetPanel.add(searchHistoryPanel, BorderLayout.CENTER); + } + } + @Override protected void loadRecent(ActionEvent evt) { if (evt.getSource() instanceof JPanel) { diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 271a68e74..b08349125 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -330,7 +330,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se public TreeItem oldItem; - private Map> searchResultsDialogs = new HashMap<>(); + public Map> searchResultsDialogs = new HashMap<>(); private static final Logger logger = Logger.getLogger(MainPanel.class.getName()); @@ -822,7 +822,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se View.expandTreeNodes(tagTree, expandedNodes); } - private ABCPanel getABCPanel() { + public ABCPanel getABCPanel() { if (abcPanel == null) { abcPanel = new ABCPanel(this); displayPanel.add(abcPanel, CARDACTIONSCRIPT3PANEL); @@ -1804,25 +1804,25 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se boolean found = false; if (fAbcResult != null) { found = true; - getABCPanel().searchPanel.setSearchText(txt); - SearchResultsDialog sr = new SearchResultsDialog<>(getMainFrame().getWindow(), txt, getABCPanel()); + SearchResultsDialog sr = new SearchResultsDialog<>(getMainFrame().getWindow(), txt, ignoreCase, regexp, getABCPanel()); sr.setResults(fAbcResult); sr.setVisible(true); if (!searchResultsDialogs.containsKey(swf)) { searchResultsDialogs.put(swf, new ArrayList<>()); } searchResultsDialogs.get(swf).add(sr); + Main.searchResultsStorage.addABCResults(swf, txt, ignoreCase, regexp, fAbcResult); } else if (fActionResult != null) { found = true; - getActionPanel().searchPanel.setSearchText(txt); - SearchResultsDialog sr = new SearchResultsDialog<>(getMainFrame().getWindow(), txt, getActionPanel()); + SearchResultsDialog sr = new SearchResultsDialog<>(getMainFrame().getWindow(), txt, ignoreCase, regexp, getActionPanel()); sr.setResults(fActionResult); sr.setVisible(true); if (!searchResultsDialogs.containsKey(swf)) { searchResultsDialogs.put(swf, new ArrayList<>()); } searchResultsDialogs.get(swf).add(sr); + Main.searchResultsStorage.addActionResults(swf, txt, ignoreCase, regexp, fActionResult); } if (!found) { @@ -1971,7 +1971,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } @Override - public void updateSearchPos(TextTag item) { + public void updateSearchPos(String searchedText, boolean ignoreCase, boolean regExp, TextTag item) { View.checkAccess(); setTagTreeSelectedNode(item); diff --git a/src/com/jpexs/decompiler/flash/gui/RecentSearchesButton.java b/src/com/jpexs/decompiler/flash/gui/RecentSearchesButton.java new file mode 100644 index 000000000..97b59146c --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/RecentSearchesButton.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010-2021 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; + +import org.pushingpixels.flamingo.api.common.JCommandButton; +import org.pushingpixels.flamingo.api.common.icon.ResizableIcon; + +/** + * + * @author JPEXS + */ +public class RecentSearchesButton extends JCommandButton { + + public String search; + + public RecentSearchesButton(String title, ResizableIcon icon) { + super(title, icon); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/SearchListener.java b/src/com/jpexs/decompiler/flash/gui/SearchListener.java index c94940c5c..0b1f79df5 100644 --- a/src/com/jpexs/decompiler/flash/gui/SearchListener.java +++ b/src/com/jpexs/decompiler/flash/gui/SearchListener.java @@ -23,5 +23,5 @@ package com.jpexs.decompiler.flash.gui; */ public interface SearchListener { - public void updateSearchPos(E item); + public void updateSearchPos(String searchedText, boolean ignoreCase, boolean regExp, E item); } diff --git a/src/com/jpexs/decompiler/flash/gui/SearchPanel.java b/src/com/jpexs/decompiler/flash/gui/SearchPanel.java index dc9654f4f..c8a234eac 100644 --- a/src/com/jpexs/decompiler/flash/gui/SearchPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/SearchPanel.java @@ -123,7 +123,7 @@ public class SearchPanel extends JPanel { View.checkAccess(); searchPos.setText((foundPos + 1) + "/" + found.size()); - listener.updateSearchPos(found.get(foundPos)); + listener.updateSearchPos(searchFor, searchIgnoreCase, searchRegexp, found.get(foundPos)); } private void cancelButtonActionPerformed(ActionEvent evt) { diff --git a/src/com/jpexs/decompiler/flash/gui/SearchResultsDialog.java b/src/com/jpexs/decompiler/flash/gui/SearchResultsDialog.java index 75199254a..691db0924 100644 --- a/src/com/jpexs/decompiler/flash/gui/SearchResultsDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/SearchResultsDialog.java @@ -27,8 +27,11 @@ import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.List; +import javax.swing.BoxLayout; import javax.swing.DefaultListModel; import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -43,6 +46,7 @@ public class SearchResultsDialog extends AppDialog { private final JList resultsList; private final DefaultListModel model; + private final boolean regExp; private final SearchListener listener; @@ -50,17 +54,30 @@ public class SearchResultsDialog extends AppDialog { private final JButton closeButton = new JButton(translate("button.close")); - public SearchResultsDialog(Window owner, String text, SearchListener listener) { + private String text; + private final boolean ignoreCase; + + public SearchResultsDialog(Window owner, String text, boolean ignoreCase, boolean regExp, SearchListener listener) { super(owner); setTitle(translate("dialog.title").replace("%text%", text)); + this.text = text; Container cnt = getContentPane(); model = new DefaultListModel<>(); resultsList = new JList<>(model); + this.regExp = regExp; this.listener = listener; gotoButton.addActionListener(this::gotoButtonActionPerformed); closeButton.addActionListener(this::closeButtonActionPerformed); + JPanel paramsPanel = new JPanel(); + paramsPanel.setLayout(new BoxLayout(paramsPanel, BoxLayout.Y_AXIS)); + JLabel ignoreCaseLabel = new JLabel(AppDialog.translateForDialog("checkbox.ignorecase", SearchDialog.class) + ": " + (ignoreCase ? AppStrings.translate("yes") : AppStrings.translate("no"))); + JLabel regExpLabel = new JLabel(AppDialog.translateForDialog("checkbox.regexp", SearchDialog.class) + ": " + (regExp ? AppStrings.translate("yes") : AppStrings.translate("no"))); + paramsPanel.add(ignoreCaseLabel); + paramsPanel.add(regExpLabel); + + JPanel buttonsPanel = new JPanel(); buttonsPanel.setLayout(new FlowLayout()); buttonsPanel.add(gotoButton); @@ -88,9 +105,11 @@ public class SearchResultsDialog extends AppDialog { sp.setPreferredSize(new Dimension(300, 300)); cnt.add(sp, BorderLayout.CENTER); cnt.add(buttonsPanel, BorderLayout.SOUTH); + cnt.add(paramsPanel, BorderLayout.NORTH); pack(); View.centerScreen(this); View.setWindowIcon(this); + this.ignoreCase = ignoreCase; } public void setResults(List results) { @@ -102,7 +121,6 @@ public class SearchResultsDialog extends AppDialog { private void gotoButtonActionPerformed(ActionEvent evt) { gotoElement(); - setVisible(false); } private void closeButtonActionPerformed(ActionEvent evt) { @@ -111,7 +129,7 @@ public class SearchResultsDialog extends AppDialog { private void gotoElement() { if (resultsList.getSelectedIndex() != -1) { - listener.updateSearchPos(resultsList.getSelectedValue()); + listener.updateSearchPos(text, ignoreCase, regExp, resultsList.getSelectedValue()); } } } diff --git a/src/com/jpexs/decompiler/flash/gui/SearchResultsStorage.java b/src/com/jpexs/decompiler/flash/gui/SearchResultsStorage.java new file mode 100644 index 000000000..69841c4ab --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/SearchResultsStorage.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2021 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; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.search.ABCSearchResult; +import com.jpexs.decompiler.flash.search.ActionSearchResult; +import com.jpexs.decompiler.flash.search.ScriptNotFoundException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class SearchResultsStorage { + + public static final String SEARCH_RESULTS_FILE = "search_results.bin"; + + private static String getConfigFile() throws IOException { + return Configuration.getFFDecHome() + SEARCH_RESULTS_FILE; + } + + List swfIds = new ArrayList<>(); + List searchedValues = new ArrayList<>(); + List isRegExp = new ArrayList<>(); + List isIgnoreCase = new ArrayList<>(); + List data = new ArrayList<>(); + Map unpackedData = new HashMap<>(); + + public static String getSwfId(SWF swf) { + + SWF s = swf; + String binaryDataSuffix = ""; + + while (s.binaryData != null) { + binaryDataSuffix += "|binaryData[" + s.binaryData.getCharacterId() + "]"; + s = s.binaryData.getSwf(); + } + + if (s.swfList != null) { + String fileInsideTitle = s.getFile() == null ? s.getFileTitle() : ""; + if (fileInsideTitle != null && !"".equals(fileInsideTitle)) { + fileInsideTitle = "|" + fileInsideTitle; + } + return s.swfList.sourceInfo.getFile() + fileInsideTitle + binaryDataSuffix; + } + return "**NONE**"; + } + + public int getCount() { + return swfIds.size(); + } + + public String getSearchedValueAt(int index) { + return searchedValues.get(index); + } + + public boolean isIgnoreCaseAt(int index) { + return isIgnoreCase.get(index); + } + + public boolean isRegExpAt(int index) { + return isRegExp.get(index); + } + + public List getIndicesForSwf(SWF swf) { + String swfId = getSwfId(swf); + List res = new ArrayList<>(); + for (int i = 0; i < swfIds.size(); i++) { + if (swfIds.get(i).equals(swfId)) { + res.add(i); + } + } + return res; + } + + @SuppressWarnings("unchecked") + public List getAbcSearchResultsAt(SWF swf, int index) { + if (unpackedData.containsKey(index)) { + return (List) unpackedData.get(index); + } + List result = new ArrayList<>(); + byte[] itemData = data.get(index); + + try { + DataInputStream dais = new DataInputStream(new ByteArrayInputStream(itemData)); + int cnt = dais.readInt(); + for (int i = 0; i < cnt; i++) { + try { + result.add(new ABCSearchResult(swf, dais)); + } catch (ScriptNotFoundException ex) { + //ignore + } + } + } catch (IOException ex) { + Logger.getLogger(SearchResultsStorage.class.getName()).log(Level.SEVERE, null, ex); + } + unpackedData.put(index, result); + return result; + } + + @SuppressWarnings("unchecked") + public List getActionSearchResultsAt(SWF swf, int index) { + if (unpackedData.containsKey(index)) { + return (List) unpackedData.get(index); + } + List result = new ArrayList<>(); + byte[] itemData = data.get(index); + + try { + DataInputStream dais = new DataInputStream(new ByteArrayInputStream(itemData)); + int cnt = dais.readInt(); + for (int i = 0; i < cnt; i++) { + try { + result.add(new ActionSearchResult(swf, dais)); + } catch (ScriptNotFoundException ex) { + //ignore + } + } + } catch (IOException ex) { + Logger.getLogger(SearchResultsStorage.class.getName()).log(Level.SEVERE, null, ex); + } + unpackedData.put(index, result); + return result; + } + + @SuppressWarnings("unchecked") + public void load() throws IOException { + String configFile = getConfigFile(); + if (new File(configFile).exists()) { + try (FileInputStream fis = new FileInputStream(configFile); + ObjectInputStream ois = new ObjectInputStream(fis)) { + swfIds = (List) ois.readObject(); + searchedValues = (List) ois.readObject(); + isIgnoreCase = (List) ois.readObject(); + isRegExp = (List) ois.readObject(); + data = (List) ois.readObject(); + } catch (ClassNotFoundException ex) { + Logger.getLogger(SearchResultsStorage.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + public void save() throws IOException { + String configFile = getConfigFile(); + try (FileOutputStream fos = new FileOutputStream(configFile); + ObjectOutputStream oos = new ObjectOutputStream(fos)) { + oos.writeObject(swfIds); + oos.writeObject(searchedValues); + oos.writeObject(isIgnoreCase); + oos.writeObject(isRegExp); + oos.writeObject(data); + } + } + + public void addABCResults(SWF swf, String searchedString, boolean ignoreCase, boolean regExp, List results) { + swfIds.add(getSwfId(swf)); + searchedValues.add(searchedString); + isIgnoreCase.add(ignoreCase); + isRegExp.add(regExp); + unpackedData.put(data.size(), new ArrayList<>(results)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream daos = new DataOutputStream(baos); + try { + daos.writeInt(results.size()); + for (ABCSearchResult res : results) { + res.save(daos); + } + daos.flush(); + } catch (IOException ex) { + Logger.getLogger(SearchResultsStorage.class.getName()).log(Level.SEVERE, null, ex); + } + + data.add(baos.toByteArray()); + + } + + public void addActionResults(SWF swf, String searchedString, boolean ignoreCase, boolean regExp, List results) { + swfIds.add(getSwfId(swf)); + searchedValues.add(searchedString); + isIgnoreCase.add(ignoreCase); + isRegExp.add(regExp); + unpackedData.put(data.size(), new ArrayList<>(results)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream daos = new DataOutputStream(baos); + try { + daos.writeInt(results.size()); + for (ActionSearchResult res : results) { + res.save(daos); + } + daos.flush(); + } catch (IOException ex) { + Logger.getLogger(SearchResultsStorage.class.getName()).log(Level.SEVERE, null, ex); + } + data.add(baos.toByteArray()); + } + + public void clear() { + swfIds.clear(); + searchedValues.clear(); + isIgnoreCase.clear(); + isRegExp.clear(); + unpackedData.clear(); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java index ae57b96b8..5eaf0fae7 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java @@ -1340,9 +1340,11 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener