raphnet.net banner

RetroChallenge, Septembre 2018

twitter@raphnetlabs
Contenu

Sommaire

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

NDM24

NDK10

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

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

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: 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!

goto top


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.

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

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 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

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

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

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é NDM24

Côté USR

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!

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

Côté USR



J'ai enregistré les données reçues dans un fichier. Voici le début en format hexdump:
00000000  2b 2b 2b 61 74 68 0d 0d  0a 4f 4b 0d 0a 61 74 61  |+++ath...OK..ata|
00000010  0d 0d 0a 43 4f 4e 4e 45  43 54 20 39 36 30 30 0d  |...CONNECT 9600.|
00000020  0a 80 78 e0 f8 78 e0 80  78 fe 80 f8 f8 78 e0 78  |..x..x..x....x.x|
00000030  e0 78 e0 78 e0 78 e0 78  e0 78 e0 78 fe 80 f8 f8  |.x.x.x.x.x.x....|
00000040  f8 78 80 f8 78 e0 78 e0  78 78 1e 0f c0 78 1e f0  |.x..x.x.xx...x..|
00000050  80 78 e0 f8 78 e0 80 78  fe 80 f8 f8 78 e0 78 e0  |.x..x..x....x.x.|
00000060  78 e0 78 e0 78 e0 78 e0  78 e0 78 fe 80 f8 f8 f8  |x.x.x.x.x.x.....|
...
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

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:
	00000000  61 74 61 0d 0d 0a 43 4f  4e 4e 45 43 54 20 39 36  |ata...CONNECT 96|
	00000010  30 30 0d 0a 02 09 2f 2e  3a 14 14 14 14 14 14 14  |00..../.:.......|
	00000020  1d 57 2f 1d 14 14 20 3b  3e 09 2f 2e 3a 14 14 14  |.W/... ;>./.:...|
	00000030  14 14 14 14 1d 57 2f 1d  14 14 20 3b 3e 0d 02 09  |.....W/... ;>...|
	00000040  2f 2e 3a 14 14 14 14 14  14 14 1d 57 2f 1d 14 14  |/.:........W/...|
	00000050  20 3b 3e 09 2f 2e 3a 14  14 14 14 14 14 14 1d 57  | ;>./.:........W|
	00000060  2f 1d 14 14 20 3b 3e 0d  02 09 2f 2e 3a 14 14 14  |/... ;>.../.:...|
	00000070  14 14 14 14 1d 57 2f 1d  14 14 20 3b 3e 09 2f 2e  |.....W/... ;>./.|
	00000080  3a 14 14 14 14 14 14 14  1d 57 2f 1d 14 14 20 3b  |:........W/... ;|
	00000090  3e 0d 45 43 5c 77 77 77  77 77 77 77 06 12 01 66  |>.EC\wwwwwww...f|
	000000a0  64 03 09 40 33 01 06 11  09 66 12 10 20 19 40 20  |d..@3....f.. .@ |
	000000b0  22 10 42 09 44 62 20 30  19 48 04 66 11 01 40 62  |".B.Db 0.H.f..@b|
	000000c0  03 60 10 19 22 10 40 02  10 26 32 04 22 10 02 0d  |.`..".@..&2."...|
	000000d0  0a 4e 4f 20 43 41 52 52  49 45 52 0d 0a           |.NO CARRIER..|
Lorsque divisé en ce que je crois être des paquets: J'ai remarqué que chaque « paquet » contenait des répétitions. 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:
  • 11111111:1111 :: 09 2f 2e 3a 14 14 14 14 14 14 14 3a 3a 3a 3a 14 14 3a 3a 3e
  • 11111112:1111 :: 09 2f 2e 3a 14 14 14 14 14 14 14 3a 3a 3a 1d 14 14 3a 3a 3e
  • 11111113:1111 :: 09 2f 2e 3a 14 14 14 14 14 14 14 3a 3a 3a 5e 14 14 3a 3a 3e
  • 12412312:9876 :: 09 2f 2e 3a 14 14 14 14 14 14 14 1d 6d 2f 1d 14 14 20 3b 3e
  • 12312312:9876 :: 09 2f 2e 3a 14 14 14 14 14 14 14 1d 57 2f 1d 14 14 20 3b 3e
  • 01020304:0506 :: 09 2f 2e 3a 14 14 14 14 14 14 14 62 49 79 25 14 14 32 4c 3e
  • 07080910:1415 :: 09 2f 2e 3a 14 14 14 14 14 14 14 4d 7d 65 33 14 14 6b 76 3e
  • 16171819:2021 :: 09 2f 2e 3a 14 14 14 14 14 14 14 12 70 18 71 14 14 73 38 3e
  • 22232425:2627 :: 09 2f 2e 3a 14 14 14 14 14 14 14 5c 2f 47 17 14 14 11 40 3e
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...).


goto top


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:






goto top


Début d'émulation

bsnes-plus modifié

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.


J'ai rendu cette version modifiée de bsnes-plus, avec support pour manette et modem (incomplet à ce jour) sur github: https://github.com/raphnet/bsnes-plus/tree/ntt_data_keypad_and_modem

Malgré la présence d'une manette NTT Data Keypad et du pseudo-modem dans l'émulateur, certaines versions de JRA PAT affichent cette erreur:

機器に不具合が発生しました。スーパーファミコンの電源を切って、最初から機動下さい。
コードー39F310009999
通信カセットに異常が発生しました。
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: 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.

goto top


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: Il reste ces deux items: 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:

C'est sans doute trop pour 3 semaines, mais cela devrait être intéressant.

goto top


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: 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
Fichier(s):
test32.zip (2.1 KB)

goto top


Émulation de la mémoire flash

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:
  1. Écrire $AA à l'adresse $5555
  2. Écrire $55 à l'adresse $2AAA
  3. Écrire $F0 à l'adresse $5555
  4. Lire l'ID du manufacturier à l'adresse $0000
  5. Lire le numéro de produit à l'adresse $0001
  6. .. 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:
<?xml version='1.0' encoding='UTF-8'?>
<cartridge region='NTSC'>
	<rom>
		<map mode='linear' address='00-9f:8000-ffff'/>
	</rom>
	<amdflash>
		<map mode='linear' address='C0-C3:0000-7FFF'/>
	</amdflash>
</cartridge>
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. 回線種別 「20」 10 PB
2. 内線ゼロ発信 「無」有
3. 通信モデム音量 無 小 「中」大
4. 新電電付加番号
5. スーパーファミコンサウンド 無 「有」
6. 入力終了
Tentative de traduction:
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.

機器に不具合が発生しました。スーパーファミコンの電源を切って、最初から機動下さい。
コードー39F330009999
通信カセットに異常が発生しました。

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.



C'est tout pour l'instant. Cette version modifiée de bsnes-plus est dans cette branche sur github: https://github.com/raphnet/bsnes-plus/tree/ntt_data_keypad_and_modem

goto top


Monologue avec le modem

La manière dont le modem communique avec la console est expliquée dans la documentation SNES de Nocash:
http://problemkaputt.de/fullsnes.htm#snesaddonsfcmodemdataio

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. 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 :)


Ma version actuelle qui affiche ce que la console transmet au modem mais ne fonctionne toujours pas pour la réception est ici, si jamais...
https://github.com/raphnet/bsnes-plus/tree/ntt_data_keypad_and_modem

goto top


Modem fonctionnel dans l'émulateur

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:

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: Mon interprétation pour le rôle du deuxième bit sur D1 est donc: 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!


goto top


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:
$ nc -l -p 5555 | hexdump -C
00000000  02 89 2f ae ba 94 94 94  94 94 94 94 94 94 94 94  |../.............|
00000010  94 94 94 94 3e 89 2f ae  ba 94 94 94 94 94 94 94  |....>./.........|
00000020  94 94 94 94 94 94 94 94  3e 0d 02 89 2f ae ba 94  |........>.../...|
00000030  94 94 94 94 94 94 94 94  94 94 94 94 94 94 3e 89  |..............>.|
00000040  2f ae ba 94 94 94 94 94  94 94 94 94 94 94 94 94  |/...............|
00000050  94 94 3e 0d 02 89 2f ae  ba 94 94 94 94 94 94 94  |..>.../.........|
00000060  94 94 94 94 94 94 94 94  3e 89 2f ae ba 94 94 94  |........>./.....|
00000070  94 94 94 94 94 94 94 94  94 94 94 94 3e 0d 02 89  |............>...|
00000080  2f ae ba 94 94 94 94 94  94 94 94 94 94 94 94 94  |/...............|
00000090  94 94 3e 89 2f ae ba 94  94 94 94 94 94 94 94 94  |..>./...........|
000000a0  94 94 94 94 94 94 3e 0d  02 89 2f ae ba 94 94 94  |......>.../.....|
000000b0  94 94 94 94 94 94 94 94  94 94 94 94 3e 89 2f ae  |............>./.|
000000c0  ba 94 94 94 94 94 94 94  94 94 94 94 94 94 94 94  |................|
000000d0  3e 0d                                             |>.|
Le code pour ma version modifiée de bsnes-plus supportant le NTT Data Keypad et une émulation de base du NDM24 est disponible ici:

https://github.com/raphnet/bsnes-plus/tree/ntt_data_keypad_and_modem

goto top


Conclusion de RetroChallenge 2018/09

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

Ce que je n'ai pas accompli

Je vais sans doute continuer à expériementer et à documenter ce qui en vaut la peine, sur cette page ou ailleurs sur mon site.

goto top


Les marques de commerce utilisées dans ce site appartiennent à leurs propriétaires respectifs.
Copyright © 2002-2018, Raphaël Assenat
Site codé avecSite codé avec vimDernière mise à jour: 6 novembre 2018 (Mardi)