From 91cb8015214f7e86e91a2c4c0307510ea322d076 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Fri, 23 Feb 2024 08:50:28 -0500 Subject: [PATCH] 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 --- .../GhidraDevPlugin/.launch/GhidraDev.launch | 54 +++---- .../GhidraDevPlugin/GhidraDev_README.html | 16 ++ .../GhidraDevPlugin/META-INF/MANIFEST.MF | 1 + .../GhidraDev/GhidraDevPlugin/plugin.xml | 33 ++++ .../launchers/GhidraLaunchDelegate.java | 9 ++ .../GhidraProjectCreatorPreferences.java | 28 ++++ .../utils/GhidraLaunchUtils.java | 1 + .../utils/GhidraModuleUtils.java | 91 +++++++++++ .../utils/GhidraProjectUtils.java | 5 + .../CreateGhidraModuleProjectWizard.java | 2 +- .../CreateGhidraScriptProjectWizard.java | 2 +- .../wizards/ExportGhidraModuleWizard.java | 27 ++++ .../ImportGhidraModuleSourceWizard.java | 150 ++++++++++++++++++ .../ChooseGhidraModuleSourceWizardPage.java | 124 +++++++++++++++ .../pages/CreateGhidraProjectWizardPage.java | 69 ++++---- .../ghidrasymbollookup/OpenDeclarations.java | 1 + GhidraBuild/Skeleton/build.gradle | 1 + GhidraBuild/Skeleton/certification.manifest | 1 + GhidraBuild/Skeleton/gitignore | 10 ++ .../main/java/skeleton/SkeletonAnalyzer.java | 4 +- 20 files changed, 568 insertions(+), 61 deletions(-) create mode 100644 GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ImportGhidraModuleSourceWizard.java create mode 100644 GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/ChooseGhidraModuleSourceWizardPage.java create mode 100644 GhidraBuild/Skeleton/gitignore diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/.launch/GhidraDev.launch b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/.launch/GhidraDev.launch index 7c584addef..5ab9dc9fdf 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/.launch/GhidraDev.launch +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/.launch/GhidraDev.launch @@ -163,19 +163,19 @@ - - + - - - - + + + + + @@ -184,10 +184,10 @@ - + @@ -204,9 +204,8 @@ + - - @@ -220,13 +219,10 @@ - + - - - @@ -250,9 +246,9 @@ - + @@ -289,15 +285,15 @@ + - - + @@ -309,14 +305,18 @@ + + + + - + + - @@ -324,30 +324,33 @@ - + - - + + + + - + + @@ -361,7 +364,6 @@ - @@ -421,16 +423,12 @@ - - - - - + diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/GhidraDev_README.html b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/GhidraDev_README.html index 4445354977..80f1d798a6 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/GhidraDev_README.html +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/GhidraDev_README.html @@ -36,6 +36,7 @@ change with future releases.

  • New Ghidra Script
  • New Ghidra Script Project
  • New Ghidra Module Project
  • +
  • Import Ghidra Module Source
  • Export Ghidra Module Extension
  • Preferences
  • Link Ghidra
  • @@ -59,6 +60,14 @@ change with future releases.

    GhidraDev has been upgraded to be compatible with Ghidra 11.1 and later. Older versions of GhidraDev will report an error when trying to link against Ghidra 11.1 or later. +
  • + GhidraDev now supports importing a Ghidra module source directory. This will work best + with Ghidra module projects created from Ghidra 11.1 or later. +
  • +
  • + GhidraDev will now fail to launch Ghidra if a top-level build directory is detected. + Presence of this intermediate build artifact can cause Ghidra to have runtime/debugging issues. +
  • 3.0.2:

      @@ -243,6 +252,13 @@ installed into Ghidra as an "extension".

      project can be initialized with optional template source files that provide a good starting point for implementing advanced Ghidra features such as Analyzers, Plugins, Loaders, etc. +
    +
  • Import
  • +
      +
    • + Ghidra Module Source: Opens a wizard that + imports a Ghidra module source directory as a new Ghidra module project. +
  • Export
    • diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/META-INF/MANIFEST.MF b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/META-INF/MANIFEST.MF index 2f3440cde9..b39940a0de 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/META-INF/MANIFEST.MF +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/META-INF/MANIFEST.MF @@ -9,6 +9,7 @@ Require-Bundle: org.eclipse.ant.core;bundle-version="3.6.200", org.eclipse.buildship.core;bundle-version="3.1.5", org.eclipse.core.expressions;bundle-version="3.8.100", org.eclipse.core.externaltools;bundle-version="1.2.100", + org.eclipse.core.resources;bundle-version="3.16.0", org.eclipse.core.runtime;bundle-version="3.24.0", org.eclipse.debug.ui;bundle-version="3.15.200", org.eclipse.jdt.core;bundle-version="3.28.0", diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/plugin.xml b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/plugin.xml index f4538462fa..ceacc9e878 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/plugin.xml +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/plugin.xml @@ -38,6 +38,23 @@ project="true"> + + + + + + Imports a Ghidra module source directory to a new project. + + + + + Exports a Ghidra module project to a zipped Ghidra extension. + + + + + + + sourcePaths = new ArrayList<>(); + IFolder srcFolder = project.getFolder("src"); + List srcSubFolders = getSubFolders(srcFolder); + if (!srcSubFolders.isEmpty()) { + for (IFolder srcSubFolder : srcSubFolders) { + List 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 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 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 getSubFolders(IFolder folder) throws CoreException { + List subFolders = new ArrayList<>(); + if (folder.exists()) { + for (IResource resource : folder.members()) { + if (resource.getType() == IResource.FOLDER) { + subFolders.add(folder.getFolder(resource.getName())); + } + } + } + + return subFolders; + } } diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraProjectUtils.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraProjectUtils.java index 5edcc8f094..f01a21fcee 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraProjectUtils.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/utils/GhidraProjectUtils.java @@ -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); diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraModuleProjectWizard.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraModuleProjectWizard.java index abbf1b23a8..d1df4dde44 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraModuleProjectWizard.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraModuleProjectWizard.java @@ -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); diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraScriptProjectWizard.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraScriptProjectWizard.java index a8b97e17c7..b78887d1b3 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraScriptProjectWizard.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/CreateGhidraScriptProjectWizard.java @@ -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); diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ExportGhidraModuleWizard.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ExportGhidraModuleWizard.java index 8db14d172d..cda82df764 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ExportGhidraModuleWizard.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ExportGhidraModuleWizard.java @@ -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; + } } diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ImportGhidraModuleSourceWizard.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ImportGhidraModuleSourceWizard.java new file mode 100644 index 0000000000..1655e08047 --- /dev/null +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/ImportGhidraModuleSourceWizard.java @@ -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; + } + +} diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/ChooseGhidraModuleSourceWizardPage.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/ChooseGhidraModuleSourceWizardPage.java new file mode 100644 index 0000000000..23784782f2 --- /dev/null +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/ChooseGhidraModuleSourceWizardPage.java @@ -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()); + } + } +} diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/CreateGhidraProjectWizardPage.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/CreateGhidraProjectWizardPage.java index 24b28e3b06..8a3ba2f1ae 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/CreateGhidraProjectWizardPage.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidraprojectcreator/wizards/pages/CreateGhidraProjectWizardPage.java @@ -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()); + } } } } diff --git a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidrasymbollookup/OpenDeclarations.java b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidrasymbollookup/OpenDeclarations.java index 101dc11642..cba3ce6a3c 100644 --- a/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidrasymbollookup/OpenDeclarations.java +++ b/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/src/main/java/ghidradev/ghidrasymbollookup/OpenDeclarations.java @@ -34,6 +34,7 @@ import org.eclipse.ui.ide.IDE; import ghidradev.EclipseMessageUtils; +@SuppressWarnings("restriction") public class OpenDeclarations { private IProject project; diff --git a/GhidraBuild/Skeleton/build.gradle b/GhidraBuild/Skeleton/build.gradle index 525dd78994..fffa507422 100644 --- a/GhidraBuild/Skeleton/build.gradle +++ b/GhidraBuild/Skeleton/build.gradle @@ -37,6 +37,7 @@ rootProject.assembleDistribution { exclude '.project' exclude 'build.gradle' rename "buildTemplate.gradle", "build.gradle" + rename "gitignore", ".gitignore" into "Extensions/Ghidra/Skeleton" } } diff --git a/GhidraBuild/Skeleton/certification.manifest b/GhidraBuild/Skeleton/certification.manifest index 26d3f90322..d6cc5eeda9 100644 --- a/GhidraBuild/Skeleton/certification.manifest +++ b/GhidraBuild/Skeleton/certification.manifest @@ -11,6 +11,7 @@ data/languages/skel.slaspec||GHIDRA||||END| data/sleighArgs.txt||GHIDRA||||END| extension.properties||GHIDRA||||END| ghidra_scripts/README.txt||GHIDRA||||END| +gitignore||GHIDRA||||END| lib/README.txt||GHIDRA||||END| os/linux_x86_64/README.txt||GHIDRA||||END| os/mac_x86_64/README.txt||GHIDRA||||END| diff --git a/GhidraBuild/Skeleton/gitignore b/GhidraBuild/Skeleton/gitignore new file mode 100644 index 0000000000..07bec394fa --- /dev/null +++ b/GhidraBuild/Skeleton/gitignore @@ -0,0 +1,10 @@ +.project +.pyproject +.classpath +.settings/ +.gradle/ +bin/ +build/ +dist/ +*.swp +.antProperties.xml diff --git a/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonAnalyzer.java b/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonAnalyzer.java index 01fa394002..bc97cdfbf5 100644 --- a/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonAnalyzer.java +++ b/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonAnalyzer.java @@ -41,7 +41,7 @@ public class SkeletonAnalyzer extends AbstractAnalyzer { // TODO: Return true if analyzer should be enabled by default - return false; + return true; } @Override @@ -50,7 +50,7 @@ public class SkeletonAnalyzer extends AbstractAnalyzer { // TODO: Examine 'program' to determine of this analyzer should analyze it. Return true // if it can. - return false; + return true; } @Override