Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Separate ASI into Native C++ Loader and Managed Runtime for Better Runtime Testability and Support for Reloading ASI #1548

Open
@kagikn

Description

@kagikn

📝 Feature Description

Separate the ASI into native C++ loader and a managed C# runtime, so we can run test code for some part of managed runtime, and add support for reloading the native ASI.

I found that we can have SHVRELOAD the ASI by manually hosting .NET Framework runtime, rather than having our ASI being a mixed assembly. Still, we have to have at least 1 managed .dll assembly get pinned for the SHVDN runtime. however, which means we can't swap all part of the runtime. This is because at least 1 managed one needs to be executed in the defaultAppDomain, which locks grabbed assemblies and can't be disposed by anything other than exiting the process.

🔧 Feature Workflow

  • Separate the ASI into native C++ loader and a managed C# runtime
    • Have the native C++ loader ASI invokes a tick method and a keyboard message method of managed C# runtime

PoC code:

Place these pieces of code in 2 different projects. You can use the projects in the src archive to build, or you can put the .asi and .dll files of the bin archive to the main directory of the game.

ScriptHookVDotNet-NativeLoader-bin.zip
ScriptHookVDotNet-NativeLoader-src.zip

Native C++ Code

#include"main.h"#include<mscoree.h>#include<corerror.h>#include<metahost.h>#include<fstream>#pragma comment (lib, "mscoree.lib")staticbool called_script_main =false;staticvoidLog(constchar* msg){std::ofstreamfile("ScriptHookVDotNet-NativeLoader.log", std::ios_base::app);file << msg << std::endl;file.close();}static ICLRRuntimeHost*GetClr45RuntimeHost(){constexpr WCHAR VER_STR_FOR_CLR45[] =L"v4.0.30319";    ICLRMetaHost* pMetaHost =nullptr;    ICLRRuntimeInfo* pRuntimeInfo =nullptr;    ICLRRuntimeHost* pRuntimeHost =nullptr;// Build runtime. Looks like calling `CLRCreateInstance` multiple times causes no problems...if (CLRCreateInstance(CLSID_CLRMetaHost,IID_PPV_ARGS(&pMetaHost)) != S_OK) {Log("CLRCreateInstance failed");returnnullptr;    }if (pMetaHost->GetRuntime(VER_STR_FOR_CLR45,IID_PPV_ARGS(&pRuntimeInfo)) != S_OK) {Log("ICLRMetaHost::GetRuntime failed");returnnullptr;    }if (pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost,IID_PPV_ARGS(&pRuntimeHost)) != S_OK) {Log("ICLRRuntimeInfo::GetInterface failed");returnnullptr;    }return pRuntimeHost;}static ICLRRuntimeHost*StartClr45RuntimeHost(){    ICLRRuntimeHost* pRuntimeHost =GetClr45RuntimeHost();// build runtimeif (pRuntimeHost ==nullptr) {Log("Failed to start CLR Runtime For .NET Framework 4.5+");returnnullptr;    }// it looks like we need to explicitly call `Start` for our case    pRuntimeHost->Start();return pRuntimeHost;}staticvoidScriptMain(){while (true)    {if (!called_script_main)        {            called_script_main =true;            ICLRRuntimeHost *runtimeHost =StartClr45RuntimeHost();// Execute our managed assembly. We shouldn't execute our code in a main script func for SHV// because of damn SHV fibers (all C++ exceptions would fall through and the process gets killed),// but we call a managed method with a fiber *for this demo*.//// Static variables in managed code do NOT reset to the default after returning to the caller.            DWORD pReturnValue;            runtimeHost->ExecuteInDefaultAppDomain(L"ScriptHookVDotNet_ManagedRuntime.dll",L"ScriptHookVDotNet_ManagedRuntime.Program",L"Main",L"",                &pReturnValue);}WAIT(0);}}staticvoidScriptKeyboardMessage(DWORD key, WORD repeats, BYTE scanCode, BOOL isExtended, BOOL isWithAlt, BOOL wasDownBefore, BOOL isUpNow){if (isUpNow || wasDownBefore)    {return;    }    ICLRRuntimeHost* runtimeHost =StartClr45RuntimeHost();// execute our managed assembly    DWORD pReturnValue;    runtimeHost->ExecuteInDefaultAppDomain(L"ScriptHookVDotNet_ManagedRuntime.dll",L"ScriptHookVDotNet_ManagedRuntime.Program",L"SendKeyboardMessage",L"",        &pReturnValue);}BOOL APIENTRYDllMain( HMODULE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved                     ){switch (ul_reason_for_call)    {case DLL_PROCESS_ATTACH:DisableThreadLibraryCalls(hModule);scriptRegister(hModule, ScriptMain);keyboardHandlerRegister(ScriptKeyboardMessage);break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:scriptUnregister(hModule);keyboardHandlerUnregister(ScriptKeyboardMessage);break;    }returnTRUE;}

C# Code (.NET Framework)

usingSystem;usingSystem.Globalization;usingSystem.IO;namespaceScriptHookVDotNet_ManagedRuntime{publicclassProgram{staticobject_lockObj=newobject();staticint_callCount=0;staticintMain(stringargs){// the count has to be incremented within a lock to avoid data race because SHV calls the main function// and the keyboard message function in different threadslock(_lockObj){_callCount++;}using(StreamWritersw=newStreamWriter("ScriptHookVDotNet-Managed-test.txt")){stringdtStr=$"[{DateTime.Now.ToString()}]";sw.WriteLine($"{dtStr}: Woah! Loader ASI and Managed Runtime DLL were loaded separately! "+$"Managed Runtime Call Count:{_callCount}");}return0;}staticintSendKeyboardMessage(stringargs){lock(_lockObj){_callCount++;}using(StreamWritersw=newStreamWriter("ScriptHookVDotNet-Managed-test.txt")){sw.WriteLine("Managed Runtime Call Count is incremented by one. Current Managed Runtime Call Count: "+_callCount.ToString());}return0;}}}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


      [8]ページ先頭

      ©2009-2025 Movatter.jp