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