Skip to content

feat: add DNCDPRG.lst symbol table with MCP tools#87

Closed
Copilot wants to merge 8 commits into
mainfrom
copilot/add-symbol-naming-recovery
Closed

feat: add DNCDPRG.lst symbol table with MCP tools#87
Copilot wants to merge 8 commits into
mainfrom
copilot/add-symbol-naming-recovery

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 7, 2026

Description of Changes

  • Added src/Cryogenic/Symbols/ with SRP classes: LstSymbol, LstSymbolLookup, ILstSymbolParser, ILstSymbolTable, LstSymbolParser, LstSymbolTable, LstSymbolTableFactory.
  • ILstSymbolTable.FindByAddress(string segment, ushort offset) returns a non-nullable LstSymbolLookup wrapper record (Found flag + non-null Symbol). No Try pattern, no nullable returns, no exceptions in the symbol API.
  • Embedded doc/DNCDPRG.lst as Cryogenic.DNCDPRG.lst.
  • Exposed three MCP tools on a new non-static CryogenicSymbolMcpTools class: cryogenic_symbol_lookup, cryogenic_symbol_search, cryogenic_symbols_list. The class receives its ILstSymbolTable through constructor injection — no static state, no calculated properties, no locks, no nullables.
  • The shared ILstSymbolTable is built once and registered as a singleton MCP service in DuneCdOverrideSupplier.GetMcpServices() so the ModelContextProtocol activator can resolve CryogenicSymbolMcpTools via DI.
  • Removed the static _symbolTable field, the SymbolTable calculated property, and the SymbolTableLock from CryogenicMcpTools.
  • Created the src/Cryogenic.Tests/ xUnit project (net10.0) and added it to Cryogenic.sln.
  • Added AwesomeAssertions 9.4.0 to the test project and structured all 30 tests with the Arrange / Act / Assert pattern using Should() fluent assertions.
  • Removed the // ═══ banner comments introduced by this change.

Rationale behind Changes

The IDA-style listing (DNCDPRG.lst) carries 380 named symbols and 3303 auto-labels for seg000 that are extremely useful for live reverse-engineering work. Exposing it through MCP tools lets the symbol names show up next to addresses during runtime inspection without manual lookups, and centralising parsing/lookup behind a small SRP-friendly API keeps the surface easy to test and extend. Splitting the symbol tools into their own non-static class with constructor-injected dependencies aligns with the project's convention of avoiding static state, calculated properties, locks for frozen data, and nullable types. Returning a LstSymbolLookup wrapper instead of a Try-style API or a nullable result keeps the public contract free of nullables and exceptions while still letting callers branch on a simple Found flag. The test refactor follows the project's preferred testing convention (AAA + AwesomeAssertions).

Suggested Testing Steps

  • From src/, run dotnet build — should be 0 warnings, 0 errors.
  • From src/, run dotnet test Cryogenic.Tests/Cryogenic.Tests.csproj — all 30 tests should pass.
  • Start the Cryogenic MCP server and call cryogenic_symbol_lookup for seg000:0000 (expect Found = true with start), cryogenic_symbol_search for play_intro, and cryogenic_symbols_list (expect a non-empty named-symbol list).

Copilot AI and others added 3 commits May 7, 2026 04:56
Agent-Logs-Url: https://github.com/OpenRakis/Cryogenic/sessions/ebdbdb95-7aa3-47ae-b6b2-f8f2c4b50d76

Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
Agent-Logs-Url: https://github.com/OpenRakis/Cryogenic/sessions/ebdbdb95-7aa3-47ae-b6b2-f8f2c4b50d76

Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
Agent-Logs-Url: https://github.com/OpenRakis/Cryogenic/sessions/daf9699d-2c53-4056-9887-5ae6e715fab4

Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
Copy link
Copy Markdown
Member

@maximilien-noal maximilien-noal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot use arrange act assert for tests.

Use AwesomeAssertions.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 7, 2026

@copilot use arrange act assert for tests.

Use AwesomeAssertions.

Done in 919ca80. Added AwesomeAssertions 9.4.0 to Cryogenic.Tests.csproj and refactored all 30 tests to follow Arrange/Act/Assert with Should() assertions. All tests pass.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • av-build-tel-api-v1.avaloniaui.net
    • Triggering command: /usr/share/dotnet/dotnet dotnet exec --runtimeconfig /home/REDACTED/.nuget/packages/avalonia.buildservices/11.3.2/tools/netstandard2.0/runtimeconfig.json /home/REDACTED/.nuget/packages/avalonia.buildservices/11.3.2/tools/netstandard2.0/Avalonia.BuildServices.Collector.dll (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from maximilien-noal May 7, 2026 05:25
@maximilien-noal maximilien-noal marked this pull request as ready for review May 7, 2026 05:28
Copy link
Copy Markdown
Member

@maximilien-noal maximilien-noal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot never use calculated properties.

Don't use lock for a frozen hash set !

Use injection of dependencies via constructors.

Make the entire class not static.

Do not use static properties.

Do not use nullables.

…es/lock

Agent-Logs-Url: https://github.com/OpenRakis/Cryogenic/sessions/a093cd2e-0009-496d-9f3b-3d76408c6ec4

Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 7, 2026

@copilot never use calculated properties.

Don't use lock for a frozen hash set !...

Done in adf734b.

  • Extracted symbol tools into a new non-static CryogenicSymbolMcpTools class with constructor-injected ILstSymbolTable.
  • Removed the calculated SymbolTable property, the _symbolTable nullable field, and the SymbolTableLock from CryogenicMcpTools.
  • Replaced LstSymbol? FindByAddress(...) with bool TryFindByAddress(string segment, ushort offset, out LstSymbol symbol) so callers no longer deal with nullable returns.
  • SymbolLookupResponse.Symbol is now non-nullable; an empty placeholder is returned when Found is false.
  • Registered ILstSymbolTable as a shared MCP service in DuneCdOverrideSupplier.GetMcpServices() so the new instance class is resolved via DI.
  • Build clean, all 30 tests still pass.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • av-build-tel-api-v1.avaloniaui.net
    • Triggering command: /usr/share/dotnet/dotnet dotnet exec --runtimeconfig /home/REDACTED/.nuget/packages/avalonia.buildservices/11.3.2/tools/netstandard2.0/runtimeconfig.json /home/REDACTED/.nuget/packages/avalonia.buildservices/11.3.2/tools/netstandard2.0/Avalonia.BuildServices.Collector.dll (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI and others added 2 commits May 7, 2026 05:40
@maximilien-noal maximilien-noal added the MCP MCP server feature label May 7, 2026
Copilot AI review requested due to automatic review settings May 7, 2026 06:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a parsed symbol table for the embedded DNCDPRG.lst listing and exposes it via MCP tools to support live reverse-engineering workflows in Cryogenic/Spice86.

Changes:

  • Added a Cryogenic.Symbols SRP-style symbol parsing/lookup API plus an embedded DNCDPRG.lst resource.
  • Added a non-static MCP tool class (CryogenicSymbolMcpTools) for symbol lookup/search/list, backed by DI-provided ILstSymbolTable.
  • Added an xUnit test project with AwesomeAssertions coverage for the parser/table/factory.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/Cryogenic/Symbols/LstSymbolTableFactory.cs Loads embedded .lst resource and builds a symbol table via parser
src/Cryogenic/Symbols/LstSymbolTable.cs Implements address/name lookup over parsed symbols
src/Cryogenic/Symbols/LstSymbolParser.cs Parses IDA-style listing label lines into LstSymbol entries
src/Cryogenic/Symbols/LstSymbolLookup.cs Non-nullable lookup-result wrapper (Found + Symbol)
src/Cryogenic/Symbols/LstSymbol.cs Symbol record model (segment/offset/name/autolabel)
src/Cryogenic/Symbols/ILstSymbolTable.cs Public symbol table lookup/list API contract
src/Cryogenic/Symbols/ILstSymbolParser.cs Parser interface for .lst lines
src/Cryogenic/DuneCdOverrideSupplier.cs Registers ILstSymbolTable as an MCP service
src/Cryogenic/CryogenicSymbolMcpTools.cs New MCP tools: lookup/search/list for .lst symbols
src/Cryogenic/CryogenicMcpTools.cs Whitespace-only change at EOF
src/Cryogenic/Cryogenic.csproj Embeds doc/DNCDPRG.lst as Cryogenic.DNCDPRG.lst
src/Cryogenic.Tests/Symbols/LstSymbolTableTests.cs Unit tests for table lookup behaviors
src/Cryogenic.Tests/Symbols/LstSymbolTableFactoryTests.cs Integration tests validating embedded resource loading
src/Cryogenic.Tests/Symbols/LstSymbolParserTests.cs Unit tests for parser correctness and edge cases
src/Cryogenic.Tests/Cryogenic.Tests.csproj New xUnit test project with AwesomeAssertions + tooling
src/Cryogenic.sln Adds the new test project to the solution

Comment on lines +42 to +47
public ILstSymbolTable Build() {
Assembly assembly = typeof(LstSymbolTableFactory).Assembly;
using Stream? stream = assembly.GetManifestResourceStream(ResourceName);
if (stream is null) {
return new LstSymbolTable(new List<LstSymbol>());
}
Comment on lines +55 to +60
private static IEnumerable<string> ReadLines(StreamReader reader) {
while (!reader.EndOfStream) {
string? line = reader.ReadLine();
if (line is not null) {
yield return line;
}
Comment on lines +38 to +41
public LstSymbolLookup FindByAddress(string segment, ushort offset) {
if (_byAddress.TryGetValue((segment, offset), out LstSymbol? sym)) {
return new LstSymbolLookup { Found = true, Symbol = sym };
}
Comment on lines +36 to +47
/// <returns>All matching symbols ordered by segment then offset.</returns>
IReadOnlyList<LstSymbol> FindByName(string nameSubstring, bool includeAutoLabels = false);

/// <summary>
/// Returns all symbols in the table.
/// </summary>
/// <param name="includeAutoLabels">
/// When <c>true</c>, includes auto-generated labels (loc_, sub_).
/// Defaults to <c>false</c>.
/// </param>
/// <returns>All symbols ordered by segment then offset.</returns>
IReadOnlyList<LstSymbol> GetAll(bool includeAutoLabels = false);
Comment on lines +29 to +47
/// (case-insensitive, ordinal comparison).
/// </summary>
/// <param name="nameSubstring">Substring to match against symbol names.</param>
/// <param name="includeAutoLabels">
/// When <c>true</c>, includes auto-generated labels whose names start with "loc_" or "sub_".
/// Defaults to <c>false</c> so only meaningful reverse-engineered names are returned.
/// </param>
/// <returns>All matching symbols ordered by segment then offset.</returns>
IReadOnlyList<LstSymbol> FindByName(string nameSubstring, bool includeAutoLabels = false);

/// <summary>
/// Returns all symbols in the table.
/// </summary>
/// <param name="includeAutoLabels">
/// When <c>true</c>, includes auto-generated labels (loc_, sub_).
/// Defaults to <c>false</c>.
/// </param>
/// <returns>All symbols ordered by segment then offset.</returns>
IReadOnlyList<LstSymbol> GetAll(bool includeAutoLabels = false);
[McpServerTool(Name = "cryogenic_symbol_lookup", UseStructuredContent = true)]
[Description("Look up the DNCDPRG.lst symbol defined at a given segment and offset. Parameters: segment (string, e.g. \"seg000\"), offset (int, decimal value of the hex offset, e.g. 0x00B0 = 176). Returns found flag, address, and symbol details.")]
public SymbolLookupResponse CryogenicSymbolLookup(string segment, int offset) {
Logger.Information("MCP tool invoked: cryogenic_symbol_lookup Segment={Segment}, Offset=0x{OffsetHex:X4}", segment, offset);
Comment on lines +154 to +176
[McpServerTool(Name = "cryogenic_symbol_search", UseStructuredContent = true)]
[Description("Search the DNCDPRG.lst symbol table by name substring. Parameters: nameSubstring (string, case-insensitive, empty = all), includeAutoLabels (bool, default false). Returns list of matching symbols with segment, offset, and name.")]
public SymbolSearchResponse CryogenicSymbolSearch(string nameSubstring, bool includeAutoLabels) {
Logger.Information("MCP tool invoked: cryogenic_symbol_search Query={Query}, IncludeAutoLabels={IncludeAutoLabels}", nameSubstring, includeAutoLabels);
IReadOnlyList<LstSymbol> matches = _symbolTable.FindByName(nameSubstring, includeAutoLabels);
return new SymbolSearchResponse {
Query = nameSubstring,
IncludedAutoLabels = includeAutoLabels,
MatchCount = matches.Count,
Matches = matches.Select(ToSymbolEntry).ToList()
};
}

/// <summary>
/// Returns all named symbols from the DNCDPRG.lst symbol table, optionally including auto-labels.
/// </summary>
/// <param name="includeAutoLabels">
/// When <c>true</c>, includes IDA-generated labels (loc_, sub_).
/// </param>
/// <returns>A <see cref="SymbolListResponse"/> containing all symbols in listing order.</returns>
[McpServerTool(Name = "cryogenic_symbols_list", UseStructuredContent = true)]
[Description("List all symbols from the embedded DNCDPRG.lst disassembly. Parameters: includeAutoLabels (bool, default false — omit loc_/sub_ labels). Returns the full symbol set with segment, hex offset, and name.")]
public SymbolListResponse CryogenicSymbolsList(bool includeAutoLabels) {
Comment on lines 87 to +100
/// <summary>
/// Provides service instances available for MCP tool method injection.
/// No service instances are currently required for Cryogenic tools.
/// Supplies a singleton <see cref="ILstSymbolTable"/> built from the embedded DNCDPRG.lst
/// resource so <see cref="CryogenicSymbolMcpTools"/> can resolve it via constructor injection.
/// </summary>
/// <returns>Service instances for MCP tool execution.</returns>
public IEnumerable<object> GetMcpServices() {
Logger.Debug("Supplying MCP services collection (empty).");
return Array.Empty<object>();
ILstSymbolParser parser = new LstSymbolParser();
LstSymbolTableFactory factory = new(parser);
ILstSymbolTable symbolTable = factory.Build();
Logger.Information(
"Supplying ILstSymbolTable as MCP service. TotalSymbols={Total}",
symbolTable.GetAll(includeAutoLabels: true).Count);
return new object[] { symbolTable };
Comment thread src/Cryogenic.sln
..\.github\workflows\release.yml = ..\.github\workflows\release.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cryogenic.Tests", "Cryogenic.Tests\Cryogenic.Tests.csproj", "{116BEFB9-B935-45B6-83B3-ABE18B0E529F}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

MCP MCP server feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants