mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
tease out GhidraScriptUtil state, clarify headless and headed cases
- GhidraScriptUtil how has (static) initialize & dispose - initialize is either through (headed/GUI) GhidraScriptMgrPlugin or directly (headless) - BundleHost - now owns bundle paths (script dirs) - save and restore to SaveState for use by GUI - no static instances - GhidraScriptUtil BundleHost coincides with GUI's when both are used - generates events - conservative use of auto-vivification - BundleStatusCompoentProvider / PathManager - better split between GUI & model - subscribes to GUI's BundleHost for events - provider registers for generated events, controls BundleHost
This commit is contained in:
parent
2c27ca1376
commit
49e3d6dcdc
19 changed files with 849 additions and 743 deletions
|
@ -19,6 +19,8 @@ import java.io.*;
|
|||
import java.net.URL;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -34,33 +36,23 @@ import org.osgi.framework.wiring.*;
|
|||
import org.osgi.service.log.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.*;
|
||||
|
||||
public class BundleHost {
|
||||
protected static final boolean DUMP_TO_STDERR = false;
|
||||
// XXX ScriptProviders don't have any way to access services or other way to access system wide resources
|
||||
static private BundleHost _instance;
|
||||
|
||||
static public BundleHost getInstance() {
|
||||
if (_instance == null) {
|
||||
_instance = new BundleHost();
|
||||
try {
|
||||
_instance.startFelix();
|
||||
}
|
||||
catch (OSGiException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
static public String getSymbolicNameFromSourceDir(ResourceFile sourceDir) {
|
||||
return Integer.toHexString(sourceDir.getAbsolutePath().hashCode());
|
||||
}
|
||||
|
||||
public BundleHost() {
|
||||
//
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (felix != null) {
|
||||
forceStopFelix();
|
||||
|
@ -68,25 +60,59 @@ public class BundleHost {
|
|||
}
|
||||
|
||||
HashMap<ResourceFile, GhidraBundle> bp2gb = new HashMap<>();
|
||||
HashMap<String, GhidraBundle> bl2gb = new HashMap<>();
|
||||
|
||||
public GhidraBundle getGhidraBundle(ResourceFile path) {
|
||||
return bp2gb.computeIfAbsent(path, p -> newGhidraBundle0(path, true, false));
|
||||
public GhidraBundle getExistingGhidraBundle(ResourceFile bundlePath) {
|
||||
GhidraBundle r = bp2gb.get(bundlePath);
|
||||
if (r == null) {
|
||||
Msg.showError(this, null, "ghidra bundle cache",
|
||||
"getExistingGhidraBundle before GhidraBundle created: " + bundlePath);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public GhidraBundle newGhidraBundle(ResourceFile path, boolean enabled, boolean systemBundle) {
|
||||
GhidraBundle gb = newGhidraBundle0(path, enabled, systemBundle);
|
||||
bp2gb.put(path, gb);
|
||||
public GhidraBundle enablePath(ResourceFile bundlePath) {
|
||||
GhidraBundle gb = bp2gb.get(bundlePath);
|
||||
if (gb == null) {
|
||||
gb = addGhidraBundle(bundlePath, true, false);
|
||||
}
|
||||
return gb;
|
||||
}
|
||||
|
||||
private GhidraBundle newGhidraBundle0(ResourceFile path, boolean enabled,
|
||||
public GhidraBundle addGhidraBundle(ResourceFile path, boolean enabled, boolean systemBundle) {
|
||||
GhidraBundle gb = newGhidraBundle(this, path, enabled, systemBundle);
|
||||
bp2gb.put(path, gb);
|
||||
bl2gb.put(gb.getBundleLoc(), gb);
|
||||
fireBundleAdded(gb);
|
||||
return gb;
|
||||
}
|
||||
|
||||
public void addGhidraBundles(List<ResourceFile> paths, boolean enabled, boolean systemBundle) {
|
||||
Map<ResourceFile, GhidraBundle> newmap =
|
||||
paths.stream().collect(Collectors.toUnmodifiableMap(Function.identity(),
|
||||
path -> newGhidraBundle(BundleHost.this, path, enabled, systemBundle)));
|
||||
bp2gb.putAll(newmap);
|
||||
bl2gb.putAll(newmap.values().stream().collect(
|
||||
Collectors.toUnmodifiableMap(GhidraBundle::getBundleLoc, Function.identity())));
|
||||
fireBundlesAdded(newmap.values());
|
||||
}
|
||||
|
||||
public void add(List<GhidraBundle> gbundles) {
|
||||
for (GhidraBundle gb : gbundles) {
|
||||
bp2gb.put(gb.getPath(), gb);
|
||||
bl2gb.put(gb.getBundleLoc(), gb);
|
||||
}
|
||||
fireBundlesAdded(gbundles);
|
||||
}
|
||||
|
||||
static private GhidraBundle newGhidraBundle(BundleHost bh, ResourceFile path, boolean enabled,
|
||||
boolean systemBundle) {
|
||||
switch (GhidraBundle.getType(path)) {
|
||||
case SourceDir:
|
||||
return new GhidraSourceBundle(this, path, enabled, systemBundle);
|
||||
return new GhidraSourceBundle(bh, path, enabled, systemBundle);
|
||||
case BndScript:
|
||||
case Jar:
|
||||
return new GhidraJarBundle(this, path, enabled, systemBundle);
|
||||
return new GhidraJarBundle(bh, path, enabled, systemBundle);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -94,8 +120,30 @@ public class BundleHost {
|
|||
}
|
||||
|
||||
// XXX consumers must clean up after themselves
|
||||
public void removeGhidraBundle(ResourceFile path) {
|
||||
bp2gb.remove(path);
|
||||
public void removeBundlePath(ResourceFile bundlePath) {
|
||||
GhidraBundle gb = bp2gb.remove(bundlePath);
|
||||
bl2gb.remove(gb.getBundleLoc());
|
||||
fireBundleRemoved(gb);
|
||||
}
|
||||
|
||||
public void removeBundleLoc(String bundleLoc) {
|
||||
GhidraBundle gb = bl2gb.remove(bundleLoc);
|
||||
bp2gb.remove(gb.getPath());
|
||||
fireBundleRemoved(gb);
|
||||
}
|
||||
|
||||
public void remove(GhidraBundle gb) {
|
||||
bp2gb.remove(gb.getPath());
|
||||
bl2gb.remove(gb.getBundleLoc());
|
||||
fireBundleRemoved(gb);
|
||||
}
|
||||
|
||||
public void remove(Collection<GhidraBundle> gbundles) {
|
||||
for (GhidraBundle gb : gbundles) {
|
||||
bp2gb.remove(gb.getPath());
|
||||
bl2gb.remove(gb.getBundleLoc());
|
||||
}
|
||||
fireBundlesRemoved(gbundles);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -343,12 +391,15 @@ public class BundleHost {
|
|||
String l = b.getLocation();
|
||||
System.err.printf("%s %s from %s\n", getEventTypeString(event), n, l);
|
||||
}
|
||||
GhidraBundle gb;
|
||||
switch (event.getType()) {
|
||||
case BundleEvent.STARTED:
|
||||
fireBundleActivationChange(b, true);
|
||||
gb = bl2gb.get(b.getLocation());
|
||||
fireBundleActivationChange(gb, true);
|
||||
break;
|
||||
case BundleEvent.UNINSTALLED:
|
||||
fireBundleActivationChange(b, false);
|
||||
gb = bl2gb.get(b.getLocation());
|
||||
fireBundleActivationChange(gb, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -544,55 +595,159 @@ public class BundleHost {
|
|||
}
|
||||
}
|
||||
|
||||
List<OSGiListener> osgiListeners = new ArrayList<>();
|
||||
List<BundleHostListener> listeners = new ArrayList<>();
|
||||
|
||||
void fireBundleBuilt(GhidraBundle sbi) {
|
||||
synchronized (osgiListeners) {
|
||||
for (OSGiListener l : osgiListeners) {
|
||||
synchronized (listeners) {
|
||||
for (BundleHostListener l : listeners) {
|
||||
l.bundleBuilt(sbi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fireBundleActivationChange(Bundle b, boolean newActivation) {
|
||||
synchronized (osgiListeners) {
|
||||
for (OSGiListener l : osgiListeners) {
|
||||
l.bundleActivationChange(b, newActivation);
|
||||
void fireBundleEnablementChange(GhidraBundle gb, boolean newEnablement) {
|
||||
synchronized (listeners) {
|
||||
for (BundleHostListener l : listeners) {
|
||||
l.bundleEnablementChange(gb, newEnablement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(OSGiListener osgiListener) {
|
||||
synchronized (osgiListeners) {
|
||||
osgiListeners.add(osgiListener);
|
||||
void fireBundleActivationChange(GhidraBundle gb, boolean newEnablement) {
|
||||
synchronized (listeners) {
|
||||
for (BundleHostListener l : listeners) {
|
||||
l.bundleActivationChange(gb, newEnablement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(OSGiListener osgiListener) {
|
||||
synchronized (osgiListeners) {
|
||||
osgiListeners.remove(osgiListener);
|
||||
private void fireBundleAdded(GhidraBundle gb) {
|
||||
synchronized (listeners) {
|
||||
for (BundleHostListener l : listeners) {
|
||||
l.bundleAdded(gb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireBundleRemoved(GhidraBundle gb) {
|
||||
synchronized (listeners) {
|
||||
for (BundleHostListener l : listeners) {
|
||||
l.bundleRemoved(gb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireBundlesAdded(Collection<GhidraBundle> gbundles) {
|
||||
synchronized (listeners) {
|
||||
for (BundleHostListener l : listeners) {
|
||||
l.bundlesAdded(gbundles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireBundlesRemoved(Collection<GhidraBundle> gbundles) {
|
||||
synchronized (listeners) {
|
||||
for (BundleHostListener l : listeners) {
|
||||
l.bundlesRemoved(gbundles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(BundleHostListener bundleHostListener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(bundleHostListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(BundleHostListener bundleHostListener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(bundleHostListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAllListeners() {
|
||||
synchronized (osgiListeners) {
|
||||
osgiListeners.clear();
|
||||
synchronized (listeners) {
|
||||
listeners.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private List<ResourceFile> bundlePaths = new ArrayList<>();
|
||||
|
||||
{
|
||||
setBundlePaths(GhidraScriptUtil.getSystemScriptPaths());
|
||||
getBundlePaths().add(0, GhidraScriptUtil.getUserScriptDirectory());
|
||||
public Collection<GhidraBundle> getGhidraBundles() {
|
||||
return bp2gb.values();
|
||||
}
|
||||
|
||||
public List<ResourceFile> getBundlePaths() {
|
||||
return bundlePaths;
|
||||
public Collection<ResourceFile> getBundlePaths() {
|
||||
return bp2gb.keySet();
|
||||
}
|
||||
|
||||
public void setBundlePaths(List<ResourceFile> bundlePaths) {
|
||||
this.bundlePaths = bundlePaths;
|
||||
public boolean enable(GhidraBundle gbundle) {
|
||||
if (!gbundle.enabled) {
|
||||
gbundle.enabled = true;
|
||||
fireBundleEnablementChange(gbundle, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* this is done at most once, and it's done AFTER system bundles have been added.
|
||||
*/
|
||||
public void restoreState(SaveState ss) {
|
||||
// XXX lock bundlehost operations
|
||||
String[] pathArr = ss.getStrings("BundleHost_PATH", new String[0]);
|
||||
|
||||
if (pathArr.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean[] enableArr = ss.getBooleans("BundleHost_ENABLE", new boolean[pathArr.length]);
|
||||
boolean[] systemArr = ss.getBooleans("BundleHost_SYSTEM", new boolean[pathArr.length]);
|
||||
|
||||
List<GhidraBundle> added = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < pathArr.length; i++) {
|
||||
ResourceFile bp = new ResourceFile(pathArr[i]);
|
||||
boolean en = enableArr[i];
|
||||
boolean sys = systemArr[i];
|
||||
GhidraBundle gb = bp2gb.get(bp);
|
||||
if (gb != null) {
|
||||
if (en != gb.isEnabled()) {
|
||||
gb.enabled = en;
|
||||
fireBundleEnablementChange(gb, en);
|
||||
}
|
||||
if (sys != gb.isSystemBundle()) {
|
||||
gb.systemBundle = sys;
|
||||
Msg.error(this, String.format("%s went from %system to %system", bp,
|
||||
sys ? "not " : "", sys ? "" : "not "));
|
||||
}
|
||||
}
|
||||
else if (sys) {
|
||||
// stored system bundles that weren't already initialized must be old, drop 'm.
|
||||
}
|
||||
else {
|
||||
added.add(newGhidraBundle(this, bp, en, sys));
|
||||
}
|
||||
}
|
||||
|
||||
add(added);
|
||||
}
|
||||
|
||||
public void saveState(SaveState ss) {
|
||||
int n = bp2gb.size();
|
||||
String[] pathArr = new String[n];
|
||||
boolean[] enableArr = new boolean[n];
|
||||
boolean[] systemArr = new boolean[n];
|
||||
|
||||
int index = 0;
|
||||
for (Entry<ResourceFile, GhidraBundle> e : bp2gb.entrySet()) {
|
||||
GhidraBundle gb = e.getValue();
|
||||
pathArr[index] = gb.getPath().toString();
|
||||
enableArr[index] = gb.isEnabled();
|
||||
systemArr[index] = gb.isSystemBundle();
|
||||
++index;
|
||||
}
|
||||
|
||||
ss.putStrings("BundleHost_PATH", pathArr);
|
||||
ss.putBooleans("BundleHost_ENABLE", enableArr);
|
||||
ss.putBooleans("BundleHost_SYSTEM", systemArr);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package ghidra.app.plugin.core.osgi;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Listener for OSGi framework events.
|
||||
*/
|
||||
public interface BundleHostListener {
|
||||
|
||||
default void bundleBuilt(GhidraBundle gbundle) {
|
||||
//
|
||||
}
|
||||
|
||||
default void bundleEnablementChange(GhidraBundle gbundle, boolean newEnablement) {
|
||||
//
|
||||
}
|
||||
|
||||
default void bundleActivationChange(GhidraBundle gbundle, boolean newActivation) {
|
||||
//
|
||||
}
|
||||
|
||||
default void bundleAdded(GhidraBundle gbundle) {
|
||||
//
|
||||
}
|
||||
|
||||
default void bundlesAdded(Collection<GhidraBundle> gbundles) {
|
||||
for (GhidraBundle gbundle : gbundles) {
|
||||
bundleAdded(gbundle);
|
||||
}
|
||||
}
|
||||
|
||||
default void bundleRemoved(GhidraBundle gbundle) {
|
||||
//
|
||||
}
|
||||
|
||||
default void bundlesRemoved(Collection<GhidraBundle> gbundles) {
|
||||
for (GhidraBundle gbundle : gbundles) {
|
||||
bundleRemoved(gbundle);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,22 +17,15 @@
|
|||
package ghidra.app.plugin.core.osgi;
|
||||
|
||||
/**
|
||||
* events thrown by BundleStatusModel component
|
||||
* events thrown by BundleStatus component when buttons are clicked
|
||||
*/
|
||||
public interface BundleStatusListener {
|
||||
public interface BundleStatusChangeRequestListener {
|
||||
|
||||
/**
|
||||
* Called when the list of bundle paths changes
|
||||
*/
|
||||
default public void bundlesChanged() {
|
||||
default public void bundleEnablementChangeRequest(BundleStatus status, boolean newValue) {
|
||||
//
|
||||
}
|
||||
|
||||
default public void bundleEnablementChanged(BundleStatus status, boolean newValue) {
|
||||
//
|
||||
}
|
||||
|
||||
default public void bundleActivationChanged(BundleStatus status, boolean newValue) {
|
||||
default public void bundleActivationChangeRequest(BundleStatus status, boolean newValue) {
|
||||
//
|
||||
}
|
||||
|
|
@ -46,12 +46,12 @@ import resources.ResourceManager;
|
|||
/**
|
||||
* component for managing OSGi bundle status
|
||||
*/
|
||||
public class BundleStatusProvider extends ComponentProviderAdapter {
|
||||
public class BundleStatusComponentProvider extends ComponentProviderAdapter {
|
||||
static String preferenceForLastSelectedBundle = "LastGhidraScriptBundle";
|
||||
|
||||
private JPanel panel;
|
||||
private LessFreneticGTable bundleStatusTable;
|
||||
private final BundleStatusModel bundleStatusModel;
|
||||
private final BundleStatusTableModel bundleStatusTableModel;
|
||||
private GTableFilterPanel<BundleStatus> filterPanel;
|
||||
|
||||
private GhidraFileChooser fileChooser;
|
||||
|
@ -61,23 +61,24 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
static final String BUNDLE_GROUP = "0bundle group";
|
||||
static final String BUNDLE_LIST_GROUP = "1bundle list group";
|
||||
|
||||
public BundleStatusProvider(PluginTool tool, String owner) {
|
||||
super(tool, "Bundle Status Manager", owner);
|
||||
this.bundleHost = BundleHost.getInstance();
|
||||
this.bundleStatusModel = new BundleStatusModel(this, bundleHost);
|
||||
public BundleStatusComponentProvider(PluginTool tool, String owner, BundleHost bundleHost) {
|
||||
super(tool, "Bundle Status Component", owner);
|
||||
this.bundleHost = bundleHost;
|
||||
this.bundleStatusTableModel = new BundleStatusTableModel(this, bundleHost);
|
||||
|
||||
bundleStatusModel.addListener(new BundleStatusListener() {
|
||||
bundleStatusTableModel.addListener(new BundleStatusChangeRequestListener() {
|
||||
@Override
|
||||
public void bundleEnablementChanged(BundleStatus status, boolean enabled) {
|
||||
public void bundleEnablementChangeRequest(BundleStatus status, boolean enabled) {
|
||||
if (!enabled && status.isActive()) {
|
||||
startActivateDeactiveTask(status, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundleActivationChanged(BundleStatus status, boolean newValue) {
|
||||
public void bundleActivationChangeRequest(BundleStatus status, boolean newValue) {
|
||||
startActivateDeactiveTask(status, newValue);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
this.filter = new GhidraFileFilter() {
|
||||
|
@ -101,7 +102,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
private void build() {
|
||||
panel = new JPanel(new BorderLayout(5, 5));
|
||||
|
||||
bundleStatusTable = new LessFreneticGTable(bundleStatusModel);
|
||||
bundleStatusTable = new LessFreneticGTable(bundleStatusTableModel);
|
||||
bundleStatusTable.setName("BUNDLESTATUS_TABLE");
|
||||
bundleStatusTable.setSelectionBackground(new Color(204, 204, 255));
|
||||
bundleStatusTable.setSelectionForeground(Color.BLACK);
|
||||
|
@ -112,7 +113,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
tool.contextChanged(BundleStatusProvider.this);
|
||||
tool.contextChanged(BundleStatusComponentProvider.this);
|
||||
});
|
||||
|
||||
// to allow custom cell renderers
|
||||
|
@ -124,14 +125,14 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
|
||||
//
|
||||
column =
|
||||
bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.enabledColumn.index);
|
||||
bundleStatusTable.getColumnModel().getColumn(bundleStatusTableModel.enabledColumn.index);
|
||||
column.setPreferredWidth(skinnyWidth);
|
||||
column.setMinWidth(skinnyWidth);
|
||||
column.setMaxWidth(skinnyWidth);
|
||||
column.setWidth(skinnyWidth);
|
||||
|
||||
//
|
||||
column = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.activeColumn.index);
|
||||
column = bundleStatusTable.getColumnModel().getColumn(bundleStatusTableModel.activeColumn.index);
|
||||
column.setPreferredWidth(skinnyWidth);
|
||||
column.setMinWidth(skinnyWidth);
|
||||
column.setMaxWidth(skinnyWidth);
|
||||
|
@ -159,11 +160,11 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
});
|
||||
|
||||
//
|
||||
column = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.typeColumn.index);
|
||||
column = bundleStatusTable.getColumnModel().getColumn(bundleStatusTableModel.typeColumn.index);
|
||||
FontMetrics fontmetrics = panel.getFontMetrics(panel.getFont());
|
||||
column.setMaxWidth(10 +
|
||||
SwingUtilities.computeStringWidth(fontmetrics, GhidraBundle.Type.SourceDir.toString()));
|
||||
column = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.pathColumn.index);
|
||||
column = bundleStatusTable.getColumnModel().getColumn(bundleStatusTableModel.pathColumn.index);
|
||||
column.setCellRenderer(new GTableCellRenderer() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
@ -177,7 +178,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
});
|
||||
|
||||
filterPanel = new GTableFilterPanel<>(bundleStatusTable, bundleStatusModel);
|
||||
filterPanel = new GTableFilterPanel<>(bundleStatusTable, bundleStatusTableModel);
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(bundleStatusTable);
|
||||
scrollPane.getViewport().setBackground(bundleStatusTable.getBackground());
|
||||
|
@ -312,8 +313,8 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
private void doClean() {
|
||||
int[] selectedModelRows = getSelectedModelRows();
|
||||
boolean anythingCleaned = false;
|
||||
for (BundleStatus o : bundleStatusModel.getRowObjects(selectedModelRows)) {
|
||||
anythingCleaned |= bundleHost.getGhidraBundle(o.getPath()).clean();
|
||||
for (BundleStatus bs : bundleStatusTableModel.getRowObjects(selectedModelRows)) {
|
||||
anythingCleaned |= bundleHost.getExistingGhidraBundle(bs.getPath()).clean();
|
||||
}
|
||||
if (anythingCleaned) {
|
||||
AnimationUtils.shakeComponent(getComponent());
|
||||
|
@ -326,7 +327,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
return;
|
||||
}
|
||||
|
||||
bundleStatusModel.remove(selectedModelRows);
|
||||
bundleStatusTableModel.remove(selectedModelRows);
|
||||
bundleStatusTable.clearSelection();
|
||||
}
|
||||
|
||||
|
@ -369,7 +370,10 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
if (!files.isEmpty()) {
|
||||
Preferences.setProperty(preferenceForLastSelectedBundle,
|
||||
files.get(0).getAbsolutePath());
|
||||
bundleStatusModel.addNewPaths(files, true, false);
|
||||
|
||||
bundleHost.addGhidraBundles(
|
||||
files.stream().map(ResourceFile::new).collect(Collectors.toUnmodifiableList()),
|
||||
true, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,9 +390,9 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
bundleStatusTable.chill();
|
||||
|
||||
List<GhidraBundle> gbs =
|
||||
bundleStatusModel.getRowObjects(selectedModelRows).stream().filter(
|
||||
bundleStatusTableModel.getRowObjects(selectedModelRows).stream().filter(
|
||||
bs -> !bs.isActive()).map(
|
||||
bs -> bundleHost.getGhidraBundle(bs.getPath())).collect(
|
||||
bs -> bundleHost.getExistingGhidraBundle(bs.getPath())).collect(
|
||||
Collectors.toList());
|
||||
int total = gbs.size();
|
||||
int total_activated = 0;
|
||||
|
@ -435,9 +439,9 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
long startTime = System.nanoTime();
|
||||
|
||||
List<GhidraBundle> gbs =
|
||||
bundleStatusModel.getRowObjects(selectedModelRows).stream().filter(
|
||||
bundleStatusTableModel.getRowObjects(selectedModelRows).stream().filter(
|
||||
bs -> bs.isActive()).map(
|
||||
bs -> bundleHost.getGhidraBundle(bs.getPath())).collect(
|
||||
bs -> bundleHost.getExistingGhidraBundle(bs.getPath())).collect(
|
||||
Collectors.toList());
|
||||
|
||||
int total = gbs.size();
|
||||
|
@ -470,7 +474,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
GhidraBundle sb = bundleHost.getGhidraBundle(status.getPath());
|
||||
GhidraBundle sb = bundleHost.getExistingGhidraBundle(status.getPath());
|
||||
if (activate) {
|
||||
sb.build(console.getStdErr());
|
||||
bundleHost.activateSynchronously(sb.getBundleLoc());
|
||||
|
@ -496,14 +500,14 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
|||
}, null, 1000);
|
||||
}
|
||||
|
||||
public BundleStatusModel getModel() {
|
||||
return bundleStatusModel;
|
||||
public BundleStatusTableModel getModel() {
|
||||
return bundleStatusTableModel;
|
||||
}
|
||||
|
||||
public void notifyTableRowChanged(BundleStatus status) {
|
||||
int modelRowIndex = bundleStatusModel.getRowIndex(status);
|
||||
int modelRowIndex = bundleStatusTableModel.getRowIndex(status);
|
||||
int viewRowIndex = filterPanel.getViewRow(modelRowIndex);
|
||||
bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusModel, viewRowIndex));
|
||||
bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusTableModel, viewRowIndex));
|
||||
}
|
||||
|
||||
@Override
|
|
@ -20,17 +20,12 @@ import java.io.File;
|
|||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.osgi.framework.Bundle;
|
||||
|
||||
import docking.widgets.table.AbstractSortedTableModel;
|
||||
import docking.widgets.table.TableSortingContext;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
||||
public class BundleStatusTableModel extends AbstractSortedTableModel<BundleStatus> {
|
||||
List<Column> columns = new ArrayList<>();
|
||||
|
||||
class Column {
|
||||
|
@ -72,8 +67,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
|
||||
@Override
|
||||
void setValue(BundleStatus status, Object newValue) {
|
||||
status.setEnabled((Boolean) newValue);
|
||||
fireBundleEnablementChanged(status, (Boolean) newValue);
|
||||
fireBundleEnablementChangeRequested(status, (Boolean) newValue);
|
||||
}
|
||||
};
|
||||
Column activeColumn = new Column("Active", Boolean.class) {
|
||||
|
@ -90,7 +84,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
@Override
|
||||
void setValue(BundleStatus status, Object newValue) {
|
||||
status.setActive((Boolean) newValue);
|
||||
fireBundleActivationChanged(status, (Boolean) newValue);
|
||||
fireBundleActivationChangeRequested(status, (Boolean) newValue);
|
||||
}
|
||||
};
|
||||
Column typeColumn = new Column("Type", String.class) {
|
||||
|
@ -126,19 +120,28 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
return badColumn;
|
||||
}
|
||||
|
||||
private BundleStatusProvider provider;
|
||||
private BundleStatusComponentProvider provider;
|
||||
private List<BundleStatus> statuses;
|
||||
private BundleHost bundleHost;
|
||||
OSGiListener bundleListener;
|
||||
BundleHostListener bundleListener;
|
||||
|
||||
private Map<String, BundleStatus> loc2status = new HashMap<>();
|
||||
|
||||
BundleStatus getStatus(String bundleLocation) {
|
||||
return loc2status.get(bundleLocation);
|
||||
BundleStatus getStatus(GhidraBundle gb) {
|
||||
BundleStatus status = loc2status.get(gb.getBundleLoc());
|
||||
if (status == null) {
|
||||
Msg.showError(BundleStatusTableModel.this, provider.getComponent(), "bundle status error",
|
||||
"bundle has no status!");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getBundleLoc(BundleStatus status) {
|
||||
return bundleHost.getGhidraBundle(status.getPath()).getBundleLoc();
|
||||
GhidraBundle gb = bundleHost.getExistingGhidraBundle(status.getPath());
|
||||
if (gb != null) {
|
||||
return gb.getBundleLoc();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,56 +157,75 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
}
|
||||
}
|
||||
|
||||
BundleStatusModel(BundleStatusProvider provider, BundleHost bundleHost) {
|
||||
BundleStatusTableModel(BundleStatusComponentProvider provider, BundleHost bundleHost) {
|
||||
super();
|
||||
this.provider = provider;
|
||||
this.bundleHost = bundleHost;
|
||||
statuses = new ArrayList<>();
|
||||
|
||||
// add unmodifiable paths
|
||||
this.statuses = GhidraScriptUtil.getSystemScriptPaths().stream().distinct().map(
|
||||
f -> new BundleStatus(f, true, true)).collect(Collectors.toList());
|
||||
// add user path
|
||||
this.statuses.add(0,
|
||||
new BundleStatus(GhidraScriptUtil.getUserScriptDirectory(), true, false));
|
||||
|
||||
computeCache();
|
||||
|
||||
bundleHost.addListener(bundleListener = new OSGiListener() {
|
||||
bundleHost.addListener(bundleListener = new BundleHostListener() {
|
||||
@Override
|
||||
public void bundleBuilt(GhidraBundle sb) {
|
||||
BundleStatus bp = getStatus(sb.getBundleLoc());
|
||||
if (bp != null) {
|
||||
bp.setSummary(sb.getSummary());
|
||||
int row = getRowIndex(bp);
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
public void bundleBuilt(GhidraBundle gb) {
|
||||
BundleStatus status = getStatus(gb);
|
||||
status.setSummary(gb.getSummary());
|
||||
int row = getRowIndex(status);
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundleActivationChange(Bundle b, boolean newActivation) {
|
||||
BundleStatus status = getStatus(b.getLocation());
|
||||
if (status == null) {
|
||||
Msg.showError(BundleStatusModel.this, provider.getComponent(),
|
||||
"bundle status error", "bundle has no status!");
|
||||
}
|
||||
public void bundleActivationChange(GhidraBundle gb, boolean newActivation) {
|
||||
BundleStatus status = getStatus(gb);
|
||||
int row = getRowIndex(status);
|
||||
if (newActivation) {
|
||||
GhidraBundle gb = bundleHost.getGhidraBundle(status.getPath());
|
||||
status.setActive(true);
|
||||
status.setSummary(gb.getSummary());
|
||||
int row = getRowIndex(status);
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
else {
|
||||
status.setActive(false);
|
||||
status.setSummary("");
|
||||
int row = getRowIndex(status);
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
});
|
||||
|
||||
// fireTableDataChanged(); // this is done by the addNewPaths during restoreState
|
||||
@Override
|
||||
public void bundleAdded(GhidraBundle gb) {
|
||||
addNewStatus(gb.getPath(), gb.isEnabled(), gb.isSystemBundle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundlesAdded(Collection<GhidraBundle> gbundles) {
|
||||
int index = statuses.size();
|
||||
for (GhidraBundle gb : gbundles) {
|
||||
BundleStatus status =
|
||||
new BundleStatus(gb.getPath(), gb.isEnabled(), gb.isSystemBundle());
|
||||
addStatusNoFire(status);
|
||||
}
|
||||
fireTableRowsInserted(index, gbundles.size() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundleRemoved(GhidraBundle gbundle) {
|
||||
BundleStatus status = getStatus(gbundle);
|
||||
removeStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundlesRemoved(Collection<GhidraBundle> gbundles) {
|
||||
List<BundleStatus> toRemove =
|
||||
gbundles.stream().map(BundleStatusTableModel.this::getStatus).collect(
|
||||
Collectors.toUnmodifiableList());
|
||||
removeStatuses(toRemove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundleEnablementChange(GhidraBundle gbundle, boolean newEnablement) {
|
||||
BundleStatus status = getStatus(gbundle);
|
||||
status.setEnabled(newEnablement);
|
||||
int row = getRowIndex(status);
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -222,7 +244,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
return list;
|
||||
}
|
||||
|
||||
private void addStatus(BundleStatus path) {
|
||||
private void addStatusNoFire(BundleStatus path) {
|
||||
if (statuses.contains(path)) {
|
||||
return;
|
||||
}
|
||||
|
@ -239,7 +261,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
private BundleStatus addNewStatus(ResourceFile path, boolean enabled, boolean readonly) {
|
||||
BundleStatus p = new BundleStatus(path, enabled, readonly);
|
||||
int index = statuses.size();
|
||||
addStatus(p);
|
||||
addStatusNoFire(p);
|
||||
fireTableRowsInserted(index, index);
|
||||
return p;
|
||||
}
|
||||
|
@ -251,32 +273,44 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
* @param enabled mark them all as enabled
|
||||
* @param readonly mark them all as readonly
|
||||
*/
|
||||
void addNewPaths(List<File> files, boolean enabled, boolean readonly) {
|
||||
void addNewStatuses(List<File> files, boolean enabled, boolean readonly) {
|
||||
int index = statuses.size();
|
||||
for (File f : files) {
|
||||
BundleStatus status = new BundleStatus(new ResourceFile(f), enabled, readonly);
|
||||
addStatus(status);
|
||||
addStatusNoFire(status);
|
||||
}
|
||||
fireBundlesChanged();
|
||||
fireTableRowsInserted(index, files.size() - 1);
|
||||
}
|
||||
|
||||
private int removeStatusNoFire(BundleStatus status) {
|
||||
if (!status.isReadOnly()) {
|
||||
int i = statuses.indexOf(status);
|
||||
loc2status.remove(getBundleLoc(status));
|
||||
return i;
|
||||
}
|
||||
Msg.showInfo(this, this.provider.getComponent(), "Unabled to remove path",
|
||||
"System path cannot be removed: " + status.getPath().toString());
|
||||
return -1;
|
||||
}
|
||||
|
||||
void removeStatus(BundleStatus status) {
|
||||
int row = removeStatusNoFire(status);
|
||||
if (row >= 0) {
|
||||
fireTableRowsDeleted(row, row);
|
||||
}
|
||||
}
|
||||
|
||||
void remove(int[] modelRows) {
|
||||
List<BundleStatus> toRemove = Arrays.stream(modelRows).mapToObj(statuses::get).collect(
|
||||
Collectors.toUnmodifiableList());
|
||||
removeStatuses(toRemove);
|
||||
}
|
||||
|
||||
void removeStatuses(List<BundleStatus> toRemove) {
|
||||
for (BundleStatus status : toRemove) {
|
||||
if (!status.isReadOnly()) {
|
||||
statuses.remove(status);
|
||||
loc2status.remove(getBundleLoc(status));
|
||||
}
|
||||
else {
|
||||
Msg.showInfo(this, this.provider.getComponent(), "Unabled to remove path",
|
||||
"System path cannot be removed: " + status.getPath().toString());
|
||||
}
|
||||
removeStatusNoFire(status);
|
||||
}
|
||||
fireTableDataChanged();
|
||||
fireBundlesChanged();
|
||||
}
|
||||
|
||||
/***************************************************/
|
||||
|
@ -327,7 +361,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return BundleStatusModel.class.getSimpleName();
|
||||
return BundleStatusTableModel.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -335,31 +369,6 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
return statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* (add and) enable a path
|
||||
* @param file path to enable
|
||||
* @return true if the path is new
|
||||
*/
|
||||
public boolean enablePath(ResourceFile file) {
|
||||
ResourceFile dir = file.isDirectory() ? file : file.getParentFile();
|
||||
for (BundleStatus path : statuses) {
|
||||
if (path.getPath().equals(dir)) {
|
||||
if (!path.isEnabled()) {
|
||||
path.setEnabled(true);
|
||||
fireTableDataChanged();
|
||||
fireBundlesChanged();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
addNewStatus(dir, true, false);
|
||||
Preferences.setProperty(BundleStatusProvider.preferenceForLastSelectedBundle,
|
||||
dir.getAbsolutePath());
|
||||
fireBundlesChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given <code>bundle</code> is managed and not marked readonly
|
||||
* @param bundle the path to test
|
||||
|
@ -385,9 +394,9 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
private ArrayList<BundleStatusListener> bundleStatusListeners = new ArrayList<>();
|
||||
private ArrayList<BundleStatusChangeRequestListener> bundleStatusListeners = new ArrayList<>();
|
||||
|
||||
public void addListener(BundleStatusListener listener) {
|
||||
public void addListener(BundleStatusChangeRequestListener listener) {
|
||||
synchronized (bundleStatusListeners) {
|
||||
if (!bundleStatusListeners.contains(listener)) {
|
||||
bundleStatusListeners.add(listener);
|
||||
|
@ -395,100 +404,28 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
|||
}
|
||||
}
|
||||
|
||||
public void removeListener(BundleStatusListener listener) {
|
||||
public void removeListener(BundleStatusChangeRequestListener listener) {
|
||||
synchronized (bundleStatusListeners) {
|
||||
bundleStatusListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void fireBundlesChanged() {
|
||||
void fireBundleEnablementChangeRequested(BundleStatus path, boolean newValue) {
|
||||
synchronized (bundleStatusListeners) {
|
||||
for (BundleStatusListener listener : bundleStatusListeners) {
|
||||
listener.bundlesChanged();
|
||||
for (BundleStatusChangeRequestListener listener : bundleStatusListeners) {
|
||||
listener.bundleEnablementChangeRequest(path, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fireBundleEnablementChanged(BundleStatus path, boolean newValue) {
|
||||
void fireBundleActivationChangeRequested(BundleStatus path, boolean newValue) {
|
||||
synchronized (bundleStatusListeners) {
|
||||
for (BundleStatusListener listener : bundleStatusListeners) {
|
||||
listener.bundleEnablementChanged(path, newValue);
|
||||
for (BundleStatusChangeRequestListener listener : bundleStatusListeners) {
|
||||
listener.bundleActivationChangeRequest(path, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fireBundleActivationChanged(BundleStatus path, boolean newValue) {
|
||||
synchronized (bundleStatusListeners) {
|
||||
for (BundleStatusListener listener : bundleStatusListeners) {
|
||||
listener.bundleActivationChanged(path, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the statuses from the specified SaveState object.
|
||||
* @param ss the SaveState object
|
||||
*/
|
||||
public void restoreState(SaveState ss) {
|
||||
String[] pathArr = ss.getStrings("BundleStatus_PATH", new String[0]);
|
||||
|
||||
if (pathArr.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean[] enableArr = ss.getBooleans("BundleStatus_ENABLE", new boolean[pathArr.length]);
|
||||
boolean[] readonlyArr = ss.getBooleans("BundleStatus_READ", new boolean[pathArr.length]);
|
||||
|
||||
List<BundleStatus> currentPaths = new ArrayList<>(statuses);
|
||||
statuses.clear();
|
||||
|
||||
for (int i = 0; i < pathArr.length; i++) {
|
||||
BundleStatus currentStatus = getStatus(pathArr[i], currentPaths);
|
||||
if (currentStatus != null) {
|
||||
currentPaths.remove(currentStatus);
|
||||
addStatus(new BundleStatus(pathArr[i], enableArr[i], readonlyArr[i]));
|
||||
}
|
||||
else if (!readonlyArr[i]) {
|
||||
// skip read-only statuses which are not present in the current config
|
||||
// This is needed to thin-out old default entries
|
||||
addStatus(new BundleStatus(pathArr[i], enableArr[i], readonlyArr[i]));
|
||||
}
|
||||
}
|
||||
fireTableDataChanged();
|
||||
fireBundlesChanged();
|
||||
}
|
||||
|
||||
private static BundleStatus getStatus(String filepath, List<BundleStatus> statuses) {
|
||||
for (BundleStatus status : statuses) {
|
||||
if (filepath.equals(status.getPathAsString())) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the statuses to the specified SaveState object.
|
||||
* @param ss the SaveState object
|
||||
*/
|
||||
public void saveState(SaveState ss) {
|
||||
String[] pathArr = new String[statuses.size()];
|
||||
boolean[] enableArr = new boolean[statuses.size()];
|
||||
boolean[] readonlyArr = new boolean[statuses.size()];
|
||||
|
||||
int index = 0;
|
||||
for (BundleStatus status : statuses) {
|
||||
pathArr[index] = status.getPathAsString();
|
||||
enableArr[index] = status.isEnabled();
|
||||
readonlyArr[index] = status.isReadOnly();
|
||||
++index;
|
||||
}
|
||||
|
||||
ss.putStrings("BundleStatus_PATH", pathArr);
|
||||
ss.putBooleans("BundleStatus_ENABLE", enableArr);
|
||||
ss.putBooleans("BundleStatus_READ", readonlyArr);
|
||||
}
|
||||
|
||||
public List<BundleStatus> getRowObjects(int[] modelRowIndices) {
|
||||
List<BundleStatus> rows = new ArrayList<>(modelRowIndices.length);
|
||||
for (int i : modelRowIndices) {
|
|
@ -359,7 +359,7 @@ public class GhidraSourceBundle extends GhidraBundle {
|
|||
int failing = getFailingSourcesCount();
|
||||
int newSourcecount = getNewSourcesCount();
|
||||
|
||||
long lastBundleActivation = 0; // XXX record last bundle activation in bundlestatusmodel
|
||||
long lastBundleActivation = 0; // XXX record last bundle activation in bundlehost
|
||||
if (failing > 0 && (lastBundleActivation > getLastCompileAttempt())) {
|
||||
needsCompile = true;
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package ghidra.app.plugin.core.osgi;
|
||||
|
||||
import org.osgi.framework.Bundle;
|
||||
|
||||
/**
|
||||
* Listener for OSGi framework events.
|
||||
*/
|
||||
public interface OSGiListener {
|
||||
|
||||
default void bundleBuilt(GhidraBundle gb) {
|
||||
//
|
||||
}
|
||||
|
||||
default void bundleActivationChange(Bundle b, boolean newActivation) {
|
||||
//
|
||||
}
|
||||
}
|
|
@ -20,6 +20,8 @@ import java.awt.datatransfer.Transferable;
|
|||
import java.awt.dnd.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
import docking.dnd.*;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
|
@ -33,9 +35,10 @@ public class DraggableScriptTable extends GhidraTable implements Draggable {
|
|||
/**
|
||||
* Constructs a new DraggableGhidraTable.
|
||||
* @param provider the provider, from which getTableModel and getScriptAt are used
|
||||
* @param model provider's table model
|
||||
*/
|
||||
public DraggableScriptTable(GhidraScriptComponentProvider provider) {
|
||||
super(provider.getTableModel());
|
||||
public DraggableScriptTable(GhidraScriptComponentProvider provider, TableModel model) {
|
||||
super(model);
|
||||
this.provider = provider;
|
||||
|
||||
initDragNDrop();
|
||||
|
@ -96,6 +99,7 @@ public class DraggableScriptTable extends GhidraTable implements Draggable {
|
|||
*/
|
||||
@Override
|
||||
public void move() {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,6 +108,7 @@ public class DraggableScriptTable extends GhidraTable implements Draggable {
|
|||
*/
|
||||
@Override
|
||||
public void dragCanceled(DragSourceDropEvent event) {
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class GhidraScriptActionManager {
|
|||
private GhidraScriptComponentProvider provider;
|
||||
private GhidraScriptMgrPlugin plugin;
|
||||
private DockingAction refreshAction;
|
||||
private DockingAction bundlePathsAction;
|
||||
private DockingAction bundleStatusAction;
|
||||
private DockingAction newAction;
|
||||
private DockingAction runAction;
|
||||
private DockingAction runLastAction;
|
||||
|
@ -351,7 +351,7 @@ class GhidraScriptActionManager {
|
|||
refreshAction.setEnabled(true);
|
||||
plugin.getTool().addLocalAction(provider, refreshAction);
|
||||
|
||||
bundlePathsAction = new DockingAction("Bundle Status", plugin.getName()) {
|
||||
bundleStatusAction = new DockingAction("Bundle Status", plugin.getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
provider.showBundleStatusComponent();
|
||||
|
@ -363,14 +363,14 @@ class GhidraScriptActionManager {
|
|||
return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile);
|
||||
}
|
||||
};
|
||||
bundlePathsAction.setPopupMenuData(new MenuData(new String[] { "Bundle Status" },
|
||||
bundleStatusAction.setPopupMenuData(new MenuData(new String[] { "Bundle Status" },
|
||||
ResourceManager.loadImage("images/text_list_bullets.png"), null));
|
||||
bundlePathsAction.setToolBarData(
|
||||
bundleStatusAction.setToolBarData(
|
||||
new ToolBarData(ResourceManager.loadImage("images/text_list_bullets.png"), null));
|
||||
|
||||
bundlePathsAction.setDescription("Bundle Status");
|
||||
bundlePathsAction.setEnabled(true);
|
||||
plugin.getTool().addLocalAction(provider, bundlePathsAction);
|
||||
bundleStatusAction.setDescription("Bundle Status");
|
||||
bundleStatusAction.setEnabled(true);
|
||||
plugin.getTool().addLocalAction(provider, bundleStatusAction);
|
||||
|
||||
helpAction = new DockingAction("Ghidra API Help", plugin.getName()) {
|
||||
@Override
|
||||
|
@ -439,7 +439,7 @@ class GhidraScriptActionManager {
|
|||
}
|
||||
|
||||
HelpLocation getPathHelpLocation() {
|
||||
return new HelpLocation(plugin.getName(), bundlePathsAction.getName());
|
||||
return new HelpLocation(plugin.getName(), bundleStatusAction.getName());
|
||||
}
|
||||
|
||||
HelpLocation getKeyBindingHelpLocation() {
|
||||
|
|
|
@ -23,7 +23,8 @@ import java.util.*;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.*;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import javax.swing.text.html.HTMLEditorKit;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
|
@ -71,7 +72,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
private GTree scriptCategoryTree;
|
||||
private DraggableScriptTable scriptTable;
|
||||
private GhidraScriptTableModel tableModel;
|
||||
private BundleStatusProvider bundleStatusProvider;
|
||||
private BundleStatusComponentProvider bundleStatusComponentProvider;
|
||||
private TaskListener taskListener = new ScriptTaskListener();
|
||||
private GhidraScriptActionManager actionManager;
|
||||
private GhidraTableFilterPanel<ResourceFile> tableFilterPanel;
|
||||
|
@ -97,28 +98,199 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
};
|
||||
|
||||
final private BundleHost bundleHost;
|
||||
final private RefreshingBundleHostListener refreshingBundleHostListener =
|
||||
new RefreshingBundleHostListener();
|
||||
|
||||
GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin) {
|
||||
super(plugin.getTool(), "Script Manager", plugin.getName());
|
||||
|
||||
this.plugin = plugin;
|
||||
|
||||
bundleHost = new BundleHost();
|
||||
GhidraScriptUtil.initialize(bundleHost);
|
||||
|
||||
// first, let the status component register its bundle host listener
|
||||
bundleStatusComponentProvider =
|
||||
new BundleStatusComponentProvider(plugin.getTool(), plugin.getName(), bundleHost);
|
||||
// now add bundles
|
||||
bundleHost.addGhidraBundle(GhidraScriptUtil.getUserScriptDirectory(), true, false);
|
||||
bundleHost.addGhidraBundles(GhidraScriptUtil.getSystemScriptPaths(), true, true);
|
||||
// finally, add the GUI listener for this component
|
||||
bundleHost.addListener(refreshingBundleHostListener);
|
||||
|
||||
setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName()));
|
||||
setIcon(ResourceManager.loadImage("images/play.png"));
|
||||
addToToolbar();
|
||||
setWindowGroup(WINDOW_GROUP);
|
||||
|
||||
build();
|
||||
|
||||
plugin.getTool().addComponentProvider(this, false);
|
||||
actionManager = new GhidraScriptActionManager(this, plugin);
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
private void build() {
|
||||
scriptRoot = new RootNode();
|
||||
|
||||
scriptCategoryTree = new GTree(scriptRoot);
|
||||
scriptCategoryTree.setName("CATEGORY_TREE");
|
||||
scriptCategoryTree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
maybeSelect(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
maybeSelect(e);
|
||||
}
|
||||
|
||||
private void maybeSelect(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
TreePath path = scriptCategoryTree.getPathForLocation(e.getX(), e.getY());
|
||||
scriptCategoryTree.setSelectionPath(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
scriptCategoryTree.addGTreeSelectionListener(e -> {
|
||||
tableModel.fireTableDataChanged(); // trigger a refilter
|
||||
TreePath path = e.getPath();
|
||||
if (path != null) {
|
||||
scriptCategoryTree.expandPath(path);
|
||||
}
|
||||
});
|
||||
|
||||
scriptCategoryTree.getSelectionModel().setSelectionMode(
|
||||
TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
|
||||
tableModel = new GhidraScriptTableModel(this);
|
||||
|
||||
scriptTable = new DraggableScriptTable(this, tableModel);
|
||||
scriptTable.setName("SCRIPT_TABLE");
|
||||
scriptTable.setAutoLookupColumn(tableModel.getNameColumnIndex());
|
||||
scriptTable.setRowSelectionAllowed(true);
|
||||
scriptTable.setAutoCreateColumnsFromModel(false);
|
||||
scriptTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
scriptTable.getSelectionModel().addListSelectionListener(e -> {
|
||||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
tool.contextChanged(GhidraScriptComponentProvider.this);
|
||||
updateDescriptionPanel();
|
||||
});
|
||||
tableModel.addTableModelListener(e -> updateTitle());
|
||||
|
||||
scriptTable.addMouseListener(new GMouseListenerAdapter() {
|
||||
@Override
|
||||
public void popupTriggered(MouseEvent e) {
|
||||
int displayRow = scriptTable.rowAtPoint(e.getPoint());
|
||||
if (displayRow >= 0) {
|
||||
scriptTable.setRowSelectionInterval(displayRow, displayRow);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doubleClickTriggered(MouseEvent e) {
|
||||
runScript();
|
||||
}
|
||||
});
|
||||
|
||||
TableColumnModel columnModel = scriptTable.getColumnModel();
|
||||
// Set default column sizes
|
||||
for (int i = 0; i < columnModel.getColumnCount(); i++) {
|
||||
TableColumn column = columnModel.getColumn(i);
|
||||
String name = (String) column.getHeaderValue();
|
||||
switch (name) {
|
||||
case GhidraScriptTableModel.SCRIPT_ACTION_COLUMN_NAME:
|
||||
initializeUnresizableColumn(column, 50);
|
||||
break;
|
||||
case GhidraScriptTableModel.SCRIPT_STATUS_COLUMN_NAME:
|
||||
initializeUnresizableColumn(column, 50);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JScrollPane scriptTableScroll = new JScrollPane(scriptTable);
|
||||
buildFilter();
|
||||
|
||||
JPanel tablePanel = new JPanel(new BorderLayout());
|
||||
tablePanel.add(scriptTableScroll, BorderLayout.CENTER);
|
||||
tablePanel.add(tableFilterPanel, BorderLayout.SOUTH);
|
||||
|
||||
JSplitPane treeTableSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
treeTableSplit.setLeftComponent(scriptCategoryTree);
|
||||
treeTableSplit.setRightComponent(tablePanel);
|
||||
treeTableSplit.setDividerLocation(150);
|
||||
|
||||
JComponent descriptionPanel = buildDescriptionComponent();
|
||||
|
||||
dataDescriptionSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
dataDescriptionSplit.setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT);
|
||||
dataDescriptionSplit.setName("dataDescriptionSplit");
|
||||
dataDescriptionSplit.setTopComponent(treeTableSplit);
|
||||
dataDescriptionSplit.setBottomComponent(descriptionPanel);
|
||||
|
||||
component = new JPanel(new BorderLayout());
|
||||
component.add(dataDescriptionSplit, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
public void readConfigState(SaveState saveState) {
|
||||
bundleHost.restoreState(saveState);
|
||||
|
||||
actionManager.restoreUserDefinedKeybindings(saveState);
|
||||
actionManager.restoreScriptsThatAreInTool(saveState);
|
||||
|
||||
final int descriptionDividerLocation = saveState.getInt(DESCRIPTION_DIVIDER_LOCATION, 0);
|
||||
if (descriptionDividerLocation > 0) {
|
||||
|
||||
ComponentListener listener = new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
dataDescriptionSplit.setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT); // give the top pane the most space
|
||||
}
|
||||
};
|
||||
component.addComponentListener(listener);
|
||||
|
||||
dataDescriptionSplit.setDividerLocation(descriptionDividerLocation);
|
||||
}
|
||||
|
||||
String filterText = saveState.getString(FILTER_TEXT, "");
|
||||
tableFilterPanel.setFilterText(filterText);
|
||||
}
|
||||
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
bundleHost.saveState(saveState);
|
||||
|
||||
actionManager.saveUserDefinedKeybindings(saveState);
|
||||
actionManager.saveScriptsThatAreInTool(saveState);
|
||||
|
||||
int dividerLocation = dataDescriptionSplit.getDividerLocation();
|
||||
if (dividerLocation > 0) {
|
||||
saveState.putInt(DESCRIPTION_DIVIDER_LOCATION, dividerLocation);
|
||||
}
|
||||
|
||||
String filterText = tableFilterPanel.getFilterText();
|
||||
saveState.putString(FILTER_TEXT, filterText);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
editorMap.clear();
|
||||
scriptCategoryTree.dispose();
|
||||
scriptCategoryTree.dispose();
|
||||
scriptTable.dispose();
|
||||
tableFilterPanel.dispose();
|
||||
actionManager.dispose();
|
||||
bundleStatusProvider.dispose();
|
||||
bundleStatusComponentProvider.dispose();
|
||||
GhidraScriptUtil.dispose();
|
||||
}
|
||||
|
||||
public BundleHost getBundleHost() {
|
||||
return bundleHost;
|
||||
}
|
||||
|
||||
GhidraScriptActionManager getActionManager() {
|
||||
|
@ -130,14 +302,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
|
||||
void showBundleStatusComponent() {
|
||||
bundleStatusProvider.setVisible(true);
|
||||
}
|
||||
|
||||
private void performRefresh() {
|
||||
GhidraScriptUtil.getBundleHost().setBundlePaths(
|
||||
bundleStatusProvider.getModel().getEnabledPaths());
|
||||
GhidraScriptUtil.clearMetadata();
|
||||
refresh();
|
||||
bundleStatusComponentProvider.setVisible(true);
|
||||
}
|
||||
|
||||
void assignKeyBinding() {
|
||||
|
@ -161,7 +326,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
void renameScript() {
|
||||
ResourceFile script = getSelectedScript();
|
||||
ResourceFile directory = script.getParentFile();
|
||||
if (!bundleStatusProvider.getModel().isWriteable(directory)) {
|
||||
if (!bundleStatusComponentProvider.getModel().isWriteable(directory)) {
|
||||
Msg.showWarn(getClass(), getComponent(), getName(),
|
||||
"Unable to rename scripts in '" + directory + "'.");
|
||||
return;
|
||||
|
@ -272,10 +437,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
TableModel getTableModel() {
|
||||
return tableModel;
|
||||
}
|
||||
|
||||
JTable getTable() {
|
||||
return scriptTable;
|
||||
}
|
||||
|
@ -284,17 +445,17 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
return tableFilterPanel.getViewRow(tableModel.getScriptIndex(scriptFile));
|
||||
}
|
||||
|
||||
ResourceFile getScriptAt(int rowIndex) {
|
||||
return tableModel.getScriptAt(tableFilterPanel.getModelRow(rowIndex));
|
||||
ResourceFile getScriptAt(int viewRowIndex) {
|
||||
return tableModel.getScriptAt(tableFilterPanel.getModelRow(viewRowIndex));
|
||||
}
|
||||
|
||||
public List<ResourceFile> getScriptDirectories() {
|
||||
return bundleStatusProvider.getModel().getEnabledPaths().stream().filter(
|
||||
return bundleStatusComponentProvider.getModel().getEnabledPaths().stream().filter(
|
||||
ResourceFile::isDirectory).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<ResourceFile> getWritableScriptDirectories() {
|
||||
BundleStatusModel m = bundleStatusProvider.getModel();
|
||||
BundleStatusTableModel m = bundleStatusComponentProvider.getModel();
|
||||
return m.getEnabledPaths().stream().filter(ResourceFile::isDirectory).filter(
|
||||
m::isWriteable).collect(Collectors.toList());
|
||||
}
|
||||
|
@ -311,7 +472,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
ResourceFile directory = script.getParentFile();
|
||||
|
||||
if (!bundleStatusProvider.getModel().isWriteable(directory)) {
|
||||
if (!bundleStatusComponentProvider.getModel().isWriteable(directory)) {
|
||||
Msg.showWarn(getClass(), getComponent(), getName(),
|
||||
"Unable to delete scripts in '" + directory + "'.");
|
||||
return;
|
||||
|
@ -353,11 +514,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
|
||||
public void enableScriptDirectory(ResourceFile scriptDir) {
|
||||
if (bundleStatusProvider.getModel().enablePath(scriptDir)) {
|
||||
Msg.showInfo(this, getComponent(), "Script Path Added/Enabled",
|
||||
"The directory has been automatically enabled for use:\n" +
|
||||
scriptDir.getAbsolutePath());
|
||||
}
|
||||
bundleHost.enablePath(scriptDir);
|
||||
Msg.showInfo(this, getComponent(), "Script Path Added/Enabled",
|
||||
"The directory has been automatically enabled for use:\n" +
|
||||
scriptDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
void newScript() {
|
||||
|
@ -410,12 +570,13 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
|
||||
void runScript(String scriptName, TaskListener listener) {
|
||||
List<ResourceFile> dirPaths = bundleStatusProvider.getModel().getEnabledPaths();
|
||||
for (ResourceFile dir : dirPaths) {
|
||||
ResourceFile scriptSource = new ResourceFile(dir, scriptName);
|
||||
if (scriptSource.exists()) {
|
||||
runScript(scriptSource, listener);
|
||||
return;
|
||||
for (ResourceFile dir : bundleHost.getBundlePaths()) {
|
||||
if (dir.isDirectory()) {
|
||||
ResourceFile scriptSource = new ResourceFile(dir, scriptName);
|
||||
if (scriptSource.exists()) {
|
||||
runScript(scriptSource, listener);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Script does not exist: " + scriptName);
|
||||
|
@ -530,6 +691,60 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
return categoryPath;
|
||||
}
|
||||
|
||||
class RefreshingBundleHostListener implements BundleHostListener {
|
||||
|
||||
@Override
|
||||
public void bundleBuilt(GhidraBundle sb) {
|
||||
if (sb instanceof GhidraSourceBundle) {
|
||||
GhidraSourceBundle gsb = (GhidraSourceBundle) sb;
|
||||
for (ResourceFile sf : gsb.getNewSources()) {
|
||||
if (GhidraScriptUtil.containsMetadata(sf)) {
|
||||
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(sf);
|
||||
BuildFailure e = gsb.getErrors(sf);
|
||||
info.setCompileErrors(e != null);
|
||||
}
|
||||
}
|
||||
tableModel.fireTableDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundleEnablementChange(GhidraBundle gbundle, boolean newEnablment) {
|
||||
if (gbundle instanceof GhidraSourceBundle) {
|
||||
performRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundleAdded(GhidraBundle gbundle) {
|
||||
plugin.getTool().setConfigChanged(true);
|
||||
performRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundlesAdded(Collection<GhidraBundle> gbundles) {
|
||||
plugin.getTool().setConfigChanged(true);
|
||||
performRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundleRemoved(GhidraBundle gbundle) {
|
||||
plugin.getTool().setConfigChanged(true);
|
||||
performRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundlesRemoved(Collection<GhidraBundle> gbundles) {
|
||||
plugin.getTool().setConfigChanged(true);
|
||||
performRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void performRefresh() {
|
||||
GhidraScriptUtil.clearMetadata();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
hasBeenRefreshed = true;
|
||||
|
||||
|
@ -753,147 +968,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
return true;
|
||||
}
|
||||
|
||||
private void build() {
|
||||
bundleStatusProvider = new BundleStatusProvider(plugin.getTool(), plugin.getName());
|
||||
|
||||
// XXX remember listener to remove later
|
||||
bundleStatusProvider.getModel().addListener(new BundleStatusListener() {
|
||||
@Override
|
||||
public void bundlesChanged() {
|
||||
plugin.getTool().setConfigChanged(true);
|
||||
performRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bundleEnablementChanged(BundleStatus status, boolean enabled) {
|
||||
if (status.isDirectory()) {
|
||||
performRefresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BundleHost.getInstance().addListener(new OSGiListener() {
|
||||
|
||||
@Override
|
||||
public void bundleBuilt(GhidraBundle sb) {
|
||||
if (sb instanceof GhidraSourceBundle) {
|
||||
GhidraSourceBundle gsb = (GhidraSourceBundle) sb;
|
||||
for (ResourceFile sf : gsb.getNewSources()) {
|
||||
if (GhidraScriptUtil.containsMetadata(sf)) {
|
||||
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(sf);
|
||||
BuildFailure e = gsb.getErrors(sf);
|
||||
info.setCompileErrors(e != null);
|
||||
}
|
||||
}
|
||||
tableModel.fireTableDataChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
scriptRoot = new RootNode();
|
||||
|
||||
scriptCategoryTree = new GTree(scriptRoot);
|
||||
scriptCategoryTree.setName("CATEGORY_TREE");
|
||||
scriptCategoryTree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
maybeSelect(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
maybeSelect(e);
|
||||
}
|
||||
|
||||
private void maybeSelect(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
TreePath path = scriptCategoryTree.getPathForLocation(e.getX(), e.getY());
|
||||
scriptCategoryTree.setSelectionPath(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
scriptCategoryTree.addGTreeSelectionListener(e -> {
|
||||
tableModel.fireTableDataChanged(); // trigger a refilter
|
||||
TreePath path = e.getPath();
|
||||
if (path != null) {
|
||||
scriptCategoryTree.expandPath(path);
|
||||
}
|
||||
});
|
||||
|
||||
scriptCategoryTree.getSelectionModel().setSelectionMode(
|
||||
TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
|
||||
tableModel = new GhidraScriptTableModel(this);
|
||||
|
||||
scriptTable = new DraggableScriptTable(this);
|
||||
scriptTable.setName("SCRIPT_TABLE");
|
||||
scriptTable.setAutoLookupColumn(tableModel.getNameColumnIndex());
|
||||
scriptTable.setRowSelectionAllowed(true);
|
||||
scriptTable.setAutoCreateColumnsFromModel(false);
|
||||
scriptTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
scriptTable.getSelectionModel().addListSelectionListener(e -> {
|
||||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
tool.contextChanged(GhidraScriptComponentProvider.this);
|
||||
updateDescriptionPanel();
|
||||
});
|
||||
tableModel.addTableModelListener(e -> updateTitle());
|
||||
|
||||
scriptTable.addMouseListener(new GMouseListenerAdapter() {
|
||||
@Override
|
||||
public void popupTriggered(MouseEvent e) {
|
||||
int displayRow = scriptTable.rowAtPoint(e.getPoint());
|
||||
if (displayRow >= 0) {
|
||||
scriptTable.setRowSelectionInterval(displayRow, displayRow);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doubleClickTriggered(MouseEvent e) {
|
||||
runScript();
|
||||
}
|
||||
});
|
||||
|
||||
TableColumnModel columnModel = scriptTable.getColumnModel();
|
||||
// Set default column sizes
|
||||
for (int i = 0; i < columnModel.getColumnCount(); i++) {
|
||||
TableColumn column = columnModel.getColumn(i);
|
||||
String name = (String) column.getHeaderValue();
|
||||
switch (name) {
|
||||
case GhidraScriptTableModel.SCRIPT_ACTION_COLUMN_NAME:
|
||||
initializeUnresizableColumn(column, 50);
|
||||
break;
|
||||
case GhidraScriptTableModel.SCRIPT_STATUS_COLUMN_NAME:
|
||||
initializeUnresizableColumn(column, 50);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JScrollPane scriptTableScroll = new JScrollPane(scriptTable);
|
||||
buildFilter();
|
||||
|
||||
JPanel tablePanel = new JPanel(new BorderLayout());
|
||||
tablePanel.add(scriptTableScroll, BorderLayout.CENTER);
|
||||
tablePanel.add(tableFilterPanel, BorderLayout.SOUTH);
|
||||
|
||||
JSplitPane treeTableSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
treeTableSplit.setLeftComponent(scriptCategoryTree);
|
||||
treeTableSplit.setRightComponent(tablePanel);
|
||||
treeTableSplit.setDividerLocation(150);
|
||||
|
||||
JComponent descriptionPanel = buildDescriptionComponent();
|
||||
|
||||
dataDescriptionSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
dataDescriptionSplit.setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT);
|
||||
dataDescriptionSplit.setName("dataDescriptionSplit");
|
||||
dataDescriptionSplit.setTopComponent(treeTableSplit);
|
||||
dataDescriptionSplit.setBottomComponent(descriptionPanel);
|
||||
|
||||
component = new JPanel(new BorderLayout());
|
||||
component.add(dataDescriptionSplit, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private void initializeUnresizableColumn(TableColumn column, int width) {
|
||||
column.setPreferredWidth(width);
|
||||
column.setMinWidth(width);
|
||||
|
@ -1058,47 +1132,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
public void readConfigState(SaveState saveState) {
|
||||
bundleStatusProvider.getModel().restoreState(saveState);
|
||||
|
||||
// pull in the just-loaded paths
|
||||
List<ResourceFile> paths = bundleStatusProvider.getModel().getEnabledPaths();
|
||||
GhidraScriptUtil.initializeScriptBundlePaths(paths);
|
||||
actionManager.restoreUserDefinedKeybindings(saveState);
|
||||
actionManager.restoreScriptsThatAreInTool(saveState);
|
||||
|
||||
final int descriptionDividerLocation = saveState.getInt(DESCRIPTION_DIVIDER_LOCATION, 0);
|
||||
if (descriptionDividerLocation > 0) {
|
||||
|
||||
ComponentListener listener = new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
dataDescriptionSplit.setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT); // give the top pane the most space
|
||||
}
|
||||
};
|
||||
component.addComponentListener(listener);
|
||||
|
||||
dataDescriptionSplit.setDividerLocation(descriptionDividerLocation);
|
||||
}
|
||||
|
||||
String filterText = saveState.getString(FILTER_TEXT, "");
|
||||
tableFilterPanel.setFilterText(filterText);
|
||||
}
|
||||
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
bundleStatusProvider.getModel().saveState(saveState);
|
||||
actionManager.saveUserDefinedKeybindings(saveState);
|
||||
actionManager.saveScriptsThatAreInTool(saveState);
|
||||
|
||||
int dividerLocation = dataDescriptionSplit.getDividerLocation();
|
||||
if (dividerLocation > 0) {
|
||||
saveState.putInt(DESCRIPTION_DIVIDER_LOCATION, dividerLocation);
|
||||
}
|
||||
|
||||
String filterText = tableFilterPanel.getFilterText();
|
||||
saveState.putString(FILTER_TEXT, filterText);
|
||||
}
|
||||
|
||||
/********************************************************************/
|
||||
|
||||
@Override
|
||||
|
@ -1150,10 +1183,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
/** Table filter that uses the state of the tree to further filter */
|
||||
private class ScriptTableSecondaryFilter implements TableFilter<ResourceFile> {
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.plugin.core.osgi.OSGiException;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -47,18 +48,51 @@ public class GhidraScriptUtil {
|
|||
|
||||
private static Map<String, List<ResourceFile>> scriptNameToFilesMap = new HashMap<>();
|
||||
|
||||
static BundleHost _bundleHost = BundleHost.getInstance();
|
||||
static BundleHost _bundleHost;
|
||||
|
||||
public static BundleHost getBundleHost() {
|
||||
return _bundleHost;
|
||||
}
|
||||
|
||||
public static void initialize(BundleHost bundleHost) {
|
||||
if (_bundleHost != null) {
|
||||
throw new RuntimeException("GhidraScriptUtil initialized multiple times!");
|
||||
}
|
||||
|
||||
try {
|
||||
_bundleHost = bundleHost;
|
||||
_bundleHost.startFelix();
|
||||
}
|
||||
catch (OSGiException | IOException e) {
|
||||
e.printStackTrace();
|
||||
Msg.error(GhidraScript.class, "failed to initialize BundleHost", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the script bundle paths
|
||||
* @param newPaths the new script bundle paths
|
||||
* initialization for headless runs.
|
||||
*
|
||||
* @param bundleHost the host to use
|
||||
* @param paths bundle paths added as system bundles
|
||||
*
|
||||
*/
|
||||
public static void initializeScriptBundlePaths(List<ResourceFile> newPaths) {
|
||||
_bundleHost.setBundlePaths(new ArrayList<>(newPaths));
|
||||
public static void initialize(BundleHost bundleHost, List<ResourceFile> paths) {
|
||||
initialize(bundleHost);
|
||||
_bundleHost.addGhidraBundles(paths, true, true);
|
||||
}
|
||||
|
||||
public static void dispose() {
|
||||
clearMetadata();
|
||||
_bundleHost = null;
|
||||
providers = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear ScriptInfo metadata cached by GhidraScriptUtil
|
||||
*/
|
||||
public static void clearMetadata() {
|
||||
scriptFileToInfoMap.clear();
|
||||
scriptNameToFilesMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,14 +235,6 @@ public class GhidraScriptUtil {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clear ScriptInfo metadata cached by GhidraScriptUtil
|
||||
*/
|
||||
public static void clearMetadata() {
|
||||
scriptFileToInfoMap.clear(); // clear our cache of old files
|
||||
scriptNameToFilesMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base name give a script file.
|
||||
* For example, given "C:\Temp\SomeClass.java",
|
||||
|
@ -437,7 +463,7 @@ public class GhidraScriptUtil {
|
|||
}
|
||||
|
||||
/** Returns true if the given filename exists in any of the given directories */
|
||||
private static ResourceFile findScriptFileInPaths(List<ResourceFile> scriptDirectories,
|
||||
private static ResourceFile findScriptFileInPaths(Collection<ResourceFile> scriptDirectories,
|
||||
String filename) {
|
||||
|
||||
String validatedName = fixupName(filename);
|
||||
|
|
|
@ -35,7 +35,7 @@ public class JavaScriptProvider extends GhidraScriptProvider {
|
|||
if (sourceDir == null) {
|
||||
return null;
|
||||
}
|
||||
return (GhidraSourceBundle) _bundleHost.getGhidraBundle(sourceDir);
|
||||
return (GhidraSourceBundle) _bundleHost.getExistingGhidraBundle(sourceDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -67,6 +67,7 @@ public class JavaScriptProvider extends GhidraScriptProvider {
|
|||
@Override
|
||||
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
|
||||
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
|
||||
// in headless operation, ScriptInfo objects can be created here
|
||||
ScriptInfo info = GhidraScriptUtil.getScriptInfo(sourceFile);
|
||||
try {
|
||||
Class<?> clazz = loadClass(sourceFile, writer);
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
|||
import generic.jar.ResourceFile;
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraLaunchable;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.script.*;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.HeadlessGhidraApplicationConfiguration;
|
||||
|
@ -204,7 +205,7 @@ public class GhidraScriptRunner implements GhidraLaunchable {
|
|||
paths.addAll(GhidraScriptUtil.getSystemScriptPaths());
|
||||
paths.add(0, GhidraScriptUtil.getUserScriptDirectory());
|
||||
}
|
||||
GhidraScriptUtil.initializeScriptBundlePaths(paths);
|
||||
GhidraScriptUtil.initialize(new BundleHost(), paths);
|
||||
|
||||
StringBuffer buf = new StringBuffer("HEADLESS Script Paths:");
|
||||
for (ResourceFile dir : GhidraScriptUtil.getScriptSourceDirectories()) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import generic.util.Path;
|
|||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraJarApplicationLayout;
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.script.*;
|
||||
import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption;
|
||||
import ghidra.app.util.importer.AutoImporter;
|
||||
|
@ -673,7 +674,7 @@ public class HeadlessAnalyzer {
|
|||
paths.addAll(GhidraScriptUtil.getSystemScriptPaths());
|
||||
paths.add(0, GhidraScriptUtil.getUserScriptDirectory());
|
||||
}
|
||||
GhidraScriptUtil.initializeScriptBundlePaths(paths);
|
||||
GhidraScriptUtil.initialize(new BundleHost(), paths);
|
||||
|
||||
StringBuffer buf = new StringBuffer("HEADLESS Script Paths:");
|
||||
for (ResourceFile dir : GhidraScriptUtil.getScriptSourceDirectories()) {
|
||||
|
|
|
@ -551,6 +551,12 @@ public class TestEnv {
|
|||
}
|
||||
|
||||
public ScriptTaskListener runScript(File script) throws PluginException {
|
||||
GhidraScriptMgrPlugin sm = getPlugin(GhidraScriptMgrPlugin.class);
|
||||
if (sm == null) {
|
||||
lazyTool().addPlugin(GhidraScriptMgrPlugin.class.getName());
|
||||
sm = getPlugin(GhidraScriptMgrPlugin.class);
|
||||
}
|
||||
|
||||
JavaScriptProvider scriptProvider = new JavaScriptProvider();
|
||||
PrintWriter writer = new PrintWriter(System.out);
|
||||
ResourceFile resourceFile = new ResourceFile(script);
|
||||
|
@ -567,11 +573,6 @@ public class TestEnv {
|
|||
throw new RuntimeException("Failed to compile script " + script.getAbsolutePath());
|
||||
}
|
||||
|
||||
GhidraScriptMgrPlugin sm = getPlugin(GhidraScriptMgrPlugin.class);
|
||||
if (sm == null) {
|
||||
lazyTool().addPlugin(GhidraScriptMgrPlugin.class.getName());
|
||||
sm = getPlugin(GhidraScriptMgrPlugin.class);
|
||||
}
|
||||
|
||||
String scriptName = script.getName();
|
||||
ScriptTaskListener listener = new ScriptTaskListener(scriptName);
|
||||
|
|
|
@ -47,7 +47,8 @@ import generic.jar.ResourceFile;
|
|||
import generic.test.TestUtils;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.console.ConsoleComponentProvider;
|
||||
import ghidra.app.plugin.core.osgi.*;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.plugin.core.osgi.BundleStatusComponentProvider;
|
||||
import ghidra.app.script.*;
|
||||
import ghidra.app.services.ConsoleService;
|
||||
import ghidra.framework.Application;
|
||||
|
@ -86,6 +87,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
|||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
System.err.printf("===== Starting %s ======\n", testName.getMethodName());
|
||||
|
||||
setErrorGUIEnabled(false);
|
||||
|
||||
// change the eclipse port so that Eclipse doesn't try to edit the script when
|
||||
|
@ -130,7 +133,6 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
|||
|
||||
waitForSwing();
|
||||
|
||||
System.err.printf("===== Starting %s ======\n", testName.getMethodName());
|
||||
}
|
||||
|
||||
protected Program buildProgram() throws Exception {
|
||||
|
@ -167,8 +169,6 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
|||
}
|
||||
wipeUserScripts();
|
||||
|
||||
BundleHost.getInstance().removeAllListeners();
|
||||
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
|
@ -632,12 +632,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
|||
}
|
||||
|
||||
protected void addScriptDirectory(ResourceFile scriptDir) {
|
||||
provider.enableScriptDirectory(scriptDir);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ResourceFile> scriptBundlePaths =
|
||||
(List<ResourceFile>) getInstanceField("bundlePaths", GhidraScriptUtil.getBundleHost());
|
||||
scriptBundlePaths.add(scriptDir);
|
||||
// when there is a provider, GhidraScriptUtil has the same BundleHost
|
||||
provider.getBundleHost().enablePath(scriptDir);
|
||||
}
|
||||
|
||||
protected String runScript(String scriptName) throws Exception {
|
||||
|
@ -985,46 +981,6 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* rip out GUI listeners from bundle and OSGI components
|
||||
* use as follows:
|
||||
*
|
||||
* <pre>{@code
|
||||
* try (var rewire = unwireGUI()) {
|
||||
* // ... test code ...
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @return an undo closure which puts the listeners back in place
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected AutoCloseable unwireGUI() {
|
||||
waitForSwing();
|
||||
BundleStatusProvider bundleStatusProvider =
|
||||
(BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider);
|
||||
BundleStatusModel bundleStatusModel = bundleStatusProvider.getModel();
|
||||
|
||||
List<BundleStatusListener> bundleStatusListeners =
|
||||
(List<BundleStatusListener>) TestUtils.getInstanceField("bundleStatusListeners",
|
||||
bundleStatusModel);
|
||||
|
||||
List<BundleStatusListener> storedBundleStatusListeners =
|
||||
new ArrayList<>(bundleStatusListeners);
|
||||
bundleStatusListeners.clear();
|
||||
|
||||
List<OSGiListener> osgiListeners =
|
||||
(List<OSGiListener>) TestUtils.getInstanceField("osgiListeners",
|
||||
BundleHost.getInstance());
|
||||
ArrayList<OSGiListener> storedOsgiListeners = new ArrayList<>(osgiListeners);
|
||||
osgiListeners.clear();
|
||||
|
||||
return () -> {
|
||||
waitForSwing();
|
||||
bundleStatusListeners.addAll(storedBundleStatusListeners);
|
||||
osgiListeners.addAll(storedOsgiListeners);
|
||||
};
|
||||
}
|
||||
|
||||
protected void cleanupOldTestFiles() throws IOException {
|
||||
// remove the compiled bundles directory so that any scripts we use will be recompiled
|
||||
wipe(BundleHost.getCompiledBundlesDir());
|
||||
|
@ -1032,9 +988,9 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
|||
String myTestName = super.testName.getMethodName();
|
||||
|
||||
// destroy any NewScriptxxx files...and Temp ones too
|
||||
BundleStatusProvider bundleStatusProvider =
|
||||
(BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider);
|
||||
List<ResourceFile> paths = bundleStatusProvider.getModel().getEnabledPaths();
|
||||
BundleStatusComponentProvider bundleStatusComponentProvider =
|
||||
(BundleStatusComponentProvider) TestUtils.getInstanceField("bundleStatusComponentProvider", provider);
|
||||
List<ResourceFile> paths = bundleStatusComponentProvider.getModel().getEnabledPaths();
|
||||
|
||||
for (ResourceFile path : paths) {
|
||||
File file = path.getFile(false);
|
||||
|
|
|
@ -61,15 +61,13 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
|||
|
||||
@Test
|
||||
public void testScriptWithInnerClassAndLocalClass() throws Exception {
|
||||
try (var rewire = unwireGUI()) {
|
||||
ResourceFile innerScriptFile = createInnerClassScript();
|
||||
ResourceFile innerScriptFile = createInnerClassScript();
|
||||
|
||||
String output = runScriptAndGetOutput(innerScriptFile);
|
||||
String output = runScriptAndGetOutput(innerScriptFile);
|
||||
|
||||
assertTrue("Inner class output not found", output.indexOf("I am an inner class") != -1);
|
||||
assertTrue("External class output not found",
|
||||
output.indexOf("I am an external class") != -1);
|
||||
}
|
||||
assertTrue("Inner class output not found", output.indexOf("I am an inner class") != -1);
|
||||
assertTrue("External class output not found",
|
||||
output.indexOf("I am an external class") != -1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -78,52 +76,47 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
|||
// Test that if a user uses a parent class other than GhidraScript, that parent
|
||||
// class will get recompiled when it changes.
|
||||
//
|
||||
try (var rewire = unwireGUI()) {
|
||||
|
||||
ResourceFile parentScriptFile = createTempScriptFile("AbstractParentScript");
|
||||
ResourceFile parentScriptFile = createTempScriptFile("AbstractParentScript");
|
||||
|
||||
String v1Message = "Hello from version 1";
|
||||
writeAbstractScriptContents(parentScriptFile, v1Message);
|
||||
String v1Message = "Hello from version 1";
|
||||
writeAbstractScriptContents(parentScriptFile, v1Message);
|
||||
|
||||
ResourceFile childScriptFile = createChildScript(parentScriptFile, null);
|
||||
ResourceFile childScriptFile = createChildScript(parentScriptFile, null);
|
||||
|
||||
String output = runScriptAndGetOutput(childScriptFile);
|
||||
String output = runScriptAndGetOutput(childScriptFile);
|
||||
|
||||
assertContainsText(v1Message, output);
|
||||
assertContainsText(v1Message, output);
|
||||
|
||||
// change the parent script
|
||||
String v2Message = "Hello from version 2";
|
||||
writeAbstractScriptContents(parentScriptFile, v2Message);
|
||||
// change the parent script
|
||||
String v2Message = "Hello from version 2";
|
||||
writeAbstractScriptContents(parentScriptFile, v2Message);
|
||||
|
||||
output = runScriptAndGetOutput(childScriptFile);
|
||||
output = runScriptAndGetOutput(childScriptFile);
|
||||
|
||||
assertContainsText(v2Message, output);
|
||||
}
|
||||
assertContainsText(v2Message, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScriptWithParentInPackageRecompile() throws Exception {
|
||||
try (var rewire = unwireGUI()) {
|
||||
final String parentName = "ParentInPackageScript";
|
||||
final String packageName = parentName + "Pkg";
|
||||
|
||||
final String parentName = "ParentInPackageScript";
|
||||
final String packageName = parentName + "Pkg";
|
||||
ResourceFile parentScriptFile = createTempScriptFile(parentName, packageName);
|
||||
writePackageScriptContents(parentScriptFile, packageName);
|
||||
|
||||
ResourceFile parentScriptFile = createTempScriptFile(parentName, packageName);
|
||||
writePackageScriptContents(parentScriptFile, packageName);
|
||||
ResourceFile childScriptFile = createChildScript(parentScriptFile, packageName);
|
||||
|
||||
ResourceFile childScriptFile = createChildScript(parentScriptFile, packageName);
|
||||
String output = runScriptAndGetOutput(childScriptFile);
|
||||
assertContainsText("0", output);
|
||||
output = runScriptAndGetOutput(childScriptFile);
|
||||
assertContainsText("1", output);
|
||||
|
||||
String output = runScriptAndGetOutput(childScriptFile);
|
||||
assertContainsText("0", output);
|
||||
output = runScriptAndGetOutput(childScriptFile);
|
||||
assertContainsText("1", output);
|
||||
|
||||
// Change the parent script so it recompiles and resets its static state
|
||||
Thread.sleep(1000); // Ensure our file write advances the last modified timestamp
|
||||
writePackageScriptContents(parentScriptFile, packageName);
|
||||
output = runScriptAndGetOutput(childScriptFile);
|
||||
assertContainsText("0", output);
|
||||
}
|
||||
// Change the parent script so it recompiles and resets its static state
|
||||
Thread.sleep(1000); // Ensure our file write advances the last modified timestamp
|
||||
writePackageScriptContents(parentScriptFile, packageName);
|
||||
output = runScriptAndGetOutput(childScriptFile);
|
||||
assertContainsText("0", output);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -132,61 +125,58 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
|||
// Test that compiling a script to the user's script dir will use a bin dir for the
|
||||
// output.
|
||||
//
|
||||
try (var rewire = unwireGUI()) {
|
||||
// create a new dummy script
|
||||
File userScriptsDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||
String rawScriptName = testName.getMethodName();
|
||||
String scriptFilename = rawScriptName + ".java";
|
||||
File newScriptFile = new File(userScriptsDir, scriptFilename);
|
||||
if (newScriptFile.exists()) {
|
||||
assertTrue("Unable to delete script file for testing: " + newScriptFile,
|
||||
newScriptFile.delete());
|
||||
}
|
||||
|
||||
// create a new dummy script
|
||||
File userScriptsDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||
String rawScriptName = testName.getMethodName();
|
||||
String scriptFilename = rawScriptName + ".java";
|
||||
File newScriptFile = new File(userScriptsDir, scriptFilename);
|
||||
if (newScriptFile.exists()) {
|
||||
assertTrue("Unable to delete script file for testing: " + newScriptFile,
|
||||
newScriptFile.delete());
|
||||
}
|
||||
JavaScriptProvider scriptProvider = new JavaScriptProvider();
|
||||
scriptProvider.createNewScript(new ResourceFile(newScriptFile), null);
|
||||
|
||||
JavaScriptProvider scriptProvider = new JavaScriptProvider();
|
||||
scriptProvider.createNewScript(new ResourceFile(newScriptFile), null);
|
||||
// remove all class files from the user script dir (none should ever be there)
|
||||
FileFilter classFileFilter = file -> file.getName().endsWith(".class");
|
||||
File[] userScriptDirFiles = userScriptsDir.listFiles(classFileFilter);
|
||||
for (File file : userScriptDirFiles) {
|
||||
file.delete();
|
||||
}
|
||||
userScriptDirFiles = userScriptsDir.listFiles(classFileFilter);
|
||||
boolean isEmpty = userScriptDirFiles == null || userScriptDirFiles.length == 0;
|
||||
assertTrue("Unable to delete class files from the user scripts directory", isEmpty);
|
||||
|
||||
// remove all class files from the user script dir (none should ever be there)
|
||||
FileFilter classFileFilter = file -> file.getName().endsWith(".class");
|
||||
File[] userScriptDirFiles = userScriptsDir.listFiles(classFileFilter);
|
||||
for (File file : userScriptDirFiles) {
|
||||
// remove all class files from the user script bin dir
|
||||
File userScriptsBinDir =
|
||||
GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
|
||||
File[] userScriptBinDirFiles;
|
||||
if (userScriptsBinDir.exists()) {
|
||||
userScriptBinDirFiles = userScriptsBinDir.listFiles(classFileFilter);
|
||||
for (File file : userScriptBinDirFiles) {
|
||||
file.delete();
|
||||
}
|
||||
userScriptDirFiles = userScriptsDir.listFiles(classFileFilter);
|
||||
boolean isEmpty = userScriptDirFiles == null || userScriptDirFiles.length == 0;
|
||||
assertTrue("Unable to delete class files from the user scripts directory", isEmpty);
|
||||
|
||||
// remove all class files from the user script bin dir
|
||||
File userScriptsBinDir = GhidraSourceBundle.getBindirFromScriptFile(
|
||||
new ResourceFile(newScriptFile)).toFile();
|
||||
File[] userScriptBinDirFiles;
|
||||
if (userScriptsBinDir.exists()) {
|
||||
userScriptBinDirFiles = userScriptsBinDir.listFiles(classFileFilter);
|
||||
for (File file : userScriptBinDirFiles) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
userScriptBinDirFiles = userScriptsDir.listFiles(classFileFilter);
|
||||
isEmpty = userScriptBinDirFiles == null || userScriptBinDirFiles.length == 0;
|
||||
assertTrue("Unable to delete class files from the bin directory", isEmpty);
|
||||
|
||||
// compile the script
|
||||
ScriptTaskListener scriptID = env.runScript(newScriptFile);
|
||||
waitForScriptCompletion(scriptID, 20000);
|
||||
|
||||
// make sure the class file is in the user dir's bin dir
|
||||
assertTrue("bin dir was not created!", userScriptsBinDir.exists());
|
||||
File classFile = new File(userScriptsBinDir, rawScriptName + ".class");
|
||||
assertTrue("Class file was not compiled to the bin dir", classFile.exists());
|
||||
|
||||
// make sure no other class files are in the user script dir
|
||||
userScriptDirFiles = userScriptsDir.listFiles(classFileFilter);
|
||||
isEmpty = userScriptDirFiles == null || userScriptDirFiles.length == 0;
|
||||
assertTrue("Class files were written to the top level script directory", isEmpty);
|
||||
|
||||
newScriptFile.delete();
|
||||
}
|
||||
userScriptBinDirFiles = userScriptsDir.listFiles(classFileFilter);
|
||||
isEmpty = userScriptBinDirFiles == null || userScriptBinDirFiles.length == 0;
|
||||
assertTrue("Unable to delete class files from the bin directory", isEmpty);
|
||||
|
||||
// compile the script
|
||||
ScriptTaskListener scriptID = env.runScript(newScriptFile);
|
||||
waitForScriptCompletion(scriptID, 20000);
|
||||
|
||||
// make sure the class file is in the user dir's bin dir
|
||||
assertTrue("bin dir was not created!", userScriptsBinDir.exists());
|
||||
File classFile = new File(userScriptsBinDir, rawScriptName + ".class");
|
||||
assertTrue("Class file was not compiled to the bin dir", classFile.exists());
|
||||
|
||||
// make sure no other class files are in the user script dir
|
||||
userScriptDirFiles = userScriptsDir.listFiles(classFileFilter);
|
||||
isEmpty = userScriptDirFiles == null || userScriptDirFiles.length == 0;
|
||||
assertTrue("Class files were written to the top level script directory", isEmpty);
|
||||
|
||||
newScriptFile.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -195,24 +185,22 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
|||
// Tests that a system script will not get compiled to the source tree in which it lives,
|
||||
// but will instead get compiled to the user scripts directory
|
||||
//
|
||||
try (var rewire = unwireGUI()) {
|
||||
// find a system script
|
||||
String scriptName = "HelloWorldScript.java";
|
||||
ResourceFile systemScriptFile = findScript(scriptName);
|
||||
// find a system script
|
||||
String scriptName = "HelloWorldScript.java";
|
||||
ResourceFile systemScriptFile = findScript(scriptName);
|
||||
|
||||
// compile the system script
|
||||
ScriptTaskListener scriptID = env.runScript(systemScriptFile.getFile(false));
|
||||
waitForScriptCompletion(scriptID, 20000);
|
||||
// compile the system script
|
||||
ScriptTaskListener scriptID = env.runScript(systemScriptFile.getFile(false));
|
||||
waitForScriptCompletion(scriptID, 20000);
|
||||
|
||||
// verify that the generated class file is placed in the default scripting home/bin
|
||||
File userScriptsBinDir =
|
||||
GhidraSourceBundle.getBindirFromScriptFile(systemScriptFile).toFile();
|
||||
String className = scriptName.replace(".java", ".class");
|
||||
File expectedClassFile = new File(userScriptsBinDir, className);
|
||||
// verify that the generated class file is placed in the default scripting home/bin
|
||||
File userScriptsBinDir =
|
||||
GhidraSourceBundle.getBindirFromScriptFile(systemScriptFile).toFile();
|
||||
String className = scriptName.replace(".java", ".class");
|
||||
File expectedClassFile = new File(userScriptsBinDir, className);
|
||||
|
||||
assertTrue("System script not compiled to the expected directory",
|
||||
expectedClassFile.exists());
|
||||
}
|
||||
assertTrue("System script not compiled to the expected directory",
|
||||
expectedClassFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -221,42 +209,39 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
|||
// Tests that we can create a user-defined scripts directory and that compiling a
|
||||
// script will put the output in the bin directory under the user settings directory.
|
||||
//
|
||||
try (var rewire = unwireGUI()) {
|
||||
// create a user-defined directory
|
||||
File tempDir = new File(AbstractGTest.getTestDirectoryPath());
|
||||
File tempScriptDir = new File(tempDir, "TestScriptDir");
|
||||
FileUtilities.deleteDir(tempScriptDir);
|
||||
tempScriptDir.mkdir();
|
||||
|
||||
// create a user-defined directory
|
||||
File tempDir = new File(AbstractGTest.getTestDirectoryPath());
|
||||
File tempScriptDir = new File(tempDir, "TestScriptDir");
|
||||
FileUtilities.deleteDir(tempScriptDir);
|
||||
tempScriptDir.mkdir();
|
||||
ResourceFile scriptDir = new ResourceFile(tempScriptDir);
|
||||
addScriptDirectory(scriptDir);
|
||||
try {
|
||||
// create a script file in that directory
|
||||
String rawScriptName = testName.getMethodName();
|
||||
String scriptFilename = rawScriptName + ".java";
|
||||
ResourceFile newScriptFile = new ResourceFile(scriptDir, scriptFilename);
|
||||
|
||||
ResourceFile scriptDir = new ResourceFile(tempScriptDir);
|
||||
addScriptDirectory(scriptDir);
|
||||
try {
|
||||
// create a script file in that directory
|
||||
String rawScriptName = testName.getMethodName();
|
||||
String scriptFilename = rawScriptName + ".java";
|
||||
ResourceFile newScriptFile = new ResourceFile(scriptDir, scriptFilename);
|
||||
JavaScriptProvider scriptProvider = new JavaScriptProvider();
|
||||
scriptProvider.createNewScript(newScriptFile, null);
|
||||
|
||||
JavaScriptProvider scriptProvider = new JavaScriptProvider();
|
||||
scriptProvider.createNewScript(newScriptFile, null);
|
||||
// compile the script
|
||||
ScriptTaskListener scriptID = env.runScript(newScriptFile.getFile(false));
|
||||
waitForScriptCompletion(scriptID, 20000);
|
||||
|
||||
// compile the script
|
||||
ScriptTaskListener scriptID = env.runScript(newScriptFile.getFile(false));
|
||||
waitForScriptCompletion(scriptID, 20000);
|
||||
// verify a bin dir was created and that the class file is in it
|
||||
File binDir = GhidraSourceBundle.getBindirFromScriptFile(newScriptFile).toFile();
|
||||
assertTrue("bin output dir not created", binDir.exists());
|
||||
|
||||
// verify a bin dir was created and that the class file is in it
|
||||
File binDir = GhidraSourceBundle.getBindirFromScriptFile(newScriptFile).toFile();
|
||||
assertTrue("bin output dir not created", binDir.exists());
|
||||
File scriptClassFile = new File(binDir, rawScriptName + ".class");
|
||||
assertTrue("Script not compiled to the user-defined script directory",
|
||||
scriptClassFile.exists());
|
||||
|
||||
File scriptClassFile = new File(binDir, rawScriptName + ".class");
|
||||
assertTrue("Script not compiled to the user-defined script directory",
|
||||
scriptClassFile.exists());
|
||||
|
||||
deleteFile(newScriptFile);
|
||||
}
|
||||
finally {
|
||||
deleteFile(scriptDir);
|
||||
}
|
||||
deleteFile(newScriptFile);
|
||||
}
|
||||
finally {
|
||||
deleteFile(scriptDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,7 +402,6 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
|||
@Test
|
||||
public void testSaveAsDoesNotAllowUseOfExistingScriptName() throws Exception {
|
||||
ResourceFile existingScript = createTempScriptFile();
|
||||
System.err.printf("secret script: %s\n", existingScript.toString());
|
||||
refreshProvider();// alert manager to new script
|
||||
|
||||
loadTempScriptIntoEditor();
|
||||
|
@ -485,16 +469,13 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
|||
// Checks for the error where script fields accumulated state because script
|
||||
// instances were reused. Script instances should be recreated for each run.
|
||||
//
|
||||
try (var rewire = unwireGUI()) {
|
||||
ResourceFile script = createInstanceFieldScript();
|
||||
String output = runScriptAndGetOutput(script);
|
||||
assertContainsText("*1*", output);
|
||||
ResourceFile script = createInstanceFieldScript();
|
||||
String output = runScriptAndGetOutput(script);
|
||||
assertContainsText("*1*", output);
|
||||
|
||||
output = runScriptAndGetOutput(script);
|
||||
assertContainsText(
|
||||
"The field of the script still has state--the script was not recreated", "*1*",
|
||||
output);
|
||||
}
|
||||
output = runScriptAndGetOutput(script);
|
||||
assertContainsText("The field of the script still has state--the script was not recreated",
|
||||
"*1*", output);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -503,15 +484,12 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
|||
// If the script is not changed, do not reload, which allows for clients to use
|
||||
// static variables to maintain state.
|
||||
//
|
||||
try (var rewire = unwireGUI()) {
|
||||
ResourceFile script = createStaticFieldScript();
|
||||
String output = runScriptAndGetOutput(script);
|
||||
assertContainsText("*1*", output);
|
||||
ResourceFile script = createStaticFieldScript();
|
||||
String output = runScriptAndGetOutput(script);
|
||||
assertContainsText("*1*", output);
|
||||
|
||||
output = runScriptAndGetOutput(script);
|
||||
assertContainsText(
|
||||
"The field of the script still has state--the script was not recreated", "*2*",
|
||||
output);
|
||||
}
|
||||
output = runScriptAndGetOutput(script);
|
||||
assertContainsText("The field of the script still has state--the script was not recreated",
|
||||
"*2*", output);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import docking.action.DockingActionIf;
|
|||
import docking.widgets.filter.FilterTextField;
|
||||
import docking.widgets.list.ListPanel;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.osgi.BundleStatusProvider;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.app.script.JavaScriptProvider;
|
||||
import ghidra.util.StringUtilities;
|
||||
|
@ -281,15 +280,10 @@ public class GhidraScriptMgrPlugin3Test extends AbstractGhidraScriptMgrPluginTes
|
|||
performAction(bundleStatusAction, false);
|
||||
waitForSwing();
|
||||
|
||||
final BundleStatusProvider bundleStatusProvider =
|
||||
waitForComponentProvider(BundleStatusProvider.class);
|
||||
|
||||
final ResourceFile dir = new ResourceFile(getTestDirectoryPath() + "/test_scripts");
|
||||
dir.getFile(false).mkdirs();
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
bundleStatusProvider.getModel().enablePath(dir);
|
||||
});
|
||||
provider.getBundleHost().enablePath(dir);
|
||||
waitForSwing();
|
||||
|
||||
pressNewButton();
|
||||
|
|
|
@ -27,7 +27,7 @@ import docking.widgets.tree.GTree;
|
|||
import docking.widgets.tree.GTreeNode;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.console.ConsoleComponentProvider;
|
||||
import ghidra.app.plugin.core.osgi.BundleStatusProvider;
|
||||
import ghidra.app.plugin.core.osgi.BundleStatusComponentProvider;
|
||||
import ghidra.app.plugin.core.script.*;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.app.services.ConsoleService;
|
||||
|
@ -115,11 +115,11 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
|
|||
paths.add("$GHIDRA_HOME/Features/Base/ghidra_scripts");
|
||||
paths.add("/User/defined/invalid/directory");
|
||||
|
||||
BundleStatusProvider bundleStatusProvider = showProvider(BundleStatusProvider.class);
|
||||
bundleStatusProvider.getModel().setPathsForTesting(paths);
|
||||
BundleStatusComponentProvider bundleStatusComponentProvider = showProvider(BundleStatusComponentProvider.class);
|
||||
bundleStatusComponentProvider.getModel().setPathsForTesting(paths);
|
||||
|
||||
waitForComponentProvider(BundleStatusProvider.class);
|
||||
captureComponent(bundleStatusProvider.getComponent());
|
||||
waitForComponentProvider(BundleStatusComponentProvider.class);
|
||||
captureComponent(bundleStatusComponentProvider.getComponent());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue