Как известно, в данной серии игр в каждой таверне игрок может нанимать лишь одного нового Героя в неделю. Однако
Описание бага: Если во внешней таверне не было найма, то, начиная с 8-го дня, можно купить двух героев в течение двух дней.
Для работы используется дизассемблированный файл heroes4.exe из последнего официального аддона Winds of War. Процедура работы таверны найдена командой ранее и расположена по адресу 4705E0. Из всего алгоритма ее работы меня интересует место, в котором определяется, можно ли в данный момент нанять в таверне Героя, либо необходимо ожидание. В игре это проявляется выводом соответствующего сообщения:
С программной точки зрения это новое окно, которое в игре создается с помощью функции NewWindowCreate (720C80) (распознанным в дизассемблере функциям даны собственные имена). В процедуре таверны несколько вызовов этой функции, и первым претендентом является вызов по адресу 470823. С помощью отладчика убеждаюсь, что, действительно, этот вызов создает искомое диалоговое окно. Код, управляющий этим вызовом NewWindowCreate, находится выше по адресу 470645:
00470638 call HeroesPricesInTavern_Lost0047063D mov al, [ebp+48h] // 0 если таверна работает; 1 если героя нанять нельзя (ждешь 7 дней).00470640 add esp, 800470643 test al, al00470645 jz loc_470866 // Если таверна работает, пропустить вывод сообщения по адресу 470823
Покупаю в таверне Героя, затем устанавливаю бряк на запись на ячейку, адресуемую [ebp+48h], после чего жду 7 игровых дней. Когда таверна освобождается, отладчик всплывает по адресу 470DFF. Давайте посмотрим окружающий код:
00470DF0 TavernCountDays proc near 00470DF0 mov dl, [ecx+48h] // ECX+48h флаг работы таверны:DL=0 если таверна работает;DL=1 если Героя нанять нельзя (ждешь 7 дней)00470DF3 xor eax, eax 00470DF5 cmp dl, al00470DF7 jz short loc_470E0600470DF9 cmp dword ptr [ecx+4Ch], 7 // В [ECX+4Ch] - число дней с момента найма последнего героя в таверне. Если меньше 7 выходим. 00470DFD jl short loc_470E0600470DFF mov [ecx+48h], al // Таверна работает (AL=0)00470E02 mov [ecx+4Ch], eax // Обнулить число дней00470E05 retn00470E0600470E06 loc_470E06: 00470E06 00470E06 inc dword ptr [ecx+4Ch] // Увеличить число дней с момента найма последнего Героя в таверне00470E09 retn00470E09 TavernCountDays endp
Эта небольшая процедура служит для проверки числа дней, в которые таверна закрыта для найма. Замечу, что она вызывается для каждой таверны на карте в каждый игровой день. Что же порождает баг? Программа зачем-то продолжает вести подсчет числа дней, в течении которых в таверне не было найма Героя и по истечении недели, в которую таверна была закрыта (см. счетчик по адресу 470E06). В результате получаем следующую картину. Пусть первый найм Героя происходит только на восьмой игровой день. На входе в процедуру значение флага доступности таверны по адресу [ecx+48h] будет равно 1 (таверна закрыта), а значение счетчика дней по адресу [ecx+4Ch] будет равно 8. Однако при этом, после сравнения по адресу 470DF9, управление получит код по адресу 470DFF, вновь открывающий таверну для найма! При этом счетчик дней сбросится, и после найма второго Героя алгоритм уже отработает, как задумывали авторы. Но через две игровых недели весь цикл повторится.
Самый простой способ пофиксить баг отказаться от подобного подсчета дней. Пусть счетчик работает только тогда, когда таверна закрыта (что логичнее), а в остальное время зададим его равным нулю. Это достигается очень просто изменением перехода по адресу 00470DF7 в конец функции:
00470DF5 cmp dl, al00470DF7 jz short loc_470E09
Теперь остается лишь пропатчить имеющийся код. Для этого смотрим исходный
и измененный
варианты.
Как видно, необходимого результата можно достичь, заменив 0D на 10 по адресу 470DF8. Классика жанра: пропатчить баг, заменив всего лишь один байт!