dsk2nib

Date 1er jet: 14/11/2008.
Date de dernière mise à jour: 20/11/2008.

1) Introduction

Si vous êtes un fan d'apple II, vous aurez nécessairement rencontré le cas de figure suivant: après avoir passé du temps à convertir votre collection de disquettes 5,25" (non protégées) en images disque au format .dsk grâce à FASTDSK, vous avez constaté avec effroi voire énervement que lorsque vous lancez des programmes avec votre émulateur préféré, ils se plantent (que ce soit dès le boot ou après une page de présentation d'un cracker).
Pourtant, vous n'aviez rencontré aucun problème lors de la création du .dsk et ces programmes se dupliquent sans soucis depuis votre Apple II avec un utilitaire type Locksmith Fast Copy, ce qui atteste à priori que les disquettes sont formattées en 16 secteurs par piste (le format standard).

Alors d'où vient le problème?

Nous avons vu dans un autre article que quelques logiciels peuvent avoir certains secteurs de données encodés en 4-4 au lieu de 6-2. Mais ce cas de figure est marginal. Par contre il y a un cas d'incompatibilité beaucoup plus fréquent: l'usage du numéro de volume.

En effet, lorsque vous formattez vous-même sur votre machine physique Apple II une disquette sous DOS 3.3, vous pouvez spécifier un numéro de volume compris de 1 à 254 pour "identifier" votre disquette.
Par exemple, vous pouvez taper: INIT HELLO, D1, V55 pour formatter la disquette présente dans le drive 1 avec un numéro de volume à 55.
Cette information de numéro de volume est ensuite rappelé à chaque fois que vous tapez la commande CATALOG.

Il faut savoir plusieurs choses:
Alors que faut-il faire en 1er quand vous avez un crash avec un .dsk pour savoir s'il s'agit de ce cas de figure?

Pour être sur que c'est bien ce problème de volume qui fait planter le .dsk, vous devez contrôler la valeur du numéro de volume sur chaque face du jeu (sur vos disquettes physiques Apple II évidemment).
Il existe plusieurs utilitaires capables d'inspecter les champs d'adresses où sont stockées ces informations, par exemple Disk Fixer, CIA, Trax de Bag Of Tricks, etc... Il suffit de lire une piste au hasard et de relever le volume (normalement le volume est unique pour toute la face).
Voici par exemple 2 écrans avec Disk Fixer (qui indique le numéro de volume en hexa en haut après Track et Sector). J'ai lu le secteur $00 de la piste $01.
Le 1er écran indique un volume $FE (c'est à dire 254 en décimal, soit un volume normal).
Le 2nd (la face Front du disk 6 du jeu Gold Rush! de Sierra) quant à lui indique $0B. Il y a toutes les "chances" pour que le .dsk ne soit pas reconnu par le jeu qui ne fonctionnera pas correctement.

Analyzing disk
Analyzing disk

Pour résoudre le problème, certains se bornent à répondre qu'il faut utiliser le logiciel SST pour créer des images disque au format .nib depuis votre Apple II.
Le problème, c'est que créer un .nib avec SST demande du temps; le process est long et puis cela signifie que tous les .dsk que vous avez créés et qui ne "passent" pas sont bons pour le poubelle.
Personnellement, je ne suis pas d'accord avec ce point de vue.
Je pense qu'il est plus simple de créer des .dsk PUIS ENSUITE rajouter le numéro de volume sur votre machine moderne.

Quand on fait un tour sur le forum comp.sys.apple2, différentes solutions sont proposées pour convertir un .dsk en .nib mais aucune ne me parait idéale.
J'ai donc décidé de faire un petit programme pour répondre à ce besoin spécifique.


hr dsk2nib

2) Le programme dsk2nib

Pour que ce programme soit compatible avec le plus de configurations possibles, il a été écrit en langage java.

Afin de ne pas perdre de temps à ré-inventer la roue, j'ai repiqué le coeur du programme, c'est à dire la routine de nibblizing des datas (transformation des 256 octets de données d'un secteur en nibbles disk). Je me suis servi dans l'émulateur Applelet écrit par Steven E. Hugg (un émulateur d'Apple ][+ sous forme d'applet java).

UTILISATION:

Placez le petit fichier dsk2nib.jar dans le répertoire contenant les .dsk à traiter et tapez:
java -jar dsk2nib.jar <volume> <liste de vos .dsk à traiter>
Le programme ne modifie pas les .dsk, il les utilise pour créer des fichiers .nib (à raison d'un .nib par .dsk).
Il faut bien entendu avoir le droit en écriture sur le répertoire et en lecture sur les .dsk!!!

Quelques exemples:

Pour affecter le volume 1 aux disks a.dsk et b.dsk:

java -jar dsk2nib.jar 1 a.dsk b.dsk

Pour affecter un volume normal aux .dsk du répertoire:

java -jar dsk2nib.jar 254 *.dsk

Pour affecter un volume 55 aux disks dont le nom contient VOL55:

java -jar dsk2nib.jar 55 *VOL55*.dsk


PRINCIPE:

Dans un .dsk, chaque piste ne contient que les données, à savoir 16 secteurs * 256 octets = 4096 octets ($1000).
Une piste d'un .nib contient $1A00 octets.
Un .dsk et un .nib contiennent tous les 2 le même nombre de piste: 35 (de la piste 0 à la piste 34).
On remplit chaque piste du .nib à partir d'une piste du .dsk.
Chaque piste du  .nib est alimentée de la façon suivante:

a) 16 blocs correspondant chacun à un secteur du .dsk. Chaque bloc a la structure suivante:

- 40 octets de synchro (valeur = $FF)
- 3 octets pour le marker de début du champ adresse ( D5 AA 96)
- 8 octets pour l'encodage en 4+4 du champ adresse (3 informations: piste/secteur/volume à raison de 2 octets par information)
- 3 octets pour le marker de fin du champ adresse ( DE AA EB)
- 6 octets de synchro (valeur = $FF)
- 3 octets pour le marker de début du champ data (D5 AA AD)
- 343 octets encodés en 6+2 pour les données du secteur de la piste traitée du .dsk 
- 3 octets pour le marker de fin du champ data (DE AA EB)
Soit un total de 409 octets par bloc.

Pour ces 16 blocs, la place occupée est de 16 * 409 octets = 6544 octets ($1990)

b) Puis on complète avec 112 octets à $FF pour finir la piste.


TELECHARGEMENT / DOWNLOAD

Logo Java
java 1.5
Download dsk2nib.jar (class)
Logo Java
java 1.5
Download dsk2nib_src.jar (sources)


Bon, et bien si avec tout ça votre soft ne fonctionne toujours pas, il ne reste plus qu'à aller faire un tour ailleurs... (sans fumer de préférence!!)

Leisure Suit Larry
Space Quest


hr dsk2nib

3) Tests sous Windows XP

Illustration sous XP pour créer des .nib avec le volume 1 à partir de 2 fichiers .dsk.
Utilisation de caractères * dans le nom spécifié pour faciliter la sélection sans tout taper. Ici on ne sélectionne que les faces "Front" des "Disk1".
java -jar dsk2nib.jar 1 *Disk1_F*.dsk

dsk2nib XP

On fait la même chose pour les autres faces en prenant bien soin d'affecter les bons numéros de volume.

On vérifie que les.nib passent avec AppleWin sous XP.
Pour Space Quest 1:

dsk2nib XP

Pour Space Quest 2:

dsk2nib XP

Autre test sous XP: on créé des .nib avec un volume normal (254) pour tous les .dsk présents dans le répertoire.
Cela peut par exemple être utile pour les utilisateurs de la carte Pseudo-Disk ][ d'Alex Freed qui exploite des .nib (mais je n'ai pas essayé de mon côté):
Il n'y a que 2 .dsk dans le répertoire.
java -jar dsk2nib.jar 254 *.dsk

dsk2nib XP

Sélection du ProDOS.nib généré et exécution avec Applewin:

dsk2nib XP
dsk2nib XP

Ecran de travail sous Eclipse (version Ganymède) avec mon eeepc connecté à un vieil écran 15 pouces:

dsk2nib XP


hr dsk2nib

4) Tests sur mac intel OS X

Le programme étant écrit en java et sans particularité, il doit tourner aussi sur mon imac.
Il faut faire attention à la version du runtime java présent sur sa machine (controlez en tapant java -version).
A la base, j'avais créé le programme avec comme cible un jre 1.6.0_07 présent sur mon eeepc.
Mais sur mon mac, j'en étais encore à la version 1.5.0_16-133.
Du coup, j'ai du recréer un dsk2nib.jar pour jre 1.5.0_06 pour être tranquille (le programme tourne ainsi sur mes 2 machines).
Si vous avez une version antérieure, vous aurez droit à un plantage similaire à celui-là:

dsk2nib OS X

Récupérez une version plus récente sur le site de Sun ou utilisez le source fourni pour faire votre propre jar.

Comme pour XP, test avec les faces Front des disks 1 de Police Quest et King's Quest IV:
java -jar dsk2nib.jar 1 *Disk1_F*.dsk
Il y a bien création de 2 fichiers .nib.

dsk2nib OS X

On recommence pour les autres faces en faisant volume+1 à chaque fois et tests des jeux avec Virtual ][.

Dans les vestiaires/chiottes de Police Quest:

dsk2nib OS X

Au début de la quête de King's Quest IV:

dsk2nib OS X

hr dsk2nib

5) Tests avec les jeux d'aventure Sierra

Une fois n'est pas coutume, voici réunies dans cette section des copies d'écran de la série complète des jeux d'aventure produits par la société américaine Sierra On-Line Inc.

King's Quest : Quest for the Crown

King's Quest I
King's Quest I

King's Quest II: Romancing the Throne

King's Quest II
King's Quest II

King's Quest III: To Heir is Human

King's Quest III
King's Quest III

King's Quest IV: The Perils of Rosella

King's Quest IV
King's Quest IV

Police Quest: In Pursuit of the Death Angel

Police Quest
Police Quest

Space Quest: The Sarien Encounter

Space Quest I
Space Quest I

Space Quest II : Vohaul's Revenge

Space Quest II
Space Quest II

Leisure Suit Larry in the Land of the Loundge Lizards

Leisure Suit Larry
Leisure Suit Larry

Mixed-up Mother Goose

Mixed Up Mother Goose
Mixed Up Mother Goose

Gold Rush!

Gold Rush!
Gold Rush!

Manhunter: New York (yes, the 8 bits version, not the IIGS version!!!)

Manhunter New York
Manhunter New York

The Black Cauldron

The Black Cauldron
The Black Cauldron

The Dark Crystal

The Dark Crystal
The Dark Crystal


hr dsk2nib

6) Compléments


PARAMETRES

N'ayant pas accès à ma collection de disquettes, j'ai demandé à mon ami Thry2 de regarder les numéros de volume de ses propres softs, ce qui l'a obligé pendant une heure à ne pas jouer à Warcraft II!!! Au passage, si vous êtes adepte de ce jeu en réseau, je vous DECONSEILLE de lancer une partie contre le pseudo Ouin ou OuinOuin: il totalise plus de 13.000 parties (oui vous avez bien lu). Aussi si vous ne voulez pas vous prendre une paté et/ou vous faire traiter de "Gros Noob", vous voila prévenu ;-) Avec tout ça, on n'est pas prêt de revoir le site www.apple2forever.net de sitôt...

Ce contrôle a permis de mettre en évidence une anomalie de face dans ses disquettes (pour Gold Rush!), ce qui est déjà ça...

Les parametres de volume (pour les quelques cracks spécifiés):

King's Quest I: french crack (14/01/85) by Eric Irq & The Wildman (ABC = Association of Broadcasting Crackers)
--------------
                      Side A = 1, Side B = 2, Side C = 3


Kings Quest II: french crack (01/02/86) by Lockbuster & Binary Digit (LCB = Laser Crack Band) >>> Thry2
--------------  french crack (17/12/85) by Chris (Copyart)                                    >>> Deckard

                                Disk 1   Disk 2   Disk 3
                       Front      1        3        5
                       Back       2        4


Kings Quest III: french crack (27/06/88) by Godfather & Steff
---------------
                                Disk 1   Disk 2   Disk 3   Disk 4   Disk 5
                       Front      1        3        5        7        9
                       Back       2        4        6        8        10


King's Quest IV: french crack (14/05/1989) by Loockheed (TBT = The Brain Trust)
---------------
                                Disk 1   Disk 2   Disk 3   Disk 4   Disk 5   Disk 6   Disk 7   Disk 8
                       Front      1        3        5        7        9        11       13       15
                       Back       2        4        6        8        10       12       14       16


Space Quest I: french crack (??/??/????) by Softpatch (avec les explications de Lot)          >>> Thry2
-------------  unsigned                                                                       >>> Deckard

                                Disk 1   Disk 2   Disk 3   Disk 4
                       Front      1        3        5        7
                       Back       2        4        6        8


Space Quest II: french crack (04/05/1988) by Gerard & Goldpom
--------------
                                Disk 1   Disk 2   Disk 3   Disk 4
                       Front      1        3        5        7
                       Back       2        4        6        8


Leisure Suit Larry: french crack (??/??/????) by HackerForce (HF)                                 >>> Thry2
------------------  cracked (12/05/1987) by The Boy! (COD = Circle Of Deneb / BBS: The Lost City) >>> Deckard

                                Disk 1   Disk 2   Disk 3
                       Front      1        3        5
                       Back       2        4


Police Quest: The game was unprotected (distributed by The Brain Trust)
------------
                                Disk 1   Disk 2   Disk 3   Disk 4
                       Front      1        3        5        7
                       Back       2        4        6        8


Mixed-up Mother Goose: The game was unprotected (distributed by The Brain Trust)
---------------------
                                Disk 1   Disk 2
                       Front      1        3
                       Back       2        4


Gold Rush!: french crack (18/05/1990) by LoGo (TOD = The Thieves Of Destiny) presentation #1     >>> Thry2
----------  french crack (18/05/1990) by ???  (TOD = The Thieves Of Destiny) presentation #2     >>> Deckard

                                Disk 1   Disk 2   Disk 3   Disk 4   Disk 5   Disk 6   Disk 7   Disk 8
                       Front      1        3        5        7        9        11       13       15
                       Back       2        4        6        8        10       12       14       16


Manhunter: New York: french crack (20/06/1990) by LoGo (TOD = The Thieves Of Destiny)
-------------------
                                Disk 1   Disk 2   Disk 3   Disk 4   Disk 5   Disk 6
                       Front      1        3        5        7        9        11
                       Back       2        4        6        8        10       12


The Black Cauldron: unsigned
------------------
                                Disk 1   Disk 2   Disk 3
                       Front      1        3        5
                       Back       2        4


The Dark Crystal: french crack (13/03/1983) by Aldo Reset (CCB = Clean Crack Band)
----------------
                                Disk 1   Disk 2
                       Front      254      4
                       Back       3        5

Je complèterai cette liste à fur et à mesure pour tous les autres jeux ne fonctionnant pas en .dsk.



POUR RESTER EN DSK

Il n'y a pas eu que les jeux à adopter des numéros de volume spéciaux.
Ce fut aussi le cas par exemple pour la série des disquettes vendues avec le magazine Tremplin Micro.
Chaque disquette avait comme numéro de volume le numéro du magazine (tout au moins la face en DOS 3.3).
Lorsqu'on lance un .dsk de ces disquettes, on obtient le beau plantage suivant:

Tremplin Micro

Quand le DOS est standard, il est plus intéressant à mon sens de garder la disquette sous forme d'un .dsk que de passer en un .nib.
Il faut alors modifier le DOS présent sur chaque disquette pour qu'il accepte ce numéro de volume standard 254.
Pour les Tremplin Micro, bien que le DOS ait été trafiqué (il affiche un "MicroBasic" et reste extrèmement lent),  il utilise tout de même une RWTS classique.

Plutôt que de rechercher toutes les tables de paramètres utilisées lors des appels de RWTS, le plus simple, c'est encore de modifier cette routine pour forcer un bon résultat du contrôle du numéro de volume.

Pour cela, je vous propose 2 patchs:

D'abord sur la routine de controle basique:

Tremplin Micro

Au lieu de faire un LDA (IOBPL),Y avec Y=3 pour recherche le numéro de volume demandé (ici le numéro du magazine), on force un LDA #$0 pour avoir toujours un volume 0.

Puis au cas où sur la routine de formattage:

Tremplin Micro

Même modif.

Ca marche à ce que j'ai pu voir pour quelques numéros testés. J'ai essayé quelques points de menu aussi...
La seule différence "visible" par rapport à la version initiale, c'est quand on fait un catalog.

On a volume 254 au lieu du numéro du magazine:

Tremplin Micro

Le patch avec un éditeur de secteurs (seulement 4 octets à changer ):

Track $00 Sector $08:
octets $12/$13 avant=B1 48. Le patch: mettre A9 00.
octets $B1/$B2 avant=B1 48. Le patch: mettre A9 00.



ASTUCE

Il y a un truc intéressant à savoir si par exemple vous avez récupéré un .dsk sur internet et qu'il ne fonctionne pas car il faut un numéro de volume spécial.
Quand vous avez créé une disquette physique avec ce .dsk, vous l'avez probablement formatté avec le volume standard 254.
Et bien est il possible de changer le numéro de volume sans devoir tout reformatter?
Oui, l'outil INIT de Bag Of Tricks permet de modifier le numéro de volume sans altérer les données. Il suffit juste de laisser l'option "PRESERVE DATA" à YES.
Dans les écrans ci-dessous, un exemple pour forcer un numéro de volume = 55 (soit $37 en hexadécimal) et le contrôle final avec Disk Fixer:

Init from Bag Of Tricks
Init from Bag Of Tricks
Init from Bag Of Tricks
Init from Bag Of Tricks
Init from Bag Of Tricks
Init from Bag Of Tricks

hr dsk2nib

7) Les sources de dsk2nib


Source dsk2nib.java:

import java.io.File;

/*
 * dsk2nib
 *
 * Deckard 20081114
 * Utilise la routine de nibblizing de l'applet Applelet (émulateur Apple II java)
 *
 */
public class dsk2nib {

    /**
     * @param args
     */
    public static void main(String[] args) {

        // Si pas assez de paramètres, rappel de la syntaxe de la commande
        if (args.length < 2) {
            System.err.println("Usage: java -jar dsk2nib.jar volume[1-254] .dsk file(s)");
            return;
        }

        // Vérifie que le volume renseigné est numérique
        if(!isNumeric(args[0])) {
            System.err.println("volume must be numeric!");
            return;
        }
       
        // Volume à affecter
        int vol = Integer.valueOf(args[0]).intValue();
        // Vérifie fourchette de validité
        if (vol < 1 || vol > 254) {
            System.err.println("Bad volume!");
            return;
        }

        // Traite les .dsk les 1 après les autres
        // A noter que si vous tapez *.dsk ou nom*.dsk, il y aura autant de parms que de fichiers matchés
        for (int i = 1; i < args.length; i++) {
            File f = new File(args[i]);

            // Vérifie que le .dsk est correct
            if (isDSKOkay(f)) {
                // Construit le nom du .nib à créer
                String nameNIB = new String(f.getName().substring(0, f.getName().length() -3).concat("nib") );       
                // Initialise le .nib en mémoire
                NIBImage myNIB = new NIBImage();
                // Alimentation depuis le .dsk
                myNIB.fillNIBWithDSK(f.getName(), vol);
                // Sauvegarde du .nib
                myNIB.saveNIB(nameNIB);
            }
       
        }

        System.out.println("FINISHED!");
       
    }

    /**
     * Test la numéricité d'une chaine
     *
     * @param String zoneTest (non null)
     * @return Boolean result
     */
    public static boolean isNumeric(String zoneTest) {
        // Décompose dans un tableau de char
        final char[] chars = zoneTest.toCharArray();
        // Teste chaque caractère
        for (int x = 0; x < chars.length; x++) {     
           final char c = chars[x];
           if ((c >= '0') && (c <= '9')) continue; // numeric
           return false;
         } 
         return true;
    }
   
    /**
     * Test un .dsk
     *
     * @param File f (non null)
     * @return Boolean result
     */
    public static boolean isDSKOkay(File f) {
       
        // Vérifie que c'est bien un .dsk
        if (!f.getName().toLowerCase().endsWith(".dsk")) {
            System.err.println(f.getName() + " is not a .dsk file!");
            return false;
        }
       
        // Vérifie que le nom de .dsk donné n'est pas bidon
        try {
            if (!f.exists()) {
                System.err.println("Can't find "  + f.getName() + "!");
                return false;
            }       
        } catch (SecurityException x) {
            System.err.println("Security err (exists)!");
        }

        // Vérifie la taille du .dsk
        try {
            if (f.length() != 143360) {
                System.err.println(f.getName() + " has a bad length!");
                return false;
            }
        } catch (SecurityException x) {
            System.err.println("Security err (length)!");
        }
       
        // OK
        return true;
       
    }
   
}

Source NIBImage.java:

import java.io.FileInputStream;
import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;


public class NIBImage
{
   // Contenu du .nib
   public byte data[][];
  
   // Nombre de pistes par face de disquette
   static int NUM_TRACKS = 35;
   // Nombre de secteurs par piste d'un .dsk
   static int NUM_SECTORS = 16;
   // Taille d'une piste du .nib
   static int TRACK_SIZE = 0x1a00;
   // Nombre de nibbles correspondant à 1 secteur complet du .dsk
   static int SECTOR_SIZE = 409;
  
  
   public NIBImage()
   {
       // Init l'espace de stockage
       data = new byte[NUM_TRACKS][TRACK_SIZE];
   }
  
    /*
     *  Alimentation du contenu du .nib en mémoire à partir du .dsk
     */
    void fillNIBWithDSK(String filename, int volume) {
        System.out.println("Loading disk image " + filename);
        try {
            FileInputStream imgFile = new FileInputStream(filename);
            DataInputStream s = new DataInputStream(imgFile);
            // Buffer contenant une piste du .dsk
            // 1 piste = 16 secteurs * 256 octets = 4096 = $1000
            byte buf[] = new byte[0x1000];
           
            // Lecture 1 à 1 des pistes du .dsk
            for (int trk = 0; trk < NUM_TRACKS; trk++) {
                // Stocke la piste lue dans le buffer buf
                s.readFully(buf, 0, 0x1000);
                // Alimente la piste équivalente du .nib
                this.data[trk] = NIBImage.nibblizeTrack(volume, trk, buf);
            }
           
        } catch (IOException e) {
            System.err.println("Could not load disk image");
        }
    }

    /*
     *  Sauvegarde du contenu du .nib en mémoire
     */
    void saveNIB(String filename) {
        System.out.println("Saving new disk image " + filename);
        try {
            FileOutputStream imgFile = new FileOutputStream(filename);
            DataOutputStream s = new DataOutputStream(imgFile);
           
            // Ecriture 1 à 1 des pistes du .nib
            for (int trk = 0; trk < NUM_TRACKS; trk++) {
                s.write(this.data[trk], 0, TRACK_SIZE);
            }
           
        } catch (IOException e) {
            System.err.println("Could not save new disk image");
        }
    }
  
       
    /*
     * Nibblize les 16 secteurs d'une piste de .dsk
     */
    public static byte[] nibblizeTrack(int vol, int trk, byte in[]) {
        // Buffer 1 piste du .nib
        byte out[] = new byte[TRACK_SIZE];
        // Pointeur dans le buffer de piste
        int out_pos = 0;

        // Remplit le buffer de piste avec les 16 secteurs nibblizés
        for (int sector = 0; sector < NUM_SECTORS; sector++) {
            nibblizeSector(vol, trk, sector, in, skewing_table[sector] << 8,
                    out, out_pos);
            // Positionne après le secteur traité
            out_pos += SECTOR_SIZE;
        }

        // Complète avec des $FF le restant de la piste
        while (out_pos < TRACK_SIZE)
            out[out_pos++] = (byte) (0xff);

        return out;
    }

    /*
     * Encode a 256-byte sector as SECTOR_SIZE disk bytes as follows:
     *
     *  40 sync bytes             : FF FF ...
     *   3 address header bytes   : D5 AA 96
     *   8 address block bytes
     *   3 address trailer bytes  : DE AA EB
     *   6 sync bytes             : FF FF FF FF FF FF
     *   3 data header bytes      : D5 AA AD
     * 343 data block bytes
     *   3 data trailer bytes     : DE AA EB
     * ===
     * 409 bytes         (409 * 16 sectors = $1990)
     */
    public static void nibblizeSector(int vol, int trk, int sector, byte in[],
            int in_ofs, byte out[], int i) {
        int loop, checksum, prev_value, value;
        int sector_buffer[] = new int[258];
        value = 0;

        /*
         * Step 1: write 40 sync bytes (0xff's).
         */
        for (loop = 0; loop < 40; loop++)
            out[i++] = (byte) 0xff;

        /*
         * Step 2: write the 3-byte address header (0xd5 0xaa 0x96).
         */
        out[i++] = (byte) 0xd5;
        out[i++] = (byte) 0xaa;
        out[i++] = (byte) 0x96;

        /*
         * Step 3: write the address block. Use 4-and-4 encoding to convert the
         * volume, track and sector and checksum into 2 disk bytes each. The
         * checksum is a simple exclusive OR of the first three values.
         */
        out[i++] = (byte) ((vol >> 1) | 0xaa);
        out[i++] = (byte) (vol | 0xaa);
        checksum = vol;
        out[i++] = (byte) ((trk >> 1) | 0xaa);
        out[i++] = (byte) (trk | 0xaa);
        checksum ^= trk;
        out[i++] = (byte) ((sector >> 1) | 0xaa);
        out[i++] = (byte) (sector | 0xaa);
        checksum ^= sector;
        out[i++] = (byte) ((checksum >> 1) | 0xaa);
        out[i++] = (byte) (checksum | 0xaa);

        /*
         * Step 4: write the 3-byte address trailer (0xde 0xaa 0xeb).
         */
        out[i++] = (byte) (0xde);
        out[i++] = (byte) (0xaa);
        out[i++] = (byte) (0xeb);

        /*
         * Step 5: write another 6 sync bytes.
         */
        for (loop = 0; loop < 6; loop++)
            out[i++] = (byte) (0xff);

        /*
         * Step 6: write the 3-byte data header.
         */
        out[i++] = (byte) (0xd5);
        out[i++] = (byte) (0xaa);
        out[i++] = (byte) (0xad);

        /*
         * Step 7: read the next 256-byte sector from the old disk image file,
         * and add two zero bytes to bring the number of bytes up to a multiple
         * of 3.
         */
        for (loop = 0; loop < 256; loop++)
            sector_buffer[loop] = in[loop + in_ofs] & 0xff;
        sector_buffer[256] = 0;
        sector_buffer[257] = 0;

        /*
         * Step 8: write the first 86 disk bytes of the data block, which
         * encodes the bottom two bits of each sector byte into six-bit values
         * as follows:
         *
         * disk byte n, bit 0 = sector byte n, bit 1 disk byte n, bit 1 = sector
         * byte n, bit 0 disk byte n, bit 2 = sector byte n + 86, bit 1 disk
         * byte n, bit 3 = sector byte n + 86, bit 0 disk byte n, bit 4 = sector
         * byte n + 172, bit 1 disk byte n, bit 5 = sector byte n + 172, bit 0
         *
         * The scheme allows each pair of bits to be shifted to the right out of
         * the disk byte, then shifted to the left into the sector byte.
         *
         * Before the 6-bit value is translated to a disk byte, it is exclusive
         * ORed with the previous 6-bit value, hence the values written are
         * really a running checksum.
         */
        prev_value = 0;
        for (loop = 0; loop < 86; loop++) {
            value = (sector_buffer[loop] & 0x01) << 1;
            value |= (sector_buffer[loop] & 0x02) >> 1;
            value |= (sector_buffer[loop + 86] & 0x01) << 3;
            value |= (sector_buffer[loop + 86] & 0x02) << 1;
            value |= (sector_buffer[loop + 172] & 0x01) << 5;
            value |= (sector_buffer[loop + 172] & 0x02) << 3;
            out[i++] = (byte) (byte_translation[value ^ prev_value]);
            prev_value = value;
        }

        /*
         * Step 9: write the last 256 disk bytes of the data block, which
         * encodes the top six bits of each sector byte. Again, each value is
         * exclusive ORed with the previous value to create a running checksum
         * (the first value is exclusive ORed with the last value of the
         * previous step).
         */

        for (loop = 0; loop < 256; loop++) {
            value = (sector_buffer[loop] >> 2);
            out[i++] = (byte) (byte_translation[value ^ prev_value]);
            prev_value = value;
        }

        /*
         * Step 10: write the last value as the checksum.
         */
        out[i++] = (byte) (byte_translation[value]);

        /*
         * Step 11: write the 3-byte data trailer.
         */
        out[i++] = (byte) (0xde);
        out[i++] = (byte) (0xaa);
        out[i++] = (byte) (0xeb);
       
    }

    // Normal byte (lower six bits only) -> disk byte translation table.
    static int byte_translation[] = {
            0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
            0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
            0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
            0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
            0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
            0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
            0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
            0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };

    // Sector skewing table.
    static int skewing_table[] = {
        0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 };
   
}