Heroes of Might and Magic IV: bug de taverna ou clássico de patching

Este conto descreve um dos projetos realizados pelo projeto Equilibris , um mod não oficial para Heroes of Might and Magic IV. Do ponto de vista da engenharia reversa e do patching, não é de interesse particular - apenas o final acabou sendo um tanto engraçado.



imagem


Como você sabe, nesta série de jogos em cada taberna, o jogador só pode contratar um novo Herói por semana. Mas…



Descrição do bug: Se não houve recrutamento na taberna externa, então, a partir do 8º dia, você pode comprar dois heróis em dois dias.



O arquivo desmontado heroes4.exe do último complemento oficial "Winds of War" é usado para o trabalho. O procedimento de operação da taverna foi descoberto pela equipe anteriormente e está localizado no endereço 4705E0. De todo o algoritmo de seu trabalho, estou interessado no local em que se determina se é possível contratar um Herói na taberna no momento, ou se é necessário esperar. No jogo, isso é manifestado pela saída da mensagem correspondente:





Do ponto de vista programático, esta é uma nova janela que é criada no jogo usando a função NewWindowCreate (720C80) (as funções reconhecidas no desmontador recebem seus próprios nomes). Existem várias chamadas para esta função no procedimento da taverna, e o primeiro desafiante é uma chamada para o endereço 470823. Com a ajuda do depurador, certifico-me de que, de fato, esta chamada cria a caixa de diálogo desejada. O código que controla esta chamada para NewWindowCreate está localizado acima em 470645:



00470638                 call    HeroesPricesInTavern_Lost
0047063D                 mov   al, [ebp+48h]  // 0 –   ; 1 –     ( 7 ).
00470640                 add     esp, 8
00470643                 test    al, al
00470645                jz      loc_470866 //   ,      470823


Eu compro na taverna do Herói, em seguida, defino o "ponto de interrupção" para escrever para o celular endereçado a [ebp + 48h], após o qual aguardo 7 dias no jogo. Quando a taverna é "esvaziada", o depurador aparece no endereço 470DFF. Vamos ver o código ao redor:



00470DF0 TavernCountDays proc near               
00470DF0                 mov     dl, [ecx+48h] // ECX+48h –   :
DL=0 –   ;
DL=1 –     ( 7 )
00470DF3                 xor     eax, eax 
00470DF5                 cmp     dl, al
00470DF7                 jz      short loc_470E06
00470DF9                 cmp     dword ptr [ecx+4Ch], 7 //  [ECX+4Ch] -         .   7 – . 
00470DFD                 jl      short loc_470E06
00470DFF                mov     [ecx+48h], al  //   (AL=0)
00470E02                 mov     [ecx+4Ch], eax  //   
00470E05                 retn
00470E06
00470E06 loc_470E06:                             
00470E06                                         
00470E06                 inc     dword ptr [ecx+4Ch] //          
00470E09                 retn
00470E09 TavernCountDays endp


Este procedimento rápido é usado para verificar o número de dias que a taberna está fechada para aluguel. Observe que ele é chamado para todas as tabernas do mapa todos os dias de jogo. O que está causando o bug? Por alguma razão, o programa continua a contar o número de dias durante os quais não houve contratação de Herói na taberna e após a semana em que a taberna foi fechada (consulte o contador em 470E06). Como resultado, temos a seguinte imagem. Deixe o primeiro recrutamento do Herói ocorrer apenas no oitavo dia de jogo. Na entrada do procedimento, o valor do sinalizador de disponibilidade da taverna em [ecx + 48h] será "1" (a taverna está fechada), e o valor do contador de dias em [ecx + 4Ch] será "8". Porém, após a comparação em 470DF9, o controle receberá um código em 470DFF, que reabrirá a taverna de aluguel! Isso zera o contador de dias.e após a contratação do segundo Herói, o algoritmo já funcionará, conforme pretendiam os autores. Mas depois de duas semanas no jogo, todo o ciclo se repetirá.



A maneira mais fácil de consertar o bug é pular a contagem dos dias. Deixe o contador funcionar apenas quando a taberna estiver fechada (o que é mais lógico) e, no resto do tempo, defina-o para zero. Isso é obtido de forma muito simples - alterando a transição no endereço 00470DF7 para o final da função:



00470DF5                 cmp     dl, al
00470DF7                 jz      short loc_470E09


Agora tudo o que resta é corrigir o código existente. Para fazer isso, olhe para o original





e modificado





opções.



Como você pode ver, o resultado desejado pode ser alcançado substituindo 0D por 10 no endereço 470DF8. Um clássico do gênero: consertar um bug substituindo apenas um byte!



All Articles