update DI disc code
[libogc.git] / libdi / di.c
blobde6139543835bcbf5697818f0b21e8e6fd641a06
1 /*-------------------------------------------------------------
3 di.c -- Drive Interface library
5 Team Twiizers
6 Copyright (C) 2008
8 Erant
9 marcan
11 This software is provided 'as-is', without any express or implied
12 warranty. In no event will the authors be held liable for any
13 damages arising from the use of this software.
15 Permission is granted to anyone to use this software for any
16 purpose, including commercial applications, and to alter it and
17 redistribute it freely, subject to the following restrictions:
19 1. The origin of this software must not be misrepresented; you
20 must not claim that you wrote the original software. If you use
21 this software in a product, an acknowledgment in the product
22 documentation would be appreciated but is not required.
24 2. Altered source versions must be plainly marked as such, and
25 must not be misrepresented as being the original software.
27 3. This notice may not be removed or altered from any source
28 distribution.
30 -------------------------------------------------------------*/
32 #include <errno.h>
33 #include <string.h>
35 #include <di/di.h>
36 #include <ogc/ipc.h>
37 #include <ogc/ios.h>
38 #include <ogc/mutex.h>
39 #include <ogc/lwp_watchdog.h>
40 #include <unistd.h>
42 #define MOUNT_TIMEOUT 15000 // 15 seconds
44 int di_fd = -1;
45 static bool load_dvdx = true;
46 static int state = DVD_INIT | DVD_NO_DISC;
48 static int _cover_callback(int ret, void* usrdata);
50 static unsigned int bufferMutex = 0;
51 static uint32_t outbuf[8] __attribute__((aligned(32)));
52 static uint32_t dic[8] __attribute__((aligned(32)));
53 static const char di_path[] ATTRIBUTE_ALIGN(32) = "/dev/di";
55 static read_func DI_ReadDVDptr = NULL;
56 static read_func_async DI_ReadDVDAsyncptr = NULL;
57 static di_callback di_cb = NULL;
59 static int _DI_ReadDVD_A8_Async(void* buf, uint32_t len, uint32_t lba, ipccallback ipc_cb){
60 int ret;
62 if(!buf)
63 return -EINVAL;
65 if((uint32_t)buf & 0x1F) // This only works with 32 byte aligned addresses!
66 return -EFAULT;
68 dic[0] = DVD_READ_UNENCRYPTED << 24;
69 dic[1] = len << 11; // 1 LB is 2048 bytes
70 dic[2] = lba << 9; // Nintendo's read function uses byteOffset >> 2, so we only shift 9 left, not 11.
72 ret = IOS_IoctlAsync(di_fd, DVD_READ_UNENCRYPTED, dic, 0x20, buf, len << 11,ipc_cb, buf);
74 if(ret == 2)
75 ret = EIO;
77 return (ret == 1)? 0 : -ret;
80 static int _DI_ReadDVD_D0_Async(void* buf, uint32_t len, uint32_t lba, ipccallback ipc_cb){
81 int ret;
83 if(!buf)
84 return -EINVAL;
86 if((uint32_t)buf & 0x1F)
87 return -EFAULT;
89 dic[0] = DVD_READ << 24;
90 dic[1] = 0; // Unknown what this does as of now. (Sets some value to 0x10 in the drive if set).
91 dic[2] = 0; // USE_DEFAULT_CONFIG flag. Drive will use default config if this bit is set.
92 dic[3] = len;
93 dic[4] = lba;
95 ret = IOS_IoctlAsync(di_fd, DVD_READ, dic, 0x20, buf, len << 11,ipc_cb, buf);
97 if(ret == 2)
98 ret = EIO;
100 return (ret == 1)? 0 : -ret;
103 static int _DI_ReadDVD_A8(void* buf, uint32_t len, uint32_t lba){
104 int ret, retry_count = LIBDI_MAX_RETRIES;
106 if(!buf)
107 return -EINVAL;
109 if((uint32_t)buf & 0x1F) // This only works with 32 byte aligned addresses!
110 return -EFAULT;
112 dic[0] = DVD_READ_UNENCRYPTED << 24;
113 dic[1] = len << 11; // 1 LB is 2048 bytes
114 dic[2] = lba << 9; // Nintendo's read function uses byteOffset >> 2, so we only shift 9 left, not 11.
116 do{
117 ret = IOS_Ioctl(di_fd, DVD_READ_UNENCRYPTED, dic, 0x20, buf, len << 11);
118 retry_count--;
119 }while(ret != 1 && retry_count > 0);
121 if(ret == 2)
122 ret = EIO;
124 return (ret == 1)? 0 : -ret;
127 static int _DI_ReadDVD_D0(void* buf, uint32_t len, uint32_t lba){
128 int ret, retry_count = LIBDI_MAX_RETRIES;
130 if(!buf)
131 return -EINVAL;
133 if((uint32_t)buf & 0x1F)
134 return -EFAULT;
136 dic[0] = DVD_READ << 24;
137 dic[1] = 0; // Unknown what this does as of now. (Sets some value to 0x10 in the drive if set).
138 dic[2] = 0; // USE_DEFAULT_CONFIG flag. Drive will use default config if this bit is set.
139 dic[3] = len;
140 dic[4] = lba;
142 do{
143 ret = IOS_Ioctl(di_fd, DVD_READ, dic, 0x20, buf, len << 11);
144 retry_count--;
145 }while(ret != 1 && retry_count > 0);
147 if(ret == 2)
148 ret = EIO;
150 return (ret == 1)? 0 : -ret;
154 Initialize the DI interface, should always be called first!
157 s32 __DI_StubLaunch(void);
159 int DI_Init() {
160 if(di_fd >= 0)
161 return 1;
163 state = DVD_INIT | DVD_NO_DISC;
165 if(load_dvdx){
166 int res = __DI_StubLaunch(); // Marcan's 1337 magics happen here!
168 if (res < 0)
169 return res;
172 if (di_fd < 0)
173 di_fd = IOS_Open(di_path, 2);
175 if (di_fd < 0)
176 return di_fd;
178 if (!bufferMutex)
179 LWP_MutexInit(&bufferMutex, false);
181 return 0;
184 void DI_LoadDVDX(bool load){
185 load_dvdx = load;
188 void DI_Mount() {
189 if(di_fd < 0)
190 return;
192 uint32_t status;
194 if (DI_GetCoverRegister(&status) != 0) {
195 state = DVD_NO_DISC;
196 return;
199 if ((status & DVD_COVER_DISC_INSERTED) == 0) {
200 state = DVD_NO_DISC;
201 return;
204 state = DVD_INIT | DVD_NO_DISC;
205 _cover_callback(1, NULL); // Initialize the callback chain.
208 void DI_Close(){
209 if(di_fd < 0)
210 return;
212 if (di_fd > 0)
213 IOS_Close(di_fd);
215 di_fd = -1;
217 DI_ReadDVDptr = NULL;
218 DI_ReadDVDAsyncptr = NULL;
219 state = DVD_INIT | DVD_NO_DISC;
221 if (bufferMutex) {
222 LWP_MutexDestroy(bufferMutex);
223 bufferMutex = 0;
227 static int _DI_ReadDVD_Check(void* buf, uint32_t len, uint32_t lba)
229 int ret;
231 ret = _DI_ReadDVD_D0(buf, len, lba);
232 if (ret == 0)
234 state = state | DVD_D0;
235 DI_ReadDVDptr = _DI_ReadDVD_D0;
236 DI_ReadDVDAsyncptr = _DI_ReadDVD_D0_Async;
237 return ret;
239 ret = _DI_ReadDVD_A8(buf, len, lba);
240 if (ret == 0)
242 state = state | DVD_A8;
243 DI_ReadDVDptr = _DI_ReadDVD_A8;
244 DI_ReadDVDAsyncptr = _DI_ReadDVD_A8_Async;
245 return ret;
247 return ret;
250 static int _DI_ReadDVD_Check_Async(void* buf, uint32_t len, uint32_t lba, ipccallback ipc_cb)
252 int ret;
254 ret = _DI_ReadDVD_D0_Async(buf, len, lba, ipc_cb);
255 if (ret == 0)
257 state = state | DVD_D0;
258 DI_ReadDVDptr = _DI_ReadDVD_D0;
259 DI_ReadDVDAsyncptr = _DI_ReadDVD_D0_Async;
260 return ret;
262 ret = _DI_ReadDVD_A8_Async(buf, len, lba, ipc_cb);
263 if (ret == 0)
265 state = state | DVD_A8;
266 DI_ReadDVDptr = _DI_ReadDVD_A8;
267 DI_ReadDVDAsyncptr = _DI_ReadDVD_A8_Async;
268 return ret;
270 return ret;
273 static void _DI_SetCallback(int ioctl_nr, ipccallback ipc_cb){
274 if ((di_fd < 0) || !ipc_cb)
275 return;
277 while(LWP_MutexLock(bufferMutex));
279 memset(dic, 0x00, sizeof(dic));
281 dic[0] = ioctl_nr << 24;
282 dic[1] = (ioctl_nr == DVD_RESET)? 1 : 0; // For reset callback. Dirty, I know...
284 IOS_IoctlAsync(di_fd,ioctl_nr, dic, 0x20, outbuf, 0x20, ipc_cb, outbuf);
286 LWP_MutexUnlock(bufferMutex);
289 #define COVER_CLOSED (*((uint32_t*)usrdata) & DVD_COVER_DISC_INSERTED)
291 static int _cover_callback(int ret, void* usrdata){
292 static int cur_state = 0;
293 static int retry_count = LIBDI_MAX_RETRIES;
294 const int callback_table[] = {
295 DVD_GETCOVER,
296 DVD_WAITFORCOVERCLOSE,
297 DVD_RESET,
298 DVD_IDENTIFY, // This one will complete when the drive is ready.
299 DVD_READ_DISCID,
301 const int return_table[] = {1,1,4,1,1};
303 if(cur_state > 1)
304 state &= ~DVD_NO_DISC;
306 if(callback_table[cur_state]){
307 if(ret == return_table[cur_state]){
308 if(cur_state == 1 && COVER_CLOSED) // Disc inside, skipping wait for cover.
309 cur_state += 2;
310 else
311 cur_state++; // If the previous callback succeeded, moving on to the next
313 retry_count = LIBDI_MAX_RETRIES;
315 else
317 retry_count--;
318 if(retry_count < 0){ // Drive init failed for unknown reasons.
319 retry_count = LIBDI_MAX_RETRIES;
320 cur_state = 0;
321 state = DVD_UNKNOWN;
322 return 0;
325 _DI_SetCallback(callback_table[cur_state - 1], _cover_callback);
328 else // Callback chain has completed OK. The drive is ready.
330 state = DVD_READY;
331 DI_ReadDVDptr = _DI_ReadDVD_Check;
332 DI_ReadDVDAsyncptr = _DI_ReadDVD_Check_Async;
335 if(di_cb)
336 di_cb(state,0);
338 retry_count = LIBDI_MAX_RETRIES;
339 cur_state = 0;
341 return 0;
344 /* Get current status, will return the API status */
345 int DI_GetStatus(){
346 return state;
349 void DI_SetInitCallback(di_callback cb){
350 di_cb = cb;
354 Request an identification from the drive, returned in a DI_DriveID struct
356 int DI_Identify(DI_DriveID* id){
357 if(di_fd < 0)
358 return -ENXIO;
360 if(!id)
361 return -EINVAL;
363 while(LWP_MutexLock(bufferMutex));
365 dic[0] = DVD_IDENTIFY << 24;
367 int ret = IOS_Ioctl(di_fd, DVD_IDENTIFY, dic, 0x20, outbuf, 0x20);
369 if(ret == 2)
370 ret = EIO;
372 memcpy(id,outbuf,sizeof(DI_DriveID));
374 LWP_MutexUnlock(bufferMutex);
375 return (ret == 1)? 0 : -ret;
379 Returns the current error code on the drive.
380 yagcd has a pretty comprehensive list of possible error codes
382 int DI_GetError(uint32_t* error){
383 if(di_fd < 0)
384 return -ENXIO;
386 if(!error)
387 return -EINVAL;
389 while(LWP_MutexLock(bufferMutex));
391 dic[0] = DVD_GET_ERROR << 24;
393 int ret = IOS_Ioctl(di_fd, DVD_GET_ERROR, dic, 0x20, outbuf, 0x20);
395 if(ret == 2)
396 ret = EIO;
398 *error = outbuf[0]; // Error code is returned as an int in the first four bytes of outbuf.
400 LWP_MutexUnlock(bufferMutex);
401 return (ret == 1)? 0 : -ret;
405 Reset the drive.
407 int DI_Reset(){
408 if(di_fd < 0)
409 return -ENXIO;
411 while(LWP_MutexLock(bufferMutex));
413 dic[0] = DVD_RESET << 24;
414 dic[1] = 1;
416 int ret = IOS_Ioctl(di_fd, DVD_RESET, dic, 0x20, outbuf, 0x20);
418 if(ret == 2)
419 ret = EIO;
421 LWP_MutexUnlock(bufferMutex);
422 return (ret == 1)? 0 : -ret;
426 Main read function, basically just a wrapper to the function pointer.
427 Nicer then just exposing the pointer itself
429 int DI_ReadDVD(void* buf, uint32_t len, uint32_t lba){
430 if(di_fd < 0)
431 return -ENXIO;
433 int ret;
434 if(DI_ReadDVDptr){
435 while(LWP_MutexLock(bufferMutex));
436 ret = DI_ReadDVDptr(buf,len,lba);
437 LWP_MutexUnlock(bufferMutex);
438 return ret;
440 return -1;
443 int DI_ReadDVDAsync(void* buf, uint32_t len, uint32_t lba,ipccallback ipc_cb){
444 if(di_fd < 0)
445 return -ENXIO;
447 int ret;
448 if(DI_ReadDVDAsyncptr){
449 while(LWP_MutexLock(bufferMutex));
450 ret = DI_ReadDVDAsyncptr(buf,len,lba,ipc_cb);
451 LWP_MutexUnlock(bufferMutex);
452 return ret;
454 return -1;
458 Unknown what this does as of now...
460 int DI_ReadDVDConfig(uint32_t* val, uint32_t flag){
461 if(di_fd < 0)
462 return -ENXIO;
464 if(!val)
465 return -EINVAL;
467 while(LWP_MutexLock(bufferMutex));
469 dic[0] = DVD_READ_CONFIG << 24;
470 dic[1] = flag & 0x1; // Update flag, val will be written if this is 1, val won't be written if it's 0.
471 dic[2] = 0; // Command will fail driveside if this is not zero.
472 dic[3] = *val;
474 int ret = IOS_Ioctl(di_fd, DVD_READ_CONFIG, dic, 0x20, outbuf, 0x20);
476 if(ret == 2)
477 ret = EIO;
479 *val = outbuf[0];
480 LWP_MutexUnlock(bufferMutex);
481 return (ret == 1)? 0 : -ret;
485 Read the copyright information on a DVDVideo
487 int DI_ReadDVDCopyright(uint32_t* copyright){
488 if(di_fd < 0)
489 return -ENXIO;
491 if(!copyright)
492 return -EINVAL;
494 while(LWP_MutexLock(bufferMutex));
496 dic[0] = DVD_READ_COPYRIGHT << 24;
497 dic[1] = 0;
499 int ret = IOS_Ioctl(di_fd, DVD_READ_COPYRIGHT, dic, 0x20, outbuf, 0x20);
500 *copyright = *((uint32_t*)outbuf); // Copyright information is returned as an int in the first four bytes of outbuf.
502 if(ret == 2)
503 ret = EIO;
505 LWP_MutexUnlock(bufferMutex);
506 return (ret == 1)? 0 : -ret;
510 Returns 0x800 bytes worth of Disc key
512 int DI_ReadDVDDiscKey(void* buf){
513 int ret;
514 int retry_count = LIBDI_MAX_RETRIES;
516 if(di_fd < 0)
517 return -ENXIO;
519 if(!buf)
520 return -EINVAL;
522 if((uint32_t)buf & 0x1F)
523 return -EFAULT;
525 while(LWP_MutexLock(bufferMutex));
527 dic[0] = DVD_READ_DISCKEY << 24;
528 dic[1] = 0; // Unknown what this flag does.
530 ret = IOS_Ioctl(di_fd, DVD_READ_DISCKEY, dic, 0x20, buf, 0x800);
531 retry_count--;
532 }while(ret != 1 && retry_count > 0);
534 if(ret == 2)
535 ret = EIO;
537 LWP_MutexUnlock(bufferMutex);
538 return (ret == 1)? 0 : -ret;
542 This function will read the initial sector on the DVD, which contains stuff like the booktype
544 int DI_ReadDVDPhysical(void* buf){
545 int ret;
546 int retry_count = LIBDI_MAX_RETRIES;
548 if(di_fd < 0)
549 return -ENXIO;
551 if(!buf)
552 return -EINVAL;
554 if((uint32_t)buf & 0x1F)
555 return -EFAULT;
557 while(LWP_MutexLock(bufferMutex));
559 dic[0] = DVD_READ_PHYSICAL << 24;
560 dic[1] = 0; // Unknown what this flag does.
563 ret = IOS_Ioctl(di_fd, DVD_READ_PHYSICAL, dic, 0x20, buf, 0x800);
564 retry_count--;
565 }while(ret != 1 && retry_count > 0);
567 if(ret == 2)
568 ret = EIO;
570 LWP_MutexUnlock(bufferMutex);
571 return (ret == 1)? 0 : -ret;
574 int DI_ReportKey(int keytype, uint32_t lba, void* buf){
575 if(di_fd < 0)
576 return -ENXIO;
578 if(!buf)
579 return -EINVAL;
581 if((uint32_t)buf & 0x1F)
582 return -EFAULT;
584 while(LWP_MutexLock(bufferMutex));
586 dic[0] = DVD_REPORTKEY << 24;
587 dic[1] = keytype & 0xFF;
588 dic[2] = lba;
590 int ret = IOS_Ioctl(di_fd, DVD_REPORTKEY, dic, 0x20, buf, 0x20);
592 if(ret == 2)
593 ret = EIO;
595 LWP_MutexUnlock(bufferMutex);
596 return (ret == 1)? 0 : -ret;
599 int DI_GetCoverRegister(uint32_t* status){
600 if(di_fd < 0)
601 return -ENXIO;
603 while(LWP_MutexLock(bufferMutex));
605 memset(dic, 0x00, 0x20);
607 int ret = IOS_Ioctl(di_fd, DVD_GETCOVER, dic, 0x20, outbuf, 0x20);
608 if(ret == 2)
609 ret = EIO;
611 *status = outbuf[0];
613 LWP_MutexUnlock(bufferMutex);
614 return (ret == 1)? 0 : -ret;
617 /* Internal function for controlling motor operations */
618 static int _DI_SetMotor(int flag){
619 if(di_fd < 0)
620 return -ENXIO;
622 while(LWP_MutexLock(bufferMutex));
624 dic[0] = DVD_SET_MOTOR << 24;
625 dic[1] = flag & 0x1; // Eject flag.
626 dic[2] = (flag >> 1) & 0x1; // Don't use this flag, it kills the drive untill next reset.
628 int ret = IOS_Ioctl(di_fd, DVD_SET_MOTOR, dic, 0x20, outbuf, 0x20);
630 if(ret == 2)
631 ret = EIO;
633 LWP_MutexUnlock(bufferMutex);
634 return(ret == 1)? 0 : -ret;
637 /* Stop the drives motor */
638 int DI_StopMotor(){
639 return _DI_SetMotor(0);
642 /* Stop the motor, and eject the disc. Also needs a reset afterwards for normal operation */
643 int DI_Eject(){
644 return _DI_SetMotor(1);
647 /* Warning, this will kill your drive untill the next reset. Will not respond to DI commands,
648 will not take in or eject the disc. Your drive will be d - e - d, dead.
650 I deem this function to be harmless, as normal operation will resume after a reset.
651 However, I am not liable for anyones drive exploding as a result from using this function.
653 int DI_KillDrive(){
654 return _DI_SetMotor(2);
657 int DI_ClosePartition() {
658 if(di_fd < 0)
659 return -ENXIO;
661 while(LWP_MutexLock(bufferMutex));
663 dic[0] = DVD_CLOSE_PARTITION << 24;
665 int ret = IOS_Ioctl(di_fd, DVD_CLOSE_PARTITION, dic, 0x20, outbuf, 0x20);
667 if(ret == 2)
668 ret = EIO;
670 LWP_MutexUnlock(bufferMutex);
671 return(ret == 1)? 0 : -ret;
674 int DI_OpenPartition(uint32_t offset)
676 if(di_fd < 0)
677 return -ENXIO;
679 static ioctlv vectors[5] __attribute__((aligned(32)));
680 static char certs[0x49e4] __attribute__((aligned(32)));
681 while(LWP_MutexLock(bufferMutex));
683 dic[0] = DVD_OPEN_PARTITION << 24;
684 dic[1] = offset;
686 vectors[0].data = dic;
687 vectors[0].len = 0x20;
688 vectors[1].data = NULL;
689 vectors[1].len = 0x2a4;
690 vectors[2].data = NULL;
691 vectors[2].len = 0;
693 vectors[3].data = certs;
694 vectors[3].len = 0x49e4;
695 vectors[4].data = outbuf;
696 vectors[4].len = 0x20;
698 int ret = IOS_Ioctlv(di_fd, DVD_OPEN_PARTITION, 3, 2, vectors);
700 if(ret == 2)
701 ret = EIO;
703 LWP_MutexUnlock(bufferMutex);
704 return(ret == 1)? 0 : -ret;
708 int DI_Read(void *buf, uint32_t size, uint32_t offset)
710 if(di_fd < 0)
711 return -ENXIO;
713 if(!buf)
714 return -EINVAL;
716 if((uint32_t)buf & 0x1F)
717 return -EFAULT;
719 while(LWP_MutexLock(bufferMutex));
721 dic[0] = DVD_LOW_READ << 24;
722 dic[1] = size;
723 dic[2] = offset;
725 int ret = IOS_Ioctl(di_fd, DVD_LOW_READ, dic, 0x20, buf, size);
727 if(ret == 2)
728 ret = EIO;
730 LWP_MutexUnlock(bufferMutex);
731 return(ret == 1)? 0 : -ret;
734 int DI_UnencryptedRead(void *buf, uint32_t size, uint32_t offset)
736 int ret, retry_count = LIBDI_MAX_RETRIES;
738 if(di_fd < 0)
739 return -ENXIO;
741 if(!buf)
742 return -EINVAL;
744 if((uint32_t)buf & 0x1F) // This only works with 32 byte aligned addresses!
745 return -EFAULT;
747 while(LWP_MutexLock(bufferMutex));
749 dic[0] = DVD_READ_UNENCRYPTED << 24;
750 dic[1] = size;
751 dic[2] = offset;
753 do{
754 ret = IOS_Ioctl(di_fd, DVD_READ_UNENCRYPTED, dic, 0x20, buf, size);
755 retry_count--;
756 }while(ret != 1 && retry_count > 0);
758 if(ret == 2)
759 ret = EIO;
761 LWP_MutexUnlock(bufferMutex);
763 return (ret == 1)? 0 : -ret;
766 int DI_ReadDiscID(uint64_t *id)
768 if(di_fd < 0)
769 return -ENXIO;
771 while(LWP_MutexLock(bufferMutex));
773 dic[0] = DVD_READ_DISCID << 24;
775 int ret = IOS_Ioctl(di_fd, DVD_READ_DISCID, dic, 0x20, outbuf, 0x20);
777 if(ret == 2)
778 ret = EIO;
780 memcpy(id, outbuf, sizeof(*id));
782 LWP_MutexUnlock(bufferMutex);
783 return(ret == 1)? 0 : -ret;
786 static bool diio_Startup()
788 u64 t1,t2;
790 if(di_fd < 0)
791 return false;
793 DI_Mount();
795 t1=ticks_to_millisecs(gettime());
797 while(state & DVD_INIT)
799 usleep(500);
800 t2=ticks_to_millisecs(gettime());
801 if( (t2 - t1) > MOUNT_TIMEOUT)
802 return false; // timeout
805 if(state & DVD_READY)
806 return true;
807 return false;
810 static bool diio_IsInserted()
812 u32 val;
814 if(di_fd < 0)
815 return false;
817 DI_GetCoverRegister(&val);
818 if(val & 0x2)
819 return true;
821 return false;
824 static bool diio_ReadSectors(sec_t sector,sec_t numSectors,void *buffer)
826 if(DI_ReadDVD(buffer, numSectors, sector) == 0)
827 return true;
828 return false;
831 static bool diio_WriteSectors(sec_t sector,sec_t numSectors,const void *buffer)
833 return true;
836 static bool diio_ClearStatus()
838 return true;
841 static bool diio_Shutdown()
843 return true;
846 const DISC_INTERFACE __io_wiidvd = {
847 DEVICE_TYPE_WII_DVD,
848 FEATURE_MEDIUM_CANREAD | FEATURE_WII_DVD,
849 (FN_MEDIUM_STARTUP)&diio_Startup,
850 (FN_MEDIUM_ISINSERTED)&diio_IsInserted,
851 (FN_MEDIUM_READSECTORS)&diio_ReadSectors,
852 (FN_MEDIUM_WRITESECTORS)&diio_WriteSectors,
853 (FN_MEDIUM_CLEARSTATUS)&diio_ClearStatus,
854 (FN_MEDIUM_SHUTDOWN)&diio_Shutdown