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