GP-3034 GZF/GDT Import/Export improvements

This commit is contained in:
ghidra1 2023-02-06 09:48:54 -05:00
parent b1cf7d1b61
commit 769ef9ec0a
22 changed files with 655 additions and 254 deletions

View File

@ -24,7 +24,9 @@
<LI><A href="#c_cpp">C/C++</A></LI>
<LI><A href="#gzf">Ghidra Zip File (.gzf)</A></LI>
<LI><A href="#gzf">Ghidra Zip File (GZF)</A></LI>
<LI><A href="#gdt">Ghidra Data Type Archive File (GDT)</a></LI>
<LI><A href="#html">HTML</A></LI>
@ -58,6 +60,16 @@
<LI>Press the <B>OK</B> button to perform the export.</LI>
</UL>
<P><I><IMG src="help/shared/note.png"> In preparation for a file export, a selected project
file may be opened in an attempt to support export formats that require an open
file. If the selected file requires an upgrade a warning dialog
will be displayed and the affected export formats will not be available until an upgrade
is perfored, however a direct packed format of the project file may still be chosen
without performing the upgrade first (e.g., <A href="#gzf">Ghidra Zip File (GZF)</A>,
<A href="#gzf">Ghidra Zip File (GZF)</A>).
</P>
</BLOCKQUOTE>
<H3>To export from the CodeBrowser tool:</H3>
@ -276,12 +288,28 @@
</UL>
</BLOCKQUOTE>
<H3><A name="gzf"/>Ghidra Zip File (.gzf)</H3>
<H3><A name="gzf"/>Ghidra Zip File (GZF)</H3>
<BLOCKQUOTE>
<P>Creates a zip file from a program in your project. You may want to create a zip file
<P>Creates a GZF file from a Program in your project. You may want to create a GZF file
so that you can give it to another user who can then <A href=
"help/topics/ImporterPlugin/importer.htm">import</A> into their project.</P>
"help/topics/ImporterPlugin/importer.htm">import</A> into their project. A program
export of this format from the Project Window will be based on the current saved file
content and bypass any potential upgrade that may be required by other formats.
</P>
</BLOCKQUOTE>
<H3><A name="gdt"/>Ghidra Data Type Archive File (GDT)</H3>
<BLOCKQUOTE>
<P>Creates a GDT file from a Data Type Archive in your project. You may want to create a GDT file
so that you can give it to another user who can then <A href=
"help/topics/ImporterPlugin/importer.htm">import</A> into their project or open directly
via the Data Type Manager as a
<A href="help/topics/DataTypeManagerPlugin/data_type_manager_description.htm#Open_File_Data_Type_Archive">File Data Type Archive</A>.
A project Data Type Archive export of this format from the Project Window will be based on
the current saved file content and bypass any potential upgrade that may be required by other formats.
</P>
</BLOCKQUOTE>
<H3><A name="html"/><A name="Options_HTML"/>HTML</H3>

View File

@ -36,8 +36,8 @@
<LI>Dump File Loader</LI>
<LI>DYLD Shared Cache</LI>
<LI>Executable and Linking Format (ELF)</LI>
<LI>Ghidra Data Type Archive Format</LI>
<LI>GZF Input Format</LI>
<LI>Ghidra Data Type Archive File (GDT)</LI>
<LI>Ghidra Zip File (GZF)</LI>
<LI>Intel Hex</LI>
<LI>Java Class File</LI>
<LI>Mac OS X Mach-O</LI>
@ -81,14 +81,40 @@
<LI>Use the <A href="#Importer_Dialog">Importer Dialog</A> (or the <A href=
"#Batch_Import_Dialog">Batch Importer Dialog</A> if it is an archive) that pops up to
configure the import.</LI>
perform the import.</LI>
<LI>Press OK to initiate the import.</LI>
<LI>Press OK from the <A href="#Importer_Dialog">Importer Dialog</A> to perform the import.</LI>
<LI>A results summary dialog will appear and, if successful, the new program will appear
in the project window and if initiated from a CodeBrowser tool, it will be opened in the
tool.</LI>
</UL>
<H4>Alternative Steps (<I>drag-and-drop</I>):</H4>
<UL>
<LI><B>Project Window</B>: Drag a file from the system file explorer application and drop
onto the Ghidra Project Tree destination folder. Dropping onto the table view is not
supported. In the case of Ghidra Zip File (GZF) or Ghidra Data Type Archive (GDT) file
imports, an immediate unpack can be performed wthout a popup dialog if the Front End option
<I>Enable simple GZF/GDT unpack</I> is enabled (
<B>Edit<IMG src="help/shared/arrow.gif">Tool Options...<IMG src="help/shared/arrow.gif">File Import<IMG src="help/shared/arrow.gif">Enable simple GZF/GDT unpack</B>).
</LI>
<LI>or <B>Running Tool</B>: Drag a file from the system file explorer application and drop
onto a Tool window (e.g., <I>Code Browser Tool</I>).</LI>
<LI>The <A href="#Importer_Dialog">Importer Dialog</A> (or the <A href=
"#Batch_Import_Dialog">Batch Importer Dialog</A> if it is an archive) will be
displayed to complete the import.</LI>
<LI>Press OK to initiate the import.</LI>
<LI>A results summary dialog will appear and, if successful, the new program will appear
in the project window and if initiated from a CodeBrowser tool, it will be opened in the
tool.</LI>
</UL>
</BLOCKQUOTE>
<H3><A name="Batch_Import"></A>Batch Import</H3>

View File

@ -81,10 +81,8 @@
</CENTER>
<BLOCKQUOTE>
<P>The legend indicates the colors that correspond to each type of program element shown in
the overview display. The colors are specified as options and can be changed from the default
values. To change the colors, click on the color, or edit the options through the <B>Edit
<IMG src="help/shared/arrow.gif">Options</B>... dialog. You can choose the color from a
color chooser dialog.</P>
the overview display. The colors used may be changed via the associated
<a href="#OverviewOptions">Options</a> (see below).</P>
</BLOCKQUOTE>
<H3><A name="OverviewOptions"></A>Options</H3>
@ -107,8 +105,8 @@
<LI>Uninitialized Color - color for memory that is not initialized</LI>
</UL>
<P>To view the options, select <B>Edit <IMG src="help/shared/arrow.gif">Options</B>... on
the tool, then choose the <I>Overview</I> node in the options tree. To change a color, double
<P>To view the options, select <B>Edit <IMG src="help/shared/arrow.gif">Tool Options</B>... on
the tool, then choose the <B>Overview</B> node in the options tree. To change a color, double
click on the color bar in the <I>Overview</I> <I>Options</I> panel. Choose the color from the
color chooser dialog.</P>
</BLOCKQUOTE>

View File

@ -244,7 +244,7 @@ public class DataTypeManagerPlugin extends ProgramPlugin
Project project = tool.getProjectManager().getActiveProject();
if (project != null && project.getName().equals(projectName)) {
DomainFile df = project.getProjectData().getFile(pathname);
if (DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass())) {
if (df != null && DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass())) {
return df;
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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();
}

View File

@ -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());
}
}

View File

@ -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) {

View File

@ -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());

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -58,7 +58,7 @@ public class OriginalFileExporter extends Exporter {
}
@Override
public boolean supportsPartialExport() {
public boolean supportsAddressRestrictedExport() {
return false;
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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
*/

View File

@ -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 {