;ASC for the IBM Personal Computer - 1986 by Jeff Prosise
;
kb_data       equ 60h                       ;keyboard data port
kb_ctrl       equ 61h                       ;keyboard control port
eoi           equ 20h                       ;8259 end-of-interrupt value
int_ctrl_port equ 20h                       ;8259 PIC port
a_key         equ 30                        ;scan code for 'A' key
alt_key       equ 8                         ;shift code for Alt key
;
bios_data     segment at 40h                ;BIOS data area
              org 63h
addr_6845     dw ?                          ;6845 Index Register address
bios_data     ends
;
code          segment para public 'code'
              assume cs:code
              org 100h
begin:        jmp initialize                ;jump to initialization code
;
notice             db 'Copyright 1986 Ziff-Davis Publishing Co.'
notice2            db 'Programmed by Jeff Prosise '
ibm_signature      db 'IBM'                 ;EGA BIOS signature
adapter            db 2                     ;0 = CGA, 1 = MDA, 2 = EGA
video_segment      dw 0B800h                ;video segment address
video_page         db ?                     ;current video page
border_attr        db 0Fh                   ;window border attribute
text_attr          db 1Fh                   ;window text attribute
header_attr        db 1Eh                   ;window header attribute
window_row         db 1                     ;row of left corner of window
window_column      db 3                     ;column of left corner of window
start_value        db 0                     ;first value in ASCII table
int_status         db 0                     ;status of interrupt routine
cursor_mode        dw ?                     ;cursor scan line definition
old_int_9h         label dword              ;old interrupt vector
old_keyboard_int   dw 2 dup (?)
screen_buffer      dw offset initialize     ;pointer to screen buffer area
;
enable_values      db 2Ch,28h,2Dh,29h,      ;values to enable CGA display
                   db 2Ah,2Eh,1Eh
header_text        db ' Dec Hex Char  Dec Hex Char '       ;window header text
;
;-----------------------------------------------------------------------------
;Front-end routine for the keyboard interrupt handler.  Execution is vectored
;here whenever an interrupt 9 is generated by the PC keyboard.
;-----------------------------------------------------------------------------
ascview       proc near
              cmp int_status,0              ;is ASCII table already displayed?
              jne short_exit                ;yes, then exit immediately
              sti                           ;enable interrupts
              push ax                       ;save registers
              push bx
              push cx
              push dx
              push si
              push di
              push ds
              push es
              in al,kb_data                 ;get scan code from keyboard
              cmp al,a_key                  ;was the 'A' key pressed?
              jne exit                      ;no, then exit to normal routine
              mov ah,2                      ;get state of shift keys
              int 16h
              test al,alt_key               ;is the Alt key depressed?
              jne asc1                      ;yes, then continue
;
;Exit is achieved thru here when execution is to be transferred to the normal
;BIOS keyboard interrupt handling routine.
;
exit:         pop es                        ;restore registers
              pop ds
              pop di
              pop si
              pop dx
              pop cx
              pop bx
              pop ax
short_exit:   jmp old_int_9h                ;goto BIOS keyboard routine
;
;The key combination Alt-A was just pressed.  Reset the keyboard and issue an
;EOI to the 8259 PIC to enable hardware interrupts.
;
asc1:         call kb_reset                 ;reset keyboard, end 8259 int
              push cs                       ;set DS and ES to the code segment
              pop ds
              push cs
              pop es
              assume ds:code
;
;Check the current video mode to see if it's one of the 80-column text modes
;(2, 3, or 7).  If it is, then continue.  If it's not, gracefully abort this
;routine by exiting thru an IRET.
;
              mov ah,15                     ;get video page and display mode
              int 10h
              cmp al,2                      ;video mode 2?
              je asc2                       ;yes, then continue
              cmp al,3                      ;mode 3?
              je asc2                       ;yes, then continue
              cmp al,7                      ;mode 7 (monochrome)?
              je asc2                       ;yes, then continue
done:         pop es                        ;restore register values for exit
              pop ds
              pop di
              pop si
              pop dx
              pop cx
              pop bx
              pop ax
              iret                          ;return to interrupted program
;
;Save the current video page number and the cursor scan line parameters,
;set the interrupt routine status flag, and blank the cursor.
;
asc2:         mov video_page,bh             ;save page number
              mov int_status,1              ;set interrupt routine status flag
              mov ah,3                      ;get the cursor shape
              int 10h
              mov cursor_mode,cx            ;save it
              mov ah,1                      ;hide the cursor for now
              mov ch,20h
              int 10h
;
;Save the contents of the portion of the screen that will underlie the window.
;
              cmp adapter,0                 ;is this a CGA?
              jne asc3                      ;no, then skip disable
              call disable_cga              ;disable CGA video
asc3:         mov di,screen_buffer          ;point DI to storage buffer
              call save_screen              ;save screen contents
;
;Pop the window border onto the display by writing directly to video.  Finish
;things up by filling the blank window with the text of the ASCII table.
;
              call open_window              ;pop up the window border
              cmp adapter,0                 ;is this a CGA?
              jne asc4                      ;no, then skip enable
              call enable_cga               ;enable CGA video
asc4:         call fill_window              ;write the window text
;
;The window is now displayed on the screen.  Wait for a keypress.
;
asc5:         mov ah,0                      ;get a keypress
              int 16h
              cmp al,0                      ;is it an extended code?
              je asc10                      ;yes, then jump
              cmp al,27                     ;ESC key pressed?
              jne asc5                      ;no, then get another keypress
;
;The ESC key has been pressed.  Restore the original contents of the screen,
;restore the cursor, reset the status flag, and exit to the application.
;
              cmp adapter,0                 ;blank video if CGA installed
              jne asc6
              call disable_cga
asc6:         mov si,screen_buffer          ;point SI to holding buffer
              call restore_screen           ;restore video memory contents
              cmp adapter,0                 ;enable video if necessary
              jne asc7
              call enable_cga
asc7:         mov ah,1                      ;unblank the cursor
              mov cx,cursor_mode            ;define cursor scan lines
              int 10h
              mov int_status,0              ;reset status flag
              jmp done                      ;exit
;
;A key has been pressed that returned an extended code.
;
asc10:        cmp ah,72                     ;Up-Arrow pressed?
              jne asc11                     ;no, then jump to next test
              call scroll_down              ;yes, then scroll down
              jmp asc5                      ;return for another keypress
asc11:        cmp ah,80                     ;Down-Arrow pressed?
              jne asc12
              call scroll_up                ;scroll window up
              jmp asc5
asc12:        cmp ah,73                     ;PgUp pressed?
              jne asc13
              call last_window_page         ;flip window page back
              jmp asc5
asc13:        cmp ah,81                     ;PgDn pressed?
              jne asc14
              call next_window_page         ;flip window page forward
              jmp asc5
asc14:        cmp ah,79                     ;End pressed?
              jne asc15
              call end_window_page          ;flip to last window page
              jmp asc5
asc15:        cmp ah,71                     ;Home pressed?
              jne asc16                     ;no, then start again
              call first_window_page        ;goto first window page
asc16:        jmp asc5                      ;look for another keypress
ascview       endp
;
;-----------------------------------------------------------------------------
;SAVE_SCREEN saves the contents of the screen beneath the window.
;Entry:  ES:DI - buffer address
;-----------------------------------------------------------------------------
save_screen   proc near
              mov dh,window_row             ;row and column of window corner
              mov dl,window_column
              mov bl,video_page             ;get video page in BX
              xor bh,bh
              push di                       ;save buffer address
              call video_offset             ;get starting address of window
              mov si,di                     ;transfer address to SI
              pop di                        ;retrieve buffer address
              push ds                       ;save DS
              mov ds,video_segment          ;set DS to video segment
              assume ds:nothing
              mov cx,19                     ;19 lines to save
              cld                           ;clear DF
save1:        push cx                       ;save line counter
              mov cx,30                     ;30 words per line
              rep movsw                     ;transfer one line to buffer
              add si,100                    ;adjust SI for next line
              pop cx                        ;get line count
              loop save1                    ;loop until done
              pop ds                        ;restore DS
              assume ds:code
              ret
save_screen   endp
;
;-----------------------------------------------------------------------------
;RESTORE_SCREEN restores the contents of the screen beneath the window.
;Entry:  DS:SI - buffer address
;-----------------------------------------------------------------------------
restore_screen proc near
              mov dh,window_row             ;row & column where window starts
              mov dl,window_column
              mov bl,video_page             ;get video page in BX
              xor bh,bh
              call video_offset             ;get window starting address
              mov es,video_segment          ;set ES to video segment
              mov cx,19                     ;19 lines to restore
              cld                           ;clear DF
restore1:     push cx                       ;save line counter
              mov cx,30                     ;30 words per line
              rep movsw                     ;restore one line
              add di,100                    ;set DI for next line
              pop cx                        ;retrieve count
              loop restore1                 ;loop until done
              ret
restore_screen endp
;
;-----------------------------------------------------------------------------
;VIDEO_OFFSET calculates the offset address in video memory that corresponds
;to the given row, column, and video page.
;Entry:  DH,DL - row, column           | Exit:  DI - offset
;        BX    - video page            |
;-----------------------------------------------------------------------------
video_offset  proc near
              mov al,160                    ;row * 160
              mul dh                        ;result in AX
              shl dl,1                      ;column * 2
              xor dh,dh                     ;byte to word in DX
              add ax,dx                     ;add the two
              mov di,ax                     ;save result in DI
              mov ax,1000h                  ;length of one video page
              mul bx                        ;page * 1000h
              add di,ax                     ;complete the offset address
              ret
video_offset  endp
;
;-----------------------------------------------------------------------------
;DISABLE_CGA and ENABLE_CGA routines disable and enable CGA video by writing
;to the Mode Select Register at port address 3D8h.
;-----------------------------------------------------------------------------
disable_cga   proc near
              mov dx,3DAh                   ;load Status Register address
disable1:     in al,dx                      ;get video status
              test al,8                     ;vertical retrace active?
              je disable1                   ;no, then wait until it is
              sub dx,2                      ;load MSR address
              mov al,25h                    ;value to disable video
              out dx,al                     ;send it to the adapter
              ret
disable_cga   endp
;
enable_cga    proc near
              mov ah,15                     ;get current video mode
              int 10h
              lea bx,enable_values          ;offset of enable value table
              xlat                          ;get the value to enable video
              mov dx,3D8h                   ;load address of MSR
              out dx,al                     ;OUT the enable value
              ret
enable_cga    endp
;
;-----------------------------------------------------------------------------
;KB_RESET resets the keyboard and issues an EOI to the 8259 PIC.
;-----------------------------------------------------------------------------
kb_reset      proc near
              in al,kb_ctrl                 ;get current control port value
              mov ah,al                     ;save it in AH
              or al,80h                     ;set bit 7
              out kb_ctrl,al                ;send reset value
              mov al,ah                     ;get original value
              out kb_ctrl,al                ;send it out to enable keyboard
              cli                           ;suspend interrupts
              mov al,eoi                    ;get EOI value
              out int_ctrl_port,al          ;send EOI to 8259
              sti                           ;enable interrupts
              ret
kb_reset      endp
;
;-----------------------------------------------------------------------------
;BIN2ASC outputs a byte value to the screen in decimal or hexadecimal form.
;Entry:  DH,DL - row, column
;        AL    - number to output
;        BL    - divisor (10 for decimal output, 16 for hex)
;-----------------------------------------------------------------------------
bin2asc       proc near
              push ax                       ;save value to output
              xor cx,cx                     ;zero digit counter
              xor ah,ah                     ;byte to word in AX
bin1:         div bl                        ;divide AX by 10 or 16
              push ax                       ;save AH (remainder)
              inc cx                        ;increment counter
              xor ah,ah                     ;byte to word (quotient)
              or al,al                      ;was the quotient zero?
              jne bin1                      ;no, then go back for more
bin2:         pop ax                        ;get digit to print next
              mov al,ah                     ;put it in AL
              or al,48                      ;convert binary to ASCII
              cmp al,'9'                    ;alphanumeric hex digit?
              jna bin3                      ;no, then jump ahead
              add al,7                      ;yes, then more conversion needed
bin3:         mov ah,text_attr              ;set text attribute for write
              call output_char              ;print the digit
              inc dl                        ;advance cursor
              loop bin2                     ;loop until done
              pop ax                        ;restore value of AX
              ret
bin2asc       endp
;
;-----------------------------------------------------------------------------
;OUTPUT_CHAR writes the designated character directly to video memory.
;Entry:  DH,DL - row, column
;        AH,AL - attribute, character
;-----------------------------------------------------------------------------
output_char   proc near
              push dx                       ;save DX and AX
              push ax
              mov bl,video_page             ;get page in BX
              xor bh,bh
              call video_offset             ;calculate address to write to
              cmp adapter,0                 ;is this a CGA?
              jne output3                   ;no, then skip wait loop
              mov dx,3DAh                   ;get CGA Status Register address
output1:      in al,dx                      ;wait until horiz. retrace done
              test al,1
              jne output1
              cli                           ;suspend interrupts during write
output2:      in al,dx                      ;wait for next horizontal retrace
              test al,1
              je output2
output3:      pop ax                        ;get character and attribute
              stosw                         ;write them to video memory
              sti                           ;enable interrupts
              pop dx                        ;restore DX
              ret
output_char   endp
;
;-----------------------------------------------------------------------------
;FILL_WINDOW writes the text of the ASCII table to the window.
;-----------------------------------------------------------------------------
fill_window   proc near
              mov dh,window_row             ;set DH for first text row
              add dh,2
              mov al,start_value            ;get first table value in AL
              mov cx,16                     ;16 lines to fill
fill1:        push cx                       ;save line counter
              mov dl,window_column          ;specify starting column
              add dl,2
              call write_line               ;write first half of the line
              add dl,5                      ;advance cursor for next half
              add al,16                     ;adjust AL for next half
              call write_line               ;write second half
              sub al,15                     ;set AL for start of next line
              inc dh                        ;set cursor for next row
              pop cx                        ;retrieve line counter
              loop fill1                    ;loop until table full
              ret
fill_window   endp
;
;-----------------------------------------------------------------------------
;WRITE_LINE outputs one ASCII character and its decimal and hex equivalents.
;Entry:  AL    - ASCII code for character to output
;        DH,DL - row, column to start writing at
;-----------------------------------------------------------------------------
write_line    proc near
              mov es,video_segment          ;set ES to video for writing
              cmp al,100                    ;advance cursor if AL < 100
              jae write1
              inc dl
              cmp al,10                     ;advance again if AL < 10
              jae write1
              inc dl
write1:       mov bl,10                     ;set BL for decimal output
              call bin2asc                  ;output decimal text
              add dl,2                      ;set cursor to next field
              cmp al,10h                    ;advance cursor if AL < 10h
              jae write2
              inc dl
write2:       mov bl,16                     ;set BL for hex output
              call bin2asc                  ;output hexadecimal text
              add dl,2                      ;goto next field
              mov ah,text_attr              ;set text attribute
              call output_char              ;print the character
              ret
write_line    endp
;
;-----------------------------------------------------------------------------
;OPEN_WINDOW draws the window border onto the screen.  Character/attribute
;pairs are sent directly to video memory for fast display speed.
;-----------------------------------------------------------------------------
open_window   proc near
              mov dh,window_row             ;get coordinates of window corner
              mov dl,window_column
              mov es,video_segment          ;point ES to video buffer
              cld                           ;clear DF for string operations
              mov bl,video_page             ;get video page in BX
              xor bh,bh
              call video_offset             ;calculate starting address
;
;Write the top line of the window border to video.
;
              mov al,218                    ;start with upper left corner
              mov ah,border_attr            ;set attribute
              stosw
              mov cx,28                     ;do the next 28 characters
              mov al,196
              rep stosw
              mov al,191                    ;do upper right corner
              stosw
;
;Do the window header line.
;
              add di,100                    ;set DI for new line
              mov al,179                    ;left window border character
              stosw                         ;write it
              lea si,header_text            ;point SI to text of line
              mov cx,28                     ;28 characters to write
              mov ah,header_attr            ;use header attribute for these
open1:        lodsb                         ;get the text character
              stosw                         ;write char/attr pair to video
              loop open1                    ;repeat for all 28
              mov ah,border_attr            ;do rightmost column
              mov al,179
              stosw
;
;Now write the next 16 lines (no text) to the display.
;
              add di,100                    ;set DI for new line
              mov cx,16                     ;16 lines to do
open2:        push cx                       ;save line counter
              mov al,179                    ;do leftmost column
              mov ah,border_attr
              stosw
              mov al,32                     ;do next 28 columns (blank)
              mov ah,text_attr
              mov cx,28
              rep stosw
              mov al,179                    ;do rightmost column
              mov ah,border_attr
              stosw
              add di,100                    ;adjust DI for next line
              pop cx                        ;retrieve counter
              loop open2                    ;loop until finished
;
;Finish things up by writing the last line.
;
              mov al,192                    ;lower left corner
              stosw
              mov cx,28                     ;next 28 characters
              mov al,196
              rep stosw
              mov al,217                    ;lower right corner
              stosw
              ret
open_window   endp
;
;-----------------------------------------------------------------------------
;SCROLL_UP scrolls the window contents up one line.
;-----------------------------------------------------------------------------
scroll_up     proc near
              cmp start_value,224           ;can we scroll more?
              je scrup1                     ;no, then do nothing
              mov ah,6                      ;use INT 10h to scroll up
              mov al,1                      ;one line
              call define_window            ;set CX and DX for scroll
              mov bh,text_attr              ;define attribute
              int 10h                       ;scroll up
              inc start_value               ;adjust starting value
              mov al,start_value            ;calculate new value to write
              add al,15
              mov dh,window_row             ;determine row to be written to
              add dh,17
              mov dl,window_column          ;set starting column
              add dl,2
              call write_line               ;write the first half of new line
              add al,16                     ;adjust AL
              add dl,5                      ;move cursor to next half of line
              call write_line               ;write second half
scrup1:       ret
scroll_up     endp
;
;-----------------------------------------------------------------------------
;SCROLL_DOWN scrolls the window contents down one line.
;-----------------------------------------------------------------------------
scroll_down   proc near
              cmp start_value,0             ;can we scroll more?
              je scrdn1                     ;no, then do nothing
              mov ah,7                      ;use INT 10h to scroll down
              mov al,1                      ;one line
              call define_window            ;set CX and DX
              mov bh,text_attr              ;set attribute
              int 10h                       ;do the scroll
              dec start_value               ;adjust starting value
              mov al,start_value            ;new value to write
              mov dh,window_row             ;determine row for write
              add dh,2
              mov dl,window_column          ;set starting column
              add dl,2
              call write_line               ;write new line at top
              add al,16                     ;adjust AL
              add dl,5                      ;move cursor to second half
              call write_line               ;write second half of line
scrdn1:       ret
scroll_down   endp
;
;-----------------------------------------------------------------------------
;LAST_WINDOW_PAGE pages the window back one page.
;-----------------------------------------------------------------------------
last_window_page proc near
              cmp start_value,0             ;can we go back more?
              je last3                      ;no, then do nothing
              cmp start_value,31            ;starting value > 31?
              ja last1                      ;yes, then jump
              mov start_value,0             ;no, then zero value
              jmp last2                     ;skip next line
last1:        sub start_value,32            ;adjust START_VALUE
last2:        call clear_window             ;clear interior of window
              call fill_window              ;write the new table
last3:        ret
last_window_page endp
;
;-----------------------------------------------------------------------------
;NEXT_WINDOW_PAGE pages the window forward one page.
;-----------------------------------------------------------------------------
next_window_page proc near
              cmp start_value,224           ;can we go forward more?
              je next3                      ;no, then do nothing
              cmp start_value,193           ;starting value <193?
              jb next1                      ;yes, then jump
              mov start_value,224           ;set START_VALUE to maximum
              jmp next2                     ;skip next line
next1:        add start_value,32            ;adjust for next page
next2:        call clear_window             ;clear window
              call fill_window              ;write new text page
next3:        ret
next_window_page endp
;
;-----------------------------------------------------------------------------
;FIRST_WINDOW_PAGE flips the window contents to the first page.
;-----------------------------------------------------------------------------
first_window_page proc near
              cmp start_value,0             ;already at beginning?
              je first1                     ;yes, then do nothing
              mov start_value,0             ;zero starting value
              call clear_window             ;clear window
              call fill_window              ;write text to window
first1:       ret
first_window_page endp
;
;-----------------------------------------------------------------------------
;END_WINDOW_PAGE flips the window contents to the final page.
;-----------------------------------------------------------------------------
end_window_page proc near
              cmp start_value,224           ;already at end?
              je end1                       ;yes, then do nothing
              mov start_value,224           ;set START_VALUE to maximum
              call clear_window             ;clear window
              call fill_window              ;write window text
end1:         ret
end_window_page endp
;
;-----------------------------------------------------------------------------
;CLEAR_WINDOW clears the interior of the window.
;-----------------------------------------------------------------------------
clear_window  proc near
              mov ah,6                      ;use INT 10h to clear window
              mov al,0                      ;code for blank function
              call define_window            ;set CX and DX
              mov bh,text_attr              ;set attribute for cleared area
              int 10h                       ;perform the clear
              ret
clear_window  endp
;
;-----------------------------------------------------------------------------
;DEFINE_WINDOW sets the values of CX and DX for call to BIOS scroll functions.
;-----------------------------------------------------------------------------
define_window proc near
              mov ch,window_row             ;define upper left corner in CX
              add ch,2
              mov cl,window_column
              add cl,2
              mov dx,cx                     ;define lower right corner in DX
              add dh,15
              add dl,25
              ret
define_window endp
;
;-----------------------------------------------------------------------------
;INITIALIZE performs a variety of tasks to set the stage for the resident part
;of the program.
;-----------------------------------------------------------------------------
initialize    proc near
;
;See if the display adapter is an EGA by looking for the EGA BIOS signature.
;
              mov ax,0C000h                 ;set ES to EGA BIOS segment
              mov es,ax
              mov di,1Eh                    ;point DI to signature location
              lea si,ibm_signature          ;point SI to 'IBM' text
              mov cx,3                      ;three bytes to compare
              cld                           ;clear DF
              repe cmpsb                    ;check three bytes
              je init1                      ;jump if EGA signature found
;
;The display adapter is not an EGA.  Determine whether it's a CGA or an MDA.
;
              mov adapter,0                 ;zero ADAPTER for CGA
              mov ah,15                     ;get video mode
              int 10h
              cmp al,7                      ;is it mode 7?
              jne init1                     ;no, then it's a CGA - jump
              inc adapter                   ;yes, it's an MDA - set ADAPTER
;
;If this is a monochrome system, modify color-dependent values accordingly.
;
init1:        cmp al,7                      ;is current video mode 7?
              je init2                      ;yes, then branch
              cmp al,15                     ;is current video mode 15?
              jne init3                     ;no, then skip modification code
init2:        sub video_segment,800h        ;set VIDEO_SEGMENT for monochrome
              mov border_attr,70h           ;change attributes for monochrome
              mov text_attr,07h
              mov header_attr,07h
;
;Reset the cursor to scan lines 6 and 7 (color) or 12 and 13 (monochrome).
;
init3:        mov cx,0C0Dh                  ;monochrome cursor type
              cmp al,7                      ;video mode 7?
              je init4                      ;yes, then jump
              cmp al,15                     ;video mode 15?
              je init4                      ;yes, then jump
              mov cx,0607h                  ;scan lines 6 and 7 for color
init4:        mov ah,1                      ;set cursor type
              int 10h
;
;Now save the old interrupt 9 vector and replace it with one pointing to the
;code that we will leave behind in memory.
;
              mov ah,35h                    ;get current interrupt 9 vector
              mov al,9
              int 21h
              mov old_keyboard_int,bx       ;save vector offset
              mov old_keyboard_int[2],es    ;save vector segment
              mov ah,25h                    ;set new vector
              mov al,9
              lea dx,ascview                ;point it to body of program
              int 21h
;
;Exit thru INT 27h and reserve enough room beyond the actual code to store the
;contents of the area of screen that underlies the pop-up window.
;
              mov dx,offset initialize+1140 ;reserve space for code and buffer
              int 27h                       ;terminate-but-stay-resident
initialize    endp
;
code          ends
              end begin
                                                                                                                               