;       SCCSID = @(#)envinit.asm	6.1 90/11/17
;      SCCSID =  @(#)envinit.asm	1.8 90/01/24
; ****************************************************************************
; *									     *
; *			  IBM/Microsoft Confidential			     *
; *									     *
; *		    Copyright (c) IBM Corporation  1987, 1990		     *
; *		    Copyright (c) Microsoft Corp.  1987, 1990		     *
; *			      All Rights Reserved			     *
; *									     *
; ****************************************************************************
	PAGE	,132
BREAK <ENVironment INITialization for DOS 2.x Stub Loader>
;***	MT-MSDOS Family API Library Stub loader Environment Init routines
;
;	Member of API library
;	Karl Schulmeisters
;	Copyright MicroSoft Corporation, 1986
;	08-May-1986
;
;	Modification History
;
; @@	    29-Apr-87 (giacomoa): JMP to LoadError instead of
; @@	    load_error (on error). (PTM4096)
;
;
;   ENVironment_INITialization:
;
;	This module sets up the DOS 5.0 runtime environment as follows
;	CS:IP	points to the program initial entry
;		point specified in the EXE header
;	SS:SP	points to the stack specified in the EXE header
;	DS	points to the data segment specified in the EXE header
;	ES	== 0
;	AX	contains the segment of the enviroment/argument list segment
;	BX	offset into environment segment of the start of the command
;		line
;	CX	Length of the data segment (0 = 65536)
;	DX	Reserved
;	BP	== 0
;
;	For further documentation see the 286DOS Product Spec. section 2.2.13
;
;	ENTRY
;
;
;	RETURNS
;		WARNING!! This code does not explicitly return!  It passes
;		control to the code specified in the 5.0 EXEcutable header.
;
;	WARNINGS
;		This code does not return.
;
;	14-Oct-86 (billme): add call to initialize api (all api
;	initialization will eventually move into this module).
;
;	30-Oct-86 (billme): add dbcs support (see #DBCS#)
;
;	28-Nov-86 (billme): There was a problem with the command line
;	arguments that was never quite right - now it should be.  The
;	problem was that the loader was not handling the arguments
;	correctly in certain cases - specifically, when there was
;	a CR at the end of the argument string.  The CR must be there
;	to be compatible with DOS 3, but sometimes it is not.  This
;	difference seems to depend on how we are started up - i.e.
;	whether from command.com or from the dos exec function (and
;	whether things are being redirected may also have something
;	to do with it).  The solution used is to check the end of
;	the arugment string.  If it is a carriage return, then we
;	replace it with a 0 and reduce the argument string count
;	by one.  Otherwise, we do the usual double increment to include
;	the double null terminator and continue.  (See code near env7.)
;	Seems to work pretty well now.
;
;	07-01-87 (ianb): added FREE_BLOCK call for PTM 4668
;
;	07-06-87 (Tarng): setup program entry convention.
;
;	01-08-89 (klee): cp12 b705150
;			  Before the user program starts, update the ss first
;			  and then sp. This will be OK for NMI.
;

IFDEF	debug_just_statistics
	%OUT *** DEBUG *** JUST note and print statistics (then exit)
debug_statistics	equ	0
ENDIF

IFDEF	debug_statistics
	%OUT *** DEBUG ***  note and print various statistics

gtime	macro	addr
	mov	ax, offset LDRGRP:addr
	call	get_time
	endm
ptime	macro	addr
	mov	ax, offset LDRGRP:addr
	call	print_time
	endm
pclock	macro
	call	print_clock
	endm

LOADER	SEGMENT

	EXTRN	TimeT1:BYTE
	EXTRN	TimeT2:BYTE
	EXTRN	TimeT3:BYTE
	EXTRN	TimeT4:BYTE
	EXTRN	TimeBC:BYTE
	EXTRN	TimeC1:BYTE
	EXTRN	TimeBL:BYTE
	EXTRN	TimeEI:BYTE
	EXTRN	TimeLE:BYTE
	EXTRN	TimeEX:BYTE
	EXTRN	TimeGo:BYTE
	EXTRN	TimeEC:BYTE
	EXTRN	TimeC2:BYTE
	EXTRN	TimeBP:BYTE
	EXTRN	TimeEP:BYTE

	EXTRN	get_time:NEAR
	EXTRN	print_time:NEAR
	EXTRN	print_clock:NEAR

LOADER	ENDS

ENDIF


.XCREF
.XLIST
	include ldrseg.inc
	include curdir.inc
	include error.inc
	include basemaca.inc
; NO 386 code for the LJ macro
MAC_NO386CODE EQU   1
	include oldmaca.inc
	include stubmac.inc
.LIST
	include segmngr.inc
	include stubload.inc
	include psp.inc
	include arena.inc
	include envinit.inc
.CREF

TaskArea	SEGMENT

	EXTRN	RenBuf:BYTE

TaskArea	ENDS

	EXTRN	_api_init:FAR

LOADER		SEGMENT

	EXTRN	LoadCS:WORD,LoadSS:WORD,LoadES:WORD,mte_h:WORD
	EXTRN	NextFixupIndex:WORD,InitAllocEnd:WORD,SegEndMem:WORD
	EXTRN	DosVersion:WORD,NameOff:WORD,ProgSize:WORD,ProgOff:WORD
	EXTRN	LdrRelocSeg:WORD

	EXTRN	LoadError:NEAR			; @@ (loaderror)

EXTRN	EnvSegSize:WORD,EnvSize:WORD,NameSize:WORD,CmdLnSize:BYTE,NewEnvSeg:WORD
EXTRN	OldEnvSeg:WORD


LOADER		ENDS


LdrCode SEGMENT
	ASSUME	    CS:LDRGRP,DS:NOTHING,ES:NOTHING

__load_lead_table dw	5 dup (0)	; lead byte table for DBCS

	public	load_testdbcs_init
load_testdbcs_init	proc near
	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	ds
	push	es

	push	cs
	pop	ds
	mov	si, offset Ldrgrp:__load_lead_table

						; DS:SI <- default lead_table
	mov	ah,63h				; AH <- lead byte table fcn
	xor	al,al
	int	21h				; DS:SI <- real lead table
	mov	cx,5
	push	cs
	pop	es
	mov	di, offset Ldrgrp:__load_lead_table    ; ES:DI <- lead table
	rep	movsw				; copy table

	pop	es
	pop	ds
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret

load_testdbcs_init	endp


;***	load_testdbcs - test if AL contains a lead byte - see api\i_versio.asm

	public	load_testdbcs

load_testdbcs	proc	near

IFDEF api_debug_pause
	int	3	; included ONLY IF api_debug_pause is defined
ENDIF

	push	bx
	push	si
	push	ds
	mov	bl,al				; BL <- byte in question
	or	bl,bl				; if byte is zero
	jz	test99				; then clear carry and return

	cld					; set direction forward
	push	cs
	pop	ds
	mov	si, offset Ldrgrp:__load_lead_table ; DS:SI <- lead byte table
test1:
	lodsw					; AL <- min, AH <- max
	or	ax,ax				; if end of table
	jz	test99				; then clear carry and return
	cmp	bl,al
	jb	test1
	cmp	bl,ah
	ja	test1
	stc
test98:
	mov	al,bl				; restore AL
	pop	ds
	pop	si
	pop	bx
	ret
test99:
	or	al,1				; set NZ
	jmp	short test98			; (we want TZ => CY)

load_testdbcs	endp


;***	load_testdbcs_b - test and bump - see api\i_versio.asm

	public	load_testdbcs_b

load_testdbcs_b proc	near

IFDEF api_debug_pause
	int	3	; included ONLY IF api_debug_pause is defined
ENDIF
	call	load_testdbcs
	jnc	bump99
	mov	ah,al
	lodsb			; AX <- double byte in reverse order
	or	al,al		; set ZE if next byte is zero
	stc			; set CY since this was a double byte
bump99:
	ret

load_testdbcs_b endp


;***	load_testdbcs_bst - test and bump and store - see api\i_versio.asm

	public	load_testdbcs_bst

load_testdbcs_bst	proc	near

IFDEF api_debug_pause
	int	3	; included ONLY IF api_debug_pause is defined
ENDIF
	call	load_testdbcs_b
	jnc	bst99
	jz	bst99
	xchg	ah,al
	stosw
bst99:
	ret

load_testdbcs_bst	endp


;***	load_testdbcs_bdec - test and bump and store

	public	load_testdbcs_bdec

load_testdbcs_bdec	proc	far

IFDEF api_debug_pause
	int	3	; included ONLY IF api_debug_pause is defined
ENDIF
	call	load_testdbcs_b
	jnc	bdec99
	jz	bdec99
	dec	cx
	jnz	bdec99
	dec	si
bdec99:
	ret

load_testdbcs_bdec	endp



Set_NewEnviron	 PROC	 NEAR
	    PUBLIC  Set_NewEnviron,End_Stb_Ldr,Set_NewEnvironX

	    push	si
	    push	ds

;
;   Calculate the space remaining in the current environment.
;
	    mov     ES,LoadES
	    mov     AX,ES:PSP.Env_Seg	    ; Establish addressability of the
	    mov     DX,AX		    ;	DOS 2.x Environment Segment and
	    mov     NewEnvSeg,DX	    ;	save it for later.

	    dec     AX			    ; Establish addressability of the of
	    mov     DS,AX		    ;	 the Environment Segment's Arena


	    mov     CX,DS:Arena.Arena_Size  ; Calculate the size of the curr. env
	    shl     CX,1		    ;			   (never > 32k )
	    shl     CX,1		    ; EnvSize := CX << PARA
	    shl     CX,1		    ;
	    shl     CX,1		    ;
	    mov     [EnvSegSize],CX

	    mov     ES,DX		    ; Get Environment Segment for scasb.
	    xor     AX,AX		    ; Scan for the end of the environment
	    xor     DI,di		    ; text (double null terminated)
EnvEndLp:				    ; FOR (i = 0, (CX) = EnvSize     ;
	    REPNE   scasb		    ;	   i <= EnvSiz &&
	    dec     CX			    ;		  ( Env[i++] == 0 &&
	    scasb			    ;		    Env[i+1] ==0)    ;
	    jne     EnvEndLp		    ;		    --(CX)	    );

	    mov     AX,DosVersion
	    cmp     AX,VERSION300	    ;IF (version Pre 3.00)
	    jb	    Env2		    ;	THEN Set NameOff to "p0:"


					    ;	ELSE Shift Name down by one
					    ;	    word to overlay "count" word
					    ;	    and set NameOff to "c0:"
	    mov     AX,ES
	    mov     DS,AX
	    mov     SI,DI
	    inc     SI
	    inc     SI
	    xor     AX,AX
Env1:				    ; FOR (i = "p0:" - 1,(CX) = SpaceLeft;
	    dec     CX		    ;	   i < EnvSiz &&
	    cmp     AL,[SI]	    ;		  ( Env[i] = Env[++i]) != 0;
	    movsb		    ;	   CX--)
	    jne     Env1


Env2:
	    mov     NameOff,DI

	    mov     BX,[EnvSegSize]	    ; EnvSize := EnvSegSize - SpaceFree
	    sub     BX,CX
	    mov     [EnvSize],BX

;
;   If we are running under DOS 3.x We need to also add in the size of
;		"prog arg1 arg2 arg3 arg4 ... argn",0D
;   If we are running under DOS 2.x, we add the size of
;   "exec_name\0\0prog arg1 arg2 arg3 arg4 ... argn",0D
;
	    mov     CX,CS		    ; Establish addressability of
	    mov     ES,CX		    ;  "exec_name".
	    mov     DS,CX
	    mov     DI,offset LdrGrp:RenBuf
	    mov     SI,DI


	    mov     AX,DosVersion
	    cmp     AX,VERSION300	    ;IF (version later than 3.00)
	    jnb     Env3		    ;	THEN "exec_name" already in Envseg

	    xor     ax,ax
	    mov     CX,RMPATHLEN	    ; Calculate size of "exec_name"
	    repne   SCASB
	    sub     DI,offset LdrGrp:RenBuf ; (DI) = size of "exec_name"
	    add     EnvSize,DI
	    mov     NameSize,DI
Env3:
	    mov     DX,SI		    ; Calculate size of "prog "
env4:
	lodsb				; remove drive and path strings

; #DBCS#
					; test for dbcs and bump si
	call	load_testdbcs_b 	; if NOT a dbcs char
	jnc	env4a			; then continue
	jnz	env4			; else if char is OK, then loop
	jmp	LoadError		; @@ else return error (loaderror)
env4a:

	    cmp     AL,':'
	    je	    env3
	    cmp     AL,'/'
	    je	    env3
	    cmp     AL,'\'
	    je	    env3
	    or	    AL,AL
	    jnz     env4

	    mov     CX,9		    ; module name is limited to 8 chars
					    ;@@(p1907)but we decrement in
					    ;@@Set_NewEnvironX so make it 9
					    ;@@this fixes truncated name bug
	    mov     SI,DX		    ; (DX) = start of name string
env5:
	lodsb
; #DBCS#
					; test for dbcs, bump si, and dec cx
	call	load_testdbcs_bdec	; if NOT a DBCS character
	jnc	env5a			; then continue
	jz	env5aa			; else if bad character, return err
	jcxz	env6			; else if cx depleated then quit loop
	jmp	short env5		; else loop
env5aa:
	jmp	LoadError		; @@ else return error (loaderror)
env5a:

	    cmp     AL,'.'
	    je	    env6		    ; end of module name
	    or	    AL,AL
	    jz	    env6		    ; end of module name

	    loop    env5
env6:
	    sub     SI,DX		    ; (SI) - (DX) = length of "prog"
	    add     EnvSize,SI
	    mov     ProgOff,DX
	    mov     ProgSize,SI

	    mov     ES,LoadES		    ; Calculate size of "arg1 ... argn"
	    mov     DI,offset PSP.Cmd_Line

	mov	CL,ES:[DI]		; First character in commandline is a
	xor	CH,CH			;   count of bytes.
	mov	bx,cx
	cmp	byte ptr es:[di+bx],0Dh ; if end of arg string is NOT CR
	jnz	env7			; then skip
	mov	es:[di+bx],ch		; else zero out CR at end of arg string
	dec	cx			; and count one less
env7:
	inc	CX			; Account for the Terminating NULLs
	inc	CX			; ###
	add	EnvSize,CX

	    mov     AX,EnvSize
	    mov     CX,EnvSegSize


;   IF the space left in the current environment is insufficient for the
;   command line text,
;	THEN attempt to grow the environment segment.
;
	    cmp     AX,CX		    ; IF (EnvSize <= EnvSegSize)
	    ljb      EnvBigEnuf 	    ;	  THEN no need to grow segment

	    add     AX,PARAsizeB - 1	    ; SpaceNeeded := CONV_PARA(SpaceNeeded)
	    shr     AX,1
	    shr     AX,1
	    shr     AX,1
	    shr     AX,1

	    mov     DX,AX		    ; save Total amount required

	    mov     BX,AX		    ; SET_BLOCK (SpaceNeeded, EnvSeg)
	    mov     CX,ES:PSP.Env_Seg
	    mov     ES,CX
	    mov     AH,SET_BLOCK
	    int     21h

	    ljnc     EnvBigEnuf

;
;	    IF the attempt to grow the environment segment failed,
;		THEN attempt to allocate a completely new segment large enough
;		     to contain both the command  line and the environment.
;

	    mov     BX,DX		    ; BX := SpaceNeeded
	    mov     AH,ALLOCATE_BLOCK
	    int     21h 		    ; ALLOCATE_MEM(SpaceNeeded)
	    jnc     env8		    ; @@ IF the new alloc attempt
	    jmp     LoadError		    ;	 failed, (loaderror)
					    ;	 THEN Abort signalling
					    ;	      insufficient memory.
env8:
;
;		ELSE check that the new segment does not overlay the portion
;		     of EnvInit yet to be executed. We have two cases to
;		     check.  The new Segment starts "Below" us (the usual case).
;		     OR the new Segment is "Above us" (overlays transient of
;		     command.com).
;
	    mov     [NewEnvSeg],AX

	    mov     CX,mte_H		    ; IF ("Bottom" of Loader > EnvSeg)
	    cmp     CX,AX
	    jnb     AboveEnv		    ;	THEN check "TOP" of EnvSeg overlaps

	; Handle case of New Env Seg Starting "Above us"
	    mov     CX,SS
	    add     CX,STACKsizeP
	    cmp     CX,AX		    ; IF ("Top" Loader < Bottom NewEnvSeg)
	    jb	    NewEnvSegOK 	    ;	 THEN no collision exists

	    mov     AL,error_not_enough_memory
	    jmp     LoadError		    ; @@ (loaderror)

AboveEnv:
	; Handle case of New Env Seg Starting "Below us"
	    add     AX,DX		    ; (BX) = NewEnvSeg + EnvSize in para
	    cmp     CX,AX		    ; IF ("Top" of NewEnvSeg < Bottom of Loader)
	    ja	    NewEnvSegOK 	    ;	 THEN no collision exists

	    mov     AL,error_not_enough_memory	; For fine tuning, we could check
	    jmp     LoadError		    ; @@ here to see if overlaying the (loaderror)
					    ; command.com transient would help.

;		     Copy the old environment segment to the new one, and
;		     release the memory allocated to the old one.
;
NewEnvSegOK:
	    mov     DS,OldEnvSeg	    ; Establish addressability of the
	ASSUME	DS:NOTHING		    ;	 DOS 2.x Environment Segment.
	    mov     AX,[NewEnvSeg]	    ; Establish addressability of the
	    mov     ES,AX		    ;	 New Environment Segment.
	    xor     DI,DI
	    mov     SI,DI
	    mov     CX,[EnvSegSize]
	    shr     CX,1
	    repnz   movsw		    ; Copy the old environment to the
					    ;	 New Environment Segment.
	    mov     DX,DS		    ; Free the old Segment
	    mov     ES,DX
	    mov     AH,FREE_BLOCK
	    int     21h
;					    ; error report removed
;					    ; since topview will always report
;					    ; an error if environ was grown.
;@@	    ljc     LoadError		    ; @@ On ERROR ABORT (loaderror)
;
;   Append the command line text to the Environment text in the Environment segment
;   (under DOS 2.x include the FileName also), and update the PSP ENV_SEG entry.
;
EnvBigEnuf:

	pop	ds
	pop	si

	ret

Set_NewEnviron	 ENDP


Set_NewEnvironX   PROC	  NEAR

	    mov     ES,NewEnvSeg
	    mov     DI,NameOff
	    mov     DX,CS
	    mov     DS,DX
	ASSUME	DS:LdrGrp
	    mov     CX,[NameSize]	    ; IF (NameSize == 0)
	    jcxz    evini40		    ;	THEN "exec_name" already in ENV

	    mov     SI,offset LdrGrp:RenBuf ; (DS:SI) -> Name of LoadFile
	    rep     movsb		    ;	ELSE copy "exec_name" into "p0:"
evini40:
	    mov     CX,ProgSize 	    ; Size of program name (leave space
	    dec     CX			    ;	    for separating ' ').
	    mov     SI,ProgOff		    ; Add "prog" to the CMD line.
	    mov     ProgOff,DI		    ; Save offseg to CMD line.
	    rep     movsb
; ###
	   xor	   ax,ax		   ; Fix PTM 1080
;	   mov	   AX,' '                  ; Insert separating ' '.
	   stosb

	    mov     DS,LoadES		    ; Re-establish PSP addressibility
	    mov     SI,offset DS:PSP.Cmd_Line ; and point to the Command line
	    lodsb			    ; IF (CmdLineSize == 0)
	ASSUME	DS:NOTHING
	    mov     CL,AL
	    xor     CH,CH
	    jcxz    envini41		    ;	 THEN nothing to copy

	    rep     movsb		    ; Copy Command line into ENV Segment.
envini41:
	    xor     AL,AL		    ; Add the Terminating NULLs
	    stosb
	    stosb			    ; ###
	    mov     DS:PSP.Env_Seg,ES	    ; Update PSP.Env_Seg

; Init everything the APIs need
	mov	AX,DosVersion	    ; pass version to api (AH=high, AL=low)
	mov	BX,DS		    ; and PSP
	call	    _api_init	    ;  and initialize api code

;
;   Set up the registers to conform with section 2.2.13
;
	mov	ds,ex_mte
	mov	dx,ds:mte_stacksiz	;DX=STACKSIZE from EXE header
	mov	si,ds:mte_heapsiz	;SI=HEAPSIZE from EXE header

	mov	AX,ex_ds		; DS := Data Segment Spec. in EXE hdr.
	mov	DS,AX

	mov	CX,ex_dslen		; CX := Length of Data Segement

;
;   Transfer control to the 5.0 EXEcutable.
;
	    mov     AX,word ptr ex_cs_ip+2
	    mov     BX,word ptr ex_cs_ip
	    mov     di,word ptr ex_ss_sp    ; save new SP in temp reg (ES)
	    mov     es,di		    ; cp12 b705150
	    mov     di,word ptr ex_ss_sp+2  ; (BP:SP)=SS:SPinit from EXE hdr.

	    cli
	    mov     SS,di		    ; Set up actual SS:SPinit
	    mov     sp,es
	    sti

	    push    AX
	    push    BX			    ; Set up actual CS:IPinit

	    mov     AX,NewEnvSeg	       ; AX := EnvSeg
	    mov     BX,ProgOff		    ; BX := offset of CmdLine in Env Seg
	    push    ax			    ; save ENVSEG
	    push    bx			    ; save CMDLINE offset

IFDEF	debug_statistics
	push	ax			; save AX (all else preserved)
	gtime	TimeGo
	gtime	TimeEC			; end clock
	pclock
	gtime	TimeC2

	gtime	TimeBP			; begin print

	ptime	TimeT1
	ptime	TimeT2
	ptime	TimeT3
	ptime	TimeT4
	ptime	TimeBC
	ptime	TimeC1
	ptime	TimeBL
	ptime	TimeEI
	ptime	TimeLE
	ptime	TimeEX
	ptime	TimeGo
	ptime	TimeEC
	ptime	TimeC2
	ptime	TimeBP

	gtime	TimeEP			; end print
	ptime	TimeEP
	pop	ax			; restore AX
ENDIF

IFDEF	debug_just_statistics
	mov	ax,0
	jmp	LoadError		; @@ (loaderror)
ENDIF

;   Free up memory occupied by relocated Stub Loader and New Exe header

	    mov     BX,CS:LdrRelocSeg
	    mov     ES,BX
	    mov     AH,FREE_BLOCK
	    int     21h

	    xor     BP,BP		    ; ES := 0, BP = 0, DI = 0
	    xor     DI,DI
	    mov     ES,BP
	    ASSUME  ES:NOTHING

	    pop     bx			   ; restore CMDLINE OFFSET
	    pop     ax			   ; restore env seg.

;   SS:SP= pointer to the stack specified in the EXE header
;   DS	 = pointer to the data segment specified in the EXE header
;   ES	 = 0
;   AX	 = environment segment handle
;   BX	 = offset in environment of command line start
;   CX	 = length of data segment (0 = 65536)
;   DX	 = STACKSIZE from EXE header
;   SI	 = HEAPSIZE from EXE header
;   DI	 = 0
;   BP	 = 0

;   Fool the assembler into generating a FAR ret
	    public  Return2App		   ; debug aid
Return2App  PROC    FAR
	    ret 			   ; ret to the new APP
Return2App  ENDP


Set_NewEnvironX   ENDP


public LoadEnd
LoadEnd:
End_Stb_Ldr:
LdrCode ENDS
	END
