Asynchronous reader program for electronic scale

Started by Kasonya, April 22, 2005, 07:09:43 AM

Previous topic - Next topic

Kasonya

I am developing a 16 bit DOS program that can read data that is coming through a Communication Port on a PC. I want to use this program to receive data from a weighing scale through the pc's Keyboard Buffer. Once the data is in the keyboard buffer I will be able to access it in an application program. I am able to link the program using the command "ML readmass.asm /AT". When I run it is first displaying gabbage followed by the Copywrite information and then the keyboard freezes.

The idea is that once the program is loaded into memory, when I simultaneously press the SHIFT and DOLLAR keys the data should be transmitted to the keyboard and should appear on the screen. Where am I going wrong?

Attached is the program code.

;*****************************************************************************
;*                                                                           *
;*              Asynchronous reader program for electronic scale             *
;*                     READMASS.COM    TSR                                   *
;*                                                                           *
;*****************************************************************************

rom_bios        segment at 40h  ; BIOS stats held here, also kb buffer
   org     65h
   modeset db      ?
   org     1ah
   head    dw      ?       ; unread chars go from head to tail
   tail    dw      ?
   buffer  dw      16 dup (?)      ; the buffer itself
   buffer_end      label   word
rom_bios        ends

;============== Code segment ==================================================

rmcode         segment para 'CODE'

   org     2ch
environ label   word
   org     80h
params  label   byte
   org     82h
parami  label   byte     
   org     100h

begin:
      jmp     init_vectors     

;-------------- Miscellaneous storage areas ----------------------------------

timer1          dw      07000                   ; receive timeout

rxaddr          label   dword                   ; receive data address
rxoff           dw      0
rxseg           dw      0

rxchars         dw      0                       ; 0=waiting for 1st char
                  ; 255=in receive

cbuffer         db      64 dup(?)               ; control buffer
reentry         db      0                       ; set to 255 if busy

cs_value        dw      0                       ; our code segment value

old_int_9       label   dword                   ; int 9 server before us
rom_keyboard    dw      2 dup (0)

hotkey          dw      00524h                  ; read scale on key
comport         db      0                       ; default com port

;******************** Keyboard Handler Section *******************************
newint9          proc    far
      assume  cs:rmcode
      push    ax
      push    bx
      push    cx
      push    dx
      push    di
      push    si
      push    ds
      push    es

      pushf
      call    old_int_9

            assume  cs:nothing
      assume  ds:rom_bios
      mov     ax,ds
      mov     ds,ax
      mov     bx,tail
      cmp     bx,head
      je      exiti1
      sub     bx,2
      cmp     bx,offset buffer
      jae     no_wraprx
      mov     bx,offset buffer_end
      sub     bx,2
no_wraprx:      mov     dx,[bx]
      assume  ds:rmcode
      push    cs
      pop     ds
      cmp     dx,hotkey
      jnz     exiti1
      assume  ds:rom_bios
      mov     ax,40h
      mov     ds,ax
      mov     tail,bx                 ; remove F12 key from kb buf
      assume  ds:rmcode
      push    cs
      pop     ds
      ;mov     al,5                    ; send ENQ to scale
      ;mov     ah,1                    ; transmit port service
      ;mov     dh,0                    ; clear top
      ;mov     dl,comport              ; get port
      ;int     14h                     ; send it
      mov     timer1,0ffffh           ; seed timeout +- 2 second
waitfor:
        jmp     going                   ; in loop
;waitfor:        dec     timer1
      ;cmp     timer1,0
      ;jnz     going
      ;mov     rxchars,0
      ;mov     ah,0eh                  ; beep
      ;mov     al,07                   ; bell char
      ;mov     bh,0                    ; tell main screen
      ;int     10h
      ;jmp     exiti1
going:          mov     ah,3                    ; receive ready service
      mov     dh,0                    ; clear top
      mov     dl,comport              ; which port
      int     14h                     ; check for char, if any
      and     ah,1                    ; mask for data ready
      cmp     ah,1                    ; test for data ready
      jnz     waitfor                 ; no, wait for it
      mov     al,0                    ; set to 0 so we can test for char in
      mov     ah,2                    ; receive service
      mov     dh,0                    ; clear top
      mov     dl,comport              ; which port
      int     14h                     ; read char
      jmp     overs

exiti1:         jmp     exiti

overs:          mov     bx,rxchars              ; are we in receive or 1st char
      cmp     bx,0                    ; 1st char
      jnz     inrecv                  ; no, already receiving
      ;cmp     al,2                    ; is it STX
      cmp     al,32                    ; is it STX
      jnz     waitfor
      mov     rxchars,255             ; yes, it is STX - set in receive
      mov     rxoff,offset cbuffer    ; rx into buffer
      mov     rxseg,cs                ; this segment
      mov     cx,15                   ; clear buffer
clearbuf:       les     bx,rxaddr               ; get current receive address
      mov     byte ptr es:[bx],0      ; save character
      inc     rxoff
      loop    clearbuf
      mov     rxoff,offset cbuffer    ; rx into buffer
      mov     rxseg,cs                ; this segment
inrecv:         les     bx,rxaddr               ; get current receive address
      mov     byte ptr es:[bx],al     ; save character
      ;cmp     al,3                    ; is it ETX?
      cmp     al,103                    ; is it ETX?
      jz      recvdone                ; yes, end of packet
      inc     rxoff                   ; point to next byte in buffer
      jmp     waitfor                 ; no, wait for rest

;-------------- End of packet - decide what to do ----------------------------
recvdone:       mov     cx,12               ; move 12 digits to kb buffer
      mov     si,11                   ; src cbuffer offset
      cli                             ; no ints during kb io
loopie1:        mov     al,cbuffer[si]          ; get char from cbuffer
      call    transl
      assume  ds:rmcode
      mov     ax,cs:cs_value
      mov     ds,ax
      inc     si                      ; point to next
      loop    loopie1                 ; till cx = 0
      sti                             ; all done

; this section is for the SCSI PLC action
      assume cs:nothing
      mov     dx,02a8h                ; port for SCSI
      mov     al,1
      out     dx,al                   ; send  code to PLC
      mov     cx,0ffffh               ; delay period
loop97:         loop    loop97
      mov     cx,0ffffh               ; delay period
loop98:         loop    loop98
      mov     cx,0ffffh               ; delay period
loop99:         loop    loop99
      assume cs:nothing
      mov     dx,02a8h                ; port for SCSI
      mov     al,0
      out     dx,al                   ; send  code to PLC
; this section is for the SCSI PLC action

; this section is for the COMX PLC action               
;                mov     al,5                    ; send ENQ to scale
;                mov     ah,1                    ; transmit port service
;                mov     dh,0                    ; clear top
;                mov     dl,3                    ; set port 0-3 0=COM1 .. 3=COM4
;                int     14h                     ; send it
; this section is for the COMX PLC action               


      mov     rxchars,0               ; reset start of packet
exiti:          pop     es
      pop     ds
      pop     si
      pop     di
      pop     dx
      pop     cx
      pop     bx
      pop     ax
      iret
newint9         endp


;============== Miscellaneous routines =======================================
checkint        proc    near
      push    ax
      push    bx
      push    es
      mov     ah,35h
      mov     al,63h
      int     21h
      cmp     bx,01fffh
      jnz     away
      mov     bx,es
      cmp     bx,0a000h
      jnz     away
      stc
      jmp     awayc
away:           clc
awayc:          pop     es
      pop     bx
      pop     ax
      retn
checkint        endp

transl          proc    near
      assume  ds:rom_bios
      push    ax
      mov     ax,ds
      mov     ds,ax
      pop     ax
        cmp     al,'0'                  ; 0?
      jnz     next1                   ; no, try next
      mov     dx,0b30h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
next1:          cmp     al,'1'                  ; 1?
      jnz     next2                   ; no, try next
      mov     dx,0231h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
next2:          cmp     al,'2'                  ; 2?
      jnz     next3                   ; no, try next
      mov     dx,0332h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
next3:          cmp     al,'3'                  ; 3?
      jnz     next4                   ; no, try next
      mov     dx,0433h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
next4:          cmp     al,'4'                  ; 4?
      jnz     next5                   ; no, try next
      mov     dx,0534h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
next5:          cmp     al,'5'                  ; 5?
      jnz     next6                   ; no, try next
      mov     dx,0635h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
next6:          cmp     al,'6'                  ; 6?
      jnz     next7                   ; no, try next
      mov     dx,0736h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
next7:          cmp     al,'7'                  ; 7?
      jnz     next8                   ; no, try next
      mov     dx,0837h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
next8:          cmp     al,'8'                  ; 8?
      jnz     next9                   ; no, try next
      mov     dx,0938h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
next9:          cmp     al,'9'                  ; 9?
      jnz     nextspc                 ; no, try next
      mov     dx,0a39h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
nextspc:        cmp     al,' '                  ; space?
      jnz     nextdot                 ; no, try next
      mov     dx,0b30h                ; mov 0
;               mov     dx,3920h                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
nextdot:        cmp     al,'.'                  ; .?
      jnz     nextcomma               ; no, try next
      mov     dx,342eh                ; set up code
      call    putbuf                  ; put the char in kb buffer
      jmp     alldone                 ; done, read next of 7
nextcomma:      cmp     al,','                  ; ,?
      jnz     alldone                 ; no, all done
      mov     dx,342eh                ; mov .
;               mov     dx,332ch                ; set up code
      call    putbuf                  ; put the char in kb buffer
alldone:        retn
transl          endp

putbuf          proc    near
      mov     bx,tail                 ; get tail
      add     bx,2                    ; check next pos
      cmp     bx,offset buffer_end    ; for end of buffer
      jle     no_wrap                 ; no, still room for 2
      mov     bx,offset buffer        ; yes, wrap around
      add     bx,2                    ; point to next pos
no_wrap:        mov     word ptr [bx-2],dx      ; copy scancode into kb buf
      cmp     bx,offset buffer_end    ; for end of buffer
      jne     no_wrap1                ; no, not at end
      mov     bx,offset buffer        ; yes, wrap around
no_wrap1:       mov     tail,bx                 ; set new tail
      retn
putbuf          endp

;============== end of resident part ========================
dbuffer         db      0

;============== INITIALISATION ===============================================

init_vectors:   jmp     init

msg0            db      0dh,0ah,0ah,'READMASS scale driver',0dh,0ah
      db      '  Adapted for KI20 scale and SCSI PLC control.',0dh,0ah
      db      '  Copyright Kasonya A.J. ',0dh,0ah,'$'
      
helpmsg         db      0dh,0ah,'Syntax:        READMASS [P=1|2|3|4] [/A] [/H]',0dh,0ah
      db      ' where:        P=X     -       com port to use COM1: thru COM4:',0dh,0ah
      db      '              /A       -       ask to install',0dh,0ah
      db      '              /H       -       display this help message',0dh,0ah
      db      'The hotkey to read the scale is F12.',0dh,0ah

      db      'If READMASS is called without parameters, it will install on ',0dh,0ah
      db      'COM1: 2400 odd 7bits 1 stop bit',0dh,0ah,0ah,'$'

badparm3        db      'Unknown parameter encountered',0dh,0ah,'$'
askinst         db      'Should READMASS be installed? (Y/N) $'
operterm        db      'Operator termination',0dh,0ah,'$'
alrins          db      0dh,0ah,'Program already installed!',0dh,0ah,'$'
termmsg         db      'READMASS not installed!',0dh,0ah,0ah,'$'
replymsg        db      ' ',0dh,0ah,0ah,'$'

      assume  ds:rmcode,es:nothing
init:           mov     cs_value,cs             ; set code segment value
      mov     dx,offset msg0          ; print greeting message
      call    dmsg

      push    cs
      pop     ds
      call    checkint                ; see if already installed
      jnc     init0001                ; no, carry-on
      mov     dx,offset alrins        ; yes, say so and quit
      jmp     init0900


;-------------- Interpret command tail ---------------------------------------
init0001:       mov     al,[params]             ; check for parameters
      cmp     al,0                    ; none, use defaults
      jz      init0500

init0002:       mov     bx,offset parami        ; get command line address
      jmp     init0011
init0010:       inc     bx
init0011:       cmp     byte ptr [bx],' '       ; intervening blank?
      je      init0010                ; yes - jump over
      cmp     byte ptr [bx],0dh       ; end of line?
      jne     init0015                ; no
      jmp     init0500                ; finish off initialisation
init0015:       cmp     word ptr [bx],'A/'      ; ask for install
      je      init0050
      cmp     word ptr [bx],'a/'      ; ask for install
      je      init0050
init0016:       cmp     word ptr [bx],'H/'      ; ask for help
      je      init0100
      cmp     word ptr [bx],'h/'      ; ask for help
      je      init0100
      cmp     word ptr [bx],'?/'      ; ask for help
      je      init0100

init0017:       mov     dx,offset badparm3      ; prepare for bad parameter
      call    xportprm                ; try for a transport parameter
      jnc     init0010                ; was a transport parameter
      jmp     init0900                ; bad choice

;-------------- Process /A switch --------------------------------------------
init0050:       add     bx,1                    ; point to next
init0060:       mov     dx,offset askinst       ; asking message
      call    dmsg                    ; display message
      mov     ah,0                    ; get a keyboard character
      int     16h                     ; using BIOS
      push    ax
      mov     replymsg,al             ; put reply in message
      mov     dx,offset replymsg
      call    dmsg
      pop     ax
      and     al,0dfh                 ; convert reply to uppercase
      cmp     al,'Y'
      jne     init0070
      jmp     init0010                ; it must be installed
init0070:       cmp     al,'N'         
      jne     init0060                ; didn't understand the question
      mov     dx,offset operterm
      jmp     init0900                ; terminate

;-------------- Process /H switch --------------------------------------------
init0100:       mov     dx,offset helpmsg       ; help message
      jmp     init0900                ; terminate


;-------------- Finalise initialisation --------------------------------------
init0500:       mov     dx,offset commsg
      call    dmsg                    ; display signon message
      call    xpinit                  ; initialise transport
      assume  ds:rmcode             
      mov     ax,cs
      mov     ds,ax

      mov     al,9                    ; set int 9
      mov     ah,35h
      int     21h
      mov     rom_keyboard,bx
      mov     bx,es
      mov     rom_keyboard[2],bx
      mov     ah,25h
      mov     al,9
      mov     dx,offset newint9
      int     21h
      mov     ah,25h                  ; set int 63h to A000:1FFF
      mov     al,63H
      mov     bx,0a000h
      mov     ds,bx
      mov     dx,01fffh
      int     21h
      mov     dx,offset init_vectors  ; TSR
      int     27h
      

;-------------- Bad parameter - terminate initialisation ---------------------
init0900:       call    dmsg                    ; display given message
      mov     dx,offset termmsg
      call    dmsg                    ; display given message
      mov     ah,4ch
      mov     al,0
      int     21h

;-------------- Process transport parameters ---------------------------------
commsg          db      'Using serial port com'
portno          db      '1:',0dh,0ah,'$'
badport         db      'Can only have P=1 or P=2 or P=3 or P=4',0dh,0ah,'$'
xportprm        proc    near
      cmp     word ptr es:[bx],'=P'
      je     xprm0000
      cmp     word ptr es:[bx],'=p'
      jne     xprm0005
xprm0000:       add     bx,2                    ; point to next
      mov     al,byte ptr es:[bx]     ; get the character
      cmp     al,'1'                  ; is it com1?
      je      xprm0010                ; yes
      cmp     al,'2'                  ; is it com2?
      je      xprm0010                ; yes
      cmp     al,'3'                  ; is it com3?
      je      xprm0010                ; yes
      cmp     al,'4'                  ; is it com4?
      je      xprm0010                ; yes
      mov     dx,offset badport
xprm0005:       stc                             ; indicate bad parameter
      retn                            ; and return to caller
xprm0010:       mov     portno,al               ; set for display
      sub     al,'1'                  ; make it in range 0 - 3
      mov     comport,al              ; set port number
      clc                             ; indicate no error
      retn                            ; done
xportprm        endp

;-------------- Initialise transport -----------------------------------------
xpinit          proc    near
      mov     al,10101010b            ; set for 2400/o/7/1
      mov     dh,0                    ; clear top
      mov     dl,comport              ; get port number
      mov     ah,0                    ; set com params
      int     14h                     ; do it
      retn
xpinit          endp

;-------------- Display message ----------------------------------------------
dmsg            proc    near
      mov     ah,9
      int     21h
      retn
dmsg            endp

rmcode          ends

end

MichaelW

Hi Kasonya,

In the order encountered:

The ASSUME directive does not set any segment register values -- it tells MASM which segment to associate with a segment register. The programmer must provide code to actually load the appropriate value into the segment register.

For MASM 6.0+, the ASSUME directive is not necessary when you change code segments.

When an interrupt handler is called the only segment register with a known value is CS, and it will have the value that was specified by DS when the vector was set.

Most instructions that access data use DS by default. In your interrupt handler (at run time) DS is set to some unknown value, so to access old_int_9 you must first load the segment address of the resident data into DS, or use a CS segment override. The same goes for any other data that the handler accesses.

To correctly simulate an Interrupt instruction with a far call, you should execute a CLI after pushing the flags, and before the far call.

To load the address of the rom_bios segment into DS, I think you would need to use the SEG operator, something like:

mov ax,seg rom_bios
mov ds,ax

You appear to be setting the Interrupt 63h vector to a location in the display buffer. What happens if some process actually calls Interrupt 63h, expecting a valid handler there?

Why is checkint in the resident part?

There are probably other errors that I am failing to see. Why a TSR? Why pass the data through the keyboard buffer? Why not include the code to receive the data directly in the application?
eschew obfuscation

Kasonya

HI MichaelW,

The application language that I am using is Progress 4GL and as far as I know has no routine for accessing a Serial Port. The TSR program would enable me to get the scale reading from the Keyboard Buffer instead of an operator manually getting the result from the scale and capturing it on the keyboard. Thanks for your suggestions I will try them.

MichaelW

I have a few more suggestions.

Instead of placing the keys directly into the keyboard buffer, it might be easier to use the BIOS Interrupt 16h function (AH=05h).

Depending on how long it takes to read the data from the scale, there could be a reentrancy problem with the calls to Interrupt 14h from the Interrupt 9 handler.

Because of potential problems with other methods of determining if a TSR was installed, I eventually started adding a paragraph-aligned signature to the resident part, and coding the loader to search for the signature. By paragraph-aligning the signature, and searching for a match to the first byte before checking for a full match, the search could be completed in a negligible amount of time (searching the bottom 1MB of the address space). There turned out to be a problem with the search finding an aligned copy of the signature in the DOS disk buffers, but this was easily corrected by coding the search routine to modify the signature in the loaded program before it performed the search. If the search found no match, the loader would then install the TSR with the modified signature. For a signature I normally used the program name and version number.

A complex TSR can be very difficult to debug. If I were doing this, I think I would use a normal program to develop and debug the handler and associated components. The program could install the handler and then wait in a loop, monitoring the state of whatever, and then uninstall the handler before terminating.
eschew obfuscation

farrier

Kasonya,

Does Progress 4GL run under DOS or Windows or just Linux/Unix?  Or are you asking about a client program/machine that only interacts with Progress 4GL?  If so, does the client machine run DOS or Windows or something else?

farrier
It is a GOOD day to code!
Some assembly required!
ASM me!
With every mistake, we must surely be learning. (George...Bush)

Kasonya

farrier,

I am asking about a client program that only interacts with the Progress 4GL. The client machine is runing MSDOS and connects to the Progress Server through Inet Software. The scale is attached to the client machine. The progress server is running on Sun Solaris Unix. The Sun Solaris server is connected to a Windows 2000 Network. In addtion  connecting through Inet software you can also connect to the Solaris server using Telnet.


Kasonya.

farrier

Kasonya,

If you do not have control over the DOS program source code, you may have to do something like what you have described.  I do a very similar thing in a Windows program.  I watch every key stroke while in the dialog used for weighing trucks, and if a W key is pressed and the cursor is in the weight field, I read the serial port in question and set the contents of the weight field accordingly.  You have described something very similar, but I'm not qualified to offer help with a DOS TSR, serial port reader and keyboard stuffer.  If I can help in any way, I will try.

Good luck,

farrier
It is a GOOD day to code!
Some assembly required!
ASM me!
With every mistake, we must surely be learning. (George...Bush)

MichaelW

#7
The attachment contains two 16-bit DOS programs that demonstrate one method of responding to a hotkey and passing an arbitrary sequence of keystrokes to another 16-bit DOS program, all without involving a TSR. The method works OK under Windows 2000, and probably under Windows XP, and it avoids most of the problems with developing and debugging a TSR.

; -------------------------------------------------------------
; This is the MASM 6+ source for a 16-bit DOS program that
; installs temporary handlers for interrupts 15h and 16h
; before executing another 16-bit DOS program. The interrupt
; 15h handler monitors for the hot key (Shift+$). When the
; hot key is detected the interrupt 16h handler switches to
; a mode where it intercepts calls (from the executed program)
; to the BIOS Get Keystroke, Check For Keystroke, Get Enhanced
; Keystroke, or Check For Enhanced Keystroke functions, and
; returns a predefined sequence of keystroke data.
;
; Although this program does pass its command line (and
; environment) to the executed program, it does not use the
; command line itself.
; -------------------------------------------------------------

    ; These parameters defined here for easy edit.
    ;
    PROGRAM_NAME      EQU "showkeys.com"
    HOTKEY_SCAN_CODE  EQU 5
    HOTKEY_SHIFT_MASK EQU 11b

    ; Declare the LoadExec structure used by the DOS Load
    ; and Execute Program function.
    ;
    LoadExec STRUCT
      leEnvironment dw 0      ; environment-block segment
      leCommandTail dd 0      ; address of command tail
      leFCB_1       dd 0      ; address of default FCB #1
      leFCB_2       dd 0      ; address of default FCB #1
    LoadExec ENDS   

    .model tiny               ; generate a COM file
    .386                      ; enable 32-bit instructions
    .stack                    ; default size (will be changed)
.code                         ; start code segmemt
.startup                      ; for tiny, expands to ORG 100h

    ; Place the data in the code segment for easy
    ; access from the interrupt handlers.

    jmp   @F                  ; jump around data

    ; Define structure var using the default initializers.
    ;
    LoadArgs      LoadExec <>

    progName      db PROGRAM_NAME,0   
   
    msgExec       db "Error executing child program, $"
    msgExit       db "press any key to exit...$"

    prevIsr15h    dd 0        ; previous Interrupt 15h handler
    prevIsr16h    dd 0        ; previous Interrupt 16h handler
    fActive       dw 0        ; set when active
    firstKey      dw 2348h    ; H
                  dw 1769h    ; i
                  dw 3920h    ;
                  dw 1454h    ; T
                  dw 2368h    ; h
                  dw 1265h    ; e
                  dw 1372h    ; r
                  dw 1265h    ; e
                  dw 0221h    ; !
    lastKey       dw 1c0dh    ; <enter>
    nextKey       dw firstKey ; offset of next key in sequence

  @@:
    ; Move stack and release unneeded memory.
    mov   bx,OFFSET theEnd
    mov   sp,bx               ; move stack
    shr   bx,4                ; convert to paragraphs
    inc   bx                  ; correct for remainder
    mov   ah,4Ah              ; set memory block size
    int   21h                 ; (ES specifies memory block)

    ; Hook Interrupt 15h.
    mov   ax,3515h            ; get interrupt vector
    int   21h                 ; returns handler address in ES:BX
    mov   WORD PTR prevIsr15h,bx
    mov   WORD PTR prevIsr15h+2,es
    mov   ax,2515h            ; set interrupt vector
    mov   dx,OFFSET Isr15h    ; DS:DX -> new interrupt handler
    int   21h

    ; Hook Interrupt 16h.
    mov   ax,3516h            ; get interrupt vector
    int   21h                 ; returns handler address in ES:BX
    mov   WORD PTR prevIsr16h,bx
    mov   WORD PTR prevIsr16h+2,es   
    mov   ax,2516h            ; set interrupt vector
    mov   dx,OFFSET Isr16h    ; DS:DX -> new interrupt handler
    int   21h

    ; Initialize LoadArgs such that child program will receive
    ; exact copy of our environment, commandTail, and FCB's.
    mov   LoadArgs.leEnvironment,0
    mov   WORD PTR LoadArgs.leCommandTail,80h
    mov   WORD PTR LoadArgs.leCommandTail+2,ds
    mov   WORD PTR LoadArgs.leFCB_1,5Ch
    mov   WORD PTR LoadArgs.leFCB_1+2,ds
    mov   WORD PTR LoadArgs.leFCB_2,6Ch
    mov   WORD PTR LoadArgs.leFCB_2+2,ds

    ; Execute program directly as child.
    mov   dx,OFFSET progName  ; DS:DX -> path
    push  ds
    pop   es
    mov   bx,OFFSET LoadArgs  ; ES:BX -> LoadExec structure
    mov   ax,4B00h            ; load and execute
    int   21h
    .if CARRY?
      mov   dx,OFFSET msgExec
      mov   ah,9              ; write string to stdout
      int   21h
    .endif

    ;Clean up and exit.
    push  ds
    mov   ax,2515h            ; set interrupt vector
    lds   dx,prevIsr15h       ; DS:DX -> interrupt handler
    int   21h                 ; restore original handler
    pop   ds
    push  ds
    mov   ax,2515h            ; set interrupt vector
    lds   dx,prevIsr16h       ; DS:DX -> interrupt handler
    int   21h                 ; restore original handler
    pop   ds

    mov   dx,OFFSET msgExit
    mov   ah,9                ; write string to stdout
    int   21h

    mov   ah,10h              ; get keystroke (will wait)
    int   16h

    .exit

; -------------------------------------------------------------
; Interrupt 15h handler. This handler tests for the hot key
; and, when it is detected, sets the active flag, absorbs the
; hot key, and returns directly to the caller (which will
; always be the BIOS as this program was the last to hook
; interrupt 15h). If the hot key is not detected this handler
; chains to the previous handler.
; -------------------------------------------------------------
CF EQU 1  ; bit value for carry flag
Isr15h proc

    push  bx
    push  es
    .if ah == 4fh             ; keyboard intercept function?
      .if al == HOTKEY_SCAN_CODE
        push  40h
        pop   es
        push  17h
        pop   bx              ; ES -> BIOS shift flags byte
        test  BYTE PTR es:[bx],HOTKEY_SHIFT_MASK
        .if !ZERO?
          mov   cs:fActive,1

          ; Clear the carry flag in the return flags (the copy
          ; of the flags that INT pushed onto the stack) so
          ; the BIOS will ignore the hot key. The IRET will
          ; pop these flags into the flags register. The
          ; contents of the stack, at this point:
          ;   SP+8  -> return flags
          ;   SP+6  -> return CS
          ;   SP+4  -> return IP
          ;   SP+2  -> preserved BX
          ;   SP    -> preserved ES
          ;
          mov   bx,sp
          xor   BYTE PTR ss:[bx+8],CF
          pop   es
          pop   bx
          iret                ; return directly to caller
        .endif
      .endif
    .endif
    pop   es
    pop   bx
    jmp   cs:prevIsr15h       ; chain to previous handler

Isr15h endp

; -------------------------------------------------------------
; Interrupt 16h handler. This handler tests the state of the
; active flag and, if it is set, returns the next key in the
; predefined keystroke sequence. When all of the keys in the
; sequence have been returned, this handler clears the active
; flag and reinitializes the sequence start address. If the
; active flag is not set this handler simply chains to the
; previous handler.
; -------------------------------------------------------------
ZF EQU 01000000b ; bit value for zero flag
Isr16h proc

    .if cs:fActive
      push  bx
      mov   bx,cs:nextKey
      .if ah == 1 || ah == 11h
        mov   ax,cs:[bx]      ; set return ascii and scan codes

        ; Clear the zero flag in the return flags (the copy of
        ; the flags that INT pushed onto the stack) to indicate
        ; that a keystroke is available. The IRET will pop
        ; these flags into the flags register. The contents of
        ; the stack, at this point:
        ;   SP+6  -> return flags
        ;   SP+4  -> return CS
        ;   SP+2  -> return IP
        ;   SP    -> preserved BX
        ;
        mov   bx,sp
        xor   BYTE PTR ss:[bx+6],ZF
        pop   bx
        iret                  ; return directly to caller
      .elseif ah == 0 || ah == 10h
        mov   ax,cs:[bx]      ; set return ascii and scan codes
        add   cs:nextKey,2    ; adjust address to next key
        .if cs:nextKey > OFFSET lastKey
          mov   cs:fActive,0
          mov   cs:nextKey,OFFSET firstKey
        .endif
        pop   bx
        iret                  ; return directly to caller
      .endif
    .endif
    jmp   cs:prevIsr16h       ; chain to previous handler

Isr16h endp

; -------------------------------------------------------------
; Allocate a new stack.
; -------------------------------------------------------------
    even                      ; specify word alignment
    dw 100h dup(0)            ; allocate 256-words
theEnd:                       ; label end of program
; -------------------------------------------------------------
end



[attachment deleted by admin]
eschew obfuscation