В стародавние времена, когда по земле ходили мамонты, а я был в два раза моложе, среди игрового сообщества пользовалась популярностью компьютерная программа для "взлома" игр под названием 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.

Благодарю за внимание!

Комментарии (27)


  1. Ambreon
    10.08.2024 16:49

    Интересная статья
    Было интересно)


  1. A1t0r
    10.08.2024 16:49
    +3

    Забавно, недавно расковырял игру, чтобы написать утилиту на плюсах для сброса очков умений и характеристик. У tq такая механика, что если сбросить все очки вложенные в мастерство, то можно сменить класс) За статью +


  1. Andrey_Solomatin
    10.08.2024 16:49
    +4

    ArtMoney была крутая, там можно было профили сохранять, чтобы потом не искать второй раз. До неё я Cheat-O-Matic пользовался.


    1. alpha_man
      10.08.2024 16:49

      Помню в детстве себе сходу накручивал деньги в сталкере первом, чтобы игралось легче) отличное было время...


    1. christener
      10.08.2024 16:49
      +1

      Уже долгое время существует и совершенствуется Cheat Engine, в которой можно не просто искать и заменять значения по адресам, но и использовать для этого скрипты. Во многих современных играх недостаточно просто найти и заменить значение. Это зачастую не работает.
      Причем, готовые таблицы для игр можно легко найти в сети, где основные значения найдены за тебя и понятно подписаны: какое значение/скрипт за какие параметры отвечает.


      1. remzalp
        10.08.2024 16:49

        А еще можно внедрить в память процесса ассемблерный код, так что патроны могут стать действительно бесконечными, а если быть недостаточно аккуратным - будут бесконечными даже у врагов :)

        Cheat Engine уже давно содержит отладчик и (слабенькую) среду разработки


  1. Dolios
    10.08.2024 16:49

    А на днях мне вдруг захотелось вспомнить молодость и поиграть в бумерский диаблойд под названием Titan Quest, выпущенный аж в 2006 году. Да вот только времени на беготню, прокачку, и вот это вот всё, у меня нет.

    Гораздо проще было скачать картинку с текстом: "Поздравляем, вы победили". Никогда не понимал пользователей читов и артмани. Игра, это же процесс, зачем его портить и делать неинтересным? Если тебе изначально не хочется играть, можно же, например, сериал посмотреть или книжку почитать. Хотя, если задача именно в том, чтобы расковырять игру и сделать красивые чиселки в и нвентаре, то это вполне себе понятно, но игра, собственно, тут вторична и не принципиальна.


    1. DaneSoul
      10.08.2024 16:49
      +4

      У игры есть разные механики и не всегда есть интерес использовать их все.
      Например как-то давно пробовал "The SIMs", конкретно хотелось поиграться с редактором домов, для чего стартовых ресурсов явно не достаточно. Надо было потратить кучу часов на нудное зарабатывание виртуальных монеток когда можно было просто ввести чит код и приступить сразу к интересной для меня части?


      1. Dolios
        10.08.2024 16:49

        Для меня это звучит так, как будто вы купили мороженное, обильно намазанное сверху навозом и потом этот навоз палочкой счищали. Мой ответ: надо было взять игру, которая нравится. Такое мороженное я не куплю. Тем более, что у меня, например, очередь лет на 5 из таких игр, если я всё брошу и буду только играть. На пенсии обязательно пройду...


        1. randomsimplenumber
          10.08.2024 16:49
          +3

          Мой ответ: надо было взять игру, которая нравится.

          А как узнать, какая игра нравится, о мудрый филин?


        1. DaneSoul
          10.08.2024 16:49
          +3

          Ну да, а если уж купил мороженное, то надо и упаковку сожрать - за все уплачено!
          Если меня угостили тортом у которого сверху украшение из малины, которая мне не нравится, это не значит что я выкину весь торт или буду кривится от малины - я уберу малину и с удовольствием съем остальное.


        1. konst90
          10.08.2024 16:49
          +4

          Мой ответ: надо было взять игру, которая нравится.

          Так он и взял игру, которая нравится.

          Но в TQ девять разных классов, игрок может выбрать любые два, то есть сочетаний 9*8. И все их хочется попробовать в полной прокачке, причём с разными вариантами выбора умений. А проходить игру 72 раза всё-таки не очень интересно.


          1. Alkhonor
            10.08.2024 16:49

            Минутка духоты. Там 11 классов на данный момент, но т.к. 1 + 2 и 2 + 1 -тот же класс, то с учетом этого выходит 66 уникальный классов. Ни сколько не меняет посыл вашего сообщения, просто уточнил


            1. konst90
              10.08.2024 16:49

              Про 11 классов не знал, давно играл. А с математикой ошибся, да.


    1. Conung_ViC
      10.08.2024 16:49
      +5

      О боже, другие люди играют не так как Вам хочется!!! Какой ужас!


    1. newintellimouse
      10.08.2024 16:49

      Не во всех играх есть Story mod, а там, где он есть, в нём может отсутствовать часть сюжета.


    1. imba
      10.08.2024 16:49
      +1

      Каждыи играет в игру как хочет, если это не сетевой кооп, то 'читы' можно добавлять по своему вкусу. Да и написание читов, это тоже своего рода игра и доставляете не менее чем сама игра, считаи прошел на скрый класс мага_зазеркалья без гаидов))


    1. Derfirm
      10.08.2024 16:49
      +4

      Читы это не только механизм "быстрой" победы, но и возможность дебага/профилирования приложения(игры). Мне иногда просто интересно пропустить часть гринда и пройти весь сторилайн, иногда поковырять как устроен файл сохранения и какие штуки вшиты в движок. Также читами могут быть всякие переключатели погоды, окраски скинов или сами скины. Как иначе, если внутри игры не реализовано условное "зеркало", позволяющее сменить лицо персонажа после создания? Сделать респек?

      Безусловно это может сломать игру и она будет сильно отходить от задуманного дизайна. А может и стать лучше, не попробуешь не узнаешь точно :)


    1. christener
      10.08.2024 16:49

      Мы с другом в школьные времена обожали в GTA: Vice City вводить чит на оружие и просто развлекаться, соревнуясь в том, кто дольше проживет, играя по очереди. Было очень весело.


  1. anzay911
    10.08.2024 16:49
    +2

    Благодарю Bethesda за консольные команды.


  1. kiberpcix
    10.08.2024 16:49

    Артмани скачать минута делов)


  1. falseshepard
    10.08.2024 16:49

    Сейчас для этих дел используется cheat engine, там функций сверх artmoney навалено ого-го.


    1. zartarn
      10.08.2024 16:49

      В смысле сейчас? И тогда тоже. Cheat engine вышел в 2000 году.

      Ну и не сказать что прям сильно много функций навалено. Есть немного чуть более узкоспециализированных функций, но та же старая добрая ollydbg в целом даст фору)

      П.С.: вот тут немного TQ ковыряли, вдруг кому пригодится

      Hidden text


  1. kate_caffeine
    10.08.2024 16:49
    +1

    это не слезы, это ностальгия в глаз попала...

    спасибо за статью! никогда не задумывалась о том, как работает арт мани, теперь интересно самой попробовать)) пойду взламывать DMC 4))


  1. QuantumBoy
    10.08.2024 16:49

    Мне вот не понятно. Поправьте, если не прав.

    Тут ищется число в 4 байта, при этом для того, чтобы его найти, искомая память разбивается на блоки по 4 байта. Как будто это не надёжно, потому что по факту оно может лежать между блоков, два байта в конце одного блока и два байта в начале другого. Или три в конце одного, один в начале следующего.

    Как будто тут просто "повезло", что перед искомым числом хлама оказалось аккурат кратно 4 байтам.


    1. Andrey_Solomatin
      10.08.2024 16:49

      Может быть выравнивание применяется? И на самом деле оно лежит вообще как 8 байтов с нулями впереди?


    1. it_police Автор
      10.08.2024 16:49

      Просто логично было начать поиск с размера, кратного размеру данных и надеяться, что чанки выравнены. Вероятно сработало бы и на 8 байт, и был бы бонус к скорости поиска. Но я не являюсь экспертом по работе с памятью, всё получилось с первого раза, дальше экспериментировать не стал. Поэтому да, повезло.