C# · 12月 28, 2021

c# – 使用SharpDX和EasyHook捕获全屏DX11程序的截图

在任何人提到之前,我指向 this链接,以了解如何将backbuffer复制到位图.

现在的情况

>我被注入目标进程
>目标进程’FeatureLevel = Level_11_0
>目标SwapChain正在使用DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH标志.
> SwapChain :: Present函数挂钩.
>屏幕截图显示黑色和目标进程崩溃.没有截图过程运行正常.

期望的情况

使截图正确,让目标进程继续正常执行.

注意Hook类与链接中的相同.我只添加了一个UnmodifiableHook版本,它的名称说.我省略了所有不重要的位.

TestSwapChainHook.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,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,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,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();

编辑

我用不同的方法来捕获this链接中描述的截图.不过我的屏幕截图如下:

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

新的钩子功能

private int presentHook(UnmodifiableHook<IDXGISwapChainHook.DXGISwapChainPresentDelegate> hook,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) { Message@R_352_2419@.Show(ex.ToString()); } return hook.original(thisPtr,flags);}

回答

结果DXGI_FORMAT_R10G10B10A2_UNORM格式是这种位格式:

A=alphaB=blueG=greenR=redAABBBBBB BBBBGGGG GGGGGGRR RRRRRRRR

而格式32bppArgb是这样的字节顺序:

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 wwwwwwwwRRRRRRRR 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)来实现.

更新

这将取代你的双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++; }}