Added MZ_Current() (returns current LPDOSTASK) and DOSVM_Wait()
[wine/multimedia.git] / loader / dos / module.c
blob404b3aaf2bb49989af966de8d87bb2baf0fa4585
1 /*
2 * DOS (MZ) loader
4 * Copyright 1998 Ove Kåven
6 * This code hasn't been completely cleaned up yet.
7 */
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <signal.h>
15 #include <unistd.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <sys/time.h>
19 #include "windef.h"
20 #include "wine/winbase16.h"
21 #include "winerror.h"
22 #include "module.h"
23 #include "peexe.h"
24 #include "neexe.h"
25 #include "task.h"
26 #include "selectors.h"
27 #include "file.h"
28 #include "ldt.h"
29 #include "process.h"
30 #include "miscemu.h"
31 #include "debug.h"
32 #include "dosexe.h"
33 #include "dosmod.h"
34 #include "options.h"
35 #include "server.h"
37 DEFAULT_DEBUG_CHANNEL(module)
39 #ifdef MZ_SUPPORTED
41 #include <sys/mman.h>
43 /* define this to try mapping through /proc/pid/mem instead of a temp file,
44 but Linus doesn't like mmapping /proc/pid/mem, so it doesn't work for me */
45 #undef MZ_MAPSELF
47 #define BIOS_DATA_SEGMENT 0x40
48 #define START_OFFSET 0
49 #define PSP_SIZE 0x10
51 #define SEG16(ptr,seg) ((LPVOID)((BYTE*)ptr+((DWORD)(seg)<<4)))
52 #define SEGPTR16(ptr,segptr) ((LPVOID)((BYTE*)ptr+((DWORD)SELECTOROF(segptr)<<4)+OFFSETOF(segptr)))
54 static void MZ_InitPSP( LPVOID lpPSP, LPCSTR cmdline, WORD env )
56 PDB16*psp=lpPSP;
57 const char*cmd=cmdline?strchr(cmdline,' '):NULL;
59 psp->int20=0x20CD; /* int 20 */
60 /* some programs use this to calculate how much memory they need */
61 psp->nextParagraph=0x9FFF;
62 psp->environment=env;
63 /* copy parameters */
64 if (cmd) {
65 #if 0
66 /* command.com doesn't do this */
67 while (*cmd == ' ') cmd++;
68 #endif
69 psp->cmdLine[0]=strlen(cmd);
70 strcpy(psp->cmdLine+1,cmd);
71 psp->cmdLine[psp->cmdLine[0]+1]='\r';
72 } else psp->cmdLine[1]='\r';
73 /* FIXME: integrate the memory stuff from Wine (msdos/dosmem.c) */
74 /* FIXME: integrate the PDB stuff from Wine (loader/task.c) */
77 /* default INT 08 handler: increases timer tick counter but not much more */
78 static char int08[]={
79 0xCD,0x1C, /* int $0x1c */
80 0x50, /* pushw %ax */
81 0x1E, /* pushw %ds */
82 0xB8,0x40,0x00, /* movw $0x40,%ax */
83 0x8E,0xD8, /* movw %ax,%ds */
84 #if 0
85 0x83,0x06,0x6C,0x00,0x01, /* addw $1,(0x6c) */
86 0x83,0x16,0x6E,0x00,0x00, /* adcw $0,(0x6e) */
87 #else
88 0x66,0xFF,0x06,0x6C,0x00, /* incl (0x6c) */
89 #endif
90 0xB0,0x20, /* movb $0x20,%al */
91 0xE6,0x20, /* outb %al,$0x20 */
92 0x1F, /* popw %ax */
93 0x58, /* popw %ax */
94 0xCF /* iret */
97 static void MZ_InitHandlers( LPDOSTASK lpDosTask )
99 WORD seg;
100 LPBYTE start=DOSMEM_GetBlock(lpDosTask->hModule,sizeof(int08),&seg);
101 memcpy(start,int08,sizeof(int08));
102 /* INT 08: point it at our tick-incrementing handler */
103 ((SEGPTR*)(lpDosTask->img))[0x08]=PTR_SEG_OFF_TO_SEGPTR(seg,0);
104 /* INT 1C: just point it to IRET, we don't want to handle it ourselves */
105 ((SEGPTR*)(lpDosTask->img))[0x1C]=PTR_SEG_OFF_TO_SEGPTR(seg,sizeof(int08)-1);
108 static char enter_xms[]={
109 /* XMS hookable entry point */
110 0xEB,0x03, /* jmp entry */
111 0x90,0x90,0x90, /* nop;nop;nop */
112 /* entry: */
113 /* real entry point */
114 /* for simplicity, we'll just use the same hook as DPMI below */
115 0xCD,0x31, /* int $0x31 */
116 0xCB /* lret */
119 static void MZ_InitXMS( LPDOSTASK lpDosTask )
121 LPBYTE start=DOSMEM_GetBlock(lpDosTask->hModule,sizeof(enter_xms),&(lpDosTask->xms_seg));
122 memcpy(start,enter_xms,sizeof(enter_xms));
125 static char enter_pm[]={
126 0x50, /* pushw %ax */
127 0x52, /* pushw %dx */
128 0x55, /* pushw %bp */
129 0x89,0xE5, /* movw %sp,%bp */
130 /* get return CS */
131 0x8B,0x56,0x08, /* movw 8(%bp),%dx */
132 /* just call int 31 here to get into protected mode... */
133 /* it'll check whether it was called from dpmi_seg... */
134 0xCD,0x31, /* int $0x31 */
135 /* we are now in the context of a 16-bit relay call */
136 /* need to fixup our stack;
137 * 16-bit relay return address will be lost, but we won't worry quite yet */
138 0x8E,0xD0, /* movw %ax,%ss */
139 0x66,0x0F,0xB7,0xE5, /* movzwl %bp,%esp */
140 /* set return CS */
141 0x89,0x56,0x08, /* movw %dx,8(%bp) */
142 0x5D, /* popw %bp */
143 0x5A, /* popw %dx */
144 0x58, /* popw %ax */
145 0xCB /* lret */
148 static void MZ_InitDPMI( LPDOSTASK lpDosTask )
150 unsigned size=sizeof(enter_pm);
151 LPBYTE start=DOSMEM_GetBlock(lpDosTask->hModule,size,&(lpDosTask->dpmi_seg));
153 lpDosTask->dpmi_sel = SELECTOR_AllocBlock( start, size, SEGMENT_CODE, FALSE, FALSE );
155 memcpy(start,enter_pm,sizeof(enter_pm));
158 static WORD MZ_InitEnvironment( LPDOSTASK lpDosTask, LPCSTR env, LPCSTR name )
160 unsigned sz=0;
161 WORD seg;
162 LPSTR envblk;
164 if (env) {
165 /* get size of environment block */
166 while (env[sz++]) sz+=strlen(env+sz)+1;
167 } else sz++;
168 /* allocate it */
169 envblk=DOSMEM_GetBlock(lpDosTask->hModule,sz+sizeof(WORD)+strlen(name)+1,&seg);
170 /* fill it */
171 if (env) {
172 memcpy(envblk,env,sz);
173 } else envblk[0]=0;
174 /* DOS 3.x: the block contains 1 additional string */
175 *(WORD*)(envblk+sz)=1;
176 /* being the program name itself */
177 strcpy(envblk+sz+sizeof(WORD),name);
178 return seg;
181 static BOOL MZ_InitMemory( LPDOSTASK lpDosTask, NE_MODULE *pModule )
183 int x;
185 if (lpDosTask->img) return TRUE; /* already allocated */
187 /* allocate 1MB+64K shared memory */
188 lpDosTask->img_ofs=START_OFFSET;
189 #ifdef MZ_MAPSELF
190 lpDosTask->img=VirtualAlloc(NULL,0x110000,MEM_COMMIT,PAGE_READWRITE);
191 /* make sure mmap accepts it */
192 ((char*)lpDosTask->img)[0x10FFFF]=0;
193 #else
194 tmpnam(lpDosTask->mm_name);
195 /* strcpy(lpDosTask->mm_name,"/tmp/mydosimage"); */
196 lpDosTask->mm_fd=open(lpDosTask->mm_name,O_RDWR|O_CREAT /* |O_TRUNC */,S_IRUSR|S_IWUSR);
197 if (lpDosTask->mm_fd<0) ERR(module,"file %s could not be opened\n",lpDosTask->mm_name);
198 /* expand file to 1MB+64K */
199 lseek(lpDosTask->mm_fd,0x110000-1,SEEK_SET);
200 x=0; write(lpDosTask->mm_fd,&x,1);
201 /* map it in */
202 lpDosTask->img=mmap(NULL,0x110000-START_OFFSET,PROT_READ|PROT_WRITE,MAP_SHARED,lpDosTask->mm_fd,0);
203 #endif
204 if (lpDosTask->img==(LPVOID)-1) {
205 ERR(module,"could not map shared memory, error=%s\n",strerror(errno));
206 return FALSE;
208 TRACE(module,"DOS VM86 image mapped at %08lx\n",(DWORD)lpDosTask->img);
209 pModule->dos_image=lpDosTask->img;
211 /* initialize the memory */
212 TRACE(module,"Initializing DOS memory structures\n");
213 DOSMEM_Init(lpDosTask->hModule);
214 MZ_InitHandlers(lpDosTask);
215 MZ_InitXMS(lpDosTask);
216 MZ_InitDPMI(lpDosTask);
217 return TRUE;
220 static BOOL MZ_LoadImage( HFILE hFile, OFSTRUCT *ofs, LPCSTR cmdline,
221 LPCSTR env, LPDOSTASK lpDosTask, NE_MODULE *pModule )
223 IMAGE_DOS_HEADER mz_header;
224 DWORD image_start,image_size,min_size,max_size,avail;
225 BYTE*psp_start,*load_start;
226 int x,old_com=0;
227 SEGPTR reloc;
228 WORD env_seg;
230 _llseek(hFile,0,FILE_BEGIN);
231 if ((_lread(hFile,&mz_header,sizeof(mz_header)) != sizeof(mz_header)) ||
232 (mz_header.e_magic != IMAGE_DOS_SIGNATURE)) {
233 old_com=1; /* assume .COM file */
234 image_start=0;
235 image_size=GetFileSize(hFile,NULL);
236 min_size=0x10000; max_size=0x100000;
237 mz_header.e_crlc=0;
238 mz_header.e_ss=0; mz_header.e_sp=0xFFFE;
239 mz_header.e_cs=0; mz_header.e_ip=0x100;
240 } else {
241 /* calculate load size */
242 image_start=mz_header.e_cparhdr<<4;
243 image_size=mz_header.e_cp<<9; /* pages are 512 bytes */
244 if ((mz_header.e_cblp!=0)&&(mz_header.e_cblp!=4)) image_size-=512-mz_header.e_cblp;
245 image_size-=image_start;
246 min_size=image_size+((DWORD)mz_header.e_minalloc<<4)+(PSP_SIZE<<4);
247 max_size=image_size+((DWORD)mz_header.e_maxalloc<<4)+(PSP_SIZE<<4);
250 MZ_InitMemory(lpDosTask,pModule);
252 /* allocate environment block */
253 env_seg=MZ_InitEnvironment(lpDosTask,env,ofs->szPathName);
255 /* allocate memory for the executable */
256 TRACE(module,"Allocating DOS memory (min=%ld, max=%ld)\n",min_size,max_size);
257 avail=DOSMEM_Available(lpDosTask->hModule);
258 if (avail<min_size) {
259 ERR(module, "insufficient DOS memory\n");
260 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
261 return FALSE;
263 if (avail>max_size) avail=max_size;
264 psp_start=DOSMEM_GetBlock(lpDosTask->hModule,avail,&lpDosTask->psp_seg);
265 if (!psp_start) {
266 ERR(module, "error allocating DOS memory\n");
267 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
268 return FALSE;
270 lpDosTask->load_seg=lpDosTask->psp_seg+(old_com?0:PSP_SIZE);
271 load_start=psp_start+(PSP_SIZE<<4);
272 MZ_InitPSP(psp_start, cmdline, env_seg);
274 /* load executable image */
275 TRACE(module,"loading DOS %s image, %08lx bytes\n",old_com?"COM":"EXE",image_size);
276 _llseek(hFile,image_start,FILE_BEGIN);
277 if ((_lread(hFile,load_start,image_size)) != image_size) {
278 SetLastError(ERROR_BAD_FORMAT);
279 return FALSE;
282 if (mz_header.e_crlc) {
283 /* load relocation table */
284 TRACE(module,"loading DOS EXE relocation table, %d entries\n",mz_header.e_crlc);
285 /* FIXME: is this too slow without read buffering? */
286 _llseek(hFile,mz_header.e_lfarlc,FILE_BEGIN);
287 for (x=0; x<mz_header.e_crlc; x++) {
288 if (_lread(hFile,&reloc,sizeof(reloc)) != sizeof(reloc)) {
289 SetLastError(ERROR_BAD_FORMAT);
290 return FALSE;
292 *(WORD*)SEGPTR16(load_start,reloc)+=lpDosTask->load_seg;
296 lpDosTask->init_cs=lpDosTask->load_seg+mz_header.e_cs;
297 lpDosTask->init_ip=mz_header.e_ip;
298 lpDosTask->init_ss=lpDosTask->load_seg+mz_header.e_ss;
299 lpDosTask->init_sp=mz_header.e_sp;
301 TRACE(module,"entry point: %04x:%04x\n",lpDosTask->init_cs,lpDosTask->init_ip);
302 return TRUE;
305 LPDOSTASK MZ_AllocDPMITask( HMODULE16 hModule )
307 LPDOSTASK lpDosTask = calloc(1, sizeof(DOSTASK));
308 NE_MODULE *pModule;
310 if (lpDosTask) {
311 lpDosTask->hModule = hModule;
313 pModule = (NE_MODULE *)GlobalLock16(hModule);
314 pModule->lpDosTask = lpDosTask;
316 lpDosTask->img=NULL; lpDosTask->mm_name[0]=0; lpDosTask->mm_fd=-1;
318 MZ_InitMemory(lpDosTask, pModule);
320 GlobalUnlock16(hModule);
322 return lpDosTask;
325 static void MZ_InitTimer( LPDOSTASK lpDosTask, int ver )
327 if (ver<1) {
328 /* can't make timer ticks */
329 } else {
330 int func;
331 struct timeval tim;
333 /* start dosmod timer at 55ms (18.2Hz) */
334 func=DOSMOD_SET_TIMER;
335 tim.tv_sec=0; tim.tv_usec=54925;
336 write(lpDosTask->write_pipe,&func,sizeof(func));
337 write(lpDosTask->write_pipe,&tim,sizeof(tim));
341 BOOL MZ_InitTask( LPDOSTASK lpDosTask )
343 int write_fd[2],x_fd;
344 pid_t child;
345 char *fname,*farg,arg[16],fproc[64],path[256],*fpath;
346 SECURITY_ATTRIBUTES attr={sizeof(attr),NULL,TRUE};
347 struct get_read_fd_request r_req;
348 struct get_write_fd_request w_req;
350 if (!lpDosTask) return FALSE;
351 /* create pipes */
352 /* this happens in the wrong process context, so we have to let the new process
353 inherit it... (FIXME: call MZ_InitTask in the right process context) */
354 if (!CreatePipe(&(lpDosTask->hReadPipe),&(lpDosTask->hXPipe),&attr,0)) return FALSE;
355 if (pipe(write_fd)<0) {
356 CloseHandle(lpDosTask->hReadPipe);
357 CloseHandle(lpDosTask->hXPipe);
358 return FALSE;
360 r_req.handle = lpDosTask->hReadPipe;
361 CLIENT_SendRequest( REQ_GET_READ_FD, -1, 1, &r_req, sizeof(r_req) );
362 CLIENT_WaitReply( NULL, &(lpDosTask->read_pipe), 0 );
363 w_req.handle = lpDosTask->hXPipe;
364 CLIENT_SendRequest( REQ_GET_WRITE_FD, -1, 1, &w_req, sizeof(w_req) );
365 CLIENT_WaitReply( NULL, &x_fd, 0 );
367 TRACE(module,"win32 pipe: read=%d, write=%d, unix pipe: read=%d, write=%d\n",
368 lpDosTask->hReadPipe,lpDosTask->hXPipe,lpDosTask->read_pipe,x_fd);
369 TRACE(module,"outbound unix pipe: read=%d, write=%d, pid=%d\n",write_fd[0],write_fd[1],getpid());
371 lpDosTask->write_pipe=write_fd[1];
373 lpDosTask->hConInput=GetStdHandle(STD_INPUT_HANDLE);
374 lpDosTask->hConOutput=GetStdHandle(STD_OUTPUT_HANDLE);
376 /* if we have a mapping file, use it */
377 fname=lpDosTask->mm_name; farg=NULL;
378 if (!fname[0]) {
379 /* otherwise, map our own memory image */
380 sprintf(fproc,"/proc/%d/mem",getpid());
381 sprintf(arg,"%ld",(unsigned long)lpDosTask->img);
382 fname=fproc; farg=arg;
385 TRACE(module,"Loading DOS VM support module (hmodule=%04x)\n",lpDosTask->hModule);
386 if ((child=fork())<0) {
387 close(write_fd[0]);
388 close(lpDosTask->read_pipe);
389 close(lpDosTask->write_pipe);
390 close(x_fd);
391 CloseHandle(lpDosTask->hReadPipe);
392 CloseHandle(lpDosTask->hXPipe);
393 return FALSE;
395 if (child!=0) {
396 /* parent process */
397 int ret;
399 close(write_fd[0]);
400 close(x_fd);
401 lpDosTask->task=child;
402 /* wait for child process to signal readiness */
403 do {
404 if (read(lpDosTask->read_pipe,&ret,sizeof(ret))!=sizeof(ret)) {
405 if ((errno==EINTR)||(errno==EAGAIN)) continue;
406 /* failure */
407 ERR(module,"dosmod has failed to initialize\n");
408 if (lpDosTask->mm_name[0]!=0) unlink(lpDosTask->mm_name);
409 return FALSE;
411 } while (0);
412 /* the child has now mmaped the temp file, it's now safe to unlink.
413 * do it here to avoid leaving a mess in /tmp if/when Wine crashes... */
414 if (lpDosTask->mm_name[0]!=0) unlink(lpDosTask->mm_name);
415 /* start simulated system timer */
416 MZ_InitTimer(lpDosTask,ret);
417 if (ret<2) {
418 ERR(module,"dosmod version too old! Please install newer dosmod properly\n");
419 ERR(module,"If you don't, the new dosmod event handling system will not work\n");
421 /* all systems are now go */
422 } else {
423 /* child process */
424 close(lpDosTask->read_pipe);
425 close(lpDosTask->write_pipe);
426 /* put our pipes somewhere dosmod can find them */
427 dup2(write_fd[0],0); /* stdin */
428 dup2(x_fd,1); /* stdout */
429 /* enable signals */
430 SIGNAL_MaskAsyncEvents(FALSE);
431 /* now load dosmod */
432 /* check argv[0]-derived paths first, since the newest dosmod is most likely there
433 * (at least it was once for Andreas Mohr, so I decided to make it easier for him) */
434 fpath=strrchr(strcpy(path,Options.argv0),'/');
435 if (fpath) {
436 strcpy(fpath,"/dosmod");
437 execl(path,fname,farg,NULL);
438 strcpy(fpath,"/loader/dos/dosmod");
439 execl(path,fname,farg,NULL);
441 /* okay, it wasn't there, try in the path */
442 execlp("dosmod",fname,farg,NULL);
443 /* last desperate attempts: current directory */
444 execl("dosmod",fname,farg,NULL);
445 /* and, just for completeness... */
446 execl("loader/dos/dosmod",fname,farg,NULL);
447 /* if failure, exit */
448 ERR(module,"Failed to spawn dosmod, error=%s\n",strerror(errno));
449 exit(1);
451 return TRUE;
454 BOOL MZ_CreateProcess( HFILE hFile, OFSTRUCT *ofs, LPCSTR cmdline, LPCSTR env,
455 LPSECURITY_ATTRIBUTES psa, LPSECURITY_ATTRIBUTES tsa,
456 BOOL inherit, LPSTARTUPINFOA startup,
457 LPPROCESS_INFORMATION info )
459 LPDOSTASK lpDosTask = NULL; /* keep gcc from complaining */
460 HMODULE16 hModule;
461 PDB *pdb = PROCESS_Current();
462 TDB *pTask = (TDB*)GlobalLock16( GetCurrentTask() );
463 NE_MODULE *pModule = pTask ? NE_GetPtr( pTask->hModule ) : NULL;
464 int alloc = !(pModule && pModule->dos_image);
466 if (alloc && (lpDosTask = calloc(1, sizeof(DOSTASK))) == NULL) {
467 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
468 return FALSE;
471 if ((!env)&&pdb) env = pdb->env_db->environ;
472 if (alloc) {
473 if ((hModule = MODULE_CreateDummyModule(ofs, NULL)) < 32) {
474 SetLastError(hModule);
475 return FALSE;
477 lpDosTask->hModule = hModule;
479 pModule = (NE_MODULE *)GlobalLock16(hModule);
480 pModule->lpDosTask = lpDosTask;
482 lpDosTask->img=NULL; lpDosTask->mm_name[0]=0; lpDosTask->mm_fd=-1;
483 } else lpDosTask=pModule->lpDosTask;
484 if (!MZ_LoadImage( hFile, ofs, cmdline, env, lpDosTask, pModule )) {
485 if (alloc) {
486 if (lpDosTask->mm_name[0]!=0) {
487 if (lpDosTask->img!=NULL) munmap(lpDosTask->img,0x110000-START_OFFSET);
488 if (lpDosTask->mm_fd>=0) close(lpDosTask->mm_fd);
489 unlink(lpDosTask->mm_name);
490 } else
491 if (lpDosTask->img!=NULL) VirtualFree(lpDosTask->img,0x110000,MEM_RELEASE);
493 return FALSE;
495 if (alloc) {
496 pModule->dos_image = lpDosTask->img;
497 if (!MZ_InitTask( lpDosTask )) {
498 MZ_KillModule( lpDosTask );
499 /* FIXME: cleanup hModule */
500 SetLastError(ERROR_GEN_FAILURE);
501 return FALSE;
503 inherit = TRUE; /* bad hack for inheriting the CreatePipe... */
504 if (!PROCESS_Create( pModule, cmdline, env, 0, 0,
505 psa, tsa, inherit, startup, info ))
506 return FALSE;
508 return TRUE;
511 void MZ_KillModule( LPDOSTASK lpDosTask )
513 DOSEVENT *event,*p_event;
514 DOSSYSTEM *sys,*p_sys;
516 TRACE(module,"killing DOS task\n");
517 if (lpDosTask->mm_name[0]!=0) {
518 munmap(lpDosTask->img,0x110000-START_OFFSET);
519 close(lpDosTask->mm_fd);
520 } else VirtualFree(lpDosTask->img,0x110000,MEM_RELEASE);
521 close(lpDosTask->read_pipe);
522 close(lpDosTask->write_pipe);
523 CloseHandle(lpDosTask->hReadPipe);
524 CloseHandle(lpDosTask->hXPipe);
525 kill(lpDosTask->task,SIGTERM);
526 /* free memory allocated for events and systems */
527 #define DFREE(var,pvar,svar) \
528 var = lpDosTask->svar; \
529 while (var) { \
530 if (var->data) free(var->data); \
531 pvar = var->next; free(var); var = pvar; \
534 DFREE(event,p_event,pending)
535 DFREE(event,p_event,current)
536 DFREE(sys,p_sys,sys)
538 #undef DFREE
540 #if 0
541 /* FIXME: this seems to crash */
542 if (lpDosTask->dpmi_sel)
543 UnMapLS(PTR_SEG_OFF_TO_SEGPTR(lpDosTask->dpmi_sel,0));
544 #endif
547 LPDOSTASK MZ_Current( void )
549 TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
550 NE_MODULE *pModule = pTask ? NE_GetPtr( pTask->hModule ) : NULL;
552 GlobalUnlock16( GetCurrentTask() );
554 if (pModule)
555 return pModule->lpDosTask;
557 return NULL;
560 #else /* !MZ_SUPPORTED */
562 BOOL MZ_CreateProcess( HFILE hFile, OFSTRUCT *ofs, LPCSTR cmdline, LPCSTR env,
563 LPSECURITY_ATTRIBUTES psa, LPSECURITY_ATTRIBUTES tsa,
564 BOOL inherit, LPSTARTUPINFOA startup,
565 LPPROCESS_INFORMATION info )
567 WARN(module,"DOS executables not supported on this architecture\n");
568 SetLastError(ERROR_BAD_FORMAT);
569 return FALSE;
572 LPDOSTASK *MZ_Current( void )
574 return NULL;
577 #endif