cdrom: handle fifo overreads
[pcsx_rearmed.git] / libpcsxcore / cdrom.c
index cace6d9..e106573 100644 (file)
@@ -463,7 +463,8 @@ 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);
 
        if (CheckSBI(time))
                return read_ok;
@@ -577,6 +578,27 @@ 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.
+        */
+       u32 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);
@@ -636,7 +658,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;
@@ -1006,6 +1028,7 @@ void cdrInterrupt(void) {
                case CdlSeekL + CMD_PART2:
                case CdlSeekP + CMD_PART2:
                        SetPlaySeekRead(cdr.StatP, 0);
+                       cdr.Result[0] = cdr.StatP;
                        cdr.Stat = Complete;
 
                        Find_CurTrack(cdr.SetSectorPlay);
@@ -1114,7 +1137,10 @@ void cdrInterrupt(void) {
                        ReadTrack(cdr.SetSectorPlay);
                        cdr.LocL[0] = LOCL_INVALID;
 
-                       CDRPLAYREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime, 1);
+                       cycles = (cdr.Mode & 0x80) ? cdReadTime : cdReadTime * 2;
+                       cycles += seekTime;
+                       cycles = cdrAlignTimingHack(cycles);
+                       CDRPLAYREAD_INT(cycles, 1);
 
                        SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
                        start_rotating = 1;
@@ -1252,7 +1278,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;
        }
@@ -1405,7 +1430,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++];
@@ -1517,7 +1542,7 @@ void psxDma3(u32 madr, u32 bcr, u32 chcr) {
        switch (chcr & 0x71000000) {
                case 0x11000000:
                        ptr = (u8 *)PSXM(madr);
-                       if (ptr == NULL) {
+                       if (ptr == INVALID_PTR) {
                                CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
                                break;
                        }
@@ -1537,10 +1562,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);
 
@@ -1630,7 +1657,7 @@ 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;
 
                // read right sub data