From 972f673ede678bef53ca145a2541484d869ea57a Mon Sep 17 00:00:00 2001 From: Rob Marsal Date: Fri, 3 Oct 2025 15:34:16 +0100 Subject: [PATCH 1/4] fix: do not display logs window when reusing an analysis --- .../binarysimilarity/ui/misc/AnalysisLogComponent.java | 6 ++++++ .../toolkit/ghidra/plugins/AnalysisManagementPlugin.java | 1 + 2 files changed, 7 insertions(+) diff --git a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/misc/AnalysisLogComponent.java b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/misc/AnalysisLogComponent.java index 56278ee..bc97cc8 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/misc/AnalysisLogComponent.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/misc/AnalysisLogComponent.java @@ -64,6 +64,12 @@ public TaskMonitor getTaskMonitor() { } public void processEvent(RevEngAIAnalysisStatusChangedEvent event) { + // We don't need to display the log window when the user selects an existing analysis because it will be an + // already completed analysis. + if (event.getSourceName().equals("Recent Analysis Dialog")) { + return; + } + this.setVisible(true); switch (event.getStatus()) { case Complete, Error -> {} diff --git a/src/main/java/ai/reveng/toolkit/ghidra/plugins/AnalysisManagementPlugin.java b/src/main/java/ai/reveng/toolkit/ghidra/plugins/AnalysisManagementPlugin.java index 42821a7..3ee2a72 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/plugins/AnalysisManagementPlugin.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/plugins/AnalysisManagementPlugin.java @@ -309,6 +309,7 @@ public void processEvent(PluginEvent event) { super.processEvent(event); // Forward the event to the analysis log component if (event instanceof RevEngAIAnalysisStatusChangedEvent analysisEvent) { + analysisLogComponent.processEvent(analysisEvent); if (analysisEvent.getStatus() == AnalysisStatus.Complete) { // If the analysis is complete, we refresh the function signatures from the server From 7a087f700c47793bd5e8cdab585a0088cf438958 Mon Sep 17 00:00:00 2001 From: Rob Marsal Date: Fri, 3 Oct 2025 16:09:56 +0100 Subject: [PATCH 2/4] feat: consistent dialog headers --- .../ui/about/AboutDialog.java | 46 +++++++++-------- .../ui/autounstrip/AutoUnstripDialog.java | 34 ++++++------- .../dialog/RevEngDialogComponentProvider.java | 51 +++++++++++++++++++ .../binarysimilarity/ui/help/HelpDialog.java | 10 ++-- .../java/docking/wizard/WizardManager.java | 3 ++ 5 files changed, 100 insertions(+), 44 deletions(-) create mode 100644 src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/dialog/RevEngDialogComponentProvider.java diff --git a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/about/AboutDialog.java b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/about/AboutDialog.java index 535e685..a6d3390 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/about/AboutDialog.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/about/AboutDialog.java @@ -1,7 +1,7 @@ package ai.reveng.toolkit.ghidra.binarysimilarity.ui.about; +import ai.reveng.toolkit.ghidra.binarysimilarity.ui.dialog.RevEngDialogComponentProvider; import ai.reveng.toolkit.ghidra.plugins.ReaiPluginPackage; -import docking.DialogComponentProvider; import ghidra.framework.plugintool.PluginTool; import resources.ResourceManager; @@ -9,19 +9,17 @@ import java.awt.*; import java.io.IOException; import java.nio.charset.StandardCharsets; +import ai.reveng.invoker.Configuration; /** * Shows a dialog with about information. */ -public class AboutDialog extends DialogComponentProvider { - private final PluginTool tool; - +public class AboutDialog extends RevEngDialogComponentProvider { public AboutDialog(PluginTool tool) { super(ReaiPluginPackage.WINDOW_PREFIX + "About", true); - this.tool = tool; buildInterface(getPluginVersion()); - setPreferredSize(300, 160); + setPreferredSize(300, 210); } private String getPluginVersion() { @@ -39,19 +37,15 @@ private String getPluginVersion() { } private void buildInterface(String pluginVersion) { - JPanel mainPanel = new JPanel(); - mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + JPanel mainPanel = new JPanel(new BorderLayout()); + + // Create title panel + JPanel titlePanel = createTitlePanel("Information about the plugin"); + mainPanel.add(titlePanel, BorderLayout.NORTH); // Create the about content JPanel contentPanel = createAboutContent(pluginVersion); - - // Make it scrollable - JScrollPane scrollPane = new JScrollPane(contentPanel); - scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); - scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - scrollPane.setBorder(null); - - mainPanel.add(scrollPane, BorderLayout.CENTER); + mainPanel.add(contentPanel, BorderLayout.CENTER); addWorkPanel(mainPanel); addDismissButton(); @@ -59,14 +53,22 @@ private void buildInterface(String pluginVersion) { private JPanel createAboutContent(String pluginVersion) { JPanel panel = new JPanel(); - panel.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5)); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + // Add padding at the top + panel.add(Box.createVerticalStrut(15)); + + // Plugin version label + JLabel pluginLabel = new JLabel("Plugin version: " + pluginVersion); + pluginLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + panel.add(pluginLabel); - JLabel label = new JLabel("RevEng.AI Ghidra Plugin: " + pluginVersion); - label.setAlignmentX(Component.LEFT_ALIGNMENT); + panel.add(Box.createVerticalStrut(5)); - // MenuBar section - panel.add(label); - panel.add(Box.createVerticalStrut(10)); + // SDK version label + JLabel sdkLabel = new JLabel("SDK version: " + Configuration.VERSION); + sdkLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + panel.add(sdkLabel); return panel; } diff --git a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/autounstrip/AutoUnstripDialog.java b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/autounstrip/AutoUnstripDialog.java index 2709e7a..7d6e0a2 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/autounstrip/AutoUnstripDialog.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/autounstrip/AutoUnstripDialog.java @@ -1,10 +1,12 @@ package ai.reveng.toolkit.ghidra.binarysimilarity.ui.autounstrip; +import ai.reveng.toolkit.ghidra.binarysimilarity.ui.dialog.RevEngDialogComponentProvider; import ai.reveng.toolkit.ghidra.core.services.api.GhidraRevengService; import ai.reveng.toolkit.ghidra.core.services.api.types.AnalysisID; import ai.reveng.toolkit.ghidra.core.services.api.types.AutoUnstripResponse; import ai.reveng.toolkit.ghidra.core.types.ProgramWithBinaryID; -import docking.DialogComponentProvider; +import ai.reveng.toolkit.ghidra.plugins.ReaiPluginPackage; +import docking.widgets.label.GDLabel; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; @@ -13,6 +15,7 @@ import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.InvalidInputException; import ghidra.util.task.TaskMonitorComponent; +import resources.ResourceManager; import javax.swing.*; import java.awt.*; @@ -20,7 +23,7 @@ import static ai.reveng.toolkit.ghidra.plugins.BinarySimilarityPlugin.REVENG_AI_NAMESPACE; -public class AutoUnstripDialog extends DialogComponentProvider { +public class AutoUnstripDialog extends RevEngDialogComponentProvider { private final AnalysisID analysisID; private final GhidraRevengService revengService; private final Program program; @@ -37,7 +40,7 @@ public class AutoUnstripDialog extends DialogComponentProvider { private static final int POLL_INTERVAL_MS = 2000; // Poll every 2 seconds public AutoUnstripDialog(PluginTool tool, ProgramWithBinaryID analysisID) { - super("Auto Unstrip", true); + super(ReaiPluginPackage.WINDOW_PREFIX + "Auto Unstrip", true); this.analysisID = analysisID.analysisID(); this.program = analysisID.program(); @@ -162,23 +165,16 @@ private void stopPolling() { private JComponent buildMainPanel() { JPanel panel = new JPanel(new BorderLayout()); - // Description at the top - JTextArea descriptionArea = new JTextArea(3, 60); - descriptionArea.setLineWrap(true); - descriptionArea.setWrapStyleWord(true); - descriptionArea.setEditable(false); - descriptionArea.setBackground(panel.getBackground()); - descriptionArea.setText( - """ - Automatically rename unknown functions in your analysis. - The names are sourced by matching functions in your analysis to functions within the RevEng.AI dataset. - """ - ); - panel.add(new JScrollPane(descriptionArea), BorderLayout.NORTH); + // Create title panel + JPanel titlePanel = createTitlePanel("Automatically rename unknown functions"); + panel.add(titlePanel, BorderLayout.NORTH); + + // Create content panel for description and progress + JPanel contentPanel = new JPanel(new BorderLayout()); // Progress panel in the center JPanel progressPanel = createProgressPanel(); - panel.add(progressPanel, BorderLayout.CENTER); + contentPanel.add(progressPanel, BorderLayout.CENTER); // Error area at the bottom (initially hidden) errorArea = new JTextArea(5, 60); @@ -188,7 +184,9 @@ private JComponent buildMainPanel() { errorArea.setBackground(Color.PINK); errorArea.setBorder(BorderFactory.createTitledBorder("Error Details")); errorArea.setVisible(false); - panel.add(new JScrollPane(errorArea), BorderLayout.SOUTH); + contentPanel.add(new JScrollPane(errorArea), BorderLayout.SOUTH); + + panel.add(contentPanel, BorderLayout.CENTER); return panel; } diff --git a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/dialog/RevEngDialogComponentProvider.java b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/dialog/RevEngDialogComponentProvider.java new file mode 100644 index 0000000..b3da6e7 --- /dev/null +++ b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/dialog/RevEngDialogComponentProvider.java @@ -0,0 +1,51 @@ +package ai.reveng.toolkit.ghidra.binarysimilarity.ui.dialog; + +import docking.DialogComponentProvider; +import docking.widgets.label.GDLabel; +import resources.ResourceManager; + +import javax.swing.*; +import java.awt.*; + +public class RevEngDialogComponentProvider extends DialogComponentProvider { + public RevEngDialogComponentProvider(String title, boolean isModal) { + super(title, isModal); + } + + protected JPanel createTitlePanel(String title) { + // Load icon from resources + Icon dialogIcon = null; + try { + dialogIcon = ResourceManager.loadImage("images/icon_50.png"); + } catch (Exception e) { + // If loading fails, fall back to no icon + } + + // Create title label + JLabel titleLabel = new GDLabel(title); + // Make the title text bold + Font currentFont = titleLabel.getFont(); + titleLabel.setFont(currentFont.deriveFont(Font.BOLD)); + + JPanel titlePanel = new JPanel(new BorderLayout()); + titlePanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), + BorderFactory.createEmptyBorder(5, 5, 5, 5))); + + // Add icon to the left if available + if (dialogIcon != null) { + JLabel iconLabel = new JLabel(dialogIcon); + titlePanel.add(iconLabel, BorderLayout.WEST); + } + + // Create a centered panel for the title text + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.X_AXIS)); + centerPanel.add(Box.createHorizontalGlue()); + centerPanel.add(titleLabel); + centerPanel.add(Box.createHorizontalGlue()); + + titlePanel.add(centerPanel, BorderLayout.CENTER); + + return titlePanel; + } +} \ No newline at end of file diff --git a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/help/HelpDialog.java b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/help/HelpDialog.java index f474292..8c6c21b 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/help/HelpDialog.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/help/HelpDialog.java @@ -1,5 +1,6 @@ package ai.reveng.toolkit.ghidra.binarysimilarity.ui.help; +import ai.reveng.toolkit.ghidra.binarysimilarity.ui.dialog.RevEngDialogComponentProvider; import ai.reveng.toolkit.ghidra.plugins.ReaiPluginPackage; import docking.DialogComponentProvider; import docking.widgets.label.GLabel; @@ -12,12 +13,9 @@ /** * Shows a dialog with help information. */ -public class HelpDialog extends DialogComponentProvider { - private final PluginTool tool; - +public class HelpDialog extends RevEngDialogComponentProvider { public HelpDialog(PluginTool tool) { super(ReaiPluginPackage.WINDOW_PREFIX + "Help", true); - this.tool = tool; buildInterface(); setPreferredSize(700, 600); @@ -27,6 +25,10 @@ private void buildInterface() { JPanel mainPanel = new JPanel(new BorderLayout()); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + // Create title panel + JPanel titlePanel = createTitlePanel("Help on using the plugin"); + mainPanel.add(titlePanel, BorderLayout.NORTH); + // Create the help content JPanel contentPanel = createHelpContent(); diff --git a/src/main/java/docking/wizard/WizardManager.java b/src/main/java/docking/wizard/WizardManager.java index 211f23a..e53edad 100644 --- a/src/main/java/docking/wizard/WizardManager.java +++ b/src/main/java/docking/wizard/WizardManager.java @@ -233,6 +233,9 @@ public void componentResized(ComponentEvent e) { // Create title label without icon - icon will be handled separately titleLabel = new GDLabel(INIT_TITLE); + // Make the title text bold + Font currentFont = titleLabel.getFont(); + titleLabel.setFont(currentFont.deriveFont(Font.BOLD)); JPanel titlePanel = new JPanel(new BorderLayout()); titlePanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), From 3d1c048ec9b9fe34c1884d6cd8e977541275f6ee Mon Sep 17 00:00:00 2001 From: Rob Marsal Date: Fri, 3 Oct 2025 17:02:13 +0100 Subject: [PATCH 3/4] feat: tidy up console logs --- .../ui/misc/AnalysisLogComponent.java | 3 ++- .../plugins/AnalysisManagementPlugin.java | 27 +++++++------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/misc/AnalysisLogComponent.java b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/misc/AnalysisLogComponent.java index bc97cc8..5242100 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/misc/AnalysisLogComponent.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/misc/AnalysisLogComponent.java @@ -66,7 +66,8 @@ public TaskMonitor getTaskMonitor() { public void processEvent(RevEngAIAnalysisStatusChangedEvent event) { // We don't need to display the log window when the user selects an existing analysis because it will be an // already completed analysis. - if (event.getSourceName().equals("Recent Analysis Dialog")) { + var sourceName = event.getSourceName(); + if (sourceName != null && sourceName.equals("Recent Analysis Dialog")) { return; } diff --git a/src/main/java/ai/reveng/toolkit/ghidra/plugins/AnalysisManagementPlugin.java b/src/main/java/ai/reveng/toolkit/ghidra/plugins/AnalysisManagementPlugin.java index 3ee2a72..4c84d93 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/plugins/AnalysisManagementPlugin.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/plugins/AnalysisManagementPlugin.java @@ -130,12 +130,12 @@ private void setupActions() { .enabledWhen(context -> { var currentProgram = tool.getService(ProgramManager.class).getCurrentProgram(); if (currentProgram == null) { - tool.getService(ReaiLoggingService.class).info("Create new action disabled: No current program"); + // Disable the action if no program is open return false; } boolean isKnown = revengService.isKnownProgram(currentProgram); boolean shouldEnable = !isKnown; - tool.getService(ReaiLoggingService.class).info("Create new action enabled: " + shouldEnable + " (program: " + currentProgram.getName() + ", isKnown: " + isKnown + ")"); + return shouldEnable; }) .onAction(context -> { @@ -178,13 +178,10 @@ private void setupActions() { .enabledWhen(c -> { var currentProgram = tool.getService(ProgramManager.class).getCurrentProgram(); if (currentProgram == null) { - tool.getService(ReaiLoggingService.class).info("Attach to existing action disabled: No current program"); return false; } boolean isKnown = revengService.isKnownProgram(currentProgram); - boolean shouldEnable = !isKnown; - tool.getService(ReaiLoggingService.class).info("Attach to existing action enabled: " + shouldEnable + " (program: " + currentProgram.getName() + ", isKnown: " + isKnown + ")"); - return shouldEnable; + return !isKnown; }) .onAction(context -> { var currentProgram = tool.getService(ProgramManager.class).getCurrentProgram(); @@ -203,12 +200,10 @@ private void setupActions() { .enabledWhen(c -> { var currentProgram = tool.getService(ProgramManager.class).getCurrentProgram(); if (currentProgram == null) { - tool.getService(ReaiLoggingService.class).info("Detach action disabled: No current program"); + // Disable the action if no program is open return false; } - boolean isKnown = revengService.isKnownProgram(currentProgram); - tool.getService(ReaiLoggingService.class).info("Detach action enabled: " + isKnown + " (program: " + currentProgram.getName() + ", isKnown: " + isKnown + ")"); - return isKnown; + return revengService.isKnownProgram(currentProgram); }) .onAction(context -> { var program = tool.getService(ProgramManager.class).getCurrentProgram(); @@ -240,12 +235,10 @@ private void setupActions() { .enabledWhen(context -> { var currentProgram = tool.getService(ProgramManager.class).getCurrentProgram(); if (currentProgram == null) { - tool.getService(ReaiLoggingService.class).info("Check status action disabled: No current program"); + // Disable the action if no program is open return false; } - boolean isKnown = revengService.isKnownProgram(currentProgram); - tool.getService(ReaiLoggingService.class).info("Check status action enabled: " + isKnown + " (program: " + currentProgram.getName() + ", isKnown: " + isKnown + ")"); - return isKnown; + return revengService.isKnownProgram(currentProgram); }) .onAction(context -> { var currentProgram = tool.getService(ProgramManager.class).getCurrentProgram(); @@ -266,12 +259,10 @@ private void setupActions() { .enabledWhen(context -> { var currentProgram = tool.getService(ProgramManager.class).getCurrentProgram(); if (currentProgram == null) { - tool.getService(ReaiLoggingService.class).info("View in portal action disabled: No current program"); + // Disable the action if no program is open return false; } - boolean isKnown = revengService.isKnownProgram(currentProgram); - tool.getService(ReaiLoggingService.class).info("View in portal action enabled: " + isKnown + " (program: " + currentProgram.getName() + ", isKnown: " + isKnown + ")"); - return isKnown; + return revengService.isKnownProgram(currentProgram); }) .onAction(context -> { var currentProgram = tool.getService(ProgramManager.class).getCurrentProgram(); From 2e8733d02e8be94e90912cab37e087a601c4f273 Mon Sep 17 00:00:00 2001 From: Rob Marsal Date: Fri, 3 Oct 2025 17:26:45 +0100 Subject: [PATCH 4/4] fix: tests --- src/test/java/ai/reveng/UnstripTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/ai/reveng/UnstripTest.java b/src/test/java/ai/reveng/UnstripTest.java index 2bf1717..828e533 100644 --- a/src/test/java/ai/reveng/UnstripTest.java +++ b/src/test/java/ai/reveng/UnstripTest.java @@ -69,7 +69,7 @@ public List getFunctionInfo(BinaryID binaryID) { performAction(action, false); // Wait for the dialog to appear and interact with it - var dialog = waitForDialogComponent("Auto Unstrip"); + var dialog = waitForDialogComponent("RevEng.AI: Auto Unstrip"); waitForSwing(); assertEquals("unstripped_function_name", func.getName()); @@ -151,7 +151,7 @@ public List getFunctionInfo(BinaryID binaryID) { performAction(action, false); // Wait for the dialog to appear and interact with it - var dialog = waitForDialogComponent("Auto Unstrip"); + var dialog = waitForDialogComponent("RevEng.AI: Auto Unstrip"); waitForSwing(); waitForCondition(() -> !responses.hasNext());