;       SCCSID = @(#)stubload.asm	6.1 90/11/17
;      SCCSID = @(#)stubload.asm	1.9 90/01/24
; ****************************************************************************
; *									     *
; *			  IBM/Microsoft Confidential			     *
; *									     *
; *		    Copyright (c) IBM Corporation  1987, 1990		     *
; *		    Copyright (c) Microsoft Corp.  1987, 1990		     *
; *			      All Rights Reserved			     *
; *									     *
; ****************************************************************************
	PAGE	,132
TITLE	STUB LOAD - routines to load DOS 5.0 EXEs under DOS 2.x
;***	MT-MSDOS Family API Library Stub loader routiness
;
;	Member of API library
;	Karl Schulmeisters
;	Copyright MicroSoft Corporation, 1986
;	08-May-1986
;
;	The program load routines handle the loading of new .EXE file formats
;	in the 2.x,3.x DOS environment.  These routines are invoked under
;	DOS 2.x after the Standard DOS EXEC has read in the complete
;	New Style .EXE
;
;	For new .EXE file formats a module table entry is allocated in
;	a fixed segment.  The module table entry contains the resident
;	EXE header information the segment table, the resource table,
;	the resident name table, a list of segment pointers to the
;	modules it directly references and a list of handles to the
;	segments allocated for the module.  The module table entry also
;	contains the entry table which must be in a fixed segment.
;
;
;	Special Information
;	    This code currently does not support Double Byte Character Strings
;
;	Modification History
;	    05/13/86		Created 	KGS
;
;	    08/07/86 (billme)	change Num_to_STE to end with 'ENDP'
;
;	    09/03/86 (billme)	add "DENY_WRITE" and change 'w_Open' to
;	    open using deny write instead of compatibility mode (0)
;
;	    09/08/86 (billme)	add code to print message and edit
;	    if DOS version is < 2.00
;
;	    09/18/86 (billme)	move environment allocation (if needed)
;	    to come before allocation of space for application; add
;	    Karl's floating point changes (jmp short lsg26); add code
;	    to provide shorter load time (FastMode) between "IFNDEF loadslow"
;	    and "ENDIF"; add debug flags which depend on IFDEFs; add
;	    code for memory allocation dependent on the inclusion of
;	    the file apilmr.obj in the bind step (i_apilmr.asm is the
;	    corresponding stub file used in the api library).
;
;	    25-Sep-86 (billme): add code to set 'SegLdrSize' and use it
;	    to figure the maximum allowable size for the big read buffer
;
;	    30-Oct-86 (billme): add dbcs support (see #DBCS#)
;
;	    04-Nov-86 (billme): Enhance and add conditionally assembled
;	    code to do timing analysis and print results.
;
;	    06-Mar-87	IDB    Relocated Stub Loader no longer runs in
;	    free memory (PTM 1232)
;
;	    19-Mar-87	IDB    Added support for huge segments (PTM 2888).
;
; @@	    29-Apr-87 (giacomoa): added error message display in
; @@	    in LoadError: if an error is detected while attempting to
; @@	    load a program, the error message 'DOS2090:' will be displayed,
; @@	    and the stubloader exits. (Deleted redundant loaderror routine).
; @@	    (PTM4096)
;
; @@	    12-May-87 (giacomoa): modified message 'DOS2090:' to be
; @@				  'SYS2090:'.
;
; @@	    4-June-87 (Tarng): Check filename/pathname is in the 8.3 range.
;
; @@	    05-Jun-87 (giacomoa): Fixed memory trashing when loading
; @@	    64K segments:
; @@	    1) figure correct amount of storage needed for the load
; @@	    2) PreloadSegAlloc will allocate 64K of storage
;
;	    18-Jun-87 (Tarng): Initialize cbNew_Header to contain
;	    the size of the new EXE header.
;
; @@	    26-Jun-87 (giacomoa): 1) Removed imbedded messages and
; @@	    use messages defined in msgseg segment in api.lib (ldr.obj)
; @@	    2) moved the DosVersion check at the top of code, so that
; @@	    an app running under DOS 1.1 can die 'gracefully'.
;
;@@	    29-Jun-87 (robertc) add variables filename and filenamesize
;@@	    positioned right before LOADINIT. These will be patched by
;@@	    the BIND utility to pass the outputed filename. PTM4040
;
;@@	    6-July-87 (Tarng): 1) Change return far ES:0 to int 21h.
;	    2) Caculate the stack size in bytes if autodata seg = stack seg.
;
;@@	    21-July-87 (Tarng): 1) Change int 21h to return far ES:0.
;	    2) Comment the calculation for cbNew_Header.
;
;@@	    12-Jan-88 (robertc) change preloadsegalloc to handle true 64k
;		auto datasegments. apilmr returns ffffh for 64k.
;
;@@	    05-Apr-88 (robertc) add code to handle multiple iterated
;			data segment. Code lifted from loadste.asm
;
;@@	    04-Aug-88 (robertc) fix build_path to handle dos2.1 when not
;			in current dir. Was using unitialized Path_namecount.
;	    19-May-89 (klee) PB783377  Fix the PreLoadSegAlloc for a hugh
;		array which needs more than 1 data segment. In LoadSegment
;		patch the trailing bytes of zero value in a segment.
;
;@@	    01-Aug-89 (klee) cp12 b705150
;		Fix the DS reg setting if no auto data segment is defined
;		in the .EXE file
;
IFDEF api_debug_pause
	%OUT *** DEBUG ***  pause (int 3) points included
ENDIF

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

	public	TimeT1, TimeT2, TimeT3, TimeT4
	public	TimeBC, TimeC1
	public	TimeBL, TimeEI, TimeLE, TimeEX
	public	TimeGo, TimeEC, TimeC2, TimeBP, TimeEP

TimeT1		db	0,0,0,0,0,0,"T1"
TimeT2		db	0,0,0,0,0,0,"T2"
TimeT3		db	0,0,0,0,0,0,"T3"
TimeT4		db	0,0,0,0,0,0,"T4"

TimeBC		db	0,0,0,0,0,0,"BC"
TimeC1		db	0,0,0,0,0,0,"C1"

TimeBL		db	0,0,0,0,0,0,"BL"
TimeEI		db	0,0,0,0,0,0,"EI"
TimeLE		db	0,0,0,0,0,0,"LE"
TimeEX		db	0,0,0,0,0,0,"EX"

TimeGo		db	0,0,0,0,0,0,"Go"
TimeEC		db	0,0,0,0,0,0,"EC"
TimeC2		db	0,0,0,0,0,0,"C2"

TimeBP		db	0,0,0,0,0,0,"BP"
TimeEP		db	0,0,0,0,0,0,"EP"

LOADER	ENDS

ENDIF


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

DENY_WRITE	equ	020h	; sharing mode for 'deny write'
MAX_PATH_LENG	EQU	 64

CONSTANTS	SEGMENT
		Public	FileExtent,pathstr

FileExtent	DB	'.EXE',0
pathstr 	db	'PATH='


CONSTANTS	ENDS

MsgSeg		segment para public 'DATA'
extrn	TXT_MSG_BIND_RESERVED:word
MsgSeg		ends


FASTFLAG	EQU	1
FASTMAXMEM	EQU	0FFFh

; Regarding the minimum read buffer size (2048 bytes)... using less than
; this (in particular, 1024 bytes) can actully slow down the load.  I believe
; the internal DOS buffer is 1024 bytes so that using a buffer of 1024
; bytes will have the overhead of moving the data from the DOS buffer to
; the special read buffer and thus take slightly longer to load.  This
; was verified by running cpmasm (approx 100k in size) on a small input
; file.  Using significantly less than 1024 (e.g. 64 or 32) can severly
; increase (e.g. double) the load time.  Thus, 2048 is the suggested min size.

MINREADBUFPAR	EQU	128	; min read buffer in paragraphs - 2048 bytes

LOADER	SEGMENT

SegLdrSize	dw	?	; Size of stubloader in paragraphs
FastMode	dw	0	; true iff able to allocate more memory
BigBufSiz	dw	?	; size of one section in bytes
BigBufSeg	dw	?	; segment of buffer
BigBufPtr	dw	0	; pointer to current read position (0 to Cnt-1)
BigBufCnt	dw	0	; number of bytes in buffer
BigBufLocLo	dw	0	; low word of location within file
BigBufLocHi	dw	0	; high word of location within file
LOADER	ENDS

	EXTRN	load_testdbcs_init:near
	EXTRN	load_testdbcs_b:near
	EXTRN	load_testdbcs_bst:near
	EXTRN	name_check:far		;@@ added support for 8.3 filename

	EXTRN	api_memrep:FAR

TaskArea	SEGMENT
	EXTRN  PTDA_Handle:WORD,RenBuf:BYTE,TempBuf:BYTE,Path_NameCount:BYTE
	EXTRN  PTDAsizeB:WORD,Path_Index:WORD,Path_NameDX:WORD,Path_NameDS:WORD

TaskArea	ENDS

LOADER		SEGMENT

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

	EXTRN	FixUp_Helper:DWORD,cbNew_Header:WORD
EXTRN	EnvSegSize:WORD,EnvSize:WORD,NameSize:WORD,CmdLnSize:BYTE,NewEnvSeg:WORD
EXTRN	OldEnvSeg:WORD,ProgSize:WORD,ProgOff:WORD

LOADER		ENDS

LdrCode 	SEGMENT
	EXTRN	End_Stb_Ldr:BYTE
	EXTRN	Set_NewEnviron:NEAR
	EXTRN	Set_NewEnvironX:NEAR
LdrCode 	ENDS


LOADER	SEGMENT

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

	PUBLIC	LoadInit,Load_NewExE,LoadModule,LoadSegment
	PUBLIC	DoFixups,Create_MTE
	PUBLIC	LoadError,Num_to_STE,BuildPathName	; @@ (load_error deleted)
	PUBLIC	PreloadSegAlloc,TmpFarProc

BREAK <LoadInit - initialises data for the loader>
;***	LoadInit - initialises data for the loader
;
; LoadInit sets up the environment for loading a New Style EXE
; under DOS 2.x,3.x. It verifies that sufficient memory is available
; for the load to complete succesfully.  It then moves the loader,
; the New Exe Header and the Stack to High memory.  It then sets
; up the Frame for Load_NewEXE and calls Load_NewEXE.  Lastly it
; transfers control to the DOS 5.0 Runtime Environment Initialization
; code.
;
;	ENTRY	Standard DOS 2.x EXE entry
;
;	EXIT
;		IF ERROR
;		   THEN AX == error code
;
;		    ELSE routine never returns but jumps to Env. init.
;
;	USES	ALL
;
;	WARNING!
;		This routine relocates itself in memory.
;		This routine does not return if no error condition is detected.
;

;*************************************************************************
;									 *
filenamesize	dw	8
filename	db	"filename",0 ; ** DO NOT MOVE THIS DECLARATION
;									 *
;*************************************************************************

	PUBLIC LoadInit
LoadInit	PROC NEAR

IFDEF	debug_statistics
	gtime	TimeT1		; comparison timings
	gtime	TimeT2
	gtime	TimeT3
	gtime	TimeT4
	gtime	TimeBC		; begin clock
	pclock
	gtime	TimeC1
	gtime	TimeBL
ENDIF

	mov	AH,GET_VERSION		; @@ first check VERSION
	int	21H
	xchg	AH,AL
	mov	LdrGrp:DosVersion,AX	; save version for future reference
	cmp	AX,VERSION200		; if >= 2.0 DosVersion
	jae	lini00			; then continue
bpn9a:					; else, dos version 1.X - special death
	mov	ax, seg MsgSeg		; @@
	mov	ds, ax			; @@ DS <- segment of message segment
	mov	si, offset MsgSeg:TXT_MSG_BIND_RESERVED ; @@ SI <- offset of message
	mov	bx, word ptr [si]	; @@ get message length
	inc	si			; @@ bump past
	inc	si			; @@   length field
	mov	word ptr [si][bx], DOLLAR_SIGN	; @@ stuff $ at end of message
	mov	dx, si			; @@ DS:DX <- message text
	mov	ah, DISPLAY_STRING	; AH <- function to display string
	int	21h
	push	es			;push far pointer to PSP
	xor	ax,ax
	push	ax
bpn9b	proc	far			;fake for far return
	ret
bpn9b	endp

;   Set up references to Load area prior to self replicating move.

lini00: 				    ; @@

	mov	LoadCS,CS
	mov	LoadSS,SS
;	mov	LoadSP,SP
	mov	LoadES,ES
	mov	AX,ES:PSP.End_Alloc
	mov	InitAllocEnd,AX
	mov	AX,ES:PSP.Env_Seg	; Establish addressability of the
	mov	OldEnvSeg,AX		;   Environment Segment

	mov	AH,SET_BLOCK		    ; Allocate the rest of the memory
	mov	BX,0FFFFh		    ; in the machine to ourselves
	int	21h
	cmp	AX,error_not_enough_memory
	ljne	LoadError

	mov	AH,SET_BLOCK
	int	21h
	ljc	LoadError

	mov	AX,ES			    ; (BX) = SegAddr of the End of the
	add	BX,AX			    ;	     allocated segment
	mov	SegEndMem,BX

;   To ease calculation of memory requirements for the load, we read the
;   segment table summing the total amount of memory required.	This
;   is an inefficiency since the table is later re-read during the load
;   to allocate the actual sements involved in the load.

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

	mov	SI,offset LdrGrp:End_Stb_Ldr	; *(SI) = start of NEW exe HDR
	mov	CX,LdrGrp:[SI].mte_segtbl_cnt	;  (CX) = number of segments in SegTable
;	mov	LdrGrp:FirstIterSeg,CX		; FirstIterSeg = LastSeg so if none
					;     are found we load the whole file
	xor	AX,AX
	mov	BX,AX
	mov	DX,LdrGrp:[SI].mte_heapsiz	; Start with the default heap and
	add	DX,LdrGrp:[SI].mte_stacksiz	; stack sizes.
	adc	BX,0
	add	SI,LdrGrp:[SI].mte_segtbl_off
lini01:
IF	FALSE
	inc	AX			; FOR each segment in module

	test	LdrGrp:[SI].ste_flags,ITERATED ; IF (Segment is not ITERATED data)
	jz	lini02			    ;	    THEN just add MinAllocSize to
					    ;		running total

	mov	LdrGrp:[FirstIterSeg],AX	;   ELSE record the first iterated
	mov	DI,LdrGrp:[SI].ste_offset	;	segment and its file offset in
	mov	LdrGrp:[IterSegOff],DI		;	blocks

lini02:

	test	LdrGrp:[SI].ste_flags,HUGE	; IF !(seg_flags && HUGE )
	jz	lini02_2			;    THEN MinSize is in bytes

	push	CX			;	     ELSE MinSize is in sectors
	mov	DI,DX			;  Save running totals and Sector count
	mov	BP,BX

	mov	DX,LdrGrp:[SI].ste_minsiz   ;	(DX) = MinSize in sectors

	xor	BX,BX

	mov	CX,LdrGrp:End_Stb_Ldr.mte_alignshft ; get segment sector size in bytes

lini02_1:
	shl	DX,1			; convert size in sectors to size in bytes
	rcl	BX,1			; BX:DX = Sec_Size * Seg_MinSize
	loop	lini02_1

	add	DX,DI
	adc	BX,BP

	add	DX,PARAsizeB - 1	; Round up at least to next segment
	adc	BX,0

	pop	CX

	jmp	short lini02_3

lini02_2:
ENDIF	    ; Huge data sector allocation code is currently not needed.

	add	DX,LdrGrp:[SI].ste_minsiz      ; (BX:DX) = (BX:DX) +
	adc	BX,0			       ;	    InitSize of Seg
	cmp	LdrGrp:[SI].ste_minsiz,0  ; @@ if size = 0 -> 64K !!
	jnz	size_ok 		  ; @@ if not zero, go round up size
	inc	BX			  ; @@ so increment high word
size_ok:

	add	DX,PARAsizeB - 1	; Round up at least to next segment
	adc	BX,0			;   and mask off least significant bits
	and	DL,NOT (PARAsizeB - 1)


lini02_3:
	add	SI,SEGTBLsizeB		;

	loop	lini01			; END of FOR


	rcr	BX,1			; (DX) = (BX:DX) in paragraphs
	rcr	DX,1			;     == Minimum Total Mem in ParaGraphs
	rcr	BX,1
	rcr	DX,1
	rcr	BX,1
	rcr	DX,1
	rcr	BX,1
	rcr	DX,1


	mov	AX,CS			; (AX) = Address of Top of required
	add	AX,DX			;	 memory
	cmp	AX,0A000h		; Continue IF ((AX) <= 640K )
	jb	lini04

lini03:
	mov	AX,ERROR_NOT_ENOUGH_MEMORY
	jmp	LoadError

lini04:

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

	mov	[MaxMemUsed],AX
	mov	BP,AX

;	mov	AX,(LOADER_SIZE + (PARAsizeB - 1)) / PARAsizeB
	mov	AX,offset LdrGrp:End_Stb_Ldr	   ; (AX) = (End_Stb_Ldr -
	mov	si,ax
;	add	AX,STACKsizeB		    ;	      STACKsizeB +
	add	AX,PARAsizeB - 1	    ;	      (PARAsizeB - 1))/
	shr	AX,1			    ;			      PARAsizeB
	shr	AX,1
	shr	AX,1
	shr	AX,1
	add	AX,STACKsizeP

; Initialize cbNew_Header to the length of the new EXE header
;	push	ax			; save ax
;	mov	BX,LdrGrp:[SI].mte_entrytbl_off
;	add	BX,LdrGrp:[SI].mte_entrytbl_len
;	mov	di,bx			; offset of filename
;	add	di,si
;	mov	cx,LdrGrp		; get program pathname from Hdr
;	mov	es,cx
;
;	xor	al,al			; compute length of pathname
;	mov	cx,MAX_PATH_LENG+MAX_NAME_LEN+5
;	repne scasb
;	sub	cx,MAX_PATH_LENG+MAX_NAME_LEN+5
;	neg	cx			; (CX) = pathname length
;	add	bx,cx
;	inc	bx			; ptr to the zeros
;	inc	bx
;	mov	cbNew_Header,bx 	; save length of new exe hdr
;	pop	ax

	mov	BX,LdrGrp:[SI].mte_segtbl_cnt  ;  The seg_tbl will later be
	shl	BX,1			       ;  expanded to include a handle
	shl	BX,1			       ;  entry, and status flags. So we
	mov	dx,bx
	shl	bx,1				; BX <- segtbl_cnt * 12
	add	bx,dx
	add	BX,cbNew_Header 		   ;  need to reserve some space now
	add	BX,PARAsizeB -1
	shr	BX,1
	shr	BX,1
	shr	BX,1
	shr	BX,1			    ; (BX) = Parasizeof(NewHeader)


	mov	CX,BX			    ; (CX) = sizeof(Loader)
					    ;	     + sizeof(Hdr)
	add	CX,AX

	mov	cs:SegLdrSize,CX	; save size of stubloader for later use

	mov	DX,LdrGrp:[SegEndMem]	    ; IF ( (EndMem - (CX)) <
	sub	DX,CX			    ;	    (MaxMemUsed) )
	cmp	DX,BP
	jb	lini03			    ;	THEN not enough memory to load

	sub	DX,TRANSsizeP		    ; IF ((EndMem - (CX) - TRANSsize) <
	cmp	DX,BP			    ;	    (MaxMemUsed) )
	mov	DX,[SegEndMem]
	jb	lini05			    ;	THEN  overlay transient

	sub	DX,TRANSsizeP		    ; No need to overlay transient
	add	cs:SegLdrSize,TRANSsizeP



lini05:

; WE now want to move the Stub Loader and New Exe header to the high
; end of memory, as they will eventually be discarded.	To do this, we
; first resize the initial program load segment to free enough space
; for the relocated copies, and then allocate that space as a separate
; unit of memroy allocation.  This enables us to resize memory later to
; exclude the original copy of the Stub Loader, without freeing the area
; containing the relocated copies prematurely.	At this point, DX contains
; the segment address of the end of memory (possibly less space for the
; Transient area)

	mov	SI,AX			    ; Save Ldr size in paras
	mov	DI,BX			    ; Save new exe header size
	mov	AX,LoadES		    ; Start of existing segment
	mov	BX,DX			    ; Current End of Memory
	sub	BX,AX			    ; Current seg. size in paras
	sub	BX,CX			    ; Free space for Ldr+Hdr
	dec	BX			    ; 1 para for memory arena
	mov	ES,AX			    ; Point at current segment
	mov	AH,SET_BLOCK		    ; Resize segment down
	int	21h			    ; to release mem for Ldr+Hdr
	ljc	LoadError		    ; should not fail

; Now reclaim the space released by the previous resize as a separate segment

	mov	BX,CX			    ; Get # of paras to claim
	mov	AH,ALLOCATE_BLOCK	    ; Allocate BX paras of memory
	int	21h			    ; return segment addr in AX
	ljc	LoadError		    ; should not fail
	mov	ES,AX			    ; ES = segment addr of new segment
	mov	cs:LdrRelocSeg,AX	    ; Save for free-up later
	mov	DX,AX			    ; Get Start of new segment in DX
	mov	AX,SI			    ; Recover Ldr size in paras
	mov	BX,DI			    ; Recover new exe hdr size in paras
	add	DX,BX			    ; Point at where Loader is to go

; Set up to relocate the Stub Loader. At this point:
;
;  DX = start address in new segment for Loader
;  AX = Loader size in paras
;  BX = New Exe header size in paras

	mov	CX,AX			    ; (CX) = move count in paras
	shl	CX,1
	shl	CX,1
	shl	CX,1			    ; (CX) = move count in words
	mov	ES,DX			    ; (ES:DI) = destination
	xor	DI,DI
	mov	DX,CS			    ; (DS:SI) = source
	mov	DS,DX
	mov	SI,DI
	cld				    ;@@ clear direction flag
	repnz	movsw			    ; Move the Loader
	mov	AX,ES			    ; Save Loader destination segment

; Now move the new exe header

	mov	DX,ES			    ; (DX) = new EXEhdr segment
	sub	DX,BX
	mov	CX,BX			    ; (CX) = move count in words
	shl	CX,1
	shl	CX,1
	shl	CX,1
	mov	ES,DX			    ; (ES:DI) = destination
	xor	DI,DI
	mov	SI,offset LdrGrp:End_Stb_Ldr
	repnz	movsw			    ; Move the Header

	mov	DS,AX			    ; Code Segment already moved
ASSUME	DS:LdrGrp			    ; (the moved version
	mov	LdrGrp:mte_h,ES

	mov	BX,offset LdrGrp:End_Stb_Ldr	; New Stack comes after the
	add	BX,PARAsizeB - 1		; Loader
	shr	BX,1
	shr	BX,1
	shr	BX,1
	shr	BX,1
	add	BX,AX

	mov	SS,BX
	mov	SP,STACKsizeB

; Transfer control to relocated portion of loader by setting up the stack
; and issuing a RETF.  We have to define TmpFarProc to fool the assembler.

	push	AX			    ; (AX) == NewCS
	mov	AX,offset LdrGrp:BeginStubLdr
	push	AX

TmpFarProc  PROC FAR
	ret
TmpFarProc  ENDP


;*** BeginStubLdr - Begin relocated part of Stub Loader
;
; This is the beggining of the relocated portion of the loader.  The
; above TmpFarProc "rets" to here.  The new memory map looks like:
;
;		    +-----------+
;		    |DOS 2.x PSP|
;		    +-----------+
;		    |API Lib. Rt|
;		    +-----------+
;		    | Usable	|
;		    | Memory	|
;			. . .
;		    +-----------+
;		    |New Exe Hdr|
;		    +-----------+
;		    |Stub Loader|
;		    +-----------+
;		    | Stack	|
;		    +-----------+
BeginStubLdr:

; We have a preallocated PTDA because there
; is no PTDA for the Parent Process. Instead we have to adjust the
; segment value of the PTDA to account for the move.
	mov	CX,CS			; Establish addressability to the PTDA
	mov	ES,CX			; (ES) = seg   PTDA to be initialized
	mov	DS,CX			; (DS) = seg   PTDA to be initialized
ASSUME	DS:LdrGrp
	mov	LdrGrp:ptda_handle,CX	    ; Save PTDA handle in PTDA
ASSUME	DS:NOTHING

;	mov	AX,DS			; (AX) = PTDA Segment
;	or	AX,AX			; IF (PTDA Segment != NULL
;	jnz	PTDAok			;     THEN continue
;
;	mov	AX,ERROR_ARENA_TRASHED	;     ELSE signal ERROR
;	jmp	LoadError

PTDAok:
;
;   Re-Size memory to exclude Stub loader from that allocated to the RunTime
;   support Modules.  The Per segment memory will be allocated as needed by
;   PreloadSegAlloc
;
	mov	AX,LoadES
	mov	BX,LoadSS
	sub	BX,AX			; (BX) = Resident Size in paragraphs
	mov	ES,AX
	mov	AH,SET_BLOCK
	int	21h
	ljc	LoadError

	mov	DX,LdrGrp:mte_h
	mov	DS,DX
	xor	SI,SI			; (DS:SI) -> mte segment
	mov	BP,SP
	sub	SP,NEXE_VAR_LEN+(BASE_VAR_LEN - 3*WORDsizeB)

	call	load_testdbcs_init	; initialize dbcs table

	call	BuildPathName		; Find and Open the Load File.	This
					;   routine fills in ex_fh and RenBuf

IFDEF	debug_statistics
	gtime	TimeEI
ENDIF

	call	LdrGrp:Set_NewEnviron	       ; setup the new environment

IFDEF	debug_statistics
	gtime	TimeLE
ENDIF

	call	Load_NewEXE

IFDEF	debug_statistics
	gtime	TimeEX
ENDIF

	jmp	LdrGrp:Set_NewEnvironX		; start application program

LoadInit	ENDP



BREAK	<Read in a new .EXE file>

;**	Load_NewEXE - load a new .EXE file format program.
;
;	This routine is called by LoadInit function.  This routine is
;	an offspring of the DOS 5.0 PROTECT MODE Load_NewEXE
;	Load the new .EXE file and save initialiasation information in
;	the PTDA, and the frame.
;
;	ENTRY	(DS:SI) = offset of new header
;		frame variables allocated and setup
;		localvar	ex_lib_name,DWORD
;		localvar	ex_func,BYTE
;		localvar	ex_mode,BYTE
;		localvar	ex_fh,WORD
;		localvar	ex_mte,WORD
;		localvar	ex_environ,WORD
;		localvar	ex_cmdoff,WORD
;		localvar	ex_ste,WORD
;		localvar	ex_segno,WORD
;		localvar	ex_seg_alias,WORD
;		localvar	ex_reloc_item,<SIZE ri_s>
;		localvar	ex_mte_sel,WORD
;
;		Direction Flag Cleared
;
;	EXIT
;		Module table entry setup.
;		RenBuf setup for Set_NewEnviron.
;
;	USES	All except BP
;
;	Module Table Entry Format
;	=========================
;	mte_signature	dw	?; must contain "NE"
;	mte_version	db	?;
;	mte_revision	db	?;
;	mte_entrytbl_off dw	?; file entry table off
;	mte_entrytbl_len dw	?; length of export entry table (bytes)
;	mte_crc 	dd	?; crc of entire file contents
;	mte_flags	dw	?;
;	mte_autods	dw	?; auto data segment table index
;	mte_heapsiz	dw	?; dynamic heap bytes added to data seg
;	mte_stacksiz	dw	?; stack bytes added to data segment
;	mte_startproc	dd	?;
;	mte_stack	dd	?;
;	mte_segtbl_cnt	dw	?; # of entries in segment table
;	mte_modref_cnt	dw	?; # of entries in module ref table
;	mte_nonresnam_len dw	?; length nonresident name table (bytes)
;	mte_segtbl_off	dw	?; file segment table offset
;	mte_resrctbl_off dw	?; file resource table offset
;	mte_resnamtbl_off dw	?; file resident name table offset
;	mte_modref_off	dw	?; file module reference table offset
;	mte_importnam_off dw	?; file import name table offset
;	mte_nonresnam_off dw	2 DUP (?); nonresident table off
;	mte_movent_cnt	dw	?; count of movable entries
;	mte_alignshft	dw	?; segment alignment shift count
;			dw	3 DUP (?)
;	mte_segrefbytes dw	?; segment reference bytes offset
;	mte_usecount	dw	?; usage count for program module
;	mte_pathname	dw	?; offset to module pathname
;	mte_s	ENDS
;	=====================
;


    ASSUME DS:NOTHING,ES:NOTHING,SS:TaskArea

	PUBLIC	Load_NewEXE
Load_NewEXE PROC NEAR			; (fh, modname)

;	 call	 BuildPathName	 ; Find and Open the Load File.  This
					;   routine fills in ex_fh and RenBuf
	mov	CX,SIZE mte_s		; Copy New EXE Header onto frame stack
	sub	SP,CX
	mov	DI,SP
	push	SS
	pop	ES
	REPNZ	movsb

	mov	BX,SP			; Access New Header through mte_s STRUC
	cmp	SS:[BX].mte_signature,EXE_VALID_NEWSIGNATURE
	jne	lne06			; error invalid format

	test	SS:[BX].mte_flags,INVALID
	jnz	lne06			; must be a valid image

	test	SS:[BX].mte_flags,LIBRARYMOD ; cannot execute a library module
	jz	lne0			; the module is ok
lne06:
	mov	al,ERROR_BAD_FORMAT	; report an error
	jmp	LoadError

lne0:

lne4:

;************ No Need to look if module is present.  its NOT ******
;	Module hasn't been loaded yet.
;	Create module table entry

lne4_1: mov	BX,SP			; Allocate and load module table entry.
	call	Create_MTE		; (AX) = Create_mte (ptr_New_EXE_Header)
;	mov	AX,ex_mte
	add	SP,SIZE mte_s		; clean up frame stack




;	need to check if file and module version, revision and CRC match

;************ No Need to map ex_mte to ex_mte_sel. in real mode they are the same
;	mov	ex_mte_sel,AX

	call	LoadModule		; Load segments for this module

;	Save CS:IP and DS for program to begin execution.
;
	mov	AX,DS:word ptr mte_startproc+2
	call	Num_to_STE
	mov	AX,[SI].ste_seghdl
	mov	word ptr ex_cs_ip+2,AX
	mov	AX,DS:word ptr mte_startproc
	mov	word ptr ex_cs_ip,AX

;	Setup Initial Stack and Data Segments.

	mov	AX,DS:word ptr mte_stack+2  ; Set SS to initial Stack segment
	call	Num_to_STE
	mov	AX,[SI].ste_seghdl
	mov	word ptr ex_ss_sp+2,AX

	mov	BX,DS:word ptr mte_stack	;BX=size of stack in byte
	mov	AX,DS:mte_autods
	cmp	AX,DS:word ptr mte_stack+2  ; IF ((SS == DS ) &&
	jne	lne8
	or	BX,BX			    ;			 (SP == 0))
	jnz	lne8

	mov	BX,[SI].ste_seghdl	    ; Get initial DS segment
	mov	AX,[SI].ste_minsiz	    ; Set SP to top of auto data segment
	add	AX,DS:mte_stacksiz	    ;	 just below the additional heap.

	xchg	AX,BX			    ; (BX) := auto DS segment #
	jmp	short	lne9

lne8:	call	Num_to_STE
	mov	AX,[SI].ste_seghdl	; Not autoDS so get actual DS segment
lne9:
	and	BX,0FFFEh		; force word alignment
	mov	word ptr ex_ss_sp,BX
	cmp	DS:word ptr mte_autods,0 ; If no auto data seg then put 0 to
	jne	lne10			 ; DS.	cp12 b705150
	mov	ax,0
lne10:
	mov	ex_ds,AX		; pass DS back to exec
;
;	Clean up and leave
;
	mov	AH,CLOSE_HANDLE 	; Release file handle
	mov	BX,ex_fh
	int	21h

	ret
load_newexe ENDP




BREAK	<LoadModule - Load a new exe file for given module table entry.>
;***	LoadModule - Load a new exe file for given module table entry.
;
; For each segment in the module, compute the segment allocation size and
; allocate memory.  Also indicate that the segment is being loaded so that
; its memory will not be discarded during the load.  A huge segment is
; represented by a consecutive group of segment table entries, each but the
; last, for a 64K chunk of the segment.  We load these contiguously, each
; call to PreloadSegAlloc deals with one huge segment. For the initial code
; and stack segments, save their starting paragraph numbers in the module
; table entry for the module being loaded.  Each segment in the module which
; has not been	previously initialized will be initialized and loaded as
; required.  All data segments will be forced to be preloaded.
;
;	ENTRY	(BP) = base of loader stack frame variables
;
;	EXIT	None
;
;	USES	All except BP
;

LoadModule PROC NEAR				; (fh, mte)
	mov	ax,1				; get first ste
lmd1:	cmp	ax,ds:mte_segtbl_cnt		; for each segment in module
	ja	lmd5				; end of segment table

lmd2:	call	PreloadSegAlloc 		; Allocate memory for segment

lmd4:	mov	ax,ex_next_segno		; Skip past ste's for allocated
	mov	ex_start_segno,ax		; segment (more than 1 if huge)
	jmp	lmd1				; repeat for next segment

; All segments in the module have been allocated memory. Now load the required
; segments.  First, create a buffer for reading the program file into.

lmd5:

IFNDEF	loadslow

	mov	ah,ALLOCATE_BLOCK
	mov	bx,0FFFFh		; try to allocate all of memory
	int	21h			; if alloc does not fail correctly
	cmp	AX,error_not_enough_memory
	ljne	LoadError		; then return error

	sub	bx,cs:SegLdrSize	; reduce paragraphs free by ldr size
	jc	lmd5c			; if BX too small, use slow mode
	sub	bx,2			; plus a bit more for arena overhead
	jc	lmd5c			; if BX too small, use slow mode
	cmp	bx,MINREADBUFPAR	; if too little can be allocated
	jb	lmd5c			; forget the whole thing (use slow mode)
	cmp	bx,FASTMAXMEM		; if BX < 64k
	jb	lmd5a			; then proceed to allocation
	mov	bx,FASTMAXMEM		; else BX <- 64k
lmd5a:
	mov	ah,ALLOCATE_BLOCK
	int	21h			; if call fails
	ljc	LoadError		; then return error

	mov	cs:FastMode,FASTFLAG	; set fast mode
	mov	cs:BigBufSeg,ax 	; BigBufSeg <- segment allocated
	shl	bx,1			; convert size from paragraphs to bytes
	shl	bx,1
	shl	bx,1
	shl	bx,1
	mov	cs:BigBufSiz,bx 	; BigBufSiz <- bytes allocated
lmd5c:

ENDIF
	mov	ex_segno,1
	mov	DS,ex_mte_sel		; (DS) = mte selector

	mov	CX,DS:mte_modref_cnt
	jcxz	lmd6

	mov	SI,DS:mte_modptrs	; (SI) = offset to modptrs table

lmd6:	mov	AX,ex_segno
	cmp	AX,DS:mte_segtbl_cnt	; for each segment in module
	ja	lmdx			; end of segment table

	call	Num_to_STE		; get offset to ste

	call	LoadSegment		; load in the segment

lmd7:	inc	ex_segno		; next segment
	jmp	short lmd6

;	code and data segments have been initialized
lmdx:

IFNDEF	loadslow

	test	cs:FastMode,FASTFLAG	; if NOT fast mode
	jz	lmd50			; then continue
	push	es			; else
	mov	ah,FREE_BLOCK		; free segment allocated for read
	mov	es,cs:BigBufSeg
	int	21h
	pop	es
lmd50:

ENDIF
	ret

LoadModule ENDP



BREAK	<LoadSegment - Load segment within module.>
;***	LoadSegment - Load segment within module.
;
;		Reads the segment from the file and does the
;		relocation fixups.
;
;	ENTRY	(DS) = module table entry selector
;		(SI) = offset to segment table entry
;		ex_segno and ex_mte have been setup
;
;	EXIT	(DS) = module table entry selector
;		(SI) = offset to segment table entry
;
;	USES	All except DS, SI, BP
;

LoadSegment PROC NEAR
	mov	ex_ste,SI		; setup segment table entry pointer

	mov	AX,[SI].ste_seghdl	; Get base of memory block allocated
	mov	ex_seg_alias,AX 	; to this segment onto the frame.

	mov	DX,[SI].ste_offset	; DX = offset to segment data (in sectors)
	or	DX,DX			; IF ( ste_offset == 0)
	ljz	lsg6			;    THEN no reloc. data: skip read.

	xor	AX,AX
	mov	CX,DS:mte_alignshft	; get segment sector size in bytes

lsg1:	shl	DX,1			; convert sector offset to byte offset
	rcl	AX,1			; AX:DX = Sec_Size * Seg_Offset
	loop	lsg1

	mov	cx,ax			; (CX:DX) = file offset of segment data
	call	ex_lseek

	mov	SI,ex_ste		;
	mov	DS,ex_mte_sel
	test	[SI].ste_flags,ITERATED ; IF (Segment type == ITERATED DATA)
	jz	lsg4
	call	Load_Iterated_Data
	jmp	short lsg6
;    Data is not iterated.
;
lsg4:
	mov	CX,[SI].ste_minsiz	; get segment data in memory
	push	cx
	mov	CX,[SI].ste_size	; get segment data
	push	cx
	mov	DS,ex_seg_alias 	; just read in the segment
	xor	DX,DX
;  b783377     For a segment of 64K, patch the remaining with zeroes.
;  If a segment ends with trailing zeroes, the size field does not include
;  those bytes and the minsiz field has the actual size of the segment.
;  So the trailing bytes should be reset to zero.
	call	ex_read 		; (fh, seg_addr, segdata->size);
	mov	si,cx			; save cx in si
	pop	bx			; restore ste_size
	pop	cx			; restore ste_minsiz
	cmp	bx,cx
	jge	lsg41
	push	di
	push	es
	mov	es,ex_seg_alias
	mov	di,bx
	sub	cx,bx
	mov	al,0
	rep	stosb
	pop	es
	pop	di
lsg41:	mov	cx,si			; restore cx

;     Do relocation fixups for segment.
;
lsg6:	mov	SI,ex_ste
	mov	DS,ex_mte_sel
	test	[SI].ste_flags,RELOCINFO
	ljz	lsg27			; segment has no relocation items

	mov	BX,SS
	mov	DS,BX			; (DS) -> PTDA stack
	push	CX			; get temp space on stack
	mov	DX,SP
	mov	CX,WORDsizeB		; read number of relocation items
	call	ex_read
	pop	CX			; (CX) = number of relocation items

;     Process each relocation item.
;

lsg7:	or	CX,CX
	ljz	lsg27			; all done check thunks

	push	CX			; save number of relocation items

;    If no type flags are set the item is skipped.  This allows
;    a blank relocation item to exist in the relocation records.
;
	mov	BX,SS
	mov	DS,BX
	lea	DX,ex_reloc_item
	mov	CX,SIZE ri_s
	call	ex_read 		; (fh, reloc_item, SIZE ri_s);

	mov	DS,ex_mte_sel
	mov	AL,ex_reloc_item.ri_flags
	and	AL,TARGET_MASK		; (AL) = relocation target type
	cmp	AL,INTERNALREF
	jne	lsg9			; not internal, check for import

;    Handle the Case of INTERNALREF segments
	mov	AX,ex_reloc_item.ri_target_seg
	xor	AH,AH
	mov	BX,ex_reloc_item.ri_target_off

	cmp	AL,MOVABLE_ENTRY	; IF (Target_seg == MOVABLE_ENTRY)
	je	lsg8a			;    THEN go get value from Entry Table

lsg8:					;    ELSE Segment is Fixed
;	The above target address should be a selector:offset pair.
;	In the case of Fixed internal reference, we simply retrieve the
;	Segment handle allocated.
;
	call	Num_To_Ste		;	  Convert Seg. No. to SegTblEnt
	mov	DX,[si].ste_seghdl
	jmp	short lsg25		; do relocation fixups


;   Handle Internal Refs to movable Segments by converting the Entry Ordinal
;   in AX to a Segment:offset pair in DX:BX
lsg8a:

	mov	AX,BX			; (AX) = Entry Ordinal
	call	GetMovAddr		; (DX:BX) = GetMovAddr(AX)
	jmp	short lsg25		; do relocation fixups


lsg9:	cmp	AL,IMPORTNAME		; IF ((FIXUP_Type == IMPORTNAME ) ||
	je	lsg10			;     (FIXUP_Type == IMPORTORDINAL))
	cmp	AL,IMPORTORDINAL	;   THEN skip to next fixup and let
	jne	lsgOSFIX		;	fixup helper handle it

; Handle Dynamic Link fixups using Stub Loaders FixUp_Helper Table
lsg10:
	mov	SI,NextFixupIndex	; (DX:BX) = *pFixUp_Helper++
	shl	SI,1			;   Convert SI to a DWORD index
	shl	SI,1
	inc	NextFixupIndex
	lES	BX,[SI].FixUp_Helper
	mov	DX,ES
lsg25:
	call	dofixups		; do relocation fixups
	jmp	short lsg26

; floating point fixup table (23-Sep-86)

	; the following table is dependent on definitions
	.erre	FP_TYPE_0 EQ 1
	.erre	FP_TYPE_1 EQ 2
	.erre	FP_TYPE_2 EQ 3
	.erre	FP_TYPE_3 EQ 4
	.erre	FP_TYPE_4 EQ 5
	.erre	FP_TYPE_5 EQ 6

lsgFtable:
	dw	FIARQQ			; case FP_TYPE_0
	db	FJARQQ_MASK
	dw	FISRQQ			; case FP_TYPE_1
	db	FJSRQQ_MASK
	dw	FICRQQ			; case FP_TYPE_2
	db	FJCRQQ_MASK
	dw	FIERQQ			; case FP_TYPE_3
	db	FDUMMY_MASK
	dw	FIDRQQinc		; case FP_TYPE_4
	db	0			; space filler
	dw	FIWRQQinc		; case FP_TYPE_5


; Handle OSFIXUPS.
lsgOSFIX:
	cmp	AL,OSFIXUP		; IF (Fixup_Type != INTERNALREF,
					;     IMPORTNAME
	ljne	LsgErr			;     IMPORTORDINAL,OSFIXUP)
					;   THEN return error

	mov	SI,ex_reloc_item.ri_source
	mov	DS,ex_seg_alias 	; (DS:SI) = source address

	mov	AX,ex_reloc_item.ri_fp_type

	; the following code is dependent on definitions
	.erre	FP_TYPE_0 EQ 1
	.erre	FP_TYPE_1 EQ 2
	.erre	FP_TYPE_2 EQ 3
	.erre	FP_TYPE_3 EQ 4
	.erre	FP_TYPE_4 EQ 5
	.erre	FP_TYPE_5 EQ 6


;   OSFIXUP type 0-3 are of the form "FWAIT,SegOver,ESCn" where 'n' can
;   vary from 0 - 7.  We replace this with "INT,3C,ESCn | (SegOverMask)".
;   SegOverMask encodes the two segment register id bits into the high
;   two bits of the ESC instruction

lsgOStg:
	dec	ax			; convert for table lookup (0 - 3,4,5)
	cmp	AX,FP_TYPE_5 - 1	; if NOT within range
	jbe	lsgOStg1

LsgErr: mov	AX,ERROR_BAD_FORMAT	; report an error
	jmp	LoadError

LsgOStg1:
	mov	bx, offset lsgFtable	; BX <- pointer to table
	add	bx, ax
	add	bx, ax
	add	bx, ax			; BX <- desired location

	cmp	ax,FP_TYPE_4 - 1	; if in range 0-3
	jb	lsgOSt03		; then process as such
					; else process as 4-5

;   OSFIXUP type 4 is of the form "FWAIT,ESCn". We replace this with
;   "INT,34+n".  An artifact of the instruction set of the 80286/8287 lets
;   us accomplish this by simply adding a constant <FIDRQQinc> to the existing
;   instruction.

;   OSFIXUP type 5 is of the form "NOP,FWAIT".	We add to this the difference
;   of "INT,3D" and "NOP,FWAIT".

	mov	ax, cs:[bx]
	add	word ptr DS:[SI],ax
	jmp	short lsg26		; Done with this fixup
lsgOSt03:
	mov	ax, cs:[bx]
	mov	word ptr DS:[SI],ax	; do fixup
	mov	al, cs:[bx+2]
	and	byte ptr DS:[SI+2],al
lsg26:					; done with this fixup
	pop	CX
	dec	CX
	jmp	lsg7			; get next relocation item

;	restore SI [so that (DS:SI) -> segment table entry in mte]
;
lsg27:
	mov	DS,ex_mte_sel
	mov	SI,ex_ste
lsgx:	ret
LoadSegment ENDP

;***	load_iterated_data - load iterated data
;
;	Reads the iterated data from the file, expands it and loads
;	it into the segment.
;
;	ENTRY	(DS) = module table entry selector
;		(SI) = offset to segment table entry
;		(ES) = alias selector to segment
;		(BP) = base of loader stack frame variables
;
;	EXIT	CY clear if success
;		   (DI) = offset to uninitialised space in segment
;		CY set if error
;		   (AX) = error code returned from ex_sft_read
;			  ERROR_ITERATED_DATA_EXCEEDS_64k
;			  ERROR_INVALID_MINALLOCSIZE
;			  ERROR_BAD_EXE_FORMAT
;
;	USES	All except BP

	ASSUME	DS:NOTHING, ES:NOTHING, SS:TaskArea

Procedure load_iterated_data,near

;	initialise local variables
	mov	ax,ex_seg_alias;
	mov	lid_seg_alias,ax	; save alias selector to segment

	mov	ax,[si].ste_size	; (AX) = bytes of seg data in file
	mov	lid_rem_filedata,ax

	xor	di,di
	mov	lid_avail_offset,di	; set offset to avail space in segment

	mov	lid_avail_sizeH,1	; assume segment size = 64k

	mov	cx,[si].ste_minsiz
	mov	lid_avail_sizeL,cx
	jcxz	lid4

	mov	lid_avail_sizeH,di	; segment size is less than 64k

;	(AX) = lid_rem_filedata
;	take care of the case where filedata size = 64k

lid4:	or	ax,ax			; is filedata size == 64k?
	jnz	lid8			;  no, continue

	mov	cx,4			; (CX) = size of hdr in iteration rec
	mov	ax,0FFFFh		; start with size = 64k - 1
	sub	ax,cx
	inc	ax			; adjust for 64k filedata size
	mov	lid_rem_filedata,ax	; save size of remaining filedata
	jmp short lid12

;	while (TRUE) {
;	  if ((lid_rem_filedata -= 4) < 0)
;	    error;
;	  if (lid_rem_filedata == 0)
;	    break;
;	  read iteration record header;
;	  if ((NumIterations ==  0) || (PatternSize == 0))
;	    continue;
;	  if ((lid_rem_filedata -= PatternSize) < 0)
;	    error;
;	  ExpandedSize = PatternSize * NumIterations
;	  if ((lid_avail_size -= ExpandedSize) < 0)
;	    error;
;	  read Pattern;
;	  expand the Pattern;
;	  lid_avail_offset = offset to byte after expanded data
;	  if (lid_rem_filedata == 0)
;	    break ;
;	}

lid8:	mov	cx,4			; read NumIterations & PatternSize
	sub	lid_rem_filedata,cx	; update remaining file data
	lje	lid60			; return if no filedata

	jc	lid52			; error if incomplete record header

;	(CX) = 4

lid12:	push	ss
	pop	ds			; (DS) = parent's PTDA selector
	sub	sp,cx			; temp space to read iteration values
	mov	dx,sp			; (DS:DX) -> space to read in data
	call	ex_read 		; read iterated data record header
	pop	ax			; (AX) = NumIterations
	pop	cx			; (CX) = PatternSize

	or	ax,ax			; NumIterations == 0 ?
	jz	lid8			;  yes, move on to the next record

	jcxz	lid8			; if (PatternSize == 0) ...
					; ... move on to the next record
	sub	lid_rem_filedata,cx	; update size of remaining file data
	jc	lid52			; error if we had a bad record

;	duplicate the iterated data for the required number of iterations

	mul	cx			; (DX:AX)= ExpandedSize
	cmp	dx,1			; is ExpandedSize == 64k?
	je	lid20			;  may be, check the low word (AX)

	jb	lid28			; ExpandedSize is less than 64k

	jmp short lid24 		; error, ExpandedSize > 64k

lid20:	sub	lid_avail_sizeH,dx	; is available size == 64k?
	jc	lid32			;   no, return error

;	(DX:AX) = ExpandedSize
;	We found DX = 1, hence AX must be 0 (since maximum size is 64k).

	or	ax,ax			; ExpandedSize larger than 64k?
	jnz	lid24			;  yes, return error

	mov	ax,0FFFFh
	sub	ax,cx			; (CX) = PatternSize
	inc	ax			; (AX) = ExpandedSize - PatternSize
	jmp short lid44

lid24:	stc
	mov	ax,ERROR_ITERATED_DATA_EXCEEDS_64k
	jmp short lidx

;	(AX) = ExpandedSize
;	(CX) = PatternSize
;	We have determined that ExpandedSize is less than 64k.

lid28:	mov	bx,lid_avail_sizeH
	or	bx,bx			; is available size == 64k?
	jnz	lid36			;  yes

	sub	lid_avail_sizeL,ax	; adjust available space in segment
	jnc	lid40

lid32:	mov	ax,ERROR_INVALID_MINALLOCSIZE
	jmp short lidx			;  yes, report an error

;	We have determined that available memory = 64k.

lid36:	mov	bx,0FFFFh
	sub	bx,ax
	inc	bx
	mov	lid_avail_sizeL,bx	; update the available space...
	mov	lid_avail_sizeH,0	; ...in segment

lid40:	sub	ax,cx			; (AX) = ExpandedSize - PatternSize

lid44:	push	ax			; (AX) = ExpandedSize - PatternSize
	push	cx			; (CX) = PatternSize
	mov	dx,lid_avail_offset
	mov	ds,lid_seg_alias	; (DS:DX) -> address to read data in
	push	dx
	push	ds
	call	ex_read 		; (fh, seg_addr, segdata->size);
	pop	ds
	pop	si			; (DS:SI) -> Pattern just read in
	pop	di			; (DI) = PatternSize
	pop	cx			; (CX) = ExpandedSize - PatternSize

	push	ds
	pop	es
	add	di,si			; (ES:DI) -> byte after Pattern in seg
	rep movsb			; iterate the data
	mov	lid_avail_offset,di	; (DI) = offset to avail space in seg
	mov	cx,lid_rem_filedata
	jcxz	lidx

	jmp	lid8

lid52:	mov	ax,ERROR_BAD_EXE_FORMAT
lid56:	jmp short lidx

lid60:	mov	di,lid_avail_offset
lidx:	ret
EndProc load_iterated_data




BREAK	<DoFixups - Do the fixups following the source chain.>
;***	DoFixups - Do the fixups following the source chain.
;
;	ENTRY	(DX:BX) = target address
;
;	EXIT	None
;
;	USES	CX, SI, DS
;
;		note that DX contains the handle... you may
;			have to call HandleGetSelector to
;			get the slector o be used in the fixups...
;

	PUBLIC	dofixups
dofixups PROC NEAR
	mov	SI,ex_reloc_item.ri_source
	mov	DS,ex_seg_alias 	; (DS:SI) = source address
	test	ex_reloc_item.ri_flags,ADDITIVE
	jz	dfu1			; follow chain of fixups

	call	fixup			; (DX:BX) = target address
	jmp	short dfux

dfu1:	push	[SI]			; save next source address
	call	fixup			; (DX:BX) = target address
	pop	SI			; get next source address
	cmp	SI,-1			; is this the end of the source chain?
	jne	dfu1			;  no, fixup new source address

dfux:	ret
dofixups ENDP


;***	fixup - Fixup source address.
;
;	Fixes up the source address defined by the source type.
;	The source address offset may be modified.
;
;	ENTRY	(DS:SI) = source address
;		(DX:BX) = target address
;
;	EXIT	(DS),(DX),(BX) = preserved
;
;	USES	CX, SI
;

	PUBLIC	fixup
fixup PROC NEAR
	mov	CL,ex_reloc_item.ri_stype
	and	CL,SOURCE_MASK

	cmp	CL,FAR_ADR		; if source type is FAR or OFFSET
	je	fix1			;  do an offset fixup

	cmp	CL,LOBYTE		; SteveSa P3687
	je	fix_lo			; support for LOBYTE fixups

	cmp	CL,OFF_ADR
	jne	fix4

;	do the offset fixup and increment the source pointer
;	for the far address segment fixup

fix1:	test	ex_reloc_item.ri_flags,ADDITIVE
	jz	fix2			; no additive fixup

	add	[SI],bx 		; add target to source contents
	jmp	short fix3

fix2:	mov	[SI],bx 		; replace source contents with target
fix3:	add	SI,WORDsizeB		; increment pointer to segment value

fix4:	cmp	CL,FAR_ADR		; if source type is FAR or SEGMENT
	je	fix5			;  do segment fixup

	cmp	CL,SEG_ADR
	jne	fixx

fix5:	test	ex_reloc_item.ri_flags,ADDITIVE
	jz	fix6			; no additive fixup

	add	[SI],dx 		; add target to source contents
	jmp	short fixx

fix_lo: add	[SI],bl 		; SteveSa P3687
	jmp	short fixx		; LOBYTE fix-up is always additive

fix6:	mov	[SI],dx 		; replace source contents with target

fixx:	ret
fixup ENDP




BREAK	<Create_MTE - allocate and load module table entry>
;***	Create_MTE - allocate and load module table entry
;
;	This routine initializes a module table
;	entry. In reality, since the New style header has already
;	been read in, it simple expands the existing Segment Table
;	to allow space for flags and the segment handle.
;	(in real mode segment handles and the actual segments are
;	one and the same.
;
;	ENTRY	(SS:BX) = pointer to new exe header
;
;	EXIT	ex_mte and ex_mte_sel are filled in
;
;	USES	All except BP
;

	PUBLIC Create_MTE
Create_MTE PROC NEAR

;	mov	AX,SS:[BX].mte_entrytbl_off
;	add	AX,SS:[BX].mte_entrytbl_len
;	mov	CX,SS:[BX].mte_segtbl_cnt ; Segment pointers occupy
;	shl	cx,1			  ;	Count_pointers * sizeof(DWORD)
;	shl	cx,1
;	add	ax,cx		    ; Add space for seg ptrs and seg status flags
;	errnz	<(SIZE ste_s)-12>
;	mov	CX,SS:[BX].mte_modref_cnt ; Module ptrs occupy
;	shl	CX,1			  ;	Count_pointers * sizeof(WORD)
;	add	AX,CX			; add space for module pointers

;	Once we have the segment number corresponding to the ordinal#,
;	we can locate the ste from the segment table, and read the
;	handle for the segment. The selector
;	and the offset would then be used for the relocation fixups
;
;	xor	CL,CL		; movable, not [swappable, discardable, shared]
;
;	mov	BX,AX		; (AX:BX) = allocation size in bytes
;	xor	AX,AX

	mov	ax,LdrGrp:mte_H
	mov	ex_mte_sel,AX
	mov	ex_mte,AX
	mov	DS,AX
     ASSUME	 DS:NOTHING

;	(DS:0) now contains the new EXE header data.
;
;	 mov	 DS:mte_usecount,0	 ; Initialize use count for module.
;	 mov	 DS:mte_link,AX 	 ; We must setup link prior to read
;					 ; just in case an error occurs.
;
;	We have read the new exe header. Next read from the segment table
;	until end of entry table
;
;	mov	CX,DS:mte_entrytbl_off
;	add	CX,DS:mte_entrytbl_len
;	sub	CX,DI

	mov	BX,DS:mte_segtbl_cnt ; (BX) = Cnt_pointers * (4 * WORDsizeB)
	mov	CX,BX		     ;	  == Cnt_pointers*8 == Size of orig tbl
	shl	BX,1
	add	CX,BX
	shl	BX,1
	shl	BX,1
	shl	CX,1	    ; (CX) = (Cnt_pointer*WORDsizeB + Cnt_pointer) *
	shl	CX,1	    ;					 WORDsizeB
			    ;	  == Cnt_pointer*12 == Size of expanded table

	dec	BX				; Adjust CX and BX to account
	dec	CX				; for zero based table.

;	module table entry
;
;		|----------------|
;		   newexe header
;		|----------------|
;		 4*mte_segtbl_cnt
;		|----------------|<-- (DS:DX) = buffer pointer for read
;		 segtbl from file
;		|----------------|

	mov	ES,ex_mte_sel
	or	BX,BX			; IF (Segtbl_cnt == 0)
	jz	cmtx			;    THEN  no adjustment is needed

;
;	Module Segment Table Entry format
;
;		ste_s	STRUC
;		ste_offset	dw	?; file offset to segment data
;		ste_size	dw	?; file data size (paragraphs)
;		ste_flags	dw	?; type and attribute flags
;		ste_minsiz	dw	?; minimum allocation size
;		ste_seghdl	dw	?; segment handle
;		ste_selector	dw	?; not used, part of common load struct.
;		ste_s	ENDS
;
;	module table entry
;
;		|----------------|
;		   newexe header
;		|----------------|
;		 segtbl from file
;		|----------------|<-- (DS:SI) = segment table source
;		 4*mte_segtbl_cnt
;		|----------------|<-- (ES:DI) = start of segment table data
;
;	Shift the Data above the Segment Table out of harms way.
;	The expand each seg table entry in the region marked
;	"segtbl from file" into the immediately adjacent region.
;
;	After copying the eight bytes for each entry, two more words are
;	allocated and initialized to zero. One word to hold the handle,
;	and one word to hold the flags for that segment.

; Starting at the top of the New Style Hdr, move everything up by sizeof
; Ste_Expansion bytes.
	mov	SI,cbNew_Header 	; (SI) -> Top of New Style Header
	mov	DI,SI
	add	DI,CX			; (DI) -> Top of Safe Area
	push	CX			; Save sizeof Ste_Expansion of actual
					;      expansion
	add	DS:mte_resrctbl_off,CX	; adjust mte offsets
	add	DS:mte_resnamtbl_off,CX
	add	DS:mte_modref_off,CX
	add	DS:mte_importnam_off,CX
	add	DS:mte_entrytbl_off,CX
	mov	CX,DI			; (CX) = (Top of Header + Expan. Size)
	sub	CX,DS:mte_resrctbl_off	;      - (Top of SetTbl + Expan. Size)
	std				; move from the top down
	repnz movsb
	pop	CX

	mov	SI,DS:mte_segtbl_off	; (DS:SI) -> Top of the read in Segment
	mov	DI,SI			;    table (mte_segtbl_off + SegTblSIZE)
	add	SI,BX			; (ES:DI) ->Top of expanded Segment Table
	add	DI,CX
	xor	AX,AX

	mov	CX,DS:mte_segtbl_cnt
crm1:	mov	BX,CX			; FOR (i=1;i<=mte_segtbl_cnt;i++)
	mov	CX,(SIZE ste_s)-4	;     Adjust the size of each ste.
	errnz	<(SIZE ste_s)-12>
	stosb				;     Init. segment handle to zero.
	stosb
	stosb				;     Init. segment status flags to zero.
	stosb
	rep	movsb			;     Move segment table entry.
	mov	CX,BX
	loop	crm1			; END for
	cld				; Lest we forget, reset the dir. flag.

cmtx:
	ret

create_mte ENDP



BREAK	<LoadError - General error handler for new EXE load errors>
;**	LoadError - General error handler for new EXE load errors
;
;	    LoadError terminates the load process in an orderly fashion
;	    leaving the Parents  environment in a consistent state.  It
;	    permits the return of error codes to the Parent.
;
;	ENTRY	(AL) = loader error code
;		may be AX should be used....
;
;	EXIT	Terminates Load Process
;
;
;	USES	All except AX

	PUBLIC LoadError
LoadError  PROC    NEAR

; @@ display error message before exiting

	push	ax			; @@ save error
	mov	ax, seg MsgSeg			; @@
	mov	ds, ax				; @@ DS <- message segment
	mov	si, offset MsgSeg:TXT_MSG_BIND_RESERVED ; @@ SI <- message text
	mov	cx, word ptr [si]		; @@ get message size
	inc	si				; @@ skip over
	inc	si				; @@   size field
	mov	dx, si				; @@ DS:DX <- message text
	mov	bx, STDERR			; @@ handle = STDERR
	mov	ah, WRITE_FILE			; @@ display the message
	int	21h				; @@
	pop	ax			; @@ restore error

	mov	ah,TERMINATE_PROC
	INT	21h
	ret			    ; Not really needed
LoadError ENDP

; @@ load_error routine deleted -> unnecessary duplication of code

BREAK	<Num_to_STE - Compute segment table entry form segment number.>
;**	num_to_ste - Compute segment table entry form segment number.
;
;	ENTRY	(AL) = segment number
;		(DS) = module table entry
;	EXIT	(SI) = segment table entry offset within module
;		   = 0 if error
;		(AX) = (SI)
;	USES	AX

Num_to_STE  PROC    NEAR
	xor	AH,AH
	or	AX,AX			; is the segment number valid?
	jz	nts1			;  no, return error

	dec	AX
	shl	AX,1
	shl	AX,1
	mov	SI,AX
	shl	AX,1
	add	AX,SI			; (AX) = (AX) * 12
	errnz	<(SIZE ste_s)-12>

	add	AX,DS:mte_segtbl_off	; (AX) = offset to ste
nts1:	mov	SI,AX			; return value in (SI)
	ret
Num_to_STE	ENDP


BREAK	<BuildPathName - Build the pathname of the exec'ed file>
;***	BuildPathName - Build the pathname of the exec'ed file
;
;	This procedure is builds the files pathname in TASKAREA:RenBuf.
;	It is trivial in 3.x. 2.x requires munging with the PATH and the
;	current directroy.
;
;	ENTRY
;		OldEnvSeg == Environment segment containing the PATH variable
;		PDTA created (so that RenBuf is useable)
;	EXIT
;		ex_fh setup
;		RenBuf setup
;	USES
;	    ES,AX,CX,DX,DI
;
;	WARNING!
;		This routine contains path "cracking" code that has not been
;		adapted to DBCS support.
;

		PUBLIC	BuildPathName
BuildPathName	PROC	NEAR
	push	SI
	push	DS


	mov	AH,GET_VERSION
	int	21H
	xchg	AH,AL
	mov	LdrGrp:DosVersion,AX	; save version for future reference
	cmp	AX,VERSION200		; if pre 2.0 DosVersion
	ljb	bpn9a			; then return error
	cmp	AX,VERSION300		;  else IF Pre 3.0 DosVersion,
	ljb	bpn10			;	THEN no invocation string exists
					;	    in the environment segment

;
;   Find the Exec name string contained at the end of the environment segment
;
	mov	CX,MAXENVsizeB
	mov	ES,OldEnvSeg		; Get Environment Segment for scasb.
	xor	AX,AX			; Scan for the end of the environment
	mov	DI,AX			; text (double null terminated)
bpn1:					   ; FOR (i = 0 ; i <= EnvSiz &&
	REPNE	scasb			;	      ( Env[i++] == 0 &&
	scasb				;		Env[i+1] ==0);
	jne	bpn1			;			      );
	inc	DI			; Get past count
	inc	DI
	mov	LdrGrp:NameOff,DI
	mov	SI,DI
	mov	DS,OldEnvSeg

; verify the filename in 8.3 range
	mov	ah,1			;@@ wild char is invalid
	call	name_check		;@@ check name in 8.3 range
	jnc	name_ok 		;@@ filename is legal
	jmp	LoadError		;@@
name_ok:				;@@

;
;   Get the size of the Exec name string and check for the existance of path
;   separators.  This is used later to decide whether to try to open the file
;   using the PATH variable. WARNING! This loop will walk forever if no NULL
;   terminator is found.
;
	mov	BX,AX			;
bpn1_5: 				; WHILE  (Exec_name[i++] != NULL)
	lodsb				; {
; #DBCS#				; test dbcs and bump
	call	load_testdbcs_b 	; if NOT dbcs char
	jnc	bpn1_7			; then continue
	jnz	bpn1_5			; else if NOT a bad character, loop
	jmp	LoadError		; else quit
bpn1_7:

	cmp	AL,'\'                  ;   IF (Path_separator)
	je	bpn2_5			;	THEN Last_Sep = i - 1;

	cmp	AL,'/'
	je	bpn2_5

	cmp	AL,':'
	jne	bpn2_6
bpn2_5:
	mov	DI,SI
	dec	DI
bpn2_6:
	cmp	AL,BL
	jne	bpn1_5			; }

	sub	SI,LdrGrp:Nameoff	; (SI) = strlen(Exec_name)
	push	DI

bpn7:
	mov	CX,CS
	mov	ES,CX
	mov	DI,offset LdrGrp:RenBuf ; (ES:DI) -> RenBuf
	mov	CX,SI			; (CX) == length of Exec_name
	push	CX
	mov	SI,LdrGrp:NameOff	; (DS:SI) == start of pathname
	repnz	movsb

bpn8:
	mov	DX,CS			; Attempt to open the file
	mov	DS,DX
	mov	DX,offset LdrGrp:RenBuf
	call	w_Open
	pop	SI		; IF Error, Check if need to search path (DX at bpn6)
	pop	CX
	jc	bpn9

	jmp	bpnop			; Succesful Open
bpn9:
	cmp	SI,LdrGrp:NameOff	; IF (pathname contains a Path Sep.)
	mov	AX,ERROR_INVALID_NAME	  ; report an error
	ljne	 bpn_error		  ;   THEN abort on error

	mov	DS,LoadES		; (DS:SI) = point to module name
	mov	DI,offset LdrGrp:RenBuf + MAX_PATH_LENG + MAX_NAME_LEN + 5
	mov	LdrGrp:Path_nameDX,DI
	mov	LdrGrp:Path_nameDS,CS
	repnz	movsb			; Copy module name into buffer
	jmp	bpn11			;@@


bpn10:
	mov	dx,cs			;@@ point ds to code
	mov	ds,dx			;@@
	mov	si,offset filename	;@@ (DS:SI) = ptr to module name
	mov	cx,filenamesize 	;@@ (CX) = len module name

;@@ deleted for PTM 4040
;	mov	DS,mte_H		; Append module name to path.
;	mov	SI,DS:mte_resnamtbl_off
;	mov	CL,byte ptr [SI]	;  (CX) = len module name
;	xor	CH,CH
;	inc	SI			;  (DS:SI) = ptr_module name
;@@

	mov	DX,CS
	mov	ES,DX
	mov	DI,offset LdrGrp:RenBuf
	repnz	movsb			; Copy module name into buffer
	mov	AX,'E.'                 ; append '.exe.'
	stosw
	mov	AL,'X'
	stosw
	xor	AL,AL
	stosb

	mov	DS,DX			; Attempt to open the file
	mov	DX,offset LdrGrp:RenBuf

; verify the filename in 8.3 range
	push	si			;@@ save si
	mov	ah,1			;@@ wild char is invalid
	mov	si,dx			;@@ ds:si->filename
	call	name_check		;@@ check name in 8.3 range
	pop	si			;@@ restore si
	jnc	bpn10_2 		;@@ filename is legal
	jmp	LoadError		;@@ filename is illegal
bpn10_2:				;@@
	call	w_Open
	jc	bpn10_5

	jmp	bpnop			; Succesful Open
bpn10_5:
	mov	DS,mte_H		; Append module name to path.
	mov	SI,DS:mte_resnamtbl_off
	mov	CL,byte ptr [SI]	;  (CX) = len module name
	xor	CH,CH
	inc	SI			;  (DS:SI) = ptr_module name
	mov	DI,offset LdrGrp:RenBuf + MAX_PATH_LENG + MAX_NAME_LEN + 5
	mov	LdrGrp:Path_nameDX,DI
	mov	LdrGrp:Path_nameDS,CS
	mov	ES,LdrGrp:Path_nameDS
	repnz	movsb			; Copy module name into buffer

	mov	AX,'E.'                 ; append '.exe.'
	stosw
	mov	AL,'X'
	stosw
	xor	AL,AL
	stosb
bpn11:

;	(DS:RenBuf) = the path name buffer
;	(DX) = offset to the module name contained
;	in the buffer...
;	NOTE that the buffer has MAX_PATH_LENG + (MAX_NAME_LEN+5)*2
;	it is the last (MAX_NAME_LEN+5) that contains the
;	MODULENAME.EXE\0 string


;   Open using the PATH= variable in the environment segment
;	(ES) = environment segment selector...

	mov	ES,OldEnvSeg
	call	find_path		; find PATH in environment

	mov	Path_index,SI		; save current index of PATH
;	lDS	DX,DWORD PTR Path_namedx ;@@ REMOVED P3131- set up for open

	mov	ES,Path_nameds

bpn12:	      mov     di,offset LdrGrp:RenBuf ; start of segment

;	(ES:DI) -> beginning of path name buffer
;	(DS:SI) -> path name in the PATH= attribute in env seg
;	extract path name from the PATH= attribute in env seg
;	character by char... if a zero or ';' is encountered,
;	leave the loop..
;	ignore a blank space and continue to loop...
;	any other character is copied into path name buffer...
;	by the time we encounter a delimiter, we may have
;	a path name to use

; #DBCS#
	xor	cx,cx			; clear "last char DB" flag
bpn13:
	lodsb				; get next character from PATH
; #DBCS#				; test dbcs, bump, and store
	call	load_testdbcs_bst	; if ill formed DB char
	jnc	bpn13c			; then continue
	jnz	bpn13a			; if db char is OK, then continue
	jmp	LoadError		; else quit
bpn13a:
	inc	cx			; else set DB char flag
	jmp	short bpn13		; and loop

bpn13c:

	or	AL,AL			; end of PATH?
	jz	bpn14			;  Yes, may have one more string to go
	cmp	AL,";"                  ; Delimiter?
	je	bpn14			;  Yes, Try string we have so far
; #DBCS#
	xor	cx,cx			; clear "last char DB" flag

	cmp	AL," "                  ; Blank?
	je	bpn13			;  Yes, ignore
	stosb				; copy this one
	jmp	short bpn13		; go get next character

;	(DS) -> env seg
;	(SI) advanced to end of path name we copied in the loop above
;	(ES) -> path name buffer
;	(DI) = 0 if no path name was copied OR..
;	(DI) advanced after the current path name was copied from env seg
;	if no path name was copied into ES:DI, we have no more
;	tries for file open... report error

;	if some path name was copied, make sure that it terminates
;	with a slash

bpn14:
; #DBCS#
	or	cx,cx			; if last char DB
	jnz	bpn15			; then continue

	cmp	DI,offset LdrGrp:RenBuf ; nothing to do?
	je	bpn17			 ;  Yes, failed open
	cmp	BYTE PTR ES:[DI-1],"\"  ; need to add slash?
	je	bpn15			;  No, it's already there
	cmp	BYTE PTR ES:[DI-1],"/"  ; need to add slash?
	rep	movsb			    ; add name to current path
	mov	AL,"\"
	stosb				; add a slash

;	note that when we are here we have SI pointing at the
;	next path name in the PATH= attribute in env seg...
;	save (SI)
	mov	CL,NAME_SIZE+EXT_SIZE+1
bpn15:	mov	Path_index,SI		; save current index of PATH
	lDS	SI,DWORD PTR Path_namedx ; get filename to copy over

;	(DS:SI) -> modulename.exe\0 string
;	(ES) -> path name buffer
;	(DI) -> end of current path name string
;	by attaching modulename.exe\0 string to (ES:DI), we get
;	full file name (including the path)

	;mov	 CL,Path_namecount	;@REMOVED P3131
	mov	CL,NAME_SIZE+EXT_SIZE+2 ;@ADDED P3131 - amount to move
	xor	CH,CH
	repnz movsb			  ; add name to current path
	push	ES			; Set (DS:DX) to start of path
	pop	DS
	mov	DX,offset LdrGrp:RenBuf

;	(DS:DX) -> file name [which may have path name prefix]
;	pointing at modulename.exe\0 string if this is the first open
;	otherwise it points at complete file name[i.e., path name prefix
;	+ modulename.exe\0 string]

bpn16:	push	DX			; save path used for success
	push	DS
	push	BP
	call	w_Open
	pop	BP
	pop	DS
	pop	SI			; for success
	jnc	bpnop			; Found it

	mov	DS,OldEnvSeg		; get environment seg # again
	mov	ES,Path_nameds
	mov	SI,Path_index		; get current index of PATH

;	(ES) = path name buffer while (SI) = path index in
;	the PATH attribute in the env seg
;	path_index is an offset into the PATH= in env seg...
;	it points at the path name that is being used currently...
;	note that PATH= may have one or more path names separated by
;	delimiters...

	or	SI,SI			; PATH variable present?
	jz	bpn17			;  No, all done

	cmp	BYTE PTR DS:-1[SI],0	; Was that the end of path?
	jne	bpn12			;  No, keep trying

bpn17:					; set carry (again?)
	mov	AL,ERROR_FILE_NOT_FOUND ; Open or PATH error will give this
	jmp	short bpn_error 	 ; return the error

;	(DS:SI) -> modulename.exe\0

;bpn18:  mov	 ex_fh,AX		 ; save file handle
;	 mov	 SI,offset LdrGrp:RenBuf
;	 mov	 DI,offset LdrGrp:TempBuf
;	 mov	 BX,DI
;	 call	 Canonicalize		 ; expand (DS:SI)(path) into TempBuf
;
;	 jmp	 short bpnop1
bpnop:
	mov	ex_fh,AX
bpnop1:
	pop	DS
	pop	SI
	ret

bpn_error:
	jmp	LoadError

BuildPathName	ENDP

;*  w_Open
;	Worker_Open. Attempt to open the requested file in deny-write mode
;	(previously, compatibility mode).
;
;	ENTRY
;	(DS:DX) points to string to open
;
;	EXIT
;	    Carry Set
;		(AX) = error
;	    Carry ReSet
;		(BX) = contains handle to open file
;
w_Open	PROC	NEAR

	mov	AX,LdrGrp:DosVersion	; need to check to set 2.x access code
	cmp	AX,VERSION300		; IF pre 3.0 DosVersion
	jb	w_Open2x		; THEN use read access = 0
					; ELSE use read access = DENY_WRITE

	mov	AX, (OPEN_FILE shl 8) + DENY_WRITE  ; >= 3.0
	jmp	w_OpenDo
w_Open2x:
	mov	AX, (OPEN_FILE shl 8) + 0; <3.0
w_OpenDo:
	int	21h
	ret				; error recovery here (search path)
w_Open	ENDP



;**	find_path - Search the environment for PATH
;
;	ENTRY	(ES) = environment segment
;
;	EXIT	(DS:SI) = start of string after PATH=
;		(SI) = zero if no PATH=
;
;	USES	CX, DI, SI, ES, AX

find_path PROC NEAR
	mov	CX,ES
	jcxz	fnp2			; no environment seg means no path

	push	CS
	pop	DS
	xor	DI,DI			; start of environment

;	environment variable search loop
;	(DS:SI) -> name of variable we're looking for
;	(ES:DI) -> place we're looking in environment

fnp1:	mov	CX,5			; search for "PATH="
	mov	SI,offset LdrGrp:PATHSTR
	repe	cmpsb			; strcmp(ds:si, es:di)
	je	fnp3			; found the path
	dec	DI			; backup to failing character
	mov	CX,MAXENVsizeB		; "unlimited" scan (32k)
	xor	AL,AL
	repne	scasb			; find start of next env. var.
	cmp	byte ptr ES:[DI],AL	; end of environment?  (2 null bytes)
	jne	fnp1			;  no, check for path string

fnp2:	xor	SI,SI			; return error, no PATH
	return

;	we have found the environment variable holding the path name

fnp3:	mov	SI,DI
	push	ES
	pop	DS			; (ds:si) -> env. value

; verify the filename in 8.3 range
	mov	ah,3			;@@ wild char is invalid
	call	name_check		;@@ check name in 8.3 range
	jc	bpn_error		;@@ filename is illegal
	return
find_path ENDP


BREAK	<GetMovAddr - Get the entry point of a movable address>
;***	GetMovAddr - Get the entry point of a movable address
;
;	For a given ordinal number, this routine locates the
;	appropriate entry in the Entry Table of the module table entry.
;	It returns the 32 bit Selector:offset pair corresponding to
;	"movable" entry.  In the PCDOS environment, these entries aren't
;	really movable, but the loadtime fixups don't have direct segment
;	offset values,	just Ordinal indices.
;
;	ENTRY	(AX)	= ordinal number
;		(DS)	= selector for module table entry
;
;	EXIT
;		(DX:BX) = entry point address
;
;	WARNING!
;		If any errors are detected, this routine exits VIA
;		LoadError with an Return Code of ERROR_BAD_FORMAT
;
;	USES	AX, BX, DX, DS, SI, ES, DI, Flags

	PUBLIC	GetMovAddr
GetMovAddr	PROC	NEAR

	or	AX,AX			; IF (OrdNum == 0 )
	jz	gma5			;    THEN report an error

	dec	AX			; (AX) = OrdNum - 1
	mov	SI,DS:mte_entrytbl_off	; (DS:SI) -> export entry table

;	Scan the Entry_tbl to for the bundle in which the OrdNum belongs
;	WHILE	( (OrdNum - 1) >= 0) && (SI < End_of_Table )
;		OrdNum -= EntryBundle[SI].Entry_Cnt
gma1:
	xor	BX,BX
	mov	BL,DS:byte ptr [SI]	; (BX) = # entries in this bundle
	inc	SI
	or	BX,BX			; IF (End Of Table)
	jz	gma5			;    THEN report an error

	cmp	AX,BX			; IF ( AX < Cnt_Entries )
	jb	gma4			;   THEN entry is in this bundle

	sub	AX,BX			;   ELSE decrement AX by the number
	push	AX			;	 of entries in this bundle
; Unnecessary I think			;	 And Skip to the next bundle
;	xor	AX,AX
	lodsb				; (AL) = Curr. bundle's Seg. Indicator
	or	AL,AL			; IF ( bundle unused )	{AL = 0}
	jz	gma3			;     THEN skip to next the bundle

	inc	AL			; SWICH (bundle_type)
	mov	AX,SIZE ent_s		;	CASE fixed entry size
	jnz	gma2

	mov	AX,SIZE cte_s		;	CASE movable entry size
gma2:
	mul	BX			; (AX) = Cnt_Entries * Entry_Size
	add	SI,AX
gma3:
	pop	AX			; (DS:SI) -> next bundle
	jmp	short gma1

gma4:					; We have found the correct bundle.
	mov	BX,AX			; (BX) = OrdNum - 1
	lodsb				; (AL) = Curr. bundle's Seg. Indicator
	cmp	AL,0FFh 		; IF (Seg. Indic != FFh)
	jne	gma5			;     THEN it is a fixed bundle

	mov	AX,SIZE cte_s		; (AX) = (OrdNum - 1) * Entry_Size
	mul	BX			;     == Entry Index within Curr. Bundle
	add	SI,AX			; (DS:SI) -> thunk entry
	mov	DI,SI
	mov	BX,[DI].cte_off 	; (BX) = offset of entry point

	mov	AL,[DI].cte_seg 	; (AX) = Segment Entry Number
	call	num_to_ste
	mov	DX,[SI].ste_seghdl	; (DX) = Segment selector

	or	DX,DX			; IF ( Segment valid )
	jz	gma5			;     THEN return Bad_Format
gmax:
	ret

gma5:
	mov	AL,ERROR_BAD_FORMAT	; report an error
	jmp	LoadError

GetMovAddr	ENDP





BREAK	<PreloadSegAlloc - Allocate memory block for segment to be loaded>
;***	PreloadSegAlloc - Allocate memory block for segment to be loaded
;
; The current segment to be loaded is allocated a block of memory.  For a
; segment up to 64k, a single segment table entry is accessed.	For a
; segment > 64k (known as a "huge" segment) a consecutive series of segment
; table entries is accessed, all but the last representing a 64k chunk.  The
; last segment table entry describes the left over, or "residual" segment.
; A group of consecutive segment table entries is assumed to describe a huge
; segment, if the segment flags are the same, and all but the last entry is
; 64k.	It does not matter if this assumption is incorrect.
;
; Two passes of the segment table entries belonging to a huge segment are
; required, one to calculate the total size in bytes, and the other to
; dispense paragraph numbers.
;
; If any segment's file size is larger than the minimum allocation size, an
; error is reported.  If this is an automatic data segment the heap and stack
; size are added to the minimum allocation size and the resulting memory size
; is allocated.  Both the minimum allocation size and the segment handle are
; saved in the segment table entry.
;
; ENTRY 	(DS) = module table entry paragraph number
;		(AX) = starting segment table entry number
;		(SI) = ptr to seg table offset
;
; EXIT		(ES) = paragraph number of start of (huge) segment
;		(DX) = size of segment in paragraph
;
; USES		All except DS, BP
;
;
;

PreloadSegAlloc PROC NEAR

; Do some checks before we begin
	test	ds:mte_flags,LIBRARYMOD  ; The Stub Loader does not
	ljnz	PsaErrorBadFormat	 ; Library Modules

; Starting with the segment table entry number in AX, make a first pass
; over the segment table from this entry onwards, to locate the number
; of constituent segments of a huge segment.  Keep scanning ste's while
; the segment size is 64k (recorded as 0) and the flags are the same.  Include
; next ste in count if flags are the same, i.e. only the size is possibly
; different from 64k.  As we go, we validate and accumulate the segment
; sizes in bytes in cx,dx so that we can allocate the memory for a huge
; segment all in one go.

;PsaHugeStart:
	mov	ex_start_segno,ax	; save starting ste number
	call	Num_to_STE		; get starting ste offset
	mov	ax,ex_start_segno	; restore starting ste number
	inc	ax			; set default next ste number
	mov	ex_next_segno,ax	; for non-huge segment
	dec	ax			; go back to start ste number
	mov	di,[si].ste_flags	; get starting ste's flags
	xor	dx,dx			; initialize lsw total byte size to 0
	xor	cx,cx			; initialize msw total byte size to 0
	jmp	short PsaHugeCheck
PsaHugeNext:
	inc	cx			; add in 64k to total byte count
	inc	ax			; count this segment
	mov	ex_next_segno,ax	; save next ste number
	cmp	ax,ds:mte_segtbl_cnt	; are we at end of segment table ?
	lja	PsaFinish		; j if we are
	call	Num_to_STE		; get offset to ste in SI
	mov	ax,ex_next_segno	; restore next ste number
	cmp	di,[si].ste_flags	; are flags the same ?
	jne	PsaAddResidSize 	; @@ jump if not; do rounding/alloc mem
	inc	ax			; b783377 klee
	mov	ex_next_segno,ax
	dec	ax
PsaHugeCheck:
	mov	bx,[si].ste_minsiz	; get segment size
	or	bx,bx			; check for 0 (means 64k segment)
	jz	PsaHugeNext		; j if 64k segment

PsaHugeResid:
	cmp	bx,[si].ste_size	; Is the minimum size valid ?
	ljb	PsaErrorBadFormat	; J if not; bad ste format

; Check for automatic data segment.  If found, check for overflow

	cmp	ax,ds:mte_autods	; is it the autodata segment ?
	jne	PsaAddResidSize 	; j if not
	add	bx,ds:mte_heapsiz	; add in heap size
	jnc	ll001
	or	bx,bx
	ljnz	PsaErrorAutoOvflw	; j if > 64k
ll001:	add	bx,ds:mte_stacksiz	; add in stack size
	jnc	ll002
	or	bx,bx
	ljnz	PsaErrorAutoOvflw	; j if > 64k
ll002:	jz	psa1			; j if 64k

; If apilmr.obj has been linked in with the bind step (replacing the
; stub in api.lib which returns 0 in BX), then more memory (the maximum
; available) will be used.  Otherwise, we will use that computed above.

PsaCheckApiLmr:
	push	cx			; save msw of total byte count
	mov	cx,bx			; CX <- allocation amount
	call	api_memrep		; BX <- 0 or max avail up to 64k
	or	ax,ax			; if api_memrep returned an error
	ljnz	PsaErrorOutOfMem	; then return error
	cmp	cx,bx			; if CX < BX
	jb	PsaCheckApiLmr2 	; then use BX
	mov	bx,cx			; else use CX
PsaCheckApiLmr2:
	pop	cx			; restore msw of total byte count

; BX contains the size of the auto data segment.  We must preserve it for
; exec. It should be 200 bytes minimum for Stub Loader to function
	cmp	bx,200			;@@
	jae	psa1			;@@
	mov	bx,200			;@@
psa1:					;@@

	cmp	bx,0ffffh		;@@ is apilmr trying to say 64k
	jne	not64k			;@@
	add	bx,1			;@@

not64k: 				;@@
	mov	ex_dslen,bx		; save auto data segment size

; Add in size of residual segment, and perform round up to next paragraph,
; checking for physical memory address wraparound.

PsaAddResidSize:
	add	dx,bx			; add in size
	adc	cx,0			; propagate carry
	or	bx,bx			; check for 64k
	jnz	ll003			; j if not 64k
	inc	cx			; else, handle it
ll003:	add	dx,PARASIZEB - 1	; add in bytes per paragraph - 1
	adc	cx,0			; account for carry
	cmp	cx,0fh			; Wraparound ?
	lja	PsaErrorOutOfMem	; J if there is wraparound

; Convert total byte count held in CX,DX to paragraphs and place the result
; in BX.

	shr	dx,1			; convert to words
	shr	dx,1			; convert to doublewords
	shr	dx,1			; convert to half paragraphs
	shr	dx,1			; convert to paragraphs
	ror	cx,1			; Rotate the most significant
	ror	cx,1			; 4-bits of the byte count
	ror	cx,1			; so that they're in the most
	ror	cx,1			; sig. 4 bits of CX
	or	dx,cx			; DX = number of paragraphs
	mov	bx,dx			; BX = number of paragraphs

;  Now allocate all the memory we need

;PsaMemAlloc:
	mov	ah,ALLOCATE_BLOCK
	int	21h
	ljc	PsaErrorOutOfMem	; J if not enough memory
	mov	es,ax			; Save para # of allocated memory

;  Now make a second pass of the segment table entries, to dispense
;  segment starting paragraph numbers (also called handles in real mode).

	mov	bx,ax			; starting segment address
	mov	ax,ex_start_segno	; get starting ste #
PsaSegAlloc:
	push	ax			; save ste number
	call	Num_To_STE		; get ste offset in SI
	pop	ax			; restore ste number
	mov	[si].ste_seghdl,bx	; save it as handle in ste
	add	bx,1000h		; skip it on 64k (= 4k paras)
	inc	ax			; increment ste number
	cmp	ax,ex_next_segno	; are we at end of huge segment ?
	jb	PsaSegAlloc		; repeat if not

;  For the residual segment, zeroise area from ste_size to ste_minsize
;  On entry to here, es points at the residual segment

PsaSegZeroise:
	mov	cx,[si].ste_minsiz
	mov	di,[si].ste_size	; DI <- ste_size
	sub	cx,di			; CX <- (ste_minsiz - ste_size)
	xor	ax,ax
	shr	cx,1			; convert to words
	rep	stosw			; if count was even
	jnc	PsaFinish		; then continue
	stosb				; else store final byte
PsaFinish:
	ret				; return to LoadModule

PsaErrorAutoOvflw:
	mov	ax,ERROR_AUTODATASEG_EXCEEDS_64K
	jmp	LoadError

PsaErrorOutOfMem:
	mov	ax,ERROR_NOT_ENOUGH_MEMORY
	jmp	LoadError

PsaErrorBadFormat:
	mov	ax,ERROR_BAD_FORMAT
	jmp	LoadError

PreloadSegAlloc ENDP


;**	ex_lseek - lseek to file pointer and handle any errors
;
;	ENTRY	(CX:DX) = offset in file to seek to
;
;	EXIT	None
;
;	USES	All except BP
;
;	WARNING!
;		This routine does not do an explicit return.  It relies on
;		being followed immediately in memory by ex_lseekh.
;
	PUBLIC	ex_lseek
ex_lseek PROC NEAR

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

IFNDEF	loadslow

	test	cs:FastMode,FASTFLAG	; if NOT fast mode
	jz	ex50			; then continue

	; if buffer is empty OR seek is outside range of buffer
	; then proceed do normal seek
	; else do pseudo seek by moving the buffer pointer

	mov	ax,cx
	mov	bx,dx			; AX::BX <- new location
	sub	bx,cs:BigBufLocLo
	sbb	ax,cs:BigBufLocHi	; if new loc preceeds current loc
	jc	ex10			; then proceed with seek
	or	ax,ax			; else if move distance > 64k bytes
	jnz	ex10			; then proceed with seek
	cmp	bx,cs:BigBufCnt 	; else if distance is smaller than count
	jb	ex20			; then do pseudo seek
ex10:					; else do normal seek
	mov	bx,ex_fh		; BX <- file handle
	xor	al,al			; seek rel to beginning of file ...
	mov	ah,LSEEK
	int	21h
	jc	exer
	mov	cs:BigBufLocLo,ax
	mov	cs:BigBufLocHi,dx	; store new location
	mov	ds,cs:BigBufSeg
	xor	dx,dx			; DS:DX <- address of start of buffer
	mov	cs:BigBufPtr,dx 	; BufPtr <- 0
	mov	cx,cs:BigBufSiz 	; CX <- count of bytes to read
	mov	ah,READ
	int	21h			; if error return from read
	jc	exer			; then return error
	mov	cs:BigBufCnt,ax 	; BufCnt <- number of bytes read
	ret
ex20:
	mov	cs:BigBufPtr,bx 	; move the pointer as requested
	ret
exer:
	jmp	LoadError
ex50:

ENDIF

	mov	BX,ex_fh		; from the handle
;	call	ex_lseekh
;	ret
ex_lseek ENDP


;**	ex_lseekh - lseek to file pointer and handle any errors
;
;	ENTRY	(CX:DX) = offset in file to seek to
;		(BX) = file handle
;
;	EXIT	None
;
;	USES	All except BP

	PUBLIC	ex_lseekh
ex_lseekh PROC NEAR
	xor	AL,AL			; seek rel to beginning of file ...
					; ... i.e., absolute position
	mov	AH,LSEEK
	INT	21h
	jc	exr_error

;	return new position in CX:DX instead of DX:AX

	mov	CX,DX
	mov	DX,AX
	ret
ex_lseekh ENDP



;**	ex_read - read file and handle any errors
;
;	ENTRY	(CX) = number of bytes to read
;		(DS:DX) = transfer address
;
;	EXIT	None
;
;	USES	All except BP
;
;	WARNING!
;		This routine does not do an explicit return.  It relies on
;		being followed immediately in memory by ex_readh.
;
	PUBLIC ex_read
ex_read PROC NEAR

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

IFNDEF	loadslow

	test	cs:FastMode,FASTFLAG	; if NOT fast mode
	jz	er50			; then continue

	mov	ax,ds
	mov	es,ax
	mov	di,dx			; ES:DI <- destination of read
	mov	ds,cs:BigBufSeg
er10:
	mov	si,cs:BigBufPtr 	; DS:SI <- source of data
	mov	ax,cs:BigBufCnt
	sub	ax,si			; AX <- number of bytes left in buffer
	cmp	cx,ax			; if bytes requested <= bytes left
	jbe	er20			; then move is OK
					; else move what we can, then read
	sub	cx,ax			; AX <- count for next move
	xchg	cx,ax			; CX <- count for this move

	shr	cx,1			; prepare to move cx bytes as words
	rep	movsw			; exec move ... if length was even
	jnc	er11			; then continue
	movsb				; else move final byte
er11:
	xchg	ax,cx			; CX <- count for next move
	push	cx
	xor	dx,dx			; DS:DX <- address of start of buffer
	mov	cs:BigBufPtr,dx 	; BufPtr <- 0
	mov	cx,cs:BigBufSiz 	; CX <- count to read in bytes
	mov	bx,ex_fh
	mov	ah,READ
	int	21h			; if error return from read
	pop	cx
	jc	er16			; then return error
	mov	cs:BigBufCnt,ax 	; BufCnt <- number of bytes read
	or	ax,ax			; if read more than zero bytes
	jnz	er10			; then continue
	mov	ax,1234h		; else report error
er16:
	jmp	LoadError
er20:
	shr	cx,1			; prepare to move cx bytes as words
	rep	movsw			; exec move ... if length was even
	jnc	er21			; then continue
	movsb				; else move final byte
er21:
	mov	cs:BigBufPtr,si
er99:
	ret
er50:

ENDIF
	mov	BX,ex_fh		; from the handle
;	call	ex_readh
;	ret
ex_read ENDP


;**	ex_readh - read file and handle any errors
;
;	ENTRY	(CX) = number of bytes to read
;		(DS:DX) = transfer address
;		(BX) = file handle
;
;	EXIT	None
;
;	USES	All except BP

	PUBLIC ex_readh
ex_readh PROC NEAR
	mov	AH,READ
	INT	21h
	jc	exr_error
	ret

exr_error:
	jmp	LoadError
ex_readh ENDP


;----------------------------------------------------------------------------
; BEGIN - conditionally assembled timer code
;----------------------------------------------------------------------------

IFDEF	debug_statistics


;**	get_time - read clock tick and count and store into given buffer
;
;	Use int 1Ah and direct conversation with 8253 timer chip
;	to gain access to a high resolution timer for profiling code.
;
;	ENTRY	(AX) = address of 6 bytes to receive the following form;
;
;			2 bytes - tick count high (from int 1Ah)
;			2 bytes - tick count low (from int 1Ah)
;			2 bytes - high res tick count (read direct from 8243)
;
;		(see also "print_time" below)
;
;	EXIT	None
;
;	USES	AX and flags

	public	get_time

get_time proc near

	push	bx
	push	cx
	push	dx
	mov	bx,ax			; BX <- time address
	mov	ah,0			; select "get timer count" function
	int	1ah			; get timer count
	mov	word ptr cs:[bx+0],cx	; store high portion
	mov	word ptr cs:[bx+2],dx	; store low portion
	mov	al,0			; prepare 8253 to send count
	cli
	out	43h,al
	jmp	$+2			; allow time for 8253 to stablize
	in	al,40h			; read low byte
	mov	ah,al
	jmp	$+2
	in	al,40h			; read high byte
	sti
	xchg	ah,al
	xor	ax,0ffffh		; swith from count down to count up
	inc	ax
	mov	word ptr cs:[bx+4],ax	; store in correct order
	pop	dx
	pop	cx
	pop	bx
	ret

get_time endp


;**	print_time - print time indicated in AX
;
;	DESC	prints the time
;
;	ENTRY	(AX) = pointer to time in the following form:
;
;			2 bytes - tick count high (from int 1Ah)
;			2 bytes - tick count low (from int 1Ah)
;			2 bytes - high res tick count (read direct from 8243)
;			2 bytes - counter id in ascii (e.g. "07")
;
;		For example, define buffers of following form within the
;		local code segment (e.g. "cs:" will be used to access):
;
;			TimeA1		db	0,0,0,0,0,0,"A1"
;			TimeA2		db	0,0,0,0,0,0,"A2"
;
;		Then insert "gtime TimeA1" and "gtime TimeA2" at
;		a two places in the code within which you would
;		like to know the amount of time lapsed.  (The time
;		required by the timer is about 70 (hex) ticks, but
;		to check this, just insert two successive gtime's
;		and check the difference.)  Someplace outside of
;		the timing loop, insert "ptime TimeA1" and "ptime
;		TimeA2".
;
;	EXIT	None
;
;	USES	AX and flags

	public	print_time

print_time	proc	near

	push	bx
	push	cx
	push	dx
	push	ds

	mov	bx,ax
	mov	ax,word ptr cs:[bx+6]
	mov	word ptr cs:prtm2,ax		; store time identifier byte

	jmp	short prtm5
prtm1:
	db	"count "
prtm2:
	db	"id "                           ; buffer for identifier
prtm3:
	db	'xxxx:xxxx:xxxx',0dh,0ah,'$'    ; buffer for output string
prtm5:
	mov	cl,byte ptr cs:[bx+1]		; convert counts to ascii
	call	convert 			; and store in local buffer
	mov	word ptr cs:prtm3+0,ax

	mov	cl,byte ptr cs:[bx+0]
	call	convert
	mov	word ptr cs:prtm3+2,ax

	mov	cl,byte ptr cs:[bx+3]
	call	convert
	mov	word ptr cs:prtm3+5,ax

	mov	cl,byte ptr cs:[bx+2]
	call	convert
	mov	word ptr cs:prtm3+7,ax

	mov	cl,byte ptr cs:[bx+5]
	call	convert
	mov	word ptr cs:prtm3+10,ax

	mov	cl,byte ptr cs:[bx+4]
	call	convert
	mov	word ptr cs:prtm3+12,ax

	push	cs
	pop	ds
	mov	dx, offset LDRGRP:prtm1
	mov	ah,9h				; print resulting string
	int	21h

	pop	ds
	pop	dx
	pop	cx
	pop	bx
	ret

print_time	endp


;**	print_clock - print current value of the clock
;
;	Print the current time in form hours:minutes:seconds.hundredths.
;
;	ENTRY	None
;
;	EXIT	None
;
;	USES	flags

	public	print_clock

print_clock	proc	near

	push	ax
	push	bx
	push	cx
	push	dx
	push	ds

	mov	ah,2ch				; get time
	int	21h
	jmp	short pclo1
pclo2:
	db	"clock "
pclo3:
	db	'xx:xx:xx.xx',0dh,0ah,'$'
pclo1:
	xchg	ch,cl				; CL <- hours
	call	convert 			; convert hours
	mov	word ptr cs:pclo3+0,ax

	xchg	ch,cl				; CL <- minutes
	call	convert 			; convert minutes
	mov	word ptr cs:pclo3+3,ax

	mov	cl,dh				; CL <- seconds
	call	convert 			; convert seconds
	mov	word ptr cs:pclo3+6,ax

	mov	cl,dl				; CL <- hundredths
	call	convert 			; convert hundredths
	mov	word ptr cs:pclo3+9,ax

	push	cs
	pop	ds
	mov	dx, offset LDRGRP:pclo2
	mov	ah,9h				; print string
	int	21h

	pop	ds
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret

print_clock	endp


;**	convert - convert given byte to ascii in hex format
;
;	ENTRY	(CL) = byte intended for conversion
;
;	EXIT	(AX) = ascii form in hex format of given byte
;
;	USES	AX and flags
;
;	NOTE	AH <- low nibble and AL <- high nibble so that
;		AX may be easily stored directly into a printable
;		buffer without having to reorder the bytes.

convert proc	near

	mov	al,cl
	mov	ah,cl
	mov	cl,'A' - ':'            ; CL <- diff for hex chars 'A' - 'F'
	mov	ch,':'                  ; CH <- comparison char
	and	ah,0fh			; AH <- low nibble of conversion byte
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	and	al,0fh			; AL <- high nibble of conversion byte
	add	ax,3030h		; convert to ascii range
	cmp	ah,ch			; if AH is between '1' and '9'
	jb	conv1			; then continue
	add	ah,cl			; else change to 'A' - 'F' range
conv1:
	cmp	al,ch			; if AL is between '1' and '9'
	jb	conv2			; then continue
	add	al,cl			; else change to 'A' - 'F' range
conv2:
	ret

convert endp

ENDIF

;----------------------------------------------------------------------------
; END - conditionally assembled timer code
;----------------------------------------------------------------------------


Loader		ENDS

		END LoadInit

