/* 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 */ //#define CHECK_CCD_GARBAGE_CUBQ_A //#define CHECK_CCD_GARBAGE_CUBQ_B //#define CHECK_CCD_GARBAGE_CUBQ_C //#define CHECK_CCD_GARBAGE_CUBQ_D #include "emuware/emuware.h" #include "../general.h" #include "../string/trim.h" #include "CDAccess_CCD.h" #include //wrapper to repair gettext stuff #define _(X) X #include #include #include using namespace CDUtility; 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'; } } } typedef std::map CCD_Section; template static T CCD_ReadInt(CCD_Section &s, const std::string &propname, const bool have_defval = false, const int defval = 0) { CCD_Section::iterator zit = s.find(propname); if(zit == s.end()) { if(have_defval) return defval; else throw MDFN_Error(0, _("Missing property: %s"), propname.c_str()); } const std::string &v = zit->second; int scan_base = 10; size_t scan_offset = 0; long ret = 0; if(v.length() >= 3 && v[0] == '0' && v[1] == 'x') { scan_base = 16; scan_offset = 2; } const char *vp = v.c_str() + scan_offset; char *ep = NULL; if(std::numeric_limits::is_signed) ret = strtol(vp, &ep, scan_base); else ret = strtoul(vp, &ep, scan_base); if(!vp[0] || ep[0]) { throw MDFN_Error(0, _("Property %s: Malformed integer: %s"), propname.c_str(), v.c_str()); } //if(ret < minv || ret > maxv) //{ // throw MDFN_Error(0, _("Property %s: Integer %ld out of range(accepted: %d through %d)."), propname.c_str(), ret, minv, maxv); //} return ret; } CDAccess_CCD::CDAccess_CCD(const std::string& path, bool image_memcache) : img_numsectors(0) { Load(path, image_memcache); } void CDAccess_CCD::Load(const std::string& path, bool image_memcache) { FileStream cf(path, FileStream::MODE_READ); std::map Sections; std::string linebuf; std::string cur_section_name; std::string dir_path, file_base, file_ext; char img_extsd[4] = { 'i', 'm', 'g', 0 }; char sub_extsd[4] = { 's', 'u', 'b', 0 }; MDFN_GetFilePathComponents(path, &dir_path, &file_base, &file_ext); if(file_ext.length() == 4 && file_ext[0] == '.') { signed char extupt[3] = { -1, -1, -1 }; for(int i = 1; i < 4; i++) { if(file_ext[i] >= 'A' && file_ext[i] <= 'Z') extupt[i - 1] = 'A' - 'a'; else if(file_ext[i] >= 'a' && file_ext[i] <= 'z') extupt[i - 1] = 0; } signed char av = -1; for(int i = 0; i < 3; i++) { if(extupt[i] != -1) av = extupt[i]; else extupt[i] = av; } if(av == -1) av = 0; for(int i = 0; i < 3; i++) { if(extupt[i] == -1) extupt[i] = av; } for(int i = 0; i < 3; i++) { img_extsd[i] += extupt[i]; sub_extsd[i] += extupt[i]; } } //printf("%s %d %d %d\n", file_ext.c_str(), extupt[0], extupt[1], extupt[2]); linebuf.reserve(256); while(cf.get_line(linebuf) >= 0) { MDFN_trim(linebuf); if(linebuf.length() == 0) // Skip blank lines. continue; if(linebuf[0] == '[') { if(linebuf.length() < 3 || linebuf[linebuf.length() - 1] != ']') throw MDFN_Error(0, _("Malformed section specifier: %s"), linebuf.c_str()); cur_section_name = linebuf.substr(1, linebuf.length() - 2); MDFN_strtoupper(cur_section_name); } else { const size_t feqpos = linebuf.find('='); const size_t leqpos = linebuf.rfind('='); std::string k, v; if(feqpos == std::string::npos || feqpos != leqpos) throw MDFN_Error(0, _("Malformed value pair specifier: %s"), linebuf.c_str()); k = linebuf.substr(0, feqpos); v = linebuf.substr(feqpos + 1); MDFN_trim(k); MDFN_trim(v); MDFN_strtoupper(k); Sections[cur_section_name][k] = v; } } { CCD_Section& ds = Sections["DISC"]; unsigned toc_entries = CCD_ReadInt(ds, "TOCENTRIES"); unsigned num_sessions = CCD_ReadInt(ds, "SESSIONS"); bool data_tracks_scrambled = CCD_ReadInt(ds, "DATATRACKSSCRAMBLED"); if(num_sessions != 1) throw MDFN_Error(0, _("Unsupported number of sessions: %u"), num_sessions); if(data_tracks_scrambled) throw MDFN_Error(0, _("Scrambled CCD data tracks currently not supported.")); //printf("MOO: %d\n", toc_entries); for(unsigned te = 0; te < toc_entries; te++) { char tmpbuf[64]; trio_snprintf(tmpbuf, sizeof(tmpbuf), "ENTRY %u", te); CCD_Section& ts = Sections[std::string(tmpbuf)]; unsigned session = CCD_ReadInt(ts, "SESSION"); uint8 point = CCD_ReadInt(ts, "POINT"); uint8 adr = CCD_ReadInt(ts, "ADR"); uint8 control = CCD_ReadInt(ts, "CONTROL"); uint8 pmin = CCD_ReadInt(ts, "PMIN"); uint8 psec = CCD_ReadInt(ts, "PSEC"); //uint8 pframe = CCD_ReadInt(ts, "PFRAME"); signed plba = CCD_ReadInt(ts, "PLBA"); if(session != 1) throw MDFN_Error(0, "Unsupported TOC entry Session value: %u", session); // Reference: ECMA-394, page 5-14 if(point >= 1 && point <= 99) { tocd.tracks[point].adr = adr; tocd.tracks[point].control = control; tocd.tracks[point].lba = plba; tocd.tracks[point].valid = true; } else switch(point) { default: throw MDFN_Error(0, "Unsupported TOC entry Point value: %u", point); break; case 0xA0: tocd.first_track = pmin; tocd.disc_type = psec; break; case 0xA1: tocd.last_track = pmin; break; case 0xA2: tocd.tracks[100].adr = adr; tocd.tracks[100].control = control; tocd.tracks[100].lba = plba; tocd.tracks[100].valid = true; break; } } } // // Open image stream. { std::string image_path = MDFN_EvalFIP(dir_path, file_base + std::string(".") + std::string(img_extsd), true); if(image_memcache) { img_stream.reset(new MemoryStream(new FileStream(image_path, FileStream::MODE_READ))); } else { img_stream.reset(new FileStream(image_path, FileStream::MODE_READ)); } uint64 ss = img_stream->size(); if(ss % 2352) throw MDFN_Error(0, _("CCD image size is not evenly divisible by 2352.")); if(ss > 0x7FFFFFFF) throw MDFN_Error(0, _("CCD image is too large.")); img_numsectors = ss / 2352; } // // Open subchannel stream { std::string sub_path = MDFN_EvalFIP(dir_path, file_base + std::string(".") + std::string(sub_extsd), true); FileStream sub_stream(sub_path, FileStream::MODE_READ); if(sub_stream.size() != (uint64)img_numsectors * 96) throw MDFN_Error(0, _("CCD SUB file size mismatch.")); sub_data.reset(new uint8[(uint64)img_numsectors * 96]); sub_stream.read(sub_data.get(), (uint64)img_numsectors * 96); } CheckSubQSanity(); } // // Checks for Q subchannel mode 1(current time) data that has a correct checksum, but the data is nonsensical or corrupted nonetheless; this is the // case for some bad rips floating around on the Internet. Allowing these bad rips to be used will cause all sorts of problems during emulation, so we // error out here if a bad rip is detected. // // This check is not as aggressive or exhaustive as it could be, and will not detect all potential Q subchannel rip errors; as such, it should definitely NOT be // used in an effort to "repair" a broken rip. // void CDAccess_CCD::CheckSubQSanity(void) { size_t checksum_pass_counter = 0; int prev_lba = INT_MAX; uint8 prev_track = 0; for(size_t s = 0; s < img_numsectors; s++) { union { uint8 full[96]; struct { uint8 pbuf[12]; uint8 qbuf[12]; }; } buf; memcpy(buf.full, &sub_data[s * 96], 96); if(subq_check_checksum(buf.qbuf)) { uint8 adr = buf.qbuf[0] & 0xF; if(adr == 0x01) { uint8 track_bcd = buf.qbuf[1]; uint8 index_bcd = buf.qbuf[2]; uint8 rm_bcd = buf.qbuf[3]; uint8 rs_bcd = buf.qbuf[4]; uint8 rf_bcd = buf.qbuf[5]; uint8 am_bcd = buf.qbuf[7]; uint8 as_bcd = buf.qbuf[8]; uint8 af_bcd = buf.qbuf[9]; //printf("%2x %2x %2x\n", am_bcd, as_bcd, af_bcd); if(!BCD_is_valid(track_bcd) || !BCD_is_valid(index_bcd) || !BCD_is_valid(rm_bcd) || !BCD_is_valid(rs_bcd) || !BCD_is_valid(rf_bcd) || !BCD_is_valid(am_bcd) || !BCD_is_valid(as_bcd) || !BCD_is_valid(af_bcd) || rs_bcd > 0x59 || rf_bcd > 0x74 || as_bcd > 0x59 || af_bcd > 0x74) { #ifdef CHECK_CCD_GARBAGE_SUBQ_A throw MDFN_Error(0, _("Garbage subchannel Q data detected(bad BCD/out of range): %02x:%02x:%02x %02x:%02x:%02x"), rm_bcd, rs_bcd, rf_bcd, am_bcd, as_bcd, af_bcd); #endif } else { int lba = ((BCD_to_U8(am_bcd) * 60 + BCD_to_U8(as_bcd)) * 75 + BCD_to_U8(af_bcd)) - 150; uint8 track = BCD_to_U8(track_bcd); #ifdef CHECK_CCD_GARBAGE_SUBQ_B if(prev_lba != INT_MAX && abs(lba - prev_lba) > 100) throw MDFN_Error(0, _("Garbage subchannel Q data detected(excessively large jump in AMSF)")); #endif #ifdef CHECK_CCD_GARBAGE_SUBQ_C if(abs(lba - (int)s) > 100) //zero 19-jun-2015 a bit of a sneaky signed/unsigned fixup here throw MDFN_Error(0, _("Garbage subchannel Q data detected(AMSF value is out of tolerance)")); #endif prev_lba = lba; #ifdef CHECK_CCD_GARBAGE_SUBQ_D if(track < prev_track) throw MDFN_Error(0, _("Garbage subchannel Q data detected(bad track number)")); #endif //else if(prev_track && track - pre prev_track = track; } checksum_pass_counter++; } } } //printf("%u/%u\n", checksum_pass_counter, img_numsectors); } CDAccess_CCD::~CDAccess_CCD() { } void CDAccess_CCD::Read_Raw_Sector(uint8 *buf, int32 lba) { if(lba < 0) { synth_udapp_sector_lba(0xFF, tocd, lba, 0, buf); return; } if((size_t)lba >= img_numsectors) { synth_leadout_sector_lba(0xFF, tocd, lba, buf); return; } img_stream->seek(lba * 2352, SEEK_SET); img_stream->read(buf, 2352); subpw_interleave(&sub_data[lba * 96], buf + 2352); } bool CDAccess_CCD::Fast_Read_Raw_PW_TSRE(uint8* pwbuf, int32 lba) const noexcept { if(lba < 0) { subpw_synth_udapp_lba(tocd, lba, 0, pwbuf); return true; } if((size_t)lba >= img_numsectors) { subpw_synth_leadout_lba(tocd, lba, pwbuf); return true; } subpw_interleave(&sub_data[lba * 96], pwbuf); return true; } void CDAccess_CCD::Read_TOC(CDUtility::TOC *toc) { *toc = tocd; }