콘텐츠로 건너뛰기

ID3D11Texture2D 이미지로 저장하기 ( .dds )

# 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)

# 참고 자료

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다