il2cpp-dump-analyzer-mcp
Version:
Agentic RAG system for analyzing IL2CPP dump.cs files from Unity games
395 lines • 15.8 kB
JavaScript
"use strict";
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.IL2CPPDumpParser = void 0;
const fs = __importStar(require("fs"));
/**
* Specialized parser for IL2CPP dump.cs files that handles the specific format
* and patterns found in IL2CPP decompiled code.
*/
class IL2CPPDumpParser {
constructor() {
this.content = '';
this.lines = [];
this.imageMap = new Map();
}
/**
* Load and parse an IL2CPP dump.cs file
* @param filePath Path to the dump.cs file
*/
async loadFile(filePath) {
this.content = fs.readFileSync(filePath, 'utf8');
this.lines = this.content.split('\n');
// Parse the image map (DLL references) at the beginning of the file
this.parseImageMap();
}
/**
* Parse the image map at the beginning of the file
* These are the DLL references in the format:
* // Image 0: holo-game.dll - 0
*/
parseImageMap() {
const imageRegex = /\/\/ Image (\d+): ([^-]+) - (\d+)/;
for (const line of this.lines) {
const match = line.match(imageRegex);
if (match) {
const imageIndex = parseInt(match[1]);
const imageName = match[2].trim();
const offset = parseInt(match[3]);
this.imageMap.set(imageIndex, imageName);
}
else if (line.trim().startsWith('// Namespace:')) {
// Stop once we reach the first namespace declaration
break;
}
}
}
/**
* Extract all classes from the IL2CPP dump
*/
extractClasses() {
const classes = [];
const classRegex = /^(public|internal|private|protected)?\s*(class|struct|sealed class|static class|abstract class)\s+(\w+)(?:\s*:\s*([^{]+))?\s*\/\/\s*TypeDefIndex:\s*(\d+)/;
let currentNamespace = '';
let i = 0;
while (i < this.lines.length) {
const line = this.lines[i].trim();
// Track namespace declarations
if (line.startsWith('// Namespace:')) {
currentNamespace = line.substring('// Namespace:'.length).trim();
i++;
continue;
}
// Look for class declarations
const classMatch = line.match(classRegex);
if (classMatch) {
const accessModifier = classMatch[1] || 'internal';
const classType = classMatch[2];
const className = classMatch[3];
const inheritance = classMatch[4] || '';
const typeDefIndex = parseInt(classMatch[5]);
// Find the class body (everything between the opening and closing braces)
let braceCount = 0;
let startLine = i;
let endLine = i;
// Find the opening brace
while (endLine < this.lines.length && !this.lines[endLine].includes('{')) {
endLine++;
}
if (endLine < this.lines.length) {
braceCount = 1;
endLine++;
// Find the matching closing brace
while (endLine < this.lines.length && braceCount > 0) {
const currentLine = this.lines[endLine];
braceCount += (currentLine.match(/{/g) || []).length;
braceCount -= (currentLine.match(/}/g) || []).length;
endLine++;
}
// Extract the class body
const classBody = this.lines.slice(startLine, endLine).join('\n');
// Parse inheritance
const baseClass = this.parseBaseClass(inheritance);
const interfaces = this.parseInterfaces(inheritance);
// Parse fields and methods
const fields = this.parseFields(classBody);
const methods = this.parseMethods(classBody);
// Determine if this is a MonoBehaviour
const isMonoBehaviour = inheritance.includes('MonoBehaviour');
classes.push({
name: className,
namespace: currentNamespace,
fullName: currentNamespace ? `${currentNamespace}.${className}` : className,
baseClass,
interfaces,
fields,
methods,
isMonoBehaviour,
typeDefIndex,
attributes: []
});
i = endLine;
}
else {
i++;
}
}
else {
i++;
}
}
return classes;
}
/**
* Extract all enums from the IL2CPP dump
*/
extractEnums() {
const enums = [];
const enumRegex = /^(public|internal|private|protected)?\s*enum\s+(\w+)(?:\s*:\s*([^{]+))?\s*\/\/\s*TypeDefIndex:\s*(\d+)/;
let currentNamespace = '';
let i = 0;
while (i < this.lines.length) {
const line = this.lines[i].trim();
// Track namespace declarations
if (line.startsWith('// Namespace:')) {
currentNamespace = line.substring('// Namespace:'.length).trim();
i++;
continue;
}
// Look for enum declarations
const enumMatch = line.match(enumRegex);
if (enumMatch) {
const enumName = enumMatch[2];
const typeDefIndex = parseInt(enumMatch[4]);
// Find the enum body
let braceCount = 0;
let startLine = i;
let endLine = i;
// Find the opening brace
while (endLine < this.lines.length && !this.lines[endLine].includes('{')) {
endLine++;
}
if (endLine < this.lines.length) {
braceCount = 1;
endLine++;
// Find the matching closing brace
while (endLine < this.lines.length && braceCount > 0) {
const currentLine = this.lines[endLine];
braceCount += (currentLine.match(/{/g) || []).length;
braceCount -= (currentLine.match(/}/g) || []).length;
endLine++;
}
// Extract the enum body
const enumBody = this.lines.slice(startLine, endLine).join('\n');
// Parse enum values
const values = this.parseEnumValues(enumBody);
enums.push({
name: enumName,
namespace: currentNamespace,
fullName: currentNamespace ? `${currentNamespace}.${enumName}` : enumName,
values,
typeDefIndex
});
i = endLine;
}
else {
i++;
}
}
else {
i++;
}
}
return enums;
}
// Helper methods for parsing class components
parseBaseClass(inheritance) {
if (!inheritance)
return undefined;
// In C#, the first type after the colon is the base class (if any)
const types = inheritance.split(',').map(t => t.trim());
// Check if the first type is an interface (starts with I and has PascalCase)
const firstType = types[0];
if (firstType && !this.isInterface(firstType)) {
return firstType;
}
return undefined;
}
parseInterfaces(inheritance) {
if (!inheritance)
return [];
const types = inheritance.split(',').map(t => t.trim());
const interfaces = [];
// If the first type is a base class, start from index 1
const startIndex = this.parseBaseClass(inheritance) ? 1 : 0;
for (let i = startIndex; i < types.length; i++) {
if (this.isInterface(types[i])) {
interfaces.push(types[i]);
}
}
return interfaces;
}
isInterface(typeName) {
// Heuristic: interfaces in C# typically start with 'I' and use PascalCase
return typeName.startsWith('I') &&
typeName.length > 1 &&
typeName[1] === typeName[1].toUpperCase();
}
/**
* Parse fields from the class body
* @param classBody The full class body text
* @returns Array of parsed IL2CPP fields
*/
parseFields(classBody) {
const fields = [];
const lines = classBody.split('\n');
// Regular expression to match field declarations in IL2CPP dump
// Format: [attributes] [access_modifier] [static] [readonly] type name; // offset
const fieldRegex = /^\s*((?:\[.*?\]\s*)*)?\s*(public|private|protected|internal)?\s*(static)?\s*(readonly)?\s*([^\s]+)\s+([^\s;]+);(?:\s*\/\/\s*([0-9A-Fx]+))?/;
for (const line of lines) {
const match = line.match(fieldRegex);
if (match) {
const attributesStr = match[1] || '';
const accessModifier = match[2] || 'private';
const isStatic = !!match[3];
const isReadOnly = !!match[4];
const type = match[5];
const name = match[6];
const offset = match[7] || '';
// Parse attributes
const attributes = this.parseAttributes(attributesStr);
fields.push({
name,
type,
isPublic: accessModifier === 'public',
isStatic,
isReadOnly,
attributes,
offset
});
}
}
return fields;
}
/**
* Parse methods from the class body
* @param classBody The full class body text
* @returns Array of parsed IL2CPP methods
*/
parseMethods(classBody) {
const methods = [];
const lines = classBody.split('\n');
// Regular expression to match method declarations in IL2CPP dump
// Format: [attributes] [access_modifier] [static] [virtual] [override] [abstract] return_type name(parameters); // RVA: offset
const methodRegex = /^\s*((?:\[.*?\]\s*)*)?\s*(public|private|protected|internal)?\s*(static)?\s*(virtual)?\s*(override)?\s*(abstract)?\s*([^\s\(]+)\s+([^\s\(]+)\s*\(([^\)]*)\);(?:\s*\/\/\s*RVA:\s*([0-9A-Fx]+)\s*Offset:\s*([0-9A-Fx]+))?/;
for (const line of lines) {
const match = line.match(methodRegex);
if (match) {
const attributesStr = match[1] || '';
const accessModifier = match[2] || 'private';
const isStatic = !!match[3];
const isVirtual = !!match[4];
const isOverride = !!match[5];
const isAbstract = !!match[6];
const returnType = match[7];
const name = match[8];
const parametersStr = match[9] || '';
const rva = match[10] || '';
const offset = match[11] || '';
// Parse attributes and parameters
const attributes = this.parseAttributes(attributesStr);
const parameters = this.parseParameters(parametersStr);
methods.push({
name,
returnType,
parameters,
isPublic: accessModifier === 'public',
isStatic,
isVirtual,
isAbstract,
isOverride,
attributes,
rva,
offset
});
}
}
return methods;
}
/**
* Parse enum values from the enum body
* @param enumBody The full enum body text
* @returns Array of enum value pairs
*/
parseEnumValues(enumBody) {
const values = [];
const lines = enumBody.split('\n');
// Regular expression to match enum value declarations
// Format: public const EnumName VALUE = value;
const enumValueRegex = /^\s*public\s+const\s+\w+\s+(\w+)\s*=\s*([^;]+);/;
for (const line of lines) {
const match = line.match(enumValueRegex);
if (match) {
const name = match[1];
const value = match[2].trim();
values.push({ name, value });
}
}
return values;
}
/**
* Parse attributes from attribute string
* @param attributesStr String containing attributes in square brackets
* @returns Array of attribute names
*/
parseAttributes(attributesStr) {
if (!attributesStr)
return [];
const attributes = [];
const attrRegex = /\[(.*?)\]/g;
let match;
while ((match = attrRegex.exec(attributesStr)) !== null) {
const attrContent = match[1].trim();
attributes.push(attrContent);
}
return attributes;
}
/**
* Parse method parameters
* @param parametersStr String containing method parameters
* @returns Array of parsed parameters
*/
parseParameters(parametersStr) {
if (!parametersStr)
return [];
const parameters = [];
const paramParts = parametersStr.split(',');
for (const part of paramParts) {
const trimmed = part.trim();
if (!trimmed)
continue;
// Split by last space to separate type and name
const lastSpaceIndex = trimmed.lastIndexOf(' ');
if (lastSpaceIndex !== -1) {
const type = trimmed.substring(0, lastSpaceIndex).trim();
const name = trimmed.substring(lastSpaceIndex + 1).trim();
parameters.push({ type, name });
}
}
return parameters;
}
}
exports.IL2CPPDumpParser = IL2CPPDumpParser;
//# sourceMappingURL=il2cpp-parser.js.map