diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Edit_Plugin_Path.htm b/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Edit_Plugin_Path.htm
index c70c74ab80..783fdb5648 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Edit_Plugin_Path.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Edit_Plugin_Path.htm
@@ -53,31 +53,6 @@
class files in that directory will be used (not jar files within that directory).
If the path is a jar file, then classes within the jar file will be used.
-
-
- The directories noted above, as well as any found jar files, are added to Ghidra's
- classpath. The search order of these paths is:
-
-
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/images/EditPluginPath.png b/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/images/EditPluginPath.png
index b7fc109283..fe4537e1d0 100644
Binary files a/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/images/EditPluginPath.png and b/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/images/EditPluginPath.png differ
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java
index e7305c1178..d1a295363d 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java
@@ -924,7 +924,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
public void println(String message) {
String decoratedMessage = getScriptName() + "> " + message;
- // note: use a Message object to facilite script message log filtering
+ // note: use a Message object to facilitate script message log filtering
Msg.info(GhidraScript.class, new ScriptMessage(decoratedMessage));
if (isRunningHeadless()) {
@@ -3700,7 +3700,8 @@ public abstract class GhidraScript extends FlatProgramAPI {
* @see #getRepeatableComment(Address)
*/
public String getRepeatableCommentAsRendered(Address address) {
- String comment = currentProgram.getListing().getComment(CodeUnit.REPEATABLE_COMMENT, address);
+ String comment =
+ currentProgram.getListing().getComment(CodeUnit.REPEATABLE_COMMENT, address);
PluginTool tool = state.getTool();
if (tool != null) {
comment = CommentUtils.getDisplayString(comment, currentProgram);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/HeadlessGhidraApplicationConfiguration.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/HeadlessGhidraApplicationConfiguration.java
index c5b4ad281b..559844f5ae 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/framework/HeadlessGhidraApplicationConfiguration.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/HeadlessGhidraApplicationConfiguration.java
@@ -20,7 +20,6 @@ import java.util.List;
import generic.jar.ResourceFile;
import ghidra.GhidraClassLoader;
-import ghidra.GhidraLauncher;
import ghidra.framework.preferences.Preferences;
import ghidra.net.ApplicationTrustManagerFactory;
import ghidra.util.Msg;
@@ -62,22 +61,8 @@ public class HeadlessGhidraApplicationConfiguration extends ApplicationConfigura
if (!(ClassLoader.getSystemClassLoader() instanceof GhidraClassLoader)) {
return;
}
+
GhidraClassLoader loader = (GhidraClassLoader) ClassLoader.getSystemClassLoader();
-
- // Add user jars
- String userJarDir = Preferences.getProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY);
- if (userJarDir != null) {
- GhidraLauncher.findJarsInDir(new ResourceFile(userJarDir)).forEach(
- p -> loader.addPath(p));
- }
-
- // Add plugins from user settings directory
- String userSettingsPath = Application.getUserSettingsDirectory().getAbsolutePath();
- String pluginPath = userSettingsPath + File.separatorChar + "plugins";
- loader.addPath(pluginPath);
- GhidraLauncher.findJarsInDir(new ResourceFile(pluginPath)).forEach(p -> loader.addPath(p));
-
- // Add user plugins
for (String path : Preferences.getPluginPaths()) {
loader.addPath(path);
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/preferences/Preferences.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/preferences/Preferences.java
index 5c2289580c..c008bf1653 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/preferences/Preferences.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/preferences/Preferences.java
@@ -40,11 +40,6 @@ public class Preferences {
*/
private final static String USER_PLUGIN_PATH = "UserPluginPath";
- /**
- * Preference name of the user plugin jar directory.
- */
- public final static String USER_PLUGIN_JAR_DIRECTORY = "UserPluginJarDirectory";
-
/**
* Preference name for the last opened archive directory.
*/
@@ -194,6 +189,8 @@ public class Preferences {
*
* Note: all getProperty(...) methods will first check {@link System#getProperty(String)}
* for a value first. This allows users to override preferences from the command-line.
+ * @param name the property name
+ * @return the current property value; null if not set
*/
public static String getProperty(String name) {
// prefer system properties, which enables uses to override preferences from the command-line
@@ -210,6 +207,9 @@ public class Preferences {
*
* Note: all getProperty(...) methods will first check {@link System#getProperty(String)}
* for a value first. This allows users to override preferences from the command-line.
+ * @param name the property name
+ * @param defaultValue the default value
+ * @return the property value; default value if not set
*
* @see #getProperty(String, String, boolean)
*/
@@ -289,6 +289,7 @@ public class Preferences {
/**
* Get the filename that will be used in the store() method.
+ * @return the filename
*/
public static String getFilename() {
return filename;
@@ -297,7 +298,7 @@ public class Preferences {
/**
* Set the filename so that when the store() method is called, the
* preferences are written to this file.
- * @param name
+ * @param name the filename
*/
public static void setFilename(String name) {
filename = name;
@@ -305,6 +306,7 @@ public class Preferences {
/**
* Store the preferences in a file for the current filename.
+ * @return true if the file was written
* @throws RuntimeException if the preferences filename was not set
*/
public static boolean store() {
@@ -346,6 +348,7 @@ public class Preferences {
/**
* Return the paths in the UserPluginPath property.
* Return zero length array if this property is not set.
+ * @return the paths
*
*/
public static String[] getPluginPaths() {
@@ -359,6 +362,7 @@ public class Preferences {
/**
* Set the paths to be used as the UserPluginPath property.
+ * @param paths the paths
*/
public static void setPluginPaths(String[] paths) {
if (paths == null || paths.length == 0) {
@@ -376,55 +380,6 @@ public class Preferences {
properties.setProperty(USER_PLUGIN_PATH, sb.toString());
}
- /**
- * Set the plugin path property.
- * @param pathProperty A string of paths separated by {@link File#pathSeparator} characters
- */
- public static void setPluginPathProperty(String pathProperty) {
- properties.setProperty(USER_PLUGIN_PATH, pathProperty);
- }
-
- /**
- * Append path to the plugin path.
- * @param path the plugin path to add
- */
- public static void addPluginPath(String path) {
- List list = getPluginPathList();
- if (list == null) {
- setPluginPaths(new String[] { path });
- return;
- }
- if (!list.contains(path)) {
- list.add(path);
- String[] p = new String[list.size()];
- setPluginPaths(list.toArray(p));
- }
- }
-
- /**
- * Append paths to the plugin path.
- * @param paths the plugin paths to add
- */
- public static void addPluginPaths(String[] paths) {
- List list = getPluginPathList();
- if (list == null) {
- setPluginPaths(paths);
- return;
- }
- boolean listChanged = false;
- for (String path : paths) {
- if (!list.contains(path)) {
- list.add(path);
- listChanged = true;
- }
- }
- // update plugin path property only if we added a path to the list
- if (listChanged) {
- String[] p = new String[list.size()];
- setPluginPaths(list.toArray(p));
- }
- }
-
private static List getPluginPathList() {
String path = properties.getProperty(USER_PLUGIN_PATH);
if (path == null) {
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassFinder.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassFinder.java
index 0a3964972d..4294a9ebab 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassFinder.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassFinder.java
@@ -58,15 +58,15 @@ public class ClassFinder {
if ((lcPath.endsWith(".jar") || lcPath.endsWith(".zip")) && file.exists()) {
if (ClassJar.ignoreJar(lcPath)) {
- log.trace("Ignoring jar file: " + path);
+ log.trace("Ignoring jar file: {}", path);
continue;
}
- log.trace("Searching jar file: " + path);
+ log.trace("Searching jar file: {}", path);
classJars.add(new ClassJar(path, monitor));
}
else if (file.isDirectory()) {
- log.trace("Searching classpath directory: " + path);
+ log.trace("Searching classpath directory: {}", path);
classDirs.add(new ClassDir(path, monitor));
}
}
@@ -107,7 +107,6 @@ public class ClassFinder {
return n1.compareTo(n2);
});
-
return classList;
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassJar.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassJar.java
index 4200075f27..48b9be6f14 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassJar.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassJar.java
@@ -23,11 +23,18 @@ import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.io.FilenameUtils;
+
+import generic.jar.ResourceFile;
+import ghidra.framework.Application;
+import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
+import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
+import utility.application.ApplicationLayout;
-class ClassJar {
+class ClassJar extends ClassLocation {
/**
* Pattern for matching jar files in a module lib dir
@@ -36,24 +43,28 @@ class ClassJar {
* build/libs, ending in .jar (non-capturing) and then
* grab that dir's parent and the name of the jar file.
*/
- private static Pattern ANY_MODULE_LIB_JAR_FILE_PATTERN =
+ private static final Pattern ANY_MODULE_LIB_JAR_FILE_PATTERN =
Pattern.compile(".*/(.*)/(?:lib|build/libs)/(.+).jar");
+ private static final String PATCH_DIR_PATH_FORWARD_SLASHED = getPatchDirPath();
+ private static final Set USER_PLUGIN_PATHS = loadUserPluginPaths();
+
private String path;
- private Set classNameList = new HashSet<>();
- private Set> classes = new HashSet<>();
ClassJar(String path, TaskMonitor monitor) throws CancelledException {
this.path = path;
+ loadUserPluginPaths();
- scan(monitor);
+ scanJar(monitor);
}
+ @Override
void getClasses(Set> set, TaskMonitor monitor) {
+ checkForDuplicates(set);
set.addAll(classes);
}
- private void scan(TaskMonitor monitor) throws CancelledException {
+ private void scanJar(TaskMonitor monitor) throws CancelledException {
File file = new File(path);
@@ -83,30 +94,64 @@ class ClassJar {
//
//
- // Dev Mode
+ // Dev Mode - don't scan 3rd-party jar files
//
if (pathName.contains("ExternalLibraries")) {
return true;
}
//
- // Production Mode - In production, only module lib jar files are scanned.
- //
- if (isModuleDependencyJar(pathName)) {
+ // Dev Mode - let everything else through
+ //
+ if (SystemUtilities.isInDevelopmentMode()) {
return false;
}
+ //
+ // Production Mode - old style (before Extensions) of user contributions
+ //
+ String forwardSlashedPathName = pathName.replaceAll("\\\\", "/");
+ if (isUserPluginJar(forwardSlashedPathName)) {
+ return false;
+ }
+
+ //
+ // Production Mode - allow users to enter code in the 'patch' directory
+ //
+ if (isPatchJar(forwardSlashedPathName)) {
+ return false;
+ }
+
+ //
+ // Production Mode - In production, only module lib jar files are scanned
+ //
+ if (isModuleDependencyJar(forwardSlashedPathName)) {
+ return false;
+ }
+
+ // this is typically a 3rd-party jar file
return true;
}
- static boolean isModuleDependencyJar(String pathName) {
+ private static boolean isUserPluginJar(String pathName) {
+ return USER_PLUGIN_PATHS.contains(pathName);
+ }
+
+ // Note: the path is expected to be using forward slashes
+ private static boolean isPatchJar(String pathName) {
+ String jarDirectory = FilenameUtils.getFullPathNoEndSeparator(pathName);
+ return jarDirectory.equalsIgnoreCase(PATCH_DIR_PATH_FORWARD_SLASHED);
+ }
+
+ // Note: the path is expected to be using forward slashes
+ private static boolean isModuleDependencyJar(String pathName) {
if (ClassSearcher.SEARCH_ALL_JARS) {
return true; // this will search all jar files
}
- String forwardSlashed = pathName.replaceAll("\\\\", "/");
- Matcher matcher = ANY_MODULE_LIB_JAR_FILE_PATTERN.matcher(forwardSlashed);
+ // Note: the path is expected to be using forward slashes
+ Matcher matcher = ANY_MODULE_LIB_JAR_FILE_PATTERN.matcher(pathName);
if (!matcher.matches()) {
return false;
}
@@ -121,15 +166,14 @@ class ClassJar {
private void processClassFiles(JarEntry entry) {
String name = entry.getName();
- if (!name.endsWith(".class")) {
+ if (!name.endsWith(CLASS_EXT)) {
return;
}
- name = name.substring(0, name.indexOf(".class"));
+ name = name.substring(0, name.indexOf(CLASS_EXT));
name = name.replace('/', '.');
Class> c = ClassFinder.loadExtensionPoint(path, name);
if (c != null) {
- classNameList.add(name);
classes.add(c);
}
}
@@ -138,4 +182,27 @@ class ClassJar {
public String toString() {
return path;
}
+
+ private static String getPatchDirPath() {
+ ApplicationLayout layout = Application.getApplicationLayout();
+ ResourceFile patchDir = layout.getPatchDir();
+ if (patchDir == null) {
+ return ""; // not in a distribution
+ }
+ String patchPath = patchDir.getAbsolutePath();
+ String forwardSlashed = patchPath.replaceAll("\\\\", "/");
+ return forwardSlashed;
+ }
+
+ private static Set loadUserPluginPaths() {
+ Set result = new HashSet<>();
+ String[] paths = Preferences.getPluginPaths();
+ for (String pathName : paths) {
+ // note: lower case because our client uses lower case for paths
+ String forwardSlashed = pathName.replaceAll("\\\\", "/").toLowerCase();
+ result.add(forwardSlashed);
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassLocation.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassLocation.java
new file mode 100644
index 0000000000..0a7e1b3a6f
--- /dev/null
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassLocation.java
@@ -0,0 +1,56 @@
+/* ###
+ * 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.util.classfinder;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * Represents a place from which {@link Class}s can be obtained
+ */
+abstract class ClassLocation {
+
+ protected static final String CLASS_EXT = ".class";
+
+ final Logger log = LogManager.getLogger(getClass());
+
+ protected Set> classes = new HashSet<>();
+
+ abstract void getClasses(Set> set, TaskMonitor monitor) throws CancelledException;
+
+ void checkForDuplicates(Set> existingClasses) {
+ if (!log.isTraceEnabled()) {
+ return;
+ }
+
+ for (Class> c : classes) {
+ if (existingClasses.contains(c)) {
+ Module module = c.getModule();
+ module.toString();
+ log.trace("Attempting to load the same class twice: {}. " +
+ "Keeping loaded class ; ignoring class from {}", c, this);
+ return;
+ }
+ }
+ }
+
+}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassPackage.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassPackage.java
index 914da5d02c..ddadfa431e 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassPackage.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassPackage.java
@@ -23,12 +23,11 @@ import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
-class ClassPackage {
+class ClassPackage extends ClassLocation {
private static final FileFilter CLASS_FILTER =
- pathname -> pathname.getName().endsWith(".class");
+ pathname -> pathname.getName().endsWith(CLASS_EXT);
- private Set> classes = new HashSet<>();
private Set children = new HashSet<>();
private File rootDir;
private File packageDir;
@@ -88,7 +87,11 @@ class ClassPackage {
return new File(lRootDir, lPackageName.replace('.', File.separatorChar));
}
+ @Override
void getClasses(Set> set, TaskMonitor monitor) throws CancelledException {
+
+ checkForDuplicates(set);
+
set.addAll(classes);
Iterator it = children.iterator();
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java
index 5235df82c8..4a10c95b36 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java
@@ -248,7 +248,7 @@ public class ClassSearcher {
monitor.setMessage("Loading classes...");
extensionPoints = searcher.getClasses(monitor);
- log.trace("Found extension classes: " + extensionPoints);
+ log.trace("Found extension classes {}", extensionPoints);
if (extensionPoints.isEmpty()) {
throw new AssertException("Unable to location extension points!");
}
@@ -307,7 +307,6 @@ public class ClassSearcher {
}
private static void loadExtensionClassesFromJar() {
- // there will only be one root in jar file mode!
ResourceFile appRoot = Application.getApplicationRootDirectory();
ResourceFile extensionClassesFile = new ResourceFile(appRoot, "EXTENSION_POINT_CLASSES");
try {
@@ -326,7 +325,8 @@ public class ClassSearcher {
}
catch (IOException e) {
- throw new AssertException("Got unexpected IOException ", e);
+ throw new AssertException("Unexpected IOException reading extension class file " +
+ extensionClassesFile, e);
}
}
@@ -338,7 +338,7 @@ public class ClassSearcher {
throw new AssertException("Could not find modules for Class Searcher!");
}
- log.trace("Scanning module root directories: " + moduleRootDirectories);
+ log.trace("Scanning module root directories: {}", moduleRootDirectories);
for (ResourceFile moduleRoot : moduleRootDirectories) {
ResourceFile file = new ResourceFile(moduleRoot, "data/ExtensionPoint.manifest");
@@ -361,7 +361,7 @@ public class ClassSearcher {
}
buffy.append(')');
extensionPointSuffixPattern = Pattern.compile(buffy.toString());
- log.trace("Using extension point pattern: " + extensionPointSuffixPattern);
+ log.trace("Using extension point pattern: {}", extensionPointSuffixPattern);
}
static boolean isExtensionPointName(String name) {
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/BrowsePathPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/BrowsePathPanel.java
deleted file mode 100644
index 00a9dfb4c3..0000000000
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/BrowsePathPanel.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/* ###
- * IP: GHIDRA
- * REVIEWED: YES
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.framework.main;
-
-import java.awt.BorderLayout;
-import java.awt.event.*;
-import java.io.File;
-
-import javax.swing.*;
-
-import docking.DockingUtils;
-import docking.options.editor.ButtonPanelFactory;
-import docking.widgets.filechooser.GhidraFileChooser;
-
-/**
- * Helper class that restricts the width of the textField to the size of the
- * scrolled paths list; also provides the listener for the textfield if user
- * presses Enter or Tab in a textfield.
- *
- */
-class BrowsePathPanel extends JPanel {
-
- private boolean changed;
- private GhidraFileChooser fileChooser;
- private JTextField pathTextField;
- private EditPluginPathDialog dialog;
- private JButton browseButton;
-
- /**
- * Construct a new BrowsePathPanel.
- * @param editDialog parent dialog
- * @param sizeComp component to use for size in creating text field
- * @param button browse button
- * @param dirOnly
- * @param textFieldLabel
- * @param fieldName name of text field component
- */
- BrowsePathPanel(EditPluginPathDialog editDialog, ActionListener buttonListener, String fieldName) {
-
- super();
- setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
- dialog = editDialog;
- create(fieldName);
- addListeners(buttonListener);
-
- }
-
- /**
- * Create the components
- * @param sizeComp component to use when creating the text field to get the
- * size
- * @param textFieldLabel label for the field
- */
- private void create(String fieldName) {
- pathTextField = new JTextField();
- pathTextField.setName(fieldName);
- pathTextField.setEditable(false);
- pathTextField.setBackground(getBackground());
-
- browseButton = ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
- browseButton.setToolTipText("Choose Directory");
-
- // construct the panel with text field and browse button
- JPanel browsePathPanel = new JPanel(new BorderLayout(5, 5));
- browsePathPanel.add(pathTextField, BorderLayout.CENTER);
- browsePathPanel.add(browseButton, BorderLayout.EAST);
- add(browsePathPanel);
-
- }
-
- private void createFileChooser() {
- // create the fileChooser this panel will use based on its input criteria
- fileChooser = new GhidraFileChooser(dialog.getComponent());
- fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
- fileChooser.setFileSelectionMode(GhidraFileChooser.DIRECTORIES_ONLY);
- fileChooser.setApproveButtonToolTipText("Choose Directory With Plugin JAR Files");
- fileChooser.setApproveButtonText("Choose JAR Directory");
- }
-
- /**
- * Add listeners.
- * @param listener listener for the browse button
- */
- private void addListeners(ActionListener listener) {
- browseButton.addActionListener(listener);
-
- pathTextField.addKeyListener(new KeyAdapter() {
- @Override
- public void keyPressed(KeyEvent e) {
- int keyCode = e.getKeyCode();
-
- // when Esc or Ctrl-C is pressed, reset the plugin
- // jar directory to what is saved in preferences
- if (keyCode == KeyEvent.VK_ESCAPE ||
- (DockingUtils.isControlModifier(e) && keyCode == KeyEvent.VK_C)) {
- dialog.initJarDirectory();
- }
- else {
- dialog.setApplyEnabled(true);
- }
- }
- });
-
- }
-
- String getPath() {
- return pathTextField.getText().trim();
- }
-
- boolean isChanged() {
- return changed;
- }
-
- @Override
- public boolean hasFocus() {
- return pathTextField.hasFocus();
- }
-
- @Override
- public void requestFocus() {
- pathTextField.requestFocus();
- pathTextField.selectAll();
- }
-
- /**
- * Pop up the file chooser.
- */
- void showFileChooser() {
- if (fileChooser == null) {
- createFileChooser();
- }
- // reset the status message
- dialog.setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
-
- File pluginFile = fileChooser.getSelectedFile();
- if (pluginFile != null) {
- setPath(pluginFile);
- }
- else {
- pathTextField.requestFocus();
- pathTextField.selectAll();
-
- }
- }
-
- /**
- * Set whether something has changed.
- * @param changed true if something changed
- */
- void setChanged(boolean changed) {
- this.changed = changed;
- }
-
- /**
- * Set the path field.
- * @param path filename for the path field
- * @return boolean true if the path is valid
- */
- private boolean setPath(File path) {
- boolean pathOK = false;
- dialog.setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
-
- if (!path.canRead()) {
- pathTextField.selectAll();
- dialog.setStatusMessage("Cannot read path: " + path.toString());
- }
- else {
- pathTextField.setText(path.getAbsolutePath());
- pathOK = (pathTextField.getText().trim().length() > 0);
- }
-
- if (pathOK) {
- dialog.setStatusMessage("Press Apply or OK to set JAR directory.");
- }
-
- changed = changed || pathOK;
-
- dialog.enableApply();
-
- return pathOK;
- }
-
- /**
- * sets the text of the text field of the panel without
- * any error checking
- */
- void setText(String text) {
- pathTextField.setText(text);
- }
-
-}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/EditPluginPathDialog.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/EditPluginPathDialog.java
index f511419714..594863aa63 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/EditPluginPathDialog.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/EditPluginPathDialog.java
@@ -18,8 +18,6 @@
package ghidra.framework.main;
import java.awt.*;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -34,6 +32,7 @@ import javax.swing.event.ListSelectionListener;
import docking.DialogComponentProvider;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.filechooser.GhidraFileChooser;
+import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.label.GDLabel;
import docking.widgets.list.GListCellRenderer;
import ghidra.framework.plugintool.PluginTool;
@@ -45,20 +44,18 @@ import ghidra.util.filechooser.GhidraFileFilter;
/**
* Dialog for editing the Plugin path and Jar directory path preferences.
+ *
* The Plugin Path and Jar directory path are locations where Ghidra searches
* for plugins to load. The Plugin Path is specified exactly as a Java Classpath
- * is specified. The Jar directory is searched only for Jar files containing
- * Plugins. When changes are made to these fields in the dialog, the
+ * is specified. When changes are made to these fields in the dialog, the
* preferences file is updated and written to disk. The preferences file is
* located in the .ghidra directory in the user's home directory.
- *
- * The preferences file also contains the last project that was opened,
- * and the positions of the Ghidra Project Window and other tools that were
- * running when the user last exited Ghidra.
- *
+ *
*/
class EditPluginPathDialog extends DialogComponentProvider {
+ static final String ADD_DIR_BUTTON_TEXT = "Add Dir ...";
+ static final String ADD_JAR_BUTTON_TEXT = "Add Jar ...";
private final static int SIDE_MARGIN = 5;
private final static Color INVALID_PATH_COLOR = Color.red.brighter();
private final static Color INVALID_SELECTED_PATH_COLOR = Color.pink;
@@ -80,7 +77,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
// gui members needed for dis/enabling and other state-dependent actions
private JScrollPane scrollPane; // need for preferred size when resizing
private JList pluginPathsList;
- private BrowsePathPanel jarPathPanel;
private GhidraFileChooser fileChooser;
private JButton upButton;
private JButton downButton;
@@ -94,7 +90,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
* Creates a non-modal dialog with OK, Apply, Cancel buttons.
* The OK and Apply buttons will be enabled when user makes unapplied
* changes to the UserPluginPath or UserPluginJarDirectory property values.
- * @param parent parent to this dialog
*/
EditPluginPathDialog() {
super("Edit Plugin Path", true, false, true, false);
@@ -134,8 +129,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
// subsequent panels
mainPanel.add(buildPluginPathsPanel());
mainPanel.add(Box.createVerticalStrut(10));
- mainPanel.add(buildJarDirectoryPanel());
- mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(Box.createVerticalGlue());
mainPanel.add(statusMessagePanel);
mainPanel.invalidate();
@@ -147,33 +140,15 @@ class EditPluginPathDialog extends DialogComponentProvider {
return mainPanel;
}
- /**
- * Gets called when the user selects Apply
- */
@Override
protected void applyCallback() {
- // validate the jar path before applying changes, since the user
- // is pressing the Apply button to save this setting
- String jarPathname = jarPathPanel.getPath();
- if (jarPathname.length() > 0) {
- File jarPath = new File(jarPathname);
- if (!jarPath.isDirectory() || !jarPath.canRead()) {
- setStatusMessage("Bad Jar Directory: " + jarPathname);
- jarPathPanel.requestFocus();
- return;
- }
- }
-
- // do the things we need to do to handle the applied changes
handleApply();
}
- /**
- * Gets called when the user selects Cancel
- */
@Override
protected void cancelCallback() {
close();
+
// reset original state of dialog for next display of dialog
enableButtons(false);
setStatusMessage(EMPTY_STATUS);
@@ -181,17 +156,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
errorMsg = null;
}
- /**
- * if the jar directory field has focus, don't let the base dialog
- * handle it.
- */
- @Override
- protected void escapeCallback() {
- if (!jarPathPanel.hasFocus()) {
- super.escapeCallback();
- }
- }
-
/**
* Gets called when the user selects Ok
*/
@@ -206,30 +170,21 @@ class EditPluginPathDialog extends DialogComponentProvider {
}
/**
- * re-set the list of paths each time the dialog is shown
+ * Reset the list of paths each time the dialog is shown
+ * @param tool the tool
*/
public void show(PluginTool tool) {
setPluginPathsListData(Preferences.getPluginPaths());
- initJarDirectory();
+ setApplyEnabled(pluginPathsChanged);
+ setStatusMessage(EMPTY_STATUS);
+
// setting the path enables the apply, but we know we haven't
// made any changes yet, so disable
setApplyEnabled(false);
tool.showDialog(this);
}
- /**
- * Method enableApply.
- */
- void enableApply() {
- setApplyEnabled(pluginPathsChanged || jarPathPanel.isChanged());
- }
-
- void initJarDirectory() {
- setApplyEnabled(pluginPathsChanged);
- setStatusMessage(EMPTY_STATUS);
- }
-
- void setStatusMessage(String msg) {
+ private void setStatusMessage(String msg) {
if (msg == null || msg.length() == 0) {
msg = EMPTY_STATUS;
}
@@ -237,15 +192,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
statusMessage.invalidate();
}
- /**
- * @see ghidra.util.bean.GhidraDialog#setApplyEnabled(boolean)
- */
- @Override
- protected void setApplyEnabled(boolean state) {
- super.setApplyEnabled(state);
- }
-
- void addJarCallback() {
+ private void addJarCallback() {
setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
@@ -253,7 +200,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
fileChooser = new GhidraFileChooser(getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
}
- fileChooser.setFileSelectionMode(GhidraFileChooser.FILES_ONLY);
+ fileChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
fileChooser.setFileFilter(JAR_FILTER);
fileChooser.setApproveButtonToolTipText("Choose Plugin Jar File");
fileChooser.setApproveButtonText("Add Jar File");
@@ -277,7 +224,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
}
}
- void addDirCallback() {
+ private void addDirCallback() {
setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
@@ -285,7 +232,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
fileChooser = new GhidraFileChooser(getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
}
- fileChooser.setFileSelectionMode(GhidraFileChooser.DIRECTORIES_ONLY);
+ fileChooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
fileChooser.setFileFilter(GhidraFileFilter.ALL);
fileChooser.setApproveButtonToolTipText("Choose Directory with Plugin class Files");
fileChooser.setApproveButtonText("Add Directory");
@@ -310,63 +257,31 @@ class EditPluginPathDialog extends DialogComponentProvider {
}
}
- /**
- * Returns an array of pathnames where plugins can be found; used by custom
- * class loader when searching for plugins.
- */
private String[] getUserPluginPaths() {
String[] pluginsArray = new String[listModel.size()];
listModel.copyInto(pluginsArray);
return pluginsArray;
}
- /**
- * construct the plugin paths button panel
- */
private JPanel buildPluginPathsPanel() {
// create the UP and DOWN arrows panel
upButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_UP_TYPE);
upButton.setName("UpArrow");
- upButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- handleSelection(UP);
- }
- });
+ upButton.addActionListener(e -> handleSelection(UP));
downButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_DOWN_TYPE);
downButton.setName("DownArrow");
- downButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- handleSelection(DOWN);
- }
- });
+ downButton.addActionListener(e -> handleSelection(DOWN));
JPanel arrowButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
arrowButtonsPanel.add(upButton);
arrowButtonsPanel.add(downButton);
// create the Add and Remove panel
- JButton addJarButton = ButtonPanelFactory.createButton("Add Jar...");
- addJarButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- addJarCallback();
- }
- });
- JButton addDirButton = ButtonPanelFactory.createButton("Add Dir...");
- addDirButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- addDirCallback();
- }
- });
+ JButton addJarButton = ButtonPanelFactory.createButton(ADD_JAR_BUTTON_TEXT);
+ addJarButton.addActionListener(e -> addJarCallback());
+ JButton addDirButton = ButtonPanelFactory.createButton(ADD_DIR_BUTTON_TEXT);
+ addDirButton.addActionListener(e -> addDirCallback());
removeButton = ButtonPanelFactory.createButton("Remove");
- removeButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- handleSelection(REMOVE);
- }
- });
+ removeButton.addActionListener(e -> handleSelection(REMOVE));
Dimension d = addJarButton.getPreferredSize();
addDirButton.setPreferredSize(d);
removeButton.setPreferredSize(d);
@@ -415,26 +330,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
return pluginPathListPanel;
}
- /**
- * construct the jar directory panel
- */
- private JPanel buildJarDirectoryPanel() {
-
- ActionListener listener = new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- jarPathPanel.showFileChooser();
- enableApply();
- }
- };
-
- jarPathPanel = new BrowsePathPanel(this, listener, "UserPluginJarDirectory");
- jarPathPanel.setText(Preferences.getProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY));
- jarPathPanel.setBorder(new TitledBorder("User Plugin Jar Directory"));
-
- return jarPathPanel;
- }
-
private void enableButtons(boolean enabled) {
upButton.setEnabled(enabled);
downButton.setEnabled(enabled);
@@ -452,22 +347,12 @@ class EditPluginPathDialog extends DialogComponentProvider {
// update Ghidra Preferences with new paths
Preferences.setPluginPaths(userPluginPaths);
- // Get user Jar directory
- String jarDirectoryName = jarPathPanel.getPath();
- if (jarDirectoryName.trim().length() == 0) {
- jarDirectoryName = null;
- }
-
- // update Ghidra Preferences with new Jar path
- Preferences.setProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY, jarDirectoryName);
-
errorMsg = null;
// save the new values
if (Preferences.store()) {
setStatusMessage("Saved plugin paths successfully!");
// indicate to user all changes have been applied
setApplyEnabled(false);
- jarPathPanel.setChanged(false);
Msg.showInfo(getClass(), rootPanel, "Restart Ghidra",
"You must restart Ghidra in order\n" + "for path changes to take effect.");
@@ -479,10 +364,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
}
}
- /**
- * dispatched method for handling button actions on the
- * dialog
- */
private void handleSelection(byte whichAction) {
// if nothing selected, nothing to do
if (selectedInList == null) {
@@ -574,8 +455,8 @@ class EditPluginPathDialog extends DialogComponentProvider {
private void setPluginPathsListData(String[] pluginPathNames) {
listModel.clear();
- for (int p = 0; p < pluginPathNames.length; p++) {
- listModel.addElement(pluginPathNames[p]);
+ for (String pluginPathName : pluginPathNames) {
+ listModel.addElement(pluginPathName);
}
}
diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java
index 06d8d26d1c..ba35977fd2 100644
--- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java
+++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java
@@ -64,6 +64,9 @@ public class GhidraApplicationLayout extends ApplicationLayout {
// Extensions
extensionInstallationDir = findExtensionInstallationDirectory();
extensionArchiveDir = findExtensionArchiveDirectory();
+
+ // Patch directory
+ patchDir = findPatchDirectory();
}
/**
@@ -142,7 +145,7 @@ public class GhidraApplicationLayout extends ApplicationLayout {
// Find standard module root directories from within the application root directories
Collection moduleRootDirectories =
ModuleUtilities.findModuleRootDirectories(applicationRootDirs, new ArrayList<>());
-
+
// Examine the classpath to look for modules outside of the application root directories.
// These might exist if Ghidra was launched from an Eclipse project that resides
// external to the Ghidra installation.
@@ -156,8 +159,9 @@ public class GhidraApplicationLayout extends ApplicationLayout {
// Skip classpath entries that live in an application root directory...we've already
// found those.
- if (applicationRootDirs.stream().anyMatch(dir -> FileUtilities.isPathContainedWithin(
- dir.getFile(false), classpathEntry.getFile(false)))) {
+ if (applicationRootDirs.stream()
+ .anyMatch(dir -> FileUtilities.isPathContainedWithin(
+ dir.getFile(false), classpathEntry.getFile(false)))) {
continue;
}
@@ -173,6 +177,24 @@ public class GhidraApplicationLayout extends ApplicationLayout {
return ModuleUtilities.findModules(applicationRootDirs, moduleRootDirectories);
}
+ /**
+ * Returns the directory that allows users to add jar and class files to override existing
+ * distribution files
+ * @return the patch dir; null if not in a distribution
+ */
+ protected ResourceFile findPatchDirectory() {
+
+ if (SystemUtilities.isInDevelopmentMode()) {
+ return null;
+ }
+
+ if (applicationInstallationDir == null) {
+ return null;
+ }
+
+ return new ResourceFile(applicationInstallationDir, "Ghidra/patch");
+ }
+
/**
* Returns the directory where all Ghidra extension archives are stored.
* This should be at the following location:
diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraLauncher.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraLauncher.java
index 5ba84d7ef7..8db165297e 100644
--- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraLauncher.java
+++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraLauncher.java
@@ -50,9 +50,10 @@ public class GhidraLauncher {
// Get application layout
GhidraApplicationLayout layout = new GhidraApplicationLayout();
+ GhidraClassLoader loader = (GhidraClassLoader) ClassLoader.getSystemClassLoader();
// Build the classpath
- List classpathList = new ArrayList();
+ List classpathList = new ArrayList<>();
Map modules = getOrderedModules(layout);
if (SystemUtilities.isInDevelopmentMode()) {
@@ -60,13 +61,12 @@ public class GhidraLauncher {
addExternalJarPaths(classpathList, layout.getApplicationRootDirs());
}
else {
- addPatchPaths(classpathList, layout.getApplicationInstallationDir());
+ addPatchPaths(classpathList, layout.getPatchDir());
addModuleJarPaths(classpathList, modules);
}
classpathList = orderClasspath(classpathList, modules);
// Add the classpath to the class loader
- GhidraClassLoader loader = (GhidraClassLoader) ClassLoader.getSystemClassLoader();
classpathList.forEach(entry -> loader.addPath(entry));
// Make sure the thing to launch is a GhidraLaunchable
@@ -86,14 +86,21 @@ public class GhidraLauncher {
* Add patch jars to the given path list. This should be done first so they take precedence in
* the classpath.
*
- * @param pathList The list of paths to add to.
- * @param installDir The application installation directory.
+ * @param pathList The list of paths to add to
+ * @param patchDir The application installation directory; may be null
*/
- private static void addPatchPaths(List pathList, ResourceFile installDir) {
- ResourceFile patchDir = new ResourceFile(installDir, "Ghidra/patch");
- if (patchDir.exists()) {
- pathList.addAll(findJarsInDir(patchDir));
+ private static void addPatchPaths(List pathList, ResourceFile patchDir) {
+ if (patchDir == null || !patchDir.exists()) {
+ return;
}
+
+ // this will allow for unbundled class files
+ pathList.add(patchDir.getAbsolutePath());
+
+ // this is each jar file, sorted for loading consistency
+ List jars = findJarsInDir(patchDir);
+ Collections.sort(jars);
+ pathList.addAll(jars);
}
/**
@@ -103,8 +110,8 @@ public class GhidraLauncher {
* @param modules The modules to get the bin directories of.
*/
private static void addModuleBinPaths(List pathList, Map modules) {
- ModuleUtilities.getModuleBinDirectories(modules).forEach(
- d -> pathList.add(d.getAbsolutePath()));
+ Collection dirs = ModuleUtilities.getModuleBinDirectories(modules);
+ dirs.forEach(d -> pathList.add(d.getAbsolutePath()));
}
/**
@@ -114,8 +121,8 @@ public class GhidraLauncher {
* @param modules The modules to get the jars of.
*/
private static void addModuleJarPaths(List pathList, Map modules) {
- ModuleUtilities.getModuleLibDirectories(modules).forEach(
- d -> pathList.addAll(findJarsInDir(d)));
+ Collection dirs = ModuleUtilities.getModuleLibDirectories(modules);
+ dirs.forEach(d -> pathList.addAll(findJarsInDir(d)));
}
/**
@@ -260,12 +267,12 @@ public class GhidraLauncher {
Map modules) {
Set fatJars = modules
- .values()
- .stream()
- .flatMap(m -> m.getFatJars().stream())
- .collect(Collectors.toSet());
+ .values()
+ .stream()
+ .flatMap(m -> m.getFatJars().stream())
+ .collect(Collectors.toSet());
- List orderedList = new ArrayList(pathList);
+ List orderedList = new ArrayList<>(pathList);
for (String path : pathList) {
if (fatJars.contains(new File(path).getName())) {
diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java
index 44d41b5b48..51d2305b5b 100644
--- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java
+++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java
@@ -55,4 +55,10 @@ public class GhidraTestApplicationLayout extends GhidraApplicationLayout {
File installDir = new File(getUserTempDir(), "ExtensionInstallDir");
return new ResourceFile(installDir);
}
+
+ @Override
+ protected ResourceFile findPatchDirectory() {
+ File dir = new File(getUserTempDir(), "patch");
+ return new ResourceFile(dir);
+ }
}
diff --git a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java
index 6d4305dc1b..aa2c46937e 100644
--- a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java
+++ b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java
@@ -42,6 +42,7 @@ public abstract class ApplicationLayout {
protected File userTempDir;
protected File userCacheDir;
protected File userSettingsDir;
+ protected ResourceFile patchDir;
protected ResourceFile extensionArchiveDir;
protected ResourceFile extensionInstallationDir;
@@ -109,10 +110,10 @@ public abstract class ApplicationLayout {
}
/**
- * Returns the directory where archived Ghidra Extensions are stored.
+ * Returns the directory where archived application Extensions are stored.
*
- * @return The Ghidra Extensions archive directory. Could be null if the
- * {@link ApplicationLayout} does not support Ghidra Extensions.
+ * @return the application Extensions archive directory. Could be null if the
+ * {@link ApplicationLayout} does not support application Extensions.
*
*/
public final ResourceFile getExtensionArchiveDir() {
@@ -120,15 +121,24 @@ public abstract class ApplicationLayout {
}
/**
- * Returns the Ghidra Extensions installation folder.
+ * Returns the application Extensions installation folder.
*
- * @return The Ghidra Extensions installation directory. Could be null if the
- * {@link ApplicationLayout} does not support Ghidra Extensions.
+ * @return the application Extensions installation directory. Could be null if the
+ * {@link ApplicationLayout} does not support application Extensions.
*/
public final ResourceFile getExtensionInstallationDir() {
return extensionInstallationDir;
}
+ /**
+ * Returns the location of the application patch directory. The patch directory can be
+ * used to modify existing code within a distribution.
+ * @return the patch directory; may be null
+ */
+ public final ResourceFile getPatchDir() {
+ return patchDir;
+ }
+
/**
* Creates the application's user directories (or ensures they already exist).
*
diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java
index 83cba824ee..c7193a51c5 100644
--- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java
+++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java
@@ -255,7 +255,6 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
@Test
public void testEditPluginPath() {
- Preferences.setProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY, "/MyPlugins");
Preferences.setPluginPaths(new String[] { "/myJar.jar", "/MyPlugins/classes" });
performAction("Edit Plugin Path", "FrontEndPlugin", false);
DialogComponentProvider dialog = getDialog();
diff --git a/GhidraBuild/patch/README.txt b/GhidraBuild/patch/README.txt
index c80aadd7a0..c49fd4483c 100644
--- a/GhidraBuild/patch/README.txt
+++ b/GhidraBuild/patch/README.txt
@@ -1,3 +1,12 @@
-Drop jar files in this directory to apply patches to an installation of Ghidra. Any jar files
-found in this directory will be placed at the front of the classpath, allowing them to override
-any existing classes in any module.
\ No newline at end of file
+This directory exits so that Ghidra releases can be patched, or overridden.
+Classes or jar files placed in this directory will found and loaded
+*before* the classes that exist in the release jar files. One exception
+is that classes in the Utility module can not be patched in this way.
+
+The jar files will be sorted by name before being prepended to the classpath
+in order to have predictable class loading between Ghidra runs. This patch
+directory will be the very first patch entry on the classpath such that any
+individual classes will be found before classes in any of the patch jar files.
+
+The class files in this directory must be in the standard java package
+directory structure.