/*++
 *
 *  WOW v1.0
 *
 *  Copyright (c) 1991, Microsoft Corporation
 *
 *  WKMAN.C
 *  WOW32 16-bit Kernel API support (manually-coded thunks)
 *
 *  History:
 *  Created 27-Jan-1991 by Jeff Parsons (jeffpar)
 *  20-Apr-91 Matt Felton (mattfe) Added WK32CheckLoadModuleDrv
 *  28-Jan-92 Matt Felton (mattfe) Added Wk32GetNextVdmCommand + MIPS build
 *  10-Feb-92 Matt Felton (mattfe) Removed WK32CheckLoadModuleDRV
 *  10-Feb-92 Matt Felton (mattfe) cleanup and task creation
 *   4-mar-92 mattfe add killprocess
 *  11-mar-92 mattfe added W32NotifyThread
 *  12-mar-92 mattfe added WowRegisterShellWindowHandle
 *  17-apr-92 daveh changed to use host_CreateThread and host_ExitThread
 *  11-jun-92 mattfe hung app support W32HungAppNotifyThread, W32EndTask
 *
--*/

#include "precomp.h"
#pragma hdrstop
#include <ntexapi.h>
#include <sharewow.h>
#include <vdmdbg.h>
#include <ntseapi.h>
#include <wingdip.h>     // GACF_ app compat flags
#include <shlobj.h>      // CSIDL_COMMON_STARTMENU etc
#include <userenv.h>     // GetAllUsersProfilesDirectory and etc
#include "wowfax.h"
#include "demexp.h"
#include "wshimdb.h"

extern void UnloadNetworkFonts( UINT id );

MODNAME(wkman.c);

BOOL GetWOWShortCutInfo (PULONG Bufsize, PVOID Buf);
extern void FreeTaskFormFeedHacks(HAND16 h16);

BOOL WOWSortEnvironmentStrings(PSZ pszEnv);

void WK32ChangeDisplayMode(DWORD dmBitsPerPel);
void WK32RevertDisplayMode(void);

// Global DATA

//
// The 5 variables below are used to hold STARTUPINFO fields between
// WowExec's GetNextVdmComand call and the InitTask call of the new
// app.  We pass them on to user32's InitTask.
//

DWORD   dwLastHotkey;
DWORD   dwLastX = (DWORD) CW_USEDEFAULT;
DWORD   dwLastY = (DWORD) CW_USEDEFAULT;
DWORD   dwLastXSize = (DWORD) CW_USEDEFAULT;
DWORD   dwLastYSize = (DWORD) CW_USEDEFAULT;

HWND    ghwndShell;           // WOWEXEC Window Handle
HANDLE  ghInstanceUser32;

HAND16  ghShellTDB;                 // WOWEXEC TDB
HANDLE  ghevWowExecMsgWait;
HANDLE  ghevWaitHungAppNotifyThread = (HANDLE)-1;  // Syncronize App Termination to Hung App NotifyThread
HANDLE  ghNotifyThread = (HANDLE)-1;        // Notification Thread Handle
HANDLE  ghHungAppNotifyThread = (HANDLE)-1; // HungAppNotification ThreadHandle
PTD gptdTaskHead;                   // Linked List of TDs
CRITICAL_SECTION gcsWOW;            // WOW Critical Section used when updating task linked list
CRITICAL_SECTION gcsHungApp;        // HungApp Critical Section used when VDM_WOWHUNGAPP bit

HMODCACHE ghModCache[CHMODCACHE];   // avoid callbacks to get 16-bit hMods

volatile HANDLE ghTaskCreation;     // hThread from task creation (see WK32SyncTask)
                                    // touched by parent and child threads during task init
HANDLE ghTaskAppHelp;      // hProcess from apphelp 
BOOL   gfTaskContinue;     // indicates whether child thread should continue without waiting for apphelp 

VPVOID  vpnum_tasks;                // Pointer to KDATA variables (KDATA.ASM)
PWORD16 pCurTDB;                    // Pointer to KDATA variables
PWORD16 pCurDirOwner;               // Pointer to KDATA variables
VPVOID  vpDebugWOW = 0;             // Pointer to KDATA variables
VPVOID  vpLockTDB;                  // Pointer to KDATA variables
VPVOID  vptopPDB = 0;               // KRNL PDB
DOSWOWDATA DosWowData;              // structure that keeps linear pointer to
                                    // DOS internal variables.

//
// List of known DLLs used by WK32WowIsKnownDLL, called by 16-bit LoadModule.
// This causes known DLLs to be forced to load from the 32-bit system
// directory, since these are "special" binaries that should not be
// overwritten by unwitting 16-bit setup programs.
//
// This list is initialized from the registry value
// ...\CurrentControlSet\Control\WOW\KnownDLLs REG_SZ (space separated list)
//

#define MAX_KNOWN_DLLS 64
PSZ apszKnownDLL[MAX_KNOWN_DLLS];

//
// Fully-qualified path to %windir%\control.exe for PM5 setup fix.
// Setup by WK32InitWowIsKnownDll, used by WK32WowIsKnownDll.
//
CHAR szBackslashControlExe[] = "\\control.exe";
PSZ pszControlExeWinDirPath;          // "c:\winnt\control.exe"
PSZ pszControlExeSysDirPath;          // "c:\winnt\system32\control.exe"
CHAR szBackslashProgmanExe[] = "\\progman.exe";
PSZ pszProgmanExeWinDirPath;          // "c:\winnt\progman.exe"
PSZ pszProgmanExeSysDirPath;          // "c:\winnt\system32\progman.exe"

char szWOAWOW32[] = "-WoAWoW32";

//
// WOW GDI/CSR batching limit.
//

DWORD  dwWOWBatchLimit = 0;


UINT GetWOWTaskId(void);

#define TOOLONGLIMIT     _MAX_PATH
#define WARNINGMSGLENGTH 255

static char szCaption[TOOLONGLIMIT + WARNINGMSGLENGTH];
static char szMsgBoxText[TOOLONGLIMIT + WARNINGMSGLENGTH];

extern HANDLE hmodWOW32;

/*
 * These are used to clean up DelayFree arrray
 *
 */

extern LPVOID glpvDelayFree[];
extern DWORD  gdwDelayFree;



/*
 * This function is living inside dpmi32/i386 and is used when we need to force dpmi linear memory allocation
 * via the compat flag
 *
 */

extern VOID DpmiSetIncrementalAlloc(BOOL);


/* WK32WaitEvent - First API called by app, courtesy the C runtimes
 *
 * ENTRY
 *
 * EXIT
 *  Returns TRUE to indicate that a reschedule occurred
 *
 *
 */

ULONG FASTCALL WK32WaitEvent(PVDMFRAME pFrame)
{
    UNREFERENCED_PARAMETER(pFrame);
    return TRUE;
}


/* WK32KernelTrace - Trace 16Bit Kernel API Calls
 *
 * ENTRY
 *
 * EXIT
 *
 *
 */

ULONG FASTCALL WK32WOWKernelTrace(PVDMFRAME pFrame)
{
#ifdef DEBUG
PBYTE pb1;
PBYTE pb2;
register PWOWKERNELTRACE16 parg16;

 // Check Filtering - Trace Correct TaskID and Kernel Tracing Enabled

    if (((WORD)(pFrame->wTDB & fLogTaskFilter) == pFrame->wTDB) &&
        ((fLogFilter & FILTER_KERNEL16) != 0 )) {

        GETARGPTR(pFrame, sizeof(*parg16), parg16);
        GETVDMPTR(parg16->lpRoutineName, 50, pb1);
        GETVDMPTR(parg16->lpUserArgs, parg16->cParms, pb2);
        if ((fLogFilter & FILTER_VERBOSE) == 0 ) {
          LOGDEBUG(12, ("%s(", pb1));
        } else {
          LOGDEBUG(12, ("%04X %08X %04X %s:%s(",pFrame->wTDB, pb2, pFrame->wAppDS, (LPSZ)"Kernel16", pb1));
        }

        pb2 += 2*sizeof(WORD);              // point past callers CS:IP

        pb2 += parg16->cParms;

        while (parg16->cParms > 0) {
        pb2 -= sizeof(WORD);
        parg16->cParms -= sizeof(WORD);
        LOGDEBUG(12,( "%04x", *(PWORD)pb2));
        if (parg16->cParms > 0) {
            LOGDEBUG(12,( ","));
        }
    }

    LOGDEBUG(12,( ")\n"));
    if (fDebugWait != 0) {
        DbgPrint("WOWSingle Step\n");
        DbgBreakPoint();
    }

    FREEVDMPTR(pb1);
    FREEVDMPTR(pb2);
    FREEARGPTR(parg16);
 }
#else
    UNREFERENCED_PARAMETER(pFrame);
#endif
    return TRUE;
}


DWORD ParseHotkeyReserved(
    CHAR *pchReserved)
{
    ULONG dw;
    CHAR *pch;

    if (!pchReserved || !*pchReserved)
        return 0;

    dw = 0;

    if ((pch = WOW32_strstr(pchReserved, "hotkey")) != NULL) {
        pch += strlen("hotkey");
        pch++;
        dw = atoi(pch);
    }

    return dw;
}


/* WK32WowGetNextVdmCommand - Get Next App Name to Exec
 *
 *
 * Entry - lpReturnedString - Pointer to String Buffer
 *     nSize - Size of Buffer
 *
 * Exit
 *     SUCCESS
 *        if (!pWowInfo->CmdLineSize) {
 *            // no apps queued
 *        } else {
 *            Buffer Has Next App Name to Exec
 *            and new environment
 *        }
 *
 *     FAILURE
 *        Buffer Size too Small or Environment is too small
 *         pWowInfo->EnvSize - required size
 *         pWowInfo->CmdLineSize - required size
 *
 *
 */

// these two functions are imported from ntvdm.exe and housed in
// dos\\command\\cmdenv.c
//
extern VOID cmdCheckTempInit(VOID);
extern LPSTR cmdCheckTemp(LPSTR lpszzEnv);

CHAR szProcessHistoryVar[] = "__PROCESS_HISTORY";
CHAR szCompatLayerVar   [] = "__COMPAT_LAYER";
CHAR szShimFileLogVar   [] = "SHIM_FILE_LOG";

ULONG FASTCALL WK32WowGetNextVdmCommand (PVDMFRAME pFrame)
{

    ULONG ul;
    PSZ pszEnv16, pszEnv, pszCurDir, pszCmd, pszAppName, pszEnv32, pszTemp;
    register PWOWGETNEXTVDMCOMMAND16 parg16;
    PWOWINFO pWowInfo;
    VDMINFO VDMInfo;
    PCHAR   pTemp;
    WORD    w;
    CHAR    szSiReservedBuf[128];

    GETARGPTR(pFrame, sizeof(WOWGETNEXTVDMCOMMAND16), parg16);
    GETVDMPTR(parg16->lpWowInfo, sizeof(WOWINFO), pWowInfo);
    GETVDMPTR(pWowInfo->lpCmdLine, pWowInfo->CmdLineSize, pszCmd);
    GETVDMPTR(pWowInfo->lpAppName, pWowInfo->AppNameSize, pszAppName);
    GETVDMPTR(pWowInfo->lpEnv, pWowInfo->EnvSize, pszEnv);
    GETVDMPTR(pWowInfo->lpCurDir, pWowInfo->CurDirSize, pszCurDir);

    pszEnv16 = pszEnv;

    // if we have a real environment pointer and size then
    // malloc a 32 bit buffer. Note that the 16 bit buffer should
    // be twice the size.

    VDMInfo.Enviornment = pszEnv;
    pszEnv32 = NULL;

    if (pWowInfo->EnvSize != 0) {
       if (pszEnv32 = malloc_w(pWowInfo->EnvSize)) {
            VDMInfo.Enviornment = pszEnv32;
       }
    }


SkipWowExec:

    VDMInfo.CmdLine = pszCmd;
    VDMInfo.CmdSize = pWowInfo->CmdLineSize;
    VDMInfo.AppName = pszAppName;
    VDMInfo.AppLen = pWowInfo->AppNameSize;
    VDMInfo.PifFile = NULL;
    VDMInfo.PifLen = 0;
    VDMInfo.CurDrive = 0;
    VDMInfo.EnviornmentSize = pWowInfo->EnvSize;
    VDMInfo.ErrorCode = TRUE;
    VDMInfo.VDMState =  fSeparateWow ? ASKING_FOR_SEPWOW_BINARY : ASKING_FOR_WOW_BINARY;
    VDMInfo.iTask = 0;
    VDMInfo.StdIn = 0;
    VDMInfo.StdOut = 0;
    VDMInfo.StdErr = 0;
    VDMInfo.CodePage = 0;
    VDMInfo.TitleLen = 0;
    VDMInfo.DesktopLen = 0;
    VDMInfo.CurDirectory = pszCurDir;
    VDMInfo.CurDirectoryLen = pWowInfo->CurDirSize;
    VDMInfo.Reserved = szSiReservedBuf;
    VDMInfo.ReservedLen = sizeof(szSiReservedBuf);

    ul = GetNextVDMCommand (&VDMInfo);

    if (ul) {

        //
        // BaseSrv will return TRUE with CmdSize == 0 if no more commands
        //
        if (VDMInfo.CmdSize == 0) {
            pWowInfo->CmdLineSize = 0;
            goto CleanUp;
        }

        //
        // If wowexec is the appname then we don't want to pass it back to
        // the existing instance of wowexec in a shared VDM since it will
        // basically do nothing but load and exit. Since it is not run we
        // need call ExitVDM to cleanup. Next we go back to look for more
        // commands.
        //
        if ((! fSeparateWow) && WOW32_strstr(VDMInfo.AppName, "wowexec.exe")) {
            ExitVDM(WOWVDM, VDMInfo.iTask);
            goto SkipWowExec;
        }

    }


    //
    // WOWEXEC will initially call with a guess of the correct environment
    // size. If he did not allocate enough then we will return the appropriate
    // size so that he can try again. WOWEXEC knows that we will require a
    // buffer twice the size specified. The environment can be up to 64k since
    // 16 bit LoadModule can only take a selector pointer to the environment.
    //

    if ( VDMInfo.EnviornmentSize > pWowInfo->EnvSize         ||
         VDMInfo.CmdSize > (USHORT)pWowInfo->CmdLineSize     ||
         VDMInfo.AppLen > (USHORT)pWowInfo->AppNameSize      ||
         VDMInfo.CurDirectoryLen > (ULONG)pWowInfo->CurDirSize )
       {

        // We return the size specified, but assume that WOWEXEC will double
        // it when allocating memory to allow for the string conversion/
        // expansion that might happen for international versions of NT.
        // See below where we uppercase and convert to OEM characters.

        w = 2*(WORD)VDMInfo.EnviornmentSize;
        if ( (DWORD)w == 2*(VDMInfo.EnviornmentSize) ) {
            // Fit in a Word!
            pWowInfo->EnvSize = (WORD)VDMInfo.EnviornmentSize;
        } else {
            // Make it the max size (see 16 bit globalrealloc)
            pWowInfo->EnvSize = (65536-17)/2;
        }

        // Pass back other correct sizes required
        pWowInfo->CmdLineSize = VDMInfo.CmdSize;
        pWowInfo->AppNameSize = VDMInfo.AppLen;
        pWowInfo->CurDirSize = (USHORT)VDMInfo.CurDirectoryLen;
        ul = FALSE;
    }

    if ( ul ) {

        //
        // Boost the hour glass
        //

        ShowStartGlass (10000);

        //
        // Save away wShowWindow, hotkey and startup window position from
        // the STARTUPINFO structure.  We'll pass them over to UserSrv during
        // the new app's InitTask call.  The assumption here is that this
        // will be the last GetNextVDMCommand call before the call to InitTask
        // by the newly-created task.
        //

        dwLastHotkey = ParseHotkeyReserved(VDMInfo.Reserved);

        if (VDMInfo.StartupInfo.dwFlags & STARTF_USESHOWWINDOW) {
            pWowInfo->wShowWindow =
              (VDMInfo.StartupInfo.wShowWindow  == SW_SHOWDEFAULT)
              ? SW_SHOW : VDMInfo.StartupInfo.wShowWindow ;
        } else {
            pWowInfo->wShowWindow = SW_SHOW;
        }

        if (VDMInfo.StartupInfo.dwFlags & STARTF_USEPOSITION) {
            dwLastX = VDMInfo.StartupInfo.dwX;
            dwLastY = VDMInfo.StartupInfo.dwY;
        } else {
            dwLastX = dwLastY = (DWORD) CW_USEDEFAULT;
        }

        if (VDMInfo.StartupInfo.dwFlags & STARTF_USESIZE) {
            dwLastXSize = VDMInfo.StartupInfo.dwXSize;
            dwLastYSize = VDMInfo.StartupInfo.dwYSize;
        } else {
            dwLastXSize = dwLastYSize = (DWORD) CW_USEDEFAULT;
        }

        LOGDEBUG(4, ("WK32WowGetNextVdmCommand: HotKey: %u\n"
                     "    Window Pos:  (%u,%u)\n"
                     "    Window Size: (%u,%u)\n",
                     dwLastHotkey, dwLastX, dwLastY, dwLastXSize, dwLastYSize));


        // 20-Jan-1994 sudeepb
        // Following callout is for inheriting the directories for the new
        // task. After this we mark the CDS's to be invalid which will force
        // new directories to be pickedup on need basis. See bug#1995 for
        // details.

        W32RefreshCurrentDirectories (pszEnv32);

        // Save iTask
        // When Server16 does the Exec Call we can put this Id into task
        // Structure.  When the WOW app dies we can notify Win32 using this
        // taskid so if any apps are waiting they will get notified.

        iW32ExecTaskId = VDMInfo.iTask;

        //
        // krnl expects ANSI strings!
        //

        OemToChar(pszCmd, pszCmd);
        OemToChar(pszAppName, pszAppName);

        //
        // So should the current directory be OEM or Ansi?
        //


        pWowInfo->iTask = VDMInfo.iTask;
        pWowInfo->CurDrive = VDMInfo.CurDrive;
        pWowInfo->EnvSize = (USHORT)VDMInfo.EnviornmentSize;


        // Uppercase the Environment KeyNames but leave the environment
        // variables in mixed case - to be compatible with MS-DOS
        // Also convert environment to OEM character set

        // another thing that we do here is a fixup to temp/tmp variables
        // which  occurs via the provided ntvdm functions


        if (pszEnv32) {

            cmdCheckTempInit();

            for (pszTemp = pszEnv32;*pszTemp;pszTemp += (strlen(pszTemp) + 1)) {

                PSZ pEnv;

                // The MS-DOS Environment is OEM

                if (NULL == (pEnv = cmdCheckTemp(pszTemp))) {
                   pEnv = pszTemp;
                }

                CharToOem(pEnv,pszEnv);

                // Ignore the NT specific Environment variables that start ==

                if (*pszEnv != '=') {
                    if (pTemp = WOW32_strchr(pszEnv,'=')) {
                        *pTemp = '\0';

                        // don't uppercase "windir" as it is lowercase for
                        // Win 3.1 and MS-DOS apps.

                       if (pTemp-pszEnv != 6 || WOW32_strncmp(pszEnv, "windir", 6))
                           WOW32_strupr(pszEnv);
                       *pTemp = '=';
                    }
                }
                pszEnv += (strlen(pszEnv) + 1);
            }

            // Environment is Double NULL terminated
            *pszEnv = '\0';
        }
    }

  CleanUp:
    if (pszEnv32) {
        free_w(pszEnv32);
    }

    FLUSHVDMPTR(parg16->lpWowInfo, sizeof(WOWINFO), pWowInfo);
    FLUSHVDMPTR((ULONG)pWowInfo->lpCmdLine, pWowInfo->CmdLineSize, pszCmd);

    FREEVDMPTR(pszCmd);
    FREEVDMPTR(pszEnv);
    FREEVDMPTR(pszCurDir);
    FREEVDMPTR(pWowInfo);
    FREEARGPTR(parg16);
    RETURN(ul);
}


#if 0

// this version inserts process history

ULONG FASTCALL WK32WowGetNextVdmCommand (PVDMFRAME pFrame)
{

    ULONG ul;
    PSZ pszEnv16, pszEnv, pszCurDir, pszCmd, pszAppName, pszEnv32, pszTemp;
    register PWOWGETNEXTVDMCOMMAND16 parg16;
    PWOWINFO pWowInfo;
    VDMINFO VDMInfo;
    PCHAR   pTemp;
    CHAR    szSiReservedBuf[128];

    GETARGPTR(pFrame, sizeof(WOWGETNEXTVDMCOMMAND16), parg16);
    GETVDMPTR(parg16->lpWowInfo, sizeof(WOWINFO), pWowInfo);
    GETVDMPTR(pWowInfo->lpCmdLine, pWowInfo->CmdLineSize, pszCmd);
    GETVDMPTR(pWowInfo->lpAppName, pWowInfo->AppNameSize, pszAppName);
    GETVDMPTR(pWowInfo->lpEnv, pWowInfo->EnvSize, pszEnv);
    GETVDMPTR(pWowInfo->lpCurDir, pWowInfo->CurDirSize, pszCurDir);

    pszEnv16 = pszEnv;

    // if we have a real environment pointer and size then
    // malloc a 32 bit buffer. Note that the 16 bit buffer should
    // be twice the size.

    VDMInfo.Enviornment = pszEnv;
    pszEnv32 = NULL;

    if (pWowInfo->EnvSize != 0) {
       if (pszEnv32 = malloc_w(pWowInfo->EnvSize)) {
            VDMInfo.Enviornment = pszEnv32;
       }
    }


SkipWowExec:

    VDMInfo.CmdLine = pszCmd;
    VDMInfo.CmdSize = pWowInfo->CmdLineSize;
    VDMInfo.AppName = pszAppName;
    VDMInfo.AppLen = pWowInfo->AppNameSize;
    VDMInfo.PifFile = NULL;
    VDMInfo.PifLen = 0;
    VDMInfo.CurDrive = 0;
    VDMInfo.EnviornmentSize = pWowInfo->EnvSize;
    VDMInfo.ErrorCode = TRUE;
    VDMInfo.VDMState =  fSeparateWow ? ASKING_FOR_SEPWOW_BINARY : ASKING_FOR_WOW_BINARY;
    VDMInfo.iTask = 0;
    VDMInfo.StdIn = 0;
    VDMInfo.StdOut = 0;
    VDMInfo.StdErr = 0;
    VDMInfo.CodePage = 0;
    VDMInfo.TitleLen = 0;
    VDMInfo.DesktopLen = 0;
    VDMInfo.CurDirectory = pszCurDir;
    VDMInfo.CurDirectoryLen = pWowInfo->CurDirSize;
    VDMInfo.Reserved = szSiReservedBuf;
    VDMInfo.ReservedLen = sizeof(szSiReservedBuf);

    ul = GetNextVDMCommand (&VDMInfo);

    if (ul) {

        //
        // BaseSrv will return TRUE with CmdSize == 0 if no more commands
        //
        if (VDMInfo.CmdSize == 0) {
            pWowInfo->CmdLineSize = 0;
            goto CleanUp;
        }

        //
        // If wowexec is the appname then we don't want to pass it back to
        // the existing instance of wowexec in a shared VDM since it will
        // basically do nothing but load and exit. Since it is not run we
        // need call ExitVDM to cleanup. Next we go back to look for more
        // commands.
        //
        if ((! fSeparateWow) && WOW32_strstr(VDMInfo.AppName, "wowexec.exe")) {
            ExitVDM(WOWVDM, VDMInfo.iTask);
            goto SkipWowExec;
        }

    }


    //
    // WOWEXEC will initially call with a guess of the correct environment
    // size. If he did not allocate enough then we will return the appropriate
    // size so that he can try again. WOWEXEC knows that we will require a
    // buffer twice the size specified. The environment can be up to 64k since
    // 16 bit LoadModule can only take a selector pointer to the environment.
    //

    if ( VDMInfo.EnviornmentSize > pWowInfo->EnvSize         ||
         VDMInfo.CmdSize > (USHORT)pWowInfo->CmdLineSize     ||
         VDMInfo.AppLen > (USHORT)pWowInfo->AppNameSize      ||
         VDMInfo.CurDirectoryLen > (ULONG)pWowInfo->CurDirSize )
       {

        // We return the size specified, but assume that WOWEXEC will double
        // it when allocating memory to allow for the string conversion/
        // expansion that might happen for international versions of NT.
        // See below where we uppercase and convert to OEM characters.
        DWORD dwEnvSize = 2 * (VDMInfo.EnviornmentSize +
                               VDMInfo.AppLen +
                               strlen(szProcessHistoryVar) + 2);
        if (0 == HIWORD(dwEnvSize)) {
            // Fit in a Word!
            pWowInfo->EnvSize = (WORD)(dwEnvSize / 2);
        } else {
            // Make it the max size (see 16 bit globalrealloc)
            pWowInfo->EnvSize = (65536-17)/2;
        }

        // Pass back other correct sizes required
        pWowInfo->CmdLineSize = VDMInfo.CmdSize;
        pWowInfo->AppNameSize = VDMInfo.AppLen;
        pWowInfo->CurDirSize = (USHORT)VDMInfo.CurDirectoryLen;
        ul = FALSE;
    }

    if ( ul ) {

        //
        // Boost the hour glass
        //

        ShowStartGlass (10000);

        //
        // Save away wShowWindow, hotkey and startup window position from
        // the STARTUPINFO structure.  We'll pass them over to UserSrv during
        // the new app's InitTask call.  The assumption here is that this
        // will be the last GetNextVDMCommand call before the call to InitTask
        // by the newly-created task.
        //

        dwLastHotkey = ParseHotkeyReserved(VDMInfo.Reserved);

        if (VDMInfo.StartupInfo.dwFlags & STARTF_USESHOWWINDOW) {
            pWowInfo->wShowWindow =
              (VDMInfo.StartupInfo.wShowWindow  == SW_SHOWDEFAULT)
              ? SW_SHOW : VDMInfo.StartupInfo.wShowWindow ;
        } else {
            pWowInfo->wShowWindow = SW_SHOW;
        }

        if (VDMInfo.StartupInfo.dwFlags & STARTF_USEPOSITION) {
            dwLastX = VDMInfo.StartupInfo.dwX;
            dwLastY = VDMInfo.StartupInfo.dwY;
        } else {
            dwLastX = dwLastY = (DWORD) CW_USEDEFAULT;
        }

        if (VDMInfo.StartupInfo.dwFlags & STARTF_USESIZE) {
            dwLastXSize = VDMInfo.StartupInfo.dwXSize;
            dwLastYSize = VDMInfo.StartupInfo.dwYSize;
        } else {
            dwLastXSize = dwLastYSize = (DWORD) CW_USEDEFAULT;
        }

        LOGDEBUG(4, ("WK32WowGetNextVdmCommand: HotKey: %u\n"
                     "    Window Pos:  (%u,%u)\n"
                     "    Window Size: (%u,%u)\n",
                     dwLastHotkey, dwLastX, dwLastY, dwLastXSize, dwLastYSize));


        // 20-Jan-1994 sudeepb
        // Following callout is for inheriting the directories for the new
        // task. After this we mark the CDS's to be invalid which will force
        // new directories to be pickedup on need basis. See bug#1995 for
        // details.

        W32RefreshCurrentDirectories (pszEnv32);

        // Save iTask
        // When Server16 does the Exec Call we can put this Id into task
        // Structure.  When the WOW app dies we can notify Win32 using this
        // taskid so if any apps are waiting they will get notified.

        iW32ExecTaskId = VDMInfo.iTask;

        //
        // krnl expects ANSI strings!
        //

        OemToChar(pszCmd, pszCmd);
        OemToChar(pszAppName, pszAppName);

        //
        // So should the current directory be OEM or Ansi?
        //


        pWowInfo->iTask = VDMInfo.iTask;
        pWowInfo->CurDrive = VDMInfo.CurDrive;
        pWowInfo->EnvSize = (USHORT)VDMInfo.EnviornmentSize;


        // Uppercase the Environment KeyNames but leave the environment
        // variables in mixed case - to be compatible with MS-DOS
        // Also convert environment to OEM character set

        // another thing that we do here is a fixup to temp/tmp variables
        // which  occurs via the provided ntvdm functions


        if (pszEnv32) {
            LPSTR pszProcessHistory = NULL; // null at first
            int   nProcessHistoryVarLen = strlen(szProcessHistoryVar);

            cmdCheckTempInit();

            for (pszTemp = pszEnv32;*pszTemp;pszTemp += (strlen(pszTemp) + 1)) {

                PSZ pEnv;

                // The MS-DOS Environment is OEM

                if (NULL == (pEnv = cmdCheckTemp(pszTemp))) {
                   pEnv = pszTemp;
                }

                //
                // check for process history variable
                //

                if (szProcessHistoryVar[0] == *pszTemp) { // quick check first
                   //
                   // might be a __process_history
                   //
                   if (NULL != (pTemp = WOW32_strchr(pszTemp, '=')) &&
                       (int)(pTemp - pszTemp) == nProcessHistoryVarLen &&
                       !WOW32_strnicmp(pszTemp, szProcessHistoryVar, nProcessHistoryVarLen)) {
                          pszProcessHistory = pszTemp;

                          // now skip the rest for this item and go to
                          // the next one. This var will be added later
                          // since we do not touch pszEnv we won't be
                          // adding this env var at this time

                          continue; // go all the way back and resume for loop

                   }
                }


                CharToOem(pEnv,pszEnv);

                // Ignore the NT specific Environment variables that start ==

                if (*pszEnv != '=') {
                    if (pTemp = WOW32_strchr(pszEnv,'=')) {
                        *pTemp = '\0';

                        // don't uppercase "windir" as it is lowercase for
                        // Win 3.1 and MS-DOS apps.

                       if (pTemp-pszEnv != 6 || WOW32_strncmp(pszEnv, "windir", 6))
                           WOW32_strupr(pszEnv);
                       *pTemp = '=';
                    }
                }
                pszEnv += (strlen(pszEnv) + 1);
            }

            // now add in the process history var
            // we have a pointer to it in the pszProcessHistory space
            if (NULL != pszProcessHistory) {
               // copy the variable
               CharToOem(pszProcessHistory, pszEnv);
               // advance the pointer
               pszEnv += strlen(pszEnv);
               // add semicolon
               *pszEnv++ = ';';
            }
            else {
               CharToOem(szProcessHistoryVar, pszEnv);
               pszEnv += strlen(pszEnv); // skip over the name
               // put in an equal sign
               *pszEnv++ = '=';
            }
            // copy app name if there
            CharToOem(pszAppName, pszEnv);
            pszEnv += strlen(pszEnv) + 1;

            // Environment is Double NULL terminated
            *pszEnv = '\0';

            // now sort it
            WOWSortEnvironmentStrings(pszEnv16);

        }
    }

  CleanUp:
    if (pszEnv32) {
        free_w(pszEnv32);
    }

    FLUSHVDMPTR(parg16->lpWowInfo, sizeof(WOWINFO), pWowInfo);
    FLUSHVDMPTR((ULONG)pWowInfo->lpCmdLine, pWowInfo->CmdLineSize, pszCmd);

    FREEVDMPTR(pszCmd);
    FREEVDMPTR(pszEnv);
    FREEVDMPTR(pszCurDir);
    FREEVDMPTR(pWowInfo);
    FREEARGPTR(parg16);
    RETURN(ul);
}

#endif

/*++

 WK32WOWInitTask - API Used to Create a New Task + Thread

 Routine Description:

    All the 16 bit initialization is completed, the app is loaded in memory and ready to go
    we come here to create a thread for this task.

    The current thread impersonates the new task, its running on the new tasks stack and it
    has its wTDB, this makes it easy for us to get a pointer to the new tasks stack and for it
    to have the correct 16 bit stack frame.   In order for the creator to continue correctly
    we set RET_TASKSTARTED on the stack.   Kernel16 will then not return to the new task
    but will know to restart the creator and put his thread ID and stack back.

    We ResetEvent so we can wait for the new thread to get going, this is important since
    we want the first YIELD call from the creator to yield to the newly created task.

    Special Case During Boot
    During the boot process the kernel will load the first app into memory on the main thread
    using the regular LoadModule.   We don't want the first app to start running until the kernel
    boot is completed so we can reuse the first thread.

 Arguments:
    pFrame - Points to the New Tasks Stack Frame

 Return Value:
    TRUE   - Successfully Created a Thread
    FALSE  - Failed to Create a New Task

--*/

ULONG FASTCALL WK32WOWInitTask(PVDMFRAME pFrame)
{
    VPVOID  vpStack;
    DWORD  dwThreadId;
    HANDLE hThread;

#if FASTBOPPING
    vpStack = FASTVDMSTACK();
#else
    vpStack = VDMSTACK();
#endif


    pFrame->wRetID = RET_TASKSTARTED;

       /*
        *  Suspend the timer thread on the startup of every task
        *  To allow resyncing of the dos time to the system time.
        *  When wowexec is the only task running the timer thread
        *  will remain suspended. When the new task actually intializes
        *  it will resume the timer thread, provided it is not wowexec.
        */
    if (nWOWTasks != 1)
        SuspendTimerThread();       // turns timer thread off

    if (fBoot) {
        W32Thread((LPVOID)vpStack);    // SHOULD NEVER RETURN

        WOW32ASSERTMSG(FALSE, "\nWOW32: WK32WOWInitTask ERROR - Main Thread Returning - Contact DaveHart\n");
        ExitVDM(WOWVDM, ALL_TASKS);
        ExitProcess(EXIT_FAILURE);
    }

    //
    // VadimB: remember parent's TDB
    //


    hThread = host_CreateThread(NULL,
                                8192,
                                W32Thread,
                                (LPVOID)vpStack,
                                CREATE_SUSPENDED,
                                &dwThreadId);

    ((PTDB)SEGPTR(pFrame->wTDB,0))->TDB_hThread = (DWORD) hThread;
    ((PTDB)SEGPTR(pFrame->wTDB,0))->TDB_ThreadID = dwThreadId;

    if ( hThread ) {

        WOW32VERIFY(DuplicateHandle(
                        GetCurrentProcess(),
                        hThread,
                        GetCurrentProcess(),
                        (HANDLE *) &ghTaskCreation,
                        0,
                        FALSE,
                        DUPLICATE_SAME_ACCESS
                        ));

    }

#ifdef DEBUG
    {
        char szModName[9];

        RtlCopyMemory(szModName, ((PTDB)SEGPTR(pFrame->wTDB,0))->TDB_ModName, 8);
        szModName[8] = 0;

        LOGDEBUG( hThread ? LOG_IMPORTANT : LOG_ALWAYS,
            ("\nWK32WOWInitTask: %s task %04x %s\n",
                hThread ? "created" : "ERROR failed to create",
                pFrame->wTDB,
                szModName
            ));
    }
#endif

    return hThread ? TRUE : FALSE;
}


/*++
 WK32YIELD - Yield to the Next Task

 Routine Description:

    Normal Case - A 16 bit task is running and wants to give up the CPU to any higher priority
    task that might want to run.   Since we are running with a non-preemptive scheduler apps
    have to cooperate.

 ENTRY
  pFrame - Not used

 EXIT
  Nothing

--*/

ULONG FASTCALL WK32Yield(PVDMFRAME pFrame)
{
    //
    // WARNING: wkgthunk.c's WOWYield16 export (part of the Generic Thunk
    //      interface) calls this thunk with a NULL pFrame.  If you
    //          change this function to use pFrame change WOWYield16 as
    //          well.
    //

    UNREFERENCED_PARAMETER(pFrame);

    BlockWOWIdle(TRUE);

    (pfnOut.pfnYieldTask)();

    BlockWOWIdle(FALSE);


    RETURN(0);
}


/*++
 WK32WowSyncNewTask - 

 Routine Description:

      Sync parent and child thread with apphelp (child thread
      could potentially be blocked by apphelp)
      
 EXIT
  for parent thread nothing
  for child thread 
      0  - continue running the app
      1  - wait in a loop
     -1  - exit the thread, user selected not to run this app 
                          , app is hard blocked
--*/

ULONG FASTCALL WK32WowSyncTask(PVDMFRAME pFrame)
{
    PTDB ptdb;

    //
    // Parent task (thread) comes here from loader
    // -ghTaskCreation is set to child thread handle
    // in Wk32WowInitTask, so it is guaranteed to be non NULL.
    // -when child task (thread) signals it from W32Thread and
    // parent returns immediately "never" going to the second 
    // part of this function
 
    if (ghTaskCreation) {
        DWORD dw;
        HANDLE ThreadEvents[2];

        ThreadEvents[0] = ghevWaitCreatorThread;
        ThreadEvents[1] = ghTaskCreation;
        ghTaskCreation = NULL;
        WOW32VERIFY( ResumeThread(ThreadEvents[1]) != (DWORD)-1 );  // ghTaskCreation

        dw = WaitForMultipleObjects(2, ThreadEvents, FALSE, INFINITE);
        if (dw != WAIT_OBJECT_0) {
            WOW32ASSERTMSGF(FALSE,
                ("\nWK32SyncNewTask: ERROR WaitInitTask %d gle %d\n\n", dw, GetLastError())
                );
            ResetEvent(ghevWaitCreatorThread);
        }

        CloseHandle(ThreadEvents[1]);  // ghTaskCreation

        WK32Yield(pFrame);
        return 0;
    }

    //
    // Child task (thread) comes here from StartWowTask 
    // - ghTaskCreation is NULL, so it skips the first part
    // of this function.
    //
    // -ghTaskAppHelp if set (in CheckAppHelpInfo) means wait for user input
    // to decide whether to run the task.
    // otherwise if gfTaskContinue is FALSE either zzzInitTask failed or AppHelpGetNtvdmInfo
    // told us to exit without showing AppHelp dialog
    // -if WaitForSingleObject timed out, return to 16bit to process hardware interrupts
    // then come back try again, apphelp is still waiting for user input

    if (ghTaskAppHelp) {
        DWORD dwResult;

        dwResult = WaitForSingleObject(ghTaskAppHelp,10);
        if (WAIT_TIMEOUT == dwResult) {
            return 1;  // back to 16-bit to loop and try again
        }
        if (WAIT_OBJECT_0 == dwResult &&
            GetExitCodeProcess(ghTaskAppHelp,&dwResult)  &&
            0 == dwResult)
          {
            gfTaskContinue = FALSE;
        }
        CloseHandle(ghTaskAppHelp);
        ghTaskAppHelp = NULL;
    }

    // 
    // gfTaskContinue is FALSE if user decided to abort the app (return -1)
    // otherwise continue to the app code (return 0)
    // 

    if (!gfTaskContinue) {
        return -1;
    }

    //
    // App is ready to run
    // if this app requires 256 color display mode, set it now
    //

    ptdb = (PTDB)SEGPTR(pFrame->wTDB,0);
    if (ptdb->TDB_WOWCompatFlagsEx & WOWCFEX_DISPMODE256){
        WK32ChangeDisplayMode(8);
    }

    // 
    // Some apps need to start from the exe file's directory
    // Whistler bug 281759
           
    if (CURRENTPTD()->dwWOWCompatFlags2 & WOWCF2_RESETCURDIR) {
        CURRENTPTD()->dwWOWCompatFlags2 &= ~WOWCF2_RESETCURDIR;
        DosWowSetCurrentDirectory((LPSTR)ptdb->TDB_Directory);
    }            
            
    return 0;
}




ULONG FASTCALL WK32OldYield(PVDMFRAME pFrame)
{

    UNREFERENCED_PARAMETER(pFrame);

    BlockWOWIdle(TRUE);

    (pfnOut.pfnDirectedYield)(DY_OLDYIELD);

    BlockWOWIdle(FALSE);


    RETURN(0);
}





/*++
 WK32ForegroundIdleHook - Supply WMU_FOREGROUNDIDLE message when system
                          (foreground "task") goes idle; support for int 2f

 Routine Description:

    This is the hook procedure for idle detection.  When the
    foregorund task goes idle, if the int 2f is hooked, then
    we will get control here and we call Wow16 to issue
    the int 2f:1689 to signal the idle condition to the hooker.

 ENTRY
    normal hook parameters: ignored

 EXIT
  Nothing

--*/

LRESULT CALLBACK WK32ForegroundIdleHook(int code, WPARAM wParam, LPARAM lParam)
{
    PARM16  Parm16;

    UNREFERENCED_PARAMETER(code);
    UNREFERENCED_PARAMETER(wParam);
    UNREFERENCED_PARAMETER(lParam);

    CallBack16(RET_FOREGROUNDIDLE, &Parm16, 0, 0);

    RETURN(0);
}


/*++
 WK32WowSetIdleHook - Set the hook so we will get notified when the
                   (foreground "task") goes idle; support for int 2f

 Routine Description:

    This sets the hook procedure for idle detection.  When the
    foregorund task goes idle, if the int 2f is hooked, then
    we will get control above and send a message to WOW so it can issue
    the int 2f:1689 to signal the idle condition to the hooker.

 ENTRY
    pFrame - not used

 EXIT
    The hook is set and it's handle is placed in to the per thread
    data ptd->hIdleHook.     0 is returned.  On
    failure, the hook is just not set (sorry), but a debug call is
    made.

--*/

ULONG FASTCALL WK32WowSetIdleHook(PVDMFRAME pFrame)
{
    PTD ptd;
    UNREFERENCED_PARAMETER(pFrame);

    ptd = CURRENTPTD();

    if (ptd->hIdleHook == NULL) {

        // If there is no hook already set then set a GlobaHook
        // It is important to set a GlobalHook otherwise we will not
        // Get accurate timing results with a LocalHook.

        ptd->hIdleHook = SetWindowsHookEx(WH_FOREGROUNDIDLE,
                                          WK32ForegroundIdleHook,
                                          hmodWOW32,
                                          0);

        WOW32ASSERTMSG(ptd->hIdleHook, "\nWK32WowSetIdleHook : ERROR failed to Set Idle Hook Proc\n\n");
    }
    RETURN(0);
}



/*++

 W32Thread - New Thread Starts Here

 Routine Description:

    A newly created thread starts here.   We Allocated the Per Task Data from
    the Threads Stack and point NtCurrentTeb()->WOW32Reserved at it, so that
    we can find it quickly when we dispatch an api or recieve a message from
    Win 32.

    NOTE - The Call to Win32 InitTask() does NOT return until we are in sync
    with the other 16 bit tasks in the non-preemptive scheduler.

    Once We have everything initialized we SetEvent to wake our Creator thread
    and then call Win32 to get in sync with the other tasks running in the
    non-preemptive scheduler.

    Special Case - BOOT
    We return (host_simulate) to the caller - kernel16, so he can complete
    his initialization and then reuse the same thread to start the first app
    (usually wowexec the wow shell).

    The second host_simulate call doesn't return until the app exits
    (see tasking.asm - ExitSchedule) at which point we tidy up the task and
    then kill this thread.   Win32 Non-Preemptive Scheduler will detect the
    thread going away and will then schedule another task.

 ENTRY
  16:16 to New Task Stack

 EXIT
  NEVER RETURNS - Thread Exits

--*/

DWORD W32Thread(LPVOID vpInitialSSSP)
{
    TD td;
    UNICODE_STRING  uImageName;
    WCHAR    wcImageName[MAX_VDMFILENAME];
    RTL_PERTHREAD_CURDIR    rptc;
    PVDMFRAME pFrame;
    PWOWINITTASK16 pArg16;
    PTDB     ptdb;
#if FASTBOPPING
#else
    USHORT SaveIp;
#endif

    RtlZeroMemory(&td, sizeof(TD));

    InitializeCriticalSection(&td.csTD);

    if (gptdShell == NULL) {

        //
        // This is the initial thread, free the temporary TD we used during
        // boot.
        //

        DeleteCriticalSection(&CURRENTPTD()->csTD);
        free_w( (PVOID) CURRENTPTD() );
        gptdShell = &td;

    } else if (pptdWOA) {

        //
        // See WK32WOWLoadModule32
        //

        *pptdWOA = &td;
        pptdWOA = NULL;
    }

    CURRENTPTD() = &td;

    if (fBoot) {
        td.htask16 = 0;
        td.hInst16 = 0;
        td.hMod16  = 0;

        {
            VPVOID vpStack;

#if FASTBOPPING
            vpStack = FASTVDMSTACK();
#else
            vpStack = VDMSTACK();
#endif

            GETFRAMEPTR(vpStack, pFrame);

            pFrame->wAX = 1;

        }

#if FASTBOPPING
        CurrentMonitorTeb = NtCurrentTeb();
        FastWOWCallbackCall();
#else
        SaveIp = getIP();
        host_simulate();
        setIP(SaveIp);
#endif

    }

    //
    // Initialize Per Task Data
    //

    GETFRAMEPTR((VPVOID)vpInitialSSSP, pFrame);
    td.htask16 = pFrame->wTDB;
    ptdb = (PTDB)SEGPTR(td.htask16,0);
    td.VDMInfoiTaskID = iW32ExecTaskId;
    iW32ExecTaskId = (UINT)-1;
    td.vpStack = (VPVOID)vpInitialSSSP;
    td.dwThreadID = GetCurrentThreadId();
    if (THREADID32(td.htask16) == 0) {
        ptdb->TDB_ThreadID = td.dwThreadID;
    }

    EnterCriticalSection(&gcsWOW);
    td.ptdNext = gptdTaskHead;
    gptdTaskHead = &td;
    LeaveCriticalSection(&gcsWOW);
    td.hrgnClip = (HRGN)NULL;

    td.ulLastDesktophDC = 0;
    td.pWOAList = NULL;

    //
    //  NOTE - Add YOUR Per Task Init Code HERE
    //

    td.hIdleHook = NULL;

    //
    // Set the CSR batching limit to whatever was specified in
    // win.ini [WOW] BatchLimit= line, which we read into
    // dwWOWBatchLimit during WOW startup in W32Init.
    //
    // This code allows the performance people to benchmark
    // WOW on an API for API basis without having to use
    // a private CSRSRV.DLL with a hardcoded batch limit of 1.
    //
    // Note:  This is a per-thread attribute, so we must call
    // ====   GdiSetBatchLimit during the initialization of
    //        each thread that could call GDI on behalf of
    //        16-bit code.
    //

    if (dwWOWBatchLimit) {

        DWORD  dwOldBatchLimit;

        dwOldBatchLimit = GdiSetBatchLimit(dwWOWBatchLimit);

        LOGDEBUG(LOG_ALWAYS,("WOW W32Thread: Changed thread %d GDI batch limit from %u to %u.\n",
                     nWOWTasks+1, dwOldBatchLimit, dwWOWBatchLimit));
    }


    nWOWTasks++;


    //
    //  Inittask: requires ExpWinVer and Modulename
    //

    {
        DWORD    dwExpWinVer;
        DWORD    dwCompatFlags;
        BYTE     szModName[9]; // modname = 8bytes + nullchar
        BYTE     szBaseFileName[9]; // 8.3 filename minus .3
        LPSTR    pszBaseName;
        CHAR     szFilePath[256];
        LPBYTE   lpModule;
        PWOWINITTASK16 pArg16;
        PTDB     ptdb;
        WORD     wPathOffset;
        BYTE     bImageNameLength;
        ULONG    ulLength;
        BOOL     fRet;
        DWORD    dw;
        HANDLE   hThread;

        GETARGPTR(pFrame, sizeof(WOWINITTASK16), pArg16);
        ptdb = (PTDB)SEGPTR(td.htask16,0);
        td.hInst16 = ptdb->TDB_Module;
        td.hMod16 = ptdb->TDB_pModule;
        hThread = (HANDLE)ptdb->TDB_hThread;
        dwExpWinVer = FETCHDWORD(pArg16->dwExpWinVer);
        RtlCopyMemory(szModName, ptdb->TDB_ModName, 8);
        dwCompatFlags = *((DWORD *)&ptdb->TDB_CompatFlags);
        
        szModName[8] = (BYTE)0;

#define NE_PATHOFFSET   10      // Offset to file path stuff

        dw = MAKELONG(0,td.hMod16);
        GETMISCPTR( dw, lpModule );

        wPathOffset = *((LPWORD)(lpModule+NE_PATHOFFSET));

        bImageNameLength = *(lpModule+wPathOffset);

        bImageNameLength -= 8;      // 7 bytes of trash at the start
        wPathOffset += 8;

        RtlCopyMemory(szFilePath, lpModule + wPathOffset, bImageNameLength);
        szFilePath[bImageNameLength] = 0;

        RtlMultiByteToUnicodeN( wcImageName,
                                sizeof(wcImageName),
                                &ulLength,
                                szFilePath,
                                bImageNameLength );

        wcImageName[bImageNameLength] = L'\0';
        RtlInitUnicodeString(&uImageName, wcImageName);

        LOGDEBUG(2,("WOW W32Thread: setting image name to %ws\n",
                    wcImageName));

        RtlAssociatePerThreadCurdir( &rptc, NULL, &uImageName, NULL );

        FREEMISCPTR( lpModule );

        //
        // Add this task to the list of 16-bit tasks
        //

        AddTaskSharedList(td.htask16, td.hMod16, szModName, szFilePath);

        //
        // Get the base part of the filename, no path or extension,
        // for InitTask to use looking for setup program names.
        // Often this is the same as the module name, to shortcut
        // redundant checks we only pass the base filename if it
        // differs from the module name.
        //

        if (!(pszBaseName = WOW32_strrchr(szFilePath, '\\'))) {
            WOW32ASSERTMSG(FALSE, "W32Thread assumed path was fully qualified, no '\\'.\n");
        }
        pszBaseName++; // skip over backslash to point at start of base filename.
        RtlCopyMemory(szBaseFileName, pszBaseName, sizeof(szBaseFileName) - 1);
        szBaseFileName[sizeof(szBaseFileName) - 1] = 0;
        if (pszBaseName = WOW32_strchr(szBaseFileName, '.')) {
            *pszBaseName = 0;
        }
        if (!WOW32_strcmp(szBaseFileName, szModName)) {
            pszBaseName = NULL;
        } else {
            pszBaseName = szBaseFileName;
        }


        //
        // Initialize WOW compatibility flags from the database
        //
        //
        gfTaskContinue = CheckAppHelpInfo(&td,szFilePath,szModName);

        //
        // We now inherit the WOW compatibility flags from the parent's TDB. Right
        // now We are only interested in inheriting the WOWCF_UNIQUEHDCHWND flag
        // in order to really fix a bug with MS Publisher. Each Wizard and Cue Cards
        // that ship with mspub is its own task and would require MANY new
        // compatibility flag entries in the registry. This mechanism allows anything
        // spawned from an app that has WOWCF_UNIQUEHDCHWND to have
        // WOWCF_UNIQUEHDCHWND.
        if (ptdb->TDB_WOWCompatFlags & LOWORD(WOWCF_UNIQUEHDCHWND)) {
            td.dwWOWCompatFlags |= LOWORD(WOWCF_UNIQUEHDCHWND);
        }

        // Exchange setup and Return of Arcade module names conflict (bootstrp)
        // so set GACF_HACKWINFLAGS if it is specified in WOWCF2_
        // Whistler bug 384201

        if (td.dwWOWCompatFlags2 & WOWCF2_HACKWINFLAGS) {
            ptdb->TDB_CompatFlags |= LOWORD(GACF_HACKWINFLAGS);
        }

        // 
        // Some apps need to start from the exe file's directory
        // Whistler bug 281759 (check wowsynctask also)

        if(td.dwWOWCompatFlags2 & WOWCF2_RESETCURDIR) {         
           if (pszBaseName = WOW32_strrchr(szFilePath, '\\')) {
               *pszBaseName = 0;
               WOW32_strncpy(ptdb->TDB_Directory,szFilePath,64);
           }
           ptdb->TDB_Directory[64]='\0';
        }             

        ptdb->TDB_WOWCompatFlags = LOWORD(td.dwWOWCompatFlags);
        ptdb->TDB_WOWCompatFlags2 = HIWORD(td.dwWOWCompatFlags);
        ptdb->TDB_WOWCompatFlagsEx = LOWORD(td.dwWOWCompatFlagsEx);
        ptdb->TDB_WOWCompatFlagsEx2 = HIWORD(td.dwWOWCompatFlagsEx);
#ifdef FE_SB
        ptdb->TDB_WOWCompatFlagsJPN = LOWORD(td.dwWOWCompatFlagsFE);
        ptdb->TDB_WOWCompatFlagsJPN2 = HIWORD(td.dwWOWCompatFlagsFE);
#endif  // FE_SB

       // Enable the special VDMAllocateVirtualMemory strategy in NTVDM.
       if (td.dwWOWCompatFlagsEx & WOWCFEX_FORCEINCDPMI) {
#ifdef i386
           DpmiSetIncrementalAlloc(TRUE);
#else
           SetWOWforceIncrAlloc(TRUE);
#endif
       }
       
       FREEVDMPTR(ptdb);

        // Init task forces us to the active task in USER
        // and does ShowStartGlass, so new app gets focus correctly
        dw = 0;
        do {
            if (dw) {
                Sleep(dw * 50);
            }

            fRet = (pfnOut.pfnInitTask)(dwExpWinVer,
                                        dwCompatFlags,
                                        td.dwUserWOWCompatFlags,
                                        szModName,
                                        pszBaseName,
                                        td.htask16 | HTW_IS16BIT,
                                        dwLastHotkey,
                                        fSeparateWow ? 0 : td.VDMInfoiTaskID,
                                        dwLastX,
                                        dwLastY,
                                        dwLastXSize,
                                        dwLastYSize
                                        );
        } while (dw++ < 6 && !fRet);

        if (!fRet) {
            LOGDEBUG(LOG_ALWAYS,
                     ("\n%04X task, PTD address %08X InitTaskFailed\n",
                     td.htask16,
                     &td)
                     );
            if(ghTaskAppHelp) {
               CloseHandle(ghTaskAppHelp);
               ghTaskAppHelp = NULL;
               gfTaskContinue = FALSE;
            }
        }

        dwLastHotkey = 0;
        dwLastX = dwLastY = dwLastXSize = dwLastYSize = (DWORD) CW_USEDEFAULT;

        if (fBoot) {

            fBoot = FALSE;

            //
            // This call needs to happen after WOWExec's InitTask call so that
            // USER sees us as expecting Windows version 3.10 -- otherwise they
            // will fail some of the LoadCursor calls.
            //

            InitStdCursorIconAlias();

        } else {

            //
            // Syncronize the new thread with the creator thread.
            // Wake our creator thread
            //

            WOW32VERIFY(SetEvent(ghevWaitCreatorThread));
        }

        td.hThread = hThread;
        LOGDEBUG(2,("WOW W32Thread: New thread ready for execution\n"));

        // turn the timer thread on if its not for the first task
        // which we presume to be wowexec
        if (nWOWTasks != 1) {
            ResumeTimerThread();
        }

        FREEARGPTR(pArg16);
    }

    FREEVDMPTR(pFrame);
    GETFRAMEPTR((VPVOID)vpInitialSSSP, pFrame);
    WOW32ASSERT(pFrame->wTDB == td.htask16);

#if FASTBOPPING
    SETFASTVDMSTACK((VPVOID)vpInitialSSSP);
#else
    SETVDMSTACK(vpInitialSSSP);
#endif
    pFrame->wRetID = RET_RETURN;


    //
    //  Let user set breakpoints before Starting App
    //

    if ( IsDebuggerAttached() ) {

        GETARGPTR(pFrame, sizeof(WOWINITTASK16), pArg16);
        DBGNotifyNewTask((LPVOID)pArg16, OFFSETOF(VDMFRAME,bArgs) );
        FREEARGPTR(pArg16);

        if (flOptions & OPT_BREAKONNEWTASK) {

            LOGDEBUG(
                LOG_ALWAYS,
                ("\n%04X %08X task is starting, PTD address %08X, type g to continue\n\n",
                td.htask16,
                pFrame->vpCSIP,
                &td));

            DebugBreak();
        }
    }


    //
    //   Start APP
    //
    BlockWOWIdle(FALSE);

#ifdef DEBUG
    // BUGBUG: HACK ALERT
    // This code has been added to aid in debugging a problem that only
    // seems to occur on MIPS chk
    // What appears to be happening is that the SS:SP is set correctly
    // above, but sometime later, perhaps during the "BlockWOWIdle" call,
    // the emulator's flat stack pointer ends up getting reset to WOWEXEC's
    // stack. The SETVDMSTACK call below will reset the values we want so
    // that the user can continue normally.
    WOW32ASSERTMSG(LOWORD(vpInitialSSSP)==getSP(), "WOW32: W32Thread Error - SP is invalid!\n");
    SETVDMSTACK(vpInitialSSSP);
#endif

#if NO_W32TRYCALL
    {
    extern INT W32FilterException(INT, PEXCEPTION_POINTERS);
    }
    try {
#endif
#if FASTBOPPING
        RtlCopyMemory(NtCurrentTeb()->Vdm, ((PTEB)CurrentMonitorTeb)->Vdm, sizeof(VDM_TIB));
        CurrentMonitorTeb = NtCurrentTeb();
        FastWOWCallbackCall();
#else
        SaveIp = getIP();
        host_simulate();
        setIP(SaveIp);
#endif
#if NO_W32TRYCALL
    } except (W32FilterException(GetExceptionCode(),
                                 GetExceptionInformation())) {
    }
#endif
    //
    //  We should Never Come Here, an app should get terminated via calling wk32killtask thunk
    //  not by doing an unsimulate call.
    //

#ifdef DEBUG
    WOW32ASSERTMSG(FALSE, "WOW32: W32Thread Error - Too many unsimulate calls\n");
#else
    if (IsDebuggerAttached() && (flOptions & OPT_DEBUG)) {
        DbgBreakPoint();
    }
#endif

    W32DestroyTask(&td);
    host_ExitThread(EXIT_SUCCESS);
    return 0;
}


/* WK32KillTask - Force the Distruction of the Current Thread
 *
 * Called When App Does an Exit
 * If there is another active Win16 app then USER32 will schedule another
 * task.
 *
 * ENTRY
 *
 * EXIT
 *  Never Returns - We kill the process
 *
 */

ULONG FASTCALL WK32WOWKillTask(PVDMFRAME pFrame)
{
    UNREFERENCED_PARAMETER(pFrame);

    CURRENTPTD()->dwFlags &= ~TDF_FORCETASKEXIT;
    W32DestroyTask(CURRENTPTD());
    RemoveTaskSharedList();
    host_ExitThread(EXIT_SUCCESS);
    return 0;  // to quiet compiler, never executed.
}


/*++

 W32RemoteThread - New Remote Thread Starts Here

 Routine Description:

    The debugger needs to be able to call back into 16-bit code to
    execute some toolhelp functions.  This function is provided as a remote
    interface to calling 16-bit functions.

 ENTRY
  16:16 to New Task Stack

 EXIT
  NEVER RETURNS - Thread Exits

--*/

VDMCONTEXT  vcRemote;
VDMCONTEXT  vcSave;
VPVOID      vpRemoteBlock = (DWORD)0;
WORD        wPrevTDB = 0;
DWORD       dwPrevEBP = 0;

DWORD W32RemoteThread(VOID)
{
    TD td;
    PVDMFRAME pFrame;
    HANDLE      hThread;
    NTSTATUS    Status;
    THREAD_BASIC_INFORMATION ThreadInfo;
    OBJECT_ATTRIBUTES   obja;
    VPVOID      vpStack;

    RtlZeroMemory(&td, sizeof(TD));

    // turn the timer thread off to resync dos time
    if (nWOWTasks != 1)
        SuspendTimerThread();

    Status = NtQueryInformationThread(
        NtCurrentThread(),
        ThreadBasicInformation,
        (PVOID)&ThreadInfo,
        sizeof(THREAD_BASIC_INFORMATION),
        NULL
        );
    if ( !NT_SUCCESS(Status) ) {
#if DBG
        DbgPrint("NTVDM: Could not get thread information\n");
        DbgBreakPoint();
#endif
        return( 0 );
    }

    InitializeObjectAttributes(
            &obja,
            NULL,
            0,
            NULL,
            0 );


    Status = NtOpenThread(
                &hThread,
                THREAD_SET_CONTEXT
                  | THREAD_GET_CONTEXT
                  | THREAD_QUERY_INFORMATION,
                &obja,
                &ThreadInfo.ClientId );

    if ( !NT_SUCCESS(Status) ) {
#if DBG
        DbgPrint("NTVDM: Could not get open thread handle\n");
        DbgBreakPoint();
#endif
        return( 0 );
    }

    cpu_createthread( hThread, NULL );

    Status = NtClose( hThread );
    if ( !NT_SUCCESS(Status) ) {
#if DBG
        DbgPrint("NTVDM: Could not close thread handle\n");
        DbgBreakPoint();
#endif
        return( 0 );
    }

    InitializeCriticalSection(&td.csTD);

    CURRENTPTD() = &td;

    //
    // Save the current state (for future callbacks)
    //
    vcSave.SegSs = getSS();
    vcSave.SegCs = getCS();
    vcSave.SegDs = getDS();
    vcSave.SegEs = getES();
    vcSave.Eax   = getAX();
    vcSave.Ebx   = getBX();
    vcSave.Ecx   = getCX();
    vcSave.Edx   = getDX();
    vcSave.Esi   = getSI();
    vcSave.Edi   = getDI();
    vcSave.Ebp   = getBP();
    vcSave.Eip   = getIP();
    vcSave.Esp   = getSP();
#if FASTBOPPING
    {
        extern DWORD    saveebp32;

        dwPrevEBP = saveebp32;
    }
#endif

    wPrevTDB = *pCurTDB;

    //
    // Now prepare for the callback.  Set the registers such that it looks
    // like we are returning from the WOWKillRemoteTask call.
    //
    setDS( (WORD)vcRemote.SegDs );
    setES( (WORD)vcRemote.SegEs );
    setAX( (WORD)vcRemote.Eax );
    setBX( (WORD)vcRemote.Ebx );
    setCX( (WORD)vcRemote.Ecx );
    setDX( (WORD)vcRemote.Edx );
    setSI( (WORD)vcRemote.Esi );
    setDI( (WORD)vcRemote.Edi );
    setBP( (WORD)vcRemote.Ebp );
#if FASTBOPPING

    vpStack = MAKELONG( LOWORD(vcRemote.Esp), LOWORD(vcRemote.SegSs) );

    SETFASTVDMSTACK( vpStack );

#else
    setIP( (WORD)vcRemote.Eip );
    setSP( (WORD)vcRemote.Esp );
    setSS( (WORD)vcRemote.SegSs );
    setCS( (WORD)vcRemote.SegCs );
    vpStack = VDMSTACK();
#endif

    //
    // Initialize Per Task Data
    //
    GETFRAMEPTR(vpStack, pFrame);

    td.htask16 = pFrame->wTDB;
    td.VDMInfoiTaskID = -1;
    td.vpStack = vpStack;
    td.pWOAList = NULL;

    //
    //  NOTE - Add YOUR Per Task Init Code HERE
    //

    nWOWTasks++;

    // turn the timer thread on
    if (nWOWTasks != 1)
        ResumeTimerThread();


    pFrame->wRetID = RET_RETURN;

    pFrame->wAX = (WORD)TRUE;
    pFrame->wDX = (WORD)0;

    //
    //   Start Callback
    //
#if FASTBOPPING
    CurrentMonitorTeb = NtCurrentTeb();
    FastWOWCallbackCall();
#else
    host_simulate();
    setIP((WORD)vcSave.Eip);
#endif

    //
    //  We should Never Come Here, an app should get terminated via calling wk32wowkilltask thunk
    //  not by doing an unsimulate call.
    //

#ifdef DEBUG
    WOW32ASSERTMSG(FALSE, "WOW32: W32RemoteThread Error - Too many unsimulate calls");
#else
    if (IsDebuggerAttached() && (flOptions & OPT_DEBUG)) {
        DbgBreakPoint();
    }
#endif

    W32DestroyTask(&td);
    host_ExitThread(EXIT_SUCCESS);
    return 0;
}

//
// lives in dos/dem/demlfn.c
//
extern VOID demLFNCleanup(VOID);

/* W32FreeTask - Per Task Cleanup
 *
 *  Put any 16-bit task clean-up code here.  The remote thread for debugging
 *  is a 16-bit task, but has no real 32-bit thread associated with it, until
 *  the debugger creates it.  Then it is created and destroyed in special
 *  ways, see W32RemoteThread and W32KillRemoteThread.
 *
 * ENTRY
 *  Per Task Pointer
 *
 * EXIT
 *  None
 *
 */
VOID W32FreeTask( PTD ptd )
{
    PWOAINST pWOA, pWOANext;

    nWOWTasks--;

    if (nWOWTasks < 2)
        SuspendTimerThread();

    // Disable the special VDMAllocateVirtualMemory strategy in NTVDM.
    if (CURRENTPTD()->dwWOWCompatFlagsEx & WOWCFEX_FORCEINCDPMI) {
#ifdef i386
        DpmiSetIncrementalAlloc(FALSE);
#else
        SetWOWforceIncrAlloc(FALSE);
#endif
    }

    // Free all DCs owned by the current task

    FreeCachedDCs(ptd->htask16);

    // Unload network fonts

    if( CURRENTPTD()->dwWOWCompatFlags & WOWCF_UNLOADNETFONTS )
    {
        UnloadNetworkFonts( (UINT)CURRENTPTD() );
    }

    // Free all timers owned by the current task

    DestroyTimers16(ptd->htask16);

    // clean up comm support

    FreeCommSupportResources(ptd->dwThreadID);

    // remove the hacks for this task from the FormFeedHackList (see wgdi.c)
    FreeTaskFormFeedHacks(ptd->htask16);

    // Cleanup WinSock support.

    if (WWS32IsThreadInitialized) {
        WWS32TaskCleanup();
    }

    // Free all local resource info owned by the current task

    DestroyRes16(ptd->htask16);

    // Unhook all hooks and reset their state.

    W32FreeOwnedHooks(ptd->htask16);

    // Free all the resources of this task

    FreeCursorIconAlias(ptd->htask16,CIALIAS_HTASK | CIALIAS_TASKISGONE);

    // Free accelerator aliases

    DestroyAccelAlias(ptd->htask16);

    // Remove idle hook, if any has been installed.

    if (ptd->hIdleHook != NULL) {
        UnhookWindowsHookEx(ptd->hIdleHook);
        ptd->hIdleHook = NULL;
    }

    // Free Special thunking list for this task (wparam.c)

    FreeParamMap(ptd->htask16);

    // cleanup lfn search handles and other lfn-related stuff
    demLFNCleanup();

    // Free WinOldAp tracking structures for this thread.

    EnterCriticalSection(&ptd->csTD);

    if (pWOA = ptd->pWOAList) {
        ptd->pWOAList = NULL;
        while (pWOA) {
            pWOANext = pWOA->pNext;
            free_w(pWOA);
            pWOA = pWOANext;
        }
    }

    LeaveCriticalSection(&ptd->csTD);
}



/* WK32KillRemoteTask - Force the Distruction of the Current Thread
 *
 * Called When App Does an Exit
 * If there is another active Win16 app then USER32 will schedule another
 * task.
 *
 * ENTRY
 *
 * EXIT
 *  Never Returns - We kill the process
 *
 */

ULONG FASTCALL WK32KillRemoteTask(PVDMFRAME pFrame)
{
    PWOWKILLREMOTETASK16 pArg16;
    WORD        wSavedTDB;
    PTD         ptd = CURRENTPTD();
    LPBYTE      lpNum_Tasks;

    //
    // Save the current state (for future callbacks)
    //
    vcRemote.SegDs = getDS();
    vcRemote.SegEs = getES();
    vcRemote.Eax   = getAX();
    vcRemote.Ebx   = getBX();
    vcRemote.Ecx   = getCX();
    vcRemote.Edx   = getDX();
    vcRemote.Esi   = getSI();
    vcRemote.Edi   = getDI();
    vcRemote.Ebp   = getBP();
#if FASTBOPPING
    {
        extern DWORD saveip16;
        extern DWORD savecs16;
        VPVOID       vpStack;

        vcRemote.Eip   = saveip16;
        vcRemote.SegCs = savecs16;
        vpStack = FASTVDMSTACK();

        vcRemote.SegSs = HIWORD(vpStack);
        vcRemote.Esp   = LOWORD(vpStack);
    }
#else
    vcRemote.Eip   = getIP();
    vcRemote.Esp   = getSP();
    vcRemote.SegSs = getSS();
    vcRemote.SegCs = getCS();
#endif

    W32FreeTask(CURRENTPTD());

    if ( vpRemoteBlock ) {

        wSavedTDB = ptd->htask16;
        ptd->htask16 = wPrevTDB;
        pFrame->wTDB = wPrevTDB;

        // This is a nop callback just to make sure that we switch tasks
        // back for the one we were on originally.
        GlobalUnlockFree16( 0 );

        GETFRAMEPTR(ptd->vpStack, pFrame);

        pFrame->wTDB = ptd->htask16 = wSavedTDB;

        //
        // We must be returning from a callback, restore the previous
        // context info.   Don't worry about flags, they aren't needed.
        //
        setSS( (WORD)vcSave.SegSs );
        setCS( (WORD)vcSave.SegCs );
        setDS( (WORD)vcSave.SegDs );
        setES( (WORD)vcSave.SegEs );
        setAX( (WORD)vcSave.Eax );
        setBX( (WORD)vcSave.Ebx );
        setCX( (WORD)vcSave.Ecx );
        setDX( (WORD)vcSave.Edx );
        setSI( (WORD)vcSave.Esi );
        setDI( (WORD)vcSave.Edi );
        setBP( (WORD)vcSave.Ebp );
        setIP( (WORD)vcSave.Eip );
        setSP( (WORD)vcSave.Esp );
#if FASTBOPPING
        {
            extern DWORD    saveebp32;

            saveebp32 = dwPrevEBP;
        }
#endif
    } else {
        //
        // Decrement the count of 16-bit tasks so that the last one,
        // excluding the remote handler (WOWDEB.EXE) will remember to
        // call ExitKernel when done.
        //
        GETVDMPTR(vpnum_tasks, 1, lpNum_Tasks);

        *lpNum_Tasks -= 1;

        FREEVDMPTR(lpNum_Tasks);

        //
        // Remove this 32-bit thread from the list of tasks as well.
        //
        WK32DeleteTask( CURRENTPTD() );
    }

    GETARGPTR(pFrame, sizeof(WOWKILLREMOTETASK16), pArg16);

    //
    // Save the current state (for future callbacks)
    //
    vpRemoteBlock = FETCHDWORD(pArg16->lpBuffer);

    // Notify DBG that we have a remote thread address
    DBGNotifyRemoteThreadAddress( W32RemoteThread, vpRemoteBlock );

    FREEARGPTR(pArg16);

    host_ExitThread(EXIT_SUCCESS);
    return 0;  // never executed, keep compiler happy.
}


/* W32DestroyTask - Per Task Cleanup
 *
 *  Task destruction code here.  Put any 32-bit task cleanup code here
 *
 * ENTRY
 *  Per Task Pointer
 *
 * EXIT
 *  None
 *
 */

VOID W32DestroyTask( PTD ptd)
{

    LOGDEBUG(LOG_IMPORTANT,("W32DestroyTask: destroying task %04X\n", ptd->htask16));

    // Inform Hung App Support

    SetEvent(ghevWaitHungAppNotifyThread);

    // Free all information pertinant to this 32-bit thread
    W32FreeTask( ptd );

    // delete the cliprgn used by GetClipRgn if it exists

    if (ptd->hrgnClip != NULL)
    {
        DeleteObject(ptd->hrgnClip);
        ptd->hrgnClip = NULL;
    }

    // Report task termination to Win32 - incase someone is waiting for us
    // LATER - fix Win32 so we don't have to report it.


    if (nWOWTasks == 0) {   // If we're the last one out, turn out the lights & tell Win32 WOWVDM is history.
        ptd->VDMInfoiTaskID = -1;
        ExitVDM(WOWVDM, ALL_TASKS);          // Tell Win32 All Tasks are gone.
    }
    else if (ptd->VDMInfoiTaskID != -1 ) {  // If 32 bit app is waiting for us - then signal we are done
        ExitVDM(WOWVDM, ptd->VDMInfoiTaskID);
    }
    ptd->dwFlags &= ~TDF_IGNOREINPUT;

    if (!(ptd->dwFlags & TDF_TASKCLEANUPDONE)) {
        (pfnOut.pfnWOWCleanup)(HINSTRES32(ptd->hInst16), (DWORD) ptd->htask16);
    }


    // Remove this task from the linked list of tasks

    WK32DeleteTask(ptd);

    // Close This Apps Thread Handle

    if (ptd->hThread) {
        CloseHandle( ptd->hThread );
    }

    DeleteCriticalSection(&ptd->csTD);
}

/***************************************************************************\
* WK32DeleteTask
*
* This function removes a task from the task list.
*
* History:
* Borrowed From User32 taskman.c - mattfe aug 5 92
\***************************************************************************/

void WK32DeleteTask(
    PTD ptdDelete)
{
    PTD ptd, ptdPrev;
    int i;

    EnterCriticalSection(&gcsWOW);
    ptd = gptdTaskHead;
    ptdPrev = NULL;

    /*
     * If this app changed display settings revert back
     *
     */
    if(ptdDelete->dwWOWCompatFlagsEx & WOWCFEX_DISPMODE256){
       WK32RevertDisplayMode();
    }

    /*
     * If cleanup environment data
     *
     */

    if (ptdDelete->pWowEnvData != NULL) {
        free_w(ptdDelete->pWowEnvData);
    }

    if (ptdDelete->pWowEnvDataChild != NULL) {
        free_w(ptdDelete->pWowEnvDataChild);
    }

    /*
     * Find the task to delete
     */
    while ((ptd != NULL) && (ptd != ptdDelete)) {
        ptdPrev = ptd;
        ptd = ptd->ptdNext;
    }

    /*
     * Error if we didn't find it.  If we did find it, remove it
     * from the chain.  If this was the head of the list, set it
     * to point to our next guy.
     */
    if (ptd == NULL) {
        LOGDEBUG(LOG_ALWAYS,("WK32DeleteTask:Task not found.\n"));
    } else if (ptdPrev != NULL) {
        ptdPrev->ptdNext = ptd->ptdNext;
    } else {
        gptdTaskHead = ptd->ptdNext;
    }


    /*
     *  Clean up DelayFree array in wkmem.c wk32virtualfree
     *
     */

    for (i=0; i < 4 ;i++) {

         if( NULL != glpvDelayFree[i]) {
            VirtualFree(glpvDelayFree[i],
            0,
            MEM_RELEASE);
            glpvDelayFree[i] = NULL;
         }
    }

    LeaveCriticalSection(&gcsWOW);
}


/*++
 WK32RegisterShellWindowHandle - 16 Bit Shell Registers is Hanle

 Routine Description:
    This routines saves the 32 bit hwnd for the 16 bit shell

    When WOWEXEC (16 bit shell) has sucessfully created its window it calls us to
    register its window handle.   If this is the shared WOW VDM, we register the
    handle with BaseSrv, which posts WM_WOWEXECSTARTAPP messages when Win16 apps
    are started.

 ENTRY
  pFrame -> hwndShell, 16 bit hwnd for shell (WOWEXEC)

 EXIT
  TRUE  - This is the shared WOW VDM
  FALSE - This is a separate WOW VDM

--*/

ULONG FASTCALL WK32RegisterShellWindowHandle(PVDMFRAME pFrame)
{
    register PWOWREGISTERSHELLWINDOWHANDLE16 parg16;
    WNDCLASS wc;
    NTSTATUS Status;

    GETARGPTR(pFrame, sizeof(WOWREGISTERSHELLWINDOWHANDLE16), parg16);

// gwFirstCmdShow is no longer used, and is available.
#if 0
    GETVDMPTR(parg16->lpwCmdShow, sizeof(WORD), pwCmdShow);
#endif

    if (ghwndShell) {

        //
        // The shared WOW is calling to deregister right before it
        // shuts down.
        //

        WOW32ASSERT( !fSeparateWow );
        WOW32ASSERT( !parg16->hwndShell );

        Status = RegisterWowExec(NULL);

        return NT_SUCCESS(Status);
    }

    ghwndShell = HWND32(parg16->hwndShell);
    ghShellTDB = pFrame->wTDB;

    //
    // Save away the hInstance for User32
    //

    GetClassInfo(0, (LPCSTR)0x8000, &wc);
    ghInstanceUser32 = wc.hInstance;

    // Fritz, when you get called about this it means that the GetClassInfo()
    // call above is returning with lpWC->hInstance == 0 instead of hModuser32.
    WOW32ASSERTMSGF((ghInstanceUser32),
                    ("WOW Error ghInstanceUser32 == NULL! Contact user folks\n"));

    //
    // If this is the shared WOW VDM, register the WowExec window handle
    // with BaseSrv so it can post WM_WOWEXECSTARTAPP messages.
    //

    if (!fSeparateWow) {
        RegisterWowExec(ghwndShell);
    }

    WOW32FaxHandler(WM_DDRV_SUBCLASS, (LPSTR)(HWND32(parg16->hwndFax)));

    FREEARGPTR(parg16);


    //
    // Return value is TRUE if this is the shared WOW VDM,
    // FALSE if this is a separate WOW VDM.
    //

    return fSeparateWow ? FALSE : TRUE;
}


//
// Worker routine for WK32WOWLoadModule32
//

VOID FASTCALL CleanupWOAList(HANDLE hProcess)
{
    PTD ptd;
    PWOAINST *ppWOA, pWOAToFree;

    EnterCriticalSection(&gcsWOW);

    ptd = gptdTaskHead;

    while (ptd) {

        EnterCriticalSection(&ptd->csTD);

        ppWOA = &(ptd->pWOAList);
        while (*ppWOA && (*ppWOA)->hChildProcess != hProcess) {
            ppWOA = &( (*ppWOA)->pNext );
        }

        if (*ppWOA) {

            //
            // We found the WOAINST structure to clean up.
            //

            pWOAToFree = *ppWOA;

            //
            // Remove this entry from the list
            //

            *ppWOA = pWOAToFree->pNext;

            free_w(pWOAToFree);

            LeaveCriticalSection(&ptd->csTD);

            break;   // no need to look at other tasks.

        }

        LeaveCriticalSection(&ptd->csTD);

        ptd = ptd->ptdNext;
    }

    LeaveCriticalSection(&gcsWOW);
}

// finds the environment variable pszName in environment block
// pointed to by pszEnv, *ppszVal receives the pointer to the value
// of the variable, if ppszVal is not NULL

PSZ WOWFindEnvironmentVar(PSZ pszName, PSZ pszEnv, PSZ* ppszVal)
{
   int nNameLen = strlen(pszName);
   PSZ pTemp; // ptr to '='

   if (NULL != pszEnv) {

      while ('\0' != *pszEnv) {
         // check the first char to be speedy
         if (*pszName == *pszEnv) {
            // compare the rest

            if (NULL != (pTemp = WOW32_strchr(pszEnv, '=')) &&
                (int)(pTemp - pszEnv) == nNameLen &&
                !WOW32_strnicmp(pszEnv, pszName, nNameLen)) {
                // found it
                if (NULL != ppszVal) {
                   *ppszVal = pTemp + 1; // next char
                }
                return(pszEnv);
            }
         }

         pszEnv += strlen(pszEnv) + 1;
      }
   }
   return(NULL); // not found
}

//
// returns size in characters
// of an env block
// pStrCount receives the number of env strings
//
DWORD WOWGetEnvironmentSize(PSZ pszEnv, LPDWORD pStrCount)
{
   PSZ pTemp = pszEnv;
   DWORD dwCount = 0;

   while ('\0' != *pTemp) {
      ++dwCount;
      pTemp += strlen(pTemp) + 1;
   }
   ++pTemp;

   if (NULL != pStrCount) {
      *pStrCount = dwCount;
   }
   return(DWORD)(pTemp - pszEnv);
}

BOOL WOWSortEnvironmentStrings(PSZ pszEnv)
{
   // we sort the strings as needed for CreateProcess
   // we implement bubble-sort which is an in-place sort using pointers
   DWORD dwStrCount;
   DWORD dwEnvSize = WOWGetEnvironmentSize(pszEnv, &dwStrCount);
   PSZ*  rgpEnv; // array of env ptrs
   INT*  rgLen; // length
   int   i, nLen;
   PSZ   pTemp, pEnv;
   PSZ   pEnd;
   BOOL  fSwap;

   // now we have the size and string count, allocate array of ptrs
   rgpEnv = (PSZ*)malloc_w(sizeof(PSZ) * dwStrCount);
   if (NULL == rgpEnv) {
      return(FALSE);
   }

   rgLen  = (INT*)malloc_w(sizeof(INT) * dwStrCount);
   if (NULL == rgLen) {
      free_w(rgpEnv);
      return(FALSE);
   }

   pEnv = (PSZ)malloc_w(dwEnvSize);
   if (NULL == pEnv) {
      free_w(rgpEnv);
      free_w(rgLen);
      return(FALSE);
   }

   // setup the pointers

   for (pTemp = pszEnv, i = 0; '\0' != *pTemp; pTemp += strlen(pTemp) + 1, ++i) {
      rgpEnv[i] = pTemp;
      pEnd = WOW32_strchr(pTemp, '=');
      rgLen[i] = (NULL == pEnd) ? strlen(pTemp) : (INT)(pEnd - pTemp);
   }


   // bubble - sort the strings using the pointers

   do {

      fSwap = FALSE;
      for (i = 0; i < (int)dwStrCount - 1; ++i) {
         // compare length, if no match use the longer string
         nLen = __max(rgLen[i], rgLen[i+1]);
         if (WOW32_strncmp(rgpEnv[i], rgpEnv[i+1], nLen) > 0) {
            fSwap = TRUE;
            pTemp = rgpEnv[i+1];
            rgpEnv[i+1] = rgpEnv[i];
            rgpEnv[i] = pTemp;
            nLen = rgLen[i+1];
            rgLen[i+1] = rgLen[i];
            rgLen[i] = nLen;
         }
      }

   } while (fSwap);

   //
   // now we have sorted the strings, have them rewritten in the buffer --
   //
   for (pTemp = pEnv, i = 0; i < (INT)dwStrCount; ++i) {
      strcpy(pTemp, rgpEnv[i]);
      pTemp += strlen(pTemp) + 1;
   }
   *pTemp = '\0';

   // now copy the env whole
   RtlCopyMemory(pszEnv, pEnv, dwEnvSize);

   // we are done now

   free_w(pEnv);
   free_w(rgLen);
   free_w(rgpEnv);
   return(TRUE);
}


BOOL WOWIsEnvVar(PSZ pszEnv, PSZ pszVarName, INT nNameLen)
{
    return !WOW32_strnicmp(pszEnv, pszVarName, nNameLen) && (*(pszEnv + nNameLen) == '=');
}


//
// Inherit parent environment, sanitizing it for all the "interesting" things
//

PSZ WOWCreateEnvBlock(PSZ pszParentEnv)
{

    LPSTR pszProcessHistory = WOWFindEnvironmentVar(szProcessHistoryVar, pszParentEnv, NULL);
    LPSTR pszCompatLayer    = WOWFindEnvironmentVar(szCompatLayerVar,    pszParentEnv, NULL);
    LPSTR pszShimFileLog    = WOWFindEnvironmentVar(szShimFileLogVar,    pszParentEnv, NULL);

    INT   nLenCompatLayer     = strlen(szCompatLayerVar);
    INT   nLenProcessHistory  = strlen(szProcessHistoryVar);
    INT   nLenShimFileLog     = strlen(szShimFileLogVar);
    INT   nLen;

    PSZ   pszNewEnv;
    PSZ   pTemp, pNew;

    DWORD dwSize;

    dwSize = WOWGetEnvironmentSize(pszParentEnv, NULL);

    if (NULL != pszProcessHistory) {
        dwSize -= strlen(pszProcessHistory) + 1;
    }

    if (NULL != pszCompatLayer) {
        dwSize -= strlen(pszCompatLayer)    + 1;
    }

    if (NULL != pszShimFileLog) {
        dwSize -= strlen(pszShimFileLog)    + 1;
    }

    //
    // allocate env block
    // filter out all the existing process_history and compat layer vars
    //
    pNew =
    pszNewEnv = (PSZ)malloc_w(dwSize);
    if (NULL == pszNewEnv) {
        return NULL;
    }

   // copy the env
    for (pTemp = pszParentEnv; '\0' != *pTemp; ) {

        nLen = strlen(pTemp);
        if (!WOWIsEnvVar(pTemp, szProcessHistoryVar, nLenProcessHistory) &&
            !WOWIsEnvVar(pTemp, szCompatLayerVar,    nLenCompatLayer) &&
            !WOWIsEnvVar(pTemp, szShimFileLogVar,    nLenShimFileLog)) {
            //
            // copy variable
            //
            strcpy(pNew, pTemp);
            pNew += nLen + 1;
        }
        pTemp += nLen + 1;
    }

    *pNew = '\0'; // done

    return pszNewEnv;

}

#if 0

//
// fn to create environment -- code to filter certain environment variables
// is located here, currently not used
//
// pszEnv - this is where most of the vars come from, except if ProcessHistory
// var is specified separately
// pszEnvWowApp -- this is where compat_layer and such come from
//
PSZ WOWCreateEnvBlock(PSZ pszEnvWowApp, PSZ pszEnv, PSZ pszProcessHistoryVal)
{
   // this will :
   // retrieve __PROCESS_HISTORY
   //          __COMPAT_LAYER
   //          SHIM_FILE_LOG
   // carry those over into the environment and insert them at the
   // appropriate place
   LPSTR pszProcessHistory = (NULL == pszProcessHistoryVal) ?
                                 WOWFindEnvironmentVar(szProcessHistoryVar, pszEnvWowApp, NULL) :
                                 pszProcessHistoryVal;
   LPSTR pszCompatLayer    = WOWFindEnvironmentVar(szCompatLayerVar,    pszEnvWowApp, NULL);
   LPSTR pszShimFileLog    = WOWFindEnvironmentVar(szShimFileLogVar,    pszEnvWowApp, NULL);

   //
   // get env size first
   //
   DWORD dwSize    = WOWGetEnvironmentSize(pszEnv, NULL); // size that we might need to expand
   DWORD dwNewSize = dwSize;

   PSZ pszNewEnv;
   PSZ pTemp, pNew;
   INT nLen;
   INT nLenCompatLayer       = strlen(szCompatLayerVar);
   INT nLenProcessHistory    = strlen(szProcessHistoryVar);
   INT nLenShimFileLog       = strlen(szShimFileLogVar);
   INT nLenCompatLayerVar    = 0;
   INT nLenProcessHistoryVar = 0;
   INT nLenShimFileLogVar    = 0;
   CHAR szCompatLayer[MAX_PATH + sizeof(szCompatLayerVar) + 1]; // buffer space for the compat layer + length of varname

   //
   // so we have the environment
   // expand it -- be safe here, allocate extra just in case
   //
   if (NULL != pszProcessHistory) {
      nLenProcessHistoryVar = strlen(pszProcessHistory);
      dwNewSize += nLenProcessHistoryVar + 1;
   }

   if (NULL == pszCompatLayer && fSeparateWow) { // if separate wow and no appcompat layer in child --
      nLen = wsprintf(szCompatLayer, "%s=", szCompatLayerVar);
      nLenCompatLayerVar = (INT)GetEnvironmentVariable(szCompatLayerVar,
                                                       szCompatLayer + nLen,
                                                       MAX_PATH);
      if (nLenCompatLayerVar && nLenCompatLayerVar <= MAX_PATH) {
         pszCompatLayer = szCompatLayer;
      }
   }

   if (NULL != pszCompatLayer) {
      nLenCompatLayerVar = strlen(pszCompatLayer);
      dwNewSize += nLenCompatLayerVar + 1;
   }

   if (NULL != pszShimFileLog) {
      nLenShimFileLogVar = strlen(pszShimFileLog);
      dwNewSize += nLenShimFileLogVar + 1;
   }

   // allocate env block
   // filter out all the existing process_history and compat layer vars
   pNew =
   pszNewEnv = (PSZ)malloc_w(dwNewSize);
   if (NULL == pszNewEnv) {
      return(NULL);
   }

   // copy the env
   for (pTemp = pszEnv; '\0' != *pTemp; ) {

      nLen = strlen(pTemp);
      if (WOW32_strnicmp(pTemp, szProcessHistoryVar, nLenProcessHistory) &&
          WOW32_strnicmp(pTemp, szCompatLayerVar, nLenCompatLayer) &&
          WOW32_strnicmp(pTemp, szShimFileLogVar, nLenShimFileLog)
          ) {
         // copy variable
         strcpy(pNew, pTemp);
         pNew += nLen + 1;
      }
      pTemp += nLen + 1;
   }

   // now copy vars
   if (NULL != pszProcessHistory) {
      strcpy(pNew, pszProcessHistory);
      pNew += nLenProcessHistoryVar + 1;
   }
   if (NULL != pszCompatLayer) {
      strcpy(pNew, pszCompatLayer);
      pNew += nLenCompatLayerVar + 1;
   }

   if (NULL != pszShimFileLog) {
      strcpy(pNew, pszShimFileLog);
      pNew += nLenShimFileLogVar + 1;
   }

   *pNew = '\0'; // final touch

   if (!WOWSortEnvironmentStrings(pszNewEnv)) {
      free_w(pszNewEnv);
      return(NULL);
   }

   return(pszNewEnv);
}

#endif // 0


ULONG FASTCALL WK32WowPassEnvironment(PVDMFRAME pFrame)
{
   PWOWPASSENVIRONMENT16 parg16;
   PDOSEXECBLOCK pParmBlk;      // exec param block
   PBYTE pExe;                  // parameter, passed from 16-bit
   PDOSPDB pDosPDB;
   PSZ pszEnvParentTask = NULL; // parent task env, the one that has __process_history
   PSZ pszEnvParent = NULL;     // parent env -- the one that has everything else
   PSZ pszEnv;                  // "forged" environment, 32-bit
   PSZ pszEnvTask;              // pointer to 16-bit task env -- the one that is passed back
   WORD wExeFlags;              // exe flags wow16\inc\newexe.inc
   WORD wExe16;                 // selector for exe header
   BYTE TDB_Flags = 0;          // tdb flags for the parent task
   DWORD dwEnvSize;             // new environment size
   DWORD dwSize;                // 16-bit memory block size
   HMEM16 hMemEnv;              // 16-bit memory selector
   PSZ pCmdLine = NULL;         // command line tail
   PSZ pModuleFileName;         // module filename, obtained from wExe16
   PSZ pProcessHistoryVar = NULL;  // process history, obtained from pszEnvParentTask
   PSZ pProcessHistory = NULL;     // process history, working ptr
   PSZ pTemp;                   // temp var, used while writing to the env
   DWORD nSizeModuleFileName;   // module file name variable size
   DWORD nSizeCmdLine = 0;      // command line tail size
   BOOL fFreeEnv = TRUE;        // free temp env flag (in case of failure, we use parent env)
   USHORT uCmdLineStart = 0;    // return value, offset of the command tail
   BOOL fCopy2 = TRUE;          // copy mod filename twice

   // get arg ptr
   GETARGPTR(pFrame, sizeof(*parg16), parg16);

   // retrieve arguments from 16-bit land
   wExe16 = FETCHWORD(parg16->pExe);
   pExe = (PBYTE)SEGPTR(wExe16, 0);
   GETVDMPTR(FETCHDWORD(parg16->pParmBlk), sizeof(DOSEXECBLOCK), pParmBlk);
   GETPSZPTR(FETCHDWORD(pParmBlk->lpcmdline), pCmdLine); // pointer
   pDosPDB = SEGPTR(FETCHWORD(parg16->cur_DOS_PDB), 0);

    if (*pCurTDB) { // extract Parent task environment info
       PTDB pTDB;

       pTDB = (PTDB)SEGPTR(*pCurTDB, 0); // tdb in windows
       if (NULL != pTDB && TDB_SIGNATURE == pTDB->TDB_sig) {
          // valid tdb, retrieve env ptr
#if 0

          pPSP = (PDOSPDB)SEGPTR(pTDB->TDB_PDB, 0); // psp
          if (NULL != pPSP) {
             pszEnvParentTask = (PSZ)SEGPTR(pPSP->PDB_environ, 0);
          }
#endif

          TDB_Flags = pTDB->TDB_flags; // flags
       }
   }

/* // dump various helpful info

   if (NULL != pszEnv) {
      LOGDEBUG(0, ("pszEnv = %lx\n", pszEnv));
   }

   LOGDEBUG(0, ("pExe = %lx\n", pExe));
   LOGDEBUG(0, ("pParmBlk = %lx\n", pParmBlk));
   LOGDEBUG(0, ("pDosPDB = %lx\n", pDosPDB));
   LOGDEBUG(0, ("pWinPDB = %lx\n", pWinPDB));

*/

   // determine which environment segment we will use as a template
   if (0 != pParmBlk->envseg) {
      // aha - envseg is passed from above
      pszEnvParent = SEGPTR(pParmBlk->envseg, 0);
   }
   else {
      // no env seg -- use default one from kernel
      pszEnvParent = SEGPTR(pDosPDB->PDB_environ, 0);
   }

   //
   // get module filename from the exe header
   //
   pModuleFileName = SEGPTR(wExe16, (*(WORD *)SEGPTR(wExe16, 10)) + 8);
   nSizeModuleFileName = strlen(pModuleFileName) + 1;


   //
   // Create Child environment cookies using our own cookies and some other hints
   //
   CreateWowChildEnvInformation(pszEnvParent);

   //
   // form the environment block
   //
   pszEnv = WOWCreateEnvBlock(pszEnvParent);

   // now see if we're out of memory
   if (NULL == pszEnv) {
      pszEnv = pszEnvParent; // no worse than before, use parent
      fFreeEnv = FALSE;
   }

   // now pszEnv is the right "merged" environment
   // measure how big it is
   dwSize =
   dwEnvSize =  WOWGetEnvironmentSize(pszEnv, NULL);

   // now let us deal with command line
   wExeFlags = *(PUSHORT)(pExe+NE_FLAGS_OFFSET);
   if (wExeFlags & NEPROT) {

      if (TDB_Flags & TDBF_OS2APP) {

         // now measure both strings
         nSizeCmdLine  = strlen(pCmdLine) + 1;
         nSizeCmdLine += strlen(pCmdLine + nSizeCmdLine) + 1;
         dwSize += nSizeCmdLine + 1;
         fCopy2 = FALSE;
      }
      else {
         // dos app executed this
         nSizeCmdLine = *pCmdLine++; // move to the next char
         // also update original value
         ++pParmBlk->lpcmdline;

         dwSize += nSizeCmdLine + 1;
      }
   }
   else {
      dwSize += 3; // room for magic word and nul
      fCopy2 = FALSE;
   }

   dwSize += nSizeModuleFileName * 2; // we need to have that twice

   // allocate memory

   hMemEnv = WOWGlobalAlloc16(GMEM_FIXED, dwSize);
   if (!hMemEnv) {
      //  we are dead!
      goto exit_passenv;
   }

   pTemp =
   pszEnvTask = SEGPTR(hMemEnv, 0); // fixed memory

   RtlCopyMemory (pTemp, pszEnv, dwEnvSize); // done with env
   pTemp += dwEnvSize; // adjust

   // env is followed by
   if (!(wExeFlags & NEPROT)) {
      // we store 1 \0
      *pTemp++ = '\x1';
      *pTemp++ = '\0';
   }

   // copy stuff
   RtlCopyMemory(pTemp, pModuleFileName, nSizeModuleFileName);
   pTemp += nSizeModuleFileName;

   // see where cmd line should start
   uCmdLineStart = (USHORT)(pTemp - pszEnvTask);

   // second copy of the same
   if (fCopy2) {
      RtlCopyMemory(pTemp, pModuleFileName, nSizeModuleFileName);
      pTemp += nSizeModuleFileName;
   }

   RtlCopyMemory(pTemp, pCmdLine, nSizeCmdLine);
   *(pTemp + nSizeCmdLine + 1) = '\0';

exit_passenv:
   if (fFreeEnv) {
      free_w(pszEnv);
   }

   FREEARGPTR(parg16);
   return(MAKELONG(hMemEnv, uCmdLineStart));
}




/*++
 WK32WOWLoadModule32

 Routine Description:
    Exec a 32 bit Process
    This routine is called by the 16 bit kernel when it fails to load a 16 bit task
    with error codes 11 - invalid exe, 12 - os2, 13 - DOS 4.0, 14 - Unknown.

 ENTRY
  pFrame -> lpCmdLine        Input\output buffer for winoldapp cmd line
  pFrame -> lpParameterBlock (see win 3.x apis) Parameter Block if NULL
                             winoldap calling
  pFrame -> lpModuleName     (see win 3.x apis) App Name

 EXIT
  32 - Sucess
  Error code

 History:
 rewrote to call CreateProcess() instead of LoadModule   - barryb 29sep92

--*/


ULONG FASTCALL WK32WOWLoadModule32(PVDMFRAME pFrame)
{
    static PSZ pszExplorerFullPathUpper = NULL;         // "C:\WINNT\EXPLORER.EXE"

    ULONG ulRet;
    int i;
    char *pch, *pSrc;
    PSZ pszModuleName;
    PSZ pszWinOldAppCmd;
    PBYTE pbCmdLine;
    BOOL CreateProcessStatus;
    PPARAMETERBLOCK16 pParmBlock16;
    PWORD16 pCmdShow = NULL;
    BOOL fProgman = FALSE;
    PROCESS_INFORMATION ProcessInformation;
    STARTUPINFO StartupInfo;
    char CmdLine[2*MAX_PATH];
    char szOut[2*MAX_PATH];
    char szMsgBoxText[4*MAX_PATH];
    register PWOWLOADMODULE16 parg16;
    PTD ptd;
    PSZ pszEnv = NULL; // environment ptr for new process
    WCHAR* pwszEnv = NULL; // environment ptr, unicode

    GETARGPTR(pFrame, sizeof(WOWLOADMODULE16), parg16);
    GETPSZPTR(parg16->lpWinOldAppCmd, pszWinOldAppCmd);
    if (parg16->lpParameterBlock) {
        GETVDMPTR(parg16->lpParameterBlock,sizeof(PARAMETERBLOCK16), pParmBlock16);
        GETPSZPTR(pParmBlock16->lpCmdLine, pbCmdLine);
    } else {
        pParmBlock16 = NULL;
        pbCmdLine = NULL;
    }

    UpdateDosCurrentDirectory(DIR_DOS_TO_NT); // update current dir


    /*
     *  if ModuleName == NULL, called by winoldap, or LM_NTLOADMODULE
     *     to deal with the process handle.
     *
     *     if lpParameterBlock == NULL
     *        winoldap calling to wait on the process handle
     *     else
     *        LM_NTLoadModule calling to clean up process handle
     *        because an error ocurred loading winoldap.
     */
    if (!parg16->lpModuleName) {
        HANDLE hProcess;
        MSG msg;

        pszModuleName = NULL;

        if (pszWinOldAppCmd &&
            *pszWinOldAppCmd &&
            RtlEqualMemory(pszWinOldAppCmd, szWOAWOW32, sizeof(szWOAWOW32)-1))
          {
            hProcess = (HANDLE)strtoul(pszWinOldAppCmd + sizeof(szWOAWOW32) - 1,
                                       NULL,
                                       16
                                       );
            if (hProcess == (HANDLE)-1)  {         // ULONG_MAX
                hProcess = NULL;
            }

            if (parg16->lpParameterBlock && hProcess) {

                //
                // Error loading winoldap.mod
                //

                pptdWOA = NULL;
                CleanupWOAList(hProcess);
                CloseHandle(hProcess);
                hProcess = NULL;
            }
        } else {
            hProcess = NULL;
        }

        BlockWOWIdle(TRUE);

        if (hProcess) {
            while (MsgWaitForMultipleObjects(1, &hProcess, FALSE, INFINITE, QS_ALLINPUT)
                   == WAIT_OBJECT_0 + 1)
            {
                PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE);
            }

            if (!GetExitCodeProcess(hProcess, &ulRet)) {
                ulRet = 0;
            }

            CleanupWOAList(hProcess);
            CloseHandle(hProcess);
        } else {
            (pfnOut.pfnYieldTask)();
            ulRet = 0;
        }

        BlockWOWIdle(FALSE);

        goto lm32Exit;


     /*
      *  if ModuleName == -1, uses traditional style winoldap cmdline
      *  and is called to spawn a non win16 app.
      *
      *    "<cbWord><CmdLineParameters>CR<ModulePathName>LF"
      *
      *  Extract the ModuleName from the command line
      *
      */
    } else if (parg16->lpModuleName == -1) {
        pszModuleName = NULL;

        pSrc = pbCmdLine + 2;
        pch = WOW32_strchr(pSrc, '\r');
        if (!pch || (i = pch - pSrc) >= MAX_PATH) {
            ulRet = 23;
            goto lm32Exit;
            }

        pSrc = pch + 1;
        pch = WOW32_strchr(pSrc, '\n');
        if (!pch || (i = pch - pSrc) >= MAX_PATH) {
            ulRet = 23;
            goto lm32Exit;
            }

        pch = CmdLine;
        while (*pSrc != '\n' && *pSrc) {
            *pch++ = *pSrc++;
        }
        *pch++ = ' ';


        pSrc = pbCmdLine + 2;
        while (*pSrc != '\r' && *pSrc) {
            *pch++ = *pSrc++;
        }
        *pch = '\0';

     /*
      * lpModuleName contains Application Path Name
      * pbCmdLIne contains Command Tail
      */
    } else {
        GETPSZPTR(parg16->lpModuleName, pszModuleName);
        if (pszModuleName) {
            //
            // 2nd part of control.exe/progman.exe implemented here.  In the
            // first part, in WK32WowIsKnownDll, forced the 16-bit loader to
            // load c:\winnt\system32\control.exe(progman.exe) if the app
            // tries to load c:\winnt\control.exe(progman.exe).  16-bit
            // LoadModule tries and eventually discovers its a PE module
            // and returns LME_PE, which causes this function to get called.
            // Unfortunately, the scope of the WK32WowIsKnownDLL modified
            // path is LMLoadExeFile, so by the time we get here, the path is
            // once again c:\winnt\control.exe(progman.exe).  Fix that.
            //

            if (!WOW32_stricmp(pszModuleName, pszControlExeWinDirPath) ||
                (fProgman = TRUE,
                 !WOW32_stricmp(pszModuleName, pszProgmanExeWinDirPath))) {

                strcpy(CmdLine, fProgman
                                 ? pszProgmanExeSysDirPath
                                 : pszControlExeSysDirPath);
            } else {
                strcpy(CmdLine, pszModuleName);
            }

            FREEPSZPTR(pszModuleName);
            }
        else {
            ulRet = 2; // LME_FNF
            goto lm32Exit;
            }


        pch = CmdLine + strlen(CmdLine);
        *pch++ = ' ';

        //
        // The cmdline is a Pascal-style string: a count byte followed by
        // characters followed by a terminating CR character.  If this string is
        // not well formed we will still try to reconstruct the command line in
        // a similar manner that the c startup code does so using the following
        // assumptions:
        //
        // 1. The command line can be no greater that 128 characters including
        //    the length byte and the terminator.
        //
        // 2. The valid terminators for a command line are CR or 0.
        //
        //

        i = 0;
        pSrc = pbCmdLine+1;
        while (*pSrc != '\r' && *pSrc && i < 0x80 - 2) {
            *pch++ = *pSrc++;
        }
        *pch = '\0';
    }


    RtlZeroMemory((PVOID)&StartupInfo, (DWORD)sizeof(StartupInfo));
    StartupInfo.cb = sizeof(StartupInfo);
    StartupInfo.dwFlags = STARTF_USESHOWWINDOW;

    //
    // pCmdShow is documented as a pointer to an array of two WORDs,
    // the first of which must be 2, and the second of which is
    // the nCmdShow to use.  It turns out that Win3.1 ignores
    // the second word (uses SW_NORMAL) if the first word isn't 2.
    // Pixie 2.0 passes an array of 2 zeros, which on Win 3.1 works
    // because the nCmdShow of 0 (== SW_HIDE) is ignored since the
    // first word isn't 2.
    //
    // Our logic, then, is to use SW_NORMAL unless pCmdShow is
    // valid and points to a WORD value 2, in which case we use
    // the next word as nCmdShow.
    //
    // DaveHart 27 June 1993.
    //

    GETVDMPTR(pParmBlock16->lpCmdShow, 4, pCmdShow);
    if (pCmdShow && 2 == pCmdShow[0]) {
        StartupInfo.wShowWindow = pCmdShow[1];
    } else {
        StartupInfo.wShowWindow = SW_NORMAL;
    }

    if (pCmdShow)
        FREEVDMPTR(pCmdShow);

    // we have a problem here -- we need to pass on our environment
    // which is in tdb -- get a pointer to it now
    if (*pCurTDB) {
       PTDB pTDB = (PTDB)SEGPTR(*pCurTDB, 0); // tdb in windows
       PDOSPDB pPSP; // psp pointer

       if (NULL != pTDB && TDB_SIGNATURE == pTDB->TDB_sig) {
          // valid tdb, retrieve env ptr
          pPSP = (PDOSPDB)SEGPTR(pTDB->TDB_PDB, 0); // psp
          if (NULL != pPSP) {
             pszEnv = (PSZ)SEGPTR(pPSP->PDB_environ, 0);
          }
       }
    }


    pwszEnv = WOWForgeUnicodeEnvironment(pszEnv, CURRENTPTD()->pWowEnvData);

    CreateProcessStatus = CreateProcess(
                            NULL,
                            CmdLine,
                            NULL,               // security
                            NULL,               // security
                            FALSE,              // inherit handles
                            CREATE_UNICODE_ENVIRONMENT |
                                CREATE_NEW_CONSOLE |
                                CREATE_DEFAULT_ERROR_MODE,
                            pwszEnv,             // environment strings
                            NULL,               // current directory
                            &StartupInfo,
                            &ProcessInformation
                            );

    if (NULL != pwszEnv) {
        WOWFreeUnicodeEnvironment(pwszEnv);
    }

    if (CreateProcessStatus) {
        DWORD WaitStatus;

        if (CURRENTPTD()->dwWOWCompatFlags & WOWCF_SYNCHRONOUSDOSAPP) {
            LPBYTE lpT;

            // This is for supporting BeyondMail installation. It uses
            // 40:72 as shared memory when it execs DOS programs. The windows
            // part of installation program loops till the byte at 40:72 is
            // non-zero. The DOS program  ORs in 0x80 into this location which
            // effectively signals the completion of the DOS task. On NT
            // Windows and Dos programs are different processes and thus this
            // 'sharing' business doesn't work. Hence this compatibility stuff.
            //                                                - nanduri

            WaitStatus = WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
            lpT = GetRModeVDMPointer(0x400072);
            *lpT |= 0x80;
        }
        else if (!(CURRENTPTD()->dwWOWCompatFlags & WOWCF_NOWAITFORINPUTIDLE)) {

           DWORD dw;
           int i = 20;

            //
            // Wait for the started process to go idle.
            //
            do {
                dw = WaitForInputIdle(ProcessInformation.hProcess, 5000);
                WaitStatus = WaitForSingleObject(ProcessInformation.hProcess, 0);
            } while (dw == WAIT_TIMEOUT && WaitStatus == WAIT_TIMEOUT && i--);
        }

        CloseHandle(ProcessInformation.hThread);

        if (ProcessInformation.hProcess) {

            PWOAINST pWOAInst;
            DWORD    cb;

            //
            // We're returning a process handle to winoldap, so
            // build up a WOAINST structure add add it to this
            // task's list of child WinOldAp instances.
            //

            if (parg16->lpModuleName && -1 != parg16->lpModuleName) {

                GETPSZPTR(parg16->lpModuleName, pszModuleName);
                cb = strlen(pszModuleName)+1;

            } else {

                cb = 1;  // null terminator
                pszModuleName = NULL;

            }

            //
            // WOAINST includes one byte of szModuleName in its
            // size, allocate enough room for the full string.
            //

            pWOAInst = malloc_w( (sizeof *pWOAInst) + cb - 1 );
            WOW32ASSERT(pWOAInst);

            if (pWOAInst) {

                ptd = CURRENTPTD();

                EnterCriticalSection(&ptd->csTD);

                pWOAInst->pNext = ptd->pWOAList;
                ptd->pWOAList = pWOAInst;

                pWOAInst->dwChildProcessID = ProcessInformation.dwProcessId;
                pWOAInst->hChildProcess = ProcessInformation.hProcess;

                //
                // point pptdWOA at pWOAInst->ptdWOA so that
                // W32Thread can fill in the pointer to the
                // WinOldAp TD.
                //

                pWOAInst->ptdWOA = NULL;
                pptdWOA = &(pWOAInst->ptdWOA);

                if (pszModuleName == NULL) {

                    pWOAInst->szModuleName[0] = 0;

                } else {

                    RtlCopyMemory(
                        pWOAInst->szModuleName,
                        pszModuleName,
                        cb
                        );

                    //
                    // We are storing pszModuleName for comparison
                    // later in WowGetModuleHandle, called by
                    // Win16 GetModuleHandle.  The latter always
                    // uppercases the paths involved, so we do
                    // as well so that we can do a case-insensitive
                    // comparison.
                    //

                    WOW32_strupr(pWOAInst->szModuleName);

                    //
                    // HACK -- PackRat can't run Explorer in one
                    // of its "Application Windows", because the
                    // spawned explorer.exe process goes away
                    // after asking the existing explorer to put
                    // up a window.
                    //
                    // If we're starting Explorer, close the
                    // process handle find the "real" shell
                    // explorer.exe process and put its handle
                    // and ID in this WOAINST structure.  This
                    // fixes PackRat, but means that the
                    // winoldap task never goes away because
                    // the shell never goes away.
                    //

                    if (! pszExplorerFullPathUpper) {

                        int nLenWin = strlen(pszWindowsDirectory);
                        int nLenExpl = strlen(szExplorerDotExe);

                        //
                        // pszExplorerFullPathUpper looks like "C:\WINNT\EXPLORER.EXE"
                        //

                        pszExplorerFullPathUpper =
                            malloc_w(nLenWin +                          // strlen(pszWindowsDirectory)
                                     1 +                                // backslash
                                     nLenExpl +                         // strlen("explorer.exe")
                                     1                                  // null terminator
                                     );

                        if (pszExplorerFullPathUpper) {
                            RtlCopyMemory(pszExplorerFullPathUpper, pszWindowsDirectory, nLenWin);
                            pszExplorerFullPathUpper[nLenWin] = '\\';
                            RtlCopyMemory(&pszExplorerFullPathUpper[nLenWin+1], szExplorerDotExe, nLenExpl+1);
                            WOW32_strupr(pszExplorerFullPathUpper);
                        }

                    }

                    if (pszExplorerFullPathUpper &&
                        ! WOW32_strcmp(pWOAInst->szModuleName, pszExplorerFullPathUpper)) {

                        GetWindowThreadProcessId(
                            GetShellWindow(),
                            &pWOAInst->dwChildProcessID
                            );

                        CloseHandle(pWOAInst->hChildProcess);
                        pWOAInst->hChildProcess = ProcessInformation.hProcess =
                            OpenProcess(
                                PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
                                FALSE,
                                pWOAInst->dwChildProcessID
                                );
                    }

                }

                LeaveCriticalSection(&ptd->csTD);
            }

            if (pszModuleName) {
                FREEPSZPTR(pszModuleName);
            }
        }

        ulRet = 33;
        pch = pszWinOldAppCmd + 2;
        sprintf(pch, "%s%x\r", szWOAWOW32, ProcessInformation.hProcess);
        *pszWinOldAppCmd = (char) strlen(pch);
        *(pszWinOldAppCmd+1) = '\0';

    } else {
        //
        // CreateProcess failed, map the most common error codes
        //
        switch (GetLastError()) {
        case ERROR_FILE_NOT_FOUND:
            ulRet = 2;
            break;

        case ERROR_PATH_NOT_FOUND:
            ulRet = 3;
            break;

        case ERROR_BAD_EXE_FORMAT:
            ulRet = 11;
            break;

        // put up warning that they're trying to load a binary intended for
        // a different platform
        case ERROR_EXE_MACHINE_TYPE_MISMATCH:

            // attempt to find the end of the module name path
            pch = CmdLine;
            while((*pch != ' ') && (*pch != '//') && (*pch != '\0')) {
               pch++;
            }
            *pch = '\0';
            LoadString(hmodWOW32,
                       iszMisMatchedBinary,
                       szMsgBoxText,
                       sizeof szMsgBoxText);

            sprintf(szOut, szMsgBoxText, CmdLine);

            LoadString(hmodWOW32,
                       iszMisMatchedBinaryTitle,
                       szMsgBoxText,
                       sizeof szMsgBoxText);

            MessageBox(NULL,
                       szOut,
                       szMsgBoxText,
                       MB_OK | MB_ICONEXCLAMATION);

            // fall through to default case

        default:
            ulRet = 0; // no memory
            break;
        }

    }


lm32Exit:
    FREEARGPTR(parg16);
    FREEPSZPTR(pbCmdLine);
    FREEPSZPTR(pszWinOldAppCmd);
    if (pParmBlock16)
        FREEVDMPTR(pParmBlock16);

    RETURN(ulRet);
}


/*++
 WK32WOWQueryPerformanceCounter

 Routine Description:
    Calls NTQueryPerformanceCounter
    Implemented for Performance Group

 ENTRY
  pFrame -> lpPerformanceFrequency points to location for storing Frequency
  pFrame -> lpPerformanceCounter points to location for storing Counter

 EXIT
  NTStatus Code

--*/

ULONG FASTCALL WK32WOWQueryPerformanceCounter(PVDMFRAME pFrame)
{
    PLARGE_INTEGER pPerfCount16;
    PLARGE_INTEGER pPerfFreq16;
    LARGE_INTEGER PerformanceCounter;
    LARGE_INTEGER PerformanceFrequency;
    register PWOWQUERYPERFORMANCECOUNTER16 parg16;

    GETARGPTR(pFrame, sizeof(WOWQUERYPERFORMANCECOUNTER16), parg16);

    if (parg16->lpPerformanceCounter != 0) {
        GETVDMPTR(parg16->lpPerformanceCounter, 8, pPerfCount16);
    }
    if (parg16->lpPerformanceFrequency != 0) {
        GETVDMPTR(parg16->lpPerformanceFrequency, 8, pPerfFreq16);
    }

    NtQueryPerformanceCounter ( &PerformanceCounter, &PerformanceFrequency );

    if (parg16->lpPerformanceCounter != 0) {
        STOREDWORD(pPerfCount16->LowPart,PerformanceCounter.LowPart);
        STOREDWORD(pPerfCount16->HighPart,PerformanceCounter.HighPart);
    }

    if (parg16->lpPerformanceFrequency != 0) {
        STOREDWORD(pPerfFreq16->LowPart,PerformanceFrequency.LowPart);
        STOREDWORD(pPerfFreq16->HighPart,PerformanceFrequency.HighPart);
    }

    FREEVDMPTR(pPerfCount16);
    FREEVDMPTR(pPerfFreq16);
    FREEARGPTR(parg16);
    RETURN(TRUE);
}

/*++
  WK32WOWOutputDebugString - Write a String to the debugger

  The 16 bit kernel OutputDebugString calls this thunk to actually output the string to the
  debugger.   The 16 bit kernel routine does all the parameter validation etc before calling
  this routine.   Note also that all 16 bit kernel trace output also uses this routine, so
  it not just the app which calls this function.

  If this is a checked build the the output is send via LOGDEBUG so that it gets mingled with
  the WOW trace information, this is useful when running the 16 bit logger tool.


  Entry
    pFrame->vpString Pointer to NULL terminated string to output to the debugger.

  EXIT
    ZERO

--*/

ULONG FASTCALL WK32WOWOutputDebugString(PVDMFRAME pFrame)
{
    PSZ psz1;
    register PWOWOUTPUTDEBUGSTRING16 parg16;

    GETARGPTR(pFrame, sizeof(*parg16), parg16);
    GETPSZPTRNOLOG(parg16->vpString, psz1);

#ifdef DEBUG            // So we can intermingle LOGGER output & WOW Logging
    if ( !(flOptions & OPT_DEBUG) ) {
        OutputDebugString(psz1);
    } else {
        INT  length;
        char text[TMP_LINE_LEN];
        PSZ  pszTemp;

        length = strlen(psz1);
        if ( length > TMP_LINE_LEN-1 ) {
            WOW32_strncpy( text, psz1, TMP_LINE_LEN );
            text[TMP_LINE_LEN-2] = '\n';
            text[TMP_LINE_LEN-1] = '\0';
            pszTemp = text;
        } else {
            pszTemp = psz1;
        }

        LOGDEBUG(LOG_ALWAYS, ("%s", pszTemp));     // in debug version
    }
#else
    OutputDebugString(psz1);
#endif
    FREEPSZPTR(psz1);
    FREEARGPTR(parg16);
    RETURN(0);
}



/* WK32WowFailedExec - WOWExec Failed to Exec Application
 *
 *
 * Entry - Global Variable iW32ExecTaskId
 *
 *
 * Exit
 *     SUCCESS TRUE
 *
 */

ULONG FASTCALL WK32WowFailedExec(PVDMFRAME pFrame)
{
    UNREFERENCED_PARAMETER(pFrame);
    if(iW32ExecTaskId != -1) {
        ExitVDM(WOWVDM,iW32ExecTaskId);
        iW32ExecTaskId = (UINT)-1;
        ShowStartGlass (0);
    }
    FlushMapFileCaches();
    return TRUE;
}


/*++

    Hung App Support
    ================

    There are many levels at which hung app support works.   The User will
    bring up the Task List and hit the End Task Button.    USER32 will post
    a WM_ENDSESSION message to the app.   If the app does not exit after a specified
    timeout them USER will call W32HunAppThread, provided that the task is at the
    client/server boundary.   If the app is looping (ie not at the client/server
    boundary) then it will use the HungAppNotifyThread to alter WOW to kill
    the currently running task.    For the case of W32EndTask we simply
    return back to the 16 bit kernel and force it to perform and Int 21 4C Exit
    call.   For the case of the HungAppNotifyThread we have to somehow grab
    the apps thread - at a point which is "safe".   On non x86 platforms this
    means that the emulator must be at a know safe state - ie not actively emulating
    instructions.    The worst case is if the app is spinning with interrupts
    disabled.

    Notify Thread will
        Force Interrupts to be Enabled SetMSW()
        Set global flag for heartbeatthread so it knows there is work to do
        wait for the app to exit
        timeout - terminate thread() reduce # of tasks

    Alter Global Flag in 16 bit Kernel, that is checked on TimerTick Routines,
    that routine will:-

        Tidy the stack if  on the DOSX stack during h/w interrupt simulation
        Force Int 21 4C exit - might have to patch return address of h/w interrupt
        and then do it at simulated TaskTime.

    Worst Case
    If we don't kill the app in the timeout specified the WOW will put up a dialog
    and then ExitProcess to kill itself.

    Suggestions - if we don't managed to cleanly kill a task we should reduce
    the app count by 2 - (ie the task and WOWExec, so when the last 16 bit app
    goes away we will shutdown WOW).   Also in the case put up a dialog box
    stating you should save your work for 16 bit apps too.

--*/


/*++

 InitializeHungAppSupport - Setup Necessary Threads and Callbacks

 Routine Description
    Create a HungAppNotification Thread
    Register CallBack Handlers With SoftPC Base which are called when
    interrupt simulation is required.

 Entry
    NONE

 EXIT
    TRUE - Success
    FALSE - Faled

--*/
BOOL WK32InitializeHungAppSupport(VOID)
{

    // Register Interrupt Idle Routine with SoftPC
    ghevWowExecMsgWait = RegisterWOWIdle();


    // Create HungAppNotify Thread

    InitializeCriticalSection(&gcsWOW);
    InitializeCriticalSection(&gcsHungApp);  // protects VDM_WOWHUNGAPP bit

    if(!(pfnOut.pfnRegisterUserHungAppHandlers)((PFNW32ET)W32HungAppNotifyThread,
                                     ghevWowExecMsgWait))
       {
        LOGDEBUG(LOG_ALWAYS,("W32HungAppNotifyThread: Error Failed to RegisterUserHungAppHandlers\n"));
        return FALSE;
    }

    if (!(ghevWaitHungAppNotifyThread = CreateEvent(NULL, TRUE, FALSE, NULL))) {
        LOGDEBUG(LOG_ALWAYS,("WK32InitializeHungAppSupport ERROR: event allocation failure\n"));
        return FALSE;
    }


    return TRUE;
}





/*++
 WK32WowWaitForMsgAndEvent

 Routine Description:
    Calls USER32 WowWaitForMsgAndEvent
    Called by WOWEXEC (interrupt dispatch optimization)

 ENTRY
  pFrame->hwnd must be WOWExec's hwnd

 EXIT
  FALSE - A message has arrived, WOWExec must call GetMessage
  TRUE  - The interrupt event was toggled, no work for WOWExec

--*/

ULONG FASTCALL WK32WowWaitForMsgAndEvent(PVDMFRAME pFrame)
{
    register PWOWWAITFORMSGANDEVENT16 parg16;
    BOOL  RetVal;

    GETARGPTR(pFrame, sizeof(WOWWAITFORMSGANDEVENT16), parg16);

    //
    // This is a private api so lets make sure it is wowexec
    //
    if (ghwndShell != HWND32(parg16->hwnd)) {
        FREEARGPTR(parg16);
        return FALSE;
    }

    //
    // WowExec will set VDM_TIMECHANGE bit in the pntvdmstate
    // when it receives a WM_TIMECHANGE message. It is now safe
    // to Reinit the Virtual Timer Hardware as wowexec is the currently
    // scheduled task, and we expect no one to be polling on
    // timer hardware\Bios tic count.
    //
    if (*pNtVDMState & VDM_TIMECHANGE) {
        SuspendTimerThread();
        ResumeTimerThread();
        }

    BlockWOWIdle(TRUE);

    RetVal = (ULONG) (pfnOut.pfnWowWaitForMsgAndEvent)(ghevWowExecMsgWait);

    BlockWOWIdle(FALSE);

    FREEARGPTR(parg16);
    return RetVal;
}


/*++
 WowMsgBoxThread

 Routine Description:
    Worker Thread routine which does all of the msg box work for
    Wk32WowMsgBox (See below)

 ENTRY

 EXIT
  VOID

--*/
DWORD WowMsgBoxThread(VOID *pv)
{
    PWOWMSGBOX16 pWowMsgBox16 = (PWOWMSGBOX16)pv;
    PSZ   pszMsg, pszTitle;
    char  szMsg[MAX_PATH*2];
    char  szTitle[MAX_PATH];
    UINT  Style;


    if (pWowMsgBox16->pszMsg) {
        GETPSZPTR(pWowMsgBox16->pszMsg, pszMsg);
        szMsg[MAX_PATH*2 - 1] = '\0';
        WOW32_strncpy(szMsg, pszMsg, MAX_PATH*2 - 1);
        FREEPSZPTR(pszMsg);
    } else {
        szMsg[0] = '\0';
    }

    if (pWowMsgBox16->pszTitle) {
        GETPSZPTR(pWowMsgBox16->pszTitle, pszTitle);
        szTitle[MAX_PATH - 1] = '\0';
        WOW32_strncpy(szTitle, pszTitle, MAX_PATH);
        FREEPSZPTR(pszTitle);
    } else {
        szTitle[0] = '\0';
    }

    Style = pWowMsgBox16->dwOptionalStyle | MB_OK | MB_SYSTEMMODAL;

    pWowMsgBox16->dwOptionalStyle = 0xffffffff;

    MessageBox (NULL, szMsg, szTitle, Style);

    return 1;
}



/*++
 WK32WowMsgBox

 Routine Description:
    Creates an asynchronous msg box and returns immediately
    without waiting for the msg box to be dismissed. Provided
    for WowExec as WowExec must use its special WowWaitForMsgAndEvent
    api for hardware interrupt dispatching.

    Called by WOWEXEC (interrupt dispatch optimization)

 ENTRY
     pszMsg          - Message for MessageBox
     pszTitle        - Caption for MessageBox
     dwOptionalStyle - MessageBox style bits additional to
                       MB_OK | MB_SYSTEMMODAL

 EXIT
     VOID - nothing is returned as we do not wait for a reply from
            the user.

--*/

ULONG FASTCALL WK32WowMsgBox(PVDMFRAME pFrame)
{
    PWOWMSGBOX16 pWowMsgBox16;
    DWORD Tid;
    HANDLE hThread;

    GETARGPTR(pFrame, sizeof(WOWMSGBOX16), pWowMsgBox16);
    hThread = CreateThread(NULL, 0, WowMsgBoxThread, (PVOID)pWowMsgBox16, 0, &Tid);
    if (hThread) {
        do {
           if (WaitForSingleObject(hThread, 15) != WAIT_TIMEOUT)
               break;
        } while (pWowMsgBox16->dwOptionalStyle != 0xffffffff);

        CloseHandle(hThread);
        }
    else {
        WowMsgBoxThread((PVOID)pWowMsgBox16);
        }

    FREEARGPTR(pWowMsgBox16);
    return 0;
}



#ifdef debug
UINT  gLasthtaskKill = 0;
#endif

/*++

 W32HungAppNotifyThread

    USER32 Calls this routine:
        1 - if the App Agreed to the End Task (from Task List)
        2 - if the app didn't respond to the End Task
        3 - shutdown

    NTVDM Calls this routine:
        1 - if an app has touched some h/w that it shouldn't and the user
            requiested to terminate the app (passed NULL for current task)

    WOW32 Calls this routine:
        1 - when WowExec receives a WM_WOWEXECKILLTASK message.

 ENTRY
  hKillUniqueID - TASK ID of task to kill or NULL for current Task

 EXIT
  NEVER RETURNS - Goes away when WOW is killed

--*/

DWORD W32HungAppNotifyThread(UINT htaskKill)
{
    PTD ptd;
    LPWORD pLockTDB;
    WORD  hTask16;
    DWORD dwThreadId;
    int nMsgBoxChoice;
    PTDB pTDB;
    char    szModName[9];
    char    szErrorMessage[200];
    DWORD   dwResult;
    BOOL    fSuccess;


    if (!ResetEvent(ghevWaitHungAppNotifyThread)) {
         LOGDEBUG(LOG_ALWAYS,("W32HungAppNotifyThread: ERROR failed to ResetEvent\n"));
    }

    ptd = NULL;

    if (htaskKill) {

        EnterCriticalSection(&gcsWOW);

        ptd = gptdTaskHead;

        /*
         * See if the Task is still alive
         */
        while ((ptd != NULL) && (ptd->htask16 != htaskKill)) {
            ptd = ptd->ptdNext;
        }

        LeaveCriticalSection(&gcsWOW);

    }

    // If we are seeing this notification for a second time, these selectors
    // probably don't match -- which means this 16-bit context is really messed
    // up.  We'd better prevent it from doing any 16-bit callbacks at this point
    // or it will result in a crash dlg for the user & will kill the VDM & any
    // other 16-bit apps (tasks) running in this VDM.
    // This situation will occur if the call to WaitForSingleObject() (following
    // the call to SendMessageTimeout() below) actually times-out rather than
    // complete.  The thread will actually be dead by the time *this* function
    // gets called again with a now-invalid TDB struct.  This situation appears
    // to result from clicking End Task from the task manager or somehow trying
    // to kill a not-hung 16-bit task via external means.  Bug #408188
    if(HIWORD(ptd->vpStack) != HIWORD(ptd->vpCBStack)) {

#ifdef debug
        // sanity check to make sure this only happens for the same task
        WOW32ASSERTMSG((htaskKill == gLasthtaskKill),
                       ("WOW: Unexpected mis-matched selector case\n"));
        gLasthtaskKill = 0;
#endif
        return 0;
    }

    // point to LockTDB

    GETVDMPTR(vpLockTDB, 2, pLockTDB);

    // If the task is alive then attempt to kill it

    if ( ( ptd != NULL ) || ( htaskKill == 0 ) ) {

        // Set LockTDB == The app we are trying to kill
        // (see \kernel31\TASKING.ASM)
        // and then try to cause a task switch by posting WOWEXEC a message
        // and then posting a message to the app we want to kill

        if ( ptd != NULL) {
            hTask16 = ptd->htask16;

        }
        else {
            // htaskKill == 0
            // Kill the Active Task
            hTask16 = *pCurTDB;
        }

        pTDB = (PTDB)SEGPTR(hTask16, 0);

        WOW32ASSERTMSGF( pTDB && pTDB->TDB_sig == TDB_SIGNATURE,
                ("W32HungAppNotifyThread: TDB sig doesn't match, TDB %x htaskKill %x pTDB %x.\n",
                 hTask16, htaskKill, pTDB));

        dwThreadId = pTDB->TDB_ThreadID;

        //
        // if the task to be killed is this task end immediately
        //
        if (dwThreadId == GetCurrentThreadId()) {
            EnterCriticalSection(&gcsHungApp);
            *pNtVDMState |= VDM_WOWHUNGAPP;
            LeaveCriticalSection(&gcsHungApp);
            call_ica_hw_interrupt( KEYBOARD_ICA, KEYBOARD_LINE, 1 );

            //
            // return to 16 bit to process int 9.
            //

            return 0;
            }

        *pLockTDB = hTask16;
        SendMessageTimeout(ghwndShell, WM_WOWEXECHEARTBEAT, 0, 0, SMTO_BLOCK,1*1000,&dwResult);

        //
        // terminate any pending named pipe operations for this thread (ie app)
        //

        VrCancelPipeIo(dwThreadId);

        PostThreadMessage(dwThreadId, WM_KEYDOWN, VK_ESCAPE, 0x1B000A);
        PostThreadMessage(dwThreadId,   WM_KEYUP, VK_ESCAPE, 0x1B0001);

        if (WaitForSingleObject(ghevWaitHungAppNotifyThread,
                                CMS_WAITTASKEXIT) == 0) {
            LOGDEBUG(2,("W32HungAppNotifyThread: Success with forced task switch\n"));
            ExitThread(EXIT_SUCCESS);
        }

#ifdef debug
        gLasthtaskKill = htaskKill;
#endif

        // Failed
        //
        // Probably means the current App is looping in 16 bit land not
        // responding to input.

        // Warn the User if its a different App than the one he wants to kill
        // Don't do this if WOWEXEC is the hung app, since users don't know
        // what that is.


        if (*pLockTDB != *pCurTDB && gptdShell->htask16 != *pCurTDB && *pCurTDB) {

            pTDB = (PTDB)SEGPTR(*pCurTDB, 0);

            WOW32ASSERTMSGF( pTDB && pTDB->TDB_sig == TDB_SIGNATURE,
                    ("W32HungAppNotifyThread: Current TDB sig doesn't match, TDB %x htaskKill %x pTDB %x.\n",
                     *pCurTDB, htaskKill, pTDB));

            RtlCopyMemory(szModName, pTDB->TDB_ModName, (sizeof szModName)-1);
            szModName[(sizeof szModName) - 1] = 0;

            fSuccess = LoadString(
                           hmodWOW32,
                           iszCantEndTask,
                           szMsgBoxText,
                           WARNINGMSGLENGTH
                           );
            WOW32ASSERT(fSuccess);

            fSuccess = LoadString(
                           hmodWOW32,
                           iszApplicationError,
                           szCaption,
                           WARNINGMSGLENGTH
                           );
            WOW32ASSERT(fSuccess);

            wsprintf(
                szErrorMessage,
                szMsgBoxText,
                szModName,
                szModName
                );

            nMsgBoxChoice =
                MessageBox(
                    NULL,
                    szErrorMessage,
                    szCaption,
                    MB_TOPMOST | MB_SETFOREGROUND | MB_TASKMODAL |
                    MB_ICONSTOP | MB_OKCANCEL
                    );

            if (nMsgBoxChoice == IDCANCEL) {
                 ExitThread(0);
            }
        }

        //
        // See code in \mvdm\wow16\drivers\keyboard\keyboard.asm
        // where keyb_int where it handles this interrupt and forces an
        // int 21 function 4c - Exit.  It only does this if VDM_WOWHUNGAPP
        // is turned on in NtVDMState, and it clears that bit.  We wait for
        // the bit to be clear if it's already set, indicating another instance
        // of this thread has already initiated an INT 9 to kill a task.  By
        // waiting we avoid screwing up the count of threads active on the
        // 16-bit side (bThreadsIn16Bit).
        //
        // LATER shouldn't allow user to kill WOWEXEC
        //
        // LATER should enable h/w interrupt before doing this - use 40: area
        // on x86.   On MIPS we'd need to call CPU interface.
        //

        EnterCriticalSection(&gcsHungApp);

        while (*pNtVDMState & VDM_WOWHUNGAPP) {
            LeaveCriticalSection(&gcsHungApp);
            LOGDEBUG(LOG_ALWAYS, ("WOW32 W32HungAppNotifyThread waiting for previous INT 9 to clear before dispatching another.\n"));
            Sleep(1 * 1000);
            EnterCriticalSection(&gcsHungApp);
        }

        *pNtVDMState |= VDM_WOWHUNGAPP;

        LeaveCriticalSection(&gcsHungApp);

        call_ica_hw_interrupt( KEYBOARD_ICA, KEYBOARD_LINE, 1 );

        if (WaitForSingleObject(ghevWaitHungAppNotifyThread,
                                CMS_WAITTASKEXIT) != 0) {

            LOGDEBUG(LOG_ALWAYS,("W32HungAppNotifyThread: Error, timeout waiting for task to terminate\n"));

            fSuccess = LoadString(
                           hmodWOW32,
                           iszUnableToEndSelTask,
                           szMsgBoxText,
                           WARNINGMSGLENGTH);
            WOW32ASSERT(fSuccess);

            fSuccess = LoadString(
                           hmodWOW32,
                           iszSystemError,
                           szCaption,
                           WARNINGMSGLENGTH);
            WOW32ASSERT(fSuccess);

            nMsgBoxChoice =
                MessageBox(
                    NULL,
                    szMsgBoxText,
                    szCaption,
                    MB_TOPMOST | MB_SETFOREGROUND | MB_TASKMODAL |
                    MB_ICONSTOP | MB_OKCANCEL | MB_DEFBUTTON1
                    );

            if (nMsgBoxChoice == IDCANCEL) {
                 EnterCriticalSection(&gcsHungApp);
                 *pNtVDMState &= ~VDM_WOWHUNGAPP;
                 LeaveCriticalSection(&gcsHungApp);
                 ExitThread(0);
            }

            LOGDEBUG(LOG_ALWAYS, ("W32HungAppNotifyThread: Destroying WOW Process\n"));

            ExitVDM(WOWVDM, ALL_TASKS);
            ExitProcess(0);
        }

        LOGDEBUG(LOG_ALWAYS,("W32HungAppNotifyThread: Success with Keyboard Interrupt\n"));

    } else { // task not found

        LOGDEBUG(LOG_ALWAYS,("W32HungAppNotifyThread: Task already Terminated \n"));

    }

    ExitThread(EXIT_SUCCESS);
    return 0;   // remove compiler warning
}



/*++

 W32EndTask - Cause Current Task to Exit (HUNG APP SUPPORT)

 Routine Description:
    This routine is called when unthunking WM_ENDSESSION to cause the current
    task to terminate.

 ENTRY
    The apps thread that we want to kill

 EXIT
  DOES NOT RETURN - The task will exit and wind up in WK32WOWKillTask which
  will cause that thread to Exit.

--*/

VOID APIENTRY W32EndTask(VOID)
{
    PARM16 Parm16;
    VPVOID vp = 0;

    LOGDEBUG(LOG_WARNING,("W32EndTask: Forcing Task %04X to Exit\n",CURRENTPTD()->htask16));

    CallBack16(RET_FORCETASKEXIT, &Parm16, 0, &vp);

    //
    //  We should Never Come Here, an app should get terminated via calling wk32wowkilltask thunk
    //  not by doing an unsimulate call
    //

    WOW32ASSERTMSG(FALSE, "W32EndTask: Error - Returned From ForceTaskExit callback - contact DaveHart");
}


ULONG FASTCALL WK32DirectedYield(PVDMFRAME pFrame)
{
    register PDIRECTEDYIELD16 parg16;

    //
    // This code is duplicated in wkgthunk.c by WOWDirectedYield16.
    // The two must be kept synchronized.
    //

    GETARGPTR(pFrame, sizeof(DIRECTEDYIELD16), parg16);


    BlockWOWIdle(TRUE);

    (pfnOut.pfnDirectedYield)(THREADID32(parg16->hTask16));

    BlockWOWIdle(FALSE);


    FREEARGPTR(parg16);
    RETURN(0);
}

/***************************************************************************\
* EnablePrivilege
*
* Enables/disables the specified well-known privilege in the current thread
* token if there is one, otherwise the current process token.
*
* Returns TRUE on success, FALSE on failure
*
* History:
* 12-05-91 Davidc       Created
* 06-15-93 BobDay       Stolen from WinLogon
\***************************************************************************/
BOOL
EnablePrivilege(
    ULONG Privilege,
    BOOL Enable
    )
{
    NTSTATUS Status;
    BOOLEAN WasEnabled;

    //
    // Try the thread token first
    //

    Status = RtlAdjustPrivilege(Privilege,
                                (BOOLEAN)Enable,
                                TRUE,
                                &WasEnabled);

    if (Status == STATUS_NO_TOKEN) {

        //
        // No thread token, use the process token
        //

        Status = RtlAdjustPrivilege(Privilege,
                                    (BOOLEAN)Enable,
                                    FALSE,
                                    &WasEnabled);
    }


    if (!NT_SUCCESS(Status)) {
        LOGDEBUG(LOG_ALWAYS,("WOW32: EnablePrivilege Failed to %s privilege : 0x%lx, status = 0x%lx\n", Enable ? "enable" : "disable", Privilege, Status));
        return(FALSE);
    }

    return(TRUE);
}

//*****************************************************************************
// W32GetAppCompatFlags -
//    Returns the Compatibility flags for the Current Task or of the
//    specified Task.
//    These are the 16-bit kernel's compatibility flags, not to be
//    confused with our separate WOW compatibility flags.
//
//*****************************************************************************

ULONG W32GetAppCompatFlags(HTASK16 hTask16)
{

    PTDB ptdb;

    if (hTask16 == (HAND16)NULL) {
        hTask16 = CURRENTPTD()->htask16;
    }

    ptdb = (PTDB)SEGPTR((hTask16),0);

    return (ULONG)MAKELONG(ptdb->TDB_CompatFlags, ptdb->TDB_CompatFlags2);
}


//*****************************************************************************
// This is called from COMM.drv via WowCloseComPort in kernel16, whenever
// a com port needs to be released.
//
// PortId 0 is COM1, 1 is COM2 etc.
//                                                                   - Nanduri
//*****************************************************************************

ULONG FASTCALL WK32WowCloseComPort(PVDMFRAME pFrame)
{
    register PWOWCLOSECOMPORT16 parg16;

    GETARGPTR(pFrame, sizeof(WOWCLOSECOMPORT16), parg16);
    host_com_close((INT)parg16->wPortId);
    FREEARGPTR(parg16);
    return 0;  // quiet compiler, not used.
}


//*****************************************************************************
// WK32WowDelFile
// The call to demFileDelete will handle the case where there there is an
// open handle to the file. In case it fails, we try hacking around the case
// where a font file being held by GDI32.
//*****************************************************************************

DWORD FASTCALL WK32WowDelFile(PVDMFRAME pFrame)
{
    PSZ psz1;
    PWOWDELFILE16 parg16;
    DWORD retval;

    GETARGPTR(pFrame, sizeof(WOWFILEDEL16), parg16);
    GETVDMPTR(parg16->lpFile, 1, psz1);

    LOGDEBUG(fileoclevel,("WK32WOWDelFile: %s \n",psz1));

    retval = demFileDelete(psz1);

    switch(retval) {
        case 0:
        case ERROR_FILE_NOT_FOUND:
        case ERROR_PATH_NOT_FOUND:
            break;

        default:
            // Some Windows Install Programs copy a .FON font file to a temp
            // directory use the font during installation and then try to delete
            // the font - without calling RemoveFontResource();   GDI32 Keeps the
            // Font file open and thus the delete fails.

            // What we attempt here is to assume that the file is a FONT file
            // and try to remove it before deleting it, since the above delete
            // has already failed.

            if ( RemoveFontResourceOem(psz1) ) {
                LOGDEBUG(fileoclevel,("WK32WOWDelFile: RemoveFontResource on %s \n",psz1));
                SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
            }

            if(DeleteFileOem(psz1)) {
                retval = 0;
            }
    }

    if ( retval ) {
        retval |= 0xffff0000;
    }

    FREEVDMPTR(psz1);
    FREEARGPTR(parg16);
    return retval;
}


//*****************************************************************************
// This is called as soon as wow is initialized to notify the 32-bit world
// what the addresses are of some key kernel variables.
//
//*****************************************************************************

ULONG FASTCALL WK32WOWNotifyWOW32(PVDMFRAME pFrame)
{
    register PWOWNOTIFYWOW3216 parg16;

    GETARGPTR(pFrame, sizeof(WOWNOTIFYWOW3216), parg16);

    vpDebugWOW  = FETCHDWORD(parg16->lpDebugWOW);
    GETVDMPTR(FETCHDWORD(parg16->lpcurTDB), 2, pCurTDB);
    vpnum_tasks = FETCHDWORD(parg16->lpnum_tasks);
    vpLockTDB   = FETCHDWORD(parg16->lpLockTDB);
    vptopPDB    = FETCHDWORD(parg16->lptopPDB);
    GETVDMPTR(FETCHDWORD(parg16->lpCurDirOwner), 2, pCurDirOwner);

    //
    // IsDebuggerAttached will tell the 16-bit kernel to generate
    // debug events.
    //
    IsDebuggerAttached();

    FREEARGPTR(parg16);

    return 0;
}

//*****************************************************************************
// Currently, this routine is called very very soon after the 16-bit kernel.exe
// has switched to protected mode. The variables set up here are used in the
// file i/o routines.
//*****************************************************************************

extern VOID demWOWLFNInit(PWOWLFNINIT pLFNInit);
extern VOID DosWowUpdateTDBDir(UCHAR Drive, LPSTR pszDir);
extern BOOL DosWowGetTDBDir(UCHAR Drive, LPSTR pCurrentDirectory);
extern BOOL DosWowDoDirectHDPopup(VOID);
#if 0
extern BOOL DosWowGetCompatFlags(LPDWORD, LPDWORD);
#endif
//
//  Function returns TRUE if we should do the popup
//  and FALSE if we should not
//

ULONG FASTCALL WK32DosWowInit(PVDMFRAME pFrame)
{
    register PWOWDOSWOWINIT16 parg16;
    PDOSWOWDATA pDosWowData;
    PULONG  pTemp;
    WOWLFNINIT LFNInit;

    GETARGPTR(pFrame, sizeof(WOWDOSWOWINIT16), parg16);

    // covert all fixed DOS address to linear addresses for fast WOW thunks.
    pDosWowData = GetRModeVDMPointer(FETCHDWORD(parg16->lpDosWowData));

    DosWowData.lpCDSCount = (DWORD) GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpCDSCount));
    pTemp = (PULONG)GetRModeVDMPointer(FETCHDWORD(pDosWowData->lpCDSFixedTable));
    DosWowData.lpCDSFixedTable = (DWORD) GetRModeVDMPointer(FETCHDWORD(*pTemp));

    DosWowData.lpCDSBuffer = (DWORD)GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpCDSBuffer));
    DosWowData.lpCurDrv = (DWORD) GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpCurDrv));
    DosWowData.lpCurPDB = (DWORD) GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpCurPDB));
    DosWowData.lpDrvErr = (DWORD) GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpDrvErr));
    DosWowData.lpExterrLocus = (DWORD) GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpExterrLocus));
    DosWowData.lpSCS_ToSync = (DWORD) GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpSCS_ToSync));
    DosWowData.lpSftAddr = (DWORD) GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpSftAddr));
    DosWowData.lpExterr = (DWORD) GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpExterr));
    DosWowData.lpExterrActionClass = (DWORD) GetRModeVDMPointer(
                                        FETCHDWORD(pDosWowData->lpExterrActionClass));

/*    // right here we shall make a dynamic check to see if wow is running on
    // Winterm Server and if so -- whether we need to thunk GetWindowsDirectory
    {
        PDWORD UNALIGNED pdwWinTermFlags;
        GETVDMPTR(FETCHDWORD(parg16->lpdwWinTermFlags), sizeof(DWORD), pdwWinTermFlags);

        if (IsTerminalServer()) {
           *pdwWinTermFlags |= WINTERM_SERVER;
        }


    }
*/



    // excellent chance to have us let ntvdm know we're lfn aware and alive

    LFNInit.pDosWowUpdateTDBDir    = DosWowUpdateTDBDir;
    LFNInit.pDosWowGetTDBDir       = DosWowGetTDBDir;
    LFNInit.pDosWowDoDirectHDPopup = DosWowDoDirectHDPopup;
#if 0
    LFNInit.pDosWowGetCompatFlags  = DosWowGetCompatFlags;
#endif

    demWOWLFNInit(&LFNInit);

    FREEARGPTR(parg16);
    return (0);
}


//*****************************************************************************
//
// WK32InitWowIsKnownDLL(HANDLE hKeyWow)
//
// Called by W32Init to read list of known DLLs from the registry.
//
// hKeyWow is an open handle to ...\CurrentControlSet\WOW, we use
// the value REG_SZ value KnownDLLs which looks like "commdlg.dll mmsystem.dll
// toolhelp.dll olecli.dll olesvr.dll".
//
//*****************************************************************************

VOID WK32InitWowIsKnownDLL(HANDLE hKeyWow)
{
    CHAR  sz[2048];
    PSZ   pszKnownDLL;
    PCHAR pch;
    ULONG ulSize = sizeof(sz);
    int   nCount;
    DWORD dwRegValueType;
    LONG  lRegError;
    ULONG ulAttrib;

    //
    // Get the list of known DLLs from the registry.
    //

    lRegError = RegQueryValueEx(
                    hKeyWow,
                    "KnownDLLs",
                    NULL,
                    &dwRegValueType,
                    sz,
                    &ulSize
                    );

    if (ERROR_SUCCESS == lRegError && REG_SZ == dwRegValueType) {

        //
        // Allocate memory to hold a copy of this string to be
        // used to hold the strings pointed to by
        // apszKnownDLL[].  This memory won't be freed until
        // WOW goes away.
        //

        pszKnownDLL = malloc_w_or_die(ulSize);

        strcpy(pszKnownDLL, sz);

        //
        // Lowercase the entire value so that we can search these
        // strings case-sensitive in WK32WowIsKnownDLL.
        //

        WOW32_strlwr(pszKnownDLL);

        //
        // Parse the KnownDLL string into apszKnownDLL array.
        // strtok() does this quite handily.
        //

        nCount = 0;

        pch = apszKnownDLL[0] = pszKnownDLL;

        while (apszKnownDLL[nCount]) {
            nCount++;
            if (nCount >= MAX_KNOWN_DLLS) {
                LOGDEBUG(0,("WOW32 Init: Too many known DLLs, must have %d or fewer.\n", MAX_KNOWN_DLLS-1));
                apszKnownDLL[MAX_KNOWN_DLLS-1] = NULL;
                break;
            }
            pch = WOW32_strchr(pch, ' ');
            if (!pch) {
                break;
            }
            *pch = 0;
            pch++;
            if (0 == *pch) {
                break;
            }
            while (' ' == *pch) {
                pch++;
            }
            apszKnownDLL[nCount] = pch;
        }

    } else {
        LOGDEBUG(0,("InitWowIsKnownDLL: RegQueryValueEx error %ld.\n", lRegError));
    }

    //
    // The Known DLL list is ready, now build up a fully-qualified paths
    // to %windir%\control.exe and %windir%\system32\control.exe
    // for WOWCF_CONTROLEXEHACK below.
    //

    //
    // pszControlExeWinDirPath looks like "c:\winnt\control.exe"
    //

    pszControlExeWinDirPath =
        malloc_w_or_die(strlen(pszWindowsDirectory)     +
                        sizeof(szBackslashControlExe)-1 + // strlen("\\control.exe")
                        1                                 // null terminator
                        );

    strcpy(pszControlExeWinDirPath, pszWindowsDirectory);
    strcat(pszControlExeWinDirPath, szBackslashControlExe);


    //
    // pszProgmanExeWinDirPath looks like "c:\winnt\progman.exe"
    //

    pszProgmanExeWinDirPath =
        malloc_w_or_die(strlen(pszWindowsDirectory)     +
                        sizeof(szBackslashProgmanExe)-1 + // strlen("\\progman.exe")
                        1                                 // null terminator
                        );

    strcpy(pszProgmanExeWinDirPath, pszWindowsDirectory);
    strcat(pszProgmanExeWinDirPath, szBackslashProgmanExe);


    //
    // pszControlExeSysDirPath looks like "c:\winnt\system32\control.exe"
    //

    pszControlExeSysDirPath =
        malloc_w_or_die(strlen(pszSystemDirectory)      +
                        sizeof(szBackslashControlExe)-1 + // strlen("\\control.exe")
                        1                                 // null terminator
                        );

    strcpy(pszControlExeSysDirPath, pszSystemDirectory);
    strcat(pszControlExeSysDirPath, szBackslashControlExe);

    //
    // pszProgmanExeSysDirPath looks like "c:\winnt\system32\control.exe"
    //

    pszProgmanExeSysDirPath =
        malloc_w_or_die(strlen(pszSystemDirectory)      +
                        sizeof(szBackslashProgmanExe)-1 + // strlen("\\progman.exe")
                        1                                 // null terminator
                        );

    strcpy(pszProgmanExeSysDirPath, pszSystemDirectory);
    strcat(pszProgmanExeSysDirPath, szBackslashProgmanExe);

    // Make the KnownDLL, CTL3DV2.DLL, file attribute ReadOnly.
    // Later we should do this for all WOW KnownDll's
    strcpy(sz, pszSystemDirectory);
    strcat(sz, "\\CTL3DV2.DLL");
    ulAttrib = GetFileAttributesOem(sz);
    if ((ulAttrib != 0xFFFFFFFF) && !(ulAttrib & FILE_ATTRIBUTE_READONLY)) {
        ulAttrib |= FILE_ATTRIBUTE_READONLY;
        SetFileAttributesOem(sz, ulAttrib);
    }

}


//*****************************************************************************
//
// WK32WowIsKnownDLL -
//
// This routine is called from within LoadModule (actually MyOpenFile),
// when kernel31 has determined that the module is not already loaded,
// and is about to search for the DLL.  If the base name of the passed
// path is a known DLL, we allocate and pass back to the 16-bit side
// a fully-qualified path to the DLL in the system32 directory.
//
//*****************************************************************************

ULONG FASTCALL WK32WowIsKnownDLL(PVDMFRAME pFrame)
{
    register WOWISKNOWNDLL16 *parg16;
    PSZ pszPath;
    VPVOID UNALIGNED *pvpszKnownDLLPath;
    PSZ pszKnownDLLPath;
    size_t cbKnownDLLPath;
    char **ppsz;
    char szLowercasePath[13];
    ULONG ul = 0;
    BOOL fProgman = FALSE;

    GETARGPTR(pFrame, sizeof(WOWISKNOWNDLL16), parg16);

    GETPSZPTRNOLOG(parg16->lpszPath, pszPath);
    GETVDMPTR(parg16->lplpszKnownDLLPath, sizeof(*pvpszKnownDLLPath), pvpszKnownDLLPath);

    if (pszPath) {

        //
        // Special hack for apps which WinExec %windir%\control.exe or
        // %windir%\progman.exe.  This formerly was only done under a
        // compatibility bit, but now is done for all apps.  Both
        // the 3.1[1] control panel and program manager binaries cannot
        // work under WOW because of other shell conflicts, like different
        // .GRP files and conflicting use of the control.ini file for both
        // 16-bit and 32-bit CPLs.
        //
        // Compare the path passed in with the precomputed
        // pszControlExeWinDirPath, which looks like "c:\winnt\control.exe".
        // If it matches, pass back the "Known DLL path" of
        // "c:\winnt\system32\control.exe".  Same for progman.exe.
        //

        if (!WOW32_stricmp(pszPath, pszControlExeWinDirPath) ||
            (fProgman = TRUE,
             !WOW32_stricmp(pszPath, pszProgmanExeWinDirPath))) {

            VPVOID vp;

            cbKnownDLLPath = 1 + strlen(fProgman
                                         ? pszProgmanExeSysDirPath
                                         : pszControlExeSysDirPath);

            vp = malloc16(cbKnownDLLPath);

            // 16-bit memory may have moved - refresh flat pointers now

            FREEVDMPTR(pvpszKnownDLLPath);
            FREEPSZPTR(pszPath);
            FREEARGPTR(parg16);
            FREEVDMPTR(pFrame);
            GETFRAMEPTR(((PTD)CURRENTPTD())->vpStack, pFrame);
            GETARGPTR(pFrame, sizeof(WOWISKNOWNDLL16), parg16);
            GETPSZPTRNOLOG(parg16->lpszPath, pszPath);
            GETVDMPTR(parg16->lplpszKnownDLLPath, sizeof(*pvpszKnownDLLPath), pvpszKnownDLLPath);

            *pvpszKnownDLLPath = vp;

            if (*pvpszKnownDLLPath) {

                GETPSZPTRNOLOG(*pvpszKnownDLLPath, pszKnownDLLPath);

                RtlCopyMemory(
                   pszKnownDLLPath,
                   fProgman
                    ? pszProgmanExeSysDirPath
                    : pszControlExeSysDirPath,
                   cbKnownDLLPath);

                // LOGDEBUG(0,("WowIsKnownDLL: %s known(c) -=> %s\n", pszPath, pszKnownDLLPath));

                FLUSHVDMPTR(*pvpszKnownDLLPath, cbKnownDLLPath, pszKnownDLLPath);
                FREEPSZPTR(pszKnownDLLPath);

                ul = 1;          // return success, meaning is known dll
                goto Cleanup;
            }
        }

        //
        // We don't mess with attempts to open that include a
        // path.
        //

        if (WOW32_strchr(pszPath, '\\') || WOW32_strchr(pszPath, ':') || strlen(pszPath) > 12) {
            // LOGDEBUG(0,("WowIsKnownDLL: %s has a path, not checking.\n", pszPath));
            goto Cleanup;
        }

        //
        // Make a lowercase copy of the path.
        //

        WOW32_strncpy(szLowercasePath, pszPath, sizeof(szLowercasePath));
        szLowercasePath[sizeof(szLowercasePath)-1] = 0;
        WOW32_strlwr(szLowercasePath);


        //
        // Step through apszKnownDLL trying to find this DLL
        // in the list.
        //

        for (ppsz = &apszKnownDLL[0]; *ppsz; ppsz++) {

            //
            // We compare case-sensitive for speed, since we're
            // careful to lowercase the strings in apszKnownDLL
            // and szLowercasePath.
            //

            if (!WOW32_strcmp(szLowercasePath, *ppsz)) {

                //
                // We found the DLL in the list, now build up
                // a buffer for the 16-bit side containing
                // the full path to that DLL in the system32
                // directory.
                //

                cbKnownDLLPath = strlen(pszSystemDirectory) +
                                 1 +                     // "\"
                                 strlen(szLowercasePath) +
                                 1;                      // null

                *pvpszKnownDLLPath = malloc16(cbKnownDLLPath);

                if (*pvpszKnownDLLPath) {
#ifndef _X86_
                    HANDLE hFile;
#endif

                    GETPSZPTRNOLOG(*pvpszKnownDLLPath, pszKnownDLLPath);

#ifndef _X86_
                    // On RISC platforms, wx86 support tells 32-bit apps that
                    // the system dir is SYS32X86 instead of SYSTEM32.  This
                    // allows us to keep the x86 binaries separate from the
                    // native RISC binaries in the SYSTEM32 dir.  It also
                    // prevents x86 setup programs from clobbering the native
                    // RISC binaries & replacing them with an x86 version in the
                    // SYSTEM32 dir.  Unfortunately several "32-bit" programs
                    // have 16-bit components (Most notably Outlook forms
                    // support).  These 16-bit components will also be copied to
                    // the SYS32X86 dir.  This is not a problem unless the
                    // binary shows up in our KnownDLLs list. This code attempts
                    // to locate KnownDLLs in the SYS32X86 dir on RISC machines
                    // before looking in the SYSTEM32 dir. See bug #321335.
                    strcpy(pszKnownDLLPath, pszWindowsDirectory);
                    strcat(pszKnownDLLPath, "\\SYS32X86\\");
                    strcat(pszKnownDLLPath, szLowercasePath);

                    // see if this knowndll exists in the sys32x86 dir
                    hFile = CreateFile(pszKnownDLLPath,
                                       GENERIC_READ,
                                       FILE_SHARE_READ,
                                       NULL,
                                       OPEN_EXISTING,
                                       FILE_ATTRIBUTE_NORMAL,
                                       NULL);

                    if(hFile != INVALID_HANDLE_VALUE) {

                         CloseHandle(hFile);

                         //Yep, that's what we'll go with
                         LOGDEBUG(0,("WowIsKnownDLL: %s known -=> %s\n",
                                    pszPath,
                                    pszKnownDLLPath));
                         FLUSHVDMPTR(*pvpszKnownDLLPath,
                                     cbKnownDLLPath,
                                     pszKnownDLLPath);
                         FREEPSZPTR(pszKnownDLLPath);

                         ul = 1;  // return success, meaning is known dll
                         goto Cleanup;
                    }
                    // otherwise we just fall through & use system32 dir
#endif  // ifndef _X86_

                    strcpy(pszKnownDLLPath, pszSystemDirectory);
                    strcat(pszKnownDLLPath, "\\");
                    strcat(pszKnownDLLPath, szLowercasePath);

                    // LOGDEBUG(0,("WowIsKnownDLL: %s known -=> %s\n", pszPath, pszKnownDLLPath));

                    FLUSHVDMPTR(*pvpszKnownDLLPath, cbKnownDLLPath, pszKnownDLLPath);
                    FREEPSZPTR(pszKnownDLLPath);

                    ul = 1;          // return success, meaning is known dll
                    goto Cleanup;
                }
            }
        }

        //
        // We've checked the Known DLL list and come up empty, or
        // malloc16 failed.
        //

        // LOGDEBUG(0,("WowIsKnownDLL: %s is not a known DLL.\n", szLowercasePath));

    } else {

        //
        // pszPath is NULL, so free the 16-bit buffer pointed
        // to by *pvpszKnownDLLPath.
        //

        if (*pvpszKnownDLLPath) {
            free16(*pvpszKnownDLLPath);
            ul = 1;
        }
    }

  Cleanup:
    FLUSHVDMPTR(parg16->lplpszKnownDLLPath, sizeof(*pvpszKnownDLLPath), pvpszKnownDLLPath);
    FREEVDMPTR(pvpszKnownDLLPath);
    FREEPSZPTR(pszPath);
    FREEARGPTR(parg16);

    return ul;
}


VOID RemoveHmodFromCache(HAND16 hmod16)
{
    INT i;

    //
    // blow this guy out of the hinst/hmod cache
    // if we find it, slide the other entries up to overwrite it
    // and then zero out the last entry
    //

    for (i = 0; i < CHMODCACHE; i++) {
        if (ghModCache[i].hMod16 == hmod16) {

            // if we're not at the last entry, slide the rest up 1

            if (i != CHMODCACHE-1) {
                RtlMoveMemory((PVOID)(ghModCache+i),
                              (CONST VOID *)(ghModCache+i+1),
                              sizeof(HMODCACHE)*(CHMODCACHE-i-1) );
            }

            // the last entry is now either a dup or the one going away

            ghModCache[CHMODCACHE-1].hMod16 =
            ghModCache[CHMODCACHE-1].hInst16 = 0;
        }
    }
}

//
// Scans the share memory segment for wow processes which might have
// been killed and removes them.
//

VOID
CleanseSharedList(
    VOID
) {
    LPSHAREDTASKMEM     lpstm;
    LPSHAREDMEMOBJECT   lpsmo;
    LPSHAREDPROCESS     lpsp;
    LPSHAREDPROCESS     lpspPrev;
    HANDLE              hProcess;
    DWORD               dwOffset;

    lpstm = LOCKSHAREWOW();
    if ( !lpstm ) {
        LOGDEBUG(0,("WOW32: CleanseSharedList failed to map in shared wow memory\n") );
        return;
    }

    if ( !lpstm->fInitialized ) {
        lpstm->fInitialized = TRUE;
        lpstm->dwFirstProcess = 0;
    }

    lpsmo = (LPSHAREDMEMOBJECT)((CHAR *)lpstm + sizeof(SHAREDTASKMEM));

    lpspPrev = NULL;
    dwOffset = lpstm->dwFirstProcess;

    while( dwOffset ) {
        lpsp = (LPSHAREDPROCESS)((CHAR *)lpstm + dwOffset);

        WOW32ASSERT(lpsp->dwType == SMO_PROCESS);

        // Test this process to see if he is still around.

        hProcess = OpenProcess( SYNCHRONIZE, FALSE, lpsp->dwProcessId );
        if ( hProcess == NULL ) {
           // cleanup tasks for this process first
           LPSHAREDTASK lpst;
           DWORD dwTaskOffset;

           dwTaskOffset = lpsp->dwFirstTask; // this is an offset of the first task
           while (dwTaskOffset) {

              lpst = (LPSHAREDTASK)((CHAR*)lpstm + dwTaskOffset);
              // log this removal
              LOGDEBUG(0, ("CleanseSharedList: Forceful removal of a dead task %s\n", lpst->szFilePath));

              dwTaskOffset = lpst->dwNextTask;
              lpst->dwType = SMO_AVAILABLE;
           }

           if ( lpspPrev ) {
              lpspPrev->dwNextProcess = lpsp->dwNextProcess;
           } else {
               lpstm->dwFirstProcess = lpsp->dwNextProcess;
           }
           lpsp->dwType = SMO_AVAILABLE;

        } else {
           CloseHandle( hProcess );
           lpspPrev = lpsp;        // only update lpspPrev if lpsp is valid
        }
        dwOffset = lpsp->dwNextProcess;
    }

    UNLOCKSHAREWOW();
}

//
// Add this process to the shared memory list of wow processes
//
VOID
AddProcessSharedList(
    VOID
) {
    LPSHAREDTASKMEM     lpstm;
    LPSHAREDMEMOBJECT   lpsmo;
    LPSHAREDPROCESS     lpsp;
    DWORD               dwResult;
    INT                 count;

    lpstm = LOCKSHAREWOW();
    if ( !lpstm ) {
        LOGDEBUG(0,("WOW32: AddProcessSharedList failed to map in shared wow memory\n") );
        return;
    }

    // Scan for available slot
    count = 0;
    dwResult = 0;

    lpsmo = (LPSHAREDMEMOBJECT)((CHAR *)lpstm + sizeof(SHAREDTASKMEM));

    while ( count < MAX_SHARED_OBJECTS ) {
        if ( lpsmo->dwType == SMO_AVAILABLE ) {
            lpsp = (LPSHAREDPROCESS)lpsmo;
            dwResult = (DWORD)((CHAR *)lpsp - (CHAR *)lpstm);
            lpsp->dwType          = SMO_PROCESS;
            lpsp->dwProcessId     = GetCurrentProcessId();
            lpsp->dwAttributes    = fSeparateWow ? 0 : WOW_SYSTEM;
            lpsp->pfnW32HungAppNotifyThread = (LPTHREAD_START_ROUTINE) W32HungAppNotifyThread;
            lpsp->dwNextProcess   = lpstm->dwFirstProcess;
            lpsp->dwFirstTask     = 0;
            lpstm->dwFirstProcess = dwResult;
            break;
        }
        lpsmo++;
        count++;
    }
    if ( count == MAX_SHARED_OBJECTS ) {
        LOGDEBUG(0, ("WOW32: AddProcessSharedList: Not enough room in WOW's Shared Memory\n") );
    }
    UNLOCKSHAREWOW();

    dwSharedProcessOffset = dwResult;
}

//
// Remove this process from the shared memory list of wow tasks
//
VOID
RemoveProcessSharedList(
    VOID
) {
    LPSHAREDTASKMEM     lpstm;
    LPSHAREDPROCESS     lpsp;
    LPSHAREDPROCESS     lpspPrev;
    DWORD               dwOffset;
    DWORD               dwCurrentId;

    lpstm = LOCKSHAREWOW();
    if ( !lpstm ) {
        LOGDEBUG(0,("WOW32: RemoveProcessSharedList failed to map in shared wow memory\n") );
        return;
    }

    lpspPrev = NULL;
    dwCurrentId = GetCurrentThreadId();
    dwOffset = lpstm->dwFirstProcess;

    while( dwOffset != 0 ) {
        lpsp = (LPSHAREDPROCESS)((CHAR *)lpstm + dwOffset);
        WOW32ASSERT(lpsp->dwType == SMO_PROCESS);

        // Is this the guy to remove?

        if ( lpsp->dwProcessId == dwCurrentId ) {
           // so we're removing this process from the shared list




            if ( lpspPrev ) {
                lpspPrev->dwNextProcess = lpsp->dwNextProcess;
            } else {
                lpstm->dwFirstProcess = lpsp->dwNextProcess;
            }
            lpsp->dwType = SMO_AVAILABLE;
            break;
        }
        lpspPrev = lpsp;
        dwOffset = lpsp->dwNextProcess;
    }

    UNLOCKSHAREWOW();
}

//
// AddTaskSharedList
//
// Add this thread to the shared memory list of wow tasks.
// If hMod16 is zero, this call is to reserve the given
// htask, another call will come later to really add the
// task entry.
//
// When reserving an htask, a return of 0 means the htask
// is in use in another VDM, a nonzero return means either
// the shared memory is full or couldn't be accessed OR
// the htask was reserved.  This way the return is passed
// directly back to krnl386's task.asm where 0 means try
// again and nonzero means go with it.
//

WORD
AddTaskSharedList(
    HTASK16 hTask16,
    HAND16  hMod16,
    PSZ     pszModName,
    PSZ     pszFilePath
) {
    LPSHAREDTASKMEM     lpstm;
    LPSHAREDPROCESS     lpsp;
    LPSHAREDTASK        lpst;
    LPSHAREDMEMOBJECT   lpsmo;
    INT                 count;
    INT                 len;
    WORD                wRet;

    lpstm = LOCKSHAREWOW();
    if ( !lpstm ) {
        LOGDEBUG(0,("WOW32: AddTaskSharedList failed to map in shared wow memory\n") );
        wRet = hTask16;
        goto Exit;
    }

    lpsp = (LPSHAREDPROCESS)((CHAR *)lpstm + dwSharedProcessOffset);

    //
    // Scan to see if this htask is already in use.
    //

    lpsmo = (LPSHAREDMEMOBJECT)((CHAR *)lpstm + sizeof(SHAREDTASKMEM));
    count = 0;
    while ( count < MAX_SHARED_OBJECTS ) {
        if ( lpsmo->dwType == SMO_TASK ) {
            lpst = (LPSHAREDTASK)lpsmo;
            if (lpst->hTask16 == hTask16) {

                //
                // This htask is already in the table, if we're calling to fill in the
                // details that's fine, if we are calling to reserve fail the call,
                //

                if (hMod16) {

                    lpst->dwThreadId     = GetCurrentThreadId();
                    lpst->hMod16         = (WORD)hMod16;

                    strcpy(lpst->szModName, pszModName);

                    len = strlen(pszFilePath);
                    WOW32ASSERTMSGF(len <= (sizeof lpst->szFilePath) - 1,
                                    ("WOW32: too-long EXE path truncated in shared memory: '%s'\n", pszFilePath));
                    len = min(len, (sizeof lpst->szFilePath) - 1);
                    RtlCopyMemory(lpst->szFilePath, pszFilePath, len);
                    lpst->szFilePath[len] = 0;

                    wRet = hTask16;
                } else {
                    wRet = 0;
                }
                goto UnlockExit;
            }
        }
        lpsmo++;
        count++;
    }

    //
    // We didn't find this htask, scan for available slot.
    //

    lpsmo = (LPSHAREDMEMOBJECT)((CHAR *)lpstm + sizeof(SHAREDTASKMEM));
    count = 0;
    while ( count < MAX_SHARED_OBJECTS ) {
        if ( lpsmo->dwType == SMO_AVAILABLE ) {
            lpst = (LPSHAREDTASK)lpsmo;
            lpst->dwType         = SMO_TASK;
            lpst->hTask16        = (WORD)hTask16;
            lpst->dwThreadId     = 0;
            lpst->hMod16         = 0;
            lpst->szModName[0]   = 0;
            lpst->szFilePath[0]  = 0;
            lpst->dwNextTask     = lpsp->dwFirstTask;
            lpsp->dwFirstTask    = (DWORD)((CHAR *)lpst - (CHAR *)lpstm);
            break;
        }
        lpsmo++;
        count++;
    }
    if ( count == MAX_SHARED_OBJECTS ) {
        LOGDEBUG(0, ("WOW32: AddTaskSharedList: Not enough room in WOW's Shared Memory\n") );
    }

    wRet = hTask16;

UnlockExit:
    UNLOCKSHAREWOW();
Exit:
    return wRet;
}


//
// Remove this thread from the shared memory list of wow tasks
//
VOID
RemoveTaskSharedList(
    VOID
) {
    LPSHAREDTASKMEM     lpstm;
    LPSHAREDPROCESS     lpsp;
    LPSHAREDTASK        lpst;
    LPSHAREDTASK        lpstPrev;
    DWORD               dwCurrentId;
    DWORD               dwOffset;

    lpstm = LOCKSHAREWOW();
    if ( !lpstm ) {
        LOGDEBUG(0,("WOW32: RemoveTaskSharedList failed to map in shared wow memory\n") );
        return;
    }

    lpsp = (LPSHAREDPROCESS)((CHAR *)lpstm + dwSharedProcessOffset);

    lpstPrev = NULL;
    dwCurrentId = GetCurrentThreadId();
    dwOffset = lpsp->dwFirstTask;

    while( dwOffset != 0 ) {
        lpst = (LPSHAREDTASK)((CHAR *)lpstm + dwOffset);

        WOW32ASSERT(lpst->dwType == SMO_TASK);

        // Is this the guy to remove?

        if ( lpst->dwThreadId == dwCurrentId ) {
            if ( lpstPrev ) {
                lpstPrev->dwNextTask = lpst->dwNextTask;
            } else {
                lpsp->dwFirstTask = lpst->dwNextTask;
            }
            lpst->dwType = SMO_AVAILABLE;
            break;
        }
        lpstPrev = lpst;
        dwOffset = lpst->dwNextTask;
    }

    UNLOCKSHAREWOW();
}


VOID W32RefreshCurrentDirectories (PCHAR lpszzEnv)
{
LPSTR   lpszVal;
CHAR   chDrive, achEnvDrive[] = "=?:";

    if (lpszzEnv) {
        while(*lpszzEnv) {
            if(*lpszzEnv == '=' &&
                    (chDrive = (CHAR)toupper(*(lpszzEnv+1))) >= 'A' &&
                    chDrive <= 'Z' &&
                    (*(PCHAR)((ULONG)lpszzEnv+2) == ':')) {
                lpszVal = (PCHAR)((ULONG)lpszzEnv + 4);
                achEnvDrive[1] = chDrive;
                SetEnvironmentVariable (achEnvDrive,lpszVal);
            }
            lpszzEnv = WOW32_strchr(lpszzEnv,'\0');
            lpszzEnv++;
        }
        *(PUCHAR)DosWowData.lpSCS_ToSync = (UCHAR)0xff;
    }
}


/* WK32CheckUserGdi - hack routine to support Simcity. See the explanation
 *                    in kernel31\3ginterf.asm routine HackCheck.
 *
 *
 * Entry - pszPath  Full Path of the file in the module table
 *
 * Exit
 *     SUCCESS
 *       1
 *
 *     FAILURE
 *       0
 *
 */

ULONG FASTCALL WK32CheckUserGdi(PVDMFRAME pFrame)
{
    PWOWCHECKUSERGDI16 parg16;
    PSTR    psz;
    CHAR    szPath[MAX_PATH+10];
    UINT    cb;
    ULONG   ul;

    //
    // Get arguments.
    //

    GETARGPTR(pFrame, sizeof(WOWCHECKUSERGDI16), parg16);
    psz = SEGPTR(FETCHWORD(parg16->pszPathSegment),
                     FETCHWORD(parg16->pszPathOffset));

    FREEARGPTR(parg16);

    strcpy(szPath, pszSystemDirectory);
    cb = strlen(szPath);

    strcpy(szPath + cb, "\\GDI.EXE");

    if (WOW32_stricmp(szPath, psz) == 0)
        goto Success;

    strcpy(szPath + cb, "\\USER.EXE");

    if (WOW32_stricmp(szPath, psz) == 0)
        goto Success;

    ul = 0;
    goto Done;

Success:
    ul = 1;

Done:
    return ul;
}



/* WK32ExitKernel - Force the Distruction of the WOW Process
 *                  Formerly known as WK32KillProcess.
 *
 * Called when the 16 bit kernel exits and by KillWOW and
 * checked WOWExec when the user wants to nuke the shared WOW.
 *
 * ENTRY
 *
 *
 * EXIT
 *  Never Returns - The Process Goes Away
 *
 */

ULONG FASTCALL WK32ExitKernel(PVDMFRAME pFrame)
{
    PEXITKERNEL16 parg16;

    GETARGPTR(pFrame, sizeof(*parg16), parg16);

    WOW32ASSERTMSGF(
        ! parg16->wExitCode,
        ("\n"
         "WOW ERROR:  ExitKernel(0x%x) called on 16-bit side.\n"
         "==========  Please contact DaveHart or another WOW developer.\n"
         "\n\n",
         parg16->wExitCode
        ));

    ExitVDM(WOWVDM, ALL_TASKS);
    ExitProcess(parg16->wExitCode);

    return 0;   // Never executed, here to avoid compiler warning.
}





/* WK32FatalExit - Called as FatalExitThunk by kernel16 FatalExit
 *
 *
 * parg16->f1 is FatalExit code
 *
 */

ULONG FASTCALL WK32FatalExit(PVDMFRAME pFrame)
{
    PFATALEXIT16 parg16;

    GETARGPTR(pFrame, sizeof(*parg16), parg16);

    WOW32ASSERTMSGF(
        FALSE,
        ("\n"
         "WOW ERROR:  FatalExit(0x%x) called by 16-bit WOW kernel.\n"
         "==========  Contact the DOSWOW alias.\n"
         "\n\n",
         FETCHWORD(parg16->f1)
        ));

    // Sometimes we get this with no harm done (app bug)

    ExitVDM(WOWVDM, ALL_TASKS);
    ExitProcess(parg16->f1);

    return 0;   // Never executed, here to avoid compiler warning.
}


//
// WowPartyByNumber is present on checked builds only as a convenience
// to WOW developers who need a quick, temporary thunk for debugging.
// The checked wowexec.exe has a menu item, Party By Number, which
// collects a number and string parameter and calls this thunk.
//

#ifdef DEBUG

#pragma warning (4:4723)        // lower to -W4

ULONG FASTCALL WK32WowPartyByNumber(PVDMFRAME pFrame)
{
    PWOWPARTYBYNUMBER16 parg16;
    PSZ psz;
    ULONG ulRet = 0;

    GETARGPTR(pFrame, sizeof(*parg16), parg16);
    GETPSZPTR(parg16->psz, psz);

    switch (parg16->dw) {

        case 0:  // Access violation
            *(char *)0xa0000000 = 0;
            break;

        case 1:  // Stack overflow
            {
                char EatStack[2048];

                strcpy(EatStack, psz);
                WK32WowPartyByNumber(pFrame);
                strcpy(EatStack, psz);
            }
            break;

        case 2:  // Datatype misalignment
            {
                DWORD adw[2];
                PDWORD pdw = (void *)((char *)adw + 2);

                *pdw = (DWORD)-1;

                //
                // On some platforms the above will just work (hardware or
                // emulation), so we force it with RaiseException.
                //
                RaiseException((DWORD)EXCEPTION_DATATYPE_MISALIGNMENT,
                               0, 0, NULL);
            }
            break;

        case 3:  // Integer divide by zero
            ulRet = 1 / (parg16->dw - 3);
            break;

        case 4:  // Other exception
            RaiseException((DWORD)EXCEPTION_ARRAY_BOUNDS_EXCEEDED,
                           EXCEPTION_NONCONTINUABLE, 0, NULL);
            break;

        case 5:  // gpm16
            //
            // Quick test that WOWGetProcModule16 is working
            //
            {
                char sz[256];

                wsprintf(sz, "GetProcModule16(%lx) == %x\n", gpfn16GetProcModule, WOWGetProcModule16(gpfn16GetProcModule));
                OutputDebugString(sz);
            }
            break;

        case 6:  // Test lstrcmp callback to user16 used by user32
            {
                char szMsg[256];
                WCHAR wsz1[256];
                WCHAR wsz2[256];
                char *s1;
                char *s2;

                s1 = psz;
                s2 = WOW32_strchr(s1, ' ');

                if (s2) {

                    *s2++ = 0;

                    RtlMultiByteToUnicodeN(
                        wsz1,
                        sizeof wsz1,
                        NULL,
                        s1,
                        strlen(s1) + 1
                        );

                    RtlMultiByteToUnicodeN(
                        wsz2,
                        sizeof wsz2,
                        NULL,
                        s2,
                        strlen(s2) + 1
                        );

                    ulRet = WOWlstrcmp16(wsz1, wsz2);

                    wsprintf(szMsg, "WOWlstrcmp16(%ws, %ws) == %d", wsz1, wsz2, ulRet);
                    MessageBox(NULL, szMsg, "WK32WowPartyByNumber", MB_OK | MB_ICONEXCLAMATION);
                } else {
                    MessageBox(NULL, "use two strings separated by a space", "WK32WowPartyByNumber", MB_OK | MB_ICONEXCLAMATION);
                    ulRet = 0;
                }
            }
            break;

        default:
            {
                char szMsg[255];

                wsprintf(szMsg, "WOW Unhandled Party By Number (%d, '%s')", parg16->dw, psz);

                MessageBeep(0);
                MessageBox(NULL, szMsg, "WK32WowPartyByNumber", MB_OK | MB_ICONEXCLAMATION);
            }
    }

    FREEPSZPTR(psz);
    FREEARGPTR(parg16);
    return ulRet;
}

#endif


//
// MyVerQueryValue checks several popular code page values for the given
// string.  This may need to be extended ala WinFile's wfdlgs2.c to search
// the translation table.  For now we only need a few.
//

BOOL
FASTCALL
MyVerQueryValue(
    const LPVOID pBlock,
    LPSTR lpName,
    LPVOID * lplpBuffer,
    PUINT puLen
    )
{
#define SFILEN 25                // Length of apszSFI strings without null
    static PSZ apszSFI[] = {
        "\\StringFileInfo\\040904E4\\",
        "\\StringFileInfo\\04090000\\"
    };
    char szSubBlock[128];
    BOOL fRet;
    int i;

    strcpy(szSubBlock+SFILEN, lpName);

    for (fRet = FALSE, i = 0;
         i < (sizeof apszSFI / sizeof apszSFI[0]) && !fRet;
         i++) {

        RtlCopyMemory(szSubBlock, apszSFI[i], SFILEN);
        fRet = VerQueryValue(pBlock, szSubBlock, lplpBuffer, puLen);
    }

    return fRet;
}


//
// Utility routine to fetch the Product Name and Product Version strings
// from a given EXE.
//

BOOL
FASTCALL
WowGetProductNameVersion(
    PSZ pszExePath,
    PSZ pszProductName,
    DWORD cbProductName,
    PSZ pszProductVersion,
    DWORD cbProductVersion,
    PSZ pszParamName,
    PSZ pszParam,
    DWORD cbParam
    )
{
    DWORD dwZeroMePlease;
    DWORD cbVerInfo;
    LPVOID lpVerInfo = NULL;
    LPSTR pName;
    DWORD cbName;
    LPSTR pVersion;
    DWORD cbVersion;
    BOOL fRet;
    DWORD cbParamValue;
    LPSTR pParamValue;

    fRet = (
        (cbVerInfo = GetFileVersionInfoSize(pszExePath, &dwZeroMePlease)) &&
        (lpVerInfo = malloc_w(cbVerInfo)) &&
        GetFileVersionInfo(pszExePath, 0, cbVerInfo, lpVerInfo) &&
        MyVerQueryValue(lpVerInfo, "ProductName", &pName, &cbName) &&
        cbName <= cbProductName &&
        MyVerQueryValue(lpVerInfo, "ProductVersion", &pVersion, &cbVersion) &&
        cbVersion <= cbProductVersion
        );
    if (fRet && NULL != pszParamName && NULL != pszParam) {
       fRet = MyVerQueryValue(lpVerInfo, pszParamName, &pParamValue, &cbParamValue) &&
              cbParamValue <= cbParam;
    }


    if (fRet) {
        strcpy(pszProductName, pName);
        strcpy(pszProductVersion, pVersion);
        if (NULL != pszParamName && NULL != pszParam) {
           strcpy(pszParam, pParamValue);
        }
    }

    if (lpVerInfo) {
        free_w(lpVerInfo);
    }

    return fRet;
}


#if 0    // currently unused
//
// This routine is simpler to use if you are doing an exact match
// against a particular name/version pair.
//

BOOL
FASTCALL
WowDoNameVersionMatch(
    PSZ pszExePath,
    PSZ pszProductName,
    PSZ pszProductVersion
    )
{
    DWORD dwJunk;
    DWORD cbVerInfo;
    LPVOID lpVerInfo = NULL;
    LPSTR pName;
    LPSTR pVersion;
    BOOL fRet;

    fRet = (
        (cbVerInfo = GetFileVersionInfoSize(pszExePath, &dwJunk)) &&
        (lpVerInfo = malloc_w(cbVerInfo)) &&
        GetFileVersionInfo(pszExePath, 0, cbVerInfo, lpVerInfo) &&
        MyVerQueryValue(lpVerInfo, "ProductName", &pName, &dwJunk) &&
        ! WOW32_stricmp(pszProductName, pName) &&
        MyVerQueryValue(lpVerInfo, "ProductVersion", &pVersion, &dwJunk) &&
        ! WOW32_stricmp(pszProductVersion, pVersion)
        );

    if (lpVerInfo) {
        free_w(lpVerInfo);
    }

    return fRet;
}
#endif




//
// This thunk is called by kernel31's GetModuleHandle
// when it cannot find a handle for given filename.
//
// We look to see if this task has any child apps
// spawned via WinOldAp, and if so we look to see
// if the module name matches for any of them.
// If it does, we return the hmodule of the
// associated WinOldAp.  Otherwise we return 0
//

ULONG FASTCALL WK32WowGetModuleHandle(PVDMFRAME pFrame)
{
    PWOWGETMODULEHANDLE16 parg16;
    ULONG ul;
    PSZ pszModuleName;
    PTD ptd;
    PWOAINST pWOA;

    GETARGPTR(pFrame, sizeof(*parg16), parg16);
    GETPSZPTR(parg16->lpszModuleName, pszModuleName);

    ptd = CURRENTPTD();

    EnterCriticalSection(&ptd->csTD);

    pWOA = ptd->pWOAList;
    while (pWOA && WOW32_strcmp(pszModuleName, pWOA->szModuleName)) {
        pWOA = pWOA->pNext;
    }

    if (pWOA && pWOA->ptdWOA) {
        ul = pWOA->ptdWOA->hMod16;
        LOGDEBUG(LOG_ALWAYS, ("WK32WowGetModuleHandle(%s) returning %04x.\n",
                              pszModuleName, ul));
    } else {
        ul = 0;
    }

    LeaveCriticalSection(&ptd->csTD);

    return ul;
}


//
// This function is called by kernel31's CreateTask after it's
// allocated memory for the TDB, the selector of which serves
// as the htask.  We want to enforce uniqueness of these htasks
// across all WOW VDMs in the system, so this function attempts
// to reserve the given htask in the shared memory structure.
// If successful the htask is returned, if it's already in use
// 0 is returned and CreateTask will allocate another selector
// and try again.
//
// -- DaveHart 24-Apr-96
//

ULONG FASTCALL WK32WowReserveHtask(PVDMFRAME pFrame)
{
    PWOWRESERVEHTASK16 parg16;
    ULONG ul;

    GETARGPTR(pFrame, sizeof(*parg16), parg16);

    ul = AddTaskSharedList(parg16->htask, 0, NULL, NULL);

    FREEARGPTR(parg16);

    return ul;
}

/*
 * This function is called by kernel31 to dispatch a wow lfn api call
 * the responsible party here is in dem code and all we have to do is to
 * - retrieve it's frame pointer
 *
 *
 */

ULONG FASTCALL WK32WOWLFNEntry(PVDMFRAME pFrame)
{
   PWOWLFNFRAMEPTR16 parg16;
   LPVOID lpUserFrame;
   ULONG ul;

   GETARGPTR(pFrame, sizeof(*parg16), parg16);

   // now retrieve a flat pointer

   GETMISCPTR(parg16->lpUserFrame, lpUserFrame);

   ul = demWOWLFNEntry(lpUserFrame);

   FREEMISCPTR(lpUserFrame);
   FREEARGPTR(parg16);

   return(ul);
}


//
// This function is called by kernel31 to start or stop the
// shared WOW shutdown timer.
//

ULONG FASTCALL WK32WowShutdownTimer(PVDMFRAME pFrame)
{
    PWOWSHUTDOWNTIMER16 parg16;

    GETARGPTR(pFrame, sizeof(*parg16), parg16);

    if (parg16->fEnable) {

        //
        // When this thunk is called with fEnable == 1, to turn on the shutdown
        // timer, it is initially called on the task that is shutting down.  Since
        // we want to be on WowExec's thread so SetTimer will work right, in this
        // case we post a message to WowExec asking it to call this API again, but
        // on the right thread.
        //

        if (ghShellTDB != pFrame->wTDB) {
            PostMessage(ghwndShell, WM_WOWEXECSTARTTIMER, 0, 0);
        } else {
#ifdef WX86
            TermWx86System();
#endif

            SetTimer(ghwndShell, 1, dwSharedWowTimeout, NULL);
        }

    } else {

        //
        // A task was started before the timer expired, kill it.
        //

        WOW32ASSERTMSG(ghShellTDB == pFrame->wTDB, "WowShutdownTimer(0) called on non-WowExec thread\n");

        KillTimer(ghwndShell, 1);
    }

    FREEARGPTR(parg16);

    return 0;
}


//
// This function is called by kernel31 to shrink the process's
// working set to a minimum.
//

ULONG FASTCALL WK32WowTrimWorkingSet(PVDMFRAME pFrame)
{
    SetProcessWorkingSetSize(ghProcess, 0xffffffff, 0xffffffff);

    return 0;
}

//
// IsQuickBooksVersion2 used by WK32SetAppCompatFlags below.
//

BOOL FASTCALL IsQuickBooksVersion2(WORD pModule)
{
    BOOL fRet;
    PSZ pszModuleFileName;
    HANDLE hEXE;
    HANDLE hSec = 0;
    PVOID pEXE = NULL;
    PIMAGE_DOS_HEADER pMZ;
    PIMAGE_OS2_HEADER pNE;
    PBYTE pNResTab;
    DWORD cbVerInfo;
    DWORD dwJunk;

    fRet = FALSE;

    //
    // see wow16\inc\newexe.inc, NEW_EXE1 struct, ne_pfileinfo
    // is at offset 10, a near pointer within the segment referenced
    // by hmod.  This points to kernel.inc's OPENSTRUC which has
    // the filename buffer at offset 8.
    //

    pszModuleFileName = SEGPTR(pModule, (*(WORD *)SEGPTR(pModule, 10)) + 8);

    hEXE = CreateFile(
        pszModuleFileName,
        GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,   // security
        OPEN_EXISTING,
        0,      // flags & attributes
        NULL
        );

    if (INVALID_HANDLE_VALUE == hEXE) {
        goto Cleanup;
    }

    hSec = CreateFileMapping(
        hEXE,
        NULL,   // security
        PAGE_READONLY,
        0,      // max size hi
        0,      // max size lo  both zero == file size
        NULL    // name
        );

    if ( ! hSec) {
        goto Cleanup;
    }

    pEXE = MapViewOfFile(
        hSec,
        FILE_MAP_READ,
        0,      // offset hi
        0,      // offset lo
        0       // size to map   zero == entire file
        );

    // if MapViewOfFile failed assume it is not QuickBooks 2.0
    if (!pEXE ) {
        goto Cleanup;
    }

    pMZ = pEXE;

    if (IMAGE_DOS_SIGNATURE != pMZ->e_magic) {
        WOW32ASSERTMSG(IMAGE_DOS_SIGNATURE == pMZ->e_magic, "WOW IsQuickBooks MZ sig.\n");
        goto Cleanup;
    }

    pNE = (PVOID) ((PBYTE)pEXE + pMZ->e_lfanew);

    if (IMAGE_OS2_SIGNATURE != pNE->ne_magic) {
        WOW32ASSERTMSG(IMAGE_OS2_SIGNATURE == pNE->ne_magic, "WOW IsQuickBooks NE sig.\n");
        goto Cleanup;
    }

    pNResTab = (PBYTE)pEXE + pNE->ne_nrestab;

    //
    // The first entry in the non-resident names table is
    // the NE description specified in the .DEF file.
    // We have the culprit if it matches the string below,
    // note the initial 'R' is a length byte, 0x52 bytes follow,
    // so we compare 0x53.
    //
    // Of course Intuit is full of clever programmers, so this
    // description string still appears in QBW.EXE v3.1 and probably
    // later.  Thankfully someone there thought to add version
    // resources between v2 and v3, so if there are version
    // resources we'll say it's not v2.
    //

    fRet = RtlEqualMemory(
        pNResTab,
        "RQuickBooks for Windows Version 2.  Copyright 1993 Intuit Inc. All rights reserved.",
        0x53
        );

    if (fRet) {
        cbVerInfo = GetFileVersionInfoSize(pszModuleFileName, &dwJunk);
        fRet = !cbVerInfo;
    }

  Cleanup:

    if (pEXE) {
        UnmapViewOfFile(pEXE);
    }

    if (hSec) {
        CloseHandle(hSec);
    }

    if (INVALID_HANDLE_VALUE != hEXE) {
        CloseHandle(hEXE);
    }

    return fRet;
}


// the code below is stolen (with some enhancements) from
// then original WOWShouldWeSayWin95
//

BOOL FASTCALL fnInstallShieldOverrideVersionFlag(PTDB pTDB)
{
   CHAR szModName[9];
   PCHAR pch;
   CHAR szName[16];
   CHAR szVersion[16];
   CHAR szVerSubstring[4];
   DWORD dwSubVer;
   PSTR pszFileName;

   RtlCopyMemory(szModName, pTDB->TDB_ModName, 8);
   for (pch = &szModName[7]; ' ' == *pch && pch >= szModName; --pch);
   *++pch = '\0';

   if (WOW32_stricmp(szModName, "ISSET_SE")) {
      return(FALSE);
   }

   // now having the pTDB retrieve module file name from pExe
   // this guy's sitting in TDB_pModule -- and we know it's a real thing
   // not an alias

   //
   // see wow16\inc\newexe.inc, NEW_EXE1 struct, ne_pfileinfo
   // is at offset 10, a near pointer within the segment referenced
   // by hmod.  This points to kernel.inc's OPENSTRUC which has
   // the filename buffer at offset 8.
   //
   pszFileName = SEGPTR(pTDB->TDB_pModule, (*(WORD *)SEGPTR(pTDB->TDB_pModule, 10)) + 8);

   if (!WowGetProductNameVersion(pszFileName,
                                 szName,
                                 sizeof(szName),
                                 szVersion,
                                 sizeof(szVersion),
                                 NULL, NULL, 0) ||
      WOW32_stricmp(szName, "InstallSHIELD")) {
      return(FALSE);
   }

   //
   // now we definitely know it's installshield and it's version is in szVersion
   //


   //
   // InstallShield _Setup SDK_ setup.exe shipped
   // with VC++ 4.0 is stamped 2.20.903.0 but also
   // needs to be lied to about it being Win95.
   // According to samir@installshield.com versions
   // 2.20.903.0 through 2.20.905.0 need this.
   // We'll settle for 2.20.903* - 2.20.905*
   // These are based on the 3.0 codebase but
   // bear the 2.20.x version stamps.
   //

   if (RtlEqualMemory(szVersion, "2.20.90", 7) &&
       ('3' == szVersion[7] ||
        '4' == szVersion[7] ||
        '5' == szVersion[7]) ) {
       return(TRUE);
   }

   //
   // We want to lie in GetVersion if the version stamp on
   // the InstallShield setup.exe is 3.00.xxx.0, where
   // xxx is 000 through 087.  Later versions know how
   // to detect NT.
   //

   if (!RtlEqualMemory(szVersion, "3.00.", 5)) {
      return(FALSE);
   }

   RtlCopyMemory(szVerSubstring, &szVersion[5], 3);
   szVerSubstring[3] = 0;
   RtlCharToInteger(szVerSubstring, 10, &dwSubVer);

   if (dwSubVer >= 88 && dwSubVer != 101) {
      return(FALSE);
   }

   return(TRUE); // version 3.00.000 - 3.00.087
}

BOOL FASTCALL fnInstallTimelineOverrideVersionFlag(PTDB pTDB)
{
   CHAR szModName[9];
   PCHAR pch;
   CHAR szName[64];
   CHAR szVersion[16];
   CHAR szFileVersion[16];
   PSTR pszFileName;

   RtlCopyMemory(szModName, pTDB->TDB_ModName, 8);
   for (pch = &szModName[7]; ' ' == *pch && pch >= szModName; --pch);
   *++pch = '\0';

   if (WOW32_stricmp(szModName, "INSTBIN")) { // instbin is an installer for
      return(FALSE);
   }

   // now having the pTDB retrieve module file name from pExe
   // this guy's sitting in TDB_pModule -- and we know it's a real thing
   // not an alias

   //
   // see wow16\inc\newexe.inc, NEW_EXE1 struct, ne_pfileinfo
   // is at offset 10, a near pointer within the segment referenced
   // by hmod.  This points to kernel.inc's OPENSTRUC which has
   // the filename buffer at offset 8.
   //
   pszFileName = SEGPTR(pTDB->TDB_pModule, (*(WORD *)SEGPTR(pTDB->TDB_pModule, 10)) + 8);

   // now retrieve version resources
   if (!WowGetProductNameVersion(pszFileName,
                                 szName,
                                 sizeof(szName),
                                 szVersion,
                                 sizeof(szVersion),
                                 "FileVersion",
                                 szFileVersion,
                                 sizeof(szFileVersion)) ||
      WOW32_stricmp(szName, "Symantec Install for Windows Applications")) {
      return(FALSE);
   }

   // so it is Symantec install -- check versions

   if (!WOW32_stricmp(szVersion, "3.4") && !WOW32_stricmp(szFileVersion, "3.4.1.1")) {
      return(FALSE);
   }


   return(TRUE); // we can munch on Win95 -- this is not the same install as
                 // used in timeline application
}


typedef BOOL (FASTCALL *PFNOVERRIDEVERSIONFLAG)(PTDB);

PFNOVERRIDEVERSIONFLAG rgOverrideFns[] = {
   fnInstallShieldOverrideVersionFlag,
   fnInstallTimelineOverrideVersionFlag
};


// this function is to be used when we set "3.1" compat flag in the registry
// then we call these functions to override the flag if function returns true
// if returns TRUE then the version flag is set to 95
// if returns FALSE then the version flag is left to what it was in the registry

BOOL IsOverrideVersionFlag(PTDB pTDB)
{
   int i;
   BOOL fOverride = FALSE;

   for (i = 0; i < sizeof(rgOverrideFns)/sizeof(rgOverrideFns[0]) && !fOverride; ++i) {
       fOverride = (*rgOverrideFns[i])(pTDB);
   }

   return(fOverride);

}


//
// This function replaces the original assembler in
// kernel31\miscapi.asm.  It's the same, except it special-cases
// some flags based on more than just the module name (for
// example version stamps).
//
// Note that we're still running on the creator thread,
// so CURRENTPTD() refers to the parent of the app we're
// looking up flags for.
//

ULONG FASTCALL WK32SetAppCompatFlags(PVDMFRAME pFrame)
{
    PSETAPPCOMPATFLAGS16 parg16;
    DWORD dwAppCompatFlags = 0;
    PTDB  pTDB;
    char  szModName[9];
    char  szAppCompatFlags[12];  // 0x00000000

    GETARGPTR(pFrame, sizeof(*parg16), parg16);

    pTDB = (PVOID)SEGPTR(parg16->TDB,0);

    //
    // Hacks don't apply to 4.0 or above
    //

    if (pTDB->TDB_ExpWinVer < 0x400) {

        RtlCopyMemory(szModName, pTDB->TDB_ModName, sizeof(szModName)-1);
        szModName[sizeof(szModName)-1] = 0;


        szAppCompatFlags[0] = 0;

        if (GetProfileString(
                "Compatibility",
                szModName,
                "",
                szAppCompatFlags,
                sizeof(szAppCompatFlags))) {

            dwAppCompatFlags = strtoul(szAppCompatFlags, NULL, 0);
        }

        //
        // SOME hacks don't apply to 3.1 or above
        //
        // one hack (enumeration of helv ) is valid for 30a as well
        // see bug 41092

        if (pTDB->TDB_ExpWinVer == 0x30a) {
            dwAppCompatFlags &= GACF_31VALIDMASK | GACF_ENUMHELVNTMSRMN | GACF_HACKWINFLAGS;
        }
        else if (pTDB->TDB_ExpWinVer > 0x30a) {
            dwAppCompatFlags &= GACF_31VALIDMASK | GACF_HACKWINFLAGS;
        }




        //
        // Intuit QuickBooks 2.0 needs to have GACF_RANDOM3XUI turned on,
        // but later versions don't want it on.  Win9x prompts the user with
        // a warning that leads to a help file that tells the user to turn
        // on this bit using a little tool if they're using QBW v2.  We are
        // going to just do the right thing by looking at the Description
        // field of the EXE header for a string we expect only to find in
        // v2, and if it's there turn on GACF_RANDOM3XUI.
        //

        if (pTDB->TDB_ExpWinVer == 0x30a &&
            RtlEqualMemory(szModName, "QBW", 4)) {

            if (IsQuickBooksVersion2(pTDB->TDB_pModule)) {

                dwAppCompatFlags |= GACF_RANDOM3XUI;
            }

        }


        // this code checks to see that ISSET_SE in adobe premier 4.2
        // is told that it's running on Win95
        if (IsOverrideVersionFlag(pTDB)) {
           dwAppCompatFlags &= ~GACF_WINVER31;
        }

        LOGDEBUG(LOG_ALWAYS, ("WK32SetAppCompatFlags '%s' got %x (%s).\n",
                              szModName, dwAppCompatFlags, szAppCompatFlags));
    }

    FREEARGPTR(parg16);

    return dwAppCompatFlags;
}


//
// This function is called by mciavi32 to facilitate usage of a 16-bit mciavi
// instead
//
//
BOOL WOWUseMciavi16(VOID)
{
   return((BOOL)(CURRENTPTD()->dwWOWCompatFlagsEx & WOWCFEX_USEMCIAVI16));
}

VOID WOWExitVdm(ULONG iWowTask)
{
   if (ALL_TASKS == iWowTask) {
      // meltdown
      RemoveProcessSharedList();
   }

   ExitVDM(WOWVDM, iWowTask);
}

#if 0
BOOL DosWowGetCompatFlags(LPDWORD lpdwCF, LPDWORD lpdwCFEx)
{
   if (NULL != lpdwCF) {
      *lpdwCF = CURRENTPTD()->dwWOWCompatFlags;
   }
   if (NULL != lpdwCFEx) {
      *lpdwCFEx = CURRENTPTD()->dwWOWCompatFlagsEx;
   }

   return(TRUE);
}
#endif

typedef struct {
      LPCSTR pszMatchPath;
      int   cbMatchPathLen;
      PSZ   pszMapPath;
      int   cbMapPathLen;
      int   dwCLSID;
}
MATCHMAPPATH,*PMATCHMAPPATH;

#define WOWCSIDL_AllUsers   -1


const CHAR szStartMenu[]         =  "\\startm~1";
const CHAR szAllUsers[]          =  "\\alluse~1";
const CHAR szDesktop[]           =  "\\desktop";
const CHAR szAllUsersStartMenu[] =  "\\Profiles\\All Users\\Start Menu\\Programs";

const CHAR szSystem[]            =  "\\System";
#define CBSZSYSTEM (sizeof(szSystem)/sizeof(CHAR)-1)          

MATCHMAPPATH MatchMapPath[ ]=
{

 {szStartMenu,
  sizeof(szStartMenu)/sizeof(CHAR)-1,      //strlen(szStartMenu) == sizeof(szStartMenu)-1
  NULL,
  0,
  CSIDL_COMMON_STARTMENU
 },
 {szAllUsers,
  sizeof(szAllUsers)/sizeof(CHAR)-1,        //strlen(szAllUsers) == sizeof(szAllUsers)-1
  NULL,
  0,
  WOWCSIDL_AllUsers
 },
 {szDesktop,
  sizeof(szDesktop)/sizeof(CHAR)-1,
  NULL,
  0,
  CSIDL_COMMON_DESKTOPDIRECTORY
 },
 {szAllUsersStartMenu,
  sizeof(szAllUsersStartMenu)/sizeof(CHAR)-1,
  NULL,
  0,
  CSIDL_COMMON_PROGRAMS
 }

};

PSZ SyncSysFile[] ={
      "\\user.exe",
      "\\ole2.dll",
      "\\olesvr.dll",
      "\\compobj.dll",
      "\\storage.dll",
      "\\commdlg.dll",
      "\\mmsystem.dll",
      "\\gdi.exe"      
// add the rest of the 16-bit system binaries
};

DWORD cbWinDirLen;
DWORD cbSystemDirLen;

VOID W32Init9xSpecialPath(
                       )
{
  char szBuf[ MAX_PATH ];
  int cb;
  PMATCHMAPPATH pMatchMapPath;

     pMatchMapPath = MatchMapPath + sizeof(MatchMapPath)/sizeof(MATCHMAPPATH);
    
     while(pMatchMapPath-- != MatchMapPath) {
         szBuf[0]='\0';
         if (pMatchMapPath->dwCLSID > 0) {
             SHGetSpecialFolderPath(NULL,
                                    szBuf,
                                    pMatchMapPath->dwCLSID,
                                    FALSE
                                    );
             }
         else
         if (WOWCSIDL_AllUsers == pMatchMapPath->dwCLSID) {
             cb = sizeof(szBuf);
             GetAllUsersProfileDirectory(szBuf, &cb);
             }

         GetShortPathName(szBuf,szBuf,sizeof(szBuf));

         cb=strlen(szBuf)+1;

         if (1 < cb){
             LPSTR lpStr=NULL;
             lpStr=malloc_w_or_die(cb);
             strcpy(lpStr,szBuf);
             pMatchMapPath->pszMapPath=lpStr;
             pMatchMapPath->cbMapPathLen=cb;
             }
         else {
             pMatchMapPath->pszMapPath=NULL;
             pMatchMapPath->cbMapPathLen=0;
             }
         }

         //
         // Lower case windows directory and get its length
         //

         _strlwr(pszWindowsDirectory);
         cbWinDirLen=strlen(pszWindowsDirectory);
         cbSystemDirLen = strlen(pszSystemDirectory);
}


BOOL W32Map9xSpecialPath(PSZ lp9xPathName,
                      PSZ lpMapPathName
                      )
{
 PSZ lpPathName=lp9xPathName;
 PMATCHMAPPATH lpMMPath;

    if( !_strnicmp(lpPathName,pszWindowsDirectory,cbWinDirLen) && lpPathName[cbWinDirLen] == '\\') {

       lpMMPath = MatchMapPath + sizeof(MatchMapPath)/sizeof(MATCHMAPPATH);
       while(lpMMPath-- != MatchMapPath) {

           // move lpPathName past windowsdirectory
           lpPathName=lp9xPathName+cbWinDirLen;

           if ( lpMMPath->pszMapPath ){

               if (!WOW32_strnicmp( lpPathName,lpMMPath->pszMatchPath, lpMMPath->cbMatchPathLen) &&
                   (!lpPathName[lpMMPath->cbMatchPathLen] || lpPathName[lpMMPath->cbMatchPathLen]=='\\'))
                 {
                   memcpy( lpMapPathName,
                           lpMMPath->pszMapPath,
                           lpMMPath->cbMapPathLen
                         );

                   // move lpPathName past MatchPath
                   lpPathName+=lpMMPath->cbMatchPathLen;

                   // copy rest of lpPathName after cbMapPathLen

                   strcpy(lpMapPathName+lpMMPath->cbMapPathLen-1,
                          lpPathName
                         );
                   
                   LOGDEBUG(LOG_WARNING, ("Mapping 9x<%s> to NT<%s>\n",lp9xPathName,lpMapPathName));
                   return TRUE;
                   }
               }
           }

       if (CURRENTPTD()->dwWOWCompatFlags2 & WOWCF2_SYNCSYSFILE) {

           PSZ *ppszSyncSysFile;

           lpPathName = lp9xPathName + cbWinDirLen;
           if (!WOW32_strnicmp(lpPathName,szSystem,CBSZSYSTEM) && lpPathName[CBSZSYSTEM]=='\\') {                                           
               
               lpPathName += CBSZSYSTEM; // mv past "%windir%\system"
               ppszSyncSysFile = SyncSysFile + sizeof(SyncSysFile)/sizeof(PSZ);
               
               while (ppszSyncSysFile-- != SyncSysFile) {
               
                   if (!WOW32_stricmp(lpPathName,*ppszSyncSysFile)){
                       memcpy(lpMapPathName,pszSystemDirectory,cbSystemDirLen);
                       strcpy(lpMapPathName+cbSystemDirLen,lpPathName);
                       LOGDEBUG(LOG_WARNING, ("Mapping System<%s> to System32<%s>\n",lp9xPathName,lpMapPathName));
                       return TRUE;
                       }
                   }
               }
           }

       }

    return FALSE;
}


DWORD    dmOldBitsPerPel=0;
DWORD    dmChangeCount=0;

/*
 * WK32ChangeDisplayMode
 * WK32RevertDisplayMode
 * - changes display settings for apps automatically
 *   both assume that they get called only for apps with
 *   wow compat flag WOWCFEX_256DISPMODE.
 * - global variables dmOldBitsPerPel and dwChangeCount
 *   are protected by critical sections in functions that
 *   call WK32ChangeDisplayMode and WK32RevertDisplayMode
 *
 */


void WK32ChangeDisplayMode(DWORD dmBitsPerPel) {

    DEVMODEA dm;

    if (EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &dm)) {
       if (!dmChangeCount++) {
          dmOldBitsPerPel = dm.dmBitsPerPel;
       }

       if (dmBitsPerPel != dm.dmBitsPerPel) {
          dm.dmBitsPerPel = dmBitsPerPel;
          ChangeDisplaySettingsA(&dm, CDS_FULLSCREEN);
       }
    }

}


void WK32RevertDisplayMode(void) {
     DEVMODEA dm;

     if (dmChangeCount &&
         !--dmChangeCount &&
         EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &dm) &&
         dm.dmBitsPerPel <= 8 &&
         dmOldBitsPerPel > dm.dmBitsPerPel)
       {
         dm.dmBitsPerPel = dmOldBitsPerPel;
         ChangeDisplaySettingsA(&dm, CDS_FULLSCREEN);
     }
}