beetle-psx-libretro/mednafen/cdrom/CDAccess_CHD.cpp
2021-11-18 01:15:38 +01:00

515 lines
14 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
*/
#include <mednafen/mednafen.h>
#include <mednafen/general.h>
#include <mednafen/mednafen-endian.h>
#include <mednafen/FileStream.h>
#include "CDAccess_CHD.h"
extern retro_log_printf_t log_cb;
// 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_COUNT
};
static const char *DI_CUE_Strings[7] =
{
"AUDIO",
"MODE1/2048",
"MODE1/2352",
"MODE2/2336",
"MODE2/2048",
"MODE2/2324",
"MODE2/2352"
};
bool CDAccess_CHD::ImageOpen(const char *path, bool image_memcache)
{
chd_error err = chd_open(path, CHD_OPEN_READ, NULL, &chd);
if (err != CHDERR_NONE)
return false;
if (image_memcache)
{
err = chd_precache(chd);
if (err != CHDERR_NONE)
return false;
}
/* allocate storage for sector reads */
const chd_header *head = chd_get_header(chd);
hunkmem = (uint8_t*)malloc(head->hunkbytes);
oldhunk = -1;
log_cb(RETRO_LOG_INFO, "chd_load '%s' hunkbytes=%d\n", path, head->hunkbytes);
int plba = -150;
uint32_t fileOffset = 0;
//int rlba = 0;
char type[64], subtype[32], pgtype[32], pgsub[32];
char meta_entry[256];
uint32_t meta_entry_size = 0;
while (1)
{
int tkid = 0, frames = 0, pad = 0, pregap = 0, postgap = 0;
err = chd_get_metadata(chd, CDROM_TRACK_METADATA2_TAG, NumTracks, meta_entry, sizeof(meta_entry), &meta_entry_size, NULL, NULL);
if (err == CHDERR_NONE)
{
sscanf(meta_entry, CDROM_TRACK_METADATA2_FORMAT,
&tkid, type, subtype, &frames, &pregap, pgtype, pgsub, &postgap);
}
else
{
err = chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, NumTracks, meta_entry, sizeof(meta_entry), &meta_entry_size, NULL, NULL);
if (err == CHDERR_NONE)
{
sscanf(meta_entry, CDROM_TRACK_METADATA_FORMAT,
&tkid, type, subtype, &frames);
}
else
{
/* if there's no valid metadata, this is the end of the TOC */
break;
}
}
if (strncmp(type, "MODE2_RAW", 9) != 0 && strncmp(type, "AUDIO", 5) != 0)
{
log_cb(RETRO_LOG_ERROR, "chd_parse track type %s unsupported\n", type);
return false;
}
else if (strncmp(subtype, "NONE", 4) != 0)
{
log_cb(RETRO_LOG_ERROR, "chd_parse track subtype %s unsupported\n", subtype);
return false;
}
/* add track */
NumTracks++;
if (NumTracks != tkid)
{
// should this be treated as a fatal error?
log_cb(RETRO_LOG_WARN, "chd tracks are out of order, missing a track or contain a duplicate!\n");
}
if (strncmp(type, "MODE2_RAW", 9) == 0)
{
Tracks[tkid].DIFormat = DI_FORMAT_MODE2_RAW;
Tracks[tkid].subq_control |= SUBQ_CTRLF_DATA;
}
else if (strncmp(type, "AUDIO", 5) == 0)
{
Tracks[tkid].DIFormat = DI_FORMAT_AUDIO;
Tracks[tkid].subq_control &= ~SUBQ_CTRLF_DATA;
Tracks[tkid].RawAudioMSBFirst = true;
}
Tracks[tkid].pregap = (tkid == 1) ? 150 : (pgtype[0] == 'V') ? 0 : pregap;
Tracks[tkid].pregap_dv = (pgtype[0] == 'V') ? pregap : 0;
plba += Tracks[tkid].pregap + Tracks[tkid].pregap_dv;
Tracks[tkid].LBA = plba;
Tracks[tkid].postgap = postgap;
Tracks[tkid].sectors = frames - Tracks[tkid].pregap_dv;
Tracks[tkid].SubchannelMode = 0;
Tracks[tkid].index[0] = -1;
Tracks[tkid].index[1] = 0;
fileOffset += Tracks[tkid].pregap_dv;
Tracks[tkid].FileOffset = fileOffset;
fileOffset += frames - Tracks[tkid].pregap_dv;
fileOffset += Tracks[tkid].postgap;
fileOffset += ((frames + 3) & ~3) - frames;
plba += frames - Tracks[tkid].pregap_dv;
plba += Tracks[tkid].postgap;
total_sectors += (tkid == 1) ? frames : frames + Tracks[tkid].pregap;
if (tkid < FirstTrack)
FirstTrack = tkid;
if (tkid > LastTrack)
LastTrack = tkid;
/*Tracks[tkid].pregap = pregap;
Tracks[tkid].postgap = postgap;
Tracks[tkid].index[0] = rlba - pregap;
Tracks[tkid].index[1] = rlba;
Tracks[tkid].pregap_dv = Tracks[tkid].index[1] - Tracks[tkid].index[0];
Tracks[tkid].LBA = rlba;
Tracks[tkid].sectors = frames;
Tracks[tkid].FileOffset = rlba * 2352;
total_sectors += frames;
if (tkid == 1)
rlba += frames + 150;
else
rlba += frames;*/
}
// prepare sbi file path
std::string base_dir, file_base, file_ext;
char sbi_ext[4] = { 's', 'b', 'i', 0 };
MDFN_GetFilePathComponents(path, &base_dir, &file_base, &file_ext);
if(file_ext.length() == 4 && file_ext[0] == '.')
{
for(int i = 0; i < 3; i++)
{
if(file_ext[1 + i] >= 'A' && file_ext[1 + i] <= 'Z')
sbi_ext[i] += 'A' - 'a';
}
}
sbi_path = MDFN_EvalFIP(base_dir, file_base + std::string(".") + std::string(sbi_ext), true);
return true;
}
void CDAccess_CHD::Cleanup(void)
{
if(chd != NULL)
chd_close(chd);
if (hunkmem != NULL)
free(hunkmem);
}
CDAccess_CHD::CDAccess_CHD(const char *path, bool image_memcache)
{
chd = NULL;
NumTracks = 0;
total_sectors = 0;
memset(Tracks, 0, sizeof(Tracks));
// initialize opposites
FirstTrack = 99;
LastTrack = 0;
if (!ImageOpen(path, image_memcache))
{
}
}
CDAccess_CHD::~CDAccess_CHD()
{
Cleanup();
}
// Note: this function makes use of the current contents(as in |=) in SubPWBuf.
int32_t CDAccess_CHD::MakeSubPQ(int32 lba, uint8 *SubPWBuf)
{
unsigned i;
uint8_t buf[0xC], adr, control;
int32_t track;
uint32_t lba_relative;
uint32_t ma, sa, fa;
uint32_t m, s, f;
uint8_t 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)
track = FirstTrack;
lba_relative = abs((int32)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);
adr = 0x1; // Q channel data encodes position
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))
pause_or = 0x80;
// Handle pregap between audio->data track
{
int32_t 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))
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;
/* 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())
{
std::map<uint32, cpp11_array_doodad>::const_iterator it = SubQReplaceMap.find(LBA_to_ABA(lba));
if(it != SubQReplaceMap.end())
memcpy(buf, it->second.data, 12);
}
for (i = 0; i < 96; i++)
SubPWBuf[i] |= (((buf[i >> 3] >> (7 - (i & 0x7))) & 1) ? 0x40 : 0x00) | pause_or;
return track;
}
bool CDAccess_CHD::Read_Raw_Sector(uint8 *buf, int32 lba)
{
uint8_t SimuQ[0xC];
int32_t track;
CDRFILE_TRACK_INFO *ct;
//
// Leadout synthesis
//
if (lba >= total_sectors)
{
uint8_t data_synth_mode = 0x01; // Default for DISC_TYPE_CDDA_OR_M1, would be 0x02 for DISC_TYPE_CD_XA
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:
data_synth_mode = 0x02;
break;
}
synth_leadout_sector_lba(data_synth_mode, ptoc, lba, buf);
}
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_t 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:
buf[12 + 6] = 0x20;
buf[12 + 10] = 0x20;
encode_mode2_form2_sector(lba + 150, buf);
// TODO: Zero out optional(?) checksum bytes?
break;
}
}
else
{
// read CHD hunk
const chd_header *head = chd_get_header(chd);
//int cad = (((lba - ct->LBA) * 2352) + ct->FileOffset) / 2352;
int cad = lba - ct->LBA + ct->FileOffset;
int sph = head->hunkbytes / (2352 + 96);
int hunknum = cad / sph; //(cad * head->unitbytes) / head->hunkbytes;
int hunkofs = cad % sph; //(cad * head->unitbytes) % head->hunkbytes;
int err = CHDERR_NONE;
/* each hunk holds ~8 sectors, optimize when reading contiguous sectors */
if (hunknum != oldhunk)
{
err = chd_read(chd, hunknum, hunkmem);
if (err != CHDERR_NONE)
log_cb(RETRO_LOG_ERROR, "chd_read_sector failed lba=%d error=%d\n", lba, err);
else
oldhunk = hunknum;
}
memcpy(buf, hunkmem + hunkofs * (2352 + 96), 2352);
if (ct->DIFormat == DI_FORMAT_AUDIO && ct->RawAudioMSBFirst)
Endian_A16_Swap(buf, 588 * 2);
}
return true;
}
bool CDAccess_CHD::Read_Raw_PW(uint8_t *buf, int32_t lba)
{
memset(buf, 0, 96);
MakeSubPQ(lba, buf);
return true;
}
bool CDAccess_CHD::Read_TOC(TOC *toc)
{
TOC_Clear(toc);
toc->first_track = FirstTrack;
toc->last_track = LastTrack;
toc->disc_type = DISC_TYPE_CD_XA; // always?
// read track info
for(int i = 1; i <= NumTracks; i++)
{
toc->tracks[i].control = Tracks[i].subq_control;
toc->tracks[i].adr = ADR_CURPOS;
toc->tracks[i].lba = Tracks[i].LBA;
}
toc->tracks[100].lba = total_sectors;
toc->tracks[100].adr = ADR_CURPOS;
toc->tracks[100].control = toc->tracks[toc->last_track].control & 0x4;
// Convenience leadout track duplication.
if (toc->last_track < 99)
toc->tracks[toc->last_track + 1] = toc->tracks[100];
if (!SubQReplaceMap.empty())
SubQReplaceMap.clear();
// Load SBI file, if present
if (filestream_exists(sbi_path.c_str()))
LoadSBI(sbi_path.c_str());
ptoc = toc;
log_cb(RETRO_LOG_INFO, "chd_read_toc: finished\n");
return true;
}
int CDAccess_CHD::LoadSBI(const char* sbi_path)
{
/* Loading SBI file */
uint8 header[4];
uint8 ed[4 + 10];
uint8 tmpq[12];
FileStream sbis(sbi_path, MODE_READ);
sbis.read(header, 4);
if(memcmp(header, "SBI\0", 4))
return -1;
while(sbis.read(ed, sizeof(ed)) == sizeof(ed))
{
/* Bad BCD MSF offset in SBI file. */
if(!BCD_is_valid(ed[0]) || !BCD_is_valid(ed[1]) || !BCD_is_valid(ed[2]))
return -1;
/* Unrecognized boogly oogly in SBI file */
if(ed[3] != 0x01)
return -1;
memcpy(tmpq, &ed[4], 10);
subq_generate_checksum(tmpq);
tmpq[10] ^= 0xFF;
tmpq[11] ^= 0xFF;
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);
}
log_cb(RETRO_LOG_INFO, "[CHD] Loaded SBI file %s\n", sbi_path);
return 0;
}
void CDAccess_CHD::Eject(bool eject_status)
{
}