You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If a method contains lambdas, compiler will generate a lambda method in <>c.<[methodName]>b__[methodOrdinal]_[lambdaOrdinal], and a cache field in <>c.<>9__[methodOrdinal]_[lambdaOrdinal]. Since the name of the cache field is only based on ordinals, if patch_[className] has the same cache field name as the original class, lambdas may not work as expected after patching.
MonoMod version: 21.1.11.1
Example
The entire project can be downloaded from here, including source code and output files.
Suppose we have ConflictTest/ConflictTest.cs, which is the original class:
=== Test1() ===
Test1
=== Test2() ===
Test2
=== Test3() ===
Test1
=== Test4() ===
Unhandled Exception: System.MissingFieldException: Field not found: '<>c.<>9__1_0'.
at ConflictTest.ConflictTest.Test4()
at ConflictTest.ConflictTest.Main(String[] args)
Test3() should outputs patched Test3 but it outputs Test1, and Test4() should outputs patched Test4 but the program crashes.
If we put the code of original class into SharpLab (link), we can find that the lambda in Test1() became <>c.<Test1>b__0_0(), and in Test1(), the code first check for the cache field <>c.<>9__0_0, if it is null, then assign <>c.<Test1>b__0_0() to it and execute it.
If we put patch_ConflictTest into SharpLab (link), we can find that the cache field of the lambda in Test3() is also <>c.<>9__0_0, so these two fields conflict and when running the patched program, the cache field is assigned to the method that outputs Test1 when executing Test1(), and when executing Test3(), the cached field is used so the output is same as Test1(). As for Test2() and Test4(), the type of cache field is Func<int> in the original class and Action in patch and patched class, so I guess the casting can't be done and it crashes.
Putting placeholder members before the actual patch methods can solve this because the ordinal of them increase and no longer conflict with original ones.
*** ConflictTest.Mod.mm/ConflictTest.cs@@ -4,8 +4,12 @@
namespace ConflictTest {
internal class patch_ConflictTest {
+ public string PlaceHolder1;+ public string PlaceHolder2;+ public string PlaceHolder3;+ public string PlaceHolder4;
[MonoModReplace]
private static void Test3() {
Action func = () => Console.WriteLine("patched Test3");
func();
}
Description
If a method contains lambdas, compiler will generate a lambda method in
<>c.<[methodName]>b__[methodOrdinal]_[lambdaOrdinal]
, and a cache field in<>c.<>9__[methodOrdinal]_[lambdaOrdinal]
. Since the name of the cache field is only based on ordinals, ifpatch_[className]
has the same cache field name as the original class, lambdas may not work as expected after patching.MonoMod version: 21.1.11.1
Example
The entire project can be downloaded from here, including source code and output files.
Suppose we have
ConflictTest/ConflictTest.cs
, which is the original class:and
ConflictTest.Mod.mm/ConflictTest.cs
, which is the patch that we are going to apply:After patching, the program is supposed to output:
But actually it outputs:
Test3()
should outputspatched Test3
but it outputsTest1
, andTest4()
should outputspatched Test4
but the program crashes.If we put the code of original class into SharpLab (link), we can find that the lambda in
Test1()
became<>c.<Test1>b__0_0()
, and inTest1()
, the code first check for the cache field<>c.<>9__0_0
, if it is null, then assign<>c.<Test1>b__0_0()
to it and execute it.If we put
patch_ConflictTest
into SharpLab (link), we can find that the cache field of the lambda inTest3()
is also<>c.<>9__0_0
, so these two fields conflict and when running the patched program, the cache field is assigned to the method that outputsTest1
when executingTest1()
, and when executingTest3()
, the cached field is used so the output is same asTest1()
. As forTest2()
andTest4()
, the type of cache field isFunc<int>
in the original class andAction
in patch and patched class, so I guess the casting can't be done and it crashes.Putting placeholder members before the actual patch methods can solve this because the ordinal of them increase and no longer conflict with original ones.
Reference: The pattern of compiler generated names: GeneratedNames.cs and GeneratedNameKind.cs
The text was updated successfully, but these errors were encountered: