В стародавние времена, когда по земле ходили мамонты, а я был в два раза моложе, среди игрового сообщества пользовалась популярностью компьютерная программа для «взлома» игр под названием ArtMoney. С помощью этой софтины можно было не только облегчить себе жизнь в прохождении хардкорного приключения, модифицировав значения ресурсов в игре, но и просто поразвлечься, изучив полюбившийся проект с разных сторон.
А на днях мне вдруг захотелось вспомнить молодость и поиграть в бумерский диаблойд под названием Titan Quest, выпущенный аж в 2006 году. Да вот только времени на беготню, прокачку, и вот это вот всё, у меня нет. И ArtMoney нет. Зато есть определенные знания программирования. Вот я и решил совместить приятное с полезным, написав аналог ArtMoney на Python, а заодно стать супербогатым, хотя бы в Titan Quest.
Для этого дела понадобились только Python и библиотека Pymem, с помощью которой можно взламывать процессы Windows и манипулировать памятью (читать и записывать).
Программа состоит из класса MemoryEditor, который отвечает за взаимодействие с процессом игры, поиск и замену значений в его памяти. И функции, которая выступает интерактивным меню для взаимодействия с юзером.
Класс MemoryEditor
class MemoryEditor:
def __init__(self, process_name: str) -> None:
self.pm = pymem.Pymem(process_name)
self.process_base = pymem.process.module_from_name(self.pm.process_handle, process_name).lpBaseOfDll
Конструктор класса принимает имя процесса (process_name
), которое нужно открыть (например, process.exe).
- pymem.Pymem(process_name) — открывает процесс и позволяет взаимодействовать с его памятью.
- process_base — это базовый адрес основного модуля процесса (обычно самого .exe файла).
Метод search_value
def search_value(self, value: int) -> list:
search_results = []
memory_size = 0x7FFFFFFF # Размер памяти для сканирования (большой диапазон)
chunk_size = 0x1000 # Размер блока чтения
search_bytes = ctypes.c_uint32(value).value.to_bytes(4, byteorder='little')
offset = 0
while offset < memory_size:
current_address = self.process_base + offset
if self.is_memory_readable(current_address):
try:
buffer = self.pm.read_bytes(current_address, chunk_size)
except pymem.exception.MemoryReadError:
offset += chunk_size
continue
chunk_offset = 0
while True:
chunk_offset = buffer.find(search_bytes, chunk_offset)
if chunk_offset == -1:
break
# Сохранение адреса найденного значения
address = current_address + chunk_offset
search_results.append(address)
chunk_offset += len(search_bytes)
offset += chunk_size
return search_results
Эта функция выполняет поиск заданного значения (value) в памяти процесса.
- memory_size определяет область памяти, в которой будет производиться поиск.
- chunk_size определяет, какой объем данных будет считываться за раз (в данном случае 4KB).
- search_bytes преобразует значение в байтовую строку для поиска в памяти.
- Цикл while offset < memory_size: проходит по всей указанной области памяти, проверяя каждую часть на наличие нужного значения.
- self.is_memory_readable(current_address) проверяет, доступна ли память для чтения.
- Если значение найдено в текущем блоке памяти, его адрес сохраняется в search_results.
Метод is_memory_readable
def is_memory_readable(self, address) -> bool:
mbi = pymem.memory.virtual_query(self.pm.process_handle, address)
if mbi.Protect & 0xF != 0x0 and mbi.State == 0x1000 and mbi.Protect & 0x100 == 0:
return True
return False
Эта функция проверяет, доступна ли память по указанному адресу для чтения.
- Использует функцию virtual_query из библиотеки pymem, которая возвращает информацию о состоянии и защите памяти.
- Проверяет, что память доступна, не защищена и не имеет флага PAGE_GUARD.
Метод search_next_value
def search_next_value(self, addresses: list, next_value: int) -> list:
search_results = []
search_bytes = ctypes.c_uint32(next_value).value.to_bytes(4, byteorder='little')
for address in addresses:
if self.is_memory_readable(address):
try:
buffer = self.pm.read_bytes(address, 4)
except pymem.exception.MemoryReadError:
continue
if buffer == search_bytes:
search_results.append(address)
return search_results
Эта функция ищет новое значение (next_value) только среди адресов, найденных на предыдущем этапе поиска.
- Функция принимает список адресов (addresses) и значение для поиска (next_value).
- Если новое значение найдено по одному из адресов, этот адрес добавляется в список search_results.
И метод replace_value
def replace_value(self, addresses: list, new_value: int) -> None:
replace_bytes = ctypes.c_uint32(new_value).value.to_bytes(4, byteorder='little')
for address in addresses:
self.pm.write_bytes(address, replace_bytes, 4)
print(f"Замена значения по адресу: {hex(address)} на {new_value}")
Функция заменяет значения по указанным адресам (addresses) на новое значение (new_value).
- replace_bytes — это новое значение в виде байтовой строки.
- self.pm.write_bytes(address, replace_bytes, 4) записывает новое значение в память по указанному адресу.
Вот и всё. Теперь в основной части программы необходимо просто создать экземпляр написанного выше класса с названием процесса игры, и поочередно вызывать необходимые методы, для поиска нужных ячеек, и замены значений. Я не стал мудрить с интерфейсом, и написал простейшее меню в командной строке, с запросами нужной информации у пользователя. Выглядит это так:
В игре у моего персонажа было 12345 монет, это значение я и ввел. Однако программа нашла слишком много адресов с идентичными значениями, поэтому в игре я собрал еще немного голды, изменив количество монет у персонажа, и отфильтровал адреса уже по новым данным. Во второй раз адресов было значительно меньше и я решил дальше не фильтровать, а изменить значения во всех. В итоге мой персонаж стал почти миллионером (совсем уж наглеть не стал).
Что сказать, я доволен, и могу без всяких там читов чистить данжы, закупившись хилками на все деньги.
Кто желает воспользоваться программой или дополнить её: репозиторий PyMoney.
Благодарю за внимание!