563 lines
23 KiB
NASM
563 lines
23 KiB
NASM
;**************************************************************************
|
|
;* walk286.ASM
|
|
;*
|
|
;* Assembly support code for the KRNL286 global heap routines
|
|
;* for TOOLHELP.DLL
|
|
;*
|
|
;**************************************************************************
|
|
|
|
INCLUDE TOOLPRIV.INC
|
|
|
|
PMODE32 = 0
|
|
PMODE = 1
|
|
SWAPPRO = 0
|
|
INCLUDE WINKERN.INC
|
|
|
|
;** External functions
|
|
externNP HelperVerifySeg
|
|
externNP HelperHandleToSel
|
|
externNP HelperPDBtoTDB
|
|
externNP HelperSegLen
|
|
|
|
;** Functions
|
|
|
|
sBegin CODE
|
|
assumes CS,CODE
|
|
|
|
; Walk286Count
|
|
;
|
|
; Returns the number of blocks in the given list
|
|
|
|
cProc Walk286Count, <PUBLIC,NEAR>, <di>
|
|
parmW wFlags
|
|
cBegin
|
|
mov es,hMaster ;Segment of master block
|
|
mov ax,wFlags ;Get the flag value
|
|
shr ax,1 ;Check for GLOBAL_LRU
|
|
jc W2C_LRU ;Bit set, must be LRU
|
|
shr ax,1 ;Check for GLOBAL_FREE
|
|
jc W2C_Free ;Must be free
|
|
;Must be GLOBAL_ALL
|
|
|
|
;** Get total object count
|
|
mov ax,es:[hi_count] ;Get heap count
|
|
inc ax ;Bump to include first sentinel
|
|
jmp SHORT W2C_End ;Get out
|
|
|
|
;** Get LRU object count
|
|
W2C_LRU:
|
|
mov ax,es:[gi_lrucount] ;Get the LRU count
|
|
jmp SHORT W2C_End ;Get out
|
|
|
|
;** Get Free list object count
|
|
W2C_Free:
|
|
mov ax,es:[gi_free_count] ;Get free count
|
|
jmp SHORT W2C_End ;Get out
|
|
|
|
;** Return the result in AX
|
|
W2C_End:
|
|
|
|
cEnd
|
|
|
|
; Walk286First
|
|
;
|
|
; Returns a handle to the first block in the 286 global heap.
|
|
|
|
cProc Walk286First, <PUBLIC,NEAR>, <di>
|
|
parmW wFlags
|
|
cBegin
|
|
mov es,hMaster ;Segment of master block
|
|
mov ax,wFlags ;Get the flag value
|
|
shr ax,1 ;Check for GLOBAL_LRU
|
|
jc W2F_LRU ;Bit set, must be LRU
|
|
shr ax,1 ;Check for GLOBAL_FREE
|
|
jc W2F_Free ;Must be free
|
|
;Must be GLOBAL_ALL
|
|
|
|
;** Get first object in complete heap (wFlags == GLOBAL_ALL)
|
|
mov ax,es:[hi_first] ;Get handle of first arena header
|
|
jmp SHORT W2F_End ;Get out
|
|
|
|
;** Get first object in LRU list
|
|
W2F_LRU:
|
|
mov ax,es:[gi_lrucount] ;Get the number of objects
|
|
or ax,ax ;Are there any objects?
|
|
je W2F_End ;No, return NULL
|
|
inc es:[gi_lrulock] ;No LRU sweeping for awhile
|
|
inc wLRUCount ;Keep a count of this
|
|
mov ax,es:[gi_lruchain] ;Get a pointer to the first item
|
|
jmp SHORT W2F_End ;Done
|
|
|
|
;** Get first object in Free list
|
|
W2F_Free:
|
|
mov ax,es:[gi_free_count] ;Get the number of objects
|
|
or ax,ax ;Are there any objects?
|
|
jz W2F_End ;No, return NULL
|
|
mov es,es:[hi_first] ;Get the first object
|
|
mov ax,es:[ga_freenext] ;Skip to the first free block
|
|
;Fall through to the return
|
|
|
|
;** Return the result in AX (return DX = NULL)
|
|
W2F_End:
|
|
xor dx,dx ;Clear high word
|
|
cEnd
|
|
|
|
|
|
; Walk286
|
|
;
|
|
; Takes a pointer to a block and returns a GLOBALENTRY structure
|
|
; full of information about the block. If the block pointer is
|
|
; NULL, looks at the first block. The last field in the GLOBALENTRY
|
|
; structure is used to chain to the next block and is thus used to walk
|
|
; the heap.
|
|
|
|
cProc Walk286, <PUBLIC,NEAR>, <di,si,ds>
|
|
parmD dwBlock
|
|
parmD lpGlobal
|
|
parmW wFlags
|
|
cBegin
|
|
;** Set up to build public structure
|
|
mov es,WORD PTR dwBlock ;Point to this block
|
|
lds si,lpGlobal ;Point to the GLOBALENTRY structure
|
|
|
|
;** Fill public structure
|
|
mov ax,es:[ga_handle] ;Get the handle of the block
|
|
mov [si].ge_hBlock,ax ;Put in public structure
|
|
mov ax,es:[ga_size] ;Get the size of the block (LOWORD)
|
|
mov dx,ax ;Clear high word
|
|
shl ax,4 ;Left shift DX:AX by 4
|
|
shr dx,16-4
|
|
mov WORD PTR [si].ge_dwBlockSize,ax ;Put in public structure
|
|
mov WORD PTR [si].ge_dwBlockSize + 2,dx ;Put in public structure
|
|
mov ax,es:[ga_owner] ;Owning task of block
|
|
mov [si].ge_hOwner,ax ;Put in public structure
|
|
xor ah,ah ;No upper BYTE
|
|
mov al,es:[ga_count] ;Lock count (moveable segments)
|
|
mov [si].ge_wcLock,ax ;Put in public structure
|
|
mov WORD PTR [si].ge_wcPageLock,0 ;Zero the page lock count
|
|
mov al,es:[ga_flags] ;BYTE of flags
|
|
xor ah,ah ;No upper BYTE
|
|
mov [si].ge_wFlags,ax ;Put in public structure
|
|
mov ax,es:[ga_next] ;Put next pointer in structure
|
|
mov WORD PTR [si].ge_dwNext,ax
|
|
mov WORD PTR [si].ge_dwNext + 2,0
|
|
|
|
;** Use DPMI to compute linear address of selector
|
|
mov ax,6 ;Get Segment Base Address
|
|
mov bx,es ;Get the segment value
|
|
int 31h ;Call DPMI
|
|
mov WORD PTR [si].ge_dwAddress,dx ;Save linear address
|
|
mov WORD PTR [si].ge_dwAddress + 2,cx
|
|
|
|
;** If this is a data segment, get local heap information
|
|
mov ax,[si].ge_hBlock ;Get the handle
|
|
cCall Walk286VerifyLocHeap
|
|
mov [si].ge_wHeapPresent,TRUE ;Flag that there's a heap
|
|
jnc W2_10 ;There really is no heap
|
|
mov [si].ge_wHeapPresent,FALSE ;Flag that there's no heap
|
|
W2_10:
|
|
|
|
;** If the owner is a PDB, translate this to the TDB
|
|
mov bx,[si].ge_hOwner ;Get the owner
|
|
cCall HelperHandleToSel, <bx> ;Translate to selector
|
|
mov bx,ax ;Get the selector in BX
|
|
cCall HelperVerifySeg, <ax,2> ;Must be two bytes long
|
|
or ax,ax ;Is it?
|
|
jz W2_15 ;No.
|
|
push es ;Save ES for later
|
|
mov es,bx ;Point to possible PDB block
|
|
cmp es:[0],20CDh ;Int 20h is first word of PDB
|
|
jnz W2_12 ;Nope, don't mess with this
|
|
mov ax,bx ;Pass parameter in AX
|
|
cCall HelperPDBtoTDB ;Get the corresponding TDB
|
|
or ax,ax ;Was one found?
|
|
jz W2_11 ;No.
|
|
mov [si].ge_hOwner,ax ;Make the owner the TDB instead
|
|
W2_11: or [si].ge_wFlags,GF_PDB_OWNER ;Flag that a PDB owned block
|
|
W2_12: pop es ;Restore ES
|
|
W2_15:
|
|
|
|
;** Check for this being the last item in both lists
|
|
mov ax,es ;Get the current pointer
|
|
cmp ax,es:[ga_next] ;See if we're at the end
|
|
jne W2_20 ;No
|
|
mov WORD PTR [si].ge_dwNext,0 ;NULL the next pointer
|
|
mov WORD PTR [si].ge_dwNext + 2,0
|
|
W2_20: mov ax,es ;Get current pointer
|
|
mov cx,wFlags ;Get the flags back
|
|
cCall NextLRU286 ;Get next LRU list pointer or 0
|
|
mov WORD PTR [si].ge_dwNextAlt,ax
|
|
mov WORD PTR [si].ge_dwNextAlt + 2,0
|
|
|
|
W2_End:
|
|
cEnd
|
|
|
|
|
|
; Walk286Handle
|
|
;
|
|
; Finds an arena pointer given a block handle
|
|
|
|
cProc Walk286Handle, <PUBLIC,NEAR>, <di,si,ds>
|
|
parmW hBlock
|
|
cBegin
|
|
mov ax,hBlock ;Get the block handle
|
|
cCall HelperHandleToSel, <ax> ;Convert to selector
|
|
cCall SelToArena286 ;Get the arena pointer
|
|
jnc W2H_10 ;Must be OK
|
|
xor ax,ax ;Return a 0L
|
|
xor dx,dx
|
|
jmp SHORT W2H_End ;Error in conversion, get out
|
|
W2H_10: mov ax,bx ;Get the low word
|
|
xor dx,dx ;No high word
|
|
W2H_End:
|
|
cEnd
|
|
|
|
|
|
; WalkLoc286Count
|
|
;
|
|
; Returns the number of blocks in the given local heap
|
|
|
|
cProc WalkLoc286Count, <PUBLIC,NEAR>, <di>
|
|
parmW hHeap
|
|
cBegin
|
|
;** Verify that it has a local heap
|
|
mov ax, hHeap ;Get the block
|
|
cCall Walk286VerifyLocHeap ;Verify it
|
|
jnc LC_10 ;It's OK
|
|
xor ax,ax ;No heap
|
|
jmp SHORT LC_End ;Get out
|
|
LC_10:
|
|
|
|
;** Point to the block
|
|
mov ax,hHeap ;Get the heap block
|
|
cCall HelperHandleToSel, <ax> ;Convert it to a selector
|
|
mov es,ax ;Use ES to point to it
|
|
mov bx,es:[6] ;Get a pointer to the HeapInfo struct
|
|
|
|
;** Get the number of blocks
|
|
mov ax,es:[bx].hi_count ;Get the count
|
|
LC_End:
|
|
cEnd
|
|
|
|
|
|
; WalkLoc286First
|
|
;
|
|
; Returns a handle to the first block in the 286 global heap.
|
|
|
|
cProc WalkLoc286First, <PUBLIC,NEAR>, <di>
|
|
parmW hHeap
|
|
cBegin
|
|
;** Verify that the given global block has a local heap
|
|
mov ax, hHeap ;Get the block
|
|
cCall Walk286VerifyLocHeap ;Verify it
|
|
jnc LF_10 ;It's OK
|
|
xor ax,ax ;No heap
|
|
jmp SHORT LF_End ;Get out
|
|
LF_10:
|
|
|
|
;** Point to the global block
|
|
mov ax,hHeap ;Get the heap block
|
|
cCall HelperHandleToSel, <ax> ;Convert it to a selector
|
|
mov es,ax ;Use ES to point to it
|
|
mov bx,es:[6] ;Get a pointer to the HeapInfo struct
|
|
|
|
;** Get the first block and return it
|
|
mov ax,WORD PTR es:[bx].hi_first ;Get the first block
|
|
LF_End:
|
|
cEnd
|
|
|
|
|
|
; WalkLoc286
|
|
;
|
|
; Takes a pointer to a block and returns a GLOBALENTRY structure
|
|
; full of information about the block. If the block pointer is
|
|
; NULL, looks at the first block. The last field in the GLOBALENTRY
|
|
; structure is used to chain to the next block and is thus used to walk
|
|
; the heap.
|
|
|
|
cProc WalkLoc286, <PUBLIC,NEAR>, <di,si,ds>
|
|
parmW wBlock
|
|
parmD lpLocal
|
|
parmW hHeap
|
|
cBegin
|
|
;** Verify that it has a local heap
|
|
mov ax, hHeap ;Get the block
|
|
cCall Walk286VerifyLocHeap ;Verify it
|
|
jnc LW_10 ;It's OK
|
|
jmp LW_End ;Get out
|
|
LW_10:
|
|
|
|
;** Get variables from the TOOLHELP DGROUP
|
|
mov ax,_DATA ;Get the variables first
|
|
mov ds,ax ;Point to our DS
|
|
mov bx,hUserHeap ;BX=User's heap block
|
|
mov cx,hGDIHeap ;CX=GDI's heap block
|
|
|
|
;** Point to the heap
|
|
mov ax,hHeap ;Get the heap block
|
|
cCall HelperHandleToSel, <ax> ;Convert it to a selector
|
|
mov es,ax ;Use ES to point to it
|
|
lds di,lpLocal ;Point to the LOCALENTRY structure
|
|
mov [di].le_wHeapType,NORMAL_HEAP ;In case we don't match below...
|
|
cmp bx,hHeap ;User's heap?
|
|
jnz LW_3 ;No
|
|
mov [di].le_wHeapType,USER_HEAP ;Yes
|
|
jmp SHORT LW_5 ;Skip on
|
|
LW_3: cmp cx,hHeap ;GDI's heap?
|
|
jnz LW_5 ;No
|
|
mov [di].le_wHeapType,GDI_HEAP ;Yes
|
|
LW_5:
|
|
mov si,wBlock ;Get the address of the block
|
|
|
|
;** Get information about the given block
|
|
mov bx,es:[si].la_handle ;Get the handle
|
|
mov [di].le_hHandle,bx ;Save in the public structure
|
|
mov ax,si ;Get block address
|
|
add ax,la_fixedsize ;Skip fixed size local arena
|
|
mov [di].le_wAddress,ax ;Save the block address
|
|
mov ax,es:[si].la_next ;Get the address of the next block
|
|
mov [di].le_wNext,ax ;Save the next pointer
|
|
sub ax,si ;Compute the size
|
|
sub ax,SIZE LocalArena ;Don't count arena size
|
|
mov [di].le_wSize,ax ;Save in public structure
|
|
mov ax,es:[si].la_prev ;Get the flags
|
|
and ax,3 ;Mask out all but flags
|
|
mov [di].le_wFlags,ax ;Save in structure
|
|
|
|
;** Moveable arenas are bigger and have a lock count to get
|
|
test al,LA_MOVEABLE ;Block has a handle iff it's moveable
|
|
jz SHORT LW_NoHandle ;No handle info
|
|
sub [di].le_wSize, (SIZE LocalArena) - la_fixedsize
|
|
add [di].le_wAddress, (SIZE LocalArena) - la_fixedsize
|
|
xor ah,ah ;Clear upper word
|
|
mov al,es:[bx].lhe_count ;Get lock count
|
|
mov [di].le_wcLock,ax ;Save it
|
|
jmp SHORT LW_20 ;Skip no handle info
|
|
LW_NoHandle:
|
|
mov ax, [di].le_wAddress ;Handle of fixed block is real offset
|
|
mov [di].le_hHandle, ax
|
|
mov [di].le_wcLock,0
|
|
LW_20:
|
|
;** See if it's the end
|
|
cmp [di].le_wNext,si ;Loop pointer?
|
|
jnz LW_End ;Nope
|
|
mov [di].le_wNext,0 ;Set a zero pointer
|
|
LW_End:
|
|
cEnd
|
|
|
|
|
|
; Walk286VerifyLocHeap
|
|
;
|
|
; Verifies that the given global block points to a data segment
|
|
; with a local heap.
|
|
;
|
|
; Call:
|
|
; AX = Block handle or selector
|
|
; Return:
|
|
; Carry flag set iff NOT a local heap segment
|
|
;
|
|
; Destroys all registers except AX, ESI, EDI, DS, and ES
|
|
|
|
cProc Walk286VerifyLocHeap, <PUBLIC,NEAR>, <es,si,di>
|
|
cBegin
|
|
;** Convert to a handle
|
|
cCall HelperHandleToSel, <ax>
|
|
|
|
;** Verify the selector
|
|
push ax ;Save the parameter
|
|
mov bx,SIZE LocalInfo ;Get the size
|
|
cCall HelperVerifySeg, <ax,bx> ;Check the selector
|
|
or ax,ax ;Is it valid?
|
|
pop ax ;Restore parameter
|
|
jnz VLH_SelOK ;Yes.
|
|
stc ;Set error flag
|
|
jmp SHORT VLH_End ;Get out
|
|
VLH_SelOK:
|
|
|
|
;** Check data segment to see if it has a local heap
|
|
mov es,ax ;Point to the local block with ES
|
|
cCall HelperSegLen, <ax> ;Get the length of the heap
|
|
or ax,ax ;Check for error
|
|
jz VLH_NoHeap ;Get out on error
|
|
mov cx,ax ;Use CX for comparisons
|
|
cmp cx,8 ;At least 8 bytes long?
|
|
ja VLH_10 ;Yes
|
|
stc ;No -- set error flag and get out
|
|
jmp SHORT VLH_End
|
|
VLH_10: mov bx,es:[6] ;Get offset to HeapInfo struct
|
|
or bx,bx ;If NULL, no local heap
|
|
jz VLH_NoHeap ;Get out
|
|
sub cx,bx ;See if HeapInfo is beyond segment
|
|
jbe VLH_NoHeap
|
|
sub cx,li_sig + 2 ;Compare against last structure mem
|
|
jbe VLH_NoHeap
|
|
cmp es:[bx].li_sig,LOCALHEAP_SIG ;Make sure the signature matches
|
|
jne VLH_NoHeap ;Doesn't match
|
|
clc ;Must be a heap!
|
|
jmp SHORT VLH_End
|
|
|
|
VLH_NoHeap:
|
|
stc ;Set error flag
|
|
|
|
VLH_End:
|
|
cEnd
|
|
|
|
|
|
;** Private helper functions
|
|
|
|
; SelToArena286
|
|
;
|
|
; Finds the arena entry for the given selector or handle.
|
|
; The arena entry is stored 16 bytes before the block in linear
|
|
; address space.
|
|
;
|
|
; Caller: AX = Selector
|
|
; Returns: BX = Arena entry
|
|
; Trashes everything except segment registers and AX
|
|
; Carry set on error
|
|
|
|
cProc SelToArena286, <NEAR>, <es,ds,ax>
|
|
cBegin
|
|
;** Convert to a handle
|
|
cCall HelperHandleToSel, <ax>
|
|
|
|
;** Verify selector
|
|
push ax ;Save the parameter
|
|
cCall HelperVerifySeg, <ax,1> ;Check the selector
|
|
or ax,ax ;Is it valid?
|
|
pop ax ;Restore parameter
|
|
jnz STA_SelOK ;Must be
|
|
stc ;Nope. Set error flag and exit
|
|
jmp SHORT STA_End
|
|
STA_SelOK:
|
|
;** If this is Win30StdMode, we're in the old KRNL286 which used
|
|
;* an arcane method of finding the arena. If that's the case
|
|
;** here, handle it seperately.
|
|
mov bx,_DATA ;Get the static data segment
|
|
mov ds,bx
|
|
test wTHFlags,TH_WIN30STDMODE ;3.0 Std Mode?
|
|
jnz STA_OldKRNL286 ;Yes
|
|
mov bx,segKernel ;Get the KERNEL variable segment
|
|
mov es,bx
|
|
mov bx,npwSelTableLen ;Get the pointer
|
|
mov dx,es:[bx] ;Get the selector table length
|
|
mov bx,npdwSelTableStart ;Get the start of the sel table
|
|
mov bx,es:[bx] ;Get the linear offset (32 bits)
|
|
mov es,hMaster ;Point to the arena table
|
|
and al,not 7 ;Clear the RPL bits for table lookup
|
|
shr ax,2 ;Convert to WORD offset
|
|
cmp ax,dx ;Check to see if off the end of table
|
|
jb STA_InTable ;It's in the table
|
|
stc ;Set error flag--not in table
|
|
jmp SHORT STA_End ;Get out
|
|
STA_InTable:
|
|
add bx,ax ;Add the selector offset
|
|
mov bx,es:[bx] ;Do the sel table indirection
|
|
clc ;BX now points to arena segment
|
|
jmp SHORT STA_End ;Skip the old stuff
|
|
|
|
STA_OldKRNL286:
|
|
mov bx,ax ;Selector in BX
|
|
mov ax,6 ;DPMI function 6, Get Seg Base Addr
|
|
int 31h ;Call DPMI
|
|
sub dx,10h ;Move back 16 bytes
|
|
sbb cx,0 ; (this is a linear address)
|
|
mov ax,7 ;DPMI function 7, Set Seg Base Addr
|
|
mov bx,wSel ;Use our roving selector
|
|
int 31h ;Call DPMI
|
|
mov ax,8 ;DPMI function 8, Set Seg Limit
|
|
xor cx,cx ;No upper byte
|
|
mov dx,16 ;Just 16 bytes
|
|
int 31h ;Call DPMI
|
|
mov ax,9 ;DPMI function 9, Set Access Rights
|
|
mov cl,0b2h ;Desired rights byte
|
|
int 31h ;Call DPMI
|
|
;Return arena segment pointer in BX
|
|
STA_End:
|
|
cEnd
|
|
|
|
|
|
; NextLRU286
|
|
;
|
|
; Checks the given arena table pointer to see if it is the last
|
|
; arena entry on the list.
|
|
; Uses a grungy method that is necessitated because of the
|
|
; unterminated linked lists we're dealing with here. The count
|
|
; stored is the only reliable method for finding the end. So, to
|
|
; provide random access to blocks in the heap, the heap is searched
|
|
; from top to bottom to find the block and see if it is the last
|
|
; one. If it is or if it is not on the given list, returns a 0
|
|
; pointer to the next item.
|
|
;
|
|
; If this search is for the entire global heap, we do not get the
|
|
; LRU link, but return NULL in AX. This speeds this up alot!
|
|
;
|
|
; Caller: AX = Arena table pointer
|
|
; CX = GLOBAL_ALL, GLOBAL_FREE, or GLOBAL_LRU
|
|
; Return: AX = Next arena table pointer or 0 if no more
|
|
; Trashes all registers except segment registers and SI,DI
|
|
|
|
cProc NextLRU286, <NEAR,PUBLIC>, <es,ds,si,di>
|
|
cBegin
|
|
;** Decode the flags
|
|
mov bx,_DATA ;Get the static data segment
|
|
mov ds,bx ;Point with DS
|
|
mov es,hMaster ;Segment of master block
|
|
cmp cx,GLOBAL_ALL ;Don't do this on full heap search
|
|
jne @F
|
|
jmp SHORT NL_BadList ;Just return NULL for this one
|
|
@@: cmp cx,GLOBAL_FREE ;Check free list?
|
|
je NL_Free ;Yes
|
|
|
|
;** Loop through LRU list until we find this item
|
|
NL_LRU:
|
|
mov si,ax ;Save the selector in AX
|
|
mov cx,es:[gi_lrucount] ;Get the number of objects
|
|
jcxz NL_Bad ;No object so return end
|
|
dec cx ;We don't want to find the last one
|
|
jcxz NL_Bad ;1 object so return end
|
|
mov ds,es:[gi_lruchain] ;Get a pointer to the first item
|
|
NL_LRULoop:
|
|
mov ax,ds ;Get in AX so we can compare
|
|
cmp si,ax ;Match yet?
|
|
je NL_ReturnNext ;Found it, return next item
|
|
mov ds,ds:[ga_lrunext] ;Not found yet, get the next one
|
|
loop NL_LRULoop ;Loop through all items
|
|
|
|
;** Unlock the LRU sweeping
|
|
NL_Bad: dec es:[gi_lrulock] ;OK to LRU sweep now
|
|
mov ax, _DATA ;Point to TH Data seg
|
|
mov ds, ax
|
|
dec wLRUCount ;Keep a count of this
|
|
jmp SHORT NL_BadList ;Not here either. Get out
|
|
|
|
;** Loop through free list until we find this item
|
|
NL_Free:
|
|
mov si,ax ;Save the selector in SI
|
|
mov cx,es:[gi_free_count] ;Get the number of objects
|
|
jcxz NL_BadList ;0 objects so return end
|
|
dec cx ;We don't want to find the last one
|
|
jcxz NL_BadList ;1 object so return end
|
|
mov ds,es:[hi_first] ;Get a pointer to the first item
|
|
NL_FreeLoop:
|
|
mov ds,ds:[ga_lrunext] ;Not found yet, get the next one
|
|
mov ax,ds ;Get the pointer so we can compare it
|
|
cmp ax,si ;Is this the one?
|
|
je NL_ReturnNext ;Yes, return it
|
|
loop NL_FreeLoop ;Loop through all items
|
|
jmp SHORT NL_BadList ;Not here either. Get out
|
|
|
|
NL_ReturnNext:
|
|
mov ax,ds:[ga_lrunext] ;Return the next one
|
|
jmp SHORT NL_End ;Get out
|
|
|
|
NL_BadList:
|
|
xor ax,ax ;Return zero for error
|
|
|
|
NL_End:
|
|
cEnd
|
|
|
|
sEnd
|
|
END
|
|
|