Pour l'édition de septembre 2018 du RetroChallenge,
je compte m'intéresser de très près à un modem (et ce qui s'y rapporte) pour Super Famicom: Le NDM24.
NDM24
NDK10
Avec ce modem raccordé à un port de manette ainsi qu'une manette inhabituelle, la NDK10, dont j'ai déjà
documenté le protocole, la cartouche JRA Pat aurait
rendu possible de placer des (vraies!) mises sur des courses de chevaux.
JRA PAT
Par essais et erreur, j'ai déjà réussi à modifier un émulateur pour faire croire au jeu qu'un modem
était bien branché. Mais il ne s'agissait que d'un test simple au démarrage, sans échange de données.
J'ai donc atteint l'écran de configuration, mais par la suite le jeu plantait. (avec un message "sauvegarde
en cours)
Plantage à l'écran de configuration
Incapable de satisfaire ma curiosité, je n'ai pas su résister. J'ai acheté un modem NDM24 ainsi qu'un
exemplaire de JRA PAT afin de pouvoir analyser le tout en action.
Mes buts sont:
Découvrir et documenter comment la communication entre la console et le modem fonctionne.
En apprendre davantage sur le modem. Quelle est sa vitesse? Communique-t-il avec des commandes AT normales?
Tenter de faire fonctionner le jeu en émulation (communication avec le modem, et commandes modem)
Prétendre que l'appel téléphonique a réussi pour voir ce qui arrive ensuite.
Tenter de découvrir comment fonctionnaient les échanges entre le serveur et le jeu.
En d'autres termes: Je vais passer plusieurs heures à décortiquer le fonctionnement d'un appareil rare
dont presque personne n'a entendu parler et auquel presque personne ne s'intéresse. Et ce sera pour moi un plaisir!
Examinons le « jeu »
J'ai installé la manette (port 1) et le modem (port 2) sur ma console Super Nintendo. Avec le modem sous
tension, le jeu n'affiche pas d'erreur et atteint l'écran titre.
Le jeu JRA PAT dans le mauvais pays, sur le mauvais modlèle de console, et dans le mauvais siècle.
学生・生徒・未成年者は勝馬投票券を購入することはできません
L'achat de tickets de pari est interdit aux étudiants et personnes mineures.
Je me demandais ce que pouvais bien vouloir dire JRA PAT, l'écran titre le clarifie. JRA PAT = Japan Racing Association Personal Access Terminal
Appuyer sur le bouton A mène à un autre écran dans lequel il faut saisir son numéro d'usager et son code d'accès. Les touches
numériques de la manette facilitent grandement cette opération.
No. d'usager et code d'accès
加入者番号とパスワードを入力して下さい。
Veuillez saisir votre numéro d'abonné et votre mot de passe.
Le jeu ne semble pas valider les données saisies. La validation doit se faire plus tard, par
exemple, lors du branchement au serveur.
Passé cette étape, le jeu indique la date du dernier téléchargement d'informations et offre
de se brancher au serveur pour télécharger les infos en date du jour. (pas d'image).
Répondre non (いいえ) permet d'arriver au menu principal:
Menu principal
Menu
投票: Mises
投票履歴: Vos mises passées
照会: Demandes (?) 情報取得: Obtenir des informations
情報表示: Afficher des informations
お知らせ: Annonces
投票要項取得: Obtenir des points importants sur les mises (?)
終了: Sortir
L'option 情報取得 (Obtenir des informations) mène à un autre menu qui permet de bâtir une
liste d'informations à obtenir:
Menu d'obtention d'informations
Obtenir des informations
開催案内: Guide/informations sur les événements (courses?)
変更情報: Informations sur les changements
オッズ: Probabilités
出馬表: Tableau des participants (?)
馬体重: Poids des chevaux (?)
払戻情報: À propos des remboursements
競求項目確認: Confirmation de mise
(Attention: Je n'ai pas beaucoup confiance en mes traductions. Je n'ai pas
pris la peine de me familiariser avec le domaine des courses de chevaux,
son vocabulaire et les variables qui intéressent les gens qui misent sur des courses. Bref,
je n'ai aucune idée de quoi je parle...)
J'ai sélectionné « 開催案内: Guide/informations sur les événements », ce qui l'a ajouté à
la liste d'informations à acquérir. Le jeu m'a alors demandé si je voulais ajouter
d'autres éléments à la liste, ou aller de l'avant pour obtenir les infos. J'ai opté
pour cette dernière option, et répondu oui (はい) pour confirmer le lancement
de la communication:
Prêt à se connecter
センタへ接続しますか?
Contacter le centre de service?
L'indicateur DEL « OH » du modem (pour "Off Hook", qui indique que le modem à « décroché » la ligne) s'est immédiatement
allumé:
Évidemment, l'appel n'a pas fonctionné. Je n'ai pas branché de ligne téléphonique au modem, et même si j'en avais eu une,
il cela n'aurait sans doute rien donné. Ce service n'existe surement plus!
Impossible de communiquer avec la centrale
センタと接続できません。
再度通信
中止
通信設定の変更
コード390040019999
電話回線を確認して下さい。
Le branchement avec la centrale a échoué.
Réessager
Abandonner
Modifier les paramètres d'appel
Code 390040019999
Veuillez vérifier la ligne téléphonnique.
Afin de permettre au jeu d'établir une connexion, j'ai relié le modem pour Super Famicom (NDM24) directement à un
modem US Robotics (USR) Sporster 28800 interne.
Côté NDM24
Côté USR
Par chance, le NDM24 ne semble pas se soucier de l'absence de tonalité.
Je n'ai donc pas eu à injecter une onde pour qu'il procède avec la composition. Comme avec un tel raccord il n'y a pas de signal
de sonnerie, j'ai toutefois eu à taper ATA du côté USR une fois la composition DTMF terminée.
Aussitôt la voie nostalgique des modems qui s'accordent s'est faite entendre. Le silence revenu, l'écran
du jeu a indiqué que la connexion était réussie avec cet écran agrémenté d'une animation de cheval au pas de course.
Connexion réussie!
只今センタと通信中です。恐れ入りますが、そのままでしばらくお待ち下さい。
Présentement en communication avec le centre de service. Nous vous demandons
de bien vouloir patienter quelques instants.
Oui d'accord, je vais fixer le cheval en attendant.
Du côté USR, les mots CONNECT 9600 indiquaient une connexion cadencée à 9600 Baud. À chaque tentative (il y en a deux ci-dessous)
le jeu a transmit quelques données pour, quelques secondes plus tard, mettre fin à l'appel. Le jeu affichait alors un message d'erreur comme quoi les échanges avec le serveur n'avaient pas réussis.
Côté USR
J'ai enregistré les données reçues dans un fichier. Voici le début en
format hexdump:
Un détail qui m'a frappé lorsque j'ai vu le hexdump ci-dessus était
le fait qu'il s'agissait surtout de valeurs formées de longues séries de 1 et de 0,
ce qui ressemble à ce qu'on voit lorsque l'émetteur transmet plus lentement
que ce à quoi le récepteur s'attend.
La communication entre le PC et le modem USR était fixée à 38400 BAUD, mais
la connexion s'établissait à 9600 BAUD. Je pensais que le modem faisait
la conversion 9600 à 38400. Eh bien il se trouve que ce comportement
est configurable. Selon le manuel de mon modem, cela se fait
par la commande AT&B:
Et en toute logique, une consultation des paramètres actuels par la commande ATI4
a confirmé que la vitesse était variable!
ATI4
Merveilleux! Je n'avais pas utilisé de modem depuis au moins 15 ans et en plus
de m'amuser, j'ai appris quelque chose!
J'ai tapé la commande AT&B1 afin d'avoir une vitesse fixe. J'aurais probablement
pu simplement changer la configuration de mon terminal (Telix 3.51) pour un baud rate de 9600.
Cette fois, les donnés reçues étaient beaucoup plus claires et j'ai pu repérer
quelques tendances.
00000000 61 74 26 62 31 0d 0d 0a 4f 4b 0d 0a 61 74 61 0d |at&b1...OK..ata.|
00000010 0d 0a 43 4f 4e 4e 45 43 54 20 39 36 30 30 0d 0a |..CONNECT 9600..|
00000020 02 89 2f ae ba 94 94 94 94 94 94 94 9d bf 43 a4 |../...........C.|
00000030 94 94 ba ba 3e 89 2f ae ba 94 94 94 94 94 94 94 |....>./.........|
00000040 9d bf 43 a4 94 94 ba ba 3e 0d 02 89 2f ae ba 94 |..C.....>.../...|
00000050 94 94 94 94 94 94 9d bf 43 a4 94 94 ba ba 3e 89 |........C.....>.|
00000060 2f ae ba 94 94 94 94 94 94 94 9d bf 43 a4 94 94 |/...........C...|
00000070 ba ba 3e 0d 02 89 2f ae ba 94 94 94 94 94 94 94 |..>.../.........|
00000080 9d bf 43 a4 94 94 ba ba 3e 89 2f ae ba 94 94 94 |..C.....>./.....|
00000090 94 94 94 94 9d bf 43 a4 94 94 ba ba 3e 0d db 2a |......C.....>..*|
000000a0 ef 77 77 77 77 77 77 9f 40 21 90 06 12 01 66 64 |.wwwwww.@!....fd|
000000b0 11 88 44 20 21 c8 66 31 80 10 09 24 21 04 64 30 |..D !.f1...$!.d0|
000000c0 91 c4 40 11 09 84 62 22 88 24 22 44 20 98 c0 20 |..@...b".$"D .. |
000000d0 84 66 44 01 91 04 32 42 33 24 32 cc 24 13 44 22 |.fD...2B3$2.$.D"|
000000e0 02 88 04 32 11 26 23 44 60 32 01 19 48 22 20 42 |...2.&#D`2..H" B|
000000f0 21 0d 0a 4e 4f 20 43 41 52 52 49 45 52 0d 0a |!..NO CARRIER..|
Les données ci-dessus n'avaient pas été transmises d'un seul bloc. J'avais pu les
voir apparaître à l'écran par petits groupes. Selon mes observations, chaque
groupe commençait par 02 (STX) et se terminait par 0D (CR).
Il me semblait que beaucoup de caractères avaient le bit le plus significatif
à 1, et cela m'a fait penser à la parité. Jusqu'à maintenant, j'avais utilisé 8N1
sans réfléchir.
Fallait-il utiliser de la parité paire ou impaire? Me suis-je demandé. Pour répondre
à cette question, j'ai compté les bits de quelques caractères:
89 : Binaire 10001001. Nombre de 1 dans les 7 derniers bits: 2
94 : Binaire 10010100. Nombre de 1 dans les 7 derniers bits: 2
9d : Binaire 10011101. Nombre de 1 dans les 7 derniers bits: 4
bf : Binaire 10111111. Nombre de 1 dans les 7 derniers bits: 6
2f : Binaire 00011111. Nombre de 1 dans les 7 derniers bits: 5
43 : Binaire 01000011. Nombre de 1 dans les 7 derniers bits: 3
Lorsqu'il y avait un nombre de bits à 1 pair dans les 7 derniers bits, le bit le plus significatif
était à 1, faisant un octet avec un nombre de bit impair. C'était donc un système à parité impaire! (ou un hasard)
Avec mon terminal reconfiguré en 7O1, j'ai obtenu les données suivantes:
J'ai remarqué que chaque « paquet » contenait des répétitions.
02
09 2f 2e 3a 14 14 14 14 14 14 14 1d 57 2f 1d 14 14 20 3b 3e
09 2f 2e 3a 14 14 14 14 14 14 14 1d 57 2f 1d 14 14 20 3b 3e
0d
Lorsque le paquet ci-dessus a été capturé, j'avais utilisé l'id 12312312 et le mot de passe 9876.
J'ai réessayé avec différents numéros d'usager et mots de passe pour voir s'ils faisaient partie
des informations transmises:
Cela a confirmé que le jeu envoie le code d'usager et le mot de passe. Ce qui semble être
le code d'usager est souligné et le mot de passe est en gras.
J'ai tenté de trouver un moyen simple de décoder les nombres (par simple, je veux dire
autre chose qu'un tableau d'équivalences) mais j'ai échoué. Mais peu importe! Si
je savais les déchiffrer, que ferais-je ensuite?
Il est clair que ce système est fait pour fonctionner sans être connecté et conçu pour ne
pas monopoliser la ligne de téléphone.
C'est dommage, j'avoue que j'espérais que le "jeu" agisse réellement comme un terminal simple où les données
reçues du modem s'afficheraient à l'écran, et où les boutons de la manette fonctionneraient comme les
touches d'un clavier. (Il y aurait eu du potentiel pour faire des choses intéressantes, comme contrôler une boîte
Linux par un Super Nintendo...).
Regardons à l'intérieur du modem
Voici l'intérieur du NDM24:
Ce modem utilise l'IC RCV144ACFW/SP (Rockwell / Conexant) qui est capable de communiquer jusqu'à 14400 baud
et présente une interface sérielle côté terminal/pc.
Voici mon interprétation du design du NDM24:
En bleu: L'alimentation
Rien de particulier.
En jaune: L'interface entre le RCV144ACFW et le port manette
Le RCV144ACFW (modèle R6746) est conçu pour une communication sérielle, de type RS-232. S'il s'agissait
d'un modem destiné à être utilisé sur un PC, un traducteur de niveaux de voltage (0-5 volt côté modem, +12 à -12v côté
port série) serait utilisé. Mais le NDM24 est conçu pour être branché à un port de manette. Évidemment, cela n'est
pas supporté par le RCV144! Il faut donc quelque chose capable de traduire entre les deux.
La conversion entre RS232 et Super Famicom semble assurée par deux circuits intégrés (IC10: 78081GA57 9937PP002, IC9: D65611 026 9948LY009). Je n'ai pas d'information sur la nature de ces deux puces.
On remarque ci-dessus l'empreinte CN0, destinée à recevoir un connecteur. Il n'y a qu'une chose à faire
lorsqu'on découvre quelque chose comme ça: Sonder le tout!
Avec un oscilloscope, j'ai observé de la communication cadencée à 19200 baud.
Je pense que cela permet de monitorer les échanges entre le RCV144ACFW et l'interface Super Famicom. Parfait en développement!
J'ai décodé un des messages, et il semble bien s'agir de communication avec un modem. Ci-dessous: [CR][LF]OK[CR][LF])
En mauve: Le modem
La puce principale (RCV144ACFW) est accompagnée à gauche de ROM et de mémoire vive. Et du côté
droit du nécessaire pour interfacer avec la ligne de téléphone: Transfo, relais pour « décrocher la ligne »,
protections contre les surtensions, filtres...
Comme on peut s'y attendre, cela n'est pas très différent de ce diagramme tiré du guide de conception:
En rouge: Pièces manquantes
Bien que des empreintes soient présentes, plusieurs pièces dans la région de l'interface avec la ligne
de téléphone ne sont pas installées. En me basant sur le diagramme ci-dessous, tiré du guide
de design du RCV144, je dirais qu'il manque les éléments suivants:
Le circuit de détection de sonnerie. (L'opto-isolateur PC2 n'est pas installé...)
Pas de relais pour débrancher le combiné (ce modem n'a pas le deuxième connecteur auquel nous
brancherions normalement un téléphone)
Début d'émulation
bsnes-plus modifié
Je prévoyais ce week-end sortir mon oscilloscope et inspecter les signaux entre la console et le modem, et souhaitais
ensuite valider mes observations sur la méthode de communication en faisant fonctionner le jeu dans un émulateur.
J'ai choisi d'utiliser l'émulateur bsnes-plus comme outil
de travail en raison des fonctionalités de déboggage dont il est doté, pensant que cela sera sûrement utile.
J'avais déjà fait quelques modfications rapide à higan pour passer le test de présence de la manette
et du modem. J'ai fait l'équivalent pour bsnes-plus, mais proprement cette fois: Les options NTT Data Keypad et Data Modem
apparaîssent dans les menus comme il se doit, et les boutons supplémentaires de la manette fonctionnent réellement. La
partie modem ne fait cependant que retourner le bon ID lors des cycles d'horloge 12 à 15.
Un problème est survenu avec l'équipement. Veuillez couper l'alimentation et recommencer.
Code 39F310009999
Une erreur s'est produite dans la cartouche. (lit.: Cassette de communication)
Une erreur avec la cartouche (de communication). J'assume qu'il s'agit de la cartouche tout simplement.
Voyons un peu ce qu'elle renferme:
Nous avons donc:
U4: La puce de protection (CIC) de Nintendo
U5: Une puce de logique standard 74AC08 (Quad 2-input AND gate)
U3: Un puce ce logique standard 74AC139 (Décodeur/démultiplexeur 2 à 4)
U2: Un puce de flash SHARP LH28F020SUT-N80 (2MBIT / 256x8)
U1: Le ROM du jeu. (Apparamment 8MBIT / 1024x8)
Donc un ROM, une puce de protection CIC, et un peu de logique (pour le décodage d'adresse sans doute). Mais
cette cartouche contient quelque chose d'un peu inhabituel: Une mémoire flash!
Je crois que ce message d'erreur est causé par l'absence de support pour cette puce de mémoire flash par l'émulateur.
Car contrairement, à un EPROM ou un MASK ROM, les mémoires flash de ce genre supportent des commandes pour lire l'ID,
déverrouiller le mode d'écriture, etc. Il est raisonnable de s'attendre à ce qu'un logiciel accède à l'ID de
la puce au démarrage pour vérification.
Il faudra probablement ajouter du support pour la puce de flash SHARP à l'émulateur pour aller plus loin.
Nouveaux buts
Je viens de découvrir que la documentation de nocash SNES couvre preque tout ce que je cherchais
à savoir:
Tout ce que j'espérais avoir le plaisir de découvrir par moi même est
déjà documenté
Je pensais m'amuser en terrain inconnu avec mon oscilloscope pour découvrir comment le modem communique,
mais tout est déjà documenté!. J'ai
pourtant fait des recherches avant de fixer mes buts..
Nous ne sommes qu'au septième jour de RC2018/09, et déjà plusieurs items de ma liste de buts sont rayés:
Découvrir et documenter comment la communication entre la console et le modem fonctionne.
En apprendre davantage sur le modem. Quelle est sa vitesse? Communique-t-il avec des commandes AT normales?
Prétendre que l'appel téléphonique a réussi pour voir ce qui arrive ensuite.
Il reste ces deux items:
Tenter de faire fonctionner le jeu en émulation (communication avec le modem, et commandes modem)
Tenter de découvrir comment fonctionnaient les échanges entre le serveur et le jeu.
J'ai certainement l'intention à présent de continuer mon travail sur bsnes-plus
pour supporter la puce de FLASH et le Modem correctement. Mais j'ignore combien
de temps je suis prêt à passer pour comprendre comment le protocole d'un système
de pari fonctionne.
Sachant que j'aurai très bientôt un émulateur supportant la manette et le modem
pour développer confortablement, j'ai eu d'autres idées qui me motivent davantage:
Débuter le développement pour SNES
Écrire mon propre code pour lire la manette NTT Data Keypad
Écrire mon propre code pour communiquer avec le modem
Écrire un terminal simple pour SNES
C'est sans doute trop pour 3 semaines, mais cela devrait être intéressant.
Coder pour SNES
Bon, alors avant d'ajouter le support des puces FLASH à l'émulateur bsnes-plus, j'ai voulu apprendre
comment programmer pour SNES en assembleur. Mais le CPU du SNES est un 65816 que je ne connais pas.
Mon expérience avec l'assembleur:
Il y a 13 ans environ, j'ai concocté un petit bout de code très simple pour 6502 qui permettait
de corriger les couleurs lorsque
Super Mario Bros VS. tourne
sur un NES standard, mais j'ai tout oublié ensuite. Alors même si le 65816 est une évolution
du 6502, je n'avais aucune longueur d'avance.
Quelques années plus tard, j'ai beaucoup codé en assembleur Atmel AVR et un
peu pour ARM (pour accélérer des graphiques avec les instructions iWMMX du Xscale).
Et ces deux dernières années, j'ai écrit des milliers de lignes d'assembleur 8088 pour la
programmation de jeux rétro sous DOS, comme RATillery.
Mes premiers pas avec le 65816:
Lorsque j'ai débuté sur le 8088, je trouvais ce CPU très limité. J'étais habitué aux CPU RISC
avec beaucoup de registres alors j'ai dû changer ma manière de penser et adopter de nouvelles
stratégies pour accomplir mes buts. J'ai à nouveau vécu cette expérience, mais cette fois
avec le 65816. Il a encore moins de registres que le 8088, et ceux qui sont disponibles sont
spécialisés et de taille variables! Je ne suis absolument pas habitué à coder pour
ce genre d'architecture.
Comme on pourrait s'y attendre, je suis tombé dans quelques pièges:
Omettre de s'assurer que l'état du Carry flag est connu avant de faire une addition. Cela
m'a coûté beaucoup de temps, car c'est du code que je n'avais pas écrit qui brisait lorsque
le carry flag était actif. (rendu actif par mon code qui s'exécutait avant).
Changer la taille de l'accumulateur dans une fonction ou un macro, sans la remettre
comme elle était au début, causant ensuite des problèmes du côté de l'appelant.
Négliger de configurer la taille de l'accumulateur ou des registres d'index dans un
macro ou fonction, pour obtenir plus tard un mauvais fonctionnement lorsque le code
avant cet appel fait des changements aux tailles.
Transférer le contenu d'un registre 8 bits vers un registre 16 bits (avec TAY, par exemple)
copie l'octet « fantôme » de poids fort de la source vers la destination, plutôt que de mettre
l'octet de poids fort à 0. (Lire le manuel ne fait pas de tort!)
Naturellement, la SNES est une machine tout de même complexe. Lorsqu'il y a un pépin,
c'est autant ma compréhension du SNES que mon code 65816 qui peut en être la cause...
Des résultats!
Après de nombreuses heures passées à lire de la documentation, à expérimenter avec des exemples,
à parcourir des tutoriels et à me familiariser avec les fonctions de débogage de bsnes-plus,
j'ai finalement réussi à créé un ROM très simple, un test pour manettes supportant le NTT Data Keypad!
Les boutons appuyés (ou les bits à 0 sur le fil) sont indiqués par de petites boîtes vertes. Les bits
d'identification sont aussi inclus. Dans le screenshot ci-dessous, il y avait un NTT Data Keypad
dans le port 1 et un modem dans le port 2. Les boutons numériques 3, 6 et 8 étaient enfoncés lorsque
j'ai capturé cette image.
C'est très épuré, mais il y a tout de même un peu de couleurs. Et pour le plaisir, j'ai mis
en place un arrière-plan défilant. C'est assez courant dans les menus des jeux, pas étonnant
puisque c'est facile à obtenir et très peu demandant sur le système.
Ce logiciel utilise les registres "style ancien" $4016 et $4017 pour générer l'impulsion de
latch et les 32 coups d'horloge nécessaires pour recevoir les bits des périphériques. Je
n'ai pas inséré de délais entre les impulsions, j'ignore si c'est OK sur une vraie console. J'ignore
aussi si le programme lui-même démarrerait sur le vrai matériel. Mais je le saurai dès que j'aurai
reçu mon Everdrive.
Version 1.0 9 septembre 2018 (Dimanche)
Première version. N'a pas été testée sur une vraie console
Ma cartouche JRA PAT (TJEJ) contient une puce de flash SHARP LH28F020SUT-N80. L'excellente
documentation SNES de Nocash explique qu'il y a plusieurs versions de JRA PAT, et donne
des détails sur les types de puces flash qu'on retrouve dans ces cartouches. La plus vieille version (TJAJ?)
de JRA PAT ne supporterait qu'une puce fabriquée par AMD, tandis que la nouvelle version supporterait
3 types de puces (fabriquées par Amd, Atmel ou Sharp).
J'ai donc décidé d'émuler la puce d'AMD, qui devrait fonctionner avec toutes les versions du jeu.
La documentation Nocash nous apprends que du point de vue du CPU de la console, la puce
est visible dans cette plage d'adresse:
C0h-C3h:0000h-7FFFh ;128Kbyte FLASH (broken into 4 chunks of 32Kbytes)
En lecture, une puce de Flash comme celle-ci se comporte comme de la mémoire vive ou un ROM. Mais
en écriture, c'est plus complexe. Écrire à ces adresses comme s'il s'agissait de mémoire n'aura
normalement aucun effet. Toutefois, lorsque certaines valeurs magiques sont écrites à des adresses
particulières, la puce entre en mode commandes. Il est alors possible d'effectuer diverses opérations,
comme identifier la marque et le modèle de puce, effacer des secteurs, ou (enfin) écrire!.
Par exemple, pour lire l'ID de la puce, et du coup détecter la marque et le modèle, il faut:
Écrire $AA à l'adresse $5555
Écrire $55 à l'adresse $2AAA
Écrire $F0 à l'adresse $5555
Lire l'ID du manufacturier à l'adresse $0000
Lire le numéro de produit à l'adresse $0001
.. et finalement faire une autre séquence pour sortir de ce mode..
En mode "normal", les deux lectures ci-dessus retourneraient ce que contient la mémoire FLASH à ces adresses.
Je me suis inspiré du code du OBC1 qui implémente les méthodes de la classe Memory, qui permettent d'attraper les
lectures et écritures au vol, ce qui est très utile pour reproduire le mode de commande décrit ci-dessus. Afin de conserver
les données dans un fichier, j'ai reproduit le mécanisme gérant la mémoire de sauvegarde (mémoire RAM avec pile) alors
un fichier .flh de 128KB sera créé.
Voici le manifest (fichier .xml correspondant au nom du rom. Par exemple, si votre fichier est JRA_PAT_TDEJ.sfc, il faudra
créer JRA_PAT_TDEJ.xml) qui déclare à ma version modifiée de bsnes-plus qu'une
puce flash de type AMD est présente:
On peut voir ci-dessous que le jeu ne fait que lire l'ID de la puce, puis fait un bon
nombre de lecture normales pour voir ce qu'elle contient. Comme elle est vierge, le
message ci-dessous indique qu'il faut compléter une procédure d'enregistrement ou d'abonnement.
[Eregistrement d'abonné]
La procédure d'enregistrement d'abonné n'a pas été complétée avec cette cartouche. Veuillez
compléter les procédures suivantes.
La première étape consiste à configurer les paramètres de communication.
1. Type de ligne (20 / 10 / PB). (*)
2. Appel avec opérateur? (en composant le zéro? Je ne suis pas certain)
3. Volume du haut parleur du modem (Coupé / Bas / Moyen / Élevé)
4. Préfixe d'appel? Numéros additionnels à composer avant l'appel? (Je ne suis pas certain)
5. Son du Super Famicom (Inactif / Actif)
6. Terminé (sauvegarder les paramètres)
(*) Je pense que le 20 et 10 réfèrent au nombre d'impulsions par secondes pour la composition par
impulsion. Et PB provient probablement de « Push Button », correspondant à la composition
par tonalités (DTMF).
Lorsque j'ai tenté d'enregistrer les paramètres, le jeu a tenté d'accéder à la flash
mais a rencontré cette erreur car je n'avais pas encore implémenté l'écriture. Voici cet
écran d'erreur.
Un problème est survenu avec l'équipement. Veuillez couper l'alimentation et recommencer.
Code 39F330009999
Une erreur s'est produite dans la cartouche. (lit.: Cassette de communication)
Voici la trace de ce qu'a fait le jeu avant d'afficher cette erreur.
....
AmdFlash write $aa at $c05555
AmdFlash write $55 at $c02aaa
AmdFlash write $90 at $c05555 ; Identification du type de flash
AmdFlash read $c00000 ; Manufacturer
AmdFlash read $c00001 ; Device type
AmdFlash write $aa at $c05555
AmdFlash write $55 at $c02aaa
AmdFlash write $f0 at $c05555 ; Fin
...
AmdFlash write $aa at $c05555
AmdFlash write $55 at $c02aaa
AmdFlash write $80 at $c05555 ; Préparation pour effacer
AmdFlash write $aa at $c05555
AmdFlash write $55 at $c02aaa
AmdFlash write $30 at $c00000 ; Effacte un secteur de 16k
AmdFlash read $c00000 ; Lit l'état de l'opération (le bit 7 est à 1 indique que c'est terminé)
AmdFlash write $aa at $c05555
AmdFlash write $55 at $c02aaa
AmdFlash write $f0 at $c05555 ; Fin
AmdFlash write $aa at $c05555
AmdFlash write $55 at $c02aaa
AmdFlash write $a0 at $c05555 ; Mode écritude octet par octet
AmdFlash write $01 at $c00000 ; Écriture de la valeur $01 à l'adresse 0
AmdFlash read $c00000 ; Lit l'état de l'opération (retourne $FF en raison de l'implémentation incomplète)
AmdFlash read $c00000 ; Lit encore l'état de l'opération (retourne encore $FF); Abandonne et affiche une erreur car cela devait fonctionner.
Cela a confirmé que j'étais sur la bonne voie. Il m'a alors fallu quelques minutes pour rendre l'écriture fonctionelle, le gros
du travail étant déjà fait.
Vous vous demandez peut-être à quoi ce genre de code peut ressembler. Voici d'abord comment les
accès en lecture sont gérés. Le contenu de la mémoire flash est retourné, sauf si elle en mode de lecture
de l'ID manufacturier, ou encore si une opération d'effacement ou d'écriture est en cours.
Normalement les opérations d'écriture ou d'effacement prennent un certain temps, et le code en suit
la progression en surveillant un bit d'état. Mon but actuel est d'arriver dès que possible à une
étape où le jeu communique avec le modem, afin que je puisse travailler sur l'émulation du modem. Je n'ai
donc pas tenté de reproduire la vitesse réelle de ces opérations. Elles sont donc instantanées.
uint8 AmdFlash::read(unsigned addr)
{
addr &= 0x1FFFF;
switch (flash_state)
{
default:
flash_state = StateNormal;
case StateNormal:
return memory::cartflash.read(addr);
case StateId:
if (addr == 0x0000) {
return 0x01; // manufacturer
}
if (addr == 0x0001) {
return 0x20;
}
return 0xff; // not sure
case StateEraseAll:
flash_state = StateNormal;
return 0x80; // instantaneous!
case StateEraseSector:
flash_state = StateNormal;
return 0x80; // instantaneous!
case StateWriteByte:
// Again, no attempt to provide realistic timing.
flash_state = StateNormal;
return (dta & 0x80);
}
}
Pour l'écriture, le code attend de voir les écritures magiques ($AA à l'adresse $5555 suivi de $55 à l'adresse $2AAA)
puis agit en fonction de la troisième écriture qui lance alors une opération d'effacement (partiel ou total) ou l'écriture
d'un octet.
void AmdFlash::write(unsigned addr, uint8 data)
{
int i;
if (flash_state == StateWriteByte) {
dta = data;
memory::cartflash.write(addr, data);
flash_state = StateNormal;
return;
}
if (step == 1) {
if ((addr == 0x2AAA) && (data == 0x55)) {
step++;
return;
}
else
step = 0;
}
if ((step == 0) && (addr == 0x5555) && (data == 0xAA)) {
step++;
return;
};
if (step < 2)
return;
step = 0;
switch (data)
{
default:
printf("unknown flash command 0x%02x\n", data);
flash_state = StateNormal;
break;
case 0x90: // enter ID mode
if (addr != 0x5555) {
break;
}
flash_state = StateId;
break;
case 0xF0: // end / return to normal mode
flash_state = StateNormal;
break;
case 0x80: // prepare erase (unlock?)
if (addr != 0x5555) { step = 0; break; }
flash_state = StatePrepareErase;
break;
case 0x10: // erase all
if (addr != 0x5555) { step = 0; break; }
for (i=0; i<0x20000; i++)
memory::cartflash.write(addr+i, 0xFF);
flash_state = StateEraseAll;
break;
case 0x30: // erase 16kb sector
for (i=0; i<0x4000; i++)
memory::cartflash.write((addr&0x1C000)+i, 0xFF);
flash_state = StateEraseSector;
break;
case 0xA0: // write byte
if (addr != 0x5555) { step = 0; break; }
flash_state = StateWriteByte;
break;
}
}
Une fois le code gérant l'écriture en place, le jeu n'affiche plus d'erreur. Après quelques secondes,
un écran confirmant l'enregistrement des paramètres de configuration apparaît. Hourra!
Appuyer sur A mène à la prochaine étape, où il faut saisir ces informations d'abonné.
Numéro d'abonné [ ]
Code d'accès [ ]
Date de naissance (AD) [___Année ___Mois ___Jour]
Valider (et se connecter au centre de service)
Une fois les données saisies, le système demande de confirmer une dernière fois
avant de se brancher (par téléphone) à la centrale:
Et nous sommes finalement à l'écran du cheval animé pendant lequel le modem doit se mettre en marche.
只今センタと電話回線を使って接続中です。恐れ入りますが、そのままでしばらくお待ち下さい。
Connexion au centre de service par téléphone en cours.
Nous vous demandons de bien vouloir patienter quelques instants.
Sans surprise, cet appel échoue puisque l'émulation du modem est incomplète.
Le message d'erreur indique un problème au niveau du modem (通信モデム) et offre
de réessayer, d'abandonner ou de modifier les paramètres de communication.
J'ai ajouté le code nécessaire pour recevoir et transmettre des octets, et créé une classe qui
s'occupe de recevoir et répondre comme le ferait un modem. J'ai d'abord fait fonctionner le code
qui reçoit les octets transmis par la console pour qu'il affiche simplement ce qui est reçu.
Des commandes AT standard semblent être utilisées! Le jeu tente en boucle d'obtenir une réponse
à la commande suivante:
ATE1Q0V1 : Active l'echo (E1), Affiche les codes de résultat (Q0) et les message en anglais (V1).
Malheureusement, cela ne fonctionne qu'à moitié: Le jeu ne semble pas tenir compte de la réponse
que je crois pourtant transmettre correctement...
(Note: Mon implémentation du modem supporte l'echo. Mais cela n'est pas visible dans la trace ci-dessus.)
Après 10 tentatives, le jeu abandonne et affiche un message d'erreur.
Pourquoi ça ne fonctionne pas? Plusieurs explications sont possible.
Erreur dans mon code d'émulation
J'ai peut-être mal compris la documentation
Bien que les commandes semblent standard, les réponses du modem ne le sont peut-être pas.
Il y a une erreur dans la documentation de Nocash
J'ai révisé mon code plusieurs fois et tenté toutes sortes d'expériences qui contredisent
même la documentation (déplacer les bits, les inverser, etc) jusqu'à tard dans la nuit, mais en vain.
Ce qui m'aiderait le plus à présent serait d'avoir un exemple de dialogue fonctionnel entre la
console et le modem. Eh bien, il semblerait que malgré l'existence de documentation, j'aurai le
plaisir d'aller sonder les signaux sur la carte du modem :)
Afin de comprendre ce qui n'allait pas avec mon code d'émulation du modem (car le jeu n'acceptait
pas les réponses que le faux modem lui donnait) je me suis tourné vers le matériel.
Je me suis procuré un analyseur logique de Saleae et installé des sondes sur tout les signaux
entre le Super Famicom et le modem ainsi que sur le port de déboggage sur le PCB du modem.
Avec un tel luxe, j'ai rapidement pu confirmer que le port de déboggage était comme je le pensais. Une broche
pour chaque direction. J'ai également appris que le modem ne fait pas echo au caractère \n. C'était
une première chose à corriger dans mon code.
Ensuite j'ai regardé la première commande que le jeu transmet (ATE1Q0V1\r\n):
J'ai pu constater que la transmission vers le modem (ligne 01) commence avant que tous les caractères
soient reçues. Il y a donc un peu de mémoire dans l'interface entre le SFC et la puce de modem RCV144. Ici
encore, le modem répète les caractères reçus (ligne 00) sauf le dernier (\n).
Quelques instants plus tard, les 9 caractères d'echo sont retransmis au SFC a peu près tel qu'expliqué dans la
documentation Full SNES de Nocash:
Les 8 premiers bits de D0 (4017h.Bit0) contient un octet proventant du modem lorsque indiqué.
Le 9ième bit de D0 indique si les 8 derniers bits sont valides et représentent un nouvel octet reçu du modem ou non.
Mais en regardant de près, j'ai remarqué que le comportement du premier bit de D1 (4017h.Bit1) semble différent que ce qui est suggéré par la documentation:
Quelques observations:
Le premier transfert utilise 16 coups d'horloge, les autres 9.
Le premier transfert ne contient pas d'octet provenant du modem (Le neuvième bit est à 1)
Le deuxième transfert et les suivants contiennent un octet
Le deuxième bit de D1 (4017h.Bit1) est à 1 uniquement au dernier octet
Mon interprétation pour le rôle du deuxième bit sur D1 est donc:
Indique au Super Famicom si le prochain transfert contiendra un octet.
Le modem ne transmet pas de nouvel octet au SFC sans dabord déclarer son intention au Super Famicom.
J'ai mis à jour mon code d'émulation, et ENFIN le jeu a accepté la réponse que mon faux modem lui fournissait. J'ai
ajouté du support pour toutes les commandes AT que le jeu utilise. Voici l'échange complet, jusqu'à la commande
qui lance normalement l'appel téléphonique:
ATE1Q0V1
OK
ATE1Q0V1
OK
AT&F&W0&W1
OK
ATZ
OK
ATS7=60
OK
ATS8=3
OK
ATT
OK
ATL2
OK
AT%B9600
OK
AT\N0%C0
OK
ATS9=6
OK
ATS10=14
OK
ATS11=95
OK
ATS91=15
OK
ATX4
OK
ATD5555555
CONNECT 9600
Mon code mémorise la commande AT%B9600 (qui sert, je pense, à demander une connection à 9600 baud) et
la répète simplement lors de la connexion à la fin (commande ATD).
Une fois les mots CONNECT 9600 reçus par le jeu, la musique change et le cheval est au galop!
只今センタと通信中です。恐れ入りますが、そのままでしばらくお待ち下さい。
Présentement en communication avec le centre de service. Nous vous demandons de bien vouloir patienter quelques instants.
Quelques secondes plus tard, la communication échoue puisqu'un fois la « connexion » établie, le code ne fait qu'afficher
ce que le jeu transmets. La dernière ligne dans la capture d'écran suivante montre ce que le jeu transmets avant
d'abandonner. Et ce sont exactement ceux que j'ai obtenus la première semaine quand j'ai branché deux modems ensemble!
Après avoir affiché cette erreur, le jeu proposait de réessayer, mais cela ne fonctionnait pas puisque le faux modem était
toujours en mode « connecté » et ne répondait plus aux commandes AT. Avec le vrai modem, cela n'arrive pas. Le jeu
le faisait raccrocher. Mais comment?
Le Super Famicom transmet des octets au modem en utilisant le bit 7 du registre 4201h qui contrôle l'état
d'une des broches du connecteur de manette. Le premier bit indique, quand il est à 0, que les 8 autres qui
suivent contiennent un octet valide.
Lorsqu'il n'y a pas d'octet valide, les bits semblent toujours être à 1. Toutefois, quand vient le temps
de raccrocher, j'ai observé une valeur de 0x43 (Ou code ASCII pour la lettre C):
J'ai mis le code d'émulation à jour encore une fois, et il est devenu possible de faire des tentatives
de connexion répétés. Mais j'ai remarqué que les connexions échouaient plus rapidement que sur
le vrai matériel. En effet, quand j'avais branché deux modems ensembles il y a quelques semaines, j'avais
remarqué que le jeu faisait 3 tentatives de dialogue avec le serveur. Or, dans l'échange ci-dessus, un seul
paquet était transmit avant que le jeu raccroche.
Je me suis demandé ce qui pouvait bien déplaire au jeu et causer cette déconnexion prématurée. Et au fait,
lorsque la connexion est réellement coupée (car quelqu'un a décroché un combiné dans la maison, un classique),
comment le jeu fait-il pour le savoir?
J'ai une certaine expérience avec les modems, alors j'ai immédiatement pensé au signal DCD (Data Carrier Detect) qui
indique justement si le modem "parle" actuellement avec un autre. Et j'ai supposé qu'un bit quelque part
dans les échanges avec le Super Famicom devait y correspondre. Et comme ceci n'est pas émulé correctement,
après avoir transmit son premier message, le jeu doit tout simplement croire que le lien a été rompu!
Je me suis dit qu'il suffirait de comparer les échanges ayant lieu avant le message CONNECT 9600 et
ceux qui suivent pour trouver le bit en question. J'ai d'abord repéré le message en question:
Quelques instants plus tard, ces 16 octets sont relayés au Super Famicom par le port de manette:
Tiens tiens, il y a davantage d'impulsions sur la ligne D1 que précédemment! Voyons cela d'un peu plus près:
Trouvé! L'état de la ligne D1 au septième coup d'horloge indique une connexion en cours lorsqu'il est à 0. (montrés en
rouge)
J'ai ajouté cette fonctionnalité à l'émulateur, et c'était bien cela. À présent le jeu fait plusieurs tentatives avant
d'abandonner. Excellent, cela se comporte comme sur le vrai modem!
Connexion TCP
L'émulateur remplace la ligne téléphonique par une connexion TCP. Lorsque la commande ATD est recue, le numéro
de téléphone est ignoré (pour l'instant) et l'émulateur fait une tentative de se brancher sur 127.0.0.1:5555. Si
cela réussi, les octets transmits par le jeu sont relayés par ce socket.
Cela rends possible le développement d'un serveur pour le jeu. Je vais creuser un peu pour tenter
de comprendre les échanges, mais j'ignore jusqu'où je me rendrai. Pour l'instant du moins,
ce que transmets le jeu peut être récupéré facilement avec netcat. Comme ceci par exemple:
Cette édition du retro-challenge viens de prendre fin (déjà!). Je voulais comprendre comment
fonctionnait le modem NDM24 pour Super Famicom, et c'est généralement réussi. J'en sais assez
sur son fonctionnement pour écrire mon propre code SNES pour y parler. J'en sais un peu plus
sur ce que le "jeu" JRA PAT était et j'ai modifié un émulateur pour le faire fonctionner
je crois aussi "bien" que sur le vrai matériel: C'est à dire que dans les deux cas, on rencontre le mur
d'une communication vouée à l'échec (le serveur qui n'existe plus!).
Ce que j'ai accompli
Découvrir et documenter comment la communication entre la console et le modem fonctionne. Ceci était
déjà assez bien documenté dans la documentation Fullsnes de Nocash.. Mais j'ai tout de même découvert
quelques nouveaux détails: Comment mettre fin à l'appel téléphonique et l'existance d'un bit de Carrier Detect.
En apprendre davantage sur le modem. Quelle est sa vitesse? Communique-t-il avec des commandes AT normales?
9600 Baud sur la ligne (et 19200 à l'interne, entre la puce du modem et l'interface super famicom.
Prétendre que l'appel téléphonique a réussi pour voir ce qui arrive ensuite.
Le jeu transmets quelques paquets d'un format inconnu et mets fin à l'appel après quelques répétitions.
Tenter de faire fonctionner le jeu en émulation (communication avec le modem, et commandes modem)
Réussi, le jeu fonctionne dans une version modifiée de Bsnes-plus. J'ai du ajouter le support pour:
La manette NTT Data Keypad
La mémoire flash interne de la cartouche
Le modem (ligne de téléphone remplacée par une connexion TCP
Débuter le développement pour SNES C'est fait, j'ai réalisé un ROM de test de manette
supportant le NTT Data Keypad.
Écrire mon propre code pour lire la manette NTT Data Keypad C'est fait, c'était mon premier
projet.
Ce que je n'ai pas accompli
Écrire mon propre code pour communiquer avec le modem J'avais commencé à le faire pendant
la dernière semaine, mais la vraie vie (tm/mc) s'est mise en travers de mon chemin et je n'ai pas terminé...
Écrire un terminal simple pour SNES J'ai mis en place le code qui gère l'écran, les retours
de chariot, etc, mais je n'ai pas terminé. Il manquait le code pour relayer les caractères reçus du modem
vers l'écran, et je n'ai pas terminé le code pour parler au modem.
Tenter de découvrir comment fonctionnaient les échanges entre le serveur et le jeu. Je n'ai
pas réussi à comprendre grand-chose au protocole. Mais j'ai tenté de le faire... c'est donc chose accomplie!? Mais plus sérieusement, même si réussir à prendre la place d'un serveur de paris sur des courses de cheveaux
est un défi intéressant du point de vue technique (reverse engineering en profondeur du ROM!), le résultat
n'est pas très excitant. Qui voudrait s'amuser à parier sur des courses de cheveaux imaginaires? Pas moi.
Je vais sans doute continuer à expériementer et à documenter ce qui en vaut la peine, sur cette page ou ailleurs
sur mon site.