Let's do some advanced VB.net modding, this involves C# projects: Bannerlib and Harmony.
Bannerlib is a easy Bannerlord API wrapper, we will use it for the Input system to spawn a console for testing
Harmony is a .Net runtime patcher, You can redirect a method procedure/function using this library
Without further ado, let's create our first patch based mod. We also going to learn how to use git along the way
0. first create your project.
follows the tutorial if you're a new starter https://forums.taleworlds.com/index...-to-write-bannerlord-mod-using-vb-net.404953/
1. create a local empty git repo
it doesn't matter how you do it, it can be via command line or a frontend
I use TortoiseGit, because i'm really used to it at work
2. copy code below, create .gitignore file on your .sln directory, and paste the content to the file. This file prevents you accidentally push intermediate garbage into your git repository.
https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
3. now, add your source code and commit them into your git repo.
4. add Harmony and bannerlib as external git module.
https://github.com/sirdoombox/BannerLib.git
https://github.com/pardeike/Harmony.git
Warning!
If your project statically linked a third party library, you have to adhere to the license. For example Bannerlib.Input happens to be AGPL and I'm using the library statically, then my source code should be licenced under AGPL as well.
Don't ask me more about licence, I'm not a ****ing lawyer. Just go to the stackoverflow, if you want debate XD
I use Bannerlib to map certain key to do a custom action in the game, we're going to use it to bind a hidden console developer in the game as patch testing later.
now you should have 2 project in your externals directory
5. we need to create a directory where our compiled dlls will be placed, module manifest, and also write build script to copy our module manifests to the bannerlord module directory (you can skip this and just put your module manifest directly like in the older tutorial, but this is more ideal)
We need to create 2 directory of module manifest: Our mod, and Bannerlib.Input
6. write Submodule.xml manifest in each module directory
So there are 2 module manifest, we need to make sure these directories get copied during compilation, that is on next step.
MyVBPatchProject has to depend on Bannerlib thats why we put <DependedModule Id="BannerLib.Input"/> in MyVBPatchProject manifest
6. now open your solution and right click in your Project (in this case MyVBPatchProject) > properties...
Go to compile and click on Build Events. Now this is where we put script to copy Module manifest directory to bannerlord module directory
in pre build textbox
for example:
In build output path:
example:
whenever you build the program any changes in your module manifest in project directory will be copied automatically to bannerlord module directory
Don't forget to setup your debug parameters (see previous tutorial)
Add your module manifest folder to git and commit any changes.
7. from this onward is the meaty stuff, we will create an entry point for our program mod
OnGameStart procedure is your CampaignBase entry point.
AddBehaviour procedure is where we load our CampaignBase modules (tutorial coming soon)
If you got reference error add Taleworlds.* dll assemblies. don't forget to set On Copy to False so you don't copy bunch of Taleworlds.* dll to your mod binary directory.
7. Now lets add our libraries. Right click on solution and add existing project
7.a add Harmony library first. Your solution look like this. Yeah I know, don't worry about yellow warning in the project, We'll fix that
7.b to add bannerlib, we have to create a paths.csproj where README.MD reside in externals/Bannerlib
this is what my paths.csproj looks like (& char is & in xml):
don't forget to append \ at the end, otherwise you won't be able to load the library into the solution.
After that we can import Bannerlib.Input csproj.
In bannerlib input project, we must add reference Taleworlds.* libraries
7.c Now, let's configure our Harmony project.
Harmony also targets to .NETCore platform, Bannerlord doesn't support that. To remove .NETCore target we need edit csproj manually. To do that we have to unload Harmony project by right clicking on the project > Unload
Now right click again and edit.
Replace that line with this
and in
add this line
so it looks like this
Now this libraries only targets to net framework 3.5, 4.5, 4.7.2, and 4.8.
Save it and reload, now Harmony is ready to be use
7.d Let's link Bannerlib.Input and harmony to our mod project.
Now build the whole solution. There will be 2 folder, BannerLib.Input contains BannerLib.Input.dll and MyVBPatchProject will contain 0Harmony.dll and our dll
8. Add console developer binding to your keyboard.
Create new Developer console Module
Now in OnBeforeInitialModuleScreenSetAsRoot() In main your entry point class add the functionality to load the hotkey like this
Let's test the module to see if your the console key works.
Make sure to tick both module
Now the console and input system are working we can use this to debug and test our Harmony patch.
9. We're going to pick a simple procedure to patch. I think ShowPartySizeDetail at Taleworlds.CampaignSystem.Sandbox.GameComponent.Party is easy enough to debug and test.
Before working with Harmony library, we need to a nuget package for MonoMod.Common -Version 20.4.3.1 in package manager, otherwise it will throw Mono.Cecil not found error during patching. (related issue: https://github.com/pardeike/Harmony/pull/263 )
run this in package manager
Create a friend class, name it Patch
Prefix means this procedure will run first before the original, Postfix the original runs first then followed by your patch
If we assign byRef parameter by any value, Harmony will assume the function returned a value, thus exiting the function. Remember, if you wish to modify a function return value add a byref parameter with matching data type on the last parameter declaration
In main.vb change procedure OnSubModule change it to this
Compile....and finger crossed.....
Now Load a campaign or create a new one.
If you are in campaign, open the devcon with tilde and type this command campaign.show_party_size_limit_detail test
if a messagebox pops up, congrats! your patch works
Ach, that was quite long tutorial, good luck VB brethren.
here's my repo:
If possible, please avoid using Harmony pacther and stick with provided API as this can cause mod conflict or instablity!
.
Bannerlib is a easy Bannerlord API wrapper, we will use it for the Input system to spawn a console for testing
Harmony is a .Net runtime patcher, You can redirect a method procedure/function using this library
Without further ado, let's create our first patch based mod. We also going to learn how to use git along the way
0. first create your project.
follows the tutorial if you're a new starter https://forums.taleworlds.com/index...-to-write-bannerlord-mod-using-vb-net.404953/
1. create a local empty git repo
it doesn't matter how you do it, it can be via command line or a frontend
I use TortoiseGit, because i'm really used to it at work
2. copy code below, create .gitignore file on your .sln directory, and paste the content to the file. This file prevents you accidentally push intermediate garbage into your git repository.
https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
3. now, add your source code and commit them into your git repo.
4. add Harmony and bannerlib as external git module.
https://github.com/sirdoombox/BannerLib.git
https://github.com/pardeike/Harmony.git
Warning!
If your project statically linked a third party library, you have to adhere to the license. For example Bannerlib.Input happens to be AGPL and I'm using the library statically, then my source code should be licenced under AGPL as well.
Don't ask me more about licence, I'm not a ****ing lawyer. Just go to the stackoverflow, if you want debate XD
I use Bannerlib to map certain key to do a custom action in the game, we're going to use it to bind a hidden console developer in the game as patch testing later.
If you're writing a mod, .I think it's a must to make it open because, you know this mods run full permission on your behalf. If the sources are open we can inspect and screen them
now you should have 2 project in your externals directory
5. we need to create a directory where our compiled dlls will be placed, module manifest, and also write build script to copy our module manifests to the bannerlord module directory (you can skip this and just put your module manifest directly like in the older tutorial, but this is more ideal)
We need to create 2 directory of module manifest: Our mod, and Bannerlib.Input
6. write Submodule.xml manifest in each module directory
XML:
<Module>
<Name value="MyVBPatchProject"/>
<Id value="MyVBPatchProject"/>
<Version value="e1.0.3"/>
<SingleplayerModule value="true"/>
<MultiplayerModule value="false"/>
<DependedModules>
<DependedModule Id="BannerLib.Input"/>
</DependedModules>
<SubModules>
<SubModule>
<Name value="MyVBPatchProject"/>
<DLLName value="MyVBPatchProject.dll"/>
<SubModuleClassType value="MyVBPatchProject.Main"/>
<Tags>
<Tag key="DedicatedServerType" value="none" />
<Tag key="IsNoRenderModeElement" value="false" />
</Tags>
</SubModule>
</SubModules>
<Xmls>
</Xmls>
</Module>
XML:
<Module>
<Name value="BannerLib.Input"/>
<Id value="BannerLib.Input"/>
<Version value="e1.0.3"/>
<SingleplayerModule value="true"/>
<MultiplayerModule value="false"/>
<DependedModules>
</DependedModules>
<SubModules>
<SubModule>
<Name value="BannerLib.Input"/>
<DLLName value="BannerLib.Input.dll"/>
<SubModuleClassType value="BannerLib.Input.InputSubModule"/>
<Tags>
<Tag key="DedicatedServerType" value="none" />
<Tag key="IsNoRenderModeElement" value="false" />
</Tags>
</SubModule>
</SubModules>
<Xmls>
</Xmls>
</Module>
So there are 2 module manifest, we need to make sure these directories get copied during compilation, that is on next step.
MyVBPatchProject has to depend on Bannerlib thats why we put <DependedModule Id="BannerLib.Input"/> in MyVBPatchProject manifest
6. now open your solution and right click in your Project (in this case MyVBPatchProject) > properties...
Go to compile and click on Build Events. Now this is where we put script to copy Module manifest directory to bannerlord module directory
in pre build textbox
Bash:
xcopy /F /R /Y /I "$(SolutionDir)<your mod manifest directory folder name>" "<bannerlord module directory>\<your mod manifest directory folder name>"
xcopy /F /R /Y /I "$(SolutionDir)BannerLib.Input" "<bannerlord module directory>\BannerLib.Input"
for example:
Bash:
xcopy /F /R /Y /I "$(SolutionDir)MyVBPatchProject" "F:\Steam\steamapps\common\Mount & Blade II Bannerlord\Modules\MyVBPatchProject"
xcopy /F /R /Y /I "$(SolutionDir)BannerLib.Input" "F:\Steam\steamapps\common\Mount & Blade II Bannerlord\Modules\BannerLib.Input"
In build output path:
Code:
<your bannerlord module directory>\<your mod manifest directory folder name>
Code:
F:\Steam\steamapps\common\Mount & Blade II Bannerlord\Modules\MyVBPatchProject\bin\Win64_Shipping_Client
whenever you build the program any changes in your module manifest in project directory will be copied automatically to bannerlord module directory
Don't forget to setup your debug parameters (see previous tutorial)
Add your module manifest folder to git and commit any changes.
7. from this onward is the meaty stuff, we will create an entry point for our program mod
C#:
Imports TaleWorlds.CampaignSystem
Imports TaleWorlds.Core
Imports TaleWorlds.MountAndBlade
Namespace Global.MyVBPatchProject
Public Class Main
Inherits MBSubModuleBase
Protected Overrides Sub OnSubModuleLoad()
MyBase.OnSubModuleLoad()
End Sub
Protected Overrides Sub OnGameStart(game As Game, gameStarterObject As IGameStarter)
Dim campaign = game.GameType
If (campaign Is Nothing) Then
'Debug.WriteLine("oops!")
Exit Sub
End If
Dim campaignStarter = CType(gameStarterObject, CampaignGameStarter)
AddBehaviour(campaignStarter)
End Sub
Private Sub AddBehaviour(gameInit As CampaignGameStarter)
'gameInit.AddBehavior(New SimpleDayCounter)
End Sub
Protected Overrides Sub OnBeforeInitialModuleScreenSetAsRoot()
MyBase.OnBeforeInitialModuleScreenSetAsRoot()
Dim ver = System.Environment.Version
InformationManager.ShowInquiry(New InquiryData(
"Net Enviroment",
$"running on version {ver}",
True,
False,
"Accept",
"",
Sub()
'Environment.Exit(1)
End Sub,
Sub()
End Sub))
End Sub
End Class
End Namespace
OnGameStart procedure is your CampaignBase entry point.
AddBehaviour procedure is where we load our CampaignBase modules (tutorial coming soon)
If you got reference error add Taleworlds.* dll assemblies. don't forget to set On Copy to False so you don't copy bunch of Taleworlds.* dll to your mod binary directory.
7. Now lets add our libraries. Right click on solution and add existing project
7.a add Harmony library first. Your solution look like this. Yeah I know, don't worry about yellow warning in the project, We'll fix that
7.b to add bannerlib, we have to create a paths.csproj where README.MD reside in externals/Bannerlib
XML:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<GameBins>Path to the main bin/Win64_Shipping_Client folder\</GameBins>
<BuildPath>Path to the desired build folder\</BuildPath>
</PropertyGroup>
</Project>
this is what my paths.csproj looks like (& char is & in xml):
XML:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<GameBins>F:\Steam\steamapps\common\Mount & Blade II Bannerlord\bin\Win64_Shipping_Client\</GameBins>
<BuildPath> F:\Steam\steamapps\common\Mount & Blade II Bannerlord\Modules\BannerLib.Input\bin\Win64_Shipping_Client\</BuildPath>
</PropertyGroup>
</Project>
don't forget to append \ at the end, otherwise you won't be able to load the library into the solution.
After that we can import Bannerlib.Input csproj.
In bannerlib input project, we must add reference Taleworlds.* libraries
7.c Now, let's configure our Harmony project.
Harmony also targets to .NETCore platform, Bannerlord doesn't support that. To remove .NETCore target we need edit csproj manually. To do that we have to unload Harmony project by right clicking on the project > Unload
Now right click again and edit.
XML:
<TargetFrameworks>net35;net45;net472;net48;netcoreapp3.0;netcoreapp3.1</TargetFrameworks>
XML:
<TargetFrameworks>net35;net45;net472;net48;</TargetFrameworks>
XML:
<ItemGroup></ItemGroup>
XML:
<Reference Include="netstandard" />
XML:
<ItemGroup>
<None Include="..\LICENSE" Pack="true" PackagePath="" />
<None Include="..\HarmonyLogo.png" Pack="true" Visible="false" PackagePath="" />
<Reference Include="netstandard" />
</ItemGroup>
Save it and reload, now Harmony is ready to be use
7.d Let's link Bannerlib.Input and harmony to our mod project.
Now build the whole solution. There will be 2 folder, BannerLib.Input contains BannerLib.Input.dll and MyVBPatchProject will contain 0Harmony.dll and our dll
8. Add console developer binding to your keyboard.
Create new Developer console Module
C#:
Imports System.Runtime.InteropServices
Imports BannerLib.Input
Imports TaleWorlds.InputSystem
Namespace Global.MyVBPatchProject
Public Module DeveloperConsole
Private m_IsStarted = False
Private Declare Sub toggle_imgui_console_visibility Lib "Rgl.dll" Alias "?toggle_imgui_console_visibility@rglCommand_line_manager@@QEAAXXZ" (x As UIntPtr)
Public Sub LoadTheHotkeys()
If (m_IsStarted) Then Exit Sub
m_IsStarted = True
Dim hotkey = HotKeys.Create("MyVBPatchProject")
Dim key = hotkey.Add("MyVBPatchProject_DeveloperConsole",
InputKey.Tilde,
HotKeyCategory.Action,
"Developer console",
"Toggles a developer console"
)
Dim lambda As Func(Of Boolean) = Function()
Return True
End Function
key.WithPredicate( lambda )
key.WithOnPressedAction(
Sub()
toggle_imgui_console_visibility(New UIntPtr(1))
End Sub)
hotkey.Build()
End Sub
End Module
End Namespace
Now in OnBeforeInitialModuleScreenSetAsRoot() In main your entry point class add the functionality to load the hotkey like this
C#:
Protected Overrides Sub OnBeforeInitialModuleScreenSetAsRoot()
MyBase.OnBeforeInitialModuleScreenSetAsRoot()
DeveloperConsole.LoadTheHotkeys() '<----
Dim ver = System.Environment.Version
InformationManager.ShowInquiry(New InquiryData(
"Net Enviroment",
$"running on version {ver}",
True,
False,
"Accept",
"",
Sub()
'Environment.Exit(1)
End Sub,
Sub()
End Sub))
End Sub
Let's test the module to see if your the console key works.
Make sure to tick both module
Now the console and input system are working we can use this to debug and test our Harmony patch.
9. We're going to pick a simple procedure to patch. I think ShowPartySizeDetail at Taleworlds.CampaignSystem.Sandbox.GameComponent.Party is easy enough to debug and test.
Before working with Harmony library, we need to a nuget package for MonoMod.Common -Version 20.4.3.1 in package manager, otherwise it will throw Mono.Cecil not found error during patching. (related issue: https://github.com/pardeike/Harmony/pull/263 )
run this in package manager
Bash:
Install-Package MonoMod.Common -Version 20.4.3.1
Create a friend class, name it Patch
C#:
Imports HarmonyLib
Imports TaleWorlds.CampaignSystem.SandBox.GameComponents.Party
Imports TaleWorlds.Core
Namespace Global.MyVBPatchProject
<HarmonyPatch(GetType(DefaultPartySizeLimitModel), "ShowPartySizeDetail")>
Friend Class Patch
Public Shared Sub Postfix(strings As List(Of String), ByRef __result As String)
InformationManager.ShowInquiry(New InquiryData(
"Patch works",
"if you see this, the patch works",
True,
False,
"Accept",
"",
Sub()
'Environment.Exit(1)
End Sub,
Sub()
End Sub))
__result = "hello world"
End Sub
End Class
End Namespace
If we assign byRef parameter by any value, Harmony will assume the function returned a value, thus exiting the function. Remember, if you wish to modify a function return value add a byref parameter with matching data type on the last parameter declaration
In main.vb change procedure OnSubModule change it to this
C#:
Protected Overrides Sub OnSubModuleLoad()
MyBase.OnSubModuleLoad()
Dim harmony = New Harmony("calradia.MyVBPatchProject.example")
harmony.PatchAll()
End Sub
Compile....and finger crossed.....
Now Load a campaign or create a new one.
If you are in campaign, open the devcon with tilde and type this command campaign.show_party_size_limit_detail test
if a messagebox pops up, congrats! your patch works
Ach, that was quite long tutorial, good luck VB brethren.
here's my repo:
GitHub - admiralnelson/bannerlord-vbnet-harmony-patching
Contribute to admiralnelson/bannerlord-vbnet-harmony-patching development by creating an account on GitHub.
github.com
If possible, please avoid using Harmony pacther and stick with provided API as this can cause mod conflict or instablity!
.
Last edited: