shithub: psxe

Download patch

ref: d2646082f757aa54bf299e06ebe1cd7ea85cad00
parent: b043b943449c64f14c220d78fe731cfa47143031
parent: 9a76a94d5146753d5d98dd130939c6528fe0f257
author: Lisandro Alarcón <lisandroaalarcon@gmail.com>
date: Thu Jul 18 08:28:27 EDT 2024

Merge pull request #3 from allkern/cdrom-rewrite

Merging CDROM core rewrite

--- a/build-win64.ps1
+++ b/build-win64.ps1
@@ -16,8 +16,8 @@
     -I"$($SDL2_DIR)\include\SDL2" `
     "psx\*.c" `
     "psx\dev\*.c" `
+    "psx\dev\cdrom\*.c" `
     "psx\input\*.c" `
-    "psx\disc\*.c" `
     "frontend\*.c" `
     -o "bin\psxe.exe" `
     -DREP_VERSION="`"$($VERSION_TAG)`"" `
--- a/frontend/main.c
+++ b/frontend/main.c
@@ -1,7 +1,7 @@
 #include "../psx/psx.h"
 #include "../psx/input/sda.h"
 #include "../psx/input/guncon.h"
-#include "../psx/disc/cue.h"
+#include "../psx/dev/cdrom/cdrom.h"
 
 #include "screen.h"
 #include "config.h"
@@ -12,7 +12,10 @@
     psx_cdrom_t* cdrom = ((psx_t*)ud)->cdrom;
     psx_spu_t* spu = ((psx_t*)ud)->spu;
 
-    psx_cdrom_get_cdda_samples(cdrom, buf, size, spu);
+    memset(buf, 0, size);
+
+    psx_cdrom_get_audio_samples(cdrom, buf, size);
+    psx_spu_update_cdda_buffer(spu, cdrom->cdda_buf);
 
     for (int i = 0; i < (size >> 2); i++) {
         uint32_t sample = psx_spu_get_sample(spu);
--- a/frontend/screen.c
+++ b/frontend/screen.c
@@ -57,7 +57,13 @@
 int screen_get_base_width(psxe_screen_t* screen) {
     int width = psx_get_dmode_width(screen->psx);
 
-    return (width == 256) ? 256 : 320;
+    switch (width) {
+        case 256: return 256;
+        case 320: return 320;
+        case 368: return 384;
+    }
+
+    return 320;
 }
 
 psxe_screen_t* psxe_screen_create(void) {
--- a/psx/bus.c
+++ b/psx/bus.c
@@ -105,7 +105,7 @@
         return 0x05;
 
     if (addr == 0x1f400004)
-        return 0xc0;
+        return 0xc8;
 
     if (addr == 0x1f400006)
         return 0x1fe0;
--- a/psx/bus_init.h
+++ b/psx/bus_init.h
@@ -1,6 +1,7 @@
 #ifndef BUS_INIT_H
 #define BUS_INIT_H
 
+#include "dev/cdrom/cdrom.h"
 #include "dev/bios.h"
 #include "dev/ram.h"
 #include "dev/dma.h"
@@ -14,7 +15,6 @@
 #include "dev/gpu.h"
 #include "dev/spu.h"
 #include "dev/timer.h"
-#include "dev/cdrom.h"
 #include "dev/pad.h"
 #include "dev/mdec.h"
 
--- a/psx/cpu.c
+++ b/psx/cpu.c
@@ -170,11 +170,11 @@
 #define R_A0 (cpu->r[4])
 #define R_RA (cpu->r[31])
 
-#define DO_PENDING_LOAD \
+#define DO_PENDING_LOAD { \
     cpu->r[cpu->load_d] = cpu->load_v; \
     R_R0 = 0; \
     cpu->load_v = 0xffffffff; \
-    cpu->load_d = 0;
+    cpu->load_d = 0; }
 
 #define SE8(v) ((int32_t)((int8_t)v))
 #define SE16(v) ((int32_t)((int16_t)v))
@@ -295,6 +295,15 @@
     cpu->next_pc += 4;
 
     if (psx_cpu_check_irq(cpu)) {
+        // GTE instructions "win" over interrupts
+        if ((cpu->opcode & 0xfe000000) == 0x4a000000)
+            g_psx_cpu_primary_table[OP](cpu);
+
+        cpu->r[0] = 0;
+
+        cpu->last_cycles = 2;
+        cpu->total_cycles += cpu->last_cycles;
+
         psx_cpu_exception(cpu, CAUSE_INT);
 
         return;
@@ -326,9 +335,6 @@
         cpu->cop0_r[COP0_CAUSE] |= 0x80000000;
     }
 
-    if ((cause == CAUSE_INT) && ((cpu->opcode & 0xfe000000) == 0x4a000000))
-        cpu->cop0_r[COP0_EPC] += 4;
-
     // Do exception stack push
     uint32_t mode = cpu->cop0_r[COP0_SR] & 0x3f;
 
@@ -603,7 +609,8 @@
 
     uint32_t s = cpu->r[S];
 
-    DO_PENDING_LOAD;
+    if (cpu->load_d != T)
+        DO_PENDING_LOAD;
 
     cpu->load_d = T;
     cpu->load_v = SE8(psx_bus_read8(cpu->bus, s + IMM16S));
@@ -614,7 +621,8 @@
 
     uint32_t s = cpu->r[S];
 
-    DO_PENDING_LOAD;
+    if (cpu->load_d != T)
+        DO_PENDING_LOAD;
 
     uint32_t addr = s + IMM16S;
 
@@ -636,11 +644,12 @@
     uint32_t addr = s + IMM16S;
     uint32_t load = psx_bus_read32(cpu->bus, addr & 0xfffffffc);
 
-    if (rt == cpu->load_d)
+    if (rt == cpu->load_d) {
         t = cpu->load_v;
+    } else {
+        DO_PENDING_LOAD;
+    }
 
-    DO_PENDING_LOAD;
-
     int shift = (int)((addr & 0x3) << 3);
     uint32_t mask = (uint32_t)0x00FFFFFF >> shift;
     uint32_t value = (t & mask) | (load << (24 - shift)); 
@@ -648,8 +657,8 @@
     cpu->load_d = rt;
     cpu->load_v = value;
 
-    // printf("lwl rt=%u s=%08x t=%08x addr=%08x load=%08x (%08x) tp=%08x shift=%u mask=%08x value=%08x\n",
-    //     rt, s, t, addr, load, addr & 0xfffffffc, tp, shift, mask, value
+    // printf("lwl rt=%u s=%08x t=%08x addr=%08x load=%08x (%08x) shift=%u mask=%08x value=%08x\n",
+    //     rt, s, t, addr, load, addr & 0xfffffffc, shift, mask, value
     // );
 }
 
@@ -659,7 +668,8 @@
     uint32_t s = cpu->r[S];
     uint32_t addr = s + IMM16S;
 
-    DO_PENDING_LOAD;
+    if (cpu->load_d != T)
+        DO_PENDING_LOAD;
 
     if (addr & 0x3) {
         psx_cpu_exception(cpu, CAUSE_ADEL);
@@ -674,7 +684,8 @@
 
     uint32_t s = cpu->r[S];
 
-    DO_PENDING_LOAD;
+    if (cpu->load_d != T)
+        DO_PENDING_LOAD;
 
     cpu->load_d = T;
     cpu->load_v = psx_bus_read8(cpu->bus, s + IMM16S);
@@ -686,7 +697,8 @@
     uint32_t s = cpu->r[S];
     uint32_t addr = s + IMM16S;
 
-    DO_PENDING_LOAD;
+    if (cpu->load_d != T)
+        DO_PENDING_LOAD;
 
     if (addr & 0x1) {
         psx_cpu_exception(cpu, CAUSE_ADEL);
@@ -706,11 +718,12 @@
     uint32_t addr = s + IMM16S;
     uint32_t load = psx_bus_read32(cpu->bus, addr & 0xfffffffc);
 
-    if (rt == cpu->load_d)
+    if (rt == cpu->load_d) {
         t = cpu->load_v;
+    } else {
+        DO_PENDING_LOAD;
+    }
 
-    DO_PENDING_LOAD;
-
     int shift = (int)((addr & 0x3) << 3);
     uint32_t mask = 0xFFFFFF00 << (24 - shift);
     uint32_t value = (t & mask) | (load >> shift); 
@@ -718,8 +731,8 @@
     cpu->load_d = rt;
     cpu->load_v = value;
 
-    // printf("lwl rt=%u s=%08x t=%08x addr=%08x load=%08x (%08x) tp=%08x shift=%u mask=%08x value=%08x\n",
-    //     rt, s, t, addr, load, addr & 0xfffffffc, tp, shift, mask, value
+    // printf("lwr rt=%u s=%08x t=%08x addr=%08x load=%08x (%08x) shift=%u mask=%08x value=%08x\n",
+    //     rt, s, t, addr, load, addr & 0xfffffffc, shift, mask, value
     // );
 }
 
@@ -1486,12 +1499,14 @@
     return (int32_t)(((value << 20) >> 20) >> cpu->gte_sf);
 }
 
-void gte_check_mac(psx_cpu_t* cpu, int i, int64_t value) {
+int64_t gte_check_mac(psx_cpu_t* cpu, int i, int64_t value) {
     if (value < -0x80000000000ll) {
         R_FLAG |= 0x8000000 >> (i - 1);
     } else if (value > 0x7ffffffffffll) {
         R_FLAG |= 0x40000000 >> (i - 1);
     }
+
+    return (value << 20) >> 20;
 }
 
 int32_t gte_clamp_ir0(psx_cpu_t* cpu, int32_t value) {
@@ -1550,7 +1565,7 @@
     return (uint8_t)value;
 }
 
-int32_t gte_clamp_ir(psx_cpu_t* cpu, int i, int value, int lm) {
+int32_t gte_clamp_ir(psx_cpu_t* cpu, int i, int64_t value, int lm) {
     if (lm && (value < 0)) {
         R_FLAG |= (uint32_t)(0x1000000 >> (i - 1));
 
@@ -1714,13 +1729,12 @@
 #define R_LB3 cpu->cop2_cr.lr.m33
 
 #define GTE_RTP_DQ(i) { \
-    R_FLAG = 0; \
     int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
     int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
     int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
-    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_TRX) << 12) + (I64((int16_t)R_RT11) * vx) + (I64((int16_t)R_RT12) * vy) + (I64((int16_t)R_RT13) * vz)); \
-    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_TRY) << 12) + (I64((int16_t)R_RT21) * vx) + (I64((int16_t)R_RT22) * vy) + (I64((int16_t)R_RT23) * vz)); \
-    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_TRZ) << 12) + (I64((int16_t)R_RT31) * vx) + (I64((int16_t)R_RT32) * vy) + (I64((int16_t)R_RT33) * vz)); \
+    R_MAC1 = gte_clamp_mac(cpu, 1, gte_check_mac(cpu, 1, gte_check_mac(cpu, 1, (((int64_t)R_TRX) << 12) + (I64((int16_t)R_RT11) * vx)) + (I64((int16_t)R_RT12) * vy)) + (I64((int16_t)R_RT13) * vz)); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, gte_check_mac(cpu, 2, gte_check_mac(cpu, 2, (((int64_t)R_TRY) << 12) + (I64((int16_t)R_RT21) * vx)) + (I64((int16_t)R_RT22) * vy)) + (I64((int16_t)R_RT23) * vz)); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, gte_check_mac(cpu, 3, gte_check_mac(cpu, 3, (((int64_t)R_TRZ) << 12) + (I64((int16_t)R_RT31) * vx)) + (I64((int16_t)R_RT32) * vy)) + (I64((int16_t)R_RT33) * vz)); \
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
     R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
@@ -1737,13 +1751,12 @@
     R_IR0 = gte_clamp_ir0(cpu, cpu->s_mac0 >> 12); }
 
 #define GTE_RTP(i) { \
-    R_FLAG = 0; \
     int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
     int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
     int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
-    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_TRX) << 12) + (I64((int16_t)R_RT11) * vx) + (I64((int16_t)R_RT12) * vy) + (I64((int16_t)R_RT13) * vz)); \
-    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_TRY) << 12) + (I64((int16_t)R_RT21) * vx) + (I64((int16_t)R_RT22) * vy) + (I64((int16_t)R_RT23) * vz)); \
-    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_TRZ) << 12) + (I64((int16_t)R_RT31) * vx) + (I64((int16_t)R_RT32) * vy) + (I64((int16_t)R_RT33) * vz)); \
+    R_MAC1 = gte_clamp_mac(cpu, 1, gte_check_mac(cpu, 1, gte_check_mac(cpu, 1, (((int64_t)R_TRX) << 12) + (I64((int16_t)R_RT11) * vx)) + (I64((int16_t)R_RT12) * vy)) + (I64((int16_t)R_RT13) * vz)); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, gte_check_mac(cpu, 2, gte_check_mac(cpu, 2, (((int64_t)R_TRY) << 12) + (I64((int16_t)R_RT21) * vx)) + (I64((int16_t)R_RT22) * vy)) + (I64((int16_t)R_RT23) * vz)); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, gte_check_mac(cpu, 3, gte_check_mac(cpu, 3, (((int64_t)R_TRZ) << 12) + (I64((int16_t)R_RT31) * vx)) + (I64((int16_t)R_RT32) * vy)) + (I64((int16_t)R_RT33) * vz)); \
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
     R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
@@ -1758,7 +1771,6 @@
     R_SY2 = gte_clamp_sxy(cpu, 2, (gte_clamp_mac0(cpu, (int64_t)((int32_t)R_OFY) + ((int64_t)R_IR2 * div)) >> 16)); }
 
 #define DPCT1 { \
-    R_FLAG = 0; \
     int64_t mac1 = gte_clamp_mac(cpu, 1, (((int64_t)R_RFC) << 12) - (((int64_t)cpu->cop2_dr.rgb[0].c[0]) << 16)); \
     int64_t mac2 = gte_clamp_mac(cpu, 2, (((int64_t)R_GFC) << 12) - (((int64_t)cpu->cop2_dr.rgb[0].c[1]) << 16)); \
     int64_t mac3 = gte_clamp_mac(cpu, 3, (((int64_t)R_BFC) << 12) - (((int64_t)cpu->cop2_dr.rgb[0].c[2]) << 16)); \
@@ -1779,28 +1791,24 @@
     R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4); }
 
 #define NCCS(i) { \
-    R_FLAG = 0; \
     int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
     int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
     int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
-    R_MAC1 = (int)(gte_clamp_mac(cpu, 1, (int64_t)R_L11 * vx + R_L12 * vy + R_L13 * vz)); \
-    R_MAC2 = (int)(gte_clamp_mac(cpu, 2, (int64_t)R_L21 * vx + R_L22 * vy + R_L23 * vz)); \
-    R_MAC3 = (int)(gte_clamp_mac(cpu, 3, (int64_t)R_L31 * vx + R_L32 * vy + R_L33 * vz)); \
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_L11) * vx) + (I64(R_L12) * vy) + (I64(R_L13) * vz)); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_L21) * vx) + (I64(R_L22) * vy) + (I64(R_L23) * vz)); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_L31) * vx) + (I64(R_L32) * vy) + (I64(R_L33) * vz)); \
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
-    R_MAC1 = (int)(gte_clamp_mac(cpu, 1, gte_clamp_mac(cpu, 1, gte_clamp_mac(cpu, 1, (long)R_RBK * 0x1000 + R_LR1 * R_IR1) + (long)R_LG1 * R_IR2) + (long)R_LB1 * R_IR3)); \
-    R_MAC2 = (int)(gte_clamp_mac(cpu, 2, gte_clamp_mac(cpu, 2, gte_clamp_mac(cpu, 2, (long)R_GBK * 0x1000 + R_LR2 * R_IR1) + (long)R_LG2 * R_IR2) + (long)R_LB2 * R_IR3)); \
-    R_MAC3 = (int)(gte_clamp_mac(cpu, 3, gte_clamp_mac(cpu, 3, gte_clamp_mac(cpu, 3, (long)R_BBK * 0x1000 + R_LR3 * R_IR1) + (long)R_LG3 * R_IR2) + (long)R_LB3 * R_IR3)); \
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm); \
+    R_MAC1 = gte_clamp_mac(cpu, 1, gte_check_mac(cpu, 1, gte_check_mac(cpu, 1, (I64(R_RBK) << 12) + (I64(R_LR1) * I64(R_IR1))) + (I64(R_LR2) * I64(R_IR2))) + (I64(R_LR3) * I64(R_IR3))); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, gte_check_mac(cpu, 2, gte_check_mac(cpu, 2, (I64(R_GBK) << 12) + (I64(R_LG1) * I64(R_IR1))) + (I64(R_LG2) * I64(R_IR2))) + (I64(R_LG3) * I64(R_IR3))); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, gte_check_mac(cpu, 3, gte_check_mac(cpu, 3, (I64(R_BBK) << 12) + (I64(R_LB1) * I64(R_IR1))) + (I64(R_LB2) * I64(R_IR2))) + (I64(R_LB3) * I64(R_IR3))); \
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
-    R_MAC1 = (int)gte_clamp_mac(cpu, 1, (R_RGB0 * R_IR1) << 4); \
-    R_MAC2 = (int)gte_clamp_mac(cpu, 2, (R_RGB1 * R_IR2) << 4); \
-    R_MAC3 = (int)gte_clamp_mac(cpu, 3, (R_RGB2 * R_IR3) << 4); \
-    R_MAC1 = (int)gte_clamp_mac(cpu, 1, R_MAC1); \
-    R_MAC2 = (int)gte_clamp_mac(cpu, 2, R_MAC2); \
-    R_MAC3 = (int)gte_clamp_mac(cpu, 3, R_MAC3); \
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm); \
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_RC) * I64(R_IR1)) << 4); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_GC) * I64(R_IR2)) << 4); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_BC) * I64(R_IR3)) << 4); \
     R_RGB0 = R_RGB1; \
     R_RGB1 = R_RGB2; \
     R_CD2 = R_CODE; \
@@ -1809,25 +1817,24 @@
     R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4); \
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); }
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm); }
 
 #define NCS(i) { \
-    R_FLAG = 0; \
     int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
     int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
     int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
-    R_MAC1 = (int)(gte_clamp_mac(cpu, 1, (int64_t)R_L11 * vx + R_L12 * vy + R_L13 * vz)); \
-    R_MAC2 = (int)(gte_clamp_mac(cpu, 2, (int64_t)R_L21 * vx + R_L22 * vy + R_L23 * vz)); \
-    R_MAC3 = (int)(gte_clamp_mac(cpu, 3, (int64_t)R_L31 * vx + R_L32 * vy + R_L33 * vz)); \
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_L11) * vx) + (I64(R_L12) * vy) + (I64(R_L13) * vz)); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_L21) * vx) + (I64(R_L22) * vy) + (I64(R_L23) * vz)); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_L31) * vx) + (I64(R_L32) * vy) + (I64(R_L33) * vz)); \
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
-    R_MAC1 = (int)(gte_clamp_mac(cpu, 1, gte_clamp_mac(cpu, 1, gte_clamp_mac(cpu, 1, (long)R_RBK * 0x1000 + R_LR1 * R_IR1) + (long)R_LG1 * R_IR2) + (long)R_LB1 * R_IR3)); \
-    R_MAC2 = (int)(gte_clamp_mac(cpu, 2, gte_clamp_mac(cpu, 2, gte_clamp_mac(cpu, 2, (long)R_GBK * 0x1000 + R_LR2 * R_IR1) + (long)R_LG2 * R_IR2) + (long)R_LB2 * R_IR3)); \
-    R_MAC3 = (int)(gte_clamp_mac(cpu, 3, gte_clamp_mac(cpu, 3, gte_clamp_mac(cpu, 3, (long)R_BBK * 0x1000 + R_LR3 * R_IR1) + (long)R_LG3 * R_IR2) + (long)R_LB3 * R_IR3)); \
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm); \
+    R_MAC1 = gte_clamp_mac(cpu, 1, gte_check_mac(cpu, 1, gte_check_mac(cpu, 1, (I64(R_RBK) << 12) + (I64(R_LR1) * I64(R_IR1))) + (I64(R_LR2) * I64(R_IR2))) + (I64(R_LR3) * I64(R_IR3))); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, gte_check_mac(cpu, 2, gte_check_mac(cpu, 2, (I64(R_GBK) << 12) + (I64(R_LG1) * I64(R_IR1))) + (I64(R_LG2) * I64(R_IR2))) + (I64(R_LG3) * I64(R_IR3))); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, gte_check_mac(cpu, 3, gte_check_mac(cpu, 3, (I64(R_BBK) << 12) + (I64(R_LB1) * I64(R_IR1))) + (I64(R_LB2) * I64(R_IR2))) + (I64(R_LB3) * I64(R_IR3))); \
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); \
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm); \
     R_RGB0 = R_RGB1; \
     R_RGB1 = R_RGB2; \
     R_CD2 = R_CODE; \
@@ -1836,10 +1843,9 @@
     R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4); \
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm); }
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm); }
 
 #define NCDS(i) { \
-    R_FLAG = 0; \
     int64_t vx = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[0]); \
     int64_t vy = (int64_t)((int16_t)cpu->cop2_dr.v[i].p[1]); \
     int64_t vz = (int64_t)cpu->cop2_dr.v[i].z; \
@@ -1849,21 +1855,9 @@
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
     R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm); \
-    gte_check_mac(cpu, 1, ((I64(R_RBK) << 12) + (I64(R_LR1) * I64(R_IR1)))); \
-    gte_check_mac(cpu, 2, ((I64(R_GBK) << 12) + (I64(R_LG1) * I64(R_IR1)))); \
-    gte_check_mac(cpu, 3, ((I64(R_BBK) << 12) + (I64(R_LB1) * I64(R_IR1)))); \
-    gte_check_mac(cpu, 1, I64(R_LR2) * I64(R_IR2)); \
-    gte_check_mac(cpu, 2, I64(R_LG2) * I64(R_IR2)); \
-    gte_check_mac(cpu, 3, I64(R_LB2) * I64(R_IR2)); \
-    gte_check_mac(cpu, 1, I64(R_LR3) * I64(R_IR3)); \
-    gte_check_mac(cpu, 2, I64(R_LG3) * I64(R_IR3)); \
-    gte_check_mac(cpu, 3, I64(R_LB3) * I64(R_IR3)); \
-    R_MAC1 = gte_clamp_mac(cpu, 1, ((I64(R_RBK) << 12) + (I64(R_LR1) * I64(R_IR1))) + (I64(R_LR2) * I64(R_IR2)) + (I64(R_LR3) * I64(R_IR3))); \
-    R_MAC2 = gte_clamp_mac(cpu, 2, ((I64(R_GBK) << 12) + (I64(R_LG1) * I64(R_IR1))) + (I64(R_LG2) * I64(R_IR2)) + (I64(R_LG3) * I64(R_IR3))); \
-    R_MAC3 = gte_clamp_mac(cpu, 3, ((I64(R_BBK) << 12) + (I64(R_LB1) * I64(R_IR1))) + (I64(R_LB2) * I64(R_IR2)) + (I64(R_LB3) * I64(R_IR3))); \
-    /* R_MAC1 = gte_clamp_mac(cpu, 1, () + () + ()); */ \
-    /* R_MAC2 = gte_clamp_mac(cpu, 2, () + () + ()); */ \
-    /* R_MAC3 = gte_clamp_mac(cpu, 3, () + () + ()); */ \
+    R_MAC1 = gte_clamp_mac(cpu, 1, gte_check_mac(cpu, 1, gte_check_mac(cpu, 1, (I64(R_RBK) << 12) + (I64(R_LR1) * I64(R_IR1))) + (I64(R_LR2) * I64(R_IR2))) + (I64(R_LR3) * I64(R_IR3))); \
+    R_MAC2 = gte_clamp_mac(cpu, 2, gte_check_mac(cpu, 2, gte_check_mac(cpu, 2, (I64(R_GBK) << 12) + (I64(R_LG1) * I64(R_IR1))) + (I64(R_LG2) * I64(R_IR2))) + (I64(R_LG3) * I64(R_IR3))); \
+    R_MAC3 = gte_clamp_mac(cpu, 3, gte_check_mac(cpu, 3, gte_check_mac(cpu, 3, (I64(R_BBK) << 12) + (I64(R_LB1) * I64(R_IR1))) + (I64(R_LB2) * I64(R_IR2))) + (I64(R_LB3) * I64(R_IR3))); \
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm); \
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm); \
     R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm); \
@@ -1883,25 +1877,44 @@
     R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4); \
     R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4); }
 
-void gte_interpolate_color(psx_cpu_t* cpu, uint64_t mac1, uint64_t mac2, uint64_t mac3) {
-    R_MAC1 = (int)(gte_clamp_mac(cpu, 1, ((long)R_RFC << 12) - mac1));
-    R_MAC2 = (int)(gte_clamp_mac(cpu, 2, ((long)R_GFC << 12) - mac2));
-    R_MAC3 = (int)(gte_clamp_mac(cpu, 3, ((long)R_BFC << 12) - mac3));
+void gte_interpolate_color(psx_cpu_t* cpu, int64_t mac1, int64_t mac2, int64_t mac3) {
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_RFC) << 12) - mac1);
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_GFC) << 12) - mac2);
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_BFC) << 12) - mac3);
 
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm);
+    // printf("input=(%08x, %08x, %08x) (%d, %d, %d) mac=(%08x, %08x, %08x), (%d, %d, %d) fc=(%08x, %08x, %08x)\n",
+    //     mac1,
+    //     mac2,
+    //     mac3,
+    //     mac1,
+    //     mac2,
+    //     mac3,
+    //     R_MAC1,
+    //     R_MAC2,
+    //     R_MAC3,
+    //     R_MAC1,
+    //     R_MAC2,
+    //     R_MAC3,
+    //     I64(R_RFC) << 12,
+    //     I64(R_GFC) << 12,
+    //     I64(R_BFC) << 12
+    // );
 
-    R_MAC1 = (int)(gte_clamp_mac(cpu, 1, ((long)R_IR1 * R_IR0) + mac1));
-    R_MAC2 = (int)(gte_clamp_mac(cpu, 2, ((long)R_IR2 * R_IR0) + mac2));
-    R_MAC3 = (int)(gte_clamp_mac(cpu, 3, ((long)R_IR3 * R_IR0) + mac3));
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, 0);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, 0);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, 0);
 
-    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
-    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
-    R_IR3 = gte_clamp_ir_z(cpu, cpu->s_mac3, cpu->gte_sf, cpu->gte_lm);
+    R_MAC1 = gte_clamp_mac(cpu, 1, (R_IR1 * R_IR0) + mac1);
+    R_MAC2 = gte_clamp_mac(cpu, 2, (R_IR2 * R_IR0) + mac2);
+    R_MAC3 = gte_clamp_mac(cpu, 3, (R_IR3 * R_IR0) + mac3);
+
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_sf);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_sf);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_sf);
 }
 
 void psx_gte_i_rtps(psx_cpu_t* cpu) {
+    R_FLAG = 0;
     GTE_RTP_DQ(0);
 }
 
@@ -1938,9 +1951,9 @@
     int64_t ir2 = gte_clamp_ir(cpu, 2, mac2, 0);
     int64_t ir3 = gte_clamp_ir(cpu, 3, mac3, 0);
 
-    R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_RC) << 16) + (R_IR0 * ir1));
-    R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_GC) << 16) + (R_IR0 * ir2));
-    R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_BC) << 16) + (R_IR0 * ir3));
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_RC) << 16) + (R_IR0 * ir1));
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_GC) << 16) + (R_IR0 * ir2));
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_BC) << 16) + (R_IR0 * ir3));
 
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
     R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
@@ -2012,7 +2025,7 @@
         case 1: mx = cpu->cop2_cr.l; break;
         case 2: mx = cpu->cop2_cr.lr; break;
         case 3: {
-            R_MX11 = -R_RC << 4;
+            R_MX11 = -(R_RC << 4);
             R_MX12 = R_RC << 4;
             R_MX13 = R_IR0;
             R_MX21 = R_RT13;
@@ -2049,21 +2062,21 @@
 
     // Bugged case (CV=FC)
     if (cpu->gte_cv == 2) {
-        R_MAC1 = gte_clamp_mac(cpu, 1, (int64_t)(I64(R_MX12) * I64(R_VY)) + (I64(R_MX13) * I64(R_VZ)));
-        R_MAC2 = gte_clamp_mac(cpu, 2, (int64_t)(I64(R_MX22) * I64(R_VY)) + (I64(R_MX23) * I64(R_VZ)));
-        R_MAC3 = gte_clamp_mac(cpu, 3, (int64_t)(I64(R_MX32) * I64(R_VY)) + (I64(R_MX33) * I64(R_VZ)));
+        R_MAC1 = gte_clamp_mac(cpu, 1, gte_check_mac(cpu, 1, I64(R_MX12) * I64(R_VY)) + (I64(R_MX13) * I64(R_VZ)));
+        R_MAC2 = gte_clamp_mac(cpu, 2, gte_check_mac(cpu, 2, I64(R_MX22) * I64(R_VY)) + (I64(R_MX23) * I64(R_VZ)));
+        R_MAC3 = gte_clamp_mac(cpu, 3, gte_check_mac(cpu, 3, I64(R_MX32) * I64(R_VY)) + (I64(R_MX33) * I64(R_VZ)));
 
-        int64_t mac1 = gte_clamp_mac(cpu, 1, (((int64_t)R_CV1) << 12) + (I64(R_MX11) * I64(R_VX))); 
-        int64_t mac2 = gte_clamp_mac(cpu, 2, (((int64_t)R_CV2) << 12) + (I64(R_MX21) * I64(R_VX))); 
-        int64_t mac3 = gte_clamp_mac(cpu, 3, (((int64_t)R_CV3) << 12) + (I64(R_MX31) * I64(R_VX))); 
+        int64_t mac1 = gte_clamp_mac(cpu, 1, (I64(R_CV1) << 12) + (I64(R_MX11) * I64(R_VX))); 
+        int64_t mac2 = gte_clamp_mac(cpu, 2, (I64(R_CV2) << 12) + (I64(R_MX21) * I64(R_VX))); 
+        int64_t mac3 = gte_clamp_mac(cpu, 3, (I64(R_CV3) << 12) + (I64(R_MX31) * I64(R_VX))); 
 
         gte_clamp_ir(cpu, 1, mac1, 0);
         gte_clamp_ir(cpu, 2, mac2, 0);
         gte_clamp_ir(cpu, 3, mac3, 0);
     } else {
-        R_MAC1 = gte_clamp_mac(cpu, 1, (((int64_t)R_CV1) << 12) + (I64(R_MX11) * I64(R_VX)) + (I64(R_MX12) * I64(R_VY)) + (I64(R_MX13) * I64(R_VZ)));
-        R_MAC2 = gte_clamp_mac(cpu, 2, (((int64_t)R_CV2) << 12) + (I64(R_MX21) * I64(R_VX)) + (I64(R_MX22) * I64(R_VY)) + (I64(R_MX23) * I64(R_VZ)));
-        R_MAC3 = gte_clamp_mac(cpu, 3, (((int64_t)R_CV3) << 12) + (I64(R_MX31) * I64(R_VX)) + (I64(R_MX32) * I64(R_VY)) + (I64(R_MX33) * I64(R_VZ)));
+        R_MAC1 = gte_clamp_mac(cpu, 1, gte_check_mac(cpu, 1, gte_check_mac(cpu, 1, (I64(R_CV1) << 12) + (I64(R_MX11) * I64(R_VX))) + (I64(R_MX12) * I64(R_VY))) + (I64(R_MX13) * I64(R_VZ)));
+        R_MAC2 = gte_clamp_mac(cpu, 2, gte_check_mac(cpu, 2, gte_check_mac(cpu, 2, (I64(R_CV2) << 12) + (I64(R_MX21) * I64(R_VX))) + (I64(R_MX22) * I64(R_VY))) + (I64(R_MX23) * I64(R_VZ)));
+        R_MAC3 = gte_clamp_mac(cpu, 3, gte_check_mac(cpu, 3, gte_check_mac(cpu, 3, (I64(R_CV3) << 12) + (I64(R_MX31) * I64(R_VX))) + (I64(R_MX32) * I64(R_VY))) + (I64(R_MX33) * I64(R_VZ)));
     }
 
     R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
@@ -2090,14 +2103,38 @@
 
 // To-do: Fix flags
 void psx_gte_i_ncds(psx_cpu_t* cpu) {
+    R_FLAG = 0;
+
     NCDS(0);
 }
 
 void psx_gte_i_cdp(psx_cpu_t* cpu) {
-    printf("cdp: Unimplemented GTE instruction\n");
+    R_FLAG = 0;
+    R_MAC1 = gte_clamp_mac(cpu, 1, gte_check_mac(cpu, 1, gte_check_mac(cpu, 1, (I64(R_RBK) << 12) + (I64(R_LR1) * I64(R_IR1))) + (I64(R_LR2) * I64(R_IR2))) + (I64(R_LR3) * I64(R_IR3)));
+    R_MAC2 = gte_clamp_mac(cpu, 2, gte_check_mac(cpu, 2, gte_check_mac(cpu, 2, (I64(R_GBK) << 12) + (I64(R_LG1) * I64(R_IR1))) + (I64(R_LG2) * I64(R_IR2))) + (I64(R_LG3) * I64(R_IR3)));
+    R_MAC3 = gte_clamp_mac(cpu, 3, gte_check_mac(cpu, 3, gte_check_mac(cpu, 3, (I64(R_BBK) << 12) + (I64(R_LB1) * I64(R_IR1))) + (I64(R_LB2) * I64(R_IR2))) + (I64(R_LB3) * I64(R_IR3)));
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+    int64_t ir1 = gte_clamp_ir(cpu, 1, gte_clamp_mac(cpu, 1, ((I64(R_RFC) << 12) - ((I64(R_RC << 4)) * I64(R_IR1)))), 0);
+    int64_t ir2 = gte_clamp_ir(cpu, 2, gte_clamp_mac(cpu, 2, ((I64(R_GFC) << 12) - ((I64(R_GC << 4)) * I64(R_IR2)))), 0);
+    int64_t ir3 = gte_clamp_ir(cpu, 3, gte_clamp_mac(cpu, 3, ((I64(R_BFC) << 12) - ((I64(R_BC << 4)) * I64(R_IR3)))), 0);
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_RC << 4) * I64(R_IR1)) + (I64(R_IR0) * ir1));
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_GC << 4) * I64(R_IR2)) + (I64(R_IR0) * ir2));
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_BC << 4) * I64(R_IR3)) + (I64(R_IR0) * ir3));
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+    R_RGB0 = R_RGB1;
+    R_RGB1 = R_RGB2;
+    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
+    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
+    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
+    R_CD2 = R_CODE;
 }
 
 void psx_gte_i_ncdt(psx_cpu_t* cpu) {
+    R_FLAG = 0;
     NCDS(0);
     NCDS(1);
     NCDS(2);
@@ -2104,18 +2141,39 @@
 }
 
 void psx_gte_i_nccs(psx_cpu_t* cpu) {
+    R_FLAG = 0;
     NCCS(0);
 }
 
 void psx_gte_i_cc(psx_cpu_t* cpu) {
-    NCS(0); // Hack
+    R_FLAG = 0;
+    R_MAC1 = gte_clamp_mac(cpu, 1, gte_check_mac(cpu, 1, gte_check_mac(cpu, 1, (I64(R_RBK) << 12) + (I64(R_LR1) * I64(R_IR1))) + (I64(R_LR2) * I64(R_IR2))) + (I64(R_LR3) * I64(R_IR3)));
+    R_MAC2 = gte_clamp_mac(cpu, 2, gte_check_mac(cpu, 2, gte_check_mac(cpu, 2, (I64(R_GBK) << 12) + (I64(R_LG1) * I64(R_IR1))) + (I64(R_LG2) * I64(R_IR2))) + (I64(R_LG3) * I64(R_IR3)));
+    R_MAC3 = gte_clamp_mac(cpu, 3, gte_check_mac(cpu, 3, gte_check_mac(cpu, 3, (I64(R_BBK) << 12) + (I64(R_LB1) * I64(R_IR1))) + (I64(R_LB2) * I64(R_IR2))) + (I64(R_LB3) * I64(R_IR3)));
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
+    R_MAC1 = gte_clamp_mac(cpu, 1, (I64(R_RC) * I64(R_IR1)) << 4);
+    R_MAC2 = gte_clamp_mac(cpu, 2, (I64(R_GC) * I64(R_IR2)) << 4);
+    R_MAC3 = gte_clamp_mac(cpu, 3, (I64(R_BC) * I64(R_IR3)) << 4);
+    R_RGB0 = R_RGB1;
+    R_RGB1 = R_RGB2;
+    R_CD2 = R_CODE;
+    R_RC2 = gte_clamp_rgb(cpu, 1, R_MAC1 >> 4);
+    R_GC2 = gte_clamp_rgb(cpu, 2, R_MAC2 >> 4);
+    R_BC2 = gte_clamp_rgb(cpu, 3, R_MAC3 >> 4);
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
 }
 
 void psx_gte_i_ncs(psx_cpu_t* cpu) {
+    R_FLAG = 0;
     NCS(0);
 }
 
 void psx_gte_i_nct(psx_cpu_t* cpu) {
+    R_FLAG = 0;
     NCS(0);
     NCS(1);
     NCS(2);
@@ -2134,12 +2192,20 @@
 }
 
 void psx_gte_i_dcpl(psx_cpu_t* cpu) {
-    R_MAC1 = (int)(gte_clamp_mac(cpu, 1, R_RC * R_IR1) << 4);
-    R_MAC2 = (int)(gte_clamp_mac(cpu, 2, R_GC * R_IR2) << 4);
-    R_MAC3 = (int)(gte_clamp_mac(cpu, 3, R_BC * R_IR3) << 4);
+    R_FLAG = 0;
 
-    gte_interpolate_color(cpu, R_MAC1, R_MAC2, R_MAC3);
-
+    R_MAC1 = gte_clamp_mac(cpu, 1, I64(R_RC) * I64(R_IR1)) << 4;
+    R_MAC2 = gte_clamp_mac(cpu, 2, I64(R_GC) * I64(R_IR2)) << 4;
+    R_MAC3 = gte_clamp_mac(cpu, 3, I64(R_BC) * I64(R_IR3)) << 4;
+    int64_t ir1 = gte_clamp_ir(cpu, 1, gte_clamp_mac(cpu, 1, ((I64(R_RFC) << 12) - ((I64(R_RC << 4)) * I64(R_IR1)))), 0);
+    int64_t ir2 = gte_clamp_ir(cpu, 2, gte_clamp_mac(cpu, 2, ((I64(R_GFC) << 12) - ((I64(R_GC << 4)) * I64(R_IR2)))), 0);
+    int64_t ir3 = gte_clamp_ir(cpu, 3, gte_clamp_mac(cpu, 3, ((I64(R_BFC) << 12) - ((I64(R_BC << 4)) * I64(R_IR3)))), 0);
+    R_MAC1 = gte_clamp_mac(cpu, 1, ((I64(R_RC << 4)) * I64(R_IR1)) + (I64(R_IR0) * ir1));
+    R_MAC2 = gte_clamp_mac(cpu, 2, ((I64(R_GC << 4)) * I64(R_IR2)) + (I64(R_IR0) * ir2));
+    R_MAC3 = gte_clamp_mac(cpu, 3, ((I64(R_BC << 4)) * I64(R_IR3)) + (I64(R_IR0) * ir3));
+    R_IR1 = gte_clamp_ir(cpu, 1, R_MAC1, cpu->gte_lm);
+    R_IR2 = gte_clamp_ir(cpu, 2, R_MAC2, cpu->gte_lm);
+    R_IR3 = gte_clamp_ir(cpu, 3, R_MAC3, cpu->gte_lm);
     R_RGB0 = R_RGB1;
     R_RGB1 = R_RGB2;
     R_CD2 = R_CODE;
@@ -2149,6 +2215,7 @@
 }
 
 void psx_gte_i_dpct(psx_cpu_t* cpu) {
+    R_FLAG = 0;
     DPCT1;
     DPCT1;
     DPCT1;
@@ -2173,6 +2240,7 @@
 }
 
 void psx_gte_i_rtpt(psx_cpu_t* cpu) {
+    R_FLAG = 0;
     GTE_RTP(0);
     GTE_RTP(1);
     GTE_RTP_DQ(2);
@@ -2213,6 +2281,7 @@
 }
 
 void psx_gte_i_ncct(psx_cpu_t* cpu) {
+    R_FLAG = 0;
     NCCS(0);
     NCCS(1);
     NCCS(2);
--- a/psx/dev/cdrom.c
+++ /dev/null
@@ -1,2328 +1,0 @@
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "cdrom.h"
-#include "../log.h"
-#include "../msf.h"
-
-/*
-    Drive Status           1st Response   2nd Response
-    Door Open              INT5(11h,80h)  N/A
-    Spin-up                INT5(01h,80h)  N/A
-    Detect busy            INT5(03h,80h)  N/A
-    No Disk                INT3(stat)     INT5(08h,40h, 00h,00h, 00h,00h,00h,00h)
-    Audio Disk             INT3(stat)     INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h)
-    Unlicensed:Mode1       INT3(stat)     INT5(0Ah,80h, 00h,00h, 00h,00h,00h,00h)
-    Unlicensed:Mode2       INT3(stat)     INT5(0Ah,80h, 20h,00h, 00h,00h,00h,00h)
-    Unlicensed:Mode2+Audio INT3(stat)     INT5(0Ah,90h, 20h,00h, 00h,00h,00h,00h)
-    Debug/Yaroze:Mode2     INT3(stat)     INT2(02h,00h, 20h,00h, 20h,20h,20h,20h)
-    Licensed:Mode2         INT3(stat)     INT2(02h,00h, 20h,00h, 53h,43h,45h,4xh)
-    Modchip:Audio/Mode1    INT3(stat)     INT2(02h,00h, 00h,00h, 53h,43h,45h,4xh)
-*/
-
-msf_t cdrom_get_track_addr(psx_cdrom_t* cdrom, msf_t msf) {
-    uint32_t lba = msf_to_address(msf);
-
-    int num_tracks, track;
-
-    psx_disc_get_track_count(cdrom->disc, &num_tracks);
-
-    for (track = 1; track < num_tracks - 1; track++) {
-        msf_t curr, next;
-
-        psx_disc_get_track_addr(cdrom->disc, &curr, track);
-        psx_disc_get_track_addr(cdrom->disc, &next, track + 1);
-
-        uint32_t curr_lba = msf_to_address(curr);
-        uint32_t next_lba = msf_to_address(next);
-
-        // printf("lba=%02u:%02u:%02u (%08x) curr=%02u:%02u:%02u (%08x) next=%02u:%02u:%02u (%08x)\n",
-        //     msf.m,
-        //     msf.s,
-        //     msf.f,
-        //     lba,
-        //     curr.m,
-        //     curr.s,
-        //     curr.f,
-        //     curr_lba,
-        //     next.m,
-        //     next.s,
-        //     next.f,
-        //     next_lba
-        // );
-
-        if ((lba >= curr_lba) && (lba < next_lba))
-            break;
-    }
-
-    msf_t track_msf;
-
-    psx_disc_get_track_addr(cdrom->disc, &track_msf, track);
-
-    return track_msf;
-}
-
-void cdrom_fetch_video_sector(psx_cdrom_t* cdrom) {
-    while (true) {
-        if (psx_disc_seek(cdrom->disc, cdrom->seek_msf))
-            return;
-
-        psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
-
-        msf_add_f(&cdrom->seek_msf, 1);
-
-        return;
-        // Check RT and Video/Data bit
-        // if (cdrom->dfifo[0x12] & 4)
-        //     continue;
-
-        // If we get here it means this is a real-time video sector.
-        // If the XA filter is disabled, we're done
-        // if (!(cdrom->mode & MODE_XA_FILTER))
-        //     return;
-
-        // // Else check XA file/channel
-        // int file_eq = cdrom->dfifo[0x10] == cdrom->xa_file;
-        // int channel_eq = cdrom->dfifo[0x11] == cdrom->xa_channel;
-
-        // // If they are equal to our filter values, we're done
-        // // else keep searching
-        // if (file_eq && channel_eq)
-        //     return;
-    }
-}
-
-#define GETID_RESPONSE_SIZE 8
-#define GETID_RESPONSE_END (GETID_RESPONSE_SIZE - 1)
-
-static const int16_t g_zigzag_table0[] = {
-     0x0000,  0x0000,  0x0000,  0x0000,
-     0x0000, -0x0002,  0x000a, -0x0022,
-     0x0041, -0x0054,  0x0034,  0x0009,
-    -0x010a,  0x0400, -0x0a78,  0x234c,
-     0x6794, -0x1780,  0x0bcd, -0x0623,
-     0x0350, -0x016d,  0x006b,  0x000a,
-    -0x0010,  0x0011, -0x0008,  0x0003,
-    -0x0001
-};
-
-static const int16_t g_zigzag_table1[] = {
-     0x0000,  0x0000,  0x0000, -0x0002,
-     0x0000,  0x0003, -0x0013,  0x003c,
-    -0x004b,  0x00a2, -0x00e3,  0x0132,
-    -0x0043, -0x0267,  0x0c9d,  0x74bb,
-    -0x11b4,  0x09b8, -0x05bf,  0x0372,
-    -0x01a8,  0x00a6, -0x001b,  0x0005,
-     0x0006, -0x0008,  0x0003, -0x0001,
-     0x0000
-};
-
-static const int16_t g_zigzag_table2[] = {
-     0x0000,  0x0000, -0x0001,  0x0003,
-    -0x0002, -0x0005,  0x001f, -0x004a,
-     0x00b3, -0x0192,  0x02b1, -0x039e,
-     0x04f8, -0x05a6,  0x7939, -0x05a6,
-     0x04f8, -0x039e,  0x02b1, -0x0192,
-     0x00b3, -0x004a,  0x001f, -0x0005,
-    -0x0002,  0x0003, -0x0001,  0x0000,
-     0x0000
-};
-
-static const int16_t g_zigzag_table3[] = {
-     0x0000, -0x0001,  0x0003, -0x0008,
-     0x0006,  0x0005, -0x001b,  0x00a6,
-    -0x01a8,  0x0372, -0x05bf,  0x09b8,
-    -0x11b4,  0x74bb,  0x0c9d, -0x0267,
-    -0x0043,  0x0132, -0x00e3,  0x00a2,
-    -0x004b,  0x003c, -0x0013,  0x0003,
-     0x0000, -0x0002,  0x0000,  0x0000,
-     0x0000
-};
-
-static const int16_t g_zigzag_table4[] = {
-    -0x0001,  0x0003, -0x0008,  0x0011,
-    -0x0010,  0x000a,  0x006b, -0x016d,
-     0x0350, -0x0623,  0x0bcd, -0x1780,
-     0x6794,  0x234c, -0x0a78,  0x0400,
-    -0x010a,  0x0009,  0x0034, -0x0054,
-     0x0041, -0x0022,  0x000a, -0x0001,
-     0x0000,  0x0001,  0x0000,  0x0000,
-     0x0000
-};
-
-static const int16_t g_zigzag_table5[] = {
-     0x0002, -0x0008,  0x0010, -0x0023,
-     0x002b,  0x001a, -0x00eb,  0x027b,
-    -0x0548,  0x0afa, -0x16fa,  0x53e0,
-     0x3c07, -0x1249,  0x080e, -0x0347,
-     0x015b, -0x0044, -0x0017,  0x0046,
-    -0x0023,  0x0011, -0x0005,  0x0000,
-     0x0000,  0x0000,  0x0000,  0x0000,
-     0x0000
-};
-
-static const int16_t g_zigzag_table6[] = {
-    -0x0005,  0x0011, -0x0023,  0x0046,
-    -0x0017, -0x0044,  0x015b, -0x0347,
-     0x080e, -0x1249,  0x3c07,  0x53e0,
-    -0x16fa,  0x0afa, -0x0548,  0x027b,
-    -0x00eb,  0x001a,  0x002b, -0x0023,
-     0x0010, -0x0008,  0x0002,  0x0000,
-     0x0000,  0x0000,  0x0000,  0x0000,
-     0x0000
-};
-
-static const int16_t* g_zigzag_table[] = {
-    g_zigzag_table0,
-    g_zigzag_table1,
-    g_zigzag_table2,
-    g_zigzag_table3,
-    g_zigzag_table4,
-    g_zigzag_table5,
-    g_zigzag_table6
-};
-
-static const uint8_t g_getid_no_disc[] = {
-    0x08, 0x40, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8_t g_getid_audio[] = {
-    0x0a, 0x90, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8_t g_getid_unlicensed[] = {
-    0x0a, 0x80, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8_t g_getid_licensed[] = {
-    0x02, 0x00, 0x20, 0x00,
-    'S' , 'C' , 'E' , 'A'
-};
-
-#define RESP_PUSH(data) { \
-    cdrom->rfifo[cdrom->rfifo_index++] = data; \
-    cdrom->rfifo_index &= 15; \
-    SET_BITS(status, STAT_RSLRRDY_MASK, STAT_RSLRRDY_MASK); }
-
-#define PFIFO_POP (cdrom->pfifo[--cdrom->pfifo_index])
-
-#define VALID_BCD(v) (((v & 0xf) <= 9) && ((v & 0xf0) <= 0x90))
-
-void cdrom_cmd_error(psx_cdrom_t* cdrom) {
-    SET_BITS(ifr, IFR_INT, IFR_INT5);
-    RESP_PUSH(cdrom->error);
-    RESP_PUSH(GETSTAT_MOTOR | cdrom->error_flags);
-
-    cdrom->pfifo_index = 0;
-    cdrom->delayed_command = CDL_NONE;
-    cdrom->state = CD_STATE_RECV_CMD;
-}
-void cdrom_cmd_unimplemented(psx_cdrom_t* cdrom) {
-    // log_set_quiet(0);
-    log_fatal("Unimplemented CDROM command (%u)", cdrom->command);
-    log_set_quiet(1);
-
-    exit(1);
-}
-void cdrom_cmd_getstat(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            // if (cdrom->ongoing_read_command) {
-            //     cdrom->status |= STAT_BUSYSTS_MASK;
-            //     // 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;
-
-            //     return;
-            // }
-
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlGetStat: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS * 2;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_GETSTAT;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(
-                GETSTAT_MOTOR |
-                (cdrom->cdda_playing ? GETSTAT_PLAY : 0) |
-                (cdrom->ongoing_read_command ? GETSTAT_READ : 0) |
-                (cdrom->disc ? 0 : GETSTAT_TRAYOPEN)
-            );
-
-            if (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 * 2;
-            } else {
-                cdrom->delayed_command = CDL_NONE;
-                cdrom->state = CD_STATE_RECV_CMD;
-            }
-        } break;
-    }
-}
-void cdrom_cmd_setloc(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index != 3) {
-                log_fatal("CdlSetloc: Expected exactly 3 parameters, got %u instead",
-                    cdrom->pfifo_index
-                );
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            int f = PFIFO_POP;
-            int s = PFIFO_POP;
-            int m = PFIFO_POP;
-
-            if (!(VALID_BCD(m) && VALID_BCD(s) && VALID_BCD(f) && (f < 0x75))) {
-                printf("setloc: invalid msf\n");
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->seek_msf.m = m;
-            cdrom->seek_msf.s = s;
-            cdrom->seek_msf.f = f;
-
-            msf_from_bcd(&cdrom->seek_msf);
-
-            cdrom->cdda_msf = cdrom->seek_msf;
-
-            cdrom->seek_pending = 1;
-
-            // printf("setloc: %02x:%02x:%02x\n",
-            //     cdrom->seek_msf.m,
-            //     cdrom->seek_msf.s,
-            //     cdrom->seek_msf.f
-            // );
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_SETLOC;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_SEEK);
-
-            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;
-
-        // Read ongoing
-        case CD_STATE_SEND_RESP2: {
-            int f = PFIFO_POP;
-            int s = PFIFO_POP;
-            int m = PFIFO_POP;
-
-            if (!(VALID_BCD(m) && VALID_BCD(s) && VALID_BCD(f) && (f < 0x75))) {
-                cdrom->ongoing_read_command = false;
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->seek_msf.m = m;
-            cdrom->seek_msf.s = s;
-            cdrom->seek_msf.f = f;
-        } break;
-    }
-}
-void cdrom_cmd_play(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            int track = 0;
-
-            // if (cdrom->cdda_playing) {
-            //     cdrom->pfifo_index = 0;
-
-            //     cdrom->irq_delay = DELAY_1MS;
-            //     cdrom->state = CD_STATE_SEND_RESP1;
-            //     cdrom->delayed_command = CDL_PLAY;
-
-            //     return;
-            // }
-
-            // Optional track number parameter
-            if (cdrom->pfifo_index)
-                track = PFIFO_POP;
-
-            cdrom->irq_delay = DELAY_1MS;
-            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);
-
-                msf_to_bcd(&cdrom->cdda_msf);
-
-                cdrom->cdda_track = track;
-                cdrom->seek_msf.m = cdrom->cdda_msf.m;
-                cdrom->seek_msf.s = cdrom->cdda_msf.s;
-                cdrom->seek_msf.f = cdrom->cdda_msf.f;
-    
-                cdrom->seek_pending = 1;
-            }
-
-            if (cdrom->seek_pending) {
-                cdrom->seek_pending = 0;
-
-                // printf("Seeked to location\n");
-
-                cdrom->cdda_msf = cdrom->seek_msf;
-
-                // Seek to that address and read sector
-                psx_disc_seek(cdrom->disc, cdrom->cdda_msf);
-                psx_disc_read_sector(cdrom->disc, cdrom->cdda_buf);
-
-                // Increment sector
-                msf_add_f(&cdrom->cdda_msf, 1);
-
-                cdrom->cdda_sector_offset = 0;
-            }
-
-            cdrom->cdda_playing = 1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_PLAY);
-
-            cdrom->delayed_command = CDL_NONE;
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_readn(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-    cdrom->ongoing_read_command = CDL_READN;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            log_fatal("CdlReadN: CD_STATE_RECV_CMD");
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_READN;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            log_fatal("CdlReadN: CD_STATE_SEND_RESP1");
-
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            if (cdrom->mode & MODE_XA_ADPCM) {
-                cdrom->xa_msf = cdrom->seek_msf;
-                cdrom->xa_current_msf = cdrom->xa_msf;
-                cdrom->xa_playing = 1;
-                cdrom->xa_remaining_samples = 0;
-
-                SET_BITS(status, STAT_ADPBUSY_MASK, STAT_ADPBUSY_MASK);
-
-                printf("Play XA-ADPCM encoded song at %02u:%02u:%02u, filter=%u, file=%02x, channel=%02x (ReadN)\n",
-                    cdrom->xa_msf.m,
-                    cdrom->xa_msf.s,
-                    cdrom->xa_msf.f,
-                    (cdrom->mode & MODE_XA_FILTER) != 0,
-                    cdrom->xa_file,
-                    cdrom->xa_channel
-                );
-            }
-
-            int err = psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-
-            if (err) {
-                // log_set_quiet(0);
-                log_fatal("CdlReadN: Out of bounds seek");
-                log_set_quiet(1);
-
-                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;
-            }
-
-            psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READN;
-
-            if (cdrom->spin_delay) {
-                cdrom->irq_delay += cdrom->spin_delay;
-                cdrom->spin_delay = 0;
-            }
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            log_fatal("CdlReadS: CD_STATE_SEND_RESP2");
-
-            // Returning only non-ADPCM sectors causes desync for some
-            // reason. I'll keep returning all sectors for now
-
-            if (cdrom->mode & MODE_XA_ADPCM) {
-                // printf("ReadS fetching non ADPCM sector...\n");
-                cdrom_fetch_video_sector(cdrom);
-
-                // printf("%02u:%02u:%02u - file=%02x channel=%02x sm=%02x ci=%02x\n",
-                //     cdrom->seek_msf.m,
-                //     cdrom->seek_msf.s,
-                //     cdrom->seek_msf.f,
-                //     cdrom->dfifo[0x10],
-                //     cdrom->dfifo[0x11],
-                //     cdrom->dfifo[0x12],
-                //     cdrom->dfifo[0x13]
-                // );
-            } else {
-                psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-                psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
-
-                msf_add_f(&cdrom->seek_msf, 1);
-            }
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READN;
-            cdrom->dfifo_index = 0;
-
-            SET_BITS(ifr, IFR_INT, IFR_INT1);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-        } break;
-    }
-}
-void cdrom_cmd_motoron(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->ongoing_read_command = CDL_NONE;
-            cdrom->cdda_playing = 0;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_MOTORON;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_MOTORON;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_stop(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->ongoing_read_command = CDL_NONE;
-            cdrom->cdda_playing = 0;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_STOP;
-            cdrom->seek_msf.m = 0;
-            cdrom->seek_msf.s = 0;
-            cdrom->seek_msf.f = 0;
-
-            cdrom->cdda_msf = cdrom->seek_msf;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_STOP;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_pause(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->ongoing_read_command = CDL_NONE;
-            cdrom->cdda_playing = 0;
-            cdrom->xa_playing = 0;
-
-            SET_BITS(status, STAT_ADPBUSY_MASK, 0);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_PAUSE;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = DELAY_1MS * (double_speed ? 70 : 65);
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_PAUSE;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_init(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_INIT;
-            cdrom->ongoing_read_command = CDL_NONE;
-            cdrom->mode = 0;
-            cdrom->dfifo_index = 0;
-            cdrom->dfifo_full = 0;
-            cdrom->pfifo_index = 0;
-            cdrom->rfifo_index = 0;
-            cdrom->seek_msf.m = 0;
-            cdrom->seek_msf.s = 2;
-            cdrom->seek_msf.f = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_INIT;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_mute(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlMute: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_MUTE;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(cdrom->stat);
-
-            if (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;
-    } 
-}
-void cdrom_cmd_unmute(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlUnmute: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_DEMUTE;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(cdrom->stat);
-
-            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;
-    }
-}
-void cdrom_cmd_setfilter(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index != 2) {
-                log_fatal("CdlSetfilter: Expected exactly 2 parameter");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->xa_channel = PFIFO_POP;
-            cdrom->xa_file = PFIFO_POP;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_SETFILTER;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            cdrom->delayed_command = CDL_NONE;
-    
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(cdrom->stat);
-
-            if (cdrom->ongoing_read_command) {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->state = CD_STATE_SEND_RESP2;
-            }
-
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_setmode(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    // Not doing this fixes a graphical issue in
-    // Castlevania - Symphony of the Night, but breaks
-    // Road Rash.
-    // cdrom->ongoing_read_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index != 1) {
-                log_fatal("CdlSetmode: Expected exactly 1 parameter");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            int prev_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->mode = PFIFO_POP;
-
-            if ((cdrom->mode & MODE_SPEED) != prev_speed)
-                cdrom->spin_delay = 0; // DELAY_1MS * 650;
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_SETMODE;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            cdrom->delayed_command = CDL_NONE;
-    
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            if (cdrom->ongoing_read_command) {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = cdrom->ongoing_read_command;
-                cdrom->state = CD_STATE_SEND_RESP2;
-
-                return;
-            }
-
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_getparam(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_GETPARAM;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            cdrom->delayed_command = CDL_NONE;
-    
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(cdrom->xa_channel);
-            RESP_PUSH(cdrom->xa_file);
-            RESP_PUSH(0x00);
-            RESP_PUSH(cdrom->mode);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_getlocl(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS * 4;
-            cdrom->delayed_command = CDL_GETLOCL;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(cdrom->xa_sector_buf[0x13]);
-            RESP_PUSH(cdrom->xa_sector_buf[0x12]);
-            RESP_PUSH(cdrom->xa_sector_buf[0x11]);
-            RESP_PUSH(cdrom->xa_sector_buf[0x10]);
-            RESP_PUSH(cdrom->xa_sector_buf[0x0f]);
-            RESP_PUSH(cdrom->xa_sector_buf[0x0e]);
-            RESP_PUSH(cdrom->xa_sector_buf[0x0d]);
-            RESP_PUSH(cdrom->xa_sector_buf[0x0c]);
-
-            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 * 4;
-            } else {
-                cdrom->delayed_command = CDL_NONE;
-                cdrom->state = CD_STATE_RECV_CMD;
-            }
-        } break;
-    }
-}
-void cdrom_cmd_getlocp(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS * 8;
-            cdrom->delayed_command = CDL_GETLOCP;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            msf_t absolute = cdrom->seek_msf;
-            msf_t relative = absolute;
-            msf_t track_msf = cdrom_get_track_addr(cdrom, absolute);
-
-            relative.m -= track_msf.m;
-            relative.s -= track_msf.s;
-            relative.f -= track_msf.f;
-
-            msf_adjust_sub(&relative);
-
-            // printf("abs=%02u:%02u:%02u tra=%02u:%02u:%02u rel=%02u:%02u:%02u\n",
-            //     absolute.m,
-            //     absolute.s,
-            //     absolute.f,
-            //     track_msf.m,
-            //     track_msf.s,
-            //     track_msf.f,
-            //     relative.m,
-            //     relative.s,
-            //     relative.f
-            // );
-
-            msf_to_bcd(&absolute);
-            msf_to_bcd(&relative);
-
-            // printf("getlocp 01 01 %02x:%02x:%02x %02x:%02x:%02x\n",
-            //     relative.m,
-            //     relative.s,
-            //     relative.f,
-            //     absolute.m,
-            //     absolute.s,
-            //     absolute.f
-            // );
-
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(absolute.f);
-            RESP_PUSH(absolute.s);
-            RESP_PUSH(absolute.m);
-            RESP_PUSH(relative.f);
-            RESP_PUSH(relative.s);
-            RESP_PUSH(relative.m);
-            RESP_PUSH(0x01);
-            RESP_PUSH(0x15);
-
-            if (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 * 8;
-            } else {
-                cdrom->delayed_command = CDL_NONE;
-                cdrom->state = CD_STATE_RECV_CMD;
-            }
-        } break;
-    }
-}
-void cdrom_cmd_setsession(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_SETSESSION;
-            cdrom->state = CD_STATE_SEND_RESP1;
-
-            cdrom->pfifo_index = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_SEEK | GETSTAT_MOTOR);
-
-            cdrom->delayed_command = CDL_SETSESSION;
-            cdrom->state = CD_STATE_SEND_RESP2;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, IFR_INT2);
-            RESP_PUSH(GETSTAT_SEEK | GETSTAT_MOTOR);
-
-            cdrom->delayed_command = CDL_NONE;
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_gettn(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlGetTN: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_GETTN;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            int tn;
-
-            psx_disc_get_track_count(cdrom->disc, &tn);
-
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(tn);
-            RESP_PUSH(0x01);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            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;
-    }
-}
-void cdrom_cmd_gettd(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index != 1) {
-                log_fatal("CdlGetTD: Expected exactly 1 parameter");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->gettd_track = PFIFO_POP;
-
-            if (!VALID_BCD(cdrom->gettd_track)) {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            int err = psx_disc_get_track_addr(cdrom->disc, NULL, cdrom->gettd_track);
-
-            if (err) {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_GETTD;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            msf_t td;
-
-            psx_disc_get_track_addr(cdrom->disc, &td, cdrom->gettd_track);
-
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(ITOB(td.s));
-            RESP_PUSH(ITOB(td.m));
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->delayed_command = CDL_NONE;
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_seekl(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlSeekL: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_SEEKL;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_SEEKL;
-
-            psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-
-            cdrom->seek_pending = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_seekp(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlSeekP: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_SEEKP;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_SEEKP;
-
-            psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-
-            cdrom->seek_pending = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_test(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index != 1) {
-                log_fatal("CdlTest: Expected exactly 1 parameter");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            if (PFIFO_POP != 0x20) {
-                log_fatal("CdlTest: Unhandled subcommand");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_INVSUBF;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->delayed_command = CDL_TEST;
-            cdrom->state = CD_STATE_SEND_RESP1;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            cdrom->delayed_command = CDL_NONE;
-
-            // 95h,05h,16h,C1h
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(0xc0);
-            RESP_PUSH(0x19);
-            RESP_PUSH(0x09);
-            RESP_PUSH(0x94);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-        } break;
-    }
-}
-void cdrom_cmd_getid(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            if (cdrom->pfifo_index) {
-                log_fatal("CdlGetID: Expected exactly 0 parameters");
-
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_PCOUNT;
-                cdrom->error_flags = GETSTAT_ERROR;
-
-                return;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_GETID;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_GETID;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            if (cdrom->disc) {
-                SET_BITS(ifr, IFR_INT, 2);
-
-                switch (cdrom->cd_type) {
-                    case CDT_LICENSED: {
-                        for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
-                            RESP_PUSH(g_getid_licensed[GETID_RESPONSE_END - i]);
-                    } break;
-
-                    case CDT_AUDIO: {
-                        for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
-                            RESP_PUSH(g_getid_audio[GETID_RESPONSE_END - i]);
-                    } break;
-
-                    case CDT_UNKNOWN: {
-                        for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
-                            RESP_PUSH(g_getid_unlicensed[GETID_RESPONSE_END - i]);
-                    } break;
-                }
-            } else {
-                SET_BITS(ifr, IFR_INT, 5);
-
-                for (int i = 0; i < GETID_RESPONSE_SIZE; i++)
-                    RESP_PUSH(g_getid_no_disc[GETID_RESPONSE_END - i]);
-            }
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_reads(psx_cdrom_t* cdrom) {
-    cdrom->delayed_command = CDL_NONE;
-    cdrom->ongoing_read_command = CDL_READS;
-
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            log_fatal("CdlReadS: CD_STATE_RECV_CMD");
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_READS;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            printf("CdlReadS: CD_STATE_SEND_RESP1\n");
-            log_fatal("CdlReadS: CD_STATE_SEND_RESP1");
-
-            SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            if (cdrom->mode & MODE_XA_ADPCM) {
-                cdrom->xa_msf = cdrom->seek_msf;
-                cdrom->xa_current_msf = cdrom->xa_msf;
-                cdrom->xa_playing = 1;
-                cdrom->xa_remaining_samples = 0;
-
-                SET_BITS(status, STAT_ADPBUSY_MASK, STAT_ADPBUSY_MASK);
-
-                printf("Play XA-ADPCM encoded song at %02u:%02u:%02u, filter=%u, file=%02x, channel=%02x (ReadS)\n",
-                    cdrom->xa_msf.m,
-                    cdrom->xa_msf.s,
-                    cdrom->xa_msf.f,
-                    (cdrom->mode & MODE_XA_FILTER) != 0,
-                    cdrom->xa_file,
-                    cdrom->xa_channel
-                );
-
-                int double_speed = cdrom->mode & MODE_SPEED;
-
-                cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
-                cdrom->state = CD_STATE_SEND_RESP2;
-                cdrom->delayed_command = CDL_READS;
-
-                // if (cdrom->spin_delay) {
-                //     cdrom->irq_delay += cdrom->spin_delay;
-                //     cdrom->spin_delay = 0;
-                // }
-
-                return;
-            }
-
-            int err = psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-
-            if (err) {
-                log_fatal("CdlReadS: Out of bounds seek");
-
-                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;
-            }
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READS;
-
-            if (cdrom->spin_delay) {
-                cdrom->irq_delay += cdrom->spin_delay;
-                cdrom->spin_delay = 0;
-            }
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            log_fatal("CdlReadS: CD_STATE_SEND_RESP2");
-
-            // Returning only non-ADPCM sectors causes desync for some
-            // reason. I'll keep returning all sectors for now
-
-            if (cdrom->mode & MODE_XA_ADPCM) {
-                // printf("ReadS fetching non ADPCM sector...\n");
-                cdrom_fetch_video_sector(cdrom);
-
-                // printf("%02u:%02u:%02u - file=%02x channel=%02x sm=%02x ci=%02x\n",
-                //     cdrom->seek_msf.m,
-                //     cdrom->seek_msf.s,
-                //     cdrom->seek_msf.f,
-                //     cdrom->dfifo[0x10],
-                //     cdrom->dfifo[0x11],
-                //     cdrom->dfifo[0x12],
-                //     cdrom->dfifo[0x13]
-                // );
-            } else {
-                psx_disc_seek(cdrom->disc, cdrom->seek_msf);
-                psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
-
-                msf_add_f(&cdrom->seek_msf, 1);
-            }
-
-            int double_speed = cdrom->mode & MODE_SPEED;
-
-            cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READS;
-            cdrom->dfifo_index = 0;
-
-            SET_BITS(ifr, IFR_INT, IFR_INT1);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-        } break;
-    }
-}
-void cdrom_cmd_readtoc(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->status |= STAT_BUSYSTS_MASK;
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_READTOC;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            SET_BITS(ifr, IFR_INT, 3);
-            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_READ);
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP2;
-            cdrom->delayed_command = CDL_READTOC;
-        } break;
-
-        case CD_STATE_SEND_RESP2: {
-            SET_BITS(ifr, IFR_INT, 2);
-            RESP_PUSH(GETSTAT_MOTOR);
-
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-void cdrom_cmd_videocd(psx_cdrom_t* cdrom) {
-    switch (cdrom->state) {
-        case CD_STATE_RECV_CMD: {
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_SEND_RESP1;
-            cdrom->delayed_command = CDL_VIDEOCD;
-            cdrom->pfifo_index = 0;
-        } break;
-
-        case CD_STATE_SEND_RESP1: {
-            printf("VideoCD task %02x\n", cdrom->pfifo[4]);
-            SET_BITS(ifr, IFR_INT, 3);
-
-            switch (cdrom->pfifo[4]) {
-                case 0: {
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(GETSTAT_MOTOR);
-                } break;
-
-                case 1: {
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x81);
-                    RESP_PUSH(GETSTAT_MOTOR);
-                } break;
-
-                case 2: {
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x00);
-                    RESP_PUSH(0x05);
-                    RESP_PUSH(GETSTAT_MOTOR);
-                } break;
-            }
-
-            cdrom->irq_delay = DELAY_1MS;
-            cdrom->state = CD_STATE_RECV_CMD;
-            cdrom->delayed_command = CDL_NONE;
-        } break;
-    }
-}
-
-typedef void (*cdrom_cmd_t)(psx_cdrom_t*);
-
-const char* g_psx_cdrom_command_names[] = {
-    "CdlUnimplemented",
-    "CdlGetstat",
-    "CdlSetloc",
-    "CdlPlay",
-    "CdlUnimplemented",
-    "CdlUnimplemented",
-    "CdlReadn",
-    "CdlMotoron",
-    "CdlStop",
-    "CdlPause",
-    "CdlInit",
-    "CdlMute",
-    "CdlUnmute",
-    "CdlSetfilter",
-    "CdlSetmode",
-    "CdlGetparam",
-    "CdlGetlocl",
-    "CdlGetlocp",
-    "CdlSetsession",
-    "CdlGettn",
-    "CdlGettd",
-    "CdlSeekl",
-    "CdlSeekp",
-    "CdlUnimplemented",
-    "CdlUnimplemented",
-    "CdlTest",
-    "CdlGetid",
-    "CdlReads",
-    "CdlUnimplemented",
-    "CdlUnimplemented",
-    "CdlReadtoc",
-    "CdlVideoCD"
-};
-
-cdrom_cmd_t g_psx_cdrom_command_table[] = {
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_getstat,
-    cdrom_cmd_setloc,
-    cdrom_cmd_play,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_readn,
-    cdrom_cmd_motoron,
-    cdrom_cmd_stop,
-    cdrom_cmd_pause,
-    cdrom_cmd_init,
-    cdrom_cmd_mute,
-    cdrom_cmd_unmute,
-    cdrom_cmd_setfilter,
-    cdrom_cmd_setmode,
-    cdrom_cmd_getparam,
-    cdrom_cmd_getlocl,
-    cdrom_cmd_getlocp,
-    cdrom_cmd_setsession,
-    cdrom_cmd_gettn,
-    cdrom_cmd_gettd,
-    cdrom_cmd_seekl,
-    cdrom_cmd_seekp,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_test,
-    cdrom_cmd_getid,
-    cdrom_cmd_reads,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_unimplemented,
-    cdrom_cmd_readtoc,
-    cdrom_cmd_videocd,
-
-    // Actually an unimplemented command, we use this
-    // index for CD error handling
-    cdrom_cmd_error
-};
-
-typedef uint8_t (*psx_cdrom_read_function_t)(psx_cdrom_t*);
-typedef void (*psx_cdrom_write_function_t)(psx_cdrom_t*, uint8_t);
-
-uint8_t cdrom_read_status(psx_cdrom_t* cdrom) {
-    return cdrom->status;
-}
-
-uint8_t cdrom_read_rfifo(psx_cdrom_t* cdrom) {
-    if (cdrom->rfifo_index < 0)
-        return 0;
-
-    uint8_t data = cdrom->rfifo[--cdrom->rfifo_index];
-
-    if (cdrom->rfifo_index == 0)
-        SET_BITS(status, STAT_RSLRRDY_MASK, 0);
-    
-    return data;
-}
-
-uint8_t cdrom_read_dfifo(psx_cdrom_t* cdrom) {
-    if (!cdrom->dfifo_full)
-        return 0;
-
-    int sector_size_bit = cdrom->mode & MODE_SECTOR_SIZE;
-
-    uint32_t sector_size = sector_size_bit ? 0x924 : 0x800;
-    uint32_t offset = sector_size_bit ? 12 : 24;
-
-    if (cdrom->dfifo_index < sector_size) {
-        SET_BITS(status, STAT_DRQSTS_MASK, STAT_DRQSTS_MASK);
-
-        uint8_t data = cdrom->dfifo[offset + (cdrom->dfifo_index++)];
-
-        if (cdrom->dfifo_index >= sector_size)
-            SET_BITS(status, STAT_DRQSTS_MASK, 0);
-
-        return data;
-    }
-
-    return 0;
-}
-
-uint8_t cdrom_read_ier(psx_cdrom_t* cdrom) {
-    return cdrom->ier;
-}
-
-uint8_t cdrom_read_ifr(psx_cdrom_t* cdrom) {
-    return 0xe0 | cdrom->ifr;
-}
-
-void cdrom_write_status(psx_cdrom_t* cdrom, uint8_t value) {
-    SET_BITS(status, STAT_INDEX_MASK, value);
-}
-
-void cdrom_write_cmd(psx_cdrom_t* cdrom, uint8_t value) {
-    printf("%s(%02x) %u params=[%02x, %02x, %02x, %02x, %02x, %02x]\n",
-        g_psx_cdrom_command_names[value],
-        value,
-        cdrom->pfifo_index,
-        cdrom->pfifo[0],
-        cdrom->pfifo[1],
-        cdrom->pfifo[2],
-        cdrom->pfifo[3],
-        cdrom->pfifo[4],
-        cdrom->pfifo[5]
-    );
-
-    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);
-}
-
-void cdrom_write_pfifo(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->pfifo[(cdrom->pfifo_index++) & 0xf] = value;
-
-    SET_BITS(status, STAT_PRMWRDY_MASK, (cdrom->pfifo_index & 0x10) ? 0x0 : 0xff);
-
-    cdrom->pfifo_index &= 0x1f;
-}
-
-void cdrom_write_req(psx_cdrom_t* cdrom, uint8_t value) {
-    if (value & REQ_BFRD) {
-        SET_BITS(status, STAT_DRQSTS_MASK, STAT_DRQSTS_MASK);
-
-        cdrom->dfifo_full = 1;
-    } else {
-        SET_BITS(status, STAT_DRQSTS_MASK, 0);
-
-        cdrom->dfifo_full = 0;
-    }
-}
-
-void cdrom_write_smdout(psx_cdrom_t* cdrom, uint8_t value) {
-    log_fatal("Sound map data out unimplemented");
-}
-
-void cdrom_write_ier(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->ier = value;
-}
-
-void cdrom_write_ifr(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->ifr &= ~(value & 0x1f);
-
-    // Clear Parameter FIFO
-    if (value & 0x40) {
-        cdrom->pfifo_index = 0;
-
-        SET_BITS(
-            status,
-            (STAT_PRMEMPT_MASK | STAT_PRMWRDY_MASK),
-            (STAT_PRMEMPT_MASK | STAT_PRMWRDY_MASK)
-        );
-    }
-}
-
-void cdrom_write_sminfo(psx_cdrom_t* cdrom, uint8_t value) {
-    log_fatal("Sound map coding info unimplemented");
-}
-
-void cdrom_write_lcdlspuv(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->vapp[0] = value;
-}
-
-void cdrom_write_rcdrspuv(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->vapp[3] = value;
-}
-
-void cdrom_write_rcdlspuv(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->vapp[2] = value;
-}
-
-void cdrom_write_lcdrspuv(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->vapp[1] = value;
-}
-
-void cdrom_write_volume(psx_cdrom_t* cdrom, uint8_t value) {
-    cdrom->xa_mute = value & 1;
-
-    if (value & 0x20) {
-        cdrom->vol[0] = cdrom->vapp[0];
-        cdrom->vol[1] = cdrom->vapp[1];
-        cdrom->vol[2] = cdrom->vapp[2];
-        cdrom->vol[3] = cdrom->vapp[3];
-    }
-}
-
-psx_cdrom_read_function_t g_psx_cdrom_read_table[] = {
-    cdrom_read_status, cdrom_read_rfifo, cdrom_read_dfifo, cdrom_read_ier,
-    cdrom_read_status, cdrom_read_rfifo, cdrom_read_dfifo, cdrom_read_ifr,
-    cdrom_read_status, cdrom_read_rfifo, cdrom_read_dfifo, cdrom_read_ier,
-    cdrom_read_status, cdrom_read_rfifo, cdrom_read_dfifo, cdrom_read_ifr
-};
-
-psx_cdrom_write_function_t g_psx_cdrom_write_table[] = {
-    cdrom_write_status, cdrom_write_cmd     , cdrom_write_pfifo   , cdrom_write_req     ,
-    cdrom_write_status, cdrom_write_smdout  , cdrom_write_ier     , cdrom_write_ifr     ,
-    cdrom_write_status, cdrom_write_sminfo  , cdrom_write_lcdlspuv, cdrom_write_lcdrspuv,
-    cdrom_write_status, cdrom_write_rcdrspuv, cdrom_write_rcdlspuv, cdrom_write_volume
-};
-
-const char* g_psx_cdrom_read_names_table[] = {
-    "cdrom_read_status", "cdrom_read_rfifo", "cdrom_read_dfifo", "cdrom_read_ier",
-    "cdrom_read_status", "cdrom_read_rfifo", "cdrom_read_dfifo", "cdrom_read_ifr",
-    "cdrom_read_status", "cdrom_read_rfifo", "cdrom_read_dfifo", "cdrom_read_ier",
-    "cdrom_read_status", "cdrom_read_rfifo", "cdrom_read_dfifo", "cdrom_read_ifr"
-};
-
-const char* g_psx_cdrom_write_names_table[] = {
-    "cdrom_write_status", "cdrom_write_cmd"     , "cdrom_write_pfifo"   , "cdrom_write_req"     ,
-    "cdrom_write_status", "cdrom_write_smdout"  , "cdrom_write_ier"     , "cdrom_write_ifr"     ,
-    "cdrom_write_status", "cdrom_write_sminfo"  , "cdrom_write_lcdlspuv", "cdrom_write_lcdrspuv",
-    "cdrom_write_status", "cdrom_write_rcdrspuv", "cdrom_write_rcdlspuv", "cdrom_write_volume"
-};
-
-psx_cdrom_t* psx_cdrom_create(void) {
-    return (psx_cdrom_t*)malloc(sizeof(psx_cdrom_t));
-}
-
-#define XA_STEREO_SAMPLES 2016
-#define XA_MONO_SAMPLES 4032
-#define XA_DECODED_SAMPLES 37632
-#define XA_RINGBUF_SIZE 32
-#define XA_STEREO_RESAMPLE_SIZE 2352
-#define XA_MONO_RESAMPLE_SIZE 4704
-
-void psx_cdrom_init(psx_cdrom_t* cdrom, psx_ic_t* ic) {
-    memset(cdrom, 0, sizeof(psx_cdrom_t));
-
-    cdrom->io_base = PSX_CDROM_BEGIN;
-    cdrom->io_size = PSX_CDROM_SIZE;
-
-    cdrom->ic = ic;
-    cdrom->status = STAT_PRMEMPT_MASK | STAT_PRMWRDY_MASK | STAT_RSLRRDY_MASK;
-    cdrom->dfifo = malloc(CD_SECTOR_SIZE);
-    cdrom->cdda_buf = malloc(CD_SECTOR_SIZE);
-
-    // Initialize XA state
-    cdrom->xa_sector_buf = malloc(CD_SECTOR_SIZE);
-    cdrom->xa_left_buf = malloc(XA_STEREO_SAMPLES * sizeof(int16_t));
-    cdrom->xa_right_buf = malloc(XA_STEREO_SAMPLES * sizeof(int16_t));
-    cdrom->xa_mono_buf = malloc(XA_MONO_SAMPLES * sizeof(int16_t));
-    cdrom->xa_upsample_buf = malloc(((14112 * 2) + 6) * sizeof(int16_t));
-    cdrom->xa_left_resample_buf = malloc((XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    cdrom->xa_right_resample_buf = malloc((XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    cdrom->xa_mono_resample_buf = malloc((XA_MONO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    cdrom->xa_step = 6;
-
-    // We will use this whenever we implement proper
-    // XA interpolation
-    (void)g_zigzag_table;
-
-    memset(cdrom->xa_left_buf, 0, XA_STEREO_SAMPLES * sizeof(int16_t));
-    memset(cdrom->xa_right_buf, 0, XA_STEREO_SAMPLES * sizeof(int16_t));
-    memset(cdrom->xa_mono_buf, 0, XA_MONO_SAMPLES * sizeof(int16_t));
-    memset(cdrom->xa_upsample_buf, 0, ((14112 * 2) + 6) * sizeof(int16_t));
-    memset(cdrom->xa_left_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    memset(cdrom->xa_right_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-    memset(cdrom->xa_mono_resample_buf, 0, (XA_MONO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-
-    cdrom->vol[0] = 0x80;
-    cdrom->vol[1] = 0x00;
-    cdrom->vol[2] = 0x80;
-    cdrom->vol[3] = 0x00;
-    cdrom->vapp[0] = 0x80;
-    cdrom->vapp[1] = 0x00;
-    cdrom->vapp[2] = 0x80;
-    cdrom->vapp[3] = 0x00;
-
-    cdrom->seek_msf.m = 0;
-    cdrom->seek_msf.s = 2;
-    cdrom->seek_msf.f = 0;
-}
-
-uint32_t psx_cdrom_read32(psx_cdrom_t* cdrom, uint32_t offset) {
-    // log_set_quiet(0);
-    log_fatal("Unhandled 32-bit CDROM read at offset %08x", offset);
-
-    // exit(1);
-
-    return 0x0;
-}
-
-uint16_t psx_cdrom_read16(psx_cdrom_t* cdrom, uint32_t offset) {
-    // log_set_quiet(0);
-    log_fatal("Unhandled 16-bit CDROM read at offset %08x", offset);
-
-    // exit(1);
-
-    return 0x0;
-}
-
-uint8_t psx_cdrom_read8(psx_cdrom_t* cdrom, uint32_t offset) {
-    uint8_t data = g_psx_cdrom_read_table[(STAT_INDEX << 2) | offset](cdrom);
-
-    if (((STAT_INDEX << 2) | offset) == 2)
-        return data;
-
-    // // log_set_quiet(0);
-    // log_fatal("%s (read %02x)", g_psx_cdrom_read_names_table[(STAT_INDEX << 2) | offset], data);
-    // log_set_quiet(1);
-
-    return data;
-}
-
-void psx_cdrom_write32(psx_cdrom_t* cdrom, uint32_t offset, uint32_t value) {
-    // log_set_quiet(0);
-    log_fatal("Unhandled 32-bit CDROM write at offset %08x (%08x)", offset, value);
-
-    // exit(1);
-}
-
-void psx_cdrom_write16(psx_cdrom_t* cdrom, uint32_t offset, uint16_t value) {
-    // log_set_quiet(0);
-    log_fatal("Unhandled 16-bit CDROM write at offset %08x (%04x)", offset, value);
-
-    // exit(1);
-}
-
-void psx_cdrom_write8(psx_cdrom_t* cdrom, uint32_t offset, uint8_t value) {
-    // // log_set_quiet(0);
-    // log_fatal("%s (write %02x)", g_psx_cdrom_write_names_table[(STAT_INDEX << 2) | offset], value);
-    // log_set_quiet(1);
-
-    g_psx_cdrom_write_table[(STAT_INDEX << 2) | offset](cdrom, value);
-}
-
-void psx_cdrom_update(psx_cdrom_t* cdrom, int cyc) {
-    if (cdrom->irq_delay) {
-        cdrom->irq_delay -= cyc;
-
-        if (cdrom->irq_delay <= 0) {
-            if (!cdrom->irq_disable) {
-                psx_ic_irq(cdrom->ic, IC_CDROM);
-            } else {
-                cdrom->irq_disable = 0;
-            }
-
-            cdrom->irq_delay = 0;
-
-            if (cdrom->delayed_command) {
-                // // log_set_quiet(0);
-                // log_fatal("%s(%02x) (Delayed)",
-                //     g_psx_cdrom_command_names[cdrom->delayed_command],
-                //     cdrom->delayed_command
-                // );
-                // log_set_quiet(1);
-                g_psx_cdrom_command_table[cdrom->delayed_command](cdrom);
-            }
-
-            // // log_set_quiet(0);
-            // log_fatal("CDROM INT%u", cdrom->ifr & 0x7);
-            // log_set_quiet(1);
-        }
-    }
-}
-
-const char* g_psx_cdrom_extensions[] = {
-    "cue",
-    "bin",
-    0
-};
-
-enum {
-    CD_EXT_CUE,
-    CD_EXT_BIN,
-    CD_EXT_UNSUPPORTED
-};
-
-int cdrom_get_extension(const char* path) {
-    const char* ptr = &path[strlen(path) - 1];
-    int i = 0;
-
-    while ((*ptr != '.') && (ptr != path))
-        ptr--;
-    
-    if (ptr == path)
-        return CD_EXT_UNSUPPORTED;
-
-    while (g_psx_cdrom_extensions[i]) {
-        if (!strcmp(ptr + 1, g_psx_cdrom_extensions[i]))
-            return i;
-        
-        ++i;
-    }
-
-    return CD_EXT_UNSUPPORTED;
-}
-
-void cdrom_check_cd_type(psx_cdrom_t* cdrom) {
-    char buf[CD_SECTOR_SIZE];
-
-    // Seek to Primary Volume Descriptor
-    msf_t pvd = { 0, 2, 16 };
-
-    // If the disc is smaller than 16 sectors
-    // then it can't be a PlayStation game.
-    // Audio discs should also have ISO volume
-    // descriptors, so it's probably something else
-    // entirely.
-    if (psx_disc_seek(cdrom->disc, pvd)) {
-        cdrom->cd_type = CDT_UNKNOWN;
-
-        return;
-    }
-
-    psx_disc_read_sector(cdrom->disc, buf);
-
-    // Check for the "PLAYSTATION" string at PVD offset 20h
-
-    // Patch 20 byte so comparison is done correctly
-    buf[0x2b] = 0;
-
-    if (strncmp(&buf[0x20], "PLAYSTATION", 12)) {
-        cdrom->cd_type = CDT_AUDIO;
-
-        return;
-    }
-
-    cdrom->cd_type = CDT_LICENSED;
-}
-
-void psx_cdrom_open(psx_cdrom_t* cdrom, const char* path) {
-    cdrom->disc = psx_disc_create();
-
-    int len = strlen(path);
-
-    char* lower = malloc(len + 1);
-
-    for (int i = 0; i < len; i++)
-        lower[i] = tolower(path[i]);
-
-    lower[len] = '\0';
-
-    int ext = cdrom_get_extension(lower);
-    int error = 0;
-
-    switch (ext) {
-        case CD_EXT_CUE: {
-            psxd_cue_t* cue = psxd_cue_create();
-
-            psxd_cue_init_disc(cue, cdrom->disc);
-            psxd_cue_init(cue);
-
-            error = psxd_cue_load(cue, path);
-
-            if (error)
-                break;
-
-            cdrom_check_cd_type(cdrom);
-        } break;
-
-        case CD_EXT_BIN: {
-            psxd_bin_t* bin = psxd_bin_create();
-
-            psxd_bin_init_disc(bin, cdrom->disc);
-            psxd_bin_init(bin);
-
-            error = psxd_bin_load(bin, path);
-
-            if (error)
-                break;
-
-            cdrom_check_cd_type(cdrom);
-        } break;
-
-        case CD_EXT_UNSUPPORTED: {
-            log_fatal("Unsupported disc format");
-
-            cdrom->cd_type = CDT_UNKNOWN;
-        } break;
-    }
-
-    free(lower);
-
-    if (error) {
-        log_fatal("Error loading file \'%s\'", path);
-
-        exit(1);
-    }
-}
-
-static const int g_spu_pos_adpcm_table[] = {
-    0, +60, +115, +98, +122
-};
-
-static const int g_spu_neg_adpcm_table[] = {
-    0,   0,  -52, -55,  -60
-};
-
-void cdrom_resample_xa_buf(psx_cdrom_t* cdrom, int16_t* dst, int16_t* src, int stereo, int16_t ls) {
-    int f18khz = ((cdrom->xa_sector_buf[0x13] >> 2) & 1) == 1;
-    int sample_count = stereo ? XA_STEREO_SAMPLES : XA_MONO_SAMPLES;
-    int resample_count = stereo ? XA_STEREO_RESAMPLE_SIZE : XA_MONO_RESAMPLE_SIZE;
-
-    resample_count *= f18khz + 1;
-
-    // Nearest neighbor
-    // for (int i = 0; i < sample_count; i++)
-    //     for (int k = 0; k < 7; k++)
-    //         cdrom->xa_upsample_buf[(i*7)+k] = src[i];
-
-    // Linear Upsampling
-    int16_t a = ls;
-    int16_t b = src[0];
-
-    for (int k = 0; k < 7; k++)
-        cdrom->xa_upsample_buf[k] = a + ((k+1)/8) * (b - a);
-
-    for (int i = 1; i < sample_count; i++) {
-        a = b;
-        b = src[i];
-
-        for (int k = 0; k < 7; k++)
-            cdrom->xa_upsample_buf[(i*7)+k] =
-                a + ((k+1)/8) * (b - a);
-    }
-
-    int m = f18khz ? 3 : 6;
-
-    for (int i = 0; i < resample_count; i++)
-        dst[i] = cdrom->xa_upsample_buf[i*m];
-
-    cdrom->xa_remaining_samples = resample_count;
-}
-
-void cdrom_decode_xa_block(psx_cdrom_t* cdrom, int idx, int blk, int nib, int16_t* buf, int16_t* h) {
-    int shift  = 12 - (cdrom->xa_sector_buf[idx + 4 + blk * 2 + nib] & 0x0F);
-    int filter =      (cdrom->xa_sector_buf[idx + 4 + blk * 2 + nib] & 0x30) >> 4;
-
-    int32_t f0 = g_spu_pos_adpcm_table[filter];
-    int32_t f1 = g_spu_neg_adpcm_table[filter];
-
-    for (int j = 0; j < 28; j++) {
-        uint16_t n = (cdrom->xa_sector_buf[idx + 16 + blk + j * 4] >> (nib * 4)) & 0x0f;
-
-        int16_t t = (int16_t)(n << 12) >> 12; 
-        int16_t s = (t << shift) + (((h[0] * f0) + (h[1] * f1) + 32) / 64);
-
-        s = (s < INT16_MIN) ? INT16_MIN : ((s > INT16_MAX) ? INT16_MAX : s);
-
-        h[1] = h[0];
-        h[0] = s;
-
-        buf[j] = s;
-    }
-}
-
-void cdrom_decode_xa_sector(psx_cdrom_t* cdrom, void* buf) {
-    int src = 24;
-
-    int16_t left[28];
-    int16_t right[28];
-
-    int16_t* left_ptr = cdrom->xa_left_buf;
-    int16_t* right_ptr = cdrom->xa_right_buf;
-    int16_t* mono_ptr = cdrom->xa_mono_buf;
-
-    for (int i = 0; i < 18; i++) {
-        for (int blk = 0; blk < 4; blk++) {
-            if (cdrom->xa_sector_buf[0x13] & 1) {
-                cdrom_decode_xa_block(cdrom, src, blk, 0, left, cdrom->xa_left_h);
-                cdrom_decode_xa_block(cdrom, src, blk, 1, right, cdrom->xa_right_h);
-
-                for (int i = 0; i < 28; i++) {
-                    *left_ptr++ = left[i];
-                    *right_ptr++ = right[i];
-                }
-            } else {
-                cdrom_decode_xa_block(cdrom, src, blk, 0, left, cdrom->xa_left_h);
-
-                for (int i = 0; i < 28; i++)
-                    *mono_ptr++ = left[i];
-
-                cdrom_decode_xa_block(cdrom, src, blk, 1, left, cdrom->xa_left_h);
-
-                for (int i = 0; i < 28; i++)
-                    *mono_ptr++ = left[i];
-            }
-        }
-
-        src += 128;
-    }
-}
-
-void cdrom_fetch_xa_sector(psx_cdrom_t* cdrom) {
-    while (true) {
-        if (psx_disc_seek(cdrom->disc, cdrom->xa_msf)) {
-            cdrom->xa_playing = 0;
-            cdrom->xa_remaining_samples = 0;
-
-            return;
-        }
-
-        psx_disc_read_sector(cdrom->disc, cdrom->xa_sector_buf);
-
-        msf_add_f(&cdrom->xa_msf, 1);
-
-        // Check for EOR bit
-        if (cdrom->xa_sector_buf[0x12] & 1)
-            return;
-
-        // Check Audio bit
-        if (!(cdrom->xa_sector_buf[0x12] & 4))
-            continue;
-
-        // If we get here it means this is a real-time audio sector.
-        // If the XA filter is disabled, we're done
-        if (!(cdrom->mode & MODE_XA_FILTER))
-            return;
-
-        // Else check XA file/channel
-        int file_eq = cdrom->xa_sector_buf[0x10] == cdrom->xa_file;
-        int channel_eq = cdrom->xa_sector_buf[0x11] == cdrom->xa_channel;
-
-        // If they are equal to our filter values, we're done
-        // else keep searching
-        if (file_eq && channel_eq)
-            return;
-    }
-}
-
-void cdrom_apply_volume_settings(psx_cdrom_t* cdrom) {
-    int16_t* ptr = cdrom->cdda_buf;
-
-    float ll_vol = (((float)cdrom->vol[0]) / 255.0f);
-    float lr_vol = (((float)cdrom->vol[1]) / 255.0f);
-    float rl_vol = (((float)cdrom->vol[2]) / 255.0f);
-    float rr_vol = (((float)cdrom->vol[3]) / 255.0f);
-
-    for (int i = 0; i < CD_SECTOR_SIZE >> 1; i += 2) {
-        ptr[i  ] = ptr[i  ] * ll_vol + ptr[i+1] * rl_vol;
-        ptr[i+1] = ptr[i+1] * rr_vol + ptr[i  ] * lr_vol;
-    }
-}
-
-void psx_cdrom_get_cdda_samples(psx_cdrom_t* cdrom, void* buf, int size, psx_spu_t* spu) {
-    memset(buf, 0, size);
-
-    if (!cdrom->disc)
-        return;
-
-    if (cdrom->xa_playing) {
-        int16_t* ptr = (int16_t*)buf;
-
-        for (int i = 0; i < (size >> 2); i++) {
-            int stereo = (cdrom->xa_sector_buf[0x13] & 1) == 1;
-
-            if (!cdrom->xa_remaining_samples) {
-                cdrom_fetch_xa_sector(cdrom);
-
-                if (cdrom->xa_sector_buf[0x12] & 0x01) {
-                    SET_BITS(status, STAT_ADPBUSY_MASK, 0);
-
-                    printf("Pausing XA-ADPCM playback\n");
-
-                    cdrom->xa_playing = 0;
-                    cdrom->xa_remaining_samples = 0;
-
-                    return;
-                }
-
-                msf_add_f(&cdrom->xa_current_msf, 1);
-
-                stereo = (cdrom->xa_sector_buf[0x13] & 1) == 1;
-
-                cdrom_decode_xa_sector(cdrom, buf);
-
-                memset(cdrom->xa_upsample_buf, 0, ((14112 * 2) + 6) * sizeof(int16_t));
-                memset(cdrom->xa_left_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-                memset(cdrom->xa_right_resample_buf, 0, (XA_STEREO_RESAMPLE_SIZE * 2) * sizeof(int16_t));
-
-                if (stereo) {
-                    cdrom_resample_xa_buf(cdrom, cdrom->xa_left_resample_buf, cdrom->xa_left_buf, stereo, cdrom->xa_last_left_sample);
-                    cdrom_resample_xa_buf(cdrom, cdrom->xa_right_resample_buf, cdrom->xa_right_buf, stereo, cdrom->xa_last_right_sample);
-                } else {
-                    cdrom_resample_xa_buf(cdrom, cdrom->xa_mono_resample_buf, cdrom->xa_mono_buf, stereo, cdrom->xa_last_mono_sample);
-                }
-
-                cdrom->xa_sample_idx = 0;
-            }
-
-            if (cdrom->xa_mute) {
-                *ptr++ = 0;
-                *ptr++ = 0;
-
-                return;
-            }
-
-            float ll_vol = (((float)cdrom->vol[0]) / 255.0f);
-            float rr_vol = (((float)cdrom->vol[3]) / 255.0f);
-
-            if (stereo) {
-                cdrom->xa_last_left_sample = cdrom->xa_left_resample_buf[cdrom->xa_sample_idx];
-                cdrom->xa_last_right_sample = cdrom->xa_right_resample_buf[cdrom->xa_sample_idx++];
-
-                float lr_vol = (((float)cdrom->vol[1]) / 255.0f);
-                float rl_vol = (((float)cdrom->vol[2]) / 255.0f);
-
-                *ptr++ = (cdrom->xa_last_left_sample * ll_vol) + (cdrom->xa_last_right_sample * rl_vol);
-                *ptr++ = (cdrom->xa_last_left_sample * lr_vol) + (cdrom->xa_last_right_sample * rr_vol);
-
-            } else {
-                cdrom->xa_last_mono_sample = cdrom->xa_mono_resample_buf[cdrom->xa_sample_idx++];
-
-                *ptr++ = cdrom->xa_last_mono_sample * ll_vol;
-                *ptr++ = cdrom->xa_last_mono_sample * rr_vol;
-            }
-
-            --cdrom->xa_remaining_samples;
-        }
-
-        return;
-    }
-
-    if (!cdrom->cdda_playing) {
-        memset(buf, 0, size);
-    
-        return;
-    }
-
-    // Seek to that address and read sector
-    if (psx_disc_seek(cdrom->disc, cdrom->cdda_msf))
-        cdrom->cdda_playing = 0;
-
-    psx_disc_read_sector(cdrom->disc, cdrom->cdda_buf);
-
-    ++cdrom->cdda_sectors_played;
-
-    // Increment sector
-    msf_add_f(&cdrom->cdda_msf, 1);
-
-    cdrom_apply_volume_settings(cdrom);
-
-    memcpy(buf, cdrom->cdda_buf, size);
-
-    psx_spu_update_cdda_buffer(spu, cdrom->cdda_buf);
-
-    // Handle report IRQ
-    if (cdrom->cdda_sectors_played == CD_SECTORS_PS) {
-        if (cdrom->mode & MODE_REPORT) {
-            SET_BITS(ifr, IFR_INT, 1);
-
-            msf_t track, current = cdrom->cdda_msf;
-
-            msf_from_bcd(&current);
-
-            psx_disc_get_track_addr(cdrom->disc, &track, cdrom->cdda_track);
-
-            unsigned int track_s = (track.m * 60) + track.s;
-            unsigned int current_s = (current.m * 60) + current.s;
-            unsigned int diff = current_s - track_s;
-
-            current.s = diff;
-            current.m = 0;
-
-            msf_adjust(&current);
-            //msf_to_bcd(&current);
-
-            RESP_PUSH(0);
-            RESP_PUSH(0);
-            RESP_PUSH(cdrom->cdda_msf.f);
-            RESP_PUSH(current.s | 0x80);
-            RESP_PUSH(current.m);
-            RESP_PUSH(0);
-            RESP_PUSH(cdrom->cdda_track);
-            RESP_PUSH(GETSTAT_PLAY);
-
-            psx_ic_irq(cdrom->ic, IC_CDROM);
-        }
-
-        cdrom->cdda_sectors_played = 0;
-    }
-}
-
-void psx_cdrom_destroy(psx_cdrom_t* cdrom) {
-    if (cdrom->disc)
-        psx_disc_destroy(cdrom->disc);
-
-    free(cdrom);
-}
\ No newline at end of file
--- a/psx/dev/cdrom.h
+++ /dev/null
@@ -1,334 +1,0 @@
-#ifndef CDROM_H
-#define CDROM_H
-
-#include <stdint.h>
-#include <stdio.h>
-
-#include "ic.h"
-#include "../disc.h"
-#include "../disc/cue.h"
-#include "../disc/bin.h"
-#include "../msf.h"
-#include "spu.h"
-
-#define DELAY_1MS (0xc4e1)
-// #define READ_SINGLE_DELAY (0x6e1cd)
-// #define READ_DOUBLE_DELAY (0x36cd2)
-// #define DELAY_1MS (PSX_CPU_CPS / 1000)
-#define READ_SINGLE_DELAY (PSX_CPU_CPS / 75)
-#define READ_DOUBLE_DELAY (PSX_CPU_CPS / (2 * 75))
-
-#define PSX_CDROM_BEGIN 0x1f801800
-#define PSX_CDROM_SIZE  0x4
-#define PSX_CDROM_END   0x1f801803
-
-enum {
-    CD_STATE_RECV_CMD,
-    CD_STATE_SEND_RESP1,
-    CD_STATE_SEND_RESP2,
-    CD_STATE_ERROR
-};
-
-#define CDL_NONE        0x00
-#define CDL_GETSTAT     0x01
-#define CDL_SETLOC      0x02
-#define CDL_PLAY        0x03
-#define CDL_FORWARD     0x04
-#define CDL_BACKWARD    0x05
-#define CDL_READN       0x06
-#define CDL_MOTORON     0x07
-#define CDL_STOP        0x08
-#define CDL_PAUSE       0x09
-#define CDL_INIT        0x0a
-#define CDL_MUTE        0x0b
-#define CDL_DEMUTE      0x0c
-#define CDL_SETFILTER   0x0d
-#define CDL_SETMODE     0x0e
-#define CDL_GETPARAM    0x0f
-#define CDL_GETLOCL     0x10
-#define CDL_GETLOCP     0x11
-#define CDL_SETSESSION  0x12
-#define CDL_GETTN       0x13
-#define CDL_GETTD       0x14
-#define CDL_SEEKL       0x15
-#define CDL_SEEKP       0x16
-#define CDL_TEST        0x19
-#define CDL_GETID       0x1a
-#define CDL_READS       0x1b
-#define CDL_RESET       0x1c
-#define CDL_GETQ        0x1d
-#define CDL_READTOC     0x1e
-#define CDL_VIDEOCD     0x1f
-#define CDL_ERROR       0x20
-
-#define STAT_INDEX_MASK   0x3
-#define STAT_ADPBUSY_MASK 0x4
-#define STAT_PRMEMPT_MASK 0x8
-#define STAT_PRMWRDY_MASK 0x10
-#define STAT_RSLRRDY_MASK 0x20
-#define STAT_DRQSTS_MASK  0x40
-#define STAT_BUSYSTS_MASK 0x80
-#define STAT_INDEX   (cdrom->status & STAT_INDEX_MASK)
-#define STAT_ADPBUSY (cdrom->status & STAT_ADPBUSY_MASK)
-#define STAT_PRMEMPT (cdrom->status & STAT_PRMEMPT_MASK)
-#define STAT_PRMWRDY (cdrom->status & STAT_PRMWRDY_MASK)
-#define STAT_RSLRRDY (cdrom->status & STAT_RSLRRDY_MASK)
-#define STAT_DRQSTS  (cdrom->status & STAT_DRQSTS_MASK)
-#define STAT_BUSYSTS (cdrom->status & STAT_BUSYSTS_MASK)
-#define SET_BITS(reg, mask, v) { cdrom->reg &= ~mask; cdrom->reg |= v & mask; }
-#define IFR_INT  0x07
-#define IFR_INT1 0x01
-#define IFR_INT2 0x02
-#define IFR_INT3 0x03
-#define IFR_INT4 0x04
-#define IFR_INT5 0x05
-#define IFR_INT6 0x06
-#define IFR_INT7 0x07
-
-#define GETSTAT_ERROR      0x01
-#define GETSTAT_MOTOR      0x02
-#define GETSTAT_SEEKERROR  0x04
-#define GETSTAT_IDERROR    0x08
-#define GETSTAT_TRAYOPEN   0x10
-#define GETSTAT_READ       0x20
-#define GETSTAT_SEEK       0x40
-#define GETSTAT_PLAY       0x80
-
-/*
-  7   Speed       (0=Normal speed, 1=Double speed)
-  6   XA-ADPCM    (0=Off, 1=Send XA-ADPCM sectors to SPU Audio Input)
-  5   Sector Size (0=800h=DataOnly, 1=924h=WholeSectorExceptSyncBytes)
-  4   Ignore Bit  (0=Normal, 1=Ignore Sector Size and Setloc position)
-  3   XA-Filter   (0=Off, 1=Process only XA-ADPCM sectors that match Setfilter)
-  2   Report      (0=Off, 1=Enable Report-Interrupts for Audio Play)
-  1   AutoPause   (0=Off, 1=Auto Pause upon End of Track) ;for Audio Play
-  0   CDDA        (0=Off, 1=Allow to Read CD-DA Sectors; ignore missing EDC)
-*/
-
-#define MODE_CDDA           0x01
-#define MODE_AUTOPAUSE      0x02
-#define MODE_REPORT         0x04
-#define MODE_XA_FILTER      0x08
-#define MODE_IGNORE         0x10
-#define MODE_SECTOR_SIZE    0x20
-#define MODE_XA_ADPCM       0x40
-#define MODE_SPEED          0x80
-
-/*
-  0-4 0    Not used (should be zero)
-  5   SMEN Want Command Start Interrupt on Next Command (0=No change, 1=Yes)
-  6   BFWR ...
-  7   BFRD Want Data         (0=No/Reset Data Fifo, 1=Yes/Load Data Fifo)
-*/
-
-#define REQ_SMEN 0x20
-#define REQ_BFWR 0x40
-#define REQ_BFRD 0x80
-
-/*
-  ___These values appear in the FIRST response; with stat.bit0 set___
-  10h - Invalid Sub_function (for command 19h), or invalid parameter value
-  20h - Wrong number of parameters
-  40h - Invalid command
-  80h - Cannot respond yet (eg. required info was not yet read from disk yet)
-           (namely, TOC not-yet-read or so)
-           (also appears if no disk inserted at all)
-  ___These values appear in the SECOND response; with stat.bit2 set___
-  04h - Seek failed (when trying to use SeekL on Audio CDs)
-  ___These values appear even if no command was sent; with stat.bit2 set___
-  08h - Drive door became opened
-*/
-
-#define ERR_INVSUBF 0x10
-#define ERR_PCOUNT  0x20
-#define ERR_INVALID 0x40
-#define ERR_BUSY    0x80
-#define ERR_SEEK    0x04
-#define ERR_LIDOPEN 0x08
-
-enum {
-    CDT_LICENSED,
-    CDT_AUDIO,
-    CDT_UNKNOWN
-};
-
-typedef struct {
-    uint32_t bus_delay;
-    uint32_t io_base, io_size;
-
-    psx_ic_t* ic;
-
-    uint8_t status;
-    uint8_t ifr;
-    uint8_t ier;
-
-    uint8_t pfifo[16];
-    uint8_t rfifo[16];
-    int pfifo_index;
-    int rfifo_index;
-
-    uint8_t* read_buf;
-    uint8_t* dfifo;
-    int dfifo_index;
-    int dfifo_full;
-
-    // GetStat bits
-    uint8_t stat;
-
-    // API
-    int tray_open;
-
-    // Setloc
-    msf_t seek_msf;
-    uint32_t seek_offset;
-    int seek_pending;
-
-    // Setmode
-    uint8_t mode;
-
-    int irq_delay;
-    int irq_disable;
-    uint8_t command;
-    uint8_t delayed_command;
-    uint8_t int_number;
-    int state;
-    int delayed_response;
-    int spin_delay;
-    uint8_t error;
-    uint8_t error_flags;
-    int ongoing_read_command;
-    int gettd_track;
-
-    // CDDA
-    uint8_t* cdda_buf;
-    int cdda_sector_offset;
-    msf_t cdda_msf;
-    int cdda_playing;
-    int cdda_sectors_played;
-    int cdda_track;
-
-    // XA-ADPCM
-    uint8_t* xa_sector_buf;
-    msf_t xa_msf;
-    msf_t xa_current_msf;
-    int xa_playing;
-    int xa_mute;
-    uint8_t xa_file;
-    uint8_t xa_channel;
-    uint8_t xa_coding;
-    int16_t xa_left_h[2];
-    int16_t xa_right_h[2];
-    int16_t* xa_left_buf;
-    int16_t* xa_right_buf;
-    int16_t* xa_mono_buf;
-    int16_t* xa_decoded_buf;
-    int16_t* xa_left_ring_buf;
-    int16_t* xa_right_ring_buf;
-    uint32_t xa_sample_idx;
-    int xa_remaining_samples;
-    uint32_t xa_step;
-    uint32_t xa_ringbuf_pos;
-    int16_t* xa_left_resample_buf;
-    int16_t* xa_right_resample_buf;
-    int16_t* xa_mono_resample_buf;
-    int16_t* xa_upsample_buf;
-    int16_t xa_last_left_sample;
-    int16_t xa_last_right_sample;
-    int16_t xa_last_mono_sample;
-    uint8_t vol[4];
-    uint8_t vapp[4];
-
-    const char* path;
-    psx_disc_t* disc;
-
-    int cd_type;
-} psx_cdrom_t;
-
-psx_cdrom_t* psx_cdrom_create(void);
-void psx_cdrom_init(psx_cdrom_t*, psx_ic_t*);
-uint32_t psx_cdrom_read32(psx_cdrom_t*, uint32_t);
-uint16_t psx_cdrom_read16(psx_cdrom_t*, uint32_t);
-uint8_t psx_cdrom_read8(psx_cdrom_t*, uint32_t);
-void psx_cdrom_write32(psx_cdrom_t*, uint32_t, uint32_t);
-void psx_cdrom_write16(psx_cdrom_t*, uint32_t, uint16_t);
-void psx_cdrom_write8(psx_cdrom_t*, uint32_t, uint8_t);
-void psx_cdrom_update(psx_cdrom_t*, int);
-void psx_cdrom_destroy(psx_cdrom_t*);
-void psx_cdrom_open(psx_cdrom_t*, const char*);
-void psx_cdrom_get_cdda_samples(psx_cdrom_t*, void*, int, psx_spu_t*);
-
-/*
-  Command          Parameters      Response(s)
-  00h -            -               INT5(11h,40h)  ;reportedly "Sync" uh?
-  01h Getstat      -               INT3(stat)
-  02h Setloc     E amm,ass,asect   INT3(stat)
-  03h Play       E (track)         INT3(stat), optional INT1(report bytes)
-  04h Forward    E -               INT3(stat), optional INT1(report bytes)
-  05h Backward   E -               INT3(stat), optional INT1(report bytes)
-  06h ReadN      E -               INT3(stat), INT1(stat), datablock
-  07h MotorOn    E -               INT3(stat), INT2(stat)
-  08h Stop       E -               INT3(stat), INT2(stat)
-  09h Pause      E -               INT3(stat), INT2(stat)
-  0Ah Init         -               INT3(late-stat), INT2(stat)
-  0Bh Mute       E -               INT3(stat)
-  0Ch Demute     E -               INT3(stat)
-  0Dh Setfilter  E file,channel    INT3(stat)
-  0Eh Setmode      mode            INT3(stat)
-  0Fh Getparam     -               INT3(stat,mode,null,file,channel)
-  10h GetlocL    E -               INT3(amm,ass,asect,mode,file,channel,sm,ci)
-  11h GetlocP    E -               INT3(track,index,mm,ss,sect,amm,ass,asect)
-  12h SetSession E session         INT3(stat), INT2(stat)
-  13h GetTN      E -               INT3(stat,first,last)  ;BCD
-  14h GetTD      E track (BCD)     INT3(stat,mm,ss)       ;BCD
-  15h SeekL      E -               INT3(stat), INT2(stat)  ;\use prior Setloc
-  16h SeekP      E -               INT3(stat), INT2(stat)  ;/to set target
-  17h -            -               INT5(11h,40h)  ;reportedly "SetClock" uh?
-  18h -            -               INT5(11h,40h)  ;reportedly "GetClock" uh?
-  19h Test         sub_function    depends on sub_function (see below)
-  1Ah GetID      E -               INT3(stat), INT2/5(stat,flg,typ,atip,"SCEx")
-  1Bh ReadS      E?-               INT3(stat), INT1(stat), datablock
-  1Ch Reset        -               INT3(stat), Delay            ;-not DTL-H2000
-  1Dh GetQ       E adr,point       INT3(stat), INT2(10bytesSubQ,peak_lo) ;\not
-  1Eh ReadTOC      -               INT3(late-stat), INT2(stat)           ;/vC0
-  1Fh VideoCD      sub,a,b,c,d,e   INT3(stat,a,b,c,d,e)   ;<-- SCPH-5903 only
-  1Fh..4Fh -       -               INT5(11h,40h)  ;-Unused/invalid
-  50h Secret 1     -               INT5(11h,40h)  ;\
-  51h Secret 2     "Licensed by"   INT5(11h,40h)  ;
-  52h Secret 3     "Sony"          INT5(11h,40h)  ; Secret Unlock Commands
-  53h Secret 4     "Computer"      INT5(11h,40h)  ; (not in version vC0, and,
-  54h Secret 5     "Entertainment" INT5(11h,40h)  ; nonfunctional in japan)
-  55h Secret 6     "<region>"      INT5(11h,40h)  ;
-  56h Secret 7     -               INT5(11h,40h)  ;/
-  57h SecretLock   -               INT5(11h,40h)  ;-Secret Lock Command
-  58h..5Fh Crash   -               Crashes the HC05 (jumps into a data area)
-  6Fh..FFh -       -               INT5(11h,40h)  ;-Unused/invalid
-*/
-
-void cdrom_cmd_unimplemented(psx_cdrom_t*);
-void cdrom_cmd_getstat(psx_cdrom_t*);
-void cdrom_cmd_setloc(psx_cdrom_t*);
-void cdrom_cmd_play(psx_cdrom_t*);
-void cdrom_cmd_readn(psx_cdrom_t*);
-void cdrom_cmd_motoron(psx_cdrom_t*);
-void cdrom_cmd_stop(psx_cdrom_t*);
-void cdrom_cmd_pause(psx_cdrom_t*);
-void cdrom_cmd_init(psx_cdrom_t*);
-void cdrom_cmd_mute(psx_cdrom_t*);
-void cdrom_cmd_unmute(psx_cdrom_t*);
-void cdrom_cmd_setfilter(psx_cdrom_t*);
-void cdrom_cmd_setmode(psx_cdrom_t*);
-void cdrom_cmd_getparam(psx_cdrom_t*);
-void cdrom_cmd_getlocl(psx_cdrom_t*);
-void cdrom_cmd_getlocp(psx_cdrom_t*);
-void cdrom_cmd_setsession(psx_cdrom_t*);
-void cdrom_cmd_gettn(psx_cdrom_t*);
-void cdrom_cmd_gettd(psx_cdrom_t*);
-void cdrom_cmd_seekl(psx_cdrom_t*);
-void cdrom_cmd_seekp(psx_cdrom_t*);
-void cdrom_cmd_test(psx_cdrom_t*);
-void cdrom_cmd_getid(psx_cdrom_t*);
-void cdrom_cmd_reads(psx_cdrom_t*);
-void cdrom_cmd_readtoc(psx_cdrom_t*);
-void cdrom_cmd_videocd(psx_cdrom_t*);
-
-#endif
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/audio.c
@@ -1,0 +1,402 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "cdrom.h"
+#include "../spu.h"
+
+#define ITOB(b) itob_table[b]
+
+static const uint8_t itob_table[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
+    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
+    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
+    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
+    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
+    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
+    0x96, 0x97, 0x98, 0x99, 0xa0, 0xa1, 0xa2, 0xa3,
+    0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xb0, 0xb1,
+    0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+    0xc8, 0xc9, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5,
+    0xd6, 0xd7, 0xd8, 0xd9, 0xe0, 0xe1, 0xe2, 0xe3,
+    0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xf0, 0xf1,
+    0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
+    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
+    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
+    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
+    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
+    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
+};
+
+static const int pos_adpcm_table[] = {
+    0, +60, +115, +98, +122
+};
+
+static const int neg_adpcm_table[] = {
+    0,   0,  -52, -55,  -60
+};
+
+void cdrom_resample_xa_buf(psx_cdrom_t* cdrom, int16_t* dst, int16_t* src, int stereo, int16_t ls) {
+    int f18khz = ((cdrom->xa_buf[0x13] >> 2) & 1) == 1;
+    int sample_count = stereo ? XA_STEREO_SAMPLES : XA_MONO_SAMPLES;
+    int resample_count = stereo ? XA_STEREO_RESAMPLE_SIZE : XA_MONO_RESAMPLE_SIZE;
+
+    resample_count *= f18khz + 1;
+
+    // Nearest neighbor
+    // for (int i = 0; i < sample_count; i++)
+    //     for (int k = 0; k < 7; k++)
+    //         cdrom->xa_upsample_buf[(i*7)+k] = src[i];
+
+    // Linear Upsampling
+    int16_t a = ls;
+    int16_t b = src[0];
+
+    for (int k = 0; k < 7; k++)
+        cdrom->xa_upsample_buf[k] = a + ((k+1)/8) * (b - a);
+
+    for (int i = 1; i < sample_count; i++) {
+        a = b;
+        b = src[i];
+
+        for (int k = 0; k < 7; k++)
+            cdrom->xa_upsample_buf[(i*7)+k] =
+                a + ((k+1)/8) * (b - a);
+    }
+
+    int m = f18khz ? 3 : 6;
+
+    for (int i = 0; i < resample_count; i++)
+        dst[i] = cdrom->xa_upsample_buf[i*m];
+
+    cdrom->xa_remaining_samples = resample_count;
+}
+
+void cdrom_decode_xa_block(psx_cdrom_t* cdrom, int idx, int blk, int nib, int16_t* buf, int16_t* h) {
+    int shift  = 12 - (cdrom->xa_buf[idx + 4 + blk * 2 + nib] & 0x0F);
+    int filter =      (cdrom->xa_buf[idx + 4 + blk * 2 + nib] & 0x30) >> 4;
+
+    int32_t f0 = pos_adpcm_table[filter];
+    int32_t f1 = neg_adpcm_table[filter];
+
+    for (int j = 0; j < 28; j++) {
+        uint16_t n = (cdrom->xa_buf[idx + 16 + blk + j * 4] >> (nib * 4)) & 0x0f;
+
+        int16_t t = (int16_t)(n << 12) >> 12; 
+        int16_t s = (t << shift) + (((h[0] * f0) + (h[1] * f1) + 32) / 64);
+
+        s = (s < INT16_MIN) ? INT16_MIN : ((s > INT16_MAX) ? INT16_MAX : s);
+
+        h[1] = h[0];
+        h[0] = s;
+
+        buf[j] = s;
+    }
+}
+
+void cdrom_decode_xa_sector(psx_cdrom_t* cdrom, void* buf) {
+    int src = 24;
+
+    int16_t left[28];
+    int16_t right[28];
+
+    int16_t* left_ptr = cdrom->xa_left_buf;
+    int16_t* right_ptr = cdrom->xa_right_buf;
+    int16_t* mono_ptr = cdrom->xa_mono_buf;
+
+    for (int i = 0; i < 18; i++) {
+        for (int blk = 0; blk < 4; blk++) {
+            if (cdrom->xa_buf[0x13] & 1) {
+                cdrom_decode_xa_block(cdrom, src, blk, 0, left, cdrom->xa_left_h);
+                cdrom_decode_xa_block(cdrom, src, blk, 1, right, cdrom->xa_right_h);
+
+                for (int i = 0; i < 28; i++) {
+                    *left_ptr++ = left[i];
+                    *right_ptr++ = right[i];
+                }
+            } else {
+                cdrom_decode_xa_block(cdrom, src, blk, 0, left, cdrom->xa_left_h);
+
+                for (int i = 0; i < 28; i++)
+                    *mono_ptr++ = left[i];
+
+                cdrom_decode_xa_block(cdrom, src, blk, 1, left, cdrom->xa_left_h);
+
+                for (int i = 0; i < 28; i++)
+                    *mono_ptr++ = left[i];
+            }
+        }
+
+        src += 128;
+    }
+}
+
+int cdrom_fetch_xa_sector(psx_cdrom_t* cdrom) {
+    while (1) {
+        int ts = psx_disc_read(cdrom->disc, cdrom->xa_lba, cdrom->xa_buf);
+
+        if (ts == TS_FAR)
+            return 0;
+
+        if (cdrom->xa_buf[0x12] & 1)
+            return 0;
+
+        ++cdrom->xa_lba;
+
+        // Check Audio bit
+        if (!(cdrom->xa_buf[0x12] & 4))
+            continue;
+
+        // If we get here it means this is a real-time audio sector.
+        // If the XA filter is disabled, we're done
+        if (!(cdrom->mode & MODE_XA_FILTER))
+            return 1;
+
+        // printf("fetch_xa_sector: lba=%u %02x:%02x:%02x mode=%02x file=%02x channel=%02x sm=%02x ci=%02x xafile=%02x xachannel=%02x\n",
+        //     cdrom->xa_lba,
+        //     cdrom->xa_buf[0x0c],
+        //     cdrom->xa_buf[0x0d],
+        //     cdrom->xa_buf[0x0e],
+        //     cdrom->xa_buf[0x0f],
+        //     cdrom->xa_buf[0x10],
+        //     cdrom->xa_buf[0x11],
+        //     cdrom->xa_buf[0x12],
+        //     cdrom->xa_buf[0x13],
+        //     cdrom->xa_file,
+        //     cdrom->xa_channel
+        // );
+
+        // Else check XA file/channel
+        int file_eq = cdrom->xa_buf[0x10] == cdrom->xa_file;
+        int channel_eq = cdrom->xa_buf[0x11] == cdrom->xa_channel;
+
+        // If they are equal to our filter values, we're done
+        // else keep searching
+        if (file_eq && channel_eq)
+            return 1;
+    }
+}
+
+int cdrom_get_xa_samples(psx_cdrom_t* cdrom, void* buf, size_t size) {
+    if ((!cdrom->xa_playing) || !(cdrom->mode & MODE_XA_ADPCM)) {
+        cdrom->xa_remaining_samples = 0;
+        cdrom->xa_sample_index = 0;
+
+        memset(buf, 0, size);
+
+        return 0;
+    }
+
+    float ll_vol = (((float)cdrom->vol[0]) / 255.0f);
+    float lr_vol = (((float)cdrom->vol[1]) / 255.0f);
+    float rr_vol = (((float)cdrom->vol[2]) / 255.0f);
+    float rl_vol = (((float)cdrom->vol[3]) / 255.0f);
+
+    int16_t* ptr = (int16_t*)buf;
+
+    for (int i = 0; i < (size >> 2); i++) {
+        int stereo = (cdrom->xa_buf[0x13] & 1) == 1;
+
+        if (!cdrom->xa_remaining_samples) {
+            if (!cdrom_fetch_xa_sector(cdrom)) {
+                cdrom->xa_playing = 0;
+                cdrom->xa_remaining_samples = 0;
+
+                return;
+            }
+
+            stereo = (cdrom->xa_buf[0x13] & 1) == 1;
+
+            cdrom_decode_xa_sector(cdrom, buf);
+
+            if (stereo) {
+                cdrom_resample_xa_buf(
+                    cdrom,
+                    cdrom->xa_left_resample_buf,
+                    cdrom->xa_left_buf,
+                    stereo,
+                    cdrom->xa_prev_left_sample
+                );
+
+                cdrom_resample_xa_buf(
+                    cdrom,
+                    cdrom->xa_right_resample_buf,
+                    cdrom->xa_right_buf,
+                    stereo,
+                    cdrom->xa_prev_right_sample
+                );
+            } else {
+                cdrom_resample_xa_buf(
+                    cdrom,
+                    cdrom->xa_mono_resample_buf,
+                    cdrom->xa_mono_buf,
+                    stereo,
+                    cdrom->xa_prev_left_sample
+                );
+            }
+
+            cdrom->xa_sample_index = 0;
+        }
+
+        if (cdrom->xa_mute || cdrom->mute) {
+            *ptr++ = 0;
+            *ptr++ = 0;
+
+            return;
+        }
+
+        if (stereo) {
+            cdrom->xa_prev_left_sample = cdrom->xa_left_resample_buf[cdrom->xa_sample_index];
+            cdrom->xa_prev_right_sample = cdrom->xa_right_resample_buf[cdrom->xa_sample_index++];
+
+            *ptr++ = (cdrom->xa_prev_left_sample * ll_vol) + (cdrom->xa_prev_right_sample * rl_vol);
+            *ptr++ = (cdrom->xa_prev_left_sample * lr_vol) + (cdrom->xa_prev_right_sample * rr_vol);
+
+        } else {
+            cdrom->xa_prev_left_sample = cdrom->xa_mono_resample_buf[cdrom->xa_sample_index++];
+
+            *ptr++ = cdrom->xa_prev_left_sample * ll_vol;
+            *ptr++ = cdrom->xa_prev_left_sample * rr_vol;
+        }
+
+        --cdrom->xa_remaining_samples;
+    }
+
+    return 1;
+}
+
+void cdrom_reload_cdda_buffer(psx_cdrom_t* cdrom, void* buf, size_t size) {
+    int ts = psx_disc_read(cdrom->disc, cdrom->lba, cdrom->cdda_buf);
+
+    // We hit the end of the disc
+    if (ts == TS_FAR) {
+        cdrom->cdda_remaining_samples = 0;
+        cdrom->cdda_sample_index = 0;
+
+        memset(buf, 0, size);
+
+        cdrom->state = CD_STATE_IDLE;
+
+        return;
+    }
+
+    // We hit pregap (end of previous track)
+    // if ((ts == TS_PREGAP) && (cdrom->mode & MODE_AUTOPAUSE)) {
+    //     cdrom->cdda_remaining_samples = 0;
+    //     cdrom->cdda_sample_index = 0;
+
+    //     memset(buf, 0, size);
+
+    //     cdrom->state = CD_STATE_IDLE;
+
+    //     return;
+    // }
+
+    cdrom->cdda_remaining_samples = CD_SECTOR_SIZE >> 1;
+    cdrom->cdda_sample_index = 0;
+
+    cdrom->pending_lba = ++cdrom->lba;
+}
+
+void cdrom_send_report_irq(psx_cdrom_t* cdrom) {
+    if (!(cdrom->mode & MODE_REPORT))
+        return;
+
+    cdrom_set_int(cdrom, 1);
+
+    int track = psx_disc_get_track_number(cdrom->disc, cdrom->lba);
+    int track_lba = psx_disc_get_track_lba(cdrom->disc, track);
+
+    int32_t diff = cdrom->lba - track_lba;
+
+    if (diff < 0)
+        diff = -diff;
+
+    int mm = diff / (60 * 75);
+    int ss = (diff % (60 * 75)) / 75;
+    int ff = (diff % (60 * 75)) % 75;
+
+    printf("report: track %u %02u:%02u:%02u\n",
+        track,
+        mm, ss, ff
+    );
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+    queue_push(cdrom->response, ITOB(track));
+    queue_push(cdrom->response, 1);
+    queue_push(cdrom->response, ITOB(mm));
+    queue_push(cdrom->response, ITOB(ss) | 0x80);
+    queue_push(cdrom->response, ITOB(ff));
+    queue_push(cdrom->response, 0);
+    queue_push(cdrom->response, 0);
+
+    psx_ic_irq(cdrom->ic, IC_CDROM);
+}
+
+void psx_cdrom_get_audio_samples(psx_cdrom_t* cdrom, void* buf, size_t size) {
+    if (!cdrom->disc)
+        return;
+
+    if (cdrom_get_xa_samples(cdrom, buf, size))
+        return;
+
+    if (!cdrom->cdda_playing) {
+        cdrom->cdda_remaining_samples = 0;
+        cdrom->cdda_sample_index = 0;
+
+        return;
+    }
+
+    int16_t* ptr = buf;
+
+    float ll_vol = (((float)cdrom->vol[0]) / 255.0f);
+    float lr_vol = (((float)cdrom->vol[1]) / 255.0f);
+    float rr_vol = (((float)cdrom->vol[2]) / 255.0f);
+    float rl_vol = (((float)cdrom->vol[3]) / 255.0f);
+
+    for (int i = 0; i < (size >> 1);) {
+        if (!cdrom->cdda_remaining_samples) {
+            cdrom_reload_cdda_buffer(cdrom, buf, size);
+
+            ++cdrom->cdda_sectors_played;
+
+            if (cdrom->cdda_sectors_played == 75) {
+                cdrom_send_report_irq(cdrom);
+
+                cdrom->cdda_sectors_played = 0;
+            }
+        }
+
+        if (cdrom->mute) {
+            ptr[i++] = 0;
+            ptr[i++] = 0;
+
+            cdrom->cdda_remaining_samples -= 2;
+            cdrom->cdda_sample_index += 2;
+
+            continue;
+        }
+
+        int16_t left = cdrom->cdda_buf[cdrom->cdda_sample_index++];
+        int16_t right = cdrom->cdda_buf[cdrom->cdda_sample_index++];
+
+        // Apply volume settings to CDDA
+        ptr[i++] = left  * ll_vol + right * rl_vol;
+        ptr[i++] = right * rr_vol + left  * lr_vol;
+
+        cdrom->cdda_remaining_samples -= 2;
+    }
+}
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/cdrom.c
@@ -1,0 +1,721 @@
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "cdrom.h"
+
+typedef void (*cdrom_cmd_func)(psx_cdrom_t* cdrom);
+
+void cdrom_cmd_getstat(psx_cdrom_t* cdrom);
+void cdrom_cmd_setloc(psx_cdrom_t* cdrom);
+void cdrom_cmd_play(psx_cdrom_t* cdrom);
+void cdrom_cmd_forward(psx_cdrom_t* cdrom);
+void cdrom_cmd_backward(psx_cdrom_t* cdrom);
+void cdrom_cmd_readn(psx_cdrom_t* cdrom);
+void cdrom_cmd_motoron(psx_cdrom_t* cdrom);
+void cdrom_cmd_stop(psx_cdrom_t* cdrom);
+void cdrom_cmd_pause(psx_cdrom_t* cdrom);
+void cdrom_cmd_init(psx_cdrom_t* cdrom);
+void cdrom_cmd_mute(psx_cdrom_t* cdrom);
+void cdrom_cmd_demute(psx_cdrom_t* cdrom);
+void cdrom_cmd_setfilter(psx_cdrom_t* cdrom);
+void cdrom_cmd_setmode(psx_cdrom_t* cdrom);
+void cdrom_cmd_getparam(psx_cdrom_t* cdrom);
+void cdrom_cmd_getlocl(psx_cdrom_t* cdrom);
+void cdrom_cmd_getlocp(psx_cdrom_t* cdrom);
+void cdrom_cmd_setsession(psx_cdrom_t* cdrom);
+void cdrom_cmd_gettn(psx_cdrom_t* cdrom);
+void cdrom_cmd_gettd(psx_cdrom_t* cdrom);
+void cdrom_cmd_seekl(psx_cdrom_t* cdrom);
+void cdrom_cmd_seekp(psx_cdrom_t* cdrom);
+void cdrom_cmd_test(psx_cdrom_t* cdrom);
+void cdrom_cmd_getid(psx_cdrom_t* cdrom);
+void cdrom_cmd_reads(psx_cdrom_t* cdrom);
+void cdrom_cmd_reset(psx_cdrom_t* cdrom);
+void cdrom_cmd_getq(psx_cdrom_t* cdrom);
+void cdrom_cmd_readtoc(psx_cdrom_t* cdrom);
+void cdrom_cmd_videocd(psx_cdrom_t* cdrom);
+
+cdrom_cmd_func cdrom_cmd_table[] = {
+    (cdrom_cmd_func)0,
+    cdrom_cmd_getstat,
+    cdrom_cmd_setloc,
+    cdrom_cmd_play,
+    cdrom_cmd_forward,
+    cdrom_cmd_backward,
+    cdrom_cmd_readn,
+    cdrom_cmd_motoron,
+    cdrom_cmd_stop,
+    cdrom_cmd_pause,
+    cdrom_cmd_init,
+    cdrom_cmd_mute,
+    cdrom_cmd_demute,
+    cdrom_cmd_setfilter,
+    cdrom_cmd_setmode,
+    cdrom_cmd_getparam,
+    cdrom_cmd_getlocl,
+    cdrom_cmd_getlocp,
+    cdrom_cmd_setsession,
+    cdrom_cmd_gettn,
+    cdrom_cmd_gettd,
+    cdrom_cmd_seekl,
+    cdrom_cmd_seekp,
+    (cdrom_cmd_func)0,
+    (cdrom_cmd_func)0,
+    cdrom_cmd_test,
+    cdrom_cmd_getid,
+    cdrom_cmd_reads,
+    cdrom_cmd_reset,
+    cdrom_cmd_getq,
+    cdrom_cmd_readtoc,
+    cdrom_cmd_videocd
+};
+
+static const char* cdrom_cmd_names[] = {
+    "<unimplemented>",
+    "CdlGetstat",
+    "CdlSetloc",
+    "CdlPlay",
+    "CdlForward",
+    "CdlBackward",
+    "CdlReadn",
+    "CdlMotoron",
+    "CdlStop",
+    "CdlPause",
+    "CdlInit",
+    "CdlMute",
+    "CdlDemute",
+    "CdlSetfilter",
+    "CdlSetmode",
+    "CdlGetparam",
+    "CdlGetlocl",
+    "CdlGetlocp",
+    "CdlSetsession",
+    "CdlGettn",
+    "CdlGettd",
+    "CdlSeekl",
+    "CdlSeekp",
+    "<unimplemented>",
+    "<unimplemented>",
+    "CdlTest",
+    "CdlGetid",
+    "CdlReads",
+    "CdlReset",
+    "CdlGetq",
+    "CdlReadtoc",
+    "CdlVideocd"
+};
+
+void cdrom_write_stat(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_cmd(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_null(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_parm(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_ier(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_ifr(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_req(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_vol0(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_vol1(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_vol2(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_vol3(psx_cdrom_t* cdrom, uint8_t data);
+void cdrom_write_vapp(psx_cdrom_t* cdrom, uint8_t data);
+
+psx_cdrom_t* psx_cdrom_create(void) {
+    return malloc(sizeof(psx_cdrom_t));
+}
+
+void psx_cdrom_init(psx_cdrom_t* cdrom, psx_ic_t* ic) {
+    memset(cdrom, 0, sizeof(psx_cdrom_t));
+
+    cdrom->io_base = PSX_CDROM_BEGIN;
+    cdrom->io_size = PSX_CDROM_SIZE;
+
+    cdrom->data = queue_create();
+    cdrom->response = queue_create();
+    cdrom->parameters = queue_create();
+    cdrom->ic = ic;
+
+    queue_init(cdrom->data, CD_SECTOR_SIZE);
+    queue_init(cdrom->response, 32);
+    queue_init(cdrom->parameters, 32);
+
+    cdrom->version = CDR_VERSION_C0A;
+    cdrom->region = CDR_REGION_AMERICA;
+
+    cdrom->vol[0] = 0x80;
+    cdrom->vol[1] = 0x00;
+    cdrom->vol[2] = 0x80;
+    cdrom->vol[3] = 0x00;
+
+    cdrom->pending_lba = 150;
+    cdrom->lba = 150;
+    cdrom->seek_precision = 1;
+    cdrom->fake_getlocl_data = 1;
+}
+
+void psx_cdrom_set_version(psx_cdrom_t* cdrom, int version) {
+    cdrom->version = version;
+}
+
+void psx_cdrom_set_region(psx_cdrom_t* cdrom, int region) {
+    cdrom->region = region;
+}
+
+void psx_cdrom_open(psx_cdrom_t* cdrom, const char* path) {
+    if (!path)
+        return;
+
+    cdrom->disc = psx_disc_create();
+    cdrom->disc_type = psx_disc_open(cdrom->disc, path);
+
+    if (cdrom->disc_type == CDT_ERROR)
+        psx_cdrom_close(cdrom);
+}
+
+void psx_cdrom_close(psx_cdrom_t* cdrom) {
+    if (cdrom->disc) {
+        psx_disc_destroy(cdrom->disc);
+
+        cdrom->disc = NULL;
+    }
+}
+
+void cdrom_process_setloc(psx_cdrom_t* cdrom) {
+    if (!cdrom->pending_lba)
+        return;
+
+    cdrom->lba = cdrom->pending_lba;
+
+    cdrom->pending_lba = 0;
+}
+
+void cdrom_set_int(psx_cdrom_t* cdrom, int n) {
+    cdrom->ifr = n;
+}
+
+int cdrom_get_read_delay(psx_cdrom_t* cdrom) {
+    return (cdrom->mode & MODE_SPEED) ? CD_DELAY_READ_DS : CD_DELAY_READ_SS;
+}
+
+int cdrom_get_pause_delay(psx_cdrom_t* cdrom) {
+    return (cdrom->mode & MODE_SPEED) ?
+        (CD_DELAY_1MS * (70/2)) : (CD_DELAY_1MS * 70);
+}
+
+int cdrom_get_seek_delay(psx_cdrom_t* cdrom, int ts) {
+    int delay = CD_DELAY_FR;
+
+    return delay;
+
+    delay += cdrom->pending_speed_switch_delay;
+    cdrom->pending_speed_switch_delay = 0;
+
+    // Ridiculous delays for seeking to an audio sector
+    // or out of the disk
+    if (ts == TS_FAR)   delay = 650 * CD_DELAY_1MS;
+    if (ts == TS_AUDIO) delay = 4000 * CD_DELAY_1MS;
+
+    return delay;
+}
+
+void cdrom_error(psx_cdrom_t* cdrom, uint8_t stat, uint8_t err) {
+    cdrom->ifr = 5;
+
+    queue_reset(cdrom->parameters);
+    queue_reset(cdrom->response);
+
+    if ((stat & CD_STAT_IDERROR) || (stat & CD_STAT_SEEKERROR)) {
+        queue_push(cdrom->response, stat);
+    } else {
+        queue_push(cdrom->response, CD_STAT_ERROR | stat);
+    }
+
+    queue_push(cdrom->response, err);
+
+    cdrom->prev_state = CD_STATE_IDLE;
+    cdrom->state = CD_STATE_IDLE;
+    cdrom->pending_command = 0;
+    cdrom->busy = 0;
+}
+
+void cdrom_handle_resp1(psx_cdrom_t* cdrom) {
+    cdrom->busy = 0;
+
+    // Check for no disc, some commands can be issued with no disc
+    // in the drive (e.g. Test, Setmode, Init, etc.)
+    // i.e. INT5(11h, 80h)
+    if (!cdrom->disc) {
+        switch (cdrom->pending_command) {
+            case CDL_SETLOC:
+            case CDL_PLAY:
+            case CDL_FORWARD:
+            case CDL_BACKWARD:
+            case CDL_READN:
+            case CDL_MOTORON:
+            case CDL_STOP:
+            case CDL_PAUSE:
+            case CDL_MUTE:
+            case CDL_DEMUTE:
+            case CDL_SETFILTER:
+            case CDL_GETLOCL:
+            case CDL_GETLOCP:
+            case CDL_SETSESSION:
+            case CDL_GETTN:
+            case CDL_GETTD:
+            case CDL_SEEKL:
+            case CDL_SEEKP:
+            case CDL_GETID:
+            case CDL_READS:
+            case CDL_GETQ: {
+                cdrom_error(cdrom, CD_STAT_SHELLOPEN, CD_ERR_NO_DISC);
+
+                return;
+            } break;
+        }
+    }
+
+    // Check for version-specific unsupported commands
+    switch (cdrom->pending_command) {
+        case CDL_RESET: {
+            if (cdrom->version == CDR_VERSION_01) {
+                cdrom_error(cdrom,
+                    CD_STAT_SPINDLE,
+                    CD_ERR_INVALID_COMMAND
+                );
+
+                return;
+            }
+        } break;
+
+        case CDL_GETQ:
+        case CDL_READTOC: {
+            if (cdrom->version < CDR_VERSION_C1A) {
+                cdrom_error(cdrom,
+                    CD_STAT_SPINDLE,
+                    CD_ERR_INVALID_COMMAND
+                );
+
+                return;
+            }
+        } break;
+    }
+
+    // Check for wrong number of parameters and invalid commands
+    // i.e. INT5(03h, 20h), INT5(03h, 40h)
+    switch (cdrom->pending_command) {
+        case CDL_GETSTAT:
+        case CDL_FORWARD:
+        case CDL_BACKWARD:
+        case CDL_READN:
+        case CDL_MOTORON:
+        case CDL_STOP:
+        case CDL_PAUSE:
+        case CDL_INIT:
+        case CDL_MUTE:
+        case CDL_DEMUTE:
+        case CDL_GETPARAM:
+        case CDL_GETLOCL:
+        case CDL_GETLOCP:
+        case CDL_GETTN:
+        case CDL_SEEKL:
+        case CDL_SEEKP:
+        case CDL_GETID:
+        case CDL_READS:
+        case CDL_RESET:
+        case CDL_READTOC: {
+            // These commands take no parameters
+            if (queue_size(cdrom->parameters)) {
+                cdrom_error(cdrom,
+                    CD_STAT_SPINDLE,
+                    CD_ERR_WRONG_PARAMETER_COUNT
+                );
+
+                return;
+            }
+        } break;
+
+        case CDL_SETLOC: {
+            // Setloc takes exactly 3 parameters
+            if (queue_size(cdrom->parameters) != 3) {
+                cdrom_error(cdrom,
+                    CD_STAT_SPINDLE,
+                    CD_ERR_WRONG_PARAMETER_COUNT
+                );
+
+                return;
+            }
+        } break;
+
+        case CDL_PLAY: {
+            // Play may take either 0 or 1 parameter
+            if (queue_size(cdrom->parameters) > 1) {
+                cdrom_error(cdrom,
+                    CD_STAT_SPINDLE,
+                    CD_ERR_WRONG_PARAMETER_COUNT
+                );
+
+                return;
+            }
+        } break;
+
+        case CDL_SETFILTER:
+        case CDL_GETQ: {
+            // Setfilter and GetQ both take exactly 2 parameters
+            if (queue_size(cdrom->parameters) != 2) {
+                cdrom_error(cdrom,
+                    CD_STAT_SPINDLE,
+                    CD_ERR_WRONG_PARAMETER_COUNT
+                );
+
+                return;
+            }
+        } break;
+
+        case CDL_SETMODE:
+        case CDL_GETTD:
+        case CDL_TEST: {
+            // Setmode and GetTD both take exactly 1 parameter
+
+            // Test may actually take additional parameters depending
+            // on the subfunction, but we only emulate subfunction 20h
+            // for now, which takes no extra parameters
+            if (queue_size(cdrom->parameters) != 1) {
+                cdrom_error(cdrom,
+                    CD_STAT_SPINDLE,
+                    CD_ERR_WRONG_PARAMETER_COUNT
+                );
+
+                return;
+            }
+        } break;
+
+        case CDL_VIDEOCD: {
+            // To-do: Check for model
+            // VideoCD is only supported on the SCPH-5903
+            // Should return invalid command normally
+
+            // VideoCD takes exactly 6 parameters
+            if (queue_size(cdrom->parameters) != 6) {
+                cdrom_error(cdrom,
+                    CD_STAT_SPINDLE,
+                    CD_ERR_WRONG_PARAMETER_COUNT
+                );
+
+                return;
+            }
+        } break;
+
+        default: {
+            // Invalid command
+            cdrom_error(cdrom,
+                CD_STAT_SPINDLE,
+                CD_ERR_INVALID_COMMAND
+            );
+
+            return;
+        } break;
+    }
+
+    // If everything is alright (i.e. disc present, valid command,
+    // correct number of parameters) then send "execute command"
+    cdrom_cmd_table[cdrom->pending_command](cdrom);
+}
+
+uint8_t cdrom_get_stat(psx_cdrom_t* cdrom) {
+    return ((cdrom->cdda_playing) ? CD_STAT_PLAY : 0) |
+           ((cdrom->read_ongoing) ? CD_STAT_READ : 0) |
+           ((cdrom->mode & 0x10) ? CD_STAT_IDERROR : 0) |
+           ((!cdrom->disc) ? CD_STAT_SHELLOPEN : 0) |
+           CD_STAT_SPINDLE;
+}
+
+void cdrom_handle_read(psx_cdrom_t* cdrom) {
+    cdrom_process_setloc(cdrom);
+
+    int ts = psx_disc_query(cdrom->disc, cdrom->lba);
+
+    // printf("ts=%u ", ts);
+
+    if (ts == TS_FAR) {
+        cdrom_error(cdrom,
+            CD_STAT_SPINDLE | CD_STAT_SEEKERROR,
+            CD_ERR_INVALID_SUBFUNCTION
+        );
+
+        return;
+    }
+
+    if (ts == TS_AUDIO) {
+        cdrom_error(cdrom,
+            CD_STAT_SPINDLE | CD_STAT_SEEKERROR,
+            CD_ERR_SEEK_FAILED
+        );
+
+        return;
+    }
+
+    cdrom_set_int(cdrom, 1);
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    // Read sector into our data FIFO
+    psx_disc_read(cdrom->disc, cdrom->lba, cdrom->data->buf);
+
+    int size_bit = cdrom->mode & MODE_SECTOR_SIZE;
+
+    cdrom->data->read_index = size_bit ? 12 : 24;
+    cdrom->data->write_index = size_bit ? 0x924 : 0x800;
+    cdrom->data->write_index += cdrom->data->read_index;
+
+    cdrom->pending_lba = cdrom->lba + 1;
+    cdrom->delay = cdrom_get_read_delay(cdrom);
+
+    // printf("size=%x off=%u lba=%d: %02x:%02x:%02x delay=%u\n",
+    //     cdrom->data->write_index,
+    //     cdrom->data->read_index,
+    //     cdrom->lba,
+    //     cdrom->data->buf[0xc],
+    //     cdrom->data->buf[0xd],
+    //     cdrom->data->buf[0xe],
+    //     cdrom->data->buf[0xf],
+    //     cdrom->delay
+    // );
+}
+
+void psx_cdrom_update(psx_cdrom_t* cdrom, int cycles) {
+    if (cdrom->delay > 0) {
+        cdrom->delay -= cycles;
+
+        if (cdrom->delay > 0)
+            return;
+    }
+
+    cdrom->delay = 0;
+
+    if (cdrom->state == CD_STATE_IDLE)
+        return;
+
+    if (cdrom->state == CD_STATE_PLAY)
+        return;
+
+    psx_ic_irq(cdrom->ic, IC_CDROM);
+
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_handle_resp1(cdrom);
+
+        switch (cdrom->pending_command) {
+            case CDL_READN:
+            case CDL_READS:
+                return;
+        }
+
+        // Switching to read mode after executing a command
+        // has a 500ms penalty
+        if (cdrom->state == CD_STATE_READ) {
+            cdrom_process_setloc(cdrom);
+
+            int ts = psx_disc_query(cdrom->disc, cdrom->lba);
+            
+            cdrom->state = CD_STATE_READ;
+            cdrom->prev_state = CD_STATE_READ;
+            cdrom->delay = CD_DELAY_ONGOING_READ;
+        }
+
+        return;
+    }
+
+    if (cdrom->state == CD_STATE_TX_RESP2) {
+        cdrom_cmd_table[cdrom->pending_command](cdrom);
+
+        // Switching to read mode after executing a command
+        // has a 500ms penalty
+        if (cdrom->state == CD_STATE_READ) {
+            cdrom_process_setloc(cdrom);
+
+            int ts = psx_disc_query(cdrom->disc, cdrom->lba);
+            
+            cdrom->state = CD_STATE_READ;
+            cdrom->prev_state = CD_STATE_READ;
+            cdrom->delay = CD_DELAY_ONGOING_READ;
+        }
+
+        return;
+    }
+
+    if (cdrom->state == CD_STATE_READ) {
+        cdrom_handle_read(cdrom);
+
+        return;
+    }
+}
+
+uint8_t cdrom_read_status(psx_cdrom_t* cdrom) {
+    int data_empty = queue_is_empty(cdrom->data) || !cdrom->data_req;
+
+    return (cdrom->index                        << 0) |
+           (cdrom->xa_playing                   << 2) |
+           (queue_is_empty(cdrom->parameters)   << 3) |
+           ((!queue_is_full(cdrom->parameters)) << 4) |
+           ((!queue_is_empty(cdrom->response))  << 5) |
+           ((!data_empty)                       << 6) |
+           (cdrom->busy                         << 7);
+}
+
+uint8_t cdrom_read_data(psx_cdrom_t* cdrom) {
+    if (!cdrom->data_req)
+        return 0;
+
+    // printf("read=%x write=%x data=%02x |%c|\n",
+    //     cdrom->data->read_index,
+    //     cdrom->data->write_index,
+    //     queue_peek(cdrom->data),
+    //     isprint(queue_peek(cdrom->data)) ? queue_peek(cdrom->data) : '.'
+    // );
+
+    return queue_pop(cdrom->data);
+}
+
+uint32_t psx_cdrom_read8(psx_cdrom_t* cdrom, uint32_t addr) {
+    switch (addr) {
+        case 0: return cdrom_read_status(cdrom);
+        case 1: return queue_pop(cdrom->response);
+        case 2: return cdrom_read_data(cdrom);
+        case 3: return (cdrom->index & 1) ? (0xe0 | cdrom->ifr) : cdrom->ier;
+    }
+}
+void psx_cdrom_write8(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value) {
+    switch ((cdrom->index << 2) | addr) {
+        case 0: cdrom_write_stat(cdrom, value); break;
+        case 1: cdrom_write_cmd(cdrom, value); break;
+        case 2: cdrom_write_parm(cdrom, value); break;
+        case 3: cdrom_write_req(cdrom, value); break;
+        case 4: cdrom_write_stat(cdrom, value); break;
+        case 5: cdrom_write_null(cdrom, value); break;
+        case 6: cdrom_write_ier(cdrom, value); break;
+        case 7: cdrom_write_ifr(cdrom, value); break;
+        case 8: cdrom_write_stat(cdrom, value); break;
+        case 9: cdrom_write_null(cdrom, value); break;
+        case 10: cdrom_write_vol0(cdrom, value); break;
+        case 11: cdrom_write_vol1(cdrom, value); break;
+        case 12: cdrom_write_stat(cdrom, value); break;
+        case 13: cdrom_write_vol2(cdrom, value); break;
+        case 14: cdrom_write_vol3(cdrom, value); break;
+        case 15: cdrom_write_vapp(cdrom, value); break;
+    }
+}
+
+void psx_cdrom_destroy(psx_cdrom_t* cdrom) {
+    psx_cdrom_close(cdrom);
+    free(cdrom);
+}
+
+void cdrom_write_stat(psx_cdrom_t* cdrom, uint8_t data) {
+    cdrom->index = data & 3;
+}
+
+void cdrom_write_cmd(psx_cdrom_t* cdrom, uint8_t data) {
+    cdrom->prev_state = cdrom->state;
+    cdrom->state = CD_STATE_TX_RESP1;
+
+    cdrom->pending_command = data;
+
+    switch (cdrom->pending_command) {
+        case CDL_INIT:
+            cdrom->delay = CD_DELAY_INIT_FR;
+        break;
+
+        default:
+            cdrom->delay = CD_DELAY_FR;
+        break;
+    }
+
+    if (cdrom->state == CD_STATE_READ)
+        cdrom->busy = 1;
+
+    printf("cdrom: %-16s (%02x) params: ", cdrom_cmd_names[data], data);
+
+    if (queue_is_empty(cdrom->parameters)) {
+        puts("(none)");
+
+        return;
+    }
+
+    for (int i = 0; i < cdrom->parameters->write_index; i++)
+        printf("%02x ", cdrom->parameters->buf[i]);
+
+    putchar('\n');
+
+    return;
+}
+
+void cdrom_write_null(psx_cdrom_t* cdrom, uint8_t data) {
+    /* Ignore writes */
+}
+
+void cdrom_write_parm(psx_cdrom_t* cdrom, uint8_t data) {
+    queue_push(cdrom->parameters, data);
+}
+
+void cdrom_write_ier(psx_cdrom_t* cdrom, uint8_t data) {
+    cdrom->ier = data;
+}
+
+void cdrom_write_ifr(psx_cdrom_t* cdrom, uint8_t data) {
+    if (data & 0x40)
+        queue_clear(cdrom->parameters);
+
+    cdrom->ifr &= ~(data & 0x1f);
+
+    // If an INT is acknowledged, then the response
+    // FIFO is cleared
+    // if ((cdrom->ifr & 0x1f) == 0)
+    //     queue_clear(cdrom->response);
+}
+
+void cdrom_write_req(psx_cdrom_t* cdrom, uint8_t data) {
+    cdrom->data_req = data & 0x80;
+}
+
+void cdrom_write_vol0(psx_cdrom_t* cdrom, uint8_t data) {
+    cdrom->vol_pending[0] = data;
+}
+
+void cdrom_write_vol1(psx_cdrom_t* cdrom, uint8_t data) {
+    cdrom->vol_pending[1] = data;
+}
+
+void cdrom_write_vol2(psx_cdrom_t* cdrom, uint8_t data) {
+    cdrom->vol_pending[2] = data;
+}
+
+void cdrom_write_vol3(psx_cdrom_t* cdrom, uint8_t data) {
+    cdrom->vol_pending[3] = data;
+}
+
+void cdrom_write_vapp(psx_cdrom_t* cdrom, uint8_t data) {
+    cdrom->xa_mute = data & 1;
+    cdrom->vol[0] = cdrom->vol_pending[0];
+    cdrom->vol[1] = cdrom->vol_pending[1];
+    cdrom->vol[2] = cdrom->vol_pending[2];
+    cdrom->vol[3] = cdrom->vol_pending[3];
+}
+
+uint32_t psx_cdrom_read32(psx_cdrom_t* cdrom, uint32_t addr) {
+    assert(("32-bit CDROM reads are not supported", 0));
+}
+
+uint32_t psx_cdrom_read16(psx_cdrom_t* cdrom, uint32_t addr) {
+    assert(("16-bit CDROM reads are not supported", 0));
+
+    // The CDROM controller is connected to the SUB-BUS which is a 16-bit
+    // bus, but the output from the controller itself is 8-bit. I think
+    // 16-bit accesses are handled as a pair of 8-bit accesses
+    return psx_cdrom_read8(cdrom, addr) << 8 | psx_cdrom_read8(cdrom, addr+1);
+}
+
+void psx_cdrom_write32(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value) {
+    assert(("32-bit CDROM writes are not supported", 0));
+}
+
+void psx_cdrom_write16(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value) {
+    assert(("16-bit CDROM writes are not supported", 0));
+}
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/cdrom.h
@@ -1,0 +1,306 @@
+#ifndef CDROM_H
+#define CDROM_H
+
+#include <stdint.h>
+
+#include "queue.h"
+#include "disc.h"
+#include "../ic.h"
+
+#define PSX_CDROM_BEGIN 0x1f801800
+#define PSX_CDROM_END   0x1f801803
+#define PSX_CDROM_SIZE  0x4
+
+/*
+    Command                Average   Min       Max
+    GetStat (normal)       000c4e1h  0004a73h..003115bh
+    GetStat (when stopped) 0005cf4h  000483bh..00093f2h
+    Pause (single speed)   021181ch  020eaefh..0216e3ch ;\time equal to
+    Pause (double speed)   010bd93h  010477Ah..011B302h ;/about 5 sectors
+    Pause (when paused)    0001df2h  0001d25h..0001f22h
+    Stop (single speed)    0d38acah  0c3bc41h..0da554dh
+    Stop (double speed)    18a6076h  184476bh..192b306h
+    Stop (when stopped)    0001d7bh  0001ce8h..0001eefh
+    GetID                  0004a00h  0004922h..0004c2bh
+    Init                   0013cceh  000f820h..00xxxxxh
+*/
+
+#define CD_DELAY_1MS 33869
+#define CD_DELAY_FR 50401
+#define CD_DELAY_INIT_FR 81102
+#define CD_DELAY_PAUSE_SS 2168860
+#define CD_DELAY_PAUSE_DS 1097107
+#define CD_DELAY_STOP_SS 13863626
+#define CD_DELAY_STOP_DS 25845878
+#define CD_DELAY_READ_SS (33868800 / 75)
+#define CD_DELAY_READ_DS (33868800 / (2*75))
+#define CD_DELAY_START_READ (cdrom_get_read_delay(cdrom))
+#define CD_DELAY_ONGOING_READ (cdrom_get_read_delay(cdrom) + (CD_DELAY_1MS * 4))
+
+#define XA_STEREO_SAMPLES 2016 // Samples per sector
+#define XA_MONO_SAMPLES 4032 // Samples per sector
+#define XA_STEREO_RESAMPLE_SIZE 2352 // 2352 * 2
+#define XA_MONO_RESAMPLE_SIZE 4704 // 4704 * 2
+#define XA_UPSAMPLE_SIZE 28224 // 4032 * 7
+#define XA_RINGBUF_SIZE 32
+
+/*
+    7   Speed       (0=Normal speed, 1=Double speed)
+    6   XA-ADPCM    (0=Off, 1=Send XA-ADPCM sectors to SPU Audio Input)
+    5   Sector Size (0=800h=DataOnly, 1=924h=WholeSectorExceptSyncBytes)
+    4   Ignore Bit  (0=Normal, 1=Ignore Sector Size and Setloc position)
+    3   XA-Filter   (0=Off, 1=Process only XA-ADPCM sectors that match Setfilter)
+    2   Report      (0=Off, 1=Enable Report-Interrupts for Audio Play)
+    1   AutoPause   (0=Off, 1=Auto Pause upon End of Track) ;for Audio Play
+    0   CDDA        (0=Off, 1=Allow to Read CD-DA Sectors; ignore missing EDC)
+*/
+
+#define MODE_CDDA           0x01
+#define MODE_AUTOPAUSE      0x02
+#define MODE_REPORT         0x04
+#define MODE_XA_FILTER      0x08
+#define MODE_IGNORE         0x10
+#define MODE_SECTOR_SIZE    0x20
+#define MODE_XA_ADPCM       0x40
+#define MODE_SPEED          0x80
+
+/*
+    Command          Parameters      Response(s)
+    00h -            -               INT5(11h,40h)  ;reportedly "Sync" uh?
+    01h Getstat      -               INT3(stat)
+    02h Setloc     E amm,ass,asect   INT3(stat)
+    03h Play       E (track)         INT3(stat), optional INT1(report bytes)
+    04h Forward    E -               INT3(stat), optional INT1(report bytes)
+    05h Backward   E -               INT3(stat), optional INT1(report bytes)
+    06h ReadN      E -               INT3(stat), INT1(stat), datablock
+    07h MotorOn    E -               INT3(stat), INT2(stat)
+    08h Stop       E -               INT3(stat), INT2(stat)
+    09h Pause      E -               INT3(stat), INT2(stat)
+    0Ah Init         -               INT3(late-stat), INT2(stat)
+    0Bh Mute       E -               INT3(stat)
+    0Ch Demute     E -               INT3(stat)
+    0Dh Setfilter  E file,channel    INT3(stat)
+    0Eh Setmode      mode            INT3(stat)
+    0Fh Getparam     -               INT3(stat,mode,null,file,channel)
+    10h GetlocL    E -               INT3(amm,ass,asect,mode,file,channel,sm,ci)
+    11h GetlocP    E -               INT3(track,index,mm,ss,sect,amm,ass,asect)
+    12h SetSession E session         INT3(stat), INT2(stat)
+    13h GetTN      E -               INT3(stat,first,last)  ;BCD
+    14h GetTD      E track (BCD)     INT3(stat,mm,ss)       ;BCD
+    15h SeekL      E -               INT3(stat), INT2(stat)  ;\use prior Setloc
+    16h SeekP      E -               INT3(stat), INT2(stat)  ;/to set target
+    17h -            -               INT5(11h,40h)  ;reportedly "SetClock" uh?
+    18h -            -               INT5(11h,40h)  ;reportedly "GetClock" uh?
+    19h Test         sub_function    depends on sub_function (see below)
+    1Ah GetID      E -               INT3(stat), INT2/5(stat,flg,typ,atip,"SCEx")
+    1Bh ReadS      E?-               INT3(stat), INT1(stat), datablock
+    1Ch Reset        -               INT3(stat), Delay
+    1Dh GetQ       E adr,point       INT3(stat), INT2(10bytesSubQ,peak_lo) ;\not
+    1Eh ReadTOC      -               INT3(late-stat), INT2(stat)           ;/vC0
+    1Fh VideoCD      sub,a,b,c,d,e   INT3(stat,a,b,c,d,e)   ;<-- SCPH-5903 only
+    1Fh..4Fh -       -               INT5(11h,40h)  ;-Unused/invalid
+    50h Secret 1     -               INT5(11h,40h)  ;\
+    51h Secret 2     "Licensed by"   INT5(11h,40h)  ;
+    52h Secret 3     "Sony"          INT5(11h,40h)  ; Secret Unlock Commands
+    53h Secret 4     "Computer"      INT5(11h,40h)  ; (not in version vC0, and,
+    54h Secret 5     "Entertainment" INT5(11h,40h)  ; nonfunctional in japan)
+    55h Secret 6     "<region>"      INT5(11h,40h)  ;
+    56h Secret 7     -               INT5(11h,40h)  ;/
+    57h SecretLock   -               INT5(11h,40h)  ;-Secret Lock Command
+    58h..5Fh Crash   -               Crashes the HC05 (jumps into a data area)
+    6Fh..FFh -       -               INT5(11h,40h)  ;-Unused/invalid
+*/
+
+#define CDL_GETSTAT    0x01
+#define CDL_SETLOC     0x02
+#define CDL_PLAY       0x03
+#define CDL_FORWARD    0x04
+#define CDL_BACKWARD   0x05
+#define CDL_READN      0x06
+#define CDL_MOTORON    0x07
+#define CDL_STOP       0x08
+#define CDL_PAUSE      0x09
+#define CDL_INIT       0x0a
+#define CDL_MUTE       0x0b
+#define CDL_DEMUTE     0x0c
+#define CDL_SETFILTER  0x0d
+#define CDL_SETMODE    0x0e
+#define CDL_GETPARAM   0x0f
+#define CDL_GETLOCL    0x10
+#define CDL_GETLOCP    0x11
+#define CDL_SETSESSION 0x12
+#define CDL_GETTN      0x13
+#define CDL_GETTD      0x14
+#define CDL_SEEKL      0x15
+#define CDL_SEEKP      0x16
+#define CDL_TEST       0x19
+#define CDL_GETID      0x1a
+#define CDL_READS      0x1b
+#define CDL_RESET      0x1c
+#define CDL_GETQ       0x1d
+#define CDL_READTOC    0x1e
+#define CDL_VIDEOCD    0x1f
+
+/*
+    ___These values appear in the FIRST response; with stat.bit0 set___
+    10h - Invalid Sub_function (for command 19h), or invalid parameter value
+    20h - Wrong number of parameters (most CD commands need an exact number of parameters)
+    40h - Invalid command
+    80h - Cannot respond yet (eg. required info was not yet read from disk yet)
+            (namely, TOC not-yet-read or so)
+            (also appears if no disk inserted at all)
+    ___These values appear in the SECOND response; with stat.bit2 set___
+    04h - Seek failed (when trying to use SeekL on Audio CDs)
+    ___These values appear even if no command was sent; with stat.bit2 set___
+    08h - Drive door became opened
+*/
+
+#define CD_ERR_SEEK_FAILED           0x04
+#define CD_ERR_DOOR_OPENED           0x08
+#define CD_ERR_INVALID_SUBFUNCTION   0x10
+#define CD_ERR_WRONG_PARAMETER_COUNT 0x20
+#define CD_ERR_INVALID_COMMAND       0x40
+#define CD_ERR_NO_DISC               0x80
+
+/*
+    7  Play          Playing CD-DA         ;\only ONE of these bits can be set
+    6  Seek          Seeking               ; at a time (ie. Read/Play won't get
+    5  Read          Reading data sectors  ;/set until after Seek completion)
+    4  ShellOpen     Once shell open (0=Closed, 1=Is/was Open)
+    3  IdError       (0=Okay, 1=GetID denied) (also set when Setmode.Bit4=1)
+    2  SeekError     (0=Okay, 1=Seek error)     (followed by Error Byte)
+    1  Spindle Motor (0=Motor off, or in spin-up phase, 1=Motor on)
+    0  Error         Invalid Command/parameters (followed by Error Byte)
+*/
+
+#define CD_STAT_PLAY        0x80
+#define CD_STAT_SEEK        0x40
+#define CD_STAT_READ        0x20
+#define CD_STAT_SHELLOPEN   0x10
+#define CD_STAT_IDERROR     0x08
+#define CD_STAT_SEEKERROR   0x04
+#define CD_STAT_SPINDLE     0x02
+#define CD_STAT_ERROR       0x01
+
+// #define SET_INT(n) cdrom->ifr = n;
+
+enum {
+    CD_STATE_IDLE,
+    CD_STATE_TX_RESP1,
+    CD_STATE_TX_RESP2,
+    CD_STATE_READ,
+    CD_STATE_PLAY
+};
+
+enum {
+    QUERY_TRACK_COUNT,
+    QUERY_TRACK_ADDR,
+    QUERY_TRACK_TYPE
+};
+
+typedef struct {
+    int mute;
+    uint32_t bus_delay;
+    uint32_t io_base, io_size;
+    psx_disc_t* disc;
+    psx_ic_t* ic;
+    int disc_type;
+    int version;
+    int region;
+    int index;
+    int pending_speed_switch_delay;
+    int seek_precision;
+    int fake_getlocl_data;
+    uint8_t ier;
+    uint8_t ifr;
+    uint8_t vol_pending[4];
+    uint8_t vol[4];
+    uint8_t mode;
+    int data_req;
+    queue_t* data;
+    queue_t* response;
+    queue_t* parameters;
+    uint8_t pending_command;
+    int busy;
+    uint32_t xa_lba;
+    int xa_playing;
+    int xa_mute;
+    int xa_channel;
+    int xa_file;
+    int xa_remaining_samples;
+    int16_t xa_left_h[2];
+    int16_t xa_right_h[2];
+    int16_t xa_prev_left_sample;
+    int16_t xa_prev_right_sample;
+    int xa_sample_index;
+    int state;
+    int prev_state;
+    int int1_pending;
+    int int2_pending;
+    int delay;
+    uint32_t pending_lba;
+    uint32_t lba;
+    int16_t cdda_buf[CD_SECTOR_SIZE >> 1];
+    int32_t cdda_remaining_samples;
+    uint32_t cdda_sample_index;
+    uint32_t cdda_sectors_played;
+    int cdda_playing;
+    int read_ongoing;
+    uint8_t xa_buf[CD_SECTOR_SIZE];
+    int16_t xa_left_buf[XA_STEREO_SAMPLES];
+    int16_t xa_right_buf[XA_STEREO_SAMPLES];
+    int16_t xa_mono_buf[XA_MONO_SAMPLES];
+    int16_t xa_upsample_buf[XA_UPSAMPLE_SIZE];
+    int16_t xa_left_resample_buf[XA_STEREO_RESAMPLE_SIZE];
+    int16_t xa_right_resample_buf[XA_STEREO_RESAMPLE_SIZE];
+    int16_t xa_mono_resample_buf[XA_MONO_RESAMPLE_SIZE];
+} psx_cdrom_t;
+
+enum {
+    CDR_VERSION_01,  // DTL-H2000                 (??-???-????)
+    CDR_VERSION_C0A, // PSX (PU-7)                (19-Sep-1994)
+    CDR_VERSION_C0B, // PSX (PU-7)                (18-Nov-1994)
+    CDR_VERSION_C1A, // PSX (EARLY-PU-8)          (16-May-1995)
+    CDR_VERSION_C1B, // PSX (LATE-PU-8)           (24-Jul-1995)
+    CDR_VERSION_D1,  // PSX (LATE-PU-8, Debug)    (24-Jul-1995)
+    CDR_VERSION_C2V, // PSX (PU-16, Video CD)     (15-Aug-1996)
+    CDR_VERSION_C1Y, // PSX (LATE-PU-8, Yaroze)   (18-Aug-1996)
+    CDR_VERSION_C2J, // PSX (PU-18) (J)           (12-Sep-1996)
+    CDR_VERSION_C2A, // PSX (PU-18) (U/E)         (10-Jan-1997)
+    CDR_VERSION_C2B, // PSX (PU-20)               (14-Aug-1997)
+    CDR_VERSION_C3A, // PSX (PU-22)               (10-Jul-1998)
+    CDR_VERSION_C3B, // PSX/PSone (PU-23, PM-41)  (01-Feb-1999)
+    CDR_VERSION_C3C  // PSone/late (PM-41(2))     (06-Jun-2001)
+};
+
+enum {
+    CDR_REGION_JAPAN,
+    CDR_REGION_EUROPE,
+    CDR_REGION_AMERICA
+};
+
+uint8_t cdrom_get_stat(psx_cdrom_t* cdrom);
+void cdrom_error(psx_cdrom_t* cdrom, uint8_t stat, uint8_t err);
+int cdrom_get_seek_delay(psx_cdrom_t* cdrom, int ts);
+int cdrom_get_pause_delay(psx_cdrom_t* cdrom);
+int cdrom_get_read_delay(psx_cdrom_t* cdrom);
+void cdrom_set_int(psx_cdrom_t* cdrom, int n);
+void cdrom_process_setloc(psx_cdrom_t* cdrom);
+
+psx_cdrom_t* psx_cdrom_create(void);
+void psx_cdrom_init(psx_cdrom_t* cdrom, psx_ic_t* ic);
+void psx_cdrom_set_version(psx_cdrom_t* cdrom, int version);
+void psx_cdrom_set_region(psx_cdrom_t* cdrom, int region);
+void psx_cdrom_open(psx_cdrom_t* cdrom, const char* path);
+void psx_cdrom_close(psx_cdrom_t* cdrom);
+uint32_t psx_cdrom_read32(psx_cdrom_t* cdrom, uint32_t addr);
+uint32_t psx_cdrom_read16(psx_cdrom_t* cdrom, uint32_t addr);
+uint32_t psx_cdrom_read8(psx_cdrom_t* cdrom, uint32_t addr);
+void psx_cdrom_write32(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value);
+void psx_cdrom_write16(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value);
+void psx_cdrom_write8(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value);
+void psx_cdrom_update(psx_cdrom_t* cdrom, int cycles);
+void psx_cdrom_get_audio_samples(psx_cdrom_t* cdrom, void* buf, size_t size);
+void psx_cdrom_destroy(psx_cdrom_t* cdrom);
+
+#endif
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/cue.c
@@ -1,0 +1,593 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "cue.h"
+
+static const char* cue_keywords[] = {
+    "4CH",
+    "AIFF",
+    "AUDIO",
+    "BINARY",
+    "CATALOG",
+    "CDG",
+    "CDI/2336",
+    "CDI/2352",
+    "CDTEXTFILE",
+    "DCP",
+    "FILE",
+    "FLAGS",
+    "INDEX",
+    "ISRC",
+    "MODE1/2048",
+    "MODE1/2352",
+    "MODE2/2336",
+    "MODE2/2352",
+    "MOTOROLA",
+    "MP3",
+    "PERFORMER",
+    "POSTGAP",
+    "PRE",
+    "PREGAP",
+    "REM",
+    "SCMS",
+    "SONGWRITER",
+    "TITLE",
+    "TRACK",
+    "WAVE",
+    0
+};
+
+char* strapp(char* dst, const char* a, const char* b) {
+    char* d = dst;
+
+    while (*a)
+        *dst++ = *a++;
+
+    while (*b)
+        *dst++ = *b++;
+
+    *dst = '\0';
+
+    return d;
+}
+
+const char* find_last_slash(const char* a) {
+    if (!a)
+        return NULL;
+
+    const char* b = a;
+
+    while (*a) {
+        if (*a == '/' || *a == '\\')
+            b = a + 1;
+
+        ++a;
+    }
+
+    return b;
+}
+
+char* get_root_path(char* dst, const char* a) {
+    if (!a) {
+        *dst = '\0';
+
+        return dst;
+    }
+
+    const char* b = a;
+    const char* c = a;
+    char* d = dst;
+
+    while (*a) {
+        if (*a == '/' || *a == '\\')
+            b = a + 1;
+
+        ++a;
+    }
+
+    while (c != b)
+        *dst++ = *c++;
+
+    *dst = '\0';
+
+    return d;
+}
+
+int cue_parse_keyword(cue_t* cue) {
+    char buf[256];
+    char* ptr = buf;
+
+    while (isalpha(cue->c) || isdigit(cue->c) || cue->c == '/') {
+        *ptr++ = cue->c;
+
+        cue->c = fgetc(cue->file);
+    }
+
+    *ptr = '\0';
+
+    int i = 0;
+
+    const char* keyword = cue_keywords[i];
+
+    while (keyword) {
+        if (!strcmp(keyword, buf)) {
+            return i;
+        } else {
+            keyword = cue_keywords[++i];
+        }
+    }
+
+    return -1;
+}
+
+int cue_parse_number(cue_t* cue) {
+    if (!isdigit(cue->c))
+        return 0;
+
+    char buf[4];
+
+    char* ptr = buf;
+
+    while (isdigit(cue->c)) {
+        *ptr++ = cue->c;
+
+        cue->c = fgetc(cue->file);
+    }
+
+    *ptr = '\0';
+
+    return atoi(buf);
+}
+
+uint32_t cue_parse_msf(cue_t* cue) {
+    int m = 0;
+    int s = 0;
+    int f = 0;
+
+    if (!isdigit(cue->c))
+        return 0;
+
+    m = cue_parse_number(cue);
+
+    if (cue->c != ':')
+        return 0;
+
+    cue->c = fgetc(cue->file);
+
+    s = cue_parse_number(cue);
+
+    if (cue->c != ':')
+        return 0;
+
+    cue->c = fgetc(cue->file);
+
+    f = cue_parse_number(cue);
+
+    // 1 second = 75 frames (sectors)
+    // 1 minute = 60 seconds = 4500 frames
+    return f + (s * 75) + (m * 4500);
+}
+
+void cue_parse_index(cue_t* cue) {
+    cue_track_t* track = list_back(cue->tracks)->data;
+
+    while (isspace(cue->c))
+        cue->c = fgetc(cue->file);
+
+    if (!isdigit(cue->c))
+        return;
+
+    int i = cue_parse_number(cue);
+
+    while (isspace(cue->c))
+        cue->c = fgetc(cue->file);
+
+    if (i > 1)
+        return;
+
+    track->index[i] = cue_parse_msf(cue);
+}
+
+cue_track_t* cue_parse_track(cue_t* cue) {
+    while (isspace(cue->c))
+        cue->c = fgetc(cue->file);
+
+    if (!isdigit(cue->c))
+        return NULL;
+
+    cue_track_t* track = malloc(sizeof(cue_track_t));
+
+    track->end = 0;
+    track->start = 0;
+    track->pregap = 0;
+    track->index[0] = -1;
+    track->index[1] = -1;
+    track->file = list_back(cue->files)->data;
+    track->number = cue_parse_number(cue);
+
+    while (isspace(cue->c))
+        cue->c = fgetc(cue->file);
+
+    track->mode = cue_parse_keyword(cue);
+
+    return track;
+}
+
+cue_file_t* cue_parse_file(cue_t* cue, const char* p, const char* s) {
+    while (isspace(cue->c))
+        cue->c = fgetc(cue->file);
+
+    if (cue->c != '\"')
+        return NULL;
+
+    cue_file_t* file = malloc(sizeof(cue_file_t));
+
+    file->tracks = list_create();
+    file->name = malloc(512);
+
+    // Append root path to track file path
+    char* ptr = file->name;
+
+    while (p != s)
+        *ptr++ = *p++;
+
+    cue->c = fgetc(cue->file);
+
+    while (cue->c != '\"') {
+        *ptr++ = cue->c;
+
+        cue->c = fgetc(cue->file);
+    }
+
+    *ptr = '\0';
+
+    cue->c = fgetc(cue->file);
+
+    // Ignore file type
+    while (isspace(cue->c))
+        cue->c = fgetc(cue->file);
+
+    while (isalpha(cue->c))
+        cue->c = fgetc(cue->file);
+
+    return file;
+}
+
+cue_t* cue_create(void) {
+    return malloc(sizeof(cue_t));
+}
+
+void cue_init(cue_t* cue) {
+    cue->files = list_create();
+    cue->tracks = list_create();
+}
+
+int cue_parse(cue_t* cue, const char* path) {
+    cue->file = fopen(path, "rb");
+
+    if (!cue->file)
+        return CUE_FILE_NOT_FOUND;
+
+    const char* s = find_last_slash(path);
+
+    cue->c = fgetc(cue->file);
+
+    while (isspace(cue->c))
+        cue->c = fgetc(cue->file);
+
+    while (!feof(cue->file)) {
+        int kw = cue_parse_keyword(cue);
+
+        switch (kw) {
+            case CUE_FILE: {
+                list_push_back(cue->files, cue_parse_file(cue, path, s));
+            } break;
+
+            case CUE_TRACK: {
+                cue_track_t* track = cue_parse_track(cue);
+                cue_file_t* file = list_back(cue->files)->data;
+
+                list_push_back(cue->tracks, track);
+                list_push_back(file->tracks, track);
+            } break;
+
+            case CUE_INDEX: {
+                cue_parse_index(cue);
+            } break;
+
+            case CUE_REM: case CUE_PREGAP: case CUE_FLAGS: case CUE_POSTGAP: {
+                // Ignore everything until a newline (handle CRLF and LF)
+                while ((cue->c != '\n') && (cue->c != '\r'))
+                    cue->c = fgetc(cue->file);
+
+                while ((cue->c == '\n') && (cue->c == '\r'))
+                    cue->c = fgetc(cue->file);
+            } break;
+
+            default: {
+                printf("Unknown keyword: %s (%u)\n", cue_keywords[kw], kw);
+
+                return 1;
+            } break;
+        }
+
+        while (isspace(cue->c))
+            cue->c = fgetc(cue->file);
+    }
+
+    return 0;
+}
+
+size_t get_file_size(FILE* file) {
+    fseek(file, 0, SEEK_END);
+
+    size_t size = ftell(file);
+
+    fseek(file, 0, SEEK_SET);
+
+    return size;
+}
+
+/*
+(0   - 0  )                   = 150
+(0   - 0  ) + 150    + 315000 = 315150
+(0   - 0  ) + 315150 + 375    = 315525
+(150 - 0  ) + 315525 + 390    = 316065
+(195 - 150) + 316065 + 390    = 316500
+(155 - 190) + 316500 + 390    = 
+*/
+
+int prev_pregap = 0;
+
+uint32_t init_tracks(cue_file_t* file, uint32_t* lba) {
+    node_t* node = list_front(file->tracks);
+
+    // 1 track per file case
+    if (file->tracks->size == 1) {
+        cue_track_t* data = node->data;
+
+        data->pregap = 0;
+
+        if ((data->index[0] != -1) && (data->index[1] != -1))
+            data->pregap = data->index[1];
+
+        data->start = *lba + data->pregap;
+        data->end = data->start + (file->size / 0x930);
+
+        *lba = data->end;
+
+        return 0;
+    }
+
+    // Multiple tracks per file
+    while (node) {
+        cue_track_t* data = node->data;
+
+        // If this is the last track
+        if (!node->next) {
+            data->pregap = 0;
+            data->start = data->index[1] + 150;
+            data->end = file->size / 0x930;
+
+            return 0;
+        }
+
+        cue_track_t* next = node->next->data;
+
+        data->start = data->index[1] + 150;
+        data->end = (next->index[1] + 150) - 1;
+        data->pregap = 0;
+
+        node = node->next;
+    }
+
+    return 0;
+}
+
+int cue_load(cue_t* cue, int mode) {
+    node_t* node = list_front(cue->files);
+
+    // 00:02:00
+    uint32_t lba = 2 * 75;
+
+    while (node) {
+        cue_file_t* data = node->data;
+
+        FILE* file = fopen(data->name, "rb");
+
+        if (!file)
+            return CUE_TRACK_FILE_NOT_FOUND;
+
+        data->buf_mode = mode;
+        data->size = get_file_size(file);
+
+        // printf("Loaded \'%s\': size=%llx, sectors=%llu\n",
+        //     data->name,
+        //     data->size,
+        //     data->size / 0x930
+        // );
+
+        if (data->buf_mode == LD_BUFFERED) {
+            data->buf = malloc(data->size);
+
+            fseek(file, 0, SEEK_SET);
+            fread(data->buf, 1, data->size, file);
+
+            fclose(file);
+        } else {
+            data->buf = file;
+        }
+
+        data->start = lba;
+
+        init_tracks(data, &lba);
+
+        node = node->next;
+    }
+
+    return CUE_OK;
+}
+
+void cue_destroy(cue_t* cue) {
+    node_t* node = list_front(cue->files);
+
+    while (node) {
+        cue_file_t* file = node->data;
+
+        if (file->buf_mode == LD_BUFFERED) {
+            free(file->buf);
+        } else {
+            fclose((FILE*)file->buf);
+        }
+
+        list_destroy(file->tracks);
+
+        free(file->name);
+        free(file);
+
+        node = node->next;
+    }
+
+    list_destroy(cue->files);
+
+    node = list_front(cue->tracks);
+
+    while (node) {
+        free(node->data);
+
+        node = node->next;
+    }
+
+    list_destroy(cue->tracks);
+
+    free(cue);
+}
+
+cue_track_t* get_sector_track(cue_t* cue, uint32_t lba) {
+    node_t* node = list_front(cue->tracks);
+
+    while (node) {
+        cue_track_t* track = node->data;
+
+        if ((lba >= track->start) && (lba < track->end))
+            return track;
+
+        node = node->next;
+    }
+
+    return NULL;
+}
+
+cue_track_t* get_sector_track_in_pregap(cue_t* cue, uint32_t lba) {
+    node_t* node = list_front(cue->tracks);
+
+    while (node) {
+        cue_track_t* track = node->data;
+
+        if (!node->next)
+            return track;
+
+        cue_track_t* next = node->next->data;
+
+        // Ignore sector number
+        int curr_start = track->start - (track->start % 75);
+        int next_start = next->start - (next->start % 75);
+
+        if ((lba >= curr_start) && (lba < next_start))
+            return track;
+
+        node = node->next;
+    }
+
+    return NULL;
+}
+
+int cue_query(cue_t* cue, uint32_t lba) {
+    if (lba >= ((cue_track_t*)list_back(cue->tracks)->data)->end)
+        return TS_FAR;
+
+    cue_track_t* track = get_sector_track(cue, lba);
+
+    // If the LBA isn't too far but the track wasn't found
+    // then we are being requested a pregap sector. Clear buffer
+    // and initialize sync data (not actually needed)
+    if (!track)
+        return TS_PREGAP;
+
+    return (track->mode == CUE_MODE2_2352) ? TS_DATA : TS_AUDIO;
+}
+
+int cue_read(cue_t* cue, uint32_t lba, void* buf) {
+    if (lba >= ((cue_track_t*)list_back(cue->tracks)->data)->end)
+        return TS_FAR;
+
+    cue_track_t* track = get_sector_track(cue, lba);
+
+    // If the LBA isn't too far but the track wasn't found
+    // then we are being requested a pregap sector. Clear buffer
+    // and initialize sync data (not actually needed)
+    if (!track) {
+        memset(buf, 0, 2352);
+        memset(buf + 1, 255, 10);
+
+        return TS_PREGAP;
+    }
+
+    cue_file_t* file = track->file;
+
+    // printf("Reading sector %u at track %u, file=%s (%u), offset=%u (%08x)\n",
+    //     lba,
+    //     track->number,
+    //     track->file->name,
+    //     file->start,
+    //     lba - file->start,
+    //     (lba - file->start) * 2352
+    // );
+
+    if (file->buf_mode == LD_BUFFERED) {
+        uint8_t* ptr = file->buf + ((lba - file->start) * 2352);
+
+        memcpy(buf, ptr, 2352);
+    } else {
+        fseek(file->buf, (lba - file->start) * 2352, SEEK_SET);
+
+        fread(buf, 1, 2352, file->buf);
+    }
+
+    return (track->mode == CUE_MODE2_2352) ? TS_DATA : TS_AUDIO;
+}
+
+int cue_get_track_number(cue_t* cue, uint32_t lba) {
+    cue_track_t* track = get_sector_track_in_pregap(cue, lba);
+
+    return track->number;
+}
+
+int cue_get_track_count(cue_t* cue) {
+    return cue->tracks->size;
+}
+
+int cue_get_track_lba(cue_t* cue, int track) {
+    if (!track)
+        return ((cue_track_t*)list_back(cue->tracks)->data)->end;
+
+    if (track > cue->tracks->size)
+        return TS_FAR;
+
+    cue_track_t* data = list_at(cue->tracks, track - 1)->data;
+
+    return data->start;
+}
+
+void cue_init_disc(cue_t* cue, psx_disc_t* disc) {
+    disc->udata = cue;
+    disc->read_sector = cue_read;
+    disc->query_sector = cue_query;
+    disc->get_track_number = cue_get_track_number;
+    disc->get_track_count = cue_get_track_count;
+    disc->get_track_lba = cue_get_track_lba;
+    disc->destroy = cue_destroy;
+}
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/cue.h
@@ -1,0 +1,99 @@
+#ifndef CUE_H
+#define CUE_H
+
+#include "list.h"
+#include "disc.h"
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+
+enum {
+    CUE_OK = 0,
+    CUE_FILE_NOT_FOUND,
+    CUE_TRACK_FILE_NOT_FOUND
+};
+
+enum {
+    CUE_4CH = 0,
+    CUE_AIFF,
+    CUE_AUDIO,
+    CUE_BINARY,
+    CUE_CATALOG,
+    CUE_CDG,
+    CUE_CDI_2336,
+    CUE_CDI_2352,
+    CUE_CDTEXTFILE,
+    CUE_DCP,
+    CUE_FILE,
+    CUE_FLAGS,
+    CUE_INDEX,
+    CUE_ISRC,
+    CUE_MODE1_2048,
+    CUE_MODE1_2352,
+    CUE_MODE2_2336,
+    CUE_MODE2_2352,
+    CUE_MOTOROLA,
+    CUE_MP3,
+    CUE_PERFORMER,
+    CUE_POSTGAP,
+    CUE_PRE,
+    CUE_PREGAP,
+    CUE_REM,
+    CUE_SCMS,
+    CUE_SONGWRITER,
+    CUE_TITLE,
+    CUE_TRACK,
+    CUE_WAVE,
+    CUE_NONE = 255
+};
+
+enum {
+    LD_BUFFERED,
+    LD_FILE
+};
+
+typedef struct {
+    char* name;
+    int buf_mode;
+    void* buf;
+    size_t size;
+    uint32_t start;
+    list_t* tracks;
+} cue_file_t;
+
+typedef struct {
+    int number;
+    int mode;
+
+    int32_t index[2];
+    uint32_t pregap;
+    uint32_t start;
+    uint32_t end;
+
+    cue_file_t* file;
+} cue_track_t;
+
+typedef struct {
+    list_t* files;
+    list_t* tracks;
+
+    char c;
+    FILE* file;
+} cue_t;
+
+cue_t* cue_create(void);
+void cue_init(cue_t* cue);
+int cue_parse(cue_t* cue, const char* path);
+int cue_load(cue_t* cue, int mode);
+
+// Disc interface
+int cue_read(cue_t* cue, uint32_t lba, void* buf);
+int cue_query(cue_t* cue, uint32_t lba);
+int cue_get_track_number(cue_t* cue, uint32_t lba);
+int cue_get_track_count(cue_t* cue);
+int cue_get_track_lba(cue_t* cue, int track);
+void cue_init_disc(cue_t* cue, psx_disc_t* disc);
+void cue_destroy(cue_t* cue);
+
+#endif
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/disc.c
@@ -1,0 +1,113 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "disc.h"
+#include "cue.h"
+
+#define MSF_TO_LBA(m, s, f) ((m * 4500) + (s * 75) + f)
+
+const char* disc_cd_extensions[] = {
+    "cue",
+    "bin",
+    "iso",
+    0
+};
+
+psx_disc_t* psx_disc_create(void) {
+    return malloc(sizeof(psx_disc_t));
+}
+
+int disc_get_extension(const char* path) {
+    const char* ptr = &path[strlen(path) - 1];
+    int i = 0;
+
+    while ((*ptr != '.') && (ptr != path))
+        --ptr;
+
+    if (ptr == path)
+        return CD_EXT_UNSUPPORTED;
+
+    while (disc_cd_extensions[i]) {
+        if (!strcmp(ptr + 1, disc_cd_extensions[i]))
+            return i;
+        
+        ++i;
+    }
+
+    return CD_EXT_UNSUPPORTED;
+}
+
+int disc_get_cd_type(psx_disc_t* disc) {
+    char buf[CD_SECTOR_SIZE];
+
+    // If the disc is smaller than 16 sectors
+    // then it can't be a PlayStation game.
+    // Audio discs should also have ISO volume
+    // descriptors, so it's probably something else
+    // entirely.
+    if (!psx_disc_read(disc, MSF_TO_LBA(0, 2, 16), buf))
+        return CDT_UNKNOWN;
+
+    // Check for the "PLAYSTATION" string at PVD offset 20h
+    // Patch 20 byte so comparison is done correctly
+    buf[0x2b] = 0;
+
+    if (strncmp(&buf[0x20], "PLAYSTATION", 12))
+        return CDT_AUDIO;
+
+    return CDT_LICENSED;
+}
+
+int psx_disc_open(psx_disc_t* disc, const char* path) {
+    if (!path)
+        return CDT_ERROR;
+
+    int ext = disc_get_extension(path);
+
+    return psx_disc_open_as(disc, path, ext);
+}
+
+int psx_disc_open_as(psx_disc_t* disc, const char* path, int type) {
+    switch (type) {
+        case CD_EXT_CUE: {
+            cue_t* cue = cue_create();
+
+            cue_init(cue);
+            cue_init_disc(cue, disc);
+
+            if (cue_parse(cue, path))
+                return CDT_ERROR;
+
+            if (cue_load(cue, LD_FILE))
+                return CDT_ERROR;
+        } break;
+    }
+
+    return disc_get_cd_type(disc);
+}
+
+int psx_disc_read(psx_disc_t* disc, uint32_t lba, void* buf) {
+    return disc->read_sector(disc->udata, lba, buf);
+}
+
+int psx_disc_query(psx_disc_t* disc, uint32_t lba) {
+    return disc->query_sector(disc->udata, lba);
+}
+
+int psx_disc_get_track_number(psx_disc_t* disc, uint32_t lba) {
+    return disc->get_track_number(disc->udata, lba);
+}
+
+int psx_disc_get_track_count(psx_disc_t* disc) {
+    return disc->get_track_count(disc->udata);
+}
+
+int psx_disc_get_track_lba(psx_disc_t* disc, int track) {
+    return disc->get_track_lba(disc->udata, track);
+}
+
+void psx_disc_destroy(psx_disc_t* disc) {
+    disc->destroy(disc->udata);
+
+    free(disc);
+}
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/disc.h
@@ -1,0 +1,73 @@
+#ifndef DISC_H
+#define DISC_H
+
+#include <stdint.h>
+
+/*
+    PSX disc reader API version 2 specification:
+
+    Mandatory formats:
+    - BIN/CUE (Multi track)
+    - BIN (Single track)
+
+    Optional (but encouraged) formats:
+    - ISO (Raw ISO9660 images)
+    - CHD (Compressed MAME "Hunks of Data")
+
+    Optional formats:
+    - MDS/MDF (Alcohol 120% images)
+*/
+
+enum {
+    TS_FAR = 0,
+    TS_DATA,
+    TS_AUDIO,
+    TS_PREGAP
+};
+
+enum {
+    CD_EXT_CUE = 0,
+    CD_EXT_BIN,
+    CD_EXT_ISO,
+    CD_EXT_RAW,
+    CD_EXT_UNSUPPORTED
+};
+
+enum {
+    CDT_ERROR = 0,
+    CDT_LICENSED,
+    CDT_AUDIO,
+    CDT_UNKNOWN
+};
+
+#define CD_SECTOR_SIZE 2352
+
+typedef int (*read_sector_func)(void*, uint32_t, void*);
+typedef int (*query_sector_func)(void*, uint32_t);
+typedef int (*get_track_number_func)(void*, uint32_t);
+typedef int (*get_track_count_func)(void*);
+typedef uint32_t (*get_track_lba_func)(void*, int);
+typedef void (*destroy_func)(void*);
+
+typedef struct {
+    void* udata;
+    read_sector_func read_sector;
+    query_sector_func query_sector;
+    get_track_number_func get_track_number;
+    get_track_count_func get_track_count;
+    get_track_lba_func get_track_lba;
+    destroy_func destroy;
+} psx_disc_t;
+
+psx_disc_t* psx_disc_create(void);
+int psx_disc_open(psx_disc_t* disc, const char* path);
+int psx_disc_open_as(psx_disc_t* disc, const char* path, int type);
+int psx_disc_read(psx_disc_t* disc, uint32_t lba, void* buf);
+int psx_disc_query(psx_disc_t* disc, uint32_t lba);
+int psx_disc_get_track_number(psx_disc_t* disc, uint32_t lba);
+int psx_disc_get_track_count(psx_disc_t* disc);
+int psx_disc_get_track_lba(psx_disc_t* disc, int track);
+void psx_disc_close(psx_disc_t* disc);
+void psx_disc_destroy(psx_disc_t* disc);
+
+#endif
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/impl.c
@@ -1,0 +1,745 @@
+#include "cdrom.h"
+
+#define BTOI(b) btoi_table[b]
+#define ITOB(b) itob_table[b]
+
+#define VALID_BCD(bcd) \
+    (((bcd & 0xf0) <= 0x90) && ((bcd & 0xf) <= 9))
+
+#define VALID_MSF(m, s, f) \
+    (VALID_BCD(m) && VALID_BCD(s) && VALID_BCD(f) && (f < 0x75) && (s < 0x60))
+
+static const uint8_t btoi_table[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+    0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
+    0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+    0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+    0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
+    0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
+    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+    0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41,
+    0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43,
+    0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
+    0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
+    0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
+    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+    0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+    0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61,
+    0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
+    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
+    0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73,
+    0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
+    0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d,
+    0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+    0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91,
+    0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93,
+    0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
+    0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d,
+    0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5,
+};
+
+static const uint8_t itob_table[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
+    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
+    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
+    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
+    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
+    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
+    0x96, 0x97, 0x98, 0x99, 0xa0, 0xa1, 0xa2, 0xa3,
+    0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xb0, 0xb1,
+    0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+    0xc8, 0xc9, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5,
+    0xd6, 0xd7, 0xd8, 0xd9, 0xe0, 0xe1, 0xe2, 0xe3,
+    0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xf0, 0xf1,
+    0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
+    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
+    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
+    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
+    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
+    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
+};
+
+static const uint8_t cdrom_cd_getid[] = {
+    0x08, 0x40, 0x00, 0x00, // No Disk
+    0x02, 0x00, 0x20, 0x00, // Licensed
+    0x0a, 0x90, 0x00, 0x00, // Audio Disk
+    0x0a, 0x80, 0x00, 0x00  // Unlicensed
+};
+
+static const uint8_t cdrom_version_id[] = {
+    0x94, 0x09, 0x19, 0x01, // DTL-H2000 (date unknown)
+    0x94, 0x09, 0x19, 0xc0,
+    0x94, 0x11, 0x18, 0xc0,
+    0x95, 0x05, 0x16, 0xc1,
+    0x95, 0x07, 0x24, 0xc1,
+    0x95, 0x07, 0x24, 0xd1,
+    0x96, 0x08, 0x15, 0xc2,
+    0x96, 0x08, 0x18, 0xc1,
+    0x96, 0x09, 0x12, 0xc2,
+    0x97, 0x01, 0x10, 0xc2,
+    0x97, 0x08, 0x14, 0xc2,
+    0x98, 0x06, 0x10, 0xc3,
+    0x99, 0x02, 0x01, 0xc3,
+    0xa1, 0x03, 0x06, 0xc3
+};
+
+static const char cdrom_region_letter[] = {
+    'I', 'E', 'A'
+};
+
+void cdrom_pause(psx_cdrom_t* cdrom) {
+    cdrom->prev_state = CD_STATE_IDLE;
+    cdrom->state = CD_STATE_IDLE;
+    cdrom->pending_command = 0;
+    cdrom->busy = 0;
+    cdrom->cdda_playing = 0;
+    cdrom->xa_playing = 0;
+    cdrom->read_ongoing = 0;
+}
+
+void cdrom_restore_state(psx_cdrom_t* cdrom) {
+    cdrom->state = CD_STATE_IDLE;
+
+    if (cdrom->prev_state == CD_STATE_PLAY ||
+        cdrom->prev_state == CD_STATE_READ)
+        cdrom->state = cdrom->prev_state;
+
+    cdrom->pending_command = 0;
+}
+
+void cdrom_cmd_getstat(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_setloc(psx_cdrom_t* cdrom) {
+    int m = queue_pop(cdrom->parameters);
+    int s = queue_pop(cdrom->parameters);
+    int f = queue_pop(cdrom->parameters);
+
+    if (!VALID_MSF(m, s, f)) {
+        cdrom_error(cdrom,
+            CD_STAT_SPINDLE,
+            CD_ERR_INVALID_SUBFUNCTION
+        );
+
+        return;
+    }
+
+    cdrom_set_int(cdrom, 3);
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    cdrom->pending_lba = (BTOI(m) * 4500) + (BTOI(s) * 75) + BTOI(f);
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_play(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    int track = -1;
+
+    if (!queue_is_empty(cdrom->parameters)) {
+        int track = BTOI(queue_pop(cdrom->parameters));
+
+        if (track)
+            cdrom->pending_lba = psx_disc_get_track_lba(cdrom->disc, track);
+    }
+
+    cdrom_process_setloc(cdrom);
+
+    int mm = cdrom->lba / (60 * 75);
+    int ss = (cdrom->lba % (60 * 75)) / 75;
+    int ff = (cdrom->lba % (60 * 75)) % 75;
+
+    printf("play song at lba=%08x %02u:%02u:%02u track=%d\n", cdrom->lba, mm, ss, ff, track);
+
+    cdrom->prev_state = CD_STATE_PLAY;
+    cdrom->state = CD_STATE_PLAY;
+    cdrom->pending_command = 0;
+    cdrom->cdda_playing = 1;
+    cdrom->cdda_remaining_samples = 0;
+    cdrom->cdda_sample_index = 0;
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_forward(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    cdrom->lba += 35;
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_backward(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    cdrom->lba -= 35;
+
+    if (cdrom->lba < 150)
+        cdrom->lba = 150;
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_readn(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+    
+    cdrom_process_setloc(cdrom);
+
+    // Preload sector
+    int ts = psx_disc_read(cdrom->disc, cdrom->lba, cdrom->data->buf);
+
+    if (cdrom->mode & MODE_XA_ADPCM) {
+        cdrom->xa_playing = 1;
+        cdrom->xa_remaining_samples = 0;
+        cdrom->xa_sample_index = 0;
+        cdrom->xa_lba = cdrom->lba;
+    }
+
+    cdrom->state = CD_STATE_READ;
+    cdrom->prev_state = CD_STATE_READ;
+    cdrom->delay = cdrom_get_read_delay(cdrom);
+    cdrom->read_ongoing = 1;
+}
+
+void cdrom_cmd_motoron(psx_cdrom_t* cdrom) {
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_set_int(cdrom, 3);
+
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom->delay = CD_DELAY_FR;
+        cdrom->state = CD_STATE_TX_RESP2;
+    } else {
+        cdrom_set_int(cdrom, 2);
+
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        // Doesn't pause!
+        cdrom_restore_state(cdrom);
+    }
+}
+
+void cdrom_cmd_stop(psx_cdrom_t* cdrom) {
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_set_int(cdrom, 3);
+
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom->delay = CD_DELAY_FR;
+        cdrom->state = CD_STATE_TX_RESP2;
+    } else {
+        cdrom_set_int(cdrom, 2);
+
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        // Reset to 00:02:00
+        cdrom->pending_lba = 150;
+
+        cdrom_process_setloc(cdrom);
+
+        cdrom->prev_state = CD_STATE_IDLE;
+        cdrom->state = CD_STATE_IDLE;
+        cdrom->pending_command = 0;
+        cdrom->busy = 0;
+        cdrom->cdda_playing = 0;
+        cdrom->xa_playing = 0;
+        cdrom->read_ongoing = 0;
+    }
+}
+
+void cdrom_cmd_pause(psx_cdrom_t* cdrom) {
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_set_int(cdrom, 3);
+
+        queue_push(cdrom->response, CD_STAT_READ | CD_STAT_SPINDLE);
+
+        // Pausing at 1x takes 70ms, 2x takes 35ms
+        // but setting delays that high breaks games
+        cdrom->delay = CD_DELAY_1MS * ((cdrom->mode & MODE_SPEED) ? 70 : 35);
+        cdrom->state = CD_STATE_TX_RESP2;
+    } else {
+        cdrom_set_int(cdrom, 2);
+
+        queue_push(cdrom->response, CD_STAT_SPINDLE);
+
+        cdrom_pause(cdrom);
+    }
+}
+
+void cdrom_cmd_init(psx_cdrom_t* cdrom) {
+    // Init sends the "same" thing twice. On real hardware 
+    // it would probably send something different, but that's
+    // not really important.
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_set_int(cdrom, 3);
+
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom->delay = CD_DELAY_FR;
+        cdrom->state = CD_STATE_TX_RESP2;
+    } else {
+        cdrom_set_int(cdrom, 2);
+
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom_pause(cdrom);
+    }
+}
+
+void cdrom_cmd_mute(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    cdrom->mute = 1;
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_demute(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    cdrom->mute = 0;
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_setfilter(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    // 0x0d byte has to be sent (and ignored) for some reason
+    // queue_pop(cdrom->parameters);
+
+    cdrom->xa_file = queue_pop(cdrom->parameters);
+    cdrom->xa_channel = queue_pop(cdrom->parameters);
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_setmode(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    int prev_speed = cdrom->mode & MODE_SPEED;
+
+    cdrom->mode = queue_pop(cdrom->parameters);
+
+    // Big speed switch delay
+    if (prev_speed != (cdrom->mode & MODE_SPEED))
+        cdrom->pending_speed_switch_delay = CD_DELAY_1MS;
+
+    cdrom_pause(cdrom);
+    // cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_getparam(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+    queue_push(cdrom->response, cdrom->mode);
+    queue_push(cdrom->response, 0);
+    queue_push(cdrom->response, cdrom->xa_file);
+    queue_push(cdrom->response, cdrom->xa_channel);
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_getlocl(psx_cdrom_t* cdrom) {
+    // GetlocL only works while reading. This is
+    // Somewhat of a hack, but should still work
+    // if (queue_is_empty(cdrom->data) && !cdrom->fake_getlocl_data) {
+    //     printf("getlocl error\n");
+    //     cdrom_error(cdrom,
+    //         CD_STAT_SPINDLE,
+    //         CD_ERR_NO_DISC
+    //     );
+
+    //     return;
+    // }
+
+    cdrom->fake_getlocl_data = 0;
+
+    // printf("getlocl: lba=%u %02x:%02x:%02x mode=%02x file=%02x channel=%02x sm=%02x ci=%02x\n",
+    //     cdrom->lba,
+    //     cdrom->data->buf[0x0c],
+    //     cdrom->data->buf[0x0d],
+    //     cdrom->data->buf[0x0e],
+    //     cdrom->data->buf[0x0f],
+    //     cdrom->data->buf[0x10],
+    //     cdrom->data->buf[0x11],
+    //     cdrom->data->buf[0x12],
+    //     cdrom->data->buf[0x13]
+    // );
+
+    cdrom_set_int(cdrom, 3);
+    queue_push(cdrom->response, cdrom->data->buf[0x0c]);
+    queue_push(cdrom->response, cdrom->data->buf[0x0d]);
+    queue_push(cdrom->response, cdrom->data->buf[0x0e]);
+    queue_push(cdrom->response, cdrom->data->buf[0x0f]);
+    queue_push(cdrom->response, cdrom->data->buf[0x10]);
+    queue_push(cdrom->response, cdrom->data->buf[0x11]);
+    queue_push(cdrom->response, cdrom->data->buf[0x12]);
+    queue_push(cdrom->response, cdrom->data->buf[0x13]);
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_getlocp(psx_cdrom_t* cdrom) {
+    int lba = cdrom->lba;
+
+    int track = psx_disc_get_track_number(cdrom->disc, lba);
+    int track_lba = psx_disc_get_track_lba(cdrom->disc, track);
+
+    if (!cdrom->seek_precision)
+        lba -= 25;
+
+    int32_t diff = lba - track_lba;
+
+    if (diff < 0)
+        diff = -diff;
+
+    int rmm = diff / (60 * 75);
+    int rss = (diff % (60 * 75)) / 75;
+    int rff = (diff % (60 * 75)) % 75;
+    int amm = lba / (60 * 75);
+    int ass = (lba % (60 * 75)) / 75;
+    int aff = (lba % (60 * 75)) % 75;
+
+    int index = psx_disc_query(cdrom->disc, lba) != TS_PREGAP;
+
+    // printf("getlocp: track %u (%02x) relative: %02u:%02u:%02u absolute: %02u:%02u:%02u pregap=%u\n",
+    //     track, ITOB(track),
+    //     rmm, rss, rff,
+    //     amm, ass, aff,
+    //     index
+    // );
+
+    cdrom_set_int(cdrom, 3);
+    queue_push(cdrom->response, ITOB(track));
+    queue_push(cdrom->response, ITOB(index));
+    queue_push(cdrom->response, ITOB(rmm));
+    queue_push(cdrom->response, ITOB(rss));
+    queue_push(cdrom->response, ITOB(rff));
+    queue_push(cdrom->response, ITOB(amm));
+    queue_push(cdrom->response, ITOB(ass));
+    queue_push(cdrom->response, ITOB(aff));
+
+    cdrom_restore_state(cdrom);
+}
+
+// To-do: Implement SetSession errors
+void cdrom_cmd_setsession(psx_cdrom_t* cdrom) {
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_set_int(cdrom, 3);
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom->delay = CD_DELAY_FR;
+        cdrom->state = CD_STATE_TX_RESP2;
+        cdrom->busy = 1;
+    } else {
+        cdrom_set_int(cdrom, 2);
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom_pause(cdrom);
+    }
+}
+
+void cdrom_cmd_gettn(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    int tn = psx_disc_get_track_count(cdrom->disc);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+    queue_push(cdrom->response, 1);
+    queue_push(cdrom->response, ITOB(tn));
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_gettd(psx_cdrom_t* cdrom) {
+    int bcd = queue_pop(cdrom->parameters);
+
+    // Check BCD
+    if (!VALID_BCD(bcd)) {
+        cdrom_error(cdrom,
+            CD_STAT_SPINDLE,
+            CD_ERR_INVALID_SUBFUNCTION
+        );
+
+        return;    
+    }
+
+    int track = BTOI(bcd);
+    int f = psx_disc_get_track_lba(cdrom->disc, track);
+
+    if (f == TS_FAR) {
+        cdrom_error(cdrom,
+            CD_STAT_SPINDLE,
+            CD_ERR_INVALID_SUBFUNCTION
+        );
+
+        return;
+    }
+
+    cdrom_set_int(cdrom, 3);
+
+    int mm = f / (60 * 75);
+    int ss = (f % (60 * 75)) / 75;
+    int ff = (f % (60 * 75)) % 75;
+
+    // printf("gettd: track %u lba=%08x (%u) %02u:%02u:%02u\n",
+    //     track,
+    //     f, f,
+    //     mm, ss, ff
+    // );
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+    queue_push(cdrom->response, ITOB(mm));
+    queue_push(cdrom->response, ITOB(ss));
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_seekl(psx_cdrom_t* cdrom) {
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_set_int(cdrom, 3);
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        int ts = psx_disc_query(cdrom->disc, cdrom->pending_lba);
+
+        cdrom->delay = CD_DELAY_1MS; // cdrom_get_seek_delay(cdrom, ts);
+        cdrom->state = CD_STATE_TX_RESP2;
+    } else {
+        int ts = psx_disc_query(cdrom->disc, cdrom->pending_lba);
+
+        if (ts == TS_FAR) {
+            cdrom_error(cdrom,
+                CD_STAT_SPINDLE | CD_STAT_SEEKERROR,
+                CD_ERR_INVALID_SUBFUNCTION
+            );
+
+            return;
+        }
+
+        if (ts == TS_AUDIO) {
+            cdrom_error(cdrom,
+                CD_STAT_SPINDLE | CD_STAT_SEEKERROR,
+                CD_ERR_SEEK_FAILED
+            );
+
+            return;
+        }
+
+        // If everything is right, then seek is successful
+        cdrom_process_setloc(cdrom);
+        cdrom->seek_precision = 1;
+
+        cdrom_set_int(cdrom, 2);
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom_restore_state(cdrom);
+    }
+}
+
+void cdrom_cmd_seekp(psx_cdrom_t* cdrom) {
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_set_int(cdrom, 3);
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom->delay = CD_DELAY_1MS; // cdrom_get_seek_delay(cdrom, ts);
+        cdrom->state = CD_STATE_TX_RESP2;
+        cdrom->busy = 1;
+    } else {
+        cdrom_set_int(cdrom, 2);
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom_process_setloc(cdrom);
+        cdrom->seek_precision = 0;
+
+        cdrom_restore_state(cdrom);
+    }
+}
+
+void cdrom_cmd_test(psx_cdrom_t* cdrom) {
+    int subf = queue_pop(cdrom->parameters);
+
+    // To-do: Handle other subfunctions (hard)
+    // assert(subf == 32);
+    if (subf == 4) {
+        cdrom_set_int(cdrom, 3);
+
+        queue_push(cdrom->response, CD_STAT_SPINDLE);
+
+        cdrom_restore_state(cdrom);
+
+        return;
+    }
+
+    if (subf == 5) {
+        cdrom_set_int(cdrom, 3);
+
+        queue_push(cdrom->response, 0);
+        queue_push(cdrom->response, 0);
+
+        cdrom_restore_state(cdrom);
+
+        return;
+    }
+
+    if (subf != 32) {
+        cdrom_error(cdrom,
+            CD_STAT_SPINDLE,
+            CD_ERR_INVALID_SUBFUNCTION
+        );
+
+        return;
+    }
+
+    int v = cdrom->version * 4;
+
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_version_id[v+0]);
+    queue_push(cdrom->response, cdrom_version_id[v+1]);
+    queue_push(cdrom->response, cdrom_version_id[v+2]);
+    queue_push(cdrom->response, cdrom_version_id[v+3]);
+
+    cdrom_restore_state(cdrom);
+}
+
+void cdrom_cmd_getid(psx_cdrom_t* cdrom) {
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_set_int(cdrom, 3);
+
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom->state = CD_STATE_TX_RESP2;
+        cdrom->delay = CD_DELAY_FR;
+    } else {
+        cdrom_set_int(cdrom, 2);
+
+        int t = cdrom->disc_type * 4;
+
+        queue_push(cdrom->response, cdrom_cd_getid[t+0]);
+        queue_push(cdrom->response, cdrom_cd_getid[t+1]);
+        queue_push(cdrom->response, cdrom_cd_getid[t+2]);
+        queue_push(cdrom->response, cdrom_cd_getid[t+3]);
+
+        if (cdrom->disc_type == CDT_LICENSED) {
+            queue_push(cdrom->response, 'S');
+            queue_push(cdrom->response, 'C');
+            queue_push(cdrom->response, 'E');
+            queue_push(cdrom->response, cdrom_region_letter[cdrom->region]);
+        } else {
+            queue_push(cdrom->response, 0);
+            queue_push(cdrom->response, 0);
+            queue_push(cdrom->response, 0);
+            queue_push(cdrom->response, 0);
+        }
+
+        cdrom_restore_state(cdrom);
+    }
+}
+
+void cdrom_cmd_reads(psx_cdrom_t* cdrom) {
+    cdrom_set_int(cdrom, 3);
+
+    queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+    cdrom_process_setloc(cdrom);
+
+    // Preload sector
+    int ts = psx_disc_read(cdrom->disc, cdrom->lba, cdrom->data->buf);
+
+    if (cdrom->mode & MODE_XA_ADPCM) {
+        cdrom->xa_playing = 1;
+        cdrom->xa_remaining_samples = 0;
+        cdrom->xa_sample_index = 0;
+        cdrom->xa_lba = cdrom->lba;
+
+        // printf("reads: xa_lba=%u (%08x) lba=%u (%08x) %02x:%02x:%02x mode=%02x file=%02x channel=%02x sm=%02x ci=%02x\n",
+        //     cdrom->xa_lba, cdrom->xa_lba,
+        //     cdrom->lba, cdrom->lba,
+        //     cdrom->data->buf[0x0c],
+        //     cdrom->data->buf[0x0d],
+        //     cdrom->data->buf[0x0e],
+        //     cdrom->data->buf[0x0f],
+        //     cdrom->data->buf[0x10],
+        //     cdrom->data->buf[0x11],
+        //     cdrom->data->buf[0x12],
+        //     cdrom->data->buf[0x13]
+        // );
+    }
+
+    cdrom->state = CD_STATE_READ;
+    cdrom->prev_state = CD_STATE_READ;
+    cdrom->delay = CD_DELAY_START_READ;
+    cdrom->read_ongoing = 1;
+}
+
+void cdrom_cmd_reset(psx_cdrom_t* cdrom) {
+    printf("reset\n");
+}
+
+void cdrom_cmd_getq(psx_cdrom_t* cdrom) {
+
+}
+
+void cdrom_cmd_readtoc(psx_cdrom_t* cdrom) {
+    if (cdrom->state == CD_STATE_TX_RESP1) {
+        cdrom_set_int(cdrom, 3);
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom->delay = CD_DELAY_1MS * 10;
+        cdrom->state = CD_STATE_TX_RESP2;
+        // cdrom->busy = 1;
+    } else {
+        cdrom_set_int(cdrom, 2);
+        queue_push(cdrom->response, cdrom_get_stat(cdrom));
+
+        cdrom_restore_state(cdrom);
+    }
+}
+
+void cdrom_cmd_videocd(psx_cdrom_t* cdrom) {
+
+}
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/list.c
@@ -1,0 +1,122 @@
+#include "list.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+list_t* list_create(void) {
+    list_t* list = malloc(sizeof(list_t));
+
+    list_init(list);
+
+    return list;
+}
+
+void list_init(list_t* list) {
+    list->first = NULL;
+    list->last = NULL;
+    list->size = 0;
+}
+
+void list_push_front(list_t* list, void* data) {
+    node_t* node = malloc(sizeof(node_t));
+
+    node->data = data;
+    node->next = list->first;
+
+    list->first = node;
+    
+    if (!list->last)
+        list->last = list->first;
+
+    ++list->size;
+}
+
+void list_push_back(list_t* list, void* data) {
+    node_t* node = malloc(sizeof(node_t));
+
+    node->data = data;
+    node->next = NULL;
+
+    if (!list->last) {
+        list->first = node;
+        list->last = node;
+    } else {
+        list->last->next = node;
+        list->last = node;
+    }
+
+    ++list->size;
+}
+
+void list_pop_front(list_t* list) {
+    if (!list->first)
+        return;
+
+    node_t* next = list->first->next;
+
+    free(list->first);
+
+    list->first = next;
+
+    --list->size;
+}
+
+void list_pop_back(list_t* list) {
+    if (!list->last)
+        return;
+
+    node_t* node = list->first;
+
+    while (node->next != list->last)
+        node = node->next;
+
+    free(node->next);
+
+    node->next = NULL;
+
+    list->last = node;
+}
+
+node_t* list_front(list_t* list) {
+    return list->first;
+}
+
+node_t* list_back(list_t* list) {
+    return list->last;
+}
+
+node_t* list_at(list_t* list, int index) {
+    if (index > list->size)
+        return NULL;
+
+    node_t* node = list->first;
+
+    for (int i = 0; i < index; i++)
+        node = node->next;
+
+    return node;
+}
+
+void list_iterate(list_t* list, void (*func)(void*)) {
+    node_t* node = list->first;
+
+    while (node) {
+        func(node->data);
+
+        node = node->next;
+    }
+}
+
+void list_destroy(list_t* list) {
+    node_t* node = list->first;
+
+    while (node) {
+        node_t* next = node->next;
+
+        free(node);
+
+        node = next;
+    }
+
+    free(list);
+}
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/list.h
@@ -1,0 +1,31 @@
+#ifndef LIST_H
+#define LIST_H
+
+#include <stddef.h>
+
+typedef struct node_t node_t;
+
+typedef struct node_t {
+    node_t* next;
+    void* data;
+} node_t;
+
+typedef struct {
+    node_t* first;
+    node_t* last;
+    size_t size;
+} list_t;
+
+list_t* list_create(void);
+void list_init(list_t* list);
+void list_push_front(list_t* list, void* data);
+void list_push_back(list_t* list, void* data);
+void list_pop_front(list_t* list);
+void list_pop_back(list_t* list);
+node_t* list_front(list_t* list);
+node_t* list_back(list_t* list);
+node_t* list_at(list_t* list, int index);
+void list_iterate(list_t* list, void (*func)(void*));
+void list_destroy(list_t* list);
+
+#endif
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/queue.c
@@ -1,0 +1,75 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "queue.h"
+
+queue_t* queue_create(void) {
+    return malloc(sizeof(queue_t));
+}
+
+void queue_init(queue_t* queue, size_t size) {
+    queue->buf = malloc(size);
+    queue->read_index = 0;
+    queue->write_index = 0;
+    queue->size = size;
+}
+
+void queue_push(queue_t* queue, uint8_t value) {
+    if (queue_is_full(queue))
+        return;
+
+    queue->buf[queue->write_index++] = value;
+}
+
+uint8_t queue_pop(queue_t* queue) {
+    if (queue_is_empty(queue))
+        return 0;
+
+    uint8_t data = queue->buf[queue->read_index++];
+
+    if (queue_is_empty(queue))
+        queue_reset(queue);
+
+    return data;
+}
+
+uint8_t queue_peek(queue_t* queue) {
+    if (queue_is_empty(queue))
+        return 0;
+
+    return queue->buf[queue->read_index];
+}
+
+int queue_is_empty(queue_t* queue) {
+    return queue->read_index == queue->write_index;
+}
+
+int queue_is_full(queue_t* queue) {
+    return queue->write_index == queue->size;
+}
+
+void queue_reset(queue_t* queue) {
+    queue->write_index = 0;
+    queue->read_index = 0;
+}
+
+void queue_clear(queue_t* queue) {
+    for (int i = 0; i < queue->write_index; i++)
+        queue->buf[i] = 0;
+
+    queue_reset(queue);
+}
+
+int queue_size(queue_t* queue) {
+    return queue->write_index - queue->read_index;
+}
+
+int queue_max_size(queue_t* queue) {
+    return queue->size;
+}
+
+void queue_destroy(queue_t* queue) {
+    free(queue->buf);
+    free(queue);
+}
\ No newline at end of file
--- /dev/null
+++ b/psx/dev/cdrom/queue.h
@@ -1,0 +1,26 @@
+#ifndef QUEUE_H
+#define QUEUE_H
+
+#include <stdint.h>
+
+typedef struct {
+    uint8_t* buf;
+    size_t read_index;
+    size_t write_index;
+    size_t size;
+} queue_t;
+
+queue_t* queue_create(void);
+void queue_init(queue_t* queue, size_t size);
+void queue_push(queue_t* queue, uint8_t value);
+uint8_t queue_pop(queue_t* queue);
+uint8_t queue_peek(queue_t* queue);
+int queue_is_empty(queue_t* queue);
+int queue_is_full(queue_t* queue);
+void queue_reset(queue_t* queue);
+void queue_clear(queue_t* queue);
+int queue_size(queue_t* queue);
+int queue_max_size(queue_t* queue);
+void queue_destroy(queue_t* queue);
+
+#endif
\ No newline at end of file
--- a/psx/dev/dma.c
+++ b/psx/dev/dma.c
@@ -402,7 +402,7 @@
         // exit(1);
     }
 
-    dma->spu_irq_delay = BCR_SIZE(spu) * BCR_BCNT(spu);
+    dma->spu_irq_delay = 32;
 
     if (CHCR_TDIR(spu)) {
         for (int j = 0; j < blocks; j++) {
@@ -482,7 +482,7 @@
     }
 
     if (dma->spu_irq_delay) {
-        dma->spu_irq_delay -= cyc;
+        dma->spu_irq_delay = 0;
 
         if (dma->spu_irq_delay <= 0)
             if (dma->dicr & DICR_DMA4EN)
--- a/psx/dev/gpu.c
+++ b/psx/dev/gpu.c
@@ -178,6 +178,47 @@
     }
 }
 
+uint16_t gpu_fetch_texel_bilinear(psx_gpu_t* gpu, float tx, float ty, uint32_t tpx, uint32_t tpy, uint16_t clutx, uint16_t cluty, int depth) {
+    float txf = floorf(tx);
+    float tyf = floorf(ty);
+    float txc = txf + 1.0f;
+    float tyc = tyf + 1.0f;
+
+    int s0 = gpu_fetch_texel(gpu, (int)txf, (int)tyf, tpx, tpy, clutx, cluty, depth);
+
+    if (!s0)
+        return 0;
+
+    int s1 = gpu_fetch_texel(gpu, (int)txc, (int)tyf, tpx, tpy, clutx, cluty, depth);
+    int s2 = gpu_fetch_texel(gpu, (int)txf, (int)tyc, tpx, tpy, clutx, cluty, depth);
+    int s3 = gpu_fetch_texel(gpu, (int)txc, (int)tyc, tpx, tpy, clutx, cluty, depth);
+
+    float s0r = (s0 >> 0) & 0x1f;
+    float s0g = (s0 >> 5) & 0x1f;
+    float s0b = (s0 >> 10) & 0x1f;
+    float s1r = (s1 >> 0) & 0x1f;
+    float s1g = (s1 >> 5) & 0x1f;
+    float s1b = (s1 >> 10) & 0x1f;
+    float s2r = (s2 >> 0) & 0x1f;
+    float s2g = (s2 >> 5) & 0x1f;
+    float s2b = (s2 >> 10) & 0x1f;
+    float s3r = (s3 >> 0) & 0x1f;
+    float s3g = (s3 >> 5) & 0x1f;
+    float s3b = (s3 >> 10) & 0x1f;
+
+    float q1r = s0r * (txc - tx) + s1r * (tx - txf);
+    float q1g = s0g * (txc - tx) + s1g * (tx - txf);
+    float q1b = s0b * (txc - tx) + s1b * (tx - txf);
+    float q2r = s2r * (txc - tx) + s3r * (tx - txf);
+    float q2g = s2g * (txc - tx) + s3g * (tx - txf);
+    float q2b = s2b * (txc - tx) + s3b * (tx - txf);
+    int qr = q1r * (tyc - ty) + q2r * (ty - tyf);
+    int qg = q1g * (tyc - ty) + q2g * (ty - tyf);
+    int qb = q1b * (tyc - ty) + q2b * (ty - tyf);
+
+    return qr | (qg << 5) | (qb << 10) | (s0 & 0x8000) | (s1 & 0x8000) | (s2 & 0x8000) | (s3 & 0x8000);
+}
+
 #define TL(z, a, b) \
     ((z < 0) || ((z == 0) && ((b.y > a.y) || ((b.y == a.y) && (b.x < a.x)))))
 
@@ -286,10 +327,10 @@
             }
 
             if (data.attrib & PA_TEXTURED) {
-                uint32_t tx = roundf(((z0 * a.tx) + (z1 * b.tx) + (z2 * c.tx)) / area);
-                uint32_t ty = roundf(((z0 * a.ty) + (z1 * b.ty) + (z2 * c.ty)) / area);
+                float tx = ((z0 * a.tx) + (z1 * b.tx) + (z2 * c.tx)) / area;
+                float ty = ((z0 * a.ty) + (z1 * b.ty) + (z2 * c.ty)) / area;
 
-                uint16_t texel = gpu_fetch_texel(gpu, tx, ty, tpx, tpy, clutx, cluty, depth);
+                uint16_t texel = gpu_fetch_texel_bilinear(gpu, tx, ty, tpx, tpy, clutx, cluty, depth);
 
                 if (!texel)
                     continue;
@@ -963,6 +1004,86 @@
     }
 }
 
+void gpu_line(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+
+            int shaded   = (gpu->buf[0] & 0x10000000) != 0;
+            int polyline = (gpu->buf[0] & 0x08000000) != 0;
+
+            gpu->cmd_args_remaining = polyline ? -1 : (shaded ? 3 : 2);
+            gpu->line_done = 0;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (gpu->buf[0] & 0x08000000) {
+                if ((gpu->buf[gpu->buf_index - 1] & 0xf000f000) == 0x50005000) {
+                    gpu->state = GPU_STATE_RECV_CMD;
+
+                    return;
+                }
+
+                // int shaded = (gpu->buf[0] & 0x10000000) != 0;
+
+                // if (shaded) {
+                //     if (gpu->buf_index > 2) {
+
+                //     }
+                // }
+
+                // if (gpu->buf_index > overflow) {
+                //     vertex_t v0, v1;
+
+                //     if (shaded) {
+                //         v0.c = gpu->buf[0] & 0xffffff;
+                //         v1.c = gpu->buf[4] & 0xffffff;
+                //         v0.x = gpu->buf[1] & 0xffff;
+                //         v0.y = gpu->buf[1] >> 16;
+                //         v1.x = gpu->buf[3] & 0xffff;
+                //         v1.y = gpu->buf[3] >> 16;
+                //     } else {
+                //         v0.c = gpu->buf[0] & 0xffffff;
+                //         v1.c = gpu->buf[0] & 0xffffff;
+                //         v0.x = gpu->buf[1] & 0xffff;
+                //         v0.y = gpu->buf[1] >> 16;
+                //         v1.x = gpu->buf[2] & 0xffff;
+                //         v1.y = gpu->buf[2] >> 16;
+                //     }
+
+                //     gpu->prev_line_vertex = v1;
+
+                //     gpu_render_flat_line(gpu, v0, v1, gpu->buf[0] & 0xffffff);
+
+                //     gpu->buf_index = 1;
+                // }
+            } else if (!gpu->cmd_args_remaining) {
+                vertex_t v0, v1;
+
+                if (gpu->buf[0] & 0x10000000) {
+                    v0.c = gpu->buf[0] & 0xffffff;
+                    v1.c = gpu->buf[2] & 0xffffff;
+                    v0.x = gpu->buf[1] & 0xffff;
+                    v0.y = gpu->buf[1] >> 16;
+                    v1.x = gpu->buf[3] & 0xffff;
+                    v1.y = gpu->buf[3] >> 16;
+                } else {
+                    v0.c = gpu->buf[0] & 0xffffff;
+                    v1.c = gpu->buf[0] & 0xffffff;
+                    v0.x = gpu->buf[1] & 0xffff;
+                    v0.y = gpu->buf[1] >> 16;
+                    v1.x = gpu->buf[2] & 0xffff;
+                    v1.y = gpu->buf[2] >> 16;
+                }
+
+                gpu_render_flat_line(gpu, v0, v1, BGR555(gpu->buf[0] & 0xffffff));
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
 void gpu_cmd_a0(psx_gpu_t* gpu) {
     switch (gpu->state) {
         case GPU_STATE_RECV_CMD: {
@@ -1547,11 +1668,11 @@
                 for (int y = gpu->v0.y; y < (gpu->v0.y + gpu->ysiz); y++) {
                     for (int x = gpu->v0.x; x < (gpu->v0.x + gpu->xsiz); x++) {
                         // This shouldn't be needed
-                        int bc = (x >= gpu->draw_x1) && (x <= gpu->draw_x2) &&
-                                 (y >= gpu->draw_y1) && (y <= gpu->draw_y2);
+                        // int bc = (x >= gpu->draw_x1) && (x <= gpu->draw_x2) &&
+                        //          (y >= gpu->draw_y1) && (y <= gpu->draw_y2);
 
-                        if (!bc)
-                            continue;
+                        // if (!bc)
+                        //     continue;
 
                         if ((x < 1024) && (y < 512) && (x >= 0) && (y >= 0))
                             gpu->vram[x + (y * 1024)] = color;
@@ -1601,18 +1722,12 @@
 void psx_gpu_update_cmd(psx_gpu_t* gpu) {
     int type = (gpu->buf[0] >> 29) & 7;
 
-    if (type == 3) {
-        gpu_rect(gpu);
-
-        return;
+    switch (type) {
+        case 1: gpu_poly(gpu); return;
+        case 2: gpu_line(gpu); return;
+        case 3: gpu_rect(gpu); return;
     }
 
-    if (type == 1) {
-        gpu_poly(gpu);
-
-        return;
-    }
-
     switch (gpu->buf[0] >> 24) {
         case 0x00: /* nop */ break;
         case 0x01: /* Cache clear */ break;
@@ -1820,7 +1935,7 @@
         // GetlocP commands, if the timer is too slow it will
         // break.
         // if (!(gpu->line & 7))
-            // psx_ic_irq(gpu->ic, IC_TIMER2);
+        //     psx_ic_irq(gpu->ic, IC_SPU);
             // psx_ic_irq(gpu->ic, IC_SPU);
     } else {
         gpu->gpustat &= ~(1 << 31);
--- a/psx/dev/gpu.h
+++ b/psx/dev/gpu.h
@@ -102,6 +102,8 @@
     int buf_index;
     int cmd_args_remaining;
     int cmd_data_remaining;
+    int line_done;
+    vertex_t prev_line_vertex;
 
     // Command counters
     uint32_t color;
--- a/psx/dev/ic.c
+++ b/psx/dev/ic.c
@@ -70,8 +70,11 @@
     }
 
     // Emulate acknowledge
-    if (!(ic->stat & ic->mask))
+    if (!(ic->stat & ic->mask)) {
         ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
+    } else {
+        psx_cpu_set_irq_pending(ic->cpu);
+    }
 }
 
 void psx_ic_write16(psx_ic_t* ic, uint32_t offset, uint16_t value) {
@@ -83,8 +86,11 @@
     }
 
     // Emulate acknowledge
-    if (!(ic->stat & ic->mask))
+    if (!(ic->stat & ic->mask)) {
         ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
+    } else {
+        psx_cpu_set_irq_pending(ic->cpu);
+    }
 }
 
 void psx_ic_write8(psx_ic_t* ic, uint32_t offset, uint8_t value) {
@@ -100,15 +106,15 @@
     }
 
     // Emulate acknowledge
-    if (!(ic->stat & ic->mask))
+    if (!(ic->stat & ic->mask)) {
         ic->cpu->cop0_r[COP0_CAUSE] &= ~SR_IM2;
+    } else {
+        psx_cpu_set_irq_pending(ic->cpu);
+    }
 }
 
 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/mc2.c
+++ b/psx/dev/mc2.c
@@ -45,7 +45,7 @@
 
 void psx_mc2_write32(psx_mc2_t* mc2, uint32_t offset, uint32_t value) {
     switch (offset) {
-        case 0x00: mc2->ram_size = value; break;
+        case 0x00: printf("ram_size write %08x\n", value); mc2->ram_size = value; break;
 
         default: {
             log_warn("Unhandled 32-bit MC2 write at offset %08x (%08x)", offset, value);
--- a/psx/dev/mcd.c
+++ b/psx/dev/mcd.c
@@ -157,7 +157,7 @@
     // log_fatal("mcd write %02x", data);
     // log_set_quiet(1);
 
-    // printf("mcd write %02x\n", data);
+    printf("mcd write %02x\n", data);
 
     switch (mcd->state) {
         case MCD_STATE_TX_FLG: mcd->mode = data; break;
--- a/psx/dev/pad.c
+++ b/psx/dev/pad.c
@@ -100,6 +100,13 @@
 
                 psx_mcd_write(mcd, data);
 
+                if (pad->ctrl & CTRL_ACIE) {
+                    pad->irq_bit = 1;
+                    pad->cycles_until_irq = 1024;
+
+                    return;
+                }
+
                 if (!psx_mcd_query(mcd))
                     pad->dest[slot] = 0;
             } break;
@@ -107,7 +114,7 @@
 
         if (pad->ctrl & CTRL_ACIE) {
             pad->irq_bit = 1;
-            pad->cycles_until_irq = JOY_IRQ_DELAY;
+            pad->cycles_until_irq = (pad->dest[slot] == DEST_MCD) ? 2048 : JOY_IRQ_DELAY;
         }
     }
 }
--- a/psx/dev/ram.c
+++ b/psx/dev/ram.c
@@ -16,8 +16,10 @@
     ram->io_size = PSX_RAM_SIZE;
 
     ram->mc2 = mc2;
-    ram->buf = (uint8_t*)malloc(RAM_SIZE);
+    ram->buf = (uint8_t*)malloc(size);
+    ram->size = size;
 
+    // Size has to be a multiple of 2MB, default to 2MB
     if (size & 0x1ffff)
         size = RAM_SIZE_2MB;
 
@@ -25,37 +27,49 @@
 }
 
 uint32_t psx_ram_read32(psx_ram_t* ram, uint32_t offset) {
-    offset &= RAM_SIZE - 1;
+    if (((ram->mc2->ram_size >> 9) & 7) == 3)
+        if (offset >= 0x400000)
+            return 0xffffffff;
 
+    offset &= ram->size - 1;
+
     return *((uint32_t*)(ram->buf + offset));
 }
 
 uint16_t psx_ram_read16(psx_ram_t* ram, uint32_t offset) {
-    offset &= RAM_SIZE - 1;
+    if (((ram->mc2->ram_size >> 9) & 7) == 3)
+        if (offset >= 0x400000)
+            return 0xffff;
 
+    offset &= ram->size - 1;
+
     return *((uint16_t*)(ram->buf + offset));
 }
 
 uint8_t psx_ram_read8(psx_ram_t* ram, uint32_t offset) {
-    offset &= RAM_SIZE - 1;
+    if (((ram->mc2->ram_size >> 9) & 7) == 3)
+        if (offset >= 0x400000)
+            return 0xff;
 
+    offset &= ram->size - 1;
+
     return ram->buf[offset];
 }
 
 void psx_ram_write32(psx_ram_t* ram, uint32_t offset, uint32_t value) {
-    offset &= RAM_SIZE - 1;
+    offset &= ram->size - 1;
 
     *((uint32_t*)(ram->buf + offset)) = value;
 }
 
 void psx_ram_write16(psx_ram_t* ram, uint32_t offset, uint16_t value) {
-    offset &= RAM_SIZE - 1;
+    offset &= ram->size - 1;
 
     *((uint16_t*)(ram->buf + offset)) = value;
 }
 
 void psx_ram_write8(psx_ram_t* ram, uint32_t offset, uint8_t value) {
-    offset &= RAM_SIZE - 1;
+    offset &= ram->size - 1;
 
     ram->buf[offset] = value;
 }
--- a/psx/dev/ram.h
+++ b/psx/dev/ram.h
@@ -6,8 +6,7 @@
 #include "../log.h"
 #include "mc2.h"
 
-#define RAM_SIZE        0x200000
-#define PSX_RAM_SIZE    0x1f000000
+#define PSX_RAM_SIZE    0x800000 // 8MB window
 #define PSX_RAM_BEGIN   0x00000000
 //#define PSX_RAM_END     0x001fffff
 #define PSX_RAM_END     0x1effffff
@@ -20,6 +19,8 @@
 typedef struct {
     uint32_t bus_delay;
     uint32_t io_base, io_size;
+
+    size_t size;
 
     psx_mc2_t* mc2;
 
--- a/psx/dev/spu.c
+++ b/psx/dev/spu.c
@@ -5,11 +5,20 @@
 #include "spu.h"
 #include "../log.h"
 
-#define CLAMP(v, l, h) ((v <= l) ? l : ((v >= h) ? h : v))
+#define CLAMP(v, l, h) (((v) <= (l)) ? (l) : (((v) >= (h)) ? (h) : (v)))
 #define MAX(a, b) ((a) > (b) ? (a) : (b))
 
 #define VOICE_COUNT 24
 
+static float interpolate_hermite(float a, float b, float c, float d, float t) {
+    float x = -a/2.0f + (3.0f*b)/2.0f - (3.0f*c)/2.0f + d/2.0f;
+    float y = a - (5.0f*b)/2.0f + 2.0f*c - d / 2.0f;
+    float z = -a/2.0f + c/2.0f;
+    float w = b;
+ 
+    return (x*t*t*t) + (y*t*t) + (z*t) + w;
+}
+
 static const int g_spu_pos_adpcm_table[] = {
     0, +60, +115, +98, +122
 };
@@ -136,15 +145,17 @@
 
     spu->data[v].block_flags = spu->ram[addr + 1];
 
-    unsigned shift  = 12 - (hdr & 0x0f);
+    unsigned hdr_shift = hdr & 0x0f;
+
+    if (hdr_shift > 12)
+        hdr_shift = 9;
+
+    unsigned shift  = 12 - hdr_shift;
     unsigned filter = (hdr >> 4) & 7;
 
     int32_t f0 = g_spu_pos_adpcm_table[filter];
     int32_t f1 = g_spu_neg_adpcm_table[filter];
 
-    if ((spu->irq9addr << 3) == addr)
-        psx_ic_irq(spu->ic, IC_SPU);
-
     for (int j = 0; j < 28; j++) {
         uint16_t n = (spu->ram[addr + 2 + (j >> 1)] >> ((j & 1) * 4)) & 0xf;
 
@@ -338,7 +349,6 @@
         }
     }
 
-    spu->kon |= value & 0x00ffffff;
     spu->endx &= ~(value & 0x00ffffff);
 }
 
@@ -346,8 +356,6 @@
     for (int i = 0; i < VOICE_COUNT; i++)
         if (value & (1 << i))
             adsr_load_release(spu, i);
-
-    spu->koff |= value & 0x00ffffff;
 }
 
 int spu_handle_write(psx_spu_t* spu, uint32_t offset, uint32_t value) {
@@ -472,6 +480,8 @@
 #define R16(addr) (spu_read_reverb(spu, addr))
 #define W16(addr, value) spu_write_reverb(spu, addr, value)
 
+#define SAT(v) CLAMP(v, INT16_MIN, INT16_MAX)
+
 void spu_get_reverb_sample(psx_spu_t* spu, int inl, int inr, int* outl, int* outr) {
     uint32_t mbase = spu->mbase << 3;
     uint32_t dapf1 = spu->dapf1 << 3;
@@ -497,46 +507,62 @@
     uint32_t mrapf1 = spu->mrapf1 << 3;
     uint32_t mrapf2 = spu->mrapf2 << 3;
 
-    float vlin = (float)spu->vlin / 32767.0f;
-    float vrin = (float)spu->vrin / 32767.0f;
-    float viir = (float)spu->viir / 32767.0f;
-    float vwall = (float)spu->vwall / 32767.0f;
-    float vcomb1 = (float)spu->vcomb1 / 32767.0f;
-    float vcomb2 = (float)spu->vcomb2 / 32767.0f;
-    float vcomb3 = (float)spu->vcomb3 / 32767.0f;
-    float vcomb4 = (float)spu->vcomb4 / 32767.0f;
-    float vapf1 = (float)spu->vapf1 / 32767.0f;
-    float vapf2 = (float)spu->vapf2 / 32767.0f;
-    float vlout = (float)spu->vlout / 32767.0f;
-    float vrout = (float)spu->vrout / 32767.0f;
+    float vlin = (float)spu->vlin;
+    float vrin = (float)spu->vrin;
+    float viir = (float)spu->viir;
+    float vwall = (float)spu->vwall;
+    float vcomb1 = (float)spu->vcomb1;
+    float vcomb2 = (float)spu->vcomb2;
+    float vcomb3 = (float)spu->vcomb3;
+    float vcomb4 = (float)spu->vcomb4;
+    float vapf1 = (float)spu->vapf1;
+    float vapf2 = (float)spu->vapf2;
+    float vlout = (float)spu->vlout;
+    float vrout = (float)spu->vrout;
 
-    int lin = ((float)inl * 0.5f) * vlin;
-    int rin = ((float)inr * 0.5f) * vrin;
+    int lin = (vlin * inl) / 32768.0f;
+    int rin = (vrin * inr) / 32768.0f;
 
-    int mlsamev = (lin + R16(dlsame)*vwall - R16(mlsame-2))*viir + R16(mlsame-2);
-    int mrsamev = (rin + R16(drsame)*vwall - R16(mrsame-2))*viir + R16(mrsame-2);
-    int mldiffv = (lin + R16(drdiff)*vwall - R16(mldiff-2))*viir + R16(mldiff-2);
-    int mrdiffv = (rin + R16(dldiff)*vwall - R16(mrdiff-2))*viir + R16(mrdiff-2);
+    // same side reflection ltol and rtor
+    int16_t mlsamev = SAT(lin + ((R16(dlsame) * vwall) / 32768.0f) - ((R16(mlsame - 2) * viir) / 32768.0f) + R16(mlsame - 2));
+    int16_t mrsamev = SAT(rin + ((R16(drsame) * vwall) / 32768.0f) - ((R16(mrsame - 2) * viir) / 32768.0f) + R16(mrsame - 2));
+    W16(mlsame, mlsamev);
+    W16(mrsame, mrsamev);
 
-    W16(mlsame, CLAMP(mlsamev, -0x8000, 0x7fff));
-    W16(mrsame, CLAMP(mrsamev, -0x8000, 0x7fff));
-    W16(mldiff, CLAMP(mldiffv, -0x8000, 0x7fff));
-    W16(mrdiff, CLAMP(mrdiffv, -0x8000, 0x7fff));
+    // different side reflection ltor and rtol
+    int16_t mldiffv = SAT(lin + ((R16(drdiff) * vwall) / 32768.0f) - ((R16(mldiff - 2) * viir) / 32768.0f) + R16(mldiff - 2));
+    int16_t mrdiffv = SAT(rin + ((R16(dldiff) * vwall) / 32768.0f) - ((R16(mrdiff - 2) * viir) / 32768.0f) + R16(mrdiff - 2));
+    W16(mldiff, mldiffv);
+    W16(mrdiff, mrdiffv);
 
-    int lout=vcomb1*R16(mlcomb1)+vcomb2*R16(mlcomb2)+vcomb3*R16(mlcomb3)+vcomb4*R16(mlcomb4);
-    int rout=vcomb1*R16(mrcomb1)+vcomb2*R16(mrcomb2)+vcomb3*R16(mrcomb3)+vcomb4*R16(mrcomb4);
+    // early echo (comb filter with input from buffer)
+    int16_t l = SAT((vcomb1 * R16(mlcomb1) / 32768.0f) + (vcomb2 * R16(mlcomb2) / 32768.0f) + (vcomb3 * R16(mlcomb3) / 32768.0f) + (vcomb4 * R16(mlcomb4) / 32768.0f));
+    int16_t r = SAT((vcomb1 * R16(mrcomb1) / 32768.0f) + (vcomb2 * R16(mrcomb2) / 32768.0f) + (vcomb3 * R16(mrcomb3) / 32768.0f) + (vcomb4 * R16(mrcomb4) / 32768.0f));
 
-    lout = CLAMP(lout, -0x8000, 0x7fff);
-    rout = CLAMP(rout, -0x8000, 0x7fff);
+    // late reverb apf1 (all pass filter 1 with input from comb)
+    l = SAT(l - SAT((vapf1 * R16(mlapf1 - dapf1)) / 32768.0f));
+    r = SAT(r - SAT((vapf1 * R16(mrapf1 - dapf1)) / 32768.0f));
 
-    lout-=CLAMP(vapf1*R16(mlapf1 - dapf1), -0x8000, 0x7fff); W16(mlapf1, lout); lout*=vapf1+((float)R16(mlapf1 - dapf1) / 32767.0f);
-    rout-=CLAMP(vapf1*R16(mrapf1 - dapf1), -0x8000, 0x7fff); W16(mrapf1, rout); rout*=vapf1+((float)R16(mrapf1 - dapf1) / 32767.0f);
-    lout-=CLAMP(vapf2*R16(mlapf2 - dapf2), -0x8000, 0x7fff); W16(mlapf2, lout); lout*=vapf2+((float)R16(mlapf2 - dapf2) / 32767.0f);
-    rout-=CLAMP(vapf2*R16(mrapf2 - dapf2), -0x8000, 0x7fff); W16(mrapf2, rout); rout*=vapf2+((float)R16(mrapf2 - dapf2) / 32767.0f);
+    W16(mlapf1, l);
+    W16(mrapf1, r);
+    
+    l = SAT((l * vapf1 / 32768.0f) + R16(mlapf1 - dapf1));
+    r = SAT((r * vapf1 / 32768.0f) + R16(mrapf1 - dapf1));
 
-    *outl = lout * vlout;
-    *outr = rout * vrout;
+    // late reverb apf2 (all pass filter 2 with input from apf1)
+    l = SAT(l - SAT((vapf2 * R16(mlapf2 - dapf2)) / 32768.0f));
+    r = SAT(r - SAT((vapf2 * R16(mrapf2 - dapf2)) / 32768.0f));
+    
+    W16(mlapf2, l);
+    W16(mrapf2, r);
 
+    l = SAT((l * vapf2 / 32768.0f) + R16(mlapf2 - dapf2));
+    r = SAT((r * vapf2 / 32768.0f) + R16(mrapf2 - dapf2));
+
+    // output to mixer (output volume multiplied with input from apf2)
+    *outl = SAT(l * vlout / 32768.0f);
+    *outr = SAT(r * vrout / 32768.0f);
+
     spu->revbaddr = MAX(mbase, (spu->revbaddr + 2) & 0x7fffe);
 }
 
@@ -563,11 +589,6 @@
 
         ++active_voice_count;
 
-        // Shift 3 older samples around
-        spu->data[v].s[3] = spu->data[v].s[2];
-        spu->data[v].s[2] = spu->data[v].s[1];
-        spu->data[v].s[1] = spu->data[v].s[0];
-
         uint32_t sample_index = spu->data[v].counter >> 12;
 
         if (sample_index > 27) {
@@ -581,7 +602,15 @@
 
             switch (spu->data[v].block_flags & 3) {
                 case 0: case 2: {
+                    if (((spu->irq9addr << 3) == spu->data[v].current_addr) && (spu->spucnt & 0x40)) {
+                        psx_ic_irq(spu->ic, IC_SPU);
+                    }
+
                     spu->data[v].current_addr += 0x10;
+
+                    if (((spu->irq9addr << 3) == spu->data[v].current_addr) && (spu->spucnt & 0x40)) {
+                        psx_ic_irq(spu->ic, IC_SPU);
+                    }
                 } break;
 
                 case 1: {
@@ -601,7 +630,13 @@
             spu_read_block(spu, v);
         }
 
-        // Fetch ADPCM sample
+        //  Fetch ADPCM sample
+        if (spu->data[v].prev_sample_index != sample_index) {
+            spu->data[v].s[3] = spu->data[v].s[2];
+            spu->data[v].s[2] = spu->data[v].s[1];
+            spu->data[v].s[1] = spu->data[v].s[0];
+        }
+
         spu->data[v].s[0] = spu->data[v].buf[sample_index];
 
         // Apply 4-point Gaussian interpolation
@@ -610,8 +645,16 @@
         int16_t g1 = g_spu_gauss_table[0x1ff - gauss_index];
         int16_t g2 = g_spu_gauss_table[0x100 + gauss_index];
         int16_t g3 = g_spu_gauss_table[0x000 + gauss_index];
-        int16_t out;
+        int16_t out = spu->data[v].s[0];
 
+        // out = interpolate_hermite(
+        //     spu->data[v].s[3],
+        //     spu->data[v].s[2],
+        //     spu->data[v].s[1],
+        //     spu->data[v].s[0],
+        //     (spu->data[v].counter & 0xfff) / 4096.0f
+        // );
+
         out  = (g0 * spu->data[v].s[3]) >> 15;
         out += (g1 * spu->data[v].s[2]) >> 15;
         out += (g2 * spu->data[v].s[1]) >> 15;
@@ -619,8 +662,8 @@
 
         float adsr_vol = (float)spu->voice[v].envcvol / 32767.0f;
 
-        float samplel = (out * spu->data[v].lvol) * adsr_vol * (float)spu->mainlvol / 32767.0f; 
-        float sampler = (out * spu->data[v].rvol) * adsr_vol * (float)spu->mainrvol / 32767.0f; 
+        float samplel = (out * spu->data[v].lvol) * adsr_vol; 
+        float sampler = (out * spu->data[v].rvol) * adsr_vol; 
 
         left += samplel;
         right += sampler;
@@ -634,23 +677,35 @@
 
         /* To-do: Do pitch modulation here */
 
+        spu->data[v].prev_sample_index = spu->data[v].counter >> 12;
         spu->data[v].counter += step;
     }
 
-    if (!active_voice_count)
-        return 0x00000000;
+    // if (!active_voice_count)
+    //     return 0x00000000;
     
     int16_t clamprl = CLAMP(revl, INT16_MIN, INT16_MAX);
     int16_t clamprr = CLAMP(revr, INT16_MIN, INT16_MAX);
     int16_t clampsl = CLAMP(left, INT16_MIN, INT16_MAX);
     int16_t clampsr = CLAMP(right, INT16_MIN, INT16_MAX);
-    
-    if ((spu->spucnt & 0x0080) && spu->even_cycle)
-        spu_get_reverb_sample(spu, clamprl, clamprr, &spu->lrsl, &spu->lrsr);
 
-    uint16_t clampl = CLAMP(clampsl + spu->lrsl, INT16_MIN, INT16_MAX);
-    uint16_t clampr = CLAMP(clampsr + spu->lrsr, INT16_MIN, INT16_MAX);
+    if ((spu->spucnt & 0x4000) == 0)
+        return 0;
 
+    uint16_t clampl;
+    uint16_t clampr;
+
+    if (spu->spucnt & 0x0080) {
+        if (spu->even_cycle)
+            spu_get_reverb_sample(spu, clamprl, clamprr, &spu->lrsl, &spu->lrsr);
+
+        clampl = CLAMP((clampsl + spu->lrsl), INT16_MIN, INT16_MAX) * (float)spu->mainlvol / 32767.0f;
+        clampr = CLAMP((clampsr + spu->lrsr), INT16_MIN, INT16_MAX) * (float)spu->mainrvol / 32767.0f;
+    } else {
+        clampl = CLAMP(clampsl, INT16_MIN, INT16_MAX) * (float)spu->mainlvol / 32767.0f;
+        clampr = CLAMP(clampsr, INT16_MIN, INT16_MAX) * (float)spu->mainrvol / 32767.0f;
+    }
+
     return clampl | (((uint32_t)clampr) << 16);
 }
 
@@ -658,14 +713,22 @@
     int16_t* ptr = buf;
     int16_t* ram = (int16_t*)spu->ram;
 
-    for (int i = 0; i < 0x400;) {
-        ram[i] = ptr[i];
+    for (int i = 0; i < 0x400; i++) {
+        ram[i + 0x000] = *ptr++;
+        ram[i + 0x400] = *ptr++;
+    }
 
-        ++i;
+    // Little bit of lowpass/smoothing
+    for (int i = 0; i < 0x400; i += 8) {
+        int l = 0, r = 0;
 
-        ram[i + 0x400] = ptr[i];
+        for (int j = 0; j < 8; j++) {
+            l += ram[i + j];
+            r += ram[i + j + 0x400];
+        }
 
-        ++i;
+        ram[i + 0x000] = l / 8;
+        ram[i + 0x400] = r / 8;
     }
 }
 
--- a/psx/dev/spu.h
+++ b/psx/dev/spu.h
@@ -124,6 +124,7 @@
         uint32_t counter;
         uint32_t current_addr;
         uint32_t repeat_addr;
+        uint32_t prev_sample_index;
         int16_t s[4];
         int block_flags;
         int16_t buf[28];
@@ -132,6 +133,8 @@
         float rvol;
         int cvol;
         int eon;
+        int reverbl;
+        int reverbr;
 
         /*
         ____lower 16bit (at 1F801C08h+N*10h)___________________________________
--- a/psx/dev/timer.c
+++ b/psx/dev/timer.c
@@ -239,7 +239,7 @@
     if (target_reached) {
         timer->timer[i].target_reached = 1;
 
-        // if ((i == 2) && (T2_CLKSRC == 2))
+        // if ((i == 1) && (T1_CLKSRC == 1))
         //     printf("target %04x (%f) reached\n", timer->timer[i].target, timer->timer[i].counter);
 
         if (timer->timer[i].reset_target)
@@ -250,7 +250,7 @@
     }
 
     if (max_reached) {
-        timer->timer[i].counter -= 65536.0f;
+        timer->timer[i].counter = 0;
         timer->timer[i].max_reached = 1;
 
         if (timer->timer[i].irq_max)
@@ -279,8 +279,8 @@
     timer->timer[i].irq = 1;
 
     if (trigger) {
-        // if ((i == 1))
-        //     printf("timer 1 irq fire\n");
+        if ((i == 1))
+            printf("timer 1 irq fire\n");
 
         psx_ic_irq(timer->ic, 16 << i);
     }
--- a/psx/disc.c
+++ /dev/null
@@ -1,46 +1,0 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    Disc Reader API
-*/
-
-#include "disc.h"
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#define CD_SECTOR_SIZE 0x930
-#define CD_SECTORS_PER_SECOND 75
-
-uint32_t disc_get_addr(msf_t msf) {
-    uint32_t sectors = (((msf.m * 60) + msf.s) * CD_SECTORS_PER_SECOND) + msf.f;
-
-    return sectors * CD_SECTOR_SIZE;
-}
-
-psx_disc_t* psx_disc_create(void) {
-    return (psx_disc_t*)malloc(sizeof(psx_disc_t));
-}
-
-int psx_disc_seek(psx_disc_t* disc, msf_t msf) {
-    return disc->seek_func(disc->udata, msf);
-}
-
-int psx_disc_read_sector(psx_disc_t* disc, void* buf) {
-    return disc->read_sector_func(disc->udata, buf);
-}
-
-int psx_disc_get_track_addr(psx_disc_t* disc, msf_t* msf, int track) {
-    return disc->get_track_addr_func(disc->udata, msf, track);
-}
-
-int psx_disc_get_track_count(psx_disc_t* disc, int* count) {
-    return disc->get_track_count_func(disc->udata, count);
-}
-
-void psx_disc_destroy(psx_disc_t* disc) {
-    disc->destroy_func(disc->udata);
-
-    free(disc);
-}
\ No newline at end of file
--- a/psx/disc.h
+++ /dev/null
@@ -1,45 +1,0 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    Disc Reader API
-*/
-
-#ifndef DISC_H
-#define DISC_H
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "log.h"
-#include "msf.h"
-
-enum {
-    DISC_ERR_TRACK_OUT_OF_BOUNDS = 1,
-    DISC_ERR_ADDR_OUT_OF_BOUNDS
-};
-
-typedef int (*disc_seek_t)(void*, msf_t);
-typedef int (*disc_read_sector_t)(void*, void*);
-typedef int (*disc_get_track_addr_t)(void*, msf_t*, int);
-typedef int (*disc_get_track_count_t)(void*, int*);
-typedef void (*disc_destroy_t)(void*);
-
-typedef struct {
-    void* udata;
-
-    disc_seek_t seek_func;
-    disc_read_sector_t read_sector_func;
-    disc_get_track_addr_t get_track_addr_func;
-    disc_get_track_count_t get_track_count_func;
-    disc_destroy_t destroy_func;
-} psx_disc_t;
-
-psx_disc_t* psx_disc_create(void);
-int psx_disc_seek(psx_disc_t*, msf_t);
-int psx_disc_read_sector(psx_disc_t*, void*);
-int psx_disc_get_track_addr(psx_disc_t*, msf_t*, int);
-int psx_disc_get_track_count(psx_disc_t*, int*);
-void psx_disc_destroy(psx_disc_t*);
-
-#endif
\ No newline at end of file
--- a/psx/disc/bin.c
+++ /dev/null
@@ -1,114 +1,0 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    BIN Loader
-*/
-
-#include "bin.h"
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-psxd_bin_t* psxd_bin_create(void) {
-    return (psxd_bin_t*)malloc(sizeof(psxd_bin_t));
-}
-
-void psxd_bin_init(psxd_bin_t* bin) {
-    memset(bin, 0, sizeof(psxd_bin_t));
-}
-
-int psxd_bin_load(psxd_bin_t* bin, const char* path) {
-    log_fatal("Loading CD image...");
-
-    FILE* file = fopen(path, "rb");
-
-    if (ferror(file) || !file) {
-        fclose(file);
-
-        return 1;
-    }
-
-    fseek(file, 0, SEEK_END);
-
-    bin->buf_size = ftell(file);
-    
-    fseek(file, 0, SEEK_SET);
-
-    bin->buf = malloc(bin->buf_size);
-
-    if (!fread(bin->buf, 1, bin->buf_size, file)) {
-        perror("Error reading BIN CD image file data");
-
-        exit(1);
-    }
-
-    msf_from_address(&bin->end, bin->buf_size);
-
-    fclose(file);
-
-    log_fatal("Loaded BIN image, size=%08x, end=%02u:%02u:%02u",
-        bin->buf_size,
-        bin->end.m,
-        bin->end.s,
-        bin->end.f
-    );
-
-    return 0;
-}
-
-int psxd_bin_seek(void* udata, msf_t msf) {
-    psxd_bin_t* bin = udata;
-
-    msf.s -= 2;
-
-    bin->seek_offset = msf_to_address(msf);
-
-    log_fatal("BIN seek to %02u:%02u:%02u (%08x < %08x)", msf.m, msf.s, msf.f, bin->seek_offset, bin->buf_size);
-
-    if (bin->seek_offset >= bin->buf_size)
-        return DISC_ERR_ADDR_OUT_OF_BOUNDS;
-
-    return 0;
-}
-
-int psxd_bin_read_sector(void* udata, void* buf) {
-    psxd_bin_t* bin = udata;
-
-    log_fatal("BIN reading sector at offset %08x", bin->seek_offset);
-
-    memcpy(buf, bin->buf + bin->seek_offset, CD_SECTOR_SIZE);
-
-    return 0;
-}
-
-int psxd_bin_get_track_addr(void* udata, msf_t* msf, int track) {
-    if (track > 1)
-        return DISC_ERR_TRACK_OUT_OF_BOUNDS;
-
-    msf->m = 0;
-    msf->s = 2;
-
-    return 0;
-}
-
-int psxd_bin_get_track_count(void* udata, int* count) {
-    *count = 1;
-
-    return 0;
-}
-
-void psxd_bin_init_disc(psxd_bin_t* bin, psx_disc_t* disc) {
-    disc->udata = bin;
-    disc->seek_func = psxd_bin_seek;
-    disc->read_sector_func = psxd_bin_read_sector;
-    disc->get_track_addr_func = psxd_bin_get_track_addr;
-    disc->get_track_count_func = psxd_bin_get_track_count;
-    disc->destroy_func = (disc_destroy_t)psxd_bin_destroy;
-}
-
-void psxd_bin_destroy(psxd_bin_t* bin) {
-    free(bin->buf);
-    free(bin);
-}
\ No newline at end of file
--- a/psx/disc/bin.h
+++ /dev/null
@@ -1,30 +1,0 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    BIN Loader
-*/
-
-#ifndef BIN_H
-#define BIN_H
-
-#include "../disc.h"
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-typedef struct {
-    char* buf;
-    uint32_t buf_size;
-    uint32_t seek_offset;
-    msf_t end;
-} psxd_bin_t;
-
-psxd_bin_t* psxd_bin_create(void);
-void psxd_bin_init(psxd_bin_t*);
-int psxd_bin_load(psxd_bin_t*, const char*);
-void psxd_bin_init_disc(psxd_bin_t*, psx_disc_t*);
-void psxd_bin_destroy(psxd_bin_t*);
-
-#endif
\ No newline at end of file
--- a/psx/disc/cue.c
+++ /dev/null
@@ -1,495 +1,0 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    CUE Parser + Loader
-*/
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stddef.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "cue.h"
-#include "../log.h"
-
-#define CUE_BUF_SIZE 256
-
-static const char* g_psxd_cue_errors[] = {
-    "PE_NO_ERROR",
-    "PE_EXPECTED_KEYWORD",
-    "PE_EXPECTED_STRING",
-    "PE_EXPECTED_NUMBER",
-    "PE_EXPECTED_COLON",
-    "PE_NON_SEQUENTIAL_TRACKS",
-    "PE_UNEXPECTED_TOKEN"
-};
-
-static const char* g_psxd_cue_tokens[] = {
-    "4CH",
-    "AIFF",
-    "AUDIO",
-    "BINARY",
-    "CATALOG",
-    "CDG",
-    "CDI_2336",
-    "CDI_2352",
-    "CDTEXTFILE",
-    "DCP",
-    "FILE",
-    "FLAGS",
-    "INDEX",
-    "ISRC",
-    "MODE1_2048",
-    "MODE1_2352",
-    "MODE2_2336",
-    "MODE2_2352",
-    "MOTOROLA",
-    "MP3",
-    "PERFORMER",
-    "POSTGAP",
-    "PRE",
-    "PREGAP",
-    "REM",
-    "SCMS",
-    "SONGWRITER",
-    "TITLE",
-    "TRACK",
-    "WAVE",
-    0
-};
-
-#define EXPECT_KEYWORD(kw) \
-    if (cue_parse_keyword(cue)) \
-        return cue->error; \
-    if (cue_get_keyword(cue) != kw) \
-        ERROR_OUT(PE_UNEXPECTED_TOKEN);
-
-void cue_add_track(psxd_cue_t* cue) {
-    ++cue->num_tracks;
-
-    cue_track_t** new_track = realloc(cue->track, cue->num_tracks * sizeof(cue_track_t*));
-
-    if (!new_track) {
-        printf("Fatal error: Couldn't allocate a new CUE track\n");
-
-        exit(1);
-    }
-
-    cue->track = new_track;
-
-    cue->track[cue->num_tracks - 1] = malloc(sizeof(cue_track_t));
-
-    memset(cue->track[cue->num_tracks - 1], 0, sizeof(cue_track_t));
-}
-
-void* cue_alloc_block(void* buf, size_t* block_size, size_t ext) {
-    *block_size += ext;
-
-    return realloc(buf, *block_size);
-}
-
-void cue_ignore_whitespace(psxd_cue_t* cue) {
-    while (isspace(cue->c))
-        cue->c = fgetc(cue->file);
-}
-
-int cue_get_keyword(psxd_cue_t* cue) {
-    int i = 0;
-
-    const char* token = g_psxd_cue_tokens[i];
-
-    while (token) {
-        if (!strcmp(token, cue->buf)) {
-            return i;
-        } else {
-            token = g_psxd_cue_tokens[++i];
-        }
-    }
-
-    return -1;
-}
-
-#define ERROR_OUT(err) \
-    { cue->error = err; return err; }
-
-int cue_parse_keyword(psxd_cue_t* cue) {
-    if (!isalpha(cue->c))
-        ERROR_OUT(PE_EXPECTED_KEYWORD);
-    
-    while (isalnum(cue->c) || (cue->c == '/')) {
-        *cue->ptr++ = cue->c;
-
-        cue->c = fgetc(cue->file);
-    }
-
-    *cue->ptr = 0;
-
-    cue->ptr = cue->buf;
-
-    cue_ignore_whitespace(cue);
-
-    return 0;
-}
-
-int cue_parse_string(psxd_cue_t* cue) {
-    if (cue->c != '\"')
-        ERROR_OUT(PE_EXPECTED_STRING);
-
-    cue->c = fgetc(cue->file);
-
-    while (cue->c != '\"') {
-        *cue->ptr++ = cue->c;
-
-        cue->c = fgetc(cue->file);
-    }
-
-    *cue->ptr = 0;
-
-    cue->c = fgetc(cue->file);
-
-    cue->ptr = cue->buf;
-
-    cue_ignore_whitespace(cue);
-
-    return 0;
-}
-
-int cue_parse_number(psxd_cue_t* cue) {
-    if (!isdigit(cue->c))
-        ERROR_OUT(PE_EXPECTED_NUMBER);
-    
-    while (isdigit(cue->c)) {
-        *cue->ptr++ = cue->c;
-
-        cue->c = fgetc(cue->file);
-    }
-
-    *cue->ptr = 0;
-
-    cue->ptr = cue->buf;
-
-    cue_ignore_whitespace(cue);
-
-    return 0;
-}
-
-int cue_parse_msf(psxd_cue_t* cue, msf_t* msf) {
-    if (cue_parse_number(cue))
-        return cue->error;
-    
-    if (cue->c != ':')
-        ERROR_OUT(PE_EXPECTED_COLON);
-    
-    cue->c = fgetc(cue->file);
-
-    msf->m = atoi(cue->buf);
-
-    if (cue_parse_number(cue))
-        return cue->error;
-
-    if (cue->c != ':')
-        ERROR_OUT(PE_EXPECTED_COLON);
-    
-    cue->c = fgetc(cue->file);
-    
-    msf->s = atoi(cue->buf);
-
-    if (cue_parse_number(cue))
-        return cue->error;
-    
-    msf->f = atoi(cue->buf);
-
-    return 0;
-}
-
-int cue_parse(psxd_cue_t* cue, FILE* file) {
-    cue->file = file;
-    cue->c = fgetc(file);
-
-    msf_t msf;
-
-    EXPECT_KEYWORD(CUE_FILE);
-
-    parse_file:
-
-    if (cue_parse_string(cue))
-        return cue->error;
-
-    strcpy(cue->current_file, cue->buf);
-
-    EXPECT_KEYWORD(CUE_BINARY);
-    EXPECT_KEYWORD(CUE_TRACK);
-
-    parse_track:
-
-    if (cue_parse_number(cue))
-        return cue->error;
-    
-    int track = atoi(cue->buf) - 1;
-
-    if (track != cue->num_tracks)
-        ERROR_OUT(PE_NON_SEQUENTIAL_TRACKS);
-    
-    cue_add_track(cue);
-
-    cue->track[track]->filename = malloc(strlen(cue->current_file) + 1);
-
-    // Copy current file to track filename
-    strcpy(cue->track[track]->filename, cue->current_file);
-
-    if (cue_parse_keyword(cue))
-        return cue->error;
-
-    cue->track[track]->type = cue_get_keyword(cue);
-
-    // Expecting at least 1 index
-    EXPECT_KEYWORD(CUE_INDEX);
-
-    parse_index:
-
-    if (cue_parse_number(cue))
-        return cue->error;
-    
-    int index = atoi(cue->buf);
-
-    if (cue_parse_msf(cue, &msf))
-        return cue->error;
-
-    cue->track[track]->index[index] = msf;
-
-    if (feof(cue->file)) {
-        fclose(file);
-
-        return 0;
-    }
-
-    if (cue_parse_keyword(cue))
-        return cue->error;
-    
-    switch (cue_get_keyword(cue)) {
-        case CUE_INDEX:
-            goto parse_index;
-
-        case CUE_FILE:
-            goto parse_file;
-
-        case CUE_TRACK:
-            goto parse_track;
-
-        default:
-            ERROR_OUT(PE_UNEXPECTED_TOKEN);
-    }
-}
-
-psxd_cue_t* psxd_cue_create(void) {
-    return (psxd_cue_t*)malloc(sizeof(psxd_cue_t));
-}
-
-void psxd_cue_init(psxd_cue_t* cue) {
-    memset(cue, 0, sizeof(psxd_cue_t));
-
-    cue->buf = malloc(CUE_BUF_SIZE);
-    cue->ptr = cue->buf;
-    cue->current_file = malloc(CUE_BUF_SIZE);
-}
-
-char* cue_get_directory(const char* path) {
-    const char* ptr = &path[strlen(path) - 1];
-    char* dir = NULL;
-
-    while ((*ptr != '/') && (*ptr != '\\') && (ptr != path))
-        ptr--;
-    
-    // If no directory specified, assume CWD
-    if (ptr == path) {
-        dir = malloc(3);
-
-        strcpy(dir, "./");
-
-        return dir;
-    }
-    
-    ptrdiff_t len = (ptr - path) + 1;
-
-    dir = malloc(len + 1);
-
-    strncpy(dir, path, len);
-
-    dir[len] = 0;
-
-    return dir;
-}
-
-int psxd_cue_load(psxd_cue_t* cue, const char* path) {
-    FILE* file = fopen(path, "rb");
-
-    if (!file) {
-        log_fatal("Couldn't open file \'%s\'", path);
-
-        return 1;
-    }
-
-    log_fatal("Parsing CUE...");
-
-    if (cue_parse(cue, file)) {
-        log_fatal("CUE error %s (%u)",
-            g_psxd_cue_errors[cue->error],
-            cue->error
-        );
-
-        exit(1);
-    }
-
-    log_fatal("Loading CD image...");
-
-    size_t offset = 0;
-
-    char* directory = cue_get_directory(path);
-    size_t directory_len = strlen(directory);
-
-    for (int i = 0; i < cue->num_tracks; i++) {
-        cue_track_t* track = cue->track[i];
-
-        int len = strlen(track->filename) + directory_len;
-
-        char* full_path = malloc(len + 2);
-
-        strcpy(full_path, directory);
-        strcat(full_path, track->filename);
-
-        log_fatal("Loading track %u at \'%s\'...", i + 1, full_path);
-
-        FILE* track_file = fopen(full_path, "rb");
-
-        if (ferror(track_file) || !track_file) {
-            fclose(track_file);
-
-            return 1;
-        }
-
-        uint32_t data_offset = msf_to_address(track->index[1]);
-
-        // Get track size
-        fseek(track_file, 0, SEEK_END);
-
-        // Account for index 1 offset
-        track->size = ftell(track_file);
-
-        cue->buf_size += track->size;
-
-        // Calculate track MS(F)
-        msf_from_address(&track->disc_offset, offset + data_offset);
-        msf_add_s(&track->disc_offset, 2);
-
-        cue->buf = cue_alloc_block(cue->buf, &offset, track->size);
-
-        fseek(track_file, 0, SEEK_SET);
-        
-        if (!fread(cue->buf + (offset - track->size), 1, track->size, track_file)) {
-            perror("Error reading CUE image file data");
-
-            exit(1);
-        }
-
-        fclose(track_file);
-        free(full_path);
-    }
-
-    // Calculate disc end MSF
-    msf_from_address(&cue->end, offset);
-    msf_add_s(&cue->end, 2);
-
-    free(directory);
-
-    log_fatal("Loaded CUE image, size=%08x, end=%02u:%02u:%02u",
-        cue->buf_size,
-        cue->end.m,
-        cue->end.s,
-        cue->end.f
-    );
-
-    return 0;
-}
-
-int psxd_cue_seek(void* udata, msf_t msf) {
-    psxd_cue_t* cue = udata;
-
-    // To-do: Check for OOB seeks
-
-    uint32_t sectors = (((msf.m * 60) + msf.s - 2) * CD_SECTORS_PS) + msf.f;
-
-    cue->seek_offset = sectors * CD_SECTOR_SIZE;
-
-    // log_fatal("CUE seek to %02u:%02u:%02u (%08x < %08x)", msf.m, msf.s, msf.f, cue->seek_offset, cue->buf_size);
-
-    if (cue->seek_offset >= cue->buf_size)
-        return DISC_ERR_ADDR_OUT_OF_BOUNDS;
-
-    return 0;
-}
-
-int psxd_cue_read_sector(void* udata, void* buf) {
-    psxd_cue_t* cue = udata;
-
-    log_fatal("Reading sector at offset %08x", cue->seek_offset);
-
-    memcpy(buf, cue->buf + cue->seek_offset, CD_SECTOR_SIZE);
-
-    return 0;
-}
-
-int psxd_cue_get_track_addr(void* udata, msf_t* msf, int track) {
-    psxd_cue_t* cue = udata;
-
-    track = BTOI(track);
-
-    if (track > cue->num_tracks)
-        return DISC_ERR_TRACK_OUT_OF_BOUNDS;
-
-    if (!msf)
-        return 0;
-
-    if (!track) {
-        msf->m = cue->end.m;
-        msf->s = cue->end.s;
-        msf->f = 0;
-
-        return 0;
-    }
-
-    msf->m = cue->track[track - 1]->disc_offset.m;
-    msf->s = cue->track[track - 1]->disc_offset.s;
-    msf->f = 0;
-
-    return 0;
-}
-
-int psxd_cue_get_track_count(void* udata, int* count) {
-    psxd_cue_t* cue = udata;
-
-    *count = ITOB(cue->num_tracks);
-
-    return 0;
-}
-
-void psxd_cue_init_disc(psxd_cue_t* cue, psx_disc_t* disc) {
-    disc->udata = cue;
-    disc->seek_func = psxd_cue_seek;
-    disc->read_sector_func = psxd_cue_read_sector;
-    disc->get_track_addr_func = psxd_cue_get_track_addr;
-    disc->get_track_count_func = psxd_cue_get_track_count;
-    disc->destroy_func = (disc_destroy_t)psxd_cue_destroy;
-}
-
-void psxd_cue_destroy(psxd_cue_t* cue) {
-    for (int i = 0; i < cue->num_tracks; i++) {
-        free(cue->track[i]->filename);
-        free(cue->track[i]);
-    }
-
-    free(cue->track);
-    free(cue->current_file);
-    free(cue->buf);
-    free(cue);
-}
\ No newline at end of file
--- a/psx/disc/cue.h
+++ /dev/null
@@ -1,91 +1,0 @@
-/*
-    This file is part of the PSXE Emulator Project
-
-    CUE Parser + Loader
-*/
-
-#ifndef CUE_H
-#define CUE_H
-
-#include "../disc.h"
-#include "../msf.h"
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdio.h>
-
-enum {
-    PE_EXPECTED_KEYWORD = 1,
-    PE_EXPECTED_STRING,
-    PE_EXPECTED_NUMBER,
-    PE_EXPECTED_COLON,
-    PE_NON_SEQUENTIAL_TRACKS,
-    PE_UNEXPECTED_TOKEN
-};
-
-enum {
-    CUE_4CH = 0,
-    CUE_AIFF,
-    CUE_AUDIO,
-    CUE_BINARY,
-    CUE_CATALOG,
-    CUE_CDG,
-    CUE_CDI_2336,
-    CUE_CDI_2352,
-    CUE_CDTEXTFILE,
-    CUE_DCP,
-    CUE_FILE,
-    CUE_FLAGS,
-    CUE_INDEX,
-    CUE_ISRC,
-    CUE_MODE1_2048,
-    CUE_MODE1_2352,
-    CUE_MODE2_2336,
-    CUE_MODE2_2352,
-    CUE_MOTOROLA,
-    CUE_MP3,
-    CUE_PERFORMER,
-    CUE_POSTGAP,
-    CUE_PRE,
-    CUE_PREGAP,
-    CUE_REM,
-    CUE_SCMS,
-    CUE_SONGWRITER,
-    CUE_TITLE,
-    CUE_TRACK,
-    CUE_WAVE,
-    CUE_NONE = 255
-};
-
-typedef struct {
-    char* filename;
-    int type;
-    void* buf;
-    msf_t index[2];
-    msf_t disc_offset;
-    size_t size;
-} cue_track_t;
-
-typedef struct {
-    int preload;
-    char* buf;
-    char* ptr;
-    char c;
-    int error;
-    FILE* file;
-    int num_tracks;
-    cue_track_t** track;
-    char* current_file;
-    char* directory;
-    uint32_t seek_offset;
-    uint32_t buf_size;
-    msf_t end;
-} psxd_cue_t;
-
-psxd_cue_t* psxd_cue_create(void);
-void psxd_cue_init(psxd_cue_t*);
-int psxd_cue_load(psxd_cue_t*, const char*);
-void psxd_cue_init_disc(psxd_cue_t*, psx_disc_t*);
-void psxd_cue_destroy(psxd_cue_t*);
-
-#endif
\ No newline at end of file
--- a/psx/msf.c
+++ /dev/null
@@ -1,86 +1,0 @@
-#include "msf.h"
-#include "log.h"
-
-uint8_t msf_btoi(uint8_t b) {
-    return ((b >> 4) * 10) + (b & 0xf);
-}
-
-uint8_t msf_itob(int i) {
-    return i + (6 * (i / 10));
-}
-
-void msf_copy(msf_t* dst, msf_t src) {
-    dst->m = src.m;
-    dst->s = src.s;
-    dst->f = src.f;
-}
-
-void msf_adjust(msf_t* msf) {
-    if (msf->f > 75) {
-        int s = msf->f / CD_SECTORS_PS;
-
-        msf->s += s;
-        msf->f -= CD_SECTORS_PS * s;
-    }
-
-    if (msf->s >= 60) {
-        int m = msf->s / 60;
-
-        msf->m += m;
-        msf->s -= 60 * m;
-    }
-}
-
-void msf_adjust_sub(msf_t* msf) {
-    if ((int)msf->f < 0) {
-        int f = ((int)msf->f) * -1;
-        int s = (f / 60) + 1;
-
-        msf->s -= s;
-        msf->f += CD_SECTORS_PS * f;
-    }
-
-    if ((int)msf->s < 0) {
-        int s = ((int)msf->s) * -1;
-        int m = (s / 60) + 1;
-
-        msf->m -= m;
-        msf->s += 60 * m;
-    }
-}
-
-void msf_to_bcd(msf_t* msf) {
-    msf->m = ITOB(msf->m);
-    msf->s = ITOB(msf->s);
-    msf->f = ITOB(msf->f);
-}
-
-void msf_from_bcd(msf_t* msf) {
-    msf->m = BTOI(msf->m);
-    msf->s = BTOI(msf->s);
-    msf->f = BTOI(msf->f);
-}
-
-uint32_t msf_to_address(msf_t msf) {
-    return (((msf.m * 60) * CD_SECTORS_PS) + (msf.s * 75) + msf.f) * CD_SECTOR_SIZE;
-}
-
-void msf_from_address(msf_t* msf, uint32_t addr) {
-    msf->f = addr / CD_SECTOR_SIZE;
-    msf->s = msf->f / CD_SECTORS_PS;
-    msf->m = msf->s / 60;
-    msf->s -= msf->m * 60;
-    msf->f -= ((msf->m * 60) + msf->s) * CD_SECTORS_PS;
-}
-
-void msf_add_f(msf_t* msf, int f) {
-    msf->f += f;
-
-    msf_adjust(msf);
-}
-
-void msf_add_s(msf_t* msf, int s) {
-    msf->s += s;
-
-    msf_adjust(msf);
-}
\ No newline at end of file
--- a/psx/msf.h
+++ /dev/null
@@ -1,100 +1,0 @@
-#ifndef MSF_H
-#define MSF_H
-
-#include <stdint.h>
-
-#define CD_SECTOR_SIZE 0x930
-#define CD_SECTORS_PS 75
-
-// #define BTOI(b) msf_btoi(b)
-// #define ITOB(b) msf_itob(b)
-#define BTOI(b) g_btoi_table[b]
-#define ITOB(b) g_itob_table[b]
-
-static const uint8_t g_btoi_table[] = {
-    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
-    0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
-    0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
-    0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
-    0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
-    0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
-    0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
-    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
-    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
-    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-    0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41,
-    0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43,
-    0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
-    0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
-    0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
-    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
-    0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
-    0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61,
-    0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
-    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
-    0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73,
-    0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
-    0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d,
-    0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
-    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-    0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
-    0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91,
-    0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93,
-    0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
-    0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d,
-    0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5,
-};
-
-static const uint8_t g_itob_table[] = {
-    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
-    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
-    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
-    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
-    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
-    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
-    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
-    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
-    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
-    0x96, 0x97, 0x98, 0x99, 0xa0, 0xa1, 0xa2, 0xa3,
-    0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xb0, 0xb1,
-    0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
-    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
-    0xc8, 0xc9, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5,
-    0xd6, 0xd7, 0xd8, 0xd9, 0xe0, 0xe1, 0xe2, 0xe3,
-    0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xf0, 0xf1,
-    0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
-    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
-    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
-    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
-    0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
-    0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
-    0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63,
-    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71,
-    0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
-    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
-    0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
-};
-
-typedef struct {
-    uint32_t m;
-    uint32_t s;
-    uint32_t f;
-} msf_t;
-
-void msf_copy(msf_t*, msf_t);
-void msf_adjust(msf_t*);
-void msf_adjust_sub(msf_t*);
-void msf_to_bcd(msf_t*);
-void msf_from_bcd(msf_t*);
-uint32_t msf_to_address(msf_t);
-void msf_from_address(msf_t*, uint32_t);
-void msf_add_f(msf_t*, int);
-void msf_add_s(msf_t*, int);
-
-#endif
\ No newline at end of file
--- a/psx/psx.c
+++ b/psx/psx.c
@@ -30,7 +30,7 @@
     psx->cpu->last_cycles = 2;
 
     psx_cdrom_update(psx->cdrom, 2);
-    psx_gpu_update(psx->gpu, psx->cpu->last_cycles);
+    psx_gpu_update(psx->gpu, 2);
     psx_pad_update(psx->pad, psx->cpu->last_cycles);
     psx_timer_update(psx->timer, psx->cpu->last_cycles);
     psx_dma_update(psx->dma, psx->cpu->last_cycles);
@@ -61,7 +61,12 @@
 
     // return (draw > dmode) ? dmode : draw;
 
-    return psx_get_dmode_width(psx);
+    int width = psx_get_dmode_width(psx);
+
+    if (width == 368)
+        width = 384;
+
+    return width;
 }
 
 uint32_t psx_get_display_height(psx_t* psx) {
--