TITLE  JDOS.ASM
;
COMMENT *

	Purpose:	Perform 'DOS' functions from within most programs.
	Allows other programs to be executed then return to the interrupted
	program.

	Created:	11-MAR-1989		Richard B. Johnson

	Modified:
	12-MAR-1989	V1.01			Richard B. Johnson
	Added sign-on screens using direct screen-writes.
	Added DOS version check. DOS V2.## does not work correctly.

	13-MAR-1989	V1.02			Richard B. Johnson
	Added code to intercept interrupt vectors that the interrupted
	program might be using. If the interrupted program was using
	the timer-tick, the program would crash.

	Changed reserved word "WIDTH" to label "@WIDTH". Would produce a
	warning message under MASM but not under OPTASM.

	14-MAR-1989	V1.03			Richard B. Johnson
	Added code to save the default interrupt table during initialization
	and use this default table during the spawned process. This should
	prevent possible reentry problems if spawned programs spawn other
	processes also.

	14-MAR-1989	V1.04			Richard B. Johnson
	Added code to save the state of four possible 8250 UARTS including
	any enabled interrupts and the baud-rate. The UART status is restored
	before returning to the interrupted program. This allows communi-
	cations programs to be interrupted, other communications programs
	executed with different baud-rates, etc., then the return to the
	interrupted program without any errors.

	14-MAR-1989	V1.05			Richard B. Johnson
	Added code to mask off the interrupt controller immediately
	upon return from the spawmed process. Some communications programs
	were leaving "hot" interrupts when they exited!! Since not even the
	stack is in a known place when the EXEC call returns, this could
	cause a crash. Symptom = "divide by zero error".

	16-MAR-1989	V1.06			Richard B. Johnson
	Added code to search for the screen segment to accommodate COMPAQ
	"compatibles" that ALWAYS have the screen segment at B800H without
	regard for any standards.

	16-MAR-1989	V1.07			Richard B. Johnson
	Fixed bug in code that failed to restore the interrupt controller
	mask after a return from the spawned process. No crashes were
	reported, but it's possible that a crash could occur if a program
	being executed failed to restore "hot" interrupt vectors when
	it was aborted. This might happen if the program being executed
	by the spawned process aborted because of a fatal error.

	Notice:		This program must be compiled as a '.COM' file!
	MASM JDOS;
	LINK JDOS;
	EXE2BIN JDOS.EXE JDOS.COM
	DEL JDOS.EXE
*
VERS	STRUC
	DB	'V1.07'			; Set version number here ONLY.
@VERS	DB	' '
VERS	ENDS
;
TRUE	EQU	-1			; Set logicals
FALSE	EQU	NOT TRUE
TESTING	EQU	FALSE			; Debugging conditional
CR	EQU	0DH
LF	EQU	0AH
INT_CTL	EQU	21H			; Interrupt controller mask addr.
INT_RES	EQU	20H			; Interrupt controller reset addr.
NOS_EOI	EQU	20H			; Non-specific end-of-interrupt
MS_DOS	EQU	21H			; Operating system interrupt
VIDEO	EQU	10H			; Video BIOS code interrupr
ENVIR	EQU	2CH			; Address of environment segment
KB	EQU	1024			; One kb =
MEM	EQU	32			; Extra bytes of memory required
TICK1	EQU	08H			; Clock tick
TICK2	EQU	1CH			; User timer
SCR_LIN	EQU	15			; Line to start
@WIDTH	EQU	28			; Width of the box
HEIGHT	EQU	6			; Height of the box
COL_SCR	EQU	(80 - @WIDTH) / 2 	; Column to center
SCR_WRD	EQU	80			; Words per line on the screen
LOCUS	EQU	(SCR_LIN * SCR_WRD * 2) + (COL_SCR * 2)
;
IN8250	STRUC
LIN_CTR	DB	?			; Line control register
DIVISOR	DW	?			; Divisor
INT_CTR	DB	?			; Interrupt control register
MOD_CTR	DB	?			; Modem control register
IN8250	ENDS
;
;	Possible UART base addresses
;
ADDR1	EQU	03F8H			; COM1
ADDR2	EQU	02F8H			; COM2
ADDR3	EQU	03E8H			; COM3
ADDR4	EQU	02E8H			; COM4
;
PSEG	SEGMENT	PARA PUBLIC 'CODE'
START	EQU	$
	ASSUME CS:PSEG, DS:PSEG, ES:PSEG, SS:NOTHING
	ORG	100H
MAIN	PROC	NEAR
	JMP	INIT
MAIN	ENDP
;
;	Write the string addressed by SI to a box on the screen.
;
SIGNON	PROC	NEAR
	PUSH	ES
	MOV	ES,WORD PTR [SCR_SEG]	; Pick up screen segment
	MOV	DI,LOCUS		; Where to start the box
	MOV	CX,HEIGHT		; Height of the box
BOX0:	PUSH	CX			; Save height
	MOV	AH,01110000B		; Reverse video
	MOV	CX,@WIDTH		; Width of the box
BOX1:	LODSB				; Get LOGO byte
	STOSW				; Write the word
	LOOP	BOX1			; Continue
	ADD	DI,(SCR_WRD - @WIDTH ) * 2 ; For next line
	POP	CX			; Restore height
	LOOP	BOX0
	POP	ES			; Restore segment
	RET
SIGNON	ENDP
;
;	This is the main clock interrupt entry point. We check to see if
;	[EFLAG] has been set. If not, we continue to old vector. If it has
;	been set, we check to see if the sub-process has already been spawned
;	by looking at [SPAWN]. If the process has not been spawned, we check
;	the interrupted program's CS. It must be below 640k and above our
;	CS or else a sub-process is not created.
;
ENTRY	PROC	FAR
	PUSH	BP
	MOV	BP,SP			; Set up index
	PUSH	AX			; Save a register
	PUSH	DS			; Save segment
;
	PUSH	CS
	POP	DS			; DS = CS
;
	CMP	BYTE PTR [SPAWN],0	; See if we spawned the process
	JNZ	BYPASS			; Yes, continue
	CMP	BYTE PTR [EFLAG],0	; Check entry flag
	JZ	BYPASS			; We don't want to do it
;
;	See what we interrupted. Must be between current CS and end of
;	640k for us to enter.
;
					; BP = Pushed BP
					; BP + 2 = IP
					; BP + 4 = CS
					; BP + 6 = FLAGS
	MOV	AX,CS			; Pick up our code-segment
	CMP	[BP+4],AX		; Compare with interrupted segment
	JC	BYPASS			; Not the code to interrupt
	CMP	[BP+4],9000H		; Check high limit
	JNC	BYPASS			; Not the code to interrupt
;
	MOV	BYTE PTR [SPAWN],0FFH	; Set flag
	CALL	@LOCAL			; Call the local routine
	MOV	BYTE PTR [SPAWN],0	; Reset the flag
	MOV	BYTE PTR [EFLAG],0	; Reset rentry flag.
;
BYPASS:	POP	DS			; Restore segment
	POP	AX			; Restore register
	POP	BP			; Restore index
	JMP	DWORD PTR CS:[OLD_CLK]	; Continue
ENTRY	ENDP
;
;	Keyboard interrupt extension. If the call is to check status, we
;	ignore it and simply jump to the old interrupt vector. If the call
;	is to receive a character, we make the call ourselves, them check
;	the character. If the character is the one used to spawn the sub-
;	process, we set the sub-process flag, [EFLAG]. Then we substitute
;	a null character for the key-code and return.
;
LCL_KBD	PROC	FAR
	CMP	BYTE PTR CS:[SPAWN],0	; Already in the spawned process?
	JZ	ASK			; No, Check the request
NOCHR:	JMP	DWORD PTR CS:[OLD_KBD]	; Continue
ASK:	CMP	AH,0			; Do we want a character?
	JNZ	NOCHR			; No, checking status
	PUSHF				; Dummy INT
	CALL	DWORD PTR CS:[OLD_KBD]	; Go get the character
	CMP	AX,2B1CH		; Character we want?
	JNZ	HOME			; Nope
	PUSH	BX			; Yes, beep
	PUSH	BP			; Is sometimes destroyed
	MOV	AX,0E07H		; Sound the bell
	MOV	BX,7			; Normal attribute
	INT	VIDEO			; Video ROM BIOS
	POP	BP			; Restore registers
	POP	BX
	MOV	BYTE PTR CS:[EFLAG],0FFH ; ..and set the flag
	MOV	AX,0			; Substitute
HOME:	RET	2			; Return to caller
LCL_KBD	ENDP
;
;	This is the local procedure to create a subprocess.
;
@LOCAL	PROC	NEAR
	MOV	AX,[BP+4]		; BP = pushed BP
					; BP + 2 = IP
					; BP + 4 = CS
					; BP + 6 = FLAGS
	MOV	WORD PTR [CS_SAV],AX	; Save the caller's code-segment
	CLI				; No interrupts
	CLD				; Forwards
	MOV	WORD PTR [SS_SAV],SS	; Save segment
	MOV	WORD PTR [SP_SAV],SP	; Save pointer
	MOV	AX,CS			; Get our segment
	MOV	SS,AX			; Into stack segment
	MOV	SP,OFFSET STKTOP	; Set up new stack
	STI				; Allow interrupts
	PUSH	BX			; Save all registers
	PUSH	CX
	PUSH	DX
	PUSH	SI
	PUSH	DI
	PUSH	ES
	MOV	DS,AX			; Fix up segments
	MOV	ES,AX
;
	MOV	AL,NOS_EOI
	OUT	INT_RES,AL		; Reset hardware controller
;
	CALL	SAV_UAR			; Save UART parameters
	CALL	SAV_DIR			; Save the directory
	CALL	SAV_DTA			; Save data transfer area
	CALL	SAV_SCR			; Save screen data
	CALL	SAV_MEM			; Free up some memory
	CALL	CLR_SCR			; Clear the screen
	CALL	SAV_INT			; Save interrupt table

	MOV	BYTE PTR DS:[80H],0	; Show no bytes typed
	MOV	WORD PTR [SP_NEW],SP	; Save stack
	MOV	DX,OFFSET COMMAND	; Point to COMMAND.COM
	MOV	BX,OFFSET BLOCK		; Parameter block for the load
	MOV	AX,4B00H		; Load/execute program
	INT	MS_DOS			; Doit-toit
	CLI				; Quiet while I fix
	MOV	AL,10011101B		; Mask off all but required interrupts
;                  ||||||||_________ Timer
;                  |||||||__________ Keyboard
;                  ||||||___________ Reserved
;                  |||||____________ Async (2)
;                  ||||_____________ Async (1)
;                  |||______________ Hard drive
;                  ||_______________ Floppy drive
;                  |________________ Printer
;
	OUT	INT_CTL,AL		; The interrupt controller
	MOV	AX,CS			; Restore all segments
	MOV	DS,AX
	MOV	ES,AX
	MOV	SS,AX
	MOV	SP,WORD PTR [SP_NEW]	; Restore stack
	STI				; Allow interrupts
	CLD				; Could be messed up
;
	JNC	GOOD
	MOV	SI,OFFSET ERROR1	; Point to string
	CALL	PROMPT			; Display to console
	MOV	AH,0			; Get response
	PUSHF
	CALL	DWORD PTR [OLD_KBD]	; Wait for a response
;
GOOD:	CALL	RES_DIR			; Restore the directory
	CALL	RES_DTA			; Restore data transfer area
	CALL	RES_MEM			; Re-acquire the memory we used
	CALL	RES_SCR			; Restore screen
	CALL	RES_INT			; Restore interrupt table
	CALL	RES_UAR			; Restore UART parameters.
	POP	ES			; Restore all registers
	POP	DI
	POP	SI
	POP	DX
	POP	CX
	POP	BX
	CLI
	MOV	SS,WORD PTR [SS_SAV]
	MOV	SP,WORD PTR [SP_SAV]
	STI
	RET
@LOCAL	ENDP
;
;	Free up memory. Save memory contents in file VIRTUAL.MEM.
;
SAV_MEM	PROC	NEAR
	MOV	DX,OFFSET VIRMEM	; Point to code-data filename
	MOV	AX,3C00H		; Create file function
	XOR	CH,CH			; File attributes
	MOV	CL,00000110B		; Hidden/system
	INT	MS_DOS			; Create the file
	JNC	W1			; Good create
	MOV	DX,OFFSET VIRMEM	; Point to filename
	MOV	SI,OFFSET ERROR5	; Point to 'create'
	JMP	SHO_ERR			; Show the error, implied return
;
W1:	MOV	BX,AX			; File handle
	PUSH	DS			; Save segment
	MOV	DS,WORD PTR [CS_SAV]	; Pick up code segment
WR_ALL:	MOV	AX,4000H		; Write to file
	MOV	CX,0FFFFH 		; 64k -1
	XOR	DX,DX			; Offset 0
	INT	MS_DOS			; Write the file
	CMP	AX,CX			; Write as requested?
	JNZ	WR_OK			; Exit, bad write
	MOV	AX,DS			; Pick up segment
	ADD	AX,1000H		; Next segment
	MOV	DS,AX			; Index 64k
	CMP	AX,0A000H		; Check limit
	JC	WR_ALL			; Continue
WR_OK:	POP	DS			; Restore segment
	JNC	W2			; Good write
	MOV	DX,OFFSET VIRMEM	; Point to filename
	MOV	SI,OFFSET ERROR6	; Point to 'write'
	JMP	SHO_ERR			; Show the error, implied return
;
W2:	MOV	AX,3E00H		; Close file handle
	INT	MS_DOS
	JNC	W3			; Good close
	MOV	DX,OFFSET VIRMEM	; Point to filename
	MOV	SI,OFFSET ERROR7	; Point to 'close'
	JMP	SHO_ERR			; Show the error, implied return
;
W3:	MOV	BX,(100H SHR 4)		; Release all memory above 100H
	PUSH	ES			; Save segment
	MOV	ES,WORD PTR [CS_SAV]	; Caller's code-segment
	MOV	AX,4A00H		; Modify memory
	INT	MS_DOS
	POP	ES			; Restore segment
	IF	TESTING
	JNC	MEM1
;
	MOV	SI,OFFSET ERROR2	; Point to memory error message.
	CALL	PROMPT			; Desplay on the screen
	MOV	AH,0
	PUSHF
	CALL	DWORD PTR [OLD_KBD]	; Wait for a response
	ENDIF
MEM1:	RET
SAV_MEM	ENDP
;
;	Acquire memory. Restore it's contents from file VIRTUAL.MEM.
;
RES_MEM	PROC	NEAR
	MOV	BX,0FFFFH 		; Get everything back.
	MOV	AX,4A00H		; Modify memory
	PUSH	ES
	MOV	ES,WORD PTR [CS_SAV]	; Pick up code segment
	INT	MS_DOS
	POP	ES
;
	MOV	DX,OFFSET VIRMEM	; Point to code-data filename
	MOV	AX,3D00H		; Open file for reading
	XOR	CX,CX			; File attributes
	INT	MS_DOS			; Open the file
	JNC	Z1			; Good open
	MOV	DX,OFFSET VIRMEM	; Point to filename
	MOV	SI,OFFSET ERROR8	; Point to 'open'
	JMP	SHO_ERR			; Show error, implied return
;
Z1:	PUSH	DS			; Save segment
	MOV	DS,WORD PTR [CS_SAV]	; Pick up code segment
	MOV	BX,AX			; File handle
RD_ALL:	MOV	CX,0FFFFH		; 64k -1
	XOR	DX,DX			; Offset 0
	MOV	AX,3F00H		; Read from file
	INT	MS_DOS			; Write the file
	JC	RD_DON			; Bad read
	CMP	CX,AX			; See if end of file
	JNZ	RD_DON			; No more bytes, all done
	MOV	AX,DS			; Pick up segment
	ADD	AX,1000H		; Next 64k block
	MOV	DS,AX			; Update segment
	JMP	SHORT RD_ALL		; Continue
RD_DON:	POP	DS			; Restore segment
	JNC	Z2
	MOV	DX,OFFSET VIRMEM	; Point to filename
	MOV	SI,OFFSET ERROR9	; Point to 'read'
	JMP	SHO_ERR			; Show error, implied return
;
Z2:	MOV	AX,3E00H		; Close file handle
	INT	MS_DOS
	JNC	Z3			; Good close
	MOV	DX,OFFSET VIRMEM	; Point to filename
	MOV	SI,OFFSET ERROR7	; Point to 'close'
	JMP	SHO_ERR			; Show error, implied return
;
Z3:	MOV	DX,OFFSET VIRMEM	; Point to code-data filename
	MOV	AX,4100H		; Delete the file
	INT	MS_DOS
	JNC	Z4			; Good delete
	MOV	DX,OFFSET VIRMEM	; Point to filename
	MOV	SI,OFFSET ERROR10	; Point to 'delete'
	JMP	SHO_ERR			; Show error, implied return
;
Z4:	RET
RES_MEM	ENDP
;
;	Restore the DTA
;
RES_DTA	PROC	NEAR
	PUSH	DS			; Save segment
	LDS	DX,DWORD PTR [DTA]	; Get old DTA address
	MOV	AX,1A00H		; Set DTA address
	INT	MS_DOS
	POP	DS			; Restore segment
	RET
RES_DTA	ENDP
;
;	Save the DTA
;
SAV_DTA	PROC	NEAR
	PUSH	ES			; Save segment
	MOV	AX,2F00H		; Get DTA address
	INT	MS_DOS
	MOV	WORD PTR [DTA_OFF],BX	; Save current DTA address
	MOV	WORD PTR [DTA_SEG],ES
	POP	ES			; Restore segment.
	RET
SAV_DTA	ENDP
;
;	Save the interrupt table.
;
SAV_INT	PROC	NEAR
	IN	AL,INT_CTL		; Get interrupt controller mask
	MOV	BYTE PTR [OLD_MSK],AL	; Save the mask
	OR	AL,00011100B		; Reset IRQ3, IRQ4, Reserved
	OUT	INT_CTL,AL		; Set the mask
;
;	Save present interrupt vectors.
;
	XOR	AX,AX			; Get a zero
	MOV	SI,AX			; Offset zero
	MOV	CX,KB			; 1k to move
;
	PUSH	DS
	MOV	DS,AX			; Point to interrupt table
    MOV DI,OFFSET TABL  ; Point to our data-space
	CLI
	REP	MOVSB			; Copy to our CS
	STI
	POP	DS			; Restore segment
;
;	Set the default interrupt vectors.
;
	XOR	AX,AX
	MOV	DI,AX			; Offset zero
	MOV	SI,OFFSET DEFAULT	; Where the interrupt data is
	MOV	CX,KB			; 1k to replace
;
	PUSH	ES			; Save the segment
	MOV	ES,AX			; Zero the segment
	CLI
	REP	MOVSB
	STI
	POP	ES			; Restore segment
	RET
SAV_INT	ENDP
;
;	Restore the interrupt table
;
RES_INT	PROC	NEAR
	XOR	AX,AX
	MOV	DI,AX			; Offset zero
    MOV SI,OFFSET TABL  ; Where the interrupt data is
	MOV	CX,KB			; 1k to replace
;
	PUSH	ES			; Save the segment
	MOV	ES,AX			; Zero the segment
	CLI
	REP	MOVSB
	STI
	POP	ES			; Restore segment
;
	MOV	AL,BYTE PTR [OLD_MSK]	; Get old controller mask
	OUT	INT_CTL,AL		; Set interrupt controller mask
	RET
RES_INT	ENDP
;
;	Clear the screen, write message.
;
CLR_SCR	PROC	NEAR
	MOV	AL,BYTE PTR [SCR_MOD]	; Get screen mode byte
	MOV	AH,0			; Set mode function
	INT	VIDEO			; That should clear the screen
	MOV	SI,OFFSET LOGO2		; Point to 'EXIT' message
	CALL	SIGNON			; Print to screen
	RET
CLR_SCR	ENDP
;
;	Save the default directory.
;
SAV_DIR	PROC	NEAR
	MOV	AH,47H			; Get current directory function
	XOR	DL,DL			; Get default
	MOV	SI,OFFSET CUR_DIR	; Where to copy the string
	INT	MS_DOS			; Put it there
	MOV	AH,19H			; Get current disk
	INT	MS_DOS			; From DOS
	ADD	AL,'A'			; Drive bias
	MOV	BYTE PTR [DRV],AL	; Put in drive specifier
	RET
SAV_DIR	ENDP
;
SAV_SCR	PROC	NEAR
	MOV	AH,15			; Return video state
	INT	VIDEO			; Video BIOS
	MOV	BYTE PTR [SCR_MOD],AL	; Current mode
	MOV	BYTE PTR [SCR_COL],AH	; Number of columns
	MOV	BYTE PTR [SCR_PAG],BH	; Current page
;
	MOV	AH,3			; Get cursor position
	INT	VIDEO			; Video BIOS
	MOV	WORD PTR [CUR_POS],DX	; Save cursor position
	MOV	WORD PTR [CUR_TYP],CX	; Save cursor type
;
	MOV	DX,OFFSET SCREEN	; Point to screen-data filename
	MOV	AX,3C00H		; Create file function
	XOR	CH,CH			; File attributes
	MOV	CL,00000110B		; Hidden/system
	INT	MS_DOS			; Create the file
	JNC	Y1			; Good create
	MOV	DX,OFFSET SCREEN	; Point to filename
	MOV	SI,OFFSET ERROR5	; Point to 'create'
	JMP	SHO_ERR			; Show error, implied return
;
Y1:	MOV	BX,AX			; File handle
	MOV	CX,0FFFFH		; 64k -1
	XOR	DX,DX			; Offset zero
	MOV	AX,4000H		; Write to file
	PUSH	DS			; Save segment
	MOV	DS,WORD PTR [SCR_SEG]	; Pick up screen segment
	INT	MS_DOS			; Write the file
	POP	DS			; Restore segment
	JNC	Y2			; Good write
	MOV	DX,OFFSET SCREEN	; Point to filename
	MOV	SI,OFFSET ERROR6	; Point to 'write'
	JMP	SHO_ERR			; Show error, implied return
;
Y2:	MOV	AX,3E00H		; Close file handle
	INT	MS_DOS
	JNC	Y3			; Good close
	MOV	DX,OFFSET SCREEN	; Point to filename
	MOV	SI,OFFSET ERROR7	; Point to 'close'
	JMP	SHO_ERR			; Show error, implied return
Y3:	RET
SAV_SCR	ENDP
;
;	Restore the default directory.
;
RES_DIR	PROC	NEAR
	MOV	AH,0EH			; Change drive function
	MOV	DL,BYTE PTR [DRV]	; Get saved drive letter
	SUB	DL,'A'			; Remove bias
	INT	MS_DOS			; Go do it
;
	MOV	AH,3BH			; Change directory function
	MOV	DX,OFFSET DRV		; Full drive/path name
	INT	MS_DOS
	RET
RES_DIR	ENDP
;
RES_SCR	PROC	NEAR
	MOV	DX,OFFSET SCREEN	; Point to screen-data filename
	MOV	AX,3D00H		; Open file for reading
	XOR	CX,CX			; File attributes
	INT	MS_DOS			; Open the file
	JNC	X1			; Good open
	MOV	DX,OFFSET SCREEN	; Filename
	MOV	SI,OFFSET ERROR8	; Point to 'open'
	JMP	SHO_ERR			; Show the error, Implied return
;
X1:	MOV	BX,AX			; File handle
	MOV	CX,0FFFFH		; 64k -1
	XOR	DX,DX			; Offset zero
	MOV	AX,3F00H		; Read from file
	PUSH	DS			; Save segment
	MOV	DS,WORD PTR [SCR_SEG]	; Pick up screen segment
	INT	MS_DOS			; Write the file
	POP	DS			; Restore segment
	JNC	X2			; Good read
	MOV	DX,OFFSET SCREEN	; Filename
	MOV	SI,OFFSET ERROR9	; Point to 'read'
	JMP	SHO_ERR			; Implied return
;
X2:	MOV	AX,3E00H		; Close file handle
	INT	MS_DOS
	JNC	X3			; Good close
	MOV	DX,OFFSET SCREEN	; Filename
	MOV	SI,OFFSET ERROR7	; Point to 'close'
	JMP	SHO_ERR			; Implied return
;
X3:	MOV	DX,OFFSET SCREEN	; Point to screen-data filename
	MOV	AX,4100H		; Delete the file
	INT	MS_DOS
	JNC	X4			; Good delete
	MOV	DX,OFFSET SCREEN	; Filename
	MOV	SI,OFFSET ERROR10	; Point to 'delete'
	JMP	SHO_ERR			; Implied return
;
X4:	MOV	AH,5			; Select active page
	MOV	AL,BYTE PTR [SCR_PAG]	; Get previous screen page
	INT	VIDEO			; Video BIOS
;
	MOV	AH,1			; Set cursor type
	MOV	CX,WORD PTR [CUR_TYP]	; Get previous cursor type
	INT	VIDEO			; Video BIOS
;
	MOV	AH,2			; Set cursor position
	MOV	BH,BYTE PTR [SCR_PAG]	; Page number.
	MOV	DX,WORD PTR [CUR_POS]	; Get saved cursor position
	INT	VIDEO			; Video ROM BIOS
	RET
RES_SCR	ENDP
;
PROMPT	PROC	NEAR
	LODSB				; Get memory byte
	TEST	AL,AL			; Check for a null
	JZ	PEXIT			; All done
	MOV	BX,7			; Page zero/normal attribute
	MOV	AH,14			; Dumb terminal mode
	INT	VIDEO			; Video BIOS
	JMP	SHORT PROMPT		; Continue
PEXIT:	RET
PROMPT	ENDP
;
;	Upon entry, DX points to filename. Si points to string for open/close
;	etc. The error message is printed to the screen.
;
SHO_ERR	PROC	NEAR
	PUSH	SI
	MOV	SI,OFFSET ERROR4	; Point to "Can't"
	CALL	PROMPT			; Write to screen
	POP	SI			; Point to create/write/read, etc
	CALL	PROMPT			; Write to screen
	MOV	SI,OFFSET ERROR11	; Point to "file"
	CALL	PROMPT			; Write to screen
	MOV	SI,DX			; Get file name
	JMP	PROMPT			; Write to screen, implied return.
SHO_ERR	ENDP
;
;	Save UART parameters.
;
SAV_UAR	PROC	NEAR
	MOV	DI,OFFSET UAR_PAR	; Point to Uart parameter table
	MOV	DX,ADDR1		; Pick up address of UART
	CALL	GET_PAR			; Get UART parameters
	MOV	DX,ADDR2		; Pick up address of UART
	CALL	GET_PAR			; Get UART parameters
	MOV	DX,ADDR3		; Pick up address of UART
	CALL	GET_PAR			; Get UART parameters
	MOV	DX,ADDR4		; Pick up address of UART
	CALL	GET_PAR			; Get UART parameters
	RET
SAV_UAR	ENDP
;
;	Get UART parameters. UART base-port is in DX. Storage for
;	parameters is pointed to by DI. After saving parameters, disable
;	any interrupt enable bits.
;
GET_PAR	PROC	NEAR
	ADD	DX,3			; Offset to line control register
	IN	AL,DX			; Get line control bits
	MOV	BL,AL			; Save the bits
	STOSB				; Save in memory
	OR	AL,10000000B		; Divisor access bit
	OUT	DX,AL			; Set divisor access bit
	SUB	DX,3			; Back to base port
	IN	AX,DX			; Get divisor
	STOSW				; Save in memory
	ADD	DX,3			; Offset to line control register
	MOV	AL,BL			; Get saved line-control bits
	OUT	DX,AL			; Reset divisor latch
	SUB	DX,2			; Set to interrupt control register
	IN	AL,DX			; Get the control bits
	STOSB				; Save in memory
	XOR	AL,AL			; Turn off interrupt control
	OUT	DX,AL			; Reset the bits
	ADD	DX,3			; Offset to modem control
	IN	AL,DX			; Save modem control bits
	STOSB				; Save in memory
	MOV	CX,6			; Registers to read
	SUB	DX,4			; Back to base port
RDALL1:	IN	AL,DX			; Read port
	INC	DX			; Ready next
	LOOP	RDALL1			; Read all ports
	MOV	AL,NOS_EOI		; Non-specific end-of-interrupt
	OUT	INT_RES,AL		; Reset hardware controller.
	RET
GET_PAR	ENDP
;
;	Restore UART parameters.
;
RES_UAR	PROC	NEAR
	MOV	SI,OFFSET UAR_PAR	; Point to Uart parameter table
	MOV	DX,ADDR1		; Pick up address of UART
	CALL	SET_PAR			; Set UART parameters
	MOV	DX,ADDR2		; Pick up address of UART
	CALL	SET_PAR			; Set UART parameters
	MOV	DX,ADDR3		; Pick up address of UART
	CALL	SET_PAR			; Set UART parameters
	MOV	DX,ADDR4		; Pick up address of UART
	CALL	SET_PAR			; Set UART parameters
	RET
RES_UAR	ENDP
;
;	Set UART parameters. UART base-port is in DX. Storage for
;	parameters is pointed to by SI.
;
SET_PAR	PROC	NEAR
	ADD	DX,3			; Line control register
	IN	AL,DX			; Get present control-bits
	OR	AL,10000000B		; Set divisor latch access bit
	OUT	DX,AL			; To line-control register
	LODSB				; Get old line-control bits
	MOV	BL,AL			; Save for now
	SUB	DX,3			; Back to the base register
	LODSW				; Get saved divisor
	OUT	DX,AX			; Out the adjacent ports
	ADD	DX,3			; Back to line-control
	MOV	AL,BL			; Get saved control-bits
	AND	AL,01111111B		; Verify DLAB is reset
	OUT	DX,AL			; Restore line-control
	SUB	DX,2			; Set to interrupt control register
	LODSB				; Get old bits
	OUT	DX,AL			; Restore
	ADD	DX,3			; Offset to modem control
	LODSB				; Get old bits
	OUT	DX,AL			; Restore
	MOV	CX,6			; Registers to read
	SUB	DX,4			; Back to base port
RDALL2:	IN	AL,DX			; Read port
	INC	DX			; Ready next
	LOOP	RDALL2			; Read all ports
	MOV	AL,NOS_EOI		; Non-specific end-of-interrupt
	OUT	INT_RES,AL		; Reset hardware controller.
	RET
SET_PAR	ENDP
;
ERROR1	DB	CR,LF,'Can''t load the command processor! <CR> ',0
ERROR2	DB	CR,LF,'Can''t release memory! <CR> ',0
ERROR4	DB	CR,LF,'Can''t ',0
ERROR5	DB	'create',0
ERROR6	DB	'write',0
ERROR7	DB	'close',0
ERROR8	DB	'open',0
ERROR9	DB	'read',0
ERROR10	DB	'delete',0
ERROR11	DB	' file ',0
OLD_MSK	DB	?			; Old interrupt mask.
SPAWN	DB	0			; Flag for spawned sub-process
EFLAG	DB	0			; Entry flag
SCR_MOD	DB	?			; Screen mode
SCR_PAG	DB	?			; Screen page
SCR_COL	DB	?			; Columns on screen
CS_SAV	DW	?			; Interrupted program's CS
SP_SAV	DW	?			; Interrupted program's SP
SS_SAV	DW	?			; Interrupted program's SS
SP_NEW	DW	?			; Save SP for EXEC call
SCR_SEG	DW	0B000H			; Segment of screen regen buffer
CUR_TYP	DW	?			; Cursor type
CUR_POS	DW	?			; Cursor position
DTA	LABEL	DWORD			; Data transfer area
DTA_OFF	DW	?			; Offset
DTA_SEG	DW	?			; Segment
OLD_KBD	LABEL	DWORD			; Old keyboard vector
KBD_OFF	DW	?			; Offset
KBD_SEG	DW	?			; Segment
OLD_CLK	LABEL	DWORD			; Old clock vector
CLK_OFF	DW	?			; Offset
CLK_SEG	DW	?			; Segment
;
COMSPEC	DB	'COMSPEC='		; Environment search string
COMLEN	EQU	$ - COMSPEC		; It's length
COMMAND	DB	65 DUP (0)		; To copy command processor name
DRV	DB	'?:\'			; Current directory drive and root.
CUR_DIR	DB	65 DUP (0)		; Rest of the current directory string
SCREEN	DB	'\SCREEN.$$$',0		; Screen data file.
VIRMEM	DB	'\VIRTUAL.MEM',0	; Virtual memory file
;
;	Parameter block for EXEC function call
;
BLOCK	DW	0			; Use current environment
	DW	80H			; Offset of command line
CS0	DW	?			; Segment of command line
	DW	5CH			; Offset of FCB #1
CS1	DW	?			; Segment of FCB #1
	DW	6CH			; Offset of FCB #2
CS2	DW	?			; Segment of FCB #2
;
UAR_PAR	LABEL	BYTE			; Where the UART parameters are kept.
	IN8250	<>
	IN8250	<>
	IN8250	<>
	IN8250	<>
;
LOGO2	DB	28 DUP (' ')
STRTL2	DB	(28 - ( @VERS + 4 ) ) / 2 DUP (' ')
	DB	'JDOS '
	VERS <>
ENDL2	EQU	$ - STRTL2
	DB	28 - ENDL2 DUP (' ')
	DB	' Resident Command Processor '
	DB	'           ACTIVE           '
	DB	'  Type EXIT ΔΩ to return   '
	DB	28 DUP (' ')
;
;	Put on a paragraph boundary.
;
	ORG	(( ($-START) + 16) AND 1111111111110000B)
	DB	32 DUP ('STACK   ')
;
STKTOP	LABEL	WORD
DEFAULT	LABEL	BYTE			; For our default interrupt table
	ORG	$ + KB
BUFFER	LABEL	BYTE			; Misc buffer for environment parse
TABL   LABEL   BYTE            ; Saved interrupt table during spawn
	ORG	$ + KB
TOP	EQU	$
;
INIT	PROC	NEAR
	MOV	AX,3000H		; Get DOS version number
	INT	MS_DOS			; From DOS
	CMP	AL,3			; Need verion 3+
	JNC	VERSOK			; Its okay
	MOV	SI,OFFSET ERROR12	; Point to version error
EXITF:	CALL	PROMPT			; Write to screen, exit fatal
	MOV	AX,4C01H		; Exit, ERRORLEVEL 1
	INT	MS_DOS			; To DOS
;
VERSOK:	INT	11H			; Equipment check
	AND	AL,00110000B		; Mask everything but video info
	CMP	AL,00100000B		; See if color
	JNZ	DEFULT			; Default is mono
	MOV	WORD PTR [SCR_SEG],0B800H ; Color screen segment

DEFULT:	CALL	CHK_SEG			; See if its writable memory
	JZ	SCR_OK			; Found writable memory
	MOV    WORD PTR [SCR_SEG],(0B000H - 100H); Start at lowest segment
SERCH0:	ADD	WORD PTR [SCR_SEG],00100H ; Incr screen segment
	CMP	WORD PTR [SCR_SEG],0E000H ; Upper limit of search
	JC	SERCH1			; Not outside limits
	MOV	SI,OFFSET ERROR13	; Point to "can't find screen"
	JMP	SHORT EXITF		; Write to screen and exit fatal
SERCH1:	CALL	CHK_SEG			; Check for writable memory
	JNZ	SERCH0			; Keep looking
;
SCR_OK:	CALL	GET_CMD			; Get command processor
	JNC	INIT2			; Found
	MOV	SI,OFFSET ERROR1	; Point to error message
	JMP	SHORT EXITF		; Write to screen and exit fatal
;
INIT2:	MOV	WORD PTR [CS0],CS	; Fix up parameter block
	MOV	WORD PTR [CS1],CS
	MOV	WORD PTR [CS2],CS
	MOV	AH,35H			; Get vector function
	MOV	AL,TICK1		; Clock tick
	INT	MS_DOS			; Go get it
	MOV	WORD PTR [CLK_OFF],BX	; Save the vector
	MOV	WORD PTR [CLK_SEG],ES
;
	MOV	AH,35H			; Get vector function
	MOV	AL,16H			; Kbd interrupt
	INT	MS_DOS			; Get the vector
	MOV	WORD PTR [KBD_OFF],BX	; Save the vector
	MOV	WORD PTR [KBD_SEG],ES
;
	MOV	DI,BX			; ES:DI = old vector
	MOV	SI,OFFSET LCL_KBD	; DS:SI = new vector
	MOV	CX,10H			; Check 16 bytes
	REPZ	CMPSB
	PUSH	CS
	POP	ES			; restore segment
	JNZ	NEW			; Never installed before
;
	MOV	SI,OFFSET ERROR3	; Point to error message
	CALL	PROMPT			; Display to console
	MOV	AX,4C00H		; Exit to DOS ERRORLEVEL 0
	INT	MS_DOS
;
NEW:	MOV	AH,25H			; Set vector function
	MOV	AL,TICK1		; Vector to set
	MOV	DX,OFFSET ENTRY		; Vector to patch
	INT	MS_DOS			; Patch the vector
;
	MOV	AH,25H			; Set vector function
	MOV	AL,16H			; Vector to set
	MOV	DX,OFFSET LCL_KBD	; Vector to patch
	INT	MS_DOS			; Patch the vector
;
	PUSH	ES			; Save segment
	MOV	ES,WORD PTR DS:[ENVIR]	; Get environment segment
	XOR	BX,BX			; Free it all up
	MOV	AX,4A00H		; Modify memory
	INT	MS_DOS			; Call DOS
	POP	ES			; Restore segment
;
;	Save our default interrupt table.
;
	XOR	AX,AX			; Get a zero
	MOV	SI,AX			; Offset zero
	MOV	CX,KB			; 1k to move
;
	PUSH	DS
	MOV	DS,AX			; Point to interrupt table
	MOV	DI,OFFSET DEFAULT	; Point to our data-space
	CLI
	REP	MOVSB			; Copy to our CS
	STI
	POP	DS			; Restore segment
;
	MOV	SI,OFFSET LOGO1		; Point to 'installed' logo
	CALL	SIGNON			; Signon the message.
	MOV	AX,3100H		; Keep process
	MOV	DX,OFFSET TOP		; Last location to keep
	ADD	DX,MEM			; Memory we need
	SHR	DX,1			; Div/2
	SHR	DX,1			; Div/4
	SHR	DX,1			; Div/8
	SHR	DX,1			; Div/16
	INT	MS_DOS			; Exit to DOS
	JMP	$			; For fatal error abort
INIT	ENDP
;
;	Check screen segment for writable memory.
;
CHK_SEG	PROC	NEAR
	PUSH	DS			; Save segment
	MOV	DS,WORD PTR [SCR_SEG]	; Get screen segment
	MOV	AX,WORD PTR DS:[0]	; Get word at address zero
	MOV	BX,AX			; Save memory word
	NOT	AX			; Invert
	NOT	WORD PTR DS:[0]		; Invert memory word
	CMP	AX,WORD PTR DS:[0]	; See if it went
	MOV	WORD PTR DS:[0],BX	; Put original word back
	POP	DS			; Restore segment
	RET
CHK_SEG	ENDP
;
;	Get boot command interpreter. Find out where COMMAND.COM is.
;
GET_CMD	PROC	NEAR
	XOR	SI,SI			; Offset zero
	MOV	DI,OFFSET BUFFER	; Where to copy the string
	MOV	AX,WORD PTR DS:[ENVIR]	; Get environment segment
	PUSH	DS			; Save segment
	MOV	DS,AX			; Set environment segment
GET0:	LODSB				; Get environment byte
	TEST	AL,AL			; Check for a null
	JNZ	GET1			; Not a null
	CMP	BYTE PTR [SI],0		; Next on a null too?
	JZ	GET3			; Yes, all done
GET1:	CMP	AL,'z'			; Check limits
	JA	GET2			; Not lower case
	CMP	AL,'a'			; Check lower limit
	JB	GET2			; Not lower case
	AND	AL,95			; Reset lower-case bits
GET2:	STOSB				; Save byte
	JMP	SHORT GET0		; Continue until the double-null
GET3:	STOSB				; Final null
	POP	DS			; Restore segment
;
	MOV	CX,DI			; Get last pointer
	SUB	CX,OFFSET BUFFER	; CX= length of environment strings
	MOV	BX,OFFSET COMSPEC	; Substring to find
	MOV	DX,COMLEN		; Length of the substring
	MOV	SI,OFFSET BUFFER	; Where the string should be found.
	CALL	COMPARE			; Scan the string
	JNZ	GET10			; Not found
	MOV	DI,OFFSET COMMAND	; Where to copy COMMAND.COM
	MOV	BX,SI			; Save location
GET4:	LODSB				; Get byte to transfer
	TEST	AL,AL			; Check for terminator
	JZ	GET5			; String is transferred
	STOSB				; Copy byte
	JMP	SHORT GET4		; Continue
GET5:	RET				; No errors
GET10:	STC				; Show error
	RET
GET_CMD	ENDP
;
;	Compares the substring addressed by DS:BX to the string addressed
;	by DS:SI. DX contains the substring length. CX contains the string
;	length. Returns ZF=TRUE if the string is found. SI points to one
;	character after the string if its found.
;
COMPARE	PROC	NEAR
	PUSH	CX			; Save string length
COMP0:	PUSH	CX			; Save original string length
	MOV	CX,DX			; Get substring length
	MOV	DI,BX			; Get substring location
	REPZ	CMPSB			; Compare string/substring
	POP	CX			; Restore string length
	JZ	FOUND			; String was found
	LOOP	COMP0			; Not found, continue
	INC	CX			; Make NZ
FOUND:	POP	CX			; Restore string length
	RET
COMPARE	ENDP
;
ERROR3	DB	CR,LF,'JDOS is already installed!',CR,LF,0
ERROR12	DB	CR,LF,'Need DOS version 3.0 or higher to execute!',CR,LF,0
ERROR13	DB	CR,LF,'Can''t find writable screen memory!',CR,LF,0
LOGO1	DB	28 DUP (' ')
STRTL	DB	(28 - ( @VERS + 4 ) ) / 2 DUP (' ')
	DB	'JDOS '
	VERS <>
ENDL	EQU	$ - STRTL
	DB	28 - ENDL DUP (' ')
	DB	' Resident Command Processor '
	DB	'                            '
	DB	'    Use ^\ to activate.     '
	DB	28 DUP (' ')
PSEG	ENDS
	END	MAIN
