-
-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Question: Injection #15
Comments
It's already possible to insert your own instructions on IL-level using using MonoMod;
using System;
namespace Game.Mod.CustomAttribs {
[MonoModCustomMethodAttribute("ReplaceString")] // MonoMod.MonoModRules::ReplaceString
public class ReplaceString : Attribute {
public ReplaceString(string a, string b) { }
}
}
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
namespace MonoMod {
static class MonoModRules {
static MonoModRules() {
// Alternatively to MonoModCustomMethodAttribute, use this.
MMIL.Rule.RegisterCustomMethodAttribute("Game.Mod.CustomAttribs.ReplaceString", "ReplaceString");
}
public static void ReplaceString(MethodDefinition method, CustomAttribute attrib) {
// The method must have a body, otherwise there's nothing to replace!
if (!method.HasBody)
return;
// With strings, it's guaranteed that the value is also a string.
string from = (string) attrib.ConstructorArguments[0].Value;
string to = (string) attrib.ConstructorArguments[1].Value;
// You could f.e. differentiate between multiple constructors here, but ReplaceStrings is quite simple.
// Iterate through the method body.
foreach (Instruction instr in method.Body.Instructions) {
// Check if the instruction is a ldstr instruction (load constant string literal).
// Also check the operand (string, just like above) if it's the "from" string.
if (instr.OpCode == OpCodes.Ldstr && (string) instr.Operand == from) {
// Just replace the operand.
instr.Operand = to;
}
}
}
}
} EDIT: It's Injecting C# statements / method bodies into the middle of another method is technically possible, but quite risky. We'd need to take code flow, local variables, IL offsets affecting instruction length and possibly other factors into account. I'll keep this issue open; The challenge's accepted, but postponed. For a start, I'll bring over the "short <-> long jump" post processor from XnaToFna. My only fear about it is that it could misbehave and introduce regressions easily, but it should be tested enough (fixed against both upper and lower boundary edge cases). |
e0d99c1 brings over the short <-> long op conversion from XnaToFna, fixing jumps that can become invalid when the injected code's too large. It also applies when removing too much code from a method body. |
Thanks! i'll try testing it soon. |
so I have my attribute, and the logic written to manipulate the IL. How would I apply that attribute to the method? Do I just make a stub method with the same signature but no body and put the attribute on that? |
Yes, with the I just noticed that my code example used |
Thanks. With this I was able to successfully target the method, edit it's IL and it came out the other side as expected. |
So ran into an issue. I have a situation where I need to run the custom rules against the orig_MethodName. so, I have something like this. UpdateHeroControllerAttack is never executed. If I stuck that attribute on Attack, then it executes, but it executes against the new method, not the original one. Thoughts? [UpdateHeroControllerAttack]
public void orig_Attack(AttackDirection attackDir) { }
public void Attack(AttackDirection attackDir)
{
ModHooks.Instance.OnAttack(attackDir);
orig_Attack(attackDir);
} |
At the moment, custom attributes on orig_ methods are ignored. It's 02:49 here right now so I won't risk unexpectedly breaking anything, will take a look at it tomorrow :) |
No worries. This is a work-in-progress project anyway. I'm just trying to get away from our current system of hand editing the dll with dnspy with every release ;) |
8d20c7c...5072f2b fixes it - custom attributes are now copied from the mod's orig_ method to the newly created orig_ method. If any further questions arise, I'm now in the Hollow Knight Discord server. |
FWIW, the DynamicMethodDefinition delegate helpers should work in MonoModRules. |
@leo60228 No they don't, and I don't know what makes you think that. Have you even looked at how
It's impossible to achieve this using MonoModRules. You can't embed object references during patching to be resolved at runtime reliably. And on top of that, the MonoModRules type is unbound from the rest of the assembly. Your best bet is to do the same thing Everest does: Emit a call to a static method that's defined in your patch class. This way, you can execute any arbitrary code and even "process" values on the stack. |
In other words, I'm closing this issue. If you need runtime injection, take a look at the RuntimeDetour README. If you need patch-time injection, MonoModRules is the best one can get for now. As explained above, you can emit a call to a static method which lets you do more advanced things. |
The provided example with attribute runs post-patch. I want to have an attribute that runs pre-patch to decide if I want to patch original method or not (i.e. if it exist I dont want to patch because I'm in CreateOnly mode). Are there any way to do it via attribute? |
Is it possible to add statements in the middle of a method with this framework? This stuff is great for replacing/appending/prepending methods, but I have a few cases where I need to put stuff in the middle of a method. I'd prefer to not have to copy the entire method from the original, but understand if this is outside the scope of this tool.
The text was updated successfully, but these errors were encountered: