Skip to content
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

Closed
iamwyza opened this issue Nov 15, 2017 · 14 comments
Closed

Question: Injection #15

iamwyza opened this issue Nov 15, 2017 · 14 comments

Comments

@iamwyza
Copy link
Contributor

iamwyza commented Nov 15, 2017

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.

@0x0ade
Copy link
Member

0x0ade commented Nov 15, 2017

It's already possible to insert your own instructions on IL-level using MonoModRules. Here's an example which replaces strings, but it should be similar enough:

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 MonoModCustomMethodAttribute, not MonoModCustomAttribute. Sorry!


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).

@0x0ade
Copy link
Member

0x0ade commented Nov 15, 2017

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.

@iamwyza
Copy link
Contributor Author

iamwyza commented Nov 15, 2017

Thanks! i'll try testing it soon.

@iamwyza
Copy link
Contributor Author

iamwyza commented Nov 15, 2017

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?

@0x0ade
Copy link
Member

0x0ade commented Nov 15, 2017

Yes, with the [MonoModIgnore] attribute. This will make sure that the method doesn't get replaced with the stub. Custom attributes are still being handled in this case.

I just noticed that my code example used MonoModCustomMethodAttribute, not MonoModCustomAttribute. Sorry about that!

@iamwyza
Copy link
Contributor Author

iamwyza commented Nov 15, 2017

Thanks. With this I was able to successfully target the method, edit it's IL and it came out the other side as expected.

@iamwyza
Copy link
Contributor Author

iamwyza commented Nov 16, 2017

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);
        }

@0x0ade
Copy link
Member

0x0ade commented Nov 16, 2017

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 :)

@iamwyza
Copy link
Contributor Author

iamwyza commented Nov 16, 2017

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 ;)

@0x0ade
Copy link
Member

0x0ade commented Nov 16, 2017

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.
Good luck :)

@leo60228
Copy link
Contributor

FWIW, the DynamicMethodDefinition delegate helpers should work in MonoModRules.

@0x0ade
Copy link
Member

0x0ade commented Oct 10, 2018

@leo60228 No they don't, and I don't know what makes you think that. Have you even looked at how EmitDelegate and MonoModRules work?

EmitDelegate is part of the HookGen component for a reason. It emits a delegate reference and invokes it.

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.

@0x0ade
Copy link
Member

0x0ade commented Oct 10, 2018

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.

@Kein
Copy link

Kein commented Apr 9, 2021

t's already possible to insert your own instructions on IL-level using MonoModRules. Here's an example which replaces strings, but it should be similar enough:

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants