大多数游戏都没有官方对mod的支持,但是不断有关于mod如何为游戏添加新内容的新闻。通常,我们谈论的是替换模型,纹理或音乐,但也有成熟的新游戏机制,即 修改器嵌入游戏代码中并更改内部逻辑。
我经常使用mod玩Beat Saber,在某些时候有必要写自己的游戏。Beat Saber是在Unity上开发的,因此我深入研究了它的代码,现有mod的代码,弄清楚了那里的一切工作原理,并撰写了一篇有关它的文章。原来有很多资料,所以我将其分为两部分。在这一部分中,我们将考虑使用什么方法使游戏启动第三方mod:修改游戏库和引擎本身,欺骗dll导入表中的地址,以及干扰加载Mono Runtime。

图像源:1,2
关于Beat Sabre
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. , , , .