Beginner needs help with porting a mod 1.0 -> B18

Started by bionicjoethehat, June 04, 2019, 11:15:49 AM

Previous topic - Next topic

Mehni

QuoteI now think that changing the whole method is an overkill

And that's exactly what transpilers are for :D. Altering a specific and small part of a method, rather than the entire method.

Your second codeblock is B18. 1.0 already has a loop, you'll want to take that into account.

Write in C# what you envision the end-result should look like or carefully explain it.

bionicjoethehat

#16
1.0 does have a loop indeed, but I'm porting a mod from 1.0 to B18 which doesn't and I have already written an envision in my previous post but to translate it to IL, I would either need to write something in C# use a decompiler which for some reason I can't install right now (but soon will) or to write IL code from scratch which I can't do. :P

So, would you please use your decompiler and post the instructions for:
do
{
this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);
}
while (!RandomSettings.CheckPawnIsSatisfiedMethodInfo);

?

Would you also answer my question regarding referring to other instructions in IL? Like in:
Brfalse ???
What would you write instead of the question marks?

bionicjoethehat

#17
Found an online C# -> IL converter and here is what it showed:

        // loop start (head: IL_0001)
            IL_0001: nop
            IL_0002: ldarg.0
            IL_0003: ldarg.0
            IL_0004: call instance int32 C::B()
            IL_0009: stfld int32 C::curPawn
            IL_000e: nop
            IL_000f: ldarg.0
            IL_0010: call instance bool C::A()
            IL_0015: ldc.i4.0
            IL_0016: ceq
            IL_0018: stloc.0
            // sequence point: hidden
            IL_0019: ldloc.0
            IL_001a: brtrue.s IL_0001


The last thing left to do is to assign a label to an instruction IL_0001 so it can be used by IL_001a but I can't find how you can do it online.

Mehni

You want to turn
// Page_ConfigureStartingPawns
using Verse;

private void RandomizeCurPawn()
{
if (TutorSystem.AllowAction("RandomizePawn"))
{
int num = 0;
do
{
curPawn = StartingPawnUtility.RandomizeInPlace(curPawn);
num++;
}
while (num <= 20 && !StartingPawnUtility.WorkTypeRequirementsSatisfied());
TutorSystem.Notify_Event("RandomizePawn");
}
}


into

// Page_ConfigureStartingPawns
using Verse;

private void RandomizeCurPawn()
{
if (TutorSystem.AllowAction("RandomizePawn"))
{
do
{
curPawn = StartingPawnUtility.RandomizeInPlace(curPawn);
}
while (!RandomSettings.CheckPawnIsSatisfiedMethodInfo);
TutorSystem.Notify_Event("RandomizePawn");
}
}


Those are a few changes too many. Keep the "num". You can elect to change it to a higher value, but removing it is too invasive. Payoff vs effort. Not to mention the side-effects.

Changing !StartingPawnUtility.WorkTypeRequirementsSatisfied() to !RandomSettings.CheckPawnIsSatisfiedMethodInfo is good, but you'll need to keep labels in mind. Those IL_003e: brfalse IL_0017 can't be used easily in a transpiler. You'll need to find those labels manually -- it's much easier to avoid that entirely.

      IL_002f: ble IL_0039

      // (no C# code)
      IL_0034: br IL_0043

      IL_0039: call bool Verse.StartingPawnUtility::WorkTypeRequirementsSatisfied()
      IL_003e: brfalse IL_0017

IL_0039 is what we're after, but there's a jump label to it. We're going to recycle it.

here's some (pseudo) code

foreach codeinstruction in instructions
{
    if (codeinstruction == call bool Verse.StartingPawnUtility::WorkTypeRequirementsSatisfied())
    {
        codeinstruction.operand = null;
        codeinstruction.opcode = OpCodes.Ldarg_0;
        yield return codeinstruction;
        yield return new CodeInstruction(OpCodes.Ldfld, curPawnFieldInfo);
        yield return new CodeInstruction(OpCodes.Call, CheckPawnIsSatisfiedMethodInfo);       
    }
    else
        yield return codeinstruction;
}


Note that this removes StartingPawnUtility.WorkTypeRequirementsSatisfied(). The good news is that it's a static public method, so you can move the call to it into the CheckPawnIsSatisfiedMethodInfo method.

bionicjoethehat

Your first code is taken from 1.0 but I am adapting a 1.0 mod for B18.

Here is the code of RandomizeCurPaw in B18:
private void RandomizeCurPawn()
{
if (!TutorSystem.AllowAction("RandomizePawn"))
{
return;
}
this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);
TutorSystem.Notify_Event("RandomizePawn");
}

As you can see, it doesn't have a loop.

And here is what I currently have:
using Harmony;
using RimWorld;
using Verse;
using UnityEngine;
using System.Collections.Generic;
using System.Reflection.Emit;
using System;
using System.Reflection;

namespace RandomPlus
{
[HarmonyPatch (typeof(Page_ConfigureStartingPawns), "RandomizeCurPawn")]
class Patch_RandomizeMethod
{
static void Prefix ()
{
RandomSettings.ResetRerollCounter ();
}

static IEnumerable<CodeInstruction> Transpiler (IEnumerable<CodeInstruction> instructions)
{
var curPawnFieldInfo = typeof(Page_ConfigureStartingPawns)
.GetField ("curPawn", BindingFlags.NonPublic | BindingFlags.Instance);
var randomizeInPlaceMethodInfo = typeof(StartingPawnUtility)
.GetMethod ("RandomizeInPlace", BindingFlags.Public | BindingFlags.Static);
var checkPawnIsSatisfiedMethodInfo = typeof(RandomSettings)
.GetMethod ("CheckPawnIsSatisfied", BindingFlags.Public | BindingFlags.Static);

var codes = new List<CodeInstruction> (instructions);

var appropriatePlace = 5;

/* Removing following code in its IL form */

// this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);

codes.RemoveRange (appropriatePlace, 5);

/* Adding following code in its IL form: */

// do {
//  this.curPawn = StartingPawnUtility.RandomizeInPlace (this.curPawn);
  // } while (!RandomSettings.CheckPawnIsSatisfiedMethodInfo);

List <CodeInstruction> newCodes = new List<CodeInstruction> {
new CodeInstruction (OpCodes.Nop), // I need to give this instruction a label
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Call, randomizeInPlaceMethodInfo),
new CodeInstruction (OpCodes.Stfld, curPawnFieldInfo),
new CodeInstruction (OpCodes.Nop),
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Call, checkPawnIsSatisfiedMethodInfo),
new CodeInstruction (OpCodes.Ldc_I4_0),
new CodeInstruction (OpCodes.Ceq),
new CodeInstruction (OpCodes.Stloc_0),
new CodeInstruction (OpCodes.Ldloc_0),
new CodeInstruction (OpCodes.Brfalse_S /* and use it here */),
};

codes.InsertRange (appropriatePlace, newCodes);

return codes;
}
}
}

bionicjoethehat

So, I need to figure out how to jump to a certain instruction, but how?

bionicjoethehat

#21
using Harmony;
using RimWorld;
using Verse;
using UnityEngine;
using System.Collections.Generic;
using System.Reflection.Emit;
using System;
using System.Reflection;

namespace RandomPlus
{
[HarmonyPatch (typeof(Page_ConfigureStartingPawns), "RandomizeCurPawn")]
class Patch_RandomizeMethod
{
static void Prefix ()
{
RandomSettings.ResetRerollCounter ();
}

static IEnumerable<CodeInstruction> Transpiler (IEnumerable<CodeInstruction> instructions)
{
var curPawnFieldInfo = typeof(Page_ConfigureStartingPawns)
.GetField ("curPawn", BindingFlags.NonPublic | BindingFlags.Instance);
var randomizeInPlaceMethodInfo = typeof(StartingPawnUtility)
.GetMethod ("RandomizeInPlace", BindingFlags.Public | BindingFlags.Static);
var checkPawnIsSatisfiedMethodInfo = typeof(RandomSettings)
.GetMethod ("CheckPawnIsSatisfied", BindingFlags.Public | BindingFlags.Static);

var codes = new List<CodeInstruction> (instructions);

var appropriatePlace = 6;

/* Removing the following code in its IL form */

// this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);

codes.RemoveRange (appropriatePlace, 5);

/* Adding the following code in its IL form: */

// do {
//  this.curPawn = StartingPawnUtility.RandomizeInPlace (this.curPawn);
// } while (!RandomSettings.CheckPawnIsSatisfiedMethodInfo);

List <CodeInstruction> newCodes = new List<CodeInstruction> {
new CodeInstruction (OpCodes.Nop), // I need to give this instruction a label
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Call, randomizeInPlaceMethodInfo),
new CodeInstruction (OpCodes.Stfld, curPawnFieldInfo),
new CodeInstruction (OpCodes.Nop),
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Call, checkPawnIsSatisfiedMethodInfo),
new CodeInstruction (OpCodes.Ldc_I4_0),
new CodeInstruction (OpCodes.Ceq),
new CodeInstruction (OpCodes.Stloc_0),
new CodeInstruction (OpCodes.Ldloc_0),
};

newCodes [0].labels.Add (new Label ());

var nopLabel = newCodes [0].labels [0];

newCodes.Add (new CodeInstruction (OpCodes.Brfalse_S, nopLabel));

codes.InsertRange (appropriatePlace, newCodes);

// for (var i = 0; i < labels.Count; i++) {
// Log.Message (labels[i].ToString());
// }

// for (var i = 0; i < codes.Count; i++) {
// Log.Message (codes [i].ToString ());
// }

return codes;
}
}
}

Managed to assign a label but there is another error shown:

Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for RandomPlus.HarmonyPatches ---> System.FormatException: Method RimWorld.Page_ConfigureStartingPawns.RandomizeCurPawn() cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) RimWorld.Page_ConfigureStartingPawns:RandomizeCurPawn_Patch1 (object): IL_0036: stloc.0   


  at Harmony.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, Harmony.PatchInfo patchInfo, System.String instanceID) [0x00000] in <filename unknown>:0
  at Harmony.PatchProcessor.Patch () [0x00000] in <filename unknown>:0
  at Harmony.HarmonyInstance.<PatchAll>b__7_0 (System.Type type) [0x00000] in <filename unknown>:0
  at Harmony.CollectionExtensions.Do[Type] (IEnumerable`1 sequence, System.Action`1 action) [0x00000] in <filename unknown>:0
  at Harmony.HarmonyInstance.PatchAll (System.Reflection.Assembly assembly) [0x00000] in <filename unknown>:0
  at RandomPlus.HarmonyPatches..cctor () [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at (wrapper managed-to-native) System.Runtime.CompilerServices.RuntimeHelpers:RunClassConstructor (intptr)
  at System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor (RuntimeTypeHandle type) [0x00000] in <filename unknown>:0
  at Verse.StaticConstructorOnStartupUtility.CallAll () [0x00000] in <filename unknown>:0
  at Verse.PlayDataLoader.<DoPlayLoad>m__2 () [0x00000] in <filename unknown>:0
  at Verse.LongEventHandler.ExecuteToExecuteWhenFinished () [0x00000] in <filename unknown>:0
Verse.Log:Error(String)
Verse.LongEventHandler:ExecuteToExecuteWhenFinished()
Verse.LongEventHandler:UpdateCurrentAsynchronousEvent()
Verse.LongEventHandler:LongEventsUpdate(Boolean&)
Verse.Root:Update()
Verse.Root_Entry:Update()

bionicjoethehat

This is the link to the playground I"m using.

https://sharplab.io/#v2:C4LglgNgNAJiDUAfAAgJgIwFgBQPkGYACNQgYUIG8dCbCBIAwsAO2EIGMBXAJwAUBDAO7MA3NVriaDIgCMA9nIiEAghAhzBydKgAUWgAyFm/ALYBTAJSVJtWzWQB2QsG6czY7HdoBfGzenEACyEAHJywGAAZgCeAPoAogBuZqx66IbG5lZUnl52Nra+uTT+jCxsAEr8zDByJmAAXmYAksy8EPzsZjrlhAAOQszZBbQMTgPCHnmERbalsgpKpAAWZuwA1gLCzQDOAMr8ETuRYGYwOsPFdmPOru4jNLMSV3R93GCJh2ZBhFU1dY0zKQeFtmBccHQ6DlIXQooQdABCYDLMA7AB0qnUmm0OgARH9avUmqDcRYLBDIdCYY4PDCijDatZsDCaMjUWiuHxBoQALzOFHogkApqtdqdb46Nnozmgiy0yEzQiCFEQCVIgVolZrTaDXYHI4nM4XOUUuhStFhCIxBLJVL46qEwEkk3MuhPEovNCoJnTIpFHAAegAVBS3h8vj8hUSgSDBuDXVTYZF4QiACqcYBybhaDFqDRaXT2/7R53k11Q01jeVu03mmXcvl7YD8bgRZgAc1BAFUIhAwMBomio4DRR0upKNfXhC6YenM9n0BbwlE4kkUsA8cPiYNSfKikGA0A==

And this is my current code:
using Harmony;
using RimWorld;
using Verse;
using UnityEngine;
using System.Collections.Generic;
using System.Reflection.Emit;
using System;
using System.Reflection;

namespace RandomPlus
{
[HarmonyPatch (typeof(Page_ConfigureStartingPawns), "RandomizeCurPawn")]
class Patch_RandomizeMethod
{
static void Prefix ()
{
RandomSettings.ResetRerollCounter ();
}

static IEnumerable<CodeInstruction> Transpiler (IEnumerable<CodeInstruction> instructions)
{
var curPawnFieldInfo = typeof(Page_ConfigureStartingPawns)
.GetField ("curPawn", BindingFlags.NonPublic | BindingFlags.Instance);
var randomizeInPlaceMethodInfo = typeof(StartingPawnUtility)
.GetMethod ("RandomizeInPlace", BindingFlags.Public | BindingFlags.Static);
var checkPawnIsSatisfiedMethodInfo = typeof(RandomSettings)
.GetMethod ("CheckPawnIsSatisfied", BindingFlags.Public | BindingFlags.Static);

var codes = new List<CodeInstruction> (instructions);

var appropriatePlace = 6;

/* Removing the following code in its IL form */

// this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);

codes.RemoveRange (appropriatePlace, 5);

/* Adding the following code in its IL form: */

// do {
//   this.curPawn = StartingPawnUtility.RandomizeInPlace (this.curPawn);
// } while (!RandomSettings.CheckPawnIsSatisfiedMethodInfo);

//
// // loop start (head: IL_0016)
// IL_0016: nop
// IL_0017: ldarg.0
// IL_0018: ldarg.0
// IL_0019: ldarg.0
// IL_001a: ldfld int32 C::curPawn
// IL_001f: call instance int32 C::RandomizeInPlace(int32)
// IL_0024: stfld int32 C::curPawn
// IL_0029: nop
// IL_002a: ldarg.0
// IL_002b: call instance bool C::CheckPawnIsSatisfied()
// IL_0030: ldc.i4.0
// IL_0031: ceq
// IL_0033: stloc.1
// // sequence point: hidden
// IL_0034: ldloc.1
// IL_0035: brtrue.s IL_0016
// // end loop

List <CodeInstruction> newCodes = new List<CodeInstruction> {
new CodeInstruction (OpCodes.Nop),
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Ldfld, curPawnFieldInfo),
new CodeInstruction (OpCodes.Call, randomizeInPlaceMethodInfo),
new CodeInstruction (OpCodes.Stfld, curPawnFieldInfo),
new CodeInstruction (OpCodes.Nop),
new CodeInstruction (OpCodes.Ldarg_0),
new CodeInstruction (OpCodes.Call, checkPawnIsSatisfiedMethodInfo),
new CodeInstruction (OpCodes.Ldc_I4_0),
new CodeInstruction (OpCodes.Ceq),
new CodeInstruction (OpCodes.Stloc_1),
new CodeInstruction (OpCodes.Ldloc_1),
};

newCodes [0].labels.Add (new Label ());

var nopLabel = newCodes [0].labels [0];

newCodes.Add (new CodeInstruction (OpCodes.Brtrue_S, nopLabel));

codes.InsertRange (appropriatePlace, newCodes);

for (var i = 0; i < codes.Count; i++) {
Log.Message (codes [i].ToString ());
}

return codes;
}
}
}


And the text of the error:
Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for RandomPlus.HarmonyPatches ---> System.FormatException: Method RimWorld.Page_ConfigureStartingPawns.RandomizeCurPawn() cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) RimWorld.Page_ConfigureStartingPawns:RandomizeCurPawn_Patch1 (object): IL_003c: stloc.1   


  at Harmony.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, Harmony.PatchInfo patchInfo, System.String instanceID) [0x00000] in <filename unknown>:0
  at Harmony.PatchProcessor.Patch () [0x00000] in <filename unknown>:0
  at Harmony.HarmonyInstance.<PatchAll>b__7_0 (System.Type type) [0x00000] in <filename unknown>:0
  at Harmony.CollectionExtensions.Do[Type] (IEnumerable`1 sequence, System.Action`1 action) [0x00000] in <filename unknown>:0
  at Harmony.HarmonyInstance.PatchAll (System.Reflection.Assembly assembly) [0x00000] in <filename unknown>:0
  at RandomPlus.HarmonyPatches..cctor () [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at (wrapper managed-to-native) System.Runtime.CompilerServices.RuntimeHelpers:RunClassConstructor (intptr)
  at System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor (RuntimeTypeHandle type) [0x00000] in <filename unknown>:0
  at Verse.StaticConstructorOnStartupUtility.CallAll () [0x00000] in <filename unknown>:0
  at Verse.PlayDataLoader.<DoPlayLoad>m__2 () [0x00000] in <filename unknown>:0
  at Verse.LongEventHandler.ExecuteToExecuteWhenFinished () [0x00000] in <filename unknown>:0
Verse.Log:Error(String)
Verse.LongEventHandler:ExecuteToExecuteWhenFinished()
Verse.LongEventHandler:UpdateCurrentAsynchronousEvent()
Verse.LongEventHandler:LongEventsUpdate(Boolean&)
Verse.Root:Update()
Verse.Root_Entry:Update()

Mehni

Quote from: bionicjoethehat on June 07, 2019, 11:26:12 AM
Your first code is taken from 1.0 but I am adapting a 1.0 mod for B18.

oh ffs -- I only now realise it. You're downgrading a mod? well screw the transpiler then -- who cares about compatibility within B18. Just prefix it and return false. Amount of users affected: You.

bionicjoethehat

Quoteoh ffs -- I only now realise it. You're downgrading a mod?
Yes, and I've said that both in my messages and in the title which is shown next to each message here.
Quotewell screw the transpiler then -- who cares about compatibility within B18. Just prefix it and return false. Amount of users affected: You.
At least I care and that fact is enough for me to port this mod though I think more that just me will find it interesting, but you're right, not many.

In the end of the day, I am free to spend my time and energy however I want (in a good way) and if something is useful for at least one person or even more people, I don't see why it shouldn't exist.

I appreciate your help, Mehni and I've already included you in the credits section of this mod but I still need to make it work and would appreciate any help from anyone.

bionicjoethehat


LWM

If you run into any further problems, code posted on a site such as github might make things easier for people trying to help.  Or might not.

Congratulations on making use of the transpiler!

--LWM

bionicjoethehat

Yay! The port is now fully working! I've been using it for two days now and I absolutely enjoy having it in B18!

Canute

Gratzs !!
And now you can slowly port all your missing mod's to 1.0 and update rimworld itself to 1.0. too:-)