cdrom: adjust a timing hack
[pcsx_rearmed.git] / libpcsxcore / cdrom.c
index b3e238b..098b77d 100644 (file)
@@ -21,6 +21,7 @@
 * Handles all CD-ROM registers and functions.
 */
 
+#include <assert.h>
 #include "cdrom.h"
 #include "ppf.h"
 #include "psxdma.h"
@@ -76,7 +77,7 @@ static struct {
        unsigned char ResultP;
        unsigned char ResultReady;
        unsigned char Cmd;
-       unsigned char unused4;
+       unsigned char SubqForwardSectors;
        unsigned char SetlocPending;
        u32 Reading;
 
@@ -208,6 +209,7 @@ unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
 #define cdReadTime (PSXCLK / 75)
 
 #define LOCL_INVALID 0xff
+#define SUBQ_FORWARD_SECTORS 2u
 
 enum drive_state {
        DRIVESTATE_STANDBY = 0, // pause, play, read
@@ -323,6 +325,7 @@ void cdrLidSeekInterrupt(void)
 
                if (stat.Status & STATUS_SHELLOPEN)
                {
+                       memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
                        cdr.DriveState = DRIVESTATE_LID_OPEN;
                        CDRLID_INT(0x800);
                }
@@ -447,11 +450,10 @@ static void generate_subq(const u8 *time)
        cdr.subq.Absolute[2] = itob(time[2]);
 }
 
-static int ReadTrack(const u8 *time) {
+static int ReadTrack(const u8 *time)
+{
        unsigned char tmp[3];
-       struct SubQ *subq;
        int read_ok;
-       u16 crc;
 
        tmp[0] = itob(time[0]);
        tmp[1] = itob(time[1]);
@@ -463,12 +465,20 @@ static int ReadTrack(const u8 *time) {
        CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
 
        read_ok = CDR_readTrack(tmp);
-       memcpy(cdr.Prev, tmp, 3);
+       if (read_ok)
+               memcpy(cdr.Prev, tmp, 3);
+       return read_ok;
+}
+
+static void UpdateSubq(const u8 *time)
+{
+       const struct SubQ *subq;
+       u16 crc;
 
        if (CheckSBI(time))
-               return read_ok;
+               return;
 
-       subq = (struct SubQ *)CDR_getBufferSub();
+       subq = (struct SubQ *)CDR_getBufferSub(MSF2SECT(time[0], time[1], time[2]));
        if (subq != NULL && cdr.CurTrack == 1) {
                crc = calcCrc((u8 *)subq + 12, 10);
                if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
@@ -478,8 +488,8 @@ static int ReadTrack(const u8 *time) {
                        memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
                }
                else {
-                       CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
-                               tmp[0], tmp[1], tmp[2]);
+                       CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
+                               time[0], time[1], time[2]);
                }
        }
        else {
@@ -490,8 +500,6 @@ static int ReadTrack(const u8 *time) {
                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]);
-
-       return read_ok;
 }
 
 static void cdrPlayInterrupt_Autopause()
@@ -577,11 +585,52 @@ static int cdrSeekTime(unsigned char *target)
        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 cdrAttenuate(s16 *buf, int samples, int stereo);
 
+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]++;
+               }
+       }
+}
+
 void cdrPlayReadInterrupt(void)
 {
        if (cdr.Reading) {
@@ -614,15 +663,7 @@ void cdrPlayReadInterrupt(void)
                cdr.FirstSector = 0;
        }
 
-       cdr.SetSectorPlay[2]++;
-       if (cdr.SetSectorPlay[2] == 75) {
-               cdr.SetSectorPlay[2] = 0;
-               cdr.SetSectorPlay[1]++;
-               if (cdr.SetSectorPlay[1] == 60) {
-                       cdr.SetSectorPlay[1] = 0;
-                       cdr.SetSectorPlay[0]++;
-               }
-       }
+       msfiAdd(cdr.SetSectorPlay, 1);
 
        // update for CdlGetlocP/autopause
        generate_subq(cdr.SetSectorPlay);
@@ -636,7 +677,7 @@ void cdrPlayReadInterrupt(void)
 void cdrInterrupt(void) {
        int start_rotating = 0;
        int error = 0;
-       unsigned int seekTime = 0;
+       u32 cycles, seekTime = 0;
        u32 second_resp_time = 0;
        const void *buf;
        u8 ParamC;
@@ -772,8 +813,9 @@ void cdrInterrupt(void) {
                        - plays tracks without retry play
                        */
                        Find_CurTrack(cdr.SetSectorPlay);
-                       ReadTrack(cdr.SetSectorPlay);
+                       generate_subq(cdr.SetSectorPlay);
                        cdr.LocL[0] = LOCL_INVALID;
+                       cdr.SubqForwardSectors = 1;
                        cdr.TrackChanged = FALSE;
                        cdr.FirstSector = 1;
 
@@ -1013,6 +1055,7 @@ void cdrInterrupt(void) {
                        read_ok = ReadTrack(cdr.SetSectorPlay);
                        if (read_ok && (buf = CDR_getBuffer()))
                                memcpy(cdr.LocL, buf, 8);
+                       UpdateSubq(cdr.SetSectorPlay);
                        cdr.TrackChanged = FALSE;
                        break;
 
@@ -1112,10 +1155,15 @@ void cdrInterrupt(void) {
 
                        // Fighting Force 2 - update subq time immediately
                        // - fixes new game
-                       ReadTrack(cdr.SetSectorPlay);
+                       UpdateSubq(cdr.SetSectorPlay);
                        cdr.LocL[0] = LOCL_INVALID;
+                       cdr.SubqForwardSectors = 1;
 
-                       CDRPLAYREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime, 1);
+                       cycles = (cdr.Mode & 0x80) ? cdReadTime : cdReadTime * 2;
+                       cycles += seekTime;
+                       if (Config.hacks.cdr_read_timing)
+                               cycles = cdrAlignTimingHack(cycles);
+                       CDRPLAYREAD_INT(cycles, 1);
 
                        SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
                        start_rotating = 1;
@@ -1241,8 +1289,19 @@ static void cdrUpdateTransferBuf(const u8 *buf)
 static void cdrReadInterrupt(void)
 {
        u8 *buf = NULL, *hdr;
+       u8 subqPos[3];
        int read_ok;
 
+       memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
+       msfiAdd(subqPos, cdr.SubqForwardSectors);
+       UpdateSubq(subqPos);
+       if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
+               cdr.SubqForwardSectors++;
+               CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
+               return;
+       }
+
+       // note: CdlGetlocL should work as soon as STATUS_READ is indicated
        SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
 
        read_ok = ReadTrack(cdr.SetSectorPlay);
@@ -1253,7 +1312,6 @@ static void cdrReadInterrupt(void)
 
        if (!read_ok) {
                CDR_LOG_I("cdrReadInterrupt() Log: err\n");
-               memset(cdr.Transfer, 0, DATA_SIZE);
                cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
                return;
        }
@@ -1295,20 +1353,7 @@ static void cdrReadInterrupt(void)
        if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
                cdrReadInterruptSetResult(cdr.StatP);
 
-       cdr.SetSectorPlay[2]++;
-       if (cdr.SetSectorPlay[2] == 75) {
-               cdr.SetSectorPlay[2] = 0;
-               cdr.SetSectorPlay[1]++;
-               if (cdr.SetSectorPlay[1] == 60) {
-                       cdr.SetSectorPlay[1] = 0;
-                       cdr.SetSectorPlay[0]++;
-               }
-       }
-
-       if (!cdr.Irq1Pending) {
-               // update for CdlGetlocP
-               ReadTrack(cdr.SetSectorPlay);
-       }
+       msfiAdd(cdr.SetSectorPlay, 1);
 
        CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
 }
@@ -1406,7 +1451,7 @@ void cdrWrite1(unsigned char rt) {
 }
 
 unsigned char cdrRead2(void) {
-       unsigned char ret = 0;
+       unsigned char ret = cdr.Transfer[0x920];
 
        if (cdr.FifoOffset < cdr.FifoSize)
                ret = cdr.Transfer[cdr.FifoOffset++];
@@ -1458,14 +1503,22 @@ void cdrWrite3(unsigned char rt) {
                break; // transfer
        case 1:
                if (cdr.Stat & rt) {
+                       u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
+                               + psxRegs.intCycle[PSXINT_CDR].cycle;
 #ifdef CDR_LOG_CMD_IRQ
-                       SysPrintf("%u cdrom: ack %02x (w %02x)\n",
-                               psxRegs.cycle, cdr.Stat & rt, rt);
+                       SysPrintf("%u cdrom: ack %02x (w=%02x p=%d,%d)\n",
+                               psxRegs.cycle, cdr.Stat & rt, rt,
+                               !!(psxRegs.interrupt & (1 << PSXINT_CDR)),
+                               nextCycle - psxRegs.cycle);
 #endif
-                       // note: Croc vs Discworld Noir
+                       // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
                        if (!(psxRegs.interrupt & (1 << PSXINT_CDR)) &&
                            (cdr.CmdInProgress || cdr.Irq1Pending))
-                               CDR_INT(850); // 711-993
+                       {
+                               s32 c = 2048 - (psxRegs.cycle - nextCycle);
+                               c = MAX_VALUE(c, 512);
+                               CDR_INT(c);
+                       }
                }
                cdr.Stat &= ~rt;
 
@@ -1538,10 +1591,12 @@ void psxDma3(u32 madr, u32 bcr, u32 chcr) {
                        {
                                memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
                                cdr.FifoOffset += size;
-                               psxCpu->Clear(madr, size / 4);
                        }
-                       if (size < cdsize)
+                       if (size < cdsize) {
                                CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
+                               memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
+                       }
+                       psxCpu->Clear(madr, cdsize / 4);
 
                        CDRDMA_INT((cdsize/4) * 24);
 
@@ -1631,8 +1686,10 @@ int cdrFreeze(void *f, int Mode) {
        if (Mode == 0) {
                getCdInfo();
 
-               cdr.FifoOffset = tmp;
+               cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
                cdr.FifoSize = (cdr.Mode & 0x20) ? 2340 : 2048 + 12;
+               if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
+                       cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
 
                // read right sub data
                tmpp[0] = btoi(cdr.Prev[0]);