1320 lines
32 KiB
C++
1320 lines
32 KiB
C++
/* Mednafen - Multi-system Emulator
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/*
|
|
Notes and TODO:
|
|
|
|
POSTGAP in CUE sheets may not be handled properly, should the directive automatically increment the index number?
|
|
|
|
INDEX nn where 02 <= nn <= 99 is not supported in CUE sheets.
|
|
|
|
TOC reading code is extremely barebones, leaving out support for more esoteric features.
|
|
|
|
A PREGAP statement in the first track definition in a CUE sheet may not work properly(depends on what is proper);
|
|
it will be added onto the implicit default 00:02:00 of pregap.
|
|
*/
|
|
|
|
#include "emuware/emuware.h"
|
|
|
|
//undo gettext stuff
|
|
#define _(X) (X)
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <trio/trio.h>
|
|
#include <memory>
|
|
|
|
#include "general.h"
|
|
#include "string/trim.h"
|
|
#include "endian.h"
|
|
#include "FileStream.h"
|
|
#include "MemoryStream.h"
|
|
|
|
#include "CDAccess.h"
|
|
#include "CDAccess_Image.h"
|
|
|
|
#include "CDAFReader.h"
|
|
|
|
#include <map>
|
|
|
|
using namespace CDUtility;
|
|
|
|
enum
|
|
{
|
|
CDRF_SUBM_NONE = 0,
|
|
CDRF_SUBM_RW = 1,
|
|
CDRF_SUBM_RW_RAW = 2
|
|
};
|
|
|
|
// Disk-image(rip) track/sector formats
|
|
enum
|
|
{
|
|
DI_FORMAT_AUDIO = 0x00,
|
|
DI_FORMAT_MODE1 = 0x01,
|
|
DI_FORMAT_MODE1_RAW = 0x02,
|
|
DI_FORMAT_MODE2 = 0x03,
|
|
DI_FORMAT_MODE2_FORM1 = 0x04,
|
|
DI_FORMAT_MODE2_FORM2 = 0x05,
|
|
DI_FORMAT_MODE2_RAW = 0x06,
|
|
DI_FORMAT_CDI_RAW = 0x07,
|
|
_DI_FORMAT_COUNT
|
|
};
|
|
|
|
static const int32 DI_Size_Table[8] =
|
|
{
|
|
2352, // Audio
|
|
2048, // MODE1
|
|
2352, // MODE1 RAW
|
|
2336, // MODE2
|
|
2048, // MODE2 Form 1
|
|
2324, // Mode 2 Form 2
|
|
2352, // MODE2 RAW
|
|
2352, // CD-I RAW
|
|
};
|
|
|
|
static const char *DI_CDRDAO_Strings[8] =
|
|
{
|
|
"AUDIO",
|
|
"MODE1",
|
|
"MODE1_RAW",
|
|
"MODE2",
|
|
"MODE2_FORM1",
|
|
"MODE2_FORM2",
|
|
"MODE2_RAW",
|
|
"CDI_RAW"
|
|
};
|
|
|
|
static const char *DI_CUE_Strings[8] =
|
|
{
|
|
"AUDIO",
|
|
"MODE1/2048",
|
|
"MODE1/2352",
|
|
"MODE2/2336", // FIXME: A guess
|
|
"MODE2/2048", // FIXME: A guess
|
|
"MODE2/2324", // FIXME: A guess
|
|
"MODE2/2352", // FIXME: A guess
|
|
"CDI/2352",
|
|
};
|
|
|
|
// Should return an offset to the start of the next argument(past any whitespace), or if there isn't a next argument,
|
|
// it'll return the length of the src string.
|
|
static size_t UnQuotify(const std::string &src, size_t source_offset, std::string &dest, bool parse_quotes = true)
|
|
{
|
|
const size_t source_len = src.length();
|
|
bool in_quote = 0;
|
|
bool already_normal = 0;
|
|
|
|
dest.clear();
|
|
|
|
while(source_offset < source_len)
|
|
{
|
|
if(src[source_offset] == ' ' || src[source_offset] == '\t')
|
|
{
|
|
if(!in_quote)
|
|
{
|
|
if(already_normal) // Trailing whitespace(IE we're done with this argument)
|
|
break;
|
|
else // Leading whitespace, ignore it.
|
|
{
|
|
source_offset++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(src[source_offset] == '"' && parse_quotes)
|
|
{
|
|
if(in_quote)
|
|
{
|
|
source_offset++;
|
|
// Not sure which behavior is most useful(or correct :b).
|
|
#if 0
|
|
in_quote = false;
|
|
already_normal = true;
|
|
#else
|
|
break;
|
|
#endif
|
|
}
|
|
else
|
|
in_quote = 1;
|
|
}
|
|
else
|
|
{
|
|
dest.push_back(src[source_offset]);
|
|
already_normal = 1;
|
|
}
|
|
source_offset++;
|
|
}
|
|
|
|
while(source_offset < source_len)
|
|
{
|
|
if(src[source_offset] != ' ' && src[source_offset] != '\t')
|
|
break;
|
|
|
|
source_offset++;
|
|
}
|
|
|
|
return source_offset;
|
|
}
|
|
|
|
uint32 CDAccess_Image::GetSectorCount(CDRFILE_TRACK_INFO *track)
|
|
{
|
|
if(track->DIFormat == DI_FORMAT_AUDIO)
|
|
{
|
|
if(track->AReader)
|
|
return(((track->AReader->FrameCount() * 4) - track->FileOffset) / 2352);
|
|
else
|
|
{
|
|
const int64 size = track->fp->size();
|
|
|
|
//printf("%d %d %d\n", (int)stat_buf.st_size, (int)track->FileOffset, (int)stat_buf.st_size - (int)track->FileOffset);
|
|
if(track->SubchannelMode)
|
|
return((size - track->FileOffset) / (2352 + 96));
|
|
else
|
|
return((size - track->FileOffset) / 2352);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int64 size = track->fp->size();
|
|
|
|
return((size - track->FileOffset) / DI_Size_Table[track->DIFormat]);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
void CDAccess_Image::ParseTOCFileLineInfo(CDRFILE_TRACK_INFO *track, const int tracknum, const std::string &filename, const char *binoffset, const char *msfoffset, const char *length, bool image_memcache, std::map<std::string, Stream*> &toc_streamcache)
|
|
{
|
|
long offset = 0; // In bytes!
|
|
long tmp_long;
|
|
int m, s, f;
|
|
uint32 sector_mult;
|
|
long sectors;
|
|
std::map<std::string, Stream*>::iterator ribbit;
|
|
|
|
ribbit = toc_streamcache.find(filename);
|
|
|
|
if(ribbit != toc_streamcache.end())
|
|
{
|
|
track->FirstFileInstance = 0;
|
|
|
|
track->fp = ribbit->second;
|
|
}
|
|
else
|
|
{
|
|
std::string efn;
|
|
|
|
track->FirstFileInstance = 1;
|
|
|
|
efn = MDFN_EvalFIP(base_dir, filename);
|
|
|
|
if(image_memcache)
|
|
track->fp = new MemoryStream(new FileStream(efn, FileStream::MODE_READ));
|
|
else
|
|
track->fp = new FileStream(efn, FileStream::MODE_READ);
|
|
|
|
toc_streamcache[filename] = track->fp;
|
|
}
|
|
|
|
if(filename.length() >= 4 && !strcasecmp(filename.c_str() + filename.length() - 4, ".wav"))
|
|
{
|
|
track->AReader = CDAFR_Open(track->fp);
|
|
|
|
if(!track->AReader)
|
|
throw MDFN_Error(0, "TODO ERROR");
|
|
}
|
|
|
|
sector_mult = DI_Size_Table[track->DIFormat];
|
|
|
|
if(track->SubchannelMode)
|
|
sector_mult += 96;
|
|
|
|
if(binoffset && trio_sscanf(binoffset, "%ld", &tmp_long) == 1)
|
|
{
|
|
offset += tmp_long;
|
|
}
|
|
|
|
if(msfoffset && trio_sscanf(msfoffset, "%d:%d:%d", &m, &s, &f) == 3)
|
|
{
|
|
offset += ((m * 60 + s) * 75 + f) * sector_mult;
|
|
}
|
|
|
|
track->FileOffset = offset; // Make sure this is set before calling GetSectorCount()!
|
|
sectors = GetSectorCount(track);
|
|
//printf("Track: %d, offset: %ld, %ld\n", tracknum, offset, sectors);
|
|
|
|
if(length)
|
|
{
|
|
tmp_long = sectors;
|
|
|
|
if(trio_sscanf(length, "%d:%d:%d", &m, &s, &f) == 3)
|
|
tmp_long = (m * 60 + s) * 75 + f;
|
|
else if(track->DIFormat == DI_FORMAT_AUDIO)
|
|
{
|
|
char *endptr = NULL;
|
|
|
|
tmp_long = strtol(length, &endptr, 10);
|
|
|
|
// Error?
|
|
if(endptr == length)
|
|
{
|
|
tmp_long = sectors;
|
|
}
|
|
else
|
|
tmp_long /= 588;
|
|
|
|
}
|
|
|
|
if(tmp_long > sectors)
|
|
{
|
|
throw MDFN_Error(0, _("Length specified in TOC file for track %d is too large by %ld sectors!\n"), tracknum, (long)(tmp_long - sectors));
|
|
}
|
|
sectors = tmp_long;
|
|
}
|
|
|
|
track->sectors = sectors;
|
|
}
|
|
|
|
static void MDFN_strtoupper(std::string &str)
|
|
{
|
|
const size_t len = str.length();
|
|
|
|
for(size_t x = 0; x < len; x++)
|
|
{
|
|
if(str[x] >= 'a' && str[x] <= 'z')
|
|
{
|
|
str[x] = str[x] - 'a' + 'A';
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
std::string MDFN_toupper(const std::string &str)
|
|
{
|
|
const size_t len = str.length();
|
|
std::string new_str;
|
|
|
|
new_str.reserve(len);
|
|
|
|
for(size_t x = 0; x < len; x++)
|
|
{
|
|
int c = str[x];
|
|
|
|
if(c >= 'a' && c <= 'z')
|
|
c = c - 'a' + 'A';
|
|
|
|
new_str.push_back(c);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void CDAccess_Image::LoadSBI(const std::string& sbi_path)
|
|
{
|
|
//test whether it exists
|
|
FILE* inf = fopen(sbi_path.c_str(),"rb");
|
|
if(inf == NULL)
|
|
return;
|
|
|
|
printf(_("Loading SBI file \"%s\"...\n"), sbi_path.c_str());
|
|
|
|
{
|
|
//MDFN_AutoIndent aind(1);
|
|
|
|
try
|
|
{
|
|
FileStream sbis(sbi_path, FileStream::MODE_READ);
|
|
uint8 header[4];
|
|
uint8 ed[4 + 10];
|
|
uint8 tmpq[12];
|
|
|
|
sbis.read(header, 4);
|
|
|
|
if(memcmp(header, "SBI\0", 4))
|
|
throw MDFN_Error(0, _("Not recognized a valid SBI file."));
|
|
|
|
while(sbis.read(ed, sizeof(ed), false) == sizeof(ed))
|
|
{
|
|
if(!BCD_is_valid(ed[0]) || !BCD_is_valid(ed[1]) || !BCD_is_valid(ed[2]))
|
|
throw MDFN_Error(0, _("Bad BCD MSF offset in SBI file: %02x:%02x:%02x"), ed[0], ed[1], ed[2]);
|
|
|
|
if(ed[3] != 0x01)
|
|
throw MDFN_Error(0, _("Unrecognized boogly oogly in SBI file: %02x"), ed[3]);
|
|
|
|
memcpy(tmpq, &ed[4], 10);
|
|
|
|
//
|
|
subq_generate_checksum(tmpq);
|
|
tmpq[10] ^= 0xFF;
|
|
tmpq[11] ^= 0xFF;
|
|
//
|
|
|
|
//printf("%02x:%02x:%02x --- ", ed[0], ed[1], ed[2]);
|
|
//for(unsigned i = 0; i < 12; i++)
|
|
// printf("%02x ", tmpq[i]);
|
|
//printf("\n");
|
|
|
|
uint32 aba = AMSF_to_ABA(BCD_to_U8(ed[0]), BCD_to_U8(ed[1]), BCD_to_U8(ed[2]));
|
|
|
|
memcpy(SubQReplaceMap[aba].data(), tmpq, 12);
|
|
}
|
|
printf(_("Loaded Q subchannel replacements for %zu sectors.\n"), SubQReplaceMap.size());
|
|
}
|
|
catch(MDFN_Error &e)
|
|
{
|
|
if(e.GetErrno() != ENOENT)
|
|
throw;
|
|
else
|
|
printf(_("Error: %s\n"), e.what());
|
|
}
|
|
catch(std::exception &e)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void StringToMSF(const char* str, unsigned* m, unsigned* s, unsigned* f)
|
|
{
|
|
if(trio_sscanf(str, "%u:%u:%u", m, s, f) != 3)
|
|
throw MDFN_Error(0, _("M:S:F time \"%s\" is malformed."), str);
|
|
|
|
if(*m > 99 || *s > 59 || *f > 74)
|
|
throw MDFN_Error(0, _("M:S:F time \"%s\" contains component(s) out of range."), str);
|
|
}
|
|
|
|
void CDAccess_Image::ImageOpen(const std::string& path, bool image_memcache)
|
|
{
|
|
MemoryStream fp(new FileStream(path, FileStream::MODE_READ));
|
|
static const unsigned max_args = 4;
|
|
std::string linebuf;
|
|
std::string cmdbuf, args[max_args];
|
|
bool IsTOC = FALSE_0;
|
|
int32 active_track = -1;
|
|
int32 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"))
|
|
{
|
|
printf(_("TOC file detected.\n"));
|
|
IsTOC = true;
|
|
}
|
|
|
|
// Check for annoying UTF-8 BOM.
|
|
if(!IsTOC)
|
|
{
|
|
uint8 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.
|
|
printf(_("UTF-8 BOM detected at start of CUE sheet."));
|
|
}
|
|
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_trim(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;
|
|
}
|
|
|
|
if(AutoTrackInc > 99)
|
|
{
|
|
throw(MDFN_Error(0, _("Invalid track number: %d"), AutoTrackInc));
|
|
}
|
|
|
|
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)
|
|
{
|
|
throw(MDFN_Error(0, _("Invalid track format: %s"), args[0].c_str()));
|
|
}
|
|
|
|
if(TmpTrack.DIFormat == DI_FORMAT_AUDIO)
|
|
TmpTrack.RawAudioMSBFirst = TRUE_1; // Silly cdrdao...
|
|
|
|
if(!strcasecmp(args[1].c_str(), "RW"))
|
|
{
|
|
TmpTrack.SubchannelMode = CDRF_SUBM_RW;
|
|
throw(MDFN_Error(0, _("\"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")
|
|
{
|
|
//throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str());
|
|
}
|
|
else if(cmdbuf == "ZERO")
|
|
{
|
|
//throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str());
|
|
}
|
|
else if(cmdbuf == "FIFO")
|
|
{
|
|
throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str());
|
|
}
|
|
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);
|
|
ParseTOCFileLineInfo(&TmpTrack, active_track, args[0], binoffset, msfoffset, length, image_memcache, toc_streamcache);
|
|
}
|
|
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();
|
|
|
|
ParseTOCFileLineInfo(&TmpTrack, active_track, args[0], binoffset, NULL, length, image_memcache, toc_streamcache);
|
|
}
|
|
else if(cmdbuf == "INDEX")
|
|
{
|
|
|
|
}
|
|
else if(cmdbuf == "PREGAP")
|
|
{
|
|
if(active_track < 0)
|
|
{
|
|
throw(MDFN_Error(0, _("Command %s is outside of a TRACK definition!\n"), cmdbuf.c_str()));
|
|
}
|
|
|
|
unsigned int m,s,f;
|
|
|
|
StringToMSF(args[0].c_str(), &m, &s, &f);
|
|
|
|
TmpTrack.pregap = (m * 60 + s) * 75 + f;
|
|
} // end to PREGAP
|
|
else if(cmdbuf == "START")
|
|
{
|
|
if(active_track < 0)
|
|
{
|
|
throw(MDFN_Error(0, _("Command %s is outside of a TRACK definition!\n"), cmdbuf.c_str()));
|
|
}
|
|
|
|
unsigned int m,s,f;
|
|
|
|
StringToMSF(args[0].c_str(), &m, &s, &f);
|
|
|
|
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
|
|
{
|
|
throw MDFN_Error(0, _("Unsupported argument to \"NO\" directive: %s"), args[0].c_str());
|
|
}
|
|
}
|
|
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
|
|
{
|
|
//throw MDFN_Error(0, _("Unsupported directive: %s"), cmdbuf.c_str());
|
|
}
|
|
// 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, FileStream::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(), "OGG") || !strcasecmp(args[1].c_str(), "VORBIS") || !strcasecmp(args[1].c_str(), "WAVE") || !strcasecmp(args[1].c_str(), "WAV") || !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)
|
|
{
|
|
throw(MDFN_Error(0, _("Unsupported audio track file format: %s\n"), args[0].c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw(MDFN_Error(0, _("Unsupported track format: %s\n"), args[1].c_str()));
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
active_track = atoi(args[0].c_str());
|
|
|
|
if(active_track < 1 || active_track > 99)
|
|
{
|
|
throw(MDFN_Error(0, _("Invalid track number: %d\n"), active_track));
|
|
}
|
|
|
|
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)
|
|
{
|
|
throw(MDFN_Error(0, _("Invalid track format: %s\n"), args[1].c_str()));
|
|
}
|
|
}
|
|
else if(cmdbuf == "INDEX")
|
|
{
|
|
if(active_track >= 0)
|
|
{
|
|
unsigned int m,s,f;
|
|
|
|
StringToMSF(args[1].c_str(), &m, &s, &f);
|
|
|
|
if(!strcasecmp(args[0].c_str(), "01") || !strcasecmp(args[0].c_str(), "1"))
|
|
TmpTrack.index[1] = (m * 60 + s) * 75 + f;
|
|
else if(!strcasecmp(args[0].c_str(), "00") || !strcasecmp(args[0].c_str(), "0"))
|
|
TmpTrack.index[0] = (m * 60 + s) * 75 + f;
|
|
}
|
|
}
|
|
else if(cmdbuf == "PREGAP")
|
|
{
|
|
if(active_track >= 0)
|
|
{
|
|
unsigned int m,s,f;
|
|
|
|
StringToMSF(args[0].c_str(), &m, &s, &f);
|
|
|
|
TmpTrack.pregap = (m * 60 + s) * 75 + f;
|
|
}
|
|
}
|
|
else if(cmdbuf == "POSTGAP")
|
|
{
|
|
if(active_track >= 0)
|
|
{
|
|
unsigned int m,s,f;
|
|
|
|
StringToMSF(args[0].c_str(), &m, &s, &f);
|
|
|
|
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
|
|
{
|
|
throw MDFN_Error(0, _("Unknown CUE sheet \"FLAGS\" directive flag \"%s\".\n"), args[i].c_str());
|
|
}
|
|
}
|
|
}
|
|
else if(cmdbuf == "CDTEXTFILE" || cmdbuf == "CATALOG" || cmdbuf == "ISRC" ||
|
|
cmdbuf == "TITLE" || cmdbuf == "PERFORMER" || cmdbuf == "SONGWRITER")
|
|
{
|
|
printf(_("Unsupported CUE sheet directive: \"%s\".\n"), cmdbuf.c_str()); // FIXME, generic logger passed by pointer to constructor
|
|
}
|
|
else
|
|
{
|
|
throw MDFN_Error(0, _("Unknown CUE sheet directive \"%s\".\n"), cmdbuf.c_str());
|
|
}
|
|
} // end of CUE sheet handling
|
|
} // end of fgets() loop
|
|
|
|
if(active_track >= 0)
|
|
memcpy(&Tracks[active_track], &TmpTrack, sizeof(TmpTrack));
|
|
|
|
if(FirstTrack > LastTrack)
|
|
{
|
|
throw(MDFN_Error(0, _("No tracks found!\n")));
|
|
}
|
|
|
|
FirstTrack = FirstTrack;
|
|
NumTracks = 1 + LastTrack - FirstTrack;
|
|
|
|
int32 RunningLBA = 0;
|
|
int32 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)
|
|
throw MDFN_Error(0, _("Missing track %u."), x);
|
|
|
|
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;
|
|
|
|
//
|
|
// 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';
|
|
}
|
|
}
|
|
|
|
LoadSBI(MDFN_EvalFIP(base_dir, file_base + std::string(".") + std::string(sbi_ext), true).c_str());
|
|
}
|
|
|
|
GenerateTOC();
|
|
}
|
|
|
|
void CDAccess_Image::Cleanup(void)
|
|
{
|
|
for(int32 track = 0; track < 100; track++)
|
|
{
|
|
CDRFILE_TRACK_INFO *this_track = &Tracks[track];
|
|
|
|
if(this_track->FirstFileInstance)
|
|
{
|
|
if(Tracks[track].AReader)
|
|
{
|
|
delete Tracks[track].AReader;
|
|
Tracks[track].AReader = NULL;
|
|
}
|
|
|
|
if(this_track->fp)
|
|
{
|
|
delete this_track->fp;
|
|
this_track->fp = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CDAccess_Image::CDAccess_Image(const std::string& path, bool image_memcache) : NumTracks(0), FirstTrack(0), LastTrack(0), total_sectors(0)
|
|
{
|
|
memset(Tracks, 0, sizeof(Tracks));
|
|
|
|
try
|
|
{
|
|
ImageOpen(path, image_memcache);
|
|
}
|
|
catch(...)
|
|
{
|
|
Cleanup();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
CDAccess_Image::~CDAccess_Image()
|
|
{
|
|
Cleanup();
|
|
}
|
|
|
|
void CDAccess_Image::Read_Raw_Sector(uint8 *buf, int32 lba)
|
|
{
|
|
uint8 SimuQ[0xC];
|
|
int32 track;
|
|
CDRFILE_TRACK_INFO *ct;
|
|
|
|
//
|
|
// Leadout synthesis
|
|
//
|
|
if(lba >= total_sectors)
|
|
{
|
|
uint8 data_synth_mode = (disc_type == DISC_TYPE_CD_XA ? 0x02 : 0x01);
|
|
|
|
switch(Tracks[LastTrack].DIFormat)
|
|
{
|
|
case DI_FORMAT_AUDIO:
|
|
break;
|
|
|
|
case DI_FORMAT_MODE1_RAW:
|
|
case DI_FORMAT_MODE1:
|
|
data_synth_mode = 0x01;
|
|
break;
|
|
|
|
case DI_FORMAT_MODE2_RAW:
|
|
case DI_FORMAT_MODE2_FORM1:
|
|
case DI_FORMAT_MODE2_FORM2:
|
|
case DI_FORMAT_MODE2:
|
|
case DI_FORMAT_CDI_RAW:
|
|
data_synth_mode = 0x02;
|
|
break;
|
|
}
|
|
|
|
synth_leadout_sector_lba(data_synth_mode, toc, lba, buf);
|
|
return;
|
|
}
|
|
//
|
|
//
|
|
//
|
|
|
|
memset(buf + 2352, 0, 96);
|
|
track = MakeSubPQ(lba, buf + 2352);
|
|
subq_deinterleave(buf + 2352, SimuQ);
|
|
|
|
ct = &Tracks[track];
|
|
|
|
//
|
|
// Handle pregap and postgap reading
|
|
//
|
|
if(lba < (ct->LBA - ct->pregap_dv) || lba >= (ct->LBA + ct->sectors))
|
|
{
|
|
int32 pg_offset = lba - ct->LBA;
|
|
CDRFILE_TRACK_INFO* et = ct;
|
|
|
|
if(pg_offset < -150)
|
|
{
|
|
if((Tracks[track].subq_control & SUBQ_CTRLF_DATA) && (FirstTrack < track) && !(Tracks[track - 1].subq_control & SUBQ_CTRLF_DATA))
|
|
et = &Tracks[track - 1];
|
|
}
|
|
|
|
memset(buf, 0, 2352);
|
|
switch(et->DIFormat)
|
|
{
|
|
case DI_FORMAT_AUDIO:
|
|
break;
|
|
|
|
case DI_FORMAT_MODE1_RAW:
|
|
case DI_FORMAT_MODE1:
|
|
encode_mode1_sector(lba + 150, buf);
|
|
break;
|
|
|
|
case DI_FORMAT_MODE2_RAW:
|
|
case DI_FORMAT_MODE2_FORM1:
|
|
case DI_FORMAT_MODE2_FORM2:
|
|
case DI_FORMAT_MODE2:
|
|
case DI_FORMAT_CDI_RAW:
|
|
buf[12 + 6] = 0x20;
|
|
buf[12 + 10] = 0x20;
|
|
encode_mode2_form2_sector(lba + 150, buf);
|
|
// TODO: Zero out optional(?) checksum bytes?
|
|
break;
|
|
}
|
|
//printf("Pre/post-gap read, LBA=%d(LBA-track_start_LBA=%d)\n", lba, lba - ct->LBA);
|
|
}
|
|
else
|
|
{
|
|
if(ct->AReader)
|
|
{
|
|
int16 AudioBuf[588 * 2];
|
|
uint64 frames_read = ct->AReader->Read((ct->FileOffset / 4) + (lba - ct->LBA) * 588, AudioBuf, 588);
|
|
|
|
ct->LastSamplePos += frames_read;
|
|
|
|
if(frames_read > 588) // This shouldn't happen.
|
|
{
|
|
printf("Error: frames_read out of range: %llu\n", (unsigned long long)frames_read);
|
|
frames_read = 0;
|
|
}
|
|
|
|
if(frames_read < 588)
|
|
memset((uint8 *)AudioBuf + frames_read * 2 * sizeof(int16), 0, (588 - frames_read) * 2 * sizeof(int16));
|
|
|
|
for(int i = 0; i < 588 * 2; i++)
|
|
MDFN_en16lsb<false>(buf + i * 2, AudioBuf[i]);
|
|
}
|
|
else // Binary, woo.
|
|
{
|
|
long SeekPos = ct->FileOffset;
|
|
long LBARelPos = lba - ct->LBA;
|
|
|
|
SeekPos += LBARelPos * DI_Size_Table[ct->DIFormat];
|
|
|
|
if(ct->SubchannelMode)
|
|
SeekPos += 96 * (lba - ct->LBA);
|
|
|
|
ct->fp->seek(SeekPos, SEEK_SET);
|
|
|
|
switch(ct->DIFormat)
|
|
{
|
|
case DI_FORMAT_AUDIO:
|
|
ct->fp->read(buf, 2352);
|
|
|
|
if(ct->RawAudioMSBFirst)
|
|
Endian_A16_Swap(buf, 588 * 2);
|
|
break;
|
|
|
|
case DI_FORMAT_MODE1:
|
|
ct->fp->read(buf + 12 + 3 + 1, 2048);
|
|
encode_mode1_sector(lba + 150, buf);
|
|
break;
|
|
|
|
case DI_FORMAT_MODE1_RAW:
|
|
case DI_FORMAT_MODE2_RAW:
|
|
case DI_FORMAT_CDI_RAW:
|
|
ct->fp->read(buf, 2352);
|
|
break;
|
|
|
|
case DI_FORMAT_MODE2:
|
|
ct->fp->read(buf + 16, 2336);
|
|
encode_mode2_sector(lba + 150, buf);
|
|
break;
|
|
|
|
|
|
// FIXME: M2F1, M2F2, does sub-header come before or after user data(standards say before, but I wonder
|
|
// about cdrdao...).
|
|
case DI_FORMAT_MODE2_FORM1:
|
|
ct->fp->read(buf + 24, 2048);
|
|
//encode_mode2_form1_sector(lba + 150, buf);
|
|
break;
|
|
|
|
case DI_FORMAT_MODE2_FORM2:
|
|
ct->fp->read(buf + 24, 2324);
|
|
//encode_mode2_form2_sector(lba + 150, buf);
|
|
break;
|
|
|
|
}
|
|
|
|
if(ct->SubchannelMode)
|
|
ct->fp->read(buf + 2352, 96);
|
|
}
|
|
} // end if audible part of audio track read.
|
|
}
|
|
|
|
bool CDAccess_Image::Fast_Read_Raw_PW_TSRE(uint8* pwbuf, int32 lba) const noexcept
|
|
{
|
|
int32 track;
|
|
|
|
if(lba >= total_sectors)
|
|
{
|
|
subpw_synth_leadout_lba(toc, lba, pwbuf);
|
|
return(true);
|
|
}
|
|
|
|
memset(pwbuf, 0, 96);
|
|
try
|
|
{
|
|
track = MakeSubPQ(lba, pwbuf);
|
|
}
|
|
catch(...)
|
|
{
|
|
return(false);
|
|
}
|
|
|
|
//
|
|
// If TOC+BIN has embedded subchannel data, we can't fast-read(synthesize) it...
|
|
//
|
|
if(Tracks[track].SubchannelMode && lba >= (Tracks[track].LBA - Tracks[track].pregap_dv) && (lba < Tracks[track].LBA + Tracks[track].sectors))
|
|
return(false);
|
|
|
|
return(true);
|
|
}
|
|
|
|
//
|
|
// Note: this function makes use of the current contents(as in |=) in SubPWBuf.
|
|
//
|
|
int32 CDAccess_Image::MakeSubPQ(int32 lba, uint8 *SubPWBuf) const
|
|
{
|
|
uint8 buf[0xC];
|
|
int32 track;
|
|
uint32 lba_relative;
|
|
uint32 ma, sa, fa;
|
|
uint32 m, s, f;
|
|
uint8 pause_or = 0x00;
|
|
bool track_found = false;
|
|
|
|
for(track = FirstTrack; track < (FirstTrack + NumTracks); track++)
|
|
{
|
|
if(lba >= (Tracks[track].LBA - Tracks[track].pregap_dv - Tracks[track].pregap) && lba < (Tracks[track].LBA + Tracks[track].sectors + Tracks[track].postgap))
|
|
{
|
|
track_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!track_found)
|
|
throw(MDFN_Error(0, _("Could not find track for sector %u!"), lba));
|
|
|
|
if(lba < Tracks[track].LBA)
|
|
lba_relative = Tracks[track].LBA - 1 - lba;
|
|
else
|
|
lba_relative = lba - Tracks[track].LBA;
|
|
|
|
f = (lba_relative % 75);
|
|
s = ((lba_relative / 75) % 60);
|
|
m = (lba_relative / 75 / 60);
|
|
|
|
fa = (lba + 150) % 75;
|
|
sa = ((lba + 150) / 75) % 60;
|
|
ma = ((lba + 150) / 75 / 60);
|
|
|
|
uint8 adr = 0x1; // Q channel data encodes position
|
|
uint8 control = Tracks[track].subq_control;
|
|
|
|
// Handle pause(D7 of interleaved subchannel byte) bit, should be set to 1 when in pregap or postgap.
|
|
if((lba < Tracks[track].LBA) || (lba >= Tracks[track].LBA + Tracks[track].sectors))
|
|
{
|
|
//printf("pause_or = 0x80 --- %d\n", lba);
|
|
pause_or = 0x80;
|
|
}
|
|
|
|
// Handle pregap between audio->data track
|
|
{
|
|
int32 pg_offset = (int32)lba - Tracks[track].LBA;
|
|
|
|
// If we're more than 2 seconds(150 sectors) from the real "start" of the track/INDEX 01, and the track is a data track,
|
|
// and the preceding track is an audio track, encode it as audio(by taking the SubQ control field from the preceding track).
|
|
//
|
|
// TODO: Look into how we're supposed to handle subq control field in the four combinations of track types(data/audio).
|
|
//
|
|
if(pg_offset < -150)
|
|
{
|
|
if((Tracks[track].subq_control & SUBQ_CTRLF_DATA) && (FirstTrack < track) && !(Tracks[track - 1].subq_control & SUBQ_CTRLF_DATA))
|
|
{
|
|
//printf("Pregap part 1 audio->data: lba=%d track_lba=%d\n", lba, Tracks[track].LBA);
|
|
control = Tracks[track - 1].subq_control;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
memset(buf, 0, 0xC);
|
|
buf[0] = (adr << 0) | (control << 4);
|
|
buf[1] = U8_to_BCD(track);
|
|
|
|
if(lba < Tracks[track].LBA) // Index is 00 in pregap
|
|
buf[2] = U8_to_BCD(0x00);
|
|
else
|
|
buf[2] = U8_to_BCD(0x01);
|
|
|
|
// Track relative MSF address
|
|
buf[3] = U8_to_BCD(m);
|
|
buf[4] = U8_to_BCD(s);
|
|
buf[5] = U8_to_BCD(f);
|
|
|
|
buf[6] = 0; // Zerroooo
|
|
|
|
// Absolute MSF address
|
|
buf[7] = U8_to_BCD(ma);
|
|
buf[8] = U8_to_BCD(sa);
|
|
buf[9] = U8_to_BCD(fa);
|
|
|
|
subq_generate_checksum(buf);
|
|
|
|
if(!SubQReplaceMap.empty())
|
|
{
|
|
//printf("%d\n", lba);
|
|
auto it = SubQReplaceMap.find(LBA_to_ABA(lba));
|
|
|
|
if(it != SubQReplaceMap.end())
|
|
{
|
|
//printf("Replace: %d\n", lba);
|
|
memcpy(buf, it->second.data(), 12);
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < 96; i++)
|
|
SubPWBuf[i] |= (((buf[i >> 3] >> (7 - (i & 0x7))) & 1) ? 0x40 : 0x00) | pause_or;
|
|
|
|
return track;
|
|
}
|
|
|
|
void CDAccess_Image::Read_TOC(TOC *rtoc)
|
|
{
|
|
*rtoc = toc;
|
|
}
|
|
|
|
void CDAccess_Image::GenerateTOC(void)
|
|
{
|
|
toc.Clear();
|
|
|
|
toc.first_track = FirstTrack;
|
|
toc.last_track = FirstTrack + NumTracks - 1;
|
|
toc.disc_type = disc_type;
|
|
|
|
for(int i = FirstTrack; i < FirstTrack + NumTracks; i++)
|
|
{
|
|
if(Tracks[i].DIFormat == DI_FORMAT_CDI_RAW)
|
|
{
|
|
toc.first_track = std::min<int>(99, i + 1);
|
|
toc.last_track = std::max<int>(toc.first_track, toc.last_track);
|
|
}
|
|
|
|
toc.tracks[i].lba = Tracks[i].LBA;
|
|
toc.tracks[i].adr = ADR_CURPOS;
|
|
toc.tracks[i].control = Tracks[i].subq_control;
|
|
toc.tracks[i].valid = true;
|
|
}
|
|
|
|
toc.tracks[100].lba = total_sectors;
|
|
toc.tracks[100].adr = ADR_CURPOS;
|
|
toc.tracks[100].control = Tracks[FirstTrack + NumTracks - 1].subq_control & 0x4;
|
|
toc.tracks[100].valid = true;
|
|
}
|
|
|
|
|