This page is at least 13 years old !
L'idée m'est venue pendant une de mes pauses-glandouille, pauses pendant lesquelles j'ai l'habitude de divaguer entre les vidéos débiles du net pour m'amuser et abrutir encore plus qu'elle ne l'est ma cervelle. Je tombe sur une vidéo assez populaire mais qui ne m'avais jamais été proposée, montrant un anglophone essayant sur lui un de ces colliers de dressage pour chiens.
Dans le principe, l'idée est presque la même: associer un événement désagréable a une peine désagréable. Sauf que l'événement désagréable l'est pour celui qui subira la peine ! Pour que ca reste tout de même intéressant hormis l'intérêt purement masochiste, je me suis dit qu'il fallait que ces événements dépendent d'une qualité (agilité, vitesse, mémorisation...) ou de la chance du sujet. Le meilleur choix a mon goût était donc la console de jeu vidéo ! Concrètement, cela donne des jeux vidéos capables de piloter ces colliers pour infliger directement des bourres aux joueurs lorsqu'ils perdent (ou par exemple, lorsque l'adversaire gagne, les possibilités et variantes peuvent être nombreuses !). Au début, je pensais faire ca sur NES car c'était une console qui restait encore aujourd'hui relativement populaire, elle possédait une vaste ludothèque, et restait a ma portée au niveau complexité (bien que je sois connu par certains pour être un detésteur de cette horreur qu'est le 6502, son processeur). Je me suis vite rappelle que la NES, en plus d'avoir un processeur de merde, était la console connue pour avoir une innombrable flopée de mappers, ces chips permettant de fragmenter la mémoire afin d'avoir des jeux plus ou moins gros. Comme je n'avais fait qu'une cartouche flash sur un circuit SLROM, on n'aurait pu jouer qu'aux jeux sortis sur ce même circuit, ce qui aurait d'un coup bien restreint nos choix (en plus d'avoir eu a flasher les différents jeux entre les parties, galère pour jouer chez des potes...). Le fait que ma cartouche flash n'était plus reconnue quel que soit le jeu flashé m'a définitivement convaincu de changer de plateforme. Le choix suivant "logique" pour moi était la Master System (Alex Kidd, ca vous dit quelque chose ?).
Il ne m'a pas fallu plus de temps pour me décider: ca sera la Megadrive. Tout les éléments décisifs étaient réunis: Le choix de jeux, la simplicité de larguer les roms sur une carte SD, et la disponibilité du hardware en cas d'accident (car oui, ne me dites pas qu'il n'y a pas de Megadrive a 20 Euros dans le Cash Express a cote de chez vous...). Tout d'abord j'aimerais en rassurer certains. Pour ceux qui voudraient se lancer dans l'aventure de cette modification mais qui seraient trop protecteurs pour toucher a un poil de leur Megadrive, n'ayez crainte: Pas besoin de modifier votre chère console pour vous adonner aux joies de la haute tension, on ne touchera qu'aux joypads ! (Qui pourront encore très bien fonctionner normalement). On veut pouvoir piloter des colliers en "tout-ou-rien", c'est a dire soit une décharge, soit pas de décharge. Pour cela il nous faut juste une sortie numérique du coté console. Connaissant déjà un peu la Megadrive, je savais qu'une telle sortie était disponible sur les ports joypads. Au premier abord, sans rien connaître de la console, on pourrait se dire que les ports joypads ne sont que des entrées, qui correspondent a chaque bouton. Mais comment ca se passe avec les 11 boutons des joypads alors que ce sont des ports DE9 qui n'ont que 7 broches de données (2 servant a l'alimentation) ? Les 7 broches de ces ports sont en fait configurables en tant qu'entrée ou sortie. Typiquement, les jeux en configurent 6 comme entrées, et 1 en sortie. Il y a un circuit intégré dans les joypads qui est câblé de sorte a "écouter" cette sortie pour donner l'état d'un groupe de boutons, ou de l'autre. Le jeu qui souhaite lire l'état de tous les boutons mettra donc la sortie a 0 pour lire les boutons Haut, Bas, Gauche, Droite, B et C, puis mettra la sortie a 1 pour lire les boutons Start et A.
Je pensais donc utiliser cette sortie pour véhiculer des informations supplémentaires, et rendre les joypads un peu plus intelligents pour qu'ils soient capables de comprendre ces nouvelles informations. Au départ je pensais utiliser un circuit compteur classique, qui compterait le nombre d'impulsions reçues sur SELECT et qui se remettrait a zéro lorsqu'il n'y avait pas d'impulsion pendant un certain temps (pour éviter de compter les impulsions 50/60 fois par seconde). N'ayant pas trouvé de solution simple pour cette remise a zéro sachant que le signal pouvait prendre plusieurs formes différentes selon les jeux, j'ai opté pour la simplicité en utilisant un microcontroleur ATTiny25 (Atmel). Ils sont petits, ont 5 broches d'entrées/sorties, fonctionnent en 5V, ont un oscillateur interne, et sont peu chers. Ils offrent tout ce dont on a besoin: comprendre un signal défini en entrée et activer ou non une sortie en fonction, et le tout peut simplement être modifie au niveau logiciel. Comme je me doute que beaucoup d'entre vous n'ont pas de quoi les programmer, je peux vous dépanner sur simple demande par mail.
On va le connecter a la broche 2 de notre ATTiny25. On va aussi lui apporter du courant en repiquant le 5V comme indiqué en rouge, et la masse comme indiqué en gris. En option, on peut ajouter une LED après une résistance de quelques centaines d'ohms sur la broche 7 du microcontroleur, qui pourra être utile pour savoir si le signal est bien compris sans avoir de collier pour tester. Finalement, la broche 3 servira de signal de commande pour le collier (appelé ici SIG).
On peut aussi profiter de l'occasion pour nettoyer les contacts des boutons a l'alcool, passer un coup de crayon de papier sur les membranes conductives, et mettre quelques épaisseurs de papier sous le disque de la croix directionnelle pour durcir le toucher. Pour la connection au collier, j'ai utilisé un connecteur Molex 3 voies (Alim + signal) a l'apparence peu esthétique mais très robuste et simple a installer. J'ai coupé 3mm du bord du plastique sur le bas du joypad pour placer le connecteur mâle, avec ses broches a souder pliées vers l'extérieur pour l'empêcher de bouger (voir photo). Il faut bien penser a ne pas faire passer de fils ou de broches devant les colonnes qui servent a guider les vis ou ca coincera au remontage. Sur la photo on peut voir que mon brochage du connecteur de sortie est le suivant, de gauche a droite: +5V, Masse, Signal de sortie vers le collier. Libre a vous d'en choisir un autre, l'important étant de vous en souvenir pour ne pas cramer le collier.
Le collier que j'ai acheté sur eBay se trouve a un prix étonnement bas quand on connaît ceux pratiqués par les boutiques spécialisées (surtout en VPC, important du matériel Américain). De nombreux brevets tels que celui-ci et celui la décrivent clairement comme ces colliers délivrent les décharges électriques. Un simple transformateur et un(deux) transistor(s) sont utilisés pour convertir des impulsions de basse tension provenant de la pile vers des impulsions de tension plus élevée avec un courant moindre.
Ce genre d'ampoules peut encore se trouver dans certains appareils électroménagers anciens ou bas de gamme comme indicateurs de présence d'une tension relativement élevée (centaine de volts) sans avoir recours a des résistances et des diodes. En effet, ici une LED ne survivrait sûrement pas aux impulsions de haute tension, bien que très courtes et de faible intensité. En revanche, une ampoule classique ne conviendrait pas non plus car elle s'allumerait avec une tension de l'ordre de quelques volts et serait donc un mauvais indicateur du bon fonctionnement du collier. Comme il était impossible de trouver des valeurs de tension et de courant sur eBay et dans le mode d'emploi, j'ai du effectuer quelques mesures simples moi même pour au moins savoir a quoi m'attendre (si j'allais souffrir beaucoup ou énormément).
Mon multimetre indiquait environ 1Mohm par centimètre de peau, j'ai donc bêtement pris une résistance de 5.6Mohms (valeur normalisée) pour la placer entre les électrodes du collier et mesurer la tension a ses bornes avec mon oscilloscope. Il faut savoir que la résistance de la peau varie beaucoup selon son humidité. En mouillant mes doigts et en appuyant les pointes de touche le plus possible, la valeur descendait a 800kOhms.
En prenant 500V pour viser large, on trouve 500/5,6.10e6 = 90μA, rien de dangereux en somme (ca ne veut pas dire que vous n'aurez pas mal !). On ne le voit pas sur la photo mais j'ai également observé le signal sur la base du transistor qui commute la tension de la batterie sur le primaire du transformateur, histoire de voir a quoi ressemblaient réellement les décharges. Il se trouve que le fabricant du collier a choisi de balancer des pics de 50μs espacés de 300μs par rafales de 50, ces rafales étant elles-meme espacées de 100ms et répétées 30 fois (pour une durée totale d'environ 3.5 secondes).
On va se contenter de bypasser complètement le cerveau du collier en coupant la connection entre son microcontroleur et le transistor connecte au transformateur. Sur le schéma ci-contre on peut voir qu'on utilisera qu'en fait une petite partie des composants du collier, juste de quoi fournir les impulsions de haute tension. Il faudra donc que le microcontroleur qu'on a ajoute dans les joypads produise un signal de commande similaire a celui d'origine, rien de complique a faire avec quelques boucles et temporisations. Ceci permettra même de prévoir plusieurs durées de décharges afin de punir plus ou moins sévèrement !
Les connections pour l'alimentation 5V vont se faire sur les deux grosses soudures du ressort et de la plaque positive pour la pile tandis que le fil SIG ira sur la résistance juste avant le transistor. A noter que la valeur de cette résistance peut varier selon les modèles. Vu la faible durée des impulsions de commande on peut l'omettre pour obtenir des pics de tension plus élevées si on aime le risque. Comme il est fort probable que le câble subisse des tractions pendant la séance de torture, il vaut mieux ne pas faire confiance aux soudures et faire un nœud dans le câble avant qu'il ne sorte du boîtier du collier. De cette manière, c'est le plastique du boîtier qui s'opposera a la force et non les soudures (pratique commune dans le matériel bas de gamme).
Dans un souci de résistance mécanique également, ces connecteurs sont dotes d'un clip auto-bloquant. J'ai ajoute quelques centimètres de gaine thermoretractable sur la base du connecteur, que j'ai remplie de colle chaude afin d'infliger l'effort de la traction sur le connecteur plutôt que sur les soudures des fils.
Notez aussi la LED rouge a droite du câble en haut.
Le code du microcontroleur a été écrit en C pour AVR-GCC. Voici les telechargements disponibles:
Le principe général est simple et reprend ce que j'ai explique concernant les modifications sur le joypad et le collier. Comme décrit plus haut, on veut détecter un signal que le jeu devra générer spécialement pour commander le collier et on veut ignorer tous les autres signaux (question d'éviter a tout prix les faux déclenchements, sinon c'est pas juste !). On sait qu'en temps normal, le signal SELECT reçu par le joypad change d'état 50 ou 60 fois par seconde. Il y a cependant un cas donc je n'ai pas parle, qui est celui des joypads a 6 boutons. Je ne vais pas détailler leur fonctionnement ici car ca nous importe peu pour ce qu'on veut faire, mais il faut savoir que les jeux qui supportent ces joypads peuvent provoquer jusqu'a 8 changements d'état par lecture ! Il faut également que le comptage des fronts se reinitialise a zéro après une durée d'inactivité sur SELECT, afin de ne pas incrementer le compteur a chaque lecture du joypad (qui je le rappelle, intervient 50/60 fois par seconde). Pour cela on utilisera le Timer0 et son interruption Overflow. Le Timer0 est un compteur 8bits qui s'incrementera automatiquement a intervalles courtes et régulières, lorsque sa valeur maximum sera atteinte (255), il déclenchera l'interruption Overflow et permettra de remettre le compteur de fronts a zéro. On mettra le prescaler du Timer0 a 3, qui selon la documentation Atmel indique une division de la fréquence principale par 64. Si on fait tourner notre microcontroleur a 8MHz, on peut calculer la fréquence d'incrementation du Timer0: 8.10e6/64 = 125kHz. Avec cette valeur on peut savoir combien de temps le Timer0 mettra pour atteindre sa valeur max et déclencher l'interruption: 1/125.10e3*256 = 2ms. C'est bien en dessous de 17ms et bien au delà de la durée entre les fronts qu'on va recevoir, qui avec une Megadrive tournant a 7.6MHz, feront au minimum 7,6.10e6 / 4 cycles par instruction = 530ns (soit 0.5ms) ! Parfait. On va avoir besoin de deux gestionnaires d'interruptions, d'abord pour le Pin Change: ISR(PCINT0_vect) { Et un second pour l'Overflow, ou on mettra le code servant a générer le signal de commande du collier. Pour avoir deux décharges de durées différentes, j'ai choisi de faire la différence entre deux nombres de fronts: entre 10 et 15 fronts pour une courte décharge, et 16 fronts ou plus pour une longue décharge. ISR(TIMER0_OVF_vect) { uint8_t pulse,zap; if (edge >= 16) { // Si nombre de fronts >= 16: gros zap PORTB = _BV(LED); for (pulse=0;pulse<20;pulse++) { // 20 rafales de 10 pics for (zap=0;zap<10;zap++) { PORTB = _BV(SIG) | _BV(LED); // SIG a 1 _delay_us(300); PORTB = _BV(LED); // SIG a 0 _delay_ms(4); } _delay_ms(50); } PORTB = 0; } else if ((edge < 16) && (edge > 10)) { // Si nombre de fronts > 10 et < 16: petit zap PORTB = _BV(LED); for (pulse=0;pulse<2;pulse++) { // 2 rafales de 10 pics for (zap=0;zap<10;zap++) { PORTB = _BV(SIG) | _BV(LED); // SIG a 1 _delay_us(300); PORTB = _BV(LED); // SIG a 0 _delay_ms(4); } _delay_ms(50); } PORTB = 0; } edge = 0; // Remet le compteur de fronts a zero } En basant la validation du signal sur l'Overflow du Timer0, on sait implicitement que la réaction au signal sera en retard de 2ms après la réception du dernier front, ce qui est complètement négligeable dans notre cas. Le reste du code n'est que le main() avec les initialisations nécessaires et un clignotement de la LED pour dire que tout se passe bien. PORTB = 0b00000000; // Etre sur que toutes les sorties seront a zero DDRB = _BV(SIG) | _BV(LED); // SIG et LED comme sorties TCCR0A = 0b00000000; // Timer0 mode normal
Vous conviendrez que de devoir lancer un jeu et perdre volontairement pour tester le bon fonctionnement du collier serait assez fastidieux, on va donc créer un petit ROM de debug de toute pièce. Premiere partie en assembleur 68000. Même en assembleur c'est bouclé en 350 lignes de code, et cela permet de tester les deux sortes de décharges sur chacun des colliers très facilement grâce a un petit menu. Rien de particulier dans le code, le strict minimum pour afficher du texte: nettoyage de la RAM, de la VRAM, chargement de l'alphabet, de la palette, une routine pour écrire des null-terminated strings, et les routines magiques qui servent a donner des ordres au microcontroleur fraîchement installé dans les joypads. Une telle routine ressemble a ceci (exemple pour une courte décharge pour le joueur 1): zap1s: move.b #$40,$A10009 ; Configurer SELECT port 1 comme sortie move.l #6-1,d0 ; On veut 6 transition (12 fronts) .z1s: move.b #$40,$A10003 ; SELECT a 5V nop move.b #$00,$A10003 ; SELECT a 0V dbra d0,.z1s ; Boucler rts Pour ceux qui programment un peu, les commentaires devraient parler d'eux mêmes mais voici quand même le déroulement détaillé pour les allergiques a l'assembleur:
Pour le second joueur, on utilisera simplement le même code mais cette fois avec les registres $A1000B et $A10005 au lieu de $A10009 et $A10003. C'est exactement les mêmes routines qu'on utilisera pour patcher les jeux afin qu'ils puissent commander les colliers.
C'est la partie qui risque d'en faire reculer plus d'un, car c'est la qu'on va devoir attaquer de l'assembleur 68000 qu'on a pas écrit nous même. Le principe du patch ici est d'introduire du code a des endroits bien précis dans les jeux pour envoyer nos commandes aux colliers. Il faut donc d'abord trouver par ou le code passe lorsque l'événement qui nous intéresse se produit (perte de vie, prise de coup...), introduire un saut vers un espace vide du ROM, remplacer ce vide par notre code, et retourner au code du jeu. Il faut bien entendu que l'exécution de notre code ne perturbe pas le code du jeu. Comme ceux qui ne connaissent pas du tout la programmation ne vont certainement pas passer par cette case et utiliseront simplement mes patchs prets-a-jouer, je ne vais pas détailler comment coder en assembleur 68000 mais seulement comment s'y prendre dans le cas de Sonic, les autres jeux seront abordes plus rapidement. Ceux qui connaissent devraient s'y retrouver sans trop de peine. Voici donc avant tout, les patchs Ninja tout prêts que j'ai réalises jusqu'a présent:
Bien sur si vous avez réalisé la modification des colliers et des joypads mais qu'aucun de ces jeux vous tente, n'hésitez pas a m'écrire pour me demander de rendre compatible votre jeu favori, je verrais ce que je pourrais faire ;) Pour appliquer ces patches il vous faut le ROM original (que je vous laisse trouver sur le net, je ne les distribue pas), et l'utilitaire Ouinja qui apportera mes modifications au ROM. Afin d'éviter toute corruption et de crash, le patch ne s'appliquera pas si le ROM n'est pas exactement le même que j'ai utilise (verif CRC). Dans cette partie, j'ai utilise IDA pour désassembler les jeux, l'emulateur Fusion pour trouver des valeurs en RAM, MESS pour la recherche plus approfondie et le debug, et finalement ASW encore pour assembler le code. On peut avoir recours a différentes techniques pour trouver ou patcher les jeux:
Petit rappel ou résumé pour utiliser le debugger de MESS: Pour poser un breakpoint il suffit d'écrire bpset adresse, pour le virer, on met bpclear numéro. Pour les watchpoints on écrit wpset adresse,r/w,taille, pour les virer, wpclear numéro.
Sonic a été l'un des jeux les plus simples a patcher. Tous les événements que je voulais hooker provoquaient des changements de valeurs qui étaient affichées a l'écran (nombre d'anneaux et de vies). Le seul petit souci a été la verif de checksum, bloquant le jeu si le ROM était modifie. Rien de bien complique a faire sauter cependant. Petite décharge quand on perd tous ses anneaux (hit):
![]()
Test: Écran rouge au démarrage, donc verif checksum quelque part.
Grosse décharge quand on meurt:
Battletoads a été un peu plus compliqué car on voulait recevoir une petite décharge quand on perdait de l'énergie (une barre de vie avec 6 cases), pas de valeur numérique donc il a fallu ouvrir un peu plus les yeux. J'ai commencé par faire le plus facile: hooker la perte de vie. Grosse décharge quand on perd une vie:
Test, le jeu se lance, pas de checksum visiblement (ou beaucoup de chance !). Petite décharge quand on perd de l'énergie (plusieurs hits).
Bomberman a été encore un peu plus complique car on avait pas de valeur en RAM a trouver, seulement des événements: exploser, prendre une maladie, et exploser son kangourou. En espérant que cela me faciliterait la tache, je suis d'abord allé voir si il y avait des codes GameGenie pour ce jeu. Il y a un "master code", c'est un code qu'on doit obligatoirement mettre avec ceux qu'on veut utiliser sinon le jeu ne fonctionne pas, c'est typiquement un code qui patch la verif de checksum, on va donc d'abord s'occuper de ca.
Le jeu se lance sans problème, le checksum ne nous dérangera plus.
![]() Deuxième étape, donner une grosse décharge quand on meurt. Ici aussi pas de chance, les codes GameGenie pour l'invincibilité étaient incorrects (du moins, ceux que j'ai trouves), ils patchaient des données graphiques... C'est reparti pour trouver avec Fusion.
|