Et un nouveau défi musical, un ! Il se trouve, cette fois, sur le disque anniversaire diffusé début 1987 par QTIPS pour fêter sa première année de participation active sur la scène Apple II. On y retrouve donc un petit aperçu de ses multiples contributions, démontrant s'il était besoin, que QTIPS a été un acteur majeur de l'underground FR de cette époque. Quant au défi en lui-même, toujours le même principe : extraire la musique et en faire un fichier exécutable le plus petit possible et autonome. Un grand merci, au passage, à Antoine qui m'a permis de récupérer l'image disque de cette production. Il aurait été dommage de passer à côté...
Pour commencer un défi, je ne le répéterai jamais assez : il faut observer ! La chose la plus flagrante ici, c'est que l'on retrouve un lecteur de fichier texte (donc après l'intro musicale) qui ressemble grandement à celui de Miami Sound Machine Maker. Et on se souvient que celui-ci était écrit en BASIC. On peut donc essayer d'office de faire un petit CTRL+C lors du boot pour voir ce que cela donne (après l’apparition du texte HAPPY BIRTHDAY QTIPS par exemple). Et presque sans surprise, on voit que cela fonctionne car un beau BREAK surgit à l'écran.
Juste une illusion...
Voici ce qu'on obtient si on fait un LIST 1-10. Il y a bien une ligne 0 mais qui apparemment génère uniquement un tas de sauts de ligne, histoire de bien nous pourrir la vie. Bref on la zappe...
Cette fois, pas de BLOAD PLAYER ou autre... On voit que le lecteur texte, qui commence dès la ligne 4 devrait donc être logiquement précédé de la musique. Si on fait un RUN, on a effectivement affichage de la page graphique et la musique... OK, donc en 3 lignes dont une de REM, on doit avoir tout ça... On sent déjà qu'il y a quelque chose qui ne colle pas... On remarque aussi les " :" au début des lignes 2, 3 et 4. Bref il y a un truc de pas net dans ce listing. Une seule solution, passer sous Monitor et regarder ce qu'il y a vraiment en mémoire. Pour info, je rappelle qu'un programme BASIC est (normalement) en mémoire à partir de $801 (on peut le vérifier par le contenu des adresses en page zéro $67 et $68).
Voici décortiqué pour vous ce qui se passe réellement quand on lance le programme par RUN pour chacune des 4 premières lignes. Ouvrez bien les yeux car l'essentiel du défi est là.
Ligne 0 :
0801- F1 08 ; adresse ligne suivante (=$8F1) 0803- 00 00 ; numéro basic de la ligne = 00 0805- B2 ; TOKEN commande REM 0806- 0A ; CTRL+J 0807- 0A ; soit déplacement du curseur 0808- 0A ; vers le bas d'où le 0809- 0A ; scrolling vers le haut de 080A- 0A ; l'écran qui se remplit 080B- 0A ; de vide 080C- 0A ; 080D- 0A ; oui on va couper 080E- 0A ; parce qu'il y en a 080F- 0A ; pour long... ... ... 08ED- 0A 08EE- 0A 08EF- 0A 08F0- 00 ; fin de la ligne 0 ! |
La Ligne 0 ne fait rien d'autre que de nous pourrir un peu l'écran en utilisant un CTRL+J (déplacement du curseur vers le bas) répété maintes et maintes fois... Next !
Ligne 01 :
08F1- 0D 09 ; adresse ligne suivante (=$90D) 08F2- 01 00 ; numéro basic de la ligne = 01 08F5- B9 ; TOKEN commande POKE 08F6- 32 31 34 ; "214" 08F9- 2C ; "," 08FA- 31 32 38 ; "128" 08FD- 3A ; ":" 08FE- B9 ; TOKEN commande POKE 08FF- 31 30 31 32 ; "1012" 0903- 2C ; "," 0904- 30 ; "0" 0905- 3A ; ":" 0906- A5 ; TOKEN commande ONERR 0907- AB ; TOKEN commande GOTO 0908- 36 30 30 30 ; "6000" 090C- 00 ; fin de la ligne 1 |
Ici aucune surprise ni fourberie, la ligne fait exactement ce qui apparaît dans le listing. Pour info, le POKE 214,128 transforme toute commande direct (comme LIST) en RUN ! C'est à dire qu'une fois exécuté, vous ne pourrez plus lister le programme BASIC par exemple car il y aura re-run automatique (et plantage ici). Quant au POKE 1012,0 c'est le RESET VECTOR. En le forçant à zéro, il y aura reboot systématique en cas de CTRL-POMME-RESET. Bref des mesures de rétorsion pour les plus bourrins ! La ligne 6000, elle, contient un simple RESUME.
Pour ceux qui découvrent l'envers du décor d'un programme BASIC, on voit que c'est un mélange de commandes sous forme de TOKEN (codé sur un octet) et d'ASCII pour les paramètres de la commande. Allez on continue !
Ligne 02 :
090D- 62 09 ; adresse ligne suivante (=$962) 090F- 02 00 ; numéro basic de la ligne = 02 0911- B0 ; TOKEN commande GOSUB 0912- 37 30 30 30 ; "7000" 0916- 3A ; ":" 0917- B2 ; TOKEN commande REM 0918- 20 ; ESPACE 0919- 08 ; CTRL+H (déplacement gauche) ... ; et ça continue ... ; encore et 092A- 08 ; encore 092B- 3A ; ":" 092C- 52 45 4D ; "REM" (attention ce n'est pas un TOKEN) 092F- 20 ; ESPACE 0930- 2A 2A 2A ; "***" 0933- 20 56 4F 0936- 49 43 0938- 49 20 ; ici on a le message que l'on peut 093A- 4C 45 20 093D- 44 093E- 45 42 0940- 55 54 ; voir dans le listing BASIC 0942- 2C 42 41 0945- 4E 44 45 0948- 20 44 45 094B- 20 50 45 094E- 54 094F- 49 54 ; pas la peine de le re-écrire... 0951- 53 0952- 20 56 4F ; vous pouvez le voir directement sur 0955- 59 4F 55 0958- 53 ; l'image juste au dessus... 0959- 20 21 21 095C- 21 20 095E- 2A 2A 2A ; "***" 0961- 00 ; fin de la ligne 2 |
Ici, on entre dans le vif du sujet avec le royaume du paraître. En effet, alors que la ligne BASIC ne semble contenir qu'un simple REM (avec petit message de la part de QTIPS), il s'avère en fait que ce REM n'existe pas ! Il n'est pas sous forme de TOKEN, c'est juste un message texte. Applesoft ne le reconnaît donc pas. L'essentiel est planqué "derrière" les nombreux CTRL+H qui masquent en fait le vrai listing à l'écran. Le but est de camoufler le GOSUB 7000, unique commande exécutée de la ligne ! Et que trouvons-nous en 7000 ?
7000 POKE 2406,140 ; le contenu de $966 devient $8C. 7001 RETURN ; $966 concerne directement la mémoire BASIC de la ligne 3 |
Ligne 03 :
0962- A4 09 ; adresse mémoire ligne suivante (=$9A4) 0964- 03 00 ; numéro de ligne BASIC =03 0966- B9 -> 8C ; le TOKEN POKE devient donc un TOKEN CALL ! 0967- 31 36 35 37 36; "16576" 096C- 3A ; ":" 096D- B2 ; TOKEN REM 096E- 20 ; ESPACE 096F- 08 ; et encore du CTRL+H (<-) 0970- 08 ; pour camoufler le CALL d'au-dessus ... ... 097F- 08 0980- 08 0981- 3A ; ":" 0982- 50 4F 4B 45 ; "POKE" 0986- 20 ; ESPACE 0987- 33 32 37 36 39; "32769" 098C- 2C ; "," 098D- 50 45 45 4B ; "PEEK" 0991- 28 ; "(" 0992- 36 34 36 39 31; "64691" 0997- 29 ; ")" 0998- 3A ; ":" 0999- 43 41 4C 4C ; "CALL" 099D- 20 ; ESPACE 099E- 33 32 37 36 39; "32769" 09A3- 00 ; fin de ligne |
Même principe ici. Les CTRL+H camouflent le CALL 16576. Tout le reste n'est que leurre, ce sont de fausses instructions sous forme ASCII (et non pas des commandes TOKEN). Comme on l'a vu précédemment, comble du luxe, QTIPS a même pris la peine de faire modifier le POKE en CALL par le GOSUB 7000 précédent...
Ligne 04 :
09A4- B1 09 ; adresse ligne suivante (=$9B1) 09A6- 04 00 ; ligne basic 04 09A8- 3A ; ":" 09A9- 86 ; TOKEN commande DIM 09AA- 4E 24 ; "N$" 09AC- 28 ; "(" 09AD- 33 30 ; "30" 09AF- 29 ; ")" 09B0- 00 ; fin de ligne |
Je parlais de luxe juste au-dessus. Encore un signe évident ici avec le " :" rajouté manuellement (et non pas dû comme pour les autres lignes 2 et 3 au camouflage imparfait par CTR+H). C'est juste histoire de brouiller un peu plus les pistes car cette ligne là est une vraie ligne BASIC avec un vrai DIM (30) !
Voilà, à partir de là le programme continue normalement mais nous avons localisé l'essentiel, le CALL 16576, soit $40C0. Il suffit de faire sous Monitor un petit $40C0G pour voir l'image s'afficher et la musique jouer. Une dernière petite chose à propos du programme BASIC... On a vu que les lignes 2 et 3 étaient remplies de fausses instructions (pendant que les vraies s’exécutent en coulisse). Pourquoi l'interpréteur Applesoft ne renvoie aucune erreur quand il rencontre ce garbage là ? C'est justement le but de la commande ON ERROR de la ligne 1 (et du RESUME en ligne 6000). L'illusion est parfaite !
Le Player
Localiser l'adresse (ou presque) du player était la partie la plus importante du défi.
Qu'avons-nous donc en $40C0 ? Pas tout à fait le Player en fait car QTIPS a utilisé une autre ruse, l'utilisation de la page texte ($400) pour y loger son programme. Voyons tout ça :
40C0- 2C 10 C0 BIT $C010 ; initialisation Clavier 40C3- 2C 57 C0 BIT $C057 ; mode Hires 40C6- 2C 50 C0 BIT $C050 ; mode graphique 40C9- 2C 54 C0 BIT $C054 ; page 1 40CC- 2C 52 C0 BIT $C052 ; plein écran 40CF- A0 00 LDY #$00 40D1- (74)/ EA NOP ; $74 = "double" NOP 40D2- (A9)/ EA NOP ; le A9 est donc shunté 40D3- B9 00 41 LDA $4100,Y 40D6- (74)/EA NOP ; idem 40D7- (85)/EA NOP 40D8- 99 00 04 STA $0400,Y ; déplacement $4100/$41F0 vers $400+ 40DB- A9 00 LDA #$00 ; on nettoie 40DD- 99 00 41 STA $4100,Y ; derrière 40E0- C8 INY 40E1- C0 F1 CPY #$F1 ; 40E3- D0 EE BNE $40D3 ; on boucle 40E5- A0 00 LDY #$00 ; 40E7- B9 F7 40 LDA $40F7,Y ; initialisation 40EA- 99 F9 00 STA $00F9,Y ; variables pour le player 40ED- C8 INY 40EE- C0 07 CPY #$07 40F0- D0 F5 BNE $40E7 40F2- EA NOP 40F3- EA NOP 40F4- 4C 00 04 JMP $0400 ; JMP Player ! |
Petite routine qui fait le déplacement vers la page Texte 1 après avoir initialisé le mode graphique (et déclenché donc l'affichage de l'image d'intro). Une fois ceci fait, on saute en $400 pour lancer la musique en elle-même. Tout le monde aura reconnu à l'oreille une musique issue de Electric Duet. QTIPS ayant développé lui-même un petit player pour ce format, il était intéressant de voir s'il y avait des différences avec celui utilisé ici. Pour info (car finalement, cela a peu d'importance pour le défi), vous trouverez ci-dessous le code ASM des deux players mis côte à côte. À gauche donc, le player du défi (adresse de base en $400) et à droite le générique de QTIPS (adresse de base en $1E00) que l'on peut retrouver sur ses différentes productions (comme Miami Sound Machine). J'ai essayé de mettre face à face les lignes correspondantes pour plus de clarté. On voit que les deux codes sont très proches, QTIPS ayant "juste" rajouté un système complexe d'EOR dans le code même. Data et Player sont ainsi intimement liés (c'est à dire que les données du défi ne pourront pas être utilisées avec le player générique et vis versa...).
0400- A5 F9 LDA $F9 ; Data Musique Adresse Lo 0402- 85 01 STA $01 0404- A5 FA LDA $FA ; Data Musique Adresse Hi 0406- 85 02 STA $02 0408-* 20 16 04 JSR $0416 040B- 2C 00 C0 BIT $C000 040E- 30 03 BMI $0413 0410-* 4C 00 04 JMP $0400 0413- 60 RTS 0414- EA NOP 0415- EA NOP 0416- A9 01 LDA #$01 1E00- A9 01 LDA #$01 0418- 85 09 STA $09 1E02- 85 09 STA $09 041A- 85 00 STA $00 1E04- 85 1D STA $1D 041C- 48 PHA 1E06- 48 PHA 041D- 48 PHA 1E07- 48 PHA 041E- 48 PHA 1E08- 48 PHA 041F- D0 21 BNE $0442 1E09- D0 15 BNE $1E20 0421- C8 INY 1E0B- C8 INY 0422- B1 01 LDA ($01),Y 1E0C- B1 1E LDA ($1E),Y 0424- 45 FB EOR $FB 0426- 85 09 STA $09 1E0E- 85 09 STA $09 0428- C8 INY 1E10- C8 INY 0429- B1 01 LDA ($01),Y 1E11- B1 1E LDA ($1E),Y 042B- 45 FF EOR $FF 042D- 85 00 STA $00 1E13- 85 1D STA $1D 042F- A5 FE LDA $FE 0431-* 8D 38 04 STA $0438 0434- A5 01 LDA $01 1E15- A5 1E LDA $1E 0436- 38 SEC 1E17- 18 CLC 0437- E9 13 SBC #$13 1E18- 69 03 ADC #$03 0439-* 8D 38 04 STA $0438 043C- 85 01 STA $01 1E1A- 85 1E STA $1E 043E- B0 02 BCS $0442 1E1C- 90 02 BCC $1E20 0440- C6 02 DEC $02 1E1E- E6 1F INC $1F 0442- A4 FD LDY $FD 1E20- A0 00 LDY #$00 0444- B1 01 LDA ($01),Y 1E22- B1 1E LDA ($1E),Y 0446- 45 FB EOR $FB 0448- C9 01 CMP #$01 1E24- C9 01 CMP #$01 044A- F0 D5 BEQ $0421 1E26- F0 E3 BEQ $1E0B 044C- B0 17 BCS $0465 1E28- B0 0D BCS $1E37 044E- 68 PLA 1E2A- 68 PLA 044F- 68 PLA 1E2B- 68 PLA 0450- 68 PLA 1E2C- 68 PLA 0451- A2 49 LDX #$49 1E2D- A2 49 LDX #$49 0453- C8 INY 1E2F- C8 INY 0454- A5 FF LDA $FF 0456-* 8D 5C 04 STA $045C 0459- B1 01 LDA ($01),Y 1E30- B1 1E LDA ($1E),Y 045B- 49 25 EOR #$25 045D-* 8D 5C 04 STA $045C 0460- D0 02 BNE $0464 1E32- D0 02 BNE $1E36 0462- A2 C9 LDX #$C9 1E34- A2 C9 LDX #$C9 0464- 60 RTS 1E36- 60 RTS 0465- 85 08 STA $08 1E37- 85 08 STA $08 0467-* 20 51 04 JSR $0451 1E39- 20 2D 1E JSR $1E2D 046A-* 8E B1 04 STX $04B1 1E3C- 8E 83 1E STX $1E83 046D- 85 06 STA $06 1E3F- 85 06 STA $06 046F- A6 09 LDX $09 1E41- A6 09 LDX $09 0471- 4A LSR 1E43- 4A LSR 0472- CA DEX 1E44- CA DEX 0473- D0 FC BNE $0471 1E45- D0 FC BNE $1E43 0475-* 8D AA 04 STA $04AA 1E47- 8D 7C 1E STA $1E7C 0478-* 20 51 04 JSR $0451 1E4A- 20 2D 1E JSR $1E2D 047B-* 8E E9 04 STX $04E9 1E4D- 8E BB 1E STX $1EBB 047E- 85 07 STA $07 1E50- 85 07 STA $07 0480- A6 00 LDX $00 1E52- A6 1D LDX $1D 0482- 4A LSR 1E54- 4A LSR 0483- CA DEX 1E55- CA DEX 0484- D0 FC BNE $0482 1E56- D0 FC BNE $1E54 0486-* 8D E2 04 STA $04E2 1E58- 8D B4 1E STA $1EB4 0489- 68 PLA 1E5B- 68 PLA 048A- A8 TAY 1E5C- A8 TAY 048B- 68 PLA 1E5D- 68 PLA 048C- AA TAX 1E5E- AA TAX 048D- 68 PLA 1E5F- 68 PLA 048E- D0 03 BNE $0493 1E60- D0 03 BNE $1E65 0490- 2C 30 C0 BIT $C030 1E62- 2C 30 C0 BIT $C030 0493- C9 00 CMP #$00 1E65- C9 00 CMP #$00 0495- 30 03 BMI $049A 1E67- 30 03 BMI $1E6C 0497- EA NOP 1E69- EA NOP 0498- 10 03 BPL $049D 1E6A- 10 03 BPL $1E6F 049A- 2C 30 C0 BIT $C030 1E6C- 2C 30 C0 BIT $C030 049D- 85 4E STA $4E 1E6F- 85 4E STA $4E 049F- 2C 00 C0 BIT $C000 1E71- 2C 00 C0 BIT $C000 04A2- 30 C0 BMI $0464 1E74- 30 C0 BMI $1E36 04A4- 88 DEY 1E76- 88 DEY 04A5- D0 02 BNE $04A9 1E77- D0 02 BNE $1E7B 04A7- F0 06 BEQ $04AF 1E79- F0 06 BEQ $1E81 04A9- C0 01 CPY #$01 1E7B- C0 03 CPY #$03 04AB- F0 04 BEQ $04B1 1E7D- F0 04 BEQ $1E83 04AD- D0 04 BNE $04B3 1E7F- D0 04 BNE $1E85 04AF- A4 06 LDY $06 1E81- A4 06 LDY $06 04B1- 45 FC EOR $FC 1E83- 49 40 EOR #$40 04B3- 24 4E BIT $4E 1E85- 24 4E BIT $4E 04B5- 50 07 BVC $04BE 1E87- 50 07 BVC $1E90 04B7- 70 00 BVS $04B9 1E89- 70 00 BVS $1E8B 04B9- 10 09 BPL $04C4 1E8B- 10 09 BPL $1E96 04BB- EA NOP 1E8D- EA NOP 04BC- 30 09 BMI $04C7 1E8E- 30 09 BMI $1E99 04BE- EA NOP 1E90- EA NOP 04BF- 30 03 BMI $04C4 1E91- 30 03 BMI $1E96 04C1- EA NOP 1E93- EA NOP 04C2- 10 03 BPL $04C7 1E94- 10 03 BPL $1E99 04C4- CD 30 C0 CMP $C030 1E96- CD 30 C0 CMP $C030 04C7- C6 4F DEC $4F 1E99- C6 4F DEC $4F 04C9- D0 11 BNE $04DC 1E9B- D0 11 BNE $1EAE 04CB- C6 08 DEC $08 1E9D- C6 08 DEC $08 04CD- D0 0D BNE $04DC 1E9F- D0 0D BNE $1EAE 04CF- 50 03 BVC $04D4 1EA1- 50 03 BVC $1EA6 04D1- 2C 30 C0 BIT $C030 1EA3- 2C 30 C0 BIT $C030 04D4- 48 PHA 1EA6- 48 PHA 04D5- 8A TXA 1EA7- 8A TXA 04D6- 48 PHA 1EA8- 48 PHA 04D7- 98 TYA 1EA9- 98 TYA 04D8- 48 PHA 1EAA- 48 PHA 04D9-* 4C 2F 04 JMP $042F 1EAB- 4C 15 1E JMP $1E15 04DC- CA DEX 1EAE- CA DEX 04DD- D0 02 BNE $04E1 1EAF- D0 02 BNE $1EB3 04DF- F0 06 BEQ $04E7 1EB1- F0 06 BEQ $1EB9 04E1- E4 FD CPX $FD 1EB3- E0 01 CPX #$01 04E3- F0 04 BEQ $04E9 1EB5- F0 04 BEQ $1EBB 04E5- D0 04 BNE $04EB 1EB7- D0 04 BNE $1EBD 04E7- A6 07 LDX $07 1EB9- A6 07 LDX $07 04E9- C9 80 CMP #$80 1EBB- 49 80 EOR #$80 04EB- 70 A3 BVS $0490 1EBD- 70 A3 BVS $1E62 04ED- EA NOP 1EBF- EA NOP 04EE- 50 A3 BVC $0493 1EC0- 50 A3 BVC $1E65 |
Bien, nous avons donc le Player, on sait qu'un $40C0G lancera la musique. Il faut maintenant localiser les Data. On sait que l'adresse de départ provient de $F9 et $FA, deux variables initialisées juste après la routine de déplacement vers $400 du Player. Problème : sur cette version spécifique, le Buffer Data ne va pas vers l'avant mais en arrière... On peut partir de $5FFD ($FA = 5F et $F9 = $FD) et remonter pour trouver manuellement le départ des data en mémoire.
Mais on peut aussi légèrement modifier le Player en $40B (donc en $410B, avant le déplacement) pour y mettre un $60 (RTS). On récupérera ainsi la main après une lecture complète de la musique juste avant qu'elle ne reboucle. Il suffira alors de checker le contenu de $01 et $02 pour trouver l'adresse de "fin" des Data (qui sera en fait l'adresse de "début" en mémoire... puisqu'on va en arrière... c'est pourtant clair non ?). Avec ce petit exercice, on arrive à trouver la valeur $5C7F.
Il ne reste plus alors qu'à tout recoller, Player et Data, ajouter une petite routine qui initialise les variables indispensables (de $F9 à $FF) et, cerise sur le gâteau, "reloger" le tout en dehors de la page Texte afin de rendre la routine un peu plus utilisable. Il va falloir modifier manuellement les différents STA/STX qui pointent vers la page $04 ainsi que les quelques JSR qui font de même. Rien de compliqué, il faut juste prendre son temps et bien recalculer les nouvelles valeurs. Les lignes à modifier sont signalées par un "*" dans le code ci-dessus (partie gauche). Concrètement, vous calculez le déplacement entre $400 et la nouvelle adresse que vous avez choisie pour le Player et vous ajoutez cette valeur à l'adresse pointée dans le code. Exemple :
- vous voulez mettre votre Player en $1020 (pensez à laisser avant la place nécessaire pour le petit code qui initialisera les variables). Le déplacement vaut donc $1020-$400 soit $C20. Il faudra donc ajouter $C20 à chaque adresse qui pointait vers un $400+.
- exemple pour la ligne en $408 : JSR $416. Cette ligne après déplacement sera en : $1028. Et le JSR $416 devra devenir un JSR $1036. Et ainsi de suite... Pour "reloger" le code du player, laissez faire le programme en $40C0 en remplaçant juste le STA $0400,Y (en $40D8) par l'adresse choisie.
Il ne reste plus ensuite qu'à déplacer les data "musique" (qui sont entre $5C7F et $5FFF) juste à la suite du player. Et sauvez le tout...
Quelques petites remarques pour finir :
- si on supprime une ligne en début du programme BASIC (comme la 0 par exemple), plus rien ne marche. Logique, tout se retrouve décalé en mémoire qui est réorganisée par Applesoft en temps réel et le POKE $966,$8C en ligne 7000 ne modifiera plus du tout un POKE en CALL mais plantera alors allègrement le programme.
- nulle trace dans le programme BASIC du chargement de l'image et de la musique. Tout est fait lors du boot en accès direct visiblement.
- sur le disque Miami Sound Machine III (autre production QTIPS), il y a 3 fichiers texte de résolution du défi écrits par Astraban, Captain Crack et Jojo. Comme d'habitude, je ne les ai lu qu'après avoir écrit ce texte proposant ma propre solution. Ils sont toutefois très intéressants car ils décrivent chacun une approche différente. J'ai particulièrement apprécié l'efficacité froide d'un Captain Crack qui, en utilisant le boot tracing, ne s'occupe même pas du programme BASIC. Mais aussi l'astuce d'un Astraban avec la commande SPEED=1 qui ralentit la vitesse d'affichage à l'écran et permet de voir en temps réel les CTRL+H en action masquant les vraies instructions... Je vous invite fortement à lire ces textes en complément du mien. Et je vous rassure, ils sont beaucoup moins bavards que moi... Cela démontre en tout cas qu'il n'y a jamais qu'une solution et que différentes approches sont toujours possibles.
Pour finir, voici en téléchargement :
- Happy Birthday QTIPS
- Le disque de travail avec ma version STANDALONE du défi (plus le Player en $40c0 et les Data Musique)
- Miami Sound Machine III (version Deckard) avec les solutions du défi par Astraban, Captain Crack et Jojo.
Et maintenant, place à la musique !
Clip audio : Le lecteur Adobe Flash (version 9 ou plus) est nécessaire pour la lecture de ce clip audio. Téléchargez la dernière version ici. Vous devez aussi avoir JavaScript activé dans votre navigateur.
envoyé le 29-08-2011 à 0 h 30 min
BIen joué, bravo !