Closed boformer closed 2 years ago
After looking a bit more, I found out what causes the issue:
StructReturnBuffer.NeedsFix
returns true
, but it should return false
for a method that returns UnityEngine.Color
!
I've hacked the method to return false for type UnityEngine.Color
by adding 16
to the specialSizes
field:
var specialSizesField = typeof(Harmony).Assembly.GetType("HarmonyLib.StructReturnBuffer").GetField("specialSizes", BindingFlags.Static | BindingFlags.NonPublic);
var specialSizes = (HashSet<int>)specialSizesField.GetValue(null);
specialSizes.Add(16);
When applying the patch after this, the game no longer crashes and all buildings become red as expected.
I don't know why no struct return buffer is used in this particular case though...
Then why does this test (or its static variant) not fail: https://github.com/pardeike/Harmony/blob/3e5c707020e7ff66cb539e9f2c6aee17c35a18bc/HarmonyTests/Patching/Assets/ReturningStructMethods.cs#L106
@boformer I use harmony 2.0.0.9 to patch CommonBuildingAI.GetColor. it seems work well.
https://github.com/pcfantasy/RealCity/blob/master/Patch/CommonBuildingAIGetColorPatch.cs
@pardeike Idk. Maybe that Unity version uses an outdated and custom version of mono that does some special optimizations for Color structs? or maybe it has something to do with virtual/override methods?
@pcfantasy why does it say using Harmony;
then?
@pardeike I did some further tests with interesting results. In Cities: Skylines environment, it seems that only static methods need the struct return buffer fix, instance methods are not "optimized" in that way. This affects all methods where sizeof(returnType) == 16
. I didn't test any other sizes.
var harmony = new Harmony("boformer.Harmony2Example");
harmony.Patch(typeof(MyClass).GetMethod("GetSt16Static"), null, new HarmonyMethod(typeof(Patches).GetMethod("GetSt16_Postfix")));
harmony.Patch(typeof(MyClass).GetMethod("GetColorStatic"), null, new HarmonyMethod(typeof(Patches).GetMethod("GetColor_Postfix")));
harmony.Patch(typeof(MyClass).GetMethod("GetSt16"), null, new HarmonyMethod(typeof(Patches).GetMethod("GetSt16_Postfix")));
harmony.Patch(typeof(MyClass).GetMethod("GetColor"), null, new HarmonyMethod(typeof(Patches).GetMethod("GetColor_Postfix")));
var st16static = MyClass.GetSt16Static();
UnityEngine.Debug.Log("st16static.b8: " + st16static.b8);
var colorStatic = MyClass.GetColorStatic();
UnityEngine.Debug.Log("colorStatic.g: " + colorStatic.g);
UnityEngine.Debug.Log(colorStatic.ToString());
var myClass = new MyClass();
var st16 = myClass.GetSt16();
UnityEngine.Debug.Log("st16.b8: " + st16.b8);
var color = myClass.GetColor();
UnityEngine.Debug.Log("color.g: " + color.g);
UnityEngine.Debug.Log(color.ToString());
public static class Patches {
public static void GetSt16_Postfix(ref St16 __result) {
UnityEngine.Debug.Log("GetSt16__Postfix __result.b8: " + __result.b8);
}
public static void GetColor_Postfix(ref UnityEngine.Color __result) {
UnityEngine.Debug.Log("GetColor__Postfix: __result.g: " + __result.g);
}
}
public class MyClass {
public static St16 GetSt16Static() {
UnityEngine.Debug.Log("GetSt16Static");
return new St16 { b8 = 42 };
}
public static UnityEngine.Color GetColorStatic() {
UnityEngine.Debug.Log("GetColorStatic");
return new UnityEngine.Color(1f, 0.75f, 0.5f, 0.25f);
}
public St16 GetSt16() {
UnityEngine.Debug.Log("GetSt16");
return new St16 { b8 = 42 };
}
public UnityEngine.Color GetColor() {
UnityEngine.Debug.Log("GetColor");
return new UnityEngine.Color(1f, 0.75f, 0.5f, 0.25f);
}
}
public struct St16 {
public byte b1;
public byte b2;
public byte b3;
public byte b4;
public byte b5;
public byte b6;
public byte b7;
public byte b8;
public byte b9;
public byte b10;
public byte b11;
public byte b12;
public byte b13;
public byte b14;
public byte b15;
public byte b16;
}
Static methods are patched correctly by Harmony 2.0.0.9:
GetSt16Static
GetSt16__Postfix __result.b8: 42
st16static.b8: 42
GetColorStatic
GetColor__Postfix: __result.g: 0.75
colorStatic.g: 0.75
RGBA(1.000, 0.750, 0.500, 0.250)
The instance method results get scrambled by Harmony 2.0.0.9:
GetSt16
GetSt16__Postfix __result.b8: 42
st16.b8: 0
GetColor
GetColor__Postfix: __result.g: 0.75
color.g: 0
RGBA(0.000, 0.000, 0.000, 0.000)
Then why does this test (or its static variant) not fail:
I investigated this as well. I modified Test_Patch_Returning_Structs
a bit so I can run it ingame: All the tests are passing, but only because you are not actually checking the returned values (just if the type matches)
@boformer @pardeike
Sorry,I forgot to switch to my harmony2.0 branch.
Yes, I can confirm that harmony2.0.0.9 can patch unity color now, but function is wrong, the same situation as boformer said.
I wonder if it only affects Cities' mono version or all of them. We could of course build a custom version of Harmony that has the correct behavior.
I wonder if it only affects Cities' mono version or all of them. We could of course build a custom version of Harmony that has the correct behavior.
If you build a local version of Harmony named harmony 2.0.0.10. Other modder use harmony 2.0.0.9. Two mods patch the same method with prefix. Does harmony priority still work for two mods? I mean the highest priority still excute first.
If you build a local version of Harmony named harmony 2.0.0.10. Other modder use harmony 2.0.0.9. Two mods patch the same method with prefix. Does harmony priority still work for two mods? I mean the highest priority still excute first.
My central Harmony mod makes sure that all mods use the one shipped with the mod. (it patches the assembly resolver of the game)
I will probably keep 2.0.0.9 version number
This needs to be verified with master. I will close this issue without more feedback.
Sorry for the inactivity on the issue.
By now the Harmony mod for Cities: Skylines uses a custom Harmony fork because the official Harmony package is no longer compatible with the outdated mono runtime of the game.
If I remember correctly, this issue was fixed by adding an additional condition to StructReturnBufferCheck.NeedsFix
in the fork:
if (AccessTools.IsMonoRuntime && method.IsStatic is false) return false;
I don't really have the time or energy to finish my pull request #280 at this point (I would probably have to redo it from scratch), but if I remember correctly the tests show that the error described in this issue occurs in various mono runtimes, not only in the outdated runtime used by CSL.
Describe the bug I got various kinds of errors while trying to patch a certain method with 2.0.0.9.
The same patch works with 1.2.0.1.
One very suspicious thing is that in the harmony.log IL code of the replacement method, there seem to be 27 local vars defined (index 0 to 26).In the harmony-generated IL after// end original
, there are statements like hat reference local var with index 27 (doesn't exist?????):Edit: I checked again and it seems var with index 27 is just not listed because the list is generated while the original method IL is printed. So I'm pretty clueless what is happening...
I don't really get why you need var 27 when you already have var 25 which stores exactly the same value
To Reproduce Steps to reproduce the behavior:
harmony.log.txt
Pretty random errors, mostly NullRefExceptions that occur outside or within the patched method, or "attempted to access invalid address":
Runtime environment (please complete the following information):