스크린 캡쳐 방식
스크린 캡쳐 방식은 크게 네 가지로 분류할 수 있다. GDI, DirectX, Window Media API, 그리고 Open GL의 API를 사용할 수 있다. Capturing the Screen은 앞선 세 가지 방식에 대해 간략히 설명하고 있다.
데스크탑 스크린 캡쳐
구글링을 하면 수많은 스크린 캡쳐 예제를 볼 수 있다. 하지만 대부분의 예제가 데스크탑 스크린 캡쳐만 할 수 있을 뿐이다. 당연히 DirectX와 같은 3D API가 아닌 GDI 방식만 사용한다. 데스크탑 스크린 캡쳐의 경우에는 전체 화면 스크린샷, 애플리케이션 윈도우 스크린샷 등의 다양한 기능을 쉽게 찾아볼 수 있다.
참고 자료 (VC++)
- Screen Capturing By Holzhauer.
참고 자료 (C#)
- Image Capture By emxx
- Capturing an image of the desktop or its work area.
- Capturing an image of a control or its client area.
- Capturing an image of what’s underneath a control or its client area.
- Converting images from one format to another.
- Converting an image to usable icon.
- Cropper in C#
C#으로 제작된 데스크탑 스크린샷 애플리케이션이다. 데스크탑 스크린샷 뿐만 아니라 지정된 화면 영역만 스크린 캡쳐할 수 있는 기능을 갖추었다. 또한 Gif 등의 이미지 프로세싱 플러그인 뿐만 아니라 OneNote, Flickr 업로드 플러그인도 지원한다. 버전 1.5에서는 소스코드를 다운로드 받을 수 있었지만, 버전 1.8에서는 바이너리 및 인스톨러 다운로드만 가능해졌다. 하지만 Reflector를 사용하면 내부 구현을 살펴볼 수 있다.
- Screenshot in D3D9 mit C#
Managed DirectX를 사용한 스크린 캡쳐 소스 코드를 볼 수 있다.
게임 스크린 캡쳐
데스크탑 스크린 캡쳐 애플리케이션을 구현하는 것은 비교적 쉽다. 상용 소프트웨어가 갖춰야 할 여러가지 덕목을 구현하기가 만만하지는 않더라도, Xfire와 같은 게임 스크린 캡쳐 기능을 구현하는 것에 비하면 그야말로 누워서 떡먹기에 불과하다. 기본적으로 게임 스크린 캡쳐 애플리케이션 Shot은 두 가지 기능을 갖춰야 한다. 하나는 Ctrl+F9 키를 누루면 Shot의 스크린 캡쳐 기능을 호출할 수 있어야 한다. 또 하나는 스크린 캡쳐 기능 그 자체인데, 데스크탑 스크린 캡쳐와는 달리 골치 아픈 문제가 있다. 이 글에서는 DirectX의 경우를 중점적으로 다루겠다.
API Hooking의 필요성
다이렉트x 9.0에서 스크린샷 저장하기에 제시된 DirectX의 스크린샷 예제를 보자.
void SaveScreenShot( char* filename, int w, int h ) { //임시로 사용할 Surface IDirect3DSurface9* BackBuff; //시스템 메모리에 정해진 크기의 버퍼를 만듬. //화일로 저장하려면 당연히 시스템 메모리에 이미지가 있어야 되겠죠. g_pd3dDevice->CreateOffscreenPlainSurface( w, h, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT ,&BackBuff, NULL ); //백버퍼의 얻어옴 //문서란 6번에 글자찍기에도 사용한 함수 HRESULT hr = g_pd3dDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &BackBuff ); //에러 체크 if(hr != D3D_OK) { BackBuff->Release(); return; } //파일로 저장 //2번째 인자는 저장할 이미지 타입을 넘겨주는 겁니다. //bmp, dds는 되는데..아직 tga나 jpg는 제대로 지원을 못합니다. D3DXSaveSurfaceToFile( filename, D3DXIFF_BMP, BackBuff, NULL, NULL ); //풀어줌 BackBuff->Release(); }
자세히 들여다보면 의심스러운 구석이 있다. IDirect3DDevice9 인터페이스의 참조인 g_pd3dDevice는 어떻게 가져온 것일까? 데스크탑 스크린 캡쳐를 구현할 때는 CreateDevice() 함수나 new Deivce() 생성자를 호출하면 됐다. 이때 자신의 메인 윈도우 핸들을 패러미터로 넘기면 모든 문제가 해결됐다. 그러나 게임 스크린샷 소프트웨어 Shot의 경우에는 문제가 다른 차원의 복잡도를 가진다. Shot의 프로세스 내부에서 카트라이더의 디바이스를 참조하려고 하면 오류가 발생한다. 이전처럼 CreateDevice() 함수에 카트라이더의 메인 윈도우 핸들을 넘기면, Device is Lost 등의 메시지를 보게 될 것이다. 이 문제를 해결하는 유일한 방법은 스크린샷을 찍는 모듈을 카트라이더 프로세스 내부에 로드하는 것 뿐이다. 아래 제시된 참고 자료을 통해 DLL Injection 등의 후킹 방법에 대해 알아볼 수 있다.
실제로 Xfire의 경우엔 이와 똑같은 방식을 사용한다. Xfire을 실행시킨 후에 Process Explorer로 Notepad2.exe 프로세스의 DLL 목록을 살펴보면, xfire_toucan_xxxx.dll을 확인할 수 있다.
API Hooking 참고 자료
- DLL Injection 가이드
- 마이크로소프트웨어 후킹 연재 by 신영진
특히 첫 두 편, 키보드 훅을 통해 키로거 제작하기.와 마우스 훅을 통한 화면 캡쳐 프로그램 제작하자를 정독하자.
키보드 후킹
일반적인 게임 스크린샷 소프트웨어는 핫키를 누르면 반응하도록 설계되어 있다. 레어 아이템을 얻었을 때 Ctrl+F9을 눌러 스크린샷을 찍고 싶을 것이다. 보통의 경우에는 RegisterHotKey() 등의 Windows API를 사용하여 핫키를 지정하는 것으로 충분하다. 데스크탑 스크린 캡쳐 애플리케이션의 경우라면 전역 핫키 지정으로 충분하다. 하지만 DirectInput을 사용하는 게임도 있다. 당연한 이야기지만 이때는 DirectInput 역시 후킹할 필요가 있다. 이와 관련된 예제 소스코드(별도 다운로드)를 키보드 후킹 참고 자료 중 DirectInput Hooking component and sample이 제공한다.
키보드 후킹 참고 자료
- [VC++] Beginner’s Tutorial – Using global hotkeys
- [C#] System Hotkey Component by Alexander Werner.
- [VC++] DirectInput Hooking component and sample
후킹 방지를 위한 노력
모두가 스크린샷과 같은 선한 의도를 갖고 후킹하는 것은 아니다. 많은 경우에 게임 핵이나 매크로 제작을 위해 DLL Injection 등을 시도한다. 상황이 이렇다보니 게임 개발진영에서는 후킹을 막으려고 노력한다. 후킹 막으려면..에서는 알려진 해킹 프로세스를 막는 방법에 대해 중점적으로 논한다. 그 내용 중에서 실행 후에 DLL 인젝션 방식을 사용해 후킹 들어 오는 류는 CreateProcess, CreateProcessInternal, CreateRemoteThread등을 감시하는 기능을 넣어 확인이 가능합니다.
라는 대목에 주목하자. 게임 스크린샷 애플리케이션에 해당하는 내용이다.
Xfire
Xfire는 한국 내의 유명한 온라인 게임을 제대로 지원하지 못하고 있다. 테스트해 본 결과, Eve Online이나 Starcraft 같은 게임의 경우에는 제대로 작동하지만, 카트라이더나 서든 어택 등의 경우에는 동작하지 않았다. Xfire 설치 폴더(C:\Program Files\Xfire)에는 xfire_games.ini라는 INI 설정 파일이 있다. 이 중에서 Starcraft 항목을 찾아보자. 금방 이해할 수 있겠지만, Xfire는 게임 각각의 정보를 바탕으로 작동한다. 개발자 입장에선 보다 일반적인 방식을 구현하고 싶을 것이다. 하지만 게임마다 렌더링 방식 등이 미묘하게 다르고, 경우에 따라선 해킹 방지를 위해 별도의 코드를 추가하기 때문에 Xfire의 접근 방식이 가장 현실적이라고 할 수 있다.
[4284] LongName=StarCraft Brood War ShortName=scbw LauncherDirKey=HKEY_LOCAL_MACHINE\SOFTWARE\Blizzard Entertainment\StarCraft\InstallPath LauncherExe=StarCraft.exe InstallHint=BrooDat.mpq Launch=%UA_LAUNCHER_EXE_PATH% %UA_LAUNCHER_EXTRA_ARGS% %UA_LAUNCHER_NETWORK_ARGS% InGameRenderer=DDRAW InGameFlags=DISPATCH_CONURE_MESSAGES|MULTIPLE_WINDOWS InGamePaletteIgnore=1-13,248-254 ChangeTolerance=6
Taksi
Taksi는 비디오 및 스크린 캡쳐 소프트웨어이다. 이것은 오픈소프 프로젝트의 산물이기 때문에 소스 코드를 살펴볼 수 있다. 게다가 상용 애플리케이션에 사용할 수 있을만큼 라이센스의 제약이 거의 없다. 하지만 게임 전용으로 개발한 소프트웨어가 아니라는 것이 문제다. 카트라이더의 스크린샷은 잘 찍었지만, 스페셜 포스의 경우에는 실패였다.
어찌됐든 Taksi는 DLL Injection 방식의 스크린샷 애플리케이션의 실제 구현을 살펴볼 수 있는 거의 유일한 기회를 제공한다. Taksi와 더불어 Direct3D hooking sample이 큰 도움이 될 것이다. 이 예제(별도 다운로드)는 PE 파일의 구조를 해석하는 것으로 보인다. 아마도 Xfire도 이와 유사한 방식을 사용할 것이라 생각한다.
디자인 이슈
워터마크
위의 그림은 Designing a Screen Shot System으로부터 가져왔다. 화면만 jpg 파일 등으로 변환시키면 된다고 생각하면 얼마나 큰 착각을 하고 있는 것인지 잘 보여주는 그림이다. 특히 워터마크는 생각해볼만한 가치가 있다. 굳이 스크린 캡쳐 애플리케이션을 제공하는 자사의 워터마크를 넣어야 하는 것은 아니다. 만약 그럴 경우 거부감을 표현하는 사용자가 있을 수도 있다. 다른 측면에서 워터마크를 생각해볼 수도 있는데, 사용자가 자신만의 시그너처를 넣고 싶어할 수 있다. 특히 블로그에 게임 스크린샷을 포스팅하는 경우에 외부의 펌으로부터 자신의 아이덴티티를 보호하기 위해 별도의 시그너처를 넣는 사용자를 심심찮게 볼 수 있다.
이미지 프로세싱
블로그 등에 포스팅할 땐 이미지의 파일 크기가 중요하다. 대부분의 서비스가 어떤 식으로든 업로드 제한을 두고 있다. 이미지의 리사이징도 중요한 이슈다. 800×600 짜리 이미지를 업로드하면 블로그의 레이아웃이 깨지기 십상이다.
Managed DirectX
Managed DirectX는 .NET Framework에서 3D 애플리케이션을 개발할 수 있도록 도와준다. DirectX 9부터 Managed DirectX가 지원되기 시작했는데, 현재까지 출시된 가장 최신의 SDK는 December 2006이다. 이전 릴리즈에선 MDX 2.0 Beta라는 이름을 달고 나오던 것이 이제는 Beta 딱지를 떼내었다. 문제는 최신 릴리즈으로 오면서 MDX를 .NET Framework 2.0이 아닌 1.1에 맞춰 컴파일했다는 사실이다. 이 때문에 Visual Studion 2005 등으로 작업하다 보면 디버깅 도중에 Loader Lock Error 메시지를 접하게 된다. 이를 해결하기 위한 방법이 몇 가지 있다.
- Visual Studion 2003으로 돌아가서 .NET Framework 1.1을 사용한다.
- 이전 MDX 2.0 Beta를 구한다. 하지만 더 이상 MDX 2.0 정식판을 출시되지 않을 것이다. XNA Framework로 대체될 예정이다.
- Loader Lock 옵션을 꺼버린다. 현실적으로 추천하는 방법이다. [디버그 / 예외 / Managed Debugging Assistants] 중에서 Loader Lock을 찾아서 체크 박스를 비운다.
Loader Lock 참고 자료
- .NET uno의 LoaderLock 에러
- Why do I get a ‘LoaderLock’ Error when debugging my Managed DirectX application
게임 스크린캡쳐를 하는 프로그램들이 어떤 방식으로 D3D 디바이스를 가져오는지 궁금했는데 DLL Injection이라는 기법이 있었군요. 잘 보고 갑니다.
아직 완성된 글이 아니라서 Draft로 해놓았어야 했는데, 깜박했네요. 그 사이 몇몇 내용이 추가됐습니다. 하하.
마이크로소프트웨어 후킹 연재19 by 신영재
=> 신영진으로 고쳐주세요. ㅎㅎ
내용이 굉장히 흥미롭네요.
내공이 느껴지는 글 입니다.
어이쿠. 예전 회사에서 같이 일하던 분의 성함을 적어버렸네요. 수정했습니다. ^^