Patchwork Design Patterns

‹ Unity Modding

If you want to see more resources like this:

https://www.patreon.com/fireundubh

Patchwork Design Patterns

This "design patterns" reference is primarily aimed at how to use Patchwork's attributes effectively.

Basics

Classes

Creating Classes

When you need to create a new class:

[NewType]
public class MyNewClass
{
    // your code
}

Instanced Classes

When you need to modify an instanced class:

[ModifiesType]
public class SomeClassMod : SomeClass
{
    // your code
}

Static Classes

When you need to modify a static class:

[ModifiesType("Fully.Qualified.Path.To.SomeClass")]
public static class SomeClassMod
{
    // your code
}

Static classes include abstract and sealed classes.

Constructors

When you need to modify the body of a constructor:

[MemberAlias(".ctor", typeof(object))]
private void object_ctor() { }

[ModifiesMember(".ctor")]
public void CtorNew()
{
    this.object_ctor();
    // duplicate and modify original constructor code, if any
}

Static constructors are named .cctor.

Fields

Initializers

Field initializers are not supported outside method bodies; therefore, you will need to modify the constructor or initialize the field where needed.

Aliases

When you need to reference a private field:

[ModifiesMember("privateFieldName", ModificationScope.Nothing)]
private bool alias_privateFieldName;

Sometimes source methods will have overly complex bodies, in which case you may need to copy the source body. However, the source body will typically reference a number of private fields, which are inaccessible outside the constructor. Fortunately, you can simply replace all occurrences of the original field name with alias_privateFieldName. Patchwork will take care of substitution.

Properties

Creating Properties

When you need to create a property with a backing field:

[NewMember]
private bool _propertyName;

[NewMember]
public bool PropertyName
{
    [NewMember]
    get
    {
        return this._propertyName;
    }
    [NewMember]
    private set
    {
        this._propertyName = value;
    }
}

Modifying Properties

When you need to modify the methods of a property:

[NewMember]
[DuplicatesBody("get_PropertyName")]
public bool source_get_PropertyName()
{
    // ignored
    return true;
}

[NewMember]
[DuplicatesBody("set_PropertyName")]
private void source_set_PropertyName(bool value)
{
    // ignored
}

[ModifiesMember("PropertyName")]
public bool PropertyName
{
    [ModifiesMember("get_PropertyName")]
    get
    {
        return this.source_get_PropertyName();
    }
    [ModifiesMember("set_PropertyName")]
    private set
    {
        this.source_set_PropertyName(value);
    }
}

Methods

Creating Methods

When you need to create a method:

[NewMember]
public void NewMethod()
{
    // your code
}

Modifying Methods

When you need to modify a method:

[ModifiesMember("SomeMethod")]
public void SomeMethod()
{
    // your code
}

Advanced

Adding Patch Settings

Patch configuration can be achieved in a number of ways. You could create a GUI, for example.

INI File Parser

Personally, I prefer INI settings. On the patch side, I use static nested classes for each INI section.

// PatchSettings.cs

[NewType]
public static class PatchSettings
{
    [NewType]
    public static class Cheats
    {
        private const string SECTION = "Cheats";
        
        public static bool GodMode { get; set; }
        
        static Cheats() {
            // UserConfig is a generic wrapper for the portable Mono-compatible INI File Parser library.
            GodMode = UserConfig.Parser.TryGetBool(SECTION, "bMyCheat", false);
        }
    }
}

// Player.cs/ApplyDamage

[ModifiesMember("ApplyDamage")]
public void mod_ApplyDamage()
{
    if (PatchSettings.Cheats.GodMode)
    {
        return;
    }
    
    this.source_ApplyDamage();
}

You can download Ricardo Hernández's INI File Parser library from GitHub or NuGet.

Duplicating Methods

Duplicating the original method is always a good idea when you want to support configuration, or return the value of the original method.

[NewMember]
[DuplicatesBody("SomeMethod")]
public void source_SomeMethod()
{
    // ignored
}

[ModifiesMember("SomeMethod")]
public void mod_SomeMethod()
{
    if (PatchSettings.DoSomethingElse.Enabled)
    {
        // your code
        return;
    }
    
    this.source_SomeMethod();
}

Throwing Unreachable Exceptions

Unreachable exceptions are exceptions that should never happen; they are useful for structuring code, returning from typed methods, and, in case they ever happen, indicating where something went really, really wrong.

Practical Example

You want to duplicate a method that returns a value, but to compile, the method requires a return statement.

[NewMember]
[DuplicatesBody("SomeMethod")]
public bool source_SomeMethod()
{
    // missing return statement
}

Instead:

[NewMember]
[DuplicatesBody("SomeMethod")]
public bool source_SomeMethod()
{
    // you should throw your own exception, but throwing NotImplementedException is fine, too.
    throw new NotImplementedException("source_SomeMethod");
}

You could just return an appropriate value assuming Patchwork will override the body, but if something does go horribly wrong, how will you know?