| /* | 
 |  * DOS (MZ) loader | 
 |  * | 
 |  * Copyright 1998 Ove Kåven | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2.1 of the License, or (at your option) any later version. | 
 |  * | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 |  * Lesser General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU Lesser General Public | 
 |  * License along with this library; if not, write to the Free Software | 
 |  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
 |  * | 
 |  * Note: This code hasn't been completely cleaned up yet. | 
 |  */ | 
 |  | 
 | #include "config.h" | 
 | #include "wine/port.h" | 
 |  | 
 | #include <stdarg.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <fcntl.h> | 
 | #include <signal.h> | 
 | #ifdef HAVE_UNISTD_H | 
 | # include <unistd.h> | 
 | #endif | 
 | #include <sys/types.h> | 
 | #ifdef HAVE_SYS_STAT_H | 
 | # include <sys/stat.h> | 
 | #endif | 
 | #ifdef HAVE_SYS_TIME_H | 
 | # include <sys/time.h> | 
 | #endif | 
 | #include "windef.h" | 
 | #include "winbase.h" | 
 | #include "wine/winbase16.h" | 
 | #include "wingdi.h" | 
 | #include "winuser.h" | 
 | #include "winerror.h" | 
 | #include "wine/debug.h" | 
 | #include "kernel16_private.h" | 
 | #include "dosexe.h" | 
 | #include "vga.h" | 
 |  | 
 | WINE_DEFAULT_DEBUG_CHANNEL(module); | 
 |  | 
 | static BOOL DOSVM_isdosexe; | 
 |  | 
 | /********************************************************************** | 
 |  *          DOSVM_IsWin16 | 
 |  *  | 
 |  * Return TRUE if we are in Windows process. | 
 |  */ | 
 | BOOL DOSVM_IsWin16(void) | 
 | { | 
 |   return DOSVM_isdosexe ? FALSE : TRUE; | 
 | } | 
 |  | 
 | /********************************************************************** | 
 |  *          DOSVM_Exit | 
 |  */ | 
 | void DOSVM_Exit( WORD retval ) | 
 | { | 
 |     DWORD count; | 
 |  | 
 |     ReleaseThunkLock( &count ); | 
 |     ExitThread( retval ); | 
 | } | 
 |  | 
 |  | 
 | #ifdef MZ_SUPPORTED | 
 |  | 
 | #define BIOS_DATA_SEGMENT 0x40 | 
 | #define PSP_SIZE 0x10 | 
 |  | 
 | #define SEG16(ptr,seg) ((LPVOID)((BYTE*)ptr+((DWORD)(seg)<<4))) | 
 | #define SEGPTR16(ptr,segptr) ((LPVOID)((BYTE*)ptr+((DWORD)SELECTOROF(segptr)<<4)+OFFSETOF(segptr))) | 
 |  | 
 | /* structures for EXEC */ | 
 |  | 
 | #include "pshpack1.h" | 
 |  | 
 | typedef struct { | 
 |   WORD env_seg; | 
 |   DWORD cmdline; | 
 |   DWORD fcb1; | 
 |   DWORD fcb2; | 
 |   WORD init_sp; | 
 |   WORD init_ss; | 
 |   WORD init_ip; | 
 |   WORD init_cs; | 
 | } ExecBlock; | 
 |  | 
 | typedef struct { | 
 |   WORD load_seg; | 
 |   WORD rel_seg; | 
 | } OverlayBlock; | 
 |  | 
 | #include "poppack.h" | 
 |  | 
 | /* global variables */ | 
 |  | 
 | pid_t dosvm_pid; | 
 |  | 
 | static WORD init_cs,init_ip,init_ss,init_sp; | 
 | static HANDLE dosvm_thread, loop_thread; | 
 | static DWORD dosvm_tid, loop_tid; | 
 |  | 
 | static DWORD MZ_Launch( LPCSTR cmdtail, int length ); | 
 | static BOOL MZ_InitTask(void); | 
 |  | 
 | static void MZ_CreatePSP( LPVOID lpPSP, WORD env, WORD par ) | 
 | { | 
 |   PDB16*psp=lpPSP; | 
 |  | 
 |   psp->int20=0x20CD; /* int 20 */ | 
 |   /* some programs use this to calculate how much memory they need */ | 
 |   psp->nextParagraph=0x9FFF; /* FIXME: use a real value */ | 
 |   /* FIXME: dispatcher */ | 
 |   psp->savedint22 = DOSVM_GetRMHandler(0x22); | 
 |   psp->savedint23 = DOSVM_GetRMHandler(0x23); | 
 |   psp->savedint24 = DOSVM_GetRMHandler(0x24); | 
 |   psp->parentPSP=par; | 
 |   psp->environment=env; | 
 |   /* FIXME: more PSP stuff */ | 
 | } | 
 |  | 
 | static void MZ_FillPSP( LPVOID lpPSP, LPCSTR cmdtail, int length ) | 
 | { | 
 |     PDB16 *psp = lpPSP; | 
 |  | 
 |     if(length > 127)  | 
 |     { | 
 |         WARN( "Command tail truncated! (length %d)\n", length ); | 
 |         length = 126; | 
 |     } | 
 |  | 
 |     psp->cmdLine[0] = length; | 
 |  | 
 |     /* | 
 |      * Length of exactly 127 bytes means that full command line is  | 
 |      * stored in environment variable CMDLINE and PSP contains  | 
 |      * command tail truncated to 126 bytes. | 
 |      */ | 
 |     if(length == 127) | 
 |         length = 126; | 
 |  | 
 |     if(length > 0) | 
 |         memmove(psp->cmdLine+1, cmdtail, length); | 
 |  | 
 |     psp->cmdLine[length+1] = '\r'; | 
 |  | 
 |     /* FIXME: more PSP stuff */ | 
 | } | 
 |  | 
 | static WORD MZ_InitEnvironment( LPCSTR env, LPCSTR name ) | 
 | { | 
 |  unsigned sz=0; | 
 |  unsigned i=0; | 
 |  WORD seg; | 
 |  LPSTR envblk; | 
 |  | 
 |  if (env) { | 
 |   /* get size of environment block */ | 
 |   while (env[sz++]) sz+=strlen(env+sz)+1; | 
 |  } else sz++; | 
 |  /* allocate it */ | 
 |  envblk=DOSMEM_AllocBlock(sz+sizeof(WORD)+strlen(name)+1,&seg); | 
 |  /* fill it */ | 
 |  if (env) { | 
 |   memcpy(envblk,env,sz); | 
 |  } else envblk[0]=0; | 
 |  /* DOS environment variables are uppercase */ | 
 |  while (envblk[i]){ | 
 |   while (envblk[i] != '='){ | 
 |    if (envblk[i]>='a' && envblk[i] <= 'z'){ | 
 |     envblk[i] -= 32; | 
 |    } | 
 |    i++; | 
 |   } | 
 |   i += strlen(envblk+i) + 1; | 
 |  } | 
 |  /* DOS 3.x: the block contains 1 additional string */ | 
 |  *(WORD*)(envblk+sz)=1; | 
 |  /* being the program name itself */ | 
 |  strcpy(envblk+sz+sizeof(WORD),name); | 
 |  return seg; | 
 | } | 
 |  | 
 | static BOOL MZ_InitMemory(void) | 
 | { | 
 |     /* initialize the memory */ | 
 |     TRACE("Initializing DOS memory structures\n"); | 
 |     DOSMEM_MapDosLayout(); | 
 |     DOSDEV_InstallDOSDevices(); | 
 |     MSCDEX_InstallCDROM(); | 
 |  | 
 |     return TRUE; | 
 | } | 
 |  | 
 | static BOOL MZ_DoLoadImage( HANDLE hFile, LPCSTR filename, OverlayBlock *oblk, WORD par_env_seg ) | 
 | { | 
 |   IMAGE_DOS_HEADER mz_header; | 
 |   DWORD image_start,image_size,min_size,max_size,avail; | 
 |   BYTE*psp_start,*load_start; | 
 |   LPSTR oldenv = 0; | 
 |   int x, old_com=0, alloc; | 
 |   SEGPTR reloc; | 
 |   WORD env_seg, load_seg, rel_seg, oldpsp_seg; | 
 |   DWORD len; | 
 |  | 
 |   if (DOSVM_psp) { | 
 |     /* DOS process already running, inherit from it */ | 
 |     PDB16* par_psp; | 
 |     alloc=0; | 
 |     oldpsp_seg = DOSVM_psp; | 
 |     if( !par_env_seg) {   | 
 |         par_psp = (PDB16*)((DWORD)DOSVM_psp << 4); | 
 |         oldenv = (LPSTR)((DWORD)par_psp->environment << 4); | 
 |     } | 
 |   } else { | 
 |     /* allocate new DOS process, inheriting from Wine environment */ | 
 |     alloc=1; | 
 |     oldpsp_seg = 0; | 
 |     if( !par_env_seg) | 
 |         oldenv = GetEnvironmentStringsA(); | 
 |   } | 
 |  | 
 |  SetFilePointer(hFile,0,NULL,FILE_BEGIN); | 
 |  if (   !ReadFile(hFile,&mz_header,sizeof(mz_header),&len,NULL) | 
 |      || len != sizeof(mz_header) | 
 |      || mz_header.e_magic != IMAGE_DOS_SIGNATURE) { | 
 |   char *p = strrchr( filename, '.' ); | 
 |   if (!p || strcasecmp( p, ".com" ))  /* check for .COM extension */ | 
 |   { | 
 |       SetLastError(ERROR_BAD_FORMAT); | 
 |       goto load_error; | 
 |   } | 
 |   old_com=1; /* assume .COM file */ | 
 |   image_start=0; | 
 |   image_size=GetFileSize(hFile,NULL); | 
 |   min_size=0x10000; max_size=0x100000; | 
 |   mz_header.e_crlc=0; | 
 |   mz_header.e_ss=0; mz_header.e_sp=0xFFFE; | 
 |   mz_header.e_cs=0; mz_header.e_ip=0x100; | 
 |  } else { | 
 |   /* calculate load size */ | 
 |   image_start=mz_header.e_cparhdr<<4; | 
 |   image_size=mz_header.e_cp<<9; /* pages are 512 bytes */ | 
 |   /* From Ralf Brown Interrupt List: If the word at offset 02h is 4, it should | 
 |    * be treated as 00h, since pre-1.10 versions of the MS linker set it that | 
 |    * way. */ | 
 |   if ((mz_header.e_cblp!=0)&&(mz_header.e_cblp!=4)) image_size-=512-mz_header.e_cblp; | 
 |   image_size-=image_start; | 
 |   min_size=image_size+((DWORD)mz_header.e_minalloc<<4)+(PSP_SIZE<<4); | 
 |   max_size=image_size+((DWORD)mz_header.e_maxalloc<<4)+(PSP_SIZE<<4); | 
 |  } | 
 |  | 
 |   if (alloc) MZ_InitMemory(); | 
 |  | 
 |   if (oblk) { | 
 |     /* load overlay into preallocated memory */ | 
 |     load_seg=oblk->load_seg; | 
 |     rel_seg=oblk->rel_seg; | 
 |     load_start=(LPBYTE)((DWORD)load_seg<<4); | 
 |   } else { | 
 |     /* allocate environment block */ | 
 |     if( par_env_seg) | 
 |         env_seg = par_env_seg; | 
 |     else | 
 |         env_seg=MZ_InitEnvironment(oldenv, filename); | 
 |     if (alloc) | 
 |         FreeEnvironmentStringsA( oldenv); | 
 |  | 
 |     /* allocate memory for the executable */ | 
 |     TRACE("Allocating DOS memory (min=%d, max=%d)\n",min_size,max_size); | 
 |     avail=DOSMEM_Available(); | 
 |     if (avail<min_size) { | 
 |       ERR("insufficient DOS memory\n"); | 
 |       SetLastError(ERROR_NOT_ENOUGH_MEMORY); | 
 |       goto load_error; | 
 |     } | 
 |     if (avail>max_size) avail=max_size; | 
 |     psp_start=DOSMEM_AllocBlock(avail,&DOSVM_psp); | 
 |     if (!psp_start) { | 
 |       ERR("error allocating DOS memory\n"); | 
 |       SetLastError(ERROR_NOT_ENOUGH_MEMORY); | 
 |       goto load_error; | 
 |     } | 
 |     load_seg=DOSVM_psp+(old_com?0:PSP_SIZE); | 
 |     rel_seg=load_seg; | 
 |     load_start=psp_start+(PSP_SIZE<<4); | 
 |     MZ_CreatePSP(psp_start, env_seg, oldpsp_seg); | 
 |   } | 
 |  | 
 |  /* load executable image */ | 
 |  TRACE("loading DOS %s image, %08x bytes\n",old_com?"COM":"EXE",image_size); | 
 |  SetFilePointer(hFile,image_start,NULL,FILE_BEGIN); | 
 |  if (!ReadFile(hFile,load_start,image_size,&len,NULL) || len != image_size) { | 
 |   /* check if this is due to the workaround for the pre-1.10 MS linker and we | 
 |      really had only 4 bytes on the last page */ | 
 |   if (mz_header.e_cblp != 4 || image_size - len != 512 - 4) { | 
 |     SetLastError(ERROR_BAD_FORMAT); | 
 |     goto load_error; | 
 |   } | 
 |  } | 
 |  | 
 |  if (mz_header.e_crlc) { | 
 |   /* load relocation table */ | 
 |   TRACE("loading DOS EXE relocation table, %d entries\n",mz_header.e_crlc); | 
 |   /* FIXME: is this too slow without read buffering? */ | 
 |   SetFilePointer(hFile,mz_header.e_lfarlc,NULL,FILE_BEGIN); | 
 |   for (x=0; x<mz_header.e_crlc; x++) { | 
 |    if (!ReadFile(hFile,&reloc,sizeof(reloc),&len,NULL) || len != sizeof(reloc)) { | 
 |     SetLastError(ERROR_BAD_FORMAT); | 
 |     goto load_error; | 
 |    } | 
 |    *(WORD*)SEGPTR16(load_start,reloc)+=rel_seg; | 
 |   } | 
 |  } | 
 |  | 
 |   if (!oblk) { | 
 |     init_cs = load_seg+mz_header.e_cs; | 
 |     init_ip = mz_header.e_ip; | 
 |     init_ss = load_seg+mz_header.e_ss; | 
 |     init_sp = mz_header.e_sp; | 
 |     if (old_com){ | 
 |       /* .COM files exit with ret. Make sure they jump to psp start (=int 20) */ | 
 |       WORD* stack = PTR_REAL_TO_LIN(init_ss, init_sp); | 
 |       *stack = 0; | 
 |     } | 
 |  | 
 |     TRACE("entry point: %04x:%04x\n",init_cs,init_ip); | 
 |   } | 
 |  | 
 |   if (alloc && !MZ_InitTask()) { | 
 |     SetLastError(ERROR_GEN_FAILURE); | 
 |     return FALSE; | 
 |   } | 
 |  | 
 |   return TRUE; | 
 |  | 
 | load_error: | 
 |   DOSVM_psp = oldpsp_seg; | 
 |  | 
 |   return FALSE; | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		__wine_load_dos_exe (KERNEL.@) | 
 |  * | 
 |  * Called from WineVDM when a new real-mode DOS process is started. | 
 |  * Loads DOS program into memory and executes the program. | 
 |  */ | 
 | void __wine_load_dos_exe( LPCSTR filename, LPCSTR cmdline ) | 
 | { | 
 |     char dos_cmdtail[126]; | 
 |     int  dos_length = 0; | 
 |  | 
 |     HANDLE hFile = CreateFileA( filename, GENERIC_READ, FILE_SHARE_READ,  | 
 |                                 NULL, OPEN_EXISTING, 0, 0 ); | 
 |     if (hFile == INVALID_HANDLE_VALUE) return; | 
 |     DOSVM_isdosexe = TRUE; | 
 |     DOSMEM_InitDosMemory(); | 
 |  | 
 |     if(cmdline && *cmdline) | 
 |     { | 
 |         dos_length = strlen(cmdline); | 
 |         memmove( dos_cmdtail + 1, cmdline,  | 
 |                  (dos_length < 125) ? dos_length : 125 ); | 
 |  | 
 |         /* Non-empty command tail always starts with at least one space. */ | 
 |         dos_cmdtail[0] = ' '; | 
 |         dos_length++; | 
 |  | 
 |         /* | 
 |          * If command tail is longer than 126 characters, | 
 |          * set tail length to 127 and fill CMDLINE environment variable  | 
 |          * with full command line (this includes filename). | 
 |          */ | 
 |         if (dos_length > 126) | 
 |         { | 
 |             char *cmd = HeapAlloc( GetProcessHeap(), 0,  | 
 |                                    dos_length + strlen(filename) + 4 ); | 
 |             char *ptr = cmd; | 
 |  | 
 |             if (!cmd) | 
 |                 return; | 
 |  | 
 |             /* | 
 |              * Append filename. If path includes spaces, quote the path. | 
 |              */ | 
 |             if (strchr(filename, ' ')) | 
 |             { | 
 |                 *ptr++ = '\"'; | 
 |                 strcpy( ptr, filename ); | 
 |                 ptr += strlen(filename);                    | 
 |                 *ptr++ = '\"'; | 
 |             } | 
 |             else | 
 |             { | 
 |                 strcpy( ptr, filename ); | 
 |                 ptr += strlen(filename);   | 
 |             } | 
 |  | 
 |             /* | 
 |              * Append command tail. | 
 |              */ | 
 |             if (cmdline[0] != ' ') | 
 |                 *ptr++ = ' '; | 
 |             strcpy( ptr, cmdline ); | 
 |  | 
 |             /* | 
 |              * Set environment variable. This will be passed to | 
 |              * new DOS process. | 
 |              */ | 
 |             if (!SetEnvironmentVariableA( "CMDLINE", cmd )) | 
 |             { | 
 |                 HeapFree(GetProcessHeap(), 0, cmd ); | 
 |                 return; | 
 |             } | 
 |  | 
 |             HeapFree(GetProcessHeap(), 0, cmd ); | 
 |             dos_length = 127; | 
 |         } | 
 |     } | 
 |  | 
 |     if (MZ_DoLoadImage( hFile, filename, NULL, 0 )) | 
 |     { | 
 |         DWORD err = MZ_Launch( dos_cmdtail, dos_length ); | 
 |         /* if we get back here it failed */ | 
 |         SetLastError( err ); | 
 |     } | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_Exec | 
 |  * | 
 |  * this may only be called from existing DOS processes | 
 |  */ | 
 | BOOL MZ_Exec( CONTEXT *context, LPCSTR filename, BYTE func, LPVOID paramblk ) | 
 | { | 
 |   DWORD binType; | 
 |   STARTUPINFOA st; | 
 |   PROCESS_INFORMATION pe; | 
 |   HANDLE hFile; | 
 |  | 
 |   BOOL ret = FALSE; | 
 |  | 
 |   if(!GetBinaryTypeA(filename, &binType))   /* determine what kind of binary this is */ | 
 |   { | 
 |     return FALSE; /* binary is not an executable */ | 
 |   } | 
 |  | 
 |   /* handle non-dos executables */ | 
 |   if(binType != SCS_DOS_BINARY) | 
 |   { | 
 |     if(func == 0) /* load and execute */ | 
 |     { | 
 |       LPSTR fullCmdLine; | 
 |       WORD fullCmdLength; | 
 |       LPBYTE psp_start = (LPBYTE)((DWORD)DOSVM_psp << 4); | 
 |       PDB16 *psp = (PDB16 *)psp_start; | 
 |       ExecBlock *blk = paramblk; | 
 |       LPBYTE cmdline = PTR_REAL_TO_LIN(SELECTOROF(blk->cmdline),OFFSETOF(blk->cmdline)); | 
 |       LPBYTE envblock = PTR_REAL_TO_LIN(psp->environment, 0); | 
 |       int    cmdLength = cmdline[0]; | 
 |  | 
 |       /* | 
 |        * If cmdLength is 127, command tail is truncated and environment  | 
 |        * variable CMDLINE should contain full command line  | 
 |        * (this includes filename). | 
 |        */ | 
 |       if (cmdLength == 127) | 
 |       { | 
 |           FIXME( "CMDLINE argument passing is unimplemented.\n" ); | 
 |           cmdLength = 126; /* FIXME */ | 
 |       } | 
 |  | 
 |       fullCmdLength = (strlen(filename) + 1) + cmdLength + 1; /* filename + space + cmdline + terminating null character */ | 
 |  | 
 |       fullCmdLine = HeapAlloc(GetProcessHeap(), 0, fullCmdLength); | 
 |       if(!fullCmdLine) return FALSE; /* return false on memory alloc failure */ | 
 |  | 
 |       /* build the full command line from the executable file and the command line being passed in */ | 
 |       snprintf(fullCmdLine, fullCmdLength, "%s ", filename); /* start off with the executable filename and a space */ | 
 |       memcpy(fullCmdLine + strlen(fullCmdLine), cmdline + 1, cmdLength); /* append cmdline onto the end */ | 
 |       fullCmdLine[fullCmdLength - 1] = 0; /* null terminate string */ | 
 |  | 
 |       ZeroMemory (&st, sizeof(STARTUPINFOA)); | 
 |       st.cb = sizeof(STARTUPINFOA); | 
 |       ret = CreateProcessA (NULL, fullCmdLine, NULL, NULL, TRUE, 0, envblock, NULL, &st, &pe); | 
 |  | 
 |       /* wait for the app to finish and clean up PROCESS_INFORMATION handles */ | 
 |       if(ret) | 
 |       { | 
 |         WaitForSingleObject(pe.hProcess, INFINITE);  /* wait here until the child process is complete */ | 
 |         CloseHandle(pe.hProcess); | 
 |         CloseHandle(pe.hThread); | 
 |       } | 
 |  | 
 |       HeapFree(GetProcessHeap(), 0, fullCmdLine);  /* free the memory we allocated */ | 
 |     } | 
 |     else | 
 |     { | 
 |       FIXME("EXEC type of %d not implemented for non-dos executables\n", func); | 
 |       ret = FALSE; | 
 |     } | 
 |  | 
 |     return ret; | 
 |   } /* if(binType != SCS_DOS_BINARY) */ | 
 |  | 
 |  | 
 |   /* handle dos executables */ | 
 |  | 
 |   hFile = CreateFileA( filename, GENERIC_READ, FILE_SHARE_READ, | 
 | 			     NULL, OPEN_EXISTING, 0, 0); | 
 |   if (hFile == INVALID_HANDLE_VALUE) return FALSE; | 
 |  | 
 |   switch (func) { | 
 |   case 0: /* load and execute */ | 
 |   case 1: /* load but don't execute */ | 
 |     { | 
 |       /* save current process's return SS:SP now */ | 
 |       LPBYTE psp_start = (LPBYTE)((DWORD)DOSVM_psp << 4); | 
 |       PDB16 *psp = (PDB16 *)psp_start; | 
 |       psp->saveStack = (DWORD)MAKESEGPTR(context->SegSs, LOWORD(context->Esp)); | 
 |     } | 
 |     ret = MZ_DoLoadImage( hFile, filename, NULL, ((ExecBlock *)paramblk)->env_seg ); | 
 |     if (ret) { | 
 |       /* MZ_LoadImage created a new PSP and loaded new values into it, | 
 |        * let's work on the new values now */ | 
 |       LPBYTE psp_start = (LPBYTE)((DWORD)DOSVM_psp << 4); | 
 |       ExecBlock *blk = paramblk; | 
 |       LPBYTE cmdline = PTR_REAL_TO_LIN(SELECTOROF(blk->cmdline),OFFSETOF(blk->cmdline)); | 
 |  | 
 |       /* First character contains the length of the command line. */ | 
 |       MZ_FillPSP(psp_start, (LPSTR)cmdline + 1, cmdline[0]); | 
 |  | 
 |       /* the lame MS-DOS engineers decided that the return address should be in int22 */ | 
 |       DOSVM_SetRMHandler(0x22, (FARPROC16)MAKESEGPTR(context->SegCs, LOWORD(context->Eip))); | 
 |       if (func) { | 
 | 	/* don't execute, just return startup state */ | 
 |         /* | 
 |          * From Ralph Brown: | 
 |          *  For function 01h, the AX value to be passed to the child program  | 
 |          *  is put on top of the child's stack | 
 |          */ | 
 |         LPBYTE stack; | 
 |         init_sp -= 2; | 
 |         stack = CTX_SEG_OFF_TO_LIN(context, init_ss, init_sp); | 
 |         /* FIXME: push AX correctly */ | 
 |         stack[0] = 0x00;    /* push AL */ | 
 |         stack[1] = 0x00;    /* push AH */ | 
 | 	 | 
 | 	blk->init_cs = init_cs; | 
 | 	blk->init_ip = init_ip; | 
 | 	blk->init_ss = init_ss; | 
 | 	blk->init_sp = init_sp; | 
 |       } else { | 
 | 	/* execute by making us return to new process */ | 
 | 	context->SegCs = init_cs; | 
 | 	context->Eip   = init_ip; | 
 | 	context->SegSs = init_ss; | 
 | 	context->Esp   = init_sp; | 
 | 	context->SegDs = DOSVM_psp; | 
 | 	context->SegEs = DOSVM_psp; | 
 | 	context->Eax   = 0; | 
 |       } | 
 |     } | 
 |     break; | 
 |   case 3: /* load overlay */ | 
 |     { | 
 |       OverlayBlock *blk = paramblk; | 
 |       ret = MZ_DoLoadImage( hFile, filename, blk, 0); | 
 |     } | 
 |     break; | 
 |   default: | 
 |     FIXME("EXEC load type %d not implemented\n", func); | 
 |     SetLastError(ERROR_INVALID_FUNCTION); | 
 |     break; | 
 |   } | 
 |   CloseHandle(hFile); | 
 |   return ret; | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_AllocDPMITask | 
 |  */ | 
 | void MZ_AllocDPMITask( void ) | 
 | { | 
 |   MZ_InitMemory(); | 
 |   MZ_InitTask(); | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_RunInThread | 
 |  */ | 
 | void MZ_RunInThread( PAPCFUNC proc, ULONG_PTR arg ) | 
 | { | 
 |   if (loop_thread) { | 
 |     DOS_SPC spc; | 
 |     HANDLE event; | 
 |  | 
 |     spc.proc = proc; | 
 |     spc.arg = arg; | 
 |     event = CreateEventW(NULL, TRUE, FALSE, NULL); | 
 |     PostThreadMessageA(loop_tid, WM_USER, (WPARAM)event, (LPARAM)&spc); | 
 |     WaitForSingleObject(event, INFINITE); | 
 |     CloseHandle(event); | 
 |   } else | 
 |     proc(arg); | 
 | } | 
 |  | 
 | static DWORD WINAPI MZ_DOSVM( LPVOID lpExtra ) | 
 | { | 
 |   CONTEXT context; | 
 |   INT ret; | 
 |  | 
 |   dosvm_pid = getpid(); | 
 |  | 
 |   memset( &context, 0, sizeof(context) ); | 
 |   context.SegCs  = init_cs; | 
 |   context.Eip    = init_ip; | 
 |   context.SegSs  = init_ss; | 
 |   context.Esp    = init_sp; | 
 |   context.SegDs  = DOSVM_psp; | 
 |   context.SegEs  = DOSVM_psp; | 
 |   context.EFlags = V86_FLAG | VIF_MASK; | 
 |   DOSVM_SetTimer(0x10000); | 
 |   ret = DOSVM_Enter( &context ); | 
 |   if (ret == -1) ret = GetLastError(); | 
 |   dosvm_pid = 0; | 
 |   return ret; | 
 | } | 
 |  | 
 | static BOOL MZ_InitTask(void) | 
 | { | 
 |   if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), | 
 |                        GetCurrentProcess(), &loop_thread, | 
 |                        0, FALSE, DUPLICATE_SAME_ACCESS)) | 
 |     return FALSE; | 
 |   dosvm_thread = CreateThread(NULL, 0, MZ_DOSVM, NULL, CREATE_SUSPENDED, &dosvm_tid); | 
 |   if (!dosvm_thread) { | 
 |     CloseHandle(loop_thread); | 
 |     loop_thread = 0; | 
 |     return FALSE; | 
 |   } | 
 |   loop_tid = GetCurrentThreadId(); | 
 |   return TRUE; | 
 | } | 
 |  | 
 | static DWORD MZ_Launch( LPCSTR cmdtail, int length ) | 
 | { | 
 |   TDB *pTask = GlobalLock16( GetCurrentTask() ); | 
 |   BYTE *psp_start = PTR_REAL_TO_LIN( DOSVM_psp, 0 ); | 
 |   DWORD rv; | 
 |   SYSLEVEL *lock; | 
 |   MSG msg; | 
 |  | 
 |   MZ_FillPSP(psp_start, cmdtail, length); | 
 |   pTask->flags |= TDBF_WINOLDAP; | 
 |  | 
 |   /* DTA is set to PSP:0080h when a program is started. */ | 
 |   pTask->dta = MAKESEGPTR( DOSVM_psp, 0x80 ); | 
 |  | 
 |   GetpWin16Lock( &lock ); | 
 |   _LeaveSysLevel( lock ); | 
 |  | 
 |   /* force the message queue to be created */ | 
 |   PeekMessageW(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); | 
 |  | 
 |   ResumeThread(dosvm_thread); | 
 |   rv = DOSVM_Loop(dosvm_thread); | 
 |  | 
 |   CloseHandle(dosvm_thread); | 
 |   dosvm_thread = 0; dosvm_tid = 0; | 
 |   CloseHandle(loop_thread); | 
 |   loop_thread = 0; loop_tid = 0; | 
 |   if (rv) return rv; | 
 |  | 
 |   VGA_Clean(); | 
 |   ExitProcess(0); | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_Exit | 
 |  */ | 
 | void MZ_Exit( CONTEXT *context, BOOL cs_psp, WORD retval ) | 
 | { | 
 |   if (DOSVM_psp) { | 
 |     WORD psp_seg = cs_psp ? context->SegCs : DOSVM_psp; | 
 |     LPBYTE psp_start = (LPBYTE)((DWORD)psp_seg << 4); | 
 |     PDB16 *psp = (PDB16 *)psp_start; | 
 |     WORD parpsp = psp->parentPSP; /* check for parent DOS process */ | 
 |     if (parpsp) { | 
 |       /* retrieve parent's return address */ | 
 |       FARPROC16 retaddr = DOSVM_GetRMHandler(0x22); | 
 |       /* restore interrupts */ | 
 |       DOSVM_SetRMHandler(0x22, psp->savedint22); | 
 |       DOSVM_SetRMHandler(0x23, psp->savedint23); | 
 |       DOSVM_SetRMHandler(0x24, psp->savedint24); | 
 |       /* FIXME: deallocate file handles etc */ | 
 |       /* free process's associated memory | 
 |        * FIXME: walk memory and deallocate all blocks owned by process */ | 
 |       DOSMEM_FreeBlock( PTR_REAL_TO_LIN(psp->environment,0) ); | 
 |       DOSMEM_FreeBlock( PTR_REAL_TO_LIN(DOSVM_psp,0) ); | 
 |       /* switch to parent's PSP */ | 
 |       DOSVM_psp = parpsp; | 
 |       psp_start = (LPBYTE)((DWORD)parpsp << 4); | 
 |       psp = (PDB16 *)psp_start; | 
 |       /* now return to parent */ | 
 |       DOSVM_retval = retval; | 
 |       context->SegCs = SELECTOROF(retaddr); | 
 |       context->Eip   = OFFSETOF(retaddr); | 
 |       context->SegSs = SELECTOROF(psp->saveStack); | 
 |       context->Esp   = OFFSETOF(psp->saveStack); | 
 |       return; | 
 |     } else | 
 |       TRACE("killing DOS task\n"); | 
 |   } | 
 |   DOSVM_Exit( retval ); | 
 | } | 
 |  | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_Current | 
 |  */ | 
 | BOOL MZ_Current( void ) | 
 | { | 
 |   return (dosvm_pid != 0); /* FIXME: do a better check */ | 
 | } | 
 |  | 
 | #else /* !MZ_SUPPORTED */ | 
 |  | 
 | /*********************************************************************** | 
 |  *		__wine_load_dos_exe (KERNEL.@) | 
 |  */ | 
 | void __wine_load_dos_exe( LPCSTR filename, LPCSTR cmdline ) | 
 | { | 
 |     SetLastError( ERROR_NOT_SUPPORTED ); | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_Exec | 
 |  */ | 
 | BOOL MZ_Exec( CONTEXT *context, LPCSTR filename, BYTE func, LPVOID paramblk ) | 
 | { | 
 |   /* can't happen */ | 
 |   SetLastError(ERROR_BAD_FORMAT); | 
 |   return FALSE; | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_AllocDPMITask | 
 |  */ | 
 | void MZ_AllocDPMITask( void ) | 
 | { | 
 |     FIXME("Actual real-mode calls not supported on this platform!\n"); | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_RunInThread | 
 |  */ | 
 | void MZ_RunInThread( PAPCFUNC proc, ULONG_PTR arg ) | 
 | { | 
 |     proc(arg); | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_Exit | 
 |  */ | 
 | void MZ_Exit( CONTEXT *context, BOOL cs_psp, WORD retval ) | 
 | { | 
 |   DOSVM_Exit( retval ); | 
 | } | 
 |  | 
 | /*********************************************************************** | 
 |  *		MZ_Current | 
 |  */ | 
 | BOOL MZ_Current( void ) | 
 | { | 
 |     return FALSE; | 
 | } | 
 |  | 
 | #endif /* !MZ_SUPPORTED */ |