Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MonoModder support multi mods patch same method #208

Open
wants to merge 5 commits into
base: reorganize
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Support multi mods patch same method.
  • Loading branch information
MikiraSora committed Dec 30, 2024
commit 3d7690be91e1c8524cc858701a80cb4d79199521
60 changes: 58 additions & 2 deletions src/MonoMod.Patcher/MonoModder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public class MonoModder : IDisposable
public bool RemovePatchReferences;
public bool PreventInline;
public bool? UpgradeMSCORLIB;
public bool CombineSameMethodMultiModPatches;

public ReadingMode ReadingMode = ReadingMode.Immediate;
public DebugSymbolFormat DebugSymbolOutputFormat = DebugSymbolFormat.Auto;
Expand Down Expand Up @@ -1671,7 +1672,8 @@ public virtual MethodDefinition PatchMethod(TypeDefinition targetType, MethodDef

MethodDefinition existingMethod = targetType.FindMethod(method.GetID(type: typeName));
MethodDefinition origMethod = targetType.FindMethod(method.GetID(type: typeName, name: method.GetOriginalName()));

bool alreadyPatched = origMethod != null;

if (method.HasCustomAttribute("MonoMod.MonoModIgnore"))
{
// MonoModIgnore is a special case, as registered custom attributes should still be applied.
Expand Down Expand Up @@ -1739,6 +1741,61 @@ public virtual MethodDefinition PatchMethod(TypeDefinition targetType, MethodDef

if (existingMethod != null)
{
//check if existingMethod also patched by other mods
MethodDefinition modOrigMethod = method.DeclaringType.FindMethod(method.GetID(type: typeName, name: method.GetOriginalName()));
if (CombineSameMethodMultiModPatches && modOrigMethod != null && alreadyPatched)
{
string PickPatchedMethodNewName(int idx = 0)
{
var name = $"patched_{(idx>0?($"{idx}_"):string.Empty)}{existingMethod.Name}";
if (targetType.Methods.Any(x=>x.Name == name))
return PickPatchedMethodNewName(idx + 1);
return name;
}
var patchedOrigMethod = new MethodDefinition(PickPatchedMethodNewName(), existingMethod.Attributes, Module.TypeSystem.Void);
patchedOrigMethod.MetadataToken = GetMetadataToken(TokenType.Method);
patchedOrigMethod.CallingConvention = existingMethod.CallingConvention;
patchedOrigMethod.ExplicitThis = existingMethod.ExplicitThis;
patchedOrigMethod.MethodReturnType = existingMethod.MethodReturnType;
patchedOrigMethod.Attributes = existingMethod.Attributes;
patchedOrigMethod.ImplAttributes = existingMethod.ImplAttributes;
patchedOrigMethod.SemanticsAttributes = existingMethod.SemanticsAttributes;
patchedOrigMethod.DeclaringType = targetType;
patchedOrigMethod.ReturnType = existingMethod.ReturnType;
//methodbody has been patched and debug information was broken and unavaliable.
//todo: maybe we could rebuild dbg info by something tools?
patchedOrigMethod.Body = existingMethod.Body.Clone(patchedOrigMethod, resolveDebugInformation:false);
patchedOrigMethod.PInvokeInfo = existingMethod.PInvokeInfo;
patchedOrigMethod.IsPInvokeImpl = existingMethod.IsPInvokeImpl;

foreach (GenericParameter genParam in existingMethod.GenericParameters)
patchedOrigMethod.GenericParameters.Add(genParam.Clone());

foreach (ParameterDefinition param in existingMethod.Parameters)
patchedOrigMethod.Parameters.Add(param.Clone());

foreach (CustomAttribute attrib in existingMethod.CustomAttributes)
patchedOrigMethod.CustomAttributes.Add(attrib.Clone());

foreach (MethodReference @override in existingMethod.Overrides)
patchedOrigMethod.Overrides.Add(@override);

patchedOrigMethod.CustomAttributes.Add(new CustomAttribute(GetMonoModAddedCtor()));

//replace call orig_() and redirect to patched__()
foreach (var inst in method.Body.Instructions)
{
if (inst.OpCode == OpCodes.Call && inst.Operand == modOrigMethod)
{
//redirect to patched__()
inst.Operand = patchedOrigMethod;
}
}

targetType.Methods.Add(patchedOrigMethod);
LogVerbose($"[PatchMethod] more mods patched method {existingMethod.GetID(simple:true)}(), old patched implement move to {patchedOrigMethod.GetID(simple:true)}()");
}

existingMethod.Body = method.Body.Clone(existingMethod);
existingMethod.IsManaged = method.IsManaged;
existingMethod.IsIL = method.IsIL;
Expand All @@ -1752,7 +1809,6 @@ public virtual MethodDefinition PatchMethod(TypeDefinition targetType, MethodDef
existingMethod.CustomAttributes.Add(attrib.Clone());

method = existingMethod;

}
else
{
Expand Down
76 changes: 40 additions & 36 deletions src/MonoMod.Utils/Extensions.Relinker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ public static partial class Extensions
/// </summary>
/// <param name="bo">The original method body.</param>
/// <param name="m">The method which will own the newly cloned method body.</param>
/// <param name="resolveDebugInformation">If we need to resolve debug infomation, false for patched methodbody</param>
/// <returns>A clone of the original method body.</returns>
[return: NotNullIfNotNull("bo")]
public static MethodBody? Clone(this MethodBody? bo, MethodDefinition m)
public static MethodBody? Clone(this MethodBody? bo, MethodDefinition m, bool resolveDebugInformation = true)
{
Helpers.ThrowIfArgumentNull(m);

Expand Down Expand Up @@ -120,46 +121,49 @@ public static partial class Extensions
return c;
}));

Instruction ResolveInstrOff(int off)
if (resolveDebugInformation)
{
// Can't check cloned instruction offsets directly, as those can change for some reason
for (var i = 0; i < bo.Instructions.Count; i++)
if (bo.Instructions[i].Offset == off)
return bc.Instructions[i];
throw new ArgumentException($"Invalid instruction offset {off}");
}

m.CustomDebugInformations.AddRange(bo.Method.CustomDebugInformations.Select(o =>
{
if (o is AsyncMethodBodyDebugInformation ao)
Instruction ResolveInstrOff(int off)
{
var c = new AsyncMethodBodyDebugInformation();
if (ao.CatchHandler.Offset >= 0)
c.CatchHandler = ao.CatchHandler.IsEndOfMethod ? new InstructionOffset() : new InstructionOffset(ResolveInstrOff(ao.CatchHandler.Offset));
c.Yields.AddRange(ao.Yields.Select(off => off.IsEndOfMethod ? new InstructionOffset() : new InstructionOffset(ResolveInstrOff(off.Offset))));
c.Resumes.AddRange(ao.Resumes.Select(off => off.IsEndOfMethod ? new InstructionOffset() : new InstructionOffset(ResolveInstrOff(off.Offset))));
c.ResumeMethods.AddRange(ao.ResumeMethods);
return c;
// Can't check cloned instruction offsets directly, as those can change for some reason
for (var i = 0; i < bo.Instructions.Count; i++)
if (bo.Instructions[i].Offset == off)
return bc.Instructions[i];
throw new ArgumentException($"Invalid instruction offset {off}");
}
else if (o is StateMachineScopeDebugInformation so)

m.CustomDebugInformations.AddRange(bo.Method.CustomDebugInformations.Select(o =>
{
var c = new StateMachineScopeDebugInformation();
c.Scopes.AddRange(so.Scopes.Select(s => new StateMachineScope(ResolveInstrOff(s.Start.Offset), s.End.IsEndOfMethod ? null : ResolveInstrOff(s.End.Offset))));
return c;
}
else
return o;
}));
if (o is AsyncMethodBodyDebugInformation ao)
{
var c = new AsyncMethodBodyDebugInformation();
if (ao.CatchHandler.Offset >= 0)
c.CatchHandler = ao.CatchHandler.IsEndOfMethod ? new InstructionOffset() : new InstructionOffset(ResolveInstrOff(ao.CatchHandler.Offset));
c.Yields.AddRange(ao.Yields.Select(off => off.IsEndOfMethod ? new InstructionOffset() : new InstructionOffset(ResolveInstrOff(off.Offset))));
c.Resumes.AddRange(ao.Resumes.Select(off => off.IsEndOfMethod ? new InstructionOffset() : new InstructionOffset(ResolveInstrOff(off.Offset))));
c.ResumeMethods.AddRange(ao.ResumeMethods);
return c;
}
else if (o is StateMachineScopeDebugInformation so)
{
var c = new StateMachineScopeDebugInformation();
c.Scopes.AddRange(so.Scopes.Select(s => new StateMachineScope(ResolveInstrOff(s.Start.Offset), s.End.IsEndOfMethod ? null : ResolveInstrOff(s.End.Offset))));
return c;
}
else
return o;
}));

m.DebugInformation.SequencePoints.AddRange(bo.Method.DebugInformation.SequencePoints.Select(o =>
{
var c = new SequencePoint(ResolveInstrOff(o.Offset), o.Document);
c.StartLine = o.StartLine;
c.StartColumn = o.StartColumn;
c.EndLine = o.EndLine;
c.EndColumn = o.EndColumn;
return c;
}));
m.DebugInformation.SequencePoints.AddRange(bo.Method.DebugInformation.SequencePoints.Select(o =>
{
var c = new SequencePoint(ResolveInstrOff(o.Offset), o.Document);
c.StartLine = o.StartLine;
c.StartColumn = o.StartColumn;
c.EndLine = o.EndLine;
c.EndColumn = o.EndColumn;
return c;
}));
}

foreach (var c in bc.Instructions)
{
Expand Down