commit 08f66b73e186777e80bdd37903f9f0072a336c83 Author: Frater <13979047+saintfrater@users.noreply.github.com> Date: Thu Jan 8 10:26:52 2026 +0100 initial commit diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..b0af759 --- /dev/null +++ b/build.bat @@ -0,0 +1,7 @@ +@echo off + +cd src +"c:\program files\NASM\nasm" -f bin boot.asm -o ..\build\bios.bin +cd .. + +qemu-system-x86_64 -bios build/bios.bin -device loader,file=build/vgabios.bin,addr=0xC0000,force-raw=on -device isa-debugcon,chardev=myconsole -chardev stdio,id=myconsole -k be -display sdl diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e95851c --- /dev/null +++ b/readme.md @@ -0,0 +1,123 @@ +# Custom BIOS / ROM Project +## Projet BIOS / ROM personnalisé + +--- + +## 🇬🇧 English + +### Overview + +This project aims to design and implement a **PC-compatible BIOS/ROM from scratch**, primarily targeted for **QEMU emulation**, with a strong focus on **low-level PC architecture** and **x86 real-mode assembly programming**. + +Rather than booting an operating system from disk, the PC starts **directly from the ROM** into a **minimalist graphical interface**, using **CGA High-Resolution Monochrome (640×200)** mode. + +The project is intentionally educational, experimental, and minimalist. + +--- + +### Objectives + +- **Write a PC-compatible BIOS from scratch** + - No reuse of existing BIOS code + - Minimal but functional implementation + - Compatible with QEMU + - Target architecture: **8086 → 80486** + +- **Boot directly from ROM** + - No DOS, no bootloader, no disk dependency + - Execution starts at reset vector (F000:FFF0) + - ROM is responsible for full system initialization + +- **Provide a minimalist graphical interface** + - Graphics mode: **CGA High-Resolution Monochrome (640×200)** + - Direct video memory access + - Custom cursor and basic UI primitives + +- **Deep understanding of PC architecture** + - CPU reset behavior + - Memory map (RAM, ROM, EBDA, IVT, BDA) + - Interrupt Vector Table + - Hardware initialization (PIC, PIT, keyboard, video) + - Real-mode execution constraints + +- **Improve mastery of x86 assembly** + - Real-mode x86 assembly (8086-compatible) + - Performance-oriented routines + - Hardware-near programming + - Clear separation between hardware-specific drivers and core logic + +--- + +### Non-Goals + +- Full IBM BIOS compatibility +- Protected mode or modern CPUs +- Advanced OS services +- DOS or legacy OS support + +This is a **learning and exploration project**, not a drop-in BIOS replacement. + +--- + +## 🇫🇷 Français + +### Présentation + +Ce projet consiste à concevoir et développer un **BIOS/ROM PC compatible entièrement écrit “from scratch”**, destiné principalement à l’émulation **QEMU**, avec pour objectif principal l’**apprentissage approfondi de l’architecture PC** et de la **programmation assembleur x86 en mode réel**. + +Au lieu de charger un système d’exploitation depuis un disque, le PC **démarre directement depuis la ROM** vers une **interface graphique minimaliste**, utilisant le mode **CGA Haute Résolution Monochrome (640×200)**. + +Le projet est volontairement **éducatif, expérimental et minimaliste**. + +--- + +### Objectifs + +- **Écrire un BIOS PC depuis zéro** + - Aucun code BIOS existant réutilisé + - Implémentation minimale mais fonctionnelle + - Compatible avec QEMU + - Architecture ciblée : **8086 jusqu’au 486** + +- **Démarrer directement depuis la ROM** + - Sans DOS, sans bootloader, sans disque + - Exécution depuis le vecteur de reset (F000:FFF0) + - La ROM initialise entièrement la machine + +- **Fournir une interface graphique minimaliste** + - Mode graphique : **CGA Hi-Res Monochrome (640×200)** + - Accès direct à la mémoire vidéo + - Curseur personnalisé et primitives graphiques simples + +- **Approfondir la compréhension de l’architecture PC** + - Séquence de reset CPU + - Cartographie mémoire (RAM, ROM, EBDA, IVT, BDA) + - Table des vecteurs d’interruptions + - Initialisation matérielle (PIC, PIT, clavier, vidéo) + - Contraintes du mode réel + +- **Perfectionner la maîtrise de l’assembleur** + - Assembleur x86 en mode réel (compatible 8086) + - Routines optimisées pour la performance + - Programmation proche du matériel + - Séparation claire entre cœur du BIOS et drivers matériels + +--- + +### Hors périmètre + +- Compatibilité BIOS IBM complète +- Mode protégé ou CPU modernes +- Services système avancés +- Support DOS ou OS legacy + +Ce projet est avant tout un **laboratoire d’apprentissage et d’exploration**, et non un BIOS de production. + +--- + +### Status + +🚧 **Work in progress** +Designed for experimentation, learning, and documentation. + +--- diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..3c363ff --- /dev/null +++ b/run.bat @@ -0,0 +1,3 @@ +@echo off + +qemu-system-x86_64 -bios build/bios.bin -device loader,file=build/vgabios.bin,addr=0xC0000,force-raw=on -device isa-debugcon,chardev=myconsole -chardev stdio,id=myconsole -k be -display sdl \ No newline at end of file diff --git a/src/bda.asm b/src/bda.asm new file mode 100644 index 0000000..23e22f0 --- /dev/null +++ b/src/bda.asm @@ -0,0 +1,29 @@ +; +; https://wiki.nox-rhea.org/back2root/ibm-pc-ms-dos/hardware/informations/bios_data_area +; +%define BDA_SEGMENT 0x0040 ; segment BDA + +; custom var +%define BDA_INFO_MEM_SIZE 0x0014 ; Memory size in Kbytes +%define BDA_INFO_MEM_SEG BDA_INFO_MEM_SIZE+2 ; highest Memory Segment + + +%define BDA_MOUSE_BUFFER 0x0068 ; dword +%define BDA_MOUSE_IDX BDA_MOUSE_BUFFER+4 ; byte +%define BDA_MOUSE_X BDA_MOUSE_BUFFER+5 ; word +%define BDA_MOUSE_Y BDA_MOUSE_BUFFER+7 ; word + +; video related information (compatible with VGA bios) +%define BDA_VIDEO_CURR_MODE 0x0049 +%define BDA_VIDEO_COLUMNS 0x004A +%define BDA_VIDEO_BUF_SIZE 0x004C +%define BDA_VIDEO_OFS_PAGE 0x004E + + +; 40:50 8 words Cursor position of pages 1-8, high order byte=row low order byte=column; changing this data isn't reflected immediately on the display +; 40:60 byte Cursor ending (bottom) scan line (don't modify) +; 40:61 byte Cursor starting (top) scan line (don't modify) +; 40:62 byte Active display page number +; 40:63 word Base port address for active 6845 CRT controller 3B4h = mono, 3D4h = color +; 40:65 byte 6845 CRT mode control register value (port 3x8h) ; EGA/VGA values emulate those of the MDA/CGA +; 40:66 byte CGA current color palette mask setting (port 3d9h) ; EGA and VGA values emulate the CGA \ No newline at end of file diff --git a/src/boot.asm b/src/boot.asm new file mode 100644 index 0000000..7dec420 --- /dev/null +++ b/src/boot.asm @@ -0,0 +1,261 @@ +; +; +; +; +BITS 16 +org 0 + +; +; configuration du bios +; +%define DEBUG_PORT 0xe9 ; 0x402 ou 0xe9 + +%define VIDEO_SEG 0xB800 ; ou 0xB000 +%define VIDEO_ATTR 0x07 + +; initial stack +%define STACK_SEG 0x0030 +%define STACK_TOP 0x0100 ; choix commun en RAM basse (pile descend) + +%define MEM_SEG_DEB 0x0800 ; 0x0800:0000 = 0x8000 (32KB) évite IVT/BDA + stack 7C00 +%define MEM_SEG_END 0xA000 ; 0xA000:0000 = 0xA0000 (début zone vidéo) +%define MEM_SEG_STEP 0x0040 ; 1KB = 0x400 bytes = 0x40 paragraphs + +; definition du BDA +%include ".\bda.asm" + + + jmp reset + +%include ".\drivers\debug.asm" +%include ".\drivers\gfx_cgam.asm" +%include ".\drivers\mouse_ps2.asm" + +err_novga db 'No VGA Card BIOS',0 +err_vganok db 'VGA Not Initialized',0 +err_end db 'code completed successfully',0 + +reset: + cli + ; il n'existe aucun 'stack' par défaut + mov ax, STACK_SEG + mov ss, ax + mov sp, STACK_TOP ; haut du segment, mot-aligné + + ; detection de la mémoire totale + call setup_ram + ; modification du stack en "haut" de la RAM + mov ax, dx + sub ax, 0x0400 ; réserver les 16 dernier KB + mov ss, ax + mov sp, 0x1000 ; sommet de pile + + ; installé une table d'interruption "dummy" + call setup_ivt + sti + + ; load other Rom + call setup_load_rom + + ; on vérifie que le BIOS VGA a installer l'INT 10h + call setup_check_vga + + call gfx_init + + call mouse_init + + mov ax,cs + mov ds,ax + + mov si, err_end + call debug_puts + +endless: nop + jmp endless + +; --------------------------------------------------------------------------- +; Détection les ROM supplementaires +; Teste la RAM de 0xC000 jusqu’à 0xE000, par pas de 2KB. +; +; Si une ROM est détectée, le code de la ROM sera appelé. +; --------------------------------------------------------------------------- +setup_check_vga: + ; vérification de l'offset 0x0000:0x0040 qui DOIT etre différent de @default_isr + xor ax,ax + mov es,ax + ; Lire INT10: offset+segment depuis 0000:0040 + mov bx, [es:0x0040] ; BX = offset INT10 + mov cx, [es:0x0042] ; CX = segment INT10 + mov ax, cs + + ; Comparer avec default_isr (offset) et CS (segment) + mov dx, default_isr ; DX = offset default_isr (dans notre CS) + cmp bx, dx + jne .init_ok ; interrupt different + cmp cx, ax + jne .init_ok + + ; no VGA init + mov ax,cs + mov ds,ax + + mov si, err_vganok + call debug_puts +.init_ok: + ret + +; --------------------------------------------------------------------------- +; Détection les ROM supplementaires +; Teste la RAM de 0xC000 jusqu’à 0xE000, par pas de 2KB. +; +; Si une ROM est détectée, le code de la ROM sera appelé. +; --------------------------------------------------------------------------- +setup_load_rom: + mov bx, 0xC000 + mov al,'*' +.scanloop: + mov ds, bx + cmp word[0x0000],0xAA55 ; ROM signature + jne .norom + + ; Debug + mov al,'.' + call debug_putc + + ; appel le code (en 0x0003) de la ROM + push cs + push word .norom ; address de retour + + push ds + push 0x0003 + retf ; call far ds:0x0003 +.norom: + add bx, 0x80 ; add 2kb + cmp bx, 0xE000 + jbe .scanloop + + cmp al,'.' + je .end + ; no rom found : + mov bx,cs + mov ds,bx + + mov si, err_novga + call debug_puts +.end: + ret + +; --------------------------------------------------------------------------- +; Détection RAM conventionnelle (8088, BIOS maison) — sans BIOS +; Teste la RAM de MEM_SEG_DEB jusqu’à 0xA000 (640KB), par pas de 1KB. +; Méthode: sauvegarde 1 mot, écrit 2 patterns, relit, restaure. +; +; Résultats: +; AX = taille détectée en Ko (KB) à partir de 0 jusqu’au “top conventionnel” +; DX = top_segment (segment du premier Ko NON valide) (optionnel) +; +; Précautions: +; - Ne testez PAS une zone où se trouve votre pile / variables. +; - Si votre pile est à 0000:7C00 (phys 0x7C00), commencez au moins à 0x0800. +; - Ne testez pas au-delà de 0xA000 (début VGA/vidéo). +; --------------------------------------------------------------------------- +setup_ram: + xor ax, ax ; AX = compteur KB trouvés + mov dx, MEM_SEG_DEB ; DX = segment courant testé + mov cx, MEM_SEG_END ; CX = segment fin (exclu) + xor di, di ; tester au début du bloc 1KB: ES:0000 + +.loop_seg: + cmp dx, cx + jae .done + mov es, dx + + ; sauvegarder le mot existant + mov bx, [es:di] + + ; pattern 1 + mov word [es:di], 0x55AA + cmp word [es:di], 0x55AA + jne .fail_restore + + ; pattern 2 (inverse) + mov word [es:di], 0xAA55 + cmp word [es:di], 0xAA55 + jne .fail_restore + + ; restaurer + mov [es:di], bx + + ; OK: avancer d’1KB + inc ax ; +1KB valide + add dx, MEM_SEG_STEP + jmp .loop_seg + +.fail_restore: + ; restaurer avant de sortir + mov [es:di], bx + +.done: + ; AX contient le nb de KB valides *à partir de MEM_SEG_DEB*. + ; Si vous voulez une taille “depuis 0KB”, ajoutez MEM_SEG_DEB*16/1024 = MEM_SEG_DEB/64. + ; + ; base_kb = MEM_SEG_DEB / 0x0040 (car 1KB = 0x40 segments) + mov bx, MEM_SEG_DEB + shr bx, 6 ; /64 = /0x40 => KB de base + add ax, bx ; AX = taille conventionnelle totale en KB (approx. 0..640) + ; DX = segment du premier bloc NON valide (top) + ; DX est déjà positionné (segment courant) + + ; stocker l'information dans le BDA + mov bx, BDA_SEGMENT + mov ds, bx + mov [BDA_INFO_MEM_SIZE], ax + mov [BDA_INFO_MEM_SEG],dx + + ret + + +; --------------------------------------------------------------------------- +; IVT install (8088, mode réel) - remplit les 256 vecteurs avec un handler IRET +; Hypothèses: +; - code en ROM (CS typiquement = 0xF000), handler réside dans ce même segment +; - interruptions désactivées (CLI) pendant l'installation +; --------------------------------------------------------------------------- +setup_ivt: + ; installation de la table d'interrupts + xor ax, ax + mov es, ax ; ES = 0000h -> base IVT + xor di, di ; DI = 0000h -> offset IVT + + mov ax, default_isr ; offset du handler + mov dx, cs ; segment du handler (ROM) + + mov cx, 256 ; 256 vecteurs +.fill: + stosw ; write offset (AX) -> [ES:DI], DI += 2 + xchg ax, dx ; AX = segment, DX = offset + stosw ; write segment (AX) -> [ES:DI], DI += 2 + xchg ax, dx ; AX = offset, DX = segment + loop .fill + ; fin de la table d'interrupt + ret + +; --------------------------------------------------------------------------- +; Handler par défaut: fait juste IRET +; IMPORTANT: doit être FAR (appelé par le CPU via IVT), et terminer par IRET +; --------------------------------------------------------------------------- +default_isr: + iret + +; ------------------------------------------------------------------ +; Padding jusqu'au reset vector +; ------------------------------------------------------------------ +times 0xFFF0 - ($ - $$) db 0xFF + +; ------------------------------------------------------------------ +; RESET VECTOR (exécuté par le CPU) +; ------------------------------------------------------------------ +reset_vector: + jmp far 0xF000:reset +builddate db '06/01/2026',0 + diff --git a/src/drivers/debug.asm b/src/drivers/debug.asm new file mode 100644 index 0000000..c4df9a8 --- /dev/null +++ b/src/drivers/debug.asm @@ -0,0 +1,20 @@ + + +debug_puts: +.next: + lodsb ; AL = *SI++ + test al, al + jz .done + + mov dx, DEBUG_PORT + + out dx, al + jmp .next +.done: + ret + +debug_putc: +.next: + mov dx, DEBUG_PORT + out dx, al + ret \ No newline at end of file diff --git a/src/drivers/gfx.asm b/src/drivers/gfx.asm new file mode 100644 index 0000000..70856f8 --- /dev/null +++ b/src/drivers/gfx.asm @@ -0,0 +1,62 @@ +%define GFX_MODE 0x06 ; MCGA HiRes (B/W) +%define GFX_WIDTH 640 +%define GFX_HEIGHT 200 + +gfx_init: + ; init graphics mode + mov ah, 0x00 ; AH=00h set video mode + mov al, GFX_MODE + int 0x10 + + call gfx_background + + ret + +; PutPixel(x=cx, y=dx, color=al) sur page bh +gfx_putpixel: + mov ah, 0Ch + ;mov al, 0Fh ; couleur (0..15) + ;mov bh, 00h ; page + ;mov cx, 100 ; x + ;mov dx, 50 ; y + int 10h + ret + +gfx_background: + mov ax,VIDEO_SEG + mov es,ax + xor di,di + mov ax,0xaaaa + mov cx,0x1000 + rep stosw + mov ax,0x5555 + mov cx,0x1000 ; 640/8/2 + rep stosw + ret + +; gfx_background: + xor dx,dx ; Y + +.loop_y: + test dl,1 + jnz .pair + + mov cx,0 ; X = 0 + jmp .loop_x +.pair: + mov cx,1 ; X = 1 + +.loop_x: + mov ah,0x0c ; putpixel + mov al,0x0f ; blanc + xor bh,bh + int 10h + + add cx,2 ; x=x+2 + cmp cx,GFX_WIDTH + jb .loop_x + + inc dx + cmp dx,GFX_HEIGHT + jb .loop_y + ret \ No newline at end of file diff --git a/src/drivers/gfx_cgam.asm b/src/drivers/gfx_cgam.asm new file mode 100644 index 0000000..b1f8c21 --- /dev/null +++ b/src/drivers/gfx_cgam.asm @@ -0,0 +1,45 @@ +; +; graphics drivers for CGA 640x200x2 +; +%define GFX_MODE 0x06 ; MCGA HiRes (B/W) +%define GFX_WIDTH 640 +%define GFX_HEIGHT 200 + +%define CGA_STRIDE 80 +%define CGA_ODD_BANK 0x2000 +%define BG16_SIZE 48 + +; ce mode est entrelacé, un bit/pixel, 8 pixels par octet + +gfx_init: + ; init graphics mode + mov ah, 0x00 ; AH=00h set video mode + mov al, GFX_MODE + int 0x10 + + call gfx_background + + ret + +; PutPixel(x=cx, y=dx, color=al) sur page bh +gfx_putpixel: + mov ah, 0Ch + ;mov al, 0Fh ; couleur (0..15) + ;mov bh, 00h ; page + ;mov cx, 100 ; x + ;mov dx, 50 ; y + int 10h + ret + +; draw background pattern +gfx_background: + mov ax,VIDEO_SEG + mov es,ax + xor di,di + mov ax,0xaaaa + mov cx,0x1000 + rep stosw + mov ax,0x5555 + mov cx,0x1000 ; 640/8/2 + rep stosw + ret diff --git a/src/drivers/mouse_ps2.asm b/src/drivers/mouse_ps2.asm new file mode 100644 index 0000000..64ffcb8 --- /dev/null +++ b/src/drivers/mouse_ps2.asm @@ -0,0 +1,127 @@ +; +; controlleur de souris via le i8042 (PC Classique) +; + +%define PS2_PORT_BUFFER 0x60 +%define PS2_PORT_CTRL 0x64 + +%define MOUSE_CMD_RESET 0xff +%define MOUSE_CMD_DEFAULT 0xf6 +%define MOUSE_EN_STREAM 0xf4 +%define MOUSE_DIS_STREAM 0xf5 + +mouse_arrow: + dw 1001111111111111b ; 0x9FFF + dw 1000111111111111b ; 0x8FFF + dw 1000011111111111b ; 0x87FF + dw 1000001111111111b ; 0x83FF + dw 1000000111111111b ; 0x81FF + dw 1000000011111111b ; 0x80FF + dw 1000000001111111b ; 0x807F + dw 1000000000111111b ; 0x803F + dw 1000000000011111b ; 0x801F + dw 1000000000001111b ; 0x800F + dw 1000000011111111b ; 0x80FF + dw 1000100001111111b ; 0x887F + dw 1001100001111111b ; 0x987F + dw 1111110000111111b ; 0xFC3F + dw 1111110000111111b ; 0xFC3F + dw 1111111000111111b ; 0xFE3F + dw 0000000000000000b ; 0x0000 + dw 0010000000000000b ; 0x2000 + dw 0011000000000000b ; 0x3000 + dw 0011100000000000b ; 0x3800 + dw 0011110000000000b ; 0x3C00 + dw 0011111000000000b ; 0x3E00 + dw 0011111100000000b ; 0x3F00 + dw 0011111110000000b ; 0x3F80 + dw 0011111111000000b ; 0x3FC0 + dw 0011111000000000b ; 0x3E00 + dw 0011011000000000b ; 0x3600 + dw 0010001100000000b ; 0x2300 + dw 0000001100000000b ; 0x0300 + dw 0000000110000000b ; 0x0180 + dw 0000000110000000b ; 0x0180 + dw 0000000000000000b ; 0x0000 + +mouse_init: + ; Activer le port souris + mov ax, BDA_SEGMENT + mov ds,ax + + ; effacer les variables du drivers + mov [BDA_MOUSE_IDX],0 + mov dword [BDA_MOUSE_BUFFER],0 + + mov al, 0xA8 + out PS2_PORT_CTRL, al + + ; Lire le command byte + mov al, 0x20 + out PS2_PORT_CTRL, al + in al, PS2_PORT_BUFFER + + ; Activer IRQ12 (bit 1) + or al, 0x02 + + ; Réécrire le command byte + mov ah, al + mov al, PS2_PORT_BUFFER + out PS2_PORT_CTRL, al + mov al, ah + out PS2_PORT_BUFFER, al + ret + +; envoyer une commande souris +mouse_sendcmd: +.wait: + in al, PS2_PORT_CTRL + test al, 2 + jnz .wait + mov al, 0xD4 + out PS2_PORT_CTRL, al + +.wait2: + in al, PS2_PORT_CTRL + test al, 2 + jnz .wait2 + mov al, bl ; BL = commande souris + out PS2_PORT_BUFFER, al + ret + +mouse_handler: + push ax + push bx + + ; use BDA segment + push ds + mov ax,BDA_SEGMENT + mov ds,ax + xor bx,bx + + in al, PS2_PORT_BUFFER ; lire octet souris + mov [BDA_MOUSE_BUFFER + BDA_MOUSE_IDX], al + inc [BDA_MOUSE_IDX] + + cmp [BDA_MOUSE_IDX], 3 + jne .done + + mov [BDA_MOUSE_IDX], 0 + ; decode buffer + mov al, [BDA_MOUSE_BUFFER+1] + cbw + add [BDA_MOUSE_X], ax + + mov al, [BDA_MOUSE_BUFFER+2] + cbw + sub [BDA_MOUSE_Y], ax + +.done: + mov al, 0x20 + out 0xA0, al ; EOI PIC esclave + out 0x20, al ; EOI PIC maître + + pop ds + pop bx + pop ax + iret \ No newline at end of file diff --git a/src/drivers/textmode.asm b/src/drivers/textmode.asm new file mode 100644 index 0000000..7f7b42c --- /dev/null +++ b/src/drivers/textmode.asm @@ -0,0 +1,106 @@ +; +; text mod functions +; +; + +vid_seg dw VIDEO_SEG +cursor_ofs dw 0 +txt_attr db VIDEO_ATTR + + +; ----------------------------------------------------------------------------- +; cls : efface l'écran en remplissant txtmode_CELLS cellules par " " + ATTR +; ----------------------------------------------------------------------------- +txtmode_clear: + mov ax,0xB800 ; select video segment + mov es,ax + xor ax,ax ; 0 + ; mov [cursor_ofs],ax ; reset 'virtual' cursor + mov di,ax + ; default attr + space + mov ah, [txt_attr] + mov al, ' ' + ; + mov cx,2000 ; 25x80 + rep stosw ; attr + char + ret + +; ----------------------------------------------------------------------------- +; puts : imprime une chaîne ASCIIZ (DS:SI) en VRAM (ES:cursor_ofs) +; - gère '\n' (LF=10) comme nouvelle ligne +; - ignore '\r' (CR=13) +; ----------------------------------------------------------------------------- +txtmode_puts: +.next: + lodsb ; AL = *SI++ + test al, al + jz .done + + cmp al, 10 ; '\n' + je .lf + cmp al, 13 ; '\r' + je .next + + call txtmode_putc + jmp .next + +.lf: + call txtmode_newline + jmp .next +.done: + ret + +; ----------------------------------------------------------------------------- +; putc : imprime le caractère AL à la position du curseur logiciel +; ----------------------------------------------------------------------------- +txtmode_putc: + push ax + push bx + push di + + mov di, [cursor_ofs] + ; écrire char + attr + mov ah, txt_attr + stosw ; écrit AX à ES:DI, puis DI += 2 + + ; mettre à jour cursor_ofs + mov [cursor_ofs], di + + ; si fin d'écran, on “reboucle” au début (simple) + ; (vous pouvez remplacer par un scroll si besoin) + mov bx, 4000 ; 80x25x2 + cmp di, bx + jb .ok + mov word [cursor_ofs], 0 +.ok: + pop di + pop bx + pop ax + ret + +; ----------------------------------------------------------------------------- +; newline : avance le curseur au début de la ligne suivante (80 colonnes) +; ----------------------------------------------------------------------------- +txtmode_newline: + push ax + push dx + + mov ax, [cursor_ofs] ; offset en octets + xor dx, dx + mov cx, 160 ; taille d'une ligne en octets = 160 + div cx ; AX = ligne, DX = reste (offset dans la ligne) + + inc ax ; ligne suivante + cmp ax, 25 + jb .set + xor ax, ax ; si dépasse, revient en haut (simple) +.set: + + mov cx, 160 ; cursor_ofs = ligne * 160 + mul cx ; DX:AX = AX*CX ; ici AX suffit + mov [cursor_ofs], ax + + pop dx + pop ax + ret +