mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-24 00:01:17 +08:00
GP-3569 - Cleanup of Extension management
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
<P>The default tool is pre-configured with a collection of plugins relevant for the Listing and
|
||||
for Debugger-related operations. As always, there is some chance that the tool will launch with
|
||||
some portion of the plugins not displayed or with a less-than-optimal layout. To verify which
|
||||
plugins you have, you can select <SPAN class="menu">File → Configure...</SPAN>. "Debugger"
|
||||
plugins you have, you can select <SPAN class="menu">File → Configure</SPAN>. "Debugger"
|
||||
should already be selected. Choosing "Configure All Plugins" (the plug icon near the top
|
||||
right), should show the full list of pre-selected plugins. Debugger-related plugins all begin
|
||||
with "Debugger". At a bare minimum, you will need the "DebuggerTargetsPlugin" and the
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
The "lib" directory is intended to hold Jar files which this contrib
|
||||
is dependent upon. This directory may be eliminated from a specific
|
||||
contrib if no other Jar files are needed.
|
||||
@@ -1,71 +1,156 @@
|
||||
<!doctype HTML public "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Extension Installation</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=windows-1252">
|
||||
<link rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
|
||||
</head>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<body>
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Extension Installation</TITLE>
|
||||
<META http-equiv="content-type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
|
||||
</HEAD>
|
||||
|
||||
<h1><a name="Extensions"></a>
|
||||
Ghidra Extensions</h1>
|
||||
|
||||
<p>Ghidra Extensions (formerly 'contribs') are Ghidra software modules that are included with a Ghidra release but not
|
||||
installed by default. Ghidra Extensions can be installed and uninstalled by Ghidra at runtime, with the changes taking
|
||||
effect when Ghidra is restarted. This dialog can be opened by selecting the <b>Extensions</b>
|
||||
option on the project file menu.</p>
|
||||
<BODY>
|
||||
<H1><A name="Extensions"></A> Ghidra Extensions</H1>
|
||||
|
||||
<p>
|
||||
<center>
|
||||
<table border="0" width="100%">
|
||||
<tr>
|
||||
<td width="100%" align="center"><img border="0" src="images/ConfigureExtensions.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</p>
|
||||
<P>Ghidra Extensions are Ghidra software modules that can be installed
|
||||
into a Ghidra distribution. This allows users to create and share new plugins and scripts.
|
||||
Ghidra ships with some pre-built extensions that not installed by default.
|
||||
</P>
|
||||
<P>Ghidra Extensions can be installed and uninstalled at runtime, with the changes taking effect
|
||||
when Ghidra is restarted. The extension installation dialog can
|
||||
be opened by selecting the <B>Install Extensions</B> option on the project <B>File</B> menu.</P>
|
||||
|
||||
<h2>Dialog Components</h2>
|
||||
<h3>Extensions List</h3>
|
||||
<blockquote>
|
||||
The list of extensions is populated when the dialog is launched. To build the list, Ghidra looks in several locations:
|
||||
<ul>
|
||||
<li>Extension Installation Directories: Contains any extensions that have been installed. The directories are located at:</li>
|
||||
<ul>
|
||||
<li><i>[user dir]/.ghidra/.ghidra_[version]/Extensions</i> - Installed/uninstalled from this dialog</li>
|
||||
<li><i>[installation dir]/Ghidra/Extensions/</i> - Installed/uninstalled from filesystem manually</li>
|
||||
</ul>
|
||||
<li>Extensions Archive Directory: This is where all archive files (zips) are stored. It is located at <i>[installation dir]/Extensions/Ghidra/</i></li>
|
||||
</ul>
|
||||
<p><b>Note: </b> Extensions that have been installed directly into the Ghidra installation directory cannot be uninstalled
|
||||
from this dialog. They must be manually removed from the filesystem.</p>
|
||||
</blockquote>
|
||||
<BLOCKQUOTE>
|
||||
<CENTER>
|
||||
<TABLE border="0" width="100%">
|
||||
<TR>
|
||||
<TD width="100%" align="center"><IMG alt="" border="0" src=
|
||||
"images/ConfigureExtensions.png"></TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
</CENTER>
|
||||
<BR>
|
||||
<BR>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<h3>Description Panel</h3>
|
||||
<blockquote>
|
||||
Displays metadata about the extension selected in the Extensions List. The information displayed is extracted from the <i>extensions.properties</i> file associated
|
||||
with the extension.
|
||||
<H2>Dialog Components</H2>
|
||||
|
||||
<h4><img border="0" src="images/program_obj.png">extension.properties</h4>
|
||||
<p>The existence of this file is what tells Ghidra that the folder or zip file is a Ghidra Extension. It is a simple property file that can contain the following 4 attributes:</p>
|
||||
<ul>
|
||||
<li><b>name</b>: Human-readable name of the extension. This is what will be displayed in the dialog.</li>
|
||||
<li><b>desc</b>: Brief description of the extension.</li>
|
||||
<li><b>author</b>: Creator of the extension.</li>
|
||||
<li><b>createdOn</b>: Date of extension creation, in the format mm/dd/yyyy</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
<H3>Extensions Table</H3>
|
||||
|
||||
<h3><a name="ExtensionTools"></a>Tools Panel</h3>
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li> <img border="0" src="images/Plus.png"> Allows the user to install a new extension. An extension can be any folder or zip file that contains an <i>extensions.properties</i> file.
|
||||
When one of these is selected, it will be copied to the extension installation folder and extracted (if it is a zip).
|
||||
<li> <img border="0" src="Icons.REFRESH_ICON"> Reloads the Extensions List
|
||||
</ul>
|
||||
</blockquote>
|
||||
<BLOCKQUOTE>
|
||||
<P>The list of extensions is populated when the dialog is launched. To build the list, Ghidra
|
||||
looks in several locations:</P>
|
||||
|
||||
<p class="relatedtopic">Related Topics:</p>
|
||||
</body>
|
||||
</html>
|
||||
<UL>
|
||||
<LI>Extension Installation Directories: Contains any extensions that have been installed.
|
||||
The directories are located at:</LI>
|
||||
|
||||
<LI style="list-style: none">
|
||||
<UL>
|
||||
<LI><I>[user dir]/.ghidra/.ghidra_[version]/Extensions</I> - Installed/uninstalled from
|
||||
this dialog</LI>
|
||||
|
||||
<LI><I>[installation dir]/Ghidra/Extensions/</I> - Installed/uninstalled from
|
||||
filesystem manually</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
|
||||
<LI>Extensions Archive Directory: This is where archive files (zips) that are bundled with
|
||||
the distribution are stored. It is
|
||||
located at <I>[installation dir]/Extensions/Ghidra/</I>. This directory is not intended for
|
||||
end-user extensions.
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/tip.png" alt="" border="0">The color red is used in the table
|
||||
to indicate that the extension version does not match the Ghidra version.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<P><B>Note:</B> Extensions that have been installed directly into the Ghidra installation
|
||||
directory cannot be uninstalled from this dialog. They must be manually removed from the
|
||||
filesystem.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Description Panel</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Displays metadata about the extension selected in the Extensions List. The information
|
||||
displayed is extracted from the <CODE><I>extensions.properties</I></CODE> file associated with the
|
||||
extension.</P>
|
||||
|
||||
<P>The existence of this file is what tells Ghidra that the folder or zip file is a Ghidra
|
||||
Extension. It is a simple property file that can contain the following attributes:</P>
|
||||
|
||||
<UL>
|
||||
<LI><B>name</B>: Human-readable name of the extension. This is what will be displayed in
|
||||
the dialog.</LI>
|
||||
|
||||
<LI><B>description</B>: Brief description of the extension.</LI>
|
||||
|
||||
<LI><B>author</B>: Creator of the extension.</LI>
|
||||
|
||||
<LI><B>createdOn</B>: Date of extension creation, in the format mm/dd/yyyy.</LI>
|
||||
|
||||
<LI><B>version</B>: The version of Ghidra for which this extension was built.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="ExtensionTools"></A>Tools Panel</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI><IMG alt="" border="0" src="images/Plus.png"> Allows the user to install a
|
||||
new extension. An extension can be any folder or zip file that contains an
|
||||
<I>extensions.properties</I> file. When one of these is selected, it will be copied to the
|
||||
extension installation folder and extracted (if it is a zip).</LI>
|
||||
|
||||
<LI> <IMG alt="" border="0" src="Icons.REFRESH_ICON"> Reloads the Extensions
|
||||
List</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<H2>Building Extensions</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
An extension is simply a Ghidra module that contains an <CODE>extension.properties</CODE> file.
|
||||
Building an extension is very similar to building a ghidra module, which is done by using
|
||||
<CODE>gradle</CODE>.
|
||||
</P>
|
||||
<P>
|
||||
Ghidra includes a <CODE>Skeleton</CODE> module in the distribution that is meant to be used as
|
||||
a template when creating extensions. This module can be found at
|
||||
</P>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
<CODE CLASS="path"><GHIDRA_INSTALL_DIR>/Extensions/Ghidra</CODE>
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
<P>
|
||||
Copy and rename this directory to get started writing your own module. You can then use
|
||||
<CODE>gradle</CODE> to build the extension by running this command from within your extension
|
||||
directory:
|
||||
</P>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
<CODE CLASS="path">gradle -PGHIDRA_INSTALL_DIR=/path/to/ghidra/ghidra_<version>/ buildExtension</CODE>
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
|
||||
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
<UL>
|
||||
<LI><A href="help/topics/Tool/Configure_Tool.htm">Configuring Tool Plugins</A></LI>
|
||||
</UL>
|
||||
|
||||
<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
<P>This plugin doesn't perform any natural language translation by itself. The
|
||||
user must install <b>string translation service</b>s that do the actual translation.
|
||||
Extensions to Ghidra are installed via the <b>File
|
||||
<IMG src="help/shared/arrow.gif" alt="->" border="0"> <a href="../FrontEndPlugin/Extensions.htm">Install Extensions...</a></b>
|
||||
<IMG src="help/shared/arrow.gif" alt="->" border="0"> <a href="../FrontEndPlugin/Extensions.htm">Install Extensions</a></b>
|
||||
menu.</P>
|
||||
|
||||
<P>When a string has been translated, the translated value will be shown in place of
|
||||
|
||||
@@ -30,8 +30,8 @@ import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.data.DomainObjectAdapter;
|
||||
import ghidra.framework.main.FrontEndTool;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.dialog.ExtensionUtils;
|
||||
import ghidra.framework.project.DefaultProjectManager;
|
||||
import ghidra.framework.project.extensions.ExtensionUtils;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.util.*;
|
||||
@@ -81,7 +81,7 @@ public class GhidraRun implements GhidraLaunchable {
|
||||
updateSplashScreenStatusMessage("Populating Ghidra help...");
|
||||
GhidraHelpService.install();
|
||||
|
||||
ExtensionUtils.cleanupUninstalledExtensions();
|
||||
ExtensionUtils.initializeExtensions();
|
||||
|
||||
// Allows handling of old content which did not have a content type property
|
||||
DomainObjectAdapter.setDefaultContentClass(ProgramDB.class);
|
||||
|
||||
@@ -26,7 +26,7 @@ import generic.jar.*;
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraLaunchable;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.framework.plugintool.dialog.ExtensionUtils;
|
||||
import ghidra.framework.project.extensions.ExtensionUtils;
|
||||
import ghidra.util.classfinder.ClassFinder;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import ghidra.framework.model.Project;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
||||
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
+5
-6
@@ -182,9 +182,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
||||
}
|
||||
|
||||
protected void deleteUserScripts() throws IOException {
|
||||
|
||||
Path userScriptDir = Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||
FileUtilities.forEachFile(userScriptDir, paths -> paths.forEach(p -> delete(p)));
|
||||
FileUtilities.forEachFile(userScriptDir, script -> delete(script));
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
@@ -988,10 +987,10 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
||||
|
||||
// destroy any NewScriptxxx files...and Temp ones too
|
||||
List<ResourceFile> paths = provider.getBundleHost()
|
||||
.getBundleFiles()
|
||||
.stream()
|
||||
.filter(ResourceFile::isDirectory)
|
||||
.collect(Collectors.toList());
|
||||
.getBundleFiles()
|
||||
.stream()
|
||||
.filter(ResourceFile::isDirectory)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (ResourceFile path : paths) {
|
||||
File file = path.getFile(false);
|
||||
|
||||
@@ -1,349 +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.util.extensions;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.plugintool.dialog.*;
|
||||
import utilities.util.FileUtilities;
|
||||
import utility.application.ApplicationLayout;
|
||||
|
||||
/**
|
||||
* Tests for the {@link ExtensionUtils} class.
|
||||
*
|
||||
*/
|
||||
public class ExtensionUtilsTest extends AbstractDockingTest {
|
||||
|
||||
// Name used in all tests when creating extensions.
|
||||
private String DEFAULT_EXT_NAME = "test";
|
||||
|
||||
private ApplicationLayout gLayout;
|
||||
|
||||
/*
|
||||
* Create dummy archive and installation folders in the temp space that we can populate
|
||||
* with extensions.
|
||||
*/
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
|
||||
gLayout = Application.getApplicationLayout();
|
||||
|
||||
// Verify that the archive and install directories are empty (each test requires
|
||||
// we start with a clean slate). If they're not empty, CORRECT THE SITUATION.
|
||||
if (!checkCleanInstall()) {
|
||||
FileUtilities.deleteDir(gLayout.getExtensionArchiveDir().getFile(false));
|
||||
for (ResourceFile installDir : gLayout.getExtensionInstallationDirs()) {
|
||||
FileUtilities.deleteDir(installDir.getFile(false));
|
||||
}
|
||||
}
|
||||
|
||||
createExtensionDirs();
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that we can install an extension from a .zip file.
|
||||
*/
|
||||
@Test
|
||||
public void testInstallExtensionFromZip() throws IOException {
|
||||
|
||||
// Create an extension and install it.
|
||||
ResourceFile rFile = new ResourceFile(createExtensionZip(DEFAULT_EXT_NAME));
|
||||
ExtensionUtils.install(rFile);
|
||||
|
||||
// Verify there is something in the installation directory and it has the correct name
|
||||
checkDirtyInstall(DEFAULT_EXT_NAME);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that we can install an extension from a folder.
|
||||
*/
|
||||
@Test
|
||||
public void testInstallExtensionFromFolder() throws IOException {
|
||||
|
||||
// Create an extension and install it.
|
||||
ResourceFile rFile = createExtensionFolder();
|
||||
ExtensionUtils.install(rFile);
|
||||
|
||||
// Verify the extension is in the install folder and has the correct name
|
||||
checkDirtyInstall(DEFAULT_EXT_NAME);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that we can uninstall an extension.
|
||||
*/
|
||||
@Test
|
||||
public void testUninstallExtension() throws ExtensionException, IOException {
|
||||
|
||||
// Create an extension and install it.
|
||||
ResourceFile rFile = new ResourceFile(createExtensionZip(DEFAULT_EXT_NAME));
|
||||
ExtensionUtils.install(rFile);
|
||||
|
||||
checkDirtyInstall(DEFAULT_EXT_NAME);
|
||||
|
||||
// Get the extension object that we need to uninstall - there will only
|
||||
// be one in the set.
|
||||
Set<ExtensionDetails> extensions = ExtensionUtils.getExtensions();
|
||||
assertTrue(extensions.size() == 1);
|
||||
|
||||
ExtensionDetails ext = extensions.iterator().next();
|
||||
|
||||
// Now uninstall it and verify we have a clean install folder
|
||||
ExtensionUtils.uninstall(ext);
|
||||
|
||||
checkCleanInstall();
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that trying to install an extension when there's already one with the same
|
||||
* name installed will overwrite the existing and not throw an exception
|
||||
*
|
||||
* @throws Exception if there's a problem creating the temp extension folder
|
||||
*/
|
||||
@Test
|
||||
public void testInstallExtensionDuplicate() throws Exception {
|
||||
|
||||
// Create an extension and install it.
|
||||
ResourceFile rFile = createExtensionFolder();
|
||||
ExtensionUtils.install(rFile);
|
||||
|
||||
// Now create another extension with the same name and try
|
||||
// to install it.
|
||||
rFile = new ResourceFile(createExtensionZip(DEFAULT_EXT_NAME));
|
||||
|
||||
boolean install = ExtensionUtils.install(rFile);
|
||||
assertEquals(install, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that we can properly recognize a valid .zip file.
|
||||
*/
|
||||
@Test
|
||||
public void testIsZip() throws IOException, ExtensionException {
|
||||
File zipFile = createExtensionZip(DEFAULT_EXT_NAME);
|
||||
assertTrue(ExtensionUtils.isZip(zipFile));
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that we can identify when a .zip is a valid extension archive vs.
|
||||
* just a regular old zip (ROZ).
|
||||
* <p>
|
||||
* Note: The presence of an extensions.properties file is the difference.
|
||||
*/
|
||||
@Test
|
||||
public void testIsExtension_Zip() throws IOException, ExtensionException {
|
||||
File zipFile1 = createExtensionZip(DEFAULT_EXT_NAME);
|
||||
assertTrue(ExtensionUtils.isExtension(new ResourceFile(zipFile1)));
|
||||
|
||||
File zipFile2 = createNonExtensionZip(DEFAULT_EXT_NAME);
|
||||
assertTrue(!ExtensionUtils.isExtension(new ResourceFile(zipFile2)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that we can recognize when a directory represents an extension.
|
||||
* <p>
|
||||
* Note: The presence of an extensions.properties file is the difference.
|
||||
*/
|
||||
@Test
|
||||
public void testIsExtension_Folder() throws IOException, ExtensionException {
|
||||
File extDir = createTempDirectory("TestExtFolder");
|
||||
new File(extDir, "extension.properties").createNewFile();
|
||||
assertTrue(ExtensionUtils.isExtension(new ResourceFile(extDir)));
|
||||
|
||||
File nonExtDir = createTempDirectory("TestNonExtFolder");
|
||||
assertTrue(!ExtensionUtils.isExtension(new ResourceFile(nonExtDir)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that the we can retrieve all unique extensions in the archive and
|
||||
* install folders.
|
||||
* <p>
|
||||
* Note: This test eliminates the need to test the methods for retrieving archived vs. installed
|
||||
* extensions individually.
|
||||
*/
|
||||
@Test
|
||||
public void testGetExtensions() throws ExtensionException, IOException {
|
||||
|
||||
// First create an extension and install it, so we have 2 extensions: one in
|
||||
// the archive folder, and one in the install folder.
|
||||
File zipFile = createExtensionZip(DEFAULT_EXT_NAME);
|
||||
ExtensionUtils.install(new ResourceFile(zipFile));
|
||||
|
||||
// Now getExtensions should give us exactly 1 extension in the return.
|
||||
Set<ExtensionDetails> extensions = ExtensionUtils.getExtensions();
|
||||
assertTrue(extensions.size() == 1);
|
||||
|
||||
// Now add an archive extension with a different name and see if we get
|
||||
// 2 total extensions.
|
||||
createExtensionZip("Extension2");
|
||||
extensions = ExtensionUtils.getExtensions();
|
||||
assertTrue(extensions.size() == 2);
|
||||
|
||||
// Now add a 3rd extension and install it. See if we have 3 total extensions.
|
||||
File extension3 = createExtensionZip("Extension3");
|
||||
ExtensionUtils.install(new ResourceFile(extension3));
|
||||
extensions = ExtensionUtils.getExtensions();
|
||||
assertTrue(extensions.size() == 3);
|
||||
}
|
||||
|
||||
/*
|
||||
* Catch-all test for verifying that 'bad' inputs to utility functions are
|
||||
* handled properly.
|
||||
*/
|
||||
@Test
|
||||
public void testBadInputs() {
|
||||
|
||||
boolean foundError = false;
|
||||
|
||||
try {
|
||||
ExtensionUtils.uninstall((ExtensionDetails) null);
|
||||
ExtensionUtils.isExtension(null);
|
||||
ExtensionUtils.isZip(null);
|
||||
ExtensionUtils.install(new ResourceFile(new File("this/file/does/not/exist")));
|
||||
ExtensionUtils.install((ResourceFile) null);
|
||||
ExtensionUtils.install((ExtensionDetails) null, true);
|
||||
}
|
||||
catch (Exception e) {
|
||||
foundError = true;
|
||||
}
|
||||
|
||||
assertTrue(foundError == false);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
/*
|
||||
* Creates the extension archive and installation directories.
|
||||
*
|
||||
* @throws IOException if there's an error creating the directories
|
||||
*/
|
||||
private void createExtensionDirs() throws IOException {
|
||||
|
||||
ResourceFile extensionDir = gLayout.getExtensionArchiveDir();
|
||||
if (!extensionDir.exists()) {
|
||||
if (!extensionDir.mkdir()) {
|
||||
throw new IOException("Failed to create extension archive directory for test");
|
||||
}
|
||||
}
|
||||
|
||||
ResourceFile installDir = gLayout.getExtensionInstallationDirs().get(0);
|
||||
if (!installDir.exists()) {
|
||||
if (!installDir.mkdir()) {
|
||||
throw new IOException("Failed to create extension installation directory for test");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that the installation folder is empty.
|
||||
*/
|
||||
private boolean checkCleanInstall() {
|
||||
ResourceFile[] files = gLayout.getExtensionInstallationDirs().get(0).listFiles();
|
||||
return (files == null || files.length == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that the installation folder is not empty and contains a folder
|
||||
* with the given name.
|
||||
*
|
||||
* @param name the name of the installed extension
|
||||
*/
|
||||
private void checkDirtyInstall(String name) {
|
||||
ResourceFile[] files = gLayout.getExtensionInstallationDirs().get(0).listFiles();
|
||||
assertTrue(files.length >= 1);
|
||||
assertTrue(files[0].getName().equals(name));
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a valid extension in the archive folder. This extension is not a
|
||||
* .zip, but a folder.
|
||||
*
|
||||
* @return the file representing the extension
|
||||
* @throws IOException if there's an error creating the extension
|
||||
*/
|
||||
private ResourceFile createExtensionFolder() throws IOException {
|
||||
|
||||
ResourceFile root = new ResourceFile(gLayout.getExtensionArchiveDir(), DEFAULT_EXT_NAME);
|
||||
root.mkdir();
|
||||
|
||||
// Have to add a prop file so this will be recognized as an extension
|
||||
File propFile = new ResourceFile(root, "extension.properties").getFile(false);
|
||||
propFile.createNewFile();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a generic zip that is a valid extension archive.
|
||||
*
|
||||
* @param zipName name of the zip to create
|
||||
* @return a zip file
|
||||
* @throws IOException if there's an error creating the zip
|
||||
*/
|
||||
private File createExtensionZip(String zipName) throws IOException {
|
||||
|
||||
File f = new File(gLayout.getExtensionArchiveDir().getFile(false), zipName + ".zip");
|
||||
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(f))) {
|
||||
out.putNextEntry(new ZipEntry(zipName + "/"));
|
||||
out.putNextEntry(new ZipEntry(zipName + "/extension.properties"));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("name:" + zipName);
|
||||
byte[] data = sb.toString().getBytes();
|
||||
out.write(data, 0, data.length);
|
||||
out.closeEntry();
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a generic zip that is NOT a valid extension archive (because it doesn't
|
||||
* have an extension.properties file).
|
||||
*
|
||||
* @param zipName name of the zip to create
|
||||
* @return a zip file
|
||||
* @throws IOException if there's an error creating the zip
|
||||
*/
|
||||
private File createNonExtensionZip(String zipName) throws IOException {
|
||||
|
||||
File f = new File(gLayout.getExtensionArchiveDir().getFile(false), zipName + ".zip");
|
||||
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(f))) {
|
||||
out.putNextEntry(new ZipEntry(zipName + "/"));
|
||||
out.putNextEntry(new ZipEntry(zipName + "/randomFile.txt"));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("name:" + zipName);
|
||||
byte[] data = sb.toString().getBytes();
|
||||
out.write(data, 0, data.length);
|
||||
out.closeEntry();
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -43,7 +43,7 @@
|
||||
a Code Browser by selecting the
|
||||
</p>
|
||||
<div class="informalexample">
|
||||
<span class="bold"><strong>File -> Configure...</strong></span>
|
||||
<span class="bold"><strong>File -> Configure</strong></span>
|
||||
</div>
|
||||
<p>
|
||||
menu option, then clicking on the <span class="emphasis"><em>Configure</em></span> link under the
|
||||
|
||||
+23
-118
@@ -15,85 +15,30 @@
|
||||
*/
|
||||
package ghidra.feature.vt.api;
|
||||
|
||||
import static ghidra.feature.vt.db.VTTestUtils.addr;
|
||||
import static ghidra.feature.vt.db.VTTestUtils.createMatchSetWithOneMatch;
|
||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.CALLING_CONVENTION;
|
||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_RETURN_TYPE;
|
||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_SIGNATURE;
|
||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.INLINE;
|
||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.NO_RETURN;
|
||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_COMMENTS;
|
||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_DATA_TYPES;
|
||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_NAMES;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static ghidra.feature.vt.db.VTTestUtils.*;
|
||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.feature.vt.api.db.VTSessionDB;
|
||||
import ghidra.feature.vt.api.main.VTAssociationStatus;
|
||||
import ghidra.feature.vt.api.main.VTMarkupItem;
|
||||
import ghidra.feature.vt.api.main.VTMarkupItemStatus;
|
||||
import ghidra.feature.vt.api.main.VTMatch;
|
||||
import ghidra.feature.vt.api.main.VTSession;
|
||||
import ghidra.feature.vt.api.main.*;
|
||||
import ghidra.feature.vt.api.markuptype.FunctionSignatureMarkupType;
|
||||
import ghidra.feature.vt.gui.plugin.VTController;
|
||||
import ghidra.feature.vt.gui.plugin.VTControllerImpl;
|
||||
import ghidra.feature.vt.gui.plugin.VTPlugin;
|
||||
import ghidra.feature.vt.gui.task.ApplyMatchTask;
|
||||
import ghidra.feature.vt.gui.task.ClearMatchTask;
|
||||
import ghidra.feature.vt.gui.task.VtTask;
|
||||
import ghidra.feature.vt.gui.plugin.*;
|
||||
import ghidra.feature.vt.gui.task.*;
|
||||
import ghidra.feature.vt.gui.util.MatchInfo;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.CallingConventionChoices;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.CommentChoices;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.FunctionNameChoices;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.FunctionSignatureChoices;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.HighestSourcePriorityChoices;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.LabelChoices;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.ParameterDataTypeChoices;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.ReplaceChoices;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.SourcePriorityChoices;
|
||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.*;
|
||||
import ghidra.feature.vt.gui.util.VTOptionDefines;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.ArrayDataType;
|
||||
import ghidra.program.model.data.BooleanDataType;
|
||||
import ghidra.program.model.data.CategoryPath;
|
||||
import ghidra.program.model.data.CharDataType;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.FloatDataType;
|
||||
import ghidra.program.model.data.IntegerDataType;
|
||||
import ghidra.program.model.data.Pointer;
|
||||
import ghidra.program.model.data.PointerDataType;
|
||||
import ghidra.program.model.data.StructureDataType;
|
||||
import ghidra.program.model.data.TypeDef;
|
||||
import ghidra.program.model.data.TypedefDataType;
|
||||
import ghidra.program.model.data.Undefined4DataType;
|
||||
import ghidra.program.model.data.VoidDataType;
|
||||
import ghidra.program.model.data.WordDataType;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.lang.CompilerSpecDescription;
|
||||
import ghidra.program.model.lang.CompilerSpecID;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
import ghidra.program.model.lang.LanguageNotFoundException;
|
||||
import ghidra.program.model.lang.LanguageService;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.IncompatibleLanguageException;
|
||||
import ghidra.program.model.listing.Parameter;
|
||||
import ghidra.program.model.listing.ParameterImpl;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.program.util.DefaultLanguageService;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
@@ -104,8 +49,6 @@ import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
// private static final String TEST_SOURCE_PROGRAM_NAME = "VersionTracking/WallaceSrc";
|
||||
// private static final String TEST_DESTINATION_PROGRAM_NAME = "VersionTracking/WallaceVersion2";
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private VTController controller;
|
||||
@@ -130,8 +73,8 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
||||
public void setUp() throws Exception {
|
||||
|
||||
env = new TestEnv();
|
||||
sourceProgram = createSourceProgram();// env.getProgram(TEST_SOURCE_PROGRAM_NAME);
|
||||
destinationProgram = createDestinationProgram();// env.getProgram(TEST_DESTINATION_PROGRAM_NAME);
|
||||
sourceProgram = createSourceProgram();
|
||||
destinationProgram = createDestinationProgram();
|
||||
tool = env.getTool();
|
||||
|
||||
tool.addPlugin(VTPlugin.class.getName());
|
||||
@@ -142,37 +85,15 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
||||
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
|
||||
sourceProgram, destinationProgram, this);
|
||||
|
||||
runSwing(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
controller.openVersionTrackingSession(session);
|
||||
}
|
||||
});
|
||||
runSwing(() -> controller.openVersionTrackingSession(session));
|
||||
|
||||
setAllOptionsToDoNothing();
|
||||
|
||||
//
|
||||
// env = new VTTestEnv();
|
||||
// session = env.createSession(TEST_SOURCE_PROGRAM_NAME, TEST_DESTINATION_PROGRAM_NAME);
|
||||
// try {
|
||||
// correlator =
|
||||
// vtTestEnv.correlate(new ExactMatchInstructionsProgramCorrelatorFactory(), null,
|
||||
// TaskMonitor.DUMMY);
|
||||
// }
|
||||
// catch (Exception e) {
|
||||
// Assert.fail(e.getMessage());
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// sourceProgram = env.getSourceProgram();
|
||||
// destinationProgram = env.getDestinationProgram();
|
||||
// controller = env.getVTController();
|
||||
// env.showTool();
|
||||
//
|
||||
// Logger functionLogger = Logger.getLogger(FunctionDB.class);
|
||||
// functionLogger.setLevel(Level.TRACE);
|
||||
//
|
||||
// Configurator.setLevel(functionLogger.getName(), org.apache.logging.log4j.Level.TRACE);
|
||||
//
|
||||
// Logger variableLogger = Logger.getLogger(VariableSymbolDB.class);
|
||||
// variableLogger.setLevel(Level.TRACE);
|
||||
// Configurator.setLevel(variableLogger.getName(), org.apache.logging.log4j.Level.TRACE);
|
||||
|
||||
}
|
||||
|
||||
@@ -478,7 +399,6 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
||||
checkSignatures("undefined use(Gadget * this, Person * person)",
|
||||
"undefined FUN_00401040(void * this, undefined4 param_1)");
|
||||
|
||||
|
||||
tx(sourceProgram, () -> {
|
||||
sourceFunction.setCustomVariableStorage(true);
|
||||
|
||||
@@ -487,7 +407,6 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
||||
SourceType.USER_DEFINED);
|
||||
});
|
||||
|
||||
|
||||
DataType personType = sourceProgram.getDataTypeManager().getDataType("/Person");
|
||||
assertNotNull(personType);
|
||||
|
||||
@@ -495,7 +414,6 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
||||
destinationFunction.setCustomVariableStorage(true);
|
||||
});
|
||||
|
||||
|
||||
// Set the function signature options for this test
|
||||
ToolOptions applyOptions = controller.getOptions();
|
||||
applyOptions.setEnum(FUNCTION_SIGNATURE, FunctionSignatureChoices.REPLACE);
|
||||
@@ -744,24 +662,14 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
||||
public void testApplyMatch_ReplaceSignatureAndCallingConventionDifferentLanguageFailUsingNameMatch()
|
||||
throws Exception {
|
||||
|
||||
runSwing(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
controller.closeCurrentSessionIgnoringChanges();
|
||||
}
|
||||
});
|
||||
runSwing(() -> controller.closeCurrentSessionIgnoringChanges());
|
||||
|
||||
env.release(destinationProgram);
|
||||
destinationProgram = createToyDestinationProgram();// env.getProgram("helloProgram"); // get a program without cdecl
|
||||
session =
|
||||
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
|
||||
sourceProgram, destinationProgram, this);
|
||||
runSwing(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
controller.openVersionTrackingSession(session);
|
||||
}
|
||||
});
|
||||
runSwing(() -> controller.openVersionTrackingSession(session));
|
||||
|
||||
useMatch("0x00401040", "0x00010938");
|
||||
|
||||
@@ -1699,12 +1607,9 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
||||
final String[] sourceStringBox = new String[1];
|
||||
final String[] destinationStringBox = new String[1];
|
||||
|
||||
runSwing(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceStringBox[0] = sourceFunction.getPrototypeString(false, false);
|
||||
destinationStringBox[0] = destinationFunction.getPrototypeString(false, false);
|
||||
}
|
||||
runSwing(() -> {
|
||||
sourceStringBox[0] = sourceFunction.getPrototypeString(false, false);
|
||||
destinationStringBox[0] = destinationFunction.getPrototypeString(false, false);
|
||||
});
|
||||
|
||||
assertEquals(expectedSourceSignature, sourceStringBox[0]);
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
modified using the <A href="#Edit_Theme">Theme Editor Dialog</A>. The Theme Editor Dialog
|
||||
can be invoked from the main application menu using the
|
||||
<B>Edit<IMG alt="" src="help/shared/arrow.gif" border="0">Theme<IMG alt=""
|
||||
src="help/shared/arrow.gif" border="0">Configure..." </b> menu. Choose the
|
||||
src="help/shared/arrow.gif" border="0">Configure" </b> menu. Choose the
|
||||
tab for the appropriate type and double-click on the ID column or Current Value column of the
|
||||
item you want to change. An editor for that type will appear.</P>
|
||||
|
||||
|
||||
@@ -646,11 +646,11 @@ public abstract class AbstractGenericTest extends AbstractGTest {
|
||||
*/
|
||||
public static void setErrorsExpected(boolean expected) {
|
||||
if (expected) {
|
||||
Msg.error(AbstractGenericTest.class, ">>>>>>>>>>>>>>>> Expected Exception");
|
||||
Msg.error(AbstractGenericTest.class, ">>>>>>>>>>>>>>>> Expected Errors");
|
||||
ConcurrentTestExceptionHandler.disable();
|
||||
}
|
||||
else {
|
||||
Msg.error(AbstractGenericTest.class, "<<<<<<<<<<<<<<<< End Expected Exception");
|
||||
Msg.error(AbstractGenericTest.class, "<<<<<<<<<<<<<<<< End Expected Errors");
|
||||
ConcurrentTestExceptionHandler.enable();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler
|
||||
* environmental issues rather than real problems. This method is intended to ignore
|
||||
* these less-than-serious issues.
|
||||
*
|
||||
* @param throwable the throwable to examine
|
||||
* @param t the throwable to examine
|
||||
* @return true if it should be ignored
|
||||
*/
|
||||
private static boolean isKnownTestMachineTimingBug(Throwable t) {
|
||||
|
||||
@@ -805,7 +805,7 @@ public class Application {
|
||||
*/
|
||||
public static Collection<ResourceFile> getLibraryDirectories() {
|
||||
checkAppInitialized();
|
||||
return ModuleUtilities.getModuleLibDirectories(app.layout.getModules());
|
||||
return ModuleUtilities.getModuleLibDirectories(app.layout.getModules().values());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,7 +61,7 @@ class ClassJar extends ClassLocation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void getClasses(Set<Class<?>> set, TaskMonitor monitor) {
|
||||
protected void getClasses(Set<Class<?>> set, TaskMonitor monitor) {
|
||||
checkForDuplicates(set);
|
||||
set.addAll(classes);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.util.classfinder;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -31,26 +32,35 @@ abstract class ClassLocation {
|
||||
|
||||
protected static final String CLASS_EXT = ".class";
|
||||
|
||||
final Logger log = LogManager.getLogger(getClass());
|
||||
protected final Logger log = LogManager.getLogger(getClass());
|
||||
|
||||
protected Set<Class<?>> classes = new HashSet<>();
|
||||
|
||||
abstract void getClasses(Set<Class<?>> set, TaskMonitor monitor) throws CancelledException;
|
||||
|
||||
void checkForDuplicates(Set<Class<?>> existingClasses) {
|
||||
if (!log.isTraceEnabled()) {
|
||||
return;
|
||||
}
|
||||
protected abstract void getClasses(Set<Class<?>> set, TaskMonitor monitor)
|
||||
throws CancelledException;
|
||||
|
||||
protected void checkForDuplicates(Set<Class<?>> existingClasses) {
|
||||
for (Class<?> c : classes) {
|
||||
// Note: our class and a matching class in 'existingClasses' will be '==' since the
|
||||
// class loader loaded the class by name--it will always find the same class, in
|
||||
// classpath order.
|
||||
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;
|
||||
log.warn(() -> generateMessage(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String generateMessage(Class<?> c) {
|
||||
return String.format("Class defined in multiple locations: %s. Keeping class loaded " +
|
||||
"from %s; ignoring class from %s", c, toLocation(c), this);
|
||||
}
|
||||
|
||||
private String toLocation(Class<?> clazz) {
|
||||
String name = clazz.getName();
|
||||
String classAsPath = '/' + name.replace('.', '/') + ".class";
|
||||
URL url = clazz.getResource(classAsPath);
|
||||
String urlPath = url.getPath();
|
||||
int index = urlPath.indexOf(classAsPath);
|
||||
return urlPath.substring(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class ClassPackage extends ClassLocation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void getClasses(Set<Class<?>> set, TaskMonitor monitor) throws CancelledException {
|
||||
protected void getClasses(Set<Class<?>> set, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
checkForDuplicates(set);
|
||||
|
||||
@@ -120,4 +120,9 @@ class ClassPackage extends ClassLocation {
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return packageDir.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<logger name="ghidra.framework" level="DEBUG"/>
|
||||
<logger name="ghidra.graph" level="DEBUG" />
|
||||
|
||||
|
||||
<!--
|
||||
Turn off debug for specific project classes.
|
||||
Leave ghidra.framework.project at DEBUG for tests; specific classes are higher to
|
||||
@@ -52,14 +53,16 @@
|
||||
<logger name="ghidra.framework.project" level="DEBUG"/>
|
||||
<logger name="ghidra.framework.project.DefaultProject" level="WARN"/>
|
||||
<logger name="ghidra.framework.project.DefaultProjectManager" level="INFO"/>
|
||||
|
||||
|
||||
<logger name="functioncalls" level="DEBUG" />
|
||||
<logger name="generic.random" level="WARN"/>
|
||||
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
||||
<logger name="ghidra.net" level="WARN"/>
|
||||
<logger name="ghidra.app.plugin.core.misc.RecoverySnapshotMgrPlugin" level="INFO"/>
|
||||
<logger name="ghidra.app.plugin.core.misc.RecoverySnapshotMgrPlugin" level="INFO"/>
|
||||
<logger name="ghidra.framework.project.extensions" level="DEBUG" />
|
||||
<logger name="ghidra.framework.store.local" level="INFO"/>
|
||||
<logger name="ghidra.pcodeCPort.slgh_compile" level="INFO"/>
|
||||
<logger name="ghidra.pcodeCPort.slgh_compile" level="INFO"/>
|
||||
<logger name="ghidra.plugins" level="INFO"/>
|
||||
<logger name="ghidra.program.database" level="DEBUG" />
|
||||
<logger name="ghidra.program.model.lang.xml" level="DEBUG"/>
|
||||
<logger name="ghidra.app.plugin.assembler" level="DEBUG" />
|
||||
@@ -73,11 +76,11 @@
|
||||
<logger name="ghidra.app.util.opinion" level="DEBUG" />
|
||||
<logger name="ghidra.util.classfinder" level="DEBUG" />
|
||||
<logger name="ghidra.util.task" level="DEBUG" />
|
||||
<logger name="org.jungrapht.visualization" level="WARN" />
|
||||
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
||||
<logger name="org.jungrapht.visualization" level="WARN" />
|
||||
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
||||
|
||||
<Root level="ALL">
|
||||
<AppenderRef ref="console" level="DEBUG"/>
|
||||
<AppenderRef ref="console" level="DEBUG"/>
|
||||
<AppenderRef ref="detail" level="DEBUG"/>
|
||||
<AppenderRef ref="script" level="DEBUG"/>
|
||||
<AppenderRef ref="logPanel" level="INFO"/>
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
<logger name="generic.random" level="WARN"/>
|
||||
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
||||
<logger name="ghidra.net" level="WARN"/>
|
||||
<logger name="ghidra.app.plugin.core.misc.RecoverySnapshotMgrPlugin" level="INFO"/>
|
||||
<logger name="ghidra.app.plugin.core.misc.RecoverySnapshotMgrPlugin" level="INFO"/>
|
||||
<logger name="ghidra.framework.project.extensions" level="DEBUG" />
|
||||
<logger name="ghidra.framework.store.local" level="INFO"/>
|
||||
<logger name="ghidra.pcodeCPort.slgh_compile" level="INFO"/>
|
||||
<logger name="ghidra.plugins" level="INFO"/>
|
||||
@@ -73,7 +74,10 @@
|
||||
<logger name="ghidra.app.util.importer" level="INFO" />
|
||||
<logger name="ghidra.app.util.opinion" level="DEBUG" />
|
||||
<logger name="ghidra.util.classfinder" level="DEBUG" />
|
||||
<logger name="ghidra.util.task" level="DEBUG" />
|
||||
<logger name="ghidra.util.task" level="DEBUG" />
|
||||
<logger name="org.jungrapht.visualization" level="WARN" />
|
||||
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
||||
|
||||
|
||||
<Root level="ALL">
|
||||
<AppenderRef ref="console" level="TRACE"/>
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
package ghidra.framework.main;
|
||||
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
||||
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||
|
||||
/**
|
||||
* A configuration that only includes {@link ApplicationLevelPlugin} plugins.
|
||||
|
||||
@@ -647,7 +647,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
|
||||
}
|
||||
};
|
||||
MenuData menuData =
|
||||
new MenuData(new String[] { ToolConstants.MENU_FILE, "Install Extensions..." }, null,
|
||||
new MenuData(new String[] { ToolConstants.MENU_FILE, "Install Extensions" }, null,
|
||||
CONFIGURE_GROUP);
|
||||
menuData.setMenuSubGroup(CONFIGURE_GROUP + 2);
|
||||
installExtensionsAction.setMenuBarData(menuData);
|
||||
@@ -674,7 +674,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
|
||||
}
|
||||
};
|
||||
|
||||
MenuData menuData = new MenuData(new String[] { ToolConstants.MENU_FILE, "Configure..." },
|
||||
MenuData menuData = new MenuData(new String[] { ToolConstants.MENU_FILE, "Configure" },
|
||||
null, CONFIGURE_GROUP);
|
||||
menuData.setMenuSubGroup(CONFIGURE_GROUP + 1);
|
||||
configureToolAction.setMenuBarData(menuData);
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.framework.plugintool.PluginEvent;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
|
||||
@@ -63,18 +62,6 @@ public interface ToolServices {
|
||||
*/
|
||||
public ToolChest getToolChest();
|
||||
|
||||
/**
|
||||
* Find a running tool like the one specified that has the named domain file.
|
||||
* If it finds a matching tool, then it is brought to the front.
|
||||
* Otherwise, it creates one and runs it.
|
||||
* It then invokes the specified event on the running tool.
|
||||
*
|
||||
* @param tool find/create a tool like this one.
|
||||
* @param domainFile open this file in the found/created tool.
|
||||
* @param event invoke this event on the found/created tool
|
||||
*/
|
||||
public void displaySimilarTool(PluginTool tool, DomainFile domainFile, PluginEvent event);
|
||||
|
||||
/**
|
||||
* Returns the default/preferred tool template which should be used to open the specified
|
||||
* domain file, whether defined by the user or the system default.
|
||||
|
||||
-1
@@ -17,7 +17,6 @@ package ghidra.framework.plugintool;
|
||||
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.Project;
|
||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
||||
|
||||
/**
|
||||
* PluginTool that is used by the Merge process to resolve conflicts
|
||||
|
||||
@@ -50,11 +50,11 @@ import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.main.UserAgreementDialog;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.framework.plugintool.dialog.ExtensionTableProvider;
|
||||
import ghidra.framework.plugintool.dialog.ManagePluginsDialog;
|
||||
import ghidra.framework.plugintool.mgr.*;
|
||||
import ghidra.framework.plugintool.util.*;
|
||||
import ghidra.framework.project.ProjectDataService;
|
||||
import ghidra.framework.project.extensions.ExtensionTableProvider;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.*;
|
||||
@@ -198,7 +198,7 @@ public abstract class PluginTool extends AbstractDockingTool {
|
||||
return new DefaultPluginsConfiguration();
|
||||
}
|
||||
|
||||
protected PluginsConfiguration getPluginsConfiguration() {
|
||||
public PluginsConfiguration getPluginsConfiguration() {
|
||||
return pluginMgr.getPluginsConfiguration();
|
||||
}
|
||||
|
||||
|
||||
+16
-3
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.plugintool.util;
|
||||
package ghidra.framework.plugintool;
|
||||
|
||||
import static java.util.function.Predicate.*;
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.util.function.Predicate;
|
||||
import org.jdom.Element;
|
||||
|
||||
import ghidra.framework.main.ProgramaticUseOnly;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.util.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
|
||||
@@ -56,7 +56,7 @@ public abstract class PluginsConfiguration {
|
||||
List<Class<? extends Plugin>> classes = ClassSearcher.getClasses(Plugin.class, classFilter);
|
||||
|
||||
for (Class<? extends Plugin> pluginClass : classes) {
|
||||
if (!PluginUtils.isValidPluginClass(pluginClass)) {
|
||||
if (!isValidPluginClass(pluginClass)) {
|
||||
Msg.warn(this, "Plugin does not have valid constructor! Skipping " + pluginClass);
|
||||
continue;
|
||||
}
|
||||
@@ -72,6 +72,19 @@ public abstract class PluginsConfiguration {
|
||||
|
||||
}
|
||||
|
||||
private boolean isValidPluginClass(Class<? extends Plugin> pluginClass) {
|
||||
try {
|
||||
// will throw exception if missing constructor
|
||||
pluginClass.getConstructor(PluginTool.class);
|
||||
return true;
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
// no matching constructor method
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public PluginDescription getPluginDescription(String className) {
|
||||
return descriptionsByName.get(className);
|
||||
}
|
||||
-1
@@ -23,7 +23,6 @@ import docking.action.*;
|
||||
import docking.tool.ToolConstants;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.framework.Platform;
|
||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
public class StandAlonePluginTool extends PluginTool {
|
||||
|
||||
-5
@@ -34,11 +34,6 @@ public class ToolServicesAdapter implements ToolServices {
|
||||
// override
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displaySimilarTool(PluginTool tool, DomainFile domainFile, PluginEvent event) {
|
||||
// override
|
||||
}
|
||||
|
||||
@Override
|
||||
public File exportTool(ToolTemplate tool) throws FileNotFoundException, IOException {
|
||||
return null;
|
||||
|
||||
+1
-3
@@ -28,9 +28,7 @@ import generic.theme.GColor;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
|
||||
/**
|
||||
* Abstract class that defines a panel for displaying name/value pairs with html-formatting.
|
||||
* <p>
|
||||
* This is used with the {@link ExtensionDetailsPanel} and the {@link PluginDetailsPanel}
|
||||
* Abstract class that defines a panel for displaying name/value pairs with html-formatting.
|
||||
*/
|
||||
public abstract class AbstractDetailsPanel extends JPanel {
|
||||
|
||||
|
||||
-208
@@ -1,208 +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.framework.plugintool.dialog;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import ghidra.framework.Application;
|
||||
import utility.module.ModuleUtilities;
|
||||
|
||||
/**
|
||||
* Representation of a Ghidra extension. This class encapsulates all information required to
|
||||
* uniquely identify an extension and where (or if) it has been installed.
|
||||
* <p>
|
||||
* Note that hashCode and equals have been implemented for this. Two extension
|
||||
* descriptions are considered equal if they have the same {@link #name} attribute; all other
|
||||
* fields are unimportant save for display purposes.
|
||||
*
|
||||
*/
|
||||
public class ExtensionDetails implements Comparable<ExtensionDetails> {
|
||||
|
||||
/** Absolute path to where this extension is installed. If not installed, this will be null. */
|
||||
private String installPath;
|
||||
|
||||
/**
|
||||
* Absolute path to where the original source archive (zip) for this extension can be found. If
|
||||
* there is no archive (likely because this is an extension that comes pre-installed with
|
||||
* Ghidra, or Ghidra is being run in development mode), this will be null.
|
||||
*/
|
||||
private String archivePath;
|
||||
|
||||
/** Name of the extension. This must be unique.*/
|
||||
private String name;
|
||||
|
||||
/** Brief description, for display purposes only.*/
|
||||
private String description;
|
||||
|
||||
/** Date when the extension was created, for display purposes only.*/
|
||||
private String createdOn;
|
||||
|
||||
/** Author of the extension, for display purposes only.*/
|
||||
private String author;
|
||||
|
||||
/** The extension version */
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name unique name of the extension; cannot be null
|
||||
* @param description brief explanation of what the extension does; can be null
|
||||
* @param author creator of the extension; can be null
|
||||
* @param createdOn creation date of the extension, can be null
|
||||
* @param version the extension version
|
||||
*/
|
||||
public ExtensionDetails(String name, String description, String author, String createdOn,
|
||||
String version) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.author = author;
|
||||
this.createdOn = createdOn;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExtensionDetails other = (ExtensionDetails) obj;
|
||||
if (name == null) {
|
||||
if (other.name != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location where this extension is installed. If the extension is
|
||||
* not installed this will be null.
|
||||
*
|
||||
* @return the extension path, or null
|
||||
*/
|
||||
public String getInstallPath() {
|
||||
return installPath;
|
||||
}
|
||||
|
||||
public void setInstallPath(String path) {
|
||||
this.installPath = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location where the extension archive is located. If there is no
|
||||
* archive this will be null.
|
||||
*
|
||||
* @return the archive path, or null
|
||||
*/
|
||||
public String getArchivePath() {
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
public void setArchivePath(String path) {
|
||||
this.archivePath = path;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getCreatedOn() {
|
||||
return createdOn;
|
||||
}
|
||||
|
||||
public void setCreatedOn(String date) {
|
||||
this.createdOn = date;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* An extension is known to be installed if it has a valid installation path AND that path
|
||||
* contains a Module.manifest file.
|
||||
* <p>
|
||||
* Note: The module manifest file is a marker that indicates several things; one of which is
|
||||
* the installation status of an extension. When a user marks an extension to be uninstalled (by
|
||||
* checking the appropriate checkbox in the {@link ExtensionTableModel}), the only thing
|
||||
* that is done is to remove this manifest file, which tells the {@link ExtensionTableProvider}
|
||||
* to remove the entire extension directory on the next launch.
|
||||
*
|
||||
* @return true if the extension is installed.
|
||||
*/
|
||||
public boolean isInstalled() {
|
||||
if (installPath == null || installPath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If running out of a jar and the install path is valid, just return true. The alternative
|
||||
// would be to inspect the jar and verify that the install path is there and is valid, but that's
|
||||
// overkill.
|
||||
if (Application.inSingleJarMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
File mm = new File(installPath, ModuleUtilities.MANIFEST_FILE_NAME);
|
||||
return mm.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ExtensionDetails other) {
|
||||
return name.compareTo(other.name);
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +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.framework.plugintool.dialog;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import ghidra.util.exception.UsrException;
|
||||
|
||||
/**
|
||||
* Defines an exception that can be thrown by {@link ExtensionUtils}. This is intended to provide
|
||||
* detailed information about issues that arise during installation (or removal) of
|
||||
* Extensions.
|
||||
*
|
||||
*/
|
||||
public class ExtensionException extends UsrException {
|
||||
|
||||
/** Provides more detail as to the specific source of the exception. */
|
||||
public enum ExtensionExceptionType {
|
||||
|
||||
/** Thrown if the required installation location does not exist */
|
||||
INVALID_INSTALL_LOCATION,
|
||||
|
||||
/** Thrown when installing an extension to an existing location */
|
||||
DUPLICATE_FILE_ERROR,
|
||||
|
||||
/** Thrown when there is a problem reading/extracting a zip file during installation */
|
||||
ZIP_ERROR,
|
||||
|
||||
/** Thrown when there is a problem copying a folder during an installation */
|
||||
COPY_ERROR,
|
||||
|
||||
/** Thrown when the user cancels the installation */
|
||||
INSTALL_CANCELLED
|
||||
}
|
||||
|
||||
private ExtensionExceptionType exceptionType;
|
||||
private File errorFile = null; // If there's a file relevant to the exception, populate this.
|
||||
|
||||
public ExtensionException(String msg, ExtensionExceptionType exceptionType) {
|
||||
super(msg);
|
||||
this.exceptionType = exceptionType;
|
||||
}
|
||||
|
||||
public ExtensionException(String msg, ExtensionExceptionType exceptionType, File errorFile) {
|
||||
super(msg);
|
||||
this.errorFile = errorFile;
|
||||
this.exceptionType = exceptionType;
|
||||
}
|
||||
|
||||
public ExtensionExceptionType getExceptionType() {
|
||||
return exceptionType;
|
||||
}
|
||||
|
||||
public File getErrorFile() {
|
||||
return errorFile;
|
||||
}
|
||||
}
|
||||
-1077
File diff suppressed because it is too large
Load Diff
+52
-5
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.framework.plugintool.dialog;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -93,6 +94,8 @@ class PluginInstallerTableModel
|
||||
descriptor.addVisibleColumn(new PluginNameColumn(), 1, true);
|
||||
descriptor.addVisibleColumn(new PluginDescriptionColumn());
|
||||
descriptor.addVisibleColumn(new PluginCategoryColumn());
|
||||
descriptor.addHiddenColumn(new PluginModuleColumn());
|
||||
descriptor.addHiddenColumn(new PluginLocationColumn());
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
@@ -177,7 +180,7 @@ class PluginInstallerTableModel
|
||||
* Column for displaying the interactive checkbox, allowing the user to install
|
||||
* or uninstall the plugin.
|
||||
*/
|
||||
class PluginInstalledColumn extends
|
||||
private class PluginInstalledColumn extends
|
||||
AbstractDynamicTableColumn<PluginDescription, Boolean, List<PluginDescription>> {
|
||||
|
||||
@Override
|
||||
@@ -200,7 +203,7 @@ class PluginInstallerTableModel
|
||||
/**
|
||||
* Column for displaying the status of the plugin.
|
||||
*/
|
||||
class PluginStatusColumn
|
||||
private class PluginStatusColumn
|
||||
extends AbstractDynamicTableColumn<PluginDescription, Icon, List<PluginDescription>> {
|
||||
|
||||
@Override
|
||||
@@ -223,7 +226,7 @@ class PluginInstallerTableModel
|
||||
/**
|
||||
* Column for displaying the extension name of the plugin.
|
||||
*/
|
||||
class PluginNameColumn
|
||||
private class PluginNameColumn
|
||||
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
||||
|
||||
@Override
|
||||
@@ -246,7 +249,7 @@ class PluginInstallerTableModel
|
||||
/**
|
||||
* Column for displaying the plugin description.
|
||||
*/
|
||||
class PluginDescriptionColumn
|
||||
private class PluginDescriptionColumn
|
||||
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
||||
|
||||
@Override
|
||||
@@ -266,10 +269,54 @@ class PluginInstallerTableModel
|
||||
}
|
||||
}
|
||||
|
||||
private class PluginModuleColumn
|
||||
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Module";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(PluginDescription rowObject, Settings settings,
|
||||
List<PluginDescription> data, ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getModuleName();
|
||||
}
|
||||
}
|
||||
|
||||
private class PluginLocationColumn
|
||||
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Location";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(PluginDescription rowObject, Settings settings,
|
||||
List<PluginDescription> data, ServiceProvider sp) throws IllegalArgumentException {
|
||||
Class<? extends Plugin> clazz = rowObject.getPluginClass();
|
||||
String name = clazz.getName();
|
||||
String path = '/' + name.replace('.', '/') + ".class";
|
||||
URL url = clazz.getResource(path);
|
||||
return url.getFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Column for displaying the plugin category.
|
||||
*/
|
||||
class PluginCategoryColumn
|
||||
private class PluginCategoryColumn
|
||||
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
||||
|
||||
@Override
|
||||
|
||||
+1
@@ -16,6 +16,7 @@
|
||||
package ghidra.framework.plugintool.util;
|
||||
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||
|
||||
/**
|
||||
* A configuration that includes all plugins on the classpath.
|
||||
|
||||
+4
-5
@@ -148,14 +148,13 @@ public class PluginDescription implements Comparable<PluginDescription> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type for the plugin: CORE, CONTRIB, PROTOTYPE, or
|
||||
* DEVELOP. Within a type, plugins are grouped by category.
|
||||
* @return the type (or null if there is no module)
|
||||
* Return the name of the module that contains the plugin.
|
||||
* @return the module name
|
||||
*/
|
||||
public String getModuleName() {
|
||||
if (moduleName == null) {
|
||||
ResourceFile moduleRootDirectory = Application.getMyModuleRootDirectory();
|
||||
moduleName = (moduleRootDirectory == null) ? null : moduleRootDirectory.getName();
|
||||
ResourceFile moduleDir = Application.getModuleContainingClass(pluginClass.getName());
|
||||
moduleName = (moduleDir == null) ? "<No Module>" : moduleDir.getName();
|
||||
}
|
||||
|
||||
return moduleName;
|
||||
|
||||
-131
@@ -15,14 +15,9 @@
|
||||
*/
|
||||
package ghidra.framework.plugintool.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.dialog.ExtensionDetails;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.exception.AssertException;
|
||||
@@ -33,99 +28,6 @@ import ghidra.util.exception.AssertException;
|
||||
*/
|
||||
public class PluginUtils {
|
||||
|
||||
/**
|
||||
* Finds all plugin classes loaded from a given set of extensions.
|
||||
*
|
||||
* @param extensions set of extensions to search
|
||||
* @return list of loaded plugin classes, or empty list if none found
|
||||
*/
|
||||
public static List<Class<?>> findLoadedPlugins(Set<ExtensionDetails> extensions) {
|
||||
|
||||
List<Class<?>> pluginClasses = new ArrayList<>();
|
||||
for (ExtensionDetails extension : extensions) {
|
||||
|
||||
if (extension == null || extension.getInstallPath() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Class<?>> classes = findLoadedPlugins(new File(extension.getInstallPath()));
|
||||
pluginClasses.addAll(classes);
|
||||
}
|
||||
|
||||
return pluginClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all plugin classes loaded from a particular folder/file.
|
||||
* <p>
|
||||
* This uses the {@link ClassSearcher} to find all <code>Plugin.class</code> objects on the
|
||||
* classpath. For each class, the original resource file is compared against the
|
||||
* given folder and if it's contained therein (or if it matches a given jar), it's
|
||||
* added to the return list.
|
||||
*
|
||||
* @param dir the directory to search, or a jar file
|
||||
* @return list of {@link Plugin} classes, or empty list if none found
|
||||
*/
|
||||
private static List<Class<?>> findLoadedPlugins(File dir) {
|
||||
|
||||
// The list of classes to return.
|
||||
List<Class<?>> retPlugins = new ArrayList<>();
|
||||
|
||||
// Find any jar files in the directory provided. Our plugin(s) will always be
|
||||
// in a jar.
|
||||
List<File> jarFiles = new ArrayList<>();
|
||||
findJarFiles(dir, jarFiles);
|
||||
|
||||
// Now get all Plugin.class files that have been loaded, and see if any of them
|
||||
// were loaded from one of the jars we just found.
|
||||
List<Class<? extends Plugin>> plugins = ClassSearcher.getClasses(Plugin.class);
|
||||
for (Class<? extends Plugin> plugin : plugins) {
|
||||
URL location = plugin.getResource('/' + plugin.getName().replace('.', '/') + ".class");
|
||||
if (location == null) {
|
||||
Msg.warn(null, "Class location for plugin [" + plugin.getName() +
|
||||
"] could not be determined.");
|
||||
continue;
|
||||
}
|
||||
String pluginLocation = location.getPath();
|
||||
for (File jar : jarFiles) {
|
||||
URL jarUrl = null;
|
||||
try {
|
||||
jarUrl = jar.toURI().toURL();
|
||||
if (pluginLocation.contains(jarUrl.getPath())) {
|
||||
retPlugins.add(plugin);
|
||||
}
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return retPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given list with all discovered jar files found in the given directory and
|
||||
* its subdirectories.
|
||||
*
|
||||
* @param dir the directory to search
|
||||
* @param jarFiles list of found jar files
|
||||
*/
|
||||
private static void findJarFiles(File dir, List<File> jarFiles) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
findJarFiles(f, jarFiles);
|
||||
}
|
||||
|
||||
if (f.isFile() && f.getName().endsWith(".jar")) {
|
||||
jarFiles.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of a {@link Plugin}.
|
||||
*
|
||||
@@ -268,37 +170,4 @@ public class PluginUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified Plugin class is well-formed and meets requirements for
|
||||
* Ghidra Plugins:
|
||||
* <ul>
|
||||
* <li>Has a constructor with a signature of <code>ThePlugin(PluginTool tool)</code>
|
||||
* <li>Has a {@link PluginInfo @PluginInfo} annotation.
|
||||
* </ul>
|
||||
* <p>
|
||||
* See {@link Plugin}.
|
||||
* <p>
|
||||
* @param pluginClass Class to examine.
|
||||
* @return boolean true if well formed.
|
||||
*/
|
||||
public static boolean isValidPluginClass(Class<? extends Plugin> pluginClass) {
|
||||
try {
|
||||
// will throw exception if missing ctor
|
||||
pluginClass.getConstructor(PluginTool.class);
|
||||
|
||||
// #if ( can_do_strict_checking )
|
||||
// PluginInfo pia = pluginClass.getAnnotation(PluginInfo.class);
|
||||
// return pia != null;
|
||||
// #else
|
||||
// for now
|
||||
return true;
|
||||
// #endif
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
// no matching constructor method
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.project.extensions;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import generic.json.Json;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.Msg;
|
||||
import utilities.util.FileUtilities;
|
||||
import utility.application.ApplicationLayout;
|
||||
import utility.module.ModuleUtilities;
|
||||
|
||||
/**
|
||||
* Representation of a Ghidra extension. This class encapsulates all information required to
|
||||
* uniquely identify an extension and where (or if) it has been installed.
|
||||
* <p>
|
||||
* Note that hashCode and equals have been implemented for this. Two extension
|
||||
* descriptions are considered equal if they have the same {@link #name} attribute; all other
|
||||
* fields are unimportant except for display purposes.
|
||||
*/
|
||||
public class ExtensionDetails implements Comparable<ExtensionDetails> {
|
||||
|
||||
/** Absolute path to where this extension is installed. If not installed, this will be null. */
|
||||
private File installDir;
|
||||
|
||||
/**
|
||||
* Absolute path to where the original source archive (zip) for this extension can be found. If
|
||||
* there is no archive (likely because this is an extension that comes pre-installed with
|
||||
* Ghidra, or Ghidra is being run in development mode), this will be null.
|
||||
*/
|
||||
private String archivePath;
|
||||
|
||||
/** Name of the extension. This must be unique.*/
|
||||
private String name;
|
||||
|
||||
/** Brief description, for display purposes only.*/
|
||||
private String description;
|
||||
|
||||
/** Date when the extension was created, for display purposes only.*/
|
||||
private String createdOn;
|
||||
|
||||
/** Author of the extension, for display purposes only.*/
|
||||
private String author;
|
||||
|
||||
/** The extension version */
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name unique name of the extension; cannot be null
|
||||
* @param description brief explanation of what the extension does; can be null
|
||||
* @param author creator of the extension; can be null
|
||||
* @param createdOn creation date of the extension, can be null
|
||||
* @param version the extension version
|
||||
*/
|
||||
public ExtensionDetails(String name, String description, String author, String createdOn,
|
||||
String version) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.author = author;
|
||||
this.createdOn = createdOn;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExtensionDetails other = (ExtensionDetails) obj;
|
||||
if (name == null) {
|
||||
if (other.name != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location where this extension is installed. If the extension is not installed
|
||||
* this will be null.
|
||||
*
|
||||
* @return the extension path, or null
|
||||
*/
|
||||
public String getInstallPath() {
|
||||
if (installDir != null) {
|
||||
return installDir.getAbsolutePath();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public File getInstallDir() {
|
||||
return installDir;
|
||||
}
|
||||
|
||||
public void setInstallDir(File installDir) {
|
||||
this.installDir = installDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location where the extension archive is located. The extension archive concept
|
||||
* is not used for all extensions, but is used for delivering extensions as part of a
|
||||
* distribution.
|
||||
*
|
||||
* @return the archive path, or null
|
||||
* @see ApplicationLayout#getExtensionArchiveDir()
|
||||
*/
|
||||
public String getArchivePath() {
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
public void setArchivePath(String path) {
|
||||
this.archivePath = path;
|
||||
}
|
||||
|
||||
public boolean isFromArchive() {
|
||||
return archivePath != null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getCreatedOn() {
|
||||
return createdOn;
|
||||
}
|
||||
|
||||
public void setCreatedOn(String date) {
|
||||
this.createdOn = date;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* An extension is known to be installed if it has a valid installation path AND that path
|
||||
* contains a Module.manifest file. Extensions that are {@link #isPendingUninstall()} are
|
||||
* still on the filesystem, may be in use by the tool, but will be removed upon restart.
|
||||
* <p>
|
||||
* Note: The module manifest file is a marker that indicates several things; one of which is
|
||||
* the installation status of an extension. When a user marks an extension to be uninstalled (by
|
||||
* checking the appropriate checkbox in the {@link ExtensionTableModel}), the only thing
|
||||
* that is done is to remove this manifest file, which tells the {@link ExtensionTableProvider}
|
||||
* to remove the entire extension directory on the next launch.
|
||||
*
|
||||
* @return true if the extension is installed.
|
||||
*/
|
||||
public boolean isInstalled() {
|
||||
if (installDir == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If running out of a jar and the install path is valid, just return true. The alternative
|
||||
// would be to inspect the jar and verify that the install path is there and is valid, but
|
||||
// that's overkill.
|
||||
if (Application.inSingleJarMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
File f = new File(installDir, ModuleUtilities.MANIFEST_FILE_NAME);
|
||||
return f.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this extension is marked to be uninstalled. The contents of the extension
|
||||
* still exist and the tool may still be using the extension, but on restart, the extension will
|
||||
* be removed.
|
||||
*
|
||||
* @return true if marked for uninstall
|
||||
*/
|
||||
public boolean isPendingUninstall() {
|
||||
if (installDir == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Application.inSingleJarMode()) {
|
||||
return false; // can't uninstall from single jar mode
|
||||
}
|
||||
|
||||
File f = new File(installDir, ModuleUtilities.MANIFEST_FILE_NAME_UNINSTALLED);
|
||||
return f.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this extension is installed under an installation folder or inside of a
|
||||
* source control repository folder.
|
||||
* @return true if this extension is installed under an installation folder or inside of a
|
||||
* source control repository folder.
|
||||
*/
|
||||
public boolean isInstalledInInstallationFolder() {
|
||||
if (installDir == null) {
|
||||
return false; // not installed
|
||||
}
|
||||
|
||||
ApplicationLayout layout = Application.getApplicationLayout();
|
||||
File appInstallDir = layout.getApplicationInstallationDir().getFile(false);
|
||||
if (FileUtilities.isPathContainedWithin(appInstallDir, installDir)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the module manifest and extension properties file that are in an installed state to
|
||||
* an uninstalled state.
|
||||
*
|
||||
* Specifically, the following will be renamed:
|
||||
* <UL>
|
||||
* <LI>Module.manifest to Module.manifest.uninstalled</LI>
|
||||
* <LI>extension.properties = extension.properties.uninstalled</LI>
|
||||
* </UL>
|
||||
*
|
||||
* @return false if any renames fail
|
||||
*/
|
||||
public boolean markForUninstall() {
|
||||
|
||||
if (installDir == null) {
|
||||
return false; // already marked as uninstalled
|
||||
}
|
||||
|
||||
Msg.trace(this, "Marking extension for uninstall '" + installDir + "'");
|
||||
|
||||
boolean success = true;
|
||||
File manifest = new File(installDir, ModuleUtilities.MANIFEST_FILE_NAME);
|
||||
if (manifest.exists()) {
|
||||
File newFile = new File(installDir, ModuleUtilities.MANIFEST_FILE_NAME_UNINSTALLED);
|
||||
if (!manifest.renameTo(newFile)) {
|
||||
Msg.trace(this, "Unable to rename module manifest file: " + manifest);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Msg.trace(this, "No manifest file found for extension '" + name + "'");
|
||||
}
|
||||
|
||||
File properties = new File(installDir, ExtensionUtils.PROPERTIES_FILE_NAME);
|
||||
if (properties.exists()) {
|
||||
File newFile = new File(installDir, ExtensionUtils.PROPERTIES_FILE_NAME_UNINSTALLED);
|
||||
if (!properties.renameTo(newFile)) {
|
||||
Msg.trace(this, "Unable to rename properties file: " + properties);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Msg.trace(this, "No properties file found for extension '" + name + "'");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* A companion method for {@link #markForUninstall()} that allows extensions marked for cleanup
|
||||
* to be restored to the installed state.
|
||||
* <p>
|
||||
* Specifically, the following will be renamed:
|
||||
* <UL>
|
||||
* <LI>Module.manifest.uninstalled to Module.manifest</LI>
|
||||
* <LI>extension.properties.uninstalled to extension.properties</LI>
|
||||
* </UL>
|
||||
* @return true if successful
|
||||
*/
|
||||
public boolean clearMarkForUninstall() {
|
||||
|
||||
if (installDir == null) {
|
||||
Msg.error(ExtensionUtils.class,
|
||||
"Cannot restore extension; extension installation dir is missing for: " + name);
|
||||
return false; // already marked as uninstalled
|
||||
}
|
||||
|
||||
Msg.trace(this, "Restoring extension state files for '" + installDir + "'");
|
||||
|
||||
boolean success = true;
|
||||
File manifest = new File(installDir, ModuleUtilities.MANIFEST_FILE_NAME_UNINSTALLED);
|
||||
if (manifest.exists()) {
|
||||
File newFile = new File(installDir, ModuleUtilities.MANIFEST_FILE_NAME);
|
||||
if (!manifest.renameTo(newFile)) {
|
||||
Msg.trace(this, "Unable to rename module manifest file: " + manifest);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Msg.trace(this, "No manifest file found for extension '" + name + "'");
|
||||
}
|
||||
|
||||
File properties = new File(installDir, ExtensionUtils.PROPERTIES_FILE_NAME_UNINSTALLED);
|
||||
if (properties.exists()) {
|
||||
File newFile = new File(installDir, ExtensionUtils.PROPERTIES_FILE_NAME);
|
||||
if (!properties.renameTo(newFile)) {
|
||||
Msg.trace(this, "Unable to rename properties file: " + properties);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Msg.trace(this, "No properties file found for extension '" + name + "'");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ExtensionDetails other) {
|
||||
return name.compareTo(other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.plugintool.dialog;
|
||||
package ghidra.framework.project.extensions;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Point;
|
||||
@@ -22,6 +22,7 @@ import javax.swing.text.SimpleAttributeSet;
|
||||
|
||||
import docking.widgets.table.threaded.ThreadedTableModelListener;
|
||||
import generic.theme.GColor;
|
||||
import ghidra.framework.plugintool.dialog.AbstractDetailsPanel;
|
||||
|
||||
/**
|
||||
* Panel that shows information about the selected extension in the {@link ExtensionTablePanel}. This
|
||||
+73
-41
@@ -13,10 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.plugintool.dialog;
|
||||
package ghidra.framework.project.extensions;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
@@ -26,13 +25,11 @@ import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Model for the {@link ExtensionTablePanel}. This defines 5 columns for displaying information in
|
||||
@@ -59,9 +56,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
|
||||
/** This is the data source for the model. Whatever is here will be displayed in the table. */
|
||||
private Set<ExtensionDetails> extensions;
|
||||
|
||||
/** Indicates if the model has changed due to an install or uninstall. */
|
||||
private boolean modelChanged = false;
|
||||
private Map<String, Boolean> originalInstallStates = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -94,21 +89,17 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
if (Application.inSingleJarMode() || SystemUtilities.isInDevelopmentMode()) {
|
||||
if (Application.inSingleJarMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not allow GUI removal of extensions manually installed in installation directory.
|
||||
ExtensionDetails extension = getSelectedExtension(rowIndex);
|
||||
|
||||
// Do not allow GUI uninstallation of extensions manually installed in installation
|
||||
// directory
|
||||
if (extension.getInstallPath() != null && FileUtilities.isPathContainedWithin(
|
||||
Application.getApplicationLayout().getApplicationInstallationDir().getFile(false),
|
||||
new File(extension.getInstallPath()))) {
|
||||
if (extension.isInstalledInInstallationFolder()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (columnIndex == INSTALLED_COL);
|
||||
return columnIndex == INSTALLED_COL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +108,6 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
*/
|
||||
@Override
|
||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||
super.setValueAt(aValue, rowIndex, columnIndex);
|
||||
|
||||
// We only care about the install column here, as it's the only one that
|
||||
// is editable.
|
||||
@@ -131,31 +121,50 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
||||
if (!installDir.exists() && !installDir.mkdir()) {
|
||||
Msg.showError(this, null, "Directory Error",
|
||||
"Cannot install/uninstall extensions: Failed to create extension installation directory.\n" +
|
||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
||||
"Cannot install/uninstall extensions: Failed to create extension installation " +
|
||||
"directory.\nSee the \"Ghidra Extension Notes\" section of the Ghidra " +
|
||||
"Installation Guide for more information.");
|
||||
}
|
||||
if (!installDir.canWrite()) {
|
||||
Msg.showError(this, null, "Permissions Error",
|
||||
"Cannot install/uninstall extensions: Invalid write permissions on installation directory.\n" +
|
||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
||||
"Cannot install/uninstall extensions: Invalid write permissions on installation " +
|
||||
"directory.\nSee the \"Ghidra Extension Notes\" section of the Ghidra " +
|
||||
"Installation Guide for more information.");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean install = ((Boolean) aValue).booleanValue();
|
||||
ExtensionDetails extension = getSelectedExtension(rowIndex);
|
||||
|
||||
if (install) {
|
||||
if (ExtensionUtils.install(extension, true)) {
|
||||
modelChanged = true;
|
||||
if (!install) {
|
||||
if (extension.markForUninstall()) {
|
||||
refreshTable();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (ExtensionUtils.removeStateFiles(extension)) {
|
||||
modelChanged = true;
|
||||
|
||||
// Restore an existing extension or install an archived extension
|
||||
if (extension.isPendingUninstall()) {
|
||||
if (extension.clearMarkForUninstall()) {
|
||||
refreshTable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
refreshTable();
|
||||
// At this point, the extension is not installed, so we cannot simply clear the uninstall
|
||||
// state. This means that the extension has not yet been installed. The only way to get
|
||||
// into this state is by clicking an extension that was discovered in the 'extension
|
||||
// archives folder'
|
||||
if (extension.isFromArchive()) {
|
||||
if (ExtensionUtils.installExtensionFromArchive(extension)) {
|
||||
refreshTable();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a programming error
|
||||
Msg.error(this,
|
||||
"Unable install an extension that no longer exists. Restart Ghidra and " +
|
||||
"try manually installing the extension: '" + extension.getName() + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,10 +173,9 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
* @param details the extension to check
|
||||
* @return true if extension version is valid for this version of Ghidra
|
||||
*/
|
||||
private boolean isValidVersion(ExtensionDetails details) {
|
||||
private boolean matchesGhidraVersion(ExtensionDetails details) {
|
||||
String ghidraVersion = Application.getApplicationVersion();
|
||||
String extensionVersion = details.getVersion();
|
||||
|
||||
return ghidraVersion.equals(extensionVersion);
|
||||
}
|
||||
|
||||
@@ -184,12 +192,28 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
extensions = ExtensionUtils.getExtensions();
|
||||
Set<ExtensionDetails> archived = ExtensionUtils.getArchiveExtensions();
|
||||
Set<ExtensionDetails> installed = ExtensionUtils.getInstalledExtensions();
|
||||
|
||||
// don't show archived extensions that have been installed
|
||||
for (ExtensionDetails extension : installed) {
|
||||
if (archived.remove(extension)) {
|
||||
Msg.trace(this,
|
||||
"Not showing archived extension that has been installed. Archive path: " +
|
||||
extension.getArchivePath()); // useful for debugging
|
||||
}
|
||||
}
|
||||
catch (ExtensionException e) {
|
||||
Msg.error(this, "Error loading extensions", e);
|
||||
return;
|
||||
|
||||
extensions = new HashSet<>();
|
||||
extensions.addAll(installed);
|
||||
extensions.addAll(archived);
|
||||
|
||||
for (ExtensionDetails e : extensions) {
|
||||
String name = e.getName();
|
||||
if (originalInstallStates.containsKey(name)) {
|
||||
continue; // preserve the original value
|
||||
}
|
||||
originalInstallStates.put(e.getName(), e.isInstalled());
|
||||
}
|
||||
|
||||
accumulator.addAll(extensions);
|
||||
@@ -201,7 +225,15 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
* @return true if the model has changed as a result of installing or uninstalling an extension
|
||||
*/
|
||||
public boolean hasModelChanged() {
|
||||
return modelChanged;
|
||||
|
||||
for (ExtensionDetails e : extensions) {
|
||||
Boolean wasInstalled = originalInstallStates.get(e.getName());
|
||||
if (e.isInstalled() != wasInstalled) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,7 +273,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
private class ExtensionNameColumn
|
||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||
|
||||
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
||||
private ExtRenderer renderer = new ExtRenderer();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
@@ -271,7 +303,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
private class ExtensionDescriptionColumn
|
||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||
|
||||
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
||||
private ExtRenderer renderer = new ExtRenderer();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
@@ -301,7 +333,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
private class ExtensionVersionColumn
|
||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||
|
||||
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
||||
private ExtRenderer renderer = new ExtRenderer();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
@@ -403,14 +435,14 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
}
|
||||
|
||||
private class ExtVersionRenderer extends AbstractGColumnRenderer<String> {
|
||||
private class ExtRenderer extends AbstractGColumnRenderer<String> {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
Component comp = super.getTableCellRendererComponent(data);
|
||||
|
||||
ExtensionDetails extension = getSelectedExtension(data.getRowViewIndex());
|
||||
if (!isValidVersion(extension)) {
|
||||
if (!matchesGhidraVersion(extension)) {
|
||||
comp.setForeground(getErrorForegroundColor(data.isSelected()));
|
||||
}
|
||||
|
||||
+1
-4
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.plugintool.dialog;
|
||||
package ghidra.framework.project.extensions;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
@@ -70,9 +70,6 @@ public class ExtensionTablePanel extends JPanel {
|
||||
// way to restrict column width.
|
||||
TableColumn col = table.getColumnModel().getColumn(ExtensionTableModel.INSTALLED_COL);
|
||||
col.setMaxWidth(25);
|
||||
|
||||
// Finally, load the table with some data.
|
||||
refreshTable();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
+26
-93
@@ -13,12 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.plugintool.dialog;
|
||||
package ghidra.framework.project.extensions;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
@@ -32,7 +31,8 @@ import generic.jar.ResourceFile;
|
||||
import ghidra.app.util.GenericHelpTopics;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.filechooser.GhidraFileChooserModel;
|
||||
import ghidra.util.filechooser.GhidraFileFilter;
|
||||
import resources.Icons;
|
||||
@@ -43,6 +43,8 @@ import resources.Icons;
|
||||
*/
|
||||
public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
|
||||
private static final String LAST_IMPORT_DIRECTORY_KEY = "LastExtensionImportDirectory";
|
||||
|
||||
private ExtensionTablePanel extensionTablePanel;
|
||||
|
||||
private boolean requireRestart = false;
|
||||
@@ -126,57 +128,28 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
||||
if (!installDir.exists() && !installDir.mkdir()) {
|
||||
Msg.showError(this, null, "Directory Error",
|
||||
"Cannot install/uninstall extensions: Failed to create extension installation directory.\n" +
|
||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
||||
"Cannot install/uninstall extensions: Failed to create extension " +
|
||||
"installation directory: " + installDir);
|
||||
}
|
||||
if (!installDir.canWrite()) {
|
||||
Msg.showError(this, null, "Permissions Error",
|
||||
"Cannot install/uninstall extensions: Invalid write permissions on installation directory.\n" +
|
||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
||||
"Cannot install/uninstall extensions: Invalid write permissions on " +
|
||||
"installation directory: " + installDir);
|
||||
return;
|
||||
}
|
||||
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES);
|
||||
chooser.setTitle("Select extension");
|
||||
chooser.setLastDirectoryPreference(LAST_IMPORT_DIRECTORY_KEY);
|
||||
chooser.setTitle("Select Extension");
|
||||
chooser.addFileFilter(new ExtensionFileFilter());
|
||||
|
||||
List<File> files = chooser.getSelectedFiles();
|
||||
chooser.dispose();
|
||||
for (File file : files) {
|
||||
try {
|
||||
if (!ExtensionUtils.isExtension(new ResourceFile(file))) {
|
||||
Msg.showError(this, null, "Installation Error", "Selected file: [" +
|
||||
file.getName() + "] is not a valid Ghidra Extension");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (ExtensionException e1) {
|
||||
Msg.showError(this, null, "Installation Error", "Error determining if [" +
|
||||
file.getName() + "] is a valid Ghidra Extension", e1);
|
||||
continue;
|
||||
}
|
||||
|
||||
String extensionVersion = getExtensionVersion(file);
|
||||
if (extensionVersion == null) {
|
||||
Msg.showError(this, null, "Installation Error",
|
||||
"Unable to read extension version for [" + file + "]");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ExtensionUtils.validateExtensionVersion(extensionVersion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (ExtensionUtils.install(new ResourceFile(file))) {
|
||||
panel.refreshTable();
|
||||
requireRestart = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(null, "Problem installing extension [" + file.getName() + "]", e);
|
||||
}
|
||||
if (installExtensions(files)) {
|
||||
panel.refreshTable();
|
||||
requireRestart = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -185,49 +158,18 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
addAction.setMenuBarData(new MenuData(new String[] { "Add Extension" }, addIcon, group));
|
||||
addAction.setToolBarData(new ToolBarData(addIcon, group));
|
||||
addAction.setHelpLocation(new HelpLocation(GenericHelpTopics.FRONT_END, "ExtensionTools"));
|
||||
addAction.setDescription(
|
||||
SystemUtilities.isInDevelopmentMode() ? "Add Extension (disabled in development mode)"
|
||||
: "Add extension");
|
||||
addAction.setEnabled(
|
||||
!SystemUtilities.isInDevelopmentMode() && !Application.inSingleJarMode());
|
||||
addAction.setDescription("Add extension");
|
||||
addAction.setEnabled(!Application.inSingleJarMode());
|
||||
addAction(addAction);
|
||||
}
|
||||
|
||||
private String getExtensionVersion(File file) {
|
||||
|
||||
// If the given file is a directory...
|
||||
if (!file.isFile()) {
|
||||
List<ResourceFile> propFiles =
|
||||
ExtensionUtils.findExtensionPropertyFiles(new ResourceFile(file), true);
|
||||
for (ResourceFile props : propFiles) {
|
||||
ExtensionDetails ext = ExtensionUtils.createExtensionDetailsFromPropertyFile(props);
|
||||
String version = ext.getVersion();
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
private boolean installExtensions(List<File> files) {
|
||||
boolean didInstall = false;
|
||||
for (File file : files) {
|
||||
boolean success = ExtensionUtils.install(file);
|
||||
didInstall |= success;
|
||||
}
|
||||
|
||||
// If the given file is a zip...
|
||||
try {
|
||||
if (ExtensionUtils.isZip(file)) {
|
||||
Properties props = ExtensionUtils.getPropertiesFromArchive(file);
|
||||
if (props == null) {
|
||||
return null; // no prop file exists
|
||||
}
|
||||
ExtensionDetails ext = ExtensionUtils.createExtensionDetailsFromProperties(props);
|
||||
String version = ext.getVersion();
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ExtensionException e) {
|
||||
// just fall through
|
||||
}
|
||||
return null;
|
||||
return didInstall;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,9 +200,8 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for a {@link GhidraFileChooser} that restricts selection to those
|
||||
* files that are Ghidra Extensions (zip files with an extension.properties
|
||||
* file) or folders.
|
||||
* Filter for a {@link GhidraFileChooser} that restricts selection to those files that are
|
||||
* Ghidra Extensions (zip files with an extension.properties file) or folders.
|
||||
*/
|
||||
private class ExtensionFileFilter implements GhidraFileFilter {
|
||||
@Override
|
||||
@@ -269,16 +210,8 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(File f, GhidraFileChooserModel l_model) {
|
||||
|
||||
try {
|
||||
return ExtensionUtils.isExtension(new ResourceFile(f)) || f.isDirectory();
|
||||
}
|
||||
catch (ExtensionException e) {
|
||||
// if something fails to be recognized as an extension, just move on.
|
||||
}
|
||||
|
||||
return false;
|
||||
public boolean accept(File f, GhidraFileChooserModel model) {
|
||||
return f.isDirectory() || ExtensionUtils.isExtension(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
+953
File diff suppressed because it is too large
Load Diff
+320
@@ -0,0 +1,320 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.project.tool;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jdom.Element;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import generic.json.Json;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.dialog.PluginInstallerDialog;
|
||||
import ghidra.framework.plugintool.util.PluginDescription;
|
||||
import ghidra.framework.project.extensions.ExtensionDetails;
|
||||
import ghidra.framework.project.extensions.ExtensionUtils;
|
||||
import ghidra.util.NumericUtilities;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.xml.XmlUtilities;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* A class to manage saving and restoring of known extension used by this tool.
|
||||
*/
|
||||
class ExtensionManager {
|
||||
|
||||
private static final String EXTENSION_ATTRIBUTE_NAME_ENCODED = "ENCODED_NAME";
|
||||
private static final String EXTENSION_ATTRIBUTE_NAME = "NAME";
|
||||
private static final String EXTENSIONS_XML_NAME = "EXTENSIONS";
|
||||
private static final String EXTENSION_ELEMENT_NAME = "EXTENSION";
|
||||
|
||||
private PluginTool tool;
|
||||
private Set<Class<?>> newExtensionPlugins = new HashSet<>();
|
||||
|
||||
ExtensionManager(PluginTool tool) {
|
||||
this.tool = tool;
|
||||
}
|
||||
|
||||
void checkForNewExtensions() {
|
||||
if (newExtensionPlugins.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
propmtToConfigureNewPlugins(newExtensionPlugins);
|
||||
newExtensionPlugins.clear();
|
||||
}
|
||||
|
||||
private void propmtToConfigureNewPlugins(Set<Class<?>> plugins) {
|
||||
|
||||
// Offer the user a chance to configure any newly discovered plugins
|
||||
int option = OptionDialog.showYesNoDialog(tool.getToolFrame(), "New Plugins Found!",
|
||||
"New extension plugins detected. Would you like to configure them?");
|
||||
if (option == OptionDialog.YES_OPTION) {
|
||||
List<PluginDescription> pluginDescriptions = getPluginDescriptions(plugins);
|
||||
PluginInstallerDialog pluginInstaller = new PluginInstallerDialog("New Plugins Found!",
|
||||
tool, new PluginConfigurationModel(tool), pluginDescriptions);
|
||||
tool.showDialog(pluginInstaller);
|
||||
}
|
||||
}
|
||||
|
||||
void saveToXml(Element xml) {
|
||||
|
||||
Set<ExtensionDetails> installedExtensions = ExtensionUtils.getActiveInstalledExtensions();
|
||||
Element extensionsParent = new Element(EXTENSIONS_XML_NAME);
|
||||
for (ExtensionDetails ext : installedExtensions) {
|
||||
Element child = new Element(EXTENSION_ELEMENT_NAME);
|
||||
String name = ext.getName();
|
||||
if (XmlUtilities.hasInvalidXMLCharacters(name)) {
|
||||
child.setAttribute(EXTENSION_ATTRIBUTE_NAME_ENCODED, NumericUtilities
|
||||
.convertBytesToString(name.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
else {
|
||||
child.setAttribute(EXTENSION_ATTRIBUTE_NAME, name);
|
||||
}
|
||||
|
||||
extensionsParent.addContent(child);
|
||||
}
|
||||
|
||||
xml.addContent(extensionsParent);
|
||||
}
|
||||
|
||||
void restoreFromXml(Element xml) {
|
||||
|
||||
Set<ExtensionDetails> installedExtensions = getExtensions();
|
||||
if (installedExtensions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> knownExtensionNames = getKnownExtensions(xml);
|
||||
Set<ExtensionDetails> newExtensions = new HashSet<>(installedExtensions);
|
||||
for (ExtensionDetails ext : installedExtensions) {
|
||||
if (knownExtensionNames.contains(ext.getName())) {
|
||||
newExtensions.remove(ext);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of all plugins contained in those extensions. If there are none, then either
|
||||
// none of the extensions has any plugins, or Ghidra hasn't been restarted since installing
|
||||
// the extension(s), so none of the plugin classes have been loaded. In either case, there
|
||||
// is nothing more to do.
|
||||
Set<Class<?>> newPlugins = findLoadedPlugins(newExtensions);
|
||||
newExtensionPlugins.addAll(newPlugins);
|
||||
}
|
||||
|
||||
private Set<ExtensionDetails> getExtensions() {
|
||||
Set<ExtensionDetails> installedExtensions = ExtensionUtils.getActiveInstalledExtensions();
|
||||
return installedExtensions.stream()
|
||||
.filter(e -> !e.isInstalledInInstallationFolder())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Set<String> getKnownExtensions(Element xml) {
|
||||
Set<String> knownExtensionNames = new HashSet<>();
|
||||
Element extensionsParent = xml.getChild(EXTENSIONS_XML_NAME);
|
||||
if (extensionsParent == null) {
|
||||
return knownExtensionNames;
|
||||
}
|
||||
|
||||
Iterator<?> it = extensionsParent.getChildren(EXTENSION_ELEMENT_NAME).iterator();
|
||||
while (it.hasNext()) {
|
||||
Element child = (Element) it.next();
|
||||
String encodedValue = child.getAttributeValue(EXTENSION_ATTRIBUTE_NAME_ENCODED);
|
||||
if (encodedValue != null) {
|
||||
byte[] bytes = NumericUtilities.convertStringToBytes(encodedValue);
|
||||
String decoded = new String(bytes, StandardCharsets.UTF_8);
|
||||
knownExtensionNames.add(decoded);
|
||||
}
|
||||
else {
|
||||
String name = child.getAttributeValue(EXTENSION_ATTRIBUTE_NAME);
|
||||
knownExtensionNames.add(name);
|
||||
}
|
||||
}
|
||||
return knownExtensionNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all {@link PluginDescription} objects that match a given set of plugin classes. This
|
||||
* effectively tells the caller which of the given plugins have been loaded by the class loader.
|
||||
* <p>
|
||||
* Note that this method does not take path/package information into account when finding
|
||||
* plugins; in the example above, if there is more than one plugin with the name "FooPlugin",
|
||||
* only one will be found (the one found is not guaranteed to be the first).
|
||||
*
|
||||
* @param plugins the list of plugin classes to search for
|
||||
* @return list of plugin descriptions
|
||||
*/
|
||||
private List<PluginDescription> getPluginDescriptions(Set<Class<?>> plugins) {
|
||||
|
||||
// First define the list of plugin descriptions to return
|
||||
List<PluginDescription> descriptions = new ArrayList<>();
|
||||
|
||||
// Get all plugins that have been loaded
|
||||
PluginsConfiguration pluginsConfiguration = tool.getPluginsConfiguration();
|
||||
List<PluginDescription> allPluginDescriptions =
|
||||
pluginsConfiguration.getManagedPluginDescriptions();
|
||||
|
||||
// see if an entry exists in the list of all loaded plugins
|
||||
for (Class<?> plugin : plugins) {
|
||||
String pluginName = plugin.getSimpleName();
|
||||
|
||||
Optional<PluginDescription> desc = allPluginDescriptions.stream()
|
||||
.filter(d -> (pluginName.equals(d.getName())))
|
||||
.findAny();
|
||||
if (desc.isPresent()) {
|
||||
descriptions.add(desc.get());
|
||||
}
|
||||
}
|
||||
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
private static Set<Class<?>> findLoadedPlugins(Set<ExtensionDetails> extensions) {
|
||||
|
||||
Set<PluginPath> pluginPaths = getPluginPaths();
|
||||
Set<Class<?>> extensionPlugins = new HashSet<>();
|
||||
for (ExtensionDetails extension : extensions) {
|
||||
File installDir = extension.getInstallDir();
|
||||
if (installDir == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Set<Class<?>> classes = findPluginsLoadedFromExtension(installDir, pluginPaths);
|
||||
extensionPlugins.addAll(classes);
|
||||
}
|
||||
|
||||
return extensionPlugins;
|
||||
}
|
||||
|
||||
private static Set<PluginPath> getPluginPaths() {
|
||||
Set<PluginPath> paths = new HashSet<>();
|
||||
List<Class<? extends Plugin>> plugins = ClassSearcher.getClasses(Plugin.class);
|
||||
for (Class<? extends Plugin> plugin : plugins) {
|
||||
paths.add(new PluginPath(plugin));
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all plugin classes loaded from a particular extension folder.
|
||||
* <p>
|
||||
* This uses the {@link ClassSearcher} to find all <code>Plugin.class</code> objects on the
|
||||
* classpath. For each class, the original resource file is compared against the
|
||||
* given extension folder and the jar files for that extension.
|
||||
*
|
||||
* @param dir the directory to search, or a jar file
|
||||
* @param pluginPaths all loaded plugin paths
|
||||
* @return list of {@link Plugin} classes, or empty list if none found
|
||||
*/
|
||||
private static Set<Class<?>> findPluginsLoadedFromExtension(File dir,
|
||||
Set<PluginPath> pluginPaths) {
|
||||
|
||||
Set<Class<?>> result = new HashSet<>();
|
||||
|
||||
// Find any jar files in the directory provided
|
||||
Set<String> jarPaths = getJarPaths(dir);
|
||||
|
||||
// Now get all Plugin.class file paths and see if any of them were loaded from one of the
|
||||
// extension the given extension directory
|
||||
for (PluginPath pluginPath : pluginPaths) {
|
||||
if (pluginPath.isFrom(dir)) {
|
||||
result.add(pluginPath.getPluginClass());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String jarPath : jarPaths) {
|
||||
if (pluginPath.isFrom(jarPath)) {
|
||||
result.add(pluginPath.getPluginClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Set<String> getJarPaths(File dir) {
|
||||
Set<File> jarFiles = new HashSet<>();
|
||||
findJarFiles(dir, jarFiles);
|
||||
Set<String> paths = new HashSet<>();
|
||||
for (File jar : jarFiles) {
|
||||
try {
|
||||
URL jarUrl = jar.toURI().toURL();
|
||||
paths.add(jarUrl.getPath());
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given list with all discovered jar files found in the given directory and
|
||||
* its subdirectories.
|
||||
*
|
||||
* @param dir the directory to search
|
||||
* @param jarFiles list of found jar files
|
||||
*/
|
||||
private static void findJarFiles(File dir, Set<File> jarFiles) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
findJarFiles(f, jarFiles);
|
||||
}
|
||||
|
||||
if (f.isFile() && f.getName().endsWith(".jar")) {
|
||||
jarFiles.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PluginPath {
|
||||
private Class<? extends Plugin> pluginClass;
|
||||
private String pluginLocation;
|
||||
private File pluginFile;
|
||||
|
||||
PluginPath(Class<? extends Plugin> pluginClass) {
|
||||
this.pluginClass = pluginClass;
|
||||
String name = pluginClass.getName();
|
||||
URL url = pluginClass.getResource('/' + name.replace('.', '/') + ".class");
|
||||
this.pluginLocation = url.getPath();
|
||||
this.pluginFile = new File(pluginLocation);
|
||||
}
|
||||
|
||||
public boolean isFrom(File dir) {
|
||||
return FileUtilities.isPathContainedWithin(dir, pluginFile);
|
||||
}
|
||||
|
||||
boolean isFrom(String jarPath) {
|
||||
return pluginLocation.contains(jarPath);
|
||||
}
|
||||
|
||||
Class<? extends Plugin> getPluginClass() {
|
||||
return pluginClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -17,7 +17,7 @@ package ghidra.framework.project.tool;
|
||||
|
||||
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
||||
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||
|
||||
/**
|
||||
* A configuration that allows all general plugins and application plugins. Plugins that may only
|
||||
|
||||
+41
-183
@@ -15,10 +15,6 @@
|
||||
*/
|
||||
package ghidra.framework.project.tool;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.jdom.Element;
|
||||
|
||||
import docking.ActionContext;
|
||||
@@ -30,13 +26,9 @@ import docking.widgets.OptionDialog;
|
||||
import ghidra.app.util.FileOpenDropHandler;
|
||||
import ghidra.framework.model.Project;
|
||||
import ghidra.framework.model.ToolTemplate;
|
||||
import ghidra.framework.options.PreferenceState;
|
||||
import ghidra.framework.plugintool.PluginConfigurationModel;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.dialog.*;
|
||||
import ghidra.framework.plugintool.util.*;
|
||||
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Tool created by the workspace when the user chooses to create a new
|
||||
@@ -47,16 +39,14 @@ public class GhidraTool extends PluginTool {
|
||||
|
||||
private static final String NON_AUTOSAVE_SAVE_TOOL_TITLE = "Save Tool?";
|
||||
|
||||
// Preference category stored in the tools' xml file, indicating which extensions
|
||||
// this tool is aware of. This is used to recognize when new extensions have been
|
||||
// installed that the user should be made aware of.
|
||||
public static final String EXTENSIONS_PREFERENCE_NAME = "KNOWN_EXTENSIONS";
|
||||
|
||||
public static boolean autoSave = true;
|
||||
|
||||
private FileOpenDropHandler fileOpenDropHandler;
|
||||
private DockingAction configureToolAction;
|
||||
|
||||
private ExtensionManager extensionManager;
|
||||
private boolean hasBeenShown;
|
||||
|
||||
/**
|
||||
* Construct a new Ghidra Tool.
|
||||
*
|
||||
@@ -77,6 +67,18 @@ public class GhidraTool extends PluginTool {
|
||||
super(project, template);
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to do this here, since our parent constructor calls methods on us that need the
|
||||
* extension manager.
|
||||
* @return the extension manager
|
||||
*/
|
||||
private ExtensionManager getExtensionManager() {
|
||||
if (extensionManager == null) {
|
||||
extensionManager = new ExtensionManager(this);
|
||||
}
|
||||
return extensionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DockingWindowManager createDockingWindowManager(boolean isDockable, boolean hasStatus,
|
||||
boolean isModal) {
|
||||
@@ -122,6 +124,31 @@ public class GhidraTool extends PluginTool {
|
||||
winMgr.restoreWindowDataFromXml(rootElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element saveToXml(boolean includeConfigState) {
|
||||
Element xml = super.saveToXml(includeConfigState);
|
||||
getExtensionManager().saveToXml(xml);
|
||||
return xml;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean restoreFromXml(Element root) {
|
||||
boolean success = super.restoreFromXml(root);
|
||||
getExtensionManager().restoreFromXml(root);
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
if (visible) {
|
||||
if (!hasBeenShown) { // first time being shown
|
||||
getExtensionManager().checkForNewExtensions();
|
||||
}
|
||||
hasBeenShown = true;
|
||||
}
|
||||
super.setVisible(visible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSave() {
|
||||
if (autoSave) {
|
||||
@@ -207,175 +234,6 @@ public class GhidraTool extends PluginTool {
|
||||
}
|
||||
|
||||
protected void showConfig() {
|
||||
// if (hasUnsavedData()) {
|
||||
// OptionDialog.showWarningDialog( getToolFrame(),"Configure Not Allowed!",
|
||||
// "The tool has unsaved data. Configuring the tool can potentially lose\n"+
|
||||
// "data. Therefore, this operation is not allowed with unsaved data.\n\n"+
|
||||
// "Please save your data before configuring the tool.");
|
||||
// return;
|
||||
// }
|
||||
showConfig(true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for extensions that have been installed since the last time this tool
|
||||
* was launched. If any are found, and if those extensions contain plugins, the user is
|
||||
* notified and given the chance to install them.
|
||||
*
|
||||
*/
|
||||
public void checkForNewExtensions() {
|
||||
|
||||
// 1. First remove any extensions that are in the tool preferences that are no longer
|
||||
// installed. This will happen if the user installs an extension, launches
|
||||
// a tool, then uninstalls the extension.
|
||||
removeUninstalledExtensions();
|
||||
|
||||
// 2. Now figure out which extensions have been added.
|
||||
Set<ExtensionDetails> newExtensions =
|
||||
ExtensionUtils.getExtensionsInstalledSinceLastToolLaunch(this);
|
||||
|
||||
// 3. Get a list of all plugins contained in those extensions. If there are none, then
|
||||
// either none of the extensions has any plugins, or Ghidra hasn't been restarted since
|
||||
// installing the extension(s), so none of the plugin classes have been loaded. In
|
||||
// either case, there is nothing more to do.
|
||||
List<Class<?>> newPlugins = PluginUtils.findLoadedPlugins(newExtensions);
|
||||
if (newPlugins.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Notify the user there are new plugins.
|
||||
int option = OptionDialog.showYesNoDialog(getActiveWindow(), "New Plugins Found!",
|
||||
"New extension plugins detected. Would you like to configure them?");
|
||||
if (option == OptionDialog.YES_OPTION) {
|
||||
List<PluginDescription> pluginDescriptions = getPluginDescriptions(this, newPlugins);
|
||||
PluginInstallerDialog pluginInstaller = new PluginInstallerDialog("New Plugins Found!",
|
||||
this, new PluginConfigurationModel(this), pluginDescriptions);
|
||||
showDialog(pluginInstaller);
|
||||
}
|
||||
|
||||
// 5. Update the preference file to reflect the new extensions now known to this tool.
|
||||
addInstalledExtensions(newExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all {@link PluginDescription} objects that match a given set of plugin classes. This
|
||||
* effectively tells the caller which of the given plugins have been loaded by the class loader.
|
||||
* <p>
|
||||
* Note that this method does not take path/package information into account when finding
|
||||
* plugins; in the example above, if there is more than one plugin with the name "FooPlugin",
|
||||
* only one will be found (the one found is not guaranteed to be the first).
|
||||
*
|
||||
* @param tool the current tool
|
||||
* @param plugins the list of plugin classes to search for
|
||||
* @return list of plugin descriptions
|
||||
*/
|
||||
private List<PluginDescription> getPluginDescriptions(PluginTool tool, List<Class<?>> plugins) {
|
||||
|
||||
// First define the list of plugin descriptions to return
|
||||
List<PluginDescription> retPlugins = new ArrayList<>();
|
||||
|
||||
// Get all plugins that have been loaded
|
||||
PluginsConfiguration pluginClassManager = getPluginsConfiguration();
|
||||
List<PluginDescription> allPluginDescriptions =
|
||||
pluginClassManager.getManagedPluginDescriptions();
|
||||
|
||||
// see if an entry exists in the list of all loaded plugins
|
||||
for (Class<?> plugin : plugins) {
|
||||
String pluginName = plugin.getSimpleName();
|
||||
|
||||
Optional<PluginDescription> desc = allPluginDescriptions.stream()
|
||||
.filter(d -> (pluginName.equals(d.getName())))
|
||||
.findAny();
|
||||
if (desc.isPresent()) {
|
||||
retPlugins.add(desc.get());
|
||||
}
|
||||
}
|
||||
|
||||
return retPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any extensions in the tool preferences that are no longer installed.
|
||||
*/
|
||||
private void removeUninstalledExtensions() {
|
||||
|
||||
try {
|
||||
// Get all installed extensions
|
||||
Set<ExtensionDetails> installedExtensions =
|
||||
ExtensionUtils.getInstalledExtensions(false);
|
||||
List<String> installedExtensionNames =
|
||||
installedExtensions.stream().map(ext -> ext.getName()).collect(Collectors.toList());
|
||||
|
||||
// Get the list of extensions in the tool preference state
|
||||
DockingWindowManager dockingWindowManager =
|
||||
DockingWindowManager.getInstance(getToolFrame());
|
||||
|
||||
PreferenceState state = getExtensionPreferences(dockingWindowManager);
|
||||
|
||||
String[] extNames = state.getStrings(EXTENSIONS_PREFERENCE_NAME, new String[0]);
|
||||
List<String> preferenceExtensionNames = new ArrayList<>(Arrays.asList(extNames));
|
||||
|
||||
// Now see if any extensions are in the current preferences that are NOT in the installed extensions
|
||||
// list. Those are the ones we need to remove.
|
||||
for (Iterator<String> i = preferenceExtensionNames.iterator(); i.hasNext();) {
|
||||
String extName = i.next();
|
||||
if (!installedExtensionNames.contains(extName)) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, put the new extension list in the preferences object
|
||||
state.putStrings(EXTENSIONS_PREFERENCE_NAME,
|
||||
preferenceExtensionNames.toArray(new String[preferenceExtensionNames.size()]));
|
||||
dockingWindowManager.putPreferenceState(EXTENSIONS_PREFERENCE_NAME, state);
|
||||
}
|
||||
catch (ExtensionException e) {
|
||||
// This is a problem but isn't catastrophic. Just warn the user and continue.
|
||||
Msg.warn(this, "Couldn't retrieve installed extensions!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the preferences for this tool with a set of new extensions.
|
||||
*
|
||||
* @param newExtensions the extensions to add
|
||||
*/
|
||||
private void addInstalledExtensions(Set<ExtensionDetails> newExtensions) {
|
||||
|
||||
DockingWindowManager dockingWindowManager =
|
||||
DockingWindowManager.getInstance(getToolFrame());
|
||||
|
||||
// Get the current preference object. We need to get the existing prefs so we can add our
|
||||
// new extensions to them. If the extensions category doesn't exist yet, just create one.
|
||||
PreferenceState state = getExtensionPreferences(dockingWindowManager);
|
||||
|
||||
// Now get the list of extensions already in the prefs...
|
||||
String[] extNames = state.getStrings(EXTENSIONS_PREFERENCE_NAME, new String[0]);
|
||||
|
||||
// ...and parse the passed-in extension list to get just the names of the extensions to add.
|
||||
List<String> extensionNamesToAdd =
|
||||
newExtensions.stream().map(ext -> ext.getName()).collect(Collectors.toList());
|
||||
|
||||
// Finally add them together and update the preference state.
|
||||
String[] allPreferences = ArrayUtils.addAll(extNames,
|
||||
extensionNamesToAdd.toArray(new String[extensionNamesToAdd.size()]));
|
||||
state.putStrings(EXTENSIONS_PREFERENCE_NAME, allPreferences);
|
||||
dockingWindowManager.putPreferenceState(EXTENSIONS_PREFERENCE_NAME, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the extensions portion of the preferences object.
|
||||
*
|
||||
* @param dockingWindowManager the docking window manager
|
||||
* @return the extensions portion of the preference state, or a new preference state object if no extension section exists
|
||||
*/
|
||||
private PreferenceState getExtensionPreferences(DockingWindowManager dockingWindowManager) {
|
||||
|
||||
PreferenceState state = dockingWindowManager.getPreferenceState(EXTENSIONS_PREFERENCE_NAME);
|
||||
if (state == null) {
|
||||
state = new PreferenceState();
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
-22
@@ -30,7 +30,6 @@ import ghidra.framework.data.*;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.main.FrontEndTool;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.PluginEvent;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.framework.protocol.ghidra.GetUrlContentTypeTask;
|
||||
@@ -169,27 +168,6 @@ class ToolServicesImpl implements ToolServices {
|
||||
return toolChest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displaySimilarTool(PluginTool tool, DomainFile domainFile, PluginEvent event) {
|
||||
|
||||
PluginTool[] similarTools = getSameNamedRunningTools(tool);
|
||||
PluginTool matchingTool = findToolUsingFile(similarTools, domainFile);
|
||||
if (matchingTool != null) {
|
||||
// Bring the matching tool forward.
|
||||
matchingTool.toFront();
|
||||
}
|
||||
else {
|
||||
// Create a new tool and pop it up.
|
||||
Workspace workspace = toolManager.getActiveWorkspace();
|
||||
matchingTool = workspace.runTool(tool.getToolTemplate(true));
|
||||
matchingTool.setVisible(true);
|
||||
matchingTool.acceptDomainFiles(new DomainFile[] { domainFile });
|
||||
}
|
||||
|
||||
// Fire the indicated event in the tool.
|
||||
matchingTool.firePluginEvent(event);
|
||||
}
|
||||
|
||||
private static DefaultLaunchMode getDefaultLaunchMode() {
|
||||
DefaultLaunchMode defaultLaunchMode = DefaultLaunchMode.DEFAULT;
|
||||
FrontEndTool frontEndTool = AppInfo.getFrontEndTool();
|
||||
|
||||
+18
-26
@@ -79,14 +79,9 @@ class WorkspaceImpl implements Workspace {
|
||||
PluginTool tool = toolManager.getTool(this, template);
|
||||
if (tool != null) {
|
||||
tool.setVisible(true);
|
||||
|
||||
if (tool instanceof GhidraTool) {
|
||||
GhidraTool gTool = (GhidraTool) tool;
|
||||
gTool.checkForNewExtensions();
|
||||
}
|
||||
runningTools.add(tool);
|
||||
|
||||
// alert the tool manager that we changed
|
||||
// alert the tool manager that we have changed
|
||||
toolManager.setWorkspaceChanged(this);
|
||||
toolManager.fireToolAddedEvent(this, tool);
|
||||
}
|
||||
@@ -161,6 +156,7 @@ class WorkspaceImpl implements Workspace {
|
||||
String defaultTool = System.getProperty("ghidra.defaulttool");
|
||||
if (defaultTool != null && !defaultTool.equals("")) {
|
||||
PluginTool tool = toolManager.getTool(defaultTool);
|
||||
tool.setVisible(isActive);
|
||||
runningTools.add(tool);
|
||||
toolManager.fireToolAddedEvent(this, tool);
|
||||
return;
|
||||
@@ -175,27 +171,23 @@ class WorkspaceImpl implements Workspace {
|
||||
}
|
||||
|
||||
PluginTool tool = toolManager.getTool(toolName);
|
||||
if (tool != null) {
|
||||
tool.setVisible(isActive);
|
||||
|
||||
if (tool instanceof GhidraTool) {
|
||||
GhidraTool gTool = (GhidraTool) tool;
|
||||
gTool.checkForNewExtensions();
|
||||
}
|
||||
|
||||
boolean hadChanges = tool.hasConfigChanged();
|
||||
tool.restoreWindowingDataFromXml(element);
|
||||
|
||||
Element toolDataElem = element.getChild("DATA_STATE");
|
||||
tool.restoreDataStateFromXml(toolDataElem);
|
||||
if (hadChanges) {
|
||||
// restore the dirty state, which is cleared by the restoreDataState call
|
||||
tool.setConfigChanged(true);
|
||||
}
|
||||
|
||||
runningTools.add(tool);
|
||||
toolManager.fireToolAddedEvent(this, tool);
|
||||
if (tool == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tool.setVisible(isActive);
|
||||
boolean hadChanges = tool.hasConfigChanged();
|
||||
tool.restoreWindowingDataFromXml(element);
|
||||
|
||||
Element toolDataElem = element.getChild("DATA_STATE");
|
||||
tool.restoreDataStateFromXml(toolDataElem);
|
||||
if (hadChanges) {
|
||||
// restore the dirty state, which is cleared by the restoreDataState call
|
||||
tool.setConfigChanged(true);
|
||||
}
|
||||
|
||||
runningTools.add(tool);
|
||||
toolManager.fireToolAddedEvent(this, tool);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package ghidra.framework.plugintool;
|
||||
|
||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
||||
|
||||
/**
|
||||
* A dummy version of {@link PluginTool} that tests can use when they need an instance of
|
||||
* the PluginTool, but do not wish to use a real version
|
||||
|
||||
+747
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -55,7 +55,7 @@ public class SpecExtensionPanel extends JPanel {
|
||||
private boolean unappliedChanges;
|
||||
private SpecExtension specExtension;
|
||||
private List<CompilerElement> tableElements;
|
||||
private ExtensionTableModel tableModel;
|
||||
private SpecExtensionTableModel tableModel;
|
||||
private GTable extensionTable;
|
||||
private JButton exportButton;
|
||||
private JButton removeButton;
|
||||
@@ -163,7 +163,7 @@ public class SpecExtensionPanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
private class ExtensionTableModel extends AbstractGTableModel<CompilerElement> {
|
||||
private class SpecExtensionTableModel extends AbstractGTableModel<CompilerElement> {
|
||||
private final String[] columnNames = { "Extension Type", "Name", "Status" };
|
||||
|
||||
@Override
|
||||
@@ -383,7 +383,7 @@ public class SpecExtensionPanel extends JPanel {
|
||||
|
||||
private void createPanel() {
|
||||
setLayout(new BorderLayout(10, 10));
|
||||
tableModel = new ExtensionTableModel();
|
||||
tableModel = new SpecExtensionTableModel();
|
||||
extensionTable = new CompilerElementTable(tableModel);
|
||||
|
||||
JScrollPane sp = new JScrollPane(extensionTable);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user