mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-27 09:17:13 +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
|
<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
|
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
|
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
|
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
|
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
|
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">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//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>
|
|
||||||
|
|
||||||
<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>
|
<BODY>
|
||||||
Ghidra Extensions</h1>
|
<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>
|
|
||||||
|
|
||||||
<p>
|
<P>Ghidra Extensions are Ghidra software modules that can be installed
|
||||||
<center>
|
into a Ghidra distribution. This allows users to create and share new plugins and scripts.
|
||||||
<table border="0" width="100%">
|
Ghidra ships with some pre-built extensions that not installed by default.
|
||||||
<tr>
|
</P>
|
||||||
<td width="100%" align="center"><img border="0" src="images/ConfigureExtensions.png"></td>
|
<P>Ghidra Extensions can be installed and uninstalled at runtime, with the changes taking effect
|
||||||
</tr>
|
when Ghidra is restarted. The extension installation dialog can
|
||||||
</table>
|
be opened by selecting the <B>Install Extensions</B> option on the project <B>File</B> menu.</P>
|
||||||
</center>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Dialog Components</h2>
|
<BLOCKQUOTE>
|
||||||
<h3>Extensions List</h3>
|
<CENTER>
|
||||||
<blockquote>
|
<TABLE border="0" width="100%">
|
||||||
The list of extensions is populated when the dialog is launched. To build the list, Ghidra looks in several locations:
|
<TR>
|
||||||
<ul>
|
<TD width="100%" align="center"><IMG alt="" border="0" src=
|
||||||
<li>Extension Installation Directories: Contains any extensions that have been installed. The directories are located at:</li>
|
"images/ConfigureExtensions.png"></TD>
|
||||||
<ul>
|
</TR>
|
||||||
<li><i>[user dir]/.ghidra/.ghidra_[version]/Extensions</i> - Installed/uninstalled from this dialog</li>
|
</TABLE>
|
||||||
<li><i>[installation dir]/Ghidra/Extensions/</i> - Installed/uninstalled from filesystem manually</li>
|
</CENTER>
|
||||||
</ul>
|
<BR>
|
||||||
<li>Extensions Archive Directory: This is where all archive files (zips) are stored. It is located at <i>[installation dir]/Extensions/Ghidra/</i></li>
|
<BR>
|
||||||
</ul>
|
</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>
|
<H2>Dialog Components</H2>
|
||||||
<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.
|
|
||||||
|
|
||||||
<h4><img border="0" src="images/program_obj.png">extension.properties</h4>
|
<H3>Extensions Table</H3>
|
||||||
<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><a name="ExtensionTools"></a>Tools Panel</h3>
|
<BLOCKQUOTE>
|
||||||
<blockquote>
|
<P>The list of extensions is populated when the dialog is launched. To build the list, Ghidra
|
||||||
<ul>
|
looks in several locations:</P>
|
||||||
<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>
|
|
||||||
|
|
||||||
<p class="relatedtopic">Related Topics:</p>
|
<UL>
|
||||||
</body>
|
<LI>Extension Installation Directories: Contains any extensions that have been installed.
|
||||||
</html>
|
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
|
<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.
|
user must install <b>string translation service</b>s that do the actual translation.
|
||||||
Extensions to Ghidra are installed via the <b>File
|
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>
|
menu.</P>
|
||||||
|
|
||||||
<P>When a string has been translated, the translated value will be shown in place of
|
<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.data.DomainObjectAdapter;
|
||||||
import ghidra.framework.main.FrontEndTool;
|
import ghidra.framework.main.FrontEndTool;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.dialog.ExtensionUtils;
|
|
||||||
import ghidra.framework.project.DefaultProjectManager;
|
import ghidra.framework.project.DefaultProjectManager;
|
||||||
|
import ghidra.framework.project.extensions.ExtensionUtils;
|
||||||
import ghidra.framework.store.LockException;
|
import ghidra.framework.store.LockException;
|
||||||
import ghidra.program.database.ProgramDB;
|
import ghidra.program.database.ProgramDB;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
@@ -81,7 +81,7 @@ public class GhidraRun implements GhidraLaunchable {
|
|||||||
updateSplashScreenStatusMessage("Populating Ghidra help...");
|
updateSplashScreenStatusMessage("Populating Ghidra help...");
|
||||||
GhidraHelpService.install();
|
GhidraHelpService.install();
|
||||||
|
|
||||||
ExtensionUtils.cleanupUninstalledExtensions();
|
ExtensionUtils.initializeExtensions();
|
||||||
|
|
||||||
// Allows handling of old content which did not have a content type property
|
// Allows handling of old content which did not have a content type property
|
||||||
DomainObjectAdapter.setDefaultContentClass(ProgramDB.class);
|
DomainObjectAdapter.setDefaultContentClass(ProgramDB.class);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import generic.jar.*;
|
|||||||
import ghidra.GhidraApplicationLayout;
|
import ghidra.GhidraApplicationLayout;
|
||||||
import ghidra.GhidraLaunchable;
|
import ghidra.GhidraLaunchable;
|
||||||
import ghidra.framework.*;
|
import ghidra.framework.*;
|
||||||
import ghidra.framework.plugintool.dialog.ExtensionUtils;
|
import ghidra.framework.project.extensions.ExtensionUtils;
|
||||||
import ghidra.util.classfinder.ClassFinder;
|
import ghidra.util.classfinder.ClassFinder;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
import ghidra.util.classfinder.ClassSearcher;
|
||||||
import ghidra.util.exception.AssertException;
|
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.Options;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||||
import ghidra.program.database.ProgramBuilder;
|
import ghidra.program.database.ProgramBuilder;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
|
|||||||
+5
-6
@@ -182,9 +182,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void deleteUserScripts() throws IOException {
|
protected void deleteUserScripts() throws IOException {
|
||||||
|
|
||||||
Path userScriptDir = Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
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
|
// destroy any NewScriptxxx files...and Temp ones too
|
||||||
List<ResourceFile> paths = provider.getBundleHost()
|
List<ResourceFile> paths = provider.getBundleHost()
|
||||||
.getBundleFiles()
|
.getBundleFiles()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(ResourceFile::isDirectory)
|
.filter(ResourceFile::isDirectory)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
for (ResourceFile path : paths) {
|
for (ResourceFile path : paths) {
|
||||||
File file = path.getFile(false);
|
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
|
a Code Browser by selecting the
|
||||||
</p>
|
</p>
|
||||||
<div class="informalexample">
|
<div class="informalexample">
|
||||||
<span class="bold"><strong>File -> Configure...</strong></span>
|
<span class="bold"><strong>File -> Configure</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
menu option, then clicking on the <span class="emphasis"><em>Configure</em></span> link under the
|
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;
|
package ghidra.feature.vt.api;
|
||||||
|
|
||||||
import static ghidra.feature.vt.db.VTTestUtils.addr;
|
import static ghidra.feature.vt.db.VTTestUtils.*;
|
||||||
import static ghidra.feature.vt.db.VTTestUtils.createMatchSetWithOneMatch;
|
import static ghidra.feature.vt.gui.util.VTOptionDefines.*;
|
||||||
import static ghidra.feature.vt.gui.util.VTOptionDefines.CALLING_CONVENTION;
|
import static org.junit.Assert.*;
|
||||||
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 java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.*;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import ghidra.feature.vt.api.db.VTSessionDB;
|
import ghidra.feature.vt.api.db.VTSessionDB;
|
||||||
import ghidra.feature.vt.api.main.VTAssociationStatus;
|
import ghidra.feature.vt.api.main.*;
|
||||||
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.markuptype.FunctionSignatureMarkupType;
|
import ghidra.feature.vt.api.markuptype.FunctionSignatureMarkupType;
|
||||||
import ghidra.feature.vt.gui.plugin.VTController;
|
import ghidra.feature.vt.gui.plugin.*;
|
||||||
import ghidra.feature.vt.gui.plugin.VTControllerImpl;
|
import ghidra.feature.vt.gui.task.*;
|
||||||
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.util.MatchInfo;
|
import ghidra.feature.vt.gui.util.MatchInfo;
|
||||||
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.CallingConventionChoices;
|
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.*;
|
||||||
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.VTOptionDefines;
|
import ghidra.feature.vt.gui.util.VTOptionDefines;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.store.LockException;
|
import ghidra.framework.store.LockException;
|
||||||
import ghidra.program.database.ProgramBuilder;
|
import ghidra.program.database.ProgramBuilder;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.data.ArrayDataType;
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.program.model.data.BooleanDataType;
|
import ghidra.program.model.lang.*;
|
||||||
import ghidra.program.model.data.CategoryPath;
|
import ghidra.program.model.listing.*;
|
||||||
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.symbol.SourceType;
|
import ghidra.program.model.symbol.SourceType;
|
||||||
import ghidra.program.util.DefaultLanguageService;
|
import ghidra.program.util.DefaultLanguageService;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
@@ -104,8 +49,6 @@ import ghidra.util.task.TaskMonitor;
|
|||||||
|
|
||||||
public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedIntegrationTest {
|
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 TestEnv env;
|
||||||
private PluginTool tool;
|
private PluginTool tool;
|
||||||
private VTController controller;
|
private VTController controller;
|
||||||
@@ -130,8 +73,8 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
|||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
|
|
||||||
env = new TestEnv();
|
env = new TestEnv();
|
||||||
sourceProgram = createSourceProgram();// env.getProgram(TEST_SOURCE_PROGRAM_NAME);
|
sourceProgram = createSourceProgram();
|
||||||
destinationProgram = createDestinationProgram();// env.getProgram(TEST_DESTINATION_PROGRAM_NAME);
|
destinationProgram = createDestinationProgram();
|
||||||
tool = env.getTool();
|
tool = env.getTool();
|
||||||
|
|
||||||
tool.addPlugin(VTPlugin.class.getName());
|
tool.addPlugin(VTPlugin.class.getName());
|
||||||
@@ -142,37 +85,15 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
|||||||
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
|
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
|
||||||
sourceProgram, destinationProgram, this);
|
sourceProgram, destinationProgram, this);
|
||||||
|
|
||||||
runSwing(new Runnable() {
|
runSwing(() -> controller.openVersionTrackingSession(session));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
controller.openVersionTrackingSession(session);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setAllOptionsToDoNothing();
|
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);
|
// 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);
|
// 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)",
|
checkSignatures("undefined use(Gadget * this, Person * person)",
|
||||||
"undefined FUN_00401040(void * this, undefined4 param_1)");
|
"undefined FUN_00401040(void * this, undefined4 param_1)");
|
||||||
|
|
||||||
|
|
||||||
tx(sourceProgram, () -> {
|
tx(sourceProgram, () -> {
|
||||||
sourceFunction.setCustomVariableStorage(true);
|
sourceFunction.setCustomVariableStorage(true);
|
||||||
|
|
||||||
@@ -487,7 +407,6 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
|||||||
SourceType.USER_DEFINED);
|
SourceType.USER_DEFINED);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
DataType personType = sourceProgram.getDataTypeManager().getDataType("/Person");
|
DataType personType = sourceProgram.getDataTypeManager().getDataType("/Person");
|
||||||
assertNotNull(personType);
|
assertNotNull(personType);
|
||||||
|
|
||||||
@@ -495,7 +414,6 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
|||||||
destinationFunction.setCustomVariableStorage(true);
|
destinationFunction.setCustomVariableStorage(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Set the function signature options for this test
|
// Set the function signature options for this test
|
||||||
ToolOptions applyOptions = controller.getOptions();
|
ToolOptions applyOptions = controller.getOptions();
|
||||||
applyOptions.setEnum(FUNCTION_SIGNATURE, FunctionSignatureChoices.REPLACE);
|
applyOptions.setEnum(FUNCTION_SIGNATURE, FunctionSignatureChoices.REPLACE);
|
||||||
@@ -744,24 +662,14 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
|||||||
public void testApplyMatch_ReplaceSignatureAndCallingConventionDifferentLanguageFailUsingNameMatch()
|
public void testApplyMatch_ReplaceSignatureAndCallingConventionDifferentLanguageFailUsingNameMatch()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
runSwing(new Runnable() {
|
runSwing(() -> controller.closeCurrentSessionIgnoringChanges());
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
controller.closeCurrentSessionIgnoringChanges();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
env.release(destinationProgram);
|
env.release(destinationProgram);
|
||||||
destinationProgram = createToyDestinationProgram();// env.getProgram("helloProgram"); // get a program without cdecl
|
destinationProgram = createToyDestinationProgram();// env.getProgram("helloProgram"); // get a program without cdecl
|
||||||
session =
|
session =
|
||||||
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
|
VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager",
|
||||||
sourceProgram, destinationProgram, this);
|
sourceProgram, destinationProgram, this);
|
||||||
runSwing(new Runnable() {
|
runSwing(() -> controller.openVersionTrackingSession(session));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
controller.openVersionTrackingSession(session);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useMatch("0x00401040", "0x00010938");
|
useMatch("0x00401040", "0x00010938");
|
||||||
|
|
||||||
@@ -1699,12 +1607,9 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg
|
|||||||
final String[] sourceStringBox = new String[1];
|
final String[] sourceStringBox = new String[1];
|
||||||
final String[] destinationStringBox = new String[1];
|
final String[] destinationStringBox = new String[1];
|
||||||
|
|
||||||
runSwing(new Runnable() {
|
runSwing(() -> {
|
||||||
@Override
|
sourceStringBox[0] = sourceFunction.getPrototypeString(false, false);
|
||||||
public void run() {
|
destinationStringBox[0] = destinationFunction.getPrototypeString(false, false);
|
||||||
sourceStringBox[0] = sourceFunction.getPrototypeString(false, false);
|
|
||||||
destinationStringBox[0] = destinationFunction.getPrototypeString(false, false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(expectedSourceSignature, sourceStringBox[0]);
|
assertEquals(expectedSourceSignature, sourceStringBox[0]);
|
||||||
|
|||||||
@@ -153,7 +153,7 @@
|
|||||||
modified using the <A href="#Edit_Theme">Theme Editor Dialog</A>. The Theme Editor Dialog
|
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
|
can be invoked from the main application menu using the
|
||||||
<B>Edit<IMG alt="" src="help/shared/arrow.gif" border="0">Theme<IMG alt=""
|
<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
|
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>
|
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) {
|
public static void setErrorsExpected(boolean expected) {
|
||||||
if (expected) {
|
if (expected) {
|
||||||
Msg.error(AbstractGenericTest.class, ">>>>>>>>>>>>>>>> Expected Exception");
|
Msg.error(AbstractGenericTest.class, ">>>>>>>>>>>>>>>> Expected Errors");
|
||||||
ConcurrentTestExceptionHandler.disable();
|
ConcurrentTestExceptionHandler.disable();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Msg.error(AbstractGenericTest.class, "<<<<<<<<<<<<<<<< End Expected Exception");
|
Msg.error(AbstractGenericTest.class, "<<<<<<<<<<<<<<<< End Expected Errors");
|
||||||
ConcurrentTestExceptionHandler.enable();
|
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
|
* environmental issues rather than real problems. This method is intended to ignore
|
||||||
* these less-than-serious issues.
|
* these less-than-serious issues.
|
||||||
*
|
*
|
||||||
* @param throwable the throwable to examine
|
* @param t the throwable to examine
|
||||||
* @return true if it should be ignored
|
* @return true if it should be ignored
|
||||||
*/
|
*/
|
||||||
private static boolean isKnownTestMachineTimingBug(Throwable t) {
|
private static boolean isKnownTestMachineTimingBug(Throwable t) {
|
||||||
|
|||||||
@@ -805,7 +805,7 @@ public class Application {
|
|||||||
*/
|
*/
|
||||||
public static Collection<ResourceFile> getLibraryDirectories() {
|
public static Collection<ResourceFile> getLibraryDirectories() {
|
||||||
checkAppInitialized();
|
checkAppInitialized();
|
||||||
return ModuleUtilities.getModuleLibDirectories(app.layout.getModules());
|
return ModuleUtilities.getModuleLibDirectories(app.layout.getModules().values());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class ClassJar extends ClassLocation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void getClasses(Set<Class<?>> set, TaskMonitor monitor) {
|
protected void getClasses(Set<Class<?>> set, TaskMonitor monitor) {
|
||||||
checkForDuplicates(set);
|
checkForDuplicates(set);
|
||||||
set.addAll(classes);
|
set.addAll(classes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.util.classfinder;
|
package ghidra.util.classfinder;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -31,26 +32,35 @@ abstract class ClassLocation {
|
|||||||
|
|
||||||
protected static final String CLASS_EXT = ".class";
|
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<>();
|
protected Set<Class<?>> classes = new HashSet<>();
|
||||||
|
|
||||||
abstract void getClasses(Set<Class<?>> set, TaskMonitor monitor) throws CancelledException;
|
protected abstract void getClasses(Set<Class<?>> set, TaskMonitor monitor)
|
||||||
|
throws CancelledException;
|
||||||
void checkForDuplicates(Set<Class<?>> existingClasses) {
|
|
||||||
if (!log.isTraceEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
protected void checkForDuplicates(Set<Class<?>> existingClasses) {
|
||||||
for (Class<?> c : classes) {
|
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)) {
|
if (existingClasses.contains(c)) {
|
||||||
Module module = c.getModule();
|
log.warn(() -> generateMessage(c));
|
||||||
module.toString();
|
|
||||||
log.trace("Attempting to load the same class twice: {}. " +
|
|
||||||
"Keeping loaded class ; ignoring class from {}", c, this);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
void getClasses(Set<Class<?>> set, TaskMonitor monitor) throws CancelledException {
|
protected void getClasses(Set<Class<?>> set, TaskMonitor monitor) throws CancelledException {
|
||||||
|
|
||||||
checkForDuplicates(set);
|
checkForDuplicates(set);
|
||||||
|
|
||||||
@@ -120,4 +120,9 @@ class ClassPackage extends ClassLocation {
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return packageDir.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
<logger name="ghidra.framework" level="DEBUG"/>
|
<logger name="ghidra.framework" level="DEBUG"/>
|
||||||
<logger name="ghidra.graph" level="DEBUG" />
|
<logger name="ghidra.graph" level="DEBUG" />
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Turn off debug for specific project classes.
|
Turn off debug for specific project classes.
|
||||||
Leave ghidra.framework.project at DEBUG for tests; specific classes are higher to
|
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" level="DEBUG"/>
|
||||||
<logger name="ghidra.framework.project.DefaultProject" level="WARN"/>
|
<logger name="ghidra.framework.project.DefaultProject" level="WARN"/>
|
||||||
<logger name="ghidra.framework.project.DefaultProjectManager" level="INFO"/>
|
<logger name="ghidra.framework.project.DefaultProjectManager" level="INFO"/>
|
||||||
|
|
||||||
<logger name="functioncalls" level="DEBUG" />
|
<logger name="functioncalls" level="DEBUG" />
|
||||||
<logger name="generic.random" level="WARN"/>
|
<logger name="generic.random" level="WARN"/>
|
||||||
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
||||||
<logger name="ghidra.net" 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.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.database" level="DEBUG" />
|
||||||
<logger name="ghidra.program.model.lang.xml" level="DEBUG"/>
|
<logger name="ghidra.program.model.lang.xml" level="DEBUG"/>
|
||||||
<logger name="ghidra.app.plugin.assembler" 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.app.util.opinion" level="DEBUG" />
|
||||||
<logger name="ghidra.util.classfinder" 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" level="WARN" />
|
||||||
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
||||||
|
|
||||||
<Root level="ALL">
|
<Root level="ALL">
|
||||||
<AppenderRef ref="console" level="DEBUG"/>
|
<AppenderRef ref="console" level="DEBUG"/>
|
||||||
<AppenderRef ref="detail" level="DEBUG"/>
|
<AppenderRef ref="detail" level="DEBUG"/>
|
||||||
<AppenderRef ref="script" level="DEBUG"/>
|
<AppenderRef ref="script" level="DEBUG"/>
|
||||||
<AppenderRef ref="logPanel" level="INFO"/>
|
<AppenderRef ref="logPanel" level="INFO"/>
|
||||||
|
|||||||
@@ -57,7 +57,8 @@
|
|||||||
<logger name="generic.random" level="WARN"/>
|
<logger name="generic.random" level="WARN"/>
|
||||||
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
||||||
<logger name="ghidra.net" 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.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.plugins" level="INFO"/>
|
||||||
@@ -73,7 +74,10 @@
|
|||||||
<logger name="ghidra.app.util.importer" level="INFO" />
|
<logger name="ghidra.app.util.importer" level="INFO" />
|
||||||
<logger name="ghidra.app.util.opinion" level="DEBUG" />
|
<logger name="ghidra.app.util.opinion" level="DEBUG" />
|
||||||
<logger name="ghidra.util.classfinder" 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">
|
<Root level="ALL">
|
||||||
<AppenderRef ref="console" level="TRACE"/>
|
<AppenderRef ref="console" level="TRACE"/>
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
package ghidra.framework.main;
|
package ghidra.framework.main;
|
||||||
|
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A configuration that only includes {@link ApplicationLevelPlugin} plugins.
|
* A configuration that only includes {@link ApplicationLevelPlugin} plugins.
|
||||||
|
|||||||
@@ -647,7 +647,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
MenuData menuData =
|
MenuData menuData =
|
||||||
new MenuData(new String[] { ToolConstants.MENU_FILE, "Install Extensions..." }, null,
|
new MenuData(new String[] { ToolConstants.MENU_FILE, "Install Extensions" }, null,
|
||||||
CONFIGURE_GROUP);
|
CONFIGURE_GROUP);
|
||||||
menuData.setMenuSubGroup(CONFIGURE_GROUP + 2);
|
menuData.setMenuSubGroup(CONFIGURE_GROUP + 2);
|
||||||
installExtensionsAction.setMenuBarData(menuData);
|
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);
|
null, CONFIGURE_GROUP);
|
||||||
menuData.setMenuSubGroup(CONFIGURE_GROUP + 1);
|
menuData.setMenuSubGroup(CONFIGURE_GROUP + 1);
|
||||||
configureToolAction.setMenuBarData(menuData);
|
configureToolAction.setMenuBarData(menuData);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import java.net.URL;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import ghidra.framework.plugintool.PluginEvent;
|
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||||
|
|
||||||
@@ -63,18 +62,6 @@ public interface ToolServices {
|
|||||||
*/
|
*/
|
||||||
public ToolChest getToolChest();
|
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
|
* 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.
|
* 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.main.AppInfo;
|
||||||
import ghidra.framework.model.Project;
|
import ghidra.framework.model.Project;
|
||||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PluginTool that is used by the Merge process to resolve conflicts
|
* 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.main.UserAgreementDialog;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.options.*;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.plugintool.dialog.ExtensionTableProvider;
|
|
||||||
import ghidra.framework.plugintool.dialog.ManagePluginsDialog;
|
import ghidra.framework.plugintool.dialog.ManagePluginsDialog;
|
||||||
import ghidra.framework.plugintool.mgr.*;
|
import ghidra.framework.plugintool.mgr.*;
|
||||||
import ghidra.framework.plugintool.util.*;
|
import ghidra.framework.plugintool.util.*;
|
||||||
import ghidra.framework.project.ProjectDataService;
|
import ghidra.framework.project.ProjectDataService;
|
||||||
|
import ghidra.framework.project.extensions.ExtensionTableProvider;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.*;
|
import ghidra.util.task.*;
|
||||||
@@ -198,7 +198,7 @@ public abstract class PluginTool extends AbstractDockingTool {
|
|||||||
return new DefaultPluginsConfiguration();
|
return new DefaultPluginsConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PluginsConfiguration getPluginsConfiguration() {
|
public PluginsConfiguration getPluginsConfiguration() {
|
||||||
return pluginMgr.getPluginsConfiguration();
|
return pluginMgr.getPluginsConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-3
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.plugintool.util;
|
package ghidra.framework.plugintool;
|
||||||
|
|
||||||
import static java.util.function.Predicate.*;
|
import static java.util.function.Predicate.*;
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ import java.util.function.Predicate;
|
|||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
import ghidra.framework.main.ProgramaticUseOnly;
|
import ghidra.framework.main.ProgramaticUseOnly;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.util.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
import ghidra.util.classfinder.ClassSearcher;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public abstract class PluginsConfiguration {
|
|||||||
List<Class<? extends Plugin>> classes = ClassSearcher.getClasses(Plugin.class, classFilter);
|
List<Class<? extends Plugin>> classes = ClassSearcher.getClasses(Plugin.class, classFilter);
|
||||||
|
|
||||||
for (Class<? extends Plugin> pluginClass : classes) {
|
for (Class<? extends Plugin> pluginClass : classes) {
|
||||||
if (!PluginUtils.isValidPluginClass(pluginClass)) {
|
if (!isValidPluginClass(pluginClass)) {
|
||||||
Msg.warn(this, "Plugin does not have valid constructor! Skipping " + pluginClass);
|
Msg.warn(this, "Plugin does not have valid constructor! Skipping " + pluginClass);
|
||||||
continue;
|
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) {
|
public PluginDescription getPluginDescription(String className) {
|
||||||
return descriptionsByName.get(className);
|
return descriptionsByName.get(className);
|
||||||
}
|
}
|
||||||
-1
@@ -23,7 +23,6 @@ import docking.action.*;
|
|||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
import ghidra.framework.OperatingSystem;
|
import ghidra.framework.OperatingSystem;
|
||||||
import ghidra.framework.Platform;
|
import ghidra.framework.Platform;
|
||||||
import ghidra.framework.plugintool.util.PluginsConfiguration;
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
|
||||||
public class StandAlonePluginTool extends PluginTool {
|
public class StandAlonePluginTool extends PluginTool {
|
||||||
|
|||||||
-5
@@ -34,11 +34,6 @@ public class ToolServicesAdapter implements ToolServices {
|
|||||||
// override
|
// override
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void displaySimilarTool(PluginTool tool, DomainFile domainFile, PluginEvent event) {
|
|
||||||
// override
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File exportTool(ToolTemplate tool) throws FileNotFoundException, IOException {
|
public File exportTool(ToolTemplate tool) throws FileNotFoundException, IOException {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+1
-3
@@ -28,9 +28,7 @@ import generic.theme.GColor;
|
|||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.util.HTMLUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class that defines a panel for displaying name/value pairs with html-formatting.
|
* 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}
|
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractDetailsPanel extends JPanel {
|
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;
|
package ghidra.framework.plugintool.dialog;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -93,6 +94,8 @@ class PluginInstallerTableModel
|
|||||||
descriptor.addVisibleColumn(new PluginNameColumn(), 1, true);
|
descriptor.addVisibleColumn(new PluginNameColumn(), 1, true);
|
||||||
descriptor.addVisibleColumn(new PluginDescriptionColumn());
|
descriptor.addVisibleColumn(new PluginDescriptionColumn());
|
||||||
descriptor.addVisibleColumn(new PluginCategoryColumn());
|
descriptor.addVisibleColumn(new PluginCategoryColumn());
|
||||||
|
descriptor.addHiddenColumn(new PluginModuleColumn());
|
||||||
|
descriptor.addHiddenColumn(new PluginLocationColumn());
|
||||||
|
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
@@ -177,7 +180,7 @@ class PluginInstallerTableModel
|
|||||||
* Column for displaying the interactive checkbox, allowing the user to install
|
* Column for displaying the interactive checkbox, allowing the user to install
|
||||||
* or uninstall the plugin.
|
* or uninstall the plugin.
|
||||||
*/
|
*/
|
||||||
class PluginInstalledColumn extends
|
private class PluginInstalledColumn extends
|
||||||
AbstractDynamicTableColumn<PluginDescription, Boolean, List<PluginDescription>> {
|
AbstractDynamicTableColumn<PluginDescription, Boolean, List<PluginDescription>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -200,7 +203,7 @@ class PluginInstallerTableModel
|
|||||||
/**
|
/**
|
||||||
* Column for displaying the status of the plugin.
|
* Column for displaying the status of the plugin.
|
||||||
*/
|
*/
|
||||||
class PluginStatusColumn
|
private class PluginStatusColumn
|
||||||
extends AbstractDynamicTableColumn<PluginDescription, Icon, List<PluginDescription>> {
|
extends AbstractDynamicTableColumn<PluginDescription, Icon, List<PluginDescription>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -223,7 +226,7 @@ class PluginInstallerTableModel
|
|||||||
/**
|
/**
|
||||||
* Column for displaying the extension name of the plugin.
|
* Column for displaying the extension name of the plugin.
|
||||||
*/
|
*/
|
||||||
class PluginNameColumn
|
private class PluginNameColumn
|
||||||
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -246,7 +249,7 @@ class PluginInstallerTableModel
|
|||||||
/**
|
/**
|
||||||
* Column for displaying the plugin description.
|
* Column for displaying the plugin description.
|
||||||
*/
|
*/
|
||||||
class PluginDescriptionColumn
|
private class PluginDescriptionColumn
|
||||||
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
||||||
|
|
||||||
@Override
|
@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.
|
* Column for displaying the plugin category.
|
||||||
*/
|
*/
|
||||||
class PluginCategoryColumn
|
private class PluginCategoryColumn
|
||||||
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
extends AbstractDynamicTableColumn<PluginDescription, String, List<PluginDescription>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+1
@@ -16,6 +16,7 @@
|
|||||||
package ghidra.framework.plugintool.util;
|
package ghidra.framework.plugintool.util;
|
||||||
|
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A configuration that includes all plugins on the classpath.
|
* 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
|
* Return the name of the module that contains the plugin.
|
||||||
* DEVELOP. Within a type, plugins are grouped by category.
|
* @return the module name
|
||||||
* @return the type (or null if there is no module)
|
|
||||||
*/
|
*/
|
||||||
public String getModuleName() {
|
public String getModuleName() {
|
||||||
if (moduleName == null) {
|
if (moduleName == null) {
|
||||||
ResourceFile moduleRootDirectory = Application.getMyModuleRootDirectory();
|
ResourceFile moduleDir = Application.getModuleContainingClass(pluginClass.getName());
|
||||||
moduleName = (moduleRootDirectory == null) ? null : moduleRootDirectory.getName();
|
moduleName = (moduleDir == null) ? "<No Module>" : moduleDir.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
return moduleName;
|
return moduleName;
|
||||||
|
|||||||
-131
@@ -15,14 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.framework.plugintool.util;
|
package ghidra.framework.plugintool.util;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.reflect.*;
|
import java.lang.reflect.*;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.dialog.ExtensionDetails;
|
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
import ghidra.util.classfinder.ClassSearcher;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
@@ -33,99 +28,6 @@ import ghidra.util.exception.AssertException;
|
|||||||
*/
|
*/
|
||||||
public class PluginUtils {
|
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}.
|
* 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.plugintool.dialog;
|
package ghidra.framework.project.extensions;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
@@ -22,6 +22,7 @@ import javax.swing.text.SimpleAttributeSet;
|
|||||||
|
|
||||||
import docking.widgets.table.threaded.ThreadedTableModelListener;
|
import docking.widgets.table.threaded.ThreadedTableModelListener;
|
||||||
import generic.theme.GColor;
|
import generic.theme.GColor;
|
||||||
|
import ghidra.framework.plugintool.dialog.AbstractDetailsPanel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Panel that shows information about the selected extension in the {@link ExtensionTablePanel}. This
|
* 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.plugintool.dialog;
|
package ghidra.framework.project.extensions;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.io.File;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
@@ -26,13 +25,11 @@ import ghidra.docking.settings.Settings;
|
|||||||
import ghidra.framework.Application;
|
import ghidra.framework.Application;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.SystemUtilities;
|
|
||||||
import ghidra.util.datastruct.Accumulator;
|
import ghidra.util.datastruct.Accumulator;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
import ghidra.util.table.column.GColumnRenderer;
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import utilities.util.FileUtilities;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for the {@link ExtensionTablePanel}. This defines 5 columns for displaying information in
|
* 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. */
|
/** This is the data source for the model. Whatever is here will be displayed in the table. */
|
||||||
private Set<ExtensionDetails> extensions;
|
private Set<ExtensionDetails> extensions;
|
||||||
|
private Map<String, Boolean> originalInstallStates = new HashMap<>();
|
||||||
/** Indicates if the model has changed due to an install or uninstall. */
|
|
||||||
private boolean modelChanged = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@@ -94,21 +89,17 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
if (Application.inSingleJarMode() || SystemUtilities.isInDevelopmentMode()) {
|
if (Application.inSingleJarMode()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not allow GUI removal of extensions manually installed in installation directory.
|
||||||
ExtensionDetails extension = getSelectedExtension(rowIndex);
|
ExtensionDetails extension = getSelectedExtension(rowIndex);
|
||||||
|
if (extension.isInstalledInInstallationFolder()) {
|
||||||
// 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()))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (columnIndex == INSTALLED_COL);
|
return columnIndex == INSTALLED_COL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,7 +108,6 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
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
|
// We only care about the install column here, as it's the only one that
|
||||||
// is editable.
|
// is editable.
|
||||||
@@ -131,31 +121,50 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
|||||||
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
||||||
if (!installDir.exists() && !installDir.mkdir()) {
|
if (!installDir.exists() && !installDir.mkdir()) {
|
||||||
Msg.showError(this, null, "Directory Error",
|
Msg.showError(this, null, "Directory Error",
|
||||||
"Cannot install/uninstall extensions: Failed to create extension installation directory.\n" +
|
"Cannot install/uninstall extensions: Failed to create extension installation " +
|
||||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
"directory.\nSee the \"Ghidra Extension Notes\" section of the Ghidra " +
|
||||||
|
"Installation Guide for more information.");
|
||||||
}
|
}
|
||||||
if (!installDir.canWrite()) {
|
if (!installDir.canWrite()) {
|
||||||
Msg.showError(this, null, "Permissions Error",
|
Msg.showError(this, null, "Permissions Error",
|
||||||
"Cannot install/uninstall extensions: Invalid write permissions on installation directory.\n" +
|
"Cannot install/uninstall extensions: Invalid write permissions on installation " +
|
||||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
"directory.\nSee the \"Ghidra Extension Notes\" section of the Ghidra " +
|
||||||
|
"Installation Guide for more information.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean install = ((Boolean) aValue).booleanValue();
|
boolean install = ((Boolean) aValue).booleanValue();
|
||||||
ExtensionDetails extension = getSelectedExtension(rowIndex);
|
ExtensionDetails extension = getSelectedExtension(rowIndex);
|
||||||
|
if (!install) {
|
||||||
if (install) {
|
if (extension.markForUninstall()) {
|
||||||
if (ExtensionUtils.install(extension, true)) {
|
refreshTable();
|
||||||
modelChanged = true;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
if (ExtensionUtils.removeStateFiles(extension)) {
|
// Restore an existing extension or install an archived extension
|
||||||
modelChanged = true;
|
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
|
* @param details the extension to check
|
||||||
* @return true if extension version is valid for this version of Ghidra
|
* @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 ghidraVersion = Application.getApplicationVersion();
|
||||||
String extensionVersion = details.getVersion();
|
String extensionVersion = details.getVersion();
|
||||||
|
|
||||||
return ghidraVersion.equals(extensionVersion);
|
return ghidraVersion.equals(extensionVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,12 +192,28 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
Set<ExtensionDetails> archived = ExtensionUtils.getArchiveExtensions();
|
||||||
extensions = ExtensionUtils.getExtensions();
|
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);
|
extensions = new HashSet<>();
|
||||||
return;
|
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);
|
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
|
* @return true if the model has changed as a result of installing or uninstalling an extension
|
||||||
*/
|
*/
|
||||||
public boolean hasModelChanged() {
|
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
|
private class ExtensionNameColumn
|
||||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||||
|
|
||||||
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
private ExtRenderer renderer = new ExtRenderer();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
@@ -271,7 +303,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
|||||||
private class ExtensionDescriptionColumn
|
private class ExtensionDescriptionColumn
|
||||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||||
|
|
||||||
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
private ExtRenderer renderer = new ExtRenderer();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
@@ -301,7 +333,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
|||||||
private class ExtensionVersionColumn
|
private class ExtensionVersionColumn
|
||||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||||
|
|
||||||
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
private ExtRenderer renderer = new ExtRenderer();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
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
|
@Override
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
Component comp = super.getTableCellRendererComponent(data);
|
Component comp = super.getTableCellRendererComponent(data);
|
||||||
|
|
||||||
ExtensionDetails extension = getSelectedExtension(data.getRowViewIndex());
|
ExtensionDetails extension = getSelectedExtension(data.getRowViewIndex());
|
||||||
if (!isValidVersion(extension)) {
|
if (!matchesGhidraVersion(extension)) {
|
||||||
comp.setForeground(getErrorForegroundColor(data.isSelected()));
|
comp.setForeground(getErrorForegroundColor(data.isSelected()));
|
||||||
}
|
}
|
||||||
|
|
||||||
+1
-4
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.plugintool.dialog;
|
package ghidra.framework.project.extensions;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
@@ -70,9 +70,6 @@ public class ExtensionTablePanel extends JPanel {
|
|||||||
// way to restrict column width.
|
// way to restrict column width.
|
||||||
TableColumn col = table.getColumnModel().getColumn(ExtensionTableModel.INSTALLED_COL);
|
TableColumn col = table.getColumnModel().getColumn(ExtensionTableModel.INSTALLED_COL);
|
||||||
col.setMaxWidth(25);
|
col.setMaxWidth(25);
|
||||||
|
|
||||||
// Finally, load the table with some data.
|
|
||||||
refreshTable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
+26
-93
@@ -13,12 +13,11 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.plugintool.dialog;
|
package ghidra.framework.project.extensions;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
@@ -32,7 +31,8 @@ import generic.jar.ResourceFile;
|
|||||||
import ghidra.app.util.GenericHelpTopics;
|
import ghidra.app.util.GenericHelpTopics;
|
||||||
import ghidra.framework.Application;
|
import ghidra.framework.Application;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
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.GhidraFileChooserModel;
|
||||||
import ghidra.util.filechooser.GhidraFileFilter;
|
import ghidra.util.filechooser.GhidraFileFilter;
|
||||||
import resources.Icons;
|
import resources.Icons;
|
||||||
@@ -43,6 +43,8 @@ import resources.Icons;
|
|||||||
*/
|
*/
|
||||||
public class ExtensionTableProvider extends DialogComponentProvider {
|
public class ExtensionTableProvider extends DialogComponentProvider {
|
||||||
|
|
||||||
|
private static final String LAST_IMPORT_DIRECTORY_KEY = "LastExtensionImportDirectory";
|
||||||
|
|
||||||
private ExtensionTablePanel extensionTablePanel;
|
private ExtensionTablePanel extensionTablePanel;
|
||||||
|
|
||||||
private boolean requireRestart = false;
|
private boolean requireRestart = false;
|
||||||
@@ -126,57 +128,28 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
|||||||
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
||||||
if (!installDir.exists() && !installDir.mkdir()) {
|
if (!installDir.exists() && !installDir.mkdir()) {
|
||||||
Msg.showError(this, null, "Directory Error",
|
Msg.showError(this, null, "Directory Error",
|
||||||
"Cannot install/uninstall extensions: Failed to create extension installation directory.\n" +
|
"Cannot install/uninstall extensions: Failed to create extension " +
|
||||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
"installation directory: " + installDir);
|
||||||
}
|
}
|
||||||
if (!installDir.canWrite()) {
|
if (!installDir.canWrite()) {
|
||||||
Msg.showError(this, null, "Permissions Error",
|
Msg.showError(this, null, "Permissions Error",
|
||||||
"Cannot install/uninstall extensions: Invalid write permissions on installation directory.\n" +
|
"Cannot install/uninstall extensions: Invalid write permissions on " +
|
||||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
"installation directory: " + installDir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
|
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
|
||||||
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES);
|
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_AND_DIRECTORIES);
|
||||||
chooser.setTitle("Select extension");
|
chooser.setLastDirectoryPreference(LAST_IMPORT_DIRECTORY_KEY);
|
||||||
|
chooser.setTitle("Select Extension");
|
||||||
chooser.addFileFilter(new ExtensionFileFilter());
|
chooser.addFileFilter(new ExtensionFileFilter());
|
||||||
|
|
||||||
List<File> files = chooser.getSelectedFiles();
|
List<File> files = chooser.getSelectedFiles();
|
||||||
chooser.dispose();
|
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 (installExtensions(files)) {
|
||||||
if (extensionVersion == null) {
|
panel.refreshTable();
|
||||||
Msg.showError(this, null, "Installation Error",
|
requireRestart = true;
|
||||||
"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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -185,49 +158,18 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
|||||||
addAction.setMenuBarData(new MenuData(new String[] { "Add Extension" }, addIcon, group));
|
addAction.setMenuBarData(new MenuData(new String[] { "Add Extension" }, addIcon, group));
|
||||||
addAction.setToolBarData(new ToolBarData(addIcon, group));
|
addAction.setToolBarData(new ToolBarData(addIcon, group));
|
||||||
addAction.setHelpLocation(new HelpLocation(GenericHelpTopics.FRONT_END, "ExtensionTools"));
|
addAction.setHelpLocation(new HelpLocation(GenericHelpTopics.FRONT_END, "ExtensionTools"));
|
||||||
addAction.setDescription(
|
addAction.setDescription("Add extension");
|
||||||
SystemUtilities.isInDevelopmentMode() ? "Add Extension (disabled in development mode)"
|
addAction.setEnabled(!Application.inSingleJarMode());
|
||||||
: "Add extension");
|
|
||||||
addAction.setEnabled(
|
|
||||||
!SystemUtilities.isInDevelopmentMode() && !Application.inSingleJarMode());
|
|
||||||
addAction(addAction);
|
addAction(addAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getExtensionVersion(File file) {
|
private boolean installExtensions(List<File> files) {
|
||||||
|
boolean didInstall = false;
|
||||||
// If the given file is a directory...
|
for (File file : files) {
|
||||||
if (!file.isFile()) {
|
boolean success = ExtensionUtils.install(file);
|
||||||
List<ResourceFile> propFiles =
|
didInstall |= success;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return didInstall;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,9 +200,8 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter for a {@link GhidraFileChooser} that restricts selection to those
|
* Filter for a {@link GhidraFileChooser} that restricts selection to those files that are
|
||||||
* files that are Ghidra Extensions (zip files with an extension.properties
|
* Ghidra Extensions (zip files with an extension.properties file) or folders.
|
||||||
* file) or folders.
|
|
||||||
*/
|
*/
|
||||||
private class ExtensionFileFilter implements GhidraFileFilter {
|
private class ExtensionFileFilter implements GhidraFileFilter {
|
||||||
@Override
|
@Override
|
||||||
@@ -269,16 +210,8 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(File f, GhidraFileChooserModel l_model) {
|
public boolean accept(File f, GhidraFileChooserModel model) {
|
||||||
|
return f.isDirectory() || ExtensionUtils.isExtension(f);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+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.main.ApplicationLevelOnlyPlugin;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
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
|
* 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;
|
package ghidra.framework.project.tool;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
@@ -30,13 +26,9 @@ import docking.widgets.OptionDialog;
|
|||||||
import ghidra.app.util.FileOpenDropHandler;
|
import ghidra.app.util.FileOpenDropHandler;
|
||||||
import ghidra.framework.model.Project;
|
import ghidra.framework.model.Project;
|
||||||
import ghidra.framework.model.ToolTemplate;
|
import ghidra.framework.model.ToolTemplate;
|
||||||
import ghidra.framework.options.PreferenceState;
|
|
||||||
import ghidra.framework.plugintool.PluginConfigurationModel;
|
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.dialog.*;
|
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||||
import ghidra.framework.plugintool.util.*;
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.Msg;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool created by the workspace when the user chooses to create a new
|
* 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?";
|
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;
|
public static boolean autoSave = true;
|
||||||
|
|
||||||
private FileOpenDropHandler fileOpenDropHandler;
|
private FileOpenDropHandler fileOpenDropHandler;
|
||||||
private DockingAction configureToolAction;
|
private DockingAction configureToolAction;
|
||||||
|
|
||||||
|
private ExtensionManager extensionManager;
|
||||||
|
private boolean hasBeenShown;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new Ghidra Tool.
|
* Construct a new Ghidra Tool.
|
||||||
*
|
*
|
||||||
@@ -77,6 +67,18 @@ public class GhidraTool extends PluginTool {
|
|||||||
super(project, template);
|
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
|
@Override
|
||||||
protected DockingWindowManager createDockingWindowManager(boolean isDockable, boolean hasStatus,
|
protected DockingWindowManager createDockingWindowManager(boolean isDockable, boolean hasStatus,
|
||||||
boolean isModal) {
|
boolean isModal) {
|
||||||
@@ -122,6 +124,31 @@ public class GhidraTool extends PluginTool {
|
|||||||
winMgr.restoreWindowDataFromXml(rootElement);
|
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
|
@Override
|
||||||
public boolean shouldSave() {
|
public boolean shouldSave() {
|
||||||
if (autoSave) {
|
if (autoSave) {
|
||||||
@@ -207,175 +234,6 @@ public class GhidraTool extends PluginTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void showConfig() {
|
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);
|
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.AppInfo;
|
||||||
import ghidra.framework.main.FrontEndTool;
|
import ghidra.framework.main.FrontEndTool;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.PluginEvent;
|
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.preferences.Preferences;
|
import ghidra.framework.preferences.Preferences;
|
||||||
import ghidra.framework.protocol.ghidra.GetUrlContentTypeTask;
|
import ghidra.framework.protocol.ghidra.GetUrlContentTypeTask;
|
||||||
@@ -169,27 +168,6 @@ class ToolServicesImpl implements ToolServices {
|
|||||||
return toolChest;
|
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() {
|
private static DefaultLaunchMode getDefaultLaunchMode() {
|
||||||
DefaultLaunchMode defaultLaunchMode = DefaultLaunchMode.DEFAULT;
|
DefaultLaunchMode defaultLaunchMode = DefaultLaunchMode.DEFAULT;
|
||||||
FrontEndTool frontEndTool = AppInfo.getFrontEndTool();
|
FrontEndTool frontEndTool = AppInfo.getFrontEndTool();
|
||||||
|
|||||||
+18
-26
@@ -79,14 +79,9 @@ class WorkspaceImpl implements Workspace {
|
|||||||
PluginTool tool = toolManager.getTool(this, template);
|
PluginTool tool = toolManager.getTool(this, template);
|
||||||
if (tool != null) {
|
if (tool != null) {
|
||||||
tool.setVisible(true);
|
tool.setVisible(true);
|
||||||
|
|
||||||
if (tool instanceof GhidraTool) {
|
|
||||||
GhidraTool gTool = (GhidraTool) tool;
|
|
||||||
gTool.checkForNewExtensions();
|
|
||||||
}
|
|
||||||
runningTools.add(tool);
|
runningTools.add(tool);
|
||||||
|
|
||||||
// alert the tool manager that we changed
|
// alert the tool manager that we have changed
|
||||||
toolManager.setWorkspaceChanged(this);
|
toolManager.setWorkspaceChanged(this);
|
||||||
toolManager.fireToolAddedEvent(this, tool);
|
toolManager.fireToolAddedEvent(this, tool);
|
||||||
}
|
}
|
||||||
@@ -161,6 +156,7 @@ class WorkspaceImpl implements Workspace {
|
|||||||
String defaultTool = System.getProperty("ghidra.defaulttool");
|
String defaultTool = System.getProperty("ghidra.defaulttool");
|
||||||
if (defaultTool != null && !defaultTool.equals("")) {
|
if (defaultTool != null && !defaultTool.equals("")) {
|
||||||
PluginTool tool = toolManager.getTool(defaultTool);
|
PluginTool tool = toolManager.getTool(defaultTool);
|
||||||
|
tool.setVisible(isActive);
|
||||||
runningTools.add(tool);
|
runningTools.add(tool);
|
||||||
toolManager.fireToolAddedEvent(this, tool);
|
toolManager.fireToolAddedEvent(this, tool);
|
||||||
return;
|
return;
|
||||||
@@ -175,27 +171,23 @@ class WorkspaceImpl implements Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PluginTool tool = toolManager.getTool(toolName);
|
PluginTool tool = toolManager.getTool(toolName);
|
||||||
if (tool != null) {
|
if (tool == null) {
|
||||||
tool.setVisible(isActive);
|
continue;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
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
|
* 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
|
* 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 boolean unappliedChanges;
|
||||||
private SpecExtension specExtension;
|
private SpecExtension specExtension;
|
||||||
private List<CompilerElement> tableElements;
|
private List<CompilerElement> tableElements;
|
||||||
private ExtensionTableModel tableModel;
|
private SpecExtensionTableModel tableModel;
|
||||||
private GTable extensionTable;
|
private GTable extensionTable;
|
||||||
private JButton exportButton;
|
private JButton exportButton;
|
||||||
private JButton removeButton;
|
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" };
|
private final String[] columnNames = { "Extension Type", "Name", "Status" };
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -383,7 +383,7 @@ public class SpecExtensionPanel extends JPanel {
|
|||||||
|
|
||||||
private void createPanel() {
|
private void createPanel() {
|
||||||
setLayout(new BorderLayout(10, 10));
|
setLayout(new BorderLayout(10, 10));
|
||||||
tableModel = new ExtensionTableModel();
|
tableModel = new SpecExtensionTableModel();
|
||||||
extensionTable = new CompilerElementTable(tableModel);
|
extensionTable = new CompilerElementTable(tableModel);
|
||||||
|
|
||||||
JScrollPane sp = new JScrollPane(extensionTable);
|
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