diff --git a/bridge_mcp_ghidra.py b/bridge_mcp_ghidra.py index c492562d..b912d70a 100644 --- a/bridge_mcp_ghidra.py +++ b/bridge_mcp_ghidra.py @@ -228,12 +228,12 @@ def set_local_variable_type(function_address: str, variable_name: str, new_type: def get_xrefs_to(address: str, offset: int = 0, limit: int = 100) -> list: """ Get all references to the specified address (xref to). - + Args: address: Target address in hex format (e.g. "0x1400010a0") offset: Pagination offset (default: 0) limit: Maximum number of references to return (default: 100) - + Returns: List of references to the specified address """ @@ -243,12 +243,12 @@ def get_xrefs_to(address: str, offset: int = 0, limit: int = 100) -> list: def get_xrefs_from(address: str, offset: int = 0, limit: int = 100) -> list: """ Get all references from the specified address (xref from). - + Args: address: Source address in hex format (e.g. "0x1400010a0") offset: Pagination offset (default: 0) limit: Maximum number of references to return (default: 100) - + Returns: List of references from the specified address """ @@ -258,12 +258,12 @@ def get_xrefs_from(address: str, offset: int = 0, limit: int = 100) -> list: def get_function_xrefs(name: str, offset: int = 0, limit: int = 100) -> list: """ Get all references to the specified function by name. - + Args: name: Function name to search for offset: Pagination offset (default: 0) limit: Maximum number of references to return (default: 100) - + Returns: List of references to the specified function """ @@ -273,12 +273,12 @@ def get_function_xrefs(name: str, offset: int = 0, limit: int = 100) -> list: def list_strings(offset: int = 0, limit: int = 2000, filter: str = None) -> list: """ List all defined strings in the program with their addresses. - + Args: offset: Pagination offset (default: 0) limit: Maximum number of strings to return (default: 2000) filter: Optional filter to match within string content - + Returns: List of strings with their addresses """ @@ -287,6 +287,231 @@ def list_strings(offset: int = 0, limit: int = 2000, filter: str = None) -> list params["filter"] = filter return safe_get("strings", params) +@mcp.tool() +def search_variables(pattern: str, offset: int = 0, limit: int = 100) -> list: + """ + Search for variables across all functions whose name matches a pattern. + + Args: + pattern: The pattern to search for (e.g., "param_", "iVar", "local_") + offset: Pagination offset (default: 0) + limit: Maximum number of results to return (default: 100) + + Returns: + List of matches in format: "FunctionName @ address: variableName (type)" + + Note: This operation can be slow as it decompiles functions to find variables. + """ + if not pattern: + return ["Error: pattern string is required"] + return safe_get("searchVariables", {"pattern": pattern, "offset": offset, "limit": limit}) + + +# ============================================================================ +# Structure/Type Management Functions +# ============================================================================ + +@mcp.tool() +def create_struct(name: str, size: int = 0, category: str = None) -> str: + """ + Create a new structure data type. + + Args: + name: Name of the structure (e.g., "LFGCooldownNode") + size: Initial size in bytes (0 for undefined/growable structure) + category: Category path for the structure (e.g., "/WoW" or None for root) + + Returns: + Success message with structure path, or error message + + Example: + create_struct("SavedInstanceEntry", 32, "/WoW/LFG") + """ + data = {"name": name} + if size > 0: + data["size"] = str(size) + if category: + data["category"] = category + return safe_post("create_struct", data) + + +@mcp.tool() +def add_struct_field(struct_name: str, field_type: str, field_name: str, + offset: int = -1, field_size: int = 0) -> str: + """ + Add a field to an existing structure. + + Args: + struct_name: Name of the structure to modify + field_type: Data type of the field (e.g., "uint", "int", "char", "pointer") + field_name: Name for the new field + offset: Byte offset for the field (-1 to append at end) + field_size: Size override in bytes (0 to use type's natural size) + + Returns: + Success or error message + + Example: + add_struct_field("LFGCooldownNode", "uint", "dungeonId", offset=0x08) + add_struct_field("LFGCooldownNode", "pointer", "pNextNode", offset=0x04) + """ + data = { + "struct_name": struct_name, + "field_type": field_type, + "field_name": field_name, + "offset": str(offset) + } + if field_size > 0: + data["field_size"] = str(field_size) + return safe_post("add_struct_field", data) + + +@mcp.tool() +def list_structs(offset: int = 0, limit: int = 100, filter: str = None) -> list: + """ + List all structure types defined in the program. + + Args: + offset: Pagination offset (default: 0) + limit: Maximum number of structures to return (default: 100) + filter: Optional filter to match structure names + + Returns: + List of structure names with their sizes and field counts + """ + params = {"offset": offset, "limit": limit} + if filter: + params["filter"] = filter + return safe_get("list_structs", params) + + +@mcp.tool() +def get_struct(name: str) -> str: + """ + Get detailed information about a structure including all fields. + + Args: + name: Name of the structure + + Returns: + Structure details with all fields, offsets, types, and sizes + + Example output: + Structure: LFGCooldownNode + Path: /WoW/LFG/LFGCooldownNode + Size: 24 bytes + Fields (5): + +0x0000: uint flags (size: 4) + +0x0004: pointer pNextNode (size: 4) + +0x0008: uint dungeonId (size: 4) + ... + """ + return "\n".join(safe_get("get_struct", {"name": name})) + + +@mcp.tool() +def apply_struct_at_address(address: str, struct_name: str) -> str: + """ + Apply a structure type at a specific memory address. + + Args: + address: Memory address in hex format (e.g., "0x00acdc00") + struct_name: Name of the structure to apply + + Returns: + Success or error message + + Example: + apply_struct_at_address("0x00bcfae8", "SavedInstanceArray") + """ + return safe_post("apply_struct_at_address", { + "address": address, + "struct_name": struct_name + }) + + +@mcp.tool() +def create_enum(name: str, size: int = 4, category: str = None) -> str: + """ + Create a new enum data type. + + Args: + name: Name of the enum (e.g., "LFG_LOCKSTATUS") + size: Size in bytes (1, 2, 4, or 8; default: 4) + category: Category path for the enum (e.g., "/WoW/Enums") + + Returns: + Success message with enum path, or error message + + Example: + create_enum("LFG_LOCKSTATUS", 4, "/WoW/LFG") + """ + data = {"name": name, "size": str(size)} + if category: + data["category"] = category + return safe_post("create_enum", data) + + +@mcp.tool() +def add_enum_value(enum_name: str, value_name: str, value: int) -> str: + """ + Add a value to an existing enum. + + Args: + enum_name: Name of the enum to modify + value_name: Name for the enum value + value: Numeric value + + Returns: + Success or error message + + Example: + add_enum_value("LFG_LOCKSTATUS", "NOT_LOCKED", 0) + add_enum_value("LFG_LOCKSTATUS", "TOO_LOW_LEVEL", 1) + add_enum_value("LFG_LOCKSTATUS", "RAID_LOCKED", 6) + """ + return safe_post("add_enum_value", { + "enum_name": enum_name, + "value_name": value_name, + "value": str(value) + }) + + +@mcp.tool() +def list_types(offset: int = 0, limit: int = 100, category: str = None) -> list: + """ + List all data types, optionally filtered by category. + + Args: + offset: Pagination offset (default: 0) + limit: Maximum number of types to return (default: 100) + category: Category path to list (e.g., "/WoW" or None for all) + + Returns: + List of type names with their kinds and sizes + """ + params = {"offset": offset, "limit": limit} + if category: + params["category"] = category + return safe_get("list_types", params) + + +@mcp.tool() +def delete_struct(name: str) -> str: + """ + Delete a structure or other data type. + + Args: + name: Name of the structure/type to delete + + Returns: + Success or error message + + Warning: This will fail if the type is in use elsewhere. + """ + return safe_post("delete_struct", {"name": name}) + + def main(): parser = argparse.ArgumentParser(description="MCP server for Ghidra") parser.add_argument("--ghidra-server", type=str, default=DEFAULT_GHIDRA_SERVER, @@ -298,12 +523,12 @@ def main(): parser.add_argument("--transport", type=str, default="stdio", choices=["stdio", "sse"], help="Transport protocol for MCP, default: stdio") args = parser.parse_args() - + # Use the global variable to ensure it's properly updated global ghidra_server_url if args.ghidra_server: ghidra_server_url = args.ghidra_server - + if args.transport == "sse": try: # Set up logging @@ -332,7 +557,6 @@ def main(): logger.info("Server stopped by user") else: mcp.run() - + if __name__ == "__main__": main() - diff --git a/src/main/java/com/lauriewired/GhidraMCPPlugin.java b/src/main/java/com/lauriewired/GhidraMCPPlugin.java index 88583bab..a1836c35 100644 --- a/src/main/java/com/lauriewired/GhidraMCPPlugin.java +++ b/src/main/java/com/lauriewired/GhidraMCPPlugin.java @@ -40,6 +40,11 @@ import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.PointerDataType; import ghidra.program.model.data.Undefined1DataType; +import ghidra.program.model.data.StructureDataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.data.EnumDataType; +import ghidra.program.model.data.CategoryPath; +import ghidra.program.model.data.Category; import ghidra.program.model.listing.Variable; import ghidra.app.decompiler.component.DecompilerUtils; import ghidra.app.decompiler.ClangToken; @@ -341,6 +346,87 @@ private void startServer() throws IOException { sendResponse(exchange, listDefinedStrings(offset, limit, filter)); }); + server.createContext("/searchVariables", exchange -> { + Map qparams = parseQueryParams(exchange); + String pattern = qparams.get("pattern"); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, searchVariablesByPattern(pattern, offset, limit)); + }); + + // ---------------------------------------------------------------------------------- + // Structure/Type Management Endpoints + // ---------------------------------------------------------------------------------- + + server.createContext("/create_struct", exchange -> { + Map params = parsePostParams(exchange); + String name = params.get("name"); + int size = parseIntOrDefault(params.get("size"), 0); + String category = params.get("category"); + sendResponse(exchange, createStructure(name, size, category)); + }); + + server.createContext("/add_struct_field", exchange -> { + Map params = parsePostParams(exchange); + String structName = params.get("struct_name"); + int offset = parseIntOrDefault(params.get("offset"), -1); + String fieldType = params.get("field_type"); + String fieldName = params.get("field_name"); + int fieldSize = parseIntOrDefault(params.get("field_size"), 0); + sendResponse(exchange, addStructField(structName, offset, fieldType, fieldName, fieldSize)); + }); + + server.createContext("/list_structs", exchange -> { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + String filter = qparams.get("filter"); + sendResponse(exchange, listStructures(offset, limit, filter)); + }); + + server.createContext("/get_struct", exchange -> { + Map qparams = parseQueryParams(exchange); + String name = qparams.get("name"); + sendResponse(exchange, getStructureDetails(name)); + }); + + server.createContext("/apply_struct_at_address", exchange -> { + Map params = parsePostParams(exchange); + String address = params.get("address"); + String structName = params.get("struct_name"); + sendResponse(exchange, applyStructureAtAddress(address, structName)); + }); + + server.createContext("/create_enum", exchange -> { + Map params = parsePostParams(exchange); + String name = params.get("name"); + int size = parseIntOrDefault(params.get("size"), 4); + String category = params.get("category"); + sendResponse(exchange, createEnum(name, size, category)); + }); + + server.createContext("/add_enum_value", exchange -> { + Map params = parsePostParams(exchange); + String enumName = params.get("enum_name"); + String valueName = params.get("value_name"); + long value = Long.parseLong(params.getOrDefault("value", "0")); + sendResponse(exchange, addEnumValue(enumName, valueName, value)); + }); + + server.createContext("/list_types", exchange -> { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + String category = qparams.get("category"); + sendResponse(exchange, listDataTypes(offset, limit, category)); + }); + + server.createContext("/delete_struct", exchange -> { + Map params = parsePostParams(exchange); + String name = params.get("name"); + sendResponse(exchange, deleteStructure(name)); + }); + server.setExecutor(null); new Thread(() -> { try { @@ -590,77 +676,106 @@ private String renameVariableInFunction(String functionName, String oldVarName, } if (func == null) { + decomp.dispose(); return "Function not found"; } DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor()); if (result == null || !result.decompileCompleted()) { + decomp.dispose(); return "Decompilation failed"; } HighFunction highFunction = result.getHighFunction(); if (highFunction == null) { + decomp.dispose(); return "Decompilation failed (no high function)"; } LocalSymbolMap localSymbolMap = highFunction.getLocalSymbolMap(); if (localSymbolMap == null) { + decomp.dispose(); return "Decompilation failed (no local symbol map)"; } + // Collect all variable names and find target variable HighSymbol highSymbol = null; + List availableVars = new ArrayList<>(); Iterator symbols = localSymbolMap.getSymbols(); while (symbols.hasNext()) { HighSymbol symbol = symbols.next(); String symbolName = symbol.getName(); - + availableVars.add(symbolName); + if (symbolName.equals(oldVarName)) { highSymbol = symbol; } if (symbolName.equals(newVarName)) { + decomp.dispose(); return "Error: A variable with name '" + newVarName + "' already exists in this function"; } } if (highSymbol == null) { - return "Variable not found"; + decomp.dispose(); + // Provide helpful error message with available variable names + String availableList = availableVars.isEmpty() ? "(none)" : String.join(", ", availableVars); + return "Variable '" + oldVarName + "' not found. Available variables: " + availableList; } boolean commitRequired = checkFullCommit(highSymbol, highFunction); final HighSymbol finalHighSymbol = highSymbol; + final HighFunction finalHighFunction = highFunction; final Function finalFunction = func; + StringBuilder errorMsg = new StringBuilder(); AtomicBoolean successFlag = new AtomicBoolean(false); try { - SwingUtilities.invokeAndWait(() -> { + SwingUtilities.invokeAndWait(() -> { int tx = program.startTransaction("Rename variable"); + boolean success = false; try { + // First commit the decompiler's locals to the database if needed + // This ensures auto-generated names like uVar1, local_10 are available if (commitRequired) { - HighFunctionDBUtil.commitParamsToDatabase(highFunction, false, + HighFunctionDBUtil.commitParamsToDatabase(finalHighFunction, false, ReturnCommitOption.NO_COMMIT, finalFunction.getSignatureSource()); } + // Always commit local names to make sure variable exists in DB + HighFunctionDBUtil.commitLocalNamesToDatabase(finalHighFunction, SourceType.USER_DEFINED); + HighFunctionDBUtil.updateDBVariable( finalHighSymbol, newVarName, null, SourceType.USER_DEFINED ); - successFlag.set(true); + success = true; } catch (Exception e) { + errorMsg.append(e.getMessage()); Msg.error(this, "Failed to rename variable", e); } finally { - successFlag.set(program.endTransaction(tx, true)); + program.endTransaction(tx, success); + successFlag.set(success); } }); } catch (InterruptedException | InvocationTargetException e) { - String errorMsg = "Failed to execute rename on Swing thread: " + e.getMessage(); - Msg.error(this, errorMsg, e); - return errorMsg; + decomp.dispose(); + String errStr = "Failed to execute rename on Swing thread: " + e.getMessage(); + Msg.error(this, errStr, e); + return errStr; + } + + decomp.dispose(); + + if (successFlag.get()) { + return "Variable renamed"; + } else { + return "Failed to rename variable" + (errorMsg.length() > 0 ? ": " + errorMsg.toString() : ""); } - return successFlag.get() ? "Variable renamed" : "Failed to rename variable"; } /** @@ -1389,12 +1504,87 @@ private boolean isStringData(Data data) { return typeName.contains("string") || typeName.contains("char") || typeName.equals("unicode"); } + /** + * Search for variables across all functions whose name matches a pattern + * @param pattern The pattern to search for (e.g., "param_", "iVar", "local_") + * @param offset Pagination offset + * @param limit Maximum number of results + * @return List of matches in format: "FunctionName: variableName (type)" + */ + private String searchVariablesByPattern(String pattern, int offset, int limit) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + if (pattern == null || pattern.isEmpty()) return "Pattern is required"; + + List matches = new ArrayList<>(); + DecompInterface decomp = new DecompInterface(); + decomp.openProgram(program); + + // Convert pattern to lowercase for case-insensitive matching + String lowerPattern = pattern.toLowerCase(); + + // Iterate through all functions + for (Function func : program.getFunctionManager().getFunctions(true)) { + try { + // Decompile the function (with short timeout to avoid hanging) + DecompileResults result = decomp.decompileFunction(func, 10, new ConsoleTaskMonitor()); + if (result == null || !result.decompileCompleted()) { + continue; + } + + HighFunction highFunction = result.getHighFunction(); + if (highFunction == null) { + continue; + } + + LocalSymbolMap localSymbolMap = highFunction.getLocalSymbolMap(); + if (localSymbolMap == null) { + continue; + } + + // Iterate through all variables in this function + Iterator symbols = localSymbolMap.getSymbols(); + while (symbols.hasNext()) { + HighSymbol symbol = symbols.next(); + String varName = symbol.getName(); + + // Check if variable name contains the pattern (case-insensitive) + if (varName.toLowerCase().contains(lowerPattern)) { + String typeName = "unknown"; + HighVariable highVar = symbol.getHighVariable(); + if (highVar != null && highVar.getDataType() != null) { + typeName = highVar.getDataType().getName(); + } + + matches.add(String.format("%s @ %s: %s (%s)", + func.getName(), + func.getEntryPoint(), + varName, + typeName)); + } + } + } catch (Exception e) { + // Skip functions that fail to decompile + continue; + } + } + + decomp.dispose(); + + if (matches.isEmpty()) { + return "No variables matching pattern '" + pattern + "'"; + } + + Collections.sort(matches); + return paginateList(matches, offset, limit); + } + /** * Escape special characters in a string for display */ private String escapeString(String input) { if (input == null) return ""; - + StringBuilder sb = new StringBuilder(); for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); @@ -1413,6 +1603,464 @@ private String escapeString(String input) { return sb.toString(); } + // ---------------------------------------------------------------------------------- + // Structure/Type Management Methods + // ---------------------------------------------------------------------------------- + + /** + * Create a new structure with the given name and size + * @param name Structure name + * @param size Initial size in bytes (0 for undefined/growable) + * @param categoryPath Category path (e.g., "/MyStructs" or null for root) + * @return Success or error message + */ + private String createStructure(String name, int size, String categoryPath) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + if (name == null || name.isEmpty()) return "Structure name is required"; + + AtomicBoolean success = new AtomicBoolean(false); + StringBuilder result = new StringBuilder(); + + try { + SwingUtilities.invokeAndWait(() -> { + int tx = program.startTransaction("Create structure"); + try { + DataTypeManager dtm = program.getDataTypeManager(); + + // Check if structure already exists + DataType existing = findDataTypeByNameInAllCategories(dtm, name); + if (existing != null) { + result.append("Structure '").append(name).append("' already exists at ").append(existing.getPathName()); + return; + } + + // Create category path if specified + CategoryPath catPath = CategoryPath.ROOT; + if (categoryPath != null && !categoryPath.isEmpty()) { + catPath = new CategoryPath(categoryPath); + } + + // Create the structure + StructureDataType struct = new StructureDataType(catPath, name, size, dtm); + + // Add to data type manager + DataType resolved = dtm.addDataType(struct, null); + + result.append("Created structure '").append(name).append("' at ").append(resolved.getPathName()); + success.set(true); + } catch (Exception e) { + result.append("Error creating structure: ").append(e.getMessage()); + Msg.error(this, "Error creating structure", e); + } finally { + program.endTransaction(tx, success.get()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Failed to execute on Swing thread: " + e.getMessage(); + } + + return result.toString(); + } + + /** + * Add a field to an existing structure + * @param structName Name of the structure + * @param offset Offset in bytes (-1 to append) + * @param fieldTypeName Data type name for the field + * @param fieldName Name of the field + * @param fieldSize Size override (0 to use type's natural size) + * @return Success or error message + */ + private String addStructField(String structName, int offset, String fieldTypeName, String fieldName, int fieldSize) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + if (structName == null || structName.isEmpty()) return "Structure name is required"; + if (fieldTypeName == null || fieldTypeName.isEmpty()) return "Field type is required"; + + AtomicBoolean success = new AtomicBoolean(false); + StringBuilder result = new StringBuilder(); + + try { + SwingUtilities.invokeAndWait(() -> { + int tx = program.startTransaction("Add struct field"); + try { + DataTypeManager dtm = program.getDataTypeManager(); + + // Find the structure + DataType dt = findDataTypeByNameInAllCategories(dtm, structName); + if (dt == null) { + result.append("Structure '").append(structName).append("' not found"); + return; + } + + if (!(dt instanceof Structure)) { + result.append("'").append(structName).append("' is not a structure"); + return; + } + + Structure struct = (Structure) dt; + + // Find the field type + DataType fieldType = resolveDataType(dtm, fieldTypeName); + if (fieldType == null) { + result.append("Field type '").append(fieldTypeName).append("' not found"); + return; + } + + // Use field size if provided, otherwise use type's size + int actualSize = fieldSize > 0 ? fieldSize : fieldType.getLength(); + + // Add the field + if (offset < 0) { + // Append to end + struct.add(fieldType, actualSize, fieldName, null); + result.append("Added field '").append(fieldName).append("' at end of ").append(structName); + } else { + // Insert at specific offset + struct.replaceAtOffset(offset, fieldType, actualSize, fieldName, null); + result.append("Added field '").append(fieldName).append("' at offset ").append(offset).append(" in ").append(structName); + } + + success.set(true); + } catch (Exception e) { + result.append("Error adding field: ").append(e.getMessage()); + Msg.error(this, "Error adding struct field", e); + } finally { + program.endTransaction(tx, success.get()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Failed to execute on Swing thread: " + e.getMessage(); + } + + return result.toString(); + } + + /** + * List all structures in the program + * @param offset Pagination offset + * @param limit Maximum results + * @param filter Optional name filter + * @return List of structure names + */ + private String listStructures(int offset, int limit, String filter) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + + List structures = new ArrayList<>(); + DataTypeManager dtm = program.getDataTypeManager(); + + Iterator allTypes = dtm.getAllDataTypes(); + while (allTypes.hasNext()) { + DataType dt = allTypes.next(); + if (dt instanceof Structure) { + Structure struct = (Structure) dt; + String entry = String.format("%s (size: %d, fields: %d) at %s", + struct.getName(), + struct.getLength(), + struct.getNumComponents(), + struct.getPathName()); + + if (filter == null || struct.getName().toLowerCase().contains(filter.toLowerCase())) { + structures.add(entry); + } + } + } + + Collections.sort(structures); + return paginateList(structures, offset, limit); + } + + /** + * Get detailed information about a structure + * @param name Structure name + * @return Structure details including all fields + */ + private String getStructureDetails(String name) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + if (name == null || name.isEmpty()) return "Structure name is required"; + + DataTypeManager dtm = program.getDataTypeManager(); + DataType dt = findDataTypeByNameInAllCategories(dtm, name); + + if (dt == null) { + return "Structure '" + name + "' not found"; + } + + if (!(dt instanceof Structure)) { + return "'" + name + "' is not a structure (type: " + dt.getClass().getSimpleName() + ")"; + } + + Structure struct = (Structure) dt; + StringBuilder sb = new StringBuilder(); + + sb.append("Structure: ").append(struct.getName()).append("\n"); + sb.append("Path: ").append(struct.getPathName()).append("\n"); + sb.append("Size: ").append(struct.getLength()).append(" bytes\n"); + sb.append("Alignment: ").append(struct.getAlignment()).append("\n"); + sb.append("Fields (").append(struct.getNumComponents()).append("):\n"); + + for (int i = 0; i < struct.getNumComponents(); i++) { + ghidra.program.model.data.DataTypeComponent comp = struct.getComponent(i); + sb.append(String.format(" +0x%04X: %s %s (size: %d)\n", + comp.getOffset(), + comp.getDataType().getName(), + comp.getFieldName() != null ? comp.getFieldName() : "(unnamed)", + comp.getLength())); + } + + return sb.toString(); + } + + /** + * Apply a structure type at a specific address + * @param addressStr Address to apply structure + * @param structName Name of the structure to apply + * @return Success or error message + */ + private String applyStructureAtAddress(String addressStr, String structName) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + if (addressStr == null || addressStr.isEmpty()) return "Address is required"; + if (structName == null || structName.isEmpty()) return "Structure name is required"; + + AtomicBoolean success = new AtomicBoolean(false); + StringBuilder result = new StringBuilder(); + + try { + SwingUtilities.invokeAndWait(() -> { + int tx = program.startTransaction("Apply structure"); + try { + DataTypeManager dtm = program.getDataTypeManager(); + Address addr = program.getAddressFactory().getAddress(addressStr); + + // Find the structure + DataType dt = findDataTypeByNameInAllCategories(dtm, structName); + if (dt == null) { + result.append("Structure '").append(structName).append("' not found"); + return; + } + + // Clear any existing data at the address + Listing listing = program.getListing(); + listing.clearCodeUnits(addr, addr.add(dt.getLength() - 1), false); + + // Apply the data type + listing.createData(addr, dt); + + result.append("Applied '").append(structName).append("' at ").append(addressStr); + success.set(true); + } catch (Exception e) { + result.append("Error applying structure: ").append(e.getMessage()); + Msg.error(this, "Error applying structure", e); + } finally { + program.endTransaction(tx, success.get()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Failed to execute on Swing thread: " + e.getMessage(); + } + + return result.toString(); + } + + /** + * Create a new enum type + * @param name Enum name + * @param size Size in bytes (1, 2, 4, or 8) + * @param categoryPath Category path or null for root + * @return Success or error message + */ + private String createEnum(String name, int size, String categoryPath) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + if (name == null || name.isEmpty()) return "Enum name is required"; + + AtomicBoolean success = new AtomicBoolean(false); + StringBuilder result = new StringBuilder(); + + try { + SwingUtilities.invokeAndWait(() -> { + int tx = program.startTransaction("Create enum"); + try { + DataTypeManager dtm = program.getDataTypeManager(); + + // Check if enum already exists + DataType existing = findDataTypeByNameInAllCategories(dtm, name); + if (existing != null) { + result.append("Type '").append(name).append("' already exists at ").append(existing.getPathName()); + return; + } + + CategoryPath catPath = CategoryPath.ROOT; + if (categoryPath != null && !categoryPath.isEmpty()) { + catPath = new CategoryPath(categoryPath); + } + + EnumDataType enumType = new EnumDataType(catPath, name, size, dtm); + DataType resolved = dtm.addDataType(enumType, null); + + result.append("Created enum '").append(name).append("' at ").append(resolved.getPathName()); + success.set(true); + } catch (Exception e) { + result.append("Error creating enum: ").append(e.getMessage()); + Msg.error(this, "Error creating enum", e); + } finally { + program.endTransaction(tx, success.get()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Failed to execute on Swing thread: " + e.getMessage(); + } + + return result.toString(); + } + + /** + * Add a value to an existing enum + * @param enumName Name of the enum + * @param valueName Name of the enum value + * @param value Numeric value + * @return Success or error message + */ + private String addEnumValue(String enumName, String valueName, long value) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + if (enumName == null || enumName.isEmpty()) return "Enum name is required"; + if (valueName == null || valueName.isEmpty()) return "Value name is required"; + + AtomicBoolean success = new AtomicBoolean(false); + StringBuilder result = new StringBuilder(); + + try { + SwingUtilities.invokeAndWait(() -> { + int tx = program.startTransaction("Add enum value"); + try { + DataTypeManager dtm = program.getDataTypeManager(); + + DataType dt = findDataTypeByNameInAllCategories(dtm, enumName); + if (dt == null) { + result.append("Enum '").append(enumName).append("' not found"); + return; + } + + if (!(dt instanceof ghidra.program.model.data.Enum)) { + result.append("'").append(enumName).append("' is not an enum"); + return; + } + + ghidra.program.model.data.Enum enumType = (ghidra.program.model.data.Enum) dt; + enumType.add(valueName, value); + + result.append("Added '").append(valueName).append("' = ").append(value).append(" to ").append(enumName); + success.set(true); + } catch (Exception e) { + result.append("Error adding enum value: ").append(e.getMessage()); + Msg.error(this, "Error adding enum value", e); + } finally { + program.endTransaction(tx, success.get()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Failed to execute on Swing thread: " + e.getMessage(); + } + + return result.toString(); + } + + /** + * List all data types in a category + * @param offset Pagination offset + * @param limit Maximum results + * @param categoryPath Category to list (null for all) + * @return List of type names + */ + private String listDataTypes(int offset, int limit, String categoryPath) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + + List types = new ArrayList<>(); + DataTypeManager dtm = program.getDataTypeManager(); + + if (categoryPath != null && !categoryPath.isEmpty()) { + // List types in specific category + CategoryPath catPath = new CategoryPath(categoryPath); + Category cat = dtm.getCategory(catPath); + if (cat == null) { + return "Category '" + categoryPath + "' not found"; + } + + for (DataType dt : cat.getDataTypes()) { + types.add(String.format("%s (%s) - size: %d", + dt.getName(), + dt.getClass().getSimpleName().replace("DataType", ""), + dt.getLength())); + } + } else { + // List all types + Iterator allTypes = dtm.getAllDataTypes(); + while (allTypes.hasNext()) { + DataType dt = allTypes.next(); + types.add(String.format("%s (%s) at %s", + dt.getName(), + dt.getClass().getSimpleName().replace("DataType", ""), + dt.getPathName())); + } + } + + Collections.sort(types); + return paginateList(types, offset, limit); + } + + /** + * Delete a structure + * @param name Structure name + * @return Success or error message + */ + private String deleteStructure(String name) { + Program program = getCurrentProgram(); + if (program == null) return "No program loaded"; + if (name == null || name.isEmpty()) return "Structure name is required"; + + AtomicBoolean success = new AtomicBoolean(false); + StringBuilder result = new StringBuilder(); + + try { + SwingUtilities.invokeAndWait(() -> { + int tx = program.startTransaction("Delete structure"); + try { + DataTypeManager dtm = program.getDataTypeManager(); + + DataType dt = findDataTypeByNameInAllCategories(dtm, name); + if (dt == null) { + result.append("Structure '").append(name).append("' not found"); + return; + } + + dtm.remove(dt, new ConsoleTaskMonitor()); + + result.append("Deleted '").append(name).append("'"); + success.set(true); + } catch (Exception e) { + result.append("Error deleting structure: ").append(e.getMessage()); + Msg.error(this, "Error deleting structure", e); + } finally { + program.endTransaction(tx, success.get()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Failed to execute on Swing thread: " + e.getMessage(); + } + + return result.toString(); + } + + // ---------------------------------------------------------------------------------- + // End Structure/Type Management Methods + // ---------------------------------------------------------------------------------- + /** * Resolves a data type by name, handling common types and pointer types * @param dtm The data type manager