想玩会儿osu!,看看我的触摸板,假如它能像数位板一样绝对定位就好了···

搜了一下,没找到什么东西,但是将关键词翻译成英文之后搜到了一个项目 AbsoluteTouchEx,它通过往WInAPI上挂钩子,实现触摸板的绝对定位。

不知道是不是我Win11的问题,这个程序似乎并不管用,但是的确挂上钩子了。

感觉像是程序的问题,几K的代码,我来研究一下这个项目吧。

Windows的可执行文件在运行的时候会需要加载动态库文件,这些动态库里面装了编译好的代码,用于扩展程序功能。

取代正常的DLL

各DLL和可执行文件是相对独立的,只使用暴露的接口交互。

针对于这一点,找到一个DLL,查看它有哪些接口,直接编译一个接口相同的DLL。

此处有两个目的,

  • 自己编译的DLL进入了虚拟内存空间,可以执行我们希望执行的代码。

  • 可以返回希望返回的值,比如,软件询问是否激活,直接返回True。

据说steam的游戏好多都是这样破解的。

加载一个新DLL

莫名其妙的DLL程序肯定不会去加载的,就算加载了也不会执行里面的代码(除了加载卸载的时候会去执行一下DllMain)。

微软有一个Detours库,用来注入挂钩子什么的,这是一个好东西。

首先DetourCreateProcessWithDllExA这个函数,用于执行一个可执行文件,并给它注入DLL,这个DLL会在可执行文件代码执行之前执行,所以可以安全地整活。

所有核心代码都在这个DLL里面。

注入DLL的位数需要和可执行文件一致,否则DLL会被注入到一个辅助进程中运行,这样和主进程是不同的虚拟内存空间,无法达到目的。

DLL内可使用DetourIsHelperProcess函数看一下自己是不是被注入对了,如果被注入和辅助进程会返回True。

DetourCreateProcessWithDllExA注入DLL似乎是修改了可执行文件的PE头,为了兼容性,需要在DLL中执行一下DetourRestoreAfterWith

挂钩子似乎需要修改只读的内存,为了能写只读内存,执行DetourTransactionBegin

然后就可以开始挂钩子了。

DetourAttach是用来挂钩子的函数,要知道,只要是同一个函数,DLL中拿到的和主程序拿到的指针是一样的,假如我们在DLL这边修改了指针指向的内容,主程序那边执行的内容就会变。

DetourAttach这个函数传入两个参数,一个是要挂钩子的函数,第二个是我们的函数,我们的函数应当与原函数签名一致,以便接收参数和给返回值。

第一个传入参数的类型是指向函数指针的指针,过后这个指向函数的指针会发生变化,因为原函数挂钩子导致它的代码发生了变化,本DLL需要调用挂钩子之前的代码,所以挂钩子的同时对原来的代码进行了备份,变化之后的指针指向备份的代码。

例如要对CreateWindowExW挂钩子,监听窗口创建。

static decltype(CreateWindowExW) *g_originalCreateWindowExW = CreateWindowExW;

static HWND WINAPI
AT_CreateWindowExWHook(···){
    // 希望执行的代码
    return g_originalCreateWindowExW(···)
}

BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason, PVOID lpReserved){
    if (DetourIsHelperProcess()) {
        return TRUE;
    }
    switch (dwReason) {
    case DLL_PROCESS_ATTACH:
        DetourRestoreAfterWith();
        DetourTransactionBegin();
        DetourAttach((void **)&g_originalCreateWindowExW, AT_CreateWindowExWHook);
    case DLL_PROCESS_DETACH:
        DetourTransactionBegin();
        DetourDetach((void **)&g_originalCreateWindowExW, AT_CreateWindowExWHook);
    }
    return True;
}

看好多地方以及这个项目中都使用了 DetourUpdateThread(GetCurrentThread());,我很好奇这是干嘛的,于是去翻了翻源代码,结果是,这句话什么都没做:

LONG WINAPI DetourUpdateThread(_In_ HANDLE hThread){
    ···
    // Silently (and safely) drop any attempt to suspend our own thread.
    if (hThread == GetCurrentThread()) {
        return NO_ERROR;
    }
    ···
}


调试了一上午程序,在其他情况似乎都没问题,但是osu!中系统好像没有将触摸板的原始数据发过来,谜之WindowsAPI,我没什么办法,结果还是不能拿触摸板玩osu!,但是学到了DLL注入。

我能想到的,最大的成功就是无愧于自己的心。