Table des matières

Le format ROMDISK de la Casio Graph100/Algebra FX 2/FX 1 (+)

version 2 - mastermage <mastermage AT bluelab DOT net>

Introduction

Le format ROMDISK, utilisé par ROM-DOS pour le stockage des fichiers sur graph100, n’est autre qu’une déclinaison du système de fichiers FAT de Microsoft pour MS-DOS. J’expliquerai ici les différentes parties de ce système de fichiers, mais vous pouvez toujours aller voir la documentation officielle de Microsoft.

Generalités

Les processeurs Intel utilisent un mode de stockage appelé Litte Endian (par opposition au Big Endian). Pour des raisons historiques (MS-DOS ayant été créé sur des PC) le système de fichiers FAT est lui aussi en L.E. Mais qu’est-ce que le Little Endian ? C’est tout simplement de mettre en mémoire l’octet le moins significatif (le LSB pour Less Signifiant Byte, en anglais) en premier, et l’octet le plus significatif en dernier (le MSB, le Most Signifiant Byte). exemple: la valeur 0x1D4EF28C sera en mémoire 8C F2 4E 1D. Je ne sais pas pourquoi cette organisation plutôt que l’autre (le Big Endian est tout simplement le contraire) mais il faut juste s’y habituer. D’autre part ça signifie que sur une machine de type Intel vous n’aurez rien a changer (vu que la plupart des gens ont un PC) mais dans le cas d’un Mac par exemple il faudra faire l’inversion des octets !

Les valeurs précédées d’un 0x sont en hexadécimal, les autres valeurs sont en décimal.

Structure d'un lecteur ROMDDISK

Un système de fichiers de type FAT est composé de quatre zones (dans l’ordre) :

  1. une zone reservée
  2. la zone contenant la File Allocation Table, ou FAT (d’où le nom du système de fichiers)
  3. la zone du répertoire racine (Root Directory)
  4. la zone des dossiers et fichiers

Secteur de Boot /BPB

La première partie importante d’un volume FAT, c’est sa zone reservée, contenant le BPB (BIOS Parameter Block) qui est situé dans le tout premier secteur du volume, le secteur communément appelé secteur de boot. Le BPB est une structure contenant beaucoup d’information très utiles sur le système de fichiers.

J’utiliserai les noms donnés par Microsoft, parce que d’une part ils sont bien choisis et d’autre part pour s’y retrouver par rapport à d’autres documentations sur le système de fichiers FAT.

Nom Offset (octets) Longueur (octets) Description
BS_jmpBoot 0 3 Instruction de saut vers le code de Boot, peut prendre deux formes différentes : jmpBoot[0] = 0xEB, jmpBoot[1] = 0x??, jmpBoot[2] = 0×90 ou jmpBoot[0] = 0xE9, jmpBoot[1] = 0x??, jmpBoot[2] = 0x?? 0x?? veut dire qu’on peut placer n’importe quelle valeur dans cet octet. En fait c’est une instruction de saut inconditionnel, dans le langage machine x86 (et donc du V30 qui équipe les graph100s), vers une routine d’initialisation du système d’exploitation. C’est d’ailleurs le code de Boot qui occupe le reste de la zone réservée apres le BPB. La première version est plus fréquemment utilisée.
BS_OEMName 3 8 Sur graph100 c’est “DLRDISK” (suivi de la valeur 0 pour occuper les 8 octets). La documentation de Microsoft affirme que ce champ n’est qu’une chaîne sans importance indiquant quel système a formatté le volume , mais aussi que certains pilotes (drivers) y font attention. Dans l’incertitude par rapport à la graph100 mieux vaut utiliser “DLRDISK”.
BPB_BytsPerSec 11 2 Nombre d’octets par secteur. Ce champ peut prendre les valeurs suivantes: 512, 1024, 2048 ou 4096, mais pour un maximum de compatibilité mieux vaut opter pour 512, ce qui est de toute façon le meilleur choix sur graph100 vu la taille des volumes.
BPB_SecPerClus 13 1 Nombre de secteurs par unité d’allocation (ou cluster). Sur graph100 ce champ est à 1 car le système de fichiers est sur ROM ou FLASH.
BPB_RsvdSecCnt 14 2 Nombre de secteur constituant la zone reservée au début du volume (c’est la zone où nous nous situons actuellement). Ce champ ne doit pas être nul, et devrait toujours être à 1 pour des raisons de compatibilité a cause des drivers ne faisant pas attention à cette valeur. (je ne sais pas si la graph100 y fait attention, mais normalement elle devrait pouvoir utiliser des valeurs autres que 1)
BPB_NumFATs 16 1 Nombre de structures FAT sur le volume. Normalement ce champ contient 2 pour avoir une FAT redondante (en cas de défaillance du disque dur par exemple) mais sur graph100 il n’y en a pas besoin vu que le système de fichiers est sur ROM ou FLASH.
BPB_RootEntCnt 17 2 Ce champ contient le nombre maximal d’entrées de 32 octets dans le répertoire racine (Root directory) que nous aurons le loisir de voir plus tard. Cette valeur, multipliée par 32, doit donner un multiple de taille de secteur (voir plus haut BPB_BytsPerSec).
BPB_TotSec16 19 2 Ce champ correspond au nombre de secteur du volume. ce champ ne doit pas être égal à 0 sur graph100.
BPB_Media 21 1 La valeur standard de ce champ est 0xF8, ce qui veut dire que le Media (le support du volume) est fixe (ne peut pas être enlevé du système), et c’est le cas pour la graph100. L’autre point important, c’est le fait que cette valeur doit être aussi mise dans le premier octet (Low Byte) de la FAT.
BPB_FATSz16 22 2 Ce champ correspond au nombre de secteur occupé par une FAT. Il ne doit pas être égal à 0 sur graph100.
BPB_SecPerTrk 24 2 Secteurs par piste, normalement utilisé par l’interruption 0×13 pour la géometrie des disques. C’est inutile pour la graph100, et cette valeur est à 0xF000.
BPB_NumHeads 26 2 Même remarque que pour le champ précédent. La valeur sur graph100 est 1.
BPB_HiddSec 28 4 Nombre de secteurs cachés avant la FAT, aussi utilisé par l’interruption 0×13. Inutile sur graph100, doit être mis à 0.

Je rappelle que ma description est spécifique à la graph100, certaines remarques ne sont pas valables pour d’autres matériels.

Une autre chose très importante pour le premier secteur du système de fichiers FAT:

sector[510] = 0x55 et sector[511] = 0xAA.

ET CELA QUELLE QUE SOIT LA TAILLE D’UN SECTEUR (qui de toute façon a une taille supérieure ou égale à 512) !

Enfin, sur graph100, les zones non utilisées sont remplies avec la valeur 0xFF.

La structure de la FAT (File Allocation Table)

La seconde partie importante du système de fichiers FAT, c’est la FAT elle-même. Cette structure définit une simple liste chainée (eh oui il faut revoir vos algorithmes et structures de données) pour mémoriser les différents emplacements des morceaux d’un même fichier (la défragmentation sert à optimiser la FAT et l’emplacement des fichiers, pour qu’un même fichier ne soit plus eparpillé sur tout un disque dur). Les fichiers et les dossiers sont traités exactement de la même façon: un dossier n’est ni plus ni moins qu’un fichier contenant une liste d’autres fichiers, chaque entrée étant sur 32 octets. La FAT gère des clusters, et le premier cluster de données est le cluster 2 (on verra pourquoi ensuite) Sur graph100 1 cluster contient 1 secteur donc il n’y a pas de différence de taille, mais le secteur 0 est le secteur de boot, alors que le cluster 2 est le premier secteur de données. Il ne faut pas confondre ces deux termes.

On calcule le premier secteur de données comme ceci sur graph100 (une seule FAT) :

FirstDataSector = BPB_ResvdSecCnt + BPB_FATSz16 + RootDirSectors;

avec RootDirSectors le nombre de secteurs occupé par le répertoire racine, qu’on peut calculer de la manière suivante :

RootDirSectors = ((BPB_RootEntCnt * 32) + (BPB_BytsPerSec - 1)) / BPB_BytsPerSec;  

Le type de FAT : Sur graph100 la FAT c’est de la FAT12. Le type de FAT est déterminé en fonction du nombre de clusters qu’elle doit gérer. Ainsi, pour un nombre de clusters < 4085 (strictement), le type de FAT est FAT12. Vu la taille des volumes sur graph100, le système de fichiers est OBLIGATOIREMENT FAT12 ! si vous voulez plus de précisions sur ce genre de chose, réferrez vous a la documentation officielle.

La FAT12 est plus compliquée a gérer que la FAT16 ou même la FAT32, car alors que pour ces deux derniers chaque entrée de la FAT est sur 2 ou 4 octets, pour la FAT12 chaque entrée est codée sur 1.5 octets.

Pour trouver le bon emplacement, il faut faire :

FATOffset = N + (N / 2);  

Avec N le numéro du cluster recherché.

Et en termes de secteur :

NumeroSecteur = BPB_ResvdSecCnt + (FATOffset / BPB_BytsPerSec);  
OffsetSecteur = FATOffset % BPB_BytsPerSec;  

Il faut maintenant recupérer le numéro de cluster sur 12 bits en mémoire, et ce n’est pas évident, sachant que les valeurs 12 bits sont codées en L.E., tout en étant les unes à la suite des autres : ainsi les valeur 0×012 et 0×345, respectivement 12 0 et 5 43 (on met le 5 a part car seulement 4 bits pourront être placés dans le premier octet) en L.E. seront à la suite sous cette forme en mémoire : 12 50 34

On peut procéder de la sorte :

si N est impair: Valeur = Valeur << 4;  
si N est pair: Valeur = Valeur & 0x0FFF;  

avec N l’index de la valeur qui a servi a calculer FATOffset.

Il suffit ensuite de ranger cette valeur 16 bits dans la FAT, à l’endroit calculé avec FATOffset (attention à effacer les bits correspondants, si N est impair: c’est & 0x000F et si N est pair: c’est & 0xF000, il suffit ensuite de faire un OU sur la FAT avec la valeur. )

Et inversement pour récuperer la valeur : on va chercher dans la FAT la valeur 16 bits, puis:

si N est impair : Valeur = Valeur >> 4;  
si N est pair : Valeur = Valeur & 0x0FFF;  

avec N l’index de la valeur qui a servi a calculer FATOffset.

Maintenant voila l’organisation de la FAT par rapport aux fichiers :

Attention ! Les index de cluster commencent à 2 (surement pour pouvoir calculer plus facilement les adresses en FAT12)

Ce qui donne en pratique:

ValeurCluster(NumeroCluster) = NumeroClusterSuivant ou FinFichier ou SecteurDefectueux.

Voila le principe de la FAT !

IMPORTANT ! Un des meilleurs moyens de comprendre est de regarder une image ROMDISK avec un éditeur héxadecimal. D’autre part les lecteurs générés par les outils de création de lecteur ont leurs fichiers sur des clusters contigus.

La structure des Répertoires - le root

Un dossier dans le système de fichiers FAT n’est rien d’autre qu’un “fichier” composé d’une liste d’entrées de 32 octets. Un seul Dossier doit être TOUJOURS présent, c’est le répertoire racine (Root Directory). dans le cas de la graph100, le root est situé dans un emplacement spécial, après la FAT, et n’est pas compris dans les cluster de données, ceux-ci démarrant APRÈS le root. La taille du root est déterminée par le champ BPB_RootEntCnt dans le secteur de boot.

Son secteur est :

FirstRootDirSecNum = BPB_ResvdSecCnt +  BPB_FATSz16;  

Structure d'une entrée de 32 octets dans une liste de fichiers

Name Offset (octets) Taille (octets) Description
DIR_Name 0 11 Nom du fichier
DIR_Attr 11 1 Attributs du fichier. Les deux derniers bits (les plus significatifs) doivent être mis à 0 lors de la création du fichier (ils sont censés être resevrés par le système d’exploitation mais sur graph100 ils sont inutiles car non modifiables)
DIR_NTRes 12 1 La documentation de microsoft annonce : “reservé pour WindowsNT”. On ne s’en occupe pas et on met ce champ à 0.
DIR_CrtTimeTenth 13 1 Champ pour les dixièmes de secondes qui sert pour les... centièmes de seconde. La précision du champ DIR_CrtTime est de deux secondes, donc DIR_CrtTimeTenth sera entre 0 et 199 dixièmes de seconde inclus pour combler cette lacune.
DIR_CrtTime 14 2 L’heure à laquelle le fichier a été créé. Le format de l’heure, codée sur 16 bits est : * Bits 0-4: Secondes/2, 0-29 inclus, (0-58 secondes) * Bits 5-10: Minutes, 0-59 inclus. * Bits 11-15: Heures, 0-23 inclus.
DIR_CrtDate 16 2 La date à laquelle le fichier a été créé. Le format de la date, codé sur 16 bits est * Bits 0-4: Jour du mois, 1-31 inclus * Bits 5-8: Mois de l’année, 1 = Janvier, 1-12 inclus. * Bits 9-15: Années depuis 1980, 0-127 inclus (1980-2107).
DIR_LstAccDate 18 2 Date du dernier accès au fichier (il n’y a pas la gestion de l’heure du dernier accès au fichier) autant en lecture qu’en écriture. Si c’est en écriture cette date doit être la même que DIR_WrtDate.
DIR_FstClusHI 20 2 Mot le plus significatif du numéro du premier cluster du fichier. Toujours à 0 sur un système de fichiers FAT12 et donc sur graph100.
DIR_WrtTime 22 2 Heure de la dernière écriture sur le fichier. La création du fichier est considérée comme une écriture.
DIR_WrtDate 24 2 Date de la dernière écriture sur le fichier. La création du fichier est considérée comme une écriture.
DIR_FstClusLO 26 2 Mot le moins significatif du numéro du premier cluster du fichier, c’est lui seul qui détermine le premier cluster du fichier en FAT12 (et donc sur graph100)
DIR_FileSize 28 4 Taille du fichier sur 32 bits.

Quelques précisions :

Les attributs de fichier

ATTR_READ_ONLY 0×01 L’écriture sur ce fichier devrait échouer.
ATTR_HIDDEN 0×02 Le fait de lister un repértoire ne doit pas afficher les fichiers/dossiers cachés.
ATTR_SYSTEM 0×04 Le fichier appartient au système d’exploitation.
ATTR_VOLUME_ID 0×08 Il ne doit y avoir qu’un seul “fichier” sur le volume ayant cet attribut, et ce fichier doit être dans le root. Le nom de ce fichier est le nom du volume. DIR_FstClusHI et DIR_FstClusLO doivent toujours être a 0 pour le nom du volume, car aucun cluster n’est associé à cette entrée. Sur Graph100, le nom de volume est “ROM-DISK”
ATTR_DIRECTORY 0×10 Ce ficher est un Dossier, il contient d’autres fichiers.
ATTR_ARCHIVE 0×20 Cet attribut est mis sur un fichier lorsqu’il a été modifié, et ce pour que les utilitaires d’archivage et de compression puissent déterminer quels fichiers ont été modifiés depuis le dernier archivage.

Les dates

Seuls DIR_WrtTime et DIR_WrtDate sont obligatoires, les autres champs sont optionnels et peuvent être mis à 0.

Les noms de fichier

Considérons DIR_Name comme un tableau de caractères :

La valeur 0 indique au système de fichiers que le reste des entrées n’a pas besoin d’être analysé car elles sont toutes libres.

DIR_Name est constitué de deux parties: le nom(8 caractères) et l’extension (3 caractères). Les caractères non utilisés sont remplis par le caractère espace (0×20).

DIR_Name[0] ne doit pas être égal à 0×20 : Il y a un caractère ‘.’ implicite entre le nom et l’extension dans DIR_Name. Les caractères minuscules ne sont pas autorisés.

Les valeurs suivantes ne sont pas autorisées :

Spécificités des dossiers

Lors de la création d’un dossier, il faut mettre ATTR_DIRECTORY dans ses attributs, puis mettre son champ DIR_FileSize à 0 (DIR_FileSize est inutilisé et toujours mis à 0 avec ATTR_DIRECTORY), on determinera alors la taille d’un dossier en calculant le nombre de clusters qu’il occupe grâce à la FAT. Un cluster est alloué au dossier et on modifie le champ DIR_FstClusLO pour qu’il pointe sur le cluster alloué, puis on place une marque de fin de cluster sur le cluster du dossier dans la FAT. Ensuite on initialise tous les octets du cluster à 0.

On doit ensuite créer deux entrées : le première doit avoir pour nom “.” et la deuxième “..”, c’est la seule exception à la rêgle des noms de fichiers.

Le champ DIR_FileSize de ces deux entrées est mis à 0, et tous les champs de date et d’heure de ces entrées sont mises aux mêmes valeurs que pour le dossier que l’on vient juste de créer, puis on fait pointer DIR_FstClusLO de “.” sur le cluster du nouveau dossier, et DIR_FstClusLO de “..” sur le cluster du dossier parent (0 si le dossier parent est le root)

Spécificités des fichiers

Si un secteur n’est pas complétement utilisé par un fichier, il faut le combler avec la valeur 0xFF.

Historique /Remerciements / etc ...

Merci à mes doigts, ma tête, le café, Superna qui a retrouvé la première version de ma doc, 2072 (Touche parce que je le vaux bien), microsoft (c’est très rare, mais bon la doc sur le FAT ça vient de chez eux), et tous les autres...