Skip to content

Commit

Permalink
fix(gui): improve resources search (#1648)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Aug 19, 2022
1 parent c7e6e28 commit bc4db61
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private void startAnalysis() {
private void loadReport() {
try {
QuarkReportNode quarkNode = new QuarkReportNode(reportFile);
JRoot root = mainWindow.getCacheObject().getJRoot();
JRoot root = mainWindow.getTreeRoot();
root.replaceCustomNode(quarkNode);
root.update();
mainWindow.reloadTree();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package jadx.gui.search.providers;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.swing.tree.TreeNode;

Expand All @@ -18,56 +15,55 @@
import jadx.api.ICodeWriter;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.core.utils.files.FileUtils;
import jadx.api.plugins.utils.CommonFileUtils;
import jadx.gui.jobs.Cancelable;
import jadx.gui.search.ISearchProvider;
import jadx.gui.search.SearchSettings;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResSearchNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.treemodel.JRoot;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.CacheObject;

public class ResourceSearchProvider implements ISearchProvider {
private static final Logger LOG = LoggerFactory.getLogger(ResourceSearchProvider.class);

private final CacheObject cache;
private final SearchSettings searchSettings;
private final Set<String> extSet = new HashSet<>();

private List<JResource> resNodes;
private String fileExts;
private final Set<String> extSet;
private final int sizeLimit;
private boolean anyExt;
private int sizeLimit;

private int progress;
/**
* Resources queue for process. Using UI nodes to reuse loading cache
*/
private final Deque<JResource> resQueue;
private int pos;

public ResourceSearchProvider(MainWindow mw, SearchSettings searchSettings) {
this.cache = mw.getCacheObject();
this.searchSettings = searchSettings;
this.sizeLimit = mw.getSettings().getSrhResourceSkipSize() * 1048576;
this.extSet = buildAllowedFilesExtensions(mw.getSettings().getSrhResourceFileExt());
this.resQueue = initResQueue(mw);
}

@Override
public @Nullable JNode next(Cancelable cancelable) {
if (resNodes == null) {
load();
}
if (resNodes.isEmpty()) {
return null;
}
while (true) {
if (cancelable.isCanceled()) {
return null;
}
JResource resNode = resNodes.get(progress);
JResource resNode = getNextNode();
if (resNode == null) {
return null;
}
JNode newResult = search(resNode);
if (newResult != null) {
return newResult;
}
progress++;
pos = 0;
if (progress >= resNodes.size()) {
resQueue.removeLast();
addChildren(resQueue, resNode);
if (resQueue.isEmpty()) {
return null;
}
}
Expand All @@ -94,133 +90,110 @@ private JNode search(JResource resNode) {
return new JResSearchNode(resNode, line.trim(), newPos);
}

private synchronized void load() {
resNodes = new ArrayList<>();
sizeLimit = cache.getJadxSettings().getSrhResourceSkipSize() * 1048576;
fileExts = cache.getJadxSettings().getSrhResourceFileExt();
for (String extStr : fileExts.split("\\|")) {
String ext = extStr.trim();
if (!ext.isEmpty()) {
anyExt = ext.equals("*");
if (anyExt) {
break;
}
extSet.add(ext);
}
private @Nullable JResource getNextNode() {
JResource node = resQueue.peekLast();
if (node == null) {
return null;
}
try (ZipFile zipFile = getZipFile(cache.getJRoot())) {
traverseTree(cache.getJRoot(), zipFile); // reindex
try {
node.loadNode();
} catch (Exception e) {
LOG.error("Failed to apply settings to resource index", e);
LOG.error("Error load resource node: {}", node, e);
resQueue.removeLast();
return getNextNode();
}
if (node.getType() == JResource.JResType.FILE) {
if (shouldProcess(node)) {
return node;
}
resQueue.removeLast();
return getNextNode();
}
// dit or root
resQueue.removeLast();
addChildren(resQueue, node);
return getNextNode();
}

private void traverseTree(TreeNode root, @Nullable ZipFile zip) {
for (int i = 0; i < root.getChildCount(); i++) {
TreeNode node = root.getChildAt(i);
private void addChildren(Deque<JResource> deque, JResource resNode) {
Enumeration<TreeNode> children = resNode.children();
while (children.hasMoreElements()) {
TreeNode node = children.nextElement();
if (node instanceof JResource) {
JResource resNode = (JResource) node;
try {
resNode.loadNode();
} catch (Exception e) {
LOG.error("Error load resource node: {}", resNode, e);
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);
}
}
deque.add((JResource) node);
}
}
}

private boolean shouldSearchXML() {
return anyExt || fileExts.contains(".xml");
}

@Nullable
private ZipFile getZipFile(TreeNode res) {
for (int i = 0; i < res.getChildCount(); i++) {
TreeNode node = res.getChildAt(i);
private static Deque<JResource> initResQueue(MainWindow mw) {
JRoot jRoot = mw.getTreeRoot();
Deque<JResource> deque = new ArrayDeque<>(jRoot.getChildCount());
Enumeration<TreeNode> children = jRoot.children();
while (children.hasMoreElements()) {
TreeNode node = children.nextElement();
if (node instanceof JResource) {
JResource resNode = (JResource) node;
try {
resNode.loadNode();
} catch (Exception e) {
LOG.error("Error load resource node: {}", resNode, e);
return null;
}
ResourceFile file = resNode.getResFile();
if (file == null) {
ZipFile zip = getZipFile(resNode);
if (zip != null) {
return zip;
}
} else {
ResourceFile.ZipRef zipRef = file.getZipRef();
if (zipRef != null) {
File zfile = zipRef.getZipFile();
if (FileUtils.isZipFile(zfile)) {
try {
return new ZipFile(zfile);
} catch (IOException ignore) {
}
}
}
}
deque.add(resNode);
}
}
return null;
return deque;
}

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();
private Set<String> buildAllowedFilesExtensions(String srhResourceFileExt) {
Set<String> set = new HashSet<>();
for (String extStr : srhResourceFileExt.split("[|.]")) {
String ext = extStr.trim();
if (!ext.isEmpty()) {
anyExt = ext.equals("*");
if (anyExt) {
break;
}
set.add(ext);
}
if (size == -1) { // resource from ARSC is unknown size
try {
size = resNode.getCodeInfo().getCodeStr().length();
} catch (Exception ignore) {
return;
}
return set;
}

private boolean shouldProcess(JResource resNode) {
if (!anyExt) {
String fileExt;
ResourceFile resFile = resNode.getResFile();
if (resFile.getType() == ResourceType.ARSC) {
fileExt = "xml";
} else {
fileExt = CommonFileUtils.getFileExtension(resFile.getOriginalName());
if (fileExt == null) {
return false;
}
}
if (size <= sizeLimit) {
if (!anyExt) {
for (String ext : extSet) {
if (resFile.getOriginalName().endsWith(ext)) {
resNodes.add(resNode);
break;
}
}
} else {
resNodes.add(resNode);
}
} else {
LOG.debug("Resource index skipped because of size limit: {} res size {} bytes", resNode, size);
if (!extSet.contains(fileExt)) {
return false;
}
}
if (sizeLimit == 0) {
return true;
}
try {
int charsCount = resNode.getCodeInfo().getCodeStr().length();
long size = charsCount * 8L;
if (size > sizeLimit) {
LOG.debug("Resource search skipped because of size limit: {} res size {} bytes", resNode, size);
return false;
}
return true;
} catch (Exception e) {
LOG.warn("Resource load error: {}", resNode, e);
return false;
}
}

@Override
public int progress() {
return progress;
return 0;
}

@Override
public int total() {
return resNodes == null ? 0 : resNodes.size();
return 0;
}
}
6 changes: 5 additions & 1 deletion jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public final void update() {
}

@Override
public void loadNode() {
public synchronized void loadNode() {
getCodeInfo();
update();
}
Expand All @@ -102,6 +102,10 @@ public String getName() {
return name;
}

public JResType getType() {
return type;
}

public List<JResource> getFiles() {
return files;
}
Expand Down
4 changes: 0 additions & 4 deletions jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,6 @@ private void update() {

protected void resetCache() {
cacheObject.reset();
cacheObject.setJRoot(treeRoot);
cacheObject.setJadxSettings(settings);
}

synchronized void runInitialBackgroundJobs() {
Expand Down Expand Up @@ -680,13 +678,11 @@ private void saveAll(boolean export) {

public void initTree() {
treeRoot = new JRoot(wrapper);
cacheObject.setJRoot(treeRoot);
treeRoot.setFlatPackages(isFlattenPackage);
treeModel.setRoot(treeRoot);
addTreeCustomNodes();
treeRoot.update();
reloadTree();
cacheObject.setJadxSettings(settings);
}

private void clearTree() {
Expand Down
Loading

0 comments on commit bc4db61

Please sign in to comment.