Un-nest nested types

Transforms nested types into non-nested types. Nested types are often generated by the compiler when you use a lambda expression, anonymous method, an iterator block (yield return), or an async method. Unnesting them removes the ability for decompilers to reconstruct these structures.

BeforeAfter
public class Item
{
    string Id;
}

...

var x = Items.Where(item => item.Id == id);
public class Item
{
    string Id;
}

// Rummage un-nested and renamed
// this compiler-generated type.
public sealed class Handle
{
    public string Neg;
    public bool Get(Item keys)
    {
        return keys.Id == this.Neg;
    }
}

...

var handle = new Handle();
handle.Neg = id;
var x = Items.Where(
  new Func<Item, bool>(handle.Get));

As with many other algorithms, the decompiled code looks longer, but the actual IL representation is almost exactly the same length as before obfuscation.

The nesting structure of types reveals information about the interconnection between different parts of the code and their relationship to each other. Rummage turns nested types into top-level types wherever possible, thus removing this information and obfuscating the connection between different methods.

Rummage automatically takes care of difficult cases. For example, private or protected methods that become inaccessible after unnesting are called via extra redirect methods, further blurring the code structure:

BeforeAfter
class MyControl : Control
{
    public void Method()
    {
        new Inner().Click(this);
    }

    private class Inner
    {
        public void Click(MyControl ctl)
        {
            // OnClick is a protected method
            // inherited from Control
            ctl.OnClick(EventArgs.Empty);
        }
    }
}
class MyControl : Control
{
    public void Method()
    {
        new Inner().Click(this);
    }

    // This method added by Rummage
    public void Error(EventArgs e)
    {
        OnClick(e);
    }
}

class Inner
{
    public void Click(MyControl ctl)
    {
        ctl.Error(EventArgs.Empty);
    }
}