어푸푸

보더랜드 2 무한 부스터 트레이너 (C++) 본문

잡동사니/자동화

보더랜드 2 무한 부스터 트레이너 (C++)

예수님부처 2021. 8. 4. 02:26

예전에 사기만 해놓고 방치해뒀던 보더랜드 2를 최근 여가 시간에 하고 있다. 그런데 이 보더랜드2가 예전에 개발된 게임이라서 그런지 다음과 같은 문제점이 있었다.

1. 게임이 실행된 뒤 메뉴에서 창모드 및 전체화면을 선택하면 게임이 튕긴다. (...)
(런쳐에서 바꿔야 함)
2. 게임이 실행중일 때 사용자 계정 컨트롤 창이 뜨면 게임이 튕긴다 (...)
3. 게임을 전체화면으로 하다가 알트 탭을 하면 튕긴다 (...)
4. Conference call 이라는 무기를 사용하면 투사체가 너무 많아서 순간적으로 CPU에 과부하가 걸리게 되고, FPS가 높을 경우 게임이 튕긴다 (...)
5. 창모드일 경우 프리싱크가 작동하지 않는다 (...)

말 그대로 대환장 콜라보다. 보더랜드 2의 경우 차량 이동 속도가 다소 답답한 편에 속하고, 부스터 지속 시간이 짧아서 너무 답답했다. 처음에는 이걸 트레이너나 치트엔진을 써서 해결하려고 해봤다. 근데 워낙에 예전 게임이다보니 최신 트레이너에서도 다른 기능은 되더라도 차량 무한 부스터 기능은 작동하지 않았고, 치트엔진으로는 해결이 가능했다.

그렇다면 치트엔진을 이용해서 부스터를 처리하는건 어떨까? 치트엔진은 게임을 실행한 다음에 프로세스를 잡고 코드를 활성화해줘야한다. 따라서 문제를 해결하기 위해 게임을 실행시키고 치트엔진을 키려고 알트 탭을 누르는 순간 게임이 튕긴다. 창모드로 게임을 실행하고 치트엔진으로 처리를 하고 게임을 전체화면으로 변경하는 순간 게임이 튕긴다. 그렇다면 처음부터 끝까지 게임을 창모드로 하되 FPS를 60으로 두는 것은 어떨까? 그렇게 하면 conference call 무기를 발사하는 순간마다 순간적으로 프레임 드랍때문에 스터터링이 발생한다. (이 게임은 창모드를 쓰면 프리싱크가 작동을 안한다.) 아니면 창모드로 게임을 실행하되 FPS를 120으로 고정한다면?그러면 conference call 무기를 발사할 때 순간적인 CPU 부하때문에 게임이 종료되는 현상이 발생한다. 그러니까.. 하나하나는 별 것 아닌 것처럼 보이지만 서로 맞물려서 문제의 해결 자체를 방해한다.

그래서 무슨 방법이 있을까 하고 인터넷을 뒤져보다보니 C++로 게임의 메모리에 직접 접근을 할 수 있다는 정보를 접하고 그냥 내가 C++로 트레이너를 짜는게 낫겠다는 결론에 도달했다. 근데 또 짜려다보니까 범용적으로 쓸 수 있는게 좋겠다는 쓸데없는 근성이 발휘되어 조금만 수정한다면 범용적으로 쓸 수 있는 트레이너 코드를 C++로 작성하였다. 2K 게임즈 놈들 때문에 짠 트레이너 프로그램 및 코드를 남긴다.

보더랜드2가 아닌 다른 게임에 쓸 경우 유저가 바꿔야 할 부분은 다음과 같다.

1. Main.cpp - 14번째 줄: 원하는 게임의 window name으로 바꿔준다. (lpWindowName 변수)
2. Trainer.h - 15, 16, 44번째 줄: 배열의 사이즈는 maximum 값이다. 동적으로 바꾸던가 필요한 경우 더 늘리거나 하면 됨.
3. Trainer.h - 44번째 줄: 넣고자, 혹은 고정하고자 하는 값의 타입으로 바꿔준다.
ex) Cheat<float> cheat[32], Cheat<int> cheat[32], Cheat<double> cheat[32], ...
4. Trainer.cpp - 278~307번째 줄: base address와 offset을 입력한다. 필자는 누군가가 만들어놓은 cheat table을 참조했다.
5. Trainer.cpp - 336~348번째 줄: 원하는 방식으로 메모리를 읽고 쓴다. 참고로 336~341줄은 float 값 100을 메모리에 강제로 할당해주는 방식이고, 343~348줄은 maximum 값을 나타내는 주소에서 읽고, 이를 current 값을 나타내는 주소에 강제로 할당해주는 방식이다.

뭐 최적화의 여지는 물론 있겠지만 (예를 들면 지금은 메모리를 할당할 때마다 포인터를 따라가면서 주소를 다시 찾는다.) 이쯤 했으면 됐지 뭐.. 이 프로그램은 그냥 보더랜드 2랑 같이 실행되어 있기만 하면 자동으로 적용된다. 나중에 게임이 업데이트되서 안되면 알아서 포인터 찾아서 쓰면 된다. 현재 프로그램은 첨부한 CT 파일의 [[ptrBaseHook]+8] 값을 베이스로 삼아서 포인터로 원하는 주소를 찾는다.

//	Main.h
#ifndef _MAIN_H_INCLUDED
#define _MAIN_H_INCLUDED

#include <iostream>
#include "Trainer.h"

#endif
//	Main.cpp
////////////////////////////////////////////////////////////////////
//
//      Borderlands 2 trainer
//
////////////////////////////////////////////////////////////////////

#include "Main.h"

using namespace std;

int main()
{
    Trainer trainerBL2;
    trainerBL2.SetlpWindowName("Borderlands 2 (32-bit, DX9)");
    trainerBL2.AttachProcess();
    trainerBL2.RunTrainer();

    return 0;
}
//	Trainer.h
#ifndef _TRAINER_H_INCLUDED
#define _TRAINER_H_INCLUDED

#include <iostream>
#include <Windows.h>
#include <psapi.h>
#include <chrono>


//	Cheat class
template <typename T>
class Cheat {
private:
	int pointer_level;
	DWORD address[32];
	DWORD offset[32];
	T value;

public:
	Cheat();
	~Cheat();
	void Initialize();
	void SetAddress(DWORD address_);
	DWORD GetAddress(int level_);
	void AddOffset(DWORD ptr_);
	void RemoveOffset();
	bool FollowPointer(HANDLE handle);
	void SetValue(T value_);
	T    GetValue();
	bool ReadValue(HANDLE handle);
	bool WriteValue(HANDLE handle);
};


//	Trainer class
class Trainer {
private:
	LPCSTR lpWindowName;
	std::wstring excutableName;
	HANDLE handle;
	HWND hwnd;
	DWORD procID;
	DWORD address_base;
	Cheat<float> cheat[32];

public:
	//	Constructor and destructor
	Trainer();
	Trainer(LPCSTR lpWindowName_);
	~Trainer();
	void Initialize();
	bool SetlpWindowName(LPCSTR lpWindowName_);
	bool AttachProcess();
	bool AttachProcess(LPCSTR lpWindowName_);
	bool GetExcutableName();
	HMODULE GetBaseAddress();
	bool isProcessRunning();
	bool SetCheats();
	bool RunTrainer();
};


#endif
//	Trainer.cpp
#include "Trainer.h"

using namespace std;



////////////////////////////////////////////////////////////////////
//
//      Cheat class
//
////////////////////////////////////////////////////////////////////

//	Constructor
template <typename T>
Cheat<T>::Cheat()
{
	Initialize();
}


//	Destructor
template <typename T>
Cheat<T>::~Cheat()
{
	Initialize();
}


//	Initializer
template <typename T>
void Cheat<T>::Initialize()
{
	int i = 0;

	pointer_level = 0;
	value = (T)0;
	for (i = 0; i < 16; i++)
	{
		address[i] = (unsigned long)0;
		offset[i] = (unsigned long)0;
	}
}


//	Set address
template <typename T>
void Cheat<T>::SetAddress(DWORD address_)
{
	address[0] = address_;
}


//	Get address: 
//		level_ < 0 --> target address
//		level_ >=0 --> address[level_]
template <typename T>
DWORD Cheat<T>::GetAddress(int level_)
{
	if (level_ < 0)
		return address[pointer_level];
	else
		return address[level_];
}


//	Increase pointer
template <typename T>
void Cheat<T>::AddOffset(DWORD ptr_)
{
	offset[pointer_level] = ptr_;
	pointer_level++;
}


//	Decrease pointer
template <typename T>
void Cheat<T>::RemoveOffset()
{
	pointer_level--;
	offset[pointer_level] = (unsigned long)0;
}


//	Follow pointer
template <typename T>
bool Cheat<T>::FollowPointer(HANDLE handle)
{
	int i = 0;
	bool flag;

	//	Follow pointer
	for (i = 1; i < pointer_level; i++)
	{
		flag = ReadProcessMemory(handle, (LPCVOID)(address[i - 1] + offset[i - 1]), &address[i], sizeof(address[0]), 0);

		//	Handle failure
		if (!flag)
			return flag;
	}

	//	Deal with the last offset
	address[i] = address[i - 1] + offset[i - 1];

	//	Check if it is readable
	flag = ReadValue(handle);

	return flag;
}


//	Set value to value_
template <typename T>
void Cheat<T>::SetValue(T value_)
{
	value = value_;
}


//	Return value
template <typename T>
T Cheat<T>::GetValue()
{
	return value;
}


//	Read address memory and store it to value
template <typename T>
bool Cheat<T>::ReadValue(HANDLE handle)
{
	return ReadProcessMemory(handle, (LPCVOID)address[pointer_level], &value, sizeof(value), 0);
}


//	Write address memory and store it to value
template <typename T>
bool Cheat<T>::WriteValue(HANDLE handle)
{
	return WriteProcessMemory(handle, (LPVOID)address[pointer_level], &value, sizeof(value), 0);
}



////////////////////////////////////////////////////////////////////
//
//      Trainer class
//
////////////////////////////////////////////////////////////////////

//	Constructor - 1
Trainer::Trainer()
{
	Initialize();
}


//	Constructor - 2
Trainer::Trainer(LPCSTR lpWindowName_)
{
	Initialize();
	SetlpWindowName(lpWindowName_);
}


//	Destruction
Trainer::~Trainer()
{
	CloseHandle(handle);
	cout << "\tHandle destructed" << endl;
}


//	Initializing function
void Trainer::Initialize()
{
	lpWindowName = "N/A";
	excutableName = L"N/A";
	handle = nullptr;
	hwnd = nullptr;
	procID = (unsigned long)0;
	address_base = (unsigned long)0;
}


//	Set lpWIndowName
bool Trainer::SetlpWindowName(LPCSTR lpWindowName_)
{
	lpWindowName = lpWindowName_;
	cout << "\tlpWindowName = " << lpWindowName << endl;

	return true;
}


//	Attach process (Open process) and its overloaded one
bool Trainer::AttachProcess()
{
	//	Get handle
	cout << "\tFinding windows: " << lpWindowName << endl;
	while (procID == (unsigned long)0)
	{
		hwnd = FindWindowA(NULL, lpWindowName);
		GetWindowThreadProcessId(hwnd, &procID);
		Sleep(1000);
	}
	cout << "\tProcess found - procID: " << procID << endl;
	handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procID);

	//	Get excutable name
	GetExcutableName();
	wcout << "\tExcutable name: " << excutableName << endl;

	//	Get base address
	address_base = (DWORD)GetBaseAddress();
	cout << "\tBase address: " << address_base << endl;

	return true;
}
bool Trainer::AttachProcess(LPCSTR lpWindowName_)
{
	SetlpWindowName(lpWindowName_);
	return AttachProcess();
}


//	Get excutable name
bool Trainer::GetExcutableName()
{
	TCHAR fullPath[MAX_PATH];

	GetModuleFileNameEx(handle, 0, fullPath, MAX_PATH);
	excutableName = fullPath;

	return true;
}


//	Get base address
HMODULE Trainer::GetBaseAddress()
{
	HMODULE hMods[1024];
	DWORD cbNeeded;
	unsigned int i;

	if (EnumProcessModules(handle, hMods, sizeof(hMods), &cbNeeded))
	{
		for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
		{
			TCHAR szModName[MAX_PATH];
			if (GetModuleFileNameEx(handle, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR)))
			{
				wstring wstrModName = szModName;
				if (wstrModName.find(excutableName) != string::npos)
				{
					//CloseHandle(pHandle);
					return hMods[i];
				}
			}
		}
	}
	return nullptr;
}


//	Check if process is running
bool Trainer::isProcessRunning()
{
	return (GetBaseAddress() != nullptr);
}



//	Set cheats: User inputs
bool Trainer::SetCheats()
{
	cout << "\tSet cheats... ";

	//	Vehicle boost
	cheat[0].SetAddress(address_base);
	cheat[0].AddOffset(0x016949D8);
	cheat[0].AddOffset(0x10);
	cheat[0].AddOffset(0x358);
	cheat[0].AddOffset(0x37C);
	cheat[0].AddOffset(0x188);
	cheat[0].AddOffset(0x6C);

	//	Grenades - max
	cheat[1].SetAddress(address_base);
	cheat[1].AddOffset(0x016949D8);
	cheat[1].AddOffset(0x2C);
	cheat[1].AddOffset(0x190);
	cheat[1].AddOffset(0x5C);
	//	Grenades - current
	cheat[2].SetAddress(address_base);
	cheat[2].AddOffset(0x016949D8);
	cheat[2].AddOffset(0x2C);
	cheat[2].AddOffset(0x190);
	cheat[2].AddOffset(0x6C);

	//cout << "Results -: " << (HMODULE)cheat[0].GetAddress(-1) << endl;
	//cout << "Results -: " << (HMODULE)cheat[1].GetAddress(-1) << endl;
	//cout << "Results -: " << (HMODULE)cheat[1].GetAddress(-1) << endl;
	//cout << "Results 0: " << (HMODULE)cheat[1].GetAddress(0) << endl;
	//cout << "Results 1: " << (HMODULE)cheat[1].GetAddress(1) << endl;
	//cout << "Results 2: " << (HMODULE)cheat[1].GetAddress(2) << endl;
	//cout << "Results 3: " << (HMODULE)cheat[1].GetAddress(3) << endl;
	//cout << "Results 4: " << (HMODULE)cheat[1].GetAddress(4) << endl;

	cout << "Done!" << endl;

	return true;
}


//	Run trainer
bool Trainer::RunTrainer()
{
	chrono::steady_clock::time_point t_0;
	chrono::steady_clock::time_point t_1;
	bool flag = true;

	//	Attach process if it has not
	if (procID == 0)
		AttachProcess();

	//	Set cheats
	SetCheats();

	//	Run trainer while the process is running
	cout << "\tRunning cheats..." << endl;
	t_0 = chrono::steady_clock::now();
	while (flag)
	{
		t_1 = chrono::steady_clock::now();

		//	Car boost
		if (cheat[0].FollowPointer(handle))
		{
			cheat[0].SetValue(100.0f);
			cheat[0].WriteValue(handle);
		}

		//	Grenades (1 = max, 2 = current)
		if (cheat[1].FollowPointer(handle) && cheat[2].FollowPointer(handle))
		{
			cheat[2].SetValue(cheat[1].GetValue());
			cheat[2].WriteValue(handle);
		}


		//	Sleep to prevent the CPU from working too hard
		Sleep(100);


		//	Check if game is running and set exit condition
		if (chrono::duration_cast<std::chrono::seconds>(t_1 - t_0).count() == 10)
		{
			t_0 = chrono::steady_clock::now();
			flag = isProcessRunning();
		}
	}

	//	Inform that the game is not running
	cout << "\tThe game is exited. This program will be end" << endl;

	return true;
}

 

코드 어떻게 접는지 모르겠다. 언젠가 알아내면 접을게요.

V 1.0: 차량 부스터 무한 + 수류탄 무한

25 BL2 Trainer.zip
0.01MB
25 BL2 Trainer.exe
0.02MB
Borderlands2_ORI.CT
1.06MB

 

V 1.1: V 1.0 + 현재 무기 레벨 스케일링 (메인 메뉴로 나갔다가 들어오세요) + 모든 탄환 무한

25 BL2 trainer V1.1.zip
0.00MB

 

V 1.2: Debugged

25 BL2 Trainer V1.2.zip
0.00MB