반응형

wxWidgets을 빌드하고 간단한 예제를 만들어 보자.

 

vcpkg install wxwidgets

 

약 4분 정도가 걸린다.

 

 

 

 

콘솔 프로젝트라면 SubSystem을 Windows로 바꿔준다.

 

 

간단한 예제.

#include <wx/wx.h>

class MyApp : public wxApp
{
public:
	bool OnInit() override;
};

wxIMPLEMENT_APP(MyApp);

class MyFrame : public wxFrame
{
public:
	MyFrame();

private:
	void OnHello(wxCommandEvent& event);
	void OnExit(wxCommandEvent& event);
	void OnAbout(wxCommandEvent& event);
};

enum
{
	ID_Hello = 1
};

bool MyApp::OnInit()
{
	MyFrame* frame = new MyFrame();
	frame->Show(true);
	return true;
}

MyFrame::MyFrame()
	: wxFrame(nullptr, wxID_ANY, "Hello World")
{
	wxMenu* menuFile = new wxMenu;
	menuFile->Append(ID_Hello, "&Hello...\tCtrl-H", "Help string shown in status bar for this menu item");
	menuFile->AppendSeparator();
	menuFile->Append(wxID_EXIT);

	wxMenu* menuHelp = new wxMenu;
	menuHelp->Append(wxID_ABOUT);

	wxMenuBar* menuBar = new wxMenuBar;
	menuBar->Append(menuFile, "&File");
	menuBar->Append(menuHelp, "&Help");

	SetMenuBar(menuBar);

	CreateStatusBar();
	SetStatusText("Welcome to wxWidgets!");

	Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
	Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
	Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
}

void MyFrame::OnExit(wxCommandEvent& event)
{
	Close(true);
}

void MyFrame::OnAbout(wxCommandEvent& event)
{
	wxMessageBox("This is a wxWidgets Hello World example", "About Hello World", wxOK | wxICON_INFORMATION);
}

void MyFrame::OnHello(wxCommandEvent& event)
{
	wxLogMessage("Hello world from wxWidgets!");
}

 

 

 

버튼을 추가해 보자.

.
.
.
	CreateStatusBar();
	SetStatusText("Welcome to wxWidgets!");

	wxPanel* panel = new wxPanel(this, wxID_ANY);
	wxButton* myButton = new wxButton(panel, wxID_ANY, "Press Me", wxPoint(30, 30), wxSize(100, 30));
	myButton->Bind(wxEVT_BUTTON, &MyFrame::OnHello, this);
	//Bind(wxEVT_BUTTON, &MyFrame::OnHello, this, myButton->GetId());

	Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
	Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
	Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
.
.
.

 

 

코드만으로 UI를 디자인하기는 쉽지 않다. 필요하다면 wysiwyg 방식으로 디자인할 수 있는 wxFormBuilder를 사용하자.

wxFormBuilder

 

이번에는 OpenCV를 이용해 이미지를 표시해 보자.

#include <wx/wx.h>
#include <opencv2/opencv.hpp>

class MyApp : public wxApp
{
public:
	bool OnInit() override;
};

wxIMPLEMENT_APP(MyApp);

class MyFrame : public wxFrame
{
public:
	MyFrame();

private:
	void OnHello(wxCommandEvent& event);
	void OnExit(wxCommandEvent& event);
	void OnAbout(wxCommandEvent& event);

	wxPanel* m_panel; // 버튼과 이미지를 담을 패널
	wxStaticBitmap* m_imageCtrl; // 이미지 표시를 위한 컨트롤
};

enum
{
	ID_Hello = 1
};

bool MyApp::OnInit()
{
	MyFrame* frame = new MyFrame();
	frame->Show(true);
	return true;
}

MyFrame::MyFrame()
	: wxFrame(nullptr, wxID_ANY, "Hello World")
{
	wxMenu* menuFile = new wxMenu;
	menuFile->Append(ID_Hello, "&Hello...\tCtrl-H", "Help string shown in status bar for this menu item");
	menuFile->AppendSeparator();
	menuFile->Append(wxID_EXIT);

	wxMenu* menuHelp = new wxMenu;
	menuHelp->Append(wxID_ABOUT);

	wxMenuBar* menuBar = new wxMenuBar;
	menuBar->Append(menuFile, "&File");
	menuBar->Append(menuHelp, "&Help");

	SetMenuBar(menuBar);

	CreateStatusBar();
	SetStatusText("Welcome to wxWidgets!");

	m_panel = new wxPanel(this, wxID_ANY);
	wxButton* myButton = new wxButton(m_panel, wxID_ANY, "Press Me", wxPoint(30, 30), wxSize(100, 30));
	myButton->Bind(wxEVT_BUTTON, &MyFrame::OnHello, this);
	//Bind(wxEVT_BUTTON, &MyFrame::OnHello, this, myButton->GetId());

	m_imageCtrl = new wxStaticBitmap(m_panel, wxID_ANY, wxNullBitmap, wxPoint(30, 80));
	// 이미지 표시 컨트롤 초기화. wxNullBitmap으로 초기화하여 나중에 이미지가 로드될 때 업데이트할 수 있도록 함.

	Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
	Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
	Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
}

void MyFrame::OnExit(wxCommandEvent& event)
{
	Close(true);
}

void MyFrame::OnAbout(wxCommandEvent& event)
{
	wxMessageBox("This is a wxWidgets Hello World example", "About Hello World", wxOK | wxICON_INFORMATION);
}

void MyFrame::OnHello(wxCommandEvent& event)
{
	//wxLogMessage("Hello world from wxWidgets!");
	cv::Mat image = cv::imread("palvin1.png");
	if (image.empty())
	{
		wxLogError("Could not load image!");
	}
	else
	{
		// OpenCV는 BGR을 사용하므로 wxImage에서 필요로 하는 RGB 형식으로 변환.
		cv::cvtColor(image, image, cv::COLOR_BGR2RGB);

		// wxWidgets 구조상 플랫폼 비의존적(Device Independent)인 픽셀 데이터를 플랫폼 의존적(Device Dependent)인
		// 이미지로 그리기 위해서는 wxImage를 거쳐 wxBitmap으로 변환해야 한다. wxImage는 픽셀 데이터를 소유하므로
		// 이미지가 wxImage에 의해 관리되도록 true를 전달한다.
		wxImage wxImg(image.cols, image.rows, image.data, true);
		wxBitmap bitmap(wxImg);

		if (m_imageCtrl)
		{
			m_imageCtrl->SetBitmap(bitmap);
			m_imageCtrl->SetSize(bitmap.GetWidth(), bitmap.GetHeight());
			m_panel->Refresh();
		}
	}
}

 

 

 

wxStaticBitmap과 같은 컨트롤을 거치지 않고 wxPaintDC(Device Context)를 이용하여 디스플레이에 직접 그리는 방식도 가능하다. 이 방법은 비디오 캡처 등으로 계속 프레임이 바뀔 때 컨트롤을 계속 업데이트하는 오버헤드를 줄이는데 유리할 수 있다.

 

#include <wx/wx.h>
//#include <wx/dcbuffer.h> // wxBufferedPaintDC를 사용하기 위한 헤더
#include <opencv2/opencv.hpp>

class MyApp : public wxApp
{
public:
	bool OnInit() override;
};

wxIMPLEMENT_APP(MyApp);

class MyFrame : public wxFrame
{
public:
	MyFrame();

private:
	void OnHello(wxCommandEvent& event);
	void OnExit(wxCommandEvent& event);
	void OnAbout(wxCommandEvent& event);
	void OnPaint(wxPaintEvent& event);

	wxPanel* m_panel; // 버튼과 이미지를 담을 패널
	wxBitmap m_bitmap; // 직접 그릴 비트맵 데이터
};

enum
{
	ID_Hello = 1
};

bool MyApp::OnInit()
{
	MyFrame* frame = new MyFrame();
	frame->Show(true);
	return true;
}

MyFrame::MyFrame()
	: wxFrame(nullptr, wxID_ANY, "Hello World")
{
	wxMenu* menuFile = new wxMenu;
	menuFile->Append(ID_Hello, "&Hello...\tCtrl-H", "Help string shown in status bar for this menu item");
	menuFile->AppendSeparator();
	menuFile->Append(wxID_EXIT);

	wxMenu* menuHelp = new wxMenu;
	menuHelp->Append(wxID_ABOUT);

	wxMenuBar* menuBar = new wxMenuBar;
	menuBar->Append(menuFile, "&File");
	menuBar->Append(menuHelp, "&Help");

	SetMenuBar(menuBar);

	CreateStatusBar();
	SetStatusText("Welcome to wxWidgets!");

	m_panel = new wxPanel(this, wxID_ANY);
	//m_panel->SetBackgroundStyle(wxBG_STYLE_PAINT);
	// wxBufferedPaintDC 사용시 검은 화면 방지, wxBG_STYLE_PAINT는 직접 패널 배경을 그리겠다는 의미로
	// OnPaint에서 DC의 배경색으로 패널의 배경색을 설정하고 Clear()를 호출하여 화면을 지워야 한다.

	wxButton* myButton = new wxButton(m_panel, wxID_ANY, "Press Me", wxPoint(30, 30), wxSize(100, 30));
	myButton->Bind(wxEVT_BUTTON, &MyFrame::OnHello, this);
	//Bind(wxEVT_BUTTON, &MyFrame::OnHello, this, myButton->GetId());

	// 패널에 페인트 이벤트를 바인드하여 DC를 통해 직접 그린다.
	m_panel->Bind(wxEVT_PAINT, &MyFrame::OnPaint, this);
	m_panel->Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) { event.Skip(); }); // 배경 지우기 이벤트를 무시하여 깜빡임 방지
	m_panel->SetDoubleBuffered(true); // 깜빡임 방지 위해 더블 버퍼링 활성화

	Bind(wxEVT_MENU, &MyFrame::OnHello, this, ID_Hello);
	Bind(wxEVT_MENU, &MyFrame::OnAbout, this, wxID_ABOUT);
	Bind(wxEVT_MENU, &MyFrame::OnExit, this, wxID_EXIT);
}

void MyFrame::OnExit(wxCommandEvent& event)
{
	Close(true);
}

void MyFrame::OnAbout(wxCommandEvent& event)
{
	wxMessageBox("This is a wxWidgets Hello World example", "About Hello World", wxOK | wxICON_INFORMATION);
}

void MyFrame::OnHello(wxCommandEvent& event)
{
	//wxLogMessage("Hello world from wxWidgets!");
	cv::Mat image = cv::imread("palvin1.png");
	if (image.empty())
	{
		wxLogError("Could not load image!");
	}
	else
	{
		// OpenCV는 BGR을 사용하므로 wxImage에서 필요로 하는 RGB 형식으로 변환.
		cv::cvtColor(image, image, cv::COLOR_BGR2RGB);

		// wxWidgets 구조상 플랫폼 비의존적(Device Independent)인 픽셀 데이터를 플랫폼 의존적(Device Dependent)인
		// 이미지로 그리기 위해서는 wxImage를 거쳐 wxBitmap으로 변환해야 한다. wxImage는 픽셀 데이터를 소유하므로
		// 이미지가 wxImage에 의해 관리되도록 true를 전달한다.
		wxImage wxImg(image.cols, image.rows, image.data, true);
		m_bitmap = wxBitmap(wxImg);

		m_panel->Refresh(); // 패널을 다시 그리도록 요청하여 OnPaint가 호출되게 한다.
	}
}

void MyFrame::OnPaint(wxPaintEvent& event)
{
	// m_panel에 대한 Paint DC 생성 (페인트 이벤트 안에서만 사용 가능)
	wxPaintDC dc(m_panel);

	// wxPaintDC 대신 메모리에 먼저 그리고 화면에 출력하는 wxBufferedPaintDC 클래스를 사용해도 부드러운 출력이
	// 가능하며 깜빡임도 줄일 수 있다고 한다. wxBufferedPaintDC는 wxPaintDC를 상속하므로 wxPaintDC 대신
	// wxBufferedPaintDC를 사용하면 된다. 이 경우 m_panel에 더블 버퍼링은 활성화할 필요가 없다.
	// 둘 중 하나만 사용하면 된다. wxPaintDC + SetDoubleBuffered 사용을 추천.
	// wxBufferedPaintDC는 운영체제의 네이티브 더블 버퍼링을 지원하지 않는 옛날 OS나 특수한 상황을 위해 존재한다.

	// 상단의 #include <wx/dcbuffer.h> 주석 취소.
	//wxBufferedPaintDC dc(m_panel);

	//dc.SetBackground(wxBrush(m_panel->GetBackgroundColour()));
	//dc.Clear();
	// 패널 기본 배경색으로 화면을 지워준다.
	// wxBG_STYLE_PAINT 스타일을 사용했다면 OnPaint에서 DC의 배경색을 패널의 배경색으로 설정하고 Clear()를 호출하여 화면을
	// 지워야 검은색으로 나타나지 않는다.

	if (m_bitmap.IsOk()) // 비트맵이 유효한 경우에만 그린다.
	{
		// 지정된 좌표 (30, 80)에 비트맵을 직접 그린다. 마스크 사용은 false로 설정.
		dc.DrawBitmap(m_bitmap, 30, 80, false);
	}
}

 

결과는 이전 코드와 같다.

 

※ 참고

Hello World Example

Documentation

Tutorials

 

반응형
Posted by J-sean
:
반응형

치트 엔진으로 삽입한(inject) 코드를 enable/disable 하기 위해 Cheat Table Framework code 사용할 수 있다. 하지만 자세히 보지 않으면 원치 않는 결과를 얻게 되므로 조심해야 한다.

 

치트 엔진 튜토리얼을 실행하고 Step 7: Code Injection 까지 진행한다.

 

Health 메모리를 찾는다.

 

Health 메모리에 쓰기를 시도하는 코드를 찾는다.

 

쓰기 코드를 찾았으면 Show disassembler 버튼을 클릭한다.

 

 

Health 메모리에 저장된 값을 1 감소(sub) 시키는 코드가 선택된다.

 

Tools - Auto Assemble 을 클릭한다.

 

삽입할 코드를 enable/disable 할 수 있도록 Cheat Table framework code를 선택한다.

 

Code injection을 선택한다.

 

 

코드를 삽입하고 복구하는 기본 코드가 작성되었다. 코드를 수정하지 않았으므로 프로그램은 변경되지 않는다.

 

Assign to current cheat table을 선택한다.

 

Cheat Table Address List에 스크립트가 추가되었다.

 

Active 체크박스를 클릭해 enable 시킨다.

 

 

스크립트가 삽입된 주소로 점프하는 코드로 변경되었다.

 

다시 Active 체크박스를 클릭해 disable 시킨다.

 

변경된 점프 코드가 다시 원래 sub 코드로 복구되었다.

 

하지만 튜토리얼의 Hit me 버튼을 클릭하면 오류가 발생하고 프로그램이 종료되어 버린다.

 

 

왜 그럴까? 원래 코드와 복구된 코드를 다시 살펴보자.

위 Memory Viewer가 원래 코드고 아래 Memory Viewer가 복구된 코드다.

원래 코드나 복구된 코드나 ebx 레지스터에 저장된 메모리 주소에서 4A4 바이트 떨어진 곳에 저장된 값에서 1을 빼는 내용으로 동일하지만 자세히 보면 Opcode가 다른것을 확인할 수 있다. 원래 코드의 Opcode는 83이고 복구된 코드의 Opcode는 81이다. 두 번째 operand도 값은 1로 동일하지만 크기가 1바이트(01)와 4바이트(00000001)로 큰 차이가 발생했다. 이렇게 코드가 바뀌었지만 내용은 같기때문에 별 문제가 없을것 같지만 명령어 크기가 바뀌는 바람에 그 다음 명령어 영역까지 침범한 것은 큰 문제이다. (그래서 프로그램이 오류로 종료되어 버리는 것이다)

 

스크립트의 복구 내용을 살펴보면 원래 어셈블리어 코드 그대로 사용했음을 확인할 수 있다. 그런데 그 어셈블리어 코드가 원래 옵코드와 오퍼랜드로 복구되지 않는것이 문제이다.

 

인텔 소프트웨어 개발자 메뉴얼을 확인해 보자.

 

intel Software Developer&rsquo;s Manual_Vol_2.pdf
11.06MB

 

sub 명령어는 여러가지 Opcode가 존재한다.

원래 코드의 83은 imm8(8비트 값)을 오퍼랜드로 사용하지만 복구된 코드의 81은 imm32(32비트 값)을 오퍼랜드로 사용한다. 복구할 스크립트의 sub 명령어를 기계어로 번역할때 Opcode를 81로 사용하기 때문에 sub dword ptr [ebx+000004A4], 01 이라는 어셈블리 명령어의 두 번째 오퍼랜드 01을 0x01로 1바이트 값이 아닌 0x00000001로 4바이트 값으로 번역하는 것이다. 치트엔진은 튜토리얼이 32비트 프로그램이므로 당연히 4바이트 값을 사용했을것이라 생각하는것 같다. 어쨌든 원래 코드로 복구시키지 못하므로 버그라고 봐야 할거같다.

 

 

해결 방법은 간단하다.

위 그림처럼 sub dword ptr [ebx+000004a4], 01 코드를 주석처리하고 그 아래 db 83 AB A4 04 00 00 01을 주석 해제한다. 어셈블리어로 작성한 코드를 기계어로 번역하지 않고 원래 있던 기계어를 다시 그대로 바이트 단위로 정의(db: define byte)하는 것이다.

 

아니면 위 그림처럼 dword(4바이트)를 byte(1바이트)로 바꿔도 된다.

 

r/m8, imm8 오퍼랜드를 사용하는 80 옵코드로 복구되지만 기계어로 번역된 결과의 길이가 같아 다른 명령어를 침범하지 않고 실행에도 문제가 없다.

 

반응형
Posted by J-sean
: