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 96f6a391a..645935bee 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java @@ -51,7 +51,7 @@ public class TabStateViewAdapter { viewState.setPreviewTab(tvs.isPreviewTab()); return viewState; } catch (Exception e) { - LOG.error("Failed to load tab state: " + tvs, e); + LOG.error("Failed to load tab state: {}", tvs, e); return null; } } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFile.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFile.java index 2dee7fd59..ade047c55 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFile.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFile.java @@ -1,6 +1,7 @@ package jadx.gui.treemodel; import java.nio.file.Path; +import java.util.Objects; import javax.swing.Icon; import javax.swing.JPopupMenu; @@ -15,11 +16,15 @@ public class JInputFile extends JNode { private final Path filePath; public JInputFile(Path filePath) { - this.filePath = filePath; + this.filePath = Objects.requireNonNull(filePath); } @Override public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + return buildInputFilePopupMenu(mainWindow, filePath); + } + + public static JPopupMenu buildInputFilePopupMenu(MainWindow mainWindow, Path filePath) { JPopupMenu menu = new JPopupMenu(); menu.add(new SimpleMenuItem(NLS.str("popup.add_files"), mainWindow::addFiles)); menu.add(new SimpleMenuItem(NLS.str("popup.remove"), () -> mainWindow.removeInput(filePath))); @@ -46,4 +51,22 @@ public class JInputFile extends JNode { public String getTooltip() { return filePath.normalize().toAbsolutePath().toString(); } + + @Override + public int hashCode() { + return filePath.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + return ((JInputFile) o).filePath.equals(filePath); + } + + @Override + public String toString() { + return "JInputFile{" + filePath + '}'; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFiles.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFiles.java index d19a98f7b..4764fbc66 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFiles.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputFiles.java @@ -17,7 +17,12 @@ public class JInputFiles extends JNode { public JInputFiles(List files) { for (Path file : files) { - add(new JInputFile(file)); + String fileName = file.getFileName().toString(); + if (fileName.endsWith(".smali")) { + add(new JInputSmaliFile(file)); + } else { + add(new JInputFile(file)); + } } } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputSmaliFile.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputSmaliFile.java new file mode 100644 index 000000000..138b19611 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputSmaliFile.java @@ -0,0 +1,99 @@ +package jadx.gui.treemodel; + +import java.nio.file.Path; +import java.util.Objects; + +import javax.swing.Icon; +import javax.swing.JPopupMenu; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.ICodeInfo; +import jadx.api.impl.SimpleCodeInfo; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.files.FileUtils; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.codearea.AbstractCodeArea; +import jadx.gui.ui.codearea.CodeContentPanel; +import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; +import jadx.gui.utils.Icons; + +public class JInputSmaliFile extends JEditableNode { + private static final Logger LOG = LoggerFactory.getLogger(JInputSmaliFile.class); + + private final Path filePath; + + public JInputSmaliFile(Path filePath) { + this.filePath = Objects.requireNonNull(filePath); + } + + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + return JInputFile.buildInputFilePopupMenu(mainWindow, filePath); + } + + @Override + public @Nullable ContentPanel getContentPanel(TabbedPane tabbedPane) { + return new CodeContentPanel(tabbedPane, this); + } + + @Override + public String getSyntaxName() { + return AbstractCodeArea.SYNTAX_STYLE_SMALI; + } + + @Override + public ICodeInfo getCodeInfo() { + try { + return new SimpleCodeInfo(FileUtils.readFile(filePath)); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to read file: " + filePath.toAbsolutePath(), e); + } + } + + @Override + public void save(String newContent) { + try { + FileUtils.writeFile(filePath, newContent); + LOG.debug("File saved: {}", filePath.toAbsolutePath()); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to write file: " + filePath.toAbsolutePath(), e); + } + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return Icons.FILE; + } + + @Override + public String makeString() { + return filePath.getFileName().toString(); + } + + @Override + public String getTooltip() { + return filePath.normalize().toAbsolutePath().toString(); + } + + @Override + public int hashCode() { + return filePath.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + return ((JInputSmaliFile) o).filePath.equals(filePath); + } +} 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 a071f6803..d269b700b 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -25,6 +25,7 @@ import jadx.core.utils.Utils; import jadx.core.xmlgen.ResContainer; import jadx.gui.jobs.SimpleTask; import jadx.gui.ui.MainWindow; +import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.codearea.BinaryContentPanel; import jadx.gui.ui.codearea.CodeContentPanel; import jadx.gui.ui.panel.ContentPanel; @@ -275,6 +276,7 @@ public class JResource extends JLoadableNode { private static final Map EXTENSION_TO_FILE_SYNTAX = jadx.core.utils.Utils.newConstStringMap( "java", SyntaxConstants.SYNTAX_STYLE_JAVA, + "smali", AbstractCodeArea.SYNTAX_STYLE_SMALI, "js", SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT, "ts", SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT, "json", SyntaxConstants.SYNTAX_STYLE_JSON, diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabBlueprint.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabBlueprint.java index da402c970..cae6cc3f4 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabBlueprint.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabBlueprint.java @@ -6,6 +6,7 @@ import jadx.gui.treemodel.JNode; public class TabBlueprint { private final JNode node; + private boolean created; private boolean pinned; private boolean bookmarked; private boolean hidden; @@ -19,6 +20,14 @@ public class TabBlueprint { return node; } + public boolean isCreated() { + return created; + } + + public void setCreated(boolean created) { + this.created = created; + } + public boolean isPinned() { return pinned; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java index 59a1d98e2..86fd20f8d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java @@ -342,9 +342,10 @@ public class TabComponent extends JPanel { } public TabBlueprint getBlueprint() { - TabBlueprint blueprint = tabsController.getTabByNode(contentPanel.getNode()); + JNode node = contentPanel.getNode(); + TabBlueprint blueprint = tabsController.getTabByNode(node); if (blueprint == null) { - throw new JadxRuntimeException("TabComponent does not have a corresponding TabBlueprint"); + throw new JadxRuntimeException("TabComponent does not have a corresponding TabBlueprint, node: " + node); } return blueprint; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java index 92aa0b430..06a888d97 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java @@ -407,10 +407,15 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener { if (blueprint.isHidden()) { return; } - ContentPanel newPanel = blueprint.getNode().getContentPanel(this); + JNode node = blueprint.getNode(); + ContentPanel newPanel = node.getContentPanel(this); if (newPanel != null) { + if (node != newPanel.getNode()) { + throw new JadxRuntimeException("Incorrect node found in content panel"); + } FocusManager.listen(newPanel); addContentPanel(newPanel); + blueprint.setCreated(true); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabsController.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabsController.java index 482d60c2c..ef4f93f25 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabsController.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabsController.java @@ -77,6 +77,10 @@ public class TabsController { blueprint = newBlueprint; } setTabHiddenInternal(blueprint, hidden); + if (!blueprint.isCreated()) { + LOG.warn("No content panel for node: {}", node); + closeTabForce(blueprint); + } return blueprint; }