diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java index eff1f5a496..bbe8e2a995 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ApplyDataArchiveAnalyzer.java @@ -15,18 +15,28 @@ */ package ghidra.app.plugin.core.analysis; -import java.util.ArrayList; -import java.util.List; +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; +import docking.options.editor.FileChooserEditor; +import docking.options.editor.StringWithChoicesEditor; +import generic.jar.ResourceFile; import ghidra.app.cmd.function.ApplyFunctionDataTypesCmd; import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility; import ghidra.app.services.*; import ghidra.app.util.importer.MessageLog; +import ghidra.framework.Application; +import ghidra.framework.model.*; +import ghidra.framework.options.OptionType; import ghidra.framework.options.Options; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.FileDataTypeManager; +import ghidra.program.model.listing.DataTypeArchive; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.SourceType; +import ghidra.util.Msg; import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; @@ -35,12 +45,36 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer { private static final String DESCRIPTION = "Apply known data type archives based on program information."; - protected static final String OPTION_NAME_CREATE_BOOKMARKS = "Create Analysis Bookmarks"; - private static final String OPTION_DESCRIPTION_CREATE_BOOKMARKS = - "If checked, an analysis bookmark will be created at each symbol address " + - "where multiple function definitions were found and not applied."; + private static final String OPTION_NAME_CREATE_BOOKMARKS = "Create Analysis Bookmarks"; + private static final String OPTION_DESCRIPTION_CREATE_BOOKMARKS = """ + If checked, an analysis bookmark will be created at each symbol address \ + where multiple function definitions were found and not applied."""; private static final boolean OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED = true; + + private static final String OPTION_NAME_ARCHIVE_CHOOSER = "Archive Chooser"; + private static final String OPTION_DESCRIPTION_ARCHIVE_CHOOSER = + "Specifies the data type archive to apply"; + + private static final String OPTION_NAME_GDT_FILEPATH = "GDT User File Archive Path"; + private static final String OPTION_DESCRIPTION_GDT_FILEPATH = """ + Path to a user-supplied data type archive .gdt file, \ + only valid when 'Archive Chooser' is '[User-File-Archive]'"""; + + private static final String OPTION_NAME_PROJECT_PATH = "User Project Archive Path"; + private static final String OPTION_DESCRIPTION_PROJECT_PATH = """ + Path to a user-supplied data type archive located in the project, \ + only valid when 'Archive Chooser' is '[User-Project-Archive]'"""; + + private static final String CHOOSER_AUTO_DETECT = "[Auto-Detect]"; + private static final String CHOOSER_USER_FILE_ARCHIVE = "[User-File-Archive]"; + private static final String CHOOSER_USER_PROJECT_ARCHIVE = "[User-Project-Archive]"; + private boolean createBookmarksEnabled = OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED; + private String archiveChooser = CHOOSER_AUTO_DETECT; + private File userGdtFileArchive; + private String userProjectArchive; + private Map builtinGDTs = getBuiltInGdts(); + private DataTypeManagerService dtmService; public ApplyDataArchiveAnalyzer() { super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); @@ -50,52 +84,24 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer { @Override public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) { - AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program); - DataTypeManagerService service = mgr.getDataTypeManagerService(); + dtmService = AutoAnalysisManager.getAnalysisManager(program).getDataTypeManagerService(); - // pick the archives to apply - List archiveList = DataTypeArchiveUtility.getArchiveList(program); - List managerList = new ArrayList<>(); - monitor.initialize(archiveList.size()); + // pick the archives to apply, typically 0 or 1 + List managerList = getDataTypeArchives(program, log, monitor); - // apply the archive restricted to the address set - for (String archiveName : archiveList) { - if (monitor.isCancelled()) { - break; - } - DataTypeManager dtm = null; - try { - dtm = service.openDataTypeArchive(archiveName); - if (dtm == null) { - log.appendMsg("Apply Data Archives", - "Failed to locate data type archive: " + archiveName); - } - else { - managerList.add(dtm); - } - } - catch (Exception e) { - Throwable cause = e.getCause(); - if (cause instanceof VersionException) { - log.appendMsg("Apply Data Archives", - "Unable to open archive " + archiveName + ": " + cause.toString()); - } - else { - String msg = e.getMessage(); - if (msg == null) { - msg = e.toString(); - } - log.appendMsg("Apply Data Archives", - "Unexpected Error opening archive " + archiveName + ": " + msg); - } + if (!managerList.isEmpty()) { + monitor.setMessage("Applying Function Signatures..."); + + // TODO: SourceType of imported is not exactly right here. + // This isn't imported. Need to add some other sourceType, like SecondaryInfo + ApplyFunctionDataTypesCmd cmd = new ApplyFunctionDataTypesCmd(managerList, set, + SourceType.IMPORTED, false, createBookmarksEnabled); + cmd.applyTo(program, monitor); + + for (DataTypeManager dtm : managerList) { + Msg.info(this, "Applied data type archive: %s".formatted(dtm.getName())); } } - monitor.setMessage("Applying Function Signatures..."); - // TODO: SourceType of imported is not exactly right here. - // This isn't imported. Need to add some other sourceType, like SecondaryInfo - ApplyFunctionDataTypesCmd cmd = new ApplyFunctionDataTypesCmd(managerList, set, - SourceType.IMPORTED, false, createBookmarksEnabled); - cmd.applyTo(program, monitor); return true; } @@ -103,12 +109,177 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer { public void registerOptions(Options options, Program program) { options.registerOption(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled, null, OPTION_DESCRIPTION_CREATE_BOOKMARKS); + + List chooserList = new ArrayList<>(); + chooserList.add(CHOOSER_AUTO_DETECT); + chooserList.add(CHOOSER_USER_FILE_ARCHIVE); + chooserList.add(CHOOSER_USER_PROJECT_ARCHIVE); + chooserList.addAll(builtinGDTs.keySet()); + + options.registerOption(OPTION_NAME_ARCHIVE_CHOOSER, OptionType.STRING_TYPE, + CHOOSER_AUTO_DETECT, null, OPTION_DESCRIPTION_ARCHIVE_CHOOSER, + new StringWithChoicesEditor(chooserList)); + + options.registerOption(OPTION_NAME_GDT_FILEPATH, OptionType.FILE_TYPE, null, null, + OPTION_DESCRIPTION_GDT_FILEPATH, + new FileChooserEditor(FileDataTypeManager.GDT_FILEFILTER)); + options.registerOption(OPTION_NAME_PROJECT_PATH, OptionType.STRING_TYPE, null, null, + OPTION_DESCRIPTION_PROJECT_PATH, new ProjectPathChooserEditor( + "Choose Data Type Archive", DATATYPEARCHIVE_PROJECT_FILTER)); } @Override public void optionsChanged(Options options, Program program) { createBookmarksEnabled = options.getBoolean(OPTION_NAME_CREATE_BOOKMARKS, createBookmarksEnabled); + archiveChooser = options.getString(OPTION_NAME_ARCHIVE_CHOOSER, archiveChooser); + userGdtFileArchive = options.getFile(OPTION_NAME_GDT_FILEPATH, userGdtFileArchive); + userProjectArchive = options.getString(OPTION_NAME_PROJECT_PATH, userProjectArchive); } + private List getDataTypeArchives(Program program, MessageLog log, + TaskMonitor monitor) { + switch (archiveChooser) { + case CHOOSER_AUTO_DETECT: + return getAutoDTMs(program, log, monitor); + case CHOOSER_USER_FILE_ARCHIVE: + return openUserFileArchive(userGdtFileArchive, log); + case CHOOSER_USER_PROJECT_ARCHIVE: + return openUserProjectArchive(userProjectArchive, program, log, monitor); + default: + return openBuiltinGDT(archiveChooser, log); + } + } + + private List getAutoDTMs(Program program, MessageLog log, + TaskMonitor monitor) { + List archiveList = DataTypeArchiveUtility.getArchiveList(program); + List result = new ArrayList<>(); + monitor.initialize(archiveList.size()); + + for (String archiveName : archiveList) { + if (monitor.isCancelled()) { + break; + } + try { + DataTypeManager dtm = dtmService.openDataTypeArchive(archiveName); + if (dtm == null) { + log.appendMsg("Apply Data Archives", + "Failed to locate data type archive: " + archiveName); + } + else { + result.add(dtm); + } + } + catch (Exception e) { + Throwable cause = e.getCause(); + if (cause instanceof VersionException) { + log.appendMsg("Apply Data Archives", + "Unable to open archive %s: %s".formatted(archiveName, cause.toString())); + } + else { + String msg = Objects.requireNonNullElse(e.getMessage(), e.toString()); + log.appendMsg("Apply Data Archives", + "Unexpected Error opening archive %s: %s".formatted(archiveName, msg)); + } + } + } + return result; + } + + private List openBuiltinGDT(String gdtName, MessageLog log) { + // opens a gdt that was included in the ghidra distro, not the 'built-in' dtm. + + ResourceFile gdtFile = builtinGDTs.get(gdtName); + if (gdtFile == null) { + log.appendMsg("Unknown built-in archive: %s".formatted(gdtName)); + return List.of(); + } + try { + return List.of(dtmService.openArchive(gdtFile, false)); + } + catch (Exception e) { + Throwable cause = e.getCause(); + if (cause instanceof VersionException) { + log.appendMsg("Apply Data Archives", + "Unable to open archive %s: %s".formatted(gdtName, cause.toString())); + } + else { + String msg = Objects.requireNonNullElse(e.getMessage(), e.toString()); + log.appendMsg("Apply Data Archives", + "Unexpected Error opening archive %s: %s".formatted(gdtName, msg)); + } + } + return List.of(); + } + + private List openUserFileArchive(File gdtFile, MessageLog log) { + if (gdtFile == null) { + return List.of(); + } + if (!gdtFile.isFile()) { + log.appendMsg("Missing archive: %s".formatted(gdtFile)); + return List.of(); + } + try { + return List.of(dtmService.openArchive(new ResourceFile(gdtFile), false)); + } + catch (Exception e) { + Throwable cause = e.getCause(); + if (cause instanceof VersionException) { + log.appendMsg("Apply Data Archives", + "Unable to open archive %s: %s".formatted(gdtFile, cause.toString())); + } + else { + String msg = Objects.requireNonNullElse(e.getMessage(), e.toString()); + log.appendMsg("Apply Data Archives", + "Unexpected Error opening archive %s: %s".formatted(gdtFile, msg)); + } + } + return List.of(); + } + + private List openUserProjectArchive(String filename, Program program, + MessageLog log, TaskMonitor monitor) { + if (filename == null || filename.isBlank()) { + return List.of(); + } + ProjectData projectData = program.getDomainFile().getParent().getProjectData(); + DomainFile gdtDomainFile = projectData.getFile(filename); + if (gdtDomainFile == null) { + log.appendMsg("Missing project archive: %s".formatted(filename)); + return List.of(); + } + if (!DataTypeArchive.class.isAssignableFrom(gdtDomainFile.getDomainObjectClass())) { + log.appendMsg("Bad project file type: %s".formatted(filename)); + return List.of(); + } + + try { + return List.of(dtmService.openArchive(gdtDomainFile, monitor)); + } + catch (Exception e) { + Throwable cause = e.getCause(); + if (cause instanceof VersionException) { + log.appendMsg("Apply Data Archives", + "Unable to open project archive %s: %s".formatted(filename, cause.toString())); + } + else { + String msg = Objects.requireNonNullElse(e.getMessage(), e.toString()); + log.appendMsg("Apply Data Archives", + "Unexpected Error opening project archive %s: %s".formatted(filename, msg)); + } + } + return List.of(); + } + //--------------------------------------------------------------------------------------------- + + private static Map getBuiltInGdts() { + return Application.findFilesByExtensionInApplication(".gdt") + .stream() + .collect(Collectors.toMap(f -> f.getName(), f -> f)); + } + + private static final DomainFileFilter DATATYPEARCHIVE_PROJECT_FILTER = + df -> DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DefaultDataTypeManagerService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DefaultDataTypeManagerService.java index 176b7e00b6..83a307202e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DefaultDataTypeManagerService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DefaultDataTypeManagerService.java @@ -15,116 +15,27 @@ */ package ghidra.app.plugin.core.analysis; -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.Map.Entry; +import java.util.List; +import java.util.Set; import javax.swing.tree.TreePath; -import generic.jar.ResourceFile; -import ghidra.app.plugin.core.datamgr.archive.*; -import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility; +import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive; +import ghidra.app.plugin.core.datamgr.archive.DefaultDataTypeArchiveService; import ghidra.app.plugin.core.datamgr.util.DataTypeComparator; import ghidra.app.services.DataTypeManagerService; -import ghidra.program.model.data.*; -import ghidra.program.model.listing.DataTypeArchive; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManagerChangeListener; import ghidra.util.HelpLocation; -import ghidra.util.UniversalID; // FIXME!! TESTING -public class DefaultDataTypeManagerService implements DataTypeManagerService { - - private Map archiveMap = new HashMap<>(); - private DataTypeManager builtInDataTypesManager = BuiltInDataTypeManager.getDataTypeManager(); - - void dispose() { - for (FileDataTypeManager dtfm : archiveMap.values()) { - dtfm.close(); - } - archiveMap.clear(); - } +public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService + implements DataTypeManagerService { // TODO: This implementation needs to be consolidated with the tool-based service in // favor of a single static data type manager service used by both the tool and // headless scenarios - private FileDataTypeManager findOpenFileArchiveWithID(UniversalID universalID) { - if (universalID == null) { - return null; - } - for (FileDataTypeManager dtm : archiveMap.values()) { - if (universalID.equals(dtm.getUniversalID()) && !dtm.isClosed()) { - return dtm; - } - } - return null; - } - - @Override - public synchronized DataTypeManager openDataTypeArchive(String archiveName) - throws IOException, DuplicateIdException { - - if (archiveMap.containsKey(archiveName)) { - FileDataTypeManager dtm = archiveMap.get(archiveName); - if (!dtm.isClosed()) { - return dtm; - } - archiveMap.remove(archiveName); - } - - ResourceFile archiveFile = DataTypeArchiveUtility.findArchiveFile(archiveName); - if (archiveFile == null) { - return null; - } - - FileDataTypeManager fileDtm = FileDataTypeManager.openFileArchive(archiveFile, false); - - FileDataTypeManager existingDtm = findOpenFileArchiveWithID(fileDtm.getUniversalID()); - if (existingDtm != null) { - fileDtm.close(); - throw new DuplicateIdException(fileDtm.getName(), existingDtm.getName()); - } - - archiveMap.put(archiveName, fileDtm); - - return fileDtm; - } - - @Override - public void closeArchive(DataTypeManager dtm) { - - String archiveName = null; - Set> entries = archiveMap.entrySet(); - for (Entry entry : entries) { - FileDataTypeManager manager = entry.getValue(); - if (manager.equals(dtm)) { - archiveName = entry.getKey(); - break; - } - - } - - if (archiveName != null) { - FileDataTypeManager manager = archiveMap.get(archiveName); - archiveMap.remove(archiveName); - manager.close(); - } - } - - @Override - public DataTypeManager[] getDataTypeManagers() { - ArrayList dtmList = new ArrayList<>(); - for (FileDataTypeManager dtm : archiveMap.values()) { - if (!dtm.isClosed()) { - dtmList.add(dtm); - } - } - DataTypeManager[] managers = new DataTypeManager[dtmList.size()]; - dtmList.toArray(managers); - return managers; - } - @Override public HelpLocation getEditorHelpLocation(DataType dataType) { throw new UnsupportedOperationException(); @@ -145,11 +56,6 @@ public class DefaultDataTypeManagerService implements DataTypeManagerService { throw new UnsupportedOperationException(); } - @Override - public DataTypeManager getBuiltInDataTypesManager() { - return builtInDataTypesManager; - } - @Override public DataType getDataType(String filterText) { throw new UnsupportedOperationException(); @@ -206,14 +112,4 @@ public class DefaultDataTypeManagerService implements DataTypeManagerService { public Set getPossibleEquateNames(long value) { throw new UnsupportedOperationException(); } - - @Override - public Archive openArchive(File file, boolean acquireWriteLock) { - throw new UnsupportedOperationException(); - } - - @Override - public Archive openArchive(DataTypeArchive dataTypeArchive) { - throw new UnsupportedOperationException(); - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ProjectPathChooserEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ProjectPathChooserEditor.java new file mode 100644 index 0000000000..e836156bc7 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ProjectPathChooserEditor.java @@ -0,0 +1,164 @@ +/* ### + * 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.app.plugin.core.analysis; + +import java.awt.Component; +import java.awt.event.MouseListener; +import java.beans.PropertyEditorSupport; +import java.util.concurrent.atomic.AtomicReference; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import org.apache.commons.lang3.StringUtils; + +import docking.widgets.button.BrowseButton; +import ghidra.framework.main.DataTreeDialog; +import ghidra.framework.model.DomainFile; +import ghidra.framework.model.DomainFileFilter; + +/** + * Bean editor to show a text field and a browse button to bring + * up a Domain File Chooser dialog. The path of the chosen domain file is returned as + * a String value. + */ +public class ProjectPathChooserEditor extends PropertyEditorSupport { + + private final static int NUMBER_OF_COLUMNS = 20; + private JTextField textField = new JTextField(NUMBER_OF_COLUMNS); + private MouseListener otherMouseListener; + private String title; + private DomainFileFilter filter; + + public ProjectPathChooserEditor() { + this(null, null); + } + + public ProjectPathChooserEditor(String title, DomainFileFilter filter) { + this.title = title; + this.filter = filter; + } + + @Override + public String getAsText() { + return textField.getText().trim(); + } + + @Override + public Object getValue() { + String text = getAsText(); + if (StringUtils.isBlank(text)) { + return null; + } + return text; + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (text == null || text.trim().isEmpty()) { + text = ""; + } + + textField.setText(text); + } + + @Override + public void setValue(Object value) { + if (value == null) { + setAsText(""); + } + else if (value instanceof String s) { + setAsText(s); + } + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + @Override + public Component getCustomEditor() { + return new ProjectFileChooserPanel(); + } + + void setMouseListener(MouseListener listener) { + this.otherMouseListener = listener; + } + + private class ProjectFileChooserPanel extends JPanel { + private JButton browseButton; + + private ProjectFileChooserPanel() { + BoxLayout bl = new BoxLayout(this, BoxLayout.X_AXIS); + setLayout(bl); + + browseButton = new BrowseButton(); + + add(textField); + add(Box.createHorizontalStrut(5)); + add(browseButton); + setBorder(BorderFactory.createEmptyBorder()); + textField.addActionListener(e -> ProjectPathChooserEditor.this.firePropertyChange()); + textField.getDocument().addDocumentListener(new TextListener()); + + browseButton.addActionListener(e -> displayFileChooser()); + if (otherMouseListener != null) { + textField.addMouseListener(otherMouseListener); + browseButton.addMouseListener(otherMouseListener); + } + } + + private void displayFileChooser() { + AtomicReference result = new AtomicReference<>(); + DataTreeDialog dataTreeDialog = + new DataTreeDialog(this, title, DataTreeDialog.OPEN, filter); + dataTreeDialog.addOkActionListener(e -> { + dataTreeDialog.close(); + DomainFile df = dataTreeDialog.getDomainFile(); + result.set(df != null ? df.getPathname() : null); + }); + dataTreeDialog.showComponent(); + + String newPath = result.get(); + if (newPath != null) { + textField.setText(newPath); + ProjectPathChooserEditor.this.firePropertyChange(); + } + } + } + + private class TextListener implements DocumentListener { + + @Override + public void changedUpdate(DocumentEvent e) { + ProjectPathChooserEditor.this.firePropertyChange(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + ProjectPathChooserEditor.this.firePropertyChange(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + ProjectPathChooserEditor.this.firePropertyChange(); + } + + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java index d8632e17e8..f3e1b3804a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java @@ -45,8 +45,7 @@ import ghidra.app.plugin.core.datamgr.editor.DataTypeEditorManager; import ghidra.app.plugin.core.datamgr.tree.ArchiveNode; import ghidra.app.plugin.core.datamgr.util.DataDropOnBrowserHandler; import ghidra.app.plugin.core.datamgr.util.DataTypeChooserDialog; -import ghidra.app.services.CodeViewerService; -import ghidra.app.services.DataTypeManagerService; +import ghidra.app.services.*; import ghidra.app.util.HelpTopics; import ghidra.framework.Application; import ghidra.framework.main.OpenVersionedFileDialog; @@ -62,7 +61,10 @@ import ghidra.program.model.listing.DataTypeArchive; import ghidra.program.model.listing.Program; import ghidra.util.*; import ghidra.util.datastruct.LRUMap; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; import ghidra.util.task.TaskLauncher; +import ghidra.util.task.TaskMonitor; /** * Plugin to pop up the dialog to manage data types in the program @@ -78,11 +80,12 @@ import ghidra.util.task.TaskLauncher; description = "Provides the window for managing and categorizing dataTypes. " + "The datatype display shows all built-in datatypes, datatypes in the " + "current program, and datatypes in all open archives.", - servicesProvided = { DataTypeManagerService.class } + servicesProvided = { DataTypeManagerService.class, DataTypeArchiveService.class } ) //@formatter:on public class DataTypeManagerPlugin extends ProgramPlugin - implements DomainObjectListener, DataTypeManagerService, PopupActionProvider { + implements DomainObjectListener, DataTypeManagerService, DataTypeArchiveService, + PopupActionProvider { private static final String EXTENSIONS_PATH_PREFIX = Path.GHIDRA_HOME + "/Extensions"; @@ -557,6 +560,20 @@ public class DataTypeManagerPlugin extends ProgramPlugin return dataTypeManagerHandler.openArchive(archiveName); } + @Override + public DataTypeManager openArchive(ResourceFile file, boolean acquireWriteLock) + throws IOException, DuplicateIdException { + Archive archive = openArchive(file.getFile(true), acquireWriteLock); + return archive.getDataTypeManager(); + } + + @Override + public DataTypeManager openArchive(DomainFile domainFile, TaskMonitor monitor) + throws VersionException, CancelledException, IOException, DuplicateIdException { + DataTypeArchive archive = openArchive(domainFile); + return archive.getDataTypeManager(); + } + public List getAllArchives() { return dataTypeManagerHandler.getAllArchives(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/OpenArchiveAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/OpenArchiveAction.java index ff7067b22a..e30084731a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/OpenArchiveAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/OpenArchiveAction.java @@ -36,7 +36,6 @@ import ghidra.framework.GenericRunInfo; import ghidra.framework.preferences.Preferences; import ghidra.program.model.data.FileDataTypeManager; import ghidra.util.Msg; -import ghidra.util.filechooser.ExtensionFileFilter; public class OpenArchiveAction extends DockingAction { @@ -59,8 +58,7 @@ public class OpenArchiveAction extends DockingAction { GhidraFileChooser fileChooser = new GhidraFileChooser(tree); File archiveDirectory = getArchiveDirectory(); - fileChooser.setFileFilter(new ExtensionFileFilter( - new String[] { FileDataTypeManager.EXTENSION }, "Ghidra Data Type Files")); + fileChooser.setFileFilter(FileDataTypeManager.GDT_FILEFILTER); fileChooser.setCurrentDirectory(archiveDirectory); fileChooser.setApproveButtonText("Open DataType Archive File"); fileChooser.setApproveButtonToolTipText("Open DataType Archive File"); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DefaultDataTypeArchiveService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DefaultDataTypeArchiveService.java new file mode 100644 index 0000000000..0b3c536cde --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DefaultDataTypeArchiveService.java @@ -0,0 +1,231 @@ +/* ### + * 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.app.plugin.core.datamgr.archive; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +import generic.jar.ResourceFile; +import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility; +import ghidra.app.services.DataTypeArchiveService; +import ghidra.framework.model.DomainFile; +import ghidra.framework.model.DomainObject; +import ghidra.program.database.data.ProgramDataTypeManager; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.DataTypeArchive; +import ghidra.util.Msg; +import ghidra.util.UniversalID; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; +import ghidra.util.task.TaskMonitor; + +/** + * Simple, non-ui implementation of the {@link DataTypeArchiveService} interface. + */ +public class DefaultDataTypeArchiveService implements DataTypeArchiveService { + protected record DataTypeManagerInfo(ResourceFile file, DomainObject domainObject, String name, + DataTypeManager dtm) { + public boolean isClosed() { + return dtm instanceof FileDataTypeManager fdtm + ? fdtm.isClosed() + : false; + } + } + + protected Map openDTMs = new HashMap<>(); + protected BuiltInDataTypeManager builtInDataTypesManager; + + public DefaultDataTypeArchiveService() { + this.builtInDataTypesManager = BuiltInDataTypeManager.getDataTypeManager(); + } + + public synchronized void dispose() { + for (DataTypeManagerInfo dtmInfo : new ArrayList<>(openDTMs.values())) { + closeDTM(dtmInfo); // mutates openDTMs map + } + } + + @Override + public DataTypeManager getBuiltInDataTypesManager() { + return builtInDataTypesManager; + } + + @Override + public DataTypeManager[] getDataTypeManagers() { + List dtmList = openDTMs.values() + .stream() + .filter(dtmInfo -> !dtmInfo.isClosed()) + .map(dtmInfo -> dtmInfo.dtm()) + .collect(Collectors.toList()); + return dtmList.toArray(DataTypeManager[]::new); + } + + @Override + public synchronized void closeArchive(DataTypeManager dtm) { + if (dtm instanceof BuiltInDataTypeManager) { + Msg.info(this, "Cannot close the built-in Data Type Manager"); + return; + } + + if (dtm instanceof ProgramDataTypeManager) { + Msg.info(this, "Cannot close the Program's Data Type Manager"); + return; + } + + DataTypeManagerInfo dtmInfo = openDTMs.get(dtm.getUniversalID()); + if (dtmInfo == null) { + Msg.info(this, "Unable close archive; archive not open: '%s'".formatted(dtm.getName())); + return; + } + + beforeCloseDataTypeManager(dtmInfo); + openDTMs.remove(dtm.getUniversalID()); + + if (dtmInfo.domainObject != null) { + dtmInfo.domainObject.release(this); + } + dtmInfo.dtm.close(); + + afterCloseDataTypeManager(dtmInfo); + } + + @Override + public Archive openArchive(DataTypeArchive dataTypeArchive) { + throw new UnsupportedOperationException(); + } + + @Override + public Archive openArchive(File file, boolean acquireWriteLock) + throws IOException, DuplicateIdException { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeManager openDataTypeArchive(String archiveName) + throws IOException, DuplicateIdException { + ResourceFile file = DataTypeArchiveUtility.findArchiveFile(archiveName); + if (file != null) { + return openArchive(file, false); + } + return null; + } + + @Override + public synchronized DataTypeManager openArchive(DomainFile domainFile, TaskMonitor monitor) + throws VersionException, CancelledException, IOException, DuplicateIdException { + + if (!DataTypeArchive.class.isAssignableFrom(domainFile.getDomainObjectClass())) { + throw new IOException("Unable to open domain file: '%s', not a data type archive" + .formatted(domainFile.getName())); + } + + DataTypeManagerInfo dtmInfo = getOpenDTMInfo(domainFile); + if (dtmInfo == null) { + DataTypeArchive dta = openDomainFile(domainFile, monitor); + DataTypeManager dtm = dta.getDataTypeManager(); + dtmInfo = addDTM(new DataTypeManagerInfo(null, dta, domainFile.getPathname(), dtm)); + } + + return dtmInfo.dtm; + } + + protected DataTypeArchive openDomainFile(DomainFile domainFile, TaskMonitor monitor) + throws VersionException, CancelledException, IOException { + DataTypeArchive dta = + (DataTypeArchive) domainFile.getDomainObject(this, false, false, monitor); + return dta; + } + + @Override + public synchronized DataTypeManager openArchive(ResourceFile file, boolean acquireWriteLock) + throws IOException, DuplicateIdException { + + file = file.getCanonicalFile(); + DataTypeManagerInfo dtmInfo = getOpenDTMInfo(file); + if (dtmInfo == null) { + FileDataTypeManager fileDTM = + FileDataTypeManager.openFileArchive(file, acquireWriteLock); + + dtmInfo = addDTM(new DataTypeManagerInfo(file, null, file.getName(), fileDTM)); + } + return dtmInfo.dtm; + } + + protected DataTypeManagerInfo addDTM(DataTypeManagerInfo dtmInfo) throws DuplicateIdException { + DataTypeManagerInfo existingDTM = openDTMs.get(dtmInfo.dtm.getUniversalID()); + if (existingDTM != null) { + dtmInfo.dtm.close(); + throw new DuplicateIdException(dtmInfo.name(), existingDTM.name()); + } + openDTMs.put(dtmInfo.dtm.getUniversalID(), dtmInfo); + afterAddDataTypeManager(dtmInfo); + return dtmInfo; + } + + protected void closeDTM(DataTypeManagerInfo dtmInfo) { + beforeCloseDataTypeManager(dtmInfo); + openDTMs.remove(dtmInfo.dtm.getUniversalID()); + + if (dtmInfo.domainObject != null) { + dtmInfo.domainObject.release(this); + } + dtmInfo.dtm.close(); + + afterCloseDataTypeManager(dtmInfo); + } + + private DataTypeManagerInfo getOpenDTMInfo(ResourceFile file) { + for (DataTypeManagerInfo dtmInfo : openDTMs.values()) { + if (dtmInfo.file != null && dtmInfo.file.equals(file)) { + if (dtmInfo.isClosed()) { + openDTMs.remove(dtmInfo.dtm.getUniversalID()); + return null; + } + return dtmInfo; + } + } + return null; + } + + private DataTypeManagerInfo getOpenDTMInfo(DomainFile projectFile) { + for (DataTypeManagerInfo dtmInfo : openDTMs.values()) { + if (dtmInfo.domainObject != null && + dtmInfo.domainObject.getDomainFile().equals(projectFile)) { + if (dtmInfo.isClosed()) { + openDTMs.remove(dtmInfo.dtm.getUniversalID()); + return null; + } + return dtmInfo; + } + } + return null; + } + + protected void afterAddDataTypeManager(DataTypeManagerInfo dtmInfo) { + // override as needed + } + + protected void afterCloseDataTypeManager(DataTypeManagerInfo dtmInfo) { + // override as needed + } + + protected void beforeCloseDataTypeManager(DataTypeManagerInfo dtmInfo) { + // override as needed + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeArchiveService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeArchiveService.java new file mode 100644 index 0000000000..3da219b6f6 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeArchiveService.java @@ -0,0 +1,132 @@ +/* ### + * 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.app.services; + +import java.io.File; +import java.io.IOException; + +import generic.jar.ResourceFile; +import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.app.plugin.core.datamgr.archive.Archive; +import ghidra.app.plugin.core.datamgr.archive.DuplicateIdException; +import ghidra.framework.model.DomainFile; +import ghidra.framework.plugintool.ServiceInfo; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.listing.DataTypeArchive; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; +import ghidra.util.task.TaskMonitor; + +/** + * A service that manages a set of data type archives, allowing re-use of already open + * archives. + */ +//@formatter:off +@ServiceInfo( + defaultProvider = DataTypeManagerPlugin.class, + description = "Service to manipulate the set of active Data Type Managers" +) +//@formatter:on +public interface DataTypeArchiveService { + + /** + * Get the data type manager that has all of the built in types. + * @return data type manager for built in data types + */ + public DataTypeManager getBuiltInDataTypesManager(); + + /** + * Gets the open data type managers. + * + * @return the open data type managers. + */ + public DataTypeManager[] getDataTypeManagers(); + + /** + * Closes the archive for the given {@link DataTypeManager}. This will ignore request to + * close the open Program's manager and the built-in manager. + * + * @param dtm the data type manager of the archive to close + */ + public void closeArchive(DataTypeManager dtm); + + /** + * Opens a data type archive that was built into the Ghidra installation. + *

+ * NOTE: This is predicated upon all archive files having a unique name within the installation. + *

+ * Any path prefix specified may prevent the file from opening (or reopening) correctly. + * + * @param archiveName archive file name (i.e., "generic_C_lib") + * @return the data type archive or null if an archive with the specified name + * can not be found. + * @throws IOException if an i/o error occurs opening the data type archive + * @throws DuplicateIdException if another archive with the same ID is already open + */ + public DataTypeManager openDataTypeArchive(String archiveName) + throws IOException, DuplicateIdException; + + /** + * Opens the specified gdt (file based) data type archive. + * + * @param file gdt file + * @param acquireWriteLock true if write lock should be acquired (i.e., open for update) + * @return the data type archive + * @throws IOException if an i/o error occurs opening the data type archive + * @throws DuplicateIdException if another archive with the same ID is already open + */ + public DataTypeManager openArchive(ResourceFile file, boolean acquireWriteLock) + throws IOException, DuplicateIdException; + + /** + * Opens the specified project-located data type archive. + * + * @param domainFile archive file located in the current project + * @param monitor {@link TaskMonitor} to display progess during the opening + * @return the data type archive + * @throws IOException if an i/o error occurs opening the data type archive + * @throws DuplicateIdException if another archive with the same ID is already open + * @throws VersionException + * @throws CancelledException + */ + public DataTypeManager openArchive(DomainFile domainFile, TaskMonitor monitor) + throws VersionException, CancelledException, IOException, DuplicateIdException; + + /** + * A method to open an Archive for the given, pre-existing DataTypeArchive (like one that + * was opened during the import process. + * + * @param dataTypeArchive the archive from which to create an Archive + * @return an Archive based upon the given DataTypeArchive + */ + @Deprecated + public Archive openArchive(DataTypeArchive dataTypeArchive); + + /** + * A method to open an Archive for the given, pre-existing archive file (*.gdt) + * + * @param file data type archive file + * @param acquireWriteLock true if write lock should be acquired (i.e., open for update) + * @return an Archive based upon the given archive files + * @throws IOException if an i/o error occurs opening the data type archive + * @throws DuplicateIdException if another archive with the same ID is already open + */ + @Deprecated + public Archive openArchive(File file, boolean acquireWriteLock) + throws IOException, DuplicateIdException; + + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeManagerService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeManagerService.java index d9fae2fe20..7aa7a901b3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeManagerService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeManagerService.java @@ -15,19 +15,15 @@ */ package ghidra.app.services; -import java.io.File; -import java.io.IOException; import java.util.List; import java.util.Set; import javax.swing.tree.TreePath; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; -import ghidra.app.plugin.core.datamgr.archive.Archive; -import ghidra.app.plugin.core.datamgr.archive.DuplicateIdException; import ghidra.framework.plugintool.ServiceInfo; -import ghidra.program.model.data.*; -import ghidra.program.model.listing.DataTypeArchive; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManagerChangeListener; import ghidra.util.HelpLocation; /** @@ -35,14 +31,13 @@ import ghidra.util.HelpLocation; * "favorites." Favorites will show up on the popup menu for creating * data and defining function return types and parameters. */ -@ServiceInfo(defaultProvider = DataTypeManagerPlugin.class, description = "Service to provide list of cycle groups and data types identified as 'Favorites.'") -public interface DataTypeManagerService extends DataTypeQueryService { - - /** - * Get the data type manager that has all of the built in types. - * @return data type manager for built in data types - */ - public DataTypeManager getBuiltInDataTypesManager(); +//@formatter:off +@ServiceInfo( + defaultProvider = DataTypeManagerPlugin.class, + description = "Service to provide list of cycle groups and data types identified as 'Favorites.'" +) +//@formatter:on +public interface DataTypeManagerService extends DataTypeQueryService, DataTypeArchiveService { /** * Get the data types marked as favorites that will show up on @@ -101,48 +96,6 @@ public interface DataTypeManagerService extends DataTypeQueryService { */ public void edit(DataType dt); - /** - * Closes the archive for the given {@link DataTypeManager}. This will ignore request to - * close the open Program's manager and the built-in manager. - * - * @param dtm the data type manager of the archive to close - */ - public void closeArchive(DataTypeManager dtm); - - /** - * Opens the specified data type archive contained within the Ghidra installation. - * NOTE: This is predicated upon all archive files having a unique name within the installation. - * Any path prefix specified may prevent the file from opening (or reopening) correctly. - * @param archiveName archive file name (i.e., "generic_C_lib") - * @return the data type archive or null if an archive with the specified name - * can not be found. - * @throws IOException if an i/o error occurs opening the data type archive - * @throws DuplicateIdException if another archive with the same ID is already open - */ - public DataTypeManager openDataTypeArchive(String archiveName) - throws IOException, DuplicateIdException; - - /** - * A method to open an Archive for the given, pre-existing DataTypeArchive (like one that - * was opened during the import process. - * - * @param dataTypeArchive the archive from which to create an Archive - * @return an Archive based upon the given DataTypeArchive - */ - public Archive openArchive(DataTypeArchive dataTypeArchive); - - /** - * A method to open an Archive for the given, pre-existing archive file (*.gdt) - * - * @param file data type archive file - * @param acquireWriteLock true if write lock should be acquired (i.e., open for update) - * @return an Archive based upon the given archive files - * @throws IOException if an i/o error occurs opening the data type archive - * @throws DuplicateIdException if another archive with the same ID is already open - */ - public Archive openArchive(File file, boolean acquireWriteLock) - throws IOException, DuplicateIdException; - /** * Selects the given data type in the display of data types. A null dataType * value will clear the current selection. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeQueryService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeQueryService.java index cb44234c23..9735ba95bc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeQueryService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/DataTypeQueryService.java @@ -18,7 +18,6 @@ package ghidra.app.services; import java.util.List; import ghidra.program.model.data.DataType; -import ghidra.program.model.data.DataTypeManager; /** * Simplified datatype service interface to provide query capabilities @@ -26,13 +25,6 @@ import ghidra.program.model.data.DataTypeManager; */ public interface DataTypeQueryService { - /** - * Gets the open data type managers. - * - * @return the open data type managers. - */ - public DataTypeManager[] getDataTypeManagers(); - /** * Gets the sorted list of all datatypes known by this service via it's owned DataTypeManagers. * This method can be called frequently, as the underlying data is indexed and only updated diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java index cac7e71780..fc311a53bf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java @@ -18,8 +18,6 @@ package ghidra.app.util.parser; import java.util.*; import java.util.regex.Pattern; -import javax.help.UnsupportedOperationException; - import org.apache.commons.lang3.StringUtils; import ghidra.app.services.DataTypeQueryService; @@ -348,11 +346,6 @@ public class FunctionSignatureParser { dtCache.clear(); } - @Override - public DataTypeManager[] getDataTypeManagers() { - throw new UnsupportedOperationException(); - } - @Override public List getSortedDataTypeList() { return service.getSortedDataTypeList(); diff --git a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java index 9e6e12327b..a4d132ae95 100644 --- a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java +++ b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java @@ -15,7 +15,8 @@ */ package help.screenshot; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.awt.*; import java.awt.geom.GeneralPath; @@ -45,7 +46,6 @@ import docking.widgets.fieldpanel.support.FieldLocation; import docking.widgets.table.*; import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.tree.GTree; -import generic.jar.ResourceFile; import generic.test.AbstractGenericTest; import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors.Java; @@ -138,9 +138,8 @@ public abstract class AbstractScreenShotGenerator extends AbstractGhidraHeadedIn public void loadProgram() throws Exception { loadProgram("WinHelloCPP.exe"); - ResourceFile file = TestEnv.findProvidedDataTypeArchive("windows_vs12_32.gdt"); - DataTypeManagerService dtm = tool.getService(DataTypeManagerService.class); - dtm.openArchive(file.getFile(false), false); + DataTypeManagerService dtms = tool.getService(DataTypeManagerService.class); + dtms.openDataTypeArchive("windows_vs12_32.gdt"); } public void closeNonProgramArchives() { diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDoubleDataTypeManagerService.java b/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDoubleDataTypeManagerService.java index 730109c1f9..5cd9480377 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDoubleDataTypeManagerService.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/services/TestDoubleDataTypeManagerService.java @@ -22,11 +22,16 @@ import java.util.Set; import javax.swing.tree.TreePath; +import generic.jar.ResourceFile; import ghidra.app.plugin.core.datamgr.archive.Archive; import ghidra.app.plugin.core.datamgr.archive.DuplicateIdException; +import ghidra.framework.model.DomainFile; import ghidra.program.model.data.*; import ghidra.program.model.listing.DataTypeArchive; import ghidra.util.HelpLocation; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; +import ghidra.util.task.TaskMonitor; /** * A stub of the {@link DataTypeManagerService} interface. This can be used to supply a test values @@ -135,4 +140,16 @@ public class TestDoubleDataTypeManagerService implements DataTypeManagerService public Set getPossibleEquateNames(long value) { throw new UnsupportedOperationException(); } + + @Override + public DataTypeManager openArchive(ResourceFile file, boolean acquireWriteLock) + throws IOException, DuplicateIdException { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeManager openArchive(DomainFile domainFile, TaskMonitor monitor) + throws VersionException, CancelledException, IOException, DuplicateIdException { + throw new UnsupportedOperationException(); + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/FileChooserEditor.java b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/FileChooserEditor.java index 53b46e104d..02d3af82d6 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/FileChooserEditor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/FileChooserEditor.java @@ -29,6 +29,7 @@ import org.apache.commons.lang3.StringUtils; import docking.widgets.button.BrowseButton; import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooserMode; +import ghidra.util.filechooser.GhidraFileFilter; /** * Bean editor to show a text field and a browse button to bring @@ -42,8 +43,17 @@ public class FileChooserEditor extends PropertyEditorSupport { private JTextField textField = new JTextField(NUMBER_OF_COLUMNS); private File currentDir; private GhidraFileChooser fileChooser; + private GhidraFileFilter fileFilter; private MouseListener otherMouseListener; + public FileChooserEditor() { + this(null); + } + + public FileChooserEditor(GhidraFileFilter fileFilter) { + this.fileFilter = fileFilter; + } + @Override public String getAsText() { return textField.getText().trim(); @@ -74,14 +84,14 @@ public class FileChooserEditor extends PropertyEditorSupport { public void setValue(Object value) { if (value == null) { currentFileValue = null; + textField.setText(""); } - - if (value instanceof File) { + else if (value instanceof File) { currentFileValue = (File) value; textField.setText(currentFileValue.getAbsolutePath()); } - else if (value instanceof String) { - setAsText((String) value); + else if (value instanceof String s) { + setAsText(s); } } @@ -162,6 +172,9 @@ public class FileChooserEditor extends PropertyEditorSupport { fileChooser.setApproveButtonText("Choose Path"); fileChooser.setTitle("Choose Path"); fileChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES); + if (fileFilter != null) { + fileChooser.setFileFilter(fileFilter); + } if (currentFileValue != null) { fileChooser.setSelectedFile(currentFileValue); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/FileDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/FileDataTypeManager.java index 1a9323c165..97cad08610 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/FileDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/FileDataTypeManager.java @@ -27,6 +27,8 @@ import ghidra.program.model.lang.Language; import ghidra.util.InvalidNameException; import ghidra.util.UniversalID; import ghidra.util.exception.*; +import ghidra.util.filechooser.ExtensionFileFilter; +import ghidra.util.filechooser.GhidraFileFilter; import ghidra.util.task.TaskMonitor; /** @@ -37,6 +39,9 @@ public class FileDataTypeManager extends StandAloneDataTypeManager implements FileArchiveBasedDataTypeManager { public final static String EXTENSION = "gdt"; // Ghidra Data Types + public static final GhidraFileFilter GDT_FILEFILTER = + ExtensionFileFilter.forExtensions("Ghidra Data Type Files", EXTENSION); + /** * Suffix for an archive file. */