title newinit.asm
comment |--------------------------------------------------------------
In text modes the active page can be switched between 1-4 (80-col) or
 1-8 (40-col) by pressing ALT and the corresponding # on the TOP ROW. This
 feature can only be used to view the other pages.  Results of all CON I/O
 while paging are unpredictable and messy.
As protection against duplicate installation, INT 15h with AX = X'FF00'
 returns AX = X'ABCD'.
The keypad "5" key will toggle processing.  While paused,  a flashing,
 highlighted "P" will appear in the lower-right corner (not graphics).
If Numlock is on, shift-"5" is required.  If Alt or Ctrl are pressed, nothing
 changes.  Scroll Lock, Caps Lock, and Ins are irrelevant.
This program sets the video mode to 80 col monochrome.
The enhanced keyboard can be toggled on/off with Alt-PrtSc, and a 1/8 sec
 2000 Hz tone will sound when toggled on.  (The PrtSc modification cannot
 be turned off.)
NOTE: If both  shift keys are depressed at installation time, the
 installation will be cancelled, with errorlevel set to X'AB'.
------------------------------------------------------------------------|
bios_data       segment at 0040h
        org     17h
kb_flag db      ?       ; keyboard status byte
kb_flag_1       db      ?

        org     49h
v_mode  db      ?       ; BIOS video mode
v_width db      ?       ; BIOS screen width

        org     62h
v_page  db      ?       ; BIOS video page
mystery_w       dw      ?       ; word used by pause routine
mystery_b       db      ?       ; byte used by pause routine
bios_data ends

cseg    segment
        assume  cs:cseg
        org     80h
parm_cnt        db      ?       ; parameter count
parm_data       db      ?       ; start of parameter data space
        org     100h    ; .COM file

begin:  jmp     init_vectors    ; initialize and attach to DOS

; addresses of video and keyboard I/O ROM routines
prtsc   dd      0       ; INT 5
kybd    dd      0       ; INT 9
cass    dd      0       ; INT 15h
kybd_io dd      0       ; INT 16h

; constants and segments
bios_dat        dw      40h     ; bios_data segment
mono_seg        dw      0b000h  ; segment of MGA
color_seg       dw      0b800h  ; segment of CGA
datatbl db      2,3,0,1,6,4,5   ; scan-code to video mode data
indicator equ "P"+256*10001111b ; an highlighted, flashing "P"
row equ 24                      ; position of indicator for mono moniter
col equ 79
mono_point      equ 2*((row*80)+col)    ; offset in mono segment

; variable data
vdata   db      0       ; current video mode or page
code_seg        dw      0       ; CS of resident routines
graph_bit       db      0       ; flag to signal graphics mode
toggle  db      1       ; enables/disables enhanced keyboard

prtsc_int:      sti             ; enable interrupts
        push    ax      ; save regs
        push    dx
        mov     ah,2    ; read printer status
        xor     dx,dx   ; zero DX - LPT1
        int     17h     ; printer I/O
        xor     ah,30h  ; check for OFFLINE
        jnz     chk_shft        ; skip beep, prepare for exit
        pop     dx
no_dump:        pop     ax
        iret
chk_shft:       pop     dx
        push    ds
        mov     ds,bios_dat     ; set DS = X'40'
        assume  ds:bios_data
        test    kb_flag,00000010b       ; check for LEFT shift key
        pop     ds      ; restore regs
        jz      no_dump ; skip screen dump if no LEFT shift
        pop     ax
        jmp     prtsc   ; print screen routine

calculate:      assume  ds:bios_data
        xor     ax,ax   ; zero AX
        xor     cx,cx   ; likewise
        mov     al,v_width      ; AX = 00 width
        mov     cl,v_page       ; save for later multiply
        mov     bx,25   ; calculate offset into page
        mul     bl
        dec     ax      ; = (25*width)-1
        shl     ax,1    ; MUL AX,2 - skip attribute bytes
        mov     bx,ax   ; save in BX
        mov     ax,2048 ; byte length of 40 column page
        cmp     v_width,40      ; check for 40 column screen
        je      multiply        ; if yes, leave AX alone
        shl     ax,1    ; if no, change to 80 column offset
multiply:       mul     cx      ; multiply by page number
        add     bx,ax   ; sum buffer offset with page offset
        mov     es,color_seg    ; load ES with CGA segment
        assume  es:nothing
skip_cga:       mov     cx,es:[bx]      ; save screen data in CX
        mov word ptr es:[bx],indicator   ; store flashing "P" on screen

rejoin: ; branch made if graphics mode - skip indicator

        mov     dx,mystery_w    ; load mystery word
        add     dx,4    ; more mysterious code
        mov     al,mystery_b    ; load mystery byte
        out     dx,al   ; I have no clue what this does

        call    reset
        or      kb_flag_1,00001000b     ; turn ctrl-num-lock on
        sti             ; enable interrupts
again:  test    kb_flag_1,00001000b     ; check if ctrl-num-lock on
        jnz     again   ; if on, check again

        cli             ; disable interrupts (play it safe)
        test    graph_bit,1     ; check if graphics mode
        mov     graph_bit,0     ; reset graphics flag
        jnz     skip_mov        ; skip restoration of screen data
        mov     es:[bx],cx      ; restore screen data
skip_mov:       pop     es      ; restore regs
        pop     dx
        pop     cx
        pop     bx
        pop     ds
        pop     ax
        iret

pause:  assume  ds:bios_data    ; DS set in FIVE: routine
        push    bx      ; save BX (for active page)
        push    cx      ; save CX to use as scratch register
        push    dx
        push    es
        cmp     v_mode,4        ; check for text mode
        jb      calculate       ; continue if yes
        cmp     v_mode,7        ; check for monochrome buffer
        jne     no_p    ; skip "P" if graphics
        mov     es,cs:mono_seg  ; point ES to segment at 0b000h
        mov     bx,mono_point   ; offset into monochrome segment
        jmp short skip_cga      ; skip color setup

no_p:   mov     graph_bit,1     ; set graphics mode flag
        jmp short rejoin        ; rejoin main code

yes:    and     kb_flag_1,11110111b     ; turn ctrl-num-lock off
        call    reset   ; reset keyboard
        pop     ds
        pop     ax
        iret

        assume  ds:nothing
five:   push    ds      ; save data segment
        mov     ds,cs:bios_dat  ; point to keyboard data
        assume  ds:bios_data
        test    kb_flag_1,00001000b     ; check if Ctrl-NumLock on
        jnz     yes     ; if yes, unpause
        mov     al,kb_flag      ; get shift key status byte
        test    al,00001100b    ; check for Alt or Ctrl pressed
        jnz     quit    ; exit if so
        and     al,00100011b    ; isolate NumLock and Shift bits
        jz      pause   ; if zero (none pressed) then go!
        cmp     al,00100000b
        ja      pause   ; both NumLock & Shift must be pressed
                        ; if not, then end
        assume  ds:nothing
quit:   pop     ds      ; restore regs
        pop     ax
        jmp     kybd    ; goto BIOS

        assume  ds:nothing
; intercepts INT 9
kybd_int:       push    ax      ; save reg
        in      al,60h  ; get scan code
        cmp     al,37h  ; scan code of PrtSc key
        je      tog_chk ; check for Alt pressed before toggling
        test    toggle,1        ; check if enhanced keyboard enabled
        jz      off     ; if not, skip enhanced processing
        cmp     al,4ch  ; scan code of keypad "5" ?
        je      five    ; if yes, check further
        cmp     al,46h  ; check for Scroll-Lock (Break) key
        je      break   ; if so, look for Ctrl-Shift
off:    pop     ax      ;  if not, restore reg and
        jmp     kybd    ; goto BIOS

; check for right-shift-Ctrl
break:  push    ds      ; save reg
        mov     ds,bios_dat     ; set DS to bios data segment
        assume  ds:bios_data
        test    kb_flag_1,00001000b     ; check if ctrl-num-lock on
        jnz     quit    ; goto BIOS if yes
        test    kb_flag,00000001b       ; right shift key pressed?
        jz      quit
        test    kb_flag,00000100b       ; check for Ctrl key pressed
        jz      quit
        call    reset
        mov     ax,4ccah        ; DOS EXIT function 4C
                        ;  ErrorLevel = 202
        int     21h
        pop     ds      ; restore regs
        pop     ax
        iret            ; return from key interrupt

tog_chk:        push    ds      ; save DS
        mov     ds,bios_dat     ; point DS to bios data segment
        assume  ds:bios_data
        test    kb_flag_1,00001000b     ; check for Ctrl-NumLock pause on
        jnz     quit    ; goto BIOS if yes
        test    kb_flag,00000111b       ; check for Ctrl or L/R shift pressed
        jnz     quit    ; don't toggle if yes - send key to bios
        test    kb_flag,00001000b       ; check for Alt pressed
        jz      quit    ; don't toggle if no - send key to bios
        xor     toggle,1        ; toggle
        jz      skip_beep       ; skip beep if toggled off
        push    cx      ; save count reg for delay
; beep
; Step 1 -- prepare the timer to receive its divisor
        mov     al,0b6h ; timer mode register signal
        out     67,al   ; output to timer control port
; Step 2 -- send the divisor count to the timer
;   Divisor = (1,190,000 / desired frequency)
        mov     ax,595  ; divisor for 2000 Hz
        out     66,al   ; output low-order byte of divisor
        mov     al,ah   ;  high-order byte to output reg
        out     66,al   ; output high-order byte of divisor
; Step 3 -- turn on the two bits which activate the speaker, and the
;           timer's control of the speaker
        in      al,97   ; get current bit settings - port 97
        mov     ah,al   ; save port 97 to turn sound off
        or      al,03   ; turn on last two bits
        out     97,al   ;  send back the new value

        mov     cx,8000h        ; approx 1/8 second
delay:  loop    delay   ; sounding tone while counting
        mov     al,ah   ; restore original port 97
        out     97,al   ; stop tone
        pop     cx      ; restore count reg

skip_beep:      call    reset   ; reset kybd and interrupt controller
        pop     ds      ; restore regs
        assume  ds:nothing
        pop     ax
        iret

reset   proc
        in      al,61h  ; reset keyboard
        mov     ah,al
        or      al,80h
        out     61h,al
        mov     al,ah
        out     61h,al
        mov     al,20h  ; send end-of-interrupt signal
        out     20h,al  ;  to interrupt controller
        ret
reset endp

        assume  ds:nothing
cass_int:       cmp     ax,0ff00h       ; duplicate installation protection
        je      duped   ; yes
        jmp     cass    ; no, goto BIOS
duped:  mov     ax,0abcdh
        iret

nope:   jmp     kybd_io ; goto BIOS

        assume  ds:nothing
kybd_io_int:
        test    toggle,1        ; check if enhanced keyboard enabled
        jz      nope    ; if not, goto bios
        test    ah,ah   ; check for AH = 0 (read key)
        jnz     nope    ; if not, goto bios
        pushf           ; simulate INT 16h
        call    kybd_io
        test    al,al   ; check for zero char code
        jz      extend  ; continue if extended character
        iret            ; return if not

extend: cmp     ah,53h  ; skip predefined ext. functions
        ja      ext     ; extended function routine(s)
back:   iret            ; return if predefined

; branch made if code greater than predefined (3-53h)
ext:    cmp     ah,94   ; check for F21 (CTRL-F1)
        jb      back    ; end if below
        cmp     ah,103  ; CTRL-F10 - 67h
        ja      next    ; goto next function

        push    dx      ; save DX
        push    ax      ; save scan code

        mov     ah,2    ; read printer status
        xor     dx,dx   ; zero DX - LPT1
        int     17h     ; printer I/O
        xor     ah,30h  ; check for OFFLINE
        jz      offline ; skip beep, prepare for exit

        pop     ax      ; retrieve scan code
        push    bx
        push    cx
        sub     ah,94   ; bring scan code down to 0-9
; mult AH by 3
        mov     bl,ah   ; save ah
        shl     ah,1    ; mult by 2
        add     bl,ah   ;  mult by 3
        xor     bh,bh   ; zero BH
        add     bx,offset PRNT_BUF      ; add mem loc to offset
        mov     cx,3    ; 3-char printer output
        xor     dx,dx   ; printer zero

loop1:  mov     al,3    ; full offset
        sub     al,cl   ; sub count from full offset
        xlat    cs:prnt_buf     ; get data in AL
        cmp     al,0ffh ; check for end-of-string
        je      beep    ;  beep if EOS
        xor     ah,ah   ; zero AH - print char
        int     17h     ; BIOS printer I/O
        loop    loop1   ; next char

beep:   cmp     cl,3    ; check for null string
        je      p_end   ; skip beep if null
        mov     ax,0007 ; printer output / BEL
        int     17h     ; BIOS printer I/O
        mov     ax,000dh        ; printer output / CR
        int     17h     ; BIOS printer I/O

p_end:  pop     cx
        pop     bx
        sub     sp,2    ; fool next POP
offline:        pop     ax
        pop     dx
        xor     ax,ax   ; zero AX (read_char)
back3:  jmp     kybd_io_int     ; goto BIOS for next key

next:   cmp     ah,113  ; code > Alt-F10 ?
        ja      next1   ; yes - goto
        je      cls     ; if code = Alt-F10 - execute CLS
        cmp     ah,110  ; Alt-F7 < code < Alt-F10 ?
        ja      back3   ; null processing

        push    bx      ; save base reg for XLAT
        mov     al,ah   ; copy key code into AL for XLAT
        sub     al,104  ; bring key-code to 0-6
        mov     bx,offset datatbl       ; look-up table of video modes
        xlat    cs:datatbl
        xor     ah,ah   ; zero AH for INT 10h
        int     10h
        pop     bx      ; restore BX
                        ; AH already zero - read_key
        jmp     kybd_io_int     ; goto bios

cls:    push    bx
        push    cx
        push    dx
        push    ds
        mov     ds,bios_dat
        assume  ds:bios_data
        mov     bh,7    ; attribute to use on blank window
        xor     cx,cx   ; upper left corner = 0,0
        mov     dh,24   ; bottom row to clear
        mov     dl,v_width      ; bios video width byte
        dec     dx      ; bios byte is either 40/80
        mov     ax,600h ; AH=6 (scroll up) AL=0 (blank window)
        int     10h
        mov     bh,v_page       ; get current video page
        xor     dx,dx   ; upper left corner
        mov     ah,2    ; position cursor
        int     10h

        assume  ds:nothing
        pop     ds      ; restore regs
        pop     dx
        pop     cx
        pop     bx
        xor     ax,ax   ; read_key
        jmp     kybd_io_int     ; get next key

; code greater than 110, ALT-F7
next1:  cmp     ah,78h  ; ALT-1 (top row)
        jb      end_ext ; end routine
        cmp     ah,127  ;        check code > ALT-8
        ja      end_ext
        push    bx      ; save regs
        push    ax
        mov     ah,15   ; read video state
        int     10h
        mov     vdata,al        ; save current mode
        cmp     al,3    ; check for graphics modes
        ja      back1   ; end if graphics
        pop     ax      ; retrieve char code
        mov     al,ah   ; char code into page result reg
        sub     al,78h  ; bring char code down to page code
        cmp     al,4    ; check for valid code 80 or 40
        jb      skip1   ; change if valid unconditionally
        cmp     vdata,1 ; check for 40 col display
        ja      back2   ; end if 80 col
skip1:  mov     ah,5    ; select active display page
        int     10h
        jmp short back2 ; finished

end_ext:                        ; if key code not defined here,
        iret            ;  give other routines a chance

back1:  add     sp,2    ; skip AX on stack
back2:  pop     bx      ; restore BX
        xor     ax,ax   ; zero AH - read key
        jmp     kybd_io_int     ; get another char

; control codes for Gemini (Epson compatible)
prnt_buf        db      27,"V",1        ; slashed-zero      F1
        db      27,"@",0ffh     ; power-on reset        F2
        db      27,"B",1        ; 10 cpi (80 col)   F3
        db      15,0ffh,00      ; 17 cpi (132 col)      F4
        db      27,"E",0ffh     ; emphasized on     F5
        db      27,"F",0ffh     ; emphasized off        F6
        db      27,"S",0        ; superscript mode  F7
        db      27,"S",1        ; subscript mode        F8
        db      27,"U",1        ; unidirectional    F9
        db      27,"U",0        ; bidirectional         F10

end_of_resident:

print_help:
        xor     cx,cx   ; upper left row/column
        mov     dx,184fh        ; lower right row/column
        mov     bh,7    ; attribute for blank region
        mov     ax,600h ; scroll up/blank entire window
        int     10h
        mov     ah,15   ; get video data
        int     10h     ; active page in BH
        xor     dx,dx   ; upper left row/column
        mov     ah,2    ; set cursor position
        int     10h
        mov     dx,offset help_text     ; get address of help screen
        mov     ah,9    ; DOS print string
        int     21h
        mov     ax,0ff00h       ; check (again) for duplicate installation
        int     15h
        cmp     ax,0abcdh
        je      skip_prnt       ; if yes, skip printing install instructions
        mov     dx,offset not_yet       ; message - type newinit install
        mov     ah,9    ; DOS print string
        int     21h
skip_prnt:      int     20h     ; terminate

init_vectors:
        mov     ds,bios_dat
        assume  ds:bios_data
        mov     al,kb_flag      ; get keyboard status byte
        xor     al,00000011b    ; check if both shift keys pressed
        jnz     continue        ; if not, continue with installation
        mov     ax,4cabh        ; DOS EXIT function, ERRORLEVEL = X'AB'
        int     21h

continue:       mov     ax,cs   ; set data segment to code segment
        mov     ds,ax
        assume  ds:cseg
        mov     code_seg,ax     ; save code segment for resident routine
        xor     cx,cx   ; zero CX
        mov     cl,parm_cnt     ; get number of parameters
        jcxz    print_help      ; print help screen
        mov     si,offset parm_data     ; start of parameter line
        cld             ; clear DF to increment SI
loop2:  lods    parm_data       ; get byte from parm_data
        cmp     al,"Z"+1        ; check if lower case
        jb      lp2
        sub     al,"a"-"A"      ; convert to lower case
        mov     [si-1],al       ; put back into param line
lp2:    loop    loop2   ; get next char

check:  mov     di,offset parm_data
        mov     al,"I"  ; search for "I"
        mov     cl,parm_cnt     ; number of characters to search
        repne scasb     ; scan parameter line
        jne     print_help      ; print help screen if not found
        mov     si,1 + offset key       ; "INSTALL" but past the "I"
        assume  es:cseg
        repne cmps parm_data,key        ; check for key in parameter line
        jne     print_help      ; if not found, print help screen
        mov     ax,0ff00h       ; if found, check for prev install
        int     15h     ; cassette (snicker!) interrupt
        cmp     ax,0abcdh       ; duplicate installation code
        jne     install ; full speed ahead
        mov     dx,offset already       ; error message
        mov     ah,9    ; DOS print string
        int     21h
        int     20h     ; bye-bye

install:        mov     ax,2    ; AH=0 - set video mode
                        ; AL=2 - 80 col mono
        int     10h
        mov     ax,3505h        ; get int vector for INT 5
        int     21h
        mov     word ptr prtsc,bx       ; save routine offset
        mov     word ptr prtsc[2],es    ; save routine segment
        mov     dx,offset prtsc_int     ; offset of new routine
                        ; DS = CS above
        mov     ah,25h  ; set int vector for INT 5
        int     21h

        mov     ax,3509h        ; get int vector for INT 9
        int     21h
        mov     word ptr kybd,bx        ; save offset
        mov     word ptr kybd[2],es     ; save segment
        mov     dx,offset kybd_int      ; offset of user routine
        mov     ah,25h  ; set int vector
        int     21h

        mov     ax,3515h        ; get int vector for INT 15h
        int     21h
        mov     word ptr cass,bx        ; save offset
        mov     word ptr cass[2],es     ; save segment
        mov     dx,offset cass_int      ; offset of user routine
        mov     ah,25h  ; set int vector
        int     21h

        mov     ax,3516h        ; get int vector for INT 16h
        int     21h
        mov     word ptr kybd_io,bx     ; save offset
        mov     word ptr kybd_io[2],es  ; save segment
        mov dx, offset kybd_io_int      ; offset of user routine
        mov     ah,25h  ; set int vector
        int     21h

        mov     dx,offset installed     ; completed message
        mov     ah,9    ; DOS print string
        int     21h
        mov     dx,offset end_of_resident
        int     27h     ; terminate, remain resident

key     db      "INSTALL"       ; installation text key

already db      13,10,10,9,9
        db      "This utility already resident.",13,7,10,10,"$"

installed       db      9,9,"NEWINIT is up and running.",10,10,"$"

help_text:      db      "This resident program modifies certain key combinations:",13,10
 db "1) PrtSc will only work if the left shift key is used.",13,10
 db "2) Ctrl-Right-Shift-Break forces a DOS function call X'4C' (EXIT) which will"
        db      13,10
 db "   exit the current program.  This avoids much of the need for Ctrl-Alt-Del."
        db      13,10
 db "   If exit is forced in this manner, errorlevel is set to X'CA' (202).",13,10
 db "3) Alt-1 thru Alt-8 (top row) will VIEW that display page (I/O not reliable)."
        db      13,10
 db "4) Alt-F1 thru Alt-F7 change the video mode.",13,10
 db "      F1 = 80 col mono (at startup)    F2 = 80 col color",13,10
 db "      F3 = 40 col mono                 F4 = 40 col color",13,10
 db "      F5 = med res mono graphics       F6 = medium res color graphics",13,10
 db "      F7 = high res graphics           F10 = clears the current display page"
 db 13,10
 db "5) Ctrl-F1 thru Ctrl-F10 send (Epson) control codes to the printer.",13,10
 db "      F1 = slashed-zero                F2 = power-on reset",13,10
 db "      F3 = 10 cpi (80 col)             F4 = 17 cpi (132 col)",13,10
 db "      F5 = emphasized on               F6 = emphasized off",13,10
 db "      F7 = superscript mode            F8 = subscript mode",13,10
 db "      F9 = unidirectional              F10 = bidirectional",13,10
 db "6) The keypad '5' key is a toggle pause key.  While on, the letter P is"
        db      13,10
 db "   displayed, flashing and highlighted, in the lower right corner.",13,10
 db "7) Alt-PrtSc will toggle the enhanced keyboard on/off.  A beep signals on."
 db 13,10,10,"$"

not_yet db 9,"To make NEWINIT resident, type:  newinit install",13,10,10,"$"

cseg ends
        end     begin

move alt-fn and ctrl-fn to int-9
