본문 바로가기

개발이야기/C#

C# 윈도우(Windows) 비활성 캡쳐

728x90

C# 윈도우(Windows) 비활성 캡쳐



C#을 이용하여 응용프로그램(Forms) 을 제작할 시 여러가지 목적이 많지만 저 같은 경우는 게임 매크로를 만들때 주로 사용 했습니다. 손쉽게 오토핫키(Autohotkey)로 만들어도 되지만, 오핫키 특성상 소스를 사용자에게 그대로 노출하는 단점(?) 아닌 단점이 있습니다. 본인이 힘들게 고생해서 만든 코드를 노출하기 싫은 경우는 어떻게 할 수가 없습니다.


잠깐 집고 넘어가보겠습니다. 오토핫키의 소스를 어떻게 보는지 궁금하신 분들도 많을 텐데요, 간단하게 언급하고 넘어가겠습니다. 윈도우에 보시면 프로세서 관리자라고 있습니다. 거기서 현재 실행중인 프로그램들을 Dump를 뜰수가 있어요. 그렇게 덤프를 떠서 보게 되면 코드가 그대로 다 보입니다. 이는 오토핫키의 제작자의 의도라서 오핫키 자체만으로는 오핫키 코드를 숨길 수가 없습니다.


다시 원점으로 돌아와서! 비활성 캡쳐란 무엇인지에 대해서도 간단하게 설명드리겠습니다. 보통 일상적으로 사용자들이 하는 캡쳐는 키보드의 스크린샷(PriSc) 버튼을 눌러 화면을 캡쳐하는 것인데, 이는 해당하는 창이 모니터 화면에 그대로 노출 되어 있어야 가능합니다.

하지만 비활성 캡쳐는 창이 모니터에 노출안되고 구석에 숨겨져 있거나 최소화 되어 있어도 캡쳐가 가능 합니다. 이는 C# 프로그램에서 핸들(창 고유의 아이디)를 잡고 해당되는 핸들정보를 캡쳐하는 원리입니다. 여기서 그 방법에 대해 논해보고 코드를 알려드리고자 합니다.


게임 매크로의 간단한 원리는 화면을 캡쳐를 해서 특정 좌표의 이미지를 잘라냅니다. 그런다음 그 이미지가 내가 미리 저장해둔 이미지와 일치하거나 비슷하면 미리 정해둔 액션을 하는 원리이며, 이런 액션들을 여러개 등록하여 능동적인 매크로를 만들어 내는 것입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public void Capt(int x, int y, string name1, string name2)
 
        {
 
            try
 
            {
 
                Clipboard.Clear();
 
                var hwW = MableLib.FindWindow(null, name1);
 
                var hwL = MableLib.FindWindow(null, name2);
 
                MableLib.SetWindowPos((int)hwW, 0007204900x10);
 
                MableLib.SetWindowPos((int)hwL, 072107204900x10);
 
 
 
                Rectangle rect = Screen.PrimaryScreen.Bounds;
 
 
 
                Bitmap bmp = new Bitmap(3030, PixelFormat.Format32bppArgb);
 
                using (Graphics gr = Graphics.FromImage(bmp))
 
                {
 
                    gr.CopyFromScreen(x, y, 00, rect.Size);
 
                }
 
                bmp.Save(Winner_tmp);
 
                bmp.Dispose();
 
                bmp = null;
 
 
 
                Bitmap bmp4 = new Bitmap(Winner_tmp);
 
                var bitmap4 = bmp4.Clone(new Rectangle(00, bmp4.Width, bmp4.Height), PixelFormat.Format4bppIndexed);
 
                bitmap4.Save(Winner);
 
                bitmap4.Dispose();
 
                bitmap4 = null;
 
                bmp4.Dispose();
 
                bmp4 = null;
 
            }
 
            catch (Exception)
 
            {
 
                Msg(textBox1, "[C]오류 ");
 
            }
 
        }
 
 
cs


위의 코드는 핸들내의 

x, y좌표를 30 * 30 크기로 잘라서 

Bmp형식인 Winner_tmp.bmp로 저장을 하고, 

tmp파일의 화소를 4bit로 낮추어 

다시 Winner.bmp로 최종 저장 하는 과정 입니다.


비활성 캡쳐를 한다는 것은, 이미지 정보를 컴퓨터의 클립보드(Clipboard)에 임시로 저장하는 것을 의미 하기 때문에 오류를 최소화 하기 위해 작업전 클립보드를 비우고 시작합니다. 이 작업은 필수는 아니기 때문에 작업하기에 따라서 빼셔도 상관 없습니다.


여기서 캡쳐한 이미지를 4bit로 낮추는 이유에 대해 궁금하신 분들이 많을 텐데요 32비트(트루컬러), 16비트(하이컬러) 로 찍을 경우 우리 사람눈에는 분명 같은 이미지 인데, 컴퓨터의 색상값으로 보면 오차가 발생해 같은 이미지로 보질 않습니다. 그렇기 때문에 흑백 이미지이며 단순한 4bit로 변경 하는 것입니다.


코드에 보면 MableLib.FindWindow(null, name1); 라는 부분이 보이는데요, 이는 핸들을 잡는 문법 입니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class MableLib
 
    {
 
        [DllImport("user32.dll")]
 
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
 
        [DllImport("user32.dll")]
 
        public static extern IntPtr FindWindowEx(IntPtr hWnd1, IntPtr hWnd2, string lpsz1, string lpsz2);
 
        [DllImport("User32.Dll", EntryPoint = "PostMessageA")]
 
        public static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam);
 
        [DllImport("user32.dll")]
 
        public static extern int SendMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam);
 
        [DllImport("user32")]
 
        public static extern int SetWindowPos(int hwnd, int hWndInsertAfter, int x, int y, int cx, int cy, int wFlags);
 
 
 
        public enum WMessages : int
 
        {
 
            WM_LBUTTONDOWN = 0x201//Left mousebutton down
 
            WM_LBUTTONUP = 0x202,  //Left mousebutton up
 
            WM_LBUTTONDBLCLK = 0x203//Left mousebutton doubleclick
 
            WM_RBUTTONDOWN = 0x204//Right mousebutton down
 
            WM_RBUTTONUP = 0x205,   //Right mousebutton up
 
            WM_RBUTTONDBLCLK = 0x206//Right mousebutton doubleclick
 
            WM_KEYDOWN = 0x100,  //Key down
 
            WM_KEYUP = 0x101,   //Key up
 
            WM_SYSKEYDOWN = 0x104,
 
            WM_SYSKEYUP = 0x105,
 
            WM_CHAR = 0x102,
 
            WM_COMMAND = 0x111
 
        }
 
 
 
        public static IntPtr NoxFind(string window)
 
        {
 
            IntPtr hw1 = FindWindow("Qt5QWindowIcon", window);
 
            IntPtr hw2 = FindWindowEx(hw1, IntPtr.Zero, null"ScreenBoardClassWindow");
 
            IntPtr hw3 = FindWindowEx(hw2, IntPtr.Zero, null"QWidgetClassWindow");
 
            return hw3;
 
        }
 
 
 
        public static void NoxClick(IntPtr Id, int X, int Y)
 
        {
 
            PostMessage(Id, (int)WMessages.WM_LBUTTONDOWN, 1new IntPtr(Y * 0x10000 + X));
 
            PostMessage(Id, (int)WMessages.WM_LBUTTONUP, 0new IntPtr(Y * 0x10000 + X));
 
        }     
 
 
 
        public static int imageComp(string a, statusList b)
 
        {
 
            using (Bitmap A = new Bitmap(a))
 
            {
 
                using (Bitmap B = new Bitmap(b.imgName))
 
                {
 
                    for(int i=0; i<A.Width; i++)
 
                    {
 
                        for (int j=0; j<B.Height; j++)
 
                        {
 
                            if(A.GetPixel(i, j).ToString() != B.GetPixel(i, j).ToString())
 
                            {
 
                                return 0;
 
                            }
 
                        }
 
                    }
 
                }
 
            }            
 
            return 1;
 
        }
 
    }
cs



위 클래스에 대해 설명 드리겠습니다.


FindWindow : 부모핸들을 잡는 함수

FindWindowEx : 먼저 잡은 부모핸들내에 있는 자식 핸들을 잡는 함수

PostMessage, SendMessage : 잡은 핸들에 특정 명령을 보내는 함수

NoxFind : 위에 잡은 핸들명으로 휴대폰 에뮬레이터인 녹스(Nox)핸들을 잡는 함수

NoxClick : 위에 잡은 핸들명으로 녹스창을 마우스 클릭 명령을 함수

imageComp : 캡쳐한 이미지와 미리 저장해둔 이미지 2개를 (a, b) 비교하여 같은 이미지인지 확인 하는 함수


핸들에 명령을 내리기 위해서는 부모핸들이 아닌 자식핸들에 명령을 해야 합니다. 그렇기 때문에 반드시 부모핸들을 먼저 찾고 그안에 있는 자식핸들까지 찾은 후에 작업을 해야 합니다.


여기 공개된 코드만으로 웬만한 게임의 매크로는 제작 가능합니다. 요즘 게임 매크로는 대부분 게임사에서 비인가한 프로그램 이기 때문에 개인의 편의를 위해서만 사용해야 하며 절대로 배포 해서는 안됩니다.


코드 작업중에 궁금하신 내용은 댓글을 주시면 답변 드리겠습니다.