/****************************** Module Header ******************************\
* Module Name: wowexec.c
*
* Copyright (c) 1991, Microsoft Corporation
*
* WOWEXEC - 16 Bit Server Task - Does Exec Calls on Behalf of 32 bit CreateProcess
*
*
* History:
* 05-21-91 MattFe       Ported to Windows
* mar-20-92 MattFe      Added Error Message Boxes (from win 3.1 progman)
* apr-1-92 mattfe       added commandline exec and switch to path (from win 3.1 progman)
* jun-1-92 mattfe       changed wowgetnextvdmcommand
* 12-Nov-93 DaveHart    Multiple WOW support and remove captive
*                       GetNextVDMCommand thread from WOW32.
* 16-Nov-93 DaveHart    Reduce data segment size.
\***************************************************************************/
#include "wowexec.h"
#include "wowinfo.h"
#include "shellapi.h"
#ifndef PULONG
#define PULONG
#endif
#include "vint.h"
#include "dde.h"


/*
 * External Prototypes
 */
extern WORD FAR PASCAL WOWQueryDebug( void );
extern WORD FAR PASCAL WowWaitForMsgAndEvent( HWND);
extern void FAR PASCAL WowMsgBox(LPSTR szMsg, LPSTR szTitle, DWORD dwOptionalStyle);
extern DWORD FAR PASCAL WowPartyByNumber(DWORD dw, LPSTR psz);
extern DWORD FAR PASCAL WowKillTask(WORD htask);
HWND FaxInit(HINSTANCE hInst);

/*
 * Global Variables
 */
HANDLE hAppInstance;
HWND ghwndMain = NULL;
HWND ghwndEdit = NULL;
char    szOOMExitTitle[32+1];
char    szOOMExitMsg[64+1];
char    szAppTitleBuffer[32];
LPSTR   lpszAppTitle = szAppTitleBuffer;
char    szWindowsDirectory[MAXITEMPATHLEN+1];
char    szOriginalDirectory[MAXITEMPATHLEN+1];
BOOL    gfSharedWOW = FALSE;
WORD    gwFirstCmdShow;



/*
 * Forward declarations.
 */
BOOL InitializeApp(LPSTR lpszCommandLine);
LONG FAR PASCAL WndProc(HWND hwnd, WORD message, WORD wParam, LONG lParam);
WORD NEAR PASCAL ExecProgram(PWOWINFO pWowInfo);
BOOL NEAR PASCAL ExecApplication(PWOWINFO pWowInfo);
void NEAR PASCAL MyMessageBox(WORD idTitle, WORD idMessage, LPSTR psz);
PSTR FAR PASCAL GetFilenameFromPath( PSTR szPath );
void NEAR PASCAL GetPathInfo ( PSTR szPath, PSTR *pszFileName, PSTR *pszExt, WORD *pich, BOOL *pfUnc);
BOOL NEAR PASCAL StartRequestedApp(VOID);
#ifdef DEBUG
BOOL FAR PASCAL PartyDialogProc(HWND hDlg, WORD msg, WORD wParam, LONG lParam);
#endif

#ifndef DBCS
#define AnsiNext(x) ((x)+1)
#define AnsiPrev(y,x) ((x)-1)
#define IsDBCSLeadByte(x) (FALSE)
#endif

typedef struct PARAMETERBLOCK {
    WORD    wEnvSeg;
    LPSTR   lpCmdLine;
    LPVOID  lpCmdShow;
    DWORD   dwReserved;
} PARAMETERBLOCK, *PPARAMETERBLOCK;

typedef struct CMDSHOW {
    WORD    two;
    WORD    nCmdShow;
} CMDSHOW, *PCMDSHOW;

#define CCHMAX 256+13  // MAX_PATH plus 8.3 plus NULL

#define ERROR_ERROR         0
#define ERROR_FILENOTFOUND  2
#define ERROR_PATHNOTFOUND  3
#define ERROR_MANYOPEN      4
#define ERROR_DYNLINKSHARE  5
#define ERROR_LIBTASKDATA   6
#define ERROR_MEMORY        8
#define ERROR_VERSION       10
#define ERROR_BADEXE        11
#define ERROR_OTHEREXE      12
#define ERROR_DOS4EXE       13
#define ERROR_UNKNOWNEXE    14
#define ERROR_RMEXE         15
#define ERROR_MULTDATAINST  16
#define ERROR_PMODEONLY     18
#define ERROR_COMPRESSED    19
#define ERROR_DYNLINKBAD    20
#define ERROR_WIN32         21


/* FindPrevInstanceProc -
 * A silly little enumproc to find any window (EnumWindows) which has a
 * matching EXE file path.  The desired match EXE pathname is pointed to
 * by the lParam.  The found window's handle is stored in the
 * first word of this buffer.
 */

BOOL CALLBACK FindPrevInstanceProc(HWND hWnd, LPSTR lpszParam)
{
    char szT[260];
    HANDLE hInstance;

    // Filter out invisible and disabled windows
    //

    if (!IsWindowEnabled(hWnd) || !IsWindowVisible(hWnd))
        return TRUE;

    hInstance = GetWindowWord(hWnd, GWW_HINSTANCE);
    GetModuleFileName(hInstance, szT, sizeof (szT)-1);

    // Make sure that the hWnd belongs to the current VDM process
    //
    // GetWindowTask returns the wowexec htask16 if the window belongs
    // to a different process - thus we filter out windows in
    // 'separate VDM' processes.
    //                                                     - nanduri

    if (lstrcmpi(szT, lpszParam) == 0 &&
        GetWindowTask(hWnd) != GetWindowTask(ghwndMain)) {
        *(LPHANDLE)lpszParam = hWnd;
        return FALSE;
    }
    else {
        return TRUE;
    }
}

HWND near pascal FindPopupFromExe(LPSTR lpExe)
{
    HWND hwnd = (HWND)0;
    BOOL b;

    b = EnumWindows(FindPrevInstanceProc, (LONG)(LPSTR)lpExe);
    if (!b && (hwnd = *(LPHANDLE)(LPSTR)lpExe))  {
        // Find a "main" window that is the ancestor of a given window
        //

        HWND hwndT;

        // First go up the parent chain to find the popup window.  Then go
        // up the owner chain to find the main window
        //

        while (hwndT = GetParent(hwnd))
             hwnd = hwndT;

        while (hwndT = GetWindow(hwnd, GW_OWNER))
             hwnd = hwndT;
    }

    return hwnd;
}

WORD ActivatePrevInstance(LPSTR lpszPath)
{
    HWND hwnd;
    HINSTANCE ret = IDS_MULTIPLEDSMSG;

    if (hwnd = FindPopupFromExe(lpszPath)) {
        if (IsIconic(hwnd)) {
            ShowWindow(hwnd,SW_SHOWNORMAL);
        }
        else {
            HWND hwndT = GetLastActivePopup(hwnd);
            BringWindowToTop(hwnd);
            if (hwndT && hwnd != hwndT)
                BringWindowToTop(hwndT);
        }
        ret = 0;
    }

    return (ret);
}

/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  ExecProgram() -                                                         */
/*                                                                          */
/* Taken from Win 3.1 Progman -maf                                          */
/*--------------------------------------------------------------------------*/

/* Returns 0 for success.  Otherwise returns a IDS_ string code. */

WORD NEAR PASCAL ExecProgram(PWOWINFO pWowInfo)
{
  WORD    ret;
  PARAMETERBLOCK ParmBlock;
  CMDSHOW CmdShow;
  char  CmdLine[CCHMAX];

  ret = 0;

  // Don't mess with the mouse state; unless we're on a mouseless system.
  if (!GetSystemMetrics(SM_MOUSEPRESENT))
      ShowCursor(TRUE);

  //
  // prepare the dos style cmd line (counted pascal string)
  // pWowInfo->lpCmdLine contains the command tail (excluding argv[0])
  //
  CmdLine[0] = lstrlen(pWowInfo->lpCmdLine) - 2;
  lstrcpy( &CmdLine[1], pWowInfo->lpCmdLine);

  // We have a WOWINFO structure, then use it to pass the correct environment

  ParmBlock.wEnvSeg = HIWORD(pWowInfo->lpEnv);
  ParmBlock.lpCmdLine = CmdLine;
  ParmBlock.lpCmdShow = &CmdShow;
  CmdShow.two = 2;
  CmdShow.nCmdShow = pWowInfo->wShowWindow;

  ParmBlock.dwReserved = NULL;

  ret = LoadModule(pWowInfo->lpAppName,(LPVOID)&ParmBlock) ;

  switch (ret)
    {
      case ERROR_ERROR:
      case ERROR_MEMORY:
          ret = IDS_NOMEMORYMSG;
          break;

      case ERROR_FILENOTFOUND:
          ret = IDS_FILENOTFOUNDMSG;
          break;

      case ERROR_PATHNOTFOUND:
          ret = IDS_BADPATHMSG;
          break;

      case ERROR_MANYOPEN:
          ret = IDS_MANYOPENFILESMSG;
          break;

      case ERROR_DYNLINKSHARE:
          ret = IDS_ACCESSDENIED;
          break;

      case ERROR_VERSION:
          ret = IDS_NEWWINDOWSMSG;
          break;

      case ERROR_RMEXE:
          /* KERNEL has already put up a messagebox for this one. */
          ret = 0;
          break;

      case ERROR_MULTDATAINST:
          ret = ActivatePrevInstance(pWowInfo->lpAppName);
          break;

      case ERROR_COMPRESSED:
          ret = IDS_COMPRESSEDEXE;
          break;

      case ERROR_DYNLINKBAD:
          ret = IDS_INVALIDDLL;
          break;

      case SE_ERR_SHARE:
          ret = IDS_SHAREERROR;
          break;

      case ERROR_WIN32:
          ret = IDS_CANTLOADWIN32DLL;
          break;

      //
      // We shouldn't get any of the following errors,
      // so the strings have been removed from the resource
      // file.  That's why there's the OutputDebugString
      // on checked builds only.
      //

#ifdef DEBUG
      case ERROR_OTHEREXE:
      case ERROR_PMODEONLY:
      case SE_ERR_ASSOCINCOMPLETE:
      case SE_ERR_DDETIMEOUT:
      case SE_ERR_DDEFAIL:
      case SE_ERR_DDEBUSY:
      case SE_ERR_NOASSOC:
          {
              char szTmp[64];
              wsprintf(szTmp, "WOWEXEC: Unexpected error %d executing app, fix that code!\n", (int)ret);
              OutputDebugString(szTmp);
          }
          //
          // fall through to default case, so the execution
          // is the same as on the free build.
          //
#endif

      default:
          if (ret < 32)
              goto EPExit;
          ret = 0;
    }

EPExit:

  if (!GetSystemMetrics(SM_MOUSEPRESENT)) {
      /*
       * We want to turn the mouse off here on mouseless systems, but
       * the mouse will already have been turned off by USER if the
       * app has GP'd so make sure everything's kosher.
       */
      if (ShowCursor(FALSE) != -1)
          ShowCursor(TRUE);
  }

  return(ret);
}

/***************************************************************************\
* ExecApplication
*
* Code Taken From Win 3.1 ExecItem()
*
\***************************************************************************/

#define TDB_PDB_OFFSET  0x60
#define PDB_ENV_OFFSET  0x2C

BOOL NEAR PASCAL ExecApplication(PWOWINFO pWowInfo)
{

    WORD    ret;
    LPSTR   szEnv;
    LPSTR   szEnd;
    BYTE    bDrive;
    WORD    wSegEnvSave;
    HANDLE  hTask;
    LPSTR   lpTask;
    HANDLE  hPDB;
    LPSTR   lpPDB;
    HANDLE  hNewEnv;

    int     nLength;
    int     nNewEnvLength;
    LPSTR   lpstrEnv;
    LPSTR   lpstr;
    LPSTR   lpOriginalEnv;
    BOOL    bBlanks;
    LPSTR   szEnvTmp;


    if (!pWowInfo) {
        return FALSE;
        }

    //
    // Seup the environment from WOWINFO record from getvdmcommand
    //


    // Figure out who we are (so we can edit our PDB/PSP)

    hTask = GetCurrentTask();
    lpTask = GlobalLock( hTask );
    if ( lpTask == NULL ) {
        ret = IDS_NOMEMORYMSG;
        goto punt;
    }

    hPDB = *((LPWORD)(lpTask + TDB_PDB_OFFSET));
    lpPDB = GlobalLock( hPDB );

    // Save our environment block
    wSegEnvSave = *((LPWORD)(lpPDB + PDB_ENV_OFFSET));


    // Now determine the length of the original env

    lpOriginalEnv = (LPSTR)MAKELONG(0,wSegEnvSave);

    do {
        nLength = lstrlen(lpOriginalEnv);
        lpOriginalEnv += nLength + 1;
    } while ( nLength != 0 );

    lpOriginalEnv += 2;         // Skip over magic word, see comment below

    nNewEnvLength = 4 + lstrlen(lpOriginalEnv); // See magic comments below!

    // WOW Apps cannot deal with an invalid TEMP=c:\bugusdir directory
    // Usually on Win 3.1 the TEMP= is checked in ldboot.asm check_temp
    // routine.   However on NT since we get a new environment with each
    // WOW app it means that we have to check it here.   If it is not
    // valid then it is edited in the environment.
    //      - mattfe june 11 93

    szEnv = pWowInfo->lpEnv;
    szEnd = szEnv + pWowInfo->EnvSize;
    szEnd--;

    while ( szEnv < szEnd ) {

       nLength = lstrlen(szEnv) + 1;

       if (  (*szEnv == 'T') &&
         (*(szEnv+1) == 'E') &&
         (*(szEnv+2) == 'M') &&
         (*(szEnv+3) == 'P') &&
         (*(szEnv+4) == '=') )  {

            // Try to set the current directory to the TEMP= dir
            // If it fails then nuke the TEMP= part of the environment
            // in the same way check_TEMP does in ldboot.asm
            // We also scan for blanks, just like check_TEMP

            bBlanks = FALSE;
            szEnvTmp = szEnv+5;
            while (*szEnvTmp != 0) {
                if (*szEnvTmp == ' ') {
                    bBlanks = TRUE;
                    break;
                }
                szEnvTmp++;
            }

            if (bBlanks || (SetCurrentDirectory(szEnv+5) )) {
                while (*szEnv != 0) {
                    *szEnv = 'x';
                    szEnv++;
                }
            }
       break;
       }
       szEnv += nLength;
    }

    // WOW Apps only have a Single Current Directory
    // Find =d:=D:\path where d is the active drive letter
    // Note that the drive info doesn have to be at the start
    // of the environment.

    bDrive = pWowInfo->CurDrive + 'A';
    szEnv = pWowInfo->lpEnv;
    szEnd = szEnv + pWowInfo->EnvSize;
    szEnd--;

    while ( szEnv < szEnd ) {

       nLength = lstrlen(szEnv) + 1;
       if ( *szEnv == '=' ) {
            if ( (bDrive == (*(szEnv+1) & 0xdf)) &&
                 (*(szEnv+2) == ':') && (*(szEnv+3) == '=') ) {
                SetCurrentDirectory(szEnv+4);
            }
       } else {
            nNewEnvLength += nLength;
       }
       szEnv += nLength;
    }

    // Now allocate and make a personal copy of the Env

    hNewEnv = GlobalAlloc( GMEM_MOVEABLE, (DWORD)nNewEnvLength );
    if ( hNewEnv == NULL ) {
        ret = IDS_NOMEMORYMSG;
        goto punt;
    }
    lpstrEnv = GlobalLock( hNewEnv );
    if ( lpstrEnv == NULL ) {
        GlobalFree( hNewEnv );
        ret = IDS_NOMEMORYMSG;
        goto punt;
    }

    // Copy only the non-current directory env variables

    szEnv = pWowInfo->lpEnv;
    lpstr = lpstrEnv;

    while ( szEnv < szEnd ) {
        nLength = lstrlen(szEnv) + 1;

        // Copy everything except the drive letters

        if ( *szEnv != '=' ) {
            lstrcpy( lpstr, szEnv );
            lpstr += nLength;
        }
        szEnv += nLength;
    }
    *lpstr++ = '\0';          // Extra '\0' on the end

    // Magic environment goop!
    //
    // Windows only supports the passing of environment information
    // using the LoadModule API.  The WinExec API just causes
    // the application to inherit the current DOS PDB's environment.
    //
    // Also, the environment block has a little gotcha at the end.  Just
    // after the double-nuls there is a magic WORD value 0x0001 (DOS 3.0
    // and later).  After the value is a nul terminated string indicating
    // the applications executable file name (including PATH).
    //
    // We copy the value from WOWEXEC's original environment because
    // that is what WinExec appears to do.
    //
    // -BobDay

    *lpstr++ = '\1';
    *lpstr++ = '\0';        // Magic 0x0001 from DOS

    lstrcpy( lpstr, lpOriginalEnv );    // More Magic (see comment above)

    // Temporarily update our environment block

    *((LPWORD)(lpPDB + PDB_ENV_OFFSET)) = (WORD)hNewEnv | 1;

    pWowInfo->lpEnv = lpstrEnv;


    //
    // Set our current drive & directory as requested.
    //

    SetCurrentDirectory(pWowInfo->lpCurDir);

    ret = ExecProgram(pWowInfo);

    // Restore our environment block

    *((LPWORD)(lpPDB + PDB_ENV_OFFSET)) = wSegEnvSave;

    GlobalUnlock( hPDB );
    GlobalUnlock( hTask );
    GlobalUnlock( hNewEnv );
    GlobalFree( hNewEnv );


punt:

    // Change back to the Windows Directory
    // So that if we are execing from a NET Drive its
    // Not kept Active

    SetCurrentDirectory(szWindowsDirectory);

    // Check for errors.
    if (ret) {
        MyMessageBox(IDS_EXECERRTITLE, ret, pWowInfo->lpAppName);
    }

    //  Always call this when we are done try to start an app.
    //  It will do nothing if we were successful in starting an
    //  app, otherwise if we were unsucessful it will signal that
    //  the app has completed.
    WowFailedExec();

    return(ret);
}


/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  MyMessageBox() -                                                        */
/*  Taken From Win 3.1 Progman - maf                                        */
/*--------------------------------------------------------------------------*/

void NEAR PASCAL MyMessageBox(WORD idTitle, WORD idMessage, LPSTR psz)
{
  char szTitle[MAXTITLELEN+1];
  char szMessage[MAXMESSAGELEN+1];
  char szTempField[MAXMESSAGELEN+1];


  if (!LoadString(hAppInstance, idTitle, szTitle, sizeof(szTitle)))
      goto MessageBoxOOM;

  if (idMessage > IDS_LAST)
    {
      if (!LoadString(hAppInstance, IDS_UNKNOWNMSG, szTempField, sizeof(szTempField)))
          goto MessageBoxOOM;
      wsprintf(szMessage, szTempField, idMessage);
    }
  else
    {
      if (!LoadString(hAppInstance, idMessage, szTempField, sizeof(szTempField)))
          goto MessageBoxOOM;

      if (psz)
          wsprintf(szMessage, szTempField, (LPSTR)psz);
      else
          lstrcpy(szMessage, szTempField);
    }

  WowMsgBox(szMessage, szTitle, MB_ICONEXCLAMATION);
  return;


MessageBoxOOM:
  WowMsgBox(szOOMExitMsg, szOOMExitTitle, MB_ICONHAND);

  return ;
}



/***************************************************************************\
* main
*
*
* History:
* 04-13-91 ScottLu      Created - from 32 bit exec app
* 21-mar-92 mattfe      significant alterations for WOW
\***************************************************************************/

int PASCAL WinMain(HANDLE hInstance,
                   HANDLE hPrevInstance, LPSTR lpszCmdLine, int iCmd)

{
    int i;
    MSG msg;
    LPSTR pch,pch1;
    WORD    ret;
    WOWINFO wowinfo;
    char aszWOWDEB[CCHMAX];
    LPSTR pchWOWDEB;
    HANDLE hMMDriver;


    char        szBuffer[150];
    BOOL        bFinished;
    int         iStart;
    int         iEnd;


    hAppInstance = hInstance ;

    // Only Want One WOWExec
    if (hPrevInstance != NULL) {
        return(FALSE);
    }

    if (!InitializeApp(lpszCmdLine)) {
        OutputDebugString("WOWEXEC: InitializeApp failure!\n");
        return 0;
    }

/*
 * Look for a drivers= line in the [boot] section of SYSTEM.INI
 * If present it is the 16 bit MultiMedia interface, so load it
 */

#ifdef OLD_MMSYSTEM_LOAD
    if (GetPrivateProfileString((LPSTR)"boot", (LPSTR)"drivers",(LPSTR)"", aszMMDriver, sizeof(aszMMDriver), (LPSTR)"SYSTEM.INI")) {
/*
 * We have now discovered an app that rewrites the "drivers" entry with
 * multiple drivers - so the existing load method fails. As a temporary fix
 * while we decide what the "proper" fix is I will always load MMSYSTEM and
 * ONLY MMSYSTEM.
 *
 *       aszMMDriver[sizeof(aszMMDriver)-1] = '\0';
 *       hMMDriver = LoadLibrary((LPSTR)aszMMDriver);
 * #ifdef DEBUG
 *       if (hMMDriver < 32) {
 *           OutputDebugString("WOWEXEC: Load of MultiMedia driver failed\n");
 *       }
 * #endif
 */
        LoadLibrary("MMSYSTEM.DLL");
    }
#else
    /* Load DDL's from DRIVERS section in system.ini
     */
    GetPrivateProfileString( (LPSTR)"boot",      /* [Boot] section */
                            (LPSTR)"drivers",   /* Drivers= */
                            (LPSTR)"",          /* Default if no match */
                            szBuffer,    /* Return buffer */
                            sizeof(szBuffer),
                            (LPSTR)"system.ini" );

    if (!*szBuffer) {
        goto Done;
    }

    bFinished = FALSE;
    iStart    = 0;

    while (!bFinished) {
        iEnd = iStart;

        while (szBuffer[iEnd] && (szBuffer[iEnd] != ' ') &&
               (szBuffer[iEnd] != ',')) {
            iEnd++;
        }

        if (szBuffer[iEnd] == NULL) {
            bFinished = TRUE;
        }
        else {
            szBuffer[iEnd] = NULL;
        }

        /* Load and enable the driver.
         */
        OpenDriver( &(szBuffer[iStart]), NULL, NULL );
        iStart = iEnd + 1;
    }

Done:

#endif

/*
 * Look for a debug= line in the [boot] section of SYSTEM.INI
 * If present it is the 16 bit MultiMedia interface, so load it
 */

    if ( WOWQueryDebug() & 0x0001 ) {
        pchWOWDEB = "WOWDEB.EXE";
    } else {
        pchWOWDEB = "";
    }

    GetPrivateProfileString((LPSTR)"boot", (LPSTR)"debug",pchWOWDEB, aszWOWDEB, sizeof(aszWOWDEB), (LPSTR)"SYSTEM.INI");
    aszWOWDEB[sizeof(aszWOWDEB)-1] = '\0';

    if ( lstrlen(pchWOWDEB) != 0 ) {
        WinExec((LPSTR)aszWOWDEB,SW_SHOW);
    }

#if 0
/*  Preload winspool.exe.   Apps will keep loading and freeing it
 *  which is slow.   We might as well load it now so the reference
 *  count is 1 so it will never be loaded or freed
 */
    //
    // Disabled load of winspool.exe to save 8k.  Size vs. speed,
    // which one do we care about?  Right now, size!
    //
    LoadLibrary("WINSPOOL.EXE");
#endif

    // Always load SHELL.DLL, FileMaker Pro and Lotus Install require it.

    LoadLibrary("SHELL.DLL");

    //
    // Start any apps pending in basesrv queue
    //

    while (StartRequestedApp() && gfSharedWOW) {
        /* null stmt */ ;
    }


    while (1)  {
        if (!WowWaitForMsgAndEvent(ghwndMain) &&
            PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) &&
            msg.message != WM_WOWEXECHEARTBEAT )
           {
            if (msg.message != WM_QUIT) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }

    return 1;
}


/***************************************************************************\
* InitializeApp
*
* History:
* 04-13-91 ScottLu      Created.
\***************************************************************************/

BOOL InitializeApp(LPSTR lpszCommandLine)
{
    WNDCLASS wc;
    int cyExecStart, cxExecStart;
    USHORT TitleLen, cbCopy;
    HWND  hwndFax;


    // Remove Real Mode Segment Address

    wc.style            = 0;
    wc.lpfnWndProc      = WndProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 0;
    wc.hInstance        = hAppInstance;
    wc.hIcon            = LoadIcon(hAppInstance, MAKEINTRESOURCE(ID_WOWEXEC_ICON));
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = GetStockObject(WHITE_BRUSH);
    wc.lpszClassName    = "WOWExecClass";
#ifdef DEBUG
    wc.lpszMenuName     = "MainMenu";
#else
    wc.lpszMenuName     = NULL;
#endif

    if (!RegisterClass(&wc)) {
        OutputDebugString("WOWEXEC: RegisterClass failed\n");
        return FALSE;
    }

    /*
     * Guess size for now.
     */
    cyExecStart = GetSystemMetrics(SM_CYMENU) * 6;
    cxExecStart = GetSystemMetrics(SM_CXSCREEN) / 2;

    /* Load these strings now.  If we need them later, we won't be able to load
     * them at that time.
     */
    LoadString(hAppInstance, IDS_OOMEXITTITLE, szOOMExitTitle, sizeof(szOOMExitTitle));
    LoadString(hAppInstance, IDS_OOMEXITMSG, szOOMExitMsg, sizeof(szOOMExitMsg));
    LoadString(hAppInstance, IDS_APPTITLE, szAppTitleBuffer, sizeof(szAppTitleBuffer));

    ghwndMain = CreateWindow("WOWExecClass", lpszAppTitle,
            WS_OVERLAPPED | WS_CAPTION | WS_BORDER | WS_THICKFRAME |
            WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN |
            WS_SYSMENU,
            30, 30, cxExecStart, cyExecStart,
            NULL, NULL, hAppInstance, NULL);

    if (ghwndMain == NULL ) {
#ifdef DEBUG
        OutputDebugString("WOWEXEC: ghwndMain Null\n");
#endif
        return FALSE;
    }

    hwndFax = FaxInit(hAppInstance);

    //
    // Give our window handle to BaseSrv, which will post WM_WOWEXECSTARTAPP
    // messages when we have commands to pick up.  The return value tells
    // us if we are the shared WOW VDM or not (a seperate WOW VDM).
    // We also pick up the ShowWindow parameter (SW_SHOW, SW_MINIMIZED, etc)
    // for the first WOW app here.  Subsequent ones we get from BaseSrv.
    //

         //
         // gwFirstCmdShow is no longer used, and is available.
         //

    gfSharedWOW = WOWRegisterShellWindowHandle(ghwndMain,
                                               &gwFirstCmdShow,
                                               hwndFax
                                               );



    //
    // If this isn't the shared WOW, tell the kernel to exit when the
    // last app (except WowExec) exits.
    //

    if (!gfSharedWOW) {
        WowSetExitOnLastApp(TRUE);
    }

      /* Remember the original directory. */
    GetCurrentDirectory(NULL, szOriginalDirectory);
    GetWindowsDirectory(szWindowsDirectory, MAXITEMPATHLEN+1);

#ifdef DEBUG

    ShowWindow(ghwndMain, SW_MINIMIZE);

    //
    // If this is the shared WOW, change the app title string to
    // reflect this and change the window title.
    //

    if (gfSharedWOW) {

        LoadString(hAppInstance, IDS_SHAREDAPPTITLE, szAppTitleBuffer, sizeof(szAppTitleBuffer));

    }

    SetWindowText(ghwndMain, lpszAppTitle);
    UpdateWindow(ghwndMain);

#endif

    return TRUE;
}


/***************************************************************************\
* WndProc
*
* History:
* 04-07-91 DarrinM      Created.
\***************************************************************************/

LONG FAR PASCAL WndProc(
    HWND hwnd,
    WORD message,
    WORD wParam,
    LONG lParam)
{
    char chbuf[50];
    HICON hIcon;

    switch (message) {
    case WM_CREATE:
        break;

    case WM_DESTROY:
        // ignore since wowexec must stay around
        return 0;

#ifdef DEBUG
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
            case MM_ABOUT:
                LoadString(hAppInstance, errTitle, (LPSTR)chbuf, sizeof(chbuf));
                hIcon = LoadIcon(hAppInstance, MAKEINTRESOURCE(ID_WOWEXEC_ICON));
                ShellAbout(ghwndMain, (LPSTR)chbuf, (LPSTR)lpszAppTitle, hIcon);
            break;

            case MM_BREAK:
                _asm int 3
            break;

            case MM_FAULT:
                _asm mov cs:0,ax
            break;

            case MM_EXIT:
                ExitKernelThunk(0);
            break;

            case MM_WATSON:
                WinExec("drwatson", SW_MINIMIZE );
            break;

            case MM_PARTY:
            {
                FARPROC lpPartyDialogProc;

                lpPartyDialogProc = MakeProcInstance(PartyDialogProc, hAppInstance);

                DialogBox(hAppInstance, MAKEINTRESOURCE(ID_PARTY_DIALOG),
                          hwnd, lpPartyDialogProc);

                FreeProcInstance(lpPartyDialogProc);
            }
            break;

        }
        break;
#endif

    case WM_WOWEXECSTARTAPP:      // WM_USER+0

#ifdef DEBUG
        OutputDebugString("WOWEXEC - got WM_WOWEXECSTARTAPP\n");
#endif

        //
        // Either BaseSrv or Wow32 asked us to go looking for
        // commands to run.
        //

        if (!gfSharedWOW) {

            //
            // We shouldn't get this message unless we are the shared
            // WOW VDM!
            //

#ifdef DEBUG
            OutputDebugString("WOWEXEC - separate WOW VDM got WM_WOWEXECSTARTAPP!\n");
            _asm int 3;
#endif
            break;
        }

        //
        // Start requested apps until there are no more to start.
        // This handles the case where several Win16 apps are launched
        // in a row, before BaseSrv has the window handle for WowExec.
        //

        while (StartRequestedApp()) {
            /* Null stmt */ ;
        }
        break;

#define WM_WOWEXEC_START_TASK (WM_USER+2)
    case WM_WOWEXEC_START_TASK:
        {
            char sz[512];

            sz[ GlobalGetAtomName(wParam, sz, sizeof sz) ] = 0;
            GlobalDeleteAtom(wParam);
            WinExec(sz, (WORD)lParam);
        }

    case WM_WOWEXECHEARTBEAT:
        // Probably will never get here...
        break;

    case WM_TIMECHANGE:
        *((DWORD *)(((DWORD)40 << 16) | FIXED_NTVDMSTATE_REL40))
         |= VDM_TIMECHANGE;
        break;

    case WM_DDE_INITIATE:
        {
            // In win31, the Program Manager WindowProc calls peekmessage to filterout
            // otherwindowcreated and otherwindowdestroyed messages (which are atoms in win31)
            // whenever it receives WM_DDE_INITIATE message.
            //
            // This has a side effect - basically when peekmessage is called the app ie program
            // manager effectively yields allowing scheduling of other apps.
            //
            // So we do the side effect thing (simulate win31 behaviour) -
            //
            // The bug: 20221, rumba as/400 can't connect the firsttime to sna server.
            // Scenario: Rumbawsf execs snasrv and blocks on yield, snasrv execs wnap and blocks
            //           on yield. Eventually wnap yields and rumbawsf is scheduled which
            //           broadcasts a ddeinitiate message. When WOWEXEC receives this message
            //           it will peek for nonexistent msg, which allows snasrv to get scheduled

            MSG msg;
            while (PeekMessage(&msg, NULL, 0xFFFF, 0xFFFF, PM_REMOVE))
                DispatchMessage(&msg);
        }
        break;



    case WM_CLOSE:
#ifdef DEBUG
        ExitKernelThunk(0);
#else
        // ignore since wowexec must stay around
        return 0;
#endif // ! DEBUG


    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 1;
}

BOOL FAR PASCAL PartyDialogProc(HWND hDlg, WORD msg, WORD wParam, LONG lParam)
{
#ifdef DEBUG
    BOOL f;
    DWORD dw;
    char szBuf[255];

    switch (msg) {

        case WM_INITDIALOG:
            SendDlgItemMessage(hDlg, IDD_PARTY_NUMBER, EM_LIMITTEXT, 5, 0L);
            SendDlgItemMessage(hDlg, IDD_PARTY_STRING, EM_LIMITTEXT, sizeof(szBuf)-1, 0L);
            break;

        case WM_COMMAND:
            switch (wParam) {

                case 0xdab /* IDCANCEL */:
                    EndDialog(hDlg, FALSE);
                    break;

                case 0xdad /* IDOK */:
                    dw = GetDlgItemInt(hDlg, IDD_PARTY_NUMBER, &f, FALSE);
                    GetDlgItemText(hDlg, IDD_PARTY_STRING, szBuf, sizeof(szBuf));
                    WowPartyByNumber(dw, szBuf);
                    EndDialog(hDlg, TRUE);
                    break;

                default:
                    return FALSE;
            }
            break;

        default:
            return FALSE;
    }
#endif

    return TRUE;
}

// Misc File Routines - taken from progman (pmdlg.c) mattfe apr-1 92

//-------------------------------------------------------------------------
PSTR FAR PASCAL GetFilenameFromPath
    // Given a full path returns a ptr to the filename bit. Unless it's a UNC style
    // path in which case
    (
    PSTR szPath
    )
    {
    DWORD dummy;
    PSTR pFileName;
    BOOL fUNC;


    GetPathInfo(szPath, &pFileName, (PSTR*) &dummy, (WORD*) &dummy,
        &fUNC);

    // If it's a UNC then the 'filename' part is the whole thing.
    if (fUNC)
        pFileName = szPath;

    return pFileName;
    }


//-------------------------------------------------------------------------
void NEAR PASCAL GetPathInfo
    // Get pointers and an index to specific bits of a path.
    // Stops scaning at first space.
    (
                        // Uses:
    PSTR szPath,        // The path.

                        // Returns:
    PSTR *pszFileName,  // The start of the filename in the path.
    PSTR *pszExt,       // Extension part of path (starting with the dot).
    WORD *pich,         // Index (in DBCS characters) of filename part starting at 0.
    BOOL *pfUnc         // Contents set to true if it's a UNC style path.
    )
    {
    char *pch;          // Temp variable.
    WORD ich = 0;       // Temp.

    *pszExt = NULL;             // If no extension, return NULL.
    *pszFileName = szPath;      // If no seperate filename component, return path.
    *pich = 0;
    *pfUnc = FALSE;             // Default to not UNC style.

    // Check for UNC style paths.
    if (*szPath == '\\' && *(szPath+1) == '\\')
        *pfUnc = TRUE;

    // Search forward to find the last backslash or colon in the path.
    // While we're at it, look for the last dot.
    for (pch = szPath; *pch; pch = AnsiNext(pch))
        {
        if (*pch == ' ')
            {
            // Found a space - stop here.
            break;
            }
        if (*pch == '\\' || *pch == ':')
            {
            // Found it, record ptr to it and it's index.
            *pszFileName = pch+1;
            *pich = ich+1;
            }
        if (*pch == '.')
            {
            // Found a dot.
            *pszExt = pch;
            }
        ich++;
        }

    // Check that the last dot is part of the last filename.
    if (*pszExt < *pszFileName)
        *pszExt = NULL;

    }


//-----------------------------------------------------------------------------
//  StartRequestedApp
//      Calls WIN32 Base GetNextVDMCommand
//      and then starts the application
//
//-----------------------------------------------------------------------------

#define LargeEnvSize()    0x1000           // Size of a large env

BOOL NEAR PASCAL StartRequestedApp(VOID)
{
    char achCmdLine[CCHMAX];
    char achAppName[CCHMAX];
#ifdef DEBUG
    char achAppNamePlusCmdLine[sizeof(achCmdLine) + sizeof(achAppName)];
    int iGetNextVdmCmdLoops = 0;
#endif
    char achCurDir[CCHMAX];
    WOWINFO wowinfo;
    BOOL    b;
    HANDLE  hmemEnvironment;
    USHORT usEnvSize;

    achCmdLine[0] = '\0';
    achAppName[0] = '\0';

    // We start by assuming that the app will have an environment that will
    // be less than a large environment size. If not then 
    // WowGetNextVdmCommand will fail and we will try again with the 
    // enviroment size needed for the app as returned from the failed
    // WowGetNextVdmCommand call. If we detect that WowGetNextVdmCommand fails
    // but that we have plenty of environment space then we know another
    // problem has occured and we give up.
    
    // We don't worry about wasting memory since the environment will be 
    // merged into another buffer and this buffer will be freed below.

    wowinfo.EnvSize = LargeEnvSize();
    hmemEnvironment = NULL;
    
    do {
        if (hmemEnvironment != NULL) {
            GlobalUnlock(hmemEnvironment);
	    GlobalFree(hmemEnvironment);
        }
	
        // We need to allocate twice the space specified so that international
        // character set conversions can be successful.
        hmemEnvironment = GlobalAlloc(GMEM_MOVEABLE, 2*wowinfo.EnvSize);
        if (hmemEnvironment == NULL) {
#ifdef DEBUG
            OutputDebugString("WOWEXEC - failed to allocate Environment Memory\n");
#endif
            MyMessageBox(IDS_EXECERRTITLE, IDS_NOMEMORYMSG, NULL);
            return FALSE;
        }
	
        wowinfo.lpEnv    = GlobalLock(hmemEnvironment);
#ifdef DEBUG
        if (wowinfo.lpEnv == NULL) {
            OutputDebugString("WOWEXEC ASSERT - GlobalLock failed, fix this!\n");
            _asm { int 3 };
        }

        if (2*wowinfo.EnvSize > GlobalSize(hmemEnvironment)) {
            OutputDebugString("WOWEXEC ASSERT - alloced memory too small, fix this!\n");
            _asm { int 3 };
        }
#endif
        wowinfo.lpCmdLine = achCmdLine;
        wowinfo.CmdLineSize = CCHMAX;
        wowinfo.lpAppName = achAppName;
        wowinfo.AppNameSize = CCHMAX;
        wowinfo.CurDrive = 0;
        wowinfo.lpCurDir = achCurDir;
        wowinfo.CurDirSize = sizeof(achCurDir);
        wowinfo.iTask = 0;

        usEnvSize = wowinfo.EnvSize;	

#ifdef DEBUG
        if (++iGetNextVdmCmdLoops == 4) {
            OutputDebugString("WOWEXEC ASSERT - Too many calls to GetNextVdmCommand\n");
            _asm { int 3 };
        }
#endif
    } while (! (b = WowGetNextVdmCommand(&wowinfo)) &&
           (wowinfo.EnvSize > usEnvSize));
    
    if ( ! b ) {
#ifdef DEBUG
        OutputDebugString("WOWEXEC - GetNextVdmCommand failed.\n");
#endif
        MyMessageBox(IDS_EXECERRTITLE, IDS_NOMEMORYMSG, achCmdLine);
        GlobalUnlock( hmemEnvironment );
        GlobalFree( hmemEnvironment );
        return FALSE;
    }

    //
    // If CmdLineSize == 0, no more commands (wowexec was the command)
    // see WK32WowGetNextVdm
    //
    if (! wowinfo.CmdLineSize) {
        GlobalUnlock( hmemEnvironment );
        GlobalFree( hmemEnvironment );
        return FALSE;
    }

        
#ifdef DEBUG
    lstrcpy(achAppNamePlusCmdLine, achAppName);
    lstrcat(achAppNamePlusCmdLine, ":");
    lstrcat(achAppNamePlusCmdLine, achCmdLine);
    // Chop off trailing CRLF from command tail.
    achAppNamePlusCmdLine[ lstrlen(achAppNamePlusCmdLine) - 2 ] = '\0';

    OutputDebugString("WOWEXEC: CommandLine = <");
    OutputDebugString(achAppNamePlusCmdLine);
    OutputDebugString(">\n");

    SetWindowText(ghwndMain, achAppNamePlusCmdLine);
    UpdateWindow(ghwndMain);
#endif

    ExecApplication(&wowinfo);

#ifdef DEBUG

    if ( ! gfSharedWOW ) {

        //
        // If this is a separate WOW, we have just executed the one and only
        // app we're going to spawn.  Change our window title to
        // Command Line - WOWExec so that it's easy to see which WOW this
        // window is associated with.  No need to worry about freeing
        // this memory, since when we go away the VDM goes away and
        // vice-versa.
        //

        lpszAppTitle = GlobalLock(
            GlobalAlloc(GMEM_FIXED,
                        lstrlen(achAppNamePlusCmdLine) +
                        3 +                        // for " - "
                        lstrlen(szAppTitleBuffer) +
                        1                          // for null terminator
                        ));

        lstrcpy(lpszAppTitle, achAppNamePlusCmdLine);
        lstrcat(lpszAppTitle, " - ");
        lstrcat(lpszAppTitle, szAppTitleBuffer);
    }


    SetWindowText(ghwndMain, lpszAppTitle);
    UpdateWindow(ghwndMain);
#endif

    GlobalUnlock(hmemEnvironment);
    GlobalFree(hmemEnvironment);

    return TRUE;  // We ran an app.
}


