1 // FBAlpha cd-img, TruRip .ccd/.sub/.img support by Jan Klaassen
2 // .bin/.cue re-work by dink
3 
4 #include "burner.h"
5 
6 const int MAXIMUM_NUMBER_TRACKS = 100;
7 
8 const int CD_FRAMES_MINUTE = 60 * 75;
9 const int CD_FRAMES_SECOND =      75;
10 
11 const int CD_TYPE_NONE     = 1 << 0;
12 const int CD_TYPE_BINCUE   = 1 << 1;
13 const int CD_TYPE_CCD      = 1 << 2;
14 
15 static int cd_pregap;
16 
17 struct MSF { UINT8 M; UINT8 S; UINT8 F; };
18 
19 struct cdimgTRACK_DATA { UINT8 Control; UINT8 TrackNumber; UINT8 Address[4]; UINT8 EndAddress[4]; };
20 struct cdimgCDROM_TOC { UINT8 FirstTrack; UINT8 LastTrack; UINT8 ImageType; TCHAR Image[MAX_PATH]; cdimgTRACK_DATA TrackData[MAXIMUM_NUMBER_TRACKS]; };
21 
22 static cdimgCDROM_TOC* cdimgTOC;
23 
24 static FILE*  cdimgFile = NULL;
25 static int    cdimgTrack = 0;
26 static int    cdimgLBA = 0;
27 
28 static int    cdimgSamples = 0;
29 
30 static int    re_sync = 0;
31 
32 // identical to the format used in clonecd .sub files, can use memcpy
33 struct QData { UINT8 Control; char track; char index; MSF MSFrel; char unused; MSF MSFabs; unsigned short CRC; };
34 
35 static QData* QChannel = NULL;
36 
37 // -----------------------------------------------------------------------------
38 
39 const int cdimgOUT_SIZE = 2352;
40 static int  cdimgOutputbufferSize = 0;
41 
42 static short* cdimgOutputbuffer = NULL;
43 
44 static int cdimgOutputPosition;
45 
46 // -----------------------------------------------------------------------------
47 
GetIsoPath()48 TCHAR* GetIsoPath()
49 {
50 	if (cdimgTOC) {
51 		return cdimgTOC->Image;
52 	}
53 
54 	return NULL;
55 }
56 
bcd(const UINT8 v)57 static UINT8 bcd(const UINT8 v)
58 {
59 	return ((v >> 4) * 10) + (v & 0x0F);
60 }
61 
tobcd(const UINT8 v)62 static UINT8 tobcd(const UINT8 v)
63 {
64 	return ((v / 10) << 4) | (v % 10);
65 }
66 
cdimgLBAToMSF(int LBA)67 static const UINT8* cdimgLBAToMSF(int LBA)
68 {
69 	static UINT8 address[4];
70 
71 	address[0] = 0;
72 	address[1] = tobcd(LBA / CD_FRAMES_MINUTE);
73 	address[2] = tobcd(LBA % CD_FRAMES_MINUTE / CD_FRAMES_SECOND);
74 	address[3] = tobcd(LBA % CD_FRAMES_SECOND);
75 
76 	return address;
77 }
78 
cdimgMSFToLBA(const UINT8 * address)79 static int cdimgMSFToLBA(const UINT8* address)
80 {
81 	int LBA;
82 
83 	LBA  = bcd(address[3]);
84 	LBA += bcd(address[2]) * CD_FRAMES_SECOND;
85 	LBA += bcd(address[1]) * CD_FRAMES_MINUTE;
86 
87 	return LBA;
88 }
89 
dinkLBAToMSF(const int LBA)90 static const UINT8* dinkLBAToMSF(const int LBA) // not BCD version
91 {
92 	static UINT8 address[4];
93 
94 	address[0] = 0;
95 	address[1] = LBA                    / CD_FRAMES_MINUTE;
96 	address[2] = LBA % CD_FRAMES_MINUTE / CD_FRAMES_SECOND;
97 	address[3] = LBA % CD_FRAMES_SECOND;
98 
99 	return address;
100 }
101 
dinkMSFToLBA(const UINT8 * address)102 static int dinkMSFToLBA(const UINT8* address)
103 {
104 	int LBA;
105 
106 	LBA  = address[3];
107 	LBA += address[2] * CD_FRAMES_SECOND;
108 	LBA += address[1] * CD_FRAMES_MINUTE;
109 
110 	return LBA;
111 }
112 
113 // -----------------------------------------------------------------------------
114 
cdimgExitStream()115 static void cdimgExitStream()
116 {
117 	free(cdimgOutputbuffer);
118 	cdimgOutputbuffer = NULL;
119 }
120 
cdimgInitStream()121 static int cdimgInitStream()
122 {
123 	cdimgExitStream();
124 
125 	cdimgOutputbuffer = (short*)malloc(cdimgOUT_SIZE * 2 * sizeof(short));
126 	if (cdimgOutputbuffer == NULL)
127 		return 1;
128 
129 	return 0;
130 }
131 
cdimgSkip(FILE * h,int samples)132 static int cdimgSkip(FILE* h, int samples)
133 {
134 	fseek(h, samples * 4, SEEK_CUR);
135 
136 	return samples * 4;
137 }
138 
139 // -----------------------------------------------------------------------------
140 
cdimgPrintImageInfo()141 static void cdimgPrintImageInfo()
142 {
143 	bprintf(0, _T("Image file: %s\n"), cdimgTOC->Image);
144 
145 	bprintf(0, _T("   CD image TOC - "));
146 	if (cdimgTOC->ImageType == CD_TYPE_CCD)
147 		bprintf(0, _T("TruRip (.CCD/.SUB/.IMG) format\n"));
148 	if (cdimgTOC->ImageType == CD_TYPE_BINCUE)
149 		bprintf(0, _T("Disk At Once (.BIN/.CUE) format\n"));
150 
151 	for (INT32 trk = cdimgTOC->FirstTrack - 1; trk <= cdimgTOC->LastTrack; trk++) {
152 		const UINT8* addressUNBCD = dinkLBAToMSF(cdimgMSFToLBA(cdimgTOC->TrackData[trk].Address));
153 
154 		if (trk != cdimgTOC->LastTrack) {
155 			bprintf(0, _T("Track %02d: %02d:%02d:%02d\n"), trk + 1, addressUNBCD[1], addressUNBCD[2], addressUNBCD[3]);
156 		} else {
157 			bprintf(0, _T("    total running time %02i:%02i:%02i\n"), addressUNBCD[1], addressUNBCD[2], addressUNBCD[3]);
158 		}
159 	}
160 }
161 
cdimgAddLastTrack()162 static void cdimgAddLastTrack()
163 { // Make a fake last-track w/total image size (for bounds checking)
164 	FILE* h = _wfopen(cdimgTOC->Image, _T("rb"));
165 	if (h)
166 	{
167 		fseek(h, 0, SEEK_END);
168 		const UINT8* address = cdimgLBAToMSF(((ftell(h) + 2351) / 2352) + cd_pregap);
169 		fclose(h);
170 
171 		cdimgTOC->TrackData[cdimgTOC->LastTrack].Address[1] = address[1];
172 		cdimgTOC->TrackData[cdimgTOC->LastTrack].Address[2] = address[2];
173 		cdimgTOC->TrackData[cdimgTOC->LastTrack].Address[3] = address[3];
174 	}
175 }
176 
177 // parse .sub file and build a TOC based in Q sub channel data
cdimgParseSubFile()178 static int cdimgParseSubFile()
179 {
180 	TCHAR  filename_sub[MAX_PATH];
181 	int    length = 0;
182 	QData* Q = 0;
183 	int    Qsize = 0;
184 	FILE*  h;
185 	int    track = 1;
186 
187 	cdimgTOC->ImageType  = CD_TYPE_CCD;
188 	cdimgTOC->FirstTrack = 1;
189 
190 	_tcscpy(filename_sub, CDEmuImage);
191 	length = _tcslen(filename_sub);
192 
193 	if (length <= 4 ||
194 		(!IsFileExt(filename_sub, _T(".ccd")) &&
195 		 !IsFileExt(filename_sub, _T(".img")) &&
196 		 !IsFileExt(filename_sub, _T(".sub"))))
197 	{
198 		dprintf(_T("*** Bad image: %s\n"), filename_sub);
199 		return 1;
200 	}
201 
202 	_tcscpy(cdimgTOC->Image, CDEmuImage);
203 	_tcscpy(cdimgTOC->Image + length - 4, _T(".img"));
204 	//bprintf(0, _T("Image file: %s\n"),cdimgTOC->Image);
205 	if (_waccess(cdimgTOC->Image, 4) == -1)
206 	{
207 		dprintf(_T("*** Bad image: %s\n"), cdimgTOC->Image);
208 		return 1;
209 	}
210 
211 	_tcscpy(filename_sub + length - 4, _T(".sub"));
212 	//bprintf(0, _T("filename_sub: %s\n"),filename_sub);
213 	h = _wfopen(filename_sub, _T("rb"));
214 	if (h == 0)
215 	{
216 		dprintf(_T("*** Bad image: %s\n"), filename_sub);
217 		return 1;
218 	}
219 
220 	fseek(h, 0, SEEK_END);
221 
222 	INT32 subQidx = 0;
223 	INT32 subQsize = ftell(h);
224 	UINT8 *subQdata = (UINT8*)malloc(subQsize);
225 	memset(subQdata, 0, subQsize);
226 
227 	//bprintf(0, _T("raw .sub data size: %d\n"), subQsize);
228 	fseek(h, 0, SEEK_SET);
229 	fread(subQdata, subQsize, 1, h);
230 	fclose(h);
231 
232 	Qsize = (subQsize + 95) / 96 * sizeof(QData);
233 	Q = QChannel = (QData*)malloc(Qsize);
234 	memset(Q, 0, Qsize);
235 
236 	INT32 track_linear = 1;
237 
238 	while (1)
239 	{
240 		subQidx += 12;
241 		if (subQidx >= subQsize) break;
242 		memcpy(Q, &subQdata[subQidx], 12);
243 		subQidx += 12;
244 		subQidx += 6*12;
245 
246 		if (Q->index && (Q->Control & 1) && (cdimgTOC->TrackData[bcd(Q->track) - 1].TrackNumber == 0))
247 		{
248 			// new track
249 			track = bcd(Q->track);
250 
251 			if (track == track_linear) {
252 				//dprintf(_T("  - Track %i found starting at %02X:%02X:%02X\n"), track, Q->MSFabs.M, Q->MSFabs.S, Q->MSFabs.F);
253 				//bprintf(0, _T("              contrl: %X  track %X(%d)   indx %X\n"),Q->Control,Q->track,track,Q->index);
254 
255 				cdimgTOC->TrackData[track - 1].Control = Q->Control; // >> 4;
256 				cdimgTOC->TrackData[track - 1].TrackNumber = Q->track;
257 				cdimgTOC->TrackData[track - 1].Address[1] = Q->MSFabs.M;
258 				cdimgTOC->TrackData[track - 1].Address[2] = Q->MSFabs.S;
259 				cdimgTOC->TrackData[track - 1].Address[3] = Q->MSFabs.F;
260 				track_linear++;
261 			} else {
262 				//bprintf(0, _T("skipped weird track: %X (%X)\n"), track, Q->track);
263 			}
264 		}
265 
266 		Q++;
267 	}
268 
269 	cdimgTOC->LastTrack = track;
270 
271 	free(subQdata);
272 
273 	cd_pregap = QChannel[0].MSFabs.F + QChannel[0].MSFabs.S * CD_FRAMES_SECOND + QChannel[0].MSFabs.M * CD_FRAMES_MINUTE;
274 	//bprintf(0, _T("pregap lba: %d MSF: %d:%d:%d\n"), cd_pregap, QChannel[0].MSFabs.M, QChannel[0].MSFabs.S, QChannel[0].MSFabs.F);
275 
276 	cdimgAddLastTrack();
277 
278 	return 0;
279 }
280 
cdimgParseCueFile()281 static int cdimgParseCueFile()
282 {
283 	TCHAR  szLine[1024];
284 	TCHAR  szFile[1024];
285 	TCHAR* s;
286 	TCHAR* t;
287 	FILE*  h;
288 	int    track = 1;
289 	int    length;
290 
291 	cdimgTOC->ImageType  = CD_TYPE_BINCUE;
292 	cdimgTOC->FirstTrack = 1;
293 	cdimgTOC->LastTrack  = 1;
294 
295 	cdimgTOC->TrackData[0].Address[1] = 0;
296 	cdimgTOC->TrackData[0].Address[2] = 2;
297 	cdimgTOC->TrackData[0].Address[3] = 0;
298 
299 	cd_pregap = 150; // default for bin/cue?
300 
301 	// derive .bin name from .cue filename (it gets the actual name from the .cue, below)
302 	length = _tcslen(CDEmuImage);
303 	_tcscpy(cdimgTOC->Image, CDEmuImage);
304 	_tcscpy(cdimgTOC->Image + length - 4, _T(".bin"));
305 	//bprintf(0, _T("Image file: %s\n"),cdimgTOC->Image);
306 
307 	h = _tfopen(CDEmuImage, _T("rt"));
308 	if (h == NULL) {
309 		return 1;
310 	}
311 
312 	while (1) {
313 		if (_fgetts(szLine, sizeof(szLine), h) == NULL) {
314 			break;
315 		}
316 
317 		length = _tcslen(szLine);
318 		// get rid of the linefeed at the end
319 		while (length && (szLine[length - 1] == _T('\r') || szLine[length - 1] == _T('\n'))) {
320 			szLine[length - 1] = 0;
321 			length--;
322 		}
323 
324 		s = szLine;
325 
326 		// file info
327 		if ((t = LabelCheck(s, _T("FILE"))) != 0) {
328 			s = t;
329 
330 			TCHAR* szQuote;
331 
332 			// read filename
333 			QuoteRead(&szQuote, NULL, s);
334 
335 			_sntprintf(szFile, ExtractFilename(CDEmuImage) - CDEmuImage, _T("%s"), CDEmuImage);
336 			_sntprintf(szFile + (ExtractFilename(CDEmuImage) - CDEmuImage), 1024 - (ExtractFilename(CDEmuImage) - CDEmuImage), _T("\\%s"), szQuote);
337 
338 			if (track == 1) {
339 				//bprintf(0, _T("Image file (from .CUE): %s\n"), szFile);
340 				_tcscpy(cdimgTOC->Image, szFile);
341 			}
342 			continue;
343 		}
344 
345 		// track info
346 		if ((t = LabelCheck(s, _T("TRACK"))) != 0) {
347 			s = t;
348 
349 			// track number
350 			track = _tcstol(s, &t, 10);
351 
352 			if (track < 1 || track > MAXIMUM_NUMBER_TRACKS) {
353 				fclose(h);
354 				return 1;
355 			}
356 
357 			if (track < cdimgTOC->FirstTrack) {
358 				cdimgTOC->FirstTrack = track;
359 			}
360 			if (track > cdimgTOC->LastTrack) {
361 				cdimgTOC->LastTrack = track;
362 			}
363 			cdimgTOC->TrackData[track - 1].TrackNumber = tobcd(track);
364 
365 			s = t;
366 
367 			// type of track
368 
369 			if ((t = LabelCheck(s, _T("MODE1/2352"))) != 0) {
370 				cdimgTOC->TrackData[track - 1].Control = 0x41;
371 				//bprintf(0, _T(".cue: Track #%d, data.\n"), track);
372 				continue;
373 			}
374 			if ((t = LabelCheck(s, _T("AUDIO"))) != 0) {
375 				cdimgTOC->TrackData[track - 1].Control = 0x01;
376 				//bprintf(0, _T(".cue: Track #%d, AUDIO.\n"), track);
377 
378 				continue;
379 			}
380 
381 			fclose(h);
382 			return 1;
383 		}
384 
385 		// PREGAP (not handled)
386 		if ((t = LabelCheck(s, _T("PREGAP"))) != 0) {
387 			continue;
388 		}
389 
390 		// TRACK Index
391 		if ((t = LabelCheck(s, _T("INDEX 01"))) != 0) {
392 			s = t;
393 
394 			int M, S, F;
395 
396 			// index M
397 			M = _tcstol(s, &t, 10);
398 			s = t + 1;
399 			// index S
400 			S = _tcstol(s, &t, 10);
401 			s = t + 1;
402 			// index F
403 			F = _tcstol(s, &t, 10);
404 
405 			if (M < 0 || M > 100 || S < 0 || S > 59 || F < 0 || F > 74) {
406 				bprintf(0, _T("Bad M:S:F!\n"));
407 				fclose(h);
408 				return 1;
409 			}
410 
411 			const UINT8 address[] = { 0, (UINT8)M, (UINT8)S, (UINT8)F };
412 			const UINT8* newaddress = cdimgLBAToMSF(dinkMSFToLBA(address) + cd_pregap);
413 			//const UINT8* newaddressUNBCD = dinkLBAToMSF(dinkMSFToLBA(address) + cd_pregap);
414 			//bprintf(0, _T("Track MSF: %02d:%02d:%02d "), newaddressUNBCD[1], newaddressUNBCD[2], newaddressUNBCD[3]);
415 
416 			cdimgTOC->TrackData[track - 1].Address[1] = newaddress[1];
417 			cdimgTOC->TrackData[track - 1].Address[2] = newaddress[2];
418 			cdimgTOC->TrackData[track - 1].Address[3] = newaddress[3];
419 
420 			continue;
421 		}
422 	}
423 
424 	fclose(h);
425 
426 	cdimgAddLastTrack();
427 
428 	return 0;
429 }
430 
431 // -----------------------------------------------------------------------------
432 
cdimgExit()433 static int cdimgExit()
434 {
435 	cdimgExitStream();
436 
437 	if (cdimgFile)
438 		fclose(cdimgFile);
439 	cdimgFile = NULL;
440 
441 	cdimgTrack = 0;
442 	cdimgLBA = 0;
443 
444 	if (cdimgTOC)
445 		free(cdimgTOC);
446 	cdimgTOC = NULL;
447 
448 	free(QChannel);
449 	QChannel = NULL;
450 
451 	return 0;
452 }
453 
cdimgInit()454 static int cdimgInit()
455 {
456 	re_sync = 0;
457 
458 	cdimgTOC = (cdimgCDROM_TOC*)malloc(sizeof(cdimgCDROM_TOC));
459 	if (cdimgTOC == NULL)
460 		return 1;
461 
462 	memset(cdimgTOC, 0, sizeof(cdimgCDROM_TOC));
463 
464 	cdimgTOC->ImageType = CD_TYPE_NONE;
465 
466 	TCHAR* filename = ExtractFilename(CDEmuImage);
467 
468 	if (_tcslen(filename) < 4)
469 		return 1;
470 
471 	if (IsFileExt(filename, _T(".cue")))
472 	{
473 		if (cdimgParseCueFile())
474 		{
475 			dprintf(_T("*** Couldn't parse .cue file\n"));
476 			cdimgExit();
477 
478 			return 1;
479 		}
480 
481 	} else
482 	if (IsFileExt(filename, _T(".ccd")))
483 	{
484 		if (cdimgParseSubFile())
485 		{
486 			dprintf(_T("*** Couldn't parse .sub file\n"));
487 			cdimgExit();
488 
489 			return 1;
490 		}
491 
492 	}
493 	else
494 	{
495 		dprintf(_T("*** Couldn't find .img / .bin file\n"));
496 		cdimgExit();
497 
498 		return 1;
499 	}
500 
501 	cdimgPrintImageInfo();
502 
503 	CDEmuStatus = idle;
504 
505 	cdimgInitStream();
506 
507 	{
508 		char buf[2048];
509 		FILE* h = _wfopen(cdimgTOC->Image, _T("rb"));
510 
511 		cdimgLBA++;
512 
513 		if (h)
514 		{
515 			if (fseek(h, 16 * 2352 + 16, SEEK_SET) == 0)
516 			{
517 				if (fread(buf, 1, 2048, h) == 2048)
518 				{
519 					if (strncmp("CD001", buf + 1, 5) == 0)
520 					{
521 						buf[48] = 0;
522 						/* BurnDrvFindMedium(buf + 40); */
523 					}
524 					else
525 						dprintf(_T("*** Bad CD!\n"));
526 				}
527 			}
528 
529 			fclose(h);
530 		}
531 
532 		//CDEmuPrintCDName();
533 	}
534 
535 	return 0;
536 }
537 
cdimgCloseFile()538 static void cdimgCloseFile()
539 {
540 	if (cdimgFile)
541 	{
542 		fclose(cdimgFile);
543 		cdimgFile = NULL;
544 	}
545 }
546 
cdimgStop()547 static int cdimgStop()
548 {
549 	cdimgCloseFile();
550 	CDEmuStatus = idle;
551 
552 	return 0;
553 }
554 
cdimgFindTrack(int LBA)555 static int cdimgFindTrack(int LBA)
556 {
557 	int trk = 0;
558 	for (trk = cdimgTOC->FirstTrack - 1; trk < cdimgTOC->LastTrack; trk++)
559 		if (LBA < cdimgMSFToLBA(cdimgTOC->TrackData[trk + 1].Address))
560 			break;
561 	return trk;
562 }
563 
cdimgPlayLBA(int LBA)564 static int cdimgPlayLBA(int LBA) // audio play start
565 {
566 	cdimgStop();
567 
568 	if (QChannel != NULL) { // .CCD dump w/.SUB
569 		if (QChannel[LBA].Control & 0x40)
570 			return 1;
571 	} else { // .BIN/.CUE dump
572 		if (cdimgTOC->TrackData[cdimgFindTrack(LBA)].Control & 0x40)
573 			return 1;
574 	}
575 
576 	cdimgLBA = LBA;
577 
578 	cdimgTrack = cdimgFindTrack(cdimgLBA);
579 
580 	if (cdimgTrack >= cdimgTOC->LastTrack)
581 		return 1;
582 
583 	bprintf(PRINT_IMPORTANT, _T("    playing track %2i\n"), cdimgTrack + 1);
584 
585 	cdimgFile = _wfopen(cdimgTOC->Image, _T("rb"));
586 	if (cdimgFile == NULL)
587 		return 1;
588 
589 	// advance if we're not starting at the beginning of a CD
590 	if (cdimgLBA > cd_pregap)
591 		cdimgSkip(cdimgFile, (cdimgLBA - cd_pregap) * (44100 / CD_FRAMES_SECOND));
592 
593 	// fill the input buffer
594 	if ((cdimgOutputbufferSize = fread(cdimgOutputbuffer, 4, cdimgOUT_SIZE, cdimgFile)) <= 0)
595 		return 1;
596 
597 	cdimgOutputPosition = 0;
598 
599 	cdimgSamples = 0;
600 	// this breaks states, commenting for now just in-case. -dink
601 	//cdimgLBA = cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTrack].Address); // start at the beginning of track
602 	CDEmuStatus = playing;
603 
604 	return 0;
605 }
606 
cdimgPlay(UINT8 M,UINT8 S,UINT8 F)607 static int cdimgPlay(UINT8 M, UINT8 S, UINT8 F)
608 {
609 	const UINT8 address[] = { 0, M, S, F };
610 
611 	const UINT8* displayaddress = dinkLBAToMSF(cdimgMSFToLBA(address));
612 	dprintf(_T("    play %02i:%02i:%02i\n"), displayaddress[1], displayaddress[2], displayaddress[3]);
613 
614 	return cdimgPlayLBA(cdimgMSFToLBA(address));
615 }
616 
cdimgLoadSector(int LBA,char * pBuffer)617 static int cdimgLoadSector(int LBA, char* pBuffer)
618 {
619 	if (CDEmuStatus == playing) return 0; // data loading
620 
621 	if (CDEmuStatus == seeking) {
622 		LBA -= cd_pregap; // when seeking, we must account for pregap
623 		re_sync = 1;
624 	}
625 
626 	if (LBA != cdimgLBA || cdimgFile == NULL || re_sync)
627 	{
628 		re_sync = 0;
629 
630 		if (cdimgFile == NULL)
631 		{
632 			cdimgStop();
633 
634 			cdimgFile = _wfopen(cdimgTOC->Image, _T("rb"));
635 			if (cdimgFile == NULL)
636 				return 0;
637 		}
638 
639 		//bprintf(PRINT_IMPORTANT, _T("    loading data at LBA %08u 0x%08X\n"), (LBA - cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTrack].Address)) * 2352, LBA * 2352);
640 
641 		if (fseek(cdimgFile, (LBA) * 2352, SEEK_SET))
642 		{
643 			dprintf(_T("*** couldn't seek (LBA %08u)\n"), LBA);
644 
645 			//cdimgStop(); // stopping here will break ssrpg,
646 			// game will seek away & recover from this.
647 
648 			return 0;
649 		}
650 
651 		CDEmuStatus = reading;
652 	}
653 
654 	//dprintf(_T("    reading LBA %08i 0x%08X"), LBA, ftell(cdimgFile));
655 
656 	cdimgLBA = cdimgMSFToLBA(cdimgTOC->TrackData[0].Address) + (ftell(cdimgFile) + 2351) / 2352 - cd_pregap;
657 
658 	bool status = (fread(pBuffer, 1, 2352, cdimgFile) <= 0);
659 
660 	if (status)
661 	{
662 		dprintf(_T("*** couldn't read from file - iso corrupt or truncated?\n"));
663 
664 		cdimgStop();
665 
666 		return 0;
667 	}
668 	// dprintf(_T("    [ %02X %02X %02X %02X  %02X %02X %02X %02X  %02X %02X %02X %02X  %02X %02X %02X %02X ]\n"), pBuffer[0], pBuffer[1], pBuffer[2], pBuffer[3], pBuffer[4], pBuffer[5], pBuffer[6], pBuffer[7], pBuffer[8], pBuffer[9], pBuffer[10], pBuffer[11], pBuffer[12], pBuffer[13], pBuffer[14], pBuffer[15]);
669 
670 	cdimgLBA++;
671 
672 	return cdimgLBA;
673 }
674 
cdimgReadTOC(int track)675 static UINT8* cdimgReadTOC(int track)
676 {
677 	static UINT8 TOCEntry[4];
678 
679 	memset(&TOCEntry, 0, sizeof(TOCEntry));
680 
681 	if (track == CDEmuTOC_FIRSTLAST)
682 	{
683 		TOCEntry[0] = tobcd(cdimgTOC->FirstTrack - 1);
684 		TOCEntry[1] = tobcd(cdimgTOC->LastTrack);
685 		TOCEntry[2] = 0;
686 		TOCEntry[3] = 0;
687 
688 		return TOCEntry;
689 	}
690 	if (track == CDEmuTOC_LASTMSF)
691 	{
692 		TOCEntry[0] = cdimgTOC->TrackData[cdimgTOC->LastTrack].Address[1];
693 		TOCEntry[1] = cdimgTOC->TrackData[cdimgTOC->LastTrack].Address[2];
694 		TOCEntry[2] = cdimgTOC->TrackData[cdimgTOC->LastTrack].Address[3];
695 
696 		TOCEntry[3] = 0;
697 
698 		return TOCEntry;
699 	}
700 	if (track == CDEmuTOC_FIRSTINDEX)
701 	{
702 		if (cdimgLBA < cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTOC->FirstTrack].Address))
703 		{
704 			const UINT8* addressUNBCD = dinkLBAToMSF(cdimgLBA);
705 			UINT8 index = ((addressUNBCD[1] * 60) + (addressUNBCD[2] + 4)) / 4;
706 			TOCEntry[0] = tobcd((index < 100) ? index : 99);
707 		}
708 		else
709 		{
710 			TOCEntry[0] = tobcd(1);
711 		}
712 
713 		return TOCEntry;
714 	}
715 	if (track == CDEmuTOC_ENDOFDISC)
716 	{
717 		if (cdimgLBA >= cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTOC->LastTrack].Address))
718 		{
719 			bprintf(0, _T("END OF DISC: curr.lba %06d end lba: %06d\n"), cdimgLBA, cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTOC->LastTrack].Address));
720 			TOCEntry[0] = 1;
721 		}
722 
723 		return TOCEntry;
724 	}
725 
726 	track = bcd(track);
727 	if (track >= cdimgTOC->FirstTrack - 1 && track <= cdimgTOC->LastTrack)
728 	{
729 		TOCEntry[0] = cdimgTOC->TrackData[track - 1].Address[1];
730 		TOCEntry[1] = cdimgTOC->TrackData[track - 1].Address[2];
731 		TOCEntry[2] = cdimgTOC->TrackData[track - 1].Address[3];
732 		TOCEntry[3] = cdimgTOC->TrackData[track - 1].Control >> 4;
733 	}
734 
735 	// dprintf(_T("    track %02i - %02x:%02x:%02x\n"), track, TOCEntry[0], TOCEntry[1], TOCEntry[2]);
736 
737 	return TOCEntry;
738 }
739 
cdimgReadQChannel()740 static UINT8* cdimgReadQChannel()
741 {
742 	// Q channel format
743 	// byte 0: 41 = data, 1 = cdda ( flags described at https://en.wikipedia.org/wiki/Compact_Disc_subcode )
744 	// track, index, M rel, S rel, F rel, M to start, S to start, F to start, 0, CRC, CRC
745 	// if index is 0, MSF rel counts down to next track
746 
747 	static UINT8 QChannelData[8];
748 
749 	switch (CDEmuStatus)
750 	{
751 		case reading:
752 		case playing:
753 		{
754 			if (QChannel != NULL) { // .CCD/.SUB
755 				QChannelData[0] = QChannel[cdimgLBA].track;
756 
757 				QChannelData[1] = QChannel[cdimgLBA].MSFrel.M;
758 				QChannelData[2] = QChannel[cdimgLBA].MSFrel.S;
759 				QChannelData[3] = QChannel[cdimgLBA].MSFrel.F;
760 
761 				QChannelData[4] = QChannel[cdimgLBA].MSFrel.M;
762 				QChannelData[5] = QChannel[cdimgLBA].MSFrel.S;
763 				QChannelData[6] = QChannel[cdimgLBA].MSFrel.F;
764 
765 				QChannelData[7] = QChannel[cdimgLBA].Control;
766 			} else { // .BIN/.ISO
767 				const UINT8* AddressAbs = cdimgLBAToMSF(cdimgLBA);
768 				const UINT8* AddressRel = cdimgLBAToMSF(cdimgLBA - cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTrack].Address));
769 
770 				QChannelData[0] = cdimgTOC->TrackData[cdimgTrack].TrackNumber;
771 
772 				QChannelData[1] = AddressAbs[1];
773 				QChannelData[2] = AddressAbs[2];
774 				QChannelData[3] = AddressAbs[3];
775 
776 				QChannelData[4] = AddressRel[1];
777 				QChannelData[5] = AddressRel[2];
778 				QChannelData[6] = AddressRel[3];
779 
780 				QChannelData[7] = cdimgTOC->TrackData[cdimgTrack].Control;
781 			}
782 
783 			// dprintf(_T("    Q %02x %02x %02x:%02x:%02x %02x:%02x:%02x\n"), QChannel[cdimgLBA].track, QChannel[cdimgLBA].index, QChannel[cdimgLBA].MSFrel.M, QChannel[cdimgLBA].MSFrel.S, QChannel[cdimgLBA].MSFrel.F, QChannel[cdimgLBA].MSFabs.M, QChannel[cdimgLBA].MSFabs.S, QChannel[cdimgLBA].MSFabs.F);
784 
785 			break;
786 		}
787 		case paused:
788 			break;
789 
790 		default:
791 			memset(QChannelData, 0, sizeof(QChannelData));
792 	}
793 
794 	return QChannelData;
795 }
796 
cdimgGetSoundBuffer(short * buffer,int samples)797 static int cdimgGetSoundBuffer(short* buffer, int samples)
798 {
799 
800 #define CLIP(A) ((A) < -0x8000 ? -0x8000 : (A) > 0x7fff ? 0x7fff : (A))
801 
802 	if (CDEmuStatus != playing) {
803 		memset(cdimgOutputbuffer, 0x00, cdimgOUT_SIZE * 2 * sizeof(short));
804 		return 0;
805 	}
806 
807 	cdimgSamples += samples;
808 	while (cdimgSamples > (44100 / CD_FRAMES_SECOND))
809 	{
810 		cdimgSamples -= (44100 / CD_FRAMES_SECOND);
811 		cdimgLBA++;
812 
813 /*		if (cdimgFile == NULL) // play next track?  bad idea. -dink
814 			if (cdimgLBA >= cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTrack + 1].Address))
815 				cdimgPlayLBA(cdimgLBA); */
816 	}
817 
818 #if 0
819 	extern int counter;
820 	if (counter) {
821 		const UINT8* displayaddress = dinkLBAToMSF(cdimgLBA);
822 		dprintf(_T("  index  %02i:%02i:%02i"), displayaddress[1], displayaddress[2], displayaddress[3]);
823 		INT32 endt = cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTrack + 1 /* next track */].Address);
824 		const UINT8* displayaddressend = dinkLBAToMSF(endt);
825 		dprintf(_T("    end  %02i:%02i:%02i\n"), displayaddressend[1], displayaddressend[2], displayaddressend[3]);
826 	}
827 #endif
828 
829 	if (cdimgFile == NULL) { // restart play if fileptr lost
830 		bprintf(0, _T("CDDA file pointer lost, re-starting @ %d!\n"), cdimgLBA);
831 		if (cdimgLBA < cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTrack + 1].Address))
832 			cdimgPlayLBA(cdimgLBA);
833 	}
834 
835 	if (cdimgFile == NULL) { // restart failed (really?) - time to give up.
836 		cdimgStop();
837 		return 0;
838 	}
839 
840 	if (cdimgLBA >= cdimgMSFToLBA(cdimgTOC->TrackData[cdimgTrack + 1 /* next track */].Address)) {
841 		bprintf(0, _T("End of audio track %d reached!! stopping.\n"), cdimgTrack + 1);
842 		cdimgStop();
843 		return 0;
844 	}
845 
846 	if ((cdimgOutputPosition + samples) >= cdimgOutputbufferSize)
847 	{
848 		short* src = cdimgOutputbuffer + cdimgOutputPosition * 2;
849 		short* dst = buffer;
850 
851 		for (int i = (cdimgOutputbufferSize - cdimgOutputPosition) * 2 - 1; i > 0; )
852 		{
853 			dst[i] = CLIP((src[i]) + dst[i]); i--;
854 			dst[i] = CLIP((src[i]) + dst[i]); i--;
855 		}
856 
857 		buffer += (cdimgOutputbufferSize - cdimgOutputPosition) * 2;
858 		samples -= (cdimgOutputbufferSize - cdimgOutputPosition);
859 
860 		cdimgOutputPosition = 0;
861 		if ((cdimgOutputbufferSize = fread(cdimgOutputbuffer, 4, cdimgOUT_SIZE, cdimgFile)) <= 0)
862 			cdimgStop();
863 	}
864 
865 	if ((cdimgOutputPosition + samples) < cdimgOutputbufferSize)
866 	{
867 		short* src = cdimgOutputbuffer + cdimgOutputPosition * 2;
868 		short* dst = buffer;
869 
870 		for (int i = samples * 2 - 1; i > 0; )
871 		{
872 			dst[i] = CLIP((src[i]) + dst[i]); i--;
873 			dst[i] = CLIP((src[i]) + dst[i]); i--;
874 		}
875 
876 		cdimgOutputPosition += samples;
877 	}
878 
879 	return 0;
880 
881 #undef CLIP
882 
883 }
884 
cdimgScan(INT32 nAction,INT32 * pnMin)885 static INT32 cdimgScan(INT32 nAction, INT32 *pnMin)
886 {
887 	if (nAction & ACB_VOLATILE) {
888 		SCAN_VAR(CDEmuStatus);
889 		SCAN_VAR(cdimgTrack);
890 		SCAN_VAR(cdimgLBA);
891 
892 		SCAN_VAR(cdimgOutputPosition);
893 		SCAN_VAR(cdimgSamples);
894 		SCAN_VAR(cdimgOutputbufferSize);
895 	}
896 
897 	if (nAction & ACB_WRITE && nAction & ACB_RUNAHEAD) { // run-ahead system state load
898 		re_sync = 1;
899 	}
900 
901 	if (nAction & ACB_WRITE && ~nAction & ACB_RUNAHEAD) { // regular state load, close file - it will recover
902 		cdimgCloseFile();
903 	}
904 
905 	return 0;
906 }
907 
cdimgGetSettings(InterfaceInfo * pInfo)908 static int cdimgGetSettings(InterfaceInfo* pInfo)
909 {
910 	return 0;
911 }
912 
913 struct CDEmuDo cdimgDo = { cdimgExit, cdimgInit, cdimgStop, cdimgPlay, cdimgLoadSector, cdimgReadTOC, cdimgReadQChannel, cdimgGetSoundBuffer, cdimgScan, cdimgGetSettings, _T("raw image CD emulation") };
914