[LIB] Harmony v1.2.0.1

Started by Brrainz, January 13, 2017, 04:59:21 PM

Previous topic - Next topic

LWM

RimWorld.Pawn_RelationsTracker's SecondaryLovinChanceFactor(Pawn otherPawn) does not have a "Pawn pawn".  To access the object's private Pawn pawn; field, use the ___ notation (it lets you access any private field - note that it's 3 _s):


static void Postfix(Pawn otherPawn, Pawn ___pawn, ref float __result)
        {

DaStormDragon

I get a bunch of 'CS0103 The name 'pawn' does not exist in the current context' errors doing that.

LWM

That's because 'pawn' doesn't exist in your Postfix, but '___pawn' does!  It will automagically be set to whatever 'pawn' is in the object, and if it's ref, it might even effect the pawn variable? But don't quote me on that last one.

So, change your variable names to ___pawn.

DaStormDragon



exotico

Hi, how would one go about setting an instruction's operand? I'm trying to write a transpiler that changes the operands of certain instructions, but I'm not sure how to actually go about doing that. I've tried setting them to a string, but that ends in a crash. Also, trying to get the operand as a string using "codes.operand as String" returns null for some reason.

LWM

I think you can just check  "as string" and it will work if it's actually a string?  What is the context of what you're doing?  A lot of the operands are...difficult for mortals to use (for example, to get the operand from a Ld_loc 13, you'd have to cast it to a ...LocalBuilder? And then get the .index?)  Following someone else's example is often very helpful.

exotico

The context is that it's calling a method, like so: call      bool [UnityEngine]UnityEngine.Input::GetKeyDown(valuetype [UnityEngine]UnityEngine.KeyCode) I'm attempting to follow the example on the Harmony wiki, which checks the value of an instruction's operand using code[i].operand as String Unfortunately, that returns null for me. I tried doing code[i].operand = "method I want to call"; but that caused the game to crash.

LWM

Ah.  That's because the operand isn't a string. It's a method.

I don't know why it's returning null for you, but I've never tried that.  I have checked ((MethodInfo)code[i+9].operand).Name=="TryStartMarriageCeremony (with appropriate checks, that the operation is a Call, that there are 9 more instructions, etc).

As far as calling a method goes, usually it's something like

  yield return new CodeInstruction(OpCodes.Call,  // or the virtual call if appropriate
                    HarmonyLib.AccessTools.Method(typeof(WhateverClass), "NameOfMethod"));
    //       also ....Method("WhateverClass:NameOfMethod"));


If you want to look at Transpilers in the wild, there are a lot of mods that use them.  I try and document what I'm doing in mine as well as I can (so I can use them as a reference, and so I can remember what I was doin).  Transpilers are hard, but really powerful.

What's the link of the example you're looking at?

exotico

Okay, I've managed to figure out something that works for finding and replacing methods in operands:

static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            var codes = new List<CodeInstruction>(instructions);

            for (var i = 0; i < codes.Count; i++)
            {
                if (codes[i].operand == typeof(UnityEngine.Input).GetMethod("GetKeyDown", new[] { typeof(KeyCode) }))
                {
                    codes[i].operand = typeof(UnityEngine.Input).GetMethod("GetKey", new[] { typeof(KeyCode) });
                }
            }

            return codes.AsEnumerable();
        }

Now I have a different question, though: how do I set an operand to something that's not a method? I have an instruction whose operand is a float, but trying to set it to a different float causes a NullReferenceException. How do I get around this?

LWM


exotico

This is what I have (it throws a NullReferenceException):

if (float.Parse(codes[i].operand.ToString()) == -10)
{
     codes[i].operand = -5;

     UnityEngine.Debug.Log("Index " + i + ": " + codes[i].operand.ToString());
}
else if (float.Parse(codes[i].operand.ToString()) == 10)
{
     codes[i].operand = 5;

     UnityEngine.Debug.Log("Index " + i + ": " + codes[i].operand.ToString());
}

LWM

Try starting with

if (codes[i].operand!=null &&....)

If the operand is null, then Parse() will throw that exception.

Kiame

Also use TryParse so no exception is thrown if the operant is not a number

LWM

The usual way I see testing in the Transpiler used is to first check the opcode - if it's "Ret" you know it's not going to have an operand.  If it's "Call" you know that operand isn't going to be a number.  Another thing to keep in mind.