COMMENT *

			     CLUBware  (tm)

	  BASPRINT - Enhancement to the Basic Interpreter Print statement

		   Copyright 1984 Rayhawk Automation N.W. Inc
				  P.O. Box 1427
				  Beaverton, Oregon   97075
									      *

SCRDSEG   SEGMENT

	  ASSUME  CS:SCRDSEG,DS:NOTHING,ES:NOTHING,SS:STACK

ORIG_INT10	  LABEL  WORD	       ; original int 10
	  DW	  0
	  DW	  0
;______________________________________________________________________________

;   Swap the B4 interrupt used by BASIC to print strings.  Swap must be made
;    after BASIC has been loaded and initialized its environment.  Swap takes
;    place when BASIC clears the screen.


ORIG_INTB4	  LABEL  WORD	       ; original int b4
	  DW	  0
	  DW	  0


INTSWAP   PROC	  FAR

	  CMP	  AX,0600h	       ; clear screen request?
	  JE	  DO_SWAP
	  JMP	  DWORD PTR ORIG_INT10 ; pass call to BIOS

DO_SWAP:
	  PUSH	  DS
	  PUSH	  AX

	  SUB	  AX,AX 	       ; address low memory
	  MOV	  DS,AX

	  CLI			       ; disable interrupts for a moment

;   vector for B4 is at 2D0

;   have we already taken over this interrupt once before?

	  CMP	  WORD PTR DS:[02D0h],OFFSET PRINTER
	  JE	  ALREADY_SAVED


;   save the original interrupt vector for use by error message reports

	  MOV	  AX,WORD PTR DS:[02D0h]
	  MOV	  ORIG_INTB4,AX
	  MOV	  AX,WORD PTR DS:[02D2h]
	  MOV	  ORIG_INTB4+2,AX

;   replace the original interrupt vector to allow us to intercept prints

	  MOV	  AX,OFFSET PRINTER
	  MOV	  WORD PTR DS:[02D0h],AX
	  MOV	  AX,SEG PRINTER
	  MOV	  WORD PTR DS:[02D2h],AX

ALREADY_SAVED:

	  STI

	  POP	  AX
	  POP	  DS
	  JMP	  DWORD PTR ORIG_INT10 ; pass clear screen to BIOS now



INTSWAP   ENDP

;______________________________________________________________________________

COMMENT * PRINTER prints a character string on the screen starting
		  at the current cursor position.  After the string is
		  written to the screen the cursor position is updated
		  to just after the string.

	  To use this routine, INTSWAP must have swapped the interrupt
		  vector for intterupt B4 and redirected it to the PRINTER
		  subroutine.

	  The BASPRINT module will take over the INT 10h and make
		  this module permanently resident.  When the screen is cleared
		  by BASIC,  INTSWAP will swap INT B4 and point the vector
		  to the PRINTER subroutine.

	  Algorithm:

		 check context for simple text to screen or possibly something
		   more involved that basic should handle

		  1) store character count and mark the flag for type of i/o
		  2) load current page from DS:[0049]
		  3) load current position on page from DS:[0056]
		  4) load attribute for string from DS:[004E]
		  5) move character count to CX for use in loop
		  6) load type of crt display from 0000:463
		  7) address the screen segment
		  8) move string to screen while synchronizing
		      with horizontal retrace
		  9) update cursor position on screen
		 10) update cursor position in database at DS:[0056]
		 11) leave registers in manner BASIC expects

	  On entry
		  DS:[SI] points to string to write
		  DH	  contains count of characters to write
		  DS:[CX] also points to string and must be updated on exit
		  DS:[0049] contains the page to which we write
		  DS:[004E] contains the attribute for the text
		  DS:[0056] contains the current screen location and must
			       be updated after print is complete


									      *
;______________________________________________________________________________

;  Local data addressable through CS register


CHAR_COUNT	  LABEL  BYTE
	  DB	  0


LINE_START	  LABEL  WORD
	  DW	  0		       ;  0
	  DW	  160		       ;  1
	  DW	  320		       ;  2
	  DW	  480		       ;  3
	  DW	  640		       ;  4
	  DW	  800		       ;  5
	  DW	  960		       ;  6
	  DW	  1120		       ;  7
	  DW	  1280		       ;  8
	  DW	  1440		       ;  9

	  DW	  1600		       ;  10
	  DW	  1760		       ;  11
	  DW	  1920		       ;  12
	  DW	  2080		       ;  13
	  DW	  2240		       ;  14
	  DW	  2400		       ;  15
	  DW	  2560		       ;  16
	  DW	  2720		       ;  17
	  DW	  2880		       ;  18
	  DW	  3040		       ;  19
	  DW	  3200		       ;  20
	  DW	  3360		       ;  21
	  DW	  3520		       ;  22
	  DW	  3680		       ;  23
	  DW	  3840		       ;  24

PAGE_START	  LABEL  WORD
	  DW	  0
	  DW	  4000
	  DW	  8000
	  DW	  12000

STARTING_PAGE	  LABEL  WORD
	  DW	  0

END_OF_PAGE	  LABEL  WORD
	  DW	  0

STARTING_LINE	  LABEL  WORD
	  DW	  0

RESIDUAL	  LABEL  BYTE
	  DB	  0

ROM_SEGMENT	  LABEL  WORD	       ; basic segment in ROM
	  DW	  0F600h

LOCAL_FLAG	  LABEL  BYTE
		  DB   00000000b
JUST_BLANKS	  EQU  00000001b       ; basic wants just blanks displayed


;______________________________________________________________________________

COMMENT *

   New service routine for interrupt B4

     Calls to routine F600:2BA5 generate B4 interrupts.
     These calls come from several places within the BASIC ROM.
     We service calls from two locations: 26CC - call to display a string
					  148D - call to display blanks
     Calls from anywhere else are serviced by the original basic code.
     The two labels corresponding to the two types of calls we service
     are  STRING_IO
	  REPEAT_BLANKS
     The code labeled BASIC_IO passes the interrupt back to the basic code
     for service.
									      *


PRINTER   PROC	  FAR

	  PUSH	  BP
	  MOV	  BP,SP 	       ; address the stack


;    check context for simple text to screen or possibly something
;      more involved that basic should handle

	  CMP	  WORD PTR DS:[04E9h],0
	  JNE	  BASIC_IO
	  CMP	  WORD PTR DS:[071Fh],0
	  JNE	  BASIC_IO
	  CMP	  BYTE PTR DS:[0758h],0
	  JNE	  BASIC_IO
	  CMP	  BYTE PTR DS:[071Bh],0
	  JNE	  BASIC_IO
	  CMP	  BYTE PTR DS:[0029h],50h  ; 80 bytes per line?
	  JNE	  BASIC_IO
	  CMP	  BYTE PTR DS:[0056h],25   ; on bottom line of screen?
	  JE	  BASIC_IO		   ; if so, let basic handle it
	  CMP	  WORD PTR [BP+8],26CCh  ; just regular i/o?
	  JE	  STRING_IO
	  CMP	  WORD PTR [BP+8],148Dh  ; just repeated blanks?
	  JE	  REPEAT_BLANKS

;    allow original B4 service routine to perform I/O

BASIC_IO:
	  POP	  BP
	  JMP	  DWORD PTR ORIG_INTB4 ; pass control to original B4



; ----------


;    repeated blanks sent to screen

REPEAT_BLANKS:

	  PUSH	  BX		       ; save registers used
	  PUSH	  DX
	  PUSH	  DI
	  PUSH	  ES
	  PUSH	  CX


;	  ...	  1) store character count and mark the flag for type of i/o

	  MOV	  CHAR_COUNT,CH        ; save character count
	  OR	  LOCAL_FLAG,JUST_BLANKS
	  MOV	  RESIDUAL,DH	       ; dh should be untouched
	  JMP	  SETUP_START


; ----------


;    write the string for basic

STRING_IO:

	  PUSH	  BX		       ; save registers used,
	  PUSH	  DX
	  PUSH	  DI
	  PUSH	  ES



;	  ...	  1) backup to start of string and store character count

	  DEC	  SI		       ; backup to start of string
	  MOV	  CHAR_COUNT,DH        ; save character count
	  MOV	  LOCAL_FLAG,0	       ; clear the flag for string i/o
	  MOV	  RESIDUAL,1	       ; dh to contain 1 on exit


;	  ...	  2) load current page from DS:[0049]

SETUP_START:
	  MOV	  BL,BYTE PTR DS:[0049h]    ; load current page
	  SUB	  BH,BH
	  SHL	  BX,1		       ; convert to a table offset
	  MOV	  DI,PAGE_START[BX]    ; load start of page
	  MOV	  STARTING_PAGE,DI     ; save the page
	  MOV	  END_OF_PAGE,DI       ; save ending page pointer
	  ADD	  END_OF_PAGE,3840



;	  ...	  3) load current position on page from DS:[0056]

	  MOV	  DX,WORD PTR DS:[0056h]    ; load current position
	  DEC	  DH		       ; basic counts from 1 instead of 0
	  DEC	  DL		       ; basic counts from 1 instead of 0
	  MOV	  BL,DL 	       ; load row number
	  SHL	  BX,1		       ; two bytes per table entry
	  ADD	  DI,LINE_START[BX]    ; add in start of line
	  MOV	  STARTING_LINE,DI     ; store this for later


	  MOV	  DL,DH 	       ; bring down column number
	  SUB	  DH,DH
	  ADD	  DI,DX 	       ; add in column position
	  ADD	  DI,DX 	       ; account for attribute bytes



;	  ...	  4) load attribute for string from DS:[004E]

	  MOV	  BH,BYTE PTR DS:[004Eh]  ; load attribute for string



;	  ...	  5) move character count to CX for use in loop

	  SUB	  CH,CH 	       ; clear upper byte
	  MOV	  CL,CHAR_COUNT        ; load the character count



;	  ...	  6) load type of crt display from 0000:463

	  SUB	  AX,AX 	       ; address system memory
	  MOV	  ES,AX

	  MOV	  DX,WORD PTR ES:[463h]      ; load address of display adapter
	  ADD	  DX,6			     ; address crt status port


;	  ...	  7) address the screen segment

	  MOV	  AX,0B000h	       ; screen seg for monochrome card
	  CMP	  DX,03DAh	       ; is this a graphic card?
	  JNE	  MONOCHROME

	  MOV	  AX,0B800h	       ; load screen seg for graphic card

MONOCHROME:

	  MOV	  ES,AX 	       ; address the screen buffer



;	  ...	  8) move string to screen while synchronizing
;		      with horizontal retrace

	  TEST	  LOCAL_FLAG,JUST_BLANKS
	  JZ	  DISPLAY_LOOP

	  MOV	  BL,20h	       ; load a blank
BLANK_LOOP:
	  CALL	  DISPLAY_CHAR	       ; display a line of blanks for basic
	  LOOP	  BLANK_LOOP
	  JMP	  UPDATE_POSITION


DISPLAY_LOOP:
	  LODSB 		       ; load next character

	  CMP	  AL,20h	       ; special character?
	  JGE	  NOT_SPECIAL
	  CALL	  SPECIAL
	  JZ	  ANOTHER_CHAR	       ; if flag set, go for another character

NOT_SPECIAL:

	  MOV	  BL,AL

	  CLI
HSYNC_WAIT1:
	  IN	  AL,DX 	       ; check for horizontal retrace
	  TEST	  AL,1
	  JNZ	  HSYNC_WAIT1	       ; wait for retrace
HSYNC_WAIT2:
	  IN	  AL,DX 	       ; check for horizontal retrace
	  TEST	  AL,1
	  JZ	  HSYNC_WAIT2	       ; wait for retrace

	  MOV	  AX,BX
	  STOSW 		       ; store character and attribute

ANOTHER_CHAR:
	  STI

	  CMP	  DI,END_OF_PAGE
	  JL	  NOT_SCROLLED

	  MOV	  AL,0Dh	       ; force a carriage return
	  CALL	  SPECIAL

NOT_SCROLLED:

	  LOOP	  DISPLAY_LOOP	       ; repeat cx times



;	  ...	 9) update cursor position on screen


UPDATE_POSITION:
	  MOV	  AX,DI
	  SUB	  AX,STARTING_PAGE
	  SHR	  AX,1		       ; eliminate attribute bytes
	  SUB	  DX,DX
	  MOV	  BX,80 	       ; divide by bytes per line
	  DIV	  BX		       ;   quotient in AL (ROW)
				       ;   remainder in DL (COLUMN)
	  MOV	  DH,AL
	  MOV	  BH,BYTE PTR DS:[0049h]    ; load page number
	  MOV	  AH,2		       ; request new position
	  INT	  10h


;	  ...	  10) update cursor position in database at DS:[0056]

	  XCHG	  DH,DL 	       ; basic likes this reversed
	  INC	  DH		       ; basic counts from 1 instead of 0
	  INC	  DL		       ; basic counts from 1 instead of 0
	  MOV	  WORD PTR DS:[0056h],DX


;	  ...	  11) leave registers in manner BASIC expects


	  TEST	  LOCAL_FLAG,JUST_BLANKS
	  JZ	  STRING_DISPLAYED

	  POP	  CX		       ; restore original blank count
	  MOV	  CH,1		       ; allow basic to decrement this to 0
	  JMP	  RESTORE_REGS


STRING_DISPLAYED:		       ; for a string
	  MOV	  CX,SI 	       ;  CX must point to last character
	  DEC	  CX


RESTORE_REGS:
	  POP	  ES
	  POP	  DI
	  POP	  DX

	  MOV	  DH,RESIDUAL	       ; let decrement instr take this to zero
				       ;  inside basic interpreter
	  POP	  BX
	  POP	  BP


	  ADD	  SP,4		       ; throw away offset and code segment
				       ;  from INT B4
	  POPF			       ; restore flags from interrupt

	  POP	  AX		       ; throw away near call on stack
	  PUSH	  ROM_SEGMENT	       ; and convert to a far call
	  PUSH	  AX
	  MOV	  AL,20h	       ; leave a character in AL for basic
				       ;  to compare against a CR  (0Dh)
	  RET			       ; return to ROM


PRINTER   ENDP

;______________________________________________________________________________

;  subroutine to handle special control characters

SPECIAL   PROC	  NEAR

; ----------

	  CMP	  AL,0Ah	       ; line feed?
	  JE	  NEW_LINE


; ----------

	  CMP	  AL,0Bh	       ; home?
	  JNE	  NOT_HOME
	  MOV	  DI,STARTING_PAGE     ; start over at top of screen
	  MOV	  STARTING_LINE,DI
	  SUB	  AL,AL
	  RET

NOT_HOME:

; ----------

	  CMP	  AL,0Ch	       ; clear screen?
	  JNE	  NOT_CLEAR
	  MOV	  DI,STARTING_PAGE
	  MOV	  STARTING_LINE,DI
	  MOV	  AL,0		       ; clear whole window
	  JMP	  SHORT SCROLL_SCREEN


NOT_CLEAR:

; ----------

	  CMP	  AL,0Dh	       ; carriage return?
	  JNE	  NOT_CR

NEW_LINE:
	  MOV	  DI,STARTING_LINE
	  ADD	  DI,160
	  JMP	  SHORT TEST_RIGHT

NOT_CR:

; ----------

	  CMP	  AL,1Ch	       ; move right?
	  JNE	  NOT_RIGHT
	  ADD	  DI,2
	  JMP	  SHORT TEST_RIGHT

NOT_RIGHT:

; ----------

	  CMP	  AL,1Dh	       ; move left?
	  JNE	  NOT_LEFT
	  SUB	  DI,2
	  JMP	  SHORT TEST_LEFT

NOT_LEFT:

; ----------

	  CMP	  AL,1Eh	       ; move up?
	  JNE	  NOT_UP

	  SUB	  DI,160
	  JMP	  SHORT TEST_LEFT

NOT_UP:

; ----------

	  CMP	  AL,1Fh	       ; move down?
	  JNE	  NOT_DOWN

	  ADD	  DI,160
	  JMP	  SHORT TEST_RIGHT

NOT_DOWN:
	  JMP	  SHORT TEST_FOR_TAB

; ----------

TEST_RIGHT:
	  MOV	  AX,DI 	       ; are we past line 24?
	  SUB	  AX,STARTING_PAGE
	  CMP	  AX,3840
	  JL	  VALID_RIGHT
	  MOV	  DI,STARTING_PAGE     ; back at start of last line
	  ADD	  DI,3680
	  MOV	  STARTING_LINE,DI

	  MOV	  AL,1		       ; scroll one line
	  JMP	  SHORT SCROLL_SCREEN

VALID_RIGHT:
	  SUB	  AL,AL
	  RET

; ----------

SCROLL_SCREEN:
	  PUSH	  CX
	  MOV	  CX,0		       ; start in upper left corner
	  PUSH	  DX
	  MOV	  DX,174Fh	       ; end in lower right, one line up
	  MOV	  AH,6
	  PUSHF 		       ; simulate an INT 10
	  CALL	  DWORD PTR ORIG_INT10
	  POP	  DX
	  POP	  CX
	  SUB	  AL,AL
	  RET

; ----------

TEST_LEFT:
	  CMP	  DI,STARTING_PAGE
	  JGE	  VALID_LEFT

	  MOV	  DI,STARTING_PAGE

VALID_LEFT:
	  SUB	  AL,AL
	  RET

; ----------

TEST_FOR_TAB:

	  CMP	  AL,09h	       ; tab?
	  JNE	  NOT_TAB

	  PUSH	  CX
	  PUSH	  DX
	  MOV	  AX,DI
	  SUB	  AX,STARTING_LINE
	  SHR	  AX,1		       ; discount attribute bytes
	  SUB	  DX,DX
	  MOV	  CX,8
	  DIV	  CX
	  MOV	  CX,8		       ; tab positions are every 8 charactes
	  SUB	  CX,DX 	       ; subtract off remainder
	  MOV	  BL,' '               ; write some blanks
	  POP	  DX
TAB_LOOP:
	  CALL	  DISPLAY_CHAR
	  LOOP	  TAB_LOOP

	  POP	  CX
	  SUB	  AL,AL
	  RET

NOT_TAB:

; ----------

	  CMP	  AL,08h	       ; backspace?
	  JNE	  NOT_BACKSPACE

	  CMP	  DI,STARTING_LINE
	  JE	  AT_START
	  SUB	  DI,2		       ; back up a space

AT_START:
	  MOV	  BL,' '               ; write a blank
	  CALL	  DISPLAY_CHAR

	  SUB	  DI,2		       ; back up a space
	  SUB	  AL,AL
	  RET

NOT_BACKSPACE:

; ----------

	  MOV	  AH,2
	  SUB	  AH,1		       ; set flag to display the char
	  RET

SPECIAL   ENDP

;______________________________________________________________________________

;  routine to display a character in BL - used only for special characters
;     attribute is in BH


DISPLAY_CHAR	  PROC	NEAR

	  CLI
HSYNC_WAIT3:
	  IN	  AL,DX 	       ; check for horizontal retrace
	  TEST	  AL,1
	  JNZ	  HSYNC_WAIT3	       ; wait for retrace
HSYNC_WAIT4:
	  IN	  AL,DX 	       ; check for horizontal retrace
	  TEST	  AL,1
	  JZ	  HSYNC_WAIT4	       ; wait for retrace

	  MOV	  AX,BX
	  STOSW 		       ; store character and attribute

	  STI
	  RET

DISPLAY_CHAR	  ENDP


;______________________________________________________________________________

LASTADDR  LABEL   BYTE

COPYRIGHT LABEL   BYTE
	  DB	  10,13
	  DB  '         CLUBware  (tm)',10,13,10,13
	  DB  'BASPRINT - Enhancement to the Basic Interpreter Print statement'
	  DB	  10,13,10,13
	  DB  '         Copyright 1984 Rayhawk Automation N.W. Inc',10,13
	  DB  '                        P.O. Box 1427',10,13
	  DB  '                        Beaverton, Oregon   97075',10,13,'$'

;______________________________________________________________________________

BASPRINT  PROC	  FAR


	  PUSH	  DS		       ; Push addr of Program Segment Prefix
	  SUB	  AX,AX 	       ; Zero AX
	  PUSH	  AX		       ; Push zero onto stack
;					 (offset of INT 20 within PSP)


;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	   take over the INT 10h		 |
;		      | 	   interrupt if not already done	 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

	  MOV	  DS,AX 		   ; address low memory
	  LDS	  BX,DWORD PTR DS:[02D0h]  ; load interrupt vector for int B4

	  MOV	  AX,WORD PTR PRINTER
	  CMP	  AX,WORD PTR DS:[BX]
	  JNE	  NOT_HERE_YET
	  MOV	  AX,WORD PTR PRINTER+2
	  CMP	  AX,WORD PTR DS:[BX+2]
	  JNE	  NOT_HERE_YET
	  MOV	  AX,WORD PTR PRINTER+4
	  CMP	  AX,WORD PTR DS:[BX+4]
	  JNE	  NOT_HERE_YET
	  MOV	  AX,WORD PTR PRINTER+6
	  CMP	  AX,WORD PTR DS:[BX+6]
	  JNE	  NOT_HERE_YET

	  JMP	  ALREADY_RESIDENT

NOT_HERE_YET:
	  SUB	  AX,AX
	  MOV	  DS,AX
	  MOV	  AX,WORD PTR DS:[40h] ; save original int10
	  MOV	  ORIG_INT10,AX
	  MOV	  AX,WORD PTR DS:[42h]
	  MOV	  ORIG_INT10+2,AX

	  MOV	  AX,SEG BASPRINT
	  MOV	  DS,AX
	  MOV	  DX,OFFSET INTSWAP    ; Load offset of interrupt service mod
	  MOV	  AX,2510h	       ; Prepare for DOS service call type 25
;					 to establish service for INT 10
	  INT	  21h		       ; Ask DOS to establish service

;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	   issue copyright message		 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

	  MOV	  DX,OFFSET COPYRIGHT
	  MOV	  AH,9
	  INT	  21h


;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	   modify INT 20 into INT 27 in the	 |
;		      | 	   program segment prefix		 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

	  MOV	  BYTE PTR ES:[01],27h ; Change INT 20h to INT 27h

;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	6) load address of ending tag into DX	 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

	  MOV	  AX,SEG LASTADDR
	  SUB	  AX,SEG BASPRINT
	  MOV	  CL,4		       ; prepare for 4 bit shift
	  SHL	  AX,CL 	       ; shift up (convert from seg to abs)
	  ADD	  AX,OFFSET LASTADDR   ; add address of bottom location
	  ADD	  AX,0103h	       ; Pad offset because DOS measures
;					  offset relative to Program
;					  Segment Prefix
	  MOV	  DX,AX 	       ; leave where DOS will find it

;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	7) use RET FAR to return to DOS and	 |
;		      | 	   leave service routine resident	 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

ALREADY_RESIDENT:

	  RET

BASPRINT  ENDP

SCRDSEG   ENDS

;______________________________________________________________________________


STACK	  SEGMENT PARA STACK 'STACK'
	  DB	  24 DUP('STACK***')
TOPSTACK  DB	  0
STACK	  ENDS

	  END	  BASPRINT
