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