[LIB] Harmony v1.2.0.1

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

Previous topic - Next topic

Nightinggale

Quote from: RawCode on November 08, 2017, 03:55:55 AMHarmony is wrapper over unsafe and it implements exactly same single line payload that used in early code injection.
I'm not entirely sure I agree with your statement here. The old approach is to claim the method call and prevent other mods from accessing that particular method. Harmony allows multiple mods to attach to the same method. Sure it's not without danger, but if you look in the help section, there was recently a thread about using Transpillar in a method where another mod use Transpillar and on how to make the two mods compatible. The fact that it eventually ended up working as intended shows that Harmony is far superior to not using Harmony. Sure it could be considered unsafe, but at the same time allowing such "unsafe and conflict prone" code allows compatibility, which would otherwise be impossible.

I don't really agree with the approach of returning false in Prefix though. I fail to see the difference between that and just redirecting the call and claim it for your mod. On the other hand I have had great success with adding multiple Postfix to the same method and since they append to the output list rather than modifying it, conflicts are avoided without even considering conflicts, just the way it should ideally be. Each such mod will not have to even consider the existence of another mod attaching itself to the same method.

Quote from: RawCode on November 08, 2017, 03:55:55 AMas for iteration\yeld return - Zenthar great job backfired, as it hide actual implementation from developer below tick layer of syntax sugar.
It's possible to automate recovery and redirect injections, but this harmful in longrun as developers must have at least basic understanding of C# internals and must be aware about syntax sugar and how real CIL looks like.
That's actually a really good point. If I had not used Zenthar's fork of ILSpy, I might not have had the amount of problems I had. I will remember this if I run into problems in the future.

Quote from: CannibarRechter on November 08, 2017, 06:30:20 AM
I actually don't like the decorator methods used by Harmony. It invited the developer to eschew proper exception handling. If the decorator method wesn't used, and you always had to call the manual methods, the pardeike probably would have had exceptions thrown all the way through to the caller. That's the way they should be. Instead, you often end up with silent failures. It's a bit of a debugging disaster.
I'm not completely following you. When will you lose exceptions? What will prevent you from using Transpillar to just inject a method call and then deal with that method including exceptions in a method, which is not part of Transpillar?

Or are you saying that going through any Harmony method (Prefix and Postfix included) will silence the exceptions in your methods?
ModCheck - boost your patch loading times and include patchmods in your main mod.

CannibarRechter

> Or are you saying that going through any Harmony method (Prefix and Postfix included) will silence the exceptions in your methods?

Harmony ITSELF doesn't throw exceptions when it should. I believe this is because of the attributes (decorators); these don't afford the programmer a useful place to catch exceptions if thrown. While the design is pretty and convenient, I don't believe this worth the trade.
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

Dingo


RawCode

@Nightinggale
https://github.com/pardeike/Harmony/blob/master/Harmony/ILCopying/Memory.cs

Multiple mods that try full replacement will conflict anyway, there are no ways around, this is how things works.
Same for bytecode injections.

People must understand and know truth about system and how exactly it works in order to use it in efficient manner.
When people do not understand internals, they phone to make wrong assumption, this is bad in longrun.

As for exceptions, try checking stacktrace of your injection, all Prefix and Postfix methods are called via reflection hack from inside of gate method one by one, it's quite easy to wrap everything into try catch and process exceptions in orderly manner.

Nightinggale

Quote from: RawCode on November 08, 2017, 09:16:14 AMMultiple mods that try full replacement will conflict anyway, there are no ways around, this is how things works.
Same for bytecode injections.
Obviously conflicts can't always be avoided and I don't think anybody with any insights at all will ever claim otherwise. My point is that you can write a Prefix and return false and you can write a Transpillar to remove the method contents entirely and just make a method call to a new method where you write the same as you would have in Prefix. Both will end up being complete replacements, but only the Transpillar will not conflict with another mod using Postfix. There are also cases where a light modification of a method with Transpillar (like changed conditions in if statement) will be compatible with other mods using Transpillar on the same method. In other words using Prefix and returning false seems to be compatibility hostile compared to other options.

It goes without saying that some mods will conflict by design. Imagine having two people giving directions for where a car should go. One says left and the other says right and then they both assume the car to do both. It's the same with mods and it only make sense to talk about compatible coding if the mods do not conflict on the concept level and I'm talking about cases where compatibility is possible if done right, but where it's possible to write code, which will conflict. I don't think it will make sense to talk about writing compatible code in any other cases. However you have to remember that while you might not have to consider being compatible with anything, the mod somebody else makes next month might have to be compatible with your mod, meaning the new mod might require you to have compatibility friendly code and not just return false in Prefix.
ModCheck - boost your patch loading times and include patchmods in your main mod.

CannibarRechter

> Prefix and return false and you can write a Transpillar to remove the method contents entirely

Almost no modders will be implementing transpilers: they would be high end work even for a software engineering professional (retired software engineering professional here). So the practical thing for most modders to do is use postfix methods when possible, and prefix methods (with false return values) when they have to. If a few stretch themselves and learn transpilers, great, but that's not gonna happen very often, let's be honest.
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

Nightinggale

Quote from: CannibarRechter on November 08, 2017, 11:21:47 AMAlmost no modders will be implementing transpilers: they would be high end work even for a software engineering professional
That's my prediction as well. People will use high level if they can get away with it. However that doesn't change the fact that Transpillar is a better option than Prefix returning false when it comes to mod compatibility.

Perhaps it would be a good idea to introduce a new hooking method, which provides a method with the same arguments and return value as the vanilla one and then the modded one is called instead. It's like detouring, except it will not conflict with mods using Prefix and Postfix. While I prefer people to not use it, it will still be better than using Prefix and returning false.

Alternatively it should be made possible to make a Prefix, which can skip vanilla only or both vanilla and other mods.
ModCheck - boost your patch loading times and include patchmods in your main mod.

Brrainz

Guys, keep to the facts. A quick search on GitHub reveals over 20 pages! of search results of people writing transpiler code:

https://github.com/search?l=C%23&q=IEnumerable%3CCodeInstruction%3E&type=Code

Nightinggale

Point well taken. What I was actually thinking of when writing my last post is the group of modders, which won't or rather aren't skilled enough to use Transpillar. I'm not really concerned with the number, but I should have pointed out that I disagree with "Almost no modders". My concern is that a number of mods relies on returning false in Prefix and that it's the least compatible approach. Another concern is the fact that some mods still use detouring. My thinking is that if a "detour" option exist in Harmony and that it support that mods can use Prefix and Postfix, then even though it isn't ideal, it will still be better than what they use today.

I'm not saying Harmony is broken. The problem is as always people are surprisingly good at figuring out how to break otherwise good solutions.
ModCheck - boost your patch loading times and include patchmods in your main mod.

Brrainz

The community and the major modders had the exact same concern. Interestingly, it turned out to not be a technical problem. It's educational and that you don't solve with technology. Instead we focused on spreading knowledge and helping out those who struggle or are ignorant. This is exactly what the discord servers are for and they do a pretty good job.

SargBjornson

Hi guys!

I have a series of doubts, and some of them may sound quite stupid. I have a little bit of background as a programmer, but my usual approach is throwing things and seeing what sticks...

So first, what I'm trying to do: I want to add to the Genetic Rim mod the possibility of controlling directly ("drafting") some of the tamed animals in the mod. Because that would be so cool. So I looked around and saw a mod called DraftAnything that hasn't been updated since a15, but the main ideas, I think, will still stand.

If I understand correctly, the best way to proceed would be to modify with Harmony the methods AddAndRemoveDynamicComponents in RimWorld.PawnComponentsUtility, GetGizmos in Verse.Pawn and CanTakeOrder in RimWorld.FloatMenuMakerMap (I'm not smart enough to figure this out, Shaun McFall did in that mod).

However, in his mod he used an "Inject" method, which if I'm interpreting this correctly just changes the vanilla method with his own (was that an old Harmony method?). This will obviously cause problems with other mods. Looking at the documentation, I infer that I should use a Postfix to add some code to the end of the methods I want to change, or a Transpiler, which either swaps code for other code, or is slang for a transexual caterpillar.

So, apart from that last idiocy, am I in the right track?

PS: the mod: https://ludeon.com/forums/index.php?topic=26830.0

Brrainz

Yes. You are on the right track. Although not the simplest example to start with. Remember the pseudo code on the Harmony wiki that explains how the patching works and the information flow through the prefix/postfix methods. Also: if you want to go beyond prefix/postfix stuff but still not feel for the full complexity of a complete transpiler, you should look into the readymade transpilers that come with Harmony, like the MethodReplacer that allows you to replace a method with your own but inside the method that you patch. If you're smart you can use that to insert code inside a method without an understanding of IL.

Nightinggale

Quote from: pardeike on November 29, 2017, 02:26:30 PMyou should look into the readymade transpilers that come with Harmony, like the MethodReplacer
Is there a list of Harmony features like that anywhere? I missed the existence of this one until right now. It looks to me like it totally beats both then redirection and then Prefix+return false approaches since both supports Postfix just fine.

In fact is there any Transpiler documentation other than the wiki from the first post? It seems that the most useful I can find is working examples of code, but looking at Harmony source code, it looks like they are only scratching the surface of all the features.

Also how soon can Transpiler be applied? It looks like all dll files are loaded in Verse.LoadedModManager.LoadAllActiveMods() and obviously Harmony will not work before being loaded. However can it be used in this method after the dll files have been loaded or will it only work on methods called after harmony is loaded?
ModCheck - boost your patch loading times and include patchmods in your main mod.

Brrainz

No, there is no such list. Transpilers are ultimately simple in design as all they do is take a list of codes and spit out a list of codes. The buildin transpilers all exist in the Transpilers class and the best I can offer is this transpiler tutorial gist:

https://gist.github.com/pardeike/c02e29f9e030e6a016422ca8a89eefc9

Other than that, try to learn how IL works and you will not regret it. It's a rather simple language with a stack and easy to reason about. Microsoft has good documentation on it.

Applying patches (pre- post- or transpiler) works at any time. It's just that the execution of the method you patch will be the original one until you patch it and only in that sense the timing of the patch is relevant. Harmony truely changes the action of a method at runtime.

SargBjornson

Oh my god, I got it! I'm right now making a Bearodile run at my command around the map munching on things! Thanks a lot, Harmony seems super powerful and it is really not very difficult to use, even if you don't quite know what you are doing hehe