Files
vbagx/source/ngc/dvd.cpp
dborth 2d9b7ef1f9 [1.0.8 - April 4, 2009]
* "Match Wii Game" controls option! Games that have a Wii equivalent can be
  played using the controls for that Wii game. For example all Zelda games
  can be played with Twilight Princess controls. See the Instructions section
  below for important details.
* Rotation/Tilt sensor games all work
* Solar sensors (Boktai 1/2/3)
* Rumble (except for games that rely on Gameboy Player)
* Keyboard
* PAL support, finally!
* New scaling options, choose how much stretching you want
* Colourised games now partially work but still have distortion
* "Corvette" no longer has a screwed up palette (but still crashes)
* Triggers net reconnection on SMB failure
* Source code refactored, and project file added
* Instructions section added to this readme file
2009-04-10 03:16:28 +00:00

797 lines
21 KiB
C++

/****************************************************************************
* Visual Boy Advance GX
*
* Tantric September 2008
*
* dvd.cpp
*
* DVD I/O functions
***************************************************************************/
#include <gccore.h>
#include <ogcsys.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#ifdef HW_RVL
extern "C" {
#include <di/di.h>
}
#endif
#include "menudraw.h"
#include "gcunzip.h"
#include "filesel.h"
#include "vba.h"
#define MAXDVDFILES 2000
static int diroffset = 0;
static u64 dvddir = 0; // offset of currently selected file or folder
static int dvddirlength = 0; // length of currently selected file or folder
static u64 dvdrootdir = 0; // offset of DVD root
static int dvdrootlength = 0; // length of DVD root
static bool isWii = false;
#ifdef HW_DOL
/** DVD I/O Address base **/
volatile unsigned long *dvd = (volatile unsigned long *) 0xCC006000;
#endif
/****************************************************************************
* dvd_read
*
* Main DVD function, everything else uses this!
* returns: 1 - ok ; 0 - error
***************************************************************************/
#define ALIGN_FORWARD(x,align) ((typeof(x))((((uint32_t)(x)) + (align) - 1) & (~(align-1))))
#define ALIGN_BACKWARD(x,align) ((typeof(x))(((uint32_t)(x)) & (~(align-1))))
static int
dvd_read (void *dst, unsigned int len, u64 offset)
{
if (len > 2048)
return 0; /*** We only allow 2k reads **/
// don't read past the end of the DVD (1.5 GB for GC DVD, 4.7 GB for DVD)
if((offset < 0x57057C00) || (isWii && (offset < 0x118244F00LL)))
{
u8 * buffer = (u8 *)memalign(32, 0x8000);
u32 off_size = 0;
DCInvalidateRange ((void *) buffer, len);
#ifdef HW_DOL
dvd[0] = 0x2E;
dvd[1] = 0;
dvd[2] = 0xA8000000;
dvd[3] = (u32)(offset >> 2);
dvd[4] = len;
dvd[5] = (u32) buffer;
dvd[6] = len;
dvd[7] = 3;
// Enable reading with DMA
while (dvd[7] & 1);
// Ensure it has completed
if (dvd[0] & 0x4)
return 0;
#else
off_size = offset - ALIGN_BACKWARD(offset,0x800);
if (DI_ReadDVD(
buffer,
(ALIGN_FORWARD(offset + len,0x800) - ALIGN_BACKWARD(offset,0x800)) >> 11,
(u32)(ALIGN_BACKWARD(offset, 0x800) >> 11)
))
return 0;
#endif
memcpy (dst, buffer+off_size, len);
free(buffer);
return 1;
}
return 0;
}
/****************************************************************************
* dvd_buffered_read
*
* the GC's dvd drive only supports offsets and length which are a multiple
* of 32 bytes additionally the max length of a read is 2048 bytes
* this function removes these limitations
* additionally the 7zip SDK does often read data in 1 byte parts from the
* DVD even when it could read 32 bytes. the dvdsf_buffer has been added to
* avoid having to read the same sector over and over again
***************************************************************************/
#define DVD_LENGTH_MULTIPLY 32
#define DVD_OFFSET_MULTIPLY 32
#define DVD_MAX_READ_LENGTH 2048
#define DVD_SECTOR_SIZE 2048
static unsigned char dvdsf_buffer[DVD_SECTOR_SIZE];
static u64 dvdsf_last_offset = 0;
static u64 dvdsf_last_length = 0;
static int dvd_buffered_read(void *dst, u32 len, u64 offset)
{
int ret = 1;
// only read data if the data inside dvdsf_buffer cannot be used
if(offset != dvdsf_last_offset || len > dvdsf_last_length)
{
memset(&dvdsf_buffer, '\0', DVD_SECTOR_SIZE);
ret = dvd_read(&dvdsf_buffer, len, offset);
dvdsf_last_offset = offset;
dvdsf_last_length = len;
}
memcpy(dst, &dvdsf_buffer, len);
return ret;
}
/****************************************************************************
* dvd_safe_read
*
* A 'safer' DVD read function
* This function relies on dvddir (file offset) being prepopulated!
* returns: 1 - ok ; 0 - error
***************************************************************************/
int dvd_safe_read(void *dst_v, u32 len, u64 fileoffset)
{
u64 offset = dvddir + fileoffset;
unsigned char buffer[DVD_SECTOR_SIZE]; // buffer for one dvd sector
// if read size and length are a multiply of DVD_(OFFSET,LENGTH)_MULTIPLY and length < DVD_MAX_READ_LENGTH
// we don't need to fix anything
if(len % DVD_LENGTH_MULTIPLY == 0 && offset % DVD_OFFSET_MULTIPLY == 0 && len <= DVD_MAX_READ_LENGTH)
{
int ret = dvd_buffered_read(buffer, len, offset);
memcpy(dst_v, &buffer, len);
return ret;
}
else
{
// no errors yet -> ret = 1
// the return value of dvd_read will be AND'd with ret
// because dvd_read does return 0 on error and 1 on success and
// because 1 & 0 = 0 ret will also contain 0 if at least one error
// occured and 1 otherwise ;)
int ret = 1; // return value of dvd_read
// we might need to fix all 3 issues
unsigned char *dst = (unsigned char *)dst_v; // gcc will not allow to use var[num] on void* types
u64 bytesToRead; // the number of bytes we still need to read & copy to the output buffer
u64 currentOffset; // the current dvd offset
u64 bufferOffset; // the current buffer offset
u64 i, j, k; // temporary variables which might be used for different stuff
// unsigned char buffer[DVD_SECTOR_SIZE]; // buffer for one dvd sector
currentOffset = offset;
bytesToRead = len;
bufferOffset = 0;
// fix first issue (offset is not a multiply of 32)
if(offset % DVD_OFFSET_MULTIPLY)
{
// calculate offset of the prior 32 byte position
i = currentOffset - (currentOffset % DVD_OFFSET_MULTIPLY);
// calculate the offset from which the data of the dvd buffer will be copied
j = currentOffset % DVD_OFFSET_MULTIPLY;
// calculate the number of bytes needed to reach the next DVD_OFFSET_MULTIPLY byte mark
k = DVD_OFFSET_MULTIPLY - j;
// maybe we'll only need to copy a few bytes and we therefore don't even reach the next sector
if(k > len)
{
k = len;
}
// read 32 bytes from the last 32 byte position
ret &= dvd_buffered_read(buffer, DVD_OFFSET_MULTIPLY, i);
// copy the bytes to the output buffer and update currentOffset, bufferOffset and bytesToRead
memcpy(&dst[bufferOffset], &buffer[j], k);
currentOffset += k;
bufferOffset += k;
bytesToRead -= k;
}
// fix second issue (more than 2048 bytes are needed)
if(bytesToRead > DVD_MAX_READ_LENGTH)
{
// calculate the number of 2048 bytes sector needed to get all data
i = (bytesToRead - (bytesToRead % DVD_MAX_READ_LENGTH)) / DVD_MAX_READ_LENGTH;
// read data in 2048 byte sector
for(j = 0; j < i; j++)
{
ret &= dvd_buffered_read(buffer, DVD_MAX_READ_LENGTH, currentOffset); // read sector
memcpy(&dst[bufferOffset], buffer, DVD_MAX_READ_LENGTH); // copy to output buffer
// update currentOffset, bufferOffset and bytesToRead
currentOffset += DVD_MAX_READ_LENGTH;
bufferOffset += DVD_MAX_READ_LENGTH;
bytesToRead -= DVD_MAX_READ_LENGTH;
}
}
// fix third issue (length is not a multiply of 32)
if(bytesToRead)
{
ret &= dvd_buffered_read(buffer, DVD_MAX_READ_LENGTH, currentOffset); // read 32 byte from the dvd
memcpy(&dst[bufferOffset], buffer, bytesToRead); // copy bytes to output buffer
}
return ret;
}
}
/** Minimal ISO Directory Definition **/
#define RECLEN 0 /* Record length */
#define EXTENT 6 /* Extent */
#define FILE_LENGTH 14 /* File length (BIG ENDIAN) */
#define FILE_FLAGS 25 /* File flags */
#define FILENAME_LENGTH 32 /* Filename length */
#define FILENAME 33 /* ASCIIZ filename */
/** Minimal Primary Volume Descriptor **/
#define PVDROOT 0x9c
static int IsJoliet = 0;
/****************************************************************************
* Primary Volume Descriptor
*
* The PVD should reside between sector 16 and 31.
* This is for single session DVD only.
***************************************************************************/
static int
getpvd ()
{
int sector = 16;
u32 rootdir32;
unsigned char dvdbuffer[2048];
dvddir = dvddirlength = 0;
IsJoliet = -1;
/** Look for Joliet PVD first **/
while (sector < 32)
{
if (dvd_read (&dvdbuffer, 2048, (u64)(sector << 11)))
{
if (memcmp (&dvdbuffer, "\2CD001\1", 8) == 0)
{
memcpy(&rootdir32, &dvdbuffer[PVDROOT + EXTENT], 4);
dvddir = (u64)rootdir32;
dvddir <<= 11;
memcpy (&dvddirlength, &dvdbuffer[PVDROOT + FILE_LENGTH], 4);
dvdrootdir = dvddir;
dvdrootlength = dvddirlength;
IsJoliet = 1;
break;
}
}
else
return 0; /*** Can't read sector! ***/
sector++;
}
if (IsJoliet > 0) /*** Joliet PVD Found ? ***/
return 1;
sector = 16;
/*** Look for standard ISO9660 PVD ***/
while (sector < 32)
{
if (dvd_read (&dvdbuffer, 2048, sector << 11))
{
if (memcmp (&dvdbuffer, "\1CD001\1", 8) == 0)
{
memcpy (&rootdir32, &dvdbuffer[PVDROOT + EXTENT], 4);
dvddir = (u64)rootdir32;
dvddir <<= 11;
memcpy (&dvddirlength, &dvdbuffer[PVDROOT + FILE_LENGTH], 4);
dvdrootdir = dvddir;
dvdrootlength = dvddirlength;
IsJoliet = 0;
break;
}
}
else
return 0; /*** Can't read sector! ***/
sector++;
}
return (IsJoliet == 0);
}
/****************************************************************************
* MountDVD()
*
* Tests if a ISO9660 DVD is inserted and available, and mounts it
***************************************************************************/
bool MountDVD(bool silent)
{
if (!getpvd())
{
ShowAction("Loading DVD...");
#ifdef HW_DOL
DVD_Mount(); // mount the DVD unit again
#elif HW_RVL
u32 val;
DI_GetCoverRegister(&val);
if(val & 0x1) // True if no disc inside, use (val & 0x2) for true if disc inside.
{
if(!silent)
WaitPrompt("No disc inserted!");
return false;
}
DI_Mount();
while(DI_GetStatus() & DVD_INIT);
#endif
if (!getpvd())
{
if(!silent)
WaitPrompt ("Invalid DVD.");
return false;
}
}
return true;
}
/****************************************************************************
* getentry
*
* Support function to return the next file entry, if any
* Declared static to avoid accidental external entry.
***************************************************************************/
static int
getentry (int entrycount, unsigned char dvdbuffer[])
{
char fname[512]; /* Huge, but experience has determined this */
char tmpname[512];
char *ptr;
char *filename;
char *filenamelength;
char *rr;
int j;
u32 offset32;
/* Basic checks */
if (entrycount >= MAXDVDFILES)
return 0;
if (diroffset >= 2048 || diroffset < 0)
return 0;
/** Decode this entry **/
if (dvdbuffer[diroffset]) /* Record length available */
{
/* Update offsets into sector buffer */
ptr = (char *) &dvdbuffer[0];
ptr += diroffset;
filename = ptr + FILENAME;
filenamelength = ptr + FILENAME_LENGTH;
/* Check for wrap round - illegal in ISO spec,
* but certain crap writers do it! */
if ((diroffset + dvdbuffer[diroffset]) > 2048 || (diroffset + dvdbuffer[diroffset]) < 0)
return 0;
if (*filenamelength)
{
memset (&fname, 0, 512);
if (!IsJoliet) /*** Do ISO 9660 first ***/
{
strncpy (fname, filename, 512);
}
else
{ /*** The more tortuous unicode joliet entries ***/
for (j = 0; j < (*filenamelength >> 1); j++)
{
fname[j] = filename[j * 2 + 1];
}
fname[j] = 0;
if (strlen (fname) >= MAXJOLIET)
fname[MAXJOLIET - 1] = 0;
if (strlen (fname) == 0)
fname[0] = filename[0];
}
if (strlen (fname) == 0) // root entry
{
fname[0] = 0; // we'll skip it by setting the filename to 0 length
}
else
{
if (fname[0] == 1)
{
if(dvddir == dvdrootdir) // at root already, don't show ..
fname[0] = 0;
else
strcpy (fname, "..");
}
else
{
/*
* Move *filenamelength to t,
* Only to stop gcc warning for noobs :)
*/
int t = *filenamelength;
fname[t] = 0;
}
}
/** Rockridge Check **/
rr = strstr (fname, ";");
if (rr != NULL)
*rr = 0;
BROWSERENTRY * newBrowserList = (BROWSERENTRY *)realloc(browserList, (entrycount+1) * sizeof(BROWSERENTRY));
if(!newBrowserList) // failed to allocate required memory
{
ResetBrowser();
WaitPrompt("Out of memory: too many files!");
return 0;
}
else
{
browserList = newBrowserList;
}
memset(&(browserList[entrycount]), 0, sizeof(BROWSERENTRY)); // clear the new entry
strncpy (browserList[entrycount].filename, fname, MAXJOLIET);
StripExt(tmpname, fname); // hide file extension
strncpy (browserList[entrycount].displayname, tmpname, MAXDISPLAY);
memcpy (&offset32, &dvdbuffer[diroffset + EXTENT], 4);
browserList[entrycount].offset = (u64)offset32;
memcpy (&(browserList[entrycount].length), &dvdbuffer[diroffset + FILE_LENGTH], 4);
memcpy (&(browserList[entrycount].isdir), &dvdbuffer[diroffset + FILE_FLAGS], 1);
browserList[entrycount].offset <<= 11;
browserList[entrycount].isdir = browserList[entrycount].isdir & 2;
/*** Prepare for next entry ***/
diroffset += dvdbuffer[diroffset];
return 1;
}
}
return 0;
}
/****************************************************************************
* ParseDVDdirectory
*
* This function will parse the directory tree.
* It relies on dvddir and dvddirlength being pre-populated by a call to
* getpvd, a previous parse or a menu selection.
*
* The return value is number of files collected, or 0 on failure.
***************************************************************************/
int
ParseDVDdirectory ()
{
int pdlength;
u64 pdoffset;
u64 rdoffset;
int len = 0;
int filecount = 0;
unsigned char dvdbuffer[2048];
// reset browser
ResetBrowser();
pdoffset = rdoffset = dvddir;
pdlength = dvddirlength;
filecount = 0;
/*** Get as many files as possible ***/
while (len < pdlength)
{
if (dvd_read (&dvdbuffer, 2048, pdoffset) == 0)
return 0;
diroffset = 0;
while (getentry (filecount, dvdbuffer))
{
if(strlen(browserList[filecount].filename) > 0 && filecount < MAXDVDFILES)
filecount++;
}
len += 2048;
pdoffset = rdoffset + len;
}
// Sort the file list
qsort(browserList, filecount, sizeof(BROWSERENTRY), FileSortCallback);
return filecount;
}
/****************************************************************************
* SetDVDdirectory
* Set the current DVD file offset
***************************************************************************/
void SetDVDdirectory(u64 dir, int length)
{
dvddir = dir;
dvddirlength = length;
}
/****************************************************************************
* DirectorySearch
*
* Searches for the directory name specified within the current directory
* Returns the index of the directory, or -1 if not found
***************************************************************************/
static int DirectorySearch(char dir[512])
{
for (int i = 0; i < browser.numEntries; i++ )
if (strcmp(browserList[i].filename, dir) == 0)
return i;
return -1;
}
/****************************************************************************
* SwitchDVDFolderR
*
* Recursively searches for any directory path 'dir' specified
* Also can be used to find and set the offset for a file
* Also loads the directory contents via ParseDVDdirectory()
* It relies on dvddir, dvddirlength, and filelist being pre-populated
***************************************************************************/
static bool SwitchDVDFolderR(char * dir, int maxDepth)
{
if(maxDepth > 8) // only search to a max depth of 8 levels
return false;
bool lastdir = false;
char * nextdir = NULL;
unsigned int t = strcspn(dir, "/");
if(t != strlen(dir))
nextdir = dir + t + 1; // next directory path to find
else
lastdir = true;
dir[t] = 0;
int dirindex = DirectorySearch(dir);
if(dirindex >= 0)
{
dvddir = browserList[dirindex].offset;
dvddirlength = browserList[dirindex].length;
browser.selIndex = dirindex;
if(browserList[dirindex].isdir) // only parse directories
browser.numEntries = ParseDVDdirectory();
if(lastdir)
return true;
else
return SwitchDVDFolderR(nextdir, maxDepth++);
}
return false;
}
bool SwitchDVDFolder(char origdir[])
{
// make a copy of origdir so we don't mess with original
char dir[200];
strcpy(dir, origdir);
char * dirptr = dir;
// strip off leading/trailing slashes on the directory path
// we don't want to screw up our recursion!
if(dir[0] == '/')
dirptr = dirptr + 1;
if(dir[strlen(dir)-1] == '/')
dir[strlen(dir)-1] = 0;
// start searching at root of DVD
dvddir = dvdrootdir;
dvddirlength = dvdrootlength;
ParseDVDdirectory();
return SwitchDVDFolderR(dirptr, 0);
}
/****************************************************************************
* LoadDVDFileOffset
* This function will load a file from DVD
* It assumes dvddir and dvddirlength are prepopulated
***************************************************************************/
int
LoadDVDFileOffset (unsigned char *buffer, int length)
{
int offset;
int blocks;
int i;
int ret = 0;
u64 discoffset;
char readbuffer[2048];
// How many 2k blocks to read
blocks = dvddirlength / 2048;
offset = 0;
discoffset = dvddir;
ShowAction ("Loading...");
if(length > 0 && length <= 2048)
{
ret = dvd_read (buffer, length, discoffset);
if(ret <= 0) // read failure
return 0;
}
else // load whole file
{
ret = dvd_read (readbuffer, 2048, discoffset);
if(ret <= 0) // read failure
return 0;
if (IsZipFile (readbuffer))
{
return UnZipBuffer (buffer, METHOD_DVD); // unzip from dvd
}
else
{
for (i = 0; i < blocks; i++)
{
ret = dvd_read (readbuffer, 2048, discoffset);
if(ret <= 0) // read failure
return 0;
memcpy (buffer + offset, readbuffer, 2048);
offset += 2048;
discoffset += 2048;
ShowProgress ("Loading...", offset, length);
}
/*** And final cleanup ***/
if (dvddirlength % 2048)
{
i = dvddirlength % 2048;
ret = dvd_read (readbuffer, 2048, discoffset);
if(ret <= 0) // read failure
return 0;
memcpy (buffer + offset, readbuffer, i);
}
}
}
return dvddirlength;
}
/****************************************************************************
* LoadDVDFile
* This function will load a file from DVD, given a filepath
* It will attempt to find the offset of the file, and if successful it
* will populate dvddir and dvddirlength, and load the file
***************************************************************************/
int
LoadDVDFile(char * buffer, char *filepath, int datasize, bool silent)
{
if(SwitchDVDFolder(filepath))
{
return LoadDVDFileOffset ((unsigned char *)buffer, datasize);
}
else
{
if(!silent)
WaitPrompt("Error loading file!");
return 0;
}
}
/****************************************************************************
* uselessinquiry
*
* As the name suggests, this function is quite useless.
* It's only purpose is to stop any pending DVD interrupts while we use the
* memcard interface.
*
* libOGC tends to foul up if you don't, and sometimes does if you do!
***************************************************************************/
#ifdef HW_DOL
void uselessinquiry ()
{
dvd[0] = 0;
dvd[1] = 0;
dvd[2] = 0x12000000;
dvd[3] = 0;
dvd[4] = 0x20;
dvd[5] = 0x80000000;
dvd[6] = 0x20;
dvd[7] = 1;
while (dvd[7] & 1);
}
/****************************************************************************
* dvd_motor_off( )
* Turns off DVD drive motor so it doesn't make noise (Gamecube only)
***************************************************************************/
void dvd_motor_off ()
{
dvd[0] = 0x2e;
dvd[1] = 0;
dvd[2] = 0xe3000000;
dvd[3] = 0;
dvd[4] = 0;
dvd[5] = 0;
dvd[6] = 0;
dvd[7] = 1; // Do immediate
while (dvd[7] & 1);
/*** PSO Stops blackscreen at reload ***/
dvd[0] = 0x14;
dvd[1] = 0;
}
/****************************************************************************
* dvd_driveid
*
* Gets and returns the dvd driveid
***************************************************************************/
int dvd_driveid()
{
static unsigned char *inquiry=(unsigned char *)0x80000004;
dvd[0] = 0x2e;
dvd[1] = 0;
dvd[2] = 0x12000000;
dvd[3] = 0;
dvd[4] = 0x20;
dvd[5] = 0x80000000;
dvd[6] = 0x20;
dvd[7] = 3;
while( dvd[7] & 1 )
;
DCFlushRange((void *)0x80000000, 32);
return (int)inquiry[2];
}
#endif
/****************************************************************************
* SetDVDDriveType()
*
* Sets the DVD drive ID for use to determine disc size (1.5 GB or 4.7 GB)
***************************************************************************/
void SetDVDDriveType()
{
#ifdef HW_RVL
isWii = true;
#else
int drvid = dvd_driveid ();
if ( drvid == 4 || drvid == 6 || drvid == 8 )
isWii = false;
else
isWii = true;
#endif
}