fix(gui): use new resource class for files in arsc (#2771)

This commit is contained in:
Skylot
2026-02-01 20:01:17 +00:00
parent 0d982e9709
commit c1fc73a524
9 changed files with 152 additions and 26 deletions
@@ -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());
@@ -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 + '}';
}
}
@@ -19,6 +19,12 @@ public abstract class JLoadableNode extends JNode {
return super.searchNode(filter);
}
@Override
public @Nullable JNode searchDepthNode(Predicate<JNode> filter) {
loadNode();
return super.searchDepthNode(filter);
}
@Override
public @Nullable JNode removeNode(Predicate<JNode> filter) {
loadNode();
@@ -140,6 +140,17 @@ public abstract class JNode extends DefaultMutableTreeNode implements ITreeNode,
return null;
}
public @Nullable JNode searchDepthNode(Predicate<JNode> 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
*/
@@ -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);
@@ -77,11 +77,14 @@ public class JResource extends JLoadableNode {
private transient List<JResource> 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<JResource> nodes = ResTableHelper.buildTree(rc);
List<JResource> nodes = ResTableHelper.buildTree(this, rc);
sortResNodes(nodes);
subNodes = nodes;
UiUtils.uiRun(this::update);
@@ -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()) {
@@ -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();
}
}
@@ -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<JResource> buildTree(ResContainer resTable) {
ResTableHelper resTableHelper = new ResTableHelper(resTable.getFileName());
public static List<JResource> buildTree(JResource resTableRes, ResContainer resTable) {
ResTableHelper resTableHelper = new ResTableHelper(resTableRes);
resTableHelper.process(resTable);
return resTableHelper.roots;
}
private final List<JResource> roots = new ArrayList<>();
private final Map<String, JResource> 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);