![](https://habrastorage.org/web/7b6/b56/0ea/7b6b560eae75424a9b3c160fbf6309ad.jpg)
Многие играли в эту замечательную игру. Интересный сюжет, хорошая музыка, неплохой геймплей. Только есть пара моментов, которые мне не нравятся. Бег персонажей очень ограниченный, буквально несколько секунд, а запас сил восстанавливается долго. Система начисления опыта не стимулирует брать напарников, потому что опыт распределяется на всех поровну, и лучше бегать одному, чтобы забирать весь опыт себе. Возьмем отладчик и попробуем это исправить.
Нам понадобятся ArtMoney, IDA, Hiew.
В действиях ничего сложного нет, здесь главное результат. Смещения приведены для версии 1.07.
Запускаем IDA, загружаем файл game.exe, ждем пока завершится анализ. Запускаем стартер, убираем настройку «Полноэкранный режим». Запускаем игру в отладчике, загружаем сохранение где уже можно взять напарника. Берем напарника, выходим на карту.
![](https://habrastorage.org/web/ec2/732/fb0/ec2732fb06c044a2bc8257e8399bf220.jpg)
Бег
Смотрим, сколько запаса сил у персонажа. Здесь это 54. Запускаем ArtMoney. Ищем это значение. Тип «С точкой 4 байта». Пробегаем сколько-нибудь и отсеиваем новое значение. Повторяем сколько нужно, у меня сразу осталось одно значение.
![](https://habrastorage.org/web/001/b72/72c/001b7272c1a04f29b94665bd0f0e5617.png)
Пробуем заморозить, если запас сил при беге восстанавливается, значит нашли правильно. Только надо снять потом. Включаем режим бега. Ставим игру на паузу (пробел).
Ставим на этот адрес брейкпойнт на запись в IDA. Перед этим обязательно надо сделать 'Pause process', иначе случаются вылеты. Переключаемся на игру, отправляем персонажа в какую-нибудь точку, снимаем с паузы.
Брейкпойнт срабатывает.
.text:00548315 loc_548315:
.text:00548315 fld dword ptr [edi+14h]
.text:00548318 fld dword ptr [edi+18h]
.text:0054831B fmul ds:dbl_73F088
.text:00548321 fsubp st(1), st
.text:00548323 fst dword ptr [edi+14h]
.text:00548326 > fcomp ds:flt_73B858
.text:0054832C fnstsw ax
.text:0054832E test ah, 1
.text:00548331 jz short loc_548388
Запись происходит в инструкции
fst dword ptr [edi+14h]
. Можно поставить Operand type — Floating point на этот адрес и на соседний [edi+18h]
.![](https://habrastorage.org/web/2fd/f13/22e/2fdf1322e1734f789f32a782a79e20c5.png)
Судя по значениям, там хранится:
[edi+14h]
— текущее значение[edi+18h]
— максимальное значениеВидно, что максимальный запас сил персонажа умножается на константу
dbl_73F088
, и результат вычитается из текущего значения. Поэтому все персонажи бегают одинаково..rdata:0073F088 dbl_73F088 dq 6.666666666666666e-3
; 6.666666666666666e-3 = 0.006666666666666666 = 1/150
То есть персонаж может пробежать примерно 150 «шагов», но это не совсем шаги, которые видно на анимации, потому что вычитание происходит гораздо чаще. Полный запас сил расходуется за 9-10 секунд, значит вычитание вызывается 15 — 16.66 раз в секунду.
Скорее всего это зеленые точки, обозначающие путь.
![](https://habrastorage.org/web/fce/8d8/9de/fce8d89de90f49ef9a3017de50ed2bfe.jpg)
В байтах эта константа записывается так:
4E 1B E8 B4 81 4E 7B 3F
Открываем game.exe в Hiew и переходим на адрес ".73F088".
![](https://habrastorage.org/web/de9/782/01a/de978201a0854f6ab7eba403b8cd5461.png)
Других констант с таким значением нет, ссылка на нее есть только в рассмотренном коде. Можно ее поменять на любое значение, которое вам нужно. Я сделал себе в 3 раза меньше.
(1/150)/3 = 1/450 = 0.0022222222222222222
Для конвертации float/double в hex-представление можно воспользоваться онлайн-конвертером, например этим.
0.0066666666666666667 - 0x3F7B4E81B4E81B4F
0.006666666666666666 - 0x3F7B4E81B4E81B4E
0.0022222222222222222 - 0x3F623456789ABCDF
0.002222222222222222 - 0x3F623456789ABCDE
Получается красивое число
0x3F623456789ABCDF
DF BC 9A 78 56 34 62 3F
Заменяем, сохраняем, запускаем. Вот, так гораздо лучше.
Опыт
Тут немного сложнее. Он не хранится в явном виде. Нужно искать через связанные значения. Начинаем, впрочем, так же.
Смотрим, сколько опыта отображается у персонажа. Это можно сделать в режиме между картами. У меня это 116.
![](https://habrastorage.org/web/8f8/66e/39c/8f866e39c03c4c8b8fa3c19d28f180e9.jpg)
Ищем это значение. Здесь нужен тип «Целое 4 байта».
Это не исходная переменная, а вычисляемое значение, приведенное к int. Сам опыт хранится во float, но там другое значение, об этом ниже.
Теперь можно с кем-нибудь подраться. Выходить с карты чтобы посмотреть опыт нельзя, перезагружаться тоже, потому что память выделяется заново, и при повторном заходе на карту будут другие адреса. Надо прибавлять в уме. При этом надо учитывать округление. То есть, если за противника дается 5 очков опыта, и в команде 2 персонажа, то на экране будет отображаться опыт 2, но прибавлять надо 2.5 и брать целую часть.
![](https://habrastorage.org/web/17d/55e/385/17d55e38502f4d2dbe22da0fb1b61580.jpg)
После пары повторений у меня осталось 6 значений, которые изменяются синхронно.
![](https://habrastorage.org/web/3c4/690/783/3c4690783d9d45f3be49cff4294a5655.png)
Попробуем поставить брейкпойнты на запись на каждый адрес. Не забываем про «Pause process».
Подходит первый адрес. Остальные срабатывают по
rep movsd
..text:00522D00 fld dword ptr [ebx+700h]
.text:00522D06 fadd dword ptr [ebx+4]
.text:00522D09 fsub dword ptr [ebx+8]
.text:00522D0C fstp [ebp+var_10]
.text:00522D0F fld [ebp+var_10]
.text:00522D12 fistp [ebp+var_C]
.text:00522D15 mov edx, [ebp+var_C]
.text:00522D18 mov [edi+8], edx
.text:00522D1B > mov eax, [ebx+10h]
.text:00522D1E mov [ebp+var_10], eax
Он срабатывает при беге или при бое любого из персонажей. Поэтому здесь лучше управлять только одним, а не группой, чтобы не путаться. В
ebx
находится адрес объекта персонажа. В [ebx+700h]
находится 0.Посмотрим значения.
![](https://habrastorage.org/web/608/e5f/3aa/608e5f3aaa764f73a52a18c5ba9d13c9.png)
387 — 271 = 116
Можно предположить, что это полученный и потраченный опыт, а текущий рассчитывается как их разность.
У меня игра падала несколько раз, поэтому я ставил брейкпойнт на код и загружал с сохранения. Поэтому тут 116, а не 120. Но это только подтверждает догадку.
Поставим новый брейкпойнт на
[ebx+4]
.Выбираем всю группу и нападаем на противника.
![](https://habrastorage.org/web/369/b1d/0fd/369b1d0fdc7a4cd3938e678502fadd72.jpg)
Брейкпойнт срабатывает до того, как это будет видно на экране.
.text:005239D7 fld ds:dbl_73E128
.text:005239DD fld dword ptr [esi+20h]
.text:005239E0 fsub ds:flt_73E124
.text:005239E6 call __CIpow
.text:005239EB fmul [ebp+arg_4]
.text:005239EE fadd dword ptr [esi+700h]
.text:005239F4 fcom ds:flt_73B858
.text:005239FA fst dword ptr [esi+700h]
.text:00523A00 fnstsw ax
.text:00523A02 test ah, 41h
.text:00523A05 jnz short loc_523A19
.text:00523A07 fadd dword ptr [esi+4]
.text:00523A0A mov dword ptr [esi+700h], 0
.text:00523A14 fstp dword ptr [esi+4]
.text:00523A17 > jmp short loc_523A1B
В
[esi+4]
новое значение опыта. В [ebp+arg_4]
число 2.0. За молодого кабана дается 4.0, значит деление находится до вызова функции.Выходим из функции через Ctrl+F7. Это обертка, выходим еще раз.
Смотрим чуть выше, там находится такой код.
.text:00591521 loc_591521:
.text:00591521 fild [ebp+var_18]
.text:00591524 xor esi, esi
.text:00591526 cmp eax, edi
.text:00591528 mov [ebp+var_14], esi
.text:0059152B fdivr [ebp+arg_4]
.text:0059152E fstp [ebp+arg_4]
.text:00591531 jle short loc_5915A5
.text:00591533 jmp short loc_591537
Поставим брейкпойнт на
00591521
и поучаствуем в битве еще раз.fdivr
делит аргумент на st(0)
и результат записывает в st(0)
: st(0) = arg / st(0)
. В st(0)
находится значение [ebp+var_18]
, в котором находится 2 — число персонажей. В [ebp+arg_4]
находится 4.0 — опыт за противника. При выполнении миссии начисление тоже происходит здесь.Также деление на число участников есть выше:
.text:00591324 fdiv [ebp+var_18]
Но я не нашел, когда выполняется этот код. Там трогать не будем.
Теперь через Hiew можно убрать код для деления. Из-за особенностей
fdivr
заменяем на nop
все 3 команды (9 байт).; было
.00591521: DB45E8 fild d,[ebp][-018]
.00591524: 33F6 xor esi,esi
.00591526: 3BC7 cmp eax,edi
.00591528: 8975EC mov [ebp][-014],esi
.0059152B: D87D0C fdivr d,[ebp][00C]
.0059152E: D95D0C fstp d,[ebp][00C]
.00591531: 7E72 jle .0005915A5
.00591533: EB02 jmps .000591537
; стало
.00591521: 909090 nop
.00591524: 33F6 xor esi,esi
.00591526: 3BC7 cmp eax,edi
.00591528: 8975EC mov [ebp][-014],esi
.0059152B: 909090 nop
.0059152E: 909090 nop
.00591531: 7E72 jle .0005915A5
.00591533: EB02 jmps .000591537
Теперь персонажи и бегают нормально, и запас сил не бесконечный, на заклинания расходуется как обычно, механика игры сохранена. А напарники получают свой независимый опыт, который можно тратить на их развитие.
Поехали!
Комментарии (14)
demoth
13.08.2017 01:16+2Хорошая статья. Рад, что ещё есть те, кто помнят эту замечательную игру. :)
Пара моментов:
1) Патч 1.07 — это неофициальный фанатский патч, он изменяет только ресурсы игры, но не сам движок. Последний официальный патч — 1.06.
2) Можно было бы обойтись без связки ArtMoney + IDA с помощью CheatEngine. Он умеет всё то, что и ArtMoney, но кроме этого ещё умеет отслеживать, какие инструкции обращаются к указанному адресу (на чтение и/или запись). Разумеется, для чего-то сложного IDA выкинуть уже не получится, но вот ArtMoney — запросто.
Maximbl4
13.08.2017 08:33-8В детстве, когда ещё не умел программировать уже ломал с помощью артмани проклятые земли) да и без всяких отладчиков…
michael_vostrikov Автор
13.08.2017 16:14+1Так многие ломали. А начисление опыта например без отладчика не поменять.
Помню, хотел побегать в сетевой игре на сложных картах. Но там всякие зомби и тени, в одиночку не пройти, надо проходить командой и хорошо прокачанными. Набивал опыт на задании с циклопами, прокачивал ближний бой и кислотную магию, но все равно не хватало. Потом сообразил, как через Артмани взломать силу заклинания в конструкторе заклинаний. Сделал себе мощную молнию на 3 цели и прошел везде где еще не был)
MorgenS
13.08.2017 20:17+1Это все очень круто, но все же несколько ломает механику игры :) При равном распределении опыта между персом и напарниками вскоре возникнет дисбаланс.
С разрешения автора утащу ссылочку для размещения на allods.gipat.ru :)michael_vostrikov Автор
13.08.2017 20:47+1Играл на сложном, особого дисбаланса не заметил. Напарники же не переходят в другой мир, новых надо заново прокачивать. Разве что под конец чуть попроще, потому что в целом опыта много дается. А на Гипате очень даже к месту.
Буду рад)
xlenz
13.08.2017 20:51+2Простите за то что не совсем по теме, но, скажите, есть ли более современный аналог этой игры? Прошёл её много раз, но хотелось бы другой сценарий и графу современную.
michael_vostrikov Автор
13.08.2017 23:03+1Не знаю, я за современными играми не слежу. Иногда читаю описания, но как-то ничего не цепляет. Из современных только в Ведьмака играл.
LoadRunner
14.08.2017 09:46аналог
Это «action-rpg с активной паузой»? На ум приходят SW:KotOR, Jade Empire, Mass Effect (первый), Dragon Age. По сравнению с третьими Аллодами — это подходит под «более современный».
tSmoker
14.08.2017 17:03+2...«Плотину надо поднять рычагом. Я его дам. Канал нужно завалить камнем. Камень я не дам»… что-то вспомнилось…
q_styler
Я как будто попал в свой 10й класс. Проклятые земли, артмани!
tangro
Да, знатный флешбек на полтора десятилетия… А ведь до этого были ещё и оригинальные «Аллоды» не испоганненые меилрушыщем.