Skip to main content
Glama
EnhancedDisassemblyTest.java59.3 kB
package com.ghidramcp.services; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; import static org.junit.jupiter.api.Assertions.*; /** * Unit tests for enhanced disassembly output format. * * These tests verify that the disassemble_function output includes all the * comprehensive Ghidra-style information: * - PLATE comment boxes * - Function signatures with calling conventions * - Local variables table with XREFs * - Function labels with caller XREFs * - Enhanced assembly with annotations, labels, and XREFs * * Note: Full integration tests with actual Ghidra decompilation would require * the Ghidra test framework and Program fixtures. These tests verify output * format and structure. */ class EnhancedDisassemblyTest { // ==================== PLATE Comment Box Tests ==================== @Test @DisplayName("PLATE comment should be enclosed in asterisk box") void testPlateCommentBoxFormat() { String samplePlateComment = "CODE_212: Combat - AI Decision Loop (Core AI Logic)\n" + "\n" + "Executes complete AI decision-making for NPC/enemy turn."; // Verify it has the box structure String[] lines = samplePlateComment.split("\n"); int maxLength = 0; for (String line : lines) { maxLength = Math.max(maxLength, line.length()); } maxLength = Math.max(maxLength, 60); // Expected format: // ************************************************************* // * CODE_212: Combat - AI Decision Loop (Core AI Logic) * // * * // * Executes complete AI decision-making for NPC/enemy turn. * // ************************************************************* assertTrue(maxLength >= 60, "PLATE comment box should have minimum width of 60"); assertTrue(lines.length > 0, "PLATE comment should have content"); } @Test @DisplayName("PLATE comment box should pad lines to consistent width") void testPlateCommentPadding() { String shortLine = "Short"; String longLine = "This is a much longer line that should determine the box width"; String[] lines = new String[]{shortLine, longLine}; int maxLength = 0; for (String line : lines) { maxLength = Math.max(maxLength, line.length()); } // All lines should be padded to the same length assertTrue(maxLength > shortLine.length(), "Max length should be based on longest line"); assertEquals(longLine.length(), maxLength, "Max length should equal longest line"); } // ==================== Register Assumptions Tests ==================== @Test @DisplayName("Register assumptions should be displayed when present") void testRegisterAssumptions() { String sampleAssumptions = " assume CS = 0x2a0a\n" + " assume DS = 0x5356"; assertTrue(sampleAssumptions.contains("assume CS"), "Should show CS register assumption"); assertTrue(sampleAssumptions.contains("assume DS"), "Should show DS register assumption"); assertTrue(sampleAssumptions.contains("0x2a0a"), "Should show hex value for CS"); assertTrue(sampleAssumptions.contains("0x5356"), "Should show hex value for DS"); } @Test @DisplayName("Register assumptions should use proper indentation") void testRegisterAssumptionsIndentation() { String assumptionLine = " assume CS = 0x2a0a"; // Should have 31 spaces of indentation (matching function signature indentation) assertTrue(assumptionLine.startsWith(" "), "Register assumptions should have 31 spaces of indentation"); } @Test @DisplayName("Register assumptions should be formatted with equals sign") void testRegisterAssumptionsFormat() { String assumptionLine = "assume CS = 0x2a0a"; assertTrue(assumptionLine.matches("assume [A-Z]+ = 0x[0-9a-f]+"), "Register assumption should match format: assume <REG> = 0x<hex>"); } // ==================== Function Signature Tests ==================== @Test @DisplayName("Function signature should include return type") void testFunctionSignatureReturnType() { String expectedPattern = "uint16_t"; // Function signature format: <return_type> [__calling_convention] [namespace::]name(params) String sampleSignature = "uint16_t __cdecl16far CODE_212::Combat_AIDecisionLoop(pointer16 charIndexPtr)"; assertTrue(sampleSignature.contains(expectedPattern), "Function signature should start with return type"); } @Test @DisplayName("Function signature should include calling convention when not default") void testFunctionSignatureCallingConvention() { String callingConvention = "__cdecl16far"; String sampleSignature = "uint16_t __cdecl16far CODE_212::Combat_AIDecisionLoop(pointer16 charIndexPtr)"; assertTrue(sampleSignature.contains(callingConvention), "Function signature should include calling convention"); } @Test @DisplayName("Function signature should include namespace") void testFunctionSignatureNamespace() { String namespace = "CODE_212::"; String sampleSignature = "uint16_t __cdecl16far CODE_212::Combat_AIDecisionLoop(pointer16 charIndexPtr)"; assertTrue(sampleSignature.contains(namespace), "Function signature should include namespace with :: separator"); } @Test @DisplayName("Function signature should include parameter types and names") void testFunctionSignatureParameters() { String paramPattern = "pointer16 charIndexPtr"; String sampleSignature = "uint16_t __cdecl16far Combat_AIDecisionLoop(pointer16 charIndexPtr)"; assertTrue(sampleSignature.contains(paramPattern), "Function signature should include parameter type and name"); assertTrue(sampleSignature.contains("(") && sampleSignature.contains(")"), "Function signature should have parentheses for parameters"); } @Test @DisplayName("Function signature with multiple parameters should use comma separator") void testFunctionSignatureMultipleParameters() { String sampleSignature = "void myFunc(int param1, char* param2, uint16_t param3)"; assertTrue(sampleSignature.contains(", "), "Multiple parameters should be comma-separated"); // Count commas - should be paramCount - 1 long commaCount = sampleSignature.chars().filter(ch -> ch == ',').count(); assertEquals(2, commaCount, "Should have 2 commas for 3 parameters"); } // ==================== Local Variables Table Tests ==================== @Test @DisplayName("Local variables should display data type, storage, and name") void testLocalVariableTableFormat() { // Expected format: <dataType> <storage> <varName> [XREF[n]: addresses] String sampleVar = "undefined Stack[-0x4] local_4 XREF[1]: 618c:0bc6(*)"; assertTrue(sampleVar.contains("undefined"), "Should include data type"); assertTrue(sampleVar.contains("Stack"), "Should include storage location"); assertTrue(sampleVar.contains("local_4"), "Should include variable name"); } @Test @DisplayName("Local variables should show XREFs when present") void testLocalVariableXREFs() { String sampleVarWithXref = "undefined Stack[-0x4] local_4 XREF[1]: 618c:0bc6(*)"; assertTrue(sampleVarWithXref.contains("XREF["), "Should show XREF indicator"); assertTrue(sampleVarWithXref.matches(".*XREF\\[\\d+\\]:.*"), "Should show XREF count"); assertTrue(sampleVarWithXref.contains("618c:0bc6"), "Should show XREF address"); } @Test @DisplayName("Local variables with multiple XREFs should show multiple addresses") void testLocalVariableMultipleXREFs() { String sampleVar = "undefined Stack[-0xe] local_e XREF[2]: 618c:1067(*), 618c:1202(*)"; assertTrue(sampleVar.contains("XREF[2]"), "Should show correct XREF count"); assertTrue(sampleVar.contains("618c:1067"), "Should show first XREF"); assertTrue(sampleVar.contains("618c:1202"), "Should show second XREF"); assertTrue(sampleVar.contains(", "), "Multiple XREFs should be comma-separated"); } @Test @DisplayName("Local variables with many XREFs should limit display") void testLocalVariableXREFLimit() { // When there are more than 5 XREFs, should show first 5 and "..." String manyXrefs = "addr1, addr2, addr3, addr4, addr5"; // Should limit to 5 and add ellipsis for more assertTrue(manyXrefs.split(", ").length <= 5, "Should limit XREF display to 5 addresses"); } // ==================== Function Label Tests ==================== @Test @DisplayName("Function label should include namespace prefix") void testFunctionLabelNamespace() { String functionLabel = "CODE_212::Combat_AIDecisionLoop"; assertTrue(functionLabel.contains("::"), "Function label should include namespace separator"); assertTrue(functionLabel.startsWith("CODE_212"), "Should start with namespace"); } @Test @DisplayName("Function label should show caller XREFs") void testFunctionLabelCallerXREFs() { String labelWithXref = "CODE_212::Combat_AIDecisionLoop XREF[1]: Combat_ModeController:618c:069d("; assertTrue(labelWithXref.contains("XREF["), "Should show XREF indicator for callers"); assertTrue(labelWithXref.contains("Combat_ModeController"), "Should show caller function name"); assertTrue(labelWithXref.contains("618c:069d"), "Should show caller address"); } @Test @DisplayName("Function label with multiple callers should show count") void testFunctionLabelMultipleCallers() { String labelWithMultipleXrefs = "myFunction XREF[3]: caller1:1000, caller2:2000, caller3:3000"; assertTrue(labelWithMultipleXrefs.contains("XREF[3]"), "Should show correct caller count"); long commaCount = labelWithMultipleXrefs.substring(labelWithMultipleXrefs.indexOf("XREF")).chars() .filter(ch -> ch == ',').count(); assertTrue(commaCount >= 1, "Multiple callers should be comma-separated"); } @Test @DisplayName("Function label should limit caller display to 3") void testFunctionLabelCallerLimit() { // When showing callers, limit to first 3 and add "..." for more String callerList = "caller1:addr1, caller2:addr2, caller3:addr3"; assertTrue(callerList.split(", ").length <= 3, "Should limit caller display to 3"); } // ==================== Assembly Instruction Tests ==================== @Test @DisplayName("Assembly instruction should show address and instruction") void testAssemblyInstructionFormat() { String sampleInstruction = " 618c:0bad 55 PUSH BP"; // Format: <spaces><address> <bytes> <mnemonic> <operands> assertTrue(sampleInstruction.contains("618c:0bad"), "Should show address"); assertTrue(sampleInstruction.contains("PUSH"), "Should show instruction mnemonic"); assertTrue(sampleInstruction.contains("BP"), "Should show operands"); } @Test @DisplayName("CALL instruction should show target function name") void testCallInstructionFunctionName() { String callInstruction = " 618c:0bcb 9a ef 32 CALLF CODE_18::CharAI_GetCharDataPointers"; assertTrue(callInstruction.contains("CALLF"), "Should show CALL instruction"); assertTrue(callInstruction.contains("CODE_18::CharAI_GetCharDataPointers"), "Should show target function name with namespace"); } @Test @DisplayName("Instruction should show EOL comment when present") void testInstructionEOLComment() { String instrWithComment = " 618c:0bad 55 PUSH BP ; save base pointer"; assertTrue(instrWithComment.contains("; "), "Should have semicolon prefix for EOL comment"); assertTrue(instrWithComment.contains("save base pointer"), "Should show comment text"); } @Test @DisplayName("Instruction should show PRE comment when present") void testInstructionPREComment() { String instrWithPreComment = " 618c:0bad 55 PUSH BP [PRE: Function prologue]"; assertTrue(instrWithPreComment.contains("[PRE:"), "Should show PRE comment with tag"); assertTrue(instrWithPreComment.contains("Function prologue"), "Should show PRE comment text"); } @Test @DisplayName("Instruction should show POST comment when present") void testInstructionPOSTComment() { String instrWithPostComment = " 618c:0bad 55 PUSH BP [POST: Stack setup complete]"; assertTrue(instrWithPostComment.contains("[POST:"), "Should show POST comment with tag"); assertTrue(instrWithPostComment.contains("Stack setup complete"), "Should show POST comment text"); } @Test @DisplayName("Instruction should show REPEATABLE comment when present") void testInstructionRepeatableComment() { String instrWithRepComment = " 618c:0bad 55 PUSH BP [REP: Common pattern]"; assertTrue(instrWithRepComment.contains("[REP:"), "Should show REPEATABLE comment with tag"); assertTrue(instrWithRepComment.contains("Common pattern"), "Should show REPEATABLE comment text"); } @Test @DisplayName("Instruction should show multiple comment types together") void testInstructionMultipleComments() { String instrWithMultipleComments = " 618c:0bad 55 PUSH BP [PRE: Setup] ; standard ; [POST: Done]"; // Should be able to show multiple comment types on the same instruction assertTrue(instrWithMultipleComments.contains("[PRE:"), "Should show PRE comment"); assertTrue(instrWithMultipleComments.contains("; "), "Should show EOL comment"); assertTrue(instrWithMultipleComments.contains("[POST:"), "Should show POST comment"); } // ==================== XREF Display Tests ==================== @Test @DisplayName("Instruction XREF should show source address and reference type") void testInstructionXREFFormat() { String xrefLine = " XREF from: 618c:0a5f (CALL)"; assertTrue(xrefLine.contains("XREF from:"), "Should show XREF from indicator"); assertTrue(xrefLine.contains("618c:0a5f"), "Should show source address"); assertTrue(xrefLine.contains("(CALL)"), "Should show reference type in parentheses"); } @Test @DisplayName("Instruction XREF from different function should show function name") void testInstructionXREFWithFunctionName() { String xrefLine = " XREF from: Combat_ModeController:618c:069d (CALL)"; assertTrue(xrefLine.contains("Combat_ModeController:"), "Should show source function name"); assertTrue(xrefLine.contains("618c:069d"), "Should show source address"); assertTrue(xrefLine.contains("(CALL)"), "Should show reference type"); } @Test @DisplayName("Multiple XREFs should each appear on separate lines") void testMultipleInstructionXREFs() { String multiXref = " XREF from: 618c:0a5f (CALL)\n" + " XREF from: 618c:0b2c (CALL)"; assertTrue(multiXref.lines().count() >= 2, "Multiple XREFs should be on separate lines"); assertTrue(multiXref.contains("618c:0a5f"), "Should show first XREF"); assertTrue(multiXref.contains("618c:0b2c"), "Should show second XREF"); } @Test @DisplayName("Too many XREFs should be limited to prevent clutter") void testXREFDisplayLimit() { // When there are more than 5 XREFs to an instruction, should limit display int maxXrefsToShow = 5; assertTrue(maxXrefsToShow == 5, "Should limit XREF display to 5 per instruction"); } // ==================== Label Display Tests ==================== @Test @DisplayName("Label at instruction address should be displayed") void testInstructionLabel() { String labelLine = " LAB_618c_0bc0:"; assertTrue(labelLine.endsWith(":"), "Label should end with colon"); assertTrue(labelLine.contains("LAB_"), "Label should be identifiable"); } @Test @DisplayName("Label should not be duplicated with function name") void testLabelNotDuplicatedWithFunctionName() { String functionName = "Combat_AIDecisionLoop"; // The function entry point should not show the function name as both a label and function name assertFalse(functionName.isEmpty(), "Function name should be present"); // Implementation should check: if label name equals function name, don't show label separately } // ==================== Overall Format Tests ==================== @Test @DisplayName("Complete disassembly should have all major sections in order") void testCompleteDisassemblyStructure() { String sampleOutput = " ********************************************************\n" + " * CODE_212: Combat - AI Decision Loop *\n" + " ********************************************************\n" + " uint16_t __cdecl16far CODE_212::Combat_AIDecisionLoop(pointer16 charIndexPtr)\n" + " assume CS = 0x2a0a\n" + " assume DS = 0x5356\n" + " undefined Stack[-0x4] local_4 XREF[1]: 618c:0bc6(*)\n" + " CODE_212::Combat_AIDecisionLoop XREF[1]: Combat_ModeController:618c:069d\n" + " 618c:0bad 55 PUSH BP\n" + " 618c:0bae 8b ec MOV BP,SP\n"; // Verify structure order int platePos = sampleOutput.indexOf("****"); int sigPos = sampleOutput.indexOf("uint16_t"); int assumePos = sampleOutput.indexOf("assume CS"); int varPos = sampleOutput.indexOf("Stack[-0x4]"); // Find the function label line (not the signature) by looking for the XREF pattern after the name int labelPos = sampleOutput.indexOf("Combat_AIDecisionLoop XREF"); int asmPos = sampleOutput.indexOf("618c:0bad"); assertTrue(platePos >= 0, "Should have PLATE comment"); assertTrue(sigPos >= 0, "Should have function signature"); assertTrue(assumePos >= 0, "Should have register assumptions"); assertTrue(varPos >= 0, "Should have local variables"); assertTrue(labelPos >= 0, "Should have function label"); assertTrue(asmPos >= 0, "Should have assembly instructions"); // Verify order: PLATE -> Signature -> Assumptions -> Variables -> Label -> Assembly assertTrue(platePos < sigPos, "PLATE comment should come before signature"); assertTrue(sigPos < assumePos, "Signature should come before register assumptions"); assertTrue(assumePos < varPos, "Register assumptions should come before variables"); assertTrue(varPos < labelPos, "Variables should come before function label"); assertTrue(labelPos < asmPos, "Function label should come before assembly"); } @Test @DisplayName("Disassembly output should use consistent indentation") void testConsistentIndentation() { // PLATE comments, signatures, and labels are indented to column 29 (29 spaces) // Variables are indented to column 13 (13 spaces) // Assembly is indented to column 7 (7 spaces) // XREFs are indented to column 21 (21 spaces) String plateIndent = " "; // 29 spaces String varIndent = " "; // 13 spaces String asmIndent = " "; // 7 spaces String xrefIndent = " "; // 21 spaces assertEquals(29, plateIndent.length(), "PLATE/signature/label indent should be 29 spaces"); assertEquals(13, varIndent.length(), "Variable indent should be 13 spaces"); assertEquals(7, asmIndent.length(), "Assembly indent should be 7 spaces"); assertEquals(21, xrefIndent.length(), "XREF indent should be 21 spaces"); } @Test @DisplayName("Empty sections should not break output formatting") void testEmptySectionsHandling() { // When a function has no PLATE comment, no variables, or no XREFs, // the output should still be well-formed String minimalOutput = " void simple_function()\n" + " simple_function\n" + " 1000:0000 90 NOP\n" + " 1000:0001 c3 RET\n"; // Should have signature, label, and assembly even without PLATE/vars/xrefs assertTrue(minimalOutput.contains("void simple_function()"), "Should have signature"); assertTrue(minimalOutput.contains("simple_function\n"), "Should have function label"); assertTrue(minimalOutput.contains("NOP"), "Should have assembly"); } @Test @DisplayName("Output should handle multiline PLATE comments correctly") void testMultilinePlateComment() { String multilinePlate = "Line 1 of comment\nLine 2 of comment\nLine 3 of comment"; String[] lines = multilinePlate.split("\n"); assertEquals(3, lines.length, "Should preserve all comment lines"); assertTrue(lines[0].equals("Line 1 of comment"), "Should preserve first line"); assertTrue(lines[2].equals("Line 3 of comment"), "Should preserve last line"); } // ==================== Enhanced Feature Tests ==================== @Test @DisplayName("Instruction bytes should be displayed in hex format") void testInstructionBytesDisplay() { String sampleInstruction = " 0022d3d0 4e 55 ff ec link.w A5,-0x14"; // Should show address, then hex bytes, then instruction assertTrue(sampleInstruction.contains("0022d3d0"), "Should show address"); assertTrue(sampleInstruction.contains("4e 55 ff ec"), "Should show instruction bytes in hex"); assertTrue(sampleInstruction.contains("link.w"), "Should show mnemonic"); assertTrue(sampleInstruction.contains("A5,-0x14"), "Should show operands"); } @Test @DisplayName("Instruction bytes should be properly formatted and spaced") void testInstructionBytesFormatting() { String twoByteInstr = " 0022d3d4 48 e7 movem.l {A6 A3 A2 D7 D2},-(SP)"; String fourByteInstr = " 0022d3d0 4e 55 ff ec link.w A5,-0x14"; // Both should have consistent column alignment assertTrue(twoByteInstr.contains("48 e7"), "Should show 2-byte instruction bytes"); assertTrue(fourByteInstr.contains("4e 55 ff ec"), "Should show 4-byte instruction bytes"); // Check that columns align properly (address at same position) int addr1Pos = twoByteInstr.indexOf("0022d3d4"); int addr2Pos = fourByteInstr.indexOf("0022d3d0"); assertEquals(addr1Pos, addr2Pos, "Addresses should align at same column"); } @Test @DisplayName("Stack variables should use hex offsets with size indicators") void testStackVariableHexOffsets() { String stackVar1 = " undefined4 Stack[-0x4]:4 local_4"; String stackVar2 = " undefined4 Stack[-0x18]:4 local_18"; // Should use hex format for negative offsets assertTrue(stackVar1.contains("Stack[-0x4]:4"), "Should show hex offset -0x4 with size :4"); assertTrue(stackVar2.contains("Stack[-0x18]:4"), "Should show hex offset -0x18 with size :4"); // Should not use decimal format assertFalse(stackVar1.contains("Stack[-4]"), "Should not use decimal format"); assertFalse(stackVar2.contains("Stack[-24]"), "Should not use decimal format"); } @Test @DisplayName("Stack variables should include size indicators") void testStackVariableSizeIndicators() { String var4byte = " undefined4 Stack[-0x4]:4 local_4"; String var1byte = " char Stack[-0x1]:1 local_1"; assertTrue(var4byte.contains(":4"), "4-byte variable should show :4 size"); assertTrue(var1byte.contains(":1"), "1-byte variable should show :1 size"); } @Test @DisplayName("Variable XREFs should include operation types") void testVariableXREFOperationTypes() { String varWithReadWrite = " undefined4 Stack[-0x18]:4 local_18 XREF[11]: 0022d424(W), 0022d44a(R), 0022d452(W)"; String varWithPointer = " undefined4 Stack[-0x34]:4 local_34 XREF[3]: 0022d4ee(*), 0022d5de(*), 0022d5e6(*)"; // Should show (W) for write operations assertTrue(varWithReadWrite.contains("(W)"), "Should show (W) for write operations"); // Should show (R) for read operations assertTrue(varWithReadWrite.contains("(R)"), "Should show (R) for read operations"); // Should show (*) for pointer dereference operations assertTrue(varWithPointer.contains("(*)"), "Should show (*) for pointer operations"); } @Test @DisplayName("Jump target labels should show jump XREFs with (j) indicator") void testJumpTargetXREFs() { String labelWithJumps = " LAB_0022d3dc XREF[2]: 0022d48c(j), 0022d4b4(j)"; assertTrue(labelWithJumps.contains("XREF[2]"), "Should show XREF count"); assertTrue(labelWithJumps.contains("0022d48c(j)"), "Should show first jump with (j)"); assertTrue(labelWithJumps.contains("0022d4b4(j)"), "Should show second jump with (j)"); } @Test @DisplayName("PC-relative references should be resolved to symbols") void testPCRelativeReferenceResolution() { String pcRelativeInstr = " 0022d4d0 43 fa 01 24 lea (0x124,PC)=>DAT_0022d5f6,A1"; // Should show the offset assertTrue(pcRelativeInstr.contains("(0x124,PC)"), "Should show PC-relative offset"); // Should resolve to symbol with => assertTrue(pcRelativeInstr.contains("=>DAT_0022d5f6"), "Should resolve to symbol DAT_0022d5f6"); } @Test @DisplayName("Data references should show target values") void testDataReferenceValues() { String dataRef1 = " 0022d4d8 2c d9 move.l (A1)+=>DAT_0022d5f6,(A6)+ = 636F6E3Ah"; String dataRef2 = " 0022d5fa 31 30 2f 31 undefined4 31302F31h"; // Should show symbol assertTrue(dataRef1.contains("=>DAT_0022d5f6"), "Should show resolved symbol"); // Should show actual data value assertTrue(dataRef1.contains("= 636F6E3Ah"), "Should show data value 636F6E3Ah"); assertTrue(dataRef2.contains("31302F31h"), "Should show hex value in data definition"); } @Test @DisplayName("Stack offsets in operands should be resolved to variable names") void testStackOffsetToVariableNameResolution() { String instrWithStackVar = " 0022d5ec 4c ed 4c 84 movem.l (-0x28=>local_28,A5),{D2 D7 A2 A3 A6}"; // Should show the stack offset assertTrue(instrWithStackVar.contains("-0x28"), "Should show stack offset -0x28"); // Should resolve to variable name with => assertTrue(instrWithStackVar.contains("-0x28=>local_28"), "Should resolve to variable name local_28"); } @Test @DisplayName("Call instructions should show function signatures") void testCallInstructionSignatures() { String callWithSig = " 0022d50e 4e ae ff e2 jsr (-0x1e,A6=>exec_library_Supervisor) BPTR dos_library_Open(CONST_STRPTR"; // Should show target function name assertTrue(callWithSig.contains("exec_library_Supervisor"), "Should show called function name"); // Should show function signature/prototype assertTrue(callWithSig.contains("BPTR dos_library_Open"), "Should show function signature"); } @Test @DisplayName("Call destination overrides should be displayed for thunks") void testCallDestinationOverride() { String callWithOverride = " -- Call Destination Override: exec_library_Supervisor (00234026)"; assertTrue(callWithOverride.contains("-- Call Destination Override:"), "Should show override indicator"); assertTrue(callWithOverride.contains("exec_library_Supervisor"), "Should show thunked function name"); assertTrue(callWithOverride.contains("(00234026)"), "Should show target address"); } @Test @DisplayName("Indirect references should be resolved to symbols") void testIndirectReferenceResolution() { String indirectRef = " 0022d50a 2c 6c 41 a0 movea.l (0x41a0,A4)=>dosLibraryPtr,A6"; // Should show offset assertTrue(indirectRef.contains("(0x41a0,A4)"), "Should show indirect offset"); // Should resolve to symbol assertTrue(indirectRef.contains("=>dosLibraryPtr") || indirectRef.contains("=>"), "Should resolve indirect reference to symbol"); } @Test @DisplayName("All stack variables should be displayed not just referenced ones") void testAllStackVariablesDisplayed() { // Ghidra UI shows ALL stack variables, even if not referenced in decompilation String varList = " undefined4 Stack[-0x4]:4 local_4 XREF[1]: 0022d5f2(R)\n" + " undefined4 Stack[-0x10]:4 local_10 XREF[1]: 0022d546(W)\n" + " undefined4 Stack[-0x14]:4 local_14 XREF[2]: 0022d52a(W), 0022d538(R)\n" + " undefined4 Stack[-0x18]:4 local_18 XREF[11]: 0022d424(W), 0022d44a(R)\n" + " undefined4 Stack[-0x28]:4 local_28 XREF[1]: 0022d5ec(*)\n" + " undefined4 Stack[-0x30]:4 local_30 XREF[1]: 0022d5da(*)\n" + " undefined4 Stack[-0x34]:4 local_34 XREF[3]: 0022d4ee(*), 0022d5de(*)"; // Should show all 7 local variables assertTrue(varList.contains("local_4"), "Should show local_4"); assertTrue(varList.contains("local_10"), "Should show local_10"); assertTrue(varList.contains("local_14"), "Should show local_14"); assertTrue(varList.contains("local_18"), "Should show local_18"); assertTrue(varList.contains("local_28"), "Should show local_28"); assertTrue(varList.contains("local_30"), "Should show local_30"); assertTrue(varList.contains("local_34"), "Should show local_34"); // All should have proper hex offsets assertTrue(varList.contains("Stack[-0x4]:4"), "local_4 should have hex offset"); assertTrue(varList.contains("Stack[-0x18]:4"), "local_18 should have hex offset"); assertTrue(varList.contains("Stack[-0x34]:4"), "local_34 should have hex offset"); } @Test @DisplayName("Variable XREFs should be limited to 11 entries like Ghidra UI") void testVariableXREFLimit() { // Ghidra limits XREF display to 11 entries per variable String varWith11Xrefs = "XREF[11]: 0022d424(W), 0022d44a(R), 0022d452(W), 0022d458(R), 0022d460(W), 0022d466(R), 0022d46c(W), 0022d474(R), 0022d47a(W), 0022d482(R), 0022d488(W)"; assertTrue(varWith11Xrefs.contains("XREF[11]"), "Should show count of 11"); // Count the number of comma-separated entries String xrefPart = varWith11Xrefs.substring(varWith11Xrefs.indexOf(":") + 1); int commas = (int) xrefPart.chars().filter(ch -> ch == ',').count(); // 11 entries means 10 commas assertTrue(commas <= 10, "Should have at most 10 commas for 11 entries"); } @Test @DisplayName("Multi-byte instructions should display all bytes") void testMultiByteInstructionDisplay() { String sixByteInstr = " 0022d3dc 0c ac 00 00 cmpi.l #0x20,(0x3ff2,A4)"; String sevenByteInstr = " 0022d5ec 4c ed 4c 84 movem.l (-0x28,A5),{D2 D7 A2 A3 A6}"; // Should show all instruction bytes assertTrue(sixByteInstr.contains("0c ac 00 00"), "Should show all bytes of 4-byte instruction"); assertTrue(sevenByteInstr.contains("4c ed 4c 84"), "Should show first part of multi-byte instruction"); } @Test @DisplayName("Function parameter variables should be included in variable listing") void testFunctionParametersInVariableListing() { String paramVar = " char * Stack[0x4]:4 commandLine XREF[3]: 0022d3dc, 0022d480, 0022d4b2"; // Should show function parameter with positive stack offset assertTrue(paramVar.contains("Stack[0x4]:4"), "Should show positive stack offset for parameter"); assertTrue(paramVar.contains("commandLine"), "Should show parameter name"); // Parameters have positive offsets, locals have negative assertTrue(paramVar.contains("[0x4]"), "Parameter should have positive offset"); assertFalse(paramVar.contains("[-"), "Parameter should not have negative offset"); } // ==================== Function Contiguity Warning Tests ==================== @Test @DisplayName("Contiguity warning should be displayed when function has gap") void testContiguityWarningPresent() { String warningOutput = "WARNING: Function body is not contiguous\n" + " Function: Build_DecodeTree @ 0x0022acee\n" + " Declared range: 0x0022acee - 0x0022acf9 (12 bytes)\n" + " Gap detected: 1 byte between end (0x0022acf9) and next function start (0x0022acfb)\n" + " Next function: NextFunc @ 0x0022acfb\n" + " Possible issue: Incorrect function boundary detection\n" + " Action: Verify function ends correctly or merge with adjacent function\n"; assertTrue(warningOutput.contains("WARNING: Function body is not contiguous"), "Should show contiguity warning header"); assertTrue(warningOutput.contains("Gap detected:"), "Should indicate gap detected"); assertTrue(warningOutput.contains("Possible issue:"), "Should explain possible issue"); assertTrue(warningOutput.contains("Action:"), "Should provide recommended action"); } @Test @DisplayName("Contiguity warning should show gap size") void testContiguityWarningGapSize() { String smallGapWarning = " Gap detected: 1 byte between end (0x0022acf9) and next function start (0x0022acfa)\n"; String largeGapWarning = " Gap detected: 150 bytes between end (0x0022acf9) and next function start (0x0022ad8d)\n"; assertTrue(smallGapWarning.contains("1 byte"), "Should show singular 'byte' for 1-byte gap"); assertTrue(largeGapWarning.contains("150 bytes"), "Should show plural 'bytes' for multi-byte gap"); } @Test @DisplayName("Contiguity warning should note large gaps") void testContiguityWarningLargeGapNote() { String largeGapWarning = "WARNING: Function body is not contiguous\n" + " Function: Build_DecodeTree @ 0x0022acee\n" + " Declared range: 0x0022acee - 0x0022ad8c (159 bytes)\n" + " Gap detected: 150 bytes between end (0x0022ad8c) and next function start (0x0022ae22)\n" + " Note: Large gap (>= 100 bytes) - may indicate legitimate spacing\n" + " Next function: NextFunc @ 0x0022ae22\n" + " Possible issue: Incorrect function boundary detection\n" + " Action: Verify function ends correctly or merge with adjacent function\n"; assertTrue(largeGapWarning.contains("Note: Large gap (>= 100 bytes)"), "Should note when gap is large"); assertTrue(largeGapWarning.contains("may indicate legitimate spacing"), "Should explain large gaps may be normal"); } @Test @DisplayName("Contiguity warning should show function size") void testContiguityWarningFunctionSize() { String warningWithSize = " Declared range: 0x0022acee - 0x0022acf9 (12 bytes)\n"; assertTrue(warningWithSize.contains("(12 bytes)"), "Should show function size in bytes"); // Use Pattern.DOTALL flag to match newlines, or just check if the pattern exists in the string assertTrue(warningWithSize.matches("(?s).*\\(\\d+ bytes?\\).*"), "Should show size in proper format"); } @Test @DisplayName("Contiguity warning should identify next function") void testContiguityWarningNextFunction() { String warningWithNextFunc = " Next function: NextFunc @ 0x0022acfb\n"; assertTrue(warningWithNextFunc.contains("Next function:"), "Should identify next function"); assertTrue(warningWithNextFunc.contains("@"), "Should show next function address"); } @Test @DisplayName("Contiguity warning should not appear for contiguous functions") void testNoWarningForContiguousFunction() { // When there's no gap between functions, no warning should appear String contiguousOutput = " void myFunction()\n" + " myFunction\n" + " 1000:0000 90 NOP\n" + " 1000:0001 c3 RET\n"; assertFalse(contiguousOutput.contains("WARNING:"), "Should not show warning for contiguous function"); assertFalse(contiguousOutput.contains("Gap detected"), "Should not indicate gap for contiguous function"); } // ==================== Address Context Tests ==================== @Test @DisplayName("Address context should show target address with arrow marker") void testAddressContextTargetMarker() { String contextOutput = " --> 0022d3d4 48 e7 movem.l {A6 A3 A2 D7 D2},-(SP)"; assertTrue(contextOutput.contains(" --> "), "Target instruction should be marked with arrow"); assertTrue(contextOutput.contains("0022d3d4"), "Should show target address"); } @Test @DisplayName("Address context should show instructions before target without arrow") void testAddressContextBeforeInstructions() { String beforeInstr = " 0022d3d0 4e 55 ff ec link.w A5,-0x14"; assertTrue(beforeInstr.startsWith(" "), "Before instructions should have standard indent"); assertFalse(beforeInstr.contains("-->"), "Before instructions should not have arrow"); } @Test @DisplayName("Address context should show instructions after target without arrow") void testAddressContextAfterInstructions() { String afterInstr = " 0022d3d8 2c 6c 41 a0 movea.l (0x41a0,A4),A6"; assertTrue(afterInstr.startsWith(" "), "After instructions should have standard indent"); assertFalse(afterInstr.contains("-->"), "After instructions should not have arrow"); } @Test @DisplayName("Address context should show context window header") void testAddressContextHeader() { String header = "Disassembly context for address: 0022d3d4\n" + "Context window: -5 to +5 instructions"; assertTrue(header.contains("Disassembly context for address:"), "Should show header"); assertTrue(header.contains("Context window:"), "Should show context window info"); assertTrue(header.contains("-5 to +5"), "Should show before/after counts"); } @Test @DisplayName("Address context should show containing function name") void testAddressContextFunctionName() { String funcInfo = "Function: myFunction @ 0022d3d0"; assertTrue(funcInfo.contains("Function:"), "Should show function indicator"); assertTrue(funcInfo.contains("myFunction"), "Should show function name"); assertTrue(funcInfo.contains("@"), "Should show at symbol for address"); } @Test @DisplayName("Address context should handle data at target address") void testAddressContextDataAddress() { String dataNote = "Note: Target address contains data, not an instruction\n" + "Data type: dword"; assertTrue(dataNote.contains("Note:"), "Should show note about data"); assertTrue(dataNote.contains("contains data, not an instruction"), "Should explain it's data"); assertTrue(dataNote.contains("Data type:"), "Should show data type"); } @Test @DisplayName("Address context should handle undefined target address") void testAddressContextUndefinedAddress() { String undefinedNote = "Note: Target address is undefined or not an instruction"; assertTrue(undefinedNote.contains("Note:"), "Should show note"); assertTrue(undefinedNote.contains("undefined or not an instruction"), "Should explain undefined"); } @Test @DisplayName("Address context should show labels for jump targets") void testAddressContextLabels() { String labelLine = " LAB_0022d3dc XREF[2]: 0022d48c(j), 0022d4b4(j)"; assertTrue(labelLine.contains("LAB_"), "Should show label"); assertTrue(labelLine.contains("XREF"), "Should show XREFs to label"); assertTrue(labelLine.contains("(j)"), "Should show jump indicator"); } @Test @DisplayName("Address context should show XREFs for target instruction only") void testAddressContextTargetXREFs() { String targetWithXref = " --> 0022d3d4 48 e7 movem.l {A6 A3 A2 D7 D2},-(SP)\n" + " XREF from: Combat_Init:0022d100 (CALL)"; assertTrue(targetWithXref.contains("XREF from:"), "Should show XREF for target"); assertTrue(targetWithXref.contains("Combat_Init"), "Should show source function"); assertTrue(targetWithXref.contains("(CALL)"), "Should show reference type"); } @Test @DisplayName("Address context should limit XREFs display for clarity") void testAddressContextXREFLimit() { String manyXrefs = " XREF from: [6 references]"; assertTrue(manyXrefs.contains("references]"), "Should indicate multiple references"); } @Test @DisplayName("Address context should use same formatting as disassemble_function") void testAddressContextConsistentFormatting() { String contextInstr = " 0022d3d0 4e 55 ff ec link.w A5,-0x14"; // Should match disassemble_function format: 7 spaces, address, bytes, mnemonic, operands assertTrue(contextInstr.startsWith(" "), "Should have 7-space indent"); assertTrue(contextInstr.contains("4e 55 ff ec"), "Should show instruction bytes"); assertTrue(contextInstr.contains("link.w"), "Should show mnemonic"); assertTrue(contextInstr.contains("A5,-0x14"), "Should show operands"); } @Test @DisplayName("Address context should show call targets with signatures") void testAddressContextCallSignatures() { String callWithSig = " 0022d50e 4e ae ff e2 jsr (-0x1e,A6) exec_library_Supervisor"; assertTrue(callWithSig.contains("jsr"), "Should show call instruction"); assertTrue(callWithSig.contains("exec_library_Supervisor"), "Should show called function"); } @Test @DisplayName("Address context should show instruction comments") void testAddressContextComments() { String instrWithComment = " 0022d3d0 4e 55 ff ec link.w A5,-0x14 ; setup stack frame"; assertTrue(instrWithComment.contains("; "), "Should show EOL comment"); assertTrue(instrWithComment.contains("setup stack frame"), "Should show comment text"); } @Test @DisplayName("Address context should respect before parameter") void testAddressContextBeforeParameter() { // With before=3, should show exactly 3 instructions before target // This is a format test - actual count would be verified in integration tests String header = "Context window: -3 to +5 instructions"; assertTrue(header.contains("-3"), "Should respect before parameter"); } @Test @DisplayName("Address context should respect after parameter") void testAddressContextAfterParameter() { // With after=10, should show exactly 10 instructions after target // This is a format test - actual count would be verified in integration tests String header = "Context window: -5 to +10 instructions"; assertTrue(header.contains("+10"), "Should respect after parameter"); } @Test @DisplayName("Address context should use default values when parameters not specified") void testAddressContextDefaultParameters() { String headerWithDefaults = "Context window: -5 to +5 instructions"; assertTrue(headerWithDefaults.contains("-5 to +5"), "Should use default values of 5 before and 5 after"); } @Test @DisplayName("Address context should show variable name resolution in operands") void testAddressContextVariableResolution() { String instrWithVar = " 0022d5ec 4c ed 4c 84 movem.l (-0x28=>local_28,A5),{D2 D7 A2 A3 A6}"; assertTrue(instrWithVar.contains("=>local_28"), "Should resolve stack offset to variable name"); assertTrue(instrWithVar.contains("-0x28"), "Should show original offset"); } @Test @DisplayName("Address context should show symbol resolution in operands") void testAddressContextSymbolResolution() { String instrWithSymbol = " 0022d4d0 43 fa 01 24 lea (0x124,PC)=>DAT_0022d5f6,A1"; assertTrue(instrWithSymbol.contains("=>DAT_"), "Should resolve to symbol"); assertTrue(instrWithSymbol.contains("(0x124,PC)"), "Should show PC-relative offset"); } @Test @DisplayName("Address context error message should include address when invalid") void testAddressContextInvalidAddressError() { String errorMsg = "Error: Invalid address format: invalid_addr"; assertTrue(errorMsg.contains("Error:"), "Should show error indicator"); assertTrue(errorMsg.contains("Invalid address format"), "Should explain the error"); assertTrue(errorMsg.contains("invalid_addr"), "Should include the problematic address"); } // ==================== Include Bytes Parameter Tests ==================== @Test @DisplayName("Default disassembly should NOT include instruction bytes") void testDefaultDisassemblyWithoutBytes() { String defaultOutput = " 0021f786 movem.l { D7 D6 D2},-(SP\n" + " 0021f78a lea (0x3a8a,A4)=>g_GameState_Buffer,A0\n" + " 0021f78e move.l A0=>g_GameState_Buffer,(0x3b8a,A4)=>g_GameState_BufferPtr"; // Should NOT contain hex bytes like "48 e7 23 00" assertFalse(defaultOutput.matches(".*[0-9a-f]{2} [0-9a-f]{2}.*"), "Default output should not include hex bytes"); // Should have address directly followed by mnemonic (no bytes column) assertTrue(defaultOutput.contains("0021f786 movem.l"), "Should have address followed immediately by mnemonic"); } @Test @DisplayName("Disassembly with include_bytes=true should show instruction bytes") void testDisassemblyWithBytesEnabled() { String outputWithBytes = " 0021f786 48 e7 23 00 movem.l { D7 D6 D2},-(SP\n" + " 0021f78a 41 ec 3a 8a lea (0x3a8a,A4)=>g_GameState_Buffer,A0\n" + " 0021f78e 29 48 3b 8a move.l A0=>g_GameState_Buffer,(0x3b8a,A4)=>g_GameState_BufferPtr"; // Should contain hex bytes assertTrue(outputWithBytes.contains("48 e7 23 00"), "Should include hex bytes for first instruction"); assertTrue(outputWithBytes.contains("41 ec 3a 8a"), "Should include hex bytes for second instruction"); assertTrue(outputWithBytes.contains("29 48 3b 8a"), "Should include hex bytes for third instruction"); } @Test @DisplayName("Disassembly with include_bytes=false should match default behavior") void testDisassemblyWithBytesExplicitlyDisabled() { String outputWithoutBytes = " 0021f786 movem.l { D7 D6 D2},-(SP\n" + " 0021f78a lea (0x3a8a,A4)=>g_GameState_Buffer,A0"; // Explicitly set to false should produce same output as default assertFalse(outputWithoutBytes.matches(".*[0-9a-f]{2} [0-9a-f]{2}.*"), "Output with include_bytes=false should not include hex bytes"); } @Test @DisplayName("Column alignment should work correctly without bytes") void testColumnAlignmentWithoutBytes() { String line1 = " 0021f786 movem.l { D7 D6 D2},-(SP"; String line2 = " 0021f78a lea (0x3a8a,A4)=>g_GameState_Buffer,A0"; String line3 = " 00000100 nop"; // All lines should align address column at same position (7 spaces) assertTrue(line1.startsWith(" "), "Should have 7-space indent"); assertTrue(line2.startsWith(" "), "Should have 7-space indent"); assertTrue(line3.startsWith(" "), "Should have 7-space indent"); // Mnemonic should start after address + 2 spaces (no bytes column) int addr1End = line1.indexOf("0021f786") + "0021f786".length(); int mnem1Start = line1.indexOf("movem.l"); assertEquals(2, mnem1Start - addr1End, "Should have 2 spaces between address and mnemonic"); } @Test @DisplayName("Column alignment should work correctly with bytes") void testColumnAlignmentWithBytes() { String line1 = " 0021f786 48 e7 23 00 movem.l { D7 D6 D2},-(SP"; String line2 = " 0021f78a 41 ec lea (0x3a8a,A4)=>g_GameState_Buffer,A0"; // Both lines should align mnemonics despite different byte lengths assertTrue(line1.contains("48 e7 23 00"), "Should show 4-byte instruction"); assertTrue(line2.contains("41 ec"), "Should show 2-byte instruction"); // Bytes field should be padded to 12 characters to maintain alignment String bytes1 = "48 e7 23 00"; String bytes2 = "41 ec "; // Padded to 12 chars assertTrue(bytes1.length() <= 12, "Bytes should fit in 12-char field"); } @Test @DisplayName("Address context with include_bytes=false should not show bytes") void testAddressContextWithoutBytes() { String contextOutput = " 0022d3d0 link.w A5,-0x14\n" + " --> 0022d3d4 movem.l {A6 A3 A2 D7 D2},-(SP)\n" + " 0022d3d8 movea.l (0x41a0,A4),A6"; // Should not contain hex bytes assertFalse(contextOutput.matches(".*[0-9a-f]{2} [0-9a-f]{2}.*"), "Address context without bytes should not show hex bytes"); // Target marker should still work assertTrue(contextOutput.contains(" --> "), "Should still show target marker"); } @Test @DisplayName("Address context with include_bytes=true should show bytes") void testAddressContextWithBytes() { String contextOutput = " 0022d3d0 4e 55 ff ec link.w A5,-0x14\n" + " --> 0022d3d4 48 e7 movem.l {A6 A3 A2 D7 D2},-(SP)\n" + " 0022d3d8 2c 6c 41 a0 movea.l (0x41a0,A4),A6"; // Should contain hex bytes assertTrue(contextOutput.contains("4e 55 ff ec"), "Should show bytes for instruction before target"); assertTrue(contextOutput.contains("48 e7"), "Should show bytes for target instruction"); assertTrue(contextOutput.contains("2c 6c 41 a0"), "Should show bytes for instruction after target"); // Target marker should still work with bytes assertTrue(contextOutput.contains(" --> 0022d3d4 48 e7"), "Target marker should work with bytes"); } @Test @DisplayName("Data context with include_bytes=false should not show data bytes") void testDataContextWithoutBytes() { String dataOutput = " 00231fe4 uint32_t 0h\n" + " --> 00231fec uint8_t[ \"\"\n" + " 002320e6 uint16_t 0h"; // Should not contain hex bytes for data assertFalse(dataOutput.matches(".*[0-9a-f]{2} [0-9a-f]{2}.*"), "Data context without bytes should not show hex bytes"); } @Test @DisplayName("Data context with include_bytes=true should show data bytes") void testDataContextWithBytes() { String dataOutput = " 00231fe4 00 00 00 00 uint32_t 0h\n" + " --> 00231fec 00 00 00 ... uint8_t[ \"\"\n" + " 002320e6 00 00 uint16_t 0h"; // Should contain hex bytes for data (limited to first 4 bytes) assertTrue(dataOutput.contains("00 00 00 00"), "Should show 4 bytes for uint32_t"); assertTrue(dataOutput.contains("00 00 00 ..."), "Should show first 4 bytes with ellipsis for array"); assertTrue(dataOutput.contains("00 00 "), "Should show 2 bytes for uint16_t"); } @Test @DisplayName("Include bytes parameter should not affect other disassembly features") void testIncludeBytesDoesNotAffectOtherFeatures() { String outputWithBytes = " 0021f786 48 e7 23 00 movem.l { D7 D6 D2},-(SP ; save registers\n" + " XREF from: Combat_Init:0022d100 (CALL)"; String outputWithoutBytes = " 0021f786 movem.l { D7 D6 D2},-(SP ; save registers\n" + " XREF from: Combat_Init:0022d100 (CALL)"; // Both should have comments assertTrue(outputWithBytes.contains("; save registers"), "Should preserve comments with bytes"); assertTrue(outputWithoutBytes.contains("; save registers"), "Should preserve comments without bytes"); // Both should have XREFs assertTrue(outputWithBytes.contains("XREF from:"), "Should preserve XREFs with bytes"); assertTrue(outputWithoutBytes.contains("XREF from:"), "Should preserve XREFs without bytes"); // Both should have enhanced operands (=>) assertTrue(outputWithBytes.contains("{ D7 D6 D2}"), "Should preserve operand formatting with bytes"); assertTrue(outputWithoutBytes.contains("{ D7 D6 D2}"), "Should preserve operand formatting without bytes"); } @Test @DisplayName("Include bytes parameter should not affect function signature and variables") void testIncludeBytesDoesNotAffectFunctionMetadata() { String signature = " uint16_t __cdecl16far CODE_212::Combat_AIDecisionLoop(pointer16 charIndexPtr)"; String variable = " undefined Stack[-0x4] local_4 XREF[1]: 618c:0bc6(*)"; String label = " CODE_212::Combat_AIDecisionLoop XREF[1]: Combat_ModeController:618c:069d"; // These sections should be identical regardless of include_bytes setting assertTrue(signature.contains("uint16_t __cdecl16far"), "Function signature should not change"); assertTrue(variable.contains("Stack[-0x4]"), "Variable table should not change"); assertTrue(label.contains("XREF[1]"), "Function label should not change"); } @Test @DisplayName("Bytes field should be exactly 12 characters wide when included") void testBytesFieldWidth() { // Test various instruction lengths String oneByte = " 618c:0bad 55 PUSH BP"; String twoBytes = " 618c:0bae 8b ec MOV BP,SP"; String fourBytes = " 0022d3d0 4e 55 ff ec link.w A5,-0x14"; // Extract bytes field (between address and mnemonic) // Bytes field should always be padded to 12 chars int bytesStart1 = oneByte.indexOf("618c:0bad") + "618c:0bad".length() + 1; int mnemonicStart1 = oneByte.indexOf("PUSH"); String bytesField1 = oneByte.substring(bytesStart1, mnemonicStart1 - 1); assertEquals(12, bytesField1.length(), "Bytes field should be 12 characters wide"); } @Test @DisplayName("Without bytes, output should be more compact and readable") void testOutputCompactnessWithoutBytes() { String withBytes = " 0021f786 48 e7 23 00 movem.l { D7 D6 D2},-(SP"; String withoutBytes = " 0021f786 movem.l { D7 D6 D2},-(SP"; // Without bytes should be shorter assertTrue(withoutBytes.length() < withBytes.length(), "Output without bytes should be more compact"); // Both should still be readable and properly formatted assertTrue(withoutBytes.contains("0021f786"), "Should have address"); assertTrue(withoutBytes.contains("movem.l"), "Should have mnemonic"); assertTrue(withoutBytes.contains("{ D7 D6 D2}"), "Should have operands"); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HK47196/GhidraMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server