예제 #1
0
		/// <summary>
		/// Wraps the current method with begin/end callbacks.
		/// </summary>
		/// <remarks>
		/// This will rename the current method and replace it with a new method that will take its place.
		/// In the new method it will call the callbacks and perform canceling on the begin callback if required.</remarks>
		/// <param name="current">The current method to be wrapped</param>
		/// <param name="beginCallback">The callback to be executed at the start of the method</param>
		/// <param name="endCallback">The optional end callback that will be executed at the end of the method</param>
		/// <param name="beginIsCancellable">Indicates that the begin callback can cancel the method execution</param>
		/// <param name="noEndHandling">Indicates to only return from the method and not do any special popping and so on</param>
		/// <param name="allowCallbackInstance">Indicates that the callbacks expect an instance parameter at the first parameter index</param>
		/// <returns></returns>
		public static MethodDefinition Wrap
		(
			this MethodDefinition current,
			MethodReference beginCallback,
			MethodReference endCallback,
			bool beginIsCancellable,
			bool noEndHandling,
			bool allowCallbackInstance,
			TypeReference overrideReturnType = null
		)
		{
			if (!current.HasBody)
				throw new InvalidOperationException("Method must have a body.");

			//This method has only yet been tested on void & string return type methods.
			if (new[]
			{
				current.Module.TypeSystem.Void,
				current.Module.TypeSystem.String,
				current.Module.TypeSystem.Double,
			}.Contains(current.ReturnType) || (overrideReturnType != null && current.ReturnType.Name == overrideReturnType.Name))
			{
				//Create the new replacement method that will take place of the current method.
				//So we must ensure we clone to meet the signatures.
				var wrapped = new MethodDefinition(current.Name, current.Attributes, current.ReturnType);
				var instanceMethod = (current.Attributes & MethodAttributes.Static) == 0;

				//Rename the existing method, and replace all references to it so that the new 
				//method receives the calls instead.
				current.Name = current.Name + WrappedMethodNameSuffix;
				//If we are renaming a virtual method, it does not need to be virtual anymore
				//as we want exclusive control over it.
				current.IsVirtual = false;
				//Finally replace all instances of the current method with the wrapped method
				//that is about to be generated
				current.ReplaceWith(wrapped);

				//Clone the parameters for the new method
				if (current.HasParameters)
				{
					foreach (var prm in current.Parameters)
					{
						wrapped.Parameters.Add(prm);
					}
				}

				//Place the new method in the declaring type of the method we are cloning
				current.DeclaringType.Methods.Add(wrapped);

				//Generate the il that will call and handle the begin callback.
				var beginResult = wrapped.EmitBeginCallback(beginCallback, instanceMethod, allowCallbackInstance, beginIsCancellable);

				//Emit the il that will execute the actual method that was renamed earlier 
				var insFirstForMethod = wrapped.EmitMethodCallback(current, instanceMethod, current.ReturnType.Name != current.Module.TypeSystem.String.Name);

				//If the begin callback is cancelable, the EmitBeginCallback method will have left a Nop
				//instruction so we can direct where to continue on to, rather than exiting the method.
				if (beginIsCancellable && beginResult != null && beginResult.OpCode == OpCodes.Nop)
				{
					if (current.ReturnType.Name == current.Module.TypeSystem.Void.Name)
					{
						beginResult.OpCode = OpCodes.Brtrue_S;
						beginResult.Operand = insFirstForMethod;
					}
					else if (current.ReturnType.Name == current.Module.TypeSystem.String.Name)
					{
						beginResult.OpCode = OpCodes.Brtrue;
						beginResult.Operand = insFirstForMethod;
					}
					else if (current.ReturnType.Name == current.Module.TypeSystem.Double.Name)
					{
						beginResult.OpCode = OpCodes.Brtrue;
						beginResult.Operand = insFirstForMethod;
					}
					else if (overrideReturnType != null && current.ReturnType.Name == overrideReturnType.Name)
					{
						beginResult.OpCode = OpCodes.Brtrue;
						beginResult.Operand = insFirstForMethod;
					}
				}

				//If a end callback is specified then we can also emit the callback to it as well
				if (endCallback != null)
				{
					wrapped.EmitEndCallback(endCallback, instanceMethod, allowCallbackInstance);
				}

				//The custom method is now fully generated, so we can now complete the il for exiting.
				wrapped.EmitMethodEnding(noEndHandling);

				return wrapped;
			}
			else throw new NotSupportedException("Non Void methods not yet supported");
		}
예제 #2
0
		public override void Run()
		{
			foreach (var newItem in SourceDefinition.Type("Terraria.Item").Methods.Where(
				x => x.Name == "NewItem"
			))
			{
				//In this patch we create a custom DropLoot method that will be the receiver
				//of all Item.NewItem calls in NPCLoot.

				var typeNpc = this.Type<Terraria.NPC>();

				//Create the new DropLoot call in the Terraria.NPC class
				var dropLoot = new MethodDefinition("DropLoot", MethodAttributes.Public | MethodAttributes.Static, newItem.ReturnType);
				typeNpc.Methods.Add(dropLoot);

				dropLoot.Body.InitLocals = true;

				var il = dropLoot.Body.GetILProcessor();

				//Clone the parameters from the Item.NewItem method (with no byreference)
				foreach (var prm in newItem.Parameters)
					dropLoot.Parameters.Add(prm);

				dropLoot.Parameters.Add(new ParameterDefinition(typeNpc)
				{
					Name = "npc",
					IsOptional = true,
					HasDefault = true,
					Constant = null
				});

				//Collect the hooks
				var cbkBegin = ModificationDefinition.Type("OTAPI.Callbacks.Terraria.Npc").Method(
					"DropLootBegin",
					parameters: dropLoot.Parameters,
					skipMethodParameters: 1,
					substituteByRefs: true
				);
				var cbkEnd = ModificationDefinition.Type("OTAPI.Callbacks.Terraria.Npc").Method(
					"DropLootEnd",
					parameters: dropLoot.Parameters,
					skipMethodParameters: 0,
					substituteByRefs: true
				);

				//Create the value to hold the new item id
				var vrbItemId = new VariableDefinition("otaItem", (cbkBegin.Parameters[0].ParameterType as ByReferenceType).ElementType);
				dropLoot.Body.Variables.Add(vrbItemId);

				il.Emit(OpCodes.Ldloca_S, vrbItemId); //Loads our variable by reference so our callback and alter it.
				var beginResult = dropLoot.EmitBeginCallback(cbkBegin, false, false, false, parameterOffset: 1);

				//Inject the begin call
				var insFirstForMethod = dropLoot.EmitMethodCallback(newItem, false, false);
				//Store the result into our new variable
				il.Emit(OpCodes.Stloc, vrbItemId);

				//Set the vanilla instruction to be resumed upon continuation of the begin hook.
				if (beginResult != null && beginResult.OpCode == OpCodes.Pop)
				{
					beginResult.OpCode = OpCodes.Brtrue_S;
					beginResult.Operand = insFirstForMethod;

					//Emit the cancellation return IL
					il.InsertAfter(beginResult, il.Create(OpCodes.Ret));
					il.InsertAfter(beginResult, il.Create(OpCodes.Ldloc, vrbItemId));
				}

				//Inject the end callback
				dropLoot.EmitEndCallback(cbkEnd, false, false);

				//Emit the return value using the result variable we injected
				il.Emit(OpCodes.Ldloc, vrbItemId);
				il.Emit(OpCodes.Ret);
			}
		}