IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Assembleur Intel avec NASM


précédentsommairesuivant

VII. Assembleur : reconstruction

68 commentaires Donner une note à l´article (5)

Relu par ClaudeLELOUP.

Depuis la création du secteur d'amorçage au chapitre précédent, je ne veux pas dire qu'on n'a plus rien, mais peu s'en faut. Les routines sont un peu bringuebalantes, tout ça. Cela nécessite une petite remise à niveau.

VII-1. Du foutoir

Il faut avouer ce qui est : il devient rude de retrouver quoi que ce soit dans le code. NASM permet de s'y retrouver un peu mieux en séparant le code en plusieurs fichiers. La directive %include "[nom du fichier]" permet d'insérer à cet endroit le contenu du fichier spécifié.

VII-2. De l'affichage de texte

Les routines d'affichage de texte ont été déportées dans un fichier "affichageTexte.asm". Il y a deux fonctions : une fonction qui affiche une chaîne de caractères à l'écran "affiche_chaine", et une fonction qui transforme un nombre en chaîne de caractères "nombre_vers_chaine". Il faut garder à l'esprit que ces fonctions ont vocation à disparaître, puisqu'elles fonctionnent en mode texte, qui disparaît rapidement. Elles sont reprises du chapitre 3, clarifiées, déboguées et ainsi de suite.

VII-2-a. affiche_chaine

Cette fonction affiche la chaîne de caractères pointée par si et se terminant par le caractère 0, aussi appelé caractère nul, de valeur numérique 0. Elle suppose que l'écran mesure 80 caractères de large sur 25 de haut. En cas de débordement, elle efface la page et continue son écriture en haut. Elle ne connaît que deux caractères non affichables : le 0 et le 13, respectivement marqueur de fin de chaîne et retour à la ligne.

 
Sélectionnez
					;Affiche à l'écran une chaîne de caractères
					affiche_chaine:
						push ax
						push bx
						push cx
						push dx
						xor bh, bh; RAZ de bh, qui stocke la page d'affichage
						mov ah, 0x03
						int 0x10; appel de l'interruption BIOS qui donne la position du curseur, stockée dans dx
						mov cx, 1; nombre de fois où l'on va afficher un caractère
					affiche_suivant:
						lodsb
						or al, al;on compare al à zéro pour s'arrêter
						jz fin_affiche_suivant
						cmp al, 13
						je nouvelle_ligne
						mov ah, 0x0A;on affiche le caractère courant cx fois
						int 0x10
						inc dl; on passe à la colonne suivante pour la position du curseur
						cmp dl, 80
						jne positionne_curseur
					nouvelle_ligne:
						inc dh; on passe à la ligne suivante
						xor dl, dl; colonne 0
						cmp dh, 25
						jb positionne_curseur
						xor dh, dh
						mov ah, 0x02;on positionne le curseur
						int 0x10
						mov cx, 25*80
						mov ah, 0x0A;on affiche le caractère courant cx fois
						mov al, ' '
						int 0x10
						mov cx, 1
					positionne_curseur:
						mov ah, 0x02;on positionne le curseur
						int 0x10
						jmp affiche_suivant
					fin_affiche_suivant:
						pop dx
						pop cx
						pop bx
						pop ax
						ret

VII-2-b. nombre_vers_chaine

Cette fonction écrit dans la chaîne pointée par di le nombre contenu dans ax.

 
Sélectionnez
					;écrit dans la chaîne pointée par DI le nombre contenu dans AX
					;si CL est à un, on écrit un caractère terminal
					;CH contient le nombre minimal de caractères à utiliser
					;BL contient la base
					nombre_vers_chaine:
						push bx
						push dx
						push cx
						xor bh, bh
						mov cl, 1
					stocke_digit:
						xor dx, dx
						div bx
						push dx ;sauve le reste dans la pile
						inc cl
						or ax, ax
						jne stocke_digit
					;Les digits sont stockés dans la pile
						inc bh ;
					ajout_zero:
						cmp ch, cl
						jb clearCH
						push ' '-'A'+10
						inc cl
						jmp ajout_zero
					clearCH:
						xor ch, ch
					;Affichage du chiffre
					boucle_digit:
						dec bh
						loop affiche_digit
						pop cx ; on récupère le paramètre bx
						test cl, 0b1 ; s'il est à 1, on écrit un caractère terminal
						jz depile
						mov byte [di], 0
					depile:
						pop dx
						pop bx
					ret
 
					affiche_digit:
						pop ax
						cmp ax, 10
						jae dixPlus
						add ax, '0'
						stosb ; met AL dans l'octet pointé par DI et incrémente DI
						jmp boucle_digit
					dixPlus:
						add ax, 'A' - 10
						stosb
						jmp boucle_digit
					;fin nombre_vers_chaine

VII-3. Des modes graphiques

Pour faire une sorte de séance de révision, nous allons retravailler les graphiques du chapitre 5. Reprenons tout le déroulement du programme :

  • saut au début, optionnel ;
  • p'tit message pour ne pas perdre la main ;
  • demande d'informations sur le VESA BIOS EXTENSION ;
  • traitement de ces informations ;
  • choix du mode graphique ;
  • passage dans le mode choisi ;
  • affichage de points ;
  • affichage de droites ;
  • affichage d'un alphabet.

VII-3-a. Saut au début, optionnel

 
Sélectionnez
				org 0x0000 ; Adresse de début .COM
 
				jmp start
				%include "affichageTexte.asm"
 
				start:
				;On initialise Data Segment et Extra Segment à Code Segment
					call initialise_segments

On s'en souvient, on est en binaire pur pour être chargé depuis un chargeur d'amorçage, on a gardé la directive org d'une inutilité certaine ici. On fait un saut pour inclure les fonctions d'affichage de texte au début. C'est un choix, absolument pas justifiable par autre chose que "c'est comme ça". On appelle une fonction "initialise_segments" qui ne sera jamais appelée qu'une fois. On pourrait la mettre en macro, mais nous verrons les macros plus tard. C'est donc encore en fonction.

 
Sélectionnez
				;Initialisation des segments
				initialise_segments:
					mov ax, cs
					mov ds, ax
					mov es, ax
					mov ax, 0x8000
					cli
					mov ss, ax
					mov sp, 0xF000
					sti
					ret
				;Fin initialisation segments

VII-3-b. P'tit message pour ne pas perdre la main

 
Sélectionnez
					mov si, hello; met l'adresse de la chaîne à afficher dans le registre SI
					call affiche_chaine

Bon, le message sera affiché, mais tel quel, on a d'excellentes chances de ne pas le voir, parce qu'on va afficher plus d'un écran par la suite. Il faut modifier le code qui viendra après pour le voir.

VII-3-c. Demande d'informations sur le VESA BIOS EXTENSION

 
Sélectionnez
					mov ax, 0x4F00 ; demande infos sur le pilote VESA VBE
					mov di, VESASignature
					int 10h
					cmp ax, 0x4F ; Si AL != 0x4F, on n'a pas de VESA, donc fin. Si AH != O, erreur, donc fin.
					jne fin

VESASignature est défini dans la partie "données" :

 
Sélectionnez
				;Informations du pilote VESA
				VESASignature: times 4 db 0; "VESA", signature de 4 octets
				VESAVersion: dw 0; numéro de version de VBE
				OEMStringPtr: dd 0; Pointeur vers le nom de l'OEM
				Capabilities: dd 0; Possibilités de la carte graphique
				VideoModePtr: dd 0; Pointeur vers les modes accessibles
				TotalMemory: dw 0; Nombre de blocs mémoire de 64ko
				reserved: times 236 db 0; Complément à 256 octets, taille du bloc

Ces informations, je ne les sors pas de mon chapeau, évidemment. Elles proviennent d'ici : VESA BIOS Extension (VBE) Core Functions Standard Version 2.0, tout ça parce que dans ma machine virtuelle, c'est VBE 2.0 qui est implanté.

VII-3-d. Traitement de ces informations

Le traitement des informations VESA consiste principalement en deux choses : gérer les cas d'erreur et pousser plus avant les investigations. Pour les erreurs, on restera très simple puisqu'on sort au moindre problème. Pour le reste, voyons cela.

Première chose, afficher le nom du constructeur :

 
Sélectionnez
					cmp ax, 0x4F ; Si AL != 0x4F, on n'a pas de VESA, donc fin. Si AH != O, erreur, donc fin.
					jne fin
					mov si, OEMStringPtr ; pointeur vers le nom de l'OEM stocké offset:segment
					lodsw; on charge l'adresse d'offset dans ax
					mov bx, ax ; BX contient l'adresse d'offset
					lodsw ; on charge l'adresse de segment dans ax
					mov si, bx ; SI pointe sur le nom de l'OEM
					push ds ; on sauvegarde DS
						mov ds, ax ; ds contient l'adresse de segment du nom de l'OEM
						call affiche_chaine
					pop ds ; on restaure DS
 
					mov cx, 18
				lignes_vides:
					mov si, retour_chariot
					call affiche_chaine
					loop lignes_vides

Notez qu'on ajoute 18 lignes de retour chariot. C'est bête, c'est méchant et ça ne sert qu'à décaler l'affichage pour en avoir plus à visualiser sur l'écran suivant. Cette horreur devrait partir prochainement.

Nous allons ensuite lire les informations de chaque mode supporté par notre interruption 0x10. Ca nous permettra de choisir un bon mode vidéo.

 
Sélectionnez
					mov si, VideoModePtr ; pointeur vers la liste des modes supportés
					lodsw ; on charge l'adresse d'offset dans ax
					mov cx, ax ; cx contient l'adresse d'offset
					lodsw ; on charge l'adresse de segment dans ax
					mov si, cx ; si pointe sur le premier mode supporté
					mov dx, ax ; dx contient l'adresse de segment
				lit_mode_suivant:
					push ds
						mov ds, dx ; ds contient l'adresse de segment de la liste des modes
						lodsw ;charge dans ax le mode
					pop ds
					cmp ax, 0xFFFF ; Fin de la liste
					je arret_modes
					mov cx, ax
					mov ax, 0x4F01 ; demande infos sur le mode VESA
					mov di, ModeAttributes
					int 0x10

VideoModePtr est défini dans le bloc de données :

 
Sélectionnez
				;Informations d'un mode vidéo
				ModeAttributes: dw 0; Attributs du mode
				WinAAttributes: db 0; Attibuts de la fenêtre A
				WinBAttributes: db 0; Attibuts de la fenêtre B
				WinGranularity: dw 0; Granularité de la fenêtre en ko
				WinSize: dw 0; Taille de la fenêtre en ko
				WinASegment: dw 0; Segment de la fenêtre A
				WinBSegment: dw 0; Segment de la fenêtre B
				WinFuncPtr: dd 0; Pointeur vers la fonction "de fenêtrage"
				BytesPerScanLine: dw 0; Octets par "scanline"
				XResolution: dw 0; Résolution horizontale
				YResolution: dw 0; Résolution verticale
				XCharSize: db 0; Largeur d'un caractère
				YCharSize: db 0; Hauteur d'un caractère
				NumberOfPlanes: db 0; Nombre de plans mémoire
				BitsPerPixel: db 0; Bits par pixel
				NumberOfBanks: db 0; Nombre de banques de style CGA
				MemoryModel: db 0; Type de modèle mémoire
				BankSize: db 0; Taille des banques de style CGA
				NumberOfImagePages: db 0; Nombre de pages image
				res1: db 0; Reservé
				RedMaskSize: db 0; Taille du masque rouge en couleur directe
				RedFieldPosition: db 0; Position du bit faible du masque rouge
				GreenMaskSize: db 0; Taille du masque vert en couleur directe
				GreenFieldPosition: db 0; Position du bit faible du masque vert
				BlueMaskSize: db 0; Taille du masque bleu en couleur directe
				BlueFieldPosition: db 0; Position du bit faible du masque bleu
				RsvdMaskSize: db 0; Taille du masque réservé en couleur directe
				RsvdFieldPosition: db 0;  Position du bit faible du masque réservé
				DirectColorModeInfo: db 0; Attributs du mode de couleur directe
				res2: times 216 db 0; Complément à 256 octets, taille du bloc

Le test d'erreurs :

 
Sélectionnez
					cmp ax, 0x4F ; Si AL != 0x4F, la fonction n'est pas supportée, on se contentera du VGA. Si AH != O, erreur, pareil.
					jne lit_mode_suivant
					test word [ModeAttributes], 0xF
					jz lit_mode_suivant

On affiche les informations pertinentes du mode courant :

 
Sélectionnez
				;On ecrit les modes
					mov di, hello ; on écrit dans hello
					mov ax, cx
					push cx
						mov ch, 3
						mov bl, 16
						call nombre_vers_chaine
						mov al, ':'
						stosb
						mov ch, 4
						mov bl, 10
						mov ax, [XResolution]
						call nombre_vers_chaine
						mov ax, ('*' << 8) + ' '
						stosw
						mov al, ' '
						stosb
						mov ax, [YResolution]
						call nombre_vers_chaine
						mov ax, ' '
						stosb
						mov ch, 2
						mov al, [BitsPerPixel]
						call nombre_vers_chaine
						mov al, ' '
						stosb
						mov ax, ';' ; on met 2 caractères d'un coup après la chaîne : un "\n" et le zéro terminal.
						stosw ; les caractères sont dépilés, c'est-à-dire qu'il faut placer le premier dans la zone basse
					pop cx
					    push si ;sauve si sur la pile
						mov si, hello
						call affiche_chaine
				    pop si ; on récupère si

Les esprits chagrins remarqueront que j'écris sur l'emplacement hello. Alors, il est fait pour cela, et j'en suis parfaitement conscient. Vu le peu que j'écris, j'ai largement la place dans cet espace pour mettre ce qui m'intéresse.

On va déborder un peu sur le paragraphe suivant : on va chercher quelle est la meilleure résolution à utiliser. On va la définir comme étant celle maximisant largeurEnPixels * hauteurEnPixels * TailleDeCodageDesCouleurs. Ca se fait dans la même boucle, c'est pour cela qu'on en parle maintenant.

 
Sélectionnez
					push dx
						mov ax, [XResolution]
						shr ax, 5
						push ax
							mov ax, [YResolution]
							shr ax, 3
							push ax
								mov al, [BitsPerPixel]
								xor ah, ah
								shr ax, 3
							pop bx
							mul bx
						pop bx
						mul bx
					pop dx
					cmp ax, [maxResol]
					jb lit_mode_suivant
					mov [maxResol], ax
					mov [mode_souhaite], cx
				    jmp lit_mode_suivant

VII-3-e. Choix du mode graphique

 
Sélectionnez
				arret_modes:
					mov cx, [mode_souhaite] ; On s'enquiert du mode souhaité
				    mov ax, 0x4F01 ; demande infos sur le mode VESA
					mov di, ModeAttributes
				    int 0x10
					mov di, hello ; on écrit dans hello
					mov ax, cx
					push cx
						mov ch, 3
						mov bl, 16
						call nombre_vers_chaine
						mov al, ':'
						stosb
						mov ch, 4
						mov bl, 10
						mov ax, [XResolution]
						call nombre_vers_chaine
						mov ax, ('*' << 8) + ' '
						stosw
						mov al, ' '
						stosb
						mov ax, [YResolution]
						call nombre_vers_chaine
						mov ax, ' '
						stosb
						mov ch, 2
						mov al, [BitsPerPixel]
						call nombre_vers_chaine
						mov ax, 13 ; on met 2 caractères d'un coup après la chaîne : un "\n" et le zéro terminal.
						stosw ; les caractères sont dépilés, c'est-à-dire qu'il faut placer le premier dans la zone basse
					pop cx
				    mov si, hello
				    call affiche_chaine
 
					mov al, [BitsPerPixel]
					shr al, 3
					mov byte [octetsParPixel], al
				    mov ax, [WinASegment]
				    or ax, ax ; on teste l'adresse du segment de la fenêtre. Si elle est nulle, on passe en mode 0x13
				    jnz adresse_OK
				adresse_mode_13h:
				    mov word [mode_souhaite], 0x0013 ; infos du mode 0x13, le mode VGA
				    mov word [WinASegment], 0xA000
				    mov word [YResolution], 200
				    mov word [XResolution], 320
					mov byte [octetsParPixel], 1
				adresse_OK:
					mov di, hello ; met l'adresse de la chaîne à lire dans le registre SI
					call lit_chaine ; On attend l'utilisateur pour nettoyer l'écran

VII-3-f. Passage dans le mode choisi

 
Sélectionnez
					mov ax, 0x4F02
					mov bx, [mode_souhaite]
					int 0x10 ; Changement de mode vidéo
					call nettoyage_ecran
VII-3-f-1. Nettoyage de l'écran

Derrière ce terme ménager se cache juste le remplissage de l'intégralité de l'écran avec une couleur de fond. Comme d'habitude, l'idée n'est pas l'optimisation, le code super rapide comme l'ont tous les logiciels spécialisés. L'idée est d'avoir d'abord un code fonctionnel, créé au plus évident. L'algorithme que je vous propose ne prend en compte aucun prérequis autre qu'un mode VESA disposant d'une fenêtre A en mode écriture. Enfin, il me semble.

On écrit dans la mémoire vidéo par bloc de 64 ko. L'idée est donc de remplir tous ces blocs par la couleur de fond.

 
Sélectionnez
					nettoyage_ecran:
						push di
						push es
						push ax
						push bx
						push cx
						push dx
						mov es, [WinASegment]; On lit l'adresse de départ de la mémoire vidéo
 
						mov cx, [YResolution]
						mov ax, [XResolution]
						mul cx	; Nombre de points total
						mov ax, dx
						mov cl, [octetsParPixel]
						mul	cl
						mov cx, ax
						xor dx, dx
						xor bh, bh
						xor bl, bl
					boucle_fenetres:
						push cx
							mov ax, 0x4F05
							int 0x10 ; Changement de fenêtre
							mov cx, [WinSize]
							shl cx, 9 ;Passage de ko en mots.
							xor di, di
						.point:
							push cx
							push bx
								mov cl, [octetsParPixel]
								xor ch, ch
								mov bx, couleur_defaut
							.couleur:
								inc bx
								mov al, byte [bx]
								stosb
								loop .couleur
							pop bx
							pop cx
							loop .point
							inc dx
						pop cx
						loop boucle_fenetres
					.depile:
						xor dx, dx
						mov [bloc_courant], dl
						mov ax, 0x4F05
						int 0x10 ; Changement de fenêtre
						pop dx
						pop cx
						pop bx
						pop ax
						pop es
						pop di
						ret
					;Fin nettoyage_ecran

VII-3-g. Affichage de points

Avec cette fonction, vous pouvez dorénavant dessiner ce que vous voulez où vous le voulez, en, normalement, n'importe quel mode VESA ! L'idée de base reste de mettre, octet par octet, la couleur spécifiée à l'adresse couleurPoint dans la mémoire vidéo, et de donner le bon numéro de plage. Ce bon numéro de plage est toujours basé sur la sainte formule : ordonnée * octetsParLigne + abscisse. Je suppose que le débordement de la multiplication me donne le numéro convoité.

 
Sélectionnez
				;fonction affiche_point : on est déjà dans un mode graphique
				;BX : Coordonnée X du point
				;AX : Coordonnée Y du point
				affiche_point:
					push bx ; On sauve les registres qu'on va manipuler
					push cx
					push es
					push di
					push dx
					push ax
						mov cx, word [BytesPerScanLine]
						mul	cx
						mov di, ax
						push dx
							mov ax, bx
							xor ch, ch
							mov cl, byte [octetsParPixel]
							mul cx
							add di, ax
						pop dx
					.change_fenetre:
						mov ax, 0x4F05
						xor bh, bh
						xor bl, bl
						int 0x10 ; Changement de fenêtre
						mov es, [WinASegment] ; On va dans la mémoire vidéo
						mov bx, couleurPoint
					.couleur:
						inc bx
						mov al, byte [bx]
						stosb
						loop .couleur
					pop ax ; On restaure les registres manipulés
					pop dx
					pop di
					pop es
					pop cx
					pop bx
					ret
				;fin de affiche_point

VII-3-h. Affichage de droites

On avait déjà parlé de l'algorithme de Bresenham, le revoici à l'identique. Il a été mis à jour pour refléter l'inversion des paramètres de affiche_point. On met sur la pile X1, puis Y1, puis X2 et enfin Y2.

 
Sélectionnez
				;fonction affiche_ligne : on est déjà dans un mode graphique
				affiche_ligne:
					jmp depart_affiche_ligne
				Y2: dw 0
				X2: dw 0
				Y1: dw 0
				X1: dw 0
				deltaX: dw 0
				deltaY: dw 0
				incX: dw 0
				incY: dw 0
				e: dw 0
				depart_affiche_ligne:
					push si
					push ax
					push bx
					push cx
					push dx
					push di
					push es
					mov ax, sp
					mov si, ax
					add si, 16 ; SI pointe sur Y2
					mov di, Y2
					mov ax, ds
					mov es, ax
					mov ax, ss
					mov ds, ax
					mov cx, 4
					rep movsw
					mov ax, es
					mov ds, ax
					mov ax, [X2]
					mov bx, [X1]
					sub ax, bx
					mov [deltaX], ax
					mov cx, [Y2]
					mov bx, [Y1]
					sub cx, bx
					mov [deltaY], cx
					or ax, ax ; test deltaX
					jnz test_deltaX_positif
					or cx, cx ; test deltaY
					jnz test_deltaY_deltaX_nul
				fin_affiche_ligne:
					mov bx, [X2]
					mov ax, [Y2]
					call affiche_point
					pop es
					pop di
					pop dx
					pop cx
					pop bx
					pop ax
					pop si
					ret
 
				deltaX_positif:
					or cx, cx
					jnz test_deltaY_deltaX_positif
					;vecteur horizontal vers la droite
					mov cx, [deltaX]
					mov word [incX], 1
					mov word [incY], 0
					jmp ligne_H_V
 
				test_deltaY_deltaX_nul:
				;cx contient deltaY
					mov word [incY], 1
					mov word [incX], 0
					cmp cx, 0
					jns ligne_H_V
					neg cx
					mov word [incY], -1
				ligne_H_V:
					mov bx, [X1]
					mov ax, [Y1]
				avance_H_V:
					call affiche_point
					add bx, [incX]
					add ax, [incY]
					loop avance_H_V
					jmp fin_affiche_ligne
 
				test_deltaX_positif:
					cmp ax, 0
					jns deltaX_positif
					or cx, cx ; CX contient DeltaY
					jnz test_deltaY_deltaX_negatif
					;vecteur horizontal vers la gauche
					mov cx, [deltaX]
					neg cx
					mov word [incX], -1
					mov word [incY], 0
					jmp ligne_H_V
 
				charge_registres:
				    shl cx, 1
				    shl ax, 1
				    mov [deltaY], cx
				    mov [deltaX], ax
				    mov bx, [X1]
				    mov ax, [Y1]
				    ret
 
				charge_e_deltaX_et_cmp_X2:
				    mov [e], ax
				    call charge_registres
				    mov cx, [X2]
				    ret
 
				charge_e_deltaY_et_cmp_Y2:
				    mov [e], cx
				    call charge_registres
				    mov cx, [Y2]
				    ret
 
				affiche_et_charge_eY:
				    call affiche_point
				    add ax, [incY]
				    mov dx, [e]
				    ret
 
				affiche_et_charge_eX:
				    call affiche_point
				    add bx, [incX]
				    mov dx, [e]
				    ret
 
				octants1_et_4:
				    call charge_e_deltaX_et_cmp_X2
				depart_boucle1:
				    call affiche_et_charge_eX
				    cmp bx, cx
				    je fin_affiche_ligne
				    sub dx, [deltaY]
				    cmp dx, 0
				    jns X_pret1
				    add ax, [incY]
				    add dx, [deltaX]
				X_pret1:
				    mov [e], dx
				    jmp depart_boucle1
 
				deltaY_positif_deltaX_negatif:
				    neg ax
				deltaY_positif_deltaX_positif:
				    mov word [incY], 1
				    ;deltaY > 0, deltaX > 0
				    cmp ax, cx
				    jae octants1_et_4
				    neg ax
				    call charge_e_deltaY_et_cmp_Y2
				depart_boucle2_et_3:
				    call affiche_et_charge_eY
				    cmp ax, cx
				    je fin_affiche_ligne
				    add dx, [deltaX]
				    cmp dx, 0
				    jns X_pret2_et_3
				    add bx, [incX]
				    add dx, [deltaY]
				X_pret2_et_3:
				    mov [e], dx
				    jmp depart_boucle2_et_3
 
				octant5:
				    call charge_e_deltaX_et_cmp_X2
				depart_boucle5:
				    call affiche_et_charge_eX
				    cmp bx, cx
				    je fin_affiche_ligne
				    sub dx, [deltaY]
				    cmp dx, 0
				    js X_pret5
				    add ax, [incY]
				    add dx, [deltaX]
				X_pret5:
				    mov [e], dx
				    jmp depart_boucle5
 
				octant8:
				    neg cx
				    call charge_e_deltaX_et_cmp_X2
				depart_boucle8:
				    call affiche_et_charge_eX
				    cmp bx, cx
				    je fin_affiche_ligne
				    add dx, [deltaY]
				    cmp dx, 0
				    jns X_pret8
				    add ax, [incY]
				    add dx, [deltaX]
				X_pret8:
				    mov [e], dx
				    jmp depart_boucle8
 
				test_deltaY_deltaX_positif:
					mov word [incX], 1
					cmp cx, 0
					jns deltaY_positif_deltaX_positif
					;deltaY < 0, deltaX > 0
					mov word [incY], -1
					neg cx
					cmp ax, cx
					jae octant8
					neg cx
					jmp octants6_et_7
				test_deltaY_deltaX_negatif:
					mov word [incX], -1
					cmp cx, 0 ; cx contient deltaY
					jns deltaY_positif_deltaX_negatif
					;deltaY < 0, deltaX < 0
					mov word [incY], -1
					cmp ax, cx ; ax contient deltaX
					jbe octant5
					neg ax
				octants6_et_7:
					call charge_e_deltaY_et_cmp_Y2
				depart_boucle6_et_7:
					call affiche_et_charge_eY
					cmp ax, cx
				    je fin_affiche_ligne
				    add dx, [deltaX]
				    cmp dx, 0
				    js X_pret6_et_7
				    add bx, [incX]
				    add dx, [deltaY]
				X_pret6_et_7:
					mov [e], dx
					jmp depart_boucle6_et_7
				;AFFICHE_LIGNE ENDP

VII-3-i. Affichage d'un alphabet

Voici la grande nouveauté de ce chapitre : un alphabet ! En mode graphique, oui madame, dessiné par mes soins, à la va-comme-j'te-pousse. Je l'ai fait parce qu'il me semble que bientôt, nous n'en aurons plus. Et puis, ça permettra d'afficher facilement des "é", "ç" et autres "à". Dès que j'aurai mis ces caractères, bien sûr. La fonction est très simple : on lui donne le numéro du caractère, avec A = 1, dans cx. ax contient l'ordonnée du coin supérieur gauche du caractère et bx son abscisse.

 
Sélectionnez
				affiche_caractere:
					push bx
					push dx
					push si
					push cx
					push ax
					push ax
					mov ax, cx
					mov cx, 6
					mul cx
					mov si, Alphabet; adresse de la lettre
					add si, ax; si contient l'adresse de la lettre
				.colonne:
					pop ax
					push cx
					mov dl, 0b10000000
					mov cx, 8; On affiche 8 colonnes
				.ligne:
					push ax
					mov al, [si]; On charge l'octet à afficher
					test al, dl
					jz .suite
					pop ax
					call affiche_point
					push ax
				.suite:
					shr dl, 1
					inc bx
					pop ax
					loop .ligne
					pop cx
					inc ax; passage à la ligne suivante
					sub bx, 8
					push ax
					inc si
					loop .colonne
					pop ax
					pop ax
					pop cx
					pop si
					pop dx
					pop bx
					ret

VII-4. Du code


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2011 Etienne Sauvage. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.