feat(gui): allow view and edit input smali files

This commit is contained in:
Skylot
2025-07-19 22:15:42 +01:00
parent 3d11d1fa87
commit 58c4f56a71
9 changed files with 154 additions and 6 deletions
@@ -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;
}
}
@@ -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 + '}';
}
}
@@ -17,7 +17,12 @@ public class JInputFiles extends JNode {
public JInputFiles(List<Path> 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));
}
}
}
@@ -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);
}
}
@@ -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<String, String> 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,
@@ -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;
}
@@ -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;
}
@@ -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);
}
}
@@ -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;
}