요즘 디아블로 3를 즐기고 있는데 로그인 에러 때문에 빡치더군요. 돈 주고 게임을 샀는데 할 수가 없어요. 그러다 로그인 과정에 일정한 패턴도 있겠다. 한번 자동으로 만들어보자 해서 만들어보게 되었습니다. ( 이렇게 프로그래밍 스킬을 써먹는 것 이지요. )
열어줘!! 열어달라고!!!
일단 크게 3 부분이 필요하다는 것을 정리했습니다.
1. 전역 키 후킹
매크로 프로그램 같은 경우 게임의 배경에서 돌아가야 합니다. 포커스가 디아블로에 있다 하더라도 자동 로그인 프로그램이 on/off 될수 있도록 전역적인 키 후킹이 필요합니다. ( 예. F1로 자동 로그인 켜기, F2로 자동 로그인 끄기 )
2. 패턴화된 작업을 자동화
보통 로그인이 과정이 아이디는 입력해놓으면 고정되고, 패스워드 입력 -> 접속 버튼 클릭 -> (에러난 경우 ) 에러창 출력 -> 에러창 닫기 -> 패스워드 입력 -> ... 대략 이렇습니다. 이 과정만 자동화 해놓으면 되죠.
3. 화면 해상도
클릭 해야 될 부분이 해상도 마다 다릅니다. 모든 사람이 1920 * 1080 해상도를 쓰는 것이 아니므로, 클릭 좌표를 비율로 관리해야 했습니다. 일단 제가 사용하는 1680 * 1050 해상도를 기준으로 좌표를 클릭해야 할 좌표들을 얻은 다음 비율을 통해 대부분의 해상도에서 ( 와이드 기준 ) 작동 하도록 하였습니다.
일단 작업 해야될 부분을 정리 했고, 이제 실제로 만들어 보도록 하죠.
먼저 전역 키 후킹 클래스를 만들어봅니다. 구글링을 해보니 이미 CodeProject에 좋은 샘플이 있더군요. 이것을 기반으로 작업해보겠습니다. ( 출처 : http://www.codeproject.com/Articles/19004/A-Simple-C-Global-Low-Level-Keyboard-Hook )
// 후킹에 필요한 Win32API를 임포트 [DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId); [DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr hInstance); [DllImport("user32.dll")] static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam); [DllImport("kernel32.dll")] static extern IntPtr LoadLibrary(string lpFileName); // 후킹 프로시저 함수와 후킹 데이타 구조체 정의 delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam); keyboardHookProc mkeyboardHookProc; struct keyboardHookStruct { public int vkCode; public int scanCode; public int flags; public int time; public int dwExtraInfo; } // 후킹 후 불려질 콜백 함수 정의 public delegate bool CustomKeyEventHandler(Key k); public event CustomKeyEventHandler CustomKeyDown; public event CustomKeyEventHandler CustomKeyUp; // 후킹 시작 const int WH_KEYBOARD_LL = 13; public void hook() { IntPtr hInstance = LoadLibrary("User32"); mkeyboardHookProc = hookProc; hhook = SetWindowsHookEx(WH_KEYBOARD_LL, mkeyboardHookProc, hInstance, 0); } // 후킹 끝 public void unhook() { UnhookWindowsHookEx(hhook); } // 후킹 프로시저 public int hookProc(int code, int wParam, ref keyboardHookStruct lParam) { if (code >= 0) { Key key = KeyInterop.KeyFromVirtualKey(lParam.vkCode); // 등록된 키가 눌러졌으면 if (HookedKeys.Contains(key)) { bool bHandled = false; if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && CustomKeyDown != null) { bHandled = CustomKeyDown(key); } else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && CustomKeyUp != null) { bHandled = CustomKeyUp(key); } if (bHandled) return 1; } } return CallNextHookEx(hhook, code, wParam, ref lParam); }
코드는 상당히 간단합니다. HookedKeys에 후킹 하고 싶은 키과 불려질 함수를 등록 해두고, 해당 키가 입력 되었을때 등록된 함수가 호출 되는 방식입니다. (KeyDown, KeyUp을 구분 해둠)
실제 사용하는 부분입니다.
// F1과 F2키를 후킹 하도록 한다. // 위의 키가 눌리면 CustomKeyUpEvent가 호출 mGlobalHooker = new GlobalHooker(); mGlobalHooker.HookedKeys.Add(Key.F1); mGlobalHooker.HookedKeys.Add(Key.F2); mGlobalHooker.CustomKeyUp += new CustomKeyEventHandler(CustomKeyUpEvent);
이제 키 후킹은 되었으니 자동 로그인 부분을 만들어 보죠. 제가 생각하는 구조는 필요한 암호를 입력 받아놓고, 패스워드 필드에 패스워드 입력 후 ( 붙여넣기 ) 접속 버튼을 클릭. 그 다음 에러 창이 뜨고, 에러창 닫기를 클릭. 다시 패스워드 필드를 클릭 하고 ( 가끔 포커스가 아이디 필드로 날아가니 ) 다시 패스워드 입력 후... 이후 반복 작업입니다.
대략 4가지 일을 해야 합니다. 이 모든 일이 한번에 일어나면 이벤트 동작이 제대로 안됩니다. 이전 이벤트가 씹히기도 하죠. 그래서 각 이벤트를 일정 딜레이를 주고, 발생하도록 합니다. 일단 그전에 클릭 해야될 좌표를 구해야 합니다. 제가 1680 * 1050 해상도를 사용하는데, 이 해상도를 기준으로 클릭 좌표를 화면 해상도 비율로 구해놓았습니다. 그렇기 때문에 해상도가 변해도 클릭해야 될 곳을 정확히 클릭하도록 했습니다.
// 마우스와 키 입력 이벤트를 생성하기 위한 Win32Api 불러오기 [DllImport("user32.dll")] static extern void keybd_event(byte vk, byte scan, int flags, int extrainfo); [DllImport("user32.dll")] static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo); [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)] static extern void SetCursorPos(uint X, uint Y); // 현재 화면 해상도(게임 해상도)를 비율로 구함 // 이 작업은 자동 로그인이 시작 될때 실행 됨 mScreenWidthRatio = 1.0 / 1680 * SystemParameters.PrimaryScreenWidth; mScreenHeightRatio = 1.0 / 1050 * SystemParameters.PrimaryScreenHeight; // 작업 이벤트를 순차적으로 등록 mEvents = new ArrayList(); mEvents.Add(new LoginEventHandler(LoginEvent1)); mEvents.Add(new LoginEventHandler(LoginEvent2)); mEvents.Add(new LoginEventHandler(LoginEvent3)); mEvents.Add(new LoginEventHandler(LoginEvent4)); // 이벤트 1 - 패스워드 필드 클릭 void LoginEvent1() { double CursorX = mScreenWidthRatio * 835; double CursorY = mScreenHeightRatio * 686; SetCursorPos((uint)CursorX, (uint)CursorY); mouse_event(LBUTTONDOWN | LBUTTONUP, (uint)CursorX, (uint)CursorY, 0, 0); } // 이벤트 2 - 패스워드 붙여넣기 void LoginEvent2() { keybd_event(CTRLKEY, 0, 0, 0); keybd_event(VKEY, 0, 0, 0); keybd_event(CTRLKEY, 0, KEYEVENT_UP, 0); } // 이벤트 3 - 접속 버튼 클릭 void LoginEvent3() { double CursorX = mScreenWidthRatio * 835; double CursorY = mScreenHeightRatio * 835; SetCursorPos((uint)CursorX, (uint)CursorY); mouse_event(LBUTTONDOWN | LBUTTONUP, (uint)CursorX, (uint)CursorY, 0, 0); } // 이벤트 4 - 에러37 확인 버튼 누르기 void LoginEvent4() { double CursorX = mScreenWidthRatio * 835; double CursorY = mScreenHeightRatio * 615; SetCursorPos((uint)CursorX, (uint)CursorY); mouse_event(LBUTTONDOWN | LBUTTONUP, (uint)CursorX, (uint)CursorY, 0, 0); }
각 이벤트들을 배열리스트에 담아두고 지정된 딜레이 시간마다 순차적으로 호출 되게 합니다. 여기서는 F1키가 입력되면 작업을 실행도록 합니다.
// 타이머를 생성 TimerClock = new DispatcherTimer(); TimerClock.Tick += new EventHandler(Timer_Tick); // 등록된 키가 눌리면 불려지는 함수 bool CustomKeyUpEvent(Key k) { switch (k) { case Key.F1: { if (!TimerClock.IsEnabled) { // 해상도 비율로 사용 가능하게 비율을 구함 mScreenWidthRatio = 1.0 / 1680 * SystemParameters.PrimaryScreenWidth; mScreenHeightRatio = 1.0 / 1050 * SystemParameters.PrimaryScreenHeight; // 실행될 프로시저 인덱스 mCurrEventIDX = 0; // 입력된 딜레이 값을 지정 // 입력된 패스워드를 클립보드에 복사 miDelayTime = Convert.ToInt32(textBox1.Text); Clipboard.SetData(DataFormats.Text, textBox2.Text); // 타이머 실행 TimerClock.Interval = new TimeSpan(0, 0, 0, 0, miDelayTime); TimerClock.Start(); } else { TimerClock.Stop(); } } return true; // F2키는 종료. 종료 되기전에 클립보드를 비운다. case Key.F2: { Clipboard.SetData(DataFormats.Text, ""); Application.Current.Shutdown(); } return true; default: return false; } } // 타이머에 의해서 호출된다 // 호출될때마다 순차적으로 등록된 이벤트 함수를 호출한다 void Timer_Tick(object sender, EventArgs e) { if (0 == mEvents.Count) return; if (mCurrEventIDX >= mEvents.Count) mCurrEventIDX = 0; (mEvents[mCurrEventIDX] as LoginEventHandler)(); ++mCurrEventIDX; }
대략 이걸로 기본적인 동작은 하게 됩니다. 이렇게 만들어진 결과물~ 쨔란~ 알고 보면 별거 아님.
인증 중인 창을 닫는 부분을 수정했습니다.
'프로그래밍' 카테고리의 다른 글
사고뭉치를 위한 디버깅 방법 #03 (5) | 2012.05.21 |
---|---|
PC에서 3D 입체 영상 게임 개발하기 #2 (2) | 2012.05.21 |
char* 문자열 버퍼 초기화의 내부 (13) | 2012.05.20 |
자기만의 게임엔진을 만들어보자! #1 (20) | 2012.05.18 |
게임 오브젝트 설계.. 나도 잘하고 싶다! #4 (5) | 2012.05.17 |