在3D全屏应用程序上覆盖叠加层

47

我想在第三方全屏Windows应用程序顶部显示一些自定义图形。

你玩过Steam游戏吗?它有一个可执行文件GameOverlayUI.exe,允许您从游戏中访问Steam窗口。 (GUI元素看起来是自定义绘制的,我不知道这是否是必要的;但在游戏内部它们看起来与桌面上完全相同)。 它甚至在背景中“变暗”游戏图形。

我想知道如何编写这样的应用程序。

我还想知道解决方案的范围有多广。 您可以为所有OpenGL应用程序使用一种方法吗? 对于所有Direct3D减去DirectDraw应用程序? 对于所有全屏Windows应用程序?

我正在寻找更“现代”和通用的解决方案-覆盖命令提示符或320x240索引颜色应用程序并不是问题。

编辑:澄清一些事情:全屏应用程序不是我的应用程序。 它可以是任何东西。 最有可能的是3D游戏。 我想在屏幕右下角显示例如数字时钟,在游戏图形的顶部。 如果可能的话,我想避免注入代码或调试进程等技巧。

5个回答

28

这里还有一个例子。Xfire是一个聊天程序,可以在玩游戏时将其界面叠加在游戏上,以便您接收消息。他们这样做的方式是通过在进程内存级别编辑d3d/opengl DLL,例如注入汇编跳转。我知道这一点是因为他们的方法导致我的模组崩溃,我实际上看到了它们注入到d3d9.dll中的奇怪汇编代码。

Xfire是这样做的:

  1. 针对游戏进程
  2. 搜索其进程内存以查找d3d.dll/opengl.dll
  3. 注入包含自定义接口逻辑的DLL到进程中
  4. 通过手动编写在进程内存中找到的d3d/opengl函数中的汇编跳转将接口的功能连接到程序流程中

没有其他可行的方式,因为您需要劫持属于该游戏的图形设备。 我愿意打赌Steam与Xfire的做法是相同的。 因此,除非有人创建了一个有用的库来在低级别上执行所有所需操作,否则没有简单的方法可做到这一点。

您还询问如何在叠加中调暗游戏图形。 这只是一个简单的DrawPrimitive调用,以在屏幕上绘制填充的透明矩形。


12
在使用像Punkbuster这样需要反作弊软件的多人游戏时,要小心操作!将自定义库注入运行中的游戏被视为侵入式游戏操纵和作弊。一些程序(如Xfire、Teamspeak Overlay等)已被列入白名单,不会再引起作弊违规,但这是由反作弊厂商进行的学习过程,并可能耗费时间,导致你无法有效地使用你的程序。 - Robert

25

在此处创建了一个带有完整代码的Gist。链接

这是一种相当深奥的技巧,有多种方法可以实现。我比较喜欢使用所谓的Direct3D包装器。虽然我已经好几年没有做过这个了,但是我曾经在一个游戏中这样做过。我可能漏掉了一些细节,但是我希望能够说明这个想法。

所有的 Direct3D9 游戏都需要在屏幕渲染完成并准备好绘制之后调用 IDirect3DDevice9::EndScene。因此,理论上我们可以在 EndScene 中放置自定义代码,覆盖在游戏上方进行绘制。我们通过创建一个类来实现这一点,该类看起来像 IDirect3DDevice9,但实际上只是将所有函数调用转发到真正的类。因此,现在,在我们的自定义类中,EndScene 的包装器函数如下:

HRESULT IDirect3DDevice9::EndScene()
{
   // draw overlays here

   realDevice.EndScene();
}

然后我们将其编译为名为d3d9.dll的DLL,并将其放置在游戏可执行文件目录中。实际的d3d9.dll应该在/system32文件夹中,但游戏首先查找当前目录,然后加载我们的DLL。一旦游戏加载,它使用我们的DLL来创建包装类的实例。每次调用EndScene时,我们的代码被执行以将所需内容绘制到屏幕上。

我认为你可以尝试使用相同的原理来使用OpenGL。但是为了防止篡改,一些现代游戏尝试防止这种行为。


有没有一个包装器可以让我在C#应用程序中使用这段代码?谢谢 - smedasn

1
Hearthstone-Deck-Tracker中,
它创建了一个针对炉石传说的工具。
基本方法是SetTopmost
就像这样:
private async void SetTopmost()
{
    if(User32.GetHearthstoneWindow() == IntPtr.Zero)
    {
        Log.Info("Hearthstone window not found");
        return;
    }

    for(var i = 0; i < 20; i++)
    {
        var isTopmost = User32.IsTopmost(new WindowInteropHelper(this).Handle);
        if(isTopmost)
        {
            Log.Info($"Overlay is topmost after {i + 1} tries.");
            return;
        }

        Topmost = false;
        Topmost = true;
        await Task.Delay(250);
    }

    Log.Info("Could not set overlay as topmost");
}

0
我一直在思考这个问题。对于OpenGL,我会使用Detours挂钩WGL的SwapBuffers函数,在游戏绘制完毕后再绘制我的内容,然后自己进行缓冲区交换。

-1

我曾经使用DirectX和WPF视口(实际上是同一种东西)来创建覆盖层,利用覆盖层在3D可视化的顶部绘制一些漂亮的2D GDI+内容。

我通过在“实际”可视化的顶部放置一个面板,并将颜色设置为透明,除了我想要叠加的部分外,成功地实现了这一点。效果还不错,但是将点击、拖动和其他事件传递到3D控件的“绘图”层有点麻烦。


如果我的问题不够清晰,我很抱歉:在我的情况下,“实际”的可视化是由第三方应用程序绘制的。我已经按照您所说的做过一次,在单个应用程序的上下文中。我不知道您的答案是否适用于两个不同应用程序的情况;如果适用,我该如何在全屏父面板的上下文中创建一个子面板? - aib
@moobaa - 你能详细说明一下这种技术如何在第一方和第三方应用程序之间使用吗? - Scuba Steve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接