BUTR/Bannerlord.BLSE

View on GitHub
src/Bannerlord.BLSE.Loaders.Standalone/NETFrameworkLoader.Hosting.cs

Summary

Maintainability
A
2 hrs
Test Coverage
#if NETFRAMEWORKHOSTING
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;

namespace Bannerlord.BLSE;

[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Local")]
[SuppressMessage("ReSharper", "UnusedVariable")]
file readonly unsafe struct IEnumUnknown
{
#pragma warning disable CS0649
    private readonly IEnumUnknownVtbl* vtbl;
#pragma warning restore CS0649

    public static nint Release(IEnumUnknown* host)
    {
        var release = (delegate*<IEnumUnknown*, nint>) host->vtbl->Release;
        return release(host);
    }
    
    public static nint Next<T>(IEnumUnknown* host, int celt, T** pEnumRuntime, int* pCeltFetched) where T : unmanaged
    {
        var start = (delegate*<IEnumUnknown*, int, T**, int*, nint>) host->vtbl->Next;
        return start(host, celt, pEnumRuntime, pCeltFetched);
    }
}

[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Local")]
[SuppressMessage("ReSharper", "UnusedVariable")]
file readonly unsafe struct IEnumUnknownVtbl
{
#pragma warning disable CS0649
    public readonly void* QueryInterface;
    public readonly void* AddRef;
    public readonly void* Release;
    public readonly void* Next;
    public readonly void* Skip;
    public readonly void* Reset;
    public readonly void* Clone;
#pragma warning restore CS0649
}

[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Local")]
[SuppressMessage("ReSharper", "UnusedVariable")]
file readonly unsafe struct ICLRRuntimeHost
{
#pragma warning disable CS0649
    private readonly ICLRRuntimeHostVtbl* vtbl;
#pragma warning restore CS0649
    
    public static nint Release(ICLRRuntimeHost* host)
    {
        var release = (delegate*<ICLRRuntimeHost*, nint>) host->vtbl->Release;
        return release(host);
    }
    
    public static nint Start(ICLRRuntimeHost* host)
    {
        var start = (delegate*<ICLRRuntimeHost*, nint>) host->vtbl->Start;
        return start(host);
    }
    
    public static int ExecuteInDefaultAppDomain(ICLRRuntimeHost* host, string dllPath, string typeName, string methodName, string argument)
    {
        fixed (char* pDllPath = dllPath)
        fixed (char* pTypeName = typeName)
        fixed (char* pMethodName = methodName)
        fixed (char* pArgument = argument)
        {
            var executeInDefaultAppDomain = (delegate*<ICLRRuntimeHost*, char*, char*, char*, char*, int*, nint>) host->vtbl->ExecuteInDefaultAppDomain;
            
            int returnVal;
            var result = executeInDefaultAppDomain(host, pDllPath, pTypeName, pMethodName, pArgument, &returnVal);
            return result == 0 ? returnVal : 1;
        }
    }
}
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Local")]
[SuppressMessage("ReSharper", "UnusedVariable")]
file readonly unsafe struct ICLRRuntimeHostVtbl
{
#pragma warning disable CS0649
    public readonly void* QueryInterface;
    public readonly void* AddRef;
    public readonly void* Release;
    public readonly void* Start;
    public readonly void* Stop;
    public readonly void* SetHostControl;
    public readonly void* GetCLRControl;
    public readonly void* UnloadAppDomain;
    public readonly void* ExecuteInAppDomain;
    public readonly void* GetCurrentAppDomainId;
    public readonly void* ExecuteApplication;
    public readonly void* ExecuteInDefaultAppDomain;
#pragma warning restore CS0649
}

[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Local")]
[SuppressMessage("ReSharper", "UnusedVariable")]
file readonly unsafe struct ICLRRuntimeInfo
{
    private static readonly Guid CLSID_CLRRuntimeHost = new("90F1A06E-7712-4762-86B5-7A5EBA6BDB02");
    private static readonly Guid IID_ICLRRuntimeHost = new("90F1A06C-7712-4762-86B5-7A5EBA6BDB02");
    
#pragma warning disable CS0649
    private readonly ICLRRuntimeInfoVtbl* vtbl;
#pragma warning restore CS0649

    public static nint Release(ICLRRuntimeInfo* host)
    {
        var release = (delegate*<ICLRRuntimeInfo*, nint>) host->vtbl->Release;
        return release(host);
    }
    
    public static string GetVersionString(ICLRRuntimeInfo* host)
    {
        var chars = stackalloc char[20];
        var size = 20;
        var release = (delegate*<ICLRRuntimeInfo*, void*, int*, nint>) host->vtbl->GetVersionString;
        var result = release(host, &chars[0], &size);
        return new string(chars);
    }
    
    private static T* GetInterface<T>(ICLRRuntimeInfo* host, Guid* rclsid, Guid* riid) where T : unmanaged
    {
        var getInterface = (delegate*<ICLRRuntimeInfo*, Guid*, Guid*, T**, nint>) host->vtbl->GetInterface;
        
        T* ptr;
        var result = getInterface(host, rclsid, riid, &ptr);
        return ptr;
    }
    
    public static ICLRRuntimeHost* GetRuntimeHost(ICLRRuntimeInfo* host)
    {
        fixed (Guid* clsid = &CLSID_CLRRuntimeHost)
        fixed (Guid* iid = &IID_ICLRRuntimeHost)
        {
            return GetInterface<ICLRRuntimeHost>(host, clsid, iid);
        }
    }
}
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Local")]
[SuppressMessage("ReSharper", "UnusedVariable")]
file readonly unsafe struct ICLRRuntimeInfoVtbl
{
#pragma warning disable CS0649
    public readonly void* QueryInterface;
    public readonly void* AddRef;
    public readonly void* Release;
    public readonly void* GetVersionString;
    public readonly void* GetRuntimeDirectory;
    public readonly void* IsLoaded;
    public readonly void* LoadErrorString;
    public readonly void* LoadLibrary;
    public readonly void* GetProcAddress;
    public readonly void* GetInterface;
    public readonly void* IsLoadable;
    public readonly void* SetDefaultStartupFlags;
    public readonly void* GetDefaultStartupFlags;
    public readonly void* BindAsLegacyV2Runtime;
    public readonly void* IsStarted;
#pragma warning restore CS0649
}

file unsafe class InstalledRuntime
{
    public ICLRRuntimeInfo* RuntimeInfo { get; set; }
    public string Version { get; set; }
}

[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Local")]
[SuppressMessage("ReSharper", "UnusedVariable")]
file readonly unsafe struct ICLRMetaHost
{
    private static readonly Guid CLSID_CLRMetaHost = new("9280188D-0E8E-4867-B30C-7FA83884E8DE");
    private static readonly Guid IID_ICLRMetaHost = new("D332DB9E-B9B3-4125-8207-A14884F53216");
    
    private static readonly Guid IID_ICLRRuntimeInfo = new("BD39D1D2-BA2F-486A-89B0-B4B0CB466891");

    [DllImport("mscoree")]
    private static extern int CLRCreateInstance(Guid* clsId, Guid* rIId, ICLRMetaHost** instance);
    
#pragma warning disable CS0649
    private readonly ICLRMetaHostVtbl *vtbl;
#pragma warning restore CS0649

    public static ICLRMetaHost* Create()
    {
        fixed (Guid* clsid = &CLSID_CLRMetaHost)
        fixed (Guid* iid = &IID_ICLRMetaHost)
        {
            ICLRMetaHost* host;
            var result = CLRCreateInstance(clsid, iid, &host);
            return host;
        }
    }
    
    public static nint Release(ICLRMetaHost* host)
    {
        var release = (delegate*<ICLRMetaHost*, nint>) host->vtbl->Release;
        return release(host);
    }

    public static List<InstalledRuntime> GetInstalledRuntimes(ICLRMetaHost* host)
    {
        var runtimeEnumerator = EnumerateInstalledRuntimes(host);
        var list = new List<InstalledRuntime>();
        var fetched = 0;
        ICLRRuntimeInfo* runtimeInfo = null;
        for (;;)
        {
            var result = IEnumUnknown.Next(runtimeEnumerator, 1, &runtimeInfo, &fetched);
            if (result != 0) break;
            list.Add(new InstalledRuntime
            {
                RuntimeInfo = runtimeInfo,
                Version = ICLRRuntimeInfo.GetVersionString(runtimeInfo),
            });
        }
        IEnumUnknown.Release(runtimeEnumerator);
        return list;
    }
    
    public static IEnumUnknown* EnumerateInstalledRuntimes(ICLRMetaHost* host)
    {
        IEnumUnknown* ptr;
        var enumerateInstalledRuntimes = (delegate*<ICLRMetaHost*, IEnumUnknown**, nint>) host->vtbl->EnumerateInstalledRuntimes;
        var result = enumerateInstalledRuntimes(host, &ptr);
        return ptr;
    }
    
    public static ICLRRuntimeInfo* GetRuntime(ICLRMetaHost* host, string str)
    {
        fixed (char* pStr = str)
        fixed (Guid* iid = &IID_ICLRRuntimeInfo)
        {
            var getRuntime = (delegate*<ICLRMetaHost*, char*, Guid*, ICLRRuntimeInfo**, nint>) host->vtbl->GetRuntime;
            
            ICLRRuntimeInfo* ptr;
            var result = getRuntime(host, pStr, iid, &ptr);
            return result == 0 ? ptr : null;
        }
    }
}
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Local")]
[SuppressMessage("ReSharper", "UnusedVariable")]
file readonly unsafe struct ICLRMetaHostVtbl
{
#pragma warning disable CS0649
    public readonly void* QueryInterface;
    public readonly void* AddRef;
    public readonly void* Release;
    public readonly void* GetRuntime;
    public readonly void* GetVersionFromFile;
    public readonly void* EnumerateInstalledRuntimes;
    public readonly void* EnumerateLoadedRuntimes;
    public readonly void* RequestRuntimeLoadedNotification;
    public readonly void* QueryLegacyV2RuntimeBinding;
    public readonly void* ExitProcess;
#pragma warning restore CS0649
}

[SuppressMessage("ReSharper", "UnusedVariable")]
public static unsafe class NETFrameworkLoader
{
    public static void Launch(string[] args)
    {
        // Catch AccessViolation
        Environment.SetEnvironmentVariable("COMPlus_legacyCorruptedStateExceptionsPolicy", "1");

        var clrMetaHost = ICLRMetaHost.Create();
        var runtimes = ICLRMetaHost.GetInstalledRuntimes(clrMetaHost);
        var runtime = runtimes.OrderBy(x => x.Version).Last(x => x.Version.StartsWith("v4"));
        var runtimeHost = ICLRRuntimeInfo.GetRuntimeHost(runtime.RuntimeInfo);
        var startResult = ICLRRuntimeHost.Start(runtimeHost);
        var executeResult = ICLRRuntimeHost.ExecuteInDefaultAppDomain(runtimeHost, "Bannerlord.BLSE.Shared.dll", "Bannerlord.BLSE.Shared.Program", "NativeEntry2", string.Join("|||", args));
        ICLRRuntimeInfo.Release(runtime.RuntimeInfo);
        ICLRRuntimeHost.Release(runtimeHost);
        ICLRMetaHost.Release(clrMetaHost);
    }
}
#endif