Beginner needs help with porting a mod 1.0 -> B18

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

Previous topic - Next topic

bionicjoethehat

My previous topic: https://ludeon.com/forums/index.php?topic=48889.0

Hi!

Since no one seemed to be interested in porting RandomPlus to B18, I figured I could try doing it myself since I had some coding experience. So I did.

There were some errors during the building process but I managed to fix them. The only thing I couldn't fix though was some run-time error and I had absolutely now idea what it said so, could anyone please help me with this?

Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for RandomPlus.HarmonyPatches ---> System.Exception: Exception from HarmonyInstance "RandomPlus" ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentOutOfRangeException: Argument is out of range.
Parameter name: index
  at System.Collections.Generic.List`1[Harmony.CodeInstruction].CheckIndex (Int32 index) [0x00000] in <filename unknown>:0
  at System.Collections.Generic.List`1[Harmony.CodeInstruction].set_Item (Int32 index, Harmony.CodeInstruction value) [0x00000] in <filename unknown>:0
  at RandomPlus.Patch_RandomizeMethod.Transpiler (IEnumerable`1 instructions) [0x00000] in <filename unknown>:0
  at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (object,object[],System.Exception&)
  at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0
  at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in <filename unknown>:0
  at Harmony.CodeTranspiler+<>c__DisplayClass10_0.<GetResult>b__0 (System.Reflection.MethodInfo transpiler) [0x00000] in <filename unknown>:0
  at System.Collections.Generic.List`1[System.Reflection.MethodInfo].ForEach (System.Action`1 action) [0x00000] in <filename unknown>:0
  at Harmony.CodeTranspiler.GetResult (System.Reflection.Emit.ILGenerator generator, System.Reflection.MethodBase method) [0x00000] in <filename unknown>:0
  at Harmony.ILCopying.MethodBodyReader.FinalizeILCodes (System.Collections.Generic.List`1 transpilers, System.Collections.Generic.List`1 endLabels, System.Collections.Generic.List`1 endBlocks) [0x00000] in <filename unknown>:0
  at Harmony.ILCopying.MethodCopier.Finalize (System.Collections.Generic.List`1 endLabels, System.Collections.Generic.List`1 endBlocks) [0x00000] in <filename unknown>:0
  at Harmony.MethodPatcher.CreatePatchedMethod (System.Reflection.MethodBase original, System.String harmonyInstanceID, System.Collections.Generic.List`1 prefixes, System.Collections.Generic.List`1 postfixes, System.Collections.Generic.List`1 transpilers) [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at Harmony.MethodPatcher.CreatePatchedMethod (System.Reflection.MethodBase original, System.String harmonyInstanceID, System.Collections.Generic.List`1 prefixes, System.Collections.Generic.List`1 postfixes, System.Collections.Generic.List`1 transpilers) [0x00000] in <filename unknown>: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_Patch1(Object)
Verse.Root_Entry:Update()


Another issue is that when I build the mod, my IDE exports not only my mod's dll, but also some dll-dependenices(?) to the Assemblies folder.

Any help is appreciated, cheers!

Mehni

Alright. So, let's look at the error.

QuoteCould not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for RandomPlus.HarmonyPatches ---> System.Exception: Exception from HarmonyInstance "RandomPlus" ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentOutOfRangeException: Argument is out of range.
Parameter name: index
That's a lot of words, but it basically says: "Your constructor threw an ArgumentOutOfRangeException."

The rest of the stacktrace tells you where:


  at System.Collections.Generic.List`1[Harmony.CodeInstruction].CheckIndex (Int32 index) [0x00000] in <filename unknown>:0
  at System.Collections.Generic.List`1[Harmony.CodeInstruction].set_Item (Int32 index, Harmony.CodeInstruction value) [0x00000] in <filename unknown>:0
  at RandomPlus.Patch_RandomizeMethod.Transpiler (IEnumerable`1 instructions) [0x00000] in <filename unknown>:0
  at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (object,object[],System.Exception&)


The RandomPlus.Patch_RandomizeMethod.Transpiler tried to grab a part of a List<T> that was out of range. In 8 out of ten cases, you tried to grab element 11 out of 10. In the other cases, you tried to grab element -1. If you want more specific help, post your code.

QuoteAnother issue is that when I build the mod, my IDE exports not only my mod's dll, but also some dll-dependenices(?) to the Assemblies folder.

https://rimworldwiki.com/wiki/Modding_Tutorials/Setting_up_a_solution

Copy Local => False.

bionicjoethehat

#2
Thanks for replying! :)

Quotehttps://rimworldwiki.com/wiki/Modding_Tutorials/Setting_up_a_solution

Copy Local => False.

I think the tutorial needs to be more specific on setting Copy Local to False in Xamarin Studio because I couldn't find how to do that and had to switch to SharpDevelop, for which there is a more detailed instruction.

Turns out, unlike in SharpDevelop, you don't right-click a reference, but left-click it and proceed to the Properties window where you can uncheck the Copy Local parameter or, if the window is not open, you right-click the appropriate reference and click Properties. I think this (perhaps with a better wording) should be added to the Wiki page.

QuoteThe RandomPlus.Patch_RandomizeMethod.Transpiler tried to grab a part of a List<T> that was out of range. In 8 out of ten cases, you tried to grab element 11 out of 10. In the other cases, you tried to grab element -1. If you want more specific help, post your code.

Here you are:

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)
        {
            int startIndex = -1;

            var curPawnFieldInfo = typeof(Page_ConfigureStartingPawns)
                .GetField("curPawn", BindingFlags.NonPublic | BindingFlags.Instance);
            var randomizeInPlaceMethodInfo = typeof(StartingPawnUtility)
                .GetMethod("RandomizeInPlace", BindingFlags.Public | BindingFlags.Static);

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

            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].opcode == OpCodes.Ldfld &&
                    codes[i].operand == curPawnFieldInfo &&
                    codes[i + 1].opcode == OpCodes.Call &&
                    codes[i + 1].operand == randomizeInPlaceMethodInfo)
                {
                    startIndex = i;
                    break;
                }
            }

            if (startIndex != -1)
            {
                var randomLimitRerollMethodInfo = typeof(RandomSettings)
                    .GetMethod("RandomRerollLimit", BindingFlags.Public | BindingFlags.Static);

                var CheckPawnIsSatisfiedMethodInfo = typeof(RandomSettings)
                    .GetMethod("CheckPawnIsSatisfied", BindingFlags.Public | BindingFlags.Static);

                codes[startIndex + 8] = new CodeInstruction(OpCodes.Call, randomLimitRerollMethodInfo);
               
                var startLoopLocation = codes[startIndex + 12].operand;

                var newCode = new List<CodeInstruction>();
                newCode.Add(new CodeInstruction(OpCodes.Nop));
                newCode.Add(new CodeInstruction(OpCodes.Ldarg_0));
                newCode.Add(new CodeInstruction(OpCodes.Ldfld, curPawnFieldInfo));
                newCode.Add(new CodeInstruction(OpCodes.Call, CheckPawnIsSatisfiedMethodInfo));
                newCode.Add(new CodeInstruction(OpCodes.Brfalse, startLoopLocation));
                codes.InsertRange(startIndex + 12, newCode);
            }

            return codes;
        }
    }
}


UPD: The error disappears when I comment the last if-clause, so, it must be it.

UPD2: Seems like this is the problem:
codes[startIndex + 8] = new CodeInstruction(OpCodes.Call, randomLimitRerollMethodInfo);

UPD3: it seems we've localized the problem but I have no idea what that piece of code does. :D

UPD4: At least the Edit window where you select skills and traits that your pawn must have is not working but the mod won't roll pawns until you get one that matches your criteria.

bionicjoethehat

#3
At this point, there is very little I understand about the Patch_RandomizeMethod class but it seems that it is used to replace the original RandomizeCurPawn method?

I'm also wondering what this code does:
var curPawnFieldInfo = typeof(Page_ConfigureStartingPawns)
                .GetField("curPawn", BindingFlags.NonPublic | BindingFlags.Instance);

Perhaps grabs a value for the valuable from somewhere else?

UPD: figured they're grabbing "curPawn" from Page_ConfigureStartingPawns which has a private field "curPawn" but I have a question, why, when I was looking at this class through Assembly Browser in the IDE, it didn't show me the field? Because when I looked at the decompilied version, there is was such field.
Solved by setting "Visibility" to "All members". So happy now. :D

UPD2: The biggest mystery for me now is perhaps those "codes": what are they? What do they do?

UPD3: seems like Transpiler is used to modify the code of an original method, probably RandomizeCurPawn or something but I have absolutely no idea what those OpCodes stand for.

UPD4:RandomizeCurPawn in 1.0 has more lines than the one in B18. I guess the code tries to change some command in the original method of 1.0 but it is not there in B18 so it fails.

bionicjoethehat

#4
Seems like this piece of code is searching for the line which calls randomizeInPlace in the original method and then, as far as I can tell, saves its instruction number or something to the startIndex variable:

            int startIndex = -1;

            var curPawnFieldInfo = typeof(Page_ConfigureStartingPawns)
                .GetField("curPawn", BindingFlags.NonPublic | BindingFlags.Instance);
            var randomizeInPlaceMethodInfo = typeof(StartingPawnUtility)
                .GetMethod("RandomizeInPlace", BindingFlags.Public | BindingFlags.Static);

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

            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].opcode == OpCodes.Ldfld &&
                    codes[i].operand == curPawnFieldInfo &&
                    codes[i + 1].opcode == OpCodes.Call &&
                    codes[i + 1].operand == randomizeInPlaceMethodInfo)
                {
                    startIndex = i;
                    break;
                }
            }

Mehni

Quote from: bionicjoethehat on June 05, 2019, 11:06:27 AM
At this point, there is very little I understand about the Patch_RandomizeMethod class but it seems that it is used to replace the original RandomizeCurPawn method?

UPD2: The biggest mystery for me now is perhaps those "codes": what are they? What do they do?

UPD3: seems like Transpiler is used to modify the code of an original method, probably RandomizeCurPawn or something but I have absolutely no idea what those OpCodes stand for.


Background info: There's a layer of abstraction between C# and machine code. C# isn't compiled into machine code, it's compiled into an intermediate language that is then compiled into machine code by the JIT-compiler during runtime.

Those OpCodes are part of the Common Intermediate Language. They're the lowest-level human readable CPU instructions. Harmony allows you to manipulate the OpCodes prior to their use by the JIT compiler, mostly to make small modifications to methods. You're basically manipulating what goes on the stack.

If you have a good decompiler, you can select "view IL" and see exactly what happens. For the interest of education, this is what ILSpy's "IL with C#" view gives me:

.method private hidebysig
instance void RandomizeCurPawn () cil managed
{
// Method begins at RVA 0x1707a8
// Code size 83 (0x53)
.maxstack 2
.locals init (
[0] int32
)

// if (TutorSystem.AllowAction("RandomizePawn"))
IL_0000: ldstr "RandomizePawn"
IL_0005: call valuetype RimWorld.EventPack RimWorld.EventPack::op_Implicit(string)
IL_000a: call bool RimWorld.TutorSystem::AllowAction(valuetype RimWorld.EventPack)
// (no C# code)
IL_000f: brtrue IL_0015

IL_0014: ret

// int num = 0;
IL_0015: ldc.i4.0
IL_0016: stloc.0
// loop start (head: IL_0017)
// curPawn = StartingPawnUtility.RandomizeInPlace(curPawn);
IL_0017: ldarg.0
IL_0018: ldarg.0
IL_0019: ldfld class Verse.Pawn RimWorld.Page_ConfigureStartingPawns::curPawn
IL_001e: call class Verse.Pawn Verse.StartingPawnUtility::RandomizeInPlace(class Verse.Pawn)
IL_0023: stfld class Verse.Pawn RimWorld.Page_ConfigureStartingPawns::curPawn
// num++;
IL_0028: ldloc.0
IL_0029: ldc.i4.1
IL_002a: add
IL_002b: stloc.0
// while (num <= 20 && !StartingPawnUtility.WorkTypeRequirementsSatisfied());
IL_002c: ldloc.0
IL_002d: ldc.i4.s 20
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
// end loop

// TutorSystem.Notify_Event("RandomizePawn");
IL_0043: ldstr "RandomizePawn"
IL_0048: call valuetype RimWorld.EventPack RimWorld.EventPack::op_Implicit(string)
IL_004d: call void RimWorld.TutorSystem::Notify_Event(valuetype RimWorld.EventPack)
// (no C# code)
IL_0052: ret
} // end of method Page_ConfigureStartingPawns::RandomizeCurPawn


I'm not too familiar with RandomPlus, but it looks like you could change

while (num <= 20 && !StartingPawnUtility.WorkTypeRequirementsSatisfied());

into

while (num <= 20 && !StartingPawnUtility.WorkTypeRequirementsSatisfied() && RandomSettings.CheckPawnIsSatisfied(curPawn));

Doing && is a bit tricky (jumping to labels is annoying in IL), so you could elect to use &. No short-circuiting, but oh well. The rest of the code is already present in the transpiler: it's just a matter of inserting it at the right spot.

bionicjoethehat

codes[startIndex + 8] = new CodeInstruction(OpCodes.Call, randomLimitRerollMethodInfo);
Does this piece of code mean that it replaces certain instruction which is at "startIndex + 8" line?

LWM

If any of those loops are on the final line of code (that is, codes.Count-1), then codes[i+1] is going to throw exactly that sort of error.

Ask me how I know :p

Quote from: bionicjoethehat on June 06, 2019, 09:09:38 AM
codes[startIndex + 8] = new CodeInstruction(OpCodes.Call, randomLimitRerollMethodInfo);
Does this piece of code mean that it replaces certain instruction which is at "startIndex + 8" line?

Yes.  Hopefully startIndex+8 IS what you want to be replacing.  And is still in bounds.

As a sanity check, I have started doing multiple for(...) loops when I transpile.  One of the checks is that the current index is (i<codes.Count-X), where X is however many steps ahead I am.  Then after each loop I check to see if my index i has hit the end of the code too early.  If it has, I know I messed something up in the code block above it.

Debugging (or writing) transpilers is tedious and needs careful attention.  Fun, right?  Kudos for taking this on - v brave ;)

--LWM

bionicjoethehat

Pardon, which loops? I only see one. :)
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].opcode == OpCodes.Ldfld &&
                    codes[i].operand == curPawnFieldInfo &&
                    codes[i + 1].opcode == OpCodes.Call &&
                    codes[i + 1].operand == randomizeInPlaceMethodInfo)
                {
                    startIndex = i;
                    break;
                }
            }

bionicjoethehat

#9
What I'm stuck with now is that I can't tell where in the code the "codes[startIndex + 8]" and "startIndex + 12" instructions are. I also don't understand what these commands do:
                var newCode = new List<CodeInstruction>();
                newCode.Add(new CodeInstruction(OpCodes.Nop));
                newCode.Add(new CodeInstruction(OpCodes.Ldarg_0));
                newCode.Add(new CodeInstruction(OpCodes.Ldfld, curPawnFieldInfo));
                newCode.Add(new CodeInstruction(OpCodes.Call, CheckPawnIsSatisfiedMethodInfo));
                newCode.Add(new CodeInstruction(OpCodes.Brfalse, startLoopLocation));
                codes.InsertRange(startIndex + 12, newCode);

But perhaps if I understood the idea behind this piece of code, I would know how to change it to so it works in B18.

UPD:
Log.Message ((startIndex + 8).ToString() + " | " + codes.Count.ToString());
shows
15 | 14
If it says 15, then the fifteenth command in the ILSpy view of the method that Mehni posted is what is replaced by
codes[startIndex + 8] = new CodeInstruction(OpCodes.Call, randomLimitRerollMethodInfo);
?

So basically, I need to know where and what is added/changed to the original code.

bionicjoethehat

#10
This may sound like a dumb idea, but if I don't understand what the mod author's code does with instructions and can't visualize where in the code of the original method, maybe I could just replace all the instructions of the original B18 method with those from 1.0 one?

bionicjoethehat

Here 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 workTypeRequirementsSatisfiedMethodInfo = typeof(StartingPawnUtility)
.GetMethod ("workTypeRequirementsSatisfied", BindingFlags.Public | BindingFlags.Static);

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

// I basically replace the B18 RandomizeCurPawn with 1.0's.
// The IL code is available at:
// https://ludeon.com/forums/index.php?topic=48902.msg460016#msg460016

codes.RemoveRange (5, 5);

var IL_0015 = new CodeInstruction (OpCodes.Ldc_I4_0);

var IL_0039 = new CodeInstruction (OpCodes.Call, workTypeRequirementsSatisfiedMethodInfo);

List<CodeInstruction> supportCode = new List<CodeInstruction> {
new CodeInstruction (OpCodes.Ldc_I4_0),
new CodeInstruction (OpCodes.Stloc_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.Ldloc_0),
new CodeInstruction (OpCodes.Ldc_I4_1),
new CodeInstruction (OpCodes.Add),
new CodeInstruction (OpCodes.Stloc_0),

new CodeInstruction (OpCodes.Ldloc_0),
new CodeInstruction (OpCodes.Ldc_I4_S, 20),
new CodeInstruction (OpCodes.Ble, IL_0039),

// These IL_* strings are just placeholders for now

new CodeInstruction (OpCodes.Br, "IL_0043"),

IL_0039,
new CodeInstruction (OpCodes.Brfalse, "IL_0017"),
};

codes.InsertRange (5, supportCode);

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

// Original code:

// int startIndex = -1;
//           
//            for (int i = 0; i < codes.Count; i++)
//            {
//                if (codes[i].opcode == OpCodes.Ldfld &&
//                    codes[i].operand == curPawnFieldInfo &&
//                    codes[i + 1].opcode == OpCodes.Call &&
//                    codes[i + 1].operand == randomizeInPlaceMethodInfo)
//                {
//                    startIndex = i;
//                    break;
//                }
//            }
//
// Log.Message (startIndex.ToString());


//            if (startIndex != -1)
//            {
//                var randomLimitRerollMethodInfo = typeof(RandomSettings)
//                    .GetMethod("RandomRerollLimit", BindingFlags.Public | BindingFlags.Static);
//
//                var CheckPawnIsSatisfiedMethodInfo = typeof(RandomSettings)
//                    .GetMethod("CheckPawnIsSatisfied", BindingFlags.Public | BindingFlags.Static);
//
// codes[startIndex + 8] = new CodeInstruction(OpCodes.Call, randomLimitRerollMethodInfo);
//
//                var startLoopLocation = codes[startIndex + 12].operand;
//
//                var newCode = new List<CodeInstruction>();
//                newCode.Add(new CodeInstruction(OpCodes.Nop));
//                newCode.Add(new CodeInstruction(OpCodes.Ldarg_0));
//                newCode.Add(new CodeInstruction(OpCodes.Ldfld, curPawnFieldInfo));
//                newCode.Add(new CodeInstruction(OpCodes.Call, CheckPawnIsSatisfiedMethodInfo));
//                newCode.Add(new CodeInstruction(OpCodes.Brfalse, startLoopLocation));
//                codes.InsertRange(startIndex + 12, newCode);
//            }

return codes;
}
}
}

And my current problem is that some instructions depend on others. For instance:
IL_002f: ble IL_0039
I tried doing this, where IL_0039 is a variable containing the appropriate CodeInstruction:
new CodeInstruction (OpCodes.Ble, IL_0039),
But it was interpreted like this:
ble call NULL
So, how do you refer properly to other instructions?

Also, there is an instruction that I don't touch but it refers to another one that is changed:
IL_000f: brtrue IL_0015
It refers to the instruction that is changed with:
new CodeInstruction (OpCodes.Ldc_I4_0),
So I basically replace one instruction with another but will it still be accessible by IL_0015?

Mehni

Now I finally think you read what I posted. If you didn't understand a lick of it, ask. Don't ignore it, because you need to understand it to update that transpiler. You cannot expect to solve this if you only look at it from a high-level perspective.

You have a list of CodeInstructions. CodeInstructions are things like:

- load the value of one on the stack.
- load the value of two on the stack.
- load the value of local variable A on the stack.
- Add together.

Think programming with BASIC.
>10 PRINT "HELLO WORLD!";
>20 GOTO 10.
That level.

What do you think "startIndex + 12" is? It's the (n + 12)th element in the list of instructions. Again, take a look at the ILCode I posted.

---

If you can't (yet) wrap your head around what the RandomizeCurPawn method should look like in ILCode, see if you can make sense of what that method should look like in C#. Then try and translate that back into ILCode.

bionicjoethehat

#13
Mehni, I read all posts as soon as I see them so don't worry. (:

I now think that changing the whole method is an overkill; it's rather easier to alter the existing code now that I know what the author does with it in their mod. I think all I need to do is to replace
this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);
in
private void RandomizeCurPawn()
{
if (!TutorSystem.AllowAction("RandomizePawn"))
{
return;
}
this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);
TutorSystem.Notify_Event("RandomizePawn");
}

with something along these lines
do
{
this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);
}
while (!RandomSettings.CheckPawnIsSatisfiedMethodInfo);

But I don't know how to write it in IL.
UDP:
Ah! I guess a loop is just piece of code that uses gotos.

bionicjoethehat

#14
Please, correct me if I'm wrong but it should be something like this, right?
ldarg.0

IL_0019: ldfld class Verse.Pawn RimWorld.Page_ConfigureStartingPawns::curPawn
IL_001e: call class Verse.Pawn Verse.StartingPawnUtility::RandomizeInPlace(class Verse.Pawn)
IL_0023: stfld class Verse.Pawn RimWorld.Page_ConfigureStartingPawns::curPawn

callvirt / CheckPawnIsSatisfiedMethodInfo
Brfalse => goto first line

I don't know how to refer to another instruction in Brfalse and I also don't think the rest of the code would work. :D How do I write it in proper IL?