shithub: psxe

Download patch

ref: 7f9fc68a99d1056963e74a37e921d449a3c99cca
parent: a6366b678095acd5d5388576ce1e8cb5574e7c88
author: allkern <lisandroaalarcon@gmail.com>
date: Tue May 21 10:04:03 EDT 2024

A lot of fixes and improvements

Fixed huge SPU bug
Completely rewrote timers

--- a/build-win64.ps1
+++ b/build-win64.ps1
@@ -29,4 +29,4 @@
     -Wno-address-of-packed-member `
     -ffast-math -Ofast -g -flto
 
-    Copy-Item -Path "$($SDL2_DIR)\bin\SDL2.dll" -Destination "bin"
\ No newline at end of file
+Copy-Item -Path "$($SDL2_DIR)\bin\SDL2.dll" -Destination "bin"
\ No newline at end of file
--- a/frontend/screen.c
+++ b/frontend/screen.c
@@ -121,8 +121,6 @@
         SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
     );
 
-    SDL_SetHint("SDL_HINT_RENDER_SCALE_QUALITY", "linear"),
-
     screen->texture = SDL_CreateTexture(
         screen->renderer,
         screen->format,
@@ -146,8 +144,6 @@
         SDL_RenderSetScale(screen->renderer, width_scale, height_scale);
     }
 
-    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0);
-
     screen->open = 1;
 }
 
@@ -486,4 +482,6 @@
     psxe_screen_t* screen = gpu->udata[0];
 
     psxe_screen_update(screen);
+
+    psxe_gpu_vblank_timer_event_cb(gpu);
 }
\ No newline at end of file
--- a/psx/dev/cdrom.c
+++ b/psx/dev/cdrom.c
@@ -262,14 +262,13 @@
             SET_BITS(ifr, IFR_INT, IFR_INT3);
             RESP_PUSH(
                 GETSTAT_MOTOR |
-                (cdrom->cdda_playing ? GETSTAT_PLAY : 0) |
+                ((cdrom->cdda_playing || cdrom->xa_playing) ? GETSTAT_PLAY : 0) |
                 (cdrom->ongoing_read_command ? GETSTAT_READ : 0) |
                 (cdrom->disc ? 0 : GETSTAT_TRAYOPEN)
             );
 
             if (cdrom->ongoing_read_command) {
-                cdrom->status |= STAT_BUSYSTS_MASK;
-                // printf("command=%02x\n", cdrom->ongoing_read_command);
+                // printf("getstat command=%02x\n", cdrom->ongoing_read_command);
                 cdrom->state = CD_STATE_SEND_RESP2;
                 cdrom->delayed_command = cdrom->ongoing_read_command;
                 cdrom->irq_delay = DELAY_1MS;
@@ -398,6 +397,8 @@
             cdrom->state = CD_STATE_SEND_RESP1;
             cdrom->delayed_command = CDL_PLAY;
 
+            printf("play track %u\n", track);
+
             if (track) {
                 psx_disc_get_track_addr(cdrom->disc, &cdrom->cdda_msf, track);
 
@@ -964,7 +965,7 @@
             RESP_PUSH(0x01);
 
             if (cdrom->ongoing_read_command) {
-                //// printf("command=%02x\n", cdrom->ongoing_read_command);
+                printf("getlocp command=%02x\n", cdrom->ongoing_read_command);
                 cdrom->state = CD_STATE_SEND_RESP2;
                 cdrom->delayed_command = cdrom->ongoing_read_command;
                 cdrom->irq_delay = DELAY_1MS;
@@ -1034,8 +1035,15 @@
             RESP_PUSH(0x01);
             RESP_PUSH(GETSTAT_MOTOR);
 
-            cdrom->delayed_command = CDL_NONE;
-            cdrom->state = CD_STATE_RECV_CMD;
+            if (cdrom->ongoing_read_command) {
+                //// printf("command=%02x\n", cdrom->ongoing_read_command);
+                cdrom->state = CD_STATE_SEND_RESP2;
+                cdrom->delayed_command = cdrom->ongoing_read_command;
+                cdrom->irq_delay = DELAY_1MS;
+            } else {
+                cdrom->delayed_command = CDL_NONE;
+                cdrom->state = CD_STATE_RECV_CMD;
+            }
         } break;
     }
 }
@@ -1415,17 +1423,16 @@
     switch (cdrom->state) {
         case CD_STATE_RECV_CMD: {
             cdrom->status |= STAT_BUSYSTS_MASK;
-            cdrom->irq_delay = DELAY_1MS * 1000;
+            cdrom->irq_delay = DELAY_1MS;
             cdrom->state = CD_STATE_SEND_RESP1;
             cdrom->delayed_command = CDL_READTOC;
         } break;
 
         case CD_STATE_SEND_RESP1: {
-            cdrom->status &= ~STAT_BUSYSTS_MASK;
             SET_BITS(ifr, IFR_INT, 3);
             RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
 
-            cdrom->irq_delay = DELAY_1MS * 1000;
+            cdrom->irq_delay = DELAY_1MS;
             cdrom->state = CD_STATE_SEND_RESP2;
             cdrom->delayed_command = CDL_READTOC;
         } break;
@@ -1634,6 +1641,17 @@
     cdrom->command = value;
     cdrom->state = CD_STATE_RECV_CMD;
 
+    // Required for Spyro - The Year of the Dragon
+    if (!cdrom->command) {
+        cdrom->irq_delay = DELAY_1MS * 600;
+        cdrom->delayed_command = CDL_ERROR;
+        cdrom->state = CD_STATE_ERROR;
+        cdrom->error = ERR_INVSUBF;
+        cdrom->error_flags = GETSTAT_SEEKERROR;
+
+        return;
+    }
+
     g_psx_cdrom_command_table[value](cdrom);
 }
 
@@ -2113,6 +2131,10 @@
         psx_disc_read_sector(cdrom->disc, cdrom->xa_sector_buf);
 
         msf_add_f(&cdrom->xa_msf, 1);
+
+        // Check for EOR, EOF bits
+        if (cdrom->xa_sector_buf[0x12] & 0x80)
+            return;
 
         // Check RT and Audio bit
         if ((cdrom->xa_sector_buf[0x12] & 4) != 4)
--- a/psx/dev/gpu.c
+++ b/psx/dev/gpu.c
@@ -1772,7 +1772,8 @@
         // enough. Sending T2 IRQs every line fixes DoA
         // but breaks a bunch of games, so I'll keep this
         // like this until I actually fix the timers
-        // Games that cared about T2:
+        // Games that seem to care about T2 timing:
+        // - Street Fighter Alpha 2
         // - Dead or Alive
         // - NBA Jam
         // - Doom
@@ -1782,7 +1783,14 @@
         // - Mortal Kombat
         // - PaRappa the Rapper
         // - In The Hunt
+        // - Crash Bandicoot
+        // - Jackie Chan Stuntmaster
         // - etc.
+        // Masking with 7 breaks Street Fighter Alpha 2. The game 
+        // just stops sending commands to the CDROM while on
+        // Player Select. It probably uses T2 IRQs to time
+        // GetlocP commands, if the timer is too slow it will
+        // break.
         if (!(gpu->line & 7))
             psx_ic_irq(gpu->ic, IC_TIMER2);
     } else {
@@ -1815,7 +1823,7 @@
 
     int curr_hblank = (gpu->cycles >= GPU_CYCLES_PER_HDRAW_NTSC) &&
                       (gpu->cycles <= GPU_CYCLES_PER_SCANL_NTSC);
-    
+
     if (curr_hblank && !prev_hblank) {
         if (gpu->event_cb_table[GPU_EVENT_HBLANK])
             gpu->event_cb_table[GPU_EVENT_HBLANK](gpu);
--- a/psx/dev/ic.c
+++ b/psx/dev/ic.c
@@ -70,9 +70,8 @@
     }
 
     // Emulate acknowledge
-    if (!(ic->stat & ic->mask)) {
+    if (!(ic->stat & ic->mask))
         ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
-    }
 }
 
 void psx_ic_write16(psx_ic_t* ic, uint32_t offset, uint16_t value) {
@@ -84,9 +83,8 @@
     }
 
     // Emulate acknowledge
-    if (!(ic->stat & ic->mask)) {
+    if (!(ic->stat & ic->mask))
         ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
-    }
 }
 
 void psx_ic_write8(psx_ic_t* ic, uint32_t offset, uint8_t value) {
@@ -102,13 +100,15 @@
     }
 
     // Emulate acknowledge
-    if (!(ic->stat & ic->mask)) {
+    if (!(ic->stat & ic->mask))
         ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
-    }
 }
 
 void psx_ic_irq(psx_ic_t* ic, int id) {
     ic->stat |= id;
+
+    // if (ic->mask & (1 << id))
+    //     printf("%u IRQ gone through\n");
 
     if (ic->mask & ic->stat)
         psx_cpu_set_irq_pending(ic->cpu);
--- a/psx/dev/spu.c
+++ b/psx/dev/spu.c
@@ -568,6 +568,9 @@
             spu->data[v].counter &= 0xfff;
             spu->data[v].counter |= sample_index << 12;
 
+            if (spu->data[v].block_flags & 4)
+                spu->data[v].repeat_addr = spu->data[v].current_addr;
+
             switch (spu->data[v].block_flags & 3) {
                 case 0: case 2: {
                     spu->data[v].current_addr += 0x10;
@@ -586,9 +589,6 @@
                     spu->data[v].current_addr = spu->data[v].repeat_addr;
                 } break;
             }
-
-            if (spu->data[v].block_flags & 4)
-                spu->data[v].repeat_addr = spu->data[v].current_addr;
 
             spu_read_block(spu, v);
         }
--- a/psx/dev/timer.c
+++ b/psx/dev/timer.c
@@ -5,53 +5,119 @@
 #include "timer.h"
 #include "../log.h"
 
-#define T0_COUNTER timer->timer[0].counter
-#define T0_PREV timer->timer[0].prev_counter
-#define T0_MODE timer->timer[0].mode
-#define T0_TARGET timer->timer[0].target
-#define T0_PAUSED timer->timer[0].paused
-#define T0_IRQ_FIRED timer->timer[0].irq_fired
+#define T0_COUNTER     timer->timer[0].counter
+#define T0_SYNC_EN     timer->timer[0].sync_enable
+#define T0_SYNC_MODE   timer->timer[0].sync_mode
+#define T0_RESET_TGT   timer->timer[0].reset_target
+#define T0_IRQ_TGT     timer->timer[0].irq_target
+#define T0_IRQ_MAX     timer->timer[0].irq_max
+#define T0_IRQ_REPEAT  timer->timer[0].irq_repeat
+#define T0_IRQ_TOGGLE  timer->timer[0].irq_toggle
+#define T0_CLKSRC      timer->timer[0].clk_source
+#define T0_IRQ         timer->timer[0].irq
+#define T0_TGT_REACHED timer->timer[0].target_reached
+#define T0_MAX_REACHED timer->timer[0].max_reached
+#define T0_IRQ_FIRED   timer->timer[0].irq_fired
+#define T0_PAUSED      timer->timer[0].paused
+#define T0_BLANK_ONCE  timer->timer[0].blank_once
+#define T1_COUNTER     timer->timer[1].counter
+#define T1_SYNC_EN     timer->timer[1].sync_enable
+#define T1_SYNC_MODE   timer->timer[1].sync_mode
+#define T1_RESET_TGT   timer->timer[1].reset_target
+#define T1_IRQ_TGT     timer->timer[1].irq_target
+#define T1_IRQ_MAX     timer->timer[1].irq_max
+#define T1_IRQ_REPEAT  timer->timer[1].irq_repeat
+#define T1_IRQ_TOGGLE  timer->timer[1].irq_toggle
+#define T1_CLKSRC      timer->timer[1].clk_source
+#define T1_IRQ         timer->timer[1].irq
+#define T1_TGT_REACHED timer->timer[1].target_reached
+#define T1_MAX_REACHED timer->timer[1].max_reached
+#define T1_IRQ_FIRED   timer->timer[1].irq_fired
+#define T1_PAUSED      timer->timer[1].paused
+#define T1_BLANK_ONCE  timer->timer[1].blank_once
+#define T2_COUNTER     timer->timer[2].counter
+#define T2_SYNC_EN     timer->timer[2].sync_enable
+#define T2_SYNC_MODE   timer->timer[2].sync_mode
+#define T2_RESET_TGT   timer->timer[2].reset_target
+#define T2_IRQ_TGT     timer->timer[2].irq_target
+#define T2_IRQ_MAX     timer->timer[2].irq_max
+#define T2_IRQ_REPEAT  timer->timer[2].irq_repeat
+#define T2_IRQ_TOGGLE  timer->timer[2].irq_toggle
+#define T2_CLKSRC      timer->timer[2].clk_source
+#define T2_IRQ         timer->timer[2].irq
+#define T2_TGT_REACHED timer->timer[2].target_reached
+#define T2_MAX_REACHED timer->timer[2].max_reached
+#define T2_IRQ_FIRED   timer->timer[2].irq_fired
+#define T2_PAUSED      timer->timer[2].paused
+#define T2_BLANK_ONCE  timer->timer[2].blank_once
+#define T2_DIV_COUNTER timer->timer[2].div_counter
 
-#define T1_COUNTER timer->timer[1].counter
-#define T1_PREV timer->timer[1].prev_counter
-#define T1_MODE timer->timer[1].mode
-#define T1_TARGET timer->timer[1].target
-#define T1_PAUSED timer->timer[1].paused
-#define T1_IRQ_FIRED timer->timer[1].irq_fired
+uint16_t timer_get_mode(psx_timer_t* timer, int index) {
+    uint16_t value = (timer->timer[index].sync_enable << 0)     |
+                     (timer->timer[index].sync_mode << 1)       |
+                     (timer->timer[index].reset_target << 3)    |
+                     (timer->timer[index].irq_target << 4)      |
+                     (timer->timer[index].irq_max << 5)         |
+                     (timer->timer[index].irq_repeat << 6)      |
+                     (timer->timer[index].irq_toggle << 7)      |
+                     (timer->timer[index].clk_source << 8)      |
+                     (timer->timer[index].irq << 10)            |
+                     (timer->timer[index].target_reached << 11) |
+                     (timer->timer[index].max_reached << 12);
 
-#define T2_COUNTER timer->timer[2].counter
-#define T2_PREV timer->timer[2].prev_counter
-#define T2_MODE timer->timer[2].mode
-#define T2_TARGET timer->timer[2].target
-#define T2_PAUSED timer->timer[2].paused
-#define T2_IRQ_FIRED timer->timer[2].irq_fired
+    timer->timer[index].target_reached = 0;
+    timer->timer[index].max_reached = 0;
 
-// bool should_I_pause_the_timer(psx_timer_t* timer) {
-//   if ((timer->mode & 1) == 0) return false;
-//   switch ((timer->mode >> 1) & 3) {
-//     case 0: return gpu.isXblank();
-//     case 1: return false;
-//     case 2: return !gpu.isXblank();
-//     case 3: return gpu.gotXblankOnce();
-//   }
-// }
+    return value;
+}
 
-// bool did_timer_reach_target(Timer timer) {
-//   if ((timer.mode & 8) == 1) return timer.value >= timer.target;
-//   return timer.value >= 0xffff;
-// }
+void timer_set_mode(psx_timer_t* timer, int index, uint16_t value) {
+    timer->timer[index].sync_enable = (value >> 0) & 1;
+    timer->timer[index].sync_mode = (value >> 1) & 3;
+    timer->timer[index].reset_target = (value >> 3) & 1;
+    timer->timer[index].irq_target = (value >> 4) & 1;
+    timer->timer[index].irq_max = (value >> 5) & 1;
+    timer->timer[index].irq_repeat = (value >> 6) & 1;
+    timer->timer[index].irq_toggle = (value >> 7) & 1;
+    timer->timer[index].clk_source = (value >> 8) & 3;
+    timer->timer[index].target_reached = 0;
+    timer->timer[index].max_reached = 0;
 
-// bool should_I_reset_the_timer(Timer timer) {
-//   if (did_timer_reach_target(timer)) return true;
-//   if ((timer.mode & 1) == 0) return false;
-//   switch ((timer.mode >> 1) & 3) {
-//     case 1:
-//     case 2:
-//       return gpu.isXBlank();
-//   }
-//   return false;
-// }
+    // IRQ and counter are set to 0 on mode writes
+    timer->timer[index].irq = 1;
+    timer->timer[index].irq_fired = 0;
+    timer->timer[index].counter = 0;
+    timer->timer[index].div_counter = 0;
+    timer->timer[index].blank_once = 0;
+    timer->timer[index].paused = 0;
 
+    if (index == 2)
+        printf("timer_set_mode %u %04x\n", index, value);
+
+    switch (index) {
+        case 0: {
+            if ((T0_SYNC_MODE == 1) || (T0_SYNC_MODE == 2) || !T0_SYNC_EN)
+                return;
+
+            T0_PAUSED = timer->hblank | (T0_SYNC_MODE == 3);
+        } break;
+
+        case 1: {
+            if ((T1_SYNC_MODE == 1) || (T1_SYNC_MODE == 2) || !T1_SYNC_EN)
+                return;
+
+            T1_PAUSED = timer->vblank | (T1_SYNC_MODE == 3);
+        } break;
+
+        case 2: {
+            if (!T2_SYNC_EN)
+                return;
+
+            T2_PAUSED = (T2_SYNC_MODE == 0) || (T2_SYNC_MODE == 3);
+        } break;
+    }
+}
+
 const char* g_psx_timer_reg_names[] = {
     "counter", 0, 0, 0,
     "mode", 0, 0, 0,
@@ -62,7 +128,7 @@
     return (psx_timer_t*)malloc(sizeof(psx_timer_t));
 }
 
-void psx_timer_init(psx_timer_t* timer, psx_ic_t* ic) {
+void psx_timer_init(psx_timer_t* timer, psx_ic_t* ic, psx_gpu_t* gpu) {
     memset(timer, 0, sizeof(psx_timer_t));
 
     timer->io_base = PSX_TIMER_BEGIN;
@@ -69,30 +135,21 @@
     timer->io_size = PSX_TIMER_SIZE;
 
     timer->ic = ic;
+    timer->gpu = gpu;
 }
 
-int t1_counter = 0;
-
 uint32_t psx_timer_read32(psx_timer_t* timer, uint32_t offset) {
     int index = offset >> 4;
     int reg = offset & 0xf;
 
     switch (reg) {
-        case 0: {
-            return timer->timer[index].counter;
-        } break;
-        case 4: {
-            timer->timer[index].mode &= 0xffffe7ff;
-
-            return timer->timer[index].mode;
-        } break;
+        case 0: return timer->timer[index].counter;
+        case 4: return timer_get_mode(timer, index);
         case 8: return timer->timer[index].target;
     }
 
-    log_fatal("Unhandled 32-bit TIMER read at offset %08x", offset);
+    printf("Unhandled 32-bit TIMER read at offset %08x\n", offset);
 
-    // exit(1);
-
     return 0x0;
 }
 
@@ -102,11 +159,7 @@
 
     switch (reg) {
         case 0: return timer->timer[index].counter;
-        case 4: {
-            timer->timer[index].mode &= 0xffffe7ff;
-
-            return timer->timer[index].mode;
-        } break;
+        case 4: return timer_get_mode(timer, index);
         case 8: return timer->timer[index].target;
     }
 
@@ -121,26 +174,19 @@
     return 0x0;
 }
 
+void timer_handle_irq(psx_timer_t* timer, int i);
+
 void psx_timer_write32(psx_timer_t* timer, uint32_t offset, uint32_t value) {
     int index = offset >> 4;
     int reg = offset & 0xf;
 
     switch (reg) {
-        case 0: {
-            timer->timer[index].counter = value;
-        } return;
-        case 4: {
-            timer->timer[index].mode = value;
-            timer->timer[index].mode |= 0x400;
-            timer->timer[index].irq_fired = 0;
-            timer->timer[index].counter = 0;
-        } return;
-        case 8: timer->timer[index].target = value; return;
+        case 0: timer->timer[index].counter = value & 0xffff; break;
+        case 4: timer_set_mode(timer, index, value); break;
+        case 8: timer->timer[index].target = value & 0xffff; break;
     }
 
-    log_fatal("Unhandled 32-bit TIMER write at offset %08x (%02x)", offset, value);
-
-    // exit(1);
+    timer_handle_irq(timer, index);
 }
 
 void psx_timer_write16(psx_timer_t* timer, uint32_t offset, uint16_t value) {
@@ -148,23 +194,12 @@
     int reg = offset & 0xf;
 
     switch (reg) {
-        case 0: {
-            timer->timer[index].counter = value;
-        } return;
-        case 4: {
-            timer->timer[index].mode = value;
-            timer->timer[index].mode |= 0x400;
-            timer->timer[index].irq_fired = 0;
-            timer->timer[index].counter = 0;
-        } return;
-        case 8: {
-            timer->timer[index].target = value;
-        } return;
+        case 0: timer->timer[index].counter = value & 0xffff; break;
+        case 4: timer_set_mode(timer, index, value); break;
+        case 8: timer->timer[index].target = value & 0xffff; break;
     }
 
-    printf("Unhandled 16-bit TIMER write at offset %08x (%02x)\n", offset, value);
-
-    // exit(1);
+    timer_handle_irq(timer, index);
 }
 
 void psx_timer_write8(psx_timer_t* timer, uint32_t offset, uint8_t value) {
@@ -171,141 +206,148 @@
     printf("Unhandled 8-bit TIMER write at offset %08x (%02x)\n", offset, value);
 }
 
-void timer_update_timer0(psx_timer_t* timer, int cyc) {
-    int reached_target = ((uint32_t)T0_COUNTER + cyc) >= T0_TARGET;
-    int reached_max = ((uint32_t)T0_COUNTER + cyc) >= 0xffff;
+void timer_handle_irq(psx_timer_t* timer, int i) {
+    int irq = 0;
 
-    // Dotclock unsupported
-    // if (T0_MODE & 0x100)
+    int prev_target_reached = timer->timer[i].target_reached;
+    int prev_max_reached = timer->timer[i].max_reached;
+    int target_reached = timer->timer[i].counter > timer->timer[i].target;
+    int max_reached = timer->timer[i].counter > 65535.0f;
 
-    if (!T0_PAUSED)
-        T0_COUNTER += cyc;
+    if (target_reached) {
+        timer->timer[i].target_reached = 1;
 
-    int can_fire_irq = (T0_MODE & MODE_IRQRMD) || !T0_IRQ_FIRED;
+        if (timer->timer[i].reset_target) {
+            timer->timer[i].counter = 0;
+            timer->timer[i].div_counter = 0;
+        }
 
-    int target_irq = reached_target && (T0_MODE & MODE_TGTIRQ);
-    int max_irq = reached_max && (T0_MODE & MODE_MAXIRQ);
+        if (timer->timer[i].irq_target && !prev_target_reached)
+            irq = 1;
+    }
 
-    T0_MODE &= ~0x0800;
-    T0_MODE |= reached_target << 11;
+    if (max_reached) {
+        timer->timer[i].counter -= 65536.0f;
+        timer->timer[i].max_reached = 1;
 
-    T0_MODE &= ~0x1000;
-    T0_MODE |= reached_max << 12;
+        if (timer->timer[i].irq_max && !prev_max_reached)
+            irq = 1;
+    }
 
-    if ((target_irq || max_irq) && can_fire_irq) {
-        if (T0_MODE & MODE_IRQPMD) {
-            T0_MODE ^= 0x400;
+    timer->timer[i].div_counter &= 0xffff;
+
+    if (!irq)
+        return;
+
+    if (!timer->timer[i].irq_toggle) {
+        timer->timer[i].irq = 0;
+    } else {
+        timer->timer[i].irq ^= 1;
+    }
+
+    int trigger = !timer->timer[i].irq;
+
+    if (!timer->timer[i].irq_repeat) {
+        if (trigger && !timer->timer[i].irq_fired) {
+            timer->timer[i].irq_fired = 1;
         } else {
-            T0_MODE |= 0x400;
+            return;
         }
+    }
 
-        timer->timer[0].irq_fired = 1;
+    timer->timer[i].irq = 1;
 
-        psx_ic_irq(timer->ic, IC_TIMER0);
+    if (trigger) {
+        // printf("timer %u irq fire\n", i);
+   
+        psx_ic_irq(timer->ic, 16 << i);
     }
+}
 
-    if (T0_MODE & MODE_RESETC) {
-        if (reached_target)
-            T0_COUNTER -= T0_TARGET;
+float timer_get_dotclock_div(psx_timer_t* timer) {
+    static const float dmode_dotclk_div_table[] = {
+        10.0f, 8.0f, 5.0f, 4.0f
+    };
+
+    if (timer->gpu->display_mode & 0x40) {
+        return 11.0f / 7.0f / 7.0f;
+    } else {
+        return 11.0f / 7.0f / dmode_dotclk_div_table[timer->gpu->display_mode & 0x3];
     }
 }
 
-void timer_update_timer1(psx_timer_t* timer, int cyc) {
-    int reached_target, reached_max;
+void timer_update_timer0(psx_timer_t* timer, int cyc) {
+    if (T0_PAUSED)
+        return;
 
-    if (T1_MODE & 0x100) {
-        reached_target = T1_COUNTER == T1_TARGET;
-        reached_max = T1_COUNTER == 0xffff;
+    if (T0_CLKSRC & 1) {
+        // Dotclock test
+        T0_COUNTER += (float)cyc * timer_get_dotclock_div(timer);
     } else {
-        reached_target = ((uint32_t)T1_COUNTER + cyc) >= T1_TARGET;
-        reached_max = ((uint32_t)T1_COUNTER + cyc) >= 0xffff;
-
-        if (!T1_PAUSED)
-            T1_COUNTER += cyc;
+        T0_COUNTER += (float)cyc;
     }
-    
-    int can_fire_irq = (T1_MODE & MODE_IRQRMD) || !T1_IRQ_FIRED;
 
-    int target_irq = reached_target && (T1_MODE & MODE_TGTIRQ);
-    int max_irq = reached_max && (T1_MODE & MODE_MAXIRQ);
+    timer_handle_irq(timer, 0);
+}
 
-    T1_MODE &= ~0x0800;
-    T1_MODE |= reached_target << 11;
+void timer_update_timer1(psx_timer_t* timer, int cyc) {
+    if (T1_PAUSED)
+        return;
 
-    T1_MODE &= ~0x1000;
-    T1_MODE |= reached_max << 12;
-
-    if ((target_irq || max_irq) && can_fire_irq) {
-        if (T1_MODE & MODE_IRQPMD) {
-            T1_MODE ^= 0x400;
-        } else {
-            T1_MODE |= 0x400;
-        }
-
-        T1_IRQ_FIRED = 1;
-
-        psx_ic_irq(timer->ic, IC_TIMER1);
+    if (T1_CLKSRC & 1) {
+        // Counter is incremented in our hblank callback
+    } else {
+        T1_COUNTER += (float)cyc;
     }
 
-    if (T1_MODE & MODE_RESETC) {
-        if (reached_target)
-            T1_COUNTER -= T1_TARGET;
-    }
+    timer_handle_irq(timer, 1);
 }
 
 void timer_update_timer2(psx_timer_t* timer, int cyc) {
-    T2_PREV = T2_COUNTER;
+    if (T2_PAUSED)
+        return;
 
-    if (!T2_PAUSED)
+    if (T2_CLKSRC <= 1) {
         T2_COUNTER += cyc;
-
-    uint16_t reset = (T2_MODE & MODE_RESETC) ? T2_TARGET : 0xffff;
-
-    if ((T2_COUNTER >= reset) && (T2_PREV <= reset)) {
-        T2_COUNTER -= reset;
-        T2_MODE |= 0x800;
-
-        if (reset == 0xffff)
-            T2_MODE |= 0x1000;
+    } else {
+        T2_COUNTER += ((float)cyc) * (1.0f / 8.0f);
+        // T2_COUNTER = T2_DIV_COUNTER >> 3;
     }
 
-    if ((T2_COUNTER >= 0xffff) && (T2_PREV <= 0xffff)) {
-        T2_COUNTER &= 0xffff;
-        T2_MODE |= 0x1000;
-    }
+    timer_handle_irq(timer, 2);
 }
 
 void psx_timer_update(psx_timer_t* timer, int cyc) {
-    timer_update_timer0(timer, cyc);
-    timer_update_timer1(timer, cyc);
-    timer_update_timer2(timer, cyc);
+    timer->prev_hblank = timer->hblank;
+    timer->prev_vblank = timer->vblank;
+
+    timer_update_timer0(timer, 2);
+    timer_update_timer1(timer, 2);
+    timer_update_timer2(timer, 2);
 }
 
 void psxe_gpu_hblank_event_cb(psx_gpu_t* gpu) {
     psx_timer_t* timer = gpu->udata[1];
 
-    if (T1_MODE & 0x100 && !T1_PAUSED)
-        T1_COUNTER++;
+    timer->hblank = 1;
 
-    if (T0_MODE & MODE_SYNCEN) {
-        switch (T0_MODE & 6) {
-            case 0: {
-                T0_PAUSED = 1;
-            } break;
+    if ((T1_CLKSRC & 1) && !T1_PAUSED)
+        ++T1_COUNTER;
 
-            case 2: {
-                T0_COUNTER = 0;
-            } break;
+    if (!T0_SYNC_EN)
+        return;
 
-            case 4: {
-                T0_COUNTER = 0;
+    switch (T0_SYNC_MODE) {
+        case 0: T0_PAUSED = 1; break;
+        case 1: T0_COUNTER = 0; break;
+        case 2: T0_COUNTER = 0; T0_PAUSED = 0; break;
+        case 3: {
+            if (!T0_BLANK_ONCE) {
+                T0_BLANK_ONCE = 1;
+                T0_SYNC_EN = 0;
                 T0_PAUSED = 0;
-            } break;
-
-            case 6: {
-                T0_MODE &= ~MODE_SYNCEN;
-            } break;
-        }
+            }
+        } break;
     }
 }
 
@@ -312,16 +354,14 @@
 void psxe_gpu_hblank_end_event_cb(psx_gpu_t* gpu) {
     psx_timer_t* timer = gpu->udata[1];
 
-    if (T0_MODE & MODE_SYNCEN) {
-        switch (T0_MODE & 6) {
-            case 0: {
-                T0_PAUSED = 0;
-            } break;
+    timer->hblank = 0;
 
-            case 4: {
-                T0_PAUSED = 1;
-            } break;
-        }
+    if (!T0_SYNC_EN)
+        return;
+
+    switch (T0_SYNC_MODE) {
+        case 0: T0_PAUSED = 0; break;
+        case 2: T0_PAUSED = 1; break;
     }
 }
 
@@ -328,25 +368,22 @@
 void psxe_gpu_vblank_timer_event_cb(psx_gpu_t* gpu) {
     psx_timer_t* timer = gpu->udata[1];
 
-    if (T1_MODE & MODE_SYNCEN) {
-        switch (T1_MODE & 6) {
-            case 0: {
-                T1_PAUSED = 1;
-            } break;
+    timer->vblank = 1;
 
-            case 2: {
-                T1_COUNTER = 0;
-            } break;
+    if (!T1_SYNC_EN)
+        return;
 
-            case 4: {
-                T1_COUNTER = 0;
+    switch (T1_SYNC_MODE) {
+        case 0: T1_PAUSED = 1; break;
+        case 1: T1_COUNTER = 0; break;
+        case 2: T1_COUNTER = 0; T1_PAUSED = 0; break;
+        case 3: {
+            if (!T1_BLANK_ONCE) {
+                T1_BLANK_ONCE = 1;
+                T1_SYNC_EN = 0;
                 T1_PAUSED = 0;
-            } break;
-
-            case 6: {
-                T1_MODE &= ~MODE_SYNCEN;
-            } break;
-        }
+            }
+        } break;
     }
 }
 
@@ -353,16 +390,14 @@
 void psxe_gpu_vblank_end_event_cb(psx_gpu_t* gpu) {
     psx_timer_t* timer = gpu->udata[1];
 
-    if (T1_MODE & MODE_SYNCEN) {
-        switch (T1_MODE & 6) {
-            case 0: {
-                T1_PAUSED = 0;
-            } break;
+    timer->vblank = 0;
 
-            case 4: {
-                T1_PAUSED = 1;
-            } break;
-        }
+    if (!T1_SYNC_EN)
+        return;
+
+    switch (T1_SYNC_MODE) {
+        case 0: T1_PAUSED = 0; break;
+        case 2: T1_PAUSED = 1; break;
     }
 }
 
--- a/psx/dev/timer.h
+++ b/psx/dev/timer.h
@@ -57,24 +57,69 @@
 #define MODE_IRQPMD 0x0080
 #define MODE_CLK 0x0080
 
+/*
+  0     Synchronization Enable (0=Free Run, 1=Synchronize via Bit1-2)
+  1-2   Synchronization Mode   (0-3, see lists below)
+         Synchronization Modes for Counter 0:
+           0 = Pause counter during Hblank(s)
+           1 = Reset counter to 0000h at Hblank(s)
+           2 = Reset counter to 0000h at Hblank(s) and pause outside of Hblank
+           3 = Pause until Hblank occurs once, then switch to Free Run
+         Synchronization Modes for Counter 1:
+           Same as above, but using Vblank instead of Hblank
+         Synchronization Modes for Counter 2:
+           0 or 3 = Stop counter at current value (forever, no h/v-blank start)
+           1 or 2 = Free Run (same as when Synchronization Disabled)
+  3     Reset counter to 0000h  (0=After Counter=FFFFh, 1=After Counter=Target)
+  4     IRQ when Counter=Target (0=Disable, 1=Enable)
+  5     IRQ when Counter=FFFFh  (0=Disable, 1=Enable)
+  6     IRQ Once/Repeat Mode    (0=One-shot, 1=Repeatedly)
+  7     IRQ Pulse/Toggle Mode   (0=Short Bit10=0 Pulse, 1=Toggle Bit10 on/off)
+  8-9   Clock Source (0-3, see list below)
+         Counter 0:  0 or 2 = System Clock,  1 or 3 = Dotclock
+         Counter 1:  0 or 2 = System Clock,  1 or 3 = Hblank
+         Counter 2:  0 or 1 = System Clock,  2 or 3 = System Clock/8
+  10    Interrupt Request       (0=Yes, 1=No) (Set after Writing)    (W=1) (R)
+  11    Reached Target Value    (0=No, 1=Yes) (Reset after Reading)        (R)
+  12    Reached FFFFh Value     (0=No, 1=Yes) (Reset after Reading)        (R)
+  13-15 Unknown (seems to be always zero)
+  16-31 Garbage (next opcode)
+*/
+
 typedef struct {
     uint32_t bus_delay;
     uint32_t io_base, io_size;
 
     psx_ic_t* ic;
+    psx_gpu_t* gpu;
 
+    int hblank, prev_hblank;
+    int vblank, prev_vblank;
+
     struct {
-        uint16_t prev_counter;
-        uint16_t counter;
-        uint32_t mode;
+        float counter;
         uint32_t target;
-        int paused;
+        int sync_enable;
+        int sync_mode;
+        int reset_target;
+        int irq_target;
+        int irq_max;
+        int irq_repeat;
+        int irq_toggle;
+        int clk_source;
+        int irq;
+        int target_reached;
+        int max_reached;
         int irq_fired;
+        uint32_t div_counter;
+
+        int paused;
+        int blank_once;
     } timer[3];
 } psx_timer_t;
 
 psx_timer_t* psx_timer_create(void);
-void psx_timer_init(psx_timer_t*, psx_ic_t*);
+void psx_timer_init(psx_timer_t*, psx_ic_t*, psx_gpu_t*);
 uint32_t psx_timer_read32(psx_timer_t*, uint32_t);
 uint16_t psx_timer_read16(psx_timer_t*, uint32_t);
 uint8_t psx_timer_read8(psx_timer_t*, uint32_t);
--- a/psx/psx.c
+++ b/psx/psx.c
@@ -175,7 +175,7 @@
     psx_scratchpad_init(psx->scratchpad);
     psx_gpu_init(psx->gpu, psx->ic);
     psx_spu_init(psx->spu);
-    psx_timer_init(psx->timer, psx->ic);
+    psx_timer_init(psx->timer, psx->ic, psx->gpu);
     psx_cdrom_init(psx->cdrom, psx->ic);
     psx_pad_init(psx->pad, psx->ic);
     psx_mdec_init(psx->mdec);
--