UNPKG

il2cpp-dump-analyzer-mcp

Version:

Agentic RAG system for analyzing IL2CPP dump.cs files from Unity games

493 lines 19.9 kB
"use strict"; /** * @fileoverview Analyze Type Dependencies MCP Tool * * Provides comprehensive analysis of IL2CPP type dependency graphs including: * - Type dependency graph creation and visualization * - Circular reference detection and analysis * - Dependency clustering and relationship mapping * - Dependency metrics and complexity analysis * * This tool implements the type dependency analysis functionality from TypeAnalyzer * as an MCP tool following established patterns and TFD methodology. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.analyzeTypeDependenciesSchema = exports.AnalyzeTypeDependenciesTool = void 0; exports.createAnalyzeTypeDependenciesTool = createAnalyzeTypeDependenciesTool; const zod_1 = require("zod"); const base_tool_handler_1 = require("../base-tool-handler"); const mcp_response_formatter_1 = require("../../utils/mcp-response-formatter"); /** * Zod schema for analyze type dependencies parameters */ const AnalyzeTypeDependenciesSchema = zod_1.z.object({ target_type: zod_1.z.string().optional().describe('Specific type to analyze dependencies for (optional, analyzes all types if not provided)'), include_circular_detection: zod_1.z.boolean().default(true).describe('Whether to include circular dependency detection'), max_depth: zod_1.z.number().min(1).max(10).default(5).describe('Maximum dependency depth to analyze (1-10)'), include_system_types: zod_1.z.boolean().default(false).describe('Whether to include system types in dependency analysis') }); /** * Analyze Type Dependencies MCP Tool Implementation * * Analyzes IL2CPP type dependency graphs and relationships using vector store search. * Provides comprehensive dependency analysis with circular detection and clustering. */ class AnalyzeTypeDependenciesTool extends base_tool_handler_1.BaseAnalysisToolHandler { constructor(context) { super({ name: 'analyze_type_dependencies', description: 'Analyze type dependency graphs, circular references, and dependency clusters in IL2CPP dumps', enableParameterValidation: true, enableResponseFormatting: true }, context); } /** * Validate input parameters using Zod schema */ async validateParameters(params) { const errors = []; const warnings = []; const adjustedValues = {}; // Validate max_depth parameter if (params.max_depth !== undefined) { if (typeof params.max_depth !== 'number' || params.max_depth < 1 || params.max_depth > 10) { errors.push('max_depth must be a number between 1 and 10'); } } else { adjustedValues.max_depth = 5; } // Set defaults for boolean parameters if (params.include_circular_detection === undefined) { adjustedValues.include_circular_detection = true; } if (params.include_system_types === undefined) { adjustedValues.include_system_types = false; } // Validate target_type if provided if (params.target_type && typeof params.target_type !== 'string') { errors.push('target_type must be a string'); } return { isValid: errors.length === 0, errors, warnings, adjustedValues }; } /** * Execute type dependency analysis */ async executeCore(params) { return await this.performAnalysis(async () => { this.context.logger.debug('Starting type dependency analysis', { params }); // Step 1: Get all types or specific target type let typeDocuments; if (params.target_type) { // Search for specific target type const targetResults = await this.context.vectorStore.searchWithFilter(params.target_type, { type: ['class', 'interface'] }, 1); if (targetResults.length === 0) { throw new Error(`Target type '${params.target_type}' not found in IL2CPP dump`); } // Get all types for dependency analysis typeDocuments = await this.context.vectorStore.searchWithFilter('', { type: ['class', 'interface'] }, 1000); } else { // Get all types typeDocuments = await this.context.vectorStore.searchWithFilter('', { type: ['class', 'interface'] }, 1000); } if (typeDocuments.length === 0) { throw new Error('No types found in IL2CPP dump for dependency analysis'); } this.context.logger.debug(`Found ${typeDocuments.length} types for dependency analysis`); // Step 2: Build dependency graph const { nodes, edges } = this.buildDependencyGraph(typeDocuments, params); // Step 3: Create type clusters and detect circular dependencies let clusters = []; if (params.include_circular_detection) { clusters = this.createTypeClusters(nodes, edges); } // Step 4: Calculate dependency metrics const metrics = this.calculateDependencyMetrics(nodes, edges, clusters); const result = { nodes, edges, clusters, metrics, analysisMetadata: { targetType: params.target_type, includeCircularDetection: params.include_circular_detection ?? true, maxDepthLimit: params.max_depth ?? 5, includeSystemTypes: params.include_system_types ?? false, timestamp: new Date().toISOString(), totalTypesAnalyzed: typeDocuments.length } }; this.context.logger.debug('Type dependency analysis completed', { nodes: nodes.length, edges: edges.length, clusters: clusters.length, circularDependencies: metrics.circularDependencies }); return result; }); } /** * Build dependency graph from type documents */ buildDependencyGraph(typeDocuments, params) { const nodes = []; const edges = []; const includeSystemTypes = params.include_system_types ?? false; // Create type lookup map const typeMap = new Map(); typeDocuments.forEach(doc => { const fullName = `${doc.metadata.namespace}.${doc.metadata.name}`; typeMap.set(fullName, doc); }); // Helper function to check if a type is a system type const isSystemType = (typeName) => { if (!typeName) return false; const systemPrefixes = ['System.', 'UnityEngine.', 'Unity.', 'Microsoft.', 'Mono.']; return systemPrefixes.some(prefix => typeName.startsWith(prefix)); }; // Build nodes and edges for (const doc of typeDocuments) { const fullName = `${doc.metadata.namespace}.${doc.metadata.name}`; // Skip system types if not included if (!includeSystemTypes && isSystemType(fullName)) { continue; } const node = this.createDependencyNode(doc, typeDocuments, includeSystemTypes); nodes.push(node); // Create edges for dependencies for (const dependency of node.dependencies) { if (!includeSystemTypes && isSystemType(dependency)) { continue; } edges.push({ fromType: fullName, toType: dependency, relationshipType: this.getRelationshipType(doc, dependency), strength: 1 }); } } return { nodes, edges }; } /** * Create a dependency node from a type document */ createDependencyNode(doc, allTypes, includeSystemTypes) { const fullName = `${doc.metadata.namespace}.${doc.metadata.name}`; const dependencies = []; // Add base class dependency if (doc.metadata.baseClass) { const baseFullName = doc.metadata.baseClass.includes('.') ? doc.metadata.baseClass : `${doc.metadata.namespace}.${doc.metadata.baseClass}`; if (includeSystemTypes || !this.isSystemType(baseFullName)) { dependencies.push(baseFullName); } } // Add interface dependencies if (doc.metadata.interfaces) { for (const interfaceName of doc.metadata.interfaces) { const interfaceFullName = interfaceName.includes('.') ? interfaceName : `${doc.metadata.namespace}.${interfaceName}`; if (includeSystemTypes || !this.isSystemType(interfaceFullName)) { dependencies.push(interfaceFullName); } } } // Find dependents (types that depend on this type) const dependents = allTypes .filter(t => { const tFullName = `${t.metadata.namespace}.${t.metadata.name}`; return (t.metadata.baseClass === doc.metadata.name || t.metadata.baseClass === fullName || (t.metadata.interfaces && t.metadata.interfaces.includes(doc.metadata.name))) && (includeSystemTypes || !this.isSystemType(tFullName)); }) .map(t => `${t.metadata.namespace}.${t.metadata.name}`); // Calculate centrality (simplified betweenness centrality) const centrality = (dependencies.length + dependents.length) / allTypes.length; return { typeName: fullName, namespace: doc.metadata.namespace || '', typeDefIndex: doc.metadata.typeDefIndex || 0, dependencies, dependents, dependencyCount: dependencies.length, dependentCount: dependents.length, centrality }; } /** * Check if a type is a system type */ isSystemType(typeName) { if (!typeName) return false; const systemPrefixes = ['System.', 'UnityEngine.', 'Unity.', 'Microsoft.', 'Mono.']; return systemPrefixes.some(prefix => typeName.startsWith(prefix)); } /** * Get relationship type between types */ getRelationshipType(doc, dependency) { if (doc.metadata.baseClass === dependency || doc.metadata.baseClass?.endsWith(`.${dependency}`)) { return 'inheritance'; } if (doc.metadata.interfaces?.includes(dependency) || doc.metadata.interfaces?.some((i) => i.endsWith(`.${dependency}`))) { return 'interface'; } return 'dependency'; } /** * Create type clusters and detect circular dependencies */ createTypeClusters(nodes, edges) { const clusters = []; const visited = new Set(); const typeToCluster = new Map(); // Use Union-Find algorithm to detect connected components const parent = new Map(); const rank = new Map(); // Initialize Union-Find for (const node of nodes) { parent.set(node.typeName, node.typeName); rank.set(node.typeName, 0); } // Find function with path compression const find = (x) => { if (parent.get(x) !== x) { parent.set(x, find(parent.get(x))); } return parent.get(x); }; // Union function with rank optimization const union = (x, y) => { const rootX = find(x); const rootY = find(y); if (rootX !== rootY) { const rankX = rank.get(rootX) || 0; const rankY = rank.get(rootY) || 0; if (rankX < rankY) { parent.set(rootX, rootY); } else if (rankX > rankY) { parent.set(rootY, rootX); } else { parent.set(rootY, rootX); rank.set(rootX, rankX + 1); } } }; // Union connected types for (const edge of edges) { union(edge.fromType, edge.toType); } // Group types by cluster const clusterGroups = new Map(); for (const node of nodes) { const root = find(node.typeName); if (!clusterGroups.has(root)) { clusterGroups.set(root, []); } clusterGroups.get(root).push(node.typeName); } // Create cluster objects let clusterId = 0; for (const [root, types] of clusterGroups) { if (types.length > 1) { // Only include clusters with multiple types const cluster = this.createCluster(`cluster_${clusterId++}`, types, edges); clusters.push(cluster); } } return clusters; } /** * Create a cluster object with metrics */ createCluster(clusterId, types, edges) { const typeSet = new Set(types); // Count internal and external edges let internalEdges = 0; let externalEdges = 0; for (const edge of edges) { const fromInCluster = typeSet.has(edge.fromType); const toInCluster = typeSet.has(edge.toType); if (fromInCluster && toInCluster) { internalEdges++; } else if (fromInCluster || toInCluster) { externalEdges++; } } // Detect circular dependencies using DFS const isCircular = this.detectCircularDependency(types, edges); return { clusterId, types, isCircular, clusterSize: types.length, internalEdges, externalEdges }; } /** * Detect circular dependencies within a cluster using DFS */ detectCircularDependency(types, edges) { const typeSet = new Set(types); const graph = new Map(); // Build adjacency list for cluster types only for (const type of types) { graph.set(type, []); } for (const edge of edges) { if (typeSet.has(edge.fromType) && typeSet.has(edge.toType)) { graph.get(edge.fromType).push(edge.toType); } } // DFS to detect cycles const visited = new Set(); const recursionStack = new Set(); const hasCycle = (node) => { visited.add(node); recursionStack.add(node); const neighbors = graph.get(node) || []; for (const neighbor of neighbors) { if (!visited.has(neighbor)) { if (hasCycle(neighbor)) { return true; } } else if (recursionStack.has(neighbor)) { return true; } } recursionStack.delete(node); return false; }; for (const type of types) { if (!visited.has(type)) { if (hasCycle(type)) { return true; } } } return false; } /** * Calculate dependency metrics */ calculateDependencyMetrics(nodes, edges, clusters) { if (nodes.length === 0) { return { totalNodes: 0, totalEdges: 0, averageDependencies: 0, maxDependencies: 0, circularDependencies: 0, maxDepth: 0, clusterCount: 0 }; } const totalNodes = nodes.length; const totalEdges = edges.length; const totalDependencies = nodes.reduce((sum, node) => sum + node.dependencyCount, 0); const averageDependencies = totalDependencies / totalNodes; const maxDependencies = Math.max(...nodes.map(node => node.dependencyCount)); const circularDependencies = clusters.filter(cluster => cluster.isCircular).length; const maxDepth = this.calculateMaxDependencyDepth(nodes, edges); const clusterCount = clusters.length; return { totalNodes, totalEdges, averageDependencies, maxDependencies, circularDependencies, maxDepth, clusterCount }; } /** * Calculate maximum dependency depth using BFS */ calculateMaxDependencyDepth(nodes, edges) { const graph = new Map(); const inDegree = new Map(); // Initialize graph and in-degree count for (const node of nodes) { graph.set(node.typeName, []); inDegree.set(node.typeName, 0); } // Build graph and calculate in-degrees for (const edge of edges) { if (graph.has(edge.fromType) && graph.has(edge.toType)) { graph.get(edge.fromType).push(edge.toType); inDegree.set(edge.toType, (inDegree.get(edge.toType) || 0) + 1); } } // Topological sort with depth tracking const queue = []; let maxDepth = 0; // Start with nodes that have no dependencies for (const [node, degree] of inDegree) { if (degree === 0) { queue.push({ node, depth: 0 }); } } while (queue.length > 0) { const { node, depth } = queue.shift(); maxDepth = Math.max(maxDepth, depth); const neighbors = graph.get(node) || []; for (const neighbor of neighbors) { const newInDegree = (inDegree.get(neighbor) || 0) - 1; inDegree.set(neighbor, newInDegree); if (newInDegree === 0) { queue.push({ node: neighbor, depth: depth + 1 }); } } } return maxDepth; } /** * Format dependency analysis results */ formatResponse(result, warnings = []) { let response = mcp_response_formatter_1.MCPResponseFormatter.formatAnalysisResults(result, this.config.name, result.analysisMetadata, Date.now() - this.startTime); if (warnings.length > 0) { response = mcp_response_formatter_1.MCPResponseFormatter.addWarnings(response, warnings); } return response; } } exports.AnalyzeTypeDependenciesTool = AnalyzeTypeDependenciesTool; /** * Zod schema for analyze type dependencies tool parameters */ exports.analyzeTypeDependenciesSchema = zod_1.z.object({ target_type: zod_1.z.string().optional().describe("Specific type to analyze dependencies for (optional, analyzes all types if not provided)"), include_circular_detection: zod_1.z.boolean().optional().default(true).describe("Include circular dependency detection"), max_depth: zod_1.z.number().min(1).max(10).optional().default(5).describe("Maximum dependency depth to analyze (1-10)"), include_system_types: zod_1.z.boolean().optional().default(false).describe("Include system types in dependency analysis") }); /** * Factory function to create and register the analyze type dependencies tool */ function createAnalyzeTypeDependenciesTool(server, context) { const handler = new AnalyzeTypeDependenciesTool(context); server.tool("analyze_type_dependencies", exports.analyzeTypeDependenciesSchema, async (params) => { return await handler.execute(params); }); return handler; } //# sourceMappingURL=analyze-type-dependencies-tool.js.map