[Release v1.0.0] NoHarmony : mod loader for bannerlord

Midnightknight

Regular
WBWF&SVC
Best answers
4
Hello everyone,
while starting to mod a little bit and looking fortips on how to do things i most of time ended with with people telling i should use harmony to get things to work. Since Bannerlord is supposed to be a game allowing you to mod a lot of things it looked a bit suspect to me and i investigated a little more and came up with this solution.

Before we start.
NoHarmony is not by any mean something to prevent you from using harmony, i have a deep respect for the people who developed this tool. But Harmony should be used as a last resort, when nothing else worked, cause it might bring more issues than it would actually solve. So this mods aims to reduce harmony's use in banner lord and keep it for really unsolvable issues.

How it works.
NoHarmony loader is available as a DLL or a C# source file, add it to your project and derive your submodule classe from NoHarmonyLoader instead of MBSubModuleBase.
You will have two methods to override :
NoHarmonyInit() that you can leave as it is, (it's used for configuration and to load things before the main menu)
NoHarmonyLoad() where you will put models and behaviors you want to load in the game. (using a collection of methods including "AddBehavior" "ReplaceBehavior" "AddModel" "ReplaceModel")

Features.
  • Compatibility, NoHarmony will manage most of the coding and updates into a single DDL. So you won't need to rewrite your class if Taleworld change their calls, simply update the DLL when the new version is out.
  • Logging and error control, choose how much control you want over the error control and the logs. It can go from completely silent to track for you every model and behavior and the moment they are loaded in the game.
  • Easy to use, I have made it as simple as possible to use and preventing most errors you can do while making your own submodule class.
  • Documentation, .NoHarmony methods are commented and explained to allows beter use and understanding, the wiki will be filled to go further and makes more in depths use of it's feature or the SubmoduleBase class TW offers.


External links.

GitHub project page

NoHarmony wiki (on GitHub)

Latest release available here

Research post about MBSubModuleBase
 
Last edited:

Midnightknight

Regular
WBWF&SVC
Best answers
4
Changelog

  • Added some comments.
  • Added minor code optimisation.
  • Made a nuGetPackage.

  • Added a Remove method to remove a model type from the game, to use with caution.
  • Base method calls have been made where necessary to ensure compatibility with any future update TW might bring to the SubModuleBase class.
  • All reflexion have been removed and replaced by delagates, making the code easier to maintain and understand.
  • Better error control using typed delegates.
 
Last edited:

mr_mouflon

Recruit
Best answers
3
Thanks for this, it looks super helpful. I've been a bit shy of using harmony, just because it seems like it could cause a catastrophe if you don't know what you're doing, and I don't really know what I'm doing.

Question tho, how exactly is this less 'shady' as you put it than harmony? Just that your way of replacing the vanilla code is less intrusive than harmony's patching?
 

Midnightknight

Regular
WBWF&SVC
Best answers
4
Well i realized i was a bit fast on explanation and i'm building on a clearer git with an example using a really simple mod to show how it works and load things easily.

How M&B2 works, basically, it offers the users skeletons for various modules that are divided into models and behavior. You should then derive a class from these skeletons to make a fully fledged module. All those now working modules are then added to a collection the game will be looking for.

Now what my mod allows is to add those new modules to the collection (like M&B allows it already) with more error and log control, or look in the said collection for a module that is already added (By another modder or a default one or even a placeholder the game might have) and replace it by your own. This might look shady but it's not that much. The game already have a sort of priority when you add two modules that do the same thing, but it's not always correctly handled and might bring strange results. When you use a module that completely override the smithy feature, it's best to remove the default one. It's even more useful to remove this default model when your is derived from this default model, cause it will have all it's features + a few you added. If the default model is changed, let's say by taleworld, the module derived from it will be changed too to some extend (Except if the developer rewrote everything).

What Harmony does on the other hand is plug itself at runtime into a small part of the memory, intercept all that is coming, and change it before giving it back to the program. The two advantages are, first it's faster than to make a full module, handle the load order and charge everything to the game with possible conflicts. Secondly, it can intercept almost anything, even what it is not supposed to.

So to highlight differences, a mod loader first do not execute at runtime and is less prone to have M&B crash in the middle of a game, but it will most likely do at loading. Secondly debugging a mod using harmony is a headache, the one who made the mod have the tool to do it, but for Taleworld of other modders that have conflicts with, it's just a pain. With a loader, you know what is loaded and when, you can understand possible problemes simply by looking what model type or behavior is loaded. If you loaded a smithing model, you know it will modify smithing. With harmony you can make a settlement model that modifies smithy or even variables that are not supposed to be accessible to modders.
And finally Harmony can take priority over any other mod you might load, what will bring issues when 2 harmony mods wants to have control on the same part of the code, but also for people not using it. You can override or try to fix a bug as much as you want, if harmony is intercepting all the O/I of this part of the code all you do will have no effect, what wouldn't happen with a mod loader, cause it do not modify the code on runtime.

So i tried to explain simply things, all might not be completely accurate, there are also issues currently with loading mods the way taleworld wants it to, cause the game is still early access right ... But i think it will help you understand the main differences and why harmony should not be overused, especially on a game that is still SO MUCH work in progress. I'm fine with Harmony, but seeing it used to load a simple model is really not wise, making the game unstable and unmoddable for nothing.
 
Last edited:

Paige 404

Recruit
Best answers
0
As a developer with no C# experience, I had been wondering how Harmony worked and if it might present some strange problems. That makes a lot of sense. It sounds like it could impact performance too, especially if you have several mods all using Harmony.

This is very cool. Are you planning to set up any versioning/tagged DLL releases for NoHarmony? Would make it simpler for folks to use if they have an option to just download the latest build from github, especially if you plan to add more features to it or need to change things as the game DLLs change in development.
 

Midnightknight

Regular
WBWF&SVC
Best answers
4
Yes all of this is planned.
I use it for my upcoming mod and for testing. I'm still learning how things works and will improve it and make releases when i feel it works completely how it should.
 

Ster

Veteran
Best answers
1
Harmony should be used as a last resort, when nothing else worked, cause it might bring more issues than it would actually solve.
Agreed. This is one of the reasons I want to get the documentation updated as soon as possible. Too many people are using Harmony for things that don't require it, either out ignorance or or laziness (convenience). Not going to make a comment on the mod loader, just wanted to say that I agree with you that Harmony is being overused right now.
 

Midnightknight

Regular
WBWF&SVC
Best answers
4
Agreed. This is one of the reasons I want to get the documentation updated as soon as possible. Too many people are using Harmony for things that don't require it, either out ignorance or or laziness (convenience). Not going to make a comment on the mod loader, just wanted to say that I agree with you that Harmony is being overused right now.
Thank you for your support. I felt i was a bit the only one thinking this way, but it's nice to see i'm not. Too much Harmony kills Harmony, and i fear we will have many issues pretty soon about it.


What's wrong with the module system that TW have already provided?
Well nothing is wrong with TW module system, except that it might be complicated for people not understanding how the loading system is made. The documentation is little by little put uptodate about it, so it's easier to know where and when to use what function but i see in a few mods errors about models loaded too early or too late, bringing possible issues. So i wanted to have something simpler to use, that could put the right thing at the best place possible and give log control about what is happening. But i wanted to also let people still make their own cooking if they chose to.

So nothing really wrong with basic loader, if you know perfectly what you are doing, NoHarmony is not really useful, but if you want to log in a file what is loaded, possible errors, duplicates behaviors and models and be able to replace some models or behaviors without having to rewrite the code once again, NoHarmony is here to help you. At least i hope it will be and get better version after version.

Edit : The only things that could go wrong with TW actual system is that it's subject to change. Some method will be changed cause using wrong types or definitions and make most mods incompatible. If you use a loader like NoHarmony you simply have to update the DLL and all works again without having to change anything else.
 
Last edited:

Unterkatze

Recruit
Best answers
0
I want to disable grievance of companions.
In the class CompanionGrievanceBehavior : CampaignBehaviorBase I override the two only public functions RegisterEvents() and SyncData(IDataStore dataStore). Then i replace the behavior with noharmony:
ReplaceBehavior<CompanionChilledGrievanceBehavior, CompanionGrievanceBehavior>();

The overriden RegisterEvents() function gets executed. The SyncData function not. And it has no effect, companions are still complaining. Any idea? Is this a case where harmony is needed to override the private functions?
 

Midnightknight

Regular
WBWF&SVC
Best answers
4
I think the events are added before NoHarmony replace the behavior. CompanionGrievanceBehavior is an engine behavior that is called before all the others and you will need to remove those registrations or i think it might even crash the game.
Behaviors are easy to add (new ones) but tricky to remove and that what i'm currently working on with NoHarmony, a way to remove those events in a clean way.
Again i'm working on campaign events right now and made a list of the public function exposed but i need a bit more time to figure all about it. I will notify as soon as i can address the issue.

Maybe you can make some test with ClearListeners (object) but i don't know how to clean the right one for now.
 

Midnightknight

Regular
WBWF&SVC
Best answers
4
I want to disable grievance of companions.
In the class CompanionGrievanceBehavior : CampaignBehaviorBase I override the two only public functions RegisterEvents() and SyncData(IDataStore dataStore). Then i replace the behavior with noharmony:
ReplaceBehavior<CompanionChilledGrievanceBehavior, CompanionGrievanceBehavior>();

The overriden RegisterEvents() function gets executed. The SyncData function not. And it has no effect, companions are still complaining. Any idea? Is this a case where harmony is needed to override the private functions?
Ok i think i have a workaround without harmony but need some testing.

Basically when NoHarmony replace a behavior, it looks for it in the collection, so it could get the instance ID of the behavior this way. And then it can clean the registered event list using ClearListeners(instanceID) before adding it's own. Will need to update it this way.
 

Midnightknight

Regular
WBWF&SVC
Best answers
4
Well after trying a lot of things and fixing NoHarmony i realized CompanionGrievanceBehavior was simply not listed in the behaviors loaded at startup and so NoHarmony couldn't find it nor the events it's registering.

14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.LordConversationsStoryModeBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.TrainingFieldCampaignBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.TutorialPhaseCampaignBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.FirstPhaseCampaignBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.SecondPhaseCampaignBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.ThirdPhaseCampaignBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.StoryModeTutorialBoxCampaignBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.Quests.TutorialPhase.TravelToVillageTutorialQuestBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.Quests.TutorialPhase.TalkToTheHeadmanTutorialQuestBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.Quests.TutorialPhase.PurchaseGrainTutorialQuestBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.Quests.TutorialPhase.RecruitTroopsTutorialQuestBehavior !!!
14/04/20 23:31:26.328 > !!![Error] StoryMode.Behaviors.Quests.TutorialPhase.LocateAndRescueTravellerTutorialQuestBehavior !!!
14/04/20 23:31:26.329 > !!![Error] StoryMode.Behaviors.Quests.TutorialPhase.FindHideoutTutorialQuestBehavior !!!
14/04/20 23:31:26.329 > !!![Error] StoryMode.Behaviors.Quests.FirstPhase.BannerInvestigationQuestBehavior !!!
14/04/20 23:31:26.329 > !!![Error] StoryMode.Behaviors.Quests.FirstPhase.MeetWithIstianaQuestBehavior !!!
14/04/20 23:31:26.329 > !!![Error] StoryMode.Behaviors.Quests.MeetWithArzagosQuestBehavior !!!
14/04/20 23:31:26.329 > !!![Error] StoryMode.Behaviors.Quests.FirstPhase.IstianasBannerPieceQuestBehavior !!!
14/04/20 23:31:26.329 > !!![Error] StoryMode.Behaviors.Quests.FirstPhase.ArzagosBannerPieceQuestBehavior !!!
14/04/20 23:31:26.330 > !!![Error] StoryMode.Behaviors.Quests.SecondPhase.WeakenEmpireQuestBehavior !!!
14/04/20 23:31:26.330 > !!![Error] StoryMode.Behaviors.Quests.SecondPhase.AssembleEmpireQuestBehavior !!!
14/04/20 23:31:26.330 > !!![Error] StoryMode.Behaviors.Quests.SupportKingdomQuestBehavior !!!
14/04/20 23:31:26.330 > !!![Error] StoryMode.Behaviors.Quests.FirstPhase.CreateKingdomQuestBehavior !!!
14/04/20 23:31:26.330 > !!![Error] StoryMode.Behaviors.Quests.SecondPhase.ConspiracyProgressQuestBehavior !!!
14/04/20 23:31:26.330 > !!![Error] StoryMode.Behaviors.Quests.SecondPhase.StopConspiracyQuestBehavior !!!
14/04/20 23:31:26.330 > !!![Error] StoryMode.Behaviors.Quests.ThirdPhase.DefeatTheConspiracyQuestBehavior !!!
14/04/20 23:31:26.331 > !!![Error] SandBox.LordConversationsCampaignBehavior !!!
14/04/20 23:31:26.331 > !!![Error] SandBox.Source.Conversations.HideoutConversationsCampaignBehavior !!!
14/04/20 23:31:26.331 > !!![Error] SandBox.Source.Towns.CommonAreaCampaignBehavior !!!
14/04/20 23:31:26.331 > !!![Error] SandBox.Source.Towns.CommonTownsfolkCampaignBehavior !!!
14/04/20 23:31:26.331 > !!![Error] SandBox.Source.CampaignComponents.CompanionRolesCampaignBehavior !!!
14/04/20 23:31:26.331 > !!![Error] SandBox.Source.Towns.GuardsCampaignBehavior !!!
14/04/20 23:31:26.331 > !!![Error] SandBox.Source.Towns.BoardGameCampaignBehavior !!!
14/04/20 23:31:26.331 > !!![Error] SandBox.Source.Towns.WorkshopsCharactersCampaignBehavior !!!
14/04/20 23:31:26.332 > !!![Error] SandBox.Source.Towns.TradersCampaignBehavior !!!
14/04/20 23:31:26.332 > !!![Error] SandBox.Source.Towns.ArenaMaster !!!
14/04/20 23:31:26.332 > !!![Error] SandBox.Source.Towns.CommonVillagersCampaignBehavior !!!
14/04/20 23:31:26.332 > !!![Error] SandBox.CampaignBehaviors.FacialAnimsTestConversationsBehaviour !!!
14/04/20 23:31:26.332 > !!![Error] TaleWorlds.CampaignSystem.NewsManager !!!
14/04/20 23:31:26.332 > !!![Error] SandBox.CampaignBehaviors.Towns.IssuesCampaignBehavior !!!
14/04/20 23:31:26.332 > !!![Error] SandBox.Quests.QuestBehaviors.RivalGangMovingInIssueBehavior !!!
14/04/20 23:31:26.333 > !!![Error] SandBox.Quests.QuestBehaviors.FamilyFeudIssueBehavior !!!
14/04/20 23:31:26.333 > !!![Error] SandBox.Quests.QuestBehaviors.NotableWantsDaughterFoundIssueBehavior !!!
14/04/20 23:31:26.333 > !!![Error] SandBox.Quests.QuestBehaviors.TheSpyPartyIssueQuestBehavior !!!

It looks engine Behaviors are stored somewhere else and will have to figure this so we can go further into modding, cause having so many hidden is really an issue.



Edit: Alright, found a way to access the second collection of behaviors with the help of a friend. Should patch this in the next update. This time almost anything in the game can be accessed without the use of harmony, but it might require a bit more work from the developers as some initialisation is required.
 
Last edited:
Best answers
0
Great work! Have not downloaded yet to look through the code,

Will this work on any static method such as collision detection code and melee callbacks?
 

Midnightknight

Regular
WBWF&SVC
Best answers
4
Well i need to update it to 1.0.0 for it to fully work and i was a bit exhausted yesterday after all the work, so will ask a few hours or days to have a massive overhaul and have it do all the work and remain compatible.

Will this work on static method, no. As the name suggest it a static method shouldn't be changed and C# won't allow it. Will it work on collision detection and melee callbacks, i do not know, i never worked on this part of the game so far so i can't make an answer but ...
It will allow you to add and remove behaviors and models, it will also allow you to remove registered events linked to them and replace them. So depending how they coded the game you might do this without changing the static methods. For example you will be able to remove all the calls made to a module by "OnMeleeHit" and unload the module, load yours and make OnMeleehit call the method of your choice in your module instead. Or not call anything at all if you wish, but it will more likely bug if you do not replace it.

But once again i can't be 100% sure as it's currently testing and even if we had awesome results, that do not mean we can do anything with it. For now i know we won't be able to change gamemenus already loaded with the upcoming change, but well, we are still learning how everything works so ...
 
Best answers
0
Yes we are learing how it all works! And that is the fun part!

Luckily it looks like Taleworlds set up most functionallity with abastact Methods and "sub models" to make things very modular. Without requiring to patch things with Harmony
 

Unterkatze

Recruit
Best answers
0
When one mod overrides a function through inheritance with noHarmony. And another mod overrides another function in the same class through inheritance. Will this two mods be compatible?
 

Midnightknight

Regular
WBWF&SVC
Best answers
4
The game do not handle more than one model of a given type. For behavior you might be able to make 2 behaviors inheriting the same module and the game using the two for different things. But will require a LOT of tweaking with how events are handled.

By the way i worked on your companion grievance and made a new experimental version of no Harmony in lite version that made it work just perfectly.
Here it is. I joined the CS code of the experimental version but you can use the submodule directly for your game until i release the full version.