/* Copyright 2009 WiiGator. */
/* Read optimisation based on code from Waninkoko. */
#include "types.h"
#include "dvd.h"
#include "dvdread_orig.h"
#include "dvdreadid_orig.h"
#include "cache.h"
#include "memcpy.h"
#include "exception.h"
#if defined(RELOAD_SUPPORT) || defined(MIOS_PLUGIN)
#include "dvdreadpatch.h"
#endif
#include "dvdreporterror_orig.h"
#include "dvdaudiostatus_orig.h"
#include "plugin.h"
#include "usb.h"

#include "debugprintf.h"
#ifdef GEKKO_DEBUG
#include "dvdreset_orig.h"
#include "dvdstopmotor_orig.h"
#endif


/** DVD sector size. */
#define SECTOR_SIZE				0x800
/** Memory alignment needed for DMA and cache. */
//#define MEM_ALIGNMENT			0x20
/** Maximum size that can be read in one step. */
#define MAX_SECTOR_SIZE			0x7f8000
/** DVD return code for success. */
#define DVD_TRANSFER_COMPLETE 1
/** DVD return code for read error. */
#define DVD_DEVICE_ERROR 2
/** DVD return code for cover. */
//#define DVD_COVER_INTERRUPT 4
/** DVD return code for break. */
//#define DVD_BREAK_COMPLETE 8
/** DVD return code for timeout. */
//#define DVD_TIMEOUT 16
/** Memory must be aligned to 32 (DMA and cache line size). */
#define MEMORY_ALIGNMENT		0x20
/** Physical start address of DMA memory (mem1). */
#define DMA1_START_ADDRESS		0x00000000
/** Physical end address of DMA memory (mem1). */
#define DMA1_END_ADDRESS		0x01800000
/** Maximum read retried for each access. */
#define MAX_READ_RETRY 30

#ifdef GEKKO_DEBUG
#ifdef LOW_PLUGIN
#define debug_printf(...) /* not enough memory */
#endif
#endif

/** Temporary DVD buffer. */
static unsigned char dvd_buffer[SECTOR_SIZE] __attribute__((aligned(32)));
/** DVD callback function, should be called when transfer is complete. */
static interrupt_callback_t *dvd_callback_function;
/** Source address for final memcpy (DVD temporary buffer.). */
static void *cpy_src;
/** Destination address for final memcpy (buffer for DVD read specified by caller. */
static void *cpy_dst;
/** Size for final memcpy (specified by caller). */
static unsigned long cpy_size;
/** Outbuf for last read cmd. */
static void *current_read_outbuf;
/** len  for last read cmd. */
static unsigned long current_read_len;
/** lba for last read cmd. */
static unsigned long current_read_lba;
/** Read error counter for one read. */
static unsigned long current_read_errors;

/* Global DI_loop variables. */
/** Number of Bytes read. */
static unsigned long global_read_cnt;
/** Next lba to read. */
static unsigned long global_read_lba;
/** Total length to read. */
static unsigned long global_read_len;
/** Next offset to read. */
static unsigned long global_read_offset;
/** Start address of read buffer specified by caller. */
static unsigned char *global_read_buffer;
/** Last sector read, which was succesfully read. Use this for DVD command simulation.
 * The access to this sector will be faster, because the data are still in the buffer.
 */
static unsigned long last_sector = 0;

int DI_loop(interrupt_callback_t *callback_fn, int result);

#ifdef GEKKO_DEBUG
#if defined(MIOS_PLUGIN) || defined(HIGH_PLUGIN) /* Only MIOS and high version has enough memory. */
void hexDump(unsigned long *buffer, unsigned long size)
{
	int i;

	for (i = 0; i < size; i++) {
		if ((i > 0) && ((i & 0x3) == 0)) {
			debug_printf("\n");
		}
		if ((i & 0x3) == 0) {
			debug_printf("%x: ", i);
		}
		unsigned long l;

		l = buffer[i];
		debug_printf("%x", l);
		if ((i + 1) < size) {
			debug_printf(" ");
		}
	}
	debug_printf("\n");
}
#endif
#endif

#ifdef HIGH_PLUGIN /* Action replay support only in high plugin. */
#ifdef ACTION_REPLAY
unsigned int dvd_read_sector(void *outbuf, int len, unsigned int lba)
{
	volatile uint32_t *dvdio = (volatile uint32_t *)0xCC006000;

	/* dst must be 32 Byte aligned. */
	/* Size should be 0x800. */
	if ((((unsigned int)outbuf) & 0xC0000000) == 0x80000000) { /* cached? */
		dvdio[0] = 0x2E;
#if defined(MIOS_PLUGIN) || defined(HIGH_PLUGIN) /* Only MIOS and high version has enough memory. */
		DCInvalidateRange(outbuf, len);
#else
		/* This may be slower. */
		DCFlushRange(outbuf, len);
		ICInvalidateRange(outbuf, len);
#endif
	}
	dvdio[1] = 0;	
	dvdio[2] = 0xD0000000;
	dvdio[3] = lba;
	dvdio[4] = len >> 11;
	dvdio[5] = (unsigned long)outbuf;
	dvdio[6] = len;
	dvdio[7] = 3; /* Start transfer. */
	while (dvdio[7] & 1);

	if (dvdio[0] & 0x4) {
		debug_printf("Read error\n");
		return 1;
	}
	return 0;
}

void ar_dvd_read_replacement1(unsigned long off, void *outbuf, unsigned long len)
{
	action_replay = 1;
	
	unsigned long cnt, lba;
	unsigned long offset;

	unsigned int ret = 0;
	debug_printf("dvd_read(outbuf 0x%x, len 0x%x, offset 0x%x)\n", outbuf, len, off);

#ifdef RELOAD_SUPPORT
	jmpNr = 0;
#endif

	offset = base_offset + (off >> 2);

	/* Set variables */
	lba = offset >> 9;
	cnt = 0;

	while (cnt < len) {
		unsigned long size, skip;

		/* Skip bytes */
		skip = (offset > (lba << 9)) ? (offset - (lba << 9)) << 2 : 0;

		/* Data length */
		size = len - cnt;
		if (size + skip > SECTOR_SIZE)
			size = SECTOR_SIZE - skip;

		/* Do read */
		ret = dvd_read_sector(dvd_buffer, SECTOR_SIZE, lba);
		if (ret) {
			break;
		}

		/* Copy data */
		memcpy(outbuf + cnt, dvd_buffer + skip, size);
#ifndef RELOAD_SUPPORT
		DCFlushRange(outbuf + cnt, size);
		ICInvalidateRange(outbuf + cnt, size);
		DCFlushRange(dvd_buffer, SECTOR_SIZE);
		ICInvalidateRange(dvd_buffer, SECTOR_SIZE);
#endif

		/* Update variables */
		cnt += size;
		lba++;
	}
#ifdef RELOAD_SUPPORT
	if (!ret) {
		dvd_patchread(outbuf, len, &jumptable[0], -1);
		DCFlushRange(outbuf, len);
		ICInvalidateRange(outbuf, len);
	}
#endif
}

void ar_dvd_read_disk_id_replacement1(void *buffer)
{
	u32 saved_offset;

	/* Switch disc: */
	saved_offset = base_offset;
	base_offset = base_offset_2nd_disc;
	base_offset_2nd_disc = saved_offset;

	ar_dvd_read_replacement1(0, buffer, 32);
}
#endif
#endif

/** Replacement for real DVD callback function. This is called if transfer is complete.
 * @param result DVD_TRANSFER_COMPLETE on success, DVD_DEVICE_ERROR on error.
 */
void DI_callback(int result)
{
	debug_printf("DI_callback by 0x%x result 0x%x\n", __builtin_return_address(0), result);

	if (dvd_callback_function != NULL) {
		if (result & DVD_TRANSFER_COMPLETE) {
			if (cpy_dst != NULL) {
				last_sector = current_read_lba + ((current_read_len - 1) % SECTOR_SIZE);

				/* Copy the data from the dvd buffer to the real user buffer. */
				if (cpy_dst != cpy_src) {
					memcpy(cpy_dst, cpy_src, cpy_size);
				}
				/* The game is expecting a DMA transfer, so we need to flush to physical memory.
				 * XXX: Removing this will increase speed.
				 */
#ifndef RELOAD_SUPPORT
				DCFlushRange(cpy_dst, cpy_size);
				ICInvalidateRange(cpy_dst, cpy_size);
#endif

				cpy_dst = NULL;
			}
		} else {
			debug_printf("DVD read error!\n");
		}

		if ((result & DVD_DEVICE_ERROR) && (current_read_errors < MAX_READ_RETRY)) {
			/* Retry on error. */
#if defined(MIOS_PLUGIN) || defined(HIGH_PLUGIN) /* Only MIOS and high version has enough memory. */
			debug_printf("outbuf 0x%x lba 0x%x len 0x%x\n", current_read_outbuf, current_read_lba, current_read_len);
#endif
			current_read_errors++;
#ifdef RELOAD_SUPPORT
			if (jmpNr == 0) {
#endif
				dvd_read_orig1(current_read_outbuf, current_read_len, current_read_lba, DI_callback);
#ifdef RELOAD_SUPPORT
			} else {
				dvd_read_orig2(current_read_outbuf, current_read_len, current_read_lba, DI_callback);
			}
#endif
		} else {
			/* Process next data. */
			DI_loop(dvd_callback_function, result);
		}
	}
}

/** Execute read transfer.
 * @param outbuf Destination buffer for read data (must be 32 Byte aligned).
 * @param len Size of transfer (multiple of 0x800).
 * @param callback_fn Callback function if transfer is complete or on error.
 * @returns Always 0 (success)
 */
int DI_ReadDvd(void *outbuf, unsigned long len, unsigned long lba, interrupt_callback_t *callback_fn)
{
#ifdef GEKKO_DEBUG
#if 0
	int i;

	/** DI hardware register (DVD). */
	volatile uint32_t *dvdio = (volatile uint32_t *)0xCC006000;
#endif
#endif

	/* Invalidate cache
	 * XXX: To increase speed move to DI_ReadSector, because the DVD driver should already have been done this.
	 */
#if defined(MIOS_PLUGIN) || defined(HIGH_PLUGIN) /* Only MIOS and high version has enough memory. */
	DCInvalidateRange(outbuf, len);
#else
	/* This may be slower. */
	DCFlushRange(outbuf, len);
	ICInvalidateRange(outbuf, len);
#endif

	/* Set callback function. */
	dvd_callback_function = callback_fn;

	/* Store values, so read can be repeated on error. */
	current_read_outbuf = outbuf;
	current_read_len = len;
	current_read_lba = lba;
	current_read_errors = 0;

#if defined(MIOS_PLUGIN) || defined(HIGH_PLUGIN) /* Only MIOS and high version has enough memory. */
	debug_printf("outbuf 0x%x lba 0x%x len 0x%x\n", outbuf, lba, len);
#endif

	/* Call original DVD read function with changed parameters. */
	/* The original DVD read function is patched to use 0xD0 instead of 0xA8. */
#ifdef RELOAD_SUPPORT
	if (jmpNr == 0) {
#endif
		dvd_read_orig1(outbuf, len, lba, DI_callback);
#ifdef RELOAD_SUPPORT
	} else {
		dvd_read_orig2(outbuf, len, lba, DI_callback);
	}
#endif

#ifdef GEKKO_DEBUG
#if 0
	/* Print DI register for debug purpose. */
	for (i = 0; i < 9; i++) {
		debug_printf("dvdio[0x%x] 0x%x\n", i, dvdio[i]);
	}
#endif
#endif
	return 0;
}

/** Read one sectors to dvd buffer and copy later to memory address where it is expected using offset pos within sector,
 * because of the aligment restrictions.
 * @param outbuf Destination buffer for read data (need not to be aligned).
 * @param len Size of transfer (need not to be a multiple of something).
 * @param pos Offset within sector.
 * @param lba lba * SECTOR_SIZE is start offset of sector.
 * @param callback_fn Callback function if transfer is complete or on error.
 * @returns Always 0 (success)
 */
int DI_ReadSector(void *outbuf, unsigned long len, unsigned long pos, unsigned long lba, interrupt_callback_t *callback_fn)
{
	int ret = 0;

	debug_printf("DI_ReadSector b 0x%x, pos 0x%x, len 0x%x, lba 0x%x\n",
		outbuf, pos, len, lba);

	/* Check length */
	if ((len + pos) > SECTOR_SIZE)
		return 1;
	
	/* Store what to copy in callback function. */
	// If this is before DI_ReadDvd, the plugin is smaller
	cpy_src = dvd_buffer + pos;
	cpy_dst = outbuf;
	cpy_size = len;

	/* Use temporary dvd buffer for reading. */
	/* Read sector */
	ret = DI_ReadDvd(dvd_buffer, SECTOR_SIZE, lba, callback_fn);
	
	return ret;
}

/** Check if data can be transfered using DMA for the expected target address.
 * @returns 0 if DMA is not possible, otherwise multiple of SECTOR_SIZE.
 */
unsigned long DI_DMACheck(unsigned long mem, unsigned long size)
{
	unsigned long ret = 0;

	mem = mem & 0x0FFFFFFF;

	/* Check for memory alignment */
	if (!(mem % MEMORY_ALIGNMENT)) {
		unsigned long dmalen = 0;

		/* DMA1 range check */
		if ((mem >= DMA1_START_ADDRESS) && (mem < DMA1_END_ADDRESS))
			dmalen = (DMA1_END_ADDRESS - mem);

		if (dmalen > SECTOR_SIZE) {
			ret = (dmalen < size) ? dmalen : size;
			/* Align to SECTOR_SIZE. */
			ret -= ret % SECTOR_SIZE;
		}
	}

	return ret;
}

/** Each time called one block of data is transfered. Call callback function if finished.
 * @param callback_fn Callback function if transfer is complete or an error happened.
 * @param result Result of last transfer.
 * @return 0 on success
 * @return 1 on error
 */
int DI_loop(interrupt_callback_t *callback_fn, int result)
{
	int ret;

	if ((global_read_cnt < global_read_len) && (result == DVD_TRANSFER_COMPLETE)) {
		unsigned long dmalen, blksize, mem, off_lba, pos;

		/* Output buffer address */
		mem  = (unsigned long)(global_read_buffer + global_read_cnt);

		/* Set values */
		blksize = global_read_len - global_read_cnt;
		off_lba = global_read_lba << 9;
		pos     = (global_read_offset > off_lba) ? (global_read_offset - off_lba) << 2 : 0;

		/* Check size limit */
		if (blksize > MAX_SECTOR_SIZE)
			blksize = MAX_SECTOR_SIZE;

		/* Check DMA range. */
		dmalen = DI_DMACheck(mem, blksize);

		/* Use proper read method */
		if ((dmalen == 0) || pos) {
			/* Check size limit */
			if ((blksize + pos) > SECTOR_SIZE)
				blksize = SECTOR_SIZE - pos;

			/* Read sector */
			ret = DI_ReadSector((void *) mem, blksize, pos, global_read_lba, callback_fn);

			/* Update values */
			global_read_cnt += blksize;
			global_read_lba += (blksize + pos) >> 11;
		} else {
			/* Read data */
			ret = DI_ReadDvd((void *) mem, dmalen, global_read_lba, callback_fn);

			/* Update values */
			global_read_cnt += dmalen;
			global_read_lba += dmalen >> 11;
		}

		if (ret)
			return ret;
	} else {
		/* All data transfered. */
		interrupt_callback_t *callback_function;

		/* Call real callback function. */
		callback_function = dvd_callback_function;
		dvd_callback_function = NULL;

#ifdef MIOS_PLUGIN
		if (result & DVD_TRANSFER_COMPLETE) {
			dvd_patchread(global_read_buffer, global_read_len, LOW_PLUGIN_BASE, -1);
			DCFlushRange(global_read_buffer, global_read_len);
			ICInvalidateRange(global_read_buffer, global_read_len);
		}
#else
#ifdef RELOAD_SUPPORT
		if (result & DVD_TRANSFER_COMPLETE) {
			/* Check if the data, which the game load, need to be patched. */
			/* Patch jumptable which is currently not used. */
			if (jmpNr == 0) {
				dvd_patchread(global_read_buffer, global_read_len, &jumptable[1], 0);
			} else {
				dvd_patchread(global_read_buffer, global_read_len, &jumptable[0], 0);
			}
			DCFlushRange(global_read_buffer, global_read_len);
			ICInvalidateRange(global_read_buffer, global_read_len);
		}
#endif
#endif

		callback_function(result);
	}

	return 0;
}

/** Setup global variables for next request and transfer all data.
 * @param out Output buffer.
 * @param length Length of data to be read.
 * @param off DVD offset (ahiftes >> 2).
 * @param callback_fn Callback function if transfer is finished or has an error.
 */
int DI_ReadUnencrypted(unsigned char *out, unsigned long length, unsigned long off, interrupt_callback_t *callback_fn)
{
	debug_printf("DI_ReadUnencrypted() mem 0x%x, offset 0x%x, len 0x%x\n",
		out, off, length);

	global_read_cnt = 0;
	global_read_len = length;
	global_read_offset = off;
	global_read_buffer = out;

	/* Initial LBA */
	global_read_lba = off >> 9;

	return DI_loop(callback_fn, DVD_TRANSFER_COMPLETE);
}

/** This function replaces the original DVD read function.
 * @param buffer Destination address for DVD read (this is always 32 Byte aligned).
 * @param size Length of data to be read.
 * @param offset Offset to DVD data (already shifted by >> 2 by MIOS), Byte offset to DVD data in games.
 * param callback_fn Function is called if DVD transfer is complete or on error.
 */
void dvd_read_replacement1(
	void *buffer,
	unsigned long size,
	unsigned long offset,
	interrupt_callback_t *callback_fn)
{
	exception_init();
#ifdef RELOAD_SUPPORT
	jmpNr = 0;
#endif

#ifdef MIOS_PLUGIN
	/* MIOS use unshifted offset, later reloaded games use shifted offset. */
	debug_printf("dvd_read_replacement MIOS 0x%x size 0x%x offset 0x%x called by 0x%x callback 0x%x\n", buffer, size, offset, __builtin_return_address(0), callback_fn);
	DI_ReadUnencrypted(buffer, size, base_offset + offset, callback_fn);
#else
	/* GC games */
	//debug_printf("dvd_read_replacement 0x%x size 0x%x offset 0x%x called by 0x%x callback 0x%x\n", buffer, size, offset, __builtin_return_address(0), callback_fn);
	DI_ReadUnencrypted(buffer, size, base_offset + (offset >> 2), callback_fn);
#endif
}

#ifdef RELOAD_SUPPORT
/** This function replaces the original DVD read function.
 * @param buffer Destination address for DVD read (this is always 32 Byte aligned).
 * @param size Length of data to be read.
 * @param offset Offset to DVD data, Byte offset to DVD data in games.
 * param callback_fn Function is called if DVD transfer is complete or on error.
 */
void dvd_read_replacement2(
	void *buffer,
	unsigned long size,
	unsigned long offset,
	interrupt_callback_t *callback_fn)
{
	int ret;

	exception_init();
	jmpNr = 1;

	/* GC games */
#if defined(MIOS_PLUGIN) || defined(HIGH_PLUGIN) /* Only MIOS and high version has enough memory. */
	debug_printf("dvd_read_replacement 0x%x size 0x%x offset 0x%x called by 0x%x callback 0x%x\n", buffer, size, offset, __builtin_return_address(0), callback_fn);
#endif
	ret = DI_ReadUnencrypted(buffer, size, base_offset + (offset >> 2), callback_fn);

	if (ret) {
		debug_printf("Error: DI_ReadUnencrypted() failed\n");
	}
}
#endif

void dvd_read_audio_replacement1(
	unsigned long subcmd,
	unsigned long length,
	unsigned long offset,
	interrupt_callback_t *callback_fn)
{
	exception_init();

#ifdef RELOAD_SUPPORT
	jmpNr = 0;
#endif

	debug_printf("dvd_read_audio_replacement1 subcmd 0x%x length 0x%x offset 0x%x callback 0x%x\n", subcmd, length, offset, callback_fn);

	/* Data not important, just read from the specific lba. */
	offset = (offset + SECTOR_SIZE - 1) & ~(SECTOR_SIZE - 1);

	/* Read one sector to simulate audio read. */
	/* XXX: Maybe a different call is faster for read audio. */
	DI_ReadUnencrypted(dvd_buffer, SECTOR_SIZE, last_sector << 9, callback_fn);
}

#ifdef RELOAD_SUPPORT
void dvd_read_audio_replacement2(
	unsigned long subcmd,
	unsigned long length,
	unsigned long offset,
	interrupt_callback_t *callback_fn)
{
	exception_init();
	jmpNr = 1;

	/* GC games */
	debug_printf("dvd_read_audio_replacement2 subcmd 0x%x length 0x%x offset 0x%x callback 0x%x\n", subcmd, length, offset, callback_fn);

	/* Data not important, just read from the specific lba. */
	offset = (offset + SECTOR_SIZE - 1) & ~(SECTOR_SIZE - 1);

	/* XXX: Maybe a different call is faster for read audio. */
	DI_ReadUnencrypted(dvd_buffer, SECTOR_SIZE, last_sector << 9, callback_fn);
}
#endif

void dvd_audio_config_replacement1(
	unsigned long activate,
	unsigned long size,
	interrupt_callback_t *callback_fn)
{
	exception_init();

#ifdef RELOAD_SUPPORT
	jmpNr = 0;
#endif

	debug_printf("dvd_read_config_replacement1 activate 0x%x size 0x%x callback 0x%x\n", activate, size, callback_fn);

	/* Read one sector to simulate audio config. */
	/* XXX: Use a faster call for simulating audio buffer config. */
	DI_ReadUnencrypted(dvd_buffer, SECTOR_SIZE, last_sector << 9, callback_fn);
}

#ifdef RELOAD_SUPPORT
void dvd_audio_config_replacement2(
	unsigned long activate,
	unsigned long size,
	interrupt_callback_t *callback_fn)
{
	exception_init();

	jmpNr = 1;

	debug_printf("dvd_read_config_replacement2 activate 0x%x size 0x%x callback 0x%x\n", activate, size, callback_fn);

	/* Read one sector to simulate audio config. */
	/* XXX: Use a faster call for simulating audio buffer config. */
	DI_ReadUnencrypted(dvd_buffer, SECTOR_SIZE, last_sector << 9, callback_fn);
}
#endif

void dvd_seek_replacement1(
	unsigned long offset,
	interrupt_callback_t *callback_fn)
{
	exception_init();

#ifdef RELOAD_SUPPORT
	jmpNr = 0;
#endif

	debug_printf("dvd_seek_replacement1 offset 0x%x callback 0x%x\n", offset, callback_fn);

	/* Data not important, just read from the specific lba. */
	offset = (offset + SECTOR_SIZE - 1) & ~(SECTOR_SIZE - 1);

	/* Read last sector to simulate seek. */
	DI_ReadUnencrypted(dvd_buffer, SECTOR_SIZE, last_sector << 9, callback_fn);
}

#ifdef RELOAD_SUPPORT
void dvd_seek_replacement2(
	unsigned long offset,
	interrupt_callback_t *callback_fn)
{
	exception_init();

	jmpNr = 1;

	debug_printf("dvd_seek_replacement2 offset 0x%x callback 0x%x\n", offset, callback_fn);

	/* Data not important, just read from the specific lba. */
	offset = (offset + SECTOR_SIZE - 1) & ~(SECTOR_SIZE - 1);

	/* Read last sector to simulate seek. */
	DI_ReadUnencrypted(dvd_buffer, SECTOR_SIZE, last_sector << 9, callback_fn);
}
#endif

/** This function replaces the original DVD read disk id function.
 * @param buffer Destination address for read.
 * @param callback_fn Callback function if transfer is complete or on error.
 */
void dvd_read_id_replacement1(
	void *buffer,
	interrupt_callback_t *callback_fn)
{
	exception_init();
#ifdef RELOAD_SUPPORT
	jmpNr = 0;
#endif

	/* This function is called after dvd_reset_replacement(). */
	debug_printf("dvd_read_id_replacement1 called by 0x%x buffer 0x%x callback 0x%x\n", __builtin_return_address(0), buffer, callback_fn);

#if 0
	dvd_read_id_orig1(buffer, callback_fn);
#else

#ifndef MIOS_PLUGIN			// Why this restriction?
	u32 saved_offset;

	/* Switch disc: */
	saved_offset = base_offset;
	base_offset = base_offset_2nd_disc;
	base_offset_2nd_disc = saved_offset;
#endif
	/* Simulate DVD Read Disc ID by reading first 32 Bytes of disc. */
	DI_ReadUnencrypted(buffer, 32, base_offset, callback_fn);
#endif
}

#ifdef RELOAD_SUPPORT
/** This function replaces the original DVD read disk id function.
 * @param buffer Destination address for read.
 * @param callback_fn Callback function if transfer is complete or on error.
 */
void dvd_read_id_replacement2(
	void *buffer,
	interrupt_callback_t *callback_fn)
{
	exception_init();
	jmpNr = 1;

	/* This function is called after dvd_reset_replacement(). */
	debug_printf("dvd_read_id_replacement2 called by 0x%x buffer 0x%x callback 0x%x\n", __builtin_return_address(0), buffer, callback_fn);

//#ifdef MIOS_PLUGIN	// Why this restriction?
	u32 saved_offset;

	/* Switch disc. */
	saved_offset = base_offset;
	base_offset = base_offset_2nd_disc;
	base_offset_2nd_disc = saved_offset;
//#endif
#if 0
	dvd_read_id_orig2(buffer, callback_fn);
#else
	/* Simulate DVD Read Disc ID by reading first 32 Bytes of disc. */
	DI_ReadUnencrypted(buffer, 32, base_offset, callback_fn);
#endif
}
#endif

void dvd_report_error_replacement1(
	interrupt_callback_t *function)
{
	exception_init();

#ifdef RELOAD_SUPPORT
	//jmpNr = 0;	Not required here
#endif

	debug_printf("dvd_report_error_orig1 call back 0x%x\n", function);
	dvd_report_error_orig1(function);
}

#ifdef RELOAD_SUPPORT
void dvd_report_error_replacement2(
	interrupt_callback_t *function)
{
	exception_init();

	//jmpNr = 1;	Not required here

	debug_printf("dvd_report_error_orig2 call back 0x%x\n", function);
	dvd_report_error_orig2(function);
}
#endif


void dvd_audio_status_replacement1(
	u32 subcmd,
	interrupt_callback_t *function)
{
	exception_init();

#ifdef RELOAD_SUPPORT
	//jmpNr = 0;	Not required here
#endif

	debug_printf("dvd_audio_status_orig1 call subcmd 0x%x back 0x%x\n", subcmd, function);
#if 0
	dvd_audio_status_orig1(subcmd, function);
#else
	/* Simulate by calling report error. */
	dvd_report_error_orig1(function);
#endif
}

#ifdef RELOAD_SUPPORT
void dvd_audio_status_replacement2(
	u32 subcmd,
	interrupt_callback_t *function)
{
	exception_init();

	//jmpNr = 1;	Not required here

	debug_printf("dvd_audio_status_orig2 call subcmd 0x%x back 0x%x\n", subcmd, function);
#if 0
	dvd_audio_status_orig2(subcmd, function);
#else
	/* Simulate by calling report error. */
	dvd_report_error_orig2(function);
#endif
}
#endif



#ifdef GEKKO_DEBUG

#if defined(MIOS_PLUGIN) || defined(HIGH_PLUGIN) /* Only MIOS and high version has enough memory. */
/** This function replaces the original DVD reset function. */
void dvd_reset_replacement1(void)
{
	exception_init();
#ifdef RELOAD_SUPPORT
	jmpNr = 0;
#endif

	/* This function will be called if DVD was ejected and reinserted.*/
	debug_printf("dvd_reset_replacement1 called\n");

	dvd_reset_orig1();
}

#ifdef RELOAD_SUPPORT
/** This function replaces the original DVD reset function. */
void dvd_reset_replacement2(void)
{
	exception_init();
	jmpNr = 1;

	/* This function will be called if DVD was ejected and reinserted.*/
	debug_printf("dvd_reset_replacement2 called\n");

	dvd_reset_orig2();
}
#endif
#endif

#ifdef REPLACE_DVD_CALLBACK
/** Replacement for callback in DVD driver code (only for debugging).
 * @param result Result of last transfer.
 * @param callback_fn Callback function that would normally be called.
 */
void dvd_callback_replacement1(int result, interrupt_callback_t *callback_fn)
{
#ifdef RELOAD_SUPPORT
	jmpNr = 0;
#endif

	debug_printf("dvd_callback_replacement1 result 0x%x, fn 0x%x called by 0x%x\n", result, callback_fn, __builtin_return_address(0));
	callback_fn(result);
}

#ifdef RELOAD_SUPPORT
/** Replacement for callback in DVD driver code (only for debugging).
 * @param result Result of last transfer.
 * @param callback_fn Callback function that would normally be called.
 */
void dvd_callback_replacement2(int result, interrupt_callback_t *callback_fn)
{
	exception_init();
	jmpNr = 1;

	debug_printf("dvd_callback_replacement2 result 0x%x, fn 0x%x called by 0x%x\n", result, callback_fn, __builtin_return_address(0));
	callback_fn(result);
}
#endif
#endif

#if defined(MIOS_PLUGIN) || defined(HIGH_PLUGIN) /* Only MIOS and high version has enough memory. */
void printDVDRegs(void)
{
	int i;

	/** DI hardware register (DVD). */
	volatile uint32_t *dvdio = (volatile uint32_t *)0xCC006000;

	/* Print DI register for debug purpose. */
	for (i = 0; i < 9; i++) {
		debug_printf("dvdio[0x%x] 0x%x\n", i, dvdio[i]);
	}
}
#endif

#if defined(MIOS_PLUGIN) || defined(HIGH_PLUGIN) /* Only MIOS and high version has enough memory. */
void dvd_stop_motor_replacement1(
	interrupt_callback_t *function)
{
	exception_init();

#ifdef RELOAD_SUPPORT
	jmpNr = 0;
#endif

	debug_printf("dvd_stop_motor_orig1 call back 0x%x\n", function);
	dvd_stop_motor_orig1(function);
}

#ifdef RELOAD_SUPPORT
void dvd_stop_motor_replacement2(
	interrupt_callback_t *function)
{
	exception_init();

	jmpNr = 1;

	debug_printf("dvd_stop_motor_orig2 call back 0x%x\n", function);
	dvd_stop_motor_orig2(function);
}
#endif
#endif

#endif





