[LIB] Harmony v1.2.0.1

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

Previous topic - Next topic

Brrainz

I guess that XpRequiredToLevelUpFrom() isn't called as often as you want. Must be something like that because your code looks ok and patching works for you in general.

oreganor

K, back with another strange issue. This time moving from autopatching approach as wiki explained to manual patching as wiki also explains (My little mod now Detours another method that's also detoured by another mod (Not using Harmony)... So I'm at the 1st step to see if I can make conditional patching based on the pressence of the Other Mod, to avoid the collision).

Still haven't even touched anything related to the final goal... I was just, in theory, converting my code to "manual patching" mode as wiki explains.

From this:


            //Initializing Harmony detours
            var harmony = HarmonyInstance.Create("net.oreganor.rimworld.mod.meleerebalance");
            harmony.PatchAll(Assembly.GetExecutingAssembly());


To this:

   
            //Initializing Harmony detours
            var harmony = HarmonyInstance.Create("net.oreganor.rimworld.mod.meleerebalance");

            var original = typeof(Verb_MeleeAttack).GetMethod("TryCastShot");
            var detour = typeof(VerbMeleeTryCastShotPatch).GetMethod("Prefix");
            harmony.Patch(original, new HarmonyMethod(detour), new HarmonyMethod(null));

            original = typeof(Pawn_DraftController).GetMethod("GetGizmos");
            detour = typeof(Pawn_DraftControllerGetGizmosPatch).GetMethod("Postfix");
            harmony.Patch(original, new HarmonyMethod(null), new HarmonyMethod(detour));


Which is just what wiki describes with the added null definitions according to what HarmonyInstance.Patch expects internally because I only need a fix in each Detour (one Prefix for the 1st, and a Postfix for the 2nd).

This is the error-stack I get from Rimworld when it loads:


Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for MeleeRebalance.MainController ---> System.ArgumentNullException: Argument cannot be null.
Parameter name: key
  at System.Collections.Generic.Dictionary`2[System.Reflection.MethodBase,System.Byte[]].TryGetValue (System.Reflection.MethodBase key, System.Byte[]& value) [0x00000] in <filename unknown>:0
  at Harmony.GeneralExtensions.GetValueSafe[MethodBase,Byte[]] (System.Collections.Generic.Dictionary`2 dictionary, System.Reflection.MethodBase key) [0x00000] in <filename unknown>:0
  at Harmony.HarmonySharedState.GetPatchInfo (System.Reflection.MethodBase method) [0x00000] in <filename unknown>:0
  at Harmony.PatchProcessor.Patch () [0x00000] in <filename unknown>:0
  at Harmony.HarmonyInstance.Patch (System.Reflection.MethodBase original, Harmony.HarmonyMethod prefix, Harmony.HarmonyMethod postfix, Harmony.HarmonyMethod transpiler) [0x00000] in <filename unknown>:0
  at MeleeRebalance.MainController..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__6F8 () [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()



The stack is so deep, so many tests passed... That I ran out of ideas (Tested even with dummy REAL postfix & prefix methods that did some bogus actions and then return instead of the null declarations... The error was the same).

What I'm missing?

EDIT: The other Mod has been completely disabled over the course of ALL this tests. In fact I already did collision tests when patching automatically and nothing happens (My Detour is just ignored, as it should be).

EDIT2: I added the 2 manual patches as reference... Just in case. I tested each of them individually and the error was the same. The only thing I'm missing is to add specifially a dummy Transpiler... The Method prototype marks it as optional, so that would be a really exotic bug.
Contributions:
Melee Skill Rebalance

oreganor

An update, pardeike. I found the issue, after some headbanging sessions checking the code flow differences between automatic patching and manual patching... Certainly the original method could be null as deep as to the GetValueSafe call...

...I forgot to overload with the appropiate BindingFlags for the method in question so, on the code above if I replace:


var original = typeof(Verb_MeleeAttack).GetMethod("TryCastShot");


with:


var original = typeof(Verb_MeleeAttack).GetMethod("TryCastShot", BindingFlags.Static | BindingFlags.Public);


Original method is now non-null and detouring works like a charm. So now I can start checking if I can make conditional patching working.


It would be nice if you warned specifically about this pitfall on the Wiki's example... Because amateur moders like me, just have the habit of "copy'n paste" examples without really understanding the innards of what we are doing :) . I think it would save some headaches to both the user and to yourself by not having to loose time in reports like the above.
Contributions:
Melee Skill Rebalance

Brrainz

Nice that you solved the problem. But regarding your suggested documentation change: no offense but I consider that part basic knowledge of handling MethodInfo. If you patch manually, I expect you to handle fetching MethodInfos the correct way.

oreganor

QuoteBut regarding your suggested documentation change: no offense but I consider that part basic knowledge of handling MethodInfo. If you patch manually, I expect you to handle fetching MethodInfos the correct way.

None taken :)... It's your documentation and you decide which kind of users it's aimed for.
Contributions:
Melee Skill Rebalance

dburgdorf

I'm probably missing something incredibly obvious, especially since nobody else seems to have reported this, but.... Whenever I load the game with any mods utilizing Harmony in the mod list, I end up with a "harmony.log.txt" file on my desktop. How do I stop that from happening?
- Rainbeau Flambe (aka Darryl Burgdorf) -
Old. Short. Grumpy. Bearded. "Yeah, I'm a dorf."



Buy me a Dr Pepper?

Brrainz

Quote from: dburgdorf on April 15, 2017, 04:34:22 PM
I'm probably missing something incredibly obvious, especially since nobody else seems to have reported this, but.... Whenever I load the game with any mods utilizing Harmony in the mod list, I end up with a "harmony.log.txt" file on my desktop. How do I stop that from happening?
At least one of your mods uses Harmony and has the debug flag set in the initialization code. Which one I don't know.

Brrainz

Hi everyone, I just would like to share a quick snippet with all of you. This one serves as a mods Main.cs and includes a Harmony patch that loads a specific save game when you start Rimworld with the command line parameters "-rungame game_file_name" instead of "-quicktest". Enjoy!

https://gist.github.com/pardeike/12c457e135a42a28e068f2aba5337221

/Andreas

Raf's

How do i install this mod? both Harmony, Harmony Tests and Harmony master appear red on my mods folder, am i missing something? or am i just dumb and there are no mods that use this library yet?

Jaxxa

You should not need to install the library separately.
The general convention should be that each mod that used Harmony includes the .dll in its download and you dont have to do anything else.

RemingtonRyder

Because HugsLib doesn't do detouring now, I had a look at Harmony for one of my mods.

I got all the way to releasing the mod on Workshop before I realised "whoops, my detour doesn't actually work."

I'm trying to override DefaultParmsNow in StoryTellerUtility with my own calculation, but it's not happening. As far as I am able to tell (I've been turning on 'Write Storyteller' and monitoring the result using the Debug Inspector) the game is continuing to use the vanilla calculation.

Is Harmony not suitable for this or am I just using it wrong?

Brrainz

Quote from: MarvinKosh on June 07, 2017, 05:13:49 PM
Because HugsLib doesn't do detouring now, I had a look at Harmony for one of my mods.

I got all the way to releasing the mod on Workshop before I realised "whoops, my detour doesn't actually work."

I'm trying to override DefaultParmsNow in StoryTellerUtility with my own calculation, but it's not happening. As far as I am able to tell (I've been turning on 'Write Storyteller' and monitoring the result using the Debug Inspector) the game is continuing to use the vanilla calculation.

Is Harmony not suitable for this or am I just using it wrong?
I can't see why it should not work with that method. Can you post your source?

RemingtonRyder

Sure thing.

I've tried a few things so if it seems off it's because I've been smashing my caveman head at it. ;)

namespace CRC_Reintegrated
{

[StaticConstructorOnStartup]
public static class CRC_Loader
{

static HarmonyInstance harmony;

static void PatchSTU()
{
harmony = HarmonyInstance.Create("net.marvinkosh.rimworld.mod.combatreadinesscheck");



var original = typeof(StorytellerUtility).GetMethod("DefaultParmsNow", BindingFlags.Static | BindingFlags.Public);
var detour = typeof(MarvsStoryTellerUtility).GetMethod("ModdedParmsNow");
harmony.Patch(original, new HarmonyMethod(null), new HarmonyMethod(detour));

//harmony.PatchAll(Assembly.GetExecutingAssembly());

}


}



public static class MarvsStoryTellerUtility
{

const float PowerLimit = 1000;
const float PointsPer1000BuildingWealth = 5.5f;


public static IncidentParms ModdedParmsNow(StorytellerDef tellerDef, IncidentCategory incCat, IIncidentTarget target)
{
if (tellerDef == null)
{
tellerDef = StorytellerDefOf.Cassandra;
}

var incidentParms = new IncidentParms();
incidentParms.target = target;

var map = target as Map;

if (incCat == IncidentCategory.ThreatSmall || incCat == IncidentCategory.ThreatBig)
{

float buildingPoints = 0;

if (map != null)
{
buildingPoints = (map.wealthWatcher.WealthBuildings - 1000) / 1000 * PointsPer1000BuildingWealth;
if (buildingPoints < 0)
{
buildingPoints = 0;
}
}

float colonistPoints = 0;
float armouryPoints = 0;

if (map != null)
{
IEnumerable<Pawn> colonists = map.mapPawns.FreeColonists;
ArmouryUtility.GetColonistArmouryPoints(colonists, target, out colonistPoints, out armouryPoints);
}
else
{
var caravan = target as Caravan;
if (caravan != null)
{
IEnumerable<Pawn> caravaneers = caravan.PawnsListForReading.FindAll((Pawn dude) => dude.IsColonist && dude.HostFaction == null);
ArmouryUtility.GetColonistArmouryPoints(caravaneers, target, out colonistPoints, out armouryPoints);
}
}


//float num3 = (float)Find.MapPawns.FreeColonistsCount * PointsPerColonist;
float difficultyFactor = Find.Storyteller.difficulty.threatScale;
if (difficultyFactor < 0.65)
{
buildingPoints *= 0.5f;
}

incidentParms.points = colonistPoints;
incidentParms.points *= Find.StoryWatcher.watcherRampUp.TotalThreatPointsFactor;
incidentParms.points *= difficultyFactor;
incidentParms.points += buildingPoints;
incidentParms.points += armouryPoints;
switch (Find.StoryWatcher.statsRecord.numThreatBigs)
{
case 0:
incidentParms.points = 35;
incidentParms.raidForceOneIncap = true;
incidentParms.raidNeverFleeIndividual = true;
break;
case 1:
incidentParms.points *= 0.5f;
break;
case 2:
incidentParms.points *= 0.7f;
break;
case 3:
incidentParms.points *= 0.8f;
break;
case 4:
incidentParms.points *= 0.9f;
break;
default:
incidentParms.points *= 1;
break;
}
if (incidentParms.points < 0f)
{
incidentParms.points = 0f;
}
if (incidentParms.points > PowerLimit)
{
if (difficultyFactor > 0.65)
{
incidentParms.points = PowerLimit - 1 + Mathf.Pow(incidentParms.points + 1 - PowerLimit, 0.87f);
}
else
{

incidentParms.points = PowerLimit - 1 + Mathf.Pow(incidentParms.points + 1 - PowerLimit, 0.8f);
}
}
}
else if (incCat == IncidentCategory.CaravanTarget)
{
var caravan = incidentParms.target as Caravan;
IEnumerable<Pawn> playerPawns;
if (caravan != null)
{
playerPawns = caravan.PawnsListForReading;
}
else
{
Faction playerFaction = Faction.OfPlayer;
playerPawns = from x in ((Map)incidentParms.target).mapPawns.AllPawnsSpawned
where x.Faction == playerFaction || x.HostFaction == playerFaction
select x;
}
incidentParms.points = CaravanIncidentUtility.CalculateIncidentPoints(playerPawns);
}


return incidentParms;

}//End of ModdedParmsNow

}//End of class




}//End of namespace

Brrainz

First, I would log out if "original" and "detour" are not null. Because if you made a small mistake there, nothing will happen when one of them is null.

Second, your patch is a Postfix which means that it will run after the original method has run. Is that your intended behavior?

/Andreas

RemingtonRyder

#119
So a postfix patch is used if you definitely want to use the original method but do something afterwards no matter what, prefix patch is used when you may want to use the original method but do something beforehand, but there is the option to skip the original method entirely. Is that right?

It would seem like either method could work, however, since I already replace the original calculation it would probably be best to do a prefix patch and skip the original method.

I did a check for null like you suggested - it seems that there's no problem finding the original or detour methods.

I think my main problem is that I don't quite get how Harmony is supposed to decide when to skip the original method. I've been reading the wiki, and I think I'm starting to understand, but putting it into practise is probably going to require some experimentation.