使用 SharpDX 和 EasyHook 捕获全屏 DX11 程序的屏幕截图

2024-05-13

在有人提到它之前,IS提到了this http://www.codeproject.com/Articles/274461/Very-fast-screen-capture-using-DirectX-in-Csharp链接以了解如何将后台缓冲区复制到位图。

现在的情况

  • 我被注入到目标进程
  • 目标进程的FeatureLevel = Level_11_0
  • 目标 SwapChain 正在使用 DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH 标志进行创建。
  • SwapChain::Present 函数已挂钩。
  • 屏幕截图显示黑色并且目标进程崩溃。没有屏幕截图的过程运行良好。

期望的情况

正确截图并让目标进程继续正常执行。

Code

注意 Hook 类与链接中的相同。我只添加了它的不可修改的 Hook 版本,它的作用正如其名称所示。我遗漏了所有不重要的部分。

测试SwapChainHook.cs

using System;
using System.Runtime.InteropServices;

namespace Test
{
    public sealed class TestSwapChainHook : IDisposable
    {
        private enum IDXGISwapChainVirtualTable
        {
            QueryInterface = 0,
            AddRef = 1,
            Release = 2,
            SetPrivateData = 3,
            SetPrivateDataInterface = 4,
            GetPrivateData = 5,
            GetParent = 6,
            GetDevice = 7,
            Present = 8,
            GetBuffer = 9,
            SetFullscreenState = 10,
            GetFullscreenState = 11,
            GetDesc = 12,
            ResizeBuffers = 13,
            ResizeTarget = 14,
            GetContainingOutput = 15,
            GetFrameStatistics = 16,
            GetLastPresentCount = 17,
        }

        public static readonly int VIRTUAL_METHOD_COUNT_LEVEL_DEFAULT = 18;

        private static IntPtr[] SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES;

        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
        public delegate int DXGISwapChainPresentDelegate(IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags);

        public delegate int DXGISwapChainPresentHookDelegate(UnmodifiableHook<DXGISwapChainPresentDelegate> hook, IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags);

        private DXGISwapChainPresentHookDelegate _present;
        private Hook<DXGISwapChainPresentDelegate> presentHook;

        static TestSwapChainHook()
        {
            SharpDX.DXGI.Rational rational = new SharpDX.DXGI.Rational(60, 1);
            SharpDX.DXGI.ModeDescription modeDescription = new SharpDX.DXGI.ModeDescription(100, 100, rational, SharpDX.DXGI.Format.R8G8B8A8_UNorm);
            SharpDX.DXGI.SampleDescription sampleDescription = new SharpDX.DXGI.SampleDescription(1, 0);

            using (SharpDX.Windows.RenderForm renderForm = new SharpDX.Windows.RenderForm())
            {
                SharpDX.DXGI.SwapChainDescription swapChainDescription = new SharpDX.DXGI.SwapChainDescription();
                swapChainDescription.BufferCount = 1;
                swapChainDescription.Flags = SharpDX.DXGI.SwapChainFlags.None;
                swapChainDescription.IsWindowed = true;
                swapChainDescription.ModeDescription = modeDescription;
                swapChainDescription.OutputHandle = renderForm.Handle;
                swapChainDescription.SampleDescription = sampleDescription;
                swapChainDescription.SwapEffect = SharpDX.DXGI.SwapEffect.Discard;
                swapChainDescription.Usage = SharpDX.DXGI.Usage.RenderTargetOutput;

                SharpDX.Direct3D11.Device device = null;
                SharpDX.DXGI.SwapChain swapChain = null;
                SharpDX.Direct3D11.Device.CreateWithSwapChain(SharpDX.Direct3D.DriverType.Hardware, SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport, swapChainDescription, out device, out swapChain);
                try
                {
                    IntPtr swapChainVirtualTable = Marshal.ReadIntPtr(swapChain.NativePointer);

                    SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES = new IntPtr[VIRTUAL_METHOD_COUNT_LEVEL_DEFAULT];
                    for (int x = 0; x < VIRTUAL_METHOD_COUNT_LEVEL_DEFAULT; x++)
                    {
                        SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES[x] = Marshal.ReadIntPtr(swapChainVirtualTable, x * IntPtr.Size);
                    }

                    device.Dispose();
                    swapChain.Dispose();
                }
                catch (Exception)
                {
                    if (device != null)
                    {
                        device.Dispose();
                    }

                    if (swapChain != null)
                    {
                        swapChain.Dispose();
                    }

                    throw;
                }
            }
        }

        public TestSwapChainHook()
        {
            this._present = null;

            this.presentHook = new Hook<DXGISwapChainPresentDelegate>(
                        SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES[(int)IDXGISwapChainVirtualTable.Present],
                        new DXGISwapChainPresentDelegate(hookPresent),
                        this);
        }

        public void activate()
        {
            this.presentHook.activate();
        }

        public void deactivate()
        {
            this.presentHook.deactivate();
        }

        private int hookPresent(IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags)
        {
            lock (this.presentHook)
            {
                if (this._present == null)
                {
                    return this.presentHook.original(thisPtr, syncInterval, flags);
                }
                else
                {
                    return this._present(new UnmodifiableHook<DXGISwapChainPresentDelegate>(this.presentHook), thisPtr, syncInterval, flags);
                }
            }
        }

        public DXGISwapChainPresentHookDelegate present
        {
            get
            {
                lock (this.presentHook)
                {
                    return this._present;
                }
            }
            set
            {
                lock (this.presentHook)
                {
                    this._present = value;
                }
            }
        }
    }
}

使用代码

初始化

private TestSwapChain swapChainHook;
private bool capture = false;
private object captureLock = new object();

this.swapChainHook = new TestSwapChainHook();
this.swapChainHook.present = presentHook;
this.swapChainHook.activate();

EDIT

我使用了不同的方法来捕获中描述的屏幕截图this https://stackoverflow.com/questions/16020988/how-to-create-bitmap-from-surface-sharpdx关联。然而我的截图是这样的:

现在这似乎是我的转换设置或其他问题,但我无法找出我到底需要做什么来解决它。我知道我要转换为位图的表面使用 DXGI_FORMAT_R10G10B10A2_UNORM 格式(32 位,每种颜色 10 位,我认为 2 位用于 alpha?)。但我不确定这在 for 循环中是如何工作的(跳过字节和其他东西)。我只是简单地复制粘贴它。

新的钩子函数

private int presentHook(UnmodifiableHook<IDXGISwapChainHook.DXGISwapChainPresentDelegate> hook, IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags)
{
    try
    {
        lock (this.captureLock)
        {
            if (this.capture)
            {
                SharpDX.DXGI.SwapChain swapChain = (SharpDX.DXGI.SwapChain)thisPtr;

                using (SharpDX.Direct3D11.Texture2D backBuffer = swapChain.GetBackBuffer<SharpDX.Direct3D11.Texture2D>(0))
                {
                    SharpDX.Direct3D11.Texture2DDescription texture2DDescription = backBuffer.Description;
                    texture2DDescription.CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.Read;
                    texture2DDescription.Usage = SharpDX.Direct3D11.ResourceUsage.Staging;
                    texture2DDescription.OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None;
                    texture2DDescription.BindFlags = SharpDX.Direct3D11.BindFlags.None;

                    using (SharpDX.Direct3D11.Texture2D texture = new SharpDX.Direct3D11.Texture2D(backBuffer.Device, texture2DDescription))
                    {
                        //DXGI_FORMAT_R10G10B10A2_UNORM
                        backBuffer.Device.ImmediateContext.CopyResource(backBuffer, texture);

                        using (SharpDX.DXGI.Surface surface = texture.QueryInterface<SharpDX.DXGI.Surface>())
                        {
                            SharpDX.DataStream dataStream;
                            SharpDX.DataRectangle map = surface.Map(SharpDX.DXGI.MapFlags.Read, out dataStream);
                            try
                            {
                                byte[] pixelData = new byte[surface.Description.Width * surface.Description.Height * 4];
                                int lines = (int)(dataStream.Length / map.Pitch);
                                int dataCounter = 0;
                                int actualWidth = surface.Description.Width * 4;

                                for (int y = 0; y < lines; y++)
                                {
                                    for (int x = 0; x < map.Pitch; x++)
                                    {
                                        if (x < actualWidth)
                                        {
                                            pixelData[dataCounter++] = dataStream.Read<byte>();
                                        }
                                        else
                                        {
                                            dataStream.Read<byte>();
                                        }
                                    }
                                }

                                GCHandle handle = GCHandle.Alloc(pixelData, GCHandleType.Pinned);
                                try
                                {
                                    using (Bitmap bitmap = new Bitmap(surface.Description.Width, surface.Description.Height, map.Pitch, PixelFormat.Format32bppArgb, handle.AddrOfPinnedObject()))
                                    {
                                        bitmap.Save(@"C:\Users\SOMEUSERNAME\Desktop\test.bmp");
                                    }
                                }
                                finally
                                {
                                    if (handle.IsAllocated)
                                    {
                                        handle.Free();
                                    }
                                }
                            }
                            finally
                            {
                                surface.Unmap();

                                dataStream.Dispose();
                            }
                        }
                    }
                }

                this.capture = false;
            }
        }
    }
    catch(Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }

    return hook.original(thisPtr, syncInterval, flags);
}

Answer

结果 DXGI_FORMAT_R10G10B10A2_UNORM 格式采用以下位格式:

A=alpha
B=blue
G=green
R=red

AABBBBBB BBBBGGGG GGGGGGRR RRRRRRRR

Format32bppArgb 的字节顺序如下:

BGRA

所以最终的循环代码将是:

while (pixelIndex < pixelData.Length)
{
    uint currentPixel = dataStream.Read<uint>();

    uint r = (currentPixel & 0x3FF);
    uint g = (currentPixel & 0xFFC00) >> 10;
    uint b = (currentPixel & 0x3FF00000) >> 20;
    uint a = (currentPixel & 0xC0000000) >> 30;

    pixelData[pixelIndex++] = (byte)(b >> 2);
    pixelData[pixelIndex++] = (byte)(g >> 2);
    pixelData[pixelIndex++] = (byte)(r >> 2);
    pixelData[pixelIndex++] = (byte)(a << 6);

    while ((pixelIndex % map.Pitch) >= actualWidth)
    {
        dataStream.Read<byte>();
        pixelIndex++;
    }
}

该屏幕截图看起来确实像是 R10G10B10A2 被塞进了 R8G8B8A8。我还没有测试你的代码,但我们应该有这个位布局

xxxxxxxx yyyyyyyy zzzzzzzz wwwwwwww
RRRRRRRR RRGGGGGG GGGGBBBB BBBBBBAA

你可以按如下方式提取它们

byte x = data[ptr++];
byte y = data[ptr++];
byte z = data[ptr++];
byte w = data[ptr++];

int r = x << 2 | y >> 6;
int g = (y & 0x3F) << 4 | z >> 4;
int b = (z & 0xF) << 6 | w >> 2;
int a = w & 0x3;

其中 r、g、b 现在具有 10 位分辨率。如果你想将它们缩放回字节,你可以使用 (byte)(r >> 2) 来实现。

Update

这将取代你的双 for 循环。我无法对此进行测试,因此我不想进一步推进它,但我相信这个想法是正确的。最后一次检查应跳过每行中的填充字节。

while(dataCounter < pixelData.Length)
{

    byte x = dataStream.Read<byte>();
    byte y = dataStream.Read<byte>();
    byte z = dataStream.Read<byte>();
    byte w = dataStream.Read<byte>();

    int r = x << 2 | y >> 6;
    int g = (y & 0x3F) << 4 | z >> 4;
    int b = (z & 0xF) << 6 | w >> 2;
    int a = w & 0x3;

    pixelData[dataCounter++] = (byte)(r >> 2);
    pixelData[dataCounter++] = (byte)(g >> 2);
    pixelData[dataCounter++] = (byte)(b >> 2);
    pixelData[dataCounter++] = (byte)(a << 6);

    while((dataCounter % map.Pitch) >= actualWidth)
    {
        dataStream.Read<byte>();
        dataCounter++;
    }

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 SharpDX 和 EasyHook 捕获全屏 DX11 程序的屏幕截图 的相关文章

  • Task.Factory.StartNew 或 Parallel.ForEach 对于许多长时间运行的任务? [复制]

    这个问题在这里已经有答案了 可能的重复 Parallel ForEach 与 Task Factory StartNew https stackoverflow com questions 5009181 parallel foreach
  • 使用sqlbulkcopy之前如何创建表

    我有一个 DBF 文件 我正在尝试导入该文件 然后将其写入 SQL 表 我遇到的问题是 如果我使用 SqlBulkCopy 它需要我提前创建表 但在我的场景中这是不可能的 因为 dbf 文件不断变化 到目前为止 这是我的代码 public
  • 成员字段、构建顺序

    在 C 中 当执行如下所示的操作时 构造顺序是否得到保证 Logger Logger kFilePath logs runtime log logFile kFilePath 是的 施工顺序始终得到保证 但是 不能保证它与对象在初始值设定项
  • 如何向WebRequest添加参数?

    我需要从 Web 服务调用一个方法 所以我编写了以下代码 private string urlPath http xxx xxx xxx manager string request urlPath index php org get or
  • 如何部署包含第三方 DLL 文件的 C# 应用程序?

    首先 我对部署了解不多 我希望我的问题有意义 我需要将 C 应用程序安装 部署到多个桌面 它需要一个第三方 DLL 文件 一个 C 库 lpsolve55 dll 对于那些感兴趣的人 它是一个免费的 MIP LP 求解器 请参阅 lpsol
  • 应用新设置时如何防止 GraphicsDevice 被丢弃?

    我的游戏窗口允许手动调整大小 这意味着它可以像任何其他普通窗口一样通过拖动其边缘来调整大小 游戏还利用了RenderTarget2D rt2d 在主 Draw 方法中设置主渲染目标 GraphicsDevice SetRenderTarge
  • 多个线程访问一个变量

    我在正在读的一本教科书中发现了这个问题 下面也给出了解决方案 我无法理解最小值怎么可能是 2 为什么一个线程不能读取 0 而所有其他线程都执行并写入 1 而无论是1还是2 最后写入的线程仍然必须完成自己的循环 int n 0 int mai
  • 根据 Active Directory 策略检查密码[重复]

    这个问题在这里已经有答案了 我有一个允许用户更改其 AD 密码的前端 有没有办法获取特定用户及其属性 长度 复杂性 的密码策略 例如细粒度 有没有办法根据此特定策略检查字符串 xyz121 编辑 我不想检查活动目录中存储的当前密码 我想检查
  • 使用 catch all 字典属性将 json 序列化为对象

    我想使用 JSON net 反序列化为对象 但将未映射的属性放入字典属性中 是否可以 例如给定 json one 1 two 2 three 3 和 C 类 public class Mapped public int One get se
  • 何时分离或加入 boost 线程?

    我有一个方法 大约每 30 秒触发一次 我需要在一个线程中包含它 我有一个可以从类外调用的方法 像 call Threaded Method 这样的东西会创建一个线程 该线程本身会调用最终的线程方法 这些是 MyClass 的方法 void
  • 文件加密与解密问题

    我一直在尝试在 VC Express 2010 中加密和解密文件 我见过的所有教程和文档都需要两个FileStreams 来加密文件 一个用于读取未加密的版本 另一个用于加密 当我实际编写代码时 它不断抛出错误 告诉我它无法打开该文件 因为
  • 在 Windows 上使用 C/C++ 开发时省略 msvcr100.dll?

    是否可以在 Windows 上使用 C C 进行开发而不链接到 msvcr100 dll 我知道这是 Windows 的标准 c 库 但我想知道如果我没有安装 Visual Studio 或 Redistributable 软件包 我的计算
  • 如果项目包含多个文件夹,如何使用 Add-Migration

    我想Add Migration使用我的 DbContext 但出现错误 The term add migration is not recognized as the name of a cmdlet function script fil
  • Code::Blocks 中的调试似乎不起作用 - 缺少调试符号

    我正在尝试在 Code Blocks 中调试程序 我跟着本指南 http wiki codeblocks org index php title Debugging with Code Blocks and 这个短视频 http www y
  • 使用 WinAPI 连接禁用的显示设备

    我的问题是启用禁用的监视器ChangeDisplaySettingsEx 我想这不是火箭科学 但经过一番挖掘后 它看起来仍然是不可能的 我找到了一种根据找到的 Microsoft 代码示例禁用所有辅助显示器的方法here https msd
  • 在 C# 窗口应用程序中运行 C/C++ 控制台应用程序?

    现在 我想开发一个简单的应用程序 因此我决定最快的编码方式是 C NET 但现在 我很难实现我需要的功能之一 我想做的是在 C 应用程序的窗口内运行 C C 控制台应用程序 就像在虚幻前端中一样 添加一点通信方式 以便我可以为控制台应用程序
  • 包含从代码隐藏 (ASP.NET C#) 到 ASPX 中的图像概述的图像列表 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • Web API 2.0 使用 pascalcase 模型接收驼峰式命名的 JSON 数据

    我正在尝试对我的 Web API 进行 PUT 调用 我在 WebApiConfig cs 中设置了以下内容 以处理以驼峰形式将数据发送回我的 Web 项目 config Formatters JsonFormatter Serialize
  • C# 和断点 - 这里有魔术师吗?

    我有这个 public static void ByLinkText string text for var i 0 i lt 50 i try Setup Driver FindElement By LinkText text Click
  • boost::spirit::qi::语法和可变参数模板

    我在使用可变参数模板定义语法时面临一个问题 我首先定义一些包含在某些结构中的简单语法 例如纬度 经度 如下所示 include

随机推荐