1. Друзья, в это тяжёлое и непонятное для всех нас время мы просим вас воздержаться от любых упоминаний политики на форуме, - этим ситуации не поможешь, а только возникнут ненужные ссоры и обиды. Это касается также шуток и юмора на тему конфликта. Пусть войны будут только виртуальными, а политики решают разногласия дипломатическим путём. С уважением, администрация Old-Games.RU.

    Скрыть объявление
  2. Если Вы видите это сообщение, значит, вы ещё не зарегистрировались на нашем форуме.

    Зарегистрируйтесь, если вы хотите принять участие в обсуждениях. Перед регистрацией примите к сведению:
    1. Не регистрируйтесь с никами типа asdfdadhgd, 354621 и тому подобными, не несущими смысловой нагрузки (ник должен быть читаемым!): такие пользователи будут сразу заблокированы!
    2. Не регистрируйте больше одной учётной записи. Если у вас возникли проблемы при регистрации, то вы можете воспользоваться формой обратной связи внизу страницы.
    3. Регистрируйтесь с реально существующими E-mail адресами, иначе вы не сможете завершить регистрацию.
    4. Обязательно ознакомьтесь с правилами поведения на нашем форуме, чтобы избежать дальнейших конфликтов и непонимания.
    С уважением, администрация форума Old-Games.RU
    Скрыть объявление

[Ковыряние] Warcraft Orc and Humans. Не так страшен ассемблер, как его малюют.

Автор: Zelya · 29 ноя 2018 · ·
  1. Как выглядит процесс декомпиляции игры изнутри. Рассмотрим на примере разбора одной элементарной процедурки. Вот есть у нас в коде закомментареный вызов неизвестной функции sub_19B80 (название генерится автоматически из адреса и префикса sub_ - subroutine). В IDA она выглядит вот так:

    тыц

    Для начала перенсем это все в Visual Studio в закомментированный текст. Будет выглядеть как-то так:

    тыц

    Все еще страшно и непонятно. Для начала выкинем неинтересные нам сохранение и восстановелние регистров в начале и конце. Функция простая, не получает параметры и не возвращает значения, не имеет локалных переменных, так что просто "режем по живому".

    тыц

    Стало немного меньше по размеру, но не менее страшно. Будем разбираться. Первую строчку
    //cseg01:00019B83 mov dx, V_CURRENT_PLAYER
    перепишем "в лоб". Переменная V_CURRENT_PLAYER у меня уже определена. Если б ее не было просто создаем статическую переменную рядом. Важно посмотреть в IDA, имеет ли она дефолтное значение, и сразу присвоить его при создании. И так, первая строчка такая:
    int edx = NetProc.V_CURRENT_PLAYER;

    Следующие две строки тоже очень простые:
    //cseg01:00019B8A xor ebx, ebx
    //cseg01:00019B8C mov V_CURRENT_PLAYER, bx

    Мы зануливаем регистр ebx и пишем его значение в V_CURRENT_PLAYER. Так и делаем:

    NetProc.V_CURRENT_PLAYER = 0;

    Здесь важно учесть, что мы выкинули ненужное нам присваивание ebx. Но в некоторых случаях это значение может использоваться далее. Нужно помнить его в голове. Но даже если забыли, IDA всегда подскажет да еще и удобно подсветит.
    Теперь наша функция выглядит так:

    тыц

    Следующим идет jump на loc_19BA6. Так-так, а что делается на loc_19BA6?

    //cseg01:00019BA6 xor eax, eax
    //cseg01:00019BA8 mov ax, V_CURRENT_PLAYER
    //cseg01:00019BAE cmp eax, 2
    //cseg01:00019BB1 jl short loc_19B95

    Ага. Присваиваем в eax значение V_CURRENT_PLAYER (с предварительным занулением) и проверяем не меньше ли оно двух. И если меньше то возвращаемся... Погодите-погодите, что-то очень знакомое. Это же типичный цикл, а ну-ка, глянем строчку выше loc_19BA6.
    //cseg01:00019B9F inc V_CURRENT_PLAYER
    Так и есть! Инкремент V_CURRENT_PLAYER. Переписываем весь страшный код в один простенький цикл:

    тыц

    Уже легче. В теле цила мы имеем два вызова. sub_2B390 У меня уже распознан. А вот sub_22930 еще нет. Чтобы его не забыть. создадим в "загашнике" пустышку вида

    public static void sub_22930() { }

    И функция теперь выглядит вот так:

    тыц

    Далее мы возвращаем в V_CURRENT_PLAYER значение edx. Теперь все становится понятным. Сначала мы бекапим V_CURRENT_PLAYER, чтобы пробежаться по всем игрокам в цикле (аж два игрока). Потом все возвращаем. Если хочется, можно edx переименовать в что-то типа currentPlayer. Но я обычно ленюсь и переименовываю регистры только в местах сложной логики.

    Ниже идет непонятная работа с регистрами и вызов w_SetupMouseSizeLimit. Здесь требуется пояснение. Игра собрана очень древним компилятором. Если сейчас все аргументы передаются в функции стеком, то раньше первые 4-ре передавались регистрами, в порядке eax, edx, ebx, ecx. Но, когда я начинал декомпиляцию, я про это не знал, и использовал порядок: eax, ebx, ecx, edx. Поэтому у меня есть некий "расколбас" в параметрах функций, например:

    public static void w_SetupMouseSizeLimit(int mixX, int maxX, int maxY, int minY)

    Аж просится minY на вторую позицию. Но мне лень переделывать сотни функций. Да и привык уже. Так что внимательно смотрим на регистры и расставляем их в виде параметров:

    MainProc.w_SetupMouseSizeLimit(0, 319, 199, 0);

    Последнего вызова у меня тоже еще нет. Ставим "заглушку". Теперь наша функция выглядит просто и понятно:

    тыц

    На все про все уходит минут 5. Следующий важный шаг - это попробовать понять, что функция делает и переименовать ее. Но об этом в другой раз.
    hardcorenexus, Dimouse, Alex Ghoust и 2 другим нравится это.

Комментарии

  1. Scorp
    Я конечно всё понимаю, последовательный перевод в код, интересно...

    Но черт возьми, зачем, когда можно нажать F5, получить сразу готовый код и там интерактивно всё переименовать? Ладно бы это был какой-нибудь недекомпилируемый код...

    [​IMG]
  2. Zelya
    Декомпайлер для IDA - весьма недешевое удовольствие. Мой пример - для бесплатной версии.
  3. Scorp
    Не хочешь через IDA - есть radare2, rec, JEB и еще какие-то бесплатные аналоги-декомпиляторы. Ну как бы дело твое, конечно, раз хочется пошагово переводить.


    Ну и такой подход работает только для очень древних игр. Если попробовать перевести что-нибудь из относительно новых, для чего нет декомпилятора (ну например какую-нибудь игру для PSP/PS2, которые на мипсах) - думаю это будет куда сложнее.
  4. Zelya
    Я несколько раз "попалился" на всяких бесплатных декомпиляторах, которые переводили непонятные им куски кода в неправильный код. Оставляли б хотя бы ассемблер. Я вполне допускаю, что приведенные Вами примеры очень добротные. Но пробовать вряд ли буду. Мне больше хочется, как опять же Вы заметили
    Ну и плюс IDA очень удобная по работе с структурами, энумками, типами данных навигацией, подвсеткой и т.д.
    Если б цель стояла не в каких-то своих маниакальных асм-интересах, а в получении полностью декомпилированной игры, тут однозначно - полная IDA. Каюсь, я игрался немного с пираткой и она мне очень понравилась. Стоила б она сотню баксов - взял бы не задумываясь. Но тысяча баксов, хоть и подъемная сумма, но для "дуракаваляния", как по-мне, слишком круто.

    Абсолютно согласен.

    ПС Есть нескромный вопросик. Тут впомнил, что у меня с пиратки 6.8 завалялся интересный файлец (64 битный формат БД). Бесплатная 7.0 не желает его читать. Пишет, что слишком старый. 5.0 такое не читает в принципе. Есть ли возможность его "перегнать" в 7.0?
  5. Scorp
    Я пробовал radare2 и rec, нормально переводили, что им непонятно - оставляли асмом.

    1000 это без декомпилятора как бы :) Декомпилятор стоит отдельно x86 Decompiler Fixed License [Windows] - 2629 USD

    Вот, рассказал бы людям про это. А то там достаточно непрозрачно сделаны структуры для входа-выхода (ну то есть их надо еще задать правильно), чтобы не получать ересь типа (signed int)(unsigned __int16).

    У меня нет 7.0, увы. Но никто не сможет тебе помешать скачать слитую 7.0, заимпортить и удалить.
Чтобы оставить комментарий просто зарегистрируйтесь и станьте участником!
  1. На этом сайте используются файлы cookie, чтобы персонализировать содержимое, хранить Ваши предпочтения и держать Вас авторизованным в системе, если Вы зарегистрировались.
    Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie.
    Скрыть объявление