كيف يتم تطوير تعديل ألعاب Unity. الجزء الأول: الدخول إلى كود اللعبة

لا تحتوي معظم الألعاب على دعم رسمي من وزارة الدفاع ، ولكن هناك أخبار باستمرار حول كيفية إضافة المشرفين شيئًا جديدًا للألعاب. عادة ما نتحدث عن استبدال النماذج أو القوام أو الموسيقى ، ولكن هناك أيضًا ميكانيكا ألعاب جديدة كاملة ، أي يتم تضمين المعتدلين في رمز اللعبة وتغيير المنطق الداخلي.


ألعب Beat Sabre مع تعديل كثير جدًا ، وفي مرحلة ما أصبح من الضروري كتابة كتابي الخاص. تم تصميم Beat Sabre على Unity ، لذلك بحثت في الكود الخاص به ، كود التعديلات الموجودة ، واكتشفت كيف يعمل كل شيء هناك ، وكتبت مقالًا عنه. اتضح الكثير من المواد ، لذلك قسمتها إلى جزأين. في هذا الجزء ، سننظر في الطرق المستخدمة لجعل اللعبة تطلق تعديلات من طرف ثالث: تعديل مكتبات الألعاب والمحرك نفسه ، وانتحال العناوين في جدول استيراد dll ، والتدخل في تحميل Mono Runtime.



مصادر الصورة: 1 ، 2


عن بيت صابر


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