windows-nt/Source/XPSP1/NT/base/mvdm/wow16/kernel31/ldheader.asm
2020-09-26 16:20:57 +08:00

881 lines
29 KiB
NASM

TITLE LDHEADER - Load Exe Header procedure
.xlist
include gpfix.inc
include kernel.inc
include newexe.inc
.list
externA __AHINCR
externFP IGlobalAlloc
externFP IGlobalFree
externFP FarSetOwner
externFP Int21Handler
externFP FarMyUpper
externFP IsBadStringPtr
externFP _hread
DataBegin
externB fBooting
externB szBozo
externW winVer
externD pSErrProc
DataEnd
externFP IGlobalLock
externFP IGlobalUnLock
sBegin NRESCODE
assumes CS,NRESCODE
externNP NResGetPureName
;-----------------------------------------------------------------------;
; LoadExeHeader ;
; ;
; Routine to read an EXE header and check for a new format EXE file. ;
; Returns NULL if not a new format EXE. Otherwise reads the resident ;
; portion of the new EXE header into allocated storage and returns ;
; the segment address of the new EXE header. ;
; ;
; Arguments: ;
; parmW fh ;
; parmW isfh ;
; parmD pfilename ;
; ;
; Returns: ;
; AX = segment of exe header ;
; DL = ne_exetyp ;
; DH = ne_flagsothers ;
; ;
; Error Returns: ;
;LME_MEM = 0 ; Out of memory ;
;LME_VERS = 10 ; Wrong windows version ;
;LME_INVEXE = 11 ; Invalid exe ;
;LME_OS2 = 12 ; OS/2 app ;
;LME_DOS4 = 13 ; DOS 4 app ;
;LME_EXETYPE = 14 ; unknown exe type ;
;LME_COMP = 19 ; Compressed EXE file ;
;LME_PE = 21 ; Portable EXE ;
; ;
; Registers Preserved: ;
; DI,SI ;
; ;
; Registers Destroyed: ;
; AX,BX,CX,DS,ES ;
; Calls: ;
; ;
; History: ;
; ;
; Thu Mar 19, 1987 08:35:32p -by- David N. Weise [davidw] ;
; Added this nifty comment block. ;
;-----------------------------------------------------------------------;
cProc LoadExeHeader,<PUBLIC,FAR>,<si,di>
parmW fh
parmW isfh
parmD pfilename
localW pnewexe
localW exetype
localW nchars
localW pseg
localW psegsrc
localW hBlock
localW pBlock
localW NEFileOffset
localD PreloadLen
localW NEBase
localW exeflags
localW expver
localW saveSP
localB fast_fail ; 1 if we can't use fastload block
localV hdrbuf,<SIZE EXE_HDR>
.errnz SIZE EXE_HDR - SIZE NEW_EXE
cBegin
xor ax,ax
mov pseg,ax
mov hBlock, ax
mov fast_fail, al
cmp SEG_pfilename,ax
je re0
IF KDEBUG ; LoadExeHeader is internal
cCall IsBadStringPtr, <pfilename, 128> ; so we assume file pointer is OK
or ax, ax
jnz refailj
ENDIF
lds si,pfilename
mov al,ds:[si].opLen
inc ax ; include null byte
re0:
mov nchars,ax
mov bx,fh ; No, seek backwards over what
cmp isfh,bx
je refile
mov ds,bx
xor si,si
jmp remem
refile: ; Here to load from a file
push ss ; DS:SI points to I/O buffer
pop ds
lea si,hdrbuf ; Read beginning of file
mov dx,si
mov cx,SIZE EXE_HDR
mov bx,fh
mov ah,3Fh
DOSFCALL
jnc @F
refailj:
jmps refail
@@:
cmp ax,cx ; Old EXE file, look for offset
jb refailj ; to new exe header
cmp ds:[si].e_magic,EMAGIC ; Check for old exe file
je @F
cmp ds:[si], 'ZS' ; Is it compressed?
jne refail
cmp ds:[si][2],'DD'
jne refail
cmp ds:[si][4],0F088h
jne refail
cmp ds:[si][6],03327h
jne refail
mov ax, LME_COMP ; Compressed EXE
jmp reexit
@@:
mov ax,ds:[si].e_lfanew.hi
mov dx,ds:[si].e_lfanew.lo
mov pnewexe,dx ; To check for bound OS/2 apps.
or ax,dx
jz refail ; Fail if not there.
mov cx,ds:[si].e_lfanew.hi
mov dx,ds:[si].e_lfanew.lo
mov bx, fh
mov ax,4200h
DOSFCALL
jc refail
mov cx,SIZE NEW_EXE
mov dx,si
mov ah,3Fh
DOSFCALL
jc refail
cmp ax,cx
jne refail
cmp ds:[si].ne_magic,NEMAGIC ; Did we get a valid new EXE header?
je remem
cmp ds:[si].ne_magic,PEMAGIC ; Did we find a Portable EXE (WIN32)
jne refail
mov ax, LME_PE
jmps rej
refail:
mov ax, LME_INVEXE ; invalid NEW EXE file format
rej: jmp reexit
remem:
mov psegsrc,bx ; bx has either fh or seg address
mov di,ds:[si].ne_enttab ; Compute size of resident new header
add di,ds:[si].ne_cbenttab
add di, 6 ; null entry space (bug #8414)
mov cx, ds:[si].ne_cbenttab
mov bx, ds:[si].ne_cmovent
shl bx, 1
sub cx, bx
shl bx, 1
sub cx, bx ; Number of bytes not moveable entries
shl cx, 1 ; Allow triple these bytes
add di, cx
mov cx,ds:[si].ne_cseg ; + 3 * #segments
; Reserve space for ns_handle field in segment table
shl cx,1
add di,cx
.errnz 10 - SIZE NEW_SEG1
; Reserve space for file info block at end
add di,nchars ; + size of file info block
xor ax,ax ; Allocate a fixed block for header
mov bx,GA_ZEROINIT or GA_MOVEABLE
cCall IGlobalAlloc,<bx,ax,di> ; that isn't code or data.
or ax,ax
jnz @F
jmps badformat
@@:
push ax
cCall IGlobalLock,<ax>
pop ax
push dx
cCall IGlobalUnlock,<ax>
pop ax
sub di,nchars
mov pseg,ax
mov es,ax ; ES:0 -> new header location
cld ; DS:SI -> old header
mov bx,psegsrc
cmp isfh,bx ; Is header in memory?
jne remem1 ; Yes, continue
mov ax,ds:[si].ne_enttab ; No, read into high end
add ax,ds:[si].ne_cbenttab ; of allocated block
sub di,ax
mov cx,SIZE NEW_EXE ; Copy part read so far
sub ax,cx
rep movsb
mov cx,ax
smov ds,es ; Read rest of header from file
mov dx,di
mov ah,3fh
DOSFCALL
mov bx,ax
jc refail1
lea si,[di-SIZE NEW_EXE] ; DS:SI -> old header
cmp bx,cx
je remem1
badformat:
mov ax, LME_INVEXE ; don't change flags
refail1: ; Here if error reading header
push ax
SetKernelDSNRes ; DS may be = pseg, prevent
cCall IGlobalFree,<pseg> ; GP faults in pmode.
pop ax
jmp reexit
remem1:
UnsetKernelDS
test ds:[si].ne_flags,NEIERR ; Errors in EXE image?
jnz badformat ; Yes, fail
cmp ds:[si].ne_ver,4 ; No, built by LINK4 or above?
jl badformat ; No, error
mov bx,ds:[si].ne_flags ; Make local copies of ne_flags &
and bl,NOT NEPROT ; ne_expver which can be modified
mov exeflags,bx ; (can't change ROM exe headers).
mov bx,ds:[si].ne_expver
mov expver,bx
mov bx,word ptr ds:[si].ne_exetyp ; get exetyp and flagsothers
mov exetype,bx
cmp bl,NE_UNKNOWN
jz windows_exe
cmp bl,NE_WINDOWS ; is it a Windows exe?
jz windows_exe
mov ax,LME_OS2
cmp bl,NE_OS2 ; is it an OS|2 exe?
jnz not_os2
test bh,NEINPROT ; can it be run under Windows?
jz @F
and exeflags,NOT NEAPPLOADER
or exeflags,NEPROT
mov expver,0300h
jmps windows_exe
@@:
cmp pnewexe,0800h ; is it a bound
jb refail1
jmp badformat
not_os2:
inc ax ; AX = 13 - LME_DOS4
cmp bl,NE_DOS4 ; is it a DOS 4 exe?
jz refail1
inc ax ; AX = 14 - LME_EXETYPE
jmp refail1
mgxlib DB 'MGXLIB'
windows_exe:
mov NEBase, si ; Offset of Source header
xor di,di ; ES:DI -> new header location
mov cx,SIZE NEW_EXE ; Copy fixed portion of header
cld
rep movsb
mov ax,exeflags
if ROM
and al,NOT NEMODINROM ; Assume module not in ROM
endif
mov es:[ne_flags],ax
mov ax,expver
mov es:[ne_expver],ax
mov si, NEBase
add si, es:[ne_segtab] ; Real location of segment table
mov cx,es:[ne_cseg] ; Copy segment table, adding
mov es:[ne_segtab],di
jcxz recopysegx
recopyseg:
if ROM
; If this is a module from ROM, the ROM ns_sector field contains the selector
; pointing to the ROM segment--this will become the RAM ns_handle value.
.errnz ns_sector
mov dx,ds:[si].ns_flags ; do while si->start of seg info
lodsw ; ns_sector
xor bx,bx ; ns_handle if not in ROM
test dh,(NSINROM SHR 8)
jz store_sector
mov bx,ax ; will be ns_handle
store_sector:
stosw ; ns_sector
else
movsw ; ns_sector
endif
movsw ; ns_cbseg
lodsw ; ns_flags
.errnz 4 - ns_flags
if ROM
mov dl,al
and dl,NSLOADED
and ax,not (NS286DOS XOR (NSGETHIGH OR NSINROM))
else
and ax,not (NS286DOS XOR NSGETHIGH) ; Clear 286DOS bits
endif
; record in the segment flags if this module is a process, this is for EMS
test ax,NSTYPE ; NSCODE
jnz not_code
or ax,NSWINCODE
not_code:
or ax,NSNOTP
test es:[ne_flags],NSNOTP
jnz not_a_process
xor ax,NSNOTP
or ax,NSMOVE
not_a_process:
if ROM
test ah,(NSINROM SHR 8) ; if sector is in ROM, preserve
jz @f ; NSLOADED flag from ROM builder
or al,dl
@@:
endif
stosw ; ns_flags
movsw ; ns_minalloc
.errnz 8 - SIZE NEW_SEG
if ROM
mov ax,bx ; bx set to selector or 0 above
else
xor ax,ax
endif
stosw ; one word for ns_handle field
.errnz 10 - SIZE NEW_SEG1
loop recopyseg
recopysegx:
test es:[ne_flagsothers], NEGANGLOAD
jz no_gang_loadj
mov bx, fh
cmp bx, isfh
jne no_gang_loadj
mov ax, es:[ne_gang_start]
or ax, ax
jz no_gang_loadj
mov NEFileOffset, ax ; file offset of gang load area
mov ax, es:[ne_gang_length]
or ax, ax
jz no_gang_loadj
mov cx, es:[ne_align]
xor dx, dx
gl_len: ; find length of Gang Load area
shl ax, 1
adc dx, dx
loop gl_len
cmp dx, 10h ; Greater than 1Mb, forget it!!
jb alloc_it ; PS: NEVER go bigger than 1Mb
no_gang_loadj:
jmp no_gang_load ; since LongPtrAdd is limited...
alloc_it:
mov word ptr PreloadLen[0], ax
mov word ptr PreloadLen[2], dx
mov ch, GA_DISCARDABLE
mov cl, GA_MOVEABLE+GA_NODISCARD+GA_NOCOMPACT
push es
cCall IGlobalAlloc,<cx,dx,ax> ; Allocate this much memory
pop es
or ax, ax
jz no_gang_loadj
mov hBlock, ax ; Have memory to read file into
push es
cCall IGlobalLock,<ax>
pop es
mov pBlock, dx
mov dx, NEFileOffset
mov cx, es:[ne_align]
xor bx, bx
gl_pos: ; find pos of Gang Load start
shl dx, 1
adc bx, bx
loop gl_pos
mov cx, bx
mov bx, fh
mov ax,4200h
DOSFCALL ; Seek to new exe header
jc refailgang
mov ax, pBlock
xor bx, bx
farptr memadr,ax,bx
cCall _hread, <fh, memadr, PreloadLen>
cmp dx, word ptr PreloadLen[2]
jnz refailgang
cmp ax, word ptr PreloadLen[0]
jz no_gang_load ; We're OK now
; push ds
; xor dx, dx
; mov ax, pBlock
; push si
; push di
; mov si, word ptr PreloadLen[2]
; mov di, word ptr PreloadLen[0]
;read_file:
; mov cx, 08000h ; Must be factor 64k DON'T CHANGE THIS
; or si, si
; jnz big_read
; cmp cx, di
; jbe big_read
; mov cx, di ; all that's left
; jcxz done_read ; Nothing left, quit.
;big_read:
; mov ds, ax
; mov ah, 3Fh
; DOSFCALL ; Read chunk from file
; jc refailgang
; cmp ax, cx ; All we asked for?
; jne refailgang ; no, file corrupted
; sub di, cx
; sbb si, 0
; mov ax, ds
; add dx, cx ; On to next block
; jnc read_file
; add ax, __AHINCR
; jmps read_file
;
refailgang:
; pop di
; pop si
; pop ds
cCall IGlobalUnlock,<hBlock>
cCall IGlobalFree,<hBlock>
mov hBlock, 0
jmps no_gang_load
BadExeHeader: ; GP fault handler!!!
mov sp, saveSP
; fix_fault_stack
jmp refail1 ; corrupt exe header (or our bug)
;done_read:
; pop di
; pop si
; pop ds
no_gang_load:
mov saveSP, sp
beg_fault_trap BadExeHeader
mov cx,es:[ne_restab] ; Copy resource table
sub cx,es:[ne_rsrctab]
mov si, NEBase ; Get correct source address
add si, es:[ne_rsrctab]
mov es:[ne_rsrctab],di
rep movsb
rerestab:
mov cx,es:[ne_modtab] ; Copy resident name table
sub cx,es:[ne_restab]
mov es:[ne_restab],di
rep movsb
push di
mov di, es:[ne_restab] ; Make the module name Upper Case
xor ch, ch
mov cl, es:[di]
inc di
uppercaseit:
mov al, es:[di]
call farMyUpper
stosb
loop uppercaseit
pop di
mov cx,es:[ne_imptab] ; Copy module xref table
sub cx,es:[ne_modtab]
mov es:[ne_modtab],di
rep movsb
mov es:[ne_psegrefbytes],di ; Insert segment reference byte table
mov es:[ne_pretthunks],di ; Setup return thunks
mov cx,es:[ne_enttab] ; Copy imported name table
sub cx,es:[ne_imptab]
mov es:[ne_imptab],di
jcxz reenttab
rep movsb
reenttab:
mov es:[ne_enttab],di
; Scan current entry table
xor ax, ax ; First entry in block
mov bx, di ; Pointer to info for this block
stosw ; Starts at 0
stosw ; Ends at 0
stosw ; And is not even here!
copy_next_block:
lodsw ; Get # entries and type
xor cx, cx
mov cl, al
jcxz copy_ent_done
mov al, ah
cmp al, ENT_UNUSED
jne copy_used_block
mov ax, es:[bx+2] ; Last entry in current block
cmp ax, es:[bx] ; No current block?
jne end_used_block
add es:[bx], cx
add es:[bx+2], cx
jmps copy_next_block
end_used_block:
mov es:[bx+4], di ; Pointer to next block
mov bx, di
add ax, cx ; Skip unused entries
stosw ; First in new block
stosw ; Last in new block
xor ax, ax
stosw ; End of list
jmps copy_next_block
copy_used_block:
add es:[bx+2], cx ; Add entries in this block
cmp al, ENT_MOVEABLE
je copy_moveable_block
; absolutes end up here as well
copy_fixed_block:
stosb ; Segno
movsb ; Flag byte
stosb ; segno again to match structure
movsw ; Offset
loop copy_fixed_block
jmps copy_next_block
copy_moveable_block:
stosb ; ENT_MOVEABLE
movsb ; Flag byte
add si, 2 ; Toss int 3Fh
movsb ; Copy segment #
movsw ; and offset
loop copy_moveable_block
jmps copy_next_block
copy_ent_done:
xor bx,bx
cmp es:[bx].ne_ver,5 ; Produced by version 5.0 LINK4
jae remem2a ; or above?
mov es:[bx].ne_expver,bx ; No, clear uninitialized fields
mov es:[bx].ne_swaparea,bx
; TEMPORARY BEGIN
push ax
push cx
push di
push si
mov si,es:[bx].ne_rsrctab
cmp si,es:[bx].ne_restab
jz prdone
mov di,es:[si].rs_align
add si,SIZE new_rsrc
prtype:
cmp es:[si].rt_id,0
je prdone
mov cx,es:[si].rt_nres
add si,SIZE rsrc_typeinfo
prname:
push cx
mov ax,es:[si].rn_flags
test ah,0F0h ; Is old discard field set?
jz @F
or ax,RNDISCARD ; Yes, convert to bit
@@:
and ax,not RNUNUSED ; Clear unused bits in 4.0 LINK files
mov es:[si].rn_flags,ax
pop cx
add si,SIZE rsrc_nameinfo
loop prname
jmp prtype
prdone: pop si
pop di
pop cx
pop ax
; TEMPORARY END
FixFlags: ; label for debugging
public FixFlags, leh_slow
public leh_code, leh_patchnext, leh_code_fixed, leh_patchdone, leh_data
remem2a: ; (bx == 0)
mov es:[bx].ne_usage,bx
mov es:[bx].ne_pnextexe,bx
mov es:[bx].ne_pfileinfo,bx
cmp es:[bx].ne_align,bx
jne @F
mov es:[bx].ne_align,NSALIGN
@@:
mov cx,nchars
jcxz @F
mov es:[bx].ne_pfileinfo,di
lds si,pfilename
rep movsb
@@: ; Save pointer to seginfo record
mov bx,es:[bx].ne_autodata ; of automatic data segment
or bx,bx
jz @F
dec bx
shl bx,1
mov cx,bx
shl bx,1
shl bx,1
add bx,cx
.errnz 10 - SIZE NEW_SEG1
add bx,es:[ne_segtab]
@@:
mov es:[ne_pautodata],bx
SetKernelDSNRes
; Scan seg table, marking nonautomatic DATA segments fixed, preload
mov ax,es:[ne_expver] ; Default expected version to
or ax,ax
jnz @F
mov ax,201h
mov es:[ne_expver],ax ; 2.01
@@:
cmp ax,winVer
jbe @F
cCall IGlobalFree,<pseg>
mov ax, LME_VERS
jmp reexit
@@:
mov bx,es:[ne_segtab]
xor cx,cx
sub bx,SIZE NEW_SEG1
jmps leh_patchnext
leh_test_preload:
test byte ptr es:[bx].ns_flags, NSPRELOAD
jnz leh_patchnext
or byte ptr es:[bx].ns_flags, NSPRELOAD
cmp es:[bx].ns_sector, 0 ; don't whine about empty segments
je leh_patchnext
krDebugOut DEB_WARN, "Segment #CX of %ES0 must be preload"
mov fast_fail, 1
leh_patchnext:
add bx,SIZE NEW_SEG1
inc cx
cmp cx,es:[ne_cseg]
ja leh_patchdone
test byte ptr es:[bx].ns_flags,NSDATA ; Is it a code segment?
jz leh_code ; Yes, next segment
.errnz NSCODE
leh_data: ; Data must be non-discardable, preload
if KDEBUG
test es:[bx].ns_flags, NSDISCARD
jz @F
krDebugOut DEB_WARN, "Data Segment #CX of %ES0 can't be discardable"
@@:
endif
and es:[bx].ns_flags,not NSDISCARD ; Data segments not discardable
jmps leh_test_preload
leh_code:
test byte ptr es:[bx].ns_flags,NSMOVE; Moveable code?
jz leh_code_fixed
; moveable code must be discardable, or must be preload
if KDEBUG
test es:[ne_flags],NENOTP ; for 3.0 libraries can't have
jz @F ; moveable only code
cmp fBooting,0 ; If not booting
jne @F
test es:[bx].ns_flags,NSDISCARD
jnz @F
krDebugOut DEB_WARN, "Segment #CX of %ES0 was discardable under Win 3.0"
@@:
endif
test es:[bx].ns_flags,NSDISCARD ; Is it discardable?
jnz leh_patchnext
jmp leh_test_preload
leh_code_fixed: ; fixed code must be preload
cmp fBooting,0 ; If not booting
jne leh_patchnext
jmp leh_test_preload
leh_patchdone:
mov bx,word ptr es:[ne_csip+2] ; Is there a start segment?
or bx,bx
jz @F ; No, continue
dec bx
shl bx,1
mov si,bx
shl si,1
shl si,1
add si,bx
.errnz 10 - SIZE NEW_SEG1
add si,es:[ne_segtab] ; Mark start segment as preload
if kdebug
test byte ptr es:[si].ns_flags,NSPRELOAD
jnz scs_pre
krDebugOut DEB_WARN, "Starting Code Segment of %ES0 must be preload"
mov fast_fail, 1
scs_pre:
endif
or byte ptr es:[si].ns_flags,NSPRELOAD
cmp es:[ne_autodata],0 ; Is there a data segment?
je @F
or es:[si].ns_flags,NSUSESDATA ; Yes, then it needs it
mov si,es:[ne_pautodata]
if kdebug
test byte ptr es:[si].ns_flags,NSPRELOAD
jnz sds_pre
cmp es:[bx].ns_sector, 0 ; don't whine about empty segments
je sds_pre
krDebugOut DEB_WARN, "Default Data Segment of %ES0 must be preload"
mov fast_fail, 1
sds_pre:
endif
or byte ptr es:[si].ns_flags,NSPRELOAD ; Mark DS as preload
@@:
test es:[ne_flags],NENOTP ; No stack if not a process
jnz @F
cmp es:[ne_stack],4096+1024
jae @F
mov es:[ne_stack],4096+1024 ; 4k stack is not enough (raor)
@@:
mov cx, es:[ne_heap] ; If the module wants a heap
jcxz leh_heapadjdone ; make sure it's big enough
mov ax, 800h ; ; Environment variables have
cmp cx, ax ; grown, so we need more heap
jae leh_heapadjdone ; space for apps so we use 800h
mov dx, ax
test es:[ne_flags],NENOTP
jnz @F
add dx, es:[ne_stack]
jc leh_heapadjmin
@@:
mov bx,es:[ne_autodata] ; set if no autodata segment
or bx,bx ; we have to do this here
jz leh_heapadjset ; because for certain dlls
dec bx ; pautodata is not initialized
shl bx,1
mov cx,bx
shl bx,1
shl bx,1
add bx,cx
add bx,es:[ne_segtab]
add dx, es:[bx].ns_minalloc
jnc leh_heapadjset
leh_heapadjmin:
; if 800h is too big fallback to
if KDEBUG ; what win9x code used as minimum
mov ax, 100h + SIZE LocalStats ; heap size 100h
else
mov ax, 100h
endif
mov cx, es:[ne_heap]
cmp cx, ax
jae leh_heapadjdone
leh_heapadjset:
mov es:[ne_heap], ax
leh_heapadjdone:
mov ax,es ; Set owner to be itself
cCall FarSetOwner,<ax,ax>
mov dx,exetype
test dh,NEINFONT ; save the font bit in exehdr
jz reexit ; somewhere
or es:[ne_flags],NEWINPROT
end_fault_trap
reexit:
; cmp ax, 20h ; translate error messages for ret
; jae @F
; mov bx, ax
; xor ax, ax
; cmp bl, 1 ; bx is 0, 1, 2, 19, other
; jz @F ; 1 -> 0 (out of memory)
; mov al, 11
; jb @F ; 0 -> 11 (invalid format)
; cmp bl, 3 ;
; mov al, 10
; jb @F ; 2 -> 10 (windows version)
; mov al, 19 ; 3 -> 19 (compressed EXE)
; jz @F ; others left alone
; mov al, bl
;@@:
push dx
mov bx, hBlock
or bx, bx
je noUnlock ; No block to unlock
push ax
cCall IGlobalUnlock,<bx>
push ss
pop ds ; might be freeing DS
cmp fast_fail, 1 ; is fastload area invalid?
jne @F
leh_slow:
krDebugOut DEB_WARN, "FastLoad area ignored due to incorrect segment flags"
cCall IGlobalFree,<hBlock> ; yes - free it, force slow-load
mov hBlock, 0
@@:
pop ax
cmp ax, LME_MAXERR
jae noFree ; Success, return memory block in bx
push ax
cCall IGlobalFree,<hBlock>
pop ax
noFree:
mov cx, NEFileOffset ; Return offset of header in CX
mov bx, hBlock
noUnlock:
pop dx
UnSetKernelDS
cEnd
sEnd NRESCODE
end