/* Copyright 2004-2008 Theo Berkau1Copyright 2005 Joost Peters2Copyright 2005-2006 Guillaume Duhamel34This file is part of Yabause.56Yabause is free software; you can redistribute it and/or modify7it under the terms of the GNU General Public License as published by8the Free Software Foundation; either version 2 of the License, or9(at your option) any later version.1011Yabause is distributed in the hope that it will be useful,12but WITHOUT ANY WARRANTY; without even the implied warranty of13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the14GNU General Public License for more details.1516You should have received a copy of the GNU General Public License17along with Yabause; if not, write to the Free Software18Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA19*/2021#include <string.h>22#include <stdlib.h>23#include <assert.h>24#include "cdbase.h"25#include "error.h"26#include "debug.h"2728//////////////////////////////////////////////////////////////////////////////2930// Contains the Dummy and ISO CD Interfaces3132static int DummyCDInit(const char *);33static void DummyCDDeInit(void);34static int DummyCDGetStatus(void);35static s32 DummyCDReadTOC(u32 *);36static int DummyCDReadSectorFAD(u32, void *);37static void DummyCDReadAheadFAD(u32);3839CDInterface DummyCD = {40CDCORE_DUMMY,41"Dummy CD Drive",42DummyCDInit,43DummyCDDeInit,44DummyCDGetStatus,45DummyCDReadTOC,46DummyCDReadSectorFAD,47DummyCDReadAheadFAD,48};4950static int ISOCDInit(const char *);51static void ISOCDDeInit(void);52static int ISOCDGetStatus(void);53static s32 ISOCDReadTOC(u32 *);54static int ISOCDReadSectorFAD(u32, void *);55static void ISOCDReadAheadFAD(u32);5657CDInterface ISOCD = {58CDCORE_ISO,59"ISO-File Virtual Drive",60ISOCDInit,61ISOCDDeInit,62ISOCDGetStatus,63ISOCDReadTOC,64ISOCDReadSectorFAD,65ISOCDReadAheadFAD,66};6768//////////////////////////////////////////////////////////////////////////////69// Dummy Interface70//////////////////////////////////////////////////////////////////////////////7172static int DummyCDInit(UNUSED const char *cdrom_name)73{74// Initialization function. cdrom_name can be whatever you want it to75// be. Obviously with some ports(e.g. the dreamcast port) you probably76// won't even use it.77return 0;78}7980//////////////////////////////////////////////////////////////////////////////8182static void DummyCDDeInit(void)83{84// Cleanup function. Enough said.85}8687//////////////////////////////////////////////////////////////////////////////8889static int DummyCDGetStatus(void)90{91// This function is called periodically to see what the status of the92// drive is.93//94// Should return one of the following values:95// 0 - CD Present, disc spinning96// 1 - CD Present, disc not spinning97// 2 - CD not present98// 3 - Tray open99//100// If you really don't want to bother too much with this function, just101// return status 0. Though it is kind of nice when the bios's cd102// player, etc. recognizes when you've ejected the tray and popped in103// another disc.104105return 0;106}107108//////////////////////////////////////////////////////////////////////////////109110static s32 DummyCDReadTOC(UNUSED u32 *TOC)111{112// The format of TOC is as follows:113// TOC[0] - TOC[98] are meant for tracks 1-99. Each entry has the114// following format:115// bits 0 - 23: track FAD address116// bits 24 - 27: track addr117// bits 28 - 31: track ctrl118//119// Any Unused tracks should be set to 0xFFFFFFFF120//121// TOC[99] - Point A0 information122// Uses the following format:123// bits 0 - 7: PFRAME(should always be 0)124// bits 7 - 15: PSEC(Program area format: 0x00 - CDDA or CDROM,125// 0x10 - CDI, 0x20 - CDROM-XA)126// bits 16 - 23: PMIN(first track's number)127// bits 24 - 27: first track's addr128// bits 28 - 31: first track's ctrl129//130// TOC[100] - Point A1 information131// Uses the following format:132// bits 0 - 7: PFRAME(should always be 0)133// bits 7 - 15: PSEC(should always be 0)134// bits 16 - 23: PMIN(last track's number)135// bits 24 - 27: last track's addr136// bits 28 - 31: last track's ctrl137//138// TOC[101] - Point A2 information139// Uses the following format:140// bits 0 - 23: leadout FAD address141// bits 24 - 27: leadout's addr142// bits 28 - 31: leadout's ctrl143//144// Special Note: To convert from LBA/LSN to FAD, add 150.145146return 0;147}148149//////////////////////////////////////////////////////////////////////////////150151static int DummyCDReadSectorFAD(UNUSED u32 FAD, void * buffer)152{153// This function is supposed to read exactly 1 -RAW- 2352-byte sector154// at the specified FAD address to buffer. Should return true if155// successful, false if there was an error.156//157// Special Note: To convert from FAD to LBA/LSN, minus 150.158//159// The whole process needed to be changed since I need more control160// over sector detection, etc. Not to mention it means less work for161// the porter since they only have to implement raw sector reading as162// opposed to implementing mode 1, mode 2 form1/form2, -and- raw163// sector reading.164165memset(buffer, 0, 2352);166167return 1;168}169170//////////////////////////////////////////////////////////////////////////////171172static void DummyCDReadAheadFAD(UNUSED u32 FAD)173{174// This function is called to tell the driver which sector (FAD175// address) is expected to be read next. If the driver supports176// read-ahead, it should start reading the given sector in the177// background while the emulation continues, so that when the178// sector is actually read with ReadSectorFAD() it'll be available179// immediately. (Note that there's no guarantee this sector will180// actually be requested--the emulated CD might be stopped before181// the sector is read, for example.)182//183// This function should NOT block. If the driver can't perform184// asynchronous reads (or you just don't want to bother handling185// them), make this function a no-op and just read sectors186// normally.187}188189//////////////////////////////////////////////////////////////////////////////190// ISO Interface191//////////////////////////////////////////////////////////////////////////////192193static const s8 syncHdr[12] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };194static FILE *isofile=NULL;195static int isofilesize=0;196static int bytesPerSector = 0;197static int isbincue = 0;198static u32 isoTOC[102];199static struct200{201u32 fadstart;202u32 fileoffset;203} isooffsettbl[100];204205#define MSF_TO_FAD(m,s,f) ((m * 4500) + (s * 75) + f)206207//////////////////////////////////////////////////////////////////////////////208209static int InitBinCue(const char *cuefilename)210{211u32 size;212char *tempbuffer, *tempbuffer2;213unsigned int tracknum;214unsigned int indexnum, min, sec, frame;215unsigned int pregap=0;216char *p, *p2;217218fseek(isofile, 0, SEEK_END);219size = ftell(isofile);220fseek(isofile, 0, SEEK_SET);221222// Allocate buffer with enough space for reading cue223if ((tempbuffer = (char *)calloc(size, 1)) == NULL)224return -1;225226// Skip image filename227if (fscanf(isofile, "FILE \"%*[^\"]\" %*s\r\n") == EOF)228{229free(tempbuffer);230return -1;231}232233// Time to generate TOC234for (;;)235{236// Retrieve a line in cue237if (fscanf(isofile, "%s", tempbuffer) == EOF)238break;239240// Figure out what it is241if (strncmp(tempbuffer, "TRACK", 5) == 0)242{243// Handle accordingly244if (fscanf(isofile, "%d %[^\r\n]\r\n", &tracknum, tempbuffer) == EOF)245break;246247if (strncmp(tempbuffer, "MODE1", 5) == 0 ||248strncmp(tempbuffer, "MODE2", 5) == 0)249{250// Figure out the track sector size251bytesPerSector = atoi(tempbuffer + 6);252253// Update toc entry254isoTOC[tracknum-1] = 0x41000000;255}256else if (strncmp(tempbuffer, "AUDIO", 5) == 0)257{258// fix me259// Update toc entry260isoTOC[tracknum-1] = 0x01000000;261}262}263else if (strncmp(tempbuffer, "INDEX", 5) == 0)264{265// Handle accordingly266267if (fscanf(isofile, "%d %d:%d:%d\r\n", &indexnum, &min, &sec, &frame) == EOF)268break;269270if (indexnum == 1)271{272// Update toc entry273isoTOC[tracknum-1] = (isoTOC[tracknum-1] & 0xFF000000) | (MSF_TO_FAD(min, sec, frame) + pregap + 150);274275isooffsettbl[tracknum-1].fadstart = MSF_TO_FAD(min, sec, frame) + pregap + 150;276isooffsettbl[tracknum-1].fileoffset = pregap + 150;277}278}279else if (strncmp(tempbuffer, "PREGAP", 5) == 0)280{281if (fscanf(isofile, "%d:%d:%d\r\n", &min, &sec, &frame) == EOF)282break;283284pregap += MSF_TO_FAD(min, sec, frame);285}286else if (strncmp(tempbuffer, "POSTGAP", 5) == 0)287{288if (fscanf(isofile, "%d:%d:%d\r\n", &min, &sec, &frame) == EOF)289break;290}291}292293// Go back, retrieve image filename294fseek(isofile, 0, SEEK_SET);295fscanf(isofile, "FILE \"%[^\"]\" %*s\r\n", tempbuffer);296fclose(isofile);297298// Now go and open up the image file, figure out its size, etc.299if ((isofile = fopen(tempbuffer, "rb")) == NULL)300{301// Ok, exact path didn't work. Let's trim the path and try opening the302// file from the same directory as the cue.303304// find the start of filename305p = tempbuffer;306307for (;;)308{309if (strcspn(p, "/\\") == strlen(p))310break;311312p += strcspn(p, "/\\") + 1;313}314315// append directory of cue file with bin filename316if ((tempbuffer2 = (char *)calloc(strlen(cuefilename) + strlen(p) + 1, 1)) == NULL)317{318free(tempbuffer);319return -1;320}321322// find end of path323p2 = (char *)cuefilename;324325for (;;)326{327if (strcspn(p2, "/\\") == strlen(p2))328break;329p2 += strcspn(p2, "/\\") + 1;330}331332// Make sure there was at least some kind of path, otherwise our333// second check is pretty useless334if (cuefilename == p2 && tempbuffer == p)335{336free(tempbuffer);337free(tempbuffer2);338return -1;339}340341strncpy(tempbuffer2, cuefilename, p2 - cuefilename);342strcat(tempbuffer2, p);343344// Let's give it another try345isofile = fopen(tempbuffer2, "rb");346free(tempbuffer2);347348if (isofile == NULL)349{350YabSetError(YAB_ERR_FILENOTFOUND, tempbuffer);351free(tempbuffer);352return -1;353}354}355356// buffer is no longer needed357free(tempbuffer);358359fseek(isofile, 0, SEEK_END);360isofilesize = ftell(isofile);361fseek(isofile, 0, SEEK_SET);362363// Now then, generate rest of TOC364isoTOC[99] = (isoTOC[0] & 0xFF000000) | 0x010000;365isoTOC[100] = (isoTOC[tracknum - 1] & 0xFF000000) | (tracknum << 16);366isoTOC[101] = (isoTOC[tracknum - 1] & 0xFF000000) | ((isofilesize / bytesPerSector) + pregap + 150);367368isooffsettbl[tracknum].fileoffset = 0;369isooffsettbl[tracknum].fadstart = 0xFFFFFFFF;370371return 0;372}373374//////////////////////////////////////////////////////////////////////////////375376static int ISOCDInit(const char * iso) {377char header[6];378379memset(isoTOC, 0xFF, 0xCC * 2);380381if (!iso)382return -1;383384if (!(isofile = fopen(iso, "rb")))385{386YabSetError(YAB_ERR_FILENOTFOUND, (char *)iso);387return -1;388}389390fread((void *)header, 1, 6, isofile);391392// Figure out what kind of image format we're dealing with393if (strncmp(header, "FILE \"", 6) == 0)394{395// It's a BIN/CUE396isbincue = 1;397398// Generate TOC for bin file399if (InitBinCue(iso) != 0)400{401if (isofile)402free(isofile);403return -1;404}405}406else407{408// Assume it's an ISO file409isbincue = 0;410411fseek(isofile, 0, SEEK_END);412isofilesize = ftell(isofile);413414if (0 == (isofilesize % 2048))415bytesPerSector = 2048;416else if (0 == (isofilesize % 2352))417bytesPerSector = 2352;418else419{420YabSetError(YAB_ERR_OTHER, "Unsupported CD image!\n");421422return -1;423}424425// Generate TOC426isoTOC[0] = 0x41000096;427isoTOC[99] = 0x41010000;428isoTOC[100] = 0x41010000;429isoTOC[101] = (0x41 << 24) | (isofilesize / bytesPerSector); //this isn't fully correct, but it does the job for now.430431isooffsettbl[0].fileoffset = 150;432isooffsettbl[0].fadstart = 150;433isooffsettbl[1].fileoffset = 0;434isooffsettbl[1].fadstart = 0xFFFFFFFF;435}436437return 0;438}439440//////////////////////////////////////////////////////////////////////////////441442static void ISOCDDeInit(void) {443if (isofile)444{445fclose(isofile);446}447}448449//////////////////////////////////////////////////////////////////////////////450451static int ISOCDGetStatus(void) {452return isofile != NULL ? 0 : 2;453}454455//////////////////////////////////////////////////////////////////////////////456457static s32 ISOCDReadTOC(u32 * TOC) {458memcpy(TOC, isoTOC, 0xCC * 2);459460return (0xCC * 2);461}462463//////////////////////////////////////////////////////////////////////////////464465static int ISOCDReadSectorFAD(u32 FAD, void *buffer) {466int sector;467int i;468469assert(isofile);470471memset(buffer, 0, 2352);472473for (i = 1; i < 100; i++)474{475if (FAD < isooffsettbl[i].fadstart)476{477sector = FAD - isooffsettbl[i-1].fileoffset;478break;479}480}481if (i == 100) {482CDLOG("Warning: Sector not found in track list");483return 0;484}485486if ((sector * bytesPerSector) >= isofilesize) {487CDLOG("Warning: Trying to read beyond end of CD image! (sector: %d)\n", sector);488return 0;489}490491fseek(isofile, sector * bytesPerSector, SEEK_SET);492493if (2048 == bytesPerSector) {494memcpy(buffer, syncHdr, 12);495fread((char *)buffer + 0x10, bytesPerSector, 1, isofile);496} else { //2352497fread(buffer, bytesPerSector, 1, isofile);498}499500return 1;501}502503//////////////////////////////////////////////////////////////////////////////504505static void ISOCDReadAheadFAD(UNUSED u32 FAD)506{507// No-op508}509510//////////////////////////////////////////////////////////////////////////////511512513514