in cores/pce/mednafen/cdrom/CDAccess_Image.cpp [384:993]
bool CDAccess_Image::ImageOpen(const std::string& path, bool image_memcache)
{
MemoryStream fp(new FileStream(path.c_str(), MODE_READ));
static const unsigned max_args = 4;
std::string linebuf;
std::string cmdbuf, args[max_args];
bool IsTOC = false;
int32_t active_track = -1;
int32_t AutoTrackInc = 1; // For TOC
CDRFILE_TRACK_INFO TmpTrack;
std::string file_base, file_ext;
std::map<std::string, Stream*> toc_streamcache;
disc_type = DISC_TYPE_CDDA_OR_M1;
memset(&TmpTrack, 0, sizeof(TmpTrack));
MDFN_GetFilePathComponents(path, &base_dir, &file_base, &file_ext);
if(!strcasecmp(file_ext.c_str(), ".toc"))
{
log_cb(RETRO_LOG_INFO, "TOC file detected.\n");
IsTOC = true;
}
// Check for annoying UTF-8 BOM.
if(!IsTOC)
{
uint8_t bom_tmp[3];
if(fp.read(bom_tmp, 3, false) == 3 && bom_tmp[0] == 0xEF && bom_tmp[1] == 0xBB && bom_tmp[2] == 0xBF)
{
// Print an annoying error message, but don't actually error out.
log_cb(RETRO_LOG_WARN, "UTF-8 BOM detected at start of CUE sheet.\n");
}
else
fp.seek(0, SEEK_SET);
}
// Assign opposite maximum values so our tests will work!
FirstTrack = 99;
LastTrack = 0;
linebuf.reserve(1024);
while(fp.get_line(linebuf) >= 0)
{
unsigned argcount = 0;
if(IsTOC)
{
// Handle TOC format comments
size_t ss_loc = linebuf.find("//");
if(ss_loc != std::string::npos)
linebuf.resize(ss_loc);
}
// Call trim AFTER we handle TOC-style comments, so we'll be sure to remove trailing whitespace in lines like: MONKEY // BABIES
MDFN_rtrim(linebuf);
MDFN_ltrim(linebuf);
if(linebuf.length() == 0) // Skip blank lines.
continue;
// Grab command and arguments.
{
size_t offs = 0;
offs = UnQuotify(linebuf, offs, cmdbuf, false);
for(argcount = 0; argcount < max_args && offs < linebuf.length(); argcount++)
offs = UnQuotify(linebuf, offs, args[argcount]);
// Make sure unused arguments are cleared out so we don't have inter-line leaks!
for(unsigned x = argcount; x < max_args; x++)
args[x].clear();
MDFN_strtoupper(cmdbuf);
}
//printf("%s\n", cmdbuf.c_str()); //: %s %s %s %s\n", cmdbuf.c_str(), args[0].c_str(), args[1].c_str(), args[2].c_str(), args[3].c_str());
if(IsTOC)
{
if(cmdbuf == "TRACK")
{
if(active_track >= 0)
{
memcpy(&Tracks[active_track], &TmpTrack, sizeof(TmpTrack));
memset(&TmpTrack, 0, sizeof(TmpTrack));
active_track = -1;
}
for(int32_t i = 2; i < 100; i++)
TmpTrack.index[i] = -1;
if(AutoTrackInc > 99)
{
log_cb(RETRO_LOG_ERROR, "Invalid track number: %d", AutoTrackInc);
return false;
}
active_track = AutoTrackInc++;
if(active_track < FirstTrack)
FirstTrack = active_track;
if(active_track > LastTrack)
LastTrack = active_track;
int format_lookup;
for(format_lookup = 0; format_lookup < _DI_FORMAT_COUNT; format_lookup++)
{
if(!strcasecmp(args[0].c_str(), DI_CDRDAO_Strings[format_lookup]))
{
TmpTrack.DIFormat = format_lookup;
break;
}
}
if(format_lookup == _DI_FORMAT_COUNT)
{
log_cb(RETRO_LOG_ERROR, "Invalid track format: %s", args[0].c_str());
return false;
}
if(TmpTrack.DIFormat == DI_FORMAT_AUDIO)
TmpTrack.RawAudioMSBFirst = true; /* Silly cdrdao... */
if(!strcasecmp(args[1].c_str(), "RW"))
{
TmpTrack.SubchannelMode = CDRF_SUBM_RW;
log_cb(RETRO_LOG_ERROR, "\"RW\" format subchannel data not supported, only \"RW_RAW\" is!");
}
else if(!strcasecmp(args[1].c_str(), "RW_RAW"))
TmpTrack.SubchannelMode = CDRF_SUBM_RW_RAW;
} // end to TRACK
else if(cmdbuf == "SILENCE")
{
#if 0
log_cb(RETRO_LOG_INFO, "Unsupported directive: %s\n", cmdbuf.c_str());
return false;
#endif
}
else if(cmdbuf == "ZERO")
{
#if 0
log_cb(RETRO_LOG_INFO, "Unsupported directive: %s\n", cmdbuf.c_str());
return false;
#endif
}
else if(cmdbuf == "FIFO")
{
log_cb(RETRO_LOG_INFO, "Unsupported directive: %s\n", cmdbuf.c_str());
return false;
}
else if(cmdbuf == "FILE" || cmdbuf == "AUDIOFILE")
{
const char *binoffset = NULL;
const char *msfoffset = NULL;
const char *length = NULL;
if(args[1].c_str()[0] == '#')
{
binoffset = args[1].c_str() + 1;
msfoffset = args[2].c_str();
length = args[3].c_str();
}
else
{
msfoffset = args[1].c_str();
length = args[2].c_str();
}
//printf("%s, %s, %s, %s\n", args[0].c_str(), binoffset, msfoffset, length);
if (!ParseTOCFileLineInfo(&TmpTrack, active_track, args[0], binoffset, msfoffset, length, image_memcache, toc_streamcache))
return false;
}
else if(cmdbuf == "DATAFILE")
{
const char *binoffset = NULL;
const char *length = NULL;
if(args[1].c_str()[0] == '#')
{
binoffset = args[1].c_str() + 1;
length = args[2].c_str();
}
else
length = args[1].c_str();
if (!ParseTOCFileLineInfo(&TmpTrack, active_track, args[0], binoffset, NULL, length, image_memcache, toc_streamcache))
return false;
}
else if(cmdbuf == "INDEX")
{
// FIXME
log_cb(RETRO_LOG_ERROR, "Unsupported directive: %s", cmdbuf.c_str());
return false;
}
else if(cmdbuf == "PREGAP")
{
if(active_track < 0)
{
log_cb(RETRO_LOG_ERROR, "Command %s is outside of a TRACK definition!\n", cmdbuf.c_str());
return false;
}
unsigned int m,s,f;
if (!StringToMSF(args[0].c_str(), &m, &s, &f))
return false;
TmpTrack.pregap = (m * 60 + s) * 75 + f;
} // end to PREGAP
else if(cmdbuf == "START")
{
if(active_track < 0)
{
log_cb(RETRO_LOG_ERROR, "Command %s is outside of a TRACK definition!\n", cmdbuf.c_str());
return false;
}
unsigned int m,s,f;
if (!StringToMSF(args[0].c_str(), &m, &s, &f))
return false;
TmpTrack.pregap = (m * 60 + s) * 75 + f;
}
else if(cmdbuf == "TWO_CHANNEL_AUDIO")
{
TmpTrack.subq_control &= ~SUBQ_CTRLF_4CH;
}
else if(cmdbuf == "FOUR_CHANNEL_AUDIO")
{
TmpTrack.subq_control |= SUBQ_CTRLF_4CH;
}
else if(cmdbuf == "NO")
{
MDFN_strtoupper(args[0]);
if(args[0] == "COPY")
{
TmpTrack.subq_control &= ~SUBQ_CTRLF_DCP;
}
else if(args[0] == "PRE_EMPHASIS")
{
TmpTrack.subq_control &= ~SUBQ_CTRLF_PRE;
}
else
{
log_cb(RETRO_LOG_ERROR, "Unsupported argument to \"NO\" directive: %s", args[0].c_str());
return false;
}
}
else if(cmdbuf == "COPY")
{
TmpTrack.subq_control |= SUBQ_CTRLF_DCP;
}
else if(cmdbuf == "PRE_EMPHASIS")
{
TmpTrack.subq_control |= SUBQ_CTRLF_PRE;
}
// TODO: Confirm that these are taken from the TOC of the disc, and not synthesized by cdrdao.
else if(cmdbuf == "CD_DA")
disc_type = DISC_TYPE_CDDA_OR_M1;
else if(cmdbuf == "CD_ROM")
disc_type = DISC_TYPE_CDDA_OR_M1;
else if(cmdbuf == "CD_ROM_XA")
disc_type = DISC_TYPE_CD_XA;
else
{
#if 0
log_cb(RETRO_LOG_ERROR, "Unsupported directive: %s", cmdbuf.c_str());
return false;
#endif
}
// TODO: CATALOG
} /*********** END TOC HANDLING ************/
else // now for CUE sheet handling
{
if(cmdbuf == "FILE")
{
if(active_track >= 0)
{
memcpy(&Tracks[active_track], &TmpTrack, sizeof(TmpTrack));
memset(&TmpTrack, 0, sizeof(TmpTrack));
active_track = -1;
}
std::string efn = MDFN_EvalFIP(base_dir, args[0]);
TmpTrack.fp = new FileStream(efn.c_str(), MODE_READ);
TmpTrack.FirstFileInstance = 1;
if(image_memcache)
TmpTrack.fp = new MemoryStream(TmpTrack.fp);
if(!strcasecmp(args[1].c_str(), "BINARY"))
{
//TmpTrack.Format = TRACK_FORMAT_DATA;
//struct stat stat_buf;
//fstat(fileno(TmpTrack.fp), &stat_buf);
//TmpTrack.sectors = stat_buf.st_size; // / 2048;
}
else if(!strcasecmp(args[1].c_str(), "WAVE") || !strcasecmp(args[1].c_str(), "WAV"))
{
// Make it work with WAVE / WAV file type names in the cue sheet, previously .wav was working only with BINARY
}
else if(!strcasecmp(args[1].c_str(), "OGG") || !strcasecmp(args[1].c_str(), "VORBIS") || !strcasecmp(args[1].c_str(), "PCM")
|| !strcasecmp(args[1].c_str(), "MPC") || !strcasecmp(args[1].c_str(), "MP+"))
{
TmpTrack.AReader = CDAFR_Open(TmpTrack.fp);
if(!TmpTrack.AReader)
{
log_cb(RETRO_LOG_ERROR, "Unsupported audio track file format: %s\n", args[0].c_str());
return false;
}
}
else
{
log_cb(RETRO_LOG_ERROR, "Unsupported track format: %s\n", args[1].c_str());
return false;
}
}
else if(cmdbuf == "TRACK")
{
if(active_track >= 0)
{
memcpy(&Tracks[active_track], &TmpTrack, sizeof(TmpTrack));
TmpTrack.FirstFileInstance = 0;
TmpTrack.pregap = 0;
TmpTrack.pregap_dv = 0;
TmpTrack.postgap = 0;
TmpTrack.index[0] = -1;
TmpTrack.index[1] = 0;
}
for(int32_t i = 2; i < 100; i++)
TmpTrack.index[i] = -1;
active_track = atoi(args[0].c_str());
if(active_track < 1 || active_track > 99)
{
log_cb(RETRO_LOG_ERROR, "Invalid track number: %d\n", active_track);
return false;
}
if(active_track < FirstTrack)
FirstTrack = active_track;
if(active_track > LastTrack)
LastTrack = active_track;
int format_lookup;
for(format_lookup = 0; format_lookup < _DI_FORMAT_COUNT; format_lookup++)
{
if(!strcasecmp(args[1].c_str(), DI_CUE_Strings[format_lookup]))
{
TmpTrack.DIFormat = format_lookup;
break;
}
}
if(format_lookup == _DI_FORMAT_COUNT)
{
log_cb(RETRO_LOG_ERROR, "Invalid track format: %s\n", args[1].c_str());
return false;
}
}
else if(cmdbuf == "INDEX")
{
if(active_track >= 0)
{
unsigned wi;
unsigned int m,s,f;
if (!StringToMSF(args[1].c_str(), &m, &s, &f))
return false;
if(sscanf(args[0].c_str(), "%u", &wi) == 1 && wi < 100)
TmpTrack.index[wi] = (m * 60 + s) * 75 + f;
else
{
log_cb(RETRO_LOG_ERROR, "Malformed \"INDEX\" directive: %s\n", cmdbuf.c_str());
return false;
}
}
}
else if(cmdbuf == "PREGAP")
{
if(active_track >= 0)
{
unsigned int m,s,f;
if (!StringToMSF(args[0].c_str(), &m, &s, &f))
return false;
TmpTrack.pregap = (m * 60 + s) * 75 + f;
}
}
else if(cmdbuf == "POSTGAP")
{
if(active_track >= 0)
{
unsigned int m,s,f;
if (!StringToMSF(args[0].c_str(), &m, &s, &f))
return false;
TmpTrack.postgap = (m * 60 + s) * 75 + f;
}
}
else if(cmdbuf == "REM")
{
}
else if(cmdbuf == "FLAGS")
{
TmpTrack.subq_control &= ~(SUBQ_CTRLF_PRE | SUBQ_CTRLF_DCP | SUBQ_CTRLF_4CH);
for(unsigned i = 0; i < argcount; i++)
{
if(args[i] == "DCP")
{
TmpTrack.subq_control |= SUBQ_CTRLF_DCP;
}
else if(args[i] == "4CH")
{
TmpTrack.subq_control |= SUBQ_CTRLF_4CH;
}
else if(args[i] == "PRE")
{
TmpTrack.subq_control |= SUBQ_CTRLF_PRE;
}
else if(args[i] == "SCMS")
{
// Not implemented, likely pointless. PROBABLY indicates that the copy bit of the subchannel Q control field is supposed to
// alternate between 1 and 0 at 9.375 Hz(four 1, four 0, four 1, four 0, etc.).
}
else
{
log_cb(RETRO_LOG_ERROR, "Unknown CUE sheet \"FLAGS\" directive flag \"%s\".\n", args[i].c_str());
return false;
}
}
}
else if(cmdbuf == "CDTEXTFILE" || cmdbuf == "CATALOG" || cmdbuf == "ISRC" ||
cmdbuf == "TITLE" || cmdbuf == "PERFORMER" || cmdbuf == "SONGWRITER")
{
log_cb(RETRO_LOG_WARN, "Unsupported CUE sheet directive: \"%s\".\n", cmdbuf.c_str()); /* FIXME, generic logger passed by pointer to constructor */
}
else
{
log_cb(RETRO_LOG_ERROR, "Unknown CUE sheet directive \"%s\".\n", cmdbuf.c_str());
return false;
}
} // end of CUE sheet handling
} // end of fgets() loop
if(active_track >= 0)
memcpy(&Tracks[active_track], &TmpTrack, sizeof(TmpTrack));
if(FirstTrack > LastTrack)
{
log_cb(RETRO_LOG_ERROR, "No tracks found!\n");
return false;
}
FirstTrack = FirstTrack;
NumTracks = 1 + LastTrack - FirstTrack;
int32_t RunningLBA = 0;
int32_t LastIndex = 0;
long FileOffset = 0;
RunningLBA -= 150;
Tracks[FirstTrack].pregap += 150;
for(int x = FirstTrack; x < (FirstTrack + NumTracks); x++)
{
if(!Tracks[x].fp && !Tracks[x].AReader)
{
log_cb(RETRO_LOG_ERROR, "Missing track %u.\n", x);
return false;
}
if(Tracks[x].DIFormat == DI_FORMAT_AUDIO)
Tracks[x].subq_control &= ~SUBQ_CTRLF_DATA;
else
Tracks[x].subq_control |= SUBQ_CTRLF_DATA;
if(!IsTOC) // TOC-format disc_type calculation is handled differently.
{
if(disc_type != DISC_TYPE_CD_I)
{
switch(Tracks[x].DIFormat)
{
default: break;
case DI_FORMAT_MODE2:
case DI_FORMAT_MODE2_FORM1:
case DI_FORMAT_MODE2_FORM2:
case DI_FORMAT_MODE2_RAW:
disc_type = DISC_TYPE_CD_XA;
break;
case DI_FORMAT_CDI_RAW:
disc_type = DISC_TYPE_CD_I;
break;
}
}
}
if(IsTOC)
{
RunningLBA += Tracks[x].pregap;
Tracks[x].LBA = RunningLBA;
RunningLBA += Tracks[x].sectors;
RunningLBA += Tracks[x].postgap;
}
else // else handle CUE sheet...
{
if(Tracks[x].FirstFileInstance)
{
LastIndex = 0;
FileOffset = 0;
}
RunningLBA += Tracks[x].pregap;
Tracks[x].pregap_dv = 0;
if(Tracks[x].index[0] != -1)
Tracks[x].pregap_dv = Tracks[x].index[1] - Tracks[x].index[0];
FileOffset += Tracks[x].pregap_dv * DI_Size_Table[Tracks[x].DIFormat];
RunningLBA += Tracks[x].pregap_dv;
Tracks[x].LBA = RunningLBA;
// Make sure FileOffset this is set before the call to GetSectorCount()
Tracks[x].FileOffset = FileOffset;
Tracks[x].sectors = GetSectorCount(&Tracks[x]);
if((x + 1) >= (FirstTrack + NumTracks) || Tracks[x+1].FirstFileInstance)
{
}
else
{
// Fix the sector count if we have multiple tracks per one binary image file.
if(Tracks[x + 1].index[0] == -1)
Tracks[x].sectors = Tracks[x + 1].index[1] - Tracks[x].index[1];
else
Tracks[x].sectors = Tracks[x + 1].index[0] - Tracks[x].index[1]; //Tracks[x + 1].index - Tracks[x].index;
}
//printf("Poo: %d %d\n", x, Tracks[x].sectors);
RunningLBA += Tracks[x].sectors;
RunningLBA += Tracks[x].postgap;
//printf("%d, %ld %d %d %d %d\n", x, FileOffset, Tracks[x].index, Tracks[x].pregap, Tracks[x].sectors, Tracks[x].LBA);
FileOffset += Tracks[x].sectors * DI_Size_Table[Tracks[x].DIFormat];
} // end to cue sheet handling
} // end to track loop
total_sectors = RunningLBA;
//
// Adjust indexes for MakeSubPQ()
//
for(int x = FirstTrack; x < (FirstTrack + NumTracks); x++)
{
const int32_t base = Tracks[x].index[1];
for(int32_t i = 0; i < 100; i++)
{
if(i == 0 || Tracks[x].index[i] == -1)
Tracks[x].index[i] = INT32_MAX;
else
Tracks[x].index[i] = Tracks[x].LBA + (Tracks[x].index[i] - base);
assert(Tracks[x].index[i] >= 0);
}
}
//
// Load SBI file, if present
//
if(!IsTOC)
{
char sbi_ext[4] = { 's', 'b', 'i', 0 };
if(file_ext.length() == 4 && file_ext[0] == '.')
{
for(unsigned i = 0; i < 3; i++)
{
if(file_ext[1 + i] >= 'A' && file_ext[1 + i] <= 'Z')
sbi_ext[i] += 'A' - 'a';
}
}
if (!LoadSBI(MDFN_EvalFIP(base_dir, file_base + std::string(".") + std::string(sbi_ext), true).c_str()))
return false;
}
GenerateTOC();
return true;
}