diff --git a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java index 9652ff8cc305de17886e67dd0ba9aef7e113f334..73311a3f079cc3681cc73a810f95439b7bb99807 100644 --- a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java @@ -256,6 +256,56 @@ public class StringUtils { return count; } + /** + * returns how many lines does it have between start to pos in content. + */ + public static int countLinesByPos(String content, int pos, int start) { + if (start >= pos) { + return 0; + } + int count = 0; + int tempPos = start; + do { + tempPos = content.indexOf("\n", tempPos); + if (tempPos == -1) { + break; + } + if (tempPos >= pos) { + break; + } + count += 1; + tempPos += 1; + } while (tempPos < content.length()); + return count; + } + + /** + * returns lines that contain pos to end if end is not -1. + */ + public static String getLine(String content, int pos, int end) { + if (pos >= content.length()) { + return ""; + } + if (end != -1) { + if (end > content.length()) { + end = content.length() - 1; + } + } else { + end = pos + 1; + } + // get to line head + int headPos = content.lastIndexOf("\n", pos); + if (headPos == -1) { + headPos = 0; + } + // get to line end + int endPos = content.indexOf("\n", end); + if (endPos == -1) { + endPos = content.length(); + } + return content.substring(headPos, endPos); + } + public static boolean isWhite(char chr) { return WHITES.indexOf(chr) != -1; } diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java index 0b7689e5a6f01e148d0cebe93ccffa318ecbfa65..307facfc7b331f92bfaa9da849ea2f62476c4841 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java @@ -12,7 +12,6 @@ import jadx.gui.JadxWrapper; import jadx.gui.utils.CacheObject; import jadx.gui.utils.CodeLinesInfo; import jadx.gui.utils.CodeUsageInfo; -import jadx.gui.utils.JNodeCache; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; import jadx.gui.utils.search.StringRef; @@ -30,12 +29,12 @@ public class IndexJob extends BackgroundJob { @Override protected void runJob() { - JNodeCache nodeCache = cache.getNodeCache(); - TextSearchIndex index = new TextSearchIndex(nodeCache); - CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache); + TextSearchIndex index = new TextSearchIndex(cache); + CodeUsageInfo usageInfo = new CodeUsageInfo(cache.getNodeCache()); + cache.setTextIndex(index); cache.setUsageInfo(usageInfo); - + addTask(index::indexResource); for (final JavaClass cls : wrapper.getIncludedClasses()) { addTask(() -> indexCls(cache, cls)); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 7534679bf5a34ffc30fbd00e988663905d8961b3..385ba946c661e29957d2649dcd7dd349a4d7161a 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -63,6 +63,10 @@ public class JadxSettings extends JadxCLIArgs { private Map windowPos = new HashMap<>(); private int mainWindowExtendedState = JFrame.NORMAL; private boolean codeAreaLineWrap = false; + private int srhResourceSkipSize = 1000; + private String srhResourceFileExt = ".xml|.html|.js|.json|.txt"; + private boolean keepCommonDialogOpen = false; + /** * UI setting: the width of the tree showing the classes, resources, ... */ @@ -400,6 +404,30 @@ public class JadxSettings extends JadxCLIArgs { return this.codeAreaLineWrap; } + public int getSrhResourceSkipSize() { + return srhResourceSkipSize; + } + + public void setSrhResourceSkipSize(int size) { + srhResourceSkipSize = size; + } + + public String getSrhResourceFileExt() { + return srhResourceFileExt; + } + + public void setSrhResourceFileExt(String all) { + srhResourceFileExt = all.trim(); + } + + public void setKeepCommonDialogOpen(boolean yes) { + keepCommonDialogOpen = yes; + } + + public boolean getKeepCommonDialogOpen() { + return keepCommonDialogOpen; + } + private void upgradeSettings(int fromVersion) { LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION); if (fromVersion == 0) { @@ -454,6 +482,10 @@ public class JadxSettings extends JadxCLIArgs { showHeapUsageBar = false; fromVersion++; } + if (fromVersion == 10) { + srhResourceSkipSize = 3; + srhResourceFileExt = ".xml|.html|.js|.json|.txt"; + } settingsVersion = CURRENT_SETTINGS_VERSION; sync(); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 22ee1b9be4ec71901816cddbcd7d07b6608844c6..540470242c965912c86e394e6bd370e07d352547 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -11,6 +11,10 @@ import java.util.Arrays; import java.util.Collection; import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,6 +78,7 @@ public class JadxSettingsWindow extends JDialog { leftPanel.add(makeProjectGroup()); leftPanel.add(makeEditorGroup()); leftPanel.add(makeOtherGroup()); + leftPanel.add(makeSearchResGroup()); rightPanel.add(makeDecompilationGroup()); @@ -474,6 +479,51 @@ public class JadxSettingsWindow extends JDialog { return group; } + private SettingsGroup makeSearchResGroup() { + SettingsGroup group = new SettingsGroup(NLS.str("preferences.search_res_title")); + int prevSize = settings.getSrhResourceSkipSize(); + String prevExts = settings.getSrhResourceFileExt(); + SpinnerNumberModel sizeLimitModel = new SpinnerNumberModel(prevSize, + 0, Integer.MAX_VALUE, 1); + JSpinner spinner = new JSpinner(sizeLimitModel); + JTextField fileExtField = new JTextField(); + group.addRow(NLS.str("preferences.res_skip_file"), spinner); + group.addRow(NLS.str("preferences.res_file_ext"), fileExtField); + + spinner.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + int size = (Integer) spinner.getValue(); + settings.setSrhResourceSkipSize(size); + } + }); + + fileExtField.getDocument().addDocumentListener(new DocumentListener() { + private void update() { + String ext = fileExtField.getText(); + settings.setSrhResourceFileExt(ext); + } + + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + }); + fileExtField.setText(prevExts); + + return group; + } + private void needReload() { needReload = true; } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResSearchNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResSearchNode.java new file mode 100644 index 0000000000000000000000000000000000000000..54b4da077f62e5c369082eb29170e40732b3bef7 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResSearchNode.java @@ -0,0 +1,68 @@ +package jadx.gui.treemodel; + +import javax.swing.*; + +import jadx.core.utils.StringUtils; + +public class JResSearchNode extends JNode { + private static final long serialVersionUID = -2222084945157778639L; + private final transient JResource resNode; + private final transient String text; + private final transient int line; + private final transient int pos; + + public JResSearchNode(JResource resNode, String text, int line, int pos) { + this.pos = pos; + this.text = text; + this.line = line; + this.resNode = resNode; + } + + public JResource getResNode() { + return resNode; + } + + public int getPos() { + return pos; + } + + @Override + public String makeDescString() { + return text; + } + + @Override + public JClass getJParent() { + return resNode.getJParent(); + } + + @Override + public int getLine() { + return line; + } + + @Override + public String makeLongStringHtml() { + return getName(); + } + + @Override + public Icon getIcon() { + return resNode.getIcon(); + } + + @Override + public String getName() { + return resNode.getName(); + } + + @Override + public String makeString() { + return resNode.makeString(); + } + + @Override + public boolean hasDescString() { + return !StringUtils.isEmpty(text); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java index 7feccda892ca38b7a659c039730bd8de95eb8157..0f22db7f4c9c7d191ad8718498586e0a3a92abd9 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java @@ -111,7 +111,10 @@ public abstract class CommonSearchDialog extends JDialog { } JumpPosition jmpPos; JNode node = (JNode) resultsModel.getValueAt(selectedId, 0); - if (node instanceof CodeNode) { + if (node instanceof JResSearchNode) { + jmpPos = new JumpPosition(((JResSearchNode) node).getResNode(), node.getLine()) + .setPrecise(((JResSearchNode) node).getPos()); + } else if (node instanceof CodeNode) { CodeNode codeNode = (CodeNode) node; jmpPos = new JumpPosition(node.getRootClass(), node.getLine(), codeNode.getPos()); if (codeNode.isPrecisePos()) { @@ -121,8 +124,9 @@ public abstract class CommonSearchDialog extends JDialog { jmpPos = new JumpPosition(node.getRootClass(), node.getLine()); } tabbedPane.codeJump(jmpPos); - - dispose(); + if (!mainWindow.getSettings().getKeepCommonDialogOpen()) { + dispose(); + } } @Override @@ -148,6 +152,15 @@ public abstract class CommonSearchDialog extends JDialog { JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + JCheckBox cbKeepOpen = new JCheckBox(NLS.str("search_dialog.keep_open")); + cbKeepOpen.setSelected(mainWindow.getSettings().getKeepCommonDialogOpen()); + cbKeepOpen.addActionListener(e -> { + mainWindow.getSettings().setKeepCommonDialogOpen(cbKeepOpen.isSelected()); + mainWindow.getSettings().sync(); + }); + buttonPane.add(cbKeepOpen); + buttonPane.add(Box.createRigidArea(new Dimension(15, 0))); buttonPane.add(progressPane); buttonPane.add(Box.createRigidArea(new Dimension(5, 0))); buttonPane.add(Box.createHorizontalGlue()); @@ -251,6 +264,10 @@ public abstract class CommonSearchDialog extends JDialog { resultsInfoLabel.setText(statusText); } + protected void showSearchState() { + resultsInfoLabel.setText(NLS.str("search_dialog.tip_searching")); + } + protected static class ResultsTable extends JTable { private static final long serialVersionUID = 3901184054736618969L; private final transient ResultsTableCellRenderer renderer; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 3a5ae0f7e2a62d47a8e7208d19824505383ca22b..1e8025fdccbd1f24abe89f3847780483c4fe2e0a 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -445,6 +445,8 @@ public class MainWindow extends JFrame { protected void resetCache() { cacheObject.reset(); // TODO: decompilation freezes sometime with several threads + this.cacheObject.setJRoot(treeRoot); + this.cacheObject.setJadxSettings(settings); int threadsCount = settings.getThreadsCount(); cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount)); cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount)); @@ -563,6 +565,8 @@ public class MainWindow extends JFrame { treeModel.setRoot(treeRoot); treeRoot.update(); reloadTree(); + cacheObject.setJRoot(treeRoot); + cacheObject.setJadxSettings(settings); } private void clearTree() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java index 9ef7817d31a8f4f30b93f0a5cdb2bc981892a1e4..5082d85f910ecb54c39e328f471db58325347ffa 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java @@ -39,7 +39,8 @@ public class SearchDialog extends CommonSearchDialog { FIELD, CODE, IGNORE_CASE, - USE_REGEX + USE_REGEX, + Resource } private transient Set options; @@ -76,7 +77,6 @@ public class SearchDialog extends CommonSearchDialog { if (lastSearch != null) { searchField.setText(lastSearch); searchField.selectAll(); - searchEmitter.emitSearch(); } searchField.requestFocus(); } @@ -91,6 +91,7 @@ public class SearchDialog extends CommonSearchDialog { JCheckBox caseChBox = makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE); JCheckBox regexChBox = makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX); + JCheckBox resChBox = makeOptionsCheckBox(NLS.str("search_dialog.resource"), SearchOptions.Resource); JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS); JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD); JCheckBox fldChBox = makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD); @@ -98,6 +99,7 @@ public class SearchDialog extends CommonSearchDialog { JPanel searchInPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); searchInPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in"))); + searchInPanel.add(resChBox); searchInPanel.add(clsChBox); searchInPanel.add(mthChBox); searchInPanel.add(fldChBox); @@ -200,6 +202,7 @@ public class SearchDialog extends CommonSearchDialog { if (index == null) { return Flowable.empty(); } + showSearchState(); return index.buildSearch(text, options); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java index bd16b1c3f25b6d662283fdcab14190022e61e8ae..71c4a2ef38909f0e8a82d343c9ed073a31064180 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java @@ -149,10 +149,10 @@ public class CodePanel extends JPanel { } public void refresh() { - int line; - int lineCount; + int line = 0; int tokenIndex; int pos = codeArea.getCaretPosition(); + int lineCount = codeArea.getLineCount(); try { // after rename the change of document is undetectable, so // use Token offset to calculate the new caret position. @@ -161,10 +161,12 @@ public class CodePanel extends JPanel { tokenIndex = getTokenIndexByOffset(token, pos); } catch (BadLocationException e) { e.printStackTrace(); - tokenIndex = 0; - line = codeArea.getLineCount() - 1; + tokenIndex = -1; + } + if (tokenIndex == -1) { + refreshToViewport(); + return; } - lineCount = codeArea.getLineCount(); codeArea.refresh(); initLineNumbers(); int lineDiff = codeArea.getLineCount() - lineCount; @@ -184,14 +186,23 @@ public class CodePanel extends JPanel { }); } + private void refreshToViewport() { + JViewport viewport = getCodeScrollPane().getViewport(); + Point viewPosition = viewport.getViewPosition(); + codeArea.refresh(); + initLineNumbers(); + SwingUtilities.invokeLater(() -> { + viewport.setViewPosition(viewPosition); + }); + } + private int getTokenIndexByOffset(Token token, int offset) { if (token != null) { int index = 1; while (token.getEndOffset() < offset) { token = token.getNextToken(); if (token == null) { - index = 0; - break; + return -1; } index++; } @@ -201,7 +212,7 @@ public class CodePanel extends JPanel { } private int getOffsetOfTokenByIndex(int index, Token token) { - if (token != null) { + if (token != null && index != -1) { for (int i = 0; i < index; i++) { token = token.getNextToken(); if (token == null) { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java index fc47dd81c3af48125383e8681c0fe66e06a6037e..c26c34db71ba5b403a095d0fa14b74c4cdaf9c88 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java @@ -7,6 +7,8 @@ import org.jetbrains.annotations.Nullable; import jadx.gui.jobs.DecompileJob; import jadx.gui.jobs.IndexJob; +import jadx.gui.settings.JadxSettings; +import jadx.gui.treemodel.JRoot; import jadx.gui.ui.SearchDialog; import jadx.gui.utils.search.TextSearchIndex; @@ -20,12 +22,16 @@ public class CacheObject { private String lastSearch; private JNodeCache jNodeCache; private Set lastSearchOptions; + private JRoot jRoot; + private JadxSettings settings; public CacheObject() { reset(); } public void reset() { + jRoot = null; + settings = null; decompileJob = null; indexJob = null; textIndex = null; @@ -89,4 +95,20 @@ public class CacheObject { public Set getLastSearchOptions() { return lastSearchOptions; } + + public void setJadxSettings(JadxSettings settings) { + this.settings = settings; + } + + public JadxSettings getJadxSettings() { + return this.settings; + } + + public JRoot getJRoot() { + return jRoot; + } + + public void setJRoot(JRoot jRoot) { + this.jRoot = jRoot; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java index 9654e2819b0e1ab95539f5454fa2ef104e9d8533..769fe195cff28bae46f2e09fd35c1203faae3abd 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java @@ -47,7 +47,7 @@ public class CodeIndex { } LOG.debug("Code search complete: {}, memory usage: {}", searchSettings.getSearchString(), UiUtils.memoryInfo()); emitter.onComplete(); - }, BackpressureStrategy.LATEST); + }, BackpressureStrategy.BUFFER); } public int size() { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/ResourceIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/ResourceIndex.java new file mode 100644 index 0000000000000000000000000000000000000000..a04c2fd9206216dacb0fb86437ef53a3b282a2cc --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/ResourceIndex.java @@ -0,0 +1,226 @@ +package jadx.gui.utils.search; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import javax.swing.tree.TreeNode; + +import io.reactivex.BackpressureStrategy; +import io.reactivex.Flowable; +import io.reactivex.FlowableEmitter; + +import jadx.api.ResourceFile; +import jadx.api.ResourceType; +import jadx.core.utils.files.FileUtils; +import jadx.gui.treemodel.JResSearchNode; +import jadx.gui.treemodel.JResource; +import jadx.gui.utils.CacheObject; + +import static jadx.core.utils.StringUtils.*; + +public class ResourceIndex { + private final List resNodes = new ArrayList<>(); + private final Set extSet = new HashSet<>(); + private CacheObject cache; + private String fileExts; + private boolean anyExt; + private int sizeLimit; + + public ResourceIndex(CacheObject cache) { + this.cache = cache; + } + + private void search(final JResource resNode, + FlowableEmitter emitter, + SearchSettings searchSettings) { + int pos = 0; + int line = 0; + int lastPos = 0; + int lastLineOccurred = -1; + JResSearchNode lastNode = null; + int searchStrLen = searchSettings.getSearchString().length(); + String content; + try { + content = resNode.getContent(); + } catch (Exception e) { + e.printStackTrace(); + return; + } + do { + searchSettings.setStartPos(lastPos); + pos = searchSettings.find(content); + if (pos > -1) { + line += countLinesByPos(content, pos, lastPos); + lastPos = pos + searchStrLen; + String lineText = getLine(content, pos, lastPos); + if (lastLineOccurred != line) { + lastLineOccurred = line; + if (lastNode != null) { + emitter.onNext(lastNode); + } + lastNode = new JResSearchNode(resNode, lineText.trim(), line + 1, pos); + } + } else { + if (lastNode != null) { // commit the final result node. + emitter.onNext(lastNode); + } + break; + } + } while (!emitter.isCancelled() && lastPos < content.length()); + } + + public Flowable search(SearchSettings settings) { + refreshSettings(); + if (resNodes.size() == 0) { + return Flowable.empty(); + } + return Flowable.create(emitter -> { + for (JResource resNode : resNodes) { + if (!emitter.isCancelled()) { + search(resNode, emitter, settings); + } + } + emitter.onComplete(); + }, BackpressureStrategy.BUFFER); + } + + public void index() { + refreshSettings(); + } + + private void clear() { + anyExt = false; + sizeLimit = -1; + fileExts = ""; + extSet.clear(); + resNodes.clear(); + } + + private void traverseTree(TreeNode root, ZipFile zip) { + for (int i = 0; i < root.getChildCount(); i++) { + TreeNode node = root.getChildAt(i); + if (node instanceof JResource) { + JResource resNode = (JResource) node; + try { + resNode.loadNode(); + } catch (Exception e) { + e.printStackTrace(); + return; + } + ResourceFile resFile = resNode.getResFile(); + if (resFile == null) { + traverseTree(node, zip); + } else { + if (resFile.getType() == ResourceType.ARSC && shouldSearchXML()) { + resFile.loadContent(); + resNode.getFiles().forEach(t -> traverseTree(t, null)); + } else { + filter(resNode, zip); + } + } + } + } + } + + private boolean shouldSearchXML() { + return anyExt || fileExts.contains(".xml"); + } + + private ZipFile getZipFile(TreeNode res) { + for (int i = 0; i < res.getChildCount(); i++) { + TreeNode node = res.getChildAt(i); + if (node instanceof JResource) { + JResource resNode = (JResource) node; + try { + resNode.loadNode(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + ResourceFile file = resNode.getResFile(); + if (file == null) { + ZipFile zip = getZipFile(resNode); + if (zip != null) { + return zip; + } + } else { + File zfile = file.getZipRef().getZipFile(); + if (FileUtils.isZipFile(zfile)) { + try { + return new ZipFile(zfile); + } catch (IOException ignore) { + } + } + } + } + } + return null; + } + + private void filter(JResource resNode, ZipFile zip) { + ResourceFile resFile = resNode.getResFile(); + if (JResource.isSupportedForView(resFile.getType())) { + long size = -1; + if (zip != null) { + ZipEntry entry = zip.getEntry(resFile.getOriginalName()); + if (entry != null) { + size = entry.getSize(); + } + } + if (size == -1) { // resource from ARSC is unknown size + try { + size = resNode.getContent().length(); + } catch (Exception ignore) { + return; + } + } + if (size <= sizeLimit) { + if (!anyExt) { + for (String ext : extSet) { + if (resFile.getOriginalName().endsWith(ext)) { + resNodes.add(resNode); + break; + } + } + } else { + resNodes.add(resNode); + } + } + + } + } + + private void refreshSettings() { + int size = cache.getJadxSettings().getSrhResourceSkipSize() * 10240; + if (size != sizeLimit + || !cache.getJadxSettings().getSrhResourceFileExt().equals(fileExts)) { + clear(); + sizeLimit = size; + fileExts = cache.getJadxSettings().getSrhResourceFileExt(); + String[] exts = fileExts.split("\\|"); + for (String ext : exts) { + ext = ext.trim(); + if (!ext.isEmpty()) { + anyExt = ext.equals("*"); + if (anyExt) { + break; + } + extSet.add(ext); + } + } + ZipFile zipFile = getZipFile(cache.getJRoot()); + traverseTree(cache.getJRoot(), zipFile); // reindex + try { + if (zipFile != null) { + zipFile.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java index d721d591504edb4d05d27686f1dd94a441543ded..4f295079b235bc74c35dd7c4acb1081d6d983654 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java @@ -35,7 +35,7 @@ public class SimpleIndex { } } emitter.onComplete(); - }, BackpressureStrategy.LATEST); + }, BackpressureStrategy.BUFFER); } public int size() { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java index f6215708d3d4c10bb920f8d8a20e2dabab7e7b07..197b7d49fe7c9b0553009b920211243d9b7ab828 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java @@ -19,6 +19,7 @@ import jadx.core.codegen.CodeWriter; import jadx.gui.treemodel.CodeNode; import jadx.gui.treemodel.JNode; import jadx.gui.ui.SearchDialog; +import jadx.gui.utils.CacheObject; import jadx.gui.utils.CodeLinesInfo; import jadx.gui.utils.JNodeCache; import jadx.gui.utils.UiUtils; @@ -28,6 +29,7 @@ import static jadx.gui.ui.SearchDialog.SearchOptions.CODE; import static jadx.gui.ui.SearchDialog.SearchOptions.FIELD; import static jadx.gui.ui.SearchDialog.SearchOptions.IGNORE_CASE; import static jadx.gui.ui.SearchDialog.SearchOptions.METHOD; +import static jadx.gui.ui.SearchDialog.SearchOptions.Resource; import static jadx.gui.ui.SearchDialog.SearchOptions.USE_REGEX; public class TextSearchIndex { @@ -40,11 +42,13 @@ public class TextSearchIndex { private final SimpleIndex mthSignaturesIndex; private final SimpleIndex fldSignaturesIndex; private final CodeIndex codeIndex; + private final ResourceIndex resIndex; private final List skippedClasses = new ArrayList<>(); - public TextSearchIndex(JNodeCache nodeCache) { - this.nodeCache = nodeCache; + public TextSearchIndex(CacheObject cache) { + this.nodeCache = cache.getNodeCache(); + this.resIndex = new ResourceIndex(cache); this.clsNamesIndex = new SimpleIndex(); this.mthSignaturesIndex = new SimpleIndex(); this.fldSignaturesIndex = new SimpleIndex(); @@ -85,6 +89,10 @@ public class TextSearchIndex { } } + public void indexResource() { + resIndex.index(); + } + public void remove(JavaClass cls) { this.clsNamesIndex.removeForCls(cls); this.mthSignaturesIndex.removeForCls(cls); @@ -121,6 +129,9 @@ public class TextSearchIndex { result = Flowable.concat(result, searchInSkippedClasses(searchSettings)); } } + if (options.contains(Resource)) { + result = Flowable.concat(result, resIndex.search(searchSettings)); + } return result; } @@ -146,7 +157,7 @@ public class TextSearchIndex { } LOG.debug("Skipped code search complete: {}, memory usage: {}", searchSettings.getSearchString(), UiUtils.memoryInfo()); emitter.onComplete(); - }, BackpressureStrategy.LATEST); + }, BackpressureStrategy.BUFFER); } private int searchNext(FlowableEmitter emitter, JavaNode javaClass, String code, final SearchSettings searchSettings) { diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index fb24d254d79ff0225320bbf1e445c5130208ca9b..a204562a4d32e079c2762862e18df7786b5683bf 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -77,6 +77,9 @@ search_dialog.info_label=Zeige Ergebnisse %1$d bis %2$d von %3$d search_dialog.col_node=Knoten search_dialog.col_code=Code search_dialog.regex=Regex +#search_dialog.resource= +#search_dialog.keep_open= +#search_dialog.tip_searching= usage_dialog.title=Verwendungssuche usage_dialog.label=Verwendung für: @@ -132,6 +135,9 @@ preferences.rename=Umbenennen preferences.rename_case=System unterscheidet zwischen Groß/Kleinschreibung preferences.rename_valid=Ist eine gültige Kennung preferences.rename_printable=Ist druckbar +#preferences.search_res_title= +#preferences.res_file_ext= +#preferences.res_skip_file= msg.open_file=Bitte Datei öffnen msg.saving_sources=Quellen speichern… diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 22369d5f91e4b1e22e3193ea7259378fca47a5e1..6ea54406803f53563f8fea972bb8000b4face172 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -77,6 +77,9 @@ search_dialog.info_label=Showing results %1$d to %2$d of %3$d search_dialog.col_node=Node search_dialog.col_code=Code search_dialog.regex=Regex +search_dialog.resource=Resource +search_dialog.keep_open=Keep open +search_dialog.tip_searching=Searching ... usage_dialog.title=Usage search usage_dialog.label=Usage for: @@ -132,6 +135,9 @@ preferences.rename=Rename preferences.rename_case=System case sensitivity preferences.rename_valid=To be valid identifier preferences.rename_printable=To be printable +preferences.search_res_title=Search Resource +preferences.res_file_ext=File Extensions (e.g. .xml|.html), * means all +preferences.res_skip_file=Skip files exceed (MB) msg.open_file=Please open file msg.saving_sources=Saving sources... diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index 111ae3c889b06cb059ebf0c52d30d20c909c2df3..9a279a3433db30b66e77d0fa1197ec3f298d9285 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -77,6 +77,9 @@ search_dialog.info_label=Mostrando resultados %1$d a %2$d de %3$d search_dialog.col_node=Nodo search_dialog.col_code=Código search_dialog.regex=Regex +#search_dialog.resource= +#search_dialog.keep_open= +#search_dialog.tip_searching= usage_dialog.title=Usage search usage_dialog.label=Usage for: @@ -132,6 +135,9 @@ preferences.reset_title=Reestablecer preferencias #preferences.rename_case= #preferences.rename_valid= #preferences.rename_printable= +#preferences.search_res_title= +#preferences.res_file_ext= +#preferences.res_skip_file= msg.open_file=Por favor, abra un archivo msg.saving_sources=Guardando fuente... diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 778729caefd8b44f796a08778065bcc144b17a93..348076c644d0138ca66b5bb222afe7a002bedf6a 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -77,6 +77,9 @@ search_dialog.info_label=%3$d 중 %1$d-%2$d 결과 표시 search_dialog.col_node=노드 search_dialog.col_code=코드 search_dialog.regex=정규식 +#search_dialog.resource= +#search_dialog.keep_open= +#search_dialog.tip_searching= usage_dialog.title=사용 검색 usage_dialog.label=다음의 사용 검색 결과: @@ -132,6 +135,9 @@ preferences.rename=이름 바꾸기 preferences.rename_case=시스템 대소문자 구분 preferences.rename_valid=유효한 식별자로 바꾸기 preferences.rename_printable=출력 가능하게 바꾸기 +#preferences.search_res_title= +#preferences.res_file_ext= +#preferences.res_skip_file= msg.open_file=파일을 여십시오 msg.saving_sources=소스 저장 중 ... diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index d2db468609d5fa1cee8f82827dd3b17de398907a..16eac3fc873502919e8a2be9e42ab37462dd1e19 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -77,6 +77,9 @@ search_dialog.info_label=显示了 %3$d 个结果中的第 %1$d 至第 %2$d 个 search_dialog.col_node=节点 search_dialog.col_code=代码 search_dialog.regex=正则表达式 +#search_dialog.resource= +#search_dialog.keep_open= +#search_dialog.tip_searching= usage_dialog.title=查找 usage_dialog.label=查找用例: @@ -132,6 +135,9 @@ preferences.rename=重命名 preferences.rename_case=系统区分大小写 preferences.rename_valid=是有效的标识符 preferences.rename_printable=是可打印 +#preferences.search_res_title= +#preferences.res_file_ext= +#preferences.res_skip_file= msg.open_file=请打开文件 msg.saving_sources=正在导出源代码...