;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; ;;;; VIDEO HARDWARE MODULE ;;;; (C) Copyright 1995 Gregory Ercolano ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; INITIALIZE SCREEN DRIVER MODULE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; initvideo proc near ;;; INIT SCROLLBACK ifdef PROM ;; ON A PROM? USE MOST OF A 64K SEGMENT FOR SCROLL BUFFER mov [scrbufin],0000h mov [scrbufinseg],1000h mov [scrbufstart],0000h mov [scrbufstartseg],1000h mov [scrbufend],SCRBUF_SIZE mov [scrbufendseg],1000h mov si,1000h mov di,0000h mov cx,SCRBUF_SIZE mov al,0 call memset ;SI:DI filled with value in AL for CX bytes else mov [scrbufin], offset scrbuf mov [scrbufinseg],cs mov [scrbufstart], offset scrbuf mov [scrbufstartseg],cs mov [scrbufend], offset scrbuf+SCRBUF_SIZE mov [scrbufendseg],cs mov si,[scrbufinseg] mov di,offset scrbuf mov cx,SCRBUF_SIZE mov al,0 call memset ;SI:DI filled with value in AL for CX bytes endif ;;; INIT CURSOR'S APPEARANCE mov ah,0ah ; 6845 register for cursor set mov cx,000dh ; block cursor (all scanlines on) call send_6845 ;;; INIT MISC VIDEO VARIABLES mov [phystop],00d ; set physical top line mov [physbottom],23d ; set physical bottom line mov [physleft],00d ; set physical left edge mov [physright],79d ; set physical right edge call resetscrollregion ; set logical top/bottom/left/right edges mov [curx],0h mov [cury],0h ; home cursor mov [curxsave],0h mov [curysave],0h mov [vidbackscroll],0h ; back scroll index (0=in active display) mov [hexdebug_x],0h ; hex debug byte position on line mov [vidflags],NO_WRAP ; initial video flags call curupdate ; update hardware cursor mov [videsc],0 ; clear esc modes call clearescbuf ; clear the escape buffer mov [vidcolor],07h ; normal screen color mov al,1 call cleartabstops ; reset all tabstops to every 8 chars mov [vidblank],0 ; no blanked screen ;;; GET SEGMENT ADDRESS OF VIDEO RAM mov bx,0b800h ; assume graphics card push ds mov ax,0040h mov ds,ax mov ax,[ds:0010h] ; equipment flag and ax,30h ; CRT bits cmp ax,30h ; monochrome? jne iscga ; no mov bx,0b000h iscga: pop ds mov [vidseg],bx ; save segment address ret initvideo endp ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PRINT CHARACTER IN AL ;; Handles ANSI modes ;; Handles DEBUG modes ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; printchar proc near test [vidflags],HEX_DEBUG jf realprintchar jmp hexdebug realprintchar: push ax mov al,[bottom] cmp [cury],al ; ENSURE WE NEVER WRITE BELOW BOTTOM LINE jle pc_nostat mov [cury],al ; clamp to bottom line pc_nostat: pop ax cmp [videsc],0 ; first see if this is part of an ESC sequence je pc_xescmode call videscsequence ; HANDLE ESCAPE SEQUENCE IN PROGRESS jmp printchardone pc_xescmode: cmp al,20h ; CONTROL CHAR? Do tests to handle it jl pc_xprintable printrawchar: ; WRITE THE RAW CHARACTER TO SCREEN, NO MATTER WHAT IT LOOKS LIKE ; Advance the cursor, as necessary. ; push ax call vidaddr ; AX returns offset address mov di,ax pop ax mov ah,[vidcolor] ; hi byte is current attribute stosw ; write the character and attribute inc [curx] ; adjust cursor push ax mov al,[right] cmp [curx],al ; hit right edge? pop ax jg pc_offedge jmp printchardone pc_offedge: test [vidflags],NO_WRAP ; don't wrap when we hit edge? jt pc_nowrap push ax mov al,[left] ; wrap to left edge mov [curx],al pop ax jmp pc_lf ; advance to next line pc_nowrap: push ax mov al,[right] mov [curx],al ; clamp cursor to right edge pop ax jmp printchardone pc_xprintable: cmp al,00h ; VT100: 00h - NULL? jnz pc_x00 jmp printchardone ; take no action (VT100 manual, pp.42) pc_x00: cmp al,07h ; VT100: 07h - BELL? jne pc_x07 ; INVERT THE SCREEN FOR 4 TIMER TICKS call vidsavescrn call vidinvert mov cx,2 ; 2 ticks is approx 1/8 sec call wait_ticks call vidresscrn ; restore screen/cursor jmp printchardone pc_x07: cmp al,08h ; VT100: 08h - BACKSPACE? jne pc_x08 push ax mov al,[left] cmp [curx],al ; already at or beyond left edge? jg pc_bs mov [curx],al ; clamp to left edge pop ax jmp printchardone ; do not wrap around (VT100 manual, pp.42) pc_bs: pop ax dec [curx] ; move back jmp printchardone pc_x08: cmp al,09h ; VT100: 09h - TAB? jne pc_x09 mov ah,0 mov al,[curx] mov si,offset tabstops add si,ax ; index into tabstop table ; FIND THE NEXT NEAREST TABSTOP TO THE RIGHT pc_tabloop: inc si ; move to right one char inc al cmp al,[right] ; hit right edge? jle pc_xtabhitedge mov al,[right] ; stop at right edge jmp pc_tabsetpos pc_xtabhitedge: cmp byte ptr [ds:si],1 ; found tabstop yet? jne pc_tabloop pc_tabsetpos: mov [curx],al jmp printchardone pc_x09: cmp al,0ah ; VT100: 0ah - LINEFEED? jne pc_x0a pc_lf: call linefeed jmp printchardone pc_x0a: cmp al,0bh ; VT100: 0bh - VERTICAL TAB? je pc_lf ; handle as line feed, VT100 Manual, pp.42 cmp al,0ch ; VT100: 0ch - FORM FEED? je pc_lf ; handle as line feed, VT100 Manual, pp.42 cmp al,0dh ; VT100: 0dh - CARRIAGE RETURN? jne pc_x0d push ax mov al,[left] mov [curx],al ; force cursor to left edge pop ax jmp printchardone pc_x0d: cmp al,1bh ; VT100: 1bh - ESC? jne pc_x1b call videscsequence jmp printchardone ; and otherwise ignore. pc_x1b: cmp al,7fh ; VT100: 7fh - DEL? jne pc_x7f jmp printchardone ; do not print, VT100, pp.42 pc_x7f: jmp printrawchar ; whatever the char is, it's printable after all printchardone: call curupdate ; update hardware cursor, whatever we did to it ret ; done printchar endp ; ; HANDLE ESCAPE SEQUENCE CHARACTERS ; Buffered escape sequence includes leading ESC.. ; videscsequence proc near ; BUFFER THE ESC SEQUENCE push si mov si,[videsc] add si,offset escbuf mov [ds:si],al ; buffer the sequence inc si inc [videsc] cmp si,offset escbufend ; buffer overrun? pop si jl ves_xescbufend ; no. jmp ves_clearesc ; buffer overrun. cancel esc sequence ves_xescbufend: cmp [videsc],1 ; first character in ESC sequence? jnz ves_x0 ;;;;; HANDLE FIRST CHARACTER IN ESC SEQUENCE ret ; do nothing if just the ESC received ves_x0: cmp [videsc],2 ; second character in ESC sequence? je ves_2 jmp ves_x2 ves_2: ;;;;; HANDLE SECOND CHARACTER IN ESC SEQUENCE cmp al,'[' ; VT100: ESC '['? jne ves_xbrackchar ret ; ignore. (part of larger sequence) ves_xbrackchar: cmp al,'#' ; VT100: ESC '#'? jne ves_xpndchar ret ; ignore. (part of larger sequence) ves_xpndchar: cmp al,'Z' ; VT100: ESC 'Z'? (TERMINAL IDENT) jne ves_xescz mov si,offset esczresponsemsg call serialoutstring jmp ves_clearesc ves_xescz: cmp al,'=' ; VT100: ESC '='? (IGNORE: APPL MODE) jne ves_xescequal ; NOT SUPPORTED: keypad application mode jmp ves_clearesc ves_xescequal: cmp al,'>' ; VT100: ESC '>'? (IGNORE: NUM MODE) jne ves_xescgt ; NOT SUPPORTED: keypad numeric mode jmp ves_clearesc ves_xescgt: cmp al,'7' ; VT100: ESC '7'? (SAVE CURSOR) jne ves_xseven ; SAVE CURSOR POSITION mov al,[curx] ; save cursor position mov [curxsave],al mov al,[cury] mov [curysave],al jmp ves_clearesc ves_xseven: cmp al,'8' ; VT100: ESC '8'? (RESTORE CURSOR) jne ves_xesc8 ; RESTORE PREVIOUS CURSOR POSITION mov al, [curxsave] ; restore cursor position mov [curx],al mov al, [curysave] mov [cury],al jmp ves_clearesc ves_xesc8: cmp al,'D' ; VT100: ESC 'D'? (LINE FEED) jne ves_xescd call linefeed ; handle like a LF (VT100, pp.53) jmp ves_clearesc ves_xescd: cmp al,'E' ; VT100: ESC 'E'? (CRLF) jne ves_xesce mov [curx],0 call linefeed ; handle like a CRLF (VT100, pp.53) jmp ves_clearesc ves_xesce: cmp al,'H' ; VT100: ESC 'H'? (SET TAB STOP) jne ves_xesch mov si,offset tabstops mov al,[curx] mov ah,0 mov byte ptr [ds:si],1 ; set a tab stop at current xpos jmp ves_clearesc ves_xesch: cmp al,'M' ; VT100: ESC 'M'? (REVERSE LF) jne ves_xesccapm call revlinefeed ; handle as reverse LF (VT100, pp.53) jmp ves_clearesc ves_xesccapm: cmp al,'c' ; VT100: ESC 'c'? (RESET TERMINAL) jne ves_xescc jmp master_init ; 'reset terminal' ves_xescc: ; UNKNOWN ESC <> SEQUENCE. CANCEL MODE jmp ves_clearesc ves_x2: cmp byte ptr [escbuf+1],'#' ; are we in ESC '#' mode? je ves_pnd jmp ves_xpnd ves_pnd: cmp [videsc],3 ; third char in sequence? je ves_pnd3rd jmp ves_pndx3rd ves_pnd3rd: cmp al,'8' ; VT100: ESC # 8? (SCREEN OF 'E'S) jne ves_xescpnd8 ; FILL SCREEN WITH E's mov ax,0745h call vidclear jmp ves_clearesc ves_xescpnd8: ; UNKNOWN ESC'#'<> SEQUENCE. CANCEL MODE jmp ves_clearesc ves_pndx3rd: ; UNKNOWN ESC'#'<><>.. SEQUENCE. CANCEL MODE jmp ves_clearesc ves_xpnd: cmp byte ptr [escbuf+1],'[' ; are we in ESC '[' mode? je ves_brackmode jmp ves_xescbrackmode ves_brackmode: cmp al,';' ; HANDLE NUMBERIC ###;### ARGS je ves_brackarg cmp al,'9' jg ves_xbrackarg cmp al,'0' jl ves_xbrackarg ves_brackarg: ret ; simply return, with the arg buffered ves_xbrackarg: cmp al,'A' ; VT100: ESC [ Ps A (CURSOR UP) jne ves_xescbracka mov si,offset escbuf add si,2 ; point to Ps mov ax,0100h ; default call escparseint mov al,[cury] sub al,ah ves_ywrap: cmp al,0 jge ves_escbracka_ywrap mov al,0 ves_escbracka_ywrap: cmp al,[physbottom] jle ves_escbracka_ywrap2 mov al,[physbottom] ves_escbracka_ywrap2: mov [cury],al call curupdate jmp ves_clearesc ves_xescbracka: cmp al,'B' ; VT100: ESC [ Ps B (CURSOR DOWN) jne ves_xescbrackb mov si,offset escbuf add si,2 ; point to Ps mov ax,0100h ; default call escparseint mov al,[cury] add al,ah jmp ves_ywrap ves_xescbrackb: cmp al,'C' ; VT100: ESC [ Ps C (CURSOR RIGHT) jne ves_xescbrackc mov si,offset escbuf add si,2 ; point to Ps mov ax,0100h ; default call escparseint mov al,[curx] add al,ah ves_xwrap: cmp al,0 jge ves_escbracka_xwrap mov al,0 ves_escbracka_xwrap: cmp al,[physright] jle ves_escbracka_xwrap2 mov al,[physright] ves_escbracka_xwrap2: mov [curx],al call curupdate jmp ves_clearesc ves_xescbrackc: cmp al,'D' ; VT100: ESC [ Ps D (CURSOR LEFT) jne ves_xescbrackd mov si,offset escbuf add si,2 ; point to Ps mov ax,0100h ; default call escparseint mov al,[curx] sub al,ah jmp ves_xwrap ves_xescbrackd: cmp al,'K' ; VT100: ESC [ Ps K (CLEOL) jne ves_xescbrackk mov si,offset escbuf add si,2 ; point to Ps mov ax,0000h ; default call escparseint ; ; 00 - clear to end of line ; 01 - clear to start of line ; 02 - clear line ; cmp ah,00d jne ves_escbrackk_x00 push ax ; VT100: ESC [ 0 K (CLEAR END OF LINE) push bx mov al,[curx] mov ah,[cury] mov bl,[physright] mov bh,[cury] mov dx,CLEAR_SCRN_WORD call clearxy2xy pop bx pop ax jmp ves_clearesc ves_escbrackk_x00: cmp ah,01d jne ves_escbrackk_x01 push ax ; VT100: ESC [ 1 K (CLEAR START OF LINE) push bx mov al,[physleft] mov ah,[cury] mov bl,[curx] mov bh,[cury] mov dx,CLEAR_SCRN_WORD call clearxy2xy pop bx pop ax jmp ves_clearesc ves_escbrackk_x01: cmp ah,02d jne ves_escbrackk_x01 push ax ; VT100: ESC [ 2 K (CLEAR CURRENT LINE) push bx mov al,[physleft] mov ah,[cury] mov bl,[physright] mov bh,[cury] mov dx,CLEAR_SCRN_WORD call clearxy2xy pop bx pop ax ves_escbrackk_x02: jmp ves_clearesc ves_xescbrackk: cmp al,'J' ; VT100: ESC [ Ps J (CLEAR TO EOS) je ves_escbrackj jmp ves_xescbrackj ves_escbrackj: mov si,offset escbuf add si,2 ; point to Ps mov ax,0000h ; default call escparseint ; ; 00 - erase from cursor to EOS inclusive (DEFAULT) ; 01 - erase from cursor to TOS inclusive ; 02 - erase entire screen ; cmp ah,00d jne ves_xescbrackj00 push ax ; VT100: ESC [ 0 J (CLEAR TO EOS) push bx mov al,[curx] mov ah,[cury] mov bl,[physright] mov bh,[physbottom] mov dx,CLEAR_SCRN_WORD call clearxy2xy pop bx pop ax jmp ves_clearesc ves_xescbrackj00: cmp ah,01d jne ves_xescbrackj01 push ax ; VT100: ESC [ 1 J (CLEAR TO TOS) push bx mov al,[physleft] mov ah,[phystop] mov bl,[curx] mov bh,[cury] mov dx,CLEAR_SCRN_WORD call clearxy2xy pop bx pop ax jmp ves_clearesc ves_xescbrackj01: cmp ah,02d jne ves_xescbrackj02 push ax ; VT100: ESC [ 2 J (CLEAR SCREEN) push bx mov al,[physleft] mov ah,[phystop] mov bl,[physright] mov bh,[physbottom] mov dx,CLEAR_SCRN_WORD call clearxy2xy pop bx pop ax jmp ves_clearesc ves_xescbrackj02: jmp ves_clearesc ves_xescbrackj: cmp al,'f' ; VT100: ESC [ Pn;Pn f (CURSOR POSN) je ves_escbrackh ; same as ESC [ Pn; Pn H cmp al,'H' ; VT100: ESC [ Pn;Pn H je ves_escbrackh jmp ves_xescbrackh ves_escbrackh: mov si,offset escbuf add si,2 ; point to Ps mov ax,0101h ; default if numbers unspecified call escparseint ; AL=X pos, AH=Y pos call setcurpos ; handle 1 based #s, ORIGIN mode, etc jmp ves_clearesc ves_xescbrackh: cmp al,'g' ; VT100: ESC [ Pv g (CLEAR TAB STOPS) jne ves_xescbrackg ; (TAB STOPS) mov si,offset escbuf add si,2 mov al,0 ; assume clear all tabs to _nothing_ cmp byte ptr [ds:si],'3' ; Pv = 3? clear ALL tabs jne ves_escgx3 mov al,1 ; use default tabstops every 8 ves_escgx3: call cleartabstops ; AL=0 clear tabs, AL=1 default tabstops jmp ves_clearesc ves_xescbrackg: ; ; I YAM HERE: ; ENSURE WE SUPPORT MULTIPLE 'Pv' SPECS WITHIN ONE ESC [ SEQUENCE! ; cmp al,'m' ; VT100: ESC [ Pv m (SET COLOR) je ves_escm jmp ves_xescbrackm ves_escm: mov si,offset escbuf add si,2 ; point to Ps mov ax,0000h ; default is reset to normal call escparseint ; AH=subfunction index mov al,ah cmp al,00h ; VT100: ESC [ 0 m (RESET COLOR) jne ves_escm_x0 mov [vidcolor],07h ; 'normal' video jmp ves_escm_done ves_escm_x0: cmp al,01h ; VT100: ESC [ 1 m (BOLD COLOR) jne ves_escm_x1 or byte ptr [vidcolor],00001000b ; set bold bit jmp ves_escm_done ves_escm_x1: cmp al,02h ; VT100: ESC [ 2 m (HALF INTENS) jne ves_escm_x2 and byte ptr [vidcolor],11110111b ; clear bold bit jmp ves_escm_done ves_escm_x2: cmp al,04h ; VT100: ESC [ 4 m (UNDERLINE) jne ves_escm_x4 ; or byte ptr [vidcolor],00000001b; set underline bit (on MDA only) jmp ves_escm_done ves_escm_x4: cmp al,05h ; VT100: ESC [ 5 m (BLINK) jne ves_escm_x5 or byte ptr [vidcolor],10000000b; set blink bit jmp ves_escm_done ves_escm_x5: cmp al,07h ; VT100: ESC [ 7 m (REVERSE VIDEO) jne ves_escm_x7 mov al,[vidcolor] mov ah,al and al,01110111b ; get RGB bits only and ah,10001000b ; get intensity/blink bits only rol al,1 ; swap RGB fg with bg rol al,1 rol al,1 rol al,1 or al,ah mov [vidcolor],al jmp ves_escm_done ves_escm_x7: cmp al,30d ; VT100: ESC [ 30 m thru ESC [ 37 m jl ves_escm_x30 ; 30-37=black,red,grn,yel, cmp al,37d ; blu,mag,cya,wht respectively jg ves_escm_x30 mov ah,0 mov si,ax sub si,30d add si,offset textcolortable mov al,[vidcolor] and al,11111000b or al,[ds:si] mov [vidcolor],al jmp ves_escm_done ves_escm_x30: cmp al,40d ; VT100: ESC [ 40 m thru ESC [ 47 m jl ves_escm_x40 ; 40-47=black,red,grn,yel, cmp al,47d ; blu,mag,cya,wht respectively jg ves_escm_x40 mov ah,0 mov si,ax sub si,40d add si,offset pagecolortable mov al,[vidcolor] and al,10001111b or al,[ds:si] mov [vidcolor],al jmp ves_escm_done ves_escm_x40: ves_escm_done: jmp ves_clearesc ves_xescbrackm: cmp al,'%' ; DEBUG jne ves_xdebug mov si,offset escbuf add si,2 ; point to Ps mov ax,0809h ; default if numbers unspecified call escparseint int 3 ves_xdebug: ; UNKNOWN ESC '[' SEQUENCE. CANCEL MODE. jmp ves_clearesc ves_xescbrackmode: ; UNKNOWN ESC SEQUENCE. CANCEL MODE. jmp ves_clearesc ; CLEAR ESCAPE SEQUENCE, DONE ves_clearesc: call clearescbuf jmp printchardone videscsequence endp ; ; CLEAR ANY PENDING ESCAPE MODES ; clearescbuf proc near mov [videsc],0 ; ZERO OUT ESCAPE BUFFER push cx push si push di mov si,offset escbuf push cs pop di mov cx,ESCBUF_SIZE mov al,0 call memset pop di pop si pop cx ret clearescbuf endp ; CREATE A VIDEO ADDRESS BASED ON CURRENT X/Y LOCATION ; ; AX returns address offset into video memory ; All other regs preserved. ; vidaddr proc near mov al,[curx] mov ah,[cury] ; ; CREATE A VIDEO ADDRESS BASED ON X/Y IN AL/AH RESPECTIVELY ; ; AL=X ; AH=Y ; vidaddrax: push bx push ds push ax mov ax,0040h mov ds,ax pop ax mov bx,ax mov al,ah mul byte ptr [ds:004ah] ;BIOS: width of hardware screen mov bh,0 add ax,bx sal ax,1 pop ds pop bx ret vidaddr endp ; SEND WORD TO 6845 VIDEO CONTROLLER ; ; AH=6845 register number to send word ; CX=word to send ; All other regs preserved ; send_6845 proc near push dx push ds push ax mov ax,0040h ; BIOS area mov ds,ax mov dx,[ds:0063h] ; BIOS: 6845 base address for video card pop ax pop ds mov al,ah ; reg# out dx,al ; send it inc dx mov al,ch ; hi byte of data sent first out dx,al dec dx mov al,ah ; reg#+1 inc al out dx,al inc dx mov al,cl ; lo byte of data sent last out dx,al pop dx ret send_6845 endp ; SCROLL THE ENTIRE SCREEN UP ONE LINE ; All registers preserved ; scrollup proc near push ax push bx push cx push dx push si push di call savetopline ; save the top line before scrolling ;;; LET'S CHEAT, AND USE THE BIOS FOR SCROLLING mov cl,[left] mov ch,[top] mov dl,[right] mov dh,[bottom] mov bh,[vidcolor] mov al,1 mov ah,6 int 10h pop di pop si pop dx pop cx pop bx pop ax ret scrollup endp ; SCROLL THE ENTIRE SCREEN DOWN ONE LINE ; All registers preserved ; scrolldown proc near push ax push bx push cx push dx push si push di ;;; LET'S CHEAT, AND USE THE BIOS FOR SCROLLING mov cl,[left] mov ch,[top] mov dl,[right] mov dh,[bottom] mov bh,[vidcolor] mov al,1 mov ah,7 int 10h pop di pop si pop dx pop cx pop bx pop ax ret scrolldown endp ; ; CLEAR THE ENTIRE PHYSICAL SCREEN INCLUDING STATUS LINE ; Used for screen blanking ; ; ax contains characters to use ; Preserves all registers, except AX ; vidclearall proc near push ax push bx push dx mov dx,ax mov ah,[phystop] mov al,[physleft] mov bh,[physbottom] mov bl,[physright] add bh,1 ; status line call clearxy2xy pop dx pop bx pop ax ret vidclearall endp ; CLEAR THE USER SCREEN ; (status line not altered) ; ; ax contains characters to use ; Preserves all registers ; vidclear proc near push ax push bx push dx mov dx,ax mov ah,[phystop] mov al,[physleft] mov bh,[physbottom] mov bl,[physright] call clearxy2xy pop dx pop bx pop ax ret vidclear endp ; ; INVERT THE ENTIRE SCREEN ; Used by screen flash routine ; vidinvert proc near push ax push bx mov ah,[phystop] mov al,[physleft] mov bh,[physbottom] mov bl,[physright] inc bh ; get status line too call invertxy2xy pop bx pop ax ret vidinvert endp ; DO A LINE FEED, SCROLL AS NECESSARY ; linefeed proc near inc [cury] ; move cursor down one line push ax mov al,[bottom] cmp [cury],al ; hit bottom of logical screen? jle lf_done mov [cury],al ; keep cursor at bottom edge call scrollup ; scroll up one line, done lf_done: pop ax ret linefeed endp ; DO A REVERSE LINE FEED, SCROLL AS NECESSARY ; revlinefeed proc near mov al,[top] dec [cury] ; move cursor up one line cmp [cury],al ; hit top? jge rlf_done ; no mov [cury],al ; clamp cursor to top call scrolldown ; scroll down one line rlf_done: ret revlinefeed endp vidstatusline proc near push es push di push cx mov al,0cdh ; double line mov ah,07h mov es,[vidseg] mov di,80d*2*24d ; PUT STATUS ON LAST PHYSICAL LINE (line #25) mov cx,80d ; #words to write cld rep stosw pop cx pop di pop es ret vidstatusline endp ; PRINT CHARACTER IN AL BOTH AS A HEX DIGIT AND RAW ASCII CHARACTER hexdebug proc near ; PRINT CHARACTER AS RAW ASCII mov bl,[curx] mov [curxsave],bl push ax mov al,3*17d ; calc index for raw characters on right add al,[hexdebug_x] ; byte # on debug line mov [curx],al ; set curpos for raw characters pop ax push ax call printrawchar mov bl,[curxsave] mov [curx],bl pop ax push ax ; PRINT HEX MSN mov cl,4 shr al,cl call hd_printnib ; MSN pop ax ; PRINT HEX LSN call hd_printnib ; PRINT SPACE FOLLOWING HEX BYTE mov al,' ' call printrawchar ; ADVANCE HEX DEBUG POSITION ON LINE inc [hexdebug_x] cmp [hexdebug_x],16d jl hd_done mov [hexdebug_x],0 mov [curx],0 call linefeed hd_done: call curupdate ret hd_printnib: and al,0fh cmp al,09h jle hd_phn sub al,0ah add al,'A' jmp printrawchar hd_phn: add al,'0' jmp printrawchar hexdebug endp ; PRINT A NULL TERMINATED STRING TO THE SCREEN ; SI contains offset address of message to print. ; Use full ANSI compatible character out routines. ; printstring proc near mov al,[ds:si] cmp al,0 je pb_done push si call printchar pop si inc si jmp printstring pb_done: ret printstring endp ; SAVE THE TOP LINE OF SCREEN INTO BACKSCROLL BUFFER ; Used just before a line is to be scrolled off the top of the screen. ; Top line is pushed into a large 'ring buffer' that can traverse ; segments.. ; savetopline proc near push bx push cx push si push di push es push ds mov bx,[scrbufend] ; (save for ringwrap test) mov di,[scrbufin] mov es,[scrbufinseg] ; COMPUTE SI - TOP LINE push ax mov al,[left] mov ah,[top] call vidaddrax mov si,ax pop ax mov ds,[vidseg] mov cx,80d ; #words to move cld stl_loop: movsw ; save character and attribute cmp di,bx ; ring wrap? jc stl_noringwrap sub di,SCRBUF_SIZE ; ring wrap back to beginning stl_noringwrap: loop stl_loop pop ds push ds mov [scrbufin],di ; update in point pop ds pop es pop di pop si pop cx pop bx ret savetopline endp ; SAVE THE ENTIRE ACTIVE VIDEO SCREEN IN A 4K BUFFER ; Used when viewing backscroll screens ; vidsavescrn proc near push ax push cx push si push di push es push ds mov al,[curx] ; save cursor position too mov [curxsave],al mov al,[cury] mov [curysave],al mov si,0000h ; top of screen mov di,offset scrsavebuf ; 4k buffer mov ax,ds mov es,ax ; dest is buffer mov ds,[vidseg] ; source is video scrn mov cx,80d*25d ; #words to move cld rep movsw pop ds pop es pop di pop si pop cx pop ax ret vidsavescrn endp ; RESTORE THE ENTIRE ACTIVE VIDEO SCREEN FROM 4K BUFFER ; Used when restoring active screen from a backscroll ; vidresscrn proc near push ax push cx push ds push es push si push di mov al,[curxsave] ; restore cursor position mov [curx],al mov al,[curysave] mov [cury],al call curupdate mov si,offset scrsavebuf mov di,0000h ; top of screen mov es,[vidseg] ; dest is screen mov ax,cs mov ds,ax ; source is buffer mov cx,80d*25d ; #words to move cld rep movsw pop di pop si pop es pop ds pop cx pop ax ret vidresscrn endp ; TOGGLE THE DEBUG MODE ; Resets the debug mode cursor position ; vidtoggledebug proc near xor [vidflags],HEX_DEBUG ; toggle debug mode mov [hexdebug_x],0h ; hex debug byte position zeroed ; NEW LINE mov al,0dh call realprintchar mov al,0ah call realprintchar ret vidtoggledebug endp ; UPDATE THE BACKSCROLL BUFFER TO THE DISPLAY ; Use the vidbackscroll value to figure out how many lines ; back into the buffer we want to display. ; vidupdatebackscroll proc near push ax push bx push cx push dx push si push es push ds ; COMPUTE STARTING LOCATION mov cx,[vidbackscroll] ; number of lines to scroll back mov si,[scrbufin] mov dx,si ; save [scrbufin] address vubs_compute: sub si,80d*2 cmp [scrbufstart],si jz vubs_nowrap jc vubs_nowrap add si,SCRBUF_SIZE ; wrap around vubs_nowrap: loop vubs_compute ; SI NOW CONTAINS STARTING ADDRESS IN BACKSCROLL BUFFER FOR TOP LINE mov bx,[scrbufend] ; (save end offset for ringwrap check) mov di,0000h ; top of screen mov es,[vidseg] mov ds,[scrbufinseg] mov cx,80d*24d ;# words to move cld vubs_loop: movsw cmp bx,0001h ; switched? jz vubs_noringwrap ; yes, no ring wrap checking cmp si,dx ; wrapped into display screen? jne vubs_nosw mov si,offset scrsavebuf ; switch to displaying saved screen mov bx,0001h ; flag successive loops jmp vubs_noringwrap vubs_nosw: cmp si,bx ; ring wrap? jc vubs_noringwrap sub si,SCRBUF_SIZE ; backup vubs_noringwrap: loop vubs_loop pop ds pop es pop si pop dx pop cx pop bx pop ax ret vidupdatebackscroll endp ; CLEAR MEMORY TO A CERTAIN VALUE ; Similar to C's memset() function ; si:di filled with value in AL for CX bytes ; All regs preserved ; memset proc near push cx push si push di push es push si pop es cld rep stosb pop es pop di pop si pop cx ret memset endp ; CONVERT ASCII STRING(S) AT [ds:si] TO INTEGER IN AX ; si returns pointing to next non-numeric character atoi proc near push bx push cx push dx push di ; FIND END OF ASCII INTEGER STRING mov cx,0 mov bx,si atoi_loop: mov al,[ds:bx] cmp al,'0' jl atoi_nan cmp al,'9' jg atoi_nan inc bx inc cx cmp cx,5 ; no more than 5 digits can be parsed jle atoi_loop atoi_nan: cmp bx,si ; no digits found? jnz atoi_parsenum mov si,bx ; return char position in si mov ax,0ffffh ; AX=0 atoi_done: pop di pop dx pop cx pop bx ret atoi_parsenum: push bx ; save char position mov dx,0000h mov di,offset dectable ; PARSE THE DIGITS IN ASCENDING ORDER (RT TO LEFT) atoi_parseloop: dec bx mov al,[ds:bx] ; get next ascii digit and al,0fh ; 31->01, 39->09 mul byte ptr [ds:di] ; apply digit position add dx,ax ; compile result in dx add di,2 loop atoi_parseloop mov ax,dx ; return result pop si ; restore char position jmp atoi_done atoi endp ; CONVERT INTEGER IN AX TO AN ASCII STRING AT [ds:di] ; itoa proc near push ax push cx push dx push si push di mov si,10d xor cx,cx itoa_xzero: xor dx,dx div si push dx inc cx or ax,ax jne itoa_xzero itoa_loop: pop ax or al,30h ; convert to printable int mov [ds:di],al ; save it inc di loop itoa_loop mov byte ptr [ds:di],0 ; terminate string pop di pop si pop dx pop cx pop ax ret itoa endp ; PARSE TWO ';' SEPARATED INTEGERS AT [ds:si], RETURN LHS->AH, RHS->AL ; AH preserved if LHS not specified ; AL preserved if RHS not specified ; ; ; ; AH=<old-AH>, AL=<old-AL> ; 01; ; AH=01, AL=<old-AL> ; ;02 ; AH=<old-AH>, AL=02 ; 01;02 ; AH=01, AL=02 ; escparseint proc near push bx mov bx,ax ; save previous al/ah call atoi ; AL will be FF if LHS not specified mov ah,al cmp ah,0ffh ; LHS? jne epi_xalff ; yes, handle it mov ah,bh ; no LHS, maintain previous ah epi_xalff: cmp byte ptr [ds:si],';' ; RHS? je epi_2 ; yes, handle it mov al,bl ; no RHS, maintain previous al pop bx ret epi_2: mov bh,ah ; save mods so far inc si call atoi mov ah,bh cmp al,0ffh jne epi_xahff mov al,bl epi_xahff: pop bx ret ; CONVERT ASCII STRING(S) AT [ds:si] TO INTEGER IN AX escparseint endp ; CLEAR ALL TABSTOPS ; ; AL=0 - clear all tabstops to nothing(!) ; AL=1 - clear all tabstops to every 8 chars ; cleartabstops proc near push cx push si mov cx,0 mov si,offset tabstops cts_loop: push cx and cx,0111b cmp cx,0111b pop cx jne cts_notabstop mov [ds:si],al ; tab stop every 8 chars jmp cts_tabstop cts_notabstop: mov byte ptr [ds:si],0 cts_tabstop: inc si inc cx cmp cx,TABSTOP_SIZE jl cts_loop ; (jle because 1 based) pop si pop cx ret cleartabstops endp ; ; REVERT LOGICAL SCROLL WINDOW TO PHYSICAL DEFAULTS ; resetscrollregion proc near push ax mov al,[phystop] mov [top],al mov al,[physbottom] mov [bottom],al mov al,[physleft] mov [left],al mov al,[physright] mov [right],al pop ax ret resetscrollregion endp ; ; SET CURSOR POSITION, AL=X, AH=Y (1 based!!) ; ; Take into account ORIGIN mode, ie. absolute vs relative cursor ; addressing with respect to logical screen edges. ; If AL is -1, cursor is disabled. ; setcurpos proc near push ax cmp al,-1 jz scp_save test [vidflags],RELATIVE_MODE jf scp_norelative add al,[left] ; apply offsets add ah,[top] scp_norelative: cmp al,0 jz scp_alzero dec al ; 1 base -> 0 base scp_alzero: cmp ah,0 jz scp_ahzero dec ah scp_ahzero: scp_save: mov [curx],al ; save curpos mov [cury],ah call curupdate ; update hardware cursor pop ax ret setcurpos endp ; UPDATE THE HARDWARE CURSOR POSITION ; All regs saved ; Physical limits are enforced ; If [curx] == -1, cursor is disabled. ; curupdate proc near push ax cmp [curx],-1 jne cu_nm1 push cx mov cx,80d*26d ; move cursor off the screen mov ah,0eh call send_6845 pop cx pop ax ret cu_nm1: mov al,[physright] cmp [curx],al ; enforce physical screen limits jle cu_okx mov [curx],al cu_okx: mov al,[physbottom] cmp [cury],al jle cu_oky mov [cury],al cu_oky: push cx call vidaddr ; get video memory address offset mov cx,ax sar cx,1 ; /2 for character offset mov ah,0eh ; 6845 register# to set cursor position call send_6845 cu_done: pop cx pop ax ret curupdate endp ; ; HANDLE SCROLL BACK CHANGES ; AX=#lines to scroll back (neg #s scroll down) ; scrollbackadjust proc near cmp byte ptr [vidbackscroll],0 jnz sba_add ; already in backscroll mode? skip test ax,10000000b ; negative number (scroll down)? jt sba_done ; nowhere to go call vidsavescrn ; transition -> backscroll mode push ax mov al,-1 mov ah,-1 call setcurpos pop ax sba_add: add [vidbackscroll],ax cmp [vidbackscroll],0 jg sba_noactive mov [vidbackscroll],0 ; down too far? (gone below zero) call vidresscrn sba_done: ret sba_noactive: cmp [vidbackscroll],SCRBUF_LINES ; up too high? jl sba_xoverrun mov [vidbackscroll],SCRBUF_LINES ; clamp to limit sba_xoverrun: call vidupdatebackscroll ; update backscroll ret scrollbackadjust endp blankupdate proc near test byte ptr [vidblank],1 ; blank the screen? jt bu_blank jmp vidresscrn ; restore screen/cursor bu_blank: cmp byte ptr [vidbackscroll],0 jz bu_nobackscroll mov byte ptr [vidbackscroll],0 ; ensure not in backscroll mode call scrollbackadjust bu_nobackscroll: call vidsavescrn ; save screen in present state mov ax,0000h ; clear to blanks call vidclearall mov [curx],0ffh ; move cursor off the screen call curupdate ret blankupdate endp ; ; CLEAR A REGION OF THE SCREEN ; ; al/ah - X/Y upper left screen to clear (inclusive) ; bl/bh - X/Y lower right screen to clear (inclusive) ; dl/dh - character/attribute to use for clear ; clearxy2xy proc near push ax push bx push cx push dx push si push di push ds push es call vidaddrax mov di,ax ; upper left address -> DI mov ax,bx call vidaddrax mov cx,ax ; lower right address -> CX sub cx,di ; convert CX to a count of bytes shr cx,1 ; divide by 2 for byte -> word convert add cx,1 ; handle 'inclusive' requirement mov es,[vidseg] mov ax,dx ; char/attrib pair to use for clear cld rep stosw ; clear the region pop es pop ds pop di pop si pop dx pop cx pop bx pop ax ret clearxy2xy endp ; ; INVERT A REGION OF THE SCREEN ; ; al/ah - X/Y upper left screen to invert (inclusive) ; bl/bh - X/Y lower right screen to invert (inclusive) ; invertxy2xy proc near push ax push bx push cx push dx push si push di push ds push es call vidaddrax mov di,ax ; upper left address -> DI mov si,ax mov ax,bx call vidaddrax mov cx,ax ; lower right address -> CX sub cx,di ; convert CX to a count of bytes shr cx,1 ; range of bytes -> words add cx,1 ; handle 'inclusive' requirement mov es,[vidseg] mov ax,[vidseg] mov ds,ax inc si ; address the attributes only inc di cld invloop: lodsb ; get attrib mov ah,al shl al,1 ; swap the hi and low 4 bits to do invert shl al,1 shl al,1 shl al,1 shr ah,1 shr ah,1 shr ah,1 shr ah,1 or al,ah stosb inc si inc di loop invloop pop es pop ds pop di pop si pop dx pop cx pop bx pop ax ret invertxy2xy endp ; ; al is number of characters to delete ; bl = X ; bh = Y ; deletechar proc near rep movsb deletechar endp ; WAIT SO MANY TICKS OF THE 18-TICK-PER-SECOND CLOCK ; CX is number of ticks to wait ; NOTE: first tick will be short.. ; wait_ticks proc near push ax push bx push ds mov ax,0040h ;; bios mov ds,ax wt_nexttick: mov bx,[ds:006ch] ;; timer low counter wt_wait: mov ax,[ds:006ch] cmp ax,bx ;; see if it changed je wt_wait loop wt_nexttick ;; it did, count down tick counter pop ds pop bx pop ax ret wait_ticks endp