/*-------------------------------------------------------------

di2.h -- Drive Interface library

Written by rodries
Modified from (and supplemental to) original libdi library:

Team Twiizers
Copyright (C) 2008

Erant
marcan


This software is provided 'as-is', without any express or implied
warranty.  In no event will the authors be held liable for any
damages arising from the use of this software.

Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1.	The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.

2.	Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.

3.	This notice may not be removed or altered from any source
distribution.

-------------------------------------------------------------*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ogc/ipc.h>
#include <ogc/ios.h>
#include <ogc/mutex.h>
#include <errno.h>

#include <unistd.h>
#include <malloc.h>
#include <ogc/lwp.h>
#include <ogc/lwp_watchdog.h>

#define UNUSED	__attribute__((unused))
#include "di2.h"

extern int di_fd;

int _DI2_ReadDVD_ReadID(void* buf, uint32_t len, uint32_t lba);
int _DI2_ReadDVD_ReadID_Async(void* buf, uint32_t len, uint32_t lba, ipccallback ipc_cb);
void _DI2_SetCallback(int di_command, ipccallback);
int _DI2_ReadDVD_Check(void* buf, uint32_t len, uint32_t lba);
static int _cover_callback(int ret, void* usrdata);

int state = DVD_INIT | DVD_NO_DISC;

static mutex_t bufferMutex = LWP_MUTEX_NULL;
static uint32_t outbuf[8] __attribute__((aligned(32)));
static uint32_t dic[8] __attribute__((aligned(32)));
static char di_path[] ATTRIBUTE_ALIGN(32) = "/dev/di";

read_func DI2_ReadDVDptr = NULL;
read_func_async DI2_ReadDVDAsyncptr = NULL;
di_callback di_cb = NULL;

#define DEFAULT_TIME_STOP_MOTOR 60  // 60 secs
#define MIN_TIME_STOP_MOTOR 8  // the min value accepted
static bool motorthreadexit = false;
static lwp_t motorthread = LWP_THREAD_NULL;
static bool motor_stopped = true;
static u8 LoadedIOS = 0;
static u64 LastAccess = 0;
static unsigned int TimeStopMotor = DEFAULT_TIME_STOP_MOTOR;

static int _DI2_ReadDVD_A8_Async(void* buf, uint32_t len, uint32_t lba, ipccallback ipc_cb){
	int ret;

	if(!buf)
		return -EINVAL;

	if((uint32_t)buf & 0x1F) // This only works with 32 byte aligned addresses!
		return -EFAULT;

	dic[0] = DVD_READ_UNENCRYPTED << 24;
	dic[1] = len << 11; // 1 LB is 2048 bytes
	dic[2] = lba << 9; // Nintendo's read function uses byteOffset >> 2, so we only shift 9 left, not 11.

	ret = IOS_IoctlAsync(di_fd, DVD_READ_UNENCRYPTED, dic, 0x20, buf, len << 11,ipc_cb, buf);

	if(ret == 2)
		ret = EIO;

	return (ret == 1)? 0 : -ret;
}

static int _DI2_ReadDVD_D0_Async(void* buf, uint32_t len, uint32_t lba, ipccallback ipc_cb){
	int ret;

	if(!buf)
		return -EINVAL;

	if((uint32_t)buf & 0x1F)
		return -EFAULT;

	dic[0] = DVD_READ << 24;
	dic[1] = 0;		// Unknown what this does as of now. (Sets some value to 0x10 in the drive if set).
	dic[2] = 0;		// USE_DEFAULT_CONFIG flag. Drive will use default config if this bit is set.
	dic[3] = len;
	dic[4] = lba;

	ret = IOS_IoctlAsync(di_fd, DVD_READ, dic, 0x20, buf, len << 11,ipc_cb, buf);

	if(ret == 2)
		ret = EIO;

	return (ret == 1)? 0 : -ret;
}

static int _DI2_ReadDVD_A8(void* buf, uint32_t len, uint32_t lba){
	int ret, retry_count = LIBDI_MAX_RETRIES;

	if(!buf)
		return -EINVAL;

	if((uint32_t)buf & 0x1F) // This only works with 32 byte aligned addresses!
		return -EFAULT;

	dic[0] = DVD_READ_UNENCRYPTED << 24;
	dic[1] = len << 11; // 1 LB is 2048 bytes
	dic[2] = lba << 9; // Nintendo's read function uses byteOffset >> 2, so we only shift 9 left, not 11.

	do{
		ret = IOS_Ioctl(di_fd, DVD_READ_UNENCRYPTED, dic, 0x20, buf, len << 11);
		retry_count--;
	}while(ret != 1 && retry_count > 0);

	if(ret == 2)
		ret = EIO;

	return (ret == 1)? 0 : -ret;
}

static int _DI2_ReadDVD_D0(void* buf, uint32_t len, uint32_t lba){
	int ret, retry_count = LIBDI_MAX_RETRIES;

	if(!buf)
		return -EINVAL;

	if((uint32_t)buf & 0x1F)
		return -EFAULT;

	dic[0] = DVD_READ << 24;
	dic[1] = 0; // Unknown what this does as of now. (Sets some value to 0x10 in the drive if set).
	dic[2] = 0; // USE_DEFAULT_CONFIG flag. Drive will use default config if this bit is set.
	dic[3] = len;
	dic[4] = lba;

	do{
		ret = IOS_Ioctl(di_fd, DVD_READ, dic, 0x20, buf, len << 11);
		retry_count--;
	}while(ret != 1 && retry_count > 0);

	if(ret == 2)
		ret = EIO;

	return (ret == 1)? 0 : -ret;
}

void DI2_SetDVDMotorStopSecs(int secs) //in seconds
{
	if (secs < MIN_TIME_STOP_MOTOR)
		return;
	LastAccess = ticks_to_secs(gettime());
	TimeStopMotor = secs;
}

unsigned int DI2_GetDVDMotorStopSecs() //in seconds
{
	return TimeStopMotor;
}

static bool DVD_DiscPresent()
{
	uint32_t val;

	DI2_GetCoverRegister(&val);
	if (val & 0x2)
		return true;
	return false;
}

static void * motorthreadfunc(void *arg UNUSED)
{
	long sleeptime;
	bool first = true;

	while (1)
	{
		if (motor_stopped)
		{
			first = true;
		}
		else
		{
			if (first) // change state from stop to start
			{
				sleeptime = 10*1000*1000; // 10 sec

				while(sleeptime > 0)
				{
					if(motorthreadexit)
						return NULL;
					usleep(100);
					sleeptime -= 100;
				}
				LastAccess = ticks_to_secs(gettime());
				first = false;
			}
			if ((ticks_to_secs(gettime()) - LastAccess) > TimeStopMotor)
			{ // we have to stop motor
				if (DVD_DiscPresent()) // only stop if dvd is present
					DI2_StopMotor();
			}
		}

		sleeptime = 1000*1000; // 1 sec

		while(sleeptime > 0)
		{
			if(motorthreadexit)
				return NULL;
			usleep(100);
			sleeptime -= 100;
		}
	}
	return NULL;
}

void DI2_StartMotorThread()
{
	if(motorthread == LWP_THREAD_NULL)
		LWP_CreateThread(&motorthread, motorthreadfunc, NULL, NULL, 0, 63); // create thread
}

void DI2_StopMotorThread()
{
	motorthreadexit = true; // signal thread to close
	if(motorthread != LWP_THREAD_NULL)
		LWP_JoinThread(motorthread, NULL); // wait for thread to finish
	motorthread = LWP_THREAD_NULL;
	motorthreadexit = false;
	motor_stopped = true;
}

int DI2_StartMotor()
{
	if (state == (DVD_INIT | DVD_NO_DISC) || (DI2_ReadDVDptr == _DI2_ReadDVD_Check))
		return 0;

	if (motor_stopped && motorthreadexit == false)
	{
		DI2_Mount();
		unsigned int t1,t2;
		t1=ticks_to_secs(gettime());
		while(DI2_GetStatus() & DVD_INIT)
		{
			t2=ticks_to_secs(gettime());
			if(t2-t1 > 15) return -1;
			usleep(5000);
		}
		motor_stopped = false;
	}
	return 0;
}

void CheckAccess()
{
	DI2_StartMotor();
	LastAccess = ticks_to_secs(gettime());
}

///// Cache
#define CACHE_FREE 0xFFFFFFFF
#define BLOCK_SIZE 0x800
#define CACHEBLOCKS 26
typedef struct
{
	uint32_t block;
	void *ptr;
} cache_page;
static cache_page *cache_read = NULL;

static void CreateDVDCache()
{
	if (cache_read != NULL)
		return;
	cache_read = (cache_page *) malloc(sizeof(cache_page));
	if (cache_read == NULL)
		return;

	cache_read->block = CACHE_FREE;
	cache_read->ptr = memalign(32, BLOCK_SIZE * CACHEBLOCKS);
	if (cache_read->ptr == NULL)
	{
		if (cache_read->ptr)
			free(cache_read->ptr);
		free(cache_read);
		cache_read = NULL;
		return;
	}
	memset(cache_read->ptr, 0, BLOCK_SIZE);
}

static int ReadBlockFromCache(void *buf, uint32_t len, uint32_t block)
{
	int retval;

	if (cache_read == NULL)
		return DI2_ReadDVDptr(buf, len, block);

	if ((block >= cache_read->block) && (block + len < (cache_read->block + CACHEBLOCKS)))
	{
		memcpy(buf, cache_read->ptr	+ ((block - cache_read->block) * BLOCK_SIZE), BLOCK_SIZE * len);
		return 0;
	}

	if (len > CACHEBLOCKS)
		return DI2_ReadDVDptr(buf, len, block);

	retval = DI2_ReadDVDptr(cache_read->ptr, CACHEBLOCKS, block);
	if (retval)
	{
		cache_read->block = CACHE_FREE;
		return retval;
	}

	cache_read->block = block;
	memcpy(buf, cache_read->ptr, len * BLOCK_SIZE);

	return 0;
}

/*
 Initialize the DI interface, should always be called first!
 */

s32 __DI_StubLaunch();

int DI2_Init()
{
    LoadedIOS = (u8) IOS_GetVersion();

    if(LoadedIOS < 202)
        return DI_Init();

	state = DVD_INIT | DVD_NO_DISC;

	if (di_fd < 0)
		di_fd = IOS_Open(di_path, 2);

	if (bufferMutex == LWP_MUTEX_NULL)
		LWP_MutexInit(&bufferMutex, false);

	LastAccess = 0;
	CreateDVDCache();
	return (di_fd >= 0) ? di_fd : -1;
}

void DI2_Mount()
{
    if(LoadedIOS < 202)
    {
        DI_Mount();
        return;
    }

	uint32_t status;

	if (DI2_GetCoverRegister(&status) != 0)
	{
		state = DVD_NO_DISC;
		return;
	}

	if ((status & DVD_COVER_DISC_INSERTED) == 0)
	{
		state = DVD_NO_DISC;
		return;
	}

	state = DVD_INIT | DVD_NO_DISC;
	_cover_callback(1, NULL); // Initialize the callback chain
	LastAccess = ticks_to_secs(gettime());
	DI2_StopMotorThread();

	if (cache_read != NULL)
		cache_read->block = CACHE_FREE; // reset cache
	LastAccess = ticks_to_secs(gettime());
	DI2_StartMotorThread();
}

void DI2_Close()
{
    if(LoadedIOS < 202)
    {
        DI_Close();
        return;
    }

	DI2_StopMotorThread();
	LastAccess = 0;

	if (di_fd > 0)
	{
		IOS_Close(di_fd);
	}
	di_fd = -1;

	DI2_ReadDVDptr = NULL;
	DI2_ReadDVDAsyncptr = NULL;
	state = DVD_INIT | DVD_NO_DISC;

	if (bufferMutex)
	{
		LWP_MutexDestroy(bufferMutex);
		bufferMutex = 0;
	}
}

#define COVER_CLOSED (*((uint32_t*)usrdata) & DVD_COVER_DISC_INSERTED)

int _DI2_ReadDVD_Check(void* buf, uint32_t len, uint32_t lba)
{
	int ret;

	ret = _DI2_ReadDVD_D0(buf, len, lba);
	if (ret >= 0)
	{
		state = state | DVD_D0;
		DI2_ReadDVDptr = _DI2_ReadDVD_D0;
		DI2_ReadDVDAsyncptr = _DI2_ReadDVD_D0_Async;
		motor_stopped = false;
		//printf("libdi: D0 functions detected\n");
		return ret;
	}
	ret = _DI2_ReadDVD_A8(buf, len, lba);
	if (ret >= 0)
	{
		state = state | DVD_A8;
		DI2_ReadDVDptr = _DI2_ReadDVD_A8;
		DI2_ReadDVDAsyncptr = _DI2_ReadDVD_A8_Async;
		motor_stopped = false;
		//printf("libdi: A8 functions detected\n");
		return ret;
	}
	//printf("libdi: error detection D0/A8 functions\n");
	return ret;
}

int _DI2_ReadDVD_Check_Async(void* buf, uint32_t len, uint32_t lba,	ipccallback ipc_cb)
{ // is bad code has to be done correctly using callback func
	int ret;

	ret = _DI2_ReadDVD_D0_Async(buf, len, lba, ipc_cb);
	if (ret >= 0)
	{
		state = state | DVD_D0;
		DI2_ReadDVDptr = _DI2_ReadDVD_D0;
		DI2_ReadDVDAsyncptr = _DI2_ReadDVD_D0_Async;
		motor_stopped = false;
		return ret;
	}
	ret = _DI2_ReadDVD_A8_Async(buf, len, lba, ipc_cb);
	if (ret >= 0)
	{
		state = state | DVD_A8;
		DI2_ReadDVDptr = _DI2_ReadDVD_A8;
		DI2_ReadDVDAsyncptr = _DI2_ReadDVD_A8_Async;
		motor_stopped = false;
		return ret;
	}
	return ret;
}

static int _cover_callback(int ret, void* usrdata)
{
	static int cur_state = 0;
	static int retry_count = LIBDI_MAX_RETRIES;
	const int callback_table[] =
	{ DVD_GETCOVER, DVD_WAITFORCOVERCLOSE, DVD_RESET, DVD_IDENTIFY, // This one will complete when the drive is ready.
			DVD_READ_DISCID, 0 };
	const int return_table[] =
	{ 1, 1, 4, 1, 1 };
	if (cur_state > 1)
		state &= ~DVD_NO_DISC;

	if (callback_table[cur_state])
	{
		if (ret == return_table[cur_state])
		{
			if (cur_state == 1 && COVER_CLOSED) // Disc inside, skipping wait for cover.
				cur_state += 2;
			else
				cur_state++; // If the previous callback succeeded, moving on to the next

			retry_count = LIBDI_MAX_RETRIES;
		}
		else
		{
			retry_count--;
			if (retry_count < 0)
			{ // Drive init failed for unknown reasons.
				retry_count = LIBDI_MAX_RETRIES;
				cur_state = 0;
				state = DVD_UNKNOWN;
				return 0;
			}
		}
		_DI2_SetCallback(callback_table[cur_state - 1], _cover_callback);

	}
	else // Callback chain has completed OK. The drive is ready.
	{
		state = DVD_READY;

		retry_count = 1;
		state = DVD_READY;

		DI2_ReadDVDptr = _DI2_ReadDVD_Check;
		DI2_ReadDVDAsyncptr = _DI2_ReadDVD_Check_Async;

		if (di_cb)
			di_cb(state, 0);

		retry_count = LIBDI_MAX_RETRIES;
		cur_state = 0;

	}
	return 0;
}

/* Get current status, will return the API status */
int DI2_GetStatus()
{
    if(LoadedIOS < 202)
        return DI_GetStatus();

	return state;
}

void DI2_SetInitCallback(di_callback cb)
{
    if(LoadedIOS < 202)
        return DI_SetInitCallback(cb);

	di_cb = cb;
}

void _DI2_SetCallback(int ioctl_nr, ipccallback ipc_cb)
{
	if (!ipc_cb)
		return;

	LWP_MutexLock(bufferMutex);
	memset(dic, 0x00, sizeof(dic));

	dic[0] = ioctl_nr << 24;
	dic[1] = (ioctl_nr == DVD_RESET) ? 1 : 0; // For reset callback. Dirty, I know...

	IOS_IoctlAsync(di_fd, ioctl_nr, dic, 0x20, outbuf, 0x20, ipc_cb, outbuf);

	//We're done with the buffer now.
	LWP_MutexUnlock(bufferMutex);
}

/*
 Request an identification from the drive, returned in a DI_DriveID struct
 */
int DI2_Identify(DI_DriveID* id)
{
    if(LoadedIOS < 202)
        return DI_Identify(id);

	if (!id)
	{
		errno = EINVAL;
		return -1;
	}

	CheckAccess();
	LWP_MutexLock(bufferMutex);
	dic[0] = DVD_IDENTIFY << 24;

	int ret = IOS_Ioctl(di_fd, DVD_IDENTIFY, dic, 0x20, outbuf, 0x20);

	if (ret == 2)
		errno = EIO;

	memcpy(id, outbuf, sizeof(DI_DriveID));

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

/*
 Returns the current error code on the drive.
 yagcd has a pretty comprehensive list of possible error codes
 */
int DI2_GetError(uint32_t* error)
{
    if(LoadedIOS < 202)
        return DI_GetError(error);

	if (!error)
	{
		errno = EINVAL;
		return -1;
	}

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_GET_ERROR << 24;

	int ret = IOS_Ioctl(di_fd, DVD_GET_ERROR, dic, 0x20, outbuf, 0x20);

	if (ret == 2)
		errno = EIO;

	*error = outbuf[0]; // Error code is returned as an int in the first four bytes of outbuf.

	LWP_MutexUnlock(bufferMutex);

	return (ret == 1) ? 0 : -ret;
}

/*
 Reset the drive.
 */
int DI2_Reset()
{
    if(LoadedIOS < 202)
        return DI_Reset();

	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_RESET << 24;
	dic[1] = 1;

	int ret = IOS_Ioctl(di_fd, DVD_RESET, dic, 0x20, outbuf, 0x20);

	if (ret == 2)
		errno = EIO;

	LWP_MutexUnlock(bufferMutex);

	return (ret == 1) ? 0 : -ret;
}

/*
 Main read function, basically just a wrapper to the function pointer.
 Nicer then just exposing the pointer itself
 */
int DI2_ReadDVD(void* buf, uint32_t len, uint32_t lba)
{
    if(LoadedIOS < 202)
        return DI_ReadDVD(buf, len, lba);

	int ret;
	if (DI2_ReadDVDptr)
	{
		CheckAccess();

		// Wait for the lock. Doing it here saves me from doing it in all the read functions.
		LWP_MutexLock(bufferMutex);
		ret = ReadBlockFromCache(buf, len, lba);
		LWP_MutexUnlock(bufferMutex);
		return ret;
	}
	return -1;
}

int DI2_ReadDVDAsync(void* buf, uint32_t len, uint32_t lba, ipccallback ipc_cb)
{
    if(LoadedIOS < 202)
        return DI_ReadDVDAsync(buf, len, lba, ipc_cb);

	int ret;
	if (DI2_ReadDVDAsyncptr)
	{
		CheckAccess();
		LWP_MutexLock(bufferMutex);
		ret = DI2_ReadDVDAsyncptr(buf, len, lba, ipc_cb);
		LWP_MutexUnlock(bufferMutex);
		return ret;
	}
	return -1;
}

/*
 Unknown what this does as of now...
 */
int DI2_ReadDVDConfig(uint32_t* val, uint32_t flag)
{
    if(LoadedIOS < 202)
        return DI_ReadDVDConfig(val, flag);

	if (!val)
	{
		errno = EINVAL;
		return -1;
	}

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_READ_CONFIG << 24;
	dic[1] = flag & 0x1; // Update flag, val will be written if this is 1, val won't be written if it's 0.
	dic[2] = 0; // Command will fail driveside if this is not zero.
	dic[3] = *val;

	int ret = IOS_Ioctl(di_fd, DVD_READ_CONFIG, dic, 0x20, outbuf, 0x20);

	if (ret == 2)
		errno = EIO;

	*val = outbuf[0];
	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

/*
 Read the copyright information on a DVDVideo
 */
int DI2_ReadDVDCopyright(uint32_t* copyright)
{
    if(LoadedIOS < 202)
        return DI_ReadDVDCopyright(copyright);

	if (!copyright)
	{
		errno = EINVAL;
		return -1;
	}

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_READ_COPYRIGHT << 24;
	dic[1] = 0;

	int ret = IOS_Ioctl(di_fd, DVD_READ_COPYRIGHT, dic, 0x20, outbuf, 0x20);
	*copyright = *((uint32_t*) outbuf); // Copyright information is returned as an int in the first four bytes of outbuf.

	if (ret == 2)
		errno = EIO;
	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

/*
 Returns 0x800 bytes worth of Disc key
 */
int DI2_ReadDVDDiscKey(void* buf)
{
    if(LoadedIOS < 202)
        return DI_ReadDVDDiscKey(buf);

	int ret;
	int retry_count = LIBDI_MAX_RETRIES;

	if (!buf)
	{
		errno = EINVAL;
		return -1;
	}

	if ((uint32_t) buf & 0x1F)
	{
		errno = EFAULT;
		return -1;
	}

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_READ_DISCKEY << 24;
	dic[1] = 0; // Unknown what this flag does.
	do
	{
		ret = IOS_Ioctl(di_fd, DVD_READ_DISCKEY, dic, 0x20, buf, 0x800);
		retry_count--;
	} while (ret != 1 && retry_count > 0);

	if (ret == 2)
		errno = EIO;

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

/*
 This function will read the initial sector on the DVD, which contains stuff like the booktype
 */
int DI2_ReadDVDPhysical(void* buf)
{
    if(LoadedIOS < 202)
        return DI_ReadDVDPhysical(buf);

	int ret;
	int retry_count = LIBDI_MAX_RETRIES;

	if (!buf)
	{
		errno = EINVAL;
		return -1;
	}

	if ((uint32_t) buf & 0x1F)
	{
		errno = EFAULT;
		return -1;
	}

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_READ_PHYSICAL << 24;
	dic[1] = 0; // Unknown what this flag does.

	do
	{
		ret = IOS_Ioctl(di_fd, DVD_READ_PHYSICAL, dic, 0x20, buf, 0x800);
		retry_count--;
	} while (ret != 1 && retry_count > 0);

	if (ret == 2)
		errno = EIO;

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

int DI2_ReportKey(int keytype, uint32_t lba, void* buf)
{
    if(LoadedIOS < 202)
        return DI_ReportKey(keytype, lba, buf);

	if (!buf)
	{
		errno = EINVAL;
		return -1;
	}

	if ((uint32_t) buf & 0x1F)
	{
		errno = EFAULT;
		return -1;
	}

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_REPORTKEY << 24;
	dic[1] = keytype & 0xFF;
	dic[2] = lba;

	int ret = IOS_Ioctl(di_fd, DVD_REPORTKEY, dic, 0x20, buf, 0x20);

	if (ret == 2)
		errno = EIO;

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

int DI2_GetCoverRegister(uint32_t* status)
{
    if(LoadedIOS < 202)
        return DI_GetCoverRegister(status);

	LWP_MutexLock(bufferMutex);

	memset(dic, 0x00, 0x20);

	int ret = IOS_Ioctl(di_fd, DVD_GETCOVER, dic, 0x20, outbuf, 0x20);
	if (ret == 2)
		errno = EIO;

	*status = outbuf[0];

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

/* Internal function for controlling motor operations */
int _DI2_SetMotor(int flag)
{
	LWP_MutexLock(bufferMutex);
	LastAccess = ticks_to_secs(gettime());

	dic[0] = DVD_SET_MOTOR << 24;
	dic[1] = flag & 0x1; // Eject flag.
	dic[2] = (flag >> 1) & 0x1; // Don't use this flag, it kills the drive untill next reset.

	int ret = IOS_Ioctl(di_fd, DVD_SET_MOTOR, dic, 0x20, outbuf, 0x20);

	if (ret == 2)
		errno = EIO;

	if (ret == 1)
		motor_stopped = true;

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

/* Stop the drives motor, needs a reset afterwards for normal operation */
int DI2_StopMotor()
{
    if(LoadedIOS < 202)
        return DI_StopMotor();

	return _DI2_SetMotor(0);
}

/* Stop the motor, and eject the disc. Also needs a reset afterwards for normal operation */
int DI2_Eject()
{
    if(LoadedIOS < 202)
        return DI_Eject();

	return _DI2_SetMotor(1);
}

/* Warning, this will kill your drive untill the next reset. Will not respond to DI commands,
 will not take in or eject the disc. Your drive will be d - e - d, dead.

 I deem this function to be harmless, as normal operation will resume after a reset.
 However, I am not liable for anyones drive exploding as a result from using this function.
 */
int DI2_KillDrive()
{
	return _DI2_SetMotor(2);
}

int DI2_ClosePartition()
{
    if(LoadedIOS < 202)
        return DI_ClosePartition();

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_CLOSE_PARTITION << 24;

	int ret = IOS_Ioctl(di_fd, DVD_CLOSE_PARTITION, dic, 0x20, outbuf, 0x20);

	if (ret == 2)
		errno = EIO;

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

int DI2_OpenPartition(uint32_t offset)
{
    if(LoadedIOS < 202)
        return DI_OpenPartition(offset);

	static ioctlv vectors[5] __attribute__((aligned(32)));
	static char certs[0x49e4] __attribute__((aligned(32)));
	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_OPEN_PARTITION << 24;
	dic[1] = offset;

	vectors[0].data = dic;
	vectors[0].len = 0x20;
	vectors[1].data = NULL;
	vectors[1].len = 0x2a4;
	vectors[2].data = NULL;
	vectors[2].len = 0;

	vectors[3].data = certs;
	vectors[3].len = 0x49e4;
	vectors[4].data = outbuf;
	vectors[4].len = 0x20;

	int ret = IOS_Ioctlv(di_fd, DVD_OPEN_PARTITION, 3, 2, vectors);

	if (ret == 2)
		errno = EIO;

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

int DI2_Read(void *buf, uint32_t size, uint32_t offset)
{
    if(LoadedIOS < 202)
        return DI_Read(buf, size, offset);

	if (!buf)
	{
		errno = EINVAL;
		return -1;
	}

	if ((uint32_t) buf & 0x1F)
	{
		errno = EFAULT;
		return -1;
	}

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_LOW_READ << 24;
	dic[1] = size;
	dic[2] = offset;

	int ret = IOS_Ioctl(di_fd, DVD_LOW_READ, dic, 0x20, buf, size);

	if (ret == 2)
		errno = EIO;

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

int DI2_UnencryptedRead(void *buf, uint32_t size, uint32_t offset)
{
    if(LoadedIOS < 202)
        return DI_UnencryptedRead(buf, size, offset);

	int ret, retry_count = LIBDI_MAX_RETRIES;

	if (!buf)
	{
		errno = EINVAL;
		return -1;
	}

	if ((uint32_t) buf & 0x1F)
	{ // This only works with 32 byte aligned addresses!
		errno = EFAULT;
		return -1;
	}

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_READ_UNENCRYPTED << 24;
	dic[1] = size;
	dic[2] = offset;

	do
	{
		ret = IOS_Ioctl(di_fd, DVD_READ_UNENCRYPTED, dic, 0x20, buf, size);
		retry_count--;
	} while (ret != 1 && retry_count > 0);

	if (ret == 2)
		errno = EIO;

	LWP_MutexUnlock(bufferMutex);

	return (ret == 1) ? 0 : -ret;
}

int DI2_ReadDiscID(uint64_t *id)
{
    if(LoadedIOS < 202)
        return DI_ReadDiscID(id);

	CheckAccess();
	LWP_MutexLock(bufferMutex);

	dic[0] = DVD_READ_DISCID << 24;

	int ret = IOS_Ioctl(di_fd, DVD_READ_DISCID, dic, 0x20, outbuf, 0x20);

	if (ret == 2)
		errno = EIO;

	memcpy(id, outbuf, sizeof(*id));

	LWP_MutexUnlock(bufferMutex);
	return (ret == 1) ? 0 : -ret;
}

bool DI2_IsMotorRunning()
{
    return !motor_stopped;
}

bool diio_Startup()
{
	return true;
}

bool diio_Shutdown()
{
	return true;
}

bool diio_ReadSectors(sec_t sector UNUSED, sec_t numSectors UNUSED, void *buffer UNUSED)
{
	return true;
}

bool diio_WriteSectors(sec_t sector UNUSED, sec_t numSectors UNUSED, const void *buffer UNUSED)
{
	return true;
}

bool diio_ClearStatus()
{
	return true;
}

bool diio_IsInserted()
{
	return true;
}

bool diio_IsInitialized()
{
	return true;
}

const DISC_INTERFACE __io_wiidvd2 = {
	DEVICE_TYPE_WII_DVD,
	FEATURE_MEDIUM_CANREAD | FEATURE_WII_DVD,
	(FN_MEDIUM_STARTUP)&diio_Startup,
	(FN_MEDIUM_ISINSERTED)&diio_IsInserted,
	(FN_MEDIUM_READSECTORS)&diio_ReadSectors,
	(FN_MEDIUM_WRITESECTORS)&diio_WriteSectors,
	(FN_MEDIUM_CLEARSTATUS)&diio_ClearStatus,
	(FN_MEDIUM_SHUTDOWN)&diio_Shutdown
};
