Je n'ai découvert ce défi que tout récemment. En faisant une recherche sur le site de Deckard pour tout autre chose, je suis arrivé par hasard sur la page consacrée à la saga des Archives et Thunderdocs, sur laquelle bien évidemment est faite une petite allusion à ce fameux défi du chapitre 10 des Archives ! Deckard n'y va d'ailleurs pas par quatre chemins puisqu'il annonce clairement que ce défi (je le cite) "n'a jamais été remporté par qui que ce soit à (sa) connaissance ! !". Il n'en fallait pas plus pour que je me jette à l'eau...
À noter, que bien évidemment, je n'ai pas récupéré la disquette de sources que Deckard propose sur son site et qui, je le suppose, doit bien aider, mais totalement fausser le défi. L'objectif ici n'étant pas de parader (le défi étant fini depuis à peine 25 ans hein...) mais bien de montrer tout le cheminement pour y arriver (donc sans raccourcis ni tricheries).
Comme vous en avez l'habitude maintenant (là je m'adresse aux 2 lecteurs des précédents défis...), on part de zéro et je montre tout le processus jusqu'à la résolution complète (enfin je l'espère), étape par étape (quitte parfois à partir sur des fausses pistes ou à faire des trucs inutiles).
Je vais scinder le tout en deux articles : un premier (celui-ci) qui traitera de la partie recherche/localisation/compréhension du code du défi. Et un second qui traitera spécifiquement de sa résolution concrète (programmation/écriture sur disque etc.).
Étape 1 : lancement de Archives 10 et de son défi (ça devrait être l'étape la plus facile).
Cela semble tout con mais avant de partir tête baissée (dans le mur), plusieurs petites choses, qui peuvent s'avérer utiles, sont à remarquer :
- Pour accéder à la page même du défi, il faut appuyer sur ESC lors du boot.
- La page texte s'affiche par scrolling vertical venant du bas de l'écran avec un petit effet sonore.
- Trois indications dans le texte en lui-même : "Ne modifier que les datas !", "signer en INVERSE dans le rectangle" et "WARNING : C'EST CHIANT". Bon bah au moins on est prévenu. Visiblement ça va être galère, on peut s'attendre à du cryptage (et donc il faudra recrypter notre propre texte, on n'a pas le droit de tout virrer pour mettre un petit "coucou" affiché par un PRINT par exemple, mais ça on s'en doutait un peu). Quant au "INVERSE dans le rectangle", un peu plus mystérieux en fait... On verra peut-être plus tard pourquoi en INVERSE particulièrement.
Étape 2 : le Boot 0. Qu'est-ce qui se passe en $801 ?
Deux options possibles. On peut éditer le secteur 0, piste 0 directement sous DiskFixer pour checker le code à partir de $801. Ou on "boot trace" classiquement. On va utiliser la méthode DiskFixer (pour changer un peu).
On lance donc DiskFixer, on lit le Secteur 00/ Piste 00. On se positionne en $01 et on appuie sur L pour avoir le code. On a donc sous nos yeux (ébahis), ce qui va se charger lors du boot en mémoire en $801+ :
0801- A9 60 LDA #$60 0803- 8D 01 08 STA $0801 0806- A9 FF LDA #$FF 0808- 8D FB 04 STA $04FB 080B- 8D F3 03 STA $03F3 080E- 2C 52 C0 BIT $C052 0811- 8D 00 C0 STA $C000 0814- 8D 02 C0 STA $C002 0817- 8D 04 C0 STA $C004 081A- 8D 0C C0 STA $C00C 081D- 8D 0E C0 STA $C00E 0820- 8D 81 C0 STA $C081 0823- 20 2F FB JSR $FB2F 0826- 20 58 FC JSR $FC58 0829- 20 84 FE JSR $FE84 082C- 20 93 FE JSR $FE93 082F- A9 0F LDA #$0F 0831- 85 50 STA $50 0833- A4 50 LDY $50 0835- A6 2B LDX $2B 0837- B9 5B 08 LDA $085B,Y 083A- 85 3D STA $3D 083C- B9 6B 08 LDA $086B,Y 083F- F0 05 BEQ $0846 0841- 85 27 STA $27 0843- 20 5C C6 JSR $C65C 0846- C6 50 DEC $50 0848- A5 50 LDA $50 084A- C9 06 CMP #$06 084C- D0 E5 BNE $0833 084E- A6 2B LDX $2B 0850- 8E 39 6C STX $6C39 0853- 20 7B 08 JSR $087B ; saut vers routine d'affichage du message Boot 0856- A9 00 LDA #$00 0858- 4C B6 08 JMP $08B6 085B- 00 BRK 085C- 0D 0B 09 ORA $090B 085F- 07 ??? 0860- 05 03 ORA $03 0862- 01 0E ORA ($0E,X) 0864- 0C ??? 0865- 0A ASL 0866- 08 PHP 0867- 06 04 ASL $04 0869- 02 ??? 086A- 0F ??? 086B- 00 BRK 086C- 00 BRK 086D- 00 BRK 086E- 00 BRK 086F- 00 BRK 0870- 00 BRK 0871- 00 BRK 0872- A0 04 LDY #$04 0874- 05 06 ORA $06 0876- 07 ??? 0877- BA TSX 0878- 9B ??? 0879- B6 B7 LDX $B7,Y 087B- A2 00 LDX #$00 ; affichage msg Boot 087D- BD 8F 08 LDA $088F,X 0880- 9D 00 04 STA $0400,X 0883- E8 INX 0884- E0 27 CPX #$27 0886- D0 F5 BNE $087D 0888- A2 60 LDX #$60 088A- 2C 10 C0 BIT $C010 088D- 60 RTS 088E- 20 20 20 JSR $2020 ; msg boot 0891- D3 ??? 0892- 0D 0F 0F ORA $0F0F 0895- 14 ??? 0896- 08 PHP 0897- 20 C6 01 JSR $01C6 089A- 13 ??? 089B- 14 ??? 089C- 20 C2 0F JSR $0FC2 089F- 0F ??? 08A0- 14 ??? 08A1- 20 20 20 JSR $2020 08A4- 28 PLP 08A5- EB ??? 08A6- 29 20 AND #$20 08A8- C5 04 CMP $04 08AA- 04 ??? 08AB- 09 05 ORA #$05 08AD- 20 C8 01 JSR $01C8 08B0- 17 ??? 08B1- 0B ??? 08B2- 20 20 20 JSR $2020 08B5- 20 08B6- 8D F2 03 STA $03F2 ; vecteur RESET 08B9- A9 BA LDA #$BA 08BB- 8D F3 03 STA $03F3 ; idem 08BE- 49 A5 EOR #$A5 08C0- 8D F4 03 STA $03F4 ; idem 08C3- A9 20 LDA #$20 08C5- 85 E6 STA $E6 08C7- 20 F2 F3 JSR $F3F2 ; efface page courante HGR 08CA- 2C 10 C0 BIT $C010 ; strobe Keyboard 08CD- 8D 02 C0 STA $C002 ; R mémoire principale 08D0- 8D 04 C0 STA $C004 ; W mémoire principale 08D3- 8D 08 C0 STA $C008 ; pile/P0 mémoire principale 08D6- 20 00 B6 JSR $B600 ; lecture écran intro 08D9- 04 ??? 08DA- EA NOP 08DB- AD 00 C0 LDA $C000 ; lecture clavier 08DE- C9 9B CMP #$9B ; appui sur ESC ? 08E0- D0 0D BNE $08EF 08E2- 20 00 B6 JSR $B600 ; oui 08E5- 00 BRK 08E6- 20 44 83 JSR $8344 ; <== ICI ! 08E9- 2C 10 C0 BIT $C010 08EC- 4C D6 08 JMP $08D6 08EF- 20 00 B6 JSR $B600 08F2- 03 ??? <= 08F3- AD 61 C0 LDA $C061 08F6- 30 03 BMI $08FB 08F8- 4C 00 9B JMP $9B00 08FB- 4C B4 9B JMP $9BB4 08FE- 00 BRK 08FF- 00 BRK |
Voici donc tout ce qui se trouve entre $801 et $8FF. Ici on a deux choses bien distinctes. Tout d'abord le Fast Boot en lui-même, avec les initialisations, le chargement de sa RWTS et ses appels (les JSR $B600) avec son passage de paramètres particuliers (valeur qui suit le JSR). Et ensuite les modifications effectuées sur ce Fast Boot pour y incorporer le défi :
On repère immédiatement la lecture Clavier rajoutée avec le test de l'appui sur ESC. Si oui la RWTS du Fast Boot charge quelque chose et on saute en $8344. On voit que si on n'appuie sur aucune touche (ou si ce n'est pas ESC), on passe par dessus ce $8344.
Étape 3 : préparation du disque pour nous faciliter la vie.
Puisque nous sommes sous DiskFixer, on va donc modifier immédiatement (sur une copie bien évidemment) quelques petits trucs de façon à travailler de manière plus détendue...
On va déjà supprimer le test du ESC pour avoir systématiquement l'affichage du défi (et nous économiser un peu) : $8E0 : EA EA
Ensuite, on va remplacer le JSR $8344 par un joli BRK : $8E6 : 00
L'objectif ici est de récupérer la main tout de suite avant le lancement (ou supposé être) du défi. On sauvegarde les modifications et on relance Archives.
Comme prévu, on récupère la main après l'écran d'intro. Et on vérifie tout de suite par un $8344G ce que l'on supposait : petit chargement et affichage du défi. OK on est sur la bonne voie !
Étape 4 : $8344
On relance et cette fois au lieu d'exécuter, on désassemble ce qui se trouve en $8344. Comme on a la main, aucun problème. On obtient :
8344- A2 00 LDX #$00 8346- BD 00 02 LDA $0200,X ; sauvegarde du buffer d'entrée 8349- 9D 00 7C STA $7C00,X ; on travaille proprement môsieur ! 834C- E8 INX 834D- D0 F7 BNE $8346 834F- A2 00 LDX #$00 8351- BD 61 83 LDA $8361,X 8354- 49 55 EOR #$55 ; décryptage du code 8356- 9D 00 02 STA $0200,X ; écriture dans le buffer d'entrée 8359- E8 INX 835A- E0 37 CPX #$37 835C- D0 F3 BNE $8351 835E- 4C 00 02 JMP $0200 ; exécution |
Cette routine sert à décrypter une portion de code vers le buffer d'entrée/clavier et à l'exécuter.
Problème, même si on empêche le JMP $0200, on ne pourra pas voir ce qui s'y passe, car en tant que buffer d'entrée, et bien comme son nom l'indique, dès que l'on tape quelque chose au clavier, son contenu est modifié !
Deckard utilise ici une technique courante à l'époque dans la protection des logiciels, en utilisant ce buffer temporairement pour son propre compte.
On va donc modifier ici le STA $0200,X pour que le code décrypté (que l'on veut étudier) soit écrit dans une zone facilement accessible. Disons $2000 (il n'y a pas d'utilisation pour le moment de la page HGR1). Let's go...
On modifie directement en mémoire :
8356 : 9D 00 20 ; STA $2000,X
835E : 00 ; BRK (pour ne pas jumper en $200 évidemment)
8344G ; on lance le décodage
On récupère la main grâce au BRK et on n'a plus qu'à aller voir ce qui a été décrypté en $2000.
Étape 5 : $2000 (ce qui aurait dû être en $200 pour ceux qui ne dorment pas encore)
0200- A9 00 LDA #$00 0202- 85 06 STA $06 0204- A9 80 LDA #$80 0206- 85 07 STA $07 0208- A9 55 LDA #$55 020A- 85 1A STA $1A 020C- 09 CC ORA #$CC 020E- 29 A2 AND #$A2 0210- 8D 36 02 STA $0236 0213- 18 CLC 0214- 6E 34 02 ROR $0234 0217- 38 SEC 0218- 6E 34 02 ROR $0234 021B- 6E 34 02 ROR $0234 021E- A2 00 LDX #$00 0220- A0 00 LDY #$00 0222- B1 06 LDA ($06),Y ; on décode 0224- 45 1A EOR $1A ; par un EOR 0226- 91 06 STA ($06),Y ; ce qui est en $8000-$8300 0228- 85 1A STA $1A 022A- C8 INY 022B- D0 F5 BNE $0222 022D- E6 07 INC $07 022F- E8 INX 0230- E0 04 CPX #$04 0232- D0 EE BNE $0222 0234- 60 RTS ; attention ça va changer ça :) 0235- 00 BRK ; 0236- 00 BRK ; ça aussi |
Attention, ici j'ai remis les adresses de base (c'est à dire $200+) pour faciliter la compréhension du code. Mais nous sommes toujours en fait, en temps réel, en $2000 après détournement de la routine précédente.
Le gros de la routine consiste encore en une phase de décryptage par un EOR. À la limite on s'en fout un peu de la façon dont c'est crypté car ce qui nous intéresse c'est le résultat. Ici la subtilité réside dans la modification du RTS de fin de routine. N'oubliez pas qu'on est censé arriver là par un JMP $200 donc si on rencontrait un vrai RTS en $236, on sortirait tout simplement de l'appel qui a commencé bien plus haut vers $8344 et donc du défi ! Pourtant, on est encore loin d'être arrivé au bout là (si si je vous assure), donc qu'est ce qui se passe ?
Et bien tout simplement, le début de la routine modifie ce RTS pour le transformer en un JMP $8000 ! Évidemment l'ami Deckard s'est fait plaisir et n'a pas utilisé de simples LDA/STA trop vulgaires... non non ! Voyons un peu ça en détails :
0208- A9 55 LDA #$55 ; $55 dans A 020A- 85 1A STA $1A 020C- 09 CC ORA #$CC ; $55 ORA $CC = $DD 020E- 29 A2 AND #$A2 ; $DD AND $A2 = $80 0210- 8D 36 02 STA $0236 ; $80 en $236 0213- 18 CLC ; vide la retenue 0214- 6E 34 02 ROR $0234 ; rotation à droite du $60 (pas de retenue) ; 01100000 devient 00110000 0217- 38 SEC ; retenue à 1 0218- 6E 34 02 ROR $0234 ; nouvelle rotation à droite (avec retenue) ; 00110000 devient 10011000 021B- 6E 34 02 ROR $0234 ; et encore une (pas de retenue) ; 10011000 devient 01001100 = $4C ! |
Et voilà comment 60 00 00 devient 4C 00 80 soit un RTS qui devient un JMP $8000 !
La routine après décryptage saute donc en $8000 (logique puisque c'est justement ce qui aura été décrypté). Là encore, il faut intercepter ce dernier JMP pour avoir la main après le décryptage du code. Le plus simple c'est tout simplement de noper les 3 ROR $0234. Le $60 n'étant plus modifié nous rendra alors la main à la fin de la routine.
$2014 : EA EA EA
$2018 : EA EA EA
$201B : EA EA EA
$2000G
On peut aussi juste mettre un $2C (BIT) en $2014, $2018 et $201B. C'est moins long à taper et ça fait plus l33t ! Voyons voir le résultat en $8000...
Étape 6 : $8000 - Les choses sérieuses commencent...
8000- A9 6E LDA #$6E 8002- 85 27 STA $27 ; buffer Hi 8004- A9 00 LDA #$00 8006- 85 26 STA $26 8008- A9 22 LDA #$22 ; 800A- A2 00 LDX #$00 800C- A0 0C LDY #$0C ; 800E- 85 41 STA $41 ; piste ($22) 8010- 86 FE STX $FE ; secteur 8012- 84 FF STY $FF ; nb Secteurs à charger ($0C) 8014- 2C E9 C0 BIT $C0E9 ; drive on 8017- 20 ED 80 JSR $80ED ; temporisation 801A- A2 60 LDX #$60 801C- BD 8E C0 LDA $C08E,X ; lecture on 801F- A9 D0 LDA #$D0 8021- A2 E4 LDX #$E4 8023- A0 60 LDY #$60 8025- 20 94 80 JSR $8094 ; autopatch1 8028- A9 60 LDA #$60 802A- 8D 14 81 STA $8114 ; RTS en 8114 802D- 20 24 81 JSR $8124 ; check headers address 8030- 20 FB 80 JSR $80FB ; on récupère la piste courante 8033- 8D 9E 80 STA $809E ; sauvegarde 8036- 0A ASL 8037- 8D 78 04 STA $0478 ; piste actuelle 803A- A9 EA LDA #$EA 803C- 8D 14 81 STA $8114 ; NOP en 8114 803F- AA TAX 8040- A8 TAY 8041- 20 94 80 JSR $8094 ; NOP en 8140/41/42 (autopatch2) ======================================= routine / boucle principale ======================================= 8044- A4 FF LDY $FF ; $0C + 8046- A5 27 LDA $27 ; $6E 8048- 18 CLC 8049- 65 FF ADC $FF ; = $7A 804B- 85 27 STA $27 804D- E6 27 INC $27 ; = $7B = buffer (Hi) (fin) 804F- 88 DEY 8050- D0 06 BNE $8058 8052- 20 AF 80 JSR $80AF ; positionnement sur piste 8055- 4C 6C 80 JMP $806C ; lecture 8058- E6 FE INC $FE ; on positionne le secteur de départ ; (00 + nbSecteurs à lire - 1) 805A- A5 FE LDA $FE 805C- C9 10 CMP #$10 ; > 15 805E- D0 06 BNE $8066 8060- A9 00 LDA #$00 8062- 85 FE STA $FE 8064- E6 41 INC $41 ; piste suivante (si besoin) 8066- 88 DEY ; 8067- D0 EF BNE $8058 8069- 4C 52 80 JMP $8052 806C- A4 FE LDY $FE 806E- B9 9F 80 LDA $809F,Y ; table secteur dos 3.3/physique 8071- 85 3D STA $3D ; secteur physique 8073- 20 22 81 JSR $8122 ; Lecture data 8076- C6 FE DEC $FE 8078- 10 06 BPL $8080 807A- A9 0F LDA #$0F 807C- 85 FE STA $FE 807E- C6 41 DEC $41 8080- C6 27 DEC $27 8082- C6 FF DEC $FF ; nb secteur à lire - 1 8084- D0 CC BNE $8052 ; il en reste ? 8086- AD 9E 80 LDA $809E ; piste sauvée à l'entrée de la routine 8089- 85 41 STA $41 ; 808B- 20 AF 80 JSR $80AF ; on se repositionne dessus ! 808E- 2C E8 C0 BIT $C0E8 ; arrêt drive 8091- 4C 98 81 JMP $8198 ; suite 8094- 8D 40 81 STA $8140 8097- 8E 41 81 STX $8141 809A- 8C 42 81 STY $8142 809D- 60 RTS 809E- 00 BRK ======================================= Table Dos 3.3 Sector -> Physical Sector ======================================= 809F- 00 BRK 80A0- 0D 0B 09 ORA $090B 80A3- 07 ??? 80A4- 05 03 ORA $03 80A6- 01 0E ORA ($0E,X) 80A8- 0C ??? 80A9- 0A ASL 80AA- 08 PHP 80AB- 06 04 ASL $04 80AD- 02 ??? 80AE- 0F ??? ======================================= routine de déplacement tête lecture ======================================= 80AF- A2 60 LDX #$60 80B1- A5 41 LDA $41 ; piste destination 80B3- 0A ASL ; 80B4- 85 43 STA $43 ; dans $43 80B6- AD 78 04 LDA $0478 ; piste actuelle 80B9- 85 FD STA $FD 80BB- 38 SEC 80BC- E5 43 SBC $43 ; piste destination 80BE- F0 2C BEQ $80EC 80C0- B0 05 BCS $80C7 80C2- EE 78 04 INC $0478 80C5- 90 03 BCC $80CA 80C7- CE 78 04 DEC $0478 80CA- 20 E0 80 JSR $80E0 80CD- 20 ED 80 JSR $80ED ; tempo 80D0- A5 FD LDA $FD ; piste actuelle 80D2- 29 03 AND #$03 80D4- 0A ASL 80D5- 09 60 ORA #$60 80D7- A8 TAY 80D8- B9 80 C0 LDA $C080,Y 80DB- 20 ED 80 JSR $80ED ; tempo 80DE- F0 D6 BEQ $80B6 80E0- AD 78 04 LDA $0478 80E3- 29 03 AND #$03 80E5- 0A ASL 80E6- 09 60 ORA #$60 80E8- A8 TAY 80E9- B9 81 C0 LDA $C081,Y 80EC- 60 RTS ======================================= routine de temporisation ======================================= 80ED- A9 28 LDA #$28 80EF- 38 SEC 80F0- 48 PHA 80F1- E9 01 SBC #$01 80F3- D0 FC BNE $80F1 80F5- 68 PLA 80F6- E9 01 SBC #$01 80F8- D0 F6 BNE $80F0 80FA- 60 RTS ======================================= routine lecture et décodage 4:4 nibbles address ======================================= 80FB- A0 02 LDY #$02 ; 2 passages (param : volume/piste) 80FD- 2C *A0 03 BIT $03A0 ; dummy code (BIT) 80FE- *A0 03 LDY #$03 ; 3 passages (param : volume/piste/secteur) 8100- 85 40 STA $40 ; (avant dernier param) 8102- BD 8C C0 LDA $C08C,X ; 8105- 10 FB BPL $8102 8107- 2A ROL 8108- 85 3C STA $3C 810A- BD 8C C0 LDA $C08C,X ; 810D- 10 FB BPL $810A 810F- 25 3C AND $3C 8111- 88 DEY 8112- D0 EC BNE $8100 8114- 60 RTS ; <= modifié (EA) ======================================= 8115- 28 PLP 8116- C5 3D CMP $3D ; est-ce le secteur voulu ? 8118- D0 08 BNE $8122 ; non on passe au secteur suivant 811A- A5 40 LDA $40 ; est-ce qu'on est sur la bonne piste ? 811C- C5 41 CMP $41 ; 811E- D0 02 BNE $8122 ; non 8120- B0 01 BCS $8123 ======================================= routine de lecture Disk bas niveau ======================================= 8122- 18 CLC ; set carry (pour que la boucle se fasse plus bas) 8123- 08 PHP ======================================= 8124- A2 60 LDX #$60 8126- BD 8C C0 LDA $C08C,X 8129- 10 FB BPL $8126 812B- 49 D5 EOR #$D5 812D- D0 F7 BNE $8126 812F- BD 8C C0 LDA $C08C,X 8132- 10 FB BPL $812F 8134- C9 AA CMP #$AA 8136- D0 F3 BNE $812B 8138- EA NOP 8139- BD 8C C0 LDA $C08C,X 813C- 10 FB BPL $8139 813E- C9 96 CMP #$96 8140- D0 E4 BNE $8126 ; modifiés 8142- 60 RTS ; " 8143- F0 B9 BEQ $80FE ; décodage données adresses 8145- 28 PLP 8146- 90 DA BCC $8122 ; boucle si Carry Set ! (check headers address) 8148- 49 AD EOR #$AD ; 3eme header champ data 814A- F0 02 BEQ $814E 814C- D0 D4 BNE $8122 --------------------------------------- ; Post "nibblelisation" 6:2 814E- A0 56 LDY #$56 8150- 84 3C STY $3C 8152- BC 8C C0 LDY $C08C,X 8155- 10 FB BPL $8152 8157- 59 FE 81 EOR $81FE,Y ; Table 815A- A4 3C LDY $3C 815C- 88 DEY 815D- 99 28 82 STA $8228,Y ; Buffer secondaire 8160- D0 EE BNE $8150 8162- 84 3C STY $3C 8164- BC 8C C0 LDY $C08C,X 8167- 10 FB BPL $8164 8169- 59 FE 81 EOR $81FE,Y 816C- A4 3C LDY $3C 816E- 91 26 STA ($26),Y ; Buffer primaire 8170- C8 INY 8171- D0 EF BNE $8162 8173- BC 8C C0 LDY $C08C,X 8176- 10 FB BPL $8173 8178- 59 FE 81 EOR $81FE,Y 817B- D0 A5 BNE $8122 817D- A0 00 LDY #$00 817F- A2 56 LDX #$56 8181- CA DEX 8182- 30 FB BMI $817F 8184- 84 1A STY $1A 8186- B1 26 LDA ($26),Y 8188- 5E 28 82 LSR $8228,X 818B- 2A ROL 818C- 5E 28 82 LSR $8228,X 818F- 2A ROL 8190- 45 1A EOR $1A ; <== ohohoho ! 8192- 91 26 STA ($26),Y 8194- C8 INY 8195- D0 EA BNE $8181 8197- 60 RTS ======================================= routine de décodage "type" 4:4 7000 et 7600 => 7000 ======================================= 8198- A9 00 LDA #$00 819A- 85 EC STA $EC 819C- A9 60 LDA #$60 819E- 85 ED STA $ED 81A0- A9 00 LDA #$00 81A2- 85 EE STA $EE 81A4- A9 63 LDA #$63 81A6- 85 EF STA $EF 81A8- A9 00 LDA #$00 81AA- 85 06 STA $06 81AC- A9 70 LDA #$70 81AE- 85 07 STA $07 81B0- A9 00 LDA #$00 81B2- 85 08 STA $08 81B4- A9 76 LDA #$76 81B6- 85 09 STA $09 81B8- A2 00 LDX #$00 81BA- A0 00 LDY #$00 81BC- B1 06 LDA ($06),Y ; $7000 81BE- 38 SEC 81BF- 2A ROL 81C0- 85 1A STA $1A 81C2- B1 08 LDA ($08),Y ; $7600 81C4- 25 1A AND $1A 81C6- 91 06 STA ($06),Y ; $7000 81C8- C8 INY 81C9- D0 F1 BNE $81BC 81CB- E6 07 INC $07 81CD- E6 09 INC $09 81CF- E8 INX 81D0- E0 06 CPX #$06 81D2- D0 E8 BNE $81BC ======================================= routine qui dispatche un octet sur 2 depuis 7000 jusqu'à 7600 vers 6000+ et 6300+ ======================================= 81D4- A9 02 LDA #$02 81D6- 85 1A STA $1A 81D8- 20 F2 81 JSR $81F2 81DB- E6 ED INC $ED 81DD- E6 EF INC $EF 81DF- EE 05 82 INC $8205 81E2- EE 0B 82 INC $820B 81E5- 20 F2 81 JSR $81F2 81E8- C6 1A DEC $1A 81EA- D0 EF BNE $81DB 81EC- 20 00 60 JSR $6000 ; vers décodage texte etc. 81EF- 4C 30 83 JMP $8330 ; sortie 81F2- A0 00 LDY #$00 81F4- 20 01 82 JSR $8201 81F7- EE 05 82 INC $8205 81FA- EE 0B 82 INC $820B 81FD- 20 01 82 JSR $8201 8200- 60 RTS 8201- A2 00 LDX #$00 8203- BD 00 70 LDA $7000,X 8206- 91 EC STA ($EC),Y ; $6000 8208- E8 INX 8209- BD 00 70 LDA $7000,X 820C- 91 EE STA ($EE),Y ; $6300 820E- C8 INY 820F- E8 INX 8210- D0 F1 BNE $8203 8212- 60 RTS ======================================= 8213- AF ??? 8214- C4 C5 CPY $C5 8216- C3 ??? 8217- CB ??? 8218- C1 D2 CMP ($D2,X) 821A- C4 AF CPY $AF 821C- CC CF C1 CPY $C1CF 821F- C4 C5 CPY $C5 8221- D2 ??? 8222- C3 ??? 8223- B6 B0 LDX $B0,Y 8225- B0 A3 BCS $81CA 8227- B2 ??? 8228- 00 BRK 8229- 00 BRK 822A- 00 BRK 822B- 00 BRK 822C- 00 BRK 822D- 00 BRK ======================================= data pour la routine Move ($FE2C) ======================================= 8328- FD FA 1E SBC $1EFA,X 832B- FE 60 FE INC $FE60,X 832E- 00 BRK 832F- 80 ??? ======================================= routine de fin - "nettoyage" ======================================= 8330- A2 00 LDX #$00 8332- BD 28 83 LDA $8328,X 8335- 95 3C STA $3C,X 8337- E8 INX 8338- E0 08 CPX #$08 833A- D0 F6 BNE $8332 833C- A0 00 LDY #$00 833E- 20 2C FE JSR $FE2C ; Move vers $8000 8341- 4C 09 61 JMP $6109 ; fin + sortie "défi" |
Et oui ça pique un peu ! On se retrouve ici en fait face à une des deux routines principales du défi !
Et c'est carrément une routine complète de lecture bas niveau de Disk à laquelle on a affaire. Avec déplacement de la tête de lecture, table de conversion secteurs physiques/secteurs logiques, lecture de nibbles, et enfin post-nibblelisation ! Plus quelques petites routines de déplacements de données et de décodages histoire de ne pas avoir fait le voyage jusqu'ici pour rien...
Ce que fait ce code :
- lecture de 12 secteurs à partir du secteur $00 de la piste $22 vers un buffer situé en $7000. En fait plus exactement, la routine lit les secteurs de façon décroissante vers un buffer décroissant lui aussi. Ce qui revient au même...
- EOR effectué entre chaque octet lu et la valeur de sa position dans le secteur (exemple : le premier octet de chaque secteur sera EORé avec $00, le suivant avec $01, etc.). À noter que ce EOR est intégré à la routine de lecture même (en $8190).
- décodage des données qui ont été lues : c'est un décodage de type 4:4 (un octet final étant codé sur 2 octets) "réunissant" les données en $7000+ et $7600+, le tout remis en $7000+.
($7000/7600->$7000, $7001/$7601->$7001,$7002/$7602->$7002 etc.). - Les données en $7000-$7600 ainsi obtenues sont dispatchées un octet sur deux entre $6000+ et $6300+. ($7000->$6000, $7001->$6300, $7002->$6001,$7003->$6301, etc.).
- la routine fait un appel en $6000 ($81EC : JSR $6000) qui est la routine de décodage du texte en lui-même.
- au retour, un peu de nettoyage est fait (utilisation de la routine Monitor Move) et on JuMP en $6109 pour la suite et fin du défi (affichage).
- c'est tout ! (comment ça bobo tête ? !)
Voilà pour les grandes lignes, avec quelques petites choses à noter en plus :
Deckard a optimisé son code au maximum. Celui-ci s'autopatche afin de modifier certaines parties qui seront ainsi réutilisées plus tard. Cela rend le suivi des évènements un peu alambiqué certes mais c'est particulièrement bien foutu il faut avouer.
Autre astuce utilisée, le coup du dummy code (utilisation de l'opcode BIT). Exemple :
80FB- A0 02 LDY #$02 80FD- 2C *A0 03 BIT $03A0 8100- 85 40 STA $40 *80FE- A0 03 LDY #$03 8100- 85 40 STA $40 |
Quand on passe la première fois sur le BIT $03A0 en $80FD, l'instruction ne change rien et on continue normalement (on a donc $02 dans Y). Par contre si on arrive en $80FE par un saut, on tombe sur un A0 03 soit LDY #$03 ! Et le code continue de la même façon. La même routine est utilisée, mais suivant où on arrive, Y en sortira avec une valeur différente, le tout sans JMP supplémentaire !
J'ai essayé de détailler et de commenter l'ensemble du code le plus possible. N'hésitez donc pas à y jeter un œil attentif !
Il est temps maintenant de passer à la suite... C'est à dire, de voir un peu ce qui se passe en $6000.
On place donc un BRK en $81EC pour shunter le JSR et on lance la routine ($8000G) pour effectuer la lecture de la suite et son décodage.
Étape 7 : $6000 - This is the End...
Voici donc la dernière partie du défi. Tout ce qui se trouve en $6000+ et qui aura été directement lu/décodé depuis la partie précédente :
6000- A9 A7 LDA #$A7 6002- 85 06 STA $06 6004- A9 61 LDA #$61 6006- 85 07 STA $07 6008- A9 00 LDA #$00 600A- 85 08 STA $08 600C- A9 65 LDA #$65 600E- 85 09 STA $09 6010- A9 80 LDA #$80 6012- 85 1A STA $1A ; valeur ORA 6014- A9 00 LDA #$00 6016- 85 ED STA $ED ; index "codage" 6018- 85 EF STA $EF ; index source 601A- 85 EE STA $EE ; index destination 601C- A8 TAY 601D- 20 64 60 JSR $6064 ; retourne $EC 6020- A5 EC LDA $EC ; 6022- F0 19 BEQ $603D ; si 00 changement de codage 6024- 84 EF STY $EF ; sauvegarde index Buffer source 6026- A4 EE LDY $EE ; index Y destination 6028- A6 EC LDX $EC ; index X table 602A- BD BD 60 LDA $60BD,X ; table 602D- 05 1A ORA $1A ; attribut affichage 602F- 91 08 STA ($08),Y ; $6500+ destination finale (texte décodé) 6031- C8 INY 6032- D0 02 BNE $6036 6034- E6 09 INC $09 6036- 84 EE STY $EE 6038- A4 EF LDY $EF ; index Y source 603A- 4C 1D 60 JMP $601D ======================================= changement de la valeur de $1A (fixe les 3 derniers bits / attribut) ======================================= 603D- 20 64 60 JSR $6064 6040- A5 EC LDA $EC 6042- F0 19 BEQ $605D ; quit si second 00 6044- 4A LSR 6045- B0 09 BCS $6050 ; $EC = $xxxxxxx1 6047- 4A LSR 6048- B0 09 BCS $6053 ; $EC = $xxxxxx10 604A- 4A LSR 604B- B0 09 BCS $6056 ; $EC = $xxxxx100 604D- 90 0A BCC $6059 ; tout le reste 604F- 2C *A9 80 BIT $80A9 6050- *A9 80 LDA #$80 ; 10000000 ; 6052- 2C *A9 00 BIT $00A9 6053- *A9 00 LDA #$00 ; 00000000 ; 6055- 2C *A9 40 BIT $40A9 6056- *A9 40 LDA #$40 ; 01000000 ; 6058- 2C *A9 E0 BIT $E0A9 6059- *A9 E0 LDA #$E0 ; 11100000 ; 605B- 2C A9 *60 BIT $60A9 605D- *60 RTS 605E- 85 1A STA $1A 6060- 4C 1D 60 JMP $601D 6063- 2C ??? ======================================= renvoie l'index de table ($EC) depuis le buffer $61A7 (codé) + incrémente index type codage ======================================= 6064- A5 ED LDA $ED ; compteur type codage (0-3) 6066- F0 18 BEQ $6080 6068- C9 01 CMP #$01 606A- F0 1D BEQ $6089 606C- C9 02 CMP #$02 606E- F0 36 BEQ $60A6 6070- B1 06 LDA ($06),Y ; type 3 6072- 29 3F AND #$3F ; 00111111 => 00xxxxxxxx 6074- 85 EC STA $EC 6076- C8 INY 6077- D0 02 BNE $607B 6079- E6 07 INC $07 607B- A9 00 LDA #$00 607D- 85 ED STA $ED 607F- 60 RTS 6080- B1 06 LDA ($06),Y ; type 0 6082- 4A LSR ; 0xxxxxxx 6083- 4A LSR ; 00xxxxxx 6084- 85 EC STA $EC 6086- E6 ED INC $ED 6088- 60 RTS 6089- B1 06 LDA ($06),Y ; type 1 608B- 29 03 AND #$03 ; 00000011 => 000000xx 608D- 0A ASL ; 00000xx0 608E- 0A ASL ; 0000xx00 608F- 0A ASL ; 000xx000 6090- 0A ASL ; 00xx0000 (1) 6091- 85 EC STA $EC 6093- C8 INY 6094- D0 02 BNE $6098 6096- E6 07 INC $07 6098- B1 06 LDA ($06),Y ; xxxxxxxx 609A- 4A LSR ; 0xxxxxxx 609B- 4A LSR ; 00xxxxxx 609C- 4A LSR ; 000xxxxx 609D- 4A LSR ; 0000xxxx (2) 609E- 18 CLC 609F- 65 EC ADC $EC 60A1- 85 EC STA $EC (1)+(2)/(3)+(4) = 00xxxxxx 60A3- E6 ED INC $ED 60A5- 60 RTS 60A6- B1 06 LDA ($06),Y ; type 2 60A8- 29 0F AND #$0F ; 00001111 => 0000xxxx 60AA- 0A ASL ; 000xxxx0 60AB- 0A ASL ; 00xxxx00 (3) 60AC- 85 EC STA $EC 60AE- C8 INY 60AF- D0 02 BNE $60B3 60B1- E6 07 INC $07 60B3- B1 06 LDA ($06),Y 60B5- 29 C0 AND #$C0 ; 11000000 => xx000000 60B7- 18 CLC ; 60B8- 2A ROL ; x0000000 60B9- 2A ROL ; 0000000x 60BA- 2A ROL ; 000000xx (4) 60BB- 90 E2 BCC $609F ======================================= 60BD- 00 BRK ======================================= Table décryptage Texte (caractères) ======================================= 60BE- 10 35 BPL $60F5 60C0- 17 ??? 60C1- 14 ??? 60C2- 32 ??? 60C3- 08 PHP 60C4- 02 ??? 60C5- 2B ??? 60C6- 15 0B ORA $0B,X 60C8- 1B ??? 60C9- 01 3E ORA ($3E,X) 60CB- 27 ??? 60CC- 09 07 ORA #$07 60CE- 39 3A 05 AND $053A,Y 60D1- 21 11 AND ($11,X) 60D3- 3F ??? 60D4- 12 ??? 60D5- 1A ??? 60D6- 30 3B BMI $6113 60D8- 3C ??? 60D9- 33 ??? 60DA- 23 ??? 60DB- 31 5D AND ($5D),Y 60DD- 22 ??? 60DE- 19 25 0D ORA $0D25,Y 60E1- 06 38 ASL $38 60E3- 1F ??? 60E4- 0A ASL 60E5- 1C ??? 60E6- 0C ??? 60E7- 29 2A AND #$2A 60E9- 37 ??? 60EA- 2E 36 04 ROL $0436 60ED- 0E 0F 1D ASL $1D0F 60F0- 24 2D BIT $2D 60F2- 18 CLC 60F3- 2F ??? 60F4- 3D 20 34 AND $3420,X 60F7- 03 ??? 60F8- 2C 13 28 BIT $2813 60FB- 16 5B ASL $5B,X 60FD- C4 C3 CPY $C3 60FF- CB ??? 6100- C4 ??? ======================================= Paramètres pour le Move Monitor ($FE2C) ======================================= 6101- FD ??? 6102- FB ??? 6103- 5F ??? 6104- FD 60 FE SBC $FE60,X 6107- 00 BRK 6108- 60 RTS ======================================= routine de fin / sortie / nettoyage + Affichage du texte à l'écran ======================================= 6109- A2 00 LDX #$00 610B- BD F6 FE LDA $FEF6,X 610E- 9D 00 60 STA $6000,X ; écrasement routine 6111- E8 INX 6112- D0 F7 BNE $610B 6114- 20 58 FC JSR $FC58 ; Home 6117- A9 00 LDA #$00 6119- 85 08 STA $08 611B- A9 65 LDA #$65 611D- 85 09 STA $09 611F- A0 00 LDY #$00 6121- 84 ED STY $ED 6123- 84 EC STY $EC 6125- A2 00 LDX #$00 6127- B1 08 LDA ($08),Y ; $6500+ vers 6129- 9D D0 07 STA $07D0,X ; dernière ligne Page 1 Text 612C- A9 00 LDA #$00 612E- 91 08 STA ($08),Y ; nettoyage 6130- 99 A7 61 STA $61A7,Y ; bis 6133- C8 INY 6134- D0 02 BNE $6138 6136- E6 09 INC $09 6138- E8 INX 6139- E0 28 CPX #$28 ; toutes les colonnes ? (40) 613B- D0 EA BNE $6127 613D- 84 ED STY $ED 613F- E6 EC INC $EC 6141- A4 EC LDY $EC 6143- C0 18 CPY #$18 ; toutes les lignes ? (24) 6145- F0 0B BEQ $6152 6147- 20 70 FC JSR $FC70 ; scroll vers le haut 614A- 2C 30 C0 BIT $C030 ; son 614D- A4 ED LDY $ED 614F- 4C 25 61 JMP $6125 6152- A2 00 LDX #$00 6154- BD 01 61 LDA $6101,X ; chargement param pour le Move 6157- 95 3C STA $3C,X 6159- E8 INX 615A- E0 08 CPX #$08 615C- D0 F6 BNE $6154 615E- A0 00 LDY #$00 6160- 20 2C FE JSR $FE2C ; Move ($FBFD-$FD5F) -> $6000 6163- 2C 10 C0 BIT $C010 ; initialisation clavier 6166- A9 00 LDA #$00 6168- 8D 00 C0 STA $C000 ; test clavier 616B- AD 00 C0 LDA $C000 616E- 10 FB BPL $616B 6170- A2 00 LDX #$00 6172- BD 00 7C LDA $7C00,X ; on restaure le buffer clavier 6175- 9D 00 02 STA $0200,X 6178- E8 INX 6179- D0 F7 BNE $6172 617B- 60 RTS ; voilà c'est fini, on sort ! ======================================= |
Deux parties distinctes ici :
- en $6000+, le décodage du texte en lui-même (oui on y arrive enfin !).
- en $6109+, l'affichage par un scrolling (routine monitor) du texte ligne par ligne avec le petit effet sonore qui va bien. Et encore un peu de nettoyage (grosso modo, on écrase les routines de décodage) avant de sortir définitivement du défi et de continuer le boot du disk.
On va donc s'attarder sur la partie qui nous intéresse directement, à savoir le décryptage du texte.
Ce que fait la routine en $6000+ :
- génère une valeur d'index à partir des données du buffer source ($61A7+) en utilisant des AND (pour isoler les bits) et des décallages/rotations (pour les positionner). Chaque valeur index est codée sur 6 bits. L’algorithme utilise un système de codage "tournant" :
- le 1er index généré utilise les 6 bits forts d'un même octet source (xxxxxxyy) après décalage ce qui donne : 00xxxxxx (1er index).
- le second index généré utilise les bits 0 et 1 de l'octet source précédent (xxxxxxyy) et les 4 bits forts de l'octet source suivant (zzzzwwww) ce qui donne (après décalage et addition) : 00yyzzzz (2nd index).
- le troisième index généré utilise les 4 bits faibles non utilisés de l'octet source (zzzzwwww) et les deux bits forts (6 et 7) de l'octet source suivant (vvuuuuuu) ce qui donne (encore une fois après décalage et addition) : 00wwwwvv (3ème index).
- le quatrième index généré utilise les 6 bits faibles restants de l'octet source précédent (vvuuuuuu) ce qui donne, après décalage : 00uuuuuu (4ème index).
- l'index suivant est généré de manière identique au premier (6 bits forts de l'octet source suivant) et ainsi de suite...
- une fois cet index généré, il est utilisé pour récupérer le caractère correspondant dans la table de décryptage (que l'on trouve à partir de $60BE).
- le caractère retourné subit un ORA avec une valeur attribut (par défaut $80) qui fixe les bits 7-6-5 du caractère. On peut ainsi avoir un affichage clignotant, inverse ou normal.
- pour modifier cet attribut, il faut que l'index retourné soit à zéro (qui sert ainsi de marqueur). L'algo récupère alors l'index suivant et l'utilise pour modifier en conséquence la valeur attribut. Si on veut être puriste, on devrait même dire que ce ne sont plus vraiment des index dans ce cas en fait...
- La caractère "généré" est sauvé dans le buffer destination ($6500+). L'affichage ligne à ligne se fera à partir de ce buffer dans la routine à partir de $6109.
À noter que Deckard utilise ici encore l'astuce de l'opcode BIT vue plus haut pour éviter de surcharger son code avec des JMP et des sauts lourdingues. On remarquera aussi le soin tout particulier apporté en fin de routine pour écraser les routines et pour effacer les buffers source et destination utilisés. Afin de laisser le moins de traces possibles je suppose en cas d'interruption du programme par un ctrl+reset à l'arrache...
Voilà c'est fini pour cette première partie. Il reste maintenant à digérer tout ça pour se lancer concrètement dans la résolution du défi, c'est à dire aborder la partie programmation des routines qui nous permettront de recrypter le texte signé ! Et ensuite replacer le tout au bon endroit sur le disque original !
À suivre...