mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 02:09:44 +02:00
Candidate release of source code.
This commit is contained in:
parent
db81e6b3b0
commit
79d8f164f8
12449 changed files with 2800756 additions and 16 deletions
|
@ -0,0 +1,130 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* NOTE: eclipse plugin code
|
||||
*
|
||||
* 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;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.ui.plugin.AbstractUIPlugin;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
/**
|
||||
* The activator class controls the plug-in life cycle.
|
||||
*
|
||||
* NOTE: Plugins declaring extensions or extension points must be implemented as a singleton.
|
||||
*/
|
||||
public class Activator extends AbstractUIPlugin {
|
||||
|
||||
private Set<Closeable> closeSet = new HashSet<>();
|
||||
|
||||
/**
|
||||
* The plug-in ID.
|
||||
*/
|
||||
public static final String PLUGIN_ID = "GhidraDev";
|
||||
|
||||
/**
|
||||
* A system property that Ghidra sets when launching Eclipse.
|
||||
*/
|
||||
private static final String GHIDRA_INSTALL_DIR_PROPERTY = "ghidra.install.dir";
|
||||
|
||||
/**
|
||||
* The shared instance.
|
||||
*/
|
||||
private static Activator plugin;
|
||||
|
||||
/**
|
||||
* Returns the shared instance.
|
||||
*
|
||||
* @return the shared instance. Could be null if the plugin is not started.
|
||||
*/
|
||||
public static Activator getDefault() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an object to be closed when the plugin shuts down.
|
||||
*
|
||||
* @param c The object to register for close on shutdown.
|
||||
*/
|
||||
public synchronized void registerCloseable(Closeable c) {
|
||||
closeSet.add(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters an object to be closed when the plugin shuts down.
|
||||
*
|
||||
* @param c The object to unregister for close on shutdown.
|
||||
*/
|
||||
public synchronized void unregisterCloseable(Closeable c) {
|
||||
closeSet.remove(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the installation directory of the Ghidra that launched Eclipse.
|
||||
*
|
||||
* @return The installation directory of the Ghidra that launched Eclipse. Could be null if
|
||||
* Ghidra did not launch Eclipse, or Ghidra did not pass along it's installation directory to
|
||||
* Eclipse properly.
|
||||
*/
|
||||
public File getGhidraInstallDir() {
|
||||
String ghidraInstallDirProperty = System.getProperty(GHIDRA_INSTALL_DIR_PROPERTY);
|
||||
if (ghidraInstallDirProperty != null && !ghidraInstallDirProperty.isEmpty()) {
|
||||
return new File(ghidraInstallDirProperty);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if Eclipse was launched by Ghidra.
|
||||
*
|
||||
* @return True if Eclipse was launched by Ghidra; otherwise, false.
|
||||
*/
|
||||
public boolean isLaunchedByGhidra() {
|
||||
return System.getProperty(GHIDRA_INSTALL_DIR_PROPERTY) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(BundleContext context) throws Exception {
|
||||
super.start(context);
|
||||
plugin = this;
|
||||
EclipseMessageUtils.info("Starting " + PLUGIN_ID + " plugin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
plugin = null;
|
||||
super.stop(context);
|
||||
EclipseMessageUtils.info("Stopping " + PLUGIN_ID + " plugin");
|
||||
|
||||
// Close registered items
|
||||
synchronized (this) {
|
||||
for (Closeable c : closeSet) {
|
||||
if (c != null) {
|
||||
EclipseMessageUtils.info("Closing " + c);
|
||||
try {
|
||||
c.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
EclipseMessageUtils.info("Failed to close " + c);
|
||||
}
|
||||
}
|
||||
}
|
||||
closeSet.clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
/* ###
|
||||
* 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;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.*;
|
||||
import org.eclipse.ui.ide.IDE;
|
||||
import org.eclipse.ui.progress.UIJob;
|
||||
import org.eclipse.ui.statushandlers.StatusManager;
|
||||
|
||||
public class EclipseMessageUtils {
|
||||
|
||||
private static IWorkbenchPage workbenchPage = getWorkbenchPage();
|
||||
private static Shell shell = workbenchPage.getWorkbenchWindow().getShell();
|
||||
|
||||
/**
|
||||
* Prevent instantiation.
|
||||
*/
|
||||
private EclipseMessageUtils() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an information dialog with a custom title and message.
|
||||
*
|
||||
* @param title The title of the information dialog.
|
||||
* @param message The message of the information dialog.
|
||||
*/
|
||||
public static void showInfoDialog(final String title, final String message) {
|
||||
Display.getDefault().syncExec(() -> {
|
||||
shell.forceActive();
|
||||
MessageDialog.openInformation(shell, title, message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a confirmation dialog with a custom title and message.
|
||||
*
|
||||
* @param title The title of the confirmation dialog.
|
||||
* @param message The message of the confirmation dialog.
|
||||
* @return True of ok was pressed; false if canceled was pressed.
|
||||
*/
|
||||
public static boolean showConfirmDialog(final String title, final String message) {
|
||||
AtomicBoolean okPressed = new AtomicBoolean();
|
||||
Display.getDefault().syncExec(() -> {
|
||||
shell.forceActive();
|
||||
okPressed.set(MessageDialog.openConfirm(shell, title, message));
|
||||
});
|
||||
return okPressed.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a question dialog with a custom title and message.
|
||||
*
|
||||
* @param title The title of the question dialog.
|
||||
* @param message The message of the question dialog.
|
||||
* @return True of yes was pressed; false if no was pressed.
|
||||
*/
|
||||
public static boolean showQuestionDialog(final String title, final String message) {
|
||||
AtomicBoolean yesPressed = new AtomicBoolean();
|
||||
Display.getDefault().syncExec(() -> {
|
||||
shell.forceActive();
|
||||
yesPressed.set(MessageDialog.openQuestion(shell, title, message));
|
||||
});
|
||||
return yesPressed.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an error dialog with a custom title and message.
|
||||
*
|
||||
* @param title The title of the error dialog.
|
||||
* @param message The message of the error dialog.
|
||||
*/
|
||||
public static void showErrorDialog(final String title, final String message) {
|
||||
Display.getDefault().syncExec(() -> {
|
||||
shell.forceActive();
|
||||
MessageDialog.openError(shell, title, message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an error dialog with a default title and custom message.
|
||||
*
|
||||
* @param message The message of the error dialog.
|
||||
*/
|
||||
public static void showErrorDialog(final String message) {
|
||||
showErrorDialog(Activator.PLUGIN_ID + " error", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a warning dialog with a custom title and custom message.
|
||||
*
|
||||
* @param title The title of the warning dialog.
|
||||
* @param message The message of the error dialog.
|
||||
*/
|
||||
public static void showWarnDialog(final String title, final String message) {
|
||||
Display.getDefault().syncExec(() -> {
|
||||
shell.forceActive();
|
||||
MessageDialog.openWarning(shell, title, message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an error dialog that a wizard can use to display information about an exception that
|
||||
* occurred while wizard'ing.
|
||||
*
|
||||
* @param wizardShell The wizard's shell.
|
||||
* @param e The exception that occurred.
|
||||
* @return The displayed message.
|
||||
*/
|
||||
public static String showWizardErrorDialog(Shell wizardShell, InvocationTargetException e) {
|
||||
String message = null;
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null) {
|
||||
message = cause.getClass().getSimpleName();
|
||||
if (cause.getMessage() != null && !cause.getMessage().isEmpty()) {
|
||||
message += ": " + cause.getMessage();
|
||||
}
|
||||
}
|
||||
else {
|
||||
message = e.getClass().getSimpleName();
|
||||
}
|
||||
MessageDialog.openError(wizardShell, "Error", message);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an info message.
|
||||
*
|
||||
* @param message The message to display.
|
||||
*/
|
||||
public static void info(String message) {
|
||||
StatusManager.getManager().handle(new Status(IStatus.INFO, Activator.PLUGIN_ID, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error message.
|
||||
*
|
||||
* @param message The message to display.
|
||||
*/
|
||||
public static void error(String message) {
|
||||
StatusManager.getManager().handle(new Status(IStatus.ERROR, Activator.PLUGIN_ID, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error message with the responsible error included.
|
||||
*
|
||||
* @param message The message to display.
|
||||
* @param t The responsible throwable.
|
||||
*/
|
||||
public static void error(String message, Throwable t) {
|
||||
StatusManager.getManager().handle(
|
||||
new Status(IStatus.ERROR, Activator.PLUGIN_ID, message, t));
|
||||
}
|
||||
|
||||
public static IWorkbenchPage getWorkbenchPage() {
|
||||
IWorkbench workbench = PlatformUI.getWorkbench();
|
||||
IWorkbenchWindow workbenchWindow = getWorkbenchWindow(workbench);
|
||||
if (workbenchWindow == null) {
|
||||
error("Couldn't get workbench window");
|
||||
return null;
|
||||
}
|
||||
IWorkbenchPage wbp = getPage(workbenchWindow);
|
||||
if (wbp == null) {
|
||||
error("Couldn't get workbench page");
|
||||
return null;
|
||||
}
|
||||
return wbp;
|
||||
}
|
||||
|
||||
private static IWorkbenchWindow getWorkbenchWindow(IWorkbench workbench) {
|
||||
IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow();
|
||||
if (workbenchWindow != null) {
|
||||
return workbenchWindow;
|
||||
}
|
||||
IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
|
||||
for (IWorkbenchWindow window : windows) {
|
||||
if (window != null) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IWorkbenchPage getPage(IWorkbenchWindow workbenchWindow) {
|
||||
IWorkbenchPage wbp = workbenchWindow.getActivePage();
|
||||
if (wbp != null) {
|
||||
return wbp;
|
||||
}
|
||||
IWorkbenchPage[] pages = workbenchWindow.getPages();
|
||||
for (IWorkbenchPage page : pages) {
|
||||
if (page != null) {
|
||||
return page;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the given file in an editor using the Java perspective.
|
||||
* If something goes wrong, this method has no effect.
|
||||
*
|
||||
* @param file The file to display.
|
||||
* @param workbench The workbench.
|
||||
*/
|
||||
public static void displayInEditor(IFile file, IWorkbench workbench) {
|
||||
new UIJob("Display in editor") {
|
||||
@Override
|
||||
public IStatus runInUIThread(IProgressMonitor m) {
|
||||
try {
|
||||
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
|
||||
IDE.openEditor(window.getActivePage(), file);
|
||||
workbench.showPerspective("org.eclipse.jdt.ui.JavaPerspective", window);
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
catch (NullPointerException | WorkbenchException e) {
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
}
|
||||
}.schedule();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* ###
|
||||
* 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;
|
||||
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.ui.*;
|
||||
import org.eclipse.ui.intro.IIntroManager;
|
||||
import org.eclipse.ui.intro.IIntroPart;
|
||||
|
||||
import ghidradev.ghidrascripteditor.ScriptEditorInitializer;
|
||||
import ghidradev.ghidrasymbollookup.SymbolLookupInitializer;
|
||||
|
||||
/**
|
||||
* When Eclipse starts, initializes the plugin's subcomponents.
|
||||
*/
|
||||
public class GhidraDevStartup implements IStartup {
|
||||
|
||||
@Override
|
||||
public void earlyStartup() {
|
||||
Job job = new Job(Activator.PLUGIN_ID + " startup") {
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
monitor.beginTask("Initializing " + Activator.PLUGIN_ID, 2);
|
||||
|
||||
// If we were launched from Ghidra, close the Eclipse welcome screen if present,
|
||||
// and make it so it never shows up again.
|
||||
if (Activator.getDefault().isLaunchedByGhidra()) {
|
||||
IIntroManager introManager = PlatformUI.getWorkbench().getIntroManager();
|
||||
IIntroPart intro = introManager.getIntro();
|
||||
if (intro != null) {
|
||||
Display.getDefault().syncExec(() -> introManager.closeIntro(intro));
|
||||
}
|
||||
PlatformUI.getPreferenceStore().setValue(
|
||||
IWorkbenchPreferenceConstants.SHOW_INTRO, false);
|
||||
}
|
||||
|
||||
// Ask the user (only once) for consent before listening on any ports
|
||||
boolean firstTimeConsent = false;
|
||||
if (!GhidraRootPreferences.requestedConsentToOpenPorts()) {
|
||||
firstTimeConsent = EclipseMessageUtils.showQuestionDialog(
|
||||
Activator.PLUGIN_ID + "User Consent",
|
||||
Activator.PLUGIN_ID + " opens ports to enable communication with Ghidra " +
|
||||
"for various features such as initiating script editing and symbol " +
|
||||
"lookup from Ghidra.\n\nDo you consent to the ports being opened?\n\n" +
|
||||
"If you do not consent now, you can enable these features at any " +
|
||||
"time in the " + Activator.PLUGIN_ID + " preferences.");
|
||||
GhidraRootPreferences.setOpenPortConsentRequest(true);
|
||||
}
|
||||
|
||||
// Initialize the script editor
|
||||
ScriptEditorInitializer.init(firstTimeConsent);
|
||||
monitor.worked(1);
|
||||
|
||||
// Initialize symbol lookup
|
||||
SymbolLookupInitializer.init(firstTimeConsent);
|
||||
monitor.worked(1);
|
||||
|
||||
monitor.done();
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
};
|
||||
job.schedule();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/* ###
|
||||
* 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;
|
||||
|
||||
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
|
||||
/**
|
||||
* Class used to initialize default preference values.
|
||||
*/
|
||||
public class GhidraRootPreferenceInitializer extends AbstractPreferenceInitializer {
|
||||
|
||||
@Override
|
||||
public void initializeDefaultPreferences() {
|
||||
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
|
||||
store.setDefault(GhidraRootPreferences.GHIDRA_REQUESTED_OPEN_PORT_CONSENT, false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/* ###
|
||||
* 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;
|
||||
|
||||
import org.eclipse.jface.preference.PreferencePage;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPreferencePage;
|
||||
|
||||
public class GhidraRootPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
|
||||
|
||||
public GhidraRootPreferencePage() {
|
||||
noDefaultAndApplyButton();
|
||||
}
|
||||
|
||||
public void init(IWorkbench workbench) {
|
||||
setDescription("Please see subcategories for Ghidra preferences.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Control createContents(Composite parent) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/* ###
|
||||
* 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;
|
||||
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
|
||||
/**
|
||||
* General preference definitions and related utility methods.
|
||||
*/
|
||||
public class GhidraRootPreferences {
|
||||
|
||||
/**
|
||||
* Whether or not we have requested consent to open network ports.
|
||||
*/
|
||||
static final String GHIDRA_REQUESTED_OPEN_PORT_CONSENT = "ghidradev.requestedOpenPortConsent";
|
||||
|
||||
/**
|
||||
* Gets whether or not consent was requested to open network ports.
|
||||
*
|
||||
* @return True if consent was requested to open network ports; otherwise, false.
|
||||
*/
|
||||
public static boolean requestedConsentToOpenPorts() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
return prefs.getBoolean(GHIDRA_REQUESTED_OPEN_PORT_CONSENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the user has given consent to open network ports.
|
||||
*
|
||||
* @param requested True if consent was requested to open network ports; otherwise, false.
|
||||
*/
|
||||
public static void setOpenPortConsentRequest(boolean requested) {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
prefs.setValue(GHIDRA_REQUESTED_OPEN_PORT_CONSENT, requested);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/* ###
|
||||
* 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.commands;
|
||||
|
||||
import org.eclipse.core.commands.*;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.osgi.framework.Version;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
|
||||
/**
|
||||
* Pops up a dialog with information about the plugin.
|
||||
*/
|
||||
public class AboutCommand extends AbstractHandler {
|
||||
|
||||
@Override
|
||||
public Object execute(ExecutionEvent event) throws ExecutionException {
|
||||
Version version = FrameworkUtil.getBundle(getClass()).getVersion();
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append("GhidraDev " + version.toString() + "\n\n");
|
||||
message.append("Ghidra Development support for Eclipse");
|
||||
EclipseMessageUtils.showInfoDialog("About", message.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/* ###
|
||||
* 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.commands;
|
||||
|
||||
import org.eclipse.core.commands.*;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.wizard.WizardDialog;
|
||||
import org.eclipse.ui.IWorkbenchWindow;
|
||||
import org.eclipse.ui.handlers.HandlerUtil;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.wizards.LinkGhidraWizard;
|
||||
|
||||
/**
|
||||
* Launches the {@link LinkGhidraWizard}.
|
||||
*/
|
||||
public class LinkGhidraCommand extends AbstractHandler {
|
||||
|
||||
@Override
|
||||
public Object execute(ExecutionEvent event) throws ExecutionException {
|
||||
IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
|
||||
ISelection selection = window.getSelectionService().getSelection();
|
||||
WizardDialog dialog =
|
||||
new WizardDialog(window.getShell(), new LinkGhidraWizard(selection));
|
||||
dialog.open();
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/* ###
|
||||
* 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.launchers;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.debug.core.*;
|
||||
import org.eclipse.debug.ui.ILaunchShortcut;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.ui.IEditorInput;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
|
||||
import ghidradev.Activator;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.testers.GhidraProjectPropertyTester;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
|
||||
/**
|
||||
* Ghidra launch shortcut actions. These shortcuts appear when you right click on a
|
||||
* Ghidra project or file and select "Run As" or "Debug As".
|
||||
* <p>
|
||||
* The {@link GhidraProjectPropertyTester} is used to determine whether or not the shortcuts appear.
|
||||
*/
|
||||
public abstract class AbstractGhidraLaunchShortcut implements ILaunchShortcut {
|
||||
|
||||
private String launchConfigTypeId;
|
||||
private String launchConfigNameSuffix;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra launch shortcut associated with the given launch configuration type ID.
|
||||
*
|
||||
* @param launchConfigTypeId The launch configuration type ID of this Ghidra launch shortcut.
|
||||
* @param launchConfigNameSuffix A string to append to the name of the launch configuration.
|
||||
*/
|
||||
protected AbstractGhidraLaunchShortcut(String launchConfigTypeId,
|
||||
String launchConfigNameSuffix) {
|
||||
this.launchConfigTypeId = launchConfigTypeId;
|
||||
this.launchConfigNameSuffix = launchConfigNameSuffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(ISelection selection, String mode) {
|
||||
IProject project = GhidraProjectUtils.getSelectedProject(selection);
|
||||
if (project != null) {
|
||||
launch(JavaCore.create(project), mode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(IEditorPart editor, String mode) {
|
||||
IEditorInput input = editor.getEditorInput();
|
||||
IResource resource = input.getAdapter(IResource.class);
|
||||
if (resource != null) {
|
||||
launch(JavaCore.create(resource.getProject()), mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the given Java project in the given mode with a Ghidra launcher.
|
||||
*
|
||||
* @param javaProject The Java project to launch.
|
||||
* @param mode The mode to launch in (run/debug).
|
||||
*/
|
||||
private void launch(IJavaProject javaProject, String mode) {
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
ILaunchConfigurationType launchType =
|
||||
launchManager.getLaunchConfigurationType(launchConfigTypeId);
|
||||
String launchConfigName = javaProject.getProject().getName() + launchConfigNameSuffix;
|
||||
try {
|
||||
ILaunchConfiguration lc = GhidraLaunchUtils.getLaunchConfig(launchConfigName);
|
||||
ILaunchConfigurationWorkingCopy wc = null;
|
||||
if (lc == null) {
|
||||
wc = GhidraLaunchUtils.createLaunchConfig(javaProject, launchConfigTypeId,
|
||||
launchConfigName, null);
|
||||
}
|
||||
else if (lc.getType().equals(launchType)) {
|
||||
wc = lc.getWorkingCopy();
|
||||
}
|
||||
else {
|
||||
throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
|
||||
IStatus.ERROR, "Failed to launch. Run configuration with name \"" +
|
||||
launchConfigName + "\" already exists.",
|
||||
null));
|
||||
}
|
||||
wc.doSave().launch(mode, null);
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.showErrorDialog(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/* ###
|
||||
* 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.launchers;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
|
||||
|
||||
/**
|
||||
* The Ghidra GUI launch shortcut actions.
|
||||
*
|
||||
* @see AbstractGhidraLaunchShortcut
|
||||
*/
|
||||
public class GhidraGuiLaunchShortcut extends AbstractGhidraLaunchShortcut {
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra GUI launch shortcut.
|
||||
*/
|
||||
public GhidraGuiLaunchShortcut() {
|
||||
super(GhidraLaunchUtils.GUI_LAUNCH, "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/* ###
|
||||
* 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.launchers;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
|
||||
|
||||
/**
|
||||
* The Ghidra headless launch shortcut actions.
|
||||
*
|
||||
* @see AbstractGhidraLaunchShortcut
|
||||
*/
|
||||
public class GhidraHeadlessLaunchShortcut extends AbstractGhidraLaunchShortcut {
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra headless launch shortcut.
|
||||
*/
|
||||
public GhidraHeadlessLaunchShortcut() {
|
||||
super(GhidraLaunchUtils.HEADLESS_LAUNCH, " (Headless)");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/* ###
|
||||
* 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.launchers;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
|
||||
import javax.naming.OperationNotSupportedException;
|
||||
|
||||
import org.eclipse.core.resources.*;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.debug.core.*;
|
||||
import org.eclipse.debug.ui.IDebugUIConstants;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
|
||||
import org.eclipse.jdt.launching.JavaLaunchDelegate;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.ui.IPerspectiveDescriptor;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.*;
|
||||
|
||||
/**
|
||||
* The Ghidra Launch delegate handles the final launch of Ghidra.
|
||||
* We can do any extra custom launch behavior here.
|
||||
*/
|
||||
public class GhidraLaunchDelegate extends JavaLaunchDelegate {
|
||||
|
||||
@Override
|
||||
public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch,
|
||||
IProgressMonitor monitor) throws CoreException {
|
||||
|
||||
boolean isHeadless =
|
||||
configuration.getType().getIdentifier().equals(GhidraLaunchUtils.HEADLESS_LAUNCH);
|
||||
ILaunchConfigurationWorkingCopy wc = configuration.getWorkingCopy();
|
||||
|
||||
// Get the launch properties associated with the version of Ghidra that is trying to launch
|
||||
String projectName =
|
||||
wc.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, "");
|
||||
IJavaProject javaProject = GhidraProjectUtils.getGhidraProject(projectName);
|
||||
if (javaProject == null) {
|
||||
EclipseMessageUtils.showErrorDialog("Failed to launch project \"" + projectName +
|
||||
"\".\nDoes not appear to be a Ghidra project.");
|
||||
return;
|
||||
}
|
||||
IFolder ghidraFolder =
|
||||
javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
|
||||
JavaConfig javaConfig;
|
||||
String ghidraInstallPath = ghidraFolder.getLocation().toOSString();
|
||||
try {
|
||||
javaConfig = new JavaConfig(new File(ghidraInstallPath));
|
||||
}
|
||||
catch (ParseException | IOException e) {
|
||||
EclipseMessageUtils.showErrorDialog(
|
||||
"Failed to launch project \"" + projectName + "\".\n" + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// Set program arguments
|
||||
String customProgramArgs =
|
||||
configuration.getAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, "").trim();
|
||||
String programArgs =
|
||||
isHeadless ? "ghidra.app.util.headless.AnalyzeHeadless" : "ghidra.GhidraRun";
|
||||
programArgs += " " + customProgramArgs;
|
||||
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, programArgs);
|
||||
if (isHeadless && customProgramArgs.isEmpty()) {
|
||||
EclipseMessageUtils.showInfoDialog("Ghidra Run Configuration",
|
||||
"Headless launch is being performed without any command line arguments!\n\n" +
|
||||
"Edit the \"" + configuration.getName() +
|
||||
"\" run configuration's program arguments to customize headless behavior. " +
|
||||
"See support/analyzeHeadlessREADME.html for more information.");
|
||||
}
|
||||
|
||||
// Set VM arguments
|
||||
String vmArgs = javaConfig.getLaunchProperties().getVmArgs();
|
||||
vmArgs += " " + configuration.getAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, "").trim();
|
||||
vmArgs += " " + "-Declipse.install.dir=\"" +
|
||||
Platform.getInstallLocation().getURL().getFile() + "\"";
|
||||
vmArgs += " " + "-Declipse.workspace.dir=\"" +
|
||||
ResourcesPlugin.getWorkspace().getRoot().getLocation() + "\"";
|
||||
vmArgs += " " + "-Declipse.project.dir=\"" + javaProject.getProject().getLocation() + "\"";
|
||||
vmArgs += " " + "-Declipse.project.dependencies=\"" +
|
||||
getProjectDependencyDirs(javaProject) + "\"";
|
||||
File pyDevSrcDir = PyDevUtils.getPyDevSrcDir();
|
||||
if (pyDevSrcDir != null) {
|
||||
vmArgs += " " + "-Declipse.pysrc.dir=\"" + pyDevSrcDir + "\"";
|
||||
}
|
||||
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, vmArgs);
|
||||
|
||||
// Handle special debug mode tasks
|
||||
if (mode.equals("debug")) {
|
||||
handleDebugMode();
|
||||
}
|
||||
|
||||
super.launch(wc.doSave(), mode, launch, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the given Java project, gets all of its classpath dependencies that are themselves
|
||||
* projects. The result is formatted as a string of paths separated by
|
||||
* {@link File#pathSeparator}.
|
||||
*
|
||||
* @param javaProject The Java project whose project dependencies we are getting.
|
||||
* @return A string of paths separated by {@link File#pathSeparator} that represents the given
|
||||
* Java project's dependencies that are projects. Could be empty if there are no
|
||||
* dependencies.
|
||||
* @throws CoreException if there was an Eclipse-related problem with getting the dependencies.
|
||||
*/
|
||||
private static String getProjectDependencyDirs(IJavaProject javaProject) throws CoreException {
|
||||
String paths = "";
|
||||
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
|
||||
if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
|
||||
if (!paths.isEmpty()) {
|
||||
paths += File.pathSeparator;
|
||||
}
|
||||
IResource resource =
|
||||
ResourcesPlugin.getWorkspace().getRoot().findMember(entry.getPath());
|
||||
if (resource != null) {
|
||||
paths += resource.getLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles extra things that should happen when we are launching in debug mode.
|
||||
*/
|
||||
private static void handleDebugMode() {
|
||||
Display.getDefault().asyncExec(() -> {
|
||||
|
||||
// Switch to debug perspective
|
||||
if (PlatformUI.getWorkbench() != null) {
|
||||
IPerspectiveDescriptor descriptor =
|
||||
PlatformUI.getWorkbench().getPerspectiveRegistry().findPerspectiveWithId(
|
||||
IDebugUIConstants.ID_DEBUG_PERSPECTIVE);
|
||||
EclipseMessageUtils.getWorkbenchPage().setPerspective(descriptor);
|
||||
}
|
||||
|
||||
// Start PyDev debugger
|
||||
if (PyDevUtils.isSupportedPyDevInstalled()) {
|
||||
try {
|
||||
PyDevUtils.startPyDevRemoteDebugger();
|
||||
}
|
||||
catch (OperationNotSupportedException e) {
|
||||
EclipseMessageUtils.error(
|
||||
"Failed to start the PyDev remote debugger. PyDev version is not supported.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/* ###
|
||||
* 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.launchers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
|
||||
import org.eclipse.debug.ui.*;
|
||||
import org.eclipse.jdt.debug.ui.launchConfigurations.JavaClasspathTab;
|
||||
import org.eclipse.jdt.debug.ui.launchConfigurations.JavaMainTab;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.*;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
|
||||
|
||||
/**
|
||||
* The Ghidra launcher tab group with default values needed for running/debugging
|
||||
* Ghidra. Some Java tabs are hidden here because we don't want the user changing their properties.
|
||||
* These properties are set in {@link GhidraLaunchDelegate}, which occurs right before
|
||||
* the launch.
|
||||
*/
|
||||
public class GhidraLaunchTabGroup extends AbstractLaunchConfigurationTabGroup {
|
||||
|
||||
@Override
|
||||
public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
|
||||
|
||||
// Create the tabs
|
||||
List<ILaunchConfigurationTab> tabs = new ArrayList<>();
|
||||
tabs.add(getJavaMainTab());
|
||||
tabs.add(getUserDefinedArgumentsTab());
|
||||
tabs.add(new JavaClasspathTab());
|
||||
tabs.add(getCommonTab());
|
||||
|
||||
// Set the tabs
|
||||
setTabs(tabs.toArray(new ILaunchConfigurationTab[tabs.size()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link JavaMainTab} to use, with Ghidra's main method pre-configured in.
|
||||
*
|
||||
* @return The {@link JavaMainTab} to use, with Ghidra's main method pre-configured in.
|
||||
*/
|
||||
private JavaMainTab getJavaMainTab() {
|
||||
return new JavaMainTab() {
|
||||
@Override
|
||||
public void initializeFrom(ILaunchConfiguration config) {
|
||||
try {
|
||||
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
|
||||
GhidraLaunchUtils.setMainTypeName(wc);
|
||||
super.initializeFrom(wc.doSave());
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Failed to initialize the java main tab.", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user-defined arguments to use. These will be appended to Ghidra's required
|
||||
* launch arguments, which are hidden from the tab group.
|
||||
*
|
||||
* @return The user-defined arguments to use.
|
||||
*/
|
||||
private AbstractLaunchConfigurationTab getUserDefinedArgumentsTab() {
|
||||
return new AbstractLaunchConfigurationTab() {
|
||||
|
||||
private Text programArgsText;
|
||||
private Text vmArgsText;
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
Composite container = new Composite(parent, SWT.NONE);
|
||||
container.setLayout(new GridLayout(1, true));
|
||||
GridData gd = new GridData(GridData.FILL_BOTH);
|
||||
container.setLayoutData(gd);
|
||||
|
||||
// Program arguments
|
||||
Group group = new Group(container, SWT.NONE);
|
||||
group.setLayout(new GridLayout());
|
||||
group.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
group.setText("Program arguments:");
|
||||
programArgsText = new Text(group, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL);
|
||||
gd = new GridData(GridData.FILL_BOTH);
|
||||
gd.heightHint = 40;
|
||||
gd.widthHint = 100;
|
||||
programArgsText.setLayoutData(gd);
|
||||
programArgsText.addModifyListener(evt -> scheduleUpdateJob());
|
||||
|
||||
// VM arguments
|
||||
group = new Group(container, SWT.NONE);
|
||||
group.setLayout(new GridLayout());
|
||||
group.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
group.setText("VM arguments (appended to arguments defined in launch.properties):");
|
||||
vmArgsText = new Text(group, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL);
|
||||
gd = new GridData(GridData.FILL_BOTH);
|
||||
gd.heightHint = 40;
|
||||
gd.widthHint = 100;
|
||||
vmArgsText.setLayoutData(gd);
|
||||
vmArgsText.addModifyListener(evt -> scheduleUpdateJob());
|
||||
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaults(ILaunchConfigurationWorkingCopy config) {
|
||||
try {
|
||||
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
|
||||
wc.setAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, "");
|
||||
wc.setAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, "");
|
||||
wc.doSave();
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Failed to set argument defaults.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeFrom(ILaunchConfiguration config) {
|
||||
try {
|
||||
programArgsText.setText(
|
||||
config.getAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS, ""));
|
||||
vmArgsText.setText(
|
||||
config.getAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, ""));
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Failed to initialize the arguments.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performApply(ILaunchConfigurationWorkingCopy config) {
|
||||
try {
|
||||
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
|
||||
wc.setAttribute(GhidraLaunchUtils.ATTR_PROGAM_ARGUMENTS,
|
||||
programArgsText.getText());
|
||||
wc.setAttribute(GhidraLaunchUtils.ATTR_VM_ARGUMENTS, vmArgsText.getText());
|
||||
wc.doSave();
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Failed to apply the arguments.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Arguments";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link CommonTab} to use, with the new launch configuration added to the favorites.
|
||||
*
|
||||
* @return The {@link CommonTab} to use, with the new launch configuration added to the
|
||||
* favorites.
|
||||
*/
|
||||
private CommonTab getCommonTab() {
|
||||
return new CommonTab() {
|
||||
@Override
|
||||
public void initializeFrom(ILaunchConfiguration config) {
|
||||
try {
|
||||
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
|
||||
GhidraLaunchUtils.setFavorites(wc);
|
||||
super.initializeFrom(wc.doSave());
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Failed to initialize the common tab.", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/* ###
|
||||
* 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.preferences;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
|
||||
import ghidradev.Activator;
|
||||
|
||||
/**
|
||||
* Class used to initialize default preference values.
|
||||
*/
|
||||
public class GhidraProjectCreatorPreferenceInitializer extends AbstractPreferenceInitializer {
|
||||
|
||||
@Override
|
||||
public void initializeDefaultPreferences() {
|
||||
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
|
||||
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_INSTALL_PATHS, "");
|
||||
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_DEFAULT_INSTALL_PATH, "");
|
||||
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_LAST_PROJECT_ROOT_PATH,
|
||||
ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString());
|
||||
store.setDefault(GhidraProjectCreatorPreferences.GHIDRA_LAST_GRADLE_DISTRIBUTION, "");
|
||||
|
||||
// If Ghidra launched Eclipse, automatically add in that Ghidra's location (if it doesn't
|
||||
// already exist) as a convenience to the user.
|
||||
File ghidraInstallDir = Activator.getDefault().getGhidraInstallDir();
|
||||
if (ghidraInstallDir != null) {
|
||||
Set<File> dirs = new HashSet<>(GhidraProjectCreatorPreferences.getGhidraInstallDirs());
|
||||
if (!dirs.contains(ghidraInstallDir)) {
|
||||
if (dirs.isEmpty()) {
|
||||
GhidraProjectCreatorPreferences.setDefaultGhidraInstallDir(ghidraInstallDir);
|
||||
}
|
||||
dirs.add(ghidraInstallDir);
|
||||
GhidraProjectCreatorPreferences.setGhidraInstallDirs(dirs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/* ###
|
||||
* 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.preferences;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.eclipse.jface.preference.PreferencePage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Font;
|
||||
import org.eclipse.swt.graphics.FontData;
|
||||
import org.eclipse.swt.layout.*;
|
||||
import org.eclipse.swt.widgets.*;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPreferencePage;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.framework.ApplicationProperties;
|
||||
import ghidra.framework.ApplicationVersion;
|
||||
import ghidradev.Activator;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import utility.application.ApplicationLayout;
|
||||
|
||||
/**
|
||||
* Page for Ghidra project creator preferences.
|
||||
*/
|
||||
public class GhidraProjectCreatorPreferencePage extends PreferencePage
|
||||
implements IWorkbenchPreferencePage {
|
||||
|
||||
private static ApplicationVersion MIN_GHIDRA_VERSION = new ApplicationVersion("9.0");
|
||||
|
||||
private Table table;
|
||||
private Button addButton;
|
||||
private Button removeButton;
|
||||
|
||||
public GhidraProjectCreatorPreferencePage() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(IWorkbench workbench) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IPreferenceStore doGetPreferenceStore() {
|
||||
return Activator.getDefault().getPreferenceStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Control createContents(Composite parent) {
|
||||
noDefaultButton();
|
||||
|
||||
FontData fontData = parent.getFont().getFontData()[0];
|
||||
Font bold = new Font(parent.getDisplay(), fontData.getName(), fontData.getHeight(), SWT.BOLD);
|
||||
|
||||
Composite container = new Composite(parent, SWT.None);
|
||||
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
container.setLayout(new GridLayout(2, false));
|
||||
|
||||
// Description label
|
||||
Label descriptionLabel = new Label(container, SWT.NULL);
|
||||
descriptionLabel.setText("Add or remove Ghidra installations.\n" +
|
||||
"The checked Ghidra installation is the default used when creating new projects.\n" +
|
||||
"Red entries correspond to invalid Ghidra installations.");
|
||||
new Label(container, SWT.NONE).setText(""); // filler
|
||||
|
||||
// Ghidra installations table
|
||||
table = new Table(container, SWT.CHECK | SWT.BORDER | SWT.FULL_SELECTION);
|
||||
table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
table.setHeaderVisible(true);
|
||||
table.setLinesVisible(true);
|
||||
table.addListener(SWT.Selection, evt -> {
|
||||
if (evt.detail == SWT.CHECK) {
|
||||
for (TableItem item : table.getItems()) {
|
||||
item.setChecked(item.equals(evt.item));
|
||||
item.setFont(item.equals(evt.item) ? bold : parent.getFont());
|
||||
}
|
||||
}
|
||||
});
|
||||
TableColumn col = new TableColumn(table, SWT.FILL);
|
||||
col.setText("Ghidra installation directories");
|
||||
col.setWidth(400);
|
||||
for (File dir : GhidraProjectCreatorPreferences.getGhidraInstallDirs()) {
|
||||
TableItem item = new TableItem(table, SWT.NONE);
|
||||
item.setText(dir.getAbsolutePath());
|
||||
try {
|
||||
validateGhidraInstallation(dir);
|
||||
item.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_BLACK));
|
||||
}
|
||||
catch (IOException e) {
|
||||
item.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));
|
||||
}
|
||||
if (dir.equals(GhidraProjectCreatorPreferences.getGhidraDefaultInstallDir())) {
|
||||
item.setFont(bold);
|
||||
item.setChecked(true);
|
||||
}
|
||||
}
|
||||
if (table.getItemCount() > 0) {
|
||||
col.pack();
|
||||
}
|
||||
|
||||
// Buttons
|
||||
Composite buttons = new Composite(container, SWT.None);
|
||||
buttons.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
|
||||
buttons.setLayout(new FillLayout(SWT.VERTICAL));
|
||||
|
||||
// Add button
|
||||
addButton = new Button(buttons, SWT.PUSH);
|
||||
addButton.setText("Add...");
|
||||
addButton.addListener(SWT.Selection, evt -> {
|
||||
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
|
||||
String path = dialog.open();
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
if (Arrays.stream(table.getItems()).anyMatch(item -> item.getText().equals(path))) {
|
||||
EclipseMessageUtils.showErrorDialog("Ghidra installation already specified.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
validateGhidraInstallation(new File(path));
|
||||
TableItem item = new TableItem(table, SWT.NONE);
|
||||
item.setText(path);
|
||||
item.setChecked(table.getItemCount() == 1);
|
||||
item.setFont(item.getChecked() ? bold : parent.getFont());
|
||||
}
|
||||
catch (IOException e) {
|
||||
EclipseMessageUtils.showErrorDialog(e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
// Remove button
|
||||
removeButton = new Button(buttons, SWT.PUSH);
|
||||
removeButton.setText("Remove");
|
||||
removeButton.addListener(SWT.Selection, evt -> {
|
||||
int selectionIndex = table.getSelectionIndex();
|
||||
if (selectionIndex == -1) {
|
||||
return;
|
||||
}
|
||||
boolean wasDefault = table.getItem(selectionIndex).getChecked();
|
||||
table.remove(selectionIndex);
|
||||
if (table.getItemCount() > 0) {
|
||||
if (selectionIndex < table.getItemCount()) {
|
||||
table.select(selectionIndex);
|
||||
}
|
||||
else {
|
||||
table.select(selectionIndex - 1);
|
||||
}
|
||||
if (wasDefault) {
|
||||
TableItem item = table.getItem(table.getSelectionIndex());
|
||||
item.setChecked(true);
|
||||
item.setFont(bold);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performOk() {
|
||||
super.performOk();
|
||||
|
||||
//@formatter:off
|
||||
GhidraProjectCreatorPreferences.setGhidraInstallDirs(
|
||||
Arrays.stream(table.getItems())
|
||||
.map(item -> new File(item.getText()))
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
|
||||
GhidraProjectCreatorPreferences.setDefaultGhidraInstallDir(
|
||||
Arrays.stream(table.getItems())
|
||||
.filter(TableItem::getChecked)
|
||||
.findFirst()
|
||||
.map(item -> new File(item.getText()))
|
||||
.orElse(null)
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given Ghidra installation directory.
|
||||
*
|
||||
* @param ghidraInstallDir The Ghidra installation directory to validate.
|
||||
* @throws IOException If the given Ghidra installation directory is not valid. The exception's
|
||||
* message has more detailed information on why it was not valid.
|
||||
*/
|
||||
public static void validateGhidraInstallation(File ghidraInstallDir) throws IOException {
|
||||
ApplicationLayout layout;
|
||||
try {
|
||||
layout = new GhidraApplicationLayout(ghidraInstallDir);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IOException("Not a valid Ghidra installation.");
|
||||
}
|
||||
ApplicationProperties applicationProperties = layout.getApplicationProperties();
|
||||
ApplicationVersion version;
|
||||
try {
|
||||
version = new ApplicationVersion(applicationProperties.getApplicationVersion());
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IOException("Error parsing application version. " + e.getMessage() + ".");
|
||||
}
|
||||
if (version.compareTo(MIN_GHIDRA_VERSION) < 0) {
|
||||
throw new IOException(
|
||||
"Ghidra installation must be version " + MIN_GHIDRA_VERSION + " or later.");
|
||||
}
|
||||
String layoutVersion = applicationProperties.getProperty(
|
||||
ApplicationProperties.APPLICATION_LAYOUT_VERSION_PROPERTY);
|
||||
if (layoutVersion == null || !layoutVersion.equals("1")) {
|
||||
// We can be smarter about this check and what we support later, once the layout version
|
||||
// actually changes.
|
||||
throw new IOException(
|
||||
"Ghidra application layout is not supported. Please upgrade " +
|
||||
Activator.PLUGIN_ID + " to use this version of Ghidra.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/* ###
|
||||
* 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.preferences;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.buildship.core.GradleDistribution;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
|
||||
import ghidradev.Activator;
|
||||
|
||||
/**
|
||||
* Ghidra project creator preference definitions and related utility methods.
|
||||
*/
|
||||
public class GhidraProjectCreatorPreferences {
|
||||
|
||||
/**
|
||||
* Paths to the Ghidra installation directories.
|
||||
*/
|
||||
static final String GHIDRA_INSTALL_PATHS = "ghidradev.ghidraInstallPaths";
|
||||
|
||||
/**
|
||||
* Path to the default Ghidra installation directory.
|
||||
*/
|
||||
static final String GHIDRA_DEFAULT_INSTALL_PATH = "ghidradev.ghidraDefaultInstallPath";
|
||||
|
||||
/**
|
||||
* Path to the last used Ghidra project root directory.
|
||||
*/
|
||||
static final String GHIDRA_LAST_PROJECT_ROOT_PATH = "ghidradev.ghidraLastProjectRootPath";
|
||||
|
||||
/**
|
||||
* The last used Gradle distribution.
|
||||
*/
|
||||
static final String GHIDRA_LAST_GRADLE_DISTRIBUTION = "ghidradev.ghidraLastGradleDistribution";
|
||||
|
||||
/**
|
||||
* Gets the set of Ghidra installation directories that's defined in the preferences.
|
||||
*
|
||||
* @return The set of Ghidra installation directories that's defined in the preferences.
|
||||
*/
|
||||
public static Set<File> getGhidraInstallDirs() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
String ghidraInstallDirPaths = prefs.getString(GHIDRA_INSTALL_PATHS);
|
||||
if (ghidraInstallDirPaths.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return Arrays.stream(ghidraInstallDirPaths.split(File.pathSeparator)).map(
|
||||
p -> new File(p)).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the set of Ghidra installation directories that's defined in the preferences.
|
||||
*
|
||||
* @param dirs The set of Ghidra installation directories that's defined in the preferences.
|
||||
*/
|
||||
public static void setGhidraInstallDirs(Set<File> dirs) {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
String paths = dirs.stream().map(dir -> dir.getAbsolutePath()).collect(
|
||||
Collectors.joining(File.pathSeparator));
|
||||
prefs.setValue(GHIDRA_INSTALL_PATHS, paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default Ghidra installation directory that's defined in the preferences.
|
||||
*
|
||||
* @return The default Ghidra installation directory that's defined in the preferences.
|
||||
* Could be null if a default is not defined.
|
||||
*/
|
||||
public static File getGhidraDefaultInstallDir() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
String ghidraDefaultInstallDirPath = prefs.getString(GHIDRA_DEFAULT_INSTALL_PATH);
|
||||
if (ghidraDefaultInstallDirPath.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new File(ghidraDefaultInstallDirPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default Ghidra installation directory that's defined in the preferences.
|
||||
*
|
||||
* @param dir The default Ghidra installation directory that's defined in the preferences.
|
||||
* Could be null if there is no default.
|
||||
*/
|
||||
public static void setDefaultGhidraInstallDir(File dir) {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
prefs.setValue(GHIDRA_DEFAULT_INSTALL_PATH, dir != null ? dir.getAbsolutePath() : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last used Ghidra project root path that's defined in the preferences.
|
||||
*
|
||||
* @return The last used Ghidra project root path that's defined in the preferences.
|
||||
* Could be the empty string.
|
||||
*/
|
||||
public static String getGhidraLastProjectRootPath() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
return prefs.getString(GHIDRA_LAST_PROJECT_ROOT_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last used Ghidra project root path that's defined in the preferences.
|
||||
*
|
||||
* @param path The last used Ghidra project root path that's defined in the preferences.
|
||||
*/
|
||||
public static void setGhidraLastProjectRootPath(String path) {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
prefs.setValue(GHIDRA_LAST_PROJECT_ROOT_PATH, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last used Ghidra Gradle distribution that's defined in the preferences.
|
||||
*
|
||||
* @return The last used Ghidra Gradle distribution that's defined in the preferences.
|
||||
* Could be null if there is no last used distribution.
|
||||
*/
|
||||
public static GradleDistribution getGhidraLastGradleDistribution() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
String pref = prefs.getString(GHIDRA_LAST_GRADLE_DISTRIBUTION);
|
||||
if (pref != null && !pref.isEmpty()) {
|
||||
try {
|
||||
return GradleDistribution.fromString(pref);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Failed to parse the string for some reason. Fall through to null.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last used Ghidra Gradle distribution that's defined in the preferences.
|
||||
*
|
||||
* @param gradleDistribution The last used Ghidra Gradle distribution that's defined in the
|
||||
* preferences. Could be null if the preference should be set to the default.
|
||||
*/
|
||||
public static void setGhidraLastGradleDistribution(GradleDistribution gradleDistribution) {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
if (gradleDistribution != null) {
|
||||
prefs.setValue(GHIDRA_LAST_GRADLE_DISTRIBUTION, gradleDistribution.toString());
|
||||
}
|
||||
else {
|
||||
prefs.setToDefault(GHIDRA_LAST_GRADLE_DISTRIBUTION);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/* ###
|
||||
* 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.testers;
|
||||
|
||||
import org.eclipse.core.expressions.PropertyTester;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
|
||||
/**
|
||||
* A {@link PropertyTester} used to determine if a given Eclipse resource is part
|
||||
* of a Ghidra module project.
|
||||
*/
|
||||
public class GhidraModuleProjectPropertyTester extends PropertyTester {
|
||||
|
||||
@Override
|
||||
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
|
||||
return GhidraProjectUtils.isGhidraModuleProject(
|
||||
GhidraProjectUtils.getEnclosingProject(receiver));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* ###
|
||||
* 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.testers;
|
||||
|
||||
import org.eclipse.core.expressions.PropertyTester;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
|
||||
/**
|
||||
* A {@link PropertyTester} used to determine if a given Eclipse resource is part
|
||||
* of a Ghidra project.
|
||||
*/
|
||||
public class GhidraProjectPropertyTester extends PropertyTester {
|
||||
|
||||
@Override
|
||||
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
|
||||
return GhidraProjectUtils.isGhidraProject(GhidraProjectUtils.getEnclosingProject(receiver));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* ###
|
||||
* 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.testers;
|
||||
|
||||
import org.eclipse.core.expressions.PropertyTester;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
|
||||
/**
|
||||
* A {@link PropertyTester} used to determine if a given Eclipse resource is part
|
||||
* of a Java project.
|
||||
*/
|
||||
public class JavaProjectPropertyTester extends PropertyTester {
|
||||
|
||||
@Override
|
||||
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
|
||||
return GhidraProjectUtils.isJavaProject(GhidraProjectUtils.getEnclosingProject(receiver));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/* ###
|
||||
* 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.testers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.expressions.PropertyTester;
|
||||
import org.eclipse.jdt.core.IPackageFragmentRoot;
|
||||
|
||||
/**
|
||||
* A {@link PropertyTester} used to determine if a given Eclipse resource is a Java package
|
||||
* fragment root (which is basically a Java source folder on the build path).
|
||||
*/
|
||||
public class PackageFragmentRootPropertyTester extends PropertyTester {
|
||||
|
||||
@Override
|
||||
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
|
||||
|
||||
if (receiver instanceof List) {
|
||||
List<?> list = (List<?>) receiver;
|
||||
if (list.size() == 1) {
|
||||
receiver = list.iterator().next();
|
||||
}
|
||||
}
|
||||
|
||||
return receiver instanceof IPackageFragmentRoot;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
/* ###
|
||||
* 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.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.debug.core.*;
|
||||
import org.eclipse.debug.internal.ui.DebugUIPlugin;
|
||||
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationManager;
|
||||
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchHistory;
|
||||
import org.eclipse.debug.ui.IDebugUIConstants;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
|
||||
|
||||
import ghidra.GhidraLauncher;
|
||||
|
||||
/**
|
||||
* Utility methods for working with Ghidra launchers in Eclipse.
|
||||
*/
|
||||
public class GhidraLaunchUtils {
|
||||
|
||||
/**
|
||||
* Launch configuration ID for a Ghidra GUI launch. Must match corresponding value in
|
||||
* plugin.xml.
|
||||
*/
|
||||
public static final String GUI_LAUNCH = "GhidraGuiLaunchConfigurationType";
|
||||
|
||||
/**
|
||||
* Launch configuration ID for a Ghidra Headless launch. Must match corresponding value in
|
||||
* plugin.xml.
|
||||
*/
|
||||
public static final String HEADLESS_LAUNCH = "GhidraHeadlessLaunchConfigurationType";
|
||||
|
||||
/**
|
||||
* Program arguments that will get passed to the launched Ghidra. These will be appended
|
||||
* to the required program arguments that are required to launch Ghidra, which are hidden
|
||||
* from the user.
|
||||
*/
|
||||
public static final String ATTR_PROGAM_ARGUMENTS = "ghidradev.ghidraProgramArguments";
|
||||
|
||||
/**
|
||||
* VM arguments that will get passed to the launched Ghidra. These will be appended
|
||||
* to the required VM arguments that are required to launch Ghidra, which are hidden
|
||||
* from the user.
|
||||
*/
|
||||
public static final String ATTR_VM_ARGUMENTS = "ghidradev.ghidraVmArguments";
|
||||
|
||||
/**
|
||||
* Creates a new launch configuration for the given Java project.
|
||||
*
|
||||
* @param javaProject The Java project to create a launch configuration for.
|
||||
* @param launchConfigTypeId The type of launch configuration.
|
||||
* @param launchConfigName The name of the launch configuration.
|
||||
* @param runConfigMemory The run configuration's desired memory. Could be null.
|
||||
* @return A launch configuration working copy.
|
||||
* @throws CoreException If there was an Eclipse-related problem with creating the launch
|
||||
* configuration.
|
||||
*/
|
||||
public static ILaunchConfigurationWorkingCopy createLaunchConfig(IJavaProject javaProject,
|
||||
String launchConfigTypeId, String launchConfigName, String runConfigMemory)
|
||||
throws CoreException {
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
ILaunchConfigurationType launchType =
|
||||
launchManager.getLaunchConfigurationType(launchConfigTypeId);
|
||||
ILaunchConfigurationWorkingCopy wc = launchType.newInstance(null, launchConfigName);
|
||||
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
|
||||
javaProject.getProject().getName());
|
||||
setMainTypeName(wc);
|
||||
setMemory(wc, runConfigMemory);
|
||||
setFavorites(wc);
|
||||
return wc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the launch configuration with the given name.
|
||||
*
|
||||
* @param name The name of the launch configuration to get.
|
||||
* @return The launch configuration with the given name, or null if it doesn't exist.
|
||||
* @throws CoreException If there was an Eclipse-related problem with getting the launch
|
||||
* configuration.
|
||||
*/
|
||||
public static ILaunchConfiguration getLaunchConfig(String name) throws CoreException {
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
for (ILaunchConfiguration lc : launchManager.getLaunchConfigurations()) {
|
||||
if (lc.getName().equals(name)) {
|
||||
return lc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the launch configuration with the given name and the given type ID.
|
||||
*
|
||||
* @param name The name of the launch configuration to get.
|
||||
* @param id The launch configuration type id of the launch configuration to get.
|
||||
* @return The launch configuration with the given name and type, or null if it doesn't exist.
|
||||
* @throws CoreException If there was an Eclipse-related problem with getting the launch
|
||||
* configuration.
|
||||
*/
|
||||
public static ILaunchConfiguration getLaunchConfig(String name, String id)
|
||||
throws CoreException {
|
||||
ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
|
||||
ILaunchConfigurationType type = launchManager.getLaunchConfigurationType(id);
|
||||
if (type != null) {
|
||||
for (ILaunchConfiguration lc : launchManager.getLaunchConfigurations(type)) {
|
||||
if (lc.getName().equals(name)) {
|
||||
return lc;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main type name attribute in the provided working copy. For Ghidra projects, this
|
||||
* should be {@link GhidraLauncher}.
|
||||
*
|
||||
* @param wc The launch configuration working copy to modify.
|
||||
* @return The modified working copy.
|
||||
*/
|
||||
public static ILaunchConfigurationWorkingCopy setMainTypeName(
|
||||
ILaunchConfigurationWorkingCopy wc) {
|
||||
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
|
||||
GhidraLauncher.class.getName());
|
||||
return wc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the maximum Java heap size (-Xmx) to the VM arguments in the provided working copy.
|
||||
*
|
||||
* @param memory The desired maximum Java heap size. Could be null if the default is to be
|
||||
* used.
|
||||
* @param wc The launch configuration working copy to modify.
|
||||
* @return The modified working copy.
|
||||
* @throws CoreException if there was an Eclipse-related issue appending the VM argument.
|
||||
*
|
||||
* @see #ATTR_VM_ARGUMENTS
|
||||
*/
|
||||
public static ILaunchConfigurationWorkingCopy setMemory(ILaunchConfigurationWorkingCopy wc,
|
||||
String memory) throws CoreException {
|
||||
if (memory != null) {
|
||||
String vmArgs = wc.getAttribute(ATTR_VM_ARGUMENTS, "");
|
||||
if (!vmArgs.isEmpty()) {
|
||||
vmArgs += " ";
|
||||
}
|
||||
wc.setAttribute(ATTR_VM_ARGUMENTS, vmArgs + "-Xmx" + memory);
|
||||
}
|
||||
return wc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the favorites attribute in the provided working copy to include the launcher in both
|
||||
* the run and debug launch groups.
|
||||
*
|
||||
* @param wc The launch configuration working copy to modify.
|
||||
* @return The modified working copy.
|
||||
* @throws CoreException If there was an Eclipse-related problem with setting the favorites
|
||||
* attribute.
|
||||
*/
|
||||
public static ILaunchConfigurationWorkingCopy setFavorites(ILaunchConfigurationWorkingCopy wc)
|
||||
throws CoreException {
|
||||
List<String> list =
|
||||
wc.getAttribute(IDebugUIConstants.ATTR_FAVORITE_GROUPS, new ArrayList<>());
|
||||
list.add(IDebugUIConstants.ID_DEBUG_LAUNCH_GROUP);
|
||||
list.add(IDebugUIConstants.ID_RUN_LAUNCH_GROUP);
|
||||
wc.setAttribute(IDebugUIConstants.ATTR_FAVORITE_GROUPS, list);
|
||||
return wc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given launch configuration to the GUI's favorites list. This is useful to do if
|
||||
* you create a launch configuration and want it to appear in the favorites list before ever
|
||||
* launching it.
|
||||
*
|
||||
* @param launchConfig The launch configuration to add.
|
||||
*/
|
||||
public static void addToFavorites(ILaunchConfiguration launchConfig) {
|
||||
LaunchConfigurationManager mgr = DebugUIPlugin.getDefault().getLaunchConfigurationManager();
|
||||
LaunchHistory runHistory = mgr.getLaunchHistory(IDebugUIConstants.ID_RUN_LAUNCH_GROUP);
|
||||
LaunchHistory debugHistory = mgr.getLaunchHistory(IDebugUIConstants.ID_DEBUG_LAUNCH_GROUP);
|
||||
runHistory.addFavorite(launchConfig);
|
||||
debugHistory.addFavorite(launchConfig);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
/* ###
|
||||
* 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.utils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.core.resources.*;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.jdt.core.*;
|
||||
import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
|
||||
import org.eclipse.jdt.core.refactoring.descriptors.RenameJavaElementDescriptor;
|
||||
import org.eclipse.ltk.core.refactoring.*;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Utility methods for working with Ghidra modules in Eclipse.
|
||||
*/
|
||||
public class GhidraModuleUtils {
|
||||
|
||||
public enum ModuleTemplateType {
|
||||
ANALYZER("Analyzer", "Extends Ghidra analysis"),
|
||||
PLUGIN("Plugin", "Extends the Ghidra user interface"),
|
||||
LOADER("Loader", "Loads/imports a binary file format into Ghidra"),
|
||||
FILESYSTEM("FileSystem", "Opens a file system format for browsing or batch import"),
|
||||
EXPORTER("Exporter", "Exports/saves a Ghidra program to a specific file format");
|
||||
|
||||
private String name;
|
||||
private String description;
|
||||
|
||||
private ModuleTemplateType(String name, String description) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra module project with the given name.
|
||||
*
|
||||
* @param projectName The name of the project to create.
|
||||
* @param projectDir The directory the project will be created in.
|
||||
* @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 created 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 createGhidraModuleProject(String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
|
||||
String jythonInterpreterName, IProgressMonitor monitor)
|
||||
throws IOException, ParseException, CoreException {
|
||||
|
||||
// Create empty Ghidra project
|
||||
IJavaProject javaProject =
|
||||
GhidraProjectUtils.createEmptyGhidraProject(projectName, projectDir, createRunConfig,
|
||||
runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
|
||||
IProject project = javaProject.getProject();
|
||||
|
||||
// Create source directories
|
||||
List<IFolder> sourceFolders = new ArrayList<>();
|
||||
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("ghidra_scripts"));
|
||||
for (IFolder sourceFolder : sourceFolders) {
|
||||
GhidraProjectUtils.createFolder(sourceFolder, monitor);
|
||||
}
|
||||
|
||||
// Put the source directories in the project's classpath
|
||||
List<IClasspathEntry> classpathEntries = new LinkedList<>();
|
||||
for (IFolder sourceFolder : sourceFolders) {
|
||||
classpathEntries.add(JavaCore.newSourceEntry(sourceFolder.getFullPath()));
|
||||
}
|
||||
GhidraProjectUtils.addToClasspath(javaProject, classpathEntries, monitor);
|
||||
|
||||
return javaProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually add in the source from the Skeleton (which should exist in the Ghidra installation
|
||||
* directory), and then look at what's in the templates set to know what to keep and what to
|
||||
* discard.
|
||||
*
|
||||
* @param javaProject The project whose source is to be configured.
|
||||
* @param projectDir The project's directory.
|
||||
* @param ghidraLayout The Ghidra layout the project is linked to.
|
||||
* @param moduleTemplateTypes The templates to include in the source.
|
||||
* @param monitor The progress monitor to use during source configuration.
|
||||
* @return The primary module source file (which could be opened in an editor by default).
|
||||
* @throws IOException If there was a file-related problem with configuring the source.
|
||||
* @throws CoreException If there was an Eclipse-related problem with configuring the source.
|
||||
*/
|
||||
public static IFile configureModuleSource(IJavaProject javaProject, File projectDir,
|
||||
GhidraApplicationLayout ghidraLayout, Set<ModuleTemplateType> moduleTemplateTypes,
|
||||
IProgressMonitor monitor) throws CoreException, IOException {
|
||||
|
||||
final String SKELETON_PKG = "skeleton";
|
||||
final String SKELETON_CLASS = "Skeleton";
|
||||
|
||||
IProject project = javaProject.getProject();
|
||||
|
||||
// Create a list of files to exclude. Use the provided templates list to know what
|
||||
// source files should be included in the project.
|
||||
List<String> excludeRegexes = new ArrayList<>();
|
||||
for (ModuleTemplateType moduleTemplateType : ModuleTemplateType.values()) {
|
||||
if (!moduleTemplateTypes.contains(moduleTemplateType)) {
|
||||
excludeRegexes.add(SKELETON_CLASS + moduleTemplateType.getName() + "\\.java");
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the skeleton files
|
||||
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
|
||||
File skeletonDir = Files.find(ghidraInstallDir.toPath(), 4, (path, attrs) -> {
|
||||
return attrs.isDirectory() && path.getFileName().toString().equals("Skeleton");
|
||||
}).map(p -> p.toFile()).findFirst().orElse(null);
|
||||
if (skeletonDir == null) {
|
||||
throw new IOException("Failed to find skeleton directory.");
|
||||
}
|
||||
try {
|
||||
FileUtilities.copyDir(skeletonDir, projectDir, f -> {
|
||||
return excludeRegexes.stream().map(r -> Pattern.compile(r)).noneMatch(
|
||||
p -> p.matcher(f.getName()).matches());
|
||||
}, null);
|
||||
}
|
||||
catch (CancelledException | IOException e) {
|
||||
throw new IOException("Failed to copy skeleton directory: " + projectDir);
|
||||
}
|
||||
|
||||
// Refresh project so it sees the new files
|
||||
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
|
||||
|
||||
// Update language ant properties file
|
||||
GhidraModuleUtils.writeAntProperties(project, ghidraLayout);
|
||||
|
||||
// Refactor/rename the source files, package, and help files
|
||||
String packageName = project.getName().toLowerCase();
|
||||
for (ModuleTemplateType moduleTemplateType : moduleTemplateTypes) {
|
||||
IType skeletonClass = javaProject.findType(
|
||||
SKELETON_PKG + "." + SKELETON_CLASS + moduleTemplateType.getName(), monitor);
|
||||
if (skeletonClass != null) {
|
||||
renameJavaElement(skeletonClass.getCompilationUnit(),
|
||||
project.getName() + moduleTemplateType.getName(), monitor);
|
||||
}
|
||||
}
|
||||
IJavaElement skeletonPackage = javaProject.findElement(new Path(SKELETON_PKG));
|
||||
if (skeletonPackage != null) {
|
||||
renameJavaElement(skeletonPackage, packageName, monitor);
|
||||
}
|
||||
IJavaElement helpTopic = javaProject.findElement(new Path("help/topics/skeleton"));
|
||||
if (helpTopic != null) {
|
||||
renameJavaElement(helpTopic, "help.topics." + packageName, monitor);
|
||||
}
|
||||
|
||||
// Return the primary source file in the project (the first java file we see in the package)
|
||||
IFolder packageFolder = project.getFolder("/src/main/java").getFolder(packageName);
|
||||
if (packageFolder.exists()) {
|
||||
for (IResource resource : packageFolder.members()) {
|
||||
if (resource instanceof IFile && resource.getName().endsWith(".java")) {
|
||||
return (IFile) resource;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* given project is not a Ghidra module project, or if the Ghidra module project does not have a
|
||||
* language build.xml ant file, this method has no effect.
|
||||
*
|
||||
* @param project The project to receive the ant properties.
|
||||
* @param ghidraLayout The layout that contains the Ghidra installation directory that the project
|
||||
* is currently linked against.
|
||||
* @throws IOException if there was a problem writing the ant properties file.
|
||||
*/
|
||||
public static void writeAntProperties(IProject project, GhidraApplicationLayout ghidraLayout)
|
||||
throws IOException {
|
||||
if (!GhidraProjectUtils.isGhidraModuleProject(project)) {
|
||||
return;
|
||||
}
|
||||
|
||||
IFolder dataFolder = project.getFolder("data");
|
||||
if (!dataFolder.exists()) {
|
||||
return;
|
||||
}
|
||||
IFile buildXmlFile = dataFolder.getFile("build.xml");
|
||||
if (!buildXmlFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
|
||||
File antFile = new File(project.getRawLocation().toFile(), ".antProperties.xml"); // hidden
|
||||
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter(antFile))) {
|
||||
writer.println(
|
||||
"<!-- This file is generated on each \"Link Ghidra\" command. Do not modify. -->");
|
||||
writer.println();
|
||||
writer.println("<project>");
|
||||
writer.println(" <property name=\"ghidra.install.dir\" value=\"" +
|
||||
ghidraInstallDir.getAbsolutePath() + "\" />");
|
||||
writer.println("</project>");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the given Java element to the given new name. Currently only supports renaming
|
||||
* packages and compilation units.
|
||||
*
|
||||
* @param element The Java element to rename.
|
||||
* @param newName The desired new name of the element.
|
||||
* @param monitor The progress monitor.
|
||||
* @throws CoreException If there is an Eclipse-related problem with the rename.
|
||||
* @throws IllegalArgumentException If the given Java element is not a package or compilation unit.
|
||||
*/
|
||||
private static void renameJavaElement(IJavaElement element, String newName,
|
||||
IProgressMonitor monitor) throws CoreException, IllegalArgumentException {
|
||||
String id;
|
||||
if (element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
|
||||
id = IJavaRefactorings.RENAME_PACKAGE;
|
||||
}
|
||||
else if (element.getElementType() == IJavaElement.COMPILATION_UNIT) {
|
||||
id = IJavaRefactorings.RENAME_COMPILATION_UNIT;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Can only rename packages and compilation units!");
|
||||
}
|
||||
RefactoringContribution contribution = RefactoringCore.getRefactoringContribution(id);
|
||||
RenameJavaElementDescriptor descriptor =
|
||||
(RenameJavaElementDescriptor) contribution.createDescriptor();
|
||||
descriptor.setProject(element.getResource().getProject().getName());
|
||||
descriptor.setNewName(newName);
|
||||
descriptor.setJavaElement(element);
|
||||
RefactoringStatus status = new RefactoringStatus();
|
||||
Refactoring refactoring = descriptor.createRefactoring(status);
|
||||
refactoring.checkInitialConditions(monitor);
|
||||
refactoring.checkFinalConditions(monitor);
|
||||
Change change = refactoring.createChange(monitor);
|
||||
change.perform(monitor);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,633 @@
|
|||
/* ###
|
||||
* 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.utils;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
|
||||
import javax.naming.OperationNotSupportedException;
|
||||
|
||||
import org.eclipse.core.resources.*;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
import org.eclipse.jdt.core.*;
|
||||
import org.eclipse.jdt.internal.launching.StandardVMType;
|
||||
import org.eclipse.jdt.launching.*;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.ui.part.FileEditorInput;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.framework.GModule;
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidradev.Activator;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import utility.module.ModuleUtilities;
|
||||
|
||||
/**
|
||||
* Utility methods for working with Eclipse Ghidra projects.
|
||||
*/
|
||||
public class GhidraProjectUtils {
|
||||
|
||||
/**
|
||||
* The name of the linked project folder that points to the installation of
|
||||
* Ghidra that it is using.
|
||||
*/
|
||||
public static final String GHIDRA_FOLDER_NAME = "Ghidra";
|
||||
|
||||
/**
|
||||
* Characters that we are not going to allow in our Ghidra project and file.
|
||||
*/
|
||||
public static final String ILLEGAL_FILENAME_CHARS = " `~!@#$%^&*()-+=[]{}\\|;:'\"<>,./?";
|
||||
|
||||
/**
|
||||
* Characters that we are not going to allow to start our Ghidra project and file names.
|
||||
*/
|
||||
public static final String ILLEGAL_FILENAME_START_CHARS = "0123456789";
|
||||
|
||||
/**
|
||||
* Gets all of the open Java projects in the workspace.
|
||||
*
|
||||
* @return A collection of the open Java projects in the workspace.
|
||||
*/
|
||||
public static Collection<IJavaProject> getJavaProjects() {
|
||||
List<IJavaProject> javaProjects = new ArrayList<>();
|
||||
for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
|
||||
if (project.isOpen() && isJavaProject(project)) {
|
||||
javaProjects.add(JavaCore.create(project));
|
||||
}
|
||||
}
|
||||
return javaProjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the given project is a Java project.
|
||||
*
|
||||
* @param project The project to check.
|
||||
* @return True if the given project is a Java project; otherwise, false.
|
||||
*/
|
||||
public static boolean isJavaProject(IProject project) {
|
||||
try {
|
||||
return project != null && project.hasNature(JavaCore.NATURE_ID);
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Java project check failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the given project is a Ghidra project.
|
||||
*
|
||||
* @param project The project to check.
|
||||
* @return True if the given project is a Ghidra project; otherwise, false.
|
||||
*/
|
||||
public static boolean isGhidraProject(IProject project) {
|
||||
return isJavaProject(project) && project.getFolder(GHIDRA_FOLDER_NAME).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the given Java project is a Ghidra module project.
|
||||
*
|
||||
* @param project The project to check.
|
||||
* @return True if the given project is a Ghidra module project; otherwise, false.
|
||||
*/
|
||||
public static boolean isGhidraModuleProject(IProject project) {
|
||||
return isGhidraProject(project) &&
|
||||
project.getFile(ModuleUtilities.MANIFEST_FILE_NAME).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all of the open Ghidra projects in the workspace.
|
||||
*
|
||||
* @return A collection of the open Ghidra projects in the workspace.
|
||||
*/
|
||||
public static Collection<IJavaProject> getGhidraProjects() {
|
||||
List<IJavaProject> ghidraProjects = new ArrayList<>();
|
||||
for (IJavaProject javaProject : getJavaProjects()) {
|
||||
if (isGhidraProject(javaProject.getProject())) {
|
||||
ghidraProjects.add(javaProject);
|
||||
}
|
||||
}
|
||||
return ghidraProjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the open Ghidra project with the given name.
|
||||
*
|
||||
* @param name The name of the project to get.
|
||||
* @return The open Ghidra project with the given name, or null if it doesn't exist.
|
||||
*/
|
||||
public static IJavaProject getGhidraProject(String name) {
|
||||
for (IJavaProject javaProject : getGhidraProjects()) {
|
||||
if (javaProject.getProject().getName().equals(name)) {
|
||||
return javaProject;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the selected project, or null if a project is not selected. If multiple things
|
||||
* are selected, only the first selected item is considered.
|
||||
*
|
||||
* @param selection A selection from which to get a project.
|
||||
* @return The selected project, or null if a project is not selected.
|
||||
*/
|
||||
public static IProject getSelectedProject(ISelection selection) {
|
||||
IProject project = null;
|
||||
|
||||
if (selection instanceof IStructuredSelection) {
|
||||
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
|
||||
Object firstElement = structuredSelection.getFirstElement();
|
||||
if (firstElement instanceof IResource) {
|
||||
project = ((IResource) (firstElement)).getProject();
|
||||
}
|
||||
else if (firstElement instanceof IJavaElement) {
|
||||
project = ((IJavaElement) (firstElement)).getResource().getProject();
|
||||
}
|
||||
}
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the given project object's enclosing project.
|
||||
*
|
||||
* @param projectObj The project object to get the enclosing project of.
|
||||
* @return The given project object's enclosing project. Could be null if it could not be
|
||||
* determined.
|
||||
*/
|
||||
public static IProject getEnclosingProject(Object projectObj) {
|
||||
IProject project = null;
|
||||
|
||||
if (projectObj instanceof List) {
|
||||
List<?> list = (List<?>) projectObj;
|
||||
if (list.size() == 1) {
|
||||
projectObj = list.iterator().next();
|
||||
}
|
||||
}
|
||||
|
||||
if (projectObj instanceof FileEditorInput) {
|
||||
FileEditorInput fileEditorInput = (FileEditorInput) projectObj;
|
||||
project = fileEditorInput.getFile().getProject();
|
||||
}
|
||||
else if (projectObj instanceof IResource) {
|
||||
IResource resource = (IResource) projectObj;
|
||||
project = resource.getProject();
|
||||
}
|
||||
else if (projectObj instanceof IJavaElement) {
|
||||
IJavaElement javaElement = (IJavaElement) projectObj;
|
||||
IResource resource = javaElement.getResource();
|
||||
if (resource != null) {
|
||||
project = resource.getProject();
|
||||
}
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the given folder, including any necessary but nonexistent parent directories.
|
||||
*
|
||||
* @param folder The folder to create.
|
||||
* @param monitor The progress monitor to use during folder creation.
|
||||
* @throws CoreException If there was an Eclipse-related problem with creating the folder.
|
||||
*/
|
||||
public static void createFolder(IFolder folder, IProgressMonitor monitor) throws CoreException {
|
||||
IContainer parent = folder.getParent();
|
||||
if (parent instanceof IFolder) {
|
||||
createFolder((IFolder) parent, monitor);
|
||||
}
|
||||
if (!folder.exists()) {
|
||||
folder.create(false, true, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Java project's classpath to include the given list of classpath entries.
|
||||
*
|
||||
* @param javaProject The Java project that will get the new classpath entries.
|
||||
* @param classpathEntries A list of classpath entries to add to the Java project's classpath.
|
||||
* @param monitor The progress monitor.
|
||||
* @throws JavaModelException If there was an issue adding to the Java project's classpath.
|
||||
*/
|
||||
public static void addToClasspath(IJavaProject javaProject,
|
||||
List<IClasspathEntry> classpathEntries, IProgressMonitor monitor)
|
||||
throws JavaModelException {
|
||||
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
|
||||
classpathEntries.add(entry);
|
||||
}
|
||||
javaProject.setRawClasspath(
|
||||
classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]), monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Java project's classpath to include the given classpath entry.
|
||||
*
|
||||
* @param javaProject The Java project that will get the new classpath entries.
|
||||
* @param classpathEntry The classpath entry to add to the Java project's classpath.
|
||||
* @param monitor The progress monitor.
|
||||
* @throws JavaModelException If there was an issue adding to the Java project's classpath.
|
||||
*/
|
||||
public static void addToClasspath(IJavaProject javaProject, IClasspathEntry classpathEntry,
|
||||
IProgressMonitor monitor) throws JavaModelException {
|
||||
List<IClasspathEntry> entryList = new ArrayList<>();
|
||||
entryList.add(classpathEntry);
|
||||
addToClasspath(javaProject, entryList, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new empty Ghidra project with the given name.
|
||||
*
|
||||
* @param projectName The name of the project to create.
|
||||
* @param projectDir The directory the project will be created in.
|
||||
* @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 created 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 createEmptyGhidraProject(String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory, GhidraApplicationLayout ghidraLayout,
|
||||
String jythonInterpreterName, IProgressMonitor monitor)
|
||||
throws IOException, ParseException, CoreException {
|
||||
|
||||
// Get Ghidra's Java configuration
|
||||
JavaConfig javaConfig =
|
||||
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
|
||||
|
||||
// Make new Java project
|
||||
IWorkspace workspace = ResourcesPlugin.getWorkspace();
|
||||
IProject project = workspace.getRoot().getProject(projectName);
|
||||
IProjectDescription projectDescription = workspace.newProjectDescription(projectName);
|
||||
projectDescription.setLocation(new Path(projectDir.getAbsolutePath()));
|
||||
projectDescription.setNatureIds(new String[] { JavaCore.NATURE_ID });
|
||||
ICommand command = projectDescription.newCommand();
|
||||
command.setBuilderName(JavaCore.BUILDER_ID);
|
||||
projectDescription.setBuildSpec(new ICommand[] { command });
|
||||
project.create(projectDescription, monitor);
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
project.open(monitor);
|
||||
|
||||
// Clear the project's classpath
|
||||
javaProject.setRawClasspath(new IClasspathEntry[0], monitor);
|
||||
|
||||
// Configure Java compiler for the project
|
||||
configureJavaCompiler(javaProject, javaConfig);
|
||||
|
||||
// Setup bin folder
|
||||
IFolder binFolder = project.getFolder("bin");
|
||||
javaProject.setOutputLocation(binFolder.getFullPath(), monitor);
|
||||
|
||||
// Link in Ghidra to the project
|
||||
linkGhidraToProject(javaProject, ghidraLayout, javaConfig, jythonInterpreterName, monitor);
|
||||
|
||||
// Create run configuration (if necessary)
|
||||
if (createRunConfig) {
|
||||
try {
|
||||
ILaunchConfiguration launchConfig =
|
||||
GhidraLaunchUtils.createLaunchConfig(javaProject, GhidraLaunchUtils.GUI_LAUNCH,
|
||||
project.getName(), runConfigMemory).doSave();
|
||||
GhidraLaunchUtils.addToFavorites(launchConfig);
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.showErrorDialog(
|
||||
"Failed to create a Ghidra run configuration for the new project. Please do it manually.");
|
||||
}
|
||||
}
|
||||
|
||||
return javaProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links the Ghidra layout to the given Java project. This effectively makes the project's
|
||||
* build path "Ghidra aware."
|
||||
* <p>
|
||||
* If the project already has a Ghidra installation directory linked to it, that link is deleted
|
||||
* and the new Ghidra installation directory is freshly linked back in.
|
||||
*
|
||||
* @param javaProject The Java project to link.
|
||||
* @param ghidraLayout The Ghidra layout to link the project to.
|
||||
* @param javaConfig Ghidra's Java configuration.
|
||||
* @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 used during link.
|
||||
* @throws IOException If there was a file-related problem with linking in Ghidra.
|
||||
* @throws CoreException If there was an Eclipse-related problem with linking in Ghidra.
|
||||
*/
|
||||
public static void linkGhidraToProject(IJavaProject javaProject,
|
||||
GhidraApplicationLayout ghidraLayout, JavaConfig javaConfig,
|
||||
String jythonInterpreterName, IProgressMonitor monitor)
|
||||
throws CoreException, IOException {
|
||||
|
||||
// Gets the Ghidra installation directory to link to from the Ghidra layout
|
||||
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
|
||||
|
||||
// Get the Java VM used to launch the Ghidra to link to
|
||||
IVMInstall vm = getGhidraVm(javaConfig);
|
||||
IPath vmPath =
|
||||
new Path(JavaRuntime.JRE_CONTAINER).append(vm.getVMInstallType().getId()).append(
|
||||
vm.getName());
|
||||
|
||||
// Get the project's existing linked Ghidra installation folder and path (it may not exist)
|
||||
IFolder ghidraFolder =
|
||||
javaProject.getProject().getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
|
||||
IPath oldGhidraInstallPath = ghidraFolder.exists()
|
||||
? new Path(ghidraFolder.getRawLocation().toFile().getAbsolutePath())
|
||||
: null;
|
||||
|
||||
// Loop through the project's existing classpath to decide what to keep (things that aren't
|
||||
// related to Ghidra), and things to not keep (things that will be added fresh from the new
|
||||
// Ghidra).
|
||||
IClasspathEntry vmEntryCandidate = null;
|
||||
List<IClasspathEntry> classpathEntriesToKeep = new ArrayList<>();
|
||||
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
|
||||
|
||||
// If the project is not linked to an old Ghidra, save off the project's existing VM.
|
||||
// We'll decide whether or not to keep it later.
|
||||
if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
|
||||
entry.getPath().toString().startsWith(JavaRuntime.JRE_CONTAINER)) {
|
||||
if (oldGhidraInstallPath == null) {
|
||||
vmEntryCandidate = entry;
|
||||
}
|
||||
}
|
||||
else if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
|
||||
// Keep all project dependencies
|
||||
classpathEntriesToKeep.add(entry);
|
||||
}
|
||||
else {
|
||||
// If the project is linked to an old Ghidra, keep the list of source folders that are
|
||||
// linked to the Ghidra installation (after updating their paths to point to the new
|
||||
// Ghidra installation).
|
||||
IFolder entryFolder =
|
||||
ResourcesPlugin.getWorkspace().getRoot().getFolder(entry.getPath());
|
||||
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE && entryFolder.isLinked() &&
|
||||
oldGhidraInstallPath != null &&
|
||||
oldGhidraInstallPath.isPrefixOf(entryFolder.getLocation())) {
|
||||
String origPath = entryFolder.getLocation().toString();
|
||||
String newPath = ghidraInstallDir.getAbsolutePath() +
|
||||
origPath.substring(oldGhidraInstallPath.toString().length());
|
||||
entryFolder.createLink(new Path(newPath), IResource.REPLACE, monitor);
|
||||
classpathEntriesToKeep.add(JavaCore.newSourceEntry(entryFolder.getFullPath()));
|
||||
}
|
||||
// If it's anything else that doesn't live in the old Ghidra installation, keep it.
|
||||
else if (oldGhidraInstallPath == null ||
|
||||
!oldGhidraInstallPath.isPrefixOf(entry.getPath())) {
|
||||
classpathEntriesToKeep.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we detected a VM to potentially keep, we should ask the user if they are OK with using
|
||||
// the VM that Ghidra wants to use. Changing it automatically might cause problems for their
|
||||
// existing Java project.
|
||||
if (vmEntryCandidate == null || (!vmEntryCandidate.getPath().equals(vmPath) &&
|
||||
EclipseMessageUtils.showConfirmDialog("Java Conflict",
|
||||
"Current Java: " + JavaRuntime.getVMInstall(javaProject).getName() +
|
||||
"\nGhidra Java: " + vm.getName() +
|
||||
"\n\nPress OK to use Ghidra's Java, or cancel to keep current Java."))) {
|
||||
classpathEntriesToKeep.add(JavaCore.newContainerEntry(vmPath));
|
||||
}
|
||||
else {
|
||||
classpathEntriesToKeep.add(vmEntryCandidate);
|
||||
}
|
||||
|
||||
// Add the Ghidra libraries from the new Ghidra installation directory to the classpath
|
||||
List<IClasspathEntry> libraryClasspathEntries =
|
||||
getGhidraLibraryClasspathEntries(ghidraLayout);
|
||||
classpathEntriesToKeep.addAll(libraryClasspathEntries);
|
||||
|
||||
// Set classpath
|
||||
javaProject.setRawClasspath(
|
||||
classpathEntriesToKeep.toArray(new IClasspathEntry[classpathEntriesToKeep.size()]),
|
||||
null);
|
||||
|
||||
// Update link to the Ghidra installation directory
|
||||
ghidraFolder.createLink(new Path(ghidraInstallDir.getAbsolutePath()), IResource.REPLACE,
|
||||
monitor);
|
||||
|
||||
// Update language ant properties file, if applicable
|
||||
GhidraModuleUtils.writeAntProperties(javaProject.getProject(), ghidraLayout);
|
||||
|
||||
// Setup Python for the project
|
||||
if (PyDevUtils.isSupportedPyDevInstalled()) {
|
||||
try {
|
||||
PyDevUtils.setupPythonForProject(javaProject, libraryClasspathEntries,
|
||||
jythonInterpreterName, monitor);
|
||||
}
|
||||
catch (OperationNotSupportedException e) {
|
||||
EclipseMessageUtils.showErrorDialog("PyDev error",
|
||||
"Failed to setup Python for the project. PyDev version is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate classpath attribute for Ghidra's javadoc in the provided layout.
|
||||
*
|
||||
* @param ghidraLayout The Ghidra layout that contains the javadoc to get.
|
||||
* @return The appropriate classpath attribute for Ghidra's javadoc in the provided layout.
|
||||
* @throws FileNotFoundException If the javadoc was not found in the provided layout.
|
||||
*/
|
||||
private static IClasspathAttribute getGhidraJavadoc(GhidraApplicationLayout ghidraLayout)
|
||||
throws FileNotFoundException {
|
||||
File ghidraInstallDir = ghidraLayout.getApplicationInstallationDir().getFile(false);
|
||||
File ghidraJavadocFile = new File(ghidraInstallDir, "docs/GhidraAPI_javadoc.zip");
|
||||
if (!ghidraJavadocFile.isFile()) {
|
||||
throw new FileNotFoundException("Ghidra javadoc file does not exist!");
|
||||
}
|
||||
String ghidraJavadocPath = String.format("jar:file:%s%s!/api/",
|
||||
(ghidraJavadocFile.getAbsolutePath().startsWith("/") ? "" : "/"),
|
||||
ghidraJavadocFile.getAbsolutePath());
|
||||
return JavaCore.newClasspathAttribute("javadoc_location", ghidraJavadocPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of classpath entries for Ghidra's module libraries.
|
||||
*
|
||||
* @param ghidraLayout The Ghidra layout that contains the classpath entries to get.
|
||||
* @return A list of classpath entries for Ghidra's module libraries.
|
||||
* @throws FileNotFoundException If the javadoc was not found in the provided layout.
|
||||
*/
|
||||
private static List<IClasspathEntry> getGhidraLibraryClasspathEntries(
|
||||
GhidraApplicationLayout ghidraLayout) throws FileNotFoundException {
|
||||
|
||||
IClasspathAttribute ghidraJavadocAttr = getGhidraJavadoc(ghidraLayout);
|
||||
List<IClasspathEntry> classpathEntries = new ArrayList<>();
|
||||
for (GModule module : ghidraLayout.getModules().values()) {
|
||||
File moduleDir = module.getModuleRoot().getFile(false);
|
||||
File libDir = new File(moduleDir, "lib");
|
||||
if (!libDir.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
for (File f : libDir.listFiles()) { // assuming no relevant subdirs exist in lib/
|
||||
String name = f.getName();
|
||||
if (!name.endsWith(".jar")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IPath jarPath = new Path(f.getAbsolutePath());
|
||||
|
||||
String baseJarName = name.substring(0, name.length() - 4);
|
||||
File srcZipFile = new File(libDir, baseJarName + "-src.zip");
|
||||
IPath srcPath = srcZipFile.exists() ? new Path(srcZipFile.getAbsolutePath()) : null;
|
||||
|
||||
classpathEntries.add(JavaCore.newLibraryEntry(jarPath, srcPath, null, null,
|
||||
new IClasspathAttribute[] { ghidraJavadocAttr }, false));
|
||||
}
|
||||
}
|
||||
return classpathEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the required VM used to build and run the Ghidra defined by the given layout.
|
||||
*
|
||||
* @param javaConfig Ghidra's Java configuration.
|
||||
* @return The required VM used to build and run the Ghidra defined by the given layout.
|
||||
* @throws IOException If there was a file-related problem with getting the VM.
|
||||
* @throws CoreException If there was an Eclipse-related problem with creating the project.
|
||||
*/
|
||||
private static IVMInstall getGhidraVm(JavaConfig javaConfig) throws IOException, CoreException {
|
||||
|
||||
File requiredJavaHomeDir = javaConfig.getSavedJavaHome(); // safe to assume it's valid
|
||||
|
||||
// First look for a matching VM in Eclipse's existing list.
|
||||
// NOTE: Mac has its own VM type, so be sure to check it for VM matches too.
|
||||
IVMInstall vm = null;
|
||||
IVMInstallType standardType =
|
||||
JavaRuntime.getVMInstallType(StandardVMType.ID_STANDARD_VM_TYPE);
|
||||
IVMInstallType macType =
|
||||
JavaRuntime.getVMInstallType("org.eclipse.jdt.internal.launching.macosx.MacOSXType");
|
||||
if (standardType == null) {
|
||||
throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, IStatus.ERROR,
|
||||
"Failed to find the standard Java VM type.", null));
|
||||
}
|
||||
for (IVMInstall existingVm : standardType.getVMInstalls()) {
|
||||
if (requiredJavaHomeDir.equals(existingVm.getInstallLocation())) {
|
||||
vm = existingVm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (macType != null && vm == null) {
|
||||
for (IVMInstall existingVm : macType.getVMInstalls()) {
|
||||
if (requiredJavaHomeDir.equals(existingVm.getInstallLocation())) {
|
||||
vm = existingVm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find a match, create a new standard type entry
|
||||
if (vm == null) {
|
||||
long unique = System.currentTimeMillis(); // This loop seems to be the accepted way to get a unique VM id
|
||||
while (standardType.findVMInstall(String.valueOf(unique)) != null) {
|
||||
unique++;
|
||||
}
|
||||
VMStandin vmStandin = new VMStandin(standardType, String.valueOf(unique));
|
||||
String dirName = requiredJavaHomeDir.getName();
|
||||
if (requiredJavaHomeDir.getAbsolutePath().contains("Contents/Home")) {
|
||||
dirName = requiredJavaHomeDir.getParentFile().getParentFile().getName();
|
||||
}
|
||||
vmStandin.setName(Activator.PLUGIN_ID + "_" + dirName);
|
||||
vmStandin.setInstallLocation(requiredJavaHomeDir);
|
||||
vm = vmStandin.convertToRealVM();
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the default Java compiler behavior for the given java project.
|
||||
*
|
||||
* @param jp The Java project to configure.
|
||||
* @param javaConfig Ghidra's Java configuration.
|
||||
*/
|
||||
private static void configureJavaCompiler(IJavaProject jp, JavaConfig javaConfig) {
|
||||
|
||||
final String WARNING = JavaCore.WARNING;
|
||||
final String IGNORE = JavaCore.IGNORE;
|
||||
final String ERROR = JavaCore.ERROR;
|
||||
|
||||
// Compliance
|
||||
jp.setOption(JavaCore.COMPILER_SOURCE, javaConfig.getCompilerComplianceLevel());
|
||||
jp.setOption(JavaCore.COMPILER_COMPLIANCE, javaConfig.getCompilerComplianceLevel());
|
||||
jp.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
|
||||
javaConfig.getCompilerComplianceLevel());
|
||||
|
||||
// Code style
|
||||
jp.setOption(JavaCore.COMPILER_PB_STATIC_ACCESS_RECEIVER, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_INDIRECT_STATIC_ACCESS, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNQUALIFIED_FIELD_ACCESS, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNDOCUMENTED_EMPTY_BLOCK, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_SYNTHETIC_ACCESS_EMULATION, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_METHOD_WITH_CONSTRUCTOR_NAME, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_PARAMETER_ASSIGNMENT, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_NON_NLS_STRING_LITERAL, IGNORE);
|
||||
|
||||
// Potential programming problems
|
||||
jp.setOption(JavaCore.COMPILER_PB_MISSING_SERIAL_VERSION, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_POSSIBLE_ACCIDENTAL_BOOLEAN_ASSIGNMENT, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_FINALLY_BLOCK_NOT_COMPLETING, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_EMPTY_STATEMENT, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_HIDDEN_CATCH_BLOCK, ERROR);
|
||||
jp.setOption(JavaCore.COMPILER_PB_VARARGS_ARGUMENT_NEED_CAST, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_AUTOBOXING, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_INCOMPLETE_ENUM_SWITCH, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_FALLTHROUGH_CASE, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_NULL_REFERENCE, WARNING);
|
||||
|
||||
// Name shadowing and conflicts
|
||||
jp.setOption(JavaCore.COMPILER_PB_FIELD_HIDING, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_LOCAL_VARIABLE_HIDING, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_TYPE_PARAMETER_HIDING, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_OVERRIDING_PACKAGE_DEFAULT_METHOD, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_INCOMPATIBLE_NON_INHERITED_INTERFACE_METHOD, ERROR);
|
||||
|
||||
// Deprecated and restricted API
|
||||
jp.setOption(JavaCore.COMPILER_PB_DEPRECATION, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, ERROR);
|
||||
jp.setOption(JavaCore.COMPILER_PB_DISCOURAGED_REFERENCE, WARNING);
|
||||
|
||||
// Unnecessary code
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNUSED_LOCAL, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNUSED_PARAMETER, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNUSED_IMPORT, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNUSED_PRIVATE_MEMBER, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNNECESSARY_ELSE, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNNECESSARY_TYPE_CHECK, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNUSED_DECLARED_THROWN_EXCEPTION, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNUSED_LABEL, WARNING);
|
||||
|
||||
// Generic types
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNCHECKED_TYPE_OPERATION, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_RAW_TYPE_REFERENCE, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_FINAL_PARAMETER_BOUND, WARNING);
|
||||
|
||||
// Annotations
|
||||
jp.setOption(JavaCore.COMPILER_PB_MISSING_OVERRIDE_ANNOTATION, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_MISSING_DEPRECATED_ANNOTATION, IGNORE);
|
||||
jp.setOption(JavaCore.COMPILER_PB_ANNOTATION_SUPER_INTERFACE, ERROR);
|
||||
jp.setOption(JavaCore.COMPILER_PB_UNHANDLED_WARNING_TOKEN, WARNING);
|
||||
jp.setOption(JavaCore.COMPILER_PB_SUPPRESS_WARNINGS, JavaCore.ENABLED);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/* ###
|
||||
* 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.utils;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
|
||||
import org.eclipse.core.resources.*;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.jdt.core.*;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.framework.GModule;
|
||||
|
||||
/**
|
||||
* Utility methods for working with Ghidra scripts in Eclipse.
|
||||
*/
|
||||
public class GhidraScriptUtils {
|
||||
|
||||
public static File userScriptsDir =
|
||||
new File(System.getProperty("user.home") + "/ghidra_scripts");
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra script project with the given name.
|
||||
*
|
||||
* @param projectName The name of the project to create.
|
||||
* @param projectDir The directory the project will be created in.
|
||||
* @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 linkUserScripts Whether or not to link in the user scripts directory.
|
||||
* @param linkSystemScripts Whether or not to link in the system scripts directories.
|
||||
* @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 created 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 createGhidraScriptProject(String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
|
||||
boolean linkSystemScripts, GhidraApplicationLayout ghidraLayout,
|
||||
String jythonInterpreterName, IProgressMonitor monitor)
|
||||
throws IOException, ParseException, CoreException {
|
||||
|
||||
List<IClasspathEntry> classpathEntries = new ArrayList<>();
|
||||
|
||||
// Create empty Ghidra project
|
||||
IJavaProject javaProject = GhidraProjectUtils.createEmptyGhidraProject(projectName,
|
||||
projectDir, createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName,
|
||||
monitor);
|
||||
|
||||
// Link each module's ghidra_scripts directory to the project
|
||||
if (linkSystemScripts) {
|
||||
for (GModule module : ghidraLayout.getModules().values()) {
|
||||
File moduleDir = module.getModuleRoot().getFile(false);
|
||||
File moduleScriptsDir = new File(moduleDir, "ghidra_scripts");
|
||||
if (!moduleScriptsDir.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IPath moduleScriptsDirPath = new Path(moduleScriptsDir.getAbsolutePath());
|
||||
String moduleName = moduleDir.getName();
|
||||
String scriptsDirName = "Ghidra " + moduleName + " scripts";
|
||||
|
||||
IFolder link = javaProject.getProject().getFolder(scriptsDirName);
|
||||
link.createLink(moduleScriptsDirPath, IResource.NONE, monitor);
|
||||
|
||||
classpathEntries.add(JavaCore.newSourceEntry(link.getFullPath()));
|
||||
}
|
||||
}
|
||||
|
||||
// Link in the user's personal ghidra_scripts directory
|
||||
if (linkUserScripts) {
|
||||
IFolder link = javaProject.getProject().getFolder("Home scripts");
|
||||
link.createLink(new Path(userScriptsDir.getAbsolutePath()), IResource.NONE, monitor);
|
||||
classpathEntries.add(JavaCore.newSourceEntry(link.getFullPath()));
|
||||
}
|
||||
|
||||
// Update the project's classpath
|
||||
GhidraProjectUtils.addToClasspath(javaProject, classpathEntries, monitor);
|
||||
|
||||
return javaProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Ghidra script with the given name in the in the user's ghidra_scripts, and link it in
|
||||
* to the provided project.
|
||||
*
|
||||
* @param scriptFolder The folder to create the script in.
|
||||
* @param scriptName The name of the script to create.
|
||||
* @param scriptAuthor The script's author.
|
||||
* @param scriptCategory The script's category.
|
||||
* @param scriptDescription The script's description lines.
|
||||
* @param monitor The progress monitor to use during script creation.
|
||||
* @return The script file (which could be opened in an editor by default).
|
||||
* @throws IOException If there was a file-related problem with creating the script.
|
||||
* @throws CoreException If there was an Eclipse-related problem with creating the script.
|
||||
*/
|
||||
public static IFile createGhidraScript(IFolder scriptFolder, String scriptName,
|
||||
String scriptAuthor, String scriptCategory, String[] scriptDescription,
|
||||
IProgressMonitor monitor) throws CoreException, IOException {
|
||||
|
||||
// Create the scripts folder directory, if necessary
|
||||
if (!scriptFolder.exists()) {
|
||||
GhidraProjectUtils.createFolder(scriptFolder, monitor);
|
||||
}
|
||||
|
||||
IFile scriptFile = scriptFolder.getFile(scriptName);
|
||||
|
||||
// Does the script exist already? If so, it's a problem.
|
||||
if (scriptFile.exists()) {
|
||||
throw new IOException("File already exists: " + scriptFile);
|
||||
}
|
||||
|
||||
// Create the script file, and fill in a useful entry point
|
||||
try (PrintWriter writer =
|
||||
new PrintWriter(new FileWriter(scriptFile.getLocation().toFile()))) {
|
||||
if (scriptName.endsWith(".java")) {
|
||||
Arrays.stream(scriptDescription).forEach(line -> writer.println("//" + line));
|
||||
writer.println("//@author " + scriptAuthor);
|
||||
writer.println("//@category " + scriptCategory);
|
||||
writer.println("//@keybinding");
|
||||
writer.println("//@menupath");
|
||||
writer.println("//@toolbar");
|
||||
writer.println();
|
||||
writer.println("import ghidra.app.script.GhidraScript;");
|
||||
writer.println();
|
||||
writer.println("public class " + scriptName.substring(0, scriptName.length() - 5) +
|
||||
" extends GhidraScript {");
|
||||
writer.println();
|
||||
writer.println("\t@Override");
|
||||
writer.println("\tprotected void run() throws Exception {");
|
||||
writer.println("\t\t//TODO: Add script code here");
|
||||
writer.println("\t}");
|
||||
writer.println("}");
|
||||
}
|
||||
else if (scriptName.endsWith(".py")) {
|
||||
Arrays.stream(scriptDescription).forEach(line -> writer.println("#" + line));
|
||||
writer.println("#@author " + scriptAuthor);
|
||||
writer.println("#@category " + scriptCategory);
|
||||
writer.println("#@keybinding");
|
||||
writer.println("#@menupath");
|
||||
writer.println("#@toolbar");
|
||||
writer.println();
|
||||
writer.println("#TODO: Add script code here");
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IOException("Failed to create: " + scriptFile);
|
||||
}
|
||||
|
||||
// Refresh project to it sees the new script
|
||||
scriptFile.getProject().refreshLocal(IResource.DEPTH_INFINITE, monitor);
|
||||
|
||||
return scriptFile;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/* ###
|
||||
* 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.utils;
|
||||
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.jdt.core.*;
|
||||
import org.eclipse.jdt.ui.JavaElementLabelProvider;
|
||||
import org.eclipse.jdt.ui.StandardJavaElementContentProvider;
|
||||
import org.eclipse.jface.viewers.Viewer;
|
||||
import org.eclipse.jface.viewers.ViewerFilter;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
|
||||
import org.eclipse.ui.dialogs.ISelectionStatusValidator;
|
||||
|
||||
import ghidradev.Activator;
|
||||
|
||||
/**
|
||||
* A dialog that lets you choose a Java package fragment root, which is basically a top-level
|
||||
* source folder.
|
||||
*/
|
||||
public class PackageFragmentRootSelectionDialog extends ElementTreeSelectionDialog {
|
||||
|
||||
/**
|
||||
* Creates a new package fragment root selection dialog.
|
||||
*
|
||||
* @param shell The parent shell for the dialog.
|
||||
* @param title The title of the dialog.
|
||||
* @param message The message of the dialog.
|
||||
* @param errorMessage the error message to display if an invalid selection is made.
|
||||
*/
|
||||
public PackageFragmentRootSelectionDialog(Shell shell, String title, String message,
|
||||
String errorMessage) {
|
||||
super(shell, new JavaElementLabelProvider(JavaElementLabelProvider.SHOW_DEFAULT),
|
||||
new StandardJavaElementContentProvider());
|
||||
setTitle(title);
|
||||
setMessage(message);
|
||||
setAllowMultiple(false);
|
||||
setInput(JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()));
|
||||
|
||||
setValidator(new ISelectionStatusValidator() {
|
||||
|
||||
@Override
|
||||
public IStatus validate(Object[] sel) {
|
||||
if (sel.length == 1 && sel[0] instanceof IPackageFragmentRoot) {
|
||||
return new Status(IStatus.OK, Activator.PLUGIN_ID, IStatus.OK, "", null);
|
||||
}
|
||||
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, IStatus.ERROR, errorMessage,
|
||||
null);
|
||||
}
|
||||
});
|
||||
|
||||
addFilter(new ViewerFilter() {
|
||||
@Override
|
||||
public boolean select(Viewer viewer, Object parentObject, Object element) {
|
||||
|
||||
if (element instanceof IPackageFragmentRoot) {
|
||||
IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) element;
|
||||
return !packageFragmentRoot.isArchive() && !packageFragmentRoot.isExternal();
|
||||
}
|
||||
|
||||
return element instanceof IJavaModel || element instanceof IJavaProject;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the selected package fragment root.
|
||||
*
|
||||
* @return The selected package fragment root. Could be null if there is not a valid
|
||||
* selection.
|
||||
*/
|
||||
public IPackageFragmentRoot getPackageFragmentRoot() {
|
||||
Object[] result = getResult();
|
||||
if (result.length == 1 && result[0] instanceof IPackageFragmentRoot) {
|
||||
return (IPackageFragmentRoot) result[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
/* ###
|
||||
* 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.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.OperationNotSupportedException;
|
||||
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
|
||||
/**
|
||||
* Utility methods for interacting with PyDev.
|
||||
*/
|
||||
public class PyDevUtils {
|
||||
|
||||
public final static String MIN_SUPPORTED_VERSION = "6.3.1";
|
||||
|
||||
/**
|
||||
* Checks to see if a supported version of PyDev is installed.
|
||||
*
|
||||
* @return True if a supported version of PyDev is installed; otherwise, false.
|
||||
*/
|
||||
public static boolean isSupportedPyDevInstalled() {
|
||||
try {
|
||||
if (PyDevUtilsInternal.isPyDevInstalled()) {
|
||||
// Make sure the installed version of PyDev is new enough to support the following
|
||||
// operation.
|
||||
getJython27InterpreterNames();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (OperationNotSupportedException | NoClassDefFoundError e) {
|
||||
// Fall through to return false
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of discovered Jython 2.7 interpreter names.
|
||||
*
|
||||
* @return a list of discovered Jython 2.7 interpreter names.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static List<String> getJython27InterpreterNames() throws OperationNotSupportedException {
|
||||
try {
|
||||
return PyDevUtilsInternal.getJython27InterpreterNames();
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given Jython interpreter to PyDev.
|
||||
*
|
||||
* @param interpreterName The name of the interpreter to add.
|
||||
* @param interpreterFile The interpreter file to add.
|
||||
* @param interpreterLibDir The interpreter library directory to add.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static void addJythonInterpreter(String interpreterName, File interpreterFile,
|
||||
File interpreterLibDir)
|
||||
throws OperationNotSupportedException {
|
||||
try {
|
||||
PyDevUtilsInternal.addJythonInterpreter(interpreterName, interpreterFile,
|
||||
interpreterLibDir);
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up Python for the given Java project.
|
||||
*
|
||||
* @param javaProject The Java project to enable Python for.
|
||||
* @param classpathEntries The classpath entries to add to the Python path.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* If this is null, Python support will be removed from the project.
|
||||
* @param monitor The progress monitor used during link.
|
||||
* @throws CoreException if there was an Eclipse-related problem with enabling Python for the
|
||||
* project.
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static void setupPythonForProject(IJavaProject javaProject,
|
||||
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
|
||||
IProgressMonitor monitor) throws CoreException, OperationNotSupportedException {
|
||||
try {
|
||||
PyDevUtilsInternal.setupPythonForProject(javaProject, classpathEntries,
|
||||
jythonInterpreterName, monitor);
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the PyDev remote debugger.
|
||||
*
|
||||
* @throws OperationNotSupportedException if PyDev is not installed or it does not support this
|
||||
* operation.
|
||||
*/
|
||||
public static void startPyDevRemoteDebugger() throws OperationNotSupportedException {
|
||||
try {
|
||||
PyDevUtilsInternal.startPyDevRemoteDebugger();
|
||||
}
|
||||
catch (NoClassDefFoundError | NoSuchMethodError e) {
|
||||
throw new OperationNotSupportedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PyDev Jython preference page ID.
|
||||
*
|
||||
* @return the PyDev Jython preference page ID.
|
||||
*/
|
||||
public static String getJythonPreferencePageId() {
|
||||
return "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPageJython";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets The PyDev source directory.
|
||||
*
|
||||
* @return The PyDev source directory, or null if it was not found.
|
||||
*/
|
||||
public static File getPyDevSrcDir() {
|
||||
String eclipsePath = Platform.getInstallLocation().getURL().getFile();
|
||||
File pluginsDir = new File(eclipsePath, "plugins");
|
||||
File[] pluginSubDirs = pluginsDir.listFiles(File::isDirectory);
|
||||
if (pluginSubDirs != null) {
|
||||
for (File dir : pluginSubDirs) {
|
||||
if (dir.getName().startsWith("org.python.pydev")) {
|
||||
File pysrcDir = new File(dir, "pysrc");
|
||||
if (pysrcDir.isDirectory()) {
|
||||
return pysrcDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/* ###
|
||||
* 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.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.python.pydev.ast.interpreter_managers.InterpreterInfo;
|
||||
import org.python.pydev.ast.interpreter_managers.InterpreterManagersAPI;
|
||||
import org.python.pydev.core.*;
|
||||
import org.python.pydev.plugin.nature.PythonNature;
|
||||
|
||||
import com.python.pydev.debug.remote.client_api.PydevRemoteDebuggerServer;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import utilities.util.ArrayUtilities;
|
||||
|
||||
/**
|
||||
* Utility methods for interacting with PyDev.
|
||||
* <p>
|
||||
* NOTE: Since PyDev may not be installed, all PyDev interactions must be done in this
|
||||
* class, and every method must throw {@link NoClassDefFoundError} so caller's can
|
||||
* handle PyDev being absent. People wanting to interact with PyDev should go through the
|
||||
* public-facing version of this class...{@link PyDevUtils}.
|
||||
*/
|
||||
class PyDevUtilsInternal {
|
||||
|
||||
/**
|
||||
* Checks to see if PyDev is installed.
|
||||
*
|
||||
* @return True if PyDev is installed; otherwise, false.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed.
|
||||
*/
|
||||
public static boolean isPyDevInstalled() throws NoClassDefFoundError {
|
||||
for (Bundle bundle : FrameworkUtil.getBundle(
|
||||
PyDevUtilsInternal.class).getBundleContext().getBundles()) {
|
||||
if (bundle.getSymbolicName().contains("pydev")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of discovered Jython 2.7 interpreter names.
|
||||
*
|
||||
* @return a list of discovered Jython 2.7 interpreter names.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static List<String> getJython27InterpreterNames()
|
||||
throws NoClassDefFoundError, NoSuchMethodError {
|
||||
|
||||
List<String> interpreters = new ArrayList<>();
|
||||
IInterpreterManager iMan = InterpreterManagersAPI.getJythonInterpreterManager(true);
|
||||
|
||||
for (IInterpreterInfo info : iMan.getInterpreterInfos()) {
|
||||
if (info.getInterpreterType() == IPythonNature.INTERPRETER_TYPE_JYTHON && info.getVersion().equals("2.7")) {
|
||||
interpreters.add(info.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return interpreters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given Jython interpreter to PyDev.
|
||||
*
|
||||
* @param interpreterName The name of the interpreter to add.
|
||||
* @param interpreterFile The interpreter to add.
|
||||
* @param interpreterLibDir The interpreter library directory to add.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static void addJythonInterpreter(String interpreterName, File interpreterFile,
|
||||
File interpreterLibDir) throws NoClassDefFoundError, NoSuchMethodError {
|
||||
IProgressMonitor monitor = new NullProgressMonitor();
|
||||
IInterpreterManager iMan = InterpreterManagersAPI.getJythonInterpreterManager(true);
|
||||
IInterpreterInfo iInfo =
|
||||
iMan.createInterpreterInfo(interpreterFile.getAbsolutePath(), monitor, false);
|
||||
iInfo.setName(interpreterName);
|
||||
if (iInfo instanceof InterpreterInfo) {
|
||||
InterpreterInfo info = (InterpreterInfo) iInfo;
|
||||
info.libs.add(interpreterLibDir.getAbsolutePath());
|
||||
info.libs.add(new File(interpreterLibDir, "site-packages").getAbsolutePath());
|
||||
}
|
||||
else {
|
||||
EclipseMessageUtils.error("Failed to add Jython Lib directory to python path");
|
||||
}
|
||||
iMan.setInfos(ArrayUtilities.copyAndAppend(iMan.getInterpreterInfos(), iInfo), null,
|
||||
monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up Python for the given Java project.
|
||||
*
|
||||
* @param javaProject The Java project to setup Python for.
|
||||
* @param classpathEntries The classpath entries to add to the Python path.
|
||||
* @param jythonInterpreterName The name of the Jython interpreter to use for Python support.
|
||||
* If this is null, Python support will be removed from the project.
|
||||
* @param monitor The progress monitor used during link.
|
||||
* @throws CoreException If there was an Eclipse-related problem with enabling Python for the project.
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static void setupPythonForProject(IJavaProject javaProject,
|
||||
List<IClasspathEntry> classpathEntries, String jythonInterpreterName,
|
||||
IProgressMonitor monitor)
|
||||
throws CoreException, NoClassDefFoundError, NoSuchMethodError {
|
||||
|
||||
PythonNature.removeNature(javaProject.getProject(), monitor);
|
||||
|
||||
if (jythonInterpreterName != null) {
|
||||
String libs = classpathEntries.stream().map(e -> e.getPath().toOSString()).collect(
|
||||
Collectors.joining("|"));
|
||||
PythonNature.addNature(javaProject.getProject(), monitor,
|
||||
IPythonNature.JYTHON_VERSION_2_7, null, libs, jythonInterpreterName, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the PyDev remote debugger.
|
||||
*
|
||||
* @throws NoClassDefFoundError if PyDev is not installed or it does not support this operation.
|
||||
* @throws NoSuchMethodError if PyDev is not installed or it does not support this operation.
|
||||
*/
|
||||
public static void startPyDevRemoteDebugger() throws NoClassDefFoundError, NoSuchMethodError {
|
||||
PydevRemoteDebuggerServer.startServer();
|
||||
}
|
||||
|
||||
private PyDevUtilsInternal() throws NoClassDefFoundError {
|
||||
// Prevent instantiation
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
/* ###
|
||||
* 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 java.util.Set;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.wizard.Wizard;
|
||||
import org.eclipse.ui.INewWizard;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.*;
|
||||
|
||||
/**
|
||||
* Wizard to create a new Ghidra module project.
|
||||
*/
|
||||
public class CreateGhidraModuleProjectWizard extends Wizard implements INewWizard {
|
||||
|
||||
private IWorkbench workbench;
|
||||
|
||||
private CreateGhidraProjectWizardPage projectPage;
|
||||
private ConfigureGhidraModuleProjectWizardPage projectConfigPage;
|
||||
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
|
||||
private EnablePythonWizardPage pythonPage;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra module project wizard.
|
||||
*/
|
||||
public CreateGhidraModuleProjectWizard() {
|
||||
setNeedsProgressMonitor(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(IWorkbench wb, IStructuredSelection selection) {
|
||||
workbench = wb;
|
||||
projectPage = new CreateGhidraProjectWizardPage();
|
||||
projectConfigPage = new ConfigureGhidraModuleProjectWizardPage();
|
||||
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
|
||||
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addPages() {
|
||||
addPage(projectPage);
|
||||
addPage(projectConfigPage);
|
||||
addPage(ghidraInstallationPage);
|
||||
addPage(pythonPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performFinish() {
|
||||
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
|
||||
String projectName = projectPage.getProjectName();
|
||||
boolean createRunConfig = projectPage.shouldCreateRunConfig();
|
||||
String runConfigMemory = projectPage.getRunConfigMemory();
|
||||
File projectDir = projectPage.getProjectDir();
|
||||
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
|
||||
Set<ModuleTemplateType> moduleTemplateTypes = projectConfigPage.getModuleTemplateTypes();
|
||||
try {
|
||||
getContainer().run(true, false,
|
||||
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
|
||||
runConfigMemory, moduleTemplateTypes, jythonInterpreterName, monitor));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
error(showWizardErrorDialog(getShell(), e), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Ghidra module project.
|
||||
*
|
||||
* @param ghidraInstallDir The Ghidra installation directory to use.
|
||||
* @param projectName The name of the project to create.
|
||||
* @param projectDir The project's directory.
|
||||
* @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 moduleTemplateTypes The desired module template types.
|
||||
* @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 create(File ghidraInstallDir, String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory,
|
||||
Set<ModuleTemplateType> moduleTemplateTypes, String jythonInterpreterName,
|
||||
IProgressMonitor monitor) throws InvocationTargetException {
|
||||
try {
|
||||
info("Creating " + projectName + " at " + projectDir);
|
||||
monitor.beginTask("Creating " + projectName, 3);
|
||||
|
||||
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
|
||||
monitor.worked(1);
|
||||
|
||||
IJavaProject javaProject =
|
||||
GhidraModuleUtils.createGhidraModuleProject(projectName, projectDir,
|
||||
createRunConfig, runConfigMemory, ghidraLayout, jythonInterpreterName, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
IFile sourceFile = GhidraModuleUtils.configureModuleSource(javaProject,
|
||||
projectDir, ghidraLayout, moduleTemplateTypes, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
if (sourceFile != null) {
|
||||
EclipseMessageUtils.displayInEditor(sourceFile, workbench);
|
||||
}
|
||||
|
||||
info("Finished creating " + projectName);
|
||||
}
|
||||
catch (IOException | ParseException | CoreException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/* ###
|
||||
* 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.INewWizard;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.*;
|
||||
|
||||
/**
|
||||
* Wizard to create a new Ghidra scripting project.
|
||||
*/
|
||||
public class CreateGhidraScriptProjectWizard extends Wizard implements INewWizard {
|
||||
|
||||
private CreateGhidraProjectWizardPage projectPage;
|
||||
private ConfigureGhidraScriptProjectWizardPage projectConfigPage;
|
||||
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
|
||||
private EnablePythonWizardPage pythonPage;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra scripting project wizard.
|
||||
*/
|
||||
public CreateGhidraScriptProjectWizard() {
|
||||
setNeedsProgressMonitor(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(IWorkbench wb, IStructuredSelection selection) {
|
||||
projectPage = new CreateGhidraProjectWizardPage("GhidraScripts");
|
||||
projectConfigPage = new ConfigureGhidraScriptProjectWizardPage();
|
||||
ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
|
||||
pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPages() {
|
||||
addPage(projectPage);
|
||||
addPage(projectConfigPage);
|
||||
addPage(ghidraInstallationPage);
|
||||
addPage(pythonPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performFinish() {
|
||||
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
|
||||
String projectName = projectPage.getProjectName();
|
||||
File projectDir = projectPage.getProjectDir();
|
||||
boolean createRunConfig = projectPage.shouldCreateRunConfig();
|
||||
String runConfigMemory = projectPage.getRunConfigMemory();
|
||||
boolean linkUserScripts = projectConfigPage.shouldLinkUsersScripts();
|
||||
boolean linkSystemScripts = projectConfigPage.shouldLinkSystemScripts();
|
||||
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
|
||||
try {
|
||||
getContainer().run(true, false,
|
||||
monitor -> create(ghidraInstallDir, projectName, projectDir, createRunConfig,
|
||||
runConfigMemory, linkUserScripts, linkSystemScripts, jythonInterpreterName,
|
||||
monitor));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
error(showWizardErrorDialog(getShell(), e), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Ghidra script project.
|
||||
*
|
||||
* @param ghidraInstallDir The Ghidra installation directory to use.
|
||||
* @param projectName The name of the project to create.
|
||||
* @param projectDir The project's directory.
|
||||
* @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 linkUserScripts Whether or not to link in the user scripts directory.
|
||||
* @param linkSystemScripts Whether or not to link in the system scripts directories.
|
||||
* @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 create(File ghidraInstallDir, String projectName, File projectDir,
|
||||
boolean createRunConfig, String runConfigMemory, boolean linkUserScripts,
|
||||
boolean linkSystemScripts, String jythonInterpreterName, IProgressMonitor monitor)
|
||||
throws InvocationTargetException {
|
||||
try {
|
||||
info("Creating " + projectName + " at " + projectDir);
|
||||
monitor.beginTask("Creating " + projectName, 2);
|
||||
|
||||
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
|
||||
monitor.worked(1);
|
||||
|
||||
GhidraScriptUtils.createGhidraScriptProject(projectName, projectDir, createRunConfig,
|
||||
runConfigMemory, linkUserScripts, linkSystemScripts, ghidraLayout,
|
||||
jythonInterpreterName, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
info("Finished creating " + projectName);
|
||||
}
|
||||
catch (IOException | ParseException | CoreException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/* ###
|
||||
* 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.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.jdt.core.IPackageFragmentRoot;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.wizard.Wizard;
|
||||
import org.eclipse.ui.INewWizard;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.CreateGhidraScriptWizardPage;
|
||||
|
||||
/**
|
||||
* Wizard to create a new Ghidra script file in an existing project.
|
||||
*/
|
||||
public class CreateGhidraScriptWizard extends Wizard implements INewWizard {
|
||||
|
||||
private IWorkbench workbench;
|
||||
|
||||
private CreateGhidraScriptWizardPage scriptPage;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra script wizard.
|
||||
*/
|
||||
public CreateGhidraScriptWizard() {
|
||||
setNeedsProgressMonitor(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(IWorkbench wb, IStructuredSelection selection) {
|
||||
workbench = wb;
|
||||
|
||||
IPackageFragmentRoot selectedPackageFragmentRoot = null;
|
||||
Object firstElement = selection.getFirstElement();
|
||||
if (firstElement instanceof IPackageFragmentRoot) {
|
||||
selectedPackageFragmentRoot = (IPackageFragmentRoot) firstElement;
|
||||
}
|
||||
scriptPage = new CreateGhidraScriptWizardPage(selectedPackageFragmentRoot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPages() {
|
||||
addPage(scriptPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performFinish() {
|
||||
IFolder scriptFolder = scriptPage.getScriptFolder();
|
||||
String scriptName = scriptPage.getScriptName();
|
||||
String scriptAuthor = scriptPage.getScriptAuthor();
|
||||
String scriptCategory = scriptPage.getScriptCategory();
|
||||
String[] scriptDescription = scriptPage.getScriptDescription();
|
||||
try {
|
||||
getContainer().run(true, false, monitor -> create(scriptFolder, scriptName,
|
||||
scriptAuthor, scriptCategory, scriptDescription, monitor));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
error(showWizardErrorDialog(getShell(), e), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Ghidra script.
|
||||
*
|
||||
* @param scriptFolder The folder to create the script in.
|
||||
* @param scriptName The name of the script to create.
|
||||
* @param scriptAuthor The script's author.
|
||||
* @param scriptCategory The script's category.
|
||||
* @param scriptDescription The script's description lines.
|
||||
* @param monitor The monitor to use during project/script creation.
|
||||
* @throws InvocationTargetException if an error occurred during project/script creation.
|
||||
*/
|
||||
private void create(IFolder scriptFolder, String scriptName, String scriptAuthor,
|
||||
String scriptCategory, String[] scriptDescription, IProgressMonitor monitor)
|
||||
throws InvocationTargetException {
|
||||
try {
|
||||
info("Creating " + scriptName + " in " + scriptFolder.toString());
|
||||
monitor.beginTask("Creating " + scriptName + " in " + scriptFolder.toString(), 1);
|
||||
|
||||
IFile scriptFile = GhidraScriptUtils.createGhidraScript(scriptFolder, scriptName,
|
||||
scriptAuthor, scriptCategory, scriptDescription, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
if (scriptFile != null) {
|
||||
EclipseMessageUtils.displayInEditor(scriptFile, workbench);
|
||||
}
|
||||
|
||||
info("Finished creating " + scriptName);
|
||||
}
|
||||
catch (IOException | CoreException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/* ###
|
||||
* 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 java.util.*;
|
||||
|
||||
import org.eclipse.buildship.core.GradleDistribution;
|
||||
import org.eclipse.buildship.core.internal.CorePlugin;
|
||||
import org.eclipse.buildship.core.internal.launch.GradleLaunchConfigurationManager;
|
||||
import org.eclipse.buildship.core.internal.launch.GradleRunConfigurationAttributes;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
import org.eclipse.debug.core.ILaunchManager;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.wizard.Wizard;
|
||||
import org.eclipse.ui.INewWizard;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.ChooseGhidraModuleProjectWizardPage;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.ConfigureGradleWizardPage;
|
||||
|
||||
/**
|
||||
* Wizard for exporting a Ghidra module project to a releasable extension zip bundle.
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class ExportGhidraModuleWizard extends Wizard implements INewWizard {
|
||||
|
||||
private ChooseGhidraModuleProjectWizardPage projectPage;
|
||||
private ConfigureGradleWizardPage gradlePage;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra module export wizard.
|
||||
*/
|
||||
public ExportGhidraModuleWizard() {
|
||||
setNeedsProgressMonitor(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(IWorkbench wb, IStructuredSelection selection) {
|
||||
projectPage = new ChooseGhidraModuleProjectWizardPage(
|
||||
GhidraProjectUtils.getSelectedProject(selection));
|
||||
gradlePage = new ConfigureGradleWizardPage(projectPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPages() {
|
||||
addPage(projectPage);
|
||||
addPage(gradlePage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performFinish() {
|
||||
IJavaProject javaProject = projectPage.getGhidraModuleProject();
|
||||
GradleDistribution gradleDist = gradlePage.getGradleDistribution();
|
||||
try {
|
||||
getContainer().run(true, false, monitor -> export(javaProject, gradleDist, monitor));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
error(showWizardErrorDialog(getShell(), e), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the given Ghidra module project to an extension zip file.
|
||||
*
|
||||
* @param javaProject The Ghidra module project to export.
|
||||
* @param gradleDistribution The Gradle distribution to use to export.
|
||||
* @param monitor The monitor to use during export.
|
||||
* @throws InvocationTargetException if an error occurred during export.
|
||||
*/
|
||||
private void export(IJavaProject javaProject, GradleDistribution gradleDistribution,
|
||||
IProgressMonitor monitor)
|
||||
throws InvocationTargetException {
|
||||
try {
|
||||
IProject project = javaProject.getProject();
|
||||
info("Exporting " + project.getName());
|
||||
monitor.beginTask("Exporting " + project.getName(), 2);
|
||||
|
||||
// Get path to Ghidra installation directory
|
||||
String ghidraInstallDirPath = project.getFolder(
|
||||
GhidraProjectUtils.GHIDRA_FOLDER_NAME).getRawLocation().toOSString();
|
||||
|
||||
// Get project's java. Gradle should use the same version.
|
||||
// TODO: It's more correct to get this from the project's classpath, since Ghidra's
|
||||
// saved Java home can change from launch to launch.
|
||||
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(new File(ghidraInstallDirPath));
|
||||
File javaHomeDir = new JavaConfig(
|
||||
ghidraLayout.getApplicationInstallationDir().getFile(false)).getSavedJavaHome();
|
||||
if(javaHomeDir == null) {
|
||||
throw new IOException("Failed to get the Java home associated with the project. " +
|
||||
"Perform a \"Link Ghidra\" operation on the project and try again.");
|
||||
}
|
||||
|
||||
// Setup the Gradle build attributes
|
||||
List<String> tasks = new ArrayList<>();
|
||||
String workingDir = project.getRawLocation().toOSString();
|
||||
String gradleDist = gradleDistribution.toString();
|
||||
String gradleUserHome = "";
|
||||
String javaHome = javaHomeDir.getAbsolutePath();
|
||||
List<String> jvmArgs = new ArrayList<>();
|
||||
List<String> gradleArgs =
|
||||
Arrays.asList(new String[] { "-PGHIDRA_INSTALL_DIR=" + ghidraInstallDirPath });
|
||||
boolean showExecutionView = false;
|
||||
boolean showConsoleView = true;
|
||||
boolean overrideWorkspaceSettings = true;
|
||||
boolean isOffline = true;
|
||||
boolean isBuildScansEnabled = false;
|
||||
GradleRunConfigurationAttributes gradleAttributes =
|
||||
new GradleRunConfigurationAttributes(tasks, workingDir, gradleDist, gradleUserHome,
|
||||
javaHome, jvmArgs, gradleArgs, showExecutionView, showConsoleView,
|
||||
overrideWorkspaceSettings, isOffline, isBuildScansEnabled);
|
||||
|
||||
// Launch Gradle
|
||||
GradleLaunchConfigurationManager lm = CorePlugin.gradleLaunchConfigurationManager();
|
||||
ILaunchConfiguration lc = lm.getOrCreateRunConfiguration(gradleAttributes);
|
||||
lc.launch(ILaunchManager.RUN_MODE, monitor, true, true);
|
||||
lc.delete();
|
||||
|
||||
monitor.worked(1);
|
||||
|
||||
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
info("Finished exporting " + project.getName());
|
||||
}
|
||||
catch (IOException | ParseException | CoreException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/* ###
|
||||
* 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.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.wizard.Wizard;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
import ghidradev.ghidraprojectcreator.wizards.pages.*;
|
||||
|
||||
/**
|
||||
* Wizard for linking a Java project's classpath and external links to a Ghidra installation directory.
|
||||
*/
|
||||
public class LinkGhidraWizard extends Wizard {
|
||||
|
||||
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
|
||||
private ChooseJavaProjectWizardPage projectPage;
|
||||
private EnablePythonWizardPage pythonPage;
|
||||
|
||||
public LinkGhidraWizard(ISelection selection) {
|
||||
setNeedsProgressMonitor(true);
|
||||
this.ghidraInstallationPage = new ChooseGhidraInstallationWizardPage();
|
||||
this.projectPage =
|
||||
new ChooseJavaProjectWizardPage((GhidraProjectUtils.getSelectedProject(selection)));
|
||||
this.pythonPage = new EnablePythonWizardPage(ghidraInstallationPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPages() {
|
||||
addPage(ghidraInstallationPage);
|
||||
addPage(projectPage);
|
||||
addPage(pythonPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performFinish() {
|
||||
File ghidraInstallDir = ghidraInstallationPage.getGhidraInstallDir();
|
||||
IJavaProject javaProject = projectPage.getJavaProject();
|
||||
String jythonInterpreterName = pythonPage.getJythonInterpreterName();
|
||||
try {
|
||||
getContainer().run(true, false,
|
||||
monitor -> link(ghidraInstallDir, javaProject, jythonInterpreterName, monitor));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
error(showWizardErrorDialog(getShell(), e), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a Java project's classpath and external links to a Ghidra installation directory.
|
||||
*
|
||||
* @param ghidraInstallDir The Ghidra installation directory to use.
|
||||
* @param javaProject The Java project to link.
|
||||
* @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 link.
|
||||
* @throws InvocationTargetException if an error occurred during link.
|
||||
*/
|
||||
private void link(File ghidraInstallDir, IJavaProject javaProject, String jythonInterpreterName,
|
||||
IProgressMonitor monitor) throws InvocationTargetException {
|
||||
IProject project = javaProject.getProject();
|
||||
try {
|
||||
info("Linking " + project.getName());
|
||||
monitor.beginTask("Linking " + project.getName(), 2);
|
||||
|
||||
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraInstallDir);
|
||||
JavaConfig javaConfig =
|
||||
new JavaConfig(ghidraLayout.getApplicationInstallationDir().getFile(false));
|
||||
GhidraProjectUtils.linkGhidraToProject(javaProject, ghidraLayout, javaConfig,
|
||||
jythonInterpreterName, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
|
||||
monitor.worked(1);
|
||||
|
||||
info("Finished linking " + project.getName());
|
||||
}
|
||||
catch (IOException | ParseException | CoreException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/* ###
|
||||
* 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 java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.eclipse.jface.preference.PreferenceDialog;
|
||||
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 org.eclipse.ui.dialogs.PreferencesUtil;
|
||||
|
||||
import ghidra.launch.JavaConfig;
|
||||
import ghidra.launch.JavaFinder.JavaFilter;
|
||||
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferencePage;
|
||||
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
|
||||
|
||||
/**
|
||||
* A wizard page that lets the user choose a Ghidra installation.
|
||||
*/
|
||||
public class ChooseGhidraInstallationWizardPage extends WizardPage {
|
||||
|
||||
private Combo ghidraInstallDirCombo;
|
||||
private Button addGhidraInstallDirButton;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra installation wizard page.
|
||||
*/
|
||||
public ChooseGhidraInstallationWizardPage() {
|
||||
super("ChooseGhidraInstallationWizardPage");
|
||||
setTitle("Choose a Ghidra Installation");
|
||||
setDescription("Choose the Ghidra installation to use.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(3, false));
|
||||
|
||||
Label ghidraInstallDirLabel = new Label(container, SWT.NULL);
|
||||
ghidraInstallDirLabel.setText("Ghidra installation:");
|
||||
ghidraInstallDirCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
|
||||
ghidraInstallDirCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
populateGhidraInstallationCombo();
|
||||
ghidraInstallDirCombo.addModifyListener(evt -> validate());
|
||||
ghidraInstallDirCombo.setToolTipText("The wizard requires a Ghidra installation to be " +
|
||||
"selected. Click the + button to add or manage Ghidra installations.");
|
||||
addGhidraInstallDirButton = new Button(container, SWT.BUTTON1);
|
||||
addGhidraInstallDirButton.setText("+");
|
||||
addGhidraInstallDirButton.setToolTipText("Adds/manages Ghidra installations.");
|
||||
addGhidraInstallDirButton.addListener(SWT.Selection, evt -> {
|
||||
PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null,
|
||||
GhidraProjectCreatorPreferencePage.class.getName(), null, null);
|
||||
dialog.open();
|
||||
populateGhidraInstallationCombo();
|
||||
validate();
|
||||
});
|
||||
|
||||
validate();
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Ghidra installation directory.
|
||||
*
|
||||
* @return The Ghidra installation directory.
|
||||
* Could be null if unspecified, however, the page will not be valid until the Ghidra
|
||||
* installation directory is valid, so it should never be null when called by other
|
||||
* classes.
|
||||
*/
|
||||
public File getGhidraInstallDir() {
|
||||
return new File(ghidraInstallDirCombo.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;
|
||||
|
||||
if (GhidraProjectCreatorPreferences.getGhidraInstallDirs().isEmpty()) {
|
||||
message = "No Ghidra installations found. Click the + button to add one.";
|
||||
}
|
||||
else if (ghidraInstallDirCombo.getText().isEmpty()) {
|
||||
message = "Ghidra installation must be specified.";
|
||||
}
|
||||
else {
|
||||
try {
|
||||
File ghidraInstallDir = new File(ghidraInstallDirCombo.getText());
|
||||
GhidraProjectCreatorPreferencePage.validateGhidraInstallation(ghidraInstallDir);
|
||||
try {
|
||||
JavaConfig javaConfig = new JavaConfig(ghidraInstallDir);
|
||||
if (!javaConfig.isSupportedJavaHomeDir(javaConfig.getSavedJavaHome(),
|
||||
JavaFilter.JDK_ONLY)) {
|
||||
message = "A supported JDK is not associated with this Ghidra " +
|
||||
"installation. Please run this Ghidra and try again.";
|
||||
}
|
||||
}
|
||||
catch (ParseException | IOException e) {
|
||||
message = "Failed to determine Ghidra's JDK version. " + e.getMessage();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
message = e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(message == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the Ghidra installations combo box with the values found in preferences.
|
||||
*/
|
||||
private void populateGhidraInstallationCombo() {
|
||||
ghidraInstallDirCombo.removeAll();
|
||||
for (File dir : GhidraProjectCreatorPreferences.getGhidraInstallDirs()) {
|
||||
ghidraInstallDirCombo.add(dir.getAbsolutePath());
|
||||
if (dir.equals(GhidraProjectCreatorPreferences.getGhidraDefaultInstallDir())) {
|
||||
ghidraInstallDirCombo.setText(dir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/* ###
|
||||
* 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 org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
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.utils.GhidraProjectUtils;
|
||||
|
||||
/**
|
||||
* A wizard page that lets the user choose an open Ghidra module project.
|
||||
*/
|
||||
public class ChooseGhidraModuleProjectWizardPage extends WizardPage {
|
||||
|
||||
private IProject selectedProject;
|
||||
private Combo projectCombo;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra module project chooser wizard page.
|
||||
*
|
||||
* @param selectedProject The currently selected project in the project explorer.
|
||||
*/
|
||||
public ChooseGhidraModuleProjectWizardPage(IProject selectedProject) {
|
||||
super("ChooseGhidraModuleProjectWizardPage");
|
||||
setTitle("Choose Ghidra Module Project");
|
||||
setDescription("Choose an open Ghidra module project.");
|
||||
this.selectedProject = selectedProject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(2, false));
|
||||
|
||||
Label projectNameLabel = new Label(container, SWT.NULL);
|
||||
projectNameLabel.setText("Ghida module project:");
|
||||
projectCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
|
||||
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
|
||||
projectCombo.setLayoutData(gd);
|
||||
projectCombo.addModifyListener(evt -> validate());
|
||||
for (IJavaProject javaProject : GhidraProjectUtils.getGhidraProjects()) {
|
||||
if (GhidraProjectUtils.isGhidraModuleProject(javaProject.getProject())) {
|
||||
IProject project = javaProject.getProject();
|
||||
projectCombo.add(project.getName());
|
||||
if (project.equals(selectedProject)) {
|
||||
projectCombo.setText(project.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validate();
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Java project.
|
||||
*
|
||||
* @return The chosen Ghidra module project. Only valid when the page is complete.
|
||||
* Could be null if unspecified, however, the page will not be valid until the project
|
||||
* is valid, so it should never be null when called by other classes.
|
||||
*/
|
||||
public IJavaProject getGhidraModuleProject() {
|
||||
return GhidraProjectUtils.getGhidraProject(projectCombo.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;
|
||||
String projectName = projectCombo.getText();
|
||||
|
||||
if (projectName.isEmpty()) {
|
||||
message = "Project name must be specified";
|
||||
}
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(message == null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/* ###
|
||||
* 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 org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
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.utils.GhidraProjectUtils;
|
||||
|
||||
/**
|
||||
* A wizard page that lets the user choose an open Java project.
|
||||
*/
|
||||
public class ChooseJavaProjectWizardPage extends WizardPage {
|
||||
|
||||
private IProject selectedProject;
|
||||
private Combo projectCombo;
|
||||
|
||||
/**
|
||||
* Creates a new Java project chooser wizard page.
|
||||
*
|
||||
* @param selectedProject The currently selected project in the project explorer.
|
||||
*/
|
||||
public ChooseJavaProjectWizardPage(IProject selectedProject) {
|
||||
super("ChooseJavaProjectWizardPage");
|
||||
setTitle("Choose Java Project");
|
||||
setDescription("Choose an existing Java project.");
|
||||
this.selectedProject = selectedProject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(2, false));
|
||||
|
||||
Label projectNameLabel = new Label(container, SWT.NULL);
|
||||
projectNameLabel.setText("Java project:");
|
||||
projectCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
|
||||
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
|
||||
projectCombo.setLayoutData(gd);
|
||||
projectCombo.addModifyListener(evt -> validate());
|
||||
for (IJavaProject javaProject : GhidraProjectUtils.getJavaProjects()) {
|
||||
IProject project = javaProject.getProject();
|
||||
projectCombo.add(project.getName());
|
||||
if (project.equals(selectedProject)) {
|
||||
projectCombo.setText(project.getName());
|
||||
}
|
||||
}
|
||||
|
||||
validate();
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Java project.
|
||||
*
|
||||
* @return The chosen Java project. Only valid when the page is complete.
|
||||
* Could be null if unspecified, however, the page will not be valid until the project
|
||||
* is valid, so it should never be null when called by other classes.
|
||||
*/
|
||||
public IJavaProject getJavaProject() {
|
||||
for (IJavaProject javaProject : GhidraProjectUtils.getJavaProjects()) {
|
||||
IProject project = javaProject.getProject();
|
||||
if (project.getName().equals(projectCombo.getText())) {
|
||||
return javaProject;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
String projectName = projectCombo.getText();
|
||||
|
||||
if (projectName.isEmpty()) {
|
||||
message = "Existing Java project must be specified";
|
||||
}
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(message == null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/* ###
|
||||
* 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.util.*;
|
||||
|
||||
import org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.layout.RowLayout;
|
||||
import org.eclipse.swt.widgets.*;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraModuleUtils.ModuleTemplateType;
|
||||
|
||||
/**
|
||||
* A wizard page that lets the user configure a new Ghidra module project.
|
||||
*/
|
||||
public class ConfigureGhidraModuleProjectWizardPage extends WizardPage {
|
||||
|
||||
private Map<Button, ModuleTemplateType> moduleTemplateCheckboxMap;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra module project configuration wizard page.
|
||||
*/
|
||||
public ConfigureGhidraModuleProjectWizardPage() {
|
||||
super("ConfigureGhidraModuleProjectWizardPage");
|
||||
setTitle("Configure Ghidra Module Project");
|
||||
setDescription("Configure a new Ghidra module project.");
|
||||
|
||||
moduleTemplateCheckboxMap = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(2, false));
|
||||
|
||||
Label moduleTemplateLabel = new Label(container, SWT.NULL);
|
||||
moduleTemplateLabel.setText("Module template:");
|
||||
Group moduleTemplateGroup = new Group(container, SWT.SHADOW_ETCHED_OUT);
|
||||
moduleTemplateGroup.setLayout(new RowLayout(SWT.HORIZONTAL));
|
||||
|
||||
SelectionListener selectionListener = new SelectionListener() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
}
|
||||
};
|
||||
|
||||
for (ModuleTemplateType moduleTemplateType : ModuleTemplateType.values()) {
|
||||
Button checkboxButton = new Button(moduleTemplateGroup, SWT.CHECK);
|
||||
checkboxButton.setSelection(true);
|
||||
checkboxButton.setText(moduleTemplateType.getName());
|
||||
checkboxButton.setToolTipText(moduleTemplateType.getDescription());
|
||||
checkboxButton.addSelectionListener(selectionListener);
|
||||
moduleTemplateCheckboxMap.put(checkboxButton, moduleTemplateType);
|
||||
|
||||
}
|
||||
|
||||
validate();
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the selected module template types.
|
||||
*
|
||||
* @return The selected module template types.
|
||||
*/
|
||||
public Set<ModuleTemplateType> getModuleTemplateTypes() {
|
||||
Set<ModuleTemplateType> moduleTemplateTypes = new HashSet<>();
|
||||
for (Button checkboxButton : moduleTemplateCheckboxMap.keySet()) {
|
||||
if (checkboxButton.isEnabled() && checkboxButton.getSelection()) {
|
||||
moduleTemplateTypes.add(moduleTemplateCheckboxMap.get(checkboxButton));
|
||||
}
|
||||
}
|
||||
return moduleTemplateTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
setErrorMessage(null);
|
||||
setPageComplete(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/* ###
|
||||
* 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 org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraScriptUtils;
|
||||
|
||||
public class ConfigureGhidraScriptProjectWizardPage extends WizardPage {
|
||||
|
||||
private Button userScriptsCheckboxButton;
|
||||
private Button systemScriptsCheckboxButton;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra script project configuration wizard page.
|
||||
*/
|
||||
public ConfigureGhidraScriptProjectWizardPage() {
|
||||
super("ConfigureGhidraScriptProjectWizardPage");
|
||||
setTitle("Configure Ghidra Script Project");
|
||||
setDescription("Configure a new Ghidra script project.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(1, false));
|
||||
|
||||
userScriptsCheckboxButton = new Button(container, SWT.CHECK);
|
||||
userScriptsCheckboxButton.setText(
|
||||
"Link user home script directory (" + GhidraScriptUtils.userScriptsDir + ")");
|
||||
userScriptsCheckboxButton.setToolTipText("Automatically links Ghidra's default user home " +
|
||||
"script directory to the new project. This is recommended.");
|
||||
userScriptsCheckboxButton.setSelection(true);
|
||||
|
||||
systemScriptsCheckboxButton = new Button(container, SWT.CHECK);
|
||||
systemScriptsCheckboxButton.setText("Link Ghidra installation script directories");
|
||||
systemScriptsCheckboxButton.setToolTipText("Automatically links any script directories " +
|
||||
"found in the Ghidra installation to the new project, which serve as good examples.");
|
||||
systemScriptsCheckboxButton.setSelection(true);
|
||||
|
||||
setErrorMessage(null);
|
||||
setPageComplete(true);
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the user scripts directory should be linked into the project.
|
||||
*
|
||||
* @return True if the user scripts directory should be linked into the project; otherwise, false.
|
||||
*/
|
||||
public boolean shouldLinkUsersScripts() {
|
||||
return userScriptsCheckboxButton.getSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the system scripts directory should be linked into the project.
|
||||
*
|
||||
* @return True if the system scripts directory should be linked into the project; otherwise, false.
|
||||
*/
|
||||
public boolean shouldLinkSystemScripts() {
|
||||
return systemScriptsCheckboxButton.getSelection();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
/* ###
|
||||
* 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 java.io.IOException;
|
||||
|
||||
import org.eclipse.buildship.core.*;
|
||||
import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.*;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.framework.ApplicationProperties;
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
|
||||
public class ConfigureGradleWizardPage extends WizardPage {
|
||||
|
||||
private Button gradleWrapperChoiceButton;
|
||||
private Button gradleLocalChoiceButton;
|
||||
private Text gradleLocalDirText;
|
||||
private Button gradlLocalDirButton;
|
||||
|
||||
private ChooseGhidraModuleProjectWizardPage projectPage;
|
||||
private String gradleVersion;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new Gradle configuration wizard page.
|
||||
*
|
||||
* @param projectPage A {@link ChooseGhidraModuleProjectWizardPage} to get the project from.
|
||||
*/
|
||||
public ConfigureGradleWizardPage(ChooseGhidraModuleProjectWizardPage projectPage) {
|
||||
super("ConfigureGradleWizardPage");
|
||||
setTitle("Configure Gradle");
|
||||
setDescription("Configure Gradle.");
|
||||
this.projectPage = projectPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(4, false));
|
||||
|
||||
SelectionListener selectionListener = new SelectionListener() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
}
|
||||
};
|
||||
|
||||
// Local Gradle
|
||||
String gradleLocalTooltip = "Use a local installation of Gradle. For best results, " +
|
||||
"ensure that the version of this local Gradle matches the version specified on this wizard page's description.";
|
||||
gradleLocalChoiceButton = new Button(container, SWT.RADIO);
|
||||
gradleLocalChoiceButton.addSelectionListener(selectionListener);
|
||||
gradleLocalChoiceButton.setToolTipText(gradleLocalTooltip);
|
||||
Label gradleLocalDirLabel = new Label(container, SWT.NULL);
|
||||
gradleLocalDirLabel.setText("Local installation directory:");
|
||||
gradleLocalDirText = new Text(container, SWT.BORDER | SWT.SINGLE);
|
||||
gradleLocalDirText.setToolTipText(gradleLocalTooltip);
|
||||
gradleLocalDirText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
gradleLocalDirText.addModifyListener(evt -> validate());
|
||||
gradlLocalDirButton = new Button(container, SWT.BUTTON1);
|
||||
gradlLocalDirButton.setText("...");
|
||||
gradlLocalDirButton.addListener(SWT.Selection, evt -> {
|
||||
DirectoryDialog dialog = new DirectoryDialog(container.getShell());
|
||||
String path = dialog.open();
|
||||
if (path != null) {
|
||||
gradleLocalDirText.setText(path);
|
||||
}
|
||||
validate();
|
||||
});
|
||||
|
||||
// Gradle Wrapper
|
||||
String gradleWrapperTooltip = "Use the Gradle Wrapper, which will automatically download " +
|
||||
"the correct version of Gradle to use from the Internet.";
|
||||
gradleWrapperChoiceButton = new Button(container, SWT.RADIO);
|
||||
gradleWrapperChoiceButton.addSelectionListener(selectionListener);
|
||||
gradleWrapperChoiceButton.setToolTipText(gradleWrapperTooltip);
|
||||
Label gradleWrapperDirLabel = new Label(container, SWT.NULL);
|
||||
gradleWrapperDirLabel.setText("Gradle Wrapper");
|
||||
Label internetLabel = new Label(container, SWT.NONE);
|
||||
internetLabel.setText("INTERNET CONNECTION REQUIRED");
|
||||
internetLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));
|
||||
internetLabel.setToolTipText(gradleWrapperTooltip);
|
||||
new Label(container, SWT.NONE).setText(""); // empty grid cell
|
||||
|
||||
// Set default value from preferences
|
||||
GradleDistribution lastGradleDistribution =
|
||||
GhidraProjectCreatorPreferences.getGhidraLastGradleDistribution();
|
||||
if (lastGradleDistribution instanceof LocalGradleDistribution) {
|
||||
gradleLocalChoiceButton.setSelection(true);
|
||||
LocalGradleDistribution localGradleDistribution =
|
||||
(LocalGradleDistribution) lastGradleDistribution;
|
||||
if (localGradleDistribution.getLocation() != null) {
|
||||
gradleLocalDirText.setText(localGradleDistribution.getLocation().getAbsolutePath());
|
||||
}
|
||||
}
|
||||
else if (lastGradleDistribution instanceof WrapperGradleDistribution ||
|
||||
lastGradleDistribution instanceof FixedVersionGradleDistribution) {
|
||||
gradleWrapperChoiceButton.setSelection(true);
|
||||
}
|
||||
else {
|
||||
gradleLocalChoiceButton.setSelection(true);
|
||||
}
|
||||
|
||||
validate();
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
super.setVisible(visible);
|
||||
|
||||
// Update the page's description to reference the version of Gradle that should be used.
|
||||
if (visible) {
|
||||
IProject project = projectPage.getGhidraModuleProject().getProject();
|
||||
IFolder ghidraFolder = project.getFolder(GhidraProjectUtils.GHIDRA_FOLDER_NAME);
|
||||
File ghidraDir = ghidraFolder.getRawLocation().toFile();
|
||||
try {
|
||||
GhidraApplicationLayout ghidraLayout = new GhidraApplicationLayout(ghidraDir);
|
||||
ApplicationProperties props = ghidraLayout.getApplicationProperties();
|
||||
gradleVersion =
|
||||
props.getProperty(ApplicationProperties.APPLICATION_GRADLE_VERSION_PROPERTY);
|
||||
if (gradleVersion != null && !gradleVersion.isEmpty()) {
|
||||
setDescription("Configure Gradle. Version " + gradleVersion + " is expected.");
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
EclipseMessageUtils.error("Unable to determine required Gradle version.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Gradle distribution to use.
|
||||
*
|
||||
* @return The gradle distribution to use.
|
||||
*/
|
||||
public GradleDistribution getGradleDistribution() {
|
||||
if (gradleLocalChoiceButton.getSelection()) {
|
||||
return GradleDistribution.forLocalInstallation(new File(gradleLocalDirText.getText()));
|
||||
}
|
||||
else if (gradleVersion != null) {
|
||||
return GradleDistribution.forVersion(gradleVersion);
|
||||
}
|
||||
else {
|
||||
// This case should only happen if someone deleted the Gradle version from
|
||||
// application.properties. In that case, we'll just try the standard wrapper and hope
|
||||
// for the best.
|
||||
return GradleDistribution.fromBuild();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
if (gradleLocalChoiceButton.getSelection()) {
|
||||
String path = gradleLocalDirText.getText().trim();
|
||||
File dir = new File(path);
|
||||
if (path.isEmpty()) {
|
||||
message = "Path to local Gradle installation must be specified.";
|
||||
}
|
||||
else if (!dir.exists()) {
|
||||
message = "Path to local Gradle installation does not exist.";
|
||||
}
|
||||
else if (!dir.isDirectory()) {
|
||||
message = "Path to local Gradle installation is not a directory.";
|
||||
}
|
||||
else if (!new File(dir, "bin/gradle").exists()) {
|
||||
message =
|
||||
"Path to local Gradle installation appears invalid. Missing gradle binary.";
|
||||
}
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
GhidraProjectCreatorPreferences.setGhidraLastGradleDistribution(
|
||||
getGradleDistribution());
|
||||
}
|
||||
else {
|
||||
GhidraProjectCreatorPreferences.setGhidraLastGradleDistribution(null);
|
||||
}
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(message == null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/* ###
|
||||
* 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 java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
import org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.*;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.preferences.GhidraProjectCreatorPreferences;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraLaunchUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
|
||||
/**
|
||||
* A wizard page that lets the user create a new Ghidra project.
|
||||
*/
|
||||
public class CreateGhidraProjectWizardPage extends WizardPage {
|
||||
|
||||
private String suggestedProjectName;
|
||||
|
||||
private Text projectNameText;
|
||||
private Text projectRootDirText;
|
||||
private Button projectDirButton;
|
||||
private Button createRunConfigCheckboxButton;
|
||||
private Text runConfigMemoryText;
|
||||
|
||||
/**
|
||||
* Creates a Ghidra new project wizard page with the given suggested project name.
|
||||
*
|
||||
* @param suggestedProjectName The suggested project name.
|
||||
*/
|
||||
public CreateGhidraProjectWizardPage(String suggestedProjectName) {
|
||||
super("CreateGhidraProjectWizardPage");
|
||||
setTitle("Create Ghidra Project");
|
||||
setDescription("Create a new Ghidra project.");
|
||||
this.suggestedProjectName = suggestedProjectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Ghidra new project wizard page.
|
||||
*/
|
||||
public CreateGhidraProjectWizardPage() {
|
||||
this("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(3, false));
|
||||
|
||||
// Project name
|
||||
Label projectNameLabel = new Label(container, SWT.NULL);
|
||||
projectNameLabel.setText("Project name:");
|
||||
projectNameText = new Text(container, SWT.BORDER | SWT.SINGLE);
|
||||
projectNameText.setText(suggestedProjectName);
|
||||
projectNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
projectNameText.addModifyListener(evt -> validate());
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
// Create run configuration checkbox
|
||||
createRunConfigCheckboxButton = new Button(container, SWT.CHECK);
|
||||
createRunConfigCheckboxButton.setText("Create run configuration");
|
||||
createRunConfigCheckboxButton.setToolTipText("Automatically create a Ghidra run " +
|
||||
"configuration that can be used to launch and debug this project in Ghidra. Run " +
|
||||
"configurations can be created later and modified in the \"Run --> Run " +
|
||||
"Configurations\" menu.");
|
||||
createRunConfigCheckboxButton.setSelection(true);
|
||||
createRunConfigCheckboxButton.addSelectionListener(new SelectionListener() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
}
|
||||
});
|
||||
new Label(container, SWT.NONE).setText(""); // empty grid cell
|
||||
new Label(container, SWT.NONE).setText(""); // empty grid cell
|
||||
|
||||
// Run configuration memory
|
||||
Label runConfigMemoryLabel = new Label(container, SWT.NULL);
|
||||
String runConfigMemoryToolTip = "The maximum Java heap size (-Xmx) Ghidra will use when " +
|
||||
"launched with the created run configuration (ex: 4G, 1500m, etc). If left blank, " +
|
||||
"Java's default heap size will be used, which is determined by your system's memory " +
|
||||
"capacity.";
|
||||
runConfigMemoryLabel.setText("Run configuration memory:");
|
||||
runConfigMemoryLabel.setToolTipText(runConfigMemoryToolTip);
|
||||
runConfigMemoryText = new Text(container, SWT.BORDER | SWT.SINGLE);
|
||||
runConfigMemoryText.addModifyListener(evt -> validate());
|
||||
runConfigMemoryText.setToolTipText(runConfigMemoryToolTip);
|
||||
new Label(container, SWT.NONE).setText(""); // empty grid cell
|
||||
|
||||
validate();
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the project.
|
||||
*
|
||||
* @return The name of the project.
|
||||
*/
|
||||
public String getProjectName() {
|
||||
return projectNameText.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public File getProjectDir() {
|
||||
if (projectNameText.getText().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (projectRootDirText.getText().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new File(projectRootDirText.getText(), getProjectName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see whether or not a run configuration for the new project should be automatically
|
||||
* created.
|
||||
*
|
||||
* @return True if a run configuration for the new project should be automatically created;
|
||||
* otherwise, false.
|
||||
*/
|
||||
public boolean shouldCreateRunConfig() {
|
||||
return createRunConfigCheckboxButton.getSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the run configuration's desired memory.
|
||||
*
|
||||
* @return The run configuration's desired memory. Could be null if unspecified.
|
||||
*/
|
||||
public String getRunConfigMemory() {
|
||||
if (!createRunConfigCheckboxButton.getSelection() ||
|
||||
runConfigMemoryText.getText().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return runConfigMemoryText.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() {
|
||||
|
||||
final String BAD = GhidraProjectUtils.ILLEGAL_FILENAME_CHARS;
|
||||
final String BAD_START = GhidraProjectUtils.ILLEGAL_FILENAME_START_CHARS;
|
||||
|
||||
runConfigMemoryText.setEnabled(createRunConfigCheckboxButton.getSelection());
|
||||
runConfigMemoryText.setMessage(runConfigMemoryText.isEnabled() ? "default" : "");
|
||||
|
||||
String message = null;
|
||||
String projectName = getProjectName();
|
||||
File projectDir = getProjectDir();
|
||||
boolean launchConfigExists = false;
|
||||
try {
|
||||
ILaunchConfiguration launchConfig = GhidraLaunchUtils.getLaunchConfig(projectName);
|
||||
if (launchConfig != null) {
|
||||
String launchConfigTypeId = launchConfig.getType().getIdentifier();
|
||||
launchConfigExists = !launchConfigTypeId.equals(GhidraLaunchUtils.GUI_LAUNCH) &&
|
||||
!launchConfigTypeId.equals(GhidraLaunchUtils.HEADLESS_LAUNCH);
|
||||
}
|
||||
}
|
||||
catch (CoreException e) {
|
||||
// launchConfigExists can remain false, we'll just overwrite the config
|
||||
}
|
||||
|
||||
if (projectName.isEmpty()) {
|
||||
message = "Project name must be specified";
|
||||
}
|
||||
else if ((BAD_START + BAD).chars().anyMatch(ch -> projectName.charAt(0) == ch)) {
|
||||
message = "Project name cannot start with an invalid character:\n " + BAD_START + BAD;
|
||||
}
|
||||
else if (BAD.chars().anyMatch(ch -> projectName.indexOf(ch) != -1)) {
|
||||
message = "Project name cannot contain invalid characters:\n " + BAD;
|
||||
}
|
||||
else if (projectDir == null) {
|
||||
message = "Project root directory must be specified";
|
||||
}
|
||||
else if (projectDir.exists()) {
|
||||
message = "Project already exists at: " + projectDir.getAbsolutePath();
|
||||
}
|
||||
else if (ResourcesPlugin.getWorkspace().getRoot().getProject(projectName).exists()) {
|
||||
message = "\"" + projectName + "\" project already exists in workspace";
|
||||
}
|
||||
else if (shouldCreateRunConfig() && launchConfigExists) {
|
||||
message = "Run configuration \"" + projectName +
|
||||
"\" project already exists (check run configuration filters)";
|
||||
}
|
||||
else if (createRunConfigCheckboxButton.getSelection() &&
|
||||
!runConfigMemoryText.getText().isEmpty() &&
|
||||
!Pattern.matches("^\\d+[KkMmGg]$", runConfigMemoryText.getText())) {
|
||||
message = "Invalid run configuration memory value. Value must match JVM -Xmx flag" +
|
||||
" syntax (4G, 1500m, etc).";
|
||||
}
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(message == null);
|
||||
if (message == null) {
|
||||
GhidraProjectCreatorPreferences.setGhidraLastProjectRootPath(
|
||||
projectRootDirText.getText());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
/* ###
|
||||
* 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 org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.eclipse.jdt.core.IPackageFragmentRoot;
|
||||
import org.eclipse.jface.window.Window;
|
||||
import org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.*;
|
||||
import org.eclipse.swt.widgets.*;
|
||||
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.PackageFragmentRootSelectionDialog;
|
||||
|
||||
/**
|
||||
* A wizard page that lets the user create a new Ghidra script.
|
||||
*/
|
||||
public class CreateGhidraScriptWizardPage extends WizardPage {
|
||||
|
||||
private IPackageFragmentRoot selecttion;
|
||||
|
||||
private Text scriptFolderText;
|
||||
private Button scriptFolderButton;
|
||||
private Text scriptNameText;
|
||||
private Button javaRadioButton;
|
||||
private Button pythonRadioButton;
|
||||
private Text scriptAuthorText;
|
||||
private Text scriptCategoryText;
|
||||
private Text scriptDescriptionText;
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra script creation wizard page.
|
||||
*/
|
||||
public CreateGhidraScriptWizardPage() {
|
||||
super("CreateGhidraScriptWizardPage");
|
||||
setTitle("Create Ghidra Script");
|
||||
setDescription("Create a new Ghidra script.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra script creation wizard page.
|
||||
*
|
||||
* @param selectedPackageFragmentRoot The current selection in the project explorer.
|
||||
* Could be null if nothing is selected.
|
||||
*/
|
||||
public CreateGhidraScriptWizardPage(IPackageFragmentRoot selectedPackageFragmentRoot) {
|
||||
this();
|
||||
this.selecttion = selectedPackageFragmentRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(3, false));
|
||||
|
||||
// Source folder
|
||||
Label sourceFolderLabel = new Label(container, SWT.NULL);
|
||||
sourceFolderLabel.setText("Script folder:");
|
||||
scriptFolderText = new Text(container, SWT.BORDER | SWT.SINGLE);
|
||||
scriptFolderText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
scriptFolderText.setEditable(false);
|
||||
scriptFolderText.setText(selecttion != null ? selecttion.getPath().toString() : "");
|
||||
scriptFolderText.addModifyListener(evt -> validate());
|
||||
scriptFolderButton = new Button(container, SWT.BUTTON1);
|
||||
scriptFolderButton.setText("...");
|
||||
scriptFolderButton.addListener(SWT.Selection, evt -> {
|
||||
PackageFragmentRootSelectionDialog selectionDialog =
|
||||
new PackageFragmentRootSelectionDialog(getShell(), "Script Folder Selection",
|
||||
"Choose a script folder:", "Select script folder");
|
||||
if (selectionDialog.open() == Window.OK) {
|
||||
IFolder scriptFolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(
|
||||
selectionDialog.getPackageFragmentRoot().getPath());
|
||||
if (scriptFolder != null) {
|
||||
scriptFolderText.setText(scriptFolder.getFullPath().toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Script name
|
||||
Label scriptNameLabel = new Label(container, SWT.NULL);
|
||||
scriptNameLabel.setText("Script name:");
|
||||
scriptNameText = new Text(container, SWT.BORDER | SWT.SINGLE);
|
||||
scriptNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
scriptNameText.addModifyListener(evt -> validate());
|
||||
new Label(container, SWT.NONE).setText(""); // empty grid cell
|
||||
|
||||
// Script type
|
||||
Label scriptTypeLabel = new Label(container, SWT.NULL);
|
||||
scriptTypeLabel.setText("Script type:");
|
||||
Group scriptTypeGroup = new Group(container, SWT.SHADOW_ETCHED_OUT);
|
||||
scriptTypeGroup.setLayout(new RowLayout(SWT.HORIZONTAL));
|
||||
javaRadioButton = new Button(scriptTypeGroup, SWT.RADIO);
|
||||
javaRadioButton.setSelection(true);
|
||||
javaRadioButton.setText("Java");
|
||||
pythonRadioButton = new Button(scriptTypeGroup, SWT.RADIO);
|
||||
pythonRadioButton.setSelection(false);
|
||||
pythonRadioButton.setText("Python");
|
||||
new Label(container, SWT.NONE).setText(""); // empty grid cell
|
||||
|
||||
// Script author
|
||||
Label scriptAuthorLabel = new Label(container, SWT.NULL);
|
||||
scriptAuthorLabel.setText("Script author:");
|
||||
scriptAuthorText = new Text(container, SWT.BORDER | SWT.SINGLE);
|
||||
scriptAuthorText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
scriptAuthorText.addModifyListener(evt -> validate());
|
||||
new Label(container, SWT.NONE).setText(""); // empty grid cell
|
||||
|
||||
// Script category
|
||||
Label scriptCategoryLabel = new Label(container, SWT.NULL);
|
||||
scriptCategoryLabel.setText("Script category:");
|
||||
scriptCategoryText = new Text(container, SWT.BORDER | SWT.SINGLE);
|
||||
scriptCategoryText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
scriptCategoryText.addModifyListener(evt -> validate());
|
||||
new Label(container, SWT.NONE).setText(""); // empty grid cell
|
||||
|
||||
// Script description
|
||||
Label scriptDescriptionLabel = new Label(container, SWT.NULL);
|
||||
scriptDescriptionLabel.setText("Script description:");
|
||||
scriptDescriptionText =
|
||||
new Text(container, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | SWT.BORDER);
|
||||
scriptDescriptionText.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
scriptDescriptionText.addModifyListener(evt -> validate());
|
||||
new Label(container, SWT.NONE).setText(""); // empty grid cell
|
||||
|
||||
validate();
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the script's target source folder. This is where the script will get created.
|
||||
*
|
||||
* @return The script's target source folder. Could be null if unspecified, however, the page
|
||||
* will not be valid until the source folder is valid, so it should never be null when called
|
||||
* by other classes.
|
||||
*/
|
||||
public IFolder getScriptFolder() {
|
||||
if (!scriptFolderText.getText().isEmpty()) {
|
||||
try {
|
||||
IPath path = new Path(scriptFolderText.getText());
|
||||
return ResourcesPlugin.getWorkspace().getRoot().getFolder(path);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// Fall through to return null
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the script to create (including extension).
|
||||
*
|
||||
* @return The name of the script to create (including extension).
|
||||
* Could be empty if unspecified, however, the page will not be valid until the script
|
||||
* name is valid, so it should never be empty when called by other classes.
|
||||
*/
|
||||
public String getScriptName() {
|
||||
return scriptNameText.getText() + (javaRadioButton.getSelection() ? ".java" : ".py");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the script author.
|
||||
*
|
||||
* @return The script author. This is an optional field, so it could be empty.
|
||||
*/
|
||||
public String getScriptAuthor() {
|
||||
return scriptAuthorText.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the script category.
|
||||
*
|
||||
* @return The script category. This is an optional field, so it could be empty.
|
||||
*/
|
||||
public String getScriptCategory() {
|
||||
return scriptCategoryText.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the script description as an array of lines.
|
||||
*
|
||||
* @return The script description as an array of lines. This is an optional field,
|
||||
* so it could be empty.
|
||||
*/
|
||||
public String[] getScriptDescription() {
|
||||
return scriptDescriptionText.getText().split("\\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
final String BAD = GhidraProjectUtils.ILLEGAL_FILENAME_CHARS;
|
||||
final String BAD_START = GhidraProjectUtils.ILLEGAL_FILENAME_START_CHARS;
|
||||
|
||||
|
||||
String message = null;
|
||||
IFolder scriptFolder = getScriptFolder();
|
||||
String scriptName = scriptNameText.getText();
|
||||
|
||||
|
||||
if (scriptFolder == null) {
|
||||
message = "Script folder must be specified";
|
||||
}
|
||||
else if (!scriptFolder.exists()) {
|
||||
message = "Script folder does not exist";
|
||||
}
|
||||
else if (scriptName.isEmpty()) {
|
||||
message = "Script name must be specified";
|
||||
}
|
||||
else if ((BAD_START + BAD).chars().anyMatch(ch -> scriptName.charAt(0) == ch)) {
|
||||
message = "Script name cannot start with an invalid character:\n " + BAD_START + BAD;
|
||||
}
|
||||
else if (BAD.chars().anyMatch(ch -> scriptName.indexOf(ch) != -1)) {
|
||||
message = "Script name cannot contain invalid characters:\n " + BAD;
|
||||
}
|
||||
else if (scriptFolder.getFile(getScriptName()).exists()) {
|
||||
message = "Script already exists";
|
||||
}
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(message == null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
/* ###
|
||||
* 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 java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.OperationNotSupportedException;
|
||||
|
||||
import org.eclipse.jface.preference.PreferenceDialog;
|
||||
import org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.*;
|
||||
import org.eclipse.ui.dialogs.PreferencesUtil;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.PyDevUtils;
|
||||
|
||||
/**
|
||||
* A wizard page that lets the user enable python for their project.
|
||||
*/
|
||||
public class EnablePythonWizardPage extends WizardPage {
|
||||
|
||||
private ChooseGhidraInstallationWizardPage ghidraInstallationPage;
|
||||
private Button enablePythonCheckboxButton;
|
||||
private Combo jythonCombo;
|
||||
private Button addJythonButton;
|
||||
|
||||
/**
|
||||
* Creates a new Python enablement wizard page.
|
||||
*
|
||||
* @param ghidraInstallationPage Ghidra installation wizard page.
|
||||
*/
|
||||
public EnablePythonWizardPage(ChooseGhidraInstallationWizardPage ghidraInstallationPage) {
|
||||
super("EnablePythonWizardPage");
|
||||
setTitle("Python Support");
|
||||
setDescription("Enable Python support for your project (requires PyDev plugin).");
|
||||
this.ghidraInstallationPage = ghidraInstallationPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createControl(Composite parent) {
|
||||
|
||||
Composite container = new Composite(parent, SWT.NULL);
|
||||
container.setLayout(new GridLayout(3, false));
|
||||
|
||||
// Enable Python checkbox.
|
||||
enablePythonCheckboxButton = new Button(container, SWT.CHECK);
|
||||
enablePythonCheckboxButton.setText("Enable Python");
|
||||
enablePythonCheckboxButton.setToolTipText("Enables Python support using the PyDev " +
|
||||
"Eclipse plugin. Requires PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
|
||||
" or later.");
|
||||
enablePythonCheckboxButton.setSelection(PyDevUtils.isSupportedPyDevInstalled());
|
||||
enablePythonCheckboxButton.addSelectionListener(new SelectionListener() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent evt) {
|
||||
validate();
|
||||
}
|
||||
});
|
||||
new Label(container, SWT.NONE).setText(""); // filler
|
||||
new Label(container, SWT.NONE).setText(""); // filler
|
||||
|
||||
// Jython interpreter combo box
|
||||
Label jythonLabel = new Label(container, SWT.NULL);
|
||||
jythonLabel.setText("Jython interpreter:");
|
||||
jythonCombo = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
|
||||
jythonCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
jythonCombo.setToolTipText("The wizard requires a Jython interpreter to be " +
|
||||
"selected. Click the + button to add or manage Jython interpreters.");
|
||||
populateJythonCombo();
|
||||
jythonCombo.addModifyListener(evt -> validate());
|
||||
|
||||
// Jython interpreter add button
|
||||
addJythonButton = new Button(container, SWT.BUTTON1);
|
||||
addJythonButton.setText("+");
|
||||
addJythonButton.setToolTipText("Adds/manages Jython interpreters.");
|
||||
addJythonButton.addListener(SWT.Selection, evt -> {
|
||||
try {
|
||||
if (PyDevUtils.getJython27InterpreterNames().isEmpty()) {
|
||||
File ghidraDir = ghidraInstallationPage.getGhidraInstallDir();
|
||||
File jythonFile = findJythonInterpreter(ghidraDir);
|
||||
File jythonLib = findJythonLibrary(ghidraDir);
|
||||
if (jythonFile != null) {
|
||||
if (EclipseMessageUtils.showQuestionDialog("Jython Found",
|
||||
"A Jython interpreter was found bundled with Ghidra. " +
|
||||
"Would you like to use it as your interpreter?")) {
|
||||
PyDevUtils.addJythonInterpreter("jython_" + ghidraDir.getName(),
|
||||
jythonFile, jythonLib);
|
||||
populateJythonCombo();
|
||||
validate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationNotSupportedException e) {
|
||||
// Fall through to show PyDev's Jython preference page
|
||||
}
|
||||
PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null,
|
||||
PyDevUtils.getJythonPreferencePageId(), null, null);
|
||||
dialog.open();
|
||||
populateJythonCombo();
|
||||
validate();
|
||||
});
|
||||
|
||||
validate();
|
||||
setControl(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not Python should be enabled.
|
||||
*
|
||||
* @return True if python should be enabled; otherwise, false.
|
||||
*/
|
||||
public boolean shouldEnablePython() {
|
||||
return enablePythonCheckboxButton.getSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the Jython interpreter to use.
|
||||
*
|
||||
* @return The name of the Jython interpreter to use. Could be null of Python isn't
|
||||
* enabled.
|
||||
*/
|
||||
public String getJythonInterpreterName() {
|
||||
if (enablePythonCheckboxButton.getSelection()) {
|
||||
return jythonCombo.getText();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
boolean pyDevInstalled = PyDevUtils.isSupportedPyDevInstalled();
|
||||
boolean pyDevEnabled = enablePythonCheckboxButton.getSelection();
|
||||
boolean comboEnabled = pyDevInstalled && pyDevEnabled;
|
||||
|
||||
if (pyDevEnabled) {
|
||||
if (!pyDevInstalled) {
|
||||
message = "PyDev version " + PyDevUtils.MIN_SUPPORTED_VERSION +
|
||||
" or later is not installed.";
|
||||
}
|
||||
else {
|
||||
try {
|
||||
List<String> interpreters = PyDevUtils.getJython27InterpreterNames();
|
||||
if (interpreters.isEmpty()) {
|
||||
message = "No Jython interpreters found. Click the + button to add one.";
|
||||
}
|
||||
}
|
||||
catch (OperationNotSupportedException e) {
|
||||
message = "PyDev version is not supported.";
|
||||
comboEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jythonCombo.setEnabled(comboEnabled);
|
||||
addJythonButton.setEnabled(comboEnabled);
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(message == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the Jython combo box with discovered Jython names.
|
||||
*/
|
||||
private void populateJythonCombo() {
|
||||
jythonCombo.removeAll();
|
||||
try {
|
||||
for (String jythonName : PyDevUtils.getJython27InterpreterNames()) {
|
||||
jythonCombo.add(jythonName);
|
||||
}
|
||||
}
|
||||
catch (OperationNotSupportedException e) {
|
||||
// Nothing to do. Combo should and will be empty.
|
||||
}
|
||||
if (jythonCombo.getItemCount() > 0) {
|
||||
jythonCombo.select(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find's a Jython interpreter file in the given Ghidra installation directory.
|
||||
*
|
||||
* @param ghidraInstallDir The Ghidra installation directory to search.
|
||||
* @return A Jython interpreter file from the given Ghidra installation directory, or
|
||||
* null if one could not be found.
|
||||
*/
|
||||
private File findJythonInterpreter(File ghidraInstallDir) {
|
||||
if (ghidraInstallDir == null || !ghidraInstallDir.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Files.find(ghidraInstallDir.toPath(), 10, (path, attrs) -> {
|
||||
String name = path.getFileName().toString();
|
||||
return attrs.isRegularFile() && name.startsWith("jython") && name.endsWith(".jar");
|
||||
}).map(p -> p.toFile()).findFirst().orElse(null);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find's a Jython library directory in the given Ghidra installation directory.
|
||||
*
|
||||
* @param ghidraInstallDir The Ghidra installation directory to search.
|
||||
* @return A Jython library directory from the given Ghidra installation directory, or
|
||||
* null if one could not be found.
|
||||
*/
|
||||
private File findJythonLibrary(File ghidraInstallDir) {
|
||||
if (ghidraInstallDir == null || !ghidraInstallDir.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Files.find(ghidraInstallDir.toPath(), 10, (path, attrs) -> {
|
||||
String name = path.getFileName().toString();
|
||||
String parentName = path.getParent().getFileName().toString();
|
||||
return attrs.isDirectory() && name.equals("Lib") && parentName.startsWith("jython");
|
||||
}).map(p -> p.toFile()).findFirst().orElse(null);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
/* ###
|
||||
* 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.ghidrascripteditor;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
import org.eclipse.core.resources.*;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jface.viewers.LabelProvider;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.window.Window;
|
||||
import org.eclipse.jface.wizard.WizardDialog;
|
||||
import org.eclipse.ui.*;
|
||||
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
|
||||
import org.eclipse.ui.ide.IDE;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidraprojectcreator.utils.GhidraProjectUtils;
|
||||
import ghidradev.ghidraprojectcreator.wizards.CreateGhidraScriptProjectWizard;
|
||||
|
||||
public class OpenFileRunnable implements Runnable {
|
||||
private String filePath;
|
||||
|
||||
public OpenFileRunnable(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
List<IFile> projectFiles = findMatchingFiles(filePath);
|
||||
IFile[] filesToOpen = maybePromptUserForFilesToOpen(projectFiles);
|
||||
openFiles(filesToOpen);
|
||||
}
|
||||
|
||||
private void openFiles(IFile[] userFileChoices) {
|
||||
if (userFileChoices == null) {
|
||||
return; // user cancelled
|
||||
}
|
||||
|
||||
for (IFile file : userFileChoices) {
|
||||
openFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
private void openFile(IFile file) {
|
||||
IWorkbenchPage page = EclipseMessageUtils.getWorkbenchPage();
|
||||
try {
|
||||
IDE.openEditor(page, file);
|
||||
}
|
||||
catch (PartInitException e) {
|
||||
EclipseMessageUtils.showErrorDialog("Unable to Open Script",
|
||||
"Couldn't open editor for " + filePath);
|
||||
}
|
||||
page.getWorkbenchWindow().getShell().forceActive();
|
||||
}
|
||||
|
||||
private IFile[] maybePromptUserForFilesToOpen(List<IFile> projectFiles) {
|
||||
if (projectFiles.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (projectFiles.size() == 1) {
|
||||
return new IFile[] { projectFiles.get(0) };
|
||||
}
|
||||
|
||||
// look for any project ending in 'scripts' and assume that is the preferred project
|
||||
for (IFile iFile : projectFiles) {
|
||||
IProject project = iFile.getProject();
|
||||
String projectName = project.getName();
|
||||
if (projectName.toLowerCase().endsWith("scripts")) {
|
||||
return new IFile[] { projectFiles.get(0) };
|
||||
}
|
||||
}
|
||||
|
||||
IWorkbenchPage page = EclipseMessageUtils.getWorkbenchPage();
|
||||
ElementListSelectionDialog dialog = new ElementListSelectionDialog(
|
||||
page.getWorkbenchWindow().getShell(), new LabelProvider());
|
||||
dialog.setTitle("Choose a File");
|
||||
List<DisplayableIFile> displayableFiles = formatStrings(projectFiles);
|
||||
dialog.setMultipleSelection(true);
|
||||
dialog.setElements(displayableFiles.toArray(new DisplayableIFile[displayableFiles.size()]));
|
||||
dialog.setMessage("Select a file to open");
|
||||
|
||||
Dimension size = calculatePreferredSizeInCharacters(displayableFiles);
|
||||
dialog.setSize(size.width, size.height);
|
||||
|
||||
dialog.open();
|
||||
Object[] results = dialog.getResult();
|
||||
IFile[] resultFiles = new IFile[results.length];
|
||||
for (int i = 0; i < results.length; i++) {
|
||||
resultFiles[i] = ((DisplayableIFile) results[i]).getFile();
|
||||
}
|
||||
return resultFiles;
|
||||
}
|
||||
|
||||
private List<IFile> findMatchingFiles(String path) {
|
||||
Collection<IJavaProject> javaProjects = GhidraProjectUtils.getGhidraProjects();
|
||||
List<IFile> projectFiles = findMatchingFilesInProjects(path, javaProjects);
|
||||
if (projectFiles.isEmpty()) {
|
||||
try {
|
||||
for (IJavaProject javaProject : javaProjects) {
|
||||
javaProject.getProject().refreshLocal(IResource.DEPTH_INFINITE,
|
||||
new NullProgressMonitor());
|
||||
}
|
||||
}
|
||||
catch (CoreException e1) {
|
||||
EclipseMessageUtils.showErrorDialog("Unable to Open Script",
|
||||
"Unexpected Exception refreshing project");
|
||||
return new ArrayList<IFile>();
|
||||
}
|
||||
}
|
||||
|
||||
projectFiles = findMatchingFilesInProjects(path, javaProjects);
|
||||
if (projectFiles.isEmpty()) {
|
||||
boolean createProject = EclipseMessageUtils.showConfirmDialog("Unable to Open Script",
|
||||
"File does not exist in any Eclipse project in your workspace.\n\n" +
|
||||
"Would you like to create a new Ghidra Scripting project?");
|
||||
if (createProject) {
|
||||
INewWizard wizard = new CreateGhidraScriptProjectWizard();
|
||||
wizard.init(PlatformUI.getWorkbench(), new StructuredSelection());
|
||||
WizardDialog dialog = new WizardDialog(
|
||||
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), wizard);
|
||||
dialog.setBlockOnOpen(true);
|
||||
if (dialog.open() == Window.OK) {
|
||||
return findMatchingFilesInProjects(path,
|
||||
GhidraProjectUtils.getGhidraProjects());
|
||||
}
|
||||
}
|
||||
return new ArrayList<IFile>();
|
||||
}
|
||||
return projectFiles;
|
||||
}
|
||||
|
||||
private Dimension calculatePreferredSizeInCharacters(List<DisplayableIFile> files) {
|
||||
int width = 0;
|
||||
int height = 10;
|
||||
for (DisplayableIFile file : files) {
|
||||
String displayString = file.getDisplayString();
|
||||
width = Math.max(width, displayString.length());
|
||||
}
|
||||
width = Math.min(width + 7, 100);
|
||||
height = Math.min(height, files.size() + 3);
|
||||
return new Dimension(width, height);
|
||||
}
|
||||
|
||||
private List<IFile> findMatchingFilesInProjects(String pathString,
|
||||
Collection<IJavaProject> javaProjects) {
|
||||
List<IFile> files = new ArrayList<IFile>();
|
||||
for (IJavaProject javaProject : javaProjects) {
|
||||
IProject project = javaProject.getProject();
|
||||
if (!project.isOpen()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
IPath path = findPathFromFolder(pathString, project);
|
||||
if (path != null) {
|
||||
IFile file = project.getFile(path);
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Unexpected exception accessing project members", e);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private IPath findPathFromFolder(String pathString, IResource resource) throws CoreException {
|
||||
if (!(resource instanceof IContainer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IContainer container = (IContainer) resource;
|
||||
IResource[] members = container.members();
|
||||
for (IResource member : members) {
|
||||
IPath location = member.getLocation();
|
||||
|
||||
// compare as files in order to bypass path separator issues
|
||||
File fileForPath = new File(pathString);
|
||||
File fileForLocation = location.toFile();
|
||||
if (fileForLocation.equals(fileForPath)) {
|
||||
return member.getProjectRelativePath();
|
||||
}
|
||||
|
||||
IPath pathFromFolder = findPathFromFolder(pathString, member);
|
||||
if (pathFromFolder != null) {
|
||||
return pathFromFolder;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<DisplayableIFile> formatStrings(List<IFile> projectFiles) {
|
||||
List<DisplayableIFile> list = new ArrayList<DisplayableIFile>();
|
||||
for (IFile file : projectFiles) {
|
||||
list.add(new DisplayableIFile(file));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
private class DisplayableIFile {
|
||||
private final IFile file;
|
||||
private final String displayString;
|
||||
|
||||
private DisplayableIFile(IFile file) {
|
||||
this.file = file;
|
||||
|
||||
String format = "";
|
||||
String[] strings = file.toString().split("/");
|
||||
for (int i = 1; i < strings.length - 1; i++) {
|
||||
format += strings[i] + "/";
|
||||
}
|
||||
displayString =
|
||||
format.substring(0, format.length() - 1) + " - " + strings[strings.length - 1];
|
||||
}
|
||||
|
||||
IFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
String getDisplayString() {
|
||||
return displayString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getDisplayString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/* ###
|
||||
* 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.ghidrascripteditor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
import ghidradev.*;
|
||||
import ghidradev.ghidrascripteditor.preferences.GhidraScriptEditorPreferences;
|
||||
|
||||
/**
|
||||
* High level driver for initializing the Ghidra Script Editor subcomponent. Should get called
|
||||
* by the startup extension. This subcomponent is responsible for receiving a script from Ghidra's
|
||||
* script manager to Eclipse over a socket.
|
||||
*
|
||||
* @see GhidraDevStartup
|
||||
*/
|
||||
public class ScriptEditorInitializer {
|
||||
|
||||
private static ServerSocket serverSocket;
|
||||
|
||||
/**
|
||||
* Initializes the Ghidra Script Editor subcomponent. Nothing in the package should be
|
||||
* used until this initialization happens. Should be called during Eclipse startup.
|
||||
*
|
||||
* @param firstTimeConsent True if the user has just consented to opening ports; otherwise,
|
||||
* false.
|
||||
* @see GhidraDevStartup
|
||||
*/
|
||||
public static void init(boolean firstTimeConsent) {
|
||||
if (firstTimeConsent) {
|
||||
GhidraScriptEditorPreferences.setScriptEditorEnabled(true);
|
||||
}
|
||||
listen(GhidraScriptEditorPreferences.getScriptEditorPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for socket connections on the given port. If there is a problem listening,
|
||||
* a popup is displayed for the user.
|
||||
*
|
||||
* @param port The port to listen on. If the port is -1, this method doesn't do anything.
|
||||
*/
|
||||
private static void listen(int port) {
|
||||
|
||||
if (!GhidraScriptEditorPreferences.isScriptEditorEnabled()) {
|
||||
EclipseMessageUtils.info(
|
||||
Activator.PLUGIN_ID + " Script Editor port listening is disabled in preferences.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (port == -1) {
|
||||
EclipseMessageUtils.info(Activator.PLUGIN_ID +
|
||||
" Script Editor port listening is disabled, port not set in preferences.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
serverSocket = new ServerSocket(port);
|
||||
EclipseMessageUtils.info(
|
||||
Activator.PLUGIN_ID + " Script Editor is listening on port " + port);
|
||||
Activator.getDefault().registerCloseable(serverSocket);
|
||||
}
|
||||
catch (IOException e) {
|
||||
EclipseMessageUtils.showErrorDialog(Activator.PLUGIN_ID + " Script Editor",
|
||||
"Failed to listen for connections on port " + port +
|
||||
". The Script Editor features will be disabled until a valid port is selected in preferences.");
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(new SocketSetupRunnable(serverSocket)).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the script editor preferences change.
|
||||
*
|
||||
* @param enabledWasChanged True if the enablement was changed to a new value.
|
||||
* @param portWasChanged True if the port preferences was changed to a new value.
|
||||
*/
|
||||
public static void notifyPreferencesChanged(boolean enabledWasChanged, boolean portWasChanged) {
|
||||
if (!enabledWasChanged && !portWasChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the old server socket. Its job is done.
|
||||
try {
|
||||
if (serverSocket != null) {
|
||||
serverSocket.close();
|
||||
Activator.getDefault().unregisterCloseable(serverSocket);
|
||||
serverSocket = null;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Oh well, we tried. This port probably won't work next time they pick it.
|
||||
}
|
||||
|
||||
// Listen on the new port
|
||||
listen(GhidraScriptEditorPreferences.getScriptEditorPort());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/* ###
|
||||
* 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.ghidrascripteditor;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
|
||||
/**
|
||||
* Code intended to run in a new thread that accepts client connections on the given
|
||||
* server socket, and opens the requested file in an editor.
|
||||
*/
|
||||
public class SocketSetupRunnable implements Runnable {
|
||||
|
||||
private ServerSocket serverSocket = null;
|
||||
|
||||
/**
|
||||
* Creates a new runnable.
|
||||
*
|
||||
* @param serverSocket The server socket that will be accepting client connections.
|
||||
*/
|
||||
public SocketSetupRunnable(ServerSocket serverSocket) {
|
||||
this.serverSocket = serverSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!serverSocket.isClosed()) {
|
||||
try (Socket socket = serverSocket.accept();
|
||||
BufferedReader input =
|
||||
new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
PrintWriter output = new PrintWriter(socket.getOutputStream())) {
|
||||
String line;
|
||||
while ((line = input.readLine()) != null) {
|
||||
String command = line.substring(0, line.indexOf('_'));
|
||||
if (command.equals("open")) {
|
||||
openInEditor(line.substring(line.indexOf('_') + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Socket was closed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given file in an editor.
|
||||
*
|
||||
* @param path The path to the file to open.
|
||||
*/
|
||||
private void openInEditor(String path) {
|
||||
File fileToOpen = new File(path);
|
||||
if (fileToOpen.exists() && fileToOpen.isFile()) {
|
||||
Display.getDefault().asyncExec(new OpenFileRunnable(path));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/* ###
|
||||
* 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.ghidrascripteditor.preferences;
|
||||
|
||||
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
|
||||
import ghidradev.Activator;
|
||||
|
||||
/**
|
||||
* Class used to initialize default preference values.
|
||||
*/
|
||||
public class GhidraScriptEditorPreferenceInitializer extends AbstractPreferenceInitializer {
|
||||
|
||||
@Override
|
||||
public void initializeDefaultPreferences() {
|
||||
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
|
||||
store.setDefault(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_ENABLED, false);
|
||||
store.setDefault(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_PORT_NUMBER, "12321");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/* ###
|
||||
* 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.ghidrascripteditor.preferences;
|
||||
|
||||
import org.eclipse.jface.preference.*;
|
||||
import org.eclipse.jface.util.PropertyChangeEvent;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPreferencePage;
|
||||
|
||||
import ghidradev.Activator;
|
||||
import ghidradev.ghidrascripteditor.ScriptEditorInitializer;
|
||||
|
||||
/**
|
||||
* This class represents a preference page that
|
||||
* is contributed to the Preferences dialog. By
|
||||
* subclassing <samp>FieldEditorPreferencePage</samp>, we
|
||||
* can use the field support built into JFace that allows
|
||||
* us to create a page that is small and knows how to
|
||||
* save, restore and apply itself.
|
||||
* <p>
|
||||
* This page is used to modify preferences only. They
|
||||
* are stored in the preference store that belongs to
|
||||
* the main plug-in class. That way, preferences can
|
||||
* be accessed directly via the preference store.
|
||||
*/
|
||||
|
||||
public class GhidraScriptEditorPreferencePage extends FieldEditorPreferencePage implements
|
||||
IWorkbenchPreferencePage {
|
||||
|
||||
private BooleanFieldEditor enabledField;
|
||||
private StringFieldEditor portField;
|
||||
|
||||
private String previousEnabledString = null;
|
||||
private String currentEnabledString = null;
|
||||
private String previousPortString = null;
|
||||
private String currentPortString = null;
|
||||
|
||||
public GhidraScriptEditorPreferencePage() {
|
||||
super(GRID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(IWorkbench workbench) {
|
||||
setPreferenceStore(Activator.getDefault().getPreferenceStore());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createFieldEditors() {
|
||||
enabledField =
|
||||
new BooleanFieldEditor(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_ENABLED,
|
||||
"Enabled", getFieldEditorParent());
|
||||
portField =
|
||||
new StringFieldEditor(GhidraScriptEditorPreferences.GHIDRA_SCRIPT_EDITOR_PORT_NUMBER,
|
||||
"Port:", getFieldEditorParent());
|
||||
|
||||
addField(enabledField);
|
||||
addField(portField);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkState() {
|
||||
super.checkState();
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
String portValue = portField.getStringValue();
|
||||
if (!portValue.isEmpty()) {
|
||||
try {
|
||||
int portNumber = Integer.parseInt(portValue);
|
||||
if (portNumber < 1024 || portNumber > 0xFFFF) {
|
||||
setErrorMessage("Port must be between 1024 and 65535.");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
setErrorMessage("Port must be an integer.");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent event) {
|
||||
super.propertyChange(event);
|
||||
if (event.getProperty().equals(FieldEditor.VALUE)) {
|
||||
checkState();
|
||||
}
|
||||
if (event.getSource() == enabledField) {
|
||||
if (previousEnabledString == null) {
|
||||
previousEnabledString = event.getOldValue().toString();
|
||||
}
|
||||
currentEnabledString = event.getNewValue().toString();
|
||||
}
|
||||
else if (event.getSource() == portField) {
|
||||
if (previousPortString == null) {
|
||||
previousPortString = event.getOldValue().toString();
|
||||
}
|
||||
currentPortString = event.getNewValue().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performOk() {
|
||||
super.performOk();
|
||||
boolean enabledWasChanged = false;
|
||||
boolean portWasChanged = false;
|
||||
if (currentEnabledString != null && previousEnabledString != null) {
|
||||
if (!currentEnabledString.equals(previousEnabledString)) {
|
||||
enabledWasChanged = true;
|
||||
}
|
||||
}
|
||||
if (currentPortString != null && previousPortString != null) {
|
||||
if (!currentPortString.equals(previousPortString)) {
|
||||
portWasChanged = true;
|
||||
}
|
||||
}
|
||||
ScriptEditorInitializer.notifyPreferencesChanged(enabledWasChanged, portWasChanged);
|
||||
previousEnabledString = null;
|
||||
previousPortString = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performCancel() {
|
||||
super.performCancel();
|
||||
previousEnabledString = null;
|
||||
previousPortString = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/* ###
|
||||
* 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.ghidrascripteditor.preferences;
|
||||
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
|
||||
import ghidradev.Activator;
|
||||
|
||||
/**
|
||||
* Ghidra script editor preference definitions and related utility methods.
|
||||
*/
|
||||
public class GhidraScriptEditorPreferences {
|
||||
|
||||
/**
|
||||
* Whether or not the script editor feature is enabled.
|
||||
*/
|
||||
static final String GHIDRA_SCRIPT_EDITOR_ENABLED = "ghidradev.scriptEditorEnabled";
|
||||
|
||||
/**
|
||||
* Port used for script editor.
|
||||
*/
|
||||
static final String GHIDRA_SCRIPT_EDITOR_PORT_NUMBER = "ghidradev.scriptEditorPortNumber";
|
||||
|
||||
/**
|
||||
* Gets whether or not the script editor feature is enabled.
|
||||
*
|
||||
* @return True if the script editor feature is enabled; otherwise, false.
|
||||
*/
|
||||
public static boolean isScriptEditorEnabled() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
return prefs.getBoolean(GHIDRA_SCRIPT_EDITOR_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the script editor feature is enabled.
|
||||
*
|
||||
* @param enabled True to enable the script editor feature; false to disable it.
|
||||
*/
|
||||
public static void setScriptEditorEnabled(boolean enabled) {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
prefs.setValue(GHIDRA_SCRIPT_EDITOR_ENABLED, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port used for script editor.
|
||||
*
|
||||
* @return The port used for script editor. Will return -1 if the port is not set.
|
||||
*/
|
||||
public static int getScriptEditorPort() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
try {
|
||||
return Integer.parseInt(prefs.getString(GHIDRA_SCRIPT_EDITOR_PORT_NUMBER));
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
/* ###
|
||||
* 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.ghidrasymbollookup;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.cdt.core.CCorePlugin;
|
||||
import org.eclipse.cdt.core.browser.ITypeReference;
|
||||
import org.eclipse.cdt.core.browser.IndexTypeInfo;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
|
||||
import org.eclipse.cdt.core.index.*;
|
||||
import org.eclipse.cdt.core.model.CoreModel;
|
||||
import org.eclipse.cdt.core.model.ICProject;
|
||||
import org.eclipse.cdt.internal.ui.browser.opentype.ElementSelectionDialog;
|
||||
import org.eclipse.cdt.internal.ui.browser.opentype.OpenTypeMessages;
|
||||
import org.eclipse.core.resources.*;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.ui.ide.IDE;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
|
||||
public class OpenDeclarations {
|
||||
|
||||
private IProject project;
|
||||
private HashMap<String, IMarker> symbolMap;
|
||||
|
||||
public OpenDeclarations(IProject project) {
|
||||
this.project = project;
|
||||
this.symbolMap = new HashMap<String, IMarker>();
|
||||
}
|
||||
|
||||
public void setProject(IProject newProject) {
|
||||
project = newProject;
|
||||
}
|
||||
|
||||
public boolean open(String filename, int lineNumber) {
|
||||
openSingleFileAtLineNumber(filename, lineNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean open(String symbolName) {
|
||||
IIndex index = null;
|
||||
if (symbolMap.containsKey(symbolName)) {
|
||||
EclipseMessageUtils.info("Re-using editor for symbol: " + symbolName);
|
||||
openFileFromMap(symbolName);
|
||||
return true;
|
||||
}
|
||||
|
||||
EclipseMessageUtils.info("Searching index for symbol: " + symbolName);
|
||||
List<IIndexName> indexNames = new ArrayList<>();
|
||||
ICProject cProject = CoreModel.getDefault().getCModel().getCProject(project.getName());
|
||||
IIndexManager manager = CCorePlugin.getIndexManager();
|
||||
|
||||
try {
|
||||
index = manager.getIndex(cProject);
|
||||
|
||||
// we may be called before Eclipse has run the CDT plugin's init
|
||||
waitForIndexInitialization(index);
|
||||
|
||||
index.acquireReadLock();
|
||||
|
||||
IIndexBinding[] bindings = index.findBindings(Pattern.compile(symbolName), false,
|
||||
IndexFilter.ALL, new NullProgressMonitor());
|
||||
EclipseMessageUtils.info("Found \"" + bindings.length + "\" bindings for symbol");
|
||||
for (IIndexBinding binding : bindings) {
|
||||
IIndexName[] names = index.findNames(binding, IIndex.FIND_DEFINITIONS);
|
||||
for (IIndexName name : names) {
|
||||
indexNames.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (indexNames.size() == 0) {
|
||||
EclipseMessageUtils.info("Found no definitions, looking for declarations...");
|
||||
for (IIndexBinding binding : bindings) {
|
||||
IIndexName[] names = index.findNames(binding, IIndex.FIND_DECLARATIONS);
|
||||
for (IIndexName name : names) {
|
||||
indexNames.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (indexNames.size() == 1) {
|
||||
EclipseMessageUtils.info("Found single match - opening editor");
|
||||
openSingleFile(indexNames.get(0).getFileLocation(), symbolName);
|
||||
return true;
|
||||
}
|
||||
else if (indexNames.size() > 1) {
|
||||
EclipseMessageUtils.info("Found multiple matches - showing dialog");
|
||||
openMultipleFileDialog(symbolName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
EclipseMessageUtils.error("Unexpected exception searching C index: " + e.getMessage(),
|
||||
e);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
if (index != null) {
|
||||
index.releaseReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForIndexInitialization(IIndex index) throws CoreException {
|
||||
int waitCount = 0;
|
||||
while (waitCount < 2) {
|
||||
waitCount++;
|
||||
try {
|
||||
index.acquireReadLock();
|
||||
|
||||
IIndexFile[] allFiles = index.getAllFiles();
|
||||
if (allFiles.length == 0) {
|
||||
EclipseMessageUtils.info("C Index is not yet initialized--waiting...");
|
||||
index.releaseReadLock();
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// don't care; try again
|
||||
}
|
||||
finally {
|
||||
index.releaseReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void openSingleFile(IASTFileLocation location, String functionName) {
|
||||
String pathToFix = location.getFileName();
|
||||
String projectName = project.getName();
|
||||
int index = pathToFix.indexOf(projectName);
|
||||
if (index == -1) {
|
||||
EclipseMessageUtils.error("Error opening the file containing " + pathToFix);
|
||||
return;
|
||||
}
|
||||
String relativePath = pathToFix.substring(index);
|
||||
final IPath path = new Path(relativePath).removeFirstSegments(1); // strip off project name
|
||||
final int offset = location.getNodeOffset();
|
||||
final int length = location.getNodeLength();
|
||||
final String fName = functionName;
|
||||
Display.getDefault().asyncExec(() -> {
|
||||
try {
|
||||
IFile file = project.getFile(path);
|
||||
IMarker marker = file.createMarker(IMarker.TEXT);
|
||||
marker.setAttribute(IMarker.CHAR_START, offset);
|
||||
marker.setAttribute(IMarker.CHAR_END, offset + length);
|
||||
IDE.openEditor(EclipseMessageUtils.getWorkbenchPage(), marker);
|
||||
symbolMap.put(fName, marker);
|
||||
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell().forceActive();
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Error opening the file containing " + fName, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openMultipleFileDialog(String functionName) {
|
||||
final ElementSelectionDialog dialog = new ElementSelectionDialog(
|
||||
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell());
|
||||
configureDialog(dialog, functionName);
|
||||
final String fName = functionName;
|
||||
Display.getDefault().asyncExec(() -> {
|
||||
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell().forceActive();
|
||||
dialog.open();
|
||||
Object[] results = dialog.getResult();
|
||||
if (results == null) {
|
||||
return; // user cancelled the dialog
|
||||
}
|
||||
|
||||
for (Object result : results) {
|
||||
if (result instanceof IndexTypeInfo) {
|
||||
ITypeReference reference = ((IndexTypeInfo) result).getResolvedReference();
|
||||
IPath path = reference.getPath();
|
||||
path = path.removeFirstSegments(1);
|
||||
IFile file = project.getFile(path);
|
||||
try {
|
||||
IMarker marker = file.createMarker(IMarker.TEXT);
|
||||
marker.setAttribute(IMarker.CHAR_START, reference.getOffset());
|
||||
marker.setAttribute(IMarker.CHAR_END,
|
||||
reference.getOffset() + reference.getLength());
|
||||
IDE.openEditor(EclipseMessageUtils.getWorkbenchPage(), marker);
|
||||
symbolMap.put(fName, marker);
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Error opening file chosen from selection dialog",
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openSingleFileAtLineNumber(final String relativeFilename, final int lineNumber) {
|
||||
final IPath path = new Path(relativeFilename).removeFirstSegments(1); // strip off project
|
||||
Display.getDefault().asyncExec(() -> {
|
||||
try {
|
||||
IFile file = project.getFile(path);
|
||||
IMarker marker = file.createMarker(IMarker.TEXT);
|
||||
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
|
||||
IDE.openEditor(EclipseMessageUtils.getWorkbenchPage(), marker);
|
||||
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell().forceActive();
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Error opening the file containing at line " + lineNumber,
|
||||
e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void configureDialog(ElementSelectionDialog dialog, String functionName) {
|
||||
dialog.setTitle(OpenTypeMessages.OpenTypeDialog_title);
|
||||
dialog.setMessage(OpenTypeMessages.OpenTypeDialog_message);
|
||||
dialog.setDialogSettings(getClass().getName());
|
||||
dialog.setIgnoreCase(true);
|
||||
if (functionName.length() > 0 && functionName.length() < 80) {
|
||||
dialog.setFilter(functionName, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void openFileFromMap(String functionName) {
|
||||
final IMarker marker = symbolMap.get(functionName);
|
||||
Display.getDefault().asyncExec(() -> {
|
||||
try {
|
||||
IDE.openEditor(EclipseMessageUtils.getWorkbenchPage(), marker);
|
||||
EclipseMessageUtils.getWorkbenchPage().getWorkbenchWindow().getShell().forceActive();
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("Error opening file from map", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
/* ###
|
||||
* 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.ghidrasymbollookup;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
import ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferences;
|
||||
import ghidradev.ghidrasymbollookup.utils.CdtUtils;
|
||||
|
||||
public class SocketSetupRunnable implements Runnable {
|
||||
|
||||
private ServerSocket serverSocket = null;
|
||||
private OpenDeclarations openDeclsDialog;
|
||||
private IProject project;
|
||||
private boolean isInitialized;
|
||||
|
||||
public SocketSetupRunnable(ServerSocket serverSocket) {
|
||||
this.serverSocket = serverSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!serverSocket.isClosed()) {
|
||||
try (Socket socket = serverSocket.accept();
|
||||
BufferedReader input =
|
||||
new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
PrintWriter output = new PrintWriter(socket.getOutputStream())) {
|
||||
|
||||
String result = "";
|
||||
// Setup the project to look in and handle any problems like project being closed
|
||||
if (!isInitialized) {
|
||||
result = init();
|
||||
}
|
||||
|
||||
// If the project is closed while running the plugin and you try to look in it again
|
||||
if (isInitialized && !project.isOpen()) {
|
||||
try {
|
||||
input.readLine();
|
||||
}
|
||||
catch (IOException e) {
|
||||
EclipseMessageUtils.error(
|
||||
"Unexpected exception receiving symbol name: " + e.getMessage());
|
||||
}
|
||||
isInitialized = false;
|
||||
result = init();
|
||||
}
|
||||
if (isInitialized) {
|
||||
String symbolName = null;
|
||||
try {
|
||||
symbolName = input.readLine();
|
||||
}
|
||||
catch (IOException e) {
|
||||
EclipseMessageUtils.error(
|
||||
"Unexpected exception looking for symbol: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (isInitialized) {
|
||||
lookup(symbolName, output);
|
||||
}
|
||||
else {
|
||||
output.write("Failed to initialize CDT project");
|
||||
output.flush();
|
||||
}
|
||||
}
|
||||
else {
|
||||
output.write(result);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Socket was closed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String init() {
|
||||
String projectName = GhidraSymbolLookupPreferences.getSymbolLookupProjectName();
|
||||
final String errorMessageContainer[] = { "" };
|
||||
Display.getDefault().syncExec(() -> {
|
||||
while (!isInitialized) {
|
||||
if (projectName == null) {
|
||||
errorMessageContainer[0] =
|
||||
"Project name not defined in the Ghidra Symbol Lookup preference page.";
|
||||
EclipseMessageUtils.showWarnDialog("Ghidra Symbol Lookup",
|
||||
errorMessageContainer[0]);
|
||||
break;
|
||||
}
|
||||
project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
|
||||
if (!project.exists()) {
|
||||
errorMessageContainer[0] =
|
||||
"The project \"" + projectName + "\" does not exist " +
|
||||
"in your workspace. Please edit the \"Project Name\" field in " +
|
||||
"the Ghidra Symbol Lookup preference page.";
|
||||
EclipseMessageUtils.showWarnDialog("Project Does Not Exist",
|
||||
errorMessageContainer[0]);
|
||||
break;
|
||||
}
|
||||
if (!project.isOpen()) {
|
||||
errorMessageContainer[0] = "Please open the project \"" + project.getName() +
|
||||
"\" or choose a different one in the plugin preference page.";
|
||||
EclipseMessageUtils.showWarnDialog("Project Not Open",
|
||||
errorMessageContainer[0]);
|
||||
break;
|
||||
}
|
||||
if (!CdtUtils.isCdtProject(project)) {
|
||||
errorMessageContainer[0] =
|
||||
"The project \"" + project.getName() + "\" is not a C or C++ project." +
|
||||
"\nPlease edit the \"Project Name\" field in the Ghidra Symbol " +
|
||||
"Lookup preference page.";
|
||||
EclipseMessageUtils.showWarnDialog("Not a C/C++ Project",
|
||||
errorMessageContainer[0]);
|
||||
break;
|
||||
}
|
||||
openDeclsDialog = new OpenDeclarations(project);
|
||||
isInitialized = true;
|
||||
}
|
||||
});
|
||||
return errorMessageContainer[0];
|
||||
}
|
||||
|
||||
private void lookup(String symbolName, PrintWriter output) {
|
||||
EclipseMessageUtils.info("Looking for symbol name: " + symbolName);
|
||||
boolean result = openDeclsDialog.open(symbolName);
|
||||
if (result) {
|
||||
output.write("Found symbol " + symbolName + "\n");
|
||||
}
|
||||
else {
|
||||
output.write("Couldn't find " + symbolName + "\n");
|
||||
}
|
||||
output.flush();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/* ###
|
||||
* 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.ghidrasymbollookup;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
import ghidradev.*;
|
||||
import ghidradev.ghidrasymbollookup.preferences.GhidraSymbolLookupPreferences;
|
||||
|
||||
/**
|
||||
* High level driver for initializing the Ghidra Symbol Lookup subcomponent. Should get called
|
||||
* by the startup extension. This subcomponent is responsible for receiving symbol lookup requests
|
||||
* from Ghidra to Eclipse over a socket.
|
||||
*
|
||||
* @see GhidraDevStartup
|
||||
*/
|
||||
public class SymbolLookupInitializer {
|
||||
|
||||
private static ServerSocket serverSocket;
|
||||
|
||||
/**
|
||||
* Initializes the Ghidra Symbol Lookup subcomponent. Nothing in the package should be
|
||||
* used until this initialization happens. Should be called during Eclipse startup.
|
||||
*
|
||||
* @param firstTimeConsent True if the user has just consented to opening ports; otherwise,
|
||||
* false.
|
||||
* @see GhidraDevStartup
|
||||
*/
|
||||
public static void init(boolean firstTimeConsent) {
|
||||
if (firstTimeConsent) {
|
||||
GhidraSymbolLookupPreferences.setSymbolLookupEnabled(true);
|
||||
}
|
||||
listen(GhidraSymbolLookupPreferences.getSymbolLookupPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for socket connections on the given port. If there is a problem listening,
|
||||
* a popup is displayed for the user.
|
||||
*
|
||||
* @param port The port to listen on. If the port is -1, this method doesn't do anything.
|
||||
*/
|
||||
private static void listen(int port) {
|
||||
|
||||
if (!GhidraSymbolLookupPreferences.isSymbolLookupEnabled()) {
|
||||
EclipseMessageUtils.info(
|
||||
Activator.PLUGIN_ID + " Symbol Lookup port listening is disabled in preferences.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (port == -1) {
|
||||
EclipseMessageUtils.info(Activator.PLUGIN_ID +
|
||||
" Symbol Lookup port listening is disabled, port not set in preferences.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
serverSocket = new ServerSocket(port);
|
||||
EclipseMessageUtils.info(
|
||||
Activator.PLUGIN_ID + " Symbol Lookup is listening on port " + port);
|
||||
Activator.getDefault().registerCloseable(serverSocket);
|
||||
}
|
||||
catch (IOException e) {
|
||||
EclipseMessageUtils.showErrorDialog(Activator.PLUGIN_ID + " Symbol Lookup",
|
||||
"Failed to listen for connections on port " + port +
|
||||
". The Symbol Lookup features will be disabled until a valid port is selected in preferences.");
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(new SocketSetupRunnable(serverSocket)).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the symbol lookup preferences change.
|
||||
*
|
||||
* @param enabledWasChanged True if the enablement was changed to a new value.
|
||||
* @param portWasChanged True if the port preferences was changed to a new value.
|
||||
*/
|
||||
public static void preferencesChanged(boolean enabledWasChanged, boolean portWasChanged) {
|
||||
if (!enabledWasChanged && !portWasChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the old server socket. Its job is done.
|
||||
try {
|
||||
if (serverSocket != null) {
|
||||
serverSocket.close();
|
||||
Activator.getDefault().unregisterCloseable(serverSocket);
|
||||
serverSocket = null;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Oh well, we tried. This port probably won't work next time they pick it.
|
||||
}
|
||||
|
||||
// Listen on the new port
|
||||
listen(GhidraSymbolLookupPreferences.getSymbolLookupPort());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/* ###
|
||||
* 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.ghidrasymbollookup.preferences;
|
||||
|
||||
import org.eclipse.jface.preference.StringButtonFieldEditor;
|
||||
import org.eclipse.jface.viewers.LabelProvider;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
|
||||
|
||||
import ghidradev.ghidrasymbollookup.utils.CdtUtils;
|
||||
|
||||
/**
|
||||
* A field editor that lets the user select an open CDT project.
|
||||
*/
|
||||
public class CdtProjectFieldEditor extends StringButtonFieldEditor {
|
||||
|
||||
/**
|
||||
* Creates a new CDT project field editor.
|
||||
*
|
||||
* @param name the name of the preference this field editor works on
|
||||
* @param labelText the label text of the field editor
|
||||
* @param parent the parent of the field editor's control
|
||||
*/
|
||||
public CdtProjectFieldEditor(String name, String labelText, Composite parent) {
|
||||
super(name, labelText, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String changePressed() {
|
||||
ElementListSelectionDialog dialog =
|
||||
new ElementListSelectionDialog(getShell(), new LabelProvider());
|
||||
dialog.setTitle("CDT project selection");
|
||||
dialog.setMessage("Select an open CDT project:");
|
||||
dialog.setElements(CdtUtils.getCDTProjects().stream().map(p -> p.getName()).toArray());
|
||||
dialog.open();
|
||||
Object[] result = dialog.getResult();
|
||||
if (result != null && result.length > 0 && result[0] instanceof String) {
|
||||
return (String) result[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/* ###
|
||||
* 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.ghidrasymbollookup.preferences;
|
||||
|
||||
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
|
||||
import ghidradev.Activator;
|
||||
|
||||
/**
|
||||
* Class used to initialize default preference values.
|
||||
*/
|
||||
public class GhidraSymbolLookupPreferenceInitializer extends AbstractPreferenceInitializer {
|
||||
|
||||
@Override
|
||||
public void initializeDefaultPreferences() {
|
||||
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
|
||||
store.setDefault(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_ENABLED, false);
|
||||
store.setDefault(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME, "");
|
||||
store.setDefault(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER, "12322");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
/* ###
|
||||
* 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.ghidrasymbollookup.preferences;
|
||||
|
||||
import org.eclipse.core.resources.*;
|
||||
import org.eclipse.jface.preference.*;
|
||||
import org.eclipse.jface.util.PropertyChangeEvent;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPreferencePage;
|
||||
|
||||
import ghidradev.Activator;
|
||||
import ghidradev.ghidrasymbollookup.SymbolLookupInitializer;
|
||||
import ghidradev.ghidrasymbollookup.utils.CdtUtils;
|
||||
|
||||
/**
|
||||
* This class represents a preference page that is contributed to the Preferences dialog. By
|
||||
* subclassing {@link FieldEditorPreferencePage}, we can use the field support built into JFace
|
||||
* that allows us to create a page that is small and knows how to save, restore and apply itself.
|
||||
* <p>
|
||||
* This page is used to modify preferences only. They are stored in the preference store that
|
||||
* belongs to the main plug-in class. That way, preferences can be accessed directly via the
|
||||
* preference store.
|
||||
*/
|
||||
|
||||
public class GhidraSymbolLookupPreferencePage extends FieldEditorPreferencePage
|
||||
implements IWorkbenchPreferencePage {
|
||||
|
||||
private BooleanFieldEditor enabledField;
|
||||
private CdtProjectFieldEditor projectField;
|
||||
private StringFieldEditor portField;
|
||||
|
||||
private String previousEnabledString = null;
|
||||
private String currentEnabledString = null;
|
||||
private String previousPortString = null;
|
||||
private String currentPortString = null;
|
||||
|
||||
public GhidraSymbolLookupPreferencePage() {
|
||||
super(GRID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(IWorkbench workbench) {
|
||||
setPreferenceStore(Activator.getDefault().getPreferenceStore());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createFieldEditors() {
|
||||
enabledField =
|
||||
new BooleanFieldEditor(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_ENABLED,
|
||||
"Enabled", getFieldEditorParent());
|
||||
projectField = new CdtProjectFieldEditor(
|
||||
GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME, "Project Name:",
|
||||
getFieldEditorParent());
|
||||
portField =
|
||||
new StringFieldEditor(GhidraSymbolLookupPreferences.GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER,
|
||||
"Port:", getFieldEditorParent());
|
||||
|
||||
addField(enabledField);
|
||||
addField(projectField);
|
||||
addField(portField);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkState() {
|
||||
super.checkState();
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
String projectValue = projectField.getStringValue();
|
||||
String portValue = portField.getStringValue();
|
||||
setValid(true);
|
||||
if (!projectValue.isEmpty()) {
|
||||
IWorkspace workspace = ResourcesPlugin.getWorkspace();
|
||||
IWorkspaceRoot workspaceRoot = workspace.getRoot();
|
||||
IProject project = workspaceRoot.getProject(projectValue);
|
||||
if (!project.exists() || !project.isOpen() || !CdtUtils.isCdtProject(project)) {
|
||||
setErrorMessage("Project is not an open CDT project.");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!portValue.isEmpty()) {
|
||||
try {
|
||||
int portNumber = Integer.parseInt(portValue);
|
||||
if (portNumber < 1024 || portNumber > 0xFFFF) {
|
||||
setErrorMessage("Port must be between 1024 and 65535.");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
setErrorMessage("Port must be an integer.");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent event) {
|
||||
super.propertyChange(event);
|
||||
if (event.getProperty().equals(FieldEditor.VALUE)) {
|
||||
checkState();
|
||||
}
|
||||
if (event.getSource() == enabledField) {
|
||||
if (previousEnabledString == null) {
|
||||
previousEnabledString = event.getOldValue().toString();
|
||||
}
|
||||
currentEnabledString = event.getNewValue().toString();
|
||||
}
|
||||
else if (event.getSource() == portField) {
|
||||
if (previousPortString == null) {
|
||||
previousPortString = event.getOldValue().toString();
|
||||
}
|
||||
currentPortString = event.getNewValue().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performOk() {
|
||||
super.performOk();
|
||||
boolean enabledWasChanged = false;
|
||||
boolean portWasChanged = false;
|
||||
if (currentEnabledString != null && previousEnabledString != null) {
|
||||
if (!currentEnabledString.equals(previousEnabledString)) {
|
||||
enabledWasChanged = true;
|
||||
}
|
||||
}
|
||||
if (currentPortString != null && previousPortString != null) {
|
||||
if (!currentPortString.equals(previousPortString)) {
|
||||
portWasChanged = true;
|
||||
}
|
||||
}
|
||||
SymbolLookupInitializer.preferencesChanged(enabledWasChanged, portWasChanged);
|
||||
previousEnabledString = null;
|
||||
previousPortString = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performCancel() {
|
||||
super.performCancel();
|
||||
previousEnabledString = null;
|
||||
previousPortString = null;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/* ###
|
||||
* 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.ghidrasymbollookup.preferences;
|
||||
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
|
||||
import ghidradev.Activator;
|
||||
|
||||
/**
|
||||
* Ghidra symbol lookup preference definitions and related utility methods.
|
||||
*/
|
||||
public class GhidraSymbolLookupPreferences {
|
||||
|
||||
/**
|
||||
* Whether or not the symbol lookup feature is enabled.
|
||||
*/
|
||||
static final String GHIDRA_SYMBOL_LOOKUP_ENABLED = "ghidradev.symbolLookupEnabled";
|
||||
|
||||
/**
|
||||
* Name of CDT project that will be used for symbol lookup.
|
||||
*/
|
||||
static final String GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME = "ghidradev.symbolLookupProjectName";
|
||||
|
||||
/**
|
||||
* Port used for symbol lookup.
|
||||
*/
|
||||
static final String GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER = "ghidradev.symbolLookupPortNumber";
|
||||
|
||||
/**
|
||||
* Gets whether or not the symbol lookup feature is enabled.
|
||||
*
|
||||
* @return True if the symbol lookup feature is enabled; otherwise, false.
|
||||
*/
|
||||
public static boolean isSymbolLookupEnabled() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
return prefs.getBoolean(GHIDRA_SYMBOL_LOOKUP_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the symbol lookup feature is enabled.
|
||||
*
|
||||
* @param enabled True to enable the symbol lookup feature; false to disable it.
|
||||
*/
|
||||
public static void setSymbolLookupEnabled(boolean enabled) {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
prefs.setValue(GHIDRA_SYMBOL_LOOKUP_ENABLED, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the CDT project that used for symbol lookup.
|
||||
*
|
||||
* @return The name of the CDT project used for symbol lookup, or null
|
||||
* if one hasn't been set.
|
||||
*/
|
||||
public static String getSymbolLookupProjectName() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
String name = prefs.getString(GHIDRA_SYMBOL_LOOKUP_PROJECT_NAME);
|
||||
if (name.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port used for symbol lookup.
|
||||
*
|
||||
* @return The port used for symbol lookup. Will return -1 if the port is not set.
|
||||
*/
|
||||
public static int getSymbolLookupPort() {
|
||||
IPreferenceStore prefs = Activator.getDefault().getPreferenceStore();
|
||||
try {
|
||||
return Integer.parseInt(prefs.getString(GHIDRA_SYMBOL_LOOKUP_PORT_NUMBER));
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/* ###
|
||||
* 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.ghidrasymbollookup.utils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
|
||||
import ghidradev.EclipseMessageUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for interacting with CDT.
|
||||
*/
|
||||
public class CdtUtils {
|
||||
|
||||
/**
|
||||
* CDT C nature.
|
||||
*/
|
||||
public static final String C_NATURE = "org.eclipse.cdt.core.cnature";
|
||||
|
||||
/**
|
||||
* CDT C++ nature.
|
||||
*/
|
||||
public static final String CC_NATURE = "org.eclipse.cdt.core.ccnature";
|
||||
|
||||
/**
|
||||
* Gets all of the open CDT projects in the workspace.
|
||||
*
|
||||
* @return A collection of the open CDT projects in the workspace.
|
||||
*/
|
||||
public static Collection<IProject> getCDTProjects() {
|
||||
List<IProject> cdtProjects = new ArrayList<>();
|
||||
for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
|
||||
if (project.isOpen() && isCdtProject(project)) {
|
||||
cdtProjects.add(project);
|
||||
}
|
||||
}
|
||||
return cdtProjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the given project is a CDT project.
|
||||
*
|
||||
* @param project The project to check.
|
||||
* @return True if the given project is a CDT project; otherwise, false.
|
||||
*/
|
||||
public static boolean isCdtProject(IProject project) {
|
||||
try {
|
||||
return project != null && (project.hasNature(C_NATURE) || project.hasNature(CC_NATURE));
|
||||
}
|
||||
catch (CoreException e) {
|
||||
EclipseMessageUtils.error("CDT project check failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue