C 게임 매크로 만들기 - C geim maekeulo mandeulgi

/*

 * http://sosal.kr/

 * made by so_Sal

 */

Autohotkey 사용법

글을 참조하셔서 autohotkey를 설치하시고, 간단한 사용법을 익히세요.

- 인터넷에 떠도는 exe파일로 된 매크로를 잘못 설치하시면 바이러스 위험이 있으니 꼭 조심해주세요.

Autohotkey 사용법

오토핫키 사용법을 체득하지 않으시고서는 이 글을 이해하실 수 없으세요 ㅠ

꼭 보고 이글을 읽으시기 바랍니다.

- loop, send examples

Loop 20
{
    Send {Tab down}  ; Auto-repeat consists of consecutive down-events (with no up-events).
    Sleep 30  ; The number of milliseconds between keystrokes (or use SetKeyDelay).
}

- send examples

Send {b down}{b up}
Send {TAB down}{TAB up}
Send {Up down}  ; Press down the up-arrow key.
Sleep 1000  ; Keep it down for one second.
Send {Up up}  ; Release the up-arrow key.

디아블로를 하면서 Autohotkey를 많이 썼는데요, 흔히 제자리에서 같은 공격을 반복하는 것을 자주 짰습니다.

제자리에서 좌클릭을 하면서 1,2,3,4 번호를 자동으로 쓰는 등등..

게임 매크로를 만들기 위해선 여러가지의 기능들이 필요하겠죠?

1. 원하는 키 자동으로 누르기: Send

2. 원하는 행동 반복하기: Sleep

3. 마우스 클릭하기: MouseClick

예제1) 제자리에서 쉬프트를 누른상태로 좌클릭을 무한히 반복한다.

C 게임 매크로 만들기 - C geim maekeulo mandeulgi

Edit this script 버튼을 누르셔서 소스 수정창으로 갑니다. 

; F1을 누르면 shift+마우스 우클릭을 무한히 반복

; F4를 누르면 매크로 종료

F1::

Loop{

Send, {shift down}

MouseClick, right

}

F4::Pause

- 소스를 입력한 화면

MouseClick, left 로 바꿔주시면 좌클릭을 반복하는 매크로가 됩니다.

파일(F) -> 저장(S), 혹은 Ctrl+S 버튼으로 저장을 하신 후에

Reload This Script를 선택해주시면

F1을 눌렀을 때, shift+우클릭을 무한히 반복하는 상태가 되며

F4를 눌렀을 때, 매크로를 정지하게 됩니다.

예2)  제자리에서 shift를 누른상태로 1,2,3,4 버튼 누르기

Edit this script 버튼을 누르셔서 소스 수정창으로 갑니다. 

; F2을 누르면 shift+1,2,3,4을 무한히 반복

; F4를 누르면 매크로 종료

F2::

Loop{

Send, {shift down}

Send, {1}

Sleep, 200

Send, {2}

Sleep, 200

Send, {3}

Sleep, 200

Send, {4}

}

F4::Pause

Sleep, 200을 써준 이유는 키보드 전송을 너무 빨리 할 시, 명령을 너무 빨리 보내서

스킬이 제대로 동작하지 않을 경우가 있기 때문에 해준것입니다.

Sleep은 1/1000초 (ms) 단위로 동작합니다.

따라서 Sleep, 200의 뜻은 0.2초동안 쉬어라. 라는 뜻입니다.

- 소스를 입력한 화면

파일(F) -> 저장(S), 혹은 Ctrl+S 버튼으로 저장을 하신 후에

해주시고, F2를 누르시면 shift + 1,2,3,4를 누르는 자동 매크로 탄생!
 

예제3) 응용편

shift를 누른상태로 1,2,3,4 버튼을 누르면서 마우스 좌클릭을 하라.

F2::

Loop{

   Send, {shift down}

   Send, {1}

   Sleep, 200

   Send, {2}

   MouseClick, left

   Sleep, 200

   Send, {3}

   Sleep, 200

   Send, {4}

    MouseClick, left

}

F4::Pause

[C/C++] 윈도우 마우스 후킹으로 제스처 인식 프로그램 만들기 (마무리)

  • 2019.07.25 00:48
  • ⌨ DEVELOPMENT/WIndows Hooking

마우스 후킹을 이용한 매크로 프로그램 제작

저번 포스팅에 이어 이번에도 마우스 후킹에 관련된 내용입니다.

아직 못 보셨다면 기본 마우스 후킹에 대한 내용을 보시고 보셔도 좋습니다.

https://wendys.tistory.com/110

[C/C++] 윈도우 마우스 후킹으로 제스처 인식 프로그램 만들기 (마우스 이동 방향 및 각도 구하기)

마우스 후킹을 이용한 매크로 프로그램 제작 저번 포스팅에 이어 이번에도 마우스 후킹에 관련된 내용입니다. 아직 못 보셨다면 기본 마우스 후킹에 대한 내용을 보시고 보셔도 좋습니다. https://wendys.tistory...

wendys.tistory.com

드디어 마무리 시간입니다.

이제 마우스의 이동 거리의 계산을 통한 방향 확인이 가능해진 시점에서 해야 할 일은 이동 방향의 목록을 만들어서 특정 기능을 할 수 있도록 하는 것만 남았습니다. 저번 시간에 80%까지 마무리 후 직접 해보는 걸 권장해드렸지만 이렇게 찾아주셨는데 마무리까지 책임져 드리는 게 좋을 것 같아서 진행하겠습니다.

C 게임 매크로 만들기 - C geim maekeulo mandeulgi

마지막으로 제시해드렸던 enum 은 보기가 조금 더 편한 거지 선호하시는 방법으로 하시길 추천드립니다.

enum mouse_gesture : long {
    unknown = 0,
    left,
    right,
    up,
    down
};

목록을 사용해야 하니 가장 간편한 std::list를 사용할 예정입니다. std::vector를 사용해도 되고, 어떤 걸 사용하던지 상관없습니다. 다만 List를 접근할 때는 전 보통 Lock Object를 사용합니다. 이유는 멀티 스레드 환경에서 1번 스레드가 List에 접근하여 값을 변경하고있는데, 이 때 2번 쓰레드가 그 값을 확인하려거나 동시에 변경을 시도하는 경우 원하지 않은 결과가 나오기 때문입니다. 그렇기 때문에 개발 시 List를 사용할 때엔 습관적으로 Lock Object를 사용하는 것이 좋습니다.

#include <mutex>
#include <list>

class hook_mouse_callback {
...
private:
...
    std::list<mouse_gesture> _gesture_list;
    std::mutex _gesture_list_lock;
};

List를 사용하기 위해서 멤버 변수로 _gesture_list를 추가하고, List접근 시 동기화를 위하여 Lock Object를 선언합니다. Lock Object는 CriticalSection을 사용해도 되고 std::mutex를 사용해도 되고 동기화 객체는 무엇이든 상관없습니다. 편한 걸로 사용하시되 주의하실 점은 동기화 오브젝트 사용 중 데드락이 걸리지 않도록 꼭 관리해주셔야 합니다.

동기화 오브젝트 사용 시 RAII라는 디자인 패턴을 주로 사용하며, Auto Lock 형태로써 지역 스코프가 완료되는 시점에 자동으로 해제를 하도록 하여 해제를 누락시키지 않는 기법이 있으니 참고하시면 좋습니다.

https://wendys.tistory.com/11

[C++] RAII 패턴 - Resource Acquisition Is Initialization Pattern

RAII RAII(Resource Acquisition Is Initialization)은 유명한 design pattern 중의 하나로 C++ 언어의 창시자인 Bjarne Stroustrup에 의해 제안되었다. RAII 패턴은 C++ 같이 개발자가 직접 resource 관리를 해주..

wendys.tistory.com

위와 같이 추가했으면 준비는 다 되었으니 본론으로 돌아와서 바로 코드로 보겠습니다.

if (wparam == WM_RBUTTONDOWN) {
    _is_right_button_down = true;

    //
    // mouse gesture clear.
    //

    _gesture_list.clear();

    _latest_mouse_point = mouse_param->pt;
}
else if (wparam == WM_MOUSEMOVE) {

    if (_is_right_button_down) {

        POINT current_point = mouse_param->pt;

        double distance = get_distance(_latest_mouse_point, current_point);
        if (distance < __min_distance) {
            break;
        }

        //
        // 방향 결정 ← ↑ ↓ → 목록 추가
        //

        double dx = _latest_mouse_point.x - current_point.x;
        double dy = _latest_mouse_point.y - current_point.y;

        double radian = atan2(dy, dx);
        double degree = (radian * 180) / __pi;

        if (degree < 0.0) {
            degree = degree + 360.0;
        }

        mouse_gesture gesture = mouse_gesture::unknown;
        if (degree < 35 || degree > 315) {
            gesture = mouse_gesture::left;
        }
        else if (degree > 135) {
            if (degree > 225) {
                gesture = mouse_gesture::down;
            }
            else {
                gesture = mouse_gesture::right;
            }
        }
        else {
            gesture = mouse_gesture::up;
        }

        //
        // 해당 방향을 list에 저장 (같은 방향인경우 추가하지 않고 진행)
        //

        if (_gesture_list.empty()) {
            _gesture_list.push_back(gesture);
        }
        else {
            if (_gesture_list.back() != gesture) {
                _gesture_list.push_back(gesture);
            }
        }

        //
        // 기준 포인트를 현재 포인트로 변환
        //

        _latest_mouse_point = current_point;
    }
}

WM_RBUTTONDOWN에서 _gesture_list.clear()를 호출하고 있습니다. 마우스 우클릭 이벤트가 발생할때마다 새로운 방향 및 커맨드로 판단을 해야하기때문에 위의 타이밍에 처리되도록 되었으며, WM_RBUTTONUP 시점에 clear() 처리를 해도 문제 없습니다만 최초 실행시점에도 clear를 하기위해 WM_RBUTTONDOWN 에서 처리되었습니다.

목록이 잘 생성되었을까요?

그렇다면 목록이 잘 생성되었는지는 어떻게 확인하면 될까요?? WM_RBUTTONUP 시점에서 판단하여 처리를 해줘야 합니다.

else if (wparam == WM_RBUTTONUP) {
    _is_right_button_down = false;

    {
        //
        // command handling
        //

        std::wstring command;
        std::lock_guard<std::mutex> lock(_gesture_list_lock);
        if (false == _gesture_list.empty()) {
            for (const auto& gesture : _gesture_list) {
                        
                if (mouse_gesture::left == gesture) {
                    command.push_back('L');
                }
                else if (mouse_gesture::up == gesture) {
                    command.push_back('U');
                }
                else if (mouse_gesture::down == gesture) {
                    command.push_back('D');
                }
                else if (mouse_gesture::right == gesture) {
                    command.push_back('R');
                }
            }

            command.push_back('\n');
            OutputDebugString(command.c_str());
        }
    }
}

WM_MOUSEMOVE시 추가되었던 _gesture_list를 확인하여 각각 방향에 대하여 LUDR 등 커맨드를 생성할 수 있습니다.

이렇게 생성된 커맨드를 이용하면 처음에 보여드렸던 마우스 매크로 프로그램을 만들 수 있습니다.

간단하게 커맨드 처리하는 내용을 보여드리자면 다음 코드처럼 처리하실 수 있습니다.

완성된 전체 코드

inline double get_distance(
    __in const POINT p1,
    __in const POINT p2
    ) {
    double distance = 0.0;
    distance = sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));

    return distance;
}

inline void keyboard_input(
    __in WORD virtual_key,
    __in bool extended,
    __in bool key_down
    ) {
    INPUT input = {};
    input.type = INPUT_KEYBOARD;
    input.ki.wVk = virtual_key;
    input.ki.dwFlags = key_down ? 0 : KEYEVENTF_KEYUP;
    if (extended) {
        input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
    }
    SendInput(1, &input, sizeof(INPUT));
}

inline void keyboard_input_down(
    __in WORD virtual_key,
    __in bool extended
    ) {
    keyboard_input(virtual_key, extended, true);
}
inline void keyboard_input_up(
    __in WORD virtual_key,
    __in bool extended
    ) {
    keyboard_input(virtual_key, extended, false);
}

bool hook_mouse_callback::execute_command(
    __in std::string command
    ) {

    bool is_execute_command = false;

    if (0 == command.compare("U")) {
        keyboard_input_down(VK_CONTROL, true);
        keyboard_input_down(VK_HOME, true);

        keyboard_input_up(VK_HOME, true);
        keyboard_input_up(VK_CONTROL, true);

        is_execute_command = true;
    }
    else if(0 == command.compare("D")) {
        keyboard_input_down(VK_CONTROL, true);
        keyboard_input_down(VK_END, true);

        keyboard_input_up(VK_END, true);
        keyboard_input_up(VK_CONTROL, true);

        is_execute_command = true;
    }

    return is_execute_command;
}

LRESULT hook_mouse_callback::new_function(
    __in int code,
    __in WPARAM wparam,
    __in LPARAM lparam
    ) {

    do {
        if (code < HC_ACTION) {
            break;
        }

        MOUSEHOOKSTRUCT* mouse_param = (MOUSEHOOKSTRUCT*)lparam;

        if (wparam == WM_RBUTTONDOWN) {
            _is_right_button_down = true;

            //
            // mouse gesture clear.
            //

            _gesture_list.clear();

            _latest_mouse_point = mouse_param->pt;
        }
        else if (wparam == WM_MOUSEMOVE) {
            if (_is_right_button_down) {

                POINT current_point = mouse_param->pt;

                double distance = get_distance(_latest_mouse_point, current_point);
                if (distance < __min_distance) {
                    break;
                }

                //
                // 방향 결정 ← ↑ ↓ → 목록 추가
                //

                double dx = _latest_mouse_point.x - current_point.x;
                double dy = _latest_mouse_point.y - current_point.y;

                double radian = atan2(dy, dx);
                double degree = (radian * 180) / __pi;

                if (degree < 0.0) {
                    degree = degree + 360.0;
                }

                mouse_gesture gesture = mouse_gesture::unknown;
                if (degree < 35 || degree > 315) {
                    gesture = mouse_gesture::left;
                }
                else if (degree > 135) {
                    if (degree > 225) {
                        gesture = mouse_gesture::down;
                    }
                    else {
                        gesture = mouse_gesture::right;
                    }
                }
                else {
                    gesture = mouse_gesture::up;
                }

                //
                // 해당 방향을 list에 저장 (같은 방향인경우 추가하지 않고 진행)
                //

                if (_gesture_list.empty()) {
                    _gesture_list.push_back(gesture);
                }
                else {
                    if (_gesture_list.back() != gesture) {
                        _gesture_list.push_back(gesture);
                    }
                }

                //
                // 기준 포인트를 현재 포인트로 변환
                //

                _latest_mouse_point = current_point;
            }
        }
        else if (wparam == WM_RBUTTONUP) {
            _is_right_button_down = false;

            {
                //
                // command handling
                //

                std::string command;
                std::lock_guard<std::mutex> lock(_gesture_list_lock);
                if (false == _gesture_list.empty()) {
                    for (const auto& gesture : _gesture_list) {
                        
                        if (mouse_gesture::left == gesture) {
                            command.push_back('L');
                        }
                        else if (mouse_gesture::up == gesture) {
                            command.push_back('U');
                        }
                        else if (mouse_gesture::down == gesture) {
                            command.push_back('D');
                        }
                        else if (mouse_gesture::right == gesture) {
                            command.push_back('R');
                        }
                    }

                    OutputDebugStringA(command.c_str());

                    if (execute_command(command)) {
                        
                        //
                        // execute command.
                        //

                        return 1;
                    }
                }
            }
        }
        else {
            break;
        }

    } while (false);

    return ::CallNextHookEx(_hook, code, wparam, lparam);
}

keyboard_input은 특정 기능을 위해서 사용하시면 될 것 같습니다. 예를 들어 위에서 처럼 ↑ command 처리 시 키보드 HOME 버튼을 시스템에 전달하거나, ↓ command 처리시 키보드 END 버튼을 전달하는 등 처리가 가능하며, 그 외에 ShellExecute, 모든 동작이 가능하기 때문에 command에 따라 원하는 기능을 구현하시면 됩니다.

여기서 핵심은 execute_command 호출 후 성공 시 return 1 처리를 하는 것에 있습니다.

이는 마우스 오른쪽 마우스 클릭이 콘텍스트 메뉴가 생성되게 되는데, 마우스 커맨드가 일어나는 시점에는 컨텍스트 메뉴가 뜨지 않게 하기 위함이기때문에 만약 마우스 로그 등의 기록을 위한경우에는 return 1 코드를 제거하여 컨텍스트 메뉴가 정상적으로 동작하도록 해야 합니다.

현재 만들어서 사용중인 프로그램은 개인적으로 만들어서 사용하다보니 UI 관련 부분이 완료가 안되어있어서 배포는 추후 할 예정입니다. 상관 없이 원하시면 비밀댓글로 요청해주세요 :) 해당 포스팅이 많은 분들에게 도움이 되었으면 좋겠습니다.

Sample Download

analog_note_mouse_hook.zip

0.07MB