mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-24 21:21:56 +00:00
GP-3034 GZF/GDT Import/Export improvements
This commit is contained in:
parent
b1cf7d1b61
commit
af989e0ff6
@ -36,7 +36,9 @@ import docking.widgets.label.GLabel;
|
||||
import ghidra.app.plugin.core.help.AboutDomainObjectUtils;
|
||||
import ghidra.app.util.*;
|
||||
import ghidra.app.util.exporter.Exporter;
|
||||
import ghidra.app.util.exporter.GzfExporter;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.framework.main.FrontEndService;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
@ -62,17 +64,19 @@ import ghidra.util.task.*;
|
||||
public class ExporterDialog extends DialogComponentProvider implements AddressFactoryService {
|
||||
|
||||
private static final String XML_WARNING =
|
||||
" Warning: XML is lossy and intended only for transfering data to external tools. GZF is the recommended format for saving and sharing program data.";
|
||||
" Warning: XML is lossy and intended only for transfering data to external tools. " +
|
||||
"GZF is the recommended format for saving and sharing program data.";
|
||||
|
||||
private static String lastUsedExporterName = "Ghidra Zip File"; // default to GZF first time
|
||||
private static String lastUsedExporterName = GzfExporter.NAME; // default to GZF first time
|
||||
|
||||
private JButton optionsButton;
|
||||
private ProgramSelection currentSelection;
|
||||
private JCheckBox selectionCheckBox;
|
||||
private JCheckBox selectionCheckBox; // null for FrontEnd Tool use
|
||||
private JTextField filePathTextField;
|
||||
private JButton fileChooserButton;
|
||||
private GhidraComboBox<Exporter> comboBox;
|
||||
private final DomainFile domainFile;
|
||||
private boolean domainObjectWasSupplied;
|
||||
private DomainObject domainObject;
|
||||
private List<Option> options;
|
||||
private PluginTool tool;
|
||||
@ -94,9 +98,9 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
* selected region.
|
||||
*
|
||||
* @param tool the tool that launched this dialog.
|
||||
* @param domainFile the program file to export.
|
||||
* @param domainFile the program file to export. (may be proxy)
|
||||
* @param domainObject the program to export if already open, otherwise null.
|
||||
* @param selection the current program selection.
|
||||
* @param selection the current program selection (ignored for FrontEnd Tool).
|
||||
*/
|
||||
public ExporterDialog(PluginTool tool, DomainFile domainFile, DomainObject domainObject,
|
||||
ProgramSelection selection) {
|
||||
@ -106,8 +110,12 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
this.domainObject = domainObject;
|
||||
this.currentSelection = selection;
|
||||
if (domainObject != null) {
|
||||
domainObjectWasSupplied = true;
|
||||
domainObject.addConsumer(this);
|
||||
}
|
||||
else {
|
||||
domainObject = getDomainObjectIfNeeded(TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
addWorkPanel(buildWorkPanel());
|
||||
addOKButton();
|
||||
@ -131,6 +139,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFrontEndPlugin() {
|
||||
return tool.getService(FrontEndService.class) != null;
|
||||
}
|
||||
|
||||
private JComponent buildWorkPanel() {
|
||||
JPanel panel = new JPanel(new VerticalLayout(5));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
@ -168,7 +180,8 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
return "Unexpected exception validating options: " + e.getMessage();
|
||||
}
|
||||
};
|
||||
OptionsDialog optionsDialog = new OptionsDialog(options, validator, this);
|
||||
AddressFactoryService svc = (domainObject instanceof Program) ? null : this;
|
||||
OptionsDialog optionsDialog = new OptionsDialog(options, validator, svc);
|
||||
optionsDialog
|
||||
.setHelpLocation(new HelpLocation("ExporterPlugin", getAnchorForSelectedFormat()));
|
||||
tool.showDialog(optionsDialog);
|
||||
@ -197,17 +210,15 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
private Component buildSelectionCheckboxPanel() {
|
||||
JPanel panel = new JPanel(new PairLayout(5, 5));
|
||||
selectionOnlyLabel = new GLabel("Selection Only:");
|
||||
panel.add(selectionOnlyLabel);
|
||||
panel.add(buildSelectionCheckbox());
|
||||
if (!isFrontEndPlugin()) {
|
||||
selectionCheckBox = new GCheckBox("");
|
||||
updateSelectionCheckbox();
|
||||
panel.add(selectionOnlyLabel);
|
||||
panel.add(selectionCheckBox);
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Component buildSelectionCheckbox() {
|
||||
selectionCheckBox = new GCheckBox("");
|
||||
updateSelectionCheckbox();
|
||||
return selectionCheckBox;
|
||||
}
|
||||
|
||||
private Component buildFilePanel() {
|
||||
filePathTextField = new JTextField();
|
||||
filePathTextField.setName("OUTPUT_FILE_TEXTFIELD");
|
||||
@ -284,7 +295,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
|
||||
private Component buildFormatChooser() {
|
||||
|
||||
List<Exporter> exporters = getApplicableExporters();
|
||||
List<Exporter> exporters = getApplicableExporters(false);
|
||||
comboBox = new GhidraComboBox<>(exporters);
|
||||
|
||||
Exporter defaultExporter = getDefaultExporter(exporters);
|
||||
@ -295,17 +306,30 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
private List<Exporter> getApplicableExporters() {
|
||||
/**
|
||||
* This list generation will be based upon the open domainObject if available, otherwise
|
||||
* the domainFile's content class will be used.
|
||||
* @return list of exporters able to handle content
|
||||
*/
|
||||
private List<Exporter> getApplicableExporters(boolean preliminaryCheck) {
|
||||
List<Exporter> list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class));
|
||||
Class<?> domainObjectClass = domainFile.getDomainObjectClass();
|
||||
DomainObject domainObj = getDomainObject(TaskMonitor.DUMMY);
|
||||
if (DomainObject.class.isAssignableFrom(domainObjectClass)) {
|
||||
list.removeIf(exporter -> !exporter.canExportDomainObject(domainObj));
|
||||
Collections.sort(list, (o1, o2) -> o1.toString().compareTo(o2.toString()));
|
||||
}
|
||||
list.removeIf(exporter -> !canExport(exporter, preliminaryCheck));
|
||||
Collections.sort(list, (o1, o2) -> o1.toString().compareTo(o2.toString()));
|
||||
return list;
|
||||
}
|
||||
|
||||
private boolean canExport(Exporter exporter, boolean preliminaryCheck) {
|
||||
if (exporter.canExportDomainFile(domainFile)) {
|
||||
return true;
|
||||
}
|
||||
if (domainObject == null) {
|
||||
return preliminaryCheck
|
||||
? exporter.canExportDomainObject(domainFile.getDomainObjectClass())
|
||||
: false;
|
||||
}
|
||||
return exporter.canExportDomainObject(domainObject);
|
||||
}
|
||||
|
||||
private Exporter getDefaultExporter(List<Exporter> list) {
|
||||
|
||||
// first try the last one used
|
||||
@ -321,17 +345,19 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
private void selectedFormatChanged() {
|
||||
Exporter selectedExporter = getSelectedExporter();
|
||||
if (selectedExporter != null) {
|
||||
options = selectedExporter.getOptions(() -> getDomainObject(TaskMonitor.DUMMY));
|
||||
options = selectedExporter.getOptions(() -> domainObject);
|
||||
}
|
||||
validate();
|
||||
updateSelectionCheckbox();
|
||||
}
|
||||
|
||||
private void updateSelectionCheckbox() {
|
||||
boolean shouldEnableCheckbox = shouldEnableCheckbox();
|
||||
selectionCheckBox.setSelected(shouldEnableCheckbox);
|
||||
selectionCheckBox.setEnabled(shouldEnableCheckbox);
|
||||
selectionOnlyLabel.setEnabled(shouldEnableCheckbox);
|
||||
if (selectionCheckBox != null) {
|
||||
boolean shouldEnableCheckbox = shouldEnableCheckbox();
|
||||
selectionCheckBox.setSelected(shouldEnableCheckbox);
|
||||
selectionCheckBox.setEnabled(shouldEnableCheckbox);
|
||||
selectionOnlyLabel.setEnabled(shouldEnableCheckbox);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldEnableCheckbox() {
|
||||
@ -339,7 +365,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
return false;
|
||||
}
|
||||
Exporter selectedExporter = getSelectedExporter();
|
||||
return selectedExporter != null && selectedExporter.supportsPartialExport();
|
||||
return selectedExporter != null && selectedExporter.supportsAddressRestrictedExport();
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
@ -410,16 +436,32 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
}
|
||||
}
|
||||
|
||||
private DomainObject getDomainObject(TaskMonitor taskMonitor) {
|
||||
if (domainObject == null) {
|
||||
if (SystemUtilities.isEventDispatchThread()) {
|
||||
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
|
||||
monitor -> doOpenFile(monitor));
|
||||
}
|
||||
else {
|
||||
doOpenFile(taskMonitor);
|
||||
private DomainObject getDomainObjectIfNeeded(TaskMonitor taskMonitor) {
|
||||
if (domainObject != null) {
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
// Only open if there is an exporter that can handle content class but can't handle
|
||||
// direct domain file export. This avoids potential upgrade issues and preserves
|
||||
// database in its current state for those exporters.
|
||||
boolean doOpen = false;
|
||||
for (Exporter exporter : getApplicableExporters(true)) {
|
||||
if (!exporter.canExportDomainFile(domainFile)) {
|
||||
doOpen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!doOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (SystemUtilities.isEventDispatchThread()) {
|
||||
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
|
||||
monitor -> doOpenFile(monitor));
|
||||
}
|
||||
else {
|
||||
doOpenFile(taskMonitor);
|
||||
}
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
@ -436,10 +478,27 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
|
||||
}
|
||||
}
|
||||
catch (VersionException | CancelledException | IOException e) {
|
||||
Msg.showError(this, getComponent(), "Error Opening File",
|
||||
"Could not open file: " + domainFile.getName() +
|
||||
"\nThis file may need to be upgraded! Try opening it in a tool first.");
|
||||
catch (VersionException e) {
|
||||
String msg = "Could not open file: " + domainFile.getName() +
|
||||
"\n\nAvailable export options will be limited.";
|
||||
if (e.isUpgradable()) {
|
||||
msg +=
|
||||
"\n\nA data upgrade is required. You may open file" +
|
||||
"\nin a tool first then Export if a different exporter" +
|
||||
"\nis required.";
|
||||
}
|
||||
else {
|
||||
msg += "\nFile was created with a newer version of Ghidra";
|
||||
}
|
||||
Msg.showError(this, getComponent(), "Error Opening File", msg);
|
||||
}
|
||||
catch (IOException e) {
|
||||
String msg = "Could not open file: " + domainFile.getName() +
|
||||
"\n\nAvailable export options will be limited.";
|
||||
Msg.showError(this, getComponent(), "Error Opening File", msg, e);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,9 +507,8 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
*/
|
||||
@Override
|
||||
public AddressFactory getAddressFactory() {
|
||||
DomainObject dobj = getDomainObject(TaskMonitor.DUMMY);
|
||||
if (dobj instanceof Program) {
|
||||
return ((Program) domainObject).getAddressFactory();
|
||||
if (domainObject instanceof Program p) {
|
||||
return p.getAddressFactory();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -468,7 +526,6 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
private boolean success;
|
||||
private boolean showResults;
|
||||
private Exporter exporter;
|
||||
private DomainObject exportedDomainObject;
|
||||
|
||||
public ExportTask() {
|
||||
super("Export " + domainFile.getName(), true, true, true, false);
|
||||
@ -480,10 +537,14 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
exporter = getSelectedExporter();
|
||||
|
||||
exporter.setExporterServiceProvider(tool);
|
||||
exportedDomainObject = getDomainObject(monitor);
|
||||
if (exportedDomainObject == null) {
|
||||
|
||||
boolean exportDomainFile =
|
||||
!domainObjectWasSupplied && exporter.canExportDomainFile(domainFile);
|
||||
if (!exportDomainFile && domainFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Program selection only relavent if isFrontEndPlugin() is false
|
||||
ProgramSelection selection = getApplicableProgramSeletion();
|
||||
File outputFile = getSelectedOutputFile();
|
||||
|
||||
@ -497,7 +558,13 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
if (options != null) {
|
||||
exporter.setOptions(options);
|
||||
}
|
||||
success = exporter.export(outputFile, exportedDomainObject, selection, monitor);
|
||||
|
||||
if (!domainObjectWasSupplied && exporter.canExportDomainFile(domainFile)) {
|
||||
success = exporter.export(outputFile, domainFile, monitor);
|
||||
}
|
||||
else {
|
||||
success = exporter.export(outputFile, domainObject, selection, monitor);
|
||||
}
|
||||
showResults = true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
@ -509,7 +576,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
|
||||
void showResults() {
|
||||
if (showResults) {
|
||||
displaySummaryResults(exporter, exportedDomainObject);
|
||||
displaySummaryResults(exporter);
|
||||
}
|
||||
}
|
||||
|
||||
@ -518,47 +585,14 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryExport(TaskMonitor monitor) {
|
||||
Exporter exporter = getSelectedExporter();
|
||||
|
||||
exporter.setExporterServiceProvider(tool);
|
||||
DomainObject dobj = getDomainObject(monitor);
|
||||
if (dobj == null) {
|
||||
return false;
|
||||
}
|
||||
ProgramSelection selection = getApplicableProgramSeletion();
|
||||
File outputFile = getSelectedOutputFile();
|
||||
|
||||
try {
|
||||
if (outputFile.exists() &&
|
||||
OptionDialog.showOptionDialog(getComponent(), "Overwrite Existing File?",
|
||||
"The file " + outputFile + " already exists.\nDo you want to overwrite it?",
|
||||
"Overwrite", OptionDialog.QUESTION_MESSAGE) != OptionDialog.OPTION_ONE) {
|
||||
return false;
|
||||
}
|
||||
if (options != null) {
|
||||
exporter.setOptions(options);
|
||||
}
|
||||
boolean success = exporter.export(outputFile, dobj, selection, monitor);
|
||||
displaySummaryResults(exporter, dobj);
|
||||
return success;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Exception exporting", e);
|
||||
SystemUtilities.runSwingLater(() -> setStatusText(
|
||||
"Exception exporting: " + e.getMessage() + ". If null, see log for details."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ProgramSelection getApplicableProgramSeletion() {
|
||||
if (selectionCheckBox.isSelected()) {
|
||||
if (selectionCheckBox != null && selectionCheckBox.isSelected()) {
|
||||
return currentSelection;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void displaySummaryResults(Exporter exporter, DomainObject obj) {
|
||||
private void displaySummaryResults(Exporter exporter) {
|
||||
File outputFile = getSelectedOutputFile();
|
||||
StringBuffer resultsBuffer = new StringBuffer();
|
||||
|
||||
@ -572,15 +606,21 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
HelpLocation helpLocation = new HelpLocation(GenericHelpTopics.ABOUT, "About_Program");
|
||||
|
||||
Object tmpConsumer = new Object();
|
||||
obj.addConsumer(tmpConsumer);
|
||||
if (domainObject != null) {
|
||||
domainObject.addConsumer(tmpConsumer);
|
||||
}
|
||||
Swing.runLater(() -> {
|
||||
try {
|
||||
AboutDomainObjectUtils.displayInformation(tool, obj.getDomainFile(),
|
||||
obj.getMetadata(), "Export Results Summary", resultsBuffer.toString(),
|
||||
Map<String, String> metadata =
|
||||
domainObject != null ? domainObject.getMetadata() : domainFile.getMetadata();
|
||||
AboutDomainObjectUtils.displayInformation(tool, domainFile,
|
||||
metadata, "Export Results Summary", resultsBuffer.toString(),
|
||||
helpLocation);
|
||||
}
|
||||
finally {
|
||||
obj.release(tmpConsumer);
|
||||
if (domainObject != null) {
|
||||
domainObject.release(tmpConsumer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -590,6 +630,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
||||
// Methods for Testing
|
||||
//==================================================================================================
|
||||
|
||||
/**
|
||||
* Get "Selection Only" checkbox.
|
||||
* @return checkbox or null if not available (e.g., FrontEnd Tool use)
|
||||
*/
|
||||
JCheckBox getSelectionCheckBox() {
|
||||
return selectionCheckBox;
|
||||
}
|
||||
|
@ -25,8 +25,9 @@ import ghidra.app.context.NavigatableContextAction;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.services.CodeViewerService;
|
||||
import ghidra.framework.main.ApplicationLevelPlugin;
|
||||
import ghidra.framework.main.datatable.ProjectDataContext;
|
||||
import ghidra.framework.main.FrontEndService;
|
||||
import ghidra.framework.main.datatable.FrontendProjectTreeAction;
|
||||
import ghidra.framework.main.datatable.ProjectDataContext;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.plugintool.*;
|
||||
@ -46,13 +47,23 @@ import ghidra.util.HelpLocation;
|
||||
//@formatter:on
|
||||
public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||
|
||||
private FrontEndService frontEndService;
|
||||
|
||||
public ExporterPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
||||
frontEndService = tool.getService(FrontEndService.class);
|
||||
|
||||
createFrontEndAction();
|
||||
createToolAction();
|
||||
}
|
||||
|
||||
private void createToolAction() {
|
||||
|
||||
if (frontEndService != null) {
|
||||
return; // do not add File menu Export Program action to front-end
|
||||
}
|
||||
|
||||
DockingAction action = new NavigatableContextAction("Export Program", getName()) {
|
||||
|
||||
@Override
|
||||
@ -83,6 +94,11 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||
}
|
||||
|
||||
private void createFrontEndAction() {
|
||||
|
||||
if (frontEndService == null) {
|
||||
return; // only add project tree actions to front-end
|
||||
}
|
||||
|
||||
DockingAction action = new FrontendProjectTreeAction("Export", getName()) {
|
||||
|
||||
@Override
|
||||
|
@ -31,6 +31,7 @@ public interface FileImporterService {
|
||||
/**
|
||||
* Imports the given file into the specified Ghidra project folder.
|
||||
* @param folder the Ghidra project folder to store the imported file.
|
||||
* If null, the active project's root folder will be assumed.
|
||||
* @param file the file to import.
|
||||
*/
|
||||
public void importFile(DomainFolder folder, File file);
|
||||
@ -38,6 +39,7 @@ public interface FileImporterService {
|
||||
/**
|
||||
* Imports the given files into the specified Ghidra project folder.
|
||||
* @param folder the Ghidra project folder to store the imported files.
|
||||
* If null, the active project's root folder will be assumed.
|
||||
* @param files the files to import.
|
||||
*/
|
||||
public void importFiles(DomainFolder folder, List<File> files);
|
||||
|
@ -22,5 +22,10 @@ import ghidra.framework.model.DomainObject;
|
||||
* a domainObject until it is needed.
|
||||
*/
|
||||
public interface DomainObjectService {
|
||||
|
||||
/**
|
||||
* Get the domain object to be exported
|
||||
* @return domain object or null if export limited to domain file
|
||||
*/
|
||||
public DomainObject getDomainObject();
|
||||
}
|
||||
|
@ -44,9 +44,7 @@ public class GhidraFileOpenDataFlavorHandlerService {
|
||||
FileOpenDropHandler.addDataFlavorHandler(DataFlavor.javaFileListFlavor,
|
||||
new JavaFileListHandler());
|
||||
|
||||
DataFlavor linuxFileUrlFlavor =
|
||||
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
|
||||
"String file URL");
|
||||
FileOpenDropHandler.addDataFlavorHandler(linuxFileUrlFlavor, new LinuxFileUrlHandler());
|
||||
FileOpenDropHandler.addDataFlavorHandler(LinuxFileUrlHandler.linuxFileUrlFlavor,
|
||||
new LinuxFileUrlHandler());
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public class OptionsDialog extends DialogComponentProvider implements OptionList
|
||||
* @param addressFactoryService a service for retrieving the AddressFactory if needed. This is
|
||||
* passed instead of an actual AddressFactory, because to get an AddressFactory, it might
|
||||
* require that a language be loaded or a program be opened and not all options require an
|
||||
* AddressFactory.
|
||||
* AddressFactory. If null, address based options will not be available.
|
||||
*/
|
||||
public OptionsDialog(List<Option> originalOptions,
|
||||
OptionValidator validator, AddressFactoryService addressFactoryService) {
|
||||
|
@ -55,7 +55,7 @@ public class OptionsEditorPanel extends JPanel {
|
||||
* Construct a new OptionsEditorPanel
|
||||
* @param options the list of options to be edited.
|
||||
* @param addressFactoryService a service for providing an appropriate AddressFactory if needed
|
||||
* for editing an options.
|
||||
* for editing an options. If null, address based options will not be available.
|
||||
*/
|
||||
public OptionsEditorPanel(List<Option> options, AddressFactoryService addressFactoryService) {
|
||||
super(new VerticalLayout(5));
|
||||
@ -76,10 +76,13 @@ public class OptionsEditorPanel extends JPanel {
|
||||
|
||||
panel.setBorder(createBorder(group));
|
||||
for (Option option : optionGroup) {
|
||||
panel.add(new GLabel(option.getName(), SwingConstants.RIGHT));
|
||||
Component editorComponent = getEditorComponent(option);
|
||||
editorComponent.setName(option.getName()); // set the component name to the option name
|
||||
panel.add(editorComponent);
|
||||
if (editorComponent != null) {
|
||||
// Editor not available - omit option from panel
|
||||
panel.add(new GLabel(option.getName(), SwingConstants.RIGHT));
|
||||
editorComponent.setName(option.getName()); // set the component name to the option name
|
||||
panel.add(editorComponent);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsSelectAllDeselectAllButton(optionGroup)) {
|
||||
@ -178,7 +181,14 @@ public class OptionsEditorPanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
public Component getEditorComponent(Option option) {
|
||||
/**
|
||||
* Get the editor component for the specified option.
|
||||
* @param option option to be edited
|
||||
* @return option editor or null if prerequisite state not available to support
|
||||
* editor (e.g., Address or AddressSpace editor when {@link AddressFactoryService}
|
||||
* is not available).
|
||||
*/
|
||||
private Component getEditorComponent(Option option) {
|
||||
|
||||
// Special cases for library link/load options
|
||||
if (option.getName().equals(AbstractLibrarySupportLoader.LINK_SEARCH_FOLDER_OPTION_NAME) ||
|
||||
@ -262,6 +272,9 @@ public class OptionsEditorPanel extends JPanel {
|
||||
}
|
||||
|
||||
private Component getAddressSpaceEditorComponent(Option option) {
|
||||
if (addressFactoryService == null) {
|
||||
return null;
|
||||
}
|
||||
JComboBox<AddressSpace> combo = new GComboBox<>();
|
||||
AddressFactory addressFactory = addressFactoryService.getAddressFactory();
|
||||
AddressSpace[] spaces =
|
||||
@ -329,6 +342,9 @@ public class OptionsEditorPanel extends JPanel {
|
||||
}
|
||||
|
||||
private Component getAddressEditorComponent(Option option) {
|
||||
if (addressFactoryService == null) {
|
||||
return null;
|
||||
}
|
||||
AddressFactory addressFactory = addressFactoryService.getAddressFactory();
|
||||
AddressInput addressInput = new AddressInput();
|
||||
addressInput.setName(option.getName());
|
||||
|
@ -24,6 +24,7 @@ import org.apache.commons.lang3.Validate;
|
||||
|
||||
import ghidra.app.util.*;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
@ -101,32 +102,67 @@ abstract public class Exporter implements ExtensionPoint {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this exporter knows how to export the given domain object type. For example,
|
||||
* some exporters know how to export programs, other exporters can export project data type
|
||||
* archives.
|
||||
* Returns true if this exporter is capable of exporting the given domain file/object content
|
||||
* type. For example, some exporters have the ability to export programs, other exporters can
|
||||
* export project data type archives.
|
||||
* <p>
|
||||
* NOTE: This method should only be used as a preliminary check, if neccessary, to identify
|
||||
* exporter implementations that are capable of handling a specified content type/class. Prior
|
||||
* to export a final check should be performed based on the export or either a
|
||||
* {@link DomainFile} or {@link DomainObject}:
|
||||
* <p>
|
||||
* {@link DomainFile} export - the method {@link #canExportDomainFile(DomainFile)} should be
|
||||
* used to verify a direct project file export is possible using the
|
||||
* {@link #export(File, DomainFile, TaskMonitor)} method.
|
||||
* <p>
|
||||
* {@link DomainObject} export - the method {@link #canExportDomainObject(DomainObject)} should
|
||||
* be used to verify an export of a specific object is possible using the
|
||||
* {@link #export(File, DomainObject, AddressSetView, TaskMonitor)} method.
|
||||
*
|
||||
* avoid opening DomainFile when possible.
|
||||
* @param domainObjectClass the class of the domain object to test for exporting.
|
||||
* @return true if this exporter knows how to export the given domain object type.
|
||||
* @deprecated use {@link #canExportDomainObject(DomainObject)}
|
||||
*/
|
||||
@Deprecated(since = "10.3", forRemoval = true)
|
||||
public boolean canExportDomainObject(Class<? extends DomainObject> domainObjectClass) {
|
||||
return Program.class.isAssignableFrom(domainObjectClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this exporter knows how to export the given domain object.
|
||||
* Returns true if exporter can export the specified {@link DomainFile} without instantiating
|
||||
* a {@link DomainObject}. This method should be used prior to exporting using the
|
||||
* {@link #export(File, DomainFile, TaskMonitor)} method. All exporter capable of a
|
||||
* {@link DomainFile} export must also support a export of a {@link DomainObject} so that any
|
||||
* possible data modification/upgrade is included within resulting export.
|
||||
*
|
||||
* @param domainFile domain file
|
||||
* @return true if export can occur else false if not
|
||||
*/
|
||||
public boolean canExportDomainFile(DomainFile domainFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this exporter knows how to export the given domain object considering any
|
||||
* constraints based on the specific makeup of the object. This method should be used prior to
|
||||
* exporting using the {@link #export(File, DomainObject, AddressSetView, TaskMonitor)} method.
|
||||
*
|
||||
* @param domainObject the domain object to test for exporting.
|
||||
* @return true if this exporter knows how to export the given domain object.
|
||||
*/
|
||||
public boolean canExportDomainObject(DomainObject domainObject) {
|
||||
if (domainObject == null) {
|
||||
return false;
|
||||
}
|
||||
return canExportDomainObject(domainObject.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this exporter can export less than the entire domain file.
|
||||
* Returns true if this exporter can perform a restricted export of a {@link DomainObject}
|
||||
* based upon a specified {@link AddressSetView}.
|
||||
*
|
||||
* @return true if this exporter can export less than the entire domain file.
|
||||
*/
|
||||
public boolean supportsPartialExport() {
|
||||
public boolean supportsAddressRestrictedExport() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -150,26 +186,47 @@ abstract public class Exporter implements ExtensionPoint {
|
||||
abstract public void setOptions(List<Option> options) throws OptionException;
|
||||
|
||||
/**
|
||||
* Actually does the work of exporting the program.
|
||||
* Actually does the work of exporting a {@link DomainObject}. Export will include all
|
||||
* saved and unsaved modifications which may have been made to the object.
|
||||
*
|
||||
* @param file the output file to write the exported info
|
||||
* @param domainObj the domain object to export
|
||||
* @param addrSet the address set if only a portion of the program should be exported
|
||||
* NOTE: see {@link #supportsAddressRestrictedExport()}.
|
||||
* @param monitor the task monitor
|
||||
*
|
||||
* @return true if the program was successfully exported; otherwise, false. If the program
|
||||
* was not successfully exported, the message log should be checked to find the source of
|
||||
* the error.
|
||||
*
|
||||
* @throws ExporterException
|
||||
* @throws IOException
|
||||
* @throws ExporterException if export error occurs
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
abstract public boolean export(File file, DomainObject domainObj, AddressSetView addrSet,
|
||||
TaskMonitor monitor) throws ExporterException, IOException;
|
||||
|
||||
/**
|
||||
* Actually does the work of exporting a domain file, if supported (see
|
||||
* {@link #canExportDomainFile(DomainFile)}). Export is performed without instantiation of a
|
||||
* {@link DomainObject}.
|
||||
*
|
||||
* @param file the output file to write the exported info
|
||||
* @param domainFile the domain file to be exported (e.g., packed DB file)
|
||||
* @param monitor the task monitor
|
||||
* @return true if the file was successfully exported; otherwise, false. If the file
|
||||
* was not successfully exported, the message log should be checked to find the source of
|
||||
* the error.
|
||||
*
|
||||
* @throws ExporterException if export error occurs
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
public boolean export(File file, DomainFile domainFile, TaskMonitor monitor)
|
||||
throws ExporterException, IOException {
|
||||
throw new UnsupportedOperationException("DomainFile export not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
final public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,22 +16,43 @@
|
||||
package ghidra.app.util.exporter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.util.DomainObjectService;
|
||||
import ghidra.app.util.Option;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.program.database.DataTypeArchiveDB;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.data.FileDataTypeManager;
|
||||
import ghidra.program.model.listing.DataTypeArchive;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ProjectArchiveExporter extends Exporter {
|
||||
public static final String NAME = "Ghidra Data Type File";
|
||||
public class GdtExporter extends Exporter {
|
||||
|
||||
public ProjectArchiveExporter() {
|
||||
super(NAME, FileDataTypeManager.EXTENSION, null);
|
||||
public static final String EXTENSION = "gdt";
|
||||
public static final String SUFFIX = "." + EXTENSION;
|
||||
|
||||
public static final String NAME = "Ghidra Data Type Archive File";
|
||||
|
||||
public GdtExporter() {
|
||||
super(NAME, EXTENSION, new HelpLocation("ExporterPlugin", EXTENSION));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canExportDomainObject(Class<? extends DomainObject> domainObjectClass) {
|
||||
return DataTypeArchiveDB.class.isAssignableFrom(domainObjectClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canExportDomainFile(DomainFile domainFile) {
|
||||
return canExportDomainObject(domainFile.getDomainObjectClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return (obj instanceof GdtExporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -56,6 +77,25 @@ public class ProjectArchiveExporter extends Exporter {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean export(File file, DomainFile domainFile, TaskMonitor monitor)
|
||||
throws ExporterException, IOException {
|
||||
if (!canExportDomainFile(domainFile)) {
|
||||
throw new UnsupportedOperationException("only DataTypeArchiveDB files are supported");
|
||||
}
|
||||
try {
|
||||
domainFile.packFile(file, monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return false;
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.appendMsg("Unexpected exception exporting file: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Option> getOptions(DomainObjectService domainObjectService) {
|
||||
return EMPTY_OPTIONS;
|
||||
@ -63,11 +103,14 @@ public class ProjectArchiveExporter extends Exporter {
|
||||
|
||||
@Override
|
||||
public void setOptions(List<Option> options) {
|
||||
// this exporter doesn't support any options
|
||||
// no options for this exporter
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false. GDT export only supports entire database.
|
||||
*/
|
||||
@Override
|
||||
public boolean canExportDomainObject(Class<? extends DomainObject> domainObjectClass) {
|
||||
return DataTypeArchive.class.isAssignableFrom(domainObjectClass);
|
||||
public boolean supportsAddressRestrictedExport() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -16,11 +16,14 @@
|
||||
package ghidra.app.util.exporter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.util.DomainObjectService;
|
||||
import ghidra.app.util.Option;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
@ -37,6 +40,15 @@ public class GzfExporter extends Exporter {
|
||||
super(NAME, EXTENSION, new HelpLocation("ExporterPlugin", "gzf"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canExportDomainFile(DomainFile domainFile) {
|
||||
return canExportDomainObject(domainFile.getDomainObjectClass());
|
||||
}
|
||||
|
||||
public boolean canExportDomainObject(Class<? extends DomainObject> domainObjectClass) {
|
||||
return ProgramDB.class.isAssignableFrom(domainObjectClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return (obj instanceof GzfExporter);
|
||||
@ -64,6 +76,25 @@ public class GzfExporter extends Exporter {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean export(File file, DomainFile domainFile, TaskMonitor monitor)
|
||||
throws ExporterException, IOException {
|
||||
if (!canExportDomainFile(domainFile)) {
|
||||
throw new UnsupportedOperationException("only ProgramDB files are supported");
|
||||
}
|
||||
try {
|
||||
domainFile.packFile(file, monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return false;
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.appendMsg("Unexpected exception exporting file: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Option> getOptions(DomainObjectService domainObjectService) {
|
||||
return EMPTY_OPTIONS;
|
||||
@ -78,7 +109,7 @@ public class GzfExporter extends Exporter {
|
||||
* Returns false. GZF export only supports entire database.
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsPartialExport() {
|
||||
public boolean supportsAddressRestrictedExport() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public class OriginalFileExporter extends Exporter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPartialExport() {
|
||||
public boolean supportsAddressRestrictedExport() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,75 @@
|
||||
/* ###
|
||||
* 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 ghidra.framework.main.datatree;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.services.FileImporterService;
|
||||
import ghidra.app.util.FileOpenDataFlavorHandler;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
/**
|
||||
* An abstract handler to facilitate drag-n-drop for a list of Java {@link File} objects which is
|
||||
* dropped onto the Project data tree (see {@link DataTreeFlavorHandler}) or a running Ghidra Tool
|
||||
* (see {@link FileOpenDataFlavorHandler}).
|
||||
*/
|
||||
abstract class AbstractFileListFlavorHandler
|
||||
implements DataTreeFlavorHandler, FileOpenDataFlavorHandler {
|
||||
|
||||
/**
|
||||
* Do import when destination folder has been specified (e.g., data tree folder node).
|
||||
* @param folder destination folder (if null root folder will be assumed)
|
||||
* @param files files to be imported
|
||||
* @param tool target tool (active/current project assumed)
|
||||
* @param component parent component for popup messages
|
||||
*/
|
||||
protected void doImport(DomainFolder folder, List<File> files, PluginTool tool,
|
||||
Component component) {
|
||||
|
||||
Swing.runLater(() -> {
|
||||
FileImporterService im = tool.getService(FileImporterService.class);
|
||||
if (im == null) {
|
||||
Msg.showError(AbstractFileListFlavorHandler.class, component, "Could Not Import",
|
||||
"Could not find importer service.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (files.size() == 1 && files.get(0).isFile()) {
|
||||
im.importFile(folder, files.get(0));
|
||||
}
|
||||
else {
|
||||
im.importFiles(folder, files);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected DomainFolder getDomainFolder(GTreeNode destinationNode) {
|
||||
if (destinationNode instanceof DomainFolderNode) {
|
||||
return ((DomainFolderNode) destinationNode).getDomainFolder();
|
||||
}
|
||||
else if (destinationNode instanceof DomainFileNode) {
|
||||
DomainFolderNode parent = (DomainFolderNode) destinationNode.getParent();
|
||||
return parent.getDomainFolder();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -36,10 +36,7 @@ public class GhidraDataFlavorHandlerService {
|
||||
DataTreeDragNDropHandler.addActiveDataFlavorHandler(DataFlavor.javaFileListFlavor,
|
||||
new JavaFileListHandler());
|
||||
|
||||
DataFlavor linuxFileUrlFlavor =
|
||||
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
|
||||
"String file URL");
|
||||
DataTreeDragNDropHandler.addActiveDataFlavorHandler(linuxFileUrlFlavor,
|
||||
DataTreeDragNDropHandler.addActiveDataFlavorHandler(LinuxFileUrlHandler.linuxFileUrlFlavor,
|
||||
new LinuxFileUrlHandler());
|
||||
}
|
||||
}
|
||||
|
@ -24,68 +24,27 @@ import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.services.FileImporterService;
|
||||
import ghidra.app.util.FileOpenDataFlavorHandler;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@literal A drag-and-drop handler for trees that is specific to List<File>.} (see
|
||||
* {@link DataFlavor#javaFileListFlavor}).
|
||||
* A handler to facilitate drag-n-drop for a list of Java {@link File} objects which is dropped
|
||||
* onto the Project data tree or a running Ghidra Tool (see {@link DataFlavor#javaFileListFlavor}).
|
||||
*/
|
||||
public final class JavaFileListHandler implements DataTreeFlavorHandler, FileOpenDataFlavorHandler {
|
||||
public final class JavaFileListHandler extends AbstractFileListFlavorHandler {
|
||||
|
||||
@Override
|
||||
// This is for the FileOpenDataFlavorHandler for handling OS files dropped on a Ghidra Tool
|
||||
public void handle(PluginTool tool, Object transferData, DropTargetDropEvent e, DataFlavor f) {
|
||||
|
||||
FileImporterService importer = tool.getService(FileImporterService.class);
|
||||
if (importer == null) {
|
||||
Msg.showError(this, null, "Could Not Import", "Could not find Importer Service");
|
||||
return;
|
||||
}
|
||||
|
||||
DomainFolder folder = tool.getProject().getProjectData().getRootFolder();
|
||||
doImport(importer, folder, transferData);
|
||||
List<File> fileList = CollectionUtils.asList((List<?>) transferData, File.class);
|
||||
doImport(null, fileList, tool, tool.getToolFrame());
|
||||
}
|
||||
|
||||
@Override
|
||||
// This is for the DataFlavorHandler interface for handling OS files dropped onto a DataTree
|
||||
public void handle(PluginTool tool, DataTree dataTree, GTreeNode destinationNode,
|
||||
Object transferData, int dropAction) {
|
||||
|
||||
FileImporterService importer = tool.getService(FileImporterService.class);
|
||||
if (importer == null) {
|
||||
Msg.showError(this, dataTree, "Could Not Import", "Could not find Importer Service");
|
||||
return;
|
||||
}
|
||||
|
||||
DomainFolder folder = getDomainFolder(destinationNode);
|
||||
doImport(importer, folder, transferData);
|
||||
}
|
||||
|
||||
private void doImport(FileImporterService importer, DomainFolder folder, Object files) {
|
||||
|
||||
List<File> fileList = CollectionUtils.asList((List<?>) files, File.class);
|
||||
Swing.runLater(() -> {
|
||||
if (fileList.size() == 1 && fileList.get(0).isFile()) {
|
||||
importer.importFile(folder, fileList.get(0));
|
||||
}
|
||||
else {
|
||||
importer.importFiles(folder, fileList);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private DomainFolder getDomainFolder(GTreeNode destinationNode) {
|
||||
if (destinationNode instanceof DomainFolderNode) {
|
||||
return ((DomainFolderNode) destinationNode).getDomainFolder();
|
||||
}
|
||||
else if (destinationNode instanceof DomainFileNode) {
|
||||
DomainFolderNode parent = (DomainFolderNode) destinationNode.getParent();
|
||||
return parent.getDomainFolder();
|
||||
}
|
||||
return null;
|
||||
List<File> fileList = CollectionUtils.asList((List<?>) transferData, File.class);
|
||||
doImport(getDomainFolder(destinationNode), fileList, tool, dataTree);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package ghidra.framework.main.datatree;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.dnd.DropTargetDropEvent;
|
||||
import java.io.File;
|
||||
@ -26,62 +25,41 @@ import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.services.FileImporterService;
|
||||
import ghidra.app.util.FileOpenDataFlavorHandler;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A special handler to deal with files dragged from Linux to Ghidra. This class does double
|
||||
* duty in that it opens files for DataTrees and for Tools (signaled via the interfaces it
|
||||
* implements).
|
||||
* A handler to facilitate drag-n-drop for a Linux URL-based file list which is dropped
|
||||
* onto the Project data tree or a running Ghidra Tool (see {@link #linuxFileUrlFlavor}).
|
||||
*/
|
||||
public final class LinuxFileUrlHandler implements DataTreeFlavorHandler, FileOpenDataFlavorHandler {
|
||||
public final class LinuxFileUrlHandler extends AbstractFileListFlavorHandler {
|
||||
|
||||
/**
|
||||
* Linux URL-based file list {@link DataFlavor} to be used during handler registration
|
||||
* using {@link DataTreeDragNDropHandler#addActiveDataFlavorHandler}.
|
||||
*/
|
||||
public static final DataFlavor linuxFileUrlFlavor =
|
||||
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
|
||||
"String file URL");
|
||||
|
||||
@Override
|
||||
// This is for the FileOpenDataFlavorHandler for handling file drops from Linux to a Tool
|
||||
public void handle(PluginTool tool, Object transferData, DropTargetDropEvent e, DataFlavor f) {
|
||||
List<File> files = toFiles(transferData);
|
||||
doImport(null, files, tool, tool.getToolFrame());
|
||||
}
|
||||
|
||||
@Override
|
||||
// This is for the DataFlavorHandler interface for handling node drops in DataTrees
|
||||
public void handle(PluginTool tool, DataTree dataTree, GTreeNode destinationNode,
|
||||
Object transferData, int dropAction) {
|
||||
|
||||
DomainFolder folder = getDomainFolder(destinationNode);
|
||||
doImport(dataTree, transferData, tool, folder);
|
||||
}
|
||||
|
||||
@Override
|
||||
// This is for the FileOpenDataFlavorHandler for handling file drops from Linux to a Tool
|
||||
public void handle(PluginTool tool, Object transferData, DropTargetDropEvent e, DataFlavor f) {
|
||||
|
||||
DomainFolder folder = tool.getProject().getProjectData().getRootFolder();
|
||||
doImport(tool.getToolFrame(), transferData, tool, folder);
|
||||
}
|
||||
|
||||
private void doImport(Component component, Object transferData, ServiceProvider sp,
|
||||
DomainFolder folder) {
|
||||
|
||||
FileImporterService im = sp.getService(FileImporterService.class);
|
||||
if (im == null) {
|
||||
Msg.showError(this, component, "Could Not Import", "Could not find importer service.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<File> files = toFiles(transferData);
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (files.size() == 1 && files.get(0).isFile()) {
|
||||
im.importFile(folder, files.get(0));
|
||||
}
|
||||
else {
|
||||
im.importFiles(folder, files);
|
||||
}
|
||||
doImport(getDomainFolder(destinationNode), files, tool, dataTree);
|
||||
}
|
||||
|
||||
private List<File> toFiles(Object transferData) {
|
||||
|
||||
return toUrls(transferData, s -> {
|
||||
return toFiles(transferData, s -> {
|
||||
try {
|
||||
return new File(new URL(s).toURI());
|
||||
}
|
||||
@ -98,29 +76,17 @@ public final class LinuxFileUrlHandler implements DataTreeFlavorHandler, FileOpe
|
||||
});
|
||||
}
|
||||
|
||||
private List<File> toUrls(Object transferData, Function<String, File> converter) {
|
||||
private List<File> toFiles(Object transferData, Function<String, File> urlToFileConverter) {
|
||||
|
||||
List<File> files = new ArrayList<>();
|
||||
String string = (String) transferData;
|
||||
String[] urls = string.split("\\n");
|
||||
for (String url : urls) {
|
||||
File file = converter.apply(url);
|
||||
File file = urlToFileConverter.apply(url);
|
||||
if (file != null) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private DomainFolder getDomainFolder(GTreeNode destinationNode) {
|
||||
if (destinationNode instanceof DomainFolderNode) {
|
||||
return ((DomainFolderNode) destinationNode).getDomainFolder();
|
||||
}
|
||||
else if (destinationNode instanceof DomainFileNode) {
|
||||
DomainFolderNode parent = (DomainFolderNode) destinationNode.getParent();
|
||||
return parent.getDomainFolder();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -15,15 +15,16 @@
|
||||
*/
|
||||
package ghidra.plugin.importer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.*;
|
||||
import docking.tool.ToolConstants;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
@ -40,22 +41,25 @@ import ghidra.formats.gfilesystem.FileCache.FileCacheEntry;
|
||||
import ghidra.formats.gfilesystem.FileCache.FileCacheEntryBuilder;
|
||||
import ghidra.formats.gfilesystem.FileSystemService;
|
||||
import ghidra.framework.main.*;
|
||||
import ghidra.framework.main.datatree.DomainFileNode;
|
||||
import ghidra.framework.main.datatree.DomainFolderNode;
|
||||
import ghidra.framework.main.datatree.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.framework.store.local.ItemDeserializer;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.plugins.importer.batch.BatchImportDialog;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.*;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.filechooser.GhidraFileFilter;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import ghidra.util.task.*;
|
||||
|
||||
/**
|
||||
* A {@link Plugin} that supplies menu items and tasks to import files into Ghidra.
|
||||
@ -80,8 +84,11 @@ public class ImporterPlugin extends Plugin
|
||||
"This plugin manages importing files, including those contained within " +
|
||||
"firmware/filesystem images.";
|
||||
|
||||
private static final String SIMPLE_UNPACK_OPTION = "Enable simple GZF/GDT unpack";
|
||||
private static final boolean SIMPLE_UNPACK_OPTION_DEFAULT = false;
|
||||
|
||||
private DockingAction importAction;
|
||||
private DockingAction importSelectionAction;// NA in front-end
|
||||
private DockingAction importSelectionAction;
|
||||
private DockingAction addToProgramAction;
|
||||
private GhidraFileChooser chooser;
|
||||
private FrontEndService frontEndService;
|
||||
@ -98,6 +105,12 @@ public class ImporterPlugin extends Plugin
|
||||
frontEndService = tool.getService(FrontEndService.class);
|
||||
if (frontEndService != null) {
|
||||
frontEndService.addProjectListener(this);
|
||||
|
||||
ToolOptions options = tool.getOptions(ToolConstants.FILE_IMPORT_OPTIONS);
|
||||
HelpLocation help = new HelpLocation("ImporterPlugin", "Project_Tree");
|
||||
|
||||
options.registerOption(SIMPLE_UNPACK_OPTION, SIMPLE_UNPACK_OPTION_DEFAULT, help,
|
||||
"Perform simple unpack when any packed DB file is imported");
|
||||
}
|
||||
|
||||
setupImportAction();
|
||||
@ -156,6 +169,16 @@ public class ImporterPlugin extends Plugin
|
||||
|
||||
@Override
|
||||
public void importFiles(DomainFolder destFolder, List<File> files) {
|
||||
|
||||
if (destFolder == null) {
|
||||
destFolder = tool.getProject().getProjectData().getRootFolder();
|
||||
}
|
||||
|
||||
files = handleSimpleDBUnpack(destFolder, files);
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BatchImportDialog.showAndImport(tool, null, files2FSRLs(files), destFolder,
|
||||
getTool().getService(ProgramManager.class));
|
||||
}
|
||||
@ -175,11 +198,115 @@ public class ImporterPlugin extends Plugin
|
||||
@Override
|
||||
public void importFile(DomainFolder folder, File file) {
|
||||
|
||||
if (folder == null) {
|
||||
folder = tool.getProject().getProjectData().getRootFolder();
|
||||
}
|
||||
|
||||
if (handleSimpleDBUnpack(folder, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FSRL fsrl = FileSystemService.getInstance().getLocalFSRL(file);
|
||||
ProgramManager manager = tool.getService(ProgramManager.class);
|
||||
ImporterUtilities.showImportDialog(tool, manager, fsrl, folder, null);
|
||||
}
|
||||
|
||||
private static String makeValidUniqueFilename(String name, DomainFolder folder) {
|
||||
|
||||
// Trim-off file extension if ours *.g?? (e.g., gzf, gdt, etc.)
|
||||
int extIndex = name.lastIndexOf(".g");
|
||||
if (extIndex > 1 && (name.length() - extIndex) == 4) {
|
||||
name = name.substring(0, extIndex);
|
||||
}
|
||||
|
||||
CharBuffer buf = CharBuffer.wrap(name.toCharArray());
|
||||
for (int i = 0; i < buf.length(); i++) {
|
||||
if (!LocalFileSystem.isValidNameCharacter(buf.get(i))) {
|
||||
buf.put(i, '_');
|
||||
}
|
||||
}
|
||||
|
||||
String baseName = buf.toString();
|
||||
name = baseName;
|
||||
int count = 0;
|
||||
while (folder.getFile(name) != null) {
|
||||
++count;
|
||||
name = baseName + "." + count;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private List<File> handleSimpleDBUnpack(DomainFolder folder, List<File> files) {
|
||||
if (frontEndService == null || !isSimpleUnpackEnabled()) {
|
||||
return files;
|
||||
}
|
||||
|
||||
ArrayList<File> remainingFiles = new ArrayList<>();
|
||||
|
||||
Task task = new Task("", true, true, true) {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
for (File f : files) {
|
||||
monitor.checkCanceled();
|
||||
|
||||
// Test for Packed DB file using ItemDeserializer
|
||||
ItemDeserializer itemDeserializer = null;
|
||||
try {
|
||||
itemDeserializer = new ItemDeserializer(f); // fails for non-packed file
|
||||
}
|
||||
catch (IOException e) {
|
||||
remainingFiles.add(f);
|
||||
continue; // not a Packed DB - skip file
|
||||
}
|
||||
finally {
|
||||
if (itemDeserializer != null) {
|
||||
itemDeserializer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
monitor.setMessage("Unpacking " + f.getName() + " ...");
|
||||
|
||||
// Perform direct unpack of Packed DB file
|
||||
String filename = makeValidUniqueFilename(f.getName(), folder);
|
||||
try {
|
||||
DomainFile df = folder.createFile(filename, f, monitor);
|
||||
Msg.info(this, "Imported " + f.getName() + " to " + df.getPathname());
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
throw new AssertException(e); // unexpected - valid name was used
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(JavaFileListHandler.class, tool.getToolFrame(),
|
||||
"Packed DB Import Failed",
|
||||
"Failed to import " + f.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
TaskLauncher.launchModal("Import", task);
|
||||
if (task.isCancelled()) {
|
||||
return List.of(); // return empty list if cancelled
|
||||
}
|
||||
|
||||
return remainingFiles; // return files not yet imported
|
||||
}
|
||||
|
||||
private boolean handleSimpleDBUnpack(DomainFolder folder, File file) {
|
||||
List<File> files = handleSimpleDBUnpack(folder, List.of(file));
|
||||
return files.isEmpty();
|
||||
}
|
||||
|
||||
private boolean isSimpleUnpackEnabled() {
|
||||
if (frontEndService == null) {
|
||||
return false;
|
||||
}
|
||||
ToolOptions options = tool.getOptions(ToolConstants.FILE_IMPORT_OPTIONS);
|
||||
return options.getBoolean(SIMPLE_UNPACK_OPTION, SIMPLE_UNPACK_OPTION_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void projectClosed(Project project) {
|
||||
if (importAction != null) {
|
||||
|
@ -108,6 +108,11 @@ public interface ToolConstants extends DockingToolConstants {
|
||||
*/
|
||||
public static final String TOOL_OPTIONS = "Tool";
|
||||
|
||||
/**
|
||||
* File Import options name
|
||||
*/
|
||||
public static final String FILE_IMPORT_OPTIONS = "File Import";
|
||||
|
||||
/**
|
||||
* Graph options name
|
||||
*/
|
||||
|
@ -42,6 +42,14 @@ public class SkeletonExporter extends Exporter {
|
||||
super("My Exporter", "exp", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAddressRestrictedExport() {
|
||||
|
||||
// TODO: return true if addrSet export parameter can be used to restrict export
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean export(File file, DomainObject domainObj, AddressSetView addrSet,
|
||||
TaskMonitor monitor) throws ExporterException, IOException {
|
||||
|
Loading…
Reference in New Issue
Block a user