il2cpp-dump-analyzer-mcp
Version:
Agentic RAG system for analyzing IL2CPP dump.cs files from Unity games
290 lines • 11.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.IL2CPPIndexer = void 0;
const path = __importStar(require("path"));
const enhanced_il2cpp_parser_1 = require("../parser/enhanced-il2cpp-parser");
const chunker_1 = require("../embeddings/chunker");
const vector_store_1 = require("../embeddings/vector-store");
const hash_manager_factory_1 = require("../utils/hash-manager-factory");
/**
* Manages the indexing process for IL2CPP dump files
*/
class IL2CPPIndexer {
constructor(chunkSize = 1000, chunkOverlap = 200, model, hashFilePath) {
this.chunkSize = chunkSize;
this.chunkOverlap = chunkOverlap;
this.model = model;
this.parser = new enhanced_il2cpp_parser_1.EnhancedIL2CPPParser();
this.chunker = new chunker_1.IL2CPPCodeChunker(chunkSize, chunkOverlap);
this.vectorStore = new vector_store_1.IL2CPPVectorStore(model);
this.hashManager = (0, hash_manager_factory_1.createHashManagerFromEnv)(hashFilePath);
}
/**
* Index an IL2CPP dump file
* @param filePath Path to the dump.cs file
* @param progressCallback Optional callback for progress updates
* @param forceReprocess Force reprocessing even if file was already processed
* @returns The vector store with indexed content
*/
async indexFile(filePath, progressCallback, forceReprocess = false) {
// Initialize hash manager if it supports async initialization
if (this.hashManager.initialize) {
await this.hashManager.initialize();
}
// Check if file was already processed (unless forced)
let isProcessed = false;
if (!forceReprocess) {
if (this.hashManager.isFileProcessedAsync) {
// Use async method for Supabase hash manager
isProcessed = await this.hashManager.isFileProcessedAsync(filePath);
}
else {
// Use sync method for local hash manager
isProcessed = this.hashManager.isFileProcessed(filePath);
}
}
if (isProcessed) {
const hash = this.hashManager.getFileHash(filePath);
const fileName = path.basename(filePath);
if (progressCallback) {
progressCallback(100, `Skipping duplicate file: ${fileName} (hash: ${hash.substring(0, 8)}...)`);
}
console.log(`Skipping already processed file: ${fileName} (hash: ${hash.substring(0, 8)}...)`);
return this.vectorStore;
}
// Calculate and log file hash
const fileHash = this.hashManager.getFileHash(filePath);
const fileName = path.basename(filePath);
console.log(`Processing file: ${fileName} (hash: ${fileHash.substring(0, 8)}...)`);
if (progressCallback) {
progressCallback(5, `Processing file: ${fileName} (hash: ${fileHash.substring(0, 8)}...)`);
}
// Load and parse the file
await this.parser.loadFile(filePath);
if (progressCallback) {
progressCallback(10, 'File loaded. Extracting classes...');
}
// Extract all entities using enhanced parser
const result = this.parser.extractAllConstructs();
if (progressCallback) {
progressCallback(30, `Extracted ${result.statistics.totalConstructs} constructs (${result.statistics.coveragePercentage.toFixed(1)}% coverage). Creating chunks...`);
}
const classes = result.classes;
const enums = result.enums;
const delegates = result.delegates;
const generics = result.generics;
const nestedTypes = result.nestedTypes;
if (progressCallback) {
progressCallback(40, `Processing ${classes.length} classes, ${enums.length} enums, ${delegates.length} delegates, ${generics.length} generics, ${nestedTypes.length} nested types...`);
}
// Create chunks
const chunks = [];
// Process classes in batches to avoid memory issues
const classBatchSize = 100;
for (let i = 0; i < classes.length; i += classBatchSize) {
const batch = classes.slice(i, i + classBatchSize);
const batchChunks = await this.processClassBatch(batch);
chunks.push(...batchChunks);
if (progressCallback) {
const progress = 40 + Math.floor((i / classes.length) * 30);
progressCallback(progress, `Processed ${i + batch.length}/${classes.length} classes...`);
}
}
// Process enums
for (const enumEntity of enums) {
const enumChunks = await this.chunker.chunkEnum(enumEntity);
chunks.push(...enumChunks);
}
// Process delegates as special classes
for (const delegate of delegates) {
const delegateAsClass = {
name: delegate.name,
namespace: delegate.namespace,
fullName: delegate.fullName,
baseClass: 'MulticastDelegate',
interfaces: [],
fields: [],
methods: [delegate.invokeMethod, delegate.constructorMethod].filter(Boolean),
isMonoBehaviour: false,
typeDefIndex: delegate.typeDefIndex,
attributes: delegate.attributes || []
};
const delegateChunks = await this.chunker.chunkClass(delegateAsClass);
chunks.push(...delegateChunks);
}
// Process generics as enhanced classes
for (const generic of generics) {
const genericAsClass = {
name: generic.name,
namespace: generic.namespace,
fullName: generic.fullName,
baseClass: generic.baseClass,
interfaces: generic.interfaces,
fields: generic.fields,
methods: generic.methods,
isMonoBehaviour: false,
typeDefIndex: generic.typeDefIndex,
attributes: generic.attributes || []
};
const genericChunks = await this.chunker.chunkClass(genericAsClass);
chunks.push(...genericChunks);
}
// Process nested types as classes
for (const nested of nestedTypes) {
const nestedAsClass = {
name: nested.name,
namespace: nested.namespace,
fullName: nested.fullName,
baseClass: nested.baseClass,
interfaces: nested.interfaces,
fields: nested.fields,
methods: nested.methods,
isMonoBehaviour: false,
typeDefIndex: nested.typeDefIndex,
attributes: nested.attributes || []
};
const nestedChunks = await this.chunker.chunkClass(nestedAsClass);
chunks.push(...nestedChunks);
}
if (progressCallback) {
progressCallback(70, `Created ${chunks.length} chunks. Adding to vector store...`);
}
// Add chunks to vector store
await this.vectorStore.addCodeChunks(chunks);
// Mark file as processed
this.hashManager.markFileAsProcessed(filePath);
if (progressCallback) {
progressCallback(100, `Indexing complete! File hash ${fileHash.substring(0, 8)}... saved to skip future duplicates.`);
}
console.log(`File processed and hash saved: ${fileName} (${fileHash.substring(0, 8)}...)`);
return this.vectorStore;
}
/**
* Process a batch of classes to create chunks
* @param classes Batch of classes to process
* @returns Array of code chunks
*/
async processClassBatch(classes) {
const chunks = [];
for (const classEntity of classes) {
// Ensure attributes is always an array for compatibility
const compatibleClass = {
...classEntity,
attributes: classEntity.attributes || []
};
const classChunks = await this.chunker.chunkClass(compatibleClass);
chunks.push(...classChunks);
}
return chunks;
}
/**
* Get the vector store instance
* @returns Vector store instance
*/
getVectorStore() {
return this.vectorStore;
}
/**
* Get the hash manager instance
* @returns Hash manager instance
*/
getHashManager() {
return this.hashManager;
}
/**
* Check if a file has been processed before
* @param filePath Path to the dump.cs file
* @returns true if file was already processed
*/
isFileProcessed(filePath) {
return this.hashManager.isFileProcessed(filePath);
}
/**
* Check if a file has been processed before (async version)
* @param filePath Path to the dump.cs file
* @returns Promise that resolves to true if file was already processed
*/
async isFileProcessedAsync(filePath) {
// Initialize hash manager if it supports async initialization
if (this.hashManager.initialize) {
await this.hashManager.initialize();
}
if (this.hashManager.isFileProcessedAsync) {
return await this.hashManager.isFileProcessedAsync(filePath);
}
else {
return this.hashManager.isFileProcessed(filePath);
}
}
/**
* Get the hash of a file
* @param filePath Path to the dump.cs file
* @returns SHA-256 hash of the file
*/
getFileHash(filePath) {
return this.hashManager.getFileHash(filePath);
}
/**
* Remove a file from the processed list (to allow reprocessing)
* @param filePath Path to the dump.cs file
* @returns true if hash was removed
*/
removeFileFromProcessed(filePath) {
return this.hashManager.removeFileHash(filePath);
}
/**
* Clear all processed file hashes
*/
clearAllProcessedFiles() {
this.hashManager.clearAllHashes();
}
/**
* Get information about processed files
* @returns Object with hash storage info
*/
getProcessedFilesInfo() {
return this.hashManager.getInfo();
}
/**
* Get all processed file hashes
* @returns Array of all stored hashes
*/
getAllProcessedHashes() {
return this.hashManager.getAllHashes();
}
}
exports.IL2CPPIndexer = IL2CPPIndexer;
//# sourceMappingURL=indexer.js.map