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