Most games do not have official mod support, but there is constantly news about how modders add something new to games. Usually we are talking about replacing models, textures or music , but there are also full-fledged new game mechanics, i.e. modders are embedded in the game code and change the internal logic.
I play Beat Saber with mods quite a lot, and at some point it became necessary to write my own. Beat Saber is made on Unity, so I delved into its code, the code of existing mods, figured out how everything works there, and wrote an article about it. It turned out quite a lot of material, so I broke it into two parts. In this part, we will consider what methods are used to make the game launch third-party mods: modifying the game libraries and the engine itself, spoofing addresses in the dll import table, and interfering with loading Mono Runtime.

Image Sources: 1 , 2
About Beat Saber
Beat Saber VR-. , , , , Beat Saber. , , , Youtube:
, Beat Saber β . , , - , , , . - . β - .
, Beat Saber : Beat Saber Modding Group (BSMG). , .
- beatsaver.com β , . .
- bsaber.com β , β - , beatsaver.com. , , .
- scoresaber.com β .
- modelsaber.com β , . 3D-, .
- beatmods.com β , .
- github.com/Assistant/ModAssistant β ModAssistant, . , , Install. .
. β , GitHub, . , , , . , , GitHub.
Beat Saber Unity. Unity C# β . C# β , .NET (managed .NET assemblies) β dll-, (Common Intermediate Language, CIL IL). C++, β , β C#. , Unity , CIL- . Unity Mono.
Mono β (Common Language Infrastructure, CLI) . Mono Windows, Linux, macOS, . 2001 Ximian, 2003 Novell, 2011 Attachmate. Attachmate , Mono Mono, Xamarin, 2016 Microsoft.
, C C++, , Lua. C# , ββ, , JIT- . :

Mono.
( ) β dll- ( .NET-), . , Beat Saber . BSIPA.
BSIPA

BSIPA (Beat Saber Illusion Plugin Architecture) β , Beat Saber , . .
BSIPA C# ( ) IPA. .. Unity Beat Saber. BSIPA : IPA, IPA.Loader IPA.Injector. , , .
IPA.exe
β
ModAssistant , , , . BSIPA, IPA.exe. , , . , -, , : , - , . :
Beat Saber_Data\Managed\I18N.dll
Beat Saber_Data\Managed\I18N.West.dll
Beat Saber_Data\Managed\IPA.Injector.dll
Beat Saber_Data\Managed\IPA.Injector.pdb
Beat Saber_Data\Managed\IPA.Loader.dll
Beat Saber_Data\Managed\IPA.Loader.pdb
Beat Saber_Data\Managed\IPA.Loader.xml
Beat Saber_Data\Managed\Microsoft.CSharp.dll
Beat Saber_Data\Managed\System.Runtime.Serialization.dll
Libs\0Harmony.1.2.0.1.dll
Libs\Ionic.Zip.1.9.1.8.dll
Libs\Mono.Cecil.0.10.4.0.dll
Libs\Mono.Cecil.Mdb.0.10.4.0.dll
Libs\Mono.Cecil.Pdb.0.10.4.0.dll
Libs\Mono.Cecil.Rocks.0.10.4.0.dll
Libs\Newtonsoft.Json.12.0.0.0.dll
Libs\SemVer.1.2.0.0.dll
winhttp.dll
BSIPA, : IPA.Loader.dll IPA.Injector.dll. , . .
IPA.Loader.dll
, . PluginLoader, PluginManager PluginComponent. PluginLoader β , Plugins. IPA , . BSIPA : Loader , , .
PluginManager : , .
PluginComponent β Unity, , . , ( - , , ), PluginComponent . .
IPA.Injector.dll
β
Injector , . : BSIPA .
if (AntiPiracy.IsInvalid(Environment.CurrentDirectory))
{
loader.Error("Invalid installation; please buy the game to run BSIPA.");
return;
}
, , , : SmartSteamEmu.ini, BSteam crack.dll, HUHUVR_steam_api64.dll .
public static bool IsInvalid(string path)
{
var dataPlugins = Path.Combine(GameVersionEarly.ResolveDataPath(path), "Plugins");
return
File.Exists(Path.Combine(path, "IGG-GAMES.COM.url")) ||
File.Exists(Path.Combine(path, "SmartSteamEmu.ini")) ||
File.Exists(Path.Combine(path, "GAMESTORRENT.CO.url")) ||
File.Exists(Path.Combine(dataPlugins, "BSteam crack.dll")) ||
File.Exists(Path.Combine(dataPlugins, "HUHUVR_steam_api64.dll")) ||
Directory.GetFiles(dataPlugins, "*.ini", SearchOption.TopDirectoryOnly).Length > 0;
}
, BSIPA . , , . BSIPA , , .
IPA.Injector : Mono.Cecil ( ). : Jb Evain, Novell Mono, Microsoft, Visual Studio Tools for Unity. Mono.Cecil 2004 , .. Mono. .NET-: IL-, .
IPA.Injector Mono.Cecil, UnityEngine.CoreModule.dll. C# ( ) Unity. , GameObject ( , , ) MonoBehaviour ( , PluginComponent). IPA.Injector UnityEngine.CoreModule.dll UnityEngine.Application ( , ), :
var unityAsmDef = AssemblyDefinition.ReadAssembly(unityPath, new ReaderParameters { ... });
var unityModDef = unityAsmDef.MainModule;
var application = unityModDef.GetType("UnityEngine", "Application");
MethodDefinition cctor = null;
foreach (var m in application.Methods)
if (m.IsRuntimeSpecialName && m.Name == ".cctor")
cctor = m;
var createBootstrapper = unityModDef.ImportReference(((Action)CreateBootstrapper).Method);
if (cctor == null)
{
cctor = new MethodDefinition(".cctor", ...);
application.Methods.Add(cctor);
var ilp = cctor.Body.GetILProcessor();
ilp.Emit(OpCodes.Call, cbs);
ilp.Emit(OpCodes.Ret);
}
else
{
var ilp = cctor.Body.GetILProcessor();
ilp.Replace(cctor.Body.Instructions[0], ilp.Create(OpCodes.Call, cbs));
ilp.Replace(cctor.Body.Instructions[1], ilp.Create(OpCodes.Ret));
}
, Unity 2019.3.0f3 Application , , . Injector , , .
Application ( UnityEngine.CoreModule.dll):
static Application()
{
IPA.Injector.Injector.CreateBootstrapper();
}
CreateBootstrapper Bootstrapper:
private static void CreateBootstrapper()
{
...
var bootstrapper = new GameObject("NonDestructiveBootstrapper")
.AddComponent<Bootstrapper>();
bootstrapper.Destroyed += Bootstrapper_Destroyed;
}
:
internal class Bootstrapper : MonoBehaviour
{
public event Action Destroyed = delegate {};
public void Start()
{
Destroy(gameObject);
}
public void OnDestroy()
{
Destroyed();
}
}
Start Awake, Awake , , Start ( ). . Bootstrapper Start , . OnDestroy , CreateBootstrapper:
private static void Bootstrapper_Destroyed()
{
pluginAsyncLoadTask.Wait();
permissionFixTask.Wait();
PluginComponent.Create();
}
PluginComponent. , , PluginComponent OnDestroy. : , β Start , , , . , Unity- .
, Application (.. .NET- Unity), PluginComponent Plugins. , Beat Saber, Unity.
IPA.Injector β MainAssembly.dll. Beat Saber. Mono.Cecil IPA.Injector sealed . . , .
BSIPA, IPA, . IPA.exe Injector, UnityEngine.CoreModule.dll, . β , , IPA.exe . BSIPA , , , IPA.exe BSIPA . Injector .
Unity Doorstop

Unity UnityDoorstop-BSIPA. BSIPA C. UnityDoorstop-BSIPA ( ) β , . UnityDoorstop-BSIPA Doorstop. Doorstop βRun managed code before Unity does!β, β , Unity β. , β β β C#. , Unity C++, Unity β C#. , Doorstop - , Unity , C#- β .
Unity (, Beat Saber.exe), UnityPlayer.dll. Unity- . , , UnityPlayer GetProcAddress kernel32.dll. GetProcAddress β WinAPI, . Unity, , BSIPA Doorstop, UnityPlayer - :
mono_dll = LoadLibrary(βMono.dllβ);
init_mono = GetProcAddress(mono_dll, βmono_jit_init_versionβ);
mono = init_mono(...);
mono_jit_init_version β , Mono. . Doorstop . .
1. GetProcAddress
Doorstop.dll , DllMain , reasonForDllLoad == DLL_PROCESS_ATTACH
, , :
HMODULE targetModule = GetModuleHandleA("UnityPlayer");
iat_hook(targetModule, "kernel32.dll", &GetProcAddress, &hookGetProcAddress);
DllMain, iat_hook
UnityPlayer.dll, (Import Address Table, IAT), GetProcAddress kernel32.dll hookGetProcAddress Doorstop.dll. hookGetProcAddress :
void * WINAPI hookGetProcAddress(HMODULE module, char const *name)
{
if (lstrcmpA(name, "mono_jit_init_version") == 0)
{
init(module);
return (void*)&ownMonoJitInitVersion;
}
return (void*)GetProcAddress(module, name);
}
hookGetProcAddress
IAT Hooking . , hookGetProcAddress GetProcAddress. - , . mono_jit_init_version, hookGetProcAddress GetProcAddress , , . mono_jit_init_version, ownMonoJitInitVersion. , - , mono_jit_init_version, Mono GetProcAddress ( init(module);
):
inline void loadMonoFunctions(HMODULE monoLib)
{
#define GET_MONO_PROC(name) name = (void*)GetProcAddress(monoLib, #name)
GET_MONO_PROC(mono_debug_domain_create);
GET_MONO_PROC(mono_domain_assembly_open);
GET_MONO_PROC(mono_assembly_get_image);
GET_MONO_PROC(mono_runtime_invoke);
GET_MONO_PROC(mono_debug_init);
GET_MONO_PROC(mono_jit_init_version);
GET_MONO_PROC(mono_jit_parse_options);
GET_MONO_PROC(mono_method_desc_new);
GET_MONO_PROC(mono_method_desc_search_in_image);
GET_MONO_PROC(mono_method_signature);
GET_MONO_PROC(mono_signature_get_param_count);
GET_MONO_PROC(mono_array_new);
GET_MONO_PROC(mono_get_string_class);
GET_MONO_PROC(mono_string_new_utf16);
GET_MONO_PROC(mono_gc_wbarrier_set_arrayref);
GET_MONO_PROC(mono_array_addr_with_size);
GET_MONO_PROC(mono_object_to_string);
GET_MONO_PROC(mono_string_to_utf8);
GET_MONO_PROC(mono_dllmap_insert);
GET_MONO_PROC(mono_free);
}
2. mono_jit_init_version
ownMonoJitInitVersion mono_jit_init_version, Mono. Mono IPA.Injector.dll Main. () ownMonoJitInitVersion :
void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_version)
{
void *domain = mono_jit_init_version(root_domain_name, runtime_version);
void *assembly = mono_domain_assembly_open(domain, dll_path);
void *image = mono_assembly_get_image(assembly);
void *desc = mono_method_desc_new("*:Main", FALSE);
void *method = mono_method_desc_search_in_image(desc, image);
mono_runtime_invoke(method, NULL, args, &exception);
return domain;
}
ownMonoJitInitVersion
, IPA.Injector , Beat Saber. , IPA.Injector , ownMonoJitInitVersion Mono Unity. Unity , - . mono_jit_init_version, Mono . Unity ownMonoJitInitVersion, Mono β , Mono - .
winhttp.dll
. -, Doorstop Doorstop.dll. , IPA.exe :
Beat Saber_Data\Managed\I18N.dll
Beat Saber_Data\Managed\I18N.West.dll
Beat Saber_Data\Managed\IPA.Injector.dll
Beat Saber_Data\Managed\IPA.Injector.pdb
Beat Saber_Data\Managed\IPA.Loader.dll
Beat Saber_Data\Managed\IPA.Loader.pdb
Beat Saber_Data\Managed\IPA.Loader.xml
Beat Saber_Data\Managed\Microsoft.CSharp.dll
Beat Saber_Data\Managed\System.Runtime.Serialization.dll
Libs\0Harmony.1.2.0.1.dll
Libs\Ionic.Zip.1.9.1.8.dll
Libs\Mono.Cecil.0.10.4.0.dll
Libs\Mono.Cecil.Mdb.0.10.4.0.dll
Libs\Mono.Cecil.Pdb.0.10.4.0.dll
Libs\Mono.Cecil.Rocks.0.10.4.0.dll
Libs\Newtonsoft.Json.12.0.0.0.dll
Libs\SemVer.1.2.0.0.dll
winhttp.dll
, Doorstop.dll . -, Doorstop.dll , Beat Saber Unity , ? : Doorstop.dll , Unity, , . winhttp.dll β Windows http- ( C:/Windows/System32). Unity , winhttp.dll, Unity Windows winhttp.dll , Unity .
Doorstop : winhttp.dll. Doorstop, GetProcAddress mono_jit_init_version, (Export Address Table) , winhttp.dll. Windows , Windows , , , , System32. Windows . winhttp ( LoadLibrary) ( GetProcAddress). : 16, β 960.
IPA BSIPA proxy.c, , , GetProcAddress. IPA PowerShell, BSIPA β Python. : proxy.c.
DLL Search Order Hijacking .
.
- IPA.exe. . .
- (Beat Saber.exe).
- Windows , Unity. β winhttp.dll. Windows winhttp.dll .
- winhttp.dll Doorstop. GetProcAddress kernel32.dll hookGetProcAddress.
- UnityPlayer.dll, C++.
- Unity GetProcAddress, mono_jit_init_version. GetProcAddress, ownMonoJitInitVersion.
- ownMonoJitInitVersion Mono.
- ownMonoJitInitVersion Mono, IPA.Injector.dll Main .
- IPA.Injector Mono.Cecil Application UnityEngine.CoreModule.dll , Beat Saber.
- ownMonoJitInitVersion Mono Unity.
- Unity Mono, , C#.
- Application . Beat Saber.
- Unity Mono, Beat Saber.
- .
2
. , Windows Unity Beat Saber. , BSIPA β , , , , Unity-.
Beat Saber: , , , Harmony β C#-, RimWorld, BATTLETECH, Cities: Skylines, Kerbal Space Program, Oxygen Not Included, Stardew Valley, Subnautica .
BSMG DaNike , Discord , Doorstop winhttp.dll. , , , .