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());
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/Exporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/Exporter.java
index cbbec0007d..69b6182d57 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/Exporter.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/Exporter.java
@@ -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.
+ *
+ * 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}:
+ *
+ * {@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.
+ *
+ * {@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 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();
}
-
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProjectArchiveExporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/GdtExporter.java
similarity index 54%
rename from Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProjectArchiveExporter.java
rename to Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/GdtExporter.java
index ddc94c4d80..40057735f8 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProjectArchiveExporter.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/GdtExporter.java
@@ -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 getOptions(DomainObjectService domainObjectService) {
return EMPTY_OPTIONS;
@@ -63,11 +103,14 @@ public class ProjectArchiveExporter extends Exporter {
@Override
public void setOptions(List 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;
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/GzfExporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/GzfExporter.java
index 090f0595ba..e8b185bdcd 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/GzfExporter.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/GzfExporter.java
@@ -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 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;
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/OriginalFileExporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/OriginalFileExporter.java
index 7c85deaf32..fcdf0bcb49 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/OriginalFileExporter.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/OriginalFileExporter.java
@@ -58,7 +58,7 @@ public class OriginalFileExporter extends Exporter {
}
@Override
- public boolean supportsPartialExport() {
+ public boolean supportsAddressRestrictedExport() {
return false;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/AbstractFileListFlavorHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/AbstractFileListFlavorHandler.java
new file mode 100644
index 0000000000..397e0c06f3
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/AbstractFileListFlavorHandler.java
@@ -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 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;
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/GhidraDataFlavorHandlerService.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/GhidraDataFlavorHandlerService.java
index 3b9bc8c351..cdb8618ba8 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/GhidraDataFlavorHandlerService.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/GhidraDataFlavorHandlerService.java
@@ -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());
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/JavaFileListHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/JavaFileListHandler.java
index 7321e3ac06..38a3fc1ee5 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/JavaFileListHandler.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/JavaFileListHandler.java
@@ -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.} (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 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 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 fileList = CollectionUtils.asList((List>) transferData, File.class);
+ doImport(getDomainFolder(destinationNode), fileList, tool, dataTree);
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/LinuxFileUrlHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/LinuxFileUrlHandler.java
index 336e2219a1..a2a3eb7e52 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/LinuxFileUrlHandler.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/datatree/LinuxFileUrlHandler.java
@@ -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 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 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 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 toUrls(Object transferData, Function converter) {
+ private List toFiles(Object transferData, Function urlToFileConverter) {
List 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;
- }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java
index c986188f9f..b09ab6aa19 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java
@@ -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 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 handleSimpleDBUnpack(DomainFolder folder, List files) {
+ if (frontEndService == null || !isSimpleUnpackEnabled()) {
+ return files;
+ }
+
+ ArrayList 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 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) {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/tool/ToolConstants.java b/Ghidra/Framework/Docking/src/main/java/docking/tool/ToolConstants.java
index ac41ecfced..7099934e0b 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/tool/ToolConstants.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/tool/ToolConstants.java
@@ -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
*/
diff --git a/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonExporter.java b/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonExporter.java
index 06401d5b0e..8719b275a2 100644
--- a/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonExporter.java
+++ b/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonExporter.java
@@ -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 {