winedos: Make some data const.
[wine] / dlls / winedos / int2f.c
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2 /*
3  * DOS interrupt 2fh handler
4  *
5  *  Cdrom - device driver emulation - Audio features.
6  *     Copyright (c) 1998 Petr Tomasek <tomasek@etf.cuni.cz>
7  *     Copyright (c) 1999,2002 Eric Pouech
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27
28 #define NONAMELESSUNION
29 #define NONAMELESSSTRUCT
30 #include "ntstatus.h"
31 #define WIN32_NO_STATUS
32 #include "wine/winbase16.h"
33 #include "wine/debug.h"
34 #include "winternl.h"
35 #include "winioctl.h"
36 #include "ntddstor.h"
37 #include "ntddcdrm.h"
38 #include "dosexe.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(int);
41
42 /* base WPROCS.DLL ordinal number for VxDs */
43 #define VXD_BASE 400
44
45 typedef struct
46 {
47     DOS_DEVICE_HEADER hdr;
48
49     WORD  reserved;             /* must be 0 */
50     BYTE  drive;                /* drive letter (0=A:, 1=B:, ...) */
51     BYTE  units;                /* number of supported units */
52 } CDROM_DEVICE_HEADER;
53
54 typedef struct
55 {
56     CDROM_DEVICE_HEADER hdr;
57     WINEDEV_THUNK thunk;
58
59     WORD  cdrom_segment;        /* Real mode segment for CDROM_HEAP */
60     WORD  cdrom_selector;       /* Protected mode selector for CDROM_HEAP */
61 } CDROM_HEAP;
62
63 static void do_int2f_16( CONTEXT86 *context );
64 static void MSCDEX_Handler( CONTEXT86 *context );
65
66 /**********************************************************************
67  *          DOSVM_Int2fHandler (WINEDOS16.147)
68  *
69  * Handler for int 2fh (multiplex).
70  */
71 void WINAPI DOSVM_Int2fHandler( CONTEXT86 *context )
72 {
73     TRACE("Subfunction 0x%X\n", AX_reg(context));
74
75     switch(AH_reg(context))
76     {
77     case 0x10:
78         SET_AL( context, 0xff ); /* share is installed */
79         break;
80
81     case 0x11:  /* Network Redirector / IFSFUNC */
82         switch (LOBYTE(context->Eax))
83         {
84         case 0x00:  /* Install check */
85             /* not installed */
86             break;
87         case 0x80:  /* Enhanced services - Install check */
88             /* not installed */
89             break;
90         default:
91            INT_BARF( context, 0x2f );
92             break;
93         }
94         break;
95
96     case 0x12:
97         switch (LOBYTE(context->Eax))
98         {
99         case 0x2e: /* get or set DOS error table address */
100             switch (DL_reg(context))
101             {
102             /* Four tables: even commands are 'get', odd are 'set' */
103             /* DOS 5.0+ ignores "set" commands */
104             case 0x01:
105             case 0x03:
106             case 0x05:
107             case 0x07:
108             case 0x09:
109                 break;
110             /* Instead of having a message table in DOS-space, */
111             /* we can use a special case for MS-DOS to force   */
112             /* the secondary interface.                               */
113             case 0x00:
114             case 0x02:
115             case 0x04:
116             case 0x06:
117                 context->SegEs = 0x0001;
118                 SET_DI( context, 0x0000 );
119                 break;
120             case 0x08:
121                 FIXME("No real-mode handler for errors yet! (bye!)\n");
122                 break;
123             default:
124                 INT_BARF(context, 0x2f);
125             }
126             break;
127         default:
128            INT_BARF(context, 0x2f);
129         }
130         break;
131
132     case 0x15: /* mscdex */
133         MSCDEX_Handler(context);
134         break;
135
136     case 0x16:
137         do_int2f_16( context );
138         break;
139
140     case 0x1a: /* ANSI.SYS / AVATAR.SYS Install Check */
141         /* Not supported yet, do nothing */
142         break;
143
144     case 0x43:
145 #if 1
146        switch (LOBYTE(context->Eax))
147        {
148        case 0x00:   /* XMS v2+ installation check */
149            WARN("XMS is not fully implemented\n");
150            SET_AL( context, 0x80 );
151            break;
152        case 0x10:   /* XMS v2+ get driver address */
153        {
154             context->SegEs = DOSVM_dpmi_segments->xms_seg;
155             SET_BX( context, 0 );
156             break;
157        }
158        default:
159            INT_BARF( context, 0x2f );
160        }
161 #else
162        FIXME("check for XMS (not supported)\n");
163        SET_AL( context, 0x42 ); /* != 0x80 */
164 #endif
165        break;
166
167     case 0x45:
168        switch (LOBYTE(context->Eax))
169        {
170        case 0x00:
171        case 0x01:
172        case 0x02:
173        case 0x03:
174        case 0x04:
175        case 0x05:
176        case 0x06:
177        case 0x07:
178        case 0x08:
179            /* Microsoft Profiler - not installed */
180            break;
181        default:
182             INT_BARF( context, 0x2f );
183        }
184        break;
185
186     case 0x4a:
187         switch(LOBYTE(context->Eax))
188         {
189        case 0x10:  /* smartdrv */
190            break;  /* not installed */
191         case 0x11:  /* dblspace */
192             break;  /* not installed */
193         case 0x12:  /* realtime compression interface */
194             break;  /* not installed */
195         case 0x32:  /* patch IO.SYS (???) */
196             break;  /* we have no IO.SYS, so we can't patch it :-/ */
197         default:
198             INT_BARF( context, 0x2f );
199         }
200         break;
201     case 0x4b:
202        switch(LOBYTE(context->Eax))
203        {
204        case 0x01:
205        case 0x02:
206        case 0x03:
207        case 0x04:
208        case 0x05:
209            FIXME("Task Switcher - not implemented\n");
210            break;
211        default:
212            INT_BARF( context, 0x2f );
213        }
214        break;
215     case 0x56:  /* INTERLNK */
216        switch(LOBYTE(context->Eax))
217        {
218        case 0x01:  /* check if redirected drive */
219            SET_AL( context, 0 ); /* not redirected */
220            break;
221        default:
222            INT_BARF( context, 0x2f );
223        }
224        break;
225     case 0x7a:  /* NOVELL NetWare */
226         switch (LOBYTE(context->Eax))
227         {
228        case 0x0:  /* Low-level Netware installation check AL=0 not installed.*/
229             SET_AL( context, 0 );
230             break;
231         case 0x20:  /* Get VLM Call Address */
232             /* return nothing -> NetWare not installed */
233             break;
234         default:
235            INT_BARF( context, 0x2f );
236             break;
237         }
238         break;
239     case 0xb7:  /* append */
240         SET_AL( context, 0 ); /* not installed */
241         break;
242     case 0xb8:  /* network */
243         switch (LOBYTE(context->Eax))
244         {
245         case 0x00:  /* Install check */
246             /* not installed */
247             break;
248         default:
249            INT_BARF( context, 0x2f );
250             break;
251         }
252         break;
253     case 0xbc:
254         if (AL_reg(context) == 0x00 && BX_reg(context) == 0x3f3f) {
255             /* MVSOUND.SYS - Install check: not installed */
256         } else { 
257             INT_BARF( context, 0x2f );
258         }
259        break;
260     case 0xbd:  /* some Novell network install check ??? */
261         SET_AX( context, 0xa5a5 ); /* pretend to have Novell IPX installed */
262        break;
263     case 0xbf:  /* REDIRIFS.EXE */
264         switch (LOBYTE(context->Eax))
265         {
266         case 0x00:  /* Install check */
267             /* not installed */
268             break;
269         default:
270            INT_BARF( context, 0x2f );
271             break;
272         }
273         break;
274     case 0xd2:
275        switch(LOBYTE(context->Eax))
276        {
277        case 0x01: /* Quarterdeck RPCI - QEMM/QRAM - PCL-838.EXE functions */
278            if(BX_reg(context) == 0x5145 && CX_reg(context) == 0x4D4D
279              && DX_reg(context) == 0x3432)
280                TRACE("Check for QEMM v5.0+ (not installed)\n");
281                break;
282        default:
283            INT_BARF( context, 0x2f );
284            break;
285        }
286        break;
287     case 0xd7:  /* Banyan Vines */
288         switch (LOBYTE(context->Eax))
289         {
290         case 0x01:  /* Install check - Get Int Number */
291             /* not installed */
292             break;
293         default:
294            INT_BARF( context, 0x2f );
295             break;
296         }
297         break;
298     case 0xde:
299        switch(LOBYTE(context->Eax))
300        {
301        case 0x01:   /* Quarterdeck QDPMI.SYS - DESQview */
302            if(BX_reg(context) == 0x4450 && CX_reg(context) == 0x4d49
303              && DX_reg(context) == 0x8f4f)
304                TRACE("Check for QDPMI.SYS (not installed)\n");
305                break;
306        default:
307            INT_BARF( context, 0x2f );
308            break;
309        }
310        break;
311     case 0xfa:  /* Watcom debugger check, returns 0x666 if installed */
312         break;
313     default:
314         INT_BARF( context, 0x2f );
315         break;
316     }
317 }
318
319
320 /**********************************************************************
321  *         do_int2f_16
322  */
323 static void do_int2f_16( CONTEXT86 *context )
324 {
325     DWORD addr;
326
327     switch(LOBYTE(context->Eax))
328     {
329     case 0x00:  /* Windows enhanced mode installation check */
330         SET_AX( context, (GetWinFlags16() & WF_ENHANCED) ? LOWORD(GetVersion16()) : 0 );
331         break;
332
333     case 0x0a:  /* Get Windows version and type */
334         SET_AX( context, 0 );
335         SET_BX( context, (LOWORD(GetVersion16()) << 8) | (LOWORD(GetVersion16()) >> 8) );
336         SET_CX( context, (GetWinFlags16() & WF_ENHANCED) ? 3 : 2 );
337         break;
338
339     case 0x0b:  /* Identify Windows-aware TSRs */
340         /* we don't have any pre-Windows TSRs */
341         break;
342
343     case 0x11:  /* Get Shell Parameters - (SHELL= in CONFIG.SYS) */
344         /* We can mock this up. But not today... */
345         FIXME("Get Shell Parameters\n");
346         break;
347
348     case 0x80:  /* Release time-slice */
349        /* Linux sched_yield() still keeps burning CPU cycles
350         * if the current process is the only one in highest priority list
351         * (as Linux will immediately return to this process to waste
352         * more CPU cycles), so sched_yield() is essentially useless for us
353         * (poor API, if you ask me: its return code should indicate
354         * whether other processes did run in between, in order for us
355         * to be able to decide whether to do an additional Sleep() or not...)
356         * Thus we better unconditionally use a well-balanced Sleep()
357         * instead to really make sure the process calling int 0x2f/0x1680
358         * *doesn't* use 100% CPU...
359         */
360        Sleep(55); /* just wait 55ms (one "timer tick") for now. */
361        SET_AL( context, 0 );
362         break;
363
364     case 0x81: /* Begin critical section.  */
365         /* FIXME? */
366         break;
367
368     case 0x82: /* End critical section.  */
369         /* FIXME? */
370         break;
371
372     case 0x83:  /* Return Current Virtual Machine ID */
373         /* Virtual Machines are usually created/destroyed when Windows runs
374          * DOS programs. Since we never do, we are always in the System VM.
375          * According to Ralf Brown's Interrupt List, never return 0. But it
376          * seems to work okay (returning 0), just to be sure we return 1.
377          */
378        SET_BX( context, 1 ); /* VM 1 is probably the System VM */
379        break;
380
381     case 0x84:  /* Get device API entry point */
382         {
383             HMODULE16 mod = GetModuleHandle16("wprocs");
384             if (mod < 32) mod = LoadLibrary16( "wprocs" );
385             addr = (DWORD)GetProcAddress16( mod, (LPCSTR)(VXD_BASE + BX_reg(context)) );
386             if (!addr)  /* not supported */
387                 ERR("Accessing unknown VxD %04x - Expect a failure now.\n", BX_reg(context) );
388             context->SegEs = SELECTOROF(addr);
389             SET_DI( context, OFFSETOF(addr) );
390         }
391        break;
392
393     case 0x86:  /* DPMI detect mode */
394         SET_AX( context, 0 );  /* Running under DPMI */
395         break;
396
397     case 0x87: /* DPMI installation check */
398         {
399            SYSTEM_INFO si;
400            GetSystemInfo(&si);
401            SET_AX( context, 0x0000 ); /* DPMI Installed */
402             SET_BX( context, 0x0001 ); /* 32bits available */
403             SET_CL( context, si.wProcessorLevel );
404             SET_DX( context, 0x005a ); /* DPMI major/minor 0.90 */
405             SET_SI( context, 0 );      /* # of para. of DOS extended private data */
406             context->SegEs = DOSVM_dpmi_segments->dpmi_seg;
407             SET_DI( context, 0 );      /* ES:DI is DPMI switch entry point */
408             break;
409         }
410     case 0x8a:  /* DPMI get vendor-specific API entry point. */
411        /* The 1.0 specs say this should work with all 0.9 hosts.  */
412        break;
413
414     default:
415         INT_BARF( context, 0x2f );
416     }
417 }
418
419 /* FIXME: this macro may have to be changed on architectures where <size> reads/writes
420  * must be <size> aligned
421  * <size> could be WORD, DWORD...
422  * in this case, we would need two macros, one for read, the other one for write
423  * Note: PTR_AT can be used as an l-value
424  */
425 #define        PTR_AT(_ptr, _ofs, _typ)        (*((_typ*)(((char*)_ptr)+(_ofs))))
426
427 /* Use #if 1 if you want full int 2f debug... normal users can leave it at 0 */
428 #if 0
429 /**********************************************************************
430  *         MSCDEX_Dump                                 [internal]
431  *
432  * Dumps mscdex requests to int debug channel.
433  */
434 static void    MSCDEX_Dump(char* pfx, BYTE* req, int dorealmode)
435 {
436     int        i;
437     BYTE       buf[2048];
438     BYTE*      ptr;
439     BYTE*      ios;
440
441     ptr = buf;
442     ptr += sprintf(ptr, "%s\tCommand => ", pfx);
443     for (i = 0; i < req[0]; i++) {
444        ptr += sprintf(ptr, "%02x ", req[i]);
445     }
446
447     switch (req[2]) {
448     case 3:
449     case 12:
450        ptr += sprintf(ptr, "\n\t\t\t\tIO_struct => ");
451        ios = (dorealmode) ? PTR_REAL_TO_LIN( PTR_AT(req, 16, WORD), PTR_AT(req, 14, WORD)) :
452                              MapSL(MAKESEGPTR(PTR_AT(req, 16, WORD), PTR_AT(req, 14, WORD)));
453
454        for (i = 0; i < PTR_AT(req, 18, WORD); i++) {
455            ptr += sprintf(ptr, "%02x ", ios[i]);
456            if ((i & 0x1F) == 0x1F) {
457                *ptr++ = '\n';
458                *ptr = 0;
459            }
460        }
461        break;
462     }
463     TRACE("%s\n", buf);
464 }
465 #else
466 #define MSCDEX_Dump(pfx, req, drm)
467 #endif
468
469 #define CDFRAMES_PERSEC                 75
470 #define CDFRAMES_PERMIN                 (CDFRAMES_PERSEC * 60)
471 #define FRAME_OF_ADDR(a)        ((a)[1] * CDFRAMES_PERMIN + (a)[2] * CDFRAMES_PERSEC + (a)[3])
472 #define FRAME_OF_TOC(toc, idx)  FRAME_OF_ADDR((toc).TrackData[idx - (toc).FirstTrack].Address)
473 #define CTRL_OF_TOC(toc, idx)   (((toc).TrackData[idx - (toc).FirstTrack].Control << 4) | \
474                                   (toc).TrackData[idx - (toc).FirstTrack].Adr)
475
476 static void    MSCDEX_StoreMSF(DWORD frame, BYTE* val)
477 {
478     val[3] = 0;        /* zero */
479     val[2] = frame / CDFRAMES_PERMIN; /* minutes */
480     val[1] = (frame / CDFRAMES_PERSEC) % 60; /* seconds */
481     val[0] = frame % CDFRAMES_PERSEC; /* frames */
482 }
483
484 static int is_cdrom( int drive)
485 {
486     char root[] = "A:\\";
487     root[0] += drive;
488     return (GetDriveTypeA(root) == DRIVE_CDROM);
489 }
490
491 /***********************************************************************
492  *           CDROM_FillHeap
493  *
494  * Initialize CDROM heap.
495  *
496  */
497 static void CDROM_FillHeap( CDROM_HEAP *heap )
498 {
499     int drive, count;
500
501     /* Count the number of contiguous CDROM drives
502      */
503     for (drive = count = 0; drive < 26; drive++) {
504         if (is_cdrom(drive)) {
505             while (is_cdrom(drive + count)) count++;
506             break;
507         }
508     }
509     TRACE("Installation check: %d cdroms, starting at %d\n", count, drive);
510     heap->hdr.drive = (drive < 26) ? drive : 0;
511     heap->hdr.units = count;
512     heap->hdr.reserved = 0;
513 }
514
515 /**********************************************************************
516  *         CDROM_GetHeap
517  *
518  * Get pointer for CDROM heap (CDROM_HEAP).
519  * Creates and initializes heap on first call.
520  */
521 static CDROM_HEAP *CDROM_GetHeap( void )
522 {
523     static CDROM_HEAP *heap_pointer = NULL;
524
525     if ( !heap_pointer )
526     {
527         WORD heap_segment;
528         WORD heap_selector;
529
530         /* allocate a new DOS data segment */
531         heap_pointer = DOSVM_AllocDataUMB( sizeof(CDROM_HEAP),
532                                            &heap_segment,
533                                            &heap_selector );
534
535         heap_pointer->cdrom_segment  = heap_segment;
536         heap_pointer->cdrom_selector = heap_selector;
537         CDROM_FillHeap( heap_pointer );
538     }
539
540     return heap_pointer;
541 }
542
543 static void MSCDEX_Request(BYTE *driver_request, BOOL dorealmode)
544 {
545     BYTE*       io_stru;
546     BYTE        Error = 255; /* No Error */
547     char        devName[] = "\\\\.\\@:";
548     HANDLE      h;
549     CDROM_TOC                   toc;
550     CDROM_SUB_Q_DATA_FORMAT     fmt;
551     SUB_Q_CHANNEL_DATA          data;
552     DWORD                       br;
553     DWORD                       present = TRUE;
554
555     /* FIXME
556      * the following tests are wrong because lots of functions don't require the
557      * tray to be closed with a CD inside
558      */
559     TRACE("CDROM device driver -> command <%d>\n", (unsigned char)driver_request[2]);
560
561     MSCDEX_Dump("Beg", driver_request, dorealmode);
562
563     /* set status to 0 */
564     PTR_AT(driver_request, 3, WORD) = 0;
565     devName[4] = 'A' + CDROM_GetHeap()->hdr.drive + driver_request[1];
566     h = CreateFileA(devName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
567     if (!h) {
568         WARN("Couldn't open cdrom handle\n");
569         driver_request[4] |= 0x80;
570         driver_request[3] = 1;  /* unknown unit */
571         return;
572     }
573
574     fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
575     if (!DeviceIoControl(h, IOCTL_CDROM_READ_TOC, NULL, 0, &toc, sizeof(toc), &br, NULL) ||
576         !DeviceIoControl(h, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
577                          &data, sizeof(data), &br, NULL)) {
578         if (GetLastError() == STATUS_NO_MEDIA_IN_DEVICE)
579         {
580             if (driver_request[2] != 6 && driver_request[2] != 15)
581             {
582                 driver_request[4] |= 0x80;
583                 driver_request[3] = 2; /* drive not ready */
584                 CloseHandle(h);
585                 return;
586             }
587             present = FALSE;
588         }
589         else
590         {
591             driver_request[4] |= 0x80;
592             driver_request[3] = 1;     /* unknown unit */
593             CloseHandle(h);
594             return;
595         }
596     }
597
598     switch (driver_request[2]) {
599     case 3:
600         io_stru = (dorealmode) ?
601             PTR_REAL_TO_LIN( PTR_AT(driver_request, 16, WORD), PTR_AT(driver_request, 14, WORD) ) :
602             MapSL( MAKESEGPTR(PTR_AT(driver_request, 16, WORD), PTR_AT(driver_request, 14, WORD)));
603
604         TRACE(" --> IOCTL INPUT <%d>\n", io_stru[0]);
605         switch (io_stru[0]) {
606 #if 0
607         case 0: /* Get device Header */
608         {
609             static  LPSTR ptr = 0;
610             if (ptr == 0)   {
611                 ptr = SEGPTR_ALLOC(22);
612                 PTR_AT(ptr,  0, DWORD) = ~1;        /* Next Device Driver */
613                 PTR_AT(ptr,  4,  WORD) = 0xC800;    /* Device attributes  */
614                 PTR_AT(ptr,  6,  WORD) = 0x1234;    /* Pointer to device strategy routine: FIXME */
615                 PTR_AT(ptr,  8,  WORD) = 0x3142;    /* Pointer to device interrupt routine: FIXME */
616                 PTR_AT(ptr, 10,  char) = 'W';       /* 8-byte character device name field */
617                 PTR_AT(ptr, 11,  char) = 'I';
618                 PTR_AT(ptr, 12,  char) = 'N';
619                 PTR_AT(ptr, 13,  char) = 'E';
620                 PTR_AT(ptr, 14,  char) = '_';
621                 PTR_AT(ptr, 15,  char) = 'C';
622                 PTR_AT(ptr, 16,  char) = 'D';
623                 PTR_AT(ptr, 17,  char) = '_';
624                 PTR_AT(ptr, 18,  WORD) = 0;         /* Reserved (must be zero) */
625                 PTR_AT(ptr, 20,  BYTE) = 0;         /* Drive letter (must be zero) */
626                 PTR_AT(ptr, 21,  BYTE) = 1;         /* Number of units supported (one or more) FIXME*/
627             }
628             PTR_AT(io_stru, DWORD,  0) = SEGPTR_GET(ptr);
629         }
630         break;
631 #endif
632
633         case 1: /* location of head */
634             switch (io_stru[1]) {
635             case 0:
636                 PTR_AT(io_stru, 2, DWORD) =
637                     FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress);
638                 break;
639             case 1:
640                 MSCDEX_StoreMSF(FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress),
641                                 io_stru + 2);
642                 break;
643             default:
644                 ERR("CD-ROM driver: unsupported addressing mode !!\n");
645                 Error = 0x0c;
646             }
647             TRACE(" ----> HEAD LOCATION <%d>\n", PTR_AT(io_stru, 2, DWORD));
648             break;
649
650         case 4: /* Audio channel info */
651             io_stru[1] = 0;
652             io_stru[2] = 0xff;
653             io_stru[3] = 1;
654             io_stru[4] = 0xff;
655             io_stru[5] = 2;
656             io_stru[6] = 0;
657             io_stru[7] = 3;
658             io_stru[8] = 0;
659             TRACE(" ----> AUDIO CHANNEL INFO\n");
660             break;
661
662         case 6: /* device status */
663             PTR_AT(io_stru, 1, DWORD) = 0x00000290;
664             /* 290 =>
665              * 1        Supports HSG and Red Book addressing modes
666              * 0        Supports audio channel manipulation
667              *
668              * 1        Supports prefetching requests
669              * 0        Reserved
670              * 0        No interleaving
671              * 1        Data read and plays audio/video tracks
672              *
673              * 0        Read only
674              * 0        Supports only cooked reading
675              * 0        Door locked
676              * 0        see below (Door closed/opened)
677              */
678             if (!present) PTR_AT(io_stru, 1, DWORD) |= 1;
679             TRACE(" ----> DEVICE STATUS <0x%08x>\n", PTR_AT(io_stru, 1, DWORD));
680             break;
681
682         case 8: /* Volume size */
683             PTR_AT(io_stru, 1, DWORD) = FRAME_OF_TOC(toc, toc.LastTrack + 1) -
684                 FRAME_OF_TOC(toc, toc.FirstTrack) - 1;
685             TRACE(" ----> VOLUME SIZE <%d>\n", PTR_AT(io_stru, 1, DWORD));
686             break;
687
688         case 9: /* media changed ? */
689             /* answers don't know... -1/1 for yes/no would be better */
690             io_stru[1] = 0; /* FIXME? 1? */
691             TRACE(" ----> MEDIA CHANGED <%d>\n", io_stru[1]);
692             break;
693
694         case 10: /* audio disk info */
695             io_stru[1] = toc.FirstTrack; /* starting track of the disc */
696             io_stru[2] = toc.LastTrack;  /* ending track */
697             MSCDEX_StoreMSF(FRAME_OF_TOC(toc, toc.LastTrack + 1) -
698                             FRAME_OF_TOC(toc, toc.FirstTrack) - 1, io_stru + 3);
699
700             TRACE(" ----> AUDIO DISK INFO <%d-%d/%08x>\n",
701                   io_stru[1], io_stru[2], PTR_AT(io_stru, 3, DWORD));
702             break;
703
704         case 11: /* audio track info */
705             if (io_stru[1] >= toc.FirstTrack && io_stru[1] <= toc.LastTrack) {
706                 MSCDEX_StoreMSF(FRAME_OF_TOC(toc, io_stru[1]), io_stru + 2);
707                 /* starting point if the track */
708                 io_stru[6] = CTRL_OF_TOC(toc, io_stru[1]);
709             } else {
710                 PTR_AT(io_stru, 2, DWORD) = 0;
711                 io_stru[6] = 0;
712             }
713             TRACE(" ----> AUDIO TRACK INFO[%d] = [%08x:%d]\n",
714                   io_stru[1], PTR_AT(io_stru, 2, DWORD), io_stru[6]);
715             break;
716
717         case 12: /* get Q-Channel info */
718             io_stru[1] = CTRL_OF_TOC(toc, data.CurrentPosition.TrackNumber);
719             io_stru[2] = data.CurrentPosition.TrackNumber;
720             io_stru[3] = 0; /* FIXME ?? */
721
722             /* why the heck did MS use another format for 0MSF information... sigh */
723             {
724                 BYTE    bTmp[4];
725
726                 MSCDEX_StoreMSF(FRAME_OF_ADDR(data.CurrentPosition.TrackRelativeAddress), bTmp);
727                 io_stru[ 4] = bTmp[2];
728                 io_stru[ 5] = bTmp[1];
729                 io_stru[ 6] = bTmp[0];
730                 io_stru[ 7] = 0;
731
732                 MSCDEX_StoreMSF(FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress), bTmp);
733                 io_stru[ 8] = bTmp[2];
734                 io_stru[ 9] = bTmp[1];
735                 io_stru[10] = bTmp[0];
736                 io_stru[11] = 0;
737             }
738             TRACE("Q-Channel info: Ctrl/adr=%02x TNO=%02x X=%02x rtt=%02x:%02x:%02x rtd=%02x:%02x:%02x (cf=%08x, tp=%08x)\n",
739                   io_stru[ 1], io_stru[ 2], io_stru[ 3],
740                   io_stru[ 4], io_stru[ 5], io_stru[ 6],
741                   io_stru[ 8], io_stru[ 9], io_stru[10],
742                   FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress),
743                   FRAME_OF_TOC(toc, data.CurrentPosition.TrackNumber));
744             break;
745
746         case 15: /* Audio status info */
747             /* !!!! FIXME FIXME FIXME !! */
748             PTR_AT(io_stru, 1,  WORD) = 2 | ((data.CurrentPosition.Header.AudioStatus == AUDIO_STATUS_PAUSED) ? 1 : 0);
749             if (!present) {
750                 PTR_AT(io_stru, 3, DWORD) = 0;
751                 PTR_AT(io_stru, 7, DWORD) = 0;
752             } else {
753                 PTR_AT(io_stru, 3, DWORD) = FRAME_OF_TOC(toc, toc.FirstTrack);
754                 PTR_AT(io_stru, 7, DWORD) = FRAME_OF_TOC(toc, toc.LastTrack + 1);
755             }
756             TRACE("Audio status info: status=%04x startLoc=%d endLoc=%d\n",
757                   PTR_AT(io_stru, 1, WORD), PTR_AT(io_stru, 3, DWORD), PTR_AT(io_stru, 7, DWORD));
758             break;
759
760         default:
761             FIXME("IOCTL INPUT: Unimplemented <%d>!!\n", io_stru[0]);
762             Error = 0x0c;
763             break;
764         }
765         break;
766
767     case 12:
768         io_stru = (dorealmode) ?
769             PTR_REAL_TO_LIN( PTR_AT(driver_request, 16, WORD), PTR_AT(driver_request, 14, WORD)) :
770             MapSL( MAKESEGPTR(PTR_AT(driver_request, 16, WORD), PTR_AT(driver_request, 14, WORD)));
771
772         TRACE(" --> IOCTL OUTPUT <%d>\n", io_stru[0]);
773         switch (io_stru[0]) {
774         case 0: /* eject */
775             DeviceIoControl(h, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &br, NULL);
776             TRACE(" ----> EJECT\n");
777             break;
778         case 2: /* reset drive */
779             DeviceIoControl(h, IOCTL_STORAGE_RESET_DEVICE, NULL, 0, NULL, 0, &br, NULL);
780             TRACE(" ----> RESET\n");
781             break;
782         case 3: /* Audio Channel Control */
783             FIXME(" ----> AUDIO CHANNEL CONTROL (NIY)\n");
784             break;
785         case 5: /* close tray */
786             DeviceIoControl(h, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &br, NULL);
787             TRACE(" ----> CLOSE TRAY\n");
788             break;
789         default:
790             FIXME(" IOCTL OUTPUT: Unimplemented <%d>!!\n", io_stru[0]);
791             Error = 0x0c;
792             break;
793         }
794         break;
795
796     case 128: /* read long */
797     {
798         LPVOID              dst = MapSL(MAKESEGPTR(PTR_AT(driver_request, 16, WORD),
799                                                    PTR_AT(driver_request, 14, WORD)));
800         DWORD               at = PTR_AT(driver_request, 20, DWORD);
801         WORD                num = PTR_AT(driver_request, 18, WORD);
802         RAW_READ_INFO       rri;
803
804         switch (driver_request[13]) {
805         case 1: /* Red book addressing mode = 0:m:s:f */
806             /* FIXME : frame <=> msf conversion routines could be shared
807              * between mscdex and mcicda
808              */
809             at = LOBYTE(HIWORD(at)) * CDFRAMES_PERMIN +
810                 HIBYTE(LOWORD(at)) * CDFRAMES_PERSEC +
811                 LOBYTE(LOWORD(at));
812             /* fall through */
813         case 0: /* HSG addressing mode */
814             switch (PTR_AT(driver_request, 24, BYTE))
815             {
816             case 0: /* cooked */
817                 ReadFile(h, dst, num * 2048, &br, NULL);
818                 break;
819             case 1:
820                 /* FIXME: computation is wrong */
821                 rri.DiskOffset.u.HighPart = 0;
822                 rri.DiskOffset.u.LowPart = at << 11;
823                 rri.TrackMode = YellowMode2;
824                 rri.SectorCount = num;
825                 DeviceIoControl(h, IOCTL_CDROM_RAW_READ, &rri, sizeof(rri),
826                                 dst, num * 2352, &br, NULL);
827                 break;
828             default:
829                 ERR("Unsupported read mode !!\n");
830                 Error = 0x0c;
831                 break;
832             }
833             break;
834         default:
835             ERR("Unsupported address mode !!\n");
836             Error = 0x0c;
837             break;
838         }
839     }
840     break;
841
842     case 131: /* seek */
843     {
844         DWORD                       at;
845         CDROM_SEEK_AUDIO_MSF        seek;
846
847         at = PTR_AT(driver_request, 20, DWORD);
848
849         TRACE(" --> SEEK AUDIO mode :<0x%02X>, [%d]\n",
850               (BYTE)driver_request[13], at);
851
852         switch (driver_request[13]) {
853         case 1: /* Red book addressing mode = 0:m:s:f */
854             /* FIXME : frame <=> msf conversion routines could be shared
855              * between mscdex and mcicda
856              */
857             at = LOBYTE(HIWORD(at)) * CDFRAMES_PERMIN +
858                 HIBYTE(LOWORD(at)) * CDFRAMES_PERSEC +
859                 LOBYTE(LOWORD(at));
860             /* fall through */
861         case 0: /* HSG addressing mode */
862             seek.M = at / CDFRAMES_PERMIN;
863             seek.S = (at / CDFRAMES_PERSEC) % 60;
864             seek.F = at % CDFRAMES_PERSEC;
865             DeviceIoControl(h, IOCTL_CDROM_SEEK_AUDIO_MSF, &seek, sizeof(seek),
866                             NULL, 0, &br, NULL);
867             break;
868         default:
869             ERR("Unsupported address mode !!\n");
870             Error = 0x0c;
871             break;
872         }
873     }
874     break;
875
876     case 132: /* play */
877     {
878         DWORD                       beg, end;
879         CDROM_PLAY_AUDIO_MSF        play;
880
881         beg = end = PTR_AT(driver_request, 14, DWORD);
882         end += PTR_AT(driver_request, 18, DWORD);
883
884         TRACE(" --> PLAY AUDIO mode :<0x%02X>, [%d-%d]\n",
885               (BYTE)driver_request[13], beg, end);
886
887         switch (driver_request[13]) {
888         case 1:
889             /* Red book addressing mode = 0:m:s:f */
890             /* FIXME : frame <=> msf conversion routines could be shared
891              * between mscdex and mcicda
892              */
893             beg = LOBYTE(LOWORD(beg)) * CDFRAMES_PERMIN +
894                 HIBYTE(LOWORD(beg)) * CDFRAMES_PERSEC +
895                 LOBYTE(HIWORD(beg));
896             end = LOBYTE(LOWORD(end)) * CDFRAMES_PERMIN +
897                 HIBYTE(LOWORD(end)) * CDFRAMES_PERSEC +
898                 LOBYTE(HIWORD(end));
899             /* fall through */
900         case 0: /* HSG addressing mode */
901             play.StartingM = beg / CDFRAMES_PERMIN;
902             play.StartingS = (beg / CDFRAMES_PERSEC) % 60;
903             play.StartingF = beg % CDFRAMES_PERSEC;
904             play.EndingM   = end / CDFRAMES_PERMIN;
905             play.EndingS   = (end / CDFRAMES_PERSEC) % 60;
906             play.EndingF   = end % CDFRAMES_PERSEC;
907             DeviceIoControl(h, IOCTL_CDROM_PLAY_AUDIO_MSF, &play, sizeof(play),
908                             NULL, 0, &br, NULL);
909             break;
910         default:
911             ERR("Unsupported address mode !!\n");
912             Error = 0x0c;
913             break;
914         }
915     }
916     break;
917
918     case 133:
919         if (data.CurrentPosition.Header.AudioStatus == AUDIO_STATUS_IN_PROGRESS) {
920             DeviceIoControl(h, IOCTL_CDROM_PAUSE_AUDIO, NULL, 0, NULL, 0, &br, NULL);
921             TRACE(" --> STOP AUDIO (Paused)\n");
922         } else {
923             DeviceIoControl(h, IOCTL_CDROM_STOP_AUDIO, NULL, 0, NULL, 0, &br, NULL);
924             TRACE(" --> STOP AUDIO (Stopped)\n");
925         }
926         break;
927
928     case 136:
929         TRACE(" --> RESUME AUDIO\n");
930         DeviceIoControl(h, IOCTL_CDROM_PAUSE_AUDIO, NULL, 0, NULL, 0, &br, NULL);
931         break;
932
933     default:
934         FIXME(" ioctl unimplemented <%d>\n", driver_request[2]);
935         Error = 0x0c;
936     }
937
938     /* setting error codes if any */
939     if (Error < 255) {
940         driver_request[4] |= 0x80;
941         driver_request[3] = Error;
942     }
943
944     CloseHandle(h);
945     /* setting status bits
946      * 3 == playing && done
947      * 1 == done
948      */
949     driver_request[4] |=
950         (data.CurrentPosition.Header.AudioStatus == AUDIO_STATUS_IN_PROGRESS) ? 3 : 1;
951
952     MSCDEX_Dump("End", driver_request, dorealmode);
953 }
954
955 static void MSCDEX_Handler(CONTEXT86* context)
956 {
957     int        drive, count;
958     char*      p;
959
960     switch (LOBYTE(context->Eax)) {
961     case 0x00: /* Installation check */
962        /* Count the number of contiguous CDROM drives
963         */
964        for (drive = count = 0; drive < 26; drive++) {
965            if (is_cdrom(drive)) {
966                while (is_cdrom(drive + count)) count++;
967                break;
968            }
969        }
970        TRACE("Installation check: %d cdroms, starting at %d\n", count, drive);
971        SET_BX( context, count );
972        SET_CX( context, (drive < 26) ? drive : 0 );
973        break;
974
975     case 0x01: /* get drive device list */
976        {
977            CDROM_HEAP*          cdrom_heap = CDROM_GetHeap();
978            CDROM_DEVICE_HEADER* dev = &cdrom_heap->hdr;
979            SEGPTR ptr_dev = ISV86(context)
980                ? MAKESEGPTR( cdrom_heap->cdrom_segment,
981                              FIELD_OFFSET(CDROM_HEAP, hdr) )
982                : MAKESEGPTR( cdrom_heap->cdrom_selector,
983                              FIELD_OFFSET(CDROM_HEAP, hdr) );
984
985            p = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Ebx);
986            for (drive = 0; drive < dev->units; drive++) {
987                *p = drive; /* subunit */
988                ++p;
989                *(DWORD*)p = ptr_dev;
990                p += sizeof(DWORD);
991            }
992            TRACE("Get drive device list\n");
993        }
994        break;
995
996     case 0x0B: /* drive check */
997        SET_AX( context, is_cdrom(CX_reg(context)) );
998        SET_BX( context, 0xADAD );
999        break;
1000
1001     case 0x0C: /* get version */
1002        SET_BX( context, 0x020a );
1003        TRACE("Version number => %04x\n", BX_reg(context));
1004        break;
1005
1006     case 0x0D: /* get drive letters */
1007        p = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Ebx);
1008        memset(p, 0, 26);
1009        for (drive = 0; drive < 26; drive++) {
1010            if (is_cdrom(drive)) *p++ = drive;
1011        }
1012        TRACE("Get drive letters\n");
1013        break;
1014
1015     case 0x10: /* direct driver access */
1016        {
1017            BYTE*       driver_request;
1018            CDROM_HEAP* cdrom_heap = CDROM_GetHeap();
1019
1020            if (!is_cdrom(CX_reg(context))) {
1021                WARN("Request made doesn't match a CD ROM drive (%d)\n", CX_reg(context));
1022                SET_CFLAG( context );
1023                SET_AX( context, 0x000f ); /* invalid drive */
1024                return;
1025            }
1026
1027            driver_request = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Ebx);
1028
1029            if (!driver_request) {
1030                /* FIXME - to be deleted ?? */
1031                ERR("ES:BX==0 ! SEGFAULT ?\n");
1032                ERR("-->BX=0x%04x, ES=0x%04x, DS=0x%04x, CX=0x%04x\n",
1033                    BX_reg(context), context->SegEs, context->SegDs, CX_reg(context));
1034                driver_request[4] |= 0x80;
1035                driver_request[3] = 5;  /* bad request length */
1036                return;
1037            }
1038
1039            driver_request[1] = CX_reg(context) - cdrom_heap->hdr.drive;
1040            MSCDEX_Request(driver_request, ISV86(context));
1041        }
1042        break;
1043     default:
1044        FIXME("Unimplemented MSCDEX function 0x%02X.\n", LOBYTE(context->Eax));
1045        break;
1046     }
1047 }
1048
1049 /* prototypes */
1050 static void WINAPI cdrom_strategy(CONTEXT86*ctx);
1051 static void WINAPI cdrom_interrupt(CONTEXT86*ctx);
1052
1053 /* device info */
1054 static const WINEDEV cdromdev =
1055 {
1056     "WINE_CD_",
1057     ATTR_CHAR|ATTR_REMOVABLE|ATTR_IOCTL,
1058     cdrom_strategy, cdrom_interrupt
1059 };
1060
1061 static REQUEST_HEADER *cdrom_driver_request;
1062
1063 /* Return to caller */
1064 static void do_lret(CONTEXT86*ctx)
1065 {
1066     WORD *stack = CTX_SEG_OFF_TO_LIN(ctx, ctx->SegSs, ctx->Esp);
1067
1068     ctx->Eip   = *(stack++);
1069     ctx->SegCs = *(stack++);
1070     ctx->Esp  += 2*sizeof(WORD);
1071 }
1072
1073 static void WINAPI cdrom_strategy(CONTEXT86*ctx)
1074 {
1075     cdrom_driver_request = CTX_SEG_OFF_TO_LIN(ctx, ctx->SegEs, ctx->Ebx);
1076     do_lret( ctx );
1077 }
1078
1079 static void WINAPI cdrom_interrupt(CONTEXT86*ctx)
1080 {
1081     if (cdrom_driver_request->unit > CDROM_GetHeap()->hdr.units)
1082         cdrom_driver_request->status = STAT_ERROR | 1; /* unknown unit */
1083     else
1084         MSCDEX_Request((BYTE*)cdrom_driver_request, ISV86(ctx));
1085
1086     do_lret( ctx );
1087 }
1088
1089 /**********************************************************************
1090  *         MSCDEX_InstallCDROM  [internal]
1091  *
1092  * Install the CDROM driver into the DOS device driver chain.
1093  */
1094 void MSCDEX_InstallCDROM(void)
1095 {
1096     CDROM_HEAP *cdrom_heap = CDROM_GetHeap();
1097
1098     DOSDEV_SetupDevice( &cdromdev,
1099                         cdrom_heap->cdrom_segment,
1100                         FIELD_OFFSET(CDROM_HEAP, hdr),
1101                         FIELD_OFFSET(CDROM_HEAP, thunk) );
1102 }