通过屏幕截图分析检测游戏作弊行为(C#)

5
我试图编写一些代码来检测游戏中的透视作弊。基本上,某些作弊程序会创建一个Windows Aero透明窗口,并在该外部窗口上绘制作弊内容,因此无法通过对游戏本身进行屏幕截图来检测。
目前我的方法是: 1. 对游戏窗口进行屏幕截图。 2. 对同一坐标的Windows桌面进行屏幕截图。 3. 进行图像分析以比较截图1和截图2是否有差异。
我的问题是,截图1和截图2不是同时进行的,因此在两个屏幕截图之间可能会绘制新的游戏画面,导致图像比较时出现误报。
我想知道是否有一种方法可以协调这些屏幕截图,使它们在完全相同的时间发生?或者以某种方式停止屏幕绘制任何新帧,直到我的屏幕截图完成?
以下是我用于截屏的代码。 请注意,我甚至尝试了使用两个工作项排队并行地进行两次屏幕截图。然而,即使这样也不能确保两个屏幕截图完全同时进行。 所以我想知道是否有一种方法可以阻止显卡更新屏幕,直到我的屏幕截图完成?或者是否有其他方法可以实现这一点?
    public void DoBitBlt(IntPtr dest, int width, int height, IntPtr src)
    {
        GDI32.BitBlt(dest, 0, 0, width, height, src, 0, 0, GDI32.SRCCOPY);
    }

    public struct Windows
    {
        public Bitmap window;
        public Bitmap desktop;
    }
    public Windows CaptureWindows(IntPtr window, IntPtr desktop, User32.RECT coords)
    {
        Windows rslt = new Windows();
        // get te hDC of the target window
        IntPtr hdcSrcWindow = User32.GetWindowDC(window);
        IntPtr hdcSrcDesktop = User32.GetWindowDC(desktop);

        // get the size
        int width = coords.right - coords.left;
        int height = coords.bottom - coords.top;

        // create a device context we can copy to
        IntPtr hdcDestWindow = GDI32.CreateCompatibleDC(hdcSrcWindow);
        IntPtr hdcDestDesktop = GDI32.CreateCompatibleDC(hdcSrcDesktop);
        // create a bitmap we can copy it to,
        // using GetDeviceCaps to get the width/height
        IntPtr hBitmapWindow = GDI32.CreateCompatibleBitmap(hdcSrcWindow, width, height);
        IntPtr hBitmapDesktop = GDI32.CreateCompatibleBitmap(hdcSrcDesktop, width, height);
        // select the bitmap object
        IntPtr hOldWindow = GDI32.SelectObject(hdcDestWindow, hBitmapWindow);
        IntPtr hOldDesktop = GDI32.SelectObject(hdcDestDesktop, hBitmapDesktop);
        // bitblt over
        var handle1 = new ManualResetEvent(false);
        var handle2 = new ManualResetEvent(false);
        Action actionWindow = () => { try { DoBitBlt(hdcDestWindow, width, height, hdcSrcWindow); } finally { handle1.Set(); } };
        Action actionDesktop = () => { try { DoBitBlt(hdcDestDesktop, width, height, hdcSrcDesktop); } finally { handle2.Set(); } };
        ThreadPool.QueueUserWorkItem(x => actionWindow());
        ThreadPool.QueueUserWorkItem(x => actionDesktop());
        WaitHandle.WaitAll(new WaitHandle[] { handle1, handle2 });

        rslt.window = Bitmap.FromHbitmap(hBitmapWindow);
        rslt.desktop = Bitmap.FromHbitmap(hBitmapDesktop);

        // restore selection
        GDI32.SelectObject(hdcDestWindow, hOldWindow);
        GDI32.SelectObject(hdcDestDesktop, hOldDesktop);
        // clean up
        GDI32.DeleteDC(hdcDestWindow);
        GDI32.DeleteDC(hdcDestDesktop);
        User32.ReleaseDC(window, hdcSrcWindow);
        User32.ReleaseDC(desktop, hdcSrcDesktop);
        // free up the Bitmap object
        GDI32.DeleteObject(hBitmapWindow);
        GDI32.DeleteObject(hBitmapDesktop);
        return rslt;
    }

3
我认为你正在做的事情不道德。你的“透视墙”检测器在收集一些用户可能不想透露的信息。为什么用户会同意你在他们玩游戏时拍摄他们桌面的屏幕截图呢?你试图实施的行为被归类为特洛伊木马。当然,如果你没有关于程序目的的谎言的话。 - Daniel Lane
这可能是使用 Parallel.Foreach 的适当情况。ThreadPool 不保证并行性。 - Dustin Kingen
2
假设我在窗口中打开了你的游戏,但是随后在该窗口上打开了一个网页浏览器以(谷歌某事|查看我的银行账户|浏览Facebook|进行非法操作),然后你对此进行了截屏。这将记录一个误报 - 即当我没有作弊时你认为我作弊了,如果你决定上传“伪造”的截图以作为证明/验证,你可能会意外地捕获到我的私人信息。 - Tim S.
1
PunkBuster会在用户同意的情况下执行此操作。因此,如果你们抱怨它不道德,其他人已经出于与DaManJ声称的相同原因而这样做了。 - string.Empty
可能有更简单的方式来检测一个标记为最顶层的透明窗口,该窗口重叠了你的游戏……例如通过枚举窗口。 - Ben Voigt
显示剩余3条评论
1个回答

2

如果你没有使用一些图形加速器,那么你将无法同时拥有两个屏幕截图,这意味着它不适用于每台计算机...

关于停止渲染,由于这是一个游戏,我认为这不是一个好主意...你希望你的游戏运行流畅。

相反,我想建议将最近渲染的游戏图像存储在内存中,当你拍摄截图时,将其与它们进行比较。如果你能添加一些视觉线索来决定要比较哪个最近的帧,那么它将工作得更好,因为否则你将不得不将截图与所有帧进行比较,这肯定会占用一些CPU/GPU时间。

你是否使用GDI进行渲染?如果是,那么你需要将游戏的帧存储为DIB(设备无关位图)以便进行比较。

至于决定使用哪个图像的线索,我会选择在屏幕上显示时间表示,可能是一个单像素点,它会改变颜色。如果是这样,你将读取该像素的颜色,使用它来找到正确的帧,然后继续比较整个图片。


如果你与游戏合作的话,就不需要真正保存旧帧。通过使用强校验和或使用隐写术在低色位中隐藏数据,完全足以确定屏幕截图是否被修改。 - Ben Voigt

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