1 /***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18 ***************************************************************************/
21 * Handles all CD-ROM registers and functions.
27 #include "arm_features.h"
31 #define CDR_LOG SysPrintf
36 #define CDR_LOG_I SysPrintf
38 #define CDR_LOG_I log_unhandled
41 #define CDR_LOG_IO SysPrintf
43 #define CDR_LOG_IO(...)
45 //#define CDR_LOG_CMD_IRQ
48 // unused members maintain savesate compatibility
49 unsigned char unused0;
50 unsigned char unused1;
52 unsigned char unused2;
58 unsigned char Transfer[DATA_SIZE];
62 unsigned char Relative[3];
63 unsigned char Absolute[3];
65 unsigned char TrackChanged;
66 unsigned char unused3[3];
67 unsigned int freeze_ver;
69 unsigned char Prev[4];
70 unsigned char Param[8];
71 unsigned char Result[16];
75 unsigned char ResultC;
76 unsigned char ResultP;
77 unsigned char ResultReady;
79 unsigned char unused4;
80 unsigned char SetlocPending;
83 unsigned char ResultTN[6];
84 unsigned char ResultTD[4];
85 unsigned char SetSectorPlay[4];
86 unsigned char SetSectorEnd[4];
87 unsigned char SetSector[4];
91 int Mode, File, Channel;
92 unsigned char LocL[8];
112 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
113 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
114 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
115 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
117 static s16 read_buf[CD_FRAMESIZE_RAW/2];
119 /* CD-ROM magic numbers */
120 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
125 #define CdlBackward 5
133 #define CdlSetfilter 13
134 #define CdlSetmode 14
135 #define CdlGetparam 15
136 #define CdlGetlocL 16
137 #define CdlGetlocP 17
143 #define CdlSetclock 23
144 #define CdlGetclock 24
150 #define CdlReadToc 30
152 #ifdef CDR_LOG_CMD_IRQ
153 static const char * const CmdName[0x100] = {
154 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
155 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
156 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
157 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
158 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
159 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
160 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
161 "CdlInit", NULL, "CDlReadToc", NULL
165 unsigned char Test04[] = { 0 };
166 unsigned char Test05[] = { 0 };
167 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
168 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
169 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
175 #define Acknowledge 3
180 #define MODE_SPEED (1<<7) // 0x80
181 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
182 #define MODE_SIZE_2340 (1<<5) // 0x20
183 #define MODE_SIZE_2328 (1<<4) // 0x10
184 #define MODE_SIZE_2048 (0<<4) // 0x00
185 #define MODE_SF (1<<3) // 0x08 channel on/off
186 #define MODE_REPORT (1<<2) // 0x04
187 #define MODE_AUTOPAUSE (1<<1) // 0x02
188 #define MODE_CDDA (1<<0) // 0x01
191 #define STATUS_PLAY (1<<7) // 0x80
192 #define STATUS_SEEK (1<<6) // 0x40
193 #define STATUS_READ (1<<5) // 0x20
194 #define STATUS_SHELLOPEN (1<<4) // 0x10
195 #define STATUS_UNKNOWN3 (1<<3) // 0x08
196 #define STATUS_UNKNOWN2 (1<<2) // 0x04
197 #define STATUS_ROTATING (1<<1) // 0x02
198 #define STATUS_ERROR (1<<0) // 0x01
201 #define ERROR_NOTREADY (1<<7) // 0x80
202 #define ERROR_INVALIDCMD (1<<6) // 0x40
203 #define ERROR_INVALIDARG (1<<5) // 0x20
205 // 1x = 75 sectors per second
206 // PSXCLK = 1 sec in the ps
207 // so (PSXCLK / 75) = cdr read time (linuzappz)
208 #define cdReadTime (PSXCLK / 75)
210 #define LOCL_INVALID 0xff
213 DRIVESTATE_STANDBY = 0, // pause, play, read
215 DRIVESTATE_RESCAN_CD,
216 DRIVESTATE_PREPARE_CD,
220 static struct CdrStat stat;
222 static unsigned int msf2sec(const u8 *msf) {
223 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
226 // for that weird psemu API..
227 static unsigned int fsm2sec(const u8 *msf) {
228 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
231 static void sec2msf(unsigned int s, u8 *msf) {
232 msf[0] = s / 75 / 60;
233 s = s - msf[0] * 75 * 60;
240 #define CDR_INT(eCycle) { \
241 psxRegs.interrupt |= (1 << PSXINT_CDR); \
242 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
243 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
244 new_dyna_set_event(PSXINT_CDR, eCycle); \
247 // cdrPlayReadInterrupt
248 #define CDRPLAYREAD_INT(eCycle, isFirst) { \
250 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
252 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
254 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
255 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
256 new_dyna_set_event_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
259 // cdrLidSeekInterrupt
260 #define CDRLID_INT(eCycle) { \
261 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
262 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
263 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
264 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
267 #define StopReading() { \
269 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
272 #define StopCdda() { \
273 if (cdr.Play && !Config.Cdda) CDR_stop(); \
275 cdr.FastForward = 0; \
276 cdr.FastBackward = 0; \
279 #define SetPlaySeekRead(x, f) { \
280 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
284 #define SetResultSize(size) { \
286 cdr.ResultC = size; \
287 cdr.ResultReady = 1; \
290 static void setIrq(int log_cmd)
292 if (cdr.Stat & cdr.Reg2)
293 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
295 #ifdef CDR_LOG_CMD_IRQ
299 SysPrintf("%u cdrom: CDR IRQ=%d cmd %02x stat %02x: ",
300 psxRegs.cycle, !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
301 for (i = 0; i < cdr.ResultC; i++)
302 SysPrintf("%02x ", cdr.Result[i]);
308 // timing used in this function was taken from tests on real hardware
309 // (yes it's slow, but you probably don't want to modify it)
310 void cdrLidSeekInterrupt(void)
312 CDR_LOG_I("%u %s cdr.DriveState=%d\n", psxRegs.cycle, __func__, cdr.DriveState);
314 switch (cdr.DriveState) {
316 case DRIVESTATE_STANDBY:
319 SetPlaySeekRead(cdr.StatP, 0);
321 if (CDR_getStatus(&stat) == -1)
324 if (stat.Status & STATUS_SHELLOPEN)
326 cdr.DriveState = DRIVESTATE_LID_OPEN;
331 case DRIVESTATE_LID_OPEN:
332 if (CDR_getStatus(&stat) == -1)
333 stat.Status &= ~STATUS_SHELLOPEN;
336 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
337 cdr.StatP |= STATUS_SHELLOPEN;
339 // could generate error irq here, but real hardware
340 // only sometimes does that
341 // (not done when lots of commands are sent?)
343 CDRLID_INT(cdReadTime * 30);
346 else if (cdr.StatP & STATUS_ROTATING) {
347 cdr.StatP &= ~STATUS_ROTATING;
349 else if (!(stat.Status & STATUS_SHELLOPEN)) {
353 // cdr.StatP STATUS_SHELLOPEN is "sticky"
354 // and is only cleared by CdlNop
356 cdr.DriveState = DRIVESTATE_RESCAN_CD;
357 CDRLID_INT(cdReadTime * 105);
362 CDRLID_INT(cdReadTime * 3);
365 case DRIVESTATE_RESCAN_CD:
366 cdr.StatP |= STATUS_ROTATING;
367 cdr.DriveState = DRIVESTATE_PREPARE_CD;
369 // this is very long on real hardware, over 6 seconds
370 // make it a bit faster here...
371 CDRLID_INT(cdReadTime * 150);
374 case DRIVESTATE_PREPARE_CD:
375 if (cdr.StatP & STATUS_SEEK) {
376 SetPlaySeekRead(cdr.StatP, 0);
377 cdr.DriveState = DRIVESTATE_STANDBY;
380 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
381 CDRLID_INT(cdReadTime * 26);
387 static void Find_CurTrack(const u8 *time)
391 current = msf2sec(time);
393 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
394 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
395 sect = fsm2sec(cdr.ResultTD);
396 if (sect - current >= 150)
401 static void generate_subq(const u8 *time)
403 unsigned char start[3], next[3];
404 unsigned int this_s, start_s, next_s, pregap;
407 CDR_getTD(cdr.CurTrack, start);
408 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
410 CDR_getTD(cdr.CurTrack + 1, next);
413 // last track - cd size
415 next[0] = cdr.SetSectorEnd[2];
416 next[1] = cdr.SetSectorEnd[1];
417 next[2] = cdr.SetSectorEnd[0];
420 this_s = msf2sec(time);
421 start_s = fsm2sec(start);
422 next_s = fsm2sec(next);
424 cdr.TrackChanged = FALSE;
426 if (next_s - this_s < pregap) {
427 cdr.TrackChanged = TRUE;
434 relative_s = this_s - start_s;
435 if (relative_s < 0) {
437 relative_s = -relative_s;
439 sec2msf(relative_s, cdr.subq.Relative);
441 cdr.subq.Track = itob(cdr.CurTrack);
442 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
443 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
444 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
445 cdr.subq.Absolute[0] = itob(time[0]);
446 cdr.subq.Absolute[1] = itob(time[1]);
447 cdr.subq.Absolute[2] = itob(time[2]);
450 static int ReadTrack(const u8 *time) {
451 unsigned char tmp[3];
456 tmp[0] = itob(time[0]);
457 tmp[1] = itob(time[1]);
458 tmp[2] = itob(time[2]);
460 if (memcmp(cdr.Prev, tmp, 3) == 0)
463 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
465 read_ok = CDR_readTrack(tmp);
467 memcpy(cdr.Prev, tmp, 3);
472 subq = (struct SubQ *)CDR_getBufferSub();
473 if (subq != NULL && cdr.CurTrack == 1) {
474 crc = calcCrc((u8 *)subq + 12, 10);
475 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
476 cdr.subq.Track = subq->TrackNumber;
477 cdr.subq.Index = subq->IndexNumber;
478 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
479 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
482 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
483 tmp[0], tmp[1], tmp[2]);
490 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
491 cdr.subq.Track, cdr.subq.Index,
492 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
493 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
498 static void cdrPlayInterrupt_Autopause()
501 boolean abs_lev_chselect;
504 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
505 CDR_LOG( "CDDA STOP\n" );
507 // Magic the Gathering
508 // - looping territory cdda
511 //cdr.ResultReady = 1;
512 //cdr.Stat = DataReady;
514 setIrq(0x1000); // 0x1000 just for logging purposes
517 SetPlaySeekRead(cdr.StatP, 0);
519 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
520 cdr.Result[0] = cdr.StatP;
521 cdr.Result[1] = cdr.subq.Track;
522 cdr.Result[2] = cdr.subq.Index;
524 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
526 /* 8 is a hack. For accuracy, it should be 588. */
527 for (i = 0; i < 8; i++)
529 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
531 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
532 abs_lev_max |= abs_lev_chselect << 15;
534 if (cdr.subq.Absolute[2] & 0x10) {
535 cdr.Result[3] = cdr.subq.Relative[0];
536 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
537 cdr.Result[5] = cdr.subq.Relative[2];
540 cdr.Result[3] = cdr.subq.Absolute[0];
541 cdr.Result[4] = cdr.subq.Absolute[1];
542 cdr.Result[5] = cdr.subq.Absolute[2];
545 cdr.Result[6] = abs_lev_max >> 0;
546 cdr.Result[7] = abs_lev_max >> 8;
548 // Rayman: Logo freeze (resultready + dataready)
550 cdr.Stat = DataReady;
557 static int cdrSeekTime(unsigned char *target)
559 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
560 int seekTime = abs(diff) * (cdReadTime / 200);
563 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
564 * and was unreliable for that game.
565 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
567 * Obviously, this isn't perfect but right now, it should be a bit better.
568 * Games to test this against if you change that setting :
569 * - Driver (titlescreen music delay and retry mission)
570 * - Worms Pinball (Will either not boot or crash in the memory card screen)
571 * - Viewpoint (short pauses if the delay in the ingame music is too long)
573 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
574 * However, 1000000 is not enough for Worms Pinball to reliably boot.
576 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
577 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
581 static u32 cdrAlignTimingHack(u32 cycles)
584 * timing hack for T'ai Fu - Wrath of the Tiger:
585 * The game has a bug where it issues some cdc commands from a low priority
586 * vint handler, however there is a higher priority default bios handler
587 * that acks the vint irq and returns, so game's handler is not reached
588 * (see bios irq handler chains at e004 and the game's irq handling func
589 * at 80036810). For the game to work, vint has to arrive after the bios
590 * vint handler rejects some other irq (of which only cd and rcnt2 are
591 * active), but before the game's handler loop reads I_STAT. The time
592 * window for this is quite small (~1k cycles of so). Apparently this
593 * somehow happens naturally on the real hardware.
595 u32 vint_rel = rcnts[3].cycleStart + 63000 - psxRegs.cycle;
596 vint_rel += PSXCLK / 60;
597 while ((s32)(vint_rel - cycles) < 0)
598 vint_rel += PSXCLK / 60;
602 static void cdrUpdateTransferBuf(const u8 *buf);
603 static void cdrReadInterrupt(void);
604 static void cdrPrepCdda(s16 *buf, int samples);
605 static void cdrAttenuate(s16 *buf, int samples, int stereo);
607 void cdrPlayReadInterrupt(void)
614 if (!cdr.Play) return;
616 CDR_LOG( "CDDA - %d:%d:%d\n",
617 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
619 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
620 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
622 SetPlaySeekRead(cdr.StatP, 0);
623 cdr.TrackChanged = TRUE;
626 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
629 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
630 cdrPlayInterrupt_Autopause();
632 if (!cdr.Muted && !Config.Cdda) {
633 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
634 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
635 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
639 cdr.SetSectorPlay[2]++;
640 if (cdr.SetSectorPlay[2] == 75) {
641 cdr.SetSectorPlay[2] = 0;
642 cdr.SetSectorPlay[1]++;
643 if (cdr.SetSectorPlay[1] == 60) {
644 cdr.SetSectorPlay[1] = 0;
645 cdr.SetSectorPlay[0]++;
649 // update for CdlGetlocP/autopause
650 generate_subq(cdr.SetSectorPlay);
652 CDRPLAYREAD_INT(cdReadTime, 0);
655 #define CMD_PART2 0x100
656 #define CMD_WHILE_NOT_READY 0x200
658 void cdrInterrupt(void) {
659 int start_rotating = 0;
661 u32 cycles, seekTime = 0;
662 u32 second_resp_time = 0;
672 CDR_LOG_I("%u cdrom: cmd %02x with irqstat %x\n",
673 psxRegs.cycle, cdr.CmdInProgress, cdr.Stat);
676 if (cdr.Irq1Pending) {
677 // hand out the "newest" sector, according to nocash
678 cdrUpdateTransferBuf(CDR_getBuffer());
679 CDR_LOG_I("cdrom: %x:%02x:%02x loaded on ack\n",
680 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
682 cdr.Result[0] = cdr.Irq1Pending;
683 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
691 cdr.Result[0] = cdr.StatP;
692 cdr.Stat = Acknowledge;
694 Cmd = cdr.CmdInProgress;
695 cdr.CmdInProgress = 0;
704 switch (cdr.DriveState) {
705 case DRIVESTATE_PREPARE_CD:
707 // Syphon filter 2 expects commands to work shortly after it sees
708 // STATUS_ROTATING, so give up trying to emulate the startup seq
709 cdr.DriveState = DRIVESTATE_STANDBY;
710 cdr.StatP &= ~STATUS_SEEK;
711 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
715 case DRIVESTATE_LID_OPEN:
716 case DRIVESTATE_RESCAN_CD:
717 // no disk or busy with the initial scan, allowed cmds are limited
718 not_ready = CMD_WHILE_NOT_READY;
722 switch (Cmd | not_ready) {
724 case CdlNop + CMD_WHILE_NOT_READY:
725 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
726 cdr.StatP &= ~STATUS_SHELLOPEN;
730 case CdlSetloc + CMD_WHILE_NOT_READY:
731 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
733 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
734 if (((cdr.Param[0] & 0x0F) > 0x09) || (cdr.Param[0] > 0x99) || ((cdr.Param[1] & 0x0F) > 0x09) || (cdr.Param[1] >= 0x60) || ((cdr.Param[2] & 0x0F) > 0x09) || (cdr.Param[2] >= 0x75))
736 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
737 error = ERROR_INVALIDARG;
742 for (i = 0; i < 3; i++)
743 set_loc[i] = btoi(cdr.Param[i]);
744 memcpy(cdr.SetSector, set_loc, 3);
745 cdr.SetSector[3] = 0;
746 cdr.SetlocPending = 1;
755 cdr.FastBackward = 0;
759 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
761 if (ParamC != 0 && cdr.Param[0] != 0) {
762 int track = btoi( cdr.Param[0] );
764 if (track <= cdr.ResultTN[1])
765 cdr.CurTrack = track;
767 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
769 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
770 for (i = 0; i < 3; i++)
771 set_loc[i] = cdr.ResultTD[2 - i];
772 seekTime = cdrSeekTime(set_loc);
773 memcpy(cdr.SetSectorPlay, set_loc, 3);
776 else if (cdr.SetlocPending) {
777 seekTime = cdrSeekTime(cdr.SetSector);
778 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
781 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
782 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
784 cdr.SetlocPending = 0;
787 Rayman: detect track changes
790 Twisted Metal 2: skip PREGAP + starting accurate SubQ
791 - plays tracks without retry play
793 Wild 9: skip PREGAP + starting accurate SubQ
794 - plays tracks without retry play
796 Find_CurTrack(cdr.SetSectorPlay);
797 ReadTrack(cdr.SetSectorPlay);
798 cdr.LocL[0] = LOCL_INVALID;
799 cdr.TrackChanged = FALSE;
803 CDR_play(cdr.SetSectorPlay);
805 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
807 // BIOS player - set flag again
810 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
815 // TODO: error 80 if stopped
818 // GameShark CD Player: Calls 2x + Play 2x
820 cdr.FastBackward = 0;
826 // GameShark CD Player: Calls 2x + Play 2x
827 cdr.FastBackward = 1;
832 if (cdr.DriveState != DRIVESTATE_STOPPED) {
833 error = ERROR_INVALIDARG;
836 second_resp_time = cdReadTime * 125 / 2;
840 case CdlStandby + CMD_PART2:
846 // grab time for current track
847 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
849 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
850 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
851 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
856 SetPlaySeekRead(cdr.StatP, 0);
857 cdr.StatP &= ~STATUS_ROTATING;
858 cdr.LocL[0] = LOCL_INVALID;
860 second_resp_time = 0x800;
861 if (cdr.DriveState == DRIVESTATE_STANDBY)
862 second_resp_time = cdReadTime * 30 / 2;
864 cdr.DriveState = DRIVESTATE_STOPPED;
867 case CdlStop + CMD_PART2:
875 Gundam Battle Assault 2: much slower (*)
876 - Fixes boot, gameplay
878 Hokuto no Ken 2: slower
879 - Fixes intro + subtitles
881 InuYasha - Feudal Fairy Tale: slower
884 /* Gameblabla - Tightening the timings (as taken from Duckstation).
885 * The timings from Duckstation are based upon hardware tests.
886 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
887 * seems to be timing sensitive as it can depend on the CPU's clock speed.
889 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
891 second_resp_time = 7000;
895 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * 1000000);
897 SetPlaySeekRead(cdr.StatP, 0);
900 case CdlPause + CMD_PART2:
905 case CdlReset + CMD_WHILE_NOT_READY:
908 SetPlaySeekRead(cdr.StatP, 0);
909 cdr.LocL[0] = LOCL_INVALID;
911 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
912 second_resp_time = not_ready ? 70000 : 4100000;
916 case CdlReset + CMD_PART2:
917 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
930 cdr.File = cdr.Param[0];
931 cdr.Channel = cdr.Param[1];
935 case CdlSetmode + CMD_WHILE_NOT_READY:
936 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
937 cdr.Mode = cdr.Param[0];
941 case CdlGetparam + CMD_WHILE_NOT_READY:
942 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
944 cdr.Result[1] = cdr.Mode;
946 cdr.Result[3] = cdr.File;
947 cdr.Result[4] = cdr.Channel;
951 if (cdr.LocL[0] == LOCL_INVALID) {
956 memcpy(cdr.Result, cdr.LocL, 8);
961 memcpy(&cdr.Result, &cdr.subq, 8);
964 case CdlReadT: // SetSession?
966 second_resp_time = cdReadTime * 290 / 4;
970 case CdlReadT + CMD_PART2:
976 if (CDR_getTN(cdr.ResultTN) == -1) {
977 cdr.Stat = DiskError;
978 cdr.Result[0] |= STATUS_ERROR;
980 cdr.Stat = Acknowledge;
981 cdr.Result[1] = itob(cdr.ResultTN[0]);
982 cdr.Result[2] = itob(cdr.ResultTN[1]);
987 cdr.Track = btoi(cdr.Param[0]);
989 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
990 cdr.Stat = DiskError;
991 cdr.Result[0] |= STATUS_ERROR;
993 cdr.Stat = Acknowledge;
994 cdr.Result[0] = cdr.StatP;
995 cdr.Result[1] = itob(cdr.ResultTD[2]);
996 cdr.Result[2] = itob(cdr.ResultTD[1]);
997 /* According to Nocash's documentation, the function doesn't care about ff.
998 * This can be seen also in Mednafen's implementation. */
999 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1007 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1009 seekTime = cdrSeekTime(cdr.SetSector);
1010 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1012 Crusaders of Might and Magic = 0.5x-4x
1013 - fix cutscene speech start
1015 Eggs of Steel = 2x-?
1019 - fix cutscene speech
1024 second_resp_time = cdReadTime + seekTime;
1028 case CdlSeekL + CMD_PART2:
1029 case CdlSeekP + CMD_PART2:
1030 SetPlaySeekRead(cdr.StatP, 0);
1031 cdr.Result[0] = cdr.StatP;
1032 cdr.Stat = Complete;
1034 Find_CurTrack(cdr.SetSectorPlay);
1035 read_ok = ReadTrack(cdr.SetSectorPlay);
1036 if (read_ok && (buf = CDR_getBuffer()))
1037 memcpy(cdr.LocL, buf, 8);
1038 cdr.TrackChanged = FALSE;
1042 case CdlTest + CMD_WHILE_NOT_READY:
1043 switch (cdr.Param[0]) {
1044 case 0x20: // System Controller ROM Version
1046 memcpy(cdr.Result, Test20, 4);
1050 memcpy(cdr.Result, Test22, 4);
1052 case 0x23: case 0x24:
1054 memcpy(cdr.Result, Test23, 4);
1060 second_resp_time = 20480;
1063 case CdlID + CMD_PART2:
1065 cdr.Result[0] = cdr.StatP;
1070 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1071 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1072 cdr.Result[1] = 0xc0;
1076 cdr.Result[1] |= 0x10;
1077 if (CdromId[0] == '\0')
1078 cdr.Result[1] |= 0x80;
1080 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1082 /* This adds the string "PCSX" in Playstation bios boot screen */
1083 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1084 cdr.Stat = Complete;
1088 case CdlInit + CMD_WHILE_NOT_READY:
1091 SetPlaySeekRead(cdr.StatP, 0);
1092 // yes, it really sets STATUS_SHELLOPEN
1093 cdr.StatP |= STATUS_SHELLOPEN;
1094 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1100 case CdlGetQ + CMD_WHILE_NOT_READY:
1104 case CdlReadToc + CMD_WHILE_NOT_READY:
1105 cdr.LocL[0] = LOCL_INVALID;
1106 second_resp_time = cdReadTime * 180 / 4;
1110 case CdlReadToc + CMD_PART2:
1111 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1112 cdr.Stat = Complete;
1117 if (cdr.Reading && !cdr.SetlocPending)
1120 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1122 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1123 // Read* acts as play for cdda tracks in cdda mode
1127 if (cdr.SetlocPending) {
1128 seekTime = cdrSeekTime(cdr.SetSector);
1129 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1130 cdr.SetlocPending = 0;
1133 cdr.FirstSector = 1;
1135 // Fighting Force 2 - update subq time immediately
1137 ReadTrack(cdr.SetSectorPlay);
1138 cdr.LocL[0] = LOCL_INVALID;
1140 cycles = (cdr.Mode & 0x80) ? cdReadTime : cdReadTime * 2;
1142 cycles = cdrAlignTimingHack(cycles);
1143 CDRPLAYREAD_INT(cycles, 1);
1145 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1151 error = ERROR_INVALIDCMD;
1155 CDR_LOG_I("cdrom: cmd %02x error %02x\n", Cmd, error);
1157 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1158 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1159 cdr.Stat = DiskError;
1163 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1164 cdr.DriveState = DRIVESTATE_STANDBY;
1165 cdr.StatP |= STATUS_ROTATING;
1168 if (second_resp_time) {
1169 cdr.CmdInProgress = Cmd | 0x100;
1170 CDR_INT(second_resp_time);
1172 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1173 cdr.CmdInProgress = cdr.Cmd;
1174 CDR_LOG_I("%u cdrom: cmd %02x came before %02x finished\n",
1175 psxRegs.cycle, cdr.Cmd, Cmd);
1182 #define ssat32_to_16(v) \
1183 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1185 #define ssat32_to_16(v) do { \
1186 if (v < -32768) v = -32768; \
1187 else if (v > 32767) v = 32767; \
1191 static void cdrPrepCdda(s16 *buf, int samples)
1193 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1195 for (i = 0; i < samples; i++) {
1196 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1197 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1202 static void cdrAttenuate(s16 *buf, int samples, int stereo)
1205 int ll = cdr.AttenuatorLeftToLeft;
1206 int lr = cdr.AttenuatorLeftToRight;
1207 int rl = cdr.AttenuatorRightToLeft;
1208 int rr = cdr.AttenuatorRightToRight;
1210 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1213 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1217 for (i = 0; i < samples; i++) {
1220 l = (l * ll + r * rl) >> 7;
1221 r = (r * rr + l * lr) >> 7;
1229 for (i = 0; i < samples; i++) {
1231 l = l * (ll + rl) >> 7;
1232 //r = r * (rr + lr) >> 7;
1240 static void cdrReadInterruptSetResult(unsigned char result)
1243 CDR_LOG_I("cdrom: %d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1244 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1245 cdr.CmdInProgress, cdr.Stat);
1246 cdr.Irq1Pending = result;
1250 cdr.Result[0] = result;
1251 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1255 static void cdrUpdateTransferBuf(const u8 *buf)
1259 memcpy(cdr.Transfer, buf, DATA_SIZE);
1260 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1261 CDR_LOG("cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1262 if (cdr.FifoOffset < 2048 + 12)
1263 CDR_LOG("cdrom: FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1266 static void cdrReadInterrupt(void)
1268 u8 *buf = NULL, *hdr;
1271 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1273 read_ok = ReadTrack(cdr.SetSectorPlay);
1275 buf = CDR_getBuffer();
1280 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1281 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1284 memcpy(cdr.LocL, buf, 8);
1286 if (!cdr.Irq1Pending)
1287 cdrUpdateTransferBuf(buf);
1289 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1291 // Firemen 2: Multi-XA files - briefings, cutscenes
1292 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1294 cdr.Channel = hdr[1];
1298 * Skips playing on channel 255.
1299 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1300 * TODO : Check if this is the proper behaviour.
1302 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1303 int ret = xa_decode_sector(&cdr.Xa, buf + 4, cdr.FirstSector);
1305 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1306 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1307 cdr.FirstSector = 0;
1309 else cdr.FirstSector = -1;
1314 Croc 2: $40 - only FORM1 (*)
1315 Judge Dredd: $C8 - only FORM1 (*)
1316 Sim Theme Park - no adpcm at all (zero)
1319 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1320 cdrReadInterruptSetResult(cdr.StatP);
1322 cdr.SetSectorPlay[2]++;
1323 if (cdr.SetSectorPlay[2] == 75) {
1324 cdr.SetSectorPlay[2] = 0;
1325 cdr.SetSectorPlay[1]++;
1326 if (cdr.SetSectorPlay[1] == 60) {
1327 cdr.SetSectorPlay[1] = 0;
1328 cdr.SetSectorPlay[0]++;
1332 if (!cdr.Irq1Pending) {
1333 // update for CdlGetlocP
1334 ReadTrack(cdr.SetSectorPlay);
1337 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1346 bit 5 - 1 result ready
1348 bit 7 - 1 command being processed
1351 unsigned char cdrRead0(void) {
1352 if (cdr.ResultReady)
1357 cdr.Ctrl |= 0x40; // data fifo not empty
1359 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1362 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1364 return psxHu8(0x1800) = cdr.Ctrl;
1367 void cdrWrite0(unsigned char rt) {
1368 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1370 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1373 unsigned char cdrRead1(void) {
1374 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1375 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1379 if (cdr.ResultP == cdr.ResultC)
1380 cdr.ResultReady = 0;
1382 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1384 return psxHu8(0x1801);
1387 void cdrWrite1(unsigned char rt) {
1388 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1389 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1391 switch (cdr.Ctrl & 3) {
1395 cdr.AttenuatorRightToRightT = rt;
1401 #ifdef CDR_LOG_CMD_IRQ
1402 SysPrintf("%u cdrom: CD1 write: %x (%s)", psxRegs.cycle, rt, CmdName[rt]);
1405 SysPrintf(" Param[%d] = {", cdr.ParamC);
1406 for (i = 0; i < cdr.ParamC; i++)
1407 SysPrintf(" %x,", cdr.Param[i]);
1414 cdr.ResultReady = 0;
1417 if (!cdr.CmdInProgress) {
1418 cdr.CmdInProgress = rt;
1419 // should be something like 12k + controller delays
1423 CDR_LOG_I("%u cdrom: cmd while busy: %02x, prev %02x, busy %02x\n",
1424 psxRegs.cycle, rt, cdr.Cmd, cdr.CmdInProgress);
1425 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1426 cdr.CmdInProgress = rt;
1432 unsigned char cdrRead2(void) {
1433 unsigned char ret = cdr.Transfer[0x920];
1435 if (cdr.FifoOffset < cdr.FifoSize)
1436 ret = cdr.Transfer[cdr.FifoOffset++];
1438 CDR_LOG_I("cdrom: read empty fifo (%d)\n", cdr.FifoSize);
1440 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1444 void cdrWrite2(unsigned char rt) {
1445 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1446 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1448 switch (cdr.Ctrl & 3) {
1450 if (cdr.ParamC < 8) // FIXME: size and wrapping
1451 cdr.Param[cdr.ParamC++] = rt;
1458 cdr.AttenuatorLeftToLeftT = rt;
1461 cdr.AttenuatorRightToLeftT = rt;
1466 unsigned char cdrRead3(void) {
1468 psxHu8(0x1803) = cdr.Stat | 0xE0;
1470 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1472 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1473 return psxHu8(0x1803);
1476 void cdrWrite3(unsigned char rt) {
1477 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1478 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1480 switch (cdr.Ctrl & 3) {
1484 if (cdr.Stat & rt) {
1485 #ifdef CDR_LOG_CMD_IRQ
1486 SysPrintf("%u cdrom: ack %02x (w %02x)\n",
1487 psxRegs.cycle, cdr.Stat & rt, rt);
1489 // note: Croc vs Discworld Noir
1490 if (!(psxRegs.interrupt & (1 << PSXINT_CDR)) &&
1491 (cdr.CmdInProgress || cdr.Irq1Pending))
1492 CDR_INT(850); // 711-993
1500 cdr.AttenuatorLeftToRightT = rt;
1504 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1505 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1506 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1507 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1513 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1514 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1516 else if (rt & 0x80) {
1517 switch (cdr.Mode & 0x30) {
1518 case MODE_SIZE_2328:
1520 cdr.FifoOffset = 12;
1521 cdr.FifoSize = 2048 + 12;
1524 case MODE_SIZE_2340:
1527 cdr.FifoSize = 2340;
1531 else if (!(rt & 0xc0))
1532 cdr.FifoOffset = DATA_SIZE; // fifo empty
1535 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1540 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1542 switch (chcr & 0x71000000) {
1544 ptr = (u8 *)PSXM(madr);
1545 if (ptr == INVALID_PTR) {
1546 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1550 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1553 GS CDX: Enhancement CD crash
1556 - Spams DMA3 and gets buffer overrun
1558 size = DATA_SIZE - cdr.FifoOffset;
1563 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1564 cdr.FifoOffset += size;
1566 if (size < cdsize) {
1567 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1568 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1570 psxCpu->Clear(madr, cdsize / 4);
1572 CDRDMA_INT((cdsize/4) * 24);
1574 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1576 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1577 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1581 psxRegs.cycle += (cdsize/4) * 24 - 20;
1586 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1590 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1594 void cdrDmaInterrupt(void)
1596 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1598 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1603 static void getCdInfo(void)
1607 CDR_getTN(cdr.ResultTN);
1608 CDR_getTD(0, cdr.SetSectorEnd);
1609 tmp = cdr.SetSectorEnd[0];
1610 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1611 cdr.SetSectorEnd[2] = tmp;
1615 memset(&cdr, 0, sizeof(cdr));
1621 cdr.FifoOffset = DATA_SIZE; // fifo empty
1622 if (CdromId[0] == '\0') {
1623 cdr.DriveState = DRIVESTATE_STOPPED;
1627 cdr.DriveState = DRIVESTATE_STANDBY;
1628 cdr.StatP = STATUS_ROTATING;
1631 // BIOS player - default values
1632 cdr.AttenuatorLeftToLeft = 0x80;
1633 cdr.AttenuatorLeftToRight = 0x00;
1634 cdr.AttenuatorRightToLeft = 0x00;
1635 cdr.AttenuatorRightToRight = 0x80;
1640 int cdrFreeze(void *f, int Mode) {
1644 if (Mode == 0 && !Config.Cdda)
1647 cdr.freeze_ver = 0x63647202;
1648 gzfreeze(&cdr, sizeof(cdr));
1651 cdr.ParamP = cdr.ParamC;
1652 tmp = cdr.FifoOffset;
1655 gzfreeze(&tmp, sizeof(tmp));
1660 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1661 cdr.FifoSize = (cdr.Mode & 0x20) ? 2340 : 2048 + 12;
1663 // read right sub data
1664 tmpp[0] = btoi(cdr.Prev[0]);
1665 tmpp[1] = btoi(cdr.Prev[1]);
1666 tmpp[2] = btoi(cdr.Prev[2]);
1671 if (cdr.freeze_ver < 0x63647202)
1672 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1674 Find_CurTrack(cdr.SetSectorPlay);
1676 CDR_play(cdr.SetSectorPlay);
1677 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1678 CDRPLAYREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1681 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1682 // old versions did not latch Reg2, have to fixup..
1683 if (cdr.Reg2 == 0) {
1684 SysPrintf("cdrom: fixing up old savestate\n");
1687 // also did not save Attenuator..
1688 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1689 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1691 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1699 void LidInterrupt(void) {
1701 cdrLidSeekInterrupt();