This "design patterns" reference is primarily aimed at how to use Patchwork's attributes effectively.
When you need to create a new class:
[NewType]
public class MyNewClass
{
// your code
}
When you need to modify an instanced class:
[ModifiesType]
public class SomeClassMod : SomeClass
{
// your code
}
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.
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
.
Field initializers are not supported outside method bodies; therefore, you will need to modify the constructor or initialize the field where needed.
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.
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;
}
}
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);
}
}
When you need to create a method:
[NewMember]
public void NewMethod()
{
// your code
}
When you need to modify a method:
[ModifiesMember("SomeMethod")]
public void SomeMethod()
{
// your code
}
Patch configuration can be achieved in a number of ways. You could create a GUI, for example.
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 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();
}
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.
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?