Giter Site home page Giter Site logo

patchwork's Introduction

Patchwork

MIT License | Latest Version: 0.9.1

Build status


User's Guide

Patchwork is a program that allows you to mod certain games using mod files made by others.

You will need to download Patchwork from a mod site or a similar source, because the version that can be downloaded from this site is only suitable for developers.

Requirements

Patchwork is a Mono/.NET application and so needs the .NET Framework or Mono to run.

Instructions

Using the program is straight-forward:

  1. Extract it into an empty folder.

  2. Launch the program (PatchworkLauncher.exe)

    Note: On Linux, you may need to open the program using mono explicitly (see instructions on running Mono applications in your distribution).

  3. Specify your game folder in the dialog box or type it in the textbox.

    Note: The dialog box will not display hidden files or folders.

  4. Go to the Active Mods menu and add the mod file(s) (usually ending with .pw.dll) to the list of mods, checking those you want enabled.

    Note: Mod files so chosen will normally be copied to the Mods folder.

  5. Use Launch with Mods and Launch without Mods to start the game.

Developer's Guide

If you have any ideas or new direction for this project, please contribute. Right now I've lost interest in it, but I really think it can be improved and I'll probably start working on it again.


Patchwork is a framework for integrating your own code into existing .NET assemblies ("patching" them). It allows you to edit, create, or replace things such as types, properties, and methods in a simple, straight-forward, and declarative way, using attributes.

The framework lets you basically rewrite entire programs, such as games, according to your whims (as long as they're written in a .NET language of course). Little in the code is beyond your control, and you can write it all using the same tools as the original developers.

You write code in C# or another language, and that code is injected into the target assembly according to your patching declarations. It is minimally transformed, fixing references to such things as types and methods, so that it remains valid at the point of injection.

The framework was written with game modding in mind, but can be used for any purpose.

The framework is mostly documented, including the non-public members.

Moddable Games

Like I said above, the library was written with game modding in mind. In general, you can mod two kinds of games with it: i

.NET/XNA

Games that run on .NET/XNA. You can mod pretty much anything in this case. However, there aren't many popular XNA titles.

Unity/.NET

Games that run on Unity and use .NET for their game logic (mainly C#, but some also use other languages).

Luckily, the majority of popular Unity titles do primarily use .NET.

Modding in this case is somewhat more limited, as you can only mod the game logic in the scripts, but from experience, you still have vastly more power than typical official modding tools would give you.

Components

The framework consists of several separate components.

PatchworkLauncher (PWL)

This is the end-user patching GUI. Users use this program to apply your modifications to their games or applications. It is most convenient to download it from the Releases section.

You also use this program to test your patch assembly for yourself.

OpenAssemblyCreator (OAC)

This is a command-line tool packaged with PWL. It allows you to create an open assembly to reference in your patch assembly. More on this later.

Patchwork.Attributes (PWA)

The Patchwork.Attributes assembly is meant to be referenced by your patch assembly, and is compiled with framework version 2.0 to improve compatibility. It contains the attributes that serve as patching instructions. It has no dependencies.

You can conveniently get this package from NuGet: Patchwork.Attributes and reference it in your patch assembly.

Patchwork.Engine

This is the library that actually does the patching. It can be used separately from the GUI. However, you generally wouldn't want to do so unless you wanted to write a new front-end, some kind of script, or... had some other reason.

Naturally, it's packaged with the launcher.

Getting Started

First, get the latest PWL release from the releases section.

Unless someone else already wrote it, you'll need to write an AppInfo.dll file for the game you want to patch. See the section on the launcher for more information.

Next, create a new project (preferably with a name ending with .pw for reasons explained later).

Reference the Patchwork.Attributes assembly from that project.

Since you probably don't want to copy files around manually, you should take advantage of the DontCopyFiles option in the Preferences section of the preferences.pw.xml file. It lets you add files to your mod list without copying them to a mods folder, so you can just add the result of your build directly.

That's... it. You're ready to go. Good luck out there.

By the way, patching using the launcher creates a dependency on Patchwork.Attributes in the target (patched) assembly.

Overview

There are a few stages to writing a patch.

Finding What to Patch

Before you start writing your patch assembly, you need to find what you want to patch. This involves decompiling the target assembly. See Recommended Decompilers below for more information.

Also, take note of the target framework version of the assembly, as for the most reliable results you'd want your patch to be built against the same framework version.

Creating an Open Assembly

You also need to have an "open" version of the assembly you want to patch. "Open" here means that all of its members are public and non-sealed.

To see why this is required, imagine you have a class like:

class Player {
	private int _hitpoints;
	
	public void GetHit() {
		_hitpoints--;
	}
}

And you want to overwrite the GetHit method to perform _hitpoints++ instead.

The problem is that _hitpoints can only be accessed from inside the class Player. But you have to write the code _hitpoints++ in your own assembly. The framework will inject it into the correct spot, but your C# compiler doesn't know that and won't let you access the member.

Changing all members to public allows you to access the member. However, it can cause problems too. You might make a mistake and illegally refer to a non-public member, for example.

On the brighter side, the PEVerify tool (which executed automatically) will catch any accessibility issues and warn you about them.

You create an open assembly using the command-line tool OpenAssemblyCreator.exe, also called OAC. Using it is straight-forward.

Creating the Patch Assembly

You patch an existing target assembly by writing a patch assembly (probably with the target assembly referenced), and load it as input to patch a "target" assembly. This assembly contains attributes that are used as patching instructions.

You need to specify the PatchAssembly attribute on any such patch assembly.

Writing It

Patch assemblies consist of patch types, which are new types or just sets of modifications to an existing type. Here is a simple example:

[ModifiesType] //means the class modifies another class
class AttackMod : Attack {
	//note that by default it modifies its parent class, if any, 
	//so this class modifies Attack.
	
	[NewMember] //this means a new method will be injected into the type
	public Hit() {
		//whatever you want to do
	}
	
	[ModifiesMember("ExistingMethod")]
	public void ExistingMethodRevised() {
		//your instructions will replace those of ExistingMethod,
		//as defined in the modified type
	}
}

[NewType]
public class MyNewType {
	//No need to specify attributes here.
	private readonly string _myField;
	
	public int MyMethod(int x) { ... }
}

You can add any members to the type, and attach the right attributes to them, depending on whether you want them to modify existing members or create new ones. (If you create a new type, there are no existing members to modify, of course).

Compiler-generated members are imported as new members by default, even if they don't have a patching attribute.

Format and Additional Info

The recommended extension for patch assemblies is pw.dll.

In order for a patch assembly to work with the Patchwork launcher, it must define a class with the following requirements.

  1. It implements Patchwork.Attributes.IPatchInfo
  2. It is decorated with Patchwork.Attributes.PatchInfoAttribute.
  3. It has a default constructor.

There must be only one such class in the assembly.

The class should not contain any references to any types not found in the GAC or in Patchwork.Attributes.

It is instantiated in a separate AppDomain from the rest of the application, and this AppDomain is unloaded if the user removes it from the patch list.

The PatchInfo is needed to tell the launcher what file to patch. The class can find the file based on the operating system and other information.

Patching

The patch is mostly viewed as data by the patchwork launcher. To patch another assembly with your patch assembly, you must load the pw.dll file into the launcher and then start the game.

That's it.

Patchwork Launcher

Each launcher executable is meant to work with one game. To work with a game, someone must write an AppInfo.dll assembly (as described below) and put it in the launcher directory. The launcher loads it on startup and uses it to get information about the game. The launcher should be distributed with this file.

The launcher allows users to manage patches for the chosen game, as well as change the order in which they are applied. It keeps the original game files on disk and switches them with modded files when the user launches the game. It only patches the files when necessary. It stays in the background, and once the game is exited, the launcher switches to the original files once more.

Once it starts up, the launcher also checks the state of the files and fixes them if necessary, in case it was terminated unexpectedly.

The launcher works on Mono and is written in Windows Forms for that purpose.

AppInfo.dll

This file is required for the launcher to work correctly with a game. It's an assembly (the name doesn't matter) containing a type that:

  1. Inherits from Patchwork.Attributes.AppInfoFactory.
  2. Is decorated with Patchwork.Attributes.AppInfoFactoryAttribute.
  3. Has a default constructor.

This type has a CreateInfo method that returns an Pathcwork.Attributes.AppInfo object which provides information about the game.

There must be only one such class in the assembly.

This class is instantiated during runtime, in the same AppDomain as the original application. The launcher can't start if the file is invalid or not found.

If this file is not found, an error message is generated and the launcher doesn't work properly.

Available Attributes

These attributes are located in the Patchwork.Attributes namespace. Note that this isn't necessarily a full list.

Note about attribute constructors

Attributes that require types as parameters invariably have an object parameter instead of a Type parameter.

This is a necessary workaround. You still use typeof(T) to specify the type.

PatchingAssembly

You must add this attribute to your assembly (using [assembly: PatchingAssembly]) for it to be recognized as an assembly that contains patching types.

ModifiesType(name)

Says that your type modifies another type in the game. Allows you to use ModifiesMember within that type.

You can specify the full name of the type you want to modify, or let PW infer it.

ReplacesType(name)

Alternative version of the above attribute. Removes all the members of the type, overwriting it with your own members. Currently implemented only on enums. ModifiesMember attributes are invalid, since they have no meaning.

ModifiesMember(name,scope)

Modifies the member, such as its accessibility, body, and maybe other things. scope controls the scope of the modification.

ModifiesAccessibility(name)

Restricted form of the last attribute. Modifies just the accessibility to be identical to your member.

Provided for convenience.

NewMember(altName)

Introduce this as a new member to the patched type. If you specify altName, the member will be introduced under this name instead.

If the member collides with an existing member, its name will be suffixed with _$pw$_RANDOM, e.g. _$pw$_dfRff. A warning will be emitted.

DuplicatesBody(methodName, declaringType)

Put this on a method marked with NewMember or ModifiesMember to insert the body of another method into it. Optionally, you can provide the type that declares the method; otherwise, it defaults to the type being modified. You can use it to call original members in the modified type, as it takes the body from the original assembly.

NewType(altName, altNamespace)

Put this attribute on a type to denote it is a new type that will be introduced into the assembly.

The name of the type will normally be the same as it is in your assembly, including namespaces and so forth. However, altName and altNamespace allow you to specify an alternative name/namespace.

In case of a collision, the type name will be suffixed with _$pw$_RANDOM, e.g. _$pw$_dffERr. A warning will be emitted.

You can create any kind of type you like, whether interface, struct, or class. You can have inheritance, generic type parameters, put constraints on those parameters, etc. Anything goes.

You don't need to use creation attributes on any of your type's members, except for other types. They will be considered to have the NewMember attribute.

You can put ModifiesType on a nested type inside a NewType, but not ModifiesMember.

RemoveThisMember

Removes a member of the same name from the modified type. Added for the sake of completeness.

After using it, it's wise to mark the member using the [Obsolete] attribute so you don't invoke it by accident.

PW will not check if this action causes an error, but errors may still come up in the patching process later on.

It is not possible to remove types.

DisablePatching

Disables the patching of this element and all child elements, including nested types.

Modifications will not be performed, and new types will not be created.

MemberAlias(memberName, declaringType, aliasCallMode)

This attribute lets you create an alias for another member. When Patchwork encounters a reference to the alias member in your code, it will replace that reference with the aliased member.

It is useful for making explicit calls to things such as base class constructors. If aliasCallMode == AliasCallMode.NonVirtual, a call to the member is translated to a non-virtual call, bypassing any overrides. This will allow you to inject base.OverriddenMethod() sorts of calls into the methods you modify.

PatchworkDebugRegister(memberName, declaringType)

This is a special attribute for debugging purposes. You can specify a static string member that will be used as a debug register for the current method. It will be updated with information about which line number is going to be executed next. It lets you find the line number at which an exception was thrown (or something else happened), when the exception does not contain this information.

For example, the register can contain the following after an exception is thrown and is caught in the same method:

10 ⇒ 11 ⇒ 45 ⇒ 46 ⇒ 47 ⇒ 251 ⇒ 252

If the catch clause was at line 251, then line 47 is the one that threw the exception.

This is a hack, but it can be quite useful.

ToggleFieldAttributes(fieldAttributes)

This custom attribute toggles (XORs) the intrinsic deceleration attributes of the patched field with the input attributes. It must be used with an action attribute, such as ModifiesMember.

This attribute allows you to change accessibility, as well as more arcane things. Using it incautiously can cause runtime errors.

ToggleMethodAttributes(methodAttributes)

This custom attribute toggles (XORs) the intrinsic deceleration attributes of the patched method with the input attributes. It must be used with an action attribute, such as ModifiesMember.

This attribute allows you to change accessibility, add/remove the sealed qualifier, and perform other, more arcane tasks. Using it incautiously can cause runtime errors.

Naming Conventions

It is best practice to follow certain naming conventions when writing your patch assembly.

You should prefix each code element according to the action the framework is expected to perform on it. That way, you will be able to tell what sort of member it is just by glancing at the name, and your code will be more readable to others.

Function Form Related Attribute
Modification of Name type/member from the original assembly mod_Name ModifiesType, ModifiesMember
Duplicate of Name in the modified type orig_Name DuplicatesBody
Duplicate of Name in type Type in the original assembly orig_Type_Name DuplicatesBody
Alias of Name in type Type alias_Type_Name MemberAlias
New type or member (none) NewMember, NewType

For instance constructors, use the name ctor and for static ones use cctor.

(none) means that you should not prefix the name with anything.

Specific Issues

About Overloading

When you put an attribute on a code element, the framework will usually use that element's name (or an alternative name you supply) and, in the case of methods and properties, their parameters, to find what to modify.

To modify one of several overloaded methods, you just need to duplicate that method's parameter types exactly.

Note that return types of existing methods cannot be modified.

Modifying/Creating Properties

Note that to modify a property's get and set accessors, you need to put ModifiesMember on the accessor you want to modify, not on the property deceleration. The accessors are actually methods, and it's those the framework modifies.

However, you can choose to put NewMember on the property, in which case the accessors will be created automatically.

This also applies to the property's accessibility. In the IL, only get/set methods have accessibility, so if you want to modify it you have to put the attribute on the accessor, possibly on both.

You might need to use the explicit name of the property accessor to modify it (if your property is named differently). Accessor names are normally get_{Property} and set_{Property}.

Pretty much the only time you'd want to use ModifiesMember attribute on a property itself is when you want to create a brand new accessor for the property. In this case, the property data must be modified. You'll still put the NewMember attribute on the new accessor.

Modifying Constructors

You can't create constructors for existing types, but you can modify existing ones. Constructors are just methods called .ctor. You just need to duplicate their signature in a normal method, and change the modified member name in the attribute. Every object has a default .ctor.

Static constructors are called .cctor. Not all types have static constructors.

Note that constructors also contain the type's initializers, so you may need to copy those or the class might not work correctly.

Instance constructors normally contain explicit calls to a base class constructor (e.g. base::.ctor()). It is best practice to add this call. This can be achieved by using the MemberAlias attribute. For example:

[MemberAlias(".ctor", typeof(object))]
private void object_ctor() {
	//this is an alias for object::.ctor()
}

[ModifiesMember(".ctor")]
public void CtorNew() {
	object_ctor();
	IEModOptions.LoadFromPrefs();
}

Static constructors do not contain such a call.

Nested Types

You can have your nested types modify other types, or you can modify other nested types, without regard to the nesting level. The location of your nested type doesn't matter, and using ModifiesType behaves the same way.

To modify a type by name (rather than having PW infer it), you have to give the full name of the type, without regard to where the attribute appears.

In the IL and in Mono.Cecil, as well as in this framework, you use / to indicate nesting. E.g. Namespace.ContainerClass/Nested/NestedNested.

You can also have a new nested type inside a modification to an existing type. In this case, the nested type will be moved to the modified type.

Modifying Explicitly Implemented Methods

These are regular methods with different actual names. The names are [INTERFACE_FULL_NAME].Method. For example, if IEnumerable<T>.GetEnumerator() were explicitly implemented, you'd set the member name to:

	System.Collections.Generic.IEnumerable<T>.GetEnumerator

The dots are actually part of the name of the method, just like the dot in .ctor is. IL doesn't follow C# naming rules.

Patching History

The launcher embeds various attributes in the target assembly that let you see what Patchwork did to it. These are called history attributes. The following are embedded.

Also, the patching attributes you use also get embedded, except for PatchAssembly.

PatchingHistoryAttribute

Abstract parent of all history attributes.

PatchedByAssemblyAttribute

Contains information about the patch assembly, the original assembly (before patching was performed), and the Patchwork assembly that performed patching.

PatchedByMemberAttribute

Indicates the member in the patch assembly that contained the patching instruction to patch this member.

PatchedByTypeAttribute

Indicates the type in the patch assembly that contained the patching instruction to patch this type.

Limitations

In this section I'll list the limitations of the library, in terms of the code that it can deal with at this stage, and what it can't allow you to do. This section will be updated as more features get worked in.

Assemblies

  1. Multi-module assemblies won't work properly (either as patches or patch targets). Note that few IDEs (if any) can naturally produce such assemblies, though they can be the result of tools such as ILMerge.
  2. Inter-dependencies between multiple patch assemblies haven't been tested.

Members

  1. You can't add new constructors or finalizers to existing types.
  2. Existing declarations can only be modified in limited ways. For example, you can't un-seal a sealed class, change type parameters and their constraints, etc. New members can still be sealed or unsealed, etc, as you prefer.
  3. Field initializers don't work in modifying types, except for const fields. This is unlikely to be fixed anytime soon, as it requires pretty tricky IL injection.

Language Features

  1. unsafe context features, like pointers and pinned variables, probably won't work.
  2. Various exotic and undocumented (in C#) constructs cannot be used, such as __arglist.
  3. Exotic features such as optional and required signature modifiers (OptionalModifier, aka modopt; RequiredModifier, aka modreq), isn't supported

Other .NET Languages

This library is for transforming IL, not transforming source code, so it doesn't actually care what language you write in. As long as you put attributes on things that are recognizable in the IL as properties, methods, and classes, it will probably work correctly.

That said, you could experience more problems if you write in languages other than C#, simply because they can be compiled to very different IL, and the different input could reveal flaws I never encountered during testing.

However, don't take this as me discouraging you from using other languages.

Recommended Decompilers

I've tried a number of decompilers.

  1. Telerik JustDecompile: Probably the best overall decompiler I've tested. It has great search functions, can produce IL as well as C#, good decompilation ability, and has a great interface. Decompilation isn't perfect, as it can't decompile such things as iterators. Tends to handle errors fairly decently.
  2. ILSpy: This one generates the best source code by far from the decompilers I've tested. It can decompile iterators, lambdas, you name it. Unfortunately, it has no search function that deserves the name and handles errors very badly, even when set to IL. The interface is also inconvenient.
  3. dotPeek: It has a good interface and decent search, with the very helpful ability of finding related (e.g. derived) types, but isn't very good at decompiling compiler-generated code. It can't even handle things like auto-properties.

Dependencies

  1. Mono.Cecil, without which none of this would have been possible.
  2. Serilog, used for logging.

patchwork's People

Contributors

awgil avatar gregros avatar gregros-personal avatar kevorr avatar soniczentropy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

patchwork's Issues

TryConstructAttribute throws NullReferenceException while updating types

I created a new Patchwork project. The only "mod" consists of declaring a new type and nothing else.

In TryConstructAttribute, customAttrData.Constructor is {System.Void System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(System.String)} when the null is created.

Traced to

In CustomAttributeHelper.cs:

		private static object TryConstructAttribute(this CustomAttribute customAttrData) {
			var constructor = customAttrData.Constructor.Resolve();

			// customAttrData.Constructor == {System.Void System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(System.String)}
			if (constructor == null)
			{
				throw new NullReferenceException();
			}

			var constructorInfo = (ConstructorInfo) constructor.LoadMethod();
			var args = customAttrData.ConstructorArguments.Select(UnpackArgument).ToArray();
			if (constructorInfo.GetParameters().Any(p => p.ParameterType == typeof (Type))) {
				//we cannot invoke this constructor.
				return null;
			}
			var ret = constructorInfo.Invoke(args);
			return ret;
		}

Exception details

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=Patchwork.Engine
  StackTrace:
   at Patchwork.Engine.Utility.CecilLoader.LoadMethod(MethodDefinition methodDef) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\Utility\Reflections and Cecil\CecilLoader.cs:line 111
   at Patchwork.Engine.Utility.CustomAttributeHelper.TryConstructAttribute(CustomAttribute customAttrData) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\Utility\Reflections and Cecil\CustomAttributeHelper.cs:line 137
   at Patchwork.Engine.Utility.CustomAttributeHelper.<>c.<GetAllCustomAttributes>b__3_1(CustomAttribute attr) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\Utility\Reflections and Cecil\CustomAttributeHelper.cs:line 43
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Patchwork.Engine.Utility.CustomAttributeHelper.GetAllCustomAttributes(ICustomAttributeProvider provider) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\Utility\Reflections and Cecil\CustomAttributeHelper.cs:line 44
   at Patchwork.Engine.Utility.CustomAttributeHelper.GetCustomAttributes[T](ICustomAttributeProvider provider) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\Utility\Reflections and Cecil\CustomAttributeHelper.cs:line 59
   at Patchwork.Engine.Utility.CustomAttributeHelper.HasCustomAttribute[T](ICustomAttributeProvider memberDef) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\Utility\Reflections and Cecil\CustomAttributeHelper.cs:line 24
   at Patchwork.Engine.Utility.CustomAttributeHelper.IsPatchingAssembly(AssemblyDefinition assembly) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\Utility\Reflections and Cecil\CustomAttributeHelper.cs:line 33
   at Patchwork.Engine.AssemblyPatcher.<>c.<CopyCustomAttributes>b__33_8(<>f__AnonymousType12`2 <>h__TransparentIdentifier3) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\AssemblyPatcher\ModifyExisting.cs:line 61
   at System.Linq.Enumerable.<>c__DisplayClass6_0`1.<CombinePredicates>b__0(TSource x)
   at System.Linq.Enumerable.<>c__DisplayClass6_0`1.<CombinePredicates>b__0(TSource x)
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at Patchwork.Engine.AssemblyPatcher.CopyCustomAttributes(ICustomAttributeProvider targetMember, ICustomAttributeProvider yourMember, Func`2 filter) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\AssemblyPatcher\ModifyExisting.cs:line 66
   at Patchwork.Engine.AssemblyPatcher.ModifyTypeDecleration(TypeDefinition yourType) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\AssemblyPatcher\ModifyExisting.cs:line 231
   at Patchwork.Engine.AssemblyPatcher.UpdateTypes(SimpleTypeLookup`1 typeActions) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\AssemblyPatcher\AssemblyPatcher.cs:line 239
   at Patchwork.Engine.AssemblyPatcher.PatchManifest(PatchingManifest manifest, IProgressMonitor o) in E:\repos\DeadfireMods.pw Launcher\Patchwork.Engine\AssemblyPatcher\AssemblyPatcher.cs:line 422
   at PatchworkLauncher.LaunchManager.ApplyInstructions(IEnumerable`1 patchGroups, ProgressObject po) in E:\repos\DeadfireMods.pw Launcher\PatchworkLauncher\GUI\LaunchManager.cs:line 709
   at PatchworkLauncher.LaunchManager.<>c__DisplayClass46_1.<Command_Patch>b__0() in E:\repos\DeadfireMods.pw Launcher\PatchworkLauncher\GUI\LaunchManager.cs:line 335
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()

How do you modify readonly fields?

For example:

private static readonly float NearEnemyPenaltyPerSecond = 1f;

The readonly keyword was added in C# 7.2. I was thinking of readonly struct and ref readonly.

Feature Request: Keep the launcher open after patching

I recently implemented a way for Patchwork to launch a game through Steam or Galaxy, but because Steam or Galaxy does not exit with the game, the launcher stays resident. Instead of minimizing Patchwork to the tray with no possibility of restoring the window, why not keep the window open?

You can just try to restore patched files when you try to launch/do a test run again if the state is not idle, or when you close the launcher.

I'm partially thinking aloud, but I don't see why the launcher needs to disappear.

Patchwork possibly losing method attributes that are non-patchwork related?

I have the following new type to be injected via Patchwork:
[NewType]
public class MyData
{
[XmlArrayItem(ElementName = "DataName", Type = typeof(string))]
public List DataElements { get; set; }
}
However, when serializing or deserializing, the XmlArrayItem appears to be missing and the elements just get serialized as "string" instead of "DataName". The only reason I can think this might be so is that the attribute isn't ending up set on the injected method.

Patchwork problem with Generic/non-Generic method pair in source object

Example (from IEMod):
[ModifiesMember(nameof(LoadPrefab))]
public static T mod_LoadPrefab(string assetName, bool instantiate)
where T : Object

Source object has:
public static T LoadPrefab(string assetName, bool instantiate)
where T : Object
and
public static Object LoadPrefab(string prefabPath, bool instantiate)

Result: InvalidOperation Exception, because GetMethodLike in CecilHelper is expecting to get exactly one result from the source object, but both methods from the source object are matching and it can't pick between them. I'd expect it instead to realize that since the mod method is templatized it should prefer the templatized source method.

Feature Request: Support patching multiple assemblies

Currently, when you try to patch multiple assemblies (with multiple PatchInfos), you get an error like this:

error CS0579: Duplicate 'PatchAssembly' attribute

Seems like a better approach would be to have GetTargetFile return a FileInfo collection.

Patch fails with "Internal message: The given key was not present in the dictionary."

Describe the bug
Unexpected patching failure of Tyranny's Assembly-CSharp.dll.
I don't know why this happens; it's just a public class that I want to patch: public class UIConversationManager : UIHudWindow
The issue persists even if I remove all the member modifications:

using Patchwork.Attributes;

namespace TyrannyHighDPI.pw
{
    [ModifiesType]
    public class ModUIConversationManager : UIConversationManager {}
}
2023-01-07 22:55:08.146 +01:00 [Information] Created patcher for assembly: Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
2023-01-07 22:55:08.196 +01:00 [Information] =====Creating new fields=====
2023-01-07 22:55:08.285 +01:00 [Error] An error has occurred,
While trying to: Patch the game
Error type: A system error or some sort of bug. (KeyNotFoundException)
Internal message: The given key was not present in the dictionary.

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at Patchwork.AssemblyPatcher.FixTypeReference(TypeReference yourTypeRef)
   at Patchwork.AssemblyPatcher.FixMethodReference(MethodReference yourMethodRef, Boolean isntFixTypeCall)
   at Patchwork.AssemblyPatcher.TransferMethodBody(MethodDefinition targetMethod, MethodDefinition yourMethod)
   at Patchwork.AssemblyPatcher.ModifyMethod(TypeDefinition targetType, MethodDefinition yourMethod, MemberActionAttribute memberAction, MethodDefinition targetMethod)
   at Patchwork.AssemblyPatcher.UpdateMethods(SimpleTypeLookup`1 methodActions)
   at Patchwork.AssemblyPatcher.PatchManifest(PatchingManifest manifest, ProgressObject o)
   at PatchworkLauncher.LaunchManager.ApplyInstructions(IEnumerable`1 patchGroups, ProgressObject po)
   at PatchworkLauncher.LaunchManager.<>c__DisplayClass46_0.<Command_Patch>b__0()
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at PatchworkLauncher.LaunchManager.<Command_Patch>d__46.MoveNext()

Patch and Target Binaries
TyrannyHighDPI.pw.zip
I don't wanna get in trouble for sharing Obsidian's original file publicly. Can provide it on a person-to-person basis.

Support for multiple game executables.

Some of the latest unity games come with separate executables for 32\64 bit platforms, and I figured that currently patchwork isn't able to let user choose which executable to use.

I made a fork to fix this for myself. My coding skills are pretty basic so I'm not sure I should commit to a project like this, but if you want I can make a pull request to save you some time.

Here's what I ended up with:
patchwork

Improve error message when nested type isn't matched

Exception Details

System.NullReferenceException: Object reference not set to an instance of an object.
   at Patchwork.Engine.Utility.CecilOverloadResolver.GetField(TypeDefinition typeDef, String name) in E:\projects\PatchworkPathfinder\Patchwork.Engine\Utility\Reflections and Cecil\CecilOverloadResolver.cs:line 20
   at Patchwork.Engine.PatchingManifest.GetPatchedMember[T](TypeDefinition targetType, T yourMemberDef, MemberActionAttribute actionAttribute) in E:\projects\PatchworkPathfinder\Patchwork.Engine\PatchingManifest\PatchingManifest.cs:line 107
   at Patchwork.Engine.PatchingManifest.SpecializeMembers[T](SimpleTypeLookup`1 lookup, AssemblyDefinition toTargetAssembly) in E:\projects\PatchworkPathfinder\Patchwork.Engine\PatchingManifest\PatchingManifest.cs:line 135
   at Patchwork.Engine.PatchingManifest.Specialize(AssemblyDefinition assemblyDef) in E:\projects\PatchworkPathfinder\Patchwork.Engine\PatchingManifest\PatchingManifest.cs:line 166
   at Patchwork.Engine.AssemblyPatcher.PatchManifest(PatchingManifest manifest, IProgressMonitor o) in E:\projects\PatchworkPathfinder\Patchwork.Engine\AssemblyPatcher\AssemblyPatcher.cs:line 300
   at PatchworkLauncher.LaunchManager.ApplyInstructions(IEnumerable`1 patchGroups, ProgressObject po) in E:\projects\PatchworkPathfinder\PatchworkLauncher\GUI\LaunchManager.cs:line 747
   at PatchworkLauncher.LaunchManager.<>c__DisplayClass46_1.<Command_Patch>b__0() in E:\projects\PatchworkPathfinder\PatchworkLauncher\GUI\LaunchManager.cs:line 369
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at PatchworkLauncher.LaunchManager.<Command_Patch>d__46.MoveNext() in E:\projects\PatchworkPathfinder\PatchworkLauncher\GUI\LaunchManager.cs:line 369

Sample Code

namespace KingmakerMods.Mods.Game.Configurables.GoldPoints
{
	[ModifiesType]
	public class BuildingItemNew : Kingmaker.UI.Settlement.BuildingItem
	{
		[ModifiesType("Kingmaker.UI.Settlement.BuildingItem.RequiredStaff")]
		private class RequiredStaffNew
		{
			[ModifiesMember("Slots")]
			public Image source_Slots;
		}
	}
}

Add support for arbitrary IL patching

Sometimes one wants to perform a small patch on a large method body (e.g. remove a code block, replace conditional branch with unconditional, etc.).

Currently it's not very convenient - you either have to duplicate method and write a replacement, which calls original at some point (which is sometimes not possible, if undoing things original method did is tricky), or just duplicate whole body manually using decompiled sources and adjust parts (which is ugly, especially if original method is large).

What would be great is having a syntax like:

[ModifiesMember("foo")]
[PatchesBody]
static void patch_foo(MethodDefinition def) {
    // modify 'def' as needed
}

So it would be quite similar to [DuplicatesBody], only instead of discarding annotated function's body it will be executed for patching.

I'm not sure how to distinguish between overloads - it seems that currently it is done via function signature comparison. One option would be to pass a collection of method definitions (all overloads) and let the patch function itself find the correct one.

Could not resolve a method reference, most likely because it wasn't imported

Source

https://gist.github.com/fireundubh/330f35c7f9a0657a94eadcbd21c3a74f

Patch

using Kingmaker.Localization;
using Patchwork;

namespace KingmakerMods.Mods.Settings
{
	[ModifiesType]
	public class LocalizedStringNew : LocalizedString
	{
		[ModifiesMember("m_Key", ModificationScope.Nothing)]
		private string mod_m_Key;

		[ModifiesMember("Key")]
		public string mod_Key
		{
			[ModifiesMember("get_Key", ModificationScope.Nothing)]
			get { return this.mod_m_Key; }
			[NewMember("set_Key")]
			set { this.mod_m_Key = value; }
		}
	}
}

Exception Details

2018-10-24 00:19:59.660 -07:00 [FTL] Could not resolve a method reference, most likely because it wasn't imported. Details: System.Void KingmakerMods.Mods.Settings.LocalizedStringNew::.ctor()
2018-10-24 00:19:59.671 -07:00 [ERR] An error has occurred,
While trying to: Patch the game
Error type: A system error or some sort of bug. (KeyNotFoundException)
Internal message: The given key was not present in the dictionary.
The given key was not present in the dictionary.

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at Patchwork.Engine.Utility.ReflectHelper.GetEnumValueText[T](T value) in E:\projects\PatchworkPathfinder\Patchwork.Engine\Utility\Reflections and Cecil\ReflectHelper.cs:line 75
   at PatchworkLauncher.LaunchManager.Command_Display_Patching_Error(PatchingProcessException ex) in E:\projects\PatchworkPathfinder\PatchworkLauncher\GUI\LaunchManager.cs:line 528
   at PatchworkLauncher.LaunchManager.<Command_Patch>d__46.MoveNext() in E:\projects\PatchworkPathfinder\PatchworkLauncher\GUI\LaunchManager.cs:line 340

I don't understand the first log entry. I tried adding a .ctor, but there was no change. I'm guessing the real problem is the implicit operator method.

PatchworkLauncher.PatchingProcessException
  HResult=0x80131500
  Message=Exception of type 'PatchworkLauncher.PatchingProcessException' was thrown.
  Source=PatchworkLauncher
  StackTrace:
   at PatchworkLauncher.LaunchManager.ApplyInstructions(IEnumerable`1 patchGroups, ProgressObject po) in E:\projects\PatchworkPathfinder\PatchworkLauncher\GUI\LaunchManager.cs:line 715
   at PatchworkLauncher.LaunchManager.<>c__DisplayClass46_1.<Command_Patch>b__0() in E:\projects\PatchworkPathfinder\PatchworkLauncher\GUI\LaunchManager.cs:line 335
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()

Inner Exception 1:
PatchDeclerationException: The attribute on the method called 'System.String KingmakerMods.Mods.Settings.LocalizedStringNew::op_Implicit(KingmakerMods.Mods.Settings.LocalizedStringNew)' refers to '.ctor', but that member doesn't exist in this context (possibly overload resolution failure).

Yeah, that's what the PatchDeclerationException message seems to be saying.

Patchwork Launcher Win11 doesnt Work

In Win11, PatchworkLauncher stops working and frequently displays various errors.

I checked several times to see if .NET Framework 4.5 was installed and used the .NET repair tool to be on the safe side.

DuplicatesBody sometimes picks up patched method body

The following pattern doesn't always work as expected:

        [NewMember]
        [DuplicatesBody("foo")]
        public void ori_foo() { }

        [ModifiesMember("foo")]
        public void mod_foo()
        {
            ori_foo();
        }

This seems to be caused by AssemblyPatcher::UpdateMethods: the order in which it iterates over lists of NewMember & ModifiesMember is undefined. If it processes NewMember attributes first, everything works correctly. Otherwise it would duplicate mod_foo()'s body into ori_foo() (and cause infinite recursion).

Now, this is a bit strange - the comment in GetBodySource seems to imply that it should always get the unmodified body, regardless of the patching order. At least for me, it doesn't work like that - and looking at the code, I don't really understand why should it work differently.

A simple fix would be to always process NewMembers before ModifiesMembers by writing two loops.

ArgumentOutOfRangeException while patching with multiple mods targeting different assemblies

  1. I created a second patch DLL that patches a different second assembly.
  2. I added that DLL in the GUI for a total of two patch DLLs.
  3. When I execute a Test Run, patching completes successfully for the first DLL, but when patching tries to start for the second, the following exception occurs.

If I remove one of the two DLLs, patching completes successfully.

Exception details

System.ArgumentOutOfRangeException: Value of '4' is not valid for 'Value'. 'Value' should be between 'minimum' and 'maximum'.
Parameter name: Value
   at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
   at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
   at System.Windows.Forms.Control.Invoke(Delegate method)
   at PatchworkLauncher.GuiBindings.<>c__DisplayClass0_0`2.<Bind>b__0(Action act)
   at Patchwork.Utility.Binding.DispatchingBindable`1.set_Value(T value)
   at Patchwork.Utility.Binding.Binding`1.OnChanged(IBindable`1 changedBinding)
   at System.Action`1.Invoke(T obj)
   at Patchwork.Utility.Binding.BindableBase`1.NotifyChange()
   at Patchwork.Utility.Binding.VariableBindable`1.set_Value(T value)
   at PatchworkLauncher.LaunchManager.ApplyInstructions(IEnumerable`1 patchGroups, ProgressObject po)
   at PatchworkLauncher.LaunchManager.<>c__DisplayClass46_1.<Command_Patch>b__0()
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at PatchworkLauncher.LaunchManager.<Command_Patch>d__46.MoveNext()

Screenshot

2018-11-28 17_54_08

How do you modify named constructors?

How do you modify named constructors with and without parameters? For example:

public class StatsDistribution {
	public StatsDistribution() {
		this.Points = 0;
		// snip
	}
}

Or:

public class LevelUpState {
	public LevelUpState(UnitDescriptor unit, bool isPregen) {
		this.m_Unit = unit;
		// snip
	}
}

The objective is to be able to use this in new and modified members.

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.