# ID3D11Texture2D 저장
ID3D11Texture2D
를 DDS 포맷 ( DirectDraw Surface )으로 저장해보겠습니다.
일반적으로 DDS를 처리하는데 DirectXTex 라이브러리를 사용하는데, 저는 구 컴파일러에서 빌드해야 하고 간단하게 DDS 포맷으로 저장하고 확인하기 위한 용도로 사용하는 것이라 DirectXTex 라이브러리를 사용하지 않고 간단하게 구현하여 사용했습니다.
DDS 포맷은 그림판에서 확인이 불가능하며 이미지 전용 뷰어를 설치하여 확인할 수 있습니다. ( 참고로 저는 꿀뷰를 사용합니다. )
# 개발에 앞서
저는 업무 특성 상 애플리케이션을 후킹하여 보안을 적용하기 위한 다양한 작업을 합니다.
그 중 하나는 화면 데이터를 보호하는 것으로 애플리케이션이 화면을 캡처하여 메모리로 저장할 때 데이터를 변조하여 화면의 특정 영역을 가리거나 복사를 불가능하게 만듭니다.
일반적으로 DirectX를 사용하여 화면 캡처를 하는 경우 CreateDXGIFactory
~ IDXGIOutputDuplication
함수를 사용합니다. 이 경우 CreateDXGIFactory
~ IDXGIOutputDuplication
까지의 과정을 잘 파악하고 있으면 캡처 시 데이터를 보호하는데 어려움은 없습니다.
하지만 일부 애플리케이션은 Shared Resource
기술을 사용하여 리소스를 공유하고 메모리에 저장하는 방식을 사용하고 있어 IDXGIOutputDuplication
에서 제어가 불가능한 경우가 존재합니다. 이 경우는 Shared Resource
영역을 후킹하여 데이터를 보호해야 하는데 Shared Resource
관련 함수는 리소스와 관련한 부분에서 다 사용할 수 있어 관련 함수를 분석하고 적용하는데 입력 리소스 또는 출력 리소스 그리고 변조한 데이터가 정상적으로 반영되었는지 화면이 아니라 파일로 확인하는 경우가 존재합니다.
# 소스 코드
Visual Studio 2013에서 사용
#include <windows.h>
#include <d3d11.h>
#include <memory>
#include <string>
#include <algorithm>
#pragma pack(push,1)
#define DDS_MAGIC 0x20534444 // "DDS "
#define DDS_FOURCC 0x00000004 // DDPF_FOURCC
#define DDS_RGB 0x00000040 // DDPF_RGB
#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS
#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE
#define DDS_LUMINANCEA 0x00020001 // DDPF_LUMINANCE | DDPF_ALPHAPIXELS
#define DDS_ALPHA 0x00000002 // DDPF_ALPHA
#define DDS_BUMPDUDV 0x00080000 // DDPF_BUMPDUDV
#define DDS_HEADER_FLAGS_TEXTURE 0x00001007 // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
#define DDS_HEADER_FLAGS_MIPMAP 0x00020000 // DDSD_MIPMAPCOUNT
#define DDS_HEADER_FLAGS_PITCH 0x00000008 // DDSD_PITCH
#define DDS_HEADER_FLAGS_LINEARSIZE 0x00080000 // DDSD_LINEARSIZE
#define DDS_SURFACE_FLAGS_TEXTURE 0x00001000 // DDSCAPS_TEXTURE
struct DDS_PIXELFORMAT {
DWORD dwSize;
DWORD dwFlags;
DWORD dwFourCC;
DWORD dwRGBBitCount;
DWORD dwRBitMask;
DWORD dwGBitMask;
DWORD dwBBitMask;
DWORD dwABitMask;
};
typedef struct {
DWORD dwSize;
DWORD dwFlags;
DWORD dwHeight;
DWORD dwWidth;
DWORD dwPitchOrLinearSize;
DWORD dwDepth;
DWORD dwMipMapCount;
DWORD dwReserved1[11];
DDS_PIXELFORMAT ddspf;
DWORD dwCaps;
DWORD dwCaps2;
DWORD dwCaps3;
DWORD dwCaps4;
DWORD dwReserved2;
} DDS_HEADER;
typedef struct {
DXGI_FORMAT dxgiFormat;
D3D10_RESOURCE_DIMENSION resourceDimension;
UINT miscFlag;
UINT arraySize;
UINT miscFlags2;
} DDS_HEADER_DXT10;
#pragma pack(pop)
const DDS_PIXELFORMAT DDSPF_A8R8G8B8 =
{ sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 };
struct com_deleter { void operator()(IUnknown* p) { if ( p ) { p->Release(); } } };
struct handle_closer { void operator()(HANDLE h) { if ( h != INVALID_HANDLE_VALUE ) CloseHandle(h); } };
template <typename T> using ScopedInterface = std::unique_ptr<T, com_deleter>;
template <typename T> ScopedInterface<T> make_com_ptr(T* p) { return ScopedInterface<T>(p); }
using ScopedHandle = std::unique_ptr<void, handle_closer>;
inline HANDLE safe_handle(HANDLE h) { return (h == INVALID_HANDLE_VALUE) ? nullptr : h; }
HRESULT SaveDDSFile(ID3D11DeviceContext* device_context, ID3D11Resource* source, const wchar_t* file_name)
{
if ( nullptr == device_context || nullptr == source || nullptr == file_name )
{
return E_INVALIDARG;
}
D3D11_RESOURCE_DIMENSION resource_type = D3D11_RESOURCE_DIMENSION_UNKNOWN;
source->GetType(&resource_type);
if ( resource_type != D3D11_RESOURCE_DIMENSION_TEXTURE2D )
{
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
}
ID3D11Texture2D* source_texture = nullptr;
HRESULT hr = source->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&source_texture));
if ( FAILED(hr) )
{
return hr;
}
auto _source_texture = make_com_ptr(source_texture);
D3D11_TEXTURE2D_DESC desc;
source_texture->GetDesc(&desc);
ID3D11Device* device = nullptr;
device_context->GetDevice(&device);
auto _device = make_com_ptr(device);
if ( desc.SampleDesc.Count != 1 && desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM )
{
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
}
ID3D11Texture2D* staging_texture = nullptr;
ScopedInterface<ID3D11Texture2D> _staging_texture;
if ( (desc.Usage == D3D11_USAGE_STAGING) && (desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ) )
{
staging_texture = source_texture;
_staging_texture = std::move(_source_texture);
}
else
{
desc.BindFlags = 0;
desc.MiscFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.Usage = D3D11_USAGE_STAGING;
hr = device->CreateTexture2D(&desc, nullptr, &staging_texture);
if ( FAILED(hr) )
{
return hr;
}
device_context->CopyResource(staging_texture, source_texture);
_staging_texture = make_com_ptr(staging_texture);
}
const DWORD kMaxHeaderSize = sizeof(DWORD) + sizeof(DDS_HEADER) + sizeof(DDS_HEADER_DXT10);
BYTE file_header[kMaxHeaderSize] = {};
*reinterpret_cast<DWORD*>(&file_header[0]) = DDS_MAGIC;
auto header = reinterpret_cast<DDS_HEADER*>(&file_header[0] + sizeof(DWORD));
size_t header_size = sizeof(DWORD) + sizeof(DDS_HEADER);
header->dwSize = sizeof(DDS_HEADER);
header->dwFlags = DDS_HEADER_FLAGS_TEXTURE | DDS_HEADER_FLAGS_MIPMAP;
header->dwHeight = desc.Height;
header->dwWidth = desc.Width;
header->dwMipMapCount = 1;
header->dwCaps = DDS_SURFACE_FLAGS_TEXTURE;
DDS_HEADER_DXT10* ext_header = nullptr;
memcpy_s(&header->ddspf, sizeof(header->ddspf), &DDSPF_A8R8G8B8, sizeof(DDS_PIXELFORMAT));
DWORD row_pitch = (DWORD(desc.Width) * 32 + 7u) / 8u;
DWORD row_count = (DWORD(desc.Height));
DWORD row_bytes = row_pitch * row_count;
header->dwFlags |= DDS_HEADER_FLAGS_PITCH;
header->dwPitchOrLinearSize = static_cast<DWORD>(row_pitch);
std::unique_ptr<BYTE[]> pixels(new (std::nothrow) BYTE[row_bytes]);
if ( !pixels )
{
return E_OUTOFMEMORY;
}
D3D11_MAPPED_SUBRESOURCE resource;
hr = device_context->Map(staging_texture, 0, D3D11_MAP_READ, 0, &resource);
if ( FAILED(hr) )
{
return hr;
}
auto src_data = static_cast<const BYTE*>(resource.pData);
if ( !src_data )
{
device_context->Unmap(staging_texture, 0);
return E_POINTER;
}
BYTE* dst_ptr = pixels.get();
const DWORD min_size = std::min<DWORD>(row_pitch, resource.RowPitch);
for ( DWORD h = 0; h < row_count; ++h )
{
memcpy_s(dst_ptr, row_pitch, src_data, min_size);
src_data += resource.RowPitch;
dst_ptr += row_pitch;
}
device_context->Unmap(staging_texture, 0);
ScopedHandle hFile(safe_handle(CreateFileW(file_name, GENERIC_WRITE | DELETE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)));
if ( !hFile )
{
return HRESULT_FROM_WIN32(GetLastError());
}
DWORD bytesWritten;
if ( !WriteFile(hFile.get(), file_header, static_cast<DWORD>(header_size), &bytesWritten, nullptr) )
{
return HRESULT_FROM_WIN32(GetLastError());
}
if ( bytesWritten != header_size )
{
return E_FAIL;
}
if ( !WriteFile(hFile.get(), pixels.get(), static_cast<DWORD>(row_bytes), &bytesWritten, nullptr) )
{
return HRESULT_FROM_WIN32(GetLastError());
}
if ( bytesWritten != row_bytes )
{
return E_FAIL;
}
return S_OK;
}
Code language: C++ (cpp)