;*****************************************************************************
; PROGRAM ----: NWBCAST.ASM, version 1.0 
; AUTHOR -----: Kevin E. Saffer, 904-296-9000 Days EST, 262-1020 Evenings
; COPYRIGHT --: None, placed into the public domain
; CREATED ----: 1/22/1994 at 13:04
;*****************************************************************************
; Introduction:
; -------------
; Implements a software interrupt service routine for the Novell shell
; versions 3.01 and above.  The interrupt handler will insert a programmer
; specified keystroke into the PC's keyboard buffer whenever a broadcast
; message is detected.  The Clipper application may then handle this event
; by defining a SET KEY procedure tied to the keystroke specified in the 
; install routine.  The Clipper procedure may then use the NETTO functions
; to handle the incoming message.
;
; Warning:  This routine installs a ISR (interrupt service routine) which
;           MUST be removed before program exit by calling the uninstall 
;           function.  Leaving the ISR active after program completion will
;           cause the computer to transfer control to an incorrect address
;           the next time a broadcast message is detected.  This will most
;           likely result in a locked up PC.  A future version can be built
;           as a small TSR loaded before the Clipper application.  This will
;           allow the routine to remain in memory even if the Clipper pgm
;           crashes.  An interface module will be linked into the Clipper
;           application to communicate with the TSR.  After this release is
;           debugged, I'll get to work on the TSR version.
;
; Since an ISR is used, this module may never be overlayed.  The ISR code
; must always be available to handle the interrupt, therefore this object
; file must be placed into the root portion of your link script.  It will
; only increase the load size of the application by 700 bytes.
;
; Compatibility:
; --------------
; Netware shell versions 3.01 and above will issue an INT 2F whenever a 
; broadcast message is waiting.  For this reason, the latest shells must be 
; used; they are available for downloading on the Compuserve NOVLIB forum.
;
; This routine will not work with the DOS extenders from CA or Blinker.  The
; reason it will not work is that I have no idea how to switch these routines
; from segment addressing over to protected mode addressing.  If anyone has
; this knowledge, please share it on the forum or call me directly.
;
; Usage Restrictions:
; -------------------
; Should you wish to shell out of the Clipper application, this routine must
; be shut down using NB_STOP() prior to running other programs.  If left 
; active, any broadcast message will cause the specified keystroke to be
; fed into the shelled program.  Re-start the routine using NB_START() when
; the Clipper application regains control.
;
; Messages Must Be Allowed! (CASTON)
; ----------------------------------
; Before use, you should change the Netware shell's brodcast mode to 0 to 
; allow normal broadcast message handling.  This routine will then prevent
; the message from being displayed on the last display line.  Use the NETTO
; function FN_SETBMOD( 0 ) to accomplish this, or ensure CASTON has been
; executed prior to loading your application.
;
; Compiling/Linking:
; ------------------
; Compile with MASM 5.1 or greater using "MASM nwbcast;"
; Link the resulting NWBCAST.OBJ into your application at the root level.
;
;*****************************************************************************
; Clipper callable function list:
;*****************************************************************************
;
; NB_START( <key> ) - installs the interrupt service routine with specified key
; NB_STOP()         - removes the interrupt service routine
; NB_SERVER()       - returns the connection ID of the server holding the last message
;
;*****************************************************************************
.RADIX 16  ; my family has 8 fingers per hand so we think in HEX! <g>

; declare callable routines as public
        PUBLIC NB_START        ; installation routine
        PUBLIC NB_STOP         ; uninstallation routine
        PUBLIC NB_SERVER       ; last message server connection number

; declare clipper parameter routines
        EXTRN   __PARC:FAR	; get character string, segment:offset in DX:AX
        EXTRN   __PARCLEN:FAR	; get length of a string parameter into ax
        EXTRN   __PARCSIZ:FAR	; get size of memory allocated for string parameter
        EXTRN   __PARDS:FAR	; get date string, segment:offset in DX:AX
        EXTRN   __PARINFA:FAR	; get size of array parameter or element type
        EXTRN   __PARINFO:FAR	; get number of parameters or type of one
        EXTRN   __PARL:FAR	; get logical, value in AX
        EXTRN   __PARND:FAR	; get numeric double, segment:offset in DX:AX
        EXTRN   __PARNI:FAR	; get numeric integer, value in AX
        EXTRN   __PARNL:FAR	; get numeric long, value in DX:AX
        EXTRN   __RETC:FAR	; return string, push seg:off onto stack
        EXTRN   __RETCLEN:FAR	; return string of x length, push seg:off:length
        EXTRN   __RETDS:FAR	; return date string, push seg:off onto stack
        EXTRN   __RETL:FAR	; return logical, push 1 register onto stack
        EXTRN   __RETND:FAR	; return double, push 4 registers onto stack
        EXTRN   __RETNI:FAR	; return integer, push 1 register onto stack
        EXTRN   __RETNL:FAR	; return long, push 2 registers onto stack

; define a keyboard buffer structure for the insertion routine
BIOS_DATA SEGMENT AT 40
        ORG             1A
        BUFFER_HEAD     DW   ?  ; pointer to the keybord buffer head
        BUFFER_TAIL     DW   ?  ; pointer to the keyboard buffer tail
        ORG             80 
        BUFFER_START    DW   ?  ; starting keyboard buffer address
        BUFFER_END      DW   ?  ; ending keyboard buffer address
BIOS_DATA ENDS

; declare our code segment
CODE SEGMENT 'CODE'
        ASSUME  CS:CODE,DS:CODE      ; inform MASM of our intentions

        OLDINTSEG       DW   0       ; old 2F vector segment
        OLDINTOFF       DW   0       ; old 2F vector offset
        KEYCODE         DW   1       ; keyboard scan code to be inserted (this is Ctrl-A)
        SERVERID        DW   0       ; server connection number

;*****************************************************************************
; NB_START() - interrupt service routine installation
;*****************************************************************************
NB_START PROC FAR
        PUSH    BP              ; save vital registers
        MOV     BP,SP          
        PUSH    DS             
        PUSH    ES            

        MOV     AX,1            ; retrieve the keycode to be used
        PUSH    AX                 
        CALL    __PARNI        
        MOV     CS:KEYCODE,AX   ; save it and restore the stack
        POP     AX

        PUSH    CS              ; reset the data segment to point to our code segment
        POP     DS

        CMP     OLDINTSEG,0     ; check to see if the vector was already changed
        JZ      NB_START1       ; no, proceed with vector change

        POP     ES              ; restore clipper registers
        POP     DS             
        POP     BP             
        CLD                     ; reset the direction flag
        RET                     ; return to clipper          

NB_START1:
        MOV     AX,352F                 ; get interrupt vector 2F into ES:BX
        INT     21                      ; call dos
        MOV     OLDINTSEG,ES            ; save the existing handler segment
        MOV     OLDINTOFF,BX            ; save the existing handler offset
        MOV	WORD PTR [INTADDR+2],ES	; update ISR with existing segment
        MOV	WORD PTR [INTADDR],BX	; update ISR with existing offset

        PUSH    CS                      ; store the segment of new routine
        POP     DS                      ; and place it into DS
        MOV     DX,OFFSET NB_INT2F      ; offset of the new routine to DX
        MOV     AX,252F                 ; set interrupt vector 2F from DS:DX
        INT     21                      ; call dos

        POP     ES              ; restore clipper registers
        POP     DS             
        POP     BP             
        CLD                     ; reset the direction flag
        RET                     ; return to clipper          

NB_START ENDP

;*****************************************************************************
; NB_STOP() - interrupt service routine uninstallation
;*****************************************************************************
NB_STOP PROC FAR
        PUSH    BP              ; save vital registers
        MOV     BP,SP          
        PUSH    DS             
        PUSH    ES            

        PUSH    CS              ; reset the data segment to point to our code segment
        POP     DS

        CMP     OLDINTSEG,0     ; check to see if the vector had been changed
        JZ      NB_STOP2        ; no, return to clipper

        MOV     AX,OLDINTSEG    ; retrieve old routine segment
        MOV     DX,OLDINTOFF    ; retrieve old routine offset

        PUSH    AX              ; store the segment of old routine
        POP     DS              ; and place it into DS
        MOV     AX,252F         ; set interrupt vector 2F from DS:DX
        INT     21              ; call dos

        MOV     OLDINTSEG,0     ; clear the existing handler segment
        MOV     OLDINTOFF,0     ; clear the existing handler offset

NB_STOP2:
        POP     ES              ; restore clipper registers
        POP     DS             
        POP     BP             
        CLD                     ; reset the direction flag
        RET                     ; return to clipper          

NB_STOP ENDP

;*****************************************************************************
; NB_SERVER() - return the connection ID of the server holding the message
;*****************************************************************************
NB_SERVER PROC FAR
        PUSH    BP              ; save vital registers
        MOV     BP,SP          
        PUSH    DS             
        PUSH    ES            

        MOV     AX,CS:SERVERID  ; retrieve the connection number
        PUSH    AX                 
        CALL    __RETNI        
        POP     AX

        POP     ES              ; restore clipper registers
        POP     DS             
        POP     BP             
        CLD                     ; reset the direction flag
        RET                     ; return to clipper          

NB_SERVER ENDP

;*****************************************************************************
; NB_INT2F - interrupt service routine
;*****************************************************************************
NB_INT2F PROC NEAR
	PUSHF                ; save calller's flags for the original interrupt 2F
	CMP     AX,7A85      ; is a broadcast message is waiting?
	JE 	NB_INT2F1    ; yes, take over the interrupt
	JMP	NB_INT2F4    ; no, transfer control to the original interrupt 

NB_INT2F1:
	PUSH	AX               ; save the registers used by this routine
        PUSH    BX
        PUSH    DX
        PUSH    DS

	PUSH	CS	         ; set DS to this segment
	POP	DS
	CLD                      ; clear direction flag
        MOV     CS:SERVERID,CX   ; save the server connection number for later
        XOR     CX,CX            ; inform Netware we will handle the message
        MOV     BX,BIOS_DATA     ; point ds to the bios data area
        MOV     DS,BX            
        ASSUME  DS:BIOS_DATA     ; inform MASM of the new data segment
        CLI                      ; disable interrupts for buffer manipulation
        MOV     BX,BUFFER_TAIL   ; get buffer tail address
        MOV     DX,BX            ; transfer it to DX
        ADD     DX,2             ; calculate next buffer position
        CMP     DX,BUFFER_END    ; did we overshoot the end?
        JNE     NB_INT2F2        ; no, then continue
        MOV     DX,BUFFER_START  ; yes, then wrap to start of buffer

NB_INT2F2:
        CMP     DX,BUFFER_HEAD   ; is the buffer full?
        JE      NB_INT2F3        ; yes, then end now
        MOV     AX,CS:KEYCODE    ; retrieve the keycode
        MOV     [BX],AX          ; insert the keycode into the keyboard buffer
        MOV     BX,DX            ; advance the tail
        MOV     BUFFER_TAIL,BX   ; record its new position

NB_INT2F3:
        STI                      ; enable interrupts
	POP	DS               ; restore registers
	POP	DX
	POP	BX
	POP	AX

NB_INT2F4:
	POPF                     ; restore the caller's flags
	DB	0EAH	         ; JMP FAR immediate opcode
INTADDR	DD	0	         ; original int 2F address, updated by NB_START()

NB_INT2F ENDP

CODE    ENDS    ; end of segment
        END     ; end of program
