mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 12:11:55 +00:00
Merge remote-tracking branch 'origin/GP-4640_dev747368_fsb_symlink_and_actions--SQUASHED'
This commit is contained in:
commit
e2e6215982
@ -21,3 +21,4 @@ ChecksumAlgorithm
|
||||
OverviewColorService
|
||||
DWARFFunctionFixup
|
||||
ElfInfoProducer
|
||||
FSBFileHandler
|
||||
|
@ -38,5 +38,6 @@ icon.fsbrowser.file.extension.zip = images/oxygen/16x16/application-x-bzi
|
||||
icon.fsbrowser.file.substring.release. = images/famfamfam_silk_icons_v013/bullet_purple.png
|
||||
|
||||
icon.fsbrowser.file.overlay.imported = EMPTY_ICON{images/checkmark_green.gif[size(8,8)][move(8,8)]} // lower right quadrant
|
||||
icon.fsbrowser.file.overlay.filesystem = EMPTY_ICON{images/ledgreen.png[size(8,8)][move(0,8)]} // lower left quadrant
|
||||
icon.fsbrowser.file.overlay.missing.password = EMPTY_ICON{images/lock.png[size(8,8)][move(8,0)]} // upper right quadrant
|
||||
icon.fsbrowser.file.overlay.filesystem = EMPTY_ICON{images/ledgreen.png[size(8,8)][move(0,8)]} // lower left quadrant
|
||||
icon.fsbrowser.file.overlay.link = EMPTY_ICON{icon.content.handler.link[move(0,8)]} // lower-left quadrant
|
||||
icon.fsbrowser.file.overlay.missing.password = EMPTY_ICON{images/lock.png[size(8,8)][move(8,0)]} // upper right quadrant
|
||||
|
@ -28,7 +28,6 @@ import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.plugin.importer.ProgramMappingService;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.database.function.OverlappingFunctionException;
|
||||
import ghidra.program.model.address.*;
|
||||
@ -367,8 +366,7 @@ public abstract class AbstractProgramLoader implements Loader {
|
||||
if (fsrl.getMD5() == null) {
|
||||
fsrl = fsrl.withMD5(md5);
|
||||
}
|
||||
prog.getOptions(Program.PROGRAM_INFO)
|
||||
.setString(ProgramMappingService.PROGRAM_SOURCE_FSRL, fsrl.toString());
|
||||
FSRL.writeToProgramInfo(prog, fsrl);
|
||||
}
|
||||
prog.setExecutableMD5(md5);
|
||||
String sha256 = computeBinarySHA256(provider);
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.formats.gfilesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@ -65,6 +66,11 @@ public abstract class AbstractFileSystem<METADATATYPE> implements GFileSystem {
|
||||
return fsIndex.lookup(null, path, getFilenameComparator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile getRootDir() {
|
||||
return fsIndex.getRootDir();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GFile> getListing(GFile directory) {
|
||||
return fsIndex.getListing(directory);
|
||||
@ -75,4 +81,9 @@ public abstract class AbstractFileSystem<METADATATYPE> implements GFileSystem {
|
||||
return fsIndex.getFileCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile resolveSymlinks(GFile file) throws IOException {
|
||||
return fsIndex.resolveSymlinks(file);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.plugin.importer.ProgramMappingService;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
@ -64,6 +63,7 @@ import ghidra.util.SystemUtilities;
|
||||
*/
|
||||
public class FSRL {
|
||||
public static final String PARAM_MD5 = "MD5";
|
||||
public static final String FSRL_OPTION_NAME = "FSRL";
|
||||
|
||||
/**
|
||||
* Returns the {@link FSRL} stored in a {@link Program}'s properties, or null if not present
|
||||
@ -73,8 +73,7 @@ public class FSRL {
|
||||
* @return {@link FSRL} from program's properties, or null if not present or invalid
|
||||
*/
|
||||
public static FSRL fromProgram(Program program) {
|
||||
String fsrlStr = program.getOptions(Program.PROGRAM_INFO)
|
||||
.getString(ProgramMappingService.PROGRAM_SOURCE_FSRL, null);
|
||||
String fsrlStr = program.getOptions(Program.PROGRAM_INFO).getString(FSRL_OPTION_NAME, null);
|
||||
if (fsrlStr != null) {
|
||||
try {
|
||||
return FSRL.fromString(fsrlStr);
|
||||
@ -86,6 +85,16 @@ public class FSRL {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a FSRL value to a {@link Program}'s properties.
|
||||
*
|
||||
* @param program {@link Program}
|
||||
* @param fsrl {@link FSRL} to write
|
||||
*/
|
||||
public static void writeToProgramInfo(Program program, FSRL fsrl) {
|
||||
program.getOptions(Program.PROGRAM_INFO).setString(FSRL_OPTION_NAME, fsrl.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FSRL} from a raw string. The parent portions of the FSRL
|
||||
* are not intern()'d so will not be shared with other FSRL instances.
|
||||
|
@ -20,6 +20,8 @@ import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
@ -31,6 +33,7 @@ import org.apache.commons.io.FilenameUtils;
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
|
||||
import ghidra.formats.gfilesystem.fileinfo.FileType;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.CryptoException;
|
||||
@ -400,9 +403,7 @@ public class FSUtilities {
|
||||
public static String getFileMD5(File f, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
try (FileInputStream fis = new FileInputStream(f)) {
|
||||
monitor.initialize(f.length());
|
||||
monitor.setMessage("Hashing file: " + f.getName());
|
||||
return getMD5(fis, monitor);
|
||||
return getMD5(fis, f.getName(), f.length(), monitor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -418,9 +419,7 @@ public class FSUtilities {
|
||||
public static String getMD5(ByteProvider provider, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
try (InputStream is = provider.getInputStream(0)) {
|
||||
monitor.initialize(provider.length());
|
||||
monitor.setMessage("Hashing file: " + provider.getName());
|
||||
return getMD5(is, monitor);
|
||||
return getMD5(is, provider.getName(), provider.length(), monitor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -428,21 +427,39 @@ public class FSUtilities {
|
||||
* Calculate the hash of an {@link InputStream}.
|
||||
*
|
||||
* @param is {@link InputStream}
|
||||
* @param name of the inputstream
|
||||
* @param expectedLength the length of the inputstream
|
||||
* @param monitor {@link TaskMonitor} to update
|
||||
* @return md5 as a hex encoded string, never null
|
||||
* @throws IOException if error
|
||||
* @throws CancelledException if cancelled
|
||||
*/
|
||||
public static String getMD5(InputStream is, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
public static String getMD5(InputStream is, String name, long expectedLength,
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
try {
|
||||
long startms = System.currentTimeMillis();
|
||||
long prevElapsed = startms;
|
||||
|
||||
monitor.initialize(expectedLength, "Hashing %s".formatted(name));
|
||||
|
||||
MessageDigest messageDigest = MessageDigest.getInstance(HashUtilities.MD5_ALGORITHM);
|
||||
byte[] buf = new byte[16 * 1024];
|
||||
int bufSize = (int) Math.max(1024, Math.min(expectedLength, 1024 * 1024));
|
||||
byte[] buf = new byte[bufSize];
|
||||
int bytesRead;
|
||||
long totalBytesRead = 0;
|
||||
while ((bytesRead = is.read(buf)) >= 0) {
|
||||
messageDigest.update(buf, 0, bytesRead);
|
||||
monitor.incrementProgress(bytesRead);
|
||||
monitor.checkCancelled();
|
||||
totalBytesRead += bytesRead;
|
||||
monitor.increment(bytesRead);
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - prevElapsed > 5000 /*5 seconds*/ && totalBytesRead > bufSize) {
|
||||
prevElapsed = now;
|
||||
long elapsed = now - startms;
|
||||
long rate = (long) (totalBytesRead / (elapsed / 1000f));
|
||||
monitor.setMessage(
|
||||
"Hashing %s %s/s".formatted(name, FileUtilities.formatLength(rate)));
|
||||
}
|
||||
}
|
||||
return NumericUtilities.convertBytesToString(messageDigest.digest());
|
||||
}
|
||||
@ -587,4 +604,50 @@ public class FSUtilities {
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSymlink(File f) {
|
||||
try {
|
||||
return f != null && Files.isSymbolicLink(f.toPath());
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination of a symlink, or null if not a symlink or other error
|
||||
*
|
||||
* @param f {@link File} that is a symlink
|
||||
* @return destination path string of the symlink, or null if not symlink
|
||||
*/
|
||||
public static String readSymlink(File f) {
|
||||
try {
|
||||
Path symlink = Files.readSymbolicLink(f.toPath());
|
||||
return symlink.toString();
|
||||
}
|
||||
catch (Throwable th) {
|
||||
// ignore and return null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static FileType getFileType(File f) {
|
||||
try {
|
||||
Path p = f.toPath();
|
||||
if (Files.isSymbolicLink(p)) {
|
||||
return FileType.SYMBOLIC_LINK;
|
||||
}
|
||||
if (Files.isDirectory(p)) {
|
||||
return FileType.DIRECTORY;
|
||||
}
|
||||
if (Files.isRegularFile(p)) {
|
||||
return FileType.FILE;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// fall thru
|
||||
}
|
||||
return FileType.UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,53 +31,49 @@ public interface GFile {
|
||||
* The {@link GFileSystem} that owns this file.
|
||||
* @return {@link GFileSystem} that owns this file.
|
||||
*/
|
||||
public GFileSystem getFilesystem();
|
||||
GFileSystem getFilesystem();
|
||||
|
||||
/**
|
||||
* The {@link FSRL} of this file.
|
||||
*
|
||||
* @return {@link FSRL} of this file.
|
||||
*/
|
||||
public FSRL getFSRL();
|
||||
FSRL getFSRL();
|
||||
|
||||
/**
|
||||
* The parent directory of this file.
|
||||
*
|
||||
* @return parent {@link GFile} directory of this file.
|
||||
*/
|
||||
public GFile getParentFile();
|
||||
GFile getParentFile();
|
||||
|
||||
/**
|
||||
* The path and filename of this file, relative to its owning filesystem.
|
||||
*
|
||||
* @return path and filename of this file, relative to its owning filesystem.
|
||||
*/
|
||||
public String getPath();
|
||||
String getPath();
|
||||
|
||||
/**
|
||||
* The name of this file.
|
||||
*
|
||||
* @return name of this file.
|
||||
*/
|
||||
public String getName();
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns true if this is a directory.
|
||||
* <p>
|
||||
* @return boolean true if this file is a directory, false otherwise.
|
||||
*/
|
||||
public boolean isDirectory();
|
||||
boolean isDirectory();
|
||||
|
||||
/**
|
||||
* Returns the length of this file, or -1 if not known.
|
||||
*
|
||||
* @return number of bytes in this file.
|
||||
*/
|
||||
public long getLength();
|
||||
|
||||
default public long getLastModified() {
|
||||
return -1;
|
||||
}
|
||||
long getLength();
|
||||
|
||||
/**
|
||||
* Returns a listing of files in this sub-directory.
|
||||
@ -85,7 +81,7 @@ public interface GFile {
|
||||
* @return {@link List} of {@link GFile} instances.
|
||||
* @throws IOException if not a directory or error when accessing files.
|
||||
*/
|
||||
default public List<GFile> getListing() throws IOException {
|
||||
default List<GFile> getListing() throws IOException {
|
||||
return getFilesystem().getListing(this);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
package ghidra.formats.gfilesystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@link GFile} implementation that refers to a real java.io.File on the local
|
||||
@ -85,11 +86,6 @@ public class GFileLocal implements GFile {
|
||||
return f.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return f.lastModified();
|
||||
}
|
||||
|
||||
public File getLocalFile() {
|
||||
return f;
|
||||
}
|
||||
@ -99,4 +95,23 @@ public class GFileLocal implements GFile {
|
||||
return "Local " + f.toString() + " with path " + path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(f, fs, fsrl, parent, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof GFileLocal)) {
|
||||
return false;
|
||||
}
|
||||
GFileLocal other = (GFileLocal) obj;
|
||||
return Objects.equals(f, other.f) && Objects.equals(fs, other.fs) &&
|
||||
Objects.equals(fsrl, other.fsrl) && Objects.equals(parent, other.parent) &&
|
||||
Objects.equals(path, other.path);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
*
|
||||
* @return string filesystem volume name.
|
||||
*/
|
||||
public String getName();
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns the type of this file system.
|
||||
@ -62,7 +62,7 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
*
|
||||
* @return type string
|
||||
*/
|
||||
default public String getType() {
|
||||
default String getType() {
|
||||
return FSUtilities.getFilesystemTypeFromClass(this.getClass());
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
*
|
||||
* @return description string
|
||||
*/
|
||||
default public String getDescription() {
|
||||
default String getDescription() {
|
||||
return FSUtilities.getFilesystemDescriptionFromClass(this.getClass());
|
||||
}
|
||||
|
||||
@ -83,21 +83,21 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
*
|
||||
* @return {@link FSRLRoot} of this filesystem.
|
||||
*/
|
||||
public FSRLRoot getFSRL();
|
||||
FSRLRoot getFSRL();
|
||||
|
||||
/**
|
||||
* Returns true if the filesystem has been {@link #close() closed}
|
||||
*
|
||||
* @return boolean true if the filesystem has been closed.
|
||||
*/
|
||||
public boolean isClosed();
|
||||
boolean isClosed();
|
||||
|
||||
/**
|
||||
* Indicates if this filesystem is a static snapshot or changes.
|
||||
*
|
||||
* @return boolean true if the filesystem is static or false if dynamic content.
|
||||
*/
|
||||
default public boolean isStatic() {
|
||||
default boolean isStatic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -107,14 +107,14 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
* <p>
|
||||
* @return {@link FileSystemRefManager} that manages references to this filesystem.
|
||||
*/
|
||||
public FileSystemRefManager getRefManager();
|
||||
FileSystemRefManager getRefManager();
|
||||
|
||||
/**
|
||||
* Returns the number of files in the filesystem, if known, otherwise -1 if not known.
|
||||
*
|
||||
* @return number of files in this filesystem, -1 if not known.
|
||||
*/
|
||||
default public int getFileCount() {
|
||||
default int getFileCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -127,7 +127,23 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
* @return {@link GFile} instance of requested file, null if not found.
|
||||
* @throws IOException if IO error when looking up file.
|
||||
*/
|
||||
public GFile lookup(String path) throws IOException;
|
||||
GFile lookup(String path) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the file system's root directory.
|
||||
* <p>
|
||||
* Note: using {@code null} when calling {@link #getListing(GFile)} is also valid.
|
||||
*
|
||||
* @return file system's root directory
|
||||
*/
|
||||
default GFile getRootDir() {
|
||||
try {
|
||||
return lookup(null);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} that contains the contents of the specified {@link GFile}.
|
||||
@ -141,7 +157,7 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
* @throws IOException if IO problem
|
||||
* @throws CancelledException if user cancels.
|
||||
*/
|
||||
default public InputStream getInputStream(GFile file, TaskMonitor monitor)
|
||||
default InputStream getInputStream(GFile file, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
return getInputStreamHelper(file, this, monitor);
|
||||
}
|
||||
@ -158,7 +174,7 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
* @throws IOException if error
|
||||
* @throws CancelledException if user cancels
|
||||
*/
|
||||
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
|
||||
ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
|
||||
throws IOException, CancelledException;
|
||||
|
||||
/**
|
||||
@ -169,7 +185,7 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
* @return {@link List} of {@link GFile} instances of file in the requested directory.
|
||||
* @throws IOException if IO problem.
|
||||
*/
|
||||
public List<GFile> getListing(GFile directory) throws IOException;
|
||||
List<GFile> getListing(GFile directory) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a container of {@link FileAttribute} values.
|
||||
@ -181,10 +197,23 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
* @param monitor {@link TaskMonitor}
|
||||
* @return {@link FileAttributes} instance (possibly read-only), maybe empty but never null
|
||||
*/
|
||||
default public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
|
||||
default FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
|
||||
return FileAttributes.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the specified (symlink) file into it's destination, or if not a symlink,
|
||||
* returns the original file unchanged.
|
||||
*
|
||||
* @param file symlink file to follow
|
||||
* @return destination of symlink, or original file if not a symlink
|
||||
* @throws IOException if error following symlink path, typically outside of the hosting
|
||||
* file system
|
||||
*/
|
||||
default GFile resolveSymlinks(GFile file) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of getting an {@link InputStream} from a {@link GFile}'s
|
||||
* {@link ByteProvider}.
|
||||
@ -197,7 +226,7 @@ public interface GFileSystem extends Closeable, ExtensionPoint {
|
||||
* @throws CancelledException if canceled
|
||||
* @throws IOException if error
|
||||
*/
|
||||
public static InputStream getInputStreamHelper(GFile file, GFileSystem fs, TaskMonitor monitor)
|
||||
static InputStream getInputStreamHelper(GFile file, GFileSystem fs, TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
ByteProvider bp = fs.getByteProvider(file, monitor);
|
||||
return (bp != null) ? new ByteProviderInputStream.ClosingInputStream(bp) : null;
|
||||
|
@ -43,7 +43,7 @@ public interface GIconProvider {
|
||||
* @throws IOException if problem reading or converting image.
|
||||
* @throws CancelledException if user cancels.
|
||||
*/
|
||||
public Icon getIcon(GFile file, TaskMonitor monitor) throws IOException, CancelledException;
|
||||
Icon getIcon(GFile file, TaskMonitor monitor) throws IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Helper static method that will get an Icon from a data file.
|
||||
@ -54,11 +54,11 @@ public interface GIconProvider {
|
||||
* file couldn't be converted into an image.
|
||||
* @throws CancelledException if the user cancels.
|
||||
*/
|
||||
public static Icon getIconForFile(GFile file, TaskMonitor monitor) throws CancelledException {
|
||||
static Icon getIconForFile(GFile file, TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
GFileSystem fs = file.getFilesystem();
|
||||
if (fs instanceof GIconProvider) {
|
||||
return ((GIconProvider) fs).getIcon(file, monitor);
|
||||
if (fs instanceof GIconProvider iconProviderFS) {
|
||||
return iconProviderFS.getIcon(file, monitor);
|
||||
}
|
||||
|
||||
try (InputStream is = file.getFilesystem().getInputStream(file, monitor)) {
|
||||
|
@ -18,7 +18,7 @@ package ghidra.formats.gfilesystem;
|
||||
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.AccessMode;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.collections4.map.ReferenceMap;
|
||||
@ -29,6 +29,7 @@ import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
|
||||
import ghidra.formats.gfilesystem.factory.GFileSystemFactory;
|
||||
import ghidra.formats.gfilesystem.factory.GFileSystemFactoryIgnore;
|
||||
import ghidra.formats.gfilesystem.fileinfo.*;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@ -55,14 +56,17 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
|
||||
return new LocalFileSystem(FSRLRoot.makeRoot(FSTYPE));
|
||||
}
|
||||
|
||||
private final List<GFile> emptyDir = List.of();
|
||||
private final FSRLRoot fsFSRL;
|
||||
private final GFile rootDir;
|
||||
private final FileSystemRefManager refManager = new FileSystemRefManager(this);
|
||||
private final ReferenceMap<FileFingerprintRec, String> fileFingerprintToMD5Map =
|
||||
new ReferenceMap<>();
|
||||
private final boolean needsListRoots =
|
||||
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS;
|
||||
|
||||
private LocalFileSystem(FSRLRoot fsrl) {
|
||||
this.fsFSRL = fsrl;
|
||||
this.rootDir = GFileImpl.fromFSRL(this, null, fsFSRL.withPath("/"), true, -1);
|
||||
}
|
||||
|
||||
boolean isSameFS(FSRL fsrl) {
|
||||
@ -143,8 +147,9 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
|
||||
@Override
|
||||
public List<GFile> getListing(GFile directory) {
|
||||
List<GFile> results = new ArrayList<>();
|
||||
directory = Objects.requireNonNullElse(directory, rootDir);
|
||||
|
||||
if (directory == null) {
|
||||
if (directory.equals(rootDir) && needsListRoots) {
|
||||
for (File f : File.listRoots()) {
|
||||
FSRL rootElemFSRL = fsFSRL.withPath(FSUtilities.normalizeNativePath(f.getName()));
|
||||
results.add(GFileImpl.fromFSRL(this, null, rootElemFSRL, f.isDirectory(), -1));
|
||||
@ -152,17 +157,17 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
|
||||
}
|
||||
else {
|
||||
File localDir = new File(directory.getPath());
|
||||
if (!localDir.isDirectory() || Files.isSymbolicLink(localDir.toPath())) {
|
||||
return emptyDir;
|
||||
if (!localDir.isDirectory() || FSUtilities.isSymlink(localDir)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
File[] files = localDir.listFiles();
|
||||
if (files == null) {
|
||||
return emptyDir;
|
||||
return List.of();
|
||||
}
|
||||
|
||||
for (File f : files) {
|
||||
if (f.isFile() || f.isDirectory()) {
|
||||
if (f.isFile() || f.isDirectory() || FSUtilities.isSymlink(f)) {
|
||||
FSRL newFileFSRL = directory.getFSRL().appendPath(f.getName());
|
||||
results.add(GFileImpl.fromFSRL(this, directory, newFileFSRL, f.isDirectory(),
|
||||
f.length()));
|
||||
@ -186,36 +191,14 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
|
||||
* @return {@link FileAttributes} instance
|
||||
*/
|
||||
public FileAttributes getFileAttributes(File f) {
|
||||
Path p = f.toPath();
|
||||
FileType fileType = fileToFileType(p);
|
||||
Path symLinkDest = null;
|
||||
try {
|
||||
symLinkDest = fileType == FileType.SYMBOLIC_LINK ? Files.readSymbolicLink(p) : null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore and continue with symLinkDest == null
|
||||
}
|
||||
FileType fileType = FSUtilities.getFileType(f);
|
||||
String symLinkDest = fileType == FileType.SYMBOLIC_LINK ? FSUtilities.readSymlink(f) : null;
|
||||
return FileAttributes.of(
|
||||
FileAttribute.create(NAME_ATTR, f.getName()),
|
||||
FileAttribute.create(FILE_TYPE_ATTR, fileType),
|
||||
FileAttribute.create(SIZE_ATTR, f.length()),
|
||||
FileAttribute.create(MODIFIED_DATE_ATTR, new Date(f.lastModified())),
|
||||
symLinkDest != null
|
||||
? FileAttribute.create(SYMLINK_DEST_ATTR, symLinkDest.toString())
|
||||
: null);
|
||||
}
|
||||
|
||||
private static FileType fileToFileType(Path p) {
|
||||
if (Files.isSymbolicLink(p)) {
|
||||
return FileType.SYMBOLIC_LINK;
|
||||
}
|
||||
if (Files.isDirectory(p)) {
|
||||
return FileType.DIRECTORY;
|
||||
}
|
||||
if (Files.isRegularFile(p)) {
|
||||
return FileType.FILE;
|
||||
}
|
||||
return FileType.UNKNOWN;
|
||||
symLinkDest != null ? FileAttribute.create(SYMLINK_DEST_ATTR, symLinkDest) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -223,6 +206,11 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
|
||||
return fsFSRL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile getRootDir() {
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile lookup(String path) throws IOException {
|
||||
File f = lookupFile(null, path, null);
|
||||
@ -259,6 +247,18 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
|
||||
return new FileByteProvider(f, fsrl, AccessMode.READ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile resolveSymlinks(GFile file) throws IOException {
|
||||
File f = getLocalFile(file.getFSRL());
|
||||
File canonicalFile = f.getCanonicalFile();
|
||||
if (f.equals(canonicalFile)) {
|
||||
return file;
|
||||
}
|
||||
return GFileImpl.fromPathString(this,
|
||||
FSUtilities.normalizeNativePath(canonicalFile.getPath()), null,
|
||||
canonicalFile.isDirectory(), canonicalFile.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Local file system " + fsFSRL;
|
||||
@ -327,13 +327,18 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
|
||||
// If not using a comparator, or if the requested path is a
|
||||
// root element (eg "/", or "c:\\"), don't do per-directory-path lookups.
|
||||
|
||||
// On windows, getCanonicalFile() will return a corrected path using the case of
|
||||
// the file element on the file system (eg. "c:/users" -> "c:/Users"), if the
|
||||
// element exists.
|
||||
return f.exists() ? f.getCanonicalFile() : null;
|
||||
if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) {
|
||||
// On windows, getCanonicalFile() will return a corrected path using the case of
|
||||
// the file element on the file system (eg. "c:/users" -> "c:/Users"), if the
|
||||
// element exists.
|
||||
// We don't want to do this on unix-ish file systems as it will follow symlinks
|
||||
f = f.getCanonicalFile();
|
||||
}
|
||||
return FSUtilities.isSymlink(f) || f.exists() ? f : null;
|
||||
}
|
||||
|
||||
if (f.exists()) {
|
||||
// Test the file's path using the name comparator
|
||||
if (f.exists() && baseDir == null) {
|
||||
// try to short-cut by comparing the entire path string
|
||||
File canonicalFile = f.getCanonicalFile();
|
||||
if (nameComp.compare(path,
|
||||
|
@ -16,7 +16,6 @@
|
||||
package ghidra.formats.gfilesystem;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -97,7 +96,7 @@ public class LocalFileSystemSub implements GFileSystem, GFileHashProvider {
|
||||
return List.of();
|
||||
}
|
||||
File localDir = getFileFromGFile(directory);
|
||||
if (Files.isSymbolicLink(localDir.toPath())) {
|
||||
if (FSUtilities.isSymlink(localDir)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@ -112,7 +111,8 @@ public class LocalFileSystemSub implements GFileSystem, GFileHashProvider {
|
||||
String relPath = FSUtilities.normalizeNativePath(directory.getPath());
|
||||
|
||||
for (File f : localFiles) {
|
||||
if (!(f.isFile() || f.isDirectory())) {
|
||||
boolean isSymlink = FSUtilities.isSymlink(f); // check this manually to allow broken symlinks to appear in listing
|
||||
if (!(isSymlink || f.isFile() || f.isDirectory())) {
|
||||
// skip non-file things
|
||||
continue;
|
||||
}
|
||||
@ -133,7 +133,7 @@ public class LocalFileSystemSub implements GFileSystem, GFileHashProvider {
|
||||
return rootFS.getFileAttributes(localFile);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// fail and return null
|
||||
// fail and return empty
|
||||
}
|
||||
return FileAttributes.EMPTY;
|
||||
}
|
||||
@ -148,6 +148,11 @@ public class LocalFileSystemSub implements GFileSystem, GFileHashProvider {
|
||||
return fsFSRL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile getRootDir() {
|
||||
return rootGFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile lookup(String path) throws IOException {
|
||||
File f = LocalFileSystem.lookupFile(localfsRootDir, path, null);
|
||||
@ -210,4 +215,14 @@ public class LocalFileSystemSub implements GFileSystem, GFileHashProvider {
|
||||
throws CancelledException, IOException {
|
||||
return rootFS.getMD5Hash(file.getFSRL(), required, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile resolveSymlinks(GFile file) throws IOException {
|
||||
File f = getFileFromGFile(file);
|
||||
File canonicalFile = f.getCanonicalFile();
|
||||
if (f.equals(canonicalFile)) {
|
||||
return file;
|
||||
}
|
||||
return getGFile(canonicalFile);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package ghidra.formats.gfilesystem.factory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@ -212,18 +211,13 @@ public class FileSystemFactoryMgr {
|
||||
byte[] startBytes = byteProvider.readBytes(0, pboByteCount);
|
||||
for (FileSystemInfoRec fsir : sortedFactories) {
|
||||
try {
|
||||
if (fsir.getFactory() instanceof GFileSystemProbeBytesOnly) {
|
||||
GFileSystemProbeBytesOnly factoryProbe =
|
||||
(GFileSystemProbeBytesOnly) fsir.getFactory();
|
||||
if (factoryProbe.getBytesRequired() <= startBytes.length) {
|
||||
if (factoryProbe.probeStartBytes(containerFSRL, startBytes)) {
|
||||
return true;
|
||||
}
|
||||
if (fsir.getFactory() instanceof GFileSystemProbeBytesOnly factoryProbe) {
|
||||
if (factoryProbe.getBytesRequired() <= startBytes.length &&
|
||||
factoryProbe.probeStartBytes(containerFSRL, startBytes)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (fsir.getFactory() instanceof GFileSystemProbeByteProvider) {
|
||||
GFileSystemProbeByteProvider factoryProbe =
|
||||
(GFileSystemProbeByteProvider) fsir.getFactory();
|
||||
if (fsir.getFactory() instanceof GFileSystemProbeByteProvider factoryProbe) {
|
||||
if (factoryProbe.probe(byteProvider, fsService, monitor)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -301,7 +301,6 @@ public class ImporterPlugin extends Plugin
|
||||
if (addToProgramAction != null) {
|
||||
addToProgramAction.setEnabled(false);
|
||||
}
|
||||
ProgramMappingService.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -315,8 +314,6 @@ public class ImporterPlugin extends Plugin
|
||||
if (addToProgramAction != null) {
|
||||
addToProgramAction.setEnabled(false);
|
||||
}
|
||||
|
||||
ProgramMappingService.clear();
|
||||
}
|
||||
|
||||
private void setupImportAction() {
|
||||
|
@ -151,8 +151,8 @@ public class ImporterUtilities {
|
||||
|
||||
if (!isFSContainer) {
|
||||
// normal file; do a single-file import
|
||||
importSingleFile(fullFsrl, destinationFolder, suggestedPath, tool, programManager,
|
||||
monitor);
|
||||
showImportSingleFileDialog(fullFsrl, destinationFolder, suggestedPath, tool,
|
||||
programManager, monitor);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -213,8 +213,8 @@ public class ImporterUtilities {
|
||||
}
|
||||
|
||||
if (choice == 1) {
|
||||
importSingleFile(fullFsrl, destinationFolder, suggestedPath, tool, programManager,
|
||||
monitor);
|
||||
showImportSingleFileDialog(fullFsrl, destinationFolder, suggestedPath, tool,
|
||||
programManager, monitor);
|
||||
}
|
||||
else if (choice == 2) {
|
||||
BatchImportDialog.showAndImport(tool, null, Arrays.asList(fullFsrl), destinationFolder,
|
||||
@ -276,8 +276,9 @@ public class ImporterUtilities {
|
||||
* to the destination filename
|
||||
* @param tool the parent UI component
|
||||
* @param programManager optional {@link ProgramManager} instance to open the imported file in
|
||||
* @param monitor {@link TaskMonitor}
|
||||
*/
|
||||
private static void importSingleFile(FSRL fsrl, DomainFolder destinationFolder,
|
||||
public static void showImportSingleFileDialog(FSRL fsrl, DomainFolder destinationFolder,
|
||||
String suggestedPath, PluginTool tool, ProgramManager programManager,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
@ -420,8 +421,6 @@ public class ImporterUtilities {
|
||||
monitor.checkCancelled();
|
||||
|
||||
if (loaded.getDomainObject() instanceof Program program) {
|
||||
ProgramMappingService.createAssociation(fsrl, program);
|
||||
|
||||
if (programManager != null) {
|
||||
int openState = firstProgram
|
||||
? ProgramManager.OPEN_CURRENT
|
||||
|
@ -1,482 +0,0 @@
|
||||
/* ###
|
||||
* 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.plugin.importer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.formats.gfilesystem.FileSystemService;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.FixedSizeHashMap;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Provides a best-effort<sup>[1]</sup> mapping / association between Ghidra Program/DomainFile
|
||||
* objects and GFilesystem files (identified by their {@link FSRL}).
|
||||
* <p>
|
||||
* As there is no current feature that allows you to quickly query the metadata of
|
||||
* Programs/DomainFile objects in the current project, finding a Program by its MD5 or by a
|
||||
* original source location string is not easily possible.
|
||||
* <p>
|
||||
* Threadsafe.
|
||||
* <p>
|
||||
* The current implementation searches current open Ghidra Programs and maintains a
|
||||
* short-lived, in-memory only mapping of FSRL->DomainFile paths
|
||||
* (manually updated by users of the ProgramMappingService when
|
||||
* they do an import or other operation that creates a Ghidra DomainFile by calling
|
||||
* {@link #createAssociation(FSRL, DomainFile)} and friends.)
|
||||
* <p>
|
||||
* [1] - best-effort (adverb): meaning a dirty hack.
|
||||
*/
|
||||
public class ProgramMappingService {
|
||||
public static final String PROGRAM_METADATA_MD5 = "Executable MD5";
|
||||
public static final String PROGRAM_SOURCE_FSRL = "FSRL";
|
||||
|
||||
private static final int FSRL_TO_PATH_MAP_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* LRU mapping from FSRL to the project path string of a DomainFile object.
|
||||
* <p>
|
||||
* Limited in size to {@value #FSRL_TO_PATH_MAP_SIZE}.
|
||||
*/
|
||||
private static Map<FSRL, String> fsrlToProjectPathMap =
|
||||
new FixedSizeHashMap<>(FSRL_TO_PATH_MAP_SIZE);
|
||||
|
||||
private ProgramMappingService() {
|
||||
// utils class; cannot instantiate
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears {@link ProgramMappingService} data.
|
||||
* <p>
|
||||
* This should be done whenever the project is opened/closed.
|
||||
*/
|
||||
public static void clear() {
|
||||
synchronized (fsrlToProjectPathMap) {
|
||||
fsrlToProjectPathMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a current open Ghidra {@link Program} that has metadata
|
||||
* that links it to the specified {@link FSRL}.
|
||||
* <p>
|
||||
* (ie. an open program has a MD5 or FSRL metadata value that matches the fsrl param.)
|
||||
*
|
||||
* @param fsrl {@link FSRL} to search for in open program info.
|
||||
* @return boolean true if found.
|
||||
*/
|
||||
public static boolean isFileOpen(FSRL fsrl) {
|
||||
String expectedMD5 = fsrl.getMD5();
|
||||
|
||||
List<DomainFile> openDomainFiles = findOpenFiles();
|
||||
|
||||
Object consumer = new Object();
|
||||
for (DomainFile df : openDomainFiles) {
|
||||
DomainObject openedDomainObject = df.getOpenedDomainObject(consumer);
|
||||
try {
|
||||
if (openedDomainObject instanceof Program) {
|
||||
Program program = (Program) openedDomainObject;
|
||||
Options propertyList = program.getOptions(Program.PROGRAM_INFO);
|
||||
String fsrlStr =
|
||||
propertyList.getString(ProgramMappingService.PROGRAM_SOURCE_FSRL, null);
|
||||
String md5 =
|
||||
propertyList.getString(ProgramMappingService.PROGRAM_METADATA_MD5, null);
|
||||
|
||||
if ((expectedMD5 != null && expectedMD5.equals(md5)) ||
|
||||
fsrl.isEquivalent(fsrlStr)) {
|
||||
createAssociation(fsrl, program);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (openedDomainObject != null && openedDomainObject.isUsedBy(consumer)) {
|
||||
openedDomainObject.release(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified {@link FSRL} has a matched Ghidra {@link DomainFile}
|
||||
* in the current project.
|
||||
* <p>
|
||||
* @param fsrl {@link FSRL} to search for
|
||||
* @return boolean true if file exists in project.
|
||||
*/
|
||||
public static boolean isFileImportedIntoProject(FSRL fsrl) {
|
||||
return isFileOpen(fsrl) || (getCachedDomainFileFor(fsrl) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to a {@link DomainFile} in the current {@link Project} that matches
|
||||
* the specified {@link FSRL}.
|
||||
* <p>
|
||||
* This method only consults an internal fsrl-to-DomainFile mapping that is short-lived
|
||||
* and not persisted.
|
||||
* <p>
|
||||
* @param fsrl {@link FSRL} to search for
|
||||
* @return {@link DomainFile} that was previously associated via
|
||||
* {@link #createAssociation(FSRL, DomainFile)} and friends.
|
||||
*/
|
||||
public static DomainFile getCachedDomainFileFor(FSRL fsrl) {
|
||||
String path = null;
|
||||
synchronized (fsrlToProjectPathMap) {
|
||||
path = fsrlToProjectPathMap.get(fsrl);
|
||||
if (path == null && fsrl.getMD5() != null) {
|
||||
fsrl = fsrl.withMD5(null);
|
||||
path = fsrlToProjectPathMap.get(fsrl);
|
||||
}
|
||||
}
|
||||
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DomainFile domainFile = getProjectFile(path);
|
||||
if (domainFile == null) {
|
||||
// The domainFile will be null if the cached path is no longer valid. Remove
|
||||
// the stale path from the cache.
|
||||
synchronized (fsrlToProjectPathMap) {
|
||||
if (Objects.equals(fsrlToProjectPathMap.get(fsrl), path)) {
|
||||
fsrlToProjectPathMap.remove(fsrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
return domainFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a short-lived association between a {@link FSRL} and an open {@link Program}.
|
||||
* <p>
|
||||
* @param fsrl {@link FSRL} of where the {@link Program} was imported from.
|
||||
* @param program {@link Program} to associate to.
|
||||
*/
|
||||
public static void createAssociation(FSRL fsrl, Program program) {
|
||||
synchronized (fsrlToProjectPathMap) {
|
||||
fsrlToProjectPathMap.put(fsrl, program.getDomainFile().getPathname());
|
||||
fsrlToProjectPathMap.put(fsrl.withMD5(null), program.getDomainFile().getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a short-lived association between a {@link FSRL} and a {@link DomainFile}.
|
||||
*
|
||||
* @param fsrl {@link FSRL} of where the DomainFile was imported from.
|
||||
* @param domainFile {@link DomainFile} to associate with
|
||||
*/
|
||||
public static void createAssociation(FSRL fsrl, DomainFile domainFile) {
|
||||
createAssociation(fsrl, domainFile, false);
|
||||
}
|
||||
|
||||
private static void createAssociation(FSRL fsrl, DomainFile domainFile,
|
||||
boolean onlyAddIfEnoughRoomInCache) {
|
||||
synchronized (fsrlToProjectPathMap) {
|
||||
if (!onlyAddIfEnoughRoomInCache ||
|
||||
fsrlToProjectPathMap.size() < FSRL_TO_PATH_MAP_SIZE) {
|
||||
fsrlToProjectPathMap.put(fsrl, domainFile.getPathname());
|
||||
fsrlToProjectPathMap.put(fsrl.withMD5(null), domainFile.getPathname());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to create an association between the specified open {@code program} and
|
||||
* any {@link FSRL} metadata found in the {@link Program}s properties.
|
||||
* <p>
|
||||
* Used by event handlers that get notified about a {@link Program} being opened to
|
||||
* opportunistically link that program to its source FSRL if the metadata is present.
|
||||
* <p>
|
||||
* @param program {@link Program} to rummage around in its metadata looking for FSRL info.
|
||||
*/
|
||||
public static void createAutoAssocation(Program program) {
|
||||
if (program != null) {
|
||||
Options propertyList = program.getOptions(Program.PROGRAM_INFO);
|
||||
String fsrlStr =
|
||||
propertyList.getString(ProgramMappingService.PROGRAM_SOURCE_FSRL, null);
|
||||
if (fsrlStr != null) {
|
||||
try {
|
||||
FSRL fsrl = FSRL.fromString(fsrlStr);
|
||||
synchronized (fsrlToProjectPathMap) {
|
||||
if (!fsrlToProjectPathMap.containsKey(fsrl)) {
|
||||
fsrlToProjectPathMap.put(fsrl, program.getDomainFile().getPathname());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
Msg.error(ProgramMappingService.class, "Bad FSRL found: " + fsrlStr +
|
||||
", program: " + program.getDomainFile().getPathname());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an open {@link Program} instance that matches the specified
|
||||
* {@link FSRL}, either from the set of currently open programs, or by
|
||||
* requesting the specified {@link ProgramManager} to
|
||||
* open a {@link DomainFile} that was found to match this GFile.
|
||||
* <p>
|
||||
* @param fsrl {@link FSRL} of program original location.
|
||||
* @param consumer Object that will be used to pin the matching Program open. Caller
|
||||
* must release the consumer when done.
|
||||
* @param programManager {@link ProgramManager} that will be used to open DomainFiles
|
||||
* if necessary.
|
||||
* @param openState one of {@link ProgramManager#OPEN_VISIBLE},
|
||||
* {@link ProgramManager#OPEN_HIDDEN}, {@link ProgramManager#OPEN_VISIBLE}
|
||||
* @return {@link Program} which was imported from the specified FSRL, or null if not found.
|
||||
*/
|
||||
public static Program findMatchingProgramOpenIfNeeded(FSRL fsrl, Object consumer,
|
||||
ProgramManager programManager, int openState) {
|
||||
return findMatchingProgramOpenIfNeeded(fsrl, null, consumer, programManager, openState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an open {@link Program} instance that matches the specified
|
||||
* {@link FSRL}, either from the set of currently open programs, or by
|
||||
* requesting the specified {@link ProgramManager} to
|
||||
* open a {@link DomainFile} that was found to match this GFile.
|
||||
* <p>
|
||||
* @param fsrl {@link FSRL} of program original location.
|
||||
* @param domainFile optional {@link DomainFile} that corresponds to the FSRL param.
|
||||
* @param consumer Object that will be used to pin the matching Program open. Caller
|
||||
* must release the consumer when done.
|
||||
* @param programManager {@link ProgramManager} that will be used to open DomainFiles
|
||||
* if necessary.
|
||||
* @param openState one of {@link ProgramManager#OPEN_VISIBLE},
|
||||
* {@link ProgramManager#OPEN_HIDDEN}, {@link ProgramManager#OPEN_VISIBLE}
|
||||
* @return {@link Program} which was imported from the specified FSRL, or null if not found.
|
||||
*/
|
||||
public static Program findMatchingProgramOpenIfNeeded(FSRL fsrl, DomainFile domainFile,
|
||||
Object consumer, ProgramManager programManager, int openState) {
|
||||
Program program = findMatchingOpenProgram(fsrl, consumer);
|
||||
if (program != null) {
|
||||
programManager.openProgram(program, openState);
|
||||
return program;
|
||||
}
|
||||
DomainFile df = (domainFile == null) ? getCachedDomainFileFor(fsrl) : domainFile;
|
||||
if (df == null || programManager == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
program = programManager.openProgram(df, DomainFile.DEFAULT_VERSION, openState);
|
||||
if (program != null) {
|
||||
program.addConsumer(consumer);
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a currently open Ghidra {@link Program} that has metadata that links it
|
||||
* to the specified {@code file} parameter.
|
||||
* <p>
|
||||
* (ie. an open program has a MD5 or FSRL metadata value that matches the file)
|
||||
* <p>
|
||||
* See also {@link #isFileOpen(FSRL)}.
|
||||
* <p>
|
||||
* @param fsrl {@link FSRL} to use when inspecting each open Program's metadata.
|
||||
* @param consumer Object that will be used to pin the matching Program open. Caller
|
||||
* must release the consumer when done.
|
||||
* @return Already open {@link Program} that has matching metadata, or null if not found.
|
||||
*/
|
||||
public static Program findMatchingOpenProgram(FSRL fsrl, Object consumer) {
|
||||
String expectedMD5 = fsrl.getMD5();
|
||||
|
||||
// use a temp consumer to hold the domainObject open because the caller-supplied
|
||||
// consumer might already have been used to open one of the files we are querying.
|
||||
Object tmpConsumer = new Object();
|
||||
List<DomainFile> openDomainFiles = getOpenFiles();
|
||||
for (DomainFile df : openDomainFiles) {
|
||||
DomainObject openedDomainObject = df.getOpenedDomainObject(tmpConsumer);
|
||||
try {
|
||||
if (openedDomainObject instanceof Program) {
|
||||
Program program = (Program) openedDomainObject;
|
||||
Options propertyList = program.getOptions(Program.PROGRAM_INFO);
|
||||
String fsrlStr =
|
||||
propertyList.getString(ProgramMappingService.PROGRAM_SOURCE_FSRL, null);
|
||||
String md5 =
|
||||
propertyList.getString(ProgramMappingService.PROGRAM_METADATA_MD5, null);
|
||||
|
||||
if ((expectedMD5 != null && expectedMD5.equals(md5)) ||
|
||||
fsrl.isEquivalent(fsrlStr)) {
|
||||
// lock the domain file with the caller-supplied consumer now that
|
||||
// we've found it.
|
||||
df.getOpenedDomainObject(consumer);
|
||||
return program;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (openedDomainObject != null) {
|
||||
openedDomainObject.release(tmpConsumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively searches the current active {@link Project} for {@link DomainFile}s that
|
||||
* have metadata that matches a {@link FSRL} in the specified list.
|
||||
* <p>
|
||||
* Warning, this operation is expensive and should only be done in a Task thread.
|
||||
* <p>
|
||||
* @param fsrls List of {@link FSRL} to match against the metadata of each DomainFile in Project.
|
||||
* @param monitor {@link TaskMonitor} to watch for cancel and update with progress.
|
||||
* @return Map of FSRLs to {@link DomainFile}s of the found files, never null.
|
||||
*/
|
||||
public static Map<FSRL, DomainFile> searchProjectForMatchingFiles(List<FSRL> fsrls,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
Project project = AppInfo.getActiveProject();
|
||||
if (project == null) {
|
||||
// this should not be possible if this call is being run as a task
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
ProjectData projectData = project.getProjectData();
|
||||
int fc = projectData.getFileCount();
|
||||
if (fc > 0) {
|
||||
monitor.setShowProgressValue(true);
|
||||
monitor.setMaximum(fc);
|
||||
monitor.setProgress(0);
|
||||
}
|
||||
else {
|
||||
monitor.setIndeterminate(true);
|
||||
}
|
||||
monitor.setMessage("Searching project for matching files");
|
||||
|
||||
Map<String, FSRL> fsrlsToFindByMD5;
|
||||
try {
|
||||
fsrlsToFindByMD5 = buildFullyQualifiedFSRLMap(fsrls, monitor);
|
||||
}
|
||||
catch (CancelledException ce) {
|
||||
Msg.info(ProgramMappingService.class, "Canceling project search");
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<FSRL, DomainFile> results = new HashMap<>();
|
||||
|
||||
Iterable<DomainFile> files = ProjectDataUtils.descendantFiles(projectData.getRootFolder());
|
||||
for (DomainFile domainFile : files) {
|
||||
if (monitor.isCancelled() || fsrlsToFindByMD5.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
monitor.incrementProgress(1);
|
||||
Map<String, String> metadata = domainFile.getMetadata();
|
||||
|
||||
FSRL dfFSRL = getFSRLFromMetadata(metadata, domainFile);
|
||||
if (dfFSRL != null) {
|
||||
// side effect: create association between the FSRL in the DomainFile's props
|
||||
// to the DomainFile's path if there is room in the cache.
|
||||
// (ie. don't blow out the cache for files that haven't been requested yet)
|
||||
createAssociation(dfFSRL, domainFile, true);
|
||||
}
|
||||
String dfMD5 = (dfFSRL != null) ? dfFSRL.getMD5() : getMD5FromMetadata(metadata);
|
||||
if (dfMD5 != null) {
|
||||
FSRL matchedFSRL = fsrlsToFindByMD5.get(dfMD5);
|
||||
if (matchedFSRL != null) {
|
||||
results.put(matchedFSRL, domainFile);
|
||||
fsrlsToFindByMD5.remove(dfMD5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static String getMD5FromMetadata(Map<String, String> metadata) {
|
||||
return metadata.get(PROGRAM_METADATA_MD5);
|
||||
}
|
||||
|
||||
private static FSRL getFSRLFromMetadata(Map<String, String> metadata, DomainFile domainFile) {
|
||||
String dfFSRLStr = metadata.get(PROGRAM_SOURCE_FSRL);
|
||||
if (dfFSRLStr != null) {
|
||||
try {
|
||||
FSRL dfFSRL = FSRL.fromString(dfFSRLStr);
|
||||
return dfFSRL;
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
Msg.warn(ProgramMappingService.class,
|
||||
"Domain file " + domainFile.getPathname() + " has a bad FSRL: " + dfFSRLStr);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DomainFile getProjectFile(String path) {
|
||||
|
||||
Project project = AppInfo.getActiveProject();
|
||||
if (project != null) {
|
||||
ProjectData data = project.getProjectData();
|
||||
if (data != null) {
|
||||
return data.getFile(path);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<DomainFile> getOpenFiles() {
|
||||
|
||||
List<DomainFile> files = new ArrayList<>();
|
||||
Project project = AppInfo.getActiveProject();
|
||||
if (project != null) {
|
||||
files = project.getOpenData();
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private static List<DomainFile> findOpenFiles() {
|
||||
|
||||
List<DomainFile> files = new ArrayList<>();
|
||||
Project project = AppInfo.getActiveProject();
|
||||
if (project != null) {
|
||||
ProjectData data = project.getProjectData();
|
||||
if (data != null) {
|
||||
data.findOpenFiles(files);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private static Map<String, FSRL> buildFullyQualifiedFSRLMap(List<FSRL> fsrls,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
Map<String, FSRL> result = new HashMap<>();
|
||||
for (FSRL fsrl : fsrls) {
|
||||
try {
|
||||
FSRL fqFSRL = FileSystemService.getInstance().getFullyQualifiedFSRL(fsrl, monitor);
|
||||
String expectedMD5 = fqFSRL.getMD5();
|
||||
result.put(expectedMD5, fsrl);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore and continue
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
/* ###
|
||||
* 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.plugin.importer;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* An in-memory index of FSRL-to-domainfile in the current project.
|
||||
*/
|
||||
public class ProjectIndexService implements DomainFolderChangeListener {
|
||||
|
||||
public static ProjectIndexService getInstance() {
|
||||
return SingletonHolder.instance;
|
||||
}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final ProjectIndexService instance = new ProjectIndexService();
|
||||
}
|
||||
|
||||
public enum IndexType {
|
||||
MD5("Executable MD5"), FSRL("FSRL");
|
||||
|
||||
private String metadataKey;
|
||||
|
||||
IndexType(String metadataKey) {
|
||||
this.metadataKey = metadataKey;
|
||||
}
|
||||
|
||||
public String getMetadataKey() {
|
||||
return metadataKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param indexType IndexType enum
|
||||
* @param mappingFunc bifunc that returns value that will be used to lookup the file
|
||||
* @param indexedFiles map of index keyvalue to fileId (either string or list of strings)
|
||||
*
|
||||
*/
|
||||
record IndexInfo(IndexType indexType,
|
||||
BiFunction<DomainFile, Map<String, String>, Object> mappingFunc,
|
||||
Map<Object, Object> indexedFiles) {
|
||||
IndexInfo(IndexType indexType,
|
||||
BiFunction<DomainFile, Map<String, String>, Object> mappingFunc) {
|
||||
this(indexType, mappingFunc, new HashMap<>());
|
||||
}
|
||||
}
|
||||
|
||||
private Project project;
|
||||
private List<IndexInfo> indexes;
|
||||
|
||||
private ProjectIndexService() {
|
||||
this.indexes = List.of(new IndexInfo(IndexType.MD5, this::getMD5),
|
||||
new IndexInfo(IndexType.FSRL, this::getFSRL));
|
||||
}
|
||||
|
||||
public synchronized void clearProject() {
|
||||
if (project != null) {
|
||||
project.getProjectData().removeDomainFolderChangeListener(this);
|
||||
for (IndexInfo index : indexes) {
|
||||
index.indexedFiles.clear();
|
||||
}
|
||||
project = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setProject(Project newProject, TaskMonitor monitor) {
|
||||
synchronized (this) {
|
||||
if (newProject == project) {
|
||||
return;
|
||||
}
|
||||
clearProject();
|
||||
project = newProject;
|
||||
|
||||
if (project != null) {
|
||||
indexes = List.of(new IndexInfo(IndexType.MD5, this::getMD5),
|
||||
new IndexInfo(IndexType.FSRL, this::getFSRL));
|
||||
ProjectData projectData = project.getProjectData();
|
||||
projectData.removeDomainFolderChangeListener(this);
|
||||
projectData.addDomainFolderChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (newProject != null) {
|
||||
// index outside of sync lock to allow concurrent lookups
|
||||
indexProject(newProject.getProjectData(), monitor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainFileAdded(DomainFile file) {
|
||||
indexFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainFileRemoved(DomainFolder parent, String name, String fileID) {
|
||||
removeFile(fileID);
|
||||
}
|
||||
|
||||
private void indexProject(ProjectData projectData, TaskMonitor monitor) {
|
||||
int fileCount = projectData.getFileCount();
|
||||
if (fileCount < 0) {
|
||||
return;
|
||||
}
|
||||
monitor.initialize(fileCount, "Indexing Project Metadata");
|
||||
for (DomainFile df : ProjectDataUtils.descendantFiles(projectData.getRootFolder())) {
|
||||
monitor.incrementProgress();
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
indexFile(df);
|
||||
if (monitor.getProgress() % 10 == 0) {
|
||||
Swing.allowSwingToProcessEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getMD5(DomainFile file, Map<String, String> metadata) {
|
||||
return metadata.get(IndexType.MD5.metadataKey);
|
||||
}
|
||||
|
||||
private FSRL getFSRL(DomainFile file, Map<String, String> metadata) {
|
||||
String fsrlStr = metadata.get(IndexType.FSRL.metadataKey);
|
||||
try {
|
||||
return fsrlStr != null ? FSRL.fromString(fsrlStr).withMD5(null) : null;
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized List<DomainFile> lookupFiles(IndexType keyType, Object keyValue) {
|
||||
IndexInfo index = indexes.get(keyType.ordinal());
|
||||
Object fileInfo = index.indexedFiles.get(keyValue);
|
||||
List<String> fileIds;
|
||||
if (fileInfo instanceof String fileIdStr) {
|
||||
fileIds = List.of(fileIdStr);
|
||||
}
|
||||
else if (fileInfo instanceof List fileInfoList) {
|
||||
fileIds = fileInfoList;
|
||||
}
|
||||
else {
|
||||
fileIds = List.of();
|
||||
}
|
||||
return fileIds.stream()
|
||||
.map(fileId -> project.getProjectData().getFileByID(fileId))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public DomainFile findFirstByFSRL(FSRL fsrl) {
|
||||
fsrl = fsrl.withMD5(null);
|
||||
List<DomainFile> files = lookupFiles(IndexType.FSRL, fsrl);
|
||||
return !files.isEmpty() ? files.get(0) : null;
|
||||
}
|
||||
|
||||
private synchronized void indexFile(DomainFile file) {
|
||||
Map<String, String> metadata = file.getMetadata();
|
||||
for (IndexInfo index : indexes) {
|
||||
Object indexedValue = index.mappingFunc.apply(file, metadata);
|
||||
if (indexedValue != null) {
|
||||
Object fileInfo = index.indexedFiles.get(indexedValue);
|
||||
if (fileInfo == null) {
|
||||
index.indexedFiles.put(indexedValue, file.getFileID());
|
||||
}
|
||||
else if (fileInfo instanceof List<?> fileInfoList) {
|
||||
((List<String>) fileInfoList).add(file.getFileID());
|
||||
}
|
||||
else if (fileInfo instanceof String prevFileId) {
|
||||
String newFileId = file.getFileID();
|
||||
if (newFileId.equals(prevFileId)) {
|
||||
// don't need to do anything
|
||||
continue;
|
||||
}
|
||||
List<String> fileInfoList = new ArrayList<>();
|
||||
fileInfoList.add(prevFileId);
|
||||
fileInfoList.add(newFileId);
|
||||
index.indexedFiles.put(indexedValue, fileInfoList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeFile(String fileId) {
|
||||
// brute force search through all entries to remove the file
|
||||
for (IndexInfo index : indexes) {
|
||||
for (Iterator<Object> it = index.indexedFiles.values().iterator(); it
|
||||
.hasNext();) {
|
||||
Object fileInfo = it.next();
|
||||
if (fileInfo instanceof String fileIdStr && fileIdStr.equals(fileId)) {
|
||||
it.remove();
|
||||
}
|
||||
else if (fileInfo instanceof List fileInfoList) {
|
||||
fileInfoList.remove(fileId);
|
||||
if (fileInfoList.isEmpty()) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -16,19 +16,17 @@
|
||||
package ghidra.plugins.fsbrowser;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import docking.DefaultActionContext;
|
||||
import docking.widgets.tree.GTree;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.plugin.importer.ProjectIndexService;
|
||||
|
||||
/**
|
||||
* {@link FileSystemBrowserPlugin}-specific action.
|
||||
* {@link FSBComponentProvider} context for actions
|
||||
*/
|
||||
public class FSBActionContext extends DefaultActionContext {
|
||||
|
||||
@ -42,12 +40,27 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
* @param event MouseEvent that caused the update, or null
|
||||
* @param gTree {@link FileSystemBrowserPlugin} provider tree.
|
||||
*/
|
||||
public FSBActionContext(FileSystemBrowserComponentProvider provider, FSBNode[] selectedNodes,
|
||||
MouseEvent event, GTree gTree) {
|
||||
public FSBActionContext(FSBComponentProvider provider,
|
||||
List<FSBNode> selectedNodes, MouseEvent event, GTree gTree) {
|
||||
super(provider, selectedNodes, gTree);
|
||||
this.gTree = gTree;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FSBComponentProvider getComponentProvider() {
|
||||
return (FSBComponentProvider) super.getComponentProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FSBNode> getContextObject() {
|
||||
return getSelectedNodes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTree getSourceComponent() {
|
||||
return gTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the GTree is not busy
|
||||
* @return boolean true if GTree is not busy
|
||||
@ -79,9 +92,7 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
* @return boolean true if there are selected nodes in the browser tree
|
||||
*/
|
||||
public boolean hasSelectedNodes() {
|
||||
FSBNode[] selectedNodes = (FSBNode[]) getContextObject();
|
||||
|
||||
return selectedNodes.length > 0;
|
||||
return !getSelectedNodes().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,7 +101,7 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
* @return list of currently selected tree nodes
|
||||
*/
|
||||
public List<FSBNode> getSelectedNodes() {
|
||||
return List.of((FSBNode[]) getContextObject());
|
||||
return (List<FSBNode>) super.getContextObject();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,27 +115,19 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
* selected
|
||||
*/
|
||||
public FSRL getFSRL(boolean dirsOk) {
|
||||
FSBNode[] selectedNodes = (FSBNode[]) getContextObject();
|
||||
if (selectedNodes.length != 1) {
|
||||
List<FSBNode> selectedNodes = getSelectedNodes();
|
||||
if (selectedNodes.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
FSBNode node = selectedNodes[0];
|
||||
FSBNode node = selectedNodes.get(0);
|
||||
FSRL fsrl = node.getFSRL();
|
||||
if (!dirsOk && node instanceof FSBRootNode && fsrlHasContainer(fsrl.getFS())) {
|
||||
if (!dirsOk && node instanceof FSBRootNode fsRootNode &&
|
||||
fsRootNode.getContainer() != null) {
|
||||
// 'convert' a file system root node back into its container file
|
||||
return fsrl.getFS().getContainer();
|
||||
return fsRootNode.getContainer();
|
||||
}
|
||||
|
||||
boolean isDir = (node instanceof FSBDirNode) || (node instanceof FSBRootNode);
|
||||
if (isDir && !dirsOk) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fsrl;
|
||||
}
|
||||
|
||||
private boolean fsrlHasContainer(FSRLRoot fsFSRL) {
|
||||
return fsFSRL.hasContainer() && !fsFSRL.getProtocol().equals(LocalFileSystem.FSTYPE);
|
||||
return node.isLeaf() || dirsOk ? fsrl : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,10 +135,9 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
* @return boolean true if the currently selected items are all directory items
|
||||
*/
|
||||
public boolean isSelectedAllDirs() {
|
||||
FSBNode[] selectedNodes = (FSBNode[]) getContextObject();
|
||||
List<FSBNode> selectedNodes = getSelectedNodes();
|
||||
for (FSBNode node : selectedNodes) {
|
||||
boolean isDir = (node instanceof FSBDirNode) || (node instanceof FSBRootNode);
|
||||
if (!isDir) {
|
||||
if (node.isLeaf()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -148,25 +150,8 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
* @return the currently selected tree node, or null if no nodes or more than 1 node is selected
|
||||
*/
|
||||
public FSBNode getSelectedNode() {
|
||||
FSBNode[] selectedNodes = (FSBNode[]) getContextObject();
|
||||
return selectedNodes.length == 1 ? selectedNodes[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FSBRootNode that contains the currently selected tree node.
|
||||
*
|
||||
* @return FSBRootNode that contains the currently selected tree node, or null nothing
|
||||
* selected
|
||||
*/
|
||||
public FSBRootNode getRootOfSelectedNode() {
|
||||
return getRootOfNode(getSelectedNode());
|
||||
}
|
||||
|
||||
private FSBRootNode getRootOfNode(GTreeNode tmp) {
|
||||
while (tmp != null && !(tmp instanceof FSBRootNode)) {
|
||||
tmp = tmp.getParent();
|
||||
}
|
||||
return (tmp instanceof FSBRootNode) ? (FSBRootNode) tmp : null;
|
||||
List<FSBNode> selectedNodes = getSelectedNodes();
|
||||
return selectedNodes.size() == 1 ? selectedNodes.get(0) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,11 +160,10 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
* @return returns the number of selected nodes in the tree.
|
||||
*/
|
||||
public int getSelectedCount() {
|
||||
FSBNode[] selectedNodes = (FSBNode[]) getContextObject();
|
||||
return selectedNodes.length;
|
||||
return getSelectedNodes().size();
|
||||
}
|
||||
|
||||
private List<FSRL> getFSRLsFromNodes(FSBNode[] nodes, boolean dirsOk) {
|
||||
private List<FSRL> getFSRLsFromNodes(List<FSBNode> nodes, boolean dirsOk) {
|
||||
List<FSRL> fsrls = new ArrayList<>();
|
||||
for (FSBNode node : nodes) {
|
||||
FSRL fsrl = node.getFSRL();
|
||||
@ -206,7 +190,7 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
* @return list of FSRLs of the currently selected items, maybe empty but never null
|
||||
*/
|
||||
public List<FSRL> getFSRLs(boolean dirsOk) {
|
||||
FSBNode[] selectedNodes = (FSBNode[]) getContextObject();
|
||||
List<FSBNode> selectedNodes = getSelectedNodes();
|
||||
return getFSRLsFromNodes(selectedNodes, dirsOk);
|
||||
}
|
||||
|
||||
@ -228,41 +212,6 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
return getFSRL(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the tree-node hierarchy of the currently selected item into a string path using
|
||||
* "/" separators.
|
||||
*
|
||||
* @return string path of the currently selected tree item
|
||||
*/
|
||||
public String getFormattedTreePath() {
|
||||
FSBNode[] selectedNodes = (FSBNode[]) getContextObject();
|
||||
if (selectedNodes.length != 1) {
|
||||
return null;
|
||||
}
|
||||
TreePath treePath = selectedNodes[0].getTreePath();
|
||||
StringBuilder path = new StringBuilder();
|
||||
for (Object pathElement : treePath.getPath()) {
|
||||
if (pathElement instanceof FSBNode) {
|
||||
FSBNode node = (FSBNode) pathElement;
|
||||
FSRL fsrl = node.getFSRL();
|
||||
if (path.length() != 0) {
|
||||
path.append("/");
|
||||
}
|
||||
String s;
|
||||
if (fsrl instanceof FSRLRoot) {
|
||||
s = fsrl.getFS().hasContainer() ? fsrl.getFS().getContainer().getName()
|
||||
: "/";
|
||||
}
|
||||
else {
|
||||
s = fsrl.getName();
|
||||
}
|
||||
path.append(s);
|
||||
}
|
||||
}
|
||||
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FSRL of the currently selected item, if it is a 'loadable' item.
|
||||
*
|
||||
@ -271,82 +220,18 @@ public class FSBActionContext extends DefaultActionContext {
|
||||
*/
|
||||
public FSRL getLoadableFSRL() {
|
||||
FSBNode node = getSelectedNode();
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
FSRL fsrl = node.getFSRL();
|
||||
if ((node instanceof FSBDirNode) || (node instanceof FSBRootNode)) {
|
||||
FSBRootNode rootNode = getRootOfSelectedNode();
|
||||
GFileSystem fs = rootNode.getFSRef().getFilesystem();
|
||||
if (fs instanceof GFileSystemProgramProvider) {
|
||||
GFile gfile;
|
||||
try {
|
||||
gfile = fs.lookup(node.getFSRL().getPath());
|
||||
if (gfile != null &&
|
||||
((GFileSystemProgramProvider) fs).canProvideProgram(gfile)) {
|
||||
return fsrl;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore error and fall thru to normal file handling
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node instanceof FSBRootNode && fsrl.getFS().hasContainer()) {
|
||||
// 'convert' a file system root node back into its container file
|
||||
return fsrl.getFS().getContainer();
|
||||
}
|
||||
return (node instanceof FSBFileNode) ? fsrl : null;
|
||||
return node != null ? node.getLoadableFSRL() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of FSRLs of the currently selected loadable items.
|
||||
*
|
||||
* @return list of FSRLs of currently selected loadable items, maybe empty but never null
|
||||
*/
|
||||
public List<FSRL> getLoadableFSRLs() {
|
||||
FSBNode[] selectedNodes = (FSBNode[]) getContextObject();
|
||||
|
||||
List<FSRL> fsrls = new ArrayList<>();
|
||||
for (FSBNode node : selectedNodes) {
|
||||
FSRL fsrl = node.getFSRL();
|
||||
|
||||
FSRL validated = vaildateFsrl(fsrl, node);
|
||||
if (validated != null) {
|
||||
fsrls.add(validated);
|
||||
continue;
|
||||
}
|
||||
else if (node instanceof FSBRootNode && fsrl.getFS().hasContainer()) {
|
||||
// 'convert' a file system root node back into its container file
|
||||
fsrls.add(fsrl.getFS().getContainer());
|
||||
}
|
||||
else if (node instanceof FSBFileNode) {
|
||||
fsrls.add(fsrl);
|
||||
public boolean hasSelectedLinkedNodes() {
|
||||
ProjectIndexService projectIndex = getComponentProvider().getProjectIndex();
|
||||
for (FSBNode node : getSelectedNodes()) {
|
||||
DomainFile df = projectIndex.findFirstByFSRL(node.getFSRL());
|
||||
if (df != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return fsrls;
|
||||
}
|
||||
|
||||
private FSRL vaildateFsrl(FSRL fsrl, FSBNode node) {
|
||||
if ((node instanceof FSBDirNode) || (node instanceof FSBRootNode)) {
|
||||
FSBRootNode rootNode = getRootOfNode(node);
|
||||
GFileSystem fs = rootNode.getFSRef().getFilesystem();
|
||||
if (fs instanceof GFileSystemProgramProvider) {
|
||||
GFile gfile;
|
||||
try {
|
||||
gfile = fs.lookup(node.getFSRL().getPath());
|
||||
if (gfile != null &&
|
||||
((GFileSystemProgramProvider) fs).canProvideProgram(gfile)) {
|
||||
return fsrl;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore error and return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,576 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser;
|
||||
|
||||
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.actions.PopupActionProvider;
|
||||
import docking.event.mouse.GMouseListenerAdapter;
|
||||
import docking.widgets.tree.GTree;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.support.GTreeRenderer;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.plugin.importer.ImporterUtilities;
|
||||
import ghidra.plugin.importer.ProjectIndexService;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.CryptoException;
|
||||
import ghidra.util.task.MonitoredRunnable;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Plugin component provider for the {@link FileSystemBrowserPlugin}.
|
||||
* <p>
|
||||
* An instance of this class is created for each file system browser window (w/tree).
|
||||
* <p>
|
||||
* See the {@link FSBFileHandler} interface for how to add actions to this component.
|
||||
*/
|
||||
public class FSBComponentProvider extends ComponentProviderAdapter
|
||||
implements FileSystemEventListener, PopupActionProvider {
|
||||
private static final String TITLE = "Filesystem Viewer";
|
||||
|
||||
private FSBIcons fsbIcons = FSBIcons.getInstance();
|
||||
private FileSystemService fsService = FileSystemService.getInstance();
|
||||
private ProjectIndexService projectIndex = ProjectIndexService.getInstance();
|
||||
|
||||
private FileSystemBrowserPlugin plugin;
|
||||
private GTree gTree;
|
||||
private FSBRootNode rootNode;
|
||||
private List<FSBFileHandler> fileHandlers = List.of();
|
||||
private ProgramManager pm;
|
||||
|
||||
/**
|
||||
* Creates a new {@link FSBComponentProvider} instance, taking
|
||||
* ownership of the passed-in {@link FileSystemRef fsRef}.
|
||||
*
|
||||
* @param plugin parent plugin
|
||||
* @param fsRef {@link FileSystemRef} to a {@link GFileSystem}.
|
||||
*/
|
||||
public FSBComponentProvider(FileSystemBrowserPlugin plugin, FileSystemRef fsRef) {
|
||||
super(plugin.getTool(), fsRef.getFilesystem().getName(), plugin.getName());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.rootNode = new FSBRootNode(fsRef);
|
||||
this.pm = plugin.getTool().getService(ProgramManager.class);
|
||||
|
||||
setTransient();
|
||||
setIcon(FSBIcons.PHOTO);
|
||||
|
||||
initTree();
|
||||
fsRef.getFilesystem().getRefManager().addListener(this);
|
||||
initFileHandlers();
|
||||
|
||||
setHelpLocation(
|
||||
new HelpLocation("FileSystemBrowserPlugin", "FileSystemBrowserIntroduction"));
|
||||
|
||||
}
|
||||
|
||||
void initFileHandlers() {
|
||||
FSBFileHandlerContext context =
|
||||
new FSBFileHandlerContext(plugin, this, fsService, projectIndex);
|
||||
fileHandlers = ClassSearcher.getInstances(FSBFileHandler.class);
|
||||
for (FSBFileHandler fileHandler : fileHandlers) {
|
||||
fileHandler.init(context);
|
||||
}
|
||||
fileHandlers.add(new DefaultFileHandler());
|
||||
plugin.getTool().addPopupActionProvider(this); // delegate to fileHandler's getPopupProviderActions()
|
||||
}
|
||||
|
||||
void initTree() {
|
||||
gTree = new GTree(rootNode);
|
||||
gTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
|
||||
gTree.getSelectionModel().addTreeSelectionListener(e -> {
|
||||
tool.contextChanged(FSBComponentProvider.this);
|
||||
TreePath[] paths = gTree.getSelectionPaths();
|
||||
if (paths.length == 1) {
|
||||
GTreeNode clickedNode = (GTreeNode) paths[0].getLastPathComponent();
|
||||
handleSingleClick(clickedNode);
|
||||
}
|
||||
});
|
||||
gTree.addMouseListener(new GMouseListenerAdapter() {
|
||||
@Override
|
||||
public void doubleClickTriggered(MouseEvent e) {
|
||||
if (handleDoubleClick(gTree.getNodeForLocation(e.getX(), e.getY()))) {
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
super.mouseClicked(e);
|
||||
if (!e.isConsumed()) {
|
||||
handleSingleClick(gTree.getNodeForLocation(e.getX(), e.getY()));
|
||||
}
|
||||
}
|
||||
});
|
||||
gTree.setCellRenderer(new GTreeRenderer() {
|
||||
@Override
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object value,
|
||||
boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||
|
||||
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row,
|
||||
hasFocus);
|
||||
|
||||
if (value instanceof FSBRootNode fsRootNode) {
|
||||
renderFS(fsRootNode, selected);
|
||||
}
|
||||
else if (value instanceof FSBDirNode) {
|
||||
// do nothing special, but exclude FSBFileNode
|
||||
}
|
||||
else if (value instanceof FSBFileNode fileNode) {
|
||||
renderFile(fileNode, selected);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void renderFS(FSBRootNode node, boolean selected) {
|
||||
FileSystemRef nodeFSRef = node.getFSRef();
|
||||
if (nodeFSRef == null || nodeFSRef.getFilesystem() == null) {
|
||||
return;
|
||||
}
|
||||
Icon image = fsbIcons.getIcon(node.getContainerName(),
|
||||
List.of(FSBIcons.FILESYSTEM_OVERLAY_ICON));
|
||||
setIcon(image);
|
||||
}
|
||||
|
||||
private void renderFile(FSBFileNode node, boolean selected) {
|
||||
FSRL fsrl = node.getFSRL();
|
||||
String filename = fsrl.getName();
|
||||
List<Icon> overlays = new ArrayList<>(4);
|
||||
|
||||
DomainFile df = projectIndex.findFirstByFSRL(fsrl);
|
||||
if (df != null) {
|
||||
overlays.add(FSBIcons.IMPORTED_OVERLAY_ICON);
|
||||
|
||||
if (plugin.isOpen(df)) {
|
||||
// TODO: change this to a OVERLAY_OPEN option when fetching icon
|
||||
setForeground(selected ? Palette.CYAN : Palette.MAGENTA);
|
||||
}
|
||||
}
|
||||
if (fsService.isFilesystemMountedAt(fsrl)) {
|
||||
overlays.add(FSBIcons.FILESYSTEM_OVERLAY_ICON);
|
||||
}
|
||||
if (node.isSymlink()) {
|
||||
overlays.add(FSBIcons.LINK_OVERLAY_ICON);
|
||||
}
|
||||
if (node.hasMissingPassword()) {
|
||||
overlays.add(FSBIcons.MISSING_PASSWORD_OVERLAY_ICON);
|
||||
}
|
||||
|
||||
Icon icon = fsbIcons.getIcon(filename, overlays);
|
||||
setIcon(icon);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public FileSystemBrowserPlugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return this provider's GTree.
|
||||
*/
|
||||
public GTree getGTree() {
|
||||
return gTree;
|
||||
}
|
||||
|
||||
FSRL getFSRL() {
|
||||
return rootNode != null ? rootNode.getFSRL() : null;
|
||||
}
|
||||
|
||||
public ProjectIndexService getProjectIndex() {
|
||||
return projectIndex;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
plugin.getTool().removePopupActionProvider(this);
|
||||
|
||||
if (rootNode != null && rootNode.getFSRef() != null && !rootNode.getFSRef().isClosed()) {
|
||||
rootNode.getFSRef().getFilesystem().getRefManager().removeListener(this);
|
||||
}
|
||||
fileHandlers.clear();
|
||||
if (gTree != null) {
|
||||
gTree.setCellRenderer(null); // avoid npe's in the cellrenderer when disposed
|
||||
gTree.dispose(); // calls dispose() on tree's rootNode, which will release the fsRefs
|
||||
}
|
||||
removeFromTool();
|
||||
rootNode = null;
|
||||
plugin = null;
|
||||
gTree = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden() {
|
||||
// if the component is 'closed', nuke ourselves
|
||||
if (plugin != null) {
|
||||
plugin.removeFileSystemBrowserComponent(this);
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
|
||||
List<DockingActionIf> results = new ArrayList<>();
|
||||
for (FSBFileHandler fileHandler : fileHandlers) {
|
||||
List<DockingAction> actions = fileHandler.getPopupProviderActions();
|
||||
results.addAll(actions);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public void afterAddedToTool() {
|
||||
fileHandlers.stream()
|
||||
.flatMap(fh -> fh.createActions().stream())
|
||||
.forEach(this::addLocalAction);
|
||||
|
||||
setProject(tool.getProject());
|
||||
}
|
||||
|
||||
public void setProject(Project project) {
|
||||
gTree.runTask(monitor -> {
|
||||
projectIndex.setProject(project, monitor);
|
||||
Swing.runLater(() -> gTree.repaint()); // icons might need repainting after new info is available
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesystemClose(GFileSystem fs) {
|
||||
Msg.info(this, "File system " + fs.getFSRL() + " was closed! Closing browser window");
|
||||
Swing.runIfSwingOrRunLater(() -> componentHidden());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesystemRefChange(GFileSystem fs, FileSystemRefManager refManager) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
public void runTask(MonitoredRunnable runnableTask) {
|
||||
gTree.runTask(runnableTask);
|
||||
}
|
||||
|
||||
/*****************************************/
|
||||
|
||||
private boolean handleSingleClick(GTreeNode clickedNode) {
|
||||
if (clickedNode instanceof FSBFileNode fileNode) {
|
||||
for (FSBFileHandler handler : fileHandlers) {
|
||||
if (handler.fileFocused(fileNode)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleDoubleClick(GTreeNode clickedNode) {
|
||||
if (clickedNode instanceof FSBFileNode fileNode) {
|
||||
for (FSBFileHandler handler : fileHandlers) {
|
||||
if (handler.fileDefaultAction(fileNode)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*****************************************/
|
||||
|
||||
@Override
|
||||
public FSBActionContext getActionContext(MouseEvent event) {
|
||||
return new FSBActionContext(this, getSelectedNodes(event), event, gTree);
|
||||
}
|
||||
|
||||
private List<FSBNode> getSelectedNodes(MouseEvent event) {
|
||||
TreePath[] selectionPaths = gTree.getSelectionPaths();
|
||||
List<FSBNode> list = new ArrayList<>(selectionPaths.length);
|
||||
for (TreePath selectionPath : selectionPaths) {
|
||||
Object lastPathComponent = selectionPath.getLastPathComponent();
|
||||
if (lastPathComponent instanceof FSBNode fsbNode) {
|
||||
list.add(fsbNode);
|
||||
}
|
||||
}
|
||||
if (list.isEmpty() && event != null) {
|
||||
Object source = event.getSource();
|
||||
if (source instanceof JTree sourceTree && gTree.isMyJTree(sourceTree)) {
|
||||
int x = event.getX();
|
||||
int y = event.getY();
|
||||
GTreeNode nodeAtEventLocation = gTree.getNodeForLocation(x, y);
|
||||
if (nodeAtEventLocation != null && nodeAtEventLocation instanceof FSBNode fsbNode) {
|
||||
list.add(fsbNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return gTree;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TITLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowPosition getDefaultWindowPosition() {
|
||||
return WindowPosition.WINDOW;
|
||||
}
|
||||
|
||||
public boolean ensureFileAccessable(FSRL fsrl, FSBNode node, TaskMonitor monitor) {
|
||||
|
||||
FSBFileNode fileNode = (node instanceof FSBFileNode) ? (FSBFileNode) node : null;
|
||||
|
||||
monitor.initialize(0);
|
||||
monitor.setMessage("Testing file access");
|
||||
boolean wasMissingPasword = (fileNode != null) ? fileNode.hasMissingPassword() : false;
|
||||
try (ByteProvider bp = fsService.getByteProvider(fsrl, false, monitor)) {
|
||||
// if we can get here and it used to have a missing password, update the node's status
|
||||
if (wasMissingPasword) {
|
||||
doRefreshInfo(List.of(fileNode), monitor);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (CryptoException e) {
|
||||
Msg.showWarn(this, gTree, "Crypto / Password Error",
|
||||
"Unable to access the specified file.\n" +
|
||||
"This could be caused by not entering the correct password or because of missing crypto information.\n\n" +
|
||||
e.getMessage());
|
||||
return false;
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, gTree, "File IO Error",
|
||||
"Unable to access the specified file.\n\n" + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean openFileSystem(FSBNode node, boolean nested) {
|
||||
if (!(node instanceof FSBFileNode fileNode) || fileNode.getFSRL() == null) {
|
||||
return false;
|
||||
}
|
||||
FSRL fsrl = fileNode.getFSRL();
|
||||
gTree.runTask(monitor -> {
|
||||
if (!ensureFileAccessable(fsrl, fileNode, monitor)) {
|
||||
return;
|
||||
}
|
||||
if (!doOpenFileSystem(fsrl, fileNode, nested, monitor)) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* run on gTree task thread
|
||||
*/
|
||||
boolean doOpenFileSystem(FSRL containerFSRL, FSBFileNode node, boolean nested,
|
||||
TaskMonitor monitor) {
|
||||
try {
|
||||
monitor.setMessage("Probing " + containerFSRL.getName() + " for filesystems");
|
||||
FileSystemRef ref = fsService.probeFileForFilesystem(containerFSRL, monitor,
|
||||
FileSystemProbeConflictResolver.GUI_PICKER);
|
||||
if (ref == null) {
|
||||
Msg.showWarn(this, plugin.getTool().getActiveWindow(), "Open Filesystem",
|
||||
"No filesystem detected in " + containerFSRL.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
Swing.runLater(() -> {
|
||||
if (nested) {
|
||||
FSBFileNode modelFileNode =
|
||||
(FSBFileNode) gTree.getModelNodeForPath(node.getTreePath());
|
||||
|
||||
FSBRootNode nestedRootNode = new FSBRootNode(ref, modelFileNode);
|
||||
|
||||
int indexInParent = modelFileNode.getIndexInParent();
|
||||
GTreeNode parent = modelFileNode.getParent();
|
||||
parent.removeNode(modelFileNode);
|
||||
parent.addNode(indexInParent, nestedRootNode);
|
||||
gTree.expandPath(nestedRootNode);
|
||||
try {
|
||||
nestedRootNode.init(monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
Msg.warn(this, "Failed to populate FSB root node with children");
|
||||
}
|
||||
contextChanged();
|
||||
}
|
||||
else {
|
||||
plugin.createNewFileSystemBrowser(ref, true);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (IOException | CancelledException e) {
|
||||
FSUtilities.displayException(this, plugin.getTool().getActiveWindow(),
|
||||
"Open Filesystem", "Error opening filesystem for " + containerFSRL.getName(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void doRefreshInfo(List<FSBNode> nodes, TaskMonitor monitor) {
|
||||
try {
|
||||
for (FSBNode node : nodes) {
|
||||
node.refreshNode(monitor);
|
||||
}
|
||||
gTree.refilterLater(); // force the changed modelNodes to be recloned and displayed (if filter active)
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// stop
|
||||
}
|
||||
Swing.runLater(() -> gTree.repaint());
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
||||
private class DefaultFileHandler implements FSBFileHandler {
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fileFocused(FSBFileNode fileNode) {
|
||||
|
||||
FSRL fsrl = fileNode.getFSRL();
|
||||
if (fsrl != null) {
|
||||
if (pm != null) {
|
||||
// if this tool is a codebrowser-ish tool, switch focus to the matching focused file
|
||||
DomainFile df = projectIndex.findFirstByFSRL(fsrl);
|
||||
DomainObject domObj;
|
||||
if (df != null && (domObj = df.getOpenedDomainObject(this)) != null) {
|
||||
domObj.release(this);
|
||||
if (domObj instanceof Program program) {
|
||||
runTask(monitor -> pm.setCurrentProgram(program));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileNode.hasMissingPassword()) {
|
||||
runTask(monitor -> doRefreshInfo(List.of(fileNode), monitor));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fileDefaultAction(FSBFileNode fileNode) {
|
||||
FSRL fsrl = fileNode.getFSRL();
|
||||
if (fsrl == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fileNode.isSymlink()) {
|
||||
gotoSymlinkDest(fileNode);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!fileNode.isLeaf()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
runTask(monitor -> {
|
||||
if (!ensureFileAccessable(fsrl, fileNode, monitor)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FSRL fullFsrl = fsService.getFullyQualifiedFSRL(fsrl, monitor);
|
||||
if (fsService.isFileFilesystemContainer(fullFsrl, monitor)) {
|
||||
doOpenFileSystem(fullFsrl, fileNode, true, monitor);
|
||||
return;
|
||||
}
|
||||
|
||||
DomainFile df = projectIndex.findFirstByFSRL(fsrl);
|
||||
OpenWithTarget openWithTarget = OpenWithTarget.getDefault(plugin.getTool());
|
||||
if (df != null && openWithTarget != null) {
|
||||
Swing.runLater(() -> openWithTarget.open(List.of(df)));
|
||||
return;
|
||||
}
|
||||
ImporterUtilities.showImportSingleFileDialog(fullFsrl, null,
|
||||
fileNode.getFormattedTreePath(), plugin.getTool(), openWithTarget.getPm(),
|
||||
monitor);
|
||||
}
|
||||
catch (IOException | CancelledException e) {
|
||||
// fall thru
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void gotoSymlinkDest(FSBFileNode fileNode) {
|
||||
GFile file = fileNode.file;
|
||||
try {
|
||||
FSBRootNode fsRootNode = fileNode.getFSBRootNode();
|
||||
GFile destFile = file.getFilesystem().resolveSymlinks(file);
|
||||
if (destFile != null && fsRootNode != null) {
|
||||
gTree.runTask(monitor -> {
|
||||
FSBNode destNode = fsRootNode.getGFileFSBNode(destFile, monitor);
|
||||
if (destNode != null) {
|
||||
Swing.runLater(() -> gTree.setSelectedNodes(destNode));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// fall thru
|
||||
}
|
||||
FileAttributes fattrs = file.getFilesystem().getFileAttributes(file, null);
|
||||
String symlinkDest = fattrs.get(SYMLINK_DEST_ATTR, String.class, null);
|
||||
plugin.getTool()
|
||||
.setStatusInfo("Unable to resolve symlink [%s]".formatted(symlinkDest), true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -19,6 +19,7 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.formats.gfilesystem.GFile;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@ -44,16 +45,8 @@ public class FSBDirNode extends FSBFileNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFileAttributes(TaskMonitor monitor) {
|
||||
for (GTreeNode node : getChildren()) {
|
||||
if (node instanceof FSBFileNode) {
|
||||
((FSBFileNode) node).updateFileAttributes(monitor);
|
||||
}
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
super.updateFileAttributes(monitor);
|
||||
public void refreshNode(TaskMonitor monitor) throws CancelledException {
|
||||
refreshChildren(monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -61,4 +54,9 @@ public class FSBDirNode extends FSBFileNode {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FSRL getLoadableFSRL() {
|
||||
return getFSBRootNode().getProgramProviderFSRL(getFSRL());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* Extension point, used by the {@link FSBComponentProvider} to create actions that appear
|
||||
* in the fsb tree, and to delegate focus and default actions.
|
||||
*/
|
||||
public interface FSBFileHandler extends ExtensionPoint {
|
||||
/**
|
||||
* Called once after creation of each instance to provide useful info
|
||||
*
|
||||
* @param context references to useful objects and services
|
||||
*/
|
||||
void init(FSBFileHandlerContext context);
|
||||
|
||||
/**
|
||||
* Returns a list of {@link DockingAction}s that should be
|
||||
* {@link PluginTool#addLocalAction(docking.ComponentProvider, docking.action.DockingActionIf) added}
|
||||
* to the {@link FSBComponentProvider} tree as local actions.
|
||||
*
|
||||
* @return list of {@link DockingAction}s
|
||||
*/
|
||||
default List<DockingAction> createActions() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a file node is focused in the {@link FSBComponentProvider} tree.
|
||||
*
|
||||
* @param fileNode {@link FSBFileNode} that was focused
|
||||
* @return boolean true if action was taken
|
||||
*/
|
||||
default boolean fileFocused(FSBFileNode fileNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a file node is the target of a 'default action' initiated by the user, such
|
||||
* as a double click, etc.
|
||||
*
|
||||
* @param fileNode {@link FSBFileNode} that was acted upon
|
||||
* @return boolean true if action was taken, false if no action was taken
|
||||
*/
|
||||
default boolean fileDefaultAction(FSBFileNode fileNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of {@link DockingAction}s that should be added to a popup menu. Called
|
||||
* each time a fsb browser tree popup menu is created.
|
||||
* <p>
|
||||
* Only use this method to provide actions when the actions need to be created freshly
|
||||
* for each popup event. Normal long-lived actions should be published by the
|
||||
* {@link #createActions()} method.
|
||||
*
|
||||
* @return list of {@link DockingAction}s
|
||||
*/
|
||||
default List<DockingAction> getPopupProviderActions() {
|
||||
return List.of();
|
||||
}
|
||||
}
|
@ -14,26 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.plugins.fsbrowser;
|
||||
import docking.action.DockingAction;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
|
||||
import ghidra.formats.gfilesystem.FileSystemService;
|
||||
import ghidra.plugin.importer.ProjectIndexService;
|
||||
|
||||
/**
|
||||
* {@link FileSystemBrowserPlugin}-specific action.
|
||||
* Context given to a {@link FSBFileHandler} instance when being initialized.
|
||||
*
|
||||
* @param plugin the FSB plugin
|
||||
* @param fsbComponent the FSB component
|
||||
* @param fsService the fs service
|
||||
* @param projectIndex the project index
|
||||
*/
|
||||
public abstract class FSBAction extends DockingAction {
|
||||
|
||||
private final String menuText;
|
||||
|
||||
public FSBAction(String menuText, Plugin plugin) {
|
||||
this(menuText, menuText, plugin);
|
||||
}
|
||||
|
||||
public FSBAction(String name, String menuText, Plugin plugin) {
|
||||
super("FSB " + name, plugin.getName());
|
||||
this.menuText = menuText;
|
||||
}
|
||||
|
||||
public String getMenuText() {
|
||||
return menuText;
|
||||
}
|
||||
}
|
||||
public record FSBFileHandlerContext(FileSystemBrowserPlugin plugin,
|
||||
FSBComponentProvider fsbComponent, FileSystemService fsService,
|
||||
ProjectIndexService projectIndex) {}
|
@ -17,15 +17,16 @@ package ghidra.plugins.fsbrowser;
|
||||
|
||||
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.formats.gfilesystem.GFile;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.formats.gfilesystem.fileinfo.FileAttributeType;
|
||||
import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* GTreeNode that represents a file on a filesystem.
|
||||
@ -35,31 +36,76 @@ public class FSBFileNode extends FSBNode {
|
||||
protected GFile file;
|
||||
protected boolean isEncrypted;
|
||||
protected boolean hasPassword;
|
||||
protected String symlinkDest;
|
||||
protected long lastModified;
|
||||
|
||||
FSBFileNode(GFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(TaskMonitor monitor) {
|
||||
updateFileProps(monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FSRL getFSRL() {
|
||||
return file.getFSRL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile getGFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
if (symlinkDest != null) {
|
||||
// unicode \u2192 is a -> right arrow
|
||||
return "%s \u2192 %s".formatted(getName(), symlinkDest);
|
||||
}
|
||||
|
||||
long flen = file.getLength();
|
||||
String flenStr = flen >= 0 ? " - " + FileUtilities.formatLength(flen) : "";
|
||||
String lastModStr =
|
||||
lastModified > 0 ? " - " + FSUtilities.formatFSTimestamp(new Date(lastModified)) : "";
|
||||
String pwInfo = isEncrypted && !hasPassword ? " (missing password)" : "";
|
||||
|
||||
return getName() + flenStr + lastModStr + pwInfo;
|
||||
}
|
||||
|
||||
public boolean isSymlink() {
|
||||
return symlinkDest != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return file.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateFileAttributes(TaskMonitor monitor) {
|
||||
private void updateFileProps(TaskMonitor monitor) {
|
||||
FileAttributes fattrs = file.getFilesystem().getFileAttributes(file, monitor);
|
||||
isEncrypted = fattrs.get(IS_ENCRYPTED_ATTR, Boolean.class, false);
|
||||
hasPassword = fattrs.get(HAS_GOOD_PASSWORD_ATTR, Boolean.class, false);
|
||||
symlinkDest = fattrs.get(SYMLINK_DEST_ATTR, String.class, null);
|
||||
Date lastModDate = fattrs.get(MODIFIED_DATE_ATTR, Date.class, null);
|
||||
lastModified = lastModDate != null ? lastModDate.getTime() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshNode(TaskMonitor monitor) throws CancelledException {
|
||||
boolean wasMissingPassword = hasMissingPassword();
|
||||
|
||||
updateFileProps(monitor);
|
||||
|
||||
if (wasMissingPassword != hasMissingPassword()) {
|
||||
getFSBRootNode().setCryptoStatusUpdated(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -93,19 +139,9 @@ public class FSBFileNode extends FSBNode {
|
||||
return isEncrypted && !hasPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this node's password status has changed, calling for a complete refresh
|
||||
* of the status of all files in the file system.
|
||||
*
|
||||
* @param monitor {@link TaskMonitor}
|
||||
* @return boolean true if this nodes password status has changed
|
||||
*/
|
||||
public boolean needsFileAttributesUpdate(TaskMonitor monitor) {
|
||||
if (hasMissingPassword()) {
|
||||
updateFileAttributes(monitor);
|
||||
return hasPassword; // if true then the attribute has changed and everything should be refreshed
|
||||
}
|
||||
return false;
|
||||
@Override
|
||||
public FSRL getLoadableFSRL() {
|
||||
return getFSRL();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,155 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import generic.theme.*;
|
||||
import ghidra.formats.gfilesystem.FSUtilities;
|
||||
import resources.MultiIcon;
|
||||
|
||||
/**
|
||||
* Static list of Icons for the file system browser plugin and its child windows.
|
||||
* <p>
|
||||
* The {@link #getInstance() singleton instance} provides {@link Icon}s that represent the type
|
||||
* and status of a file, based on a filename mapping and caller specified status overlays.
|
||||
* <p>
|
||||
* Thread safe
|
||||
*/
|
||||
public class FSBIcons {
|
||||
//@formatter:off
|
||||
public static final Icon COPY = new GIcon("icon.plugin.fsbrowser.copy");
|
||||
public static final Icon CUT = new GIcon("icon.plugin.fsbrowser.cut");
|
||||
public static final Icon DELETE = new GIcon("icon.plugin.fsbrowser.delete");
|
||||
public static final Icon FONT = new GIcon("icon.plugin.fsbrowser.font");
|
||||
public static final Icon LOCKED = new GIcon("icon.plugin.fsbrowser.locked");
|
||||
public static final Icon NEW = new GIcon("icon.plugin.fsbrowser.new");
|
||||
public static final Icon PASTE = new GIcon("icon.plugin.fsbrowser.paste");
|
||||
public static final Icon REDO = new GIcon("icon.plugin.fsbrowser.redo");
|
||||
public static final Icon RENAME = new GIcon("icon.plugin.fsbrowser.rename");
|
||||
public static final Icon REFRESH = new GIcon("icon.plugin.fsbrowser.refresh");
|
||||
public static final Icon SAVE = new GIcon("icon.plugin.fsbrowser.save");
|
||||
public static final Icon SAVE_AS = new GIcon("icon.plugin.fsbrowser.save.as");
|
||||
public static final Icon UNDO = new GIcon("icon.plugin.fsbrowser.undo");
|
||||
public static final Icon UNLOCKED = new GIcon("icon.plugin.fsbrowser.unlocked");
|
||||
public static final Icon CLOSE = new GIcon("icon.plugin.fsbrowser.close");
|
||||
public static final Icon COLLAPSE_ALL = new GIcon("icon.plugin.fsbrowser.collapse.all");
|
||||
public static final Icon COMPRESS = new GIcon("icon.plugin.fsbrowser.compress");
|
||||
public static final Icon CREATE_FIRMWARE = new GIcon("icon.plugin.fsbrowser.create.firmware");
|
||||
public static final Icon EXPAND_ALL = new GIcon("icon.plugin.fsbrowser.expand.all");
|
||||
public static final Icon EXTRACT = new GIcon("icon.plugin.fsbrowser.extract");
|
||||
public static final Icon INFO = new GIcon("icon.plugin.fsbrowser.info");
|
||||
public static final Icon OPEN = new GIcon("icon.plugin.fsbrowser.open");
|
||||
public static final Icon OPEN_AS_BINARY = new GIcon("icon.plugin.fsbrowser.open.as.binary");
|
||||
public static final Icon OPEN_IN_LISTING = new GIcon("icon.plugin.fsbrowser.open.in.listing");
|
||||
public static final Icon OPEN_FILE_SYSTEM = new GIcon("icon.plugin.fsbrowser.open.file.system");
|
||||
public static final Icon PHOTO = new GIcon("icon.plugin.fsbrowser.photo");
|
||||
public static final Icon VIEW_AS_IMAGE = new GIcon("icon.plugin.fsbrowser.view.as.image");
|
||||
public static final Icon VIEW_AS_TEXT = new GIcon("icon.plugin.fsbrowser.view.as.text");
|
||||
public static final Icon ECLIPSE = new GIcon("icon.plugin.fsbrowser.eclipse");
|
||||
public static final Icon JAR = new GIcon("icon.plugin.fsbrowser.jar");
|
||||
public static final Icon IMPORT = new GIcon("icon.plugin.fsbrowser.import");
|
||||
public static final Icon iOS = new GIcon("icon.plugin.fsbrowser.ios");
|
||||
public static final Icon OPEN_ALL = new GIcon("icon.plugin.fsbrowser.open.all");
|
||||
public static final Icon LIST_MOUNTED = new GIcon("icon.plugin.fsbrowser.list.mounted");
|
||||
public static final Icon LIBRARY = new GIcon("icon.plugin.fsbrowser.library");
|
||||
|
||||
public static final Icon IMPORTED_OVERLAY_ICON = new GIcon("icon.fsbrowser.file.overlay.imported");
|
||||
public static final Icon FILESYSTEM_OVERLAY_ICON = new GIcon("icon.fsbrowser.file.overlay.filesystem");
|
||||
public static final Icon MISSING_PASSWORD_OVERLAY_ICON = new GIcon("icon.fsbrowser.file.overlay.missing.password");
|
||||
public static final Icon LINK_OVERLAY_ICON = new GIcon("icon.fsbrowser.file.overlay.link");
|
||||
public static final Icon DEFAULT_ICON = new GIcon("icon.fsbrowser.file.extension.default");
|
||||
//@formatter:on
|
||||
|
||||
public static FSBIcons getInstance() {
|
||||
return Singleton.INSTANCE;
|
||||
}
|
||||
|
||||
private static final class Singleton {
|
||||
private static final FSBIcons INSTANCE = new FSBIcons();
|
||||
}
|
||||
|
||||
private static final String EXTENSION_ICON_PREFIX = "icon.fsbrowser.file.extension";
|
||||
private static final String SUBSTRING_ICON_PREFIX = "icon.fsbrowser.file.substring";
|
||||
|
||||
private Map<String, Icon> substringToIconMap = createSubstringMap();
|
||||
|
||||
private FSBIcons() {
|
||||
// don't create instances of this class, use getInstance() instead
|
||||
}
|
||||
|
||||
private Map<String, Icon> createSubstringMap() {
|
||||
Map<String, Icon> results = new HashMap<>();
|
||||
GThemeValueMap values = ThemeManager.getInstance().getCurrentValues();
|
||||
List<IconValue> icons = values.getIcons();
|
||||
for (IconValue iconValue : icons) {
|
||||
String id = iconValue.getId();
|
||||
if (id.startsWith(SUBSTRING_ICON_PREFIX)) {
|
||||
String substring = id.substring(SUBSTRING_ICON_PREFIX.length());
|
||||
results.put(substring, new GIcon(id));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Icon} that represents a file's content based on its
|
||||
* name.
|
||||
*
|
||||
* @param fileName name of file that an icon is being requested for.
|
||||
* @param overlays optional list of overlay icons that
|
||||
* should be overlaid on top of the base icon. These icons represent a
|
||||
* status or feature independent of the file's base icon.
|
||||
* @return {@link Icon} instance that best represents the named file, never
|
||||
* null.
|
||||
*/
|
||||
public Icon getIcon(String fileName, List<Icon> overlays) {
|
||||
fileName = fileName.toLowerCase();
|
||||
String ext = FSUtilities.getExtension(fileName, 1);
|
||||
if (ext != null) {
|
||||
String iconId = EXTENSION_ICON_PREFIX + ext;
|
||||
if (Gui.hasIcon(iconId)) {
|
||||
Icon base = new GIcon(iconId);
|
||||
return buildIcon(base, overlays);
|
||||
}
|
||||
}
|
||||
|
||||
for (String substring : substringToIconMap.keySet()) {
|
||||
if (fileName.indexOf(substring) != -1) {
|
||||
return buildIcon(substringToIconMap.get(substring), overlays);
|
||||
}
|
||||
}
|
||||
|
||||
// return default icon for generic file
|
||||
return buildIcon(DEFAULT_ICON, overlays);
|
||||
}
|
||||
|
||||
private Icon buildIcon(Icon base, List<Icon> overlays) {
|
||||
if (overlays == null || overlays.isEmpty()) {
|
||||
return base;
|
||||
}
|
||||
MultiIcon multiIcon = new MultiIcon(base);
|
||||
for (Icon overlay : overlays) {
|
||||
if (overlay != null) {
|
||||
multiIcon.addIcon(overlay);
|
||||
}
|
||||
}
|
||||
return multiIcon;
|
||||
}
|
||||
|
||||
}
|
@ -15,9 +15,12 @@
|
||||
*/
|
||||
package ghidra.plugins.fsbrowser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.GTreeSlowLoadingNode;
|
||||
@ -39,6 +42,14 @@ public abstract class FSBNode extends GTreeSlowLoadingNode {
|
||||
*/
|
||||
public abstract FSRL getFSRL();
|
||||
|
||||
public void init(TaskMonitor monitor) throws CancelledException {
|
||||
// nothing
|
||||
}
|
||||
|
||||
public GFile getGFile() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return getName();
|
||||
@ -56,13 +67,136 @@ public abstract class FSBNode extends GTreeSlowLoadingNode {
|
||||
|
||||
public FSBRootNode getFSBRootNode() {
|
||||
GTreeNode node = getParent();
|
||||
while (node != null && !(node instanceof FSBRootNode)) {
|
||||
while (node != null) {
|
||||
if (node instanceof FSBRootNode rootNode) {
|
||||
return rootNode;
|
||||
}
|
||||
node = node.getParent();
|
||||
}
|
||||
return (node instanceof FSBRootNode) ? (FSBRootNode) node : null;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract void updateFileAttributes(TaskMonitor monitor) throws CancelledException;
|
||||
public abstract void refreshNode(TaskMonitor monitor) throws CancelledException;
|
||||
|
||||
protected void loadChildrenIfNeeded(TaskMonitor monitor) throws CancelledException {
|
||||
if (!isLeaf() && !isLoaded()) {
|
||||
doSetChildren(generateChildren(monitor));
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<FSRL, GFile> getListing(GFile f) {
|
||||
try {
|
||||
List<GFile> listing = f.getListing();
|
||||
return listing.stream().collect(Collectors.toMap(f1 -> f1.getFSRL(), f1 -> f1));
|
||||
}
|
||||
catch (IOException e) {
|
||||
return Map.of();
|
||||
}
|
||||
}
|
||||
|
||||
protected void refreshChildren(TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
GFile f = getGFile();
|
||||
if (f == null || !isLoaded() || isLeaf()) {
|
||||
return;
|
||||
}
|
||||
Map<FSRL, GFile> currentFiles = getListing(f);
|
||||
|
||||
int changeCount = 0;
|
||||
boolean cryptoCausesFullRefresh = true;
|
||||
boolean flagFSBRootNodeWithCryptoUpdate = false;
|
||||
|
||||
List<GTreeNode> newNodes = new ArrayList<>();
|
||||
List<GTreeNode> currentChildren = new ArrayList<>(children());
|
||||
for (GTreeNode oldNode : currentChildren) {
|
||||
monitor.increment();
|
||||
if (oldNode instanceof FSBNode fsbNode) {
|
||||
GFile currentFile = currentFiles.get(fsbNode.getFSRL());
|
||||
if (fileMatchesNode(currentFile, fsbNode)) {
|
||||
boolean checkPwUpdate = cryptoCausesFullRefresh &&
|
||||
fsbNode instanceof FSBFileNode fileNode && fileNode.hasMissingPassword();
|
||||
|
||||
fsbNode.refreshNode(monitor);
|
||||
|
||||
flagFSBRootNodeWithCryptoUpdate |= checkPwUpdate &&
|
||||
fsbNode instanceof FSBFileNode fileNode && !fileNode.hasMissingPassword();
|
||||
|
||||
newNodes.add(fsbNode); // port old node over to new list
|
||||
currentFiles.remove(fsbNode.getFSRL());
|
||||
}
|
||||
else {
|
||||
// by not adding to newNodes, the old node will disappear
|
||||
changeCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add any remaining GFiles as new nodes
|
||||
changeCount += currentFiles.size();
|
||||
currentFiles.values()
|
||||
.stream()
|
||||
.map(f1 -> createNodeFromFile(f1, monitor))
|
||||
.forEach(newNodes::add);
|
||||
|
||||
Collections.sort(newNodes, FSBNODE_NAME_TYPE_COMPARATOR);
|
||||
|
||||
FSBRootNode fsbRootNode;
|
||||
if (flagFSBRootNodeWithCryptoUpdate && (fsbRootNode = getFSBRootNode()) != null) {
|
||||
fsbRootNode.setCryptoStatusUpdated(true);
|
||||
}
|
||||
|
||||
if (changeCount > 0) {
|
||||
setChildren(newNodes);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean fileMatchesNode(GFile f, FSBNode node) {
|
||||
if (f == null) {
|
||||
return false;
|
||||
}
|
||||
if (node instanceof FSBFileNode fileNode &&
|
||||
f.isDirectory() != (fileNode instanceof FSBDirNode)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
protected FSBFileNode findMatchingNode(GFile f, TaskMonitor monitor) throws CancelledException {
|
||||
loadChildrenIfNeeded(monitor);
|
||||
for (GTreeNode treeNode : children()) {
|
||||
if (treeNode instanceof FSBFileNode fileNode) {
|
||||
if (fileNode.file.equals(f)) {
|
||||
return fileNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getFormattedTreePath() {
|
||||
TreePath treePath = getTreePath();
|
||||
StringBuilder path = new StringBuilder();
|
||||
for (Object pathElement : treePath.getPath()) {
|
||||
if (pathElement instanceof FSBNode node) {
|
||||
if (!path.isEmpty()) {
|
||||
path.append("/");
|
||||
}
|
||||
if (node instanceof FSBRootNode rootNode) {
|
||||
FSRL fsContainer = rootNode.getContainer();
|
||||
if (fsContainer != null) {
|
||||
path.append(fsContainer.getName());
|
||||
}
|
||||
}
|
||||
else {
|
||||
path.append(node.getFSRL().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
abstract public FSRL getLoadableFSRL();
|
||||
|
||||
/**
|
||||
* Returns the {@link FSBRootNode} that represents the root of the file system that
|
||||
@ -93,10 +227,7 @@ public abstract class FSBNode extends GTreeSlowLoadingNode {
|
||||
|
||||
List<GTreeNode> nodes = new ArrayList<>(files.size());
|
||||
for (GFile child : files) {
|
||||
FSBFileNode node = createNodeFromFile(child);
|
||||
if (node.isLeaf()) {
|
||||
node.updateFileAttributes(monitor);
|
||||
}
|
||||
FSBFileNode node = createNodeFromFile(child, monitor);
|
||||
nodes.add(node);
|
||||
}
|
||||
return nodes;
|
||||
@ -108,8 +239,25 @@ public abstract class FSBNode extends GTreeSlowLoadingNode {
|
||||
* @param file {@link GFile} to convert
|
||||
* @return a new {@link FSBFileNode} with type specific to the GFile's type.
|
||||
*/
|
||||
public static FSBFileNode createNodeFromFile(GFile file) {
|
||||
return file.isDirectory() ? new FSBDirNode(file) : new FSBFileNode(file);
|
||||
public static FSBFileNode createNodeFromFile(GFile file, TaskMonitor monitor) {
|
||||
FSBFileNode result = file.isDirectory() ? new FSBDirNode(file) : new FSBFileNode(file);
|
||||
result.init(monitor);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static final Comparator<GTreeNode> FSBNODE_NAME_TYPE_COMPARATOR = (o1, o2) -> {
|
||||
if (!(o1 instanceof FSBNode node1) || !(o2 instanceof FSBNode node2)) {
|
||||
return 0;
|
||||
}
|
||||
GFile f1 = node1.getGFile();
|
||||
GFile f2 = node2.getGFile();
|
||||
int result = Boolean.compare(!f1.isDirectory(), !f2.isDirectory());
|
||||
if (result == 0) {
|
||||
String n1 = Objects.requireNonNullElse(f1.getName(), "");
|
||||
String n2 = Objects.requireNonNullElse(f2.getName(), "");
|
||||
result = n1.compareToIgnoreCase(n2);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ public class FSBRootNode extends FSBNode {
|
||||
|
||||
private FileSystemRef fsRef;
|
||||
private FSBFileNode prevNode;
|
||||
private List<FSBRootNode> subRootNodes = new ArrayList<>();
|
||||
private FSBRootNode modelNode;
|
||||
private boolean cryptoStatusUpdated;
|
||||
|
||||
FSBRootNode(FileSystemRef fsRef) {
|
||||
this(fsRef, null);
|
||||
@ -60,11 +60,24 @@ public class FSBRootNode extends FSBNode {
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
releaseFSRefsIfModelNode();
|
||||
releaseFSRefIfModelNode();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void swapBackPrevModelNodeAndDispose() {
|
||||
@Override
|
||||
public void init(TaskMonitor monitor) throws CancelledException {
|
||||
setChildren(generateChildren(monitor));
|
||||
}
|
||||
|
||||
public void setCryptoStatusUpdated(boolean cryptoStatusUpdated) {
|
||||
this.cryptoStatusUpdated = cryptoStatusUpdated;
|
||||
}
|
||||
|
||||
boolean isCryptoStatusUpdated() {
|
||||
return cryptoStatusUpdated;
|
||||
}
|
||||
|
||||
public void swapBackPrevModelNodeAndDispose() {
|
||||
if (this != modelNode) {
|
||||
modelNode.swapBackPrevModelNodeAndDispose();
|
||||
return;
|
||||
@ -76,34 +89,32 @@ public class FSBRootNode extends FSBNode {
|
||||
dispose(); // releases the fsRef
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFile getGFile() {
|
||||
return fsRef.getFilesystem().getRootDir();
|
||||
}
|
||||
|
||||
public FileSystemRef getFSRef() {
|
||||
return modelNode.fsRef;
|
||||
}
|
||||
|
||||
private void releaseFSRefsIfModelNode() {
|
||||
private void releaseFSRefIfModelNode() {
|
||||
if (this != modelNode) {
|
||||
return;
|
||||
}
|
||||
for (FSBRootNode subFSBRootNode : subRootNodes) {
|
||||
subFSBRootNode.releaseFSRefsIfModelNode();
|
||||
}
|
||||
subRootNodes.clear();
|
||||
|
||||
FileSystemService.getInstance().releaseFileSystemImmediate(fsRef);
|
||||
fsRef = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFileAttributes(TaskMonitor monitor) throws CancelledException {
|
||||
public void refreshNode(TaskMonitor monitor) throws CancelledException {
|
||||
if (this != modelNode) {
|
||||
modelNode.updateFileAttributes(monitor);
|
||||
modelNode.refreshNode(monitor);
|
||||
return;
|
||||
}
|
||||
for (GTreeNode node : getChildren()) {
|
||||
monitor.checkCancelled();
|
||||
if (node instanceof FSBFileNode) {
|
||||
((FSBFileNode) node).updateFileAttributes(monitor);
|
||||
}
|
||||
refreshChildren(monitor);
|
||||
if (cryptoStatusUpdated) {
|
||||
// do something to refresh children's status that may have been affected by crypto update
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,6 +152,68 @@ public class FSBRootNode extends FSBNode {
|
||||
|
||||
@Override
|
||||
public FSRL getFSRL() {
|
||||
return modelNode.fsRef.getFilesystem().getFSRL();
|
||||
return modelNode != null && modelNode.fsRef != null
|
||||
? modelNode.fsRef.getFilesystem().getFSRL()
|
||||
: null;
|
||||
}
|
||||
|
||||
public FSBNode getGFileFSBNode(GFile file, TaskMonitor monitor) {
|
||||
List<GFile> pathParts = splitGFilePath(file);
|
||||
FSBNode fileNode = this;
|
||||
for (int i = 1 /* skip root */; fileNode != null && i < pathParts.size(); i++) {
|
||||
try {
|
||||
fileNode = fileNode.findMatchingNode(pathParts.get(i), monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return fileNode;
|
||||
}
|
||||
|
||||
public FSRL getContainer() {
|
||||
// use the rootDir's FSRL to sidestep issue with LocalFileSystemSub's non-standard fsFSRL
|
||||
return fsRef != null
|
||||
? fsRef.getFilesystem().getRootDir().getFSRL().getFS().getContainer()
|
||||
: null;
|
||||
}
|
||||
|
||||
public String getContainerName() {
|
||||
return prevNode != null ? prevNode.getName() : "/";
|
||||
}
|
||||
|
||||
private List<GFile> splitGFilePath(GFile f) {
|
||||
List<GFile> result = new ArrayList<>();
|
||||
while (f != null) {
|
||||
result.add(0, f);
|
||||
f = f.getParentFile();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public FSRL getProgramProviderFSRL(FSRL fsrl) {
|
||||
GFileSystem fs = fsRef.getFilesystem();
|
||||
if (fs instanceof GFileSystemProgramProvider programProviderFS) {
|
||||
try {
|
||||
GFile gfile = fs.lookup(fsrl.getPath());
|
||||
if (gfile != null && programProviderFS.canProvideProgram(gfile)) {
|
||||
return fsrl;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore error and fall thru
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FSRL getLoadableFSRL() {
|
||||
FSRL ppFSRL = getProgramProviderFSRL(getFSRL());
|
||||
if (ppFSRL != null) {
|
||||
return ppFSRL;
|
||||
}
|
||||
return getContainer();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,99 +0,0 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.SelectFromListDialog;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* {@link FileSystemBrowserPlugin} utility methods that other things might find useful.
|
||||
*/
|
||||
public class FSBUtils {
|
||||
|
||||
/**
|
||||
* Returns the {@link ProgramManager} associated with this fs browser plugin.
|
||||
* <p>
|
||||
* When this FS Browser plugin is part of the front-end tool, this will search
|
||||
* for an open CodeBrowser tool that can be used to handle programs.
|
||||
* <p>
|
||||
* When this FS Browser plugin is part of a CodeBrowser tool, this will just return
|
||||
* the local ProgramManager / CodeBrowser.
|
||||
*
|
||||
* @param tool The plugin tool.
|
||||
* @param allowUserPrompt boolean flag to allow this method to query the user to select
|
||||
* a CodeBrowser.
|
||||
* @return null if front-end and no open CodeBrowser, otherwise returns the local
|
||||
* CodeBrowser ProgramManager service.
|
||||
*/
|
||||
public static ProgramManager getProgramManager(PluginTool tool, boolean allowUserPrompt) {
|
||||
PluginTool pmTool = null;
|
||||
ProgramManager pm = tool.getService(ProgramManager.class);
|
||||
if (pm != null) {
|
||||
pmTool = tool;
|
||||
}
|
||||
else {
|
||||
List<PluginTool> runningPMTools = FSBUtils.getRunningProgramManagerTools(tool);
|
||||
if (runningPMTools.size() == 1) {
|
||||
pmTool = runningPMTools.get(0);
|
||||
}
|
||||
else {
|
||||
pmTool = allowUserPrompt ? selectPMTool(tool) : null;
|
||||
}
|
||||
}
|
||||
return (pmTool != null) ? pmTool.getService(ProgramManager.class) : null;
|
||||
}
|
||||
|
||||
public static List<PluginTool> getRunningProgramManagerTools(PluginTool tool) {
|
||||
List<PluginTool> pluginTools = new ArrayList<>();
|
||||
for (PluginTool runningTool : tool.getToolServices().getRunningTools()) {
|
||||
PluginTool pt = runningTool;
|
||||
ProgramManager pmService = pt.getService(ProgramManager.class);
|
||||
if (pmService != null) {
|
||||
pluginTools.add(pt);
|
||||
}
|
||||
}
|
||||
return pluginTools;
|
||||
}
|
||||
|
||||
private static PluginTool selectPMTool(PluginTool tool) {
|
||||
ProgramManager pm = tool.getService(ProgramManager.class);
|
||||
if (pm != null) {
|
||||
return tool;
|
||||
}
|
||||
|
||||
List<PluginTool> pluginTools = FSBUtils.getRunningProgramManagerTools(tool);
|
||||
|
||||
if (pluginTools.size() == 1) {
|
||||
return pluginTools.get(0);
|
||||
}
|
||||
|
||||
if (pluginTools.isEmpty()) {
|
||||
Msg.showWarn(tool, tool.getActiveWindow(), "No open tools",
|
||||
"There are no open tools to use to open a program with");
|
||||
return null;
|
||||
}
|
||||
|
||||
PluginTool pt = SelectFromListDialog.selectFromList(pluginTools, "Select tool",
|
||||
"Select a tool to use to open programs", pluginTool -> pluginTool.getName());
|
||||
return pt;
|
||||
}
|
||||
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import generic.theme.*;
|
||||
import ghidra.formats.gfilesystem.FSUtilities;
|
||||
import resources.MultiIcon;
|
||||
|
||||
/**
|
||||
* Provides {@link Icon}s that represent the type and status of a file, based on
|
||||
* a filename mapping and caller specified status overlays.
|
||||
* <p>
|
||||
* The mappings between a file's extension and its icon are stored in a resource
|
||||
* file called "file_extension_icons.xml", which is read and parsed the first
|
||||
* time this service is referenced.
|
||||
* <p>
|
||||
* Status overlays are also specified in the file_extension_icons.xml file, and
|
||||
* are resized to be 1/2 the width and height of the icon they are being
|
||||
* overlaid on.
|
||||
* <p>
|
||||
* Thread safe
|
||||
* <p>
|
||||
*/
|
||||
public class FileIconService {
|
||||
|
||||
private static final class Singleton {
|
||||
private static final FileIconService INSTANCE = new FileIconService();
|
||||
}
|
||||
|
||||
public static FileIconService getInstance() {
|
||||
return Singleton.INSTANCE;
|
||||
}
|
||||
|
||||
public static final Icon IMPORTED_OVERLAY_ICON =
|
||||
new GIcon("icon.fsbrowser.file.overlay.imported");
|
||||
public static final Icon FILESYSTEM_OVERLAY_ICON =
|
||||
new GIcon("icon.fsbrowser.file.overlay.filesystem");
|
||||
public static final Icon MISSING_PASSWORD_OVERLAY_ICON =
|
||||
new GIcon("icon.fsbrowser.file.overlay.missing.password");
|
||||
public static final Icon DEFAULT_ICON = new GIcon("icon.fsbrowser.file.extension.default");
|
||||
|
||||
private static final String EXTENSION_ICON_PREFIX = "icon.fsbrowser.file.extension";
|
||||
private static final String SUBSTRING_ICON_PREFIX = "icon.fsbrowser.file.substring";
|
||||
|
||||
private Map<String, Icon> substringToIconMap = new HashMap<>();
|
||||
|
||||
private FileIconService() {
|
||||
createSubstringMap();
|
||||
}
|
||||
|
||||
private void createSubstringMap() {
|
||||
GThemeValueMap values = ThemeManager.getInstance().getCurrentValues();
|
||||
List<IconValue> icons = values.getIcons();
|
||||
for (IconValue iconValue : icons) {
|
||||
String id = iconValue.getId();
|
||||
if (id.startsWith(SUBSTRING_ICON_PREFIX)) {
|
||||
String substring = id.substring(SUBSTRING_ICON_PREFIX.length());
|
||||
substringToIconMap.put(substring, new GIcon(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Icon} that represents a file's content based on its
|
||||
* name.
|
||||
*
|
||||
* @param fileName name of file that an icon is being requested for.
|
||||
* @param overlays optional list of overlay icons that
|
||||
* should be overlaid on top of the base icon. These icons represent a
|
||||
* status or feature independent of the file's base icon.
|
||||
* @return {@link Icon} instance that best represents the named file, never
|
||||
* null.
|
||||
*/
|
||||
public Icon getIcon(String fileName, List<Icon> overlays) {
|
||||
fileName = fileName.toLowerCase();
|
||||
String ext = FSUtilities.getExtension(fileName, 1);
|
||||
if (ext != null) {
|
||||
String iconId = EXTENSION_ICON_PREFIX + ext;
|
||||
if (Gui.hasIcon(iconId)) {
|
||||
Icon base = new GIcon(iconId);
|
||||
return buildIcon(base, overlays);
|
||||
}
|
||||
}
|
||||
|
||||
for (String substring : substringToIconMap.keySet()) {
|
||||
if (fileName.indexOf(substring) != -1) {
|
||||
return buildIcon(substringToIconMap.get(substring), overlays);
|
||||
}
|
||||
}
|
||||
|
||||
// return default icon for generic file
|
||||
return buildIcon(DEFAULT_ICON, overlays);
|
||||
}
|
||||
|
||||
private Icon buildIcon(Icon base, List<Icon> overlays) {
|
||||
if (overlays == null || overlays.isEmpty()) {
|
||||
return base;
|
||||
}
|
||||
MultiIcon multiIcon = new MultiIcon(base);
|
||||
for (Icon overlay : overlays) {
|
||||
if (overlay != null) {
|
||||
multiIcon.addIcon(overlay);
|
||||
}
|
||||
}
|
||||
return multiIcon;
|
||||
}
|
||||
}
|
@ -1,339 +0,0 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
|
||||
import docking.WindowPosition;
|
||||
import docking.event.mouse.GMouseListenerAdapter;
|
||||
import docking.widgets.tree.GTree;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.support.GTreeRenderer;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.plugin.importer.ProgramMappingService;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.*;
|
||||
|
||||
/**
|
||||
* Plugin component provider for the {@link FileSystemBrowserPlugin}.
|
||||
* <p>
|
||||
* An instance of this class is created for each file system browser window (w/tree).
|
||||
* <p>
|
||||
* Visible to just this package.
|
||||
*/
|
||||
class FileSystemBrowserComponentProvider extends ComponentProviderAdapter
|
||||
implements FileSystemEventListener {
|
||||
private static final String TITLE = "Filesystem Viewer";
|
||||
|
||||
private FileSystemBrowserPlugin plugin;
|
||||
private FSBActionManager actionManager;
|
||||
private GTree gTree;
|
||||
private FSBRootNode rootNode;
|
||||
private FileSystemService fsService = FileSystemService.getInstance();
|
||||
|
||||
/**
|
||||
* Creates a new {@link FileSystemBrowserComponentProvider} instance, taking
|
||||
* ownership of the passed-in {@link FileSystemRef fsRef}.
|
||||
*
|
||||
* @param plugin parent plugin
|
||||
* @param fsRef {@link FileSystemRef} to a {@link GFileSystem}.
|
||||
*/
|
||||
public FileSystemBrowserComponentProvider(FileSystemBrowserPlugin plugin, FileSystemRef fsRef) {
|
||||
super(plugin.getTool(), fsRef.getFilesystem().getName(), plugin.getName());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.rootNode = new FSBRootNode(fsRef);
|
||||
|
||||
setTransient();
|
||||
setIcon(ImageManager.PHOTO);
|
||||
|
||||
gTree = new GTree(rootNode);
|
||||
gTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
|
||||
gTree.getSelectionModel().addTreeSelectionListener(e -> {
|
||||
tool.contextChanged(FileSystemBrowserComponentProvider.this);
|
||||
TreePath[] paths = gTree.getSelectionPaths();
|
||||
if (paths.length == 1) {
|
||||
GTreeNode clickedNode = (GTreeNode) paths[0].getLastPathComponent();
|
||||
handleSingleClick(clickedNode);
|
||||
}
|
||||
});
|
||||
gTree.addMouseListener(new GMouseListenerAdapter() {
|
||||
@Override
|
||||
public void doubleClickTriggered(MouseEvent e) {
|
||||
handleDoubleClick(gTree.getNodeForLocation(e.getX(), e.getY()));
|
||||
e.consume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
super.mouseClicked(e);
|
||||
if (!e.isConsumed()) {
|
||||
handleSingleClick(gTree.getNodeForLocation(e.getX(), e.getY()));
|
||||
}
|
||||
}
|
||||
});
|
||||
gTree.setCellRenderer(new GTreeRenderer() {
|
||||
@Override
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object value,
|
||||
boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||
|
||||
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row,
|
||||
hasFocus);
|
||||
|
||||
if (value instanceof FSBRootNode) {
|
||||
renderFS((FSBRootNode) value, selected);
|
||||
}
|
||||
else if (value instanceof FSBDirNode) {
|
||||
// do nothing special
|
||||
}
|
||||
else if (value instanceof FSBFileNode) {
|
||||
renderFile((FSBFileNode) value, selected);
|
||||
}
|
||||
else if (value instanceof FSBNode) {
|
||||
renderNode((FSBNode) value, selected);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void renderFS(FSBRootNode node, boolean selected) {
|
||||
FileSystemRef nodeFSRef = node.getFSRef();
|
||||
if (nodeFSRef == null || nodeFSRef.getFilesystem() == null) {
|
||||
return;
|
||||
}
|
||||
FSRLRoot fsFSRL = nodeFSRef.getFilesystem().getFSRL();
|
||||
String containerFilename =
|
||||
fsFSRL.hasContainer() ? fsFSRL.getContainer().getName() : "unknown";
|
||||
Icon image = FileIconService.getInstance()
|
||||
.getIcon(containerFilename,
|
||||
List.of(FileIconService.FILESYSTEM_OVERLAY_ICON));
|
||||
setIcon(image);
|
||||
}
|
||||
|
||||
private void renderFile(FSBFileNode node, boolean selected) {
|
||||
FSRL fsrl = node.getFSRL();
|
||||
String filename = fsrl.getName();
|
||||
List<Icon> overlays = new ArrayList<>(3);
|
||||
|
||||
if (ProgramMappingService.isFileImportedIntoProject(fsrl)) {
|
||||
overlays.add(FileIconService.IMPORTED_OVERLAY_ICON);
|
||||
}
|
||||
if (fsService.isFilesystemMountedAt(fsrl)) {
|
||||
overlays.add(FileIconService.FILESYSTEM_OVERLAY_ICON);
|
||||
}
|
||||
if (node.hasMissingPassword()) {
|
||||
overlays.add(FileIconService.MISSING_PASSWORD_OVERLAY_ICON);
|
||||
}
|
||||
|
||||
Icon icon = FileIconService.getInstance().getIcon(filename, overlays);
|
||||
setIcon(icon);
|
||||
|
||||
if (ProgramMappingService.isFileOpen(fsrl)) {
|
||||
// TODO: change this to a OVERLAY_OPEN option when fetching icon
|
||||
setForeground(selected ? Palette.CYAN : Palette.MAGENTA);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderNode(FSBNode node, boolean selected) {
|
||||
// do nothing for now
|
||||
}
|
||||
});
|
||||
|
||||
actionManager = new FSBActionManager(plugin, this, gTree);
|
||||
|
||||
// TODO: fix this Help stuff
|
||||
setHelpLocation(
|
||||
new HelpLocation("FileSystemBrowserPlugin", "FileSystemBrowserIntroduction"));
|
||||
|
||||
fsRef.getFilesystem().getRefManager().addListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing access only.
|
||||
*
|
||||
* @return this provider's GTree.
|
||||
*/
|
||||
GTree getGTree() {
|
||||
return gTree;
|
||||
}
|
||||
|
||||
FSRL getFSRL() {
|
||||
return rootNode != null ? rootNode.getFSRL() : null;
|
||||
}
|
||||
|
||||
FSBActionManager getActionManager() {
|
||||
return actionManager;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
if (rootNode != null && rootNode.getFSRef() != null && !rootNode.getFSRef().isClosed()) {
|
||||
rootNode.getFSRef().getFilesystem().getRefManager().removeListener(this);
|
||||
}
|
||||
removeFromTool();
|
||||
if (actionManager != null) {
|
||||
actionManager.dispose();
|
||||
actionManager = null;
|
||||
}
|
||||
if (gTree != null) {
|
||||
gTree.dispose(); // calls dispose() on tree's rootNode, which will release the fsRefs
|
||||
gTree = null;
|
||||
}
|
||||
rootNode = null;
|
||||
plugin = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden() {
|
||||
// if the component is 'closed', nuke ourselves
|
||||
if (plugin != null) {
|
||||
plugin.removeFileSystemBrowserComponent(this);
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void afterAddedToTool() {
|
||||
actionManager.registerComponentActionsInTool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesystemClose(GFileSystem fs) {
|
||||
Msg.info(this, "File system " + fs.getFSRL() + " was closed! Closing browser window");
|
||||
Swing.runIfSwingOrRunLater(() -> componentHidden());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesystemRefChange(GFileSystem fs, FileSystemRefManager refManager) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
/*****************************************/
|
||||
|
||||
/**
|
||||
* Finds an associated already open {@link Program} and makes it visible in the
|
||||
* current tool's ProgramManager.
|
||||
*
|
||||
* @param fsrl {@link FSRL} of the file to attempt to quickly show if its already open in a PM.
|
||||
* @return boolean true if already open program was found and it was switched to.
|
||||
*/
|
||||
private boolean quickShowProgram(FSRL fsrl) {
|
||||
if (plugin.hasProgramManager()) {
|
||||
ProgramManager programManager = FSBUtils.getProgramManager(plugin.getTool(), false);
|
||||
if (programManager != null) {
|
||||
Object consumer = new Object();
|
||||
Program program = ProgramMappingService.findMatchingOpenProgram(fsrl, consumer);
|
||||
if (program != null) {
|
||||
programManager.setCurrentProgram(program);
|
||||
program.release(consumer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleSingleClick(GTreeNode clickedNode) {
|
||||
if (clickedNode instanceof FSBFileNode) {
|
||||
FSBFileNode node = (FSBFileNode) clickedNode;
|
||||
if (node.getFSRL() != null) {
|
||||
quickShowProgram(node.getFSRL());
|
||||
updatePasswordStatus(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePasswordStatus(FSBFileNode node) {
|
||||
// currently this is the only state that might change
|
||||
// and that effect the node display
|
||||
if (node.hasMissingPassword()) {
|
||||
// check and see if its status has changed
|
||||
gTree.runTask(monitor -> {
|
||||
if (node.needsFileAttributesUpdate(monitor)) {
|
||||
actionManager.doRefreshInfo(List.of(node), monitor);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDoubleClick(GTreeNode clickedNode) {
|
||||
if (clickedNode instanceof FSBFileNode && clickedNode.isLeaf()) {
|
||||
FSBFileNode node = (FSBFileNode) clickedNode;
|
||||
|
||||
if (node.getFSRL() != null && !quickShowProgram(node.getFSRL())) {
|
||||
actionManager.actionOpenPrograms.actionPerformed(getActionContext(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************/
|
||||
|
||||
@Override
|
||||
public FSBActionContext getActionContext(MouseEvent event) {
|
||||
return new FSBActionContext(this, getSelectedNodes(event), event, gTree);
|
||||
}
|
||||
|
||||
private FSBNode[] getSelectedNodes(MouseEvent event) {
|
||||
TreePath[] selectionPaths = gTree.getSelectionPaths();
|
||||
List<FSBNode> list = new ArrayList<>(selectionPaths.length);
|
||||
for (TreePath selectionPath : selectionPaths) {
|
||||
Object lastPathComponent = selectionPath.getLastPathComponent();
|
||||
if (lastPathComponent instanceof FSBNode) {
|
||||
list.add((FSBNode) lastPathComponent);
|
||||
}
|
||||
}
|
||||
if (list.isEmpty() && event != null) {
|
||||
Object source = event.getSource();
|
||||
int x = event.getX();
|
||||
int y = event.getY();
|
||||
if (source instanceof JTree) {
|
||||
JTree sourceTree = (JTree) source;
|
||||
if (gTree.isMyJTree(sourceTree)) {
|
||||
GTreeNode nodeAtEventLocation = gTree.getNodeForLocation(x, y);
|
||||
if (nodeAtEventLocation != null && nodeAtEventLocation instanceof FSBNode) {
|
||||
list.add((FSBNode) nodeAtEventLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list.toArray(FSBNode[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return gTree;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TITLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowPosition getDefaultWindowPosition() {
|
||||
return WindowPosition.WINDOW;
|
||||
}
|
||||
|
||||
}
|
@ -33,16 +33,14 @@ import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.services.FileSystemBrowserService;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.framework.main.ApplicationLevelPlugin;
|
||||
import ghidra.framework.main.FrontEndService;
|
||||
import ghidra.framework.model.Project;
|
||||
import ghidra.framework.model.ProjectListener;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.plugin.importer.ImporterUtilities;
|
||||
import ghidra.plugin.importer.ProgramMappingService;
|
||||
import ghidra.plugin.importer.ProjectIndexService;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
@ -74,8 +72,9 @@ public class FileSystemBrowserPlugin extends Plugin
|
||||
/* package */ DockingAction showFileSystemImplsAction;
|
||||
private GhidraFileChooser chooserOpen;
|
||||
private FrontEndService frontEndService;
|
||||
private Map<FSRL, FileSystemBrowserComponentProvider> currentBrowsers = new HashMap<>();
|
||||
private Map<FSRL, FSBComponentProvider> currentBrowsers = new HashMap<>();
|
||||
private FileSystemService fsService; // don't use this directly, use fsService() instead
|
||||
private File lastExportDirectory;
|
||||
|
||||
public FileSystemBrowserPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
@ -89,9 +88,6 @@ public class FileSystemBrowserPlugin extends Plugin
|
||||
if (frontEndService != null) {
|
||||
frontEndService.addProjectListener(this);
|
||||
}
|
||||
else {
|
||||
FSBUtils.getProgramManager(tool, false);
|
||||
}
|
||||
|
||||
setupActions();
|
||||
}
|
||||
@ -122,7 +118,7 @@ public class FileSystemBrowserPlugin extends Plugin
|
||||
chooserOpen.dispose();
|
||||
}
|
||||
|
||||
for (FileSystemBrowserComponentProvider provider : currentBrowsers.values()) {
|
||||
for (FSBComponentProvider provider : currentBrowsers.values()) {
|
||||
provider.dispose();
|
||||
}
|
||||
currentBrowsers.clear();
|
||||
@ -143,19 +139,19 @@ public class FileSystemBrowserPlugin extends Plugin
|
||||
* @param fsRef {@link FileSystemRef} of open {@link GFileSystem}
|
||||
* @param show boolean true if the new browser component should be shown
|
||||
*/
|
||||
/* package */ void createNewFileSystemBrowser(FileSystemRef fsRef, boolean show) {
|
||||
public void createNewFileSystemBrowser(FileSystemRef fsRef, boolean show) {
|
||||
Swing.runIfSwingOrRunLater(() -> doCreateNewFileSystemBrowser(fsRef, show));
|
||||
}
|
||||
|
||||
private void doCreateNewFileSystemBrowser(FileSystemRef fsRef, boolean show) {
|
||||
FSRLRoot fsFSRL = fsRef.getFilesystem().getFSRL();
|
||||
FileSystemBrowserComponentProvider provider = currentBrowsers.get(fsFSRL);
|
||||
FSBComponentProvider provider = currentBrowsers.get(fsFSRL);
|
||||
if (provider != null) {
|
||||
Msg.info(this, "Filesystem browser already open for " + fsFSRL);
|
||||
fsRef.close();
|
||||
}
|
||||
else {
|
||||
provider = new FileSystemBrowserComponentProvider(this, fsRef);
|
||||
provider = new FSBComponentProvider(this, fsRef);
|
||||
currentBrowsers.put(fsFSRL, provider);
|
||||
getTool().addComponentProvider(provider, false);
|
||||
provider.afterAddedToTool();
|
||||
@ -168,7 +164,7 @@ public class FileSystemBrowserPlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
void removeFileSystemBrowserComponent(FileSystemBrowserComponentProvider componentProvider) {
|
||||
void removeFileSystemBrowserComponent(FSBComponentProvider componentProvider) {
|
||||
if (componentProvider != null) {
|
||||
Swing.runIfSwingOrRunLater(() -> currentBrowsers.remove(componentProvider.getFSRL()));
|
||||
}
|
||||
@ -179,7 +175,7 @@ public class FileSystemBrowserPlugin extends Plugin
|
||||
*/
|
||||
private void removeAllFileSystemBrowsers() {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
for (FileSystemBrowserComponentProvider fsbcp : new ArrayList<>(
|
||||
for (FSBComponentProvider fsbcp : new ArrayList<>(
|
||||
currentBrowsers.values())) {
|
||||
fsbcp.dispose();
|
||||
}
|
||||
@ -187,29 +183,6 @@ public class FileSystemBrowserPlugin extends Plugin
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
|
||||
if (event instanceof ProgramActivatedPluginEvent) {
|
||||
ProgramActivatedPluginEvent pape = (ProgramActivatedPluginEvent) event;
|
||||
ProgramMappingService.createAutoAssocation(pape.getActiveProgram());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void projectClosed(Project project) {
|
||||
removeAllFileSystemBrowsers();
|
||||
if (FileSystemService.isInitialized()) {
|
||||
fsService().closeUnusedFileSystems();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void projectOpened(Project project) {
|
||||
// nada
|
||||
}
|
||||
|
||||
private void openChooser(String title, String buttonText, boolean multiSelect) {
|
||||
if (chooserOpen == null) {
|
||||
chooserOpen = new GhidraFileChooser(tool.getActiveWindow());
|
||||
@ -253,7 +226,7 @@ public class FileSystemBrowserPlugin extends Plugin
|
||||
* Prompts the user to pick a file system container file to open using a local
|
||||
* filesystem browser and then displays that filesystem in a new fsb browser.
|
||||
*/
|
||||
/* package */ void openFileSystem() {
|
||||
public void openFileSystem() {
|
||||
Swing.runLater(this::doOpenFileSystem);
|
||||
}
|
||||
|
||||
@ -291,28 +264,61 @@ public class FileSystemBrowserPlugin extends Plugin
|
||||
return fsService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a {@link ProgramManager} associated with this FSB.
|
||||
*
|
||||
* @return boolean true if there is a ProgramManager.
|
||||
*/
|
||||
/* package */ boolean hasProgramManager() {
|
||||
return tool.getService(ProgramManager.class) != null ||
|
||||
FSBUtils.getRunningProgramManagerTools(getTool()).size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing access only.
|
||||
*
|
||||
* @param fsFSRL {@link FSRLRoot} of browser component to fetch.
|
||||
* @return provider or null if not found.
|
||||
*/
|
||||
/* package */ FileSystemBrowserComponentProvider getProviderFor(FSRLRoot fsFSRL) {
|
||||
FileSystemBrowserComponentProvider provider = currentBrowsers.get(fsFSRL);
|
||||
/* package */ FSBComponentProvider getProviderFor(FSRLRoot fsFSRL) {
|
||||
FSBComponentProvider provider = currentBrowsers.get(fsFSRL);
|
||||
if (provider == null) {
|
||||
Msg.info(this, "Could not find browser for " + fsFSRL);
|
||||
return null;
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void projectClosed(Project project) {
|
||||
removeAllFileSystemBrowsers();
|
||||
if (FileSystemService.isInitialized()) {
|
||||
fsService().closeUnusedFileSystems();
|
||||
}
|
||||
ProjectIndexService.getInstance().clearProject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void projectOpened(Project project) {
|
||||
// there shouldn't be any fsb components open because the previous projectClosed would have
|
||||
// removed all fsb trees, therefore, we don't need to update any of the components
|
||||
// to tell them about the new project
|
||||
}
|
||||
|
||||
public boolean isOpen(DomainFile df) {
|
||||
Object tmp = new Object();
|
||||
DomainObject openDF = df.getOpenedDomainObject(tmp);
|
||||
if (openDF != null) {
|
||||
openDF.release(tmp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public File getLastExportDirectory() {
|
||||
return lastExportDirectory != null
|
||||
? lastExportDirectory
|
||||
: new File(System.getProperty("user.home"));
|
||||
}
|
||||
|
||||
public void setLastExportDirectory(File lastExportDirectory) {
|
||||
this.lastExportDirectory = lastExportDirectory;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,66 +0,0 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import generic.theme.GIcon;
|
||||
|
||||
/**
|
||||
* Static helper to register and load Icons for the file system browser plugin and its
|
||||
* child windows.
|
||||
* <p>
|
||||
* Visible to just this package.
|
||||
*/
|
||||
public class ImageManager {
|
||||
//@formatter:off
|
||||
public final static Icon COPY = new GIcon("icon.plugin.fsbrowser.copy");
|
||||
public final static Icon CUT = new GIcon("icon.plugin.fsbrowser.cut");
|
||||
public final static Icon DELETE = new GIcon("icon.plugin.fsbrowser.delete");
|
||||
public final static Icon FONT = new GIcon("icon.plugin.fsbrowser.font");
|
||||
public final static Icon LOCKED = new GIcon("icon.plugin.fsbrowser.locked");
|
||||
public final static Icon NEW = new GIcon("icon.plugin.fsbrowser.new");
|
||||
public final static Icon PASTE = new GIcon("icon.plugin.fsbrowser.paste");
|
||||
public final static Icon REDO = new GIcon("icon.plugin.fsbrowser.redo");
|
||||
public final static Icon RENAME = new GIcon("icon.plugin.fsbrowser.rename");
|
||||
public final static Icon REFRESH = new GIcon("icon.plugin.fsbrowser.refresh");
|
||||
public final static Icon SAVE = new GIcon("icon.plugin.fsbrowser.save");
|
||||
public final static Icon SAVE_AS = new GIcon("icon.plugin.fsbrowser.save.as");
|
||||
public final static Icon UNDO = new GIcon("icon.plugin.fsbrowser.undo");
|
||||
public final static Icon UNLOCKED = new GIcon("icon.plugin.fsbrowser.unlocked");
|
||||
public final static Icon CLOSE = new GIcon("icon.plugin.fsbrowser.close");
|
||||
public final static Icon COLLAPSE_ALL = new GIcon("icon.plugin.fsbrowser.collapse.all");
|
||||
public final static Icon COMPRESS = new GIcon("icon.plugin.fsbrowser.compress");
|
||||
public final static Icon CREATE_FIRMWARE = new GIcon("icon.plugin.fsbrowser.create.firmware");
|
||||
public final static Icon EXPAND_ALL = new GIcon("icon.plugin.fsbrowser.expand.all");
|
||||
public final static Icon EXTRACT = new GIcon("icon.plugin.fsbrowser.extract");
|
||||
public final static Icon INFO = new GIcon("icon.plugin.fsbrowser.info");
|
||||
public final static Icon OPEN = new GIcon("icon.plugin.fsbrowser.open");
|
||||
public final static Icon OPEN_AS_BINARY = new GIcon("icon.plugin.fsbrowser.open.as.binary");
|
||||
public final static Icon OPEN_IN_LISTING = new GIcon("icon.plugin.fsbrowser.open.in.listing");
|
||||
public final static Icon OPEN_FILE_SYSTEM = new GIcon("icon.plugin.fsbrowser.open.file.system");
|
||||
public final static Icon PHOTO = new GIcon("icon.plugin.fsbrowser.photo");
|
||||
public final static Icon VIEW_AS_IMAGE = new GIcon("icon.plugin.fsbrowser.view.as.image");
|
||||
public final static Icon VIEW_AS_TEXT = new GIcon("icon.plugin.fsbrowser.view.as.text");
|
||||
public final static Icon ECLIPSE = new GIcon("icon.plugin.fsbrowser.eclipse");
|
||||
public final static Icon JAR = new GIcon("icon.plugin.fsbrowser.jar");
|
||||
public final static Icon IMPORT = new GIcon("icon.plugin.fsbrowser.import");
|
||||
public final static Icon iOS = new GIcon("icon.plugin.fsbrowser.ios");
|
||||
public final static Icon OPEN_ALL = new GIcon("icon.plugin.fsbrowser.open.all");
|
||||
public final static Icon LIST_MOUNTED = new GIcon("icon.plugin.fsbrowser.list.mounted");
|
||||
public final static Icon LIBRARY = new GIcon("icon.plugin.fsbrowser.library");
|
||||
//@formatter:on
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.database.ProgramContentHandler;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* Represents a way to open a {@link DomainFile} in a {@link ProgramManager}
|
||||
*/
|
||||
public class OpenWithTarget {
|
||||
|
||||
/**
|
||||
* Returns a list of all running tools and tool templates that can be used to open a domainfile.
|
||||
*
|
||||
* @return list of OpenWithTarget instances, maybe empty but not null
|
||||
*/
|
||||
public static List<OpenWithTarget> getAll() {
|
||||
List<OpenWithTarget> results = new ArrayList<>();
|
||||
Project project = AppInfo.getActiveProject();
|
||||
if (project != null) {
|
||||
results.addAll(getRunningTargets(project));
|
||||
|
||||
ToolTemplate defaultTT = project.getToolServices()
|
||||
.getDefaultToolTemplate(ProgramContentHandler.PROGRAM_CONTENT_TYPE);
|
||||
results.add(new OpenWithTarget(defaultTT.getName(), null, defaultTT.getIcon()));
|
||||
|
||||
ToolTemplate[] templates = project.getLocalToolChest().getToolTemplates();
|
||||
for (ToolTemplate toolTemplate : templates) {
|
||||
if (!toolTemplate.getName().equals(defaultTT.getName())) {
|
||||
results.add(
|
||||
new OpenWithTarget(toolTemplate.getName(), null, toolTemplate.getIcon()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an OpenWithTarget, or null, that represents the specified tool's default ability
|
||||
* to open a {@link DomainFile}.
|
||||
*
|
||||
* @param tool a {@link PluginTool}
|
||||
* @return a {@link OpenWithTarget}, or null if the specified tool can't open a domain file
|
||||
*/
|
||||
public static OpenWithTarget getDefault(PluginTool tool) {
|
||||
Project project = tool.getProject();
|
||||
if (project == null) {
|
||||
return null;
|
||||
}
|
||||
ProgramManager pm = tool.getService(ProgramManager.class);
|
||||
if (pm != null) {
|
||||
return new OpenWithTarget(tool.getName(), pm, tool.getIcon());
|
||||
}
|
||||
if (AppInfo.getFrontEndTool().getDefaultLaunchMode() == DefaultLaunchMode.REUSE_TOOL) {
|
||||
List<OpenWithTarget> runningTargets = getRunningTargets(project);
|
||||
if (!runningTargets.isEmpty()) {
|
||||
return runningTargets.get(0);
|
||||
}
|
||||
}
|
||||
ToolTemplate defaultTT = project.getToolServices()
|
||||
.getDefaultToolTemplate(ProgramContentHandler.PROGRAM_CONTENT_TYPE);
|
||||
return new OpenWithTarget(defaultTT.getName(), null, defaultTT.getIcon());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an OpenWithTarget, or null, that represents a running {@link ProgramManager}.
|
||||
*
|
||||
* @param tool a {@link PluginTool}
|
||||
* @return a {@link OpenWithTarget}, or null if there is no open {@link ProgramManager}
|
||||
*/
|
||||
public static OpenWithTarget getRunningProgramManager(PluginTool tool) {
|
||||
Project project = tool.getProject();
|
||||
if (project == null) {
|
||||
return null;
|
||||
}
|
||||
ProgramManager pm = tool.getService(ProgramManager.class);
|
||||
if (pm != null) {
|
||||
return new OpenWithTarget(tool.getName(), pm, tool.getIcon());
|
||||
}
|
||||
List<OpenWithTarget> runningTargets = getRunningTargets(project);
|
||||
return !runningTargets.isEmpty() ? runningTargets.get(0) : null;
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final ProgramManager pm;
|
||||
private final Icon icon;
|
||||
|
||||
public OpenWithTarget(String name, ProgramManager pm, Icon icon) {
|
||||
this.name = name;
|
||||
this.pm = pm;
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ProgramManager getPm() {
|
||||
return pm;
|
||||
}
|
||||
|
||||
public Icon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the specified files, using whatever program manager / tool this instance represents.
|
||||
* <p>
|
||||
* The first item in the list of files will be focused / made visible, the other items in the
|
||||
* list will be opened but not focused.
|
||||
*
|
||||
* @param files {@link DomainFile}s to open
|
||||
*/
|
||||
public void open(List<DomainFile> files) {
|
||||
Project project = AppInfo.getActiveProject();
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
if (pm != null) {
|
||||
openWithPM(files);
|
||||
}
|
||||
else {
|
||||
openWithToolTemplate(project, files);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<DomainFile, Program> openWithPM(List<DomainFile> files) {
|
||||
Map<DomainFile, Program> results = new HashMap<>();
|
||||
for (DomainFile file : files) {
|
||||
int openMode =
|
||||
results.isEmpty() ? ProgramManager.OPEN_CURRENT : ProgramManager.OPEN_VISIBLE;
|
||||
Program program = pm.openProgram(file, DomainFile.DEFAULT_VERSION, openMode);
|
||||
if (program != null) {
|
||||
results.put(file, program);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private Map<DomainFile, Program> openWithToolTemplate(Project project, List<DomainFile> files) {
|
||||
Map<DomainFile, Program> results = new HashMap<>();
|
||||
|
||||
PluginTool newTool = project.getToolServices().launchTool(name, files);
|
||||
|
||||
ProgramManager newToolPM;
|
||||
if (newTool != null && (newToolPM = newTool.getService(ProgramManager.class)) != null) {
|
||||
Set<DomainFile> fileSet = new HashSet<>(files);
|
||||
for (Program openProgram : newToolPM.getAllOpenPrograms()) {
|
||||
if (fileSet.contains(openProgram.getDomainFile())) {
|
||||
results.put(openProgram.getDomainFile(), openProgram);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
private static List<OpenWithTarget> getRunningTargets(Project project) {
|
||||
List<OpenWithTarget> results = new ArrayList<>();
|
||||
for (PluginTool runningTool : project.getToolManager().getRunningTools()) {
|
||||
ProgramManager runningPM = runningTool.getService(ProgramManager.class);
|
||||
if (runningPM != null) {
|
||||
Program currentProgram = runningPM.getCurrentProgram();
|
||||
int programCount = runningPM.getAllOpenPrograms().length;
|
||||
String descName = runningTool.getName();
|
||||
if (currentProgram != null) {
|
||||
descName += ": " + currentProgram.getName();
|
||||
if (programCount > 1) {
|
||||
descName += " (+%d more)".formatted(programCount - 1);
|
||||
}
|
||||
}
|
||||
results.add(new OpenWithTarget(descName, runningPM, runningTool.getIcon()));
|
||||
}
|
||||
}
|
||||
Collections.sort(results, (r1, r2) -> r2.name.compareTo(r1.name));
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
@ -33,7 +33,6 @@ import docking.options.editor.FontEditor;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import generic.theme.*;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.FixedSizeStack;
|
||||
@ -59,8 +58,9 @@ public class TextEditorComponentProvider extends ComponentProviderAdapter {
|
||||
private FixedSizeStack<UndoableEdit> undoStack = new FixedSizeStack<>(MAX_UNDO_REDO_SIZE);
|
||||
private FixedSizeStack<UndoableEdit> redoStack = new FixedSizeStack<>(MAX_UNDO_REDO_SIZE);
|
||||
|
||||
TextEditorComponentProvider(String textFileName, String text) {
|
||||
super(AppInfo.getFrontEndTool(), TITLE, "TextEditorComponentProvider");
|
||||
public TextEditorComponentProvider(FileSystemBrowserPlugin plugin, String textFileName,
|
||||
String text) {
|
||||
super(plugin.getTool(), TITLE, "TextEditorComponentProvider");
|
||||
this.textFileName = textFileName;
|
||||
initialize(text);
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.plugin.importer.ImporterUtilities;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class AddToProgramFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder("FSB Add To Program", context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getLoadableFSRL() != null)
|
||||
.popupMenuIcon(FSBIcons.IMPORT)
|
||||
.popupMenuPath("Add To Program")
|
||||
.popupMenuGroup("F", "C")
|
||||
.onAction(ac -> {
|
||||
FSRL fsrl = ac.getLoadableFSRL();
|
||||
if (fsrl == null) {
|
||||
return;
|
||||
}
|
||||
OpenWithTarget openWith =
|
||||
OpenWithTarget.getRunningProgramManager(context.plugin().getTool());
|
||||
if (openWith == null || openWith.getPm().getCurrentProgram() == null) {
|
||||
Msg.showError(this, ac.getSourceComponent(), "Unable To Add To Program",
|
||||
"No programs are open");
|
||||
return;
|
||||
}
|
||||
|
||||
FSBComponentProvider fsbComp = ac.getComponentProvider();
|
||||
Program program = openWith.getPm().getCurrentProgram();
|
||||
if (program != null) {
|
||||
fsbComp.runTask(monitor -> {
|
||||
if (fsbComp.ensureFileAccessable(fsrl, ac.getSelectedNode(), monitor)) {
|
||||
ImporterUtilities.showAddToProgramDialog(fsrl, program,
|
||||
fsbComp.getTool(), monitor);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.plugins.importer.batch.BatchImportDialog;
|
||||
|
||||
public class BatchImportFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder("FSB Import Batch", context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getSelectedCount() > 0)
|
||||
.popupMenuIcon(FSBIcons.IMPORT)
|
||||
.popupMenuPath("Batch Import")
|
||||
.popupMenuGroup("F", "B")
|
||||
.onAction(ac -> {
|
||||
// Do some fancy selection logic.
|
||||
// If the user selected a combination of files and folders,
|
||||
// ignore the folders.
|
||||
// If they only selected folders, leave them in the list.
|
||||
List<FSRL> files = ac.getFSRLs(true);
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allDirs = ac.isSelectedAllDirs();
|
||||
if (files.size() > 1 && !allDirs) {
|
||||
files = ac.getFileFSRLs();
|
||||
}
|
||||
|
||||
PluginTool tool = context.plugin().getTool();
|
||||
OpenWithTarget openWith = OpenWithTarget.getDefault(tool);
|
||||
BatchImportDialog.showAndImport(tool, null, files, null, openWith.getPm());
|
||||
})
|
||||
.build());
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.formats.gfilesystem.crypto.CachedPasswordProvider;
|
||||
import ghidra.formats.gfilesystem.crypto.CryptoProviders;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class ClearCachedPwdFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder("FSB Clear Cached Passwords", context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(FSBActionContext::notBusy)
|
||||
.popupMenuPath("Clear Cached Passwords")
|
||||
.popupMenuGroup("Z", "B")
|
||||
.description("Clear cached container file passwords")
|
||||
.onAction(ac -> {
|
||||
CachedPasswordProvider ccp =
|
||||
CryptoProviders.getInstance().getCachedCryptoProvider();
|
||||
int preCount = ccp.getCount();
|
||||
ccp.clearCache();
|
||||
|
||||
String msg =
|
||||
"Cleared %d cached passwords.".formatted(preCount - ccp.getCount());
|
||||
|
||||
Msg.info(this, msg);
|
||||
context.fsbComponent().getPlugin().getTool().setStatusInfo(msg);
|
||||
})
|
||||
.build());
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
|
||||
public class CloseFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
public static final String FSB_CLOSE = "FSB Close";
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder(FSB_CLOSE, context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getSelectedNode() instanceof FSBRootNode)
|
||||
.description("Close")
|
||||
.toolBarIcon(FSBIcons.CLOSE)
|
||||
.toolBarGroup("ZZZZ")
|
||||
.popupMenuIcon(FSBIcons.CLOSE)
|
||||
.popupMenuPath("Close")
|
||||
.popupMenuGroup("ZZZZ")
|
||||
.onAction(ac -> {
|
||||
FSBNode selectedNode = ac.getSelectedNode();
|
||||
if (!(selectedNode instanceof FSBRootNode node)) {
|
||||
return;
|
||||
}
|
||||
if (node.getParent() == null) {
|
||||
// Close entire window
|
||||
if (OptionDialog.showYesNoDialog(ac.getSourceComponent(),
|
||||
"Close File System",
|
||||
"Do you want to close the filesystem browser for %s?"
|
||||
.formatted(node.getName())) == OptionDialog.YES_OPTION) {
|
||||
ac.getComponentProvider().componentHidden(); // cause component to close itself
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Close file system that is nested in the container's tree and swap
|
||||
// in the saved node that was the original container file
|
||||
ac.getComponentProvider()
|
||||
.runTask(monitor -> node.swapBackPrevModelNodeAndDispose());
|
||||
}
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.tree.GTree;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.plugins.fsbrowser.tasks.GFileSystemExtractAllTask;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ExportFSBFileHandler implements FSBFileHandler {
|
||||
public static final String FSB_EXPORT_ALL = "FSB Export All";
|
||||
public static final String FSB_EXPORT = "FSB Export";
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder(FSB_EXPORT, context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getFileFSRL() != null)
|
||||
.popupMenuIcon(FSBIcons.EXTRACT)
|
||||
.popupMenuPath("Export...")
|
||||
.popupMenuGroup("F", "C")
|
||||
.onAction(ac -> {
|
||||
FSRL fsrl = ac.getFileFSRL();
|
||||
if (fsrl == null) {
|
||||
return;
|
||||
}
|
||||
GTree tree = ac.getTree();
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(tree);
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
|
||||
chooser.setTitle("Select Where To Export File");
|
||||
chooser.setApproveButtonText("Export");
|
||||
chooser.setSelectedFile(
|
||||
new File(context.plugin().getLastExportDirectory(), fsrl.getName()));
|
||||
File selectedFile = chooser.getSelectedFile();
|
||||
chooser.dispose();
|
||||
if (selectedFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedFile.exists()) {
|
||||
int answer = OptionDialog.showYesNoDialog(tree, "Confirm Overwrite",
|
||||
"%s\nThe file already exists.\nDo you want to overwrite it?"
|
||||
.formatted(selectedFile.getAbsolutePath()));
|
||||
if (answer == OptionDialog.NO_OPTION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
context.plugin().setLastExportDirectory(selectedFile.getParentFile());
|
||||
tree.runTask(
|
||||
monitor -> doExtractFile(fsrl, selectedFile, ac.getSelectedNode(),
|
||||
monitor));
|
||||
})
|
||||
.build(),
|
||||
new ActionBuilder(FSB_EXPORT_ALL, context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.isSelectedAllDirs())
|
||||
.popupMenuIcon(FSBIcons.EXTRACT)
|
||||
.popupMenuPath("Export All...")
|
||||
.popupMenuGroup("F", "C")
|
||||
.onAction(ac -> {
|
||||
FSRL fsrl = ac.getFSRL(true);
|
||||
if (fsrl == null) {
|
||||
return;
|
||||
}
|
||||
GTree tree = ac.getTree();
|
||||
if (fsrl instanceof FSRLRoot) {
|
||||
fsrl = fsrl.appendPath("/");
|
||||
}
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(tree);
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
chooser.setTitle("Select Export Directory");
|
||||
chooser.setApproveButtonText("Export All");
|
||||
chooser.setCurrentDirectory(context.plugin().getLastExportDirectory());
|
||||
File selectedFile = chooser.getSelectedFile();
|
||||
chooser.dispose();
|
||||
if (selectedFile == null) {
|
||||
return;
|
||||
}
|
||||
if (!selectedFile.isDirectory()) {
|
||||
Msg.showInfo(this, tree, "Export All",
|
||||
"Selected file is not a directory.");
|
||||
return;
|
||||
}
|
||||
context.plugin().setLastExportDirectory(selectedFile);
|
||||
|
||||
TaskLauncher.launch(new GFileSystemExtractAllTask(fsrl, selectedFile, tree));
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
private void doExtractFile(FSRL fsrl, File outputFile, FSBNode node, TaskMonitor monitor) {
|
||||
if (!context.fsbComponent().ensureFileAccessable(fsrl, node, monitor)) {
|
||||
return;
|
||||
}
|
||||
monitor.setMessage("Exporting...");
|
||||
try (ByteProvider fileBP = context.fsService().getByteProvider(fsrl, false, monitor)) {
|
||||
monitor.initialize(fileBP.length(), "Exporting %s".formatted(fsrl.getName()));
|
||||
long bytesCopied = FSUtilities.copyByteProviderToFile(fileBP, outputFile, monitor);
|
||||
|
||||
String msg = "Exported %s to %s, %d bytes copied.".formatted(fsrl.getName(), outputFile,
|
||||
bytesCopied);
|
||||
|
||||
context.fsbComponent().getTool().setStatusInfo(msg);
|
||||
Msg.info(this, msg);
|
||||
}
|
||||
catch (IOException | CancelledException | UnsupportedOperationException e) {
|
||||
FSUtilities.displayException(this, context.plugin().getTool().getActiveWindow(),
|
||||
"Error Exporting File", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
|
||||
import static java.util.Map.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.dialogs.MultiLineMessageDialog;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.formats.gfilesystem.fileinfo.*;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class GetInfoFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
public static final String FSB_GET_INFO = "FSB Get Info";
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder(FSB_GET_INFO, context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getFSRL(true) != null)
|
||||
.popupMenuPath("Get Info")
|
||||
.popupMenuGroup("A", "A")
|
||||
.popupMenuIcon(FSBIcons.INFO)
|
||||
.description("Show information about a file")
|
||||
.onAction(ac -> {
|
||||
FSRL fsrl = ac.getFSRL(true);
|
||||
FSBComponentProvider fsbComp = ac.getComponentProvider();
|
||||
fsbComp.runTask(
|
||||
monitor -> showInfoForFile(ac.getSourceComponent(), fsrl, monitor));
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
private void showInfoForFile(Component parentComp, FSRL fsrl, TaskMonitor monitor) {
|
||||
if (fsrl == null) {
|
||||
Msg.showError(this, parentComp, "Missing File", "Unable to retrieve information");
|
||||
return;
|
||||
}
|
||||
|
||||
// if looking at the root of a nested file system, also include its parent container
|
||||
List<FSRL> fsrls = (fsrl instanceof FSRLRoot && ((FSRLRoot) fsrl).hasContainer())
|
||||
? List.of(((FSRLRoot) fsrl).getContainer(), fsrl)
|
||||
: List.of(fsrl);
|
||||
String title = "Info about " + fsrls.get(0).getName();
|
||||
List<FileAttributes> fattrs = new ArrayList<>();
|
||||
for (FSRL fsrl2 : fsrls) {
|
||||
try {
|
||||
fattrs.add(getAttrsFor(fsrl2, monitor));
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.warn(this, "Failed to get info for file " + fsrl2, e);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
String html = getHTMLInfoStringForAttributes(fattrs);
|
||||
|
||||
MultiLineMessageDialog.showMessageDialog(parentComp, title, null, html,
|
||||
MultiLineMessageDialog.INFORMATION_MESSAGE);
|
||||
}
|
||||
|
||||
private FileAttributes getAttrsFor(FSRL fsrl, TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
try (RefdFile refdFile = context.fsService().getRefdFile(fsrl, monitor)) {
|
||||
GFileSystem fs = refdFile.fsRef.getFilesystem();
|
||||
GFile file = refdFile.file;
|
||||
FileAttributes fattrs = fs.getFileAttributes(file, monitor);
|
||||
if (fattrs == null) {
|
||||
fattrs = FileAttributes.EMPTY;
|
||||
}
|
||||
fattrs = fattrs.clone();
|
||||
|
||||
DomainFile associatedDomainFile = context.projectIndex().findFirstByFSRL(fsrl);
|
||||
if (associatedDomainFile != null) {
|
||||
fattrs.add(PROJECT_FILE_ATTR, associatedDomainFile.getPathname());
|
||||
}
|
||||
|
||||
if (!fattrs.contains(NAME_ATTR)) {
|
||||
fattrs.add(NAME_ATTR, file.getName());
|
||||
}
|
||||
if (!fattrs.contains(PATH_ATTR)) {
|
||||
fattrs.add(PATH_ATTR, FilenameUtils.getFullPath(file.getPath()));
|
||||
}
|
||||
if (!fattrs.contains(FSRL_ATTR)) {
|
||||
fattrs.add(FSRL_ATTR, file.getFSRL());
|
||||
}
|
||||
return fattrs;
|
||||
}
|
||||
}
|
||||
|
||||
private String getHTMLInfoStringForAttributes(List<FileAttributes> fileAttributesList) {
|
||||
StringBuilder sb = new StringBuilder("<html>\n<table>\n");
|
||||
sb.append("<tr><th>Property</th><th>Value</th></tr>\n");
|
||||
for (FileAttributes fattrs : fileAttributesList) {
|
||||
if (fattrs != fileAttributesList.get(0)) {
|
||||
// not first element, put a visual divider line
|
||||
sb.append("<tr><td colspan=2><hr></td></tr>");
|
||||
}
|
||||
List<FileAttribute<?>> sortedAttribs = fattrs.getAttributes();
|
||||
Collections.sort(sortedAttribs, (o1, o2) -> Integer
|
||||
.compare(o1.getAttributeType().ordinal(), o2.getAttributeType().ordinal()));
|
||||
|
||||
FileAttributeTypeGroup group = null;
|
||||
for (FileAttribute<?> attr : sortedAttribs) {
|
||||
if (attr.getAttributeType().getGroup() != group) {
|
||||
group = attr.getAttributeType().getGroup();
|
||||
if (group != FileAttributeTypeGroup.GENERAL_INFO) {
|
||||
sb.append("<tr><td><b>")
|
||||
.append(group.getDescriptiveName())
|
||||
.append("</b></td><td><hr></td></tr>\n");
|
||||
}
|
||||
}
|
||||
String valStr =
|
||||
FAT_TOSTRING_FUNCS.getOrDefault(attr.getAttributeType(), PLAIN_TOSTRING)
|
||||
.apply(attr.getAttributeValue());
|
||||
|
||||
String html = HTMLUtilities.escapeHTML(valStr);
|
||||
html = html.replace("\n", "<br>\n");
|
||||
sb.append("<tr><td>")
|
||||
.append(attr.getAttributeDisplayName())
|
||||
.append(":</td><td>")
|
||||
.append(html)
|
||||
.append("</td></tr>\n");
|
||||
}
|
||||
}
|
||||
sb.append("</table>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
// static lookup tables for rendering file attributes
|
||||
//---------------------------------------------------------------------------------------------
|
||||
private static final Function<Object, String> PLAIN_TOSTRING = o -> o.toString();
|
||||
private static final Function<Object, String> SIZE_TOSTRING =
|
||||
o -> (o instanceof Long) ? FSUtilities.formatSize((Long) o) : o.toString();
|
||||
private static final Function<Object, String> UNIX_ACL_TOSTRING =
|
||||
o -> (o instanceof Number) ? String.format("%05o", (Number) o) : o.toString();
|
||||
private static final Function<Object, String> DATE_TOSTRING =
|
||||
o -> (o instanceof Date) ? FSUtilities.formatFSTimestamp((Date) o) : o.toString();
|
||||
private static final Function<Object, String> FSRL_TOSTRING =
|
||||
o -> (o instanceof FSRL) ? ((FSRL) o).toPrettyString().replace("|", "|\n\t") : o.toString();
|
||||
|
||||
private static final Map<FileAttributeType, Function<Object, String>> FAT_TOSTRING_FUNCS =
|
||||
Map.ofEntries(entry(FSRL_ATTR, FSRL_TOSTRING), entry(SIZE_ATTR, SIZE_TOSTRING),
|
||||
entry(COMPRESSED_SIZE_ATTR, SIZE_TOSTRING), entry(CREATE_DATE_ATTR, DATE_TOSTRING),
|
||||
entry(MODIFIED_DATE_ATTR, DATE_TOSTRING), entry(ACCESSED_DATE_ATTR, DATE_TOSTRING),
|
||||
entry(UNIX_ACL_ATTR, UNIX_ACL_TOSTRING));
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.label.GIconLabel;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ImageFSBFileHandler implements FSBFileHandler {
|
||||
public static final String FSB_VIEW_AS_IMAGE = "FSB View As Image";
|
||||
|
||||
private static final Set<String> COMMON_IMAGE_EXTENSIONS = Set.of("png", "jpg", "jpeg", "gif");
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fileDefaultAction(FSBFileNode fileNode) {
|
||||
FSRL fsrl = fileNode.getFSRL();
|
||||
String extension = FilenameUtils.getExtension(fsrl.getName().toLowerCase());
|
||||
if (COMMON_IMAGE_EXTENSIONS.contains(extension)) {
|
||||
FSBComponentProvider fsbComponent = context.fsbComponent();
|
||||
fsbComponent.runTask(monitor -> doViewAsImage(fileNode.getFSRL(),
|
||||
fsbComponent.getComponent(), monitor));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
DockingAction action = new ActionBuilder(FSB_VIEW_AS_IMAGE, context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getFileFSRL() != null)
|
||||
.popupMenuIcon(FSBIcons.VIEW_AS_IMAGE)
|
||||
.popupMenuPath("View As", "Image")
|
||||
.popupMenuGroup("G")
|
||||
.onAction(ac -> {
|
||||
FSRL fsrl = ac.getFileFSRL();
|
||||
if (fsrl != null) {
|
||||
ac.getTree()
|
||||
.runTask(monitor -> doViewAsImage(fsrl, ac.getSourceComponent(),
|
||||
monitor));
|
||||
}
|
||||
})
|
||||
.build();
|
||||
action.getPopupMenuData().setParentMenuGroup("C");
|
||||
|
||||
return List.of(action);
|
||||
}
|
||||
|
||||
void doViewAsImage(FSRL fsrl, Component parent, TaskMonitor monitor) {
|
||||
|
||||
try (RefdFile refdFile = context.fsService().getRefdFile(fsrl, monitor)) {
|
||||
|
||||
Icon icon = GIconProvider.getIconForFile(refdFile.file, monitor);
|
||||
if (icon == null) {
|
||||
Msg.showError(this, parent, "Unable To View Image",
|
||||
"Unable to view " + fsrl.getName() + " as an image.");
|
||||
return;
|
||||
}
|
||||
Swing.runLater(() -> {
|
||||
JLabel label = new GIconLabel(icon);
|
||||
JOptionPane.showMessageDialog(null, label, "Image Viewer: " + fsrl.getName(),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
});
|
||||
}
|
||||
catch (IOException | CancelledException e) {
|
||||
FSUtilities.displayException(this, parent, "Error Viewing Image File", e.getMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.plugin.importer.ImporterUtilities;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
|
||||
public class ImportFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
public static final String FSB_IMPORT_SINGLE = "FSB Import Single";
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder(FSB_IMPORT_SINGLE, context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getLoadableFSRL() != null)
|
||||
.popupMenuIcon(FSBIcons.IMPORT)
|
||||
.popupMenuPath("Import")
|
||||
.popupMenuGroup("F", "A")
|
||||
.onAction(ac -> {
|
||||
FSBNode node = ac.getSelectedNode();
|
||||
FSRL fsrl = node.getLoadableFSRL();
|
||||
if (fsrl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String suggestedPath = FilenameUtils
|
||||
.getFullPathNoEndSeparator(node.getFormattedTreePath())
|
||||
.replaceAll(":/", "/");
|
||||
|
||||
FSBComponentProvider fsbComp = ac.getComponentProvider();
|
||||
FileSystemBrowserPlugin plugin = fsbComp.getPlugin();
|
||||
OpenWithTarget openWith = OpenWithTarget.getDefault(plugin.getTool());
|
||||
|
||||
ac.getTree().runTask(monitor -> {
|
||||
if (!fsbComp.ensureFileAccessable(fsrl, node, monitor)) {
|
||||
return;
|
||||
}
|
||||
ImporterUtilities.showImportSingleFileDialog(fsrl, null, suggestedPath,
|
||||
plugin.getTool(), openWith.getPm(), monitor);
|
||||
});
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.app.util.importer.LibrarySearchPathManager;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class LibrarySearchPathFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder("FSB Add Library Search Path", context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getFSRL(true) != null)
|
||||
.popupMenuPath("Add Library Search Path")
|
||||
.popupMenuGroup("F", "D")
|
||||
.popupMenuIcon(FSBIcons.LIBRARY)
|
||||
.description("Add file/folder to library search paths")
|
||||
.onAction(ac -> {
|
||||
Component parentComp = context.fsbComponent().getComponent();
|
||||
try {
|
||||
FSRL fsrl = ac.getFSRL(true);
|
||||
FileSystemService fsService = context.fsService();
|
||||
LocalFileSystem localFs = fsService.getLocalFS();
|
||||
String path = fsService.isLocal(fsrl)
|
||||
? localFs.getLocalFile(fsrl).getPath()
|
||||
: fsrl.toString();
|
||||
if (LibrarySearchPathManager.addPath(path)) {
|
||||
Msg.showInfo(this, parentComp, "Add Library Search Path",
|
||||
"Added '%s' to library search paths.".formatted(fsrl));
|
||||
}
|
||||
else {
|
||||
Msg.showInfo(this, parentComp, "Add Library Search Path",
|
||||
"Library search path '%s' already exists.".formatted(fsrl));
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, parentComp, "Add Library Search Path", e);
|
||||
}
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.SelectFromListDialog;
|
||||
import ghidra.formats.gfilesystem.FSRLRoot;
|
||||
import ghidra.formats.gfilesystem.FileSystemRef;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
|
||||
public class ListMountedFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder("FSB List Mounted Filesystems", context.plugin().getName())
|
||||
.description("List Mounted Filesystems")
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(FSBActionContext::notBusy)
|
||||
.toolBarIcon(FSBIcons.LIST_MOUNTED)
|
||||
.toolBarGroup("ZZZZ")
|
||||
.popupMenuIcon(FSBIcons.LIST_MOUNTED)
|
||||
.popupMenuPath("List Mounted Filesystems")
|
||||
.popupMenuGroup("L")
|
||||
.onAction(ac -> {
|
||||
FSRLRoot fsFSRL = SelectFromListDialog.selectFromList(
|
||||
context.fsService().getMountedFilesystems(), "Select filesystem",
|
||||
"Choose filesystem to view", f -> f.toPrettyString());
|
||||
|
||||
FileSystemRef fsRef;
|
||||
if (fsFSRL != null &&
|
||||
(fsRef = context.fsService().getMountedFilesystem(fsFSRL)) != null) {
|
||||
context.fsbComponent().getPlugin().createNewFileSystemBrowser(fsRef, true);
|
||||
}
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
|
||||
public class OpenFsFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
public static final String FSB_OPEN_FILE_SYSTEM_CHOOSER = "FSB Open File System Chooser";
|
||||
public static final String FSB_OPEN_FILE_SYSTEM_IN_NEW_WINDOW =
|
||||
"FSB Open File System In New Window";
|
||||
public static final String FSB_OPEN_FILE_SYSTEM_NESTED = "FSB Open File System Nested";
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(
|
||||
new ActionBuilder(FSB_OPEN_FILE_SYSTEM_NESTED, context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() &&
|
||||
ac.getSelectedNode() instanceof FSBFileNode fileNode && fileNode.isLeaf() &&
|
||||
!fileNode.isSymlink())
|
||||
.popupMenuIcon(FSBIcons.OPEN_FILE_SYSTEM)
|
||||
.popupMenuPath("Open File System")
|
||||
.popupMenuGroup("C")
|
||||
.onAction(
|
||||
ac -> ac.getComponentProvider().openFileSystem(ac.getSelectedNode(), true))
|
||||
.build(),
|
||||
|
||||
new ActionBuilder(FSB_OPEN_FILE_SYSTEM_IN_NEW_WINDOW, context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() &&
|
||||
ac.getSelectedNode() instanceof FSBFileNode fileNode && fileNode.isLeaf() &&
|
||||
!fileNode.isSymlink())
|
||||
.popupMenuIcon(FSBIcons.OPEN_FILE_SYSTEM)
|
||||
.popupMenuPath("Open File System in new window")
|
||||
.popupMenuGroup("C")
|
||||
.onAction(
|
||||
ac -> ac.getComponentProvider().openFileSystem(ac.getSelectedNode(), false))
|
||||
.build(),
|
||||
|
||||
new ActionBuilder(FSB_OPEN_FILE_SYSTEM_CHOOSER, context.plugin().getName())
|
||||
.description("Open File System Chooser")
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(FSBActionContext::notBusy)
|
||||
.toolBarIcon(FSBIcons.OPEN_FILE_SYSTEM)
|
||||
.toolBarGroup("B")
|
||||
.onAction(ac -> context.plugin().openFileSystem())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.plugin.importer.ProjectIndexService;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
|
||||
public class OpenWithFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> getPopupProviderActions() {
|
||||
FileSystemBrowserPlugin plugin = context.plugin();
|
||||
List<DockingAction> results = new ArrayList<>();
|
||||
for (OpenWithTarget target : OpenWithTarget.getAll()) {
|
||||
DockingAction action =
|
||||
new ActionBuilder("FSB Open With " + target.getName(), plugin.getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.hasSelectedLinkedNodes())
|
||||
.popupMenuIcon(target.getIcon())
|
||||
.popupMenuPath("Open With", target.getName())
|
||||
.popupMenuGroup(target.getPm() != null ? "A" : "B") // list running targets first
|
||||
.onAction(ac -> {
|
||||
FSBComponentProvider fsbComp = ac.getComponentProvider();
|
||||
ProjectIndexService projectIndex = fsbComp.getProjectIndex();
|
||||
List<DomainFile> filesToOpen = ac.getSelectedNodes()
|
||||
.stream()
|
||||
.map(node -> projectIndex.findFirstByFSRL(node.getFSRL()))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
target.open(filesToOpen);
|
||||
})
|
||||
.build();
|
||||
action.getPopupMenuData().setParentMenuGroup("C");
|
||||
results.add(action);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.tree.GTree;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class RefreshFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder("FSB Refresh", context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.hasSelectedNodes())
|
||||
.popupMenuPath("Refresh")
|
||||
.popupMenuGroup("Z", "Z")
|
||||
.popupMenuIcon(FSBIcons.REFRESH)
|
||||
.toolBarIcon(FSBIcons.REFRESH)
|
||||
.description("Refresh file info")
|
||||
.onAction(ac -> ac.getComponentProvider()
|
||||
.runTask(
|
||||
monitor -> doRefreshInfo(ac.getSelectedNodes(), ac.getTree(), monitor)))
|
||||
.build());
|
||||
}
|
||||
|
||||
void doRefreshInfo(List<FSBNode> nodes, GTree gTree, TaskMonitor monitor) {
|
||||
try {
|
||||
for (FSBNode node : nodes) {
|
||||
node.refreshNode(monitor);
|
||||
}
|
||||
|
||||
gTree.refilterLater(); // force the changed modelNodes to be recloned and displayed (if filter active)
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// stop
|
||||
}
|
||||
Swing.runLater(() -> gTree.repaint());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser.filehandlers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.formats.gfilesystem.FSUtilities;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
public class TextFSBFileHandler implements FSBFileHandler {
|
||||
public static final String FSB_VIEW_AS_TEXT = "FSB View As Text";
|
||||
|
||||
private static final int MAX_TEXT_FILE_LEN = 64 * 1024;
|
||||
|
||||
FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fileDefaultAction(FSBFileNode fileNode) {
|
||||
if (fileNode.getName().toLowerCase().endsWith(".txt")) {
|
||||
FSBComponentProvider fsbComponent = context.fsbComponent();
|
||||
fsbComponent.runTask(
|
||||
monitor -> doViewAsText(fileNode.getFSRL(), fsbComponent.getComponent(), monitor));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
DockingAction action = new ActionBuilder(FSB_VIEW_AS_TEXT, context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getFileFSRL() != null)
|
||||
.popupMenuIcon(FSBIcons.VIEW_AS_TEXT)
|
||||
.popupMenuPath("View As", "Text")
|
||||
.popupMenuGroup("G")
|
||||
.onAction(ac -> {
|
||||
if (ac.getSelectedNode() instanceof FSBFileNode fileNode &&
|
||||
fileNode.getFSRL() != null) {
|
||||
ac.getTree()
|
||||
.runTask(monitor -> doViewAsText(fileNode.getFSRL(),
|
||||
ac.getSourceComponent(), monitor));
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
action.getPopupMenuData().setParentMenuGroup("C");
|
||||
return List.of(action);
|
||||
|
||||
}
|
||||
|
||||
void doViewAsText(FSRL fsrl, Component parent, TaskMonitor monitor) {
|
||||
try (ByteProvider fileBP = context.fsService().getByteProvider(fsrl, false, monitor)) {
|
||||
|
||||
if (fileBP.length() > MAX_TEXT_FILE_LEN) {
|
||||
Msg.showInfo(this, context.fsbComponent().getComponent(), "View As Text Failed",
|
||||
"File too large to view as text inside Ghidra. " +
|
||||
"Please use the \"EXPORT\" action.");
|
||||
return;
|
||||
}
|
||||
|
||||
try (InputStream is = fileBP.getInputStream(0)) {
|
||||
String text = FileUtilities.getText(is);
|
||||
Swing.runLater(() -> {
|
||||
new TextEditorComponentProvider(context.plugin(), fsrl.getName(), text);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (IOException | CancelledException e) {
|
||||
FSUtilities.displayException(this, parent, "Error Viewing Text File",
|
||||
"Error when trying to view text file %s".formatted(fsrl.getName()), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -30,7 +30,6 @@ import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.plugin.importer.ProgramMappingService;
|
||||
import ghidra.plugins.importer.batch.*;
|
||||
import ghidra.plugins.importer.batch.BatchGroup.BatchLoadConfig;
|
||||
import ghidra.program.model.listing.Program;
|
||||
@ -205,8 +204,6 @@ public class ImportBatchTask extends Task {
|
||||
totalObjsImported == 0 ? ProgramManager.OPEN_CURRENT
|
||||
: ProgramManager.OPEN_VISIBLE);
|
||||
}
|
||||
|
||||
ProgramMappingService.createAssociation(appInfo.getFSRL(), program);
|
||||
}
|
||||
totalObjsImported++;
|
||||
}
|
||||
|
@ -17,9 +17,12 @@ package ghidra.plugins.fsbrowser;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
@ -27,12 +30,14 @@ import org.junit.Test;
|
||||
import docking.test.AbstractDockingTest;
|
||||
import generic.theme.GIcon;
|
||||
import resources.MultiIcon;
|
||||
import resources.ResourceManager;
|
||||
|
||||
public class FileIconServiceTest extends AbstractDockingTest {
|
||||
public class FSBIconsTest extends AbstractDockingTest {
|
||||
|
||||
FSBIcons fis = FSBIcons.getInstance();
|
||||
|
||||
@Test
|
||||
public void testGetIcon() {
|
||||
FileIconService fis = FileIconService.getInstance();
|
||||
Icon icon = fis.getIcon("blah.txt", null);
|
||||
Assert.assertNotNull(icon);
|
||||
assertTrue(icon instanceof GIcon);
|
||||
@ -42,8 +47,7 @@ public class FileIconServiceTest extends AbstractDockingTest {
|
||||
|
||||
@Test
|
||||
public void testGetOverlayIcon() {
|
||||
FileIconService fis = FileIconService.getInstance();
|
||||
Icon icon = fis.getIcon("blah.txt", List.of(FileIconService.FILESYSTEM_OVERLAY_ICON));
|
||||
Icon icon = fis.getIcon("blah.txt", List.of(FSBIcons.FILESYSTEM_OVERLAY_ICON));
|
||||
Assert.assertNotNull(icon);
|
||||
assertTrue(icon instanceof MultiIcon);
|
||||
MultiIcon multiIcon = (MultiIcon) icon;
|
||||
@ -54,7 +58,6 @@ public class FileIconServiceTest extends AbstractDockingTest {
|
||||
|
||||
@Test
|
||||
public void testGetSubstringIcon() {
|
||||
FileIconService fis = FileIconService.getInstance();
|
||||
Icon icon = fis.getIcon("blah.release.abcx.123", null);
|
||||
Assert.assertNotNull(icon);
|
||||
assertTrue(icon instanceof GIcon);
|
||||
@ -64,8 +67,27 @@ public class FileIconServiceTest extends AbstractDockingTest {
|
||||
|
||||
@Test
|
||||
public void testNoMatch() {
|
||||
FileIconService fis = FileIconService.getInstance();
|
||||
Icon icon = fis.getIcon("aaaaaaaa.bbbbbbbb.cccccccc", null);
|
||||
assertEquals(FileIconService.DEFAULT_ICON, icon);
|
||||
assertEquals(FSBIcons.DEFAULT_ICON, icon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImageManagerLoadedIconResources()
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
|
||||
ImageIcon defaultIcon = ResourceManager.getDefaultIcon();
|
||||
|
||||
Set<String> failedIcons = new HashSet<>();
|
||||
for (Field field : FSBIcons.class.getDeclaredFields()) {
|
||||
if (Modifier.isStatic(field.getModifiers()) &&
|
||||
field.getType().equals(ImageIcon.class)) {
|
||||
Object fieldValue = field.get(null);
|
||||
if (fieldValue == null || fieldValue == defaultIcon) {
|
||||
failedIcons.add(field.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.assertTrue("Some icons failed to load or misconfigured: " + failedIcons.toString(),
|
||||
failedIcons.isEmpty());
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/* ###
|
||||
* 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.plugins.fsbrowser;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import resources.ResourceManager;
|
||||
|
||||
public class ImageManagerTest extends AbstractGenericTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testImageManagerLoadedIconResources()
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
|
||||
ImageIcon defaultIcon = ResourceManager.getDefaultIcon();
|
||||
|
||||
Set<String> failedIcons = new HashSet<>();
|
||||
for (Field field : ImageManager.class.getDeclaredFields()) {
|
||||
if (Modifier.isStatic(field.getModifiers()) &&
|
||||
field.getType().equals(ImageIcon.class)) {
|
||||
Object fieldValue = field.get(null);
|
||||
if (fieldValue == null || fieldValue == defaultIcon) {
|
||||
failedIcons.add(field.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.assertTrue("Some icons failed to load or misconfigured: " + failedIcons.toString(),
|
||||
failedIcons.isEmpty());
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
Decryptor
|
||||
FileSystem
|
||||
FileSystemModel
|
||||
FSBFileHandler
|
||||
|
@ -0,0 +1,109 @@
|
||||
/* ###
|
||||
* 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.file.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.formats.gfilesystem.FSUtilities;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
|
||||
public class CryptoKeysFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List
|
||||
.of(new ActionBuilder("FSB Create Crypto Key Template", context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() &&
|
||||
ac.getSelectedNode() instanceof FSBRootNode && ac.getFSRL(true) != null)
|
||||
.popupMenuPath("Create Crypto Key Template...")
|
||||
.popupMenuGroup("Z", "B")
|
||||
.onAction(ac -> {
|
||||
FSRL fsrl = ac.getFSRL(true);
|
||||
if (ac.getSelectedNode() instanceof FSBRootNode rootNode &&
|
||||
fsrl != null) {
|
||||
createCryptoTemplate(fsrl, rootNode);
|
||||
}
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a crypto key file template based on the specified files under the GTree node.
|
||||
*
|
||||
* @param fsrl FSRL of a child file of the container that the crypto will be associated with
|
||||
* @param node GTree node with children that will be iterated
|
||||
*/
|
||||
private void createCryptoTemplate(FSRL fsrl, FSBRootNode node) {
|
||||
try {
|
||||
String fsContainerName = fsrl.getFS().getContainer().getName();
|
||||
CryptoKeyFileTemplateWriter writer = new CryptoKeyFileTemplateWriter(fsContainerName);
|
||||
if (writer.exists()) {
|
||||
int answer =
|
||||
OptionDialog.showYesNoDialog(null, "WARNING!! Crypto Key File Already Exists",
|
||||
"WARNING!!" + "\n" + "The crypto key file already exists. " +
|
||||
"Are you really sure that you want to overwrite it?");
|
||||
if (answer == OptionDialog.NO_OPTION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
writer.open();
|
||||
try {
|
||||
// gTree.expandAll( node );
|
||||
writeFile(writer, node.getChildren());
|
||||
}
|
||||
finally {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
FSUtilities.displayException(this, null, "Error writing crypt key file", e.getMessage(),
|
||||
e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void writeFile(CryptoKeyFileTemplateWriter writer, List<GTreeNode> children)
|
||||
throws IOException {
|
||||
|
||||
if (children == null || children.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (GTreeNode child : children) {
|
||||
if (child instanceof FSBFileNode fileNode) {
|
||||
FSRL childFSRL = fileNode.getFSRL();
|
||||
writer.write(childFSRL.getName());
|
||||
}
|
||||
else {
|
||||
writeFile(writer, child.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/* ###
|
||||
* 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.file.formats.android.apk;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import ghidra.file.eclipse.AndroidProjectCreator;
|
||||
import ghidra.file.jad.JadProcessWrapper;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ApkFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
private File lastDirectory;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private static boolean isAPK(FSRL fsrl) {
|
||||
return (fsrl != null) && (fsrl.getName() != null) &&
|
||||
"apk".equalsIgnoreCase(FilenameUtils.getExtension(fsrl.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder("FSB Export Eclipse Project", context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && JadProcessWrapper.isJadPresent() &&
|
||||
isAPK(ac.getFileFSRL()))
|
||||
.popupMenuPath("Export Eclipse Project")
|
||||
.popupMenuIcon(FSBIcons.ECLIPSE)
|
||||
.popupMenuGroup("H")
|
||||
.onAction(ac -> {
|
||||
FSRL fsrl = ac.getFileFSRL();
|
||||
if (fsrl == null) {
|
||||
Msg.info(this, "Unable to export eclipse project");
|
||||
return;
|
||||
}
|
||||
|
||||
lastDirectory = lastDirectory == null
|
||||
? new File(System.getProperty("user.home"))
|
||||
: lastDirectory;
|
||||
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(ac.getSourceComponent());
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
chooser.setTitle("Select Eclipse Project Directory");
|
||||
chooser.setApproveButtonText("SELECT");
|
||||
chooser.setCurrentDirectory(context.plugin().getLastExportDirectory());
|
||||
File selectedFile = chooser.getSelectedFile();
|
||||
chooser.dispose();
|
||||
if (selectedFile == null) {
|
||||
return;
|
||||
}
|
||||
lastDirectory = selectedFile;
|
||||
|
||||
ac.getComponentProvider()
|
||||
.runTask(monitor -> doExportToEclipse(fsrl, lastDirectory, monitor));
|
||||
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
private void doExportToEclipse(FSRL fsrl, File outputDirectory, TaskMonitor monitor) {
|
||||
try (RefdFile refdFile = FileSystemService.getInstance().getRefdFile(fsrl, monitor)) {
|
||||
AndroidProjectCreator creator =
|
||||
new AndroidProjectCreator(refdFile.file.getFSRL(), outputDirectory);
|
||||
creator.create(monitor);
|
||||
|
||||
if (creator.getLog().hasMessages()) {
|
||||
Msg.showInfo(this, null, "Export to Eclipse Project", creator.getLog().toString());
|
||||
}
|
||||
}
|
||||
catch (IOException | CancelledException e) {
|
||||
FSUtilities.displayException(this, null, "Error Exporting to Eclipse", e.getMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -106,7 +106,7 @@ public class BootImageFileSystem extends GFileSystemBase {
|
||||
FileAttribute.create(FileAttributeType.COMMENT_ATTR,
|
||||
"This is a second stage loader file. It appears unused at this time."));
|
||||
}
|
||||
return null;
|
||||
return FileAttributes.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -16,21 +16,14 @@
|
||||
package ghidra.file.formats.android.bootimg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.app.util.bin.ByteProviderWrapper;
|
||||
import ghidra.formats.gfilesystem.GFile;
|
||||
import ghidra.formats.gfilesystem.GFileImpl;
|
||||
import ghidra.formats.gfilesystem.GFileSystemBase;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
|
||||
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
|
||||
import ghidra.formats.gfilesystem.fileinfo.FileAttribute;
|
||||
import ghidra.formats.gfilesystem.fileinfo.FileAttributeType;
|
||||
import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
|
||||
import ghidra.formats.gfilesystem.fileinfo.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.CryptoException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@ -121,7 +114,7 @@ public class VendorBootImageFileSystem extends GFileSystemBase {
|
||||
FileAttribute.create(FileAttributeType.COMMENT_ATTR,
|
||||
"This is a DTB file. It appears unused at this time."));
|
||||
}
|
||||
return null;
|
||||
return FileAttributes.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -74,7 +74,7 @@ public class ProfileFileSystem extends GFileSystemBase {
|
||||
if (file == dataFile) {
|
||||
return FileAttributes.of(FileAttribute.create("Magic", header.getMagic()));
|
||||
}
|
||||
return null;
|
||||
return FileAttributes.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.plugins.fsbrowser.tasks;
|
||||
package ghidra.file.formats.ios.prelink;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@ -21,10 +21,9 @@ import java.util.List;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.model.ProjectDataUtils;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.plugin.importer.ProgramMappingService;
|
||||
import ghidra.plugin.importer.ProjectIndexService;
|
||||
import ghidra.program.model.lang.LanguageService;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.DefaultLanguageService;
|
||||
@ -115,21 +114,20 @@ public class GFileSystemLoadKernelTask extends Task {
|
||||
}
|
||||
monitor.setMessage("Opening " + file.getName());
|
||||
|
||||
Program program = ProgramMappingService.findMatchingProgramOpenIfNeeded(file.getFSRL(),
|
||||
this, programManager, ProgramManager.OPEN_VISIBLE);
|
||||
if (program != null) {
|
||||
program.release(this);
|
||||
ProjectIndexService projectIndex = ProjectIndexService.getInstance();
|
||||
DomainFile existingDF = projectIndex.findFirstByFSRL(file.getFSRL());
|
||||
if ( existingDF != null && programManager != null ) {
|
||||
programManager.openProgram(existingDF);
|
||||
return;
|
||||
}
|
||||
|
||||
//File cacheFile = FileSystemService.getInstance().getFile(file.getFSRL(), monitor);
|
||||
|
||||
if (file.getFilesystem() instanceof GFileSystemProgramProvider) {
|
||||
Program program = null;
|
||||
if (file.getFilesystem() instanceof GFileSystemProgramProvider programProviderFS) {
|
||||
LanguageService languageService = DefaultLanguageService.getLanguageService();
|
||||
|
||||
GFileSystemProgramProvider fileSystem =
|
||||
(GFileSystemProgramProvider) file.getFilesystem();
|
||||
program = fileSystem.getProgram(file, languageService, monitor, this);
|
||||
program = programProviderFS.getProgram(file, languageService, monitor, this);
|
||||
}
|
||||
|
||||
if (program != null) {
|
||||
@ -144,7 +142,6 @@ public class GFileSystemLoadKernelTask extends Task {
|
||||
folder.createFile(fileName, program, monitor);
|
||||
|
||||
programManager.openProgram(program);
|
||||
ProgramMappingService.createAssociation(file.getFSRL(), program);
|
||||
}
|
||||
finally {
|
||||
program.release(this);
|
@ -0,0 +1,100 @@
|
||||
/* ###
|
||||
* 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.file.formats.ios.prelink;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
|
||||
public class MachoPrelinkFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder("FSB Load iOS Kernel", context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> {
|
||||
if (ac.isBusy() || ac.getSelectedNode() == null) {
|
||||
return false;
|
||||
}
|
||||
FSBRootNode rootNode = ac.getSelectedNode().getFSBRootNode();
|
||||
return rootNode != null && rootNode.getFSRef() != null &&
|
||||
rootNode.getFSRef().getFilesystem() instanceof MachoPrelinkFileSystem;
|
||||
})
|
||||
.popupMenuPath("Load iOS Kernel")
|
||||
.popupMenuIcon(FSBIcons.iOS)
|
||||
.popupMenuGroup("I")
|
||||
.onAction(ac -> {
|
||||
FSRL fsrl = ac.getFSRL(true);
|
||||
List<FSRL> fileList = new ArrayList<>();
|
||||
|
||||
if (fsrl != null) {
|
||||
FSBNode selectedNode = ac.getSelectedNode();
|
||||
if (selectedNode instanceof FSBRootNode) {
|
||||
for (GTreeNode childNode : ac.getSelectedNode().getChildren()) {
|
||||
if (childNode instanceof FSBNode baseNode) {
|
||||
fileList.add(baseNode.getFSRL());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selectedNode instanceof FSBFileNode ||
|
||||
selectedNode instanceof FSBDirNode) {
|
||||
fileList.add(fsrl);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileList.isEmpty()) {
|
||||
if (OptionDialog.showYesNoDialog(null, "Load iOS Kernel?",
|
||||
"Performing this action will load the entire kernel and all KEXT files.\n" +
|
||||
"Do you want to continue?") == OptionDialog.YES_OPTION) {
|
||||
loadIOSKernel(fileList);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ac.getComponentProvider()
|
||||
.getPlugin()
|
||||
.getTool()
|
||||
.setStatusInfo("Load iOS kernel -- nothing to do.");
|
||||
}
|
||||
})
|
||||
.build()
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
private void loadIOSKernel(List<FSRL> fileList) {
|
||||
FileSystemBrowserPlugin fsbPlugin = context.plugin();
|
||||
OpenWithTarget openWith = OpenWithTarget.getRunningProgramManager(fsbPlugin.getTool());
|
||||
if (openWith.getPm() != null) {
|
||||
TaskLauncher
|
||||
.launch(new GFileSystemLoadKernelTask(fsbPlugin, openWith.getPm(), fileList));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -35,8 +35,14 @@ public class SquashFileSystemFactory
|
||||
throws IOException, CancelledException {
|
||||
|
||||
SquashFileSystem fs = new SquashFileSystem(targetFSRL, byteProvider, fsService);
|
||||
fs.mount(monitor);
|
||||
return fs;
|
||||
try {
|
||||
fs.mount(monitor);
|
||||
return fs;
|
||||
}
|
||||
catch (IOException e) {
|
||||
FSUtilities.uncheckedClose(fs, null);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -109,7 +109,7 @@ public class TarFileSystem extends AbstractFileSystem<TarMetadata> {
|
||||
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
|
||||
TarMetadata tmd = fsIndex.getMetadata(file);
|
||||
if (tmd == null) {
|
||||
return null;
|
||||
return FileAttributes.EMPTY;
|
||||
}
|
||||
TarArchiveEntry blob = tmd.tarArchiveEntry;
|
||||
return FileAttributes.of(
|
||||
|
@ -95,7 +95,7 @@ public class ZipFileSystemBuiltin extends AbstractFileSystem<ZipEntry> {
|
||||
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
|
||||
ZipEntry zipEntry = fsIndex.getMetadata(file);
|
||||
if (zipEntry == null) {
|
||||
return null;
|
||||
return FileAttributes.EMPTY;
|
||||
}
|
||||
FileAttributes result = new FileAttributes();
|
||||
|
||||
|
@ -0,0 +1,92 @@
|
||||
/* ###
|
||||
* 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.file.jad;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.formats.gfilesystem.FSUtilities;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class JadFSBFileHandler implements FSBFileHandler {
|
||||
|
||||
private FSBFileHandlerContext context;
|
||||
private File lastDirectory;
|
||||
|
||||
@Override
|
||||
public void init(FSBFileHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingAction> createActions() {
|
||||
return List.of(new ActionBuilder("FSB Decompile JAR", context.plugin().getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && JadProcessWrapper.isJadPresent() &&
|
||||
ac.getFileFSRL() != null)
|
||||
.popupMenuPath("Decompile JAR")
|
||||
.popupMenuIcon(FSBIcons.JAR)
|
||||
.popupMenuGroup("J")
|
||||
.onAction(ac -> {
|
||||
FSRL jarFSRL = ac.getFileFSRL();
|
||||
if (jarFSRL == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastDirectory = lastDirectory == null
|
||||
? new File(System.getProperty("user.home"))
|
||||
: lastDirectory;
|
||||
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(ac.getSourceComponent());
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
chooser.setTitle("Select JAR Output Directory");
|
||||
chooser.setApproveButtonText("SELECT");
|
||||
chooser.setCurrentDirectory(context.plugin().getLastExportDirectory());
|
||||
File selectedFile = chooser.getSelectedFile();
|
||||
chooser.dispose();
|
||||
if (selectedFile == null) {
|
||||
return;
|
||||
}
|
||||
lastDirectory = selectedFile;
|
||||
|
||||
context.fsbComponent().runTask(monitor -> {
|
||||
try {
|
||||
JarDecompiler decompiler = new JarDecompiler(jarFSRL, selectedFile);
|
||||
decompiler.decompile(monitor);
|
||||
|
||||
if (decompiler.getLog().hasMessages()) {
|
||||
Msg.showInfo(this, null, "Decompiling Jar " + jarFSRL.getName(),
|
||||
decompiler.getLog().toString());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
FSUtilities.displayException(this, null, "Error Decompiling Jar",
|
||||
e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
})
|
||||
.build()
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,335 +0,0 @@
|
||||
/* ###
|
||||
* 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.plugins.fileformats;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.tree.GTree;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.file.crypto.CryptoKeyFileTemplateWriter;
|
||||
import ghidra.file.eclipse.AndroidProjectCreator;
|
||||
import ghidra.file.formats.ios.prelink.MachoPrelinkFileSystem;
|
||||
import ghidra.file.jad.JadProcessWrapper;
|
||||
import ghidra.file.jad.JarDecompiler;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.framework.main.ApplicationLevelPlugin;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.plugins.fsbrowser.*;
|
||||
import ghidra.plugins.fsbrowser.tasks.GFileSystemLoadKernelTask;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A plugin that adds file format related actions to the file system browser.
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.COMMON,
|
||||
shortDescription = "File format actions",
|
||||
description = "This plugin provides file format related actions to the File System Browser."
|
||||
)
|
||||
//@formatter:on
|
||||
public class FileFormatsPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||
|
||||
private GhidraFileChooser chooserEclipse;
|
||||
private GhidraFileChooser chooserJarFolder;
|
||||
|
||||
private List<DockingAction> actions = new ArrayList<>();
|
||||
|
||||
public FileFormatsPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
actions.add(createEclipseProjectAction());
|
||||
actions.add(createDecompileJarAction());
|
||||
actions.add(createCryptoTemplateAction());
|
||||
actions.add(createLoadKernelAction());
|
||||
|
||||
actions.forEach(action -> getTool().addAction(action));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (chooserJarFolder != null) {
|
||||
chooserJarFolder.dispose();
|
||||
}
|
||||
|
||||
if (chooserEclipse != null) {
|
||||
chooserEclipse.dispose();
|
||||
}
|
||||
|
||||
actions.forEach(action -> getTool().removeAction(action));
|
||||
}
|
||||
|
||||
private boolean isAPK(FSRL fsrl) {
|
||||
return (fsrl != null) && (fsrl.getName() != null) &&
|
||||
"apk".equalsIgnoreCase(FilenameUtils.getExtension(fsrl.getName()));
|
||||
}
|
||||
|
||||
private void doExportToEclipse(FSRL fsrl, File outputDirectory, TaskMonitor monitor) {
|
||||
try (RefdFile refdFile =
|
||||
FileSystemService.getInstance().getRefdFile(fsrl, monitor)) {
|
||||
AndroidProjectCreator creator =
|
||||
new AndroidProjectCreator(refdFile.file.getFSRL(), outputDirectory);
|
||||
creator.create(monitor);
|
||||
|
||||
if (creator.getLog().hasMessages()) {
|
||||
Msg.showInfo(this, getTool().getActiveWindow(), "Export to Eclipse Project",
|
||||
creator.getLog().toString());
|
||||
}
|
||||
}
|
||||
catch (IOException | CancelledException e) {
|
||||
FSUtilities.displayException(this, getTool().getActiveWindow(),
|
||||
"Error Exporting to Eclipse", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private DockingAction createEclipseProjectAction() {
|
||||
return new ActionBuilder("FSB Export Eclipse Project", this.getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && JadProcessWrapper.isJadPresent() &&
|
||||
isAPK(ac.getFileFSRL()))
|
||||
.popupMenuPath("Export Eclipse Project")
|
||||
.popupMenuIcon(ImageManager.ECLIPSE)
|
||||
.popupMenuGroup("H")
|
||||
.onAction(
|
||||
ac -> {
|
||||
FSRL fsrl = ac.getFileFSRL();
|
||||
if (fsrl == null) {
|
||||
Msg.info(this, "Unable to export eclipse project");
|
||||
return;
|
||||
}
|
||||
|
||||
if (chooserEclipse == null) {
|
||||
chooserEclipse = new GhidraFileChooser(null);
|
||||
}
|
||||
chooserEclipse.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
chooserEclipse.setTitle("Select Eclipe Project Directory");
|
||||
chooserEclipse.setApproveButtonText("SELECT");
|
||||
chooserEclipse.setSelectedFile(null);
|
||||
File outputDirectory = chooserEclipse.getSelectedFile();
|
||||
if (outputDirectory == null) {
|
||||
return;
|
||||
}
|
||||
GTree gTree = ac.getTree();
|
||||
gTree.runTask(monitor -> doExportToEclipse(fsrl, outputDirectory, monitor));
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private DockingAction createDecompileJarAction() {
|
||||
return new ActionBuilder("FSB Decompile JAR", this.getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && JadProcessWrapper.isJadPresent() &&
|
||||
ac.getFileFSRL() != null)
|
||||
.popupMenuPath("Decompile JAR")
|
||||
.popupMenuIcon(ImageManager.JAR)
|
||||
.popupMenuGroup("J")
|
||||
.onAction(
|
||||
ac -> {
|
||||
FSRL jarFSRL = ac.getFileFSRL();
|
||||
if (jarFSRL == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chooserJarFolder == null) {
|
||||
chooserJarFolder = new GhidraFileChooser(null);
|
||||
}
|
||||
chooserJarFolder.setFileSelectionMode(
|
||||
GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||
chooserJarFolder.setTitle("Select JAR Output Directory");
|
||||
chooserJarFolder.setApproveButtonText("SELECT");
|
||||
chooserJarFolder.setSelectedFile(null);
|
||||
File outputDirectory = chooserJarFolder.getSelectedFile();
|
||||
if (outputDirectory == null) {
|
||||
return;
|
||||
}
|
||||
GTree gTree = ac.getTree();
|
||||
gTree.runTask(monitor -> {
|
||||
try {
|
||||
JarDecompiler decompiler =
|
||||
new JarDecompiler(jarFSRL, outputDirectory);
|
||||
decompiler.decompile(monitor);
|
||||
|
||||
if (decompiler.getLog().hasMessages()) {
|
||||
Msg.showInfo(this, gTree,
|
||||
"Decompiling Jar " + jarFSRL.getName(),
|
||||
decompiler.getLog().toString());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
FSUtilities.displayException(this, gTree, "Error Decompiling Jar",
|
||||
e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private DockingAction createCryptoTemplateAction() {
|
||||
return new ActionBuilder("FSB Create Crypto Key Template", this.getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> ac.notBusy() && ac.getSelectedNode() instanceof FSBRootNode &&
|
||||
ac.getFSRL(true) != null)
|
||||
.popupMenuPath("Create Crypto Key Template...")
|
||||
.popupMenuGroup("Z", "B")
|
||||
.onAction(
|
||||
ac -> {
|
||||
FSRL fsrl = ac.getFSRL(true);
|
||||
if (ac.getSelectedNode() instanceof FSBRootNode && fsrl != null) {
|
||||
createCryptoTemplate(fsrl, (FSBRootNode) ac.getSelectedNode());
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a crypto key file template based on the specified files under the GTree node.
|
||||
*
|
||||
* @param fsrl FSRL of a child file of the container that the crypto will be associated with
|
||||
* @param node GTree node with children that will be iterated
|
||||
*/
|
||||
private void createCryptoTemplate(FSRL fsrl, FSBRootNode node) {
|
||||
try {
|
||||
String fsContainerName = fsrl.getFS().getContainer().getName();
|
||||
CryptoKeyFileTemplateWriter writer = new CryptoKeyFileTemplateWriter(fsContainerName);
|
||||
if (writer.exists()) {
|
||||
int answer = OptionDialog.showYesNoDialog(getTool().getActiveWindow(),
|
||||
"WARNING!! Crypto Key File Already Exists",
|
||||
"WARNING!!" + "\n" + "The crypto key file already exists. " +
|
||||
"Are you really sure that you want to overwrite it?");
|
||||
if (answer == OptionDialog.NO_OPTION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
writer.open();
|
||||
try {
|
||||
// gTree.expandAll( node );
|
||||
writeFile(writer, node.getChildren());
|
||||
}
|
||||
finally {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
FSUtilities.displayException(this, getTool().getActiveWindow(),
|
||||
"Error writing crypt key file", e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void writeFile(CryptoKeyFileTemplateWriter writer, List<GTreeNode> children)
|
||||
throws IOException {
|
||||
|
||||
if (children == null || children.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (GTreeNode child : children) {
|
||||
if (child instanceof FSBFileNode) {
|
||||
FSRL childFSRL = ((FSBFileNode) child).getFSRL();
|
||||
writer.write(childFSRL.getName());
|
||||
}
|
||||
else {
|
||||
writeFile(writer, child.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DockingAction createLoadKernelAction() {
|
||||
return new ActionBuilder("FSB Load iOS Kernel", this.getName())
|
||||
.withContext(FSBActionContext.class)
|
||||
.enabledWhen(ac -> {
|
||||
if (ac.isBusy()) {
|
||||
return false;
|
||||
}
|
||||
FSBRootNode rootNode = ac.getRootOfSelectedNode();
|
||||
return rootNode != null && rootNode.getFSRef() != null &&
|
||||
rootNode.getFSRef().getFilesystem() instanceof MachoPrelinkFileSystem;
|
||||
})
|
||||
.popupMenuPath("Load iOS Kernel")
|
||||
.popupMenuIcon(ImageManager.iOS)
|
||||
.popupMenuGroup("I")
|
||||
.onAction(
|
||||
ac -> {
|
||||
FSRL fsrl = ac.getFSRL(true);
|
||||
List<FSRL> fileList = new ArrayList<>();
|
||||
|
||||
if (fsrl != null) {
|
||||
FSBNode selectedNode = ac.getSelectedNode();
|
||||
if (selectedNode instanceof FSBRootNode) {
|
||||
for (GTreeNode childNode : ac.getSelectedNode().getChildren()) {
|
||||
if (childNode instanceof FSBNode) {
|
||||
FSBNode baseNode = (FSBNode) childNode;
|
||||
fileList.add(baseNode.getFSRL());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selectedNode instanceof FSBFileNode ||
|
||||
selectedNode instanceof FSBDirNode) {
|
||||
fileList.add(fsrl);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileList.isEmpty()) {
|
||||
if (OptionDialog.showYesNoDialog(null, "Load iOS Kernel?",
|
||||
"Performing this action will load the entire kernel and all KEXT files." +
|
||||
"\n" + "Do you want to continue?") == OptionDialog.YES_OPTION) {
|
||||
loadIOSKernel(fileList);
|
||||
}
|
||||
}
|
||||
else {
|
||||
getTool().setStatusInfo("Load iOS kernel -- nothing to do.");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads or imports iOS kernel files.
|
||||
*
|
||||
* @param fileList List of {@link FSRL}s of the iOS kernel files.
|
||||
*/
|
||||
private void loadIOSKernel(List<FSRL> fileList) {
|
||||
ProgramManager pm = FSBUtils.getProgramManager(getTool(), true);
|
||||
if (pm != null) {
|
||||
TaskLauncher.launch(new GFileSystemLoadKernelTask(this, pm, fileList));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user