Merge remote-tracking branch 'origin/GP-5148_ryanmkurtz_vscode--SQUASHED'

This commit is contained in:
Ryan Kurtz
2024-12-03 07:03:53 -05:00
14 changed files with 628 additions and 66 deletions
@@ -607,6 +607,7 @@ src/main/help/help/topics/Trees/images/FilterOptions.png||GHIDRA||||END|
src/main/help/help/topics/Trees/images/TableColumnFilter.png||GHIDRA||||END|
src/main/help/help/topics/Trees/images/TableColumnFilterAfterFilterApplied.png||GHIDRA||||END|
src/main/help/help/topics/Trees/images/TableColumnFilterDialog.png||GHIDRA||||END|
src/main/help/help/topics/VSCodeIntegration/VSCodeIntegration.htm||GHIDRA||||END|
src/main/help/help/topics/ValidateProgram/ValidateProgram.html||GHIDRA||||END|
src/main/help/help/topics/ValidateProgram/images/ValidateProgram.png||GHIDRA||||END|
src/main/help/help/topics/ValidateProgram/images/ValidateProgramDone.png||GHIDRA||||END|
@@ -934,6 +935,7 @@ src/main/resources/images/view_left_right.png||Nuvola Icons - LGPL 2.1|||Nuvola
src/main/resources/images/view_top_bottom.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/viewmag+.png||Crystal Clear Icons - LGPL 2.1||||END|
src/main/resources/images/viewmag.png||GHIDRA||||END|
src/main/resources/images/vscode.png||MIT||||END|
src/main/resources/images/window.png||GHIDRA||||END|
src/main/resources/images/wizard.png||Nuvola Icons - LGPL 2.1|||nuvola|END|
src/main/resources/images/x-office-document-template.png||Tango Icons - Public Domain|||tango icon set|END|
@@ -267,6 +267,7 @@ icon.plugin.scriptmanager.run = icon.run
icon.plugin.scriptmanager.run.again = play_again.png
icon.plugin.scriptmanager.edit = accessories-text-editor.png
icon.plugin.scriptmanager.edit.eclipse = eclipse.png
icon.plugin.scriptmanager.edit.vscode = vscode.png
icon.plugin.scriptmanager.keybinding = key.png
icon.plugin.scriptmanager.delete = icon.delete
icon.plugin.scriptmanager.rename = icon.rename
@@ -210,6 +210,17 @@
Ghidra scripts in Eclipse, see Extensions/Eclipse/GhidraDev/GhidraDev_README.html.</I></P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H3 align="left"><A name="EditVSCode"></A>Edit Script with Visual Studio Code <IMG src="images/vscode.png" border="0"></H3>
<BLOCKQUOTE>
<P align="left">Edits the selected script in Visual Studio Code.
<BLOCKQUOTE>
<P align="left"><I><IMG src="help/shared/note.png" border="0">Before a script can be edited in
Visual Studio Code, a Visual Studio Code executable path must be defined in the Tool's
<A href="help/topics/VSCodeIntegration/VSCodeIntegration.htm"> Visual Studio Code Integration</A>
options if Visual Studio Code is installed in a non-default location.</I></P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H3 align="left"><A name="Key_Binding"></A>Assign Key Binding <IMG src="images/key.png"
border="0"></H3>
@@ -0,0 +1,57 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<TITLE>Visual Studio Code Integration</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="VSCodeIntegration"></A>Visual Studio Code Integration</H1>
<P>Ghidra is capable of integrating with an existing Visual Studio Code installation to aid
in the development of Ghidra scripts and modules.<p>
<H2><A name="VSCodeIntegrationOptions"></A>Visual Studio Code Integration Tool Options</H2>
<P>The following Front-End tool options (Edit -> Tool Options) may need to be configured for
Ghidra to successfully launch Visual Studio Code on your platform.</P>
<BR>
<CENTER>
<TABLE border="1" width="80%">
<TBODY>
<TR valign="middle">
<TD colspan="2" bgcolor="#c0c0c0" valign="top" align="left">
<P align="center"><B>Tool Options</B></P>
</TD>
</TR>
<TR>
<TD bgcolor="#DDDDDD" align="left"><B>Option</B></TD>
<TD bgcolor="#DDDDDD" align="left"><B>Description</B></TD>
</TR>
<TR valign="middle">
<TD valign="top" width="200" align="left">Visual Studio Code Executable Path</TD>
<TD valign="top" align="left">Path to a Visual Studio Code executable file. It defaults
to the most commonly used location on your specific platform.</TD>
</TR>
</TBODY>
</TABLE>
</CENTER>
<H2><A name="VSCodeModuleProject"></A>Create Visual Studio Code Module Project</H2>
<P>This action creates a new Visual Studio Code project folder which can be used as a convenient
starting point to develop a new Ghidra module. The new project will be linked against the
version of Ghidra that was used to create the project. Once the project is created, the
associated Ghidra version cannot change. This behavior may become more flexible in the
future.</P>
<P>Visual Studio Code launchers are provided which will allow you to debug your module's code.
Also, a Gradle task named <i>ghidra/distributeExtension</i> is provided that will allow you to
build a distributable Ghidra extension.
</P>
</BODY>
</HTML>
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -35,7 +35,7 @@ import ghidra.util.HelpLocation;
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.COMMON,
shortDescription = "Eclipse Integration Options",
description = "Options Eclipse Integration"
description = "Options for Eclipse Integration"
)
//@formatter:on
public class EclipseIntegrationOptionsPlugin extends Plugin implements ApplicationLevelOnlyPlugin {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -226,6 +226,9 @@ class GhidraScriptActionManager {
createScriptAction("EditEclipse", "Edit with Eclipse", "Edit Script with Eclipse",
new GIcon("icon.plugin.scriptmanager.edit.eclipse"), null, provider::editScriptEclipse);
createScriptAction("EditVSCode", "Edit with VSCode", "Edit Script with Visual Studio Code",
new GIcon("icon.plugin.scriptmanager.edit.vscode"), null, provider::editScriptVSCode);
keyBindingAction =
createScriptAction("Key Binding", "Assign Key Binding", "Assign Key Binding",
new GIcon("icon.plugin.scriptmanager.keybinding"), null,
@@ -927,6 +927,20 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
plugin.tryToEditFileInEclipse(script);
}
void editScriptVSCode() {
ResourceFile script = getSelectedScript();
if (script == null) {
plugin.getTool().setStatusInfo("Script is null.");
return;
}
if (!script.exists()) {
plugin.getTool().setStatusInfo("Script " + script.getName() + " does not exist.");
return;
}
plugin.tryToEditFileInVSCode(script);
}
GhidraScriptEditorComponentProvider editScriptInGhidra(ResourceFile script) {
GhidraScriptEditorComponentProvider editor = editorMap.get(script);
if (editor == null) {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -155,6 +155,13 @@ public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScript
}
}
@Override
public boolean tryToEditFileInVSCode(ResourceFile file) {
VSCodeIntegrationService service = tool.getService(VSCodeIntegrationService.class);
service.launchVSCode(file.getFile(false));
return true;
}
@Override
protected void programClosed(Program program) {
provider.programClosed(program);
@@ -0,0 +1,77 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.vscode;
import java.io.File;
import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.framework.OperatingSystem;
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
import ghidra.framework.options.OptionType;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.util.HelpLocation;
/**
* {@link Plugin} responsible for registering Visual Studio Code-related options
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.COMMON,
shortDescription = "Visual Studio Code Integration Options",
description = "Options for Visual Studio Code Integration"
)
//@formatter:on
public class VSCodeIntegrationOptionsPlugin extends Plugin implements ApplicationLevelOnlyPlugin {
public static final String PLUGIN_OPTIONS_NAME = "Visual Studio Code Integration";
public static final String VSCODE_EXE_PATH_OPTION = "Visual Studio Code Executable Path";
private static final String VSCODE_EXE_PATH_DESC = "Path to Visual Studio Code executable";
private static final File VSCODE_EXE_PATH_DEFAULT = getDefaultVSCodeExecutable();
public VSCodeIntegrationOptionsPlugin(PluginTool tool) {
super(tool);
}
@Override
public void init() {
super.init();
ToolOptions options = tool.getOptions(PLUGIN_OPTIONS_NAME);
options.registerOption(VSCODE_EXE_PATH_OPTION, OptionType.FILE_TYPE,
VSCODE_EXE_PATH_DEFAULT, null, VSCODE_EXE_PATH_DESC);
options.setOptionsHelpLocation(
new HelpLocation("VSCodeIntegration", "VSCodeIntegrationOptions"));
}
/**
* {@return the default Visual Studio Code executable location for the current platform}
*/
private static File getDefaultVSCodeExecutable() {
return switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) {
case WINDOWS -> new File(System.getenv("LOCALAPPDATA"),
"Programs/Microsoft VS Code/bin/code.cmd");
case MAC_OS_X -> new File(
"/Applications/Visual Studio Code.app/Contents/MacOS/Electron");
case LINUX -> new File("/usr/bin/code");
default -> null;
};
}
}
@@ -13,11 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Creates a new VSCode project for Ghidra script and module development.
// @category Development
package ghidra.app.plugin.core.vscode;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
@@ -25,22 +23,81 @@ import org.apache.commons.io.*;
import com.google.gson.*;
import docking.DockingWindowManager;
import docking.action.builder.ActionBuilder;
import docking.options.OptionsService;
import docking.tool.ToolConstants;
import docking.widgets.OptionDialog;
import docking.widgets.values.ValuesMapDialog;
import generic.theme.GIcon;
import ghidra.*;
import ghidra.app.script.GhidraScript;
import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.services.VSCodeIntegrationService;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.framework.Application;
import ghidra.framework.ApplicationProperties;
import ghidra.util.SystemUtilities;
import ghidra.framework.main.AppInfo;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.util.*;
import ghidra.util.task.TaskLauncher;
import utilities.util.FileUtilities;
public class VSCodeProjectScript extends GhidraScript {
/**
* {@link Plugin} responsible integrating Ghidra with Visual Studio Code
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.COMMON,
shortDescription = "Visual Studio Code Integration",
description = "Allows Ghidra to integrate with Visual Studio Code.",
servicesRequired = { OptionsService.class },
servicesProvided = { VSCodeIntegrationService.class }
)
//@formatter:on
public class VSCodeIntegrationPlugin extends ProgramPlugin implements VSCodeIntegrationService {
private ToolOptions options;
private Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
/**
* Create a new {@link VSCodeIntegrationPlugin}
*
* @param tool The associated {@link PluginTool tool}
*/
public VSCodeIntegrationPlugin(PluginTool tool) {
super(tool);
}
@Override
protected void run() throws Exception {
public void init() {
super.init();
options = AppInfo.getFrontEndTool().getOptions(
VSCodeIntegrationOptionsPlugin.PLUGIN_OPTIONS_NAME);
new ActionBuilder("CreateVSCodeModuleProject", name)
.menuPath(ToolConstants.MENU_TOOLS, "Create VSCode Module Project...")
.menuIcon(new GIcon("icon.plugin.scriptmanager.edit.vscode"))
.description("Creates a new Visual Studio Code module project.")
.helpLocation(new HelpLocation("VSCodeIntegration", "VSCodeModuleProject"))
.onAction(context -> showNewProjectDialog())
.buildAndInstall(tool);
}
/**
* Present the user with a dialog that allows them to select a name and location for their new
* Visual Studio Code Module Project
*/
private void showNewProjectDialog() {
if (!SystemUtilities.isInReleaseMode()) {
printerr("This script may only run from a built Ghidra release.");
Msg.showInfo(this, tool.getToolFrame(), name,
"This action may only run from a built Ghidra release.");
return;
}
@@ -50,27 +107,132 @@ public class VSCodeProjectScript extends GhidraScript {
GhidraValuesMap values = new GhidraValuesMap();
values.defineString(PROJECT_NAME_PROMPT);
values.defineDirectory(PROJECT_ROOT_PROMPT, new File(System.getProperty("user.home")));
values = askValues("Setup New VSCode Project", null, values);
ValuesMapDialog dialog =
new ValuesMapDialog("Create New Visual Studio Code Module Project", null, values);
DockingWindowManager.showDialog(dialog);
if (dialog.isCancelled()) {
return;
}
values = (GhidraValuesMap) dialog.getValues();
String projectName = values.getString(PROJECT_NAME_PROMPT);
File projectRootDir = values.getFile(PROJECT_ROOT_PROMPT);
File projectDir = new File(projectRootDir, projectName);
if (projectDir.exists()) {
printerr("Directory '%s' already exists...exiting".formatted(projectDir));
Msg.showError(this, tool.getToolFrame(), name,
"Directory '%s' already exists...exiting".formatted(projectDir));
return;
}
try {
createVSCodeModuleProject(projectDir);
Msg.showInfo(this, tool.getToolFrame(), name,
"Successfully created Visual Studio Code module project directory at: " +
projectDir);
}
catch (IOException e) {
Msg.showError(this, tool.getToolFrame(), name,
"Failed to create Visual Studio Code module project directory at: " + projectDir,
e);
}
}
@Override
public ToolOptions getVSCodeIntegrationOptions() {
return options;
}
@Override
public File getVSCodeExecutableFile() throws FileNotFoundException {
File vscodeExecutableFile =
options.getFile(VSCodeIntegrationOptionsPlugin.VSCODE_EXE_PATH_OPTION, null);
if (vscodeExecutableFile == null || !vscodeExecutableFile.isFile()) {
throw new FileNotFoundException(
"Visual Studio Code installation executable file does not exist.");
}
return vscodeExecutableFile;
}
@Override
public void launchVSCode(File file) {
TaskLauncher.launch(new VSCodeLauncherTask(this, file));
}
@Override
public void handleVSCodeError(String error, boolean askAboutOptions, Throwable t) {
if (askAboutOptions && !SystemUtilities.isInHeadlessMode()) {
SystemUtilities.runSwingNow(() -> {
int choice =
OptionDialog.showYesNoDialog(null, "Failed to launch Visual Studio Code",
error + "\nWould you like to verify your \"" +
VSCodeIntegrationOptionsPlugin.PLUGIN_OPTIONS_NAME +
"\" options now?");
if (choice == OptionDialog.YES_OPTION) {
AppInfo.getFrontEndTool()
.getService(OptionsService.class)
.showOptionsDialog(
VSCodeIntegrationOptionsPlugin.PLUGIN_OPTIONS_NAME, null);
}
});
}
else {
Msg.showError(VSCodeIntegrationPlugin.class, null,
"Failed to launch Visual Studio Code", error, t);
}
}
@Override
public void createVSCodeModuleProject(File projectDir) throws IOException {
File installDir = Application.getInstallationDirectory().getFile(false);
Map<String, String> classpathSourceMap = getClasspathSourceMap();
writeSettings(installDir, projectDir, classpathSourceMap);
writeLaunch(installDir, projectDir, classpathSourceMap);
JsonObject settings = createSettings(installDir, projectDir, classpathSourceMap,
List.of("src/main/java", "ghidra_scripts"), "bin/main");
JsonObject launch =
createLaunch(installDir, projectDir, classpathSourceMap, "${workspaceFolder}");
File vscodeDir = new File(projectDir, ".vscode");
if (!FileUtilities.mkdirs(vscodeDir)) {
throw new IOException("Failed to create: " + vscodeDir);
}
File settingsFile = new File(vscodeDir, "settings.json");
File launchFile = new File(vscodeDir, "launch.json");
FileUtils.writeStringToFile(settingsFile, gson.toJson(settings), StandardCharsets.UTF_8);
FileUtils.writeStringToFile(launchFile, gson.toJson(launch), StandardCharsets.UTF_8);
writeSampleScriptJava(projectDir);
writeSampleScriptPyGhidra(projectDir);
writeSampleModule(installDir, projectDir);
}
println("Successfully created VSCode project directory at: " + projectDir);
println(
"To debug, please close Ghidra and relaunch from the VSCode Ghidra launch configuration.");
@Override
public void addToVSCodeWorkspace(File workspaceFile, File projectDir) throws IOException {
File installDir = Application.getInstallationDirectory().getFile(false);
Map<String, String> classpathSourceMap = getClasspathSourceMap();
JsonObject settings =
createSettings(installDir, projectDir, classpathSourceMap, List.of("."), null);
JsonObject launch = createLaunch(installDir, projectDir, classpathSourceMap, null);
JsonObject workspace;
if (workspaceFile.isFile()) {
String str = FileUtils.readFileToString(workspaceFile, StandardCharsets.UTF_8);
JsonElement element = JsonParser.parseString(str);
if (!(element instanceof JsonObject json)) {
throw new IOException("'%s' was not a JsonObject".formatted(workspaceFile));
}
workspace = addToExistingWorkspace(projectDir, json, settings, launch);
}
else {
workspace = addToExistingWorkspace(projectDir, createNewWorkspace(settings, launch),
settings, launch);
}
if (!FileUtilities.mkdirs(workspaceFile.getParentFile())) {
throw new IOException("Failed to create: " + workspaceFile.getParentFile());
}
FileUtils.writeStringToFile(workspaceFile, gson.toJson(workspace), StandardCharsets.UTF_8);
}
/**
@@ -94,21 +256,22 @@ public class VSCodeProjectScript extends GhidraScript {
}
/**
* Write the .vscode/settings.json file
* Creates the VSCode settings json
*
* @param installDir The Ghidra installation directory
* @param projectDir The VSCode project directory
* @param classpathSourceMap The classpath/source map (see {@link #getClasspathSourceMap()})
* @param sourcePaths A {@link List} of source paths
* @param outputPath The output path (null for default)
* @return The VSCode settings json
* @throws IOException if an IO-related error occurs
*/
private void writeSettings(File installDir, File projectDir,
Map<String, String> classpathSourceMap) throws IOException {
File vscodeDir = new File(projectDir, ".vscode");
File settingsFile = new File(vscodeDir, "settings.json");
private JsonObject createSettings(File installDir, File projectDir,
Map<String, String> classpathSourceMap, List<String> sourcePaths, String outputPath)
throws IOException {
String gradleVersion = Application
.getApplicationProperty(ApplicationProperties.APPLICATION_GRADLE_MIN_PROPERTY);
String pythonInterpreterPath = System.getProperty("pyghidra.sys.prefix", null);
// Build settings json object
JsonObject json = new JsonObject();
json.addProperty("java.import.maven.enabled", false);
@@ -120,12 +283,13 @@ public class VSCodeProjectScript extends GhidraScript {
JsonArray sourcePathArray = new JsonArray();
json.add("java.project.sourcePaths", sourcePathArray);
sourcePathArray.add("src/main/java");
sourcePathArray.add("ghidra_scripts");
sourcePaths.forEach(sourcePathArray::add);
if (outputPath != null) {
json.addProperty("java.project.outputPath", outputPath);
}
json.addProperty("java.project.outputPath", "bin/main");
JsonObject referencedLibrariesObject = new JsonObject();
json.add("java.project.referencedLibraries", referencedLibrariesObject);
JsonArray includeArray = new JsonArray();
referencedLibrariesObject.add("include", includeArray);
@@ -139,29 +303,22 @@ public class VSCodeProjectScript extends GhidraScript {
json.addProperty("python.analysis.stubPath",
new File(installDir, "docs/ghidra_stubs/typestubs").getAbsolutePath());
if (pythonInterpreterPath != null) {
json.addProperty("python.defaultInterpreterPath", pythonInterpreterPath);
}
// Write settings json object
if (!FileUtilities.mkdirs(settingsFile.getParentFile())) {
throw new IOException("Failed to create: " + settingsFile.getParentFile());
}
FileUtils.writeStringToFile(settingsFile, gson.toJson(json), StandardCharsets.UTF_8);
return json;
}
/**
* Write the .vscode/launch.json file
* Creates the VSCode launch json
*
* @param installDir The Ghidra installation directory
* @param projectDir The VSCode project directory
* @param classpathSourceMap The classpath/source map (see {@link #getClasspathSourceMap()})
* @param externalModules The Ghidra external modules to pass to Ghidra (could be null)
* @return The VSCode launch json
* @throws IOException if an IO-related error occurs
*/
private void writeLaunch(File installDir, File projectDir,
Map<String, String> classpathSourceMap) throws IOException {
File vscodeDir = new File(projectDir, ".vscode");
File launchFile = new File(vscodeDir, "launch.json");
private JsonObject createLaunch(File installDir, File projectDir,
Map<String, String> classpathSourceMap, String externalModules) throws IOException {
// Get the path of Utility.jar so we can put it on the classpath
String utilityJarPath = classpathSourceMap.keySet()
@@ -201,7 +358,9 @@ public class VSCodeProjectScript extends GhidraScript {
classPathsArray.add(utilityJarPath);
JsonArray vmArgsArray = new JsonArray();
ghidraConfigObject.add("vmArgs", vmArgsArray);
vmArgsArray.add("-Dghidra.external.modules=${workspaceFolder}");
if (externalModules != null) {
vmArgsArray.add("-Dghidra.external.modules=" + externalModules);
}
vmArgs.forEach(vmArgsArray::add);
// PyGhidra launcher
@@ -222,20 +381,63 @@ public class VSCodeProjectScript extends GhidraScript {
pyghidraConfigObject.add("env", envObject);
envObject.addProperty("PYGHIDRA_DEBUG", "1");
// PyGhidra Java Attach
JsonObject pyghidraAttachObject = new JsonObject();
configurationsArray.add(pyghidraAttachObject);
pyghidraAttachObject.addProperty("type", "java");
pyghidraAttachObject.addProperty("name", "PyGhidra Java Attach");
pyghidraAttachObject.addProperty("request", "attach");
pyghidraAttachObject.addProperty("hostName", "localhost");
pyghidraAttachObject.addProperty("port", 18001);
// Ghidra Attach
JsonObject ghidraAttachObject = new JsonObject();
configurationsArray.add(ghidraAttachObject);
ghidraAttachObject.addProperty("type", "java");
ghidraAttachObject.addProperty("name", "Ghidra Attach");
ghidraAttachObject.addProperty("request", "attach");
ghidraAttachObject.addProperty("hostName", "localhost");
ghidraAttachObject.addProperty("port", 18001);
return json;
}
// Write launch json object
if (!FileUtilities.mkdirs(launchFile.getParentFile())) {
throw new IOException("Failed to create: " + launchFile.getParentFile());
/**
* Creates a new VSCode workspace with no folders added
*
* @param settings The VSCode settings JSON
* @param launch The VSCode launch JSON
* @return The new workspace JSON
*/
private JsonObject createNewWorkspace(JsonObject settings, JsonObject launch) {
JsonObject json = new JsonObject();
JsonArray foldersArray = new JsonArray();
json.add("folders", foldersArray);
JsonObject folderObject = new JsonObject();
foldersArray.add(folderObject);
json.add("settings", settings);
json.add("launch", launch);
return json;
}
/**
* Adds the given project directory to the given workspace
*
* @param projectDir The VSCode project directory to add
* @param workspace The VSCode workspace to add to
* @param settings The VSCode settings JSON
* @param launch The VSCode launch JSON
* @return The new workspace JSON with the project added
*/
private JsonObject addToExistingWorkspace(File projectDir, JsonObject workspace, JsonObject settings,
JsonObject launch) {
File projectParentDir = projectDir.getParentFile();
String folderName = projectDir.getName();
if (projectParentDir != null) {
folderName = projectParentDir.getName() + "/" + folderName;
}
FileUtils.writeStringToFile(launchFile, gson.toJson(json), StandardCharsets.UTF_8);
JsonArray foldersArray = workspace.getAsJsonArray("folders");
JsonObject folderObject = new JsonObject();
folderObject.addProperty("name",
projectDir.getParentFile().getName() + "/" + projectDir.getName());
folderObject.addProperty("path", projectDir.getAbsolutePath());
if (!foldersArray.contains(folderObject)) {
foldersArray.add(folderObject);
}
return workspace;
}
/**
@@ -265,7 +467,7 @@ public class VSCodeProjectScript extends GhidraScript {
}
FileUtils.writeStringToFile(scriptFile, sampleScript, StandardCharsets.UTF_8);
}
private void writeSampleScriptPyGhidra(File projectDir) throws IOException {
File scriptsDir = new File(projectDir, "ghidra_scripts");
File scriptFile = new File(scriptsDir, "sample_script.py");
@@ -309,11 +511,11 @@ public class VSCodeProjectScript extends GhidraScript {
// Rename java files and text replace their contents
for (File f : newPackageDir.listFiles()) {
String name = f.getName();
if (!name.startsWith(skeleton)) {
String oldName = f.getName();
if (!oldName.startsWith(skeleton)) {
continue;
}
String newName = projectName + name.substring(skeleton.length(), name.length());
String newName = projectName + oldName.substring(skeleton.length(), oldName.length());
File newFile = new File(f.getParentFile(), newName);
if (!f.renameTo(newFile)) {
throw new IOException("Failed to rename: " + f);
@@ -0,0 +1,107 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.vscode;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.services.VSCodeIntegrationService;
import ghidra.framework.Application;
import ghidra.util.SystemUtilities;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
/**
* A {@link Task} to launch Visual Studio Code
*/
class VSCodeLauncherTask extends Task {
private VSCodeIntegrationService vscodeService;
private File file;
/**
* Constructs a new Visual Studio Code launcher task
*
* @param vscodeService The Visual Studio Code integration service
* @param file The file to open in Visual Studio Code
*/
public VSCodeLauncherTask(VSCodeIntegrationService vscodeService, File file) {
super("Visual Studio Code Launcher Task", true, true, true);
this.vscodeService = vscodeService;
this.file = file;
}
@Override
public void run(TaskMonitor monitor) {
if (SystemUtilities.isInDevelopmentMode()) {
vscodeService.handleVSCodeError(
"Launching Visual Studio Code is not supported in development mode.", false, null);
return;
}
// Get required Visual Studio Code components. If VSCode isn't found at the default
// location present the user with the options window, and when they close that window, try
// again.
File vscodeExecutableFile;
try {
vscodeExecutableFile = vscodeService.getVSCodeExecutableFile();
}
catch (IOException e1) {
vscodeService.handleVSCodeError(e1.getMessage(), true, null);
try {
vscodeExecutableFile = vscodeService.getVSCodeExecutableFile();
}
catch (IOException e2) {
vscodeService.handleVSCodeError(
"Failed to launch Visual Studio Code. The required Visual Studio Code components have not been configured.",
false, null);
return;
}
}
// Setup the workspace
File vscodeSettingsDir = new File(Application.getUserSettingsDirectory(), "vscode");
File workspaceFile = new File(vscodeSettingsDir, "ghidra_scripts.code-workspace");
try {
vscodeService.addToVSCodeWorkspace(workspaceFile, file.getParentFile());
}
catch (IOException e) {
vscodeService.handleVSCodeError("Failed to create Visual Studio Code workspace.", false,
e);
return;
}
// Launch Visual Studio Code
monitor.setIndeterminate(true);
monitor.setMessage("Launching Visual Studio Code...");
try {
List<String> args = new ArrayList<>();
args.add(vscodeExecutableFile.getAbsolutePath());
args.add("-a");
args.add(workspaceFile.getAbsolutePath());
args.add(file.getAbsolutePath());
new ProcessBuilder(args).redirectErrorStream(true).start();
}
catch (Exception e) {
vscodeService.handleVSCodeError(
"Unexpected exception occurred while launching Visual Studio Code.", false, null);
return;
}
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -30,4 +30,12 @@ public interface GhidraScriptService {
* @return True if the file opened in Eclipse; otherwise, false.
*/
public boolean tryToEditFileInEclipse(ResourceFile file);
/**
* Attempts to edit the provided file in Visual Studio Code.
*
* @param file The file to edit in Visual Studio Code.
* @return True if the file opened in Visual Studio Code; otherwise, false.
*/
public boolean tryToEditFileInVSCode(ResourceFile file);
}
@@ -0,0 +1,73 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.services;
import java.io.*;
import ghidra.framework.options.ToolOptions;
/**
* Service that provides Visual Studio Code-related functionality
*/
public interface VSCodeIntegrationService {
/**
* {@return the Visual Studio Code Integration options}
*/
public ToolOptions getVSCodeIntegrationOptions();
/**
* {@return the Visual Studio Code executable file}
*
* @throws FileNotFoundException if the executable file does not exist
*/
public File getVSCodeExecutableFile() throws FileNotFoundException;
/**
* Launches Visual Studio Code
*
* @param file The initial file to open in Visual Studio Code
*/
public void launchVSCode(File file);
/**
* Displays the given Visual Studio Code related error message in an error dialog
*
* @param error The error message to display in a dialog
* @param askAboutOptions True if we should ask the user if they want to be taken to the Visual
* Studio Code options; otherwise, false
* @param t An optional throwable to tie to the message
*/
public void handleVSCodeError(String error, boolean askAboutOptions, Throwable t);
/**
* Creates a new Visual Studio Code module project at the given directory
*
* @param projectDir The new directory to create
* @throws IOException if the directory failed to be created
*/
public void createVSCodeModuleProject(File projectDir) throws IOException;
/**
* Adds the given project directory to the the given Visual Studio Code workspace file
* A new workspace will be created if it doesn't already exist
*
* @param workspaceFile The location of the workspace file
* @param projectDir An existing project directory to add to the workspace
* @throws IOException if the directory failed to be created
*/
public void addToVSCodeWorkspace(File workspaceFile, File projectDir) throws IOException;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB