Parte 1. Senhas
O jogo NES, Mike Tyson, Punch-Out usa um sistema de senha que permite aos jogadores continuar o jogo a partir de um ponto específico. Cada senha consiste em 10 dígitos, que podem variar de 0 a 9. O jogo pode aceitar dois tipos de senhas, que eu chamo de senhas “normais” e “especiais”. Senhas especiais são certas combinações de 10 dígitos, cuja entrada o jogo reage de uma maneira única. Uma lista completa de senhas especiais é assim:
- 075 541 6113 - sinal de ocupado 1
- 800 422 2602 - sinal de ocupado 2
- 206 882 2040 - sinal de ocupado 3
- 135 792 4680 - jogo em um torneio oculto: “Another World Circuit” (para que a senha seja aceita, você deve pressionar o botão Selecionar e pressionar A + B)
- 106 113 0120 - exibição de títulos (para que a senha seja aceita, você deve manter pressionado o botão Selecionar e pressionar A + B)
- 007 373 5963 - transfere o jogador para a batalha com Mike Tyson
O segundo tipo de senhas aceitas pelo jogo são senhas regulares. Em senhas regulares, o progresso que o jogador fez no jogo é codificado. Os seguintes dados do jogo são codificados em uma senha regular:
- Número de vitórias na carreira
- Número de perdas de carreira
- Vitórias por nocaute
- Próximo oponente
Codificação de senha
Como exemplo, para estudar a geração de senhas, usamos um jogo com 24 vitórias, 1 derrota, 19 nocautes e começando no torneio mundial com uma batalha contra o Super Macho Man.
O processo de codificação do estado de um jogo em uma senha começa com a coleta do número de vitórias, derrotas e nocautes no buffer. O jogo apresenta cada número na forma de um
código decimal binário formado por 8 bits por dígito e 2 dígitos para cada valor. Ou seja, para 24 vitórias, você precisa de um byte com um valor de 2 e um segundo byte com um valor de 4. O mesmo ocorre com pares de bytes para perdas e nocautes, ou seja, é obtido um total de 6 bytes de dados. No diagrama abaixo, esses 6 bytes são indicados com valores em decimal e binário.
O próximo passo é gerar uma soma de verificação para esses 6 bytes. O byte da soma de verificação é calculado adicionando 6 bytes separados e subtraindo o resultado de 255. No nosso caso, 2 + 4 + 0 + 1 + 1 + 9 = 17, ou seja, 255 - 17 = 238.
Em seguida, escrevemos alguns bits de 6 bytes em um novo buffer. Esse buffer pode ser interpretado como um valor intermediário de 28 bits, que preencheremos passo a passo. Os bits do primeiro buffer são divididos em grupos de dois e são movidos para diferentes posições codificadas do segundo buffer. Esta é a primeira de várias etapas cuja única tarefa é simplesmente ofuscar os dados para complicar o processo de geração de senhas para os jogadores.
Observe que nem todos os bits do buffer original são transferidos para o novo buffer intermediário. Esses bits são ignorados, porque sabe-se que eles sempre são 0. Graças às regras do jogo, basta transmitir o número de perdas na senha com apenas 2 bits de informação. Se a quantidade total de perdas atingir 3, o game over ocorre e o jogador não recebe uma senha. Portanto, basta descrever o número de perdas com os números 0, 1 e 2 e, para isso, apenas 2 bits são suficientes.
Em seguida, escrevemos outros pares de bits no buffer intermediário. Os quatro primeiros pares são obtidos do valor da soma de verificação calculado anteriormente. Outro par é retirado do valor do inimigo. O valor de um adversário é um número que indica qual adversário um jogador lutará após inserir uma senha. Três possíveis valores inimigos podem ser usados:
0 - DON FLAMENCO (primeira luta do torneio principal)
1 - PISTON HONDA (primeira luta do torneio mundial)
2 - SUPER MACHO MAN (última luta do torneio mundial)
Como queremos gerar uma senha que nos confronte com o Super Macho Man, usamos 2 como o valor do adversário.Em seguida, os bits de soma de verificação e os valores do adversário são gravados nos bits intermediários da seguinte maneira:
O próximo passo é executar várias permutações cíclicas dos bits intermediários à esquerda. Uma permutação cíclica para a esquerda significa que todos os bits são deslocados uma posição para a esquerda, e o bit que foi o mais à esquerda se move e se torna o mais à direita. Para calcular o número de permutações para a esquerda, pegamos a soma do valor do oponente e o número de perdas, adicionamos 1 e dividimos por 3 esse resultado. No nosso caso, resulta 2 + 1 + 1 = 4. Então o restante de 4/3 é 1, então alternamos ciclicamente os bits intermediários para a esquerda uma vez.

Neste ponto, os bits intermediários já estão completamente misturados e é hora de começar a quebrá-los para obter os números que compõem a senha. As senhas devem consistir em 10 dígitos, portanto, dividiremos 28 bits intermediários em 10 números separados, que chamaremos de valores de senha P0, P1, P2 etc. Cada um dos nove primeiros valores da senha recebe 3 bits de dados e o último obtém apenas um dos bits intermediários. Para completar o valor da senha final, também incluiremos bits indicando o número de permutações realizadas na etapa anterior.
Por fim, adicionamos a cada valor de senha um deslocamento exclusivo e codificado. O dígito da senha final será o restante dessa soma da divisão por 10. Por exemplo, na sétima posição, usamos o deslocamento 1, ou seja, obtemos 5 + 1 = 6, e o dígito final é o restante de 6/10, ou seja, 6. Na quarta posição, usamos deslocamento 7, ou seja, obtemos 5 + 7 = 12, e o número final é igual ao restante 12/10, ou seja, 2.
Então, temos dígitos de senha prontos que podem ser verificados no jogo.
Decodificação de senha
O processo de decodificação de senhas para o número de vitórias / derrotas / nocautes e o valor do oponente é uma implementação simples na ordem inversa de todas as etapas descritas acima. Vou deixar isso como uma tarefa para os leitores. No entanto, o jogo tem dois erros notáveis que ele faz ao decodificar e verificar as senhas inseridas pelo jogador.
O primeiro erro ocorre na primeira etapa de decodificação da senha, ou seja, ao subtrair compensações para retornar aos valores da senha. Os valores iniciais da senha continham 3 bits de dados cada, ou seja, seus valores antes de aplicar as compensações devem estar no intervalo de 0 a 7. No entanto, o jogador pode inserir uma senha que, após subtrair o deslocamento, resulta em um valor de senha de 8 ou 9 (dividindo por 10 com o restante). Em vez de rejeitar imediatamente essa senha, o jogo por engano não verifica esse caso e permite adicionar um bit de dados adicional ao valor da senha que pode poluir o conjunto de bits intermediários de forma que as senhas não sejam mais exclusivas. Como certos bits intermediários podem ser configurados com o dígito correspondente da senha, OU com um bit adicional do valor da senha vizinha, há muitas senhas que podem ser convertidas no mesmo conjunto de bits intermediários. É por isso que você pode encontrar senhas diferentes que dão o mesmo resultado no jogo, embora devessem ser únicas.
O segundo erro é um erro na lógica que o jogo usa para verificar os dados após decodificar a senha. O jogo está tentando aplicar as seguintes condições:
- a soma de verificação armazenada na senha corresponde à soma de verificação que deve ser obtida levando em consideração o número de vitórias / derrotas / nocautes armazenados na senha
- o valor da perda é 0, 1 ou 2
- valor inimigo é 0, 1 ou 2
- o número de permutações cíclicas armazenadas na senha é o número correto, levando em consideração o valor das perdas e o valor do oponente armazenado na senha
- todos os números de vitória / perda / nocaute armazenados na senha estão no intervalo de 0 a 9
- vitórias> = 3
- vitórias> = nocautes
Se alguma dessas condições não for atendida, o jogo deverá rejeitar a senha. No entanto, há um erro na implementação da verificação final (ou seja, ao verificar os números codificados pelo BCD.) Em vez de verificar a vitória> = nocautes, o jogo permite casos em que o número superior de vitórias é 0, o número mais baixo de vitórias> = 3 e o número superior de nocautes é menor que o número mais baixo vitórias. Por exemplo, um registro com 3 vitórias, 0 derrota e 23 nocautes será aceito pelo jogo (que comprova a senha 099 837 5823), embora deva ser rejeitado (já que é impossível vencer 23 lutas por nocaute se você venceu em apenas 3 lutas).
Conclusão
Os detalhes específicos desse esquema de codificação são exclusivos do Punch-Out, mas a ideia geral de obter bits importantes do estado do jogo, convertendo-os com a possibilidade de restauração para ofuscar o estado inicial e usá-los para gerar um certo número de caracteres para demonstrar ao jogador como uma senha é uma abordagem bastante universal. Você pode usar somas de verificação para que alterações acidentais de senha (por exemplo, se um jogador cometer um erro) geralmente levem à sua rejeição, em vez de criar outra senha com um estado aleatório do jogo.
Parte 2. Visão Geral do Punch-Out
Todo lutador no Punch-Out de Mike Tyson !!! controlado por um ou mais scripts de bytecode interpretados. O personagem do jogador, Little Mac, executa um script simples que contém a lógica de cada ação disponível para o jogador (evasão, bloqueios, socos etc.) Os personagens dos oponentes são controlados por 3 níveis de scripts independentes que juntos criam o comportamento do personagem.
Script de correspondência
O script inimigo de mais alto nível é executado em todas as 3 rodadas de batalha e controla as mudanças mais ambiciosas no comportamento do oponente. Vou chamar esse script de "script de correspondência". Sua principal tarefa é escolher os comportamentos que o inimigo executará em resposta a vários eventos durante a batalha. Por exemplo, um determinado comportamento será acionado instantaneamente depois que o oponente se levantar após um knockdown ou quando o jogador ficar sem corações e ficar cansado. Esses comportamentos são gravados na tabela e são chamados pelo mecanismo de jogo em resposta aos eventos correspondentes. O script de partida também define os valores iniciais para as opções de configuração relacionadas à complexidade da batalha (por exemplo, a quantidade de tempo que o oponente permanece vulnerável após um ataque perdido.) Finalmente, o script de partida começa a esperar por determinados marcadores temporários durante a batalha para fazer alterações nos valores definidos anteriormente .
Script de comportamento
O script de um nível inferior de um adversário é um "script de comportamento". Esse nível é responsável pela sequência de certos golpes e ataques que o oponente deve executar dentro da estrutura do comportamento atual (definido pelo script de partida.) Os scripts de comportamento executam comandos como “aplique o jab certo, pause por 28 quadros, aplique aleatoriamente o uppercut esquerdo ou direito, repita tudo são 5 vezes. " O script também possui comandos para ler e gravar em qualquer endereço na memória do mecanismo do jogo, para que o comportamento possa ser muito dinâmico.
Script de animação
O script do adversário de nível mais baixo é um "script de animação". Esses scripts executam os detalhes de cada golpe individual, bloqueio ou ataque especial como parte do comportamento (definido pelo script de comportamento.) Nesse nível, comandos como "atribuem o sprite 23 ao quadro atual da animação do inimigo, mova-o para baixo e para a direita por 1 pixel a cada segundo quadro em para os próximos 10 quadros, altere o quadro de animação para sprite 24, toque o efeito de som 7 ". Além dos comandos de animação, os scripts de animação também executam sequências de várias mudanças nos estados de jogabilidade que estão intimamente relacionados aos movimentos inimigos. Por exemplo, em uma longa animação de um ataque especial, um script de animação pode inserir comandos que tornam o inimigo vulnerável a knockdowns com um golpe por um período muito curto. Assim como os scripts de comportamento, os scripts de animação podem ler e gravar endereços de memória arbitrários no mecanismo do jogo para obter efeitos mais dinâmicos.
Script Little Mac
O script executado pelo personagem do Little Mac player é mais parecido com os scripts de animação inimigos. Ele altera o quadro atual da animação refletida e move o player pela tela. Como os scripts de animação, o script do Little Mac executa sequências de determinados eventos de jogabilidade, por exemplo, em que momento específico o Mac deve atingir o inimigo ou quando deve executar um bloqueio ou evasão. O script do Little Mac controla a entrada do jogador, semelhante à maneira como os scripts comportamentais controlam os scripts de animação inimigos.
Cada um desses quatro scripts é processado por seu próprio intérprete. Embora muitos deles tenham a mesma funcionalidade, por exemplo, controle de controle básico e acesso direto à memória, cada um dos sistemas implementa sua própria versão e não compartilha o código comum (ou o espaço do opcode) com outros sistemas. Isso permite que cada tipo de script seja muito específico e use efetivamente um pequeno conjunto de comandos de destino. Os dados de script compõem cerca de 22% dos dados não gráficos do cartucho de jogo (o código da máquina para o próprio mecanismo de jogo leva apenas 17%), por isso era muito importante que os scripts tivessem uma aparência compacta.
Parte 3. Script de correspondência de punchout
O script da partida controla o comportamento do oponente no nível mais alto. A principal operação que ele executa repetidamente está aguardando um certo tempo da rodada e fazendo alterações nos dados de configuração do oponente naquele momento. O vídeo mostra a primeira rodada da primeira batalha contra o Bald Bull, junto com um script de partida que controla seu comportamento geral.
Existem três operações principais que um script de correspondência pode executar. O primeiro é aguardar até que o cronômetro redondo atinja um determinado valor. O segundo é perguntar se o comportamento atual do adversário mudou. Os comportamentos são registrados na tabela de configuração da batalha na memória e, em seguida, chamados em momentos diferentes pelos scripts de partida e pelo próprio mecanismo de jogo. A tabela possui dois segmentos de comportamento usados pelos scripts de correspondência. Eu os chamo de comportamento "básico" e "especial". Comportamentos especiais são, por exemplo, um ataque de Bull Charge de um oponente da Bald Bull ou Honda Rush de um oponente da Piston Honda, e os principais comportamentos são os acertos habituais que o oponente realiza pelo resto do tempo. Os scripts comportamentais específicos usados para implementar esses tipos de comportamento podem ser alterados pelo script de partida diretamente durante a batalha, para que os lutadores possam começar com um comportamento principal e depois mudar para outro (conforme notado no vídeo, Bald Bull faz isso quando o cronômetro chega a 0 : 20.)
Uma característica das mudanças de comportamento executadas pelos scripts de correspondência é que elas podem ser substituídas por mudanças comportamentais solicitadas pelo mecanismo de jogo. O mecanismo de jogo usa quatro segmentos comportamentais para solicitar novos comportamentos quando o Mac perde todo o coração e se cansa, e também quando o oponente se levanta após um knockdown. Se o script de partida atender à solicitação para alterar o comportamento, mas um desses quatro eventos do mecanismo de jogo ocorrer antes do processamento da solicitação (as solicitações não poderão ser processadas até que o oponente entre no estado de espera), o mecanismo de jogo definirá o comportamento desejado e a solicitação para o script de partida será rejeitada. Alguns lutadores, como o Bald Bull, solicitam comportamentos especiais várias vezes durante um curto período de tempo. Parece que isso é necessário apenas para reduzir a probabilidade de que qualquer uma dessas solicitações seja descartada acidentalmente.
A terceira operação principal do script de correspondência é a correção de memória. A maioria dos patches de memória afeta a tabela de configuração de batalha, na qual os scripts de comportamento são registrados. Além de conjuntos de comportamentos, a tabela contém dados relacionados à complexidade da batalha. Por exemplo, quando o cronômetro do vídeo atinge 0:30, o Bald Bull altera suas configurações de segurança. Isso leva ao fato de que o jogador não pode mais enganá-lo pressionando e, em seguida, dando um golpe no corpo. Além disso, os scripts de correspondência têm a capacidade de corrigir endereços de memória arbitrários, mas essa função é usada apenas uma vez - no início do segundo turno com Mike Tyson, para que o jogador receba uma estrela pela primeira vez quando o atinge, que está no modo de espera.
Parte 4. Script de comportamento de perfuração
Agora, examinamos os scripts de comportamento diretamente envolvidos na implementação de comportamentos.
O vídeo mostra uma interpretação de como pode ser o script de comportamento rival do Piston Honda 1 nas equipes inglesas.
Comandos de animação
Os scripts comportamentais são responsáveis pelo lançamento sequencial de animações da mesma maneira que os scripts de correspondência foram responsáveis pelo lançamento das animações. O comando
anim
reproduz uma animação específica e o comando
anim_rnd
executa uma animação selecionada aleatoriamente em uma lista de 8 opções. No vídeo acima, no momento de uma seleção aleatória de uma lista de opções, a opção selecionada é destacada momentaneamente em vermelho. Quando a Piston Honda aplica seus dois primeiros jabs, o
anim
usado para cada um deles. Depois disso, ele usa
anim_rnd
para selecionar aleatoriamente um conjunto contendo 6 animações de gancho e 2 animações vazias. Como resultado disso, ele fica viciado em 75% das vezes e não faz nada em 25% das vezes.
Do ponto de vista do script de comportamento, as animações são reproduzidas de forma síncrona, porque quando o sistema de animação não está no modo ocioso, o interpretador de scripts é pausado.
Comandos de controle de execução
Existem vários comandos que modificam a execução do próprio script de comportamento.
pause
comandos
pause
podem pausar a execução do script para um determinado número de quadros ou para o número de quadros selecionados aleatoriamente em uma lista de 2 opções.
Existem vários comandos de ramificação que, sob certas condições, alternam opcionalmente para diferentes partes do script de comportamento. O
branch_rnd
tem uma probabilidade determinada de que um branch ocorra toda vez que for executado. Um caso especial de ramificação probabilística é o comando
branch_always
, que tem 100% de probabilidade de ramificação.
Um mecanismo de loop simples é incorporado ao interpretador de scripts de comportamento. O comando
set_loop_count
define o valor atual do contador de loop. Cada vez que o comando
branch_while_loop
é
branch_while_loop
ele diminui o valor do contador de loop em um e executa ramificação apenas se o valor do contador for maior que zero.
O último tipo de ramificação verifica o conteúdo da memória para tomar uma decisão sobre ramificação. Piston Honda usa esse comando
branch_mem_test
para verificar se seu último golpe em um determinado comportamento foi bem-sucedido. Se o acerto atingir o alvo, ele se ramifica para o próximo acerto. Se a
branch_while_loop
não tiver sido bem-sucedida, ele usará o comando
branch_while_loop
para continuar
branch_while_loop
apenas quando cinco
branch_while_loop
com
branch_while_loop
acumuladas.
Comandos de comportamento
Existem dois comandos pelos quais os scripts de comportamento podem controlar o próprio sistema de comportamento. O comando
begin_behavior_main
usado para finalizar o comportamento atual do executável e iniciar o comportamento principal. Isso difere da ramificação no script de comportamento, porque a parte do script considerada o comportamento "principal" atual pode ser alterada durante a correspondência pelo script de correspondência (consulte a parte anterior do artigo sobre scripts de correspondência).
Outro comando relacionado ao comportamento é
enable_behavior_change
. Quando um novo comportamento é iniciado, ele começa com um estado de bloqueio, quando todas as outras solicitações de alteração de comportamento são bloqueadas. Usando o comando
enable_behavior_change
script sinaliza que está pronto para permitir outros comportamentos. Por exemplo, no comportamento especial da Piston Honda, o comando
enable_behavior_change
nunca
enable_behavior_change
executado; portanto, se o Mac estiver cansado naquele momento, o comportamento especial continuará. No entanto, eventos de knockdown ignoram esse sistema, portanto, se durante o comportamento especial do Piston Honda o personagem principal for derrubado, o comportamento será alterado em qualquer caso.