/// <summary> /// Parses the instruction. /// </summary> /// <param name="mainProcessor">The main instruction processor.</param> /// <param name="asmCollection">The papyrus assembly collection.</param> /// <param name="instruction">The instruction.</param> /// <param name="targetMethod">The target method.</param> /// <param name="targetType">Type of the target.</param> /// <returns></returns> /// <exception cref="StackUnderflowException"></exception> public IEnumerable <PapyrusInstruction> Process( IClrInstructionProcessor mainProcessor, IReadOnlyCollection <PapyrusAssemblyDefinition> asmCollection, Instruction instruction, MethodDefinition targetMethod, TypeDefinition targetType) { bool isStructAccess; var structGets = new List <PapyrusInstruction>(); this.papyrusAssemblyCollection = asmCollection; var processInstruction = new List <PapyrusInstruction>(); var stack = mainProcessor.EvaluationStack; currentInstruction = instruction; var methodRef = instruction.Operand as MethodReference; if (methodRef != null) { if (methodRef.FullName.ToLower().Contains("system.void") && methodRef.FullName.ToLower().Contains(".ctor")) { return(new PapyrusInstruction[0]); } // What we need: // 1. Call Location (The name of the type that has this method) // 2. Method Name (The name of the method, duh) // 3. Method Parameters (The parameters that we need to pass to the method) // 4. Destination Variable (The variable needed to store the return value of the method) // - Destination Variable must always exist, the difference between a function returning a object // and a void, is that we "assign" the destination to a ::nonevar temp variable of type None (void) var name = methodRef.FullName; var itemsToPop = instruction.OpCode.StackBehaviourPop == StackBehaviour.Varpop ? methodRef.Parameters.Count : Utility.GetStackPopCount(instruction.OpCode.StackBehaviourPop); if (stack.Count < itemsToPop) { if (mainProcessor.PapyrusCompilerOptions == PapyrusCompilerOptions.Strict) { throw new StackUnderflowException(targetMethod, instruction); } return(processInstruction); } // Check if the current invoked Method is inside the same instance of this type // by checking if it is using "this.Method(params);" << "this."... var isThisCall = false; var isStaticCall = false; var callerLocation = ""; var parameters = new List <object>(); if (methodRef.HasThis) { callerLocation = "self"; isThisCall = true; } for (var paramIndex = 0; paramIndex < itemsToPop; paramIndex++) { var parameter = stack.Pop(); if (parameter.IsThis && stack.Count > methodRef.Parameters.Count || methodRef.CallingConvention == MethodCallingConvention.ThisCall) { isThisCall = true; callerLocation = "self"; // Location: 'self' is the same as 'this' } parameters.Insert(0, parameter); } var methodDefinition = mainProcessor.TryResolveMethodReference(methodRef); if (methodDefinition != null) { isStaticCall = methodDefinition.IsStatic; } if (methodDefinition != null && methodDefinition.IsConstructor) { return(processInstruction); } if (methodDefinition == null) { isStaticCall = name.Contains("::"); } if (isStaticCall) { callerLocation = name.Split("::")[0]; } if (callerLocation.Contains(".")) { callerLocation = callerLocation.Split('.').LastOrDefault(); } if (methodRef.Name.ToLower().Contains("concat")) { processInstruction.AddRange(mainProcessor.ProcessStringConcat(instruction, methodRef, parameters)); } else if (methodRef.Name.ToLower().Contains("op_equal") || methodRef.Name.ToLower().Contains("op_inequal")) { // TODO: Add Equality comparison mainProcessor.InvertedBranch = methodRef.Name.ToLower().Contains("op_inequal"); if (!InstructionHelper.IsStore(instruction.Next.OpCode.Code)) { mainProcessor.SkipToOffset = instruction.Next.Offset; return(processInstruction); } // EvaluationStack.Push(new EvaluationStackItem { IsMethodCall = true, Value = methodRef, TypeName = methodRef.ReturnType.FullName }); processInstruction.AddRange(mainProcessor.ProcessConditionalInstruction(instruction, Code.Ceq)); } else { if (methodRef.Name.ToLower().StartsWith("get_") || methodRef.Name.ToLower().StartsWith("set_")) { processInstruction.AddRange(ProcessPropertyAccess(mainProcessor, instruction, methodRef, methodDefinition, parameters)); return(processInstruction); } if (isStaticCall) { var destinationVariable = mainProcessor.GetTargetVariable(instruction, methodRef, out isStructAccess); { processInstruction.Add(CreatePapyrusCallInstruction(mainProcessor, PapyrusOpCodes.Callstatic, methodRef, callerLocation, destinationVariable, parameters, out structGets)); processInstruction.InsertRange(processInstruction.Count - 1, structGets); return(processInstruction); } } if (isThisCall) { var isDelegateInvoke = false; if (stack.Count > 0) { var next = stack.Peek().Value; var varRef = next as PapyrusVariableReference; if (varRef != null && varRef.IsDelegateReference) { isDelegateInvoke = true; } } var destinationVariable = mainProcessor.GetTargetVariable(instruction, methodRef, out isStructAccess); if (isDelegateInvoke) { var targetDelegate = stack.Pop().Value as PapyrusVariableReference; var targetDelegateMethodName = targetDelegate.DelegateInvokeReference; // In case we are invoking a delegate from inside a delegate then we need to find the // reference to the previous delegate method and use that one instead. var target = InstructionHelper.FindPreviousInstruction(instruction, InstructionHelper.IsLoadMethodRef); if (target != null && target.Operand is MethodReference) { methodRef = target.Operand as MethodReference; targetDelegateMethodName = methodRef.Name; } processInstruction.Add(CreatePapyrusCallInstruction(mainProcessor, PapyrusOpCodes.Callmethod, methodRef, callerLocation, destinationVariable, parameters, out structGets, targetDelegateMethodName)); processInstruction.InsertRange(processInstruction.Count - 1, structGets); } else { if (isStructAccess) { var structRef = stack.Pop().Value as PapyrusStructFieldReference; if (structRef != null) { // (Call and Assign Temp then do StructSet using Temp) // Call and Assign return value to temp processInstruction.Add(CreatePapyrusCallInstruction(mainProcessor, PapyrusOpCodes.Callmethod, methodRef, callerLocation, destinationVariable, parameters, out structGets)); // StructSet processInstruction.Add( mainProcessor.CreatePapyrusInstruction(PapyrusOpCodes.StructSet, structRef.StructSource, structRef.StructVariable, mainProcessor.CreateVariableReferenceFromName(destinationVariable))); // Skip next instruction as it should be stfld and we already store the field here. mainProcessor.SkipNextInstruction = true; } } else { processInstruction.Add(CreatePapyrusCallInstruction(mainProcessor, PapyrusOpCodes.Callmethod, methodRef, callerLocation, destinationVariable, parameters, out structGets)); processInstruction.InsertRange(processInstruction.Count - 1, structGets); } } return(processInstruction); } } } return(processInstruction); }