Merge branch 'GP-3551_ghidra1_InternalProjectLinks'

This commit is contained in:
ghidra1
2025-07-09 13:34:56 -04:00
209 changed files with 10096 additions and 3050 deletions
@@ -15,6 +15,43 @@ applied Ghidra SRE capabilities to a variety of problems that involve analyzing
generating deep insights for NSA analysts who seek a better understanding of potential
vulnerabilities in networks and systems.
# What's coming in Ghidra 11.5
This is a preview of what is coming in the future Ghidra 11.5 release.
**NOTE:** Ghidra Server: The Ghidra 11.5 server is compatible with Ghidra 9.2 and later Ghidra
clients although the presence of any newer link-files within a repository may not be handled properly
by client versions prior to 11.5 which lack support for the new storage format. Ghidra 11.5 clients
which introduce new link-files into a project will not be able to add such files into version
control if connected to older Ghidra Server versions.
## Project Link Files
Support for link-files within a Ghidra Project has been significantly expanded with this release and
with it a new file storage type has been introduced which can create some incompatibilties if projects
and repositories containing such files are used by older version of Ghidra or the Ghidra Server.
Previously only external folder and file links were supported through the use of a Ghidra URL.
With 11.5 the ability to establish internal folder and file links has been introduced. The new
storage format avoids the use of a database and relies only on a light-weight property file. Internal
project links also allow for either absolute or relative links. Due to the fact that Ghidra allows
a folder or file to have the same pathname, some abiguities can result. It is highly recommended that
the use of conflicting folder and file pathnames be avoided.
The use of internally linked folders and files allows batch import processing to more accurately
reflect the native file-system and its use of symbolic links which allow for the same content to
be referenced by multiple paths. Allowing this within a Ghidra project can avoid the potential for
importing content multiple times with the different paths and simply import once with additional
link-files which reference it. How best to leverage links very much depends on the end-user's
needs and project file management preferences. Special care must be taken when defining or
traversing link-files to avoid external and circular references.
Additional Ghidra API methods have been provided or refined on the following classes to leverage
link-files: `DomainFolder`, `DomainFile`, `LinkFile`, `LinkHandler`, `DomainFileFilter`,
`DomainFileIterator`, etc.
...TO BE CONTINUED...
# What's New in Ghidra 11.4
This release includes new features, enhancements, performance improvements, quite a few bug fixes,
and many pull-request contributions. Thanks to all those who have contributed their time, thoughts,
@@ -4,9 +4,9 @@
* 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.
@@ -116,10 +116,8 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
? view.getTrace().getFixedProgramView(view.getSnap())
: view;
ExporterDialog dialog =
new ExporterDialog(tool, fixed.getDomainFile(), fixed,
getSelectionFromContext(context));
tool.showDialog(dialog);
ExporterDialog.showExporterDialog(tool, fixed.getDomainFile(), fixed,
getSelectionFromContext(context));
}
protected void activatedCopyIntoCurrentProgram(DebuggerProgramLocationActionContext context) {
@@ -26,6 +26,7 @@ import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.framework.model.*;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramContentHandler;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Lifespan;
@@ -73,7 +74,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
// TODO: Note language and prefer those from the same processor?
// Will get difficult with new OBTR, since I'd need a platform
// There's also the WoW64 issue....
protected record IndexEntry(String name, String dfID, NameSource source) {}
protected record IndexEntry(String name, String dfID, NameSource source) {
}
protected class ModuleChangeListener
implements DomainObjectListener, DomainObjectClosedListener {
@@ -212,10 +214,13 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
if (disposed) {
return;
}
if (!Program.class.isAssignableFrom(file.getDomainObjectClass())) {
return;
// Folder-links and program link-files are not handled. Using content type
// to filter is the best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
addToIndex(file, file.getMetadata());
}
addToIndex(file, file.getMetadata());
}
protected void addToIndex(DomainFile file, Map<String, String> metadata) {
@@ -383,9 +388,9 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
public DomainFile getBestMatch(TraceModule module, long snap, Program currentProgram,
Collection<IndexEntry> entries) {
Address base = module.getBase(snap);
AddressSpace space = base == null
? module.getTrace().getBaseAddressFactory().getDefaultAddressSpace()
: base.getAddressSpace();
AddressSpace space =
base == null ? module.getTrace().getBaseAddressFactory().getDefaultAddressSpace()
: base.getAddressSpace();
return getBestMatch(space, module, snap, currentProgram, entries);
}
@@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.service.tracemgr;
import static ghidra.framework.main.DataTreeDialogType.OPEN;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
@@ -223,9 +223,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
public void targetWithdrawn(Target target) {
Swing.runLater(() -> updateCurrentTarget());
boolean save = isSaveTracesByDefault();
CompletableFuture<Void> flush = save
? waitUnlockedDebounced(target)
: AsyncUtils.nil();
CompletableFuture<Void> flush = save ? waitUnlockedDebounced(target) : AsyncUtils.nil();
flush.thenRunAsync(() -> {
if (!isAutoCloseOnTerminate()) {
return;
@@ -416,20 +414,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
protected DataTreeDialog getTraceChooserDialog() {
DomainFileFilter filter = new DomainFileFilter() {
@Override
public boolean accept(DomainFile df) {
return Trace.class.isAssignableFrom(df.getDomainObjectClass());
}
@Override
public boolean followLinkedFolders() {
return false;
}
};
DomainFileFilter filter = new DefaultDomainFileFilter(Trace.class, false);
return new DataTreeDialog(null, OpenTraceAction.NAME, OPEN, filter);
}
@@ -454,11 +439,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
public void closeDeadTraces() {
checkCloseTraces(targetService == null
? getOpenTraces()
: getOpenTraces().stream()
.filter(t -> targetService.getTarget(t) == null)
.toList(),
checkCloseTraces(targetService == null ? getOpenTraces()
: getOpenTraces().stream().filter(t -> targetService.getTarget(t) == null).toList(),
false);
}
@@ -790,8 +772,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
varView.setSnap(snap);
varView.setPlatform(coordinates.getPlatform());
fireLocationEvent(coordinates, cause);
}, cause == ActivationCause.EMU_STATE_EDIT
? SwingExecutorService.MAYBE_NOW // ProgramView may call .get on Swing thread
}, cause == ActivationCause.EMU_STATE_EDIT ? SwingExecutorService.MAYBE_NOW // ProgramView may call .get on Swing thread
: SwingExecutorService.LATER); // Respect event order
}
@@ -845,7 +826,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
// TODO: Support upgrading
e = new VersionException(e.getVersionIndicator(), false).combine(e);
VersionExceptionHandler.showVersionError(null, file.getName(), file.getContentType(),
"Open", e);
"Open", false, e);
return null;
}
catch (IOException e) {
@@ -1069,10 +1050,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
protected void checkCloseTraces(Collection<Trace> traces, boolean noConfirm) {
List<Target> live =
traces.stream()
.map(t -> targetService.getTarget(t))
.filter(t -> t != null)
.toList();
traces.stream().map(t -> targetService.getTarget(t)).filter(t -> t != null).toList();
/**
* A provider may be reading a trace, likely via the Swing thread, so schedule this on the
* same thread to avoid a ClosedException.
@@ -4,9 +4,9 @@
* 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.
@@ -313,7 +313,7 @@ public class DBTraceContentHandler extends DBWithUserDataContentHandler<DBTrace>
@Override
public String getContentTypeDisplayString() {
return "Trace";
return TRACE_CONTENT_TYPE;
}
@Override
@@ -4,9 +4,9 @@
* 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.
@@ -15,32 +15,15 @@
*/
package ghidra.trace.database;
import java.io.IOException;
import javax.swing.Icon;
import ghidra.framework.data.LinkHandler;
import ghidra.framework.data.URLLinkObject;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.FileSystem;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class DBTraceLinkContentHandler extends LinkHandler<DBTrace> {
public static final String TRACE_LINK_CONTENT_TYPE = "TraceLink";
public static DBTraceLinkContentHandler INSTANCE = new DBTraceLinkContentHandler();
@Override
public long createFile(FileSystem fs, FileSystem userfs, String path, String name,
DomainObject obj, TaskMonitor monitor)
throws IOException, InvalidNameException, CancelledException {
if (!(obj instanceof URLLinkObject)) {
throw new IOException("Unsupported domain object: " + obj.getClass().getName());
}
return createFile((URLLinkObject) obj, TRACE_LINK_CONTENT_TYPE, fs, path, name,
monitor);
}
public static final String TRACE_LINK_CONTENT_TYPE = "TraceLink";
@Override
public String getContentType() {
@@ -4,9 +4,9 @@
* 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.
@@ -15,8 +15,6 @@
*/
package ghidra.machinelearning.functionfinding;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.BorderLayout;
import java.util.*;
import java.util.stream.Collectors;
@@ -35,7 +33,7 @@ import docking.widgets.table.GTable;
import docking.widgets.table.threaded.GThreadedTablePanel;
import docking.widgets.textfield.IntegerTextField;
import ghidra.app.services.ProgramManager;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.DomainFile;
import ghidra.framework.preferences.Preferences;
import ghidra.program.model.address.AddressSet;
@@ -485,11 +483,7 @@ public class FunctionStartRFParamsDialog extends ReusableDialogComponentProvider
}
private void searchOtherProgram(RandomForestRowObject modelRow) {
DataTreeDialog dtd =
new DataTreeDialog(null, "Select Program", OPEN, f -> {
Class<?> c = f.getDomainObjectClass();
return Program.class.isAssignableFrom(c);
});
ProgramFileChooser dtd = new ProgramFileChooser(null, "Select Program");
dtd.show();
DomainFile dFile = dtd.getDomainFile();
if (dFile == null) {
@@ -4,9 +4,9 @@
* 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.
@@ -22,6 +22,7 @@ import java.net.URL;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.protocol.ghidra.*;
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
import ghidra.program.database.ProgramContentHandler;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
@@ -56,7 +57,8 @@ public abstract class IterateRepository {
throw new MalformedURLException("Unsupported repository URL: " + ghidraURL);
}
GhidraURLQuery.queryUrl(ghidraURL, new GhidraURLResultHandlerAdapter(true) {
// Query URL - may be either file or folder (no link following)
GhidraURLQuery.queryUrl(ghidraURL, null, new GhidraURLResultHandlerAdapter(true) {
@Override
public void processResult(DomainFolder domainFolder, URL url, TaskMonitor m)
@@ -76,7 +78,9 @@ public abstract class IterateRepository {
process(domainFile, monitor);
}
}, monitor);
// Link files are skipped to avoid duplicate processing
// Processing should be done on actual folder - not a linked folder
}, LinkFileControl.NO_FOLLOW, monitor);
}
@@ -115,12 +119,11 @@ public abstract class IterateRepository {
private void process(DomainFile file, TaskMonitor monitor)
throws IOException, CancelledException {
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// Do not follow folder-links or consider program links to avoid possible duplication of
// file processing. Using content type is the best way to restrict this. If program links
// should be considered "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
// NOTE: linked-folders and linked-files are not currently supported
return; // skip non-program file
}
@@ -129,6 +132,7 @@ public abstract class IterateRepository {
Msg.debug(IterateRepository.class, "Processing " + file.getPathname() + "...");
monitor.setMessage("Processing: " + file.getName());
monitor.incrementProgress(1);
// NOTE: The following method invocation will follow all links if presented one
program = (Program) file.getReadOnlyDomainObject(this, -1, monitor);
process(program, monitor);
}
@@ -398,6 +398,10 @@ src/main/help/help/topics/FrontEndPlugin/Project_Info.htm||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/Re-opening_a_Project.htm||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/Restore_Project.htm||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/Saving_a_Ghidra_Project.htm||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteBrokenFileLinkIcon.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteBrokenFolderLinkIcon.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteFileLinkIcon.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteFolderLinkIcon.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/ArchiveFileExists.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/ArchiveProject.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/ChangeAccessList.png||GHIDRA||||END|
@@ -439,6 +443,7 @@ src/main/help/help/topics/FrontEndPlugin/images/VersionedFileIcon.png||GHIDRA|||
src/main/help/help/topics/FrontEndPlugin/images/ViewOtherProjects.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/ViewProjectAccessPanel.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/hijack_file.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/start-here_16.png||GHIDRA||||END|
src/main/help/help/topics/FunctionComparison/FunctionComparison.htm||GHIDRA||||END|
src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png||GHIDRA||||END|
src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png||GHIDRA||||END|
@@ -4,9 +4,9 @@
* 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.
@@ -30,7 +30,7 @@ import ghidra.app.script.ImproperUseException;
import ghidra.framework.data.GhidraFile;
import ghidra.framework.data.GhidraFileData;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.*;
import ghidra.framework.store.FolderItem;
import ghidra.framework.store.local.LocalDatabaseItem;
import ghidra.program.model.lang.LanguageDescription;
@@ -115,8 +115,8 @@ public class FixLangId extends GhidraScript {
if (langId != null) {
Msg.warn(this, "Changing language ID from '" + record.getString(0) + "' to '" +
langId + "' for program: " + df.getName());
desc = DefaultLanguageService.getLanguageService().getLanguageDescription(
new LanguageID(langId));
desc = DefaultLanguageService.getLanguageService()
.getLanguageDescription(new LanguageID(langId));
long txId = dbh.startTransaction();
try {
record.setString(0, langId);
@@ -139,7 +139,10 @@ public class FixLangId extends GhidraScript {
public DomainFile askProgramFile(String title) {
final DomainFile[] domainFile = new DomainFile[] { null };
final DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
// The file filter employed restricts selection to a program file within the active
// project where we have the ability to update file data.
final DataTreeDialog dtd =
new DataTreeDialog(null, title, OPEN, new DefaultDomainFileFilter(Program.class, true));
dtd.addOkActionListener(e -> {
dtd.close();
domainFile[0] = dtd.getDomainFile();
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* 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.
@@ -20,48 +19,56 @@
import ghidra.app.script.GhidraScript;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramContentHandler;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.CancelledException;
import java.io.IOException;
public class RenameProgramsInProjectScript extends GhidraScript {
@Override
public void run() throws Exception {
if ( currentProgram != null ) {
popup( "This script should be run from a tool with no open programs" );
return;
}
PluginTool tool = state.getTool();
Project project = tool.getProject();
ProjectData projectData = project.getProjectData();
DomainFolder rootFolder = projectData.getRootFolder();
recurseProjectFolder( rootFolder );
}
private void recurseProjectFolder( DomainFolder domainFolder ) {
DomainFile[] files = domainFolder.getFiles();
for ( DomainFile domainFile : files ) {
processDomainFile( domainFile );
}
DomainFolder[] folders = domainFolder.getFolders();
for ( DomainFolder folder : folders ) {
recurseProjectFolder( folder );
}
}
private void processDomainFile( DomainFile domainFile ) {
String oldName = domainFile.getName();
try {
domainFile.setName( oldName + "_renamed" );
}
catch ( InvalidNameException e ) {
e.printStackTrace();
}
catch ( IOException e ) {
e.printStackTrace();
}
}
@Override
public void run() throws Exception {
if (currentProgram != null) {
popup("This script should be run from a tool with no open programs.\n" +
"Warning! If using file-links to programs within this project such linkages will break.");
return;
}
PluginTool tool = state.getTool();
Project project = tool.getProject();
ProjectData projectData = project.getProjectData();
DomainFolder rootFolder = projectData.getRootFolder();
recurseProjectFolder(rootFolder);
}
private void recurseProjectFolder(DomainFolder domainFolder) throws CancelledException {
DomainFile[] files = domainFolder.getFiles();
for (DomainFile domainFile : files) {
monitor.checkCancelled();
processDomainFile(domainFile);
}
DomainFolder[] folders = domainFolder.getFolders();
for (DomainFolder folder : folders) {
monitor.checkCancelled();
recurseProjectFolder(folder);
}
}
private void processDomainFile(DomainFile domainFile) {
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domainFile.getContentType())) {
return;
}
String oldName = domainFile.getName();
try {
domainFile.setName(oldName + "_renamed");
}
catch (InvalidNameException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -102,6 +102,8 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
}
private int listCheckouts(DomainFolder folder) throws IOException, CancelledException {
// Avoid following folder-links so we don't count the same file more than once.
// Link-files will never be in a checked-out state.
int count = 0;
for (DomainFile df : folder.getFiles()) {
monitor.checkCancelled();
@@ -115,8 +117,8 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
}
private int listCheckouts(DomainFile df) throws IOException {
if (!df.isVersioned()) {
return 0;
if (!df.isVersioned() || df.isLink()) {
return 0; // ignore non-versioned files and link-files
}
int count = 0;
for (ItemCheckoutStatus checkout : df.getCheckouts()) {
@@ -4,9 +4,9 @@
* 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.
@@ -27,7 +27,6 @@ public class VersionControl_ResetAll extends GhidraScript {
public VersionControl_ResetAll() {
}
@Override
public void run() throws Exception {
@@ -54,7 +53,8 @@ public class VersionControl_ResetAll extends GhidraScript {
if (monitor.isCancelled()) {
break;
}
// Do not follow folder-links or consider program links. Checking the content type
// is the best way to restrict this.
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType()) ||
!file.isVersioned() || file.getLatestVersion() < 2) {
continue;// skip
@@ -60,6 +60,8 @@
<LI><A href="#ProjectDataTable">Data Table</A></LI>
<LI><A href="#FileIcons">File Icons</A></LI>
<LI><A href="#GhidraURLFormats">Ghidra URL Formats</A></LI>
<LI><A href="#StatusWindow">Console</A></LI>
@@ -93,8 +95,10 @@
<H2><A name="ActiveProjectPanel"></A>Active Project</H2>
<BLOCKQUOTE>
<P>The Active Project view shows your programs and datatype archives in a tree view or a
table view. The tree view is useful for organizing your files into folders and sub-folders.
<P>The Active Project view shows the various files associated with the current
project which has been open for update. Project files generally consist of programs and
datatype archives but may also be related to other Ghidra content.
The tree view is useful for organizing your files into folders and sub-folders.
The table view is useful for sorting all your files on some particular attribute such as
size, processor, or modification date. In either view, you open and perform various
actions on program files or datatype archives.</P>
@@ -105,13 +109,23 @@
</CENTER>
<BLOCKQUOTE>
<P>The data tree shows all files in the project orgnanized into folders and sub-folders.
<A href="#FileIcons">Icons for files</A>
indicate whether they are under <A href=
<A href="#FileIcons">Icons for files</A> indicate whether they are under <A href=
"help/topics/VersionControl/project_repository.htm#Versioning">version control</A> and whether
you have the file <A href=
"help/topics/VersionControl/project_repository.htm#SampleCheckOutIcon">checked out</A>.
Open this view by activating the "Tree View" tab.</P>
In addition, unique icons are used to reflect content-type and if it corresponds to
a link-file referring to another file or folder (see <A href="#Paste_Link">creating links</A>).
Open this view by activating the project window "Tree View" tab.</P>
<P><IMG src="help/shared/tip.png" border="0">Although Ghidra allows a folder and file within
the same parent folder to have the same name, it is recommended this be avoided if possible.
Allowing both a folder and file to have the same pathname can result in ambiguous path problems
when using link files and/or Ghidra URLs where only a path is used to identify either a project
resource.
</P>
</BLOCKQUOTE>
<P>&nbsp;</P>
<H3>Tree Only Actions</H3>
@@ -124,7 +138,7 @@
<P>To create a new folder,</P>
<OL>
<LI>Select a folder that you own.&nbsp;</LI>
<LI>Select a folder which should contain the new folder.</LI>
<LI>Right mouse click and choose the <I>New Folder</I> option.</LI>
@@ -133,8 +147,6 @@
editing.</LI>
</OL>
<P><IMG src="help/shared/note.png" border="0"> You cannot create
a sub-folder of a folder that you do not own.</P>
</BLOCKQUOTE>
<H4><A name="Copy"></A><A name="Paste"></A>Copy Folders and Files</H4>
@@ -159,7 +171,47 @@
time.&nbsp;</LI>
</OL>
</BLOCKQUOTE>
<H4><A name="Paste_Link"></A><A name="Paste_Relative_Link"></A>Paste Copied Folder or File as a Link</H4>
<BLOCKQUOTE>
<P>A Link may be created within the active project to a file or folder within the
same project (internal) or to a viewed project/repository (external).
Internal links may be defined using either a relative path or an absolute path. Once
a link is created its stored path will not change. The link will need to be replaced
should the referenced path need to be changed. In addition, file-links are specific
to the content-type of the referenced file at the time of link creation (e.g.,
ProgramLink).
</P>
<P>To create a Link use the following steps from the source project data tree:</P>
<OL>
<LI>Select a single file or folder, right mouse click and choose the&nbsp; <I>Copy</I> option.</LI>
<LI>Select a destination folder within the active project data tree.</LI>
<LI>Right mouse click and choose the <I>Paste as Link</I> or <I>Paste as Relative-Link</I>
option.</LI>
</OL>
<P>See <A href="#Create_File_Links">Create Linked Folder or File</A> for more information
about links and creating external links.
</P>
<P>An internal link in the project tree may indicate a "broken" status for
various reasons, including:</P>
<ul>
<li>The referenced file or folder does not exist,</li>
<li>the content-type at the referenced location does not match the link type, or</li>
<li>a folder-link results in a circular path reference.</li>
</ul>
<P>A broken link will have an icon which conveys its type but with a jagged red line
through it and a tooltip which conveys the issue detected.</P>
<P><IMG src="help/shared/note.png" border="0">External links will never show a broken
link state since they are not evaluated for such conditions.</P>
</BLOCKQUOTE>
<H4><A name="Cut"></A>Move Folders and Files</H4>
<BLOCKQUOTE>
@@ -182,7 +234,7 @@
</OL>
<P><IMG src="help/shared/note.png" border="0">You cannot move a
file that is in use.</P>
file that is in use or a folder that contains a file that is in use.</P>
</BLOCKQUOTE>
<H4>Drag/Drop for Copy</H4>
@@ -217,6 +269,9 @@
<LI>Release the mouse button when you get a valid drop target.</LI>
</OL>
<P><IMG src="help/shared/note.png" border="0">You cannot move a
file that is in use or a folder that contains a file that is in use.</P>
</BLOCKQUOTE>
<P><IMG src="help/shared/note.png" border="0"> If a folder or file
@@ -241,6 +296,24 @@
<LI>Right mouse click and choose the <B>Collapse All</B> option.</LI>
</OL>
</BLOCKQUOTE>
<H4><A name="Follow_Link"></A>Follow Link</H4>
<BLOCKQUOTE>
<P>Select the internal or external folder or file referenced by a selected link-file.
While internal folders may be expanded directly from a folder-link, following a link
to the actual referenced location may be useful at times.
</P>
<OL>
<LI>
Select a file-link or folder-link, right mouse click and choose the <I>Follow Link</I>
option. The referenced file or folder will be selected if possible. If associated
with an external project or repository the selection will occur in a READ-ONLY
project view once opened.</LI>
</OL>
</BLOCKQUOTE>
<P>&nbsp;</P>
@@ -421,9 +494,9 @@
<TD style="vertical-align: top; width: 10px;">-<BR>
</TD>
<TD style="vertical-align: top;">A <A href=
<TD style="vertical-align: top;"><A href=
"help/topics/Program/Ghidra_Programs.htm"><SPAN style=
"font-weight: bold;">program</SPAN></A><BR>
"font-weight: bold;">Program</SPAN></A><BR>
</TD>
</TR>
@@ -434,12 +507,34 @@
<TD style="vertical-align: top; width: 10px;">-<BR>
</TD>
<TD style="vertical-align: top;">A <A href=
<TD style="vertical-align: top;"><A href=
"help/topics/DataTypeManagerPlugin/data_type_manager_description.htm#ProjectDataTypeArchive"><SPAN
style="font-weight: bold;">project data type archive</SPAN></A> (a data type file
style="font-weight: bold;">Data Type Archive</SPAN></A> (a data type file
stored in the project)<BR>
</TD>
</TR>
<TR>
<TD style="vertical-align: top; width: 20px;"><IMG alt="" src=
"images/video-x-generic16.png"></TD>
<TD style="vertical-align: top; width: 10px;">-<BR>
</TD>
<TD style="vertical-align: top;">Debugger Trace Data<BR>
</TD>
</TR>
<TR>
<TD style="vertical-align: top; width: 20px;"><IMG alt="" src=
"images/start-here_16.png"></TD>
<TD style="vertical-align: top; width: 10px;">-<BR>
</TD>
<TD style="vertical-align: top;">Version Tracking Session Data<BR>
</TD>
</TR>
</TBODY>
</TABLE>
@@ -521,6 +616,61 @@
not under version control, exists only on your local machine, and is not visible to
other users.</TD>
</TR>
<TR>
<TD width="25%">File Link&nbsp;</TD>
<TD width="20%"><IMG src="images/AbsoluteFileLinkIcon.png" border="0"></TD>
<TD width="40%"><A name="FileLink"></A>A file link named "Example" which refers to
a Program at <I>/data/example</I>. File links may reference another file using either an
1) absolute file path within the same project, 2) a relative file path within
the same project, 3) a shared repository Ghidra URL, or 4) a local project Ghidra URL.
See <A href="#GhidraURLFormats">Ghidra URL formats</A> below.
A file link may appear with various icon states which correspond to version control.
File links only support a single version and may not be modified.
</TD>
</TR>
<TR>
<TD width="25%">File Link (Broken)&nbsp;</TD>
<TD width="20%"><IMG src="images/AbsoluteBrokenFileLinkIcon.png" border="0"></TD>
<TD width="40%"><A name="BrokenFileLink"></A>A file link named "Example" which refers to
a Program at <I>/data/example</I> and is in a "Broken" state. Hovering the mouse
on this node will display a tooltip which indicates the reason for the broken state.
External file links will never show a broken link state since they are not evaluated for such conditions.
</TD>
</TR>
<TR>
<TD width="25%">Folder Link&nbsp;</TD>
<TD width="20%"><IMG src="images/AbsoluteFolderLinkIcon.png" border="0"></TD>
<TD width="40%"><A name="FolderLink"></A>A folder link named "Example" which refers
to a folder at <I>/data/example</I>. Folder links may reference another folder using either an
1) absolute file path within the same project, 2) a relative file path within
the same project, 3) a shared repository Ghidra URL, or 4) a local project Ghidra URL.
See <A href="#GhidraURLFormats">Ghidra URL formats</A> below.
Since a folder link is stored as a file, it may appear with various icon states which
correspond to version control. Folder links only support a single version and may not
be modified.
</TD>
</TR>
<TR>
<TD width="25%">Folder Link (Broken)&nbsp;</TD>
<TD width="20%"><IMG src="images/AbsoluteBrokenFolderLinkIcon.png" border="0"></TD>
<TD width="40%"><A name="BrokenFolderLink"></A>A folder link named "Example" which refers to
a folder at <I>/data/example</I> and is in a "Broken" state. Hovering the mouse
on this node will display a tooltip which indicates the reason for the broken state.
External folder links will never show a broken link state since they are not evaluated for such conditions.
</TD>
</TR>
<TR>
<TD width="25%">Hijacked File</TD>
@@ -545,9 +695,44 @@
</TABLE>
</CENTER>
</DIV>
</BLOCKQUOTE>
<BLOCKQUOTE>
<H3>&nbsp;</H3>
<H3><A name="GhidraURLFormats"></A>Ghidra URL Formats</H3>
<P>The format of a remote <EM>Ghidra Server URL</EM> is distinctly different from a
<EM>Local Ghidra Project URL</EM>. These URLs have the following formats:</P>
<P><STRONG>Remote Ghidra Server Repository</STRONG><BR>
</P>
<BLOCKQUOTE>
<TABLE border="0" class="simplelist">
<TR>
<TD><CODE>ghidra://&lt;hostname&gt;[:&lt;port&gt;]/&lt;repository_name&gt;[/&lt;folder_or_file_path&gt;]</CODE></TD>
</TR>
</TABLE>
</BLOCKQUOTE>
<P>If the default Ghidra Server port (13100) is in use it is not specified by the URL.
The <EM>hostname</EM> may specify either a Fully Qualified Domain Name (FQDN, e.g.,
<EM>host.abc.com</EM>) or IP v4 Address (e.g., <EM>1.2.3.4</EM>).</P>
<P><STRONG>Local Ghidra Project</STRONG><BR>
</P>
<BLOCKQUOTE>
<TABLE border="0" class="simplelist">
<TR>
<TD><CODE>ghidra:[/&lt;directory_path&gt;]/&lt;project_name&gt;[?/&lt;folder_or_file_path&gt;]</CODE></TD>
</TR>
</TABLE>
</BLOCKQUOTE>
<P>For local project URLs, the absolute directory path containing the project
<EM>*.gpr</EM> locator file is specified with the project name but excludes any <EM>.gpr/.rep</EM> suffix.
The folder or file path within the project is conveyed with a URL query so the '?' is required.</P>
</BLOCKQUOTE>
<H2><A name="ReadOnlyProjectDataPanel"></A>Read-Only Project Data</H2>
@@ -697,47 +882,54 @@
<BLOCKQUOTE>
<P>This feature allows you to create a folder or file link in your active project to a
corresponding folder or file within a read-only viewed project.
This is done using a Ghidra URL which references the
file in its local or remote storage location. If the viewed project corresponds to a
viewed repository a remote URL will be used, while other cases will refer to the
locally viewed project. It is possible for links to become broken if the referenced
repository, local project or file location are changed.</P>
corresponding folder or file within your project or to a read-only viewed project.
External links are established using a Ghidra URL which references a
file or folder in its local or remote storage location. An external Ghidra URL will
be used if a link refers to a viewed project or repository. It is possible for internal links to
become broken if the referenced file or folder location has changed (e.g., no longers exists
or has the wrong content type). External links may become invalid for various reasons
but will not convey an issue until the link is used. The broken link icon does not apply
to external link files.
</P>
<P>To create an external folder or file link the following steps may be used:</P>
<ol>
<li>Select a single folder or file from a viewed READ-ONLY Project Data tree.</li>
<li>Right mouse click on the selected tree node and choose the <I>Copy</I> option.</li>
<li>Select a destination folder in the active project tree.</li>
<li>Right mouse click on the folder and choose the <I>Paste as Link</I> option.
<P><IMG src="help/shared/note.png" border="0">Currently, linked-file types are
currently limited to <I>Program</I> and <I>Data Type Archive</I> files
only. The <I>Past as Link</I> menu item will be disabled for
unsupported file content types or for other unsupported situations such as internal
linking within the same project.</P>
</li>
<li>Select a destination folder in the active project data tree.</li>
<li>Right mouse click on the folder and choose the <I>Paste as Link</I> option.</li>
</ol>
<P>A linked-file may be opened in a tool via the project window in the same fashion that
<P>It is important to note that the resulting link is always stored as a file within the
project. With the exception of external links to local project content, a link may be
added to version control so that it may be shared. Once added to version control it cannot
be checked-out, since they are immutable, however they can still be deleted.</P>
<P>A file-link may be opened in a tool via the project window in the same fashion that
a normal file is opened (e.g., double-left-mouse-click or drag-n-drop onto a tool box icon).
Such a project file may also be opened within a Tool using its <B>File->Open...</B> action
and selected from the resulting project file selection dilaog.
Clicking on a linked-folder in the active project window will open that location in a
Clicking on an external folder-link in the active project window will open that location in a
<B>READ-ONLY Project Data</B> tree. The user may be prompted for a shared repository
connection password when accessing a linked folder or file.</P>
<P>Within a project file chooser dialog a linked-folder may be expanded in a similar fashion
connection password when accessing an external folder or file link.</P>
<P>Within a project file chooser dialog a folder-link may be expanded in a similar fashion
to local folders provided any neccessary repository connection can be completed.</P>
<P><IMG src="help/shared/note.png" border="0"><B>Add to Version Control...</B> is supported
for repository folder and file links only and will be disabled for links to a
local project.</P>
<P><IMG src="help/shared/note.png" border="0">Currently, linked-files only provide access
to the latest file version and do not facilitate access to older file versions.</P>
<P><IMG src="help/shared/note.png" border="0">Currently, external file-links only provide access
to the latest file version and do not facilitate access to older file versions. An external
folder-link will allow access to file versions contained within such a folder.
</P>
<P><IMG src="help/shared/note.png" border="0">Some file chooser use cases, including the
<I>GhidraScript</I> API, are restricted to selecting files and folders within the active
project only and will hide all external links.
</P>
<P>The project window below shows a Program file-link "Program1" which is linked to the
same file in the viewed project. Hovering the mouse over a linked-file will show the URL
of the linked file or folder. The chain-link icon decoration indicates such a linked
file or folder.</P>
same file in the viewed project.</P>
<CENTER>
<IMG src= "images/LinkOtherProject.png" border="0">
</CENTER>
<P>A folder or file link will show its referenced location with either
same file in the viewed project.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

@@ -4,9 +4,9 @@
* 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.
@@ -133,8 +133,9 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
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));
OPTION_DESCRIPTION_PROJECT_PATH,
() -> new ProjectPathChooserEditor("Choose Data Type Archive",
new DefaultDomainFileFilter(DataTypeArchive.class, false)));
}
@Override
@@ -289,6 +290,4 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
.collect(Collectors.toMap(f -> f.getName(), f -> f));
}
private static final DomainFileFilter DATATYPEARCHIVE_PROJECT_FILTER =
df -> DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
}
@@ -4,9 +4,9 @@
* 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.
@@ -46,10 +46,6 @@ public class ProjectPathChooserEditor extends PropertyEditorSupport {
private String title;
private DomainFileFilter filter;
public ProjectPathChooserEditor() {
this(null, null);
}
public ProjectPathChooserEditor(String title, DomainFileFilter filter) {
this.title = title;
this.filter = filter;
@@ -127,8 +123,7 @@ public class ProjectPathChooserEditor extends PropertyEditorSupport {
private void displayFileChooser() {
AtomicReference<String> result = new AtomicReference<>();
DataTreeDialog dataTreeDialog =
new DataTreeDialog(this, title, OPEN, filter);
DataTreeDialog dataTreeDialog = new DataTreeDialog(this, title, OPEN, filter);
dataTreeDialog.addOkActionListener(e -> {
dataTreeDialog.close();
DomainFile df = dataTreeDialog.getDomainFile();
@@ -4,9 +4,9 @@
* 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.
@@ -92,8 +92,7 @@ class OpenDomainFileTask extends Task {
private boolean isFileOpen() {
List<Archive> dtArchiveList = dtmHandler.getAllArchives();
for (int i = 0; i < dtArchiveList.size(); i++) {
Archive archive = dtArchiveList.get(i);
for (Archive archive : dtArchiveList) {
if (archive instanceof ProjectArchive) {
ProjectArchive projectArchive = (ProjectArchive) archive;
DomainFile archiveDomainFile = projectArchive.getDomainFile();
@@ -156,7 +155,7 @@ class OpenDomainFileTask extends Task {
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(tool.getToolFrame(), domainFile.getName(),
contentType, "Open", e);
contentType, "Open", false, e);
}
}
@@ -179,7 +178,7 @@ class OpenDomainFileTask extends Task {
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
"Open", e);
"Open", false, e);
}
catch (CancelledException e) {
// we don't care, the task has been canceled
@@ -77,7 +77,7 @@ public class DataTypeManagerHandler {
private Map<UniversalID, InvalidFileArchive> invalidArchives = new HashMap<>();
private boolean treeDialogCancelled = false;
private DomainFileFilter createArchiveFileFilter;
private DomainFileFilter archiveFileFilter;
private DataTypeIndexer dataTypeIndexer;
private List<ArchiveManagerListener> archiveManagerlisteners = new ArrayList<>();
@@ -107,18 +107,7 @@ public class DataTypeManagerHandler {
dataTypeIndexer.addDataTypeManager(builtInDataTypesManager);
openArchives.add(new BuiltInArchive(this, builtInDataTypesManager));
createArchiveFileFilter = new DomainFileFilter() {
@Override
public boolean accept(DomainFile df) {
return DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
}
@Override
public boolean followLinkedFolders() {
return false;
}
};
archiveFileFilter = new DefaultDomainFileFilter(DataTypeArchive.class, true);
folderListener = new MyFolderListener();
tool.getProject().getProjectData().addDomainFolderChangeListener(folderListener);
@@ -1454,7 +1443,7 @@ public class DataTypeManagerHandler {
}
private DataTreeDialog getSaveDialog() {
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, createArchiveFileFilter);
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, archiveFileFilter);
ActionListener listener = event -> {
DomainFolder folder = dialog.getDomainFolder();
@@ -1486,7 +1475,7 @@ public class DataTypeManagerHandler {
private CreateDataTypeArchiveDataTreeDialog getCreateDialog() {
CreateDataTypeArchiveDataTreeDialog dialog = new CreateDataTypeArchiveDataTreeDialog(null,
"Create", CREATE, createArchiveFileFilter);
"Create", CREATE, archiveFileFilter);
ActionListener listener = event -> {
DomainFolder folder = dialog.getDomainFolder();
@@ -1726,7 +1715,7 @@ public class DataTypeManagerHandler {
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, newDomainFile.getName(), contentType,
"Re-open", e);
"Re-open", false, e);
}
catch (CancelledException e) {
throw new AssertException(e);
@@ -1766,7 +1755,7 @@ public class DataTypeManagerHandler {
Throwable cause = t.getCause();
if (cause instanceof VersionException) {
VersionExceptionHandler.showVersionError(null, archiveFile.getName(), "Archive",
"open", (VersionException) cause);
"open", false, (VersionException) cause);
}
else {
Msg.showError(plugin, plugin.getProvider().getComponent(), "Open Archive Failed",
@@ -4,9 +4,9 @@
* 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.
@@ -78,6 +78,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private JCheckBox selectionCheckBox; // null for FrontEnd Tool use
private JTextField filePathTextField;
private JButton fileChooserButton;
private List<Exporter> applicableExporters;
private GhidraComboBox<Exporter> comboBox;
private final DomainFile domainFile;
private boolean domainObjectWasSupplied;
@@ -86,39 +87,82 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private PluginTool tool;
private JLabel selectionOnlyLabel;
private boolean showNoExporterErrorIfNeeded = true;
/**
* Construct a new ExporterDialog for exporting an entire program.
* Show a new ExporterDialog for exporting an entire program.
* The method {@link #hasNoApplicableExporter()} should be checked before showing the
* dilaog. If no exporters are available a popup error will be displayed and the exporter
* dialog will not be shown.
*
* @param tool the tool that launched this dialog.
* @param domainFile the program to export
*/
public ExporterDialog(PluginTool tool, DomainFile domainFile) {
this(tool, domainFile, null, null);
public static void show(PluginTool tool, DomainFile domainFile) {
showExporterDialog(tool, domainFile, null, null);
}
/**
* Construct a new ExporterDialog for exporting a program, optionally only exported a
* selected region.
* selected region. The method {@link #hasNoApplicableExporter()} should be checked before
* showing the dilaog. If no exporters are available a popup error will be displayed and the
* exporter dialog will not be shown.
* The {@link #close()} method must always be invoked on the dialog instance even if it
* is never shown to ensure any {@link DomainObject} instance held is properly released.
*
* @param tool the tool that launched this dialog.
* @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 (ignored for FrontEnd Tool).
*/
public ExporterDialog(PluginTool tool, DomainFile domainFile, DomainObject domainObject,
public static void showExporterDialog(PluginTool tool, DomainFile domainFile,
DomainObject domainObject, ProgramSelection selection) {
ExporterDialog dialog = new ExporterDialog(tool, domainFile, domainObject, selection);
if (dialog.hasNoApplicableExporter()) {
dialog.close();
}
else {
tool.showDialog(dialog);
}
}
/**
* Construct a new modal ExporterDialog for exporting a program, optionally only exported a
* selected region. The method {@link #hasNoApplicableExporter()} should be checked before
* showing the dilaog. If no exporters are available a popup error will be displayed.
* The {@link #close()} method must always be invoked on the dialog instance even if it
* is never shown to ensure any {@link DomainObject} instance held is properly released.
*
* @param tool the tool that launched this dialog.
* @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 (ignored for FrontEnd Tool).
*/
private ExporterDialog(PluginTool tool, DomainFile domainFile, DomainObject domainObject,
ProgramSelection selection) {
super("Export " + domainFile.getName());
if (!Swing.isSwingThread()) {
throw new RuntimeException("ExporterDialog must be instantiated within Swing thread");
}
this.tool = tool;
this.domainFile = domainFile;
this.domainObject = domainObject;
this.currentSelection = selection;
if (domainObject != null) {
applicableExporters = getApplicableExporters(false);
domainObjectWasSupplied = true;
domainObject.addConsumer(this);
}
else {
domainObject = getDomainObjectIfNeeded(TaskMonitor.DUMMY);
applicableExporters = getApplicableExporters(true);
List<Exporter> applicableDomainFileExporters = getApplicableExporters(false);
domainObject = getDomainObjectIfNeeded(!applicableDomainFileExporters.isEmpty());
applicableExporters = getApplicableExporters(false);
}
addWorkPanel(buildWorkPanel());
@@ -133,6 +177,11 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
// need to initialize a few things
selectedFormatChanged();
validate();
if (showNoExporterErrorIfNeeded && hasNoApplicableExporter()) {
Msg.showError(this, tool.getToolFrame(), "Unable to Export",
"No available exporters for content type");
}
}
@Override
@@ -305,10 +354,9 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private Component buildFormatChooser() {
List<Exporter> exporters = getApplicableExporters(false);
comboBox = new GhidraComboBox<>(exporters);
comboBox = new GhidraComboBox<>(applicableExporters);
Exporter defaultExporter = getDefaultExporter(exporters);
Exporter defaultExporter = getDefaultExporter(applicableExporters);
if (defaultExporter != null) {
comboBox.setSelectedItem(defaultExporter);
}
@@ -319,8 +367,8 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
/**
* 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
* the domainFile's content class will be used. The {@code applicableExporters} variable
* is set to the applicable list of exporters.
*/
private List<Exporter> getApplicableExporters(boolean preliminaryCheck) {
List<Exporter> list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class));
@@ -330,15 +378,13 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
}
private boolean canExport(Exporter exporter, boolean preliminaryCheck) {
if (exporter.canExportDomainFile(domainFile)) {
return true;
if (domainObject != null) {
return exporter.canExportDomainObject(domainObject);
}
if (domainObject == null) {
return preliminaryCheck
? exporter.canExportDomainObject(domainFile.getDomainObjectClass())
: false;
if (preliminaryCheck) {
return exporter.canExportDomainObject(domainFile.getDomainObjectClass());
}
return exporter.canExportDomainObject(domainObject);
return exporter.canExportDomainFile(domainFile);
}
private Exporter getDefaultExporter(List<Exporter> list) {
@@ -410,6 +456,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
setOkEnabled(true);
}
public boolean hasNoApplicableExporter() {
return applicableExporters.isEmpty();
}
private boolean hasOptions() {
return options != null && !options.isEmpty();
}
@@ -450,7 +500,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
}
}
private DomainObject getDomainObjectIfNeeded(TaskMonitor taskMonitor) {
private DomainObject getDomainObjectIfNeeded(boolean exportPossibleWithoutOpening) {
if (domainObject != null) {
return domainObject;
}
@@ -459,7 +509,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
// 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)) {
for (Exporter exporter : applicableExporters) {
if (!exporter.canExportDomainFile(domainFile)) {
doOpen = true;
break;
@@ -469,35 +519,28 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
return null;
}
if (SystemUtilities.isEventDispatchThread()) {
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
monitor -> doOpenFile(monitor));
}
else {
doOpenFile(taskMonitor);
}
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
monitor -> doOpenFile(exportPossibleWithoutOpening, monitor));
return domainObject;
}
private void doOpenFile(TaskMonitor monitor) {
private void doOpenFile(boolean exportPossibleWithoutOpening, TaskMonitor monitor) {
showNoExporterErrorIfNeeded = false;
String linkedPrefix = domainFile.isLink() ? "linked-" : "";
try {
if (domainFile.isLinkFile()) {
// Linked files are always subject to upgrade if needed and do not support
// getImmutableDomainObject
domainObject =
domainFile.getReadOnlyDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
}
else {
domainObject =
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
}
domainObject =
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
}
catch (VersionException e) {
String msg = "Could not open file: " + domainFile.getName() +
"\n\nAvailable export options will be limited.";
String msg = "Could not open " + linkedPrefix + "file: " + domainFile.getName();
if (exportPossibleWithoutOpening) {
msg += "\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.";
msg += "\n\nA " + linkedPrefix +
"content upgrade is required. You may open file in a" +
"\ntool first to complete upgrade then Export if needed.";
}
else {
msg += "\nFile was created with a newer version of Ghidra";
@@ -505,8 +548,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
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.";
String msg = "Could not open " + linkedPrefix + "file: " + domainFile.getName();
if (exportPossibleWithoutOpening) {
msg += "\n\nAvailable export options will be limited.";
}
Msg.showError(this, getComponent(), "Error Opening File", msg, e);
}
catch (CancelledException e) {
@@ -552,7 +597,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
boolean exportDomainFile =
!domainObjectWasSupplied && exporter.canExportDomainFile(domainFile);
if (!exportDomainFile && domainFile == null) {
if (!exportDomainFile && (domainFile == null || domainFile.isLink())) {
return;
}
@@ -4,9 +4,9 @@
* 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.
@@ -70,9 +70,8 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
protected void actionPerformed(NavigatableActionContext context) {
Program program = context.getProgram();
DomainFile domainFile = program.getDomainFile();
ExporterDialog dialog =
new ExporterDialog(tool, domainFile, program, context.getSelection());
tool.showDialog(dialog);
ExporterDialog.showExporterDialog(tool, domainFile, program,
context.getSelection());
}
};
MenuData menuData =
@@ -104,8 +103,7 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
@Override
protected void actionPerformed(ProjectDataContext context) {
DomainFile domainFile = context.getSelectedFiles().get(0);
ExporterDialog dialog = new ExporterDialog(tool, domainFile);
tool.showDialog(dialog);
ExporterDialog.show(tool, domainFile);
}
@Override
@@ -118,6 +116,10 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
if (selectedFiles.size() != 1) {
return false;
}
DomainFile domainFile = context.getSelectedFiles().get(0);
if (domainFile.isLink() && domainFile.getLinkInfo().isFolderLink()) {
return false;
}
return true;
}
};
@@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.gotoquery;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.util.Stack;
import org.apache.commons.lang3.StringUtils;
@@ -32,7 +30,7 @@ import ghidra.app.util.NamespaceUtils;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.query.TableService;
import ghidra.framework.cmd.Command;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.ProjectData;
import ghidra.framework.plugintool.PluginTool;
@@ -106,8 +104,10 @@ public class GoToHelper {
ExternalLocation externalLoc =
program.getExternalManager().getExternalLocation(externalSym);
// TODO - this seems like a mistake to always pass 'false' here; please doc why we
// wish to ignore the user options for when to navigate to external programs
// TODO - This seems like a mistake to always pass 'false' here; please doc why we
// wish to ignore the user options for when to navigate to external programs.
// It appears this was done since this method is invoked on simple external
// location node selection within symbol tree where you would not want a popup.
return goToExternalLinkage(navigatable, externalLoc, false);
}
@@ -187,10 +187,11 @@ public class GoToHelper {
*
* @param nav Navigatable
* @param externalLoc external location
* @param popupAllowed if true a table may be displayed when multiple linkage locations exist,
* otherwise navigation to the first linkage location will be performed
* @param popupAllowed if true a table may be displayed when multiple linkage locations exist
* or navigation to an external program, otherwise navigation to the first linkage
* location will be performed
* @return true if navigation was successful or a list of possible linkage locations was
* displayed.
* displayed, false if no navigation was performed.
*/
protected boolean goToExternalLinkage(Navigatable nav, ExternalLocation externalLoc,
boolean popupAllowed) {
@@ -205,8 +206,14 @@ public class GoToHelper {
NavigationUtils.getExternalLinkageAddresses(program, externalSym.getAddress());
if (externalLinkageAddresses.length == 0) {
if (externalLoc.isFunction()) {
tool.setStatusInfo("Failed to identify external linkage address for " +
externalSym.getName(true) + ". Unable to perform navigation.", true);
// This assume external functions always require linkage location
tool.setStatusInfo("Failed to identify external linkage address for function " +
externalSym.getName(true), true);
}
else if (popupAllowed) {
// If there are no linkage location try to navigate to external program if a popup
// is tolerated.
return goToExternalLocation(nav, externalLoc, false);
}
return false;
}
@@ -306,7 +313,7 @@ public class GoToHelper {
}
ProjectData pd = tool.getProject().getProjectData();
DomainFile domainFile = pd.getFile(pathName);
DomainFile domainFile = pd.getFile(pathName, ProgramFileChooser.PROGRAM_FILE_FILTER);
ProgramManager service = tool.getService(ProgramManager.class);
if (domainFile == null || service == null) {
tool.setStatusInfo("Unable to navigate to external location. " +
@@ -441,8 +448,8 @@ public class GoToHelper {
return;
}
DataTreeDialog dialog = new DataTreeDialog(null,
"Choose External Program (" + extProgName + ")", OPEN);
ProgramFileChooser dialog =
new ProgramFileChooser(null, "Choose External Program (" + extProgName + ")");
dialog.setSearchText(extProgName);
dialog.setHelpLocation(new HelpLocation("ReferencesPlugin", "ChooseExternalProgram"));
tool.showDialog(dialog);
@@ -4,9 +4,9 @@
* 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.
@@ -79,16 +79,25 @@ public class AboutProgramPlugin extends Plugin implements ApplicationLevelPlugin
@Override
protected void actionPerformed(ProjectDataContext context) {
DomainFile domainFile = context.getSelectedFiles().get(0);
showAbout(domainFile, domainFile.getMetadata());
Map<String, String> metadata = domainFile.getMetadata();
showAbout(domainFile, metadata);
}
@Override
protected boolean isAddToPopup(ProjectDataContext context) {
return context.getFileCount() == 1 && context.getFolderCount() == 0;
if (context.getFileCount() == 1 && context.getFolderCount() == 0) {
// Adjust popup menu text
DomainFile domainFile = context.getSelectedFiles().get(0);
String contentType = domainFile.getContentType();
setPopupMenuData(
new MenuData(new String[] { "About " + contentType }, null, "AAA"));
return true;
}
return false;
}
};
aboutAction.setPopupMenuData(
new MenuData(new String[] { ACTION_NAME }, null, "AAA"));
aboutAction.setPopupMenuData(new MenuData(new String[] { ACTION_NAME }, null, "AAA"));
aboutAction.setEnabled(true);
}
@@ -88,6 +88,10 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
return false;
}
if (file.isLink() && file.getLinkInfo().isExternalLink()) {
return false;
}
return file.isInWritableProject() &&
Program.class.isAssignableFrom(file.getDomainObjectClass());
}
@@ -105,12 +109,12 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
}
};
setLanguageAction.setPopupMenuData(
new MenuData(new String[] { "Set Language..." }, "Language"));
setLanguageAction
.setPopupMenuData(new MenuData(new String[] { "Set Language..." }, "Language"));
setLanguageAction.setEnabled(true);
setLanguageAction.setHelpLocation(
new HelpLocation("LanguageProviderPlugin", "set language"));
setLanguageAction
.setHelpLocation(new HelpLocation("LanguageProviderPlugin", "set language"));
tool.addAction(setLanguageAction);
}
@@ -20,10 +20,10 @@ import java.net.URL;
import java.util.Objects;
import ghidra.framework.data.DomainFileProxy;
import ghidra.framework.data.LinkHandler;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
/**
* Programs locations can be specified from either a {@link DomainFile} or a ghidra {@link URL}.
@@ -81,11 +81,19 @@ public class ProgramLocator {
file = domainFile;
}
else {
try {
url = GhidraURL.getNormalizedURL(resolveURL(domainFile));
if (domainFile instanceof LinkedDomainFile linkedFile) {
try {
// Attempt to resolve to actual linked-file to allow for
// direct URL reference
domainFile = linkedFile.getLinkedFile();
}
catch (IOException e) {
Msg.error(this, "Failed to resolve linked-file", e);
}
}
catch (IOException e) {
file = domainFile;
url = domainFile.getLocalProjectURL(null);
if (url == null) {
url = domainFile.getSharedProjectURL(null);
}
}
this.domainFile = file;
@@ -177,25 +185,4 @@ public class ProgramLocator {
Objects.equals(ghidraURL, other.ghidraURL) && version == other.version;
}
private URL resolveURL(DomainFile file) throws IOException {
if (file.isLinkFile()) {
return LinkHandler.getURL(file);
}
DomainFolder parent = file.getParent();
if (file instanceof LinkedDomainFile linkedFile) {
return resolveLinkedDomainFile(linkedFile);
}
if (!parent.getProjectLocator().isTransient()) {
return file.getLocalProjectURL(null);
}
return file.getSharedProjectURL(null);
}
private URL resolveLinkedDomainFile(LinkedDomainFile linkedFile) {
URL url = linkedFile.getLocalProjectURL(null);
if (url == null) {
url = linkedFile.getSharedProjectURL(null);
}
return url;
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -41,23 +41,12 @@ class ProgramSaveManager {
private ProgramManager programMgr;
private PluginTool tool;
private boolean treeDialogCancelled;
private DomainFileFilter domainFileFilter;
private DomainFileFilter programFileFilter;
ProgramSaveManager(PluginTool tool, ProgramManager programMgr) {
this.tool = tool;
this.programMgr = programMgr;
domainFileFilter = new DomainFileFilter() {
@Override
public boolean accept(DomainFile df) {
return Program.class.isAssignableFrom(df.getDomainObjectClass());
}
@Override
public boolean followLinkedFolders() {
return false; // can't save to linked-folder (read-only)
}
};
programFileFilter = new DefaultDomainFileFilter(Program.class, true);
}
/**
@@ -443,8 +432,7 @@ class ProgramSaveManager {
}
private DataTreeDialog getSaveDialog() {
DataTreeDialog dialog =
new DataTreeDialog(null, "Save As", SAVE, domainFileFilter);
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, programFileFilter);
ActionListener listener = event -> {
DomainFolder folder = dialog.getDomainFolder();
@@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.references;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.*;
@@ -30,7 +28,7 @@ import javax.swing.event.DocumentListener;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GLabel;
import ghidra.app.util.AddressInput;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
@@ -113,8 +111,8 @@ class EditExternalReferencePanel extends EditReferencePanel {
}
});
editButton = new JButton("Edit");
editButton.setToolTipText("Edit Link to External Program");
editButton = new JButton("Select...");
editButton.setToolTipText("Select External Program");
editButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
@@ -187,10 +185,8 @@ class EditExternalReferencePanel extends EditReferencePanel {
* Pop up the data tree dialog so the user can choose the external program.
*/
private void popupProgramChooser() {
DataTreeDialog d =
new DataTreeDialog(this.getParent(), "Choose External Program", OPEN);
final DataTreeDialog dialog = d;
d.addOkActionListener(new ActionListener() {
ProgramFileChooser dialog = new ProgramFileChooser(this.getParent(), "Choose External Program");
dialog.addOkActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
DomainFile df = dialog.getDomainFile();
@@ -206,7 +202,7 @@ class EditExternalReferencePanel extends EditReferencePanel {
extLibPath.setText(df.getPathname());
}
});
plugin.getTool().showDialog(d);
plugin.getTool().showDialog(dialog);
}
@Override
@@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.references;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.util.*;
@@ -34,7 +32,7 @@ import generic.theme.GIcon;
import ghidra.app.cmd.refs.*;
import ghidra.framework.cmd.Command;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.*;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.plugintool.ComponentProviderAdapter;
@@ -236,8 +234,8 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter {
private void setExternalProgramAssociation() {
List<String> selectedExternalNames = getSelectedExternalNames();
String externalName = selectedExternalNames.get(0); // must be exactly one for us to be enabled.
DataTreeDialog dialog = new DataTreeDialog(mainPanel,
"Choose External Program (" + externalName + ")", OPEN);
DataTreeDialog dialog = new ProgramFileChooser(mainPanel,
"Choose External Program (" + externalName + ")", AppInfo.getActiveProject());
dialog.setSearchText(externalName);
@@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.symboltree;
import static ghidra.framework.main.DataTreeDialogType.*;
import java.awt.*;
import java.awt.event.ItemListener;
import java.util.Arrays;
@@ -36,7 +34,7 @@ import docking.widgets.label.GLabel;
import ghidra.app.util.AddressInput;
import ghidra.app.util.NamespaceUtils;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
@@ -270,9 +268,9 @@ class EditExternalLocationPanel extends JPanel {
* Pop up the data tree dialog so the user can choose the external program.
*/
private void popupProgramChooser() {
DataTreeDialog d = new DataTreeDialog(this.getParent(), "Choose External Program", OPEN);
final DataTreeDialog dialog = d;
d.addOkActionListener(e -> {
ProgramFileChooser dialog =
new ProgramFileChooser(this.getParent(), "Choose External Program");
dialog.addOkActionListener(e -> {
DomainFile df = dialog.getDomainFile();
if (df == null) {
return;
@@ -285,7 +283,7 @@ class EditExternalLocationPanel extends JPanel {
dialog.close();
extLibPathTextField.setText(df.getPathname());
});
DockingWindowManager.showDialog(this, d);
DockingWindowManager.showDialog(this, dialog);
}
private void initialize() {
@@ -363,7 +361,8 @@ class EditExternalLocationPanel extends JPanel {
Project project = AppInfo.getActiveProject();
ProjectData projectData = project.getProjectData();
DomainFile file = projectData.getFile(extLibPath);
DomainFile file =
projectData.getFile(extLibPath, ProgramFileChooser.PROGRAM_FILE_FILTER);
if (file == null) {
showInputErr("Cannot find the program for the specified library 'Path' of " +
extLibPath + ".");
@@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.symboltree.actions;
import static ghidra.framework.main.DataTreeDialogType.*;
import javax.swing.Icon;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;
@@ -29,7 +27,7 @@ import ghidra.app.plugin.core.symboltree.*;
import ghidra.app.plugin.core.symboltree.nodes.LibrarySymbolNode;
import ghidra.framework.cmd.Command;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.ProgramFileChooser;
import ghidra.framework.model.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.ExternalManager;
@@ -82,8 +80,8 @@ public class SetExternalProgramAction extends SymbolTreeContextAction {
ExternalManager externalManager = program.getExternalManager();
final String externalLibraryPath = externalManager.getExternalLibraryPath(externalName);
final DataTreeDialog dialog = new DataTreeDialog(provider.getComponent(),
"Choose External Program (" + externalName + ")", OPEN);
ProgramFileChooser dialog = new ProgramFileChooser(provider.getComponent(),
"Choose External Program (" + externalName + ")");
dialog.setSearchText(externalName);
@@ -2827,8 +2827,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
DomainFile choice = loadAskValue(this::parseDomainFile, title);
if (!isRunningHeadless()) {
choice = doAsk(Program.class, title, "", choice, lastValue -> {
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
// File filter employed limits access to program files within the active project
// only to ensure the ability to open for update is possible.
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN,
new DefaultDomainFileFilter(Program.class, true));
dtd.show();
if (dtd.wasCancelled()) {
return null;
@@ -2932,8 +2934,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
String message = "";
DomainFile choice = doAsk(DomainFile.class, title, message, existingValue, lastValue -> {
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
// File filter employed limits access to files within the active project
// only to ensure the ability to open for update is possible.
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN,
DomainFileFilter.ALL_FILES_NO_EXTERNAL_FOLDERS_FILTER);
dtd.show();
if (dtd.wasCancelled()) {
throw new CancelledException();
@@ -4,9 +4,9 @@
* 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.
@@ -47,7 +47,8 @@ public class GdtExporter extends Exporter {
@Override
public boolean canExportDomainFile(DomainFile domainFile) {
return canExportDomainObject(domainFile.getDomainObjectClass());
// Avoid exporting link-file itself
return !domainFile.isLink() && canExportDomainObject(domainFile.getDomainObjectClass());
}
@Override
@@ -42,7 +42,8 @@ public class GzfExporter extends Exporter {
@Override
public boolean canExportDomainFile(DomainFile domainFile) {
return canExportDomainObject(domainFile.getDomainObjectClass());
// Avoid exporting link-file itself
return !domainFile.isLink() && canExportDomainObject(domainFile.getDomainObjectClass());
}
@Override
@@ -43,6 +43,7 @@ import ghidra.framework.model.*;
import ghidra.framework.project.DefaultProject;
import ghidra.framework.project.DefaultProjectManager;
import ghidra.framework.protocol.ghidra.*;
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
import ghidra.framework.remote.User;
import ghidra.framework.store.LockException;
import ghidra.framework.store.local.LocalFileSystem;
@@ -354,7 +355,10 @@ public class HeadlessAnalyzer {
}
throw new IOException(title + ": " + message);
}
}, TaskMonitor.DUMMY);
// Link files are skipped to avoid duplicate processing
// Processing should be done on actual folder - not a linked folder
}, LinkFileControl.NO_FOLLOW, TaskMonitor.DUMMY);
}
catch (CancelledException e) {
@@ -1338,7 +1342,7 @@ public class HeadlessAnalyzer {
boolean filesProcessed = false;
DomainFile domFile = parentFolder.getFile(filename);
// Do not follow folder-links or consider program links. Using content type
// Do not follow folder-links or program links. Using content type
// to filter is best way to control this.
if (domFile != null &&
ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) {
@@ -4,9 +4,9 @@
* 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.
@@ -27,6 +27,7 @@ import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.DomainFile;
import ghidra.framework.protocol.ghidra.GhidraURLQuery;
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
import ghidra.framework.protocol.ghidra.GhidraURLResultHandlerAdapter;
import ghidra.framework.remote.User;
import ghidra.framework.store.ExclusiveCheckoutException;
@@ -103,13 +104,13 @@ public class ProgramOpener {
AtomicReference<Program> openedProgram = new AtomicReference<>();
try {
GhidraURLQuery.queryUrl(ghidraUrl, new GhidraURLResultHandlerAdapter() {
GhidraURLQuery.queryUrl(ghidraUrl, Program.class, new GhidraURLResultHandlerAdapter() {
@Override
public void processResult(DomainFile domainFile, URL url, TaskMonitor m) {
Program p = openProgram(locator, domainFile, m); // may return null
openedProgram.set(p);
}
}, monitor);
}, LinkFileControl.FOLLOW_EXTERNAL, monitor);
}
catch (IOException | CancelledException e) {
// IOException reported to user by GhidraURLResultHandlerAdapter
@@ -148,7 +149,7 @@ public class ProgramOpener {
}
catch (VersionException e) {
String contentType = domainFile.getContentType();
VersionExceptionHandler.showVersionError(null, filename, contentType, "Open", e);
VersionExceptionHandler.showVersionError(null, filename, contentType, "Open", false, e);
}
catch (CancelledException e) {
// we don't care, the task has been cancelled
@@ -197,7 +198,7 @@ public class ProgramOpener {
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
"Open", e);
"Open", false, e);
}
return null;
}
@@ -4,9 +4,9 @@
* 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.
@@ -129,31 +129,37 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
serviceProvider.getService(ProjectDataService.class);
ProjectData projectData = projectDataService.getProjectData();
// default folder is the root folder
DomainFolder folder = projectData.getRootFolder();
// Get program name and folder from program comment annotation
// handles forward and back slashes and with and without first slash
String programText = getProgramText(annotationParts);
String programName = FilenameUtils.getName(programText);
String path = FilenameUtils.getFullPathNoEndSeparator(programText);
DomainFolder folder;
if (path.length() > 0) {
path = StringUtils.prependIfMissing(FilenameUtils.separatorsToUnix(path), "/");
folder = projectData.getFolder(path);
if (folder == null) {
Msg.showInfo(getClass(), null, "Folder Not Found: " + path,
"Unable to locate folder by the name \"" + path);
return true;
}
}
else {
folder = projectData.getRootFolder();
}
if (folder == null) {
Msg.showInfo(getClass(), null, "No Folder: " + path,
"Unable to locate folder by the name \"" + path);
DomainFile programFile = folder.getFile(programName);
if (programFile == null ||
!Program.class.isAssignableFrom(programFile.getDomainObjectClass())) {
Msg.showInfo(getClass(), null, "Program Not Found: " + programName,
"Unable to locate program at path \"" + programText +
"\".\nNOTE: File names are case-sensitive.");
return true;
}
DomainFile programFile = findProgramByName(programName, folder);
if (programFile == null) {
Msg.showInfo(getClass(), null, "No Program: " + programName,
"Unable to locate a program by the name \"" + programName +
"\".\nNOTE: Program name is case-sensitive. ");
if (!Program.class.isAssignableFrom(programFile.getDomainObjectClass())) {
Msg.showInfo(getClass(), null, "Program Not Found: " + programName,
"File exists with incorrect content type. ");
return true;
}
@@ -199,7 +205,7 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
return;
}
Msg.showInfo(getClass(), null, "No Symbol: " + symbolName,
Msg.showInfo(getClass(), null, "Symbol Not Found: " + symbolName,
"Unable to navigate to '" + symbolName + "' in the program '" + programFile.getName() +
"'.\nMake sure that the given symbol/address exists.");
if (!programManager.isVisible(program)) {
@@ -247,27 +253,6 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
return address;
}
// recursive program to find a program by the given name within the given folder
private DomainFile findProgramByName(String programText, DomainFolder folder) {
DomainFile[] files = folder.getFiles();
for (DomainFile file : files) {
if (file.getName().equals(programText)) {
return file;
}
}
// not at the current level, then check sub-folders
DomainFolder[] folders = folder.getFolders();
for (DomainFolder subFolder : folders) {
DomainFile domainFile = findProgramByName(programText, subFolder);
if (domainFile != null) {
return domainFile;
}
}
return null;
}
@Override
public String getDisplayString() {
return "Program";
@@ -275,7 +260,7 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
@Override
public String getPrototypeString() {
return "{@program program_name.exe@symbol_name}";
return "{@program program_path@symbol_name}";
}
@Override
@@ -291,16 +291,28 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
else {
domainFile = treePanel.getSelectedDomainFile();
if (domainFile != null) {
folderNameLabel.setText(domainFile.getParent().getPathname());
nameField.setText(domainFile.getName());
domainFolder = domainFile.getParent();
}
else {
domainFolder = treePanel.getSelectedDomainFolder();
if (domainFolder == null) {
domainFolder = project.getProjectData().getRootFolder();
LinkFileInfo linkInfo = domainFile.getLinkInfo();
if (linkInfo != null && linkInfo.isFolderLink()) {
// Ensure we don't have a folder name conflict
if (domainFile.getParent().getFolder(domainFile.getName()) == null) {
domainFolder = linkInfo.getLinkedFolder();
domainFile = null;
}
}
else {
folderNameLabel.setText(domainFile.getParent().getPathname());
nameField.setText(domainFile.getName());
domainFolder = domainFile.getParent();
}
}
if (domainFile == null) {
if (domainFolder == null) {
domainFolder = treePanel.getSelectedDomainFolder();
if (domainFolder == null) {
domainFolder = project.getProjectData().getRootFolder();
}
}
folderNameLabel.setText(domainFolder.getPathname());
if (nameField.isEditable()) {
if (nameField.getText().length() > 0) {
@@ -349,7 +361,9 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
* @param file the file
*/
public void selectDomainFile(DomainFile file) {
Swing.runLater(() -> treePanel.selectDomainFile(file));
if (file != null) {
Swing.runLater(() -> treePanel.selectDomainFile(file));
}
}
@Override
@@ -584,20 +598,6 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
}
}
protected static DomainFileFilter getDefaultFilter(DataTreeDialogType type) {
if (type == CHOOSE_FOLDER || type == OPEN) {
// return filter which forces folder selection and allow navigation into linked-folders
return new DomainFileFilter() {
@Override
public boolean accept(DomainFile df) {
return true; // show all files (legacy behavior)
}
};
}
return null;
}
private class FieldKeyListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
@@ -4,9 +4,9 @@
* 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.
@@ -17,8 +17,7 @@ package ghidra.framework.main;
import java.awt.Component;
import ghidra.framework.model.DomainFileFilter;
import ghidra.framework.model.Project;
import ghidra.framework.model.*;
/**
* Dialog to open or save domain data items to a new location or name.
@@ -33,9 +32,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
/**
* Construct a new DataTreeDialog for the active project. This chooser will show all project
* files. Following linked-folders will only be allowed if a type of CHOOSE_FOLDER
* or OPEN is specified. If different behavior is required a filter should
* be specified using the other constructor.
* files and/or folders within the active project only. Broken and external links will not be
* shown. If different behavior is required a filter should be specified using the other
* constructor.
*
* @param parent dialog's parent
* @param title title to use
@@ -43,7 +42,7 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
* @throws IllegalArgumentException if invalid type is specified
*/
public DataTreeDialog(Component parent, String title, DataTreeDialogType type) {
this(parent, title, type, getDefaultFilter(type));
this(parent, title, type, DomainFileFilter.ALL_INTERNAL_FILES_FILTER);
}
/**
@@ -52,7 +51,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
* @param parent dialog's parent
* @param title title to use
* @param type specify OPEN, SAVE, CHOOSE_FOLDER, or CREATE
* @param filter filter used to control what is displayed in the data tree
* @param filter filter used to control what is displayed in the data tree. See static
* implementations provided by {@link DomainFileFilter} and a more tailored
* {@link DefaultDomainFileFilter}.
* @throws IllegalArgumentException if invalid type is specified
*/
public DataTreeDialog(Component parent, String title, DataTreeDialogType type,
@@ -66,7 +67,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
* @param parent dialog's parent
* @param title title to use
* @param type specify OPEN, SAVE, CHOOSE_FOLDER, or CREATE
* @param filter filter used to control what is displayed in the data tree
* @param filter filter used to control what is displayed in the data tree. See static
* implementations provided by {@link DomainFileFilter} and a more tailored
* {@link DefaultDomainFileFilter}.
* @param project the project to browse
* @throws IllegalArgumentException if invalid type is specified
*/
@@ -4,9 +4,9 @@
* 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.
@@ -90,9 +90,8 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
*/
public OpenVersionedFileDialog(PluginTool tool, String title, Class<T> domainObjectClass,
List<T> openDomainObjects) {
super(tool.getToolFrame(), title, OPEN, f -> {
return domainObjectClass.isAssignableFrom(f.getDomainObjectClass());
}, AppInfo.getActiveProject());
super(tool.getToolFrame(), title, OPEN,
new DefaultDomainFileFilter(domainObjectClass, false), AppInfo.getActiveProject());
this.tool = tool;
this.domainObjectClass = domainObjectClass;
@@ -214,8 +213,7 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
tabbedPane = new JTabbedPane();
tabbedPane.setName("Tabs");
tabbedPane.add("Project Files", projectFilePanel);
tabbedPane.add("Open " + domainObjectClass.getSimpleName() + "s",
buildOpenObjectsTable());
tabbedPane.add("Open " + domainObjectClass.getSimpleName() + "s", buildOpenObjectsTable());
tabbedPane.addChangeListener(e -> {
int selectedTabIndex = tabbedPane.getModel().getSelectedIndex();
@@ -254,8 +252,7 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
openObjectsTable = new GFilterTable<>(new OpenObjectsTableModel());
GTable table = openObjectsTable.getTable();
table.getSelectionModel()
.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
openObjectsTable.addSelectionListener(e -> {
setOkEnabled(true);
okButton.setToolTipText("Use the selected " + domainObjectClass.getSimpleName());

Some files were not shown because too many files have changed in this diff Show More