Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1191 lines
32 KiB

f16ptr typedef ptr far16
f32ptr typedef ptr far32
;==============================================================================
; BODY_PARAM_16
; Macro to generate equate for parameter on stack
;
;==============================================================================
BODY_PARAM_16 macro name, offset
name equ <[bp+offsetThkParams+offset]>
endm
;==============================================================================
; CB_THRU_COMMON
; Push address to be called and jump to common entry to 32bit code
;
;==============================================================================
CB_THRU_COMMON macro addr
local ret_addr
local log_msg_before
local log_msg_after
externDef W32S_BackTo32:near32
ifdef DEBUG
LogCBThkSL proto near stdcall, psz:DWORD
push offset log_msg_before
call LogCBThkSL
endif
push ret_addr
push addr
; BUGBUG [KevinR] 12-Oct-1993
; This hardcoded offset is a hack for M5. We should use a proper kernel
; dispatcher.
assume fs:nothing
Win16LockCount equ <word ptr fs:[30]> ; BUGBUG from k32share.inc
mov di,-1
xchg di,Win16LockCount
; PERFORMANCE! is is possible to jump directly thru import thunk
; W32S_BackTo32 just does a retn
jmp W32S_BackTo32
ifdef DEBUG
log_msg_before db "SL before",0
log_msg_after db "SL after",0
endif
ret_addr:
mov Win16LockCount,di
assume fs:error
ifdef DEBUG
push offset log_msg_after
call LogCBThkSL
endif
endm
;==============================================================================
; local macro, maps 32-bit pointer to 16-bit pointer,
;
; Guarantees:
; If &SaveAddr& referenes di, the value of di entering MAP_POINTER
; is used.
;==============================================================================
MAP_POINTER macro SaveAddr
lodsd ds:[esi]
push eax
call MapLS
ifnb <SaveAddr>
mov &SaveAddr&,eax
endif
stosd es:[di] ; dst in 16:16 stack
endm
;==============================================================================
; local macro, clean up after MAP_POINTER.
;
; Guarantees:
; If &SaveAddr& references si, the value of si entering UMAP_POINTER
; is used.
;==============================================================================
UMAP_POINTER macro SaveAddr
ifnb <SaveAddr>
push es
pushd &SaveAddr&
call UnmapLS
pop es
endif
add si,4
add edi,4
endm
;==============================================================================
; local macro, gets current process hInstance if null
; Win32/NT allows NULL hInstance where win3.1 doesn't
;
;==============================================================================
MAP_NULL_HINST macro hInst
local not_null
; externDef GetNullhInst:far16
externDef MaphInstLS:far16
ifnb <hInst>
mov eax, hInst
endif
; call GetNullhInst
call MaphInstLS
not_null:
endm
;==============================================================================
; local macro, gets current process hInstance if null
; Win32/NT allows NULL hInstance where win3.1 doesn't
;
;==============================================================================
UMAP_NULL_HINST macro hInst
local not_null
externDef MaphInstSL:far16
ifnb <hInst>
mov ax, hInst
endif
call MaphInstSL
not_null:
endm
;==============================================================================
; Convert either an instance handle, a global Win16 handle or junk.
; Used by commdlg.
;==============================================================================
MAP_CD_NULL_HINST macro hInst,isInst
local not_inst,exit
cmp isInst,0
jz not_inst
MAP_NULL_HINST hinst
jmp exit
not_inst: ;"hInst" is really 32-bit junk, or a zero-extended global handle.
;; No need to do anything: "ax" already contains lo-word.
exit:
endm
;==============================================================================
; Convert either an instance handle, a global Win16 handle or junk.
; Used by commdlg.
;==============================================================================
UMAP_CD_NULL_HINST macro hInst,isInst
local not_inst,exit
cmp isInst,0
jz not_inst
UMAP_NULL_HINST hinst
jmp exit
not_inst: ;"hInst" is really junk, or a global handle.
ror eax,16
xor ax,ax
ror eax,16
exit:
endm
;==============================================================================
; local macro, maps 32-bit call-back function pointer to 16-bit
; call back function pointer,
; check if it is a NULL pointer before calling AllocCallBack.
;==============================================================================
MAP_CALLBACK macro CallBackType:req
lods dword ptr ds:[esi] ;wndproc must have a 16-bit
push bx ;save BX
push es ;save ES
push eax ;push address as parameter
push dword ptr CallBackType ;push cb type as parameter
call AllocCallback ;DX:AX = 16-bit cb address
pop es ;restore ES
pop bx ;restore BX
stos dword ptr es:[di] ;pass on NULL pointer
endm
;==============================================================================
; local macro, free resources allocated for a call-back function
; check if it is a NULL pointer before calling FreeCallBack.
; eax contains the 32-bit address of the mapped call-back function
;==============================================================================
FREE_CALLBACK macro iCallbackType:req
endm
;==============================================================================
; local macro, maps byte size field to byte size field
;==============================================================================
MAP_BYTETOBYTE macro
lodsb ds:[esi]
stosb es:[di]
endm
;==============================================================================
; local macro, maps byte size field to byte size field
;==============================================================================
UMAP_BYTETOBYTE macro
lodsb ds:[si]
stosb es:[edi]
endm
;==============================================================================
; local macro, maps word size field to word size field
;==============================================================================
MAP_WORDTOWORD macro
lodsw ds:[esi]
stosw es:[di]
endm
;==============================================================================
; local macro, maps word size field to word size field
;==============================================================================
UMAP_WORDTOWORD macro
lodsw ds:[si]
stosw es:[edi]
endm
;==============================================================================
; local macro, maps dword size field to word size field
;==============================================================================
MAP_DWORDTOWORD macro
lodsd ds:[esi]
stosw es:[di]
endm
;==============================================================================
; local macro, maps dword size field to word size field
;==============================================================================
UMAP_WORDTODWORD macro
lodsw ds:[si]
movzx eax,ax
stosd es:[edi]
endm
;==============================================================================
; local macro, maps long size field to int size field
;==============================================================================
MAP_LONGTOINT macro
lodsd ds:[esi]
stosw es:[di]
endm
;==============================================================================
; local macro, maps int size field to long size field
;==============================================================================
UMAP_INTTOLONG macro
lodsw ds:[si]
cwde
stosd es:[edi]
endm
;==============================================================================
; local macro, maps dword size field to dword size field
;==============================================================================
MAP_DWORDTODWORD macro
movsd es:[di], ds:[esi]
endm
;==============================================================================
; local macro, maps dword size field to dword size field
;==============================================================================
UMAP_DWORDTODWORD macro
movsd es:[edi], ds:[si]
endm
;==============================================================================
; The following are variants of the above macros that are better suited for
; repacking structures in-place.
;==============================================================================
;==============================================================================
; local macro, maps 32-bit call-back function pointer to 16-bit
; call back function pointer,
; check if it is a NULL pointer before calling AllocCallBack.
;==============================================================================
MAP_CALLBACK_32_16_IP macro reg:=<ds>,CallBackType:req
lods dword ptr reg:[si] ;wndproc must have a 16-bit
push bx
push es
push eax
push dword ptr CallBackType
call AllocCallback
pop es
pop bx
stosd
endm
;==============================================================================
; local macro, maps 16-bit call-back function pointer to 32-bit
; call back function pointer,
; check if it is a NULL pointer before calling AllocCallback.
;
; ASSUMES DIRECTION FLAG DOWN, i.e. STD.
;==============================================================================
MAP_CALLBACK_16_32_IP macro reg:=<ds>,CallBackType:req
lods dword ptr reg:[si] ;wndproc must have a 16-bit
push bx
push es
push eax
push dword ptr CallBackType
call AllocCallback32
pop es
pop bx
stosd
endm
;==============================================================================
; local macro, maps 32-bit pointer to 16-bit pointer,
;==============================================================================
MAP_POINTER_32_16_IP macro reg:=<ds>
lodsd reg:[si]
push eax
call MapLS
stosd es:[di]
endm
;==============================================================================
; MAP_HINST_32_16_IP
; Map an instance handle from 32->16 in-place. If the original value is 0,
; replace it with the instance handle from win32s16.dll.
;
; Assumes ds = _DATA.
;==============================================================================
MAP_HINST_32_16_IP macro reg:=<ds>
local have_hinst
lods dword ptr reg:[si] ; convert hinst to word
or ax,ax ; translate to hComboInst if
jnz have_hinst ; null
mov ax,hComboInst
have_hinst:
stosw
endm
;==============================================================================
; MAP_HINST_16_32_IP
; Map an instance handle from 16->32 in-place. If the original value is
; hComboInst, replace it with 0.
;
; Assumes ds = _DATA.
;==============================================================================
MAP_HINST_16_32_IP macro reg:=<ds>
local have_hinst
lods word ptr reg:[si] ; convert hinst to word
cmp ax,hComboInst ; translate to null if
jnz have_hinst ; hComboInst
sub ax,ax
have_hinst:
movzx eax,ax
stosd
endm
;==============================================================================
; local macro, maps word size field to word size field
;==============================================================================
MAP_WORDTOWORD_IP macro reg:=<ds>
movsw es:[di], reg:[si]
endm
;==============================================================================
; local macro, maps dword size field to word size field
;==============================================================================
MAP_DWORDTOWORD_IP macro reg:=<ds>
lodsd reg:[si]
stosw es:[di]
endm
;==============================================================================
; local macro, maps dword size field to word size field
;==============================================================================
MAP_LONGTOINT_IP macro reg:=<ds>
lodsd reg:[si]
stosw es:[di]
endm
;==============================================================================
; local macro, maps dword size field to dword size field
;==============================================================================
MAP_DWORDTODWORD_IP macro reg:=<ds>
movsd es:[di], reg:[si]
endm
;==============================================================================
; local macro, maps word size field to dword size field by zero-extension
;==============================================================================
MAP_WORDTODWORD_IP macro reg:=<ds>
lodsw reg:[si]
movzx eax,ax
stosd es:[di]
endm
;==============================================================================
; local macro, maps word size field to dword size field by sign-extension
;==============================================================================
MAP_INTTOLONG_IP macro reg:=<ds>
lodsw reg:[si]
cwde
stosd es:[di]
endm
;==============================================================================
; save all 16-bit registers, except dx:ax
;
;==============================================================================
SAVEALL macro
push cx ; save all 16-bit registers, except dx:ax
push bx
push bp
push si
push di
push ds
push es
endm
;==============================================================================
; restore all 16-bit registers, except dx:ax
;
;==============================================================================
RESTOREALL macro StackType:=<Stack16>
POPW es ; restore all 16-bit registers, except dx:ax
CHECKW ds, StackType
POPW ds
CHECKW di, StackType
pop di
CHECKW si, StackType
pop si
CHECKW bp, StackType
pop bp
pop bx
pop cx
endm
;==============================================================================
; save all 32-bit registers, except eax
;
;==============================================================================
SAVEALL32 macro
push edx ;save all 32-bit registers except eax
push ecx
push ebx
push ebp
push esi
push edi
push ds
push es
endm
;==============================================================================
; restore all 32-bit registers, except eax
;
;==============================================================================
RESTOREALL32 macro StackType:=<Stack16>
POPD es ; restore all 32-bit registers, except eax
CHECKD ds, StackType
POPD ds
CHECKD edi, StackType
pop edi
CHECKD esi, StackType
pop esi
CHECKD ebp, StackType
pop ebp
CHECKD ebx, StackType
pop ebx
pop ecx
pop edx
endm
;==============================================================================
; test two text macros for equality
;
;
;==============================================================================
TextEqual? macro Text_1, Text_2
ifidni <Text_1>, <Text_2>
exitm <not 0>
endif
exitm <0>
endm
;==============================================================================
; test two text macros for difference
;
;
;==============================================================================
TextDiff? macro Text_1, Text_2
ifidni <Text_1>, <Text_2>
exitm <0>
endif
exitm <not 0>
endm
;==============================================================================
; check a word on top of the stack
; if not equal, break
;
;==============================================================================
CHECKW macro CurrentReg, StackType:=<Stack16>
local skip_int3
local skip_another_int3
if (@WordSize eq 4) and TextDiff? (&StackType&,Stack16)
push eax
mov ax,&CurrentReg&
cmp ax,word ptr [esp+4]
pop eax
je skip_int3
int 3
skip_int3:
else
push bp
push ax
mov ax,&CurrentReg&
mov bp,sp
and ebp,0ffffh
cmp ax,word ptr [ebp+4]
pop ax
pop bp
je skip_another_int3
int 3
skip_another_int3:
endif
endm
;==============================================================================
; check a dword on top of the stack
; if not equal, break
;
;==============================================================================
CHECKD macro CurrentReg, StackType:=<Stack32>
local skip_int3
local skip_another_int3
if 0
;!!! fix this
if (@WordSize eq 4) and TextDiff? (&StackType&,Stack32)
push eax
mov ax,&CurrentReg&
cmp ax,word ptr [esp+4]
pop eax
je skip_int3
int 3
skip_int3:
else
push bp
push ax
mov ax,&CurrentReg&
mov bp,sp
and ebp,0ffffh
cmp ax,word ptr [ebp+4]
pop ax
pop bp
je skip_another_int3
int 3
skip_another_int3:
endif
endif
endm
;==============================================================================
; pop word
;
;==============================================================================
POPW macro SegReg
if @WordSize eq 4
db 66h
endif
pop SegReg
endm
;==============================================================================
; pop dword
;
;==============================================================================
POPD macro SegReg
if @WordSize eq 2
db 66h
endif
pop SegReg
endm
;==============================================================================
; operand-size override
;
;==============================================================================
OTHER_OPERAND_SIZE macro arg
db 66h
arg
endm
;==============================================================================
; address-size override
;
;==============================================================================
OTHER_ADDRESS_SIZE macro arg
db 67h
arg
endm
;==============================================================================
; pop 16
;
;==============================================================================
POP16 macro Reg
if @WordSize eq 4
irp curreg,<cs,ds,ss,es,fs,gs>
ifidni <curreg>,<Reg>
db 66h
endif
endm
endif
pop Reg
endm
;==============================================================================
; allocate and public a byte flag
;
;==============================================================================
PubByte macro name, value
public name
name db value
endm
;==============================================================================
; log a 16=>32 api call
;
;==============================================================================
APILOGSL macro argName
local exit,szLogMsg
ifdef DEBUG
LogApiThkSL proto near stdcall, psz:dword
push offset szLogMsg
call LogApiThkSL
jmp exit
szLogMsg db '&argName&',13,10,0
exit:
endif
endm
;==============================================================================
; log a 32=>16 api call, non-flat
;
;==============================================================================
APILOGLS macro argName
local exit,szLogMsg
ifdef DEBUG
externDef LogApiThkLS:far16
push ax
push cs
push offset szLogMsg
call LogApiThkLS
pop ax
jmp exit
szLogMsg db '&argName&',13,10,0
exit:
endif
endm
;==============================================================================
; log an api call, 16-bit
;
; BUGBUG [KevinR] 26-Aug-1993
; rip out this macro when we get rid of fNewDispatcherLS
;
;==============================================================================
APILOG16 macro argName, argUnused, argComment
local exit,szApiName,szComment
ifdef DEBUG
externDef Log16BitThunkCall:far16
dsOffsetInCS CATSTR @code, <CodeData>
push ds
push ax
mov ds,cs:dsOffsetInCS
push cs
push offset szApiName
push cs
push offset szComment
call Log16BitThunkCall
pop ax
pop ds
jmp exit
szApiName db '&argName& ',0
szComment db '&argComment& ',0
exit:
endif
endm
;==============================================================================
; log an api call, 32-bit
;
;==============================================================================
APILOG macro argName, argFlag
local do_it,done,szApiName
ifdef DEBUG
;externDef _DbgPrint:near32
;;If argFlag is nonzero, print out the message.
cmp argFlag&,0
jnz do_it
jmp short done
;;Define the name here so we can pass it to _DbgPrint.
szApiName db '&argName&',0
do_it:
push offset FLAT:szApiName
push offset FLAT:szApiFmt
;call _DbgPrint
add esp,2*4
done:
endif
endm
;==============================================================================
; log 16-bit api return, in 32-bit code
;
;==============================================================================
RETLOG macro argFlag
endm
;==============================================================================
; conditionally break
;
;==============================================================================
SWITCHABLE_INT3 macro argLabel, argFlag
local skip_int3
externDef argLabel :far16
push ds
push ax
mov ax,seg &argFlag
mov ds,ax
cmp &argFlag,0
je skip_int3
argLabel& label far16
int 3
skip_int3:
pop ax
pop ds
endm
;==============================================================================
;
;
;==============================================================================
STUB0 macro module, argLabel, nBytes, argComment:=<stub0>
externDef argLabel&16 :far16
argLabel&16 label far16
ifdef FSAVEALL
SAVEALL
endif
APILOG16 argLabel&16, f&module&ApiLog, argComment
ifdef INT3
SWITCHABLE_INT3 argLabel&_stub, f&module&Int3
endif
xor ax,ax
cwd
ifdef FSAVEALL
RESTOREALL
endif
retf &nBytes&
endm
;==============================================================================
;
;
;==============================================================================
STUB macro module, argLabel, nBytes, nRetAX, argComment:=<stub>
externDef argLabel&16 :far16
externDef PCodeDebug16 :far16
argLabel&16 label far16
ifdef FSAVEALL
SAVEALL
endif
APILOG16 argLabel&16, f&module&ApiLog, argComment nRetAX
ifdef INT3
SWITCHABLE_INT3 argLabel&_stub, f&module&Int3
endif
mov ax,&nRetAX
ifdef FSAVEALL
RESTOREALL
endif
retf nBytes
endm
;==============================================================================
; entry code for flat common callback
;
;==============================================================================
CALLBACK_PROLOGUE macro
pop eax ; 16:16 callback
pop edx ; eip, API32
push cs ; flat cs
push edx ; eip, API32
push eax ; 16:16 callback
push ebp
mov ebp,esp
push ds ; save registers
push es
push ebx
push edi
push esi
endm
;==============================================================================
; exit code for flat common callback
;
;==============================================================================
CALLBACK_EPILOGUE macro size
LOCAL bad_esp
;--------------------------------------------------
; switch stacks and jump to 16:16 callback
; when the 16:16 callback does a retf, we will hit our cleanup routine
push dword ptr ADDR_THK_CLEANUP_&size
; prepare to transfer to the 16-bit callback function
push pCallback16
; get the ss16 we had when we entered the callback API16
; make the 16-bit ss:sp point to the same linear address as the flat ss:esp
call UsrQuerySS16
mov esi,eax ; save ss16
push eax
call GetSelectorBase32 ; LATER: LDT lookup
xchg eax,esp
sub eax,esp
jb bad_esp
cmp eax,65535
ja bad_esp
mov ss,si
mov sp,ax
; effectively, jmp to 16:16 callback
retw
bad_esp:
int 3
endm
;==============================================================================
; save flat stack and thunkID
;
;==============================================================================
SAVE_STACK_AND_THUNKID macro
lea eax,[addr_registers] ; save flat stack
push ss
push eax
call GetThunkID32
push eax ; save 16:16 thunkID
endm
;==============================================================================
; 32-bit callback cleanup code
;
;==============================================================================
CALLBACK_CLEANUP32 macro size
externDef CALLBACK_CLEANUP_&size&:near32
CALLBACK_CLEANUP_&size&:
;;;-----------------------------------------------------------------------
;;; DO NOT REMOVE THE OVERRIDE PREFIX. IT IS NEEDED TO GET PAST A BUG
;;; IN A CERTAIN BRAND OF CLONE CHIPS.
;;;-----------------------------------------------------------------------
lss sp,ss:[ebx]
POP16 ds
POP16 di
POP16 si
POP16 bp
add sp, 8 ; pop dispatcher ptr+index
OTHER_OPERAND_SIZE
retf size ; return to the 16-bit API
endm
;==============================================================================
; SetLastError mechanism
;
;==============================================================================
SETERROR macro Value:req, Error:req
local done
externDef SetLastError16:far16
cmp ax,Value
jne short done
push dword ptr Error
call SetLastError16
done:
endm
;-----------------------------------------------------------------------;
; MoveBytes -- generate code to move n consecutive bytes
;
; Entry:
; DS:ESI --> source
; ES:EDI --> destination
;-----------------------------------------------------------------------;
MoveBytes macro n
local q, r
q = n / 4
r = n and 3
if q
if q lt 256
mov ecx,byte ptr q
else
mov ecx,q
endif
rep movs dword ptr es:[edi], dword ptr ds:[esi]
endif
if r
mov ecx,byte ptr r
rep movs byte ptr es:[edi], byte ptr ds:[esi]
endif
endm
;-----------------------------------------------------------------------;
; ZeroBytes -- generate optimized code to set n consecutive bytes to 0
;
; Entry:
; ES:EDI --> address at which to start
;-----------------------------------------------------------------------;
ZeroBytes macro n
local q, r
q = n / 4
r = n and 3
sub eax,eax
if q
if q lt 256
mov ecx,byte ptr q
else
mov ecx,q
endif
rep stos dword ptr es:[edi]
endif
if r
mov ecx,byte ptr r
rep stos byte ptr es:[edi]
endif
endm
;-----------------------------------------------------------------------;
; GMH2Sel
;
; This macro encapsulates the assumption that a global memory handle
; in win3.1 is either a selector (if fixed) or a selector with the
; low bit cleared (if moveable). Therefore, it can be turned into a
; selector by always setting the low bit.
;-----------------------------------------------------------------------;
GMH2Sel macro reg:req, regMask1, regMask2
ifb <regMask1>
or reg,1
else
.errb <regMask2>
;The following sequence uses two mask registers to
;convert only non-zero values of "reg" to selectors.
mov regMask1,1 ;mov cx,1
cmp reg,regMask1 ;cmp ax,cx C=1 if AX=0
cmc ;cmc C=0 if AX=0
sbb regMask2,regMask2 ;sbb dx,dx DX=0 if AX=0
and regMask1,regMask2 ;and cx,dx CX=0 if AX=0
or reg,regMask1 ;or ax,cx AX=0 if AX=0
endif
endm
;-----------------------------------------------------------------------;
; PACK_CALLBACK
;-----------------------------------------------------------------------;
PACK_CALLBACK macro iOffset,iTempOffset,dwCallbackType
local exit
local not_null
mov esi,[bp+&iOffset&]
or esi,esi
jnz not_null
sub sp,18 ;Space the thunklet would have taken
jmp exit
not_null:
; Push the following code on the stack:
;
; 66 68 xx xx xx xx push imm32 <flat lpCallback>
; 66 68 xx xx xx xx push imm32 <dwCallbackType>
; 90 nop ;for convenient
; EA xx xx xx xx jmp CALLBACK_BODY_16
mov ax,seg CALLBACK_BODY_16
push ax
mov ax,offset CALLBACK_BODY_16
push ax
push 0ea90h
push dword ptr &dwCallbackType&
push 6866h
push esi
push 6866h
push ss
call GetCSAlias
mov [bp-&iTempOffset&],sp
mov [bp-(&iTempOffset&)+2],ax
exit:
endm; PACK_CALLBACK
;-----------------------------------------------------------------------;
; UNPACK_CALLBACK
;-----------------------------------------------------------------------;
UNPACK_CALLBACK macro iOffset,iTempOffset,dwCallbackType
push word ptr [bp-&iTempOffset&+2]
call FreeCSAlias
endm; UNPACK_CALLBACK
;-----------------------------------------------------------------------;
; TILE_BUFFER
;
; Allocates overlapping tiling selectors for a huge memory block.
;
; Inputs:
; eax: 32-bit linear address (NULL is allowed)
; ecx: 32-bit buffer size (if 0, eax is treated as NULL)
;
; Outputs:
; eax: upper 16 bits = first selector
; lower 16 bits = 0 (thus, eax is the 16:16 pointer)
; ecx: upper 16 bits = first selector (same as upper eax)
; lower 16 bits = # of selectors allocated
; CF: 0 = success, 1 = failure.
;
; Registers preserved:
; bp, sp, all segment registers.
;
; Special cases:
; if buffer address is NULL or the size is 0, macro returns
; eax == ecx == 0, CF = 0.
; if macro fails, it returns eax == 0, CF = 1.
;
; Use UNTILE_BUFFER to deallocate tiling selectors.
;-----------------------------------------------------------------------;
TILE_BUFFER macro
extern TileBuffer:far16 ;Dynalink to win32c.dll
call TileBuffer
endm ;TILEBUFFER
;-----------------------------------------------------------------------;
; UNTILE_BUFFER
;
; Releases tiling selectors allocated by TILE_BUFFER.
;
; Inputs:
; ecx == sel:count (as returned by TILE_BUFFER. 00:00 is legal).
;
; Outputs:
; None.
;
; Registers preserved:
; bp, sp, all segment registers
;
; Special case:
; UNTILE_BUFFER does nothing if sel:count == 00:00.
;-----------------------------------------------------------------------;
UNTILE_BUFFER macro
extern UntileBuffer:far16 ;Dynalink to win32c.dll
call UntileBuffer
endm ;UNTILE_BUFFER