+static void Find_CurTrack(const u8 *time)
+{
+ int current, sect;
+
+ current = msf2sec(time);
+
+ for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
+ CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
+ sect = fsm2sec(cdr.ResultTD);
+ if (sect - current >= 150)
+ break;
+ }
+}
+
+static void generate_subq(const u8 *time)
+{
+ unsigned char start[3], next[3];
+ unsigned int this_s, start_s, next_s, pregap;
+ int relative_s;
+
+ CDR_getTD(cdr.CurTrack, start);
+ if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
+ pregap = 150;
+ CDR_getTD(cdr.CurTrack + 1, next);
+ }
+ else {
+ // last track - cd size
+ pregap = 0;
+ next[0] = cdr.SetSectorEnd[2];
+ next[1] = cdr.SetSectorEnd[1];
+ next[2] = cdr.SetSectorEnd[0];
+ }
+
+ this_s = msf2sec(time);
+ start_s = fsm2sec(start);
+ next_s = fsm2sec(next);
+
+ cdr.TrackChanged = FALSE;
+
+ if (next_s - this_s < pregap) {
+ cdr.TrackChanged = TRUE;
+ cdr.CurTrack++;
+ start_s = next_s;
+ }
+
+ cdr.subq.Index = 1;
+
+ relative_s = this_s - start_s;
+ if (relative_s < 0) {
+ cdr.subq.Index = 0;
+ relative_s = -relative_s;
+ }
+ sec2msf(relative_s, cdr.subq.Relative);
+
+ cdr.subq.Track = itob(cdr.CurTrack);
+ cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
+ cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
+ cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
+ cdr.subq.Absolute[0] = itob(time[0]);
+ cdr.subq.Absolute[1] = itob(time[1]);
+ cdr.subq.Absolute[2] = itob(time[2]);
+}
+
+static int ReadTrack(const u8 *time)
+{
+ unsigned char tmp[3];
+ int read_ok;
+
+ tmp[0] = itob(time[0]);
+ tmp[1] = itob(time[1]);
+ tmp[2] = itob(time[2]);
+
+ CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
+
+ if (memcmp(cdr.Prev, tmp, 3) == 0)
+ return 1;
+
+ read_ok = CDR_readTrack(tmp);
+ if (read_ok)
+ memcpy(cdr.Prev, tmp, 3);
+ return read_ok;
+}
+
+static void UpdateSubq(const u8 *time)
+{
+ const struct SubQ *subq;
+ int s = MSF2SECT(time[0], time[1], time[2]);
+ u16 crc;
+
+ if (CheckSBI(s))
+ return;
+
+ subq = (struct SubQ *)CDR_getBufferSub(s);
+ if (subq != NULL && cdr.CurTrack == 1) {
+ crc = calcCrc((u8 *)subq + 12, 10);
+ if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
+ cdr.subq.Track = subq->TrackNumber;
+ cdr.subq.Index = subq->IndexNumber;
+ memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
+ memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
+ }
+ else {
+ CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
+ time[0], time[1], time[2]);
+ }
+ }
+ else {
+ generate_subq(time);
+ }
+
+ CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
+ cdr.subq.Track, cdr.subq.Index,
+ cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
+ cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
+}
+
+static void cdrPlayInterrupt_Autopause()
+{
+ u32 abs_lev_max = 0;
+ boolean abs_lev_chselect;
+ u32 i;
+
+ if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
+ CDR_LOG_I("autopause\n");
+
+ SetResultSize(1);
+ cdr.Result[0] = cdr.StatP;
+ setIrq(DataEnd, 0x1000); // 0x1000 just for logging purposes
+
+ StopCdda();
+ SetPlaySeekRead(cdr.StatP, 0);
+ cdr.DriveState = DRIVESTATE_PAUSED;
+ }
+ else if ((cdr.Mode & MODE_REPORT) && !cdr.ReportDelay &&
+ ((cdr.subq.Absolute[2] & 0x0f) == 0 || cdr.FastForward || cdr.FastBackward))
+ {
+ SetResultSize(8);
+ cdr.Result[0] = cdr.StatP;
+ cdr.Result[1] = cdr.subq.Track;
+ cdr.Result[2] = cdr.subq.Index;
+
+ abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
+
+ /* 8 is a hack. For accuracy, it should be 588. */
+ for (i = 0; i < 8; i++)
+ {
+ abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
+ }
+ abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
+ abs_lev_max |= abs_lev_chselect << 15;
+
+ if (cdr.subq.Absolute[2] & 0x10) {
+ cdr.Result[3] = cdr.subq.Relative[0];
+ cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
+ cdr.Result[5] = cdr.subq.Relative[2];
+ }
+ else {
+ cdr.Result[3] = cdr.subq.Absolute[0];
+ cdr.Result[4] = cdr.subq.Absolute[1];
+ cdr.Result[5] = cdr.subq.Absolute[2];
+ }
+ cdr.Result[6] = abs_lev_max >> 0;
+ cdr.Result[7] = abs_lev_max >> 8;
+
+ setIrq(DataReady, 0x1001);
+ }
+
+ if (cdr.ReportDelay)
+ cdr.ReportDelay--;
+}
+
+static int cdrSeekTime(unsigned char *target)
+{
+ int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
+ int seekTime = abs(diff) * (cdReadTime / 2000);
+ int cyclesSinceRS = psxRegs.cycle - cdr.LastReadSeekCycles;
+ seekTime = MAX_VALUE(seekTime, 20000);
+
+ // need this stupidly long penalty or else Spyro2 intro desyncs
+ // note: if misapplied this breaks MGS cutscenes among other things
+ if (cdr.DriveState == DRIVESTATE_PAUSED && cyclesSinceRS > cdReadTime * 50)
+ seekTime += cdReadTime * 25;
+ // Transformers Beast Wars Transmetals does Setloc(x),SeekL,Setloc(x),ReadN
+ // and then wants some slack time
+ else if (cdr.DriveState == DRIVESTATE_PAUSED || cyclesSinceRS < cdReadTime *3/2)
+ seekTime += cdReadTime;
+
+ seekTime = MIN_VALUE(seekTime, PSXCLK * 2 / 3);
+ CDR_LOG("seek: %.2f %.2f (%.2f) st %d\n", (float)seekTime / PSXCLK,
+ (float)seekTime / cdReadTime, (float)cyclesSinceRS / cdReadTime,
+ cdr.DriveState);
+ return seekTime;
+}
+
+static u32 cdrAlignTimingHack(u32 cycles)
+{
+ /*
+ * timing hack for T'ai Fu - Wrath of the Tiger:
+ * The game has a bug where it issues some cdc commands from a low priority
+ * vint handler, however there is a higher priority default bios handler
+ * that acks the vint irq and returns, so game's handler is not reached
+ * (see bios irq handler chains at e004 and the game's irq handling func
+ * at 80036810). For the game to work, vint has to arrive after the bios
+ * vint handler rejects some other irq (of which only cd and rcnt2 are
+ * active), but before the game's handler loop reads I_STAT. The time
+ * window for this is quite small (~1k cycles of so). Apparently this
+ * somehow happens naturally on the real hardware.
+ *
+ * Note: always enforcing this breaks other games like Crash PAL version
+ * (inputs get dropped because bios handler doesn't see interrupts).
+ */
+ u32 vint_rel;
+ if (psxRegs.cycle - rcnts[3].cycleStart > 250000)
+ return cycles;
+ vint_rel = rcnts[3].cycleStart + 63000 - psxRegs.cycle;
+ vint_rel += PSXCLK / 60;
+ while ((s32)(vint_rel - cycles) < 0)
+ vint_rel += PSXCLK / 60;
+ return vint_rel;
+}
+
+static void cdrUpdateTransferBuf(const u8 *buf);
+static void cdrReadInterrupt(void);
+static void cdrPrepCdda(s16 *buf, int samples);
+
+static void msfiAdd(u8 *msfi, u32 count)
+{
+ assert(count < 75);
+ msfi[2] += count;
+ if (msfi[2] >= 75) {
+ msfi[2] -= 75;
+ msfi[1]++;
+ if (msfi[1] == 60) {
+ msfi[1] = 0;
+ msfi[0]++;
+ }
+ }
+}
+
+static void msfiSub(u8 *msfi, u32 count)
+{
+ assert(count < 75);
+ msfi[2] -= count;
+ if ((s8)msfi[2] < 0) {
+ msfi[2] += 75;
+ msfi[1]--;
+ if ((s8)msfi[1] < 0) {
+ msfi[1] = 60;
+ msfi[0]--;
+ }
+ }
+}
+
+void cdrPlayReadInterrupt(void)
+{
+ cdr.LastReadSeekCycles = psxRegs.cycle;
+
+ if (cdr.Reading) {
+ cdrReadInterrupt();
+ return;
+ }
+
+ if (!cdr.Play) return;
+
+ CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
+ cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
+
+ cdr.DriveState = DRIVESTATE_PLAY_READ;
+ SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
+ if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
+ CDR_LOG_I("end stop\n");
+ StopCdda();
+ SetPlaySeekRead(cdr.StatP, 0);
+ cdr.TrackChanged = TRUE;
+ cdr.DriveState = DRIVESTATE_PAUSED;
+ }
+ else {
+ CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
+ }
+
+ if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
+ cdrPlayInterrupt_Autopause();
+
+ if (cdr.Play && !Config.Cdda) {
+ cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
+ SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, 0);
+ }
+
+ msfiAdd(cdr.SetSectorPlay, 1);
+
+ // update for CdlGetlocP/autopause
+ generate_subq(cdr.SetSectorPlay);
+
+ CDRPLAYREAD_INT(cdReadTime, 0);
+}
+
+static void softReset(void)
+{
+ CDR_getStatus(&stat);
+ if (stat.Status & STATUS_SHELLOPEN) {
+ cdr.DriveState = DRIVESTATE_LID_OPEN;
+ cdr.StatP = STATUS_SHELLOPEN;
+ }
+ else if (CdromId[0] == '\0') {
+ cdr.DriveState = DRIVESTATE_STOPPED;
+ cdr.StatP = 0;
+ }
+ else {
+ cdr.DriveState = DRIVESTATE_STANDBY;
+ cdr.StatP = STATUS_ROTATING;
+ }
+
+ cdr.FifoOffset = DATA_SIZE; // fifo empty
+ cdr.LocL[0] = LOCL_INVALID;
+ cdr.Mode = MODE_SIZE_2340;
+ cdr.Muted = FALSE;
+ SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
+ cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
+}
+
+#define CMD_PART2 0x100
+#define CMD_WHILE_NOT_READY 0x200
+
+void cdrInterrupt(void) {
+ int start_rotating = 0;
+ int error = 0;
+ u32 cycles, seekTime = 0;
+ u32 second_resp_time = 0;
+ const void *buf;
+ u8 ParamC;
+ u8 set_loc[3];
+ int read_ok;
+ u16 not_ready = 0;
+ u8 IrqStat = Acknowledge;
+ u8 DriveStateOld;
+ u16 Cmd;