1
2 #include "libretro.h"
3 #include <string/stdstring.h>
4
5 #include "mednafen/mednafen-types.h"
6 #include "mednafen/git.h"
7 #include "mednafen/general.h"
8 #include "mednafen/cdrom/cdromif.h"
9 #include "mednafen/hash/md5.h"
10 #include "mednafen/hash/sha256.h"
11 #include "mednafen/ss/ss.h"
12 #include "mednafen/ss/cdb.h"
13 #include "mednafen/ss/smpc.h"
14
15 //------------------------------------------------------------------------------
16 // Locals
17 //------------------------------------------------------------------------------
18
19 static bool g_eject_state;
20
21 static int g_current_disc;
22
23 static unsigned g_initial_disc;
24 static std::string g_initial_disc_path;
25
26 static std::vector<CDIF *> CDInterfaces;
27
28 static std::vector<std::string> disk_image_paths;
29 static std::vector<std::string> disk_image_labels;
30
31 //
32 // Remember to rebuild region database in db.cpp if changing the order of
33 // entries in this table(and be careful about game id collisions,
34 // e.g. with some Korean games).
35 //
36 static const struct
37 {
38 const char c;
39 const char* str; // Community-defined region string that may appear in filename.
40 unsigned region;
41 }
42 region_strings[] =
43 {
44 // Listed in order of preference for multi-region games.
45 { 'U', "USA", SMPC_AREA_NA },
46 { 'J', "Japan", SMPC_AREA_JP },
47 { 'K', "Korea", SMPC_AREA_KR },
48
49 { 'E', "Europe", SMPC_AREA_EU_PAL },
50 { 'E', "Germany", SMPC_AREA_EU_PAL },
51 { 'E', "France", SMPC_AREA_EU_PAL },
52 { 'E', "Spain", SMPC_AREA_EU_PAL },
53
54 { 'B', "Brazil", SMPC_AREA_CSA_NTSC },
55
56 { 'T', "Asia_NTSC", SMPC_AREA_ASIA_NTSC },
57 { 'A', "Asia_PAL", SMPC_AREA_ASIA_PAL },
58 { 'L', "CSA_PAL", SMPC_AREA_CSA_PAL },
59 };
60
61
62 //------------------------------------------------------------------------------
63 // Local Functions
64 //------------------------------------------------------------------------------
65
ReadM3U(std::vector<std::string> & file_list,std::string path,unsigned depth=0)66 static void ReadM3U( std::vector<std::string> &file_list, std::string path, unsigned depth = 0 )
67 {
68 std::string dir_path;
69 char linebuf[ 2048 ];
70 FILE *fp = fopen(path.c_str(), "rb");
71 if (fp == NULL)
72 return;
73
74 MDFN_GetFilePathComponents(path, &dir_path);
75
76 while(fgets(linebuf, sizeof(linebuf), fp) != NULL)
77 {
78 std::string efp;
79
80 if(linebuf[0] == '#')
81 continue;
82 string_trim_whitespace_right(linebuf);
83 if(linebuf[0] == 0)
84 continue;
85
86 efp = MDFN_EvalFIP(dir_path, std::string(linebuf));
87 if(efp.size() >= 4 && efp.substr(efp.size() - 4) == ".m3u")
88 {
89 if(efp == path)
90 {
91 log_cb(RETRO_LOG_ERROR, "M3U at \"%s\" references self.\n", efp.c_str());
92 goto end;
93 }
94
95 if(depth == 99)
96 {
97 log_cb(RETRO_LOG_ERROR, "M3U load recursion too deep!\n");
98 goto end;
99 }
100
101 ReadM3U(file_list, efp, depth++);
102 }
103 else
104 {
105 file_list.push_back(efp);
106 }
107 }
108
109 end:
110 fclose(fp);
111 }
112
IsSaturnDisc(const uint8 * sa32k)113 static bool IsSaturnDisc( const uint8* sa32k )
114 {
115 if(sha256(&sa32k[0x100], 0xD00) != "96b8ea48819cfa589f24c40aa149c224c420dccf38b730f00156efe25c9bbc8f"_sha256)
116 return false;
117
118 if(memcmp(&sa32k[0], "SEGA SEGASATURN ", 16))
119 return false;
120
121 log_cb(RETRO_LOG_INFO, "This is a Saturn disc.\n");
122 return true;
123 }
124
disk_set_eject_state(bool ejected)125 static bool disk_set_eject_state( bool ejected )
126 {
127 if ( ejected == g_eject_state )
128 {
129 // no change
130 return false;
131 }
132 else
133 {
134 // store
135 g_eject_state = ejected;
136
137 if ( ejected )
138 {
139 // open disc tray
140 CDB_SetDisc( true, NULL );
141 }
142 else
143 {
144 // close the tray - with a disc inside
145 if ( g_current_disc < CDInterfaces.size() ) {
146 CDB_SetDisc( false, CDInterfaces[g_current_disc] );
147 } else {
148 CDB_SetDisc( false, NULL );
149 }
150 }
151
152 return true;
153 }
154 }
155
disk_get_eject_state(void)156 static bool disk_get_eject_state(void)
157 {
158 return g_eject_state;
159 }
160
disk_set_image_index(unsigned index)161 static bool disk_set_image_index(unsigned index)
162 {
163 // only listen if the tray is open
164 if ( g_eject_state == true )
165 {
166 if ( index < CDInterfaces.size() ) {
167 // log_cb(RETRO_LOG_INFO, "Selected disc %d of %d.\n", index+1, CDInterfaces.size() );
168 g_current_disc = index;
169 return true;
170 }
171 }
172
173 return false;
174 }
175
disk_get_num_images(void)176 static unsigned disk_get_num_images(void)
177 {
178 return CDInterfaces.size();
179 }
180
181 /*
182 #if 0
183 // Mednafen really doesn't support adding disk images on the fly ...
184 // Hack around this.
185 static void update_md5_checksum(CDIF *iface)
186 {
187 uint8 LayoutMD5[16];
188 md5_context layout_md5;
189 CD_TOC toc;
190
191 md5_starts(&layout_md5);
192
193 TOC_Clear(&toc);
194
195 iface->ReadTOC(&toc);
196
197 md5_update_u32_as_lsb(&layout_md5, toc.first_track);
198 md5_update_u32_as_lsb(&layout_md5, toc.last_track);
199 md5_update_u32_as_lsb(&layout_md5, toc.tracks[100].lba);
200
201 for (uint32 track = toc.first_track; track <= toc.last_track; track++)
202 {
203 md5_update_u32_as_lsb(&layout_md5, toc.tracks[track].lba);
204 md5_update_u32_as_lsb(&layout_md5, toc.tracks[track].control & 0x4);
205 }
206
207 md5_finish(&layout_md5, LayoutMD5);
208 memcpy(MDFNGameInfo->MD5, LayoutMD5, 16);
209
210 char *md5 = md5_asciistr(MDFNGameInfo->MD5);
211 log_cb(RETRO_LOG_INFO, "[Mednafen]: Updated md5 checksum: %s.\n", md5);
212 }
213 #endif
214 */
disk_replace_image_index(unsigned index,const struct retro_game_info * info)215 static bool disk_replace_image_index(unsigned index, const struct retro_game_info *info)
216 {
217 log_cb(RETRO_LOG_INFO, "disk_replace_image_index(%d,*info) called.\n", index);
218 return false;
219
220 // todo - untested
221
222 #if 0
223 if (index >= disk_get_num_images() || !g_eject_state)
224 return false;
225
226 if (!info)
227 {
228 delete CDInterfaces.at(index);
229 CDInterfaces.erase(CDInterfaces.begin() + index);
230 if (index < CD_SelectedDisc)
231 CD_SelectedDisc--;
232
233 disk_image_paths.erase(disk_image_paths.begin() + index);
234 disk_image_labels.erase(disk_image_labels.begin() + index);
235
236 // Poke into psx.cpp
237 CalcDiscSCEx();
238
239 return true;
240 }
241
242 bool success = true;
243 CDIF *iface = CDIF_Open(&success, info->path, false, false);
244
245 if (!success)
246 return false;
247
248 delete CDInterfaces.at(index);
249 CDInterfaces.at(index) = iface;
250 CalcDiscSCEx();
251
252 /* If we replace, we want the "swap disk manually effect". */
253 extract_basename(retro_cd_base_name, info->path, sizeof(retro_cd_base_name));
254 /* Ugly, but needed to get proper disk swapping effect. */
255 update_md5_checksum(iface);
256
257 /* Update disk path/label vectors */
258 disk_image_paths[index] = info->path;
259 disk_image_labels[index] = retro_cd_base_name;
260
261 return true;
262 #endif
263 }
264
disk_add_image_index(void)265 static bool disk_add_image_index(void)
266 {
267 log_cb(RETRO_LOG_INFO, "disk_add_image_index called.\n");
268
269 CDInterfaces.push_back(NULL);
270 disk_image_paths.push_back("");
271 disk_image_labels.push_back("");
272 return true;
273 }
274
disk_set_initial_image(unsigned index,const char * path)275 static bool disk_set_initial_image(unsigned index, const char *path)
276 {
277 if (string_is_empty(path))
278 return false;
279
280 g_initial_disc = index;
281 g_initial_disc_path = path;
282
283 return true;
284 }
285
disk_get_image_path(unsigned index,char * path,size_t len)286 static bool disk_get_image_path(unsigned index, char *path, size_t len)
287 {
288 if (len < 1)
289 return false;
290
291 if ((index < CDInterfaces.size()) &&
292 (index < disk_image_paths.size()))
293 {
294 if (!string_is_empty(disk_image_paths[index].c_str()))
295 {
296 strlcpy(path, disk_image_paths[index].c_str(), len);
297 return true;
298 }
299 }
300
301 return false;
302 }
303
disk_get_image_label(unsigned index,char * label,size_t len)304 static bool disk_get_image_label(unsigned index, char *label, size_t len)
305 {
306 if (len < 1)
307 return false;
308
309 if ((index < CDInterfaces.size()) &&
310 (index < disk_image_labels.size()))
311 {
312 if (!string_is_empty(disk_image_labels[index].c_str()))
313 {
314 strlcpy(label, disk_image_labels[index].c_str(), len);
315 return true;
316 }
317 }
318
319 return false;
320 }
321
322 //------------------------------------------------------------------------------
323 // Global Functions
324 //------------------------------------------------------------------------------
325
326 /* This has to be 'global', since we need to
327 * access the current disk index inside
328 * libretro.cpp */
disk_get_image_index(void)329 unsigned disk_get_image_index(void)
330 {
331 return g_current_disc;
332 }
333
334 static struct retro_disk_control_callback disk_interface =
335 {
336 disk_set_eject_state,
337 disk_get_eject_state,
338 disk_get_image_index,
339 disk_set_image_index,
340 disk_get_num_images,
341 disk_replace_image_index,
342 disk_add_image_index,
343 };
344
345 static struct retro_disk_control_ext_callback disk_interface_ext =
346 {
347 disk_set_eject_state,
348 disk_get_eject_state,
349 disk_get_image_index,
350 disk_set_image_index,
351 disk_get_num_images,
352 disk_replace_image_index,
353 disk_add_image_index,
354 disk_set_initial_image,
355 disk_get_image_path,
356 disk_get_image_label,
357 };
358
extract_basename(char * buf,const char * path,size_t size)359 void extract_basename(char *buf, const char *path, size_t size)
360 {
361 const char *base = strrchr(path, '/');
362 if (!base)
363 base = strrchr(path, '\\');
364 if (!base)
365 base = path;
366
367 if (*base == '\\' || *base == '/')
368 base++;
369
370 strncpy(buf, base, size - 1);
371 buf[size - 1] = '\0';
372
373 char *ext = strrchr(buf, '.');
374 if (ext)
375 *ext = '\0';
376 }
377
extract_directory(char * buf,const char * path,size_t size)378 void extract_directory(char *buf, const char *path, size_t size)
379 {
380 strncpy(buf, path, size - 1);
381 buf[size - 1] = '\0';
382
383 char *base = strrchr(buf, '/');
384 if (!base)
385 base = strrchr(buf, '\\');
386
387 if (base)
388 *base = '\0';
389 else
390 buf[0] = '\0';
391 }
392
disc_init(retro_environment_t environ_cb)393 void disc_init( retro_environment_t environ_cb )
394 {
395 unsigned dci_version = 0;
396
397 // start closed
398 g_eject_state = false;
399
400 g_initial_disc = 0;
401 g_initial_disc_path.clear();
402
403 // register vtable with environment
404 if (environ_cb(RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION, &dci_version) && (dci_version >= 1))
405 environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE, &disk_interface_ext);
406 else
407 environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &disk_interface);
408 }
409
disc_calcgameid(uint8 * id_out16,uint8 * fd_id_out16,char * sgid)410 void disc_calcgameid( uint8* id_out16, uint8* fd_id_out16, char* sgid )
411 {
412 md5_context mctx;
413 uint8_t buf[2048];
414
415 log_cb(RETRO_LOG_INFO, "Calculating game ID (%d discs)\n", CDInterfaces.size() );
416
417 mctx.starts();
418
419 for(size_t x = 0; x < CDInterfaces.size(); x++)
420 {
421 CDIF *c = CDInterfaces[x];
422 TOC toc;
423
424 c->ReadTOC(&toc);
425
426 mctx.update_u32_as_lsb(toc.first_track);
427 mctx.update_u32_as_lsb(toc.last_track);
428 mctx.update_u32_as_lsb(toc.disc_type);
429
430 for(unsigned i = 1; i <= 100; i++)
431 {
432 const auto& t = toc.tracks[i];
433
434 mctx.update_u32_as_lsb(t.adr);
435 mctx.update_u32_as_lsb(t.control);
436 mctx.update_u32_as_lsb(t.lba);
437 mctx.update_u32_as_lsb(t.valid);
438 }
439
440 for(unsigned i = 0; i < 512; i++)
441 {
442 if(c->ReadSector((uint8_t*)&buf[0], i, 1) >= 0x1)
443 {
444 if(i == 0)
445 {
446 char* tmp;
447 memcpy(sgid, (void*)(&buf[0x20]), 16);
448 sgid[16] = 0;
449 if((tmp = strrchr(sgid, 'V')))
450 {
451 do
452 {
453 *tmp = 0;
454 } while(tmp-- != sgid && (signed char)*tmp <= 0x20);
455 }
456 }
457
458 mctx.update(&buf[0], 2048);
459 }
460 }
461
462 if(x == 0)
463 {
464 md5_context fd_mctx = mctx;
465 fd_mctx.finish(fd_id_out16);
466 }
467 }
468
469 mctx.finish(id_out16);
470 }
471
disc_cleanup()472 void disc_cleanup()
473 {
474 for(unsigned i = 0; i < CDInterfaces.size(); i++) {
475 delete CDInterfaces[i];
476 }
477 CDInterfaces.clear();
478
479 disk_image_paths.clear();
480 disk_image_labels.clear();
481
482 g_current_disc = 0;
483 }
484
disc_detect_region(unsigned * region)485 bool disc_detect_region( unsigned* region )
486 {
487 uint8_t *buf = new uint8[2048 * 16];
488 uint64 possible_regions = 0;
489
490 for(auto& c : CDInterfaces)
491 {
492 if(c->ReadSector(&buf[0], 0, 16) != 0x1)
493 continue;
494
495 if(!IsSaturnDisc(&buf[0]))
496 continue;
497
498 for(unsigned i = 0; i < 16; i++)
499 {
500 for(auto const& rs : region_strings)
501 {
502 if(rs.c == buf[0x40 + i])
503 {
504 possible_regions |= (uint64)1 << rs.region;
505 break;
506 }
507 }
508 }
509
510 break;
511 }
512
513 delete[] buf;
514
515 for(auto const& rs : region_strings)
516 {
517 if(possible_regions & ((uint64)1 << rs.region))
518 {
519 log_cb(RETRO_LOG_INFO, "Disc Region: \"%s\"\n", rs.str );
520 *region = rs.region;
521 return true;
522 }
523 }
524
525 return false;
526 }
527
disc_test()528 bool disc_test()
529 {
530 size_t i;
531
532 // For each disc
533 for( i = 0; i < CDInterfaces.size(); i++ )
534 {
535 TOC toc;
536
537 CDInterfaces[i]->ReadTOC(&toc);
538
539 // For each track
540 for( int32 track = 1; track <= 99; track++)
541 {
542 if(!toc.tracks[track].valid)
543 continue;
544
545 if(toc.tracks[track].control & SUBQ_CTRLF_DATA)
546 continue;
547
548 //
549 //
550 //
551
552 const int32 start_lba = toc.tracks[track].lba;
553 const int32 end_lba = start_lba + 32 - 1;
554 bool any_subq_curpos = false;
555
556 for(int32 lba = start_lba; lba <= end_lba; lba++)
557 {
558 uint8 pwbuf[96];
559 uint8 qbuf[12];
560
561 if(!CDInterfaces[i]->ReadRawSectorPWOnly(pwbuf, lba, false))
562 {
563 log_cb(RETRO_LOG_ERROR,
564 "Testing Disc %zu of %zu: Error reading sector at LBA %d.\n",
565 i + 1, CDInterfaces.size(), lba );
566 return false;
567 }
568
569 subq_deinterleave(pwbuf, qbuf);
570 if(subq_check_checksum(qbuf) && (qbuf[0] & 0xF) == ADR_CURPOS)
571 {
572 const uint8 qm = qbuf[7];
573 const uint8 qs = qbuf[8];
574 const uint8 qf = qbuf[9];
575 uint8 lm, ls, lf;
576
577 any_subq_curpos = true;
578
579 LBA_to_AMSF(lba, &lm, &ls, &lf);
580 lm = U8_to_BCD(lm);
581 ls = U8_to_BCD(ls);
582 lf = U8_to_BCD(lf);
583
584 if(lm != qm || ls != qs || lf != qf)
585 {
586 log_cb(RETRO_LOG_ERROR,
587 "Testing Disc %zu of %zu: Time mismatch at LBA=%d(%02x:%02x:%02x); Q subchannel: %02x:%02x:%02x\n",
588 i + 1, CDInterfaces.size(),
589 lba,
590 lm, ls, lf,
591 qm, qs, qf);
592
593 return false;
594 }
595 }
596 }
597
598 if(!any_subq_curpos)
599 {
600 log_cb(RETRO_LOG_ERROR,
601 "Testing Disc %zu of %zu: No valid Q subchannel ADR_CURPOS data present at LBA %d-%d?!\n",
602 i + 1, CDInterfaces.size(),
603 start_lba, end_lba );
604 return false;
605 }
606
607 break;
608
609 }; // for each track
610
611 }; // for each disc
612
613 return true;
614 }
615
disc_select(unsigned disc_num)616 void disc_select( unsigned disc_num )
617 {
618 if ( disc_num < CDInterfaces.size() ) {
619 g_current_disc = disc_num;
620 CDB_SetDisc( false, CDInterfaces[ g_current_disc ] );
621 }
622 }
623
disc_load_content(MDFNGI * game_interface,const char * content_name,uint8 * fd_id,char * sgid,bool image_memcache)624 bool disc_load_content( MDFNGI* game_interface, const char* content_name, uint8* fd_id, char* sgid, bool image_memcache )
625 {
626 disc_cleanup();
627
628 if ( !content_name ) {
629 return false;
630 }
631
632 uint8 LayoutMD5[ 16 ];
633
634 log_cb( RETRO_LOG_INFO, "Loading \"%s\"\n", content_name );
635
636 try
637 {
638 size_t content_name_len;
639 content_name_len = strlen( content_name );
640 if ( content_name_len > 4 )
641 {
642 const char* content_ext = content_name + content_name_len - 4;
643 if ( !strcasecmp( content_ext, ".m3u" ) )
644 {
645 // multiple discs
646 ReadM3U(disk_image_paths, content_name);
647 for(unsigned i = 0; i < disk_image_paths.size(); i++)
648 {
649 char image_label[4096];
650 bool success = true;
651
652 image_label[0] = '\0';
653
654 log_cb(RETRO_LOG_INFO, "Adding CD: \"%s\".\n", disk_image_paths[i].c_str());
655 CDIF *image = CDIF_Open(disk_image_paths[i].c_str(), image_memcache);
656 CDInterfaces.push_back(image);
657
658 extract_basename(
659 image_label, disk_image_paths[i].c_str(), sizeof(image_label));
660 disk_image_labels.push_back(image_label);
661 }
662 }
663 else
664 {
665 // single disc
666 char image_label[4096];
667 bool success = true;
668
669 image_label[0] = '\0';
670
671 disk_image_paths.push_back(content_name);
672 CDIF *image = CDIF_Open(content_name, image_memcache);
673 CDInterfaces.push_back(image);
674
675 extract_basename(
676 image_label, content_name, sizeof(image_label));
677 disk_image_labels.push_back(image_label);
678 }
679
680 /* Attempt to set initial disk index */
681 if ((g_initial_disc > 0) &&
682 (g_initial_disc < CDInterfaces.size()))
683 if (g_initial_disc < disk_image_paths.size())
684 if (string_is_equal(
685 disk_image_paths[g_initial_disc].c_str(),
686 g_initial_disc_path.c_str()))
687 g_current_disc = (int)g_initial_disc;
688 }
689 }
690 catch( std::exception &e )
691 {
692 log_cb(RETRO_LOG_ERROR, "Loading Failed.\n");
693 return false;
694 }
695
696 #ifdef DEBUG
697 // Print out a track list for all discs.
698 for(unsigned i = 0; i < CDInterfaces.size(); i++)
699 {
700 TOC toc;
701 CDInterfaces[i]->ReadTOC(&toc);
702 log_cb(RETRO_LOG_DEBUG, "Disc %d\n", i + 1);
703 for(int32 track = toc.first_track; track <= toc.last_track; track++) {
704 log_cb(RETRO_LOG_DEBUG, "- Track %2d, LBA: %6d %s\n", track, toc.tracks[track].lba, (toc.tracks[track].control & 0x4) ? "DATA" : "AUDIO");
705 }
706 log_cb(RETRO_LOG_DEBUG, "Leadout: %6d\n", toc.tracks[100].lba);
707 }
708 #endif
709
710 log_cb(RETRO_LOG_DEBUG, "Calculating layout MD5.\n");
711 // Calculate layout MD5. The system emulation LoadCD() code is free to ignore this value and calculate
712 // its own, or to use it to look up a game in its database.
713 {
714 md5_context layout_md5;
715 layout_md5.starts();
716
717 for( unsigned i = 0; i < CDInterfaces.size(); i++ )
718 {
719 TOC toc;
720
721 CDInterfaces[i]->ReadTOC(&toc);
722
723 layout_md5.update_u32_as_lsb(toc.first_track);
724 layout_md5.update_u32_as_lsb(toc.last_track);
725 layout_md5.update_u32_as_lsb(toc.tracks[100].lba);
726
727 for(uint32 track = toc.first_track; track <= toc.last_track; track++)
728 {
729 layout_md5.update_u32_as_lsb(toc.tracks[track].lba);
730 layout_md5.update_u32_as_lsb(toc.tracks[track].control & 0x4);
731 }
732 }
733
734 layout_md5.finish(LayoutMD5);
735 }
736 log_cb(RETRO_LOG_DEBUG, "Done calculating layout MD5.\n");
737 // TODO: include module name in hash
738
739 memcpy( game_interface->MD5, LayoutMD5, 16 );
740
741 disc_calcgameid( game_interface->MD5, fd_id, sgid );
742
743 return true;
744 }
745
746 //==============================================================================
747