GP-707: GhidraDev updates

- New wizard to import a module source dir
- Ghidra won't launch if build dir is present
- Better validation when exporting extension
This commit is contained in:
Ryan Kurtz 2024-02-23 08:50:28 -05:00
parent fc0e3e1b6f
commit 91cb801521
20 changed files with 568 additions and 61 deletions

View file

@ -73,6 +73,15 @@ public class GhidraLaunchDelegate extends JavaLaunchDelegate {
return;
}
// Make sure there isn't a build/ directory present...it messes up the classpath.
// The build directory could exist if the user built an extension from the command line
// rather than from the Eclipse wizard
if (javaProject.getProject().getFolder("build").exists()) {
EclipseMessageUtils.showErrorDialog("Failed to launch project \"" + projectName +
"\".\nDelete top-level 'build' directory and try again.");
return;
}
// Set program arguments
String customProgramArgs =
configuration.getAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, "").trim();

View file

@ -44,6 +44,12 @@ public class GhidraProjectCreatorPreferences {
*/
static final String GHIDRA_LAST_PROJECT_ROOT_PATH = "ghidradev.ghidraLastProjectRootPath";
/**
* Path to the last used Ghidra module source directory.
*/
static final String GHIDRA_LAST_MODULE_SOURCE_DIR_PATH =
"ghidradev.ghidraLastModuleSourceDirPath";
/**
* The last used Gradle distribution.
*/
@ -123,6 +129,28 @@ public class GhidraProjectCreatorPreferences {
prefs.setValue(GHIDRA_LAST_PROJECT_ROOT_PATH, path);
}
/**
* Gets the last used Ghidra module source directory path that's defined in the preferences.
*
* @return The last used Ghidra module source directory path that's defined in the preferences.
* Could be the empty string.
*/
public static String getGhidraLastModuleSourceDirPath() {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
return prefs.getString(GHIDRA_LAST_MODULE_SOURCE_DIR_PATH);
}
/**
* Sets the last used Ghidra module source directory path that's defined in the preferences.
*
* @param path The last used Ghidra module source directory path that's defined in the
* preferences.
*/
public static void setGhidraLastModuleSourceDirPath(String path) {
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
prefs.setValue(GHIDRA_LAST_MODULE_SOURCE_DIR_PATH, path);
}
/**
* Gets the last used Ghidra Gradle distribution that's defined in the preferences.
*

View file

@ -32,6 +32,7 @@ import ghidra.GhidraLauncher;
/**
* Utility methods for working with Ghidra launchers in Eclipse.
*/
@SuppressWarnings("restriction")
public class GhidraLaunchUtils {
/**

View file

@ -94,6 +94,7 @@ public class GhidraModuleUtils {
sourceFolders.add(project.getFolder("src/main/java"));
sourceFolders.add(project.getFolder("src/main/help"));
sourceFolders.add(project.getFolder("src/main/resources"));
sourceFolders.add(project.getFolder("src/test/java"));
sourceFolders.add(project.getFolder("ghidra_scripts"));
for (IFolder sourceFolder : sourceFolders) {
GhidraProjectUtils.createFolder(sourceFolder, monitor);
@ -203,6 +204,76 @@ public class GhidraModuleUtils {
return null;
}
/**
* Imports a Ghidra module source directory to a new Ghidra module project with the given name.
*
* @param projectName The name of the project to create.
* @param moduleSourceDir The module source directory to import.
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param ghidraLayout The Ghidra layout to link the project to.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param monitor The progress monitor to use during project creation.
* @return The imported project.
* @throws IOException If there was a file-related problem with creating the project.
* @throws ParseException If there was a parse-related problem with creating the project.
* @throws CoreException If there was an Eclipse-related problem with creating the project.
*/
public static IJavaProject importGhidraModuleSource(String projectName, File moduleSourceDir,
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
String jythonInterpreterName, IProgressMonitor monitor)
throws IOException, ParseException, CoreException {
// Create empty Ghidra project
IJavaProject javaProject =
GhidraProjectUtils.createEmptyGhidraProject(projectName, moduleSourceDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
IProject project = javaProject.getProject();
// Find source directory paths
List<IPath> sourcePaths = new ArrayList<>();
IFolder srcFolder = project.getFolder("src");
List<IFolder> srcSubFolders = getSubFolders(srcFolder);
if (!srcSubFolders.isEmpty()) {
for (IFolder srcSubFolder : srcSubFolders) {
List<IFolder> subSubFolders = getSubFolders(srcSubFolder);
if (!subSubFolders.isEmpty()) {
sourcePaths.addAll(subSubFolders.stream().map(e -> e.getFullPath()).toList());
}
else {
sourcePaths.add(srcSubFolder.getFullPath());
}
}
}
else {
sourcePaths.add(srcFolder.getFullPath());
}
// Find jar file paths
List<IPath> jarPaths = new ArrayList<>();
IFolder libFolder = project.getFolder("lib");
if (libFolder.exists()) {
for (IResource resource : libFolder.members()) {
if (resource.getType() == IResource.FILE &&
resource.getFileExtension().equals("jar")) {
jarPaths.add(resource.getFullPath());
}
}
}
// Put the source and jar paths in the project's classpath
List<IClasspathEntry> cp = new ArrayList<>();
cp.addAll(sourcePaths.stream().map(e -> JavaCore.newSourceEntry(e)).toList());
cp.addAll(jarPaths.stream().map(e -> JavaCore.newLibraryEntry(e, null, null)).toList());
GhidraProjectUtils.addToClasspath(javaProject, cp, monitor);
// Update language ant properties file
GhidraModuleUtils.writeAntProperties(project, ghidraLayout);
return javaProject;
}
/**
* Writes project-specific ant properties, which get imported by the module project's language
* build.xml file to allow building against a Ghidra that lives in an external location. If the
@ -278,4 +349,24 @@ public class GhidraModuleUtils {
Change change = refactoring.createChange(monitor);
change.perform(monitor);
}
/**
* Gets a {@link List} of sub-folders
*
* @param folder The folder to get the sub-folders of
* @return A {@link List} of
* @throws CoreException If there was an Eclipse-related problem getting the sub-folders
*/
private static List<IFolder> getSubFolders(IFolder folder) throws CoreException {
List<IFolder> subFolders = new ArrayList<>();
if (folder.exists()) {
for (IResource resource : folder.members()) {
if (resource.getType() == IResource.FOLDER) {
subFolders.add(folder.getFolder(resource.getName()));
}
}
}
return subFolders;
}
}

View file

@ -16,6 +16,7 @@
package ghidradev.ghidraprojectcreator.utils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.*;
@ -43,6 +44,7 @@ import utility.module.ModuleUtilities;
/**
* Utility methods for working with Eclipse Ghidra projects.
*/
@SuppressWarnings("restriction")
public class GhidraProjectUtils {
/**
@ -290,6 +292,9 @@ public class GhidraProjectUtils {
IJavaProject javaProject = JavaCore.create(project);
project.open(monitor);
// Set the project's default encoding
project.setDefaultCharset(StandardCharsets.UTF_8.displayName(), monitor);
// Clear the project's classpath
javaProject.setRawClasspath(new IClasspathEntry[0], monitor);

View file

@ -61,7 +61,7 @@ public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizar
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
workbench = wb;
projectPage = new CreateGhidraProjectWizardPage();
projectPage = new CreateGhidraProjectWizardPage(true);
projectConfigPage = new ConfigureGhidraModuleProjectWizardPage();
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);

View file

@ -54,7 +54,7 @@ public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizar
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
projectPage = new CreateGhidraProjectWizardPage("GhidraScripts");
projectPage = new CreateGhidraProjectWizardPage("GhidraScripts", true);
projectConfigPage = new ConfigureGhidraScriptProjectWizardPage();
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);

View file

@ -41,6 +41,7 @@ import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidra.launch.JavaConfig;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.ChooseGhidraModuleProjectWizardPage;
import ghidradev.ghidraprojectcreator.wizards.pages.ConfigureGradleWizardPage;
@ -77,6 +78,10 @@ public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
@Override
public boolean performFinish() {
if (!validate()) {
return false;
}
IJavaProject javaProject = projectPage.getGhidraModuleProject();
GradleDistribution gradleDist = gradlePage.getGradleDistribution();
try {
@ -169,4 +174,26 @@ public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
monitor.done();
}
}
/**
* Validates the wizard pages. If they are invalid, an error popup will be displayed which
* will indicate the problem.
*
* @return True if the data returned from the wizard pages are valid; otherwise, false
*/
private boolean validate() {
String title = "Invalid Ghidra Module Extension";
IJavaProject javaProject = projectPage.getGhidraModuleProject();
if (!javaProject.getProject().getFile("extension.properties").exists()) {
EclipseMessageUtils.showErrorDialog(title,
"Cannot export extension because 'extension.properties' file does not exist.");
return false;
}
if (!javaProject.getProject().getFile("Module.manifest").exists()) {
EclipseMessageUtils.showErrorDialog(title,
"Cannot export extension because 'Module.manifest' file does not exist.");
return false;
}
return true;
}
}

View file

@ -0,0 +1,150 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards;
import static ghidradev.EclipseMessageUtils.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.IImportWizard;
import org.eclipse.ui.IWorkbench;
import ghidra.GhidraApplicationLayout;
import ghidradev.EclipseMessageUtils;
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
import ghidradev.ghidraprojectcreator.wizards.pages.*;
import utilities.util.FileUtilities;
/**
* Wizard for importing Ghidra module source to a new Ghidra module project.
*/
public class ImportGhidraModuleSourceWizard extends Wizard implements IImportWizard {
private ChooseGhidraModuleSourceWizardPage sourcePage;
private CreateGhidraProjectWizardPage projectPage;
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
private EnablePythonWizardPage pythonPage;
public ImportGhidraModuleSourceWizard() {
super();
}
@Override
public void init(IWorkbench wb, IStructuredSelection selection) {
sourcePage = new ChooseGhidraModuleSourceWizardPage();
projectPage = new CreateGhidraProjectWizardPage(false);
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
}
@Override
public void addPages() {
addPage(sourcePage);
addPage(projectPage);
addPage(ghidraInstallationPage);
addPage(pythonPage);
}
@Override
public boolean performFinish() {
if (!validate()) {
return false;
}
File moduleSourceDir = sourcePage.getSourceDir();
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
String projectName = projectPage.getProjectName();
boolean createRunConfig = projectPage.shouldCreateRunConfig();
String runConfigMemory = projectPage.getRunConfigMemory();
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
try {
getContainer().run(true, false,
monitor -> importModuleSource(ghidraInstallDir, projectName, moduleSourceDir,
createRunConfig, runConfigMemory, jythonInterpreterName, monitor));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
catch (InvocationTargetException e) {
error(showWizardErrorDialog(getShell(), e), e);
return false;
}
return true;
}
/**
* Imports a Ghidra module source directory to a new Ghidra module project.
*
* @param ghidraInstallDir The Ghidra installation directory to use.
* @param projectName The name of the project to create.
* @param moduleSourceDir The module source directory to import.
* @param createRunConfig Whether or not to create a new run configuration for the project.
* @param runConfigMemory The run configuration's desired memory. Could be null.
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
* Could be null if Python support is not wanted.
* @param monitor The monitor to use during project creation.
* @throws InvocationTargetException if an error occurred during project creation.
*/
private void importModuleSource(File ghidraInstallDir, String projectName, File moduleSourceDir,
boolean createRunConfig, String runConfigMemory, String jythonInterpreterName,
IProgressMonitor monitor) throws InvocationTargetException {
try {
info("Importing " + projectName + " at " + moduleSourceDir);
monitor.beginTask("Importing " + projectName, 2);
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
monitor.worked(1);
GhidraModuleUtils.importGhidraModuleSource(projectName, moduleSourceDir,
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
monitor.worked(1);
info("Finished importing " + projectName);
}
catch (IOException | ParseException | CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
/**
* Validates the wizard pages. If they are invalid, an error popup will be displayed which
* will indicate the problem.
*
* @return True if the data returned from the wizard pages are valid; otherwise, false
*/
private boolean validate() {
if (FileUtilities.isPathContainedWithin(ghidraInstallationPage.getGhidraInstallDir(),
sourcePage.getSourceDir())) {
EclipseMessageUtils.showErrorDialog("Invalid Module Source Directory",
"Module source directory cannot reside inside of the selected Ghidra installation directory.");
return false;
}
return true;
}
}

View file

@ -0,0 +1,124 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidradev.ghidraprojectcreator.wizards.pages;
import java.io.File;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
/**
* A wizard page that lets the user choose a Ghidra module source directory.
*/
public class ChooseGhidraModuleSourceWizardPage extends WizardPage {
private Text sourceDirText;
private Button sourceDirButton;
/**
* Creates a new Ghidra module source chooser wizard page.
*/
public ChooseGhidraModuleSourceWizardPage() {
super("ChooseGhidraModuleSourceWizardPage");
setTitle("Choose Ghidra Module Source");
setDescription("Choose a Ghidra module source directory.");
}
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
container.setLayout(new GridLayout(3, false));
// Source directory
Label sourceDirLabel = new Label(container, SWT.NULL);
String sourceDirToolTip = "The Ghidra module source directory.";
sourceDirLabel.setText("Source directory:");
sourceDirLabel.setToolTipText(sourceDirToolTip);
sourceDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
sourceDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
sourceDirText.setText(GhidraProjectCreatorPreferences.getGhidraLastModuleSourceDirPath());
sourceDirText.addModifyListener(evt -> validate());
sourceDirText.setToolTipText(sourceDirToolTip);
sourceDirButton = new Button(container, SWT.BUTTON1);
sourceDirButton.setText("...");
sourceDirButton.setToolTipText("Browse to select source directory");
sourceDirButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path != null) {
sourceDirText.setText(path);
}
});
validate();
setControl(container);
}
/**
* Gets the module source directory.
*
* @return The module source directory. Could be null if unspecified, however, the page will not
* be valid until the module source directory is valid, so it should never be null when called
* by other classes.
*/
public File getSourceDir() {
if (sourceDirText.getText().isEmpty()) {
return null;
}
return new File(sourceDirText.getText());
}
/**
* Validates the fields on the page and updates the page's status.
* Should be called every time a field on the page changes.
*/
private void validate() {
String message = null;
File sourceDir = new File(sourceDirText.getText());
if (!sourceDir.isAbsolute()) {
message = "Source directory must be an absolute path";
}
else if (!sourceDir.isDirectory()) {
message = "Source directory does not exist";
}
else if (!new File(sourceDir, "Module.manifest").exists()) {
message = "Source directory does not contain a Module.manifest file";
}
else if (!new File(sourceDir, "build.gradle").exists()) {
message = "Source directory does not contain a build.gradle file";
}
else if (new File(sourceDir, ".project").exists()) {
message = "Source directory already contains a .project file";
}
else if (new File(sourceDir, ".classpath").exists()) {
message = "Source directory already contains a .classpath file";
}
setErrorMessage(message);
setPageComplete(message == null);
if (message == null) {
GhidraProjectCreatorPreferences
.setGhidraLastModuleSourceDirPath(sourceDirText.getText());
}
}
}

View file

@ -39,6 +39,7 @@ import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
public class CreateGhidraProjectWizardPage extends WizardPage {
private String suggestedProjectName;
private boolean showProjectDir;
private Text projectNameText;
private Text projectRootDirText;
@ -50,19 +51,25 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
* Creates a Ghidra new project wizard page with the given suggested project name.
*
* @param suggestedProjectName The suggested project name.
* @param showProjectDir True to show a component for selecting the root project directory;
* otherwise, false
*/
public CreateGhidraProjectWizardPage(String suggestedProjectName) {
public CreateGhidraProjectWizardPage(String suggestedProjectName, boolean showProjectDir) {
super("CreateGhidraProjectWizardPage");
setTitle("Create Ghidra Project");
setDescription("Create a new Ghidra project.");
this.suggestedProjectName = suggestedProjectName;
this.showProjectDir = showProjectDir;
}
/**
* Creates a Ghidra new project wizard page.
*
* @param showProjectDir True to show a component for selecting the root project directory;
* otherwise, false
*/
public CreateGhidraProjectWizardPage() {
this("");
public CreateGhidraProjectWizardPage(boolean showProjectDir) {
this("", showProjectDir);
}
@Override
@ -81,25 +88,28 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
new Label(container, SWT.NONE).setText(""); // empty grid cell
// Project directory
Label projectDirLabel = new Label(container, SWT.NULL);
String projectDirToolTip = "The directory where this project will be created.";
projectDirLabel.setText("Project root directory:");
projectDirLabel.setToolTipText(projectDirToolTip);
projectRootDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
projectRootDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
projectRootDirText.setText(GhidraProjectCreatorPreferences.getGhidraLastProjectRootPath());
projectRootDirText.addModifyListener(evt -> validate());
projectRootDirText.setToolTipText(projectDirToolTip);
projectDirButton = new Button(container, SWT.BUTTON1);
projectDirButton.setText("...");
projectDirButton.setToolTipText("Browse to select project root directory");
projectDirButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path != null) {
projectRootDirText.setText(path);
}
});
if (showProjectDir) {
Label projectDirLabel = new Label(container, SWT.NULL);
String projectDirToolTip = "The directory where this project will be created.";
projectDirLabel.setText("Project root directory:");
projectDirLabel.setToolTipText(projectDirToolTip);
projectRootDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
projectRootDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
projectRootDirText
.setText(GhidraProjectCreatorPreferences.getGhidraLastProjectRootPath());
projectRootDirText.addModifyListener(evt -> validate());
projectRootDirText.setToolTipText(projectDirToolTip);
projectDirButton = new Button(container, SWT.BUTTON1);
projectDirButton.setText("...");
projectDirButton.setToolTipText("Browse to select project root directory");
projectDirButton.addListener(SWT.Selection, evt -> {
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
String path = dialog.open();
if (path != null) {
projectRootDirText.setText(path);
}
});
}
// Create run configuration checkbox
createRunConfigCheckboxButton = new Button(container, SWT.CHECK);
@ -153,14 +163,13 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
* Gets the project directory. This is the directory where the .project file should live.
*
* @return The project directory. This is the directory where the .project file should live.
* Could be null if unspecified, however, the page will not be valid until the project
* directory is valid, so it should never be null when called by other classes.
* Could be null if unspecified.
*/
public File getProjectDir() {
if (projectNameText.getText().isEmpty()) {
return null;
}
if (projectRootDirText.getText().isEmpty()) {
if (projectRootDirText == null || projectRootDirText.getText().isEmpty()) {
return null;
}
return new File(projectRootDirText.getText(), getProjectName());
@ -227,10 +236,10 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
else if (BAD.chars().anyMatch(ch -> projectName.indexOf(ch) != -1)) {
message = "Project name cannot contain invalid characters:\n " + BAD;
}
else if (projectDir == null) {
else if (showProjectDir && projectDir == null) {
message = "Project root directory must be specified";
}
else if (projectDir.exists()) {
else if (showProjectDir && projectDir.exists()) {
message = "Project already exists at: " + projectDir.getAbsolutePath();
}
else if (ResourcesPlugin.getWorkspace().getRoot().getProject(projectName).exists()) {
@ -250,8 +259,10 @@ public class CreateGhidraProjectWizardPage extends WizardPage {
setErrorMessage(message);
setPageComplete(message == null);
if (message == null) {
GhidraProjectCreatorPreferences.setGhidraLastProjectRootPath(
projectRootDirText.getText());
if (projectRootDirText != null) {
GhidraProjectCreatorPreferences
.setGhidraLastProjectRootPath(projectRootDirText.getText());
}
}
}
}

View file

@ -34,6 +34,7 @@ import org.eclipse.ui.ide.IDE;
import ghidradev.EclipseMessageUtils;
@SuppressWarnings("restriction")
public class OpenDeclarations {
private IProject project;