Bagaimana mod untuk game Unity dikembangkan. Bagian 1: masuk ke kode permainan

Sebagian besar gim tidak memiliki dukungan mod resmi, tetapi selalu ada berita tentang bagaimana modder menambahkan sesuatu yang baru ke gim. Biasanya kita berbicara tentang mengganti model, tekstur atau musik , tetapi ada juga mekanisme permainan baru yang lengkap, mis. modders tertanam dalam kode permainan dan mengubah logika internal.


Saya bermain Beat Saber dengan mod cukup banyak, dan pada titik tertentu menjadi perlu untuk menulis sendiri. Beat Saber dibuat di Unity, jadi saya menyelidiki kodenya, kode mod yang ada, menemukan cara kerja semuanya di sana, dan menulis artikel tentang itu. Ternyata bahannya cukup banyak, jadi saya membaginya menjadi dua bagian. Pada bagian ini, kami akan mempertimbangkan metode apa yang digunakan untuk membuat game meluncurkan mod pihak ketiga: memodifikasi perpustakaan game dan mesin itu sendiri, menipu alamat di tabel impor dll, dan mengganggu pemuatan Mono Runtime.



Sumber Gambar: 1 , 2


Tentang Beat Saber


Beat Saber VR-. , , , , Beat Saber. , , , Youtube:



, Beat Saber β€” . , , - , , , . - . β€” - .


, Beat Saber : Beat Saber Modding Group (BSMG). , .



. β€” , GitHub, . , , , . , , GitHub.


Unity


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()
{
    // wait for plugins to finish loading
    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,      

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);):


/**
* \brief Loads Mono C API function pointers so that the above definitions can be called.
* \param monoLib Mono.dll module.
*/
inline void loadMonoFunctions(HMODULE monoLib)
{
    // Enjoy the fact that C allows such sloppy casting
    // In C++ you would have to cast to the precise function pointer type
#define GET_MONO_PROC(name) name = (void*)GetProcAddress(monoLib, #name)

    // Find and assign all our functions that we are going to use
    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);

    // Load our custom assembly into the domain
    void *assembly = mono_domain_assembly_open(domain, dll_path); // dll_path -    IPA.Inhector.dll

    // Get assembly's image that contains CIL code
    void *image = mono_assembly_get_image(assembly);

    // Create a descriptor for a random Main method
    void *desc = mono_method_desc_new("*:Main", FALSE);

    // Find the first possible Main method in the assembly
    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. , , , .


All Articles