windows-nt/Source/XPSP1/NT/base/mvdm/wow32/i386/fastwow.asm
2020-09-26 16:20:57 +08:00

955 lines
24 KiB
NASM

title "Fast Protected Mode services"
;++
;
; Copyright (c) 1989 Microsoft Corporation
;
; Module Name:
;
; fastwow.asm
;
; Abstract:
;
; This module implements a fast mechanism for WOW apps to go back
; and forth from app code (protected mode, 16-bit segmented) to
; WOW32.DLL (flat).
;
;
; Author:
;
; Bob Day (bobday) 07-29-92
; copied from FASTPM.ASM written by Dave Hastings (daveh) 26-Jul-91
;
; barryb 11-nov-92 added fast callback mechanism, pared
; WOWBopEntry to the bare essentials (hopefully)
;
;
; rules of thumb:
; - monitor context gets saved on monitor stack on entry to
; FastWOWCallbackCall and restored on the way out of FastWOWCallbackRet
; - no need to save any VDM general registers - krnl286 uses the
; WOW16Call stack frame for this
; - you can't save anything on the 16-bit stack because the stack
; gets tinkered with by DispatchInterrupts
;
; WARNING WARNING WARNING WARNING WARNING WARNING WARNING
; these routines are optimized for the straight-through case, no
; interrupt being dispatched, so there's duplicate code in the
; routines. if you add stuff, be sure to add it to both paths.
; WARNING WARNING WARNING WARNING WARNING WARNING WARNING
;
; sudeepb 09-Dec-1992
; Changed all the refernces to virtual interrupt flag in the VDMTIB
; to fixed DOS location.
;--
.386p
include ks386.inc
include bop.inc
include wowtd.inc
if DBG
DEBUG equ 1
endif
ifdef DEBUG
DEBUG_OR_WOWPROFILE equ 1
endif
ifdef WOWPROFILE
DEBUG_OR_WOWPROFILE equ 1
endif
include wow.inc
include callconv.inc
include vint.inc
include vdmtib.inc
EXTRN __imp__CurrentMonitorTeb:DWORD
EXTRN __imp__FlatAddress:DWORD
_TEXT SEGMENT PARA PUBLIC 'CODE'
ASSUME DS:NOTHING, ES:NOTHING, SS:FLAT, FS:NOTHING, GS:NOTHING
EXTRNP _DispatchInterrupts,0
EXTRNP @W32PatchCodeWithLpfnw32, 2
EXTRNP @InterpretThunk, 2
EXTRNP _Ssync_WOW_CommDlg_Structs, 3
ifdef DEBUG
EXTRNP _logargs,2
EXTRNP _logreturn,3
endif
_TEXT ENDS
_DATA SEGMENT DWORD PUBLIC 'DATA'
extrn _aw32WOW:Dword
public _WowpLockPrefixTable
_WowpLockPrefixTable label dword
dd offset FLAT:_wowlock1
dd offset FLAT:_wowlock2
dd offset FLAT:_wowlock3
dd offset FLAT:_wowlock4
dd offset FLAT:_wowlock5
dd offset FLAT:_wowlock6
dd 0
Stack16 LABEL DWORD
_savesp16 DW 0
_savess16 DW 0
_saveip16 DD 0
_savecs16 DD 0
_saveebp32 DD 0
_saveeax DD 0
_saveebx DD 0
_saveecx DD 0
_saveedx DD 0
_fKernelCSIPFixed DB 0
_DATA ENDS
public _saveeax
public _saveebx
public _saveecx
public _saveedx
public _savesp16
public _savess16
public _savecs16
public _saveip16
public _saveebp32
public _fKernelCSIPFixed
public Stack16
;
; The variable fKernelCSIPFixed is used so the fastbop/callback mechanism
; knows when it no longer needs to pop the 16-bit return address off the
; 16-bit stack on entry to WOWBopEntry and FastWOWCallbackRet because kernel
; has booted and the address won't be changing.
;
;
; The assumption is that we're always coming from the same place in kernel.
; It also saves a jmp when returning to kernel.
;
_TEXT SEGMENT
page ,132
subttl "WOWBopEntry"
;++
;
; Routine Description:
;
; This routine switches from the VDM context to the monitor context.
; Then executes the appropriate WOW api call.
; Then returns back to the VDM context.
;
; Arguments:
;
; none
;
; Returns:
;
; puts result of WOW api call (EAX) into pFrame->wDX, pFrame->wAX
;
; [LATER] add a field to the frame and set it to !0 if a task switch
; has occurred. kernel can read this off the stack instead
; of having to load the kernelDS to look at wCurTDB
;
assume DS:Nothing,ES:Nothing,SS:Nothing
ALIGN 16
cPublicProc _WOWBopEntry,0
mov bx,KGDT_R3_DATA OR RPL_MASK ; Move back to Flat DS
mov ds,bx ; so push and pop size same
assume ds:_DATA
mov _saveeax,eax ; Multi Media Apps call api's
mov _saveebx,ebx ; at interrupt time which trash
mov _saveecx,ecx ; the high parts of the 32 bit
mov _saveedx,edx ; registers. [LATER] this
; should be moved to fame
pushfd ; Save them flags
; we make here the assumption that the c compiler always keeps the
; direction flag clear. We clear it here instead of restoring it
; from the context for performance
cld
mov eax,KGDT_R3_TEB OR RPL_MASK ; restore flat FS
mov fs,ax
mov ebx, dword ptr fs:[PcTeb]
mov ebx, dword ptr [ebx].TeVdm ; get pointer to contexts
;
; start saving some of the 16-bit context
;
; Interrupts are always enabled in WOW, but if we are trying
; to simulate the fact that they are disabled, we need to
; turn them off in the structure.
pop eax
mov edx,dword ptr ds:FIXED_NTVDMSTATE_LINEAR ; Get simulated bits
or edx,NOT VDM_VIRTUAL_INTERRUPTS
and eax,edx ; Pass on interrupt bit if it was on
mov dword ptr [ebx].VtVdmContext.CsEFlags,eax
; Save the calling address
; this address remains constant once krnl286 has booted.
; these three instructions are cheaper than saving it
; [LATER] this can be reduced. how?
cmp _fKernelCSIPFixed, 0
je wbegetretaddress
add esp, 8 ; pop cs, ip
wbe10:
; Save the stack (pre-call)
mov _savess16,di ; 16-bit SS put in di by krnl286
mov _savesp16,sp
; switch Stacks
.errnz (CsEsp + 4 - CsSegSS)
lss esp, [ebx].VtMonitorContext.CsEsp
; Now running on Monitor stack
; save hiword of ESI, EDI
; note that di has already been trashed
;
; [LATER] move this to the vdmframe, don't need to push it twice
push esi
push edi
push ebp
cPublicFpo 6,0 ; locals, params
; we only save 3 but FastWOWCallbackCall
; saves 3 as well
; Set up flat segment registers
mov edx,KGDT_R3_DATA OR RPL_MASK
mov es,dx ; Make them all point to DS
mov gs,dx ; [LATER] do we really have to set up GS?
mov eax,KGDT_R3_TEB OR RPL_MASK ; restore flat FS
mov fs,ax
mov ebp, _saveebp32
; Update the CURRENTPTD->vpStack
mov eax,fs:[PcTeb]
mov ecx,[eax+TbWOW32Reserved] ; move CURRENTPTD into ecx
mov esi, [Stack16]
mov dword ptr [ecx],esi ; PTD->vpStack = vpCurrentStack
; Convert the 16:16 SS:SP pointer into a 32-bit flat pointer
; DI = 16-bit SS, put there by kernel
and esi, 0FFFFh ; esi = 16-bit sp
and edi, 0FFFFh ; remove junk from high word
shr edi, 3 ; remove ring and table bits
mov edx, dword ptr [__imp__FlatAddress]
add esi, dword ptr [edx+edi*4]
; esi is now a flat pointer to the frame
mov eax,dword ptr [ecx+cbOffCOMMDLGTD] ; ecx = CURRENTPTD
cmp eax,0 ; eax = PTD->PCOMMDLGTD
jnz SsyncCommDlg16to32
SsyncContinue1:
ifdef DEBUG
push ecx
stdCall _logargs,<3,esi>
pop ecx
endif
; Convert the wCallID into a Thunk routine address.
mov edx, dword ptr [esi].vf_wCallID
; esi = pFrame
mov [ecx].WtdFastWowEsp, esp
test edx, 0ffff0000h
mov eax, edx
jnz MakeCall
ifdef DEBUG
imul edx, size W32
else
.errnz (size W32 - 4)
shl edx, 2
endif
mov edx, dword ptr [_aw32WOW + edx] ; eax = aw32WOW[edx].lpfnW32
test edx, 0ffff0000h ; check for intthunk (hiword 0)
jnz @f
mov eax, @InterpretThunk@8
jmp MakeCall
@@:
mov ecx, esi ; ecx = pFrame & edx = lpfnW32
call @W32PatchCodeWithLpfnw32@8
MakeCall:
mov ecx, esi ; ecx = pFrame & edx = lpfnW32
call eax
ApiReturnAddress:
;
; IMPORTANT NOTE: Upon retruned from wow32 worker routine, the
; non-volatile registers may be destroyed if it is returned via
; try-except handler.
;
mov ebx, dword ptr [__imp__CurrentMonitorTeb]
mov ecx,fs:[PcTeb]
mov [ebx], ecx ; Tell NTVDM which is the active thread
mov ecx,[ecx+TbWOW32Reserved] ; move CURRENTPTD into ecx
mov ebx,dword ptr [ecx+cbOffCOMMDLGTD] ; ecx = CURRENTPTD
cmp ebx,0 ; ebx = PTD->PCOMMDLGTD
jnz SsyncCommDlg32to16
SsyncContinue2:
ifdef DEBUG
push ecx
push eax
movzx esi, word ptr [ecx] ; offset
movzx edi, word ptr [ecx+2] ; selector
shr edi, 3 ; remove ring and table bits
mov ebx, dword ptr [__imp__FlatAddress]
add esi, dword ptr [ebx+edi*4] ; esi = offset + base
stdCall _logreturn,<5, esi, eax>
pop eax
pop ecx
endif
mov ebx, dword ptr fs:[PcTeb]
mov ebx, dword ptr [ebx].TeVdm ; get pointer to contexts
push dword ptr [ebx].VtVdmContext.CsEFlags ;get 16-bit flags
popfd ; in case the direction flag was set
test dword ptr ds:FIXED_NTVDMSTATE_LINEAR,dword ptr VDM_INTERRUPT_PENDING
jnz wl70
wl40:
mov _saveebp32, ebp
pop ebp
pop edi
pop esi
mov [ebx].VtMonitorContext.CsEsp,esp
mov [ecx].WtdFastWowEsp,esp
; return to vdm stack
push word ptr [ecx+2]
push word ptr 0
push word ptr [ecx]
lss esp,[esp]
; stick the API return value in the stack for kernel to pop.
; it's been in EAX since we called the thunk routine.
mov bx, sp ; for temporary addressing
mov dword ptr ss:[bx].vf_wAX, eax
; return to VDM, fake a far jump
push _savecs16
push _saveip16
mov eax,_saveeax ; Retore High parts of regs
mov ebx,_saveebx
mov ecx,_saveecx
mov edx,_saveedx
retf
;
wl60: ; Interrupts are disabled, turn them off in the virtual flags
;
_wowlock1:
lock and dword ptr ds:FIXED_NTVDMSTATE_LINEAR,NOT VDM_VIRTUAL_INTERRUPTS
jmp wl40
;
wl70: ; Interrupt came in, dispatch it
;
; translate the interrupt flag to the virtual interrupt flag
; if interrupts are disabled then blow it off
test [ebx].VtVdmContext.CsEFlags,dword ptr EFLAGS_INTERRUPT_MASK
jz wl60
_wowlock2:
lock or dword ptr ds:FIXED_NTVDMSTATE_LINEAR,dword ptr VDM_VIRTUAL_INTERRUPTS
push eax ; save API return value
push ebx
push ecx
;
; refresh VdmTib.VtVdmContext so DispatchInterrupts can party
;
; ecx points to CURRENTPTD->vpStack 16-bit ss:sp
mov si, [ecx + 2]
xor edi,edi
mov di, [ecx]
mov eax, _saveip16
mov dword ptr [ebx].VtVdmContext.CsEip, eax
mov eax, _savecs16
mov dword ptr [ebx].VtVdmContext.CsSegCs, eax
mov word ptr [ebx].VtVdmContext.CsSegSs, si
mov dword ptr [ebx].VtVdmContext.CsEsp, edi
stdCall _DispatchInterrupts
test dword ptr [ebx].VtVdmContext.CsEFlags,VDM_VIRTUAL_INTERRUPTS
jnz wl80
_wowlock3:
lock and dword ptr ds:FIXED_NTVDMSTATE_LINEAR,NOT VDM_VIRTUAL_INTERRUPTS
wl80: pop ecx ; points to ptd->vpStack
pop ebx
pop eax ; eax = API return value
mov _saveebp32, ebp
; restore the hiwords of esi, edi
pop ebp
pop edi
pop esi
mov dword ptr [ebx].VtMonitorContext.CsEsp,esp
mov [ecx].WtdFastWowEsp,esp
pushfd
and dword ptr [esp], 0ffffbfffH
popfd
;
; switch to 16-bit stack (probably an interrupt stack)
;
.errnz (CsEsp + 4 - CsSegSS)
lss esp, [ebx].VtVdmContext.CsEsp
;
; fake far jump to the 16-bit interrupt routine
;
push dword ptr [ebx].VtVdmContext.CsEflags
push dword ptr [ebx].VtVdmContext.CsSegCs
push dword ptr [ebx].VtVdmContext.CsEip
; stick the API return value in the stack for kernel to pop.
; it's been in EAX since we called the thunk routine.
; les bx, Stack16
les bx, [ecx]
mov dword ptr es:[bx].vf_wAX, eax
mov eax,_saveeax ; Retore High parts of regs
mov ebx,_saveebx
mov ecx,_saveecx
mov edx,_saveedx
iretd
wbegetretaddress:
pop eax
mov _saveip16, eax
pop edx
mov _savecs16, edx
jmp wbe10
SsyncCommDlg16to32:
push ecx ; save CURRENTPTD
push dword ptr [esi+cbOffwThunkCSIP] ; esi = pFrame
push 1 ; 16to32 flag
push eax ; pComDlgTD
call _Ssync_WOW_CommDlg_Structs@12
pop ecx
jmp SsyncContinue1
SsyncCommDlg32to16:
push ecx ; save CURRENTPTD
push eax ; preserve API return value
; build a flat ptr to pFrame
mov esi, [ecx]
and esi, 0FFFFh ; esi = 16-bit sp
; edi should already be set
mov edx, dword ptr [__imp__FlatAddress]
add esi, dword ptr [edx+edi*4] ; esi = pframe
push dword ptr [esi+cbOffwThunkCSIP] ; esi = pFrame
push 0 ; 32to16 flag
push ebx ; pComDlgTD
call _Ssync_WOW_CommDlg_Structs@12
pop eax
pop ecx
jmp SsyncContinue2
stdENDP _WOWBopEntry
cPublicProc _PostExceptionHandler,0
assume ds:_DATA
;
; Fixed up Exception registration pointer (just in case)
;
mov eax, fs:[PcExceptionList]
peh00:
cmp eax, esp
jb short @f
mov fs:[PcExceptionList], eax
xor eax, eax
jmp ApiReturnAddress
@@:
mov eax, [eax] ; move to next reg record
jmp short peh00
stdENDP _PostExceptionHandler
;++
;
; VOID
; W32SetExceptionContext (
; PCONTEXT ContextRecord
; );
;
; Routine Description:
;
; This functions updates exception context to our predefined
; post-exception handler such that if we continue exception
; execution the control will continue on our post-exception
; handling code.
;
; Arguments:
;
; ContextRecord - supplies a pointer to the exception context.
;
; Returns:
;
; Exception context updated.
;--
cPublicProc _W32SetExceptionContext,1
assume ds:_DATA
push fs ; I don;t think we need this, just in case
mov ecx,KGDT_R3_TEB OR RPL_MASK ; restore flat FS
mov fs,cx
mov ecx, fs:[PcTeb]
mov ecx, dword [ecx].TeVdm
pop fs
mov edx, [esp+4] ; (edx) = Context Record
mov ecx, dword ptr [ecx].VtMonitorContext.CsEsp
mov [edx].CsEsp, ecx
mov [edx].CsEip, offset FLAT:_PostExceptionHandler
stdRET _W32SetExceptionContext
stdENDP _W32SetExceptionContext
;++
;
; BOOLEAN
; IsW32WorkerException (
; VOID
; );
;
; Routine Description:
;
; This function checks if the exception occurred in WOW32 API.
;
; Arguments:
;
; None
;
; Returns:
;
; returns a BOOLEAN value to indicate if this is WOW32 API exception.
;
;--
cPublicProc _IsW32WorkerException, 0
assume ds:_DATA
mov ecx, fs:[PcTeb]
mov ecx, [ecx].TbWOW32Reserved
mov eax, dword ptr [ecx].WtdFastWowEsp
or eax, eax
jz @f ; FastWowEsp is zero, not worker exception
mov edx, [eax-4]
cmp edx, offset FLAT:ApiReturnAddress
; eax is still monitor esp, so it's nonzero
je short @f
xor eax, eax
@@: stdRET _IsW32WorkerException
stdENDP _IsW32WorkerException
_TEXT ends
page ,132
subttl "FastWOWCallbackCall"
;++
;
; Routine Description:
;
; This routine is a fast callback from WOW32 to 16-bit code.
; It assumes that the 16-bit stack pointer is already in Stack16.
; The caller must set this up with FastBopSetVDMStack() before
; calling this routine.
;
; WARNING: only the minimal set of registers are saved/restored
; as a speed optimization.
;
; Arguments:
;
; none
;
; Returns:
;
; nothing.
;
_TEXT SEGMENT
assume DS:FLAT
cPublicProc _FastWOWCallbackCall,0
; Save monitor general registers on monitor stack
; we'll pick em back up on the way out of FastWOWCallbackRet
push esi
push edi
push ebx
mov ebx, fs:[PcTeb]
mov ebx, dword ptr [ebx].TeVdm
; translate the interrupt flag to the virtual interrupt flag
test [ebx].VtVdmContext.CsEFlags,dword ptr EFLAGS_INTERRUPT_MASK
jz fe10
_wowlock4:
lock or dword ptr ds:FIXED_NTVDMSTATE_LINEAR,dword ptr VDM_VIRTUAL_INTERRUPTS
test dword ptr ds:FIXED_NTVDMSTATE_LINEAR,dword ptr VDM_INTERRUPT_PENDING
jnz fe70
jmp fe20
fe10:
_wowlock5:
lock and dword ptr ds:FIXED_NTVDMSTATE_LINEAR, NOT VDM_VIRTUAL_INTERRUPTS
fe20:
mov _saveebp32, ebp
mov ecx, fs:[PcTeb]
mov ecx, [ecx].TbWOW32Reserved ; move CURRENTPTD into ecx
mov [ebx].VtMonitorContext.CsEsp,esp
mov [ecx].WtdFastWowEsp,esp
pushfd
pop ecx
mov [ebx].VtMonitorContext.CsEflags,ecx
pushfd
and dword ptr [esp], 0ffffbfffH
popfd
; switch to vdm stack
push _savess16
push word ptr 0
push _savesp16
lss esp, [esp]
; before going to 16 bit, patch ebp to zero the high bits
; hijaak pro app is relying upon hiword of ebp being 0
and ebp, 0ffffh
; put flags, cs, and ip onto the 16-bit stack so we can iret to krnl286
push dword ptr [ebx].VtVdmContext.CsEflags
push _savecs16
push _saveip16
; return to krnl286
iretd
fe70: ; Interrupt pending, dispatch it
;
push ebx
push eax
;
; refresh VdmTib.VtVdmContext so DispatchInterrupts can party
;
mov eax, _saveip16
mov edx, _savecs16
mov dword ptr [ebx].VtVdmContext.CsEip, eax
mov dword ptr [ebx].VtVdmContext.CsSegCs, edx
mov ax, _savess16
xor edx,edx
mov dx, _savesp16
mov word ptr [ebx].VtVdmContext.CsSegSs, ax
mov dword ptr [ebx].VtVdmContext.CsEsp, edx
stdCall _DispatchInterrupts
test dword ptr [ebx].VtVdmContext.CsEFlags,VDM_VIRTUAL_INTERRUPTS
jnz fe80
_wowlock6:
lock and dword ptr ds:FIXED_NTVDMSTATE_LINEAR,NOT VDM_VIRTUAL_INTERRUPTS
fe80: pop eax
pop ebx
mov _saveebp32, ebp
mov ecx, fs:[PcTeb]
mov ecx, [ecx].TbWOW32Reserved
mov [ebx].VtMonitorContext.CsEsp,esp
mov [ecx].WtdFastWowEsp,esp
pushfd
pop ecx
mov [ebx].VtMonitorContext.CsEflags,ecx
pushfd
and dword ptr [esp], 0ffffbfffH
popfd
; switch to vdm stack
push word ptr [ebx].VtVdmContext.CsSegSs
push dword ptr [ebx].VtVdmContext.CsEsp
lss esp, [esp]
; put flags, cs, and ip onto the 16-bit stack so we can iret to krnl286
; before interrupts are dispatched, patch ebp to zero the high bits
; hijaak pro app is relying upon hiword of ebp being 0
and ebp, 0ffffh
push dword ptr [ebx].VtVdmContext.CsEflags
push dword ptr [ebx].VtVdmContext.CsSegCs
push dword ptr [ebx].VtVdmContext.CsEip
; return to krnl or interrupt routine
iretd
stdENDP _FastWOWCallbackCall
;++
;
; Routine Description:
;
; This routine is called by krnl286 upon returning from a
; callback.
;
; Arguments:
;
; none
;
; Returns:
;
; nothing
;
; WARNING: only the minimal set of registers are saved/restored
; as a speed optimization.
;
;
assume DS:Nothing,ES:Nothing,SS:Nothing
ALIGN 16
cPublicProc _FastWOWCallbackRet,0
mov ebx,KGDT_R3_DATA OR RPL_MASK
mov ds,bx
assume ds:_DATA
mov ebx, KGDT_R3_TEB OR RPL_MASK
mov fs, bx
mov ebx, fs:[PcTeb]
mov ebx, dword ptr [ebx].TeVdm
pushfd
pop eax
mov dword ptr [ebx].VtVdmContext.CsEFlags,eax
; the words at top of stack are the return address of our
; caller (krnl286).
; this address remains constant once krnl286 has booted.
; these three instructions are cheaper than saving it
cmp _fKernelCSIPFixed, 0
je fwcbrgetretaddress
add esp, 8 ; pop cs, ip
fwcbr10:
; refresh CURRENTPTD->vpStack with the current stack
;mov ecx, KGDT_R3_TEB OR RPL_MASK
;mov fs, cx
mov eax, fs:[PcTeb]
mov ecx, [eax+TbWOW32Reserved] ; move CURRENTPTD into ecx
mov _savess16,ss
mov _savesp16,sp
mov esi, [Stack16]
mov [ecx], esi
movzx esi,si
mov dword ptr [ebx].VtVdmContext.CsEsp, esi
mov word ptr [ebx].VtVdmContext.CsSegSs, ss
; switch Stacks
.errnz (CsEsp + 4 - CsSegSS)
lss esp, [ebx].VtMonitorContext.CsEsp
; Now running on Monitor stack
test dword ptr ds:FIXED_NTVDMSTATE_LINEAR,dword ptr VDM_VIRTUAL_INTERRUPTS
jz fl10
or [ebx].VtVdmContext.CsEFlags,dword ptr EFLAGS_INTERRUPT_MASK
jmp fl20
fl10: and dword ptr [ebx].VtVdmContext.CsEFlags, NOT EFLAGS_INTERRUPT_MASK
fl20:
; set up Monitor regs
mov esi, KGDT_R3_DATA OR RPL_MASK
mov es, si
; [LATER] do we really have to set up the GS?
mov gs, si
; set up Monitor general regs
mov ebp, _saveebp32
; return
pop ebx
pop edi
pop esi
ret
fwcbrgetretaddress:
pop eax
pop edx
mov _saveip16, eax
mov _savecs16, edx
jmp fwcbr10
stdENDP _FastWOWCallbackRet
;++
;
; Routine Description:
;
; This returns the current task's 16-bit ss:sp (vpStack)
;
; Arguments:
;
; none
;
; Returns:
;
; returns a 32-bit value that can be passed to GetVDMPointer
;
assume DS:_DATA,SS:Nothing
cPublicProc _FastBopVDMStack,0
mov eax, [Stack16]
stdRET _FastBopVDMStack
stdENDP _FastBopVDMStack
;++
;
; Routine Description:
;
; This sets the current task's 16-bit ss:sp (vpStack),
; used only when FASTSTACK is enabled.
;
; Arguments:
;
; vpStack (32-bit ss:sp, not a flat pointer)
;
; Returns:
;
; none
;
cPublicProc _FastBopSetVDMStack,1
mov eax, [esp+4]
mov [Stack16], eax
stdRET _FastBopSetVDMStack
stdENDP _FastBopSetVDMStack
_TEXT ends
end