;
interrupts    segment at 0h                      ;interrupt table segment
              org 9h*4
keyboard_int  dw 2 dup (?)                       ;interrupt 9 vector
interrupts    ends
;
rom_bios_data segment at 40h                     ;ROM BIOS data area segment
              org 60h
;
cursor_mode   dw ?                      ;starting and ending cursor scan lines
rom_bios_data ends
;
rom           segment at 0F000h                  ;ROM segment
              org 0FFFEh
;
machine_id    db ?                ;ID byte identifies machine as PCjr or other
rom           ends
;
code          segment para public 'code'         ;code segment
              assume cs:code
              org 100h
;
begin:        jmp initialize                     ;goto initialization routine
;
              db '(C) Copyright 1986, Ziff-Davis Publishing Company ', 1Ah
;
column_count  dw ?                               ;width of window in columns
cursor_type   dw ?                               ;cursor scan line definition
setup_status  db 0              ;indicates if printer window is already active
display_mode  dw ?                               ;current crt display mode
page_no       dw ?                               ;current displayed page
attribute1    db 4Fh                             ;window attribute bytes
attribute2    db 70h
;
display_table db 2Dh,29h        ;display re-enable values for modes 2 and 3
video         dw 0B800h,0B900h,0BA00h,0BB00h     ;starting addresses of video
                                                 ;memory for CGA pages 0 - 3
;
mono_video    dw 0B000h                          ;video segment address for
                                                 ;monochrome adapter
;
old_kb_int              label dword
old_keyboard_int        dw 2 dup (?)             ;storage for old keyboard
                                                 ;interrupt vector
;
;----------------------------------------------------------------------------
;Text of the Printer Setup Menu window.
;After initialization, text and attribute bytes are combined and stored
;in the WINDOW_TEXT area, and this area is used to store the contents of
;the screen that underlie the window when the window is called up.
;----------------------------------------------------------------------------
;
window_buffer           label word
buffer_text             db 201,26 dup (205),187
                        db 186,'    PRINTER SETUP MENU    ',186
                        db 186,'   EPSON RX/FX PRINTERS   ',186
                        db 199,26 dup (196),182
                        db 186,' F1    Compressed Mode    ',186
                        db 186,' F2    Expanded Mode      ',186
                        db 186,' F3    Emphasized Mode    ',186
                        db 186,' F4    Double-Strike Mode ',186
                        db 186,' F5    Elite Mode         ',186
                        db 186,' F6    Miniature Mode     ',186
                        db 186,' F7    Skip Perforation   ',186
                        db 186,' F8    Indent Left Margin ',186
                        db 186,' F9    Reset Top-of-Form  ',186
                        db 186,' F10   Reset Print Modes  ',186
                        db 186,' ESC   Exit               ',186
                        db 199,26 dup (196),182
                        db 186,' Unshifted:   Toggle ON   ',186
                        db 186,' Shifted:     Toggle OFF  ',186
                        db 200,26 dup (205),188
                        db 532 dup (?)
;
;Storage area for the combination of text and attribute bytes that
;form the window image.
;
window_bytes  label byte
window_text   dw 532 dup (?)
;
;Control code strings for all of the printer setup options.
;
code_table:   db 15,255,14 dup (0)                      ;compressed mode on
              db 27,87,1,255,12 dup (0)                 ;expanded mode on
              db 27,69,255,13 dup (0)                   ;emphasized mode on
              db 27,71,255,13 dup (0)                   ;double-strike mode on
              db 27,77,255,13 dup (0)                   ;elite mode on
              db 15,27,83,0,27,65,6,255,8 dup (0)       ;miniature mode on
              db 27,78,12,255,12 dup (0)                ;perfskip on
              db 27,108,10,255,12 dup (0)               ;indent left margin
              db 27,67,66,255,12 dup (0)                ;reset top-of-form
              db 18,27,87,0,27,70,27,72,27,80,27,84,27,50,255,0      ;reset
                                                               ;print modes
;
              db 18,255,14 dup (0)                      ;compress off
              db 27,87,0,255,12 dup (0)                 ;expand off
              db 27,70,255,13 dup (0)                   ;emphasize off
              db 27,72,255,13 dup (0)                   ;double-strike off
              db 27,80,255,13 dup (0)                   ;elite off
              db 18,27,84,27,50,255,10 dup (0)          ;miniature off
              db 27,79,255,13 dup (0)                   ;perfskip off
              db 27,108,0,255,12 dup (0)                ;indent off
;
;---------------------------------------------------------------------------
;Execution comes here, to the main body of the program, when an interrupt 9
;is generated from the keyboard.  Registers are saved, then the keypress is
;checked and compared to the key combination that activates the menu window.
;---------------------------------------------------------------------------
;
main          proc near
              sti                                ;enable software interrupts
              push ax                            ;save all registers
              push bx
              push cx
              push dx
              push si
              push di
              push ds
              push es
              push ax                   ;save ax for call to old routine
              in al,0A0h                         ;re-enable NMI on PCjr
              pop ax                             ;restore ax
              pushf          ;simulate interrupt call to old keyboard routine
              call old_kb_int                    ;call old routine
              mov ah,2                   ;check status of the shift keys
              int 16h
              and al,5                           ;Ctrl and Rt-Shift depressed?
              cmp al,5
              je do_program                      ;yes, then skip exit routine
;
;Exit routine is the common point of exit for all routines in the program.
;
exit:         pop es
              pop ds
              pop di
              pop si
              pop dx
              pop cx
              pop bx
              pop ax
              iret
;
;Execution comes here when the proper key combination, Ctrl/Rt-Shift, is
;pressed.  First task is to check whether or not the window is already open.
;
do_program:   push cs                   ;set es and ds to the code segment
              pop ds
              push cs
              pop es
              cmp setup_status,0                 ;is the window already open?
              jne exit                           ;yes, then ignore request
;
;----------------------------------------------------------------------------
;Check current video mode.  If it's mode 2, 3, or 7, then set the window
;status flag, store the mode number and page number, save the cursor type,
;and hide the cursor.  If any other display mode is active instead, ignore
;the request and exit.
;----------------------------------------------------------------------------
;
              mov ah,15                          ;get page and mode numbers
              int 10h                            ;al=mode, bh=page
              cmp al,2                  ;is crt now in an acceptable mode?
              je prog0                           ;yes, then continue
              cmp al,3
              je prog0
              cmp al,7
              je prog0
              jmp exit                           ;no, then ignore request
prog0:        mov setup_status,1                 ;set status flag to indicate
                                                 ;that window is active
              mov ah,0                           ;save mode number
              mov display_mode,ax
              push bx
              mov bl,bh                 ;save page number for color displays
              mov bh,0
              mov page_no,bx
              pop bx
              mov ah,3                           ;get cursor type
              int 10h
              mov cursor_type,cx                 ;save it
              mov ah,1                           ;hide the cursor until later
              mov ch,20h
              int 10h
;
;Preparatory routines are completed.  Now open the window by first saving the
;contents of video memory beneath the window and then writing the window text
;directly to memory.
;
              mov bx,page_no             ;use bx as index into video segment
                                         ;address table
              cmp display_mode,7         ;manually adjust index for monochrome
                                         ;adapter
              jne prog1
              mov bx,4
prog1:        shl bx,1                           ;multiply bx by two since
                                                 ;table is made up of words
              mov ax,video[bx]                   ;read segment from table
              mov ds,ax                          ;ds set to video memory
              cmp display_mode,7                 ;skip disable if in mode 7
              je prog2
              call video_disable      ;turn display off for snow-free writing
prog2:        lea di,window_buffer         ;set di to buffer area to save
                                           ;screen contents
              mov ch,28               ;define window dimensions and location
              mov cl,19
              mov dh,2
              mov dl,26
              call video2mem          ;then transfer screen contents to buffer
              push ds                            ;set es to video memory
              pop es
              push cs                            ;reset ds to code segment
              pop ds
              lea si,window_text                 ;point si to window image
prog3:        mov ch,28                          ;define window region
              mov cl,19
              mov dh,2
              mov dl,26
              call mem2video            ;and write the window to the display
              cmp display_mode,7                 ;skip enable if in mode 7
              je getkey
              call video_enable                  ;re-enable the video display
;
;Window is now present on the screen, so wait for a keypress.
;
getkey:      mov ah,0                           ;get a keypress
              int 16h
              cmp al,0                           ;is it an extended code?
              je extended_code                   ;yes, go interpret it
              cmp al,27                          ;is it the ESC key?
              jne getkey1               ;no, then signal illegal keypress
;
;----------------------------------------------------------------------------
;Execution comes here when the ESC key is pressed.  The window is refilled
;with its original contents, the cursor is restored, and control is handed
;back to the application program.
;----------------------------------------------------------------------------
;
              cmp display_mode,7                 ;skip disable if in mode 7
              je prog4
              call video_disable                 ;turn off the display
prog4:        lea si,window_buffer               ;point si to the buffer area
              mov ch,28                          ;define the window
              mov cl,19
              mov dh,2
              mov dl,26
              call mem2video     ;and write the buffer contents to the display
              cmp display_mode,7                 ;skip enable if in mode 7
              je prog5
              call video_enable                  ;turn display back on
prog5:        mov ah,1                           ;restore cursor
              mov cx,cursor_type
              int 10h
              mov setup_status,0                 ;reset window status
              jmp exit                           ;and exit
;
;Getkey1 routine handles an illegal keypress by beeping and returning
;for another.
;
getkey1:      call beep                 ;beep and return for another keypress
              jmp getkey
;
;An extended code has been entered...check its validity and goto the
;appropriate routine.
;
extended_code:
              cmp ah,59                          ;less than F1?
              jb getkey1                         ;yes, then don't accept it
              cmp ah,91                          ;greater than Shft-F8?
              ja getkey1                         ;yes, then don't accept it
              cmp ah,68                          ;between F1 and F10?
              jbe unshifted                      ;yes
              cmp ah,84                          ;between Shft-F1 and Shft-F9?
              jae shifted                        ;yes
              jmp getkey1     ;if all tests failed, then keypress was illegal
;
;----------------------------------------------------------------------------
;If a legal function key was pressed, its scan code is translated here to the
;starting address of the string of bytes to be sent to the printer.  The
;string is then sent to LPT1: provided it's powered on and on-line.
;----------------------------------------------------------------------------
;
shifted:      sub ah,15                ;adjustment for shifted function keys
unshifted:    sub ah,59                ;adjustment for unshifted function keys
              mov al,ah                          ;convert index to word in ax
              xor ah,ah
              mov cl,4                           ;multiply ax by 16
              shl ax,cl
              add ax,offset code_table     ;convert ax to full offset address
              mov si,ax                          ;and transfer it to si
              call lpt1stat                      ;check for printer ready
              jc getkey1                         ;beep if printer not ready
              mov bl,255               ;specify delimiter for call to LPRINTZ
              call lprintz             ;send control code string to printer
              jmp getkey                         ;return for another keypress
;
main          endp                               ;end of main body of program
;
;---------------------------------------------------------------------------
;VIDEO_ENABLE and VIDEO_DISABLE routines manipulate bit 3 of port 3D8h,
;the CGA Mode Control Register, to temporarily turn the display on or off.
;Since these routines write directly to hardware, they have no effect on
;other video adapters.
;---------------------------------------------------------------------------
;
video_disable proc near
              mov dx,3DAh                        ;read CGA status port
disable1:     in al,dx                  ;wait for vertical retrace to occur
              test al,8                          ;is bit 3 set?
              je disable1                        ;no, wait until it is
              mov dx,3D8h                        ;now disable the display
              mov al,25h      ;by clearing bit 3 of the Mode Control Register
              out dx,al
              ret
video_disable endp
;
video_enable  proc near
              mov dx,3D8h                        ;CGA Mode Control Register
              mov bx,display_mode        ;get value to re-enable display
              sub bx,2
              mov al,display_table[bx]
              out dx,al                          ;and send it to the port
              ret
video_enable  endp
;
;---------------------------------------------------------------------------
;VIDEO2MEM routine transfers the contents of a portion of video memory
;to a memory buffer for storage.
;Entry:       DS    - video segment
;             ES:DI - memory buffer
;             DH,DL - row and column of upper left corner of window
;             CH    - width of window in columns
;             CL    - number of lines in window
;---------------------------------------------------------------------------
;
video2mem     proc near
              mov al,ch                          ;store number of columns
              mov ah,0
              mov column_count,ax
              mov ch,0                           ;cx = number of lines
              push di                            ;save di
              call video_offset         ;get cell address of first character
              mov si,di                          ;put it in si
              pop di                             ;restore di
v2mem1:       push si                            ;save si for next line
              push cx                            ;save line count
              mov cx,column_count                ;set cx for call to WRITELN
              call writeln                       ;transfer one line
              pop cx                             ;restore saved registers
              pop si
              add si,160                         ;set si for next line address
              loop v2mem1               ;loop until all lines are done
              ret
video2mem     endp
;
;---------------------------------------------------------------------------
;MEM2VIDEO writes a selected area of memory to the video display.
;Entry:       DS:SI - memory buffer
;             ES    - video segment
;             DH,DL - row and column of upper left corner of window
;             CH    - width of window in columns
;             CL    - number of lines in window
;---------------------------------------------------------------------------
;
mem2video     proc near
              mov al,ch                          ;save number of columns
              mov ah,0
              mov column_count,ax
              mov ch,0                           ;cx = number of lines
              call video_offset                  ;get offset into video memory
mem2v1:       push di                            ;save video starting address
              push cx                            ;save line count
              mov cx,column_count                ;set cx for call to WRITELN
              call writeln                       ;transfer one line
              pop cx                             ;restore registers
              pop di
              add di,160                         ;set di for next display line
              loop mem2v1                        ;loop until done
              ret
mem2video     endp
;
;---------------------------------------------------------------------------
;VIDEO_OFFSET calculates the offset into video memory of a character cell.
;Entry:       DH,DL - row and column of cell (0-24,0-79)
;Exit:        DI    - offset address
;---------------------------------------------------------------------------
;
video_offset  proc near
              mov al,160
              mul dh                             ;row * 160
              shl dl,1                           ;column * 2
              mov dh,0                           ;byte to word
              add ax,dx                          ;(row *160)+(column*2)
              mov di,ax                          ;set offset in di
              ret
video_offset  endp
;
;---------------------------------------------------------------------------
;WRITELN subroutine copies a string of words from one memory location to
;another.  The CGA status port is not checked for vertical retrace status
;before transfer.
;Entry:       DS:SI - source
;             ES:DI - destination
;             CX    - number of words
;---------------------------------------------------------------------------
;
writeln       proc near
              cld                       ;clear for string instructions
write1:       movsw                              ;move one word
              loop write1                        ;loop until done
              ret
writeln       endp
;
;---------------------------------------------------------------------------
;LPRINTZ routine sends a string of bytes delimited by a user-specified byte to
;LPT1: thru INT 17h.
;Entry:       DS:SI - string address
;             BL    - delimiter (0-255)
;---------------------------------------------------------------------------
;
lprintz       proc near
              cld                                ;for 8088 string instructions
lprintz1:     lodsb                              ;get one byte
              cmp al,bl                          ;is it the delimiter?
              je lprintz2                        ;yes, then exit
              mov dx,0                           ;printer no. 0 (LPT1:)
              mov ah,0
              int 17h                            ;send byte to printer
              jmp lprintz1                       ;return for next byte
lprintz2:     ret
lprintz       endp
;
;---------------------------------------------------------------------------
;LPT1STAT checks the current status of printer LPT1:.  If it's either
;powered off or off-line, then an error condition is signalled upon return
;thru the carry flag.
;Exit:        Carry clear - no error
;             Carry set   - error
;---------------------------------------------------------------------------
;
lpt1stat      proc near
              mov dx,0                  ;printer no. 0
              mov ah,2                  ;use ROM BIOS 'get status' function
              int 17h
              test ah,8                 ;test bit 3, I/O error indicator
              je stat1                  ;if clear, then no error
              stc                       ;raise error flag
              ret
stat1:        clc                       ;clear error flag
              ret
lpt1stat      endp
;
;---------------------------------------------------------------------------
;BEEP uses the 8253 timer chip to emit a short beep thru the PC's speaker.
;---------------------------------------------------------------------------
;
beep          proc near
              mov al,182          ;notify 8253 that frequency data is coming
              out 67,al
              mov al,0                           ;send frequency (776.8 Hz)
              out 66,al
              mov al,6
              out 66,al
              in al,97                           ;activate speaker
              or al,3
              out 97,al
              mov cx,6000h                  ;time delay for sound duration
beep1:        loop beep1
              in al,97                           ;deactivate speaker
              and al,252
              out 97,al
              ret
beep          endp
;
;---------------------------------------------------------------------------
;MEM2MEM subroutine transfers a non-overlapping block of memory one byte
;at a time.
;Entry:       DS:SI - source
;             ES:DI - destination
;             CX    - number of bytes
;---------------------------------------------------------------------------
;
mem2mem       proc near
              cld
mem1:         movsb                              ;transfer one byte
              loop mem1                          ;and loop until done
              ret
mem2mem       endp
;
;---------------------------------------------------------------------------
;Initialization routine sets up the window image in the WINDOW_TEXT area,
;resets the CURSOR_MODE word if this is a PCjr, and saves and replaces the
;old keyboard interrupt vector.
;---------------------------------------------------------------------------
;
initialize    proc near
;
;Initialize the window text area by combining the text data with the attribute
;bytes and placing the conglomeration in the WINDOW_TEXT area.
;
              assume ds:code,es:code
              mov ah,15                          ;check the current video mode
              int 10h
              cmp al,7           ;if it's mode 7, then replace the attribute
              jne init0          ;bytes with ones appropriate for mono adapter
              mov attribute1,70h
              mov attribute2,07h
init0:        cld                  ;now combine the text and attribute bytes
              lea si,buffer_text                 ;point si to table of text
              lea di,window_bytes                ;and di to storage area
              mov cx,112           ;create first four lines by combining
              mov al,attribute1          ;text with attribute1 (112 words)
init1:        movsb                              ;text byte
              stosb                              ;attribute byte
              loop init1                 ;loop until all 112 words are done
              mov cx,11                          ;now do the next 11 lines
init2:        push cx              ;first attribute in each line is attribute1
              movsb
              stosb
              mov cx,26            ;next 26 attributes are attribute2
              mov al,attribute2
init3:        movsb
              stosb
              loop init3
              movsb
              mov al,attribute1     ;and the last in each line is attribute1
              stosb
              pop cx
              loop init2            ;loop until all 11 lines are done
              mov cx,112            ;create the last four lines just like
init4:        movsb                 ;the first four
              stosb
              loop init4
;
;Check the machine ID byte in ROM and if this is a PCjr, then reset the
;cursor and correct the CURSOR_MODE word at 0040:0060.
;
              mov ax,rom                         ;set ds to rom
              mov ds,ax
              assume ds:rom
              cmp machine_id,0FDh                ;is this a PCjr?
              jne init5                          ;no, then skip this routine
              mov ax,rom_bios_data               ;set ds to ROM BIOS data area
              mov ds,ax
              assume ds:rom_bios_data
              mov cursor_mode,0607h          ;reset the cursor mode indicator
              mov ah,1                       ;then physically reset the cursor
              mov cx,0607h
              int 10h
;
;Now save the old keyboard interrupt vector and replace it with the new one.
;
init5:        mov ax,interrupts          ;set ds to the interrupt vector area
              mov ds,ax
              assume ds:interrupts
              mov ax,keyboard_int                ;save old vector
              mov old_keyboard_int,ax
              mov ax,keyboard_int[2]
              mov old_keyboard_int[2],ax
              cli                        ;disable all interrupts but NMI
              mov keyboard_int,offset main       ;and install new vector
              mov keyboard_int[2],cs
              sti                                ;re-enable interrupts
              mov dx,offset initialize   ;point dx to end of resident section
              int 27h                            ;terminate-but-stay-resident
initialize    endp
;
code          ends
              end begin
