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.
28 #include "arm_features.h"
32 #define CDR_LOG SysPrintf
37 #define CDR_LOG_I SysPrintf
39 #define CDR_LOG_I log_unhandled
42 #define CDR_LOG_IO SysPrintf
44 #define CDR_LOG_IO(...)
46 //#define CDR_LOG_CMD_IRQ
49 // unused members maintain savesate compatibility
50 unsigned char unused0;
51 unsigned char unused1;
53 unsigned char unused2;
59 unsigned char Transfer[DATA_SIZE];
63 unsigned char Relative[3];
64 unsigned char Absolute[3];
66 unsigned char TrackChanged;
67 unsigned char unused3[3];
68 unsigned int freeze_ver;
70 unsigned char Prev[4];
71 unsigned char Param[8];
72 unsigned char Result[16];
76 unsigned char ResultC;
77 unsigned char ResultP;
78 unsigned char ResultReady;
80 unsigned char SubqForwardSectors;
81 unsigned char SetlocPending;
84 unsigned char ResultTN[6];
85 unsigned char ResultTD[4];
86 unsigned char SetSectorPlay[4];
87 unsigned char SetSectorEnd[4];
88 unsigned char SetSector[4];
92 int Mode, File, Channel;
93 unsigned char LocL[8];
113 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
114 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
115 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
116 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
118 static s16 read_buf[CD_FRAMESIZE_RAW/2];
120 /* CD-ROM magic numbers */
121 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
126 #define CdlBackward 5
134 #define CdlSetfilter 13
135 #define CdlSetmode 14
136 #define CdlGetparam 15
137 #define CdlGetlocL 16
138 #define CdlGetlocP 17
144 #define CdlSetclock 23
145 #define CdlGetclock 24
151 #define CdlReadToc 30
153 #ifdef CDR_LOG_CMD_IRQ
154 static const char * const CmdName[0x100] = {
155 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
156 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
157 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
158 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
159 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
160 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
161 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
162 "CdlInit", NULL, "CDlReadToc", NULL
166 unsigned char Test04[] = { 0 };
167 unsigned char Test05[] = { 0 };
168 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
169 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
170 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
176 #define Acknowledge 3
181 #define MODE_SPEED (1<<7) // 0x80
182 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
183 #define MODE_SIZE_2340 (1<<5) // 0x20
184 #define MODE_SIZE_2328 (1<<4) // 0x10
185 #define MODE_SIZE_2048 (0<<4) // 0x00
186 #define MODE_SF (1<<3) // 0x08 channel on/off
187 #define MODE_REPORT (1<<2) // 0x04
188 #define MODE_AUTOPAUSE (1<<1) // 0x02
189 #define MODE_CDDA (1<<0) // 0x01
192 #define STATUS_PLAY (1<<7) // 0x80
193 #define STATUS_SEEK (1<<6) // 0x40
194 #define STATUS_READ (1<<5) // 0x20
195 #define STATUS_SHELLOPEN (1<<4) // 0x10
196 #define STATUS_UNKNOWN3 (1<<3) // 0x08
197 #define STATUS_UNKNOWN2 (1<<2) // 0x04
198 #define STATUS_ROTATING (1<<1) // 0x02
199 #define STATUS_ERROR (1<<0) // 0x01
202 #define ERROR_NOTREADY (1<<7) // 0x80
203 #define ERROR_INVALIDCMD (1<<6) // 0x40
204 #define ERROR_INVALIDARG (1<<5) // 0x20
206 // 1x = 75 sectors per second
207 // PSXCLK = 1 sec in the ps
208 // so (PSXCLK / 75) = cdr read time (linuzappz)
209 #define cdReadTime (PSXCLK / 75)
211 #define LOCL_INVALID 0xff
212 #define SUBQ_FORWARD_SECTORS 2u
215 DRIVESTATE_STANDBY = 0, // pause, play, read
217 DRIVESTATE_RESCAN_CD,
218 DRIVESTATE_PREPARE_CD,
222 static struct CdrStat stat;
224 static unsigned int msf2sec(const u8 *msf) {
225 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
228 // for that weird psemu API..
229 static unsigned int fsm2sec(const u8 *msf) {
230 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
233 static void sec2msf(unsigned int s, u8 *msf) {
234 msf[0] = s / 75 / 60;
235 s = s - msf[0] * 75 * 60;
242 #define CDR_INT(eCycle) { \
243 psxRegs.interrupt |= (1 << PSXINT_CDR); \
244 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
245 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
246 new_dyna_set_event(PSXINT_CDR, eCycle); \
249 // cdrPlayReadInterrupt
250 #define CDRPLAYREAD_INT(eCycle, isFirst) { \
252 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
254 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
256 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
257 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
258 new_dyna_set_event_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
261 // cdrLidSeekInterrupt
262 #define CDRLID_INT(eCycle) { \
263 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
264 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
265 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
266 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
269 #define StopReading() { \
271 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
274 #define StopCdda() { \
275 if (cdr.Play && !Config.Cdda) CDR_stop(); \
277 cdr.FastForward = 0; \
278 cdr.FastBackward = 0; \
281 #define SetPlaySeekRead(x, f) { \
282 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
286 #define SetResultSize(size) { \
288 cdr.ResultC = size; \
289 cdr.ResultReady = 1; \
292 static void setIrq(int log_cmd)
294 if (cdr.Stat & cdr.Reg2)
295 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
297 #ifdef CDR_LOG_CMD_IRQ
301 SysPrintf("%u cdrom: CDR IRQ=%d cmd %02x stat %02x: ",
302 psxRegs.cycle, !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
303 for (i = 0; i < cdr.ResultC; i++)
304 SysPrintf("%02x ", cdr.Result[i]);
310 // timing used in this function was taken from tests on real hardware
311 // (yes it's slow, but you probably don't want to modify it)
312 void cdrLidSeekInterrupt(void)
314 CDR_LOG_I("%u %s cdr.DriveState=%d\n", psxRegs.cycle, __func__, cdr.DriveState);
316 switch (cdr.DriveState) {
318 case DRIVESTATE_STANDBY:
321 SetPlaySeekRead(cdr.StatP, 0);
323 if (CDR_getStatus(&stat) == -1)
326 if (stat.Status & STATUS_SHELLOPEN)
328 cdr.DriveState = DRIVESTATE_LID_OPEN;
333 case DRIVESTATE_LID_OPEN:
334 if (CDR_getStatus(&stat) == -1)
335 stat.Status &= ~STATUS_SHELLOPEN;
338 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
339 cdr.StatP |= STATUS_SHELLOPEN;
341 // could generate error irq here, but real hardware
342 // only sometimes does that
343 // (not done when lots of commands are sent?)
345 CDRLID_INT(cdReadTime * 30);
348 else if (cdr.StatP & STATUS_ROTATING) {
349 cdr.StatP &= ~STATUS_ROTATING;
351 else if (!(stat.Status & STATUS_SHELLOPEN)) {
355 // cdr.StatP STATUS_SHELLOPEN is "sticky"
356 // and is only cleared by CdlNop
358 cdr.DriveState = DRIVESTATE_RESCAN_CD;
359 CDRLID_INT(cdReadTime * 105);
364 CDRLID_INT(cdReadTime * 3);
367 case DRIVESTATE_RESCAN_CD:
368 cdr.StatP |= STATUS_ROTATING;
369 cdr.DriveState = DRIVESTATE_PREPARE_CD;
371 // this is very long on real hardware, over 6 seconds
372 // make it a bit faster here...
373 CDRLID_INT(cdReadTime * 150);
376 case DRIVESTATE_PREPARE_CD:
377 if (cdr.StatP & STATUS_SEEK) {
378 SetPlaySeekRead(cdr.StatP, 0);
379 cdr.DriveState = DRIVESTATE_STANDBY;
382 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
383 CDRLID_INT(cdReadTime * 26);
389 static void Find_CurTrack(const u8 *time)
393 current = msf2sec(time);
395 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
396 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
397 sect = fsm2sec(cdr.ResultTD);
398 if (sect - current >= 150)
403 static void generate_subq(const u8 *time)
405 unsigned char start[3], next[3];
406 unsigned int this_s, start_s, next_s, pregap;
409 CDR_getTD(cdr.CurTrack, start);
410 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
412 CDR_getTD(cdr.CurTrack + 1, next);
415 // last track - cd size
417 next[0] = cdr.SetSectorEnd[2];
418 next[1] = cdr.SetSectorEnd[1];
419 next[2] = cdr.SetSectorEnd[0];
422 this_s = msf2sec(time);
423 start_s = fsm2sec(start);
424 next_s = fsm2sec(next);
426 cdr.TrackChanged = FALSE;
428 if (next_s - this_s < pregap) {
429 cdr.TrackChanged = TRUE;
436 relative_s = this_s - start_s;
437 if (relative_s < 0) {
439 relative_s = -relative_s;
441 sec2msf(relative_s, cdr.subq.Relative);
443 cdr.subq.Track = itob(cdr.CurTrack);
444 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
445 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
446 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
447 cdr.subq.Absolute[0] = itob(time[0]);
448 cdr.subq.Absolute[1] = itob(time[1]);
449 cdr.subq.Absolute[2] = itob(time[2]);
452 static int ReadTrack(const u8 *time)
454 unsigned char tmp[3];
457 tmp[0] = itob(time[0]);
458 tmp[1] = itob(time[1]);
459 tmp[2] = itob(time[2]);
461 if (memcmp(cdr.Prev, tmp, 3) == 0)
464 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
466 read_ok = CDR_readTrack(tmp);
468 memcpy(cdr.Prev, tmp, 3);
472 static void UpdateSubq(const u8 *time)
474 const struct SubQ *subq;
480 subq = (struct SubQ *)CDR_getBufferSub(MSF2SECT(time[0], time[1], time[2]));
481 if (subq != NULL && cdr.CurTrack == 1) {
482 crc = calcCrc((u8 *)subq + 12, 10);
483 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
484 cdr.subq.Track = subq->TrackNumber;
485 cdr.subq.Index = subq->IndexNumber;
486 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
487 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
490 CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
491 time[0], time[1], time[2]);
498 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
499 cdr.subq.Track, cdr.subq.Index,
500 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
501 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
504 static void cdrPlayInterrupt_Autopause()
507 boolean abs_lev_chselect;
510 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
511 CDR_LOG( "CDDA STOP\n" );
513 // Magic the Gathering
514 // - looping territory cdda
517 //cdr.ResultReady = 1;
518 //cdr.Stat = DataReady;
520 setIrq(0x1000); // 0x1000 just for logging purposes
523 SetPlaySeekRead(cdr.StatP, 0);
525 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
526 cdr.Result[0] = cdr.StatP;
527 cdr.Result[1] = cdr.subq.Track;
528 cdr.Result[2] = cdr.subq.Index;
530 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
532 /* 8 is a hack. For accuracy, it should be 588. */
533 for (i = 0; i < 8; i++)
535 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
537 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
538 abs_lev_max |= abs_lev_chselect << 15;
540 if (cdr.subq.Absolute[2] & 0x10) {
541 cdr.Result[3] = cdr.subq.Relative[0];
542 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
543 cdr.Result[5] = cdr.subq.Relative[2];
546 cdr.Result[3] = cdr.subq.Absolute[0];
547 cdr.Result[4] = cdr.subq.Absolute[1];
548 cdr.Result[5] = cdr.subq.Absolute[2];
551 cdr.Result[6] = abs_lev_max >> 0;
552 cdr.Result[7] = abs_lev_max >> 8;
554 // Rayman: Logo freeze (resultready + dataready)
556 cdr.Stat = DataReady;
563 static int cdrSeekTime(unsigned char *target)
565 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
566 int seekTime = abs(diff) * (cdReadTime / 200);
569 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
570 * and was unreliable for that game.
571 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
573 * Obviously, this isn't perfect but right now, it should be a bit better.
574 * Games to test this against if you change that setting :
575 * - Driver (titlescreen music delay and retry mission)
576 * - Worms Pinball (Will either not boot or crash in the memory card screen)
577 * - Viewpoint (short pauses if the delay in the ingame music is too long)
579 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
580 * However, 1000000 is not enough for Worms Pinball to reliably boot.
582 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
583 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
587 static u32 cdrAlignTimingHack(u32 cycles)
590 * timing hack for T'ai Fu - Wrath of the Tiger:
591 * The game has a bug where it issues some cdc commands from a low priority
592 * vint handler, however there is a higher priority default bios handler
593 * that acks the vint irq and returns, so game's handler is not reached
594 * (see bios irq handler chains at e004 and the game's irq handling func
595 * at 80036810). For the game to work, vint has to arrive after the bios
596 * vint handler rejects some other irq (of which only cd and rcnt2 are
597 * active), but before the game's handler loop reads I_STAT. The time
598 * window for this is quite small (~1k cycles of so). Apparently this
599 * somehow happens naturally on the real hardware.
601 u32 vint_rel = rcnts[3].cycleStart + 63000 - psxRegs.cycle;
602 vint_rel += PSXCLK / 60;
603 while ((s32)(vint_rel - cycles) < 0)
604 vint_rel += PSXCLK / 60;
608 static void cdrUpdateTransferBuf(const u8 *buf);
609 static void cdrReadInterrupt(void);
610 static void cdrPrepCdda(s16 *buf, int samples);
611 static void cdrAttenuate(s16 *buf, int samples, int stereo);
613 static void msfiAdd(u8 *msfi, u32 count)
627 void cdrPlayReadInterrupt(void)
634 if (!cdr.Play) return;
636 CDR_LOG( "CDDA - %d:%d:%d\n",
637 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
639 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
640 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
642 SetPlaySeekRead(cdr.StatP, 0);
643 cdr.TrackChanged = TRUE;
646 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
649 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
650 cdrPlayInterrupt_Autopause();
652 if (!cdr.Muted && !Config.Cdda) {
653 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
654 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
655 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
659 msfiAdd(cdr.SetSectorPlay, 1);
661 // update for CdlGetlocP/autopause
662 generate_subq(cdr.SetSectorPlay);
664 CDRPLAYREAD_INT(cdReadTime, 0);
667 #define CMD_PART2 0x100
668 #define CMD_WHILE_NOT_READY 0x200
670 void cdrInterrupt(void) {
671 int start_rotating = 0;
673 u32 cycles, seekTime = 0;
674 u32 second_resp_time = 0;
684 CDR_LOG_I("%u cdrom: cmd %02x with irqstat %x\n",
685 psxRegs.cycle, cdr.CmdInProgress, cdr.Stat);
688 if (cdr.Irq1Pending) {
689 // hand out the "newest" sector, according to nocash
690 cdrUpdateTransferBuf(CDR_getBuffer());
691 CDR_LOG_I("cdrom: %x:%02x:%02x loaded on ack\n",
692 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
694 cdr.Result[0] = cdr.Irq1Pending;
695 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
703 cdr.Result[0] = cdr.StatP;
704 cdr.Stat = Acknowledge;
706 Cmd = cdr.CmdInProgress;
707 cdr.CmdInProgress = 0;
716 switch (cdr.DriveState) {
717 case DRIVESTATE_PREPARE_CD:
719 // Syphon filter 2 expects commands to work shortly after it sees
720 // STATUS_ROTATING, so give up trying to emulate the startup seq
721 cdr.DriveState = DRIVESTATE_STANDBY;
722 cdr.StatP &= ~STATUS_SEEK;
723 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
727 case DRIVESTATE_LID_OPEN:
728 case DRIVESTATE_RESCAN_CD:
729 // no disk or busy with the initial scan, allowed cmds are limited
730 not_ready = CMD_WHILE_NOT_READY;
734 switch (Cmd | not_ready) {
736 case CdlNop + CMD_WHILE_NOT_READY:
737 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
738 cdr.StatP &= ~STATUS_SHELLOPEN;
742 case CdlSetloc + CMD_WHILE_NOT_READY:
743 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
745 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
746 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))
748 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
749 error = ERROR_INVALIDARG;
754 for (i = 0; i < 3; i++)
755 set_loc[i] = btoi(cdr.Param[i]);
756 memcpy(cdr.SetSector, set_loc, 3);
757 cdr.SetSector[3] = 0;
758 cdr.SetlocPending = 1;
767 cdr.FastBackward = 0;
771 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
773 if (ParamC != 0 && cdr.Param[0] != 0) {
774 int track = btoi( cdr.Param[0] );
776 if (track <= cdr.ResultTN[1])
777 cdr.CurTrack = track;
779 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
781 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
782 for (i = 0; i < 3; i++)
783 set_loc[i] = cdr.ResultTD[2 - i];
784 seekTime = cdrSeekTime(set_loc);
785 memcpy(cdr.SetSectorPlay, set_loc, 3);
788 else if (cdr.SetlocPending) {
789 seekTime = cdrSeekTime(cdr.SetSector);
790 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
793 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
794 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
796 cdr.SetlocPending = 0;
799 Rayman: detect track changes
802 Twisted Metal 2: skip PREGAP + starting accurate SubQ
803 - plays tracks without retry play
805 Wild 9: skip PREGAP + starting accurate SubQ
806 - plays tracks without retry play
808 Find_CurTrack(cdr.SetSectorPlay);
809 generate_subq(cdr.SetSectorPlay);
810 cdr.LocL[0] = LOCL_INVALID;
811 cdr.SubqForwardSectors = 1;
812 cdr.TrackChanged = FALSE;
816 CDR_play(cdr.SetSectorPlay);
818 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
820 // BIOS player - set flag again
823 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
828 // TODO: error 80 if stopped
831 // GameShark CD Player: Calls 2x + Play 2x
833 cdr.FastBackward = 0;
839 // GameShark CD Player: Calls 2x + Play 2x
840 cdr.FastBackward = 1;
845 if (cdr.DriveState != DRIVESTATE_STOPPED) {
846 error = ERROR_INVALIDARG;
849 second_resp_time = cdReadTime * 125 / 2;
853 case CdlStandby + CMD_PART2:
859 // grab time for current track
860 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
862 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
863 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
864 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
869 SetPlaySeekRead(cdr.StatP, 0);
870 cdr.StatP &= ~STATUS_ROTATING;
871 cdr.LocL[0] = LOCL_INVALID;
873 second_resp_time = 0x800;
874 if (cdr.DriveState == DRIVESTATE_STANDBY)
875 second_resp_time = cdReadTime * 30 / 2;
877 cdr.DriveState = DRIVESTATE_STOPPED;
880 case CdlStop + CMD_PART2:
888 Gundam Battle Assault 2: much slower (*)
889 - Fixes boot, gameplay
891 Hokuto no Ken 2: slower
892 - Fixes intro + subtitles
894 InuYasha - Feudal Fairy Tale: slower
897 /* Gameblabla - Tightening the timings (as taken from Duckstation).
898 * The timings from Duckstation are based upon hardware tests.
899 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
900 * seems to be timing sensitive as it can depend on the CPU's clock speed.
902 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
904 second_resp_time = 7000;
908 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * 1000000);
910 SetPlaySeekRead(cdr.StatP, 0);
913 case CdlPause + CMD_PART2:
918 case CdlReset + CMD_WHILE_NOT_READY:
921 SetPlaySeekRead(cdr.StatP, 0);
922 cdr.LocL[0] = LOCL_INVALID;
924 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
925 second_resp_time = not_ready ? 70000 : 4100000;
929 case CdlReset + CMD_PART2:
930 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
943 cdr.File = cdr.Param[0];
944 cdr.Channel = cdr.Param[1];
948 case CdlSetmode + CMD_WHILE_NOT_READY:
949 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
950 cdr.Mode = cdr.Param[0];
954 case CdlGetparam + CMD_WHILE_NOT_READY:
955 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
957 cdr.Result[1] = cdr.Mode;
959 cdr.Result[3] = cdr.File;
960 cdr.Result[4] = cdr.Channel;
964 if (cdr.LocL[0] == LOCL_INVALID) {
969 memcpy(cdr.Result, cdr.LocL, 8);
974 memcpy(&cdr.Result, &cdr.subq, 8);
977 case CdlReadT: // SetSession?
979 second_resp_time = cdReadTime * 290 / 4;
983 case CdlReadT + CMD_PART2:
989 if (CDR_getTN(cdr.ResultTN) == -1) {
990 cdr.Stat = DiskError;
991 cdr.Result[0] |= STATUS_ERROR;
993 cdr.Stat = Acknowledge;
994 cdr.Result[1] = itob(cdr.ResultTN[0]);
995 cdr.Result[2] = itob(cdr.ResultTN[1]);
1000 cdr.Track = btoi(cdr.Param[0]);
1002 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1003 cdr.Stat = DiskError;
1004 cdr.Result[0] |= STATUS_ERROR;
1006 cdr.Stat = Acknowledge;
1007 cdr.Result[0] = cdr.StatP;
1008 cdr.Result[1] = itob(cdr.ResultTD[2]);
1009 cdr.Result[2] = itob(cdr.ResultTD[1]);
1010 /* According to Nocash's documentation, the function doesn't care about ff.
1011 * This can be seen also in Mednafen's implementation. */
1012 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1020 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1022 seekTime = cdrSeekTime(cdr.SetSector);
1023 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1025 Crusaders of Might and Magic = 0.5x-4x
1026 - fix cutscene speech start
1028 Eggs of Steel = 2x-?
1032 - fix cutscene speech
1037 second_resp_time = cdReadTime + seekTime;
1041 case CdlSeekL + CMD_PART2:
1042 case CdlSeekP + CMD_PART2:
1043 SetPlaySeekRead(cdr.StatP, 0);
1044 cdr.Result[0] = cdr.StatP;
1045 cdr.Stat = Complete;
1047 Find_CurTrack(cdr.SetSectorPlay);
1048 read_ok = ReadTrack(cdr.SetSectorPlay);
1049 if (read_ok && (buf = CDR_getBuffer()))
1050 memcpy(cdr.LocL, buf, 8);
1051 UpdateSubq(cdr.SetSectorPlay);
1052 cdr.TrackChanged = FALSE;
1056 case CdlTest + CMD_WHILE_NOT_READY:
1057 switch (cdr.Param[0]) {
1058 case 0x20: // System Controller ROM Version
1060 memcpy(cdr.Result, Test20, 4);
1064 memcpy(cdr.Result, Test22, 4);
1066 case 0x23: case 0x24:
1068 memcpy(cdr.Result, Test23, 4);
1074 second_resp_time = 20480;
1077 case CdlID + CMD_PART2:
1079 cdr.Result[0] = cdr.StatP;
1084 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1085 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1086 cdr.Result[1] = 0xc0;
1090 cdr.Result[1] |= 0x10;
1091 if (CdromId[0] == '\0')
1092 cdr.Result[1] |= 0x80;
1094 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1096 /* This adds the string "PCSX" in Playstation bios boot screen */
1097 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1098 cdr.Stat = Complete;
1102 case CdlInit + CMD_WHILE_NOT_READY:
1105 SetPlaySeekRead(cdr.StatP, 0);
1106 // yes, it really sets STATUS_SHELLOPEN
1107 cdr.StatP |= STATUS_SHELLOPEN;
1108 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1114 case CdlGetQ + CMD_WHILE_NOT_READY:
1118 case CdlReadToc + CMD_WHILE_NOT_READY:
1119 cdr.LocL[0] = LOCL_INVALID;
1120 second_resp_time = cdReadTime * 180 / 4;
1124 case CdlReadToc + CMD_PART2:
1125 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1126 cdr.Stat = Complete;
1131 if (cdr.Reading && !cdr.SetlocPending)
1134 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1136 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1137 // Read* acts as play for cdda tracks in cdda mode
1141 if (cdr.SetlocPending) {
1142 seekTime = cdrSeekTime(cdr.SetSector);
1143 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1144 cdr.SetlocPending = 0;
1147 cdr.FirstSector = 1;
1149 // Fighting Force 2 - update subq time immediately
1151 UpdateSubq(cdr.SetSectorPlay);
1152 cdr.LocL[0] = LOCL_INVALID;
1153 cdr.SubqForwardSectors = 1;
1155 cycles = (cdr.Mode & 0x80) ? cdReadTime : cdReadTime * 2;
1157 cycles = cdrAlignTimingHack(cycles);
1158 CDRPLAYREAD_INT(cycles, 1);
1160 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1166 error = ERROR_INVALIDCMD;
1170 CDR_LOG_I("cdrom: cmd %02x error %02x\n", Cmd, error);
1172 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1173 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1174 cdr.Stat = DiskError;
1178 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1179 cdr.DriveState = DRIVESTATE_STANDBY;
1180 cdr.StatP |= STATUS_ROTATING;
1183 if (second_resp_time) {
1184 cdr.CmdInProgress = Cmd | 0x100;
1185 CDR_INT(second_resp_time);
1187 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1188 cdr.CmdInProgress = cdr.Cmd;
1189 CDR_LOG_I("%u cdrom: cmd %02x came before %02x finished\n",
1190 psxRegs.cycle, cdr.Cmd, Cmd);
1197 #define ssat32_to_16(v) \
1198 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1200 #define ssat32_to_16(v) do { \
1201 if (v < -32768) v = -32768; \
1202 else if (v > 32767) v = 32767; \
1206 static void cdrPrepCdda(s16 *buf, int samples)
1208 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1210 for (i = 0; i < samples; i++) {
1211 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1212 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1217 static void cdrAttenuate(s16 *buf, int samples, int stereo)
1220 int ll = cdr.AttenuatorLeftToLeft;
1221 int lr = cdr.AttenuatorLeftToRight;
1222 int rl = cdr.AttenuatorRightToLeft;
1223 int rr = cdr.AttenuatorRightToRight;
1225 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1228 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1232 for (i = 0; i < samples; i++) {
1235 l = (l * ll + r * rl) >> 7;
1236 r = (r * rr + l * lr) >> 7;
1244 for (i = 0; i < samples; i++) {
1246 l = l * (ll + rl) >> 7;
1247 //r = r * (rr + lr) >> 7;
1255 static void cdrReadInterruptSetResult(unsigned char result)
1258 CDR_LOG_I("cdrom: %d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1259 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1260 cdr.CmdInProgress, cdr.Stat);
1261 cdr.Irq1Pending = result;
1265 cdr.Result[0] = result;
1266 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1270 static void cdrUpdateTransferBuf(const u8 *buf)
1274 memcpy(cdr.Transfer, buf, DATA_SIZE);
1275 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1276 CDR_LOG("cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1277 if (cdr.FifoOffset < 2048 + 12)
1278 CDR_LOG("cdrom: FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1281 static void cdrReadInterrupt(void)
1283 u8 *buf = NULL, *hdr;
1287 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1289 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1290 msfiAdd(subqPos, cdr.SubqForwardSectors);
1291 UpdateSubq(subqPos);
1292 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1293 cdr.SubqForwardSectors++;
1294 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1298 read_ok = ReadTrack(cdr.SetSectorPlay);
1300 buf = CDR_getBuffer();
1305 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1306 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1309 memcpy(cdr.LocL, buf, 8);
1311 if (!cdr.Irq1Pending)
1312 cdrUpdateTransferBuf(buf);
1314 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1316 // Firemen 2: Multi-XA files - briefings, cutscenes
1317 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1319 cdr.Channel = hdr[1];
1323 * Skips playing on channel 255.
1324 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1325 * TODO : Check if this is the proper behaviour.
1327 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1328 int ret = xa_decode_sector(&cdr.Xa, buf + 4, cdr.FirstSector);
1330 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1331 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1332 cdr.FirstSector = 0;
1334 else cdr.FirstSector = -1;
1339 Croc 2: $40 - only FORM1 (*)
1340 Judge Dredd: $C8 - only FORM1 (*)
1341 Sim Theme Park - no adpcm at all (zero)
1344 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1345 cdrReadInterruptSetResult(cdr.StatP);
1347 msfiAdd(cdr.SetSectorPlay, 1);
1349 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1358 bit 5 - 1 result ready
1360 bit 7 - 1 command being processed
1363 unsigned char cdrRead0(void) {
1364 if (cdr.ResultReady)
1369 cdr.Ctrl |= 0x40; // data fifo not empty
1371 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1374 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1376 return psxHu8(0x1800) = cdr.Ctrl;
1379 void cdrWrite0(unsigned char rt) {
1380 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1382 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1385 unsigned char cdrRead1(void) {
1386 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1387 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1391 if (cdr.ResultP == cdr.ResultC)
1392 cdr.ResultReady = 0;
1394 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1396 return psxHu8(0x1801);
1399 void cdrWrite1(unsigned char rt) {
1400 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1401 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1403 switch (cdr.Ctrl & 3) {
1407 cdr.AttenuatorRightToRightT = rt;
1413 #ifdef CDR_LOG_CMD_IRQ
1414 SysPrintf("%u cdrom: CD1 write: %x (%s)", psxRegs.cycle, rt, CmdName[rt]);
1417 SysPrintf(" Param[%d] = {", cdr.ParamC);
1418 for (i = 0; i < cdr.ParamC; i++)
1419 SysPrintf(" %x,", cdr.Param[i]);
1426 cdr.ResultReady = 0;
1429 if (!cdr.CmdInProgress) {
1430 cdr.CmdInProgress = rt;
1431 // should be something like 12k + controller delays
1435 CDR_LOG_I("%u cdrom: cmd while busy: %02x, prev %02x, busy %02x\n",
1436 psxRegs.cycle, rt, cdr.Cmd, cdr.CmdInProgress);
1437 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1438 cdr.CmdInProgress = rt;
1444 unsigned char cdrRead2(void) {
1445 unsigned char ret = cdr.Transfer[0x920];
1447 if (cdr.FifoOffset < cdr.FifoSize)
1448 ret = cdr.Transfer[cdr.FifoOffset++];
1450 CDR_LOG_I("cdrom: read empty fifo (%d)\n", cdr.FifoSize);
1452 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1456 void cdrWrite2(unsigned char rt) {
1457 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1458 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1460 switch (cdr.Ctrl & 3) {
1462 if (cdr.ParamC < 8) // FIXME: size and wrapping
1463 cdr.Param[cdr.ParamC++] = rt;
1470 cdr.AttenuatorLeftToLeftT = rt;
1473 cdr.AttenuatorRightToLeftT = rt;
1478 unsigned char cdrRead3(void) {
1480 psxHu8(0x1803) = cdr.Stat | 0xE0;
1482 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1484 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1485 return psxHu8(0x1803);
1488 void cdrWrite3(unsigned char rt) {
1489 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1490 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1492 switch (cdr.Ctrl & 3) {
1496 if (cdr.Stat & rt) {
1497 #ifdef CDR_LOG_CMD_IRQ
1498 SysPrintf("%u cdrom: ack %02x (w %02x)\n",
1499 psxRegs.cycle, cdr.Stat & rt, rt);
1501 // note: Croc vs Discworld Noir
1502 if (!(psxRegs.interrupt & (1 << PSXINT_CDR)) &&
1503 (cdr.CmdInProgress || cdr.Irq1Pending))
1504 CDR_INT(850); // 711-993
1512 cdr.AttenuatorLeftToRightT = rt;
1516 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1517 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1518 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1519 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1525 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1526 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1528 else if (rt & 0x80) {
1529 switch (cdr.Mode & 0x30) {
1530 case MODE_SIZE_2328:
1532 cdr.FifoOffset = 12;
1533 cdr.FifoSize = 2048 + 12;
1536 case MODE_SIZE_2340:
1539 cdr.FifoSize = 2340;
1543 else if (!(rt & 0xc0))
1544 cdr.FifoOffset = DATA_SIZE; // fifo empty
1547 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1552 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1554 switch (chcr & 0x71000000) {
1556 ptr = (u8 *)PSXM(madr);
1557 if (ptr == INVALID_PTR) {
1558 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1562 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1565 GS CDX: Enhancement CD crash
1568 - Spams DMA3 and gets buffer overrun
1570 size = DATA_SIZE - cdr.FifoOffset;
1575 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1576 cdr.FifoOffset += size;
1578 if (size < cdsize) {
1579 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1580 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1582 psxCpu->Clear(madr, cdsize / 4);
1584 CDRDMA_INT((cdsize/4) * 24);
1586 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1588 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1589 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1593 psxRegs.cycle += (cdsize/4) * 24 - 20;
1598 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1602 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1606 void cdrDmaInterrupt(void)
1608 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1610 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1615 static void getCdInfo(void)
1619 CDR_getTN(cdr.ResultTN);
1620 CDR_getTD(0, cdr.SetSectorEnd);
1621 tmp = cdr.SetSectorEnd[0];
1622 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1623 cdr.SetSectorEnd[2] = tmp;
1627 memset(&cdr, 0, sizeof(cdr));
1633 cdr.FifoOffset = DATA_SIZE; // fifo empty
1634 if (CdromId[0] == '\0') {
1635 cdr.DriveState = DRIVESTATE_STOPPED;
1639 cdr.DriveState = DRIVESTATE_STANDBY;
1640 cdr.StatP = STATUS_ROTATING;
1643 // BIOS player - default values
1644 cdr.AttenuatorLeftToLeft = 0x80;
1645 cdr.AttenuatorLeftToRight = 0x00;
1646 cdr.AttenuatorRightToLeft = 0x00;
1647 cdr.AttenuatorRightToRight = 0x80;
1652 int cdrFreeze(void *f, int Mode) {
1656 if (Mode == 0 && !Config.Cdda)
1659 cdr.freeze_ver = 0x63647202;
1660 gzfreeze(&cdr, sizeof(cdr));
1663 cdr.ParamP = cdr.ParamC;
1664 tmp = cdr.FifoOffset;
1667 gzfreeze(&tmp, sizeof(tmp));
1672 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1673 cdr.FifoSize = (cdr.Mode & 0x20) ? 2340 : 2048 + 12;
1674 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1675 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1677 // read right sub data
1678 tmpp[0] = btoi(cdr.Prev[0]);
1679 tmpp[1] = btoi(cdr.Prev[1]);
1680 tmpp[2] = btoi(cdr.Prev[2]);
1685 if (cdr.freeze_ver < 0x63647202)
1686 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1688 Find_CurTrack(cdr.SetSectorPlay);
1690 CDR_play(cdr.SetSectorPlay);
1691 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1692 CDRPLAYREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1695 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1696 // old versions did not latch Reg2, have to fixup..
1697 if (cdr.Reg2 == 0) {
1698 SysPrintf("cdrom: fixing up old savestate\n");
1701 // also did not save Attenuator..
1702 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1703 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1705 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1713 void LidInterrupt(void) {
1715 cdrLidSeekInterrupt();