В стародавние времена, когда по земле ходили мамонты, а я был в два раза моложе, среди игрового сообщества пользовалась популярностью компьютерная программа для «взлома» игр под названием 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.
Благодарю за внимание!
ссылка на оригинал статьи https://habr.com/ru/articles/835212/
Добавить комментарий