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(fmt, ...) \
40 log_unhandled("%u cdrom: " fmt, psxRegs.cycle, ##__VA_ARGS__)
43 #define CDR_LOG_IO SysPrintf
45 #define CDR_LOG_IO(...)
47 //#define CDR_LOG_CMD_IRQ
50 // unused members maintain savesate compatibility
51 unsigned char unused0;
52 unsigned char unused1;
54 unsigned char unused2;
60 unsigned char Transfer[DATA_SIZE];
64 unsigned char Relative[3];
65 unsigned char Absolute[3];
67 unsigned char TrackChanged;
68 unsigned char unused3[3];
69 unsigned int freeze_ver;
71 unsigned char Prev[4];
72 unsigned char Param[8];
73 unsigned char Result[16];
77 unsigned char ResultC;
78 unsigned char ResultP;
79 unsigned char ResultReady;
81 unsigned char SubqForwardSectors;
82 unsigned char SetlocPending;
85 unsigned char ResultTN[6];
86 unsigned char ResultTD[4];
87 unsigned char SetSectorPlay[4];
88 unsigned char SetSectorEnd[4];
89 unsigned char SetSector[4];
93 int Mode, File, Channel;
94 unsigned char LocL[8];
114 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
115 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
116 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
117 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
119 static s16 read_buf[CD_FRAMESIZE_RAW/2];
121 /* CD-ROM magic numbers */
122 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
127 #define CdlBackward 5
135 #define CdlSetfilter 13
136 #define CdlSetmode 14
137 #define CdlGetparam 15
138 #define CdlGetlocL 16
139 #define CdlGetlocP 17
145 #define CdlSetclock 23
146 #define CdlGetclock 24
152 #define CdlReadToc 30
154 #ifdef CDR_LOG_CMD_IRQ
155 static const char * const CmdName[0x100] = {
156 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
157 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
158 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
159 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
160 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
161 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
162 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
163 "CdlInit", NULL, "CDlReadToc", NULL
167 unsigned char Test04[] = { 0 };
168 unsigned char Test05[] = { 0 };
169 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
170 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
171 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
177 #define Acknowledge 3
182 #define MODE_SPEED (1<<7) // 0x80
183 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
184 #define MODE_SIZE_2340 (1<<5) // 0x20
185 #define MODE_SIZE_2328 (1<<4) // 0x10
186 #define MODE_SIZE_2048 (0<<4) // 0x00
187 #define MODE_SF (1<<3) // 0x08 channel on/off
188 #define MODE_REPORT (1<<2) // 0x04
189 #define MODE_AUTOPAUSE (1<<1) // 0x02
190 #define MODE_CDDA (1<<0) // 0x01
193 #define STATUS_PLAY (1<<7) // 0x80
194 #define STATUS_SEEK (1<<6) // 0x40
195 #define STATUS_READ (1<<5) // 0x20
196 #define STATUS_SHELLOPEN (1<<4) // 0x10
197 #define STATUS_UNKNOWN3 (1<<3) // 0x08
198 #define STATUS_UNKNOWN2 (1<<2) // 0x04
199 #define STATUS_ROTATING (1<<1) // 0x02
200 #define STATUS_ERROR (1<<0) // 0x01
203 #define ERROR_NOTREADY (1<<7) // 0x80
204 #define ERROR_INVALIDCMD (1<<6) // 0x40
205 #define ERROR_INVALIDARG (1<<5) // 0x20
207 // 1x = 75 sectors per second
208 // PSXCLK = 1 sec in the ps
209 // so (PSXCLK / 75) = cdr read time (linuzappz)
210 #define cdReadTime (PSXCLK / 75)
212 #define LOCL_INVALID 0xff
213 #define SUBQ_FORWARD_SECTORS 2u
216 DRIVESTATE_STANDBY = 0, // pause, play, read
218 DRIVESTATE_RESCAN_CD,
219 DRIVESTATE_PREPARE_CD,
223 static struct CdrStat stat;
225 static unsigned int msf2sec(const u8 *msf) {
226 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
229 // for that weird psemu API..
230 static unsigned int fsm2sec(const u8 *msf) {
231 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
234 static void sec2msf(unsigned int s, u8 *msf) {
235 msf[0] = s / 75 / 60;
236 s = s - msf[0] * 75 * 60;
243 #define CDR_INT(eCycle) { \
244 psxRegs.interrupt |= (1 << PSXINT_CDR); \
245 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
246 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
247 new_dyna_set_event(PSXINT_CDR, eCycle); \
250 // cdrPlayReadInterrupt
251 #define CDRPLAYREAD_INT(eCycle, isFirst) { \
253 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
255 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
257 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
258 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
259 new_dyna_set_event_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
262 // cdrLidSeekInterrupt
263 #define CDRLID_INT(eCycle) { \
264 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
265 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
266 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
267 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
270 #define StopReading() { \
272 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
275 #define StopCdda() { \
276 if (cdr.Play && !Config.Cdda) CDR_stop(); \
278 cdr.FastForward = 0; \
279 cdr.FastBackward = 0; \
282 #define SetPlaySeekRead(x, f) { \
283 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
287 #define SetResultSize(size) { \
289 cdr.ResultC = size; \
290 cdr.ResultReady = 1; \
293 static void setIrq(int log_cmd)
295 if (cdr.Stat & cdr.Reg2)
296 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
298 #ifdef CDR_LOG_CMD_IRQ
302 CDR_LOG_I("CDR IRQ=%d cmd %02x stat %02x: ",
303 !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
304 for (i = 0; i < cdr.ResultC; i++)
305 SysPrintf("%02x ", cdr.Result[i]);
311 // timing used in this function was taken from tests on real hardware
312 // (yes it's slow, but you probably don't want to modify it)
313 void cdrLidSeekInterrupt(void)
315 CDR_LOG_I("%s cdr.DriveState=%d\n", __func__, cdr.DriveState);
317 switch (cdr.DriveState) {
319 case DRIVESTATE_STANDBY:
322 SetPlaySeekRead(cdr.StatP, 0);
324 if (CDR_getStatus(&stat) == -1)
327 if (stat.Status & STATUS_SHELLOPEN)
329 memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
330 cdr.DriveState = DRIVESTATE_LID_OPEN;
335 case DRIVESTATE_LID_OPEN:
336 if (CDR_getStatus(&stat) == -1)
337 stat.Status &= ~STATUS_SHELLOPEN;
340 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
341 cdr.StatP |= STATUS_SHELLOPEN;
343 // could generate error irq here, but real hardware
344 // only sometimes does that
345 // (not done when lots of commands are sent?)
347 CDRLID_INT(cdReadTime * 30);
350 else if (cdr.StatP & STATUS_ROTATING) {
351 cdr.StatP &= ~STATUS_ROTATING;
353 else if (!(stat.Status & STATUS_SHELLOPEN)) {
357 // cdr.StatP STATUS_SHELLOPEN is "sticky"
358 // and is only cleared by CdlNop
360 cdr.DriveState = DRIVESTATE_RESCAN_CD;
361 CDRLID_INT(cdReadTime * 105);
366 CDRLID_INT(cdReadTime * 3);
369 case DRIVESTATE_RESCAN_CD:
370 cdr.StatP |= STATUS_ROTATING;
371 cdr.DriveState = DRIVESTATE_PREPARE_CD;
373 // this is very long on real hardware, over 6 seconds
374 // make it a bit faster here...
375 CDRLID_INT(cdReadTime * 150);
378 case DRIVESTATE_PREPARE_CD:
379 if (cdr.StatP & STATUS_SEEK) {
380 SetPlaySeekRead(cdr.StatP, 0);
381 cdr.DriveState = DRIVESTATE_STANDBY;
384 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
385 CDRLID_INT(cdReadTime * 26);
391 static void Find_CurTrack(const u8 *time)
395 current = msf2sec(time);
397 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
398 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
399 sect = fsm2sec(cdr.ResultTD);
400 if (sect - current >= 150)
405 static void generate_subq(const u8 *time)
407 unsigned char start[3], next[3];
408 unsigned int this_s, start_s, next_s, pregap;
411 CDR_getTD(cdr.CurTrack, start);
412 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
414 CDR_getTD(cdr.CurTrack + 1, next);
417 // last track - cd size
419 next[0] = cdr.SetSectorEnd[2];
420 next[1] = cdr.SetSectorEnd[1];
421 next[2] = cdr.SetSectorEnd[0];
424 this_s = msf2sec(time);
425 start_s = fsm2sec(start);
426 next_s = fsm2sec(next);
428 cdr.TrackChanged = FALSE;
430 if (next_s - this_s < pregap) {
431 cdr.TrackChanged = TRUE;
438 relative_s = this_s - start_s;
439 if (relative_s < 0) {
441 relative_s = -relative_s;
443 sec2msf(relative_s, cdr.subq.Relative);
445 cdr.subq.Track = itob(cdr.CurTrack);
446 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
447 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
448 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
449 cdr.subq.Absolute[0] = itob(time[0]);
450 cdr.subq.Absolute[1] = itob(time[1]);
451 cdr.subq.Absolute[2] = itob(time[2]);
454 static int ReadTrack(const u8 *time)
456 unsigned char tmp[3];
459 tmp[0] = itob(time[0]);
460 tmp[1] = itob(time[1]);
461 tmp[2] = itob(time[2]);
463 if (memcmp(cdr.Prev, tmp, 3) == 0)
466 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
468 read_ok = CDR_readTrack(tmp);
470 memcpy(cdr.Prev, tmp, 3);
474 static void UpdateSubq(const u8 *time)
476 const struct SubQ *subq;
482 subq = (struct SubQ *)CDR_getBufferSub(MSF2SECT(time[0], time[1], time[2]));
483 if (subq != NULL && cdr.CurTrack == 1) {
484 crc = calcCrc((u8 *)subq + 12, 10);
485 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
486 cdr.subq.Track = subq->TrackNumber;
487 cdr.subq.Index = subq->IndexNumber;
488 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
489 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
492 CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
493 time[0], time[1], time[2]);
500 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
501 cdr.subq.Track, cdr.subq.Index,
502 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
503 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
506 static void cdrPlayInterrupt_Autopause()
509 boolean abs_lev_chselect;
512 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
513 CDR_LOG( "CDDA STOP\n" );
515 // Magic the Gathering
516 // - looping territory cdda
519 //cdr.ResultReady = 1;
520 //cdr.Stat = DataReady;
522 setIrq(0x1000); // 0x1000 just for logging purposes
525 SetPlaySeekRead(cdr.StatP, 0);
527 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
528 cdr.Result[0] = cdr.StatP;
529 cdr.Result[1] = cdr.subq.Track;
530 cdr.Result[2] = cdr.subq.Index;
532 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
534 /* 8 is a hack. For accuracy, it should be 588. */
535 for (i = 0; i < 8; i++)
537 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
539 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
540 abs_lev_max |= abs_lev_chselect << 15;
542 if (cdr.subq.Absolute[2] & 0x10) {
543 cdr.Result[3] = cdr.subq.Relative[0];
544 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
545 cdr.Result[5] = cdr.subq.Relative[2];
548 cdr.Result[3] = cdr.subq.Absolute[0];
549 cdr.Result[4] = cdr.subq.Absolute[1];
550 cdr.Result[5] = cdr.subq.Absolute[2];
553 cdr.Result[6] = abs_lev_max >> 0;
554 cdr.Result[7] = abs_lev_max >> 8;
556 // Rayman: Logo freeze (resultready + dataready)
558 cdr.Stat = DataReady;
566 static int cdrSeekTime(unsigned char *target)
568 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
569 int pausePenalty, seekTime = abs(diff) * (cdReadTime / 2000);
570 seekTime = MAX_VALUE(seekTime, 20000);
572 // need this stupidly long penalty or else Spyro2 intro desyncs
573 pausePenalty = (s32)(psxRegs.cycle - cdr.LastReadCycles) > cdReadTime * 4 ? cdReadTime * 25 : 0;
574 seekTime += pausePenalty;
576 seekTime = MIN_VALUE(seekTime, PSXCLK * 2 / 3);
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 * Note: always enforcing this breaks other games like Crash PAL version
596 * (inputs get dropped because bios handler doesn't see interrupts).
599 if (psxRegs.cycle - rcnts[3].cycleStart > 250000)
601 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)
629 cdr.LastReadCycles = psxRegs.cycle;
636 if (!cdr.Play) return;
638 CDR_LOG( "CDDA - %d:%d:%d\n",
639 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
641 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
642 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
644 SetPlaySeekRead(cdr.StatP, 0);
645 cdr.TrackChanged = TRUE;
648 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
651 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
652 cdrPlayInterrupt_Autopause();
654 if (!cdr.Muted && !Config.Cdda) {
655 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
656 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
657 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
661 msfiAdd(cdr.SetSectorPlay, 1);
663 // update for CdlGetlocP/autopause
664 generate_subq(cdr.SetSectorPlay);
666 CDRPLAYREAD_INT(cdReadTime, 0);
669 #define CMD_PART2 0x100
670 #define CMD_WHILE_NOT_READY 0x200
672 void cdrInterrupt(void) {
673 int start_rotating = 0;
675 u32 cycles, seekTime = 0;
676 u32 second_resp_time = 0;
686 CDR_LOG_I("cmd %02x with irqstat %x\n",
687 cdr.CmdInProgress, cdr.Stat);
690 if (cdr.Irq1Pending) {
691 // hand out the "newest" sector, according to nocash
692 cdrUpdateTransferBuf(CDR_getBuffer());
693 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
694 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
695 cdr.CmdInProgress, cdr.Irq1Pending);
697 cdr.Result[0] = cdr.Irq1Pending;
698 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
706 cdr.Result[0] = cdr.StatP;
707 cdr.Stat = Acknowledge;
709 Cmd = cdr.CmdInProgress;
710 cdr.CmdInProgress = 0;
719 switch (cdr.DriveState) {
720 case DRIVESTATE_PREPARE_CD:
722 // Syphon filter 2 expects commands to work shortly after it sees
723 // STATUS_ROTATING, so give up trying to emulate the startup seq
724 cdr.DriveState = DRIVESTATE_STANDBY;
725 cdr.StatP &= ~STATUS_SEEK;
726 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
730 case DRIVESTATE_LID_OPEN:
731 case DRIVESTATE_RESCAN_CD:
732 // no disk or busy with the initial scan, allowed cmds are limited
733 not_ready = CMD_WHILE_NOT_READY;
737 switch (Cmd | not_ready) {
739 case CdlNop + CMD_WHILE_NOT_READY:
740 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
741 cdr.StatP &= ~STATUS_SHELLOPEN;
745 case CdlSetloc + CMD_WHILE_NOT_READY:
746 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
748 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
749 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))
751 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
752 if (++cdr.errorRetryhack > 100)
754 error = ERROR_INVALIDARG;
759 for (i = 0; i < 3; i++)
760 set_loc[i] = btoi(cdr.Param[i]);
761 memcpy(cdr.SetSector, set_loc, 3);
762 cdr.SetSector[3] = 0;
763 cdr.SetlocPending = 1;
764 cdr.errorRetryhack = 0;
773 cdr.FastBackward = 0;
777 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
779 if (ParamC != 0 && cdr.Param[0] != 0) {
780 int track = btoi( cdr.Param[0] );
782 if (track <= cdr.ResultTN[1])
783 cdr.CurTrack = track;
785 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
787 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
788 for (i = 0; i < 3; i++)
789 set_loc[i] = cdr.ResultTD[2 - i];
790 seekTime = cdrSeekTime(set_loc);
791 memcpy(cdr.SetSectorPlay, set_loc, 3);
794 else if (cdr.SetlocPending) {
795 seekTime = cdrSeekTime(cdr.SetSector);
796 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
799 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
800 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
802 cdr.SetlocPending = 0;
805 Rayman: detect track changes
808 Twisted Metal 2: skip PREGAP + starting accurate SubQ
809 - plays tracks without retry play
811 Wild 9: skip PREGAP + starting accurate SubQ
812 - plays tracks without retry play
814 Find_CurTrack(cdr.SetSectorPlay);
815 generate_subq(cdr.SetSectorPlay);
816 cdr.LocL[0] = LOCL_INVALID;
817 cdr.SubqForwardSectors = 1;
818 cdr.TrackChanged = FALSE;
822 CDR_play(cdr.SetSectorPlay);
824 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
826 // BIOS player - set flag again
829 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
834 // TODO: error 80 if stopped
837 // GameShark CD Player: Calls 2x + Play 2x
839 cdr.FastBackward = 0;
845 // GameShark CD Player: Calls 2x + Play 2x
846 cdr.FastBackward = 1;
851 if (cdr.DriveState != DRIVESTATE_STOPPED) {
852 error = ERROR_INVALIDARG;
855 second_resp_time = cdReadTime * 125 / 2;
859 case CdlStandby + CMD_PART2:
865 // grab time for current track
866 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
868 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
869 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
870 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
875 SetPlaySeekRead(cdr.StatP, 0);
876 cdr.StatP &= ~STATUS_ROTATING;
877 cdr.LocL[0] = LOCL_INVALID;
879 second_resp_time = 0x800;
880 if (cdr.DriveState == DRIVESTATE_STANDBY)
881 second_resp_time = cdReadTime * 30 / 2;
883 cdr.DriveState = DRIVESTATE_STOPPED;
886 case CdlStop + CMD_PART2:
894 Gundam Battle Assault 2: much slower (*)
895 - Fixes boot, gameplay
897 Hokuto no Ken 2: slower
898 - Fixes intro + subtitles
900 InuYasha - Feudal Fairy Tale: slower
903 /* Gameblabla - Tightening the timings (as taken from Duckstation).
904 * The timings from Duckstation are based upon hardware tests.
905 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
906 * seems to be timing sensitive as it can depend on the CPU's clock speed.
908 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
910 second_resp_time = 7000;
914 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 1 : 2) * 1097107);
916 SetPlaySeekRead(cdr.StatP, 0);
919 case CdlPause + CMD_PART2:
924 case CdlReset + CMD_WHILE_NOT_READY:
927 SetPlaySeekRead(cdr.StatP, 0);
928 cdr.LocL[0] = LOCL_INVALID;
930 cdr.Mode = MODE_SIZE_2340; /* This fixes This is Football 2, Pooh's Party lockups */
931 second_resp_time = not_ready ? 70000 : 4100000;
935 case CdlReset + CMD_PART2:
936 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
949 cdr.File = cdr.Param[0];
950 cdr.Channel = cdr.Param[1];
954 case CdlSetmode + CMD_WHILE_NOT_READY:
955 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
956 cdr.Mode = cdr.Param[0];
960 case CdlGetparam + CMD_WHILE_NOT_READY:
961 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
963 cdr.Result[1] = cdr.Mode;
965 cdr.Result[3] = cdr.File;
966 cdr.Result[4] = cdr.Channel;
970 if (cdr.LocL[0] == LOCL_INVALID) {
975 memcpy(cdr.Result, cdr.LocL, 8);
980 memcpy(&cdr.Result, &cdr.subq, 8);
983 case CdlReadT: // SetSession?
985 second_resp_time = cdReadTime * 290 / 4;
989 case CdlReadT + CMD_PART2:
995 if (CDR_getTN(cdr.ResultTN) == -1) {
996 cdr.Stat = DiskError;
997 cdr.Result[0] |= STATUS_ERROR;
999 cdr.Stat = Acknowledge;
1000 cdr.Result[1] = itob(cdr.ResultTN[0]);
1001 cdr.Result[2] = itob(cdr.ResultTN[1]);
1006 cdr.Track = btoi(cdr.Param[0]);
1008 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1009 cdr.Stat = DiskError;
1010 cdr.Result[0] |= STATUS_ERROR;
1012 cdr.Stat = Acknowledge;
1013 cdr.Result[0] = cdr.StatP;
1014 cdr.Result[1] = itob(cdr.ResultTD[2]);
1015 cdr.Result[2] = itob(cdr.ResultTD[1]);
1016 /* According to Nocash's documentation, the function doesn't care about ff.
1017 * This can be seen also in Mednafen's implementation. */
1018 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1026 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1028 seekTime = cdrSeekTime(cdr.SetSector);
1029 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1031 Crusaders of Might and Magic = 0.5x-4x
1032 - fix cutscene speech start
1034 Eggs of Steel = 2x-?
1038 - fix cutscene speech
1043 second_resp_time = cdReadTime + seekTime;
1047 case CdlSeekL + CMD_PART2:
1048 case CdlSeekP + CMD_PART2:
1049 SetPlaySeekRead(cdr.StatP, 0);
1050 cdr.Result[0] = cdr.StatP;
1051 cdr.Stat = Complete;
1053 Find_CurTrack(cdr.SetSectorPlay);
1054 read_ok = ReadTrack(cdr.SetSectorPlay);
1055 if (read_ok && (buf = CDR_getBuffer()))
1056 memcpy(cdr.LocL, buf, 8);
1057 UpdateSubq(cdr.SetSectorPlay);
1058 cdr.TrackChanged = FALSE;
1062 case CdlTest + CMD_WHILE_NOT_READY:
1063 switch (cdr.Param[0]) {
1064 case 0x20: // System Controller ROM Version
1066 memcpy(cdr.Result, Test20, 4);
1070 memcpy(cdr.Result, Test22, 4);
1072 case 0x23: case 0x24:
1074 memcpy(cdr.Result, Test23, 4);
1080 second_resp_time = 20480;
1083 case CdlID + CMD_PART2:
1085 cdr.Result[0] = cdr.StatP;
1090 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1091 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1092 cdr.Result[1] = 0xc0;
1096 cdr.Result[1] |= 0x10;
1097 if (CdromId[0] == '\0')
1098 cdr.Result[1] |= 0x80;
1100 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1102 /* This adds the string "PCSX" in Playstation bios boot screen */
1103 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1104 cdr.Stat = Complete;
1108 case CdlInit + CMD_WHILE_NOT_READY:
1111 SetPlaySeekRead(cdr.StatP, 0);
1112 // yes, it really sets STATUS_SHELLOPEN
1113 cdr.StatP |= STATUS_SHELLOPEN;
1114 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1120 case CdlGetQ + CMD_WHILE_NOT_READY:
1124 case CdlReadToc + CMD_WHILE_NOT_READY:
1125 cdr.LocL[0] = LOCL_INVALID;
1126 second_resp_time = cdReadTime * 180 / 4;
1130 case CdlReadToc + CMD_PART2:
1131 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1132 cdr.Stat = Complete;
1137 if (cdr.Reading && !cdr.SetlocPending)
1140 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1142 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1143 // Read* acts as play for cdda tracks in cdda mode
1147 if (cdr.SetlocPending) {
1148 seekTime = cdrSeekTime(cdr.SetSector);
1149 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1150 cdr.SetlocPending = 0;
1153 cdr.FirstSector = 1;
1155 // Fighting Force 2 - update subq time immediately
1157 UpdateSubq(cdr.SetSectorPlay);
1158 cdr.LocL[0] = LOCL_INVALID;
1159 cdr.SubqForwardSectors = 1;
1161 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1163 if (Config.hacks.cdr_read_timing)
1164 cycles = cdrAlignTimingHack(cycles);
1165 CDRPLAYREAD_INT(cycles, 1);
1167 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1173 error = ERROR_INVALIDCMD;
1177 CDR_LOG_I("cmd %02x error %02x\n", Cmd, error);
1179 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1180 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1181 cdr.Stat = DiskError;
1185 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1186 cdr.DriveState = DRIVESTATE_STANDBY;
1187 cdr.StatP |= STATUS_ROTATING;
1190 if (second_resp_time) {
1191 cdr.CmdInProgress = Cmd | 0x100;
1192 CDR_INT(second_resp_time);
1194 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1195 cdr.CmdInProgress = cdr.Cmd;
1196 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1203 #define ssat32_to_16(v) \
1204 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1206 #define ssat32_to_16(v) do { \
1207 if (v < -32768) v = -32768; \
1208 else if (v > 32767) v = 32767; \
1212 static void cdrPrepCdda(s16 *buf, int samples)
1214 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1216 for (i = 0; i < samples; i++) {
1217 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1218 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1223 static void cdrAttenuate(s16 *buf, int samples, int stereo)
1226 int ll = cdr.AttenuatorLeftToLeft;
1227 int lr = cdr.AttenuatorLeftToRight;
1228 int rl = cdr.AttenuatorRightToLeft;
1229 int rr = cdr.AttenuatorRightToRight;
1231 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1234 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1238 for (i = 0; i < samples; i++) {
1241 l = (l * ll + r * rl) >> 7;
1242 r = (r * rr + l * lr) >> 7;
1250 for (i = 0; i < samples; i++) {
1252 l = l * (ll + rl) >> 7;
1253 //r = r * (rr + lr) >> 7;
1261 static void cdrReadInterruptSetResult(unsigned char result)
1264 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1265 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1266 cdr.CmdInProgress, cdr.Stat);
1267 cdr.Irq1Pending = result;
1271 cdr.Result[0] = result;
1272 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1276 static void cdrUpdateTransferBuf(const u8 *buf)
1280 memcpy(cdr.Transfer, buf, DATA_SIZE);
1281 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1282 CDR_LOG("cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1283 if (cdr.FifoOffset < 2048 + 12)
1284 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1287 static void cdrReadInterrupt(void)
1289 u8 *buf = NULL, *hdr;
1293 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1294 msfiAdd(subqPos, cdr.SubqForwardSectors);
1295 UpdateSubq(subqPos);
1296 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1297 cdr.SubqForwardSectors++;
1298 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1302 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1303 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1305 read_ok = ReadTrack(cdr.SetSectorPlay);
1307 buf = CDR_getBuffer();
1312 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1313 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1316 memcpy(cdr.LocL, buf, 8);
1318 if (!cdr.Stat && !cdr.Irq1Pending)
1319 cdrUpdateTransferBuf(buf);
1321 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1323 // Firemen 2: Multi-XA files - briefings, cutscenes
1324 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1326 cdr.Channel = hdr[1];
1330 * Skips playing on channel 255.
1331 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1332 * TODO : Check if this is the proper behaviour.
1334 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1335 int ret = xa_decode_sector(&cdr.Xa, buf + 4, cdr.FirstSector);
1337 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1338 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1339 cdr.FirstSector = 0;
1341 else cdr.FirstSector = -1;
1346 Croc 2: $40 - only FORM1 (*)
1347 Judge Dredd: $C8 - only FORM1 (*)
1348 Sim Theme Park - no adpcm at all (zero)
1351 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1352 cdrReadInterruptSetResult(cdr.StatP);
1354 msfiAdd(cdr.SetSectorPlay, 1);
1356 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1365 bit 5 - 1 result ready
1367 bit 7 - 1 command being processed
1370 unsigned char cdrRead0(void) {
1371 if (cdr.ResultReady)
1376 cdr.Ctrl |= 0x40; // data fifo not empty
1378 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1381 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1383 return psxHu8(0x1800) = cdr.Ctrl;
1386 void cdrWrite0(unsigned char rt) {
1387 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1389 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1392 unsigned char cdrRead1(void) {
1393 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1394 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1398 if (cdr.ResultP == cdr.ResultC)
1399 cdr.ResultReady = 0;
1401 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1403 return psxHu8(0x1801);
1406 void cdrWrite1(unsigned char rt) {
1407 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1408 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1410 switch (cdr.Ctrl & 3) {
1414 cdr.AttenuatorRightToRightT = rt;
1420 #ifdef CDR_LOG_CMD_IRQ
1421 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1424 SysPrintf(" Param[%d] = {", cdr.ParamC);
1425 for (i = 0; i < cdr.ParamC; i++)
1426 SysPrintf(" %x,", cdr.Param[i]);
1433 cdr.ResultReady = 0;
1436 if (!cdr.CmdInProgress) {
1437 cdr.CmdInProgress = rt;
1438 // should be something like 12k + controller delays
1442 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1443 rt, cdr.Cmd, cdr.CmdInProgress);
1444 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1445 cdr.CmdInProgress = rt;
1451 unsigned char cdrRead2(void) {
1452 unsigned char ret = cdr.Transfer[0x920];
1454 if (cdr.FifoOffset < cdr.FifoSize)
1455 ret = cdr.Transfer[cdr.FifoOffset++];
1457 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1459 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1463 void cdrWrite2(unsigned char rt) {
1464 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1465 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1467 switch (cdr.Ctrl & 3) {
1469 if (cdr.ParamC < 8) // FIXME: size and wrapping
1470 cdr.Param[cdr.ParamC++] = rt;
1477 cdr.AttenuatorLeftToLeftT = rt;
1480 cdr.AttenuatorRightToLeftT = rt;
1485 unsigned char cdrRead3(void) {
1487 psxHu8(0x1803) = cdr.Stat | 0xE0;
1489 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1491 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1492 return psxHu8(0x1803);
1495 void cdrWrite3(unsigned char rt) {
1496 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1497 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1499 switch (cdr.Ctrl & 3) {
1503 if (cdr.Stat & rt) {
1504 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1505 + psxRegs.intCycle[PSXINT_CDR].cycle;
1506 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1507 #ifdef CDR_LOG_CMD_IRQ
1508 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n", cdr.Stat & rt, rt,
1509 !!pending, cdr.CmdInProgress,
1510 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1512 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1513 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1516 if (cdr.CmdInProgress) {
1517 c = 2048 - (psxRegs.cycle - nextCycle);
1518 c = MAX_VALUE(c, 512);
1529 cdr.AttenuatorLeftToRightT = rt;
1533 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1534 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1535 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1536 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1542 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1543 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1545 else if (rt & 0x80) {
1546 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1547 case MODE_SIZE_2328:
1549 cdr.FifoOffset = 12;
1550 cdr.FifoSize = 2048 + 12;
1553 case MODE_SIZE_2340:
1556 cdr.FifoSize = 2340;
1560 else if (!(rt & 0xc0))
1561 cdr.FifoOffset = DATA_SIZE; // fifo empty
1564 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1565 u32 cdsize, max_words;
1570 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1571 if (cdr.FifoOffset == 0) {
1573 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1578 switch (chcr & 0x71000000) {
1580 ptr = getDmaRam(madr, &max_words);
1581 if (ptr == INVALID_PTR) {
1582 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1586 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1589 GS CDX: Enhancement CD crash
1592 - Spams DMA3 and gets buffer overrun
1594 size = DATA_SIZE - cdr.FifoOffset;
1597 if (size > max_words * 4)
1598 size = max_words * 4;
1601 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1602 cdr.FifoOffset += size;
1604 if (size < cdsize) {
1605 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1606 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1608 psxCpu->Clear(madr, cdsize / 4);
1610 CDRDMA_INT((cdsize/4) * 24);
1612 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1614 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1615 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1619 psxRegs.cycle += (cdsize/4) * 24 - 20;
1624 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1628 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1632 void cdrDmaInterrupt(void)
1634 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1636 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1641 static void getCdInfo(void)
1645 CDR_getTN(cdr.ResultTN);
1646 CDR_getTD(0, cdr.SetSectorEnd);
1647 tmp = cdr.SetSectorEnd[0];
1648 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1649 cdr.SetSectorEnd[2] = tmp;
1653 memset(&cdr, 0, sizeof(cdr));
1659 cdr.FifoOffset = DATA_SIZE; // fifo empty
1660 if (CdromId[0] == '\0') {
1661 cdr.DriveState = DRIVESTATE_STOPPED;
1665 cdr.DriveState = DRIVESTATE_STANDBY;
1666 cdr.StatP = STATUS_ROTATING;
1669 // BIOS player - default values
1670 cdr.AttenuatorLeftToLeft = 0x80;
1671 cdr.AttenuatorLeftToRight = 0x00;
1672 cdr.AttenuatorRightToLeft = 0x00;
1673 cdr.AttenuatorRightToRight = 0x80;
1678 int cdrFreeze(void *f, int Mode) {
1682 if (Mode == 0 && !Config.Cdda)
1685 cdr.freeze_ver = 0x63647202;
1686 gzfreeze(&cdr, sizeof(cdr));
1689 cdr.ParamP = cdr.ParamC;
1690 tmp = cdr.FifoOffset;
1693 gzfreeze(&tmp, sizeof(tmp));
1698 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1699 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1700 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1701 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1703 // read right sub data
1704 tmpp[0] = btoi(cdr.Prev[0]);
1705 tmpp[1] = btoi(cdr.Prev[1]);
1706 tmpp[2] = btoi(cdr.Prev[2]);
1711 if (cdr.freeze_ver < 0x63647202)
1712 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1714 Find_CurTrack(cdr.SetSectorPlay);
1716 CDR_play(cdr.SetSectorPlay);
1719 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1720 // old versions did not latch Reg2, have to fixup..
1721 if (cdr.Reg2 == 0) {
1722 SysPrintf("cdrom: fixing up old savestate\n");
1725 // also did not save Attenuator..
1726 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1727 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1729 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1737 void LidInterrupt(void) {
1739 cdrLidSeekInterrupt();