pardeike / Harmony

A library for patching, replacing and decorating .NET and Mono methods during runtime
https://www.patreon.com/pardeike
MIT License
5.15k stars 485 forks source link

FormatException: Method cannot be patched. Reason: Invalid IL code #105

Closed dymanoid closed 6 years ago

dymanoid commented 6 years ago

I have a very strange issue. Using Harmony 1.1.0. This is again a patch for the Cities:Skylines game.

I have an x64 assembly that perfectly works on my system: Windows 10 64bit. No issues, the patching works very well. A Steam user reports an exception with the same assembly on his machine: Windows 10 64bit, same game version, same binary of my assembly.

The exception is:

System.FormatException: Method WorldInfoPanel.UpdateBindings() cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) WorldInfoPanel:UpdateBindings_Patch1 (object): IL_007f: stfld     0x0000001f

  at Harmony.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, Harmony.PatchInfo patchInfo, System.String instanceID)
  at Harmony.PatchProcessor.Patch ()
  at Harmony.HarmonyInstance.Patch (System.Reflection.MethodBase original, Harmony.HarmonyMethod prefix, Harmony.HarmonyMethod postfix, Harmony.HarmonyMethod transpiler) 

Again, I cannot reproduce this on my machine. The game recompiles the assemblies with Mono, so I thought the user has a Mac or a Linux version and that causes some binary incompatibility. But no - Windows 10 64bit, exactly like mine. There are hundreds of other users having no issues too.

What can we do to diagnose this issue and find a solution?

Maybe this is somewhat similar to #38, but the exception is FormatException in my case.

pardeike commented 6 years ago

Could easily be another mod that user has installed, also changing this method and leading to incompatibilities.

dymanoid commented 6 years ago

Do you mean a "direct" patch?

pardeike commented 6 years ago

I think the IL code of that message could be changed by another mods transpiler before or after you change it. Have you tried to turn on the debug log of Harmony to see the result of your transpiler and to verify if it produces correct IL code?

dymanoid commented 6 years ago

Now having the same exception with another game method:

System.FormatException: Method WeatherManager.SimulationStepImpl(System.Int32) cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) WeatherManager:SimulationStepImpl_Patch1 (object,int): IL_0025: stfld     0x00000009

I'm not using any transpilers in my patches, just plain prefixes and postfixes. Does a debug log help in this case? On my machine, there are no exceptions. That's why it makes it so difficult to diagnose. The user who experience this is not a dev, so it's hard to explain what he needs to do.

pardeike commented 6 years ago

Yes. Use HarmonyInstance.DEBUG = true before the patch assignment an it will give you a log file on your desktop. Post the part that shows the IL of your patched original method. Thanks

dymanoid commented 6 years ago

So I was able to get the Harmony log from the user who experiences this issue. I also switched the debug log on on my machine to compare them.

First of all, all methods appear twice in the log. I suppose, the first time after patching and the second time after reverting the patch. Is this correct?

Then, I compared the logs from the user and mine. The second appearances of the method in both logs are exactly the same. (After reverting the patch?)

Here is the beginning of the method:

### Patch WeatherManager, Void SimulationStepImpl(Int32)
L_0000: Local var 0: SimulationManager
L_0000: Local var 1: System.Single
L_0000: Local var 2: System.Single
L_0000: Local var 3: ItemClass+Availability
L_0000: Local var 4: System.Single
L_0000: Local var 5: System.Int32
L_0000: Local var 6: System.Single
L_0000: Local var 7: System.Int32
L_0000: Local var 8: System.Int32
L_0000: Local var 9: System.Single
L_0000: Local var 10: System.Single
L_0000: Local var 11: System.Single
L_0000: Local var 12: System.Int32
L_0000: Local var 13: System.Int32
L_0000: Local var 14: System.Single
L_0000: Local var 15: System.Single
L_0000: Local var 16: System.Single
L_0000: Local var 17: System.Int32
L_0000: Local var 18: System.UInt32
L_0000: Local var 19: InstanceID
L_0000: Local var 20: System.Single
L_0000: Local var 21: System.Int32
L_0000: ldarg.1
L_0001: brfalse Label1
L_0006: ldarg.1
L_0007: ldc.i4 1000
L_000c: beq Label2
L_0011: call SimulationManager get_instance()
L_0016: stloc.0
L_0017: ldarg.0
L_0018: ldfld System.Single m_targetDirection
L_001d: ldarg.0
L_001e: ldfld System.Single m_windDirection
L_0023: call Single DeltaAngle(Single, Single)

So it looks like the original code is the same both on my machine and on the user's one.

But the first appearances of the method in the logs differ! Here is my log (WORKING):

### Patch WeatherManager, Void SimulationStepImpl(Int32)
L_0000: Local var 0: SimulationManager
L_0000: Local var 1: System.Single
L_0000: Local var 2: System.Single
L_0000: Local var 3: ItemClass+Availability
L_0000: Local var 4: System.Single
L_0000: Local var 5: System.Int32
L_0000: Local var 6: System.Single
L_0000: Local var 7: System.Int32
L_0000: Local var 8: System.Int32
L_0000: Local var 9: System.Single
L_0000: Local var 10: System.Single
L_0000: Local var 11: System.Single
L_0000: Local var 12: System.Int32
L_0000: Local var 13: System.Int32
L_0000: Local var 14: System.Single
L_0000: Local var 15: System.Single
L_0000: Local var 16: System.Single
L_0000: Local var 17: System.Int32
L_0000: Local var 18: System.UInt32
L_0000: Local var 19: InstanceID
L_0000: Local var 20: System.Single
L_0000: Local var 21: System.Int32
L_0000: ldarg.0
L_0001: ldflda System.Single m_temperatureSpeed
L_0006: ldarg.0
L_0007: ldfld System.Single m_targetTemperature
L_000c: ldarg.0
L_000d: ldfld System.Single m_currentTemperature
L_0012: call Void Prefix(Single ByRef, Single, Single)
L_0017: ldarg.1
L_0018: brfalse Label1
L_001d: ldarg.1
L_001e: ldc.i4 1000
L_0023: beq Label2
L_0028: call SimulationManager get_instance()
L_002d: stloc.0
L_002e: ldarg.0
L_002f: ldfld System.Single m_targetDirection
L_0034: ldarg.0
L_0035: ldfld System.Single m_windDirection
L_003a: call Single DeltaAngle(Single, Single)

Here is the user's log (NOT WORKING)

FormatException: Invalid IL code: IL_0025: stfld     0x00000009
### Patch WeatherManager, Void SimulationStepImpl(Int32)
L_0000: Local var 0: SimulationManager
L_0000: Local var 1: System.Single
L_0000: Local var 2: System.Single
L_0000: Local var 3: ItemClass+Availability
L_0000: Local var 4: System.Single
L_0000: Local var 5: System.Int32
L_0000: Local var 6: System.Single
L_0000: Local var 7: System.Int32
L_0000: Local var 8: System.Int32
L_0000: Local var 9: System.Single
L_0000: Local var 10: System.Single
L_0000: Local var 11: System.Single
L_0000: Local var 12: System.Int32
L_0000: Local var 13: System.Int32
L_0000: Local var 14: System.Single
L_0000: Local var 15: System.Single
L_0000: Local var 16: System.Single
L_0000: Local var 17: System.Int32
L_0000: Local var 18: System.UInt32
L_0000: Local var 19: InstanceID
L_0000: Local var 20: System.Single
L_0000: Local var 21: System.Int32
L_0000: Local var 22: System.Single
L_0000: ldc.r4 0
L_0005: stloc 22 (System.Single)
L_0007: ldarg.0
L_0008: ldfld System.Single m_temperatureSpeed
L_000d: stloc 22 (System.Single)
L_000f: ldloca 22 (System.Single)
L_0011: ldarg.0
L_0012: ldfld System.Single m_targetTemperature
L_0017: ldarg.0
L_0018: ldfld System.Single m_currentTemperature
L_001d: call Void Prefix(Single ByRef, Single, Single)
L_0022: ldloc 22 (System.Single)
L_0024: ldarg.0
L_0025: stfld System.Single m_temperatureSpeed
L_002a: ldarg.1
L_002b: brfalse Label1
L_0030: ldarg.1
L_0031: ldc.i4 1000
L_0036: beq Label2
L_003b: call SimulationManager get_instance()
L_0040: stloc.0
L_0041: ldarg.0
L_0042: ldfld System.Single m_targetDirection
L_0047: ldarg.0
L_0048: ldfld System.Single m_windDirection
L_004d: call Single DeltaAngle(Single, Single)

Note that IL code at the beginning of the method differs! There is an additional local variable that is missing in my log, and e.g. some stloc 22 (System.Single) statements.

This local variable (22) is never used in the method again, the last usage is at L_0022.

And for your reference, here is the prefix method signature:

static void Prefix(
    ref float ___m_temperatureSpeed,
    float ___m_targetTemperature,
    float ___m_currentTemperature)
dymanoid commented 6 years ago

And another comment: the user reports that sometimes it works.

In the game's log file, I see that there are multiple mods that load different Harmony assemblies: 0Harmony, Version=1.0.9.1 0Harmony, Version=1.1.0.0 (my mod) 0Harmony, Version=1.1.1.0

Could the invalid IL be caused by some conflict of which Harmony version "wins"?

pardeike commented 6 years ago

Please test again with the latest HEAD. I made a few changes and bug fixes regarding loading and unloading of mods and patches. Specifically, transpilers were not correctly removed.

dymanoid commented 6 years ago

Unfortunately I cannot test it actually. On my dev machine, I cannot reproduce the exceptions. But I'm not willing to deploy a not-yet-released library to 15,000+ players just to see if something became better. I have to wait for the next official Harmony release.

dymanoid commented 6 years ago

Okay, the 1.2.0.1 is out. Despite it has a bug that potentially might lead to a crash due to a MissingFieldException (see #115), I will roll out this version and let the players test whether the `FormatException´ described in the original post is fixed.

pardeike commented 6 years ago

Can you explain where the MissingFieldException is coming from?

dymanoid commented 6 years ago

Okay, I didn't check the Traverse implementation, my bad. There will be no MissingFieldException. However, due to a potential hash collision, the Harmony code might get a wrong version field value (0). I cannot say what happens then, but it's a thing I'm not expecting in a library that messes with the IL code on such a low level.

dymanoid commented 6 years ago

This issue seems to be fixed. No more user complaints with version 1.2.0.1.