1 /******************************************************************************/
2 /* Mednafen - Multi-system Emulator                                           */
3 /******************************************************************************/
4 /* CDAccess_CCD.cpp:
5 **  Copyright (C) 2013-2016 Mednafen Team
6 **
7 ** This program is free software; you can redistribute it and/or
8 ** modify it under the terms of the GNU General Public License
9 ** as published by the Free Software Foundation; either version 2
10 ** of the License, or (at your option) any later version.
11 **
12 ** This program is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ** GNU General Public License for more details.
16 **
17 ** You should have received a copy of the GNU General Public License
18 ** along with this program; if not, write to the Free Software Foundation, Inc.,
19 ** 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 */
21 
22 #include <mednafen/mednafen.h>
23 #include <mednafen/general.h>
24 
25 #include "CDAccess_CCD.h"
26 
27 #include <limits>
28 #include <limits.h>
29 #include <map>
30 
MDFN_strtoupper(std::string & str)31 static void MDFN_strtoupper(std::string &str)
32 {
33    const size_t len = str.length();
34 
35    for(size_t x = 0; x < len; x++)
36    {
37       if(str[x] >= 'a' && str[x] <= 'z')
38          str[x] = str[x] - 'a' + 'A';
39    }
40 }
41 
42 typedef std::map<std::string, std::string> CCD_Section;
43 
44    template<typename T>
CCD_ReadInt(CCD_Section & s,const std::string & propname,const bool have_defval=false,const int defval=0)45 static T CCD_ReadInt(CCD_Section &s, const std::string &propname, const bool have_defval = false, const int defval = 0)
46 {
47    CCD_Section::iterator zit = s.find(propname);
48 
49    if(zit == s.end())
50    {
51       if(have_defval)
52          return defval;
53       else
54          throw MDFN_Error(0, _("Missing property: %s"), propname.c_str());
55    }
56 
57    const std::string &v = zit->second;
58    int scan_base = 10;
59    size_t scan_offset = 0;
60    long ret = 0;
61 
62    if(v.length() >= 3 && v[0] == '0' && v[1] == 'x')
63    {
64       scan_base = 16;
65       scan_offset = 2;
66    }
67 
68    const char *vp = v.c_str() + scan_offset;
69    char *ep = NULL;
70 
71    if(std::numeric_limits<T>::is_signed)
72       ret = strtol(vp, &ep, scan_base);
73    else
74       ret = strtoul(vp, &ep, scan_base);
75 
76    if(!vp[0] || ep[0])
77    {
78       throw MDFN_Error(0, _("Property %s: Malformed integer: %s"), propname.c_str(), v.c_str());
79    }
80 
81    return ret;
82 }
83 
84 
CDAccess_CCD(const std::string & path,bool image_memcache)85 CDAccess_CCD::CDAccess_CCD(const std::string& path, bool image_memcache) : img_numsectors(0)
86 {
87    Load(path, image_memcache);
88 }
89 
Load(const std::string & path,bool image_memcache)90 bool CDAccess_CCD::Load(const std::string& path, bool image_memcache)
91 {
92    FileStream cf(path.c_str(), MODE_READ);
93    std::map<std::string, CCD_Section> Sections;
94    std::string linebuf;
95    std::string cur_section_name;
96    std::string dir_path, file_base, file_ext;
97    char img_extsd[4] = { 'i', 'm', 'g', 0 };
98    char sub_extsd[4] = { 's', 'u', 'b', 0 };
99 
100    MDFN_GetFilePathComponents(path, &dir_path, &file_base, &file_ext);
101 
102    if(file_ext.length() == 4 && file_ext[0] == '.')
103    {
104       signed char extupt[3] = { -1, -1, -1 };
105 
106       for(int i = 1; i < 4; i++)
107       {
108          if(file_ext[i] >= 'A' && file_ext[i] <= 'Z')
109             extupt[i - 1] = 'A' - 'a';
110          else if(file_ext[i] >= 'a' && file_ext[i] <= 'z')
111             extupt[i - 1] = 0;
112       }
113 
114       signed char av = -1;
115       for(int i = 0; i < 3; i++)
116       {
117          if(extupt[i] != -1)
118             av = extupt[i];
119          else
120             extupt[i] = av;
121       }
122 
123       if(av == -1)
124          av = 0;
125 
126       for(int i = 0; i < 3; i++)
127       {
128          if(extupt[i] == -1)
129             extupt[i] = av;
130       }
131 
132       for(int i = 0; i < 3; i++)
133       {
134          img_extsd[i] += extupt[i];
135          sub_extsd[i] += extupt[i];
136       }
137    }
138 
139    //printf("%s %d %d %d\n", file_ext.c_str(), extupt[0], extupt[1], extupt[2]);
140 
141    linebuf.reserve(256);
142 
143    while(cf.get_line(linebuf) >= 0)
144    {
145       MDFN_rtrim(linebuf);
146       MDFN_ltrim(linebuf);
147 
148       if(linebuf.length() == 0)	// Skip blank lines.
149          continue;
150 
151       if(linebuf[0] == '[')
152       {
153          if(linebuf.length() < 3 || linebuf[linebuf.length() - 1] != ']')
154          {
155             log_cb(RETRO_LOG_ERROR, "Malformed section specifier: %s", linebuf.c_str());
156             return false;
157          }
158 
159          cur_section_name = linebuf.substr(1, linebuf.length() - 2);
160          MDFN_strtoupper(cur_section_name);
161       }
162       else
163       {
164          const size_t feqpos = linebuf.find('=');
165          const size_t leqpos = linebuf.rfind('=');
166          std::string k, v;
167 
168          if(feqpos == std::string::npos || feqpos != leqpos)
169          {
170             log_cb(RETRO_LOG_ERROR, "Malformed value pair specifier: %s\n", linebuf.c_str());
171             return false;
172          }
173 
174          k = linebuf.substr(0, feqpos);
175          v = linebuf.substr(feqpos + 1);
176 
177          MDFN_rtrim(k);
178          MDFN_ltrim(k);
179 
180          MDFN_rtrim(v);
181          MDFN_ltrim(v);
182 
183          MDFN_strtoupper(k);
184 
185          Sections[cur_section_name][k] = v;
186       }
187    }
188 
189    {
190       CCD_Section& ds = Sections["DISC"];
191       unsigned toc_entries = CCD_ReadInt<unsigned>(ds, "TOCENTRIES");
192       unsigned num_sessions = CCD_ReadInt<unsigned>(ds, "SESSIONS");
193       bool data_tracks_scrambled = CCD_ReadInt<unsigned>(ds, "DATATRACKSSCRAMBLED");
194 
195       if(num_sessions != 1)
196       {
197          log_cb(RETRO_LOG_ERROR, "Unsupported number of sessions: %u\n", num_sessions);
198          return false;
199       }
200 
201       if(data_tracks_scrambled)
202       {
203          log_cb(RETRO_LOG_ERROR, "Scrambled CCD data tracks currently not supported.\n");
204          return false;
205       }
206 
207       //printf("MOO: %d\n", toc_entries);
208 
209       for(unsigned te = 0; te < toc_entries; te++)
210       {
211          char tmpbuf[64];
212          snprintf(tmpbuf, sizeof(tmpbuf), "ENTRY %u", te);
213          CCD_Section& ts = Sections[std::string(tmpbuf)];
214          unsigned session = CCD_ReadInt<unsigned>(ts, "SESSION");
215          uint8_t point = CCD_ReadInt<uint8_t>(ts, "POINT");
216          uint8_t adr = CCD_ReadInt<uint8_t>(ts, "ADR");
217          uint8_t control = CCD_ReadInt<uint8_t>(ts, "CONTROL");
218          uint8_t pmin = CCD_ReadInt<uint8_t>(ts, "PMIN");
219          uint8_t psec = CCD_ReadInt<uint8_t>(ts, "PSEC");
220          //uint8_t pframe = CCD_ReadInt<uint8_t>(ts, "PFRAME");
221          signed plba = CCD_ReadInt<signed>(ts, "PLBA");
222 
223          if(session != 1)
224          {
225             log_cb(RETRO_LOG_ERROR, "Unsupported TOC entry Session value: %u\n", session);
226             return false;
227          }
228 
229          // Reference: ECMA-394, page 5-14
230          if(point >= 1 && point <= 99)
231          {
232             tocd.tracks[point].adr = adr;
233             tocd.tracks[point].control = control;
234             tocd.tracks[point].lba = plba;
235             tocd.tracks[point].valid = true;
236          }
237          else switch(point)
238          {
239             default:
240                log_cb(RETRO_LOG_ERROR, "Unsupported TOC entry Point value: %u\n", point);
241                return false;
242             case 0xA0:
243                tocd.first_track = pmin;
244                tocd.disc_type = psec;
245                break;
246 
247             case 0xA1:
248                tocd.last_track = pmin;
249                break;
250 
251             case 0xA2:
252                tocd.tracks[100].adr = adr;
253                tocd.tracks[100].control = control;
254                tocd.tracks[100].lba = plba;
255                tocd.tracks[100].valid = true;
256                break;
257          }
258       }
259    }
260 
261    // Open image stream.
262    {
263       std::string image_path = MDFN_EvalFIP(dir_path, file_base + std::string(".") + std::string(img_extsd), true);
264 
265       if(image_memcache)
266          img_stream = new MemoryStream(new FileStream(image_path.c_str(), MODE_READ));
267       else
268          img_stream = new FileStream(image_path.c_str(), MODE_READ);
269 
270       uint64 ss = img_stream->size();
271 
272       if(ss % 2352)
273       {
274          log_cb(RETRO_LOG_ERROR, "CCD image size is not evenly divisible by 2352.\n");
275          return false;
276       }
277 
278       if(ss > 0x7FFFFFFF)
279       {
280          log_cb(RETRO_LOG_ERROR, "CCD image is too large.\n");
281          return false;
282       }
283 
284       img_numsectors = ss / 2352;
285    }
286 
287    // Open subchannel stream
288    {
289       std::string sub_path = MDFN_EvalFIP(dir_path, file_base + std::string(".") + std::string(sub_extsd), true);
290       FileStream sub_stream(sub_path.c_str(), MODE_READ);
291 
292       if(sub_stream.size() != (uint64)img_numsectors * 96)
293       {
294          log_cb(RETRO_LOG_ERROR, "CCD SUB file size mismatch.\n");
295          return false;
296       }
297 
298       sub_data = new uint8_t[(uint64)img_numsectors * 96];
299       sub_stream.read(sub_data, (uint64)img_numsectors * 96);
300    }
301 
302    CheckSubQSanity();
303 
304    return true;
305 }
306 
307 //
308 // 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
309 // 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
310 // error out here if a bad rip is detected.
311 //
312 // 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
313 // used in an effort to "repair" a broken rip.
314 //
CheckSubQSanity(void)315 bool CDAccess_CCD::CheckSubQSanity(void)
316 {
317    size_t checksum_pass_counter = 0;
318    int prev_lba = INT_MAX;
319    uint8_t prev_track = 0;
320 
321    for(size_t s = 0; s < img_numsectors; s++)
322    {
323       union
324       {
325          uint8_t full[96];
326          struct
327          {
328             uint8_t pbuf[12];
329             uint8_t qbuf[12];
330          };
331       } buf;
332 
333       memcpy(buf.full, &sub_data[s * 96], 96);
334 
335       if(subq_check_checksum(buf.qbuf))
336       {
337          uint8_t adr = buf.qbuf[0] & 0xF;
338 
339          if(adr == 0x01)
340          {
341             uint8_t track_bcd = buf.qbuf[1];
342             uint8_t index_bcd = buf.qbuf[2];
343             uint8_t rm_bcd = buf.qbuf[3];
344             uint8_t rs_bcd = buf.qbuf[4];
345             uint8_t rf_bcd = buf.qbuf[5];
346             uint8_t am_bcd = buf.qbuf[7];
347             uint8_t as_bcd = buf.qbuf[8];
348             uint8_t af_bcd = buf.qbuf[9];
349 
350             //printf("%2x %2x %2x\n", am_bcd, as_bcd, af_bcd);
351 
352             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) ||
353                   !BCD_is_valid(am_bcd) || !BCD_is_valid(as_bcd) || !BCD_is_valid(af_bcd) ||
354                   rs_bcd > 0x59 || rf_bcd > 0x74 || as_bcd > 0x59 || af_bcd > 0x74)
355             {
356                log_cb(RETRO_LOG_ERROR, "Garbage subchannel Q data detected(bad BCD/out of range): %02x:%02x:%02x %02x:%02x:%02x\n", rm_bcd, rs_bcd, rf_bcd, am_bcd, as_bcd, af_bcd);
357                return false;
358             }
359             else
360             {
361                int lba = ((BCD_to_U8(am_bcd) * 60 + BCD_to_U8(as_bcd)) * 75 + BCD_to_U8(af_bcd)) - 150;
362                uint8_t track = BCD_to_U8(track_bcd);
363 
364                if(prev_lba != INT_MAX && abs(lba - prev_lba) > 100)
365                {
366                   log_cb(RETRO_LOG_ERROR, "Garbage subchannel Q data detected(excessively large jump in AMSF)\n");
367                   return false;
368                }
369 
370                if(abs((int)(lba - s)) > 100)
371                {
372                   log_cb(RETRO_LOG_ERROR, "Garbage subchannel Q data detected(AMSF value is out of tolerance)\n");
373                   return false;
374                }
375 
376                prev_lba = lba;
377 
378                if(track < prev_track)
379                {
380                   log_cb(RETRO_LOG_ERROR, "Garbage subchannel Q data detected(bad track number)\n");
381                   return false;
382                }
383 
384                prev_track = track;
385             }
386             checksum_pass_counter++;
387          }
388       }
389    }
390 
391    //printf("%u/%u\n", checksum_pass_counter, img_numsectors);
392    return true;
393 }
394 
~CDAccess_CCD()395 CDAccess_CCD::~CDAccess_CCD()
396 {
397    if (img_stream)
398       delete[] img_stream;
399    if (sub_data)
400       delete[] sub_data;
401 }
402 
Read_Raw_Sector(uint8_t * buf,int32_t lba)403 bool CDAccess_CCD::Read_Raw_Sector(uint8_t *buf, int32_t lba)
404 {
405    if(lba < 0)
406    {
407       synth_udapp_sector_lba(0xFF, tocd, lba, 0, buf);
408       return true; /* TODO/FIXME - see if we need to return false here? */
409    }
410 
411    if((size_t)lba >= img_numsectors)
412    {
413       synth_leadout_sector_lba(0xFF, tocd, lba, buf);
414       return true; /* TODO/FIXME - see if we need to return false here? */
415    }
416 
417    img_stream->seek(lba * 2352, SEEK_SET);
418    img_stream->read(buf, 2352);
419 
420    subpw_interleave(&sub_data[lba * 96], buf + 2352);
421 
422    return true;
423 }
424 
Fast_Read_Raw_PW_TSRE(uint8_t * pwbuf,int32_t lba)425 bool CDAccess_CCD::Fast_Read_Raw_PW_TSRE(uint8_t* pwbuf, int32_t lba)
426 {
427    if(lba < 0)
428    {
429       subpw_synth_udapp_lba(tocd, lba, 0, pwbuf);
430       return true;
431    }
432 
433    if((size_t)lba >= img_numsectors)
434    {
435       subpw_synth_leadout_lba(tocd, lba, pwbuf);
436       return true;
437    }
438 
439    subpw_interleave(&sub_data[lba * 96], pwbuf);
440 
441    return true;
442 }
443 
Read_TOC(TOC * toc)444 bool CDAccess_CCD::Read_TOC(TOC *toc)
445 {
446    *toc = tocd;
447    return true;
448 }
449 
450