Note This guide is incomplete.
This guide is a collection of best practices for writing mods, it is heavily focused on DLL mods but some parts can still be applied to the JSON part of mods.
BattleTech is based on Unity 2018.4.2 with Mono based on .NET Framework 4.7.2 . Microsoft still supports the mentioned .NET version with its latest development tools.
Use the latest and greatest Visual Studio you can find. As of this writing it is Visual Studio 2022.
- Visual Studio 2022 community is for free. Do not even think about using Visual Studio Code, its not worth it.
- Newer Visual Studios and newer C# versions can be used, as long as they can target the old .NET Framework 4.7.2 used by the game.
- If you have the money, use Resharper from JetBrains.
Rider is the C# IDE of the JetBrains that integrates similar functionality as Resharper. It still requires the build tools from MS to be installed. Rider is part of the family of products covering IntellJ, WebStorm, etc.. if you know those you can already guess what you get.
- Code inspections are very good, it notifies you on bad code practices, issues with your code or just some inconsistent styling.
- You learn C# quickly because Rider/Resharper provide refactoring suggestions as you write code.
- Manual code refactoring is also great.
- Git integration with preview and commit + stashing is also very good.
All mods need to know where to find the game' assemblies, this is done by setting the project property BattleTechGameDir
to point to the game's directory.
Instead of configuring it per project, on can set the property in a special file called Directory.Build.props
.
When a projects gets opened/built, the build tools search for such a file in the current directory and any parent directory.
So usually a developer sets this file up only once in a directory where all sources are located somewhere below
(putting it in C:/
would be a valid but an extreme case).
Example contents
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BattleTechGameDir>C:\Games\Steam\SteamApps\common\BATTLETECH\</BattleTechGameDir>
</PropertyGroup>
</Project>
Note Linux users should note that
Directory.Build.props
is case-sensitive. If you find the project won't compile for you, make sure the case is correct.
Use the latest SDK style csproj format. If your csproj file starts with <Project Sdk="Microsoft.NET.Sdk"
you are using it already.
Many older mods still use the old style csproj files. Don't copy those, use SDK style csproj formats!
- Recommended by Microsoft: When you create projects with old .NET Framework it uses the new SDK style csproj format.
- Removes the need to list all files in the csproj file, in SDK style csproj files there are wildcards to include files. This avoids loosing files in your projects while they are still in your repository. Also allows to collaborate better to due less merge conflicts.
- Removes a lot of boilerplate!
- Create empty project in old format: 50 lines in csproj + AssemblyInfo.cs + one line more for every source code file
- Create empty project in new format: 16 lines in csproj + NO AssemblyInfo.cs since autogenerated + NO additional line for every source code due to wildcard.
While .NET Framework 4.7.2 came with C# 7.3, you can use the latest C# (as of this writing 11) with albeit less features. Here are the highlights:
- Nullable reference types: When enabled, all reference types (e.g. string) are assumed to be wanted to be not-null. Assigning null or not initializing a field variable will lead to compiler warnings. Modern languages (Kotlin/Swift) have this already, C# has it too. Only caveat is that while we can use this, they are some language improvements regarding this feature that are not available in our old .NET Framework version.
- Target-typed object creation: Write
new()
instead ofnew Type()
this reduces clutter and thus readability of your code. This becomes apparent when writing lots of structures. - Pattern matching: No not the Regex variant, but for writing code. With C# 7.3 lof it null checks can be mode more compact and implicit. Allowing for more readable code.
filter.ComponentTypes != null && filter.ComponentTypesLength > 0
->filter.ComponentTypes is { Length: > 0 }
- File scoped namespaces: Removes unnecessary whitespaces everywhere.
- Null coalescing assignments:
myvar = myvar ?? default()
->myvar ??= default()
. Resharper/Rider suggests these automatically. - Raw string literals: Basically multiline strings that support quotes in the content without having to escape them.
For more see the left navigation on Microsoft C# Features.
dnSpy
- tool to decompile and debug .NET dlls that come with BattleTech and Mods
- setup by removing all dlls from the aseembly explorer list and only add all dlls found in
BattleTech_Data/Managed/*.dll
andMods/**/*.dll
.
dotPeek
- similar to dnSpy but not as good and does not support debugging
Debugging allows to follow the runtime behavior and in-memory data of BattleTech and its mods.
Only the .NET (C#) part of the game can be debugged.
You can't debug the dynamic patching part of Harmony, meaning you can only debug un-patched methods or any methods that are called from a pre- or postfix patch. Any breakpoint set in a method that gets directly patched by Harmony will not break.
Steps to get a working debugger setup:
- in
BATTLETECH/doorstop_config.ini
changedebug_enabled=false
todebug_enabled=true
- start the game now or just at least once to let all dlls be created or updated that you will let dnSpy(Ex) open
- download dnSpy(Ex) and extract it to its own folder somewhere
- start dnSpy(Ex) and add all dlls from
Mods/.modtek/AssembliesShimmed
,Mods/.modtek/AssembliesInjected
,BATTLETECH/BattleTech_Data/Managed
andMods/*/*.dll
to its project window.- Unfortunately that will contain a lot of duplicates, you need to make sure to select the correct assembly for adding a breakpoint
- TODO add a way to import the current list of dlls (as seen by ModTek) to dnSpy
- find the spot you want to debug, place a breakpoint
- connect dnSpy(Ex) to BattleTech by pressing
Start
and useUnity (connect)
using the default port your breakpoint should be hit as long as no mods patch the method with your breakpoint (seeHarmonyFileLog.log
)
- is a ModTek mod
- allows to navigate and see the UI unity tree structure
- also allows to modify UI elements to some extent, e.g. position, text, active state, etc..
Note TODO finalize
for steam installs:
create a file as
BATTLETECH/steam_appid.txt
with the contents:637090
start steam in the background
use dottrace for cpu profiling:
- Unity Application, Path to BattleTech.exe
- timeline, enable native profiling, collect native allocations, download symbol files
- Start via dottrace
- discard/drop/stop collecting until you want to collect
- start collect
- take snapshot
- analyzing/opening snapshot can take longer than actual collecting time
- select main thread, show system calls or not
- dont forget to save snapshot so you dont have to collect again
Memory Profiling -> have to write own stuff, existing stuff not really good.
UnityEditor profiler -> good for non-C# stuff, as you can drill down whats slow in a scene. requires unity debug player.
As modders we want to modify the behavior of the existing code. We do that by patching it.
Warning Do not use offline injections (examples are old style RogueTechPerfFixInjector, ModTeKInjector). They are hard to maintain and require to reinstall/re-verify the game after removing a mod that did the injection.
Recommended is doing runtime patching using harmony. Runtime patching (old Harmony1.2, new HarmonyX) Assembly patching via ModTekPreloader
Warning HarmonyX integration into ModTek bleeding edge, not every mod compilation will support this in the near future
HarmonyX is the latest harmony version available and can by used by adding a package reference as follows:
<ItemGroup>
<PackageReference Include="HarmonyX" Version="2.10.1">
<PrivateAssets>All</PrivateAssets>
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
</ItemGroup>
The version should be updated by you as ModTek updates it, though HarmonyX tries to keep good backwards compatibility with older HarmonyY versions.
Warning Publicizing is not without quirks, please visit github.com/krafs/Publicizer for a list of possible issues and suggested workarounds.
Note BepInEx.AssemblyPublicizer.MSBuild is not recommended anymore as Krafs.Publicizer covers provides more options and has a documented list of issues that come from publicizing.
What does a publicizer do and why do we need it?
- It makes all classes, methods, properties and fields of assemblies public (with some exceptions).
- It avoids the need to write reflection tools or wrappers to access private stuff, leads to cleaner and faster code at runtime.
Add to your csproj:
<PropertyGroup>
<!-- avoids IgnoresAccessChecksToAttribute warnings -->
<PublicizerRuntimeStrategies>Unsafe</PublicizerRuntimeStrategies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Krafs.Publicizer" Version="2.2.1" />
<Publicize Include="Assembly-CSharp" />
</ItemGroup>
Now instead of writing something with Harmony Traverse:
new Traverse(__instance).Property("InternetAvailable").SetValue(success);
One writes it this way:
__instance.InternetAvailable = success;
Only publicize libraries that you need to, makes compilation faster and not every library works nicely with publicizing.
Vanilla HBS logging was not that great, but with ModTek the HBS logger was extended to be efficient, fast and feature rich. See ModTek Logging.
Any mod not using HBS logging will make debugging harder as you can't at a glance see issues between mods, e.g. one mod creating an exception for another.
To avoid users overwriting their settings when updating your mod, don't load settings from the ModTek given settingsJson which comes from mod.json
,
but load them explicitly from the disk.
When dealing with JSON use Newtonsoft as much as possible. HBS uses its own JSON serialization mechanism, which should only be used when dealing with HBS data structures or HBS jsons.
Use git and github in order to avoid loosing your progress when writing a mod.
Use git versioning to help keep track of changes to your mod. Visual Studio and Rider have excellent support and allow to manage git related functionality directly from the IDE. Publish the sources of your mod on github as a backup to your local copy, make it public so its accessible in case you loose interest but someone want to take over.
ModTek itself is automatically built on GitHub using GitHub workflows, see ci.yml and mod-builder.yml.
Recommended is to setup the flow to allow the last commit on master to become a "latest" pre-release, and any tag to produce a versioned released. That way during development people can grab experimental "latest" versions.
microsoft has some styling defaults, also as .editorconfig . due to harmony patches requiring special naming syntax, styles can't just be applied directly
RogueTech and other communities have lots of help.