Arcade Reverse Engineering: Record Michael Jordan au NBA Jam


L'été dernier, j'ai été invité à une fête à Sunnyvale. Il s'est avéré que les propriétaires du garage avaient une machine d'arcade NBA JAM Tournament Edition pour quatre joueurs. Malgré le fait que le jeu a déjà plus de 25 ans (il est sorti en 1993), il est toujours très intéressant de le jouer, surtout pour les fans enthousiastes.

J'ai été surpris par la liste de joueurs des Chicago Bulls qui n'incluait pas Michael Jordan. Selon des sources [1] , MJ a reçu sa propre licence et ne faisait pas partie de l'accord que Midway avait conclu avec la NBA.

Après avoir demandé au propriétaire de la machine, j'ai appris que les pirates ont sorti un mod pour SNES "NBA Jam 2K17", qui permet aux nouveaux joueurs et MJ de jouer, mais personne n'analysait le fonctionnement de la version arcade. Par conséquent, je devais définitivement regarder à l'intérieur.

Contexte


L'histoire du Jam NBA ne commence pas avec le basket-ball, mais avec Jean-Claude Van Damme. À peu près au même moment où Universal Soldier est sorti, Midway Games a développé une technologie pour manipuler de grands sprites photoréalistes numérisés qui gardent une ressemblance avec de vrais acteurs. Ce fut une énorme percée technologique: des animations avec 60 images par seconde, des sprites inédits de 100x100 pixels, chacun ayant sa propre palette de 256 couleurs.

La société a utilisé cette technologie avec grand succès dans le jeu de tir populaire "Terminator 2: Judgment Day" [2] , mais n'a pas pu acquérir une licence pour "Universal Soldier" (les conditions financières de JCVD ​​étaient inacceptables pour Midway [3] ). Lorsque les négociations se sont soldées par un échec, Midway a changé de cap et a commencé à développer un jeu de combat méga-hit Capcom de 1991 appelé Street Fighter II: The World Warrior.

Une équipe de quatre personnes a été constituée (Ed Boone a écrit le code, John Tobias était impliqué dans l'art et les scripts, John Vogel a dessiné des graphiques et Dan Forden était un ingénieur du son). Après un an de dur labeur [4] Midway a lancé le Mortal Kombat en 1992.

Le style visuel était très différent du pixel art habituel, et la conception du jeu était, pour le moins, "controversée". Le jeu avec des litres de sang à l'écran et des poussées incroyablement cruelles - la «fatalité» est instantanément devenu un succès mondial et a rapporté près d'un milliard de dollars en un an [5] .


SF2: 384 × 224 avec 4 096 couleurs.


MK: 400 × 254 avec 32 768 couleurs.

Fait intéressant: comme en mode VGA 0x13 sur PC, dans ces jeux, les pixels n'étaient pas carrés. Bien que le tampon de trame Mortal Kombat ait une taille de 400 × 254, il est étiré à un rapport 4: 3 d'un écran CRT, offrant une résolution de 400 × 300 [6]

Équipement Midway T-Unit


Le matériel développé par Midway pour Mortal Kombat s'est avéré très bon. Tellement bon qu'il a reçu son propre nom T-Unit et réutilisé dans d'autres jeux.

  • Mortal Kombat.
  • Mortal Kombat II.
  • NBA Jam.
  • NBA Jam Tournament Edition.
  • Le juge Dredd (n'a pas été libéré).

T-Unit se compose de deux cartes. La plupart d'entre eux traitent de la logique et des graphismes du jeu.


Carte processeur NBA JAM TE Edition (environ 40x40 cm ou 15 pouces).


L'autre planche est moins compliquée, mais aussi capable de beaucoup. Il est conçu pour l'audio, mais il peut jouer non seulement de la musique en utilisant la synthèse FM, mais aussi du son numérique.

La carte son est connectée à une source d'alimentation et à une carte graphique installée à l'arrière. Faites attention à l'énorme radiateur situé dans le coin supérieur gauche.

Ensemble, ces deux cartes contiennent plus de deux cents puces, résistances et EPROM. Comprendre tout cela uniquement sur la base de numéros de série prendrait beaucoup de temps. Mais, de façon surprenante, parfois dans des appareils des années 90, la documentation est découverte accidentellement. Et dans le cas de NBA Jam, elle était tout simplement géniale.

Architecture de l'unité T à mi-chemin


À la recherche de données, je suis tombé sur un kit de confiture NBA. Le niveau de détail de ce document est étonnant [7] . Entre autres choses, j'ai réussi à trouver une description détaillée des connexions de câblage, y compris les EPROM et les puces.


Les informations du document nous ont permis de dessiner un schéma des planches et de déterminer la fonction de chaque pièce. Pour aider à la recherche de composants, la carte a des coordonnées avec un début dans le coin inférieur droit (UA0), augmentant jusqu'au coin supérieur gauche (UJ26).


Le cœur de la carte principale est le Texas Instrument TMS34010 (UB21) avec une fréquence de 50 MHz et avec un code de 1 mégaoctet dans les EPROM et une DRAM de 512 kibio [8] . 34010 est une puce 32 bits avec un bus 16 bits, qui a des instructions graphiques remarquables comme PIXT et PIXBLT [9] . Au début des années 90, cette puce était utilisée dans plusieurs cartes d'accélération matérielle [10] , et je pensais qu'elle gère une quantité considérable d'effets graphiques. Étonnamment, il ne traite que de la logique du jeu et ne dessine rien.

En fait, la puce UE13 appelée «DMA2» s'est avérée être un monstre graphique. Selon les schémas de la documentation, il possède un impressionnant (à l'époque) bus de données 32 bits et bus d'adresse 32 bits, c'est pourquoi il est devenu la plus grande puce de la carte. Ce circuit intégré spécialisé (ASIC) est capable de nombreuses opérations graphiques, dont je parlerai ci-dessous.

Toutes les puces (RAM système, EPROM GFX, SDRAM de palette, code, banques vidéo) sont mappées sur un espace d'adressage 32 bits et connectées au même bus. Je n'ai pas pu trouver d'informations sur le protocole de bus, donc si vous en savez quelque chose, écrivez à l'e-mail.

Faites attention à une astuce: un composant EPROM (marqué en bleu) est utilisé pour créer un autre système de stockage (et économiser de l'argent). Ces EPROM de 512 kb ont des broches d'adresse 32 bits et des broches de données 8 bits. Pour le 34010, qui nécessite un bus de données 16 bits, deux EPROM (J12 et G12) sont connectées avec une double alternance d'adresses, créant une mémoire de 1 mégaoctet. De même, les ressources graphiques sont connectées avec une alternance quadruple d'adresses pour former une adresse 32 bits avec un système de stockage 32 bits contenant 8 mégaoctets.

Bien que dans cet article, je considère principalement le pipeline graphique, je ne résiste pas à la tentation, et je vais donc brièvement parler du système audio.


Le schéma de la carte son montre le Motorola 6809 (U4 avec une fréquence de 2 MHz), qui reçoit des instructions d'une EPROM (U3) pour contrôler la musique et les effets sonores.

La puce de synthèse FM 2151 (3,5 MHz) de Yamaha génère de la musique directement à partir des instructions reçues du 6809 (la musique utilise une bande passante assez petite).

OKI6295 (1 MHz) est responsable de la lecture audio numérique au format ADPCM (par exemple, le légendaire «Boomshakalaka» [11] Tim Kittsrow).

Notez que sur la carte principale, les mêmes EPROM bleu 512 Ko 32a / 8d sont utilisées dans un système 16 bits avec double entrelacement d'adresses pour stocker des voix numérisées, mais pour les instructions 8 bits, les données / adresses de Motorola 6809 ne sont pas entrelacées.

Durée de vie du cadre


L'écran NBA Jam entier est indexé dans une palette 16 bits. Les couleurs sont stockées au format xRGB 1555 dans une palette de 64 Ko. La palette est divisée en 128 blocs (256 * 16 bits) de 512 octets. Les sprites stockés dans l'EPROM sont marqués comme «GFX». Chaque sprite a sa propre palette de couleurs jusqu'à 256x16 bits. Un sprite utilise souvent un bloc de palette entier, mais jamais plus d'un. Un signal CRT est transmis au moniteur à l'aide de RAMDAC, qui pour chaque pixel lit l'index des banques de DRAM vidéo et effectue une recherche de couleur dans la palette.

La durée de vie de chaque image d'une vidéo NBA Jam se déroule comme suit:

  1. La logique de jeu consiste en un flux d'instructions 16 bits transmises de J12 / G12 à 34010.
  2. 34010 lit l'entrée du joueur, calcule l'état du jeu, puis dessine un écran.
  3. Pour dessiner sur l'écran, 34010 trouve d'abord un bloc inutilisé dans la palette et y écrit la palette de sprites (les palettes de sprites sont stockées avec les instructions 34010 dans J12 / G12).
  4. 34010 fait une demande à DMA2, qui comprend l'adresse et la taille du sprite, le bloc de palette 8 bits utilisé, la troncature, la mise à l'échelle, la méthode de traitement des pixels transparents, etc.
  5. DMA2 lit les index de sprites 8 bits de la puce ROM J14-G23 GFX, combine cette valeur avec l'index d'un bloc de palette 8 bits et écrit un index 16 bits dans les banques vidéo. La DRAM2 peut être considérée comme un blitter qui lit les valeurs 8 bits de l'EPROM GFX et écrit les valeurs 16 bits dans les banques vidéo
  6. Les étapes 3 à 5 sont répétées jusqu'à ce que toutes les demandes de dessin de sprites soient terminées.
  7. Lorsqu'il s'agit de rafraîchir l'écran, RAMDAC convertit les données des banques vidéo en un signal qu'un moniteur CRT peut comprendre. Pour que la bande passante soit suffisante pour convertir l'index 16 bits en RVB 16 bits, la palette est stockée dans une SRAM extrêmement coûteuse et extrêmement rapide.


Un fait intéressant: le firmware flash EPROM n'est pas un processus aussi simple. Avant d'écrire sur la puce, vous devez effacer complètement tout son contenu.

Pour ce faire, la puce doit être irradiée avec une lumière UV. Vous devez d'abord décoller l'autocollant du haut de l'EPROM pour ouvrir son diagramme. L'EPROM est ensuite placée dans un dispositif de gomme spécial dans lequel se trouve une lampe UV.

Après 20 minutes, l'EPROM sera remplie de zéros et prête pour l'enregistrement.

Documentation MAME


Après avoir compris l'équipement, j'ai réalisé quel ensemble d'EPROM vous pouviez écrire à Michael Jordan (la palette est stockée dans les EPROM de code et les indices dans les EPROM de GFX). Cependant, je ne connaissais toujours pas l'emplacement exact ni le format utilisé.

Documentation manquante trouvée dans MAME.

Dans le cas où vous ne savez pas comment fonctionne cet émulateur incroyable, je vais vous expliquer brièvement. MAME est basé sur le concept de "drivers", qui sont une imitation de la planche. Chaque pilote est composé de composants qui imitent (généralement) chaque puce. Dans le cas de Midway T-Unit, nous sommes intéressés par les fichiers suivants:

  mame / includes / midtunit.h
 mame / src / mame / video / midtunit.cpp
 mame / src / mame / drivers / midtunit.cpp
 mame / src / mame / machine / midtunit.cpp
 cpu / tms34010 / tms34010.h 

Si vous regardez drivers / midtunit.cpp, nous verrons que chaque puce mémoire fait partie d'un seul espace d'adressage 32 bits. Il peut être vu à partir du code source du pilote que la palette commence à 0x01800000, gfxrom commence à 0x02000000 et la puce DMA2 commence à 0x01a80000. Pour suivre le chemin des données, nous devons suivre les fonctions C ++ exécutées lorsque l'objet de l'opération de lecture ou d'écriture est l'adresse mémoire.

void midtunit_state::main_map(address_map &map) { map.unmap_value_high(); map(0x00000000, 0x003fffff).rw(m_video, FUNC(midtunit_vram_r), FUNC(midtunit_vram_w)); map(0x01000000, 0x013fffff).ram(); map(0x01400000, 0x0141ffff).rw(FUNC(midtunit_cmos_r), FUNC(midtunit_cmos_w)).share("nvram"); map(0x01480000, 0x014fffff).w(FUNC(midtunit_cmos_enable_w)); map(0x01600000, 0x0160000f).portr("IN0"); map(0x01600010, 0x0160001f).portr("IN1"); map(0x01600020, 0x0160002f).portr("IN2"); map(0x01600030, 0x0160003f).portr("DSW"); map(0x01800000, 0x0187ffff).ram().w(m_palette, FUNC(write16)).share("palette"); map(0x01a80000, 0x01a800ff).rw(m_video, FUNC(midtunit_dma_r), FUNC(midtunit_dma_w)); map(0x01b00000, 0x01b0001f).w(m_video, FUNC(midtunit_control_w)); map(0x01d00000, 0x01d0001f).r(FUNC(midtunit_sound_state_r)); map(0x01d01020, 0x01d0103f).rw(FUNC(midtunit_sound_r), FUNC(midtunit_sound_w)); map(0x01d81060, 0x01d8107f).w("watchdog", FUNC(watchdog_timer_device::reset16_w)); map(0x01f00000, 0x01f0001f).w(m_video, FUNC(midtunit_control_w)); map(0x02000000, 0x07ffffff).r(m_video, FUNC(midtunit_gfxrom_r)).share("gfxrom"); map(0x1f800000, 0x1fffffff).rom().region("maincpu", 0); /* mirror used by MK*/ map(0xff800000, 0xffffffff).rom().region("maincpu", 0); } 

À la fin du même fichier «drivers / midtunit.cpp», nous voyons comment le contenu des EPROM est chargé dans la RAM. Dans le cas des ressources graphiques «gfxrom» (associées à l'adresse 0x02000000), nous pouvons voir qu'elles s'étendent sur 8 mégaoctets d'espace d'adressage dans les blocs de puces avec une alternance quadruple d'adresses. Notez que les noms de fichiers correspondent à l'emplacement des puces (par exemple, UJ12 / UG12). L'ensemble de ces fichiers EPROM dans le monde des émulateurs est mieux connu sous le nom de «ROM».

 ROM_START( nbajamte ) ROM_REGION( 0x50000, "adpcm:cpu", 0 ) /* sound CPU*/ ROM_LOAD( "l1_nba_jam_tournament_u3_sound_rom.u3", 0x010000, 0x20000, NO_DUMP) ROM_RELOAD( 0x030000, 0x20000 ) ROM_REGION( 0x100000, "adpcm:oki", 0 ) /* ADPCM*/ ROM_LOAD( "l1_nba_jam_tournament_u12_sound_rom.u12", 0x000000, 0x80000, NO_DUMP) ROM_LOAD( "l1_nba_jam_tournament_u13_sound_rom.u13", 0x080000, 0x80000, NO_DUMP) ROM_REGION16_LE( 0x100000, "maincpu", 0 ) /* 34010 code*/ ROM_LOAD16_BYTE( "l4_nba_jam_tournament_game_rom_uj12.uj12", 0x00000, 0x80000, NO_DUMP) ROM_LOAD16_BYTE( "l4_nba_jam_tournament_game_rom_ug12.ug12", 0x00001, 0x80000, NO_DUMP) ROM_REGION( 0xc00000, "gfxrom", 0 ) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_ug14.ug14", 0x000000, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_uj14.uj14", 0x000001, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_ug19.ug19", 0x000002, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_uj19.uj19", 0x000003, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_ug16.ug16", 0x200000, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_uj16.uj16", 0x200001, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_ug20.ug20", 0x200002, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_uj20.uj20", 0x200003, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_ug17.ug17", 0x400000, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_uj17.uj17", 0x400001, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_ug22.ug22", 0x400002, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_uj22.uj22", 0x400003, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_ug18.ug18", 0x600000, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_uj18.uj18", 0x600001, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_ug23.ug23", 0x600002, 0x80000, NO_DUMP) ROM_LOAD32_BYTE( "l1_nba_jam_tournament_game_rom_uj23.uj23", 0x600003, 0x80000, NO_DUMP) ROM_END 

Un fait intéressant: dans l'exemple de code ci-dessus, le dernier paramètre de la fonction a été remplacé par «NO_DUMP» afin que les EPROM modifiées puissent être chargées. Ces champs sont généralement [12] un hachage CRC / SHA1 du contenu de l'EPROM. C'est ainsi que MAME détermine quel jeu appartient à la ROM et vous permet de savoir que l'une des ROM de l'ensemble est manquante ou endommagée.

Moteur vidéo Heart: DMA2


La clé pour comprendre le format graphique est la fonction qui traite l'écriture / lecture DMA dans 256 registres DMA2 situés aux adresses de 0x01a80000 à 0x01a800ff. Tout le travail acharné de la rétro-ingénierie a déjà été effectué par les développeurs de MAME. Ils ont même pris le temps de bien documenter le format de commande.

  Registres DMA
  ------------------

   S'inscrire |  Bit |  Candidature
  ---------- + - FEDCBA9876543210 - + ------------
      0 |  xxxxxxxx -------- |  pixels supprimés au début de chaque ligne
            |  -------- xxxxxxxx |  pixels supprimés à la fin de chaque ligne
      1 |  x --------------- |  activer l'enregistrement (ou effacer si zéro)
            |  -421 ------------ |  images bpp (0 = 8)
            |  ---- 84 ---------- |  passer la taille après = (1 << x)
            |  ------ 21 -------- |  passer la taille jusqu'à = (1 << x)
            |  -------- 8 ------- |  activer le saut avant / après
            |  --------- 4 ------ |  activer la troncature
            |  ---------- 2 ----- |  y miroir
            |  ----------- 1 ---- |  x mise en miroir
            |  ------------ 8 --- |  transférer des pixels non nuls sous forme de couleurs
            |  ------------- 4-- |  transmission de zéro pixel sous forme de couleurs
            |  -------------- 2- |  transmission de pixels non nulle
            |  --------------- 1 |  transmission zéro pixel
      2 |  xxxxxxxxxxxxxxxx |  adresse source mot bas
      3 |  xxxxxxxxxxxxxxxx |  adresse source de mot élevée
      4 |  ------- xxxxxxxxx |  x destinataire
      5 |  ------- xxxxxxxxx |  destinataire
      6 |  ------ xxxxxxxxxx |  colonnes d'images
      7 |  ------ xxxxxxxxxx |  lignes d'image
      8 |  xxxxxxxxxxxxxxxx |  palette
      9 |  xxxxxxxxxxxxxxxx |  couleur
     10 |  --- xxxxxxxxxxxxx |  échelle x
     11 |  --- xxxxxxxxxxxxx |  échelle y
     12 |  ------- xxxxxxxxx |  coupe haut / gauche
     13 |  ------- xxxxxxxxx |  coupe en bas / à droite
     14 |  ---------------- |  le test
     15 |  xxxxxxxx -------- |  octet de détection zéro
            |  -------- 8 ------- |  page supplémentaire
            |  --------- 4 ------ |  taille du destinataire
            |  ---------- 2 ----- |  sélection du bord supérieur / inférieur ou gauche / droit pour le registre 12/13 

Il existe même une fonction de débogage qui vous permet d'enregistrer les sprites d'origine dans le processus de transfert vers DMA2 (la fonction a été écrite par un participant de longue date du projet MAME, Ryan Holtz [13] ). Il me suffisait de simplement jouer au jeu pour que tous les fichiers contenant des métadonnées soient enregistrés sur le disque.

Il s'est avéré que les sprites sont constitués d'éléments simples d'une palette 16 bits sans compression. Cependant, tous les sprites n'ont pas le même nombre de couleurs. Certains sprites n'utilisent que 16 couleurs avec des indices de couleur 4 bits, tandis que d'autres utilisent 256 couleurs et nécessitent des indices de couleur 8 bits.

Patch


Maintenant, je connais l'emplacement et le format des sprites, il reste donc à effectuer le minimum d'ingénierie inverse. J'ai écrit un petit programme sur Golang pour éliminer l'alternance des «code» et «gfx» des EPROM. En éliminant l'entrelacement, il est facile de rechercher des valeurs ASCII ou connues, car j'ai travaillé exactement avec ce à quoi ressemble la RAM pendant l'exécution du programme.

Après cela, vous pouvez facilement trouver les caractéristiques du lecteur. Il s'est avéré qu'ils étaient tous stockés les uns après les autres au format big-endian 16 bits non signé (ce qui est très logique, car le 34010 fonctionne avec le big-endian). J'ai ajouté un patcher pour modifier les attributs du joueur. Pas vraiment passionné de basket-ball, j'ai entré SPEED = 9, 3 PTS = 9, DUNKS = 9, PASS = 9, POWER = 9, STEAL = 9, BLOCK = 9 et CLTCH = 9.

J'ai également écrit le code pour patcher le jeu avec de nouveaux sprites avec la seule restriction - les nouveaux sprites devraient avoir les mêmes tailles que les remplaçables. Pour la photo MJ, j'ai créé un PNG indexé de 256 couleurs (vous pouvez le voir ici ).

Enfin, j'ai ajouté du code pour convertir le format intermédiaire au format entrelacé pour l'écriture dans des fichiers EPROM individuels.

Démarrez le jeu



Après avoir corrigé le contenu de l'EPROM, l'outil de diagnostic NBAJam a montré que le contenu de certaines puces est marqué comme «BAD». Je m'y attendais car je n'ai corrigé que le contenu des EPROM, mais je n'ai pas pris la peine de rechercher le format CRC et même leur emplacement de stockage.

Les EPROM GFX sont marquées en rouge (UG16 / UJ16, UG17 / UJ17, UG18 / UJ18, UG20 / UJ20, UG22 / UJ22 et UG23 / UJ23), car elles contiennent des images que j'ai modifiées. Les deux EPROM dans lesquelles les instructions (UG12 et UJ12) sont stockées sont également rouges, car il existe des palettes.

Heureusement, ici, les CRC ne sont pas utilisés pour protéger contre le contenu modifié et sont uniquement nécessaires pour vérifier l'intégrité des puces. Le jeu a commencé. Et gagné!


Hasta La Vista, bébé!



Ayant fini avec des difficultés techniques, j'ai rapidement perdu tout intérêt pour l'outil et arrêté de le développer. Idées pour ceux qui veulent jouer avec le code:

  • Ajoutez à la Conférence Est des Raptors de Toronto.
  • Ajoutez la possibilité de changer les noms des joueurs. Malheureusement, ils ne sont pas constitués d'ASCII, mais sont des images pré-générées.

Livre sur NBA Jam


Si vous êtes un fan de la NBA Jam, alors Reyan Ali a écrit un livre entier à son sujet [14] . Vous pouvez l'acheter ici .

Code source


Si vous souhaitez contribuer ou simplement voir comment tout fonctionne, la source complète est téléchargée sur github ici .

Les références


[1] Source: «NJA Jam» par Reyan Ali

[2] Source: «NJA Jam» par Reyan Ali

[3] Source: «NJA Jam» par Reyan Ali

[4] Source: Mortal Kombat 1 dans les coulisses

[5] Source: «NJA Jam» par Reyan Ali

[6] Source: 4: 3 contre pixels carrés

[7] Commentaire: Malheureusement, l'ère de cette excellente documentation est révolue depuis longtemps

[8] Source: écran de démarrage de Mame NBA Jam

[9] Source: jeu d'instructions TMS34010

[10] Source: T34010 Guide de l'utilisateur

[11] Source: NBA Jam - vidéo BoomShakaLaka

[12] Source: MAME T-Unit driver.cpp

[13] Source: Commit 'midtunit.cpp: Ajout d'une visionneuse DMA-blitter optionnelle'

[14] Source: «NBA JAM Book» par Reyan Ali

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


All Articles