diff --git a/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java b/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java index 645935bee..972bc775e 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java @@ -12,8 +12,10 @@ import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JInputScript; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JResource; +import jadx.gui.treemodel.JSubResource; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorViewState; +import jadx.gui.utils.UiUtils; public class TabStateViewAdapter { private static final Logger LOG = LoggerFactory.getLogger(TabStateViewAdapter.class); @@ -21,11 +23,13 @@ public class TabStateViewAdapter { @Nullable public static TabViewState build(EditorViewState viewState) { TabViewState tvs = new TabViewState(); + tvs.setSubPath(viewState.getSubPath()); if (!saveJNode(tvs, viewState.getNode())) { - LOG.debug("Can't save view state: " + viewState); + if (UiUtils.JADX_GUI_DEBUG) { + LOG.warn("Can't save view state: {}", viewState); + } return null; } - tvs.setSubPath(viewState.getSubPath()); tvs.setCaret(viewState.getCaretPos()); tvs.setView(new ViewPoint(viewState.getViewPoint())); tvs.setActive(viewState.isActive()); @@ -41,6 +45,9 @@ public class TabStateViewAdapter { try { JNode node = loadJNode(mw, tvs); if (node == null) { + if (UiUtils.JADX_GUI_DEBUG) { + LOG.warn("Can't restore view for {}", tvs); + } return null; } EditorViewState viewState = new EditorViewState(node, tvs.getSubPath(), tvs.getCaret(), tvs.getView().toPoint()); @@ -67,8 +74,16 @@ public class TabStateViewAdapter { break; case "resource": - JResource tmpNode = new JResource(null, tvs.getTabPath(), JResource.JResType.FILE); - return mw.getTreeRoot().searchNode(tmpNode); // equals method in JResource check only name + return mw.getTreeRoot().searchResourceByName(tvs.getTabPath()); + + case "sub-resource": + String[] parts = tvs.getTabPath().split(JSubResource.SUB_RES_PREFIX); + JResource baseRes = mw.getTreeRoot().searchResourceByName(parts[0]); + if (baseRes != null) { + String subName = parts[1]; + return baseRes.searchDepthNode(n -> n.getName().equals(subName)); // will load node before search + } + return null; case "script": return mw.getTreeRoot() @@ -87,6 +102,12 @@ public class TabStateViewAdapter { tvs.setTabPath(((JClass) node).getCls().getRawName()); return true; } + if (node instanceof JSubResource) { + JSubResource subRes = (JSubResource) node; + tvs.setType("sub-resource"); + tvs.setTabPath(subRes.getBaseRes().getName() + JSubResource.SUB_RES_PREFIX + subRes.getName()); + return true; + } if (node instanceof JResource) { tvs.setType("resource"); tvs.setTabPath(node.getName()); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/data/TabViewState.java b/jadx-gui/src/main/java/jadx/gui/settings/data/TabViewState.java index 69f10b628..1a4d1c0b0 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/data/TabViewState.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/data/TabViewState.java @@ -91,4 +91,9 @@ public class TabViewState { public void setPreviewTab(boolean previewTab) { this.previewTab = previewTab; } + + @Override + public String toString() { + return "TabViewState{type=" + type + ", tabPath=" + tabPath + ", subPath=" + subPath + '}'; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JLoadableNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JLoadableNode.java index 2e652ad0a..54005c44e 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JLoadableNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JLoadableNode.java @@ -19,6 +19,12 @@ public abstract class JLoadableNode extends JNode { return super.searchNode(filter); } + @Override + public @Nullable JNode searchDepthNode(Predicate filter) { + loadNode(); + return super.searchDepthNode(filter); + } + @Override public @Nullable JNode removeNode(Predicate filter) { loadNode(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 16e2eaa85..f84a839d2 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -140,6 +140,17 @@ public abstract class JNode extends DefaultMutableTreeNode implements ITreeNode, return null; } + public @Nullable JNode searchDepthNode(Predicate filter) { + Enumeration en = this.breadthFirstEnumeration(); + while (en.hasMoreElements()) { + JNode node = (JNode) en.nextElement(); + if (filter.test(node)) { + return node; + } + } + return null; + } + /** * Remove and return first found node */ diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResSearchNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResSearchNode.java index 13a849457..482c61641 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResSearchNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResSearchNode.java @@ -34,16 +34,6 @@ public class JResSearchNode extends JNode { return resNode.getJParent(); } - @Override - public String makeLongStringHtml() { - return getName(); - } - - @Override - public Icon getIcon() { - return resNode.getIcon(); - } - @Override public String getName() { return resNode.getName(); @@ -54,6 +44,31 @@ public class JResSearchNode extends JNode { return resNode.makeString(); } + @Override + public String makeLongString() { + return resNode.makeLongString(); + } + + @Override + public String makeLongStringHtml() { + return resNode.makeLongStringHtml(); + } + + @Override + public String getTooltip() { + return resNode.getTooltip(); + } + + @Override + public boolean disableHtml() { + return resNode.disableHtml(); + } + + @Override + public Icon getIcon() { + return resNode.getIcon(); + } + @Override public boolean hasDescString() { return !StringUtils.isEmpty(text); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index 09732a60f..7893a7522 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -77,11 +77,14 @@ public class JResource extends JLoadableNode { private transient List subNodes = Collections.emptyList(); private transient ICodeInfo content = ICodeInfo.EMPTY; - public JResource(ResourceFile resFile, String name, JResType type) { + public JResource(@Nullable ResourceFile resFile, String name, JResType type) { this(resFile, name, name, type); } - public JResource(ResourceFile resFile, String name, String shortName, JResType type) { + public JResource(@Nullable ResourceFile resFile, String name, String shortName, JResType type) { + if (resFile == null && type == JResType.FILE) { + throw new IllegalArgumentException("Null resource file"); + } this.resFile = resFile; this.name = name; this.shortName = shortName; @@ -217,7 +220,7 @@ public class JResource extends JLoadableNode { } if (rc.getDataType() == ResContainer.DataType.RES_TABLE) { ICodeInfo codeInfo = loadCurrentSingleRes(rc); - List nodes = ResTableHelper.buildTree(rc); + List nodes = ResTableHelper.buildTree(this, rc); sortResNodes(nodes); subNodes = nodes; UiUtils.uiRun(this::update); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java index 40162b398..add6e2393 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java @@ -66,7 +66,7 @@ public class JRoot extends JNode { int count = parts.length; for (int i = 0; i < count - 1; i++) { String name = parts[i]; - JResource subRF = getResourceByName(curRf, name); + JResource subRF = getSubNodeByName(curRf, name); if (subRF == null) { subRF = new JResource(null, name, JResType.DIR); curRf.addSubNode(subRF); @@ -81,7 +81,7 @@ public class JRoot extends JNode { return root; } - private JResource getResourceByName(JResource rf, String name) { + public static JResource getSubNodeByName(JResource rf, String name) { for (JResource sub : rf.getSubNodes()) { if (sub.getName().equals(name)) { return sub; @@ -90,6 +90,20 @@ public class JRoot extends JNode { return null; } + public @Nullable JResource searchResourceByName(String name) { + Enumeration en = this.breadthFirstEnumeration(); + while (en.hasMoreElements()) { + Object obj = en.nextElement(); + if (obj instanceof JResource) { + JResource res = (JResource) obj; + if (res.getName().equals(name)) { + return res; + } + } + } + return null; + } + public @Nullable JNode searchNode(JNode node) { Enumeration en = this.breadthFirstEnumeration(); while (en.hasMoreElements()) { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JSubResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JSubResource.java new file mode 100644 index 000000000..34b7a8774 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JSubResource.java @@ -0,0 +1,50 @@ +package jadx.gui.treemodel; + +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.ResourceFile; + +/** + * Resource inside resource file. + * Add base file prefix to distinguish from other files. + */ +public class JSubResource extends JResource { + public static final String SUB_RES_PREFIX = ":/"; + + public JResource baseRes; + + public JSubResource(JResource baseRes, @Nullable ResourceFile resFile, String name, String shortName, JResType type) { + super(resFile, name, shortName, type); + this.baseRes = Objects.requireNonNull(baseRes); + } + + public JResource getBaseRes() { + return baseRes; + } + + @Override + public String makeLongString() { + return baseRes.makeLongString() + SUB_RES_PREFIX + super.makeLongString(); + } + + @Override + public final boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof JSubResource)) { + return false; + } + JSubResource other = (JSubResource) o; + return baseRes.equals(other.baseRes) + && getName().equals(other.getName()) + && getType().equals(other.getType()); + } + + @Override + public int hashCode() { + return baseRes.hashCode() + 31 * super.hashCode(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/res/ResTableHelper.java b/jadx-gui/src/main/java/jadx/gui/utils/res/ResTableHelper.java index ce66274b4..485378448 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/res/ResTableHelper.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/res/ResTableHelper.java @@ -12,6 +12,7 @@ import jadx.api.ResourceFileContent; import jadx.api.ResourceType; import jadx.core.xmlgen.ResContainer; import jadx.gui.treemodel.JResource; +import jadx.gui.treemodel.JSubResource; public class ResTableHelper { @@ -20,18 +21,18 @@ public class ResTableHelper { * * @return root nodes */ - public static List buildTree(ResContainer resTable) { - ResTableHelper resTableHelper = new ResTableHelper(resTable.getFileName()); + public static List buildTree(JResource resTableRes, ResContainer resTable) { + ResTableHelper resTableHelper = new ResTableHelper(resTableRes); resTableHelper.process(resTable); return resTableHelper.roots; } private final List roots = new ArrayList<>(); private final Map dirs = new HashMap<>(); - private final String resNamePrefix; + private final JResource resTableRes; - private ResTableHelper(String resTableFileName) { - this.resNamePrefix = resTableFileName + "/"; + private ResTableHelper(JResource resTableRes) { + this.resTableRes = resTableRes; } private void process(ResContainer resTable) { @@ -54,7 +55,7 @@ public class ResTableHelper { } ICodeInfo code = rc.getText(); ResourceFileContent fileContent = new ResourceFileContent(name, ResourceType.XML, code); - JResource resFile = new JResource(fileContent, resNamePrefix + resName, name, JResource.JResType.FILE); + JResource resFile = new JSubResource(resTableRes, fileContent, resName, name, JResource.JResType.FILE); addResFile(dir, resFile); for (ResContainer subFile : rc.getSubFiles()) { @@ -82,7 +83,7 @@ public class ResTableHelper { JResource curDir = dirs.get(path); if (curDir == null) { String dirName = last ? dir.substring(prevStart) : dir.substring(prevStart, splitPos); - curDir = new JResource(null, resNamePrefix + path, dirName, JResource.JResType.DIR); + curDir = new JSubResource(resTableRes, null, path, dirName, JResource.JResType.DIR); dirs.put(path, curDir); if (parentDir == null) { roots.add(curDir);