I use Mono.Cecil to inject two method calls into a constructor. Somehow the construction always fails with the following error:
InvalidProgramException: Invalid IL code in Networking.ServerController:.ctor (): IL_0028: callvirt 0x0600017f
What I basically do is to inject a method call to the following method (from two base-classes above) for every stfld while passing the name of the method (without class) and the current field value (boxed) as object:
protected void LocalNetworkVariableChange(string variableName, object value) {
// Fails no matter what i put here
}
The IL code before injection:
IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_001d: ret
After Injection:
IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByClientId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByPlayerId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_001d: ret
(!) The labels in front of the instructions are updated lazy when writing out the instructions to file via Mono.Cecil. This is why the seem to have a wrong offset and map all to IL_0000.
The final IL code looks like this:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 62 (0x3e)
.maxstack 8
IL_0000: ldarg.0
IL_0001: newobj instance void [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj instance void [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call instance void class Networking.PeerController`1<class Networking.ServerController>::.ctor()
IL_001c: nop
IL_001d: ldarg.0
IL_001e: ldstr "_connectedPlayersByClientId"
IL_0023: ldfld [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_0028: callvirt instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
object)
IL_002d: ldarg.0
IL_002e: ldstr "_connectedPlayersByPlayerId"
IL_0033: ldfld [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0038: callvirt instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
object)
IL_003d: ret
} // end of method ServerController::.ctor
The two fields look like this:
[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<ushort, ConnectedPlayer> _connectedPlayersByClientId = new
Dictionary<ushort, ConnectedPlayer>();
[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<NuID, ConnectedPlayer> _connectedPlayersByPlayerId = new
Dictionary<NuID, ConnectedPlayer>();
Any idea?
The first problem is that
box System.Collections.Generic.Dictionary.... You can't box a reference type.The second problem is the
ldfld. From the docs:In other words,
ldfldneeds to know the object to read the field from, and it consumes that object parameter from the stack.So you need: