Les entrailles des jeux rétro: Punch-Out pour NES

image

Partie 1. Mots de passe


Le jeu NES Punch-Out de Mike Tyson utilise un système de mot de passe qui permet aux joueurs de continuer le jeu à partir d'un point spécifique. Chaque mot de passe se compose de 10 chiffres, qui peuvent aller de 0 à 9. Le jeu peut accepter deux types de mots de passe, que j'appelle des mots de passe «normaux» et «spéciaux». Les mots de passe spéciaux sont certaines combinaisons de 10 chiffres, dont l'entrée réagit de manière unique au jeu. Une liste complète des mots de passe spéciaux ressemble à ceci:

  • 075541 6113 - signal occupé 1
  • 800422 2602 - signal occupé 2
  • 206 882 2040 - signal occupé 3
  • 135 792 4680 - jeu dans un tournoi caché: «Another World Circuit» (pour que le mot de passe soit accepté, vous devez maintenir le bouton Select enfoncé et appuyer sur A + B)
  • 106113 0120 - affichage des titres (pour que le mot de passe soit accepté, vous devez maintenir le bouton Select enfoncé et appuyer sur A + B)
  • 007373 5963 - transfère le joueur à la bataille avec Mike Tyson

Le deuxième type de mots de passe acceptés par le jeu sont les mots de passe normaux. Dans les mots de passe normaux, la progression du joueur dans le jeu est codée. Les données de jeu suivantes sont encodées dans un mot de passe normal:

  • Nombre de victoires en carrière
  • Nombre de pertes de carrière
  • Knockout gagne
  • Adversaire suivant

Encodage du mot de passe


À titre d'exemple, pour étudier la génération de mots de passe, nous utilisons un jeu avec 24 victoires, 1 défaite, 19 KO et commençant dans le tournoi mondial avec une bataille contre Super Macho Man.

Le processus d'encodage de l'état d'un jeu dans un mot de passe commence par la collecte du nombre de victoires, de pertes et de KO dans le tampon. Le jeu présente chaque nombre sous la forme d'un code décimal binaire formé de 8 bits par chiffre et de 2 chiffres pour chaque valeur. C'est-à-dire que pour 24 victoires, vous avez besoin d'un octet d'une valeur de 2 et d'un deuxième octet d'une valeur de 4. La même chose se produit avec des paires d'octets pour les pertes et les KO, c'est-à-dire que 6 octets de données sont obtenus au total. Dans le diagramme ci-dessous, ces 6 octets sont indiqués avec des valeurs à la fois décimales et binaires.


L'étape suivante consiste à générer une somme de contrôle pour ces 6 octets. L'octet de somme de contrôle est calculé en ajoutant 6 octets distincts et en soustrayant le résultat de 255. Dans notre cas, 2 + 4 + 0 + 1 + 1 + 9 = 17, c'est-à-dire 255 - 17 = 238.

Ensuite, nous écrivons quelques bits de 6 octets dans un nouveau tampon. Ce tampon peut être interprété comme une valeur intermédiaire de 28 bits, que nous remplirons étape par étape. Les bits du premier tampon sont divisés en groupes de deux et sont déplacés vers différentes positions codées en dur du second tampon. Il s'agit de la première de plusieurs étapes dont la seule tâche est de simplement brouiller les données afin de compliquer le processus de génération de mots de passe pour les joueurs.


Notez que tous les bits du tampon d'origine ne sont pas transférés vers le nouveau tampon intermédiaire. Ces bits sont ignorés, car on sait qu'ils sont toujours à 0. Grâce aux règles du jeu, il suffit de transmettre le nombre de pertes dans le mot de passe avec seulement 2 bits d'information. Si le montant total des pertes atteint 3, la partie se termine et le joueur ne reçoit pas de mot de passe. Par conséquent, il suffit de décrire le nombre de pertes avec les nombres 0, 1 et 2, et pour cela, seuls 2 bits suffisent.

Ensuite, nous écrivons d'autres paires de bits dans le tampon intermédiaire. Les quatre premières paires sont extraites de la valeur de somme de contrôle précédemment calculée. Une autre paire est prise de la valeur de l'ennemi. La valeur d'un adversaire est un nombre indiquant quel adversaire un joueur combattra après avoir entré un mot de passe. Trois valeurs ennemies possibles peuvent être utilisées:

0 - DON FLAMENCO (premier combat du tournoi majeur)

1 - PISTON HONDA (premier combat du tournoi mondial)

2 - SUPER MACHO MAN (dernier combat du tournoi mondial)

Puisque nous voulions générer un mot de passe qui nous confronte à Super Macho Man, nous utilisons 2 comme valeur de l'adversaire. Ensuite, les bits de somme de contrôle et les valeurs de l'adversaire sont écrits dans les bits intermédiaires comme suit:


L'étape suivante consiste à effectuer plusieurs permutations cycliques des bits intermédiaires à gauche. Une permutation cyclique vers la gauche signifie que tous les bits sont décalés d'une position vers la gauche, et le bit qui était le plus à gauche se déplace et devient le plus à droite. Pour calculer le nombre de permutations à gauche, nous prenons la somme de la valeur de l'adversaire et du nombre de pertes, ajoutons 1 et prenons le reste de la division par 3 de ce résultat. Dans notre cas, il s'avère 2 + 1 + 1 = 4. Ensuite, le reste de 4/3 est 1, donc nous décalons cycliquement les bits intermédiaires vers la gauche une fois.


À ce stade, les bits intermédiaires sont déjà complètement mélangés et il est temps de commencer à les casser pour obtenir les chiffres qui composent le mot de passe. Les mots de passe doivent être composés de 10 chiffres, nous allons donc diviser 28 bits intermédiaires en 10 nombres distincts, que nous appellerons les valeurs de mot de passe P0, P1, P2, etc. Chacune des neuf premières valeurs de mot de passe reçoit 3 bits de données, et la dernière n'obtient qu'un seul des bits intermédiaires. Pour compléter la valeur finale du mot de passe, nous inclurons également des bits indiquant le nombre de permutations effectuées à l'étape précédente.


Enfin, nous ajoutons à chaque valeur de mot de passe un décalage unique et codé en dur. Le chiffre de mot de passe fini sera le reste de cette somme de la division par 10. Par exemple, dans la septième position, nous utilisons le décalage 1, c'est-à-dire que nous obtenons 5 + 1 = 6, et le dernier chiffre est le reste de 6/10, c'est-à-dire 6. Dans la quatrième position que nous utilisons décalage 7, c'est-à-dire que nous obtenons 5 + 7 = 12, et le chiffre final est égal au reste 12/10, soit 2.


Nous avons donc obtenu des chiffres de mot de passe prêts à l'emploi qui peuvent être vérifiés dans le jeu.


Décodage de mot de passe


Le processus de décodage des mots de passe vers le nombre de victoires / pertes / KO et la valeur de l'adversaire est une implémentation simple dans l'ordre inverse de toutes les étapes décrites ci-dessus. Je vais laisser cela comme tâche aux lecteurs. Cependant, le jeu comporte deux erreurs notables qu'il commet lors du décodage et de la vérification des mots de passe saisis par le joueur.

La première erreur se produit lors de la toute première étape de décodage du mot de passe, c'est-à-dire lors de la soustraction des décalages pour revenir aux valeurs du mot de passe. Les valeurs de mot de passe initiales contenaient chacune 3 bits de données, c'est-à-dire que leurs valeurs avant d'appliquer les décalages devraient être comprises entre 0 et 7. Cependant, le joueur peut entrer un mot de passe qui, après avoir soustrait le décalage, donne une valeur de mot de passe de 8 ou 9 (en divisant par 10 le reste). Au lieu de rejeter immédiatement un tel mot de passe, le jeu ne vérifie pas ce cas par erreur et vous permet d'ajouter un bit de données supplémentaire à la valeur du mot de passe qui peut polluer l'ensemble de bits intermédiaires de telle sorte que les mots de passe ne seront plus uniques. Étant donné que certains bits intermédiaires peuvent être définis soit avec le chiffre correspondant du mot de passe, soit avec un bit supplémentaire de la valeur de mot de passe voisine, il existe de nombreux mots de passe qui peuvent être convertis en le même ensemble de bits intermédiaires. C'est pourquoi vous pouvez trouver différents mots de passe qui donnent le même résultat dans le jeu, bien qu'ils auraient dû être uniques.

La deuxième erreur est un bug dans la logique que le jeu utilise pour vérifier les données après avoir décodé le mot de passe. Le jeu essaie d'appliquer les conditions suivantes:

  • la somme de contrôle stockée dans le mot de passe correspond à la somme de contrôle qui doit être obtenue en tenant compte du nombre de victoires / pertes / KO stocké dans le mot de passe
  • la valeur de perte est 0, 1 ou 2
  • la valeur ennemie est 0, 1 ou 2
  • le nombre de permutations cycliques stockées dans le mot de passe est le nombre correct en tenant compte de la valeur des pertes et de la valeur de l'adversaire stockée dans le mot de passe
  • tous les numéros de gain / perte / KO stockés dans le mot de passe sont compris entre 0 et 9
  • gagne> = 3
  • gagne> = KO

Si l'une de ces conditions n'est pas remplie, le jeu doit rejeter le mot de passe. Cependant, il y a un bug dans l'implémentation de la vérification finale (à savoir, lors de la vérification des nombres encodés par BCD.) Au lieu de vérifier la victoire> = KO, le jeu autorise les cas où le nombre supérieur de victoires est 0, le nombre inférieur de victoires> = 3 et le nombre supérieur de KO est inférieur au nombre inférieur. gagne. Par exemple, un record avec 3 victoires, 0 défaite et 23 KO sera accepté par le jeu (ce qui prouve le mot de passe 099 837 5823), bien qu'il doive être rejeté (car il est impossible de gagner 23 combats par KO si vous avez gagné en seulement 3 combats).

Conclusion


Les détails particuliers d'un tel schéma de codage sont uniques à Punch-Out, mais l'idée générale d'obtenir des bits importants de l'état du jeu, de les convertir avec la possibilité de restauration pour obscurcir l'état initial, puis de les utiliser pour générer un certain nombre de caractères à démontrer au joueur comme mot de passe est une approche assez universelle. Vous pouvez utiliser des sommes de contrôle pour que les modifications accidentelles du mot de passe (par exemple, si un joueur fait une erreur) conduisent le plus souvent à son rejet, plutôt que de créer un autre mot de passe avec un état de jeu aléatoire.

Partie 2. Présentation du Punch-Out


Chaque combattant au Punch-Out de Mike Tyson !!! contrôlé par un ou plusieurs scripts de bytecode interprétés. Le personnage du joueur, Little Mac, exécute un script simple qui contient la logique de chaque action disponible pour le joueur (esquive, blocs, coups de poing, etc.) Les personnages des adversaires sont contrôlés par 3 niveaux de scripts indépendants qui créent ensemble le comportement du personnage.


Script de correspondance


Le script ennemi de plus haut niveau est exécuté tout au long des 3 rounds de bataille et contrôle les changements les plus ambitieux dans le comportement de l'adversaire. J'appellerai ce script «script de correspondance». Sa tâche principale consiste à choisir les comportements que l'ennemi adoptera en réponse à divers événements au cours de la bataille. Par exemple, un certain comportement sera déclenché instantanément après que l'adversaire se lève après un renversement, ou lorsque le joueur manque de cœur et se fatigue. Ces comportements sont écrits dans la table et sont appelés par le moteur de jeu en réponse aux événements correspondants. Le script de match définit également les valeurs initiales des options de configuration liées à la complexité de la bataille (par exemple, la durée pendant laquelle l'adversaire reste vulnérable après une frappe manquée.) Enfin, le script de match commence à attendre certains marqueurs temporaires pendant la bataille pour apporter des modifications aux valeurs précédemment définies. .

Script de comportement


Le script d'un adversaire de niveau inférieur est un «script de comportement». Ce niveau est responsable de la séquence de certains coups et attaques que l'adversaire doit effectuer dans le cadre du comportement actuel (défini par le script de match.) Les scripts de comportement exécutent des commandes comme «appliquer le jab droit, faire une pause de 28 images, appliquer au hasard l'uppercut gauche ou droit, répéter tout c'est 5 fois. " Le script a également des commandes pour lire et écrire à n'importe quelle adresse en mémoire à partir du moteur de jeu, donc le comportement peut être très dynamique.

Script d'animation


Le script adversaire de niveau le plus bas est un «script d'animation». Ces scripts exécutent les détails de chaque coup, bloc ou attaque spéciale dans le cadre du comportement (défini par le script de comportement). À ce niveau, des commandes telles que «attribuer l'image-objet 23 à l'image actuelle de l'animation de l'ennemi, la déplacer vers le bas et vers la droite de 1 pixel toutes les deux images dans pour les 10 images suivantes, changez l'image d'animation en image-objet 24, jouez l'effet sonore 7 ". En plus des commandes d'animation, les scripts d'animation effectuent également des séquences de divers changements dans les états de jeu qui sont étroitement liés aux mouvements ennemis. Par exemple, dans une longue animation d'une attaque spéciale, un script d'animation peut insérer des commandes qui rendent l'ennemi vulnérable aux renversements d'un coup sur une très courte période de temps. Comme les scripts de comportement, les scripts d'animation peuvent lire et écrire des adresses mémoire arbitraires dans le moteur de jeu afin d'obtenir des effets plus dynamiques.

Script Little Mac


Le script exécuté par le personnage du joueur Little Mac est le plus similaire aux scripts d'animation ennemis. Il modifie l'image actuelle de l'animation réfléchie et déplace le joueur sur l'écran. Comme les scripts d'animation, le script Little Mac exécute des séquences de certains événements de gameplay, par exemple, à quel moment particulier le Mac doit frapper l'ennemi, ou quand il doit exécuter un blocage ou une évasion. Le script Little Mac contrôle l'entrée du joueur, de la même manière que les scripts comportementaux contrôlent les scripts d'animation ennemis.

Chacun de ces quatre scripts est traité par son propre interprète. Bien que beaucoup d'entre eux aient les mêmes fonctionnalités, par exemple, un contrôle de contrôle de base et un accès direct à la mémoire, chaque système implémente sa propre version et ne partage pas le code commun (ou l'espace opcode) avec d'autres systèmes. Cela permet à chaque type de script d'être très spécifique et d'utiliser efficacement un petit ensemble de commandes cibles. Les données de script représentent environ 22% des données non graphiques de la cartouche de jeu (le code machine du moteur de jeu lui-même ne représente que 17%), il était donc très important que les scripts aient un aspect compact.

Partie 3. Script de correspondance Punch-Out


Le script du match contrôle le comportement de l'adversaire au plus haut niveau. L'opération principale qu'il exécute encore et encore attend un certain temps du tour et apporte des modifications aux données de configuration de l'adversaire à ce moment. La vidéo montre le premier tour de la première bataille contre Bald Bull, ainsi qu'un script de match qui contrôle son comportement général.


Il existe trois opérations principales qu'un script de correspondance peut effectuer. La première consiste à attendre que la minuterie ronde atteigne une certaine valeur. La seconde consiste à se demander si le comportement actuel de l'adversaire a changé. Les comportements sont enregistrés dans la table de configuration du combat en mémoire, puis appelés à différents moments par les scripts de match et le moteur de jeu lui-même. Le tableau comporte deux segments de comportement utilisés par les scripts de correspondance. Je les appelle un comportement «de base» et un comportement «spécial». Les comportements spéciaux sont, par exemple, une frappe Bull Charge d'un adversaire de Bald Bull ou Honda Rush d'un adversaire de Piston Honda, et les principaux comportements sont les coups habituels que l'adversaire délivre pour le reste du temps. Les scripts de comportement spécifiques utilisés pour implémenter ces types de comportements peuvent être modifiés par le script de match directement pendant la bataille, de sorte que les combattants peuvent commencer avec un comportement principal et passer ensuite à un autre (comme on le voit dans la vidéo, Bald Bull le fait lorsque le chronomètre atteint 0). : 20.)

Une caractéristique du changement des comportements effectués par les scripts de correspondance est qu'ils peuvent être remplacés par des changements de comportement demandés par le moteur de jeu. Le moteur de jeu utilise quatre segments comportementaux pour demander de nouveaux comportements lorsque le Mac perd tout son cœur et se fatigue, ainsi que lorsque l'adversaire se lève après un renversement. Si le script de match a répondu à la demande de changement de comportement, mais l'un de ces quatre événements du moteur de jeu se produit avant le traitement de la demande (les requêtes ne peuvent pas être traitées tant que l'adversaire n'est pas en attente), le moteur de jeu définit le comportement souhaité et la demande de script de match est rejetée. Certains combattants, comme Bald Bull, demandent plusieurs fois un comportement spécial sur une courte période. Il semble que cela ne soit nécessaire que pour réduire la probabilité que l'une de ces demandes soit abandonnée accidentellement.

La troisième opération principale du script de correspondance est l'application de correctifs de mémoire. La plupart des correctifs de mémoire affectent la table de configuration de combat, où les scripts de comportement sont enregistrés. En plus des ensembles de comportements, le tableau contient des données liées à la complexité de la bataille. Par exemple, lorsque le minuteur de la vidéo atteint 0h30, Bald Bull modifie ses paramètres de sécurité. Cela conduit au fait que le joueur ne peut plus le tromper en appuyant dessus, puis en frappant le corps. De plus, les scripts de match ont la possibilité de patcher des adresses mémoire arbitraires, mais cette fonction n'est utilisée qu'une seule fois - au début du deuxième tour avec Mike Tyson, afin que le joueur reçoive une étoile pour la première fois lorsqu'il la frappe, ce qui est en mode veille.

Partie 4. Script de comportement de perforation


Nous examinons maintenant les scripts de comportement directement impliqués dans l'implémentation des comportements.

La vidéo montre une interprétation de ce à quoi pourrait ressembler le script de comportement du rival Piston Honda 1 dans les équipes anglaises.


Commandes d'animation


Les scripts comportementaux sont responsables du lancement séquentiel des animations de la même manière que les scripts de correspondance étaient responsables du lancement des animations. La commande anim joue une animation spécifique et la commande anim_rnd exécute une animation sélectionnée au hasard dans une liste de 8 options. Dans la vidéo ci-dessus, lors d'une sélection aléatoire dans une liste d'options, l'option sélectionnée est momentanément surlignée en rouge. Lorsque Piston Honda applique ses deux premiers jabs, anim utilisé pour chacun d'eux. Après cela, il utilise anim_rnd pour sélectionner au hasard dans un ensemble contenant 6 animations de crochet et 2 animations vides. À la suite de cela, il accroche à 75% du temps et ne fait rien à 25% du temps.

Du point de vue du script, le comportement des animations est joué de manière synchrone, car lorsque le système d'animation n'est pas en mode veille, l'interpréteur de script est suspendu.

Commandes de contrôle d'exécution


Il existe plusieurs commandes qui modifient l'exécution du script de comportement lui-même. pause commandes de pause peuvent suspendre l'exécution du script pour un certain nombre d'images ou pour le nombre d'images sélectionnées au hasard dans une liste de 2 options.

Il existe différentes commandes de branchement qui, dans certaines conditions, basculent éventuellement vers différentes parties du script de comportement. La branch_rnd a une probabilité donnée qu'une branche se produise à chaque exécution. Un cas particulier de branchement probabiliste est la commande branch_always , qui a une probabilité de branchement de 100%.

Un mécanisme de boucle simple est intégré à l'interpréteur de script de comportement. La commande set_loop_count définit la valeur actuelle du compteur de boucle. Chaque fois que la commande branch_while_loop est branch_while_loop elle diminue la valeur du compteur de boucle de un et n'effectue de branchement que si la valeur du compteur est supérieure à zéro.

Le dernier type de branchement vérifie le contenu de la mémoire pour prendre une décision sur le branchement. Piston Honda utilise cette commande branch_mem_test pour vérifier si son dernier coup dans un comportement particulier a réussi. Si le coup atteint la cible, il se ramifie pour le coup suivant. Si le hit n'a pas réussi, il utilise la commande branch_while_loop pour continuer à frapper uniquement lorsque 5 hits ont échoué.

Commandes de comportement


Il existe deux commandes par lesquelles les scripts de comportement peuvent contrôler le système de comportement lui-même. La commande begin_behavior_main utilisée pour mettre fin au comportement exécutable actuel et démarrer le comportement principal. Cela diffère de la ramification dans le script de comportement, car la partie du script considérée comme le comportement «principal» actuel peut être modifiée pendant la correspondance par le script de correspondance (voir la partie précédente de l'article sur les scripts de correspondance).

Une autre commande liée au comportement est enable_behavior_change . Lorsqu'un nouveau comportement est lancé, il commence par un état de verrouillage, lorsque toutes les autres demandes de changement de comportement sont bloquées. À l'aide de la commande enable_behavior_change script signale qu'il est prêt à autoriser d'autres comportements. Par exemple, dans le comportement spécial Piston Honda, la commande enable_behavior_change jamais exécutée, donc si le Mac est fatigué pendant ce temps, le comportement spécial continue. Cependant, les événements de renversement contournent ce système, donc si pendant le comportement spécial de Piston Honda le personnage principal est renversé, le comportement sera changé dans tous les cas.

Source: https://habr.com/ru/post/fr434550/


All Articles