I have two assemblies called game and core. The game assembly I manipulate with Mono.Cecil: I search for specific classes with specifically marked properties in whose setter I inject a method call to a public method located in the class's parent.

This works perfectly fine when the parent class NetworkNode is located in the mainModule (game assembly), but fails when it is outside of it (core assembly): When invoking the setter of the property I get a MissingMethodException:

ERROR: System.MissingMethodException: Method not found: 'Void core.Networking.NetworkNode.LocalNetworkVariableChange(System.Object, System.String)'.
   at game.TestNetworkNode.set_MoveData(String value)
   ...

When stopping in the setter, right before the injected call to the method, I can call the method by myself via IDE without any issue. Because of this and because I have a fully constructed object, I assume that the parent class hosting the method is fully available and loaded at runtime.

CODE:

The method of the parent class (NetworkNode) I inject a call of into the child class's property's setter:

public class NetworkNode : Node {
  public void LocalNetworkVariableChange(object value, string variableName) {
    ...
  }
}

The property of the child class (TestNetworkNode):

public partial class TestNetworkNode : NetworkNode {
  private string _myString = "initial";

  [NetworkVariable(
    Interval = 1,
    Authority = Authority.Client,
    SyncOwnerOnly = true,
  SyncOnChange = false)]
  public string MoveData {
    get {
      Debug.Print("MoveData get");
      return _myString;
    }
    set {
      Debug.Print("MoveData set");
      _myString = value;
    }
  }
}

The IL code I inject:

private static List<Instruction> 
CreateNetworkVariableChangedMethodCallInstructions(
  ModuleDefinition mainModule,
  MethodDefinition propertySetMethod,
  string name) {
  return new List<Instruction> {
    // Load this on the evaluation step
    Instruction.Create(OpCodes.Ldarg_0),

    // Finds the value of a field in the object whose reference is currently on the evaluation stack.
    Instruction.Create(OpCodes.Ldarg_1),

    // Box the value
    Instruction.Create(OpCodes.Box, mainModule.ImportReference(propertySetMethod.Parameters[0].ParameterType)),

    // Load to the stack the index of network variable
    Instruction.Create(OpCodes.Ldstr, name),

    // Call the method (also checked CallVirt without success)
    Instruction.Create(OpCodes.Call, GetOnLocalChangeMethodDefinition(mainModule))
  };
}

private static MethodReference GetOnLocalChangeMethodDefinition(ModuleDefinition mainModule) {
  var networkNodeTypeDefinition = mainModule.Types.FirstOrDefault(IsNetworkNode);

  if (networkNodeTypeDefinition == null) {
    throw new InvalidOperationException(
      $"No NetworkNode implementation found in module {mainModule}");
  }

  var onLocalChangeMethodDefinition = GetMethods(networkNodeTypeDefinition)
    .Single(md => md.Name == "LocalNetworkVariableChange");
  return mainModule.ImportReference(onLocalChangeMethodDefinition);
}

private static IEnumerable<MethodDefinition> GetMethods(TypeDefinition type) {
  var methods = type.Methods.AsEnumerable();
  if (type.BaseType != null) {
    methods = methods.Concat(GetMethods(type.BaseType.Resolve()));
  }
  return methods;
}

Any idea?

0

There are 0 answers