%title "NNANSI Terminal Driver Version 5/91" %pagesize 60, 132 %noconds JUMPS ; clarify branches -- TASM V2.0 recommended ;--- nnansi.asm ---------------------------------------------------------- ; New, New ANSI terminal driver. ; Optimized for speed in the case of multi-character write requests. ; Original NANSI terminal driver (C) 1986 Daniel Kegel, Pasadena, CA ; Modifications by Tom Almy without restrictions. ; May be distributed for educational and personal use only ; See file NNANSI.DOC for licensing information. ; Daniel Kegel, Bellevue, Washington & Pasadena, California ; Revision history: ; 5 july 85: brought up non-ANSI portion except forgot backspace ; 6 july 85: split off ANSI stuff into other files, added backspace ; 11 july 85: fixed horrible bug in getchar; changed dosfns to subroutines ; 12 july 85: fixed some scrolling bugs, began adding compaq flag ; 9 aug 85: added cursor position reporting ; 10 aug 85: added output character translation ; 11 aug 85: added keyboard redefinition, some EGA 80x43 support ; 19 Aug 85: Fixed horrible bug in insert/delete line. ; 26 Aug 85: Fixed simple limit-to-one-too-few-lines bug in ins/del line; ; anyway, it inserts 24 lines when on line 2 now. Whether it's fixed... ; 4 Sept 85: Fixed bug created on 26 Aug 85; when limiting ins/del line ; count, we are clearing, not scrolling; fixed BIOS call to reflect this. ; 10 sept 85: Tandy 2000 support via compaq flag (finding refresh buffer) ; 30 Jan 86: removed Tandy 2000 stuff, added graphics mode support ; 12 feb 86: added int 29h handler, added PUSHA/POPA, added direct beep, ; direct cursor positioning, takeover of BIOS write_tty, ; noticed & squashed 2 related bugs in tab expansion ; 30 Jan 86: Added EGA cursor patch ; 31 Jan 86: Disabled insert/delete char in graphics modes ; Implemented keyboard redefinition reset ; 1 Feb 86: added video_mode and max_x test after mode set ; 13 feb 86: Squashed them again, harder ; 24 feb 86: There is a bug in the timing code used by the BEEP routine. ; If the addition of the beep period to the ; BIOS low timer word results in an overflow, the beep will be ; supressed. Also made code compatible eith earlier versions ; of assembler. ; Tom Almy, Tualatin, Oregon (toma@sail.labs.tek.com) modified the NANSI ; version ; 2.2 code for use in EGA/VGA environments only: ; 8 Jan 89: Additional compilation options ; Scrolling via reprogramming display start (*MUCH* faster) ; INT29 updates display directly if not control character. ; Various cleanups ; Nov 89: Some bug fixes, customization for various cards enhanced ; display modes, better handling of graphic cursor, graphic ; characters in 16 color modes are drawn by NNANSI rather ; than BIOS (much faster). ; Sept 90: Color backgrounds and XOR mode (as BLINK) in 16 color graphic ; modes. VGA has 43 or 50 line modes (instead of producing 50 ; (when 43 selected). FAST mode can be toggled via escape ; sequences. Lots of code clean up. Some old options incorporated ; (extra ANSI sequences) or deleted (output conversion). ; The fast graphic draw routine has been speeded up, since it ; was slower than some BIOSes (!). The BIOS TTY call has been ; redirected to the INT29 routine which has some efficiency ; speedups; this also saved code space. ; May 1991: Restored CGA and MDA support. MDA is optional. Added ; DOS 4.x compatibility. Added 40x43 and 40x50 text mode support. ; Added VESA support. Fixed bug allowing either = or ? or nothing ; in all set mode commands. Added enable/disable for bios ; write_tty takeover. Added drivers for new cards and changed ; some defaults. Merged source code into one file. ; Card id printout idea by Arend van den Brug. page TRUE equ 1 FALSE equ 0 INCLUDE CONFIG.INC page ; Option dependent definitions if VGA if EGA card_id macro db 'Generic EGA/VGA' endm else card_id macro db 'Generic VGA' endm endif else if EGA card_id macro db 'Generic EGA' endm else card_id macro db 'Generic NANSI Clone' endm endif endif IF key_redef buf_size equ init_buf_size ; size of parameter/redef buffer ELSE buf_size equ 20 ; size of parameter/redef buffer ENDIF IF cheap_pc .8086 ELSE .286c ENDIF page INCLUDE DRIVERS.INC page ; Some Useful Macros IF cheap_pc push_all macro push ax push bx push cx push dx push bp push si push di endm ELSE push_all macro pusha endm ENDIF IF cheap_pc pop_all macro pop di pop si pop bp pop dx pop cx pop bx pop ax endm ELSE pop_all macro popa endm ENDIF call_video macro ; call original video interrupt pushf ; push flags call dword ptr old_vid_bios endm draw_gcursor macro ; draw graphic cursor if quick_char mov ax, 8f16h call quick_graph else mov ax, 0916h ; draw cursor at location mov bx, 8fh mov cx, 1 call_video endif endm page keybuf struc ; Used in getchar len dw ? adr dw ? keybuf ends ABS40 segment at 40h org 1ah buffer_head dw ? ; Used in 'flush input buffer' dos call. buffer_tail dw ? org 49h crt_mode db ? crt_cols dw ? crt_len dw ? crt_start dw ? cursor_posn dw 8 dup (?) cursor_mode dw ? active_page db ? addr_6845 dw ? crt_mode_set db ? ; = 7 only if monochrome display adaptor crt_palette db ? org 6ch timer_low dw ? ; low word of time-of-day counter (18.2 hz) org 84h ega_rows db ? ; #rows-1 on display ega_points dw ? ; bytes per character ABS40 ends page CODE segment word public 'CODE' assume cs:CODE, ds:CODE ; Device Driver Header org 0 dd -1 ; next device if dos4 dw 08053h ; attributes else dw 8013h ; attributes endif dw strategy ; request header pointer entry dw interrupt ; request entry point db 'CON' ; device name (8 char) db 5 dup (20h) ; ... and 5 blanks) ; Identification- in case somebody TYPEs the assembled driver db 27,'[2J' db "NNANSI.SYS 05/91 for " card_id ife cheap_pc db "(80286)" else db "(80x86)" endif db 13, 10 db 'by Tom Almy based on code (C) Daniel Kegel, Pasadena, CA 1986.' db 13, 10, 26 even ;----- variable area -------------------- org $-38 ; overlay id string with uninitialized data if DOS4 org $-4 endif if key_redef org $-2 endif if MONO org $-2 endif req_ptr label dword req_off dw ? req_seg dw ? f_cptr_seg dw ? ; part of fastout write buffer pointer cur_parm_ptr dw ? ; last byte of parm area now used old_vid_bios dd ? ; pointer to old video bios routine saved_coords dw ? ; holds XY after a SCP escape sequence temp_val dw ? ; just a temporary param_buffer dw ? ; address of first byte free for new params param_end dw ? ; address of end of free area if key_redef redef_end dw ? ; address of end of redefinition area endif if dos4 old_mpx dd ? ; original mpx interrupt handler endif if MONO our_6845 dw ? endif no_c_flag db ? ; there is no graphic cursor on the screen. max_y db ? ; lines-1 max_cur_x label word ; used to get both max & cur at once max_x db ? ; line width (79 for 80x25 modes) cur_coords label word cur_x db ? ; cursor position (0 = left edge) cur_y db ? ; (0 = top edge) video_mode db ? ; ROM BIOS video mode (2=BW, 3=color, etc) string_term db ? ; either escape or double quote in_num db ? ; true if between a digit and a semi in parse int_29_buf db ? ; character buffer for int 29 calls fnkeybuf db ? ; holds second byte of fn key codes eat_key db ? ; Eaten key (bug fix) cpr_buf db 8 dup (?), '[' cpr_esc db 1bh ; descending buffer for cpr function even ; this should be redundant, if I did it right ; following four keybufs hold information about input ; Storage order determines priority- since the characters making up a function ; key code must never be separated (say, by a Control-Break), they have the ; highest priority, and so on. Keyboard keys (except ctrl-break) have the ; lowest priority. fnkey keybuf <0, fnkeybuf> ; fn key string (0 followed by scan code) cprseq keybuf <0> ; CPR string (ESC [ y;x R) brkkey keybuf <0, brkkeybuf> ; ^C if key_redef xlatseq keybuf <0> ; keyboard reassignment string endif escvector dw 0 ; state vector of ESCape sequencor brkkeybuf db 3 ; control C wrap_flag db 1 ; 0 = no wrap past line end cur_attrib db 7 ; current char attributes gmode_flag db 0 ; true if in graphics mode ; <0 for mono card gcursor db initgc ; true if graphic cursor enabled fmode db initfast ; in fast mode? bmode db initbiosw ; ANSI write_tty BIOS command? atr_flag db 0 ; 80h - blink, 8 - bright ; 4 - invisible 2 - reverse 1 underline color_flag db 7h ; fg and bg colors port_6845 equ 3d4h page ;------ xy_to_regs -------------------------------------------- ; on entry: x in cur_x, y in cur_y ; on exit: dx = chars left on line, di = address ; Alters ax, bx. xy_to_regs proc near ; Find number of chars 'till end of line, keep in DX mov ax, max_cur_x mov bx, ax ; save max_x & cur_x for next block xor ah, ah ; ax = max_x xchg dx, ax mov al, bh xor ah, ah ; ax = cur_x sub dx, ax inc dx ; dx is # of chars till EOL ; Calculate DI = current address in text buffer mov al, bl ; al = max_x inc al mul cur_y add al, bh ; al += cur_x adc ah, 0 ; AX is # of chars into buffer add ax, ax xchg di, ax ; DI is now offset of cursor. push ds mov ax, ABS40 mov ds, ax assume ds:ABS40 add di, crt_start ; crt offset ; the offset could be non-zero because ; of video pages or fast scrolling. pop ds assume ds:nothing ret xy_to_regs endp page ;------- dos_fn_tab ------------- ; This table is used in "interrupt" to call the routine that handles ; the requested function. if dos4 max_cmd equ 24 dos_fn_tab: dw dosfn0, nopcmd, nopcmd, badcmd, dosfn4, dosfn5, dosfn6 dw dosfn7, dosfn8, dosfn8, nopcmd, nopcmd dw badcmd, badcmd, badcmd, nopcmd, badcmd ; 12-16 dw badcmd, badcmd ; 17-18 unassigned dw dosfn19 dw badcmd, badcmd, badcmd ; 20-22 unassigned dw nopcmd, nopcmd else max_cmd equ 12 dos_fn_tab: dw dosfn0, nopcmd, nopcmd, badcmd, dosfn4, dosfn5, dosfn6 dw dosfn7, dosfn8, dosfn8, nopcmd, nopcmd endif ;------- strategy ---------------------------------------------------- ; DOS calls strategy with a request which is to be executed later. ; Strategy just saves the request. strategy proc far mov cs:req_off,BX mov cs:req_seg,ES ret strategy endp ;------ interrupt ----------------------------------------------------- ; This is where the request handed us during "strategy" is ; actually carried out. ; Calls one of 12 subroutines depending on the function requested. ; Each subroutine returns with exit status in AX. interrupt proc far sti push_all ; preserve caller's registers push ds push es ; Read requested function information into registers lds bx,cs:req_ptr xor ah,ah ; clear upper part of ax mov al,ds:[BX+02h] ; al = function code ; ; The next instruction blows up MASM 1.0 but who cares!! ; les si,[BX+0Eh] ; ES:SI = input/output buffer addr mov cx,[BX+12h] ; cx = input/output byte count cmp al, max_cmd ja unk_command ; too big, exit with error code xchg bx, ax shl bx, 1 ; form index to table of words mov ax, cs mov ds, ax call word ptr dos_fn_tab[bx] int_done: lds bx,cs:req_ptr ; report status or ax, 100h ; (always set done bit upon exit) mov [bx+03],ax pop ES ; restore caller's registers pop DS pop_all ret ; return to DOS. unk_command: call badcmd jmp int_done interrupt endp page ;----- BIOS break handler ----------------------------------------- ; Called by BIOS when Control-Break is hit (vector was set up in Init). ; Simply notes that a break was hit. Flag is checked during input calls. break_handler proc mov cs:brkkey.len, 1 iret break_handler endp page ;------ badcmd ------------------------------------------------------- ; Invalid function request by DOS. badcmd proc near mov ax, 813h ; return "Error: invalid cmd" ret badcmd endp ;------ nopcmd ------------------------------------------------------- ; Unimplemented or dummy function request by DOS. nopcmd proc near xor ax, ax ; No error, not busy. ret nopcmd endp page ;------- dos function #4 ---------------------------------------- ; Reads CX characters from the keyboard, places them in buffer at ; ES:SI. dosfn4 proc near jcxz dos4done mov di, si dos4lp: push cx call getchar pop cx stosb loop dos4lp dos4done: xor ax, ax ; No error, not busy. ret dosfn4 endp page ;-------- dos function #5: non-destructive input, no wait ------ ; One-character lookahead into the keyboard buffer. ; If no characters in buffer, return BUSY; otherwise, get value of first ; character of buffer, stuff into request header, return DONE. dosfn5 proc near call peekchar jz dos5_busy lds bx,req_ptr mov [bx+0Dh], al xor ax, ax ; No error, not busy. jmp short dos5_exit dos5_busy: MOV ax, 200h ; No error, busy. dos5_exit: ret dosfn5 endp page ;-------- dos function #6: input status -------------------------- ; Returns "busy" if no characters waiting to be read. dosfn6 proc near call peekchar mov ax, 200h ; No error, busy. jz dos6_exit xor ax, ax ; No error, not busy. dos6_exit: ret dosfn6 endp page ;-------- dos function #7: flush input buffer -------------------- ; Clears the IBM keyboard input buffer. Since it is a circular ; queue, we can do this without knowing the beginning and end ; of the buffer; all we need to do is set the tail of the queue ; equal to the head (as if we had read the entire queue contents). ; Also resets all the device driver's stuffahead buffers. dosfn7 proc near xor ax, ax mov fnkey.len, ax ; Reset the stuffahead buffers. mov cprseq.len, ax mov brkkey.len, ax if key_redef mov xlatseq.len, ax endif mov ax, ABS40 mov es, ax mov ax, es:buffer_head ; clear queue by making the tail mov es:buffer_tail, ax ; equal to the head xor ax, ax ; no error, not busy. ret dosfn7 endp page IF dos4 ;------- dos function #19: Generic IOCTL ; DOS4 way of setting display size using MODE command. ; We will do more than necessary. ; dosfn19 proc near les bx, req_ptr ; get request block mov ax, es:[BX+13] ; get function code cmp ax, 5f03h ; is it set information? jz setinfo cmp ax, 7f03h ; is it get information? jz getinfo mov ax, 8003h ; return bad command ret getinfo: les bx, es:[bx+19] ; data packet to set push ds mov ax, ABS40 ; address ABS40 area mov ds, ax assume ds:ABS40 mov word ptr es:[bx+0], 0 ; level and reserved fields mov word ptr es:[bx+2], 14 ; length field mov word ptr es:[bx+4], 0 ; flags mov al, crt_mode call set_gmode mov ax, 1 cmp cs:gmode_flag, 0 ; see if graphics mode jz fn19txt inc ax if MONO cmp cs:gmode_flag, -7 ; check for mono mode jne fn19txt dec ax mov word ptr es:[bx+8], 0 ; monochrome jmp short fn19mono endif fn19txt: mov word ptr es:[bx+8], 4 ; say 16 colors fn19mono: mov word ptr es:[bx+6], ax ; store mode mov ax, crt_cols mov word ptr es:[bx+0eh], ax ; character columns mov dx, 8 ; all characters are 8 pixels wide mul dx mov word ptr es:[bx+0ah], ax ; pixel columns xor ax, ax mov al, ega_rows inc ax mov word ptr es:[bx+10h], ax ; character rows mov dx, ega_points mul dx mov word ptr es:[bx+0ch], ax ; pixel rows pop ds assume ds:CODE xor ax, ax ret fn19fail: mov ax, 8003h ; unknown mode, failure ret setinfo: les bx, es:[bx+19] ; data packet to read mov dx, 80h ; monochrome, don't clear screen cmp word ptr es:[bx+8], 0 ; set to monochrome? je setismono inc dx setismono: mov ax, es:[bx+0eh] ; character columns cmp ax, 40 ; 40 columns? je fn1940col cmp ax, 80 jne fn19fail add dx, 2 ; mode 2 or 3 fn1940col: mov ax, es:[bx+10h] ; character rows cmp ax, 25 je fn1925lin IF VGA cmp ax, 50 je fn1950lin ENDIF cmp ax, 43 jne fn19fail IF VGA mov ax, 1201h ; 350 lines jmp short ln50join fn1950lin: mov ax, 1202h ; 400 lines ln50join: push dx mov bl, 30h ; set scan lines int 10h pop ax ; the mode ELSE mov ax, dx ENDIF int 10h ; set video mode mov ax, 1112h ; set 8x8 font mov bl, 0 int 10h mov ax, 1200h ; load new printscreen mov bl, 20h int 10h IF EGA ; set the EGA cursor mov dx, port_6845 ; '6845' command reg mov ax, 070ah ; start on line 7 out dx, ax mov al, 0bh ; end on line 7 out dx, ax ENDIF jmp short clrs fn1925lin: IF VGA push dx mov ax, 1202h ; select 400 scan lines mov bl,30h ; this call ignored on EGA int 10h pop ax ELSE mov ax, dx ENDIF int 10h ; set video mode clrs: push ds ; address abs40 area mov ax, ABS40 mov ds, ax ASSUME ds:ABS40 mov ax, 600h ; clear display area xor cx, cx ; upper left corner mov dx, crt_cols dec dl ; right column mov dh, ega_rows ; lower row mov bh, cs:cur_attrib ; character attribute int 10h pop ds ASSUME ds:CODE xor ax, ax ; return success ret dosfn19 endp ENDIF page if dos4 ;--- new_mpx ------------------------------------------------ ; This routine acknoledges that an ansi driver is installed. ; new_mpx proc cmp ax, 1a00h ; is this for us? jz ackansi jmp dword ptr cs:old_mpx ; no -- go to next handler ackansi: mov al, 0ffh ; say we are installed iret new_mpx endp endif page ;--- new_vid_bios ------------------------------------------- ; new_vid_bios takes the set cursor, get display mode, change mode, and ; get mode calls. ; If bios_write_tty defined, new_vid_bios replaces the write_tty call. ; This gives BIOS ANSI capability. ; However, it takes away the escape character. ; If this is not desired, just tell init to not take over the vector. ; ; All other calls get sent to the old video bios. new_vid_bios proc STI IF bios_write_tty cmp ah, 0Eh je nvb_write_tty ENDIF cmp Ah, 02h ; set cursor position command? je nvb_setcursor cmp Ah,0 ; change mode command? je nvb_smode cmp cs:fmode,0 ; slow mode? je new_vid_pass ; then pass it on cmp Ah, 0Fh ; get display mode command? je nvb_display cmp Ah, 06h ; clear screen command? je nvb_scroll cmp Ah, 07h ; alternative cls command? je nvb_scroll IF VESA cmp AX, 4f02h ; set extended display mode? je nvb_smode cmp AX, 4f03h ; get extended display mode? je nvb_display ENDIF new_vid_pass: jmp dword ptr cs:old_vid_bios IF bios_write_tty ; WRITE TTY SUBCOMMAND nvb_write_tty: cmp cs:bmode, 0 ; don't write with our driver? jz new_vid_pass push cx ; save register mov cl, cs:cur_attrib ; If in graphics mode, BL is new color cmp cs:gmode_flag, 0 jle nvb_wt_text mov cs:cur_attrib, bl ; ja? nvb_wt_text: int 29h mov cs:cur_attrib, cl pop cx iret ENDIF ; GET DISPLAY MODE SUBCOMMAND nvb_display: cmp cs:gmode_flag,0 ; Graphic mode? Mono? jnz new_vid_pass push ds push dx mov dx, ABS40 mov ds, dx assume ds:ABS40 cmp crt_start,0 ; At start of mem? jz nvb_pass call move_back jmp nvb_pass ; SCROLL DISPLAY SUBCOMMAND nvb_scroll: push ds push dx mov dx, ABS40 mov ds, dx assume ds:ABS40 mov cs:no_c_flag, 1 ; if graphic, don't draw cursor afterwards cmp cs:gmode_flag,0 ; graphic mode? jnz nvb_pass cmp crt_start,0 ; not at start of mem jz nvb_pass cmp al, 0 ; scroll, not erase jne nvb_pass or cx, cx ; not entire screen? jne nvb_pass pop dx push dx inc dl cmp dl, byte ptr crt_cols ; same question, max columns ja nvb_pass ; >size is full screen, though cmp dh, ega_rows ; same question, max rows ja nvb_pass ; >size is full screen, though push ax ; erase is easier since we dont move screen xor ax,ax mov crt_start,0 ; reset offsets mov dx,port_6845 mov al,0ch out dx,ax inc al out dx,ax pop ax ; jmp nvb_pass ; jump is redundant nvb_pass: pop dx assume ds:nothing pop ds jmp dword ptr cs:old_vid_bios ; now doit ; SET CURSOR SUBCOMMAND nvb_setcursor: push ds push dx mov dx, ABS40 mov ds,dx assume ds:ABS40 cmp cs:gmode_flag,0 ; Alpha mode?, Mono mode? jle nvb_pass cmp cs:no_c_flag, 0 ; inhibited cursor? jnz nvb_pass ; then keep inhibited cmp cs:gcursor, 0 ; no cursor? jz nvb_pass ; then don't want one now! push_all draw_gcursor pop_all assume ds:nothing pop dx ; restore registers pop ds call_video ; original int 10h push_all draw_gcursor ; redraw the cursor pop_all iret ; return from interrupt ; SET DISPLAY MODE SUBCOMMAND nvb_smode: call_video push_all push ds mov dx, ABS40 mov ds, dx assume ds:ABS40 mov al, crt_mode ; get mode and check for being graphic call set_gmode mov cs:no_c_flag, al ; if graphic, then no cursor is on screen. pop ds assume ds:nothing pop_all iret new_vid_bios endp page ;------ int_29 ---------------------------------------------- ; Int 29 handles DOS quick-access putchar. ; Last device loaded with attribute bit 4 set gets accessed for ; single-character writes via int 29h instead of via interrupt. ; Must preserve all registers. ; Installed as int 29h by dosfn0 (init). int_29 proc near sti push ds push es push_all if fast29 cmp al, 20h ; control char? jb slow_way cmp cs:escvector, 0 ; middle of an escape sequence? jnz slow_way mov dx, ABS40 mov ds, dx ; set addressability assume ds:ABS40 mov cx, word ptr crt_mode ; mode in cl, columns in ch push ax mov al, cl call set_gmode ; check for graphics mode pop ax cmp cs:gmode_flag, 0 ; slow if graphics jg slow_way ; cmp cl, 3 ; graphics mode? ; ja slow_way xor bx, bx ; get cursor position mov bl, active_page add bx, bx mov dx, cursor_posn[bx] ; dh has y, dl has x inc dl ; point to next location cmp dl, ch ; at edge? jnb slow_way ; we can go with it! mov cursor_posn[bx], dx ; update pointer xchg ax, bx mov al, dh mul ch ; ax has line offset add al, dl adc ah, 0 ; total offset mov cx, bx mov bx, ax ; cl has character, bx offset mov ax, crt_start shr ax, 1 add bx, ax ; corrected cursor offset, either ; because of fast scroll or ; page<>0 if MONO mov dx, addr_6845 ; update cursor location else mov dx, port_6845 ; update cursor location endif mov al,0eh ; more effective to write two bytes at a time mov ah,bh out dx,ax inc al mov ah,bl out dx,ax mov al, cl ; get back character dec bx add bx, bx ; byte offset mov dx, 0b800h ; address screen if MONO cmp crt_mode, 7 jne int29_not_mono mov dh, 0b0h int29_not_mono: endif mov ds, dx assume ds:nothing mov ah, cs:cur_attrib mov ds:[bx], ax ; write character jmp short int_fin endif slow_way: mov cx, 1 mov bx, cs mov es, bx mov si, offset int_29_buf mov byte ptr es:[si], al call dosfn8 int_fin: pop_all pop es pop ds iret int_29 endp page ;------ dosfn8 ------------------------------------------------------- ; Handles writes to the device (with or without verify). ; Called with ; CX = number of bytes to write ; ES:SI = transfer buffer ; DS = CS, so we can access local variables. NOT ANY MORE dosfn8 proc near mov cs:f_cptr_seg, es ; save segment of char ptr ; Read the BIOS buffer address/cursor position variables. mov ax, ABS40 mov ds, ax assume ds:ABS40 ; Find current video mode and screen size. mov ax,word ptr crt_mode ; al = crt mode; ah = # of columns mov cs:video_mode, al dec ah ; ah = max column mov cs:max_x, ah ; Save graphics mode flag call set_gmode mov al, ega_rows ; number of display rows mov cs:max_y, al ; set maxy value ; Find current cursor coordinates. mov al, active_page cbw add ax, ax xchg bx, ax mov ax, cursor_posn[bx] mov cs:cur_coords, ax if MONO mov ax, addr_6845 ; get the right 6845 mov cs:our_6845, ax endif ; Find video buffer segment address; adjust it ; so the offset is zero; return in AX. mov ax, 0B800H ; this gets corrected in xy_to_regs ; if we are not screen 0 if MONO cmp crt_mode, 7 jne not_mono_mode mov ah, 0b0h not_mono_mode: endif push cs pop ds assume ds:CODE mov es, ax call xy_to_regs ; Set DX, DI according to cur_coords. ; | If in graphics mode, clear old pseudocursor cmp gmode_flag, 0 jle d8_no_cp cmp no_c_flag, 0 ; cursor not previously drawn? mov no_c_flag, 0 ; (reset flag, will be drawn from now on) jnz d8_no_cp ; not drawn -- don't clear cmp gcursor, 0 ; don't clear if cursor is off, either jz d8_no_cp push cx push dx push di draw_gcursor pop di pop dx pop cx d8_no_cp: mov ah, cur_attrib mov ds, f_cptr_seg ; get segment of char ptr assume ds:nothing cld ; make sure we'll increment ; The Inner Loop: 12+4+4+11+14+2+19= 66 cycles/loop ; on 8088; at 4.77 MHz, that gives 16.1 microseconds/loop. ; At that speed, it takes 32 milliseconds to fill a screen. ; Get a character, put it on the screen, repeat 'til end of line ; or no more characters. jcxz f_loopdone ; if count = 0, we're already done. cmp cs:escvector, 0 ; If in middle of an escape sequence, jz f_tloop jmp f_in_escape ; jump to escape sequence handler. f_tloop:; | If in graphics mode, jump to alternate loop ; | What a massive kludge! A better approach would have been ; | to collect characters for a "write n chars" routine ; | which would handle both text and graphics modes. cmp cs:gmode_flag,0 jle f_t_cloop jmp f_g_cloop even ; tiny performance boost f_t_cloop: LODSB ; get char! (al = ds:[si++]) cmp al, 28 ; is it a control char? jb f_control ; maybe... f_t_nctl: STOSW ; Put Char! (es:[di++] = ax) dec dx ; count down to end of line loopnz f_t_cloop ; and go back for more. jz f_t_at_eol ; at end of line, maybe do a crlf. jmp short f_loopdone ; finished execution f_looploop: f_ansi_exit: ; in case we switched into loopnz f_tloop ; a graphics mode jnz f_loopdone f_t_at_eol: jmp f_at_eol f_loopdone: ;--------- All done with write request ----------- ; DI is cursor address, cursor position in cur_y, dl assume ds:ABS40 mov ax, ABS40 mov ds, ax ; Set cursor position in low memory. mov al,active_page cbw add ax,ax xchg bx,ax mov al, cs:max_x inc al sub al, dl mov ah, cs:cur_y mov cursor_posn[bx],ax cmp cs:gmode_flag,0 jg pseudocursor ; In graphics mode, there is ; a pseudo cursor to draw. ; Write directly to 6845 cursor address register. mov bx, di shr bx, 1 ; convert word index to byte index if MONO mov dx, cs:our_6845 ; either mono or color card else mov dx, port_6845 ; color card endif mov al,0eh ; more effective to write two bytes at a time mov ah,bh out dx,ax inc al mov ah,bl out dx,ax ; Return to DOS. xor ax, ax ; No error, not busy. ret pseudocursor: cmp cs:gcursor, 0 ; graphics cursor off? jz nopseudo mov cs:no_c_flag,0 ; there is a cursor now! draw_gcursor nopseudo: xor ax, ax ret ;---- handle control characters ---- ; Note: cur_x is not kept updated in memory, but can be ; computed from max_x and dx. ; Cur_y is kept updated in memory. f_escapex: ; far jump jmp f_escape f_control: cmp al, 27 ; Is it an escape? jz f_escapex cmp al, 13 ; carriage return? jz f_cr cmp al, 10 ; line feed? jz f_lf cmp al, 8 ; backspace? jz f_bs cmp al, 9 ; tab? jz f_tab cmp al, 7 ; bell jz f_bell jmp f_nctl ; then it is not a control char. f_bell: ;----- Handle bell ---------------------- ; Use BIOS to do the beep. DX is not changed, as bell is nonprinting. call beep or al, al ; clear z jmp f_looploop ; Let main loop decrement cx. f_bs: ;----- Handle backspace ----------------- ; Moves cursor back one space without erasing. No wraparound. cmp dl, cs:max_x ; wrap around to previous line? ja fbs_wrap ; yep; disallow it. dec di ; back up one char & attrib, dec di inc dx ; and note one more char left on line. fbs_wrap: jmp f_looploop f_cr: ;----- Handle carriage return ----------- ; di -= cur_x<<1; set di= address of start of line ; dx=max_x+1; set bx= chars left in line mov al, cs:max_x inc al sub al, dl ; Get cur_x into ax. mov ah, 0 sub di, ax sub di, ax mov dl, cs:max_x ; Full line ahead of us. inc dx mov ah, cs:cur_attrib ; restore current attribute or al, 1 ; clear z jmp f_looploop ; and let main loop decrement cx f_at_eol: ;----- Handle overrunning right end of screen ------- ; cx++; compensate for double loop ; if (!wrap_flag) { dx++; di-=2; } ; else do_crlf; inc cx test cs:wrap_flag, 1 jnz feol_wrap dec di dec di inc dx jmp f_looploop feol_wrap: ; dx=max_x+1; set bx= chars left in line ; di -= 2*(max_x+1); ; do_lf mov dl, cs:max_x inc dx sub di, dx sub di, dx ; fall thru to line feed routine f_lf: ;----- Handle line feed ----------------- ; if (cur_y >= max_y) scroll; scroll screen up if needed ; else { cur_y++; di += max_x<<1; else increment Y mov al, cs:max_y cmp cs:cur_y, al jb flf_noscroll call scroll_up ; preserves bx,cx,dx,si,di jmp short flf_done flf_noscroll: inc cs:cur_y mov al, cs:max_x mov ah, 0 inc ax add ax, ax add di, ax flf_done: mov ah, cs:cur_attrib ; restore current attribute or al, 1 ; clear z jmp f_looploop ; and let main loop decrement cx f_tab: ;----- Handle tab expansion ------------- ; Get cur_x into al. mov al, cs:max_x inc al sub al, dl ; Calculate number of spaces to output. push cx ; save cx mov ch, 0 mov cl, al ; get zero based x coordinate and cl, 7 neg cl add cl, 8 ; 0 -> 8, 1 -> 8, ... 7 -> 1 sub dx, cx ; update chars-to-eol, maybe set z pushf ; || save Z for main loop ; ah is still current attribute. Move CX spaces to the screen. mov al, ' ' cmp cs:gmode_flag,0 jg f_tab_putc REP STOSW popf ; || restore Z flag for main loop test pop cx ; restore cx jmp f_looploop ; Let main loop decrement cx. ;--------------- graphics mode support ----------------------- ;---- Alternate main loop for graphics mode ---- f_g_cloop: LODSB ; get char! (al = ds:[si++]) cmp al, 28 ; is it a control char? jb f_g_control ; maybe... f_g_nctl: call putchar dec dx ; count down to end of line loopnz f_g_cloop ; and go back for more. jz f_at_eol ; at end of line; maybe do a crlf. jmp f_loopdone f_g_control: jmp f_control ; Tabs in graphic mode f_tab_putc: ; graphics mode- call putc to put the char add dx, cx ; move back to start of tab f_tp_lp: call putchar dec dx ; go to next cursor position loop f_tp_lp popf ; Z set if wrapped around EOL pop cx jmp f_looploop ;---- Where to go when a character turns out not to be special f_nctl: f_not_ansi: cmp cs:gmode_flag,0 jg f_g_nctl jmp f_t_nctl ; text mode page ;---- putchar ------------------------------------------------ ; Writes char AL, attribute AH to screen at (max_x+1-dl), cur_y. ; On entry, registers set up as per xy_to_regs. ; Preserves all registers. putchar proc near push dx push cx push bx push ax ; 1. Set cursor position. mov al, cs:max_x inc al sub al, dl mov cs:cur_x, al mov dx, cs:cur_coords ; get X & Y into DX push ds mov ax, 40h mov ds, ax assume ds:ABS40 mov cursor_posn,dx pop ds assume ds:nothing xor bx, bx ; choose dpy page 0 mov ah, 2 ; chose "Set Cursor Position" call_video ; 2. Write char & attribute. IF quick_char pop ax push ax ; character and attribute call quick_graph ELSE mov cx, 1 pop ax ; get char in AL push ax mov bl, ah ; attribute in BL mov bh, 0 mov ah, 9 call_video ENDIF pop ax pop bx pop cx pop dx ret putchar endp page IF quick_char quick_graph proc near ; this code has been reworked for much greater speed. ; ah= mode, al= char, ax,bx,cx,dx destroyed gmode_test yesQuick mov bl,ah xor bh,bh mov cx, 1 mov ah, 9 call_video ; do it the old way ret yesQuick: push ds mov bx, 40h mov ds, bx assume ds:ABS40 ; address abs segment push es push bp push si push di ; save some registers push ax ; save char and mode mov ax, crt_cols mov cx, ega_points ; pixel rows in character mov bp, ax ; save number of columns=#bytes mul byte ptr (cursor_posn+1) mul cx ; (ignore upper product in DX) add al, byte ptr (cursor_posn) ; y*#cols*#rows + x adc ah, 0 ; take care of carry mov si, ax ; save address in si xor ax, ax mov es, ax ; absolute zero les di, es: dword ptr (43h * 4) ; contents of vector 43h pop ax push ax ; get char and mode mul cl ; offset to character in table add di,ax ; di has character bit pattern start mov ax, 0a000h ; address of display segment mov ds, ax assume ds:nothing ; to recap: cx=#rows, bp=#columns, ds:si=display address, es:di=character addr mov dx, 3ceh mov ax, 0a05h out dx,ax ; set write mode 2, read mode 1 mov ax, 7 ; set color dontcare register to zero out dx,ax pop bx ; character mode in bh IF gbackground mov bl,bh ; extract background color IF cheap_pc shr bl,1 shr bl,1 shr bl,1 shr bl,1 ELSE shr bl,4 ENDIF or bh, bh jns overMode mov ax, 1803h ; exor mode out dx,ax and bx, 0f07h ; xor=blink bit ELSE or bh, bh jns overMode mov ax, 1803h ; exor mode out dx,ax and bx, 7f00h ; mask off xor bit ENDIF mov al, 8 ; bit mask register out dx, al inc dx chLoop: mov al, es:[di] ; get pixel pattern out dx, al and [si],bh ; update foreground not al out dx, al ; and background and [si],bl inc di add si, bp ; go to next character byte and line loop chLoop joinret: dec dx mov ax, 0ff08h ; bit mask out dx, ax mov ax, 5 ; mode register out dx, ax mov al, 3 ; (ah is zero) out dx, ax mov ax, 0f07h out dx, ax pop di pop si pop bp pop es pop ds ret overMode: IF gbackground and bx, 0f07h ; xor=blink bit ELSE and bx, 7f00h ; mask off xor bit ENDIF mov al, 8 ; bit mask register out dx, al inc dx ; we need to load the internal buffer with a solid ; background. By writing a solid background and then ; reading it back, we can do the job. mov al, 0ffh ; force set background out dx, al mov [si], bl mov al, [si] ; read reset pattern chLoop2: mov al, es:[di] ; get pixel pattern out dx, al mov [si],bh ; update foreground inc di add si, bp ; go to next character byte and line loop chLoop2 jmp joinret quick_graph endp ENDIF ;--------------- end of graphics mode support -------------------- dosfn8 endp page ;--- get_blank_attrib ------------------------------------------------ ; Determine new attribute and character for a new blank region. ; Use current attribute, just disallow blink and underline. ; Returns result in AH, preserves all other registers. get_blank_attrib proc near IF gbackground cmp cs:gmode_flag,0 jle get_attrib ; if alpha mode gmode_test get_attribg ; or good graphic mode, get attrib xor ah,ah ret get_attribg: mov ah, cs:cur_attrib ; must do different technique IF cheap_pc shr ah,1 ; color must be shifted into position shr ah,1 shr ah,1 shr ah,1 ELSE shr ah,4 ENDIF and ah,07 ret get_attrib: mov ah, cs:cur_attrib ; the attribute and ah, 7fh ; but disallowing blink IF MONO cmp cs:video_mode, 7 ; monochrome? jne gb_aok cmp ah, 7 ; underline? jnz gb_aok mov ah, 7 gb_aok: ENDIF ret ELSE mov ah, 0 ; 0 is background if graphics mode cmp cs:gmode_flag,0 jg gb_aok mov ah, cs:cur_attrib and ah, 7fh ; disallow blink IF MONO cmp cs:video_mode, 7 ; monochrome? jne gb_aok cmp ah, 7 ; underline? jnz gb_aok mov ah, 7 ENDIF gb_aok: ret ENDIF get_blank_attrib endp page ;---- scroll_up --------------------------------------------------- ; Scroll screen up- preserves ax, bx, cx, dx, si, di, ds, es. ; Moves screen up 1 line, fills the last line with blanks. ; Attribute of blanks is the current attribute sans blink and underline. scroll_up proc near push_all cmp cs:gmode_flag,0 jz scroll_char if MONO jl mono_scroll endif jmp scroll_graphic scroll_char: push es push ds ; save all! mov ax, ABS40 ; address low mem via ds mov ds, ax mov ax, 0b800h ; address display via es mov es, ax assume ds:ABS40 cmp cs:fmode,0 ; see if in fast mode jz slow_scroll_up xor ax,ax ; calc addresses mov al, cs:max_x inc ax mov cx, ax ; save (word) count for fill mov bx, ax ; and save byte count shl bx, 1 ; byte count mov cs:temp_val, bx mul cs:max_y ; address offset of last line (words) shl ax, 1 ; address offset in bytes mov di, ax mov ax, crt_start ; start of display add ax, bx ; add line size in bytes add di, ax ; di is now address of new last line cmp di, 7fffh - 264 ; is there room here? ja no_room_here mov crt_start, ax shr ax, 1 ; make into word offset mov bx, ax ; and put into 6845 mov dx, port_6845 mov al, 0ch out dx, ax inc al mov ah, bl out dx, ax mov ah, cs:cur_attrib and ah, 7fh ; disallow blink mov al, 20h ; blank rep stosw ; clear line assume ds:nothing pop ds pop es pop_all add di, cs:temp_val ret no_room_here: pop ds ; restore registers pop es pop_all call move_back ; go to buffer start sub di, cs:temp_val jmp scroll_up ; try again if MONO mono_scroll: push es push ds ; save all! mov ax, ABS40 ; address low mem via ds mov ds, ax mov ax, 0b000h ; address display via es mov es, ax endif slow_scroll_up: assume ds:ABS40 mov di, crt_start ; offset of display (because of ; different page) mov ds, ax ; ds is now display assume ds:nothing xor ax,ax ; calc addresses mov al, cs:max_x inc ax mov bx, ax ; save (word) count shl ax, 1 ; byte count mov si, ax ; start address is second line add si, di ; adjust start address by any offset mov ax, bx mul cs:max_y ; number of words to move mov cx, ax rep movsw ; move them! mov cx, bx ; words to clear mov ah, cs:cur_attrib and ah, 7fh ; disallow blink mov al, 20h ; blank rep stosw ; clear line pop ds pop es pop_all ret scroll_graphic: gmode_test scrOurself mov bh, 0 mov al, 1 ; AL is number of lines to scroll. mov ah, 6 ; BIOS: scroll up xor cx, cx mov dl, cs:max_x ; lower-rite-x mov dh, cs:max_y ; lower-rite-y (zero based) call_video ; call BIOS to scroll a rectangle. scrret: pop_all ret scrOurself: ; try scrolling screen ourself! push es push ds mov dx, 3ceh ; set write mode 1 mov ax, 105h out dx, ax mov ax, 40h ; address abs40 segment mov ds, ax assume ds:ABS40 mov ax, crt_cols ; calculate length of line in bytes mul byte ptr ega_points mov si, ax ; source of move xor dx,dx mov dl, ega_rows mul dx ; number of bytes to move mov cx, ax mov ax, si ; save bytes in line for later mov bx, 0a000h ; address display mov ds, bx mov es, bx xor di, di ; destination of move rep movsb ; scroll mov cx, ax ; bytes in line = bytes to clear mov dx, 3ceh mov ax, 05h ; return to write mode 0 out dx, ax IF gbackground mov ah, cs:cur_attrib IF cheap_pc shr ah,1 shr ah,1 shr ah,1 shr ah,1 ELSE shr ah,4 ENDIF and ah,07 ; background color mov al,0 out dx,ax ; set color to write mov ax,0f01h ; set mask out dx,ax rep stosb ; clear the line mov ax,0001 ; reset mask out dx,ax ELSE xor ax, ax rep stosb ; clear the line ENDIF pop ds ; restore registers and return pop es jmp scrret scroll_up endp page ;-----move_back -------------------------------------------- ; This routine moves the display to offset zero. ; alters: ; cs:temp_val = original crt_start value ; crt_start = 0 ; controller reset properly move_back proc near push ds push es push_all mov ax, ABS40 mov ds, ax assume ds:ABS40 mov al, ega_rows inc al mul byte ptr crt_cols ; words to move mov cx, ax mov si, crt_start mov cs:temp_val, si ; save this value xor di, di mov crt_start, di mov bx, cursor_posn ; y in bh, x in bl mov al, byte ptr crt_cols mul bh add al, bl adc ah, 0 xchg bx, ax ; save cursor position in bx mov ax, 0B800h mov es, ax mov ds, ax mov dx, cx add dx, cx ; see if overlapping cmp dx, si ja slow_move join_move: cld rep movsw ; move data mov dx, port_6845 mov al, 0ch ; reset offset xor ah,ah out dx, ax inc al out dx, ax inc al mov ah, bh out dx, ax inc al mov ah, bl out dx, ax ; sti assume ds:nothing pop_all pop es pop ds ret slow_move: ; we gotta move to another spot first push cx ; save length dec dx ; length-2 dec dx add si, dx ; point to end mov di, 7FFEh ; safe location -- as safe as we can get std rep movsw ; move from far end in case of overlap ; (may happen on large displays) mov dx, port_6845 mov si, di ; source becomes destination inc si ; take care of last decrement inc si mov cx, si shr cx, 1 ; word offset to start of new area mov al, 0Ch ; display at this new location mov ah, ch out dx, ax inc al mov ah, cl out dx, ax pop cx ; reset all registers xor di, di ; destination is zero jmp join_move ; NOW move to destination move_back endp page if key_redef ;---- lookup ----------------------------------------------- ; Called by getchar, peekchar, and key to see if a given key has ; been redefined. ; Sets AH to zero if AL is not zero (i.e. if AX is not a function key). ; Returns with Z cleared if no redefinition; otherwise, ; Z is set, SI points to redefinition string, CX is its length. ; Preseves AL, all but CX and SI. ; Redefinition table organization: ; Strings are stored in reversed order, first char last. ; The word following the string is the character to be replaced; ; the next word is the length of the string sans header. ; param_end points to the last byte used by the parameter buffer; ; redef_end points to the last word used by the redef table. lookup proc near mov si, redef_end ; Start at end of table, move down. or al, al jz lu_lp mov ah, 0 ; clear extraneous scan code lu_lp: cmp si, param_end jbe lu_notfound ; If below redef table, exit. mov cx, [si] cmp ax, [si-2] ; are you my mommy? jz lu_gotit sub si, 4 sub si, cx ; point to next header jmp lu_lp lu_notfound: or si, si ; clear Z jmp short lu_exit lu_gotit: sub si, 2 sub si, cx ; point to lowest char in memory cmp al, al ; set Z lu_exit: ret lookup endp endif page ;---- searchbuf -------------------------------------------- ; Called by getchar and peekchar to see if any characters are ; waiting to be gotten from sources other than BIOS. ; Returns with Z set if no chars found, BX=keybuf & SI=keybuf.len otherwise. searchbuf proc near ; Search the stuffahead buffers. if key_redef mov cx, 4 ; number of buffers to check for chars else mov cx, 3 endif mov bx, offset fnkey - 4 sbloop: add bx, 4 ; point to next buffer record mov si, [bx].len or si, si ; empty? loopz sbloop ; if so, loop. ret searchbuf endp page ;---- getchar ----------------------------------------------- ; Returns AL = next char. ; Trashes AX, BX, CX, BP, SI. getchar proc near gc_searchbuf: ; See if any chars are waiting in stuffahead buffers. call searchbuf jz gc_trykbd ; No chars? Try the keyboard. ; A nonempty buffer was found. dec [bx].len dec si mov bp, [bx].adr ; get pointer to string mov al, byte ptr ds:[bp][si]; get the char ; Recognize function key sequences, move them to highest priority ; queue. sub si, 1 ; set carry if si=0 jc gc_nofnkey ; no chars left -> nothing to protect. cmp bx, offset fnkey jz gc_nofnkey ; already highest priority -> done. or al, al jnz gc_nofnkey ; nonzero first byte -> not fnkey. ; Found a function key; move it to highest priority queue. dec [bx].len mov ah, byte ptr ds:[bp][si]; gec [bx].len mov ah, byte ptr ds:[bp][si]; get the second byte of fn key code gc_fnkey: mov fnkey.len, 1 mov fnkeybuf, ah ; save it. gc_nofnkey: ; Valid char in AL. Return with it. jmp short gcdone gc_trykbd: ; Actually get a character from the keyboard. mov ah, 0 int 16h ; BIOS returns with char in AX ; If it's Ctrl-break, it has already been taken care of. or ax, ax jz gc_trykbd if key_redef ; Look in the reassignment table to see if it needs translation. call lookup ; Z=found; CX=length; SI=ptr jnz gc_noredef ; Okay; set up the reassignment, and run thru the translation code. mov xlatseq.len, cx mov xlatseq.adr, si jmp gc_searchbuf endif gc_noredef: ; Is it a function key? cmp al, 0 jz gc_fnkey ; yep- special treatment. gcdone: ret ; with character in AL. getchar endp page ;---- peekchar ----------------------------------------------- ; Returns Z if no character ready, AL=char otherwise. ; Trashes AX, BX, CX, BP, SI. peekchar proc near call searchbuf jz pc_trykbd ; No chars? Try the keyboard. ; A nonempty buffer was found. dec si mov bp, [bx].adr ; get pointer to string mov al, byte ptr ds:[bp][si]; get the char ; Valid char from buffer in AL. Return with it. jmp short pcdone pc_trykbd: ; Actually peek at the keyboard. mov ah, 1 int 16h ; BIOS returns with char in AX jz pcexit ; If it's control-break, it's already been taken care of. or ax, ax jnz pc_notbrk mov ah, 0 int 16h ; so get rid of it! jmp short pc_trykbd pc_notbrk: if key_redef ; Look in the reassignment table to see if it needs translation. call lookup ; Z=found; CX=length; SI=ptr jnz pcdone ; Nope; just return the char. ; Okay; get the first code to be returned. add si, cx mov al, [si-1] endif pcdone: or ah, 1 ; NZ; char ready! pcexit: ret ; with character in AL, Z true if no char waiting. peekchar endp page ;----- set_gmode ------------------------------------------------ ; Set gmode_flag based on mode byte in register al set_gmode proc near if MONO cmp al, 7 ; mono? je gmode_mono gmode_code ret gmode_mono: neg al mov cs:gmode_flag, al ; set gmode_flag to neg value ret else gmode_code ; a macro in nnansi_d.asm ret endif set_gmode endp ;---- beep ------------------------------------------------------ ; Beep speaker; period given by beep_div, duration by beep_len. ; Preserves all registers. beep_div equ 1300 ; fairly close to IBM beep beep_len equ 3 ; 3/18 sec- shorter than IBM beep proc near push_all mov al, 10110110b ; select 8253 mov dx, 43h ; control port address out dx, al dec dx ; timer 2 address mov ax, beep_div jmp $+2 out dx, al ; low byte of divisor xchg ah, al jmp $+2 out dx, al ; high byte of divisor mov dx, 61h jmp $+2 in al, dx ; get current value of control bits push ax or al, 3 jmp $+2 out dx, al ; turn speaker on ; Wait for desired duration by monitoring time-of-day 18 Hz clock push es mov ax, ABS40 mov es, ax assume es:ABS40 mov bx, timer_low mov cx, -1 beeplp: mov ax, timer_low sub ax, bx cmp ax, beep_len jg beepover loop beeplp beepover: pop es assume es:CODE ; Turn off speaker pop ax and al, not 3 ; turn speaker off out dx, al pop_all ret beep endp page ; A state machine implementation of the mechanics of ANSI terminal control ; string parsing. ; ; Entered with a jump to f_escape when driver finds an escape, or ; to f_in_escape when the last string written to this device ended in the ; middle of an escape sequence. ; ; Exits by jumping to f_ANSI_exit when an escape sequence ends, or ; to f_not_ANSI when a bad escape sequence is found, or (after saving state) ; to f_loopdone when the write ends in the middle of an escape sequence. ; ; Parameters are stored as bytes in param_buffer. If a parameter is ; omitted, it is stored as zero. Each character in a keyboard reassignment ; command counts as one parameter. ; ; When a complete escape sequence has been parsed, the address of the ; ANSI routine to call is found in ansi_fn_table. ; ; Register usage during parsing: ; DS:SI points to the incoming string. ; CX holds the length remaining in the incoming string. ; ES:DI points to the current location on the memory-mapped screen. ; DX is number of characters remaining on the current screen line. ; BX points to the current paramter byte being assembled from the incoming ; string. (Stored in cur_parm_ptr between device driver calls, if needed.) ; ; The registers are set as follows before calling the ANSI subroutine: ; AX = max(1, value of first parameter) ; CX = number of paramters ; SI = offset of second parameter from CS ; DS = CS ; ES:DI points to the current location on the memory-mapped screen. ; DX is number of characters remaining on the current screen line. ; The subroutine is free to trash AX, BX, CX, SI, and DS. ; It must preserve ES, and can alter DX and DI if it wants to move the ; cursor. ; ;------------------------------------------------------------------------ assume cs:code, ds:code ;----- next_is ------------------------------------------------------- ; Next_is is used to advance to the next state. If there are characters ; left in the input string, we jump immediately to the new state; ; otherwise, we shut down the recognizer, and wait for the next call ; to the device driver. next_is macro statename loop statename mov ax, offset statename jmp sleep endm ;----- sleep -------------------------------------------------------- ; Remember bx and next state, then jump to device driver exit routine. ; Device driver will re-enter at f_in_escape upon next invocation ; because escvector is nonzero; parsing will then be resumed. sleep: mov cs:cur_parm_ptr, bx mov cs:escvector, ax jmp f_loopdone ;----- f_in_escape --------------------------------------------------- ; Main loop noticed that escvector was not zero. ; Recall value of BX saved when sleep was jumped to, and jump into parser. f_in_escape: mov bx, cs:cur_parm_ptr jmp word ptr cs:escvector fbr_syntax_error_gate: ; jumped to from inside f_bracket jmp syntax_error ;----- f_escape ------------------------------------------------------ ; We found an escape. Next character should be a left bracket. f_escape: next_is f_bracket ;----- f_bracket ----------------------------------------------------- ; Last char was an escape. This one should be a [; if not, print it. ; Next char should begin a parameter string. f_bracket: lodsb cmp al, '[' jnz fbr_syntax_error_gate ; Set up for getting a parameter string. mov bx, cs:param_buffer mov byte ptr cs:[bx], 0 mov cs:in_num, 0 mov cs:eat_key, 0 ; no eaten key next_is f_get_args ;----- f_get_args --------------------------------------------------- ; Last char was a [. If the current char is a '=' or a '?', eat it. ; In any case, proceed to f_get_param. ; This is only here to strip off the strange chars that follow [ in ; the SET/RESET MODE escape sequence. f_get_args: lodsb cmp al, '=' jz fga_ignore cmp al, '?' jz fga_ignore dec si ; let f_get_param fetch al again jmp short f_get_param fga_ignore: mov cs:eat_key, al ; save eaten key (BUG FIX!) next_is f_get_param ;----- f_get_param --------------------------------------------------- ; Last char was one of the four characters "]?=;". ; We are getting the first digit of a parameter, a quoted string, ; a ;, or a command. f_get_param: lodsb cmp al, '0' jb fgp_may_quote cmp al, '9' ja fgp_may_quote ; It's the first digit. Initialize current parameter with it. sub al, '0' mov byte ptr cs:[bx], al mov cs:in_num, 1 ; set flag for sensing at cmd exec next_is f_in_param fgp_may_quote: cmp al, '"' jz fgp_isquote cmp al, "'" jnz fgp_semi_or_cmd ; jump to code shared with f_in_param fgp_isquote: mov cs:string_term, al ; save it for end of string next_is f_get_string ; and read string into param_buffer ;----- f_get_string ------------------------------------- ; Last character was a quote or a string element. ; Get characters until ending quote found. f_get_string: lodsb cmp al, cs:string_term jz fgs_init_next_param mov byte ptr cs:[bx], al cmp bx, cs:param_end adc bx, 0 ; if bx99 case -- TAA mod jb goodbyteout push ax mov al, '1' ; assume value <200! stosb pop ax sub ax, 100 goodbyteout: aam add ax, 3030h xchg ah, al stosb xchg ah, al stosb ret byteout endp ;----- ansi_fn_table ----------------------------------- ; Table of offsets of terminal control subroutines in order of ; the character that invokes them, @..Z, a..z. Exactly 53 entries. ; All the subroutines are defined below in this module. ansi_fn_table label word dw ic, cup, cdn, cfw, cbk ; @, A, B, C, D dw nul, nul, nul, hvp, nul ; E, F, G, H, I dw eid, eil, il, d_l, nul ; J, K, L, M, N dw nul, dc, nul, nul, nul ; O, P, Q, R, S dw nul, nul, nul, nul, nul ; T, U, V, W, X dw nul, nul ; Y, Z dw nul, nul, nul, nul, nul ; a, b, c, d, e dw hvp, nul, sm, nul, nul ; f, g, h, i, j dw nul, rm, sgr, dsr, nul ; k, l, m, n, o if key_redef dw key, nul, nul, scp, nul ; p, q, r, s, t else dw nul, nul, nul, scp, nul ; p, q, r, s, t endif dw rcp, nul, nul, nul, nul ; u, v, w, x, y dw nul ; z ansi_functions proc near ; set return type to NEAR ;----- Cursor Motion ----------------------------------------------- ;-- cursor to y,x hvp: or al, al ; First parameter is desired Y coordinate. jz hvp_yok dec ax ; Convert to zero-based coordinates. hvp_yok:mov cur_y, al ; Get second parameter, if it is there, and set X with it. xor ax, ax cmp cx, 2 ; was there a second parameter? jb hvp_xok lodsb ; yes. or al, al jz hvp_xok dec ax ; convert to zero-based coordinates. hvp_xok:mov cur_x, al ; Clip to maximum coordinates. hvp_set: mov ax, cur_coords ; al = x, ah = y cmp al, max_x jbe hvp_sxok mov al, max_x mov cur_x, al hvp_sxok: cmp ah, max_y jbe hvp_syok mov al, max_y mov cur_y, al hvp_syok: ; Set values of DX and DI accordingly. call xy_to_regs ret ;-- cursor forward -- cfw: add cur_x, al jmp hvp_set ;-- cursor back ----- cbk: sub cur_x, al jae hvp_set mov cur_x, 0 jmp hvp_set ;-- cursor down ----- cdn: add cur_y, al jmp hvp_set ;-- cursor up ------- cup: sub cur_y, al jae hvp_set mov cur_y, 0 jmp hvp_set ;-- save cursor position -------------------------------------- scp: mov ax, cur_coords mov saved_coords, ax ret ;-- restore cursor position ----------------------------------- rcp: mov ax, saved_coords mov cur_coords, ax jmp hvp_set ; Clip in case we have switched video modes. ;-- set graphics rendition ------------------------------------ ; Modifies the color in which new characters are written. sgr: dec si ; get back pointer to first parameter or cx, cx ; Did he give any parameters? jnz sgr_loop mov byte ptr [si], 0 ; no parameters, so fake inc cx ; one with the default value. ; For each parameter sgr_loop: lodsb ; al = next parameter ; Search color table push cx cmp al, 0 ; not reset? jnz sgr_continue mov color_flag, 7 ; finish up normally sgr_continue: mov cx, colors mov bx, offset color_table-3 sgr_search: add bx, 3 cmp al, byte ptr [bx] loopnz sgr_search ; until match found or done jnz sgr_loopx ; If parameter named a known color, set the current ; color variable. cmp al, 30 jge sgr_color mov ax, [bx+1] and atr_flag, al or atr_flag, ah jmp short sgr_loopx sgr_color: mov ax, [bx+1] and color_flag, al or color_flag, ah sgr_loopx: pop cx loop sgr_loop ; until no more parameters. mov al, color_flag mov ah, atr_flag test ah, 4 ; invisible jz sgr_notinv mov ah, al ; then fade to background if cheap_pc ror al,1 ; swap nibbles ror al,1 ror al,1 ror al,1 else ror al,4 endif and ax, 7007h or al, ah jmp short sgr_done sgr_notinv: test ah, 2 ; reverse? jz sgr_notrev if cheap_pc ror al,1 ; swap nibbles ror al,1 ror al,1 ror al,1 else ror al,4 endif sgr_notrev: test ah, 1 ; underline? jz sgr_notund cmp gmode_flag, 0 ; monochrome display? jl sgr_mono_und and al, 0f0h or al, 1 ; blue chars on color display jmp short sgr_notund sgr_mono_und: cmp al, 7 ; can only underline if white on black jne sgr_notund mov al, 1 sgr_notund: and ah, 88h ; get bold and blink attributes or al, ah ; and merge them in sgr_done: mov cur_attrib, al ; save the new attribute ; ret ;----- nul --------------------------------------------- ; No-action ansi sequence; called when unknown command given. nul: ret ;-- erase in line ---------------------------------------- ; Uses BIOS to scroll away a one-line rectangle eil: push dx mov cx, cur_coords mov dh, ch jmp short scrollem ;-- erase in display ------------------------------------- ; Uses BIOS to scroll away all of display eid: jz eid_toend ; first parms=0 -- erase to end or cx,cx ; no args? jz eid_toend ; then erase to end cmp al, 1 ; erase to beginning? jz eid_tostart cmp al, 2 jnz eid_ignore ; param must be two mov cur_coords, 0 call xy_to_regs cmp cs:fmode,0 ; see if running in fast mode jz eid_slow xor di, di ; offset will be zero after clearing eid_slow: push dx xor cx, cx mov dh, max_y scrollem: call get_blank_attrib mov bh, ah mov dl, max_x eid_process: mov ax, 600h int 10h ; use int 10 so screen repositions itself ; call_video rather than "call_video" eid_done: pop dx eid_ignore: ret eid_toend: ; erase following lines then go back and erase in line push dx call get_blank_attrib mov bh,ah mov ax, 600h mov dh, max_y mov dl, max_x mov ch, cur_y inc ch cmp ch, dh ; don't erase if no following ja eid_nopost xor cl,cl call_video eid_nopost: mov cx, cur_coords mov dh, ch jmp short scrollem eid_tostart: ; erase preceeding lines then go back and erase in line push dx call get_blank_attrib mov bh,ah mov dh, cur_y dec dh js eid_nopre ; don't erase if no preceeding mov ax,600h xor cx,cx mov dl, max_x call_video eid_nopre: mov dx, cur_coords dec dl js eid_done mov ch, dh xor cl, cl jmp eid_process ;-- device status report -------------------------------- ; Stuffs an escape, a left bracket, current Y, semicolon, current X, ; a capital R, and a carriage return into input stream. ; The coordinates are 1 to 3 decimal digits each. dsr: push di push dx push es mov ax, cs mov es, ax std ; Store string in reversed order for fun mov di, offset cpr_esc - 2 mov al, cur_y inc al ; convert to one-based coords call byteout ; row mov al, ';' ; ; stosb mov al, cur_x inc al ; convert to one-based coords call byteout ; column mov al, 'R' ; R ANSI function 'Cursor Position Report' stosb mov al, 13 mov word ptr cprseq.adr, di ; save pointer to last char in string stosb ; send a carriage return, too mov ax, offset cpr_esc sub ax, di ; ax is # of characters in string mov word ptr cprseq.len, ax ; pass info to the getchar routine cld pop es pop dx pop di ret if key_redef ;-- keyboard reassignment ------------------------------- ; Key reassignment buffer is between param_end and redef_end+2, exclusive. ; When it shrinks or grows, param_end is moved. ; Format of an entry is as follows: ; highest address -> length:word (may be 0) ; key to replace:word (either hi or low byte is zero) ; . ; . new key value, "length" bytes long ; . ; lowest address -> next entry, or free space. ; If no arguments are given, keyboard is reset to default condition. ; Otherwise, first parameter (or first two, if first is zero) defines ; the key whose value is to be changed, and the following parameters ; define the key's new, possibly zero-length, value. key: ; Is this a reset? or cx, cx jz key_init ; Get the first (or first two) parameters cld dec si ; point to first param dec cx ; Assume it's a fn key, get two params dec cx lodsw or al, al ; Is it a function key? jz key_fnkey ; It's not a function key- put second param back inc cx dec si key_fnkey: ; Key to redefine now in AX. If it's already redefined, ; lookup will set Z, point SI to redef string, set CX to its length. push di push es push cx push si std ; moving up, must move from top down push ds pop es ; string move must have ES=DS call lookup ; rets Z if redefined... jnz key_newkey ; It's already defined. Erase its old definition- i.e., move ; region param_end+1..SI-1 upwards CX+4 bytes, add CX+4 to param_end. add cx, 4 mov bp, param_end ; save old value in bp... add param_end, cx dec si ; start at (SI-1) mov di, si add di, cx ; move to (start + CX+4) mov cx, si sub cx, bp ; length of region old_param_end+1..start rep movsb key_newkey: ; Key not redefined. See if there's enough room to redefine it. pop si ; get back pointer to redef string pop cx ; get back number of bytes in redef string mov di, param_end ; hi byte of new redef record, hi byte of len sub di, 4 ; hi byte of new data field mov bx, di sub bx, cx ; hi byte of remaining buffer space sub bx, 16 ; better be at least 16 bytes room cmp bx, param_buffer jb key_popem ; nope- forget it. ; Nothing in the way now! mov [di+3], cx ; save length field mov [di+1], ax ; save name field jcxz key_nullstring key_saveloop: ; save data field movsb add si, 2 ; input string ascending, output descending loop key_saveloop key_nullstring: mov param_end, di ; save adr of new hi byte of free area key_popem: pop es pop di key_exit: cld ret key_init: ; Build the default redefinition table: ; control-printscreen -> control-P push es push ds pop es std mov di, redef_end mov ax, 1 stosw mov ax, 7200h ; control-printscreen stosw mov al, 16 ; control P stosb mov param_end, di ; save new bottom of redef table pop es jmp key_exit endif ;---- Delete/Insert Lines ------------------------------- ; AL is number of lines to delete/insert. ; Preserves DX, DI; does not move cursor. d_l: ; Delete lines. mov ah, 6 ; BIOS: scroll up jmp short il_open il: ; Insert lines. mov ah, 7 ; BIOS: scroll down il_open: ; Whether inserting or deleting, limit him to (max_y - cur_y) lines; ; if above that, we're just clearing; set AL=0 so BIOS doesn't burp. mov bh, max_y sub bh, cur_y cmp al, bh jbe il_ok ; DRK 9/4... mov al, 0 ; he tried to move too far il_ok: push ax call get_blank_attrib mov bh, ah ; color to use on new blank areas pop ax ; AL is number of lines to scroll. mov cl, 0 ; upper-left-x of data to scroll mov ch, cur_y ; upper-left-y of data to scroll push dx mov dl, max_x ; lower-rite-x mov dh, max_y ; lower-rite-y (zero based) call_video ; call BIOS to scroll a rectangle. pop dx ret ; done. ;-- Insert / Delete Characters ---------------------------- ; AL is number of characters to insert or delete. ; Preserves DX, DI; does not move cursor. ic: mov ch, 1 ; 1 => swap dest & source below jmp short dc_ch dc: mov ch, 0 dc_ch: cmp cs:gmode_flag,0 jg dc_ret ; | if in graphics mode, ignore. ; AL = number of chars to ins or del (guarenteed nonzero). ; Limit him to # of chars left on line. cmp al, dl jbe dc_cok mov al, dl dc_cok: push di ; DI is current address of cursor xchg ax, cx ; CX gets # of chars to ins/del mov bp, cx ; BP gets # of columns to clear. ; Set up source = destination + cx*2, count = dx - cx mov ch, 0 ; make it a word mov si, di add si, cx add si, cx neg cl add cl, dl mov ch, 0 ; CX = # of words to transfer cld ; REP increments si & di ; If this is an insert, then flip transfer around in both ways. test ah, 1 jz dc_noswap xchg di, si ; source <-> dest std ; up <-> down mov ax, cx ; make move over same range dec ax add ax, ax ; AX=dist from 1st to last byte. add di, ax ; Start transfer at high end of block add si, ax ; instead of low end. dc_noswap: ; Move those characters. push es pop ds rep movsw mov cx, bp ; Figure out what color to make the new blanks. call get_blank_attrib mov al, ' ' ; Blank out vacated region. rep stosw ; All done. cld ; restore normal REP state and pop di ; cursor address. dc_ret: ret ;---- set / reset mode --------------------------------------- ; Sets graphics/text mode; also sets/resets "no wrap at eol" mode. ; also set/reset graphic cursor mode rm: mov cl, 0 ; reset jmp short sm_rs sm: mov cl, 0ffh ; set sm_rs: ; Is it "wrap at eol" ? cmp al, 7 jnz sm_notwrap mov wrap_flag, cl ; true = wrap at EOL ret sm_notwrap: ; We will make this smarter by requiring the correct lead character, ; except for wrap at eol, which has been badly documented in the MSDOS ; manuals. cmp eat_key, '=' ; set mode -- display mode jz sm_notbios cmp eat_key, '?' ; set mode of togglable attribute? jnz sm_done ; Is it set/reset graphic cursor mode? cmp al,99 jnz sm_notgcursor mov gcursor, cl ret sm_notgcursor: ; Is it set/reset fast mode? cmp al,98 jnz sm_notspeedy mov fmode, cl cmp cl,0 ; mode now fast? jnz sm_done call move_back sm_done: ret sm_notspeedy: ; Is it set/reset bios write? cmp al,97 jnz sm_done if bios_write_tty mov bmode, cl endif ret sm_notbios: ; we can now do 43 lines on a VGA display, if desired, as well as 50. ; of course an EGA display will only do 43. IF MONO cmp video_mode, 7 ; mono mode? je sm_done ENDIF IF VGA cmp al, 43 je is43or50 cmp al, 50 jne sm_video ; set to whatever it happens to be is43or50: dec al cmp al, max_y je sm_done ; no change in # lines cmp video_mode, 3 ; display mode > 3? ja sm_done cmp al, 49 ; 50 line mode? mov ax, 1201H ; set 350 lines on EGA jne sm_is43 mov ax, 1202h ; select 400 scan lines sm_is43: mov bl,30h ; this call ignored on EGA call_video ELSE cmp al, 43 jne sm_video ; set to whatever it happens to be cmp max_y, 42 ; no change in # lines? je sm_done cmp video_mode, 3 ; must be currently mode <= 3 ja sm_done ENDIF mov ah,0 mov al, video_mode ; select the video mode. call_video mov ax,1112h ; Load 8x8 font mov bl,0 ; (instead of 8x14 or 8x16) call_video mov ax, 1200h ; Load new printscreen mov bl, 20h call_video IF EGA ; set the EGA cursor mov dx, port_6845 ; '6845' command reg mov ax, 070ah ; start on line 7 out dx, ax mov al, 0bh ; end on line 7 out dx, ax ENDIF jmp short sm_home sm_video: ; It must be a video mode. Call BIOS. ; Save graphics mode flag IF VGA cmp al, 3 ; On VGA, modes 0-3, set 400 scan lines ja no_scan_change push ax mov ax, 1202h ; select 400 scan lines mov bl,30h ; this call ignored on EGA call_video pop ax no_scan_change: ENDIF IF VESA cmp al, 80h ; not a vesa mode? jb normal_mode xor bx, bx mov bl, al add bx, 8080h ; set msb and put mode in range mov ax, 4f02h int 10h ; can't use call_video, because often ; VESA compatibility driver is used and ; loaded after us. jmp short sm_home normal_mode: ENDIF mov ah, 0 ; "set video mode" or al, 80h ; but don't erase screen, since colors wrong. call_video sm_home: ; Read the BIOS buffer address/cursor position variables. mov ax, ABS40 push ds mov ds, ax assume ds:ABS40 ; Find current video mode and screen size. mov ax,word ptr crt_mode ; al = crt mode; ah = # of columns mov cl, ega_rows ; cl = max row pop ds assume ds:code call set_gmode ; set gmode based on video mode mov video_mode, al dec ah ; ah = max column mov max_x, ah mov max_y, cl ; Since cursor may end up in illegal position, it's best to ; just go home after switching video modes. mov cur_coords, 0 call xy_to_regs jmp eid_slow ; then clear the screen ansi_functions endp ; end dummy procedure block ;-------- Color table ----------------------------------------- ; Used in "set graphics rendition" colors equ 28 ; number of colors in table color_table: ; The first set attributes rather than colors db 0, 000h,00h ; all attribs off; normal. db 1, 0ffh,08h ; bold db 2, 0f7h,00h ; dim (not bold) db 4, 0ffh,01h ; underline db 5, 0ffh,80h ; blink db 7, 0ffh,02h ; reverse db 8, 0ffh,04h ; invisible db 22,0f7h,00h ; un-bold, un-dim db 24,0feh,00h ; un-underline db 25,07fh,00h ; un-blink db 27,0fdh,00h ; unreverse db 28,0fbh,00h ; un-invisible ; These set the colors db 30,0f8h,00h ; black foreground db 31,0f8h,04h ; red db 32,0f8h,02h ; green db 33,0f8h,06h ; yellow db 34,0f8h,01h ; blue db 35,0f8h,05h ; magenta db 36,0f8h,03h ; cyan db 37,0f8h,07h ; white db 40,08fh,00h ; black background db 41,08fh,40h ; red db 42,08fh,20h ; green db 43,08fh,60h ; yellow db 44,08fh,10h ; blue db 45,08fh,50h ; magenta db 46,08fh,30h ; cyan db 47,08fh,70h ; white page ;-------- dos function # 0 : init driver --------------------- ; Initializes device driver interrupts and buffers, then ; passes ending address of the device driver to DOS. ; Since this code is only used once, the buffer can be set up on top ; of it to save RAM. Placed at end so that any excess can be deleted ; after running dosfn0 proc near assume cs:code, ds:code ; The following check for MDA/CGA courtesy of Arend van den Brug ; initialise ega_rows to normal value ; to enable MDA/CGA support mov ax, ABS40 mov ds, ax assume ds:ABS40 mov al, ega_rows or al, al ; not already filled in ? jnz set_kb mov ega_rows, 24 ; then set it to the normal 25 lines set_kb: ; Install BIOS keyboard break handler. xor ax, ax mov ds, ax assume ds:NOTHING mov bx, 6Ch mov word ptr [BX],offset break_handler mov [BX+02], cs ; Install INT 29 quick putchar. mov bx, 0a4h mov word ptr [bx], offset int_29 mov [bx+2], cs IF dos4 ; Install INT 2Fh multiplex interrupt, saving old vector. mov bx, 02fh * 4 mov ax, [bx] mov word ptr cs:old_mpx, ax mov ax, [bx+2] mov word ptr cs:old_mpx[2], ax mov word ptr [bx], offset new_mpx mov word ptr [bx+2], cs ENDIF ; Install INT 10h video bios replacement, saving old vector. mov bx, 40h mov ax, [bx] mov word ptr cs:old_vid_bios, ax mov ax, [bx+2] mov word ptr cs:old_vid_bios[2], ax mov word ptr [bx], offset new_vid_bios mov word ptr [bx+2], cs push cs pop ds push cs pop es ; es=cs so we can use stosb assume ds:code cld ; make sure stosb increments di ; Calculate addresses of start and end of parameter/redef buffer. ; The buffer occupies the same area of memory as this code! ; ANSI parameters are accumulated at the lower end, and ; keyboard redefinitions are stored at the upper end; the variable ; param_end is the last byte used by params (changes as redefs added); ; redef_end is the last word used by redefinitions. mov di, offset dosfn0 mov param_buffer, di add di, buf_size mov param_end, di ; addr of last byte in free area inc di ; Announce our presence mov ax,cs mov bx,offset Ident1 call hexcnv mov si, offset welcome msg_loop: lodsb cmp al,0 je msg_done int 29h jmp msg_loop msg_done: IF key_redef ; Build the default redefinition table: ; control-printscreen -> control-P ; (Must be careful not to write over ourselves here!) mov al, 16 ; control P stosb mov ax, 7200h ; control-printscreen stosw mov ax, 1 ; length field mov redef_end, di ; address of last used word in table stosw endif ; Return ending address of this device driver. ; Status is in AX. lds si, req_ptr mov word ptr [si+0Eh], di mov [si+10h], cs xor ax, ax ; Return exit status in ax. ret welcome: db 27,'[33;1m' db "NNANSI.SYS for " card_id if cheap_pc db " (XT class processor)" else db " (AT class processor)" endif db 13, 10 db 'By Tom Almy, version 05/91' db 13,10,10 db 'Based on NANSI.SYS V2.2' db 13,10 db '(C) Daniel Kegel, Pasadena, CA 1986.' db 13,10 db 'License NANSI.SYS by sending $10.00(US) to Daniel Kegel,' db 13,10 db '221 Fairview Ave, South Pasadena, CA 91030, USA.' db 13, 10, 13, 10 db 'NNANSI driver loaded at ' Ident1 db 'XXXX:0000' db 13, 10, 27, '[0m', 0 dosfn0 endp hexcnv proc near ; AX= value ; BX= pointer to destination string ; AX, BX destroyed, all other registers intact. push cx push dx mov cx, 4 hexcnv_loop: if cheap_pc rol ax, 1 rol ax, 1 rol ax, 1 rol ax, 1 else rol ax, 4 endif mov dl, al and dl, 0fh add dl, '0' cmp dl, '9' jbe hexcnv_isdigit add dl, 'A'-'9'-1 hexcnv_isdigit: mov [bx], dl inc bx loop hexcnv_loop pop dx pop cx ret hexcnv endp code ends end