UNPKG

il2cpp-dump-analyzer-mcp

Version:

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

560 lines 24.3 kB
"use strict"; /** * Unity MonoBehaviour Template Generator for IL2CPP MonoBehaviour classes * Generates Unity-ready MonoBehaviour scripts with proper lifecycle methods and serialization */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MonoBehaviourGenerator = void 0; const types_1 = require("./types"); const base_generator_1 = require("./base-generator"); /** * Generator for Unity MonoBehaviour templates from IL2CPP MonoBehaviour classes */ class MonoBehaviourGenerator extends base_generator_1.BaseGenerator { constructor() { super(...arguments); /** * Unity lifecycle methods with their metadata */ this.unityLifecycleMethods = [ { name: 'Awake', description: 'Called when the script instance is being loaded', returnType: 'void', parameters: [], isRequired: false, executionOrder: 1 }, { name: 'Start', description: 'Called before the first frame update', returnType: 'void', parameters: [], isRequired: false, executionOrder: 2 }, { name: 'Update', description: 'Called once per frame', returnType: 'void', parameters: [], isRequired: false, executionOrder: 3 }, { name: 'FixedUpdate', description: 'Called at fixed intervals for physics updates', returnType: 'void', parameters: [], isRequired: false, executionOrder: 4 }, { name: 'LateUpdate', description: 'Called after all Update functions have been called', returnType: 'void', parameters: [], isRequired: false, executionOrder: 5 }, { name: 'OnDestroy', description: 'Called when the MonoBehaviour will be destroyed', returnType: 'void', parameters: [], isRequired: false, executionOrder: 6 }, { name: 'OnEnable', description: 'Called when the object becomes enabled and active', returnType: 'void', parameters: [], isRequired: false, executionOrder: 7 }, { name: 'OnDisable', description: 'Called when the behaviour becomes disabled', returnType: 'void', parameters: [], isRequired: false, executionOrder: 8 } ]; } /** * Validate the generation request for MonoBehaviour template generation * @param request Code generation request * @returns Validation result */ async validateRequest(request) { const errors = []; const warnings = []; // Check if request type is correct if (request.type !== types_1.GenerationType.MONOBEHAVIOUR_TEMPLATE) { errors.push({ type: types_1.GenerationErrorType.VALIDATION_ERROR, message: `Invalid generation type: expected ${types_1.GenerationType.MONOBEHAVIOUR_TEMPLATE}, got ${request.type}`, code: 'INVALID_GENERATION_TYPE', context: 'MonoBehaviourGenerator.validateRequest' }); } // Check if source is an IL2CPP class const source = request.source; if (!source || typeof source !== 'object') { errors.push({ type: types_1.GenerationErrorType.VALIDATION_ERROR, message: 'Source entity is required and must be an object', code: 'INVALID_SOURCE_ENTITY', context: 'MonoBehaviourGenerator.validateRequest' }); } else { // Check for required class properties const requiredProps = ['name', 'namespace', 'fullName', 'fields', 'methods', 'isMonoBehaviour']; for (const prop of requiredProps) { if (!(prop in source)) { errors.push({ type: types_1.GenerationErrorType.VALIDATION_ERROR, message: `Source entity missing required property: ${prop}`, code: 'MISSING_REQUIRED_PROPERTY', context: 'MonoBehaviourGenerator.validateRequest' }); } } // Check if this is actually a MonoBehaviour if (!source.isMonoBehaviour) { errors.push({ type: types_1.GenerationErrorType.VALIDATION_ERROR, message: 'Source entity must be a MonoBehaviour class', code: 'NOT_MONOBEHAVIOUR', context: 'MonoBehaviourGenerator.validateRequest', suggestion: 'Use ClassWrapperGenerator for non-MonoBehaviour classes' }); } // Check if fields and methods are arrays if (source.fields && !Array.isArray(source.fields)) { errors.push({ type: types_1.GenerationErrorType.VALIDATION_ERROR, message: 'Source entity fields must be an array', code: 'INVALID_FIELDS_TYPE', context: 'MonoBehaviourGenerator.validateRequest' }); } if (source.methods && !Array.isArray(source.methods)) { errors.push({ type: types_1.GenerationErrorType.VALIDATION_ERROR, message: 'Source entity methods must be an array', code: 'INVALID_METHODS_TYPE', context: 'MonoBehaviourGenerator.validateRequest' }); } } // Check target language if (request.target.language !== 'csharp') { errors.push({ type: types_1.GenerationErrorType.VALIDATION_ERROR, message: `Unsupported target language: ${request.target.language}`, code: 'UNSUPPORTED_LANGUAGE', context: 'MonoBehaviourGenerator.validateRequest' }); } // Warnings for potential issues if (source && source.name && !/^[A-Z][a-zA-Z0-9]*$/.test(source.name)) { warnings.push('MonoBehaviour class name should follow PascalCase convention'); } if (source && source.baseClass && source.baseClass !== 'MonoBehaviour') { warnings.push(`MonoBehaviour inherits from ${source.baseClass} instead of MonoBehaviour directly`); } return { isValid: errors.length === 0, errors: errors.map(err => ({ message: err.message, line: 1, column: 1, severity: 'error' })), warnings }; } /** * Parse the IL2CPP MonoBehaviour source entity * @param request Code generation request * @returns Parsed MonoBehaviour data */ async parseSourceEntity(request) { const source = request.source; const usings = new Set(); // Add essential Unity using statements usings.add('using System;'); usings.add('using UnityEngine;'); // Add Unity-specific using statements based on target Unity version if (request.target.unityVersion) { const majorVersion = parseInt(request.target.unityVersion.split('.')[0]); if (majorVersion >= 2019) { usings.add('using UnityEngine.Serialization;'); } } // Add using statements based on base class and interfaces if (source.baseClass && source.baseClass !== 'MonoBehaviour') { const baseUsings = this.context.typeResolver.getUsingsForType(source.baseClass); baseUsings.forEach(using => usings.add(using)); } if (source.interfaces) { for (const interfaceType of source.interfaces) { const interfaceUsings = this.context.typeResolver.getUsingsForType(interfaceType); interfaceUsings.forEach(using => usings.add(using)); } } // Categorize fields into serializable and non-serializable const serializableFields = []; const allFields = source.fields || []; for (const field of allFields) { // Add using statements for field types const fieldUsings = this.context.typeResolver.getUsingsForType(field.type); fieldUsings.forEach(using => usings.add(using)); // Determine if field should be serializable if (this.isSerializableField(field)) { serializableFields.push(field); } } // Categorize methods into lifecycle and custom methods const lifecycleMethods = []; const customMethods = []; const allMethods = source.methods || []; for (const method of allMethods) { // Add using statements for method types const returnUsings = this.context.typeResolver.getUsingsForType(method.returnType); returnUsings.forEach(using => usings.add(using)); for (const param of method.parameters || []) { const paramUsings = this.context.typeResolver.getUsingsForType(param.type); paramUsings.forEach(using => usings.add(using)); } // Check if this is a Unity lifecycle method if (this.isUnityLifecycleMethod(method.name)) { lifecycleMethods.push(method); } else { customMethods.push(method); } } // Add additional using statements from options for (const additionalUsing of request.options.additionalUsings) { usings.add(`using ${additionalUsing};`); } return { name: source.name, namespace: source.namespace, fullName: source.fullName, baseClass: source.baseClass || 'MonoBehaviour', interfaces: source.interfaces || [], fields: allFields, methods: allMethods, serializableFields, lifecycleMethods, customMethods, typeDefIndex: source.typeDefIndex, usings, unityVersion: request.target.unityVersion }; } /** * Generate Unity MonoBehaviour template code from parsed data * @param parsedEntity Parsed MonoBehaviour data * @param options Generation options * @returns Generated C# MonoBehaviour code */ async generateCode(parsedEntity, options) { let code = ''; // Add using statements const sortedUsings = Array.from(parsedEntity.usings).sort(); for (const usingStatement of sortedUsings) { code += `${usingStatement}\n`; } if (sortedUsings.length > 0) { code += '\n'; } // Add namespace declaration if (parsedEntity.namespace) { code += `namespace ${parsedEntity.namespace}\n{\n`; } // Add XML documentation if requested if (options.includeDocumentation) { const indent = parsedEntity.namespace ? ' ' : ''; code += `${indent}/// <summary>\n`; code += `${indent}/// Unity MonoBehaviour template generated from ${parsedEntity.name}\n`; code += `${indent}/// IL2CPP TypeDefIndex: ${parsedEntity.typeDefIndex}\n`; if (parsedEntity.unityVersion) { code += `${indent}/// Target Unity Version: ${parsedEntity.unityVersion}\n`; } code += `${indent}/// </summary>\n`; } // Add class declaration const indent = parsedEntity.namespace ? ' ' : ''; code += `${indent}public class ${parsedEntity.name} : MonoBehaviour\n`; code += indent + '{\n'; // Add serializable fields with SerializeField attributes if (parsedEntity.serializableFields.length > 0) { code += this.generateSerializableFields(parsedEntity.serializableFields, options, indent + ' '); code += '\n'; } // Add non-serializable fields const nonSerializableFields = parsedEntity.fields.filter(field => !this.isSerializableField(field)); if (nonSerializableFields.length > 0) { code += this.generateNonSerializableFields(nonSerializableFields, options, indent + ' '); code += '\n'; } // Add Unity lifecycle methods code += this.generateUnityLifecycleMethods(parsedEntity, options, indent + ' '); // Add custom methods if (parsedEntity.customMethods.length > 0) { code += '\n'; code += this.generateCustomMethods(parsedEntity.customMethods, options, indent + ' '); } // Close class code += indent + '}\n'; // Close namespace if (parsedEntity.namespace) { code += '}\n'; } // Format the code according to style options return this.formatCode(code, options.codeStyle); } /** * Check if a field should be serializable in Unity * @param field IL2CPP field to check * @returns True if field should be serializable */ isSerializableField(field) { // Unity serializes public fields by default if (field.isPublic && !field.isStatic && !field.isReadOnly) { return this.isUnitySerializableType(field.type); } // Check for SerializeField attribute on private fields if (!field.isPublic && field.attributes.some(attr => attr.includes('SerializeField'))) { return this.isUnitySerializableType(field.type); } return false; } /** * Check if a type is serializable by Unity * @param type Type string to check * @returns True if type is Unity serializable */ isUnitySerializableType(type) { // Unity built-in serializable types const unitySerializableTypes = [ 'bool', 'byte', 'sbyte', 'char', 'decimal', 'double', 'float', 'int', 'uint', 'long', 'ulong', 'short', 'ushort', 'string', 'Vector2', 'Vector3', 'Vector4', 'Rect', 'Quaternion', 'Matrix4x4', 'Color', 'Color32', 'LayerMask', 'AnimationCurve', 'Gradient', 'RectOffset', 'GUIStyle' ]; // Remove generic type parameters and array brackets for checking const baseType = type.replace(/[\[\]<>]/g, '').split(',')[0].trim(); // Check if it's a Unity serializable type if (unitySerializableTypes.includes(baseType)) { return true; } // Check if it's a Unity Object type if (baseType.includes('GameObject') || baseType.includes('Transform') || baseType.includes('Component') || baseType.includes('MonoBehaviour') || baseType.includes('ScriptableObject')) { return true; } // Arrays and Lists of serializable types are serializable if (type.includes('[]') || type.includes('List<')) { return true; } return false; } /** * Check if a method name is a Unity lifecycle method * @param methodName Method name to check * @returns True if it's a Unity lifecycle method */ isUnityLifecycleMethod(methodName) { return this.unityLifecycleMethods.some(lifecycle => lifecycle.name === methodName); } /** * Generate serializable fields with SerializeField attributes * @param fields Array of serializable fields * @param options Generation options * @param indent Indentation string * @returns Generated field code */ generateSerializableFields(fields, options, indent) { let code = ''; if (options.includeDocumentation) { code += `${indent}#region Serialized Fields\n\n`; } for (const field of fields) { // Add XML documentation if requested if (options.includeDocumentation) { code += `${indent}/// <summary>\n`; code += `${indent}/// ${field.name} field (Unity serialized)\n`; code += `${indent}/// Type: ${field.type}\n`; code += `${indent}/// </summary>\n`; } // Add SerializeField attribute for private fields if (!field.isPublic && options.includeUnityAttributes) { code += `${indent}[SerializeField]\n`; } // Add field declaration const accessModifier = field.isPublic ? 'public' : 'private'; const resolvedType = this.context.typeResolver.resolveType(field.type); code += `${indent}${accessModifier} ${resolvedType} ${field.name};\n\n`; } if (options.includeDocumentation) { code += `${indent}#endregion\n\n`; } return code; } /** * Generate non-serializable fields * @param fields Array of non-serializable fields * @param options Generation options * @param indent Indentation string * @returns Generated field code */ generateNonSerializableFields(fields, options, indent) { let code = ''; if (fields.length === 0) return code; if (options.includeDocumentation) { code += `${indent}#region Non-Serialized Fields\n\n`; } for (const field of fields) { // Add XML documentation if requested if (options.includeDocumentation) { code += `${indent}/// <summary>\n`; code += `${indent}/// ${field.name} field (not serialized)\n`; code += `${indent}/// Type: ${field.type}\n`; code += `${indent}/// </summary>\n`; } // Add field declaration const accessModifier = field.isPublic ? 'public' : 'private'; const staticModifier = field.isStatic ? 'static ' : ''; const readonlyModifier = field.isReadOnly ? 'readonly ' : ''; const resolvedType = this.context.typeResolver.resolveType(field.type); code += `${indent}${accessModifier} ${staticModifier}${readonlyModifier}${resolvedType} ${field.name};\n\n`; } if (options.includeDocumentation) { code += `${indent}#endregion\n\n`; } return code; } /** * Generate Unity lifecycle methods * @param parsedEntity Parsed MonoBehaviour data * @param options Generation options * @param indent Indentation string * @returns Generated lifecycle method code */ generateUnityLifecycleMethods(parsedEntity, options, indent) { let code = ''; if (options.includeDocumentation) { code += `${indent}#region Unity Lifecycle Methods\n\n`; } // Sort lifecycle methods by execution order const sortedLifecycleMethods = this.unityLifecycleMethods .filter(lifecycle => { // Include method if it exists in the original IL2CPP class or if it's commonly used const existsInOriginal = parsedEntity.lifecycleMethods.some(method => method.name === lifecycle.name); const isCommonlyUsed = ['Awake', 'Start', 'Update', 'OnDestroy'].includes(lifecycle.name); return existsInOriginal || isCommonlyUsed; }) .sort((a, b) => a.executionOrder - b.executionOrder); for (const lifecycle of sortedLifecycleMethods) { // Check if method exists in original IL2CPP class const originalMethod = parsedEntity.lifecycleMethods.find(method => method.name === lifecycle.name); // Add XML documentation if requested if (options.includeDocumentation) { code += `${indent}/// <summary>\n`; code += `${indent}/// ${lifecycle.description}\n`; if (originalMethod) { code += `${indent}/// Original IL2CPP method: RVA ${originalMethod.rva}, Offset ${originalMethod.offset}\n`; } code += `${indent}/// </summary>\n`; } // Generate method signature code += `${indent}private void ${lifecycle.name}()\n`; code += `${indent}{\n`; if (originalMethod) { code += `${indent} // TODO: Implement original ${lifecycle.name} logic\n`; code += `${indent} // Original method had ${originalMethod.parameters.length} parameters\n`; } else { code += `${indent} // TODO: Implement ${lifecycle.name} logic\n`; } code += `${indent}}\n\n`; } if (options.includeDocumentation) { code += `${indent}#endregion\n`; } return code; } /** * Generate custom methods from the original IL2CPP class * @param methods Array of custom methods * @param options Generation options * @param indent Indentation string * @returns Generated custom method code */ generateCustomMethods(methods, options, indent) { let code = ''; if (methods.length === 0) return code; if (options.includeDocumentation) { code += `${indent}#region Custom Methods\n\n`; } for (let i = 0; i < methods.length; i++) { const method = methods[i]; // Add XML documentation if requested if (options.includeDocumentation) { code += `${indent}/// <summary>\n`; code += `${indent}/// ${method.name} method from original IL2CPP class\n`; code += `${indent}/// RVA: ${method.rva}, Offset: ${method.offset}\n`; code += `${indent}/// </summary>\n`; // Add parameter documentation for (const param of method.parameters) { code += `${indent}/// <param name="${param.name}">Parameter of type ${param.type}</param>\n`; } if (method.returnType !== 'void') { code += `${indent}/// <returns>Returns ${method.returnType}</returns>\n`; } } // Generate method signature const accessModifier = method.isPublic ? 'public' : 'private'; const staticModifier = method.isStatic ? 'static ' : ''; const virtualModifier = method.isVirtual ? 'virtual ' : ''; const overrideModifier = method.isOverride ? 'override ' : ''; const resolvedReturnType = this.context.typeResolver.resolveType(method.returnType); code += `${indent}${accessModifier} ${staticModifier}${virtualModifier}${overrideModifier}${resolvedReturnType} ${method.name}(`; // Add parameters const parameters = method.parameters.map(param => { const resolvedParamType = this.context.typeResolver.resolveType(param.type); return `${resolvedParamType} ${param.name}`; }); code += parameters.join(', '); code += ')\n'; code += `${indent}{\n`; code += `${indent} // TODO: Implement ${method.name} logic\n`; if (resolvedReturnType !== 'void') { code += `${indent} throw new System.NotImplementedException();\n`; } code += `${indent}}\n`; // Add spacing between methods (except for the last one) if (i < methods.length - 1) { code += '\n'; } } if (options.includeDocumentation) { code += `\n${indent}#endregion\n`; } return code; } } exports.MonoBehaviourGenerator = MonoBehaviourGenerator; //# sourceMappingURL=monobehaviour-generator.js.map