	page	78,132
	title	Monitor for IBM PC by Tim Paterson

	include	dos.inc

;System equates

BreakVect	=	1BH
TermAddr	equ	word ptr 0AH
PROMPT		=	">"

ExecBlock	struc

Environment	dw	0		;Pass on parent's environment
CommandTail	dw	80H
CommandTailSeg	dw	0
FCB1		dw	5CH
FCB1Seg		dw	0
FCB2		dw	6CH
FCB2Seg		dw	0
InitSp		dw	0
InitSs		dw	0
InitIp		dw	0
InitCs		dw	0

ExecBlock	ends

	.model	small
	.code
	.data
Strings	segment	byte public
Strings	ends
InitSeg segment public
InitSeg ends
LastSeg	segment byte
	ret				;End of init code
LastSeg	ends

DGroup	group	_TEXT,_DATA,Strings,InitSeg,LastSeg

	.data

	public	AxSave,BxSave,SpSave,SsSave,DsSave,CsSave,IpSave,FlSave
	public	BpSave,SiSave,DiSave,TestPSP

	extrn	LineBuf:byte, TCount:word

ExecFile	ExecBlock<,DGroup:LineBuf>
TestPSP		dw	0
NextInt15	dd	0

	ALIGN	2
	dw	80H dup(?)		;Working stack area
STACK	label	word
;Register save area
AxSave	dw	?
BxSave	dw	?
CxSave	dw	?
DxSave	dw	?
SpSave	dw	?
BpSave	dw	?
SiSave	dw	?
DiSave	dw	?
DsSave	dw	?
EsSave	dw	?
SsSave	dw	?
CsSave	dw	?
IpSave	dw	?
FlSave	dw	?

;Start of Monitor code

	.code
	assume	cs:DGroup,ds:DGroup,es:DGroup,ss:DGroup

	public	Monitor, Command, Error, PrintAbort, PErr, ErrMes

	extrn	InBuf:near, OutCh:near, CrLf:near, PrintMes:near, ScanB:near
	extrn	Tab:near, ReEnter:near

	org	100H

Monitor:
     	cld

	IFDEF	DEBUG
;Copy psp
	xor	si,si
	mov	di,si
	push	cs
	pop	es
	mov	cx,100H/2
rep	movsw
	push	cs
	pop	ds
	push	cs
	pop	ss
	ENDIF	;DEBUG

	mov	sp,offset DGroup:Stack
	DOS	Version,0
	cmp	al,3			;Version OK?
	jae	VersionOK
;Bad version.  Terminate in a DOS 1.0-compatible way
	DOS	Print,,<offset DGroup:BadVersionMsg>
	int	20H

VersionOK:
	call	Init
	mov	si,offset DGroup:Header
	call	PrintMes
;Load file for debugging
	mov	ax,cs

	IFDEF	DEBUG
	sub	ax,10H			;Get to true PSP
	mov	es,ax
	ENDIF	;DEBUG

	mov	bx,[CsSave]		;First segment to free
	sub	bx,ax			;Compute paragraphs we're keeping
	DOS	ResizeMem

	IFDEF	DEBUG
	push	cs
	pop	es
	ENDIF	;DEBUG

	mov	si,80H			;Point to command line in PSP
	lodsb				;Get length byte
	cbw
	xchg	cx,ax			;Put length in cx
	call	ParseFile
	mov	si,dx
	cmp	byte ptr [si],0		;Was there a file name?
	jz	Command
	mov	[AxSave],ax
	mov	[ExecFile].CommandTailSeg,ds
	mov	[ExecFile].FCB1Seg,ds
	mov	[ExecFile].FCB2Seg,ds
	mov	bx,offset DGroup:ExecFile
	DOS	Exec,1			;Load, don't execute
	jc	NoFileLoad
	mov	ax,[ExecFile].InitIp
	mov	[IpSave],ax
	mov	ax,[ExecFile].InitCs
	mov	[CsSave],ax
	mov	ax,[ExecFile].InitSp
	mov	[SpSave],ax
	mov	ax,[ExecFile].InitSs
	mov	[SsSave],ax
	DOS	GetPSP
	mov	[DsSave],bx		;DS = ES = PSP
	mov	[EsSave],bx
	mov	es,bx
	mov	es:[TermAddr],offset DGroup:ProgTerminate
	mov	es:[TermAddr+2],cs	;Terminate address now set
	mov	[TestPSP],bx
	mov	bx,cs

	IFDEF	DEBUG
	sub	bx,10H			;True PSP
	ENDIF	;DEBUG

	DOS	SetPSP			;Change back to our own PSP
Command:
;Re-establish initial conditions
	cld
	mov	ax,cs
	mov	ds,ax
	mov	es,ax
	mov	ss,ax
	mov	sp,offset DGroup:STACK
	sti
	MOV	AL,PROMPT
	CALL	OutCh
	CALL	INBUF		;Get command line
;From now and throughout command line processing, SI points
;to next character in command line to be processed.
	CALL	SCANB		;Scan off leading blanks
	JZ	COMMAND		;Null command?
	LODSB			;AL=first non-blank character
;Prepare command letter for table lookup
	SUB	AL,"B"		;Low end range check
	JC	ERR1
	CMP	AL,"U"+1-"B"	;Upper end range check
	JNC	ERR1
	SHL	AL,1		;Times two
	CBW			;Now a 16-bit quantity
	XCHG	BX,AX		;In BX we can address with it
	CALL	[BX+COMTAB]	;Execute command
	jmp	Command		;Get next command

NoFileLoad:
	mov	si,offset DGroup:FileErrMsg
	jmp	PrintAbort

ERR1:	JMP	PERR

BadVersionMsg	db	"Invalid DOS--version 3 or later required$"
Header		db	13,10,"IBM-PC Monitor 1.0",13,10+80H
SYNERR		db	'^'
ERRMES		db	" Error",13,10+80H
ProgEndMsg	db	13,10,"Program terminated",13,10+80H
FileErrMsg	db	"Error loading file",13,10+80H

;Ctrl-BREAK handler.  Allows program abort.
Interrupt:
	MOV	AL,20H		;End of interrupt command
	OUT	20H,al		;Send to 8259A
	mov	al,0AEH		;Enable keyboard
	out	64H,al		;Send to keyboard
	MOV	AL,"^"
	CALL	OutCh
	MOV	AL,"C"
	CALL	OutCh
	call	CRLF
	jmp	Command

SysReq:
	cmp	ax,8500H		;Sys Req key pressed?
	jnz	ChainInt15
	mov	cs:[TCount],1		;Stop step mode
	jmp	ReEnter

ChainInt15:
	jmp	cs:[NextInt15]

;Command Table. Command letter indexes into table to get
;address of command. PERR prints error for no such command.

DwExt	Macro	lab
	extrn	lab:near
	dw	offset DGroup:lab
	EndM

COMTAB	label	word
	DW	PERR		;B
	DwExt	COMPARE		;C
	DwExt	DUMP		;D
	DwExt	ENTER		;E
	DwExt	FILL		;F
	DwExt	GO		;G
	DW	PERR		;H
	DwExt	INPUT		;I
	DW	PERR		;J
	DW	PERR		;K
	DW	PERR		;L
	DwExt	MOVE		;M
	DW	PERR		;N
	DwExt	OUTPUT		;O
	DW	PERR		;P
	DW	Quit		;Q
	DwExt	REG		;R
	DwExt	SEARCH		;S
	DwExt	TRACE		;T
	DwExt	UnAssemble	;U

ParseFile:
;Find start and end of file name
;Inputs:
;	ds:si = pointer to input string
;	cx = length of string
;Outputs:
;	ax = Starting value for ax (drive validity flags)
;	dx = File name to execute, zero-terminated

	mov	bx,si			;Save initial pointer
	call	ScanB
	mov	dx,si			;Save starting address
	call	FindNameEnd
	sub	bx,si
	neg	bx			;Amount scanned so far
	sub	cx,bx			;Amount remaining in string
	mov	bx,si			;Save end of name--start of args
	mov	di,offset DGroup:LineBuf+1
	mov	[di-1],cl		;Put length in first byte
	inc	cx			;Copy terminating CR
rep	movsb				;Copy to argument buffer
	mov	si,bx			;Restore start of args
	mov	di,5CH			;First FCB
	DOS	ParseName,1		;Parse file name, scan off blanks
	cbw				;0FFH if invalid drive
	and	al,ah			;Make sure al is zero or one
	xchg	cx,ax			;Save return value in cl
	call	FindNameEnd		;Skip over any "\" chars
	mov	di,6CH			;Second FCB
	DOS	ParseName,1		;Parse file name, scan off blanks
	cbw				;0FFH if invalid drive
	and	ah,al			;Make sure ah is zero or one
	mov	al,cl
	mov	byte ptr [bx],0		;Zero terminate file name
	ret

FindNameEnd:
	lodsb
	cmp	al," "			;Check for blank or control char
	jbe	NameEnd
	cmp	al,","
	jz	NameEnd
	cmp	al,";"
	jz	NameEnd
	cmp	al,"/"
	jnz	FindNameEnd
NameEnd:
	dec	si			;Point back at terminator
	ret


;Command error. SI has been incremented beyond the
;command letter so it must decremented for the
;error pointer to work.

PERR:
	DEC	SI

;Syntax error. SI points to character in the input buffer
;which caused error. By subtracting from start of buffer,
;we will know how far to tab over to appear directly below
;it on the terminal. Then print "^ Error".

ERROR:
	SUB	SI,offset DGroup:LINEBUF-1	;How many char processed so far?
	MOV	CX,SI		;Parameter for TAB in CX
	CALL	TAB		;Directly below bad char
	MOV	SI,offset DGroup:SYNERR	;Error message

;Print error message and abort to command level

PrintAbort:
	CALL	PRINTMES
	JMP	COMMAND

;************************************************************
; "Q" - Quit

ProgTerminate:
;TestPSP = our own PSP if we are terminating the child in order to quit
;Monitor (suppress "program terminated" message).
	push	cs
	pop	ds
	mov	bx,cs

	IFDEF	DEBUG
	sub	bx,10H			;True PSP
	ENDIF	;DEBUG

	mov	ax,bx
	xchg	bx,[TestPSP]		;Set to our own PSP to show no child
	cmp	ax,bx			;Do we have a child?
	jz	JustExit
	mov	si,offset DGroup:ProgEndMsg
	jmp	PrintAbort		;Print message, get next command line

Quit:
;We must end child first.  If no child, TestPSP = our own PSP, so we'll
;just terminate directly.  If there is a child, set TestPSP to our own PSP
;as a flag to ProgTerminate to suppress "program terminated" message.
	mov	bx,cs

	IFDEF	DEBUG
	sub	bx,10H			;True PSP
	ENDIF	;DEBUG

	xchg	bx,[TestPSP]
	DOS	SetPSP
JustExit:
	lds	dx,[NextInt15]
	DOS	SetVect,15H
	mov	ax,76*100H + 0		;Terminate, no error
	int	21H

;************************************************************
;
;Initialization code
;
;Each module can have its own initialization code.  The module simply
;puts the code in InitSeg.  All InitSegs are combined end-to-end,
;thus executing the init code for each module.  The RET instruction in
;LastSeg, immediately following InitSeg, returns program flow back to
;the caller of Init.
;
;All this code is thrown away when memory is resized.

InitSeg	segment
	assume	cs:DGroup,ds:Dgroup

Init:
;one-time register initialization
	DOS	SetVect,BreakVect,<offset DGroup:Interrupt>
	DOS	GetVect,15H
	mov	word ptr [NextInt15],bx
	mov	word ptr [NextInt15+2],es
	push	ds
	pop	es
	DOS	SetVect,15H,<offset DGroup:SysReq>
	mov	[TestPSP],cs		;Use our own PSP until program loads
	xor	ax,ax
	mov	di,offset DGroup:AxSave
	mov	cx,8			;Init all general registers
rep	stosw
	mov	ax,cs
	mov	bx,offset DGroup:InitSeg
	mov	cl,4
	shr	bx,cl
	add	ax,bx
rep	stosw				;Fill 4 segment registers
	mov	ax,100H
	stosw				;Set IP
	mov	ah,2
	stosw				;Enable user interrupts
	mov	byte ptr [SpSave+1],1	;Set user SP to 100H
;Will now fall into next module's init code
InitSeg	ends

	END	Monitor
