shithub: psxe

Download patch

ref: c7663873ef41dabd7beb740615a64fd42effa572
parent: 99be385e3fd8ba853017925aa3c45566be233044
author: allkern <lisandroaalarcon@gmail.com>
date: Thu Dec 14 19:35:14 EST 2023

Project reboot

Fixed long-standing bugs
Now booting a lot of games
Implemented fullscreen mode
Implemented vertical mode
Improved display area

--- a/frontend/main.c
+++ b/frontend/main.c
@@ -16,8 +16,8 @@
     for (int i = 0; i < (size >> 2); i++) {
         uint32_t sample = psx_spu_get_sample(spu);
 
-        int16_t left = (int16_t)(sample & 0xffff) * 2;
-        int16_t right = (int16_t)(sample >> 16) * 2;
+        int16_t left = (int16_t)(sample & 0xffff) * 1.5f;
+        int16_t right = (int16_t)(sample >> 16) * 1.5f;
 
         *(int16_t*)(&buf[(i << 2) + 0]) += left;
         *(int16_t*)(&buf[(i << 2) + 2]) += right;
--- a/frontend/screen.c
+++ b/frontend/screen.c
@@ -21,6 +21,12 @@
     return 0;
 }
 
+int screen_get_base_width(psxe_screen_t* screen) {
+    int width = psx_get_dmode_width(screen->psx);
+
+    return (width == 256) ? 256 : 320;
+}
+
 psxe_screen_t* psxe_screen_create() {
     return (psxe_screen_t*)malloc(sizeof(psxe_screen_t));
 }
@@ -42,9 +48,12 @@
     screen->psx = psx;
     screen->pad = psx_get_pad(psx);
 
-    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
+    screen->texture_width = PSX_GPU_FB_WIDTH;
+    screen->texture_height = PSX_GPU_FB_HEIGHT;
 
+    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
     SDL_SetHint("SDL_HINT_RENDER_SCALE_QUALITY", "0");
+    SDL_SetRenderDrawColor(screen->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
 }
 
 void psxe_screen_reload(psxe_screen_t* screen) {
@@ -58,8 +67,13 @@
         screen->width = PSX_GPU_FB_WIDTH;
         screen->height = PSX_GPU_FB_HEIGHT;
     } else {
-        screen->width = 320;
-        screen->height = 240;
+        if (screen->vertical_mode) {
+            screen->width = 240;
+            screen->height = screen_get_base_width(screen);
+        } else {
+            screen->width = screen_get_base_width(screen);
+            screen->height = 240;
+        }
     }
 
     screen->window = SDL_CreateWindow(
@@ -80,7 +94,7 @@
         screen->renderer,
         screen->format,
         SDL_TEXTUREACCESS_STREAMING,
-        PSX_GPU_FB_WIDTH, PSX_GPU_FB_HEIGHT
+        screen->texture_width, screen->texture_height
     );
 
     // Check for retina displays
@@ -95,6 +109,8 @@
         SDL_RenderSetScale(screen->renderer, width_scale, height_scale);
     }
 
+    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0);
+
     screen->open = 1;
 }
 
@@ -107,6 +123,9 @@
 
     psxe_screen_set_scale(screen, screen->saved_scale);
 
+    screen->texture_width = PSX_GPU_FB_WIDTH;
+    screen->texture_height = PSX_GPU_FB_HEIGHT;
+
     psxe_gpu_dmode_event_cb(screen->psx->gpu);
 }
 
@@ -114,8 +133,38 @@
     void* display_buf = screen->debug_mode ?
         psx_get_vram(screen->psx) : psx_get_display_buffer(screen->psx);
 
-    SDL_UpdateTexture(screen->texture, NULL, display_buf, PSX_GPU_FB_STRIDE);
-    SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
+    // Update texture
+    int pitch;
+    uint8_t* ptr = NULL;
+
+    SDL_LockTexture(screen->texture, NULL, &ptr, &pitch);
+
+    for (int y = 0; y < screen->texture_height; y++)
+        memcpy(ptr + (y * pitch), display_buf + (y * PSX_GPU_FB_STRIDE), pitch);
+
+    SDL_UnlockTexture(screen->texture);
+
+    SDL_RenderClear(screen->renderer);
+
+    if (!screen->debug_mode) {
+        SDL_Rect dstrect;
+
+        dstrect.x = screen->image_xoff;
+        dstrect.y = screen->image_yoff;
+        dstrect.w = screen->image_width;
+        dstrect.h = screen->image_height;
+
+        SDL_RenderCopyEx(
+            screen->renderer,
+            screen->texture,
+            NULL, &dstrect,
+            screen->vertical_mode ? 270 : 0,
+            NULL, SDL_FLIP_NONE
+        );
+    } else {
+        SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
+    }
+
     SDL_RenderPresent(screen->renderer);
 
     SDL_Event event;
@@ -137,8 +186,8 @@
                     case SDLK_F2: {
                         SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(
                             0,
-                            screen->width * screen->scale,
-                            screen->height * screen->scale,
+                            screen->width,
+                            screen->height,
                             16,
                             SDL_PIXELFORMAT_BGR555
                         );
@@ -154,6 +203,26 @@
 
                         SDL_FreeSurface(surface);
                     } break;
+
+                    case SDLK_F3: {
+                        screen->vertical_mode = !screen->vertical_mode;
+
+                        psxe_gpu_dmode_event_cb(screen->psx->gpu);
+                    } break;
+
+                    case SDLK_F4: {
+                        screen->fullscreen = !screen->fullscreen;
+
+                        SDL_SetWindowFullscreen(
+                            screen->window,
+                            screen->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0
+                        );
+
+                        psxe_gpu_dmode_event_cb(screen->psx->gpu);
+
+                        SDL_SetRenderDrawColor(screen->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
+                        SDL_RenderClear(screen->renderer);
+                    } break;
                 }
 
                 uint16_t mask = screen_get_button(event.key.keysym.sym);
@@ -196,7 +265,7 @@
     int texture_height;
 
     screen->format = psx_get_display_format(screen->psx) ?
-        SDL_PIXELFORMAT_BGR555 : SDL_PIXELFORMAT_RGB888;
+        SDL_PIXELFORMAT_RGB24 : SDL_PIXELFORMAT_BGR555;
 
     if (screen->debug_mode) {
         screen->width = PSX_GPU_FB_WIDTH;
@@ -204,32 +273,59 @@
         texture_width = PSX_GPU_FB_WIDTH;
         texture_height = PSX_GPU_FB_HEIGHT;
     } else {
-        screen->width = 320;
-        screen->height = 240;
-        texture_width = psx_get_display_width(screen->psx);
-        texture_height = psx_get_display_height(screen->psx);
-    }
+        if (screen->fullscreen) {
+            screen->width = 1920;
+            screen->height = 1080;
 
-    // if (screen->width > 512) {
-    //     screen->scale = 1;
-    // } else {
-    //     screen->scale = screen->saved_scale;
-    // }
+            if (screen->vertical_mode) {
+                screen->image_width = screen->height;
+                screen->image_height = (double)screen->height / psx_get_display_aspect(screen->psx);
 
+                int off = (screen->image_width - screen->image_height) / 2;
+
+                screen->image_xoff = (screen->width / 2) - (screen->image_width / 2);
+                screen->image_yoff = off;
+            } else {
+                screen->image_width = (double)screen->height * psx_get_display_aspect(screen->psx);
+                screen->image_height = screen->height;
+                screen->image_xoff = (screen->width / 2) - (screen->image_width / 2);
+                screen->image_yoff = 0;
+            }
+        } else {
+            if (screen->vertical_mode) {
+                screen->width = 240 * screen->scale;
+                screen->height = screen_get_base_width(screen) * screen->scale;
+                screen->image_width = screen->height;
+                screen->image_height = screen->width;
+
+                int off = (screen->image_width - screen->image_height) / 2;
+
+                screen->image_xoff = -off;
+                screen->image_yoff = off;
+            } else {
+                screen->width = screen_get_base_width(screen) * screen->scale;
+                screen->height = 240 * screen->scale;
+                screen->image_width = screen->width;
+                screen->image_height = screen->height;
+                screen->image_xoff = 0;
+                screen->image_yoff = 0;
+            }
+        }
+
+        screen->texture_width = psx_get_display_width(screen->psx);
+        screen->texture_height = psx_get_display_height(screen->psx);
+    }
+
     SDL_DestroyTexture(screen->texture);
 
     screen->texture = SDL_CreateTexture(
         screen->renderer,
-        SDL_PIXELFORMAT_BGR555,
+        screen->format,
         SDL_TEXTUREACCESS_STREAMING,
-        texture_width, texture_height
+        screen->texture_width, screen->texture_height
     );
 
-    SDL_SetWindowSize(
-        screen->window,
-        screen->width * screen->scale,
-        screen->height * screen->scale
-    );
+    SDL_SetWindowSize(screen->window, screen->width, screen->height);
 }
 
 void psxe_gpu_vblank_event_cb(psx_gpu_t* gpu) {
--- a/frontend/screen.h
+++ b/frontend/screen.h
@@ -18,8 +18,13 @@
 
     unsigned int saved_scale;
     unsigned int width, height, scale;
+    unsigned int image_width, image_height;
+    unsigned int image_xoff, image_yoff;
     unsigned int format;
+    unsigned int texture_width, texture_height;
 
+    int fullscreen;
+    int vertical_mode;
     int debug_mode;
     int open;
 } psxe_screen_t;
--- a/psx/cpu.c
+++ b/psx/cpu.c
@@ -700,6 +700,8 @@
 }
 
 void psx_cpu_cycle(psx_cpu_t* cpu) {
+    cpu->last_cycles = 0;
+
     // if ((cpu->pc & 0x3fffffff) == 0x000000a4) {
     //     if (cpu->r[9] == 0x2f)
     //         goto no_putchar;
@@ -762,6 +764,7 @@
     }
 
     cpu->opcode = psx_bus_read32(cpu->bus, cpu->pc);
+    cpu->last_cycles = psx_bus_get_access_cycles(cpu->bus);
 
     cpu->pc = cpu->next_pc;
     cpu->next_pc += 4;
@@ -774,7 +777,10 @@
 
     g_psx_cpu_primary_table[OP](cpu);
 
-    cpu->last_cycles = 2 + psx_bus_get_access_cycles(cpu->bus);
+    // Discard instruction access cycles
+    psx_bus_get_access_cycles(cpu->bus);
+
+    cpu->last_cycles = 1;
     cpu->total_cycles += cpu->last_cycles;
 
     cpu->r[0] = 0;
@@ -1113,8 +1119,6 @@
 
     uint32_t aligned = psx_bus_read32(cpu->bus, addr & ~0x3);
 
-    cpu->load_v = cpu->r[T];
-
     switch (addr & 0x3) {
         case 0: cpu->load_v = (cpu->load_v & 0x00ffffff) | (aligned << 24); break;
         case 1: cpu->load_v = (cpu->load_v & 0x0000ffff) | (aligned << 16); break;
@@ -1174,8 +1178,6 @@
     uint32_t addr = cpu->r[S] + IMM16S;
 
     uint32_t aligned = psx_bus_read32(cpu->bus, addr & ~0x3);
-
-    cpu->load_v = cpu->r[T];
 
     switch (addr & 0x3) {
         case 0: cpu->load_v =                               aligned       ; break;
--- a/psx/dev/bios.c
+++ b/psx/dev/bios.c
@@ -14,7 +14,7 @@
 
     bios->io_base = PSX_BIOS_BEGIN;
     bios->io_size = PSX_BIOS_SIZE;
-    bios->bus_delay = 20;
+    bios->bus_delay = 18;
 
     bios->buf = (uint8_t*)malloc(PSX_BIOS_SIZE);
 }
--- a/psx/dev/cdrom.c
+++ b/psx/dev/cdrom.c
@@ -24,6 +24,93 @@
 #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
@@ -63,7 +150,9 @@
     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);
 }
@@ -91,7 +180,11 @@
 
         case CD_STATE_SEND_RESP1: {
             SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(GETSTAT_MOTOR | (cdrom->disc ? 0 : GETSTAT_TRAYOPEN));
+            RESP_PUSH(
+                GETSTAT_MOTOR |
+                (cdrom->cdda_playing ? GETSTAT_PLAY : 0) |
+                (cdrom->disc ? 0 : GETSTAT_TRAYOPEN)
+            );
 
             if (cdrom->read_ongoing) {
                 cdrom->state = CD_STATE_SEND_RESP2;
@@ -105,6 +198,8 @@
     }
 }
 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) {
@@ -121,15 +216,15 @@
                 return;
             }
 
-            if (!cdrom->read_ongoing) {
+            //if (!cdrom->read_ongoing) {
                 cdrom->irq_delay = DELAY_1MS;
                 cdrom->delayed_command = CDL_SETLOC;
                 cdrom->state = CD_STATE_SEND_RESP1;
-            } else {
-                cdrom->irq_delay = DELAY_1MS;
-                cdrom->delayed_command = CDL_READN;
-                cdrom->state = CD_STATE_SEND_RESP2;
-            }
+            // } else {
+            //     cdrom->irq_delay = DELAY_1MS;
+            //     cdrom->delayed_command = CDL_READN;
+            //     cdrom->state = CD_STATE_SEND_RESP2;
+            // }
 
             int f = PFIFO_POP;
             int s = PFIFO_POP;
@@ -197,6 +292,16 @@
         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;
@@ -206,7 +311,6 @@
             cdrom->delayed_command = CDL_PLAY;
 
             if (track) {
-
                 psx_disc_get_track_addr(cdrom->disc, &cdrom->cdda_msf, track);
 
                 msf_to_bcd(&cdrom->cdda_msf);
@@ -281,6 +385,21 @@
 
             msf_from_bcd(&msf);
 
+            cdrom->xa_msf = msf;
+
+            if (cdrom->mode & MODE_XA_ADPCM) {
+                cdrom->xa_playing = 1;
+
+                printf("Play XA-ADPCM encoded song at %02u:%02u:%02x, filter=%u, file=%02x, channel=%02x\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, msf);
 
             if (err) {
@@ -469,6 +588,7 @@
         case CD_STATE_RECV_CMD: {
             cdrom->read_ongoing = 0;
             cdrom->cdda_playing = 0;
+            cdrom->xa_playing = 0;
 
             cdrom->irq_delay = DELAY_1MS;
             cdrom->state = CD_STATE_SEND_RESP1;
@@ -580,7 +700,8 @@
                 return;
             }
 
-            cdrom->pfifo_index = 0;
+            cdrom->xa_file = PFIFO_POP;
+            cdrom->xa_channel = PFIFO_POP;
 
             cdrom->irq_delay = DELAY_1MS;
             cdrom->delayed_command = CDL_SETFILTER;
@@ -630,6 +751,30 @@
             cdrom->delayed_command = CDL_NONE;
     
             SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(GETSTAT_MOTOR | GETSTAT_PLAY);
+
+            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;
@@ -636,7 +781,30 @@
         } break;
     }
 }
-void cdrom_cmd_getlocl(psx_cdrom_t* cdrom) { log_fatal("getlocl: Unimplemented"); exit(1); }
+void cdrom_cmd_getlocl(psx_cdrom_t* cdrom) {
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->irq_delay = DELAY_1MS;
+            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(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x01);
+            RESP_PUSH(0xff);
+
+            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: {
@@ -661,6 +829,33 @@
         } 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;
+
+            PFIFO_POP;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(GETSTAT_READ);
+
+            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_READ);
+
+            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;
 
@@ -890,10 +1085,10 @@
             cdrom->delayed_command = CDL_NONE;
     
             SET_BITS(ifr, IFR_INT, IFR_INT3);
-            RESP_PUSH(0x01);
-            RESP_PUSH(0x95);
-            RESP_PUSH(0x13);
-            RESP_PUSH(0x03);
+            RESP_PUSH(0xc0);
+            RESP_PUSH(0x19);
+            RESP_PUSH(0x09);
+            RESP_PUSH(0x94);
 
             cdrom->state = CD_STATE_RECV_CMD;
         } break;
@@ -988,6 +1183,21 @@
 
             msf_from_bcd(&msf);
 
+            cdrom->xa_msf = msf;
+
+            if (cdrom->mode & MODE_XA_ADPCM) {
+                cdrom->xa_playing = 1;
+
+                printf("Play XA-ADPCM encoded song at %02u:%02u:%02x, filter=%u, file=%02x, channel=%02x\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, msf);
 
             if (err) {
@@ -1001,35 +1211,7 @@
 
                 return;
             }
-
-            psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
-
-            msf_t sector_msf;
-
-            sector_msf.m = cdrom->dfifo[0x0c];
-            sector_msf.s = cdrom->dfifo[0x0d];
-            sector_msf.f = cdrom->dfifo[0x0e];
-
-            int correct_msf = (cdrom->seek_mm == sector_msf.m) && 
-                              (cdrom->seek_ss == sector_msf.s) &&
-                              (cdrom->seek_ff == sector_msf.f);
             
-            // Most probably audio sector:
-            // Purposefully constructed audio data could
-            // circumvent this detection code, but it will work
-            // for most intents and purposes 
-            if (!correct_msf) {
-                log_fatal("CdlReadS: Audio read");
-
-                cdrom->irq_delay = DELAY_1MS * 600;
-                cdrom->delayed_command = CDL_ERROR;
-                cdrom->state = CD_STATE_ERROR;
-                cdrom->error = ERR_SEEK;
-                cdrom->error_flags = GETSTAT_SEEKERROR;
-
-                return;
-            }
-            
             int double_speed = cdrom->mode & MODE_SPEED;
 
             cdrom->irq_delay = double_speed ? READ_DOUBLE_DELAY : READ_SINGLE_DELAY;
@@ -1056,19 +1238,6 @@
             psx_disc_seek(cdrom->disc, msf);
             psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
 
-            if (cdrom->mode & MODE_XA_ADPCM) {
-                cdrom->state = CD_STATE_RECV_CMD;
-                cdrom->delayed_command = CDL_NONE;
-
-                return;
-            }
-
-            if (cdrom->dfifo[0x12] & 0x20) {
-                log_fatal("Unimplemented XA Form2 Sector");
-
-                // exit(1);
-            }
-
             cdrom->seek_ff++;
 
             if ((cdrom->seek_ff & 0xF) == 10) { cdrom->seek_ff += 0x10; cdrom->seek_ff &= 0xF0; }
@@ -1109,10 +1278,10 @@
     "CdlUnmute",
     "CdlSetfilter",
     "CdlSetmode",
-    "CdlUnimplemented",
+    "CdlGetparam",
     "CdlGetlocl",
     "CdlGetlocp",
-    "CdlUnimplemented",
+    "CdlSetsession",
     "CdlGettn",
     "CdlGettd",
     "CdlSeekl",
@@ -1144,10 +1313,10 @@
     cdrom_cmd_unmute,
     cdrom_cmd_setfilter,
     cdrom_cmd_setmode,
-    cdrom_cmd_unimplemented,
+    cdrom_cmd_getparam,
     cdrom_cmd_getlocl,
     cdrom_cmd_getlocp,
-    cdrom_cmd_unimplemented,
+    cdrom_cmd_setsession,
     cdrom_cmd_gettn,
     cdrom_cmd_gettd,
     cdrom_cmd_seekl,
@@ -1178,7 +1347,7 @@
 
     if (cdrom->rfifo_index == 0)
         SET_BITS(status, STAT_RSLRRDY_MASK, 0);
-
+    
     return data;
 }
 
@@ -1186,19 +1355,32 @@
     if (!cdrom->dfifo_full)
         return 0;
 
+    // int data_sector_size = cdrom->dfifo[0x12] & MODE_SECTOR_SIZE;
     int sector_size_bit = cdrom->mode & MODE_SECTOR_SIZE;
 
+    uint8_t m = cdrom->dfifo[0x0c];
+    uint8_t s = cdrom->dfifo[0x0d];
+    uint8_t f = cdrom->dfifo[0x0e];
+
     uint32_t sector_size = sector_size_bit ? 0x924 : 0x800;
     uint32_t offset = sector_size_bit ? 12 : 24;
 
+    // if ((m == 0) && (s == 2) && (f == 0x18)) {
+    //     if ((cdrom->dfifo_index & 0xf) == 0)
+    //         printf("\n");
+
+    //     printf("%02x ", cdrom->dfifo[offset + (cdrom->dfifo_index)]);
+    // }
+
     if (cdrom->dfifo_index < sector_size) {
         SET_BITS(status, STAT_DRQSTS_MASK, STAT_DRQSTS_MASK);
 
-        return cdrom->dfifo[offset + (cdrom->dfifo_index++)];
-    } else {
-        SET_BITS(status, STAT_DRQSTS_MASK, 0);
+        uint8_t data = cdrom->dfifo[offset + (cdrom->dfifo_index++)];
 
-        cdrom->dfifo_full = 0;
+        if (cdrom->dfifo_index >= sector_size)
+            SET_BITS(status, STAT_DRQSTS_MASK, 0);
+
+        return data;
     }
 
     return 0x00;
@@ -1217,19 +1399,19 @@
 }
 
 void cdrom_write_cmd(psx_cdrom_t* cdrom, uint8_t value) {
-    log_set_quiet(0);
-    log_fatal("%s(%02x) %u params=[%02x, %02x, %02x, %02x, %02x, %02x]",
-        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]
-    );
-    log_set_quiet(1);
+    // log_set_quiet(0);
+    // log_fatal("%s(%02x) %u params=[%02x, %02x, %02x, %02x, %02x, %02x]",
+    //     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]
+    // );
+    // log_set_quiet(1);
 
     cdrom->command = value;
     cdrom->state = CD_STATE_RECV_CMD;
@@ -1250,12 +1432,10 @@
         SET_BITS(status, STAT_DRQSTS_MASK, STAT_DRQSTS_MASK);
 
         cdrom->dfifo_full = 1;
-        cdrom->dfifo_index = 0;
     } else {
         SET_BITS(status, STAT_DRQSTS_MASK, 0);
 
         cdrom->dfifo_full = 0;
-        cdrom->dfifo_index = 0;
     }
 }
 
@@ -1344,6 +1524,13 @@
     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));
 
@@ -1354,17 +1541,44 @@
     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_decoded_buf = malloc(XA_DECODED_SAMPLES * sizeof(int16_t));
+    cdrom->xa_left_ring_buf = malloc(XA_RINGBUF_SIZE * sizeof(int16_t));
+    cdrom->xa_right_ring_buf = malloc(XA_RINGBUF_SIZE * sizeof(int16_t));
+    cdrom->xa_stereo_resample_buf = malloc(XA_STEREO_RESAMPLE_SIZE * sizeof(int16_t));
+    cdrom->xa_mono_resample_buf = malloc(XA_MONO_RESAMPLE_SIZE * sizeof(int16_t));
+    cdrom->xa_step = 6;
+
+    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_decoded_buf, 0, XA_DECODED_SAMPLES * sizeof(int16_t));
+    memset(cdrom->xa_left_ring_buf, 0, XA_RINGBUF_SIZE * sizeof(int16_t));
+    memset(cdrom->xa_right_ring_buf, 0, XA_RINGBUF_SIZE * sizeof(int16_t));
+    memset(cdrom->xa_stereo_resample_buf, 0, XA_STEREO_RESAMPLE_SIZE * sizeof(int16_t));
+    memset(cdrom->xa_mono_resample_buf, 0, XA_MONO_RESAMPLE_SIZE * sizeof(int16_t));
 }
 
 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;
 }
 
@@ -1371,21 +1585,34 @@
 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);
 }
@@ -1405,13 +1632,13 @@
                 //     g_psx_cdrom_command_names[cdrom->delayed_command],
                 //     cdrom->delayed_command
                 // );
-                // log_set_quiet(1);
+                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);
+            log_set_quiet(1);
         }
     }
 }
@@ -1529,7 +1756,242 @@
     }
 }
 
+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_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;
+    }
+}
+
+int16_t cdrom_xa_interpolate(psx_cdrom_t* cdrom, int table, int channel) {
+    int16_t* ringbuf = channel ? cdrom->xa_right_ring_buf : cdrom->xa_left_ring_buf;
+
+    int sum = 0;
+
+    for (int i = 0; i < 29; i++) {
+        int16_t* zigzag = g_zigzag_table[table];
+
+        sum += (ringbuf[(cdrom->xa_ringbuf_pos - i) & 0x1f] * zigzag[i]) / 0x8000;
+    }
+
+    return (sum > INT16_MAX) ? INT16_MAX : ((sum < INT16_MIN) ? INT16_MIN : sum);
+}
+
+int16_t* cdrom_resample_xa_sector(psx_cdrom_t* cdrom, int16_t* buf, int stereo, int f18khz, int channel) {
+    int16_t* ringbuf = channel ? cdrom->xa_right_ring_buf : cdrom->xa_left_ring_buf;
+    int16_t* resample_buf = stereo ? cdrom->xa_stereo_resample_buf : cdrom->xa_mono_resample_buf;
+
+    int sample_count = stereo ? XA_STEREO_SAMPLES : XA_MONO_SAMPLES;
+
+    for (int i = 0; i < sample_count; i++) {
+        ringbuf[cdrom->xa_ringbuf_pos++ & 0x1f] = buf[i];
+
+        cdrom->xa_step--;
+
+        if (!cdrom->xa_step) {
+            cdrom->xa_step = 6;
+
+            for (int table = 0; table < 7; table++) {
+                int16_t sample = cdrom_xa_interpolate(cdrom, table, channel);
+
+                *resample_buf++ = sample;
+
+                if (f18khz)
+                    *resample_buf++ = sample;
+            }
+        }
+    }
+
+    return resample_buf;
+}
+
+void cdrom_decode_xa_sector(psx_cdrom_t* cdrom, void* buf) {
+    int16_t* ptr = (int16_t*)buf;
+
+    cdrom->xa_coding = cdrom->xa_sector_buf[0x13];
+
+    int src = 24;
+
+    int16_t left[28];
+    int16_t right[28];
+    int16_t left_h[2] = { 0, 0 };
+    int16_t right_h[2] = { 0, 0 };
+
+    int stereo = (cdrom->xa_coding & 1) == 1;
+    int f18khz = ((cdrom->xa_coding >> 2) & 1) == 1;
+
+    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 < 12; i++) {
+        for (int blk = 0; blk < 4; blk++) {
+            if (stereo) {
+                cdrom_decode_xa_block(cdrom, src, blk, 0, left, left_h);
+                cdrom_decode_xa_block(cdrom, src, blk, 1, right, 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, left_h);
+
+                for (int i = 0; i < 28; i++)
+                    *mono_ptr++ = left[i];
+
+                cdrom_decode_xa_block(cdrom, src, blk, 1, left, left_h);
+
+                for (int i = 0; i < 28; i++)
+                    *mono_ptr++ = left[i];
+            }
+        }
+
+        src += 128;
+    }
+
+    if (stereo) {
+        for (int i = 0; i < XA_STEREO_SAMPLES; i++) {
+            *ptr++ = cdrom->xa_left_buf[i];
+            *ptr++ = cdrom->xa_right_buf[i];
+        }
+    } else {
+        for (int i = 0; i < XA_MONO_SAMPLES; i++) {
+            *ptr++ = cdrom->xa_mono_buf[i];
+            *ptr++ = cdrom->xa_mono_buf[i];
+        }
+    }
+    
+    // if (stereo) {
+    //     int16_t* resample_buf;
+
+    //     resample_buf = cdrom_resample_xa_sector(cdrom, cdrom->xa_left_buf, 1, f18khz, 0);
+
+    //     for (int i = 0, j = 0; i < XA_STEREO_RESAMPLE_SIZE; i++) {
+    //         int16_t sample = resample_buf[i];
+
+    //         cdrom->xa_decoded_buf[j] = sample;
+
+    //         j += 2;
+    //     }
+
+    //     resample_buf = cdrom_resample_xa_sector(cdrom, cdrom->xa_right_buf, 1, f18khz, 1);
+
+    //     for (int i = 0, j = 1; i < XA_STEREO_RESAMPLE_SIZE; i++) {
+    //         int16_t sample = resample_buf[i];
+
+    //         cdrom->xa_decoded_buf[j] = sample;
+
+    //         j += 2;
+    //     }
+        
+    //     cdrom->xa_remaining_samples = XA_STEREO_RESAMPLE_SIZE;
+    // } else {
+    //     int16_t* resample_buf;
+
+    //     resample_buf = cdrom_resample_xa_sector(cdrom, cdrom->xa_mono_buf, 0, f18khz, 0);
+
+    //     for (int i = 0, j = 0; i < XA_MONO_RESAMPLE_SIZE; i++) {
+    //         int16_t sample = resample_buf[i];
+
+    //         cdrom->xa_decoded_buf[j++] = sample;
+    //         cdrom->xa_decoded_buf[j++] = sample;
+    //     }
+
+    //     cdrom->xa_remaining_samples = XA_MONO_RESAMPLE_SIZE;
+    // }
+}
+
+void cdrom_fetch_xa_sector(psx_cdrom_t* cdrom) {
+    while (true) {
+        psx_disc_seek(cdrom->disc, cdrom->xa_msf);
+        psx_disc_read_sector(cdrom->disc, cdrom->xa_sector_buf);
+
+        msf_add_f(&cdrom->xa_msf, 1);
+
+        // Check RT and Audio bit
+        if ((cdrom->xa_sector_buf[0x12] & 0x44) != 0x44)
+            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 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) {
+        return;
+
+        if (!cdrom->xa_remaining_samples) {
+            cdrom_fetch_xa_sector(cdrom);
+
+            cdrom->xa_remaining_samples = 4;
+        }
+
+        cdrom_decode_xa_sector(cdrom, buf);
+
+        --cdrom->xa_remaining_samples;
+
+        // int16_t* ptr = (int16_t*)buf;
+
+        // memset(buf, 0, size);
+
+        // if (!cdrom->xa_remaining_samples) {
+        //     cdrom->xa_sample_idx = 0;
+
+        //     cdrom_fetch_xa_sector(cdrom);
+        //     cdrom_decode_xa_sector(cdrom, buf);
+        // }
+
+        // // for (int i = 0; i < (size >> 2); i++) {
+        // //     *ptr++ = cdrom->xa_decoded_buf[cdrom->xa_sample_idx++];
+        // //     *ptr++ = cdrom->xa_decoded_buf[cdrom->xa_sample_idx++];
+        // // }
+
+        // cdrom->xa_remaining_samples -= size >> 2;
+
+        return;
+    }
+
     if (!cdrom->cdda_playing) {
         memset(buf, 0, size);
     
@@ -1536,9 +1998,6 @@
         return;
     }
 
-    if (!cdrom->disc)
-        return;
-
     // Convert seek to I
     msf_t msf = cdrom->cdda_msf;
     
@@ -1545,9 +2004,8 @@
     msf_from_bcd(&msf);
 
     // Seek to that address and read sector
-    if (psx_disc_seek(cdrom->disc, msf)) {
+    if (psx_disc_seek(cdrom->disc, msf))
         cdrom->cdda_playing = 0;
-    }
 
     psx_disc_read_sector(cdrom->disc, cdrom->cdda_buf);
 
@@ -1583,7 +2041,7 @@
             current.m = 0;
 
             msf_adjust(&current);
-            msf_to_bcd(&current);
+            //msf_to_bcd(&current);
 
             RESP_PUSH(0);
             RESP_PUSH(0);
--- a/psx/dev/cdrom.h
+++ b/psx/dev/cdrom.h
@@ -205,6 +205,26 @@
     int cdda_sectors_played;
     int cdda_track;
 
+    // XA-ADPCM
+    uint8_t* xa_sector_buf;
+    msf_t xa_msf;
+    int xa_playing;
+    uint8_t xa_file;
+    uint8_t xa_channel;
+    uint8_t xa_coding;
+    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;
+    int16_t* xa_stereo_resample_buf;
+    int16_t* xa_mono_resample_buf;
+    uint32_t xa_sample_idx;
+    uint32_t xa_remaining_samples;
+    uint32_t xa_step;
+    uint32_t xa_ringbuf_pos;
+
     const char* path;
     psx_disc_t* disc;
 
@@ -283,8 +303,10 @@
 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*);
--- a/psx/dev/dma.c
+++ b/psx/dev/dma.c
@@ -5,6 +5,7 @@
 #include <stdlib.h>
 #include <assert.h>
 #include <string.h>
+#include <ctype.h>
 
 psx_dma_t* psx_dma_create() {
     return (psx_dma_t*)malloc(sizeof(psx_dma_t));
@@ -115,18 +116,15 @@
         switch (offset) {
             case 0x70: log_error("DMA control write %08x", value); dma->dpcr = value; break;
             case 0x74: {
-                // IRQ signal is read-only
-                value &= ~DICR_IRQSI;
-
-                // Reset flags
-                dma->dicr &= ~(value & DICR_FLAGS);
-
-                // Write other fields
+                uint32_t ack = value & DICR_FLAGS;
                 uint32_t flags = dma->dicr & DICR_FLAGS;
 
-                dma->dicr &= ~DICR_FLAGS;
-                dma->dicr |= value & (~DICR_FLAGS);
+                flags &= (~ack);
+                flags &= DICR_FLAGS;
+
+                dma->dicr &= 0x80000000;
                 dma->dicr |= flags;
+                dma->dicr |= value & 0xffffff;
             } break;
 
             default: {
@@ -199,7 +197,7 @@
 
     dma->mdec_out_irq_delay = size;
 
-    dma->mdec_out.chcr &= ~(CHCR_BUSY_MASK | CHCR_TRIG_MASK);
+    dma->mdec_out.chcr = 0;
     dma->mdec_out.bcr = 0;
 }
 
@@ -223,7 +221,8 @@
 
         addr = hdr & 0xffffff;
 
-        if (addr == 0xffffff) break;
+        if (addr == 0xffffff)
+            break;
 
         hdr = psx_bus_read32(dma->bus, addr);
         size = hdr >> 24;
@@ -293,7 +292,8 @@
 void psx_dma_do_cdrom(psx_dma_t* dma) {
     if (!CHCR_BUSY(cdrom))
         return;
-    
+
+    // log_set_quiet(0);
     // log_fatal("CDROM DMA transfer: madr=%08x, dir=%s, sync=%s (%u), step=%s, size=%x",
     //     dma->cdrom.madr,
     //     CHCR_TDIR(cdrom) ? "to device" : "to RAM",
@@ -308,6 +308,7 @@
     //     (dma->dicr >> 23) & 1,
     //     (dma->dicr >> 24) & 0x7f
     // );
+    // log_set_quiet(1);    
 
     uint32_t size = BCR_SIZE(cdrom);
 
@@ -319,6 +320,8 @@
 
     dma->cdrom_irq_delay = size * 24;
 
+    uint32_t base_addr = dma->cdrom.madr;
+
     if (!CHCR_TDIR(cdrom)) {
         for (int i = 0; i < size; i++) {
             uint32_t data = 0;
@@ -335,10 +338,27 @@
     } else {
         log_fatal("Invalid CDROM DMA transfer direction");
     }
+
+    // size *= 4;
+
+    // for (int i = 0; i < size; i += 16) {
+    //     for (int k = 0; k < 16; k++) {
+    //         printf("%02x ", psx_bus_read8(dma->bus, base_addr + i + k));
+    //     }
+
+    //     printf("| ");
+
+    //     for (int k = 0; k < 16; k++) {
+    //         char c = psx_bus_read8(dma->bus, base_addr + i + k);
+
+    //         printf("%c ", isgraph(c) ? c : '.');
+    //     }
+
+    //     printf("\n");
+    // }
     
     // Clear BCR and CHCR trigger and busy bits
     dma->cdrom.chcr = 0;
-    //dma->otc.chcr &= ~(CHCR_BUSY_MASK | CHCR_TRIG_MASK);
     dma->cdrom.bcr = 0;
 }
 
@@ -361,7 +381,7 @@
     //     (dma->dicr >> 23) & 1,
     //     (dma->dicr >> 24) & 0x7f
     // );
-    // log_set_quiet(1);
+    log_set_quiet(1);
 
     uint32_t size = BCR_SIZE(spu);
     uint32_t blocks = BCR_BCNT(spu);
@@ -424,7 +444,8 @@
 
     uint32_t size = BCR_SIZE(otc);
 
-    if (!size) size = 0x10000;
+    if (!size)
+        size = 0x10000;
 
     for (int i = size; i > 0; i--) {
         uint32_t addr = (i != 1) ? (dma->otc.madr - 4) : 0xffffff;
@@ -444,7 +465,7 @@
 
 void psx_dma_update(psx_dma_t* dma, int cyc) {
     if (dma->cdrom_irq_delay) {
-        dma->cdrom_irq_delay -= cyc;
+        dma->cdrom_irq_delay = 0;
 
         if ((dma->dicr & DICR_DMA3EN) && !dma->cdrom_irq_delay)
             dma->dicr |= DICR_DMA3FL;
@@ -459,7 +480,7 @@
     }
 
     if (dma->gpu_irq_delay) {
-        dma->gpu_irq_delay -= cyc;
+        dma->gpu_irq_delay = 0;
 
         if (!dma->gpu_irq_delay)
             if (dma->dicr & DICR_DMA2EN)
@@ -467,7 +488,7 @@
     }
 
     if (dma->otc_irq_delay) {
-        dma->otc_irq_delay -= cyc;
+        dma->otc_irq_delay = 0;
 
         if (!dma->otc_irq_delay)
             if (dma->dicr & DICR_DMA6EN)
@@ -475,7 +496,7 @@
     }
 
     if (dma->mdec_in_irq_delay) {
-        dma->mdec_in_irq_delay -= cyc;
+        dma->mdec_in_irq_delay = 0;
 
         if (!dma->mdec_in_irq_delay)
             if (dma->dicr & DICR_DMA0EN)
@@ -483,7 +504,7 @@
     }
 
     if (dma->mdec_out_irq_delay) {
-        dma->mdec_out_irq_delay -= cyc;
+        dma->mdec_out_irq_delay = 0;
 
         if (!dma->mdec_out_irq_delay)
             if (dma->dicr & DICR_DMA1EN)
--- a/psx/dev/gpu.c
+++ b/psx/dev/gpu.c
@@ -26,18 +26,16 @@
 
 // #define BGR555(c) gpu_to_bgr555(c)
 
-#define VRAM(x, y) gpu->vram[(x) + ((y) * 1024)]
-
 int min3(int a, int b, int c) {
-    int m = a < b ? a : b;
+    int m = a <= b ? a : b;
 
-    return m < c ? m : c;
+    return m <= c ? m : c;
 }
 
 int max3(int a, int b, int c) {
-    int m = a > b ? a : b;
+    int m = a >= b ? a : b;
 
-    return m > c ? m : c;
+    return m >= c ? m : c;
 }
 
 psx_gpu_t* psx_gpu_create() {
@@ -53,6 +51,9 @@
     gpu->vram = (uint16_t*)malloc(PSX_GPU_VRAM_SIZE);
     gpu->state = GPU_STATE_RECV_CMD;
 
+    // Default window size, this is not normally needed
+    gpu->display_mode = 1;
+
     gpu->ic = ic;
 }
 
@@ -104,7 +105,7 @@
 
             return data;
         } break;
-        case 0x04: return gpu->gpustat | 0x1e000000;
+        case 0x04: return gpu->gpustat | 0x1c000000;
     }
 
     log_warn("Unhandled 32-bit GPU read at offset %08x", offset);
@@ -125,11 +126,11 @@
 }
 
 int min(int x0, int x1) {
-    return (x0 < x1) ? x0 : x1;
+    return (x0 <= x1) ? x0 : x1;
 }
 
 int max(int x0, int x1) {
-    return (x0 > x1) ? x0 : x1;
+    return (x0 >= x1) ? x0 : x1;
 }
 
 #define EDGE(a, b, c) ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x))
@@ -143,23 +144,25 @@
     switch (depth) {
         // 4-bit
         case 0: {
-            uint16_t texel = VRAM(tpx + (tx >> 2), tpy + ty);
-            uint16_t index = (texel >> ((tx & 0x3) << 2)) & 0xf;
+            uint16_t texel = gpu->vram[(tpx + (tx >> 2)) + ((tpy + ty) * 1024)];
 
-            return VRAM(clutx + index, cluty);
+            int index = (texel >> ((tx & 0x3) << 2)) & 0xf;
+
+            return gpu->vram[(clutx + index) + (cluty * 1024)];
         } break;
 
         // 8-bit
         case 1: {
-            uint16_t texel = VRAM(tpx + (tx >> 1), tpy + ty);
-            uint16_t index = (texel >> ((tx & 0x1) << 3)) & 0xff;
+            uint16_t texel = gpu->vram[(tpx + (tx >> 1)) + ((tpy + ty) * 1024)];
 
-            return VRAM(clutx + index, cluty);
+            int index = (texel >> ((tx & 0x1) << 3)) & 0xff;
+
+            return gpu->vram[(clutx + index) + (cluty * 1024)];
         } break;
 
         // 15-bit
         default: {
-            return VRAM(tpx + tx, tpy + ty);
+            return gpu->vram[(tpx + tx) + ((tpy + ty) * 1024)];
         } break;
     }
 }
@@ -199,11 +202,17 @@
     c.x += gpu->off_x;
     c.y += gpu->off_y;
 
-    int xmin = min3(a.x, b.x, c.x);
-    int ymin = min3(a.y, b.y, c.y);
-    int xmax = max3(a.x, b.x, c.x); 
-    int ymax = max3(a.y, b.y, c.y);
+    int xmin = max(min3(a.x, b.x, c.x), gpu->draw_x1);
+    int ymin = max(min3(a.y, b.y, c.y), gpu->draw_y1);
+    int xmax = min(max3(a.x, b.x, c.x), gpu->draw_x2);
+    int ymax = min(max3(a.y, b.y, c.y), gpu->draw_y2);
 
+    // Hack
+    if (!(data.attrib & PA_TEXTURED)) {
+        ++xmax;
+        ++ymax;
+    }
+
     float area = EDGE(a, b, c);
 
     for (int y = ymin; y < ymax; y++) {
@@ -217,12 +226,6 @@
 
             if ((z0 < 0) || (z1 < 0) || (z2 < 0))
                 continue;
-            
-            int bc = (x >= gpu->draw_x1) && (x <= gpu->draw_x2) &&
-                     (y >= gpu->draw_y1) && (y <= gpu->draw_y2);
-            
-            if (!bc)
-                continue;
 
             uint16_t color = 0;
             uint32_t mod   = 0;
@@ -298,12 +301,13 @@
             int cb = ((color >> 10) & 0x1f) << 3;
 
             if (transp) {
-                uint16_t back = VRAM(x, y);
+                uint16_t back = gpu->vram[x + (y * 1024)];
 
                 int br = ((back >> 0 ) & 0x1f) << 3;
                 int bg = ((back >> 5 ) & 0x1f) << 3;
                 int bb = ((back >> 10) & 0x1f) << 3;
 
+                // Do we use transp or gpustat here?
                 switch (transp_mode) {
                     case 0: {
                         cr = (0.5f * br) + (0.5f * cr);
@@ -336,7 +340,7 @@
                 color = BGR555(rgb);
             }
 
-            VRAM(x, y) = color;
+            gpu->vram[x + (y * 1024)] = color;
         }
     }
 }
@@ -418,7 +422,7 @@
             int cb = ((color >> 10) & 0x1f) << 3;
 
             if (transp) {
-                uint16_t back = VRAM(x, y);
+                uint16_t back = gpu->vram[x + (y * 1024)];
 
                 int br = ((back >> 0 ) & 0x1f) << 3;
                 int bg = ((back >> 5 ) & 0x1f) << 3;
@@ -456,7 +460,7 @@
                 color = BGR555(rgb);
             }
 
-            VRAM(x, y) = color;
+            gpu->vram[x + (y * 1024)] = color;
 
             skip:
 
@@ -481,8 +485,8 @@
     int y = y0;
 
     for (int x = x0; x < x1; x++) {
-        if ((x >= gpu->draw_x1) && (x <= gpu->draw_x2) && (y >= gpu->draw_y1) && (y <= gpu->draw_y2))
-            VRAM(x, y) = color;
+        if ((x < 1024) && (y < 512) && (x >= 0) && (y >= 0))
+            gpu->vram[x + (y * 1024)] = color;
 
         if (d > 0) {
             y += yi;
@@ -505,9 +509,8 @@
     int x = x0;
 
     for (int y = y0; y < y1; y++) {
-        if ((x >= gpu->draw_x1) && (x <= gpu->draw_x2) && (y >= gpu->draw_y1) && (y <= gpu->draw_y2))
-            VRAM(x, y) = color;
-
+        if ((x < 1024) && (y < 512) && (x >= 0) && (y >= 0))
+            gpu->vram[x + (y * 1024)] = color;
         if (d > 0) {
             x = x + xi;
             d += (2 * (dx - dy));
@@ -534,6 +537,11 @@
 }
 
 void gpu_render_flat_line(psx_gpu_t* gpu, vertex_t v0, vertex_t v1, uint32_t color) {
+    v0.x += gpu->off_x;
+    v0.y += gpu->off_y;
+    v1.x += gpu->off_x;
+    v1.y += gpu->off_y;
+    
     plotLine(gpu, v0.x, v0.y, v1.x, v1.y, color);
 }
 
@@ -548,9 +556,11 @@
     int xmax = min(xmin + w, gpu->draw_x2);
     int ymax = min(ymin + h, gpu->draw_y2);
 
-    for (uint32_t y = ymin; y < ymax; y++)
-        for (uint32_t x = xmin; x < xmax; x++)
-            VRAM(x, y) = color;
+    for (uint32_t y = ymin; y < ymax; y++) {
+        for (uint32_t x = xmin; x < xmax; x++) {
+            gpu->vram[x + (y * 1024)] = color;
+        }
+    }
 }
 
 void gpu_render_textured_rectangle(psx_gpu_t* gpu, vertex_t v, uint32_t w, uint32_t h, uint16_t clutx, uint16_t cluty, uint32_t color) {
@@ -814,6 +824,16 @@
             int vertices = 3 + quad;
  
             gpu->cmd_args_remaining = (fields_per_vertex * vertices) - shaded;
+
+            // log_set_quiet(0);
+            // log_fatal("Poly: GP0(%02x) shaded=%u, quad=%u, textured=%u, argc=%u, fpv=%u, vertc=%u",
+            //     gpu->buf[0] >> 24,
+            //     shaded, quad, textured,
+            //     gpu->cmd_args_remaining,
+            //     fields_per_vertex,
+            //     vertices
+            // );
+            log_set_quiet(1);
         } break;
 
         case GPU_STATE_RECV_ARGS: {
@@ -859,6 +879,15 @@
                     gpu_render_triangle(gpu, poly.v[0], poly.v[1], poly.v[2], poly);
                     gpu_render_triangle(gpu, poly.v[1], poly.v[2], poly.v[3], poly);
                 } else {
+                    // log_set_quiet(0);
+                    // log_fatal("v0=(%3u, %3u, %06x) v1=(%3u, %3u, %06x) v2=(%3u, %3u, %06x) co=%u vo=%u tco=%u tpo=%u",
+                    //     poly.v[0].x, poly.v[0].y, poly.v[0].c,
+                    //     poly.v[1].x, poly.v[1].y, poly.v[1].c,
+                    //     poly.v[2].x, poly.v[2].y, poly.v[2].c,
+                    //     color_offset, vert_offset,
+                    //     texc_offset, texp_offset
+                    // );
+                    log_set_quiet(1);
                     gpu_render_triangle(gpu, poly.v[0], poly.v[1], poly.v[2], poly);
                 }
 
@@ -868,34 +897,10 @@
     }
 }
 
-void gpu_copy(psx_gpu_t* gpu) {
+void gpu_cmd_a0(psx_gpu_t* gpu) {
     switch (gpu->state) {
         case GPU_STATE_RECV_CMD: {
             gpu->state = GPU_STATE_RECV_ARGS;
-            gpu->cmd_args_remaining = 3;
-        } break;
-
-        case GPU_STATE_RECV_ARGS: {
-            if (!gpu->cmd_args_remaining) {
-                uint32_t srcx = gpu->buf[1] & 0xffff;
-                uint32_t srcy = gpu->buf[1] >> 16;
-                uint32_t dstx = gpu->buf[2] & 0xffff;
-                uint32_t dsty = gpu->buf[2] >> 16;
-                uint32_t xsiz = gpu->buf[3] & 0xffff;
-                uint32_t ysiz = gpu->buf[3] >> 16;
-
-                for (int y = 0; y < ysiz; y++)
-                    for (int x = 0; x < xsiz; x++)
-                        VRAM(dstx + x, dsty + y) = VRAM(srcx + x, srcy + y);
-            }
-        } break;
-    }
-}
-
-void gpu_recv(psx_gpu_t* gpu) {
-    switch (gpu->state) {
-        case GPU_STATE_RECV_CMD: {
-            gpu->state = GPU_STATE_RECV_ARGS;
             gpu->cmd_args_remaining = 2;
         } break;
 
@@ -923,7 +928,7 @@
 
             // To-do: This is segfaulting for some reason
             //        Fix GPU edge cases in general
-            VRAM(xpos, ypos) = gpu->recv_data & 0xffff;
+            gpu->vram[xpos + (ypos * 1024)] = gpu->recv_data & 0xffff;
 
             ++gpu->xcnt;
 
@@ -938,7 +943,7 @@
                 xpos = (gpu->xpos + gpu->xcnt) & 0x3ff;
             }
 
-            VRAM(xpos, ypos) = gpu->recv_data >> 16;
+            gpu->vram[xpos + (ypos * 1024)] = gpu->recv_data >> 16;
 
             ++gpu->xcnt;
             
@@ -961,34 +966,6 @@
     }
 }
 
-void gpu_send(psx_gpu_t* gpu) {
-    switch (gpu->state) {
-        case GPU_STATE_RECV_CMD: {
-            gpu->state = GPU_STATE_RECV_ARGS;
-            gpu->cmd_args_remaining = 2;
-        } break;
-
-        case GPU_STATE_RECV_ARGS: {
-            if (!gpu->cmd_args_remaining) {
-                gpu->c0_xcnt = 0;
-                gpu->c0_ycnt = 0;
-                uint32_t c0_xpos = gpu->buf[1] & 0xffff;
-                uint32_t c0_ypos = gpu->buf[1] >> 16;
-                gpu->c0_xsiz = gpu->buf[2] & 0xffff;
-                gpu->c0_ysiz = gpu->buf[2] >> 16;
-                c0_xpos = c0_xpos & 0x3ff;
-                c0_ypos = c0_ypos & 0x1ff;
-                gpu->c0_xsiz = ((gpu->c0_xsiz - 1) & 0x3ff) + 1;
-                gpu->c0_ysiz = ((gpu->c0_ysiz - 1) & 0x1ff) + 1;
-                gpu->c0_tsiz = ((gpu->c0_xsiz * gpu->c0_ysiz) + 1) & 0xfffffffe;
-                gpu->c0_addr = c0_xpos + (c0_ypos * 1024);
-
-                gpu->state = GPU_STATE_RECV_CMD;
-            }
-        } break;
-    }
-}
-
 // Monochrome Opaque Quadrilateral
 void gpu_cmd_28(psx_gpu_t* gpu) {
     switch (gpu->state) {
@@ -1438,6 +1415,34 @@
     }
 }
 
+void gpu_cmd_c0(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 2;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->c0_xcnt = 0;
+                gpu->c0_ycnt = 0;
+                uint32_t c0_xpos = gpu->buf[1] & 0xffff;
+                uint32_t c0_ypos = gpu->buf[1] >> 16;
+                gpu->c0_xsiz = gpu->buf[2] & 0xffff;
+                gpu->c0_ysiz = gpu->buf[2] >> 16;
+                c0_xpos = c0_xpos & 0x3ff;
+                c0_ypos = c0_ypos & 0x1ff;
+                gpu->c0_xsiz = ((gpu->c0_xsiz - 1) & 0x3ff) + 1;
+                gpu->c0_ysiz = ((gpu->c0_ysiz - 1) & 0x1ff) + 1;
+                gpu->c0_tsiz = ((gpu->c0_xsiz * gpu->c0_ysiz) + 1) & 0xfffffffe;
+                gpu->c0_addr = c0_xpos + (c0_ypos * 1024);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
 void gpu_cmd_02(psx_gpu_t* gpu) {
     switch (gpu->state) {
         case GPU_STATE_RECV_CMD: {
@@ -1464,8 +1469,8 @@
 
                 for (uint32_t y = gpu->v0.y; y < (gpu->v0.y + gpu->ysiz); y++) {
                     for (uint32_t x = gpu->v0.x; x < (gpu->v0.x + gpu->xsiz); x++) {
-                        if ((x >= gpu->draw_x1) && (x <= gpu->draw_x2) && (y >= gpu->draw_y1) && (y <= gpu->draw_y2))
-                            VRAM(x, y) = color;
+                        if ((x < 1024) && (y < 512) && (x >= 0) && (y >= 0))
+                            gpu->vram[x + (y * 1024)] = color;
                     }
                 }
 
@@ -1495,7 +1500,10 @@
 
                 for (int y = 0; y < ysiz; y++) {
                     for (int x = 0; x < xsiz; x++) {
-                        if ((x >= gpu->draw_x1) && (x <= gpu->draw_x2) && (y >= gpu->draw_y1) && (y <= gpu->draw_y2))
+                        int dstb = ((dstx + x) < 1024) && ((dsty + y) < 512);
+                        int srcb = ((srcx + x) < 1024) && ((srcy + y) < 512);
+                        
+                        if (dstb && srcb)
                             gpu->vram[(dstx + x) + (dsty + y) * 1024] = gpu->vram[(srcx + x) + (srcy + y) * 1024];
                     }
                 }
@@ -1509,53 +1517,56 @@
 void psx_gpu_update_cmd(psx_gpu_t* gpu) {
     int type = (gpu->buf[0] >> 29) & 7;
 
-    switch (type) {
-        case 1: gpu_poly(gpu); return;
-        case 3: gpu_rect(gpu); return;
-        case 4: gpu_copy(gpu); return;
-        case 5: gpu_recv(gpu); return;
-        case 6: gpu_send(gpu); return;
-        default: break;
+    if (type == 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;
         case 0x02: gpu_cmd_02(gpu); break;
-        // case 0x24: gpu_cmd_24(gpu); break;
-        // case 0x25: gpu_cmd_24(gpu); break;
-        // case 0x26: gpu_cmd_24(gpu); break;
-        // case 0x27: gpu_cmd_24(gpu); break;
-        // case 0x28: gpu_cmd_28(gpu); break;
-        // case 0x2a: gpu_cmd_28(gpu); break;
-        // case 0x2c: gpu_cmd_2d(gpu); break;
-        // case 0x2d: gpu_cmd_2d(gpu); break;
-        // case 0x2e: gpu_cmd_2d(gpu); break;
-        // case 0x2f: gpu_cmd_2d(gpu); break;
-        // case 0x30: gpu_cmd_30(gpu); break;
-        // case 0x32: gpu_cmd_30(gpu); break;
-        // case 0x38: gpu_cmd_38(gpu); break;
-        // case 0x3c: gpu_cmd_3c(gpu); break;
-        // case 0x3e: gpu_cmd_3c(gpu); break;
+        case 0x24: gpu_cmd_24(gpu); break;
+        case 0x25: gpu_cmd_24(gpu); break;
+        case 0x26: gpu_cmd_24(gpu); break;
+        case 0x27: gpu_cmd_24(gpu); break;
+        case 0x28: gpu_cmd_28(gpu); break;
+        case 0x2a: gpu_cmd_28(gpu); break;
+        case 0x2c: gpu_cmd_2d(gpu); break;
+        case 0x2d: gpu_cmd_2d(gpu); break;
+        case 0x2e: gpu_cmd_2d(gpu); break;
+        case 0x2f: gpu_cmd_2d(gpu); break;
+        case 0x30: gpu_cmd_30(gpu); break;
+        case 0x32: gpu_cmd_30(gpu); break;
+        case 0x38: gpu_cmd_38(gpu); break;
+        case 0x3c: gpu_cmd_3c(gpu); break;
+        case 0x3e: gpu_cmd_3c(gpu); break;
         case 0x40: gpu_cmd_40(gpu); break;
-        // case 0x60: gpu_cmd_60(gpu); break;
-        // case 0x62: gpu_cmd_60(gpu); break;
-        // case 0x64: gpu_cmd_64(gpu); break;
-        // case 0x65: gpu_cmd_64(gpu); break;
-        // case 0x66: gpu_cmd_64(gpu); break;
-        // case 0x67: gpu_cmd_64(gpu); break;
-        // case 0x68: gpu_cmd_68(gpu); break;
-        // case 0x74: gpu_cmd_74(gpu); break;
-        // case 0x75: gpu_cmd_74(gpu); break;
-        // case 0x76: gpu_cmd_74(gpu); break;
-        // case 0x77: gpu_cmd_74(gpu); break;
-        // case 0x7c: gpu_cmd_7c(gpu); break;
-        // case 0x7d: gpu_cmd_7c(gpu); break;
-        // case 0x7e: gpu_cmd_7c(gpu); break;
-        // case 0x7f: gpu_cmd_7c(gpu); break;
-        // case 0x80: gpu_cmd_80(gpu); break;
-        // case 0xa0: gpu_cmd_a0(gpu); break;
-        // case 0xc0: gpu_cmd_c0(gpu); break;
+        case 0x60: gpu_cmd_60(gpu); break;
+        case 0x62: gpu_cmd_60(gpu); break;
+        case 0x64: gpu_cmd_64(gpu); break;
+        case 0x65: gpu_cmd_64(gpu); break;
+        case 0x66: gpu_cmd_64(gpu); break;
+        case 0x67: gpu_cmd_64(gpu); break;
+        case 0x68: gpu_cmd_68(gpu); break;
+        case 0x74: gpu_cmd_74(gpu); break;
+        case 0x75: gpu_cmd_74(gpu); break;
+        case 0x76: gpu_cmd_74(gpu); break;
+        case 0x77: gpu_cmd_74(gpu); break;
+        case 0x7c: gpu_cmd_7c(gpu); break;
+        case 0x7d: gpu_cmd_7c(gpu); break;
+        case 0x7e: gpu_cmd_7c(gpu); break;
+        case 0x7f: gpu_cmd_7c(gpu); break;
+        case 0x80: gpu_cmd_80(gpu); break;
+        case 0xa0: gpu_cmd_a0(gpu); break;
+        case 0xc0: gpu_cmd_c0(gpu); break;
         case 0xe1: {
             gpu->gpustat &= 0xfffff800;
             gpu->gpustat |= gpu->buf[0] & 0x7ff;
@@ -1628,33 +1639,6 @@
             uint8_t cmd = value >> 24;
 
             switch (cmd) {
-                case 0x00: {
-                    gpu->gpustat = 0x14802000;
-
-                    /*
-                        GP1(01h)      ;clear fifo
-                        GP1(02h)      ;ack irq (0)
-                        GP1(03h)      ;display off (1)
-                        GP1(04h)      ;dma off (0)
-                        GP1(05h)      ;display address (0)
-                        GP1(06h)      ;display x1,x2 (x1=200h, x2=200h+256*10)
-                        GP1(07h)      ;display y1,y2 (y1=010h, y2=010h+240)
-                        GP1(08h)      ;display mode 320x200 NTSC (0)
-                        GP0(E1h..E6h) ;rendering attributes (0)
-                    */
-
-                    gpu->disp_x1 = 0x200;
-                    gpu->disp_x2 = 0xc00;
-                    gpu->disp_y1 = 0x010;
-                    gpu->disp_y2 = 0x100;
-                    gpu->display_mode = 0;
-
-                    gpu->disp_x = 0;
-                    gpu->disp_y = 0;
-
-                    if (gpu->event_cb_table[GPU_EVENT_DMODE])
-                        gpu->event_cb_table[GPU_EVENT_DMODE](gpu);
-                } break;
                 case 0x04: {
                 } break;
                 case 0x05: {
@@ -1730,7 +1714,7 @@
     } else if (gpu->line == GPU_SCANS_PER_FRAME_NTSC) {
         if (gpu->event_cb_table[GPU_EVENT_VBLANK_END])
             gpu->event_cb_table[GPU_EVENT_VBLANK_END](gpu);
-
+        
         // psx_ic_irq(gpu->ic, IC_SPU);
 
         gpu->line = 0;
@@ -1761,7 +1745,7 @@
 }
 
 void* psx_gpu_get_display_buffer(psx_gpu_t* gpu) {
-    return gpu->vram + (gpu->disp_x + (gpu->disp_y * 1024));
+    return gpu->vram; // + (gpu->disp_x + (gpu->disp_y * 1024));
 }
 
 void psx_gpu_destroy(psx_gpu_t* gpu) {
--- a/psx/dev/ic.c
+++ b/psx/dev/ic.c
@@ -28,7 +28,7 @@
         case 0x04: return ic->mask;
     }
 
-    log_warn("Unhandled 32-bit IC read at offset %08x", offset);
+    log_fatal("Unhandled 32-bit IC read at offset %08x", offset);
 
     return 0x0;
 }
@@ -39,14 +39,18 @@
         case 0x04: return ic->mask;
     }
 
-    log_warn("Unhandled 16-bit IC read at offset %08x", offset);
+    log_fatal("Unhandled 16-bit IC read at offset %08x", offset);
 
+    exit(0);
+
     return 0x0;
 }
 
 uint8_t psx_ic_read8(psx_ic_t* ic, uint32_t offset) {
-    log_warn("Unhandled 8-bit IC read at offset %08x", offset);
+    log_fatal("Unhandled 8-bit IC read at offset %08x", offset);
 
+    exit(0);
+
     return 0x0;
 }
 
@@ -56,7 +60,7 @@
         case 0x04: ic->mask = value; break;
 
         default: {
-            log_warn("Unhandled 32-bit IC write at offset %08x (%08x)", offset, value);
+            log_fatal("Unhandled 32-bit IC write at offset %08x (%08x)", offset, value);
         } break;
     }
 
@@ -68,11 +72,11 @@
 
 void psx_ic_write16(psx_ic_t* ic, uint32_t offset, uint16_t value) {
     switch (offset) {
-        case 0x00: ic->stat &= value; break;
-        case 0x04: ic->mask = value; break;
+        case 0x00: ic->stat &= (uint32_t)value; break;
+        case 0x04: ic->mask &= 0xffff0000; ic->mask |= (uint32_t)value; break;
 
         default: {
-            log_warn("Unhandled 16-bit IC write at offset %08x (%08x)", offset, value);
+            
         } break;
     }
 
@@ -83,7 +87,9 @@
 }
 
 void psx_ic_write8(psx_ic_t* ic, uint32_t offset, uint8_t value) {
-    log_warn("Unhandled 8-bit IC write at offset %08x (%02x)", offset, value);
+    log_fatal("Unhandled 8-bit IC write at offset %08x (%02x)", offset, value);
+
+    exit(1);
 }
 
 void psx_ic_irq(psx_ic_t* ic, int id) {
--- a/psx/dev/mcd.c
+++ b/psx/dev/mcd.c
@@ -29,6 +29,8 @@
 }
 
 uint8_t psx_mcd_read(psx_mcd_t* mcd) {
+    return 0xff;
+
     switch (mcd->state) {
         case MCD_STATE_TX_HIZ: mcd->tx_data = 0xff; break;
         case MCD_STATE_TX_FLG: mcd->tx_data = mcd->flag; mcd->flag = 0x00; break;
@@ -45,7 +47,7 @@
 
             // log_set_quiet(0);
             // log_fatal("mcd read %02x", mcd->tx_data);
-            // log_set_quiet(1);
+            log_set_quiet(1);
 
             return mcd->tx_data;
         } break;
@@ -73,7 +75,7 @@
 
             // log_set_quiet(0);
             // log_fatal("mcd read %02x", data);
-            // log_set_quiet(1);
+            log_set_quiet(1);
 
             return data;
         } break;
@@ -84,7 +86,7 @@
 
             // log_set_quiet(0);
             // log_fatal("mcd read %02x", 'G');
-            // log_set_quiet(1);
+            log_set_quiet(1);
 
             return 'G';
         } break;
@@ -106,7 +108,7 @@
 
             // log_set_quiet(0);
             // log_fatal("mcd read %02x", mcd->rx_data);
-            // log_set_quiet(1);
+            log_set_quiet(1);
 
             return mcd->rx_data;
         } break;
@@ -119,7 +121,7 @@
 
             // log_set_quiet(0);
             // log_fatal("mcd read %02x", 'G');
-            // log_set_quiet(1);
+            log_set_quiet(1);
 
             return 'G';
         } break;
@@ -130,12 +132,14 @@
 
     // log_set_quiet(0);
     // log_fatal("mcd read %02x", mcd->tx_data);
-    // log_set_quiet(1);
+    log_set_quiet(1);
 
     return mcd->tx_data;
 }
 
 void psx_mcd_write(psx_mcd_t* mcd, uint8_t data) {
+    return;
+
     // log_set_quiet(0);
     // log_fatal("mcd write %02x", data);
     // log_set_quiet(1);
--- a/psx/dev/mdec.c
+++ b/psx/dev/mdec.c
@@ -162,6 +162,8 @@
 void mdec_nop(psx_mdec_t* mdec) { /* Do nothing */ }
 
 void mdec_decode_macroblock(psx_mdec_t* mdec) {
+    return;
+
     if (mdec->output_depth < 2) {
         rl_decode_block(mdec->yblk, mdec->input, mdec->y_quant_table, mdec->scale_table);
 
@@ -277,17 +279,19 @@
             if (mdec->output_words_remaining) {
                 --mdec->output_words_remaining;
 
-                log_set_quiet(0);
-                log_fatal("output read %08x", 0);
-                log_set_quiet(1);
+                // log_set_quiet(0);
+                // log_fatal("output read %08x", 0);
+                // log_set_quiet(1);
 
-                return ((uint32_t*)mdec->output)[mdec->output_index++];
+                return 0xaaaaaaaa;
+
+                // return ((uint32_t*)mdec->output)[mdec->output_index++];
             } else {
                 mdec->output_empty = 1;
                 mdec->output_index = 0;
                 mdec->output_request = 0;
 
-                return 0xaabbccdd;
+                return 0xaaaaaaaa;
             }
         } break;
         case 4: {
@@ -313,14 +317,10 @@
 
 uint16_t psx_mdec_read16(psx_mdec_t* mdec, uint32_t offset) {
     log_fatal("Unhandled 16-bit MDEC read offset=%u", offset);
-
-    exit(1);
 }
 
 uint8_t psx_mdec_read8(psx_mdec_t* mdec, uint32_t offset) {
     log_fatal("Unhandled 8-bit MDEC read offset=%u", offset);
-
-    exit(1);
 }
 
 void psx_mdec_write32(psx_mdec_t* mdec, uint32_t offset, uint32_t value) {
@@ -338,7 +338,7 @@
                     mdec->busy = 0;
                     mdec->output_request = mdec->enable_dma1;
 
-                    g_mdec_cmd_table[mdec->cmd >> 29](mdec);
+                    // g_mdec_cmd_table[mdec->cmd >> 29](mdec);
 
                     free(mdec->input);
                 }
@@ -356,7 +356,7 @@
             mdec->input_full = 0;
             mdec->busy = 1;
 
-            log_set_quiet(0);
+            //log_set_quiet(0);
             switch (mdec->cmd >> 29) {
                 case MDEC_CMD_NOP: {
                     mdec->busy = 0;
@@ -393,7 +393,7 @@
                     );
                 } break;
             }
-            log_set_quiet(1);
+            //log_set_quiet(1);
 
             if (mdec->words_remaining) {
                 mdec->input_request = mdec->enable_dma0;
@@ -431,14 +431,10 @@
 
 void psx_mdec_write16(psx_mdec_t* mdec, uint32_t offset, uint16_t value) {
     log_fatal("Unhandled 16-bit MDEC write offset=%u, value=%04x", offset, value);
-
-    exit(1);
 }
 
 void psx_mdec_write8(psx_mdec_t* mdec, uint32_t offset, uint8_t value) {
     log_fatal("Unhandled 8-bit MDEC write offset=%u, value=%02x", offset, value);
-
-    exit(1);
 }
 
 void psx_mdec_destroy(psx_mdec_t* mdec) {
--- /dev/null
+++ b/psx/dev/old/cdrom.c
@@ -1,0 +1,1610 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.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)
+*/
+
+#define GETID_RESPONSE_SIZE 8
+#define GETID_RESPONSE_END (GETID_RESPONSE_SIZE - 1)
+
+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_fatal("Unimplemented CDROM command (%u)", cdrom->command);
+
+    exit(1);
+}
+void cdrom_cmd_getstat(psx_cdrom_t* cdrom) {
+    cdrom->delayed_command = CDL_NONE;
+
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            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;
+            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->disc ? 0 : GETSTAT_TRAYOPEN));
+
+            if (cdrom->read_ongoing) {
+                cdrom->state = CD_STATE_SEND_RESP2;
+                cdrom->delayed_command = CDL_READN;
+                cdrom->irq_delay = DELAY_1MS;
+            } else {
+                cdrom->delayed_command = CDL_NONE;
+                cdrom->state = CD_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+void cdrom_cmd_setloc(psx_cdrom_t* cdrom) {
+    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;
+            }
+
+            if (!cdrom->read_ongoing) {
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_SETLOC;
+                cdrom->state = CD_STATE_SEND_RESP1;
+            } else {
+                cdrom->irq_delay = DELAY_1MS;
+                cdrom->delayed_command = CDL_READN;
+                cdrom->state = 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->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_ff = f;
+            cdrom->seek_ss = s;
+            cdrom->seek_mm = m;
+
+            cdrom->seek_pending = 1;
+
+            log_fatal("setloc: %02x:%02x:%02x",
+                cdrom->seek_mm,
+                cdrom->seek_ss,
+                cdrom->seek_ff
+            );
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            cdrom->delayed_command = CDL_NONE;
+
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            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->read_ongoing = 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_ff = f;
+            cdrom->seek_ss = s;
+            cdrom->seek_mm = m;
+        } 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;
+
+            // 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;
+
+            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_mm = cdrom->cdda_msf.m;
+                cdrom->seek_ss = cdrom->cdda_msf.s;
+                cdrom->seek_ff = cdrom->cdda_msf.f;
+    
+                cdrom->seek_pending = 1;
+            }
+
+            if (cdrom->seek_pending) {
+                cdrom->seek_pending = 0;
+
+                cdrom->cdda_msf.m = cdrom->seek_mm;
+                cdrom->cdda_msf.s = cdrom->seek_ss;
+                cdrom->cdda_msf.f = cdrom->seek_ff;
+
+                // Convert seek to I
+                msf_t msf = cdrom->cdda_msf;
+                
+                msf_from_bcd(&msf);
+
+                // Seek to that address and read sector
+                psx_disc_seek(cdrom->disc, msf);
+                psx_disc_read_sector(cdrom->disc, cdrom->cdda_buf);
+
+                // Increment sector
+                msf_add_f(&msf, 1);
+                msf_to_bcd(&msf);
+
+                cdrom->cdda_msf = msf;
+                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->read_ongoing = 1;
+
+    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);
+
+            // msf_t msf;
+
+            // msf.m = cdrom->seek_mm;
+            // msf.s = cdrom->seek_ss;
+            // msf.f = cdrom->seek_ff;
+
+            // msf_from_bcd(&msf);
+
+            // int err = psx_disc_seek(cdrom->disc, 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);
+
+            // msf_t sector_msf;
+
+            // sector_msf.m = cdrom->dfifo[0x0c];
+            // sector_msf.s = cdrom->dfifo[0x0d];
+            // sector_msf.f = cdrom->dfifo[0x0e];
+
+            // int correct_msf = (cdrom->seek_mm == sector_msf.m) && 
+            //                   (cdrom->seek_ss == sector_msf.s) &&
+            //                   (cdrom->seek_ff == sector_msf.f);
+            
+            // Most probably audio sector:
+            // Purposefully constructed audio data could
+            // circumvent this detection code, but it will work
+            // for most intents and purposes 
+            // if (!correct_msf) {
+            //     log_set_quiet(0);
+            //     log_fatal("CdlReadN: Audio read");
+            //     log_set_quiet(1);
+
+            //     cdrom->irq_delay = DELAY_1MS * 600;
+            //     cdrom->delayed_command = CDL_ERROR;
+            //     cdrom->state = CD_STATE_ERROR;
+            //     cdrom->error = ERR_SEEK;
+            //     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_READN;
+
+            // if (cdrom->spin_delay) {
+            //     cdrom->irq_delay += cdrom->spin_delay;
+            //     cdrom->spin_delay = 0;
+            // }
+        } break;
+
+        case CD_STATE_SEND_RESP2: {
+            log_fatal("CdlReadN: CD_STATE_SEND_RESP2");
+
+            msf_t msf;
+
+            msf.m = cdrom->seek_mm;
+            msf.s = cdrom->seek_ss;
+            msf.f = cdrom->seek_ff;
+
+            msf_from_bcd(&msf);
+
+            psx_disc_seek(cdrom->disc, msf);
+            psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
+            cdrom->dfifo_index = 0;
+            cdrom->dfifo_full = 1;
+
+            // printf("Sector header: msf=%02x:%02x:%02x, mode=%02x, subheader=%02x,%02x,%02x,%02x\n",
+            //     cdrom->dfifo[0x0c],
+            //     cdrom->dfifo[0x0d],
+            //     cdrom->dfifo[0x0e],
+            //     cdrom->dfifo[0x0f],
+            //     cdrom->dfifo[0x10],
+            //     cdrom->dfifo[0x11],
+            //     cdrom->dfifo[0x12],
+            //     cdrom->dfifo[0x13]
+            // );
+
+            if (cdrom->dfifo[0x12] & 0x20) {
+                log_fatal("Unimplemented XA Form2 Sector");
+
+                // exit(1);
+            }
+
+            cdrom->seek_ff++;
+
+            if ((cdrom->seek_ff & 0xF) == 10) { cdrom->seek_ff += 0x10; cdrom->seek_ff &= 0xF0; }
+            if (cdrom->seek_ff == 0x75) { cdrom->seek_ss++; cdrom->seek_ff = 0; }
+            if ((cdrom->seek_ss & 0xF) == 10) { cdrom->seek_ss += 0x10; cdrom->seek_ss &= 0xF0; }
+            if (cdrom->seek_ss == 0x60) { cdrom->seek_mm++; cdrom->seek_ss = 0; }
+            if ((cdrom->seek_mm & 0xF) == 10) { cdrom->seek_mm += 0x10; cdrom->seek_mm &= 0xF0; }
+
+            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;
+
+            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->read_ongoing = 0;
+            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->read_ongoing = 0;
+            cdrom->cdda_playing = 0;
+
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->state = CD_STATE_SEND_RESP1;
+            cdrom->delayed_command = CDL_STOP;
+            cdrom->seek_ff = 0;
+            cdrom->seek_ss = 0;
+            cdrom->seek_mm = 0;
+            cdrom->cdda_msf.m = 0;
+            cdrom->cdda_msf.s = 0;
+            cdrom->cdda_msf.f = 0;
+        } 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->read_ongoing = 0;
+            cdrom->cdda_playing = 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->read_ongoing = 0;
+            cdrom->mode = 0;
+            cdrom->dfifo_index = 0;
+            cdrom->dfifo_full = 0;
+            cdrom->pfifo_index = 0;
+            cdrom->rfifo_index = 0;
+            cdrom->seek_mm = 0;
+            cdrom->seek_ss = 0;
+            cdrom->seek_ff = 0;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, 3);
+            RESP_PUSH(cdrom->stat);
+
+            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(cdrom->stat);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+            cdrom->delayed_command = CDL_NONE;
+        } 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);
+
+            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->pfifo_index = 0;
+
+            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);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_setmode(psx_cdrom_t* cdrom) {
+    cdrom->delayed_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 = 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);
+
+            cdrom->state = CD_STATE_RECV_CMD;
+        } break;
+    }
+}
+void cdrom_cmd_getlocl(psx_cdrom_t* cdrom) { log_fatal("getlocl: Unimplemented"); exit(1); }
+void cdrom_cmd_getlocp(psx_cdrom_t* cdrom) {
+    switch (cdrom->state) {
+        case CD_STATE_RECV_CMD: {
+            cdrom->irq_delay = DELAY_1MS;
+            cdrom->delayed_command = CDL_GETLOCP;
+            cdrom->state = CD_STATE_SEND_RESP1;
+        } break;
+
+        case CD_STATE_SEND_RESP1: {
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x00);
+            RESP_PUSH(0x01);
+            RESP_PUSH(0xff);
+
+            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);
+
+            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 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->gettd_track = PFIFO_POP;
+
+            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_PCOUNT;
+                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;
+
+            msf_t msf;
+
+            msf.m = cdrom->seek_mm;
+            msf.s = cdrom->seek_ss;
+            msf.f = cdrom->seek_ff;
+
+            msf_from_bcd(&msf);
+
+            psx_disc_seek(cdrom->disc, 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;
+
+            msf_t msf;
+
+            msf.m = cdrom->seek_mm;
+            msf.s = cdrom->seek_ss;
+            msf.f = cdrom->seek_ff;
+
+            msf_from_bcd(&msf);
+
+            psx_disc_seek(cdrom->disc, 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;
+    
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(0x01);
+            RESP_PUSH(0x95);
+            RESP_PUSH(0x13);
+            RESP_PUSH(0x03);
+
+            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->read_ongoing = 1;
+
+    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: {
+            log_fatal("CdlReadS: CD_STATE_SEND_RESP1");
+
+            SET_BITS(ifr, IFR_INT, IFR_INT3);
+            RESP_PUSH(GETSTAT_MOTOR);
+
+            msf_t msf;
+
+            msf.m = cdrom->seek_mm;
+            msf.s = cdrom->seek_ss;
+            msf.f = cdrom->seek_ff;
+
+            msf_from_bcd(&msf);
+
+            int err = psx_disc_seek(cdrom->disc, 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;
+            }
+
+            psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
+
+            msf_t sector_msf;
+
+            sector_msf.m = cdrom->dfifo[0x0c];
+            sector_msf.s = cdrom->dfifo[0x0d];
+            sector_msf.f = cdrom->dfifo[0x0e];
+
+            int correct_msf = (cdrom->seek_mm == sector_msf.m) && 
+                              (cdrom->seek_ss == sector_msf.s) &&
+                              (cdrom->seek_ff == sector_msf.f);
+            
+            // Most probably audio sector:
+            // Purposefully constructed audio data could
+            // circumvent this detection code, but it will work
+            // for most intents and purposes 
+            if (!correct_msf) {
+                log_fatal("CdlReadS: Audio read");
+
+                cdrom->irq_delay = DELAY_1MS * 600;
+                cdrom->delayed_command = CDL_ERROR;
+                cdrom->state = CD_STATE_ERROR;
+                cdrom->error = ERR_SEEK;
+                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");
+
+            msf_t msf;
+
+            msf.m = cdrom->seek_mm;
+            msf.s = cdrom->seek_ss;
+            msf.f = cdrom->seek_ff;
+
+            msf_from_bcd(&msf);
+
+            psx_disc_seek(cdrom->disc, msf);
+            psx_disc_read_sector(cdrom->disc, cdrom->dfifo);
+
+            if (cdrom->mode & MODE_XA_ADPCM) {
+                cdrom->state = CD_STATE_RECV_CMD;
+                cdrom->delayed_command = CDL_NONE;
+
+                return;
+            }
+
+            if (cdrom->dfifo[0x12] & 0x20) {
+                log_fatal("Unimplemented XA Form2 Sector");
+
+                // exit(1);
+            }
+
+            cdrom->seek_ff++;
+
+            if ((cdrom->seek_ff & 0xF) == 10) { cdrom->seek_ff += 0x10; cdrom->seek_ff &= 0xF0; }
+            if (cdrom->seek_ff == 0x75) { cdrom->seek_ss++; cdrom->seek_ff = 0; }
+            if ((cdrom->seek_ss & 0xF) == 10) { cdrom->seek_ss += 0x10; cdrom->seek_ss &= 0xF0; }
+            if (cdrom->seek_ss == 0x60) { cdrom->seek_mm++; cdrom->seek_ss = 0; }
+            if ((cdrom->seek_mm & 0xF) == 10) { cdrom->seek_mm += 0x10; cdrom->seek_mm &= 0xF0; }
+
+            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) { log_fatal("readtoc: Unimplemented"); exit(1); }
+
+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",
+    "CdlUnimplemented",
+    "CdlUnmute",
+    "CdlSetfilter",
+    "CdlSetmode",
+    "CdlUnimplemented",
+    "CdlGetlocl",
+    "CdlGetlocp",
+    "CdlUnimplemented",
+    "CdlGettn",
+    "CdlGettd",
+    "CdlSeekl",
+    "CdlSeekp",
+    "CdlUnimplemented",
+    "CdlUnimplemented",
+    "CdlTest",
+    "CdlGetid",
+    "CdlReads",
+    "CdlUnimplemented",
+    "CdlUnimplemented",
+    "CdlReadtoc",
+    "CdlUnimplemented"
+};
+
+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_unimplemented,
+    cdrom_cmd_unmute,
+    cdrom_cmd_setfilter,
+    cdrom_cmd_setmode,
+    cdrom_cmd_unimplemented,
+    cdrom_cmd_getlocl,
+    cdrom_cmd_getlocp,
+    cdrom_cmd_unimplemented,
+    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,
+
+    // 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) {
+    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);
+
+        return cdrom->dfifo[offset + (cdrom->dfifo_index++)];
+    } else {
+        SET_BITS(status, STAT_DRQSTS_MASK, 0);
+
+        cdrom->dfifo_full = 0;
+    }
+
+    return 0x00;
+}
+
+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) {
+    log_set_quiet(0);
+    log_fatal("%s(%02x) %u params=[%02x, %02x, %02x, %02x, %02x, %02x]",
+        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]
+    );
+    log_set_quiet(1);
+
+    cdrom->command = value;
+    cdrom->state = CD_STATE_RECV_CMD;
+
+    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;
+        cdrom->dfifo_index = 0;
+    } else {
+        SET_BITS(status, STAT_DRQSTS_MASK, 0);
+
+        cdrom->dfifo_full = 0;
+        cdrom->dfifo_index = 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);
+
+    // if (value & 0x7) {
+    //     log_set_quiet(0);
+    //     log_fatal("Acknowledge %02x", value & 0x7);
+    //     log_set_quiet(1);
+    // }
+
+    // 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) {
+    log_fatal("Volume registers unimplemented");
+}
+
+void cdrom_write_rcdrspuv(psx_cdrom_t* cdrom, uint8_t value) {
+    log_fatal("Volume registers unimplemented");
+}
+
+void cdrom_write_rcdlspuv(psx_cdrom_t* cdrom, uint8_t value) {
+    log_fatal("Volume registers unimplemented");
+}
+
+void cdrom_write_lcdrspuv(psx_cdrom_t* cdrom, uint8_t value) {
+    log_fatal("Volume registers unimplemented");
+}
+
+void cdrom_write_volume(psx_cdrom_t* cdrom, uint8_t value) {
+    log_fatal("Volume registers unimplemented");
+}
+
+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() {
+    return (psx_cdrom_t*)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->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);
+}
+
+uint32_t psx_cdrom_read32(psx_cdrom_t* cdrom, uint32_t offset) {
+    log_fatal("Unhandled 32-bit CDROM read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint16_t psx_cdrom_read16(psx_cdrom_t* cdrom, uint32_t offset) {
+    log_fatal("Unhandled 16-bit CDROM read at offset %08x", offset);
+
+    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);
+
+    // log_fatal("%s (read %02x)", g_psx_cdrom_read_names_table[(STAT_INDEX << 2) | offset], data);
+
+    return data;
+}
+
+void psx_cdrom_write32(psx_cdrom_t* cdrom, uint32_t offset, uint32_t value) {
+    log_fatal("Unhandled 32-bit CDROM write at offset %08x (%08x)", offset, value);
+}
+
+void psx_cdrom_write16(psx_cdrom_t* cdrom, uint32_t offset, uint16_t value) {
+    log_fatal("Unhandled 16-bit CDROM write at offset %08x (%04x)", offset, value);
+}
+
+void psx_cdrom_write8(psx_cdrom_t* cdrom, uint32_t offset, uint8_t value) {
+    // log_fatal("%s (write %02x)", g_psx_cdrom_write_names_table[(STAT_INDEX << 2) | offset], value);
+
+    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) {
+            psx_ic_irq(cdrom->ic, IC_CDROM);
+
+            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 ext = cdrom_get_extension(path);
+    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;
+    }
+
+    if (error) {
+        log_fatal("Error loading file \'%s\'", path);
+
+        exit(1);
+    }
+}
+
+void psx_cdrom_get_cdda_samples(psx_cdrom_t* cdrom, void* buf, int size, psx_spu_t* spu) {
+    if (!cdrom->cdda_playing) {
+        memset(buf, 0, size);
+    
+        return;
+    }
+
+    if (!cdrom->disc)
+        return;
+
+    // Convert seek to I
+    msf_t msf = cdrom->cdda_msf;
+    
+    msf_from_bcd(&msf);
+
+    // Seek to that address and read sector
+    if (psx_disc_seek(cdrom->disc, msf)) {
+        cdrom->cdda_playing = 0;
+    }
+
+    psx_disc_read_sector(cdrom->disc, cdrom->cdda_buf);
+
+    ++cdrom->cdda_sectors_played;
+
+    // Increment sector
+    msf_add_f(&msf, 1);
+    msf_to_bcd(&msf);
+    
+    // Assign to CDDA MSF
+    cdrom->cdda_msf = msf;
+
+    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/pad.c
+++ b/psx/dev/pad.c
@@ -101,7 +101,7 @@
 uint32_t pad_handle_stat_read(psx_pad_t* pad) {
     // log_set_quiet(0);
     // log_fatal("pad stat read");
-    // log_set_quiet(1);
+    log_set_quiet(1);
     return 0x07;
     psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
 
--- /dev/null
+++ b/psx/dev/rework/gpu.c
@@ -1,0 +1,1773 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "gpu.h"
+#include "../log.h"
+
+int g_psx_gpu_dither_kernel[] = {
+    -4, +0, -3, +1,
+    +2, -2, +3, -1,
+    -3, +1, -4, +0,
+    +3, -1, +2, -2,
+};
+
+uint16_t gpu_to_bgr555(uint32_t color) {
+    return ((color & 0x0000f8) >> 3) |
+           ((color & 0x00f800) >> 6) |
+           ((color & 0xf80000) >> 9);
+}
+
+#define BGR555(c) \
+    (((c & 0x0000f8) >> 3) | \
+     ((c & 0x00f800) >> 6) | \
+     ((c & 0xf80000) >> 9))
+
+// #define BGR555(c) gpu_to_bgr555(c)
+
+#define VRAM(x, y) gpu->vram[(x) + ((y) * 1024)]
+
+int min3(int a, int b, int c) {
+    int m = a < b ? a : b;
+
+    return m < c ? m : c;
+}
+
+int max3(int a, int b, int c) {
+    int m = a > b ? a : b;
+
+    return m > c ? m : c;
+}
+
+psx_gpu_t* psx_gpu_create() {
+    return (psx_gpu_t*)malloc(sizeof(psx_gpu_t));
+}
+
+void psx_gpu_init(psx_gpu_t* gpu, psx_ic_t* ic) {
+    memset(gpu, 0, sizeof(psx_gpu_t));
+
+    gpu->io_base = PSX_GPU_BEGIN;
+    gpu->io_size = PSX_GPU_SIZE;
+
+    gpu->vram = (uint16_t*)malloc(PSX_GPU_VRAM_SIZE);
+    gpu->state = GPU_STATE_RECV_CMD;
+
+    gpu->ic = ic;
+}
+
+uint32_t psx_gpu_read32(psx_gpu_t* gpu, uint32_t offset) {
+    switch (offset) {
+        case 0x00: {
+            uint32_t data = 0x0;
+
+            if (gpu->c0_tsiz) {
+                data |= gpu->vram[gpu->c0_addr + (gpu->c0_xcnt + (gpu->c0_ycnt * 1024))];
+
+                gpu->c0_xcnt += 1;
+
+                if (gpu->c0_xcnt == gpu->c0_xsiz) {
+                    gpu->c0_ycnt += 1;
+                    gpu->c0_xcnt = 0;
+                }
+
+                data |= gpu->vram[gpu->c0_addr + (gpu->c0_xcnt + (gpu->c0_ycnt * 1024))] << 16;
+
+                gpu->c0_xcnt += 1;
+
+                if (gpu->c0_xcnt == gpu->c0_xsiz) {
+                    gpu->c0_ycnt += 1;
+                    gpu->c0_xcnt = 0;
+                }
+
+                gpu->c0_tsiz -= 2;
+            }
+
+            if (gpu->gp1_10h_req) {
+                switch (gpu->gp1_10h_req & 7) {
+                    case 2: {
+                        data = ((gpu->texw_oy / 8) << 15) | ((gpu->texw_ox / 8) << 10) | ((gpu->texw_my / 8) << 5) | (gpu->texw_mx / 8);
+                    } break;
+                    case 3: {
+                        data = (gpu->draw_y1 << 10) | gpu->draw_x1;
+                    } break;
+                    case 4: {
+                        data = (gpu->draw_y2 << 10) | gpu->draw_x2;
+                    } break;
+                    case 5: {
+                        data = (gpu->off_y << 10) | gpu->off_x;
+                    } break;
+                }
+
+                gpu->gp1_10h_req = 0;
+            }
+
+            return data;
+        } break;
+        case 0x04: return gpu->gpustat | 0x1e000000;
+    }
+
+    log_warn("Unhandled 32-bit GPU read at offset %08x", offset);
+
+    return 0x0;
+}
+
+uint16_t psx_gpu_read16(psx_gpu_t* gpu, uint32_t offset) {
+    log_fatal("Unhandled 16-bit GPU read at offset %08x", offset);
+}
+
+uint8_t psx_gpu_read8(psx_gpu_t* gpu, uint32_t offset) {
+    log_fatal("Unhandled 8-bit GPU read at offset %08x", offset);
+}
+
+int min(int x0, int x1) {
+    return (x0 < x1) ? x0 : x1;
+}
+
+int max(int x0, int x1) {
+    return (x0 > x1) ? x0 : x1;
+}
+
+#define EDGE(a, b, c) ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x))
+
+uint16_t gpu_fetch_texel(psx_gpu_t* gpu, uint16_t tx, uint16_t ty, uint32_t tpx, uint32_t tpy, uint16_t clutx, uint16_t cluty, int depth) {
+    tx = (tx & ~gpu->texw_mx) | (gpu->texw_ox & gpu->texw_mx);
+    ty = (ty & ~gpu->texw_my) | (gpu->texw_oy & gpu->texw_my);
+    tx &= 0xff;
+    ty &= 0xff;
+
+    switch (depth) {
+        // 4-bit
+        case 0: {
+            uint16_t texel = VRAM(tpx + (tx >> 2), tpy + ty);
+            uint16_t index = (texel >> ((tx & 0x3) << 2)) & 0xf;
+
+            return VRAM(clutx + index, cluty);
+        } break;
+
+        // 8-bit
+        case 1: {
+            uint16_t texel = VRAM(tpx + (tx >> 1), tpy + ty);
+            uint16_t index = (texel >> ((tx & 0x1) << 3)) & 0xff;
+
+            return VRAM(clutx + index, cluty);
+        } break;
+
+        // 15-bit
+        default: {
+            return VRAM(tpx + tx, tpy + ty);
+        } break;
+    }
+}
+
+void gpu_render_triangle(psx_gpu_t* gpu, vertex_t v0, vertex_t v1, vertex_t v2, poly_data_t data) {
+    vertex_t a, b, c, p;
+
+    int tpx = (data.texp & 0xf) << 6;
+    int tpy = (data.texp & 0x10) << 4;
+    int clutx = (data.clut & 0x3f) << 4;
+    int cluty = (data.clut >> 6) & 0x1ff;
+    int depth = (data.texp >> 7) & 3;
+    int transp = (data.attrib & PA_TRANSP) != 0;
+    int transp_mode;
+
+    if (data.attrib & PA_TEXTURED) {
+        transp_mode = (data.texp >> 5) & 3;
+    } else {
+        transp_mode = (gpu->gpustat >> 5) & 3;
+    }
+
+    a = v0;
+
+    /* Ensure the winding order is correct */
+    if (EDGE(v0, v1, v2) < 0) {
+        b = v2;
+        c = v1;
+    } else {
+        b = v1;
+        c = v2;
+    }
+
+    a.x += gpu->off_x;
+    a.y += gpu->off_y;
+    b.x += gpu->off_x;
+    b.y += gpu->off_y;
+    c.x += gpu->off_x;
+    c.y += gpu->off_y;
+
+    int xmin = min3(a.x, b.x, c.x);
+    int ymin = min3(a.y, b.y, c.y);
+    int xmax = max3(a.x, b.x, c.x); 
+    int ymax = max3(a.y, b.y, c.y);
+
+    float area = EDGE(a, b, c);
+
+    for (int y = ymin; y < ymax; y++) {
+        for (int x = xmin; x < xmax; x++) {
+            p.x = x;
+            p.y = y;
+
+            float z0 = EDGE(b, c, p);
+            float z1 = EDGE(c, a, p);
+            float z2 = EDGE(a, b, p);
+
+            if ((z0 < 0) || (z1 < 0) || (z2 < 0))
+                continue;
+            
+            int bc = (x >= gpu->draw_x1) && (x <= gpu->draw_x2) &&
+                     (y >= gpu->draw_y1) && (y <= gpu->draw_y2);
+            
+            if (!bc)
+                continue;
+
+            uint16_t color = 0;
+            uint32_t mod   = 0;
+
+            if (data.attrib & PA_SHADED) {
+                int cr = (z0 * ((a.c >>  0) & 0xff) + z1 * ((b.c >>  0) & 0xff) + z2 * ((c.c >>  0) & 0xff)) / area;
+                int cg = (z0 * ((a.c >>  8) & 0xff) + z1 * ((b.c >>  8) & 0xff) + z2 * ((c.c >>  8) & 0xff)) / area;
+                int cb = (z0 * ((a.c >> 16) & 0xff) + z1 * ((b.c >> 16) & 0xff) + z2 * ((c.c >> 16) & 0xff)) / area;
+
+                int dy = (y - ymin) & 3;
+                int dx = (x - xmin) & 3;
+
+                int dither = g_psx_gpu_dither_kernel[dx + (dy * 4)];
+
+                cr += dither;
+                cg += dither;
+                cb += dither;
+
+                // Saturate (clamp) to 00-ff
+                cr = (cr >= 0xff) ? 0xff : ((cr <= 0) ? 0 : cr);
+                cg = (cg >= 0xff) ? 0xff : ((cg <= 0) ? 0 : cg);
+                cb = (cb >= 0xff) ? 0xff : ((cb <= 0) ? 0 : cb);
+
+                uint32_t rgb = (cb << 16) | (cg << 8) | cr;
+
+                mod = rgb;
+            } else {
+                mod = data.v[0].c;
+            }
+
+            if (data.attrib & PA_TEXTURED) {
+                uint32_t tx = ((z0 * a.tx) + (z1 * b.tx) + (z2 * c.tx)) / area;
+                uint32_t 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);
+
+                if (!texel)
+                    continue;
+
+                if (transp) {
+                    transp = (texel & 0x8000) != 0;
+                }
+
+                if (data.attrib & PA_RAW) {
+                    color = texel;
+                } else {
+                    int tr = ((texel >> 0 ) & 0x1f) << 3;
+                    int tg = ((texel >> 5 ) & 0x1f) << 3;
+                    int tb = ((texel >> 10) & 0x1f) << 3;
+
+                    int mr = (mod >> 0 ) & 0xff;
+                    int mg = (mod >> 8 ) & 0xff;
+                    int mb = (mod >> 16) & 0xff;
+
+                    int cr = (tr * mr) / 0x80;
+                    int cg = (tg * mg) / 0x80;
+                    int cb = (tb * mb) / 0x80;
+
+                    cr = (cr >= 0xff) ? 0xff : ((cr <= 0) ? 0 : cr);
+                    cg = (cg >= 0xff) ? 0xff : ((cg <= 0) ? 0 : cg);
+                    cb = (cb >= 0xff) ? 0xff : ((cb <= 0) ? 0 : cb);
+
+                    uint32_t rgb = cr | (cg << 8) | (cb << 16);
+
+                    color = BGR555(rgb);
+                }
+            } else {
+                color = BGR555(mod);
+            }
+
+            int cr = ((color >> 0 ) & 0x1f) << 3;
+            int cg = ((color >> 5 ) & 0x1f) << 3;
+            int cb = ((color >> 10) & 0x1f) << 3;
+
+            if (transp) {
+                uint16_t back = VRAM(x, y);
+
+                int br = ((back >> 0 ) & 0x1f) << 3;
+                int bg = ((back >> 5 ) & 0x1f) << 3;
+                int bb = ((back >> 10) & 0x1f) << 3;
+
+                switch (transp_mode) {
+                    case 0: {
+                        cr = (0.5f * br) + (0.5f * cr);
+                        cg = (0.5f * bg) + (0.5f * cg);
+                        cb = (0.5f * bb) + (0.5f * cb);
+                    } break;
+                    case 1: {
+                        cr = br + cr;
+                        cg = bg + cg;
+                        cb = bb + cb;
+                    } break;
+                    case 2: {
+                        cr = br - cr;
+                        cg = bg - cg;
+                        cb = bb - cb;
+                    } break;
+                    case 3: {
+                        cr = br + (0.25 * cr);
+                        cg = bg + (0.25 * cg);
+                        cb = bb + (0.25 * cb);
+                    } break;
+                }
+
+                cr = (cr >= 0xff) ? 0xff : ((cr <= 0) ? 0 : cr);
+                cg = (cg >= 0xff) ? 0xff : ((cg <= 0) ? 0 : cg);
+                cb = (cb >= 0xff) ? 0xff : ((cb <= 0) ? 0 : cb);
+
+                uint32_t rgb = cr | (cg << 8) | (cb << 16);
+
+                color = BGR555(rgb);
+            }
+
+            VRAM(x, y) = color;
+        }
+    }
+}
+
+void gpu_render_rect(psx_gpu_t* gpu, rect_data_t data) {
+    uint16_t width, height;
+
+    switch ((data.attrib >> 3) & 3) {
+        case RS_VARIABLE: { width = data.width; height = data.height; } break;
+        case RS_1X1     : { width = 1         ; height = 1          ; } break;
+        case RS_8X8     : { width = 8         ; height = 8          ; } break;
+        case RS_16X16   : { width = 16        ; height = 16         ; } break;
+    }
+
+    int textured = (data.attrib & RA_TEXTURED) != 0;
+    int transp = (data.attrib & RA_TRANSP) != 0;
+    int transp_mode = (gpu->gpustat >> 5) & 3;
+
+    int clutx = (data.clut & 0x3f) << 4;
+    int cluty = (data.clut >> 6) & 0x1ff;
+
+    /* Offset coordinates */
+    data.v0.x += gpu->off_x;
+    data.v0.y += gpu->off_y;
+
+    /* Calculate bounding box */
+    int xmax = data.v0.x + width;
+    int ymax = data.v0.y + height;
+
+    int32_t xc = 0, yc = 0;
+
+    for (int16_t y = data.v0.y; y < ymax; y++) {
+        for (int16_t x = data.v0.x; x < xmax; x++) {
+            int bc = (x >= gpu->draw_x1) && (x <= gpu->draw_x2) &&
+                     (y >= gpu->draw_y1) && (y <= gpu->draw_y2);
+
+            if (!bc)
+                goto skip;
+
+            uint16_t color;
+
+            if (textured) {
+                uint16_t texel = gpu_fetch_texel(
+                    gpu,
+                    data.v0.tx + xc, data.v0.ty + yc,
+                    gpu->texp_x, gpu->texp_y,
+                    clutx, cluty,
+                    gpu->texp_d
+                );
+
+                if (!texel)
+                    goto skip;
+
+                if (transp) {
+                    transp = (texel & 0x8000) != 0;
+                }
+
+                int tr = ((texel >> 0 ) & 0x1f) << 3;
+                int tg = ((texel >> 5 ) & 0x1f) << 3;
+                int tb = ((texel >> 10) & 0x1f) << 3;
+
+                int mr = (data.v0.c >> 0 ) & 0xff;
+                int mg = (data.v0.c >> 8 ) & 0xff;
+                int mb = (data.v0.c >> 16) & 0xff;
+
+                int cr = (tr * mr) / 0x80;
+                int cg = (tg * mg) / 0x80;
+                int cb = (tb * mb) / 0x80;
+
+                uint32_t rgb = cr | (cg << 8) | (cb << 16);
+
+                color = BGR555(rgb);
+            } else {
+                color = BGR555(data.v0.c);
+            }
+
+            int cr = ((color >> 0 ) & 0x1f) << 3;
+            int cg = ((color >> 5 ) & 0x1f) << 3;
+            int cb = ((color >> 10) & 0x1f) << 3;
+
+            if (transp) {
+                uint16_t back = VRAM(x, y);
+
+                int br = ((back >> 0 ) & 0x1f) << 3;
+                int bg = ((back >> 5 ) & 0x1f) << 3;
+                int bb = ((back >> 10) & 0x1f) << 3;
+
+                switch (transp_mode) {
+                    case 0: {
+                        cr = (0.5f * br) + (0.5f * cr);
+                        cg = (0.5f * bg) + (0.5f * cg);
+                        cb = (0.5f * bb) + (0.5f * cb);
+                    } break;
+                    case 1: {
+                        cr = br + cr;
+                        cg = bg + cg;
+                        cb = bb + cb;
+                    } break;
+                    case 2: {
+                        cr = br - cr;
+                        cg = bg - cg;
+                        cb = bb - cb;
+                    } break;
+                    case 3: {
+                        cr = br + (0.25f * cr);
+                        cg = bg + (0.25f * cg);
+                        cb = bb + (0.25f * cb);
+                    } break;
+                }
+
+                cr = (cr >= 0xff) ? 0xff : ((cr <= 0) ? 0 : cr);
+                cg = (cg >= 0xff) ? 0xff : ((cg <= 0) ? 0 : cg);
+                cb = (cb >= 0xff) ? 0xff : ((cb <= 0) ? 0 : cb);
+
+                uint32_t rgb = cr | (cg << 8) | (cb << 16);
+
+                color = BGR555(rgb);
+            }
+
+            VRAM(x, y) = color;
+
+            skip:
+
+            ++xc;
+        }
+
+        xc = 0;
+
+        ++yc;
+    }
+}
+
+void plotLineLow(psx_gpu_t* gpu, int x0, int y0, int x1, int y1, uint16_t color) {
+    int dx = x1 - x0;
+    int dy = y1 - y0;
+    int yi = 1;
+    if (dy < 0) {
+        yi = -1;
+        dy = -dy;
+    }
+    int d = (2 * dy) - dx;
+    int y = y0;
+
+    for (int x = x0; x < x1; x++) {
+        if ((x >= gpu->draw_x1) && (x <= gpu->draw_x2) && (y >= gpu->draw_y1) && (y <= gpu->draw_y2))
+            VRAM(x, y) = color;
+
+        if (d > 0) {
+            y += yi;
+            d += (2 * (dy - dx));
+        } else {
+            d += 2*dy;
+        }
+    }
+}
+
+void plotLineHigh(psx_gpu_t* gpu, int x0, int y0, int x1, int y1, uint16_t color) {
+    int dx = x1 - x0;
+    int dy = y1 - y0;
+    int xi = 1;
+    if (dx < 0) {
+        xi = -1;
+        dx = -dx;
+    }
+    int d = (2 * dx) - dy;
+    int x = x0;
+
+    for (int y = y0; y < y1; y++) {
+        if ((x >= gpu->draw_x1) && (x <= gpu->draw_x2) && (y >= gpu->draw_y1) && (y <= gpu->draw_y2))
+            VRAM(x, y) = color;
+
+        if (d > 0) {
+            x = x + xi;
+            d += (2 * (dx - dy));
+        } else {
+            d += 2*dx;
+        }
+    }
+}
+
+void plotLine(psx_gpu_t* gpu, int x0, int y0, int x1, int y1, uint16_t color) {
+    if (abs(y1 - y0) < abs(x1 - x0)) {
+        if (x0 > x1) {
+            plotLineLow(gpu, x1, y1, x0, y0, color);
+        } else {
+            plotLineLow(gpu, x0, y0, x1, y1, color);
+        }
+    } else {
+        if (y0 > y1) {
+            plotLineHigh(gpu, x1, y1, x0, y0, color);
+        } else {
+            plotLineHigh(gpu, x0, y0, x1, y1, color);
+        }
+    }
+}
+
+void gpu_render_flat_line(psx_gpu_t* gpu, vertex_t v0, vertex_t v1, uint32_t color) {
+    plotLine(gpu, v0.x, v0.y, v1.x, v1.y, color);
+}
+
+void gpu_render_flat_rectangle(psx_gpu_t* gpu, vertex_t v, uint32_t w, uint32_t h, uint32_t color) {
+    /* Offset coordinates */
+    v.x += gpu->off_x;
+    v.y += gpu->off_y;
+
+    /* Calculate bounding box */
+    int xmin = max(v.x, gpu->draw_x1);
+    int ymin = max(v.y, gpu->draw_y1);
+    int xmax = min(xmin + w, gpu->draw_x2);
+    int ymax = min(ymin + h, gpu->draw_y2);
+
+    for (uint32_t y = ymin; y < ymax; y++)
+        for (uint32_t x = xmin; x < xmax; x++)
+            VRAM(x, y) = color;
+}
+
+void gpu_render_textured_rectangle(psx_gpu_t* gpu, vertex_t v, uint32_t w, uint32_t h, uint16_t clutx, uint16_t cluty, uint32_t color) {
+    vertex_t a = v;
+
+    a.x += gpu->off_x;
+    a.y += gpu->off_y;
+
+    int xmin = max(a.x, gpu->draw_x1);
+    int ymin = max(a.y, gpu->draw_y1);
+    int xmax = min(xmin + w, gpu->draw_x2);
+    int ymax = min(ymin + h, gpu->draw_y2);
+
+    uint32_t xc = 0, yc = 0;
+
+    for (int y = ymin; y < ymax; y++) {
+        for (int x = xmin; x < xmax; x++) {
+            uint16_t texel = gpu_fetch_texel(
+                gpu,
+                a.tx + xc, a.ty + yc,
+                gpu->texp_x, gpu->texp_y,
+                clutx, cluty,
+                gpu->texp_d
+            );
+
+            ++xc;
+
+            gpu->vram[x + (y * 1024)] = texel;
+        }
+
+        xc = 0;
+
+        ++yc;
+    }
+}
+
+void gpu_render_flat_triangle(psx_gpu_t* gpu, vertex_t v0, vertex_t v1, vertex_t v2, uint32_t color) {
+    vertex_t a, b, c;
+
+    a = v0;
+
+    /* Ensure the winding order is correct */
+    if (EDGE(v0, v1, v2) < 0) {
+        b = v2;
+        c = v1;
+    } else {
+        b = v1;
+        c = v2;
+    }
+
+    a.x += gpu->off_x;
+    a.y += gpu->off_y;
+    b.x += gpu->off_x;
+    b.y += gpu->off_y;
+    c.x += gpu->off_x;
+    c.y += gpu->off_y;
+
+    int xmin = max(min(min(a.x, b.x), c.x), gpu->draw_x1);
+    int ymin = max(min(min(a.y, b.y), c.y), gpu->draw_y1);
+    int xmax = min(max(max(a.x, b.x), c.x), gpu->draw_x2); 
+    int ymax = min(max(max(a.y, b.y), c.y), gpu->draw_y2);
+
+    for (int y = ymin; y < ymax; y++) {
+        for (int x = xmin; x < xmax; x++) {
+            int z0 = ((b.x - a.x) * (y - a.y)) - ((b.y - a.y) * (x - a.x));
+            int z1 = ((c.x - b.x) * (y - b.y)) - ((c.y - b.y) * (x - b.x));
+            int z2 = ((a.x - c.x) * (y - c.y)) - ((a.y - c.y) * (x - c.x));
+
+            if ((z0 >= 0) && (z1 >= 0) && (z2 >= 0)) {
+                gpu->vram[x + (y * 1024)] = BGR555(color);
+            }
+        }
+    }
+}
+
+void gpu_render_shaded_triangle(psx_gpu_t* gpu, vertex_t v0, vertex_t v1, vertex_t v2) {
+    vertex_t a, b, c, p;
+
+    a = v0;
+
+    /* Ensure the winding order is correct */
+    if (EDGE(v0, v1, v2) < 0) {
+        b = v2;
+        c = v1;
+    } else {
+        b = v1;
+        c = v2;
+    }
+
+    a.x += gpu->off_x;
+    a.y += gpu->off_y;
+    b.x += gpu->off_x;
+    b.y += gpu->off_y;
+    c.x += gpu->off_x;
+    c.y += gpu->off_y;
+
+    int xmin = max(min(min(a.x, b.x), c.x), gpu->draw_x1);
+    int ymin = max(min(min(a.y, b.y), c.y), gpu->draw_y1);
+    int xmax = min(max(max(a.x, b.x), c.x), gpu->draw_x2); 
+    int ymax = min(max(max(a.y, b.y), c.y), gpu->draw_y2);
+
+    int area = EDGE(a, b, c);
+
+    for (int y = ymin; y < ymax; y++) {
+        for (int x = xmin; x < xmax; x++) {
+            p.x = x;
+            p.y = y;
+
+            float z0 = EDGE((float)b, (float)c, (float)p);
+            float z1 = EDGE((float)c, (float)a, (float)p);
+            float z2 = EDGE((float)a, (float)b, (float)p);
+
+            if ((z0 >= 0) && (z1 >= 0) && (z2 >= 0)) {
+                int cr = (z0 * ((a.c >>  0) & 0xff) + z1 * ((b.c >>  0) & 0xff) + z2 * ((c.c >>  0) & 0xff)) / area;
+                int cg = (z0 * ((a.c >>  8) & 0xff) + z1 * ((b.c >>  8) & 0xff) + z2 * ((c.c >>  8) & 0xff)) / area;
+                int cb = (z0 * ((a.c >> 16) & 0xff) + z1 * ((b.c >> 16) & 0xff) + z2 * ((c.c >> 16) & 0xff)) / area;
+
+                // Calculate positions within our 4x4 dither
+                // kernel
+                int dy = (y - ymin) % 4;
+                int dx = (x - xmin) % 4;
+
+                // Shift two pixels horizontally on the last
+                // two scanlines?
+                // if (dy > 1) {
+                //     dx = ((x + 2) - xmin) % 4;
+                // }
+
+                int dither = g_psx_gpu_dither_kernel[dx + (dy * 4)];
+
+                // Add to the original 8-bit color values
+                cr += dither;
+                cg += dither;
+                cb += dither;
+
+                // Saturate (clamp) to 00-ff
+                cr = (cr >= 0xff) ? 0xff : ((cr <= 0) ? 0 : cr);
+                cg = (cg >= 0xff) ? 0xff : ((cg <= 0) ? 0 : cg);
+                cb = (cb >= 0xff) ? 0xff : ((cb <= 0) ? 0 : cb);
+
+                uint32_t color = (cb << 16) | (cg << 8) | cr;
+
+                gpu->vram[x + (y * 1024)] = BGR555(color);
+            }
+        }
+    }
+}
+
+void gpu_render_textured_triangle(psx_gpu_t* gpu, vertex_t v0, vertex_t v1, vertex_t v2, uint32_t tpx, uint32_t tpy, uint16_t clutx, uint16_t cluty, int depth) {
+    vertex_t a, b, c;
+
+    a = v0;
+
+    /* Ensure the winding order is correct */
+    if (EDGE(v0, v1, v2) < 0) {
+        b = v2;
+        c = v1;
+    } else {
+        b = v1;
+        c = v2;
+    }
+
+    a.x += gpu->off_x;
+    a.y += gpu->off_y;
+    b.x += gpu->off_x;
+    b.y += gpu->off_y;
+    c.x += gpu->off_x;
+    c.y += gpu->off_y;
+
+    int xmin = max(min(min(a.x, b.x), c.x), gpu->draw_x1);
+    int ymin = max(min(min(a.y, b.y), c.y), gpu->draw_y1);
+    int xmax = min(max(max(a.x, b.x), c.x), gpu->draw_x2); 
+    int ymax = min(max(max(a.y, b.y), c.y), gpu->draw_y2);
+
+    uint32_t area = EDGE(a, b, c);
+
+    for (int y = ymin; y < ymax; y++) {
+        for (int x = xmin; x < xmax; x++) {
+            vertex_t p;
+
+            p.x = x;
+            p.y = y;
+
+            float z0 = EDGE((float)b, (float)c, (float)p);
+            float z1 = EDGE((float)c, (float)a, (float)p);
+            float z2 = EDGE((float)a, (float)b, (float)p);
+
+            if ((z0 >= 0) && (z1 >= 0) && (z2 >= 0)) {
+                uint32_t tx = ((z0 * a.tx) + (z1 * b.tx) + (z2 * c.tx)) / area;
+                uint32_t ty = ((z0 * a.ty) + (z1 * b.ty) + (z2 * c.ty)) / area;
+
+                uint16_t color = gpu_fetch_texel(
+                    gpu,
+                    tx, ty,
+                    tpx, tpy,
+                    clutx, cluty,
+                    depth
+                );
+
+                if (!color) continue;
+
+                gpu->vram[x + (y * 1024)] = color;
+            }
+        }
+    }
+}
+
+void gpu_rect(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+
+            int size = (gpu->buf[0] >> 27) & 3;
+            int textured = (gpu->buf[0] & 0x04000000) != 0;
+
+            gpu->cmd_args_remaining = 1 + (size == RS_VARIABLE) + textured;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                rect_data_t rect;
+
+                rect.attrib = gpu->buf[0] >> 24;
+
+                int textured = (rect.attrib & RA_TEXTURED) != 0;
+                int raw      = (rect.attrib & RA_RAW) != 0;
+
+                // Add 1 if is textured
+                int size_offset = 2 + textured;
+
+                rect.v0.c   = gpu->buf[0] & 0xffffff;
+                rect.v0.x   = gpu->buf[1] & 0xffff;
+                rect.v0.y   = gpu->buf[1] >> 16;
+                rect.v0.tx  = (gpu->buf[2] >> 0) & 0xff;
+                rect.v0.ty  = (gpu->buf[2] >> 8) & 0xff;
+                rect.clut   = gpu->buf[2] >> 16;
+                rect.width  = gpu->buf[size_offset] & 0xffff;
+                rect.height = gpu->buf[size_offset] >> 16;
+
+                if (textured && raw)
+                    rect.v0.c = 0x808080;
+                
+                gpu_render_rect(gpu, rect);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_poly(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 quad     = (gpu->buf[0] & 0x08000000) != 0;
+            int textured = (gpu->buf[0] & 0x04000000) != 0;
+
+            int fields_per_vertex = 1 + shaded + textured;
+            int vertices = 3 + quad;
+ 
+            gpu->cmd_args_remaining = (fields_per_vertex * vertices) - shaded;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                poly_data_t poly;
+
+                poly.attrib = gpu->buf[0] >> 24;
+
+                int shaded   = (poly.attrib & PA_SHADED) != 0;
+                int textured = (poly.attrib & PA_TEXTURED) != 0;
+
+                int color_offset = shaded * (2 + textured);
+                int vert_offset = 1 + (textured | shaded) +
+                                      (textured & shaded);
+                int texc_offset = textured * (2 + shaded);
+                int texp_offset = textured * (4 + shaded);
+
+                poly.clut = gpu->buf[2] >> 16;
+                poly.texp = gpu->buf[texp_offset] >> 16;
+
+                poly.v[0].c = gpu->buf[0+0*color_offset] & 0xffffff;
+                poly.v[1].c = gpu->buf[0+1*color_offset] & 0xffffff;
+                poly.v[2].c = gpu->buf[0+2*color_offset] & 0xffffff;
+                poly.v[3].c = gpu->buf[0+3*color_offset] & 0xffffff;
+                poly.v[0].x = gpu->buf[1+0*vert_offset] & 0xffff;
+                poly.v[1].x = gpu->buf[1+1*vert_offset] & 0xffff;
+                poly.v[2].x = gpu->buf[1+2*vert_offset] & 0xffff;
+                poly.v[3].x = gpu->buf[1+3*vert_offset] & 0xffff;
+                poly.v[0].y = gpu->buf[1+0*vert_offset] >> 16;
+                poly.v[1].y = gpu->buf[1+1*vert_offset] >> 16;
+                poly.v[2].y = gpu->buf[1+2*vert_offset] >> 16;
+                poly.v[3].y = gpu->buf[1+3*vert_offset] >> 16;
+                poly.v[0].tx = gpu->buf[2+0*texc_offset] & 0xff;
+                poly.v[1].tx = gpu->buf[2+1*texc_offset] & 0xff;
+                poly.v[2].tx = gpu->buf[2+2*texc_offset] & 0xff;
+                poly.v[3].tx = gpu->buf[2+3*texc_offset] & 0xff;
+                poly.v[0].ty = (gpu->buf[2+0*texc_offset] >> 8) & 0xff;
+                poly.v[1].ty = (gpu->buf[2+1*texc_offset] >> 8) & 0xff;
+                poly.v[2].ty = (gpu->buf[2+2*texc_offset] >> 8) & 0xff;
+                poly.v[3].ty = (gpu->buf[2+3*texc_offset] >> 8) & 0xff;
+
+                if (poly.attrib & PA_QUAD) {
+                    gpu_render_triangle(gpu, poly.v[0], poly.v[1], poly.v[2], poly);
+                    gpu_render_triangle(gpu, poly.v[1], poly.v[2], poly.v[3], poly);
+                } else {
+                    gpu_render_triangle(gpu, poly.v[0], poly.v[1], poly.v[2], poly);
+                }
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_copy(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 3;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                uint32_t srcx = gpu->buf[1] & 0xffff;
+                uint32_t srcy = gpu->buf[1] >> 16;
+                uint32_t dstx = gpu->buf[2] & 0xffff;
+                uint32_t dsty = gpu->buf[2] >> 16;
+                uint32_t xsiz = gpu->buf[3] & 0xffff;
+                uint32_t ysiz = gpu->buf[3] >> 16;
+
+                for (int y = 0; y < ysiz; y++)
+                    for (int x = 0; x < xsiz; x++)
+                        VRAM(dstx + x, dsty + y) = VRAM(srcx + x, srcy + y);
+            }
+        } break;
+    }
+}
+
+void gpu_recv(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 2;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                // Save static data
+                gpu->xpos = gpu->buf[1] & 0x3ff;
+                gpu->ypos = (gpu->buf[1] >> 16) & 0x1ff;
+                gpu->xsiz = gpu->buf[2] & 0xffff;
+                gpu->ysiz = gpu->buf[2] >> 16;
+                gpu->xsiz = ((gpu->xsiz - 1) & 0x3ff) + 1;
+                gpu->ysiz = ((gpu->ysiz - 1) & 0x1ff) + 1;
+                gpu->tsiz = ((gpu->xsiz * gpu->ysiz) + 1) & 0xfffffffe;
+                gpu->addr = gpu->xpos + (gpu->ypos * 1024);
+                gpu->xcnt = 0;
+                gpu->ycnt = 0;
+            }
+        } break;
+
+        case GPU_STATE_RECV_DATA: {
+            unsigned int xpos = (gpu->xpos + gpu->xcnt) & 0x3ff;
+            unsigned int ypos = (gpu->ypos + gpu->ycnt) & 0x1ff;
+
+            // To-do: This is segfaulting for some reason
+            //        Fix GPU edge cases in general
+            VRAM(xpos, ypos) = gpu->recv_data & 0xffff;
+
+            ++gpu->xcnt;
+
+            xpos = (gpu->xpos + gpu->xcnt) & 0x3ff;
+            ypos = (gpu->ypos + gpu->ycnt) & 0x1ff;
+
+            if (gpu->xcnt == gpu->xsiz) {
+                ++gpu->ycnt;
+                gpu->xcnt = 0;
+
+                ypos = (gpu->ypos + gpu->ycnt) & 0x1ff;
+                xpos = (gpu->xpos + gpu->xcnt) & 0x3ff;
+            }
+
+            VRAM(xpos, ypos) = gpu->recv_data >> 16;
+
+            ++gpu->xcnt;
+            
+            if (gpu->xcnt == gpu->xsiz) {
+                ++gpu->ycnt;
+                gpu->xcnt = 0;
+
+                xpos = (gpu->xpos + gpu->xcnt) & 0x3ff;
+                ypos = (gpu->ypos + gpu->ycnt) & 0x1ff;
+            }
+
+            gpu->tsiz -= 2;
+
+            if (!gpu->tsiz) {
+                gpu->xcnt = 0;
+                gpu->ycnt = 0;
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_send(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 2;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->c0_xcnt = 0;
+                gpu->c0_ycnt = 0;
+                uint32_t c0_xpos = gpu->buf[1] & 0xffff;
+                uint32_t c0_ypos = gpu->buf[1] >> 16;
+                gpu->c0_xsiz = gpu->buf[2] & 0xffff;
+                gpu->c0_ysiz = gpu->buf[2] >> 16;
+                c0_xpos = c0_xpos & 0x3ff;
+                c0_ypos = c0_ypos & 0x1ff;
+                gpu->c0_xsiz = ((gpu->c0_xsiz - 1) & 0x3ff) + 1;
+                gpu->c0_ysiz = ((gpu->c0_ysiz - 1) & 0x1ff) + 1;
+                gpu->c0_tsiz = ((gpu->c0_xsiz * gpu->c0_ysiz) + 1) & 0xfffffffe;
+                gpu->c0_addr = c0_xpos + (c0_ypos * 1024);
+
+                printf("c0addr=%08x c0xcnt=%u c0ycnt=%u\n",
+                    gpu->c0_addr,
+                    gpu->c0_xcnt,
+                    gpu->c0_ycnt
+                );
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+// Monochrome Opaque Quadrilateral
+void gpu_cmd_28(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 4;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                gpu->v0.x = gpu->buf[1] & 0xffff;
+                gpu->v0.y = gpu->buf[1] >> 16;
+                gpu->v1.x = gpu->buf[2] & 0xffff;
+                gpu->v1.y = gpu->buf[2] >> 16;
+                gpu->v2.x = gpu->buf[3] & 0xffff;
+                gpu->v2.y = gpu->buf[3] >> 16;
+                gpu->v3.x = gpu->buf[4] & 0xffff;
+                gpu->v3.y = gpu->buf[4] >> 16;
+                gpu->color = gpu->buf[0] & 0xffffff;
+
+                gpu_render_flat_triangle(gpu, gpu->v0, gpu->v1, gpu->v2, gpu->color);
+                gpu_render_flat_triangle(gpu, gpu->v1, gpu->v2, gpu->v3, gpu->color);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+// Monochrome Opaque Quadrilateral
+void gpu_cmd_30(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 5;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                gpu->v0.c = gpu->buf[0] & 0xffffff;
+                gpu->v0.x = gpu->buf[1] & 0xffff;
+                gpu->v0.y = gpu->buf[1] >> 16;
+                gpu->v1.c = gpu->buf[2] & 0xffffff;
+                gpu->v1.x = gpu->buf[3] & 0xffff;
+                gpu->v1.y = gpu->buf[3] >> 16;
+                gpu->v2.c = gpu->buf[4] & 0xffffff;
+                gpu->v2.x = gpu->buf[5] & 0xffff;
+                gpu->v2.y = gpu->buf[5] >> 16;
+
+                gpu_render_shaded_triangle(gpu, gpu->v0, gpu->v1, gpu->v2);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+// Monochrome Opaque Quadrilateral
+void gpu_cmd_38(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 7;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                gpu->v0.c = gpu->buf[0] & 0xffffff;
+                gpu->v0.x = gpu->buf[1] & 0xffff;
+                gpu->v0.y = gpu->buf[1] >> 16;
+                gpu->v1.c = gpu->buf[2] & 0xffffff;
+                gpu->v1.x = gpu->buf[3] & 0xffff;
+                gpu->v1.y = gpu->buf[3] >> 16;
+                gpu->v2.c = gpu->buf[4] & 0xffffff;
+                gpu->v2.x = gpu->buf[5] & 0xffff;
+                gpu->v2.y = gpu->buf[5] >> 16;
+                gpu->v3.c = gpu->buf[6] & 0xffffff;
+                gpu->v3.x = gpu->buf[7] & 0xffff;
+                gpu->v3.y = gpu->buf[7] >> 16;
+
+                gpu_render_shaded_triangle(gpu, gpu->v0, gpu->v1, gpu->v2);
+                gpu_render_shaded_triangle(gpu, gpu->v1, gpu->v2, gpu->v3);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+// Monochrome Opaque Quadrilateral
+void gpu_cmd_3c(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 11;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                uint32_t texp = gpu->buf[5] >> 16;
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->pal   = gpu->buf[2] >> 16;
+                gpu->v0.tx = gpu->buf[2] & 0xff;
+                gpu->v0.ty = (gpu->buf[2] >> 8) & 0xff;
+                gpu->v1.tx = gpu->buf[5] & 0xff;
+                gpu->v1.ty = (gpu->buf[5] >> 8) & 0xff;
+                gpu->v2.tx = gpu->buf[8] & 0xff;
+                gpu->v2.ty = (gpu->buf[8] >> 8) & 0xff;
+                gpu->v3.tx = gpu->buf[11] & 0xff;
+                gpu->v3.ty = (gpu->buf[11] >> 8) & 0xff;
+                gpu->v0.x = gpu->buf[1] & 0xffff;
+                gpu->v0.y = gpu->buf[1] >> 16;
+                gpu->v1.x = gpu->buf[4] & 0xffff;
+                gpu->v1.y = gpu->buf[4] >> 16;
+                gpu->v2.x = gpu->buf[7] & 0xffff;
+                gpu->v2.y = gpu->buf[7] >> 16;
+                gpu->v3.x = gpu->buf[10] & 0xffff;
+                gpu->v3.y = gpu->buf[10] >> 16;
+
+                uint16_t clutx = (gpu->pal & 0x3f) << 4;
+                uint16_t cluty = (gpu->pal >> 6) & 0x1ff;
+                uint16_t tpx = (texp & 0xf) << 6;
+                uint16_t tpy = (texp & 0x10) << 4;
+                uint16_t depth = (texp >> 7) & 0x3;
+
+                gpu_render_textured_triangle(gpu, gpu->v0, gpu->v1, gpu->v2, tpx, tpy, clutx, cluty, depth);
+                gpu_render_textured_triangle(gpu, gpu->v1, gpu->v2, gpu->v3, tpx, tpy, clutx, cluty, depth);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+// Monochrome Opaque Quadrilateral
+void gpu_cmd_2c(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 8;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                uint32_t texp = gpu->buf[4] >> 16;
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->pal   = gpu->buf[2] >> 16;
+                gpu->v0.tx = gpu->buf[2] & 0xff;
+                gpu->v0.ty = (gpu->buf[2] >> 8) & 0xff;
+                gpu->v1.tx = gpu->buf[4] & 0xff;
+                gpu->v1.ty = (gpu->buf[4] >> 8) & 0xff;
+                gpu->v2.tx = gpu->buf[6] & 0xff;
+                gpu->v2.ty = (gpu->buf[6] >> 8) & 0xff;
+                gpu->v3.tx = gpu->buf[8] & 0xff;
+                gpu->v3.ty = (gpu->buf[8] >> 8) & 0xff;
+                gpu->v0.x = gpu->buf[1] & 0xffff;
+                gpu->v0.y = gpu->buf[1] >> 16;
+                gpu->v1.x = gpu->buf[3] & 0xffff;
+                gpu->v1.y = gpu->buf[3] >> 16;
+                gpu->v2.x = gpu->buf[5] & 0xffff;
+                gpu->v2.y = gpu->buf[5] >> 16;
+                gpu->v3.x = gpu->buf[7] & 0xffff;
+                gpu->v3.y = gpu->buf[7] >> 16;
+
+                uint16_t clutx = (gpu->pal & 0x3f) << 4;
+                uint16_t cluty = (gpu->pal >> 6) & 0x1ff;
+                uint16_t tpx = (texp & 0xf) << 6;
+                uint16_t tpy = (texp & 0x10) << 4;
+                uint16_t depth = (texp >> 7) & 0x3;
+
+                gpu_render_textured_triangle(gpu, gpu->v0, gpu->v1, gpu->v2, tpx, tpy, clutx, cluty, depth);
+                gpu_render_textured_triangle(gpu, gpu->v1, gpu->v2, gpu->v3, tpx, tpy, clutx, cluty, depth);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+// Monochrome Opaque Quadrilateral
+void gpu_cmd_24(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 6;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                uint32_t texp = gpu->buf[4] >> 16;
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->pal   = gpu->buf[2] >> 16;
+                gpu->v0.tx = gpu->buf[2] & 0xff;
+                gpu->v0.ty = (gpu->buf[2] >> 8) & 0xff;
+                gpu->v1.tx = gpu->buf[4] & 0xff;
+                gpu->v1.ty = (gpu->buf[4] >> 8) & 0xff;
+                gpu->v2.tx = gpu->buf[6] & 0xff;
+                gpu->v2.ty = (gpu->buf[6] >> 8) & 0xff;
+                gpu->v0.x = gpu->buf[1] & 0xffff;
+                gpu->v0.y = gpu->buf[1] >> 16;
+                gpu->v1.x = gpu->buf[3] & 0xffff;
+                gpu->v1.y = gpu->buf[3] >> 16;
+                gpu->v2.x = gpu->buf[5] & 0xffff;
+                gpu->v2.y = gpu->buf[5] >> 16;
+
+                uint16_t clutx = (gpu->pal & 0x3f) << 4;
+                uint16_t cluty = (gpu->pal >> 6) & 0x1ff;
+                uint16_t tpx = (texp & 0xf) << 6;
+                uint16_t tpy = (texp & 0x10) << 4;
+                uint16_t depth = (texp >> 7) & 0x3;
+
+                gpu_render_textured_triangle(gpu, gpu->v0, gpu->v1, gpu->v2, tpx, tpy, clutx, cluty, depth);
+                gpu_render_textured_triangle(gpu, gpu->v1, gpu->v2, gpu->v3, tpx, tpy, clutx, cluty, depth);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+// Monochrome Opaque Quadrilateral
+void gpu_cmd_2d(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 8;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                uint32_t texp = gpu->buf[4] >> 16;
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->pal   = gpu->buf[2] >> 16;
+                gpu->v0.tx = gpu->buf[2] & 0xff;
+                gpu->v0.ty = (gpu->buf[2] >> 8) & 0xff;
+                gpu->v1.tx = gpu->buf[4] & 0xff;
+                gpu->v1.ty = (gpu->buf[4] >> 8) & 0xff;
+                gpu->v2.tx = gpu->buf[6] & 0xff;
+                gpu->v2.ty = (gpu->buf[6] >> 8) & 0xff;
+                gpu->v3.tx = gpu->buf[8] & 0xff;
+                gpu->v3.ty = (gpu->buf[8] >> 8) & 0xff;
+                gpu->v0.x = gpu->buf[1] & 0xffff;
+                gpu->v0.y = gpu->buf[1] >> 16;
+                gpu->v1.x = gpu->buf[3] & 0xffff;
+                gpu->v1.y = gpu->buf[3] >> 16;
+                gpu->v2.x = gpu->buf[5] & 0xffff;
+                gpu->v2.y = gpu->buf[5] >> 16;
+                gpu->v3.x = gpu->buf[7] & 0xffff;
+                gpu->v3.y = gpu->buf[7] >> 16;
+
+                uint16_t clutx = (gpu->pal & 0x3f) << 4;
+                uint16_t cluty = (gpu->pal >> 6) & 0x1ff;
+                uint16_t tpx = (texp & 0xf) << 6;
+                uint16_t tpy = (texp & 0x10) << 4;
+                uint16_t depth = (texp >> 7) & 0x3;
+
+                gpu_render_textured_triangle(gpu, gpu->v0, gpu->v1, gpu->v2, tpx, tpy, clutx, cluty, depth);
+                gpu_render_textured_triangle(gpu, gpu->v1, gpu->v2, gpu->v3, tpx, tpy, clutx, cluty, depth);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_cmd_64(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 3;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->v0.x  = gpu->buf[1] & 0xffff;
+                gpu->v0.y  = gpu->buf[1] >> 16;
+                gpu->v0.tx = gpu->buf[2] & 0xff;
+                gpu->v0.ty = (gpu->buf[2] >> 8) & 0xff;
+                gpu->pal   = gpu->buf[2] >> 16;
+
+                uint32_t w = gpu->buf[3] & 0xffff;
+                uint32_t h = gpu->buf[3] >> 16;
+                uint16_t clutx = (gpu->pal & 0x3f) << 4;
+                uint16_t cluty = (gpu->pal >> 6) & 0x1ff;
+
+                gpu_render_textured_rectangle(gpu, gpu->v0, w, h, clutx, cluty, gpu->color);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_cmd_7c(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 2;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->v0.x  = gpu->buf[1] & 0xffff;
+                gpu->v0.y  = gpu->buf[1] >> 16;
+                gpu->v0.tx = gpu->buf[2] & 0xff;
+                gpu->v0.ty = (gpu->buf[2] >> 8) & 0xff;
+                gpu->pal   = gpu->buf[2] >> 16;
+
+                uint32_t w = 16;
+                uint32_t h = 16;
+                uint16_t clutx = (gpu->pal & 0x3f) << 4;
+                uint16_t cluty = (gpu->pal >> 6) & 0x1ff;
+
+                gpu_render_textured_rectangle(gpu, gpu->v0, w, h, clutx, cluty, gpu->color);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_cmd_74(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 2;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->v0.x  = gpu->buf[1] & 0xffff;
+                gpu->v0.y  = gpu->buf[1] >> 16;
+                gpu->v0.tx = gpu->buf[2] & 0xff;
+                gpu->v0.ty = (gpu->buf[2] >> 8) & 0xff;
+                gpu->pal   = gpu->buf[2] >> 16;
+
+                uint32_t w = 8;
+                uint32_t h = 8;
+                uint16_t clutx = (gpu->pal & 0x3f) << 4;
+                uint16_t cluty = (gpu->pal >> 6) & 0x1ff;
+
+                gpu_render_textured_rectangle(gpu, gpu->v0, w, h, clutx, cluty, gpu->color);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_cmd_60(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 2;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->v0.x  = gpu->buf[1] & 0xffff;
+                gpu->v0.y  = gpu->buf[1] >> 16;
+                gpu->xsiz  = gpu->buf[2] & 0xffff;
+                gpu->ysiz  = gpu->buf[2] >> 16;
+
+                gpu->v0.x += gpu->off_x;
+                gpu->v0.y += gpu->off_y;
+
+                gpu_render_flat_rectangle(gpu, gpu->v0, gpu->xsiz, gpu->ysiz, BGR555(gpu->color));
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_cmd_68(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 1;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->v0.x  = gpu->buf[1] & 0xffff;
+                gpu->v0.y  = gpu->buf[1] >> 16;
+
+                gpu->v0.x += gpu->off_x;
+                gpu->v0.y += gpu->off_y;
+
+                gpu->vram[gpu->v0.x + (gpu->v0.y * 1024)] = BGR555(gpu->color);
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_cmd_40(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 2;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->v0.x  = gpu->buf[1] & 0xffff;
+                gpu->v0.y  = gpu->buf[1] >> 16;
+                gpu->v1.x  = gpu->buf[2] & 0xffff;
+                gpu->v1.y  = gpu->buf[2] >> 16;
+
+                gpu_render_flat_line(gpu, gpu->v0, gpu->v1, BGR555(gpu->color));
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_cmd_02(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 2;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                gpu->color = gpu->buf[0] & 0xffffff;
+                gpu->v0.x  = gpu->buf[1] & 0xffff;
+                gpu->v0.y  = gpu->buf[1] >> 16;
+                gpu->xsiz  = gpu->buf[2] & 0xffff;
+                gpu->ysiz  = gpu->buf[2] >> 16;
+
+                gpu->v0.x = (gpu->v0.x & 0x3f0);
+                gpu->v0.y = gpu->v0.y & 0x1ff;
+                gpu->xsiz = (((gpu->xsiz & 0x3ff) + 0x0f) & 0xfffffff0);
+                gpu->ysiz = gpu->ysiz & 0x1ff;
+
+                uint16_t color = BGR555(gpu->color);
+
+                for (uint32_t y = gpu->v0.y; y < (gpu->v0.y + gpu->ysiz); y++) {
+                    for (uint32_t x = gpu->v0.x; x < (gpu->v0.x + gpu->xsiz); x++) {
+                        if ((x >= gpu->draw_x1) && (x <= gpu->draw_x2) && (y >= gpu->draw_y1) && (y <= gpu->draw_y2))
+                            VRAM(x, y) = color;
+                    }
+                }
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void gpu_cmd_80(psx_gpu_t* gpu) {
+    switch (gpu->state) {
+        case GPU_STATE_RECV_CMD: {
+            gpu->state = GPU_STATE_RECV_ARGS;
+            gpu->cmd_args_remaining = 3;
+        } break;
+
+        case GPU_STATE_RECV_ARGS: {
+            if (!gpu->cmd_args_remaining) {
+                gpu->state = GPU_STATE_RECV_DATA;
+
+                uint32_t srcx = gpu->buf[1] & 0xffff;
+                uint32_t srcy = gpu->buf[1] >> 16;
+                uint32_t dstx = gpu->buf[2] & 0xffff;
+                uint32_t dsty = gpu->buf[2] >> 16;
+                uint32_t xsiz = gpu->buf[3] & 0xffff;
+                uint32_t ysiz = gpu->buf[3] >> 16;
+
+                for (int y = 0; y < ysiz; y++) {
+                    for (int x = 0; x < xsiz; x++) {
+                        if ((x >= gpu->draw_x1) && (x <= gpu->draw_x2) && (y >= gpu->draw_y1) && (y <= gpu->draw_y2))
+                            gpu->vram[(dstx + x) + (dsty + y) * 1024] = gpu->vram[(srcx + x) + (srcy + y) * 1024];
+                    }
+                }
+
+                gpu->state = GPU_STATE_RECV_CMD;
+            }
+        } break;
+    }
+}
+
+void psx_gpu_update_cmd(psx_gpu_t* gpu) {
+    int type = (gpu->buf[0] >> 29) & 7;
+
+    switch (type) {
+        case 1: gpu_poly(gpu); return;
+        case 3: gpu_rect(gpu); return;
+        case 4: gpu_copy(gpu); return;
+        case 5: gpu_recv(gpu); return;
+        case 6: gpu_send(gpu); return;
+        default: break;
+    }
+
+    switch (gpu->buf[0] >> 24) {
+        case 0x00: /* nop */ break;
+        case 0x01: /* Cache clear */ break;
+        case 0x02: gpu_cmd_02(gpu); break;
+        // case 0x24: gpu_cmd_24(gpu); break;
+        // case 0x25: gpu_cmd_24(gpu); break;
+        // case 0x26: gpu_cmd_24(gpu); break;
+        // case 0x27: gpu_cmd_24(gpu); break;
+        // case 0x28: gpu_cmd_28(gpu); break;
+        // case 0x2a: gpu_cmd_28(gpu); break;
+        // case 0x2c: gpu_cmd_2d(gpu); break;
+        // case 0x2d: gpu_cmd_2d(gpu); break;
+        // case 0x2e: gpu_cmd_2d(gpu); break;
+        // case 0x2f: gpu_cmd_2d(gpu); break;
+        // case 0x30: gpu_cmd_30(gpu); break;
+        // case 0x32: gpu_cmd_30(gpu); break;
+        // case 0x38: gpu_cmd_38(gpu); break;
+        // case 0x3c: gpu_cmd_3c(gpu); break;
+        // case 0x3e: gpu_cmd_3c(gpu); break;
+        case 0x40: gpu_cmd_40(gpu); break;
+        // case 0x60: gpu_cmd_60(gpu); break;
+        // case 0x62: gpu_cmd_60(gpu); break;
+        // case 0x64: gpu_cmd_64(gpu); break;
+        // case 0x65: gpu_cmd_64(gpu); break;
+        // case 0x66: gpu_cmd_64(gpu); break;
+        // case 0x67: gpu_cmd_64(gpu); break;
+        // case 0x68: gpu_cmd_68(gpu); break;
+        // case 0x74: gpu_cmd_74(gpu); break;
+        // case 0x75: gpu_cmd_74(gpu); break;
+        // case 0x76: gpu_cmd_74(gpu); break;
+        // case 0x77: gpu_cmd_74(gpu); break;
+        // case 0x7c: gpu_cmd_7c(gpu); break;
+        // case 0x7d: gpu_cmd_7c(gpu); break;
+        // case 0x7e: gpu_cmd_7c(gpu); break;
+        // case 0x7f: gpu_cmd_7c(gpu); break;
+        // case 0x80: gpu_cmd_80(gpu); break;
+        // case 0xa0: gpu_cmd_a0(gpu); break;
+        // case 0xc0: gpu_cmd_c0(gpu); break;
+        case 0xe1: {
+            gpu->gpustat &= 0xfffff800;
+            gpu->gpustat |= gpu->buf[0] & 0x7ff;
+            gpu->texp_x = (gpu->gpustat & 0xf) << 6;
+            gpu->texp_y = (gpu->gpustat & 0x10) << 4;
+            gpu->texp_d = (gpu->gpustat >> 7) & 0x3;
+        } break;
+        case 0xe2: {
+            gpu->texw_mx = (gpu->buf[0] >> 0 ) & 0x1f;
+            gpu->texw_my = (gpu->buf[0] >> 5 ) & 0x1f;
+            gpu->texw_ox = (gpu->buf[0] >> 10) & 0x1f;
+            gpu->texw_oy = (gpu->buf[0] >> 15) & 0x1f;
+        } break;
+        case 0xe3: {
+            gpu->draw_x1 = (gpu->buf[0] >> 0 ) & 0x3ff;
+            gpu->draw_y1 = (gpu->buf[0] >> 10) & 0x1ff;
+        } break;
+        case 0xe4: {
+            gpu->draw_x2 = (gpu->buf[0] >> 0 ) & 0x3ff;
+            gpu->draw_y2 = (gpu->buf[0] >> 10) & 0x1ff;
+        } break;
+        case 0xe5: {
+            gpu->off_x = (gpu->buf[0] >> 0 ) & 0x7ff;
+            gpu->off_y = (gpu->buf[0] >> 11) & 0x7ff;
+        } break;
+        case 0xe6: {
+            /* To-do: Implement mask bit thing */
+        } break;
+        default: {
+            // log_set_quiet(0);
+            // log_fatal("Unhandled GP0(%02Xh)", gpu->buf[0] >> 24);
+            log_set_quiet(1);
+
+            // exit(1);
+        } break;
+    }
+}
+
+void psx_gpu_write32(psx_gpu_t* gpu, uint32_t offset, uint32_t value) {
+    switch (offset) {
+        // GP0
+        case 0x00: {
+            switch (gpu->state) {
+                case GPU_STATE_RECV_CMD: {
+                    gpu->buf_index = 0;
+                    gpu->buf[gpu->buf_index++] = value;
+
+                    psx_gpu_update_cmd(gpu);
+                } break;
+
+                case GPU_STATE_RECV_ARGS: {
+                    gpu->buf[gpu->buf_index++] = value;
+                    gpu->cmd_args_remaining--;
+
+                    psx_gpu_update_cmd(gpu);
+                } break;
+
+                case GPU_STATE_RECV_DATA: {
+                    gpu->recv_data = value;
+
+                    psx_gpu_update_cmd(gpu);
+                } break;
+            }
+
+            return;
+        } break;
+
+        // GP1
+        case 0x04: {
+            uint8_t cmd = value >> 24;
+
+            switch (cmd) {
+                case 0x00: {
+                    gpu->gpustat = 0x14802000;
+
+                    /*
+                        GP1(01h)      ;clear fifo
+                        GP1(02h)      ;ack irq (0)
+                        GP1(03h)      ;display off (1)
+                        GP1(04h)      ;dma off (0)
+                        GP1(05h)      ;display address (0)
+                        GP1(06h)      ;display x1,x2 (x1=200h, x2=200h+256*10)
+                        GP1(07h)      ;display y1,y2 (y1=010h, y2=010h+240)
+                        GP1(08h)      ;display mode 320x200 NTSC (0)
+                        GP0(E1h..E6h) ;rendering attributes (0)
+                    */
+
+                    gpu->disp_x1 = 0x200;
+                    gpu->disp_x2 = 0xc00;
+                    gpu->disp_y1 = 0x010;
+                    gpu->disp_y2 = 0x100;
+                    gpu->display_mode = 0;
+
+                    gpu->disp_x = 0;
+                    gpu->disp_y = 0;
+
+                    if (gpu->event_cb_table[GPU_EVENT_DMODE])
+                        gpu->event_cb_table[GPU_EVENT_DMODE](gpu);
+                } break;
+                case 0x04: {
+                } break;
+                case 0x05: {
+                    gpu->disp_x = value & 0x3ff;
+                    gpu->disp_y = (value >> 10) & 0x1ff;
+                } break;
+                case 0x06: {
+                    gpu->disp_x1 = value & 0xfff;
+                    gpu->disp_x2 = (value >> 12) & 0xfff;
+                } break;
+                case 0x08:
+                    gpu->display_mode = value & 0xffffff;
+
+                    if (gpu->event_cb_table[GPU_EVENT_DMODE])
+                        gpu->event_cb_table[GPU_EVENT_DMODE](gpu);
+                break;
+
+                case 0x10: {
+                    gpu->gp1_10h_req = value & 7;
+                } break;
+            }
+
+            log_error("GP1(%02Xh) args=%06x", value >> 24, value & 0xffffff);
+
+            return;
+        } break;
+    }
+
+    log_warn("Unhandled 32-bit GPU write at offset %08x (%08x)", offset, value);
+}
+
+void psx_gpu_write16(psx_gpu_t* gpu, uint32_t offset, uint16_t value) {
+    log_warn("Unhandled 16-bit GPU write at offset %08x (%04x)", offset, value);
+}
+
+void psx_gpu_write8(psx_gpu_t* gpu, uint32_t offset, uint8_t value) {
+    log_warn("Unhandled 8-bit GPU write at offset %08x (%02x)", offset, value);
+}
+
+void psx_gpu_set_event_callback(psx_gpu_t* gpu, int event, psx_gpu_event_callback_t cb) {
+    gpu->event_cb_table[event] = cb;
+}
+
+void psx_gpu_set_udata(psx_gpu_t* gpu, int index, void* udata) {
+    gpu->udata[index] = udata;
+}
+
+#define GPU_CYCLES_PER_HDRAW_NTSC 2560.0f
+#define GPU_CYCLES_PER_SCANL_NTSC 3413.0f
+#define GPU_SCANS_PER_VDRAW_NTSC 240
+#define GPU_SCANS_PER_FRAME_NTSC 263
+#define GPU_CYCLES_PER_SCANL_PAL 3406.0f
+#define GPU_SCANS_PER_FRAME_PAL  314
+
+void gpu_hblank_event(psx_gpu_t* gpu) {
+    gpu->line++;
+
+    if (gpu->line < GPU_SCANS_PER_VDRAW_NTSC) {
+        if (gpu->line & 1) {
+            gpu->gpustat |= 1 << 31;
+        } else {
+            gpu->gpustat &= ~(1 << 31);
+        }
+    } else {
+        gpu->gpustat &= ~(1 << 31);
+    }
+
+    if (gpu->line == GPU_SCANS_PER_VDRAW_NTSC) {
+        if (gpu->event_cb_table[GPU_EVENT_VBLANK])
+            gpu->event_cb_table[GPU_EVENT_VBLANK](gpu);
+
+        psx_ic_irq(gpu->ic, IC_VBLANK);
+    } else if (gpu->line == GPU_SCANS_PER_FRAME_NTSC) {
+        if (gpu->event_cb_table[GPU_EVENT_VBLANK_END])
+            gpu->event_cb_table[GPU_EVENT_VBLANK_END](gpu);
+
+        // psx_ic_irq(gpu->ic, IC_SPU);
+
+        gpu->line = 0;
+    }
+}
+
+void psx_gpu_update(psx_gpu_t* gpu, int cyc) {
+    int prev_hblank = (gpu->cycles >= GPU_CYCLES_PER_HDRAW_NTSC) &&
+                      (gpu->cycles <= GPU_CYCLES_PER_SCANL_NTSC);
+
+    // Convert CPU (~33.8 MHz) cycles to GPU (~53.7 MHz) cycles
+    // gpu->cycles += (float)cyc * (PSX_GPU_CLOCK_FREQ_NTSC / PSX_CPU_FREQ);
+    gpu->cycles += (float)cyc * (11.0f / 7.0f);
+
+    int curr_hblank = (gpu->cycles >= GPU_CYCLES_PER_HDRAW_NTSC) &&
+                      (gpu->cycles <= GPU_CYCLES_PER_SCANL_NTSC);
+    
+    if (curr_hblank && !prev_hblank) {
+        if (gpu->event_cb_table[GPU_EVENT_HBLANK])
+            gpu->event_cb_table[GPU_EVENT_HBLANK](gpu);
+
+        gpu_hblank_event(gpu);
+    } else if (prev_hblank && !curr_hblank) {
+        if (gpu->event_cb_table[GPU_EVENT_HBLANK_END])
+            gpu->event_cb_table[GPU_EVENT_HBLANK_END](gpu);
+        
+        gpu->cycles -= (float)GPU_CYCLES_PER_SCANL_NTSC;
+    }
+}
+
+void* psx_gpu_get_display_buffer(psx_gpu_t* gpu) {
+    return gpu->vram + (gpu->disp_x + (gpu->disp_y * 1024));
+}
+
+void psx_gpu_destroy(psx_gpu_t* gpu) {
+    free(gpu->vram);
+    free(gpu);
+}
\ No newline at end of file
--- a/psx/dev/spu.c
+++ b/psx/dev/spu.c
@@ -427,6 +427,7 @@
 }
 
 void psx_spu_destroy(psx_spu_t* spu) {
+    printf("psx_spu_destroy called\n");
     free(spu->ram);
     free(spu);
 }
@@ -565,16 +566,21 @@
                     spu->data[v].current_addr = spu->data[v].repeat_addr;
                     spu->data[v].playing = 0;
                     spu->voice[v].envcvol = 0;
+
+                    adsr_load_release(spu, v);
                 } break;
 
                 case 3: {
                     spu->endx |= (1 << v);
                     spu->data[v].current_addr = spu->data[v].repeat_addr;
+
+                    adsr_load_release(spu, v);
                 } break;
             }
 
-            if (spu->data[v].block_flags & 4)
+            if (spu->data[v].block_flags & 4) {
                 spu->data[v].repeat_addr = spu->data[v].current_addr;
+            }
 
             spu_read_block(spu, v);
         }
--- a/psx/dev/timer.c
+++ b/psx/dev/timer.c
@@ -49,8 +49,6 @@
     int index = offset >> 4;
     int reg = offset & 0xf;
 
-    //log_fatal("Timer %u %s read32", index, g_psx_timer_reg_names[reg]);
-
     switch (reg) {
         case 0: return timer->timer[index].counter;
         case 4: {
@@ -63,7 +61,7 @@
 
     log_fatal("Unhandled 32-bit TIMER read at offset %08x", offset);
 
-    exit(1);
+    // exit(1);
 
     return 0x0;
 }
@@ -72,8 +70,6 @@
     int index = offset >> 4;
     int reg = offset & 0xf;
 
-    //log_fatal("Timer %u %s read16 %04x", index, g_psx_timer_reg_names[reg], timer->timer[index].counter);
-
     switch (reg) {
         case 0: return timer->timer[index].counter;
         case 4: {
@@ -86,8 +82,6 @@
 
     log_fatal("Unhandled 16-bit TIMER read at offset %08x", offset);
 
-    exit(1);
-    
     return 0x0;
 }
 
@@ -94,8 +88,6 @@
 uint8_t psx_timer_read8(psx_timer_t* timer, uint32_t offset) {
     log_fatal("Unhandled 8-bit TIMER read at offset %08x", offset);
 
-    exit(1);
-
     return 0x0;
 }
 
@@ -103,8 +95,6 @@
     int index = offset >> 4;
     int reg = offset & 0xf;
 
-    //log_fatal("Timer %u %s write32 %08x", index, g_psx_timer_reg_names[reg], value);
-
     switch (reg) {
         case 0: {
             timer->timer[index].counter = value;
@@ -113,6 +103,7 @@
             timer->timer[index].mode = value;
             timer->timer[index].mode |= 0x400;
             timer->timer[index].irq_fired = 0;
+            timer->timer[index].counter = 0;
         } return;
         case 8: timer->timer[index].target = value; return;
     }
@@ -119,17 +110,13 @@
 
     log_fatal("Unhandled 32-bit TIMER write at offset %08x (%02x)", offset, value);
 
-    exit(1);
+    // exit(1);
 }
 
 void psx_timer_write16(psx_timer_t* timer, uint32_t offset, uint16_t value) {
-    return;
-
     int index = offset >> 4;
     int reg = offset & 0xf;
 
-    log_fatal("Timer %u %s write16 %04x", index, g_psx_timer_reg_names[reg], value);
-
     switch (reg) {
         case 0: {
             timer->timer[index].counter = value;
@@ -138,6 +125,7 @@
             timer->timer[index].mode = value;
             timer->timer[index].mode |= 0x400;
             timer->timer[index].irq_fired = 0;
+            timer->timer[index].counter = 0;
         } return;
         case 8: {
             timer->timer[index].target = value;
@@ -146,13 +134,11 @@
 
     log_fatal("Unhandled 16-bit TIMER write at offset %08x (%02x)", offset, value);
 
-    exit(1);
+    // exit(1);
 }
 
 void psx_timer_write8(psx_timer_t* timer, uint32_t offset, uint8_t value) {
     log_fatal("Unhandled 8-bit TIMER write at offset %08x (%02x)", offset, value);
-
-    exit(1);
 }
 
 void timer_update_timer0(psx_timer_t* timer, int cyc) {
@@ -226,7 +212,7 @@
             T1_MODE |= 0x400;
         }
 
-        timer->timer[0].irq_fired = 1;
+        T1_IRQ_FIRED = 1;
 
         psx_ic_irq(timer->ic, IC_TIMER1);
     }
--- a/psx/msf.c
+++ b/psx/msf.c
@@ -1,4 +1,5 @@
 #include "msf.h"
+#include "log.h"
 
 uint8_t msf_btoi(uint8_t b) {
     return ((b >> 4) * 10) + (b & 0xf);
--- a/psx/psx.c
+++ b/psx/psx.c
@@ -26,7 +26,10 @@
 
 void psx_update(psx_t* psx) {
     psx_cpu_cycle(psx->cpu);
-    psx_cdrom_update(psx->cdrom, 22);
+
+    psx->cpu->last_cycles = 2;
+
+    psx_cdrom_update(psx->cdrom, 2);
     psx_gpu_update(psx->gpu, psx->cpu->last_cycles);
     psx_pad_update(psx->pad, psx->cpu->last_cycles);
     psx_timer_update(psx->timer, psx->cpu->last_cycles);
@@ -53,6 +56,18 @@
 }
 
 uint32_t psx_get_display_width(psx_t* psx) {
+    return psx->gpu->draw_x2 - psx->gpu->draw_x1;
+}
+
+uint32_t psx_get_display_height(psx_t* psx) {
+    return psx->gpu->draw_y2 - psx->gpu->draw_y1;
+}
+
+uint32_t psx_get_display_format(psx_t* psx) {
+    return (psx->gpu->display_mode >> 4) & 1;
+}
+
+uint32_t psx_get_dmode_width(psx_t* psx) {
     static int dmode_hres_table[] = {
         256, 320, 512, 640
     };
@@ -64,12 +79,19 @@
     }
 }
 
-uint32_t psx_get_display_height(psx_t* psx) {
+uint32_t psx_get_dmode_height(psx_t* psx) {
     return (psx->gpu->display_mode & 0x4) ? 480 : 240;
 }
 
-uint32_t psx_get_display_format(psx_t* psx) {
-    return (psx->gpu->display_mode >> 4) & 1;
+double psx_get_display_aspect(psx_t* psx) {
+    double width = psx_get_dmode_width(psx);
+    double height = psx_get_dmode_height(psx);
+    double aspect = width / height;
+
+    if (aspect > (4.0 / 3.0))
+        return 4.0 / 3.0;
+
+    return aspect;
 }
 
 void psx_init(psx_t* psx, const char* bios_path) {
--- a/psx/psx.h
+++ b/psx/psx.h
@@ -39,9 +39,12 @@
 void psx_run_frame(psx_t*);
 void* psx_get_display_buffer(psx_t*);
 void* psx_get_vram(psx_t*);
+uint32_t psx_get_dmode_width(psx_t*);
+uint32_t psx_get_dmode_height(psx_t*);
 uint32_t psx_get_display_width(psx_t*);
 uint32_t psx_get_display_height(psx_t*);
 uint32_t psx_get_display_format(psx_t*);
+double psx_get_display_aspect(psx_t*);
 uint32_t* psx_take_screenshot(psx_t*);
 psx_bios_t* psx_get_bios(psx_t*);
 psx_ram_t* psx_get_ram(psx_t*);
--