Skip to main content
Glama

RE-MCP

A safety-first local Model Context Protocol server for ROM reverse-engineering workflows.

RE-MCP uses stdio and exposes narrow, tested tools rather than an unrestricted shell.

Current capabilities

General

  • Project Git status

  • Allowlisted npm verification

  • SHA-256 file verification

  • Capability and policy reporting

Bakugan DS

  • Compile, Ruff, mypy, pytest, or full quality suite

  • Regenerate Milestone 6E contracts

  • Run the Milestone 6E installer in dry-run mode

  • Generate the Milestone 6E roster analysis

Nintendo DS static analysis

  • Parse one canonical ROM identity using SHA-256

  • Read ARM9 and ARM7 executable metadata

  • Parse FAT physical file ranges

  • Reconstruct FNT/NitroFS paths while retaining unnamed FAT entries

  • Parse ARM9 and ARM7 overlay tables, including initialized range, BSS, file backing, compression metadata, and flags

  • Resolve ARM9/ARM7 runtime addresses against main executables and static overlay candidates

  • Reverse-map ROM offsets to structural, NitroFS, executable, and overlay relationships

  • Decode bounded ARM/Thumb instruction windows from deterministic file-backed NDS code sources

  • Build bounded direct-control-flow graphs across deterministic same-processor branch targets without recursively traversing calls

  • Classify deterministic single-instruction direct branch/call, literal-pool-slot, and PC-relative address references

  • Find bounded reverse cross-references through proven code seeds with explicit component coverage and truncation status

  • Discover bounded ARM9/ARM7 function-entry call graphs using only program-entry and deterministic resolved direct-call proof

  • Prove one requested function entry and distinguish complete negative evidence from incomplete proof coverage before analyzing its CFG

  • Search validated NDS bytes for exact/wildcard signatures, typed integers, ASCII strings, and UTF-16LE strings using canonical component or explicit whole-ROM scope

  • Extract validated ARM9, ARM7, overlay, or NitroFS components to a deterministic generated-analysis tree

  • Build a transactional static-analysis bundle without dumping every NitroFS asset

  • Optionally bootstrap and inspect a full-ROM-SHA-scoped Ghidra project through a configured local Ghidra 12.x installation

The source ROM is read-only. Static-analysis extraction artifacts are restricted to analysis/generated/nds/<sha-prefix>/ under the configured workspace. The static-analysis tools do not accept generic binary inputs, caller-selected output paths, arbitrary ROM offset/length extraction requests, or caller-defined raw search ranges.

Controlled NDS Mutation — Milestone 1

RE-MCP now exposes a narrow manifest-driven write/build surface for exact Nintendo DS ROM revisions:

  • nds_mutation_validate validates the strict mutation manifest, exact source SHA-256, canonical component selectors, original-byte/component guards, replacement artifacts, conflicts, and deterministic build identity without publishing output.

  • nds_mutation_build applies the validated plan only to a temporary copy of the source ROM, reparses and verifies the result, attributes every changed byte to an approved operation, and atomically publishes the deterministic build.

  • nds_mutation_verify freshly revalidates an existing deterministic build and its evidence; it never silently repairs or overwrites a divergent/tampered build.

The source ROM remains immutable. Milestone 1 supports only same-size guarded byte replacements and exact-size whole-component replacements selected through the canonical NDS model. Callers cannot provide an arbitrary ROM offset or caller-selected output paths.

Successful builds are published beneath:

output/nds/<source-sha-prefix>/<build-id>/

alongside the rebuilt .nds file and deterministic evidence:

mutation-manifest.json
resolved-plan.json
verification.json
changed-components.json
output.sha256

Verification requires the rebuilt ROM to parse through the canonical NDS model, preserves immutable structural geometry, revalidates compressed-overlay runtime images when applicable, checks every requested operation, and requires zero unexpected changed bytes. Re-running the same exact build may reuse it only after fresh verification; a mismatched or tampered deterministic output fails closed as a publish collision.

Milestone 1 deliberately does not provide variable-size rebuilding or relocation, FAT/FNT mutation, decoded compressed-overlay editing, BLZ recompression, generic source-ROM writes, arbitrary ROM offset writes, or caller-selected output paths. Those capabilities remain future controlled-build work rather than being inferred from this narrow mutation surface.

DeSmuME and ARM9 GDB

  • Start, inspect, and stop one server-owned DeSmuME process

  • Probe and wait for the owned ARM9 GDB port

  • Read the raw ARM9 register packet

  • Read up to 4096 bytes of ARM9 memory

  • Derive the main ARM9 executable range from the NDS ROM header before launch

  • Maintain an allowlist of the main ARM9 range plus up to 64 explicit or overlay executable ranges

  • Add, remove, and list controlled ARM9 software breakpoints

  • Continue execution, wait for a stop, interrupt/pause, and single-step up to 100 instructions

  • Decode DeSmuME ARM9 registers into r0-r12, sp, lr, pc, and cpsr

  • Capture structured stop context with bounded PC, stack, and optional memory windows

  • Match breakpoint hits, track hit counts, and retain ARM/Thumb execution history

  • Atomically capture raw registers plus labeled memory regions

  • Reset debugger state automatically when the owned emulator exits or its process generation changes

  • Correlate the exact stopped ARM9 PC/CPSR mode with the launch-time ROM SHA-256, canonical NDS ownership, bounded static instructions/references, and exact function-entry proof

RE-MCP does not expose register writes, general memory writes, watchpoints, or an arbitrary GDB-command tool.

Related MCP server: local-code-mcp

NDS Static Analysis

The canonical static-analysis surface consists of fourteen MCP tools:

  • nds_inspect_rom

  • nds_list_files

  • nds_list_overlays

  • nds_resolve_runtime_address

  • nds_resolve_rom_offset

  • nds_extract_component

  • nds_extract_analysis_bundle

  • nds_disassemble_range

  • nds_analyze_control_flow

  • nds_list_references

  • nds_find_xrefs

  • nds_search_pattern

  • nds_discover_functions

  • nds_analyze_function

These canonical static tools are native-independent and have no DeSmuME, GDB, or Ghidra dependency. The optional Ghidra bridge described below consumes their canonical evidence but does not change their proof rules.

Canonical ROM model

nds_inspect_rom parses the ROM into one validated model containing:

  • full source SHA-256 and file size

  • game title, game code, maker code, unit code, capacity, and ROM version

  • ARM9 and ARM7 ROM offsets, entry addresses, RAM/load addresses, sizes, and runtime ranges

  • FNT and FAT regions

  • ARM9 and ARM7 overlay-table regions

  • NitroFS file count

  • ARM9 and ARM7 overlay counts

  • validated static executable/runtime candidate ranges

FAT remains authoritative for physical file byte ranges. FNT remains authoritative for names and directory hierarchy. Overlay records keep file-backed bytes, initialized runtime bytes, and BSS/runtime-only bytes distinct.

Address-resolution rules

nds_resolve_runtime_address does not guess when static overlay ranges overlap. If more than one main/overlay candidate contains an address, every candidate is returned with an ambiguity status.

BSS has no source ROM bytes, so BSS results return no ROM offset.

Compressed overlay bytes require special provenance handling. Stored FAT-backed bytes and decoded runtime bytes remain distinct: a decoded runtime byte never receives a fabricated direct ROM-byte offset. The resolver reports overlay/file/runtime/backing metadata, while validated BLZ-derived runtime images may be consumed by the later disassembly, reference, function, Ghidra, and runtime-correlation layers with romOffset: null.

nds_resolve_rom_offset performs the reverse classification and may return multiple valid relationships for one ROM byte, such as a NitroFS file plus an ARM9 overlay backing file. Compressed overlay backing bytes do not receive fabricated runtime addresses.

ARM/Thumb static disassembly

nds_disassemble_range and nds_analyze_control_flow use @alexaltea/capstone-js 5.0.9 through a narrow RE-MCP-owned ARM decoder interface. The backend is JavaScript + WebAssembly and is bundled with RE-MCP; no external Capstone, Ghidra, or radare2 executable is required.

Both tools accept only Nintendo DS sources resolved through the canonical ROM model. A request identifies arm9 or arm7, exactly one runtime address or ROM offset, an optional overlay ID used only as a static disambiguator, and an ARM/Thumb mode.

Supported modes are:

  • arm

  • thumb

  • conservative auto

Initial auto mode succeeds only when the resolved source is the matching ARM9 or ARM7 main header entry point, which is an ARM seed. Merely being in an executable range or overlay is not sufficient evidence. During CFG traversal, a deterministic direct edge may propagate its statically proven target mode. RE-MCP never decodes both modes and chooses the more plausible stream, and it does not use address bit 0 as a general-purpose mode guess.

ARM starts and deterministic ARM targets must be 4-byte aligned. Thumb starts and deterministic Thumb targets must be 2-byte aligned. Invalid alignment is rejected rather than rounded.

Decodable code sources are limited to validated initialized executable representations:

  • ARM9 main

  • ARM7 main

  • uncompressed ARM9 overlays

  • uncompressed ARM7 overlays

  • validated decoded BLZ runtime images for compressed overlays, represented as derived code with romOffset: null

BSS remains runtime-only and is never fabricated into an instruction stream. If an uncompressed overlay's runtime initialized extent is larger than its physical backing file, only the exact file-backed prefix is eligible.

If multiple static code mappings contain a requested address or branch target, RE-MCP returns or records ambiguous-code-source rather than guessing which overlay is loaded. Supplying overlayId can select one starting static source, but it never claims that overlay is loaded at runtime. A deterministic branch that stays within that already selected component preserves its static component identity; a cross-component branch is re-resolved and traversed only when the same processor, source bytes, and target mode are all deterministic.

The ROM SHA-256 used to construct the canonical map is checked immediately before and after each top-level linear or CFG operation. A modified ROM invalidates the operation even if a decode callback also fails.

Linear disassembly limits

nds_disassemble_range decodes sequentially and classifies control flow without changing linear traversal based on branch instructions.

Limit

Default

Maximum

Instructions

32

256

Source bytes

128

1,024

Decoding stops at the first instruction limit, byte limit, component boundary, instruction that would cross a component boundary, or undecodable instruction. A local decode failure returns the successfully decoded prefix with decode-stopped; RE-MCP never skips bytes and silently resumes. complete means the requested bounded window completed, not that a whole function or component was discovered.

Direct-control-flow limits and semantics

nds_analyze_control_flow builds basic blocks using a deterministic FIFO worklist. Block identity includes processor, component, overlay ID, runtime address, and mode, preventing cycles from repeatedly decoding the same block identity.

Limit

Default

Maximum

Basic blocks

64

256

Total instructions

512

4,096

Total decoded source bytes

2 KiB

16 KiB

Traversal edges

128

1,024

All limits apply simultaneously. If any cap prevents further exploration, the graph returns status: "truncated" with explicit reasons chosen from block-limit, instruction-limit, byte-limit, and edge-limit. A truncated graph is a valid partial result and is never presented as complete.

Deterministic non-call direct branches may be traversed. Conditional branches may create both taken and valid same-component fall-through edges. Direct calls are fully annotated but their callees are not queued as CFG blocks. Indirect call targets are recorded as unresolved rather than guessed; caller-side sequential decoding can continue at the valid fall-through. Indirect branches and returns terminate the current block. Register-indirect targets never receive invented addresses or modes.

Static disassembly is independent of physical Catalina/DeSmuME Dynamic Debugging acceptance. Passing the Capstone.js tests or package smoke check does not constitute native emulator-debugger acceptance.

Proven reference discovery

Reference discovery is deliberately narrower than generic pattern or pointer searching. RE-MCP emits only deterministic single-instruction references in four classes:

  • direct-branch

  • direct-call

  • literal-pool

  • pc-relative-address

Direct branch/call references also retain the canonical ARM/Thumb target mode when control-flow decoding proves it. Data/address references such as literal-pool slots do not receive an invented target mode.

literal-pool means the architecturally computed literal-pool slot address. The word stored in that slot is not automatically interpreted as another pointer or reference. Ordinary immediates are not references merely because their numeric value looks like a ROM/RAM address, and this milestone performs no register-value or broader data-flow inference.

nds_list_references is source → reference analysis. It decodes one bounded sequential ARM/Thumb window using the same source policy as nds_disassemble_range, classifies each decoded instruction, and does not follow branches or calls. Its bounds are therefore the same:

Limit

Default

Maximum

Instructions

32

256

Source bytes

128

1,024

nds_find_xrefs is target → cross-reference analysis. It scans only caller-selected static scope for one processor, using deterministic FIFO traversal from proven code seeds. Main code has one implicit ARM seed at the processor's NDS header entry point. Overlays are scanned only when the caller supplies an explicit aligned ARM/Thumb seed for that uncompressed overlay or a proven direct branch/call from already scanned code reaches it. Selecting an overlay does not imply that it is loaded at runtime.

A direct call may expand xref search coverage because the purpose of this tool is to discover references in proven reachable code. This does not change nds_analyze_control_flow: the CFG tool still records direct calls without traversing their callees.

Reverse-xref search bounds are:

Limit

Default

Maximum

Components

32

128

Basic blocks

128

512

Instructions

2,048

16,384

Decoded source bytes

8 KiB

64 KiB

Traversal edges

512

4,096

Returned xrefs

256

2,048

The result status is one of:

  • complete: all selected/considered components had proven seeds and their bounded reachable work completed;

  • partial-coverage: at least one selected component could not be proven/scanned, but no global scan limit truncated explored work;

  • truncated: one or more scan/result limits prevented complete bounded exploration.

Per-component coverage is explicit:

  • scanned

  • no-proven-seed

  • compressed-overlay-not-decodable

  • out-of-limit

A result containing zero xrefs is definitive for the selected static scope only when status === "complete". A zero-result partial-coverage or truncated response is intentionally not presented as proof that no xref exists.

Runtime targets may preserve resolved, ambiguous-overlay, BSS, compressed-overlay, or unmapped ownership metadata; reference matching still uses the exact requested runtime address. A ROM-offset target is accepted only when that offset maps to exactly one runtime address for the selected processor. Structural/NitroFS-only bytes are not reverse-xref targets in this milestone.

Reference searches are on-demand only. RE-MCP does not create a persistent whole-ROM xref database or index. Raw pattern search is a separate exact byte-level facility and does not change or broaden the deterministic reference classifier. Heuristic pointer discovery and arbitrary immediate-pointer inference remain deferred.

Proven function-entry discovery

nds_discover_functions and nds_analyze_function add a higher-level static layer without broadening the evidence model. A function entry is proven only by one of two sources:

  • the selected processor's NDS main executable entry address in ARM mode (program-entry); or

  • a deterministic resolved direct call whose target address, target ARM/Thumb mode, processor, component, and overlay ownership are exact (direct-call).

The following are explicitly not function proof: direct or conditional branch targets, indirect calls, returns, alignment, prologue-looking bytes, pointer-like constants, selected overlay IDs, or caller-supplied seeds. Explicit seeds provide bounded code-search coverage only.

A proven function identity is deterministic across:

processor + component + overlay ID + runtime address + ARM/Thumb mode

nds_discover_functions starts from the selected main program entry plus any validated coverage-only seeds, analyzes bounded CFGs, and follows deterministic resolved direct calls as function-to-function proof. Recursion and mutual recursion terminate through canonical function identity. Distinct direct call sites remain distinct evidence, while duplicate observations of the same site/target are deduplicated.

Direct branches remain intrafunction CFG edges and do not create functions. Indirect calls remain unresolved. The tool does not infer tail calls, shared epilogues, function ends, or exclusive byte ownership.

Whole-operation discovery bounds are:

Limit

Default

Maximum

Components considered

32

128

Proven functions

128

1,024

Direct call sites

512

8,192

Total basic blocks

512

4,096

Total instructions

4,096

32,768

Total decoded source bytes

32 KiB

256 KiB

Total traversal edges

2,048

16,384

Each individual function CFG is also capped independently:

Per-function CFG limit

Default

Maximum

Basic blocks

64

256

Instructions

512

4,096

Decoded bytes

2 KiB

16 KiB

Traversal edges

128

1,024

Aggregate budgets always dominate. Before a CFG is analyzed, its local limits are clipped to the remaining whole-operation budget so one function cannot overshoot a global cap before returning control.

Discovery status is complete, partial-coverage, or truncated. Component coverage uses the same explicit vocabulary as xref search: scanned, no-proven-seed, compressed-overlay-not-decodable, and out-of-limit. Selecting an overlay does not disambiguate overlapping runtime ownership by itself. A call target becomes a proven function only when the canonical control-flow resolver actually produces one exact source.

nds_analyze_function focuses on one requested processor/address/mode/optional overlay identity. It first requires that identity to resolve uniquely to exact initialized, uncompressed file-backed code. It then returns one proof status:

  • proven: program-entry or at least one exact direct-call proof exists;

  • not-proven-function-entry: the selected proof search completed with no qualifying proof;

  • proof-inconclusive: no proof was found, but truncation or a coverage gap means a negative conclusion would be unsafe.

A positive proof remains proven even when unrelated selected coverage is incomplete; the coverage metadata still reports that incompleteness.

Focused proof-search bounds are:

Limit

Default

Maximum

Components considered

32

128

Blocks decoded

128

512

Instructions decoded

2,048

16,384

Decoded bytes

8 KiB

64 KiB

Traversal edges

512

4,096

Direct-call proof sites

256

2,048

A full target CFG is returned only when the entry is proven, using the standard CFG bounds of 64/256 blocks, 512/4,096 instructions, 2 KiB/16 KiB decoded bytes, and 128/1,024 traversal edges.

Neither function tool claims an end address. Multiple returns, shared epilogues, jump tables, tail branches, interleaved data, and unreachable code make such a claim unsafe under this milestone. Heuristic function discovery and function-boundary ownership inference remain deferred.

This function layer is fully static and does not depend on physical Catalina/DeSmuME Dynamic Debugging acceptance.

Raw pattern and signature discovery

nds_search_pattern searches one validated .nds ROM for one deterministic byte-level pattern. It accepts exactly four pattern kinds:

  • byte-signature

  • integer

  • ascii

  • utf16le

Byte signatures use whitespace-separated exact bytes plus the whole-byte wildcard ??:

12 34 56 78
12 34 ?? 78
AA ?? ?? FF

Concrete bytes must contain exactly two hexadecimal digits. ?? is the only wildcard syntax. Nibble wildcards such as A?, regular expressions, alternation, repetition, fuzzy matching, and all-wildcard signatures are rejected.

Typed integers require an explicit width of 8, 16, or 32 bits, explicit little- or big-endian encoding, and explicit signedness. Alignment defaults to 1 byte and may be set explicitly to 1, 2, or 4 bytes. Alignment is checked against the absolute ROM offset; width never silently implies alignment.

ASCII search is exact and case-sensitive and rejects non-ASCII input. UTF-16LE search is also exact and case-sensitive. Neither string mode appends a null terminator, performs Unicode normalization, folds case, or tries alternate encodings.

Every encoded pattern must contain between 1 and 4,096 bytes.

The search scope is either:

  • whole-rom, which treats the validated ROM file as one physical matching domain; or

  • components, selecting any bounded combination of ARM9 main, ARM7 main, explicit ARM9/ARM7 overlay IDs, NitroFS file IDs, and exact NitroFS paths.

Component selections retain their canonical boundaries even when physical ranges overlap or are adjacent. Overlapping selected physical bytes are scanned once, but a component-scoped match is valid only when its complete byte span lies inside at least one selected canonical component. A signature cannot begin in one adjacent component and finish in another unless one selected component contains the entire span. whole-rom is the explicit mode that permits matches across structural/component boundaries.

Compressed overlays are searchable because this tool operates on physical ROM bytes. RE-MCP searches the exact stored FAT-backed compressed representation and marks the overlay ownership as compressed; it never decompresses the overlay or fabricates a decompressed runtime mapping.

Each physical hit is emitted once in ascending ROM-offset order and preserves every deterministic canonical owner known for the complete hit span. Ownership may include main executable, overlay storage, NitroFS/FAT file, parsed header metadata, FNT, FAT, overlay tables, or unmapped. An owner receives a runtime address only when the entire hit has a deterministic direct file-backed runtime mapping. For an uncompressed overlay this mapping is limited to the initialized prefix min(ramSize, romSize). Compressed overlay storage never receives a runtime address. bannerOffset alone does not define a validated banner extent, so the search tool does not invent banner ownership.

Overlapping matches are preserved. For example, searching AA AA in AA AA AA returns starts at offsets 0 and 1 relative to that region.

Search limits are:

Limit

Default

Maximum

Returned page size

100

1,000

Match-index offset

0

99,999

Physical bytes scanned

64 MiB

512 MiB

Context bytes per side

0

64

Encoded pattern bytes

4,096

Discovered matches

100,000

offset is a match index, not a ROM-byte offset and not a scan-resume cursor. Increasing offset does not extend coverage after a maxScanBytes boundary. To inspect beyond a maxScanBytes boundary, raise the scan budget or narrow/change the selected scope.

A result is complete only when the selected physical scope was fully examined. Otherwise it is truncated, with explicit reasons chosen from:

  • scan-byte-limit

  • match-count-limit

discoveredMatches counts only matches actually established before completion or truncation. nextOffset is non-null only when the current scan has already discovered later matches beyond the returned page; it does not speculate about unscanned bytes. Therefore a zero-hit result is definitive only when status === "complete".

Optional context bytes are informational only and do not affect matching or the physical scan-byte counter. Whole-ROM context is clipped only to ROM bounds. Component-scoped context remains inside a deterministic selected component that fully contains the hit, so it never leaks across an adjacent component boundary.

Pattern hits are byte-level facts only. RE-MCP does not promote a matching integer or byte sequence into a pointer, reference, function, table, or other semantic claim. There is no generic binary search input, caller-supplied byte buffer, arbitrary caller-defined ROM range, output path, persistent signature database, decompression path, or ROM mutation surface.

Controlled extraction

nds_extract_component accepts only canonical component selectors:

  • ARM9 main

  • ARM7 main

  • ARM9 overlay ID

  • ARM7 overlay ID

  • NitroFS file ID or exact parsed NitroFS path

The caller cannot provide a raw ROM offset, byte length, or output destination. RE-MCP chooses the deterministic location below:

analysis/generated/nds/<first-16-sha256-hex>/

Before extraction, RE-MCP verifies that the source ROM still matches the SHA-256 used to construct the canonical map. Extracted artifacts record both the source ROM SHA-256 and their own SHA-256. Compressed overlays are extracted exactly as their stored FAT-backed bytes and remain compressed.

nds_extract_analysis_bundle builds the complete static-analysis package transactionally:

analysis/generated/nds/<sha-prefix>/
├── manifest.json
├── address-map.json
├── filesystem.json
├── overlays.json
├── arm9.bin
├── arm7.bin
└── overlays/
    ├── arm9/
    └── arm7/

The bundle is assembled in a temporary sibling directory and promoted only when complete. If replacement of an existing completed bundle fails, RE-MCP attempts to restore the previous complete bundle. The bundle intentionally does not extract every NitroFS asset; individual assets remain opt-in through nds_extract_component.

Example static-analysis workflow

  1. Call nds_inspect_rom to validate the ROM and obtain the canonical structural summary.

  2. Use nds_list_files, nds_list_overlays, and the address resolvers to identify deterministic code/file relationships.

  3. Call nds_search_pattern to locate an exact/wildcard byte signature, typed constant, or exact string within explicit canonical components or the whole validated ROM.

  4. Call nds_disassemble_range for a bounded ARM/Thumb instruction window at a validated runtime address or ROM offset.

  5. Call nds_list_references when you want deterministic references from a bounded sequential source window without traversal.

  6. Call nds_analyze_control_flow when deterministic non-call direct branch traversal is useful.

  7. Call nds_find_xrefs to search for references to one runtime target within an explicit same-processor static scope; inspect status and component coverage before treating a negative result as definitive.

  8. Call nds_discover_functions to turn program-entry/direct-call evidence into a bounded proven-function call graph, or nds_analyze_function to prove and inspect one exact entry.

  9. Extract a specific validated component with nds_extract_component, or generate the executable/metadata bundle with nds_extract_analysis_bundle, when an external artifact is actually needed.

The canonical static layer still does not implement heuristic function discovery, function-end or exclusive-boundary ownership inference, heuristic pointer discovery, persistent pattern/xref/function indexing, symbol recovery, generic binary disassembly/search, broad code/data heuristics, generic recompression/rebuilding, graphics decoding, runtime overlay-loaded-state detection, Ghidra-to-RE-MCP evidence promotion, watchpoints, ROM mutation, NitroFS rebuilding, or patch generation.

Controlled Ghidra Integration

Ghidra support is optional and deliberately sits on top of the canonical static-analysis layer. The controlled Ghidra MCP surface includes project bootstrap/status plus bounded read-only inspection:

  • nds_ghidra_bootstrap

  • nds_ghidra_status

  • nds_ghidra_inspect_function

  • nds_ghidra_decompile_function

  • nds_ghidra_search_symbols

  • nds_ghidra_list_references

  • nds_ghidra_list_calls

There is no generic Ghidra command, arbitrary script runner, caller-selected project path, loader/language selector, raw Ghidra argument list, arbitrary environment map, or caller-selected output path.

Configuration

RE_MCP_GHIDRA_HOME points to a supported local Ghidra 12.x installation. The reference acceptance release is Ghidra 12.1.2. RE_MCP_GHIDRA_TIMEOUT_MS defaults to 900000 ms (15 minutes) and is capped at 3600000 ms (60 minutes) per headless invocation.

These settings are optional at server startup. All non-Ghidra tools continue to work without them. Calling nds_ghidra_bootstrap without RE_MCP_GHIDRA_HOME returns ghidra-not-configured; nds_ghidra_status only reads deterministic ROM/project state and does not invoke Ghidra.

Example:

RE_MCP_WORKSPACE_ROOT=/absolute/path/to/rom-modding
RE_MCP_GHIDRA_HOME=/absolute/path/to/ghidra_12.1.2_PUBLIC
RE_MCP_GHIDRA_TIMEOUT_MS=900000

RE-MCP derives support/analyzeHeadless beneath RE_MCP_GHIDRA_HOME, requires the installation to expose both ARM:LE:32:v5t and ARM:LE:32:v4t, invokes with an argument array and shell: false, and terminates a headless process if the timeout or RE_MCP_MAX_OUTPUT_BYTES bound is exceeded.

Project and bridge layout

Every full ROM SHA-256 receives an isolated persistent project/state root:

analysis/ghidra/nds/<full-sha256>/
├── project/
└── state/

Replaceable bridge inputs stay separate:

analysis/generated/nds/<sha-prefix>/ghidra-bridge/
├── manifest.json
├── evidence/
├── results/
└── scripts/

The ARM9 program uses ARM:LE:32:v5t; ARM7 uses ARM:LE:32:v4t. NDS overlays are represented as distinct Ghidra overlay address spaces at their canonical runtime offsets, so overlapping overlay addresses remain distinct. Validated decoded compressed-overlay runtime artifacts are imported as derived overlay code while retaining distinct stored-byte provenance; BSS remains runtime-only.

Evidence and analyst-work rules

RE-MCP imports only facts it has already established: canonical mappings, exact ARM/Thumb proven entries, program-entry/direct-call proof, and deterministic direct-call evidence. It does not invent function-body or function-end boundaries for Ghidra. Normal Ghidra auto-analysis runs after RE-MCP evidence is installed; functions, labels, strings, types, references, switch recovery, decompiler output, and other analysis that Ghidra derives remain non-authoritative to RE-MCP.

Reruns reconcile only RE-MCP-owned metadata and evidence. Analyst-created labels, comments, bookmarks, types, namespaces, function names/signatures, and Ghidra-only discoveries are preserved. If project ownership/state cannot be reconciled safely, RE-MCP returns project-state-mismatch instead of overwriting the project.

nds_ghidra_status is non-mutating: it does not validate/install Ghidra, regenerate the bridge, run analyzeHeadless, or modify project state. Read-only inspection tools require an already-current SHA-scoped project and disable auto-analysis during inspection.

The packaged RE-MCP bundle includes its Ghidra Java resources, but it does not bundle Ghidra itself. Normal CI/package smoke verifies the bridge, resources, runner, state model, and tool registration without downloading Ghidra. Real Ghidra 12.1.2 acceptance is a separate workflow and is also separate from the physical Intel Catalina/DeSmuME debugger acceptance gate. See docs/nds-ghidra-integration.md for the focused integration contract.

Current-stop NDS runtime correlation

nds_correlate_stop_context connects the server-owned stopped DeSmuME ARM9 session to the canonical static-analysis stack without resuming execution. The ROM path comes only from the owned process metadata, and every DeSmuME generation is bound to the full launch-time ROM SHA-256. Correlation reparses/revalidates that exact ROM, uses the observed PC and CPSR ARM/Thumb mode without breakpoint rewind heuristics, and revalidates the source SHA before returning.

Inputs are deliberately narrow:

  • timeoutMs: 100–30000, default 3000

  • nearbyInstructions: 1–32, default 8

  • referenceLimit: 0–64, default 16

  • includeGhidra: boolean, default false

  • decompileGhidraFunction: boolean, default false; requires includeGhidra: true

By default, correlation performs no Ghidra work. With includeGhidra: true, each decodable canonical candidate may be enriched from an already-current Ghidra project that is scoped to the same full ROM SHA-256. Runtime correlation does not bootstrap Ghidra, reconcile/migrate projects, run auto-analysis, or mutate persistent analyst state. decompileGhidraFunction: true requests bounded decompilation only after the exact candidate function is found by the controlled read-only inspection path.

Overlapping overlay candidates remain separate canonical candidates. Each candidate may receive candidate-specific static/Ghidra interpretation using its exact overlay ID, but Ghidra output is never used to claim which overlay is loaded. Compressed overlays use the validated decoded derived runtime image and retain romOffset: null; BSS/runtime-only candidates never receive fabricated instructions.

The result keeps authority classes separate: observed runtime facts, canonical NDS ownership, RE-MCP static evidence, and ghidraDerived inference. A missing/stale Ghidra project is reported as not-ready for enrichment without invalidating an otherwise valid canonical/static correlation result.

Real Ghidra 12.1.2/JDK 21 acceptance covers both ARM9 main code and a compressed-overlay candidate and verifies that the ROM and persistent Ghidra project remain byte-for-byte unchanged. Physical Catalina/DeSmuME debugger acceptance remains a separate gate.

Dynamic-debugging tools

The controlled debugger surface consists of nine MCP tools:

  • desmume_breakpoint_add

  • desmume_breakpoint_remove

  • desmume_breakpoint_list

  • desmume_continue

  • desmume_step_instruction

  • desmume_pause

  • desmume_wait_for_stop

  • desmume_capture_stop_context

  • desmume_executable_ranges_replace

The existing desmume_read_register_packet, desmume_read_memory, desmume_probe_gdb, and desmume_wait_for_gdb tools share the same owned debugger session rather than opening a competing GDB connection.

Dynamic-debugging limits

  • GDB host is fixed to 127.0.0.1 and the ARM9 port recorded for the current owned DeSmuME process.

  • At most 32 active breakpoints are allowed.

  • Breakpoints must resolve inside the main ARM9 executable range or an explicitly allowlisted executable range.

  • ARM breakpoints must be 4-byte aligned; Thumb breakpoints must be 2-byte aligned.

  • auto execution mode fails when ARM versus Thumb remains ambiguous.

  • Continue and stop-wait requests are bounded to at most 30000 ms.

  • Single-step requests allow 1 through 100 instructions, with a bounded wait for every step.

  • Stop context captures 64 bytes around PC and up to 64 bytes from SP, clamped at address-space boundaries.

  • A stop-context request may add at most eight labeled regions, each from 1 through 4096 bytes.

  • Additional executable ranges are capped at 64.

  • Stop-context output is bounded by the configured maxOutputBytes value.

  • Emulator exit, explicit stop, or a new process generation invalidates the old debugger session and session-scoped state.

Example debugger workflow

  1. Call desmume_start with a verified launcher, the intended .nds ROM, and an ARM9 GDB port. RE-MCP parses the ROM header before launch and initializes the debugger with the derived main ARM9 range.

  2. Use desmume_wait_for_gdb or desmume_probe_gdb to confirm the owned stub is reachable.

  3. Add a validated breakpoint with desmume_breakpoint_add. Specify arm or thumb when mode is not already unambiguous.

  4. Call desmume_continue, optionally supplying expectedBreakpointId. Context capture is enabled by default.

  5. Inspect the returned stop reason, decoded pc/cpsr, matched breakpoint, hit count, and bounded memory windows.

  6. Call nds_correlate_stop_context while stopped to map the exact live PC/mode back to canonical code and bounded static evidence; opt into already-current Ghidra enrichment only when needed.

  7. Use desmume_step_instruction for a bounded instruction sequence while stopped.

  8. If execution is running after a timeout, use desmume_wait_for_stop or desmume_pause rather than issuing a stopped-state command.

  9. Remove the breakpoint with desmume_breakpoint_remove when finished.

  10. Call desmume_stop or restart the emulator. Session-scoped breakpoints, executable ranges, stop state, and the old GDB connection are invalidated.

Requirements

  • Node.js 20 or newer

  • An MCP host that can launch local stdio servers

  • A dedicated workspace containing the intended repositories and private ROM-development inputs

  • For Ghidra bootstrap/inspection or optional runtime-correlation enrichment, a supported local Ghidra 12.x installation; Ghidra 12.1.2 is the reference acceptance release

  • For emulator tools, a verified DeSmuME debug bundle

Downloadable RE-MCP bundle

The Package GitHub Actions workflow publishes a re-mcp-downloadable-bundle artifact containing:

  • Compiled JavaScript

  • Production dependencies, including the pinned Capstone.js WebAssembly backend

  • RE-MCP-owned Ghidra Java bridge/inspection resources

  • Configuration template

  • Installation self-check

  • SHA-256 checksum

Before publishing the artifact, the package workflow performs a production-only install inside the assembled bundle, verifies the packaged Ghidra resources and controlled tool registration, requires the runtime-correlation service/Ghidra adapter/tool modules, initializes the packaged Capstone.js runtime, decodes known ARM and Thumb instructions, smoke-classifies an ARM direct call plus a Thumb PC-relative literal-slot reference, smoke-searches a temporary valid NDS ROM through the compiled pattern-search service to verify wildcard overlap and canonical ARM9 ownership, and runs a packaged ARM9 BL fixture through proven-function discovery to verify program-entry/direct-call proof and call-edge construction. The package check does not require a Ghidra installation or external disassembler download.

After downloading and extracting the archive:

cd re-mcp-0.6.0
node scripts/check-install.mjs .

The same self-check verifies the required package files, assembled function/Ghidra/runtime-correlation tool registration, Ghidra resources, ARM/Thumb decoder fixtures, deterministic reference classifier, packaged NDS pattern-search path, and packaged proven-function discovery path before reporting ok: true.

Copy mcp-config.example.json, replace the required workspace/server paths, and either set the optional Ghidra paths for Ghidra bootstrap/inspection/runtime enrichment or remove those optional environment entries when Ghidra tools are not needed.

Build the Catalina-native DeSmuME debugger bundle

The manual Build Catalina-Native DeSmuME Debug Bundle workflow builds the DeSmuME 0.9.13 Cocoa dev+ application for Intel x86_64 Macs with a macOS 10.15 deployment target. It applies a narrow patch that starts the existing ARM9 GDB stub when RE_MCP_ARM9_GDB_PORT is supplied.

To run it:

  1. Open the repository's Actions tab.

  2. Select Build Catalina-Native DeSmuME Debug Bundle.

  3. Select branch feature/catalina-native-desmume.

  4. Choose Run workflow.

  5. Download desmume-catalina-native-debug-bundle after the job finishes.

  6. Verify desmume-catalina-native-debug.zip against the accompanying .zip.sha256 file before extraction.

After extraction on the Catalina Mac:

xattr -dr com.apple.quarantine desmume-catalina-native-debug
chmod +x desmume-catalina-native-debug/run-desmume-debug.command
./desmume-catalina-native-debug/run-desmume-debug.command \
  /absolute/path/to/Bakugan.nds 20000

Only remove quarantine after verifying the checksum and confirming that the artifact came from the expected workflow run.

Catalina dynamic-debugging acceptance

Automated CI verifies packet framing, breakpoint lifecycle, execution state, timeout behavior, register decoding, context capture, lifecycle reset, and MCP validation. Final acceptance still requires the verified native DeSmuME bundle on the target Intel macOS Catalina system.

Follow docs/dynamic-debugging-catalina-acceptance.md to verify breakpoint installation, continue/stop, PC and CPSR capture, single stepping, pause, breakpoint removal, debugger-state reset after emulator restart, and the final stopped nds_correlate_stop_context check. That final check must confirm the launch SHA-256, observed PC/CPSR, canonical candidates, and bounded static interpretation against the real stop.

Build RE-MCP from source

npm install
npm run check
npm run build

Run directly without Ghidra integration:

RE_MCP_WORKSPACE_ROOT=/absolute/path/to/rom-modding \
node dist/index.js

Run with the optional controlled Ghidra integration enabled:

RE_MCP_WORKSPACE_ROOT=/absolute/path/to/rom-modding \
RE_MCP_GHIDRA_HOME=/absolute/path/to/ghidra_12.1.2_PUBLIC \
RE_MCP_GHIDRA_TIMEOUT_MS=900000 \
node dist/index.js

The server refuses to start without an explicit workspace root. A Ghidra home is not required unless a Ghidra operation is requested.

DeSmuME launcher contract

The currently verified Linux launcher contract is:

run-desmume-debug.sh --arm9gdb=20000 /path/to/game.nds

The Catalina-native bundle uses:

run-desmume-debug.command /path/to/game.nds 20000

RE-MCP owns at most one emulator child process per server instance. It rejects duplicate starts, captures bounded logs, resets session-scoped debugger state when that process exits, and terminates the owned emulator during MCP shutdown.

Security model

  • No arbitrary shell tool

  • No shell interpolation

  • Fixed executable and argument construction

  • Workspace path containment

  • Process timeouts and bounded output

  • Minimal child-process environment

  • Milestone 6E installation restricted to dry-run mode

  • One server-owned DeSmuME process

  • GDB restricted to the owned localhost ARM9 port

  • Breakpoints restricted to validated executable ranges

  • Maximum 32 active breakpoints and 100 instructions per single-step request

  • Bounded continue, wait, pause, register, memory, and stop-context operations

  • Controlled GDB packets only: software breakpoint insert/remove, continue, single-step, interrupt, register read, bounded memory read, and stop-status query

  • No arbitrary GDB packet tool

  • No register writes, general memory writes, or watchpoints

  • Runtime evidence restricted to project analysis/generated

  • Runtime correlation derives ROM identity, stopped PC/mode, and process generation only from the owned session; callers cannot supply a ROM path, PC/registers, processor, overlay selector, arbitrary GDB memory range, or arbitrary Ghidra project/program/script path

  • Runtime correlation defaults to zero Ghidra work; optional Ghidra enrichment requires an already-current full-SHA-scoped project and never bootstraps/reconciles/mutates it

  • Runtime correlation preserves overlapping overlay candidates and never turns candidate-specific static/Ghidra interpretation into a loaded-overlay claim

  • NDS source ROMs are read-only; generated static-analysis artifacts are restricted to analysis/generated/nds/<sha-prefix>/

  • NDS extraction accepts canonical component selectors only; no raw offset/length extraction or caller-controlled output path

  • NDS disassembly and reference listing accept canonical NDS code mappings only; no generic binary path, caller-provided byte buffer, arbitrary base address, or arbitrary raw byte range

  • nds_search_pattern accepts only a validated NDS ROM plus canonical component scope or explicit whole-ROM scope; no generic binary path, caller-supplied byte buffer, caller-defined start/end range, runtime-memory target, or output path

  • Pattern search is bounded to 4,096 encoded pattern bytes, 512 MiB scanned bytes, 1,000 returned hits per page, 100,000 discovered matches, and 64 context bytes per side

  • Component-scoped pattern hits require full-span containment in at least one selected canonical component; physical overlap is deduplicated and adjacent components do not authorize cross-boundary matches

  • Compressed overlays are pattern-searched only as exact stored FAT-backed bytes; pattern search performs no decompression and fabricates no compressed-runtime address mapping

  • Pattern hits remain byte-level facts and are not promoted into pointers, references, functions, or tables

  • ARM/Thumb linear decoding and source-reference listing are bounded to 256 instructions and 1,024 bytes per request

  • CFG traversal is bounded to 256 blocks, 4,096 instructions, 16 KiB decoded bytes, and 1,024 traversal edges

  • Reverse-xref traversal is bounded to 128 components, 512 blocks, 16,384 instructions, 64 KiB decoded bytes, 4,096 traversal edges, and 2,048 returned xrefs

  • Only deterministic single-instruction direct branch/call, literal-pool-slot, and PC-relative address-construction references are emitted

  • Literal-pool contents and pointer-looking ordinary immediates are not interpreted as references

  • nds_find_xrefs may follow proven direct calls for search coverage, while nds_analyze_control_flow continues to annotate calls without traversing them

  • Reverse-xref coverage gaps and truncation are explicit; a zero-xref result is definitive for selected scope only when status is complete

  • Proven function discovery is bounded to 128 selected components, 1,024 functions, 8,192 direct call sites, 4,096 blocks, 32,768 instructions, 256 KiB decoded bytes, and 16,384 traversal edges

  • Focused function proof is bounded to 128 components, 512 blocks, 16,384 instructions, 64 KiB decoded bytes, 4,096 traversal edges, and 2,048 retained direct-call proof sites

  • Function entries are proven only by NDS program-entry or exact deterministic direct-call evidence; direct branches, indirect calls, returns, explicit seeds, alignment, and prologue-like bytes do not prove functions

  • Function tools do not infer end addresses, tail calls, shared-epilogue ownership, or exclusive function byte ranges

  • Function proof preserves exact processor/component/overlay/address/mode identity; scope selection never turns ambiguous overlay ownership into proof

  • Explicit function seeds provide coverage only; unseeded components remain explicit coverage gaps and incomplete negative proof returns proof-inconclusive

  • No persistent pattern/xref/function index or heuristic pointer/function discovery

  • Indirect targets are never guessed

  • Compressed overlay runtime code is consumed only from validated decoded derived artifacts and never receives a fabricated direct ROM offset; BSS remains non-decodable runtime-only memory

  • Overlapping static overlay ranges are reported as ambiguous candidates rather than guessed

  • Static overlay selection/disassembly/reference/function search never claims that an overlay is loaded at runtime

  • Static operations revalidate the source ROM SHA-256 before and after decoding/searching

  • Ghidra bootstrap derives one analyzeHeadless executable from RE_MCP_GHIDRA_HOME; callers cannot provide executable paths, project paths, loaders, languages, scripts, raw Ghidra arguments, environment maps, or output paths

  • Ghidra projects are isolated by full source ROM SHA-256; generated bridge inputs remain separate from persistent analyst state

  • NDS overlays use distinct Ghidra overlay address spaces; validated compressed-overlay runtime images remain derived and separate from stored-byte provenance

  • RE-MCP imports proven entries/modes/direct-call evidence only and does not promote Ghidra-derived functions, bodies, types, or other heuristics into canonical RE-MCP evidence

  • Ghidra reruns preserve analyst-created state and fail project-state-mismatch instead of destructively repairing unrecognized ownership

  • Ghidra headless execution is shell-free, timeout-bounded, output-bounded, and source-ROM identity is revalidated during bootstrap

  • nds_ghidra_status is non-mutating and does not invoke Ghidra

  • Controlled Ghidra inspection and correlation enrichment are read-only and disable auto-analysis; correlation does not create or repair projects

  • Debugger session, breakpoint registry, executable ranges, and stop state reset with emulator lifecycle

  • No attachment to unrelated emulator processes

Do not use your general home directory as RE_MCP_WORKSPACE_ROOT. Create a dedicated directory containing only the repositories and private inputs intended for RE-MCP.

F
license - not found
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    An MCP server providing direct access to gdb-multiarch for debugging Nintendo Switch executables on Yuzu or hardware via a GDB stub. It features specialized tools for offset-based breakpoints, instruction patching, and frame-pointer backtraces relative to the game's base address.
    33
    3
    MIT
  • A
    license
    -
    quality
    A
    maintenance
    MCP server for reverse engineering Windows executables and related binary formats, offering static analysis, Ghidra-assisted function recovery, plugin-driven tooling, and optional isolated Windows runtime execution.
    5
    238
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

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/79cbd8hmgj-wq/RE-MCP'

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