Merged msacm and msacm32 dlls.
[wine] / loader / dos / dosvm.c
1 /*
2  * DOS Virtual Machine
3  *
4  * Copyright 1998 Ove Kåven
5  *
6  * This code hasn't been completely cleaned up yet.
7  */
8
9 #include "config.h"
10
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/time.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21
22 #include "wine/winbase16.h"
23 #include "wine/exception.h"
24 #include "windef.h"
25 #include "wingdi.h"
26 #include "winuser.h"
27 #include "winnt.h"
28
29 #include "callback.h"
30 #include "msdos.h"
31 #include "file.h"
32 #include "miscemu.h"
33 #include "dosexe.h"
34 #include "dosmod.h"
35 #include "stackframe.h"
36 #include "debugtools.h"
37
38 DECLARE_DEBUG_CHANNEL(int)
39 DECLARE_DEBUG_CHANNEL(module)
40 DECLARE_DEBUG_CHANNEL(relay)
41
42 #ifdef MZ_SUPPORTED
43
44 #ifdef HAVE_SYS_VM86_H
45 # include <sys/vm86.h>
46 #endif
47 #ifdef HAVE_SYS_MMAN_H
48 # include <sys/mman.h>
49 #endif
50
51 #define IF_CLR(ctx) EFL_reg(ctx) &= ~VIF_MASK
52 #define IF_ENABLED(ctx) (EFL_reg(ctx) & VIF_MASK)
53 #define SET_PEND(ctx) EFL_reg(ctx) |= VIP_MASK
54 #define CLR_PEND(ctx) EFL_reg(ctx) &= ~VIP_MASK
55 #define IS_PEND(ctx) (EFL_reg(ctx) & VIP_MASK)
56
57 #undef TRY_PICRETURN
58
59 static void do_exception( int signal, CONTEXT86 *context )
60 {
61     EXCEPTION_RECORD rec;
62     if ((signal == SIGTRAP) || (signal == SIGHUP))
63     {
64         rec.ExceptionCode  = EXCEPTION_BREAKPOINT;
65         rec.ExceptionFlags = EXCEPTION_CONTINUABLE;
66     }
67     else
68     {
69         rec.ExceptionCode  = EXCEPTION_ILLEGAL_INSTRUCTION;  /* generic error */
70         rec.ExceptionFlags = EH_NONCONTINUABLE;
71     }
72     rec.ExceptionRecord  = NULL;
73     rec.ExceptionAddress = (LPVOID)EIP_reg(context);
74     rec.NumberParameters = 0;
75     EXC_RtlRaiseException( &rec, context );
76 }
77
78 static void DOSVM_Dump( LPDOSTASK lpDosTask, int fn, int sig,
79                         struct vm86plus_struct*VM86 )
80 {
81  unsigned iofs;
82  BYTE*inst;
83  int x;
84
85  switch (VM86_TYPE(fn)) {
86   case VM86_SIGNAL:
87    printf("Trapped signal %d\n",sig); break;
88   case VM86_UNKNOWN:
89    printf("Trapped unhandled GPF\n"); break;
90   case VM86_INTx:
91    printf("Trapped INT %02x\n",VM86_ARG(fn)); break;
92   case VM86_STI:
93    printf("Trapped STI\n"); break;
94   case VM86_PICRETURN:
95    printf("Trapped due to pending PIC request\n"); break;
96   case VM86_TRAP:
97    printf("Trapped debug request\n"); break;
98   default:
99    printf("Trapped unknown VM86 type %d arg %d\n",VM86_TYPE(fn),VM86_ARG(fn)); break;
100  }
101 #define REGS VM86->regs
102  fprintf(stderr,"AX=%04lX CX=%04lX DX=%04lX BX=%04lX\n",REGS.eax,REGS.ecx,REGS.edx,REGS.ebx);
103  fprintf(stderr,"SI=%04lX DI=%04lX SP=%04lX BP=%04lX\n",REGS.esi,REGS.edi,REGS.esp,REGS.ebp);
104  fprintf(stderr,"CS=%04X DS=%04X ES=%04X SS=%04X\n",REGS.cs,REGS.ds,REGS.es,REGS.ss);
105  fprintf(stderr,"IP=%04lX EFLAGS=%08lX\n",REGS.eip,REGS.eflags);
106
107  iofs=((DWORD)REGS.cs<<4)+REGS.eip;
108 #undef REGS
109  inst=(BYTE*)lpDosTask->img+iofs;
110  printf("Opcodes:");
111  for (x=0; x<8; x++) printf(" %02x",inst[x]);
112  printf("\n");
113 }
114
115 static int DOSVM_Int( int vect, CONTEXT86 *context, LPDOSTASK lpDosTask )
116 {
117  extern UINT16 DPMI_wrap_seg;
118
119  if (vect==0x31) {
120   if (CS_reg(context)==DPMI_wrap_seg) {
121    /* exit from real-mode wrapper */
122    return -1;
123   }
124   /* we could probably move some other dodgy stuff here too from dpmi.c */
125  }
126  INT_RealModeInterrupt(vect,context);
127  return 0;
128 }
129
130 static void DOSVM_SimulateInt( int vect, CONTEXT86 *context, LPDOSTASK lpDosTask )
131 {
132   FARPROC16 handler=INT_GetRMHandler(vect);
133
134   if (SELECTOROF(handler)==0xf000) {
135     /* if internal interrupt, call it directly */
136     INT_RealModeInterrupt(vect,context);
137   } else {
138     WORD*stack=(WORD*)(V86BASE(context)+(((DWORD)SS_reg(context))<<4)+LOWORD(ESP_reg(context)));
139     WORD flag=LOWORD(EFL_reg(context));
140
141     if (IF_ENABLED(context)) flag|=IF_MASK;
142     else flag&=~IF_MASK;
143
144     *(--stack)=flag;
145     *(--stack)=CS_reg(context);
146     *(--stack)=LOWORD(EIP_reg(context));
147     ESP_reg(context)-=6;
148     CS_reg(context)=SELECTOROF(handler);
149     EIP_reg(context)=OFFSETOF(handler);
150     IF_CLR(context);
151   }
152 }
153
154 #define SHOULD_PEND(x) \
155   (x && ((!lpDosTask->current) || (x->priority < lpDosTask->current->priority)))
156
157 static void DOSVM_SendQueuedEvent(CONTEXT86 *context, LPDOSTASK lpDosTask)
158 {
159   LPDOSEVENT event = lpDosTask->pending;
160
161   if (SHOULD_PEND(event)) {
162     /* remove from "pending" list */
163     lpDosTask->pending = event->next;
164     /* process event */
165     if (event->irq>=0) {
166       /* it's an IRQ, move it to "current" list */
167       event->next = lpDosTask->current;
168       lpDosTask->current = event;
169       TRACE_(int)("dispatching IRQ %d\n",event->irq);
170       /* note that if DOSVM_SimulateInt calls an internal interrupt directly,
171        * lpDosTask->current might be cleared (and event freed) in this very call! */
172       DOSVM_SimulateInt((event->irq<8)?(event->irq+8):(event->irq-8+0x70),context,lpDosTask);
173     } else {
174       /* callback event */
175       TRACE_(int)("dispatching callback event\n");
176       (*event->relay)(lpDosTask,context,event->data);
177       free(event);
178     }
179   }
180   if (!SHOULD_PEND(lpDosTask->pending)) {
181     TRACE_(int)("clearing Pending flag\n");
182     CLR_PEND(context);
183   }
184 }
185
186 static void DOSVM_SendQueuedEvents(CONTEXT86 *context, LPDOSTASK lpDosTask)
187 {
188   /* we will send all queued events as long as interrupts are enabled,
189    * but IRQ events will disable interrupts again */
190   while (IS_PEND(context) && IF_ENABLED(context))
191     DOSVM_SendQueuedEvent(context,lpDosTask);
192 }
193
194 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,CONTEXT86*,void*), void *data)
195 {
196   LPDOSTASK lpDosTask = MZ_Current();
197   LPDOSEVENT event, cur, prev;
198
199   if (lpDosTask) {
200     event = malloc(sizeof(DOSEVENT));
201     if (!event) {
202       ERR_(int)("out of memory allocating event entry\n");
203       return;
204     }
205     event->irq = irq; event->priority = priority;
206     event->relay = relay; event->data = data;
207
208     /* insert event into linked list, in order *after*
209      * all earlier events of higher or equal priority */
210     cur = lpDosTask->pending; prev = NULL;
211     while (cur && cur->priority<=priority) {
212       prev = cur;
213       cur = cur->next;
214     }
215     event->next = cur;
216     if (prev) prev->next = event;
217     else lpDosTask->pending = event;
218     
219     /* get dosmod's attention to the new event, except for irq==0 where we already have it */
220     if (irq && !lpDosTask->sig_sent) {
221       TRACE_(int)("new event queued, signalling dosmod\n");
222       kill(lpDosTask->task,SIGUSR2);
223       lpDosTask->sig_sent++;
224     } else {
225       TRACE_(int)("new event queued\n");
226     }
227   }
228 }
229
230 #define CV CP(eax,EAX); CP(ecx,ECX); CP(edx,EDX); CP(ebx,EBX); \
231            CP(esi,ESI); CP(edi,EDI); CP(esp,ESP); CP(ebp,EBP); \
232            CP(cs,CS); CP(ds,DS); CP(es,ES); \
233            CP(ss,SS); CP(fs,FS); CP(gs,GS); \
234            CP(eip,EIP); CP(eflags,EFL)
235
236 static int DOSVM_Process( LPDOSTASK lpDosTask, int fn, int sig,
237                           struct vm86plus_struct*VM86 )
238 {
239  CONTEXT86 context;
240  int ret=0;
241
242 #define CP(x,y) y##_reg(&context) = VM86->regs.x
243   CV;
244 #undef CP
245  if (VM86_TYPE(fn)==VM86_UNKNOWN) {
246   ret=INSTR_EmulateInstruction(&context);
247 #define CP(x,y) VM86->regs.x = y##_reg(&context)
248   CV;
249 #undef CP
250   if (ret) return 0;
251   ret=0;
252  }
253 #ifdef TRY_PICRETURN
254  if (VM86->vm86plus.force_return_for_pic) {
255    SET_PEND(&context);
256  }
257 #else
258  /* linux doesn't preserve pending flag on return */
259  if (SHOULD_PEND(lpDosTask->pending)) {
260    SET_PEND(&context);
261  }
262 #endif
263
264  switch (VM86_TYPE(fn)) {
265   case VM86_SIGNAL:
266    TRACE_(int)("DOS module caught signal %d\n",sig);
267    if ((sig==SIGALRM) || (sig==SIGUSR2)) {
268      if (sig==SIGALRM) {
269        DOSVM_QueueEvent(0,DOS_PRIORITY_REALTIME,NULL,NULL);
270      }
271      if (lpDosTask->pending) {
272        TRACE_(int)("setting Pending flag, interrupts are currently %s\n",
273                  IF_ENABLED(&context) ? "enabled" : "disabled");
274        SET_PEND(&context);
275        DOSVM_SendQueuedEvents(&context,lpDosTask);
276      } else {
277        TRACE_(int)("no events are pending, clearing Pending flag\n");
278        CLR_PEND(&context);
279      }
280      if (sig==SIGUSR2) lpDosTask->sig_sent--;
281    }
282    else if ((sig==SIGHUP) || (sig==SIGILL) || (sig==SIGSEGV)) {
283        do_exception( sig, &context );
284    } else {
285     DOSVM_Dump(lpDosTask,fn,sig,VM86);
286     ret=-1;
287    }
288    break;
289   case VM86_UNKNOWN: /* unhandled GPF */
290    DOSVM_Dump(lpDosTask,fn,sig,VM86);
291    do_exception( SIGSEGV, &context );
292    break;
293   case VM86_INTx:
294    if (TRACE_ON(relay))
295     DPRINTF("Call DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
296    ret=DOSVM_Int(VM86_ARG(fn),&context,lpDosTask);
297    if (TRACE_ON(relay))
298     DPRINTF("Ret  DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
299    break;
300   case VM86_STI:
301   case VM86_PICRETURN:
302     TRACE_(int)("DOS task enabled interrupts with events pending, sending events\n");
303     DOSVM_SendQueuedEvents(&context,lpDosTask);
304     break;
305   case VM86_TRAP:
306    do_exception( SIGTRAP, &context );
307    break;
308   default:
309    DOSVM_Dump(lpDosTask,fn,sig,VM86);
310    ret=-1;
311  }
312
313 #define CP(x,y) VM86->regs.x = y##_reg(&context)
314  CV;
315 #undef CP
316 #ifdef TRY_PICRETURN
317  VM86->vm86plus.force_return_for_pic = IS_PEND(&context) ? 1 : 0;
318  CLR_PEND(&context);
319 #endif
320  return ret;
321 }
322
323 void DOSVM_ProcessMessage(LPDOSTASK lpDosTask,MSG *msg)
324 {
325   BYTE scan = 0;
326
327   fprintf(stderr,"got message %04x, wparam=%08x, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam);
328   if ((msg->message>=WM_MOUSEFIRST)&&
329       (msg->message<=WM_MOUSELAST)) {
330     INT_Int33Message(msg->message,msg->wParam,msg->lParam);
331   } else {
332     switch (msg->message) {
333     case WM_KEYUP:
334       scan = 0x80;
335     case WM_KEYDOWN:
336       scan |= (msg->lParam >> 16) & 0x7f;
337
338       /* check whether extended bit is set,
339        * and if so, queue the extension prefix */
340       if (msg->lParam & 0x1000000) {
341         /* FIXME: some keys (function keys) have
342          * extended bit set even when they shouldn't,
343          * should check for them */
344         INT_Int09SendScan(0xE0);
345       }
346       INT_Int09SendScan(scan);
347       break;
348     }
349   }
350 }
351
352 void DOSVM_Wait( int read_pipe, HANDLE hObject )
353 {
354   LPDOSTASK lpDosTask = MZ_Current();
355   MSG msg;
356   DWORD waitret;
357   BOOL got_msg = FALSE;
358
359   do {
360     /* check for messages (waste time before the response check below) */
361     while (Callout.PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) {
362       /* got a message */
363       DOSVM_ProcessMessage(lpDosTask,&msg);
364       /* we don't need a TranslateMessage here */
365       Callout.DispatchMessageA(&msg);
366       got_msg = TRUE;
367     }
368     if (read_pipe == -1) {
369       if (got_msg) break;
370     } else {
371       fd_set readfds;
372       struct timeval timeout={0,0};
373       /* quick check for response from dosmod
374        * (faster than doing the full blocking wait, if data already available) */
375       FD_ZERO(&readfds); FD_SET(read_pipe,&readfds);
376       if (select(read_pipe+1,&readfds,NULL,NULL,&timeout)>0)
377         break;
378     }
379     /* check for data from win32 console device */
380
381     /* nothing yet, block while waiting for something to do */
382     waitret=MsgWaitForMultipleObjects(1,&hObject,FALSE,INFINITE,QS_ALLINPUT);
383     if (waitret==(DWORD)-1) {
384       ERR_(module)("dosvm wait error=%ld\n",GetLastError());
385     }
386     if (read_pipe != -1) {
387       if (waitret==WAIT_OBJECT_0) break;
388     }
389   } while (TRUE);
390 }
391
392 int DOSVM_Enter( CONTEXT86 *context )
393 {
394  LPDOSTASK lpDosTask = MZ_Current();
395  struct vm86plus_struct VM86;
396  int stat,len,sig;
397
398  if (!lpDosTask) {
399   /* MZ_CreateProcess or MZ_AllocDPMITask should have been called first */
400   ERR_(module)("dosmod has not been initialized!");
401   return -1;
402  }
403
404  if (context) {
405 #define CP(x,y) VM86.regs.x = y##_reg(context)
406   CV;
407 #undef CP
408   if (VM86.regs.eflags & IF_MASK)
409     VM86.regs.eflags |= VIF_MASK;
410  } else {
411 /* initial setup */
412   /* allocate standard DOS handles */
413   FILE_InitProcessDosHandles(); 
414   /* registers */
415   memset(&VM86,0,sizeof(VM86));
416   VM86.regs.cs=lpDosTask->init_cs;
417   VM86.regs.eip=lpDosTask->init_ip;
418   VM86.regs.ss=lpDosTask->init_ss;
419   VM86.regs.esp=lpDosTask->init_sp;
420   VM86.regs.ds=lpDosTask->psp_seg;
421   VM86.regs.es=lpDosTask->psp_seg;
422   VM86.regs.eflags=VIF_MASK;
423   /* hmm, what else do we need? */
424  }
425
426  /* main exchange loop */
427  do {
428   stat = VM86_ENTER;
429   errno = 0;
430   /* transmit VM86 structure to dosmod task */
431   if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
432    ERR_(module)("dosmod sync lost, errno=%d, fd=%d, pid=%d\n",errno,lpDosTask->write_pipe,getpid());
433    return -1;
434   }
435   if (write(lpDosTask->write_pipe,&VM86,sizeof(VM86))!=sizeof(VM86)) {
436    ERR_(module)("dosmod sync lost, errno=%d\n",errno);
437    return -1;
438   }
439   /* wait for response, doing other things in the meantime */
440   DOSVM_Wait(lpDosTask->read_pipe, lpDosTask->hReadPipe);
441   /* read response */
442   while (1) {
443     if ((len=read(lpDosTask->read_pipe,&stat,sizeof(stat)))==sizeof(stat)) break;
444     if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
445      WARN_(module)("rereading dosmod return code due to errno=%d, result=%d\n",errno,len);
446      continue;
447     }
448     ERR_(module)("dosmod sync lost reading return code, errno=%d, result=%d\n",errno,len);
449     return -1;
450   }
451   TRACE_(module)("dosmod return code=%d\n",stat);
452   while (1) {
453     if ((len=read(lpDosTask->read_pipe,&VM86,sizeof(VM86)))==sizeof(VM86)) break;
454     if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
455      WARN_(module)("rereading dosmod VM86 structure due to errno=%d, result=%d\n",errno,len);
456      continue;
457     }
458     ERR_(module)("dosmod sync lost reading VM86 structure, errno=%d, result=%d\n",errno,len);
459     return -1;
460   }
461   if ((stat&0xff)==DOSMOD_SIGNAL) {
462     while (1) {
463       if ((len=read(lpDosTask->read_pipe,&sig,sizeof(sig)))==sizeof(sig)) break;
464       if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
465         WARN_(module)("rereading dosmod signal due to errno=%d, result=%d\n",errno,len);
466         continue;
467       }
468       ERR_(module)("dosmod sync lost reading signal, errno=%d, result=%d\n",errno,len);
469       return -1;
470     } while (0);
471   } else sig=0;
472   /* got response */
473  } while (DOSVM_Process(lpDosTask,stat,sig,&VM86)>=0);
474
475  if (context) {
476 #define CP(x,y) y##_reg(context) = VM86.regs.x
477   CV;
478 #undef CP
479  }
480  return 0;
481 }
482
483 void DOSVM_PIC_ioport_out( WORD port, BYTE val)
484 {
485   LPDOSTASK lpDosTask = MZ_Current();
486   LPDOSEVENT event;
487
488   if (lpDosTask) {
489     if ((port==0x20) && (val==0x20)) {
490       if (lpDosTask->current) {
491         /* EOI (End Of Interrupt) */
492         TRACE_(int)("received EOI for current IRQ, clearing\n");
493         event = lpDosTask->current;
494         lpDosTask->current = event->next;
495         if (event->relay)
496         (*event->relay)(lpDosTask,NULL,event->data);
497         free(event);
498
499         if (lpDosTask->pending &&
500             !lpDosTask->sig_sent) {
501           /* another event is pending, which we should probably
502            * be able to process now, so tell dosmod about it */
503           TRACE_(int)("another event pending, signalling dosmod\n");
504           kill(lpDosTask->task,SIGUSR2);
505           lpDosTask->sig_sent++;
506         }
507       } else {
508         WARN_(int)("EOI without active IRQ\n");
509       }
510     } else {
511       FIXME_(int)("unrecognized PIC command %02x\n",val);
512     }
513   }
514 }
515
516 void DOSVM_SetTimer( unsigned ticks )
517 {
518   LPDOSTASK lpDosTask = MZ_Current();
519   int stat=DOSMOD_SET_TIMER;
520   struct timeval tim;
521
522   if (lpDosTask) {
523     /* the PC clocks ticks at 1193180 Hz */
524     tim.tv_sec=0;
525     tim.tv_usec=((unsigned long long)ticks*1000000)/1193180;
526     /* sanity check */
527     if (!tim.tv_usec) tim.tv_usec=1;
528
529     if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
530       ERR_(module)("dosmod sync lost, errno=%d\n",errno);
531       return;
532     }
533     if (write(lpDosTask->write_pipe,&tim,sizeof(tim))!=sizeof(tim)) {
534       ERR_(module)("dosmod sync lost, errno=%d\n",errno);
535       return;
536     }
537     /* there's no return */
538   }
539 }
540
541 unsigned DOSVM_GetTimer( void )
542 {
543   LPDOSTASK lpDosTask = MZ_Current();
544   int stat=DOSMOD_GET_TIMER;
545   struct timeval tim;
546
547   if (lpDosTask) {
548     if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
549       ERR_(module)("dosmod sync lost, errno=%d\n",errno);
550       return 0;
551     }
552     /* read response */
553     while (1) {
554       if (read(lpDosTask->read_pipe,&tim,sizeof(tim))==sizeof(tim)) break;
555       if ((errno==EINTR)||(errno==EAGAIN)) continue;
556       ERR_(module)("dosmod sync lost, errno=%d\n",errno);
557       return 0;
558     }
559     return ((unsigned long long)tim.tv_usec*1193180)/1000000;
560   }
561   return 0;
562 }
563
564 void DOSVM_SetSystemData( int id, void *data )
565 {
566   LPDOSTASK lpDosTask = MZ_Current();
567   DOSSYSTEM *sys, *prev;
568
569   if (lpDosTask) {
570     sys = lpDosTask->sys;
571     prev = NULL;
572     while (sys && (sys->id != id)) {
573       prev = sys;
574       sys = sys->next;
575     }
576     if (sys) {
577       free(sys->data);
578       sys->data = data;
579     } else {
580       sys = malloc(sizeof(DOSSYSTEM));
581       sys->id = id;
582       sys->data = data;
583       sys->next = NULL;
584       if (prev) prev->next = sys;
585       else lpDosTask->sys = sys;
586     }
587   } else free(data);
588 }
589
590 void* DOSVM_GetSystemData( int id )
591 {
592   LPDOSTASK lpDosTask = MZ_Current();
593   DOSSYSTEM *sys;
594
595   if (lpDosTask) {
596     sys = lpDosTask->sys;
597     while (sys && (sys->id != id))
598       sys = sys->next;
599     if (sys)
600       return sys->data;
601   }
602   return NULL;
603 }
604
605 #else /* !MZ_SUPPORTED */
606
607 int DOSVM_Enter( CONTEXT86 *context )
608 {
609  ERR_(module)("DOS realmode not supported on this architecture!\n");
610  return -1;
611 }
612
613 void DOSVM_Wait( int read_pipe, HANDLE hObject) {}
614 void DOSVM_PIC_ioport_out( WORD port, BYTE val) {}
615 void DOSVM_SetTimer( unsigned ticks ) {}
616 unsigned DOSVM_GetTimer( void ) { return 0; }
617 void DOSVM_SetSystemData( int id, void *data ) { free(data); }
618 void* DOSVM_GetSystemData( int id ) { return NULL; }
619 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,CONTEXT86*,void*), void *data) {}
620
621 #endif