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 void cdrUpdateTransferBuf(const u8 *buf);
582 static void cdrReadInterrupt(void);
583 static void cdrPrepCdda(s16 *buf, int samples);
584 static void cdrAttenuate(s16 *buf, int samples, int stereo);
586 void cdrPlayReadInterrupt(void)
593 if (!cdr.Play) return;
595 CDR_LOG( "CDDA - %d:%d:%d\n",
596 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
598 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
599 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
601 SetPlaySeekRead(cdr.StatP, 0);
602 cdr.TrackChanged = TRUE;
605 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
608 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
609 cdrPlayInterrupt_Autopause();
611 if (!cdr.Muted && !Config.Cdda) {
612 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
613 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
614 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
618 cdr.SetSectorPlay[2]++;
619 if (cdr.SetSectorPlay[2] == 75) {
620 cdr.SetSectorPlay[2] = 0;
621 cdr.SetSectorPlay[1]++;
622 if (cdr.SetSectorPlay[1] == 60) {
623 cdr.SetSectorPlay[1] = 0;
624 cdr.SetSectorPlay[0]++;
628 // update for CdlGetlocP/autopause
629 generate_subq(cdr.SetSectorPlay);
631 CDRPLAYREAD_INT(cdReadTime, 0);
634 #define CMD_PART2 0x100
635 #define CMD_WHILE_NOT_READY 0x200
637 void cdrInterrupt(void) {
638 int start_rotating = 0;
640 unsigned int seekTime = 0;
641 u32 second_resp_time = 0;
651 CDR_LOG_I("%u cdrom: cmd %02x with irqstat %x\n",
652 psxRegs.cycle, cdr.CmdInProgress, cdr.Stat);
655 if (cdr.Irq1Pending) {
656 // hand out the "newest" sector, according to nocash
657 cdrUpdateTransferBuf(CDR_getBuffer());
658 CDR_LOG_I("cdrom: %x:%02x:%02x loaded on ack\n",
659 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
661 cdr.Result[0] = cdr.Irq1Pending;
662 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
670 cdr.Result[0] = cdr.StatP;
671 cdr.Stat = Acknowledge;
673 Cmd = cdr.CmdInProgress;
674 cdr.CmdInProgress = 0;
683 switch (cdr.DriveState) {
684 case DRIVESTATE_PREPARE_CD:
686 // Syphon filter 2 expects commands to work shortly after it sees
687 // STATUS_ROTATING, so give up trying to emulate the startup seq
688 cdr.DriveState = DRIVESTATE_STANDBY;
689 cdr.StatP &= ~STATUS_SEEK;
690 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
694 case DRIVESTATE_LID_OPEN:
695 case DRIVESTATE_RESCAN_CD:
696 // no disk or busy with the initial scan, allowed cmds are limited
697 not_ready = CMD_WHILE_NOT_READY;
701 switch (Cmd | not_ready) {
703 case CdlNop + CMD_WHILE_NOT_READY:
704 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
705 cdr.StatP &= ~STATUS_SHELLOPEN;
709 case CdlSetloc + CMD_WHILE_NOT_READY:
710 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
712 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
713 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))
715 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
716 error = ERROR_INVALIDARG;
721 for (i = 0; i < 3; i++)
722 set_loc[i] = btoi(cdr.Param[i]);
723 memcpy(cdr.SetSector, set_loc, 3);
724 cdr.SetSector[3] = 0;
725 cdr.SetlocPending = 1;
734 cdr.FastBackward = 0;
738 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
740 if (ParamC != 0 && cdr.Param[0] != 0) {
741 int track = btoi( cdr.Param[0] );
743 if (track <= cdr.ResultTN[1])
744 cdr.CurTrack = track;
746 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
748 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
749 for (i = 0; i < 3; i++)
750 set_loc[i] = cdr.ResultTD[2 - i];
751 seekTime = cdrSeekTime(set_loc);
752 memcpy(cdr.SetSectorPlay, set_loc, 3);
755 else if (cdr.SetlocPending) {
756 seekTime = cdrSeekTime(cdr.SetSector);
757 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
760 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
761 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
763 cdr.SetlocPending = 0;
766 Rayman: detect track changes
769 Twisted Metal 2: skip PREGAP + starting accurate SubQ
770 - plays tracks without retry play
772 Wild 9: skip PREGAP + starting accurate SubQ
773 - plays tracks without retry play
775 Find_CurTrack(cdr.SetSectorPlay);
776 ReadTrack(cdr.SetSectorPlay);
777 cdr.LocL[0] = LOCL_INVALID;
778 cdr.TrackChanged = FALSE;
782 CDR_play(cdr.SetSectorPlay);
784 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
786 // BIOS player - set flag again
789 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
794 // TODO: error 80 if stopped
797 // GameShark CD Player: Calls 2x + Play 2x
799 cdr.FastBackward = 0;
805 // GameShark CD Player: Calls 2x + Play 2x
806 cdr.FastBackward = 1;
811 if (cdr.DriveState != DRIVESTATE_STOPPED) {
812 error = ERROR_INVALIDARG;
815 second_resp_time = cdReadTime * 125 / 2;
819 case CdlStandby + CMD_PART2:
825 // grab time for current track
826 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
828 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
829 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
830 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
835 SetPlaySeekRead(cdr.StatP, 0);
836 cdr.StatP &= ~STATUS_ROTATING;
837 cdr.LocL[0] = LOCL_INVALID;
839 second_resp_time = 0x800;
840 if (cdr.DriveState == DRIVESTATE_STANDBY)
841 second_resp_time = cdReadTime * 30 / 2;
843 cdr.DriveState = DRIVESTATE_STOPPED;
846 case CdlStop + CMD_PART2:
854 Gundam Battle Assault 2: much slower (*)
855 - Fixes boot, gameplay
857 Hokuto no Ken 2: slower
858 - Fixes intro + subtitles
860 InuYasha - Feudal Fairy Tale: slower
863 /* Gameblabla - Tightening the timings (as taken from Duckstation).
864 * The timings from Duckstation are based upon hardware tests.
865 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
866 * seems to be timing sensitive as it can depend on the CPU's clock speed.
868 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
870 second_resp_time = 7000;
874 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * 1000000);
876 SetPlaySeekRead(cdr.StatP, 0);
879 case CdlPause + CMD_PART2:
884 case CdlReset + CMD_WHILE_NOT_READY:
887 SetPlaySeekRead(cdr.StatP, 0);
888 cdr.LocL[0] = LOCL_INVALID;
890 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
891 second_resp_time = not_ready ? 70000 : 4100000;
895 case CdlReset + CMD_PART2:
896 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
909 cdr.File = cdr.Param[0];
910 cdr.Channel = cdr.Param[1];
914 case CdlSetmode + CMD_WHILE_NOT_READY:
915 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
916 cdr.Mode = cdr.Param[0];
920 case CdlGetparam + CMD_WHILE_NOT_READY:
921 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
923 cdr.Result[1] = cdr.Mode;
925 cdr.Result[3] = cdr.File;
926 cdr.Result[4] = cdr.Channel;
930 if (cdr.LocL[0] == LOCL_INVALID) {
935 memcpy(cdr.Result, cdr.LocL, 8);
940 memcpy(&cdr.Result, &cdr.subq, 8);
943 case CdlReadT: // SetSession?
945 second_resp_time = cdReadTime * 290 / 4;
949 case CdlReadT + CMD_PART2:
955 if (CDR_getTN(cdr.ResultTN) == -1) {
956 cdr.Stat = DiskError;
957 cdr.Result[0] |= STATUS_ERROR;
959 cdr.Stat = Acknowledge;
960 cdr.Result[1] = itob(cdr.ResultTN[0]);
961 cdr.Result[2] = itob(cdr.ResultTN[1]);
966 cdr.Track = btoi(cdr.Param[0]);
968 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
969 cdr.Stat = DiskError;
970 cdr.Result[0] |= STATUS_ERROR;
972 cdr.Stat = Acknowledge;
973 cdr.Result[0] = cdr.StatP;
974 cdr.Result[1] = itob(cdr.ResultTD[2]);
975 cdr.Result[2] = itob(cdr.ResultTD[1]);
976 /* According to Nocash's documentation, the function doesn't care about ff.
977 * This can be seen also in Mednafen's implementation. */
978 //cdr.Result[3] = itob(cdr.ResultTD[0]);
986 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
988 seekTime = cdrSeekTime(cdr.SetSector);
989 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
991 Crusaders of Might and Magic = 0.5x-4x
992 - fix cutscene speech start
998 - fix cutscene speech
1003 second_resp_time = cdReadTime + seekTime;
1007 case CdlSeekL + CMD_PART2:
1008 case CdlSeekP + CMD_PART2:
1009 SetPlaySeekRead(cdr.StatP, 0);
1010 cdr.Result[0] = cdr.StatP;
1011 cdr.Stat = Complete;
1013 Find_CurTrack(cdr.SetSectorPlay);
1014 read_ok = ReadTrack(cdr.SetSectorPlay);
1015 if (read_ok && (buf = CDR_getBuffer()))
1016 memcpy(cdr.LocL, buf, 8);
1017 cdr.TrackChanged = FALSE;
1021 case CdlTest + CMD_WHILE_NOT_READY:
1022 switch (cdr.Param[0]) {
1023 case 0x20: // System Controller ROM Version
1025 memcpy(cdr.Result, Test20, 4);
1029 memcpy(cdr.Result, Test22, 4);
1031 case 0x23: case 0x24:
1033 memcpy(cdr.Result, Test23, 4);
1039 second_resp_time = 20480;
1042 case CdlID + CMD_PART2:
1044 cdr.Result[0] = cdr.StatP;
1049 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1050 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1051 cdr.Result[1] = 0xc0;
1055 cdr.Result[1] |= 0x10;
1056 if (CdromId[0] == '\0')
1057 cdr.Result[1] |= 0x80;
1059 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1061 /* This adds the string "PCSX" in Playstation bios boot screen */
1062 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1063 cdr.Stat = Complete;
1067 case CdlInit + CMD_WHILE_NOT_READY:
1070 SetPlaySeekRead(cdr.StatP, 0);
1071 // yes, it really sets STATUS_SHELLOPEN
1072 cdr.StatP |= STATUS_SHELLOPEN;
1073 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1079 case CdlGetQ + CMD_WHILE_NOT_READY:
1083 case CdlReadToc + CMD_WHILE_NOT_READY:
1084 cdr.LocL[0] = LOCL_INVALID;
1085 second_resp_time = cdReadTime * 180 / 4;
1089 case CdlReadToc + CMD_PART2:
1090 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1091 cdr.Stat = Complete;
1096 if (cdr.Reading && !cdr.SetlocPending)
1099 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1101 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1102 // Read* acts as play for cdda tracks in cdda mode
1106 if (cdr.SetlocPending) {
1107 seekTime = cdrSeekTime(cdr.SetSector);
1108 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1109 cdr.SetlocPending = 0;
1112 cdr.FirstSector = 1;
1114 // Fighting Force 2 - update subq time immediately
1116 ReadTrack(cdr.SetSectorPlay);
1117 cdr.LocL[0] = LOCL_INVALID;
1119 CDRPLAYREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime, 1);
1121 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1127 error = ERROR_INVALIDCMD;
1131 CDR_LOG_I("cdrom: cmd %02x error %02x\n", Cmd, error);
1133 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1134 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1135 cdr.Stat = DiskError;
1139 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1140 cdr.DriveState = DRIVESTATE_STANDBY;
1141 cdr.StatP |= STATUS_ROTATING;
1144 if (second_resp_time) {
1145 cdr.CmdInProgress = Cmd | 0x100;
1146 CDR_INT(second_resp_time);
1148 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1149 cdr.CmdInProgress = cdr.Cmd;
1150 CDR_LOG_I("%u cdrom: cmd %02x came before %02x finished\n",
1151 psxRegs.cycle, cdr.Cmd, Cmd);
1158 #define ssat32_to_16(v) \
1159 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1161 #define ssat32_to_16(v) do { \
1162 if (v < -32768) v = -32768; \
1163 else if (v > 32767) v = 32767; \
1167 static void cdrPrepCdda(s16 *buf, int samples)
1169 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1171 for (i = 0; i < samples; i++) {
1172 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1173 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1178 static void cdrAttenuate(s16 *buf, int samples, int stereo)
1181 int ll = cdr.AttenuatorLeftToLeft;
1182 int lr = cdr.AttenuatorLeftToRight;
1183 int rl = cdr.AttenuatorRightToLeft;
1184 int rr = cdr.AttenuatorRightToRight;
1186 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1189 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1193 for (i = 0; i < samples; i++) {
1196 l = (l * ll + r * rl) >> 7;
1197 r = (r * rr + l * lr) >> 7;
1205 for (i = 0; i < samples; i++) {
1207 l = l * (ll + rl) >> 7;
1208 //r = r * (rr + lr) >> 7;
1216 static void cdrReadInterruptSetResult(unsigned char result)
1219 CDR_LOG_I("cdrom: %d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1220 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1221 cdr.CmdInProgress, cdr.Stat);
1222 cdr.Irq1Pending = result;
1226 cdr.Result[0] = result;
1227 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1231 static void cdrUpdateTransferBuf(const u8 *buf)
1235 memcpy(cdr.Transfer, buf, DATA_SIZE);
1236 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1237 CDR_LOG("cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1238 if (cdr.FifoOffset < 2048 + 12)
1239 CDR_LOG("cdrom: FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1242 static void cdrReadInterrupt(void)
1244 u8 *buf = NULL, *hdr;
1247 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1249 read_ok = ReadTrack(cdr.SetSectorPlay);
1251 buf = CDR_getBuffer();
1256 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1257 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1260 memcpy(cdr.LocL, buf, 8);
1262 if (!cdr.Irq1Pending)
1263 cdrUpdateTransferBuf(buf);
1265 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1267 // Firemen 2: Multi-XA files - briefings, cutscenes
1268 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1270 cdr.Channel = hdr[1];
1274 * Skips playing on channel 255.
1275 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1276 * TODO : Check if this is the proper behaviour.
1278 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1279 int ret = xa_decode_sector(&cdr.Xa, buf + 4, cdr.FirstSector);
1281 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1282 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1283 cdr.FirstSector = 0;
1285 else cdr.FirstSector = -1;
1290 Croc 2: $40 - only FORM1 (*)
1291 Judge Dredd: $C8 - only FORM1 (*)
1292 Sim Theme Park - no adpcm at all (zero)
1295 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1296 cdrReadInterruptSetResult(cdr.StatP);
1298 cdr.SetSectorPlay[2]++;
1299 if (cdr.SetSectorPlay[2] == 75) {
1300 cdr.SetSectorPlay[2] = 0;
1301 cdr.SetSectorPlay[1]++;
1302 if (cdr.SetSectorPlay[1] == 60) {
1303 cdr.SetSectorPlay[1] = 0;
1304 cdr.SetSectorPlay[0]++;
1308 if (!cdr.Irq1Pending) {
1309 // update for CdlGetlocP
1310 ReadTrack(cdr.SetSectorPlay);
1313 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1322 bit 5 - 1 result ready
1324 bit 7 - 1 command being processed
1327 unsigned char cdrRead0(void) {
1328 if (cdr.ResultReady)
1333 cdr.Ctrl |= 0x40; // data fifo not empty
1335 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1338 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1340 return psxHu8(0x1800) = cdr.Ctrl;
1343 void cdrWrite0(unsigned char rt) {
1344 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1346 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1349 unsigned char cdrRead1(void) {
1350 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1351 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1355 if (cdr.ResultP == cdr.ResultC)
1356 cdr.ResultReady = 0;
1358 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1360 return psxHu8(0x1801);
1363 void cdrWrite1(unsigned char rt) {
1364 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1365 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1367 switch (cdr.Ctrl & 3) {
1371 cdr.AttenuatorRightToRightT = rt;
1377 #ifdef CDR_LOG_CMD_IRQ
1378 SysPrintf("%u cdrom: CD1 write: %x (%s)", psxRegs.cycle, rt, CmdName[rt]);
1381 SysPrintf(" Param[%d] = {", cdr.ParamC);
1382 for (i = 0; i < cdr.ParamC; i++)
1383 SysPrintf(" %x,", cdr.Param[i]);
1390 cdr.ResultReady = 0;
1393 if (!cdr.CmdInProgress) {
1394 cdr.CmdInProgress = rt;
1395 // should be something like 12k + controller delays
1399 CDR_LOG_I("%u cdrom: cmd while busy: %02x, prev %02x, busy %02x\n",
1400 psxRegs.cycle, rt, cdr.Cmd, cdr.CmdInProgress);
1401 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1402 cdr.CmdInProgress = rt;
1408 unsigned char cdrRead2(void) {
1409 unsigned char ret = 0;
1411 if (cdr.FifoOffset < cdr.FifoSize)
1412 ret = cdr.Transfer[cdr.FifoOffset++];
1414 CDR_LOG_I("cdrom: read empty fifo (%d)\n", cdr.FifoSize);
1416 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1420 void cdrWrite2(unsigned char rt) {
1421 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1422 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1424 switch (cdr.Ctrl & 3) {
1426 if (cdr.ParamC < 8) // FIXME: size and wrapping
1427 cdr.Param[cdr.ParamC++] = rt;
1434 cdr.AttenuatorLeftToLeftT = rt;
1437 cdr.AttenuatorRightToLeftT = rt;
1442 unsigned char cdrRead3(void) {
1444 psxHu8(0x1803) = cdr.Stat | 0xE0;
1446 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1448 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1449 return psxHu8(0x1803);
1452 void cdrWrite3(unsigned char rt) {
1453 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1454 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1456 switch (cdr.Ctrl & 3) {
1460 if (cdr.Stat & rt) {
1461 #ifdef CDR_LOG_CMD_IRQ
1462 SysPrintf("%u cdrom: ack %02x (w %02x)\n",
1463 psxRegs.cycle, cdr.Stat & rt, rt);
1465 // note: Croc vs Discworld Noir
1466 if (!(psxRegs.interrupt & (1 << PSXINT_CDR)) &&
1467 (cdr.CmdInProgress || cdr.Irq1Pending))
1468 CDR_INT(850); // 711-993
1476 cdr.AttenuatorLeftToRightT = rt;
1480 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1481 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1482 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1483 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1489 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1490 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1492 else if (rt & 0x80) {
1493 switch (cdr.Mode & 0x30) {
1494 case MODE_SIZE_2328:
1496 cdr.FifoOffset = 12;
1497 cdr.FifoSize = 2048 + 12;
1500 case MODE_SIZE_2340:
1503 cdr.FifoSize = 2340;
1507 else if (!(rt & 0xc0))
1508 cdr.FifoOffset = DATA_SIZE; // fifo empty
1511 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1516 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1518 switch (chcr & 0x71000000) {
1520 ptr = (u8 *)PSXM(madr);
1521 if (ptr == INVALID_PTR) {
1522 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1526 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1529 GS CDX: Enhancement CD crash
1532 - Spams DMA3 and gets buffer overrun
1534 size = DATA_SIZE - cdr.FifoOffset;
1539 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1540 cdr.FifoOffset += size;
1541 psxCpu->Clear(madr, size / 4);
1544 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1546 CDRDMA_INT((cdsize/4) * 24);
1548 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1550 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1551 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1555 psxRegs.cycle += (cdsize/4) * 24 - 20;
1560 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1564 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1568 void cdrDmaInterrupt(void)
1570 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1572 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1577 static void getCdInfo(void)
1581 CDR_getTN(cdr.ResultTN);
1582 CDR_getTD(0, cdr.SetSectorEnd);
1583 tmp = cdr.SetSectorEnd[0];
1584 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1585 cdr.SetSectorEnd[2] = tmp;
1589 memset(&cdr, 0, sizeof(cdr));
1595 cdr.FifoOffset = DATA_SIZE; // fifo empty
1596 if (CdromId[0] == '\0') {
1597 cdr.DriveState = DRIVESTATE_STOPPED;
1601 cdr.DriveState = DRIVESTATE_STANDBY;
1602 cdr.StatP = STATUS_ROTATING;
1605 // BIOS player - default values
1606 cdr.AttenuatorLeftToLeft = 0x80;
1607 cdr.AttenuatorLeftToRight = 0x00;
1608 cdr.AttenuatorRightToLeft = 0x00;
1609 cdr.AttenuatorRightToRight = 0x80;
1614 int cdrFreeze(void *f, int Mode) {
1618 if (Mode == 0 && !Config.Cdda)
1621 cdr.freeze_ver = 0x63647202;
1622 gzfreeze(&cdr, sizeof(cdr));
1625 cdr.ParamP = cdr.ParamC;
1626 tmp = cdr.FifoOffset;
1629 gzfreeze(&tmp, sizeof(tmp));
1634 cdr.FifoOffset = tmp;
1635 cdr.FifoSize = (cdr.Mode & 0x20) ? 2340 : 2048 + 12;
1637 // read right sub data
1638 tmpp[0] = btoi(cdr.Prev[0]);
1639 tmpp[1] = btoi(cdr.Prev[1]);
1640 tmpp[2] = btoi(cdr.Prev[2]);
1645 if (cdr.freeze_ver < 0x63647202)
1646 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1648 Find_CurTrack(cdr.SetSectorPlay);
1650 CDR_play(cdr.SetSectorPlay);
1651 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1652 CDRPLAYREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1655 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1656 // old versions did not latch Reg2, have to fixup..
1657 if (cdr.Reg2 == 0) {
1658 SysPrintf("cdrom: fixing up old savestate\n");
1661 // also did not save Attenuator..
1662 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1663 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1665 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1673 void LidInterrupt(void) {
1675 cdrLidSeekInterrupt();