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