Изменяем заклинание Стрекоз на Проклятье, вместо Слабости.
Сначала подумаем: за что мы можем зацепиться? Что происходит такого особенного во время колдовства заклинания? Очевидно, играет уникальная анимация закла и его уникальный звук. Они грузятся из геройских архивов и где-то в коде колдовства закла для этого должны использоваться их имена. Вот за это и зацепимся.
snd копать проще и имена файлов там более адекватные, поэтому возьмём звук. Несложно догадаться, что звук называется как-то вроде "weakess.wav". Ищем его в архиве (используя MMArchieve) и находим - он действительно называется "WEAKNESS".
Открываем Ida, в ней экзешник Эры, если это первый раз - ждём окончания анализа. Далее жмём View - Open Suviews - Signatures. Смотрим - если в списке сигнатур нет vc32rtf - нашимаем правой кнопкой мыши - Apply new signature, выбираем vc32rtf, ждём окончания анализа. В этой сигнатуре содержатся стандартные сишные функции, чтобы не пришлось самим разбирать какой-нибудь printf.
Ищем текст "Weakness" (лучше искать в HexView в поле с символами (которое справа), т. к. в самом коде анализатор мог не распознать эти данные как текст и там он не найдётся). Находится несколько "Weakness", одни из них, по адресу 0x688118 - "Weakness.wav" - очевидно, то, что нам нужно. Если в главном окне эти данные не расшифровались как текст, а как что-то другое, жмём на них правой кнопкой мыши, далее жмём "Undefine" - они превратятся в последовательность байтов. Наводим на первый байт и жмём клавишу "A" - последовательность преобразовывается в строку.
Теперь нам надо отловить обращения к этой строке. Если это первый запуск Иды, то нужно выбрать отладчик: внизу верхней полосы с кнопками в выпадающем меню выбрать Local Win32 debugger.
После выбора отладчика появилась возможность ставить брейкпоинты. Наводим курсор на нашу строку, жмём F2 - строка становится красной. Жмём по ней правой кнопкой мыши, выбираем "Edit breakpoint" - там включаем (если они сразу не включены) галочки "Enabled", "Hardware breakpoint", в "Size" выбираем 1 (основное обращение - к первому символу строки), выбираем "Read/Write", включаем галочку "Break" и выключаем "Trace".
Теперь брейкпоинт при чтении (и записи, но записи туда всё равно не будет) первого байта строки остановит Героев и передаст управление отладчику.
Теперь запускаем Героев через Иду. При этом надо убедиться, что они запустятся в оконном режиме, в противном случае любой брейкпоинт или просто вылет повесит Героев и Иду и их придётся вырубать через диспетчер задач. При запуске Героев, а так же при загрузке карты могут вызваться какие-то исключения. Надо в появившемся окне выбрать "Change exception definition", снять галочку "Stop Program", включить "Pass to application" и нажать "Ok". Тогда всё запустится, а эти исключения (видимо, какие-то внутренние, хз, зачем нужны) больше не будут мешать.
Включаем игру, загружаем карту, вступаем в битву со стрекозами, делаем, чтобы стрекозы кого-нибудь ударили. Сразу после удара окно Иды выйдет на первый план - сработал брейкпоинт. Смотрим - он сработал в функции strncpy по адресу 0x618FE0. Очевидно, имя звука куда-то копируется.
Жмём Debugger - Tracing - Stack trace. Открывается Call Stack. Жмём правой кнопкой мыши - Refresh. Теперь здесь видна вся вложенность вызовов функций, вплоть до текущего момента (некоторые функции, правда, пропущены - но тут уж ничего не поделаешь).
Нам надо определить, где была вызвана функция наложения заклинания. Там недалеко должен передаваться его номер - 45 (2Dh).
Идём по Call Stack`у сверху вниз.
По адресу 0x59A8FA - что-то невнятное.
Адрес 0x5A0641 находится внутри большой функции по 0x5A0140 (для наглядности лучше работать в режиме Graph - для переключения используется пробел).
Посмотрим, что это такое. Жмём F5 - декомпиляция. Проглядываем декомпилированный код. Видим интересные вещи:
Code:
LODWORD(v193) = sub_59A770("MagicRes.wav");
Code:
lpCriticalSection = (LPCRITICAL_SECTION)sub_55C930("Quiksand.wav");
Code:
lpCriticalSection = (LPCRITICAL_SECTION)sub_55C930("landmine.wav");
Code:
LODWORD(v92) = sub_59A770("IceRayEx.wav");
Code:
v189 = *(_UNKNOWN **)(loc_703EC6(&v264, "Acid breath reduces the defense of the %s by %i", v188, v185 - *(_DWORD *)(v19 + 204)) + 4);
Code:
v251 = sub_59A770("MagChDrn.wav")
Code:
LODWORD(v252) = sub_59A770("MagChFil.wav");
Т. е. тут в явном виде используются так или иначе связанные с заклинаниями строки. Вполне возможно, что это и есть функция наложения заклинания!
Идём дальше по Call Stack`у. Сразу же, по адресу 0x4411A7, видим вызов функции по 0x5A0140 - ту которую мы предположили функцией наложения заклинания. И прямо перед вызовом, по адресу 0x4411A5 видим "push 2Dh". Это явно то, что мы ищем!
Пытаемся декомпилировать - не выходит, какие-то воговские правки не анализируются. Тогда запоминаем адрес, останавливаем отладку, жмём клавишу "G" - прыжок и прыгаем на запомненный адрес. Теперь, просто в базе, без отладчика всё декомпилируется.
Смотрим декомпилированный код - видим там большой switch. Замечаем, что наш вызов функции с параметром 45 происходит в case 104 и 105 (0x68 и 0x69 в шестнадцатеричке). Смотрим по таблице - это змий и стрекоза. Видим - вызывается функция наложения заклинания (по 0x5A0140) с параметром 78 (снятие полезных заклинания), а дальнейшее выполняется только, если определённая переменная равна 105 (стрекоза). Значит, мы нашли то, что нам нужно.
Смотрим и видим, что там наложение заклинания происходит только при двух условиях: "!*(_DWORD *)(a3 + 588)" и "(unsigned __int8)sub_5A8950(45, v77, a3, 1, 1)". Во втором условии вызывается функция с параметром - номером заклинания. Можно догадаться, что это функция проверки сопротивлений. А можно и не догадываться. Но то, что заменить номер заклинания нужно - это очевидно.
Итак, мы нашли 2 адреса, с номерами (смотрим в HexView, чтобы понять, по какому адресу находится сам номер в команде): байт по 0x441184 + 1 и байт по 0x4411A5 + 1.
Создаём плагин с их правкой. Смотрим - и правда, прокдятье накладывается. Но что-то не так. Ага, оказывается, проклятье накладывается даже тогда, когда оно уже наложено. Смотрим как без плагина: не накладывается. Значит проверка "!*(_DWORD *)(a3 + 588)" была проверкой на наложенность слабости. Нам надо изменить её тоже.
Смотрим ERM-help, команду BM:G. Она предназначена для изменения длительности и силы заклинаний на монстре. Но есть сноска про её недокументированные особенности - изменение любых параметров стека. Что это значит? А это значит, что команда BM:G работает напрямую с памятью и первый её параметр - смещение относительно определённой точки в структуре стека. Для нас тут важно то, что сила и длительность наложенных на существо заклинаний хранится прямо в структуре стека, причём один за другим в порядке возрастания номеров заклинаний. Занимают они все по 4 байта (т. к. здоровье и т. п. занимают, очевидно, по 4 байта, а они правятся так же как и заклинания при помощи BM:G).
Вот, как выглядит наша проверка (по адресу 0x44114B):
Code:
mov eax, [edi+24Сh]
test eax, eax
Очевидно, в edi хранится адрес начала структуры стека, а 24Ch (т. е. 588) - смещение длительности слабости. Т. е. длительность слабости в структуре стека хранится в 4-х байтах, начиная с 589-го. Тут оно оттуда берётся и проверяется на 0.
Проклятье - номер 42, на 3 меньше слабости. Поскольку на каждое поле выделяется по 4 байта, смещение проклятья будет на 3*4 = 12 меньше, чем у слабости. Т. е. прописываем по адресу 0x44114B + 2 в виде 4-х байтов (тип int в C) 576. И теперь будет браться длительность проклятья.
Итак, наш результат:
C++:
Code:
*(char*)(0x441184 + 1) = 42; // Для проверки на сопротивления
*(char*)(0x4411A5 + 1) = 42; // Для каста
*(int*)(0x44114B + 2) = 576; // Для проверки уже наложенного закла
Delphi:
Code:
PByte($441184 + 1)^ := 42; // Для проверки на сопротивления
PByte($4411A5 + 1)^ := 42; // Для каста
PInteger($44114B + 2)^ := 576; // Для проверки уже наложенного закла
Проверяем - всё работает! Даже лог пишется в функции срабатывания заклинания и не надо отдельно его править.