        PAGE ,132
        TITLE UNITHUNK.ASM

;;;-------------------------------Module Prologue-----------------------
;;; Module Name: UNITHUNK.ASM
;;;
;;; This file contains a set of universal thunks for use by all of the
;;; DLLs in the sytem.
;;;
;;; Created 09 JUN 1993 by Scott T Jones, STJONES at BCRVMPC1
;;;
;;; Copyright (c) IBM Corporation 1993, 1994
;;;
;;; All Rights Reserved
;;;---------------------------------------------------------------------
;;; 19 MAR 94 -- STJONES -- New types added for 32->16 thunks
;;; 06 APR 94 -- STJONES -- Replace DosQueryMem with examination of TIB
;;; 12 APR 94 -- STJONES -- Rewrite interfaces for hand thunks
;;; 14 APR 94 -- STJONES -- Move THK32(ALLOC|FREE)STACK from THKRT.ASM
;;; 18 APR 94 -- STJONES -- Save/restore FS for all thunks
;;; 29 APR 94 -- STJONES -- Implement stack swapping using TLM, 81694
;;; 06 MAY 94 -- STJONES -- Minimize effects of Rogue Thunks
;;; 19 MAY 94 -- STJONES -- Add support for 32->16 PBool
;;; 23 MAY 94 -- STJONES -- Add support for Composite Pointers
;;; 24 MAY 94 -- STJONES -- Use LODSW for UT16 (U)SHORT parms, 84695
;;; 31 MAY 94 -- STJONES -- Change TILED encoding for cleaner parsing, 84930
;;; 07 JUN 94 -- STJONES -- Restore DS and ES after 16-bit calls, 84882
;;; 10 JUN 94 -- STJONES -- Add detection of exceptions and invalid ESP, 85821
;;; 30 JUN 94 -- STJONES -- Allow swapping of non-TIB stacks, 88475
;;; 01 JUL 94 -- STJONES -- Add special 32-bit Rogue processing, 88287
;;; 19 JUL 94 -- MARTYN  -- Reintegrate 88475 and 88287 and disallow
;;;                         ut_rogue stack to switch to system stack if
;;;                         the system stack count is non-zero.  89801
;;; 03 AUG 94 -- STJONES -- Disable the restore of BX for 16->32 thunks, 91889
;;; 06 AUG 94 -- STJONES -- Enable 64-bit thunk strings for 32->16 thunks
;;; 10 AUG 94 -- STJONES -- Add Exception Handling for DOS calls, 91295
;;;---------------------------------------------------------------------

;DEBUG   equ     1                       ; This enables counters

        .386

; External references

BUILD_UNITHUNK equ 1

        INCLUDE bsememf.inc
        INCLUDE bsetib.inc
        INCLUDE infoseg.inc
        INCLUDE struc.inc
        INCLUDE systhunk.inc

        THK32R2SS equ   04Ah

        EXTRN   DOS32FLATDS:ABS
        EXTRN   DOS32TIB:ABS

        EXTRN   localIS:WORD

        EXTRN   DOS32SELTOFLAT:NEAR
        EXTRN   DOS32FLATTOSEL:NEAR

        EXTRN   DOS32ALLOCMEM:NEAR
        EXTRN   DOS32FREEMEM:NEAR

        EXTRN   THK32XHNDLR:NEAR
        EXTRN   DOS32SETEXCEPTIONHANDLER:NEAR

        EXTRN   THK32ALLOCBLOCK:NEAR
        EXTRN   THK32FREEBLOCK:NEAR

MinStackSize equ 01000h                 ; Minimum stack size before realloc
NewStackSize equ 0F000h                 ; New stack size after realloc

CODE32  SEGMENT DWORD USE32 PUBLIC 'CODE'
        ASSUME  CS:FLAT,DS:FLAT,ES:FLAT
CODE32  ENDS

CODE16C SEGMENT DWORD USE16 PUBLIC 'CODE'
        ASSUME  CS:CODE16C
CODE16C ENDS

        ifdef   DEBUG

;-----------------------------------------------------------------------
; Global Debug Counters
;-----------------------------------------------------------------------

SHRDATA     SEGMENT DWORD USE16 PUBLIC 'DATA'

            ALIGN   4

            PUBLIC  UT_GlobalCounters
UT_GlobalCounters LABEL DWORD

            PUBLIC  UTGC_Thunk16
UTGC_Thunk16 DD     0                   ; Define 16->32 bit thunk entry counter

            PUBLIC  UTGC_Exit16
UTGC_Exit16 DD      0                   ; Define 16->32 bit thunk exit counter

            PUBLIC  UTGC_Thunk32
UTGC_Thunk32 DD     0                   ; Define 32->16 bit thunk entry counter

            PUBLIC  UTGC_Exit32
UTGC_Exit32 DD      0                   ; Define 32->16 bit thunk exit counter

            PUBLIC  HTGC_Thunk16
HTGC_Thunk16 DD     0                   ; Define 16->32 hand thunk entry counter

            PUBLIC  HTGC_Exit16
HTGC_Exit16 DD      0                   ; Define 16->32 hand thunk exit counter

            PUBLIC  HTGC_Thunk32
HTGC_Thunk32 DD     0                   ; Define 32->16 hand thunk entry counter

            PUBLIC  HTGC_Exit32
HTGC_Exit32 DD      0                   ; Define 32->16 hand thunk exit counter

            PUBLIC  UTGC_AllocParm
UTGC_AllocParm DD   0                   ; Define Alloc Parm counter

            PUBLIC  UTGC_FreeParm
UTGC_FreeParm DD    0                   ; Define Free Parm counter

            PUBLIC  UTGC_AllocData
UTGC_AllocData DD   0                   ; Define Alloc Data counter

            PUBLIC  UTGC_FreeData
UTGC_FreeData DD    0                   ; Define Free Data counter

            PUBLIC  UTGC_AllocMem
UTGC_AllocMem DD    0                   ; Define THK32ALLOCMEM counter

            PUBLIC  UTGC_FreeMem
UTGC_FreeMem DD     0                   ; Define THK32FREEMEM counter

            PUBLIC  UTGC_DosAllocMem
UTGC_DosAllocMem DD 0                   ; Define DosAllocMem counter

            PUBLIC  UTGC_DosFreeMem
UTGC_DosFreeMem DD  0                   ; Define DosFreeMem counter

            PUBLIC  UTGC_ThkAllocStack
UTGC_ThkAllocStack DD 0                 ; Define THK32ALLOCSTACK counter

            PUBLIC  UTGC_ThkFreeStack
UTGC_ThkFreeStack DD 0                  ; Define THK32FREESTACK counter

            PUBLIC  UTGC_AllocStack
UTGC_AllocStack DD  0                   ; Define UT_SwapStack Alloc counter

            PUBLIC  UTGC_SwapStack
UTGC_SwapStack DD   0                   ; Define UT_SwapStack Swap counter

            PUBLIC  UTGC_ThunkDepth
UTGC_ThunkDepth DD  0                   ; Define Thunk Depth counter

            PUBLIC  UTGC_RogueDepth
UTGC_RogueDepth DD  0                   ; Define Rogue Depth counter

            PUBLIC  UTGC_NonTIBStack
UTGC_NonTIBStack DD 0                   ; Define Non-TIB Stack counter

            PUBLIC  UTGC_Exceptions
UTGC_Exceptions DD  0                   ; Define Exception counter

SHRDATA     ENDS

;-----------------------------------------------------------------------
; Local Debug Counters
;-----------------------------------------------------------------------

DATA32      SEGMENT DWORD USE32 PUBLIC 'DATA'

            ALIGN   4

            PUBLIC  UT_LocalCounters
UT_LocalCounters LABEL DWORD

            PUBLIC  UTLC_Thunk16
UTLC_Thunk16 DD     0                   ; Define 16->32 bit thunk entry counter

            PUBLIC  UTLC_Exit16
UTLC_Exit16 DD      0                   ; Define 16->32 bit thunk exit counter

            PUBLIC  UTLC_Thunk32
UTLC_Thunk32 DD     0                   ; Define 32->16 bit thunk entry counter

            PUBLIC  UTLC_Exit32
UTLC_Exit32 DD      0                   ; Define 32->16 bit thunk exit counter

            PUBLIC  HTLC_Thunk16
HTLC_Thunk16 DD     0                   ; Define 16->32 hand thunk entry counter

            PUBLIC  HTLC_Exit16
HTLC_Exit16 DD      0                   ; Define 16->32 hand thunk exit counter

            PUBLIC  HTLC_Thunk32
HTLC_Thunk32 DD     0                   ; Define 32->16 hand thunk entry counter

            PUBLIC  HTLC_Exit32
HTLC_Exit32 DD      0                   ; Define 32->16 hand thunk exit counter

            PUBLIC  UTLC_AllocParm
UTLC_AllocParm DD   0                   ; Define Alloc Parm counter

            PUBLIC  UTLC_FreeParm
UTLC_FreeParm DD    0                   ; Define Free Parm counter

            PUBLIC  UTLC_AllocData
UTLC_AllocData DD   0                   ; Define Alloc Data counter

            PUBLIC  UTLC_FreeData
UTLC_FreeData DD    0                   ; Define Free Data counter

            PUBLIC  UTLC_AllocMem
UTLC_AllocMem DD    0                   ; Define THK32ALLOCMEM counter

            PUBLIC  UTLC_FreeMem
UTLC_FreeMem DD     0                   ; Define THK32FREEMEM counter

            PUBLIC  UTLC_DosAllocMem
UTLC_DosAllocMem DD 0                   ; Define DosAllocMem counter

            PUBLIC  UTLC_DosFreeMem
UTLC_DosFreeMem DD  0                   ; Define DosFreeMem counter

            PUBLIC  UTLC_ThkAllocStack
UTLC_ThkAllocStack DD 0                 ; Define THK32ALLOCSTACK counter

            PUBLIC  UTLC_ThkFreeStack
UTLC_ThkFreeStack DD 0                  ; Define THK32FREESTACK counter

            PUBLIC  UTLC_AllocStack
UTLC_AllocStack DD  0                   ; Define UT_SwapStack Alloc counter

            PUBLIC  UTLC_SwapStack
UTLC_SwapStack DD   0                   ; Define UT_SwapStack Swap counter

            PUBLIC  UTLC_ThunkDepth
UTLC_ThunkDepth DD  0                   ; Define Thunk Depth counter

            PUBLIC  UTLC_RogueDepth
UTLC_RogueDepth DD  0                   ; Define Rogue Depth counter

            PUBLIC  UTLC_NonTIBStack
UTLC_NonTIBStack DD 0                   ; Define Non-TIB Stack counter

            PUBLIC  UTLC_Exceptions
UTLC_Exceptions DD  0                   ; Define Exception counter

DATA32      ENDS

        endif

;=======================================================================
; Data used by UT_SwapStack
;=======================================================================

;CTFORMAT,,16

DATA32          SEGMENT DWORD USE32 PUBLIC 'DATA'

                ALIGN   4

                PUBLIC  ulThunkAppType
ulThunkAppType  DD      0

                PUBLIC  _pThunkTLM
_pThunkTLM      LABEL   DWORD

                PUBLIC  pThunkTLM
pThunkTLM       DD      0

                PUBLIC  pThunkStacks
pThunkStacks    DD      0

DATA32          ENDS

;-----------------------------------------------------------------------
; Thunk Stack descriptor fields
;-----------------------------------------------------------------------

TS_InitialESP   equ     NewStackSize-28h ; Initial value for ESP

TS_Signature    equ     NewStackSize-28h ; "ThunkTIB" signature

TS_NextStack    equ     NewStackSize-20h ; Pointer to next thread stack
TS_ThreadId     equ     NewStackSize-1Ch ; Thread Id

TS_ThunkDepth   equ     NewStackSize-18h ; Count of nested Thunks
TS_RogueDepth   equ     NewStackSize-14h ; Count of nested Rogue Thunks

TS_SystemESP    equ     NewStackSize-10h ; Current ESP on System Stack
TS_UserESP      equ     NewStackSize-0Ch ; Current ESP on User Stack

TS_UserStack    equ     NewStackSize-08h ; User Stack pointer from TIB
TS_UserLimit    equ     NewStackSize-04h ; User Stack limit from TIB

;CTFORMAT,

CODE32  SEGMENT DWORD USE32 PUBLIC 'CODE'
        ASSUME  CS:FLAT,DS:FLAT,ES:FLAT,FS:NOTHING,GS:NOTHING,SS:FLAT

;=======================================================================
; UT_SwapStack handles all stack swapping at both the beginning and end
;    of a thunk.  It allocates thread stacks and thread local memory,
;    as needed.  It checks the TS_Direction flag to determine if we are
;    switching to or from the user's stack.  On exit, the stack pointer
;    has already been changed to the new stack.
;=======================================================================

;-----------------------------------------------------------------------
; Allocate Thread Local Memory for this Process
;   This routine also performs process identification.
;   EAX not saved since this is never done when returning up the stack.
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT_AllocTLM
UT_AllocTLM:

        mov     es,localIS

        mov     ax,es:[LIS_DS]

        push    ds
        pop     es

        cmp     ax,DOS32FLATDS
        jnz     SHORT @F                ; Initial DS is not FLAT segment

        or      BYTE PTR ulThunkAppType+3,1 ; Indicate 32-bit application
@@:
        lea     eax,pThunkTLM
        push    eax
        push    DWORD PTR 1

        EXTRN   DosAllocThreadLocalMemory:NEAR
        call    DosAllocThreadLocalMemory
        add     esp,8

        jmp     SHORT UT_AllocTS

;-----------------------------------------------------------------------
; Initialize Thread Local Memory for this Thread
;   EAX not saved since this is never done when returning up the stack.
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT_InitTLM
UT_InitTLM:

        mov     esi,pThunkStacks

        test    esi,esi
        jz      SHORT UT_AllocTS        ; No thread stacks allocated

        mov     eax,fs:[ tib_ptib2 ]
        mov     eax,[ eax + tib2_ultid ] ; Get current TID

        PUBLIC  UT_StackSearch
UT_StackSearch:

        cmp     [ esi + TS_ThreadID ],eax
        je      SHORT UT_InitTS         ; Reuse a stack from a dead thread

        mov     esi,[ esi + TS_NextStack ] ; Get pointer to next Thread Stack

        test    esi,esi
        jnz     SHORT UT_StackSearch    ; Check for matching TID

        PUBLIC  UT_AllocTS
UT_AllocTS:

        ifdef   DEBUG

            inc     UTGC_AllocStack
            inc     UTLC_AllocStack

        endif

        mov     esi,pThunkTLM

        push    0053h                   ; Alloc Flags: Tile+Commit+Read+Write
        push    NewStackSize            ; # of bytes to allocate
        push    esi                     ; Addr of new stack pointer
        call    DOS32ALLOCMEM
        add     esp,12

        test    eax,eax
        jnz     UT_SwapExit

        mov     esi,[ esi ]             ; Get new stack pointer

; Add the new stack to the head of the Stack Chain, after touching the
; stack to ensure that no page fault occurs to interrupt the chaining.
; If interrupted, the worst that can happen is that the stack of a dead
; thread will not get reused.  Therefore, semaphores are not worthwhile.

        mov     eax,fs:[ tib_ptib2 ]
        mov     eax,[ eax + tib2_ultid ] ; Get current TID
        mov     [ esi + TS_ThreadID ],eax ; Save TID in TS

        mov     eax,esi
        xchg    pThunkStacks,eax
        mov     [ esi + TS_NextStack ],eax ; Add new stack to Stack Chain

        mov     DWORD PTR [ esi + TS_Signature + 0 ],'T' + ( 'h' SHL 8 ) + ( 'u' SHL 16 ) + ( 'n' SHL 24 )
        mov     DWORD PTR [ esi + TS_Signature + 4 ],'k' + ( 'T' SHL 8 ) + ( 'I' SHL 16 ) + ( 'B' SHL 24 )

        PUBLIC  UT_InitTS
UT_InitTS:

        mov     eax,pThunkTLM
        mov     [ eax ],esi             ; Set Thread Stack pointer

        lea     eax,[ esi + TS_InitialESP ]
        mov     [ esi + TS_SystemESP ],eax ; Initialize System ESP

        sub     eax,eax
        mov     [ esi + TS_ThunkDepth ],eax ; Count of nested Thunks
        mov     [ esi + TS_RogueDepth ],eax ; Count of nested Rogue Thunks

        jmp     UT_HaveTS

;-----------------------------------------------------------------------
; Ensure that the depth counters are correct when returning from calls
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT_ValidateDepth
UT_ValidateDepth:

        mov     edx,[ ebp + OS_THUNKDEPTH ]
        and     edx,00FFFFFFh           ; Mask off thunk flags

        test    BYTE PTR [ ebp + OS_THUNKFLAGS ],TF_ROGUEDEPTH
        jz      SHORT UT_ValidateThunkDepth

        ifdef   DEBUG

            cmp     edx,[ esi + TS_RogueDepth ]
            je      SHORT @F

            inc     UTGC_Exceptions
            inc     UTLC_Exceptions
@@:
        endif

        mov     [ esi + TS_RogueDepth ],edx ; Restore Rogue Depth

        jmp     UT_ValidDepth

        PUBLIC  UT_ValidateThunkDepth
UT_ValidateThunkDepth:

        ifdef   DEBUG

            cmp     DWORD PTR [ esi + TS_RogueDepth ],0
            je      SHORT @F
            cmp     edx,[ esi + TS_ThunkDepth ]
            je      SHORT @F

            inc     UTGC_Exceptions
            inc     UTLC_Exceptions
@@:
        endif

        mov     DWORD PTR [ esi + TS_RogueDepth ],0 ; Set Rogue Depth
        mov     [ esi + TS_ThunkDepth ],edx ; Restore Thunk Depth

        jmp     SHORT UT_ValidDepth

;-----------------------------------------------------------------------
; Ensure that DS points to FLAT
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT_RestoreDS
UT_RestoreDS:
        push    DOS32FLATDS
        pop     ds
        jmp     SHORT UT_HaveDS

;-----------------------------------------------------------------------
; Ensure that ES points to FLAT
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT_RestoreES
UT_RestoreES:
        push    DOS32FLATDS
        pop     es
        jmp     SHORT UT_HaveES

;-----------------------------------------------------------------------
; Ensure that FS points to the TIB
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT_RestoreFS
UT_RestoreFS:
        push    DOS32TIB
        pop     fs
        jmp     SHORT UT_HaveFS

;-----------------------------------------------------------------------
; Swap the Stack for this Thread
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT_SwapStack
UT_SwapStack PROC NEAR

        mov     edx,ds
        cmp     edx,DOS32FLATDS
        jnz     SHORT UT_RestoreDS

        PUBLIC  UT_HaveDS
UT_HaveDS:

        mov     edx,es
        cmp     edx,DOS32FLATDS
        jnz     SHORT UT_RestoreES

        PUBLIC  UT_HaveES
UT_HaveES:

        mov     edx,fs
        cmp     edx,DOS32TIB
        jnz     SHORT UT_RestoreFS

        PUBLIC  UT_HaveFS
UT_HaveFS:
        mov     edx,cs
        test    dl,1                    ; Test assumes no ring 1
        jz      UT_SwapExit             ; Only swap ring 3 stacks

        ifdef   DEBUG

            inc     UTGC_SwapStack
            inc     UTLC_SwapStack

        endif

        mov     esi,pThunkTLM
        test    esi,esi
        jz      UT_AllocTLM             ; TLM not yet allocated

        PUBLIC  UT_HaveTLM
UT_HaveTLM:
        mov     esi,[ esi ]             ; Get Thread Stack pointer
        test    esi,esi
        jz      UT_InitTLM              ; Thread Stack not found

        PUBLIC  UT_HaveTS
UT_HaveTS:

        cmp     DWORD PTR [ ebp + OS_PSTACK ],0
        jnz     UT_ValidateDepth        ; Validate only if returning from call

        PUBLIC  UT_ValidDepth
UT_ValidDepth:

        cmp     DWORD PTR [ esi + TS_RogueDepth ],0
        jnz     UT_RogueStack           ; Continue processing Rogue Stacks

        mov     edx,esp
        sub     edx,esi
        cmp     edx,NewStackSize
        jb      UT_SystemStack          ; Current ESP within System Stack

        cmp     DWORD PTR [ esi + TS_ThunkDepth ],0
        jz      SHORT UT_UserStack      ; Do not validate bounds on first thunk

        cmp     DWORD PTR [ ebp + OS_PSTACK ],0
        jnz     SHORT UT_UserStack      ; Do not validate bounds on return

        cmp     esp,[ esi + TS_UserStack ]
        jb      UT_RogueStack           ; Current ESP outside User Stack
        cmp     esp,[ esi + TS_UserLimit ]
        jae     SHORT UT_RogueStack     ; Current ESP outside User Stack

;-----------------------------------------------------------------------
; Currently executing on User Stack
;-----------------------------------------------------------------------

        PUBLIC  UT_UserStack
UT_UserStack:

        ifdef   DEBUG

            cmp     esp,fs:[ tib_pstack ]
            jb      SHORT UT_NonTIBStack ; Current ESP below TIB stack
            cmp     esp,fs:[ tib_pstacklimit ]
            jb      SHORT @F            ; Current ESP within TIB stack

            PUBLIC  UT_NonTIBStack
UT_NonTIBStack:

            cmp     DWORD PTR [ esi + TS_ThunkDepth ],0
            jnz     SHORT @F            ; Count only the first level of thunk

            inc     UTGC_NonTIBStack
            inc     UTLC_NonTIBStack
@@:
        endif

        cmp     DWORD PTR [ ebp + OS_PSTACK ],0
        jnz     SHORT @F                ; Skip mode validation when returning

        mov     dl,[ ebp + OS_THUNKFLAGS ]
        xor     dl,BYTE PTR ulThunkAppType + 3
        test    dl,TF_DIRECTION
        jnz     SHORT UT_RogueStack     ; Wrong Mode ==> Wrong stack in use
@@:
        pop     edx                     ; Get return address in EDX
        xchg    [ ebp + OS_PSTACK ],edx ; Save return address / Get old ESP

        test    edx,edx
        jz      SHORT @F                ; Skip restore when moving down stack

        mov     esp,edx                 ; Restore ESP when returning up stack

        dec     DWORD PTR [ esi + TS_ThunkDepth ]
        jmp     SHORT UT_SetSystemStack
@@:
        inc     DWORD PTR [ esi + TS_ThunkDepth ]

        PUBLIC  UT_SetSystemStack
UT_SetSystemStack:

        mov     [ esi + TS_UserESP ],esp ; Save User ESP in TS

        mov     edx,fs:[ tib_pstack ]
        mov     [ esi + TS_UserStack ],edx
        mov     edx,fs:[ tib_pstacklimit ]
        mov     [ esi + TS_UserLimit ],edx ; Save TIB stack limits in TS

        mov     esp,[ esi + TS_SystemESP ] ; Restore System ESP from TS

        mov     fs:[ tib_pstack ],esi
        lea     edx,[ esi + TS_InitialESP ]
        mov     fs:[ tib_pstacklimit ],edx ; Set System Stack limits in TIB

        jmp     UT_SwapComplete

;-----------------------------------------------------------------------
; Currently executing on Rogue Stack
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT_RogueStack
UT_RogueStack:

        pop     edx                     ; Get return address in EDX
        xchg    [ ebp + OS_PSTACK ],edx ; Save return address / Get old ESP

        test    edx,edx
        jz      SHORT @F                ; Skip restore when moving down stack

        mov     esp,edx                 ; Restore ESP when returning up stack
        dec     DWORD PTR [ esi + TS_RogueDepth ]
        jnz     UT_SwapComplete         ; Recursive Rogue processing #88287 =>

        cmp     DWORD PTR [ esi + TS_ThunkDepth ],0
        jnz     UT_SwapComplete         ; <= #89801
                                        ; Already thunked at least once and
                                        ; we are currently on either the
                                        ; system stack or a rogue stack,
                                        ; therefore we cannot switch to the
                                        ; user stack, yet.

        test    BYTE PTR ulThunkAppType + 3,TF_DIRECTION
        jnz     SHORT UT_SetUserStack   ; Restore user stack for 32-bit apps

        cmp     WORD PTR [ esi + TS_UserESP ],1000h ; Check the 16-bit threshold
        jbe     SHORT UT_SetUserStack   ; Switch back to the user stack

        jmp     UT_SwapComplete
@@:
        inc     DWORD PTR [ esi + TS_RogueDepth ]
        cmp     DWORD PTR [ esi + TS_RogueDepth ],1
        jne     SHORT UT_SwapComplete   ; Recursive Rogue processing

        cmp     DWORD PTR [ esi + TS_ThunkDepth ],0
        jnz     SHORT UT_SwapComplete   ; <= #89801
                                        ; We have already switched to the
                                        ; system stack at least once, so we
                                        ; have to stay where we are.

        test    BYTE PTR ulThunkAppType + 3,TF_DIRECTION
        jnz     UT_SetSystemStack       ; Use system stack for 32-bit apps

        cmp     sp,1000h                ; Check old 16-bit stack threshold
        jbe     UT_SetSystemStack       ; Use system stack, if too small

        mov     [ esi + TS_UserESP ],esp ; Save User ESP for threshold test
        jmp     SHORT UT_SwapComplete   ; <= #88287

;-----------------------------------------------------------------------
; Currently executing on System Stack
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT_SystemStack
UT_SystemStack:

        cmp     DWORD PTR [ ebp + OS_PSTACK ],0
        jnz     SHORT @F                ; Skip mode validation when returning

        mov     dl,[ ebp + OS_THUNKFLAGS ]
        xor     dl,BYTE PTR ulThunkAppType + 3
        test    dl,TF_DIRECTION
        jz      UT_RogueStack     ; Wrong Mode ==> Wrong stack in use

@@:
        pop     edx                     ; Get return address in EDX
        xchg    [ ebp + OS_PSTACK ],edx ; Save return address / Get old ESP

        test    edx,edx
        jz      SHORT @F                ; Skip restore when moving down stack

        mov     esp,edx                 ; Restore ESP when returning up stack

        dec     DWORD PTR [ esi + TS_ThunkDepth ]
        jmp     SHORT UT_SetUserStack
@@:
        inc     DWORD PTR [ esi + TS_ThunkDepth ]

        PUBLIC  UT_SetUserStack
UT_SetUserStack:

        mov     [ esi + TS_SystemESP ],esp ; Save System ESP in TS

        mov     edx,[ esi + TS_UserStack ]
        mov     fs:[ tib_pstack ],edx
        mov     edx,[ esi + TS_UserLimit ]
        mov     fs:[ tib_pstacklimit ],edx ; Restore TIB stack limits from TS

        mov     esp,[ esi + TS_UserESP ] ; Restore User ESP from TS

;-----------------------------------------------------------------------
; Update statistics and return to caller
;-----------------------------------------------------------------------

        PUBLIC  UT_SwapComplete
UT_SwapComplete:

        mov     edx,esp
        xchg    [ ebp + OS_PSTACK ],edx ; Save ESP and get Return Address
        push    edx                     ; Push Return Address on New Stack

        ifdef   DEBUG

            mov     edx,[ esi + TS_ThunkDepth ]
            cmp     edx,UTGC_ThunkDepth
            jbe     SHORT @F

            mov     UTGC_ThunkDepth,edx
@@:
            cmp     edx,UTLC_ThunkDepth
            jbe     SHORT @F

            mov     UTLC_ThunkDepth,edx

            test    edx,edx
            jns     SHORT @F

            int     3                   ; Stop if ThunkDepth goes negative
@@:
            mov     edx,[ esi + TS_RogueDepth ]
            cmp     edx,UTGC_RogueDepth
            jbe     SHORT @F

            mov     UTGC_RogueDepth,edx
@@:
            cmp     edx,UTLC_RogueDepth
            jbe     SHORT @F

            mov     UTLC_RogueDepth,edx

            test    edx,edx
            jns     SHORT @F

            int     3                   ; Stop if RogueDepth goes negative
@@:
        endif

        mov     dl,[ ebp + OS_THUNKFLAGS ]

        cmp     DWORD PTR [ esi + TS_RogueDepth ],0
        jz      SHORT @F

        or      dl,TF_ROGUEDEPTH
        shl     edx,24
        or      edx,[ esi + TS_RogueDepth ] ; Save current Rogue Depth, if any

        jmp     SHORT UT_SaveThunkDepth
@@:
        shl     edx,24
        or      edx,[ esi + TS_ThunkDepth ] ; Save current Thunk Depth

        PUBLIC  UT_SaveThunkDepth
UT_SaveThunkDepth:

        mov     [ ebp + OS_THUNKDEPTH ],edx

        PUBLIC  UT_SwapExit
UT_SwapExit:

        ret

UT_SwapStack ENDP

;-----------------------------------------------------------------------
; THK32ALLOCMEM -- Allocate memory for a thunk
;
; On entry: ECX = size to allocate
; On exit:  EAX = return code, 0 = success
;           EDI = pointer to allocated memory
;           ZF  = result of TEST EAX,EAX
;-----------------------------------------------------------------------

        PUBLIC  THK32ALLOCMEM
THK32ALLOCMEM PROC NEAR

        ifdef   DEBUG

            inc     UTGC_AllocMem
            inc     UTLC_AllocMem

        endif

        cmp     ecx,128
        ja      SHORT @F

        call    THK32ALLOCBLOCK
        test    eax,eax                 ; Set flags before returning
        ret

        ALIGN   4
@@:
        ifdef   DEBUG

            inc     UTGC_DosAllocMem
            inc     UTLC_DosAllocMem

        endif

        push    ecx
        push    eax                     ; Make room for new pointer
        mov     eax,esp                 ; Addr of new data pointer
        push    0053h                   ; Alloc Flags: Tile+Commit+Read+Write
        push    ecx                     ; # of bytes to allocate
        push    eax                     ; Addr of new data pointer
        call    DOS32ALLOCMEM
        add     esp,12
        pop     edi                     ; Get new pointer in edi
        pop     ecx
        test    eax,eax                 ; Set flags before returning
        ret

THK32ALLOCMEM ENDP

;-----------------------------------------------------------------------
; THK32FREEMEM -- Free memory for a thunk
;
; On entry: ECX = size to free
;           ESI = pointer to allocated memory
; On exit:  EAX = return code, 0 = success
;-----------------------------------------------------------------------

        PUBLIC  THK32FREEMEM
THK32FREEMEM PROC NEAR

        ifdef   DEBUG

            inc     UTGC_FreeMem
            inc     UTLC_FreeMem

        endif

        cmp     ecx,128
        ja      SHORT @F

        call    THK32FREEBLOCK
        ret

        ALIGN   4
@@:
        ifdef   DEBUG

            inc     UTGC_DosFreeMem
            inc     UTLC_DosFreeMem

        endif

        push    esi
        call    DOS32FREEMEM
        add     esp,4
        ret

THK32FREEMEM ENDP

;-----------------------------------------------------------------------
; THK32ALLOCSTACK unconditionally allocates a new ring 3 stack and
;    updates the TIB to reflect the bounds of this new stack.
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  THK32ALLOCSTACK
THK32ALLOCSTACK PROC NEAR

        push    eax                     ; Make room for return pointer
        mov     eax,esp

        push    0053h                   ; Alloc Flags: Tile+Commit+Read+Write
        push    NewStackSize            ; # of bytes to allocate
        push    eax                     ; Addr of new stack pointer
        call    DOS32ALLOCMEM
        add     esp,12

        test    eax,eax
        pop     eax

        .if     < z >                   ; Allocation successful

            ifdef   DEBUG

                inc     UTGC_ThkAllocStack
                inc     UTLC_ThkAllocStack

            endif

            push    ecx
            push    fs

            push    DOS32TIB
            pop     fs                  ; Ensure correct TIB selector

            mov     ecx,fs:[ tib_pstack ]
            mov     fs:[ tib_pstack ],eax ; Save new stack start in TIB

            lea     eax,[ eax + TS_UserStack ] ; Get new value of ESP
            mov     [ eax ],ecx         ; Save old stack start in new stack

            mov     ecx,fs:[ tib_pstacklimit ]
            mov     fs:[ tib_pstacklimit ],eax ; Save new stack limit in TIB

            mov     [ eax + 4 ],ecx     ; Save old stack limit in new stack

            pop     fs
            pop     ecx

        .else                           ; Allocation failed

            lea     eax,[ esp + 4 ]     ; Get value of ESP before call

        .endif

        ret

THK32ALLOCSTACK ENDP

;-----------------------------------------------------------------------
; THK32FREESTACK frees a stack allocated by THK32ALLOCSTACK and restores
;    the stack boundaries in the TIB.
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  THK32FREESTACK
THK32FREESTACK PROC NEAR

        push    eax                     ; Save the return code

        .if     < cx eq < TS_UserStack > > and ; Stack would be 64K aligned if size=8K

        lea     eax,[ esp + 8 ]         ; Get value of ESP before call

        .if     < ecx ne eax >          ; Stack is not same as callers stack

            ifdef   DEBUG

                inc     UTGC_ThkFreeStack
                inc     UTLC_ThkFreeStack

            endif

            push    fs

            push    DOS32TIB
            pop     fs                  ; Ensure correct TIB selector

            mov     eax,[ ecx ]
            mov     fs:[ tib_pstack ],eax

            mov     eax,[ ecx + 4 ]
            mov     fs:[ tib_pstacklimit ],eax ; Restore the TIB stack limits

            sub     cx,cx               ; Calculate base address of stack
            push    ecx                 ; Address to free
            call    DOS32FREEMEM        ; Free stack from THK32ALLOCSTACK
            pop     ecx                 ; Equivalent to ADD ESP,4

            pop     fs

        .endif

        pop     eax                     ; Restore the return code
        ret

THK32FREESTACK ENDP

;-----------------------------------------------------------------------
; THK32STRLEN return in ECX then length of the string referenced by ESI.
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  THK32STRLEN
THK32STRLEN PROC NEAR
                                        ;Assume that ESI holds address
        push    edi
        mov     edi,esi
        xor     ecx,ecx
        mov     eax,ecx
        dec     ecx
        repne   scasb
        not     ecx                     ;ECX gets length including zero byte
        pop     edi
        ret

THK32STRLEN ENDP

CODE32  ENDS

;;;---------------------------------------------------------------------
;;; THK16_UNITHUNK:  Universal 16->32 Thunk with up to 14 parameters and
;;;                  a return value from the set of (U)Long, Pointer,
;;;                  Short, and UShort, where the Pointer references
;;;                  a structure which does not change going from 16
;;;                  to 32 bits.
;;;
;;; On Entry:  EAX = Destination of Thunk
;;;            EDX = Description of Thunk
;;;            where bits  3  thru  0  = Number of parameters
;;;                  bits  5  thru  4  = Type code of first parameter
;;;                  ...
;;;                  bits  n  thru n-1 = Type code of last parameter
;;;                  bits n+2 thru n+1 = Type code of return value
;;;
;;; Type codes:  0 = (U)Long, 1 = Pointer, 2 = UShort, 3 = Short,
;;;                  so that bit 1 indicates WORD vs DWORD parameter
;;;
;;; Note:  If the number of parameters is 15, an extra boolean of 0 or 1,
;;;        taken from the next bit, will be pushed and the 5 bits will be
;;;        discarded.  The remaining bits will be used as described above.
;;;        The maximum number of parmeters for a thunk is usually 13.
;;;        If the return value is (U)Long, up to 14 parameters can be
;;;        processed due to the side effects of shifting.
;;;---------------------------------------------------------------------

CODE32  SEGMENT
        ASSUME  CS:FLAT,DS:FLAT,ES:FLAT

;-----------------------------------------------------------------------
; Use a count of 15 to indicate the need for an extra parameter of 0 or
;   1, from the next bit.  These 5 bits must then be discarded.
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT16_AddedBool
UT16_AddedBool:

        mov     cl,dl
        and     cl,1
        push    ecx                     ; Push boolean

        shr     edx,1
        jmp     SHORT UT16_CheckBool    ; Check for another BOOL

;-----------------------------------------------------------------------
; Code executed after switching to 32-bit mode
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT16_TARGET
UT16_TARGET LABEL FAR

        call    UT_SwapStack            ; Sets DS, ES, and FS for 32-bit mode

        ifdef   DEBUG

            inc     UTGC_Thunk16
            inc     UTLC_Thunk16

        endif

        lea     esi,[ ebp + OS_PARMS ]  ; Get pointer to last parameter,
                                        ;   which is first on the stack
        mov     edx,[ ebp + OS16_STRING ]

        PUBLIC  UT16_CheckBool
UT16_CheckBool:

        mov     ecx,edx
        shr     edx,4                   ; EDX = current thunk string
        and     ecx,0Fh                 ; ECX = count of parameters
        jz      SHORT UT16_Call

        cmp     cl,0Fh
        je      UT16_AddedBool          ; Count of 15 pushes 0/1 from next bit

        ALIGN   4                       ; Possible waste to ensure better loop

        PUBLIC  UT16_Loop
UT16_Loop:

        test    dl,03h                  ; Check for (U)Long vs all others
        jnz     SHORT UT16_PointerParm

        ;lodsd                           ; Get one DWORD of parameters
        mov     eax, dword ptr[esi]
        add     esi, 4

        push    eax                     ; Transfer (U)Long as is

        shr     edx,2
        dec     ecx
        jnz     SHORT UT16_Loop
        jmp     SHORT UT16_Call

        ALIGN   4

        PUBLIC  UT16_PointerParm
UT16_PointerParm:

        test    dl,02h                  ; Check for Pointer vs all others
        jnz     SHORT UT16_WordParm

        ;lodsd                           ; Get one DWORD of parameters
        mov     eax, dword ptr[esi]
        add     esi, 4

        push    ecx
        push    edx
        call    DOS32SELTOFLAT
        pop     edx
        pop     ecx

        push    eax                     ; Convert SEL:OFF to FLAT

        shr     edx,2
        dec     ecx
        jnz     SHORT UT16_Loop
        jmp     SHORT UT16_Call

        ALIGN   4

        PUBLIC  UT16_WordParm
UT16_WordParm:

        ;lodsw                           ; Get one WORD of parameters
        mov     ax, word ptr[esi]
        add     esi, 2

        test    dl,01h                  ; Check for UShort vs Short
        jnz     SHORT UT16_ShortParm

        and     eax,0000FFFFh
        push    eax                     ; Convert UShort to ULong

        shr     edx,2
        dec     ecx
        jnz     SHORT UT16_Loop
        jmp     SHORT UT16_Call

        ALIGN   4

        PUBLIC  UT16_ShortParm
UT16_ShortParm:

        movsx   eax,ax
        push    eax                     ; Convert Short to Long

        shr     edx,2
        dec     ecx
        jnz     SHORT UT16_Loop

        ALIGN   4

        PUBLIC  UT16_Call
UT16_Call:

        shl     dl,4
        or      BYTE PTR [ ebp + OS_THUNKFLAGS ],dl ; Save type of return

        call    [ ebp + OS16_TARGET ]

        mov     dl,[ ebp + OS_THUNKFLAGS ]
        shr     dl,4
        cmp     dl,01h                  ; Return is pointer
        jnz     SHORT UT16_Exit

        call    DOS32FLATTOSEL          ; Convert return value to SEL:OFF

        PUBLIC  UT16_Exit
UT16_Exit:                              ; Uncommon jump target not aligned

        mov     ecx,esi
        lea     edx,[ ebp + OS_PARMS ]
        sub     ecx,edx                 ; Get size of parameters in ECX

        ifdef   DEBUG

            inc     UTGC_Exit16
            inc     UTLC_Exit16

        endif

        PUBLIC  HT16_Exit
HT16_Exit:

        call    UT_SwapStack

        lss     esp,[ ebp + OS16_RESTORE ] ; Restore SS:ESP

        jmp     FAR PTR UT16_RETURN

CODE32  ENDS

CODE16C SEGMENT
        ASSUME  CS:CODE16C

;-----------------------------------------------------------------------
; Code executed before switching to 32-bit mode
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  THK16_UNITHUNK
THK16_UNITHUNK PROC FAR

        push    ebp
        movzx   ebp,sp                  ; Prepare for conversion to flat model
        push    DWORD PTR 0
        push    DWORD PTR 0

        push    ds
        push    es
        push    fs
        push    bx
        push    esi
        mov     esi,esp

        push    eax                     ; Save target address

        push    ss
        push    esi                     ; Save SS:ESP for restore

        push    edx                     ; Save UniThunk string

        and     esp,0FFFCh              ; DWORD align ESP

        mov     ax,ss
        shr     ax,3
        shl     eax,16
        or      ebp,eax                 ; Convert EBP to flat model
        mov     ax,sp                   ; EAX = flat model ESP

        mov     cx,ss
        test    cl,1                    ; Assumes no Ring 1

        mov     cx,THK32R2SS
        jz      SHORT @F

        mov     cx,DOS32FLATDS          ; Ring 3 case falls thru JZ
@@:
        push    cx
        push    eax

        lss     esp,[ esp ]             ; Convert SS:ESP to flat model

        jmp     FAR PTR FLAT:UT16_TARGET

        ALIGN   4

        PUBLIC  UT16_RETURN
UT16_RETURN LABEL FAR                   ; Label defining return jmp location

        pop     esi
        add     sp,2                    ; Discard unchanged BX ; #91889
        pop     fs
        pop     es
        pop     ds
        add     sp,8                    ; Discard thunk variables
        pop     ebp

        shl     cx,1
        lea     dx,UT16_ReturnArray
        add     cx,dx                   ; Get offset of correct RET N

        shld    edx,eax,16              ; Convert EAX to DX:AX

        jmp     cx                      ; Jump to appropriate return

        ALIGN   4

        PUBLIC  UT16_ReturnArray
UT16_ReturnArray:                       ; All RET N for 0 thru 14 parms

        i       = 0
        REPT    29

        ALIGN   4
        ret     i

        i       = i + 2
        ENDM

THK16_UNITHUNK ENDP

CODE16C ENDS

;;;---------------------------------------------------------------------
;;; THK32_UNITHUNK:  Universal 32->16 Thunk with up to 14 parameters, from
;;;                  the set of (U)Long, Short, and UShort, and a return
;;;                  value from the set of (U)Long, Pointer, Short, and
;;;                  and UShort.
;;;
;;; On Entry:  EAX = Destination of Thunk
;;;            EDX = Description of Thunk (low DWORD)
;;;            ECX = Description of Thunk (high DWORD)
;;;            where bits  3  thru  0  = Number of parameters
;;;                  bits  5  thru  4  = Type code of last parameter
;;;                  ...
;;;                  bits  n  thru n-1 = Type code of first parameter
;;;                  bits n+3 thru n+1 = Type code of return value
;;;
;;; Type codes:  00 = (U)Long, 01 = Input Ptr, 10 = (U)Short, 11 = Inout Ptr
;;;
;;; If the type is Pointer, the next 3 bits determine the type of target:
;;;             000 = ASCIIZ (with 01), Inout PULONG to PUSHORT (with 11)
;;;             001 =  2 bytes (size = 1 << index)
;;;             010 =  4 bytes (size = 1 << index)
;;;             011 =  8 bytes (size = 1 << index)
;;;             100 = 16 bytes (size = 1 << index)
;;;             101 = Leading count of bytes
;;;             110 = Trailing count of bytes
;;;             111 (with 01) = Tiled pointer without Length
;;;             111 (with 11) = Next bit is 0 = Input, 1 = Inout,
;;;                 with next 3 bits as follows:
;;;                     000 = Leading count of DWORDs
;;;                     001 = Trailing count of DWORDs
;;;                     010 = Trailing count of 8-byte items
;;;                     011 = Trailing count of 16-byte items
;;;                     100 = Size from first USHORT of data
;;;                     101 = 24 bytes
;;;                     110 = 12 bytes
;;;                     111 = Future Use
;;;
;;; The return type is as follows:
;;;             000 = (U)Long
;;;             001 = Pointer
;;;             010 = UShort
;;;             011 = Short
;;;             100 = Deleted WORD with more following
;;;             101 = Deleted DWORD with more following
;;;             110 = Future use
;;;             111 = Future use
;;;
;;; Note:  The maximum number of parmeters for a thunk is 14.
;;;        If the number of parameters is 15, the next bit is
;;;        defined as follows:
;;;             0 = Enable DOS Exception Handling
;;;             1 = Future use
;;;---------------------------------------------------------------------

;CTFORMAT,,16

; EBX -> Data defined on New Stack

NS32_TARGET     equ     4               ; Target of thunk
NS32_PARMBLOCK  equ     0               ; Pointer to parameter block
NS32_EBP        equ     -4              ; New EBP
NS32_RESTORE    equ     -12             ; SS:ESP for restore after call

;CTFORMAT,

CODE16C SEGMENT
        ASSUME  CS:CODE16C,DS:FLAT

;-----------------------------------------------------------------------
; Code stub executed in 16-bit mode which requires target address in EDI
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT32_TARGET
UT32_TARGET LABEL FAR

        call    DWORD PTR ss:[ ebx + NS32_TARGET ]

        jmp     FAR PTR FLAT:UT32_RETURN

CODE16C ENDS

CODE32  SEGMENT
        ASSUME  CS:FLAT,DS:FLAT,ES:FLAT

;-----------------------------------------------------------------------
; Code executed before switching to 16-bit mode
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  THK32_UNITHUNK
THK32_UNITHUNK PROC NEAR

        push    ebp
        mov     ebp,esp
        push    DWORD PTR 0
        push    DWORD PTR ( TF_DIRECTION SHL 24 )

        push    edx                     ; Save low  DWORD of UniThunk string
        push    ecx                     ; Save high DWORD of UniThunk String

        sub     esp,( OS32_EXTSTRING - OS32_ESP ) ; Possible exception record
        push    esi
        push    edi
        push    ebx

        mov     edi,eax                 ; Target address

        and     dl,11111b
        cmp     dl,01111b
        jne     SHORT UT32_ExceptionSet ; No exception handling requested

        shrd    DWORD PTR [ ebp + OS32_STRING ],ecx,5
        shr     DWORD PTR [ ebp + OS32_EXTSTRING ],5 ; Remove flag from string

        mov     edx,fs
        cmp     edx,DOS32TIB            ; Ensure FS points to the TIB
        je      SHORT @F

        push    DOS32TIB
        pop     fs
@@:
        cmp     esp,fs:[ tib_pstack ]
        jb      SHORT UT32_ExceptionSet ; Current ESP below TIB stack
        cmp     esp,fs:[ tib_pstacklimit ]
        jae     SHORT UT32_ExceptionSet ; Current ESP above TIB stack

        or      BYTE PTR [ ebp + OS_THUNKFLAGS ],TF_EXCEPTIONS ; Set flag

        push    OFFSET FLAT:THK32XHNDLR
        push    DWORD PTR 0
        lea     edx,[ ebp + 4 ]
        mov     [ ebp + OS32_ESP ],edx  ; Save entry value of ESP

        mov     edx,fs:[ tib_pexchain ]
        mov     [ ebp + OS32_CHN ],edx

        push    esp
        call    DOS32SETEXCEPTIONHANDLER

        PUBLIC  UT32_ExceptionSet
UT32_ExceptionSet:

        call    UT_SwapStack

        ifdef   DEBUG

            inc     UTGC_Thunk32
            inc     UTLC_Thunk32

        endif

        push    edi                     ; Save target address on NewStack
        push    0                       ; Allocate pointer to Parm Save Area
        mov     ebx,esp                 ; Get pointer to data on New Stack

        push    ebp                     ; Save EBP across 16-bit call
        mov     eax,esp

        push    ss
        push    eax                     ; Prepare stack for restore after call

        lea     esi,[ ebp + OS_PARMS ]  ; Get pointer to first parameter,
                                        ;   which is first on the stack

        mov     edx,[ ebp + OS32_EXTSTRING ]
        mov     ecx,[ ebp + OS32_STRING ]

        shrd    DWORD PTR [ ebp + OS32_STRING ],edx,4
        shr     edx,4                   ; Shift out number of parameters

        and     ecx,0Fh                 ; ECX = count of parameters
        jz      SHORT UT32_Call

        ALIGN   4                       ; Fall thru to common jump target

        PUBLIC  UT32_Loop
UT32_Loop:

        ;lodsd                           ; Get one DWORD of parameters
        mov     eax, dword ptr[esi]
        add     esi, 4

        test    BYTE PTR [ ebp + OS32_STRING ],03h ; Check for non-(U)Long
        jnz     SHORT UT32_WordParm

        push    eax                     ; Transfer (U)Long as is

        shrd    DWORD PTR [ ebp + OS32_STRING ],edx,2
        shr     edx,2

        dec     ecx
        jnz     SHORT UT32_Loop
        jmp     SHORT UT32_Call

        ALIGN   4

        PUBLIC  UT32_WordParm
UT32_WordParm:

        test    BYTE PTR [ ebp + OS32_STRING ],01h ; Check for pointer parms
        jnz     UT32_PointerParm

; Check here for possible truncation errors when not 0000xxxx or FFFFxxxx

        push    ax                      ; (U)Long to (U)Short

        shrd    DWORD PTR [ ebp + OS32_STRING ],edx,2
        shr     edx,2

        dec     ecx
        jnz     SHORT UT32_Loop
        jmp     SHORT UT32_Call

        ALIGN   4

        PUBLIC  UT32_DeletedParm
UT32_DeletedParm:

        mov     cl,[ ebp + OS32_STRING ]

        shrd    DWORD PTR [ ebp + OS32_STRING ],edx,3
        shr     edx,3                   ; Discard type code

        and     cl,3

        cmp     cl,1
        ja      SHORT UT32_Call         ; 11x -> future use

        push    0                       ; Push reserved (D)WORD of 0
        je      SHORT UT32_Call         ; 101 -> push reserved DWORD

        pop     ax                      ; 100 -> push reserved WORD

        ALIGN   4                       ; Fall thru to common jump target

        PUBLIC  UT32_Call
UT32_Call:

        test    BYTE PTR [ ebp + OS32_STRING ],100b
        jnz     SHORT UT32_DeletedParm

        mov     dl,BYTE PTR [ ebp + OS32_STRING ]
        shl     dl,4
        or      [ ebp + OS_THUNKFLAGS ],dl ; Save return type

        and     ebx,0FFFFh              ; Convert EBX to 16-bit offset

        mov     ecx,ss
        and     ecx,3
        or      cl,4                    ; Get CPL bits to use as DPL bits

        mov     eax,esp
        ror     eax,16
        shl     eax,3
        or      al,cl
        mov     ss,eax                  ; Convert SS to 16-bit selector

        jmp     FAR PTR UT32_TARGET

;-----------------------------------------------------------------------
; Code executed after returning from 16-bit mode
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT32_RestoreDS
UT32_RestoreDS:

        push    DOS32FLATDS
        pop     ds
        jmp     SHORT UT32_HaveDS

        ALIGN   4

        PUBLIC  UT32_RestoreES
UT32_RestoreES:

        push    DOS32FLATDS
        pop     es
        jmp     SHORT UT32_HaveES

        ALIGN   4

        PUBLIC  UT32_RETURN
UT32_RETURN:

        movzx   esp,sp
        lss     esp,[esp]               ; Restore 32-bit stack

        pop     ebp                     ; Restore EBP

        rol     eax,16
        mov     ax,dx
        rol     eax,16                  ; Convert DX:AX to EAX

        mov     edx,ds
        cmp     edx,DOS32FLATDS
        jnz     SHORT UT32_RestoreDS

        PUBLIC  UT32_HaveDS
UT32_HaveDS:

        mov     edx,es
        cmp     edx,DOS32FLATDS
        jnz     SHORT UT32_RestoreES

        PUBLIC  UT32_HaveES
UT32_HaveES:

        cmp     DWORD PTR [ esp + NS32_PARMBLOCK ],0
        jnz     UT32_CopyBack

        PUBLIC  UT32_CheckReturn
UT32_CheckReturn:

        mov     cl,[ ebp + OS_THUNKFLAGS ] ; Restore return type
        shr     cl,4

        test    cl,03h                  ; Check for (U)Long vs all others
        jz      SHORT UT32_AllReturn

        test    cl,02h                  ; Check for Pointer vs all others
        jnz     SHORT UT32_WordReturn

        PUBLIC  UT32_PointerReturn
UT32_PointerReturn:

        call    DOS32SELTOFLAT          ; Convert SEL:OFF to FLAT offset

        jmp     SHORT UT32_AllReturn

        ALIGN   4

        PUBLIC  UT32_WordReturn
UT32_WordReturn:

        test    cl,01h                  ; Check for UShort vs Short
        jnz     SHORT UT32_ShortReturn

        and     eax,0000FFFFh           ; Convert UShort to ULong

        jmp     SHORT UT32_AllReturn

        ALIGN   4

        PUBLIC  UT32_ShortReturn
UT32_ShortReturn:

        movsx   eax,ax

        ALIGN   4

        PUBLIC  UT32_AllReturn
UT32_AllReturn:

        ifdef   DEBUG

            inc     UTGC_Exit32
            inc     UTLC_Exit32

        endif

        PUBLIC  HT32_Exit
HT32_Exit:

        call    UT_SwapStack

        test    BYTE PTR [ ebp + OS_THUNKFLAGS ],TF_EXCEPTIONS
        jz      SHORT @F

        mov     edx,[ ebp + OS32_CHN ]
        mov     fs:[ tib_pexchain ],edx ; Unset the exception handler
@@:
        lea     esp,[ ebp + OS32_EBX ]  ; Restore stack to pop saved regs

        pop     ebx
        pop     edi
        pop     esi
        add     esp,( OS_EBP - OS32_ESP ) ; Discard thunk/exception variables
        pop     ebp

        ret

;-----------------------------------------------------------------------
; Process extended pointer definitions (9 bits)
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT32_ExtendedPointer
UT32_ExtendedPointer:

        shrd    DWORD PTR [ ebp + OS32_STRING ],edx,4
        shr     edx,4                   ; Align new output flag in string

        test    eax,eax
        jz      UT32_PushPointer        ; Finished processing for NULL pointers

        push    eax                     ; Save pointer in Parm Stack
        push    ecx                     ; Space for length in Parm Stack

        mov     ecx,[ ebp + OS32_STRING ]
        shr     ecx,2
        and     ecx,111b

        test    cl,100b
        jz      SHORT UT32_CountOf

        sub     cl,4
        jnz     SHORT @F

        mov     cx,[ eax ]              ; 100x11111 -> Size from leading USHORT
        jmp     SHORT UT32_CheckPointer
@@:
        dec     cl
        jnz     SHORT @F

        mov     cl,24
        jmp     SHORT UT32_CheckPointer ; 101x11111 -> Size is 24
@@:
        dec     cl
        jnz     SHORT UT32_NoCrossing   ; 111x11111 -> Future Use

        mov     cl,12
        jmp     SHORT UT32_CheckPointer ; 110x11111 -> Size is 12

        ALIGN   4

        PUBLIC  UT32_CountOf
UT32_CountOf:

        test    cl,cl
        jnz     SHORT @F

        mov     ecx,[ esi - 8 ]
        shl     ecx,2                   ; 000x11111 -> Size is 4*leading count
        jmp     SHORT UT32_CheckPointer
@@:
        dec     cl
        jnz     SHORT @F

        mov     ecx,[ esi ]
        shl     ecx,2                   ; 001x11111 -> Size is 4*trailing count
        jmp     SHORT UT32_CheckPointer
@@:
        dec     cl
        jnz     SHORT @F

        mov     ecx,[ esi ]
        shl     ecx,3                   ; 010x11111 -> Size is 8*trailing count
        jmp     SHORT UT32_CheckPointer
@@:
        mov     ecx,[ esi ]
        shl     ecx,4                   ; 011x11111 -> Size is 16*trailing count
        jmp     SHORT UT32_CheckPointer

;-----------------------------------------------------------------------
; Identify pointer type and check for 64K boundary crossing (5 or 9 bits)
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT32_PointerParm
UT32_PointerParm:

; If this is a non-zero pointer, set the exception handler here

        push    ecx                     ; Save parameter count in Parm Stack

        mov     cl,[ ebp + OS32_STRING ]
        and     ecx,11111b

        cmp     cl,11111b
        je      SHORT UT32_ExtendedPointer

        test    eax,eax
        jz      UT32_PushPointer        ; Finished processing for NULL pointers

        push    eax                     ; Save pointer in Parm Stack
        push    ecx                     ; Space for length in Parm Stack

        shr     cl,2
        jz      SHORT UT32_StringPointer ; Pointer to ASCIIZ or PBOOL

        cmp     cl,5
        jae     SHORT UT32_SizeOf       ; Pointer with leading/trailing size

        mov     eax,1
        shl     eax,cl
        mov     ecx,eax                 ; Get fixed count of bytes from type

        PUBLIC  UT32_CheckPointer
UT32_CheckPointer:                      ; Check for boundary crossing

        mov     [ esp + PS32_LENGTH ],ecx ; Save computed length

        add     cx,WORD PTR [ esp + PS32_POINTER ]
        jc      SHORT UT32_ParmBlock    ; Boundary crossed, build parm record

        PUBLIC  UT32_NoCrossing
UT32_NoCrossing:

        pop     eax                     ; Discard parm length
        pop     eax                     ; Get pointer in EAX

        PUBLIC  UT32_ConvertPointer
UT32_ConvertPointer:

        call    DOS32FLATTOSEL          ; Convert pointer 32->16

        PUBLIC  UT32_PushPointer
UT32_PushPointer:

        pop     ecx                     ; Restore parameter count
        push    eax                     ; Save pointer to the new data

        shrd    DWORD PTR [ ebp + OS32_STRING ],edx,5
        shr     edx,5                   ; Remove pointer type from string

        dec     ecx
        jnz     UT32_Loop
        jmp     UT32_Call

        ALIGN   4

        PUBLIC  UT32_StringPointer
UT32_StringPointer:                     ; Get length of string including 0

        test    BYTE PTR [ ebp + OS32_STRING ],10b
        jnz     SHORT UT32_PBool        ; String if input, PBool if inout

        mov     edi,eax
        sub     eax,eax
        mov     ecx,-1
        repne   scasb
        not     ecx

        jmp     SHORT UT32_CheckPointer

        ALIGN   4

        PUBLIC  UT32_PBool
UT32_PBool:

        mov     WORD PTR [ eax + 2 ],0  ; Clear high word of Boolean,
        mov     cl,2                    ;   then treat as P2BYTES

        jmp     SHORT UT32_CheckPointer

        ALIGN   4

        PUBLIC  UT32_SizeOf
UT32_SizeOf:

        cmp     cl,6
        ja      SHORT UT32_NoCrossing   ; 11101 -> Tiled Pointer
        je      SHORT @F

        mov     ecx,[ esi - 8 ]         ; 101x1 -> Get size from leading parm
        jmp     SHORT UT32_CheckPointer
@@:
        mov     ecx,[ esi ]             ; 110x1 -> Get size from trailing parm
        jmp     SHORT UT32_CheckPointer

;------------------------------------------------------------------------------
; Parm Block Processing
;------------------------------------------------------------------------------
;    Field       Bits   Usage
; ------------   ----   ------------------------
; PB32_LENGTH     17    Length of parameter data
;                  7    Flags
;                  8    Size of parm record
; PB32_POINTER    32    Pointer to user data
; PB32_DATA      32*x   Pointer to allocated data
;                       or DWORDs of parameter data
;------------------------------------------------------------------------------
; The total space available in the parm block is 128 bytes minus 12 bytes
; per pointer parm ( Length + User Pointer + Allocated Pointer ).  If there
; is enough free space in the Parm Blockk to hold the data, the amount of
; free space is reduced by only size - 4, since the Allocated Pointer is
; not used.  Note that by counting the unused Allocated Pointer when checking
; for enough space, there is always enough room for the Max Size in the first
; DWORD of the next record.
;------------------------------------------------------------------------------

;CTFORMAT,,16

; EDI -> Parm record in Parm Block

PBF32_OUTPUT    equ     10h             ; This is an output parameter
PBF32_ALLOC     equ     20h             ; This parameter was allocated
PBM32_LENGTH    equ     0001FFFFh       ; Mask for parameter data length
PBS32_ENTRYLEN  equ     24              ; Shift for entry length

PB32_LENGTH     equ     0               ; Length of this data (with flags)
PB32_FLAGS      equ     2               ; Flags (overlaps LENGTH)
PB32_ENTRYLEN   equ     3               ; Entry Length (overlaps LENGTH)
PB32_POINTER    equ     4               ; Pointer for data return
PB32_DATA       equ     8               ; Data/Pointer for this parm

; ESP -> Stack during Parm processing

PS32_LENGTH     equ     0               ; Length of this parameter
PS32_POINTER    equ     4               ; Pointer to this parameter
PS32_PARMCOUNT  equ     8               ; Saved count of parameters

;CTFORMAT,

        ALIGN   4

        PUBLIC  UT32_ParmBlock
UT32_ParmBlock:                         ; Allocate the Parameter Block

        mov     edi,[ ebx + NS32_PARMBLOCK ]
        test    edi,edi
        jnz     SHORT UT32_SaveParm     ; ParmBlock already allocated

        mov     ecx,[ esp + PS32_PARMCOUNT ] ; Restore parm count

        mov     eax,[ ebp + OS32_STRING ] ; EDX:EAX = parm string

        push    ebx
        push    edx

        PUBLIC  UT32_ParmSearch
UT32_ParmSearch:                        ; Count the number of pointer types

        mov     bl,al                   ; Save type bits for compare

        shrd    eax,edx,2
        shr     edx,2                   ; Discard non-pointer type bits

        test    bl,1
        jz      SHORT @F

        inc     edi                     ; Pointer type found, count it

        shrd    eax,edx,3
        shr     edx,3                   ; Discard standard pointer type bits

        and     bl,11111b
        cmp     bl,11111b
        jnz     SHORT @F

        shrd    eax,edx,4
        shr     edx,4                   ; Discard extended pointer type bits
@@:
        dec     ecx
        jnz     SHORT UT32_ParmSearch

        pop     edx
        pop     ebx

        mov     ecx,132
        shl     edi,2
        sub     ecx,edi
        shl     edi,1
        sub     ecx,edi                 ; Max parm size is 128 - 12*EDI + 4

        call    THK32ALLOCBLOCK         ; Get 128-byte save area pointer in EDI
        test    eax,eax
        jnz     UT32_AllReturn          ; Out of memory, just leave

        ifdef   DEBUG

            inc     UTGC_AllocParm
            inc     UTLC_AllocParm

        endif

        mov     [ edi + PB32_LENGTH ],ecx ; Save data space in first record

        mov     [ ebx + NS32_PARMBLOCK ],edi

        PUBLIC  UT32_SaveParm
UT32_SaveParm:                          ; Save one parameter in Parm Block

        mov     eax,[ esp + PS32_POINTER ]
        mov     [ edi + PB32_POINTER ],eax ; Save pointer in Parm Block

        mov     eax,[ edi + PB32_LENGTH ] ; Get max data space from Parm Block

        mov     ecx,[ esp + PS32_LENGTH ]
        mov     [ edi + PB32_LENGTH ],ecx ; Save data length in Parm Block

        test    BYTE PTR [ ebp + OS32_STRING ],10b
        jz      SHORT @F

        or      BYTE PTR [ edi + PB32_FLAGS ],PBF32_OUTPUT ; Copy the output flag
@@:
        cmp     ecx,eax
        ja      SHORT UT32_AllocData    ; Not enough space in Parm Block

        add     cl,8                    ; ECX is known to be less than 128
        and     cl,0FCh                 ; Round size of data + header to DWORD
        mov     [ edi + PB32_ENTRYLEN ],cl ; Save in high byte of length

        sub     eax,ecx                 ; Reduce amount of free space
        add     eax,12                  ; Adjust for header

        lea     ecx,[ edi + ecx ]       ; Get offset of next entry
        mov     [ ecx + PB32_LENGTH ],eax ; Store free space in next entry
        mov     [ ebx + NS32_PARMBLOCK ],ecx ; Update pointer to next entry

        lea     eax,[ edi + PB32_DATA ] ; Get pointer to save area

        jmp     SHORT UT32_CopyParm

        PUBLIC  UT32_AllocData
UT32_AllocData:                         ; Allocate space for the parm data

        or      WORD PTR [ edi + PB32_FLAGS ],0C00h + PBF32_ALLOC ; Size + Flag
        add     DWORD PTR [ ebx + NS32_PARMBLOCK ],12 ; Update ptr to next entry
        mov     [ edi + 12 + PB32_LENGTH ],eax ; Move max space to next entry

        push    edi

        call    THK32ALLOCMEM           ; Allocate memory for the parm
        test    eax,eax
        jnz     UT32_AllReturn          ; Out of memory, just leave

        ifdef   DEBUG

            inc     UTGC_AllocData
            inc     UTLC_AllocData

        endif

        pop     eax
        xchg    eax,edi
        mov     [ edi + PB32_DATA ],eax ; Save the target pointer

        PUBLIC  UT32_CopyParm
UT32_CopyParm:

        mov     [ esp + PS32_POINTER ],eax ; Replace pointer for call

        mov     ecx,[ esp + PS32_LENGTH ]
        push    esi
        mov     esi,[ edi + PB32_POINTER ] ; Get user data pointer
        mov     edi,eax                 ; Get non-crossing data pointer

        push    ecx
        shr     ecx,2
        rep     movsd                   ; Copy aligned double words first
        pop     ecx
        and     ecx,3
        rep     movsb                   ; Copy remaining bytes to save area

        pop     esi
        jmp     UT32_NoCrossing

;-----------------------------------------------------------------------
; Copy back output parameters defined in the Parm Block
;-----------------------------------------------------------------------

        ALIGN   4

        PUBLIC  UT32_CopyBack
UT32_CopyBack:

        mov     ebx,esp
        push    eax

        mov     esi,[ ebx + NS32_PARMBLOCK ]
        and     esi,0FFFFFF80h          ; Reset to start of Parm Block

        PUBLIC  UT32_CopyLoop
UT32_CopyLoop:

        test    BYTE PTR [ esi + PB32_FLAGS ],PBF32_ALLOC
        jnz     SHORT UT32_CopyBackAlloc

        test    BYTE PTR [ esi + PB32_FLAGS ],PBF32_OUTPUT
        jz      SHORT UT32_EndCopyLoop

        push    esi

        mov     ecx,[ esi + PB32_LENGTH ]
        mov     edi,[ esi + PB32_POINTER ]
        lea     esi,[ esi + PB32_DATA ]
        and     ecx,PBM32_LENGTH        ; Mask off flags and entry length

        push    ecx
        shr     ecx,2
        rep     movsd                   ; Copy double words to save area
        pop     ecx
        and     ecx,3
        rep     movsb                   ; Copy remaining bytes to save area

        pop     esi

        PUBLIC  UT32_EndCopyLoop
UT32_EndCopyLoop:

        mov     ecx,[ esi + PB32_LENGTH ]
        shr     ecx,PBS32_ENTRYLEN
        add     esi,ecx                 ; Get next parameter record

        cmp     esi,[ ebx + NS32_PARMBLOCK ]
        jb      SHORT UT32_CopyLoop     ; More records to process

        and     esi,0FFFFFF80h          ; Reset to start of Parm Block
        call    THK32FREEBLOCK          ; Free the parameter save area

        ifdef   DEBUG

            inc     UTGC_FreeParm
            inc     UTLC_FreeParm

        endif

        pop     eax

        jmp     UT32_CheckReturn

        ALIGN   4

        PUBLIC  UT32_CopyBackAlloc
UT32_CopyBackAlloc:

        mov     ecx,[ esi + PB32_LENGTH ]
        and     ecx,PBM32_LENGTH        ; Get data length in ECX

        test    BYTE PTR [ esi + PB32_FLAGS ],PBF32_OUTPUT
        jz      SHORT UT32_FreeData

        push    esi
        push    ecx

        mov     edi,[ esi + PB32_POINTER ]
        mov     esi,[ esi + PB32_DATA ]

        push    ecx
        shr     ecx,2
        rep     movsd                   ; Copy double words to save area
        pop     ecx
        and     ecx,3
        rep     movsb                   ; Copy remaining bytes to save area

        pop     ecx
        pop     esi

        PUBLIC  UT32_FreeData
UT32_FreeData:

        push    esi

        mov     esi,[ esi + PB32_DATA ]
        call    THK32FREEMEM            ; Free the saved parameter data

        pop     esi

        ifdef   DEBUG

            inc     UTGC_FreeData
            inc     UTLC_FreeData

        endif

        jmp     SHORT UT32_EndCopyLoop

THK32_UNITHUNK ENDP

CODE32  ENDS

;=======================================================================
; Support Routines for Hand Coded 16->32 Thunks
;=======================================================================

;-----------------------------------------------------------------------
; HT16_STARTUP performs all of the initial processing for a 16->32 thunk,
;   including stack reallocation, as needed.  Thunks jump to HT16_STARTUP
;   with the address of the 32-bit portion of the thunk in EBX.  This
;   routine should not be called.
;-----------------------------------------------------------------------

CODE32  SEGMENT
        ASSUME  CS:FLAT,DS:FLAT,ES:FLAT

        ALIGN   4

        PUBLIC  HT16_TARGET
HT16_TARGET LABEL FAR

        call    UT_SwapStack            ; Sets DS, ES, and FS for 32-bit mode

        ifdef   DEBUG

            inc     HTGC_Thunk16
            inc     HTLC_Thunk16

        endif

        jmp     ebx                     ; Continue this thunk

CODE32  ENDS

CODE16C SEGMENT DWORD USE16 PUBLIC 'CODE'
        ASSUME  CS:CODE16C

        ALIGN   4

        PUBLIC  HT16_STARTUP
HT16_STARTUP:

        push    ebp
        movzx   ebp,sp                  ; Prepare for conversion to flat model
        push    DWORD PTR 0
        push    DWORD PTR 0

        push    ds
        push    es
        push    fs
        push    bx
        push    esi
        mov     esi,esp

        push    eax                     ; Save target address

        push    ss
        push    esi                     ; Save SS:ESP for restore

        and     esp,0FFFCh              ; DWORD align ESP

        mov     ax,ss
        shr     ax,3
        shl     eax,16
        or      ebp,eax                 ; Convert EBP to flat model
        mov     ax,sp                   ; EAX = flat model ESP

        mov     cx,ss
        test    cl,1                    ; Assumes no Ring 1

        mov     cx,THK32R2SS
        jz      SHORT @F

        mov     cx,DOS32FLATDS          ; Ring 3 case falls thru JZ
@@:
        push    cx
        push    eax

        lss     esp,[ esp ]             ; Convert SS:ESP to flat model

        jmp     FAR PTR FLAT:HT16_TARGET

CODE16C ENDS

;-----------------------------------------------------------------------
; HT16_CLEANUP performs all of the cleanup processing for a 16->32 thunk,
;   including stack deallocation, as needed.  Thunks jump to HT16_CLEANUP
;   with the number of parm bytes to be discarded in DL.  This routine
;   should not be called.
;-----------------------------------------------------------------------

CODE32  SEGMENT
        ASSUME  CS:FLAT,DS:FLAT,ES:FLAT

        ALIGN   4

        PUBLIC  HT16_CLEANUP
HT16_CLEANUP:

        sub     ecx,ecx
        mov     cl,dl

        ifdef   DEBUG

            inc     HTGC_Exit16
            inc     HTLC_Exit16

        endif

        jmp     HT16_Exit

CODE32  ENDS

;=======================================================================
; Support Routines for Hand Coded 32->16 Thunks
;=======================================================================

;-----------------------------------------------------------------------
; HT32_STARTUP performs all of the initial processing for a 32->16 thunk,
;   including stack reallocation, as needed.  Thunks call HT32_STARTUP.
;-----------------------------------------------------------------------

CODE32  SEGMENT
        ASSUME  CS:FLAT,DS:FLAT,ES:FLAT

        ALIGN   4

        PUBLIC  HT32_STARTUP
HT32_STARTUP:

        pop     ecx                     ; Save return address

        push    ebp
        mov     ebp,esp
        push    DWORD PTR 0
        push    DWORD PTR ( TF_DIRECTION SHL 24 )

        push    edx                     ; Save low DWORD of UniThunk string
        push    ecx                     ; Save return address in EXTSTRING

        sub     esp,( OS32_EXTSTRING - OS32_ESP ) ; Possible exception record
        push    esi
        push    edi
        push    ebx
        sub     esp,( OS32_EBX - OS32_PRV ) ; Possible exception record

        mov     edi,eax                 ; Target address

        call    UT_SwapStack

        ifdef   DEBUG

            inc     HTGC_Thunk32
            inc     HTLC_Thunk32

        endif

        push    edi                     ; Save target address on NewStack
        mov     ebx,esp                 ; Get pointer to data on New Stack
        and     ebx,0FFFFh              ; EBX = 16-bit offset of target in SS

        push    DWORD PTR [ ebp + OS32_EXTSTRING ] ; Restore return address

        ret

CODE32  ENDS

;-----------------------------------------------------------------------
; HT32_CLEANUP performs all of the cleanup processing for a 32->16 thunk,
;   including stack deallocation, as needed.  Thunks jump to HT32_CLEANUP.
;   This routine should not be called.
;-----------------------------------------------------------------------

CODE32  SEGMENT
        ASSUME  CS:FLAT,DS:FLAT,ES:FLAT

        ALIGN   4

        PUBLIC  HT32_CLEANUP
HT32_CLEANUP:

        ifdef   DEBUG

            inc     HTGC_Exit32
            inc     HTLC_Exit32

        endif

        jmp     HT32_Exit

CODE32  ENDS

        END
