Здравствуйте, друзья. Сегодня поговорим о "трудных" смещениях.
В современных играх обширно используются классы. Я не буду сейчас рассказывать об их преимуществах, напомню лишь о недостатке для создателей ботов/читов/плагинов: динамическое расположение объектов (экземпляров классов).
Именно поэтому любому разработчику приходится докапываться до нужных ему значений через указатели и смещения: существует какой-то базовый адрес (который никогда не меняется) и смещения от него. Здесь всё строится на том, что смещения, в отличие от указателей, всегда статические.
Напомню, что какое-либо значение, лежащее в определённой структуре и изменяемое самой игрой, может привести нас к началу структуры. Что мы для этого делали в Cheat Engine? Искали начальное значение, изменяли его в игре, отсеивали в CE, так доходили до одного адреса, который точно отвечал именно за искомый параметр. Затем цепляли отладчик CE к процессу и ставили BreakPoint на адрес данного параметра, дабы отследить команды, меняющие его значение. Он выдавал нам команды, содержащие смещения. Вычитали из адреса параметра смещение, получали начало структуры. Затем в памяти искали уже начало структуры (чтобы выйти к структурам более высоких уровней).
А теперь вопрос: как быть, если у нас нет ни конкретного значения, ни возможности изменить его, но есть начало структуры (которое легко вычисляется по "нормальным" оффсетам вышеописанным способом)?
За примерами далеко ходить не придётся: пол, класс персонажа, внешний вид его фейки (ад/рай 1/2/3).
Первые два стандартными средствами изменены не могут быть вообще. А в третьем примере придётся надолго оставлять Cheat Engine, чтобы изменить фейку и выполнить отсеивание (причём шанс добиться успеха довольно мал, так как у нас будет мало отсеиваний; особенно худо придётся, если мы не знаем даже примерных возможных значений).
Или вот такая ситуация: предмет лежит в инвентаре, его свойства можно изменить только так или иначе передвинув его. Как найти эти свойства? Проблема в том, что если сдвинуть/снять/одеть предмет и потом вернуть на место, то его структура будет лежать уже в другом месте. К подобным свойствам можно отнести, например, уровень джинна (можно изменить только экипировав его) или количество его свободных статов, уровень питомца (лежащего в инвентаре в виде яйца). Именно здесь Cheat Engine не справляется, да-да.
В подобных случаях удобно сохранить структуру куда-нибудь на жёсткий диск и сверить её с уже изменённой версией. Например, играли лучником - сохранили структуру, зашли воином - сохранили ещё раз; идём сверять. Рано или поздно найдём смещение для расы/класса игрока.
Чем сверять? Для данных целей удобнее всего будет использовать шестнадцатеричные редакторы, например, 010 Editor. В нём есть функция сравнения двух файлов.
Другая проблема в том, как правильно (и что конкретно) сохранять. Задавшись однажды этим вопросом, я был не в силах сопротивляться своему желанию написать-таки софтину для автоматизации данной задачи. Свою программу "Offset Memory Dumper" прикрепляю к этой статье.
В чём вообще удобства такого подхода:
1) безопасность: сохранённые значения никуда не денутся (Ваш К.О.);
2) надёжность и наглядность: любые смещения байтов от начала файла равны смещениям для соответствующих байтов от начала структуры (разумеется, речь идёт о бинарных файлах);
3) компактность: не нужно снимать полные дампы памяти, достаточно нескольких сотен байтов от начала исследуемой структуры.
Сейчас я покажу пример, как пользоваться такой техникой и этой программой. Найдём смещение от начала информации о джинне, лежащем в инвентаре, до уровня джинна.
Поместим джинна в первый слот инвентаря (для удобства). [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
Запоминаем уровень - 41. Идём в программу-дампер, выбираем процесс игры. [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
Задаём смещения от базового адреса:
BA +1C +34 +CAC +C +0, где 0 - смещение для первой ячейки инвентаря. Если нужна другая, то ставим N*4, где N - номер ячейки от 0 до 63.
Следует учесть, что по данному адресу будет лежать адрес начала структуры (а не сами значения, лежащие в ней). Поэтому мы добавляем ещё одно смещение +0. Нажимаем кнопочку "Get Raw Data". [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
В текстовом поле должны появиться байты, но это не так важно для нас. Сохраняем в двоичном виде, даём название аля "LvL__41" (чтобы потом не запутаться). [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
Идём в игру, экипируем джинна и повышаем ему уровень (например, засчёт духа игрока). [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
Затем возвращаем джинна в тот же слот инвентаря, делаем второй дамп структуры по тем же оффсетам (можно просто нажать кнопку Update в программе), сохраняем его под именем типа "LvL__42". [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
Основная задача выполнена, закрываем дампер и идём в HEX-редактор, я использую 010 Editor. Открываем в нём сравнение файлов, указываем наши bin-файлики. [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
И здесь хочу заметить, что дамп это как бы снимок памяти, как, например, скриншот из игры: мы видим то, что было когда-то до настоящего момента. Это и позволяет изучить структуру позднее (словно мы открыли старый скрин и смотрим его).
Итак, внизу мы можем видеть совпадающие и отличающиеся области файла. У меня получилось, что первое отличие в файлах находится по смещению +E0 от начала файлов (и, соответственно, структуры джинна в инвентаре), причём отличаются всего три байта. [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
Поставим курсор в начало отличающейся области (прямо в первый байт на +E0) и посмотрим на таблицу слева вверху. Это - интерпретация байтов в виде привычных глазу человека типов. Красным в этой таблице (на скриншоте) я обозначил огромные числа - вряд ли это корректные/нужные данные, а числа размерностью в 1 и 2 байта (отмеченные на скриншоте синим цветом) очень даже подходят: маленькие (уровень точно не будет равен 3473449), равны для двух файлов 41 и 42 соответственно, что и является уровнями джиннов. Какое совпадение!
А теперь определим размер данного значения. Мы уже заметили, что 4-байтовые величины не подходят, а что насчёт 2-байтовых?
Смотрим на запись: 29 00 35 00 (из дампа "LvL__41"). Понимать её стоит справа налево, то есть байты в памяти перевёрнуты. Это совершенно нормально, всегда так и есть.
Перевернём байты: 00 35 00 29. Совершенно очевидно, что байт между двумя числами не используется. Переведём эти два значения (как мы договорились, двухбайтовых) в десятичную систему счисления: 53 и 41. Делаем вывод, что уровень джинна должен храниться в одном или двух байтах (занятый числом 29h + "пустой", где лежит 00), однако я уверен на 100%, что "пустой" байт действительно тоже относится к уровню, и стоит использовать тип short или unsigned short. В точности установить это поможет отладчик - смотреть следует на операции, которые будут работать с этим смещением. Проверим с помощью Cheat Engine. Добавим в таблицу указатель на наше значение (BA +1C +34 +CAC +C +0 +E0), поставим BreakPoint на доступ к конечному адресу. [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
Судя по команде movsx, это целое со знаком (т.е. может принимать отрицательные значения), размерность действительно 2 байта ("word ptr").
Кстати, наш третий отличающийся байт (по смещению +E0) в десятичной системе счисления для первого случая равен 53, а для второго - 54. Смотрим в игре на экипированного джинна с целью найти число 54, обнаруживаем, что свободно 54 стата для распределения.
Логично предположить, что в таком случае смещение до количества свободных статов будет +E2, и это действительно так. [скриншот]
[Ссылки могут видеть только зарегистрированные пользователи. ]
Как видим, это значение тоже хранится в двух байтах. На скриншоте показано, как получать смещения (складываем смещение в столбце и смещение в строке). Берём число, начиная именно с третьего (по счёту) байта, а не с четвёртого, потому что так организована память (повторюсь, байты перевёрнуты). Тем же способом уточним тип значения, это снова будет двухбайтовое целое со знаком.
Таким образом:
ItemStructStart = BA +1C +34 +CAC +C +I*4, где I - номер ячейки инвентаря, в которой лежит предмет, структура которого нас интересует.
Пример не очень удачный.
Поясню: у большинства предметов в игре есть определенный набор байт, определяющий его свойства. Китайцы зовут это октетом.
Для каждого типа предмета используется свой, уникальный по структуре октет.
Октет может содержать в себе как статичный по размеру набор данных, так и динамический.
Если уж говорить о джинах, то структура октета примерно такая:
Код:
Int32 Experience
UInt16 Level
UInt16 TotalAP
UInt16 Strength
UInt16 Agility
UInt16 Vitality
UInt16 Intellect
... еще 9 полей тут (описывать структуру целиком не собираюсь)
Int32 кол-во одежки
{
Int32 ID одежки
...
}
Int32 кол-во скилов
{
Int32 ID скила
...
}
Исходя из выше сказанного можно сразу понять, что оффсеты могут попадать, а могут и нет на нужные вам значения.
Последний раз редактировалось Kitsune; 23.02.2012 в 18:24.
Эм.. Немного не понял. В статье предполагается, что в инвентаре лежит именно джинн, а у него же структура одинаковая для всех типов джиннов, разве нет?
у него же структура одинаковая для всех типов джиннов
Да, для всех предметов одного типа используется одна и та же структура.
Цитата:
Сообщение от BritishColonist
В статье предполагается, что в инвентаре лежит именно джинн
К джинам претензий нет
Хотел лишь подчеркнуть, что некоторые оффсеты в рамках одной структуры для разных предметов могут указывать на совсем разные значения, потому что структуры могут быть динамическими.
Jok3r666, окей, и какое значение ты будешь искать в "просканенной памяти", если оно для тебя неизвестно? И, ах да, чем же ты её "просканишь" так, чтобы сохранилось, скажем, 1000 байт и чтобы потом сразу были заметны разные места двух структур?
________________
Принимаю реквесты на статьи, программы. Всё будет запилено в лучшем виде :3
Jok3r666, окей, и какое значение ты будешь искать в "просканенной памяти", если оно для тебя неизвестно? И, ах да, чем же ты её "просканишь" так, чтобы сохранилось, скажем, 1000 байт и чтобы потом сразу были заметны разные места двух структур?
О_О как неизвестно?
Цитата:
Сообщение от BritishColonist
Запоминаем уровень - 41
Ручками все, а если этот скан частое явление дык и приблуду написать можно.
________________
Для просмотра ссылок или изображений в подписях, у Вас должно быть не менее 10 сообщение(ий). Сейчас у Вас 0 сообщение(ий).
Для просмотра ссылок или изображений в подписях, у Вас должно быть не менее 10 сообщение(ий). Сейчас у Вас 0 сообщение(ий).
Jok3r666, сдаётся мне, ты просто не сталкивался с тем, что описано в теме. Хорошо, раз уж ты не вник в её суть, попробуй рассказать мне, как ты найдёшь смещение до фейки персонажа. А лучше до его класса или пола.
Меня не интересуют сами смещения, только способ поиска.
________________
Принимаю реквесты на статьи, программы. Всё будет запилено в лучшем виде :3
Последний раз редактировалось BritishColonist; 14.03.2012 в 14:58.
bugadenis94, не откет, а октет.
На самом деле сложно сказать. Иногда этой штукой можно вычислить примерные границы структуры.
Безусловно, это можно назвать тяжёлой артиллерией - тут ты точно определяешь, какие байты структур отличаются, определяешь, что это за байты, т.е. описываешь структуру. Одновременно ты видишь, какие байты либо всегда повторяются, либо не имеют смысла. Разве что на основе такого исследования можно выделить октеты.
________________
Принимаю реквесты на статьи, программы. Всё будет запилено в лучшем виде :3