Code Monkey home page Code Monkey logo

sharpin's Introduction

Sharpin

Simple WIP Mixin Framework for C# using Mono.Cecil

Usage

PreSharpin

When working with third-party assemblies, access levels aren't always how you'd want them to be. Sometimes it's necessary to make certain things public or non-final (readonly / sealed).

The PreSharpin class can be used to apply access transformers to a target assembly, which you can then add as a Reference to your Mixin project instead of the original assembly.

PreSharpin.ApplyAccessTransformer("CleanTest.exe", "CleanTest-presharp.exe", @"
    # this is a comment
    public-f CleanTest.PrivateTest

    public System.Boolean CleanTest.PrivateTest::IsThisGud()
    public System.Boolean CleanTest.PrivateTest::IsThisGud(System.String)

    public System.String CleanTest.PrivateTest::noseepls # I can see!

    public-f System.Int32 CleanTest.PrivateTest::notouchpls
    ");

Modifiers can be applied to classes, methods and fields.

  • public will simply make the target public
  • -f will strip readonly for fields and sealed for classes
  • -ns will add a [NonSerialized] attribute to the field (this is necessary when making fields public that are serialized, like in Unity's MonoBehaviour; otherwise Unity will not be able to load scenes with this behaviour)

Inject

[Mixin(typeof(RudeBear))]
public abstract class MixinRudeBear : RudeBear {
    [Inject(method = "System.Void RudeBear::CommonDeath(System.Int32)", at = "HEAD", cancellable = true)]
    public void CommonDeath(int specialText, CallbackInfo info) {
        if(RudeAPIHooks.OnDeath(this)) {
            Debug.Log("Not today!");
            info.Cancel();
        }
    }
}

will (simplified) result in

public class RudeBear {
    // [...]
    public void CommonDeath(int specialText) {
        if(RudeAPIHooks.OnDeath(this)) {
            Debug.Log("Not today!");
            return;
        }
        // [... the rest of CommonDeath ...]
    }
    // [...]
}

Overwrite

[Mixin(typeof(SpearKill))]
public abstract class MixinSpearKill : SpearKill {
    [Overwrite]
    public void OnTriggerEnter2D(UnityEngine.Collider2D col) {
        UnityEngine.Debug.Log("Spears were never meant to be used for killing.");
    }
}

Implements

public interface IMonster {
    public int Health { get; set; }
}

[Mixin(typeof(StardewValley.Monsters.Monster))]
[Implements(typeof(IMonster))]
public class MixinMonster : StardewValley.Monsters.Monster, IMonster {
    public int Health {
        get { return this.health; }
        set { this.health = value; }
    }
}

CaptureLocal & StoreLocal

Pretend we have SomeCharacter with an OnDamage function that takes a base value, and inside it does calculations to get a modified value based on the character's armor stats or something.

These are the locals (with name, pretend it's a Debug build):

.locals init (
    [0] int32 armorStat,
    [1] int32 modifiedDamage
)

Sadly locals lose their names on Release builds, so we have to access them by index.

The following will grab the modifiedDamage right before it's applied via SomeCharacter::ReduceHealth

[Inject(method = "System.Void SomeCharacter::OnDamage(System.Int32)", at = "IL_0017: call System.Void SomeCharacter::ReduceHealth(System.Int32)")]
public void OnDamage(int baseDamage, [CaptureLocal(1, typeof(int))] int modifiedDamage, [StoreLocal(1, typeof(int))] out int outModifiedDamage) {
    if(modifiedDamage == 1336) { // Ugh, so close. Let's just sneakily add one to make it happen.
        outModifiedDamage = modifiedDamage + 1;
    }
}

Injection Points

There are two inbuilt injection points for the at parameter: HEAD and RETURN.

  • HEAD will always be right before the first statement of the target method
  • RETURN will be before any return statement in the target method - this can be multiple, so set the expectedInjections attribute accordingly

Apart from these, specific instructions can be named in a similar manner that you would see them in ILSpy, for example:

IL_0017: call System.Void SomeCharacter::ReduceHealth(System.Int32)

Note that injection points will always be shifted backwards to a state of empty stack. This means that if we have bytecode that looks like

IL_0009: ldloc.0
IL_000a: ret

and we specify RETURN or IL_000a: ret as injection point, the actual injection point will end up right before IL_0009: ldloc.0.

sharpin's People

Contributors

blaytheninth avatar

Stargazers

 avatar

Watchers

 avatar  avatar

sharpin's Issues

Sanity: Method Access

Need to catch injected calls to classes, methods and fields that may be accessible to the mixin class, but won't be accessible to the target class.

Will result in a System.MethodAccessException otherwise.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.