23 octobre 2023

Mettre du code dans un header AMDOS

Par Claire CheshireCat

Toutes les personnes qui ont joué un peu avec le système de gestion de disques de l’Amstrad vous diront qu’il prend un peu trop ses aises. Il réserve 128 octets pour un header de fichier alors que seuls une quarantaine sont utilisés : Il y a 90 octets de zéros pour chaque fichier que vous mettez sur votre disquette ! C’est parfois problématique quand on veut faire le code le plus compact possible : Si vous voulez qu’un programme prenne au maximum 1 ou 2K sur la disquette, vous avez en réalité 1024-128 octets, 2048-128 octets à votre disposition. C’est très handicapant si vous voulez participer à des concours de programmation multi-machines où on vous impose une taille limite. Les C64 par exemple n’ont que deux octets de header (Merci Monos) dans leurs fichiers et ont donc 1024-2 ou 2048-2 octets à leur disposition.

À la conquête des octets perdus !

Il faut savoir que le CPC copie le header en mémoire lorsqu’il charge un fichier. Donc ces octets sont déjà dans la RAM quand votre programme démarre. Il suffit de savoir où les chercher. Je vous épargne le boulot, j’ai un peu souffert pour faire fonctionner le code parce que la zone dans laquelle se trouve le header bouge en fonction de divers paramètres, notamment si vous avez installé des ROMS ou non (J’en profite pour remercier Candy/OVL qui a pointé ce problème).
Pour trouver l’adresse des données du header il faut aller chercher du côté de #BE7D. Il y a ensuite un petit décalage à appliquer pour trouver la zone non utilisée du header (Attention, au milieu de la zone en question l’Amsdos utilise 2 ou 3 octets. Je vous laisse fouiller dans le code pour trouver les octets concernés).

Pour faire court, le principe consiste alors à recopier ces données quelque part pour l’utiliser dans vos programmes.
Dans le code d’exemple (non fonctionnel, c’est juste pour vous donner les clefs parce que chaque utilisation est différente) j’ai mis ZX0, cela vous permettra d’avoir votre petit décompresseur sous la main pour pas cher.

Le code utilise DSKLua et le « ZX0 Amsdos Header » que vous pouvez trouver sur mon Github.

Plaintext
    DEVICE AMSTRADCPC6128
    include "std/dsk.asm"

DECOMPRESSED_DATA	equ #4000
BOOT_CODE 		equ #2000

;=======================================================
; Code de décompression
;=======================================================
    org BOOT_CODE
decompress:
    // Get the address of the header in RAM - Thank you Candy !
    ld hl,(#BE7D)
    ld bc,#00E4 + 32 //+ decompress_header - dzx0_amsdosheader
    add hl,bc
    ld de,#2000-#80+32
    ld bc,#80-32
    ldir	// On recopie le code du décompresseur à l'endroit désiré
    jr #2000-#80+32+decompress_header-dzx0_amsdosheader
CODE_COMPRESSE:
    include "code_compresse.zx0"
END_CODE_DECOMPRESSE:
;========================================================
; Code qu'on va retrouver dans le header
;========================================================
HEADER:
    include "std/extra/dzx0_amsdosheader.asm"
decompress_header:
    di

    ld hl,CODE_COMPRESSE
    ld de,DECOMPRESSED_DATA
    ld bc,DECOMPRESSED_DATA
    push bc // ZX0 fait un ret quand il a fini la décompression
    jr dzx0_amsdosheader

;=========================================================
; Code LUA pour coller les bouts ensemble dans un DSK
;=========================================================
    LUA
        local decompress = sj.get_label("decompress")
        local size = sj.get_label("END_CODE_COMPRESSE") - decompress
        local cpt

-- Les données compressées vont dans un string
        local frombyte = sj.get_label("CODE_COMPRESSE")
        local tobyte = sj.get_label("HEADER")
        local datablock = ""

        for cpt = frombyte,tobyte-1,1 do
            datablock = datablock .. string.char(sj.get_byte(cpt))
        end

-- On colle les données du header dans un string
        local header_address = sj.get_label("HEADER")
        local header=""

        for cpt = header_address-32,header_address+128-32,1 do
            header = header .. string.char(sj.get_byte(cpt))
        end

-- On LuaDSK met les bonnes valeurs aux bons endroits pour que notre pâté d'octets
-- soit interprété comme un header valide
        local blockdata = dsk.populateheader(header,0,"TOTO.BIN",dsk.AMSDOS_FILETYPE_BINARY
                                   ,decompress,decompress,string.len(datablock))..datablock

	-- On sauvegarde !
        dsk.saveamsdosfile(0,"TOTO.BIN",blockdata)
    LUA

Pan sur la truffe !

À l’origine j’avais noté dans le billet que la bidouille s’appliquait aussi pour les programmes 4k (4096-128 octets), mais apparemment ça n’est pas le cas ! A priori le problème réside dans le fait que l’Amsdos efface les 59 derniers octets de la zone quand la taille du fichier + header dépasse les 2 kilo-octets (soit un bloc dans le langage Amsdos). Mes plus plates excuses vont à Krusty/Benediction pour l’avoir induit en erreur. Et dans la foulée, tous mes remerciements à lui pour m’avoir signalé le problème !