Il m'arrive régulièrement de tomber sur des images disque refusant de se lancer sous émulateur. La plupart du temps, cela est généralement dû à un problème lors de la numérisation, science qui est tout sauf exacte ! Les secteurs défectueux (ou illisibles) et les artefacts de lecture sont de véritables bombes à retardement. Mais quand une erreur est récurrente sur plusieurs versions d'un même soft, cela mérite de plus amples investigations ! Les émulateurs Apple II ne sont pas parfaits et se heurtent - souvent - à certaines astuces de programmation, l'utilisation de certains softswitches alternatifs en étant un bon exemple. Pourtant les JSR $F3D4 (et leur pendant Basic CALL -3116) dont nous allons parler ici sont, eux, parfaitement émulés. Ce qui ne les empêchent pas dans certains cas de faire planter lamentablement le programme les utilisant. À la décharge des émulateurs, il se passe exactement la même chose sur les machines physiques !
Mais commençons par le commencement. D'où provient cette adresse $F3D4 ? Si on se réfère à La pratique de l'Apple II volume 3 de Nicole Bréaud-Pouliquen (éditions du P.S.I. - 1985), il n'y a rien d'officiel en $F3D4 ! Confirmé par La Rom de l'Apple II de Marcel Cottini (éditions Sybex - 1986), beaucoup plus complet à ce niveau. On y découvre que $F3D4 est en plein dans la routine Applesoft RECALL, qui commence en $F3BC et se termine juste avant la routine HGR2 dont le début est en $F3D8. $F3D4 ne semble donc pas être un point d'entrée Applesoft documenté...
Pourtant, si on consulte What's Where in the Apple de William F. Luebbert (éditions Micro Ink - 1982), on trouve référence à $F3D4 comme point d'entrée pour la routine HGR2 ! Cette même info apparaît également dans le tout premier numéro de Apple Orchard (Mars/Avril 1980) détaillant tous les entry points Applesoft, la source étant Apple Computer, Inc. himself ! Et c'est également l'adresse qui est reprise dans la FAQ officielle de Comp.Sys.Apple2.Programmer !
Il n'y a eu que peu d'évolutions de l'Applesoft au cours des années, une fois la version définitive en place - Applesoft II - en 1978 et son intégration en ROM. En fait, il faudra attendre 1984 et la prise en compte du mode 80 colonnes avec la sortie des Apple IIc et IIe Enhanced pour voir apparaître une révision (et, somme toute, légère). Même l'Apple IIgs, en 1986, n'aura droit qu'à un Applesoft très légèrement remanié. Et à chaque nouvelle version, la rétrocompatibilité descendante stricte du Basic imposait la correspondance des points d'entrées officiels.
La routine Applesoft HGR 2 :
On imagine aisément que la routine HGR2 ne pouvait pas changer d'emplacement au gré du vent (et des versions). Pour en avoir définitivement le cœur net, le plus simple est bien évidemment d'aller directement regarder dans le code. Pour ce faire, j'ai conjointement utilisé :
- des Apple II (IIe Enhanced, IIgs, IIc) physiques en ma possession.
- les différents émulateurs sous Windows (AppleWin, ActiveGS, etc.) permettant ainsi d'avoir accès à un large panel de machines (II, II+, IIe, IIe Enhanced, IIgs).
- le listing Applesoft qui fait référence à savoir S-C DocuMentor for Applesoft.
Résultat des courses : peu importe que l'on soit sur II+, IIe, IIe Enhanced, IIc ou IIgs, la routine HGR2 commence en $F3D8 et son code reste le même.
; routine Applesoft HGR2 (PAGE 2) F3D8- 2C 55 C0 BIT $C055 ; TXTPAGE2 (page 2) F3DB- 2C 52 C0 BIT $C052 ; MIXCLR (affiche tout l'écran gfx) F3DE- A9 40 LDA #$40 ; $40 (page 2) pour HPAG F3E0- D0 08 BNE $F3EA ; saut (systématique) ; routine Applesoft HGR (PAGE 1) F3E2- A9 20 LDA #$20 ; $20 (page 1) pour HPAG F3E4- 2C 54 C0 BIT $C054 ; TXTPAGE1 (page 1) F3E7- 2C 53 C0 BIT $C053 ; MIXCLR (4 pages de texte !) F3EA- 85 E6 STA $E6 ; HPAG (numéro de page - $20 si 1, $40 si 2) F3EC- AD 57 C0 LDA $C057 ; HIRES (haute résolution 280x192) F3EF- AD 50 C0 LDA $C050 ; TXTCLR (mode graphique) F3F2- A9 00 LDA #$00 ; début routine HCLR (efface page courante HGR) F3F4- 85 1C STA $1C ; début routine BKGND F3F6- A5 E6 LDA $E6 F3F8- 85 1B STA $1B F3FA- A0 00 LDY #$00 F3FC- 84 1A STY $1A F3FE- A5 1C LDA $1C F400- 91 1A STA ($1A),Y F402- 20 7E F4 JSR $F47E F405- C8 INY F406- D0 F6 BNE $F3FE F408- E6 1B INC $1B F40A- A5 1B LDA $1B F40C- 29 1F AND #$1F F40E- D0 EE BNE $F3FE F410- 60 RTS |
On peut donc en conclure que toutes les documentations référençant HGR2 en $F3D4 se plantent ! Alors erreur de frappe sur une vieille documentation Apple ? Résidu des toutes premières versions de l'Applesoft ? Le mystère reste entier...
À noter que l'Apple II originel et son Basic Integer est bien entendu hors concours ici.
Plusieurs choses sont à remarquer dans le code ci-dessus :
- les instructions HGR et HGR2 partagent du code commun, l'une continuant à la suite de l'autre.
- HGR = Page 1 + Mode Graphique + Haute Résolution + 4 lignes de texte + Effacement Page 1
- HGR2 = Page 2 + Mode Graphique + Haute Résolution + Full Screen + Effacement Page 2
On retrouve bien évidemment le comportement normal des commandes HGR et HGR2 utilisées depuis un programme Basic ou directement depuis le prompt Applesoft.
Et $F3D4 dans tout ça ?
Que se passe-t'il concrètement lorsqu'un programme rencontre un JSR $F3D4 ou un CALL -3116 ?
Et bien, le résultat sera différent suivant la machine sur lequel le code sera exécuté.
• Sous II+, IIe et IIe Enhanced :
Voici ce qu'affiche le Monitor sous ces différentes machines :
F3D4 : F7 : ??? F3D5 : 4C FD FE : JMP $FEFD ; JMP vers routine READ |
Un opcode (F7) a priori inconnu suivi d'un JMP vers la routine READ (lecture depuis l'interface cassette). Ceci confirme bien d'ailleurs que $F3D4 se situe bien en plein dans la routine RECALL (rappel en mémoire centrale d'un tableau sauvegardé au préalable sur cassette par l'instruction STORE).
Ce qui se passe réellement, nous pouvons le visualiser grâce au mode debugger d'AppleWin :
Capture d'écran du mode debugger avec AppleWin en mode Apple II+ ou IIe. On remarque immédiatement qu'en $F3D4, une instruction est visible mais ne fait surement pas partie des documentées ! Car nous entrons ici dans le monde merveilleux des opcodes illégaux. Sur 6502, les opcodes non officiels sont tout simplement exécutés logiquement par le processeur (logique, dans le sens de "correspondant à son câblage interne"). Ce qui peut conduire à l'exécution d'instructions un poil farfelues... L'opcode F7 correspond donc à un INC-SBC (INS) : on incrémente l'argument (ici en page zéro, indexée par x) et on soustrait le résultat du registre A ! Bien évidemment, l'instruction en elle-même ne sert à rien ici, ce qu'il faut par contre savoir c'est que pour le 6502, F7 est considéré comme un opcode sur 2 bytes puisqu'il prend un argument ! Ce qui entraîne ensuite un décalage dans tout le code qui suit.
Reprise du code précédent :
F3D4 : F7 4C : INS $4C,X ; garbage (mais $4C+X modifié !) F3D6 : FD FE 2C : SBC $2CFE,X ; garbage F3D9 : 55 C0 : EOR $C0,X ; garbage F3DB : 2C 52 C0 : BIT $C052 ; MIXCLR F3DE : A9 40 : LDA #$40 ; $40 (page 2) pour HPAG F3E0 : D0 08 : BNE $F3EA ; saut (systématique) ... -> suite routine HGR |
Une fois les instructions en $F3D4, $F3D6 et $F3D9 exécutées, il y a resynchronisation avec le code de la routine HGR2 en $F3DB. Par contre, BIT $C055 (normalement en $F3D8) a été shunté. On ne passe donc plus en page 2.
Il est à noter qu'en page zéro, la mémoire $4C+X est incrémentée. X dépendant du code d'avant l'appel vers $3DF4, n'importe quelle adresse de la page zéro est susceptible d'être touchée. C'est ce qu'on pourrait appeler un dommage collatéral. C'est bien évidemment non voulu et résulte juste de l'interprétation de l'opcode F7 par le 6502.
Plus important encore, la variable HPAG ($E6) est positionnée à $40 (page 2) alors que la page courante (qui peut très bien être 1) est toujours active. La routine en $F3F2, exécutée à la suite, censée effacer la page graphique "courante", utilise HPAG pour déterminer celle-ci. C'est donc systématiquement la page 2 qui sera effacée, même si la page courante est la 1.
En résumé, sur Apple II+ et IIe, un JSR $F3D4 (ou un CALL -3116) équivaut à :
- Page Courante + Mode Graphique + Haute Résolution + Full Screen + Effacement Page 2
Nouvelle capture d'écran du mode debugger d'AppleWin, cette fois en mode Apple IIe Enhanced et son 65C02 ! On remarque que l'opcode F7 est ici considéré comme un NOP ! Sur 65C02 en effet, tous les opcodes non utilisés (ou illégaux ou non documentés, peu importe le nom) sont systématiquement considérés comme des NOP. C'est une des features du processeur. Seul le nombre de bytes de l'instruction et son nombre de cycles varient. On pourra donc obtenir des NOP sur 1 ou 2 bytes. Pour F7, c'est 1 byte. D'où le NOP uniquement sur l'adresse $F3D4.
Attention, sur Rockwell 65C02 et WDC 65C02, l'opcode F7 existe ! AppleWin, lui n'émule qu'un 65C02 classique (comme sur IIe Enhanced), donc F7 = NOP !
Reprise du code précédent :
F3D4 : F7 : NOP F3D5 : 4C FD FE : JMP $FEFD (routine READ) FEFD : 8D 07 C0 : STA $C007 (SETINTCXROMC007) FF00 : 4C D1 C5 : JSR $C5D1 (XREAD) -> plantage ! |
Le pseudo NOP en $F3D4 nous amène donc directement au JMP $FEFD (routine READ) qui elle-même nous conduit en $C5D1 entraînant un joli plantage ! Nous venons de découvrir pourquoi l'appel à la routine $F3D4 (ou CALL -3116) pouvait planter sur certains Apple II (et sous émulateur). Ceci ne concerne que les IIe Enhanced ! Et paradoxalement, c'est parce que l'opcode F7 est traité correctement par le processeur que l'on arrive à ce résultat.
En résumé, sur Apple IIe Enhanced, un appel à $F3D4 entraîne :
- un plantage !
• Sur Apple IIc (Rom 00, 03 et 04) :
Listing Monitor :
F3D4 : 1F : ??? ; NOP 1 byte/1 cycle F3D5 : C0 0A : CPY #$0A ; garbage F3D7 : 60 : RTS ; sortie ! F3D8 : 2C 55 C0 : BIT $C055 F3DB : 2C 52 C0 : BIT $C052 F3DE : ... |
L'opcode inutilisé 1F est une nouvelle fois traité comme un NOP d'un byte par le 65C02. Le CPY en $F3D5 ne changeant rien, on se retrouve ensuite en $F3D7 et le RTS nous ramène vers l'appelant.
Sur Apple IIc, un appel à la routine $F3D4 ne fait donc strictement rien ! On évite le plantage, ce qui n'est déjà pas si mal...
Contrairement aux Apple II+, IIe et IIe Enhanced, $F3D4 ne se situe plus dans la routine RECALL. En effet, n'ayant plus de port cassette, sur Apple IIc, toutes les routines dédiées ont été supprimées et l'espace ainsi libéré a été utilisé pour d'autres routines. Ce qui explique pourquoi en $F3D4, on ne retrouve plus l'opcode F7 vu précédemment mais 1F. La routine HGR2 en $F3D8 est bien évidemment présente et identique.
En conclusion, sur Apple IIc, un appel à $F3D4 :
- ne fait strictement rien.
• Sur Apple IIgs (Rom 01 et 03) :
Listing Monitor :
FF/F3D4 : 1F C0 0A 60 : ORA 600AC0,X FF/F3D8 : 2C 55 C0 : BIT C055 FF/F3DB : 2C 52 C0 : BIT C052 FF/F3DE : A9 40 : LDA #40 ... |
On retrouve les mêmes opcodes que sur IIc mais traités différemment. En effet, le IIgs et son 65SC816 reconnaît l'opcode 1F comme un ORA (sur 4 bytes). Après exécution, on se retrouve directement au début de la routine HGR2 en $F3D8. Aucun plantage donc, par contre, contrairement aux Apple II+ et IIe, on se retrouve systématiquement page 2 et non plus page courante.
En résumé, sur Apple IIgs, un appel à $F3D4 équivaut à l'exécution de la routine HGR2 soit :
- Page 2 + Mode Graphique + Haute Résolution + Full Screen + Effacement Page 2
Le Fix :
Maintenant que nous avons tout décortiqué, il va bien falloir trouver un moyen de fixer tout ça. Non seulement $F3D4 n'est pas l'adresse réelle de HGR2, mais son exécution ne fera pas exactement la même chose. Il ne faut donc pas simplement remplacer $F3D4 par $F3D8 sous peine d'obtenir un résultat différent de celui voulu par le programmeur.
Nous voulons également obtenir le même résultat peu importe la machine, du II+ au IIgs. Le plus évident, c'est de faire en sorte que l'on se retrouve en $F3DB directement. Car, c'est finalement là que nous atterrissons sur II+ et IIe. Sur IIgs, cela aura l'avantage de nous laisser dans la page courante (et pas forcément la 2). Et sur IIc, il y aura changement de mode graphique (alors qu'il ne se passait rien). En fait, $F3DB est l'adresse de la première instruction réellement voulue par le programmeur lorsqu'il utilisait par erreur $F3D4.
En remplaçant les JSR $F3D4 par des JSR $F3DB (et les CALL -3116 par des CALL -3109), nous nous retrouverons avec ce code-ci, exécuté, quelque soit la machine :
F3DB- 2C 52 C0 BIT $C052 ; MIXCLR (tout l'écran gfx) F3DE- A9 40 LDA #$40 ; $40 pour HPAG F3E0- D0 08 BNE $F3EA ; saut ... F3EA- 85 E6 STA $E6 ; HPAG ($40 -> effacement page 2) F3EC- AD 57 C0 LDA $C057 ; HIRES (haute résolution) F3EF- AD 50 C0 LDA $C050 ; TXTCLR (mode graphique) ... |
- $F3DB = Page Courante + Mode Graphique + Haute Résolution + Full Screen + Effacement Page 2
Concrètement :
• Pour remplacer les JSR $F3D4 : faire une recherche sur 20 D4 F3. Et remplacer par 20 DB F3.
• Pour les CALL -3116 : en Token Applesoft, CALL -3116 correspond à 8C (CALL) C9 (-) 33 (3) 31 (1) 31 (1) 36 (6).
Faire une recherche sur 8CC933313136. Et remplacer par 8CC933313039.
Pour modifier des images disque, il est recommandé d'utiliser un éditeur Hexa et d'agir directement sur l'image. C'est bien plus rapide. Pour les disquettes physiques, un éditeur de secteur style DiskFixer fera le job au coup par coup !
Attention, tous les JSR $F3D4 ne sont pas sources de problèmes. Dans le cas de programmes utilisant la Hi-Ram ($D000-$FFFF) en Mémoire Auxiliaire, un appel vers $F3D4 peut être tout à fait légitime.
Pour aller plus loin, références et sources :
http://bbc.nvg.org/doc/6502OpList.txt : liste des opcodes 6502 y compris ceux non documentés.
http://www.pagetable.com/?p=39 : l'explication du pourquoi du comment les opcodes illégaux sur 6502 existent et font ce qu'ils font...
http://www.6502.org/tutorials/65c02opcodes.html : liste des opcodes 65C02. J'attire tout particulièrement votre attention sur la partie Unused opcodes (guaranteed NOPs).
http://www.txbobsc.com/scsc/scdocumentor/ : code source Applesoft (attention, ne contient que l'Applesoft II de base, pas les modifications IIc et IIgs).
Apple Orchard numéro 1 : avec la référence à HGR2 en $F3D4.
http://www.faqs.org/faqs/apple2/programmerfaq/part1/ : même chose pour la FAQ officielle de C.S.A.P. sur Usenet.
Open-Apple numéro 5 : contient une analyse des nouveautés Applesoft de l'Apple IIc. Parle notamment de la routine en $F3CB qui remplace la routine RECALL.
Galeries :
Un des jeux récurrent posant problème sous IIe Enhanced à cause de l'appel à $F3D4 est Robot Battle. On retrouve ce vieux jeu régulièrement sur les compiles "Catalog" de l'époque. Peu importe la version, c'est souvent le même crack et donc la même source.
Autre grande victime, Lock'n'Chase. Là encore, c'est un jeu que l'on retrouve souvent sur des vieilles compilations. La version en cause semble être celle du Disk Jockey et ses dérivées (comme ici le joli défacement d'un certain Dick Jokey - on appréciera le trait d'esprit à sa juste valeur).
Plus près de nous, la version The Softman/Solex Crack Band du Casse. Le Call -3116 dans le HELLO modifié par les crackers fait planter lamentablement le boot sur IIe Enhanced.
On retrouve également un CALL -3116 sur la disquette officielle Calvados ! Le bug n’apparaît que lors de la démonstration. Sans surprise, c'est le programme en Basic DEMONSTRATION qui est en cause...
La compilation avec Robotron, Frogger et Q*Bert, proposée par Jim Dos, souffre aussi du JSR $F3D4 ! Cette fois, c'est la présentation qui est en cause et pas les jeux en eux-même !
Même chose pour le Minit Man signé Mandrake pour le Xanadu Crack Band.
envoyé le 05-07-2013 à 20 h 22 min
Bien bel et intéressant article, Arnaud, bravo et merci ! J’apprends encore et toujours des choses, c’est sympa de partager tes recherches.
J’ai une question : quand on fait appel à la ROM Apple II sur un IIgs, est-on en mode émulé ou natif ?
– Si on est en mode émulé, on fait comme un 65C02, non ? Dans ce cas, le comportement est comme sur un //c.
– si on est en mode natif, ton analyse est bonne.
Là, j’ai un doute et des IIgs près de moi, je crois que je vais les allumer… Même comportement sur ROM 01 et 3, ton analyse est la bonne.
Marrant, j’aurais vraiment pas cru. Bon, en tout cas, le gars qui a de la RAM à outrance sur //gs, s’il lance un soft qui fait un CALL -3116 a sa mémoire corrompue.
Arf,
el toto
envoyé le 05-07-2013 à 22 h 46 min
Sur IIgs, c’est purement empirique. J’ai lancé un petit programme de test très simple et j’ai effectivement constaté qu’on se retrouvait dans HGR2 après exécution en $F3D4, donc que le ORA ‘Absolute Long'(pourtant purement 65816) était bien exécuté.
Je précise que je lance le test à partir d’un DOS 3.3 booté depuis un DISK II.
Le programme de test ne spécifie aucun mode particulier (ni émulé, ni natif) tout simplement pour me retrouver dans les même conditions que les programmes de l’époque posant problème, qui ne s’occupaient pas du GS car antérieurs à lui.
Visiblement le m se met à 1 par défaut. Ce qui fait que le ORA exécuté ne l’est en fait que sur 8 bits. Même si l’instruction est en apparence16 bits.
envoyé le 06-07-2013 à 10 h 49 min
En complément et après vérifications, on a bien accès à toutes les instructions du 65816 même en mode émulation.
Le 1F est bien traité comme un ORA quelque soit le mode sous GS.
Même en mode émulation, un 65816 est donc plus puissant qu’un 6502/65C02 de part ses instructions supplémentaires.
On retrouve l’info dans le Lichty (Programming the 65816) mais aussi ici par exemple :
http://www.defence-force.org/computing/oric/coding/annexe_2/
http://www.zophar.net/fileuploads/2/10538ivwiu/65816info.txt
envoyé le 08-07-2013 à 22 h 28 min
Oui, c’est clairement écrit.
Merci Arnaud, j’en apprends tous les jours.
Comme on en discutait avec Olivier ce matin, c’est logique puisque les softs protégés par de « false opcodes » ne passent pas sur IIgs (Lady Tut, Ultima III par ex.), CQFD
av
envoyé le 08-07-2013 à 22 h 29 min
Donc, ne jamais utiliser ce CALL sur un IIgs, le bank $60 sera corrompu, zut alors !
envoyé le 18-07-2013 à 6 h 23 min
En anglais:
$F3D$: Struck, but not sunk!
It happens pretty often that I’ll come across some disk images that refuse to run on an emulator. Most of the time, this is usually a problem with the imaging process from the physical disk, a science that is far from exact! The defective (unreadable) sectors and the other irregularities from reading the disk are definitely roadblocks for imaging a disk. But when an error happens on many versions of the same program disk, it requires a close examination of the problem!
The Apple II emulators aren’t perfect and they crash pretty often– usually with unusual programming methods such as undocumented opcodes or the use of certain alternative softswitches, to give a couple of good examples. However, JSR $F3D4 (and its BASIC equivalent, CALL -3116), which we are going to talk about, is emulated perfectly. However even this call will sometimes cause the program to crash. But it’s not the emulator’s fault, because this call will even crash a real Apple II!
But let’s begin at the beginning. Where does this address $F3D4 come from? If one takes a look at « La pratique de l’Apple II, volume 3 » by Nicole Breaud-Pouliquen (published by P.S.I. – 1985) there isn’t an official entry point listed for $F3D4! This address is documented in « La ROM de l’Apple II » by Marcel Cottini (published by Sybex – 2986), which is a much more extensive reference book for the Apple ROM. Therein we find that $F3D4 is used in the Applesoft RECALL command, which starts at $F3BC and ends just before the HGR2 routine of whose starting address is $F3D8. $F3D4 therefore is not a documented Applesoft entry point.
However, if one consults William F. Lubbert’s « What’s Where in the Apple » (published by Micro Ink – 1982), one finds a reference to $F3D4 at the entry point of the HGR2 routine! This same information appears exactly the same way in the first Apple Orchard (Mar/Apr 1980) which details all the entry points in Applesoft, the source being Apple Computer, Inc. itself! Also, this exact address and information is also printed in the official FAQ for comp.sys.apple2.programmer!
There haven’t been too many revisions to Applesoft in those years, since the debut of Applesoft II in 1978 and its subsequent inclusion in ROM. In fact, it was even enhanced to work with 80-columns mode on the Apple IIc and IIe Enhanced in 1984. Likewise, the Apple IIgs in 1986 also had a slightly updated version of Applesoft. With each new version, the backwards-compatibility meant that strict adherence to the documented entry points was a must!