Как выглядит процесс декомпиляции игры изнутри. Рассмотрим на примере разбора одной элементарной процедурки. Вот есть у нас в коде закомментареный вызов неизвестной функции sub_19B80 (название генерится автоматически из адреса и префикса sub_ - subroutine). В IDA она выглядит вот так:
тыц
Код:seg01:00019B80 sub_19B80 proc near ; CODE XREF: w_ProcessHotkey+10Cp cseg01:00019B80 ; w_ShowBoxButtons+40p cseg01:00019B80 push ebx cseg01:00019B81 push ecx cseg01:00019B82 push edx cseg01:00019B83 mov dx, V_CURRENT_PLAYER cseg01:00019B8A xor ebx, ebx cseg01:00019B8C mov V_CURRENT_PLAYER, bx cseg01:00019B93 jmp short loc_19BA6 cseg01:00019B95 ; --------------------------------------------------------------------------- cseg01:00019B95 cseg01:00019B95 loc_19B95: ; CODE XREF: sub_19B80+31j cseg01:00019B95 call sub_22930 cseg01:00019B9A call sub_2B390 cseg01:00019B9F inc V_CURRENT_PLAYER cseg01:00019BA6 cseg01:00019BA6 loc_19BA6: ; CODE XREF: sub_19B80+13j cseg01:00019BA6 xor eax, eax cseg01:00019BA8 mov ax, V_CURRENT_PLAYER cseg01:00019BAE cmp eax, 2 cseg01:00019BB1 jl short loc_19B95 cseg01:00019BB3 mov V_CURRENT_PLAYER, dx cseg01:00019BBA call sub_18488 cseg01:00019BBF mov ecx, 199 cseg01:00019BC4 mov ebx, 319 cseg01:00019BC9 xor edx, edx cseg01:00019BCB xor eax, eax cseg01:00019BCD call w_SetupMouseSizeLimit ; ebx - Max X cseg01:00019BCD ; ecx - Max Y cseg01:00019BD2 call sub_29410 cseg01:00019BD7 pop edx cseg01:00019BD8 pop ecx cseg01:00019BD9 pop ebx cseg01:00019BDA retn cseg01:00019BDA sub_19B80 endp
Для начала перенсем это все в Visual Studio в закомментированный текст. Будет выглядеть как-то так:
тыц
Код:private static void sub_19B80() { //cseg01:00019B80 ; w_ShowBoxButtons+40p //cseg01:00019B80 push ebx //cseg01:00019B81 push ecx //cseg01:00019B82 push edx //cseg01:00019B83 mov dx, V_CURRENT_PLAYER //cseg01:00019B8A xor ebx, ebx //cseg01:00019B8C mov V_CURRENT_PLAYER, bx //cseg01:00019B93 jmp short loc_19BA6 //cseg01:00019B95 ; --------------------------------------------------------------------------- //cseg01:00019B95 //cseg01:00019B95 loc_19B95: ; CODE XREF: sub_19B80+31j //cseg01:00019B95 call sub_22930 //cseg01:00019B9A call sub_2B390 //cseg01:00019B9F inc V_CURRENT_PLAYER //cseg01:00019BA6 //cseg01:00019BA6 loc_19BA6: ; CODE XREF: sub_19B80+13j //cseg01:00019BA6 xor eax, eax //cseg01:00019BA8 mov ax, V_CURRENT_PLAYER //cseg01:00019BAE cmp eax, 2 //cseg01:00019BB1 jl short loc_19B95 //cseg01:00019BB3 mov V_CURRENT_PLAYER, dx //cseg01:00019BBA call sub_18488 //cseg01:00019BBF mov ecx, 199 //cseg01:00019BC4 mov ebx, 319 //cseg01:00019BC9 xor edx, edx //cseg01:00019BCB xor eax, eax //cseg01:00019BCD call w_SetupMouseSizeLimit ; ebx - Max X //cseg01:00019BCD ; ecx - Max Y //cseg01:00019BD2 call sub_29410 //cseg01:00019BD7 pop edx //cseg01:00019BD8 pop ecx //cseg01:00019BD9 pop ebx //cseg01:00019BDA retn //cseg01:00019BDA sub_19B80 endp }
Все еще страшно и непонятно. Для начала выкинем неинтересные нам сохранение и восстановелние регистров в начале и конце. Функция простая, не получает параметры и не возвращает значения, не имеет локалных переменных, так что просто "режем по живому".
тыц
Код:private static void sub_19B80() { //cseg01:00019B83 mov dx, V_CURRENT_PLAYER //cseg01:00019B8A xor ebx, ebx //cseg01:00019B8C mov V_CURRENT_PLAYER, bx //cseg01:00019B93 jmp short loc_19BA6 //cseg01:00019B95 ; --------------------------------------------------------------------------- //cseg01:00019B95 //cseg01:00019B95 loc_19B95: ; CODE XREF: sub_19B80+31j //cseg01:00019B95 call sub_22930 //cseg01:00019B9A call sub_2B390 //cseg01:00019B9F inc V_CURRENT_PLAYER //cseg01:00019BA6 //cseg01:00019BA6 loc_19BA6: ; CODE XREF: sub_19B80+13j //cseg01:00019BA6 xor eax, eax //cseg01:00019BA8 mov ax, V_CURRENT_PLAYER //cseg01:00019BAE cmp eax, 2 //cseg01:00019BB1 jl short loc_19B95 //cseg01:00019BB3 mov V_CURRENT_PLAYER, dx //cseg01:00019BBA call sub_18488 //cseg01:00019BBF mov ecx, 199 //cseg01:00019BC4 mov ebx, 319 //cseg01:00019BC9 xor edx, edx //cseg01:00019BCB xor eax, eax //cseg01:00019BCD call w_SetupMouseSizeLimit ; ebx - Max X //cseg01:00019BCD ; ecx - Max Y //cseg01:00019BD2 call sub_29410 }
Стало немного меньше по размеру, но не менее страшно. Будем разбираться. Первую строчку
//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 всегда подскажет да еще и удобно подсветит.
Теперь наша функция выглядит так:
тыц
Код:private static void sub_19B80() { int edx = NetProc.V_CURRENT_PLAYER; NetProc.V_CURRENT_PLAYER = 0; //cseg01:00019B93 jmp short loc_19BA6 //cseg01:00019B95 ; --------------------------------------------------------------------------- //cseg01:00019B95 //cseg01:00019B95 loc_19B95: ; CODE XREF: sub_19B80+31j //cseg01:00019B95 call sub_22930 //cseg01:00019B9A call sub_2B390 //cseg01:00019B9F inc V_CURRENT_PLAYER //cseg01:00019BA6 //cseg01:00019BA6 loc_19BA6: ; CODE XREF: sub_19B80+13j //cseg01:00019BA6 xor eax, eax //cseg01:00019BA8 mov ax, V_CURRENT_PLAYER //cseg01:00019BAE cmp eax, 2 //cseg01:00019BB1 jl short loc_19B95 //cseg01:00019BB3 mov V_CURRENT_PLAYER, dx //cseg01:00019BBA call sub_18488 //cseg01:00019BBF mov ecx, 199 //cseg01:00019BC4 mov ebx, 319 //cseg01:00019BC9 xor edx, edx //cseg01:00019BCB xor eax, eax //cseg01:00019BCD call w_SetupMouseSizeLimit ; ebx - Max X //cseg01:00019BCD ; ecx - Max Y //cseg01:00019BD2 call sub_29410 }
Следующим идет 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. Переписываем весь страшный код в один простенький цикл:
тыц
Код:private static void sub_19B80() { int edx = NetProc.V_CURRENT_PLAYER; NetProc.V_CURRENT_PLAYER = 0; for (NetProc.V_CURRENT_PLAYER = 0; NetProc.V_CURRENT_PLAYER < 2; NetProc.V_CURRENT_PLAYER++) { //cseg01:00019B95 call sub_22930 //cseg01:00019B9A call sub_2B390 } //cseg01:00019BB3 mov V_CURRENT_PLAYER, dx //cseg01:00019BBA call sub_18488 //cseg01:00019BBF mov ecx, 199 //cseg01:00019BC4 mov ebx, 319 //cseg01:00019BC9 xor edx, edx //cseg01:00019BCB xor eax, eax //cseg01:00019BCD call w_SetupMouseSizeLimit ; ebx - Max X //cseg01:00019BCD ; ecx - Max Y //cseg01:00019BD2 call sub_29410 }
Уже легче. В теле цила мы имеем два вызова. sub_2B390 У меня уже распознан. А вот sub_22930 еще нет. Чтобы его не забыть. создадим в "загашнике" пустышку вида
public static void sub_22930() { }
И функция теперь выглядит вот так:
тыц
Код:private static void sub_19B80() { int edx = NetProc.V_CURRENT_PLAYER; NetProc.V_CURRENT_PLAYER = 0; for (NetProc.V_CURRENT_PLAYER = 0; NetProc.V_CURRENT_PLAYER < 2; NetProc.V_CURRENT_PLAYER++) { Empty.sub_22930(); GameProc.sub_2B390(); } //cseg01:00019BB3 mov V_CURRENT_PLAYER, dx //cseg01:00019BBA call sub_18488 //cseg01:00019BBF mov ecx, 199 //cseg01:00019BC4 mov ebx, 319 //cseg01:00019BC9 xor edx, edx //cseg01:00019BCB xor eax, eax //cseg01:00019BCD call w_SetupMouseSizeLimit ; ebx - Max X //cseg01:00019BCD ; ecx - Max Y //cseg01:00019BD2 call sub_29410 }
Далее мы возвращаем в 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);
Последнего вызова у меня тоже еще нет. Ставим "заглушку". Теперь наша функция выглядит просто и понятно:
тыц
Код:private static void sub_19B80() { int edx = NetProc.V_CURRENT_PLAYER; NetProc.V_CURRENT_PLAYER = 0; for (NetProc.V_CURRENT_PLAYER = 0; NetProc.V_CURRENT_PLAYER < 2; NetProc.V_CURRENT_PLAYER++) { Empty.sub_22930(); GameProc.sub_2B390(); } NetProc.V_CURRENT_PLAYER = edx; MainProc.sub_18488(); MainProc.w_SetupMouseSizeLimit(0, 319, 199, 0); Empty.sub_29410(); }
На все про все уходит минут 5. Следующий важный шаг - это попробовать понять, что функция делает и переименовать ее. Но об этом в другой раз.
-
Скрыть объявление
Друзья, в это тяжёлое и непонятное для всех нас время мы просим вас воздержаться от любых упоминаний политики на форуме, - этим ситуации не поможешь, а только возникнут ненужные ссоры и обиды. Это касается также шуток и юмора на тему конфликта. Пусть войны будут только виртуальными, а политики решают разногласия дипломатическим путём. С уважением, администрация Old-Games.RU.
-
Скрыть объявлениеЕсли Вы видите это сообщение, значит, вы ещё не зарегистрировались на нашем форуме.
Зарегистрируйтесь, если вы хотите принять участие в обсуждениях. Перед регистрацией примите к сведению:
- Не регистрируйтесь с никами типа asdfdadhgd, 354621 и тому подобными, не несущими смысловой нагрузки (ник должен быть читаемым!): такие пользователи будут сразу заблокированы!
- Не регистрируйте больше одной учётной записи. Если у вас возникли проблемы при регистрации, то вы можете воспользоваться формой обратной связи внизу страницы.
- Регистрируйтесь с реально существующими E-mail адресами, иначе вы не сможете завершить регистрацию.
- Обязательно ознакомьтесь с правилами поведения на нашем форуме, чтобы избежать дальнейших конфликтов и непонимания.
С уважением, администрация форума Old-Games.RU
Комментарии
Сортировать комментарии по