;=============================================================================
; SETUP 2.0 allows text and control codes to be sent directly to a printer
; from the command line or from within a popup menu window.  Syntax is:
;
;       SETUP2 [d:][path][filename] | [/C codes] | [/U]
;
; where filename = Name of PMF file
;       /C = Codes to be sent from the command line
;       /U = Uninstall the program
;=============================================================================

code            segment para public 'code'
                assume  cs:code
                org     100h
begin:          jmp     initialize

program         db      "SETUP 2.0 "
copyright       db      "(c) 1989 Ziff Communications Co.",13,10
author          db      "PC Magazine ",254," Jeff Prosise",13,10
hotkey          db      "Hotkey is Ctrl-Rt/Shift",13,10,"$",1Ah

videocols       db      ?                       ;number of columns displayed
videopage       db      ?                       ;active video page
videoseg        dw      ?                       ;video segment
videostart      dw      ?                       ;starting video address
videocursormode dw      ?                       ;cursor description
videocursorpos  dw      ?                       ;BIOS cursor position

pname           db      26 dup (20h)            ;menu window title
lpt_number      dw      0                       ;LPT port number
status          db      0                       ;program status flag
int9h           dd      ?                       ;interrupt 9h vector
window_start    label word
window_x        db      ?                       ;starting window column
window_y        db      1                       ;starting window row
window_end      dw      ?                       ;lower right window corner
border_attr     db      ?                       ;border color
menu_attr       db      ?                       ;window color
hilite_attr     db      ?                       ;highlight color
cursor_def      dw      ?                       ;default cursor definition
index           db      0                       ;menu selection index
ten             db      10                      ;base 10 divisor
linecount       dw      0                       ;number of menu lines
pagecount       db      1                       ;number of menu pages
menupage        db      0                       ;current menu page
code_table      dw      offset menu_table       ;address of control code table
end_offset      dw      offset menu_table       ;ending program address

;=============================================================================
; KBINT receives control when an interrupt 9 is generated.
;=============================================================================
kbint           proc    far
                pushf                           ;call BIOS keyboard routine
                call    int9h
                sti                             ;enable interrupts
                push    ax
                mov     ah,2                    ;get shift key status
                int     16h
                and     al,0Fh                  ;mask off upper four bits
                cmp     al,5                    ;was the hotkey combo pressed?
                pop     ax
                jne     kb_exit                 ;no, then exit
                cmp     status,0                ;check program status
                jne     kb_exit                 ;exit if it's already up
                call    main                    ;pop up the window
kb_exit:        iret                            ;exit
kbint           endp

;=============================================================================
; MAIN is the main body of the program.
;=============================================================================
main            proc    near
                mov     status,1                ;set program status flag
                push    ax                      ;save all registers
                push    bx
                push    cx
                push    dx
                push    bp
                push    si
                push    di
                push    ds
                push    es
                mov     ah,15                   ;abort if the current video
                int     10h                     ;  mode is not a text mode
                cmp     al,7
                je      main1
                cmp     al,4
                jb      main1
;
;Restore register values and return to caller.
;
main_exit:      pop     es                      ;restore registers
                pop     ds
                pop     di
                pop     si
                pop     bp
                pop     dx
                pop     cx
                pop     bx
                pop     ax
                mov     status,0                ;clear program status flag
                ret
;
;Save video parameters and read the cursor address from the CRT controller.
;
main1:          push    cs                      ;establish DS addressability
                pop     ds                      ;  by pointing it to the
                assume  ds:code                 ;  code segment
                cld                             ;clear DF for string ops
                mov     videocols,ah            ;save columns displayed
                mov     videopage,bh            ;save active video page
                mov     ah,3                    ;get cursor information
                int     10h
                mov     videocursormode,cx      ;save cursor mode
                mov     videocursorpos,dx       ;save cursor position
                mov     ax,40h                  ;point ES to the BIOS
                mov     es,ax                   ;  data area
                mov     dx,es:[4Eh]             ;save video start address
                mov     videostart,dx
;
;Define monochrome or color video attributes.
;
                test    byte ptr es:[63h],40h   ;branch if this is
                jnz     colorvideo              ;  a color system
                mov     videoseg,0B000h         ;monochrome attributes
                mov     border_attr,70h
                mov     menu_attr,07h
                mov     hilite_attr,70h
                mov     cursor_def,0C0Dh
                jmp     short vsave
colorvideo:     mov     videoseg,0B800h         ;color attributes
                mov     border_attr,70h
                mov     menu_attr,4Fh
                mov     hilite_attr,70h
                mov     cursor_def,0607h
;
;Save video memory underlying the menu window, then open the window.
;
vsave:          mov     ah,1                    ;hide the cursor
                mov     ch,20h
                int     10h
                mov     cl,videocols            ;determine window position
                sub     cl,32
                mov     window_x,cl             ;save starting column number
                mov     ch,window_y
                mov     dx,cx
                add     dx,101Dh                ;save coordinates of lower
                mov     window_end,dx           ;  right window corner
                push    cs                      ;point ES:DI to screen buffer
                pop     es
                mov     di,offset screen_buffer
                mov     ax,offset udr_vio2mem   ;call SCANREGION routine to
                mov     cx,window_start         ;  buffer the contents of
                mov     dx,window_end           ;  video memory
                call    scanregion
                call    openwindow              ;open printer menu window
                mov     al,hilite_attr          ;draw menu selection bar
                call    drawmenubar
;
;Monitor the keyboard for keystrokes and act upon the ones received.
;
keyloop:        mov     ah,0                    ;get a keystroke
                int     16h
                or      al,al                   ;branch on extended keycodes
                jz      functionkey
;
;Output control codes if ENTER was pressed.
;
                cmp     al,13                   ;check for ENTER keycode
                jne     escape
                call    outputlpt               ;output indexed control codes
                jmp     keyloop                 ;return to input loop
;
;Close the window and exit if ESC was pressed.
;
escape:         cmp     al,27                   ;check for ESC keycode
                jne     slash
                jmp     close                   ;jump to exit routines
;
;Jump down to the input line if the slash key was pressed.
;
slash:          cmp     al,"/"                  ;check for slash character
                jne     keyloop                 ;ignore anything else
                mov     al,menu_attr            ;blank the menu bar
                call    drawmenubar
                call    scanline                ;read input and send to LPT
                mov     al,hilite_attr          ;redraw the menu bar
                call    drawmenubar
                jmp     keyloop                 ;loop back for more
;
;Send the corresponding control codes if a function key was pressed.
;
functionkey:    cmp     ah,59                   ;determine whether or not a
                jb      keyloop                 ;  function key was pressed
                cmp     ah,68
                ja      up_arrow
                push    ax                      ;save extended keycode
                mov     al,menu_attr            ;erase the menu bar
                call    drawmenubar
                pop     ax                      ;recover keycode
                sub     ah,59                   ;calculate new INDEX value
                mov     index,ah
                mov     al,hilite_attr          ;draw new menu bar
                call    drawmenubar
                call    outputlpt               ;output corresponding codes
                jmp     keyloop                 ;return to input loop
;
;Move the menu bar up one line if up-arrow was pressed.
;
up_arrow:       cmp     ah,72                   ;check for up-arrow
                jne     dn_arrow
                mov     al,menu_attr            ;erase the menu bar
                call    drawmenubar
                dec     index                   ;decrement INDEX and wrap
                jns     newbar                  ;  if necessary
                mov     index,9
newbar:         mov     al,hilite_attr          ;redraw the menu bar
                call    drawmenubar
                jmp     keyloop                 ;return to input loop
;
;Move the menu bar down one line if down-arrow was pressed.
;
dn_arrow:       cmp     ah,80                   ;check for down-arrow
                jne     pgup
                mov     al,menu_attr            ;erase the menu bar
                call    drawmenubar
                inc     index                   ;increment INDEX and wrap
                cmp     index,10                ;  around if necessary
                jne     newbar                  ;jump to finish up
                mov     index,0
                jmp     newbar
;
;Flip to the preceding menu page if PgUp was pressed.
;
pgup:           cmp     ah,73                   ;check for PgUp
                jne     pgdn
                cmp     pagecount,1             ;ignore if there is only
                je      retkey                  ;  one menu
                dec     menupage                ;set MENUPAGE to previous
                jns     newpage                 ;  page and wrap around
                mov     al,pagecount            ;  if necessary
                dec     al
                mov     menupage,al
newpage:        call    writemenu               ;write text of page to memory
                mov     al,hilite_attr          ;redraw the menu bar
                call    drawmenubar
retkey:         jmp     keyloop                 ;return to input loop
;
;Flip to the next page if PgDn was pressed.
;
pgdn:           cmp     ah,81                   ;check for PgDn
                jne     retkey
                cmp     pagecount,1             ;ignore if there is only
                je      retkey                  ;  one menu
                inc     menupage                ;adjust MENUPAGE
                mov     al,pagecount
                cmp     menupage,al
                jne     newpage                 ;jump to finish up
                mov     menupage,0
                jmp     newpage
;
;Close the menu window.
;
close:          mov     si,offset screen_buffer ;point SI to buffer
                mov     ax,offset udr_mem2vio   ;call SCANREGION routine to
                mov     cx,window_start         ;  restore the contents of
                mov     dx,window_end           ;  video memory
                call    scanregion
;
;Restore the cursor mode and position, then exit.
;
restore:        mov     ah,2                    ;restore position
                mov     bh,videopage
                mov     dx,videocursorpos
                int     10h
                mov     ah,1                    ;restore mode
                mov     cx,videocursormode
                int     10h
                jmp     main_exit               ;return from interrupt
main            endp

;-----------------------------------------------------------------------------
; SCANREGION scans the cursor over the indicated region and calls a
; user-defined routine at each stop.
;   Entry:  AX    - offset address of user-defined routine
;           CH,CL - upper left corner of region
;           DH,DL - lower right corner of region
;-----------------------------------------------------------------------------
addr_udr        dw      ?                       ;routine address
columns         dw      0                       ;number of columns in region

scanregion      proc    near
                mov     addr_udr,ax             ;save address
                push    cx                      ;save starting cursor position
                sub     dl,cl                   ;calculate number of columns
                inc     dl
                mov     byte ptr columns,dl
                sub     dh,ch                   ;calculate number of rows
                inc     dh
                mov     cl,dh
                xor     ch,ch
                pop     dx                      ;retrieve cursor position
                mov     bh,videopage            ;page number in BH
scanreg1:       push    cx                      ;save row counter
                push    dx                      ;save cursor position
                mov     cx,columns              ;load column counter
scanreg2:       mov     ah,2                    ;position cursor
                int     10h
                call    word ptr cs:[addr_udr]  ;call user-defined
                inc     dl                      ;  routine and
                loop    scanreg2                ;  advance the cursor
                pop     dx                      ;advance cursor to next row
                inc     dh                      ;  and loop until all rows
                pop     cx                      ;  are done
                loop    scanreg1
                ret
scanregion      endp

;-----------------------------------------------------------------------------
; UDR_VIO2MEM is called by SCANREGION to save the contents of a screen area.
;   Entry:  ES:DI - buffer address
;-----------------------------------------------------------------------------
udr_vio2mem     proc    near
                mov     ah,8                    ;get character and attribute
                int     10h                     ;  under the cursor
                stosw                           ;buffer them
                ret
udr_vio2mem     endp

;-----------------------------------------------------------------------------
; UDR_MEM2VIO is called by SCANREGION to write to a screen area.
;   Entry:  DS:SI - buffer address
;-----------------------------------------------------------------------------
udr_mem2vio     proc    near
                push    cx                      ;save CX
                lodsw                           ;get character and attribute
                mov     bl,ah                   ;attribute to BL
                mov     ah,9                    ;write character/attribute
                mov     cx,1
                int     10h
                pop     cx                      ;restore CX and exit
                ret
udr_mem2vio     endp

;-----------------------------------------------------------------------------
; OPENWINDOW displays the printer control window.
;-----------------------------------------------------------------------------
ftext           db      "1 2 3 4 5 6 7 8 9 10"

openwindow      proc    near
                mov     ax,0600h                ;blank window region
                mov     bh,menu_attr
                mov     cx,window_start
                mov     dx,window_end
                int     10h
                mov     cx,window_start         ;draw box around region to
                mov     dx,window_end           ;  form window border
                mov     bl,border_attr
                call    textbox
                mov     dx,window_start         ;display window title
                add     dx,0102h
                push    dx
                mov     si,offset pname
                mov     cx,26
                call    writeln
                pop     dx
                inc     dh
                push    dx                      ;draw first horizontal
                mov     cx,26                   ;  divider
                mov     bl,menu_attr
                call    drawhorizontal
                pop     dx
                inc     dh
                mov     cx,10                   ;draw "F1".."F10" designators
                mov     si,offset ftext
open1:          push    cx
                push    dx
                mov     ah,2
                int     10h
                mov     ax,0E46h
                int     10h
                inc     dl
                mov     cx,2
                call    writeln
                pop     dx
                inc     dh
                pop     cx
                loop    open1
                push    dx                      ;draw second horizontal
                mov     cx,26                   ;  divider
                mov     bl,menu_attr
                call    drawhorizontal
                pop     dx
                inc     dh
                push    dx                      ;draw input line
                mov     al,hilite_attr
                mov     cx,26
                call    writeattr
                pop     dx
                inc     dh
                mov     si,offset menutxt       ;display menu line
                mov     cx,24
                call    writeln
                call    writemenu               ;display current menu
                ret
openwindow      endp

;-----------------------------------------------------------------------------
; WRITELN displays a text string of known length.
;   Entry:  DS:SI - string address
;           DH,DL - starting row and column
;           CX    - string length
;-----------------------------------------------------------------------------
writeln         proc    near
                mov     ah,2                    ;position cursor
                mov     bh,videopage
                int     10h
wln1:           lodsb                           ;get a byte
                mov     ah,0Eh                  ;display it and advance the
                int     10h                     ;  cursor one column
                loop    wln1                    ;loop until done
                ret
writeln         endp

;-----------------------------------------------------------------------------
; TEXTBOX draws a box of specified color around a region.
;   Entry:  CH,CL - upper left corner row and column
;           DH,DL - lower right corner row and column
;           BL    - attribute
;-----------------------------------------------------------------------------
wide            dw      0                       ;region width
height          dw      0                       ;region height
upleft          dw      ?                       ;upper left row and column

textbox         proc    near
                mov     upleft,cx               ;save coordinates
                sub     dh,ch                   ;calculate height
                dec     dh
                mov     byte ptr height,dh
                sub     dl,cl                   ;calculate width
                dec     dl
                mov     byte ptr wide,dl
                mov     bh,videopage            ;set BH for video calls
                mov     ah,2                    ;draw upper left corner
                mov     dx,upleft
                int     10h
                mov     ax,09DAh
                mov     cx,1
                int     10h
                inc     dl                      ;draw top horizontal line
                mov     cx,wide
                call    drawhorizontal
                mov     ah,2                    ;draw upper right corner
                add     dl,byte ptr wide
                int     10h
                mov     ax,09BFh
                mov     cx,1
                int     10h
                mov     ah,2                    ;draw lower left corner
                mov     dx,upleft
                add     dh,byte ptr height
                inc     dh
                int     10h
                mov     ax,09C0h
                mov     cx,1
                int     10h
                inc     dl                      ;draw bottom horizontal line
                mov     cx,wide
                call    drawhorizontal
                mov     ah,2                    ;draw lower right corner
                add     dl,byte ptr wide
                int     10h
                mov     ax,09D9h
                mov     cx,1
                int     10h
                mov     dx,upleft               ;draw left vertical
                inc     dh
                push    dx
                mov     cx,height
                call    drawvertical
                pop     dx
                add     dl,byte ptr wide        ;draw right vertical
                inc     dl
                mov     cx,height
                call    drawvertical
                ret
textbox         endp

;-----------------------------------------------------------------------------
; DRAWHORIZONTAL draws a horizontal line of specified length.
;   Entry:  DH,DL - starting row and column
;           CX    - length
;           BL    - attribute
;-----------------------------------------------------------------------------
drawhorizontal  proc    near
                mov     ah,2                    ;set cursor position
                mov     bh,videopage
                int     10h
                mov     ax,09C4h                ;draw line
                int     10h
                ret
drawhorizontal  endp

;-----------------------------------------------------------------------------
; DRAWVERTICAL draws a vertical line of specified length.
;   Entry:  DH,DL - starting row and column
;           CX    - length
;           BL    - attribute
;-----------------------------------------------------------------------------
drawvertical    proc    near
                mov     bh,videopage
vdraw1:         push    cx
                mov     ah,2                    ;position cursor
                int     10h
                mov     ax,09B3h                ;draw one character
                mov     cx,1
                int     10h
                inc     dh                      ;move cursor to next line
                pop     cx
                loop    vdraw1                  ;loop until done
                ret
drawvertical    endp

;-----------------------------------------------------------------------------
; WRITEMENU displays a page of menu text.
;   Entry:  MENUPAGE - menu page number
;-----------------------------------------------------------------------------
writemenu       proc    near
                mov     al,menupage             ;point SI to menu text
                mov     bl,200
                mul     bl
                mov     si,ax
                add     si,offset menu_table
                mov     ax,0600h                ;blank current menu
                mov     bh,menu_attr
                mov     cx,window_start
                add     cx,0308h
                push    cx
                mov     dx,cx
                add     dx,0913h
                int     10h
                pop     dx
                mov     cx,10                   ;determine how many lines are
                mov     al,pagecount            ;  to be written
                dec     al
                cmp     al,menupage
                jne     wmloop
                mul     ten
                mov     cx,linecount
                sub     cx,ax
                jcxz    nomenu
wmloop:         push    cx                      ;save line counter
                push    dx                      ;save cursor position
                mov     cx,20                   ;display one line of menu text
                call    writeln
                pop     dx                      ;advance cursor to next line
                inc     dh
                pop     cx
                loop    wmloop                  ;loop until done
nomenu:         ret
writemenu       endp

;-----------------------------------------------------------------------------
; WRITEATTR writes a series of attribute bytes to video memory.  Writing is
; performed during the vertical retrace on color adapters.
;   Entry:  AL    - attribute
;           CX    - number of attributes to write
;           DH,DL - starting row and column
;-----------------------------------------------------------------------------
writeattr       proc    near
                push    ax                      ;save attribute
                mov     al,videocols            ;calculate address of the
                mul     dh                      ;  indicated character cell
                xor     dh,dh                   ;  in video memory
                add     ax,dx
                shl     ax,1
                add     ax,videostart
                mov     di,ax
                inc     di
                mov     es,videoseg             ;point ES to video memory
                cmp     videoseg,0B000h         ;don't wait if display
                je      nowait                  ;  is monochrome
                mov     dx,3DAh                 ;address CRTC status register
wait_for_vert:  in      al,dx                   ;wait for vertical retrace
                test    al,8
                jz      wait_for_vert
nowait:         pop     ax                      ;retrieve attribute
attrloop:       stosb                           ;write attributes
                inc     di
                loop    attrloop
                ret
writeattr       endp

;-----------------------------------------------------------------------------
; DRAWMENUBAR draws or erases the menu selection bar.
;   Entry:  AL    - attribute
;           INDEX - selection index number
;-----------------------------------------------------------------------------
drawmenubar     proc    near
                mov     dx,window_start         ;calculate starting row and
                add     dh,index                ;  column
                add     dx,0302h
                mov     cx,26                   ;26 character cells to alter
                call    writeattr               ;write the attribute bytes
                ret
drawmenubar     endp

;-----------------------------------------------------------------------------
; SCANLINE reads an input string from the keyboard, converts ASCII numbers
; to binary, and sends them to the designated printer port.
;-----------------------------------------------------------------------------
input_pos       dw      ?                       ;row and column of input line

scanline        proc    near
                push    cs                      ;point ES:DI to input buffer
                pop     es
                mov     di,offset input_buffer
                mov     dx,window_start         ;position the cursor
                add     dx,0E02h
                mov     input_pos,dx            ;save coordinates
                mov     ah,2
                mov     bh,videopage
                int     10h
                mov     ah,1                    ;unblank the cursor
                mov     cx,cursor_def
                int     10h
                xor     cl,cl                   ;zero character count
;
;Read and buffer keystrokes until ENTER or ESC is pressed.
;
scanloop:       mov     ah,0                    ;wait for a keystroke
                int     16h
                cmp     al,8                    ;check for backspace
                jne     scan1
                or      cl,cl                   ;ignore backspace if count
                jz      scanloop                ;  is zero
                call    backspace               ;perform backspace
                jmp     scanloop                ;return to input loop
scan1:          cmp     al,13                   ;branch if ENTER pressed
                je      endscan
                cmp     al,27                   ;branch if ESC pressed
                je      esckey
                cmp     al,32                   ;ignore control codes
                jb      scanloop
                cmp     cl,255                  ;ignore the character if
                je      scanloop                ;  input buffer is full
                call    insert_char             ;buffer the character
                jmp     scanloop                ;return to input loop
;
;Parse the text just entered and output binary codes to the printer.
;
endscan:        mov     es:[di],al              ;buffer EOL character
                push    bx                      ;save registers
                push    cx
                push    di
                mov     si,offset input_buffer  ;point SI to text
                mov     di,offset output_buffer ;point DI to output buffer
                call    asc2bin                 ;convert ASCII to binary
                jnc     checkstat               ;continue if no error
                mov     si,offset errtxt1       ;display error message if
scan2:          call    win_error               ;  code string is invalid
                pop     di                      ;restore registers
                pop     cx
                pop     bx
                jmp     scanloop                ;return to input loop
checkstat:      call    lptstatus               ;check printer status
                jnc     scan3                   ;continue if printer ready
                mov     si,offset errtxt2       ;display error message
                jmp     scan2
scan3:          mov     si,offset output_buffer ;point SI to codes
                call    sendlptcodes            ;output them
                add     sp,6                    ;clean up the stack
;
;Clear the input line and exit.
;
esckey:         mov     ah,1                    ;blank the cursor
                mov     ch,20h
                int     10h
                mov     ah,2                    ;position cursor on input line
                mov     dx,input_pos
                mov     bh,videopage
                int     10h
                mov     ax,0A20h                ;blank the input line
                mov     cx,25
                int     10h
                ret                             ;and exit
scanline        endp

;-----------------------------------------------------------------------------
; BACKSPACE deletes the character left of the cursor.
;-----------------------------------------------------------------------------
backspace       proc    near
                dec     cl                      ;decrement character count
                dec     di                      ;decrement buffer pointer
                cmp     cl,24                   ;branch if count > 24
                ja      refresh
                push    cx                      ;save character count
                mov     ah,0Eh                  ;move the cursor back one
                int     10h                     ;  space
                mov     ax,0A20h                ;overwrite character under the
                mov     cx,1                    ;  cursor with a space
                int     10h
                pop     cx                      ;restore count
                ret
refresh:        push    cx                      ;save character count
                mov     si,di                   ;scroll the input line one
                sub     si,25                   ;  character to the right to
                mov     cx,25                   ;  blank the last character
                mov     dx,input_pos
                call    writeln
                pop     cx                      ;restore count
                ret
backspace       endp

;-----------------------------------------------------------------------------
; INSERT_CHAR inserts a character into the input buffer.
;-----------------------------------------------------------------------------
insert_char     proc    near
                stosb                           ;buffer the character
                inc     cl                      ;increment character count
                cmp     cl,25                   ;branch if count > 25
                ja      refresh
                mov     ah,0Eh                  ;display the character
                int     10h
                ret
insert_char     endp

;-----------------------------------------------------------------------------
; ASC2BIN converts an ASCII text string into a string of binary bytes.
;   Entry:  DS:SI - text string address
;           ES:DI - binary buffer address
;   Exit:   CF clear = no error, CF set = error
;           CL - number of codes converted
;-----------------------------------------------------------------------------
asc2bin         proc    near
                push    di                      ;save count byte address
                inc     di                      ;advance past count byte
                xor     cl,cl                   ;zero count
                xor     bh,bh                   ;zero BH
a2bloop:        mov     dx,0A00h                ;initialize DH (base) and
                                                ;  DL (accumulator)
;
;Search until the first character of an entry or EOL marker is encountered.
;
nextchar:       lodsb                           ;get a character
                call    check_20_2c             ;skip spaces, commas, and tabs
                jz      nextchar
                cmp     al,0Dh                  ;done if EOL encountered
                je      endconvert
                cmp     al,"/"                  ;done if slash encountered
                je      endconvert
                call    check_22_27             ;check for " or ' mark
                jnz     isdigit                 ;branch if neither
;
;Buffer everything between quotation marks or apostrophes.
;
inquotes:       lodsb                           ;get next character
                call    check_22_27             ;exit loop if ending quote
                jz      nextchar                ;  symbol is encountered
                cmp     al,0Dh                  ;exit if line terminator is
                je      endconvert              ;  is encountered
                stosb                           ;buffer the character
                inc     cl                      ;increment count
                jz      a2berror                ;error if count > 255
                jmp     inquotes                ;loop back for more
;
;Convert a numeric entry to binary.
;
digitloop:      lodsb                           ;get next character
                call    check_20_2c             ;check for delimiter marking
                jz      endnumtext              ;  end of the current number
                call    check_22_27
                jz      endnumtext
                cmp     al,0Dh                  ;check for end-of-line
                je      endnumtext
isdigit:        cmp     al,30h                  ;digit is valid if it lies
                jb      a2berror                ;  between "0" and "9"
                cmp     al,3Ah
                jb      ascii_adjust
                and     al,0DFh                 ;capitalize presumed alpha
                cmp     al,"X"                  ;change base to 16 for hex
                jne     check_hex               ;  numbers if "X" is found
                mov     dh,16
                jmp     digitloop
check_hex:      cmp     dh,16                   ;error if this isn't base 16
                jne     a2berror
                cmp     al,41h                  ;digit is valid if it lies
                jb      a2berror                ;  between "A" and "F"
                cmp     al,46h
                ja      a2berror
                sub     al,7                    ;hex conversion
ascii_adjust:   sub     al,30h                  ;ASCII to binary conversion
                mov     bl,al                   ;save digit
                mov     al,dl                   ;multiply accumulator by base
                mul     dh
                add     ax,bx                   ;add new digit to result
                or      ah,ah                   ;error if result > 255
                jnz     a2berror
                mov     dl,al                   ;put new value in accumulator
                jmp     digitloop               ;get next digit
a2berror:       pop     bx                      ;clean up the stack
                xor     cl,cl                   ;zero CX on error
                mov     es:[bx],cl              ;store zero in buffer
                stc                             ;set CF for error
                ret
endnumtext:     mov     al,dl                   ;write accumulator value to
                stosb                           ;  binary buffer
                inc     cl                      ;increment count
                jz      a2berror                ;error if count > 255
                dec     si                      ;point SI to terminator
                jmp     a2bloop                 ;search for next number
;
;Do housekeeping and exit.
;
endconvert:     pop     bx                      ;recover count byte address
                mov     es:[bx],cl              ;store count in buffer
                clc                             ;clear CF for exit
                ret
asc2bin         endp

;-----------------------------------------------------------------------------
; CHECK_20_2C returns a flag if AL holds an ASCII space, comma, or tab.
;   Entry:  AL - character
;   Exit:   ZF clear = no match, ZF set = space or comma
;-----------------------------------------------------------------------------
check_20_2c     proc    near
                cmp     al,20h                  ;check for ASCII space
                je      ischar1
                cmp     al,2Ch                  ;check for ASCII comma
                je      ischar1
                cmp     al,9                    ;check for ASCII tab
ischar1:        ret
check_20_2c     endp

;-----------------------------------------------------------------------------
; CHECK_22_27 returns a flag if AL holds an ASCII quote mark or apostrophe.
;   Entry:  AL - character
;   Exit:   ZF clear = no match, ZF set = space or comma
;-----------------------------------------------------------------------------
check_22_27     proc    near
                cmp     al,22h                  ;check for quotation mark
                je      ischar2
                cmp     al,27h                  ;check for apostrophe
ischar2:        ret
check_22_27     endp

;-----------------------------------------------------------------------------
; LPTSTATUS checks the status of the indicated printer.
;   Entry:  LPT_NUMBER - printer number (0-2)
;   Exit:   CF clear = printer ready, CF set = printer not ready
;-----------------------------------------------------------------------------
lptstatus       proc    near
                mov     ah,2                    ;get status byte from BIOS
                mov     dx,lpt_number
                int     17h
                test    ah,29h                  ;check bits 0, 3, and 5
                jnz     notready                ;error if one is set
                test    ah,0F9h                 ;check all but bits 1 and 2
                jz      notready                ;error if all are zero
                clc                             ;clear CF for return
                ret
notready:       stc                             ;set CF for return
                ret
lptstatus       endp

;-----------------------------------------------------------------------------
; SENDLPTCODES sends a string of bytes to the designated printer.
;   Entry:  DS:SI      - string (first byte = length)
;           LPT_NUMBER - printer number (0-2)
;-----------------------------------------------------------------------------
sendlptcodes    proc    near
                lodsb                           ;get string length in CX
                mov     cl,al
                xor     ch,ch
                jcxz    nosend                  ;exit if length is zero
                mov     dx,lpt_number           ;initialize DX
sendloop:       lodsb                           ;loop until all bytes are
                mov     ah,0                    ;  output
                int     17h
                loop    sendloop
nosend:         ret
sendlptcodes    endp

;-----------------------------------------------------------------------------
; OUTPUTLPT sends the string of control codes indexed by INDEX and MENUPAGE
; to the printer designated by LPT_NUMBER.
;-----------------------------------------------------------------------------
outputlpt       proc    near
                call    lptstatus               ;check printer status
                jnc     isready
                mov     si,offset errtxt2       ;error if printer is not
                call    win_error               ;  ready
no_output:      ret
isready:        mov     al,menupage             ;calculate offset address
                mul     ten                     ;  of control code string
                add     al,index                ;  from MENUPAGE and INDEX
                cmp     ax,linecount            ;make sure a control code
                jae     no_output               ;  string is defined
                mov     cx,ax
                mov     si,code_table           ;find offset address of the
                jcxz    nocalc                  ;  corresponding code string
outloop:        mov     al,[si]                 ;  by scanning the linked
                inc     al                      ;  list of control codes
                add     si,ax
                loop    outloop
nocalc:         call    sendlptcodes            ;output the code string
                ret
outputlpt       endp

;-----------------------------------------------------------------------------
; WIN_ERROR displays an error message and waits for a keystroke.
;   Entry:  DS:SI - text address
;-----------------------------------------------------------------------------
menutxt         db      "Esc Enter /",20h,18h,19h,20h,"PgUp PgDn"
errtxt1         db      "Error: Invalid entry",4 dup (20h)
errtxt2         db      "Error: Printer not ready"

win_error       proc    near
                call    disp_err_msg            ;display error message
                mov     ax,0E07h                ;beep
                int     10h
win1:           mov     ah,1                    ;loop until a key is
                int     16h                     ;  pressed
                jz      win1
                mov     si,offset menutxt       ;restore original text
                call    disp_err_msg
                ret
win_error       endp

;-----------------------------------------------------------------------------
; DISP_ERR_MSG displays a window error message.
;   Entry:  DS:SI - text address
;-----------------------------------------------------------------------------
disp_err_msg    proc    near
                mov     ah,3                    ;get cursor position
                mov     bh,videopage
                int     10h
                push    dx                      ;save it
                mov     dx,window_start         ;display error message
                add     dx,0F02h
                mov     cx,24
                call    writeln
                mov     ah,2                    ;restore cursor position
                pop     dx
                int     10h
                ret
disp_err_msg    endp

;=============================================================================
; INITIALIZE handles command line directives and installation/deinstallation.
;=============================================================================
errmsg1         db      "Usage: SETUP2 [d:][path][filename] | [/C codes] | [/U]$"
errmsg2         db      "Already installed$"
errmsg3         db      "Cannot uninstall$"
errmsg4         db      "Not installed$"
errmsg6         db      "Invalid code string$"
errmsg7         db      "Printer not ready$"
errmsg8         db      "File not found$"
errmsg9         db      "File too large$"
errmsg10        db      "Insufficient memory$"
errmsg11        db      "Make file error - line $"
initmsg1        db      "Uninstalled$"
installed       db      0

initialize      proc    near
                assume  cs:code, ds:code
;
;See if a copy is already resident in memory.
;
                cld                             ;clear DF
                mov     word ptr [begin],0      ;initialize fingerprint
                xor     bx,bx                   ;zero BX for start
                mov     ax,cs                   ;keep CS value in AX
init1:          inc     bx                      ;increment search segment value
                mov     es,bx
                cmp     ax,bx                   ;not installed if current
                je      parse                   ;  segment is looped back to
                mov     si,offset begin         ;search this segment for ASCII
                mov     di,si                   ;  fingerprint
                mov     cx,16
                repe    cmpsb
                jne     init1                   ;loop back if not found
                mov     installed,1             ;set installed flag
;
;Parse the command line for entries and take appropriate action.
;
parse:          mov     si,81h                  ;point SI to command line
parseloop:      lodsb                           ;get a character
                cmp     al,20h                  ;skip it if it's a space
                je      parseloop
                cmp     al,0Dh                  ;exit loop when a carriage
                jne     init2                   ;  return is encountered
                jmp     install
init2:          cmp     al,"/"                  ;check for "/" modifier
                je      init3                   ;branch if found
                mov     dx,offset errmsg2       ;initialize error pointer
                cmp     installed,0             ;check installed flag
                jne     error_exit              ;error if already installed
                dec     si                      ;point SI to filename
                call    make                    ;process printer make file
                jmp     setvec                  ;install
init3:          lodsb                           ;get parameter
                and     al,0DFh                 ;capitalize
                cmp     al,"U"                  ;branch to handling routine
                je      uninstall
                cmp     al,"C"
                je      readcodes
;
;An error was encountered in parsing.  Display error message and exit.
;
syntax_error:   mov     dx,offset errmsg1       ;display error message
error_exit:     mov     ah,9
                int     21h
                mov     ax,4C01h                ;exit with ERRORLEVEL = 1
                int     21h
;
;Process a /U command line parameter.
;
uninstall:      mov     dx,offset errmsg4       ;error if not installed
                cmp     installed,1
                jne     error_exit
                call    remove                  ;call uninstall routine
                mov     dx,offset errmsg3       ;exit on error
                jc      error_exit
                mov     dx,offset initmsg1      ;display "Uninstalled"
                mov     ah,9                    ;  message
                int     21h
                mov     ax,4C00h                ;exit with ERRORLEVEL = 0
                int     21h
;
;Process a /C command line parameter.
;
readcodes:      push    cs                      ;set ES to code segment
                pop     es
                mov     di,offset menu_table    ;point DI to scratch area
                call    asc2bin                 ;convert text to binary
                mov     dx,offset errmsg6       ;exit on conversion error
                jc      error_exit
                or      cl,cl                   ;exit if no codes were entered
                jz      error_exit
                call    lptstatus               ;check printer status
                mov     dx,offset errmsg7
                jc      error_exit              ;error if printer not ready
                mov     si,offset menu_table    ;point SI to output buffer
                call    sendlptcodes            ;output control codes
read_exit:      mov     ax,4C00h                ;exit with ERRORLEVEL = 0
                int     21h
;
;Make sure a copy isn't already loaded before installing this one.
;
install:        mov     dx,offset errmsg2       ;initialize error pointer
                cmp     installed,0             ;check installed flag
                jne     error_exit              ;error if already installed
;
;Reset the interrupt 9h vector to an internal handler.
;
setvec:         mov     ax,3509h                ;save old vector
                int     21h
                mov     word ptr int9h,bx
                mov     word ptr int9h[2],es
                mov     ax,2509h                ;then set the new vector
                mov     dx,offset kbint
                int     21h
;
;Deallocate the program's environment block.
;
                mov     ax,ds:[2Ch]             ;get environment segment
                mov     es,ax
                mov     ah,49h                  ;free it
                int     21h
;
;Display copyright notice, then terminate and remain resident in memory.
;
                mov     ah,9                    ;display message
                mov     dx,offset program
                int     21h
                mov     ax,3100h                ;terminate with ERRORLEVEL = 0
                mov     dx,end_offset           ;calculate amount of memory
                sub     dx,offset code - 15     ;  to reserve in DX
                mov     cl,4                    ;convert bytes to paragraphs
                shr     dx,cl
                int     21h                     ;terminate
initialize      endp

;-----------------------------------------------------------------------------
; REMOVE deallocates the memory block addressed by ES and restores the
; interrupt vector displaced on installation.
;   Entry:  ES - segment to release
;   Exit:   CF clear - program uninstalled
;           CF set   - can't uninstall
;-----------------------------------------------------------------------------
remove          proc    near
                mov     cx,es                   ;abort if the interrupt 9
                mov     ax,3509h                ;  vector has been altered
                int     21h                     ;  since installation
                mov     ax,es
                cmp     ax,cx
                jne     remove_error
                mov     es,cx
                mov     ah,49h                  ;free memory given to
                int     21h                     ;  original program block
                jc      remove_error            ;branch on error
                push    ds                      ;restore interrupt vector
                assume  ds:nothing
                mov     ax,2509h
                lds     dx,es:[int9h]
                int     21h
                pop     ds
                assume  ds:code
                not     word ptr es:[begin]     ;destroy ASCII fingerprint
                clc                             ;clear CF for exit
                ret
remove_error:   stc                             ;set CF to indicate program
                ret                             ;  couldn't be uninstalled
remove          endp

;=============================================================================
; MAKE reads a make file and builds the program's menu and code tables.
;=============================================================================
handle          dw      ?                       ;file handle
filesize        dw      ?                       ;file size in bytes
fileseg         dw      ?                       ;file buffer segment

make            proc    near
                mov     dx,si                   ;save address of first char
make1:          lodsb                           ;find end of filename
                cmp     al,0Dh
                je      make2
                cmp     al,20h
                jne     make1
make2:          mov     byte ptr [si-1],0       ;convert filename to ASCIIZ
;
;Open the printer make file and read it into memory.
;
                mov     ax,3D00h                ;open the make file
                int     21h
                mov     dx,offset errmsg8       ;error if call failed
                jc      make_error2
                mov     handle,ax               ;save file handle
                mov     bx,ax                   ;determine the file's size
                mov     ax,4202h                ;  in bytes
                xor     cx,cx
                xor     dx,dx
                int     21h
                or      dx,dx                   ;error if greater than 64K
                jz      make3
make_error1:    mov     dx,offset errmsg9
make_error2:    jmp     error_exit
make3:          cmp     ax,0FFD0H
                ja      make_error1
                mov     filesize,ax             ;save file size
                mov     ah,4Ah                  ;shrink memory allocation
                mov     bx,1000h                ;  to 64K
                push    cs
                pop     es
                int     21h
                mov     dx,offset errmsg10      ;insufficient memory if
                jc      make_error2             ;  call failed
                mov     ah,48h                  ;request enough additional
                mov     bx,filesize             ;  memory to read in the
                add     bx,18                   ;  make file
                mov     cl,4
                shr     bx,cl
                int     21h
                mov     dx,offset errmsg10      ;insufficient memory if
                jc      make_error2             ;  call failed
                mov     fileseg,ax              ;save segment address
                mov     ax,4200h                ;reset file pointer
                mov     bx,handle
                xor     cx,cx
                xor     dx,dx
                int     21h
                mov     ah,3Fh                  ;read make file into the
                mov     bx,handle               ;  portion of memory just
                mov     cx,filesize             ;  allocated
                xor     dx,dx
                assume  ds:nothing
                mov     ds,fileseg
                int     21h
                mov     ah,3Eh                  ;close the file
                mov     bx,handle
                int     21h
                mov     bx,filesize             ;find the end of the file
                cmp     byte ptr [bx-1],1Ah     ;delete last character if it
                jne     make4                   ;  is a Ctrl-Z
                dec     bx
make4:          cmp     word ptr [bx-2],0A0Dh   ;add a carriage return/
                je      make5                   ;  line feed pair if the
                mov     word ptr [bx],0A0Dh     ;  file doesn't already
                add     bx,2                    ;  end with one
make5:          mov     byte ptr [bx],0         ;write terminating zero
;
;Copy the printer name from the printer make file.
;
                push    cs                      ;initialize DS:SI to address
                pop     es                      ;  file buffer and ES:DI to
                xor     si,si                   ;  address printer name
                mov     di,offset pname         ;  string
pname1:         call    skip_comment            ;skip comment lines
pname2:         cmp     byte ptr [si],0         ;exit if EOF is reached
                jne     pname3
                jmp     make_exit
pname3:         mov     cx,26
pname4:         lodsb                           ;get a character
                cmp     al,0Dh                  ;quit now if the character is
                jne     pname5                  ;  a carriage return
                inc     si
                jmp     short extract_text
pname5:         stosb                           ;otherwise copy the character
                loop    pname4                  ;copy up to 26 characters
                call    nextline                ;advance to start of next line
;
;Extract the menu text entries from the printer make file.
;
extract_text:   mov     di,offset menu_table    ;initialize DI
                push    si                      ;save starting address
text1:          call    skip_comment            ;skip comment lines
text2:          cmp     byte ptr [si],0         ;exit if EOF is reached
                je      extract_codes
                mov     cx,20
text3:          lodsb                           ;get a character
                cmp     al,0Dh                  ;finish this line and proceed
                je      text5                   ;  to the next if a semicolon
                cmp     al,";"                  ;  or carriage return is
                je      text4                   ;  encountered
                stosb                           ;copy the character
                loop    text3                   ;loop back for another
text4:          call    add_spaces              ;pad text with spaces
                inc     linecount               ;register the entry
                call    nextline                ;find start of next line
                jmp     text1
text5:          call    add_spaces              ;pad text with spaces
                inc     linecount               ;register the entry
                inc     si                      ;advance SI to start of
                jmp     text1                   ;  next line
;
;Extract the control codes from the printer make file.
;
extract_codes:  mov     code_table,di           ;save code table address
                pop     si                      ;point SI to first line
                mov     cx,linecount            ;initialize line counter
                jcxz    calc_pages              ;skip if no lines
codes1:         call    skip_comment            ;skip comment lines
codes2:         lodsb                           ;get a character
                cmp     al,0Dh                  ;error if carriage return is
                je      code_error              ;  found before semicolon
                cmp     al,";"                  ;loop back for another if this
                jne     codes2                  ;  one isn't a semicolon
                push    cx                      ;if a semicolon was found, call
                call    asc2bin                 ;  ASC2BIN to convert the text
                pop     cx                      ;  string that follows to
                jc      code_error              ;  to binary
                inc     si                      ;advance SI to next line
                loop    codes1                  ;loop until all lines are done
;
;Calculate the number of menu pages.
;
calc_pages:     push    cs                      ;restore DS
                pop     ds
                assume  ds:code
                mov     end_offset,di           ;save ending address
                mov     ax,linecount            ;exit now if there are
                or      ax,ax                   ;  no entries
                jz      make_exit
                dec     ax                      ;calculate number of menu
                div     ten                     ;  pages
                inc     al
                mov     pagecount,al            ;store count in PAGECOUNT
make_exit:      mov     ah,49h                  ;free memory allocated for
                mov     es,fileseg              ;  the file buffer
                int     21h
                ret
;
;Display the line number where a make error occurred, then exit.
;
code_error:     push    cs                      ;reset DS
                pop     ds
                mov     ah,9                    ;display error message
                mov     dx,offset errmsg11
                int     21h
                mov     ax,linecount            ;display line number
                sub     ax,cx
                inc     ax
                call    bin2asc
                mov     ax,4C01h                ;exit with ERRORLEVEL = 1
                int     21h
make            endp

;-----------------------------------------------------------------------------
; BIN2ASC converts a binary value in AX to ASCII and displays it.
;-----------------------------------------------------------------------------
bin2asc         proc    near
                mov     bx,10                   ;initialize divisor word and
                xor     cx,cx                   ;  digit counter
b2a1:           inc     cx                      ;increment digit count
                xor     dx,dx                   ;divide by 10
                div     bx
                push    dx                      ;save remainder on stack
                or      ax,ax                   ;loop until quotient is zero
                jnz     b2a1
b2a2:           pop     dx                      ;retrieve a digit from stack
                add     dl,30h                  ;convert it to ASCII
                mov     ah,2                    ;display it
                int     21h
                loop    b2a2                    ;loop until done
                ret
bin2asc         endp

;-----------------------------------------------------------------------------
; ADD_SPACES pads a menu text string with space characters.
;-----------------------------------------------------------------------------
add_spaces      proc    near
                jcxz    add_exit
                mov     al,20h
                rep     stosb
add_exit:       ret
add_spaces      endp

;-----------------------------------------------------------------------------
; SKIP_COMMENT advances SI to the first non-comment line.
;-----------------------------------------------------------------------------
skip_comment    proc    near
                cmp     byte ptr [si],"#"
                jne     skip_exit
                call    nextline
                jmp     skip_comment
skip_exit:      ret
skip_comment    endp

;-----------------------------------------------------------------------------
; NEXTLINE advances SI to the beginning of the next line.
;-----------------------------------------------------------------------------
nextline        proc    near
                lodsb
                cmp     al,0Dh
                jnz     nextline
                inc     si
                ret
nextline        endp

;=============================================================================
; Buffer areas used after installation.
;=============================================================================
pc              =       $
screen_buffer   =       pc
pc              =       pc + 1020
input_buffer    =       pc
pc              =       pc + 256
output_buffer   =       pc
pc              =       pc + 256
menu_table      =       pc

code            ends
                end     begin
