diff --git a/bridge_mcp_ghidra.py b/bridge_mcp_ghidra.py index 7b54646..3f1aaa0 100644 --- a/bridge_mcp_ghidra.py +++ b/bridge_mcp_ghidra.py @@ -435,19 +435,183 @@ def search_bytes(bytes_hex: str, offset: int = 0, limit: int = 100) -> list: ) @mcp.tool() -def set_bytes(address: str, bytes_hex: str) -> str: +def create_enum(name: str, category: str = None, size: int = 4, values: list = None) -> str: """ - Writes a sequence of bytes to the specified address in the program's memory. + Create a new enum. + + Args: + name: The name of the new enum. + category: The category path for the enum (e.g., /my_enums). Defaults to root. + size: The size of the enum in bytes (default: 4). + values: A list of value dictionaries to add to the new enum. + Each dict should have 'name', 'value', and optionally 'comment'. + Example: [{"name": "VALUE1", "value": 0, "comment": "First value"}] + + Returns: + A status message indicating success or failure. + """ + data = {"name": name, "size": str(size)} + if category: + data["category"] = category + if values: + data["values"] = json.dumps(values) + return safe_post("create_enum", data) + +@mcp.tool() +def add_enum_values(enum_name: str, values: list, category: str = None) -> str: + """ + Add values to an existing enum. + + Args: + enum_name: The name of the enum to modify. + values: A list of value dictionaries to add to the enum. + Each dict should have 'name', 'value', and optionally 'comment'. + Example: [{"name": "VALUE1", "value": 0, "comment": "First value"}] + category: The category path for the enum. Defaults to root. + + Returns: + A status message indicating success or failure. + """ + data = {"enum_name": enum_name, "values": json.dumps(values)} + if category: + data["category"] = category + return safe_post("add_enum_values", data) + +@mcp.tool() +def get_enum(name: str, category: str = None) -> dict: + """ + Get an enum's definition. + + Args: + name: The name of the enum. + category: The category path for the enum. Defaults to root. + + Returns: + A dictionary representing the enum, or an error message. + """ + params = {"name": name} + if category: + params["category"] = category + + response_lines = safe_get("get_enum", params) + response_str = "\n".join(response_lines) + + try: + # Attempt to parse the JSON response + return json.loads(response_str) + except json.JSONDecodeError: + # If it's not JSON, it's likely an error message + return {"error": response_str} + +@mcp.tool() +def set_global_data_type(address: str, data_type: str, length: int = -1, clear_mode: str = "CHECK_FOR_SPACE") -> str: + """ + Set the data type of a global variable or data at a specific memory address. + + Args: + address: The memory address in hex format (e.g., "0x401000") + data_type: The name of the data type to apply (e.g., "int", "char*", "MyStruct") + length: Optional length for dynamic data types (default: -1, let type determine) + clear_mode: How to handle conflicting data. Options: + - "CHECK_FOR_SPACE": Ensure data fits before clearing (default) + - "CLEAR_SINGLE_DATA": Always clear single code unit at address + - "CLEAR_ALL_UNDEFINED_CONFLICT_DATA": Clear conflicting undefined data + - "CLEAR_ALL_DEFAULT_CONFLICT_DATA": Clear conflicting default data + - "CLEAR_ALL_CONFLICT_DATA": Clear all conflicting data + + Returns: + A status message indicating success or failure. + """ + data = { + "address": address, + "data_type": data_type, + "clear_mode": clear_mode + } + if length > 0: + data["length"] = str(length) + + return safe_post("set_global_data_type", data) +@mcp.tool() +def add_class_members(class_name: str, members: list, parent_namespace: str = None) -> str: + """ + Add members to an existing C++ class. + Args: - address: Destination address (e.g., "0x140001000") - bytes_hex: Sequence of space-separated bytes in hexadecimal format (e.g., "90 90 90 90") + class_name: The name of the class to modify. + members: A list of member dictionaries to add to the class. + Each dict should have 'name', 'type', and optionally 'offset' and 'comment'. + Example: [{"name": "health", "type": "float", "comment": "Player health"}] + parent_namespace: The parent namespace where the class is located (optional). + + Returns: + A status message indicating success or failure. + """ + params = {"class_name": class_name, "members": json.dumps(members)} + if parent_namespace: + params["parent_namespace"] = parent_namespace + + return safe_post("add_class_members", params) +@mcp.tool() +def remove_class_members(class_name: str, members: list, parent_namespace: str = None) -> str: + """ + Remove members from an existing C++ class. + + Args: + class_name: The name of the class to modify. + members: A list of member names to remove from the class. + Example: ["old_member", "unused_field"] + parent_namespace: The parent namespace where the class is located (optional). + Returns: - Result of the operation (e.g., "Bytes written successfully" or a detailed error) + A status message indicating success or failure. """ - return safe_post("set_bytes", {"address": address, "bytes": bytes_hex}) + params = {"class_name": class_name, "members": json.dumps(members)} + if parent_namespace: + params["parent_namespace"] = parent_namespace + + return safe_post("remove_class_members", params) + +@mcp.tool() +def remove_enum_values(enum_name: str, values: list, category: str = None) -> str: + """ + Remove values from an existing enum. + + Args: + enum_name: The name of the enum to modify. + values: A list of value names to remove from the enum. + Example: ["OLD_VALUE", "DEPRECATED_OPTION"] + category: The category path for the enum (optional, defaults to root). + + Returns: + A status message indicating success or failure. + """ + params = {"enum_name": enum_name, "values": json.dumps(values)} + if category: + params["category"] = category + + return safe_post("remove_enum_values", params) + +@mcp.tool() +def remove_struct_members(struct_name: str, members: list, category: str = None) -> str: + """ + Remove members from an existing struct. + + Args: + struct_name: The name of the struct to modify. + members: A list of member names to remove from the struct. + Example: ["old_field", "unused_member"] + category: The category path for the struct (optional, defaults to root). + + Returns: + A status message indicating success or failure. + """ + params = {"struct_name": struct_name, "members": json.dumps(members)} + if category: + params["category"] = category + return safe_post("remove_struct_members", params) def main(): parser = argparse.ArgumentParser(description="MCP server for Ghidra") diff --git a/src/main/java/com/lauriewired/handlers/act/AddClassMembers.java b/src/main/java/com/lauriewired/handlers/act/AddClassMembers.java new file mode 100644 index 0000000..796496c --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/AddClassMembers.java @@ -0,0 +1,185 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.GhidraClass; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Namespace; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolTable; +import ghidra.program.model.symbol.SymbolType; +import ghidra.program.database.data.DataTypeUtilities; + +import com.google.gson.Gson; + +import javax.swing.SwingUtilities; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.lauriewired.util.GhidraUtils.*; +import static com.lauriewired.util.ParseUtils.*; +import static com.lauriewired.util.StructUtils.StructMember; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for adding members to a C++ class in Ghidra. + * This modifies both the class namespace and its associated structure data type. + * Expects a POST request with parameters: + * - class_name: Name of the class to modify + * - parent_namespace: Parent namespace where the class is located (optional) + * - members: JSON array of members to add, each with fields: + * - type: Data type of the member + * - name: Name of the member + * - comment: Comment for the member (optional) + * - offset: Offset in bytes (optional, -1 for next available position) + */ +public final class AddClassMembers extends Handler { + /** + * Constructor for the AddClassMembers handler. + * + * @param tool The Ghidra plugin tool instance. + */ + public AddClassMembers(PluginTool tool) { + super(tool, "/add_class_members"); + } + + /** + * Handles the HTTP request to add members to a class. + * + * @param exchange The HTTP exchange containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String className = params.get("class_name"); + String parentNamespace = params.get("parent_namespace"); + String membersJson = params.get("members"); + + if (className == null || membersJson == null) { + sendResponse(exchange, "class_name and members are required"); + return; + } + sendResponse(exchange, addClassMembers(className, parentNamespace, membersJson)); + } + + /** + * Adds members to a class in the current Ghidra program. + * + * @param className The name of the class to modify. + * @param parentNamespace The parent namespace where the class is located (optional). + * @param membersJson JSON array of members to add. + * @return A message indicating success or failure. + */ + private String addClassMembers(String className, String parentNamespace, String membersJson) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Add Class Members"); + boolean success = false; + try { + SymbolTable symbolTable = program.getSymbolTable(); + DataTypeManager dtm = program.getDataTypeManager(); + + // Find the class namespace + Namespace parent = program.getGlobalNamespace(); + if (parentNamespace != null && !parentNamespace.isEmpty()) { + parent = symbolTable.getNamespace(parentNamespace, program.getGlobalNamespace()); + if (parent == null) { + result.set("Error: Parent namespace '" + parentNamespace + "' not found"); + return; + } + } + + // Find the class by iterating through symbols + GhidraClass classNamespace = null; + for (Symbol symbol : symbolTable.getSymbols(className, parent)) { + if (symbol.getSymbolType() == SymbolType.CLASS) { + classNamespace = (GhidraClass) symbol.getObject(); + break; + } + } + + if (classNamespace == null) { + result.set("Error: Class '" + className + "' not found" + + (parent != null ? " in namespace " + parent.getName() : "")); + return; + } + + // Find the associated structure + Structure classStruct = DataTypeUtilities.findExistingClassStruct(dtm, classNamespace); + if (classStruct == null) { + result.set("Error: No structure found for class '" + className + "'"); + return; + } + + StringBuilder responseBuilder = new StringBuilder( + "Adding members to class " + className); + + if (membersJson != null && !membersJson.isEmpty()) { + Gson gson = new Gson(); + StructMember[] members = gson.fromJson(membersJson, StructMember[].class); + + int membersAdded = 0; + for (StructMember member : members) { + DataType memberDt = resolveDataType(tool, dtm, member.type); + if (memberDt == null) { + responseBuilder.append("\nError: Could not resolve data type '").append(member.type) + .append("' for member '").append(member.name) + .append("'. Aborting further member creation."); + break; + } + + // Check if member already exists + DataTypeComponent existingComponent = null; + for (DataTypeComponent comp : classStruct.getComponents()) { + if (comp.getFieldName() != null && comp.getFieldName().equals(member.name)) { + existingComponent = comp; + break; + } + } + if (existingComponent != null) { + responseBuilder.append("\nWarning: Member '").append(member.name) + .append("' already exists. Skipping."); + continue; + } + + if (member.offset != -1) { + classStruct.insertAtOffset((int) member.offset, memberDt, -1, member.name, member.comment); + responseBuilder.append("\nAdded member '").append(member.name) + .append("' at offset ").append(member.offset); + } else { + classStruct.add(memberDt, member.name, member.comment); + responseBuilder.append("\nAdded member '").append(member.name) + .append("' at end of structure"); + } + membersAdded++; + } + responseBuilder.append("\nSuccessfully added ").append(membersAdded).append(" members to class ").append(className); + result.set(responseBuilder.toString()); + success = membersAdded > 0; + } + + } catch (Exception e) { + result.set("Error: Failed to add members to class: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute add class members on Swing thread: " + e.getMessage(); + } + return result.get(); + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/handlers/act/AddEnumValues.java b/src/main/java/com/lauriewired/handlers/act/AddEnumValues.java new file mode 100644 index 0000000..e60f468 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/AddEnumValues.java @@ -0,0 +1,137 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.Enum; +import ghidra.program.model.listing.Program; + +import com.google.gson.Gson; + +import javax.swing.SwingUtilities; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.lauriewired.util.ParseUtils.*; +import static com.lauriewired.util.EnumUtils.EnumValue; +import ghidra.program.model.data.CategoryPath; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for adding values to an enum in Ghidra. + * Expects a POST request with parameters: + * - enum_name: Name of the enum to modify + * - category: Category path where the enum is located (optional) + * - values: JSON array of values to add, each with fields: + * - name: Name of the enum value + * - value: Numeric value of the enum entry + * - comment: Comment for the enum value (optional) + */ +public final class AddEnumValues extends Handler { + /** + * Constructor for the AddEnumValues handler. + * + * @param tool The Ghidra plugin tool instance. + */ + public AddEnumValues(PluginTool tool) { + super(tool, "/add_enum_values"); + } + + /** + * Handles the HTTP request to add values to an enum. + * + * @param exchange The HTTP exchange containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String enumName = params.get("enum_name"); + String category = params.get("category"); + String valuesJson = params.get("values"); + + if (enumName == null || valuesJson == null) { + sendResponse(exchange, "enum_name and values are required"); + return; + } + sendResponse(exchange, addEnumValues(enumName, category, valuesJson)); + } + + /** + * Adds values to an enum in the current Ghidra program. + * + * @param enumName The name of the enum to modify. + * @param category The category path where the enum is located (optional). + * @param valuesJson JSON array of values to add. + * @return A message indicating success or failure. + */ + private String addEnumValues(String enumName, String category, String valuesJson) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Add Enum Values"); + boolean success = false; + try { + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath(category == null ? "/" : category); + DataType dt = dtm.getDataType(path, enumName); + + if (dt == null || !(dt instanceof Enum)) { + result.set("Error: Enum " + enumName + " not found in category " + path); + return; + } + Enum enumDt = (Enum) dt; + + StringBuilder responseBuilder = new StringBuilder(); + + if (valuesJson != null && !valuesJson.isEmpty()) { + Gson gson = new Gson(); + EnumValue[] values = gson.fromJson(valuesJson, EnumValue[].class); + + int valuesAdded = 0; + for (EnumValue enumValue : values) { + if (enumValue.name == null || enumValue.name.isEmpty()) { + responseBuilder.append("\nError: Enum value name cannot be empty. Skipping value."); + continue; + } + + // Check if value name already exists + if (enumDt.contains(enumValue.name)) { + responseBuilder.append("\nWarning: Enum value '").append(enumValue.name) + .append("' already exists. Skipping."); + continue; + } + + // Add the enum value with or without comment + if (enumValue.comment != null && !enumValue.comment.isEmpty()) { + enumDt.add(enumValue.name, (long) enumValue.value, enumValue.comment); + } else { + enumDt.add(enumValue.name, (long) enumValue.value); + } + valuesAdded++; + } + responseBuilder.append("\nAdded ").append(valuesAdded).append(" values to enum ").append(enumName).append("."); + result.set(responseBuilder.toString()); + success = valuesAdded > 0; + } + + } catch (Exception e) { + result.set("Error: Failed to add values to enum: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute add enum values on Swing thread: " + e.getMessage(); + } + return result.get(); + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/handlers/act/CreateClass.java b/src/main/java/com/lauriewired/handlers/act/CreateClass.java new file mode 100644 index 0000000..c284390 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/CreateClass.java @@ -0,0 +1,190 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.StructureDataType; +import ghidra.program.model.listing.GhidraClass; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Namespace; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.model.symbol.SymbolTable; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.InvalidInputException; + +import com.google.gson.Gson; + +import javax.swing.SwingUtilities; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.lauriewired.util.GhidraUtils.*; +import static com.lauriewired.util.ParseUtils.*; +import static com.lauriewired.util.StructUtils.StructMember; +import ghidra.program.model.data.CategoryPath; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for creating a new C++ class in Ghidra. + * This creates both a class namespace and an associated structure data type. + * Expects parameters: name, parent_namespace (optional), members (optional JSON array). + * Members should be in the format: [{"name": "member1", "type": "int", "offset": 0, "comment": "Member 1"}, ...] + */ +public final class CreateClass extends Handler { + /** + * Constructs a new CreateClass handler. + * + * @param tool The PluginTool instance to interact with Ghidra. + */ + public CreateClass(PluginTool tool) { + super(tool, "/create_class"); + } + + /** + * Handles the HTTP request to create a new C++ class. + * Parses parameters from the POST request and creates the class in Ghidra. + * + * @param exchange The HTTP exchange containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String name = params.get("name"); + String parentNamespace = params.get("parent_namespace"); + String membersJson = params.get("members"); + + if (name == null || name.isEmpty()) { + sendResponse(exchange, "Error: Class name is required"); + return; + } + + String response = createClassInGhidra(name, parentNamespace, membersJson); + sendResponse(exchange, response); + } + + /** + * Creates a C++ class in Ghidra with the specified parameters. + * + * @param name The name of the class to create. + * @param parentNamespace The parent namespace (null for global). + * @param membersJson JSON string representing class members. + * @return A status message indicating success or failure. + */ + private String createClassInGhidra(String name, String parentNamespace, String membersJson) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Create Class"); + boolean success = false; + try { + SymbolTable symbolTable = program.getSymbolTable(); + DataTypeManager dtm = program.getDataTypeManager(); + + // Resolve parent namespace + Namespace parent = null; + if (parentNamespace != null && !parentNamespace.isEmpty()) { + parent = symbolTable.getNamespace(parentNamespace, program.getGlobalNamespace()); + if (parent == null) { + result.set("Error: Parent namespace '" + parentNamespace + "' not found"); + return; + } + } + + // Create the class namespace + GhidraClass classNamespace; + try { + classNamespace = symbolTable.createClass(parent, name, SourceType.USER_DEFINED); + } catch (DuplicateNameException e) { + result.set("Error: Class '" + name + "' already exists in namespace " + + (parent != null ? parent.getName() : "global")); + return; + } catch (InvalidInputException e) { + result.set("Error: Invalid class name '" + name + "': " + e.getMessage()); + return; + } + + StringBuilder responseBuilder = new StringBuilder( + "Class " + name + " created successfully"); + if (parent != null) { + responseBuilder.append(" in namespace ").append(parent.getName()); + } + + // Create associated structure data type for the class + CategoryPath classCategory = getCategoryPath(classNamespace); + StructureDataType classStruct = new StructureDataType(classCategory, name, 0, dtm); + + // Add members if provided + if (membersJson != null && !membersJson.isEmpty()) { + Gson gson = new Gson(); + StructMember[] members = gson.fromJson(membersJson, StructMember[].class); + + int membersAdded = 0; + for (StructMember member : members) { + DataType memberDt = resolveDataType(tool, dtm, member.type); + if (memberDt == null) { + responseBuilder.append("\nError: Could not resolve data type '").append(member.type) + .append("' for member '").append(member.name) + .append("'. Aborting further member creation."); + break; + } + + if (member.offset != -1) { + classStruct.insertAtOffset((int) member.offset, memberDt, -1, member.name, + member.comment); + } else { + classStruct.add(memberDt, member.name, member.comment); + } + membersAdded++; + } + responseBuilder.append("\nAdded ").append(membersAdded).append(" members to class structure."); + } + + // Add the structure to the data type manager + dtm.addDataType(classStruct, DataTypeConflictHandler.DEFAULT_HANDLER); + + responseBuilder.append("\nClass structure created in category: ").append(classCategory); + result.set(responseBuilder.toString()); + success = true; + } catch (Exception e) { + result.set("Error: Failed to create class: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute create class on Swing thread: " + e.getMessage(); + } + return result.get(); + } + + /** + * Get the category path for a class namespace. + * This creates a category path based on the class namespace hierarchy. + */ + private CategoryPath getCategoryPath(GhidraClass classNamespace) { + List pathParts = new ArrayList<>(); + Namespace current = classNamespace; + + // Build path from class up to root (excluding global namespace) + while (current != null && !current.isGlobal()) { + pathParts.add(0, current.getName()); // Insert at beginning to reverse order + current = current.getParentNamespace(); + } + + // Create category path starting with "/classes" + if (pathParts.isEmpty()) { + return new CategoryPath("/classes"); + } else { + return new CategoryPath("/classes/" + String.join("/", pathParts)); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/handlers/act/CreateEnum.java b/src/main/java/com/lauriewired/handlers/act/CreateEnum.java new file mode 100644 index 0000000..b8aadd6 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/CreateEnum.java @@ -0,0 +1,131 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.EnumDataType; +import ghidra.program.model.listing.Program; + +import com.google.gson.Gson; + +import javax.swing.SwingUtilities; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.lauriewired.util.ParseUtils.*; +import static com.lauriewired.util.EnumUtils.EnumValue; +import ghidra.program.model.data.CategoryPath; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for creating a new enum in Ghidra. + * Expects parameters: name, category (optional), size (optional), values (optional JSON array). + * Values should be in the format: [{"name": "VALUE1", "value": 0, "comment": "First value"}, ...] + */ +public final class CreateEnum extends Handler { + /** + * Constructs a new CreateEnum handler. + * + * @param tool The PluginTool instance to interact with Ghidra. + */ + public CreateEnum(PluginTool tool) { + super(tool, "/create_enum"); + } + + /** + * Handles the HTTP request to create a new enum. + * Parses parameters from the POST request and creates the enum in Ghidra. + * + * @param exchange The HTTP exchange containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String name = params.get("name"); + String category = params.get("category"); + long size = parseIntOrDefault(params.get("size"), 4); // Default to 4 bytes (int size) + String valuesJson = params.get("values"); // Optional + + if (name == null || name.isEmpty()) { + sendResponse(exchange, "Enum name is required"); + return; + } + sendResponse(exchange, createEnum(name, category, (int) size, valuesJson)); + } + + /** + * Creates a new enum in Ghidra with the specified parameters. + * This method runs on the Swing thread to ensure thread safety when interacting with Ghidra's data types. + * + * @param name The name of the enum to create. + * @param category The category path where the enum will be created (optional). + * @param size The size of the enum in bytes (optional, defaults to 4). + * @param valuesJson JSON array of enum values (optional). + * @return A message indicating success or failure of the operation. + */ + private String createEnum(String name, String category, int size, String valuesJson) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Create Enum"); + boolean success = false; + try { + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath(category == null ? "/" : category); + + if (dtm.getDataType(path, name) != null) { + result.set("Error: Enum " + name + " already exists in category " + path); + return; + } + + // Create the enum with specified size + EnumDataType newEnum = new EnumDataType(path, name, size, dtm); + + StringBuilder responseBuilder = new StringBuilder( + "Enum " + name + " created successfully in category " + path + " with size " + size + " bytes"); + + if (valuesJson != null && !valuesJson.isEmpty()) { + Gson gson = new Gson(); + EnumValue[] values = gson.fromJson(valuesJson, EnumValue[].class); + + int valuesAdded = 0; + for (EnumValue enumValue : values) { + if (enumValue.name == null || enumValue.name.isEmpty()) { + responseBuilder.append("\nError: Enum value name cannot be empty. Skipping value."); + continue; + } + + // Add the enum value with or without comment + if (enumValue.comment != null && !enumValue.comment.isEmpty()) { + newEnum.add(enumValue.name, (long) enumValue.value, enumValue.comment); + } else { + newEnum.add(enumValue.name, (long) enumValue.value); + } + valuesAdded++; + } + responseBuilder.append("\nAdded ").append(valuesAdded).append(" values."); + } + + dtm.addDataType(newEnum, DataTypeConflictHandler.DEFAULT_HANDLER); + result.set(responseBuilder.toString()); + success = true; + } catch (Exception e) { + result.set("Error: Failed to create enum: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute create enum on Swing thread: " + e.getMessage(); + } + return result.get(); + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/handlers/act/RemoveClassMembers.java b/src/main/java/com/lauriewired/handlers/act/RemoveClassMembers.java new file mode 100644 index 0000000..5016ec4 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/RemoveClassMembers.java @@ -0,0 +1,179 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.Structure; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.listing.GhidraClass; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Namespace; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolTable; +import ghidra.program.model.symbol.SymbolType; +import ghidra.program.database.data.DataTypeUtilities; + +import com.google.gson.Gson; + +import javax.swing.SwingUtilities; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for removing members from a C++ class in Ghidra. + * This modifies the class's associated structure data type. + * Expects a POST request with parameters: + * - class_name: Name of the class to modify + * - parent_namespace: Parent namespace where the class is located (optional) + * - members: JSON array of member names to remove, or single member name as string + */ +public final class RemoveClassMembers extends Handler { + /** + * Constructor for the RemoveClassMembers handler. + * + * @param tool The Ghidra plugin tool instance. + */ + public RemoveClassMembers(PluginTool tool) { + super(tool, "/remove_class_members"); + } + + /** + * Handles the HTTP request to remove members from a class. + * + * @param exchange The HTTP exchange containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String className = params.get("class_name"); + String parentNamespace = params.get("parent_namespace"); + String membersParam = params.get("members"); + + if (className == null || membersParam == null) { + sendResponse(exchange, "class_name and members are required"); + return; + } + sendResponse(exchange, removeClassMembers(className, parentNamespace, membersParam)); + } + + /** + * Removes members from a class in the current Ghidra program. + * + * @param className The name of the class to modify. + * @param parentNamespace The parent namespace where the class is located (optional). + * @param membersParam JSON array of member names to remove, or single member name. + * @return A message indicating success or failure. + */ + private String removeClassMembers(String className, String parentNamespace, String membersParam) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Remove Class Members"); + boolean success = false; + try { + SymbolTable symbolTable = program.getSymbolTable(); + DataTypeManager dtm = program.getDataTypeManager(); + + // Find the class namespace + Namespace parent = program.getGlobalNamespace(); + if (parentNamespace != null && !parentNamespace.isEmpty()) { + parent = symbolTable.getNamespace(parentNamespace, program.getGlobalNamespace()); + if (parent == null) { + result.set("Error: Parent namespace '" + parentNamespace + "' not found"); + return; + } + } + + // Find the class by iterating through symbols + GhidraClass classNamespace = null; + for (Symbol symbol : symbolTable.getSymbols(className, parent)) { + if (symbol.getSymbolType() == SymbolType.CLASS) { + classNamespace = (GhidraClass) symbol.getObject(); + break; + } + } + + if (classNamespace == null) { + result.set("Error: Class '" + className + "' not found" + + (parent != null ? " in namespace " + parent.getName() : "")); + return; + } + + // Find the associated structure + Structure classStruct = DataTypeUtilities.findExistingClassStruct(dtm, classNamespace); + if (classStruct == null) { + result.set("Error: No structure found for class '" + className + "'"); + return; + } + + StringBuilder responseBuilder = new StringBuilder( + "Removing members from class " + className); + + // Parse member names to remove + List memberNames = new ArrayList<>(); + try { + // Try to parse as JSON array first + Gson gson = new Gson(); + String[] names = gson.fromJson(membersParam, String[].class); + memberNames.addAll(Arrays.asList(names)); + } catch (Exception e) { + // If not JSON array, treat as single member name + memberNames.add(membersParam.trim()); + } + + int membersRemoved = 0; + for (String memberName : memberNames) { + DataTypeComponent component = null; + for (DataTypeComponent comp : classStruct.getComponents()) { + if (comp.getFieldName() != null && comp.getFieldName().equals(memberName)) { + component = comp; + break; + } + } + + if (component == null) { + responseBuilder.append("\nWarning: Member '").append(memberName) + .append("' not found in class. Skipping."); + continue; + } + + int ordinal = component.getOrdinal(); + classStruct.delete(ordinal); + responseBuilder.append("\nRemoved member '").append(memberName) + .append("' (ordinal ").append(ordinal).append(")"); + membersRemoved++; + } + + if (membersRemoved > 0) { + responseBuilder.append("\nSuccessfully removed ").append(membersRemoved) + .append(" members from class ").append(className); + success = true; + } else { + responseBuilder.append("\nNo members were removed from class ").append(className); + } + + result.set(responseBuilder.toString()); + + } catch (Exception e) { + result.set("Error: Failed to remove members from class: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute remove class members on Swing thread: " + e.getMessage(); + } + return result.get(); + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/handlers/act/RemoveEnumValues.java b/src/main/java/com/lauriewired/handlers/act/RemoveEnumValues.java new file mode 100644 index 0000000..218c1ae --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/RemoveEnumValues.java @@ -0,0 +1,154 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.EnumDataType; +import ghidra.program.model.listing.Program; + +import com.google.gson.Gson; + +import javax.swing.SwingUtilities; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.lauriewired.util.ParseUtils.*; +import ghidra.program.model.data.CategoryPath; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for removing values from an enum in Ghidra. + * Expects a POST request with parameters: + * - enum_name: Name of the enum to modify + * - category: Category path where the enum is located (optional) + * - values: JSON array of value names to remove, or single value name as string + */ +public final class RemoveEnumValues extends Handler { + /** + * Constructor for the RemoveEnumValues handler. + * + * @param tool The Ghidra plugin tool instance. + */ + public RemoveEnumValues(PluginTool tool) { + super(tool, "/remove_enum_values"); + } + + /** + * Handles the HTTP request to remove values from an enum. + * + * @param exchange The HTTP exchange containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String enumName = params.get("enum_name"); + String category = params.get("category"); + String valuesParam = params.get("values"); + + if (enumName == null || valuesParam == null) { + sendResponse(exchange, "enum_name and values are required"); + return; + } + sendResponse(exchange, removeEnumValues(enumName, category, valuesParam)); + } + + /** + * Removes values from an enum in the current Ghidra program. + * + * @param enumName The name of the enum to modify. + * @param category The category path where the enum is located (optional). + * @param valuesParam JSON array of value names to remove, or single value name. + * @return A message indicating success or failure. + */ + private String removeEnumValues(String enumName, String category, String valuesParam) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Remove Enum Values"); + boolean success = false; + try { + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath(category == null ? "/" : category); + DataType dt = dtm.getDataType(path, enumName); + + if (dt == null || !(dt instanceof EnumDataType)) { + result.set("Error: Enum " + enumName + " not found in category " + path); + return; + } + EnumDataType enumDt = (EnumDataType) dt; + + StringBuilder responseBuilder = new StringBuilder( + "Removing values from enum " + enumName); + + // Parse value names to remove + List valueNames = new ArrayList<>(); + try { + // Try to parse as JSON array first + Gson gson = new Gson(); + String[] names = gson.fromJson(valuesParam, String[].class); + valueNames.addAll(Arrays.asList(names)); + } catch (Exception e) { + // If not JSON array, treat as single value name + valueNames.add(valuesParam.trim()); + } + + int valuesRemoved = 0; + for (String valueName : valueNames) { + try { + // Check if value exists + boolean valueExists = false; + String[] enumValueNames = enumDt.getNames(); + for (String existingName : enumValueNames) { + if (existingName.equals(valueName)) { + valueExists = true; + break; + } + } + + if (!valueExists) { + responseBuilder.append("\nWarning: Value '").append(valueName) + .append("' not found in enum. Skipping."); + continue; + } + + // Remove the value + enumDt.remove(valueName); + responseBuilder.append("\nRemoved value '").append(valueName).append("'"); + valuesRemoved++; + } catch (Exception e) { + responseBuilder.append("\nError removing value '").append(valueName) + .append("': ").append(e.getMessage()); + } + } + + if (valuesRemoved > 0) { + responseBuilder.append("\nSuccessfully removed ").append(valuesRemoved) + .append(" values from enum ").append(enumName); + success = true; + } else { + responseBuilder.append("\nNo values were removed from enum ").append(enumName); + } + + result.set(responseBuilder.toString()); + + } catch (Exception e) { + result.set("Error: Failed to remove values from enum: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute remove enum values on Swing thread: " + e.getMessage(); + } + return result.get(); + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/handlers/act/RemoveStructMembers.java b/src/main/java/com/lauriewired/handlers/act/RemoveStructMembers.java new file mode 100644 index 0000000..b55ce63 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/RemoveStructMembers.java @@ -0,0 +1,149 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.Structure; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.listing.Program; + +import com.google.gson.Gson; + +import javax.swing.SwingUtilities; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.lauriewired.util.ParseUtils.*; +import ghidra.program.model.data.CategoryPath; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for removing members from a structure in Ghidra. + * Expects a POST request with parameters: + * - struct_name: Name of the structure to modify + * - category: Category path where the structure is located (optional) + * - members: JSON array of member names to remove, or single member name as string + */ +public final class RemoveStructMembers extends Handler { + /** + * Constructor for the RemoveStructMembers handler. + * + * @param tool The Ghidra plugin tool instance. + */ + public RemoveStructMembers(PluginTool tool) { + super(tool, "/remove_struct_members"); + } + + /** + * Handles the HTTP request to remove members from a structure. + * + * @param exchange The HTTP exchange containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String structName = params.get("struct_name"); + String category = params.get("category"); + String membersParam = params.get("members"); + + if (structName == null || membersParam == null) { + sendResponse(exchange, "struct_name and members are required"); + return; + } + sendResponse(exchange, removeStructMembers(structName, category, membersParam)); + } + + /** + * Removes members from a structure in the current Ghidra program. + * + * @param structName The name of the structure to modify. + * @param category The category path where the structure is located (optional). + * @param membersParam JSON array of member names to remove, or single member name. + * @return A message indicating success or failure. + */ + private String removeStructMembers(String structName, String category, String membersParam) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Remove Struct Members"); + boolean success = false; + try { + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath(category == null ? "/" : category); + DataType dt = dtm.getDataType(path, structName); + + if (dt == null || !(dt instanceof Structure)) { + result.set("Error: Struct " + structName + " not found in category " + path); + return; + } + Structure struct = (Structure) dt; + + StringBuilder responseBuilder = new StringBuilder( + "Removing members from struct " + structName); + + // Parse member names to remove + List memberNames = new ArrayList<>(); + try { + // Try to parse as JSON array first + Gson gson = new Gson(); + String[] names = gson.fromJson(membersParam, String[].class); + memberNames.addAll(Arrays.asList(names)); + } catch (Exception e) { + // If not JSON array, treat as single member name + memberNames.add(membersParam.trim()); + } + + int membersRemoved = 0; + for (String memberName : memberNames) { + DataTypeComponent component = null; + for (DataTypeComponent comp : struct.getComponents()) { + if (comp.getFieldName() != null && comp.getFieldName().equals(memberName)) { + component = comp; + break; + } + } + + if (component == null) { + responseBuilder.append("\nWarning: Member '").append(memberName) + .append("' not found in struct. Skipping."); + continue; + } + + int ordinal = component.getOrdinal(); + struct.delete(ordinal); + responseBuilder.append("\nRemoved member '").append(memberName) + .append("' (ordinal ").append(ordinal).append(")"); + membersRemoved++; + } + + if (membersRemoved > 0) { + responseBuilder.append("\nSuccessfully removed ").append(membersRemoved) + .append(" members from struct ").append(structName); + success = true; + } else { + responseBuilder.append("\nNo members were removed from struct ").append(structName); + } + + result.set(responseBuilder.toString()); + + } catch (Exception e) { + result.set("Error: Failed to remove members from struct: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute remove struct members on Swing thread: " + e.getMessage(); + } + return result.get(); + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/handlers/get/GetEnum.java b/src/main/java/com/lauriewired/handlers/get/GetEnum.java new file mode 100644 index 0000000..71f9ea7 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetEnum.java @@ -0,0 +1,111 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.Enum; +import ghidra.program.model.listing.Program; + +import com.google.gson.Gson; + +import java.io.IOException; +import java.util.*; + +import static com.lauriewired.util.ParseUtils.*; +import ghidra.program.model.data.CategoryPath; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for retrieving details of an enum by its name and category. + * Expects query parameters: name (required), category (optional). + */ +public final class GetEnum extends Handler { + /** + * Constructor for the GetEnum handler. + * + * @param tool the PluginTool instance to use for accessing the current program. + */ + public GetEnum(PluginTool tool) { + super(tool, "/get_enum"); + } + + /** + * Handles the HTTP request to retrieve enum details. + * + * @param exchange the HttpExchange object containing the request and response. + * @throws IOException if an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String enumName = qparams.get("name"); + String category = qparams.get("category"); + if (enumName == null) { + sendResponse(exchange, "name is required"); + return; + } + sendResponse(exchange, getEnum(enumName, category)); + } + + /** + * Retrieves the enum details as a JSON string. + * + * @param enumName the name of the enum to retrieve. + * @param category the category path where the enum is located + * (optional). + * @return a JSON representation of the enum or an error message if not + * found. + */ + private String getEnum(String enumName, String category) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath(category == null ? "/" : category); + DataType dt = dtm.getDataType(path, enumName); + + if (dt == null || !(dt instanceof Enum)) { + return "Error: Enum " + enumName + " not found in category " + path; + } + + Enum enumDt = (Enum) dt; + + Map enumRepr = new HashMap<>(); + enumRepr.put("name", enumDt.getName()); + enumRepr.put("category", enumDt.getCategoryPath().getPath()); + enumRepr.put("size", enumDt.getLength()); + enumRepr.put("count", enumDt.getCount()); + enumRepr.put("isSigned", enumDt.isSigned()); + enumRepr.put("description", enumDt.getDescription()); + + List> valuesList = new ArrayList<>(); + String[] names = enumDt.getNames(); + long[] values = enumDt.getValues(); + + // Create a map for quick lookup of values by name + Map nameToValue = new HashMap<>(); + for (int i = 0; i < names.length; i++) { + nameToValue.put(names[i], values[i]); + } + + // Build the values list + for (String name : names) { + Long value = nameToValue.get(name); + if (value != null) { + Map valueMap = new HashMap<>(); + valueMap.put("name", name); + valueMap.put("value", value); + String comment = enumDt.getComment(name); + valueMap.put("comment", comment != null ? comment : ""); + valuesList.add(valueMap); + } + } + enumRepr.put("values", valuesList); + + Gson gson = new Gson(); + return gson.toJson(enumRepr); + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/handlers/set/SetGlobalDataType.java b/src/main/java/com/lauriewired/handlers/set/SetGlobalDataType.java new file mode 100644 index 0000000..9d9e5e9 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/set/SetGlobalDataType.java @@ -0,0 +1,199 @@ +package com.lauriewired.handlers.set; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.DataUtilities; +import ghidra.program.model.data.DataUtilities.ClearDataMode; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; + +import javax.swing.SwingUtilities; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static com.lauriewired.util.GhidraUtils.resolveDataType; +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for setting the data type of a global variable or data at a specific address. + * This handler allows users to specify an address and the new data type they want to apply, + * effectively creating typed data at memory locations. + * + * Expects POST parameters: + * - address: The memory address where to set the data type + * - data_type: The name of the data type to apply + * - length: Optional length for dynamic data types (default: -1, let type determine) + * - clear_mode: Optional clearing mode (default: "CHECK_FOR_SPACE") + */ +public final class SetGlobalDataType extends Handler { + /** + * Constructor for the SetGlobalDataType handler. + * + * @param tool The PluginTool instance to use for accessing the current program. + */ + public SetGlobalDataType(PluginTool tool) { + super(tool, "/set_global_data_type"); + } + + /** + * Handles the HTTP request to set a global data type at a specific address. + * + * @param exchange The HttpExchange object containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String addressStr = params.get("address"); + String dataTypeName = params.get("data_type"); + String lengthStr = params.get("length"); + String clearModeStr = params.get("clear_mode"); + + if (addressStr == null || addressStr.isEmpty()) { + sendResponse(exchange, "Error: address parameter is required"); + return; + } + + if (dataTypeName == null || dataTypeName.isEmpty()) { + sendResponse(exchange, "Error: data_type parameter is required"); + return; + } + + int length = parseIntOrDefault(lengthStr, -1); + ClearDataMode clearMode = parseClearDataMode(clearModeStr); + + // Capture detailed information about setting the data type + StringBuilder responseMsg = new StringBuilder(); + responseMsg.append("Setting data type at address ").append(addressStr) + .append(" to type ").append(dataTypeName); + if (length > 0) { + responseMsg.append(" with length ").append(length); + } + responseMsg.append(" using clear mode ").append(clearMode).append("\n\n"); + + // Attempt to find the data type + Program program = getCurrentProgram(tool); + if (program != null) { + DataTypeManager dtm = program.getDataTypeManager(); + DataType dataType = resolveDataType(tool, dtm, dataTypeName); + if (dataType != null) { + responseMsg.append("Found data type: ").append(dataType.getPathName()).append("\n"); + } else { + responseMsg.append("Warning: Data type not found: ").append(dataTypeName).append("\n"); + } + } + + // Try to set the data type + String result = setGlobalDataType(addressStr, dataTypeName, length, clearMode); + responseMsg.append("\nResult: ").append(result); + + sendResponse(exchange, responseMsg.toString()); + } + + /** + * Sets the data type at the specified address. + * + * @param addressStr The address where to set the data type as a string. + * @param dataTypeName The name of the data type to apply. + * @param length The length for dynamic data types, or -1 to let the type determine. + * @param clearMode The clearing mode to use when conflicting data exists. + * @return A message indicating success or failure of the operation. + */ + private String setGlobalDataType(String addressStr, String dataTypeName, int length, ClearDataMode clearMode) { + Program program = getCurrentProgram(tool); + if (program == null) { + return "No program loaded"; + } + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Set Global Data Type"); + boolean success = false; + try { + // Parse the address + Address address = program.getAddressFactory().getAddress(addressStr); + if (address == null) { + result.set("Error: Invalid address format: " + addressStr); + return; + } + + // Resolve the data type + DataTypeManager dtm = program.getDataTypeManager(); + DataType dataType = resolveDataType(tool, dtm, dataTypeName); + if (dataType == null) { + result.set("Error: Could not resolve data type: " + dataTypeName); + return; + } + + Msg.info(this, "Setting data type " + dataType.getName() + " at address " + address); + + // Create the data using DataUtilities + Data newData = DataUtilities.createData(program, address, dataType, length, clearMode); + + if (newData != null) { + result.set("Successfully set data type '" + dataType.getName() + + "' at address " + address + ". Data length: " + newData.getLength() + " bytes."); + success = true; + } else { + result.set("Error: Failed to create data at address " + address); + } + + } catch (CodeUnitInsertionException e) { + result.set("Error: Could not insert data at address " + addressStr + + " - " + e.getMessage() + ". Try using a different clear_mode."); + } catch (Exception e) { + result.set("Error: Failed to set data type: " + e.getMessage()); + Msg.error(this, "Error setting global data type", e); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute set data type on Swing thread: " + e.getMessage(); + } + return result.get(); + } + + /** + * Parses the clear mode string into a ClearDataMode enum value. + * + * @param clearModeStr The clear mode string. + * @return The corresponding ClearDataMode enum value, or CHECK_FOR_SPACE as default. + */ + private ClearDataMode parseClearDataMode(String clearModeStr) { + if (clearModeStr == null || clearModeStr.isEmpty()) { + return ClearDataMode.CHECK_FOR_SPACE; + } + + try { + switch (clearModeStr.toUpperCase()) { + case "CHECK_FOR_SPACE": + return ClearDataMode.CHECK_FOR_SPACE; + case "CLEAR_SINGLE_DATA": + return ClearDataMode.CLEAR_SINGLE_DATA; + case "CLEAR_ALL_UNDEFINED_CONFLICT_DATA": + return ClearDataMode.CLEAR_ALL_UNDEFINED_CONFLICT_DATA; + case "CLEAR_ALL_DEFAULT_CONFLICT_DATA": + return ClearDataMode.CLEAR_ALL_DEFAULT_CONFLICT_DATA; + case "CLEAR_ALL_CONFLICT_DATA": + return ClearDataMode.CLEAR_ALL_CONFLICT_DATA; + default: + Msg.warn(this, "Unknown clear mode: " + clearModeStr + ", using CHECK_FOR_SPACE"); + return ClearDataMode.CHECK_FOR_SPACE; + } + } catch (Exception e) { + Msg.warn(this, "Error parsing clear mode: " + clearModeStr + ", using CHECK_FOR_SPACE"); + return ClearDataMode.CHECK_FOR_SPACE; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/util/EnumUtils.java b/src/main/java/com/lauriewired/util/EnumUtils.java new file mode 100644 index 0000000..e899f7f --- /dev/null +++ b/src/main/java/com/lauriewired/util/EnumUtils.java @@ -0,0 +1,28 @@ +package com.lauriewired.util; + +/** + * Utility class for handling enums and their values. + * This class provides a representation of an enum value with its name, + * value, and comment. + */ +public final class EnumUtils { + /** + * Represents a value of an enum. + */ + public static class EnumValue { + /** + * The name of the enum value. + */ + public String name; + + /** + * The numeric value of the enum entry. + */ + public double value = 0; // Use double to handle GSON parsing number as double + + /** + * The comment for the enum value. + */ + public String comment; + } +} \ No newline at end of file