[LIB] Harmony v1.2.0.1

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

Previous topic - Next topic

CannibarRechter

I'm a little confused here. Using the most recent version of ILSpy, below is what ContentFinder<T>.Get() looks like to me. Is there some reason I am seeing a different ContentFinder than you are?


public static T Get(string itemPath, bool reportFailure = true)
{
if (!UnityData.IsInMainThread)
{
Log.Error("Tried to get a resource \"" + itemPath + "\" from a different thread. All resources must be loaded in the main thread.");
return (T)((object)null);
}
T t = (T)((object)null);
foreach (ModContentPack current in LoadedModManager.RunningMods)
{
t = current.GetContentHolder<T>().Get(itemPath);
if (t != null)
{
T result = t;
return result;
}
}
if (typeof(T) == typeof(Texture2D))
{
t = (T)((object)Resources.Load<Texture2D>(GenFilePaths.ContentPath<Texture2D>() + itemPath));
}
if (typeof(T) == typeof(AudioClip))
{
t = (T)((object)Resources.Load<AudioClip>(GenFilePaths.ContentPath<AudioClip>() + itemPath));
}
if (t != null)
{
return t;
}
if (reportFailure)
{
Log.Error(string.Concat(new object[]
{
"Could not load ",
typeof(T),
" at ",
itemPath,
" in any active mod or in base resources."
}));
}
return (T)((object)null);
}
CR All Mods and Tools Download Link
CR Total Texture Overhaul : Gives RimWorld a Natural Feel
CR Moddable: make RimWorld more moddable.
CR CompFX: display dynamic effects over RimWorld objects

Brrainz

Don't use the disassembled source code. Look at the IL code and you see that it contains try/catch meta data.

DoctorVanGogh

You're wrooooong ;D The decompiled code is correct - and that is because
foreach(var foo in bar) {... }
is just syntactic sugar for

IEnumerable<X> e;
try {
  e = bar.GetEnumerator();
  while (e.MoveNext()) {
    var foo = e.Current;
    ...
  }
} finally {
  e.Dispose();
}
Appreciate my mods? Buy me a coffee

Master Bucketsmith

Just passing through, wanted to say I'm happy you continued the good work, pardeike!

Brrainz

Quote from: Master Bucketsmith on September 14, 2017, 10:20:01 AM
Just passing through, wanted to say I'm happy you continued the good work, pardeike!
ai remember our conversation like they were yesterday. Hope all is well my friend!

CannibarRechter

Hi, pardeike,

Using Visual Studio 2017 Enterprise, when attempting to compile Transpilers.cs, it issues he following error:

Severity   Code   Description   Project   File   Line   Suppression State
Error   CS0252   Possible unintended reference comparison; to get a value comparison, cast the left hand side to type 'MethodBase'   Harmony   H:\joe.kraska\Artifacts\Mods\Harmony-master\Harmony\Transpilers.cs   13   Active

... while I can fix it in the base code, I thought I'd let you know about the issue.
CR All Mods and Tools Download Link
CR Total Texture Overhaul : Gives RimWorld a Natural Feel
CR Moddable: make RimWorld more moddable.
CR CompFX: display dynamic effects over RimWorld objects

CannibarRechter

Hi, pardeike, as promised I have created a standalone test program for you, attached below. Regarding your pull request for generics support, I would suggest that you make harmony modal: one mode "strict," which attempts to implement everything well, and another one that implements transpilers, but doesn't support generics. Transpilers appear to be more an edge case to me, but perhaps your other users think otherwise. Regardless, why not support both? Just support it an argument to the constructor of the HarmonyInstance.

Looking on your github site, it's unclear to me which pull request adds the try/catch metadata. I only see the 3. I was wondering if I could try it (I do not need transpilers, unless somehow their involved in basic patching, and I don't think they are).

Anyway, here is the code:


using System;
using System.Reflection;
using Harmony;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
Console.WriteLine( "------------- BASE BLOCK ------------" );
Alpha.Do();
Beta.Do();
Test<Alpha>.Do();
Test<Beta>.Do();

try
{
HarmonyInstance harmony = HarmonyInstance.Create( "testme.testme" );

MethodInfo origMethod = null;
MethodInfo prefixMethod = null;
MethodInfo postfixMethod = null;
HarmonyMethod prefix = null;
HarmonyMethod postfix = null;

//--> This block has problems;

Console.WriteLine( "------------- BLOCK WITH GENERIC ------------" );
origMethod = AccessTools.Method( typeof(Test<Beta>), "Do" );
Console.WriteLine( $"origMethod: {origMethod}" );
prefixMethod = AccessTools.Method( typeof(Patch), "Prefix" );
Console.WriteLine( $"targetMethod: {prefixMethod}" );
postfixMethod = AccessTools.Method( typeof(Patch), "Postfix" );
Console.WriteLine( $"targetMethod: {postfixMethod}" );
prefix = new HarmonyMethod( prefixMethod );
postfix = new HarmonyMethod( postfixMethod );
Console.WriteLine( $"prefix: {prefix}" );
Console.WriteLine( $"postfix: {postfix}" );
harmony.Patch( origMethod, prefix, null );
harmony.Patch( origMethod, null, postfix );


Console.WriteLine( "------------- TEST<ALPHA> ------------" );
Test<Alpha>.Do();
Console.WriteLine( "------------- TEST<BETA> ------------" );
Test<Beta>.Do();

//--> This block works:

Console.WriteLine( "------------- BLOCK NO GENERIC ------------" );
origMethod = AccessTools.Method( typeof(Alpha), "Do" );
Console.WriteLine( $"origMethod: {origMethod}" );
prefixMethod = AccessTools.Method( typeof(Patch), "Prefix" );
Console.WriteLine( $"targetMethod: {prefixMethod}" );
postfixMethod = AccessTools.Method( typeof(Patch), "Postfix" );
Console.WriteLine( $"targetMethod: {postfixMethod}" );
prefix = new HarmonyMethod( prefixMethod );
postfix = new HarmonyMethod( postfixMethod );
Console.WriteLine( $"prefix: {prefix}" );
Console.WriteLine( $"postfix: {postfix}" );
harmony.Patch( origMethod, prefix, null );
harmony.Patch( origMethod, null, postfix );

Alpha.Do();

}
catch (Exception e)
{
Console.WriteLine($"Caught exception: {e}");
}

                   
}
    }

public class Patch
{
public static bool Prefix()
{
Console.WriteLine(">>>>>>>>>>>>>> PREFIX ");
return true;
}
public static void Postfix()
{
Console.WriteLine("<<<<<<<<<<<<<< POSTFIX");
}
}


public class Test<T> // where T : class
{
public static void Do()
{
Console.WriteLine( "A." );
if (typeof(T) == typeof(Alpha))
{
Console.WriteLine( "B." );
Alpha.Do();
}
Console.WriteLine( "C." );
if (typeof(T) == typeof(Beta))
{
Console.WriteLine( "D." );
Beta.Do();
}
Console.WriteLine( "E." );
}
}

public class Alpha
{
public static void Do()
{
Console.WriteLine("Alpha");
}
}

public  class Beta
{
public static void Do()
{
Console.WriteLine("Beta");
}
}
}
CR All Mods and Tools Download Link
CR Total Texture Overhaul : Gives RimWorld a Natural Feel
CR Moddable: make RimWorld more moddable.
CR CompFX: display dynamic effects over RimWorld objects

Brrainz

I have downloaded your example and am testing it with my latest version that is based on the master but with the fixes I am working on. I can confirm that your example does not work. It simply does not have any effect when I run it in the latest .NET 4.6. My guess is that generics work different in mono and .NET core.

Regarding transpilers: there is nothing special about them. Harmony builds its new method using IL code to call prefix/postfixes and transpilers only affect the il codes that come from the original method like a filter. So there is no need to exclude them since they are not the cause of the problem.

All I can say without looking deeper into the issue is that generics work in the mono version of Harmony.

CannibarRechter

BTW, is Harmony supposed to be working on constructors right now? I tied a pattern of className plus function name = className, and it appears to hard crash rimworld entirely. The most straightforwad patch I'm working on is a Postfix on a constructor. I can probably find somewhere else to hook this, but...
CR All Mods and Tools Download Link
CR Total Texture Overhaul : Gives RimWorld a Natural Feel
CR Moddable: make RimWorld more moddable.
CR CompFX: display dynamic effects over RimWorld objects

Brrainz

Yes, Harmony can patch constructors. You need to use manual patching or TargetMethod() to return the correct MethodBase value but it should work.

Keep in mind though that constructors are not returning any values. They are called on empty new instances of a type and are suppose to fill/initialize the instance. Look at a typical IL code example. I have personally used transpilers on a constructor with success.

CannibarRechter

Okay. I see what I was doing wrong. #1, I was using MethodInfo everywhere. That's probably why you use MethodBase in your examples, as that is the common parent of both MethodInfo and ConstructorInfo. So I had to fix that up to get around a casting error. After that, you have to use a different method to get a hold of the constructor.

I was looking for a null argument constructor, so it needed a request like this:


origMethod = (MethodBase) typeof(Alpha).GetConstructor( new Type[]{} );


That's postfixable just fine.

Regarding the your comments about not returning any values, I think what you are saying is that if I wanted to run a prefix, I would initialize all the __instance data items individually, and probablly best return false. I can't imagine the fuckery if constructors are chained. LOL.

Anyway, my needs are pretty simple. I need to make a core class change a single value in the constructor, so the postfix is cleaner.

Thanks for your help.
CR All Mods and Tools Download Link
CR Total Texture Overhaul : Gives RimWorld a Natural Feel
CR Moddable: make RimWorld more moddable.
CR CompFX: display dynamic effects over RimWorld objects

DoctorVanGogh

Quote from: CannibarRechter on September 20, 2017, 01:34:14 PM

origMethod = (MethodBase) typeof(Alpha).GetConstructor( new Type[]{} );

*cough* Type.EmptyTypes *cough*
Appreciate my mods? Buy me a coffee

CannibarRechter

I just ran across a strange situation. What I really want to do is merely add an override method to an existing (defined) class. Since that doesn't appear possible, my next question is: is it expected to work to replace a virtual method on a base class?

CR All Mods and Tools Download Link
CR Total Texture Overhaul : Gives RimWorld a Natural Feel
CR Moddable: make RimWorld more moddable.
CR CompFX: display dynamic effects over RimWorld objects

Brrainz

The anser is: if a disassembler shows code for a method and that method does not get inlined, then Harmony can patch that method. If a class does not override a base classes method then there is nothing to patch but that base class method.

Quote from: CannibarRechter on September 27, 2017, 07:04:17 PM
I just ran across a strange situation. What I really want to do is merely add an override method to an existing (defined) class. Since that doesn't appear possible, my next question is: is it expected to work to replace a virtual method on a base class?

Nightinggale

I have run into what I consider an odd problem. I added this piece of code:
    [HarmonyPatch(typeof(Verse.Corpse))]
    [HarmonyPatch("SpecialDisplayStats")]
    class CorpseDisplay
    {
    }

This results in "System.ArgumentException: No target method specified for class....". The odd thing is that if I replace SpecialDisplayStats with something else, it works. The class is indeed completely empty, meaning it's not the contents of my class, which cause this issue. The question is then: what does and what do I do to solve this issue?

The vanilla code in question is this:
public override IEnumerable<StatDrawEntry> SpecialDisplayStats
{
get
{
foreach (StatDrawEntry s in base.SpecialDisplayStats)
{
yield return s;
}
if (this.GetRotStage() == RotStage.Fresh)
{
yield return new StatDrawEntry(StatCategoryDefOf.Basics, "Nutrition".Translate(), FoodUtility.GetBodyPartNutrition(this.InnerPawn, this.InnerPawn.RaceProps.body.corePart).ToString("0.##"), 0);
StatDef meatAmount = StatDefOf.MeatAmount;
yield return new StatDrawEntry(meatAmount.category, meatAmount, this.InnerPawn.GetStatValue(meatAmount, true), StatRequest.For(this.InnerPawn), ToStringNumberSense.Undefined);
StatDef leatherAmount = StatDefOf.LeatherAmount;
yield return new StatDrawEntry(leatherAmount.category, leatherAmount, this.InnerPawn.GetStatValue(leatherAmount, true), StatRequest.For(this.InnerPawn), ToStringNumberSense.Undefined);
}
}
}

It's a little big to expect it to be inlined.

Another semi related question. When the vanilla function is based on IEnumerable and yield return, how do I append to the output in Postfix? I thought I could get __result to be a reference to a list, but I can't get that to work. I'm thinking it would be best to answer this question with an example on the wiki, which say reads __result, removes the first element and then appends it to the end. It might not be that useful, but it's simple and demonstrates both adding and removal/editing of the output from vanilla.
ModCheck - boost your patch loading times and include patchmods in your main mod.